List transformed outputs

This commit is contained in:
Jenit Jain 2025-08-10 01:54:11 -07:00
parent d3fccbd4e4
commit 4c6564b5d1
5 changed files with 380 additions and 169 deletions

View File

@ -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"""

View File

@ -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>

View File

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

View File

@ -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>

View File

@ -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;