======================================================================== 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. ========================================================================