import { reportError } from '@/lib/error-handling'; import { BaseService } from '@/lib/service-base'; import { SERVICE_CONFIGS } from '@/lib/service-config'; import { nonNegativeNumberSchema } from '@/schemas/common.ts'; import { z } from 'zod'; // ============================================================================ // Zod v4 Schemas - Single source of truth for types // ============================================================================ export const itemCountSchema = z.object({ item: z.string().describe('Item name'), count: nonNegativeNumberSchema.describe('Item count'), }); export type ItemCount = z.infer; const activityItemSchema = z.object({ id: z.string().describe('Activity ID'), type: z.string().describe('Activity type'), description: z.string().describe('Activity description'), timestamp: z.string().describe('ISO 8601 timestamp'), }); export const connectionStatisticsSchema = z.object({ total_connections: nonNegativeNumberSchema.describe('Total connections'), active_connections: nonNegativeNumberSchema.describe('Active connections'), potential_connections: nonNegativeNumberSchema.describe('Potential connections'), }); export const supplyDemandAnalysisSchema = z.object({ top_needs: z.array(itemCountSchema).describe('Top resource needs'), top_offers: z.array(itemCountSchema).describe('Top resource offers'), }); export const dashboardStatisticsSchema = z.object({ total_organizations: nonNegativeNumberSchema.describe('Total organizations'), total_sites: nonNegativeNumberSchema.describe('Total sites'), total_resource_flows: nonNegativeNumberSchema.describe('Total resource flows'), total_matches: nonNegativeNumberSchema.describe('Total matches'), active_proposals: nonNegativeNumberSchema.describe('Active proposals'), recent_activity: z.array(activityItemSchema).describe('Recent activity items'), }); export const organizationStatisticsSchema = z.object({ organization_id: z.string().describe('Organization ID'), total_sites: nonNegativeNumberSchema.describe('Total sites'), total_resource_flows: nonNegativeNumberSchema.describe('Total resource flows'), active_matches: nonNegativeNumberSchema.describe('Active matches'), total_matches: nonNegativeNumberSchema.describe('Total matches'), co2_savings_tonnes: nonNegativeNumberSchema.describe('CO₂ savings in tonnes'), economic_value_eur: nonNegativeNumberSchema.describe('Economic value in EUR'), }); export const platformStatisticsSchema = z.object({ total_organizations: nonNegativeNumberSchema.describe('Total organizations'), total_sites: nonNegativeNumberSchema.describe('Total sites'), total_resource_flows: nonNegativeNumberSchema.describe('Total resource flows'), total_matches: nonNegativeNumberSchema.describe('Total matches'), total_shared_assets: nonNegativeNumberSchema.describe('Total shared assets'), active_matches: nonNegativeNumberSchema.describe('Active matches'), co2_savings_tonnes: nonNegativeNumberSchema.describe('CO₂ savings in tonnes'), economic_value_eur: nonNegativeNumberSchema.describe('Economic value in EUR'), }); export const matchingStatisticsSchema = z.object({ total_matches: nonNegativeNumberSchema.describe('Total matches'), active_matches: nonNegativeNumberSchema.describe('Active matches'), completed_matches: nonNegativeNumberSchema.describe('Completed matches'), avg_compatibility_score: z.number().min(0).max(1).describe('Average compatibility score (0-1)'), avg_economic_value: nonNegativeNumberSchema.describe('Average economic value'), avg_distance_km: nonNegativeNumberSchema.describe('Average distance in km'), match_success_rate: z.number().min(0).max(1).optional().describe('Match success rate (0-1)'), avg_match_time_days: nonNegativeNumberSchema.optional().describe('Average match time in days'), top_resource_types: z.array(z.string()).optional().describe('Top resource types'), match_trends: z.array(z.any()).optional().describe('Match trends data'), total_match_value: nonNegativeNumberSchema.optional().describe('Total match value'), }); export const resourceFlowStatisticsSchema = z.object({ total_flows: nonNegativeNumberSchema.describe('Total resource flows'), input_flows: nonNegativeNumberSchema.describe('Input flows'), output_flows: nonNegativeNumberSchema.describe('Output flows'), flows_by_type: z.record(z.string(), z.number()).describe('Flows grouped by type'), flows_by_direction: z.record(z.string(), z.number()).describe('Flows grouped by direction'), flows_by_sector: z.record(z.string(), z.number()).optional().describe('Flows grouped by sector'), avg_flow_value: nonNegativeNumberSchema.optional().describe('Average flow value'), total_flow_volume: nonNegativeNumberSchema.optional().describe('Total flow volume'), }); export const impactMetricsSchema = z.object({ total_co2_savings_tonnes: nonNegativeNumberSchema.describe('Total CO₂ saved in tonnes'), total_economic_value_eur: nonNegativeNumberSchema.describe('Total economic value'), active_matches_count: nonNegativeNumberSchema.describe('Active matches count'), materials_recycled_tonnes: nonNegativeNumberSchema .optional() .describe('Materials recycled in tonnes'), energy_shared_mwh: nonNegativeNumberSchema.optional().describe('Energy shared in MWh'), water_reclaimed_m3: nonNegativeNumberSchema.optional().describe('Water reclaimed in m³'), environmental_breakdown: z .record(z.string(), z.any()) .optional() .describe('Environmental breakdown'), }); // ============================================================================ // Type Exports - Inferred from Zod schemas (Zod v4 pattern) // ============================================================================ export type ConnectionStatistics = z.infer; export type SupplyDemandAnalysis = z.infer; export type DashboardStatistics = z.infer; export type OrganizationStatistics = z.infer; export type PlatformStatistics = z.infer; export type MatchingStatistics = z.infer; export type ResourceFlowStatistics = z.infer; export type ImpactMetrics = z.infer; export type ActivityItem = z.infer; /** * Analytics Service * Handles all analytics-related API operations with improved reliability and type safety */ class AnalyticsService extends BaseService { constructor() { super(SERVICE_CONFIGS.ANALYTICS); } /** * Get connection statistics */ async getConnectionStatistics(): Promise { const result = await this.get('connections', connectionStatisticsSchema, undefined, { context: 'getConnectionStatistics', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to fetch connection statistics'), { operation: 'getConnectionStatistics' } ); throw error; } return result.data; } /** * Get supply and demand analysis */ async getSupplyDemandAnalysis(): Promise { const result = await this.get('supply-demand', supplyDemandAnalysisSchema, undefined, { context: 'getSupplyDemandAnalysis', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to fetch supply demand analysis'), { operation: 'getSupplyDemandAnalysis' } ); throw error; } return result.data; } /** * Get dashboard statistics */ async getDashboardStatistics(): Promise { const result = await this.get('dashboard', dashboardStatisticsSchema, undefined, { context: 'getDashboardStatistics', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to fetch dashboard statistics'), { operation: 'getDashboardStatistics' } ); throw error; } return result.data; } /** * Get platform statistics */ async getPlatformStatistics(): Promise { const result = await this.get('platform-stats', platformStatisticsSchema, undefined, { context: 'getPlatformStatistics', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to fetch platform statistics'), { operation: 'getPlatformStatistics' } ); throw error; } return result.data; } /** * Get matching statistics */ async getMatchingStatistics(): Promise { const result = await this.get('matching-stats', matchingStatisticsSchema, undefined, { context: 'getMatchingStatistics', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to fetch matching statistics'), { operation: 'getMatchingStatistics' } ); throw error; } return result.data; } /** * Get resource flow statistics */ async getResourceFlowStatistics(): Promise { const result = await this.get('resource-flow-stats', resourceFlowStatisticsSchema, undefined, { context: 'getResourceFlowStatistics', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to fetch resource flow statistics'), { operation: 'getResourceFlowStatistics' } ); throw error; } return result.data; } /** * Get impact metrics */ async getImpactMetrics(): Promise { const result = await this.get('impact-metrics', impactMetricsSchema, undefined, { context: 'getImpactMetrics', }); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to fetch impact metrics'), { operation: 'getImpactMetrics' } ); throw error; } return result.data; } /** * Get organization statistics */ async getOrganizationStatistics(organizationId: string): Promise { const result = await this.get( `organizations/${organizationId}/stats`, organizationStatisticsSchema, undefined, { context: 'getOrganizationStatistics', } ); if (!result.success) { const error = reportError( new Error(result.error?.message || 'Failed to fetch organization statistics'), { operation: 'getOrganizationStatistics', organizationId } ); throw error; } return result.data; } } // Create and export service instance const analyticsService = new AnalyticsService(); // Export service instance for direct usage export { analyticsService }; // Export service methods directly for cleaner imports export const getConnectionStatistics = analyticsService.getConnectionStatistics.bind(analyticsService); export const getSupplyDemandAnalysis = analyticsService.getSupplyDemandAnalysis.bind(analyticsService); export const getDashboardStatistics = analyticsService.getDashboardStatistics.bind(analyticsService); export const getPlatformStatistics = analyticsService.getPlatformStatistics.bind(analyticsService); export const getMatchingStatistics = analyticsService.getMatchingStatistics.bind(analyticsService); export const getResourceFlowStatistics = analyticsService.getResourceFlowStatistics.bind(analyticsService); export const getImpactMetrics = analyticsService.getImpactMetrics.bind(analyticsService); export const getOrganizationStatistics = analyticsService.getOrganizationStatistics.bind(analyticsService);