BrianIsaac commited on
Commit
22e48cb
Β·
1 Parent(s): 4cc5533

feat: display ML forecasts in Gradio UI

Browse files

Add ensemble forecast visualisation to portfolio analysis results:
- Display 30-day price predictions with direction indicators
- Show model count and confidence information
- Include Phase 2.5 duration in performance metrics
- Add phase_2_5_duration_ms to AgentState model
- Format forecasts with emoji indicators (πŸ“ˆ/πŸ“‰/➑️)
- Display below key metrics and above LLM synthesis

app.py CHANGED
@@ -207,12 +207,15 @@ def format_performance_metrics(final_state: AgentState) -> str:
207
  # Phase Execution Times
208
  phase_1_ms = final_state.get("phase_1_duration_ms", 0) or 0
209
  phase_2_ms = final_state.get("phase_2_duration_ms", 0) or 0
 
210
  phase_3_ms = final_state.get("phase_3_duration_ms", 0) or 0
211
- total_ms = phase_1_ms + phase_2_ms + phase_3_ms
212
 
213
  metrics_md += "**Execution Timeline:**\n\n"
214
  metrics_md += f"- Phase 1 (Data Collection): {phase_1_ms:,}ms\n"
215
  metrics_md += f"- Phase 2 (Computation): {phase_2_ms:,}ms\n"
 
 
216
  metrics_md += f"- Phase 3 (LLM Synthesis): {phase_3_ms:,}ms\n"
217
  metrics_md += f"- **Total**: {total_ms:,}ms ({total_ms / 1000:.2f}s)\n\n"
218
 
@@ -304,6 +307,28 @@ def format_analysis_result(final_state: AgentState, holdings: List[Dict[str, Any
304
  - **CVaR (95%)**: ${cvar_95:,.2f}
305
  """
306
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  # Get LLM synthesis and recommendations
308
  ai_synthesis = final_state.get("ai_synthesis", "Analysis completed successfully.")
309
  recommendations = final_state.get("recommendations", [])
@@ -322,6 +347,7 @@ def format_analysis_result(final_state: AgentState, holdings: List[Dict[str, Any
322
 
323
  {metrics_md}
324
 
 
325
  {ai_synthesis}
326
 
327
  {recs_md}
 
207
  # Phase Execution Times
208
  phase_1_ms = final_state.get("phase_1_duration_ms", 0) or 0
209
  phase_2_ms = final_state.get("phase_2_duration_ms", 0) or 0
210
+ phase_2_5_ms = final_state.get("phase_2_5_duration_ms", 0) or 0
211
  phase_3_ms = final_state.get("phase_3_duration_ms", 0) or 0
212
+ total_ms = phase_1_ms + phase_2_ms + phase_2_5_ms + phase_3_ms
213
 
214
  metrics_md += "**Execution Timeline:**\n\n"
215
  metrics_md += f"- Phase 1 (Data Collection): {phase_1_ms:,}ms\n"
216
  metrics_md += f"- Phase 2 (Computation): {phase_2_ms:,}ms\n"
217
+ if phase_2_5_ms > 0:
218
+ metrics_md += f"- Phase 2.5 (ML Predictions): {phase_2_5_ms:,}ms\n"
219
  metrics_md += f"- Phase 3 (LLM Synthesis): {phase_3_ms:,}ms\n"
220
  metrics_md += f"- **Total**: {total_ms:,}ms ({total_ms / 1000:.2f}s)\n\n"
221
 
 
307
  - **CVaR (95%)**: ${cvar_95:,.2f}
308
  """
309
 
310
+ # Format ML forecasts if available
311
+ forecasts_md = ""
312
+ ensemble_forecasts = final_state.get("ensemble_forecasts", {})
313
+ if ensemble_forecasts:
314
+ forecasts_md = "### ML Price Forecasts (30-day)\n\n"
315
+ for ticker, forecast_data in ensemble_forecasts.items():
316
+ if isinstance(forecast_data, dict):
317
+ models_used = forecast_data.get("models_used", [])
318
+ num_models = len(models_used) if models_used else forecast_data.get("metadata", {}).get("num_models", "N/A")
319
+
320
+ # Get first and last prediction (current vs 30-day)
321
+ predictions = forecast_data.get("predictions", [])
322
+ if predictions and len(predictions) > 0:
323
+ first_pred = float(predictions[0])
324
+ last_pred = float(predictions[-1])
325
+ change_pct = ((last_pred - first_pred) / first_pred * 100) if first_pred > 0 else 0
326
+
327
+ direction = "πŸ“ˆ" if change_pct > 0 else "πŸ“‰" if change_pct < 0 else "➑️"
328
+ forecasts_md += f"- **{ticker}**: {direction} {change_pct:+.1f}% (using {num_models} models)\n"
329
+
330
+ forecasts_md += "\n*Note: Forecasts combine Chronos foundation model with statistical baselines.*\n\n"
331
+
332
  # Get LLM synthesis and recommendations
333
  ai_synthesis = final_state.get("ai_synthesis", "Analysis completed successfully.")
334
  recommendations = final_state.get("recommendations", [])
 
347
 
348
  {metrics_md}
349
 
350
+ {forecasts_md}
351
  {ai_synthesis}
352
 
353
  {recs_md}
backend/agents/workflow.py CHANGED
@@ -393,6 +393,7 @@ class PortfolioAnalysisWorkflow:
393
 
394
  # Track phase duration
395
  phase_duration_ms = int((time.perf_counter() - phase_start) * 1000)
 
396
 
397
  logger.info(
398
  f"PHASE 2.5 COMPLETE: Generated forecasts for {len(ensemble_forecasts)} assets ({phase_duration_ms}ms)"
 
393
 
394
  # Track phase duration
395
  phase_duration_ms = int((time.perf_counter() - phase_start) * 1000)
396
+ state["phase_2_5_duration_ms"] = phase_duration_ms
397
 
398
  logger.info(
399
  f"PHASE 2.5 COMPLETE: Generated forecasts for {len(ensemble_forecasts)} assets ({phase_duration_ms}ms)"
backend/models/agent_state.py CHANGED
@@ -67,6 +67,7 @@ class AgentState(TypedDict):
67
  # Performance Metrics
68
  phase_1_duration_ms: Optional[int]
69
  phase_2_duration_ms: Optional[int]
 
70
  phase_3_duration_ms: Optional[int]
71
  llm_input_tokens: Optional[int]
72
  llm_output_tokens: Optional[int]
 
67
  # Performance Metrics
68
  phase_1_duration_ms: Optional[int]
69
  phase_2_duration_ms: Optional[int]
70
+ phase_2_5_duration_ms: Optional[int]
71
  phase_3_duration_ms: Optional[int]
72
  llm_input_tokens: Optional[int]
73
  llm_output_tokens: Optional[int]