# PROTOTYPE vs BUILD — what changed, and why

**Purpose.** A plain, honest comparison of the original prototype against the
current deployment build, for **both** the doctor app and the admin console —
covering behaviour and functions, not just looks. Written so anyone can see what
moved, whether it was on purpose, and why.

**Date:** 2026-07-23 · **Documentation only — no code was changed to produce this.**

---

## What "the build" means (the pieces being compared)

| Artifact | What it is |
|---|---|
| `prototype/iris_chat_prototype.html` | The original single file — doctor app **and** admin console together, AI via the Claude.ai bridge. The reference. |
| `app/iris_chat_app.html` | The doctor app after Step 3: the **same file with only the outbound-call destination changed** (points at the proxy). |
| `mobile/www/index.html` | The doctor app wrapped for phones (Step 4/4.5): **doctor-only** (admin removed), durable offline storage. |
| `admin/index.html` | The **standalone** admin console (Step 5.5) — a separate browser tool that reads the real filed reports. |
| `server/` | The proxy + managed database the build talks to (Steps 2 / 5.5). |

When this document says "the doctor app build," the shipping form is
`mobile/www/index.html`. When it says "the admin console build," it means
`admin/index.html`.

---

## Legend for the flags

- 🟢 **Deliberate — at parity.** Same behaviour; only the plumbing moved.
- 🔒 **Deliberate — confidentiality.** Removed or restricted on purpose to protect real names.
- 🟡 **Deliberate — not yet built.** Intentionally deferred; needs building for the pilot/real product.
- 🔴 **Unintended regression.** Something that broke by accident. *(None found — see the summary.)*

---

## Bottom line first

- **Doctor app: at full parity in behaviour.** The masking, name-hiding gate,
  extraction, game vocabulary, confirm logic, duplicate/backdating handling,
  multi-play, offline verification, stock reporting, and cost metering are the
  **same code** as the prototype. The doctor app changed in only three ways —
  where its calls go, where it saves offline reports, and how it's packaged —
  all deliberate.
- **Admin console: deliberately narrower than the prototype's.** The standalone
  console is **read-only** and **codes-only** by design. Everything missing was
  removed either to protect real names (🔒) or because it is deferred to a later
  build (🟡).
- **No unintended regressions were found (no 🔴).** The closest things a reader
  might call a regression — the admin console's lost buttons, and no built-in
  admin sign-in — are deliberate and are documented under "not yet built."

---

# Part A — Doctor app

### What is identical (behaviour and functions)

Confirmed the same in the prototype and both build copies (the code differs by
only 17 lines, all of them call-destination lines):

- On-device masking and substitution; the two-net patient extraction; the
  net-3 "verify a possible name before sending" gate (including the surname-first
  fix, DECISIONS #55) — **byte-for-byte the same engine**.
- Game-vocabulary display, the banned-word output filter, confirm cards
  (single + multi-play), duplicate detection, backdating/Era logic, off-assignment
  flagging, the quick-access Location chips, attachments (file + photo),
  automatic offline handling, on-device report/stats, out-of-stock reporting,
  and client-side cost metering.

### What changed

| # | Area | Prototype | Build | Deliberate? | Why | Flag |
|---|---|---|---|---|---|---|
| A1 | **Outbound call destination** | AI call goes straight to Anthropic via the Claude.ai bridge (keyless, only works inside Claude.ai); backend calls hit the same origin | All outbound calls go through one config value (`PROXY_BASE`) to the `/server` proxy, which holds the key | Yes (Step 3) | The app must reach the AI on its own once hosted, and the key must live on the server, never in the app | 🟢 |
| A2 | **Offline parked-report storage** | The waiting-to-send queue lives in memory only (no storage allowed in a Claude.ai artifact). A parked report is lost if the app closes/crashes/restarts before signal returns | The queue is saved to the phone's own storage: restored on launch, saved on every park, each item removed only after it actually files | Yes (Step 4.5) | Reliability — a report must survive the app closing in a patchy-signal hospital. An improvement over the prototype | 🟢 |
| A3 | **A half-finished, unconfirmed free-text turn across a full restart** | Held in memory; resumes in the same session on reconnect; does not survive a full restart | Same — only the confirmed/parked queue is made durable; a mid-conversation turn still isn't restored after a full restart | Yes (scoped in Step 4.5) | Persisting a mid-conversation turn needs full session restore, which is beyond a storage-location change | 🟡 |
| A4 | **Admin console on the phone** | The single file carries **both** the doctor app and the admin console (admin behind an admin login) | The wrapped phone build is **doctor-only** — the admin console (~950 lines) and admin credentials are removed; the roster data the masking needs stays | Yes (DECISIONS #53) | Real-name roster data and admin logic must never ship on a doctor's phone (invariant #1); also shrinks the app. Relocated to the standalone console, not deleted | 🔒 |
| A5 | **Packaging** | Runs as a web page (inside Claude.ai or the local harness) | Wrapped with Capacitor into real iOS/Android projects, reusing the web code as-is; `config.js` sets the proxy URL; camera + offline wired to the native equivalents | Yes (Step 4) | To install on real phones and publish through the stores | 🟢 |
| A6 | **Camera / photo OCR and file parsing** | Photo held in memory; the doctor confirms/types the text; CSV/text tables read in-browser and masked on-device. No real OCR (manual confirm stands in); no native Excel/Word reading | **Built (Step 5.6B).** Real on-device readers: **Excel (.xlsx) / Word (.docx)** unzipped + extracted in the web view (browser `DecompressionStream`, no third-party lib, nothing uploaded); **photo OCR** via the bundled Capacitor plugin `mobile/plugins/iris-ocr` (**Apple Vision** iOS / **ML Kit** Android, image never uploaded). All extracted text funnels through ONE choke point into the **existing** on-device masking/name-gate before anything else. Browser fallback: doctor types the text when the native OCR plugin is absent. Native OCR compiles on IT's machine (same boundary as Step 4). | Yes (DECISIONS #60) | Real-build item flagged since DECISIONS #45; the CRITICAL rule is that raw extracted text is masked exactly like typed text | 🟢 |

> **Note on `app/` vs `mobile/`.** The Step 3 copy (`app/iris_chat_app.html`)
> still contains the admin console — it is the full prototype with only the call
> path changed (A1). The **doctor-only** removal (A4) applies to the wrapped
> phone build (`mobile/`). Both share the identical doctor-side engine.

---

# Part B — Admin console

The prototype's admin console and the standalone build differ the most. The
standalone was built to a deliberately stricter brief (Step 5.5): **read-only,
codes-only, holds no code→name lookup.** The look and tab layout were then
matched to the prototype so it feels familiar — but *what it shows* was not
widened.

### Structural differences

| # | Area | Prototype admin | Build (`admin/index.html`) | Deliberate? | Why | Flag |
|---|---|---|---|---|---|---|
| B1 | **Delivery** | One of two views inside the single app, reached by admin login | A **separate standalone file**, hosted on its own | Yes (DECISIONS #28/#53) | Keeps admin off doctors' phones; matches the "separate desktop web tool" decision | 🔒 |
| B2 | **Sign-in / access control** | Behind an admin card + PIN login (`IRIS-ADMIN`) inside the app | **No built-in login.** Relies on the host putting it behind normal sign-in / network controls (stated in its README) | Partly | A standalone read-only tool leans on host-level access for the pilot; a proper auth gate is a real-build item. **Flagged — needs building before real back-office use** | 🟡 |
| B3 | **Interactivity** | **Read-write:** dispositions, roster edits, resolve-and-notify, Era locking | **Read-only — never writes** | Yes (Step 5.5) | Safety-first first cut; the back-office *actions* are deferred (see B-actions below) | 🟡 |
| B4 | **Data source** | Reads the harness backend, plus in-memory demo seeds, plus the roster (real names) held in memory | Reads the managed database via GET only; holds **no roster and no demo seeds** | Yes (Step 5.5 / #56) | Real data, codes-only; no code→name table in the console | 🔒 |
| B5 | **Real-name handling** | Resolves codes → real names throughout (it is the privileged back office) | Shows codes only, **except** the whitelisted review-queue and feedback fields; never fetches the roster | Yes (Step 5.5 point 4) | The standalone console must never expose the code→name lookup or un-code a report | 🔒 |

### Tab-by-tab

| Tab | Prototype does | Build does | Deliberate? / Why | Flag |
|---|---|---|---|---|
| **Roster** | Full add/edit/delete of doctors, hospitals, products; real names; alternate names; card/PIN issuance; assigned Locations | **Locked placeholder** — shows nothing | Yes — it *is* the code→name list, which this console must never show; roster management stays in the secure back office / real build | 🔒 🟡 |
| **Review queue** | Entries **plus actions**: add as new to roster, add as alternate name (self-growing library), reject, link to existing | Read-only table with the **whitelisted fields** (type, temp code, typed name, Location, Era, date) — no actions | Yes — read-only by design; the sanctioned typed name still shows; dispositions deferred | 🟡 |
| **Dashboards** | Stat tiles + "Units by group (Blaze/Breeze/Terra)" + Plays per Location + Top Avatars + Player activity, scoped to the current Era | Stat tiles + Plays per Location + Top Avatars + Player activity, over all plays. **"Units by group" dropped** | Yes — the group mapping needs the product dictionary, which also holds real names; omitted to keep zero code→name data in the console | 🔒 |
| **AI Cost** | Full dashboard: budget + alert, projections (current + target headcount), period selector, editable peso rate, cost-over-time trend, by-doctor/Location/function breakdowns, unit economics, clarify overhead, Haiku lever, health strip | Tiles (total spend, tokens in/out, cache savings) + 3 breakdowns (by model / Player / function), read-only, no controls | Yes — the codes-only core is reproduced; the elaborate controls/projections are a hosted-build item | 🟡 |
| **Reconcile** | Match logged plays vs distributor pull-through; resolve variances; lock the Era | **Locked placeholder** — shows nothing | Yes — needs the Data Team's pull-through file, which this backend doesn't have | 🟡 |
| **Reports** | Era report table + Era selector + CSV download + a disabled "PDF coming" button | Era report table + Era selector + CSV download | Yes — near parity; only the disabled-PDF placeholder is absent | 🟢 |
| **Stock** | Resolves Avatar→real product name and Location→real hospital name; surfaces an unknown hospital's typed name with "Add to roster"; **resolve + notify the doctor** (writes an update back) | **Codes only** (Avatar + Location codes), read-only; no name resolution; no resolve/notify | Yes — no name resolution (confidentiality); the resolve-and-notify write-back is deferred | 🔒 🟡 |
| **Feedback** | Table + "Mark reviewed" action + red highlight for "name on screen" | Table (date, Player, category, message, status) + red highlight; read-only (no Mark reviewed) | Yes — read-only; the action is deferred | 🟡 |
| **Issues** | Live duplicate detection + demo sample issues + "Mark resolved" | Live duplicate detection only (codes-only); read-only; **no demo samples** | Yes — demo samples correctly dropped (they were sample data); the action is deferred | 🟢 🟡 |
| **Requests** | Demo request data + "Mark done" | **BUILT (DECISIONS #61)** — a real queue: doctor-submitted requests (correct/cancel a play, supply follow-up; masked on-device) + admin back-office to-dos; resolve-and-notify back to the doctor | No — real backend `/api/requests` + admin UI + doctor "Raise a request" | 🟢 |

### Admin capabilities removed across tabs (write-back actions)

The prototype admin could **act**; the build can only **read**. These actions are
present in the prototype and **not yet built** in the standalone console:

- Add-to-roster clean-up flow (promote a temporary code to a real roster entry).
- The self-growing name library (link an unknown typed name to an existing roster entry so the app recognises it thereafter).
- Card / PIN issuance to doctors.
- Off-assignment, late-entry, and unknown-product dispositions.
- Resolve-and-notify a stock alert back to the reporting doctor.
- Mark feedback reviewed / mark an issue resolved / lock an Era.

All 🟡 — deliberately deferred; needed for the back office to actually work a
queue during the pilot.

---

## The three flags, gathered

### (a) Unintended regressions — 🔴
**None found.** The doctor app is behaviour-identical to the prototype, and every
admin-console difference is deliberate. The two items most likely to be *mistaken*
for regressions are covered below: the admin console's read-only nature (B3) and
its lack of a built-in sign-in (B2) — both intentional, both listed under "not
yet built."

### (b) Deliberately removed for confidentiality — 🔒
- Admin console removed from the doctor phone build (A4).
- The standalone console is a separate tool that holds no roster and reads codes only (B1, B4, B5).
- **Roster tab** locked — the code→name list is never exposed (Roster row).
- **Stock tab** shows codes only — the prototype's code→real-name resolution is deliberately not reproduced (Stock row).
- **Dashboards** "Units by group" dropped to avoid pulling in the product dictionary (which holds real names).

### (c) Not yet built (deferred) — 🟡
- **Admin console access control / sign-in (B2)** — currently relies on host-level access. *Recommend building a proper gate before real back-office use.*
- **All back-office write-back actions (B3 + the actions list)** — dispositions, add-to-roster, resolve-and-notify, mark reviewed/resolved, Era lock.
- **Reconcile** — the one remaining locked tab (needs the Data Team pull-through file). Roster management (5.6A) and Requests (#61) are now built.
- **AI Cost** full controls (budget, projections, period, trend) beyond the codes-only core.
- Doctor app: ~~real on-device OCR and native Excel/Word parsing (A6)~~ — **built in Step 5.6B** (DECISIONS #60); **persisting a mid-conversation unconfirmed turn across a full restart** (A3) still open.

---

## Also worth knowing (unchanged in both — including a known gap)

- The **surname-first name leak** (DECISIONS #55) — an all-caps single surname
  after a preposition ("for SANTOS, Maria") — is present and unfixed in **both**
  the prototype and the build; it is not a build regression. It remains the one
  open item before real doctors (tracked; the QA suite prints it every run).
- The masking safety guarantees hold identically in prototype and build (46/46
  name-hiding checks; the Step 5.5 console proof 15/15).
