- **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`.
193 lines
5.6 KiB
Rust
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("e)
|
|
.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(())
|
|
}
|
|
}
|