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):
|
for company_dir in os.listdir(results_dir):
|
||||||
company_path = os.path.join(results_dir, company_dir)
|
company_path = os.path.join(results_dir, company_dir)
|
||||||
if os.path.isdir(company_path):
|
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")
|
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):
|
if os.path.exists(logs_dir):
|
||||||
json_files = glob.glob(os.path.join(logs_dir, "*.json"))
|
json_files = glob.glob(os.path.join(logs_dir, "*.json"))
|
||||||
|
total_analyses += len(json_files)
|
||||||
if json_files:
|
if json_files:
|
||||||
latest_file = max(json_files, key=os.path.getctime)
|
latest_file = max(json_files, key=os.path.getctime)
|
||||||
latest_date = os.path.basename(latest_file).replace("full_states_log_", "").replace(".json", "")
|
latest_date = os.path.basename(latest_file).replace("full_states_log_", "").replace(".json", "")
|
||||||
companies.append({
|
|
||||||
"symbol": company_dir,
|
# Count transformed analyses
|
||||||
"latest_analysis": latest_date,
|
transformed_count = 0
|
||||||
"total_analyses": len(json_files)
|
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}
|
return {"companies": companies}
|
||||||
|
|
||||||
|
|
@ -195,6 +211,47 @@ async def get_company_results(symbol: str):
|
||||||
|
|
||||||
return {"symbol": symbol.upper(), "results": results}
|
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}")
|
@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"""
|
||||||
|
|
@ -219,6 +276,31 @@ async def get_specific_result(symbol: str, date: str):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"Error reading result: {str(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")
|
@app.get("/config")
|
||||||
async def get_default_config():
|
async def get_default_config():
|
||||||
"""Get the default configuration"""
|
"""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 axios from 'axios';
|
||||||
import AnalysisDataAdapter from './components/AnalysisDataAdapter.tsx';
|
import AnalysisDataAdapter from './components/AnalysisDataAdapter.tsx';
|
||||||
import TransformedDataAdapter from './components/TransformedDataAdapter.tsx';
|
import TransformedDataAdapter from './components/TransformedDataAdapter.tsx';
|
||||||
|
|
@ -26,10 +26,12 @@ function App() {
|
||||||
const [selectedTransformedData, setSelectedTransformedData] = useState(null);
|
const [selectedTransformedData, setSelectedTransformedData] = useState(null);
|
||||||
const [isLoadingTransformedData, setIsLoadingTransformedData] = useState(false);
|
const [isLoadingTransformedData, setIsLoadingTransformedData] = useState(false);
|
||||||
const [transformedDataError, setTransformedDataError] = useState(null);
|
const [transformedDataError, setTransformedDataError] = useState(null);
|
||||||
|
const [selectedTransformedCompany, setSelectedTransformedCompany] = useState(null);
|
||||||
|
const [transformedCompanyFiles, setTransformedCompanyFiles] = useState([]);
|
||||||
const [activeDetailTab, setActiveDetailTab] = useState(null);
|
const [activeDetailTab, setActiveDetailTab] = useState(null);
|
||||||
|
|
||||||
// Fields to display as pretty cards in the Details modal
|
// Fields to display as pretty cards in the Details modal
|
||||||
const detailFields = [
|
const detailFields = useMemo(() => ([
|
||||||
'market_report',
|
'market_report',
|
||||||
'sentiment_report',
|
'sentiment_report',
|
||||||
'news_report',
|
'news_report',
|
||||||
|
|
@ -49,7 +51,7 @@ function App() {
|
||||||
'risk_debate_state.judge_decision',
|
'risk_debate_state.judge_decision',
|
||||||
'company_of_interest',
|
'company_of_interest',
|
||||||
'trade_date',
|
'trade_date',
|
||||||
];
|
]), []);
|
||||||
|
|
||||||
const fieldLabelMap = {
|
const fieldLabelMap = {
|
||||||
market_report: 'Market Report',
|
market_report: 'Market Report',
|
||||||
|
|
@ -169,7 +171,7 @@ function App() {
|
||||||
} else if (!activeDetailTab || !available.includes(activeDetailTab)) {
|
} else if (!activeDetailTab || !available.includes(activeDetailTab)) {
|
||||||
setActiveDetailTab(available[0]);
|
setActiveDetailTab(available[0]);
|
||||||
}
|
}
|
||||||
}, [resultDetail, selectedResult]);
|
}, [resultDetail, selectedResult, detailFields, activeDetailTab]);
|
||||||
|
|
||||||
const checkBackendStatus = async () => {
|
const checkBackendStatus = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -250,7 +252,8 @@ function App() {
|
||||||
setTransformedDataError(null);
|
setTransformedDataError(null);
|
||||||
|
|
||||||
try {
|
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);
|
setSelectedTransformedData(transformedData);
|
||||||
setShowWidgetsView(true);
|
setShowWidgetsView(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -279,9 +282,23 @@ function App() {
|
||||||
setCompanyResults([]);
|
setCompanyResults([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewTransformedData = () => {
|
const handleViewTransformedData = async () => {
|
||||||
setShowTransformedDataModal(true);
|
setShowTransformedDataModal(true);
|
||||||
setTransformedDataError(null);
|
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 () => {
|
const runAnalysis = async () => {
|
||||||
|
|
@ -312,6 +329,17 @@ function App() {
|
||||||
setTransformedDataError(null);
|
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 (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|
@ -424,7 +452,7 @@ function App() {
|
||||||
<span className="text-2xl">📈</span>
|
<span className="text-2xl">📈</span>
|
||||||
</span>
|
</span>
|
||||||
<div className="text-center">
|
<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 className="text-sm opacity-90">Browse analysis results</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -437,7 +465,7 @@ function App() {
|
||||||
<span className="text-2xl">🔄</span>
|
<span className="text-2xl">🔄</span>
|
||||||
</span>
|
</span>
|
||||||
<div className="text-center">
|
<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 className="text-sm opacity-90">View enhanced analyses</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</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="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="mt-3">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900">Transformed Analysis Data</h3>
|
<h3 className="text-lg font-medium text-gray-900">
|
||||||
<button
|
{selectedTransformedCompany ? `${selectedTransformedCompany} Transformed Analyses` : 'Transformed Analysis Data'}
|
||||||
onClick={closeAllModals}
|
</h3>
|
||||||
className="text-gray-400 hover:text-gray-600"
|
<div className="flex items-center gap-2">
|
||||||
>
|
{selectedTransformedCompany && (
|
||||||
<span className="sr-only">Close</span>
|
<button
|
||||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
onClick={() => {
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
setSelectedTransformedCompany(null);
|
||||||
</svg>
|
setTransformedCompanyFiles([]);
|
||||||
</button>
|
}}
|
||||||
|
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>
|
</div>
|
||||||
|
|
||||||
{transformedDataError && (
|
{transformedDataError && (
|
||||||
|
|
@ -501,31 +564,57 @@ function App() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="max-h-96 overflow-y-auto">
|
<div className="max-h-96 overflow-y-auto">
|
||||||
{transformedDataFiles.length === 0 ? (
|
{!selectedTransformedCompany ? (
|
||||||
<div className="text-center py-8">
|
// Company list view (only companies with transformed analyses)
|
||||||
<div className="text-gray-500 mb-2">No transformed data files found</div>
|
companies && companies.filter(c => (c.transformed_analyses || 0) > 0).length > 0 ? (
|
||||||
<div className="text-sm text-gray-400">
|
<div className="space-y-2">
|
||||||
Run the data transformation agent to generate transformed analyses
|
{companies.filter(c => (c.transformed_analyses || 0) > 0).map((company) => (
|
||||||
</div>
|
<div
|
||||||
</div>
|
key={company.symbol}
|
||||||
) : (
|
className="flex items-center justify-between p-3 bg-gray-50 rounded-md hover:bg-gray-100 cursor-pointer"
|
||||||
<div className="space-y-2">
|
onClick={() => fetchTransformedCompanyFiles(company.symbol)}
|
||||||
{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"
|
|
||||||
>
|
>
|
||||||
{isLoadingTransformedData ? 'Loading...' : 'View Dashboard'}
|
<div>
|
||||||
</button>
|
<div className="font-medium text-gray-900">{company.symbol}</div>
|
||||||
</div>
|
<div className="text-sm text-gray-500">Transformed analyses: {company.transformed_analyses}</div>
|
||||||
))}
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{ name: 'Dashboard', href: '/', icon: Home },
|
{ name: 'Dashboard', href: '/', icon: Home },
|
||||||
{ name: 'Run Analysis', href: '/run-analysis', icon: Play },
|
{ name: 'Run Analysis', href: '/run-analysis', icon: Play },
|
||||||
{ name: 'View Results', href: '/results', icon: Database },
|
{ name: 'View Agent Outputs', href: '/results', icon: Database },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ const Dashboard: React.FC = () => {
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Database className="h-8 w-8 mr-4" />
|
<Database className="h-8 w-8 mr-4" />
|
||||||
<div>
|
<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>
|
<p className="text-success-100">Browse historical analysis results</p>
|
||||||
</div>
|
</div>
|
||||||
</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;
|
filename: string;
|
||||||
company: string;
|
|
||||||
date: 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;
|
displayName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransformedDataService {
|
export interface TransformedData {
|
||||||
private baseUrl = '/transformed_data';
|
metadata: {
|
||||||
private cachedFiles: TransformedDataFile[] | null = null;
|
company_ticker: string;
|
||||||
private cachedData: Map<string, TransformedAnalysisData> = new Map();
|
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>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
class TransformedDataService {
|
||||||
* Get list of available transformed data files
|
private baseUrl = API_BASE_URL;
|
||||||
*/
|
private cachedFiles: TransformedAnalysis[] | null = null;
|
||||||
async getAvailableFiles(): Promise<TransformedDataFile[]> {
|
private cachedData: Map<string, TransformedData> = new Map();
|
||||||
|
|
||||||
|
async getAvailableFiles(): Promise<TransformedAnalysis[]> {
|
||||||
if (this.cachedFiles) {
|
if (this.cachedFiles) {
|
||||||
return this.cachedFiles;
|
return this.cachedFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// In a real implementation, you might have an API endpoint that lists files
|
const companiesResponse = await axios.get(`${this.baseUrl}/results/companies`);
|
||||||
// For now, we'll try to load a manifest file or use a predefined list
|
const companiesData = companiesResponse.data;
|
||||||
const response = await fetch(`${this.baseUrl}/manifest.json`);
|
const companies = companiesData.companies || [];
|
||||||
|
|
||||||
if (response.ok) {
|
const files: TransformedAnalysis[] = [];
|
||||||
const manifest = await response.json();
|
|
||||||
this.cachedFiles = manifest.files || [];
|
for (const company of companies) {
|
||||||
} else {
|
if (company.transformed_analyses > 0) {
|
||||||
// Fallback: try to load some common files
|
try {
|
||||||
this.cachedFiles = await this.discoverFiles();
|
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) {
|
} catch (error) {
|
||||||
console.warn('Could not load transformed data manifest, using fallback discovery:', error);
|
console.warn('Could not load transformed data files:', error);
|
||||||
this.cachedFiles = await this.discoverFiles();
|
this.cachedFiles = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.cachedFiles;
|
return this.cachedFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async loadTransformedData(filename: string): Promise<TransformedData> {
|
||||||
* 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
|
|
||||||
if (this.cachedData.has(filename)) {
|
if (this.cachedData.has(filename)) {
|
||||||
return this.cachedData.get(filename)!;
|
return this.cachedData.get(filename)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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) {
|
const files = await this.getAvailableFiles();
|
||||||
throw new Error(`Failed to load ${filename}: ${response.status} ${response.statusText}`);
|
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);
|
this.validateTransformedData(data);
|
||||||
|
|
||||||
// Cache the data
|
|
||||||
this.cachedData.set(filename, data);
|
this.cachedData.set(filename, data);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error loading transformed data file ${filename}:`, error);
|
console.error(`Error loading transformed data from ${filename}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async loadByCompanyAndDate(company: string, date: string): Promise<TransformedData> {
|
||||||
* Load transformed data by company and date
|
const files = await this.getAvailableFiles();
|
||||||
*/
|
const entry = files.find(f => f.symbol === company && f.date === date);
|
||||||
async loadByCompanyAndDate(company: string, date: string): Promise<TransformedAnalysisData> {
|
if (entry) {
|
||||||
const filename = `${company}_full_states_log_${date}_transformed.json`;
|
return this.loadTransformedData(entry.filename);
|
||||||
return this.loadTransformedData(filename);
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(`${this.baseUrl}/transformed-results/${company}/${date}`);
|
||||||
|
const data: TransformedData = response.data.data;
|
||||||
|
this.validateTransformedData(data);
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async getLatestForCompany(company: string): Promise<TransformedData | null> {
|
||||||
* Get the most recent analysis for a company
|
|
||||||
*/
|
|
||||||
async getLatestForCompany(company: string): Promise<TransformedAnalysisData | null> {
|
|
||||||
const files = await this.getAvailableFiles();
|
const files = await this.getAvailableFiles();
|
||||||
const companyFiles = files
|
const companyFiles = files
|
||||||
.filter(f => f.company === company)
|
.filter(f => f.symbol === company)
|
||||||
.sort((a, b) => b.date.localeCompare(a.date)); // Sort by date descending
|
.sort((a, b) => b.date.localeCompare(a.date));
|
||||||
|
|
||||||
if (companyFiles.length === 0) {
|
if (companyFiles.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -129,31 +196,22 @@ class TransformedDataService {
|
||||||
return this.loadTransformedData(companyFiles[0].filename);
|
return this.loadTransformedData(companyFiles[0].filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all available companies
|
|
||||||
*/
|
|
||||||
async getAvailableCompanies(): Promise<string[]> {
|
async getAvailableCompanies(): Promise<string[]> {
|
||||||
const files = await this.getAvailableFiles();
|
const files = await this.getAvailableFiles();
|
||||||
const companies = [...new Set(files.map(f => f.company))];
|
const companies = [...new Set(files.map(f => f.symbol))].sort();
|
||||||
return companies.sort();
|
return companies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get available dates for a specific company
|
|
||||||
*/
|
|
||||||
async getAvailableDatesForCompany(company: string): Promise<string[]> {
|
async getAvailableDatesForCompany(company: string): Promise<string[]> {
|
||||||
const files = await this.getAvailableFiles();
|
const files = await this.getAvailableFiles();
|
||||||
const dates = files
|
const dates = files
|
||||||
.filter(f => f.company === company)
|
.filter(f => f.symbol === company)
|
||||||
.map(f => f.date)
|
.map(f => f.date)
|
||||||
.sort((a, b) => b.localeCompare(a)); // Sort by date descending
|
.sort((a, b) => b.localeCompare(a));
|
||||||
|
|
||||||
return dates;
|
return dates;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that the loaded data conforms to the expected structure
|
|
||||||
*/
|
|
||||||
private validateTransformedData(data: any): void {
|
private validateTransformedData(data: any): void {
|
||||||
const requiredSections = [
|
const requiredSections = [
|
||||||
'metadata',
|
'metadata',
|
||||||
|
|
@ -162,7 +220,7 @@ class TransformedDataService {
|
||||||
'investment_strategy',
|
'investment_strategy',
|
||||||
'debate_summary',
|
'debate_summary',
|
||||||
'text_content',
|
'text_content',
|
||||||
'widgets_config'
|
'widget_config'
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const section of requiredSections) {
|
for (const section of requiredSections) {
|
||||||
|
|
@ -171,49 +229,37 @@ class TransformedDataService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate metadata
|
|
||||||
const metadata = data.metadata;
|
const metadata = data.metadata;
|
||||||
if (!metadata.company_ticker || !metadata.analysis_date) {
|
if (!metadata.company_ticker || !metadata.analysis_date) {
|
||||||
throw new Error('Invalid metadata: missing company_ticker or 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}$/;
|
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||||
if (!dateRegex.test(metadata.analysis_date)) {
|
if (!dateRegex.test(metadata.analysis_date)) {
|
||||||
throw new Error('Invalid date format in metadata.analysis_date');
|
throw new Error('Invalid date format in metadata.analysis_date');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all cached data
|
|
||||||
*/
|
|
||||||
clearCache(): void {
|
clearCache(): void {
|
||||||
this.cachedFiles = null;
|
this.cachedFiles = null;
|
||||||
this.cachedData.clear();
|
this.cachedData.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async generateManifest(): Promise<{ files: TransformedAnalysis[] }> {
|
||||||
* Create a manifest file content for available transformed data
|
const files = await this.getAvailableFiles();
|
||||||
* This can be used to generate a manifest.json file
|
|
||||||
*/
|
|
||||||
async generateManifest(): Promise<{ files: TransformedDataFile[] }> {
|
|
||||||
const files = await this.discoverFiles();
|
|
||||||
return { files };
|
return { files };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for analyses by various criteria
|
|
||||||
*/
|
|
||||||
async searchAnalyses(criteria: {
|
async searchAnalyses(criteria: {
|
||||||
company?: string;
|
company?: string;
|
||||||
dateFrom?: string;
|
dateFrom?: string;
|
||||||
dateTo?: string;
|
dateTo?: string;
|
||||||
recommendation?: 'BUY' | 'SELL' | 'HOLD';
|
recommendation?: 'BUY' | 'SELL' | 'HOLD';
|
||||||
}): Promise<TransformedDataFile[]> {
|
}): Promise<TransformedAnalysis[]> {
|
||||||
const files = await this.getAvailableFiles();
|
const files = await this.getAvailableFiles();
|
||||||
|
|
||||||
return files.filter(file => {
|
return files.filter(file => {
|
||||||
if (criteria.company && file.company !== criteria.company) {
|
if (criteria.company && file.symbol !== criteria.company) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,16 +271,10 @@ class TransformedDataService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For recommendation filtering, we'd need to load the actual data
|
|
||||||
// This is left as a future enhancement
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get summary statistics about available data
|
|
||||||
*/
|
|
||||||
async getDataSummary(): Promise<{
|
async getDataSummary(): Promise<{
|
||||||
totalFiles: number;
|
totalFiles: number;
|
||||||
companies: string[];
|
companies: string[];
|
||||||
|
|
@ -243,12 +283,13 @@ class TransformedDataService {
|
||||||
}> {
|
}> {
|
||||||
const files = await this.getAvailableFiles();
|
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 dates = files.map(f => f.date).sort();
|
||||||
|
|
||||||
const companyCounts: Record<string, number> = {};
|
const companyCounts: Record<string, number> = {};
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
companyCounts[file.company] = (companyCounts[file.company] || 0) + 1;
|
const company = file.symbol;
|
||||||
|
companyCounts[company] = (companyCounts[company] || 0) + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -263,6 +304,5 @@ class TransformedDataService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export a singleton instance
|
|
||||||
export const transformedDataService = new TransformedDataService();
|
export const transformedDataService = new TransformedDataService();
|
||||||
export default transformedDataService;
|
export default transformedDataService;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue