From d54814fa70a1f6e7eb457a5e37d96f6dfa66272b Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Tue, 24 Mar 2026 08:38:11 +0100 Subject: [PATCH] feat: color bar respects edits + column pattern auto-correction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Color bar (red/colored indicator) now only shows when word_boxes text still matches the cell text — editing the cell hides stale colors - New "Auto-Korrektur" button: detects dominant prefix+number patterns per column (e.g. p.70, p.71) and completes partial entries (.65 → p.65) — requires 3+ matching entries before correcting Co-Authored-By: Claude Opus 4.6 --- .../components/grid-editor/GridTable.tsx | 3 +- .../components/grid-editor/useGridEditor.ts | 76 +++++++++++++++++++ .../ocr-pipeline/StepGridReview.tsx | 12 +++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/admin-lehrer/components/grid-editor/GridTable.tsx b/admin-lehrer/components/grid-editor/GridTable.tsx index 02b32f4..bb8839c 100644 --- a/admin-lehrer/components/grid-editor/GridTable.tsx +++ b/admin-lehrer/components/grid-editor/GridTable.tsx @@ -443,7 +443,6 @@ export function GridTable({ const isBold = col.bold || cell?.is_bold const isLowConf = cell && cell.confidence > 0 && cell.confidence < 60 const isMultiSelected = selectedCells?.has(cellId) - const cellColor = getCellColor(cell) // Show per-word colored display only when word_boxes // match the cell text. Post-processing steps (e.g. 5h // slash-IPA → bracket conversion) modify cell.text but @@ -451,6 +450,8 @@ export function GridTable({ // plain input when they diverge. const wbText = cell?.word_boxes?.map((wb) => wb.text).join(' ') ?? '' const textMatches = !cell?.text || wbText === cell.text + // Color bar only when word_boxes still match edited text + const cellColor = textMatches ? getCellColor(cell) : null const hasColoredWords = textMatches && (cell?.word_boxes?.some( diff --git a/admin-lehrer/components/grid-editor/useGridEditor.ts b/admin-lehrer/components/grid-editor/useGridEditor.ts index e1c84e6..56a70b7 100644 --- a/admin-lehrer/components/grid-editor/useGridEditor.ts +++ b/admin-lehrer/components/grid-editor/useGridEditor.ts @@ -655,6 +655,81 @@ export function useGridEditor(sessionId: string | null) { [grid, pushUndo], ) + // ------------------------------------------------------------------ + // Column pattern auto-correction + // ------------------------------------------------------------------ + + /** + * Detect dominant prefix+number patterns per column and complete + * partial matches. E.g. if 3+ cells read "p.70", "p.71", etc., + * a cell reading ".65" is corrected to "p.65". + * Returns the number of corrections made. + */ + const autoCorrectColumnPatterns = useCallback(() => { + if (!grid) return 0 + pushUndo(grid.zones) + + let totalFixed = 0 + const numberPattern = /^(.+?)(\d+)\s*$/ + + setGrid((prev) => { + if (!prev) return prev + return { + ...prev, + zones: prev.zones.map((zone) => { + // Group cells by column + const cellsByCol = new Map() + zone.cells.forEach((cell, idx) => { + const arr = cellsByCol.get(cell.col_index) || [] + arr.push({ cell, idx }) + cellsByCol.set(cell.col_index, arr) + }) + + const newCells = [...zone.cells] + + for (const [, colEntries] of cellsByCol) { + // Count prefix occurrences + const prefixCounts = new Map() + for (const { cell } of colEntries) { + const m = cell.text.trim().match(numberPattern) + if (m) { + prefixCounts.set(m[1], (prefixCounts.get(m[1]) || 0) + 1) + } + } + + // Find dominant prefix (>= 3 occurrences) + let dominantPrefix = '' + let maxCount = 0 + for (const [prefix, count] of prefixCounts) { + if (count >= 3 && count > maxCount) { + dominantPrefix = prefix + maxCount = count + } + } + if (!dominantPrefix) continue + + // Fix partial matches — entries that are just [.?\s*]NUMBER + for (const { cell, idx } of colEntries) { + const text = cell.text.trim() + if (!text || text.startsWith(dominantPrefix)) continue + + const numMatch = text.match(/^[.\s]*(\d+)\s*$/) + if (numMatch) { + newCells[idx] = { ...newCells[idx], text: `${dominantPrefix}${numMatch[1]}` } + totalFixed++ + } + } + } + + return { ...zone, cells: newCells } + }), + } + }) + + if (totalFixed > 0) setDirty(true) + return totalFixed + }, [grid, pushUndo]) + // ------------------------------------------------------------------ // Multi-select & bulk formatting // ------------------------------------------------------------------ @@ -805,5 +880,6 @@ export function useGridEditor(sessionId: string | null) { toggleCellSelection, clearCellSelection, toggleSelectedBold, + autoCorrectColumnPatterns, } } diff --git a/admin-lehrer/components/ocr-pipeline/StepGridReview.tsx b/admin-lehrer/components/ocr-pipeline/StepGridReview.tsx index 9a3b854..e53c2ca 100644 --- a/admin-lehrer/components/ocr-pipeline/StepGridReview.tsx +++ b/admin-lehrer/components/ocr-pipeline/StepGridReview.tsx @@ -55,6 +55,7 @@ export function StepGridReview({ sessionId, onNext, saveRef }: StepGridReviewPro toggleCellSelection, clearCellSelection, toggleSelectedBold, + autoCorrectColumnPatterns, } = useGridEditor(sessionId) const [showImage, setShowImage] = useState(true) @@ -246,6 +247,17 @@ export function StepGridReview({ sessionId, onNext, saveRef }: StepGridReviewPro )}
+