paft_fundamentals/
profile.rs

1//! Profile-related types under `paft_fundamentals::fundamentals::profile`.
2
3use serde::{Deserialize, Serialize};
4use std::str::FromStr;
5use strum::{AsRefStr, Display, EnumString};
6
7use chrono::{DateTime, Utc};
8#[cfg(feature = "dataframe")]
9use df_derive::ToDataFrame;
10#[cfg(feature = "dataframe")]
11use paft_core::dataframe::ToDataFrame;
12
13/// Fund types with canonical variants and extensible fallback.
14///
15/// This enum provides type-safe handling of fund types while gracefully
16/// handling unknown or provider-specific fund types through the `Other` variant.
17#[derive(
18    Debug,
19    Clone,
20    PartialEq,
21    Eq,
22    Hash,
23    Serialize,
24    Deserialize,
25    Display,
26    AsRefStr,
27    EnumString,
28    Default,
29)]
30#[strum(ascii_case_insensitive)]
31#[serde(from = "String", into = "String")]
32pub enum FundKind {
33    /// Exchange-Traded Fund
34    #[strum(to_string = "ETF", serialize = "EXCHANGE_TRADED_FUND")]
35    #[default]
36    Etf,
37    /// Mutual Fund
38    #[strum(to_string = "MUTUAL_FUND", serialize = "MUTUAL")]
39    MutualFund,
40    /// Index Fund
41    #[strum(to_string = "INDEX_FUND", serialize = "INDEX")]
42    IndexFund,
43    /// Closed-End Fund
44    #[strum(to_string = "CLOSED_END_FUND", serialize = "CEF")]
45    ClosedEndFund,
46    /// Money Market Fund
47    #[strum(to_string = "MONEY_MARKET_FUND", serialize = "MMF")]
48    MoneyMarketFund,
49    /// Hedge Fund
50    #[strum(to_string = "HEDGE_FUND")]
51    HedgeFund,
52    /// Real Estate Investment Trust
53    #[strum(to_string = "REIT", serialize = "REAL_ESTATE_INVESTMENT_TRUST")]
54    Reit,
55    /// Unit Investment Trust
56    #[strum(to_string = "UIT", serialize = "UNIT_INVESTMENT_TRUST")]
57    UnitInvestmentTrust,
58    /// Unknown or provider-specific fund type
59    Other(String),
60}
61
62impl From<String> for FundKind {
63    fn from(s: String) -> Self {
64        // Try to parse as a known variant first
65        Self::from_str(&s).unwrap_or_else(|_| Self::Other(s.to_uppercase()))
66    }
67}
68
69impl From<FundKind> for String {
70    fn from(fund_kind: FundKind) -> Self {
71        match fund_kind {
72            FundKind::Other(s) => s,
73            _ => fund_kind.to_string(),
74        }
75    }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
79#[cfg_attr(feature = "dataframe", derive(ToDataFrame))]
80/// Postal address details.
81pub struct Address {
82    /// First address line.
83    pub street1: Option<String>,
84    /// Second address line.
85    pub street2: Option<String>,
86    /// City or locality.
87    pub city: Option<String>,
88    /// State or region.
89    pub state: Option<String>,
90    /// Country.
91    pub country: Option<String>,
92    /// Postal or ZIP code.
93    pub zip: Option<String>,
94}
95
96/// Company profile details (provider-agnostic; maps well to common models).
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98#[cfg_attr(feature = "dataframe", derive(ToDataFrame))]
99pub struct CompanyProfile {
100    /// Company display name.
101    pub name: String,
102    /// Sector classification.
103    pub sector: Option<String>,
104    /// Industry classification.
105    pub industry: Option<String>,
106    /// Company website.
107    pub website: Option<String>,
108    /// Registered address.
109    pub address: Option<Address>,
110    /// Business summary.
111    pub summary: Option<String>,
112    /// International Securities Identification Number.
113    pub isin: Option<String>,
114}
115
116/// Fund profile details.
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118#[cfg_attr(feature = "dataframe", derive(ToDataFrame))]
119pub struct FundProfile {
120    /// Fund name.
121    pub name: String,
122    /// Fund family (e.g., Vanguard, iShares).
123    pub family: Option<String>,
124    /// Fund type with canonical variants and extensible fallback.
125    #[cfg_attr(feature = "dataframe", df_derive(as_string))]
126    pub kind: FundKind,
127    /// International Securities Identification Number.
128    pub isin: Option<String>,
129}
130
131/// Union of supported profile kinds.
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub enum Profile {
134    /// Company profile.
135    Company(CompanyProfile),
136    /// Fund profile.
137    Fund(FundProfile),
138}
139
140impl Profile {
141    /// Returns the ISIN for the company or fund, if available.
142    #[must_use]
143    pub fn isin(&self) -> Option<&str> {
144        match self {
145            Self::Company(c) => c.isin.as_deref(),
146            Self::Fund(f) => f.isin.as_deref(),
147        }
148    }
149}
150
151/// Represents a single data point in a time series of shares outstanding.
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153#[cfg_attr(feature = "dataframe", derive(ToDataFrame))]
154pub struct ShareCount {
155    /// The timestamp for the data point.
156    #[serde(with = "chrono::serde::ts_seconds")]
157    pub date: DateTime<Utc>,
158    /// The number of shares outstanding.
159    pub shares: u64,
160}