Tighten Auto-Match refs and log accept/reject (Phase 2).
CI / skip-ci-check (pull_request) Successful in 30s
CI / python-lint (pull_request) Successful in 31s
CI / docker-ci (pull_request) Successful in 32s
CI / secret-scan (pull_request) Successful in 35s
CI / e2e (pull_request) Successful in 2m34s
CI / viewer-unit (pull_request) Successful in 2m53s
CI / admin-unit (pull_request) Successful in 3m8s

Raise reference quality floor to 0.5, align browse/run/auto-accept defaults with UI copy, and persist match_decisions for later confidence calibration.
This commit is contained in:
2026-08-05 11:23:24 -04:00
parent dfd0c582e6
commit 085b7962cf
13 changed files with 485 additions and 87 deletions
+109
View File
@@ -0,0 +1,109 @@
"""Unit tests for Auto-Match Phase 2 helpers (no DeepFace / DB required)."""
from __future__ import annotations
from types import SimpleNamespace
from backend.config import (
DEFAULT_AUTO_ACCEPT_THRESHOLD,
DEFAULT_BROWSE_TOLERANCE,
DEFAULT_RUN_TOLERANCE,
MIN_AUTO_MATCH_REFERENCE_QUALITY,
)
from backend.services.face_service import (
pick_best_reference_face_per_person,
record_match_decisions,
)
def _face(person_id: int | None, quality: float, face_id: int = 1):
return SimpleNamespace(id=face_id, person_id=person_id, quality_score=quality)
class TestPhase2ConfigDefaults:
def test_reference_quality_stricter_than_legacy(self):
assert MIN_AUTO_MATCH_REFERENCE_QUALITY >= 0.5
def test_auto_accept_aligns_with_ui_copy(self):
assert DEFAULT_AUTO_ACCEPT_THRESHOLD >= 85.0
def test_run_tolerance_stricter_than_browse(self):
assert DEFAULT_RUN_TOLERANCE < DEFAULT_BROWSE_TOLERANCE
assert DEFAULT_BROWSE_TOLERANCE <= 0.5
class TestPickBestReferenceFacePerPerson:
def test_keeps_first_face_per_person(self):
faces = [
_face(1, 0.9, face_id=10),
_face(1, 0.8, face_id=11),
_face(2, 0.7, face_id=20),
]
picked = pick_best_reference_face_per_person(faces)
assert set(picked.keys()) == {1, 2}
assert picked[1].id == 10
assert picked[2].id == 20
def test_skips_unidentified(self):
faces = [_face(None, 0.9, face_id=1), _face(3, 0.6, face_id=3)]
picked = pick_best_reference_face_per_person(faces)
assert list(picked.keys()) == [3]
assert picked[3].id == 3
def test_empty(self):
assert pick_best_reference_face_per_person([]) == {}
class TestRecordMatchDecisions:
def test_bulk_adds_and_skips_missing_face_id(self):
added: list = []
class FakeSession:
def add(self, row):
added.append(row)
def commit(self):
pass
count = record_match_decisions(
FakeSession(), # type: ignore[arg-type]
decision="accept",
source="auto_match",
person_id=42,
items=[
{"face_id": 1, "similarity": 91.0, "distance": 0.2, "reference_face_id": 9},
{"similarity": 50.0}, # missing face_id — skipped
{"face_id": 2, "similarity": 88.0},
],
user_id=7,
commit=True,
)
assert count == 2
assert len(added) == 2
assert added[0].decision == "accept"
assert added[0].person_id == 42
assert added[0].face_id == 1
assert float(added[0].similarity) == 91.0
assert added[0].user_id == 7
assert added[1].face_id == 2
def test_reject_source(self):
added: list = []
class FakeSession:
def add(self, row):
added.append(row)
def commit(self):
pass
record_match_decisions(
FakeSession(), # type: ignore[arg-type]
decision="reject",
source="auto_match",
person_id=1,
items=[{"face_id": 99, "similarity": 72.0}],
commit=True,
)
assert added[0].decision == "reject"
assert added[0].source == "auto_match"