diff --git a/src/components/Findings.tsx b/src/components/Findings.tsx
index 6206e16..4cf2ee7 100644
--- a/src/components/Findings.tsx
+++ b/src/components/Findings.tsx
@@ -38,7 +38,7 @@ export function Verdict({
const summary = str(row.summary);
if (!summary) return null;
const line = prose(headline(summary));
- const scanned = prose(row.scanned_summary);
+ const scanned = str(row.scanned_summary);
const affectedHref = affectedClaimsHref(kind, slice, dates);
const recoverableHref = recoverableClaimsHref(kind, slice, dates);
const atRisk = num(row.amount_at_risk);
@@ -98,6 +98,8 @@ export function Verdict({
* 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 }) {
+ // longDate passes non-ISO text through unchanged, so an already-humanised
+ // range renders as it was written.
const span = (r: { from: string; to: string } | null) => (r ? `${longDate(r.from)} – ${longDate(r.to)}` : '—');
return (
@@ -105,12 +107,12 @@ function Periods({ f }: { f: ReviewedFacts }) {
This period
{span(f.window)}
- {f.windowClaims &&
{f.windowClaims} claims
}
+ {f.windowClaims &&
{f.windowClaims} claims reviewed
}
Compared with
{span(f.baseline)}
- {f.baselineClaims &&
{f.baselineClaims} claims
}
+ {f.baselineClaims &&
{f.baselineClaims} claims as the normal picture
}
@@ -135,35 +137,67 @@ function Periods({ f }: { f: ReviewedFacts }) {
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',
- };
- return (
-
- {heading &&
{heading}
}
- {rows && reviewed &&
}
- {rows ? (
-
- {rows.filter((r) => !(reviewed && r.key === 'REVIEWED')).map((r) => (
-
-
- {r.label}
- -
- {r.items.map((it, i) => (
-
- {it}
-
- ))}
-
-
- ))}
-
- ) : (
+ const row = (k: string) => rows?.find((r) => r.key === k);
+
+ // Older cases wrote a paragraph; bullets beat a wall of text.
+ if (!rows) {
+ return (
+
+ {heading &&
{heading}
}
- {sentences(text).map((s, i) => (
+ {sentences(prose(text)).map((s, i) => (
- {s}
))}
+
+ );
+ }
+
+ // The four lines have different jobs, so they are not four identical chip
+ // rows: the periods are a comparison, the findings are the point, the rest
+ // is reassurance and belongs in small type.
+ const found = row('FOUND') ?? row('ACTIONS');
+ const watched = row('WATCHED');
+ const clear = row('CLEAR');
+ const rest = rows.filter((r) => !['REVIEWED', 'FOUND', 'WATCHED', 'CLEAR'].includes(r.key));
+
+ return (
+
+ {heading &&
{heading}
}
+ {reviewed &&
}
+ {!reviewed && row('REVIEWED') && (
+
{row('REVIEWED')!.items.map(prose).join(' · ')}
+ )}
+
+ {found && found.items.length > 0 && (
+
+ {found.items.map((it, i) => (
+ -
+
+ {prose(it)}
+
+ ))}
+
+ )}
+
+ {rest.map((r) => (
+
+ {r.label}:
+ {r.items.map(prose).join(' · ')}
+
+ ))}
+
+ {watched && watched.items.length > 0 && (
+
+ Watched, below the threshold:
+ {watched.items.map(prose).join(' · ')}
+
+ )}
+ {clear && clear.items.length > 0 && (
+
+ Checked, nothing found:
+ {clear.items.map(prose).join(' · ')}
+
)}
);
@@ -306,7 +340,7 @@ export function SqlEvidence({ row, steps, decisions }: { row: Row; steps: AvaTra
/** The re-check. Same shape either way, so "fixed" and "not fixed" read alike. */
export function Recheck({ row }: { row: Row }) {
- const summary = prose(row.recheck_summary);
+ const summary = str(row.recheck_summary);
if (!summary) return null;
const current = num(row.current_rate);
const baseline = num(row.baseline_rate);
diff --git a/src/format.ts b/src/format.ts
index 7987fe0..43ade57 100644
--- a/src/format.ts
+++ b/src/format.ts
@@ -225,8 +225,24 @@ export function workStatement(v: unknown): Array<{ key: string; label: string; i
const m = line.match(/^([A-Z][A-Z ]{2,20}):\s*(.*)$/);
if (!m) continue;
const key = m[1].trim();
- const items = m[2].split(/;\s*/).map((x) => x.trim().replace(/[.]$/, '')).filter(Boolean);
- rows.push({ key, label: WORK_LABELS[key] ?? key.charAt(0) + key.slice(1).toLowerCase(), items });
+ // Split on ";" but not inside brackets — "(… $6,764,000; 60.4% pending)"
+ // is one fact, not two.
+ const items: string[] = [];
+ let depth = 0;
+ let buf = '';
+ for (const ch of m[2]) {
+ if (ch === '(' || ch === '[') depth++;
+ else if (ch === ')' || ch === ']') depth = Math.max(0, depth - 1);
+ if (ch === ';' && depth === 0) {
+ items.push(buf);
+ buf = '';
+ continue;
+ }
+ buf += ch;
+ }
+ items.push(buf);
+ const cleaned = items.map((x) => x.trim().replace(/[.]$/, '')).filter(Boolean);
+ rows.push({ key, label: WORK_LABELS[key] ?? key.charAt(0) + key.slice(1).toLowerCase(), items: cleaned });
}
return rows.length >= 2 ? rows : null;
}
@@ -253,9 +269,13 @@ export function reviewedFacts(items: string[]): ReviewedFacts | null {
if (m) kv[m[1].toLowerCase()] = m[2].trim();
}
if (!kv.window && !kv.baseline) return null;
+ // Accept ISO ("2026-08-02..2026-09-13") and already-humanised dates, since
+ // the same text is sometimes rendered before it is parsed.
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;
+ const iso = v?.match(/(\d{4}-\d{2}-\d{2})\s*(?:\.\.|to|–|-)\s*(\d{4}-\d{2}-\d{2})/);
+ if (iso) return { from: iso[1], to: iso[2] };
+ const human = v?.match(/^(.+?)\s*(?:\.\.|\bto\b|–)\s*(.+)$/);
+ return human ? { from: human[1].trim(), to: human[2].trim() } : null;
};
return {
window: range(kv.window),