Technical Architecture
Functional architecture
| Module | Role |
|---|---|
| Social registry (RSU) | Households, members, housing; import from data collection (Kobo, Survey Solutions, files) and pre-collection |
| Targeting and eligibility | PMT score per project, eligibility rules per programme |
| Beneficiaries | Enrolment, identity documents, life cycle |
| Programmes and transfers | Transfer cycles, payments, reconciliation with operators (ST/CT files) |
| Grievances (GRM) | Configurable workflow, escalation, sensitive SEA/SH cases, offline mode |
| Monitoring & evaluation | AWPB activities, indicators, risks, missions, reports |
| Community workers | Registry, activities, caregiver payments |
| Map | Geographic visualisation |
| Bemba assistant | Conversational assistant (Claude) with typed tools and a document base |
| Public website | Public pages, news, figures, online grievance form |
Technical architecture
Path of a request
- The browser calls
https://bemba.ucp-pch.org. Traefik terminates TLS (Let's Encrypt certificate). - The
/apiand/photospaths are routed topch_backend(port 8000), everything else topch_frontend(React build).mariacare.ucp-pch.organdsig.ucp-pch.orgredirect tobemba.ucp-pch.org. - Inside
pch_backend, Nginx hands over to PHP-FPM; Symfony queries PostgreSQL (and Redis for caching). - 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
| Technology | Version | Use |
|---|---|---|
| React | 18 | Interface |
| TypeScript | 5 | Typing |
| TanStack Query | 5 | Requests and client-side cache |
| Zustand | 4 | Global state |
| Tailwind CSS | 3 | Styles |
| React Router | 6 (HashRouter, /#/… URLs) | Navigation |
| i18next | 23 | French, Portuguese, English |
| Leaflet, Recharts | 1.9, 2 | Maps, charts |
| Sentry | 10 | Error reporting |
| Service worker | — | Asset cache and offline mode (grievances) |
Backend
| Technology | Version | Use |
|---|---|---|
| PHP | 8.3 (FPM, Alpine) | Language |
| Symfony | 7 | Framework, Messenger, Scheduler, Mailer |
| API Platform | 3 | Part of the REST resources |
| Doctrine ORM | 3 | Persistence, migrations |
| LexikJWT + Gesdinet | 2 / 1.5 | Access token (1 h) and refresh token |
| Sentry | 5 | Server errors |
Entity identifiers are Ramsey UUIDs (Ramsey\Uuid\UuidInterface).
Data and infrastructure
| Technology | Version | Use |
|---|---|---|
| PostgreSQL + PostGIS | 15 + 3.4 | pch_sig database |
| Redis | 7 | Cache |
| Traefik | 2.10 | Reverse proxy, HTTPS |
| Docker + Compose v2 | — | Containers (always -f) |
| Loki + Promtail | 2.9 | Logs, one-year retention |
Security at a glance
- Authentication: signed JWT (RS256), sent in an HttpOnly cookie or the
Authorizationheader; automatic refresh. See JWT authentication. - Authorisation:
module.actionpermissions 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