Runn the MVP of the app

This commit is contained in:
Jenit Jain 2025-08-10 10:05:55 -07:00
parent 09abe2da38
commit 60c0970c2e
6 changed files with 391 additions and 96 deletions

2
.gitignore vendored
View File

@ -3,7 +3,7 @@ __pycache__/
.DS_Store .DS_Store
*.csv *.csv
# src/ # src/
# eval_results/ eval_results/
eval_data/ eval_data/
*.egg-info/ *.egg-info/
.env .env

View File

@ -1,13 +1,11 @@
from fastapi import FastAPI, HTTPException, BackgroundTasks from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel from pydantic import BaseModel
from typing import Dict, Any, List, Optional from typing import Dict, Any, Optional
import json import json
import os import os
import asyncio from datetime import datetime
from datetime import datetime, date
import glob import glob
from pathlib import Path
import uuid import uuid
# Import your TradingAgents components # Import your TradingAgents components
@ -18,6 +16,9 @@ from tradingagents.default_config import DEFAULT_CONFIG
app = FastAPI(title="TradingAgents API", version="1.0.0") app = FastAPI(title="TradingAgents API", version="1.0.0")
# Centralized results directory to avoid repetition
RESULTS_BASE = "/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results"
# Configure CORS # Configure CORS
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@ -69,6 +70,8 @@ async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config
# Initialize TradingAgents # Initialize TradingAgents
jobs[job_id].progress = "Setting up trading graph..." jobs[job_id].progress = "Setting up trading graph..."
# Do not set API keys in code. Use environment variables or a secure secret manager.
ta = TradingAgentsGraph(debug=True, config=config) ta = TradingAgentsGraph(debug=True, config=config)
# Run the analysis # Run the analysis
@ -93,33 +96,37 @@ async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config
async def start_analysis(request: AnalysisRequest, background_tasks: BackgroundTasks): async def start_analysis(request: AnalysisRequest, background_tasks: BackgroundTasks):
"""Start a new trading analysis""" """Start a new trading analysis"""
job_id = str(uuid.uuid4()) job_id = str(uuid.uuid4())
# Normalize inputs
symbol = request.symbol.upper().strip()
date = request.date.strip()
# Validate date format # Validate date format
try: try:
datetime.strptime(request.date, "%Y-%m-%d") datetime.strptime(date, "%Y-%m-%d")
except ValueError: except ValueError:
raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD") raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
# Initialize job # Initialize job
jobs[job_id] = JobStatus( jobs[job_id] = JobStatus(
job_id=job_id, job_id=job_id,
status="queued", status="queued",
progress="Analysis queued" progress="Analysis queued"
) )
# Start background task # Start background task
background_tasks.add_task( background_tasks.add_task(
run_analysis_task, run_analysis_task,
job_id, job_id,
request.symbol.upper(), symbol,
request.date, date,
request.config_overrides or {} request.config_overrides or {}
) )
return AnalysisResponse( return AnalysisResponse(
job_id=job_id, job_id=job_id,
status="queued", status="queued",
message=f"Analysis started for {request.symbol} on {request.date}" message=f"Analysis started for {symbol} on {date}"
) )
@app.get("/analysis/status/{job_id}", response_model=JobStatus) @app.get("/analysis/status/{job_id}", response_model=JobStatus)
@ -133,7 +140,7 @@ async def get_analysis_status(job_id: str):
@app.get("/results/companies") @app.get("/results/companies")
async def get_companies(): async def get_companies():
"""Get list of companies with analysis results""" """Get list of companies with analysis results"""
results_dir = "/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results" results_dir = RESULTS_BASE
if not os.path.exists(results_dir): if not os.path.exists(results_dir):
return {"companies": []} return {"companies": []}
@ -176,7 +183,7 @@ async def get_companies():
@app.get("/results/{symbol}") @app.get("/results/{symbol}")
async def get_company_results(symbol: str): async def get_company_results(symbol: str):
"""Get all analysis results for a specific company""" """Get all analysis results for a specific company"""
results_dir = f"/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results/{symbol.upper()}/TradingAgentsStrategy_logs" results_dir = os.path.join(RESULTS_BASE, symbol.upper(), "TradingAgentsStrategy_logs")
if not os.path.exists(results_dir): if not os.path.exists(results_dir):
raise HTTPException(status_code=404, detail=f"No results found for {symbol}") raise HTTPException(status_code=404, detail=f"No results found for {symbol}")
@ -214,7 +221,7 @@ async def get_company_results(symbol: str):
@app.get("/transformed-results/{symbol}") @app.get("/transformed-results/{symbol}")
async def get_transformed_company_results(symbol: str): async def get_transformed_company_results(symbol: str):
"""Get all transformed analysis results for a specific company""" """Get all transformed analysis results for a specific company"""
results_dir = f"/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results/{symbol.upper()}/TradingAgentsStrategy_transformed_logs" results_dir = os.path.join(RESULTS_BASE, symbol.upper(), "TradingAgentsStrategy_transformed_logs")
if not os.path.exists(results_dir): if not os.path.exists(results_dir):
raise HTTPException(status_code=404, detail=f"No transformed results found for {symbol}") raise HTTPException(status_code=404, detail=f"No transformed results found for {symbol}")
@ -255,7 +262,12 @@ async def get_transformed_company_results(symbol: str):
@app.get("/results/{symbol}/{date}") @app.get("/results/{symbol}/{date}")
async def get_specific_result(symbol: str, date: str): async def get_specific_result(symbol: str, date: str):
"""Get specific analysis result""" """Get specific analysis result"""
file_path = f"/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results/{symbol.upper()}/TradingAgentsStrategy_logs/full_states_log_{date}.json" file_path = os.path.join(
RESULTS_BASE,
symbol.upper(),
"TradingAgentsStrategy_logs",
f"full_states_log_{date}.json",
)
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail=f"No result found for {symbol} on {date}") raise HTTPException(status_code=404, detail=f"No result found for {symbol} on {date}")
@ -279,7 +291,12 @@ async def get_specific_result(symbol: str, date: str):
@app.get("/transformed-results/{symbol}/{date}") @app.get("/transformed-results/{symbol}/{date}")
async def get_specific_transformed_result(symbol: str, date: str): async def get_specific_transformed_result(symbol: str, date: str):
"""Get specific transformed analysis result""" """Get specific transformed analysis result"""
file_path = f"/home/brabus61/Desktop/Github Repos/TradingAgents/scripts/eval_results/{symbol.upper()}/TradingAgentsStrategy_transformed_logs/full_states_log_{date}_transformed.json" file_path = os.path.join(
RESULTS_BASE,
symbol.upper(),
"TradingAgentsStrategy_transformed_logs",
f"full_states_log_{date}_transformed.json",
)
if not os.path.exists(file_path): if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail=f"Transformed result not found for {symbol} on {date}") raise HTTPException(status_code=404, detail=f"Transformed result not found for {symbol} on {date}")

View File

@ -304,7 +304,7 @@ function App() {
const runAnalysis = async () => { const runAnalysis = async () => {
setIsRunningAnalysis(true); setIsRunningAnalysis(true);
try { try {
const response = await axios.post('/run-analysis', analysisForm); const response = await axios.post('/analysis/start', analysisForm);
alert('Analysis completed successfully!'); alert('Analysis completed successfully!');
setShowAnalysisModal(false); setShowAnalysisModal(false);
setAnalysisForm({ symbol: '', date: '' }); setAnalysisForm({ symbol: '', date: '' });

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import AnalysisWidgets from '../pages/AnalysisWidgets.tsx';
// New interface for the transformed JSON structure // New interface for the transformed JSON structure
interface TransformedAnalysisData { interface TransformedAnalysisData {
@ -379,58 +378,226 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
widgets_config: transformedData?.widgets_config || buildTransformedWidgetsConfig(transformedData) widgets_config: transformedData?.widgets_config || buildTransformedWidgetsConfig(transformedData)
}; };
// Minimalist Technical Indicators widget (single-card dashboard) // Minimalist dashboard that shows ALL main sections of transformed JSON
const TechnicalIndicatorsOnly: React.FC<{ data: TransformedAnalysisData }> = ({ data }) => { const MinimalTransformedDashboard: React.FC<{ data: TransformedAnalysisData }> = ({ data }) => {
const ti = data?.technical_indicators || {} as any; const md = data?.metadata as any || {};
const fd = data?.financial_data as any || {};
const ti = data?.technical_indicators as any || {};
const istrat = data?.investment_strategy as any || {};
const ds = data?.debate_summary as any || {};
const txt = data?.text_content as any || {};
const trends = ti?.trend_directions || {}; const trends = ti?.trend_directions || {};
const fmt = (v: any, d = 0) => { const fmt = (v: any, d = 0) => {
const n = Number(v); const n = Number(v);
return Number.isFinite(n) ? n.toFixed(d) : '—'; if (v === null || v === undefined || Number.isNaN(n)) return '-';
return Number.isFinite(n) ? n.toFixed(d) : String(v);
}; };
const trendChip = (label: string, dir?: string) => { const fmtNumber = (v: any, d = 0) => {
const map: Record<string, string> = { BULLISH: 'bg-green-100 text-green-800', BEARISH: 'bg-red-100 text-red-800', NEUTRAL: 'bg-gray-100 text-gray-700' }; const n = Number(v);
const cls = map[String(dir || 'NEUTRAL').toUpperCase()] || map.NEUTRAL; if (!Number.isFinite(n)) return '-';
return <span className={`px-2 py-1 rounded text-xs font-medium ${cls}`}>{label}: {dir || 'NEUTRAL'}</span>; return n.toLocaleString(undefined, { maximumFractionDigits: d, minimumFractionDigits: d });
}; };
const fmtCurrency = (v: any, d = 2) => {
const n = Number(v);
if (!Number.isFinite(n)) return '-';
return n.toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: d, minimumFractionDigits: d });
};
const fmtPercent = (v: any, d = 2) => {
const n = Number(v);
if (!Number.isFinite(n)) return '-';
return `${n.toFixed(d)}%`;
};
const toneFromNumber = (v: any): 'pos' | 'neg' | 'neutral' => {
const n = Number(v);
if (!Number.isFinite(n)) return 'neutral';
if (n > 0) return 'pos';
if (n < 0) return 'neg';
return 'neutral';
};
const Stat: React.FC<{ label: string; value: React.ReactNode; sub?: React.ReactNode; tone?: 'pos' | 'neg' | 'neutral' }>
= ({ label, value, sub, tone = 'neutral' }) => {
const toneCls = tone === 'pos' ? 'text-green-600' : tone === 'neg' ? 'text-red-600' : 'text-gray-900';
const ringCls = tone === 'pos' ? 'ring-green-200' : tone === 'neg' ? 'ring-red-200' : 'ring-gray-200';
return (
<div className={`rounded-lg border border-gray-200 ring-1 ${ringCls} p-4 bg-white shadow-sm`}>
<p className="text-xs text-gray-500">{label}</p>
<p className={`text-xl font-semibold ${toneCls}`}>{value}</p>
{sub && <p className="text-xs text-gray-500 mt-0.5">{sub}</p>}
</div>
);
};
const row = (label: string, value: any, fmtDigits?: number) => (
<div className="flex items-center justify-between py-1">
<span className="text-sm text-gray-500">{label}</span>
<span className="text-sm font-medium">{fmtDigits === undefined ? (value ?? '-') : fmt(value, fmtDigits)}</span>
</div>
);
const chip = (label: string, val?: string) => (
<span className="px-2 py-1 rounded bg-gray-100 text-gray-700 text-xs font-medium">{label}: {val ?? '-'}</span>
);
// Tabs state and memoized content for text-heavy sections
const debateTabs = React.useMemo(() => ([
{ key: 'bull', label: 'Bull', content: Array.isArray(ds?.bull_key_points) ? ds.bull_key_points : [] as any[] },
{ key: 'bear', label: 'Bear', content: Array.isArray(ds?.bear_key_points) ? ds.bear_key_points : [] as any[] },
{ key: 'neutral', label: 'Neutral', content: ds?.neutral_perspective ? [ds.neutral_perspective] : [] as any[] },
{ key: 'final', label: 'Final', content: ds?.final_decision_rationale ? [ds.final_decision_rationale] : [] as any[] },
]), [ds]);
const firstDebateWithContent = React.useMemo(
() => debateTabs.find(t => (t.content?.length ?? 0) > 0)?.key || 'bull',
[debateTabs]
);
const [activeDebateTab, setActiveDebateTab] = React.useState<string>(firstDebateWithContent);
React.useEffect(() => { setActiveDebateTab(firstDebateWithContent); }, [firstDebateWithContent]);
const reportTabsAll = React.useMemo(() => ([
{ key: 'market', title: txt?.market_report?.title || 'Market Report', bullets: txt?.market_report?.key_takeaways, content: txt?.market_report?.content },
{ key: 'sentiment', title: txt?.sentiment_report?.title || 'Sentiment Report', bullets: txt?.sentiment_report?.recent_developments, content: txt?.sentiment_report?.content },
{ key: 'fundamentals', title: txt?.fundamentals_report?.title || 'Fundamentals Report', bullets: txt?.fundamentals_report?.financial_highlights, content: txt?.fundamentals_report?.content },
]), [txt]);
const availableReports = React.useMemo(
() => reportTabsAll.filter(t => t.title || (Array.isArray(t.bullets) && t.bullets.length) || t.content),
[reportTabsAll]
);
const [activeReportTab, setActiveReportTab] = React.useState<string>(availableReports[0]?.key || 'market');
React.useEffect(() => { setActiveReportTab(availableReports[0]?.key || 'market'); }, [availableReports]);
return ( return (
<div className="min-h-[200px] p-6"> <div className="p-6">
<div className="max-w-3xl mx-auto"> <div className="mx-auto max-w-6xl space-y-4">
<div className="bg-white rounded-lg shadow-md p-6"> {/* Metadata - Full width */}
<h2 className="text-xl font-semibold mb-4">Technical Indicators</h2> <div className="bg-white rounded-lg shadow p-6">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <h2 className="text-lg font-semibold mb-3">Company Information</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<Stat label="Company" value={`${md?.company_name ?? '-'} (${md?.company_ticker ?? '-'})`} />
<Stat label="Analysis Date" value={md?.analysis_date ?? '-'} />
<Stat label="Final Recommendation" value={md?.final_recommendation ?? '-'} />
<Stat label="Confidence" value={md?.confidence_level ?? '-'} />
</div>
</div>
{/* Two column layout for remaining sections */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* Debate Summary with sub-tabs */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-3">Debate Summary</h2>
<div> <div>
<p className="text-sm text-gray-500">RSI</p> <div className="flex gap-2 border-b border-gray-200 mb-3">
<p className="text-lg font-semibold">{fmt(ti?.rsi, 2)}</p> {debateTabs.map(t => (
</div> <button
<div> key={t.key}
<p className="text-sm text-gray-500">MACD</p> onClick={() => setActiveDebateTab(t.key)}
<p className="text-lg font-semibold">{fmt(ti?.macd, 3)}</p> className={`px-3 py-1.5 text-sm rounded-t ${activeDebateTab === t.key ? 'bg-gray-100 text-gray-900' : 'text-gray-500 hover:text-gray-700'}`}
<p className="text-xs text-gray-500">Signal: {fmt(ti?.macd_signal, 3)}</p> >{t.label}</button>
</div> ))}
<div> </div>
<p className="text-sm text-gray-500">SMA 50</p> <div className="text-sm text-gray-800 space-y-2 max-h-64 overflow-auto pr-1">
<p className="text-lg font-semibold">{fmt(ti?.sma_50, 2)}</p> {debateTabs.find(t => t.key === activeDebateTab)?.content?.length ? (
</div> <ul className="list-disc list-inside">
<div> {(debateTabs.find(t => t.key === activeDebateTab)?.content as any[]).map((c, i) => <li key={i}>{c}</li>)}
<p className="text-sm text-gray-500">SMA 200</p> </ul>
<p className="text-lg font-semibold">{fmt(ti?.sma_200, 2)}</p> ) : (
</div> <p className="text-gray-500">No content.</p>
{ti?.ema_10 !== undefined && ( )}
<div>
<p className="text-sm text-gray-500">EMA 10</p>
<p className="text-lg font-semibold">{fmt(ti?.ema_10, 2)}</p>
</div> </div>
)}
<div>
<p className="text-sm text-gray-500">ATR</p>
<p className="text-lg font-semibold">{fmt(ti?.atr, 3)}</p>
</div> </div>
</div> </div>
<div className="mt-6 flex flex-wrap gap-2">
{trendChip('SMA 50', trends?.sma_50)} {/* Text Content with sub-tabs */}
{trendChip('SMA 200', trends?.sma_200)} <div className="bg-white rounded-lg shadow p-6">
{trends?.ema_10 !== undefined && trendChip('EMA 10', trends?.ema_10)} <h2 className="text-lg font-semibold mb-3">Reports</h2>
{trends?.price_action !== undefined && trendChip('Price', trends?.price_action)} {!availableReports.length ? (
<p className="text-sm text-gray-500">No reports available.</p>
) : (
<div>
<div className="flex gap-2 border-b border-gray-200 mb-3">
{availableReports.map(t => (
<button
key={t.key}
onClick={() => setActiveReportTab(t.key)}
className={`px-3 py-1.5 text-sm rounded-t ${activeReportTab === t.key ? 'bg-gray-100 text-gray-900' : 'text-gray-500 hover:text-gray-700'}`}
>{t.title}</button>
))}
</div>
{(() => {
const activeTab = availableReports.find(t => t.key === activeReportTab);
if (!activeTab) return null;
return (
<div className="space-y-3 max-h-72 overflow-auto pr-1">
{Array.isArray(activeTab.bullets) && activeTab.bullets.length > 0 && (
<ul className="list-disc list-inside text-sm text-gray-700">
{activeTab.bullets.map((k: string, i: number) => <li key={i}>{k}</li>)}
</ul>
)}
{activeTab.content && (
<p className="text-sm text-gray-700 whitespace-pre-wrap">{activeTab.content}</p>
)}
</div>
);
})()}
</div>
)}
</div>
{/* Financial Data */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-3">Financial Data</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Stat label="Current Price" value={fmtCurrency(fd?.current_price)} />
<Stat label="Target Price" value={fmtCurrency(fd?.analyst_data?.price_target ?? fd?.target_price)} />
<Stat label="Price Change" value={fmtPercent(fd?.price_change)} tone={toneFromNumber(fd?.price_change)} />
<Stat label="Price Change %" value={fmtPercent(fd?.price_change_percent)} tone={toneFromNumber(fd?.price_change_percent)} />
<Stat label="Market Cap" value={fd?.market_cap ?? '-'} />
<Stat label="Enterprise Value" value={fd?.enterprise_value ?? '-'} />
<Stat label="Shares Outstanding" value={fd?.shares_outstanding ?? '-'} />
<Stat label="Volume" value={fmtNumber(fd?.volume, 0)} />
<Stat label="P/E Ratio" value={fmtNumber(fd?.pe_ratio ?? fd?.valuation_ratios?.forward_pe, 2)} />
<Stat label="P/S Ratio" value={fmtNumber(fd?.valuation_ratios?.current_ps_ratio, 2)} />
</div>
</div>
{/* Technical Indicators */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-3">Technical Indicators</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Stat label="RSI" value={fmtNumber(ti?.rsi, 2)} tone={toneFromNumber((Number(ti?.rsi) - 50))} />
<Stat label="MACD" value={fmtNumber(ti?.macd, 3)} />
<Stat label="Signal" value={fmtNumber(ti?.macd_signal, 3)} />
<Stat label="SMA 50" value={fmtCurrency((ti?.sma_50 ?? ti?.moving_avg_50), 2)} />
<Stat label="SMA 200" value={fmtCurrency((ti?.sma_200 ?? ti?.moving_avg_200), 2)} />
<Stat label="MA 20" value={fmtCurrency(ti?.moving_avg_20, 2)} />
<Stat label="EMA 10" value={fmtCurrency(ti?.ema_10, 2)} />
<Stat label="ATR" value={fmtNumber(ti?.atr, 3)} />
<Stat label="Bollinger Upper" value={fmtCurrency(ti?.bollinger_upper, 2)} />
<Stat label="Bollinger Lower" value={fmtCurrency(ti?.bollinger_lower, 2)} />
<Stat label="Support" value={fmtCurrency(ti?.support_level, 2)} />
<Stat label="Resistance" value={fmtCurrency(ti?.resistance_level, 2)} />
</div>
<div className="mt-4 flex flex-wrap gap-2">
{chip('Trend SMA 50', trends?.sma_50)}
{chip('Trend SMA 200', trends?.sma_200)}
{trends?.ema_10 !== undefined && chip('Trend EMA 10', trends?.ema_10)}
{trends?.price_action !== undefined && chip('Price', trends?.price_action)}
</div>
</div>
{/* Investment Strategy */}
<div className="bg-white rounded-lg shadow p-6 lg:col-span-2">
<h2 className="text-lg font-semibold mb-3">Investment Strategy</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<Stat label="Risk Level (SL%)" value={fmtPercent(istrat?.risk_management?.stop_loss_percent ?? md?.risk_level)} />
<Stat label="Initial Stop Loss" value={fmtCurrency(istrat?.risk_management?.initial_stop_loss)} />
<Stat label="Profit Targets" value={istrat.profit_targets.map((t: any, idx: number) => (
<li key={idx} className="flex gap-4">
<span className="font-semibold">{fmtCurrency(t?.target_price)}</span>
{t?.action && <span className="text-xs text-gray-500">{t.action}</span>}
{t?.rationale && <span className="text-xs text-gray-500">{t.rationale}</span>}
<hr />
</li>
))} />
</div>
</div> </div>
</div> </div>
</div> </div>
@ -438,8 +605,8 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
); );
}; };
// Only render the minimalist Technical Indicators widget for transformed data // Render minimalist dashboard with all keys from the transformed JSON
return <TechnicalIndicatorsOnly data={transformedWithConfig} />; return <MinimalTransformedDashboard data={transformedWithConfig} />;
}; };
export default TransformedDataAdapter; export default TransformedDataAdapter;

View File

@ -101,6 +101,21 @@ class TransformedDataService {
return `${symbol}|${date}`; return `${symbol}|${date}`;
} }
private buildFileEntry(companySymbol: string, result: any): TransformedAnalysis {
const id = this.makeCacheKey(companySymbol, result.date);
return {
filename: result.filename,
date: result.date,
modified_at: result.modified_at,
file_size: result.file_size,
preview: result.preview,
error: result.error,
symbol: companySymbol,
displayName: `${companySymbol} - ${result.date}`,
id
};
}
async getAvailableFiles(): Promise<TransformedAnalysis[]> { async getAvailableFiles(): Promise<TransformedAnalysis[]> {
if (this.cachedFiles) { if (this.cachedFiles) {
return this.cachedFiles; return this.cachedFiles;
@ -108,38 +123,23 @@ class TransformedDataService {
try { try {
const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`); const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`);
const companiesData = companiesResponse.data; const companies = (companiesResponse.data?.companies || [])
const companies = companiesData.companies || []; .filter((c: any) => (c?.transformed_analyses ?? 0) > 0);
const files: TransformedAnalysis[] = []; // Fetch each company's transformed results in parallel
const companyFetches = companies.map(async (company: any) => {
for (const company of companies) { try {
if (company.transformed_analyses > 0) { const resultsResponse = await axios.get(`${this.baseUrl}/transformed-results/${company.symbol}`);
try { const results = resultsResponse.data?.results || [];
const resultsResponse = await axios.get(`${this.baseUrl}/transformed-results/${company.symbol}`); return results.map((r: any) => this.buildFileEntry(company.symbol, r));
const resultsData = resultsResponse.data; } catch (err) {
const results = resultsData.results || []; console.warn(`Failed to fetch transformed results for ${company.symbol}:`, err);
return [] as TransformedAnalysis[];
for (const result of results) {
const id = this.makeCacheKey(company.symbol, result.date);
files.push({
filename: result.filename,
date: result.date,
modified_at: result.modified_at,
file_size: result.file_size,
preview: result.preview,
error: result.error,
symbol: company.symbol,
displayName: `${company.symbol} - ${result.date}`,
id
});
}
} catch (error) {
console.warn(`Failed to fetch transformed results for ${company.symbol}:`, error);
}
} }
} });
const resultsByCompany = await Promise.all(companyFetches);
const files = resultsByCompany.flat();
this.cachedFiles = files; this.cachedFiles = files;
} catch (error) { } catch (error) {
console.warn('Could not load transformed data files:', error); console.warn('Could not load transformed data files:', error);
@ -164,7 +164,12 @@ class TransformedDataService {
const files = await this.getAvailableFiles(); const files = await this.getAvailableFiles();
const fileEntry = files.find(f => f.symbol === symbol && f.date === date); const fileEntry = files.find(f => f.symbol === symbol && f.date === date);
if (!fileEntry) { if (!fileEntry) {
throw new Error(`File not found in index: ${symbol} ${date}`); // Fallback: fetch directly if not in index
const response = await axios.get(`${this.baseUrl}/transformed-results/${symbol}/${date}`);
const data: TransformedData = response.data.data;
this.validateTransformedData(data);
this.cachedData.set(cacheKey, data);
return data;
} }
const response = await axios.get(`${this.baseUrl}/transformed-results/${fileEntry.symbol}/${date}`); const response = await axios.get(`${this.baseUrl}/transformed-results/${fileEntry.symbol}/${date}`);

106
web_app/test_backend.py Normal file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""
Test script to verify TradingAgents backend API endpoints
"""
import requests
import json
import sys
import os
def test_backend():
"""Test all backend endpoints"""
base_url = "http://localhost:8000"
print("🧪 Testing TradingAgents Backend API")
print("=" * 50)
# Test 1: Health check
print("\n1. Testing health check...")
try:
response = requests.get(f"{base_url}/health", timeout=5)
if response.status_code == 200:
print("✅ Health check passed")
print(f" Response: {response.json()}")
else:
print(f"❌ Health check failed: {response.status_code}")
return False
except requests.exceptions.ConnectionError:
print("❌ Backend not running. Please start the backend server first:")
print(" cd web_app/backend && python main.py")
return False
except Exception as e:
print(f"❌ Health check error: {e}")
return False
# Test 2: Companies endpoint
print("\n2. Testing companies endpoint...")
try:
response = requests.get(f"{base_url}/results/companies", timeout=10)
if response.status_code == 200:
data = response.json()
companies = data.get('companies', [])
print(f"✅ Companies endpoint working - Found {len(companies)} companies")
for company in companies:
print(f" - {company['symbol']}: {company['total_analyses']} analyses, {company.get('transformed_analyses', 0)} transformed")
else:
print(f"❌ Companies endpoint failed: {response.status_code}")
print(f" Response: {response.text}")
except Exception as e:
print(f"❌ Companies endpoint error: {e}")
# Test 3: Transformed data endpoints
print("\n3. Testing transformed data endpoints...")
try:
# Get companies first
response = requests.get(f"{base_url}/results/companies", timeout=10)
if response.status_code == 200:
companies = response.json().get('companies', [])
for company in companies:
if company.get('transformed_analyses', 0) > 0:
symbol = company['symbol']
print(f"\n Testing {symbol} transformed data...")
# Test company transformed results
response = requests.get(f"{base_url}/transformed-results/{symbol}", timeout=10)
if response.status_code == 200:
results = response.json().get('results', [])
print(f"{symbol} has {len(results)} transformed analyses")
# Test specific transformed result
if results:
first_result = results[0]
date = first_result['date']
response = requests.get(f"{base_url}/transformed-results/{symbol}/{date}", timeout=10)
if response.status_code == 200:
data = response.json()
print(f" ✅ Specific analysis loaded for {symbol} on {date}")
# Check data structure
if 'metadata' in data and 'financial_data' in data:
print(f" ✅ Data structure is correct")
metadata = data['metadata']
print(f" - Company: {metadata.get('company_ticker', 'N/A')}")
print(f" - Recommendation: {metadata.get('final_recommendation', 'N/A')}")
print(f" - Confidence: {metadata.get('confidence_level', 'N/A')}")
print(f" - Current Price: ${metadata.get('current_price', 0)}")
else:
print(f" ⚠️ Data structure may be incomplete")
else:
print(f" ❌ Failed to load specific analysis: {response.status_code}")
else:
print(f" ❌ Failed to load {symbol} transformed data: {response.status_code}")
print(f" Response: {response.text}")
break # Test only first company with transformed data
except Exception as e:
print(f"❌ Transformed data endpoints error: {e}")
print("\n" + "=" * 50)
print("🏁 Backend API test completed")
return True
if __name__ == "__main__":
test_backend()