hub / content /engineering /bootstrap.html
amirmh's picture
Audit pass: merge duplicate docs (57 total), fix ADR index links, header overflow, mobile tables, members button in header, WebP illustrations
411218e verified
Raw
History Blame Contribute Delete
10.1 kB
<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 &amp;&amp; make seed &amp;&amp; make test</code> works and CI is green.</em></p>
<h2 id="doc-engineering-bootstrap--h1">Accounts &amp; 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 &sect;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 &sect;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&minus;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 &sect;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 &amp; 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>