by Tiago Rodrigues, 2025
This project implements a scalable, secure, and event-driven backend service for clinical recommendations.
Built with Python and FastAPI, it exposes a RESTful API to process patient health data and return clinical recommendations based on predefined business rules.
The system is fully containerized with Docker and leverages RabbitMQ for event-driven processing, Redis for caching, and SQLite for persistent storage.
Key Features:
- REST API for patient data and recommendations
- Rule-based engine simulating clinical recommendation logic
- Event-driven architecture: recommendations trigger background events
- Asynchronous, scalable, and cache-optimized (Redis)
- JWT-based authentication for secure access
- Out-of-the-box Docker deployment
- OpenAPI (Swagger) documentation
Assumptions
- We consider that a patient shall only be subjected to a Post-Op Rehabilitation Plan if it is from orthopaedics surgery, for instance. Other departments may be included on .venv variable ADMISSIBLE_DEPARTMENTS_FOR_REHAB.
- The API does not generate Patient, Procedures, neither Recommendation IDs (Usually that role belongs to the Hospital MPI and HIS). Instead, it expects these IDs to be unique and to be sent on the methods' payload.
- A POST /evaluate should accept patient data and return a mock clinical recommendation. For that, it is required for that patient to have procedure history, registered beforehand via API. The method therefore only requires the Patient ID.
- The GET /recommendation method was replaced by the GET /patient_recommendation for API consistency. It allows GET by patient_id, procedure_id and object expansion.
- No actual email or phone call was implemented. Instead, a placeholder method is in place, which logs a hypothetical email.
Considerations:
- The API only generates internal IDs, such as key association between patient and procedure, and patient and recommendation. Also accepting external ones as long as it does not violate DBs integrity.
- In order to evaluate patient history, a procedure shall be registered and linked to the patient previously via API.
- The API relies on its database, which is delivered empty. Refer to MAKE commands to seed it with Mock data before testing. With this data:
- Patient with ID 108 shall be recommended with “Physical Therapy” and “Post-Op Rehabilitation Plan”
- Patient with ID 105 shall be recommended with "Weight Management Program"
- Patient with ID 102 shall be recommended with “Post-Op Rehabilitation Plan”
- The recommendation logic should be easy to replace by actual AI modeled recommendations.
- OpenAPI is intrinsically provided by FastAPI
- A Makefile is delivered with a set of commands for easy running, implementations, and testing. Check below.
- FastAPI serves as the main API, handling patient data, authentication, and recommendation requests.
- Rule Engine applies business logic to generate recommendations:
- Age > 65 & chronic pain → "Physical Therapy"
- BMI > 30 → "Weight Management Program"
- Recent surgery → "Post-Op Rehabilitation Plan"
- Event Producer: When a recommendation is generated, an event is published to RabbitMQ.
- Event Consumer: A separate process listens for events, logs recommendations, simulates notifications, and appends analytics.
- Redis: Caches frequent queries for performance.
- SQLite: Stores all persistent data (patients, procedures, recommendations, users).
- JWT Auth: Secures all endpoints except authentication.
- Pseudo-pagination with limitation of DB entries
The project includes a Makefile to simplify common operations, data seeding, and API validation. Check help for full command list.
make helpmake run-rabbitmq
make run-redisNote: If needed, set environment variables (or copy from .env.example) before creating the venv.
make venvmake run-appmake run-consumerAPI docs: http://localhost:8000/docs
RabbitMQ UI: http://localhost:15672 (guest/guest)
make stop-rabbitmq
make rm-rabbitmq
make stop-redis
make rm-redisFor a simple running test, docker shall be used. Start Docker stack: API, Consumer, RabbitMQ, and Redis.
make docker-upYou may need to way a few seconds for RabbitMQ to start and the Consumer to be able to connect to it. The Consumer will retry the connection until it actually succeeds. You may check the logs with:
make logs
make logs-rabbit
make logs-redismake docker-downConsidering default user "admin", password "admin"
make get-token USER=admin PASS=adminIn order to really use the Evaluate method, we need to populate the database with mock patient and clinical data, including Patients, Clinical Procedures, Recommendations, and Patient Procedure Records
make seed-dbmake eval-patient PATIENT_ID=108make get-recom PATIENT_ID=108API docs: http://localhost:8000/docs
RabbitMQ UI: http://localhost:15672 (guest/guest)
- POST /security/token > Issue access & refresh tokens (OAuth2 password; form fields: username, password)
- POST /security/refresh > Exchange a valid refresh token for a new access token
- POST /security/register > Create a new user (protected; requires authenticated caller)
- POST /patient/ > Create a patient
- PUT /patient/{patient_id} > Full replace/update a patient (path ID must match body ID)
- GET /patient/{patient_id} > Retrieve a single patient (cached)
- GET /patients > List patients (paginated; query: skip, limit; cached)
- DELETE /patient/{patient_id} > Delete a patient
- POST /procedure > Create a procedure
- GET /procedure/{procedure_id} > Retrieve a single procedure (cached)
- GET /procedures > List procedures (paginated; query: skip, limit; cached)
- PUT /procedure/{procedure_id} > Full replace/update a procedure (path ID must match body ID)
- DELETE /procedure/{procedure_id} > Delete a procedure
- POST /patient_procedure > Link a patient to a procedure (with datetime/report)
- GET /patient_procedures > List patient-procedure links (paginated; filters: patient_id, procedure_id; expand: procedures|patients|patient_ids; cached)
- GET /patient_procedure/{patient_procedure_id} > Retrieve a single patient-procedure link
- PUT /patient_procedure/{patient_procedure_id} > Full replace/update a patient-procedure link
- DELETE /patient_procedure/{patient_procedure_id} > Delete a patient-procedure link
- POST /recommendation > Create a recommendation definition (e.g., por-123)
- GET /recommendations > List recommendations (paginated; query: skip, limit; cached)
- GET /recommendation/{recommendation_id} > Retrieve a single recommendation (cached)
- PUT /recommendation/{recommendation_id} > Full replace/update a recommendation
- DELETE /recommendation/{recommendation_id} > Delete a recommendation
- POST /patient_recommendation > Link a patient to a recommendation (with timestamp)
- GET /patient_recommendations > List links (paginated; filters: patient_id, recommendation_id; expand: recommendations|patients; cached)
- GET /patient_recommendation/{patient_recommendation_id} > Retrieve a single patient-recommendation link
- PUT /patient_recommendation/{patient_recommendation_id} > Full replace/update a patient-recommendation link
- DELETE /patient_recommendation/{patient_recommendation_id} > Delete a patient-recommendation link
- POST /evaluate/{patient_id} > Run rule-based evaluation for a patient; publish events; returns generated events or {"message":"Nothing to recommend","recommendations":[]}
- [DONE] Validate DB operations so that a primary key cannot be deleted when a secondary key exists on another table (enforce referential integrity)
- Allow patient merge operations (e.g., deduplicate patients)
- Compute BMI automatically from height and weight if not provided
- Generate order or alert HL7 FHIR, v3, or v2 messages
- Implement and manage roles authorization
- Improve security and validate command injection vulnerabilities. Ref.: https://owasp.org/www-community/attacks/Command_Injection
- Improve API design. Ref.: https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
- Implement automatic testing