faces: ignore/junk bucket — DB schema + lazy-create endpoint
A single global "Ignored" person row, marked is_ignored=true, that the
frontend lazily creates on first use to hold strangers, false
detections, and faces the user doesn't want bound to a real person.
Schema (new migration 2026-04-29-000200_add_is_ignored):
- persons.is_ignored BOOLEAN NOT NULL DEFAULT 0
- Partial index on (is_ignored) WHERE is_ignored = 1; small WHERE
set means a tiny index that only ever services the bucket lookup.
Why a real persons row instead of a separate table or status enum:
- face_detections.person_id stays a clean foreign key — no special
code paths for "ignored faces" anywhere else in the schema.
- The cluster-suggester already filters by `person_id IS NULL`, so
bound-to-ignored faces are naturally excluded from re-clustering
without any change.
- merge / rename / delete all work on it with the existing routes
(the management UI just hides it from default views).
DAO additions / changes:
- get_or_create_ignored_person (idempotent; race-safe via the
UNIQUE COLLATE NOCASE on persons.name + retry-on-409 fallback).
- list_persons gains an include_ignored parameter; default false
so the management screen hides the bucket unless asked.
- find_persons_by_names_ci filters is_ignored=0 in SQL so the
auto-bind path can NEVER target the bucket — even if the user
happens to tag photos as "Ignored", the heuristic look-up skips
it. Bucket assignment is always an explicit operator action.
- update_person accepts is_ignored: Option<bool> so a person can
be moved into / out of the bucket without a delete + recreate.
Routes:
- POST /persons/ignore-bucket — returns the bucket, creating it on
first call. Frontend uses this lazily right before binding.
- GET /persons gains ?include_ignored=true; default behavior
unchanged.
- PATCH /persons/{id} now accepts is_ignored.
Tests: ignore_bucket_idempotent_and_filters_auto_bind covers the
contract: bucket is idempotent across calls, find_persons_by_names_ci
skips it (even on exact name match), default list_persons hides it,
include_ignored=true surfaces it. All other tests updated to pass
the new is_ignored: false / Option<bool> fields explicitly.
cargo test --lib: 181/0; fmt + clippy clean for new code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
20
migrations/2026-04-29-000200_add_is_ignored/up.sql
Normal file
20
migrations/2026-04-29-000200_add_is_ignored/up.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
-- IGNORE / junk bucket for the face recognition feature.
|
||||
--
|
||||
-- An "Ignored" person is the destination for strangers, faces the user
|
||||
-- doesn't want tagged, and false detections. It looks like any other
|
||||
-- person row (so face_detections.person_id stays a clean foreign key)
|
||||
-- but `is_ignored=1` flags it for special UI treatment:
|
||||
-- - hidden from the persons list by default
|
||||
-- - excluded from `find_persons_by_names_ci` so a tag-name match
|
||||
-- can never auto-bind a real face to the ignore bucket
|
||||
-- - cluster-suggest already filters by `person_id IS NULL`, so faces
|
||||
-- bound to an ignored person are naturally excluded from future
|
||||
-- re-clustering
|
||||
--
|
||||
-- Partial index because the WHERE-clause is small (typically 1 row),
|
||||
-- and we only ever query for `is_ignored = 1` to find the bucket.
|
||||
|
||||
ALTER TABLE persons ADD COLUMN is_ignored BOOLEAN NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX idx_persons_is_ignored
|
||||
ON persons(is_ignored) WHERE is_ignored = 1;
|
||||
Reference in New Issue
Block a user