feat(ml): CCIP references exclude unconfirmed auto character tags + confirm trips detectors (m139)
CI / lint (push) Failing after 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Failing after 42s
CI / integration (push) Successful in 3m46s

Completes "no self-training": unconfirmed auto-applied character tags no longer
seed CCIP references — character_references + the prototype builder
(_current_fingerprints/_rebuild_one) gain a shared _positive_char_tag filter
(human-applied OR operator-confirmed), mirroring the head-positive exclusion.

Confirming a tag also has to move the change-detectors, or an incremental
refresh/Retrain right after a confirm wouldn't fold the tag in (only the nightly
full pass would): the CCIP global gate now counts character confirmations, and
the head training fingerprint counts confirmations. Test for the CCIP path.

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-06 18:59:00 -04:00
parent 775941609d
commit bae077e323
4 changed files with 60 additions and 3 deletions
+10 -1
View File
@@ -189,11 +189,20 @@ def _head_fingerprints(session: Session, tag_ids: list[int]) -> dict[int, str]:
.group_by(TagSuggestionRejection.tag_id)
).all()
rej_map = {t: (c, m) for t, c, m in rej}
# Confirmations promote an auto-applied tag to a positive (milestone 139), so
# a confirm must move the fingerprint too — else a manual Retrain right after
# confirming wouldn't fold the tag in (the nightly full run would).
conf = session.execute(
select(TagPositiveConfirmation.tag_id, func.count())
.where(TagPositiveConfirmation.tag_id.in_(tag_ids))
.group_by(TagPositiveConfirmation.tag_id)
).all()
conf_map = {t: c for t, c in conf}
out = {}
for t in tag_ids:
pc, pm = pos_map.get(t, (0, None))
rc, rm = rej_map.get(t, (0, None))
out[t] = f"{pc}:{pm}:{rc}:{rm}"
out[t] = f"{pc}:{pm}:{rc}:{rm}:{conf_map.get(t, 0)}"
return out