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 { 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 { 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) } }