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
-2
View File
@@ -38,7 +38,6 @@ def all_blueprints() -> list[Blueprint]:
from .suggestions import suggestions_bp
from .system_activity import system_activity_bp
from .system_backup import system_backup_bp
from .tag_eval import tag_eval_bp
from .tags import tags_bp
from .thumbnails import thumbnails_bp
return [
@@ -58,7 +57,6 @@ def all_blueprints() -> list[Blueprint]:
import_admin_bp,
suggestions_bp,
aliases_bp,
tag_eval_bp,
heads_bp,
gpu_bp,
ccip_bp,
-70
View File
@@ -1,70 +0,0 @@
"""Tag-eval API (#1130): trigger + revisit the head-vs-centroid eval.
The run + full report live in the tag_eval_run row, so the admin card rehydrates
from GET (history / detail) on mount — the report survives navigation rather than
living in transient frontend state.
"""
from quart import Blueprint, jsonify, request
from sqlalchemy import select
from ..extensions import get_session
from ..models import TagEvalRun
from ..services.ml.tag_eval import EvalAlreadyRunning, start_tag_eval_run
tag_eval_bp = Blueprint("tag_eval", __name__, url_prefix="/api/tag-eval")
def _serialize(run: TagEvalRun, *, include_report: bool) -> dict:
out = {
"id": run.id,
"params": run.params,
"status": run.status,
"started_at": run.started_at.isoformat() if run.started_at else None,
"finished_at": run.finished_at.isoformat() if run.finished_at else None,
"error": run.error,
}
if include_report:
out["report"] = run.report
return out
@tag_eval_bp.route("", methods=["POST"])
async def create():
body = await request.get_json(silent=True) or {}
params = body.get("params") or body or {}
async with get_session() as session:
try:
run_id = await session.run_sync(
lambda s: start_tag_eval_run(s, params)
)
except EvalAlreadyRunning as running:
return jsonify({
"error": "eval_already_running",
"running_id": int(running.args[0]),
}), 409
await session.commit()
return jsonify({"run_id": run_id, "status": "running"}), 202
@tag_eval_bp.route("", methods=["GET"])
async def history():
try:
limit = min(int(request.args.get("limit", "20")), 100)
except ValueError:
return jsonify({"error": "invalid_limit"}), 400
async with get_session() as session:
rows = (await session.execute(
select(TagEvalRun).order_by(TagEvalRun.id.desc()).limit(limit)
)).scalars().all()
# List is light — no full report (the detail endpoint carries it).
return jsonify({"runs": [_serialize(r, include_report=False) for r in rows]})
@tag_eval_bp.route("/<int:run_id>", methods=["GET"])
async def detail(run_id: int):
async with get_session() as session:
run = await session.get(TagEvalRun, run_id)
if run is None:
return jsonify({"error": "not_found"}), 404
return jsonify(_serialize(run, include_report=True))