Aller au contenu

Effort Estimator

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.

WorkflowIDNodesRole
Estimation Coverage WeeklyBABocZHi7nlZ1uU59Digest hebdomadaire de couverture vers le seuil de 30 echantillons apparies
Effort Estimator V12A03Nc8Co0Blr96m9Sub-workflow : selection de cohorte + centiles + score de confiance
Estimate HandlerZzB58z0NVIntIDT37Handler de la commande /estimate <description> cote Telegram
Odoo Project PickerTMGWxV5BrVcGht4Z10Selecteur paginé de projets Odoo (sub-workflow reutilise)
Source OdooChamps clesUsage
project.taskx_estimated_hours, x_complexity, x_claude_category, x_github_repo, x_claude_sessions, x_claude_cost_totalConstitue l’echantillon d’apprentissage
account.analytic.linetask_id, unit_amountHeures reelles (timesheets)
project.task.claude.sessiontask_id, active_time_hoursFallback : 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.


Sans estimateur, chaque nouvelle issue arrive sans reference. Trois consequences immediates :

Sans estimateurAvec Effort Estimator V1
Estimations a la louche, biais d’optimismeCentiles tires de l’historique, exposes en direct sur Telegram
Aucune visibilite sur l’ecart estime / reelSuivi hebdomadaire de la couverture vers le seuil de 30 apparies
Une seule estimation, sans intervalleTriplet low / likely / high (P25/P50/P75) + confiance
CritereLLMDeterministe centiles
ReproductibiliteVariable selon le modele et la temperatureIdentique a donnees egales
AuditabiliteBoite noireCohorte + samples visibles
CoutToken-based, recurrentZero
Donnee priveeSort du VPSReste interne
Qualite avec n < 30Acceptable par fluffHonnete : “low confidence” affichee

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.


Coeur de calcul

Cycle interactif Telegram

Cycle hebdomadaire

optionnel

Schedule Trigger · lundi 09:00

Estimation Coverage Weekly · 9n

Data Table · estimation_coverage_history

Notification Hub · digest Telegram

/estimate description · ou menu Estimer

Orchestrateur Telegram

Estimate Handler · 7n

Effort Estimator V1 · 9n

Odoo Project Picker · 10n

Odoo · project.task

account.analytic.line

project.task.claude.session

Les trois requetes paralleles d’Effort Estimator V1

Section intitulée « 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.

#NodeTypeRole
1Execute Workflow TriggertriggerInput passthrough : description, project_id?, repo?
2Odoo Get Paired Tasksodooproject.task getAll, filtre x_estimated_hours != false, exclut generic bucket
3Odoo Get Timesheetsodooaccount.analytic.line getAll, unit_amount > 0
4Odoo Get Sessionsodooproject.task.claude.session getAll, fallback heures Claude Code
5Wait All QueriesmergeCombine by position, 3 entrees
6Build EstimatecodeSelection cohorte, centiles, score
7Has Data?ifAiguillage si echantillon vide
8Respond (true)codeRenvoi structure estimee
9Respond (false)codeRenvoi {error: "no_data"}

Le node Code Build Estimate applique une priorite stricte : il prend la premiere cohorte qui contient au moins 5 echantillons.

PrioriteDimensionSource
1project_idParametre d’entree
2x_github_repoParametre d’entree
3x_claude_categoryDerivee de la description (matching de mots-cles)
4x_complexityDerivee de la description (longueur + mots-cles)
5GlobalTous les echantillons apparies

La derivation de categorie est volontairement courte : doc/readmedocs, fix/bug/crashfix, refactor/cleanrefactor, etc. Pareil pour la complexite (court + typo/rename → simple, architect/migrat/redesigncomplex, defaut medium).

Trois facteurs pesent dans un total :

FacteurPoidsHigh (3)Medium (2)Low (1)
Taille de l’echantillon40 %n >= 20n >= 10n < 10
Dispersion (IQR / mediane)30 %<= 0.5<= 1.0> 1.0
Proximite de cohorte30 %project / repocategory / complexityglobal

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.

{
"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 Estimate Handler (ZzB58z0NVIntIDT3, 7 nodes) recoit deux types d’entrees, deja extraites par l’Orchestrateur Telegram :

SourceFormat d’entree
Commande /estimate <description>Texte brut, parse pour retirer le prefixe
Bouton menu “Estimer” + ForceReplyReponse 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

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.

#NodeRole
1Schedule TriggerCron lundi 09:00
2Odoo Get Tasksproject.task getAll (limit 500), champs estimation + telemetrie
3Odoo Get Timesheetsaccount.analytic.line getAll (limit 2000)
4DT Get Previous ReportLecture de la derniere ligne pour calcul de delta
5Wait All QueriesMerge des 3 entrees
6Build Coverage ReportJointure + metriques + formatage HTML
7Has Data?Skip si aucune tache active
8Call Notification Hubtype=digest, severity=info
9DT Save SnapshotINSERT 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.

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.


LimiteImpactMitigation
Faible volume d’echantillonsEn dessous de 30 paires, la confiance reste basseDigest hebdomadaire surveille la progression
Matching par mot-cleCategorisation grossierePhase 2 prevoit un matching semantique
Pas d’apprentissage en lignePas de mise a jour incrementale par executionAcceptable, les centiles sont stables
Cohorte globale en fallbackUne grosse tache peut tirer la mediane vers le hautLa dispersion abaisse la confiance

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)

  • Claude Code Telemetry — Alimente x_claude_sessions, x_claude_cost_total et le fallback project.task.claude.session
  • GitHub-Odoo Sync — Fournit le pipeline d’issues GitHub vers project.task (champs x_estimated_hours, x_complexity)
  • Digests & Triage Quotidien — Famille de digests reguliers dont Estimation Coverage Weekly fait partie