EUNICE M. Get in Touch
Back to Projects

Machine Learning · Health Informatics

STI Predictive Model

A clinical intelligence platform that scores a patient's STI risk from behavioural and clinical indicators, then gives clinicians and public-health teams the dashboard, geospatial heatmap and reporting tools to act on it. Random Forest classifier, Django Ninja API, React front end.

Python Scikit-learn Django 6 Django Ninja React + Vite Leaflet Tailwind

Demo status: the React front end is deployed on Vercel, so you can walk the full interface. The Django API that serves predictions isn't hosted yet — data panels stay empty until the backend runs. To see it end to end, follow the run-it-locally steps below, or ask me for a walkthrough.

sti-predictive-model.vercel.app Open

Live embed of the deployed front end. If it doesn't load in your browser, open it in a new tab.

Model performance at a glance

Measured on a held-out test split and written to metadata.json at training time, so the registry reports what the model actually scored rather than a number typed in by hand.

0.774 AUC-ROC Ranking quality across thresholds
81.6% Accuracy Held-out test split
0.55 Recall Share of true cases caught
0.35 Precision Deliberately traded for recall
16 Features Behavioural + clinical inputs
8,000 Training rows Reproducible, seeded generation

Honest caveat on the data

The current model trains on a synthetic dataset I generate with documented epidemiological effect sizes — younger age, higher partner counts, low condom use, prior STI history and HIV status all pushing risk up, with noise added so the model has to learn rather than memorise. It's a placeholder while access to the KDHS Individual Recode is approved. Swapping in the real dataset is a data-source change, not a rewrite: the training command, feature contract and registry all stay as they are.

The Problem

STI risk assessment in a busy clinic is a judgement call made under time pressure, and the signals that matter are scattered across a patient's history. Screening everyone is expensive; screening by intuition misses people. What's needed is a consistent score that ranks who to prioritise, plus a record of why that score was given.

Public-health teams have a second problem on top of it: they need the aggregate picture — where cases cluster, how risk moves over time — in a form that goes into a Ministry of Health report without manual collation.

My Role

Sole developer, end to end. I designed the feature set and training pipeline, built the model registry, wrote the Django Ninja API across nine apps, and built the React clinician dashboard on top of it — including the geospatial heatmap and the audit-logging layer.

  • ML pipeline & model registry
  • REST API design & schemas
  • Frontend architecture & UI
  • Compliance & audit trail

System architecture

Nine Django apps behind a single Ninja API, one React client, and model artifacts on disk that the registry keeps in sync with the database.

Client

  • React 18 + Vite + Tailwind
  • Risk assessment form
  • Dashboard & patient records
  • Leaflet county heatmap
  • Recharts trend reporting

API layer

  • Django 6 + Django Ninja
  • Pydantic request/response schemas
  • /api/predictions/
  • /api/patients/ · /api/clinicians/
  • /api/geospatial/ · /api/reporting/
  • /api/ml/ · /api/compliance/

Inference

  • STIPredictor wrapper
  • Loads model.joblib + scaler
  • Fixed 16-feature vector order
  • Heuristic fallback if no model
  • Registry sync to MLModel

patients

Patient records and the behavioural/clinical fields the model reads.

clinicians

Clinician accounts and the assessments attributed to them.

prediction_engine

Scoring endpoints and the model wrapper that produces them.

ml_pipeline

Training command, model registry and management commands.

preprocessing

Cleaning and encoding utilities shared by training and inference.

data_ingestion

CSV upload and batch intake of patient data.

geospatial

County-level aggregation powering the risk heatmap.

moh_reporting

Ministry of Health reporting summaries and dashboard metrics.

compliance

Middleware writing an audit log entry for every API request.

The ML pipeline

From raw intake to a live prediction, every stage is a command you can re-run rather than a notebook someone has to remember how to execute.

  1. 01

    Ingest

    Patient data arrives through the API or a CSV upload handled by data_ingestion, and is persisted as structured records.

  2. 02

    Preprocess

    Categorical fields are one-hot encoded — gender and marital status — and numeric fields scaled with a StandardScaler that is saved alongside the model so inference applies the exact same transform.

  3. 03

    Train

    manage.py train_sti_model builds a seeded dataset, splits it, fits a RandomForestClassifier, and evaluates AUC-ROC, accuracy, F1, precision and recall on the held-out set.

  4. 04

    Register

    Artifacts land in media/models/<name>/. sync_model_registry reads metadata.json and upserts an MLModel row with the real metrics, version and hyperparameters, promoting the first model to default.

  5. 05

    Serve

    STIPredictor loads the joblib artifacts on demand and scores against a fixed feature order shared with the trainer — a mismatch there is the classic silent ML bug, so both sides read one canonical list.

  6. 06

    Audit

    Middleware records who called what, from which IP, for every request — and distinguishes a prediction being generated from a prediction being read, so the log means what it says.

What the model looks at

Sixteen features across four groups, chosen to match indicators clinicians already collect rather than data that would need a new intake form.

Demographics

  • Age
  • Gender (male / female / other)

Behavioural

  • Partners in last 12 months
  • Lifetime partners
  • Condom use frequency
  • Substance use

Clinical history

  • Prior STI history
  • HIV positive
  • HIV status unknown
  • Symptoms present

Social context

  • Single
  • Married
  • Divorced
  • Cohabiting

Engineering decisions

Recall over precision

At a 0.55 recall and 0.35 precision, the model over-flags. That's the right direction for a screening tool: a false positive costs a test, a false negative costs a missed infection. The threshold is a tunable dial, not an accident.

Metrics that can't drift from reality

The registry reads its numbers from the metadata the trainer wrote. Nobody can hand-edit an accuracy figure into the database and have the dashboard quietly repeat it.

One canonical feature order

Trainer and predictor share a single FEATURE_ORDER list. Silently reordered columns are the failure mode that produces confident, wrong predictions with no error to catch.

Graceful degradation

If no trained artifact is present, the predictor falls back to a heuristic instead of crashing the API — a fresh clone still runs before anyone trains anything.

Audit by default

Compliance isn't a feature bolted on at the end. Middleware logs every non-static request, so the trail exists whether or not a given endpoint remembered to write one.

Versioned model artifacts

Models live in versioned directories with their scaler and metadata. Retraining produces a new registry entry rather than overwriting the one currently serving traffic.

Run it locally

The backend isn't hosted yet, so this is the fastest way to see predictions actually being served. Two terminals, about five minutes.

Backend — Django API

git clone https://github.com/Eunice-ctrlz/STI-PREDICTIVE-MODEL.git
cd STI-PREDICTIVE-MODEL
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py train_sti_model
python manage.py sync_model_registry
python manage.py runserver

API on localhost:8000, interactive docs at /api/docs.

Frontend — React client

cd sti-frontend
npm install
npm run dev

Vite serves on localhost:5173 and proxies to the Django API. Set VITE_API_URL to point at a different backend.

What's next

Host the API

Deploy Django to a container host with a managed Postgres, so the Vercel front end talks to a live backend instead of localhost.

Real training data

Retrain on the KDHS Individual Recode once access is approved, and re-baseline every metric on this page against it.

Calibration & thresholds

Probability calibration plus a threshold clinicians can tune to their own screening capacity.

Explainability

Per-prediction feature attributions, so a clinician sees which inputs drove a score rather than a bare number.

Want a walkthrough of the code?

I'm happy to talk through the pipeline, the API design or the decisions above — and I build systems like this for other people too.