fix: DB schema missing scan_export column, add threshold filter and N hotkey

- Fresh databases were missing scan_export column — broke first export
- Threshold slider now filters existing scan results without rescanning
- N key toggles hard negative on selected scan regions
- All 59 tests passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-19 12:45:14 +02:00
parent 518554f788
commit 2b7dfb330d
2 changed files with 32 additions and 2 deletions
+31 -2
View File
@@ -697,12 +697,31 @@ class ScanResultsPanel(QWidget):
self._tabs.setTabText(tab_idx, f"{model} ({count})")
self.tab_changed.emit()
def filter_by_threshold(self, threshold: float) -> None:
"""Show/hide rows based on score threshold across all tabs."""
for i in range(self._tabs.count()):
table = self._tabs.widget(i)
if not isinstance(table, QTableWidget):
continue
visible = 0
for row in range(table.rowCount()):
score = float(table.item(row, 2).text())
hide = score < threshold
table.setRowHidden(row, hide)
if not hide:
visible += 1
model = self._tabs.tabText(i).rsplit(" (", 1)[0]
self._tabs.setTabText(i, f"{model} ({visible})")
self.regions_edited.emit()
def _get_tab_regions(self, table: QTableWidget,
include_disabled: bool = False
) -> list[tuple[float, float, float]]:
"""Extract (start, end, score) from a table widget, skipping disabled rows."""
"""Extract (start, end, score) from a table widget, skipping disabled/hidden rows."""
regions = []
for row in range(table.rowCount()):
if table.isRowHidden(row):
continue
if not include_disabled:
disabled = table.item(row, 0).data(Qt.ItemDataRole.UserRole + 2)
if disabled:
@@ -714,12 +733,14 @@ class ScanResultsPanel(QWidget):
return regions
def current_regions_with_orig(self) -> list[tuple[float, float, float, float, float]]:
"""Return (start, end, score, orig_start, orig_end) for enabled rows."""
"""Return (start, end, score, orig_start, orig_end) for enabled, visible rows."""
table = self._tabs.currentWidget()
if not isinstance(table, QTableWidget):
return []
regions = []
for row in range(table.rowCount()):
if table.isRowHidden(row):
continue
item0 = table.item(row, 0)
disabled = item0.data(Qt.ItemDataRole.UserRole + 2)
if disabled:
@@ -961,6 +982,8 @@ class ScanResultsPanel(QWidget):
self.undo()
elif event.key() in (Qt.Key.Key_Delete, Qt.Key.Key_Backspace):
self.toggle_disable_selected()
elif event.key() == Qt.Key.Key_N:
self._on_add_negatives()
else:
super().keyPressEvent(event)
@@ -2700,6 +2723,7 @@ class MainWindow(QMainWindow):
self._scan_panel.negatives_removed.connect(self._on_scan_negatives_removed)
self._scan_panel.tab_changed.connect(self._update_scan_export_count)
self._scan_panel.regions_edited.connect(self._on_scan_regions_edited)
self._sld_threshold.valueChanged.connect(self._on_threshold_changed)
# Root: horizontal splitter
splitter = QSplitter(Qt.Orientation.Horizontal)
@@ -2783,6 +2807,7 @@ class MainWindow(QMainWindow):
"<tr><td><b>G</b></td><td>Toggle cursor lock</td></tr>"
"<tr><td><b>A</b></td><td>Autoclip — fit clip count to pause position</td></tr>"
"<tr><td><b>Delete / Backspace</b></td><td>Toggle disable on selected scan regions</td></tr>"
"<tr><td><b>N</b></td><td>Toggle hard negative on selected scan regions</td></tr>"
"<tr><td><b>Ctrl+Z</b></td><td>Undo last scan panel action</td></tr>"
"<tr><td><b>? / F1</b></td><td>This help</td></tr>"
"<tr><td colspan='2'><hr></td></tr>"
@@ -3694,6 +3719,10 @@ class MainWindow(QMainWindow):
self._update_scan_export_count()
self._show_status(f"Removed {len(times)} hard negative(s)")
def _on_threshold_changed(self, value: float) -> None:
"""Filter existing scan results by threshold without rescanning."""
self._scan_panel.filter_by_threshold(value)
def _on_scan_regions_edited(self) -> None:
"""A scan region was disabled/enabled or resized — refresh timeline and count."""
self._timeline.set_scan_regions(