|
========================================================================
|
|
QA #49 — Backend : enrichir le draft de ticket avec classification &
|
|
estimation de complexité (epic #26)
|
|
Statut Redmine au démarrage : QA (id 10) — OK pour QA.
|
|
Surface : backend uniquement (api/, FastAPI hexagonal).
|
|
Branche testée : feat/49-draft-classification-complexity
|
|
Commit : 966a5d7 feat(intake): enrich ticket draft with classification
|
|
& complexity (#49)
|
|
PR : #24 -> dev
|
|
Date QA : 2026-06-11
|
|
(Secrets filtrés : aucune clé API / token / URL de DB / mot de passe
|
|
n'apparaît dans cette preuve.)
|
|
========================================================================
|
|
|
|
------------------------------------------------------------------------
|
|
1. GATE BACKEND (api/.venv, PYTHONPATH=src sur la source du worktree)
|
|
DB éphémère : pgvector/pgvector:pg16 + alembic upgrade head
|
|
------------------------------------------------------------------------
|
|
alembic upgrade head ......... 0001 -> 0009 : OK (toutes migrations)
|
|
|
|
python -m pytest -q (run 1) : 388 passed, 1 skipped, 6 warnings (38.41s)
|
|
python -m pytest -q (run 2) : 388 passed, 1 skipped, 6 warnings (37.79s)
|
|
-> idempotent x2 sur DB partagée : OK
|
|
|
|
ruff check . ................. All checks passed!
|
|
mypy src ..................... Success: no issues found in 109 source files
|
|
|
|
------------------------------------------------------------------------
|
|
2. TESTS CIBLÉS PAR CRITÈRE D'ACCEPTATION (tous PASSED)
|
|
------------------------------------------------------------------------
|
|
[Domaine — value objects]
|
|
test_complexity_clamps_score_into_range
|
|
test_complexity_normalises_on_build
|
|
test_draft_defaults_to_zero_complexity_for_legacy_callers
|
|
test_draft_carries_explicit_complexity_and_classification
|
|
|
|
[Infra — MistralTicketSpecWriter (draft_ticket)]
|
|
test_draft_clamps_complexity_and_defaults_missing_to_zero
|
|
test_draft_unparseable_complexity_score_degrades_to_zero
|
|
test_draft_french_localizes_complexity_explanation_instruction
|
|
test_draft_french_language_instructs_french_values
|
|
test_draft_english_language_instructs_english_values
|
|
|
|
[Application — create_ticket_from_draft]
|
|
test_persists_classification_and_complexity_in_description
|
|
test_bf_disagree_marker_added_when_client_changed_classification
|
|
test_no_bf_disagree_marker_when_classification_unchanged
|
|
test_no_bf_disagree_marker_when_no_proposal_given
|
|
test_optional_argumentation_persisted_when_present
|
|
test_empty_argumentation_is_non_blocking_and_omitted
|
|
test_complexity_explanation_persisted_in_client_language
|
|
|
|
[Présentation / API + multi-tenant]
|
|
test_create_flags_bf_disagree_when_client_reclassifies
|
|
test_create_empty_argumentation_is_non_blocking
|
|
test_create_returns_409_when_project_not_linked
|
|
test_other_client_cannot_draft (isolation multi-tenant)
|
|
|
|
------------------------------------------------------------------------
|
|
3. SMOKE END-TO-END — rendu réel de la description Redmine
|
|
(_render_description, exécuté en direct)
|
|
------------------------------------------------------------------------
|
|
CAS A — FR, classification INCHANGÉE (feature), argumentation présente :
|
|
|
|
Le client veut exporter ses tickets en CSV.
|
|
|
|
## Acceptance criteria
|
|
- bouton export
|
|
- fichier .csv valide
|
|
|
|
## Classification
|
|
- feature
|
|
|
|
## Complexity
|
|
- 4/10 — Logique simple, nouvel endpoint et un bouton UI.
|
|
|
|
## Argumentation
|
|
Priorité client élevée.
|
|
|
|
-> classification + note/10 + explication FR persistées ; PAS de
|
|
bf-disagree (classif inchangée) ; argumentation persistée.
|
|
|
|
CAS B — EN, client CHANGE bug -> feature, argumentation vide (espaces),
|
|
score proposé 15 (hors borne) :
|
|
|
|
Login is broken.
|
|
|
|
## Acceptance criteria
|
|
- login works
|
|
|
|
## Classification
|
|
- feature
|
|
- `bf-disagree`
|
|
|
|
## Complexity
|
|
- 10/10 — Out-of-range score should clamp to 10
|
|
|
|
-> token `bf-disagree` estampillé (classif finale != proposition) ;
|
|
argumentation vide => section omise (non-bloquant) ;
|
|
score 15 clampé à 10/10.
|
|
|
|
------------------------------------------------------------------------
|
|
4. SYNTHÈSE PAR CRITÈRE D'ACCEPTATION
|
|
------------------------------------------------------------------------
|
|
AC1 draft_ticket renvoie classification (bug|feature) + complexity
|
|
(note/10 + explication) par défaut .................... OK
|
|
AC2 explication de complexité dans la langue du client (FR/EN) ... OK
|
|
AC3 create_ticket_from_draft persiste classification + note +
|
|
explication sur le ticket Redmine .................... OK
|
|
AC4 classification finale != proposition => `bf-disagree` ........ OK
|
|
(label = token machine-greppable dans le bloc metadata de la
|
|
description ; Redmine n'a pas de modèle de label de 1ère classe
|
|
ici et les custom fields 2-9 sont réservés Jenkins — décision de
|
|
conception du dev, critère satisfait sous cette lecture.)
|
|
AC5 argumentation optionnelle persistée ; vide/espaces =>
|
|
non-bloquant, section omise ......................... OK
|
|
AC6 isolation multi-tenant (un client n'écrit que ses tickets) ... OK
|
|
AC7 gate vert pytest x2 + ruff + mypy, TDD .................. OK
|
|
|
|
VERDICT : PASS — tous les critères d'acceptation OK.
|
|
========================================================================
|