feat(v1): per-frame fall decision diagnostics overlay

PersonAnalysis now carries the policy Evidence; view_model builds a Qt-free
per-person diagnostic line (acc/horiz/ang/rapid/cand/state, or the reject
reason) and VideoView overlays it in a corner. Read-only, no decision change;
locates where a real fall stops. 67 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-21 23:14:05 +08:00
co-authored by Claude Opus 4.8
parent 2eeaba0ab8
commit bf86b5612f
7 changed files with 95 additions and 3 deletions
+17
View File
@@ -123,8 +123,25 @@ class VideoView(QtWidgets.QWidget):
painter.drawImage(target, self._image)
if self._view is not None:
self._draw_overlays(painter, target, self._image)
self._draw_diagnostics(painter)
painter.end()
def _draw_diagnostics(self, painter: QtGui.QPainter) -> None:
lines = list(self._view.diagnostics)
if not lines:
return
painter.setFont(QtGui.QFont("Consolas", 9))
metrics = painter.fontMetrics()
line_h = metrics.height()
box_w = max(metrics.width(line) for line in lines) + 12
box_h = line_h * len(lines) + 8
painter.fillRect(QtCore.QRect(6, 6, box_w, box_h), QtGui.QColor(0, 0, 0, 160))
painter.setPen(QtGui.QColor("#9BE9A8"))
y = 6 + line_h
for line in lines:
painter.drawText(12, y, line)
y += line_h
def _fitted_rect(self, image: QtGui.QImage) -> QtCore.QRect:
widget = self.rect()
scale = min(widget.width() / image.width(), widget.height() / image.height())