//! //! 数据持久化客户端 //! //! 提供一个类型化的接口,用于与 `data-persistence-service` 进行通信。 //! use crate::error::Result; use common_contracts::{ config_models::{AnalysisTemplateSets, LlmProvidersConfig}, dtos::{ CompanyProfileDto, NewAnalysisResult, RealtimeQuoteDto, TimeSeriesFinancialBatchDto, TimeSeriesFinancialDto, }, }; use tracing::info; #[derive(Clone)] pub struct PersistenceClient { client: reqwest::Client, base_url: String, } impl PersistenceClient { pub fn new(base_url: String) -> Self { Self { client: reqwest::Client::new(), base_url, } } pub async fn get_company_profile(&self, symbol: &str) -> Result { let url = format!("{}/companies/{}", self.base_url, symbol); info!("Fetching company profile for {} from {}", symbol, url); let dto = self .client .get(&url) .send() .await? .error_for_status()? .json::() .await?; Ok(dto) } pub async fn get_financial_statements( &self, symbol: &str, ) -> Result> { let url = format!("{}/market-data/financial-statements/{}", self.base_url, symbol); info!("Fetching financials for {} from {}", symbol, url); let dtos = self .client .get(&url) .send() .await? .error_for_status()? .json::>() .await?; Ok(dtos) } // --- Config Fetching Methods --- pub async fn get_llm_providers_config(&self) -> Result { let url = format!("{}/configs/llm_providers", self.base_url); info!("Fetching LLM providers config from {}", url); let config = self .client .get(&url) .send() .await? .error_for_status()? .json::() .await?; Ok(config) } pub async fn get_analysis_template_sets(&self) -> Result { let url = format!("{}/configs/analysis_template_sets", self.base_url); info!("Fetching analysis template sets from {}", url); let config = self .client .get(&url) .send() .await? .error_for_status()? .json::() .await?; Ok(config) } // --- Data Writing Methods --- pub async fn create_analysis_result(&self, result: NewAnalysisResult) -> Result<()> { let url = format!("{}/analysis-results", self.base_url); info!( "Persisting analysis result for symbol '{}', module '{}' to {}", result.symbol, result.module_id, url ); self.client .post(&url) .json(&result) .send() .await? .error_for_status()?; Ok(()) } pub async fn upsert_company_profile(&self, profile: CompanyProfileDto) -> Result<()> { let url = format!("{}/companies", self.base_url); info!("Upserting company profile for {} to {}", profile.symbol, url); self.client .put(&url) .json(&profile) .send() .await? .error_for_status()?; Ok(()) } pub async fn upsert_realtime_quote(&self, quote: RealtimeQuoteDto) -> Result<()> { let url = format!("{}/market-data/quotes", self.base_url); info!("Upserting realtime quote for {} to {}", quote.symbol, url); self.client .post(&url) .json("e) .send() .await? .error_for_status()?; Ok(()) } pub async fn batch_insert_financials(&self, dtos: Vec) -> Result<()> { if dtos.is_empty() { return Ok(()); } let url = format!("{}/market-data/financials/batch", self.base_url); let symbol = dtos[0].symbol.clone(); info!( "Batch inserting {} financial statements for {} to {}", dtos.len(), symbol, url ); let batch = TimeSeriesFinancialBatchDto { records: dtos }; self.client .post(&url) .json(&batch) .send() .await? .error_for_status()?; Ok(()) } }