2026-04-02 02:36:40 +00:00
2026-05-23 18:05:46 +02:00
2026-04-02 02:36:40 +00:00
2026-04-02 02:36:40 +00:00
2026-04-02 02:36:40 +00:00

La Voix du Peuple

Plateforme civique de participation démocratique assistée par intelligence artificielle.

Les citoyens soumettent des propositions politiques en texte libre. Chaque contribution est filtrée automatiquement selon le droit international des droits humains et le droit pénal français, puis intégrée à une synthèse thématique collective générée par IA. La plateforme fonctionne en mode ouvert (contributions globales) ou en mode consultation ciblée (sujet défini, dates de début et de fin, organisateur identifié). Elle ne produit pas un consensus : elle donne à voir ce que des citoyens ont choisi d'exprimer, tel quel.

La Voix du Peuple est un projet personnel open-source. Si vous l'utilisez et voulez soutenir son développement, un don est toujours bienvenu — mais une étoile sur le dépôt et des retours concrets comptent autant.

  • Ko-fi : [LIEN_KO_FI]
  • GitHub Sponsors : [LIEN_GITHUB_SPONSORS]

Sommaire

  1. Fonctionnalités
  2. Architecture
  3. Stack technique
  4. Installation et développement local
  5. Déploiement en production
  6. Contribuer
  7. Licence

Fonctionnalités

Contribution citoyenne

  • Formulaire texte libre (10 à 1 000 caractères), pseudonyme optionnel
  • Filtrage IA automatique à la soumission selon quinze textes de référence : DUDH, PIDCP, CEDH, Code pénal français, Loi du 29 juillet 1881, LCEN, SREN 2024, RGPD, et autres
  • Injection dynamique des textes légaux consolidés depuis l'API officielle Légifrance (PISTE) — le modèle travaille sur la version du jour, pas sur sa mémoire d'entraînement
  • Motif de rejet et base légale retournés au contributeur si la contribution est refusée
  • Dialogue de recueil du consentement RGPD avant soumission

Synthèse collective

  • Générée automatiquement à chaque nouvelle contribution acceptée (arrière-plan, non bloquant)
  • Regroupement thématique, ton neutre et factuel, sans formule solennelle
  • Régénération manuelle possible depuis le panel admin

Consultations ciblées

  • Création via le panel admin : slug, titre, sujet, message d'introduction, organisateur, logo, dates d'ouverture et de clôture
  • Fermeture automatique à l'échéance ou manuelle depuis l'admin
  • Webhook de clôture configurable (envoi non bloquant en arrière-plan)
  • Page publique /consultation/:slug avec formulaire de soumission, synthèse et contributions paginées
  • Index public des consultations actives et clôturées (/consultations)

Exports

  • Contributions globales : JSON et CSV (routes publiques)
  • Synthèse de consultation : rendu HTML pour impression ou export PDF navigateur
  • Export CSV complet (admin uniquement, tous champs)

Panel d'administration (protégé par ADMIN_SECRET)

  • Liste des contributions avec filtres (toutes / acceptées / rejetées / signalées), pagination, recherche plein texte
  • Suppression unitaire et en masse, override de statut (accepté/refusé), gestion des signalements
  • Création, clôture et suppression de consultations

Interface publique complémentaire

  • Page des contributions brutes paginées (/contributions-brutes)
  • Signalement de contributions par les visiteurs (limité à 3/min, 10/h par IP)
  • Flyer QR code imprimable configuré sur l'URL du déploiement (/flyer)
  • Partage de la synthèse (copie presse-papier ou partage natif mobile)
  • Pages légales : mentions légales, politique de confidentialité

Accessibilité

  • Dark mode, police dyslexie, contraste élevé, texte agrandi
  • Navigation clavier complète, lien d'évitement pour lecteurs d'écran
  • Préférences persistées dans localStorage

Sécurité et anti-abus

  • Honeypot anti-bot (champ caché côté serveur)
  • hCaptcha optionnel (activé via variables d'environnement)
  • Cooldown cookie signé HMAC-SHA256 entre deux soumissions
  • Rate limiting par IP ou fingerprint (flask-limiter, Redis recommandé en production)
  • Détection de flood (alerte log si dépassement du seuil sur 5 minutes)
  • Fingerprinting non-PII : identifiant FingerprintJS hashé SHA-256, sans cookie tiers
  • En-têtes de sécurité HTTP sur toutes les réponses (CSP, X-Frame-Options, etc.)
  • Sanitisation XSS systématique des entrées (bleach)

Architecture

Vue d'ensemble

                         RÉSEAU EXTERNE (Internet)
                                   │
                                   ▼
                        ┌─────────────────────┐
                        │       HAProxy        │
                        │  (réseau séparé)     │
                        │  - Terminaison TLS   │
                        │  - Load balancing    │
                        │  - ACL / filtrage IP │
                        └──────────┬──────────┘
                                   │  HTTP (port 80, réseau interne)
                                   ▼
                        ┌─────────────────────┐
                        │        Nginx         │
                        │  (reverse proxy)     │
                        │  - Routage /api/*    │
                        │  - Serve SPA React   │
                        │  - Cache statiques   │
                        └────────┬────────────┘
                                 │
               ┌─────────────────┴──────────────────┐
               │                                     │
               ▼                                     ▼
   ┌───────────────────────┐           ┌───────────────────────┐
   │   Gunicorn + Flask    │           │   Fichiers statiques   │
   │   (127.0.0.1:8000)   │           │   React SPA (dist/)    │
   │   - API REST /api/*   │           │   (servis par Nginx)   │
   │   - Filtrage IA       │           └───────────────────────┘
   │   - Rate limiting     │
   └──────────┬────────────┘
              │
    ┌─────────┴──────────┐
    │                    │
    ▼                    ▼
┌──────────┐     ┌───────────────────┐
│PostgreSQL│     │  Mistral AI API   │
│(local)   │     │  (externe, UE)    │
│- ideas   │     │  mistral-small    │
│- synth.  │     │  mistral-large    │
│- consult.│     │  (ou OpenAI-compat│
│- consents│     │   si configuré)   │
│- ip_abuse│     └───────────────────┘
└──────────┘

HAProxy

Propriété Valeur
Rôle Point d'entrée unique, terminaison TLS, load balancing
Réseau DMZ / réseau séparé dédié
Protocoles entrants HTTPS/443, HTTP/80 (redirect)
Protocoles sortants HTTP/80 vers Nginx (réseau interne)
TLS Terminaison SSL/TLS sur HAProxy, communication interne en HTTP clair

Configuration de référence :

frontend voix_du_peuple_https
    bind *:443 ssl crt /etc/ssl/certs/voix-du-peuple.pem
    mode http
    option forwardfor
    http-request set-header X-Forwarded-Proto https
    default_backend voix_du_peuple_backend

frontend voix_du_peuple_http
    bind *:80
    mode http
    redirect scheme https code 301 if !{ ssl_fc }

backend voix_du_peuple_backend
    mode http
    balance roundrobin
    option httpchk GET /api/healthz
    http-check expect status 200
    server app01 <IP_NGINX>:80 check inter 10s

Nginx

Propriété Valeur
Rôle Reverse proxy applicatif, service des fichiers statiques
Écoute 0.0.0.0:80 (réseau interne uniquement)
Vers Flask 127.0.0.1:8000 pour toute requête /api/*
Vers SPA dist/public/ pour tout le reste (try_files)

Nginx doit faire confiance à l'en-tête X-Forwarded-For positionné par HAProxy pour que Flask voie la vraie IP cliente (rate limiting par IP) :

set_real_ip_from  <IP_HAPROXY>;
real_ip_header    X-Forwarded-For;
real_ip_recursive on;

Gunicorn + Flask (Backend API)

Propriété Valeur
Serveur WSGI Gunicorn 23+
Workers 4 (synchrones, ajustable)
Bind 127.0.0.1:8000 (non exposé directement)
Framework Flask 3.1+
Python 3.11+

Routes exposées :

Méthode Route Description Auth
GET /api/healthz Santé du service
GET /api/ideas Idées globales acceptées
POST /api/ideas Soumettre une idée globale
GET /api/ideas/stats Statistiques globales (admin)
GET /api/synthesis Synthèse collective globale
POST /api/consent Enregistrer le consentement RGPD
GET /api/stats/public Statistiques publiques globales
GET /api/contributions Contributions paginées (vue publique)
GET /api/contributions/export/json Export JSON contributions globales
GET /api/contributions/export/csv Export CSV contributions globales
POST /api/ideas/:id/flag Signaler une contribution
POST /api/check-law Vérifier si une proposition est couverte par la loi
GET /api/consultations Liste des consultations
GET /api/consultations/:slug Détails d'une consultation
GET /api/consultations/:slug/synthesis Synthèse d'une consultation
GET /api/consultations/:slug/contributions Contributions paginées d'une consultation
POST /api/consultations/:slug/ideas Soumettre une idée dans une consultation
GET /api/consultations/:slug/export/print Rendu HTML pour impression
POST /api/admin/login Authentification admin
GET /api/admin/stats Statistiques détaillées Admin
GET /api/admin/ideas Liste toutes les contributions (filtres, pagination) Admin
DELETE /api/admin/ideas/:id Supprimer une contribution Admin
POST /api/admin/ideas/bulk-delete Suppression en masse Admin
POST /api/admin/ideas/:id/override Modifier le statut d'une contribution Admin
POST /api/admin/ideas/:id/unflag Retirer un signalement Admin
POST /api/admin/synthesis/regenerate Régénérer la synthèse globale Admin
GET /api/admin/export/csv Export CSV complet (tous champs) Admin
GET /api/admin/consultations Liste toutes les consultations Admin
POST /api/admin/consultations Créer une consultation Admin
POST /api/admin/consultations/:slug/close Fermer une consultation Admin
DELETE /api/admin/consultations/:slug Supprimer une consultation Admin
GET /api/admin/ip-blacklist Liste des IPs blacklistées Admin
DELETE /api/admin/ip-blacklist/:hash Lever un blacklist IP Admin

Sécurité applicative :

  • Rate limiting par IP réelle ou fingerprint FingerprintJS (via flask-limiter)
  • Assainissement XSS : bleach.clean() sur toutes les entrées
  • Requêtes SQL paramétrées (psycopg2) — pas de concaténation de chaînes
  • En-têtes HTTP de sécurité sur toutes les réponses
  • Aucun secret exposé dans les messages d'erreur

Agent IA

Trois agents distincts, tous via le client OpenAI-compatible (Mistral AI par défaut).

Agent de filtrage

Propriété Valeur
Modèle par défaut mistral-small-latest
Variable d'env FILTER_MODEL
Tokens max 300
Format de sortie JSON strict (response_format: json_object)
Déclenchement À chaque soumission d'idée

Logique de décision :

  1. Récupération des textes légaux consolidés depuis Légifrance (PISTE) — cache 24 h, fallback statique si API indisponible
  2. Appel API avec le prompt légal complet (16 sources + textes injectés)
  3. Parse JSON {"accepted": bool, "reason"?: str, "legal_basis"?: str}
  4. Si l'API retourne une erreur de filtre de contenu → rejet automatique avec citation DUDH/PIDCP/CEDH
  5. Résultat persisté en base avec l'idée

Agent de synthèse

Propriété Valeur
Modèle par défaut mistral-large-latest
Variable d'env SYNTHESIS_MODEL
Tokens max 1200
Déclenchement En arrière-plan après chaque acceptation, ou à la suppression

La synthèse est non-bloquante : l'API répond immédiatement à l'utilisateur, la synthèse s'effectue dans un thread daemon.

Agent de vérification légale

Propriété Valeur
Modèle même que FILTER_MODEL
Tokens max 200
Déclenchement À la demande via POST /api/check-law
Rôle Informer le contributeur si sa proposition est déjà couverte par une loi — non bloquant

Base légale du filtre (16 sources)

# Texte
1 Déclaration universelle des droits de l'homme (DUDH, ONU 1948)
2 Pacte international relatif aux droits civils et politiques (PIDCP, ONU 1966)
3 Convention européenne des droits de l'homme (CEDH, 1950)
4 Charte des droits fondamentaux de l'UE (2000/2009)
5 Convention pour la prévention du génocide (ONU 1948)
6 Statut de Rome / CPI (1998)
7 Convention sur la discrimination raciale (CERD, ONU 1965)
8-13 Code pénal français — Livres I à VI
14 Loi sur la liberté de la presse du 29 juillet 1881
15 LCEN (2004) · Loi SREN (2024) · RGPD · Code de la santé publique
16 Principes constitutionnels (CC 94-343/344 DC) · Code civil Art. 9, 1240

Intégration Légifrance (PISTE)

Les textes des articles les plus sensibles sont récupérés à chaque filtrage depuis l'API officielle Légifrance (PISTE) et injectés dans le prompt avant les critères d'analyse. Le modèle travaille ainsi sur les textes consolidés du jour.

Articles surveillés (vérifiés et mis à jour via scripts/check_legal_refs.py) :

Clé Référence Description Priorité
CP-225-1 Code pénal Art. 225-1 Discrimination Critique
L1881-24 Loi 1881 Art. 24 Provocation à la haine Critique
CP-222-33 Code pénal Art. 222-33 Harcèlement sexuel Haute
CP-222-33-2-2 Code pénal Art. 222-33-2-2 Cyberharcèlement en meute Haute
CP-421-2-5 Code pénal Art. 421-2-5 Apologie du terrorisme Haute
CP-211-1 Code pénal Art. 211-1 Génocide Haute
L1881-24bis Loi 1881 Art. 24 bis Négationnisme Haute
CP-226-4-1 Code pénal Art. 226-4-1 Usurpation d'identité numérique Haute
CP-212-1 Code pénal Art. 212-1 Crimes contre l'humanité Moyenne
CP-223-13 Code pénal Art. 223-13 Provocation au suicide Moyenne
L1881-29 Loi 1881 Art. 29 Diffamation Moyenne

Les articles de priorité critique et haute sont injectés dans le prompt. Les articles de priorité moyenne sont suivis pour détecter les changements législatifs mais ne sont pas injectés (le cadre légal statique les couvre déjà).

Le script scripts/check_legal_refs.py --check compare les textes courants avec la baseline stockée dans artifacts/flask-api/legal_refs.yaml. Exit code 2 si des modifications sont détectées — intégrable dans un cron ou une pipeline CI.

PostgreSQL

Propriété Valeur
Version 15+
Accès Localhost uniquement (127.0.0.1)
Connexion Flask Via DATABASE_URL (psycopg2-binary)
Authentification scram-sha-256
Migrations Incrémentales et idempotentes — appliquées à chaque démarrage

Schéma de la base de données :

CREATE TABLE consultations (
    id                  SERIAL PRIMARY KEY,
    slug                VARCHAR(100) UNIQUE NOT NULL,
    title               VARCHAR(200) NOT NULL,
    subject             TEXT NOT NULL,
    intro_message       TEXT,
    organizer_name      VARCHAR(200),
    organizer_logo_url  TEXT,
    starts_at           TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    ends_at             TIMESTAMPTZ,
    closed_at           TIMESTAMPTZ,
    webhook_url         TEXT,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE ideas (
    id               SERIAL PRIMARY KEY,
    content          TEXT        NOT NULL,
    author           VARCHAR(100),
    accepted         BOOLEAN     NOT NULL DEFAULT FALSE,
    rejection_reason TEXT,
    legal_basis      TEXT,
    flagged          BOOLEAN     NOT NULL DEFAULT FALSE,
    flag_count       INTEGER     NOT NULL DEFAULT 0,
    admin_note       TEXT,
    fingerprint_hash VARCHAR(64),
    consultation_id  INTEGER REFERENCES consultations(id) ON DELETE SET NULL,
    created_at       TIMESTAMPTZ NOT NULL DEFAULT NOW()
    -- consultation_id IS NULL → contribution globale (page d'accueil)
);

CREATE TABLE synthesis (
    id              SERIAL PRIMARY KEY,
    text            TEXT    NOT NULL,
    idea_count      INTEGER NOT NULL DEFAULT 0,
    consultation_id INTEGER REFERENCES consultations(id) ON DELETE CASCADE,
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
    -- consultation_id IS NULL → synthèse globale
);

CREATE TABLE consents (
    id               SERIAL PRIMARY KEY,
    fingerprint_hash VARCHAR(64) NOT NULL,
    consent_version  VARCHAR(20) NOT NULL,
    consented_at     TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE ip_abuse (
    ip_hash           TEXT PRIMARY KEY,  -- SHA-256 de l'IP (RGPD)
    rejection_count   INTEGER     NOT NULL DEFAULT 1,
    first_rejected_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_rejected_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_idea_id      INTEGER,
    blacklisted_at    TIMESTAMPTZ,
    expires_at        TIMESTAMPTZ
    -- blacklist déclenché à rejection_count >= 2
);

Frontend React

Propriété Valeur
Framework React 18 + Vite 7
Styles Tailwind CSS v4 + shadcn/ui + Radix UI
Routing Wouter (léger, côté client)
Requêtes API TanStack React Query
Build Sortie statique dans dist/public/

Communication avec le backend : toutes les requêtes passent par /api/ (même origine). La synthèse est rafraîchie automatiquement toutes les 15 secondes.

Pages publiques :

Route Description
/ Formulaire de soumission + synthèse
/about Présentation du projet
/transparence Fonctionnement, critères IA, prompts verbatim
/contributions-brutes Fil paginé des contributions acceptées
/consultations Index des consultations
/consultation/:slug Page d'une consultation ciblée
/flyer Flyer QR code imprimable
/mentions-legales Mentions légales (LCEN)
/politique-confidentialite Politique de confidentialité RGPD
/admin Panel d'administration (protégé)

Flux de données

Soumission d'une idée

Citoyen
  │
  │  POST /api/ideas {"content": "...", "author": "..."}
  ▼
HAProxy ──[TLS]──► Nginx ──► Gunicorn/Flask
                               │
                               ├─ Vérification blacklist IP
                               ├─ Honeypot / hCaptcha
                               ├─ Consentement RGPD (cookie _ct)
                               ├─ Cooldown session (cookie _cv)
                               ├─ Validation entrées (longueur, XSS)
                               ├─ Rate limiting (fingerprint ou IP)
                               ├─ Flood detection
                               │
                               ├─ Fetch textes Légifrance (cache 24h)
                               ├─ Agent filtrage ──► Mistral AI API
                               │                        │
                               │                   JSON {"accepted": bool, ...}
                               │
                               ├─ INSERT INTO ideas (...)
                               │
                               ├─ Si refusé :
                               │    record_ip_rejection() → blacklist si >= 2 rejets
                               │
                               ├─ Si accepté :
                               │    Thread daemon ──► Agent synthèse ──► Mistral AI
                               │                                             │
                               │                                        UPSERT synthesis
                               │
  ◄── HTTP 201 {"accepted": bool, "reason": "...", "idea": {...}} ──────────┘

Lecture de la synthèse

Citoyen (polling 15s)
  │
  │  GET /api/synthesis
  ▼
HAProxy ──► Nginx ──► Flask ──► SELECT FROM synthesis ──► {"text": "...", "ideaCount": N}

Fermeture automatique des consultations

Thread daemon (toutes les 60s, par worker)
  │
  ├─ pg_try_advisory_lock() — un seul worker exécute le check
  ├─ SELECT consultations WHERE ends_at < NOW() AND closed_at IS NULL
  ├─ UPDATE consultations SET closed_at = NOW()
  └─ Thread ──► webhook (non-bloquant)

Sécurité

Couches de défense

Couche Mécanisme
Réseau HAProxy sur réseau séparé, Nginx non exposé directement
Transport TLS 1.2/1.3 terminé sur HAProxy
Applicative Flask Rate limiting, validation, assainissement XSS, headers sécurité
Anti-abus Honeypot · hCaptcha (optionnel) · cooldown cookie · flood detection · blacklist IP (SHA-256, 30 jours)
IA Filtre de contenu Mistral AI + filtre légal interne (16 sources + Légifrance live)
Base de données Requêtes paramétrées, accès localhost uniquement
Système Utilisateur dédié non-root, systemd sandboxing

Périmètre des ports réseau

Port Interface Exposé à Rôle
443 HAProxy Internet HTTPS public
80 HAProxy Internet Redirect HTTPS
80 Nginx Réseau interne HTTP depuis HAProxy
8000 Gunicorn 127.0.0.1 API Flask
5432 PostgreSQL 127.0.0.1 Base de données

En-têtes HTTP de sécurité (Flask)

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'none'; object-src 'none'
Cache-Control: no-store
Pragma: no-cache

Exception : /api/consultations/:slug/export/print autorise script-src 'unsafe-inline' pour window.print().

Décisions d'architecture

Décision Choix Justification
ORM vs SQL direct psycopg2 direct Transparence, simplicité, facilité d'audit
Sync vs Async Flask Synchrone (Gunicorn) Complexité réduite, suffisant pour la charge attendue
Synthèse bloquante vs thread Thread daemon Latence utilisateur réduite, synthèse non-critique au retour
TLS sur HAProxy vs Nginx HAProxy TLS centralisé sur le composant réseau dédié à cet usage
SPA vs SSR SPA statique Déploiement simple, aucune dépendance Node en production
Rate limiting en mémoire vs Redis Mémoire (memory://) Installation simple ; Redis optionnel pour cluster multi-instances
Fournisseur IA Mistral AI (défaut) Hébergement UE, conformité RGPD, API compatible OpenAI
Stockage IP SHA-256 (32 car.) Anti-abus sans stocker l'IP brute — conformité RGPD art. 6(1)(f)
Textes légaux Légifrance PISTE live Textes consolidés du jour injectés dans le prompt, fallback statique si indisponible

Scalabilité

L'architecture actuelle est dimensionnée pour une instance unique. En cas de montée en charge :

  • Gunicorn workers : augmenter via --workers (règle : 2 × CPU + 1)
  • Multi-instances : ajouter des backends dans HAProxy + basculer le rate limiting sur Redis (REDIS_URL=redis://...)
  • Base de données : index existants sur ideas.consultation_id, synthesis.consultation_id, consents.fingerprint_hash, ip_abuse.expires_at ; ajouter un index sur ideas.accepted si la table grossit
  • Cache synthèse : la synthèse est unique en base par contexte, naturellement partagée entre workers

Stack technique

Couche Technologie
Frontend React 18 · TypeScript · Vite 7 · Tailwind CSS · shadcn/ui · Wouter
Backend Python 3.11+ · Flask 3 · Gunicorn
Base de données PostgreSQL 15+ (psycopg2, sans ORM)
IA Mistral AI (défaut) ou tout fournisseur compatible API OpenAI
Textes légaux API Légifrance PISTE — injection temps réel dans le prompt de filtrage
Sécurité flask-limiter · bleach · FingerprintJS · Redis (optionnel) · hCaptcha (optionnel)
Déploiement Nginx · systemd

Installation et développement local

Prérequis

  • Python 3.11+
  • Node.js 20+ et pnpm
  • PostgreSQL 15+
  • Un compte Mistral AI (ou un fournisseur compatible OpenAI)

Variables d'environnement

Copier .env.example en .env et renseigner les valeurs :

cp .env.example .env
chmod 600 .env

Obligatoires :

Variable Description
DATABASE_URL Chaîne de connexion PostgreSQL — postgresql://user:pass@host:5432/db
MISTRAL_API_KEY Clé API Mistral AI (ou OPENAI_API_KEY pour un fournisseur alternatif)

Recommandées en production :

Variable Description
SECRET_KEY Active le cooldown cookie signé HMAC-SHA256 — générer avec python3 -c "import secrets; print(secrets.token_hex(32))"
ADMIN_SECRET Mot de passe Bearer token pour le panel /admin
REDIS_URL Rate limiting persistant entre workers — redis://localhost:6379/0 (sinon stockage mémoire, non partagé)
VITE_APP_URL URL publique du site, utilisée par le QR code du flyer
HCAPTCHA_SECRET_KEY Active la vérification hCaptcha côté serveur
VITE_HCAPTCHA_SITE_KEY Clé publique hCaptcha côté frontend (nécessite un rebuild)

Intégration Légifrance (optionnelle — recommandée) :

Variable Description
PISTE_CLIENT_ID Client ID OAuth de l'application PISTE
PISTE_CLIENT_SECRET Client Secret OAuth de l'application PISTE
LEGIFRANCE_CACHE_TTL Durée de cache des textes en secondes (défaut : 86400 = 24h)

Inscription gratuite sur piste.gouv.fr — créer une application et activer l'API Légifrance. Après configuration, initialiser les identifiants d'articles :

python scripts/check_legal_refs.py --init
python scripts/check_legal_refs.py --update

Les modèles IA sont configurables via FILTER_MODEL (défaut : mistral-small-latest) et SYNTHESIS_MODEL (défaut : mistral-large-latest).

Développement local

# Prérequis : Node.js et PostgreSQL déjà installés
bash scripts/dev-local.sh

Le script installe automatiquement le virtualenv Python, les dépendances pip et pnpm, puis lance Flask sur le port 8080 et Vite sur le port 5173 en parallèle.

Pour accéder à l'interface depuis un poste distant :

ssh -L 5173:localhost:5173 -L 8080:localhost:8080 utilisateur@votre-serveur
# Puis ouvrir http://localhost:5173

Build frontend

cd artifacts/voix-du-peuple
pnpm install
pnpm build
# Résultat dans artifacts/voix-du-peuple/dist/public/

Pour configurer l'URL du QR code avant le build :

bash scripts/set-domain.sh https://votredomaine.fr

Déploiement en production

La procédure complète (PostgreSQL, Gunicorn, Nginx, systemd, certificat TLS) est documentée dans DEPLOIEMENT.md.

Les fichiers de configuration prêts à l'emploi se trouvent dans deploy/ :

  • deploy/nginx.conf — configuration Nginx
  • deploy/voix-du-peuple-api.service — unité systemd

La base de données est initialisée automatiquement au premier démarrage du backend. Les migrations sont incrémentales et idempotentes — un redémarrage après mise à jour suffit.


Contribuer

Les contributions sont les bienvenues. Voir CONTRIBUTING.md pour les modalités.


Licence

EUPL-1.2 — voir fichier LICENSE.

S
Description
Site d'expression citoyenne
Readme EUPL-1.2 2.2 MiB
Languages
TypeScript 73.7%
Python 22.4%
CSS 2.1%
Shell 1.6%
HTML 0.2%