feat(translation): quality pipeline overhaul + new features (audit 2026-08-29)
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m20s

Translation quality & format preservation:
- Word: merge adjacent same-format runs into one unit (sentence-level
  coherence like inline-tag handling); translate comments/balloons;
  dedupe textbox collection (was translated twice); RTL no longer
  overrides center/justify alignment; CJK/Arabic font hints (eastAsia/cs)
- PPTX: chart translations now actually reach the output file
  (ChartPart.blob is read-only — rewrite chart XML in the saved ZIP);
  CJK typeface hints (a:ea)
- Excel: sheet renames no longer break references — rewrite cell
  formulas (3D/quoted), defined names, data validations, cond. formats
- PDF: bold/italic honored (hebo/heit/hebi); table cells never merge;
  unchanged blocks left untouched (typography preserved, fixes duplicate
  hyperlinks); attempted/changed stats + route gate now cover PDF;
  CJK font paths; scanned PDFs via Mistral OCR (detection + admin settings)

Features:
- formality param (formal/informal) + automatic regional-variant prompts
- output_mode=bilingual docx (source above translation)
- per-user translation memory on Redis (falls back to LRU), context-hashed
- QA report + 0-100 confidence score in job status; L0 on by default
- OpenAI-compatible providers: whole chunk in ONE numbered-JSON request
  (~15x fewer calls) with per-item fallback; base prompt always present
  (custom prompt no longer replaces translation instructions)

Infra & marketing alignment:
- plan-based engine gating + vision gating (closes paid-engine leak);
  /providers/available filtered per plan; 107 languages exposed
- zh-CN/zh-TW validation fixed; libmagic disabled on Windows (native crash)
- admin: Mistral OCR settings + engine status dashboard; httpx<0.28 pin
  (TestClient breakage); Prometheus test fixture fixed
- marketing docs aligned with code (PDF+OCR, retention, engines, pricing)
- security: .env.ionos/.env.production/provider_settings.json removed

Tests: 1173 passed / 0 failed (6 network tests deselected: free Google
endpoint temporarily blocked from this machine)
This commit is contained in:
2026-08-29 18:38:09 +02:00
parent 992f13d53c
commit 526c87348f
87 changed files with 6996 additions and 1024 deletions

View File

@@ -0,0 +1,146 @@
# Audit fonctionnel — Pipeline de traduction (wordly.art)
**Date :** 29 août 2026 · **Périmètre :** backend de traduction (routes → providers → translators docx/xlsx/pptx/pdf) + benchmark concurrentiel web
**Méthode :** lecture du code, traduction réelle de fichiers de test via le pipeline de production (provider Google, fr→en), vérification programmatique des sorties, revue de la suite pytest, recherche web concurrents/bonnes pratiques.
---
## 0. Corrections appliquées (même jour)
| # | Correction | Fichiers | Vérification |
|---|---|---|---|
| 1 | **zh-CN/zh-TW acceptés** : validation insensible à la casse, forme canonique propagée (`validate("zh-cn")``"zh-CN"`, alias `chinese`/`tw` conservés) | `middleware/validation.py` · `routes/translate_routes.py` (valeurs de retour utilisées) | 11 tests `tests/test_language_validation.py` ✅ |
| 2 | **NameError `current_user`** dans `_run_translation_job` : nouveau helper `_tier_from_plan_str()` (gère `"PlanType.PRO"` et `"pro"`) ; les couches qualité L0/L1/L2 reçoivent enfin le bon tier | `routes/translate_routes.py` | Suite pytest ✅ |
| 3 | **Crash libmagic sous Windows** : `import magic` désactivé sur win32, dégradation magie-bytes (PDF/ZIP) ; **la suite pytest ne bloque plus** | `middleware/validation.py` | 207 tests passent (vs hang avant) |
| 4 | **OCR Mistral pour PDF scannés** : détection (page quasi sans texte ET couverte ≥50 % par une image), client API `services/mistral_ocr.py` (chunks de 8 pages, retries, erreurs typées), chemin OCR → traduction → PDF propre ; erreur explicite sans `MISTRAL_API_KEY` | `services/mistral_ocr.py` (nouveau) · `translators/pdf_translator.py` · `config.py` · `.env.example` | 12 tests `tests/test_scanned_pdf_ocr.py` + E2E réel (OCR mocké, traduction Google réelle) ✅ |
**Total : 207 tests passent** (`tests/test_translators` + les 2 nouveaux fichiers). La sortie OCR est un PDF re-mis en page propre : les pages scannées étant des images, la disposition d'origine ne peut pas être réécrite en place.
---
## 1. Synthèse exécutive
Le pipeline fonctionne : un document Word, Excel ou PowerPoint envoyé sur `POST /api/v1/translate` est traduit, mis en forme à l'identique (gras, tableaux, formules, styles) et reste téléchargeable. Les tests réels effectués passent à **14/14**, et les **106 tests unitaires** des traducteurs Office passent en 3,7 s.
En revanche, l'audit a confirmé **4 bugs fonctionnels** dont un bloquant pour le chinois (rejeté côté API alors que l'UI le propose), **2failles qualité structurelles** (glossaire ignoré par les providers non-LLM, phrase découpée en « runs » traduite morceau par morceau), et un point d'architecture fragile (jobs perdus au redémarrage). Les couches qualité L0/L1/L2 déjà développées sont désactivées par défaut et la L2 est cassée par un bug (`NameError`).
Le benchmark web montre que le positionnement est bon (LLM + préservation du format + prix), mais que les différenciateurs attendus en 2026 sont : **PDF scannés via OCR** (refusés par DeepL/Azure), **sortie bilingue**, **mémoire de traduction persistante** (le code existe mais n'est pas branché) et **scores de qualité visibles**.
---
## 2. Ce qui fonctionne (vérifié en conditions réelles)
Script de test : `temp/audit/audit_functional.py` (fichiers générés puis traduits via `WordTranslator`/`ExcelTranslator`/`PowerPointTranslator` + provider Google de production).
| Vérification | Résultat |
|---|---|
| DOCX — titre, paragraphes traduits | ✅ « Rapport financier annuel » → « Annual financial report » |
| DOCX — phrase coupée en 3 runs (gras au milieu) | ✅ cohérente dans ce cas, gras conservé sur le bon run |
| DOCX — nombres/monnaies préservés | ✅ « 4 500 000 euros en 2025 » intact |
| DOCX — tableau traduit | ✅ |
| XLSX — cellules, en-têtes, mois | ✅ « Janvier » → « January » |
| XLSX — **formules préservées** | ✅ `=SUM(B3:B4)` inchangée |
| PPTX — titre + puces | ✅ « Stratégie commerciale 2026 » → « Commercial strategy 2026 » |
| Statistiques anti-échec (`attempted/changed`) | ✅ remontées au job |
Points d'architecture solides constatés : validation magic bytes + zip-bomb, protection SSRF sur `file_url`, quotas mensuels atomiques, progression temps réel (Redis, TTL 2h), webhooks signés par jeton par-job, watermark gratuit, RTL (ar/he/fa), notes de bas de page, SmartArt, graphiques (ré-injection ZIP), noms de feuilles Excel reécrits avec mise à jour des références.
---
## 3. Bugs confirmés
### P1 — Le chinois est rejeté comme langue cible (bloquant, visible utilisateur) — ✅ CORRIGÉ
`middleware/validation.py:488` met le code en minuscules (`zh-cn`) mais `SUPPORTED_LANGUAGES` contient `"zh-CN"`/`"zh-TW"` en casse mixte (`:349-350`) → `LanguageValidator.validate("zh-CN")` lève une erreur → **400 systématique**. L'UI propose pourtant `zh-CN`/`zh-TW` (`frontend/src/app/dashboard/translate/useTranslationConfig.ts:31-32`). De plus la valeur normalisée retournée par `validate()` est ignorée dans `routes/translate_routes.py:796,806` (alias `chinese``zh-CN` jamais propagés).
**Fix appliqué (voir §0).**
### P1 — `NameError: current_user` : la couche qualité L2 ne fonctionne jamais — ✅ CORRIGÉ
Dans `_run_translation_job` (`routes/translate_routes.py:1574-1575, 1592-1593, 1611`), `current_user` n'existe pas (signature `:1105-1120`). L'`NameError` est avalé par les `except` : quand `QUALITY_L2_ENABLED=true`, le juge qualité Pro **échoue silencieusement à chaque job** ; les métriques `record_translation_retry` L0/L1 sont mortes aussi.
**Fix appliqué (voir §0).**
### P2 — Glossaire silencieusement ignoré selon le provider
Le glossaire n'est injecté que via `metadata["custom_prompt"]`, lu uniquement par openai/deepseek/minimax. Avec **google, deepl ou google_cloud, un utilisateur Pro qui paie pour le glossaire n'a aucune application des termes** — sans avertissement. `build_full_prompt` avec glossaire seul produit en outre un prompt système **sans instruction de traduction** (le prompt par défaut est remplacé : `services/providers/openai_provider.py:124-125`), ce qui fragilise la qualité même en LLM.
**Fix :** fusionner glossaire + prompt par défaut au lieu de remplacer ; refuser ou avertir quand provider non-LLM + glossary_id ; à terme, utiliser les glossaires natifs DeepL.
### P2 — URL de téléchargement cassée sur `/translate-batch` (legacy)
`routes/legacy_routes.py:304` renvoie `/api/v1/download/{output_filename}` alors que la route exige un id `tr_*` (`translate_routes.py:26`) → 400 INVALID_JOB_ID pour tout client de l'endpoint batch.
### P2 — Crash natif Windows dans le chemin « format loss » PDF + violation de couche — ✅ CORRIGÉ
`translators/pdf_translator.py:69` (`_record_format_loss_metric`) importe `middleware.metrics` au milieu de la traduction PDF. L'import déclenche `middleware/__init__.py``middleware/validation.py:7``import magic`**`python-magic`/libmagic plante en « access violation » sous Windows** (crash natif non rattrapable par le `try/except` du wrapper). Conséquences : (a) la suite pytest **bloque indéfiniment** sur `tests/test_translators/test_b3_5_pdf_smart_fit.py::test_tier_3_4_limit_to_two_shrink_steps` ; (b) en production Windows, tout PDF avec perte de format (overflow → placeholder `[translation overflow]`) risque le même sort. C'est aussi une violation de couche : `translators/` ne devrait pas dépendre de `middleware/`.
**Fix appliqué (voir §0) : `import magic` désactivé sous Windows + dégradation magie-bytes ; l'import paresseux `middleware.metrics` dans pdf_translator est désormais inoffensif. La répartition translators/→core/ reste à faire (P3).**
### P3 — Divers
- Échec de traduction = **texte source renvoyé silencieusement** (chaque provider). Le garde-fou `changed == 0` (`translate_routes.py:1480`) ne bloque que l'échec total : un document traduit à 10 % passe (warning seul, `:1490`). L'utilisateur paie pour un document partiel.
- Modèle OpenRouter réécrit silencieusement (`:1182-1183`) : la config admin `deepseek/deepseek-v3.2` est forcée vers `google/gemini-3.5-flash`.
- `max_tokens=500` dans le provider legacy `OpenAITranslationProvider` (`services/translation_service.py:1071`) peut tronquer un paragraphe long.
- `OllamaTranslationProvider.list_models` défini 2 fois (l'instance method est masquée par le staticmethod, `translation_service.py:649` vs `:693`).
- Provider « zai » pointe par défaut vers xAI/Grok (`translate_routes.py:1319-1329`) — dénomination trompeuse.
- Code mort : `WebLLMTranslationProvider`, `_GoogleCloudWithFallback`, `services/translation_cache.py` (cache Redis TM — voir §5), chaîne de fallback `services/providers/fallback.py` non branchée sur la route principale.
---
## 4. Risques qualité structurels (traduction)
1. **Segmentation par run (docx/pptx).** Chaque `<w:r>` est traduit indépendamment (`translators/word_translator.py:1147-1220`). Dès qu'une phrase est découpée par du gras, une couleur, une correction orthographique (rsid) ou un saut de champ, les morceaux sont traduits séparément : l'ordre des mots cible peut rendre la phrase incohérente (cas fr→en avec adjectifs). Le test est passé parce que la découpe tombait bien ; c'est fragile par construction. C'est **l'écart principal face à DeepL** qui traduit au niveau document/phrase.
**Piste :** traduire au niveau paragraphe avec masquage de placeholders (`<0>`, `<1>`…) pour protéger les frontières de mise en forme, puis redistribuer — technique standard validée (cf. recherche web, « tag protection »).
2. **Moteur par défaut = Google Translate gratuit non officiel** (deep_translator, scraping). Limité à 5 000 caractères/requête, non contractuel, risque de blocage/ToS, qualité plafonnée. Acceptable en free tier, risqué comme moteur par défaut des offres payantes.
3. **Aucune détection de langue réelle** : `source_lang=auto` délégué au provider ; les prompts LLM disent « détecte la langue » (correct avec les LLM récents, approximatif avec Google).
4. **Cohérence document** : chaque segment est traduit sans contexte document (pas de contexte titre/section). Les LLM supporteraient un batch contextuel.
---
## 5. Architecture — points fragiles
- **Jobs non persistés** : `asyncio.create_task` (`translate_routes.py:959`) — un redémarrage du process perd tous les jobs en cours (le statut Redis survit mais rien ne reprend). Pour un SaaS payant, prévoir une queue (Celery/Arq/RQ ou table jobs + worker) avec reprise.
- **Cache mémoire seulement** (LRU 5 000, perdu au redémarrage). Le cache Redis avec clés sha256 + TTL 24h **existe** (`services/translation_cache.py`) mais n'est branché nulle part en production — c'est une mémoire de traduction (TM) gratuite à activer.
---
## 6. Benchmark & recommandations produit (recherche web, août 2026)
### Concurrents et standards du marché
| Acteur | Positionnement | Enseignement |
|---|---|---|
| DeepL | Référence qualité ; documents pdf/docx/pptx/xlsx/idml ; facturation API **min 50 000 chars/document** ; glossaires natives, formality, variantes pt-BR/pt-PT, fr-CA | Le minimum 50K chars est un pain point : un pipeline LLM peut être moins cher. Refuse les PDF scannés. |
| Google Cloud Translation Advanced | **0,08 $/page**, PDF natifs et scannés (OCR intégré, 1 000 pages) | Alternative crédible au scraping gratuit pour le paid tier |
| Azure Translator | 15 $/M chars, batch documents, refuse PDF scannés | |
| Immersive Translate / BabelDOC | 20M+ utilisateurs, PDF bilingue, OCR | La sortie bilingue est très demandée |
| PDFMathTranslate (35K★) / BabelDOC (open source) | Préservation layout PDF, formules, multi-moteurs | À étudier pour améliorer `pdf_translator` |
| Pairaphrase/Redokun/Smartcat | TM + glossaires + relecture humaine, 285 $+/mois | Le segment pro attend TM/glossaires |
| DocTranslator, oTranslator | 14,99 $/mois ou ~0,006-0,06 $/page | Prix marché prosumer : 8-15 $/mois |
État de l'art modèles (WMT25) : **GPT-4.1 et Gemini-2.5-Pro mènent** la traduction IA ; DeepL next-gen « stable mais mid-tier ». Qualité : MetricX-24/25, xCOMET (QE), RUBRIC-MQM (LLM-judge) — aucun concurrent grand public n'affiche de scores de confiance par segment : **différenciateur ouvert**.
### Recommandations priorisées
1. **P0 — Corriger les 2 bugs P1** (chinois, L2) : quelques lignes, impact utilisateur direct.
2. **P0 — Activer le cache Redis existant comme TM** (déjà codé) : baisse de coût + cohérence terminologique entre documents.
3. **P1 — Paragraph-level translation avec placeholders** pour docx/pptx : plus grand gain qualité perçue.
4. **P1 — PDF scannés via OCR** : ✅ **implémenté (Mistral OCR, voir §0)**. Reste : brancher Mistral OCR (~1 $/1 000 pages) — il suffit de définir `MISTRAL_API_KEY`.
5. **P1 — Glossaire :** fusionner avec le prompt par défaut ; supporter les glossaires natifs DeepL ; avertir si provider incompatible.
6. **P2 — Sortie bilingue** (docx avec colonnes/annotations, PDF duo) : forte demande, faible coût.
7. **P2 — Activer L0/L1 en production** après fix (coût ~0,0003 $/job) et exposer un « score de confiance » par document.
8. **P2 — Formalité & variantes régionales** (fr-CA, pt-BR/PT) via instructions prompt (LLM) — table stakes 2026.
9. **P3 — Queue persistante** pour les jobs ; remplacer le moteur par défaut du paid tier par DeepL API ou Google Cloud Advanced (0,08 $/page) plutôt que le scraping gratuit.
10. **P3 — Nettoyage :** supprimer le code mort listé §3, documenter l'alias « zai »→xAI.
---
## 7. Annexes
### A. Résultats des tests réels
Voir §2 — script `temp/audit/audit_functional.py`, sorties dans `temp/audit/`.
### B. Suite pytest
- `tests/test_translators/test_word_translator.py + test_excel_translator.py + test_pptx_translator.py` : **106 passed en 3,68 s**
- `tests/test_translators/test_b3_5_pdf_smart_fit.py` : **bloque indéfiniment** sur `test_tier_3_4_limit_to_two_shrink_steps` (crash natif libmagic, cf. bug P2 ci-dessus) — reproduit isolément ; dumps faulthandler « Windows fatal exception: access violation » dans `magic/compat.py:189`.
- Recommandations : corriger l'import `magic`, ajouter `pytest-timeout`, et noter que `pytest.ini` active la couverture par défaut (`addopts --cov=...`) ce qui rend les runs complets très lents.
### C. Sources principales (benchmark)
- DeepL document translation & API : deepl.com/en/features/document-translation · developers.deepl.com (document upload, glossaries, formality, variants)
- Google Cloud Translation (documents, 0,08 $/page, OCR) : cloud.google.com/translate/docs/advanced/translate-documents
- Azure Translator document translation : learn.microsoft.com/azure/ai-services/translator/document-translation/
- WMT25 (GPT-4.1/Gemini 2.5 Pro en tête) : slator.com/wmt25-preliminary-results-gemini-2-5-pro-gpt-4-1-lead-ai-translation/
- MetricX-24/25 (QE) : github.com/google-research/metricx · RUBRIC-MQM : aclanthology.org/2025.acl-industry.12/
- PDFMathTranslate : github.com/PDFMathTranslate/PDFMathTranslate · BabelDOC : github.com/funstory-ai/BabelDOC
- Mistral OCR (1 $/1 000 pages) : mistral.ai/news/mistral-ocr/ · PaddleOCR : github.com/PaddlePaddle/PaddleOCR
- Immersive Translate (bilingue, PDF Pro) : immersivetranslate.com/pricing/
- Pairaphrase (pricing entreprise) : pairaphrase.com · Redokun : redokun.com/pricing