PawDoc
AI pet-health triage where the safety rail runs before the model does.
The short version
The emergency keyword list is written three times — in Dart, Python and TypeScript — and a test fails the build if the three ever disagree.

Google Play production release build 1.0.0+8, approved 15 August 2026. The public store listing had not resolved when this page was written, so it is not linked.
How PawDoc works
An owner describes a symptom, or photographs it, and PawDoc returns a triage decision: get help now, call today, or watch and re-check. A false negative is the worst thing this product can do, so the architecture treats the language model as the least trustworthy component in the system and constrains it accordingly.
The problem
Owners cannot tell an emergency from a nuisance, and the internet answers both the same way. Telling someone their animal is fine when it is not is unrecoverable — so the design question is not "how good is the model" but "what happens when the model is wrong".
The architecture
A Flutter client, a Python FastAPI analysis service, Supabase Postgres with row-level security on every user table, roughly thirteen Deno edge functions, and Cloudflare R2 for image and video objects. Analysis tiers from Gemini up to Claude depending on what the request needs.
The interesting decision
A hardcoded emergency-keyword override executes before any model call, and is mirrored client-side so it still works on an offline cold start. The keyword lists live in three languages — safety.py, emergency_keywords.mjs, emergency_keywords.dart — and a parity test fails the build if they drift apart.
What it does now
Structured JSON output only, temperature 0.1, a confidence floor below which the answer becomes "not enough information", and an action ladder with no "do nothing" rung. The model never names a condition and never says "normal".
Worth knowing
- The emergency override runs before the model, not after it — and is mirrored on-device so it survives an offline cold start.
- The AnalysisResult contract is frozen across Dart, Python and TypeScript; all three change together or not at all.
- Row-level security on every user table with both USING and WITH CHECK, verified by a scripted test against a real database.
- Disclaimers are injected server-side. The client only gates on the flag, so it cannot render a result without one.
- A safety-critical defect found during device QA — Emergency unreachable on an offline cold start — was fixed and regression-tested rather than noted.