Files
la-voix-du-peuple/docs/DEX.md
T
billisdead fbc1fad8b9 P5 — Mode consultation ciblée (Option B, implémentation complète)
Backend :
- Nouvelle table `consultations` (slug unique, fenêtre temporelle, webhook, logo)
- `ideas.consultation_id` FK nullable (NULL = contexte global home)
- `synthesis.consultation_id` FK nullable (synthèse par contexte)
- Boucle auto-fermeture (thread daemon, 60 s) — ferme + webhook à l'échéance
- Webhook de clôture : POST JSON (synthèse + métadonnées) via urllib.request
- Routes publiques : GET/POST /api/consultations/<slug>, synthèse, contributions, export/print
- Routes admin : list, create, close (+ webhook), delete (cascade explicite)
- CSP ajustée sur /export/print pour autoriser window.print()

Frontend :
- Nouvelle page /consultation/:slug — formulaire, synthèse live, contributions paginées, PDF
- Admin panel : onglet Consultations — liste, formulaire création, fermeture, suppression

Docs : DAT.md v1.5, DEX.md v1.7 (section P5, tables, routes, webhook)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 10:00:39 +02:00

472 lines
16 KiB
Markdown

# Document d'Exploitation — La Voix du Peuple
**Version** : 1.7
**Date** : Mai 2026
---
## Historique des versions
| Version | Date | Modifications |
|---------|------|---------------|
| 1.0 | Avril 2026 | Version initiale |
| 1.1 | Avril 2026 | Ajout section flyer QR, export PDF, partage horodaté |
| 1.2 | Avril 2026 | Palette pétrol neutre, textes de posture sur l'expression vs. vérité |
| 1.3 | Avril 2026 | Dark mode pétrol, panneau d'accessibilité (dyslexie, contraste, zoom) |
| 1.4 | Avril 2026 | Synchronisation Gitea sécurisée — `GITEA_TOKEN` + `scripts/push-gitea.sh` |
| 1.5 | Avril 2026 | Droit pénal français intégré dans le filtre IA (16 sections) |
| 1.6 | Avril 2026 | Panel admin sécurisé (`/admin`), signalement public, export CSV |
| 1.7 | Mai 2026 | P5 — Mode consultation ciblée (table `consultations`, webhook, auto-fermeture, panel admin) |
---
## 1. Démarrage et arrêt des services
### En auto-hébergement (RockyLinux / Debian)
```bash
# Démarrer le backend
systemctl start voix-du-peuple-api
# Arrêter le backend
systemctl stop voix-du-peuple-api
# Redémarrer après mise à jour
systemctl restart voix-du-peuple-api
# Voir le statut
systemctl status voix-du-peuple-api
# Activer au démarrage
systemctl enable voix-du-peuple-api
```
```bash
# Redémarrer Nginx après modification de la config
nginx -t && systemctl reload nginx
```
---
## 2. Variables d'environnement
Fichier de référence : `.env.example` à la racine du projet.
En production, créer `/etc/voix-du-peuple/.env` :
```bash
# Base de données
DATABASE_URL=postgresql://user:password@localhost:5432/voix_du_peuple
# IA — choisir l'un des deux
MISTRAL_API_KEY=sk-...
# OPENAI_API_KEY=sk-...
# Modèles (optionnel — valeurs par défaut ci-dessous)
FILTER_MODEL=mistral-small-latest
SYNTHESIS_MODEL=mistral-large-latest
# Sécurité Flask
SESSION_SECRET=une-longue-chaine-aleatoire-securisee
```
> **Important** : Après toute modification du fichier `.env`, redémarrer le service API.
---
## 3. Mise à jour du code
```bash
# Depuis votre serveur, après un git pull
cd /opt/voix-du-peuple
git pull origin main
# Mettre à jour les dépendances Python si requirements.txt a changé
pip install -r artifacts/flask-api/requirements.txt
# Mettre à jour les dépendances Node si package.json a changé
pnpm install
# Reconstruire le frontend si le code frontend a changé
pnpm --filter @workspace/voix-du-peuple run build --config vite.config.selfhost.ts
# Redémarrer l'API
systemctl restart voix-du-peuple-api
```
---
## 4. Consultation des logs
### En auto-hébergement
```bash
# Logs du service systemd (temps réel)
journalctl -u voix-du-peuple-api -f
# 100 dernières lignes
journalctl -u voix-du-peuple-api -n 100
# Logs d'une journée spécifique
journalctl -u voix-du-peuple-api --since "2026-04-01" --until "2026-04-02"
# Logs Nginx
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
```
---
## 5. Base de données
### Connexion directe
```bash
psql "$DATABASE_URL"
# ou
psql -U voix_user -d voix_du_peuple -h localhost
```
### Requêtes utiles
```sql
-- Nombre de contributions par statut
SELECT accepted, COUNT(*) FROM ideas GROUP BY accepted;
-- Dernières contributions
SELECT id, author, left(content, 60), accepted, created_at
FROM ideas ORDER BY created_at DESC LIMIT 10;
-- Contributions refusées avec motif
SELECT id, left(content, 60), rejection_reason, created_at
FROM ideas WHERE accepted = false ORDER BY created_at DESC;
-- État de la synthèse
SELECT idea_count, left(text, 200), updated_at FROM synthesis;
```
### Sauvegarde
```bash
# Dump complet
pg_dump "$DATABASE_URL" > backup_$(date +%Y%m%d_%H%M).sql
# Restauration
psql "$DATABASE_URL" < backup_20260401_0800.sql
```
---
## 6. Vérification du bon fonctionnement
```bash
# Health check API
curl http://localhost:8080/health
# Test de soumission
curl -X POST http://localhost:8080/api/ideas \
-H "Content-Type: application/json" \
-d '{"content": "Test de fonctionnement de la plateforme.", "author": "Admin"}'
# Lecture de la synthèse
curl http://localhost:8080/api/synthesis | python3 -m json.tool
# Statistiques
curl http://localhost:8080/api/ideas/stats
```
Réponse attendue du health check :
```json
{"status": "ok"}
```
---
## 7. Changement de modèle IA
Pour basculer vers un modèle différent sans toucher au code :
```bash
# Exemple : passer à mistral-medium
# Éditer le .env
FILTER_MODEL=mistral-medium-latest
SYNTHESIS_MODEL=mistral-medium-latest
# Redémarrer
systemctl restart voix-du-peuple-api
```
Modèles Mistral disponibles (avril 2026) :
- `mistral-small-latest` — Rapide, économique (filtre)
- `mistral-medium-latest` — Équilibré
- `mistral-large-latest` — Meilleure qualité (synthèse)
---
## 8. Gestion du flyer QR code
La page `/flyer` génère un flyer A4 imprimable avec un QR code.
### Changer l'URL de destination
**Méthode 1 — Interface (sans code)** : ouvrir `/flyer`, modifier le champ "Destination du QR code", cliquer "Appliquer", puis imprimer.
**Méthode 2 — Paramètre URL** : ajouter `?url=` à l'adresse de la page, ex. :
```
https://lavoixdupeuple.fr/flyer?url=https://autresite.fr
```
**Méthode 3 — Modification permanente dans le code** : éditer la ligne 10 de `artifacts/voix-du-peuple/src/pages/flyer.tsx` :
```ts
const DEFAULT_QR_URL = "https://lavoixdupeuple.fr";
```
Reconstruire ensuite le frontend (`pnpm build`).
### Imprimer / exporter en PDF
Sur la page `/flyer`, cliquer **Imprimer / Exporter PDF**. La barre de configuration disparaît automatiquement à l'impression, seul le flyer A4 est produit. Pour obtenir un PDF, choisir "Enregistrer en PDF" dans la boîte de dialogue du navigateur.
---
## 9. Export de la synthèse
Deux boutons sont disponibles dans l'en-tête de la colonne de synthèse (page principale) :
### Partager / Copier
- Sur mobile (navigateurs supportant Web Share) : ouvre le menu de partage natif du système
- Sur bureau : copie le texte dans le presse-papier avec horodatage et compteur de contributions ; un message de confirmation apparaît
### PDF
Génère et ouvre une page HTML mise en page (tricolore, titre, date, texte de synthèse, pied de page) puis déclenche l'impression. Choisir "Enregistrer en PDF" dans la boîte de dialogue pour obtenir un fichier.
> Ces deux fonctions s'exécutent entièrement côté client — aucune donnée n'est envoyée au serveur.
---
## 10. Purge des données
```sql
-- Supprimer toutes les contributions (irréversible)
TRUNCATE ideas;
TRUNCATE synthesis;
-- Supprimer uniquement les contributions refusées
DELETE FROM ideas WHERE accepted = false;
-- Forcer une re-synthèse (supprimer le cache)
DELETE FROM synthesis;
```
Après une purge, la synthèse se régénère automatiquement à la prochaine contribution acceptée.
---
## 11. Dépannage courant
| Symptôme | Cause probable | Solution |
|----------|---------------|----------|
| `Service temporairement indisponible` sur soumission | Clé Mistral invalide ou quota dépassé | Vérifier `MISTRAL_API_KEY`, consulter console.mistral.ai |
| Synthèse non mise à jour | Erreur silencieuse en arrière-plan | Consulter les logs, vérifier la connexion à l'API IA |
| Erreur 500 sur toutes les routes | `DATABASE_URL` incorrect ou PostgreSQL arrêté | `systemctl status postgresql`, vérifier le `.env` |
| Frontend vide (page blanche) | Build manquant ou `BASE_URL` incorrect | Relancer le build Vite, vérifier la config Nginx |
| Rate limit atteint (429) | Trop de soumissions depuis la même IP | Normal — attendre 1 minute |
| Modèle IA introuvable (404) | Nom de modèle incorrect dans `FILTER_MODEL` | Corriger le nom, redémarrer le service |
| QR code ne redirige pas | URL incorrecte dans le champ ou la constante | Vérifier `DEFAULT_QR_URL` dans `flyer.tsx`, ou passer `?url=` en paramètre |
| Bouton "Copier" sans retour visuel | Navigateur sans accès au presse-papier (HTTP non sécurisé) | Servir le site en HTTPS |
| Fenêtre PDF bloquée | Bloqueur de popups actif | Autoriser les popups pour ce site dans le navigateur |
---
## 12. Accessibilité — fonctionnement et personnalisation
Le panneau d'accessibilité est accessible via l'icône dans la barre de navigation (à gauche du drapeau). Quatre options sont disponibles :
| Option | Effet | Classe CSS sur `<html>` | Clé `localStorage` |
|--------|-------|------------------------|---------------------|
| Mode sombre | Fond sombre, pétrol clair | `.dark` | `a11y-dark` |
| Police dyslexie | Arial/Verdana, espacement élargi, interligne 2 | `.dyslexia` | `a11y-dyslexia` |
| Contraste élevé | Fond blanc/noir pur, contrastes WCAG AA+ | `.high-contrast` | `a11y-contrast` |
| Texte agrandi | +20 % sur tous les textes | `.large-text` | `a11y-large` |
Les préférences sont stockées dans le navigateur (localStorage) et relues automatiquement à chaque visite.
**Pour désactiver une option par code** (au déploiement, si souhaité), supprimer le `ToggleRow` correspondant dans `src/components/accessibility-panel.tsx`.
**Fichiers impliqués** :
- `src/hooks/use-accessibility.tsx` — contexte React, état, persistance
- `src/components/accessibility-panel.tsx` — interface utilisateur
- `src/index.css` — section `/* ─── Accessibilité */` — toutes les classes CSS
- `src/App.tsx``<AccessibilityProvider>`, skip-link, `id="main-content"`
---
## 13. Modifier les textes de posture
Les phrases de positionnement éditorial ("expression citoyenne, pas vérité établie", "auteur attaché à l'expertise") sont définies directement dans le code des composants React. Pour les modifier :
| Emplacement | Fichier |
|-------------|---------|
| Bandeau d'intro | `artifacts/voix-du-peuple/src/pages/home.tsx` — bloc `Bandeau d'introduction` |
| Pied de synthèse | `artifacts/voix-du-peuple/src/pages/home.tsx` — bloc `Pied de page fixe` |
| Section "Expression, pas vérité" | `artifacts/voix-du-peuple/src/pages/about.tsx` — section `MessageSquare` |
| Encart limites | `artifacts/voix-du-peuple/src/pages/transparence.tsx` — bloc `Ce que cette plateforme n'est pas` |
Après modification, reconstruire le frontend si en production (`pnpm build`), ou laisser le hot-reload agir en développement.
---
## 14. Modifier la palette de couleurs
La couleur principale est définie dans `artifacts/voix-du-peuple/src/index.css`, ligne `--primary`. La valeur actuelle est `185 42% 28%` (pétrol foncé, politiquement neutre).
```css
/* Pour changer la couleur principale */
--primary: 185 42% 28%; /* pétrol actuel */
/* Exemples alternatifs neutres :
210 20% 30% → ardoise bleue
155 35% 28% → vert forêt
270 30% 35% → violet institutionnel
*/
```
Toutes les occurrences de `--primary` dans le fichier CSS s'appliquent automatiquement à l'ensemble de l'interface. Pas besoin de modifier les composants.
---
## 15. Push vers Gitea
```bash
git push
```
> **Note** : le script `scripts/push-gitea.sh` permet un push avec token HTTP si besoin (`GITEA_TOKEN`). Pour Git 2.50+, préférer `git remote set-url` avec un token en variable d'environnement.
Pour régénérer un token Gitea : **Paramètres du compte → Applications → Générer un token**
Permissions requises : `repository` (lecture + écriture).
---
## 16. Panel d'administration
### Accès
Le panel admin est accessible à l'URL `/admin` — il n'est **pas lié dans la navigation publique**. Seul l'administrateur connaît son existence.
```
https://votredomaine.fr/admin
```
### Authentification
À l'ouverture, un formulaire de connexion demande le mot de passe. Celui-ci est la valeur de la variable `ADMIN_SECRET` configurée dans les secrets de l'environnement. La session est stockée dans `sessionStorage` du navigateur — elle est perdue à la fermeture de l'onglet.
| Variable | Où la définir |
|----------|---------------|
| `ADMIN_SECRET` | `.env` ou variable d'environnement système |
### Fonctionnalités
| Fonctionnalité | Description |
|----------------|-------------|
| **Tableau de bord** | Compteurs : total, acceptées, rejetées, signalées |
| **Liste filtrée** | Onglets : Toutes · Acceptées · Rejetées · Signalées |
| **Recherche** | Filtrage textuel en temps réel sur le contenu |
| **Suppression** | Suppression unitaire avec confirmation, puis régénération automatique de la synthèse |
| **Suppression en masse** | Sélection multiple → suppression groupée → synthèse régénérée |
| **Override IA** | Forcer l'acceptation ou le rejet d'une contribution, avec motif |
| **Note admin** | Annotation interne sur une contribution (invisible du public) |
| **Retrait de signalement** | Marquer un signalement comme traité |
| **Régénération manuelle** | Forcer la régénération complète de la synthèse |
| **Export CSV** | Télécharger toutes les contributions (UTF-8 BOM, compatible Excel) |
### Signalement public
Sur la page d'accueil, chaque contribution affiche un bouton **Signaler** au survol (icône drapeau). Ce bouton est limité à 3 signalements par minute et 10 par heure par IP. Les contributions signalées apparaissent en premier dans l'onglet **Signalées** du panel admin.
### Sécurité
- Toutes les routes `/api/admin/*` vérifient le header `Authorization: Bearer <ADMIN_SECRET>`
- La route de login est limitée à 10 tentatives par minute (protection brute-force)
- Aucune session persistante côté serveur — pas de cookie, pas de base de sessions
- L'URL `/admin` n'est pas mentionnée dans le code source public ni dans le `robots.txt`
### Routes API admin
| Méthode | Route | Action |
|---------|-------|--------|
| `POST` | `/api/admin/login` | Vérification du mot de passe |
| `GET` | `/api/admin/stats` | Statistiques détaillées |
| `GET` | `/api/admin/ideas` | Liste avec filtres (`status`, `page`, `q`) |
| `DELETE` | `/api/admin/ideas/<id>` | Suppression unitaire |
| `POST` | `/api/admin/ideas/bulk-delete` | Suppression en masse (`{ids: [...]}`) |
| `POST` | `/api/admin/ideas/<id>/override` | Override IA (`{accepted, reason, note}`) |
| `POST` | `/api/admin/ideas/<id>/unflag` | Retirer le signalement |
| `POST` | `/api/admin/synthesis/regenerate` | Forcer la régénération |
| `GET` | `/api/admin/export/csv` | Export CSV (toutes les contributions) |
| `GET` | `/api/admin/consultations` | Liste toutes les consultations avec stats |
| `POST` | `/api/admin/consultations` | Crée une consultation (`{slug, title, subject, ...}`) |
| `POST` | `/api/admin/consultations/<slug>/close` | Ferme manuellement + déclenche webhook |
| `DELETE` | `/api/admin/consultations/<slug>` | Supprime une consultation et ses données |
---
## 17. Mode consultation ciblée
### Créer une consultation (panel admin)
1. Ouvrir `/admin` → onglet **Consultations****Nouvelle consultation**
2. Remplir : slug (URL-safe, ex. `budget-mairie-2026`), titre, sujet
3. Optionnel : message d'introduction, organisateur, logo HTTPS, dates, webhook
4. Les dates sont interprétées comme UTC
5. La consultation est immédiatement accessible sur `/consultation/<slug>`
### Fermeture automatique
La fermeture automatique est gérée par un thread arrière-plan (toutes les 60 secondes).
Si `ends_at` est défini et dépassé, la consultation est fermée automatiquement et le webhook déclenché.
```bash
# Vérifier les consultations actives
psql "$DATABASE_URL" -c "SELECT slug, title, starts_at, ends_at, closed_at FROM consultations;"
```
### Webhook de clôture
Si `webhook_url` est configuré, un POST JSON est envoyé à la clôture :
```json
{
"event": "consultation_closed",
"consultation": {
"slug": "budget-mairie-2026",
"title": "Budget participatif 2026",
"closedAt": "2026-06-30T18:00:00+00:00"
},
"synthesis": {
"text": "...",
"ideaCount": 42
}
}
```
### Export PDF d'une consultation
```
GET /api/consultations/<slug>/export/print
```
Retourne une page HTML auto-imprimante. Accessible depuis le bouton **Exporter PDF** sur la page de consultation, ou directement en navigateur.
---
## 18. Contacts et ressources
- Documentation Mistral : https://docs.mistral.ai
- PostgreSQL : https://www.postgresql.org/docs/
- Flask : https://flask.palletsprojects.com
- qrcode.react : https://github.com/zpao/qrcode.react
- Architecture : `docs/DAT.md`