docs(home-assistant): mettre à jour la documentation de l'intégration présence
Précise l'authentification Bearer (WORKLOG_API_TOKEN), la configuration des secrets et commandes REST Home Assistant, les codes de réponse de l'API et la conservation des événements de présence. Co-authored-by: OpenAI/GPT-5.6-Terra <vibecoder@antoineve.me>
This commit is contained in:
13
AGENTS.md
13
AGENTS.md
@@ -39,6 +39,7 @@ 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)
|
||||
- `WORKLOG_API_TOKEN` : requis en production pour l'authentification par jeton Bearer de l'API de présence Home Assistant (`/api/v1/workplace-presence`)
|
||||
|
||||
## Git & Conventions de Commit
|
||||
|
||||
@@ -54,10 +55,10 @@ grep -A 1 -B 1 subagent /home/antoine/.config/opencode/opencode.json
|
||||
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
|
||||
- `config.toml` → `app/config_loader.py` → accessed via `get_vehicles()`, `get_journeys()`, `get_bareme(year, cv)`, `get_home_assistant_config()`
|
||||
- `app/models.py` defines `WorkEntry` (one row per day), `TimeSlot` (N plages horaires per entry), `LeaveBalance` (annual quotas), `WorkplacePresenceEvent` (presence tracking events)
|
||||
- `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), `presence_service.py` (presence event processing, idempotency, arrival/departure rules)
|
||||
- Routes in `app/routes/` and API in `app/api.py` use business functions and config_loader, then render Jinja2 templates or return JSON responses
|
||||
|
||||
**Key domain rules:**
|
||||
- Day types: `WORK | TT | GARDE | ASTREINTE | FORMATION | RTT | CONGE | MALADE | FERIE`
|
||||
@@ -70,9 +71,9 @@ Flask app using the factory pattern (`create_app()` in `app/__init__.py`). The D
|
||||
|
||||
**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.
|
||||
**Auth:** Web interface handled entirely by HAProxy upstream (no authentication in app). Workplace presence API (`/api/v1/workplace-presence`) uses Bearer token authentication validated via the `WORKLOG_API_TOKEN` environment variable and `hmac.compare_digest`.
|
||||
|
||||
**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.
|
||||
**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`, `test_presence_service.py`) and API tests (`test_api.py`) have no Flask dependencies or use client fixtures respectively.
|
||||
|
||||
## Gotchas
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ Application web personnelle de suivi du temps de travail et des déplacements pr
|
||||
- **Solde congés / RTT** : suivi des jours posés et du solde restant
|
||||
- **Rapports annuels** : kilométrage total, frais réels déductibles, répartition par type de journée
|
||||
- **Véhicules électriques** : majoration de 20 % appliquée automatiquement sur les frais réels
|
||||
- **Intégration présence Home Assistant** : automatisation des arrivées et départs via une API REST dédiée sécurisée par jeton Bearer
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -67,6 +68,9 @@ Toute la configuration métier se trouve dans `config.toml` :
|
||||
- **`[vehicles.*]`** : véhicules avec puissance fiscale, type de carburant et émissions CO₂
|
||||
- **`[journeys.*]`** : profils de trajet avec distances par véhicule
|
||||
- **`[bareme_kilometrique.YYYY.*]`** : barème fiscal par année et puissance (à mettre à jour chaque année)
|
||||
- **`[home_assistant]`** : paramètres optionnels de l'API de présence (fuseau horaire, valeurs par défaut des entrées créées automatiquement)
|
||||
|
||||
En production, l'API de présence nécessite également la configuration de la variable d'environnement **`WORKLOG_API_TOKEN`**.
|
||||
|
||||
### Tests
|
||||
|
||||
|
||||
10
docs/api.md
10
docs/api.md
@@ -71,7 +71,7 @@ Le corps de la requête doit être un objet JSON valide contenant les champs sui
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `event` | `string` | Oui | Type d'événement : `"arrival"` ou `"departure"`. |
|
||||
| `occurred_at` | `string` | Oui | Horodatage au format ISO 8601 comportant obligatoirement un offset de fuseau horaire (ex: `2026-08-13T08:00:00+02:00`). |
|
||||
| `idempotency_key` | `string` | Oui | Clé d'idempotence unique (limitée à 255 caractères). **Obligatoire** : transmise soit dans le corps JSON (`idempotency_key`), soit via l'en-tête HTTP `X-Idempotency-Key`. |
|
||||
| `idempotency_key` | `string` | Oui (corps ou en-tête) | Clé d'idempotence unique (limitée à 255 caractères). **Obligatoire** : transmise soit dans le corps JSON (`idempotency_key`), soit via l'en-tête HTTP `X-Idempotency-Key`. |
|
||||
|
||||
### Règle sur la clé d'idempotence :
|
||||
La clé d'idempotence est **obligatoire**. Elle doit être fournie soit dans le corps JSON (`idempotency_key`), soit via l'en-tête HTTP `X-Idempotency-Key`. Si la clé est fournie à la fois dans le corps JSON et dans l'en-tête, **leurs valeurs doivent être strictement identiques**, sous peine d'un rejet avec le code `422`. Ne jamais considérer ce champ comme facultatif.
|
||||
@@ -83,7 +83,7 @@ La clé d'idempotence est **obligatoire**. Elle doit être fournie soit dans le
|
||||
- **Fuseau horaire** : Les horodatages `occurred_at` sont convertis dans le fuseau horaire configuré dans `[home_assistant].timezone` (la configuration explicite de `timezone` est obligatoire dans la section `[home_assistant]` ; `"Europe/Paris"` n'est qu'un exemple de valeur fourni dans la configuration de référence du projet).
|
||||
- **Arrivée (`arrival`)** :
|
||||
- Crée une entrée de journée (`WorkEntry`) à la date locale si elle n'existe pas, en appliquant les valeurs par défaut de la configuration (`default_day_type`, `default_journey_profile_id`, `default_motor_vehicle_id`).
|
||||
- Une seule arrivée peut être ouverte (`time_slot_id IS NULL`) simultanément dans toute l'application.
|
||||
- Une seule arrivée peut être ouverte (`time_slot_id IS NULL`) simultanément **de manière globale à l'application**.
|
||||
- **Départ (`departure`)** :
|
||||
- Ferme l'arrivée ouverte correspondante.
|
||||
- Crée un intervalle de temps (`TimeSlot`) rattaché à l'entrée de journée, défini entre l'heure de l'arrivée et l'heure du départ.
|
||||
@@ -159,7 +159,9 @@ Toutes les réponses de l'API (succès et erreurs) comportent l'en-tête `Cache-
|
||||
- L'utilisateur peut consulter et modifier ces entrées via l'interface Web (formulaire d'édition `/entries/<id>/edit`) :
|
||||
- Modification du type de journée (ex: passer de `WORK` à `FORMATION`), du profil de trajet ou du véhicule à moteur.
|
||||
- Modification, ajout ou suppression de plages horaires (`TimeSlot`).
|
||||
- *Cohérence des événements de présence (comportement validé par les tests d'intégration)* : Lorsque les plages horaires d'une entrée sont modifiées ou recréées depuis l'interface Web (formulaire d'édition), les événements de présence associés (`WorkplacePresenceEvent`) conservent leur traçabilité (rattachés à l'entrée `WorkEntry`), mais leurs identifiants de plage (`time_slot_id`) sont dissociés (`NULL`) pour refléter la réorganisation manuelle de la journée, conformément aux tests d'intégration existants. Aucune autre promesse au-delà de cette dissociation (`NULL`) et du maintien des événements n'est garantie.
|
||||
- *Cohérence et conservation des événements de présence (comportement validé par les tests d'intégration)* :
|
||||
- **Conservation** : Les événements de présence (`WorkplacePresenceEvent`) sont conservés en base de données **tant que l'entrée de journée associée (`WorkEntry`) n'est pas supprimée** (liaison soumise à la suppression en cascade `cascade="all, delete-orphan"` au niveau du modèle de données).
|
||||
- **Dissociation de plage** : Lorsque les plages horaires d'une entrée sont modifiées ou recréées depuis l'interface Web (formulaire d'édition), les événements de présence associés (`WorkplacePresenceEvent`) conservent leur traçabilité et leur rattachement à l'entrée `WorkEntry`, mais leurs identifiants de plage (`time_slot_id`) sont dissociés (`NULL`) pour refléter la réorganisation manuelle de la journée, conformément aux tests d'intégration existants. Aucune autre promesse au-delà de cette dissociation (`NULL`), du maintien conditionnel (tant que `WorkEntry` existe) et du suivi des événements n'est garantie.
|
||||
|
||||
---
|
||||
|
||||
@@ -200,4 +202,4 @@ curl -i -X POST "http://localhost:5000/api/v1/workplace-presence" \
|
||||
- Pas de rotation automatique des jetons d'API.
|
||||
- Pas de limite de débit distribuée (le rate-limit est confiné à chaque processus/worker Gunicorn).
|
||||
- Pas d'import CSV en masse via l'API REST.
|
||||
- **Exemple Home Assistant** : La configuration d'automatisation Home Assistant (YAML) n'est pas documentée dans ce fichier (cette documentation fait l'objet d'une étape ultérieure).
|
||||
- **Intégration Home Assistant** : La configuration détaillée des scripts, secrets, commandes REST et automatisations Home Assistant est documentée dans la [Documentation Home Assistant](home-assistant.md).
|
||||
|
||||
@@ -17,12 +17,14 @@ Avant de configurer les automatisations dans Home Assistant, assurez-vous des é
|
||||
|
||||
## 2. Configuration des Secrets (`secrets.yaml`)
|
||||
|
||||
Dans le fichier `secrets.yaml` de votre instance Home Assistant, ajoutez votre jeton d'API :
|
||||
Dans le fichier `secrets.yaml` de votre instance Home Assistant, ajoutez votre jeton d'API en veillant à ce que sa valeur **inclue obligatoirement le préfixe exact `Bearer `** (suivi d'un espace et de votre jeton secret) :
|
||||
|
||||
```yaml
|
||||
worklog_api_token: "votre_jeton_api_securise_ici"
|
||||
worklog_api_bearer_token: "Bearer votre_jeton_api_securise_ici"
|
||||
```
|
||||
|
||||
> **Attention (point critique)** : Ne préfixez jamais par `Bearer ` dans la section `rest_command`. La commande utilise `authorization: !secret worklog_api_bearer_token`, ce qui injecte directement la valeur complète définie dans `secrets.yaml` (qui doit donc impérativement commencer par `Bearer `).
|
||||
|
||||
---
|
||||
|
||||
## 3. Configuration de la Commande REST (`rest_command`)
|
||||
@@ -34,9 +36,9 @@ rest_command:
|
||||
worklog_presence:
|
||||
url: "https://tableau-de-bord-pro.example.com/api/v1/workplace-presence"
|
||||
method: "post"
|
||||
content_type: "application/json"
|
||||
headers:
|
||||
authorization: "Bearer !secret worklog_api_token"
|
||||
content_type: "application/json"
|
||||
authorization: !secret worklog_api_bearer_token
|
||||
payload: '{"event": "{{ event }}", "occurred_at": "{{ occurred_at }}", "idempotency_key": "{{ idempotency_key }}"}'
|
||||
verify_ssl: true
|
||||
```
|
||||
@@ -111,7 +113,7 @@ action:
|
||||
occurred_at: "{{ event_timestamp }}"
|
||||
idempotency_key: "{{ idempotency }}"
|
||||
- choose:
|
||||
- conditions: "{{ response.status not in [200, 201] }}"
|
||||
- conditions: "{{ response.status != 200 }}"
|
||||
sequence:
|
||||
- service: persistent_notification.create
|
||||
data:
|
||||
@@ -126,14 +128,17 @@ mode: single
|
||||
|
||||
## 5. Gestion des Réponses et de la Sécurité
|
||||
|
||||
- **Succès (200 / 201)** : Les codes de succès sont validés silencieusement. En cas de rejeu (réseau instable), l'API renvoie un statut `200` avec le statut `"replayed"`, ce qui est géré sans erreur par l'automatisation.
|
||||
- **Succès (200 / 201)** : Les codes de succès sont validés silencieusement :
|
||||
- **Arrivée** : Renvoie `201 Created` lors d'une nouvelle création, ou `200 OK` en cas de rejeu (requête idempotente).
|
||||
- **Départ** : Renvoie strictement `200 OK` (qu'il s'agisse d'un nouvel enregistrement de départ ou d'un rejeu).
|
||||
En cas de rejeu ou de succès normal, la réponse est gérée sans erreur par les automatisations.
|
||||
- **Erreurs et Notifications** : Si l'API renvoie un code d'erreur (`400`, `401`, `404`, `405`, `409`, `413`, `415`, `422`, `429`, `500`, `503`), une notification persistante est créée dans Home Assistant. **Le jeton d'authentification n'est jamais inclus** dans les messages de notification pour des raisons évidentes de sécurité.
|
||||
|
||||
---
|
||||
|
||||
## 6. Limites, Risques et Points de Vigilance
|
||||
|
||||
1. **Maximum d'une arrivée ouverte** : L'application métier n'autorise qu'une seule arrivée ouverte (`time_slot_id IS NULL`) simultanément. Si une arrivée précédente n'a pas été fermée (ou en cas de faux départ/réarrivée manqués), une tentative de nouvelle arrivée provoquera un conflit `409` (`ArrivalAlreadyOpenError`).
|
||||
1. **Maximum d'une arrivée ouverte (globale)** : L'application métier n'autorise qu'une seule arrivée ouverte (`time_slot_id IS NULL`) simultanément **à l'échelle globale de l'application**. Si une arrivée précédente n'a pas été fermée (ou en cas de faux départ/réarrivée manqués), une tentative de nouvelle arrivée provoquera un conflit `409` (`ArrivalAlreadyOpenError`).
|
||||
2. **Précision GPS et Oscillations (Doublons / Faux déclenchements)** : Les capteurs de géolocalisation (GPS mobile, intégration companion app) peuvent osciller aux abords de la zone `lieu_de_travail` (mauvaise réception GPS, rebonds d'antenne). Cela peut générer des micro-entrées et sorties successives. Bien que la clé d'idempotence et la règle chronologique protègent contre les doublons stricts basés sur le même horodatage, des entrées/sorties rapprochées multiples peuvent nécessiter des corrections manuelles dans l'interface web du Tableau de bord pro.
|
||||
3. **Limitation de débit (Rate-limit)** : L'API applique une limite stricte de **30 requêtes par fenêtre de 60 secondes** par processus. En usage normal avec un unique tracker `person`, cette limite ne risque pas d'être atteinte, mais un dysfonctionnement d'automatisation en boucle pourrait déclencher un blocage `429`.
|
||||
4. **Alignement des fuseaux horaires** : Le fuseau horaire configuré dans la section `[home_assistant]` de `config.toml` (ex: `Europe/Paris`) doit être cohérent avec celui de l'instance Home Assistant pour que les conversions d'horodatages ISO 8601 avec offset (`occurred_at`) s'alignent parfaitement avec les journées de travail locales.
|
||||
|
||||
@@ -101,7 +101,7 @@ Ce fichier contient les paramètres qui ne changent pas fréquemment et qui déf
|
||||
- **Véhicules (`[vehicles.*]`)** : Nom, type (moteur ou velo), carburant (electric, diesel, essence, none), émissions de CO₂ par km, et puissance fiscale (CV).
|
||||
- **Trajets (`[journeys.*]`)** : Profils de trajets prédéfinis (ex: "moteur_seul", "moteur_velo") avec les distances associées par type de moyen de transport.
|
||||
- **Barème kilométrique (`[bareme_kilometrique.YYYY.*]`)** : Les tranches fiscales officielles de remboursement par année et par puissance fiscale (CV).
|
||||
- **Home Assistant (`[home_assistant]`)** : Les valeurs par défaut et le fuseau utilisés par la future API de présence :
|
||||
- **Home Assistant (`[home_assistant]`)** : Les valeurs par défaut et le fuseau utilisés par l'API de présence (implémentée dans `app/api.py` et `app/business/presence_service.py`) :
|
||||
```toml
|
||||
[home_assistant]
|
||||
timezone = "Europe/Paris"
|
||||
@@ -113,7 +113,7 @@ Ce fichier contient les paramètres qui ne changent pas fréquemment et qui déf
|
||||
|
||||
*Note : Ces données sont chargées en mémoire au démarrage de l'application dans `app.config["TOML"]`.*
|
||||
|
||||
Le secret de la future API ne doit pas être ajouté au fichier TOML. Il proviendra de la variable d'environnement `WORKLOG_API_TOKEN` lorsqu'elle sera implémentée ; cette étape ne le stocke ni ne le gère.
|
||||
Le secret de l'API de présence ne doit pas être ajouté au fichier TOML. Il est configuré via la variable d'environnement `WORKLOG_API_TOKEN` sur le serveur d'exécution.
|
||||
|
||||
### SQLite / `instance/worklog.db` (Données dynamiques)
|
||||
La base de données stocke l'activité saisie par l'utilisateur :
|
||||
@@ -126,8 +126,7 @@ Les événements de présence stockent `received_at` comme un timestamp UTC naï
|
||||
conformément à la convention existante des métadonnées. `occurred_at` est différent :
|
||||
il représente une heure locale Europe/Paris naïve, et `local_date` est le jour local
|
||||
qui en est dérivé. Une arrivée non encore rattachée à une plage est identifiée par
|
||||
`event_type = "arrival"` et `time_slot_id IS NULL`; la signification de
|
||||
`processed_at` sera précisée par le service métier de l'étape suivante.
|
||||
`event_type = "arrival"` et `time_slot_id IS NULL`; `processed_at` enregistre l'instant de traitement effectif de l'événement par le service métier.
|
||||
|
||||
---
|
||||
|
||||
@@ -293,3 +292,5 @@ sudo systemctl restart tableau-de-bord-pro
|
||||
Pour aller plus loin, n'hésite pas à consulter les documents suivants à la racine du projet :
|
||||
- [README.md](../README.md) : Présentation générale, instructions d'installation détaillées et script d'import CSV en masse.
|
||||
- [AGENTS.md](../AGENTS.md) : Guide de développement et consignes pour les agents d'intelligence artificielle travaillant sur ce dépôt.
|
||||
- [Documentation API REST (Intégration Présence)](api.md) : Spécifications techniques et contractuelles de l'API REST de présence.
|
||||
- [Documentation Home Assistant](home-assistant.md) : Guide de configuration des scripts, secrets et automatisations Home Assistant.
|
||||
|
||||
Reference in New Issue
Block a user