import { z } from 'zod'; import { createBackendEntitySchema, idSchema, timestampSchema, nonNegativeNumberSchema, positiveNumberSchema, optionalUrlSchema, scoreSchema, } from '@/schemas/common'; /** * Backend-aligned Match schemas * Matches the Go backend domain.Match struct and related types * * Uses Zod v4's composition features and common schemas for DRY code */ /** * Match status enum * Uses Zod v4's improved enum support */ export const matchStatusSchema = z .enum(['suggested', 'negotiating', 'reserved', 'contracted', 'live', 'failed', 'cancelled']) .describe('Match status'); export type MatchStatus = z.infer; /** * Transportation estimate schema * Uses Zod v4's composition with common schemas */ export const transportationEstimateSchema = z .object({ cost_per_year: nonNegativeNumberSchema.describe('Annual transportation cost'), method: z.string().describe('Transportation method (piping, truck, tanker)'), feasibility_score: scoreSchema.describe('Feasibility score (0-1)'), }) .describe('Transportation estimate'); export type TransportationEstimate = z.infer; /** * Risk assessment schema * Uses Zod v4's composition with score validation */ export const riskAssessmentSchema = z .object({ technical_risk: scoreSchema.describe('Technical risk (0-1)'), regulatory_risk: scoreSchema.describe('Regulatory risk (0-1)'), market_risk: scoreSchema.optional().describe('Market risk (0-1)'), counterparty_risk: scoreSchema.optional().describe('Counterparty risk (0-1)'), }) .describe('Risk assessment'); export type RiskAssessment = z.infer; /** * Economic impact schema * Uses Zod v4's composition with common number schemas */ export const economicImpactSchema = z .object({ annual_savings: nonNegativeNumberSchema.optional().describe('Annual savings (€/year)'), npv: z.number().optional().describe('Net Present Value'), irr: z.number().optional().describe('Internal Rate of Return (%)'), payback_years: positiveNumberSchema.optional().describe('Payback period (years)'), co2_avoided_tonnes: nonNegativeNumberSchema.optional().describe('CO2 avoided (tonnes/year)'), capex_required: nonNegativeNumberSchema.optional().describe('Initial investment (CAPEX)'), opex_per_year: nonNegativeNumberSchema.optional().describe('Annual operational cost (OPEX)'), }) .describe('Economic impact analysis'); export type EconomicImpact = z.infer; /** * Match history entry schema * Uses Zod v4's composition with timestamp validation */ export const matchHistoryEntrySchema = z .object({ timestamp: timestampSchema.describe('Entry timestamp'), actor: idSchema.describe('User ID who performed the action'), action: z.string().describe('Action type (status_change, comment, update)'), notes: z.string().optional().describe('Additional notes'), old_value: z.string().optional().describe('Previous value'), new_value: z.string().optional().describe('New value'), }) .describe('Match history entry'); export type MatchHistoryEntry = z.infer; /** * Contract details schema * Uses Zod v4's composition with timestamp and URL validation */ export const contractDetailsSchema = z .object({ contract_id: idSchema.optional().describe('Contract identifier'), signed_at: timestampSchema.optional().describe('Contract signing timestamp'), effective_from: timestampSchema.optional().describe('Contract effective date'), value_per_year: nonNegativeNumberSchema.optional().describe('Annual contract value'), terms_url: optionalUrlSchema.describe('URL to contract terms'), }) .describe('Contract details'); export type ContractDetails = z.infer; /** * Backend Match schema * Uses Zod v4's composition with common entity schema */ export const backendMatchSchema = createBackendEntitySchema({ SourceResourceID: idSchema.describe('Source resource flow ID'), TargetResourceID: idSchema.describe('Target resource flow ID'), CompatibilityScore: scoreSchema.describe('Overall compatibility score (0-1)'), TemporalOverlapScore: scoreSchema.optional().describe('Temporal overlap score (0-1)'), QualityScore: scoreSchema.optional().describe('Quality compatibility score (0-1)'), EconomicValue: z.number().describe('Economic value'), DistanceKm: nonNegativeNumberSchema.describe('Distance in kilometers'), Status: matchStatusSchema, Priority: z.number().int().describe('Match priority'), TransportationEstimate: transportationEstimateSchema.optional(), RiskAssessment: riskAssessmentSchema.optional(), EconomicImpact: economicImpactSchema.optional(), History: z.array(matchHistoryEntrySchema).optional().describe('Match history'), ContractDetails: contractDetailsSchema.optional(), ReservedUntil: timestampSchema.optional().describe('Reservation expiration timestamp'), FailureReason: z.string().optional().describe('Reason for failure (if failed)'), Version: z.number().int().optional().describe('Optimistic locking version'), }); export type BackendMatch = z.infer; /** * Response schema for find matches endpoint * Uses Zod v4's composition */ export const findMatchesResponseSchema = z.object({ resource_id: idSchema.describe('Resource flow ID (from handler)'), matches: z.array(backendMatchSchema).describe('Matching resource flows'), count: z.number().int().nonnegative().describe('Total match count'), }); export type FindMatchesResponse = z.infer; /** * Query parameters for finding matches * Uses Zod v4's coerce and common number schemas */ export const findMatchesQuerySchema = z.object({ max_distance_km: z.coerce .number() .pipe(positiveNumberSchema) .optional() .describe('Maximum distance in kilometers'), min_score: z.coerce .number() .pipe(scoreSchema) .optional() .describe('Minimum compatibility score (0-1)'), limit: z.coerce .number() .int() .pipe(positiveNumberSchema) .optional() .describe('Maximum number of results'), }); export type FindMatchesQuery = z.infer;