Maternal Health Multi-Task Intelligence β€” Inference Service

FastAPI service that wraps the joint multi-task PyTorch network behind a typed, explainable HTTP contract. It is Part 1 of a three-tier platform:

Next.js dashboard  ->  Spring Boot API  ->  this FastAPI service  ->  PyTorch model
   (Client/)             (Server/)              (Model/)

One shared-backbone network predicts three targets at once:

Task Head Output
Gestational Diabetes head_gdm probability + binary decision
Hypertensive Disorders head_hdp probability + binary decision
Composite Urgency Risk head_composite Low / Mid / High + full class breakdown

Architecture

The package is layered so that each concern is testable in isolation and no layer depends on one above it.

Model/
β”œβ”€β”€ app.py                        entry point; imports the factory and nothing else
β”œβ”€β”€ maternal_ai/
β”‚   β”œβ”€β”€ config.py                 typed settings (env driven, MATERNAL_* prefix)
β”‚   β”œβ”€β”€ exceptions.py             error hierarchy -> HTTP status + JSON envelope
β”‚   β”œβ”€β”€ factory.py                composition root: create_app()
β”‚   β”œβ”€β”€ domain/                   framework-free value objects
β”‚   β”‚   β”œβ”€β”€ enums.py              ClinicalTask, RiskLevel, FindingSeverity, PredictorKind
β”‚   β”‚   β”œβ”€β”€ vitals.py             PatientVitals aggregate (validation + MAP/PP behaviour)
β”‚   β”‚   └── assessment.py         RawPrediction, BinaryTaskOutcome, CompositeRiskOutcome,
β”‚   β”‚                             ClinicalFinding, RiskAssessment
β”‚   β”œβ”€β”€ features/
β”‚   β”‚   β”œβ”€β”€ engineering.py        FeatureEngineer (Template Method) + ClinicalFeatureEngineer
β”‚   β”‚   └── scaling.py            FeatureScaler (Adapter) + StandardScalerAdapter, IdentityScaler
β”‚   β”œβ”€β”€ clinical/
β”‚   β”‚   β”œβ”€β”€ rules.py              ClinicalRule (Strategy) + ClinicalRuleEngine (Composite)
β”‚   β”‚   └── narrative.py          NarrativeComposer -> summary text and action list
β”‚   β”œβ”€β”€ inference/
β”‚   β”‚   β”œβ”€β”€ architecture.py       JointMultiTaskNet (serving copy of the trained net)
β”‚   β”‚   β”œβ”€β”€ artifacts.py          ArtifactLoader + ArtifactBundle (with SHA-256 provenance)
β”‚   β”‚   β”œβ”€β”€ assembler.py          RiskAssessmentAssembler (raw numbers -> interpreted result)
β”‚   β”‚   β”œβ”€β”€ predictor.py          RiskPredictor ABC + ClinicalRuleBasedPredictor fallback
β”‚   β”‚   └── torch_predictor.py    TorchMultiTaskPredictor
β”‚   └── api/
β”‚       β”œβ”€β”€ schemas.py            Pydantic DTOs (camelCase wire format)
β”‚       β”œβ”€β”€ dependencies.py       DI providers: settings, predictor, API key guard
β”‚       β”œβ”€β”€ middleware.py         correlation id + server timing
β”‚       β”œβ”€β”€ errors.py             one exception handler for every failure mode
β”‚       └── routes.py             thin handlers
└── tests/test_inference_service.py

OOP principles in practice

  • Encapsulation β€” PatientVitals is a frozen dataclass whose constructor enforces every clinical invariant. There is no way to hold an invalid instance, so no downstream code needs defensive checks.
  • Abstraction β€” routes depend on RiskPredictor, not on PyTorch. Swapping the engine (neural, rule-based, a future ensemble) changes one factory line.
  • Polymorphism β€” TorchMultiTaskPredictor and ClinicalRuleBasedPredictor are interchangeable; predict_batch in the base class works with either.
  • Inheritance used for behaviour reuse, not taxonomy β€” RiskPredictor implements timing, batching and assembly once; subclasses supply _infer.
  • Open/Closed β€” a new clinical criterion is a new ClinicalRule subclass registered with the engine; no existing class is edited.
  • Separation of concerns β€” DTOs never appear below api/, and domain objects never know about HTTP.

Feature contract

The scaler and the first nn.Linear layer are position-sensitive, so the serving code reproduces the training column order exactly. ArtifactLoader verifies this at start-up against both scaler.n_features_in_ and the checkpoint's first layer width, and refuses to start on a mismatch.

# Feature Unit Source
0 Age years submitted
1 Systolic BP mmHg submitted
2 Diastolic mmHg submitted
3 BS mmol/L submitted
4 Body Temp Β°F submitted
5 BMI kg/mΒ² submitted
6 Previous Complications 0/1 submitted
7 Preexisting Diabetes 0/1 submitted
8 Mental Health 0/1 submitted
9 Heart Rate bpm submitted
10 MAP mmHg engineered: (SBP + 2Β·DBP) / 3
11 PP mmHg engineered: SBP βˆ’ DBP

Celsius temperatures and mg/dL glucose values are detected and converted to the trained units before scaling; the response echoes the canonicalised values.

Endpoints

Method Path Purpose
GET /health liveness/readiness, active engine, uptime
GET /metadata model card: features, thresholds, checkpoint SHA-256
POST /predict assess one patient (?includeFeatures=true adds the feature vector)
POST /predict/batch assess a cohort in one forward pass, with summary counts
GET /docs interactive OpenAPI documentation

Request

curl -X POST http://localhost:7860/predict \
  -H "Content-Type: application/json" \
  -d '{
    "age": 34, "systolicBp": 148, "diastolicBp": 96, "bloodSugar": 8.4,
    "bodyTemp": 99.1, "bmi": 31.2, "previousComplications": 1,
    "preexistingDiabetes": 0, "mentalHealth": 1, "heartRate": 96,
    "patientReference": "ANC-2026-0142"
  }'

Response (abridged)

{
  "assessmentId": "08ae58fe-94ab-4cdf-a08a-7899d4092439",
  "modelVersion": "joint-multitask-v1",
  "engine": "neural_multitask",
  "riskScore": 47.48,
  "requiresEscalation": true,
  "gestationalDiabetes": { "prediction": 1, "label": "Positive", "probability": 0.503830 },
  "hypertensiveDisorders": { "prediction": 0, "label": "Negative", "probability": 0.244116 },
  "compositeRisk": {
    "label": "High Risk",
    "confidence": 0.446449,
    "reviewWindow": "24 hours",
    "probabilities": { "lowRisk": 0.362502, "midRisk": 0.191050, "highRisk": 0.446449 }
  },
  "findings": [
    {
      "code": "BP_ELEVATED",
      "severity": "warning",
      "detail": "Blood pressure 148/96 mmHg meets the gestational hypertension threshold (>= 140/90).",
      "recommendation": "Confirm with a repeat reading after 4 hours of rest and test for proteinuria."
    }
  ],
  "recommendedActions": ["Escalate to an obstetric clinician for same-day assessment."],
  "narrative": "A 34-year-old patient presenting with blood pressure 148/96 mmHg ..."
}

Errors always use the same envelope:

{
  "errorCode": "invalid_vitals",
  "message": "Systolic blood pressure must be greater than diastolic blood pressure.",
  "details": { "systolicBp": 70.0, "diastolicBp": 80.0 },
  "requestId": "69f7ce98-9d6f-4991-a76e-ce991143ff5e"
}

Configuration

Every setting is an environment variable prefixed with MATERNAL_.

Variable Default Purpose
MATERNAL_MODEL_PATH ./joint_multitask_model.pth checkpoint location
MATERNAL_SCALER_PATH ./joint_scaler.pkl fitted scaler location
MATERNAL_DEVICE auto auto, cpu or cuda
MATERNAL_GDM_THRESHOLD 0.5 gestational diabetes decision boundary
MATERNAL_HDP_THRESHOLD 0.5 hypertensive disorders decision boundary
MATERNAL_MAX_BATCH_SIZE 256 rejects larger batches with HTTP 413
MATERNAL_ALLOW_RULE_BASED_FALLBACK true serve rule-based results if artifacts fail to load
MATERNAL_API_KEY unset when set, /predict* requires the X-API-Key header
MATERNAL_CORS_ORIGINS * comma separated allowed origins
MATERNAL_PORT 7860 listen port

Running

# local
pip install torch==2.13.0 --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
python app.py                       # http://localhost:7860/docs

# tests
pip install -r requirements-dev.txt
python -m pytest tests -q

# docker
docker build -t maternal-ai .
docker run --rm -p 7860:7860 maternal-ai

Hugging Face Spaces

Create a Space with the Docker SDK and push app.py, maternal_ai/, Dockerfile, requirements.txt and both artifact files. The YAML header at the top of this README is the Space configuration; app_port: 7860 matches the EXPOSE/CMD in the Dockerfile.

Known limitation of the shipped checkpoint

joint_scaler.pkl was fitted on three training rows, so the bundled weights are a smoke-test artifact rather than a trained model β€” for example it returns a low hypertensive-disorder probability for a clearly hypertensive 148/96 reading. Re-run maternal_pipeline.py against the full Maternal Health Risk Data Set and replace both artifacts; the service picks up the new files with no code changes, because the loader validates the contract dynamically.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support