diff --git a/backend/alembic/versions/dabc97097e0e_add_cache_hits_column_to_translation_.py b/backend/alembic/versions/dabc97097e0e_add_cache_hits_column_to_translation_.py new file mode 100644 index 00000000..a2fd5e74 --- /dev/null +++ b/backend/alembic/versions/dabc97097e0e_add_cache_hits_column_to_translation_.py @@ -0,0 +1,40 @@ +"""add cache_hits column to translation_runs + +Revision ID: dabc97097e0e +Revises: ed28d34edde7 +Create Date: 2026-06-02 11:54:03.550164 + +""" +from collections.abc import Sequence + +from alembic import op +import sqlalchemy as sa +from sqlalchemy import inspect + + +# revision identifiers, used by Alembic. +revision: str = 'dabc97097e0e' +down_revision: str | Sequence[str] | None = 'ed28d34edde7' +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def _table_exists(table_name: str) -> bool: + conn = op.get_bind() + inspector = inspect(conn) + return table_name in inspector.get_table_names() + + +def upgrade() -> None: + """Add cache_hits column to translation_runs table.""" + if not _table_exists("translation_runs"): + return + op.add_column("translation_runs", sa.Column( + "cache_hits", sa.Integer(), nullable=False, server_default=sa.text("0"), + comment="Number of rows served from translation cache", + )) + + +def downgrade() -> None: + """Remove cache_hits column from translation_runs table.""" + op.drop_column("translation_runs", "cache_hits") diff --git a/backend/src/api/routes/translate/_helpers.py b/backend/src/api/routes/translate/_helpers.py index ab47b4a3..3c843e52 100644 --- a/backend/src/api/routes/translate/_helpers.py +++ b/backend/src/api/routes/translate/_helpers.py @@ -23,6 +23,7 @@ def _run_to_response(run: Any) -> dict: "successful_records": run.successful_records or 0, "failed_records": run.failed_records or 0, "skipped_records": run.skipped_records or 0, + "cache_hits": run.cache_hits or 0, "insert_status": run.insert_status, "superset_execution_id": run.superset_execution_id, "config_snapshot": run.config_snapshot, diff --git a/backend/src/api/routes/translate/_run_list_routes.py b/backend/src/api/routes/translate/_run_list_routes.py index f240ea9e..558a7770 100644 --- a/backend/src/api/routes/translate/_run_list_routes.py +++ b/backend/src/api/routes/translate/_run_list_routes.py @@ -104,6 +104,7 @@ async def list_runs( "successful_records": r.successful_records or 0, "failed_records": r.failed_records or 0, "skipped_records": r.skipped_records or 0, + "cache_hits": r.cache_hits or 0, "insert_status": r.insert_status, "superset_execution_id": r.superset_execution_id, "config_hash": r.config_hash, diff --git a/backend/src/models/translate.py b/backend/src/models/translate.py index 39c64ee9..74c91440 100644 --- a/backend/src/models/translate.py +++ b/backend/src/models/translate.py @@ -81,6 +81,7 @@ class TranslationRun(Base): successful_records = Column(Integer, default=0) failed_records = Column(Integer, default=0) skipped_records = Column(Integer, default=0) + cache_hits = Column(Integer, default=0, comment="Number of rows served from translation cache") insert_status = Column(String, nullable=True, comment="Status of the Superset insert/update operation") superset_execution_id = Column(String, nullable=True, comment="Superset execution/task ID") superset_execution_log = Column(JSON, nullable=True, comment="Superset execution log output") diff --git a/backend/src/plugins/translate/_batch_proc.py b/backend/src/plugins/translate/_batch_proc.py index e5b85435..7a24b05d 100644 --- a/backend/src/plugins/translate/_batch_proc.py +++ b/backend/src/plugins/translate/_batch_proc.py @@ -73,6 +73,8 @@ class BatchProcessingService: llm_rows, pre_rows = self._classify(batch_rows, preview_edits_cache, tls) result["successful"] += self._persist_pre(pre_rows, bid, run_id, tls) + # Count cache hits: pre_rows that have _cached_lang_values (served from cache, not same-lang/approved) + result["cache_hits"] = sum(1 for r in pre_rows if r.get("_cached_lang_values")) if llm_rows: llm_res = self._process_llm(job, run_id, llm_rows, dict_matches, bid, tls) for k in ("successful", "failed", "skipped", "retries"): diff --git a/backend/src/plugins/translate/executor.py b/backend/src/plugins/translate/executor.py index 1d51db0a..e845c048 100644 --- a/backend/src/plugins/translate/executor.py +++ b/backend/src/plugins/translate/executor.py @@ -129,7 +129,7 @@ class TranslationExecutor: target_languages: list[str], language_stats_map: dict[str, TranslationRunLanguageStats] | None = None) -> TranslationRun: """Process all batches: execute, insert to target, check cancellation.""" - successful_records = failed_records = skipped_records = 0 + successful_records = failed_records = skipped_records = cache_hits = 0 for batch_idx, batch_rows in enumerate(batches): batch_result = self._process_batch( job=job, run_id=run.id, batch_index=batch_idx, batch_rows=batch_rows, @@ -138,9 +138,11 @@ class TranslationExecutor: successful_records += batch_result.get("successful", 0) failed_records += batch_result.get("failed", 0) skipped_records += batch_result.get("skipped", 0) + cache_hits += batch_result.get("cache_hits", 0) run.successful_records = successful_records run.failed_records = failed_records run.skipped_records = skipped_records + run.cache_hits = cache_hits self.db.commit() batch_id = batch_result.get("batch_id") diff --git a/backend/src/plugins/translate/orchestrator_aggregator.py b/backend/src/plugins/translate/orchestrator_aggregator.py index 05602c52..1fc6d359 100644 --- a/backend/src/plugins/translate/orchestrator_aggregator.py +++ b/backend/src/plugins/translate/orchestrator_aggregator.py @@ -78,6 +78,7 @@ class TranslationResultAggregator: "successful_records": run.successful_records or 0, "failed_records": run.failed_records or 0, "skipped_records": run.skipped_records or 0, + "cache_hits": run.cache_hits or 0, "insert_status": run.insert_status, "superset_execution_id": run.superset_execution_id, "batch_count": batch_count, diff --git a/backend/src/schemas/translate.py b/backend/src/schemas/translate.py index e1820714..707f179a 100644 --- a/backend/src/schemas/translate.py +++ b/backend/src/schemas/translate.py @@ -406,6 +406,7 @@ class TranslationRunResponse(BaseModel): successful_records: int = 0 failed_records: int = 0 skipped_records: int = 0 + cache_hits: int = 0 insert_status: str | None = None superset_execution_id: str | None = None config_snapshot: dict[str, Any] | None = None diff --git a/frontend/src/lib/components/translate/TranslationRunGlobalIndicator.svelte b/frontend/src/lib/components/translate/TranslationRunGlobalIndicator.svelte index 82d5799f..5f0dfaf3 100644 --- a/frontend/src/lib/components/translate/TranslationRunGlobalIndicator.svelte +++ b/frontend/src/lib/components/translate/TranslationRunGlobalIndicator.svelte @@ -66,6 +66,7 @@ let successfulRecords = $derived(storeState?.successfulRecords || 0); let failedRecords = $derived(storeState?.failedRecords || 0); let skippedRecords = $derived(storeState?.skippedRecords || 0); + let cacheHits = $derived(storeState?.cacheHits || 0); let progressPct = $derived(storeState?.progressPct || 0); // Auto-dismiss terminal states after a few seconds @@ -154,7 +155,7 @@ -
{totalRecords}
@@ -113,6 +114,10 @@ {$t.translate?.run?.skipped}{skippedRecords}
{cacheHits}
+{$t.translate?.run?.total_records}
{status.total_records || 0}
@@ -231,6 +231,10 @@{$t.translate?.run?.skipped}
{status.skipped_records || 0}
Cache
+{status.cache_hits || 0}
+{selectedRunDetail.total_records || 0}
{$t.translate?.history?.total}
@@ -424,6 +425,10 @@{selectedRunDetail.skipped_records || 0}
{$t.translate?.history?.skipped}
{selectedRunDetail.cache_hits || 0}
+Cache
+