mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
307 lines
12 KiB
TypeScript
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);
|