Files
Oxicloud/src/domain/entities/face.rs
T
Claude c930f865b0 perf: round 14 — faces narrow projection, auth per-request allocs, CalDAV emit buffers, frontend set churn
Benchmark-gated (benches/ROUND14.md); every change ships a BEFORE/AFTER
benchmark with an equivalence gate and is rolled back on regression (the
rule is encoded as a GATE FAIL exit / threshold expect).

Backend
- Q1 faces_for_file → narrow face_boxes_for_file(id, person_id, bbox) with the
  caller filter pushed into SQL: drops the 2 KiB embedding BYTEA + 6 unused
  columns per face. 15-face lightbox open 0.312→0.219 ms, 32 KB→840 B/req.
- A1 cookie auth uses the borrow-only extract_cookie_str (already backs CSRF)
  instead of extract_cookie_value's owned String: -1 alloc/cookie request.
- A2 compute_relevance ASCII case-fold fast path vs name.to_lowercase() per
  result row (Unicode fallback preserved): 1.40x, 12→3 allocs/page.
- A3 sub pre-parsed to Uuid at decode time (TokenClaims.sub_id) vs re-parsing
  the 36-char claim on every request incl. cache hits: 22.7→0.7 ns.
- A4 auth + NextCloud middlewares borrow request.headers() instead of taking
  axum's HeaderMap extractor (a full map clone): 2→0 allocs/authed request.
- A5 CalDAV getlastmodified via the stack rfc2822_utc (byte-identical to
  chrono) vs a per-event to_rfc2822() heap String: 5→0 allocs.
- A6 CalDAV per-event href + quoted etag written into reused page buffers vs a
  fresh format! pair per event: 3.48x, 240→6 allocs/40-event page.

Frontend
- F1 t() shares one frozen EMPTY_PARAMS for the no-interpolation call forms vs
  a throwaway {} per call: -1 alloc/call.
- F2 favorites favoriteIds is a persistent SvelteSet with per-page add (clear
  on reset) vs a brand-new set over the whole accumulated list each page:
  22.3x over a 40-page drain (O(N^2)→O(N)).

Verified: cargo check --all-targets, cargo clippy -D warnings, both bench
packs (GATE PASS), frontend npm run check + vitest (4/4). ROUND14.md also
records the investigated-but-deferred backlog (music N+1, contact vcard
over-fetch, CachedBlobBackend syscalls, ResourceList.sections builder, etc.).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PymgCdK78NzUF3oRAQCJfN
2026-07-19 10:22:12 +00:00

86 lines
2.5 KiB
Rust

//! Domain entities for the People (faces) feature.
use chrono::{DateTime, Utc};
use uuid::Uuid;
/// Length of a face embedding vector (ArcFace-style).
pub const EMBEDDING_DIM: usize = 512;
/// A face bounding box in normalized image coordinates (each component 0..1).
#[derive(Debug, Clone, Copy)]
pub struct BoundingBox {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
impl BoundingBox {
/// `[x, y, w, h]` — the storage representation (Postgres `REAL[]`).
pub fn to_array(self) -> Vec<f32> {
vec![self.x, self.y, self.w, self.h]
}
/// Build from a stored `[x, y, w, h]` array; missing components default to 0.
pub fn from_slice(a: &[f32]) -> Self {
Self {
x: a.first().copied().unwrap_or(0.0),
y: a.get(1).copied().unwrap_or(0.0),
w: a.get(2).copied().unwrap_or(0.0),
h: a.get(3).copied().unwrap_or(0.0),
}
}
}
/// A face box for the lightbox tagging overlay — the narrow projection of a
/// persisted [`Face`] that the People API's `faces_for_file` needs (`id`,
/// `person_id`, `bbox`). Fetching this instead of a full [`Face`] keeps the
/// 2 KiB `embedding` BYTEA (plus det_score/quality/blob_hash/created_at) off
/// the wire on every lightbox open of a face-tagged photo. See
/// benches/ROUND14.md §Q1.
#[derive(Debug, Clone)]
pub struct FaceBox {
pub id: Uuid,
pub person_id: Option<Uuid>,
pub bbox: BoundingBox,
}
/// A face produced by the analyzer but not yet persisted: where it is, how
/// confident the detector was, an optional quality score, and a 512-d,
/// L2-normalized embedding.
#[derive(Debug, Clone)]
pub struct DetectedFace {
pub bbox: BoundingBox,
pub det_score: f32,
pub quality: Option<f32>,
pub embedding: Vec<f32>,
}
/// A persisted face detection.
#[derive(Debug, Clone)]
pub struct Face {
pub id: Uuid,
pub file_id: Uuid,
pub user_id: Uuid,
/// Identity cluster this face belongs to, if any.
pub person_id: Option<Uuid>,
pub bbox: BoundingBox,
pub det_score: f32,
pub quality: Option<f32>,
pub embedding: Vec<f32>,
pub blob_hash: Option<String>,
pub created_at: DateTime<Utc>,
}
/// An identity cluster ("person"). `display_name` is `None` until the user
/// names it.
#[derive(Debug, Clone)]
pub struct Person {
pub id: Uuid,
pub user_id: Uuid,
pub display_name: Option<String>,
pub cover_face_id: Option<Uuid>,
pub is_hidden: bool,
pub created_at: DateTime<Utc>,
}