use crate::models::quote::FormattedValue;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)]
struct RawSectorResponse {
data: RawSectorData,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawSectorData {
name: String,
symbol: Option<String>,
key: String,
overview: Option<RawOverview>,
performance: Option<RawPerformance>,
#[serde(default)]
performance_overview_benchmark: Option<RawBenchmarkPerformance>,
#[serde(default)]
top_companies: Vec<RawCompany>,
#[serde(default, rename = "topETFs")]
top_etfs: Vec<RawETF>,
#[serde(default)]
top_mutual_funds: Vec<RawMutualFund>,
#[serde(default)]
industries: Vec<RawIndustry>,
#[serde(default)]
research_reports: Vec<RawResearchReport>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawOverview {
companies_count: Option<i64>,
market_cap: Option<FormattedValue<f64>>,
description: Option<String>,
industries_count: Option<i64>,
market_weight: Option<FormattedValue<f64>>,
employee_count: Option<FormattedValue<i64>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawPerformance {
ytd_change_percent: Option<FormattedValue<f64>>,
reg_market_change_percent: Option<FormattedValue<f64>>,
three_year_change_percent: Option<FormattedValue<f64>>,
one_year_change_percent: Option<FormattedValue<f64>>,
five_year_change_percent: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawBenchmarkPerformance {
name: Option<String>,
ytd_change_percent: Option<FormattedValue<f64>>,
reg_market_change_percent: Option<FormattedValue<f64>>,
three_year_change_percent: Option<FormattedValue<f64>>,
one_year_change_percent: Option<FormattedValue<f64>>,
five_year_change_percent: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawCompany {
symbol: String,
name: Option<String>,
market_cap: Option<FormattedValue<f64>>,
market_weight: Option<FormattedValue<f64>>,
last_price: Option<FormattedValue<f64>>,
target_price: Option<FormattedValue<f64>>,
reg_market_change_percent: Option<FormattedValue<f64>>,
ytd_return: Option<FormattedValue<f64>>,
rating: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawETF {
symbol: String,
name: Option<String>,
net_assets: Option<FormattedValue<f64>>,
expense_ratio: Option<FormattedValue<f64>>,
last_price: Option<FormattedValue<f64>>,
ytd_return: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawMutualFund {
symbol: String,
name: Option<String>,
net_assets: Option<FormattedValue<f64>>,
expense_ratio: Option<FormattedValue<f64>>,
last_price: Option<FormattedValue<f64>>,
ytd_return: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawIndustry {
symbol: Option<String>,
key: Option<String>,
name: String,
market_weight: Option<FormattedValue<f64>>,
reg_market_change_percent: Option<FormattedValue<f64>>,
ytd_return: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawResearchReport {
id: String,
head_html: Option<String>,
provider: Option<String>,
report_date: Option<String>,
report_title: Option<String>,
report_type: Option<String>,
target_price: Option<f64>,
target_price_status: Option<String>,
investment_rating: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SectorData {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
pub key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub overview: Option<SectorOverview>,
#[serde(skip_serializing_if = "Option::is_none")]
pub performance: Option<SectorPerformance>,
#[serde(skip_serializing_if = "Option::is_none")]
pub benchmark: Option<SectorPerformance>,
#[serde(skip_serializing_if = "Option::is_none")]
pub benchmark_name: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub top_companies: Vec<SectorCompany>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub top_etfs: Vec<SectorETF>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub top_mutual_funds: Vec<SectorMutualFund>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub industries: Vec<SectorIndustry>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub research_reports: Vec<ResearchReport>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SectorOverview {
#[serde(skip_serializing_if = "Option::is_none")]
pub companies_count: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub market_cap: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub industries_count: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub market_weight: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub employee_count: Option<FormattedValue<i64>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SectorPerformance {
#[serde(skip_serializing_if = "Option::is_none")]
pub ytd_change_percent: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub day_change_percent: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub one_year_change_percent: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub three_year_change_percent: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub five_year_change_percent: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SectorCompany {
pub symbol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub market_cap: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub market_weight: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_price: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_price: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub day_change_percent: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ytd_return: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rating: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SectorETF {
pub symbol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub net_assets: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expense_ratio: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_price: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ytd_return: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SectorMutualFund {
pub symbol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub net_assets: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expense_ratio: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_price: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ytd_return: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SectorIndustry {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub market_weight: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub day_change_percent: Option<FormattedValue<f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ytd_return: Option<FormattedValue<f64>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "dataframe", derive(crate::ToDataFrame))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ResearchReport {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub headline: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub report_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub report_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub report_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_price: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_price_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub investment_rating: Option<String>,
}
impl SectorData {
pub(crate) fn from_response(json: &serde_json::Value) -> Result<Self, String> {
let raw: RawSectorResponse = serde_json::from_value(json.clone())
.map_err(|e| format!("Failed to parse sector response: {}", e))?;
let data = raw.data;
let overview = data.overview.map(|o| SectorOverview {
companies_count: o.companies_count,
market_cap: o.market_cap,
description: o.description,
industries_count: o.industries_count,
market_weight: o.market_weight,
employee_count: o.employee_count,
});
let performance = data.performance.map(|p| SectorPerformance {
ytd_change_percent: p.ytd_change_percent,
day_change_percent: p.reg_market_change_percent,
one_year_change_percent: p.one_year_change_percent,
three_year_change_percent: p.three_year_change_percent,
five_year_change_percent: p.five_year_change_percent,
});
let (benchmark, benchmark_name) = match data.performance_overview_benchmark {
Some(b) => (
Some(SectorPerformance {
ytd_change_percent: b.ytd_change_percent,
day_change_percent: b.reg_market_change_percent,
one_year_change_percent: b.one_year_change_percent,
three_year_change_percent: b.three_year_change_percent,
five_year_change_percent: b.five_year_change_percent,
}),
b.name,
),
None => (None, None),
};
let top_companies = data
.top_companies
.into_iter()
.map(|c| SectorCompany {
symbol: c.symbol,
name: c.name,
market_cap: c.market_cap,
market_weight: c.market_weight,
last_price: c.last_price,
target_price: c.target_price,
day_change_percent: c.reg_market_change_percent,
ytd_return: c.ytd_return,
rating: c.rating,
})
.collect();
let top_etfs = data
.top_etfs
.into_iter()
.map(|e| SectorETF {
symbol: e.symbol,
name: e.name,
net_assets: e.net_assets,
expense_ratio: e.expense_ratio,
last_price: e.last_price,
ytd_return: e.ytd_return,
})
.collect();
let top_mutual_funds = data
.top_mutual_funds
.into_iter()
.map(|f| SectorMutualFund {
symbol: f.symbol,
name: f.name,
net_assets: f.net_assets,
expense_ratio: f.expense_ratio,
last_price: f.last_price,
ytd_return: f.ytd_return,
})
.collect();
let industries = data
.industries
.into_iter()
.map(|i| SectorIndustry {
symbol: i.symbol,
key: i.key,
name: i.name,
market_weight: i.market_weight,
day_change_percent: i.reg_market_change_percent,
ytd_return: i.ytd_return,
})
.collect();
let research_reports = data
.research_reports
.into_iter()
.map(|r| ResearchReport {
id: r.id,
headline: r.head_html,
provider: r.provider,
report_date: r.report_date,
report_title: r.report_title,
report_type: r.report_type,
target_price: r.target_price,
target_price_status: r.target_price_status,
investment_rating: r.investment_rating,
})
.collect();
Ok(Self {
name: data.name,
symbol: data.symbol,
key: data.key,
overview,
performance,
benchmark,
benchmark_name,
top_companies,
top_etfs,
top_mutual_funds,
industries,
research_reports,
})
}
}