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
*.csv
# src/
# eval_results/
eval_results/
eval_data/
*.egg-info/
.env

View File

@ -1,13 +1,11 @@
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Dict, Any, List, Optional
from typing import Dict, Any, Optional
import json
import os
import asyncio
from datetime import datetime, date
from datetime import datetime
import glob
from pathlib import Path
import uuid
# Import your TradingAgents components
@ -18,6 +16,9 @@ from tradingagents.default_config import DEFAULT_CONFIG
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
app.add_middleware(
CORSMiddleware,
@ -69,6 +70,8 @@ async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config
# Initialize TradingAgents
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)
# 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):
"""Start a new trading analysis"""
job_id = str(uuid.uuid4())
# Normalize inputs
symbol = request.symbol.upper().strip()
date = request.date.strip()
# Validate date format
try:
datetime.strptime(request.date, "%Y-%m-%d")
datetime.strptime(date, "%Y-%m-%d")
except ValueError:
raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
# Initialize job
jobs[job_id] = JobStatus(
job_id=job_id,
status="queued",
progress="Analysis queued"
)
# Start background task
background_tasks.add_task(
run_analysis_task,
job_id,
request.symbol.upper(),
request.date,
symbol,
date,
request.config_overrides or {}
)
return AnalysisResponse(
job_id=job_id,
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)
@ -133,7 +140,7 @@ async def get_analysis_status(job_id: str):
@app.get("/results/companies")
async def get_companies():
"""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):
return {"companies": []}
@ -176,7 +183,7 @@ async def get_companies():
@app.get("/results/{symbol}")
async def get_company_results(symbol: str):
"""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):
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}")
async def get_transformed_company_results(symbol: str):
"""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):
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}")
async def get_specific_result(symbol: str, date: str):
"""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):
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}")
async def get_specific_transformed_result(symbol: str, date: str):
"""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):
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 () => {
setIsRunningAnalysis(true);
try {
const response = await axios.post('/run-analysis', analysisForm);
const response = await axios.post('/analysis/start', analysisForm);
alert('Analysis completed successfully!');
setShowAnalysisModal(false);
setAnalysisForm({ symbol: '', date: '' });

View File

@ -1,5 +1,4 @@
import React from 'react';
import AnalysisWidgets from '../pages/AnalysisWidgets.tsx';
// New interface for the transformed JSON structure
interface TransformedAnalysisData {
@ -379,58 +378,226 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
widgets_config: transformedData?.widgets_config || buildTransformedWidgetsConfig(transformedData)
};
// Minimalist Technical Indicators widget (single-card dashboard)
const TechnicalIndicatorsOnly: React.FC<{ data: TransformedAnalysisData }> = ({ data }) => {
const ti = data?.technical_indicators || {} as any;
// Minimalist dashboard that shows ALL main sections of transformed JSON
const MinimalTransformedDashboard: React.FC<{ data: TransformedAnalysisData }> = ({ data }) => {
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 fmt = (v: any, d = 0) => {
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 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 cls = map[String(dir || 'NEUTRAL').toUpperCase()] || map.NEUTRAL;
return <span className={`px-2 py-1 rounded text-xs font-medium ${cls}`}>{label}: {dir || 'NEUTRAL'}</span>;
const fmtNumber = (v: any, d = 0) => {
const n = Number(v);
if (!Number.isFinite(n)) return '-';
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 (
<div className="min-h-[200px] p-6">
<div className="max-w-3xl mx-auto">
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">Technical Indicators</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="p-6">
<div className="mx-auto max-w-6xl space-y-4">
{/* Metadata - Full width */}
<div className="bg-white rounded-lg shadow p-6">
<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>
<p className="text-sm text-gray-500">RSI</p>
<p className="text-lg font-semibold">{fmt(ti?.rsi, 2)}</p>
</div>
<div>
<p className="text-sm text-gray-500">MACD</p>
<p className="text-lg font-semibold">{fmt(ti?.macd, 3)}</p>
<p className="text-xs text-gray-500">Signal: {fmt(ti?.macd_signal, 3)}</p>
</div>
<div>
<p className="text-sm text-gray-500">SMA 50</p>
<p className="text-lg font-semibold">{fmt(ti?.sma_50, 2)}</p>
</div>
<div>
<p className="text-sm text-gray-500">SMA 200</p>
<p className="text-lg font-semibold">{fmt(ti?.sma_200, 2)}</p>
</div>
{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 className="flex gap-2 border-b border-gray-200 mb-3">
{debateTabs.map(t => (
<button
key={t.key}
onClick={() => setActiveDebateTab(t.key)}
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'}`}
>{t.label}</button>
))}
</div>
<div className="text-sm text-gray-800 space-y-2 max-h-64 overflow-auto pr-1">
{debateTabs.find(t => t.key === activeDebateTab)?.content?.length ? (
<ul className="list-disc list-inside">
{(debateTabs.find(t => t.key === activeDebateTab)?.content as any[]).map((c, i) => <li key={i}>{c}</li>)}
</ul>
) : (
<p className="text-gray-500">No content.</p>
)}
</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 className="mt-6 flex flex-wrap gap-2">
{trendChip('SMA 50', trends?.sma_50)}
{trendChip('SMA 200', trends?.sma_200)}
{trends?.ema_10 !== undefined && trendChip('EMA 10', trends?.ema_10)}
{trends?.price_action !== undefined && trendChip('Price', trends?.price_action)}
{/* Text Content with sub-tabs */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-lg font-semibold mb-3">Reports</h2>
{!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>
@ -438,8 +605,8 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
);
};
// Only render the minimalist Technical Indicators widget for transformed data
return <TechnicalIndicatorsOnly data={transformedWithConfig} />;
// Render minimalist dashboard with all keys from the transformed JSON
return <MinimalTransformedDashboard data={transformedWithConfig} />;
};
export default TransformedDataAdapter;

View File

@ -101,6 +101,21 @@ class TransformedDataService {
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[]> {
if (this.cachedFiles) {
return this.cachedFiles;
@ -108,38 +123,23 @@ class TransformedDataService {
try {
const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`);
const companiesData = companiesResponse.data;
const companies = companiesData.companies || [];
const files: TransformedAnalysis[] = [];
for (const company of companies) {
if (company.transformed_analyses > 0) {
try {
const resultsResponse = await axios.get(`${this.baseUrl}/transformed-results/${company.symbol}`);
const resultsData = resultsResponse.data;
const results = resultsData.results || [];
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 companies = (companiesResponse.data?.companies || [])
.filter((c: any) => (c?.transformed_analyses ?? 0) > 0);
// Fetch each company's transformed results in parallel
const companyFetches = companies.map(async (company: any) => {
try {
const resultsResponse = await axios.get(`${this.baseUrl}/transformed-results/${company.symbol}`);
const results = resultsResponse.data?.results || [];
return results.map((r: any) => this.buildFileEntry(company.symbol, r));
} catch (err) {
console.warn(`Failed to fetch transformed results for ${company.symbol}:`, err);
return [] as TransformedAnalysis[];
}
}
});
const resultsByCompany = await Promise.all(companyFetches);
const files = resultsByCompany.flat();
this.cachedFiles = files;
} catch (error) {
console.warn('Could not load transformed data files:', error);
@ -164,7 +164,12 @@ class TransformedDataService {
const files = await this.getAvailableFiles();
const fileEntry = files.find(f => f.symbol === symbol && f.date === date);
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}`);

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()