Registrationqualityandsuitabilityscoringplan
jhj
2026년 08월 15일
갤러리 등록 품질 검증 및 Re-ID 적합성 진단 시스템 구현 계획 (Implementation Plan)
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 고양이 Re-ID 등록 파이프라인의 오탐(False Positive) 방지를 위해 엄격한 품질 필터링(Strict Quality Gate), BBox 종횡비와 임베딩 Medoid를 결합한 2단계 포즈 다양성 진단, 100점 만점 적합성 점수화, 그리고 SQLite 2계층 캐싱 시스템을 구축합니다.
Architecture: 등록 시 YOLOv8 검출 및 선명도/해상도/조도 기반 Strict 필터링을 거쳐 고품질 크롭만 추출기에 전달합니다. 개체 등록 데이터셋 내부의 BBox 종횡비 분포와 임베딩 상호 중심성(Centrality)으로 Front Anchor(Medoid)를 선별하여 상대적 포즈 다양성을 평가하고, 종합 적합도(0~100점)와 추천 가이드를 산출하여 SQLite embeddings 및 identity_profiles 테이블에 원자적으로 캐싱합니다.
Tech Stack: Python 3.12, PyTorch, OpenCV (cv2), SQLite3, NumPy, YOLOv8 (Ultralytics), pytest
작업 전제 조건 및 규칙
- Branch 관리: 사용자가 직접 관리하므로 별도의 Git Worktree는 생성하지 않습니다.
- Git Commit: 모든 구현 및 단위 테스트 검증 완료 후, 사용자의 최종 확인 및 요청 시 단계별로 커밋을 진행합니다.
- 웹 API 연동: 향후 FastAPI/React 연동을 위한 DTO 메서드는 코드 상에
TODO주석으로 명시합니다.
File Structure
reid/
├── cfg/
│ └── default.yaml # [Modify] 등록 품질 관련 기본 설정 추가
├── core/
│ ├── config.py # [Check] 설정 바인딩 확인
│ └── quality.py # [Create] QualityMetrics, SuitabilityReport DTO 및 RegistrationQualityInspector
├── models/
│ └── extractor/
│ ├── embedding.py # [Modify] identity_profiles 테이블, 컬럼 확장, 스키마 마이그레이션
│ └── model.py # [Modify] register()에 YOLO 검출, Strict 필터, 프로필 자동 생성 연동
├── cli.py # [Modify] reid list 등급 출력, reid inspect 핸들러, reid migrate 연동
└── config.yaml # [Modify] 등록 품질 임계치 설정 반영
tests/
├── test_quality_inspector.py # [Create] 품질 검사 및 BBox-Medoid 적합도 산출 단위 테스트
├── test_embedding_store_profiles.py # [Create] SQLite identity_profiles 및 확장 컬럼 CRUD 테스트
├── test_register_quality.py # [Create] Strict 등록 및 Lazy Auto-fill 통합 테스트
└── test_cli_quality.py # [Create] CLI inspect, list, migrate 적합도 출력 테스트
Tasks
Task 1: DTO 및 품질 검사/적합성 진단 엔진 구현 (reid/core/quality.py)
Files:
- Create:
reid/core/quality.py -
Test:
tests/test_quality_inspector.py - Step 1: 실패하는 단위 테스트 작성 (
tests/test_quality_inspector.py)
import numpy as np
import pytest
from reid.core.quality import (
QualityMetrics,
SuitabilityReport,
RegistrationQualityInspector,
StatusLevel
)
def test_quality_metrics_dto():
qm = QualityMetrics(
blur_score=45.2,
crop_w=200,
crop_h=250,
aspect_ratio=0.8,
det_conf=0.92,
is_valid=True,
quality_score=85.0
)
assert qm.is_valid is True
assert qm.quality_score == 85.0
def test_inspect_single_crop_blur_and_size():
inspector = RegistrationQualityInspector(min_blur=30.0, min_size=64, min_conf=0.60)
# 1. Very small crop (invalid)
tiny_crop = np.zeros((32, 32, 3), dtype=np.uint8)
res_tiny = inspector.inspect_single_crop(tiny_crop, det_conf=0.9)
assert res_tiny.is_valid is False
assert res_tiny.crop_w == 32
# 2. Solid color crop (low blur variance -> invalid)
solid_crop = np.full((128, 128, 3), 128, dtype=np.uint8)
res_solid = inspector.inspect_single_crop(solid_crop, det_conf=0.9)
assert res_solid.is_valid is False
assert res_solid.blur_score < 5.0
# 3. High texture / sharp crop (valid)
np.random.seed(42)
sharp_crop = np.random.randint(0, 255, (128, 128, 3), dtype=np.uint8)
res_sharp = inspector.inspect_single_crop(sharp_crop, det_conf=0.85)
assert res_sharp.is_valid is True
assert res_sharp.blur_score >= 30.0
def test_evaluate_identity_bbox_medoid():
inspector = RegistrationQualityInspector()
# Generate 5 normalized mock embeddings (dim=128)
np.random.seed(100)
base_vec = np.random.randn(128)
base_vec /= np.linalg.norm(base_vec)
# 3 Front candidates (close to base_vec), 2 Side candidates
e1 = base_vec + np.random.randn(128) * 0.05
e2 = base_vec + np.random.randn(128) * 0.02
e3 = base_vec + np.random.randn(128) * 0.04
e4 = -base_vec + np.random.randn(128) * 0.2
e5 = np.random.randn(128)
embeddings = np.array([e / np.linalg.norm(e) for e in [e1, e2, e3, e4, e5]], dtype=np.float32)
aspect_ratios = [0.65, 0.68, 0.72, 1.30, 1.45]
qualities = [
QualityMetrics(50.0, 200, 300, 0.65, 0.95, True, 90.0),
QualityMetrics(60.0, 200, 300, 0.68, 0.95, True, 95.0),
QualityMetrics(45.0, 200, 300, 0.72, 0.90, True, 88.0),
QualityMetrics(40.0, 300, 200, 1.30, 0.88, True, 80.0),
QualityMetrics(42.0, 320, 220, 1.45, 0.85, True, 82.0),
]
report = inspector.evaluate_identity(embeddings, aspect_ratios, qualities, label="Nabi")
assert report.label == "Nabi"
assert report.sample_count == 5
assert report.front_anchor_idx in [0, 1, 2]
assert report.total_score > 60.0
assert report.status_level in [StatusLevel.EXCELLENT, StatusLevel.GOOD]
- Step 2: 테스트 실행하여 실패 확인
Run: pytest tests/test_quality_inspector.py -v
Expected: FAIL with ModuleNotFoundError: No module named 'reid.core.quality'
- Step 3:
reid/core/quality.py구현
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional, Any
import cv2
import numpy as np
class StatusLevel(str, Enum):
EXCELLENT = "EXCELLENT"
GOOD = "GOOD"
WARN = "WARN"
POOR = "POOR"
UNCHECKED = "UNCHECKED"
@dataclass
class QualityMetrics:
blur_score: float
crop_w: int
crop_h: int
aspect_ratio: float
det_conf: float
is_valid: bool
quality_score: float
@dataclass
class SuitabilityReport:
label: str
sample_count: int
total_score: float
quality_avg: float
consistency_score: float
diversity_score: float
status_level: StatusLevel
recommendations: List[str] = field(default_factory=list)
front_anchor_idx: Optional[int] = None
# TODO: 향후 FastAPI 백엔드 및 React 웹 대시보드 연동 시 to_dict / JSON 직렬화 지원 확장
class RegistrationQualityInspector:
"""등록 전용 엄격 품질 검증 및 BBox-Medoid 결합형 Re-ID 적합성 진단 엔진."""
def __init__(
self,
min_blur: float = 30.0,
min_size: int = 64,
min_conf: float = 0.60
) -> None:
self.min_blur = min_blur
self.min_size = min_size
self.min_conf = min_conf
def inspect_single_crop(self, crop: np.ndarray, det_conf: float) -> QualityMetrics:
"""단일 크롭 이미지의 블러, 해상도, 조도 및 유효성을 판별하고 품질 점수를 산출합니다."""
if crop is None or crop.size == 0:
return QualityMetrics(0.0, 0, 0, 1.0, float(det_conf), False, 0.0)
h, w = crop.shape[:2]
aspect_ratio = float(w) / float(max(h, 1))
# Size check
if w < self.min_size or h < self.min_size:
return QualityMetrics(0.0, w, h, aspect_ratio, float(det_conf), False, 0.0)
# Confidence check
if det_conf < self.min_conf:
return QualityMetrics(0.0, w, h, aspect_ratio, float(det_conf), False, 0.0)
# Blur check (Laplacian variance)
gray = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY)
blur_var = float(cv2.Laplacian(gray, cv2.CV_64F).var())
if blur_var < self.min_blur:
return QualityMetrics(blur_var, w, h, aspect_ratio, float(det_conf), False, 0.0)
# Illumination check
mean_brightness = float(np.mean(gray))
if mean_brightness < 25.0 or mean_brightness > 235.0:
return QualityMetrics(blur_var, w, h, aspect_ratio, float(det_conf), False, 0.0)
# Compute single-image quality score (0 ~ 100)
# Blur factor (up to 40 pts) + Resolution factor (up to 30 pts) + Conf factor (up to 30 pts)
s_blur = min(40.0, (blur_var / 50.0) * 40.0)
s_res = min(30.0, (min(w, h) / 256.0) * 30.0)
s_conf = min(30.0, (det_conf / 1.0) * 30.0)
quality_score = float(np.clip(s_blur + s_res + s_conf, 0.0, 100.0))
return QualityMetrics(
blur_score=blur_var,
crop_w=w,
crop_h=h,
aspect_ratio=aspect_ratio,
det_conf=float(det_conf),
is_valid=True,
quality_score=quality_score
)
def evaluate_identity(
self,
embeddings: np.ndarray,
aspect_ratios: List[float],
qualities: List[QualityMetrics],
label: str
) -> SuitabilityReport:
"""BBox 종횡비 + Medoid 결합형 Front Anchor 선정 및 종합 적합도를 평가합니다."""
n_samples = len(embeddings)
recommendations = []
if n_samples == 0:
return SuitabilityReport(
label=label,
sample_count=0,
total_score=0.0,
quality_avg=0.0,
consistency_score=0.0,
diversity_score=0.0,
status_level=StatusLevel.POOR,
recommendations=["등록된 유효 사진이 없습니다. 재등록이 필요합니다."]
)
# 1. Quality Average Score (0 ~ 30 pts)
raw_quality_avg = float(np.mean([q.quality_score for q in qualities])) if qualities else 0.0
s_quality = (raw_quality_avg / 100.0) * 30.0
if raw_quality_avg < 40.0:
recommendations.append("선명도 저조: 일부 사진의 해상도가 낮거나 흔들렸습니다. 선명한 고화질 사진을 권장합니다.")
# 2. Detection Score (0 ~ 20 pts)
raw_conf_avg = float(np.mean([q.det_conf for q in qualities])) if qualities else 0.0
s_detect = min(20.0, raw_conf_avg * 20.0)
# 3. Consistency Score (0 ~ 25 pts)
if n_samples == 1:
s_consistency = 15.0
s_diversity = 5.0
front_anchor_idx = 0
recommendations.append("등록 장수 부족: 현재 1장만 등록되어 있습니다. 최소 3~5장 이상 등록을 권장합니다.")
else:
# Pairwise cosine similarity matrix
sim_matrix = np.dot(embeddings, embeddings.T)
# Upper triangular without diagonal
triu_indices = np.triu_indices(n_samples, k=1)
pair_sims = sim_matrix[triu_indices]
mean_sim = float(np.mean(pair_sims))
if mean_sim < 0.50:
s_consistency = 0.0
recommendations.append("오라벨링 의심: 등록된 사진들 간의 유사도가 매우 낮습니다. 다른 고양이 사진 혼입 여부를 확인해 주세요.")
elif mean_sim < 0.65:
s_consistency = float((mean_sim - 0.50) / 0.15 * 15.0 + 10.0)
else:
s_consistency = 25.0
# 4. Pose Diversity (BBox + Medoid Anchor, 0 ~ 25 pts)
front_anchor_idx = self._select_front_anchor(aspect_ratios, embeddings)
anchor_emb = embeddings[front_anchor_idx]
# Cosine distances from anchor (1 - cos_sim)
dists = 1.0 - np.dot(embeddings, anchor_emb)
dist_variance = float(np.var(dists))
dist_range = float(np.ptp(dists))
# Sample count component (up to 15 pts)
s_count = min(15.0, (n_samples / 4.0) * 15.0)
if n_samples < 3:
recommendations.append(f"등록 장수 부족: 현재 {n_samples}장 등록되어 있습니다. 최소 3~5장 이상 등록을 권장합니다.")
# Spread variance component (up to 10 pts)
# A healthy variance/range indicates multi-angle coverage
if dist_range < 0.08:
s_spread = 2.0
recommendations.append("시점 편향: 정면 등 특정 각도 위주로 등록되었습니다. 고양이의 '좌/우 측면 몸통' 사진을 추가해 주세요.")
elif dist_range < 0.15:
s_spread = 6.0
else:
s_spread = 10.0
s_diversity = float(s_count + s_spread)
total_score = float(np.clip(s_quality + s_detect + s_consistency + s_diversity, 0.0, 100.0))
# Status level
if s_consistency == 0.0 or total_score < 50.0:
status_level = StatusLevel.POOR
elif total_score < 70.0:
status_level = StatusLevel.WARN
elif total_score < 85.0:
status_level = StatusLevel.GOOD
else:
status_level = StatusLevel.EXCELLENT
if not recommendations:
recommendations.append("최적 등록 상태: 다각도 및 고화질 데이터가 고르게 확보되어 실시간 Re-ID에 최적화되었습니다.")
return SuitabilityReport(
label=label,
sample_count=n_samples,
total_score=round(total_score, 1),
quality_avg=round(raw_quality_avg, 1),
consistency_score=round(s_consistency, 1),
diversity_score=round(s_diversity, 1),
status_level=status_level,
recommendations=recommendations,
front_anchor_idx=front_anchor_idx
)
def _select_front_anchor(self, aspect_ratios: List[float], embeddings: np.ndarray) -> int:
"""BBox 종횡비 분포로 정면 후보군을 좁히고, 후보군 내 Medoid를 선정합니다."""
n = len(aspect_ratios)
if n == 1:
return 0
# Sort indices by aspect ratio (smaller ratio = more vertical/front-like)
sorted_indices = np.argsort(aspect_ratios)
# Pick top 40% (at least 2 if n >= 2) as front candidates
n_candidates = max(2, int(np.ceil(n * 0.4)))
candidate_indices = sorted_indices[:n_candidates]
if len(candidate_indices) == 1:
return int(candidate_indices[0])
candidate_embeddings = embeddings[candidate_indices]
# Candidate pairwise distance matrix
cand_sim = np.dot(candidate_embeddings, candidate_embeddings.T)
cand_dist = 1.0 - cand_sim
# Medoid: Candidate with minimal average distance to others
centralities = np.mean(cand_dist, axis=1)
best_cand_idx = int(np.argmin(centralities))
return int(candidate_indices[best_cand_idx])
- Step 4: 테스트 실행하여 통과 확인
Run: pytest tests/test_quality_inspector.py -v
Expected: PASS (모든 단위 테스트 통과)
Task 2: SQLite DB 스키마 확장 및 프로필 캐싱 구현 (reid/models/extractor/embedding.py)
Files:
- Modify:
reid/models/extractor/embedding.py -
Test:
tests/test_embedding_store_profiles.py - Step 1: 실패하는 단위 테스트 작성 (
tests/test_embedding_store_profiles.py)
import sqlite3
import numpy as np
import pytest
from pathlib import Path
from reid.models.extractor.embedding import EmbeddingStore
from reid.core.quality import SuitabilityReport, StatusLevel
def test_embedding_store_schema_migration(tmp_path):
db_file = tmp_path / "test_store.db"
store = EmbeddingStore(str(db_file))
# Verify tables created
cursor = store.conn.cursor()
cursor.execute("PRAGMA table_info(embeddings)")
cols = {row[1] for row in cursor.fetchall()}
for expected_col in ["blur_score", "crop_w", "crop_h", "aspect_ratio", "det_conf", "quality_score"]:
assert expected_col in cols
cursor.execute("PRAGMA table_info(identity_profiles)")
prof_cols = {row[1] for row in cursor.fetchall()}
assert "label" in prof_cols
assert "total_score" in prof_cols
assert "status_level" in prof_cols
store.close()
def test_save_and_get_identity_profile(tmp_path):
db_file = tmp_path / "test_store.db"
store = EmbeddingStore(str(db_file))
report = SuitabilityReport(
label="Nabi",
sample_count=4,
total_score=82.5,
quality_avg=78.0,
consistency_score=25.0,
diversity_score=20.0,
status_level=StatusLevel.GOOD,
recommendations=["시점 편향: 측면 사진을 추가해 주세요."]
)
store.save_identity_profile(report)
loaded = store.get_identity_profile("Nabi")
assert loaded is not None
assert loaded.label == "Nabi"
assert loaded.total_score == 82.5
assert loaded.status_level == StatusLevel.GOOD
assert len(loaded.recommendations) == 1
# Check non-existent label returns UNCHECKED
unchecked = store.get_identity_profile("UnknownCat")
assert unchecked is not None
assert unchecked.status_level == StatusLevel.UNCHECKED
store.close()
def test_add_batch_with_quality_metadata(tmp_path):
db_file = tmp_path / "test_store.db"
store = EmbeddingStore(str(db_file))
embs = np.random.randn(2, 128).astype(np.float32)
labels = ["Nabi", "Nabi"]
paths = ["/tmp/n1.jpg", "/tmp/n2.jpg"]
hashes = ["h1", "h2"]
metas = [
{"blur_score": 45.0, "crop_w": 200, "crop_h": 250, "aspect_ratio": 0.8, "det_conf": 0.9, "quality_score": 80.0},
{"blur_score": 55.0, "crop_w": 210, "crop_h": 260, "aspect_ratio": 0.81, "det_conf": 0.95, "quality_score": 85.0}
]
store.add_batch(
embeddings=embs,
labels=labels,
image_paths=paths,
image_hashes=hashes,
model_name="test_model",
quality_metas=metas
)
cursor = store.conn.cursor()
cursor.execute("SELECT blur_score, quality_score FROM embeddings WHERE label = 'Nabi'")
rows = cursor.fetchall()
assert len(rows) == 2
assert rows[0][0] == 45.0
assert rows[1][1] == 85.0
store.close()
- Step 2: 테스트 실행하여 실패 확인
Run: pytest tests/test_embedding_store_profiles.py -v
Expected: FAIL with missing methods or schema mismatch
- Step 3:
reid/models/extractor/embedding.py수정
EmbeddingStore에 스키마 자동 마이그레이션, identity_profiles CRUD, 및 add_batch 품질 메타데이터 인자를 추가합니다:
# reid/models/extractor/embedding.py 수정 내용 요약
# - SQL_CREATE_IDENTITY_PROFILES 및 SQL_CREATE_TABLE 확장
# - _migrate_schema() 구현으로 기존 DB 호환성 유지
# - save_identity_profile(report), get_identity_profile(label), list_identity_profiles() 구현
# - add_batch()에 quality_metas 파라미터(Optional) 추가 지원
- Step 4: 테스트 실행하여 통과 확인
Run: pytest tests/test_embedding_store_profiles.py -v
Expected: PASS (모든 단위 테스트 통과)
Task 3: 등록 파이프라인 연동 (reid/models/extractor/model.py)
Files:
- Modify:
reid/models/extractor/model.py - Modify:
config.yaml - Modify:
reid/cfg/default.yaml -
Test:
tests/test_register_quality.py - Step 1: 실패하는 통합 테스트 작성 (
tests/test_register_quality.py)
import os
import cv2
import numpy as np
import pytest
from pathlib import Path
from reid.models.extractor.model import ExtractorModel
from reid.models.extractor.embedding import EmbeddingStore
from reid.core.config import get_config
class DummyDetectorBox:
def __init__(self, x1, y1, x2, y2, conf=0.9):
self.xyxy = [x1, y1, x2, y2]
self.conf = conf
self.track_id = 1
def crop(self, img):
return img[int(self.y1):int(self.y2), int(self.x1):int(self.x2)]
@property
def x1(self): return self.xyxy[0]
@property
def y1(self): return self.xyxy[1]
@property
def x2(self): return self.xyxy[2]
@property
def y2(self): return self.xyxy[3]
class DummyDetectorResults:
def __init__(self, img, boxes):
self.orig_img = img
self.boxes = boxes
class DummyDetector:
def __init__(self, boxes=None):
self._boxes = boxes
def predict(self, im):
if isinstance(im, (str, Path)):
im = cv2.imread(str(im))
h, w = im.shape[:2]
boxes = self._boxes if self._boxes is not None else [DummyDetectorBox(10, 10, w - 10, h - 10, conf=0.92)]
return DummyDetectorResults(im, boxes)
def test_register_strict_filtering_and_profile_creation(tmp_path, monkeypatch):
# Setup test dataset directory with 1 sharp and 1 blurry image
cat_dir = tmp_path / "dataset" / "Nabi"
cat_dir.mkdir(parents=True)
# Sharp image
np.random.seed(42)
sharp_img = np.random.randint(0, 255, (200, 200, 3), dtype=np.uint8)
cv2.imwrite(str(cat_dir / "sharp.jpg"), sharp_img)
# Blurry/solid image (should be rejected by Strict Gate)
blur_img = np.full((200, 200, 3), 128, dtype=np.uint8)
cv2.imwrite(str(cat_dir / "blur.jpg"), blur_img)
db_path = str(tmp_path / "test.db")
cfg = get_config()
cfg.db_path = db_path
cfg.reg_blur_threshold = 30.0
cfg.reg_min_crop_size = 64
cfg.reg_min_det_conf = 0.60
# Mock extractor predictor
class DummyExtractor(ExtractorModel):
def _load_model(self, path): self.model = None
def _get_trainer(self): pass
def _get_validator(self): pass
def _get_predictor(self):
class Pred:
def predict_batch(self, paths):
return np.random.randn(len(paths), 128).astype(np.float32)
return Pred()
extractor = DummyExtractor(db_path=db_path, cfg=cfg)
extractor.detector = DummyDetector()
# Run register
extractor.register(source=str(tmp_path / "dataset"), label="Nabi")
# Verify only sharp.jpg registered in DB
cursor = extractor.store.conn.cursor()
cursor.execute("SELECT image_path, quality_score FROM embeddings WHERE label = 'Nabi'")
rows = cursor.fetchall()
assert len(rows) == 1
assert "sharp.jpg" in rows[0][0]
# Verify identity_profiles created
prof = extractor.store.get_identity_profile("Nabi")
assert prof is not None
assert prof.sample_count == 1
assert prof.status_level != "UNCHECKED"
extractor.store.close()
- Step 2: 테스트 실행하여 실패 확인
Run: pytest tests/test_register_quality.py -v
Expected: FAIL
- Step 3:
reid/models/extractor/model.py수정
register 및 register_batch_images에 YOLO 검출기 크롭, RegistrationQualityInspector 선별, 메타데이터 적재 및 identity_profiles 생성 로직을 통합합니다.
- Step 4: 테스트 실행하여 통과 확인
Run: pytest tests/test_register_quality.py -v
Expected: PASS
Task 4: CLI 명령어 확장 및 리포트 렌더링 (reid/cli.py)
Files:
- Modify:
reid/cli.py -
Test:
tests/test_cli_quality.py - Step 1: 실패하는 CLI 테스트 작성 (
tests/test_cli_quality.py)
import sys
import pytest
from reid.cli import main
from reid.models.extractor.embedding import EmbeddingStore
from reid.core.quality import SuitabilityReport, StatusLevel
def test_cli_list_with_suitability(tmp_path, monkeypatch, caplog):
db_file = tmp_path / "test.db"
store = EmbeddingStore(str(db_file))
report = SuitabilityReport(
label="Nabi",
sample_count=3,
total_score=88.0,
quality_avg=85.0,
consistency_score=25.0,
diversity_score=20.0,
status_level=StatusLevel.EXCELLENT,
recommendations=["최적 등록 상태입니다."]
)
store.save_identity_profile(report)
store.close()
monkeypatch.setattr(sys, "argv", ["reid", "list", f"db_path={db_file}"])
import logging
with caplog.at_level(logging.INFO):
main()
assert "Nabi" in caplog.text
assert "88.0" in caplog.text or "EXCELLENT" in caplog.text
def test_cli_inspect_label(tmp_path, monkeypatch, caplog):
db_file = tmp_path / "test.db"
store = EmbeddingStore(str(db_file))
report = SuitabilityReport(
label="Lulu",
sample_count=2,
total_score=62.0,
quality_avg=70.0,
consistency_score=20.0,
diversity_score=10.0,
status_level=StatusLevel.WARN,
recommendations=["시점 편향: 측면 사진을 추가해 주세요."]
)
store.save_identity_profile(report)
store.close()
monkeypatch.setattr(sys, "argv", ["reid", "inspect", "label=Lulu", f"db_path={db_file}"])
import logging
with caplog.at_level(logging.INFO):
main()
assert "Lulu" in caplog.text
assert "시점 편향" in caplog.text
assert "WARN" in caplog.text
- Step 2: 테스트 실행하여 실패 확인
Run: pytest tests/test_cli_quality.py -v
Expected: FAIL with Unknown mode: inspect or missing output
- Step 3:
reid/cli.py수정
reid list:store.list_identity_profiles()와 결합하여 라벨별 적합도 점수 및[STATUS]뱃지 출력 (UNCHECKED지원)reid inspect:label인자를 받아 해당 개체의 세부 4대 점수 및 추천 가이드 출력reid migrate: 특징 재추출 시 단일 패스로 품질 점수 및 프로필 일괄 갱신 연동
- Step 4: 테스트 실행하여 통과 확인
Run: pytest tests/test_cli_quality.py -v
Expected: PASS
Task 5: 전체 통합 검증 및 회귀 테스트
- Step 1: 전체 pytest 스위트 실행
Run: pytest tests/ -v
Expected: All tests PASS (기존 테스트 및 신규 테스트 전체 통과)
- Step 2: 실제 CLI 동작 검증
Run: reid list
Expected: 정상적으로 DB 상태 및 요약 출력 확인
Execution Handoff
이 구현 계획은 dev/lumi_docs/superpowers/plans/2026-08-15-RegistrationQualityAndSuitabilityScoringPlan.md에 저장되었습니다.
두 가지 실행 방식 중 선택하실 수 있습니다:
- Subagent-Driven (권장): 각 태스크마다 서브에이전트를 생성하여 TDD 방식으로 구현 및 리뷰 진행
- Inline Execution: 현재 세션에서
executing-plans를 사용하여 태스크를 순차적으로 구현
어떤 방식으로 진행하시겠습니까?
C
Contents
