diff --git a/frontend/src/routes/admin/ocr/OcrHealthBar.svelte b/frontend/src/routes/admin/ocr/OcrHealthBar.svelte new file mode 100644 index 00000000..8319fc6c --- /dev/null +++ b/frontend/src/routes/admin/ocr/OcrHealthBar.svelte @@ -0,0 +1,14 @@ + + +
+ + + {ocrServiceAvailable ? 'Online' : 'Offline'} + +
diff --git a/frontend/src/routes/admin/ocr/OcrHealthBar.svelte.spec.ts b/frontend/src/routes/admin/ocr/OcrHealthBar.svelte.spec.ts new file mode 100644 index 00000000..5e0a561b --- /dev/null +++ b/frontend/src/routes/admin/ocr/OcrHealthBar.svelte.spec.ts @@ -0,0 +1,18 @@ +import { afterEach, describe, it, expect } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import OcrHealthBar from './OcrHealthBar.svelte'; + +afterEach(cleanup); + +describe('OcrHealthBar', () => { + it('shows online status when OCR service is available', async () => { + render(OcrHealthBar, { ocrServiceAvailable: true }); + await expect.element(page.getByText(/online/i)).toBeInTheDocument(); + }); + + it('shows offline status when OCR service is unavailable', async () => { + render(OcrHealthBar, { ocrServiceAvailable: false }); + await expect.element(page.getByText(/offline/i)).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/routes/admin/ocr/OcrModelsTable.svelte b/frontend/src/routes/admin/ocr/OcrModelsTable.svelte new file mode 100644 index 00000000..785c96b0 --- /dev/null +++ b/frontend/src/routes/admin/ocr/OcrModelsTable.svelte @@ -0,0 +1,67 @@ + + +
+ + + + + + + + + + + + {#each senderModels as model (model.id)} + + + + + + + + {/each} + +
PersonCERAccuracyLinesActions
+ + {personNames[model.personId] ?? model.personId} + + + {model.cer != null ? (model.cer * 100).toFixed(1) + '%' : '—'} + + {model.accuracy != null ? (model.accuracy * 100).toFixed(1) + '%' : '—'} + + {model.correctedLinesAtTraining} + + Details +
+
diff --git a/frontend/src/routes/admin/ocr/OcrModelsTable.svelte.spec.ts b/frontend/src/routes/admin/ocr/OcrModelsTable.svelte.spec.ts new file mode 100644 index 00000000..67ff1e83 --- /dev/null +++ b/frontend/src/routes/admin/ocr/OcrModelsTable.svelte.spec.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, it, expect } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import OcrModelsTable from './OcrModelsTable.svelte'; +import type { components } from '$lib/generated/api'; + +afterEach(cleanup); + +type SenderModel = components['schemas']['SenderModel']; + +const personId = '123e4567-e89b-12d3-a456-426614174000'; +const model: SenderModel = { + id: 'aaa00000-0000-0000-0000-000000000001', + personId, + correctedLinesAtTraining: 120, + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-06-01T00:00:00Z', + cer: 0.04, + accuracy: 0.96 +}; + +describe('OcrModelsTable', () => { + it('shows person name when provided', async () => { + render(OcrModelsTable, { + senderModels: [model], + personNames: { [personId]: 'Anna Müller' } + }); + await expect.element(page.getByText('Anna Müller')).toBeInTheDocument(); + }); + + it('shows person ID when name is missing', async () => { + render(OcrModelsTable, { + senderModels: [model], + personNames: {} + }); + await expect.element(page.getByText(personId)).toBeInTheDocument(); + }); + + it('shows empty state when no models', async () => { + render(OcrModelsTable, { senderModels: [], personNames: {} }); + const rows = document.querySelectorAll('tbody tr'); + expect(rows.length).toBe(0); + }); +}); diff --git a/frontend/src/routes/admin/ocr/OcrStatCards.svelte b/frontend/src/routes/admin/ocr/OcrStatCards.svelte new file mode 100644 index 00000000..6375bdd5 --- /dev/null +++ b/frontend/src/routes/admin/ocr/OcrStatCards.svelte @@ -0,0 +1,36 @@ + + +
+
+
{availableBlocks}
+
+ Training blocks +
+
+
+
{totalOcrBlocks}
+
Total blocks
+
+
+
{availableDocuments}
+
Documents
+
+
+
{availableSegBlocks}
+
Seg. blocks
+
+
diff --git a/frontend/src/routes/admin/ocr/OcrStatCards.svelte.spec.ts b/frontend/src/routes/admin/ocr/OcrStatCards.svelte.spec.ts new file mode 100644 index 00000000..f471c57f --- /dev/null +++ b/frontend/src/routes/admin/ocr/OcrStatCards.svelte.spec.ts @@ -0,0 +1,30 @@ +import { afterEach, describe, it, expect } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import OcrStatCards from './OcrStatCards.svelte'; + +afterEach(cleanup); + +const stats = { + availableBlocks: 42, + totalOcrBlocks: 200, + availableDocuments: 15, + availableSegBlocks: 8 +}; + +describe('OcrStatCards', () => { + it('shows available block count', async () => { + render(OcrStatCards, stats); + await expect.element(page.getByText('42')).toBeInTheDocument(); + }); + + it('shows total OCR block count', async () => { + render(OcrStatCards, stats); + await expect.element(page.getByText('200')).toBeInTheDocument(); + }); + + it('shows available document count', async () => { + render(OcrStatCards, stats); + await expect.element(page.getByText('15')).toBeInTheDocument(); + }); +});