Revise wearer entry and yield annotation protocol
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate wearer-centric EasyCom EOT/INT candidates for AI and human review.
|
||||
"""Generate wearer-centric EasyCom entry/INT candidates for AI and human review.
|
||||
|
||||
This stage never changes EasyCom speech boundaries and never claims semantic
|
||||
ground truth. It groups short wearer resumptions into floor-level episodes,
|
||||
starts EOT candidates from observed wearer entries and retrieves preceding
|
||||
non-wearer release options, requires a new
|
||||
starts entry candidates from every observed floor-level wearer onset and
|
||||
retains preceding non-wearer release options as context, requires a new
|
||||
non-wearer onset inside the wearer floor episode for INT candidates, and keeps
|
||||
unpaired or ineligible raw boundaries as deterministic controls. All timing
|
||||
ineligible raw boundaries as deterministic controls. All timing
|
||||
thresholds are CLI parameters, and raw evidence is retained for DeepSeek
|
||||
pre-labeling and human correction.
|
||||
"""
|
||||
@@ -28,8 +28,8 @@ from derive_easycom_interaction_events import (
|
||||
from easycom_manifest import ALL_SESSIONS, TEST_SESSIONS
|
||||
|
||||
|
||||
SCHEMA_VERSION = 4
|
||||
PROTOCOL_VERSION = "easycom-wearer-anchor-first-eot-int-candidates-v6"
|
||||
SCHEMA_VERSION = 5
|
||||
PROTOCOL_VERSION = "easycom-wearer-entry-int-candidates-v7"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -88,7 +88,7 @@ class ProtocolCandidate:
|
||||
def requires_semantic_adjudication(row: dict[str, Any]) -> bool:
|
||||
"""Return whether a stored row belongs in the primary AI/human queue.
|
||||
|
||||
Explicit v3--v5 control sources are authoritative. For old v2 candidate
|
||||
Explicit legacy control sources are authoritative. For old v2 candidate
|
||||
files, reconstruct the same-speaker crossing requirement from the stored
|
||||
activity IDs. Missing legacy evidence remains reviewable rather than being
|
||||
silently discarded.
|
||||
@@ -354,8 +354,8 @@ def derive_protocol_candidates(utterances: Sequence[Utterance], clips: Sequence[
|
||||
|
||||
# A: classify raw wearer onsets before proposing any behavioral event.
|
||||
# Short uncompeted gaps are internal to a floor episode. All other raw
|
||||
# onsets begin a new floor episode, but become EOT candidates only when
|
||||
# they can be paired with a preceding non-wearer release opportunity.
|
||||
# onsets begin a new floor episode and are all entry candidates. A
|
||||
# preceding non-wearer release is optional context, not a gate.
|
||||
onset_records: dict[int, dict[str, Any]] = {}
|
||||
raw_offsets: list[int] = []
|
||||
for index in range(1, len(wearer)):
|
||||
@@ -412,10 +412,10 @@ def derive_protocol_candidates(utterances: Sequence[Utterance], clips: Sequence[
|
||||
for frame in sorted(resume_onsets):
|
||||
record = onset_records[frame]
|
||||
add(_candidate(
|
||||
session=session, task="eot_response_control", source="WEARER_RESUME",
|
||||
session=session, task="wearer_entry_control", source="WEARER_RESUME",
|
||||
rule_type="WITHIN_TURN_RESUME_CONTROL", anchor_type="wearer_onset", frame=frame,
|
||||
region_start=frame, region_end=min(segment_end, frame + relation_frames),
|
||||
label="not_applicable", subtype=None,
|
||||
label="control", subtype=None,
|
||||
previous_ids=_active_other_ids(rows, frame - 1, wearer_id=wearer_id),
|
||||
incoming_ids=_active_other_ids(rows, frame, wearer_id=wearer_id),
|
||||
evidence=record["evidence"], clips=segment_clips, rows=rows,
|
||||
@@ -423,11 +423,9 @@ def derive_protocol_candidates(utterances: Sequence[Utterance], clips: Sequence[
|
||||
reason="short raw wearer pause without sustained competing other takeover; merged into the existing floor episode",
|
||||
), ("WEARER_RESUME", frame, ()))
|
||||
|
||||
# A1: start from each observed floor-level wearer entry, then search
|
||||
# backward for release anchors that survive deterministic timing checks.
|
||||
# This yields at most one review item per wearer behavior. The semantic
|
||||
# annotator may select only one of the stored options; it cannot invent
|
||||
# a trigger or move either timestamp. Long silence alone is not a veto.
|
||||
# A1: every observed floor-level wearer onset is an entry candidate.
|
||||
# Search backward for release anchors only to preserve response context;
|
||||
# no preceding release is required for a positive WEARER_ENTRY.
|
||||
for onset_frame in floor_onsets:
|
||||
release_options: list[tuple[Utterance, int]] = []
|
||||
for release in other_rows:
|
||||
@@ -481,19 +479,24 @@ def derive_protocol_candidates(utterances: Sequence[Utterance], clips: Sequence[
|
||||
minimum_response_delay_seconds=(onset_frame - latest_opportunity_frame) / SOURCE_FPS,
|
||||
maximum_response_delay_seconds=(onset_frame - earliest_opportunity_frame) / SOURCE_FPS,
|
||||
competing_other_onsets_before_behavior=[],
|
||||
eot_opportunity_paired=True,
|
||||
preceding_release_paired=True,
|
||||
entry_context_timing=(
|
||||
"OTHER_ACTIVE"
|
||||
if _active_other_ids(rows, onset_frame, wearer_id=wearer_id)
|
||||
else "AFTER_OTHER_RELEASE"
|
||||
),
|
||||
)
|
||||
previous_ids = tuple(sorted({row.participant_id for row, _ in release_options}))
|
||||
add(_candidate(
|
||||
session=session, task="eot_response", source="WEARER_ONSET",
|
||||
rule_type="EOT_OPPORTUNITY_CANDIDATE", anchor_type="wearer_onset",
|
||||
session=session, task="wearer_entry", source="WEARER_ONSET",
|
||||
rule_type="WEARER_ENTRY_CANDIDATE", anchor_type="wearer_onset",
|
||||
frame=onset_frame, region_start=earliest_opportunity_frame,
|
||||
region_end=min(segment_end, onset_frame + relation_frames),
|
||||
label="unsure", subtype=None, previous_ids=previous_ids,
|
||||
label="candidate", subtype=None, previous_ids=previous_ids,
|
||||
incoming_ids=_active_other_ids(rows, onset_frame, wearer_id=wearer_id),
|
||||
evidence=evidence, clips=segment_clips, rows=rows, wearer_id=wearer_id,
|
||||
context_frames=context_frames,
|
||||
reason="floor-level wearer entry with one or more surviving preceding release options",
|
||||
reason="floor-level wearer onset; preceding release options retained as context",
|
||||
), ("WEARER_ONSET", onset_frame, previous_ids))
|
||||
continue
|
||||
|
||||
@@ -507,19 +510,26 @@ def derive_protocol_candidates(utterances: Sequence[Utterance], clips: Sequence[
|
||||
behavior_anchor_type="wearer_floor_onset",
|
||||
behavior_anchor_frame=frame,
|
||||
behavior_anchor_seconds=frame / SOURCE_FPS,
|
||||
eot_opportunity_paired=False,
|
||||
preceding_release_paired=False,
|
||||
entry_context_timing=(
|
||||
"OTHER_ACTIVE"
|
||||
if _active_other_ids(rows, frame, wearer_id=wearer_id)
|
||||
else "FROM_SILENCE_OR_UNRESOLVED"
|
||||
),
|
||||
)
|
||||
previous_ids = _active_other_ids(rows, frame - 1, wearer_id=wearer_id)
|
||||
add(_candidate(
|
||||
session=session, task="eot_response_control", source="WEARER_ONSET_CONTROL",
|
||||
rule_type="UNPAIRED_WEARER_ENTRY_CONTROL", anchor_type="wearer_onset", frame=frame,
|
||||
region_start=frame, region_end=min(segment_end, frame + relation_frames),
|
||||
label="not_applicable", subtype=None,
|
||||
previous_ids=_active_other_ids(rows, frame - 1, wearer_id=wearer_id),
|
||||
session=session, task="wearer_entry", source="WEARER_ONSET",
|
||||
rule_type="WEARER_ENTRY_CANDIDATE", anchor_type="wearer_onset", frame=frame,
|
||||
region_start=max(segment_start, frame - relation_frames),
|
||||
region_end=min(segment_end, frame + relation_frames),
|
||||
label="candidate", subtype=None,
|
||||
previous_ids=previous_ids,
|
||||
incoming_ids=_active_other_ids(rows, frame, wearer_id=wearer_id),
|
||||
evidence=evidence, clips=segment_clips, rows=rows, wearer_id=wearer_id,
|
||||
context_frames=context_frames,
|
||||
reason="floor-level wearer entry has no surviving preceding non-wearer release opportunity",
|
||||
), ("WEARER_ONSET_CONTROL", frame, ()))
|
||||
reason="floor-level wearer onset without a surviving preceding release option",
|
||||
), ("WEARER_ONSET", frame, previous_ids))
|
||||
|
||||
# A2: a raw wearer offset is an INT candidate only at the end of a
|
||||
# floor-level wearer episode and only if a new non-wearer onset obeys
|
||||
@@ -604,7 +614,7 @@ def derive_protocol_candidates(utterances: Sequence[Utterance], clips: Sequence[
|
||||
anchor_type="wearer_offset", frame=frame,
|
||||
region_start=max(segment_start, frame - relation_frames),
|
||||
region_end=min(segment_end, frame + relation_frames),
|
||||
label="unsure" if is_int_candidate else "not_applicable", subtype=None,
|
||||
label="candidate" if is_int_candidate else "control", subtype=None,
|
||||
previous_ids=_active_other_ids(rows, frame - 1, wearer_id=wearer_id),
|
||||
incoming_ids=eligible_ids,
|
||||
evidence=evidence, clips=segment_clips, rows=rows, wearer_id=wearer_id,
|
||||
@@ -636,10 +646,10 @@ def derive_protocol_candidates(utterances: Sequence[Utterance], clips: Sequence[
|
||||
"original_timestamp_seconds": frame / SOURCE_FPS,
|
||||
}
|
||||
add(_candidate(
|
||||
session=session, task="eot_response", source="SAME_SPEAKER_PAUSE",
|
||||
rule_type="EOT_HARD_NEGATIVE_REGION", anchor_type="other_offset", frame=frame,
|
||||
session=session, task="wearer_entry", source="SAME_SPEAKER_PAUSE",
|
||||
rule_type="ENTRY_HARD_NEGATIVE_REGION", anchor_type="other_offset", frame=frame,
|
||||
region_start=frame, region_end=resumed.dense_start_frame,
|
||||
label="hard_negative", subtype="EOT_MIDTURN_PAUSE",
|
||||
label="hard_negative", subtype="ENTRY_OTHER_MIDTURN_PAUSE",
|
||||
previous_ids=(participant_id,), incoming_ids=(participant_id,), evidence=evidence,
|
||||
clips=segment_clips, rows=rows, wearer_id=wearer_id, context_frames=context_frames,
|
||||
reason="same participant resumes after a configurable pause while wearer stays silent",
|
||||
@@ -669,10 +679,10 @@ def derive_protocol_candidates(utterances: Sequence[Utterance], clips: Sequence[
|
||||
"wearer_active_during_handover": False, "original_timestamp_seconds": frame / SOURCE_FPS,
|
||||
}
|
||||
add(_candidate(
|
||||
session=session, task="eot_response", source="OTHER_TO_OTHER",
|
||||
rule_type="EOT_HARD_NEGATIVE_REGION", anchor_type="other_offset", frame=frame,
|
||||
session=session, task="wearer_entry", source="OTHER_TO_OTHER",
|
||||
rule_type="ENTRY_HARD_NEGATIVE_REGION", anchor_type="other_offset", frame=frame,
|
||||
region_start=frame, region_end=incoming.dense_start_frame,
|
||||
label="hard_negative", subtype="EOT_OTHER_TO_OTHER",
|
||||
label="hard_negative", subtype="ENTRY_OTHER_TO_OTHER",
|
||||
previous_ids=(first.participant_id,), incoming_ids=(incoming.participant_id,),
|
||||
evidence=evidence, clips=segment_clips, rows=rows, wearer_id=wearer_id,
|
||||
context_frames=context_frames,
|
||||
|
||||
Reference in New Issue
Block a user