Read the story

Architecture

Puts each patient's records on one chart.Built for coordinators at three offices.

The CDF dashboard reads the day's book from the Denticon mirror and puts intakes, estimates and recorded consults onto each patient's chart. Rules link what they can prove, a coordinator links the rest, and every link is one audited transaction.

15 min

at most between matcher runs while anyone has the dashboard open

453 of 453

blind test against bookings: when the rules picked a chart, it was the right one

81 of 103

real intakes the name and booking rule links by itself, measured Sep 30. A coordinator confirms the rest

259 links

written by the first backfill with 0 errors, each audited and undoable

New York --:-- simulated

An intake finishes
CDF Patient Dashboard The CDF dashboard reads the day's book from the Denticon mirror and puts intakes, estimates and recorded consults onto each patient's chart. Rules link what they can prove, a coordinator links the rest, and every link is one audited transaction. Estimator lookup estimates and stages Report renderer intake finished event OIDC Google sign in company accounts only Denticon mirror Firestore, from sync Record links rules, then a person CDF dashboard Cloud Run, Next.js 15 Patient profiles one per Denticon chart Coordinators three offices DocReview lookup consults, never scores Records and audit Firestore, report PDFs charts, bookings chart ids profiles use estimates consults intake finished read, link, audit sign in

Hover a part to trace what it touches. Click it for its notes.

  • Built by me
  • Managed by Google
  • Outside system
  • Main data path
  • On demand
  • Credentials cross

When it breaks

What each failure does, and what it does not do.

  1. An intake event is lost

    The renderer retries for about 25 minutes, then alerts. The sweep links every intake without a chart whenever the dashboard is in use, so a lost event delays a link and never loses one.

    docs/plans/2026-09-30-intake-links.md

  2. A lookup is down

    Estimator calls give up at 10 seconds and DocReview at 12. A failure is never cached, so the next caller tries again.

    lib/links/sources.ts:15

  3. The evidence changes later

    Recent automatic links are worked out again on every run, and one whose evidence no longer holds is withdrawn rather than left standing.

    docs/plans/2026-09-30-record-links.md

What I tuned

Small choices that keep it fast and cheap.

  1. Join at read time

    A profile resolves each record once: a link first, then the record's own id. There is no copied master table to drift.

    docs/HANDOFF.md:454

  2. One read, shared three minutes

    Profiles, the directory, the link sheet and the sweep share each lookup's answer instead of fetching it four times.

    lib/links/sources.ts:15

  3. Sweep when the page opens

    Opening the dashboard starts a matcher run when the last finished one is older than 15 minutes. No extra scheduler job to run or watch.

    app/api/links/sweep/route.ts:29

Guards

Rules the code enforces, not rules people remember.

  1. A person's call wins

    The matcher never touches a link a person decided and never links a chart a person rejected.

    lib/links/store.ts:123

  2. Decide again inside the write

    Every link write is one transaction that rereads the mirror, decides again and writes its audit entry. A stale screen gets a 409, not an overwrite.

    lib/links/store.ts:119

  3. Never the scores

    Consult scoring never reaches this screen. The upstream sends an allowlist, and the CDF strips anything named like a score, percent or grade.

    lib/profiles/lookups.ts:193