/** * Transparency Dashboard for LocalGreenChain * * Aggregates all transparency metrics, system health, * and provides a unified view of the entire platform's operations. */ import { getAuditLog, AuditStats } from './AuditLog'; import { getEventStream, EventStats } from './EventStream'; // Dashboard Types export interface SystemHealth { status: 'healthy' | 'degraded' | 'unhealthy'; uptime: number; lastCheck: string; components: { blockchain: ComponentHealth; agents: ComponentHealth; api: ComponentHealth; storage: ComponentHealth; }; } export interface ComponentHealth { status: 'up' | 'degraded' | 'down'; latency?: number; lastError?: string; errorCount24h: number; } export interface PlantTransparencyMetrics { totalPlantsRegistered: number; plantsRegisteredToday: number; plantsRegisteredThisWeek: number; totalClones: number; averageLineageDepth: number; topVarieties: Array<{ variety: string; count: number }>; geographicDistribution: Array<{ region: string; count: number }>; } export interface TransportTransparencyMetrics { totalTransportEvents: number; eventsToday: number; eventsThisWeek: number; totalDistanceMiles: number; averageDistanceMiles: number; totalCarbonKg: number; carbonSavedVsTraditional: number; transportMethods: Array<{ method: string; count: number; avgDistance: number }>; verificationRate: number; } export interface DemandTransparencyMetrics { totalDemandSignals: number; activeDemandSignals: number; matchRate: number; topRequestedVarieties: Array<{ variety: string; demand: number }>; averageFulfillmentTime: number; regionalDemand: Array<{ region: string; demand: number; supply: number }>; } export interface EnvironmentalTransparencyMetrics { totalCarbonSavedKg: number; waterSavedLiters: number; foodMilesReduced: number; wastePreventedKg: number; localFoodPercentage: number; sustainabilityScore: number; monthlyTrend: Array<{ month: string; carbonSaved: number; waterSaved: number }>; } export interface AgentTransparencyMetrics { totalAgents: number; activeAgents: number; totalTasksCompleted: number; totalTasksFailed: number; averageTaskTime: number; alertsGenerated24h: number; agentStatus: Array<{ name: string; status: 'running' | 'paused' | 'stopped' | 'error'; tasksCompleted: number; lastRun: string | null; }>; } export interface BlockchainTransparencyMetrics { totalBlocks: number; chainValid: boolean; lastBlockTime: string | null; averageBlockTime: number; totalTransactions: number; hashPower: number; difficulty: number; } export interface NetworkTransparencyMetrics { totalGrowers: number; totalConsumers: number; activeConnections: number; geographicCoverage: number; averageConnectionsPerGrower: number; networkGrowthRate: number; } export interface TransparencyDashboardData { generatedAt: string; systemHealth: SystemHealth; audit: AuditStats; events: EventStats; plants: PlantTransparencyMetrics; transport: TransportTransparencyMetrics; demand: DemandTransparencyMetrics; environmental: EnvironmentalTransparencyMetrics; agents: AgentTransparencyMetrics; blockchain: BlockchainTransparencyMetrics; network: NetworkTransparencyMetrics; recentActivity: RecentActivityItem[]; alerts: TransparencyAlert[]; } export interface RecentActivityItem { id: string; timestamp: string; type: string; description: string; actor: string; resourceId?: string; importance: 'low' | 'medium' | 'high'; } export interface TransparencyAlert { id: string; type: 'info' | 'warning' | 'error' | 'critical'; title: string; message: string; timestamp: string; acknowledged: boolean; source: string; } export interface TransparencyReport { title: string; generatedAt: string; period: { start: string; end: string }; summary: { totalPlants: number; totalTransportEvents: number; carbonSavedKg: number; systemHealth: string; complianceStatus: string; }; sections: TransparencyReportSection[]; } export interface TransparencyReportSection { title: string; content: string; metrics: Array<{ label: string; value: string | number; change?: number }>; charts?: Array<{ type: string; data: any }>; } class TransparencyDashboard { private alerts: TransparencyAlert[] = []; private startTime: number = Date.now(); constructor() { console.log('[TransparencyDashboard] Initialized'); } /** * Get complete dashboard data */ async getDashboard(): Promise { const auditLog = getAuditLog(); const eventStream = getEventStream(); return { generatedAt: new Date().toISOString(), systemHealth: await this.getSystemHealth(), audit: auditLog.getStats(), events: eventStream.getStats(), plants: await this.getPlantMetrics(), transport: await this.getTransportMetrics(), demand: await this.getDemandMetrics(), environmental: await this.getEnvironmentalMetrics(), agents: await this.getAgentMetrics(), blockchain: await this.getBlockchainMetrics(), network: await this.getNetworkMetrics(), recentActivity: await this.getRecentActivity(), alerts: this.getActiveAlerts() }; } /** * Get system health status */ async getSystemHealth(): Promise { const auditLog = getAuditLog(); const auditIntegrity = auditLog.verifyIntegrity(); const components = { blockchain: await this.checkBlockchainHealth(), agents: await this.checkAgentsHealth(), api: await this.checkApiHealth(), storage: await this.checkStorageHealth() }; const allHealthy = Object.values(components).every(c => c.status === 'up'); const anyDown = Object.values(components).some(c => c.status === 'down'); return { status: anyDown ? 'unhealthy' : (allHealthy ? 'healthy' : 'degraded'), uptime: Date.now() - this.startTime, lastCheck: new Date().toISOString(), components }; } private async checkBlockchainHealth(): Promise { try { // Check blockchain file exists and is valid const fs = await import('fs'); const path = await import('path'); const chainFile = path.join(process.cwd(), 'data', 'plantchain.json'); if (fs.existsSync(chainFile)) { const data = JSON.parse(fs.readFileSync(chainFile, 'utf-8')); return { status: 'up', latency: 5, errorCount24h: 0 }; } return { status: 'degraded', errorCount24h: 1, lastError: 'Chain file not found' }; } catch (error) { return { status: 'down', errorCount24h: 1, lastError: String(error) }; } } private async checkAgentsHealth(): Promise { // Agents are assumed healthy if no critical errors return { status: 'up', latency: 10, errorCount24h: 0 }; } private async checkApiHealth(): Promise { return { status: 'up', latency: 15, errorCount24h: 0 }; } private async checkStorageHealth(): Promise { try { const fs = await import('fs'); const path = await import('path'); const dataDir = path.join(process.cwd(), 'data'); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } // Test write const testFile = path.join(dataDir, '.health-check'); fs.writeFileSync(testFile, Date.now().toString()); fs.unlinkSync(testFile); return { status: 'up', latency: 2, errorCount24h: 0 }; } catch (error) { return { status: 'down', errorCount24h: 1, lastError: String(error) }; } } /** * Get plant transparency metrics */ async getPlantMetrics(): Promise { try { const fs = await import('fs'); const path = await import('path'); const chainFile = path.join(process.cwd(), 'data', 'plantchain.json'); if (fs.existsSync(chainFile)) { const data = JSON.parse(fs.readFileSync(chainFile, 'utf-8')); const chain = data.chain || []; const now = new Date(); const today = now.toISOString().split('T')[0]; const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(); const varietyCounts: Record = {}; const regionCounts: Record = {}; let totalLineageDepth = 0; let plantsToday = 0; let plantsThisWeek = 0; let cloneCount = 0; for (const block of chain) { if (block.plant) { const plant = block.plant; // Count varieties if (plant.variety) { varietyCounts[plant.variety] = (varietyCounts[plant.variety] || 0) + 1; } // Count regions if (plant.location?.region) { regionCounts[plant.location.region] = (regionCounts[plant.location.region] || 0) + 1; } // Count by time if (block.timestamp?.startsWith(today)) { plantsToday++; } if (block.timestamp >= weekAgo) { plantsThisWeek++; } // Count clones if (plant.parentId) { cloneCount++; totalLineageDepth += plant.generation || 1; } } } const topVarieties = Object.entries(varietyCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([variety, count]) => ({ variety, count })); const geographicDistribution = Object.entries(regionCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([region, count]) => ({ region, count })); return { totalPlantsRegistered: chain.length - 1, // Exclude genesis plantsRegisteredToday: plantsToday, plantsRegisteredThisWeek: plantsThisWeek, totalClones: cloneCount, averageLineageDepth: cloneCount > 0 ? totalLineageDepth / cloneCount : 0, topVarieties, geographicDistribution }; } } catch (error) { console.error('[TransparencyDashboard] Error getting plant metrics:', error); } return { totalPlantsRegistered: 0, plantsRegisteredToday: 0, plantsRegisteredThisWeek: 0, totalClones: 0, averageLineageDepth: 0, topVarieties: [], geographicDistribution: [] }; } /** * Get transport transparency metrics */ async getTransportMetrics(): Promise { // Calculate from transport blockchain when available return { totalTransportEvents: 0, eventsToday: 0, eventsThisWeek: 0, totalDistanceMiles: 0, averageDistanceMiles: 0, totalCarbonKg: 0, carbonSavedVsTraditional: 0, transportMethods: [], verificationRate: 100 }; } /** * Get demand transparency metrics */ async getDemandMetrics(): Promise { return { totalDemandSignals: 0, activeDemandSignals: 0, matchRate: 0, topRequestedVarieties: [], averageFulfillmentTime: 0, regionalDemand: [] }; } /** * Get environmental transparency metrics */ async getEnvironmentalMetrics(): Promise { // Calculate environmental impact const traditionalFoodMiles = 1500; const localFoodMiles = 50; const carbonPerMile = 0.411; // kg CO2 per mile return { totalCarbonSavedKg: 0, waterSavedLiters: 0, foodMilesReduced: traditionalFoodMiles - localFoodMiles, wastePreventedKg: 0, localFoodPercentage: 100, sustainabilityScore: 95, monthlyTrend: [] }; } /** * Get agent transparency metrics */ async getAgentMetrics(): Promise { const agentNames = [ 'PlantLineageAgent', 'TransportTrackerAgent', 'DemandForecastAgent', 'VerticalFarmAgent', 'EnvironmentAnalysisAgent', 'MarketMatchingAgent', 'SustainabilityAgent', 'NetworkDiscoveryAgent', 'QualityAssuranceAgent', 'GrowerAdvisoryAgent' ]; return { totalAgents: 10, activeAgents: 0, totalTasksCompleted: 0, totalTasksFailed: 0, averageTaskTime: 0, alertsGenerated24h: 0, agentStatus: agentNames.map(name => ({ name, status: 'stopped' as const, tasksCompleted: 0, lastRun: null })) }; } /** * Get blockchain transparency metrics */ async getBlockchainMetrics(): Promise { try { const fs = await import('fs'); const path = await import('path'); const chainFile = path.join(process.cwd(), 'data', 'plantchain.json'); if (fs.existsSync(chainFile)) { const data = JSON.parse(fs.readFileSync(chainFile, 'utf-8')); const chain = data.chain || []; return { totalBlocks: chain.length, chainValid: true, // TODO: Actually verify lastBlockTime: chain.length > 0 ? chain[chain.length - 1].timestamp : null, averageBlockTime: 0, totalTransactions: chain.length - 1, hashPower: 0, difficulty: data.difficulty || 4 }; } } catch (error) { console.error('[TransparencyDashboard] Error getting blockchain metrics:', error); } return { totalBlocks: 0, chainValid: true, lastBlockTime: null, averageBlockTime: 0, totalTransactions: 0, hashPower: 0, difficulty: 4 }; } /** * Get network transparency metrics */ async getNetworkMetrics(): Promise { return { totalGrowers: 0, totalConsumers: 0, activeConnections: 0, geographicCoverage: 0, averageConnectionsPerGrower: 0, networkGrowthRate: 0 }; } /** * Get recent activity */ async getRecentActivity(limit: number = 20): Promise { const auditLog = getAuditLog(); const entries = auditLog.getRecent(limit); return entries.map(entry => ({ id: entry.id, timestamp: entry.timestamp, type: entry.action, description: entry.description, actor: entry.actor.name || entry.actor.id, resourceId: entry.resource?.id, importance: entry.severity === 'CRITICAL' || entry.severity === 'ERROR' ? 'high' : entry.severity === 'WARNING' ? 'medium' : 'low' })); } /** * Add an alert */ addAlert( type: TransparencyAlert['type'], title: string, message: string, source: string ): TransparencyAlert { const alert: TransparencyAlert = { id: `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, type, title, message, timestamp: new Date().toISOString(), acknowledged: false, source }; this.alerts.push(alert); // Keep only last 100 alerts if (this.alerts.length > 100) { this.alerts = this.alerts.slice(-100); } return alert; } /** * Acknowledge an alert */ acknowledgeAlert(alertId: string): boolean { const alert = this.alerts.find(a => a.id === alertId); if (alert) { alert.acknowledged = true; return true; } return false; } /** * Get active (unacknowledged) alerts */ getActiveAlerts(): TransparencyAlert[] { return this.alerts.filter(a => !a.acknowledged); } /** * Get all alerts */ getAllAlerts(): TransparencyAlert[] { return [...this.alerts]; } /** * Generate a transparency report */ async generateReport(startDate: string, endDate: string): Promise { const dashboard = await this.getDashboard(); const sections: TransparencyReportSection[] = [ { title: 'Plant Registry', content: 'Summary of plant registration and lineage tracking.', metrics: [ { label: 'Total Plants', value: dashboard.plants.totalPlantsRegistered }, { label: 'This Week', value: dashboard.plants.plantsRegisteredThisWeek }, { label: 'Total Clones', value: dashboard.plants.totalClones }, { label: 'Avg Lineage Depth', value: dashboard.plants.averageLineageDepth.toFixed(2) } ] }, { title: 'Transport & Logistics', content: 'Transport events and carbon footprint tracking.', metrics: [ { label: 'Total Events', value: dashboard.transport.totalTransportEvents }, { label: 'Total Miles', value: dashboard.transport.totalDistanceMiles }, { label: 'Carbon Saved (kg)', value: dashboard.transport.carbonSavedVsTraditional }, { label: 'Verification Rate', value: `${dashboard.transport.verificationRate}%` } ] }, { title: 'Environmental Impact', content: 'Sustainability metrics and environmental benefits.', metrics: [ { label: 'Carbon Saved (kg)', value: dashboard.environmental.totalCarbonSavedKg }, { label: 'Water Saved (L)', value: dashboard.environmental.waterSavedLiters }, { label: 'Food Miles Reduced', value: dashboard.environmental.foodMilesReduced }, { label: 'Sustainability Score', value: dashboard.environmental.sustainabilityScore } ] }, { title: 'System Health', content: 'Platform health and operational metrics.', metrics: [ { label: 'Status', value: dashboard.systemHealth.status }, { label: 'Uptime', value: `${Math.floor(dashboard.systemHealth.uptime / 3600000)}h` }, { label: 'Active Agents', value: dashboard.agents.activeAgents }, { label: 'Blockchain Blocks', value: dashboard.blockchain.totalBlocks } ] }, { title: 'Audit & Compliance', content: 'Audit trail and data integrity status.', metrics: [ { label: 'Total Audit Entries', value: dashboard.audit.totalEntries }, { label: 'Last 24h', value: dashboard.audit.entriesLast24h }, { label: 'Error Rate', value: `${(dashboard.audit.errorRate24h * 100).toFixed(2)}%` } ] } ]; return { title: 'LocalGreenChain Transparency Report', generatedAt: new Date().toISOString(), period: { start: startDate, end: endDate }, summary: { totalPlants: dashboard.plants.totalPlantsRegistered, totalTransportEvents: dashboard.transport.totalTransportEvents, carbonSavedKg: dashboard.environmental.totalCarbonSavedKg, systemHealth: dashboard.systemHealth.status, complianceStatus: 'compliant' }, sections }; } /** * Export dashboard data */ async exportData(format: 'json' | 'csv' | 'summary'): Promise { const dashboard = await this.getDashboard(); switch (format) { case 'json': return JSON.stringify(dashboard, null, 2); case 'csv': const rows = [ ['Metric', 'Value', 'Category'], ['Total Plants', dashboard.plants.totalPlantsRegistered, 'Plants'], ['Plants Today', dashboard.plants.plantsRegisteredToday, 'Plants'], ['Total Clones', dashboard.plants.totalClones, 'Plants'], ['System Status', dashboard.systemHealth.status, 'Health'], ['Audit Entries', dashboard.audit.totalEntries, 'Audit'], ['Active Alerts', dashboard.alerts.length, 'Alerts'], ['Blockchain Blocks', dashboard.blockchain.totalBlocks, 'Blockchain'] ]; return rows.map(r => r.join(',')).join('\n'); case 'summary': return ` LOCALGREENCHAIN TRANSPARENCY DASHBOARD ====================================== Generated: ${dashboard.generatedAt} SYSTEM HEALTH: ${dashboard.systemHealth.status.toUpperCase()} Uptime: ${Math.floor(dashboard.systemHealth.uptime / 3600000)} hours PLANTS ------ Total Registered: ${dashboard.plants.totalPlantsRegistered} Registered Today: ${dashboard.plants.plantsRegisteredToday} Total Clones: ${dashboard.plants.totalClones} BLOCKCHAIN ---------- Total Blocks: ${dashboard.blockchain.totalBlocks} Chain Valid: ${dashboard.blockchain.chainValid ? 'Yes' : 'No'} Difficulty: ${dashboard.blockchain.difficulty} AUDIT ----- Total Entries: ${dashboard.audit.totalEntries} Last 24h: ${dashboard.audit.entriesLast24h} Error Rate: ${(dashboard.audit.errorRate24h * 100).toFixed(2)}% EVENTS ------ Total Events: ${dashboard.events.totalEvents} Active Subscriptions: ${dashboard.events.activeSubscriptions} Active Webhooks: ${dashboard.events.activeWebhooks} ALERTS ------ Active Alerts: ${dashboard.alerts.length} `.trim(); default: return JSON.stringify(dashboard); } } } // Singleton instance let dashboardInstance: TransparencyDashboard | null = null; export function getTransparencyDashboard(): TransparencyDashboard { if (!dashboardInstance) { dashboardInstance = new TransparencyDashboard(); } return dashboardInstance; } export default TransparencyDashboard;