diff --git a/src/components/Findings.tsx b/src/components/Findings.tsx index a2b140d..6206e16 100644 --- a/src/components/Findings.tsx +++ b/src/components/Findings.tsx @@ -2,11 +2,12 @@ import { useState } from 'react'; import { Link } from 'react-router-dom'; import { AlertTriangle, CheckCircle2, ChevronDown, ExternalLink, FileSearch } from 'lucide-react'; import type { Slice } from '../api/charts'; +import type { ReviewedFacts } from '../format'; import { affectedClaimsHref, recoverableClaimsHref } from '../lib/claimsLink'; import type { LinkDates } from '../lib/claimsLink'; import { Badge, Card, Facts } from './core'; import type { AvaDecision, AvaTraceStep, Row } from '../api/types'; -import { firstDeadline, headline, money, num, pct, prose, recommendations, sentences, shortDate, splitRecommendation, sqlSteps, str, workStatement } from '../format'; +import { firstDeadline, headline, longDate, money, num, pct, prose, recommendations, reviewedFacts, sentences, shortDate, splitRecommendation, sqlSteps, str, workStatement } from '../format'; /** * Ava's findings, laid out for a person who has ninety seconds. @@ -95,8 +96,45 @@ export function Verdict({ /** What was covered, as rows of short facts — not a paragraph. Ava writes it * as labelled lines; older cases wrote prose, which falls back to sentence * bullets so nothing renders as a wall of text. */ +/** The comparison, drawn: this period against that one, then what was set aside. */ +function Periods({ f }: { f: ReviewedFacts }) { + const span = (r: { from: string; to: string } | null) => (r ? `${longDate(r.from)} – ${longDate(r.to)}` : '—'); + return ( +
+
+
+

This period

+

{span(f.window)}

+ {f.windowClaims &&

{f.windowClaims} claims

} +
+
+

Compared with

+

{span(f.baseline)}

+ {f.baselineClaims &&

{f.baselineClaims} claims

} +
+
+

+ {f.pendingExcluded && ( + <> + Set aside {f.pendingExcluded} pending claims (the insurer has not answered yet) + + )} + {(f.insurers || f.departments || f.desks) && ( + <> + {f.pendingExcluded ? ' · ' : ''}Looked across{' '} + {[f.insurers && `${f.insurers} insurers`, f.departments && `${f.departments} departments`, f.desks && `${f.desks} desks`].filter(Boolean).join(' · ')} + + )} + {f.ceiling && <> · Nothing after {longDate(f.ceiling)} was used} + {f.requested && <> · Requested {span(f.requested)}; the last 6 weeks of it were compared with the 8 before them} +

+
+ ); +} + function WorkStatement({ text, heading = 'Review coverage', bare = false }: { text: string; heading?: string; bare?: boolean }) { const rows = workStatement(text); + const reviewed = rows ? reviewedFacts(rows.find((r) => r.key === 'REVIEWED')?.items ?? []) : null; const TONE: Record = { FOUND: 'text-warn', WATCHED: 'text-muted', CLEAR: 'text-good', REVIEWED: 'text-ink', 'SINCE APPROVAL': 'text-ink', ACTIONS: 'text-ink', RATE: 'text-ink', VERDICT: 'text-warn', @@ -104,9 +142,10 @@ function WorkStatement({ text, heading = 'Review coverage', bare = false }: { te return (
{heading &&

{heading}

} + {rows && reviewed && } {rows ? (
- {rows.map((r) => ( + {rows.filter((r) => !(reviewed && r.key === 'REVIEWED')).map((r) => (
{r.label}
diff --git a/src/format.ts b/src/format.ts index 3daa1a8..7987fe0 100644 --- a/src/format.ts +++ b/src/format.ts @@ -231,6 +231,46 @@ export function workStatement(v: unknown): Array<{ key: string; label: string; i return rows.length >= 2 ? rows : null; } +/** The REVIEWED row as fixed fields. Ava writes `window=2026-08-03..2026-09-13; + * window_claims=14,860; baseline=…` — this turns that into the two periods + * and the counts the console draws. Null when the row is older free text. */ +export interface ReviewedFacts { + window: { from: string; to: string } | null; + windowClaims: string; + baseline: { from: string; to: string } | null; + baselineClaims: string; + pendingExcluded: string; + insurers: string; + departments: string; + desks: string; + ceiling: string | null; + requested: { from: string; to: string } | null; +} +export function reviewedFacts(items: string[]): ReviewedFacts | null { + const kv: Record = {}; + for (const it of items) { + const m = it.match(/^([a-z_]+)\s*=\s*(.+)$/i); + if (m) kv[m[1].toLowerCase()] = m[2].trim(); + } + if (!kv.window && !kv.baseline) return null; + const range = (v?: string) => { + const m = v?.match(/(\d{4}-\d{2}-\d{2})\s*(?:\.\.|to|–|-)\s*(\d{4}-\d{2}-\d{2})/); + return m ? { from: m[1], to: m[2] } : null; + }; + return { + window: range(kv.window), + windowClaims: kv.window_claims ?? '', + baseline: range(kv.baseline), + baselineClaims: kv.baseline_claims ?? '', + pendingExcluded: kv.pending_excluded ?? '', + insurers: kv.insurers ?? '', + departments: kv.departments ?? '', + desks: kv.desks ?? '', + ceiling: kv.ceiling ?? null, + requested: range(kv.requested), + }; +} + /** 2026-07-26 → "Jul 26, 2026". For prose; shortDate is for axes and chips. */ export function longDate(iso: string | null): string { if (!iso) return '';