- fix: infinite message loop in workflow orchestrator - feat: restore realtime LLM streaming from report generator to frontend - refactor: major update to provider services (generic workers, workflow adapters) - refactor: common contracts and message definitions updated - feat: enhanced logging and observability in orchestrator - docs: update project management tasks and status - chore: dependency updates and config adjustments
151 lines
5.0 KiB
Rust
151 lines
5.0 KiB
Rust
|
|
use async_trait::async_trait;
|
|
use anyhow::{Result, anyhow};
|
|
use serde_json::{json, Value};
|
|
use std::collections::HashMap;
|
|
use chrono::NaiveDate;
|
|
|
|
use common_contracts::workflow_node::{WorkflowNode, NodeContext, NodeExecutionResult, ArtifactContent, CacheKey};
|
|
use common_contracts::data_formatting;
|
|
use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto};
|
|
use crate::state::AppState;
|
|
use std::time::Duration;
|
|
|
|
pub struct MockNode {
|
|
#[allow(dead_code)]
|
|
state: AppState,
|
|
}
|
|
|
|
impl MockNode {
|
|
pub fn new(state: AppState) -> Self {
|
|
Self { state }
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl WorkflowNode for MockNode {
|
|
fn node_type(&self) -> &str {
|
|
"mock"
|
|
}
|
|
|
|
fn get_cache_config(&self, config: &Value) -> Option<(CacheKey, Duration)> {
|
|
let symbol = config.get("symbol").and_then(|s| s.as_str())?;
|
|
|
|
let key_parts = vec![
|
|
"mock",
|
|
"company_data",
|
|
symbol,
|
|
"all"
|
|
];
|
|
|
|
let cache_key = CacheKey(key_parts.join(":"));
|
|
// Mock data is static, but we can cache it for 1 hour
|
|
let ttl = Duration::from_secs(3600);
|
|
|
|
Some((cache_key, ttl))
|
|
}
|
|
|
|
async fn execute(&self, _ctx: &NodeContext, config: &Value) -> Result<NodeExecutionResult> {
|
|
let mode = config.get("simulation_mode").and_then(|v| v.as_str()).unwrap_or("normal");
|
|
|
|
if mode == "hang" {
|
|
tracing::info!("Simulating Hang (Sleep 600s)...");
|
|
tokio::time::sleep(Duration::from_secs(600)).await;
|
|
}
|
|
|
|
if mode == "crash" {
|
|
tracing::info!("Simulating Crash (Process Exit)...");
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
std::process::exit(1);
|
|
}
|
|
|
|
let symbol = config.get("symbol").and_then(|s| s.as_str()).unwrap_or("MOCK").to_string();
|
|
|
|
// Generate Dummy Data
|
|
let profile = CompanyProfileDto {
|
|
symbol: symbol.clone(),
|
|
name: format!("Mock Company {}", symbol),
|
|
industry: Some("Testing".to_string()),
|
|
list_date: Some(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()),
|
|
additional_info: Some(json!({
|
|
"description": "This is a mock company for testing purposes.",
|
|
"market_cap": 1000000000.0,
|
|
"currency": "USD",
|
|
"country": "US"
|
|
})),
|
|
updated_at: Some(chrono::Utc::now()),
|
|
};
|
|
|
|
let date = NaiveDate::from_ymd_opt(2023, 12, 31).unwrap();
|
|
let financials = vec![
|
|
TimeSeriesFinancialDto {
|
|
symbol: symbol.clone(),
|
|
metric_name: "revenue".to_string(),
|
|
period_date: date,
|
|
value: 1000000.0,
|
|
source: Some("mock".to_string()),
|
|
},
|
|
TimeSeriesFinancialDto {
|
|
symbol: symbol.clone(),
|
|
metric_name: "net_income".to_string(),
|
|
period_date: date,
|
|
value: 500000.0,
|
|
source: Some("mock".to_string()),
|
|
},
|
|
TimeSeriesFinancialDto {
|
|
symbol: symbol.clone(),
|
|
metric_name: "total_assets".to_string(),
|
|
period_date: date,
|
|
value: 2000000.0,
|
|
source: Some("mock".to_string()),
|
|
},
|
|
TimeSeriesFinancialDto {
|
|
symbol: symbol.clone(),
|
|
metric_name: "operating_cash_flow".to_string(),
|
|
period_date: date,
|
|
value: 550000.0,
|
|
source: Some("mock".to_string()),
|
|
},
|
|
];
|
|
|
|
// Prepare Artifacts
|
|
let mut artifacts = HashMap::new();
|
|
artifacts.insert("profile.json".to_string(), json!(profile).into());
|
|
artifacts.insert("financials.json".to_string(), json!(financials).into());
|
|
|
|
Ok(NodeExecutionResult {
|
|
artifacts,
|
|
meta_summary: Some(json!({
|
|
"symbol": symbol,
|
|
"records": financials.len()
|
|
})),
|
|
})
|
|
}
|
|
|
|
fn render_report(&self, result: &NodeExecutionResult) -> Result<String> {
|
|
let profile_json = match result.artifacts.get("profile.json") {
|
|
Some(ArtifactContent::Json(v)) => v,
|
|
_ => return Err(anyhow!("Missing profile.json")),
|
|
};
|
|
let financials_json = match result.artifacts.get("financials.json") {
|
|
Some(ArtifactContent::Json(v)) => v,
|
|
_ => return Err(anyhow!("Missing financials.json")),
|
|
};
|
|
|
|
let symbol = profile_json["symbol"].as_str().unwrap_or("Unknown");
|
|
|
|
let mut report_md = String::new();
|
|
report_md.push_str(&format!("# Mock Data Report: {}\n\n", symbol));
|
|
|
|
report_md.push_str("## Company Profile\n\n");
|
|
report_md.push_str(&data_formatting::format_data(profile_json));
|
|
report_md.push_str("\n\n");
|
|
|
|
report_md.push_str("## Financial Statements\n\n");
|
|
report_md.push_str(&data_formatting::format_data(financials_json));
|
|
|
|
Ok(report_md)
|
|
}
|
|
}
|
|
|