Skip to content

Repository files navigation

Medication Request Service

A FastAPI service that manages Medication Requests for clinical research staff, associating existing Patients, Clinicians, and Medications.

Tech Stack

  • Python 3.10+ / FastAPI / Pydantic v2
  • SQLAlchemy 2.0 with Alembic migrations
  • PostgreSQL (runtime), SQLite in-memory (test suite)
  • pytest, mypy (strict), Docker + docker-compose

Quick Start (Docker)

docker compose up --build

This starts PostgreSQL, applies migrations, seeds sample data, and serves the API at http://localhost:8000. Interactive OpenAPI docs: http://localhost:8000/docs

Local Development (without Docker)

Requires a running PostgreSQL instance (or use docker compose up db).

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
cp .env.example .env          # adjust DATABASE_URL if needed
alembic upgrade head          # apply migrations
python -m app.seed            # insert sample data
uvicorn app.main:app --reload

Running Tests

Tests run against an in-memory SQLite database — no PostgreSQL needed:

pip install -r requirements-dev.txt
pytest

Static type checking:

mypy app

API

Method Path Description
POST /patients/{patientId}/medication-requests Create a medication request
GET /patients/{patientId}/medication-requests List requests; filters: status, prescribed_from, prescribed_to
PATCH /medication-requests/{id} Update end_date, frequency, status only

Example requests

The seed data uses fixed UUIDs so these work out of the box:

curl -X POST http://localhost:8000/patients/11111111-1111-1111-1111-111111111111/medication-requests \
  -H "Content-Type: application/json" \
  -d '{
    "clinician_id": "22222222-2222-2222-2222-222222222222",
    "medication_id": "33333333-3333-3333-3333-333333333333",
    "reason_text": "Schistosomiasis treatment",
    "prescribed_date": "2026-07-01",
    "start_date": "2026-07-02",
    "end_date": "2026-07-30",
    "frequency": "3 times/day"
  }'
curl "http://localhost:8000/patients/11111111-1111-1111-1111-111111111111/medication-requests?status=active&prescribed_from=2026-07-01"

Business Rules

  • No overlapping active requests — a patient may not have more than one active request for the same medication whose effective dates (start_date..end_date, open-ended if end_date is null) overlap. Violations return 409 Conflict. Enforced on create and on PATCH (including re-activating an on-hold request or extending an end date).
  • No resurrection — a completed or cancelled request may not transition back to active (409 Conflict).
  • Date orderingend_date cannot be earlier than start_date (422 on create and on PATCH).
  • Restricted PATCH — only end_date, frequency, and status are accepted; any other field is rejected with 422 (Pydantic extra="forbid").

Design Decisions

  • Service layer (app/services/medication_requests.py) holds all business rules, keeping routers thin and rules unit-testable independently of HTTP. Domain exceptions (NotFoundError, ConflictError, BusinessRuleError) are mapped to HTTP status codes by exception handlers in app/main.py, so the service layer stays framework-agnostic.
  • 404 for missing references — creating a request with a non-existent patient, clinician, or medication returns 404 with a message naming the missing resource.
  • Reject rather than ignore unknown PATCH fields — explicit feedback beats silently dropping data a client believed it saved.
  • Nested response objects — the list/create responses embed clinician {first_name, last_name} and medication {code, code_name} as required, loaded with joinedload to avoid N+1 queries.
  • Status on create — the API accepts an optional status on create (defaulting to active) since the domain model lists it as a field; the overlap rule is only checked when the request is (or becomes) active.
  • SQLite for tests — keeps the suite dependency-free and fast (<1s). The SQL used is dialect-neutral; with more time I would run the same suite against Postgres in CI (see Future Improvements).

Assumptions

  • Patients, Clinicians, and Medications already exist; they are populated via the idempotent seed script (python -m app.seed) rather than CRUD endpoints.
  • "Effective dates" for the overlap rule means start_date..end_date; a null end_date is treated as open-ended (overlaps everything from start_date).
  • Date-only precision (no times/timezones) is sufficient for prescribing.
  • Only the completed/cancelled → active transition is forbidden, per the brief; other transitions (e.g. completed → on-hold) are left unrestricted rather than inventing a stricter state machine.
  • The GET endpoint returns the full filtered list (no pagination) — acceptable at per-patient volumes; pagination is listed under future improvements.
  • No authentication/authorization — out of scope for the exercise, but noted as a must-have for anything handling clinical data.

Future Improvements

  • Concurrency: the overlap check is check-then-insert and could race under concurrent writes. In production I would add a PostgreSQL exclusion constraint (EXCLUDE USING gist on a daterange + patient/medication, filtered WHERE status = 'active') to enforce the rule at the database level.
  • Run the test suite against PostgreSQL in CI (e.g. testcontainers) in addition to SQLite.
  • Pagination and sorting for the list endpoint.
  • AuthN/AuthZ (OAuth2/JWT), audit logging of status transitions.
  • Structured logging, request IDs, and observability (metrics/tracing).
  • CI pipeline (lint, mypy, tests) via GitHub Actions.

Use of AI-Assisted Development Tools

This solution was built with Claude Code (Anthropic). I provided the requirements and made the design decisions (service-layer architecture, domain exceptions, overlap semantics, 404/409/422 mapping, test cases); Claude generated the implementation code, migration, tests, and Docker setup under my direction, and I reviewed the output. The test suite and mypy strict mode were run locally to verify the result.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages