turash/bugulma/frontend/services/analytics-api.ts
2025-12-15 10:06:41 +01:00

307 lines
12 KiB
TypeScript

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<typeof itemCountSchema>;
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<typeof connectionStatisticsSchema>;
export type SupplyDemandAnalysis = z.infer<typeof supplyDemandAnalysisSchema>;
export type DashboardStatistics = z.infer<typeof dashboardStatisticsSchema>;
export type OrganizationStatistics = z.infer<typeof organizationStatisticsSchema>;
export type PlatformStatistics = z.infer<typeof platformStatisticsSchema>;
export type MatchingStatistics = z.infer<typeof matchingStatisticsSchema>;
export type ResourceFlowStatistics = z.infer<typeof resourceFlowStatisticsSchema>;
export type ImpactMetrics = z.infer<typeof impactMetricsSchema>;
export type ActivityItem = z.infer<typeof activityItemSchema>;
/**
* 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<ConnectionStatistics> {
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<SupplyDemandAnalysis> {
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<DashboardStatistics> {
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<PlatformStatistics> {
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<MatchingStatistics> {
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<ResourceFlowStatistics> {
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<ImpactMetrics> {
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<OrganizationStatistics> {
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);