|
22 | 22 | from transcria.jobs.filesystem import JobFilesystem |
23 | 23 | from transcria.jobs.models import Job, JobState |
24 | 24 | from transcria.web.pages_routes import _apply_animator_suggestion |
| 25 | +from transcria.workflow.phases.correction import reanchored_summary_error, summary_to_reanchor |
25 | 26 | from transcria.workflow.animator_hint import animator_from_roles, suggest_animator |
26 | 27 |
|
27 | 28 | ROOT = Path(__file__).resolve().parents[1] |
@@ -181,3 +182,70 @@ def test_le_bouton_ne_coche_pas_d_office(self): |
181 | 182 | assert "s.get('animator_suggested')" in template |
182 | 183 | assert "{% if s.get('animator_suggested') %} checked" not in template |
183 | 184 | assert "W.applyAnimatorSuggestion" in js |
| 185 | + |
| 186 | + |
| 187 | +class TestReancrageSynthese: |
| 188 | + """Lot 1bis — la synthèse est réancrée sur le SRT corrigé PAR LA PASSE QUI LE RELIT. |
| 189 | +
|
| 190 | + Constat qui motive le lot : la synthèse livrée est rédigée sur `quick_transcript.txt`, |
| 191 | + la transcription RAPIDE d'avant correction — jamais sur le SRT qu'on livre à côté. La |
| 192 | + passe de correction est la seule qui relit tout le SRT ET reçoit déjà le contexte |
| 193 | + validé (animateur compris) : le réancrage n'y coûte que la génération, contre une |
| 194 | + passe complète (≈ 1× la durée de l'audio) pour un ré-résumé autonome. |
| 195 | + """ |
| 196 | + |
| 197 | + _HEADINGS = ["## Synthèse", "## Summary"] |
| 198 | + |
| 199 | + def test_la_synthese_non_editee_part_au_reancrage(self): |
| 200 | + ctx = {"summary_llm": "# Résumé\n\n## Synthèse\nDeux points discutés.\n\n## Termes\n(aucun)", |
| 201 | + "summary": "Deux points discutés."} |
| 202 | + |
| 203 | + assert summary_to_reanchor(ctx, self._HEADINGS) == "Deux points discutés." |
| 204 | + |
| 205 | + def test_une_synthese_EDITEE_par_l_humain_n_est_jamais_reecrite(self): |
| 206 | + """Et on ne la demande même pas à la LLM : coût nul, risque nul.""" |
| 207 | + ctx = {"summary_llm": "## Synthèse\nDeux points discutés.", |
| 208 | + "summary": "Deux points discutés, dont le budget que j'ai ajouté."} |
| 209 | + |
| 210 | + assert summary_to_reanchor(ctx, self._HEADINGS) == "" |
| 211 | + |
| 212 | + def test_sans_synthese_il_n_y_a_rien_a_reancrer(self): |
| 213 | + assert summary_to_reanchor({}, self._HEADINGS) == "" |
| 214 | + assert summary_to_reanchor({"summary_llm": ""}, self._HEADINGS) == "" |
| 215 | + |
| 216 | + def test_la_garde_rejette_une_synthese_qui_fond(self): |
| 217 | + """Même doctrine que le SRT : le prompt EXIGE, le code VÉRIFIE. C'est la dérive |
| 218 | + observée quand on laisse un modèle « réécrire » librement.""" |
| 219 | + precedente = "x" * 1000 |
| 220 | + |
| 221 | + assert reanchored_summary_error(precedente, "x" * 400) is not None |
| 222 | + assert reanchored_summary_error(precedente, "x" * 1500) is not None |
| 223 | + assert reanchored_summary_error(precedente, "x" * 950) is None |
| 224 | + |
| 225 | + def test_la_garde_rejette_un_fichier_qui_n_est_pas_une_synthese(self): |
| 226 | + """Vécu ailleurs : l'agent recrache des lignes de SRT dans le mauvais fichier.""" |
| 227 | + srt = "1\n00:00:01,000 --> 00:00:04,000\nBonjour." |
| 228 | + |
| 229 | + assert "SRT" in (reanchored_summary_error("x" * 60, srt) or "") |
| 230 | + assert reanchored_summary_error("x" * 60, " ") is not None |
| 231 | + |
| 232 | + def test_la_regle_est_repliquee_dans_les_cinq_prompts(self): |
| 233 | + """Une consigne ajoutée au FR et oubliée ailleurs = une fonctionnalité qui |
| 234 | + n'existe que pour les livrables français.""" |
| 235 | + fr = (ROOT / "configs/prompts/correction_prompt.txt").read_text(encoding="utf-8") |
| 236 | + assert "synthese_a_reancrer.md" in fr and "summary_reancree.md" in fr |
| 237 | + for loc in ("en", "de", "es", "it"): |
| 238 | + texte = (ROOT / f"configs/prompts/{loc}/correction_prompt.txt").read_text(encoding="utf-8") |
| 239 | + assert "synthese_a_reancrer.md" in texte, f"{loc} : entrée absente" |
| 240 | + assert "summary_reancree.md" in texte, f"{loc} : sortie absente" |
| 241 | + assert "is_animator" in texte, f"{loc} : règle animateur absente" |
| 242 | + |
| 243 | + def test_le_reancrage_est_demande_APRES_le_srt(self): |
| 244 | + """Le SRT est l'artefact critique : la re-synthèse est une étape finale, jamais un |
| 245 | + second objectif mené de front (un agent à qui on demande deux choses fait moins |
| 246 | + bien les deux).""" |
| 247 | + source = (ROOT / "transcria/llm_tools/opencode_runner.py").read_text(encoding="utf-8") |
| 248 | + bloc = source.split("def run_correction(", 1)[1].split("def ", 1)[0] |
| 249 | + |
| 250 | + assert "ENSUITE SEULEMENT" in bloc |
| 251 | + assert bloc.index("transcription_corrigee.srt") < bloc.index("summary_reancree.md") |
0 commit comments