File size: 18,784 Bytes
612e777 5a79992 612e777 5a79992 612e777 5a79992 612e777 5a79992 | 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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | <article class="doc" id="doc-reference-platform" aria-hidden="true"><div class="strip"><span class="path">reference/platform.md</span><span class="tag">Engineering, reference</span><span class="meta">~7 min read</span></div>
<h1>Platform: Architecture, Stack, Cloud & Actors</h1>
<h1>4. System Architecture</h1>
<p><img alt="Figure 1, MVP system architecture. One deployable API, one async plane, one canonical store." loading="lazy" src="assets/img/platform-architecture.png"/>
<em>Figure 1, MVP system architecture. One deployable API, one async plane, one canonical store.</em></p>
<h2 id="doc-reference-platform--h1">4.1 Module Inventory (Monolith Internal Boundaries)</h2>
<p>Each module is a Python package under <code>app/modules/</code> with a public interface (<code>api.py</code> router + <code>service.py</code> facade) and private internals. Cross-module calls go through service facades only; <code>import-linter</code> contracts in CI fail the build on any deeper import. Modules share the database but own their tables (write access enforced by convention + code review; the <code>audit</code> schema is enforced by a separate INSERT-only DB role).</p>
<div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-lg"/><col class="col-auto"/></colgroup>
<thead>
<tr>
<th>Module</th>
<th>Responsibility</th>
<th>Key Requirements Owned</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Module"><code>identity</code></td>
<td data-label="Responsibility">Activation tokens, patient/caregiver/clinician sessions, grants, step-up, recovery, revocation lists.</td>
<td data-label="Key Requirements Owned">ENR-003/004/005, CLN-001, CGV-001/004</td>
</tr>
<tr>
<td data-label="Module"><code>consent</code></td>
<td data-label="Responsibility">Consent records, versions, scopes (program, caregiver, recording-optional), withdrawal cascade.</td>
<td data-label="Key Requirements Owned">SEC-010, CGV-007</td>
</tr>
<tr>
<td data-label="Module"><code>ingestion</code></td>
<td data-label="Responsibility">EHR adapter, ADT listener, FHIR client, document fingerprinting, source versioning.</td>
<td data-label="Key Requirements Owned">INT-001, DPC-001</td>
</tr>
<tr>
<td data-label="Module"><code>compiler</code></td>
<td data-label="Responsibility">Discharge plan compiler + seven-gate LLM pipeline; extracted-fact publication.</td>
<td data-label="Key Requirements Owned">DPC-003/004/007/008, DQS-004/011</td>
</tr>
<tr>
<td data-label="Module"><code>episode</code></td>
<td data-label="Responsibility">Episode & Universal Task state machines, transitions, idempotency, prioritization engine.</td>
<td data-label="Key Requirements Owned">PAT-001/003/005, ENR-001/002</td>
</tr>
<tr>
<td data-label="Module"><code>medication</code></td>
<td data-label="Responsibility">Medication plan items, status events (8 states), barriers, verification labeling.</td>
<td data-label="Key Requirements Owned">MED-003/005/006/010</td>
</tr>
<tr>
<td data-label="Module"><code>scheduling</code></td>
<td data-label="Responsibility">Appointment requirements, appointments, matching, assisted-scheduling flows, result completion.</td>
<td data-label="Key Requirements Owned">APT-001/004/010</td>
</tr>
<tr>
<td data-label="Module"><code>notification</code></td>
<td data-label="Responsibility">Channel selection, escalation ladders, quiet hours, suppression outbox, templates, silent-deterioration detector.</td>
<td data-label="Key Requirements Owned">ENG-002/004/012, EDU-002/007</td>
</tr>
<tr>
<td data-label="Module"><code>brief</code></td>
<td data-label="Responsibility">Brief snapshots, precompute triggers, staleness checks, post-visit update loop.</td>
<td data-label="Key Requirements Owned">VIS-002/005/007, CLN-004/008</td>
</tr>
<tr>
<td data-label="Module"><code>agents</code></td>
<td data-label="Responsibility">Agent orchestrator, policy envelopes, tool registry, evidence records, kill switches.</td>
<td data-label="Key Requirements Owned">AUT-001/006/009</td>
</tr>
<tr>
<td data-label="Module"><code>admin</code></td>
<td data-label="Responsibility">Tenant/cohort/config management, versioned publication, RBAC administration.</td>
<td data-label="Key Requirements Owned">ADM-004 config side</td>
</tr>
<tr>
<td data-label="Module"><code>analytics</code></td>
<td data-label="Responsibility">Event projections, denominators, execution & outcome reports, coverage flags.</td>
<td data-label="Key Requirements Owned">ADM-004/007</td>
</tr>
<tr>
<td data-label="Module"><code>audit</code></td>
<td data-label="Responsibility">Append-only audit writer + query API; S3 WORM archiver.</td>
<td data-label="Key Requirements Owned">SEC-003, DQS-001</td>
</tr>
</tbody>
</table></div>
<h2 id="doc-reference-platform--h2">4.2 Runtime Topology</h2>
<div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-auto"/><col class="col-md"/></colgroup>
<thead>
<tr>
<th>Process</th>
<th>Scale (pilot)</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Process"><code>api</code></td>
<td data-label="Scale (pilot)">2–4 tasks, 1 vCPU/2 GB</td>
<td data-label="Notes">FastAPI + Uvicorn behind ALB; stateless; readiness = DB+Redis ping.</td>
</tr>
<tr>
<td data-label="Process"><code>worker-default</code></td>
<td data-label="Scale (pilot)">2 tasks</td>
<td data-label="Notes">Celery queues: <code>episode</code>, <code>ingest</code>, <code>analytics</code>. Prefetch 1, acks-late, visibility 300 s.</td>
</tr>
<tr>
<td data-label="Process"><code>worker-llm</code></td>
<td data-label="Scale (pilot)">1–2 tasks</td>
<td data-label="Notes">Queue <code>llm</code>; concurrency 4; per-task Bedrock budget caps (§11.7); isolates latency.</td>
</tr>
<tr>
<td data-label="Process"><code>worker-notify</code></td>
<td data-label="Scale (pilot)">2 tasks</td>
<td data-label="Notes">Queues <code>notify</code>, <code>outbox</code>; drains suppression outbox every 5 s to meet the 60 s SLA.</td>
</tr>
<tr>
<td data-label="Process"><code>beat</code></td>
<td data-label="Scale (pilot)">1 task (singleton)</td>
<td data-label="Notes">Celery beat: schedules in §16.5. Leader lock in Redis.</td>
</tr>
<tr>
<td data-label="Process"><code>migrator</code></td>
<td data-label="Scale (pilot)">on deploy</td>
<td data-label="Notes">Alembic <code>upgrade head</code> as ECS one-off task; deploy gate.</td>
</tr>
</tbody>
</table></div>
<h2 id="doc-reference-platform--h3">4.3 Technology Stack (pinned)</h2>
<div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-md"/><col class="col-md"/></colgroup>
<thead>
<tr>
<th>Layer</th>
<th>Choice (version)</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Layer">Language / API</td>
<td data-label="Choice (version)">Python 3.12 · FastAPI 0.115 · Uvicorn 0.30</td>
<td data-label="Notes">OpenAPI generated; <code>/v1</code> prefix; Pydantic v2 models shared with workers.</td>
</tr>
<tr>
<td data-label="Layer">ORM / migrations</td>
<td data-label="Choice (version)">SQLAlchemy 2.0.32 · Alembic 1.13</td>
<td data-label="Notes">One migration lineage; no auto-generated DDL in prod without review.</td>
</tr>
<tr>
<td data-label="Layer">Async jobs</td>
<td data-label="Choice (version)">Celery 5.4 · Redis 7.2</td>
<td data-label="Notes">Priorities via dedicated queues, not priority flag.</td>
</tr>
<tr>
<td data-label="Layer">Frontend</td>
<td data-label="Choice (version)">React 18.3 · TypeScript 5.5 · Vite 5 · Workbox 7</td>
<td data-label="Notes">PWA manifest; bundle budget 250 KB gz initial for patient routes.</td>
</tr>
<tr>
<td data-label="Layer">UI kit</td>
<td data-label="Choice (version)">Radix UI + Tailwind 3.4</td>
<td data-label="Notes">44 px min touch targets; WCAG 2.2 AA tokens; EN/ES i18n via i18next.</td>
</tr>
<tr>
<td data-label="Layer">LLM</td>
<td data-label="Choice (version)">Bedrock: <code>anthropic.claude-sonnet</code> pinned model ID; Titan embed v2</td>
<td data-label="Notes">IDs live in model registry table (§11.6), never in code.</td>
</tr>
<tr>
<td data-label="Layer">Telephony</td>
<td data-label="Choice (version)">Twilio SMS + Voice (Studio flows)</td>
<td data-label="Notes">BAA signed; status callbacks → <code>/webhooks/twilio</code>.</td>
</tr>
<tr>
<td data-label="Layer">Email</td>
<td data-label="Choice (version)">AWS SES</td>
<td data-label="Notes">Transactional only; DKIM/DMARC enforced.</td>
</tr>
<tr>
<td data-label="Layer">Observability</td>
<td data-label="Choice (version)">OpenTelemetry SDK → ADOT collector → CloudWatch; Sentry (PHI-scrubbed)</td>
<td data-label="Notes">Trace ID propagated into Celery headers and audit rows.</td>
</tr>
<tr>
<td data-label="Layer">Testing</td>
<td data-label="Choice (version)">pytest 8 · Playwright 1.45 · schemathesis · Locust</td>
<td data-label="Notes">Layer mapping in §17.1.</td>
</tr>
</tbody>
</table></div>
<h2 id="doc-reference-platform--h4">4.4 Repository & Conventions</h2>
<pre><code>travi ai agent/ # single monorepo
app/ # FastAPI monolith
modules/{identity,consent,ingestion,compiler,episode,medication,
scheduling,notification,brief,agents,admin,analytics,audit}/
api.py service.py models.py schemas.py tasks.py tests/
core/ # settings, db, security, llm_gateway, ehr_adapter, outbox
main.py
web/ # React PWA (patient/caregiver/clinician routes)
infra/ # Terraform: modules/ + envs/{dev,stage,prod}
ops/ # runbooks, dashboards-as-code, oncall
docs/adr/ # ADR-001..018 as markdown, this doc's source
.github/workflows/ # ci.yml, deploy.yml, nightly-golden.yml
</code></pre>
<ul>
<li><strong>Branching:</strong> trunk-based; short-lived feature branches; squash merge; Conventional Commits; release tags <code>v0.x.y</code>.</li>
<li><strong>Quality gates (CI-blocking):</strong> ruff + mypy --strict, eslint + tsc, import-linter contracts, pytest ≥85% line coverage on <code>modules/</code>, schemathesis against OpenAPI, Playwright smoke, Trivy image scan (no HIGH/CRITICAL), tfsec.</li>
<li><strong>Code review:</strong> 1 approval; 2 for <code>identity</code>, <code>compiler</code> gates, <code>audit</code>, Terraform, or any migration touching PHI tables.</li>
<li><strong>PHI hygiene:</strong> no PHI in logs, traces, Sentry, commit messages, or fixtures, synthetic data only outside prod (§15.6).</li>
</ul>
<h1>5. Environments, Cloud & Delivery Infrastructure</h1>
<h2 id="doc-reference-platform--h5">5.1 Accounts & Environments</h2>
<div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-auto"/><col class="col-lg"/></colgroup>
<thead>
<tr>
<th>Env</th>
<th>AWS Account</th>
<th>Purpose / Data Policy</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Env">dev</td>
<td data-label="AWS Account"><code>tai-dev</code></td>
<td data-label="Purpose / Data Policy">Ephemeral stacks per PR optional; synthetic data only; relaxed sizes; nightly teardown of previews.</td>
</tr>
<tr>
<td data-label="Env">stage</td>
<td data-label="AWS Account"><code>tai-stage</code></td>
<td data-label="Purpose / Data Policy">Production-shaped; Epic <strong>sandbox</strong> connectivity; synthetic + partner test patients only; load & DR drills here.</td>
</tr>
<tr>
<td data-label="Env">prod</td>
<td data-label="AWS Account"><code>tai-prod</code></td>
<td data-label="Purpose / Data Policy">Live PHI. Change only via pipeline; console read-only except break-glass role (audited).</td>
</tr>
</tbody>
</table></div>
<h2 id="doc-reference-platform--h6">5.2 Network & Core Services</h2>
<ul>
<li><strong>VPC</strong> per env: 3 AZs; public subnets = ALB + NAT only; private subnets = ECS, RDS, ElastiCache; no public IPs on tasks; VPC endpoints for S3, ECR, Secrets Manager, Bedrock, CloudWatch.</li>
<li><strong>Hospital connectivity:</strong> site-to-site IPsec VPN (or partner-preferred private link) terminating in a dedicated <code>integration</code> subnet; ADT listener reachable only from partner interface-engine CIDR; all inbound mTLS where the engine supports it.</li>
<li><strong>Edge:</strong> CloudFront serves the static PWA bundle (no PHI cached; <code>Cache-Control: no-store</code> on all API responses); AWS WAF managed rules + rate rules (<code>/v1/patient/activate</code>: 10/min/IP) in front of ALB.</li>
<li><strong>Data:</strong> RDS PostgreSQL 16 Multi-AZ (db.m7g.large pilot), PITR 15-min granularity, 35-day backups; ElastiCache Redis 7 (cluster mode off, multi-AZ); S3 buckets <code>tai-{env}-sources</code>, <code>-briefs</code>, <code>-exports</code>, <code>-audit-archive</code> (Object Lock compliance 6 y on audit), all SSE-KMS with per-purpose CMKs, versioning + access logs on.</li>
<li><strong>Secrets:</strong> AWS Secrets Manager; 90-day rotation for DB/Twilio/Epic client credentials; no secrets in env files or Terraform state (state in S3 + DynamoDB lock, KMS-encrypted).</li>
<li><strong>IAM:</strong> task roles per process (api / workers / migrator); Bedrock invoke restricted to registry model IDs; deny <code>s3:DeleteObject</code> on audit archive to all runtime roles.</li>
</ul>
<h2 id="doc-reference-platform--h7">5.3 CI/CD</h2>
<ol>
<li>PR → CI gates (§4.4) on GitHub Actions; preview deploy to dev on label.</li>
<li>Merge to <code>main</code> → build multi-arch image → push ECR → auto-deploy <strong>stage</strong> (migrator → blue/green ECS via CodeDeploy, 10% canary 10 min, auto-rollback on alarm).</li>
<li>Manual approval (CTO or delegate) → <strong>prod</strong> deploy, same shape; release notes auto-generated from Conventional Commits.</li>
<li>Nightly: golden-scenario suite + extraction benchmark against pinned models; regression fails page the on-call and block the next prod deploy.</li>
</ol>
<h2 id="doc-reference-platform--h8">5.4 Resilience, DR & Retention</h2>
<div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-lg"/></colgroup>
<thead>
<tr>
<th>Item</th>
<th>Commitment / Mechanism</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Item">Availability target</td>
<td data-label="Commitment / Mechanism">≥99.9% monthly, Multi-AZ RDS/Redis, ≥2 tasks/service across AZs, ALB health checks, graceful drains.</td>
</tr>
<tr>
<td data-label="Item">RPO / RTO</td>
<td data-label="Commitment / Mechanism">RPO ≤15 min (PITR + Redis is rebuildable); RTO ≤4 h via Terraform re-provision runbook; quarterly restore test in stage is a release-blocking control.</td>
</tr>
<tr>
<td data-label="Item">Degradation modes</td>
<td data-label="Commitment / Mechanism">Bedrock down → pipeline pauses at Gate 2, tasks stay <code>pending_extraction</code>, ops alert (no unvalidated publishing). Twilio down → channel ladder skips to next channel; safety-tier failures page. Epic down → ingestion retries with backoff; episodes continue on last-known plan.</td>
</tr>
<tr>
<td data-label="Item">Data retention</td>
<td data-label="Commitment / Mechanism">PHI: contract term + 6 y (audit) unless partner policy stricter; Twilio/SES message bodies not stored at vendor beyond delivery (config); LLM: Bedrock zero-retention, no training use (SEC-010); voice recordings: not stored in MVP.</td>
</tr>
<tr>
<td data-label="Item">Environments parity</td>
<td data-label="Commitment / Mechanism">Stage = prod Terraform with size overrides only; drift detection nightly (<code>terraform plan</code> must be empty).</td>
</tr>
</tbody>
</table></div>
<h1>2. Scope & Requirements Baseline</h1>
<p>The functional baseline is the Product Requirements Document. All requirement IDs referenced in this document (e.g., <code>MED-006</code>, <code>ENG-012</code>) are normative and their acceptance criteria are the test oracle. Section 20 provides the full requirement-to-component traceability matrix; no MVP requirement is unowned. Where the PRD says “configurable,” this specification names the configuration key, its default value, and its owner.</p>
<h2 id="doc-reference-platform--h9">2.1 Actors and Trust Levels</h2>
<div class="tbl-wrap"><table class="col-table compact"><colgroup><col class="col-auto"/><col class="col-md"/><col class="col-lg"/></colgroup>
<thead>
<tr>
<th>Actor</th>
<th>Authentication</th>
<th>Trust / Authorization Scope</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Actor">Patient</td>
<td data-label="Authentication">Activation token → session JWT (Section 9.1)</td>
<td data-label="Trust / Authorization Scope">Own episode only. Can report status, respond to tasks, manage consent & caregivers.</td>
</tr>
<tr>
<td data-label="Actor">Caregiver</td>
<td data-label="Authentication">Own invitation token → own session JWT</td>
<td data-label="Trust / Authorization Scope">Patient-granted scope on one episode; read + report; never sees other episodes; revocable instantly.</td>
</tr>
<tr>
<td data-label="Actor">Clinician</td>
<td data-label="Authentication">Magic link + email OTP; step-up OTP for confirmations</td>
<td data-label="Trust / Authorization Scope">One patient, one episode, time-boxed grant. Read brief; confirm/clarify material items with step-up.</td>
</tr>
<tr>
<td data-label="Actor">Hospital admin</td>
<td data-label="Authentication">Design-partner IdP via OIDC SSO + MFA</td>
<td data-label="Trust / Authorization Scope">Tenant-scoped configuration, cohorts, reports, kill switches per RBAC matrix (Section 9.5).</td>
</tr>
<tr>
<td data-label="Actor">Travi AI Agent operator</td>
<td data-label="Authentication">Internal IdP (Google Workspace OIDC) + MFA</td>
<td data-label="Trust / Authorization Scope">Break-glass PHI access is separately authorized, reasoned, time-boxed, and audited.</td>
</tr>
<tr>
<td data-label="Actor">System agents</td>
<td data-label="Authentication">Service identity (IAM role) + policy envelope</td>
<td data-label="Trust / Authorization Scope">Least-privilege tool allowlists; can never transition clinical state directly (Section 12).</td>
</tr>
</tbody>
</table></div>
<h2 id="doc-reference-platform--h10">2.2 Configuration Ownership Model</h2>
<p>Every tunable named in this document lives in the <code>tenant_config</code> service (Section 16.4) with three tiers: <strong>platform defaults</strong> (owned by Travi AI Agent engineering, changeable by release), <strong>tenant policy</strong> (owned by the design partner, changeable via admin console with versioning per ADM requirements), and <strong>cohort overrides</strong> (owned by the clinical lead). Configuration changes never retroactively alter active episodes; a new <code>config_version</code> applies only to episodes created after publication, satisfying the enterprise immutability constraint.</p>
<div class="pn"><a class="pn-prev" href="#"></a><a class="pn-next" href="#"></a></div></article>
|