fix(translate): log RUN_CANCELLED event on flag cancel, skip source-lang TranslationLanguage entries

- Add RUN_CANCELLED event logging in orchestrator when executor returns CANCELLED (cancel flag path)
- Skip TranslationLanguage creation for languages matching detected source language (no ru in stats)
- Update tests for new behavior (3 entries instead of 4, no fr entry for source-match)
- Fix clickhouse insert test mocks for .options(joinedload()) chain
- All 208 translate tests pass
This commit is contained in:
2026-05-15 22:15:53 +03:00
parent e754efc7bd
commit 2ece47d561
3 changed files with 22 additions and 16 deletions

View File

@@ -693,13 +693,14 @@ class TestTranslationExecutorMultiLang:
assert result["skipped"] == 0
# Check that TranslationLanguage entries were created for each row per language
# Each row should have 2 language entries (ru, en) = 4 total
# Row 0: detected_source=fr, target_languages=[ru,en] → ru and en both create entries = 2
# Row 1: detected_source=en, target_languages=[ru,en] → en skipped (matches source), ru creates entry = 1
# Total: 3 entries (source-matched languages are now skipped)
lang_added_calls = [
call for call in db.add.call_args_list
if isinstance(call[0][0], TranslationLanguage)
]
# Should have 4 language entries (2 rows x 2 languages)
assert len(lang_added_calls) >= 4, f"Expected at least 4 TranslationLanguage entries, got {len(lang_added_calls)}"
assert len(lang_added_calls) >= 3, f"Expected at least 3 TranslationLanguage entries, got {len(lang_added_calls)}"
# Group by language code
lang_codes: list[str] = []
@@ -708,9 +709,9 @@ class TestTranslationExecutorMultiLang:
lang_codes.append(lang_entry.language_code)
assert "ru" in lang_codes
assert "en" in lang_codes
# Each language should appear twice (once per row)
# ru should appear twice (once per row), en only once (skipped for row 1 where source=en)
assert lang_codes.count("ru") == 2
assert lang_codes.count("en") == 2
assert lang_codes.count("en") == 1
# endregion test_multi_language_translation_language_entries
# region test_source_as_reference [TYPE Function]
@@ -751,7 +752,9 @@ class TestTranslationExecutorMultiLang:
assert result["successful"] == 1
# Find the TranslationLanguage for French — should use source text (original)
# Find the TranslationLanguage for French and English
# Detected source is "fr" → fr entry is skipped (matches source)
# Only "en" should have a TranslationLanguage entry
lang_calls = [
call for call in db.add.call_args_list
if isinstance(call[0][0], TranslationLanguage)
@@ -765,11 +768,7 @@ class TestTranslationExecutorMultiLang:
elif le.language_code == "en":
en_entry = le
assert fr_entry is not None, "FR language entry should exist"
# Source-as-reference: fr translation should be the original source text
assert fr_entry.translated_value == "texte français", (
f"Expected source text 'texte français' for fr, got '{fr_entry.translated_value}'"
)
assert fr_entry is None, "FR language entry should NOT exist (detected source is fr, now skipped)"
assert en_entry is not None, "EN language entry should exist"
# EN should get the LLM-translated value
assert en_entry.translated_value == "french text"

View File

@@ -1169,12 +1169,12 @@ class TranslationExecutor:
# Create per-language entries — one TranslationLanguage per language_code
for lang_code in target_languages:
lang_translation = per_lang_values.get(lang_code, "")
# Source-as-reference: if detected source language matches this target,
# store the original text verbatim (no translation needed)
# Skip if this language matches the detected source language —
# no translation needed, and it shouldn't appear in stats
if detected_lang != "und" and str(lang_code).lower() == str(detected_lang).lower():
lang_translation = source_text
continue
lang_translation = per_lang_values.get(lang_code, "")
# Check for undetermined source language
lang_needs_review = (detected_lang == "und")

View File

@@ -255,6 +255,13 @@ class TranslationOrchestrator:
# Check if executor cancelled itself due to cancellation flag
if run.status == "CANCELLED":
self._update_language_stats(run.id, language_stats_map)
self.event_log.log_event(
job_id=job.id if job else (self._job.id if self._job else run.job_id),
run_id=run.id,
event_type="RUN_CANCELLED",
payload={"reason": "cancellation_flag"},
created_by=self.current_user,
)
self.db.commit()
logger.reflect("Run cancelled via cancellation flag", {
"run_id": run.id,