Skip to main content

Technical Architecture

Functional architecture​

Bemba functional architecture

ModuleRole
Social registry (RSU)Households, members, housing; import from data collection (Kobo, Survey Solutions, files) and pre-collection
Targeting and eligibilityPMT score per project, eligibility rules per programme
BeneficiariesEnrolment, identity documents, life cycle
Programmes and transfersTransfer cycles, payments, reconciliation with operators (ST/CT files)
Grievances (GRM)Configurable workflow, escalation, sensitive SEA/SH cases, offline mode
Monitoring & evaluationAWPB activities, indicators, risks, missions, reports
Community workersRegistry, activities, caregiver payments
MapGeographic visualisation
Bemba assistantConversational assistant (Claude) with typed tools and a document base
Public websitePublic pages, news, figures, online grievance form

Technical architecture​

Technical architecture

Path of a request​

  1. The browser calls https://bemba.ucp-pch.org. Traefik terminates TLS (Let's Encrypt certificate).
  2. The /api and /photos paths are routed to pch_backend (port 8000), everything else to pch_frontend (React build). mariacare.ucp-pch.org and sig.ucp-pch.org redirect to bemba.ucp-pch.org.
  3. Inside pch_backend, Nginx hands over to PHP-FPM; Symfony queries PostgreSQL (and Redis for caching).
  4. E-mails and long jobs are queued (Symfony Messenger, Doctrine transport) and consumed by the workers that supervisord runs in the same container. No e-mail is sent during a request.

Backend container processes​

supervisord starts Nginx, PHP-FPM, messenger:consume async (e-mails, jobs) and messenger:consume scheduler_plaintes scheduler_transferts (scheduled tasks: grievances, and transfer recurrence every day at 06:00). At start-up the entrypoint checks the JWT keys, waits for the database, runs pending migrations, warms the cache and gives var/ back to www-data.

Stack​

Frontend​

TechnologyVersionUse
React18Interface
TypeScript5Typing
TanStack Query5Requests and client-side cache
Zustand4Global state
Tailwind CSS3Styles
React Router6 (HashRouter, /#/… URLs)Navigation
i18next23French, Portuguese, English
Leaflet, Recharts1.9, 2Maps, charts
Sentry10Error reporting
Service worker—Asset cache and offline mode (grievances)

Backend​

TechnologyVersionUse
PHP8.3 (FPM, Alpine)Language
Symfony7Framework, Messenger, Scheduler, Mailer
API Platform3Part of the REST resources
Doctrine ORM3Persistence, migrations
LexikJWT + Gesdinet2 / 1.5Access token (1 h) and refresh token
Sentry5Server errors

Entity identifiers are Ramsey UUIDs (Ramsey\Uuid\UuidInterface).

Data and infrastructure​

TechnologyVersionUse
PostgreSQL + PostGIS15 + 3.4pch_sig database
Redis7Cache
Traefik2.10Reverse proxy, HTTPS
Docker + Compose v2—Containers (always -f)
Loki + Promtail2.9Logs, one-year retention

Security at a glance​

  • Authentication: signed JWT (RS256), sent in an HttpOnly cookie or the Authorization header; automatic refresh. See JWT authentication.
  • Authorisation: module.action permissions checked by a Symfony voter, plus data partitioning by institution and project (CloisonnementService), also checked object by object.
  • Responses: 401 without a session, 403 with a session but no right, 404 for a malformed identifier.
  • Browser: Content Security Policy (CSP) applied by the frontend Nginx; photos are only served through signed links valid for 12 hours.
  • Rate limiting on sign-in (account lockout), the assistant and the public site. See rate limiting.

Repository layout​

pch-sig/
├── backend/ # Symfony (src/, config/, migrations/, docker/, Dockerfile.prod)
├── frontend/ # React (src/pages, src/components, src/services, src/i18n)
├── documentation/ # This site (Docusaurus), diagrams (diagrams/), videos (videos/)
├── nginx/ # Frontend Nginx configuration (CSP)
├── monitoring/ # Grafana / Prometheus for the admin workstation
├── docs/ # Internal documents (HANDOVER, deployment, secrets, QA…)
├── docker-compose.prod.yml
└── docker-compose.preprod.yml