export const INCIDENT_TYPES = ['injury', 'near_miss', 'hazard', 'asset_damage', 'environmental', 'security', 'fire', 'transport'] as const export const MEDICAL_STATUSES = ['none', 'first_aid', 'medical_treatment', 'lti'] as const export type IncidentType = typeof INCIDENT_TYPES[number] export type MedicalStatus = typeof MEDICAL_STATUSES[number] export interface IncidentInput { zone_token: string incident_type: IncidentType description: string injury_involved: boolean medical_status?: MedicalStatus asset_involved: boolean type_details?: Record truck_id?: string | null } // Whitelisted type-specific intake fields per incident type (PRD ยง3). // 's' = free text, 'b' = boolean. Near miss / hazard / injury stay minimal by design. export const TYPE_DETAIL_FIELDS: Partial>> = { environmental: { substance: 's', estimated_volume: 's', containment_deployed: 'b' }, asset_damage: { equipment_id: 's', loto_applied: 'b' }, security: { persons_involved: 's', police_reported: 'b' }, fire: { alarm_raised: 'b', fire_brigade_called: 'b' }, } export function validateTypeDetails( incidentType: IncidentType, details: Record | undefined, ): { ok: boolean; errors: string[]; sanitized: Record | null } { if (details == null || Object.keys(details).length === 0) { return { ok: true, errors: [], sanitized: null } } const allowed = TYPE_DETAIL_FIELDS[incidentType] if (!allowed) { return { ok: false, errors: [`type_details not allowed for incident_type ${incidentType}`], sanitized: null } } const errors: string[] = [] const sanitized: Record = {} for (const [key, value] of Object.entries(details)) { const kind = allowed[key] if (!kind) { errors.push(`unknown type_details field: ${key}`) continue } if (kind === 's') { if (typeof value !== 'string') { errors.push(`${key} must be a string`) continue } const trimmed = value.trim() if (trimmed) sanitized[key] = trimmed.slice(0, 500) } else { if (typeof value !== 'boolean') { errors.push(`${key} must be a boolean`) continue } sanitized[key] = value } } return { ok: errors.length === 0, errors, sanitized: Object.keys(sanitized).length > 0 ? sanitized : null, } } export function validateIncidentInput(input: IncidentInput): { ok: boolean; errors: string[] } { const errors: string[] = [] if (!input.zone_token?.trim()) errors.push('zone_token is required') if (input.description.trim().length < 10) errors.push('description must be at least 10 characters') if (!INCIDENT_TYPES.includes(input.incident_type)) errors.push('incident_type is invalid') if (input.injury_involved && !input.medical_status) errors.push('medical_status is required when injury is involved') if (input.medical_status && !MEDICAL_STATUSES.includes(input.medical_status)) errors.push('medical_status is invalid') if (input.incident_type === 'transport' && !input.truck_id?.trim()) errors.push('truck_id is required for transport incidents') return { ok: errors.length === 0, errors } }