- docker-compose: 下线 Python backend/config-service,切换至 config-service-rs - archive: 归档 legacy Python 目录至 archive/python/* - services: 新增/更新 common-contracts、api-gateway、各 provider、report-generator-service、config-service-rs - data-persistence-service: API/system 模块与模型/DTO 调整 - frontend: 更新 useApi 与 API 路由 - docs: 更新路线图并勾选光荣退役 - cleanup: 移除 data-distance-service 占位测试
105 lines
2.9 KiB
Rust
105 lines
2.9 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 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,
|
|
}
|
|
|
|
pub struct FinnhubDataProvider {
|
|
client: FinnhubClient,
|
|
}
|
|
|
|
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))
|
|
}
|
|
}
|
|
|
|
|