diff --git a/drb-frontend/lib/types.ts b/drb-frontend/lib/types.ts index 0eb32a6..b352295 100644 --- a/drb-frontend/lib/types.ts +++ b/drb-frontend/lib/types.ts @@ -109,6 +109,17 @@ export interface CallRecord { /** Legacy field — present on calls recorded before the multi-scene migration. */ incident_id?: string | null; location: string | null; + /** Per-call geocode, written by intelligence.py. Powers the incident-path polyline on the map. */ + location_coords?: { lat: number; lng: number } | null; + /** Unit callsigns mentioned in this specific transmission (e.g. "15-9", "E-41"). */ + units?: string[]; + vehicles?: string[]; + /** Units this call reported as clearing/back in service. */ + cleared_units?: string[]; + /** Set when dedup.py identifies this as a second node's recording of the same transmission — the canonical copy has this null. */ + duplicate_of?: string | null; + /** Radio source address, when the system exposes it. */ + srcaddr?: string | null; tags: string[]; status: "active" | "ended"; /** Four-level ladder: routine | minor | moderate | major. Legacy docs may still carry "unknown". */ @@ -134,6 +145,14 @@ export interface IncidentRecord { talkgroup_ids: string[]; units: string[]; vehicles: string[]; + /** Units currently believed on scene — maintained by incident_correlator.py `_attach`. */ + units_active?: string[]; + /** Units that reported clearing/back in service on this incident. */ + units_cleared?: string[]; + /** Free-text location mentions accumulated across the incident's calls, beyond the primary `location`. */ + location_mentions?: string[]; + /** ISO timestamp of the last thin/status-only call attached (doesn't refresh `updated_at`). */ + last_thin_at?: string | null; severity: string | null; started_at: string; updated_at: string; diff --git a/drb-frontend/lib/useCalls.ts b/drb-frontend/lib/useCalls.ts index 6af6aa1..42b355d 100644 --- a/drb-frontend/lib/useCalls.ts +++ b/drb-frontend/lib/useCalls.ts @@ -52,10 +52,17 @@ export function useCalls(limitCount = 50, dateFrom?: Date, dateTo?: Date) { const toISO = (v: any): string | null => v?.toDate?.()?.toISOString?.() ?? (typeof v === "string" ? v : null); unsubFirestore = onSnapshot(q, (snap) => { - setCalls(snap.docs.map((d) => { - const data = d.data(); - return { ...data, started_at: toISO(data.started_at) ?? "", ended_at: toISO(data.ended_at) } as CallRecord; - })); + const docs = snap.docs + .map((d) => { + const data = d.data(); + return { ...data, started_at: toISO(data.started_at) ?? "", ended_at: toISO(data.ended_at) } as CallRecord; + }) + // dedup.py flags a second node's recording of the same transmission + // with duplicate_of set to the canonical call_id — filtered client- + // side (not a where() clause) so this doesn't need a new composite + // index alongside the existing org_id/started_at query. + .filter((c) => !c.duplicate_of); + setCalls(docs); setLoading(false); }, (err: FirestoreError) => { console.error("useCalls:", err); setError(err.message); setLoading(false); }); }); @@ -92,10 +99,12 @@ export function useCallsByIncident(incidentId: string | null) { where("incident_ids", "array-contains", incidentId) ); unsubFirestore = onSnapshot(q, (snap) => { - const docs = snap.docs.map((d) => { - const data = d.data(); - return { ...data, started_at: toISO(data.started_at) ?? "", ended_at: toISO(data.ended_at) } as CallRecord; - }); + const docs = snap.docs + .map((d) => { + const data = d.data(); + return { ...data, started_at: toISO(data.started_at) ?? "", ended_at: toISO(data.ended_at) } as CallRecord; + }) + .filter((c) => !c.duplicate_of); docs.sort((a, b) => a.started_at.localeCompare(b.started_at)); setCalls(docs); setLoading(false); @@ -131,10 +140,14 @@ export function useActiveCalls() { const toISO = (v: any): string | null => v?.toDate?.()?.toISOString?.() ?? (typeof v === "string" ? v : null); unsubFirestore = onSnapshot(q, (snap) => { - setCalls(snap.docs.map((d) => { - const data = d.data(); - return { ...data, started_at: toISO(data.started_at) ?? "", ended_at: toISO(data.ended_at) } as CallRecord; - })); + setCalls( + snap.docs + .map((d) => { + const data = d.data(); + return { ...data, started_at: toISO(data.started_at) ?? "", ended_at: toISO(data.ended_at) } as CallRecord; + }) + .filter((c) => !c.duplicate_of) + ); }, (err: FirestoreError) => { console.error("useActiveCalls:", err); }); });