List transformed outputs
This commit is contained in:
parent
d3fccbd4e4
commit
4c6564b5d1
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
{/* Header */}
|
||||
|
|
@ -424,7 +452,7 @@ function App() {
|
|||
<span className="text-2xl">📈</span>
|
||||
</span>
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-semibold">View Results</div>
|
||||
<div className="text-lg font-semibold">View Agent Outputs</div>
|
||||
<div className="text-sm opacity-90">Browse analysis results</div>
|
||||
</div>
|
||||
</button>
|
||||
|
|
@ -437,7 +465,7 @@ function App() {
|
|||
<span className="text-2xl">🔄</span>
|
||||
</span>
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-semibold">Transformed Data</div>
|
||||
<div className="text-lg font-semibold">Visualize Output Data</div>
|
||||
<div className="text-sm opacity-90">View enhanced analyses</div>
|
||||
</div>
|
||||
</button>
|
||||
|
|
@ -451,16 +479,51 @@ function App() {
|
|||
<div className="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-md bg-white">
|
||||
<div className="mt-3">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">Transformed Analysis Data</h3>
|
||||
<button
|
||||
onClick={closeAllModals}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
{selectedTransformedCompany ? `${selectedTransformedCompany} Transformed Analyses` : 'Transformed Analysis Data'}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedTransformedCompany && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedTransformedCompany(null);
|
||||
setTransformedCompanyFiles([]);
|
||||
}}
|
||||
className="text-sm px-3 py-1 rounded-md border text-gray-700 hover:bg-gray-50"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
transformedDataService.clearCache();
|
||||
const [files, summary] = await Promise.all([
|
||||
transformedDataService.getAvailableFiles(),
|
||||
transformedDataService.getDataSummary(),
|
||||
]);
|
||||
setTransformedDataFiles(files);
|
||||
setTransformedDataSummary(summary);
|
||||
setTransformedDataError(null);
|
||||
} catch (e) {
|
||||
console.error('Refresh failed:', e);
|
||||
setTransformedDataError('Refresh failed');
|
||||
}
|
||||
}}
|
||||
className="text-sm px-3 py-1 rounded-md border text-gray-700 hover:bg-gray-50"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
<button
|
||||
onClick={closeAllModals}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{transformedDataError && (
|
||||
|
|
@ -501,31 +564,57 @@ function App() {
|
|||
)}
|
||||
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
{transformedDataFiles.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-gray-500 mb-2">No transformed data files found</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
Run the data transformation agent to generate transformed analyses
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{transformedDataFiles.map((file, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-md hover:bg-gray-100">
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{file.displayName}</div>
|
||||
<div className="text-sm text-gray-500">{file.filename}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => openTransformedWidgetsView(file)}
|
||||
disabled={isLoadingTransformedData}
|
||||
className="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 disabled:opacity-50"
|
||||
{!selectedTransformedCompany ? (
|
||||
// Company list view (only companies with transformed analyses)
|
||||
companies && companies.filter(c => (c.transformed_analyses || 0) > 0).length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{companies.filter(c => (c.transformed_analyses || 0) > 0).map((company) => (
|
||||
<div
|
||||
key={company.symbol}
|
||||
className="flex items-center justify-between p-3 bg-gray-50 rounded-md hover:bg-gray-100 cursor-pointer"
|
||||
onClick={() => fetchTransformedCompanyFiles(company.symbol)}
|
||||
>
|
||||
{isLoadingTransformedData ? 'Loading...' : 'View Dashboard'}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{company.symbol}</div>
|
||||
<div className="text-sm text-gray-500">Transformed analyses: {company.transformed_analyses}</div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
View Files
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-gray-500 mb-2">No companies with transformed data found</div>
|
||||
<div className="text-sm text-gray-400">Run the data transformation agent to generate transformed analyses</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
// File list for selected transformed company
|
||||
transformedCompanyFiles.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{transformedCompanyFiles.map((file, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-md hover:bg-gray-100">
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{selectedTransformedCompany} - {file.date}</div>
|
||||
<div className="text-sm text-gray-500">{file.filename}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => openTransformedWidgetsView(file)}
|
||||
disabled={isLoadingTransformedData}
|
||||
className="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 disabled:opacity-50"
|
||||
>
|
||||
{isLoadingTransformedData ? 'Loading...' : 'View Dashboard'}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-gray-500 mb-2">No transformed files found for {selectedTransformedCompany}</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const Dashboard: React.FC = () => {
|
|||
<div className="flex items-center">
|
||||
<Database className="h-8 w-8 mr-4" />
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">View Results</h3>
|
||||
<h3 className="text-xl font-semibold">View Agent Outputs</h3>
|
||||
<p className="text-success-100">Browse historical analysis results</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<string, TransformedAnalysisData> = 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<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of available transformed data files
|
||||
*/
|
||||
async getAvailableFiles(): Promise<TransformedDataFile[]> {
|
||||
class TransformedDataService {
|
||||
private baseUrl = API_BASE_URL;
|
||||
private cachedFiles: TransformedAnalysis[] | null = null;
|
||||
private cachedData: Map<string, TransformedData> = new Map();
|
||||
|
||||
async getAvailableFiles(): Promise<TransformedAnalysis[]> {
|
||||
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<TransformedDataFile[]> {
|
||||
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<TransformedAnalysisData> {
|
||||
// Check cache first
|
||||
async loadTransformedData(filename: string): Promise<TransformedData> {
|
||||
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<TransformedAnalysisData> {
|
||||
const filename = `${company}_full_states_log_${date}_transformed.json`;
|
||||
return this.loadTransformedData(filename);
|
||||
async loadByCompanyAndDate(company: string, date: string): Promise<TransformedData> {
|
||||
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<TransformedAnalysisData | null> {
|
||||
async getLatestForCompany(company: string): Promise<TransformedData | null> {
|
||||
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<string[]> {
|
||||
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<string[]> {
|
||||
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<TransformedDataFile[]> {
|
||||
}): Promise<TransformedAnalysis[]> {
|
||||
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<string, number> = {};
|
||||
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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue