aa4cc7c629
Rename returns 409 on collision (frontend shows FC-2c merge hint), 400 on empty. ml_admin: GET/PATCH settings (thresholds + min_reference; model versions read-only here), POST backfill / recompute-centroids returning 202 + celery task id. Tests integration-marked, eager Celery. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
88 lines
2.6 KiB
Python
88 lines
2.6 KiB
Python
"""ML admin API: settings, backfill trigger, centroid recompute trigger."""
|
|
|
|
from quart import Blueprint, jsonify, request
|
|
|
|
from ..extensions import make_engine, make_session_factory
|
|
from ..models import MLSettings
|
|
|
|
ml_admin_bp = Blueprint("ml_admin", __name__, url_prefix="/api/ml")
|
|
|
|
_engine = None
|
|
_Session = None
|
|
|
|
|
|
def _session_factory():
|
|
global _engine, _Session
|
|
if _engine is None:
|
|
_engine = make_engine()
|
|
_Session = make_session_factory(_engine)
|
|
return _Session
|
|
|
|
|
|
_EDITABLE = (
|
|
"suggestion_threshold_artist",
|
|
"suggestion_threshold_character",
|
|
"suggestion_threshold_copyright",
|
|
"suggestion_threshold_general",
|
|
"centroid_similarity_threshold",
|
|
"min_reference_images",
|
|
)
|
|
|
|
|
|
@ml_admin_bp.route("/settings", methods=["GET"])
|
|
async def get_settings():
|
|
from sqlalchemy import select
|
|
|
|
Session = _session_factory()
|
|
async with Session() as session:
|
|
s = (
|
|
await session.execute(select(MLSettings).where(MLSettings.id == 1))
|
|
).scalar_one()
|
|
return jsonify(
|
|
{
|
|
"suggestion_threshold_artist": s.suggestion_threshold_artist,
|
|
"suggestion_threshold_character": s.suggestion_threshold_character,
|
|
"suggestion_threshold_copyright": s.suggestion_threshold_copyright,
|
|
"suggestion_threshold_general": s.suggestion_threshold_general,
|
|
"centroid_similarity_threshold": s.centroid_similarity_threshold,
|
|
"min_reference_images": s.min_reference_images,
|
|
"tagger_model_version": s.tagger_model_version,
|
|
"embedder_model_version": s.embedder_model_version,
|
|
}
|
|
)
|
|
|
|
|
|
@ml_admin_bp.route("/settings", methods=["PATCH"])
|
|
async def patch_settings():
|
|
from sqlalchemy import select
|
|
|
|
body = await request.get_json()
|
|
if not isinstance(body, dict):
|
|
return jsonify({"error": "body must be an object"}), 400
|
|
Session = _session_factory()
|
|
async with Session() as session:
|
|
s = (
|
|
await session.execute(select(MLSettings).where(MLSettings.id == 1))
|
|
).scalar_one()
|
|
for field in _EDITABLE:
|
|
if field in body:
|
|
setattr(s, field, body[field])
|
|
await session.commit()
|
|
return await get_settings()
|
|
|
|
|
|
@ml_admin_bp.route("/backfill", methods=["POST"])
|
|
async def trigger_backfill():
|
|
from ..tasks.ml import backfill
|
|
|
|
r = backfill.delay()
|
|
return jsonify({"celery_task_id": r.id}), 202
|
|
|
|
|
|
@ml_admin_bp.route("/recompute-centroids", methods=["POST"])
|
|
async def trigger_recompute():
|
|
from ..tasks.ml import recompute_centroids
|
|
|
|
r = recompute_centroids.delay()
|
|
return jsonify({"celery_task_id": r.id}), 202
|