ADR-0118: Finding-derived UI reads the case record, not the accumulated entity-network graph
Date: 2026-07-21 Status: Accepted Deciders: Adrian (Soft4U), Claude (Opus 4.8) Refines: ADR-0117
Decision context:
- Latency: none — removes graph iteration from the Verdict/Evidence; reads
caseDetailalready loaded. - Dependency surface: none — frontend-only, no new packages.
- Debuggability: high — the finding count now equals the case's
investigation_resultslength, trivially checkable; the graph accumulation no longer leaks into a headline number. - Reversibility: single source swap per component (~3 spots); ~1 hour to undo.
- Blast radius: 3 components (
VerdictStepred flags + Findings KPI,EvidenceStep); the graph visualization (nodes/edges/companies/persons/risk-map) is untouched. - Alternative considered: dedup/scope Finding nodes in the graph backend — rejected (the graph is deliberately bitemporal, ADR-0014; historical findings are a feature there, not a bug — the defect is only presenting the cumulative count as the current case's evidence).
Context
The Network Intelligence view derives several compliance-facing numbers from the
entity-network graph (allNodes): the Verdict's red flags, its "Findings" KPI,
and the Evidence tab's material/routine finding counts.
Finding (and FollowUpTask) graph nodes are created per-investigation and
never deduped — the ETL (populate_knowledge_graph) ingests a fresh set on
every run, keyed by case_id/investigation_id. This is correct for the
bitemporal graph (ADR-0014), which keeps history. But the entity-network fetch
(find_entity_network) returns all Finding nodes within N hops across
every investigation, with no scoping to the current run.
Measured on the live graph (2026-07-21): OB Holding (reg 14975047) carries 1,097 Finding nodes across 52 distinct investigations (dev/test/demo runs plus production re-runs); the depth-2 network sees ~1,123 across 53 cases. So the Evidence tab reported "Showing 40 of 751 material findings. 1,239 routine findings … 742 follow-up tasks" — when the current investigation has only ~15 material / ~26 routine findings and 12 tasks. The number was ~40 recurring findings multiplied across 52 runs.
ADR-0117 had (a) unioned graph Finding/SanctionMatch nodes into the Verdict red
flags to "never drop a graph-only signal", and (b) floored the Findings KPI with
nodeTypeCounts.Finding. Both re-inject the accumulation: the union assumed the
entity-network fetch was subject-and-run-scoped; it is subject-scoped but NOT
run-scoped.
Decision
Source every finding-derived UI element from the current case record
(caseDetail.investigation_results[].findings), not the accumulated graph:
- Verdict red flags — critical/high findings from the case record only (drop the ADR-0117 graph union).
- Verdict "Findings" KPI — the case record's finding count (drop the
Math.maxwithnodeTypeCounts.Finding). - Evidence tab — material list, per-severity summary, routine count and follow-up-task count all from the case record; and the material table paginates (40 per page, "show more") instead of a hard 40-row cap.
The graph visualization (Network/Ownership steps, the companies/persons KPI, the risk map) continues to read the graph — those are entity-structure signals, and Company/Person nodes ARE deduped by identity (survivorship), so they do not accumulate.
Never-suppress holds without the graph union: the current investigation already
covers the subject and its network (e.g. the network_adverse_association
finding), so investigation_results is the complete, authoritative evidence for
this decision. A graph-only finding is another run's artifact or another entity's
own case — not this case's evidence.
Consequences
Positive
- Finding counts are truthful and defensible: the Evidence tab and Findings KPI reflect the investigation the officer is actually reviewing (~15 vs 751).
- Removes accumulation duplicates from the Verdict red flags (52 near-duplicate criticals could otherwise crowd the 6-slot list).
- The Evidence table paginates — no material finding is silently truncated.
Negative
- The Evidence tab no longer surfaces a related entity's own-case findings (e.g. a subsidiary investigated separately). Acceptable: those belong to that entity's case; this case's investigation already folds the relevant network signal into its own findings.
- Two finding sources now exist conceptually (case record for evidence, graph for structure). Documented here to prevent a future re-union.
Neutral
- The graph keeps accumulating history (by design, ADR-0014); nothing is deleted.
- ADR-0117's other decisions (case-scoped view, entity-less empty-state, motif subject-scoping) are unchanged and remain in force.
Alternatives Considered
Alternative 1: Dedup / latest-run-scope Finding nodes in the graph backend
- Collapse Finding nodes per entity, or have
find_entity_networkreturn only the latest investigation's findings. - Why rejected: the bitemporal graph (ADR-0014) intentionally retains historical findings for time-scoped and audit queries; deleting/hiding them there would damage a deliberate capability. The defect is purely presentational, so it is fixed at the presentation layer.
Alternative 2: Keep the union but scope graph findings to the current case_id
- Filter
allNodesFinding nodes toproperties.case_id === caseDetail.case_idbefore unioning. - Why rejected: adds complexity and depends on
case_idbeing present on every frontend node; and a current-run graph finding is already a copy of ainvestigation_resultsfinding (same ETL source), so the union would add nothing the case record doesn't already hold.