use std::sync::Arc; use async_trait::async_trait; use anyhow::{Result, anyhow, Context}; use serde_json::{json, Value}; use std::collections::HashMap; use common_contracts::workflow_node::{WorkflowNode, NodeContext, NodeExecutionResult, ArtifactContent}; use common_contracts::data_formatting; use common_contracts::persistence_client::PersistenceClient; use crate::state::AppState; pub struct YFinanceNode { state: AppState, } impl YFinanceNode { pub fn new(state: AppState) -> Self { Self { state } } } #[async_trait] impl WorkflowNode for YFinanceNode { fn node_type(&self) -> &str { "yfinance" } async fn execute(&self, _ctx: &NodeContext, config: &Value) -> Result { let symbol = config.get("symbol").and_then(|s| s.as_str()).unwrap_or("").to_string(); let _market = config.get("market").and_then(|s| s.as_str()).unwrap_or("US").to_string(); if symbol.is_empty() { return Err(anyhow!("Missing symbol in config")); } // 1. Fetch Data let provider = self.state.yfinance_provider.clone(); let (profile, financials) = provider.fetch_all_data(&symbol).await .context("Failed to fetch data from YFinance")?; // 2. Cache to DB (Side effect) let persistence_url = self.state.config.data_persistence_service_url.clone(); let p_client = PersistenceClient::new(persistence_url); if let Err(e) = p_client.upsert_company_profile(profile.clone()).await { tracing::warn!("Failed to cache company profile: {}", e); } // 3. 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!("# YFinance 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) } }