feat(tag-eval): "keep" records a confirmation so doubts stop resurfacing
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m23s

"Keep" on a doubted positive was a no-op, so the same confirmed-correct images
came back in "head doubts" every run (operator-flagged: reinforcement keeps
surfacing the same images). Add tag_positive_confirmation (mirror of
tag_suggestion_rejection): keep → POST /images/<id>/tags/<tag_id>/confirm, and
the eval excludes confirmed positives from the doubts list — exactly as rejected
items already drop out of the suggest list. The tag stays a positive either way
(confirmation is a "reviewed" marker, not a training change).

- model TagPositiveConfirmation + migration 0057; confirm endpoint (idempotent).
- tag_eval: _confirmed_ids + exclude from head_doubts_positive examples.
- store.confirmTag + card "keep" calls it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 01:32:20 -04:00
parent 4fd8790c85
commit b69c70ab2b
7 changed files with 129 additions and 12 deletions
@@ -247,7 +247,7 @@ async function act(c, it, dir, verdict) {
if (dir === 'suggest' && verdict === 'yes') { call = store.applyTag(it.id, c.tag_id); label = 'tagged' }
else if (dir === 'suggest' && verdict === 'no') { call = store.rejectTag(it.id, c.tag_id); label = 'rejected' }
else if (dir === 'doubts' && verdict === 'no') { call = store.removeTag(it.id, c.tag_id); label = 'removed' }
else { acted.value[key] = 'kept'; return } // doubt + yes = keep, no write
else { call = store.confirmTag(it.id, c.tag_id); label = 'kept' } // doubt + yes = keep (confirm)
try {
await call
acted.value[key] = label
+7 -1
View File
@@ -47,5 +47,11 @@ export const useTagEvalStore = defineStore('tagEval', () => {
{ body: { tag_id: tagId } })
}
return { start, getRun, latest, applyTag, rejectTag, removeTag }
// "Keep" — affirm a doubted positive is correct. Records a confirmation so it
// stops resurfacing in the doubts list (it stays a positive either way).
async function confirmTag(imageId, tagId) {
return await api.post(`/api/images/${imageId}/tags/${tagId}/confirm`)
}
return { start, getRun, latest, applyTag, rejectTag, removeTag, confirmTag }
})