- Backend: Add template_id to DataRequest and AnalysisResult metadata - Frontend: Add TemplateSelector to Report page - Frontend: Refactor ReportPage to use AnalysisTemplateSets for dynamic tabs - Frontend: Strict mode for report rendering based on template_id - Cleanup: Remove legacy analysis config hooks
117 lines
3.1 KiB
Rust
117 lines
3.1 KiB
Rust
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<String>,
|
|
pub currency: Option<String>,
|
|
pub exchange: Option<String>,
|
|
pub name: Option<String>,
|
|
pub ticker: Option<String>,
|
|
pub ipo: Option<String>,
|
|
pub market_capitalization: Option<f64>,
|
|
pub share_outstanding: Option<f64>,
|
|
pub logo: Option<String>,
|
|
pub phone: Option<String>,
|
|
pub weburl: Option<String>,
|
|
pub finnhub_industry: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct FinnhubFinancialsReported {
|
|
pub data: Vec<AnnualReport>,
|
|
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<ReportItem>,
|
|
pub ic: Vec<ReportItem>,
|
|
pub cf: Vec<ReportItem>,
|
|
}
|
|
|
|
#[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<TimeSeriesFinancialDto>), 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::<FinnhubProfile>("/stock/profile2", params_profile);
|
|
let financials_task =
|
|
self.client
|
|
.get::<FinnhubFinancialsReported>("/stock/financials-reported", params_financials);
|
|
|
|
let (profile_res, financials_res) = tokio::try_join!(profile_task, financials_task)?;
|
|
|
|
Ok((profile_res, financials_res))
|
|
}
|
|
}
|
|
|
|
|