feat(ml): Settings → Tagging 'Crop proposers' card (#134 step 3)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 40s
CI / integration (push) Successful in 3m39s

Exposes the detector config (per-proposer enable + weights + confidence, caps,
dedupe IoU) in Settings → Tagging, backed by MLSettings via /api/ml/settings.
ml_admin adds the detector fields to _EDITABLE + GET payload + validation (conf
0..1, caps >=1, IoU 0..1). New CropProposersCard.vue (mirrors HeadsCard) with
working defaults pre-filled, per-field live-save (no restart — the agent picks
changes up on its next lease), weights-format help, switch-revert on error.
Closes milestone #134: all three proposers are on out-of-the-box and tunable in
the UI. Test: detector defaults GET + patch round-trip + range validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
2026-07-05 19:51:55 -04:00
parent a4df279343
commit ab362bc79c
4 changed files with 217 additions and 0 deletions
+29
View File
@@ -52,6 +52,35 @@ async def test_embedder_model_default_settable_and_empty_rejected(client):
assert bad.status_code == 400
@pytest.mark.asyncio
async def test_detector_settings_defaults_patch_and_validation(client):
# #134: crop-proposer config is exposed + editable here (announced to the
# agent via the lease). Defaults are all-on with the pinned weights.
body = await (await client.get("/api/ml/settings")).get_json()
assert body["detector_person_enabled"] is True
assert body["detector_anatomy_enabled"] is True
assert "yolov11m_aa22" in body["detector_anatomy_weights"]
assert body["detector_panel_weights"].endswith("::best.pt")
assert body["detector_max_regions"] == 128
ok = await client.patch("/api/ml/settings", json={
"detector_anatomy_enabled": False,
"detector_person_conf": 0.5,
"detector_max_panels": 4,
})
assert ok.status_code == 200
out = await ok.get_json()
assert out["detector_anatomy_enabled"] is False
assert out["detector_person_conf"] == 0.5
assert out["detector_max_panels"] == 4
# Out-of-range confidence + non-positive cap are rejected.
assert (await client.patch(
"/api/ml/settings", json={"detector_panel_conf": 1.5})).status_code == 400
assert (await client.patch(
"/api/ml/settings", json={"detector_max_regions": 0})).status_code == 400
@pytest.mark.asyncio
async def test_embedder_models_list(client):
# #1203: the dropdown reads the supported-model list from the server.