6.4 KiB
AGENTS.md
Ce fichier fournit des directives et des consignes pour les agents d'intelligence artificielle et assistants de développement (multi-modèles et multi-fournisseurs) travaillant sur ce dépôt.
Pour une présentation complète de l'architecture, de la configuration et du guide de démarrage, se référer à docs/onboarding.md.
Commands
# Setup (first time)
python -m venv .venv
.venv/bin/pip install -r requirements.txt
# Run dev server
.venv/bin/python run.py
# Run all tests
.venv/bin/python -m pytest
# Run a single test file
.venv/bin/python -m pytest tests/test_time_calc.py -v
# Run a single test
.venv/bin/python -m pytest tests/test_routes.py::test_create_entry -v
# Production (systemd) — déployé dans /var/www/tableau-de-bord-pro/
sudo systemctl edit --full tableau-de-bord-pro # configurer SECRET_KEY
sudo systemctl restart tableau-de-bord-pro
# Qualité du code (Ruff)
.venv/bin/ruff check .
.venv/bin/ruff format --check .
.venv/bin/ruff format .
# Découverte des modèles OpenCode (configuration environnementale hors dépôt)
grep -A 1 -B 1 subagent /home/antoine/.config/opencode/opencode.json
Variables d'environnement
SECRET_KEY: requis en production (défautdev-secret-change-in-proden dev)
Git & Conventions de Commit
- Politique Git : Les commits intermédiaires générés par les agents d'IA doivent être non signés en utilisant l'option
git -c commit.gpgsign=false commit -m "..."(car pinentry est inaccessible dans cet environnement). L'utilisateur effectuera un amend signé (git commit --amend -S) du commit final lorsqu'il sera disponible. - Convention de trailers : Chaque commit réalisé par un agent d'IA doit inclure le trailer suivant à la fin du message de commit pour identifier le modèle utilisé :
Exemple :
Co-authored-by: Fournisseur/Modèle <vibecoder@antoineve.me>Co-authored-by: Anthropic/Claude-3.5-Sonnet <vibecoder@antoineve.me>ouCo-authored-by: Google/Gemini-3.5-Flash <vibecoder@antoineve.me>.
Architecture
Flask app using the factory pattern (create_app() in app/__init__.py). The DB is SQLite via SQLAlchemy, stored in instance/worklog.db. All vehicle/journey/tax configuration lives in config.toml (loaded at startup into app.config["TOML"]), not in the database.
Data flow:
config.toml→app/config_loader.py→ accessed viaget_vehicles(),get_journeys(),get_bareme(year, cv)app/models.pydefinesWorkEntry(one row per day),TimeSlot(N plages horaires per entry),LeaveBalance(annual quotas)app/business/contains pure functions with no Flask dependencies:time_calc.py(minutes/reference),travel_calc.py(km, CO2, frais réels),leave_calc.py(solde congés/RTT)- Routes in
app/routes/use business functions and config_loader, then render Jinja2 templates
Key domain rules:
- Day types:
WORK | TT | GARDE | ASTREINTE | FORMATION | RTT | CONGE | MALADE | FERIE - Types without journey:
TT, MALADE, CONGE, RTT, FERIE(seeday_types_without_journey()) - Work reference: 7h45 (465 min) for WORK/TT/FORMATION, 10h (600 min) for GARDE, 0 for absences
total_minutes()onWorkEntrysumsTimeSlotdurations, handles midnight crossing- Frais réels: uses
bareme_kilometriquetranches from config.toml;km_max = 0means "no upper limit". Keys:cv_3,cv_4,cv_5,cv_6,cv_7plus. Vehicles withfuel = "electric"get +20% applied incompute_frais_reels(..., electric=True).
Frontend: Tailwind CSS CDN + HTMX in base.html. No build step. Design system defined via CSS variables (--ink, --amber, --sage, --rust, --cream) and custom classes (.card, .card-*, .btn-primary, .field-input, .font-display, .font-data) — all in base.html <style>. JS inline in entry_form.html only.
Tailwind CDN limitation: Dynamic Jinja2 classes (e.g. class="{{ var }}") are not included by the CDN. Use style= inline for dynamic colors.
Auth: Handled entirely by HAProxy upstream. The app has no authentication.
Tests: tests/conftest.py provides app and client fixtures using an in-memory SQLite DB and a temporary TOML config file. Business logic tests (test_time_calc.py, test_travel_calc.py) have no Flask dependencies and need no fixtures.
Gotchas
- Pas de migration de schéma : l'app utilise
db.create_all()uniquement (pas d'Alembic). Tout changement de modèle nécessite de supprimerinstance/worklog.dben dev, ou une migration manuelle en prod. - Barème kilométrique : les tranches dans
config.tomlsont à mettre à jour manuellement chaque année (section[bareme_kilometrique.YYYY]). - Métadonnées et horodatages (
created_at/updated_at) : L'application utilisedatetime.now(UTC)pour enregistrer ces métadonnées. Bien que les valeurs soient émises en UTC, SQLite et SQLAlchemy restituent par défaut des objetsdatetimenaïfs (sanstzinfo). Ces champs sont actuellement informatifs et non exploités par la logique métier. En cas de besoin ultérieur d'exploitation de ces métadonnées avec fuseau, les pistes incluent la conservation explicite du fuseau (DateTime(timezone=True)) ou la stricte convention documentée « naïf = UTC ». - Calculs de durée métier et transitions DST : Le calcul de la durée des plages horaires de travail (
TimeSlotviatotal_minutes()) est totalement indépendant des métadonnées et repose sur des heures murales (locales). Une plage horaire traversant un changement d'heure saisonnier (passage heure d'été/hiver / DST) soulève un enjeu métier spécifique (gestion des durées d'heures locales) qui nécessiterait, le cas échéant, une représentation dédiée ou une politique métier spécifique. - Filtres Jinja2 (définis dans
app/__init__.py) :{{ date | date_fr }}pour les dates en français ;{{ day_type | day_type_fr }}pour les libellés de types de jours (WORK→Travail, TT→Télétravail, etc.). db.get_engine()deprecated en Flask-SQLAlchemy 3.x → utiliserdb.engine.- Migration
_migrate_db: vérifier l'existence de la table avantALTER TABLE— SQLite peut avoir un fichier DB sans tables (ex: premier démarrage avecinstance/worklog.dbvide). - Tests de routes :
test_routes.pyvérifie des chaînes de la réponse HTML. Utiliser les libellés affichés (ex:"Télétravail"pas"TT"), et les noms de véhicules du TOML (pas les IDs). Mettre à jour si les libellés changent.