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
+46 -2
View File
@@ -34,7 +34,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 = 3
REVIEW_SCHEMA_VERSION = 4
MAX_REQUEST_BYTES = 128 * 1024
PROTOCOL_DISPOSITION_VALUES = {"positive", "hard_negative", "exclude", "uncertain"}
@@ -459,14 +459,20 @@ class ReviewStore:
verdict = candidate.get("ai_verdict") or (candidate.get("llm_suggestion") or {}).get("verdict")
event_type = candidate.get("ai_event_type") or (candidate.get("llm_suggestion") or {}).get("event_type")
subtype = candidate.get("ai_subtype") or (candidate.get("llm_suggestion") or {}).get("subtype")
selected_opportunity_uid = candidate.get("ai_selected_opportunity_uid") or (
candidate.get("llm_suggestion") or {}
).get("selected_opportunity_uid")
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
elif action == "second_review":
verdict, event_type, subtype = "UNSURE", None, None
selected_opportunity_uid = None
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")
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:
@@ -477,6 +483,35 @@ class ReviewStore:
raise ValueError("HARD_NEGATIVE requires a valid human_subtype")
if verdict != "HARD_NEGATIVE" and subtype is not None:
raise ValueError("Only HARD_NEGATIVE may have a human_subtype")
evidence = candidate.get("temporal_evidence") or {}
opportunity_options = evidence.get("opportunity_options") or []
allowed_opportunity_uids = {
str(option["uid"])
for option in opportunity_options
if isinstance(option, dict) and option.get("uid") is not None
}
if verdict == "POSITIVE" and event_type == "EOT_RESPONSE" and allowed_opportunity_uids:
if selected_opportunity_uid not in allowed_opportunity_uids:
raise ValueError("EOT_RESPONSE requires one supplied opportunity anchor")
elif selected_opportunity_uid is not None:
raise ValueError("selected_opportunity_uid is only valid for EOT_RESPONSE")
selected_opportunity = next(
(
option for option in opportunity_options
if isinstance(option, dict) and str(option.get("uid")) == selected_opportunity_uid
),
None,
)
selected_opportunity_seconds = (
None if selected_opportunity is None else selected_opportunity.get("anchor_seconds")
)
behavior_anchor_seconds = evidence.get(
"behavior_anchor_seconds",
candidate.get(
"anchor_dense_seconds",
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")
@@ -490,6 +525,13 @@ class ReviewStore:
human_verdict=verdict,
human_event_type=event_type,
human_subtype=subtype,
selected_opportunity_uid=selected_opportunity_uid,
selected_opportunity_seconds=selected_opportunity_seconds,
valid_prediction_interval_seconds=(
None
if selected_opportunity_seconds is None
else [selected_opportunity_seconds, behavior_anchor_seconds]
),
exclude_reason=exclude_reason,
relabeled=action == "relabel",
boundary_correction_seconds=correction,
@@ -607,7 +649,9 @@ class ReviewStore:
"automatic_hard_negative_type",
"ai_verdict", "ai_event_type", "ai_subtype",
"human_action", "human_status", "human_verdict", "human_event_type", "human_subtype",
"exclude_reason", "relabeled", "boundary_correction_seconds", "original_event_time",
"selected_opportunity_uid", "selected_opportunity_seconds",
"valid_prediction_interval_seconds", "exclude_reason", "relabeled",
"boundary_correction_seconds", "original_event_time",
"confidence",
"reviewer",
"notes",