fix(translate): CR1-CR2 migration + H1-H4 code review bugs

CR1: Add needs_review, language_overridden to translation_languages migration
CR2: Add per_language_metrics to translation_metric_snapshots migration
H1: BulkFindReplaceService.preview() now uses replacement_text (not find pattern)
H2: get_run_records now includes per-language data via selectinload
H3: Preview per-language approve/reject uses correct (jobId, rowId, lang) args
H4: _compute_config_hash now includes target_languages
This commit is contained in:
2026-05-14 17:38:52 +03:00
parent bb0fbfdafd
commit 1853d008e9
6 changed files with 64 additions and 11 deletions

View File

@@ -507,6 +507,7 @@ async def bulk_find_replace(
run_id=run_id,
pattern=payload.find_pattern,
is_regex=payload.is_regex,
replacement_text=payload.replacement_text,
target_language=payload.target_language,
)
return {

View File

@@ -548,9 +548,10 @@ class TestTranslationOrchestrator:
record.status = "SUCCESS"
record.error_message = None
record.created_at = datetime.now(UTC)
record.languages = []
db.query.return_value.filter.return_value.count.return_value = 1
db.query.return_value.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [record]
db.query.return_value.options.return_value.filter.return_value.count.return_value = 1
db.query.return_value.options.return_value.filter.return_value.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [record]
orch = TranslationOrchestrator(db, config_manager, "test-user")
result = orch.get_run_records("run-1")

View File

@@ -23,7 +23,7 @@ from collections.abc import Callable
from datetime import UTC, datetime
from typing import Any
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, selectinload
from ...core.config_manager import ConfigManager
from ...core.logger import belief_scope, logger
@@ -827,8 +827,10 @@ class TranslationOrchestrator:
status_filter: str | None = None,
) -> dict[str, Any]:
with belief_scope("TranslationOrchestrator.get_run_records"):
query = self.db.query(TranslationRecord).filter(
TranslationRecord.run_id == run_id
query = (
self.db.query(TranslationRecord)
.options(selectinload(TranslationRecord.languages))
.filter(TranslationRecord.run_id == run_id)
)
if status_filter:
@@ -856,6 +858,17 @@ class TranslationOrchestrator:
"status": r.status,
"error_message": r.error_message,
"created_at": r.created_at.isoformat() if r.created_at else None,
"languages": [
{
"language_code": tl.language_code,
"translated_value": tl.translated_value,
"final_value": tl.final_value or tl.translated_value,
"source_language_detected": tl.source_language_detected,
"status": tl.status,
"needs_review": tl.needs_review or False,
}
for tl in (r.languages or [])
],
}
for r in records
],
@@ -995,6 +1008,7 @@ class TranslationOrchestrator:
"translation_column": job.translation_column,
"context_columns": job.context_columns,
"target_language": job.target_language,
"target_languages": sorted(job.target_languages) if job.target_languages else [],
"provider_id": job.provider_id,
"batch_size": job.batch_size,
"upsert_strategy": job.upsert_strategy,

View File

@@ -865,6 +865,7 @@ class BulkFindReplaceService:
run_id: str,
pattern: str,
is_regex: bool,
replacement_text: str,
target_language: str,
) -> list[dict]:
"""Scan TranslationLanguage entries and return matching items without applying changes."""
@@ -888,7 +889,7 @@ class BulkFindReplaceService:
source_term = record.source_sql if record else ""
new_value = compiled.sub(
BulkFindReplaceService._get_replacement(pattern, is_regex),
replacement_text,
current_value,
)
if new_value != current_value:
@@ -904,10 +905,9 @@ class BulkFindReplaceService:
return preview_items
@staticmethod
def _get_replacement(pattern: str, is_regex: bool) -> str:
def _get_replacement(replacement_text: str, is_regex: bool) -> str:
"""Return replacement pattern (for regex, use backrefs; for plain, use literal)."""
# The replacement_text is passed through at apply time
return pattern # Placeholder — actual replacement is done in apply()
return replacement_text
@staticmethod
def apply(