Fundamental_Analysis/services/finnhub-provider-service/src/finnhub.rs
Lv, Qi e855a16c69 feat(analysis): integrate analysis templates into report workflow
- 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
2025-11-19 06:59:36 +08:00

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))
}
}