Make EasyCom event review wearer anchor first

This commit is contained in:
Xin Wang
2026-09-14 23:30:31 +08:00
parent 06b5e6c385
commit 65347dd75d
12 changed files with 336 additions and 107 deletions
+77 -62
View File
@@ -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,