# SejeteralO Plateforme de tarification participative de l'eau pour communes françaises. Les citoyens votent sur la forme de la courbe tarifaire via des éditeurs Bézier interactifs ; le système calcule un tarif à équilibre de recettes en temps réel. ## Protocole de début de session 1. `git pull --rebase origin main` 2. Vérifier que `data/` existe à la racine — si absent, signaler avant toute opération 3. Si l'objectif de la session n'est pas précisé, le demander ## Stack - **Frontend** : Nuxt 4 (Vue 3, TypeScript) + Pinia ; package manager : npm ; port dev : 3009 - **Backend** : Python FastAPI + SQLAlchemy 2.0 async + SQLite (aiosqlite) ; port dev : 8000 - Déploiement : Docker multi-stage + Traefik (backend + frontend) ; CI Woodpecker - Pas d'UnoCSS — CSS vanilla avec variables CSS palettes dans `main.css` ## Structure ``` frontend/ app/ components/ # composants Vue (dont DisplaySettings.vue — 6 palettes) layouts/ # layouts Nuxt pages/ commune/[slug]/index.vue # page principale citoyenne (~1900 lignes) composables/ useApi.ts # wraps fetch avec Bearer token ; usage : api.get('/path') middleware/ # route middleware (auth) plugins/ auth-restore.client.ts # restaure token depuis localStorage au démarrage stores/ auth.ts # token, role, communeSlug commune.ts utils/ bezier-math.ts # formule Bézier (Cardano + Newton-Raphson) — miroir du backend nuxt.config.ts # port 3009, apiBase via NUXT_PUBLIC_API_BASE (défaut :8000) backend/ app/ routers/ # 6 routers : auth, communes, tariff, votes, households, content engine/ pricing.py # compute_p0(), compute_tariff(), compute_impacts() integrals.py # coefficients α₁, α₂, β₂ (intégrales Bézier) median.py # médiane élément par élément des votes current_model.py # tarif linéaire de référence models/ # Commune, TariffParams, Household, Vote, AdminUser, CommuneContent alembic/versions/ # migrations tests/ seed.py # Commune Saoû, 363 foyers, codes auth 8 chars, comptes admin data/ # runtime — JAMAIS dans git (voir Données runtime) docker/ docker-compose.yml # backend + frontend (réseau traefik externe) backend.Dockerfile frontend.Dockerfile docker-compose.dev.yml Makefile # docker-up, docker-dev ``` ## Données runtime (CRITIQUE) - `data/` à la racine : contenu non géré par git, **jamais écrasé par les commits** - Volume Docker `backend-data` monté sur `/app` — contient `sejeteralo.db` (SQLite) - **Avant toute migration de chemin ou écriture sur data/ ou le volume : demander confirmation** - En dev local : la DB SQLite est dans `backend/` (chemin relatif `./sejeteralo.db`) - Seed requis après chaque reset DB : `cd backend && python seed.py` ## Commandes ```bash # Backend cd backend && . venv/bin/activate uvicorn app.main:app --reload --port 8000 --host 0.0.0.0 python -m pytest tests/ -v python seed.py # Saoû, 363 foyers, admin accounts # Frontend cd frontend && npm run dev # :3009 npm run build # Docker make docker-up # production make docker-dev # dev avec hot-reload ``` ## Conventions / pièges - **UI français, code anglais** — "foyer" = household (facturation), "électeur" = voter (vote) - **Modèle Bézier deux niveaux** — 6 paramètres citoyens (vinf, a, b, c, d, e) + p0 auto-calculé : ``` p0 = (Recettes − Σabo − Σβ₂) / Σ(α₁ + α₂) ``` Implémenté deux fois : backend Python (`engine/pricing.py`) et frontend TS (`utils/bezier-math.ts`) — garder synchronisés - **Agrégation votes** : médiane élément par élément des votes actifs (pas moyenne) - **Graphiques SVG** : axe X inversé (volumes élevés à gauche) — utiliser bindings réactifs `t.*` ou `var(--svg-*)`, jamais de couleurs hex codées en dur dans les SVG - **Thème** : 6 palettes via `useState('theme-dark')` ; CSS vars : `--color-primary`, `--color-bg`, `--color-surface`, `--color-text`, `--color-border`, `--svg-plot-bg`, `--svg-grid`, `--svg-text`, `--svg-text-light` ; `.palette-dark` sur `` - **Dev hints** : classe `.dev-hint` + `v-if="isDev"` ; les codes auth doivent exister dans la DB seedée - **Port backend** : 8000 en local (nuxt.config + uvicorn) — la table globale CLAUDE.md indique 8009 par erreur ; les fichiers de config font foi - **Pas de `ssr: true`** — CSR uniquement