feat(quality): add L0 quality layer (Track A1 + A2 of dev plan)
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m5s

L0 quality detection layer to catch translation failures BEFORE they
reach users. Pure Python/TypeScript, zero new dependencies, no API calls.

Backend (Python — services/quality/):
  - Script detection: 145 langs mapped to 23 scripts (Latin, Cyrillic,
    Greek, Arabic, Hebrew, CJK, Hangul, Kana, Devanagari, Bengali, etc.)
  - Language confusion detection (e.g. Arabic text for French target)
  - Arabic-script variant discrimination (Persian/Urdu/Pashto/Kurdish
    confusion — e.g. Persian text returned when Arabic was requested)
  - Length sanity check (with numeric/short-source exemptions)
  - Prompt leak detection (Translation: / Voici la traduction: / 翻译:)
  - Repetition hallucination detection (token + character level)
  - File text extraction for .docx/.xlsx/.pptx/.pdf (no translator
    changes needed)
  - Defensive pipeline that never raises (L0 must NEVER break a job)

Frontend (TypeScript — wordly.art---traduction-de-documents/src/utils/):
  - Exact 1:1 mirror of the Python module
  - Zero dependencies, works in browser AND Node.js
  - Native Unicode regex (\\p{L}/u) and codePoint iteration
  - 63 tests using Node's built-in test runner

Integration:
  - Feature-flagged: QUALITY_L0_ENABLED=false (default)
  - Observation only: logs structured events, never modifies files
  - try/except wrapped: impossible to break a translation job
  - Lazy imports: only loaded when flag is on
  - Zero impact on existing tests / behavior

Tests:
  - 111 Python tests covering all paths (config, script, length, leak,
    pipeline, file_extractor) — 100% pass
  - 63 TypeScript tests (Node --test) — 100% pass
  - 174/174 total tests for the L0 layer

Bug fixes in script mapping:
  - yi (Yiddish) -> hebrew (was incorrectly mapped to arabic)
  - dv (Maldivian) -> thaana (was incorrectly mapped to arabic)
  - ja (Japanese) -> hiragana_katakana (distinguishes from Chinese CJK)

Phase 1 (backend) + Phase 2 (frontend) of Track A complete.
Next: Track B1 (Word/Excel format preservation quick wins).

Closes Track A phase 1+2 of the dev plan.
This commit is contained in:
2026-07-14 16:17:43 +02:00
parent ebb2537fda
commit f403b2851d
20 changed files with 3132 additions and 1 deletions

View File

@@ -1354,6 +1354,34 @@ async def _run_translation_job(
f"{changed}/{attempted} ({ratio:.1%})"
)
# ------------------------------------------------------------------
# Quality L0 layer (Track A1 — observability only)
# Extract a small sample of text from the output file and run the
# L0 quality checks. NEVER blocks the job, NEVER modifies the file.
# Enabled by feature flag QUALITY_L0_ENABLED (default: false).
# ------------------------------------------------------------------
if getattr(config, "QUALITY_L0_ENABLED", False):
try:
from services.quality import run_l0_check, extract_sample
samples = extract_sample(
output_path,
file_extension,
max_samples=getattr(config, "QUALITY_L0_SAMPLE_SIZE", 20),
)
translated_chunks = [s["translated"] for s in samples]
run_l0_check(
source_chunks=[""] * len(translated_chunks), # L0 doesn't need source
translated_chunks=translated_chunks,
target_lang=target_lang,
job_id=job_id,
file_extension=file_extension,
)
except Exception as l0_err:
# Quality L0 must NEVER break a job. Log and continue.
logger.warning(
f"Job {job_id}: quality L0 layer failed: {l0_err}"
)
if user_id:
# Determine cost factor based on selected provider and model
cost_factor = 1