Initial commit: digital reception monorepo (M1-M11 + demo extensions)

This commit is contained in:
2026-05-25 12:59:54 +05:00
commit b9f88194d9
182 changed files with 20578 additions and 0 deletions
View File
+77
View File
@@ -0,0 +1,77 @@
"""Pytest-фикстуры для face-service.
Подключение к локальному pgvector через DATABASE_URL из корневого .env.
Тесты re-id логики работают на чистых эмбеддингах — модель InsightFace не нужна.
"""
import os
import sys
import uuid
from pathlib import Path
import pytest
from dotenv import load_dotenv
# Подгружаем корневой .env и .env сервиса.
load_dotenv(Path(__file__).parent.parent / ".env")
load_dotenv(Path(__file__).parent.parent.parent.parent / ".env")
sys.path.insert(0, str(Path(__file__).parent.parent))
# Импортируем после load_dotenv, чтобы DATABASE_URL подцепился.
from database import get_connection # noqa: E402
@pytest.fixture
def db_conn():
conn = get_connection()
yield conn
conn.close()
@pytest.fixture
def seed_camera_and_track(db_conn):
"""Создаёт уникальные camera_id + track_id для теста и убирает после."""
camera_ids = []
track_ids = []
def _make(zone_code: str = "A"):
cam_id = str(uuid.uuid4())
zone_id = str(uuid.uuid4())
track_id = str(uuid.uuid4())
with db_conn.cursor() as cur:
cur.execute(
"INSERT INTO zones (id, code, name) VALUES (%s, %s::\"ZoneCode\", %s)"
" ON CONFLICT (code) DO NOTHING RETURNING id",
(zone_id, zone_code, f"test-zone-{zone_code}"),
)
row = cur.fetchone()
if row is None:
cur.execute('SELECT id FROM zones WHERE code = %s::"ZoneCode"', (zone_code,))
zone_id = str(cur.fetchone()[0])
cur.execute(
"INSERT INTO cameras (id, name, zone_id) VALUES (%s, %s, %s)",
(cam_id, f"test-cam-{cam_id[:6]}", zone_id),
)
cur.execute(
"INSERT INTO tracks (id, status, first_seen_at, last_seen_at, updated_at)"
" VALUES (%s, 'UNMATCHED', NOW(), NOW(), NOW())",
(track_id,),
)
db_conn.commit()
camera_ids.append(cam_id)
track_ids.append(track_id)
return cam_id, track_id
yield _make
# Cleanup
with db_conn.cursor() as cur:
if track_ids:
cur.execute(
"DELETE FROM face_embeddings WHERE track_id = ANY(%s::uuid[])",
(track_ids,),
)
cur.execute("DELETE FROM tracks WHERE id = ANY(%s::uuid[])", (track_ids,))
if camera_ids:
cur.execute("DELETE FROM cameras WHERE id = ANY(%s::uuid[])", (camera_ids,))
db_conn.commit()
+124
View File
@@ -0,0 +1,124 @@
"""Тесты cross-camera re-id логики.
Используем синтетические 512-мерные эмбеддинги (без InsightFace).
Проверяем, что find_topk_in_window:
1. Возвращает соседей в правильном порядке по cos-дистанции.
2. Фильтрует по camera_id (исключает ту же камеру).
3. Фильтрует по временному окну.
"""
from datetime import datetime, timedelta
import numpy as np
import pytest
from database import (
save_embedding_with_meta,
find_topk_in_window,
find_nearest_patient,
attach_track_to_patient,
delete_patient_embeddings,
)
def normed(vec: np.ndarray) -> np.ndarray:
return (vec / np.linalg.norm(vec)).astype(np.float32)
def make_embedding(seed: int) -> np.ndarray:
rng = np.random.default_rng(seed)
return normed(rng.standard_normal(512))
def test_topk_in_window_basic(seed_camera_and_track):
"""Из 3 эмбеддингов на 3 разных камерах находим 2 ближайших к query (исключая саму камеру query)."""
cam_a, track_a = seed_camera_and_track("A")
cam_b, track_b = seed_camera_and_track("B")
cam_c, track_c = seed_camera_and_track("C")
base = make_embedding(seed=42)
# Соседи: tweak base слегка для cam_b, сильнее для cam_c.
near = normed(base + 0.05 * make_embedding(seed=43))
far = normed(base + 0.5 * make_embedding(seed=44))
save_embedding_with_meta(base, track_a, cam_a, quality=0.9, captured_at=datetime.utcnow())
save_embedding_with_meta(near, track_b, cam_b, quality=0.9, captured_at=datetime.utcnow())
save_embedding_with_meta(far, track_c, cam_c, quality=0.9, captured_at=datetime.utcnow())
# Запрос с cam_a — должен вернуть cam_b раньше cam_c, cam_a исключаем.
results = find_topk_in_window(
embedding=base.tolist(),
camera_id=cam_a,
window_minutes=5,
k=5,
exclude_same_camera=True,
)
cam_ids_in_results = [r["camera_id"] for r in results]
assert cam_a not in cam_ids_in_results
assert cam_b in cam_ids_in_results
assert cam_c in cam_ids_in_results
# Порядок: ближе → дальше
assert results[0]["camera_id"] == cam_b
assert results[0]["distance"] < results[-1]["distance"]
def test_topk_filters_by_window(seed_camera_and_track):
"""Старый эмбеддинг (вне окна) не должен попадать в результат."""
cam_a, track_a = seed_camera_and_track("A")
cam_b, track_b = seed_camera_and_track("B")
base = make_embedding(seed=7)
save_embedding_with_meta(
base, track_b, cam_b, quality=0.9,
captured_at=datetime.utcnow() - timedelta(hours=1), # вне окна 5 мин
)
results = find_topk_in_window(
embedding=base.tolist(),
camera_id=cam_a,
window_minutes=5,
k=5,
)
cam_ids = [r["camera_id"] for r in results]
assert cam_b not in cam_ids
def test_find_nearest_patient_only_consented(db_conn, seed_camera_and_track):
"""find_nearest_patient ищет только среди эмбеддингов с patient_id IS NOT NULL."""
cam_a, track_a = seed_camera_and_track("A")
base = make_embedding(seed=100)
# Сохраняем эмбеддинг без patient_id.
save_embedding_with_meta(base, track_a, cam_a, quality=0.9)
# Ищем — никого не должно найти.
assert find_nearest_patient(base.tolist(), threshold=0.5) is None
# Создаём пациента и привязываем трек.
import uuid
patient_id = str(uuid.uuid4())
with db_conn.cursor() as cur:
cur.execute(
"INSERT INTO patients (id, full_name, updated_at) VALUES (%s, %s, NOW())",
(patient_id, "Тестовый Пациент"),
)
db_conn.commit()
affected = attach_track_to_patient(track_a, patient_id)
assert affected == 1
# Теперь должны найти.
result = find_nearest_patient(base.tolist(), threshold=0.5)
assert result is not None
assert result["patient_id"] == patient_id
assert result["distance"] < 0.01 # тот же эмбеддинг
# Очистка.
deleted = delete_patient_embeddings(patient_id)
assert deleted == 1
with db_conn.cursor() as cur:
cur.execute("DELETE FROM patients WHERE id = %s", (patient_id,))
db_conn.commit()