#!/usr/bin/env python3 """Verify visual/OCR/face GPU indexing and named-person search on live data.""" from __future__ import annotations import argparse import json import time from pathlib import Path from typing import Any import httpx def call(client: httpx.Client, method: str, path: str, **kwargs: Any) -> httpx.Response: response = client.request(method, path, **kwargs) if response.is_error: raise RuntimeError(f"{method} {path} -> {response.status_code}: {response.text[:1000]}") return response def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--base-url", required=True) parser.add_argument("--token-file", type=Path, required=True) parser.add_argument("--run-dir", type=Path, required=True) parser.add_argument("--case-id", default="face") parser.add_argument("--timeout", type=int, default=600) args = parser.parse_args() state = json.loads((args.run_dir / "state.json").read_text()) token = args.token_file.read_text().strip() client = httpx.Client( base_url=args.base_url.rstrip("/"), headers={"Authorization": f"Bearer {token}"}, timeout=httpx.Timeout(60, connect=10), ) filename = f"{state['run_id']}-{args.case_id}-e2e-face.mp4" observed: dict[str, list[str]] = {name: [] for name in ("visual", "ocr", "faces")} deadline = time.monotonic() + args.timeout video: dict[str, Any] | None = None while time.monotonic() < deadline: models = call(client, "GET", "/api/v1/models").json() components = models.get("accelerator", {}).get("components", {}) for component in observed: device = components.get(component, {}).get("actual_device") if device and device not in observed[component]: observed[component].append(device) videos = call(client, "GET", "/api/v1/videos?limit=500").json() video = next( ( item for item in videos if item.get("source_id") == state["source_id"] and str(item.get("source_key") or "").rsplit("/", 1)[-1] == filename ), None, ) if video and video.get("index_state", {}).get("faces") in {"ready", "failed"}: break time.sleep(1) if not video: raise AssertionError("face fixture never appeared in the video list") if video.get("index_state", {}).get("faces") != "ready": raise AssertionError(f"face indexing failed: {video.get('index_state')} {video.get('error')}") matched_person: dict[str, Any] | None = None matched_faces: list[dict[str, Any]] = [] for person in call(client, "GET", "/api/v1/people").json(): faces = call(client, "GET", f"/api/v1/people/{person['id']}/faces?limit=500").json() owned = [face for face in faces if face.get("video_id") == video["id"]] if owned: matched_person = person matched_faces = owned break if not matched_person: raise AssertionError("the official OpenCV face fixture did not produce a person cluster") person_name = f"{state['run_id']}-人物-729" call(client, "PATCH", f"/api/v1/people/{matched_person['id']}", json={"name": person_name}) positive = call( client, "POST", "/api/v1/search", json={"text": person_name, "recognition_types": ["person"], "limit": 100}, ).json() if video["id"] not in {item["video_id"] for item in positive.get("items", [])}: raise AssertionError("named-person search omitted the face fixture video") negative = call( client, "POST", "/api/v1/search", json={ "text": f"{state['run_id']}-不存在人物-9834721", "recognition_types": ["person"], "limit": 100, }, ).json() if video["id"] in {item["video_id"] for item in negative.get("items", [])}: raise AssertionError("non-matching person search returned the face fixture video") non_gpu = {name: devices for name, devices in observed.items() if not any("GPU" in d.upper() for d in devices)} if non_gpu: raise AssertionError(f"GPU was not observed for all frame components: {non_gpu}") report = { "video_id": video["id"], "person_id": matched_person["id"], "person_name": person_name, "face_count": len(matched_faces), "observed_devices": observed, "positive_matches": len(positive.get("items", [])), "negative_matches": len(negative.get("items", [])), } (args.run_dir / "face-report.json").write_text( json.dumps(report, ensure_ascii=False, indent=2) + "\n" ) print(json.dumps(report, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()