Tanya 68d280e8f5 feat: Add new analysis documents and update installation scripts for backend integration
This commit introduces several new analysis documents, including Auto-Match Load Performance Analysis, Folder Picker Analysis, Monorepo Migration Summary, and various performance analysis documents. Additionally, the installation scripts are updated to reflect changes in backend service paths, ensuring proper integration with the new backend structure. These enhancements provide better documentation and streamline the setup process for users.
2025-12-30 15:04:32 -05:00

349 lines
9.7 KiB
Python

"""Face processing and identify workflow schemas."""
from __future__ import annotations
from datetime import date
from typing import Optional
from pydantic import BaseModel, Field, ConfigDict
class ProcessFacesRequest(BaseModel):
"""Request to process faces in photos."""
model_config = ConfigDict(protected_namespaces=())
batch_size: Optional[int] = Field(
None,
ge=1,
description="Maximum number of photos to process (None = all unprocessed)",
)
detector_backend: str = Field(
"retinaface",
description="DeepFace detector backend (retinaface, mtcnn, opencv, ssd)",
)
model_name: str = Field(
"ArcFace",
description="DeepFace model name (ArcFace, Facenet, Facenet512, VGG-Face)",
)
class ProcessFacesResponse(BaseModel):
"""Response after initiating face processing."""
model_config = ConfigDict(protected_namespaces=())
job_id: str
message: str
batch_size: Optional[int] = None
detector_backend: str
model_name: str
class FaceItem(BaseModel):
"""Minimal face item for list views."""
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
id: int
photo_id: int
quality_score: float
face_confidence: float
location: str
pose_mode: Optional[str] = Field("frontal", description="Pose classification (frontal, profile_left, etc.)")
excluded: bool = Field(False, description="Whether this face is excluded from identification")
class UnidentifiedFacesQuery(BaseModel):
"""Query params for listing unidentified faces."""
model_config = ConfigDict(protected_namespaces=())
page: int = 1
page_size: int = 50
min_quality: float = 0.0
date_from: Optional[date] = None
date_to: Optional[date] = None
sort_by: str = Field("quality", description="quality|date_taken|date_added")
sort_dir: str = Field("desc", description="asc|desc")
class UnidentifiedFacesResponse(BaseModel):
"""Paginated unidentified faces list."""
model_config = ConfigDict(protected_namespaces=())
items: list[FaceItem]
page: int
page_size: int
total: int
class SimilarFaceItem(BaseModel):
"""Similar face with similarity score (0-1)."""
id: int
photo_id: int
similarity: float
location: str
quality_score: float
filename: str
pose_mode: Optional[str] = Field("frontal", description="Pose classification (frontal, profile_left, etc.)")
class SimilarFacesResponse(BaseModel):
"""Response containing similar faces for a given face."""
model_config = ConfigDict(protected_namespaces=())
base_face_id: int
items: list[SimilarFaceItem]
class BatchSimilarityRequest(BaseModel):
"""Request to get similarities between multiple faces."""
model_config = ConfigDict(protected_namespaces=())
face_ids: list[int] = Field(..., description="List of face IDs to calculate similarities for")
min_confidence: float = Field(60.0, ge=0.0, le=100.0, description="Minimum confidence percentage (0-100)")
class FaceSimilarityPair(BaseModel):
"""A pair of similar faces with their similarity score."""
model_config = ConfigDict(protected_namespaces=())
face_id_1: int
face_id_2: int
similarity: float # 0-1 range
confidence_pct: float # 0-100 range
class BatchSimilarityResponse(BaseModel):
"""Response containing similarities between face pairs."""
model_config = ConfigDict(protected_namespaces=())
pairs: list[FaceSimilarityPair] = Field(..., description="List of similar face pairs")
class IdentifyFaceRequest(BaseModel):
"""Identify a face by selecting existing or creating new person."""
model_config = ConfigDict(protected_namespaces=())
# Either provide person_id or the fields to create new person
person_id: Optional[int] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
middle_name: Optional[str] = None
maiden_name: Optional[str] = None
date_of_birth: Optional[date] = None
# Optionally identify a batch of face IDs along with this one
additional_face_ids: Optional[list[int]] = None
class IdentifyFaceResponse(BaseModel):
"""Result of identify operation."""
model_config = ConfigDict(protected_namespaces=())
identified_face_ids: list[int]
person_id: int
created_person: bool
class FaceUnmatchResponse(BaseModel):
"""Result of unmatch operation."""
model_config = ConfigDict(protected_namespaces=())
face_id: int
message: str
class BatchUnmatchRequest(BaseModel):
"""Request to batch unmatch multiple faces."""
model_config = ConfigDict(protected_namespaces=())
face_ids: list[int] = Field(..., min_items=1)
class BatchUnmatchResponse(BaseModel):
"""Result of batch unmatch operation."""
model_config = ConfigDict(protected_namespaces=())
unmatched_face_ids: list[int]
count: int
message: str
class PersonFaceItem(BaseModel):
"""Face item for person's faces list (includes photo info)."""
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
id: int
photo_id: int
photo_path: str
photo_filename: str
location: str
face_confidence: float
quality_score: float
detector_backend: str
model_name: str
class PersonFacesResponse(BaseModel):
"""Response containing all faces for a person."""
model_config = ConfigDict(protected_namespaces=())
person_id: int
items: list[PersonFaceItem]
total: int
class AutoMatchRequest(BaseModel):
"""Request to start auto-match process."""
model_config = ConfigDict(protected_namespaces=())
tolerance: float = Field(0.6, ge=0.0, le=1.0, description="Tolerance threshold (lower = stricter matching)")
auto_accept: bool = Field(False, description="Enable automatic acceptance of matching faces")
auto_accept_threshold: float = Field(70.0, ge=0.0, le=100.0, description="Similarity threshold for auto-acceptance (0-100%)")
class AutoMatchFaceItem(BaseModel):
"""Unidentified face match for a person."""
model_config = ConfigDict(protected_namespaces=())
id: int
photo_id: int
photo_filename: str
location: str
quality_score: float
similarity: float # Confidence percentage (0-100)
distance: float
pose_mode: str = Field("frontal", description="Pose classification (frontal, profile_left, etc.)")
class AutoMatchPersonItem(BaseModel):
"""Person with matches for auto-match workflow."""
model_config = ConfigDict(protected_namespaces=())
person_id: int
person_name: str
reference_face_id: int
reference_photo_id: int
reference_photo_filename: str
reference_location: str
reference_pose_mode: str = Field("frontal", description="Reference face pose classification")
face_count: int # Number of faces already identified for this person
matches: list[AutoMatchFaceItem]
total_matches: int
class AutoMatchPersonSummary(BaseModel):
"""Person summary without matches (for fast initial load)."""
model_config = ConfigDict(protected_namespaces=())
person_id: int
person_name: str
reference_face_id: int
reference_photo_id: int
reference_photo_filename: str
reference_location: str
reference_pose_mode: str = Field("frontal", description="Reference face pose classification")
face_count: int # Number of faces already identified for this person
total_matches: int = Field(0, description="Total matches (loaded separately)")
class AutoMatchPeopleResponse(BaseModel):
"""Response containing people list without matches (for fast initial load)."""
model_config = ConfigDict(protected_namespaces=())
people: list[AutoMatchPersonSummary]
total_people: int
class AutoMatchPersonMatchesResponse(BaseModel):
"""Response containing matches for a specific person."""
model_config = ConfigDict(protected_namespaces=())
person_id: int
matches: list[AutoMatchFaceItem]
total_matches: int
class AutoMatchResponse(BaseModel):
"""Response from auto-match start operation."""
model_config = ConfigDict(protected_namespaces=())
people: list[AutoMatchPersonItem]
total_people: int
total_matches: int
auto_accepted: bool = Field(False, description="Whether auto-acceptance was performed")
auto_accepted_faces: int = Field(0, description="Number of faces automatically accepted")
skipped_persons: int = Field(0, description="Number of persons skipped (non-frontal reference)")
skipped_matches: int = Field(0, description="Number of matches skipped (didn't meet criteria)")
class AcceptMatchesRequest(BaseModel):
"""Request to accept matches for a person."""
model_config = ConfigDict(protected_namespaces=())
face_ids: list[int] = Field(..., min_items=0, description="Face IDs to identify with this person")
class MaintenanceFaceItem(BaseModel):
"""Face item for maintenance view with person info and file path."""
model_config = ConfigDict(protected_namespaces=())
id: int
photo_id: int
photo_path: str
photo_filename: str
quality_score: float
person_id: Optional[int] = None
person_name: Optional[str] = None # Full name if identified
excluded: bool
class MaintenanceFacesResponse(BaseModel):
"""Response containing all faces for maintenance."""
model_config = ConfigDict(protected_namespaces=())
items: list[MaintenanceFaceItem]
total: int
class DeleteFacesRequest(BaseModel):
"""Request to delete multiple faces."""
model_config = ConfigDict(protected_namespaces=())
face_ids: list[int] = Field(..., min_items=1, description="Face IDs to delete")
class DeleteFacesResponse(BaseModel):
"""Response after deleting faces."""
model_config = ConfigDict(protected_namespaces=())
deleted_face_ids: list[int]
count: int
message: str