use serde::Deserialize; use crate::{ error::AppError, fh_client::FinnhubClient, mapping::{map_financial_dtos, map_profile_dto}, }; use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto}; use common_contracts::provider::DataProvider; use tokio; #[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct FinnhubProfile { pub country: Option, pub currency: Option, pub exchange: Option, pub name: Option, pub ticker: Option, pub ipo: Option, pub market_capitalization: Option, pub share_outstanding: Option, pub logo: Option, pub phone: Option, pub weburl: Option, pub finnhub_industry: Option, } #[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct FinnhubFinancialsReported { pub data: Vec, pub symbol: String, } #[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct AnnualReport { pub year: u16, pub start_date: String, pub end_date: String, pub report: Report, } #[derive(Debug, Deserialize, Clone)] pub struct Report { pub bs: Vec, pub ic: Vec, pub cf: Vec, } #[derive(Debug, Deserialize, Clone)] pub struct ReportItem { pub value: f64, pub concept: String, pub label: String, } #[derive(Clone)] pub struct FinnhubDataProvider { client: FinnhubClient, } impl DataProvider for FinnhubDataProvider { fn name(&self) -> &str { "finnhub" } fn is_enabled(&self) -> bool { true } } impl FinnhubDataProvider { pub fn new(api_url: String, api_token: String) -> Self { Self { client: FinnhubClient::new(api_url, api_token).expect("Failed to create Finnhub client"), } } pub async fn fetch_all_data( &self, symbol: &str, ) -> Result<(CompanyProfileDto, Vec), AppError> { let (profile_raw, financials_raw) = self.fetch_raw_data(symbol).await?; // 1. Build CompanyProfileDto let profile = map_profile_dto(&profile_raw, symbol)?; // 2. Build TimeSeriesFinancialDto list let financials = map_financial_dtos(&financials_raw, symbol)?; Ok((profile, financials)) } async fn fetch_raw_data( &self, symbol: &str, ) -> Result<(FinnhubProfile, FinnhubFinancialsReported), AppError> { let params_profile = vec![("symbol".to_string(), symbol.to_string())]; let params_financials = vec![ ("symbol".to_string(), symbol.to_string()), ("freq".to_string(), "annual".to_string()), ]; let profile_task = self.client.get::("/stock/profile2", params_profile); let financials_task = self.client .get::("/stock/financials-reported", params_financials); let (profile_res, financials_res) = tokio::try_join!(profile_task, financials_task)?; Ok((profile_res, financials_res)) } }