Files
tableau-de-bord/AGENTS.md
2026-08-13 15:23:09 +02:00

87 lines
6.4 KiB
Markdown

# 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](docs/onboarding.md).
## Commands
```bash
# 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éfaut `dev-secret-change-in-prod` en 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é :
```text
Co-authored-by: Fournisseur/Modèle <vibecoder@antoineve.me>
```
*Exemple :* `Co-authored-by: Anthropic/Claude-3.5-Sonnet <vibecoder@antoineve.me>` ou `Co-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 via `get_vehicles()`, `get_journeys()`, `get_bareme(year, cv)`
- `app/models.py` defines `WorkEntry` (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` (see `day_types_without_journey()`)
- Work reference: 7h45 (465 min) for WORK/TT/FORMATION, 10h (600 min) for GARDE, 0 for absences
- `total_minutes()` on `WorkEntry` sums `TimeSlot` durations, handles midnight crossing
- Frais réels: uses `bareme_kilometrique` tranches from config.toml; `km_max = 0` means "no upper limit". Keys: `cv_3`, `cv_4`, `cv_5`, `cv_6`, `cv_7plus`. Vehicles with `fuel = "electric"` get +20% applied in `compute_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 supprimer `instance/worklog.db` en dev, ou une migration manuelle en prod.
- **Barème kilométrique** : les tranches dans `config.toml` sont à mettre à jour manuellement chaque année (section `[bareme_kilometrique.YYYY]`).
- **Métadonnées et horodatages (`created_at` / `updated_at`)** : L'application utilise `datetime.now(UTC)` pour enregistrer ces métadonnées. Bien que les valeurs soient émises en UTC, SQLite et SQLAlchemy restituent par défaut des objets `datetime` naïfs (sans `tzinfo`). 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 (`TimeSlot` via `total_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 → utiliser `db.engine`.
- **Migration `_migrate_db`** : vérifier l'existence de la table avant `ALTER TABLE` — SQLite peut avoir un fichier DB sans tables (ex: premier démarrage avec `instance/worklog.db` vide).
- **Tests de routes** : `test_routes.py` vé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.