Revise wearer entry and yield annotation protocol
This commit is contained in:
+31
-29
@@ -34,10 +34,10 @@ 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 = 4
|
||||
REVIEW_SCHEMA_VERSION = 5
|
||||
MAX_REQUEST_BYTES = 128 * 1024
|
||||
|
||||
PROTOCOL_DISPOSITION_VALUES = {"positive", "hard_negative", "exclude", "uncertain"}
|
||||
PROTOCOL_DISPOSITION_VALUES = {"positive", "hard_negative", "exclude"}
|
||||
HARD_NEGATIVE_TYPE_VALUES = {
|
||||
"eot_midturn_pause",
|
||||
"eot_other_to_other",
|
||||
@@ -46,17 +46,13 @@ HARD_NEGATIVE_TYPE_VALUES = {
|
||||
"int_non_takeover_overlap",
|
||||
"int_retain_floor",
|
||||
"other",
|
||||
"not_applicable",
|
||||
"uncertain",
|
||||
}
|
||||
VALIDITY_VALUES = {"valid", "artifact", "uncertain"}
|
||||
VALIDITY_VALUES = {"valid", "artifact"}
|
||||
ONSET_FUNCTION_VALUES = {
|
||||
"floor_taking",
|
||||
"backchannel",
|
||||
"within_turn_resume",
|
||||
"nonverbal",
|
||||
"not_applicable",
|
||||
"uncertain",
|
||||
}
|
||||
CONTEXT_VALUES = {"addressed_wearer", "third_party", "group", "unknown"}
|
||||
OFFSET_OUTCOME_VALUES = {
|
||||
@@ -65,21 +61,20 @@ OFFSET_OUTCOME_VALUES = {
|
||||
"temporary_pause",
|
||||
"backchannel_end",
|
||||
"cutoff",
|
||||
"not_applicable",
|
||||
"uncertain",
|
||||
}
|
||||
CONFIDENCE_VALUES = {"high", "medium", "low"}
|
||||
HUMAN_ACTION_VALUES = {"accept", "relabel", "exclude", "second_review"}
|
||||
HUMAN_VERDICT_VALUES = {"POSITIVE", "HARD_NEGATIVE", "NOT_APPLICABLE", "EXCLUDE", "UNSURE"}
|
||||
HUMAN_EVENT_TYPE_VALUES = {"EOT_RESPONSE", "INT_YIELD"}
|
||||
HUMAN_ACTION_VALUES = {"accept", "relabel", "exclude"}
|
||||
HUMAN_VERDICT_VALUES = {"POSITIVE", "HARD_NEGATIVE", "EXCLUDE"}
|
||||
HUMAN_EVENT_TYPE_VALUES = {"WEARER_ENTRY", "INT_YIELD"}
|
||||
HUMAN_SUBTYPE_VALUES = {
|
||||
"EOT_MIDTURN_PAUSE", "EOT_OTHER_TO_OTHER", "EOT_WEARER_BACKCHANNEL",
|
||||
"ENTRY_OTHER_MIDTURN_PAUSE", "ENTRY_OTHER_TO_OTHER", "ENTRY_WEARER_BACKCHANNEL",
|
||||
"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", "UNRESOLVABLE",
|
||||
"TIMESTAMP_CORRUPTION", "OUT_OF_SCOPE_NORMAL_COMPLETION", "OUT_OF_SCOPE_NON_EVENT",
|
||||
"UNRESOLVABLE",
|
||||
}
|
||||
|
||||
|
||||
@@ -123,12 +118,12 @@ class Candidate:
|
||||
|
||||
@property
|
||||
def family(self) -> str:
|
||||
if self.payload.get("candidate_source"):
|
||||
return "eot" if self.task == "eot_response" else "int"
|
||||
if self.task == "eot_response":
|
||||
return "eot"
|
||||
if self.task == "int_yield":
|
||||
if self.task in {"wearer_entry", "wearer_entry_control", "eot_response", "eot_response_control"}:
|
||||
return "entry"
|
||||
if self.task in {"int_yield", "int_yield_control"}:
|
||||
return "int"
|
||||
if self.payload.get("candidate_source") == "WEARER_ONSET":
|
||||
return "entry"
|
||||
if "holder_uid" in self.payload or self.automatic_label.startswith("eot_"):
|
||||
return "offset"
|
||||
if self.payload.get("participant_role") == "wearer":
|
||||
@@ -454,7 +449,8 @@ class ReviewStore:
|
||||
normalized[key] = value
|
||||
|
||||
action = normalized["human_action"]
|
||||
candidate = self.candidates[candidate_id].payload
|
||||
candidate_record = self.candidates[candidate_id]
|
||||
candidate = candidate_record.payload
|
||||
if action == "accept":
|
||||
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")
|
||||
@@ -467,22 +463,28 @@ class ReviewStore:
|
||||
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:
|
||||
raise ValueError("POSITIVE requires EOT_RESPONSE or INT_YIELD")
|
||||
raise ValueError("POSITIVE requires WEARER_ENTRY or INT_YIELD")
|
||||
if verdict != "POSITIVE" and event_type is not None:
|
||||
raise ValueError("Only POSITIVE may have a human_event_type")
|
||||
if verdict == "HARD_NEGATIVE" and subtype not in HUMAN_SUBTYPE_VALUES:
|
||||
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")
|
||||
family = candidate_record.family
|
||||
if verdict == "POSITIVE" and family in {"entry", "int"}:
|
||||
required_event_type = "WEARER_ENTRY" if family == "entry" else "INT_YIELD"
|
||||
if event_type != required_event_type:
|
||||
raise ValueError(f"{family.upper()} candidate requires {required_event_type}")
|
||||
if verdict == "HARD_NEGATIVE" and family in {"entry", "int"}:
|
||||
prefix = family.upper() + "_"
|
||||
if subtype != "OTHER_HARD_NEGATIVE" and not str(subtype).startswith(prefix):
|
||||
raise ValueError(f"{family.upper()} candidate requires {prefix}* hard-negative subtype")
|
||||
evidence = candidate.get("temporal_evidence") or {}
|
||||
opportunity_options = evidence.get("opportunity_options") or []
|
||||
allowed_opportunity_uids = {
|
||||
@@ -490,11 +492,11 @@ class ReviewStore:
|
||||
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 is not None:
|
||||
if verdict != "POSITIVE" or event_type != "WEARER_ENTRY":
|
||||
raise ValueError("selected_opportunity_uid is only valid for WEARER_ENTRY")
|
||||
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")
|
||||
raise ValueError("WEARER_ENTRY may select only a supplied opportunity anchor")
|
||||
selected_opportunity = next(
|
||||
(
|
||||
option for option in opportunity_options
|
||||
@@ -521,7 +523,7 @@ class ReviewStore:
|
||||
if correction not in {-0.2, 0, 0.2, None}:
|
||||
raise ValueError("boundary_correction_seconds must be -0.2, 0, 0.2, or null")
|
||||
normalized.update(
|
||||
human_status="SECOND_REVIEW" if action == "second_review" else "REVIEWED",
|
||||
human_status="REVIEWED",
|
||||
human_verdict=verdict,
|
||||
human_event_type=event_type,
|
||||
human_subtype=subtype,
|
||||
|
||||
Reference in New Issue
Block a user