// Shapes mirror the backend Pydantic schemas (spec ยง12). Kept hand-written for clarity; // could be codegen'd from /openapi.json (see DECISIONS.md). export type Role = "owner" | "finder" | "admin"; export type DogSize = "small" | "medium" | "large"; export type CaseType = "lost" | "found"; export type CaseStatus = "open" | "matched" | "resolved" | "closed"; export type MatchStatus = "pending" | "confirmed" | "rejected"; export interface User { id: number; name: string; email: string; phone: string | null; zip: string; role: Role; } export interface Picture { id: number; subject_type: "known" | "unknown"; subject_id: number; is_primary: boolean; is_probably_not_dog: boolean; url: string | null; thumb_url: string | null; } export interface KnownDog { id: number; owner_id: number; name: string; breed: string | null; age: string | null; color: string | null; size: DogSize | null; description: string; other_info: string | null; last_known_zip: string | null; status: "home" | "lost" | "reunited"; created_at: string; pictures: Picture[]; estimated_breeds: string[]; } export interface Case { id: number; type: CaseType; status: CaseStatus; event_zip: string; event_date: string; current_location: string | null; search_radius_miles: number; notes: string | null; known_dog_id: number | null; unknown_dog_id: number | null; person_id: number | null; created_at: string; } export interface MatchCandidate { type: "known" | "unknown"; id: number; name?: string; description?: string; breed?: string | null; est_breed?: string | null; color?: string | null; size?: string | null; current_zip?: string | null; current_location_detail?: string | null; // shelter/vet holding a found dog (public) last_known_zip?: string | null; status?: string; estimated_breeds?: string[]; pictures: Picture[]; } export interface Match { id: number; case_id: number; candidate_type: "known" | "unknown"; candidate_id: number; candidate_case_id: number | null; similarity_score: number; rank: number; status: MatchStatus; model_name: string; model_version: string; candidate: MatchCandidate | null; } export interface FoundReportResponse { case: Case; matches: Match[]; vet_guidance: { name: string; detail: string; type: string }[] | null; } export interface ReclaimContact { name: string | null; email: string | null; phone: string | null; } export interface ReclaimInfo { match_id: number; match_status: MatchStatus; your_dog_name: string | null; your_photos: DogPhoto[]; found: boolean; held_at: "shelter_or_vet" | "finder_custody" | "unknown"; location_name: string | null; current_zip: string | null; current_location: string | null; found_date: string | null; contact: ReclaimContact | null; photos: DogPhoto[]; nearby_shelters: { name: string; detail: string; type: string }[]; } export interface Page { items: T[]; total: number; limit: number; offset: number; } // ---- Admin: datasets / data loading ---- export type DatasetType = "test_known" | "test_found" | "known" | "unknown"; export interface Dataset { id: number; name: string; type: DatasetType; description: string | null; source_path: string | null; dog_count: number; creation_time: string; case_count: number; } export interface DatasetStats { known_dog_count: number; unknown_dog_count: number; dog_count: number; user_count: number; case_count: number; match_count: number; picture_count: number; embedded_picture_count: number; error_count: number; } export interface DatasetDetail extends Dataset { stats: DatasetStats; } export interface DatasetDog { kind: "known" | "unknown"; id: number; name: string; status: string; zip: string | null; thumb_url: string | null; } export interface DatasetDogsPage { items: DatasetDog[]; total: number; limit: number; offset: number; } export interface PurgeResult { dataset_id: number; deleted: Record; } export interface EmbedAllResult { dataset_id: number; total_pictures: number; embedded: number; breeds: number; skipped: number; errors: number; } export interface MatchDatasetResult { dataset_id: number; cases_processed: number; matches_created: number; candidate_dataset_id: number | null; } export interface AdminDog { kind: "known" | "unknown"; id: number; name: string; breed: string | null; color: string | null; size: string | null; status: string; zip: string | null; dataset_id: number | null; dataset_name: string | null; picture_count: number; thumb_url: string | null; predicted_breeds: string[]; } export interface BreedOption { label: string; count: number; } export interface AdminDogsPage { items: AdminDog[]; total: number; limit: number; offset: number; counts: { known: number; unknown: number }; } export interface TestMatchCandidate extends AdminDog { score: number; } export interface TestMatchResult { query: AdminDog; results: TestMatchCandidate[]; model: string; candidate_count: number; query_embedded: boolean; } export interface DogPhoto { id: number; url: string | null; thumb_url: string | null; is_primary: boolean; } export interface DogDetailResult { profile: AdminDog; photos: DogPhoto[]; } export interface CaseDog { kind: "known" | "unknown" | null; photos: DogPhoto[]; } export interface PhotoSearchMatch { dog: AdminDog; score: number; distance_miles: number | null; photos: DogPhoto[]; } export interface PhotoSearchResult { results: PhotoSearchMatch[]; model: string; candidate_count: number; zip: string | null; radius_miles: number; pool: "found" | "lost"; } export interface BreedEstimate { label: string; score: number; } export interface BreedEstimateResult { breeds: BreedEstimate[]; model: string; } export interface AdminCaseDog { kind: "known" | "unknown"; id: number; name: string; status: string; zip: string | null; thumb_url: string | null; } export interface AdminCase { id: number; type: "lost" | "found"; status: string; event_zip: string; event_date: string; created_at: string; dog: AdminCaseDog | null; person: { id: number; name: string; email: string } | null; finder_name: string | null; finder_email: string | null; match_count: number; } export interface AdminCasesPage { items: AdminCase[]; total: number; limit: number; offset: number; counts: { lost: number; found: number }; } export interface AdminCaseDetail { case: AdminCase; dog: { profile: AdminDog; photos: DogPhoto[] } | null; matches: Match[]; } export interface AdminOwner { id: number; name: string; email: string; zip: string; dog_count: number; dogs: { id: number; name: string; status: string; zip: string | null; thumb_url: string | null }[]; } export interface AdminOwnersPage { items: AdminOwner[]; total: number; limit: number; offset: number; } export interface AdminOwnerDetail { owner: { id: number; name: string; email: string; zip: string; phone: string | null; dog_count: number; }; dogs: AdminDog[]; cases: AdminCase[]; } export interface Job { id: string; type: string; status: "pending" | "running" | "done" | "error"; progress: { processed?: number; total?: number; dataset_id?: number }; result: Record | null; error: string | null; created_at: string; }