TradingAgents/web_app/frontend/src/services/transformedDataService.ts

269 lines
7.4 KiB
TypeScript

import { TransformedAnalysisData } from '../components/TransformedDataAdapter.tsx';
export interface TransformedDataFile {
filename: string;
company: string;
date: string;
displayName: string;
}
class TransformedDataService {
private baseUrl = '/transformed_data';
private cachedFiles: TransformedDataFile[] | null = null;
private cachedData: Map<string, TransformedAnalysisData> = new Map();
/**
* Get list of available transformed data files
*/
async getAvailableFiles(): Promise<TransformedDataFile[]> {
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`);
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();
}
} catch (error) {
console.warn('Could not load transformed data manifest, using fallback discovery:', error);
this.cachedFiles = await this.discoverFiles();
}
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
if (this.cachedData.has(filename)) {
return this.cachedData.get(filename)!;
}
try {
const response = await fetch(`${this.baseUrl}/${filename}`);
if (!response.ok) {
throw new Error(`Failed to load ${filename}: ${response.status} ${response.statusText}`);
}
const data: TransformedAnalysisData = await response.json();
// 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);
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);
}
/**
* Get the most recent analysis for a company
*/
async getLatestForCompany(company: string): Promise<TransformedAnalysisData | 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
if (companyFiles.length === 0) {
return null;
}
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();
}
/**
* 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)
.map(f => f.date)
.sort((a, b) => b.localeCompare(a)); // Sort by date descending
return dates;
}
/**
* Validate that the loaded data conforms to the expected structure
*/
private validateTransformedData(data: any): void {
const requiredSections = [
'metadata',
'financial_data',
'technical_indicators',
'investment_strategy',
'debate_summary',
'text_content',
'widgets_config'
];
for (const section of requiredSections) {
if (!data[section]) {
throw new Error(`Missing required section: ${section}`);
}
}
// 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();
return { files };
}
/**
* Search for analyses by various criteria
*/
async searchAnalyses(criteria: {
company?: string;
dateFrom?: string;
dateTo?: string;
recommendation?: 'BUY' | 'SELL' | 'HOLD';
}): Promise<TransformedDataFile[]> {
const files = await this.getAvailableFiles();
return files.filter(file => {
if (criteria.company && file.company !== criteria.company) {
return false;
}
if (criteria.dateFrom && file.date < criteria.dateFrom) {
return false;
}
if (criteria.dateTo && file.date > criteria.dateTo) {
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[];
dateRange: { earliest: string; latest: string };
companyCounts: Record<string, number>;
}> {
const files = await this.getAvailableFiles();
const companies = [...new Set(files.map(f => f.company))].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;
});
return {
totalFiles: files.length,
companies,
dateRange: {
earliest: dates[0] || '',
latest: dates[dates.length - 1] || ''
},
companyCounts
};
}
}
// Export a singleton instance
export const transformedDataService = new TransformedDataService();
export default transformedDataService;