File size: 10,081 Bytes
5f864ad
 
411218e
5f864ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411218e
5f864ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<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>