Fundamental_Analysis/services/report-generator-service/src/persistence.rs
Lv, Qi 0c975bb8f1 Refactor: Remove legacy analysis results and implement workflow history
- **Common Contracts**: Updated DTOs and models to support workflow history; removed legacy analysis result DTOs.
- **Data Persistence Service**:
    - Removed `analysis_results` table logic and API endpoints.
    - Implemented `workflow_history` API and DB access (`history.rs`).
    - Fixed compilation errors and updated tests.
    - Exposed Postgres port in `docker-compose.yml` for easier debugging/offline checks.
- **API Gateway**:
    - Implemented `history` endpoints (get history list, get by ID).
    - Removed legacy `analysis-results` endpoints.
    - Fixed routing and handler logic in `api.rs`.
- **Report Generator Service**:
    - Removed dependency on legacy `analysis-results` persistence calls.
    - Fixed compilation errors.
- **Workflow Orchestrator**: Fixed warnings and minor logic issues.
- **Providers**: Updated provider services (alphavantage, tushare, finnhub, yfinance, mock) to align with contract changes.
- **Frontend**:
    - Updated `ReportPage` and stores to use new workflow history.
    - Added `RecentReportsDropdown` component.
    - Cleaned up `RealtimeLogs` component.
- **Documentation**: Moved completed design tasks to `completed/` and added refactoring context docs.

Confirmed all services pass `cargo check`.
2025-11-29 14:46:44 +08:00

193 lines
5.6 KiB
Rust

//!
//! 数据持久化客户端
//!
//! 提供一个类型化的接口,用于与 `data-persistence-service` 进行通信。
//!
use crate::error::Result;
use common_contracts::{
config_models::{AnalysisTemplateSets, LlmProvidersConfig},
dtos::{
CompanyProfileDto, RealtimeQuoteDto, SessionDataDto,
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<CompanyProfileDto> {
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::<CompanyProfileDto>()
.await?;
Ok(dto)
}
pub async fn get_financial_statements(
&self,
symbol: &str,
) -> Result<Vec<TimeSeriesFinancialDto>> {
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::<Vec<TimeSeriesFinancialDto>>()
.await?;
Ok(dtos)
}
pub async fn get_session_data(&self, request_id: uuid::Uuid) -> Result<Vec<SessionDataDto>> {
let url = format!("{}/session-data/{}", self.base_url, request_id);
info!("Fetching session data for {} from {}", request_id, url);
let dtos = self
.client
.get(&url)
.send()
.await?
.error_for_status()?
.json::<Vec<SessionDataDto>>()
.await?;
Ok(dtos)
}
pub async fn insert_session_data(&self, data: &SessionDataDto) -> Result<()> {
let url = format!("{}/session-data", self.base_url);
info!("Inserting session data for {} to {}", data.request_id, url);
self.client
.post(&url)
.json(data)
.send()
.await?
.error_for_status()?;
Ok(())
}
// --- Config Fetching & Updating Methods ---
pub async fn get_llm_providers_config(&self) -> Result<LlmProvidersConfig> {
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::<LlmProvidersConfig>()
.await?;
Ok(config)
}
pub async fn update_llm_providers_config(&self, config: &LlmProvidersConfig) -> Result<()> {
let url = format!("{}/configs/llm_providers", self.base_url);
info!("Updating LLM providers config to {}", url);
self.client
.put(&url)
.json(config)
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn get_analysis_template_sets(&self) -> Result<AnalysisTemplateSets> {
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::<AnalysisTemplateSets>()
.await?;
Ok(config)
}
pub async fn update_analysis_template_sets(&self, templates: &AnalysisTemplateSets) -> Result<()> {
let url = format!("{}/configs/analysis_template_sets", self.base_url);
info!("Updating analysis template sets to {}", url);
self.client
.put(&url)
.json(templates)
.send()
.await?
.error_for_status()?;
Ok(())
}
// --- Data Writing Methods ---
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(&quote)
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn batch_insert_financials(&self, dtos: Vec<TimeSeriesFinancialDto>) -> 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(())
}
}