use crate::error::AppError; use crate::mapping::{map_financial_statements, map_profile}; use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto}; use anyhow::anyhow; #[derive(Clone)] pub struct YFinanceDataProvider { client: reqwest::Client, } impl YFinanceDataProvider { pub fn new() -> Self { Self { client: reqwest::Client::new(), } } pub async fn fetch_all_data( &self, symbol: &str, ) -> Result<(CompanyProfileDto, Vec), AppError> { let (summary_raw, financials_raw) = self.fetch_raw_data(symbol).await?; let profile = map_profile(&summary_raw, symbol)?; let financials = map_financial_statements(&financials_raw, symbol)?; Ok((profile, financials)) } async fn fetch_raw_data( &self, symbol: &str, ) -> Result<(serde_json::Value, serde_json::Value), AppError> { // Yahoo quoteSummary: assetProfile + quoteType let summary_url = format!( "https://query1.finance.yahoo.com/v10/finance/quoteSummary/{}?modules=assetProfile,quoteType", symbol ); // Yahoo financials: income/balance/cashflow histories let financials_url = format!( "https://query1.finance.yahoo.com/v10/finance/quoteSummary/{}?modules=incomeStatementHistory,balanceSheetHistory,cashflowStatementHistory", symbol ); let summary_task = self.client.get(&summary_url).send(); let financials_task = self.client.get(&financials_url).send(); let (summary_res, financials_res) = tokio::try_join!(summary_task, financials_task) .map_err(|e| AppError::ServiceRequest(e))?; let summary_res = summary_res.error_for_status()?; let financials_res = financials_res.error_for_status()?; let summary_json: serde_json::Value = summary_res .json() .await .map_err(|e| AppError::DataParsing(anyhow!(e)))?; let financials_json: serde_json::Value = financials_res .json() .await .map_err(|e| AppError::DataParsing(anyhow!(e)))?; Ok((summary_json, financials_json)) } }