swbanga.com
adr-002Acceptedby Super Washington Banga

FastAPI for Med-Core Async Healthcare PHI Processing

  • med-core
  • fastapi
  • python
  • phi

Context

Med-Core's intake pipeline encrypts PHI at the field level, enforces OAuth2/JWT scopes in the request pipeline, and rate-limits through Redis. The service must stay responsive under bursty synthetic intake load while keeping plaintext PHI out of the data plane.

Decision

Build Med-Core on FastAPI with async request handlers. Pydantic models validate payloads before encryption, dependency injection enforces scope authorization in the pipeline, and the async runtime keeps PHI payloads out of blocking I/O paths.

Consequences — Positive

  • Async-first design matches Redis and Postgres I/O without thread pools
  • Pydantic v2 model validation mirrors the content-layer Zod discipline
  • Single-file dependency injection makes the security boundary auditable

Consequences — Negative

  • Python runtime startup is slower than a compiled alternative
  • Async debugging and profiling require different tooling than sync Python
  • Type safety is best-effort without mypy enforcement in CI

The security boundary

The pipeline is ordered so that unauthenticated or unauthorized traffic never reaches the encryption layer: authentication, then scope authorization, then rate limiting, then schema validation, then field-level PHI encryption. Each step is a dependency in the handler chain, so the boundary is reviewable in a single file:

python
@app.post("/intake", dependencies=[Depends(require_scope("intake:write"))])
async def ingest(
    payload: IntakePayload,
    limiter: RateLimiter = Depends(get_limiter),
) -> Receipt:
    await limiter.acquire("intake", key=payload.patient_id)
    encrypted = encrypt_phi(payload.model_dump())
    return await store.record(encrypted)

Async under load

Synthetic intake bursts previously serialized on thread-pool scheduling. Async handlers keep the event loop free during Redis round trips and Postgres commits, and the rate limiter throttles per patient instead of per connection, which is what the burst tests actually exercise.

Related Case Studies