| <article class="doc" id="doc-engineering-bootstrap" aria-hidden="true"><div class="strip"><span class="path">engineering/BOOTSTRAP.md</span><span class="tag">Engineering β MVP track</span><span class="meta">~8 min read</span></div> |
| <h1>Day-One Bootstrap</h1> |
| <p><em>From empty laptop to a running, tested skeleton. Everything here uses synthetic data only β no BAA is needed until real PHI flows, which is months away. Target for the very first working session: <code>make up && make seed && make test</code> works and CI is green.</em></p> |
|
|
| <h2 id="doc-engineering-bootstrap--h1">Accounts & tools checklist</h2> |
| <div class="tbl-wrap"><table class="col-table compact"> |
| <thead><tr><th>Item</th><th>Notes</th></tr></thead> |
| <tbody> |
| <tr><td data-label="Item">GitHub org + private repo <code>travi-platform</code></td><td data-label="Notes">Branch protection on <code>main</code>; PRs only; CI required. Repo name β the public hub.</td></tr> |
| <tr><td data-label="Item">AWS account (dev)</td><td data-label="Notes">Start with ONE dev account; add stage/prod accounts when Terraform is stable (the three-account layout is ADR-005's end state, not day one's).</td></tr> |
| <tr><td data-label="Item">AWS Bedrock model access</td><td data-label="Notes">Request Claude model access in the dev region on day 1 β approval can take days. Pin the model ID in config; temperature 0.</td></tr> |
| <tr><td data-label="Item">Twilio trial + Verify</td><td data-label="Notes">Trial account for dev (verified numbers only). SES sandbox for email. BAAs come later with production.</td></tr> |
| <tr><td data-label="Item">Local: Docker, Python 3.12, uv, Node 20, Terraform</td><td data-label="Notes"><code>uv</code> for env + lockfile; Node for the PWA workspace; direnv is nice-to-have.</td></tr> |
| <tr><td data-label="Item">Sentry (free tier)</td><td data-label="Notes">Wire the PHI-scrub config from the start even on synthetic data β it must be boring by pilot.</td></tr> |
| </tbody> |
| </table></div> |
|
|
| <h2 id="doc-engineering-bootstrap--h2">Repo layout (matches platform §4.1)</h2> |
| <pre><code>travi-platform/ |
| βββ app/ |
| β βββ main.py # FastAPI factory; mounts every module router |
| β βββ core/ # settings, db, ehr_adapter interface, llm_gateway, |
| β β # outbox drainer, config client (10 s cache) |
| β βββ modules/ |
| β βββ identity/ # api.py (router) + service.py (facade) + internal/ |
| β βββ consent/ ingestion/ compiler/ episode/ medication/ |
| β βββ scheduling/ notification/ brief/ agents/ admin/ |
| β βββ analytics/ |
| β βββ audit/ # own schema; INSERT-only role; emit() facade |
| βββ pwa/ # React PWA (patient/caregiver/clinician surfaces) |
| βββ fakeehr/ # the fixture EHR service (see below) |
| βββ infra/ # Terraform (dev first) |
| βββ tests/ # unit / contract / property / integration / e2e |
| β βββ golden/ # GS-01β¦GS-12 scenario harnesses + fixtures |
| βββ corpus/ # synthetic discharge docs + labels (never PHI) |
| βββ importlinter.toml # module-boundary contracts β CI fails deep imports |
| βββ docker-compose.yml Makefile pyproject.toml |
| </code></pre> |
| <p>Two structural rules CI enforces from day 1: cross-module calls go through <code>service.py</code> facades only (import-linter contract per module), and nothing outside <code>app/modules/episode</code> imports the task-state writers β the single-writer rule as a build failure, not a convention.</p> |
|
|
| <h2 id="doc-engineering-bootstrap--h3">docker-compose (the whole dev world)</h2> |
| <pre><code>services: |
| db: postgres:16 # + a second logical db for the audit schema role split |
| redis: redis:7 # celery broker + config/kill-switch cache |
| s3: localstack # documents + brief PDFs + (later) audit archive |
| mail: mailpit # catches all SES email locally (web UI :8025) |
| fakeehr: ./fakeehr # the hospital, in a box (below) |
| api: ./app (reload) # FastAPI on :8000 |
| worker: ./app celery # default + notifications queues |
| pwa: ./pwa (vite dev) # :5173, proxied to api |
| </code></pre> |
| <p>SMS in dev: a console/mailpit-backed fake Twilio client behind the same interface as the real one β activation links print to the compose log and land in mailpit; flipping to real Twilio is config, not code.</p> |
|
|
| <h2 id="doc-engineering-bootstrap--h4">FakeEHR β the hospital in a box</h2> |
| <p>A tiny FastAPI service implementing the same <code>ehr_adapter</code> surface the Epic adapter will: FHIR-ish <code>Patient</code>, <code>Encounter</code>, <code>MedicationRequest</code>, <code>DocumentReference</code> endpoints serving the synthetic corpus PDFs, plus two control endpoints that make demos and tests deterministic: <code>POST /control/discharge?persona=GS-01</code> (emits the ADT A03 + documents for a persona) and <code>POST /control/admit</code> (mid-episode readmission for GS-08). Build it before any real module β every phase of the <a href="#doc-engineering-build-plan">Build Plan</a> runs against it, and the delivery plan's biggest schedule risk (Epic paperwork) stops blocking development entirely.</p> |
|
|
| <h2 id="doc-engineering-bootstrap--h5">Synthetic personas = the golden scenarios</h2> |
| <p>Seed data and test fixtures are the same twelve people, named for the scenario they exercise (testing §17.3): <strong>GS-01 "Dorothy"</strong> clean discharge (3 new meds, 1 stopped, 2 follow-ups) Β· <strong>GS-02 "Frank"</strong> amended summary supersedes Β· <strong>GS-03 "Rosa"</strong> AVS-vs-order dose conflict Β· <strong>GS-04 "Earl"</strong> cannot_do(cost) on a critical med Β· <strong>GS-05 "Mae"</strong> pharmacy phone confirmation Β· <strong>GS-06 "Walter"</strong> stale brief Β· <strong>GS-07 "Gloria"</strong> caregiver invite/revoke Β· <strong>GS-08 "Hank"</strong> readmitted day 9 Β· <strong>GS-09 "Vera"</strong> silent 48 h Β· <strong>GS-10 "Sam"</strong> post-visit plan update Β· <strong>GS-11 "Nina"</strong> red-flag safety path Β· <strong>GS-12 "Otis"</strong> prompt-injection document (a discharge PDF that <em>tries</em> to jailbreak the extractor β Gate 5's span proof is the defense on trial). <code>make seed</code> loads all twelve; <code>make gs S=03</code> replays one end-to-end.</p> |
|
|
| <h2 id="doc-engineering-bootstrap--h6">Makefile targets</h2> |
| <pre><code>make up # compose up + migrate + healthcheck |
| make seed # load the 12 personas into FakeEHR + db |
| make gs S=04 # run one golden scenario end-to-end |
| make test # unit + property + contract (the PR gate) |
| make bench # extraction benchmark vs corpus/ labels (field-level P/R table) |
| make compress # day-compression: run a full 30-day episode at 1 day/minute |
| make fmt lint # ruff + import-linter + tsc for the PWA |
| </code></pre> |
| <p><code>make compress</code> matters more than it looks: episode timers (48 h silence, T−24 h brief, day-30 close) must read a <strong>clock service</strong> injected everywhere β never <code>datetime.now()</code> inline β so tests and demos can run a month in minutes. Retrofitting an injectable clock later is misery; do it on day one.</p> |
|
|
| <h2 id="doc-engineering-bootstrap--h7">CI pipeline (GitHub Actions, mirrors testing §17.1)</h2> |
| <div class="tbl-wrap"><table class="col-table compact"> |
| <thead><tr><th>Job</th><th>Gate</th></tr></thead> |
| <tbody> |
| <tr><td data-label="Job">lint: ruff + import-linter + tsc --noEmit</td><td data-label="Gate">PR</td></tr> |
| <tr><td data-label="Job">unit + property (Hypothesis state machines) β coverage β₯85% on modules/</td><td data-label="Gate">PR</td></tr> |
| <tr><td data-label="Job">contract: schemathesis vs generated OpenAPI + authz matrix probes</td><td data-label="Gate">PR</td></tr> |
| <tr><td data-label="Job">integration: compose Postgres/Redis/FakeEHR; outbox β€60 s suppression sim</td><td data-label="Gate">PR</td></tr> |
| <tr><td data-label="Job">golden: GS suite; extraction bench vs corpus labels</td><td data-label="Gate">nightly + release</td></tr> |
| <tr><td data-label="Job">e2e: Playwright activation/Today/med/brief + axe WCAG 2.2 AA scan</td><td data-label="Gate">release</td></tr> |
| </tbody> |
| </table></div> |
|
|
| <h2 id="doc-engineering-bootstrap--h8">Config & secrets hygiene</h2> |
| <p><code>.env</code> for local only, never committed; AWS Secrets Manager once deployed. Non-negotiables that live in config from day 1: <code>BEDROCK_MODEL_ID</code> (pinned, never "latest"), <code>LLM_TEMPERATURE=0</code>, <code>BRIEF_LEAD_HOURS=24</code>, quiet-hour defaults, kill-switch cache TTL 10 s, <code>TIME_SOURCE=real|compressed</code>. Rule for the repo: synthetic data may be committed (corpus/), credentials never, and there is no such thing as "temporary" real patient data in dev.</p> |
|
|
| <h2 id="doc-engineering-bootstrap--h9">The first five PRs</h2> |
| <p>A concrete on-ramp that leaves CI green after each merge: <strong>PR-1</strong> repo scaffold + compose + Makefile + CI skeleton (a failing-then-fixed import-linter test proves the boundary gate). <strong>PR-2</strong> core: settings, db/session, clock service, migration baseline. <strong>PR-3</strong> audit module complete + its property tests (everything after this emits). <strong>PR-4</strong> FakeEHR with GS-01 persona + the ehr_adapter interface + contract test. <strong>PR-5</strong> episode tables + the state-machine skeleton with the five-element transition contract and its first Hypothesis suite. From there, the <a href="#doc-engineering-build-plan">Build Plan</a> phases take over β and each module you open starts by pasting its module brief + the overview's hard rules into your coding agent's context, exactly as the overview prescribes.</p> |
| <div class="pn"><a class="pn-prev" href="#"></a><a class="pn-next" href="#"></a></div> |
| </article> |
|
|