Institut Pasteur de Dakar

Epidemiological Surveillance Platform

Design and progressive replacement of the forms service of a national health surveillance programme, running in Senegal and nine other countries.

Role
Full-Stack Software Engineer - Consultant
Period
March – August 2026
countries, no downtime
10
forms put into service
4
lines of code for the 4th
0
  • NestJS
  • Angular
  • Kafka
  • PostgreSQL
  • Keycloak
  • Drizzle ORM
  • Fastify
  • TypeScript
  • IndexedDB
  • Storybook
  • Kubernetes

Context

Institut Pasteur de Dakar is one of West Africa’s leading public health institutions. Its 4S programme runs syndromic and sentinel health surveillance.

In practice: health workers collect epidemiological forms in the field — respiratory infections, community surveillance, maternal and neonatal deaths, diphtheria — and the associated samples travel on to the laboratory and then the biobank. The chain runs from the field worker’s notebook to the stored specimen.

The problem

The system relied on a legacy Django service in which every new form meant writing code: a hard-coded list of types, and a backend redeployment to publish a form. A software delivery timeline imposed on a public health need.

The difficulty was not designing a better system. It was substituting it for one already running in ten countries, without interruption. You do not take a health surveillance chain offline for the duration of a migration.

The constraints

No downtime available. Client applications in production, across ten countries, depended on the existing service.

Heterogeneous deployment environments. Countries are not all equally equipped. A single release has to reach targets of different kinds.

The network is not guaranteed. Field workers enter data where they meet patients, not where there is connectivity.

A form crosses several roles. It is split into steps assigned to different roles, with handover, review, and distinct permissions depending on whether you read, start, or fill it.

Several authentication boundaries. The consuming applications do not share a realm.

Health data. Strict compartmentalisation, traceability, and sample integrity once a specimen has entered the laboratory circuit.

What I designed and built

Forms management service

≈ 98% of commits

Design and development

The core of the platform, written to replace the legacy service one route at a time.

  • NestJS 10
  • Fastify
  • TypeScript
  • Drizzle ORM
  • PostgreSQL
  • Kafka
  • Keycloak
  • Jest
See details (6)Hide details
  • Progressive migration at constant contract. The new service reproduces the legacy HTTP contract exactly. Each route can switch over independently, with no change required in any client application and no downtime window.
  • Hexagonal architecture, ten bounded contexts. Domain, ports, infrastructure, and presentation kept separate, with ports injected by token. End-to-end tests run against in-memory doubles, with no real infrastructure.
  • Reliable messaging via transactional outbox. Kafka events are written to the database in the same transaction as the data, then drained by a dedicated worker with scheduled purging and Prometheus metrics. The database stays the source of truth: a broker incident never causes a field worker's submission to fail.
  • Multi-realm security. Keycloak authentication with strict issuer validation, and access control through role / site / step bindings, with distinct policies for reading, starting, and filling a form.
  • Data-driven forms. A new form feeds the sample circuit as soon as its schema declares the corresponding block. No hard-coded list, no backend redeployment to publish a form.
  • Integrations with the patient, analysis, and biobank services, resolved and cached reference data, MinIO object storage, and synchronisation with the existing forms system.

Angular component library

≈ 92% of commits

Design and maintenance

Nineteen components shared by three applications, built on a kit-first strategy: extract rather than duplicate. Wizard and sub-step navigation, editable grids, text, select, chip, tile, counter, date and time fields, upload, geolocation, autosave.

  • Angular
  • PrimeNG
  • ng-packagr
  • Storybook
  • CSS design tokens
  • released via Git tags

Forms administration console

≈ 98% of commits

Design and development

An Angular application for designing, versioning, publishing, and managing access to forms. It is what makes the rest usable without a developer.

Field data entry application

≈ 24% of commits

Major contributor

A Progressive Web App used by health workers.

  • Angular standalone
  • PrimeNG
  • Dexie / IndexedDB
  • Keycloak
  • service worker
See details (3)Hide details
  • Custom form rendering engine, roughly 4,400 lines, with no dependency on the third-party library used until then: wizard, conditional fields, grids, per-step validation, prefilling.
  • Offline-first: drafts persisted locally and resumed without a connection.
  • Community surveillance: moving from a community alert to a prefilled clinical form.

Laboratory application

Integration

Linking forms to specimens and publishing samples to the biobank, primarily on the backend side. Realigning this application's rendering engine with the field application's.

Infrastructure and delivery

Per-environment overlays, CI/CD pipelines, configuration and secrets management. Diagnosis and recovery of a blocked environment, with the fix carried into infrastructure-as-code rather than applied by hand.

  • Kubernetes
  • kustomize
  • Jenkins
  • Docker

Decisions and trade-offs

Reproducing the legacy HTTP contract exactly
This means inheriting API choices you would not have made yourself, and carrying them into a new architecture. In exchange, the switchover happens route by route, every step is reversible, and no client application across the ten countries had to change. On a health surveillance system, reversibility is worth more than elegance.
Transactional outbox rather than publishing straight to Kafka
One more worker and one more purge to operate, against the guarantee that a broker outage never reaches the field worker. When data entry happens in the field, a failed submission cannot be retried: the person has already left.
In-memory doubles rather than test containers
This imposes design discipline — anything leaving the domain has to go through a port — but the end-to-end suite runs anywhere, with no database, no broker, and no identity provider.
Declaring the sample circuit in the form schema
The alternative, a list of types maintained in the backend, would have preserved exactly the coupling we were trying to remove. The success criterion was that a new form should no longer require a developer.
Writing a custom rendering engine on the field side
Rather than keeping the existing dependency. Roughly 4,400 lines to maintain, against full control of offline behaviour, per-step validation, and prefilling — precisely what real-world use depended on.
Extracting components before duplicating them
Three applications, one versioned and published library: a fix made once benefits all three, and visual drift never sets in.
Fixing a blocked environment in infrastructure-as-code
Bringing it back by hand would have been faster and would have undone itself at the next deployment.

The forms delivered

A forms engine is only worth what it is willing to carry. Four forms went into service, deliberately different in nature.

  • SARI — surveillance of severe acute respiratory infections

    A hospital circuit, with specimens attached and passed on to the laboratory and then the biobank.

  • CVACi — integrated community alert and surveillance cells

    Community-based surveillance is one of the pillars of health security: it picks up signals where they appear, before they reach a care facility. The circuit has two steps, from a community worker to a health professional who validates, with automatic routing into the validator's queue. A bidirectional bridge links these alerts to the corresponding clinical form, prefilled from the source.

  • Maternal and neonatal death notification

    A notification, not a clinical observation: transmission is final, which calls for an exhaustive summary before sending.

  • Diphtheria

    Published during the outbreak.

The first three were delivered while the platform was being built. They are what tested the engine's generality: a hospital circuit, a community circuit with two-stage validation, and an irreversible notification have almost nothing in common, and having to fit them into the same abstraction is what shaped the design.

The proof

The fourth demonstrates something else.

The whole platform rests on one promise: publishing a new form should no longer require a developer. As part of the response to a diphtheria outbreak, the corresponding surveillance form went into service through the administration console, with no code written and no backend redeployment.

That is the only validation that counts for a system of this kind. Until a form has been published under pressure, through the intended path, a decoupled architecture remains an intention.

The engagement ended at the close of the five-month contract, followed by a one-week return in September as part of the outbreak response.