--- title: Effort Estimator url: https://blog.guigpap.com/fr/workflows/effort-estimator/ url_md: https://blog.guigpap.com/fr/workflows/effort-estimator.md category: automation date: '2026-05-19' maturite: production techno: - n8n - odoo - telegram application: - automation - business --- # Effort Estimator > Estimateur deterministe d'effort base sur des cohortes de taches Odoo, expose via Telegram et un digest hebdomadaire ## 1. Quoi ? — Definition et contexte L'**Effort Estimator** est une famille de quatre workflows N8N qui repondent a une question recurrente : combien d'heures pour cette tache ? Pas d'appel LLM, pas de devinette : un calcul deterministe base sur les taches deja terminees dans Odoo, dont on connait a la fois l'estimation initiale et le temps reel passe. Le principe est simple. Chaque tache Odoo accumule un champ `x_estimated_hours` (estimation manuelle ou heuristique) et un total d'heures reelles, agregees depuis les timesheets (`account.analytic.line`) ou les sessions Claude Code. Une fois assez d'echantillons apparies, on extrait des centiles (P25 / P50 / P75) sur une cohorte similaire et on les renvoie comme estimation `low / likely / high`. > **Note - Pas de LLM dans la boucle** > > Toutes les briques d'estimation sont deterministes. Le seul role de Claude dans l'epic est l'enrichissement amont (categorisation de commits, telemetrie). L'estimation elle-meme est un simple calcul de centiles sur un echantillon historique : reproductible, auditable, gratuit. ### Les quatre workflows de la famille | Workflow | ID | Nodes | Role | |----------|----|-------|------| | **Estimation Coverage Weekly** | `BABocZHi7nlZ1uU5` | 9 | Digest hebdomadaire de couverture vers le seuil de 30 echantillons apparies | | **Effort Estimator V1** | `2A03Nc8Co0Blr96m` | 9 | Sub-workflow : selection de cohorte + centiles + score de confiance | | **Estimate Handler** | `ZzB58z0NVIntIDT3` | 7 | Handler de la commande `/estimate ` cote Telegram | | **Odoo Project Picker** | `TMGWxV5BrVcGht4Z` | 10 | Selecteur paginé de projets Odoo (sub-workflow reutilise) | ### Donnees mobilisees | Source Odoo | Champs cles | Usage | |-------------|-------------|-------| | `project.task` | `x_estimated_hours`, `x_complexity`, `x_claude_category`, `x_github_repo`, `x_claude_sessions`, `x_claude_cost_total` | Constitue l'echantillon d'apprentissage | | `account.analytic.line` | `task_id`, `unit_amount` | Heures reelles (timesheets) | | `project.task.claude.session` | `task_id`, `active_time_hours` | Fallback : sessions Claude Code pour les taches sans timesheet | Les taches dites "generic bucket" (`x_claude_category` defini, agregats par type de commit) sont exclues : elles ne sont pas individuellement estimables. --- ## 2. Pourquoi ? — Enjeux et motivations ### Le besoin Sans estimateur, chaque nouvelle issue arrive sans reference. Trois consequences immediates : | Sans estimateur | Avec Effort Estimator V1 | |-----------------|--------------------------| | Estimations a la louche, biais d'optimisme | Centiles tires de l'historique, exposes en direct sur Telegram | | Aucune visibilite sur l'ecart estime / reel | Suivi hebdomadaire de la couverture vers le seuil de 30 apparies | | Une seule estimation, sans intervalle | Triplet `low / likely / high` (P25/P50/P75) + confiance | ### Pourquoi deterministe et pas LLM ? | Critere | LLM | Deterministe centiles | |---------|-----|----------------------| | Reproductibilite | Variable selon le modele et la temperature | Identique a donnees egales | | Auditabilite | Boite noire | Cohorte + samples visibles | | Cout | Token-based, recurrent | Zero | | Donnee privee | Sort du VPS | Reste interne | | Qualite avec n < 30 | Acceptable par fluff | Honnete : "low confidence" affichee | ### La porte de phase 2 : 30 echantillons apparies L'epic #188 prevoit de debloquer un estimateur semantique (matching par description) seulement quand 30 paires `(estime, reel)` sont disponibles. En dessous, le bruit domine le signal. Le digest **Estimation Coverage Weekly** materialise cette porte : chaque lundi 09:00 Europe/Paris, un message Telegram montre le nombre courant de paires, le delta semaine sur semaine, et un message "Phase 2 DEBLOQUEE" quand le seuil tombe. --- ## 3. Comment ? — Mise en oeuvre technique ### Vue d'ensemble ```mermaid flowchart TD subgraph Cron["Cycle hebdomadaire"] direction TB Sched["Schedule Trigger · lundi 09:00"] Cov["Estimation Coverage Weekly · 9n"] DT["Data Table · estimation_coverage_history"] Hub["Notification Hub · digest Telegram"] end subgraph TG["Cycle interactif Telegram"] direction TB Cmd["/estimate description · ou menu Estimer"] Orch["Orchestrateur Telegram"] Handler["Estimate Handler · 7n"] end subgraph Core["Coeur de calcul"] direction TB V1["Effort Estimator V1 · 9n"] Picker["Odoo Project Picker · 10n"] end Odoo[("Odoo · project.task
account.analytic.line
project.task.claude.session")] Sched --> Cov Cov --> Odoo Cov --> DT Cov --> Hub Cmd --> Orch --> Handler Handler --> V1 V1 --> Odoo Picker -.->|optionnel| Handler Hub --> TG ``` ### Les trois requetes paralleles d'Effort Estimator V1 Le sub-workflow `2A03Nc8Co0Blr96m` (9 nodes) declenche trois requetes Odoo en parallele, puis un node Merge attend les trois sorties avant de passer au calcul. Ce pattern est partage avec le digest hebdomadaire. | # | Node | Type | Role | |---|------|------|------| | 1 | Execute Workflow Trigger | trigger | Input passthrough : `description`, `project_id?`, `repo?` | | 2 | Odoo Get Paired Tasks | odoo | `project.task` getAll, filtre `x_estimated_hours != false`, exclut generic bucket | | 3 | Odoo Get Timesheets | odoo | `account.analytic.line` getAll, `unit_amount > 0` | | 4 | Odoo Get Sessions | odoo | `project.task.claude.session` getAll, fallback heures Claude Code | | 5 | Wait All Queries | merge | Combine by position, 3 entrees | | 6 | Build Estimate | code | Selection cohorte, centiles, score | | 7 | Has Data? | if | Aiguillage si echantillon vide | | 8 | Respond (true) | code | Renvoi structure estimee | | 9 | Respond (false) | code | Renvoi `{error: "no_data"}` | ### La cascade de cohortes Le node Code `Build Estimate` applique une priorite stricte : il prend la premiere cohorte qui contient au moins 5 echantillons. | Priorite | Dimension | Source | |----------|-----------|--------| | 1 | `project_id` | Parametre d'entree | | 2 | `x_github_repo` | Parametre d'entree | | 3 | `x_claude_category` | Derivee de la description (matching de mots-cles) | | 4 | `x_complexity` | Derivee de la description (longueur + mots-cles) | | 5 | Global | Tous les echantillons apparies | La derivation de categorie est volontairement courte : `doc/readme` → `docs`, `fix/bug/crash` → `fix`, `refactor/clean` → `refactor`, etc. Pareil pour la complexite (court + typo/rename → `simple`, `architect/migrat/redesign` → `complex`, defaut `medium`). ### Le score de confiance Trois facteurs pesent dans un total : | Facteur | Poids | High (3) | Medium (2) | Low (1) | |---------|-------|----------|------------|---------| | Taille de l'echantillon | 40 % | `n >= 20` | `n >= 10` | `n < 10` | | Dispersion (IQR / mediane) | 30 % | `<= 0.5` | `<= 1.0` | `> 1.0` | | Proximite de cohorte | 30 % | project / repo | category / complexity | global | Le total >= 2.5 = `high`, >= 1.5 = `medium`, sinon `low`. La confiance est toujours retournee a l'appelant : utile pour le Handler Telegram qui ajoute un avertissement quand elle est basse. ### Format de sortie ```json { "has_data": true, "estimated_hours_low": 0.5, "estimated_hours_likely": 1.0, "estimated_hours_high": 3.1, "expected_sessions": 2, "expected_api_cost": 0.31, "sample_size": 34, "confidence": "medium", "based_on": "repo=GuiGPaP/stacks_vps (n=34)" } ``` En absence d'echantillon exploitable, le payload tombe sur `{has_data: false, error: "no_data"}` et le Handler renvoie un message d'usage plutot qu'un faux chiffre. ### Le parcours `/estimate` depuis Telegram Le **Estimate Handler** (`ZzB58z0NVIntIDT3`, 7 nodes) recoit deux types d'entrees, deja extraites par l'Orchestrateur Telegram : | Source | Format d'entree | |--------|------------------| | Commande `/estimate ` | Texte brut, parse pour retirer le prefixe | | Bouton menu "Estimer" + ForceReply | Reponse libre du Reply Action Handler | Le node `Extract Description` valide la presence d'un texte non vide, puis appelle l'Effort Estimator V1. Le retour est mis en forme HTML pour Telegram : ``` Effort Estimate Add pagination to /todo digest Low Likely High 0.5 1.0 3.1 (hours) Sessions: ~2 | API cost: ~$0.31 Based on: 34 tasks (repo=GuiGPaP/stacks_vps (n=34)) Confidence: medium ``` > **Tip - Sub-workflow reutilisable** > > L'Effort Estimator V1 expose un contrat d'entree minimal (`description` obligatoire, `project_id` et `repo` optionnels) et un payload de sortie stable. N'importe quel workflow peut donc l'appeler via Execute Workflow : agent conversationnel, daily digest, integration future avec un IDE. ### Le digest hebdomadaire Le **Estimation Coverage Weekly** (`BABocZHi7nlZ1uU5`, 9 nodes) tourne via un Schedule Trigger calé sur `lundi 09:00 Europe/Paris`. Son objectif est de tracker la progression vers la porte de phase 2. | # | Node | Role | |---|------|------| | 1 | Schedule Trigger | Cron lundi 09:00 | | 2 | Odoo Get Tasks | `project.task` getAll (limit 500), champs estimation + telemetrie | | 3 | Odoo Get Timesheets | `account.analytic.line` getAll (limit 2000) | | 4 | DT Get Previous Report | Lecture de la derniere ligne pour calcul de delta | | 5 | Wait All Queries | Merge des 3 entrees | | 6 | Build Coverage Report | Jointure + metriques + formatage HTML | | 7 | Has Data? | Skip si aucune tache active | | 8 | Call Notification Hub | `type=digest`, `severity=info` | | 9 | DT Save Snapshot | INSERT dans `estimation_coverage_history` | La Data Table `estimation_coverage_history` (`yBggBMumLMqfzQbv`) conserve un snapshot par semaine : `report_date`, `total_tasks`, `estimated_count`, `paired_count`, `gate_ready`. Le delta affiche dans le message Telegram (`[+3]`, `[+2]`) sort directement de cette table. ### Et l'Odoo Project Picker ? Le quatrieme workflow, **Odoo Project Picker** (`TMGWxV5BrVcGht4Z`, 10 nodes), est un selecteur paginé generique. Ce n'est pas un composant strict de l'estimation, mais il est branchable au Handler quand l'utilisateur veut une estimation cantonnee a un projet precis : `/estimate` ouvre alors une vue paginée Telegram, l'utilisateur tape sur un projet, et le `project_id` selectionne est injecte dans l'appel V1. Il est partage avec les routes `/projet status` et la pagination de listes Odoo. --- ## 4. Et si ? — Perspectives et limites ### Limites actuelles | Limite | Impact | Mitigation | |--------|--------|------------| | Faible volume d'echantillons | En dessous de 30 paires, la confiance reste basse | Digest hebdomadaire surveille la progression | | Matching par mot-cle | Categorisation grossiere | Phase 2 prevoit un matching semantique | | Pas d'apprentissage en ligne | Pas de mise a jour incrementale par execution | Acceptable, les centiles sont stables | | Cohorte globale en fallback | Une grosse tache peut tirer la mediane vers le haut | La dispersion abaisse la confiance | ### Scenarios d'evolution **Si la couverture franchit 30 paires** : - Activation de la phase 2 : matching semantique sur le titre + corps via embeddings - Comparaison automatique avec l'estimation manuelle au moment de l'ouverture d'issue - Affichage de tasks "voisines" dans le message Telegram pour comparaison **Si le volume explose** : - Indexation par projet pour eviter le full scan `limit=500` - Cache des cohortes en Redis avec invalidation a chaque cloture - Pre-calcul des centiles dans une table de stats Odoo **Si plusieurs utilisateurs** : - Cohorte additionnelle par contributeur (utile pour personnaliser l'estimation) - Possibilite de filtrer les samples sur sa propre velocite - Calibration interactive (re-estimer une tache passee via Telegram) --- ## Pages liees ### Workflows - [Claude Code Telemetry](/fr/workflows/claude-code-telemetry/) — Alimente `x_claude_sessions`, `x_claude_cost_total` et le fallback `project.task.claude.session` - [GitHub-Odoo Sync](/fr/workflows/github-odoo-sync/) — Fournit le pipeline d'issues GitHub vers `project.task` (champs `x_estimated_hours`, `x_complexity`) - [Digests & Triage Quotidien](/fr/workflows/monitoring-digests/) — Famille de digests reguliers dont Estimation Coverage Weekly fait partie ### Infrastructure - [Odoo 18 sur Docker](/fr/infrastructure/odoo-18-setup/) — Module `project_github_sync` et champs `x_*` ### Reference - [Glossaire](/fr/reference/glossary/) — Centile, IQR, sub-workflow ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0