Make EasyCom event review wearer anchor first
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
|
||||
This stage never changes EasyCom speech boundaries and never claims semantic
|
||||
ground truth. It groups short wearer resumptions into floor-level episodes,
|
||||
pairs EOT candidates with a preceding non-wearer release, requires a new
|
||||
starts EOT candidates from observed wearer entries and retrieves preceding
|
||||
non-wearer release options, 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
|
||||
thresholds are CLI parameters, and raw evidence is retained for DeepSeek
|
||||
@@ -27,8 +28,8 @@ from derive_easycom_interaction_events import (
|
||||
from easycom_manifest import ALL_SESSIONS, TEST_SESSIONS
|
||||
|
||||
|
||||
SCHEMA_VERSION = 3
|
||||
PROTOCOL_VERSION = "easycom-wearer-centric-eot-int-candidates-v5"
|
||||
SCHEMA_VERSION = 4
|
||||
PROTOCOL_VERSION = "easycom-wearer-anchor-first-eot-int-candidates-v6"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -422,70 +423,84 @@ 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: open an opportunity at a non-wearer release and pair it with the
|
||||
# subsequent floor-level wearer entry only if no competing other onset
|
||||
# or wearer activity intervenes. Long silence alone does not invalidate
|
||||
# the opportunity. Unpaired floor onsets are retained as controls.
|
||||
paired_floor_onsets: set[int] = set()
|
||||
for release in other_rows:
|
||||
opportunity_frame = release.dense_end_frame_exclusive
|
||||
onset_frame = next(
|
||||
(value for value in floor_onsets if value >= opportunity_frame), None
|
||||
)
|
||||
if onset_frame is None:
|
||||
continue
|
||||
wearer_silent = (
|
||||
opportunity_frame == onset_frame
|
||||
or _all_state(
|
||||
wearer,
|
||||
opportunity_frame - segment_start,
|
||||
onset_frame - segment_start,
|
||||
False,
|
||||
# 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.
|
||||
for onset_frame in floor_onsets:
|
||||
release_options: list[tuple[Utterance, int]] = []
|
||||
for release in other_rows:
|
||||
opportunity_frame = release.dense_end_frame_exclusive
|
||||
if opportunity_frame > onset_frame:
|
||||
continue
|
||||
wearer_silent = (
|
||||
opportunity_frame == onset_frame
|
||||
or _all_state(
|
||||
wearer,
|
||||
opportunity_frame - segment_start,
|
||||
onset_frame - segment_start,
|
||||
False,
|
||||
)
|
||||
)
|
||||
)
|
||||
competing_onsets = [
|
||||
row for row in other_rows
|
||||
if opportunity_frame <= row.dense_start_frame < onset_frame
|
||||
]
|
||||
other_already_holds = bool(
|
||||
_active_other_ids(rows, opportunity_frame, wearer_id=wearer_id)
|
||||
)
|
||||
if not wearer_silent or competing_onsets or other_already_holds:
|
||||
continue
|
||||
evidence = dict(onset_records[onset_frame]["evidence"])
|
||||
evidence.update(
|
||||
opportunity_anchor_type="other_offset",
|
||||
opportunity_anchor_frame=opportunity_frame,
|
||||
opportunity_anchor_seconds=opportunity_frame / SOURCE_FPS,
|
||||
opportunity_speaker_id=release.participant_id,
|
||||
opportunity_source_uid=release.uid,
|
||||
opportunity_source_transcription=release.transcription,
|
||||
opportunity_source_target_of_speech=list(release.target_of_speech),
|
||||
behavior_anchor_type="wearer_floor_onset",
|
||||
behavior_anchor_frame=onset_frame,
|
||||
behavior_anchor_seconds=onset_frame / SOURCE_FPS,
|
||||
response_delay_seconds=(onset_frame - opportunity_frame) / SOURCE_FPS,
|
||||
competing_other_onsets_before_behavior=[],
|
||||
eot_opportunity_paired=True,
|
||||
)
|
||||
add(_candidate(
|
||||
session=session, task="eot_response", source="WEARER_ONSET",
|
||||
rule_type="EOT_OPPORTUNITY_CANDIDATE", anchor_type="wearer_onset",
|
||||
frame=onset_frame, region_start=opportunity_frame,
|
||||
region_end=min(segment_end, onset_frame + relation_frames),
|
||||
label="unsure", subtype=None, previous_ids=(release.participant_id,),
|
||||
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="non-wearer release opportunity paired with a subsequent floor-level wearer entry",
|
||||
), ("WEARER_ONSET", onset_frame, (release.participant_id,)))
|
||||
paired_floor_onsets.add(onset_frame)
|
||||
competing_onset = any(
|
||||
opportunity_frame <= row.dense_start_frame < onset_frame
|
||||
for row in other_rows
|
||||
)
|
||||
other_already_holds = bool(
|
||||
_active_other_ids(rows, opportunity_frame, wearer_id=wearer_id)
|
||||
)
|
||||
if wearer_silent and not competing_onset and not other_already_holds:
|
||||
release_options.append((release, opportunity_frame))
|
||||
|
||||
for frame in floor_onsets:
|
||||
if frame in paired_floor_onsets:
|
||||
release_options.sort(
|
||||
key=lambda item: (item[1], item[0].participant_id, item[0].uid)
|
||||
)
|
||||
if release_options:
|
||||
_, latest_opportunity_frame = release_options[-1]
|
||||
earliest_opportunity_frame = release_options[0][1]
|
||||
option_rows = [{
|
||||
"uid": release.uid,
|
||||
"anchor_type": "other_offset",
|
||||
"anchor_frame": frame,
|
||||
"anchor_seconds": frame / SOURCE_FPS,
|
||||
"participant_id": release.participant_id,
|
||||
"transcription": release.transcription,
|
||||
"target_of_speech": list(release.target_of_speech),
|
||||
"response_delay_seconds": (onset_frame - frame) / SOURCE_FPS,
|
||||
} for release, frame in release_options]
|
||||
evidence = dict(onset_records[onset_frame]["evidence"])
|
||||
evidence.update(
|
||||
opportunity_options=option_rows,
|
||||
opportunity_anchor_type=None,
|
||||
opportunity_anchor_frame=None,
|
||||
opportunity_anchor_seconds=None,
|
||||
behavior_anchor_type="wearer_floor_onset",
|
||||
behavior_anchor_frame=onset_frame,
|
||||
behavior_anchor_seconds=onset_frame / SOURCE_FPS,
|
||||
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,
|
||||
)
|
||||
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",
|
||||
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,
|
||||
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",
|
||||
), ("WEARER_ONSET", onset_frame, previous_ids))
|
||||
continue
|
||||
|
||||
frame = onset_frame
|
||||
evidence = dict(onset_records[frame]["evidence"])
|
||||
evidence.update(
|
||||
opportunity_options=[],
|
||||
opportunity_anchor_type=None,
|
||||
opportunity_anchor_frame=None,
|
||||
opportunity_anchor_seconds=None,
|
||||
|
||||
Reference in New Issue
Block a user