diff --git a/web_app/backend/main.py b/web_app/backend/main.py index 08818a12..8029ea3d 100644 --- a/web_app/backend/main.py +++ b/web_app/backend/main.py @@ -142,18 +142,34 @@ async def get_companies(): for company_dir in os.listdir(results_dir): company_path = os.path.join(results_dir, company_dir) if os.path.isdir(company_path): - # Get latest analysis date + # Check both regular logs and transformed logs logs_dir = os.path.join(company_path, "TradingAgentsStrategy_logs") + transformed_logs_dir = os.path.join(company_path, "TradingAgentsStrategy_transformed_logs") + + total_analyses = 0 + latest_date = None + + # Count regular analyses if os.path.exists(logs_dir): json_files = glob.glob(os.path.join(logs_dir, "*.json")) + total_analyses += len(json_files) if json_files: latest_file = max(json_files, key=os.path.getctime) latest_date = os.path.basename(latest_file).replace("full_states_log_", "").replace(".json", "") - companies.append({ - "symbol": company_dir, - "latest_analysis": latest_date, - "total_analyses": len(json_files) - }) + + # Count transformed analyses + transformed_count = 0 + if os.path.exists(transformed_logs_dir): + transformed_files = glob.glob(os.path.join(transformed_logs_dir, "*_transformed.json")) + transformed_count = len(transformed_files) + + if total_analyses > 0 or transformed_count > 0: + companies.append({ + "symbol": company_dir, + "latest_analysis": latest_date, + "total_analyses": total_analyses, + "transformed_analyses": transformed_count + }) return {"companies": companies} @@ -195,6 +211,47 @@ async def get_company_results(symbol: str): return {"symbol": symbol.upper(), "results": results} +@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" + + if not os.path.exists(results_dir): + raise HTTPException(status_code=404, detail=f"No transformed results found for {symbol}") + + results = [] + json_files = glob.glob(os.path.join(results_dir, "*_transformed.json")) + + for file_path in sorted(json_files, key=os.path.getctime, reverse=True): + filename = os.path.basename(file_path) + # Extract date from filename like "full_states_log_2025-07-26_transformed.json" + analysis_date = filename.replace("full_states_log_", "").replace("_transformed.json", "") + + try: + with open(file_path, 'r') as f: + data = json.load(f) + + results.append({ + "date": analysis_date, + "filename": filename, + "file_size": os.path.getsize(file_path), + "modified_at": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat(), + "preview": { + "company_ticker": data.get("metadata", {}).get("company_ticker", "N/A"), + "final_recommendation": data.get("metadata", {}).get("final_recommendation", "N/A"), + "confidence_level": data.get("metadata", {}).get("confidence_level", "N/A"), + "current_price": data.get("financial_data", {}).get("current_price", 0) + } + }) + except Exception as e: + results.append({ + "date": analysis_date, + "filename": filename, + "error": f"Could not read file: {str(e)}" + }) + + return {"symbol": symbol.upper(), "results": results} + @app.get("/results/{symbol}/{date}") async def get_specific_result(symbol: str, date: str): """Get specific analysis result""" @@ -219,6 +276,31 @@ async def get_specific_result(symbol: str, date: str): except Exception as e: raise HTTPException(status_code=500, detail=f"Error reading result: {str(e)}") +@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" + + if not os.path.exists(file_path): + raise HTTPException(status_code=404, detail=f"Transformed result not found for {symbol} on {date}") + + try: + with open(file_path, 'r') as f: + data = json.load(f) + + return { + "symbol": symbol.upper(), + "date": date, + "data": data, + "file_info": { + "filename": os.path.basename(file_path), + "file_size": os.path.getsize(file_path), + "modified_at": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat() + } + } + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error reading file: {str(e)}") + @app.get("/config") async def get_default_config(): """Get the default configuration""" diff --git a/web_app/frontend/src/App.js b/web_app/frontend/src/App.js index f2b2ee75..d5e527ff 100644 --- a/web_app/frontend/src/App.js +++ b/web_app/frontend/src/App.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import axios from 'axios'; import AnalysisDataAdapter from './components/AnalysisDataAdapter.tsx'; import TransformedDataAdapter from './components/TransformedDataAdapter.tsx'; @@ -26,10 +26,12 @@ function App() { const [selectedTransformedData, setSelectedTransformedData] = useState(null); const [isLoadingTransformedData, setIsLoadingTransformedData] = useState(false); const [transformedDataError, setTransformedDataError] = useState(null); + const [selectedTransformedCompany, setSelectedTransformedCompany] = useState(null); + const [transformedCompanyFiles, setTransformedCompanyFiles] = useState([]); const [activeDetailTab, setActiveDetailTab] = useState(null); // Fields to display as pretty cards in the Details modal - const detailFields = [ + const detailFields = useMemo(() => ([ 'market_report', 'sentiment_report', 'news_report', @@ -49,7 +51,7 @@ function App() { 'risk_debate_state.judge_decision', 'company_of_interest', 'trade_date', - ]; + ]), []); const fieldLabelMap = { market_report: 'Market Report', @@ -169,7 +171,7 @@ function App() { } else if (!activeDetailTab || !available.includes(activeDetailTab)) { setActiveDetailTab(available[0]); } - }, [resultDetail, selectedResult]); + }, [resultDetail, selectedResult, detailFields, activeDetailTab]); const checkBackendStatus = async () => { try { @@ -250,7 +252,8 @@ function App() { setTransformedDataError(null); try { - const transformedData = await transformedDataService.loadTransformedData(file.filename); + // Load by company and date to avoid relying on cached index + const transformedData = await transformedDataService.loadByCompanyAndDate(selectedTransformedCompany, file.date); setSelectedTransformedData(transformedData); setShowWidgetsView(true); } catch (error) { @@ -279,9 +282,23 @@ function App() { setCompanyResults([]); }; - const handleViewTransformedData = () => { + const handleViewTransformedData = async () => { setShowTransformedDataModal(true); setTransformedDataError(null); + setSelectedTransformedCompany(null); + setTransformedCompanyFiles([]); + try { + transformedDataService.clearCache(); + const [files, summary] = await Promise.all([ + transformedDataService.getAvailableFiles(), + transformedDataService.getDataSummary(), + ]); + setTransformedDataFiles(files); + setTransformedDataSummary(summary); + } catch (e) { + console.error('Failed to load transformed data on open:', e); + setTransformedDataError('Failed to load transformed data'); + } }; const runAnalysis = async () => { @@ -312,6 +329,17 @@ function App() { setTransformedDataError(null); }; + const fetchTransformedCompanyFiles = async (symbol) => { + try { + const response = await axios.get(`/transformed-results/${symbol}`); + setTransformedCompanyFiles(response.data.results || []); + setSelectedTransformedCompany(symbol); + } catch (error) { + console.error('Error fetching transformed company files:', error); + setTransformedDataError('Error loading transformed company files'); + } + }; + return (
{/* Header */} @@ -424,7 +452,7 @@ function App() { 📈
-
View Results
+
View Agent Outputs
Browse analysis results
@@ -437,7 +465,7 @@ function App() { 🔄
-
Transformed Data
+
Visualize Output Data
View enhanced analyses
@@ -451,16 +479,51 @@ function App() {
-

Transformed Analysis Data

- +

+ {selectedTransformedCompany ? `${selectedTransformedCompany} Transformed Analyses` : 'Transformed Analysis Data'} +

+
+ {selectedTransformedCompany && ( + + )} + + +
{transformedDataError && ( @@ -501,31 +564,57 @@ function App() { )}
- {transformedDataFiles.length === 0 ? ( -
-
No transformed data files found
-
- Run the data transformation agent to generate transformed analyses -
-
- ) : ( -
- {transformedDataFiles.map((file, index) => ( -
-
-
{file.displayName}
-
{file.filename}
-
- -
- ))} -
+
+
{company.symbol}
+
Transformed analyses: {company.transformed_analyses}
+
+
+ View Files +
+
+ ))} +
+ ) : ( +
+
No companies with transformed data found
+
Run the data transformation agent to generate transformed analyses
+
+ ) + ) : ( + // File list for selected transformed company + transformedCompanyFiles.length > 0 ? ( +
+ {transformedCompanyFiles.map((file, index) => ( +
+
+
{selectedTransformedCompany} - {file.date}
+
{file.filename}
+
+ +
+ ))} +
+ ) : ( +
+
No transformed files found for {selectedTransformedCompany}
+
+ ) )}
diff --git a/web_app/frontend/src/components/Layout.tsx b/web_app/frontend/src/components/Layout.tsx index 796d83b0..a453652d 100644 --- a/web_app/frontend/src/components/Layout.tsx +++ b/web_app/frontend/src/components/Layout.tsx @@ -8,7 +8,7 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => { const navigation = [ { name: 'Dashboard', href: '/', icon: Home }, { name: 'Run Analysis', href: '/run-analysis', icon: Play }, - { name: 'View Results', href: '/results', icon: Database }, + { name: 'View Agent Outputs', href: '/results', icon: Database }, ]; return ( diff --git a/web_app/frontend/src/pages/Dashboard.tsx b/web_app/frontend/src/pages/Dashboard.tsx index e5e4f36e..1f8678e5 100644 --- a/web_app/frontend/src/pages/Dashboard.tsx +++ b/web_app/frontend/src/pages/Dashboard.tsx @@ -53,7 +53,7 @@ const Dashboard: React.FC = () => {
-

View Results

+

View Agent Outputs

Browse historical analysis results

diff --git a/web_app/frontend/src/services/transformedDataService.ts b/web_app/frontend/src/services/transformedDataService.ts index 619ea38d..c3f2f047 100644 --- a/web_app/frontend/src/services/transformedDataService.ts +++ b/web_app/frontend/src/services/transformedDataService.ts @@ -1,126 +1,193 @@ -import { TransformedAnalysisData } from '../components/TransformedDataAdapter.tsx'; +import axios from 'axios'; -export interface TransformedDataFile { +const API_BASE_URL = 'http://localhost:8000'; + +export interface TransformedAnalysis { filename: string; - company: string; date: string; + modified_at: string; + file_size: number; + preview?: { + final_recommendation: string; + confidence_level: string; + current_price: number; + }; + error?: string; + symbol: string; displayName: string; } -class TransformedDataService { - private baseUrl = '/transformed_data'; - private cachedFiles: TransformedDataFile[] | null = null; - private cachedData: Map = new Map(); +export interface TransformedData { + metadata: { + company_ticker: string; + analysis_date: string; + final_recommendation: string; + confidence_level: string; + current_price: number; + target_price: number; + risk_level: string; + time_horizon: string; + }; + financial_data: { + current_price: number; + target_price: number; + price_change_1d: number; + price_change_1w: number; + price_change_1m: number; + market_cap: number; + volume: number; + pe_ratio: number; + revenue: number; + profit_margin: number; + debt_to_equity: number; + roe: number; + dividend_yield: number; + }; + technical_indicators: { + rsi: number; + macd: number; + moving_avg_20: number; + moving_avg_50: number; + bollinger_upper: number; + bollinger_lower: number; + support_level: number; + resistance_level: number; + }; + investment_strategy: { + position_size: number; + entry_price: number; + stop_loss: number; + take_profit: number; + holding_period: string; + risk_reward_ratio: number; + }; + debate_summary: { + bull_case: { + key_points: string[]; + strength_score: number; + }; + bear_case: { + key_points: string[]; + strength_score: number; + }; + consensus: string; + }; + text_content: { + executive_summary: string; + key_takeaways: string[]; + detailed_analysis: string; + risk_factors: string[]; + catalysts: string[]; + }; + widget_config: { + charts_enabled: string[]; + priority_widgets: string[]; + display_preferences: Record; + }; +} - /** - * Get list of available transformed data files - */ - async getAvailableFiles(): Promise { +class TransformedDataService { + private baseUrl = API_BASE_URL; + private cachedFiles: TransformedAnalysis[] | null = null; + private cachedData: Map = new Map(); + + async getAvailableFiles(): Promise { if (this.cachedFiles) { return this.cachedFiles; } try { - // In a real implementation, you might have an API endpoint that lists files - // For now, we'll try to load a manifest file or use a predefined list - const response = await fetch(`${this.baseUrl}/manifest.json`); + const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`); + const companiesData = companiesResponse.data; + const companies = companiesData.companies || []; - if (response.ok) { - const manifest = await response.json(); - this.cachedFiles = manifest.files || []; - } else { - // Fallback: try to load some common files - this.cachedFiles = await this.discoverFiles(); + 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) { + 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}` + }); + } + } catch (error) { + console.warn(`Failed to fetch transformed results for ${company.symbol}:`, error); + } + } } + + this.cachedFiles = files; } catch (error) { - console.warn('Could not load transformed data manifest, using fallback discovery:', error); - this.cachedFiles = await this.discoverFiles(); + console.warn('Could not load transformed data files:', error); + this.cachedFiles = []; } return this.cachedFiles; } - /** - * Discover available files by trying common patterns - */ - private async discoverFiles(): Promise { - const companies = ['AVAH', 'PLTR', 'RDDT']; - const dates = [ - '2025-07-26', '2025-08-05', '2025-08-06', '2025-08-07' - ]; - - const files: TransformedDataFile[] = []; - - for (const company of companies) { - for (const date of dates) { - const filename = `${company}_full_states_log_${date}_transformed.json`; - - try { - const response = await fetch(`${this.baseUrl}/${filename}`, { method: 'HEAD' }); - if (response.ok) { - files.push({ - filename, - company, - date, - displayName: `${company} - ${date}` - }); - } - } catch (error) { - // File doesn't exist, skip - } - } - } - - return files; - } - - /** - * Load a specific transformed data file - */ - async loadTransformedData(filename: string): Promise { - // Check cache first + async loadTransformedData(filename: string): Promise { if (this.cachedData.has(filename)) { return this.cachedData.get(filename)!; } try { - const response = await fetch(`${this.baseUrl}/${filename}`); + const match = filename.match(/full_states_log_(.+)_transformed\.json/); + if (!match) { + throw new Error(`Invalid filename format: ${filename}`); + } + const date = match[1]; - if (!response.ok) { - throw new Error(`Failed to load ${filename}: ${response.status} ${response.statusText}`); + const files = await this.getAvailableFiles(); + const fileEntry = files.find(f => f.filename === filename); + if (!fileEntry) { + throw new Error(`File not found in index: ${filename}`); } - const data: TransformedAnalysisData = await response.json(); + const response = await axios.get(`${this.baseUrl}/transformed-results/${fileEntry.symbol}/${date}`); + const responseData = response.data; + const data: TransformedData = responseData.data; - // Validate the data structure this.validateTransformedData(data); - // Cache the data this.cachedData.set(filename, data); return data; } catch (error) { - console.error(`Error loading transformed data file ${filename}:`, error); + console.error(`Error loading transformed data from ${filename}:`, error); throw error; } } - /** - * Load transformed data by company and date - */ - async loadByCompanyAndDate(company: string, date: string): Promise { - const filename = `${company}_full_states_log_${date}_transformed.json`; - return this.loadTransformedData(filename); + async loadByCompanyAndDate(company: string, date: string): Promise { + const files = await this.getAvailableFiles(); + const entry = files.find(f => f.symbol === company && f.date === date); + if (entry) { + return this.loadTransformedData(entry.filename); + } + + const response = await axios.get(`${this.baseUrl}/transformed-results/${company}/${date}`); + const data: TransformedData = response.data.data; + this.validateTransformedData(data); + return data; } - /** - * Get the most recent analysis for a company - */ - async getLatestForCompany(company: string): Promise { + async getLatestForCompany(company: string): Promise { const files = await this.getAvailableFiles(); const companyFiles = files - .filter(f => f.company === company) - .sort((a, b) => b.date.localeCompare(a.date)); // Sort by date descending + .filter(f => f.symbol === company) + .sort((a, b) => b.date.localeCompare(a.date)); if (companyFiles.length === 0) { return null; @@ -129,31 +196,22 @@ class TransformedDataService { return this.loadTransformedData(companyFiles[0].filename); } - /** - * Get all available companies - */ async getAvailableCompanies(): Promise { const files = await this.getAvailableFiles(); - const companies = [...new Set(files.map(f => f.company))]; - return companies.sort(); + const companies = [...new Set(files.map(f => f.symbol))].sort(); + return companies; } - /** - * Get available dates for a specific company - */ async getAvailableDatesForCompany(company: string): Promise { const files = await this.getAvailableFiles(); const dates = files - .filter(f => f.company === company) + .filter(f => f.symbol === company) .map(f => f.date) - .sort((a, b) => b.localeCompare(a)); // Sort by date descending + .sort((a, b) => b.localeCompare(a)); return dates; } - /** - * Validate that the loaded data conforms to the expected structure - */ private validateTransformedData(data: any): void { const requiredSections = [ 'metadata', @@ -162,7 +220,7 @@ class TransformedDataService { 'investment_strategy', 'debate_summary', 'text_content', - 'widgets_config' + 'widget_config' ]; for (const section of requiredSections) { @@ -171,49 +229,37 @@ class TransformedDataService { } } - // Validate metadata const metadata = data.metadata; if (!metadata.company_ticker || !metadata.analysis_date) { throw new Error('Invalid metadata: missing company_ticker or analysis_date'); } - // Validate that dates are in correct format const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(metadata.analysis_date)) { throw new Error('Invalid date format in metadata.analysis_date'); } } - /** - * Clear all cached data - */ clearCache(): void { this.cachedFiles = null; this.cachedData.clear(); } - /** - * Create a manifest file content for available transformed data - * This can be used to generate a manifest.json file - */ - async generateManifest(): Promise<{ files: TransformedDataFile[] }> { - const files = await this.discoverFiles(); + async generateManifest(): Promise<{ files: TransformedAnalysis[] }> { + const files = await this.getAvailableFiles(); return { files }; } - /** - * Search for analyses by various criteria - */ async searchAnalyses(criteria: { company?: string; dateFrom?: string; dateTo?: string; recommendation?: 'BUY' | 'SELL' | 'HOLD'; - }): Promise { + }): Promise { const files = await this.getAvailableFiles(); return files.filter(file => { - if (criteria.company && file.company !== criteria.company) { + if (criteria.company && file.symbol !== criteria.company) { return false; } @@ -225,16 +271,10 @@ class TransformedDataService { return false; } - // For recommendation filtering, we'd need to load the actual data - // This is left as a future enhancement - return true; }); } - /** - * Get summary statistics about available data - */ async getDataSummary(): Promise<{ totalFiles: number; companies: string[]; @@ -243,12 +283,13 @@ class TransformedDataService { }> { const files = await this.getAvailableFiles(); - const companies = [...new Set(files.map(f => f.company))].sort(); + const companies = [...new Set(files.map(f => f.symbol))].sort(); const dates = files.map(f => f.date).sort(); const companyCounts: Record = {}; files.forEach(file => { - companyCounts[file.company] = (companyCounts[file.company] || 0) + 1; + const company = file.symbol; + companyCounts[company] = (companyCounts[company] || 0) + 1; }); return { @@ -263,6 +304,5 @@ class TransformedDataService { } } -// Export a singleton instance export const transformedDataService = new TransformedDataService(); export default transformedDataService;