chore: retire the tag-eval harness — it proved the heads system, job done (operator-approved)
CI / lint (push) Successful in 4s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 33s
CI / integration (push) Successful in 3m24s

The head-vs-centroid eval (#1130) existed to prove the 'frozen embedding +
trained head' spine; the operator accepted the tagging system and dropped the
harness. Removed per rule 22: TagEvalCard + store, /api/tag_eval blueprint,
tag_eval_run ml task, recover-stalled-tag-eval-runs sweep + beat entry,
TagEvalRun model + table (migration 0073), and its tests.

The eval's data loaders + metric helpers were NOT eval-specific — the nightly
heads trainer runs on them — so they moved verbatim to
services/ml/training_data.py (heads.py import updated; behavior unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
2026-07-02 12:41:24 -04:00
parent a7abcc41ca
commit eaea4308fc
17 changed files with 178 additions and 1091 deletions
-77
View File
@@ -1,77 +0,0 @@
import pytest
from backend.app.models import TagEvalRun
from backend.app.services.ml.tag_eval import (
DEFAULT_CONCEPTS,
_normalize_params,
)
pytestmark = pytest.mark.integration
def test_normalize_params_defaults_and_overrides():
d = _normalize_params(None)
assert d["concepts"] == DEFAULT_CONCEPTS
assert d["neg_ratio"] >= 1 and d["cv_folds"] >= 2
over = _normalize_params(
{"concepts": ["glasses", " ", "cat"], "neg_ratio": "4",
"cv_folds": "1", "curve_points": [30, 10, 10]}
)
assert over["concepts"] == ["glasses", "cat"] # blanks dropped
assert over["neg_ratio"] == 4
assert over["cv_folds"] == 2 # clamped to >=2
assert over["curve_points"] == [10, 30] # deduped + sorted
@pytest.mark.asyncio
async def test_history_and_detail_rehydrate(client, db):
# A finished run with a report — the persisted row IS the survives-navigation
# source: history is light (no report), detail carries it.
run = TagEvalRun(
params={"concepts": ["glasses"]},
status="ready",
report={"concepts": [{"name": "glasses", "head": {"ap": 0.9}}]},
)
db.add(run)
await db.flush()
await db.commit()
rid = run.id
h = await client.get("/api/tag-eval?limit=10")
assert h.status_code == 200
hbody = await h.get_json()
row = next(r for r in hbody["runs"] if r["id"] == rid)
assert row["status"] == "ready"
assert "report" not in row # list stays light
d = await client.get(f"/api/tag-eval/{rid}")
assert d.status_code == 200
dbody = await d.get_json()
assert dbody["report"]["concepts"][0]["name"] == "glasses"
@pytest.mark.asyncio
async def test_create_enqueues_running(client, db, monkeypatch):
monkeypatch.setattr(
"backend.app.tasks.ml.tag_eval_run.delay", lambda *a, **k: None
)
resp = await client.post("/api/tag-eval", json={"params": {"concepts": ["cat"]}})
assert resp.status_code == 202
body = await resp.get_json()
assert body["status"] == "running"
got = await db.get(TagEvalRun, body["run_id"])
assert got is not None and got.status == "running"
@pytest.mark.asyncio
async def test_create_conflicts_when_one_running(client, db, monkeypatch):
monkeypatch.setattr(
"backend.app.tasks.ml.tag_eval_run.delay", lambda *a, **k: None
)
db.add(TagEvalRun(params={}, status="running"))
await db.flush()
await db.commit()
resp = await client.post("/api/tag-eval", json={"params": {}})
assert resp.status_code == 409
body = await resp.get_json()
assert body["error"] == "eval_already_running"