Skip to content
DHIS2.org Community GitHub

Architecture diagrams

This page gives a top-level view of the CHAP system across the three main repositories — chap-core (backend), chap-frontend (web UI) and automatic-model (an example chapkit model service) — as a set of mermaid diagrams.

The diagrams complement the more detailed guides:

Component diagram

Shows the main components across the three repos and how they relate. The FastAPI server, the Celery worker, Redis and PostgreSQL all live in chap-core. chap-frontend is an embedded DHIS2 app that talks to the REST API through a generated OpenAPI client. Model execution happens either in-process via a TrainPredictRunner (e.g. Docker, MLflow, UV) or remotely against a chapkit HTTP service such as automatic-model.

flowchart LR
    User([User])

    subgraph DHIS2["DHIS2 instance"]
        DHIS2Core[DHIS2 core]
    end

    subgraph Frontend["chap-frontend (pnpm monorepo)"]
        ModelingApp[modeling-app<br/>React + Vite]
        UIPkg[ui package<br/>OpenAPI client + components]
        ModelingApp --> UIPkg
    end

    subgraph ChapCore["chap-core"]
        subgraph API["FastAPI app (rest_api/app.py)"]
            V1[/v1 routers<br/>crud, analytics, jobs, visualization/]
            V2[/v2 routers<br/>services registry/]
            Common[/common routes<br/>health, info/]
        end

        Worker[Celery worker<br/>db_worker_functions]
        Orchestrator[Orchestrator<br/>rest_api/services]

        subgraph Runners["TrainPredictRunner implementations"]
            Docker[DockerTrainPredictRunner]
            UV[UvTrainPredictRunner]
            Conda[CondaTrainPredictRunner]
            Renv[RenvTrainPredictRunner]
            CLI2[CommandLineTrainPredictRunner]
            MLflow[MlFlowTrainPredictRunner]
        end

        ChapkitClient[CHAPKitRestAPIWrapper<br/>httpx client]
        ChapCLI[chap CLI<br/>cli.py]
    end

    Redis[(Redis<br/>Celery broker +<br/>service registry)]
    Postgres[(PostgreSQL<br/>datasets, backtests,<br/>predictions, models)]
    MLflowDB[(MLflow tracking<br/>mlflow.db)]

    subgraph ExternalModels["External model services (chapkit)"]
        AutoModel[automatic-model<br/>FastAPI + chapkit<br/>RandomForest disease model]
        OtherChapkit[Other chapkit services<br/>e.g. ewars, bayesian]
    end

    ExternalRepos[Model source repos<br/>git / MLproject]

    User --> DHIS2Core
    DHIS2Core --> ModelingApp
    UIPkg -- HTTPS --> API

    V1 -- queue task --> Redis
    V1 -- read/write --> Postgres
    V2 -- register/ping --> Orchestrator
    Orchestrator -- TTL keys --> Redis
    Orchestrator -- sync templates --> Postgres

    Worker -- consume --> Redis
    Worker -- read/write --> Postgres
    Worker -- resolve URL --> Orchestrator
    Worker --> Runners
    Worker --> ChapkitClient

    Runners -- clone / run --> ExternalRepos
    MLflow -- runs --> MLflowDB
    ChapkitClient -- HTTP --> AutoModel
    ChapkitClient -- HTTP --> OtherChapkit
    AutoModel -- $register / $ping --> V2
    OtherChapkit -- $register / $ping --> V2

    ChapCLI --> Runners

Sequence diagram

Representative end-to-end flow for a user-triggered evaluation (backtest) launched from the modeling app. The same shape applies to predictions (/v1/analytics/make-prediction) — only the worker function and the result endpoint differ.

sequenceDiagram
    actor User
    participant FE as modeling-app<br/>(chap-frontend)
    participant API as FastAPI<br/>(rest_api/v1)
    participant DB as PostgreSQL
    participant Q as Redis<br/>(Celery broker)
    participant W as Celery worker<br/>(db_worker_functions)
    participant O as Orchestrator<br/>(service registry)
    participant M as Model service<br/>(chapkit / Docker runner)

    User->>FE: configure and submit backtest
    FE->>API: POST /v1/analytics/create-backtest-with-data
    API->>DB: insert DataSet + Observations
    API->>Q: queue run_backtest task
    API-->>FE: 200 JobResponse { id }

    loop poll until terminal
        FE->>API: GET /v1/jobs/{id}
        API->>Q: fetch job_meta:{id}
        API-->>FE: status (PENDING / STARTED / ...)
    end

    Q-->>W: deliver task
    W->>DB: load DataSet + ConfiguredModel
    W->>O: resolve live service URL (if chapkit)
    O-->>W: base_url or stored fallback
    W->>M: train + predict per fold
    M-->>W: samples / forecasts
    W->>DB: insert BackTest + BackTestForecast + BackTestMetric
    W->>Q: set job_meta status = SUCCESS

    FE->>API: GET /v1/jobs/{id}
    API-->>FE: SUCCESS + database_result id
    FE->>API: GET /v1/analytics/evaluation-entry?backtestId=...
    API->>DB: load forecasts + actuals
    API-->>FE: quantile forecast payload
    FE-->>User: render charts and metrics

Class diagram

Main domain classes and their relationships as persisted in PostgreSQL. All tables inherit from DBModel (SQLModel + camelCase aliasing). BackTest and Prediction are the two "run" concepts — a BackTest is a retrospective evaluation over known data, a Prediction is a forward forecast. Both carry forecast samples and reference the DataSet and ConfiguredModelDB they were produced from.

classDiagram
    class DBModel {
        <<base, SQLModel>>
        +id: int
    }

    class DataSet {
        +name: str
        +type: str
        +geojson: str
        +created: datetime
    }

    class Observation {
        +period: str
        +orgUnit: str
        +element: str
        +value: float
    }

    class ModelTemplateDB {
        +name: str
        +sourceUrl: str
        +description: str
    }

    class ConfiguredModelDB {
        +name: str
        +userOptions: dict
        +additionalContinuousCovariates: list
        +deleted: bool
    }

    class BackTest {
        +name: str
        +created: datetime
        +aggregationLevel: str
        +splitPeriods: list
    }

    class BackTestForecast {
        +period: str
        +orgUnit: str
        +lastTrainPeriod: str
        +values: list~float~
    }

    class BackTestMetric {
        +metricId: str
        +period: str
        +orgUnit: str
        +value: float
    }

    class Prediction {
        +name: str
        +created: datetime
        +metaData: dict
    }

    class PredictionSamplesEntry {
        +period: str
        +orgUnit: str
        +values: list~float~
    }

    class DebugEntry {
        +timestamp: datetime
        +payload: dict
    }

    DBModel <|-- DataSet
    DBModel <|-- Observation
    DBModel <|-- ModelTemplateDB
    DBModel <|-- ConfiguredModelDB
    DBModel <|-- BackTest
    DBModel <|-- BackTestForecast
    DBModel <|-- BackTestMetric
    DBModel <|-- Prediction
    DBModel <|-- PredictionSamplesEntry
    DBModel <|-- DebugEntry

    DataSet "1" --> "*" Observation : observations
    ModelTemplateDB "1" --> "*" ConfiguredModelDB : configurations
    DataSet "1" --> "*" BackTest : backtests
    DataSet "1" --> "*" Prediction : predictions
    ConfiguredModelDB "1" --> "*" BackTest : runs
    ConfiguredModelDB "1" --> "*" Prediction : runs
    BackTest "1" --> "*" BackTestForecast : forecasts
    BackTest "1" --> "*" BackTestMetric : metrics
    Prediction "1" --> "*" PredictionSamplesEntry : samples