Download backend/app/api/datasets.py from Duke410/PawTrace: direct link, hf CLI and curl.
- Browser
- Download file 8.21 kB
-
https://huggingface.co/spaces/Duke410/PawTrace/resolve/main/backend/app/api/datasets.py
- Command line
-
hf download hf://spaces/Duke410/PawTrace/backend/app/api/datasets.py
-
curl -L -o datasets.py https://huggingface.co/spaces/Duke410/PawTrace/resolve/main/backend/app/api/datasets.py
8.21 kB
| """Admin dataset management endpoints (auth-protected, under /admin). | |
| Covers list/view/purge, image→dataset lookup, embed-all, and match-all. The dataset *load* job | |
| endpoints live in api/jobs.py (background job + polling for the dashboard's load form). | |
| """ | |
| from __future__ import annotations | |
| from datetime import date | |
| from fastapi import APIRouter, Depends, HTTPException, status | |
| from sqlalchemy import select | |
| from sqlalchemy.orm import Session | |
| from ..db import get_db | |
| from ..models import Dataset, Picture, User | |
| from ..models.base import SubjectType | |
| from ..schemas.dataset import ( | |
| DatasetDetailOut, | |
| DatasetOut, | |
| EmbedAllResult, | |
| ImageDatasetOut, | |
| MatchDatasetResult, | |
| PurgeResult, | |
| ) | |
| from ..security import require_admin | |
| from ..services import datasets as ds | |
| router = APIRouter(prefix="/admin", tags=["admin-datasets"]) | |
| def _get_dataset(db: Session, dataset_id: int) -> Dataset: | |
| dataset = db.get(Dataset, dataset_id) | |
| if not dataset: | |
| raise HTTPException(status.HTTP_404_NOT_FOUND, "Dataset not found") | |
| return dataset | |
| def list_datasets( | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> list[DatasetOut]: | |
| datasets = db.execute(select(Dataset).order_by(Dataset.creation_time.desc())).scalars().all() | |
| out: list[DatasetOut] = [] | |
| for d in datasets: | |
| item = DatasetOut.model_validate(d) | |
| item.case_count = ds.case_count(db, d) | |
| out.append(item) | |
| return out | |
| def get_dataset( | |
| dataset_id: int, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> DatasetDetailOut: | |
| dataset = _get_dataset(db, dataset_id) | |
| stats = ds.dataset_stats(db, dataset) | |
| base = DatasetOut.model_validate(dataset) | |
| base.case_count = stats["case_count"] | |
| return DatasetDetailOut(**base.model_dump(), stats=stats) | |
| def test_match( | |
| kind: str, | |
| dog_id: int, | |
| top_k: int = 10, | |
| apply_breed_gate: bool = False, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> dict: | |
| """Score one dog against ALL dogs in the opposite category by embedding similarity (testing).""" | |
| if kind not in ("known", "unknown"): | |
| raise HTTPException(status.HTTP_400_BAD_REQUEST, "kind must be 'known' or 'unknown'") | |
| subject_type = SubjectType.known if kind == "known" else SubjectType.unknown | |
| query = ds.dog_profile(db, subject_type, dog_id) | |
| if query is None: | |
| raise HTTPException(status.HTTP_404_NOT_FOUND, "Dog not found") | |
| top_k = max(1, min(top_k, 50)) | |
| from ..services.matching import rank_dog_against_opposite | |
| ranked, considered, query_embedded = rank_dog_against_opposite( | |
| db, subject_type=subject_type, subject_id=dog_id, top_k=top_k, | |
| apply_breed_gate=apply_breed_gate, | |
| ) | |
| results = [] | |
| for cand_type, cid, score in ranked: | |
| prof = ds.dog_profile(db, cand_type, cid) | |
| if prof: | |
| results.append({**prof, "score": score}) | |
| from ..ml import get_embedder | |
| e = get_embedder() | |
| return { | |
| "query": query, | |
| "results": results, | |
| "model": f"{e.name}/{e.version}", | |
| "candidate_count": considered, | |
| "query_embedded": query_embedded, | |
| } | |
| def dog_detail( | |
| kind: str, | |
| dog_id: int, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> dict: | |
| """A dog's profile plus ALL of its photos (full images) — for the profile viewer.""" | |
| if kind not in ("known", "unknown"): | |
| raise HTTPException(status.HTTP_400_BAD_REQUEST, "kind must be 'known' or 'unknown'") | |
| subject_type = SubjectType.known if kind == "known" else SubjectType.unknown | |
| profile = ds.dog_profile(db, subject_type, dog_id) | |
| if profile is None: | |
| raise HTTPException(status.HTTP_404_NOT_FOUND, "Dog not found") | |
| from .helpers import pictures_for | |
| photos = [p.model_dump() for p in pictures_for(db, subject_type, dog_id)] | |
| return {"profile": profile, "photos": photos} | |
| def list_breeds( | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> dict: | |
| """Predicted breed labels (HF softmax, active model) available to filter by.""" | |
| return {"model": ds.active_breed_model_in_db(db), "breeds": ds.list_breeds(db)} | |
| def list_all_dogs( | |
| kind: str = "all", | |
| limit: int = 30, | |
| offset: int = 0, | |
| breed: str | None = None, | |
| breed_k: int = 10, | |
| sort: str = "newest", | |
| zip: str | None = None, | |
| added_from: date | None = None, | |
| added_to: date | None = None, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> dict: | |
| """Browse every dog profile, filterable by predicted breed / ZIP / added-date and sortable. | |
| ``breed_k`` = 1 matches the top predicted breed; 5–10 matches within the top-K. ``sort`` is | |
| ``newest`` (default) or ``oldest``. ``zip`` matches a dog's ZIP by prefix; ``added_from`` / | |
| ``added_to`` bound the added date (inclusive). | |
| """ | |
| kind = kind if kind in ("all", "known", "unknown") else "all" | |
| limit = max(1, min(limit, 100)) | |
| offset = max(0, offset) | |
| breed_k = max(1, min(breed_k, 50)) | |
| sort = sort if sort in ("newest", "oldest") else "newest" | |
| return ds.list_all_dogs( | |
| db, kind, limit, offset, breed=breed or None, breed_k=breed_k, | |
| sort=sort, zip_prefix=(zip or None), added_from=added_from, added_to=added_to, | |
| ) | |
| def list_dataset_dogs( | |
| dataset_id: int, | |
| limit: int = 25, | |
| offset: int = 0, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> dict: | |
| dataset = _get_dataset(db, dataset_id) | |
| limit = max(1, min(limit, 100)) | |
| return ds.list_dogs(db, dataset, limit, offset) | |
| def delete_dataset( | |
| dataset_id: int, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> PurgeResult: | |
| dataset = _get_dataset(db, dataset_id) | |
| deleted = ds.purge_dataset(db, dataset) | |
| return PurgeResult(dataset_id=dataset_id, deleted=deleted) | |
| def image_dataset( | |
| image_id: int, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> ImageDatasetOut: | |
| pic = db.get(Picture, image_id) | |
| if not pic: | |
| raise HTTPException(status.HTTP_404_NOT_FOUND, "Image not found") | |
| from ..models import KnownDog, UnknownDog | |
| if pic.subject_type == SubjectType.known: | |
| dog = db.get(KnownDog, pic.subject_id) | |
| else: | |
| dog = db.get(UnknownDog, pic.subject_id) | |
| dataset_id = getattr(dog, "dataset_id", None) if dog else None | |
| dataset_name = None | |
| if dataset_id: | |
| d = db.get(Dataset, dataset_id) | |
| dataset_name = d.name if d else None | |
| return ImageDatasetOut( | |
| image_id=image_id, | |
| subject_type=pic.subject_type.value, | |
| subject_id=pic.subject_id, | |
| dataset_id=dataset_id, | |
| dataset_name=dataset_name, | |
| ) | |
| def embed_all( | |
| dataset_id: int, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> EmbedAllResult: | |
| dataset = _get_dataset(db, dataset_id) | |
| result = ds.embed_all(db, dataset) | |
| return EmbedAllResult(dataset_id=dataset_id, **result) | |
| def match_dataset( | |
| dataset_id: int, | |
| candidate_dataset_id: int | None = None, | |
| _: User = Depends(require_admin), | |
| db: Session = Depends(get_db), | |
| ) -> MatchDatasetResult: | |
| dataset = _get_dataset(db, dataset_id) | |
| result = ds.match_dataset(db, dataset, candidate_dataset_id=candidate_dataset_id) | |
| return MatchDatasetResult( | |
| dataset_id=dataset_id, | |
| cases_processed=result["cases_processed"], | |
| matches_created=result["matches_created"], | |
| candidate_dataset_id=result["candidate_dataset_id"], | |
| ) | |