Stop labelling a model guess as a read tail
_annotate_speaker_methods stamped `tail`, the highest-trust provenance, on any line whose speaker matched a present local_id, at gemma's confidence of 1.0. No balloon was read. Three of three sampled two-character panels had the speakers swapped, so a multi-character guess is now dropped to unknown, and a solo-panel guess is kept as model_solo at 0.7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+23
-3
@@ -374,16 +374,31 @@ def resolve_speakers(dialogue: list, present: list) -> list:
|
||||
|
||||
|
||||
def _annotate_speaker_methods(dialogue: list, present: list) -> list:
|
||||
"""Fill provenance for model-attributed lines without overwriting grounded/backstop methods."""
|
||||
"""Fill provenance for model-attributed lines without overwriting grounded/backstop methods.
|
||||
|
||||
A speaker matching a present local_id used to be stamped `tail`, the highest-trust label, at
|
||||
gemma's own confidence of 1.0. No balloon geometry was ever read. On the 2026-08-11 chapter every
|
||||
sampled two-character panel had the speakers swapped
|
||||
(`caveats/speaker-attribution.md#tail-is-not-geometry`), so with 2+ present the guess is dropped
|
||||
rather than shipped as truth. With one present it is the same claim as the solo backstop, so it is
|
||||
kept and named for what it is.
|
||||
ponytail: drop-on-crowd is the honest floor, not the fix. Bind by tail geometry when the balloon
|
||||
detector lands, then this branch reads a tail for real."""
|
||||
local_ids = {c.get("local_id") for c in present if c.get("local_id")}
|
||||
crowded = len(present) > 1
|
||||
for d in dialogue:
|
||||
if d.get("speaker_method"):
|
||||
continue
|
||||
speaker = (d.get("speaker") or "").strip()
|
||||
if d.get("type", "speech") not in _SPEECH or not speaker or speaker == "unknown":
|
||||
d["speaker_method"] = "unknown"
|
||||
elif speaker in local_ids and crowded:
|
||||
d["speaker"] = "unknown"
|
||||
d["confidence"] = 0.0
|
||||
d["speaker_method"] = "unknown"
|
||||
elif speaker in local_ids:
|
||||
d["speaker_method"] = "tail"
|
||||
d["speaker_method"] = "model_solo"
|
||||
d["confidence"] = min(float(d.get("confidence") or 0.7), 0.7)
|
||||
else:
|
||||
d["speaker_method"] = "turn_taking"
|
||||
return dialogue
|
||||
@@ -976,7 +991,12 @@ if __name__ == "__main__":
|
||||
two = [{"local_id": "person_1"}, {"local_id": "person_2"}]
|
||||
assert resolve_speakers([{"speaker": "unknown", "type": "speech", "text": "x"}], two)[0]["speaker"] == "unknown"
|
||||
assert resolve_speakers([{"speaker": "unknown", "type": "speech", "text": "x"}], [])[0]["speaker"] == "unknown"
|
||||
assert resolve_speakers([{"speaker": "person_1", "type": "speech", "text": "x"}], two)[0]["speaker_method"] == "tail"
|
||||
# a present-local_id match is gemma's guess, not a read tail: dropped when 2+ are present,
|
||||
# kept but capped at 0.7 when only one is (caveats/speaker-attribution.md#tail-is-not-geometry)
|
||||
crowd = resolve_speakers([{"speaker": "person_1", "type": "speech", "text": "x", "confidence": 1.0}], two)[0]
|
||||
assert crowd["speaker"] == "unknown" and crowd["speaker_method"] == "unknown" and crowd["confidence"] == 0.0
|
||||
lone = resolve_speakers([{"speaker": "person_1", "type": "speech", "text": "x", "confidence": 1.0}], solo)[0]
|
||||
assert lone["speaker"] == "person_1" and lone["speaker_method"] == "model_solo" and lone["confidence"] == 0.7
|
||||
# set-of-mark: gemma answers a face label -> remapped to local_id; a name/unknown passes through
|
||||
lbl = _apply_speaker_labels([{"speaker": "P1"}, {"speaker": "Aria"}, {"speaker": "unknown"}],
|
||||
{"P1": "person_3"})
|
||||
|
||||
Reference in New Issue
Block a user