# Document d'Architecture Technique — La Voix du Peuple **Version** : 1.0 **Date** : Avril 2026 **Statut** : En production (Replit), prêt pour auto-hébergement --- ## 1. Présentation générale **La Voix du Peuple** est une plateforme civique permettant à des citoyens de soumettre des propositions politiques. Ces contributions sont : 1. Filtrées par un agent IA selon le droit international des droits humains 2. Synthétisées automatiquement en un résumé clair par thème 3. Affichées en temps réel à destination d'élus ou de décideurs --- ## 2. Architecture globale ``` ┌─────────────────────────────────────────────────────────┐ │ Navigateur client │ │ React + Vite (TypeScript) │ └────────────────────────┬────────────────────────────────┘ │ HTTP/REST (JSON) ▼ ┌─────────────────────────────────────────────────────────┐ │ Reverse Proxy — Nginx / HAProxy │ │ (rate limiting, TLS, X-Forwarded-For) │ └────────────────────────┬────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ Backend API — Flask (Python) │ │ Port 8080 │ │ │ │ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │ │ │ app.py │ │ ai_agent.py │ │ database.py │ │ │ │ (routes) │──▶│ (filtre + │ │ (PostgreSQL) │ │ │ │ │ │ synthèse) │ │ │ │ │ └──────────────┘ └──────┬──────┘ └──────┬───────┘ │ └─────────────────────────────┼───────────────┼──────────┘ │ │ ▼ ▼ ┌──────────────────┐ ┌──────────────┐ │ Mistral AI API │ │ PostgreSQL │ │ api.mistral.ai │ │ (base DB) │ └──────────────────┘ └──────────────┘ ``` --- ## 3. Composants ### 3.1 Frontend — React + Vite | Élément | Détail | |---------|--------| | Framework | React 18 + TypeScript | | Build | Vite 7 | | Styles | Tailwind CSS + shadcn/ui | | Routing | Wouter | | État serveur | TanStack Query | | Client API | `@workspace/api-client-react` (généré depuis OpenAPI) | | Police | Bahnschrift (titres), Inter (corps) | **Pages** : - `/` — Page principale (formulaire + fil + synthèse) - `/about` — À propos et fondements juridiques - `/transparence` — Fonctionnement technique et données collectées **Variables d'environnement** : - `BASE_URL` — Préfixe de chemin (injecté par Vite) - `PORT` — Port du serveur de développement (assigné par Replit) --- ### 3.2 Backend — Flask | Élément | Détail | |---------|--------| | Framework | Flask 3 (Python 3.11+) | | Serveur WSGI | Gunicorn (production) | | CORS | flask-cors | | Rate limiting | flask-limiter (5 req/min par IP sur POST /api/ideas) | | ORM | psycopg2-binary (requêtes SQL directes) | **Endpoints** : | Méthode | Route | Description | |---------|-------|-------------| | `GET` | `/api/ideas` | Liste des contributions acceptées | | `POST` | `/api/ideas` | Soumet une nouvelle contribution | | `GET` | `/api/ideas/stats` | Statistiques (acceptées/refusées) | | `GET` | `/api/synthesis` | Texte de synthèse actuel | | `GET` | `/health` | Health check | **Réponses** : JSON camelCase, statuts HTTP standard. --- ### 3.3 Agent IA Deux appels distincts à l'API Mistral (compatible OpenAI SDK) : #### Filtre de modération - **Modèle** : `mistral-small-latest` (configurable via `FILTER_MODEL`) - **Entrée** : Texte brut de la contribution - **Sortie** : JSON `{"accepted": bool, "reason"?: string, "legal_basis"?: string}` - **Référentiel** : DUDH, PIDCP, CEDH, Charte UE, CERD, Statut de Rome - **Max tokens** : 300 #### Synthèse collective - **Modèle** : `mistral-large-latest` (configurable via `SYNTHESIS_MODEL`) - **Entrée** : Liste de toutes les contributions acceptées - **Sortie** : Texte libre structuré par thèmes, destiné aux élus - **Max tokens** : 1200 - **Déclencheur** : À chaque nouvelle contribution acceptée (asynchrone) **Priorité de configuration du client IA** : 1. `MISTRAL_API_KEY` → `https://api.mistral.ai/v1` 2. `OPENAI_API_KEY` → API OpenAI standard 3. `AI_INTEGRATIONS_OPENAI_*` → Proxy Replit (intégration native) --- ### 3.4 Base de données — PostgreSQL **Table `ideas`** : | Colonne | Type | Description | |---------|------|-------------| | `id` | SERIAL PK | Identifiant | | `content` | TEXT | Texte de la contribution | | `author` | VARCHAR(100) | Pseudonyme (nullable) | | `accepted` | BOOLEAN | Résultat du filtre | | `rejection_reason` | TEXT | Motif de refus (nullable) | | `legal_basis` | TEXT | Base légale du refus (nullable) | | `created_at` | TIMESTAMPTZ | Horodatage de soumission | **Table `synthesis`** : | Colonne | Type | Description | |---------|------|-------------| | `id` | SERIAL PK | (toujours 1 — ligne unique) | | `text` | TEXT | Dernier texte de synthèse | | `idea_count` | INTEGER | Nombre de contributions intégrées | | `updated_at` | TIMESTAMPTZ | Dernière mise à jour | --- ## 4. Variables d'environnement | Variable | Obligatoire | Description | |----------|-------------|-------------| | `DATABASE_URL` | ✅ | URL PostgreSQL complète | | `MISTRAL_API_KEY` | ✅ (ou OpenAI) | Clé API Mistral | | `SESSION_SECRET` | ✅ | Secret Flask (sessions) | | `FILTER_MODEL` | — | Modèle de filtrage (défaut : `mistral-small-latest`) | | `SYNTHESIS_MODEL` | — | Modèle de synthèse (défaut : `mistral-large-latest`) | | `OPENAI_API_KEY` | — | Alternative à Mistral | | `MISTRAL_BASE_URL` | — | URL custom Mistral (défaut : `https://api.mistral.ai/v1`) | --- ## 5. Infrastructure de déploiement (auto-hébergement) ``` Internet ──▶ HAProxy (TLS, load balancing) └──▶ Nginx (reverse proxy, rate limiting IP) └──▶ Gunicorn × N workers (Flask) └──▶ Fichiers statiques Vite (build) │ PostgreSQL (local ou RDS) ``` **Fichiers fournis** : - `deploy/nginx.conf` — Configuration Nginx avec HAProxy support - `deploy/voix-du-peuple-api.service` — Unité systemd pour Gunicorn - `artifacts/voix-du-peuple/vite.config.selfhost.ts` — Build sans plugins Replit --- ## 6. Sécurité | Mesure | Implémentation | |--------|----------------| | Rate limiting | 5 POST/min par IP (flask-limiter) | | Sanitisation | bleach sur le contenu avant stockage | | CORS | Origines configurées explicitement | | SQL injection | Requêtes paramétrées (psycopg2) | | Secrets | Variables d'environnement uniquement, jamais dans le code | | Données personnelles | Aucune IP stockée, pseudonyme facultatif | --- ## 7. Flux de traitement d'une contribution ``` POST /api/ideas │ ├─ Validation (longueur 10–1000 chars) ├─ Sanitisation (bleach) ├─ INSERT en base (statut pending) │ ├─ Appel Mistral (filtre) │ ├─ accepted=true → UPDATE idea, déclenche synthèse async │ └─ accepted=false → UPDATE idea avec motif │ ├─ [async] Appel Mistral (synthèse) sur toutes contributions acceptées │ └─ UPSERT table synthesis │ └─ Réponse JSON 201 ```