From c484a89b78b220c9357e8d6112aa0faf84806284 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Wed, 4 Mar 2026 08:39:55 +0100 Subject: [PATCH] fix: dewarp UI shows detection details, quality gate status, confidence bars - Add DewarpDetection type with per-method results - Expand method labels for all 4 detectors (A-D) - Show green/amber banner: applied vs quality-gate-rejected - Expandable "Details" panel showing all 4 methods with confidence bars - Visual confidence bars instead of plain percentage Co-Authored-By: Claude Opus 4.6 --- .../app/(admin)/ai/ocr-pipeline/types.ts | 9 +- .../ocr-pipeline/DewarpControls.tsx | 134 ++++++++++++++++-- 2 files changed, 129 insertions(+), 14 deletions(-) diff --git a/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts b/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts index bbb3be0..7213df0 100644 --- a/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts +++ b/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts @@ -50,13 +50,20 @@ export interface DeskewGroundTruth { notes?: string } +export interface DewarpDetection { + method: string + shear_degrees: number + confidence: number +} + export interface DewarpResult { session_id: string - method_used: 'vertical_edge' | 'manual' | 'none' + method_used: string shear_degrees: number confidence: number duration_seconds: number dewarped_image_url: string + detections?: DewarpDetection[] } export interface DewarpGroundTruth { diff --git a/admin-lehrer/components/ocr-pipeline/DewarpControls.tsx b/admin-lehrer/components/ocr-pipeline/DewarpControls.tsx index e199e25..d5d83bf 100644 --- a/admin-lehrer/components/ocr-pipeline/DewarpControls.tsx +++ b/admin-lehrer/components/ocr-pipeline/DewarpControls.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect, useState } from 'react' -import type { DewarpResult, DewarpGroundTruth } from '@/app/(admin)/ai/ocr-pipeline/types' +import type { DewarpResult, DewarpDetection, DewarpGroundTruth } from '@/app/(admin)/ai/ocr-pipeline/types' interface DewarpControlsProps { dewarpResult: DewarpResult | null @@ -14,11 +14,35 @@ interface DewarpControlsProps { } const METHOD_LABELS: Record = { - vertical_edge: 'Vertikale Kanten', + vertical_edge: 'A: Vertikale Kanten', + projection: 'B: Projektions-Varianz', + hough_lines: 'C: Hough-Linien', + text_lines: 'D: Textzeilenanalyse', manual: 'Manuell', none: 'Keine Korrektur', } +/** Colour for a confidence value (0-1). */ +function confColor(conf: number): string { + if (conf >= 0.7) return 'text-green-600 dark:text-green-400' + if (conf >= 0.5) return 'text-yellow-600 dark:text-yellow-400' + return 'text-gray-400' +} + +/** Short confidence bar (visual). */ +function ConfBar({ value }: { value: number }) { + const pct = Math.round(value * 100) + const bg = value >= 0.7 ? 'bg-green-500' : value >= 0.5 ? 'bg-yellow-500' : 'bg-gray-400' + return ( +
+
+
+
+ {pct}% +
+ ) +} + export function DewarpControls({ dewarpResult, showGrid, @@ -32,6 +56,7 @@ export function DewarpControls({ const [gtFeedback, setGtFeedback] = useState<'correct' | 'incorrect' | null>(null) const [gtNotes, setGtNotes] = useState('') const [gtSaved, setGtSaved] = useState(false) + const [showDetails, setShowDetails] = useState(false) // Initialize slider to auto-detected value when result arrives useEffect(() => { @@ -57,32 +82,61 @@ export function DewarpControls({ setGtSaved(true) } + const wasRejected = dewarpResult && dewarpResult.method_used === 'none' && (dewarpResult.detections || []).length > 0 + const wasApplied = dewarpResult && dewarpResult.method_used !== 'none' && dewarpResult.method_used !== 'manual' + const detections = dewarpResult?.detections || [] + return (
- {/* Results */} + {/* Summary banner */} {dewarpResult && ( -
-
+
+ {/* Status line */} +
+ + {wasRejected ? '\u26A0\uFE0F' : wasApplied ? '\u2705' : '\u2796'} + + + {wasRejected + ? 'Quality Gate: Korrektur verworfen (Projektion nicht verbessert)' + : wasApplied + ? `Korrektur angewendet: ${dewarpResult.shear_degrees.toFixed(2)}°` + : dewarpResult.method_used === 'manual' + ? `Manuelle Korrektur: ${dewarpResult.shear_degrees.toFixed(2)}°` + : 'Keine Korrektur noetig'} + +
+ + {/* Key metrics */} +
Scherung:{' '} - {dewarpResult.shear_degrees}° + {dewarpResult.shear_degrees.toFixed(2)}°
Methode:{' '} - {METHOD_LABELS[dewarpResult.method_used] || dewarpResult.method_used} + {dewarpResult.method_used.includes('+') + ? `Ensemble (${dewarpResult.method_used.split('+').map(m => METHOD_LABELS[m] || m).join(' + ')})` + : METHOD_LABELS[dewarpResult.method_used] || dewarpResult.method_used}
-
- Konfidenz:{' '} - {Math.round(dewarpResult.confidence * 100)}% +
+ Konfidenz: +
- {/* Toggle */} -
+ {/* Toggles row */} +
+ {detections.length > 0 && ( + + )}
+ + {/* Detailed detections */} + {showDetails && detections.length > 0 && ( +
+
Einzelne Detektoren:
+
+ {detections.map((d: DewarpDetection) => { + const isUsed = dewarpResult.method_used.includes(d.method) + const aboveThreshold = d.confidence >= 0.5 + return ( +
+ + {isUsed ? '\u2713' : aboveThreshold ? '\u2012' : '\u2717'} + + + {METHOD_LABELS[d.method] || d.method} + + + {d.shear_degrees.toFixed(2)}° + + + {!aboveThreshold && ( + (unter Schwelle) + )} +
+ ) + })} +
+ {wasRejected && ( +
+ Die Korrektur wurde verworfen, weil die horizontale Projektions-Varianz nach Anwendung nicht besser war als vorher. +
+ )} +
+ )}
)}