Filter the people rail in the query, and count the unassigned faces

`faces::people` grouped `face_person` after a LEFT JOIN over every person
and sorted the lot by name; the rail then discarded the empty, unnamed
groups a regrouping pass leaves behind — 17,000 of 19,000 rows on the
reference library. `people_in_use` filters them in the WHERE and joins
`people` to face counts aggregated first (2,000 groups), so the sort sees
only the rows that will be drawn. `count_unassigned` replaces fetching
2,400 ids to take their length. `load_people` 22 ms → 10 ms.
This commit is contained in:
2026-09-20 10:56:36 +02:00
parent 9d1aa5735b
commit b4821ee1ab
2 changed files with 58 additions and 14 deletions
+5 -6
View File
@@ -216,7 +216,9 @@ pub struct IdentityView {
/// Filtered rather than deleted, because this is a screen being drawn and not a
/// catalog being repaired. A row is withheld; nothing is lost, a sync cannot
/// resurrect what was never removed, and the prune stays the one place that
/// decides these are disposable.
/// decides these are disposable. The filter is the catalog's
/// (`faces::people_in_use`), in the query, so the rows withheld are never
/// read or sorted either.
///
/// An empty group with a *name* still shows. That one is not debris, it is the
/// symptom of a real failure — a named person whose faces were regrouped out
@@ -228,11 +230,8 @@ pub fn load_people(
model_id: &str,
) -> Result<IdentityView, dr_catalog::CatalogError> {
let conn = catalog.connection();
let people = faces::people(conn)?
let people = faces::people_in_use(conn)?
.into_iter()
.filter(|p| {
p.confirmed_faces + p.suggested_faces > 0 || !p.name.trim().is_empty() || p.ignored
})
.map(|p| PersonRow {
id: p.id,
name: p.name,
@@ -247,7 +246,7 @@ pub fn load_people(
people,
selected: None,
faces: Vec::new(),
unassigned: faces::unassigned(conn, model_id)?.len(),
unassigned: faces::count_unassigned(conn, model_id)? as usize,
calibrated: faces::calibration(conn, model_id)?.is_some_and(|(c, _)| c.valid),
})
}