Skip to content

Repository files navigation

Work in Progress CI License Contributor Covenant Node Open Issues


Ctg-Early-Warning-System

Ctg - Early Warning System

Open-source, multi-guideline CTG (Cardiotocography) interpretation platform.
Auto-classify fetal heart rate traces against FIGO, NICE, and ACOG guidelines — side by side, in real time.

Explore the architecture docs »

Project Charter · Developer Docs · View Demo · Report Bug · Request Feature

⚠️ Clinical disclaimer: This software is a decision-support and educational tool. It does not replace clinical judgement, and it is not a certified medical device unless independently validated and cleared for your jurisdiction. See DISCLAIMER.md.

Project Status

Pre-release — work in progress.

  • ✅ Implemented — classification engine (packages/core: FIGO, NICE, ACOG strategies), REST API (apps/api, NestJS, Swagger docs), web UI (apps/web), multi-tenant branding, CI (build + lint + test on every PR)
  • 🟡 Work in progress — see the Roadmap for specifics

Persistence (e.g. Prisma/PostgreSQL) and authentication are intentionally not part of the open-source core — apps/api's traces/branding stores are deliberately in-memory reference implementations. Deployers who need persistence or auth add them as an extension layer on top of this project.

See the Roadmap for the fuller list and OPEN_SOURCE_CHECKLIST.md for open-source readiness status.

Table of Contents
  1. About The Project
  2. Project Structure
  3. Getting Started
  4. API Usage Example
  5. Roadmap
  6. Contributing
  7. License
  8. Contact
  9. Acknowledgments

About The Project

Ctg-Early-Warning-System takes structured CTG features (baseline FHR, variability, accelerations, decelerations, contraction frequency, etc.) — extracted from a monitor, an upload, or entered via form — and classifies the trace against multiple clinical guidelines at once, instead of forcing a clinician to pick just one system.

This project re-platforms established FIGO/NICE/ACOG classification logic as a web-based, brandable, multi-tenant service, so hospitals, clinics, and monitoring device vendors can white-label it under their own name and a partner's logo.

(back to top)

Core Capabilities

  • 📋 Structured intake form for CTG features (or programmatic ingestion via API)
  • 🧠 Guideline engine with pluggable strategies: FIGO, NICE, ACOG (extensible to new guidelines)
  • 🔀 "Compare all guidelines" view — run all three classifiers on one trace simultaneously
  • 🎨 White-label theming — swap primary/accent colors and logo per organization/partner
  • 🔌 REST API first, so the same engine can back a web UI, a mobile app, or a device integration

(back to top)

Tech Stack

Layer Choice Why
Backend NestJS (Node.js, TypeScript) Opinionated modular structure maps cleanly onto "one module per guideline strategy," built-in DI makes swapping/extending classifiers trivial, first-class OpenAPI/Swagger support for a public API partners will integrate against. Express is a fine minimal alternative if you want a lighter footprint.
Classification engine Plain TypeScript, framework-agnostic core package Keeps the clinical rule logic portable/testable independent of the web framework — can be reused in a CLI, a batch job, or embedded elsewhere.
Frontend HTML + Tailwind CSS (+ Alpine.js for light interactivity) Simple, dependency-light UI; Tailwind's config-driven theming (tailwind.config.js) is the natural place to inject per-partner brand tokens.
Charting Chart.js Renders the FHR/contraction trace and highlights decel/accel windows.
Database PostgreSQL (via Prisma) Structured feature data, audit trail of classifications, org/branding config. MongoDB is a reasonable swap if you prefer schema-less trace payloads.
Auth JWT + refresh tokens (or OIDC/SSO for hospital IT) Multi-tenant org isolation.
Object storage S3-compatible (MinIO for local dev) Partner logo assets, exported PDF reports.
Containerization Docker + docker-compose One-command local spin-up; matches typical hospital IT deployment constraints (on-prem friendly).

Why NestJS over plain Express: three (soon possibly more) independent classification strategies, a multi-tenant branding layer, and a public-facing API partners will build against — Nest's modules/providers/guards give you that structure for free. If the team is small and wants minimal ceremony, Express + a manual strategies/ folder is a completely valid fallback; the architecture works with either.

(back to top)

Brand System (default theme)

The UI ships with a default brand theme, fully overridable per tenant/partner via the branding config (see ARCHITECTURE.md § Multi-tenant branding).

Token Value Use
Primary — Navy #00296B Headers, primary buttons, nav bar, category badges
Accent — Signal Yellow #FFD500 Highlights, active tab indicator, warning-adjacent accents (never body text on white — fails contrast)
Background #FFFFFF Page background
Surface #F5F7FA Cards, table stripes
Text #1A1A1A Body copy
// tailwind.config.js (excerpt)
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          primary: "var(--brand-primary, #00296B)",
          accent: "var(--brand-accent, #FFD500)",
        },
      },
    },
  },
};

Per-tenant overrides are injected as CSS custom properties (--brand-primary, --brand-accent) at render time from each organization's stored branding config — so the same compiled CSS serves every partner without a rebuild.

(back to top)


Project Structure

This is the current scaffold. docker-compose.yml, Prisma/Postgres, and auth are the next layer to add — right now apps/api's traces/branding stores are deliberately in-memory reference implementations (clearly marked in the source) so the classification engine, Swagger contract, and UI wiring can be reviewed and extended independently of a persistence choice.

Ctg-Early-Warning-System/
├── ARCHITECTURE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DISCLAIMER.md
├── LICENSE.md
├── MAINTAINERS.md
├── OPEN_SOURCE_CHECKLIST.md
├── PROJECT_CHARTER.md
├── README.md
├── apps/
│   ├── api/                          # NestJS backend
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── eslint.config.mjs
│   │   ├── nest-cli.json
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── app.module.ts
│   │   │   ├── main.ts                       # Swagger setup, validation, CORS
│   │   │   └── modules/
│   │   │       ├── branding/                 # in-memory tenant theme store (swap for Postgres)
│   │   │       │   ├── branding.controller.ts
│   │   │       │   ├── branding.module.ts
│   │   │       │   ├── branding.service.ts
│   │   │       │   └── dto/
│   │   │       │       └── branding-config.dto.ts
│   │   │       ├── classification/
│   │   │       │   ├── classification.controller.ts
│   │   │       │   ├── classification.module.ts
│   │   │       │   ├── classification.service.ts
│   │   │       │   └── dto/
│   │   │       │       ├── classification-result.dto.ts
│   │   │       │       └── ctg-features.dto.ts
│   │   │       ├── health/
│   │   │       │   └── health.controller.ts
│   │   │       └── traces/                   # in-memory audit trail (swap for Prisma/Postgres)
│   │   │           ├── dto/
│   │   │           │   └── create-trace.dto.ts
│   │   │           ├── entities/
│   │   │           │   └── trace-record.entity.ts
│   │   │           ├── traces.controller.ts
│   │   │           ├── traces.module.ts
│   │   │           └── traces.service.ts
│   │   ├── test/
│   │   │   ├── app.e2e-spec.ts
│   │   │   └── jest-e2e.json
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   └── web/                          # HTML + Tailwind v4 UI
│       ├── Dockerfile
│       ├── docker-entrypoint.sh
│       ├── nginx.conf
│       ├── package.json
│       ├── public/
│       │   ├── config.js
│       │   ├── favicon.ico
│       │   ├── index.html
│       │   ├── main.js
│       │   └── output.css            # compiled by `npm run build:css`
│       ├── server.js                 # zero-dep static server for local dev
│       └── src/
│           └── input.css             # @theme brand tokens (Tailwind v4 CSS-first config)
├── assets/
│   └── logo-wordmark.svg
├── docker-compose.yml
├── docs/                             # GitHub Pages developer docs site
│   ├── _config.yml
│   ├── api-reference.md
│   ├── architecture.md
│   ├── getting-started.md
│   └── index.md
├── package.json
└── packages/
    └── core/                         # framework-agnostic classification engine
        ├── package.json
        ├── src/
        │   ├── guideline-strategy.interface.ts
        │   ├── index.ts
        │   ├── strategies/
        │   │   ├── acog.strategy.ts
        │   │   ├── figo.strategy.ts
        │   │   ├── nice.strategy.ts
        │   │   └── strategies.spec.ts
        │   └── types/
        │       └── ctg-features.ts
        └── tsconfig.json

(back to top)


Getting Started

This repo is an npm workspaces monorepo: packages/core (classification engine) is a workspace dependency of apps/api.

Prerequisites

  • Node.js ≥ 20
  • npm
    npm install npm@latest -g

Installation

  1. Clone the repo
    git clone https://github.com/DOTO-Health/ctg-early-warning-system.git
    cd ctg-early-warning-system
  2. Install and link all workspaces
    npm install
  3. Build the classification engine once (apps/api depends on its dist/ output)
    npm run build --workspace=packages/core
  4. Start the API (NestJS 11, Swagger at /api/docs) — Terminal 1
    cd apps/api
    npm run start:dev
    # -> http://localhost:3000/api
    # -> http://localhost:3000/api/docs   (interactive Swagger UI)
  5. Start the Web UI (HTML + Tailwind v4) — Terminal 2
    cd apps/web
    npm run dev
    # -> http://localhost:5173
  6. Open http://localhost:5173?org=default — the UI fetches its theme from GET /api/branding/default (Navy #00296B / Signal Yellow #FFD500) and applies it as CSS custom properties, so a different partner's theme is just a different ?org= slug (see PUT /api/branding/:orgSlug) with no rebuild.

(back to top)

Running Tests

# classification engine unit tests
cd packages/core && npm run build && node --test dist/strategies/strategies.spec.js

# API e2e tests (health, guidelines, classify-all, validation, branding)
cd apps/api && npm run test:e2e

(back to top)


API Usage Example

POST /api/classification/all
Content-Type: application/json

{
  "baseline": 145,
  "variability": 8,
  "accelerationCount": 2,
  "lateDecelCount": 0,
  "earlyDecelCount": 1,
  "variableDecelCount": 0,
  "prolongedDecelCount": 0,
  "repetitiveVariable": false,
  "contractionsPer10Min": 3,
  "totalDecelCount": 1
}
{
  "FIGO": "Normal",
  "NICE": "Atypical",
  "ACOG": "Category II"
}

(back to top)


Roadmap

  • Classification engine: FIGO / NICE / ACOG strategies
  • "Compare all guidelines" endpoint
  • Multi-tenant branding (CSS custom properties, per-org slug)
  • Swagger/OpenAPI docs
  • Docker + docker-compose one-command local stack
  • Prisma/PostgreSQL persistence (replace in-memory traces/branding stores)
  • JWT / OIDC auth and org isolation
  • PDF report export
  • Additional guideline strategies (pluggable)

See the open issues for a full list of proposed features (and known issues).

(back to top)


Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

See CONTRIBUTING.md for the full dev workflow, branch/PR conventions, and how to add a new guideline strategy. Every PR runs through CI — split into core, api, and web jobs so you can see exactly which part broke. See OPEN_SOURCE_CHECKLIST.md for everything the maintainers still need to lock down before/while going public.

Fuller developer documentation (architecture, API reference, getting started) lives at DOTO-Health.github.io/ctg-early-warning-system.

All contributors are expected to follow our Code of Conduct.

(back to top)


License

Licensed under Apache License 2.0

(back to top)


Contact

DOTO Software - software@dotohealth.com

Project Link: https://github.com/DOTO-Health/ctg-early-warning-system

(back to top)


Acknowledgments

(back to top)


DOTO Health

DOTO and the DOTO logo are trademarks of DOTO Health. Licensed under Apache 2.0 — trademark use is not covered by the code license. See LICENSE.md.

About

Early Warning System is an open-source, rule-based engine that classifies Cardiotocography (CTG) traces against multiple international clinical guidelines — FIGO (2015), NICE NG229 (2022), and ACOG (2025) — using deterministic logic.

Topics

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages