finance_query_core/models/
sectors.rs

1use serde::{Deserialize, Serialize};
2use std::str::FromStr;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
6pub enum Sector {
7    #[serde(rename = "Basic Materials")]
8    BasicMaterials,
9    #[serde(rename = "Communication Services")]
10    Communication,
11    #[serde(rename = "Consumer Cyclical")]
12    ConsumerCyclical,
13    #[serde(rename = "Consumer Defensive")]
14    ConsumerDefensive,
15    Energy,
16    #[serde(rename = "Financial Services")]
17    FinancialServices,
18    Healthcare,
19    Industrials,
20    #[serde(rename = "Real Estate")]
21    RealEstate,
22    Technology,
23    Utilities,
24}
25
26impl Sector {
27    pub fn as_str(&self) -> &'static str {
28        match self {
29            Sector::BasicMaterials => "Basic Materials",
30            Sector::Communication => "Communication Services",
31            Sector::ConsumerCyclical => "Consumer Cyclical",
32            Sector::ConsumerDefensive => "Consumer Defensive",
33            Sector::Energy => "Energy",
34            Sector::FinancialServices => "Financial Services",
35            Sector::Healthcare => "Healthcare",
36            Sector::Industrials => "Industrials",
37            Sector::RealEstate => "Real Estate",
38            Sector::Technology => "Technology",
39            Sector::Utilities => "Utilities",
40        }
41    }
42
43    #[allow(dead_code)]
44    pub fn url_path(&self) -> &'static str {
45        match self {
46            Sector::BasicMaterials => "basic-materials",
47            Sector::Communication => "communication-services",
48            Sector::ConsumerCyclical => "consumer-cyclical",
49            Sector::ConsumerDefensive => "consumer-defensive",
50            Sector::Energy => "energy",
51            Sector::FinancialServices => "financial-services",
52            Sector::Healthcare => "healthcare",
53            Sector::Industrials => "industrials",
54            Sector::RealEstate => "real-estate",
55            Sector::Technology => "technology",
56            Sector::Utilities => "utilities",
57        }
58    }
59
60
61    #[allow(dead_code)]
62    pub fn all() -> Vec<Sector> {
63        vec![
64            Sector::Technology,
65            Sector::Healthcare,
66            Sector::FinancialServices,
67            Sector::ConsumerCyclical,
68            Sector::Industrials,
69            Sector::ConsumerDefensive,
70            Sector::Energy,
71            Sector::RealEstate,
72            Sector::Utilities,
73            Sector::BasicMaterials,
74            Sector::Communication,
75        ]
76    }
77}
78
79impl FromStr for Sector {
80    type Err = String;
81
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        match s {
84            "Basic Materials" => Ok(Sector::BasicMaterials),
85            "Communication Services" => Ok(Sector::Communication),
86            "Consumer Cyclical" => Ok(Sector::ConsumerCyclical),
87            "Consumer Defensive" => Ok(Sector::ConsumerDefensive),
88            "Energy" => Ok(Sector::Energy),
89            "Financial Services" => Ok(Sector::FinancialServices),
90            "Healthcare" => Ok(Sector::Healthcare),
91            "Industrials" => Ok(Sector::Industrials),
92            "Real Estate" => Ok(Sector::RealEstate),
93            "Technology" => Ok(Sector::Technology),
94            "Utilities" => Ok(Sector::Utilities),
95            _ => Err(format!("Invalid sector: {}", s)),
96        }
97    }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct MarketSector {
102    pub sector: String,
103    #[serde(rename = "dayReturn")]
104    pub day_return: String,
105    #[serde(rename = "ytdReturn")]
106    pub ytd_return: String,
107    #[serde(rename = "yearReturn")]
108    pub year_return: String,
109    #[serde(rename = "threeYearReturn")]
110    pub three_year_return: String,
111    #[serde(rename = "fiveYearReturn")]
112    pub five_year_return: String,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct MarketSectorDetails {
117    pub sector: String,
118    #[serde(rename = "dayReturn")]
119    pub day_return: String,
120    #[serde(rename = "ytdReturn")]
121    pub ytd_return: String,
122    #[serde(rename = "yearReturn")]
123    pub year_return: String,
124    #[serde(rename = "threeYearReturn")]
125    pub three_year_return: String,
126    #[serde(rename = "fiveYearReturn")]
127    pub five_year_return: String,
128    #[serde(rename = "marketCap")]
129    pub market_cap: String,
130    #[serde(rename = "marketWeight")]
131    pub market_weight: String,
132    pub industries: i32,
133    pub companies: i32,
134    #[serde(rename = "topIndustries")]
135    pub top_industries: Vec<String>,
136    #[serde(rename = "topCompanies")]
137    pub top_companies: Vec<String>,
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use proptest::prelude::*;
144
145    // **Feature: crate-extraction, Property 1: Model Serialization Round-Trip**
146    // **Validates: Requirements 2.2**
147    proptest! {
148        #![proptest_config(ProptestConfig::with_cases(100))]
149
150        #[test]
151        fn market_sector_roundtrip(
152            sector in "[A-Za-z ]{1,30}",
153            day_return in "-?[0-9]{1,3}\\.[0-9]{2}%",
154            ytd_return in "-?[0-9]{1,3}\\.[0-9]{2}%",
155            year_return in "-?[0-9]{1,3}\\.[0-9]{2}%",
156            three_year_return in "-?[0-9]{1,3}\\.[0-9]{2}%",
157            five_year_return in "-?[0-9]{1,3}\\.[0-9]{2}%",
158        ) {
159            let ms = MarketSector {
160                sector: sector.clone(),
161                day_return: day_return.clone(),
162                ytd_return: ytd_return.clone(),
163                year_return: year_return.clone(),
164                three_year_return: three_year_return.clone(),
165                five_year_return: five_year_return.clone(),
166            };
167
168            let json = serde_json::to_string(&ms).unwrap();
169            let parsed: MarketSector = serde_json::from_str(&json).unwrap();
170
171            prop_assert_eq!(ms.sector, parsed.sector);
172            prop_assert_eq!(ms.day_return, parsed.day_return);
173            prop_assert_eq!(ms.ytd_return, parsed.ytd_return);
174            prop_assert_eq!(ms.year_return, parsed.year_return);
175        }
176
177        #[test]
178        fn sector_roundtrip(sector in prop_oneof![
179            Just(Sector::BasicMaterials),
180            Just(Sector::Communication),
181            Just(Sector::ConsumerCyclical),
182            Just(Sector::ConsumerDefensive),
183            Just(Sector::Energy),
184            Just(Sector::FinancialServices),
185            Just(Sector::Healthcare),
186            Just(Sector::Industrials),
187            Just(Sector::RealEstate),
188            Just(Sector::Technology),
189            Just(Sector::Utilities),
190        ]) {
191            let json = serde_json::to_string(&sector).unwrap();
192            let parsed: Sector = serde_json::from_str(&json).unwrap();
193
194            prop_assert_eq!(sector.as_str(), parsed.as_str());
195        }
196    }
197}