From d30a49f6445594ed8daa91784db26283e3cbbff7 Mon Sep 17 00:00:00 2001 From: lorentz Date: Thu, 16 Jul 2026 14:28:27 -0400 Subject: [PATCH] feat(22-01): implement mergeTimeline for reports/classifications/audit merge - Discriminated union TimelineEntry with report/classification/audit variants - Ascending sort by createdAt with stable report = { + report: 0, + classification: 1, + audit: 2, +}; + +/** + * Maps each of the three sources into its `TimelineEntry` variant, + * concatenates, and sorts ascending by `at` (oldest first — matches the + * existing `ORDER BY created_at ASC` convention). Ties on identical + * timestamps fall back to a fixed kind priority (report < classification < + * audit) for a stable, deterministic order. + */ +export function mergeTimeline( + reports: ReportTimelineSource[], + classifications: ClassificationTimelineSource[], + auditEvents: AuditTimelineSource[] +): TimelineEntry[] { + const entries: TimelineEntry[] = [ + ...reports.map((report) => ({ + kind: 'report', + at: report.createdAt, + reportId: report.reportId, + ticketNumber: report.ticketNumber, + companyName: report.companyName, + })), + ...classifications.map((classification) => ({ + kind: 'classification', + at: classification.createdAt, + verdict: classification.verdict, + confidence: classification.confidence, + })), + ...auditEvents.map((event) => ({ + kind: 'audit', + at: event.createdAt, + eventType: event.eventType, + actor: event.actor, + payload: event.payload, + })), + ]; + + return entries.sort((a, b) => { + const diff = new Date(a.at).getTime() - new Date(b.at).getTime(); + if (diff !== 0) return diff; + return KIND_PRIORITY[a.kind] - KIND_PRIORITY[b.kind]; + }); +}