# Project Brief — "IRIS Chat" (working name)
### AI-Assisted Conversational Usage Reporting for Hospital MDs
**Version:** 1.3 (v1.1 photo capture; v1.2 display-side game vocabulary; v1.3 masking & extraction design law, AI guardrails, and hardening from prototype stress-testing) · **Owner:** Von (COO) · **Status:** Locked architecture, prototype v1 stress-tested
**Replaces:** IRIS app (form-based, code-memorization reporting)

---

## 1. Problem

The current IRIS app requires doctors to memorize an entire coded vocabulary before they can file a usage report:

- Product codes (Kenzolin → PRAZ, Prosec → OTEM, …)
- Hospital pseudonames (e.g., SATURN 33)
- Patient identity format (initials + age)
- Co-player / referring MD aliases
- Game vocabulary that adds a second layer (Era = month, Match = patient, Game Mode = single/multi-MD)

Result: high friction → late or missing reports → the Data Team recomputes PRD shares for entire hospitals → delayed treats crediting → mistrust of the system. The 10th-of-the-month deadline cascade is the core business pain.

## 2. Concept

Doctors report the way they already communicate: **by chatting naturally, using real names.**

> "Gave 6 vials Kenzolin to Xyriel Yamat, 25, at Cardinal Santos. 2 more vials came from Mercury. Referred by Dr. Ken Dela Cruz."

Before that message ever renders in the chat window or leaves the phone, the app masks it:

> "Gave 6 vials **PRAZ** to **XY-25** at **SATURN 33**. 2 more vials (OH). Referred by **THOR**."

The AI then parses the masked message into a structured report, asks for anything missing, confirms, and files it. Doctors can also ask the assistant for their summaries and stats, and the assistant proactively nudges them before deadlines.

**Photo input:** instead of typing, the doctor can snap a photo of a patient record, prescription, or their own handwritten notes. Text is extracted **on the device**, run through the same masking engine, and only the masked text reaches the AI — which then summarizes it into the same confirmation card. **The image itself is never saved and never uploaded.**

**Tagline candidate:** Report like you talk. We handle the codes.

## 3. Locked Architecture Decisions

| # | Decision | Choice |
|---|----------|--------|
| 1 | Platform | Standalone mobile app (replaces IRIS) |
| 2 | Masking | **Hybrid** — deterministic on-device masking; server LLM sees masked text only |
| 3 | V1 scope | Capture + Query + Proactive nudges |
| 4 | Gamification | **Two-sided:** input side is friction-free (doctors type real names — no codes to memorize); display side speaks full game vocabulary as a **confidentiality layer** (see §4a), plus streaks & progress bars |
| 5 | Back-office depth | Full pipeline incl. deterministic crediting engine (parallel-run 1 cycle) |
| 6 | PO / Area Sales data | Data Team monthly file upload with strict ingest validation |
| 7 | Stack | React Native / Expo; COO prototype in Claude Code → IT handoff |

## 4. The Confidentiality Boundary (non-negotiable)

**Principle preserved from IRIS: the company's servers never receive patient identity or unmasked entities.**

- All masking is **deterministic and on-device**, using three lookup tables synced to the app:
  1. `products` — brand name + generic + common misspellings → IRIS code
  2. `hospitals` — real name + abbreviations → pseudoname
  3. `md_aliases` — accredited member real names → alias (scoped per hospital)
- **Patient identity** is never free-text-detected. The chat composer has a dedicated **Patient chip**: doctor types the name + age; the field converts to `initials + age` live on screen (Xyriel Yamat, 25 → XY-25). Only the initials ever exist in the message payload.
- Fuzzy matching (edit distance ≤ 2, phonetic variants) catches misspellings on-device.
- If a word *looks like* it could be an unmasked entity (e.g., "Dr." + unrecognized name), it is **converted in place to a stable temporary code** (PEND-1 for a doctor, LOC-PEND-1 for a Location) — the code is what appears on screen, in the report data, and in the message to the LLM. The report is never blocked (DECISIONS #39, "convert don't reject"). **The raw typed name never reaches the LLM and never enters the codes-only plays store.** It travels on ONE path only: the **access-controlled back-office review queue**, where a human resolves the unknown entity into the roster (the self-growing name library, add-to-roster). This back-office channel is the sanctioned real-name exception (see §4b and DECISIONS #47); it is separate from the codes-only data pipeline and, in the real build, an authenticated admin endpoint. **Patient identity never uses this channel** — patients are converted to initials on-device and are never sent anywhere in the clear.
- The server-side LLM receives **only the masked message** for parsing and conversation. Full parsing power, zero identity exposure.
- Raw keystrokes are never logged. Lookup tables are the only PII-adjacent artifacts, distributed to the device, not collected from it.

**Image capture rules (photos of patient records / prescriptions / handwritten notes):**
- OCR runs **fully on-device** (Apple Vision framework on iOS, Google ML Kit Text Recognition on Android — both offline, no network call). The raw image is **never uploaded to any server, including the LLM.**
- The image is held **in memory only**: never written to app storage, never saved to the phone's gallery, no thumbnail, no cache. Discarded immediately after text extraction. The in-app camera is used (not the gallery picker) so no copy is created outside the app.
- Extracted text passes through the **same on-device masking engine + send gate** as typed text (products, hospitals, MD aliases auto-masked via lookup tables).
- **Patient name in a photo:** since we deliberately avoid free-text name detection, the app shows the extracted text in a local preview and asks the doctor to **tap the patient's name** — it converts to initials on the spot (simple heuristics can pre-highlight likely candidates, but the doctor confirms). Send is blocked until a patient is tagged or the doctor marks "no patient identity in this text." Human-in-the-loop, entirely on-device.
- What the server ultimately receives from a photo is indistinguishable from a typed masked message.
- If on-device OCR can't read the text confidently (very messy handwriting), the app asks the doctor to type the report instead. **There is no server-side fallback for images** — that path stays closed by design.

## 4a. Display-Side Game Vocabulary (shoulder-surfing protection)

Prototype learning: **the chat transcript itself is a confidentiality surface.** A colleague, patient, or hospital staffer glancing at the doctor's phone must not see words like *brand, hospital, patient, vials, medication, doctor, prescription, report* — the copy alone would reveal what the app is. So the vocabulary asymmetry is deliberate:

- **Input side (what the doctor types):** real names, plain language, zero memorization. This is the friction fix.
- **Display side (everything the system renders):** full IRIS game vocabulary. All UI copy, AI messages, confirmation cards, empty states, error messages, and **push notifications** (lock-screen visible!) use game terms only.

**Vocabulary of record** (continuous with the trained field force's IRIS terms):

| Real concept | On-screen term |
|---|---|
| Product code | Avatar |
| Product groups (IV / Inhaler / Nutra) | Blaze / Breeze / Terra |
| Hospital pseudoname | Location |
| Month (YYMM, e.g. 2607 = Jul 2026) | Era |
| Patient initials + age (XY-25) | Match / Username |
| The doctor | Player |
| Other accredited MD alias | Co-Player |
| Quantity | Units |
| Source: hospital pharmacy / outside | I / O |
| A filed report | A play |
| Filing a report | Logging a play |
| MD who started the med | The starter |
| Single vs multi-MD case | Game Mode: Single / Multiple |

**Enforcement is two-layer:** (1) the LLM system prompt carries the strict vocabulary rule with the banned-word list; (2) production adds a **deterministic output filter** — every AI message is scanned against the banned list before rendering, and any violation is rejected and regenerated. Prompts alone are not a guarantee; the filter is.

Masking is **silent**: no "preview" of the masked message is shown while typing (a preview strip duplicates content and itself explains the mechanism to onlookers). The doctor types real names; coded chips appear only in the sent bubble. The only pre-send surface is the **send-gate warning** when an unrecognized name is detected (rendered in game terms: "Unknown Co-Player — send blocked").

## 4b. Masking & Extraction Design Law (hard-won from prototype stress-testing)

These rules were each discovered by a real failure during live testing. They are binding for the Expo build.

**1. Substitution, not redaction.** Every masked entity — product, hospital, MD, patient — is replaced **in place**, preserving the doctor's word order and grammar ("gave 2 praz to juan reyes, 70 yrs old" → "gave 2 PRAZ to JR-70"). Never cut an entity out and append its code elsewhere; intact syntax reads naturally to the doctor and parses more reliably for the LLM.

**2. Convert, don't reject.** The composer's job is to transform unsafe input into safe input silently — never to bounce the doctor back to retype. Escalation ladder: exact match → fuzzy match → silent local extraction → (last resort only) block with guidance. The **only** hard block is an unrecognized doctor name ("Dr. + unknown"), because sending it would leak an unregistered person's identity.

**3. Silent patient conversion, two nets, both on-device and deterministic:**
- *Net 1 — typo-tolerant, Taglish-aware age detection.* The age-unit word is fuzzy-matched, never exact-matched: "50urs old", "50 yesr old", "50yo", "45/F", "aged 50", "edad 50", "50 anyos" all convert. A deny-list ("2 hrs", "500mg", "6 vials", "2x") prevents false triggers.
- *Net 2 — name with no age.* 2+ non-dictionary words after a patient preposition (to / for / kay / si / ni / px / patient) convert to initials alone; the AI then asks only for the age as a number. The 2-word minimum prevents "to ICU" false positives.
- The conversion is invisible — no warning, no confirmation tap. The **Confirm Game Play table (USERNAME row)** is the human checkpoint before anything is logged.

**4. Surname-context rule.** Shared-surname ambiguity (Dela Cruz, Reyes, Santos…) is resolved by **context, not dictionary membership**: an MD alias masks ONLY on full-name match or an honorific ("Dr./Doc") prefix; a lone surname always belongs to the patient. Corollary: **the alias registry must never contain bare-surname variants.** Extraction breaks only on words that are unambiguous entities alone (product names, hospital abbreviations, honorifics).

**5. Layered nets, honestly assessed.** Heuristics are not a complete net (a lone first name with no preposition and no age can slip). Production defense in depth: (1) deterministic nets above → (2) lightweight on-device name detector (Expo build) → (3) AI never-echo rule → (4) no raw-name storage server-side. Each layer catches most of what the previous misses.

## 4c. AI Guardrails (parsing conversation rules)

- **Never assume.** A Co-Player and Game Mode must be *explicitly established* — an alias merely appearing in a message is not confirmation of involvement or role. No alias mentioned → ask the generic "Single or Multiple?" question without inventing names.
- **Logical inference — never ask what's answered or implied.** "Just me"/Single resolves the entire role chain at once (Single mode + player is starter + no Co-Player). "I started it" resolves the starter. Every clarify question first re-checks the conversation; each redundant question costs doctor trust.
- **Never echo unrecognized free text** that could be a real-world name — redirect (pick a valid Location; use ＋Match) without repeating what was typed.
- **Initials-only Match** → ask for the age as a plain number, combine to initials-age.
- **One clarifying question at a time**, always in game vocabulary.

## 4d. LLM Proposes, Deterministic Code Disposes (parsing layer)

The model's structured output is never trusted:
- **Schema validation** client-side on every confirm payload (hospital, patient, non-empty items with qty > 0 required).
- **Normalization** of model quirks before render: month coerced to string, `"null"` string → null, source coerced to H/OH from variants (O, outside, I).
- **Auto-repair loop:** an incomplete confirm is bounced back to the model once ("re-send complete or ask a clarify") before anything renders — invisible to the doctor.
- **The confirmation card can never render broken:** missing fields display as explicit "⚠ missing — tap Edit" markers, and the Edit / Confirm & Submit buttons are guaranteed in every render path including the fallback.
- Same principle as the crediting engine (§7), applied at the parsing layer.

Compliance posture: Bell-Kenz remains a non-recipient of patient sensitive personal information (PH Data Privacy Act). This is the same posture as today's IRIS — worth confirming with legal, but no new exposure is created.

## 5. System Architecture

```
┌─────────────── DOCTOR'S PHONE (React Native / Expo) ───────────────┐
│                                                                     │
│  Chat Composer                                                      │
│   ├─ Live masking engine (lookup tables + fuzzy match)  ← on-device │
│   ├─ Patient chip (name+age → initials, local only)     ← on-device │
│   ├─ Photo capture → on-device OCR (Vision / ML Kit)    ← on-device │
│   │    image in memory only, never stored/uploaded,                 │
│   │    extracted text → masking engine → local preview              │
│   └─ Send gate (blocks unmasked entities)               ← on-device │
│                                                                     │
│  Chat UI · Streaks & progress bars · Push notifications             │
└──────────────────────────────┬──────────────────────────────────────┘
                               │  masked text only (TLS)
┌──────────────────────────────▼──────────────────────────────────────┐
│                        BACKEND (server)                              │
│                                                                      │
│  1. Conversation service — LLM (Claude API)                          │
│     · Parses masked chat → structured report JSON                    │
│     · Asks clarifying questions (missing qty, source, month…)        │
│     · Answers doctor queries against their own data                  │
│     · NEVER performs crediting math                                   │
│                                                                      │
│  2. Report store (DB) — reports, confirmations, audit trail          │
│                                                                      │
│  3. Crediting Engine — DETERMINISTIC CODE, fully testable            │
│     · Validation: Total Reported vs Total PO (hospital/product/month)│
│     · Scenario 1  Reported = PO   → direct credit                    │
│     · Scenario 2  Reported < PO   → direct credit + carry-over       │
│     · Scenario 3  Reported > PO   → distribute PO by % contribution  │
│     · Multi-MD share splits (AMD / SP1 / SP2 rules, per day ranges)  │
│     · Semester-end floating-PO redistribution by % share             │
│                                                                      │
│  4. Nudge scheduler — deadline countdowns, gap detection, push       │
│                                                                      │
│  5. Admin (Data Team)                                                │
│     · Monthly PO / Area Sales file upload + ingest validation        │
│     · Reconciliation dashboard (hospital / product / month)          │
│     · Exception queue (unmatched reports, disputes)                  │
└──────────────────────────────────────────────────────────────────────┘
```

## 6. V1 Feature Scope

### 6.1 Capture (replaces the IRIS form flow)
- Free-text chat report; **silent in-place masking** per §4b — no preview; coded chips appear in the sent bubble in the doctor's own word order. Send gate blocks only unrecognized doctor names.
- Patient via silent extraction (§4b nets) or the optional **＋ Match chip** (name + age → initials, converted locally).
- **Photo report:** in-app camera → on-device OCR → local preview with auto-masked entities → doctor taps the patient name to convert to initials → masked text sent to the AI, which summarizes it into the same confirmation card. Image discarded from memory immediately; never stored, never uploaded. Multi-photo supported for multi-page notes (each processed and discarded the same way).
- LLM extracts: hospital, month, patient (initials+age), product(s), quantity, source per quantity (H = hospital pharmacy / OH = outside), attending vs. referred role, referring/co-managing MD alias, "did you start the med" flag.
- Missing-field follow-ups are conversational, one at a time, in game vocabulary per §4c ("Were all 6 PRAZ units from inside the Location (I), or some from outside (O)?").
- **Confirm Game Play table** before filing — same structure the field force already knows from IRIS: GAME MODE / LOCATION / ERA / USERNAME / PLAYER TIME (deck-style compact notation, e.g. `PRAZ6 (I4-O2)`) / MAIN PLAYER / CO-PLAYER — plus a **UNITS THIS PLAY** sub-table (AVATAR | I | O | TOTAL with a totals row, mirroring the old H/OH/Total recording grid). Tapping **Confirm & Submit** posts "Confirmed ✓" as the doctor's reply in the thread and logs the play; the assistant acknowledges and streak/progress update. This leaves an explicit, timestamped consent trail in the conversation itself.
- Edits allowed until the report is locked by the monthly cycle.

### 6.2 Query
- "How many Kenzolin did I report this month?"
- "Show my June summary" → product-count and patient views (replaces IRIS summary screens)
- "Did my report for XY go through?" / "What's still unvalidated?"
- Answers generated from the doctor's own records only; strict per-user scoping.

### 6.3 Proactive nudges
**All notification copy uses game vocabulary — notifications appear on lock screens** where anyone can read them. "Log your plays for Era 2607 by Aug 10 🔥", never anything that mentions reports, products, or hospitals.
- T-5, T-2, T-0 reminders before the 10th (only if the doctor has activity gaps).
- Ongoing-treatment follow-ups in game terms ("Your PRAZ play for XY-25 — still active this Era?").
- Post-validation notifications ("Your Era 2606 play is credited ✓").
- Streak protection alert ("Log by tomorrow to keep your 4-Era streak 🔥").

### 6.4 Gamification & vocabulary
- On-time streak counter (per Era).
- Progress bars per group, labeled **Blaze / Breeze / Terra** (the group names double as confidentiality labels — see §4a).
- Display-side game vocabulary throughout per §4a; input side stays plain natural language with real names.

### 6.5 Admin (Data Team)
- Monthly PO + Area Sales upload (XLSX/CSV). Ingest validation: schema check, duplicate month detection, unknown hospital/product rejection, delta report vs. prior month.
- Reconciliation dashboard by hospital × product × month with scenario tags (1/2/3).
- Exception queue + manual override with audit log (all crediting overrides logged with who/when/why).

## 7. Crediting Engine — Rules of Record

Carried over verbatim from current IRIS mechanics (source: IRIS Guide 07-26):

1. Only the member who **started** the medicine records usage. Products: IV, Inhalers, Nutra (EnPlus Gold).
2. Record **actual** usage per patient after treatment (no estimating).
3. Deadline: **on or before the 10th** of the following month.
4. Only **accredited members** share credit.
5. Validation: Total Reported (all MDs in hospital) vs. Total Hospital PO, by hospital × product × month, cross-checked with Area Sales.

**Scenarios:** (1) Reported = PO → direct PRD credit. (2) Reported < PO → direct credit, remainder carries to next month. (3) Reported > PO → PO distributed by % contribution.
**Semester-end:** floating/remaining POs redistributed by each member's % share for that semester.
**Multi-MD share table** (AMD/SP1/SP2, day-range splits) implemented exactly as the current guide's share matrix; encoded as unit-tested rules, not prompts.

**Hard rule: no LLM involvement in any crediting computation.** The engine is pure functions with a golden-test suite built from the Data Team's historical worked examples. Go-live gate: one full monthly cycle parallel-run with ≤ 0.5% variance vs. manual computation, all variances explained.

## 8. Prototype Plan (Claude Code, one function per prompt)

> **Status:** an interactive HTML prototype (`iris_chat_prototype.html`) implements the full capture + query loop and has been stress-tested through six real bug-fix cycles. It is the behavioral reference for the Expo build: silent in-place masking with fuzzy matching, two-net patient extraction (§4b), send gate, LLM parsing on masked text only (the model's prompt contains codes, never real names), never-assume/inference clarify rules (§4c), schema-validated confirms with auto-repair (§4d), Confirm Game Play table with units summary and "Confirmed ✓" reply, query answers, and Blaze/Breeze/Terra progress.
>
> **Engineering lessons already paid for:** (a) the chat scroll container must pin children with `flex-shrink:0` or long conversations crush the confirmation card; (b) every UI state must be tested in a *long* conversation, not just a fresh one; (c) prompt rules regress silently — the never-assume and inference behaviors (§4c) must become a scripted-dialogue eval suite (including the "just me" regression case) that gates every prompt or model change.

**Phase 0 — Seed data**
- Build the three lookup tables from the IRIS guide (products/codes below), hospital registry, MD alias registry (dummy data for prototype).

**Phase 1 — The magic moment (masking composer)**
- RN/Expo chat screen with live on-device masking + patient chip. This alone is the demo that sells the concept to LTG and the sales team. No backend needed yet.

**Phase 2 — Parse & confirm**
- Wire composer to Claude API (masked text → structured JSON), confirmation card, local report store.

**Phase 2.5 — Photo capture**
- In-app camera + on-device OCR (Expo: `react-native-vision-camera` + ML Kit / Vision bindings) → extracted text into the Phase 1 masking pipeline + tap-to-tag patient preview. Verify with real (dummy) prescription samples and typical PH doctor handwriting.

**Phase 3 — Query**
- Conversational retrieval over the local store.

**Phase 4 — Crediting engine**
- Pure TypeScript functions + golden tests from Data Team examples. Runs against mock PO uploads.

**Phase 5 — Nudges + streaks**
- Local notifications first; server scheduler at IT handoff.

**Phase 6 — Admin upload + reconciliation view**
- Web dashboard (can reuse your existing HTML dashboard patterns).

**IT handoff package:** repo + this brief + golden-test suite + data-flow/DPA note.

### Seed product table (from IRIS Guide)

| Brand | Generic | Code |
|---|---|---|
| Prosec | Omeprazole | OTEM |
| Viatrex | Ceftriaxone | VISTU |
| Kenzamide | Furosemide | FRUK |
| Kenzipine | Nicardipine | DRU |
| Kenzolin | Piperacillin + Tazobactam | PRAZ |
| Citifar | Citicoline | TAK |
| Carmepem | Meropenem | MORI |
| Saptaz | Cefepime + Tazobactam | CAZ |
| Epiao | Epoetin Alfa | BLUK |
| I-Breath Plus | Salbutamol + Ipratropium | BREE |
| Rezpira | Salmeterol + Fluticasone | ROU |
| Topium | Tiotropium | TUK |
| EnPlus Gold | Adult nutritional drink | DOO |

## 9. Open Items (decide during prototyping)

1. **Auth:** keep KASS-issued credentials + SMS OTP, or move to email magic link + device binding? (OTP via SMS has cost + delivery issues in PH.)
2. **Multi-MD conversational flow:** exact phrasing for shift/referral chains ("I took over from Dr. X on day 3") → maps to the share matrix. Needs 3–5 worked dialogue examples with the Data Team.
3. **Offline capture:** doctors in hospitals with poor signal — queue reports locally, sync when online? (Recommended yes; masking is on-device anyway.)
4. **Alias hint UX:** the IRIS "hint for code" bulb becomes unnecessary for input, but doctors may want a "who is THOR?" reverse lookup — decide if allowed (confidentiality question).
5. **Scale numbers:** # of accredited MDs, hospitals, and monthly report volume — needed before IT sizes infra.
6. **Legal review:** confirm DPA posture of on-device masking + alias tables with legal/compliance before pilot.
7. **Name:** keep IRIS branding ("IRIS 2.0") for continuity, or rebrand?
8. **Handwriting OCR accuracy:** on-device OCR is excellent on printed records but variable on handwriting (and doctor handwriting is famously hostile). Pilot-test with real handwriting samples from 5–10 friendly MDs; if accuracy is poor, the feature still ships for printed records/prescriptions with typing as the handwriting fallback — the no-server-image rule does not bend for accuracy.
9. **Banned-word filter list:** finalize the definitive banned-word list for the production output filter (§4a) with the sales/compliance team — including Tagalog equivalents (e.g. *gamot*, *ospital*, *pasyente*), since the AI may reply in Taglish.
10. **Residual extraction gap:** a lone patient first name with no preposition and no age ("juan had 2 prosec") can evade the deterministic nets. Expo build adds a lightweight on-device name detector as net 3 (§4b-5); pilot with friendly MDs to measure the real-world miss rate.
11. **Scripted-dialogue eval suite:** build the regression dialogues (never-assume, "just me" inference, incomplete-confirm repair, Taglish age formats) as an automated gate before any prompt/model change ships.
12. **API key architecture:** the prototype's in-Claude.ai API bridge does not work outside Claude.ai. Production: Anthropic API key lives on the backend only (never in the app); the app sends masked text to the backend, which calls the model and runs the banned-word output filter and per-doctor data scoping.

---
*Prepared July 2026 · Basis document: IRIS_Guide_0726.pdf*
