Project

General

Profile

Actions

Feature #49

closed
RA

Feature #26: Implémenter la classification automatique et l'estimation de complexité des tickets par l'assistant AI

Backend — enrichir le draft de ticket avec classification & estimation de complexité

Feature #49: Backend — enrichir le draft de ticket avec classification & estimation de complexité

Added by Redmine Admin about 2 months ago. Updated about 2 months ago.

Status:
Shipped
Priority:
Normal
Assignee:
-
Start date:
06/11/2026
Due date:
% Done:

0%

Estimated time:
spec_ref:

conversation:21#26

build_status:
build_number:
ci_run_url:
scan_status:
scan_report_url:
deploy_status:
preprod_url:
deployed_at:
branch:
feat/49-draft-classification-complexity
pr_url:
https://github.com/omdev-tech/PipeLiner-Client/pull/24
security_key:
severity:
paused:

Description

**Parent :** #26 · **Taille :** L · **Dépendances :** aucune · **Surface :** backend (`api/`, hexagonal FastAPI)

## Objectif
Enrichir le draft de ticket produit par l'assistant Mistral (`MistralTicketSpecWriter`, `draft_ticket`, `create_ticket_from_draft`) avec :
- une **classification** par défaut : `bug` | `évolution`
- une **estimation de complexité** : note `/10` + explication courte

Propager ces champs dans le domaine + DTO du draft ; à la création (`create_ticket_from_draft`), persister sur Redmine, ajouter le label **`bf-disagree`** si le client a modifié la classification proposée, et persister une **argumentation** optionnelle (commentaire/champ, peut être vide). Multi-tenant + langue du client (FR/EN). Non-bloquant.

## Acceptance criteria
- [ ] `draft_ticket` renvoie `classification` (bug|évolution) + `complexity` (note/10 + explication) par défaut.
- [ ] L'explication de complexité est rendue dans la langue du client (FR/EN).
- [ ] `create_ticket_from_draft` persiste classification + note + explication sur le ticket Redmine.
- [ ] Si la classification finale ≠ proposition de l'assistant → label `bf-disagree` ajouté automatiquement.
- [ ] Argumentation optionnelle persistée (peut être vide → pas d'erreur, non-bloquant).
- [ ] Isolation multi-tenant (un client ne voit/écrit que ses tickets).
- [ ] Gate vert : `python -m pytest -q` (×2 sur une DB), `ruff check .`, `mypy src`. TDD.


Files

qa49-proof.txt (5.14 KB) qa49-proof.txt Redmine Admin, 06/11/2026 07:11 PM

RA Updated by Redmine Admin about 2 months ago Actions #1

  • Status changed from Submitted to Spec
  • spec_ref updated (diff)

RA Updated by Redmine Admin about 2 months ago Actions #2

  • Status changed from Spec to In development

RA Updated by Redmine Admin about 2 months ago Actions #3

  • Status changed from In development to QA
  • branch set to feat/49-draft-classification-complexity
  • pr_url set to https://github.com/omdev-tech/PipeLiner-Client/pull/24

PR ouverte vers `dev` : https://github.com/omdev-tech/PipeLiner-Client/pull/24

Implémentation backend (hexagonal `api/`, TDD) :
- **Domaine** : nouvel objet valeur `Complexity` (note 0..10 bornée + explication) porté par `TicketDraft` ; `classification` (= `issue_type`, « bug » | « évolution »).
- **Infra** (`MistralTicketSpecWriter`) : le modèle renvoie `complexity_score` + `complexity_explanation` ; parsing tolérant (clamp, score illisible → 0) ; l'explication est produite dans la langue du client (FR/EN).
- **Application** (`create_ticket_from_draft`) : persistance de la classification + note/10 + explication dans la description Redmine ; label **`bf-disagree`** ajouté automatiquement quand la classification finale ≠ proposition de l'assistant ; **argumentation** optionnelle persistée (vide → non bloquant, pas d'erreur). Isolation multi-tenant inchangée.
- **Présentation** : `TicketDraftOut` expose `classification` + `complexity` ; `TicketDraftBody` accepte `proposed_classification`, `complexity`, `argumentation`.

Note : Redmine n'a pas de notion native de « label » dans cette intégration (les champs personnalisés 2–9 sont pilotés par Jenkins) — `bf-disagree` est donc un marqueur stable et grepable dans le bloc de métadonnées de la description. Aucun changement de schéma → pas de migration Alembic.

Gate vert : `pytest -q` (388 passed, 1 skipped, exécuté ×2 sur une même DB pgvector éphémère), `ruff check .`, `mypy src`.

Passage en **QA**.

RA Updated by Redmine Admin about 2 months ago Actions #5

**QA — VERDICT : PASS** ✅ (le ticket reste en **QA**, prêt pour promotion préprod par l'agent dédié)

Branche testée : `feat/49-draft-classification-complexity` · commit `966a5d7` · PR #24 → dev.
Surface backend uniquement (`api/`, hexagonal). Preuve technique jointe : **qa49-proof.txt** (filtrée de tout secret). Pas de capture UI : ticket backend → preuve = suites de tests + rendu end-to-end de la description (politique de repli).

**Gate backend (api/.venv, PYTHONPATH=src sur le worktree, DB éphémère pgvector + `alembic upgrade head`)**
- `python -m pytest -q` ×2 : **388 passed, 1 skipped** (run 1 et run 2, idempotent)
- `ruff check .` : All checks passed!
- `mypy src` : Success: no issues found in 109 source files

**Critères d'acceptation**
- AC1 — `draft_ticket` renvoie `classification` (bug|feature) + `complexity` (note/10 + explication) par défaut → **OK** (`Complexity` par défaut score 0 pour les anciens appelants ; `classification` == `issue_type`).
- AC2 — explication de complexité dans la langue du client (FR/EN) → **OK** (le prompt instruit le modèle d'écrire `complexity_explanation` dans la langue ; tests FR/EN verts).
- AC3 — `create_ticket_from_draft` persiste classification + note + explication sur le ticket Redmine → **OK** (bloc metadata structuré dans la description ; vérifié par rendu réel : `## Classification` / `## Complexity - 4/10 — …`).
- AC4 — classification finale ≠ proposition → label `bf-disagree` ajouté → **OK** (token `bf-disagree` estampillé dans le bloc metadata uniquement quand `proposed_classification` est renvoyée ET diffère ; jamais inféré sinon). NB : « label » = token machine-greppable dans la description, Redmine n'ayant pas de modèle de label de 1ʳᵉ classe ici et les custom fields 2–9 étant réservés Jenkins — critère satisfait sous cette lecture (décision de conception du dev).
- AC5 — argumentation optionnelle persistée ; vide/espaces → non-bloquant → **OK** (section `## Argumentation` rendue si non vide ; argumentation `" "` → section omise, aucune erreur).
- AC6 — isolation multi-tenant → **OK** (`test_other_client_cannot_draft`, garde owned-project + conversation-in-project ; `409` si projet non lié).
- AC7 — gate vert (pytest ×2, ruff, mypy), TDD → **OK**.

**Smoke end-to-end** (rendu réel `_render_description`) : score hors borne 15 → clampé **10/10** ; client change bug→feature → `bf-disagree` présent ; classif inchangée → pas de marqueur ; argumentation vide → section absente. Tous conformes.

Aucune anomalie. Statut conservé : **QA**.

RA Updated by Redmine Admin about 2 months ago Actions #6

  • Status changed from QA to Shipped

Shipped to production (second promotion cycle). Backend draft enrichment: classification (bug|feature) + complexity (score/10 + explanation), bf-disagree token, optional argumentation, FR/EN, multi-tenant.
- QA PASS (api suite 388 passed ×2 on ephemeral Postgres, ruff/mypy clean, all 7 AC) — proof attached.
- PR #24: resolved a test_tasks_api.py merge conflict vs dev (shared Design stage-order edit — kept dev's wording, no logic change); CI green after rebase on dev.
- dev build #54 SUCCESS @ d8ba85e; dev→preprod (ff 9b5108a..d8ba85e); preprod build #62 SUCCESS; preprod→master (ff); master build #64 SUCCESS (252s incl. backend deploy + alembic) — prod gate approved, deployed.
- Prod live: https://pipeliner.omdev.tech HTTP 200, /api/health 200.

Epic #26: #49 done; siblings #50 + #51 (frontend, unblocked by this) remain in Spec.

Actions

Also available in: PDF Atom