feat(translate): add cache_hits counter — backend + frontend
Backend: - TranslationRun model: add cache_hits Integer column (default 0) - TranslationRunResponse schema: add cache_hits field - _helpers.py/_run_list_routes.py/orchestrator_aggregator.py: include cache_hits in all run API responses - _batch_proc.py: count pre_rows served from translation cache per batch, return cache_hits in batch result - executor.py: accumulate cache_hits across batches, persist to run - Alembic migration: dabc9709 — add cache_hits column to translation_runs Frontend: - translationRun.svelte.ts: add cacheHits to store state, WS handler, polling handler - TranslationRunProgress.svelte: 5-col stats grid with purple Cache card - TranslationRunGlobalIndicator.svelte: 5-col stats with Cache - TranslationRunResult.svelte: 5-col detail stats with Cache card - History page: cache_hits shown in run list row + detail panel Visual: cache hits shown in purple alongside green/yellow/red metrics (total/success/failed/skipped). Visible during run + in history. Tests: backend 69/69 translate tests ✅, frontend 698/698 tests ✅, frontend build ✅
This commit is contained in:
@@ -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 @@
|
||||
</div>
|
||||
|
||||
<!-- Stats grid -->
|
||||
<div class="grid grid-cols-4 gap-2 text-xs">
|
||||
<div class="grid grid-cols-5 gap-2 text-xs">
|
||||
<div class="bg-gray-50 rounded px-2 py-1 text-center">
|
||||
<span class="text-gray-500 block">{($t.translate?.run?.total || 'Total')}</span>
|
||||
<span class="font-bold text-gray-900">{totalRecords}</span>
|
||||
@@ -171,6 +172,10 @@
|
||||
<span class="text-yellow-600 block">{($t.translate?.run?.skipped || 'Skip')}</span>
|
||||
<span class="font-bold text-yellow-700">{skippedRecords}</span>
|
||||
</div>
|
||||
<div class="bg-purple-50 rounded px-2 py-1 text-center">
|
||||
<span class="text-purple-600 block">Cache</span>
|
||||
<span class="font-bold text-purple-700">{cacheHits}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
let successfulRecords = $derived(storeSnapshot?.successfulRecords || 0);
|
||||
let failedRecords = $derived(storeSnapshot?.failedRecords || 0);
|
||||
let skippedRecords = $derived(storeSnapshot?.skippedRecords || 0);
|
||||
let cacheHits = $derived(storeSnapshot?.cacheHits || 0);
|
||||
let progressPct = $derived(storeSnapshot?.progressPct || 0);
|
||||
let batchCount = $derived(storeSnapshot?.batchCount || 0);
|
||||
let insertStatus = $derived(storeSnapshot?.insertStatus || null);
|
||||
@@ -96,7 +97,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="grid grid-cols-4 gap-3 text-sm">
|
||||
<div class="grid grid-cols-5 gap-3 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-500">{$t.translate?.run?.total}</span>
|
||||
<p class="font-semibold">{totalRecords}</p>
|
||||
@@ -113,6 +114,10 @@
|
||||
<span class="text-yellow-600">{$t.translate?.run?.skipped}</span>
|
||||
<p class="font-semibold text-yellow-700">{skippedRecords}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-purple-600">Cache</span>
|
||||
<p class="font-semibold text-purple-700">{cacheHits}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Batch counter -->
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Statistics Grid -->
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||
<div class="grid grid-cols-2 sm:grid-cols-5 gap-4">
|
||||
<div class="bg-gray-50 rounded-lg p-3">
|
||||
<p class="text-xs text-gray-500 uppercase">{$t.translate?.run?.total_records}</p>
|
||||
<p class="text-xl font-bold text-gray-900">{status.total_records || 0}</p>
|
||||
@@ -231,6 +231,10 @@
|
||||
<p class="text-xs text-yellow-600 uppercase">{$t.translate?.run?.skipped}</p>
|
||||
<p class="text-xl font-bold text-yellow-700">{status.skipped_records || 0}</p>
|
||||
</div>
|
||||
<div class="bg-purple-50 rounded-lg p-3">
|
||||
<p class="text-xs text-purple-600 uppercase">Cache</p>
|
||||
<p class="text-xl font-bold text-purple-700">{status.cache_hits || 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Per-language Statistics -->
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface TranslationRunState {
|
||||
successfulRecords: number;
|
||||
failedRecords: number;
|
||||
skippedRecords: number;
|
||||
cacheHits: number;
|
||||
progressPct: number;
|
||||
insertStatus: string | null;
|
||||
batchCount: number;
|
||||
@@ -48,6 +49,7 @@ const initialState: TranslationRunState = {
|
||||
successfulRecords: 0,
|
||||
failedRecords: 0,
|
||||
skippedRecords: 0,
|
||||
cacheHits: 0,
|
||||
progressPct: 0,
|
||||
insertStatus: null,
|
||||
batchCount: 0,
|
||||
@@ -167,6 +169,7 @@ function _connectWebSocket(runId: string): void {
|
||||
successfulRecords: (data.successful_records as number) || 0,
|
||||
failedRecords: (data.failed_records as number) || 0,
|
||||
skippedRecords: (data.skipped_records as number) || 0,
|
||||
cacheHits: (data.cache_hits as number) || 0,
|
||||
progressPct: pct,
|
||||
insertStatus: insertS || null,
|
||||
batchCount: (data.batch_count as number) || 0,
|
||||
@@ -268,6 +271,7 @@ async function pollStatus(): Promise<void> {
|
||||
successfulRecords: (data?.successful_records as number) || 0,
|
||||
failedRecords: (data?.failed_records as number) || 0,
|
||||
skippedRecords: (data?.skipped_records as number) || 0,
|
||||
cacheHits: (data?.cache_hits as number) || 0,
|
||||
progressPct: pct,
|
||||
insertStatus: (data?.insert_status as string) || null,
|
||||
batchCount: (data?.batch_count as number) || 0,
|
||||
|
||||
@@ -287,6 +287,7 @@
|
||||
<span>{_('translate.history.records_label').replace('{count}', run.total_records || 0)}</span>
|
||||
<span>{_('translate.history.ok_label').replace('{count}', run.successful_records || 0)}</span>
|
||||
<span>{_('translate.history.fail_label').replace('{count}', run.failed_records || 0)}</span>
|
||||
<span class="text-purple-500">Cache: {run.cache_hits || 0}</span>
|
||||
{#if run.created_by}
|
||||
<span>{_('translate.history.by_label').replace('{user}', run.created_by)}</span>
|
||||
{/if}
|
||||
@@ -407,7 +408,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div class="grid grid-cols-4 gap-3 mb-6">
|
||||
<div class="grid grid-cols-5 gap-3 mb-6">
|
||||
<div class="bg-gray-50 rounded p-3 text-center">
|
||||
<p class="text-lg font-bold text-gray-900">{selectedRunDetail.total_records || 0}</p>
|
||||
<p class="text-xs text-gray-500">{$t.translate?.history?.total}</p>
|
||||
@@ -424,6 +425,10 @@
|
||||
<p class="text-lg font-bold text-yellow-700">{selectedRunDetail.skipped_records || 0}</p>
|
||||
<p class="text-xs text-yellow-600">{$t.translate?.history?.skipped}</p>
|
||||
</div>
|
||||
<div class="bg-purple-50 rounded p-3 text-center">
|
||||
<p class="text-lg font-bold text-purple-700">{selectedRunDetail.cache_hits || 0}</p>
|
||||
<p class="text-xs text-purple-600">Cache</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Per-Language Statistics -->
|
||||
|
||||
Reference in New Issue
Block a user