Skip to content

Drkilla/mediguard

Repository files navigation

MediGuard

Intelligent therapeutic monitoring platform for polymedicated patients. Centralizes treatments, detects drug interactions in real time using official ANSM data, and orchestrates clinical alerts.

Stack Tests Architecture Data


Why This Project

A polymedicated patient sees 3 specialists. Each prescribes without knowing the full treatment picture. MediGuard automatically cross-checks all of a patient's medications against the official ANSM Interaction Thesaurus and raises clinical alerts when a contraindication or inadvisable combination is detected.

The system resolves the complete chain: brand name → BDPM → active substance → salt form → normalized INN → therapeutic classes → interactions. Without this resolution, most interactions remain invisible.

Why Event Sourcing

Event Sourcing is not a gratuitous technical choice — it's a domain requirement.

In pharmacovigilance, every mutation must be traceable: who added this treatment, when, who stopped it, why, which interactions were detected at that point, who acknowledged the alert, with what justification. A simple CRUD with updated_at columns loses this information.

With Event Sourcing, the patient's event stream tells the complete story:

TherapeuticRecordCreated          → 2026-04-06 10:00
TreatmentAdded (Warfarin)         → 2026-04-06 10:01
InteractionAnalysisCompleted (0)  → 2026-04-06 10:01
TreatmentAdded (Fluconazole)      → 2026-04-06 10:05
InteractionAnalysisUpdated (1 PE) → 2026-04-06 10:05
TreatmentAdded (Domperidone)      → 2026-04-06 10:10
InteractionAnalysisUpdated (2)    → 2026-04-06 10:10
NewCriticalInteractionFound (CI)  → 2026-04-06 10:10
AlertRaised (PENDING)             → 2026-04-06 10:10
AlertAcknowledged                 → 2026-04-06 10:15
AlertOverridden (justification)   → 2026-04-06 10:20

Every event is immutable, timestamped, and replayable. The patient's state can be reconstructed at any point in time.

Architecture

Bounded Contexts

┌─────────────────────────────────────────────────────────────────┐
│                     Frontend React 18 + MUI 7                   │
└──────────────────────────────┬──────────────────────────────────┘
                               │ REST API (10 endpoints)
┌──────────────────────────────▼──────────────────────────────────┐
│                   Symfony 7.4 — PHP 8.5                         │
│           command.bus (sync) / query.bus (sync)                  │
│                event.bus (async — RabbitMQ)                      │
├────────────┬────────────┬────────────┬──────────────────────────┤
│ Therapeutic│ Interaction│  Alert &   │      Drug Catalog        │
│   Record   │  Analysis  │  Decision  │    (supporting ctx)      │
│    (ES)    │   (ES)     │    (ES)    │    batch import          │
├────────────┴────────────┴────────────┴──────────────────────────┤
│                    PostgreSQL 16 — Event Store                   │
│               append-only, optimistic concurrency               │
└─────────────────────────────────────────────────────────────────┘

Therapeutic Record — Patient's therapeutic assessment. ES aggregate with addTreatment, stopTreatment (reason + author required), modifyPosology. Every mutation carries a UserId for traceability.

Interaction Analysis — Core domain. InteractionEngine (Domain Service) with multi-level detection algorithm: direct lookup → via class A → via class B → via both classes, with deduplication by symmetric key. PatientInteractionReport (ES aggregate) persists the analysis history.

Alert & Decision — State machine: PENDING → ACKNOWLEDGED → OVERRIDDEN → RESOLVED. An override requires a justification of at least 50 characters. Alerts are raised automatically via EventSubscriber on critical interactions.

Drug Catalog — Supporting context. Batch import of BDPM (15,033 drugs) and ANSM Thesaurus (3,220 interactions). Isolated from the core by an Anti-Corruption Layer.

CQRS — 3 Symfony Messenger Buses

  • command.bus (sync) — mutations: AddTreatmentCommand, StopTreatmentCommand, AcknowledgeAlertCommand...
  • query.bus (sync) — reads: GetActiveTreatmentsQuery, GetPatientInteractionsQuery...
  • event.bus (async, RabbitMQ) — cross-BC propagation: TreatmentAdded triggers analysis, NewCriticalInteractionFound raises an alert

Automated Pipeline

Adding a medication
  → TreatmentAdded (async)
    → AnalyzePatientInteractions (sync)
      → InteractionEngine detects CI
        → NewCriticalInteractionFound (async)
          → RaiseAlert (sync)
            → ClinicalAlert PENDING

Pharmaceutical Data

Source Content Volume
BDPM Drugs, compositions, substances 15,033 specialties
ANSM Thesaurus Drug interactions (parsed PDF) 3,220 interactions
ANSM Thesaurus Therapeutic classes ↔ substances 168 classes, 1,924 members

Hybrid substance normalization: official ANSM FT (priority) → regex fallback for salts → ICU transliterator for diacritics (2,234 substances affected).

Stack

Layer Technologies
Backend PHP 8.5, Symfony 7.4 LTS
Frontend React 18, TypeScript, MUI 7, TanStack Query v5, Vite
Database PostgreSQL 16
Message broker RabbitMQ 3.13
Architecture DDD, Clean Architecture, CQRS, Event Sourcing, TDD
CI GitHub Actions (PHPUnit + PHPStan level 8)

Installation

Prerequisites

  • Docker & Docker Compose
  • Node.js 18+ and npm

Getting Started

git clone https://github.com/Drkilla/mediguard.git
cd mediguard
docker compose up -d

Migrations and Data

docker compose exec php php bin/console doctrine:migrations:migrate --no-interaction
docker compose exec php php bin/console drug-catalog:import-bdpm
docker compose exec php php bin/console drug-catalog:import-thesaurus

Running the Application

# Async worker (terminal 1)
docker compose exec -e APP_DEBUG=0 php php bin/console messenger:consume async -vv --env=prod

# Frontend (terminal 2)
cd frontend && npm install && npm run dev

Tests

docker compose exec php vendor/bin/phpunit       # 243 tests, 630 assertions
docker compose exec php vendor/bin/phpstan analyse  # level 8, 0 errors

API Endpoints

Method Route Description
GET /api/drugs/search?q= Search drugs
POST /api/patients/{id}/treatments Add a treatment
GET /api/patients/{id}/treatments Active treatments
POST /api/patients/{id}/treatments/{tid}/stop Stop a treatment
POST /api/patients/{id}/treatments/{tid}/modify-posology Modify posology
GET /api/patients/{id}/interactions Detected interactions
GET /api/patients/{id}/alerts Clinical alerts
POST /api/alerts/{id}/acknowledge Acknowledge an alert
POST /api/alerts/{id}/override Override an alert
POST /api/alerts/{id}/resolve Resolve an alert

Structure

src/
├── SharedKernel/          # Event Sourcing, shared Value Objects
├── TherapeuticRecord/     # BC — Therapeutic assessment (ES)
├── InteractionAnalysis/   # BC — Interaction engine (ES)
├── AlertDecision/         # BC — Clinical alerts (ES)
├── DrugCatalog/           # BC — Drug reference catalog
└── UI/Http/Controller/    # REST API
frontend/                  # React 18 + TypeScript + MUI 7
docs/                      # Detailed project documentation

Disclaimer

This project is a technical demonstrator. It is not a medical device and must not be used to make therapeutic decisions.

Author

About

Therapeutic monitoring platform — drug interaction detection on ANSM data. DDD, CQRS, Event Sourcing, PHP/Symfony.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages