fix(i18n): localize admin diagnostics

This commit is contained in:
cesnimda
2026-08-29 22:35:28 +02:00
parent 0ef51fed74
commit ee8eacf0de
4 changed files with 134 additions and 41 deletions
+2
View File
@@ -35,6 +35,7 @@ Updated: 2026-08-29
- Localized the complete public landing experience—hero, previews, feature cards, workflow, plan capabilities, CTAs, accessibility text, and footer—while retaining the server-aligned Free/Pro plan structure and commercial-terms safeguards. - Localized the complete public landing experience—hero, previews, feature cards, workflow, plan capabilities, CTAs, accessibility text, and footer—while retaining the server-aligned Free/Pro plan structure and commercial-terms safeguards.
- Reused the shared EN/NO language control on the unauthenticated landing page, so language can be changed before sign-in and persists through the existing locale mechanism. - Reused the shared EN/NO language control on the unauthenticated landing page, so language can be changed before sign-in and persists through the existing locale mechanism.
- Finished the remaining active CV Builder format labels: long-document page counts, US Letter naming, and localized long/short date examples now follow the selected UI language. - Finished the remaining active CV Builder format labels: long-document page counts, US Letter naming, and localized long/short date examples now follow the selected UI language.
- Localized the Admin System runtime and CV benchmark diagnostics, including probe/email failures, model/Ollama state, parser findings, benchmark summaries, fallback guidance, and locale-aware timestamps while leaving runtime/provider/file values untouched.
### In progress ### In progress
@@ -87,6 +88,7 @@ Updated: 2026-08-29
- Next optimized production build and integrated TypeScript check passed after the Job Email localization batch. - Next optimized production build and integrated TypeScript check passed after the Job Email localization batch.
- Public landing focused verification: 2 suites, 3/3 passed, including live switching to Bokmål and preservation of protected-route sign-in intent; TypeScript passed. - Public landing focused verification: 2 suites, 3/3 passed, including live switching to Bokmål and preservation of protected-route sign-in intent; TypeScript passed.
- CV Builder focused verification: 12/12 passed; TypeScript passed after the final format-label pass. - CV Builder focused verification: 12/12 passed; TypeScript passed after the final format-label pass.
- Admin System focused verification: 3/3 passed, including Bokmål benchmark diagnostics that preserve runtime values; TypeScript passed.
- Backend matcher/intelligence focused verification: 35/35 passed, including detection of a manually created Norwegian advert with no saved translation. - Backend matcher/intelligence focused verification: 35/35 passed, including detection of a manually created Norwegian advert with no saved translation.
- Full backend: 712/712 tests passed on .NET 9. - Full backend: 712/712 tests passed on .NET 9.
- Next optimized production build and TypeScript: passed after the Job Workspace/checklist batch. - Next optimized production build and TypeScript: passed after the Job Workspace/checklist batch.
@@ -21,6 +21,7 @@ const mockedApi = api as jest.Mocked<typeof api>;
describe('AdminSystemPage', () => { describe('AdminSystemPage', () => {
beforeEach(() => { beforeEach(() => {
window.localStorage.clear();
mockedApi.get.mockImplementation((url: string) => { mockedApi.get.mockImplementation((url: string) => {
if (url === '/admin/system') { if (url === '/admin/system') {
return Promise.resolve({ return Promise.resolve({
@@ -199,4 +200,14 @@ describe('AdminSystemPage', () => {
})); }));
}); });
}); });
it('localizes benchmark diagnostics without changing runtime values', async () => {
window.localStorage.setItem('uiLanguage', 'nb');
render(<I18nProvider><AdminSystemPage /></I18nProvider>);
expect(await screen.findByText('Gjennomgang av CV-referansetest')).toBeTruthy();
expect(screen.getByText(/Mistenkelig kontaktsted: san francisco, hobbies/i)).toBeTruthy();
expect(screen.getByText('Production')).toBeTruthy();
expect(screen.getByText(/Modell · qwen2.5:7b/i)).toBeTruthy();
});
}); });
+80
View File
@@ -1367,6 +1367,46 @@ export const translations = {
kanbanFollowUpNow: "Follow up now", kanbanFollowUpNow: "Follow up now",
kanbanReplyDueIn: "Reply due in {days}d", kanbanReplyDueIn: "Reply due in {days}d",
adminSystemTitle: "System status", adminSystemTitle: "System status",
adminSystemLoadFailed: "Failed to load system status.",
adminSystemTestEmailFailed: "Failed to send test email.",
adminSystemEmailSaveFailed: "Failed to save email settings.",
adminSystemProbeFailed: "Failed to run AI service probe.",
adminSystemVersionCommit: "Version {version} · Commit {commit}",
adminSystemNoTarget: "No target",
adminSystemUnknownDevice: "unknown device",
adminSystemProbeDevice: "{latency} ms probe · {device}",
adminSystemHealthDevice: "{latency} ms health · {device}",
adminSystemOllamaVersion: "Ollama version",
adminSystemLoadedModels: "Loaded models",
adminSystemOcrEnabled: "OCR {languages}",
adminSystemModelName: "Model · {model}",
adminSystemLoadedModel: "Loaded · {model}",
adminBenchmarkTitle: "CV benchmark review",
adminBenchmarkRoot: "Benchmark root",
adminBenchmarkLastUpdate: "Last benchmark update",
adminBenchmarkCorpusRoot: "Corpus root",
adminBenchmarkFiles: "Files",
adminBenchmarkCorpusInputs: "Corpus inputs",
adminBenchmarkCoverage: "Coverage",
adminBenchmarkCoverageHelp: "Structured field coverage",
adminBenchmarkConfidence: "Confidence",
adminBenchmarkConfidenceHelp: "Field metadata confidence",
adminBenchmarkConsistency: "Consistency",
adminBenchmarkConsistencyHelp: "Normalization consistency",
adminBenchmarkMissingApproved: "Missing approved",
adminBenchmarkNeedsReview: "Needs fixture review",
adminBenchmarkTopFindings: "Top parser findings",
adminBenchmarkNoAnomalies: "No standout benchmark anomalies in the current run.",
adminBenchmarkWeakest: "Weakest files in current run",
adminBenchmarkCoverageValue: "Coverage {value}",
adminBenchmarkConfidenceValue: "Confidence {value}",
adminBenchmarkConsistencyValue: "Consistency {value}",
adminBenchmarkLatestSummary: "Latest markdown summary",
adminBenchmarkRunScript: "Run scripts/run-cv-benchmark.sh to generate the latest corpus report and fixture candidates.",
adminBenchmarkSuspiciousLocation: "Suspicious contact location: {value}",
adminBenchmarkEducationOvercaptured: "Education qualification looks over-captured.",
adminBenchmarkWorkTitleOvercaptured: "Work title looks over-captured.",
adminBenchmarkQualificationOther: "Qualification level fell back to Other.",
adminSystemSubtitle: "Production diagnostics for runtime, database, auth, email, AI service health, and OCR readiness.", adminSystemSubtitle: "Production diagnostics for runtime, database, auth, email, AI service health, and OCR readiness.",
adminSystemRunProbe: "Run probe now", adminSystemRunProbe: "Run probe now",
adminSystemRunningProbe: "Running probe...", adminSystemRunningProbe: "Running probe...",
@@ -3456,6 +3496,46 @@ export const translations = {
kanbanFollowUpNow: "Følg opp nå", kanbanFollowUpNow: "Følg opp nå",
kanbanReplyDueIn: "Svar forfaller om {days}d", kanbanReplyDueIn: "Svar forfaller om {days}d",
adminSystemTitle: "Systemstatus", adminSystemTitle: "Systemstatus",
adminSystemLoadFailed: "Kunne ikke laste systemstatus.",
adminSystemTestEmailFailed: "Kunne ikke sende test-e-post.",
adminSystemEmailSaveFailed: "Kunne ikke lagre e-postinnstillingene.",
adminSystemProbeFailed: "Kunne ikke kjøre kontrollen av AI-tjenesten.",
adminSystemVersionCommit: "Versjon {version} · Commit {commit}",
adminSystemNoTarget: "Ingen målressurs",
adminSystemUnknownDevice: "ukjent enhet",
adminSystemProbeDevice: "{latency} ms kontroll · {device}",
adminSystemHealthDevice: "{latency} ms helse · {device}",
adminSystemOllamaVersion: "Ollama-versjon",
adminSystemLoadedModels: "Innlastede modeller",
adminSystemOcrEnabled: "OCR {languages}",
adminSystemModelName: "Modell · {model}",
adminSystemLoadedModel: "Innlastet · {model}",
adminBenchmarkTitle: "Gjennomgang av CV-referansetest",
adminBenchmarkRoot: "Rotmappe for referansetest",
adminBenchmarkLastUpdate: "Siste oppdatering av referansetest",
adminBenchmarkCorpusRoot: "Rotmappe for korpus",
adminBenchmarkFiles: "Filer",
adminBenchmarkCorpusInputs: "Inndata i korpus",
adminBenchmarkCoverage: "Dekning",
adminBenchmarkCoverageHelp: "Dekning av strukturerte felt",
adminBenchmarkConfidence: "Sikkerhet",
adminBenchmarkConfidenceHelp: "Sikkerhet i feltmetadata",
adminBenchmarkConsistency: "Konsistens",
adminBenchmarkConsistencyHelp: "Konsistens i normalisering",
adminBenchmarkMissingApproved: "Mangler godkjenning",
adminBenchmarkNeedsReview: "Trenger gjennomgang av testdata",
adminBenchmarkTopFindings: "Viktigste parserfunn",
adminBenchmarkNoAnomalies: "Ingen fremtredende avvik i den gjeldende kjøringen.",
adminBenchmarkWeakest: "Svakeste filer i gjeldende kjøring",
adminBenchmarkCoverageValue: "Dekning {value}",
adminBenchmarkConfidenceValue: "Sikkerhet {value}",
adminBenchmarkConsistencyValue: "Konsistens {value}",
adminBenchmarkLatestSummary: "Siste markdown-sammendrag",
adminBenchmarkRunScript: "Kjør scripts/run-cv-benchmark.sh for å generere den nyeste korpusrapporten og kandidater til testdata.",
adminBenchmarkSuspiciousLocation: "Mistenkelig kontaktsted: {value}",
adminBenchmarkEducationOvercaptured: "Utdanningskvalifikasjonen ser ut til å inneholde for mye tekst.",
adminBenchmarkWorkTitleOvercaptured: "Jobbtittelen ser ut til å inneholde for mye tekst.",
adminBenchmarkQualificationOther: "Kvalifikasjonsnivået falt tilbake til Annet.",
adminSystemSubtitle: "Produksjonsdiagnostikk for kjøretid, database, autentisering, e-post, AI-tjenestehelse og OCR-beredskap.", adminSystemSubtitle: "Produksjonsdiagnostikk for kjøretid, database, autentisering, e-post, AI-tjenestehelse og OCR-beredskap.",
adminSystemRunProbe: "Kjør probe nå", adminSystemRunProbe: "Kjør probe nå",
adminSystemRunningProbe: "Kjører probe...", adminSystemRunningProbe: "Kjører probe...",
+41 -41
View File
@@ -166,8 +166,8 @@ function displayMetadata(value?: string | null) {
return value && value.trim().length > 0 ? value : "-"; return value && value.trim().length > 0 ? value : "-";
} }
function formatDate(value?: string | null) { function formatDate(value?: string | null, locale?: string) {
return value ? new Date(value).toLocaleString() : "-"; return value ? new Date(value).toLocaleString(locale) : "-";
} }
function formatPercent(value?: number | null) { function formatPercent(value?: number | null) {
@@ -206,7 +206,7 @@ function DetailRow({ label, value }: { label: string; value: React.ReactNode })
} }
export default function AdminSystemPage() { export default function AdminSystemPage() {
const { t } = useI18n(); const { language, t } = useI18n();
const [tab, setTab] = useState(0); const [tab, setTab] = useState(0);
const [status, setStatus] = useState<SystemStatus | null>(null); const [status, setStatus] = useState<SystemStatus | null>(null);
const [emailSettings, setEmailSettings] = useState<EditableEmailSettings | null>(null); const [emailSettings, setEmailSettings] = useState<EditableEmailSettings | null>(null);
@@ -237,7 +237,7 @@ export default function AdminSystemPage() {
setSmtpPassword(""); setSmtpPassword("");
setClearPassword(false); setClearPassword(false);
} catch (e: any) { } catch (e: any) {
setError(getApiErrorMessage(e, "Failed to load system status.")); setError(getApiErrorMessage(e, t("adminSystemLoadFailed")));
setStatus(null); setStatus(null);
setEmailSettings(null); setEmailSettings(null);
setBenchmarkStatus(null); setBenchmarkStatus(null);
@@ -277,20 +277,20 @@ export default function AdminSystemPage() {
return benchmarkIndex.Entries.flatMap((entry) => { return benchmarkIndex.Entries.flatMap((entry) => {
const findings: Array<{ file: string; issue: string }> = []; const findings: Array<{ file: string; issue: string }> = [];
if (entry.ContactLocation && /(culture|education|arial|hobbies|cooperate|ag, ni|bold)/i.test(entry.ContactLocation)) { if (entry.ContactLocation && /(culture|education|arial|hobbies|cooperate|ag, ni|bold)/i.test(entry.ContactLocation)) {
findings.push({ file: entry.FileName, issue: `Suspicious contact location: ${entry.ContactLocation}` }); findings.push({ file: entry.FileName, issue: t("adminBenchmarkSuspiciousLocation", { value: entry.ContactLocation }) });
} }
if (entry.FirstEducation && entry.FirstEducation.length > 120) { if (entry.FirstEducation && entry.FirstEducation.length > 120) {
findings.push({ file: entry.FileName, issue: "Education qualification looks over-captured." }); findings.push({ file: entry.FileName, issue: t("adminBenchmarkEducationOvercaptured") });
} }
if ((entry.FirstJob ?? "").length > 120) { if ((entry.FirstJob ?? "").length > 120) {
findings.push({ file: entry.FileName, issue: "Work title looks over-captured." }); findings.push({ file: entry.FileName, issue: t("adminBenchmarkWorkTitleOvercaptured") });
} }
if ((entry.QualificationLevels ?? []).includes("Other")) { if ((entry.QualificationLevels ?? []).includes("Other")) {
findings.push({ file: entry.FileName, issue: "Qualification level fell back to Other." }); findings.push({ file: entry.FileName, issue: t("adminBenchmarkQualificationOther") });
} }
return findings; return findings;
}).slice(0, 10); }).slice(0, 10);
}, [benchmarkIndex]); }, [benchmarkIndex, language]);
const sendTestEmail = async () => { const sendTestEmail = async () => {
setSendingTestEmail(true); setSendingTestEmail(true);
@@ -302,7 +302,7 @@ export default function AdminSystemPage() {
}); });
setError(null); setError(null);
} catch (e: any) { } catch (e: any) {
setError(getApiErrorMessage(e, "Failed to send test email.")); setError(getApiErrorMessage(e, t("adminSystemTestEmailFailed")));
} finally { } finally {
setSendingTestEmail(false); setSendingTestEmail(false);
} }
@@ -329,7 +329,7 @@ export default function AdminSystemPage() {
setClearPassword(false); setClearPassword(false);
await load(); await load();
} catch (e: any) { } catch (e: any) {
setError(getApiErrorMessage(e, "Failed to save email settings.")); setError(getApiErrorMessage(e, t("adminSystemEmailSaveFailed")));
} finally { } finally {
setSavingSettings(false); setSavingSettings(false);
} }
@@ -352,7 +352,7 @@ export default function AdminSystemPage() {
await api.post("/admin/system/ai/probe"); await api.post("/admin/system/ai/probe");
await load(); await load();
} catch (e: any) { } catch (e: any) {
setError(getApiErrorMessage(e, "Failed to run AI service probe.")); setError(getApiErrorMessage(e, t("adminSystemProbeFailed")));
} finally { } finally {
setRunningProbe(false); setRunningProbe(false);
} }
@@ -382,12 +382,12 @@ export default function AdminSystemPage() {
<SummaryCard <SummaryCard
title={t("adminSystemEnvironment")} title={t("adminSystemEnvironment")}
value={status?.environment ?? "-"} value={status?.environment ?? "-"}
subtitle={`Version ${displayMetadata(status?.version)} · Commit ${displayMetadata(status?.commitSha)}`} subtitle={t("adminSystemVersionCommit", { version: displayMetadata(status?.version), commit: displayMetadata(status?.commitSha) })}
/> />
<SummaryCard <SummaryCard
title={t("adminSystemDatabase")} title={t("adminSystemDatabase")}
value={status ? (status.database.canConnect ? t("adminSystemConnected") : t("adminSystemOffline")) : "-"} value={status ? (status.database.canConnect ? t("adminSystemConnected") : t("adminSystemOffline")) : "-"}
subtitle={status ? `${status.database.provider} · ${status.database.target || "No target"}` : "-"} subtitle={status ? `${status.database.provider} · ${status.database.target || t("adminSystemNoTarget")}` : "-"}
tone={dbTone} tone={dbTone}
/> />
<SummaryCard <SummaryCard
@@ -400,9 +400,9 @@ export default function AdminSystemPage() {
title={t("adminSystemSummarizer")} title={t("adminSystemSummarizer")}
value={status?.ai.healthy ? t("adminSystemHealthy") : t("adminSystemOffline")} value={status?.ai.healthy ? t("adminSystemHealthy") : t("adminSystemOffline")}
subtitle={status?.ai.probeLatencyMs != null subtitle={status?.ai.probeLatencyMs != null
? `${status.ai.probeLatencyMs} ms probe · ${status.ai.device || "unknown device"}` ? t("adminSystemProbeDevice", { latency: status.ai.probeLatencyMs, device: status.ai.device || t("adminSystemUnknownDevice") })
: status?.ai.healthLatencyMs != null : status?.ai.healthLatencyMs != null
? `${status.ai.healthLatencyMs} ms health · ${status.ai.device || "unknown device"}` ? t("adminSystemHealthDevice", { latency: status.ai.healthLatencyMs, device: status.ai.device || t("adminSystemUnknownDevice") })
: t("adminSystemNoLatencyData")} : t("adminSystemNoLatencyData")}
tone={aiTone} tone={aiTone}
/> />
@@ -468,13 +468,13 @@ export default function AdminSystemPage() {
<DetailRow label={t("adminSystemOllamaReachable")} value={status?.ai.ollamaReachable ? t("yes") : t("noWord")} /> <DetailRow label={t("adminSystemOllamaReachable")} value={status?.ai.ollamaReachable ? t("yes") : t("noWord")} />
<DetailRow label={t("adminSystemOllamaModel")} value={status?.ai.ollamaModel || "-"} /> <DetailRow label={t("adminSystemOllamaModel")} value={status?.ai.ollamaModel || "-"} />
<DetailRow label={t("adminSystemOllamaModelAvailable")} value={status?.ai.ollamaModelAvailable ? t("yes") : t("noWord")} /> <DetailRow label={t("adminSystemOllamaModelAvailable")} value={status?.ai.ollamaModelAvailable ? t("yes") : t("noWord")} />
<DetailRow label="Ollama version" value={status?.ai.ollamaVersion || "-"} /> <DetailRow label={t("adminSystemOllamaVersion")} value={status?.ai.ollamaVersion || "-"} />
<DetailRow label="Loaded models" value={status?.ai.ollamaLoadedCount ?? 0} /> <DetailRow label={t("adminSystemLoadedModels")} value={status?.ai.ollamaLoadedCount ?? 0} />
<DetailRow label={t("adminSystemHealthLatency")} value={status?.ai.healthLatencyMs != null ? `${status.ai.healthLatencyMs} ms` : "-"} /> <DetailRow label={t("adminSystemHealthLatency")} value={status?.ai.healthLatencyMs != null ? `${status.ai.healthLatencyMs} ms` : "-"} />
<DetailRow label={t("adminSystemProbeLatency")} value={status?.ai.probeLatencyMs != null ? `${status.ai.probeLatencyMs} ms` : "-"} /> <DetailRow label={t("adminSystemProbeLatency")} value={status?.ai.probeLatencyMs != null ? `${status.ai.probeLatencyMs} ms` : "-"} />
<DetailRow label={t("adminSystemLastProbe")} value={formatDate(status?.ai.lastProbeAt)} /> <DetailRow label={t("adminSystemLastProbe")} value={formatDate(status?.ai.lastProbeAt, language === "nb" ? "nb-NO" : "en")} />
<DetailRow label={t("adminSystemLastSuccessfulProbe")} value={formatDate(status?.ai.lastProbeSuccessAt)} /> <DetailRow label={t("adminSystemLastSuccessfulProbe")} value={formatDate(status?.ai.lastProbeSuccessAt, language === "nb" ? "nb-NO" : "en")} />
<DetailRow label={t("adminSystemLastSummarizationSuccess")} value={formatDate(status?.ai.lastSuccessAt)} /> <DetailRow label={t("adminSystemLastSummarizationSuccess")} value={formatDate(status?.ai.lastSuccessAt, language === "nb" ? "nb-NO" : "en")} />
</Stack> </Stack>
</Paper> </Paper>
</Box> </Box>
@@ -497,57 +497,57 @@ export default function AdminSystemPage() {
<Chip label={status?.auth.googleConfigured ? t("adminSystemGoogleReady") : t("adminSystemGoogleOff")} variant="outlined" size="small" /> <Chip label={status?.auth.googleConfigured ? t("adminSystemGoogleReady") : t("adminSystemGoogleOff")} variant="outlined" size="small" />
<Chip label={status?.auth.gmailConfigured ? t("adminSystemGmailReady") : t("adminSystemGmailIncomplete")} variant="outlined" size="small" /> <Chip label={status?.auth.gmailConfigured ? t("adminSystemGmailReady") : t("adminSystemGmailIncomplete")} variant="outlined" size="small" />
<Chip label={status?.ai.gpuAvailable ? t("adminSystemGpuVisible") : t("adminSystemCpuMode")} color={status?.ai.gpuAvailable ? "success" : "default"} size="small" /> <Chip label={status?.ai.gpuAvailable ? t("adminSystemGpuVisible") : t("adminSystemCpuMode")} color={status?.ai.gpuAvailable ? "success" : "default"} size="small" />
<Chip label={status?.ai.ocrAvailable ? `OCR ${status.ai.ocrLanguages || "enabled"}` : t("adminSystemOcrUnavailable")} variant="outlined" size="small" /> <Chip label={status?.ai.ocrAvailable ? t("adminSystemOcrEnabled", { languages: status.ai.ocrLanguages || t("adminSystemEnabled") }) : t("adminSystemOcrUnavailable")} variant="outlined" size="small" />
{(status?.ai.ollamaInstalledModels ?? []).slice(0, 4).map((model) => ( {(status?.ai.ollamaInstalledModels ?? []).slice(0, 4).map((model) => (
<Chip key={model} label={`Model · ${model}`} variant="outlined" size="small" /> <Chip key={model} label={t("adminSystemModelName", { model })} variant="outlined" size="small" />
))} ))}
{(status?.ai.ollamaLoadedModels ?? []).slice(0, 3).map((model) => ( {(status?.ai.ollamaLoadedModels ?? []).slice(0, 3).map((model) => (
<Chip key={`loaded-${model}`} label={`Loaded · ${model}`} color="primary" variant="outlined" size="small" /> <Chip key={`loaded-${model}`} label={t("adminSystemLoadedModel", { model })} color="primary" variant="outlined" size="small" />
))} ))}
</Box> </Box>
</Paper> </Paper>
<Paper sx={{ p: 2, borderRadius: 3 }}> <Paper sx={{ p: 2, borderRadius: 3 }}>
<Typography variant="h6" sx={{ fontWeight: 900, mb: 1 }}>CV benchmark review</Typography> <Typography variant="h6" sx={{ fontWeight: 900, mb: 1 }}>{t("adminBenchmarkTitle")}</Typography>
<Stack spacing={0.75}> <Stack spacing={0.75}>
<DetailRow label="Benchmark root" value={benchmarkStatus?.rootPath || "-"} /> <DetailRow label={t("adminBenchmarkRoot")} value={benchmarkStatus?.rootPath || "-"} />
<DetailRow label="Last benchmark update" value={formatDate(benchmarkStatus?.lastUpdatedAtUtc)} /> <DetailRow label={t("adminBenchmarkLastUpdate")} value={formatDate(benchmarkStatus?.lastUpdatedAtUtc, language === "nb" ? "nb-NO" : "en")} />
<DetailRow label="Corpus root" value={benchmarkIndex?.CorpusRoot || "-"} /> <DetailRow label={t("adminBenchmarkCorpusRoot")} value={benchmarkIndex?.CorpusRoot || "-"} />
</Stack> </Stack>
{benchmarkIndex ? ( {benchmarkIndex ? (
<> <>
<Box sx={{ mt: 2, display: "grid", gridTemplateColumns: { xs: "1fr 1fr", md: "repeat(5, 1fr)" }, gap: 1.25 }}> <Box sx={{ mt: 2, display: "grid", gridTemplateColumns: { xs: "1fr 1fr", md: "repeat(5, 1fr)" }, gap: 1.25 }}>
<SummaryCard title="Files" value={String(benchmarkIndex.TotalFiles)} subtitle="Corpus inputs" /> <SummaryCard title={t("adminBenchmarkFiles")} value={String(benchmarkIndex.TotalFiles)} subtitle={t("adminBenchmarkCorpusInputs")} />
<SummaryCard title="Coverage" value={formatPercent(benchmarkIndex.AverageCoverage)} subtitle="Structured field coverage" tone={benchmarkTone(benchmarkIndex.AverageCoverage)} /> <SummaryCard title={t("adminBenchmarkCoverage")} value={formatPercent(benchmarkIndex.AverageCoverage)} subtitle={t("adminBenchmarkCoverageHelp")} tone={benchmarkTone(benchmarkIndex.AverageCoverage)} />
<SummaryCard title="Confidence" value={formatPercent(benchmarkIndex.AverageConfidence)} subtitle="Field metadata confidence" tone={benchmarkTone(benchmarkIndex.AverageConfidence)} /> <SummaryCard title={t("adminBenchmarkConfidence")} value={formatPercent(benchmarkIndex.AverageConfidence)} subtitle={t("adminBenchmarkConfidenceHelp")} tone={benchmarkTone(benchmarkIndex.AverageConfidence)} />
<SummaryCard title="Consistency" value={formatPercent(benchmarkIndex.AverageConsistency)} subtitle="Normalization consistency" tone={benchmarkTone(benchmarkIndex.AverageConsistency)} /> <SummaryCard title={t("adminBenchmarkConsistency")} value={formatPercent(benchmarkIndex.AverageConsistency)} subtitle={t("adminBenchmarkConsistencyHelp")} tone={benchmarkTone(benchmarkIndex.AverageConsistency)} />
<SummaryCard title="Missing approved" value={String(benchmarkIndex.MissingApprovedFixtures)} subtitle="Needs fixture review" tone={benchmarkIndex.MissingApprovedFixtures > 0 ? "warning" : "success"} /> <SummaryCard title={t("adminBenchmarkMissingApproved")} value={String(benchmarkIndex.MissingApprovedFixtures)} subtitle={t("adminBenchmarkNeedsReview")} tone={benchmarkIndex.MissingApprovedFixtures > 0 ? "warning" : "success"} />
</Box> </Box>
<Box sx={{ mt: 2.5, display: "grid", gridTemplateColumns: { xs: "1fr", xl: "1.1fr 0.9fr" }, gap: 2 }}> <Box sx={{ mt: 2.5, display: "grid", gridTemplateColumns: { xs: "1fr", xl: "1.1fr 0.9fr" }, gap: 2 }}>
<Paper variant="outlined" sx={{ p: 1.5, borderRadius: 3 }}> <Paper variant="outlined" sx={{ p: 1.5, borderRadius: 3 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 900, mb: 1 }}>Top parser findings</Typography> <Typography variant="subtitle1" sx={{ fontWeight: 900, mb: 1 }}>{t("adminBenchmarkTopFindings")}</Typography>
<Stack spacing={1}> <Stack spacing={1}>
{benchmarkFindings.length > 0 ? benchmarkFindings.map((finding) => ( {benchmarkFindings.length > 0 ? benchmarkFindings.map((finding) => (
<Box key={`${finding.file}:${finding.issue}`} sx={{ p: 1.25, borderRadius: 2, backgroundColor: "background.default", border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)" }}> <Box key={`${finding.file}:${finding.issue}`} sx={{ p: 1.25, borderRadius: 2, backgroundColor: "background.default", border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)" }}>
<Typography variant="caption" sx={{ color: "text.secondary" }}>{finding.file}</Typography> <Typography variant="caption" sx={{ color: "text.secondary" }}>{finding.file}</Typography>
<Typography variant="body2">{finding.issue}</Typography> <Typography variant="body2">{finding.issue}</Typography>
</Box> </Box>
)) : <Typography variant="body2" sx={{ color: "text.secondary" }}>No standout benchmark anomalies in the current run.</Typography>} )) : <Typography variant="body2" sx={{ color: "text.secondary" }}>{t("adminBenchmarkNoAnomalies")}</Typography>}
</Stack> </Stack>
</Paper> </Paper>
<Paper variant="outlined" sx={{ p: 1.5, borderRadius: 3 }}> <Paper variant="outlined" sx={{ p: 1.5, borderRadius: 3 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 900, mb: 1 }}>Weakest files in current run</Typography> <Typography variant="subtitle1" sx={{ fontWeight: 900, mb: 1 }}>{t("adminBenchmarkWeakest")}</Typography>
<Stack spacing={1}> <Stack spacing={1}>
{weakestEntries.map((entry) => ( {weakestEntries.map((entry) => (
<Box key={entry.Slug} sx={{ p: 1.25, borderRadius: 2, backgroundColor: "background.default", border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)" }}> <Box key={entry.Slug} sx={{ p: 1.25, borderRadius: 2, backgroundColor: "background.default", border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)" }}>
<Typography variant="body2" sx={{ fontWeight: 800 }}>{entry.FileName}</Typography> <Typography variant="body2" sx={{ fontWeight: 800 }}>{entry.FileName}</Typography>
<Box sx={{ display: "flex", gap: 0.75, flexWrap: "wrap", mt: 0.75 }}> <Box sx={{ display: "flex", gap: 0.75, flexWrap: "wrap", mt: 0.75 }}>
<Chip size="small" label={`Coverage ${formatPercent(entry.CoverageScore)}`} color={benchmarkTone(entry.CoverageScore)} /> <Chip size="small" label={t("adminBenchmarkCoverageValue", { value: formatPercent(entry.CoverageScore) })} color={benchmarkTone(entry.CoverageScore)} />
<Chip size="small" label={`Confidence ${formatPercent(entry.ConfidenceScore)}`} color={benchmarkTone(entry.ConfidenceScore)} /> <Chip size="small" label={t("adminBenchmarkConfidenceValue", { value: formatPercent(entry.ConfidenceScore) })} color={benchmarkTone(entry.ConfidenceScore)} />
<Chip size="small" label={`Consistency ${formatPercent(entry.ConsistencyScore)}`} color={benchmarkTone(entry.ConsistencyScore)} /> <Chip size="small" label={t("adminBenchmarkConsistencyValue", { value: formatPercent(entry.ConsistencyScore) })} color={benchmarkTone(entry.ConsistencyScore)} />
</Box> </Box>
<Typography variant="caption" sx={{ display: "block", color: "text.secondary", mt: 0.75 }}>{entry.DiffSummary || "-"}</Typography> <Typography variant="caption" sx={{ display: "block", color: "text.secondary", mt: 0.75 }}>{entry.DiffSummary || "-"}</Typography>
</Box> </Box>
@@ -557,7 +557,7 @@ export default function AdminSystemPage() {
</Box> </Box>
<Box sx={{ mt: 2, p: 1.5, borderRadius: 2, backgroundColor: "background.default", border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)", maxHeight: 280, overflow: "auto" }}> <Box sx={{ mt: 2, p: 1.5, borderRadius: 2, backgroundColor: "background.default", border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)", maxHeight: 280, overflow: "auto" }}>
<Typography variant="subtitle2" sx={{ fontWeight: 800, mb: 1 }}>Latest markdown summary</Typography> <Typography variant="subtitle2" sx={{ fontWeight: 800, mb: 1 }}>{t("adminBenchmarkLatestSummary")}</Typography>
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap", fontFamily: "ui-monospace, SFMono-Regular, monospace" }}> <Typography variant="body2" sx={{ whiteSpace: "pre-wrap", fontFamily: "ui-monospace, SFMono-Regular, monospace" }}>
{benchmarkStatus?.reportMarkdown || "-"} {benchmarkStatus?.reportMarkdown || "-"}
</Typography> </Typography>
@@ -566,7 +566,7 @@ export default function AdminSystemPage() {
) : ( ) : (
<Box sx={{ mt: 1.5, p: 1.5, borderRadius: 2, backgroundColor: "background.default", border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)" }}> <Box sx={{ mt: 1.5, p: 1.5, borderRadius: 2, backgroundColor: "background.default", border: "none", boxShadow: "0px 1px 2px 0px rgba(15,23,42,0.04), 0px 8px 24px -12px rgba(15,23,42,0.12)" }}>
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap", fontFamily: "ui-monospace, SFMono-Regular, monospace" }}> <Typography variant="body2" sx={{ whiteSpace: "pre-wrap", fontFamily: "ui-monospace, SFMono-Regular, monospace" }}>
{benchmarkStatus?.reportMarkdown || "Run scripts/run-cv-benchmark.sh to generate the latest corpus report and fixture candidates."} {benchmarkStatus?.reportMarkdown || t("adminBenchmarkRunScript")}
</Typography> </Typography>
</Box> </Box>
)} )}