Delegate wearer boundary semantics to review

This commit is contained in:
Xin Wang
2026-09-15 02:12:18 +08:00
parent 71dfd8808a
commit 3a127a3f9f
12 changed files with 596 additions and 404 deletions
+23 -12
View File
@@ -27,6 +27,9 @@ from threading import Lock
from typing import Any, Iterable
from urllib.parse import parse_qs, urlparse
from easycom_event_taxonomy import (
EXCLUDE_REASONS, LEGACY_EXCLUDE_REASONS, exclude_reason_group,
)
from prepare_easycom_eot_int_review import requires_semantic_adjudication
@@ -34,7 +37,7 @@ SOURCE_FPS = 20.0
STATIC_ROOT = Path(__file__).resolve().parent / "floor_event_review_web"
VIDEO_ROOT_PARTS = ("Main", "Video_Compressed")
TRANSCRIPT_ROOT_PARTS = ("Main", "Speech_Transcriptions")
REVIEW_SCHEMA_VERSION = 5
REVIEW_SCHEMA_VERSION = 6
MAX_REQUEST_BYTES = 128 * 1024
PROTOCOL_DISPOSITION_VALUES = {"positive", "hard_negative", "exclude"}
@@ -71,11 +74,7 @@ HUMAN_SUBTYPE_VALUES = {
"INT_OTHER_BACKCHANNEL", "INT_NON_TAKEOVER_OVERLAP", "INT_WEARER_HOLDS_FLOOR",
"OTHER_HARD_NEGATIVE",
}
EXCLUDE_REASON_VALUES = {
"ANNOTATION_ERROR", "RECORDING_BOUNDARY", "MISSING_CONTEXT", "NON_SPEECH_ARTIFACT",
"TIMESTAMP_CORRUPTION", "OUT_OF_SCOPE_NORMAL_COMPLETION", "OUT_OF_SCOPE_NON_EVENT",
"UNRESOLVABLE",
}
EXCLUDE_REASON_VALUES = EXCLUDE_REASONS | LEGACY_EXCLUDE_REASONS
@dataclass(frozen=True)
@@ -341,6 +340,8 @@ class CandidateRepository:
"ai_verdict": row.get("ai_verdict"),
"ai_event_type": row.get("ai_event_type"),
"ai_subtype": row.get("ai_subtype"),
"ai_exclude_reason": row.get("ai_exclude_reason"),
"ai_exclude_reason_group": row.get("ai_exclude_reason_group"),
"participant_role": row.get("participant_role") or row.get("holder_role"),
"event_time_seconds": event_time,
"region_start_seconds": row.get("region_start_seconds", event_time),
@@ -458,14 +459,19 @@ class ReviewStore:
selected_opportunity_uid = candidate.get("ai_selected_opportunity_uid") or (
candidate.get("llm_suggestion") or {}
).get("selected_opportunity_uid")
exclude_reason = candidate.get("ai_exclude_reason") or (
candidate.get("llm_suggestion") or {}
).get("exclude_reason")
if verdict not in HUMAN_VERDICT_VALUES:
raise ValueError("Accept requires an AI pre-label; use Relabel when no AI label is available")
elif action == "exclude":
verdict, event_type, subtype = "EXCLUDE", None, None
selected_opportunity_uid = None
exclude_reason = payload.get("exclude_reason")
else:
verdict, event_type, subtype = payload.get("human_verdict"), payload.get("human_event_type"), payload.get("human_subtype")
selected_opportunity_uid = payload.get("selected_opportunity_uid")
exclude_reason = payload.get("exclude_reason")
if verdict not in HUMAN_VERDICT_VALUES:
raise ValueError(f"Invalid human_verdict: {verdict!r}")
if verdict == "POSITIVE" and event_type not in HUMAN_EVENT_TYPE_VALUES:
@@ -514,11 +520,12 @@ class ReviewStore:
candidate.get("event_time", candidate.get("dense_start_seconds")),
),
)
exclude_reason = payload.get("exclude_reason")
if action == "exclude" and exclude_reason not in EXCLUDE_REASON_VALUES:
raise ValueError("Exclude requires an exclude_reason")
if action != "exclude" and exclude_reason is not None:
raise ValueError("exclude_reason is only valid for Exclude")
if verdict == "EXCLUDE" and exclude_reason not in EXCLUDE_REASON_VALUES:
raise ValueError(
"EXCLUDE requires an exclude_reason; legacy AI exclusions must be manually categorized"
)
if verdict != "EXCLUDE" and exclude_reason is not None:
raise ValueError("exclude_reason is only valid for EXCLUDE")
correction = payload.get("boundary_correction_seconds")
if correction not in {-0.2, 0, 0.2, None}:
raise ValueError("boundary_correction_seconds must be -0.2, 0, 0.2, or null")
@@ -535,6 +542,7 @@ class ReviewStore:
else [selected_opportunity_seconds, behavior_anchor_seconds]
),
exclude_reason=exclude_reason,
exclude_reason_group=exclude_reason_group(exclude_reason),
relabeled=action == "relabel",
boundary_correction_seconds=correction,
original_event_time=candidate.get("event_time", candidate.get("anchor_dense_seconds")),
@@ -597,6 +605,8 @@ class ReviewStore:
"ai_verdict": candidate.payload.get("ai_verdict"),
"ai_event_type": candidate.payload.get("ai_event_type"),
"ai_subtype": candidate.payload.get("ai_subtype"),
"ai_exclude_reason": candidate.payload.get("ai_exclude_reason"),
"ai_exclude_reason_group": candidate.payload.get("ai_exclude_reason_group"),
"review": normalized,
"updated_at": datetime.now(timezone.utc).isoformat(),
"candidate": candidate.payload,
@@ -650,9 +660,10 @@ class ReviewStore:
"automatic_protocol_label",
"automatic_hard_negative_type",
"ai_verdict", "ai_event_type", "ai_subtype",
"ai_exclude_reason", "ai_exclude_reason_group",
"human_action", "human_status", "human_verdict", "human_event_type", "human_subtype",
"selected_opportunity_uid", "selected_opportunity_seconds",
"valid_prediction_interval_seconds", "exclude_reason", "relabeled",
"valid_prediction_interval_seconds", "exclude_reason", "exclude_reason_group", "relabeled",
"boundary_correction_seconds", "original_event_time",
"confidence",
"reviewer",