Revise wearer entry and yield annotation protocol

This commit is contained in:
Xin Wang
2026-09-15 01:25:39 +08:00
parent 026d89978f
commit 71dfd8808a
12 changed files with 478 additions and 248 deletions
+28 -20
View File
@@ -31,7 +31,7 @@ const elements = Object.fromEntries(
);
const familyNames = {
eot: "EOT-RESPONSE",
entry: "WEARER-ENTRY",
int: "INT-YIELD",
onset: "WEARER ONSET",
offset: "LEGACY EOT PROXY",
@@ -39,7 +39,7 @@ const familyNames = {
};
const reviewQuestions = {
eot: "Other 释放 floor 后,wearer 是否真正持续接话?",
entry: "这次 floor-level wearer onset 是否是真正进入或重新进入 floor?",
int: "Other 是否在 wearer 发言中接管,使 wearer 让出 floor?",
onset: "这次 wearer onset 是否代表新的 floor entry?",
offset: "这次 wearer offset 是否代表让出 floor?",
@@ -47,22 +47,22 @@ const reviewQuestions = {
};
const decisionMeanings = {
EOT_RESPONSE: "Other 释放 floor 后,wearer 持续接话。",
WEARER_ENTRY: "Wearer 真正进入或重新进入 floor;不要求此前 other 已释放。",
INT_YIELD: "Other 在 wearer 发言中接管,wearer 随后让出 floor。",
EOT_MIDTURN_PAUSE: "Other 只是短暂停顿后继续,没有释放 floor 给 wearer。",
EOT_OTHER_TO_OTHER: "这是 non-wearer 之间的换人,不是 wearer 接话。",
EOT_WEARER_BACKCHANNEL: "Wearer 只是反馈、笑声或短回应,没有接过 floor。",
INT_OTHER_BACKCHANNEL: "Other 只是反馈或笑声,没有接管 wearer 的 floor。",
INT_NON_TAKEOVER_OVERLAP: "虽然发生重叠,但 other 没有形成持续接管。",
INT_WEARER_HOLDS_FLOOR: "Wearer 在重叠后仍持有或继续自己的 floor。",
OTHER_HARD_NEGATIVE: "这是有效困难负例,但不属于更具体的负例类型。",
NOT_APPLICABLE: "这是真实活动边界,但不属于当前 EOT/INT 评测问题。",
UNSURE: "现有时间和转写证据不足,需要人工直接判断标签。",
EXCLUDE: "该区域存在数据质量问题,不应进入可靠评分。",
ENTRY_OTHER_MIDTURN_PAUSE: "同一个 other 只是在自己的 turn 内暂停后继续;wearer 没有进入 floor。",
ENTRY_OTHER_TO_OTHER: "一个 other 结束后由另一个 other 接话;wearer 没有进入 floor。",
ENTRY_WEARER_BACKCHANNEL: "Wearer 只有附和或反馈,没有形成真实 floor entry;other 保持或恢复 floor。",
INT_OTHER_BACKCHANNEL: "Other 只是反馈、笑声或附和,且 wearer 明确继续或恢复原 turn。若 wearer 正常说完,应 Exclude。",
INT_NON_TAKEOVER_OVERLAP: "Other 不是单纯 backchannel,但重叠仍未形成持续接管。",
INT_WEARER_HOLDS_FLOOR: "Other 不是单纯 backchannel,且 wearer 在重叠后明确继续或恢复原 turn。",
OTHER_HARD_NEGATIVE: "这是可解释的困难负例,但不符合任何命名子类型;不能用于替代 Exclude。",
EXCLUDE: "该区域是无需参评的非目标事件,或存在证据/数据问题,不进入评分。",
};
const currentAiVerdicts = new Set(["POSITIVE", "HARD_NEGATIVE", "EXCLUDE"]);
function suggestionView(suggestion) {
const verdict = suggestion.verdict || suggestion.protocol_disposition || "UNSURE";
const verdict = suggestion.verdict || suggestion.protocol_disposition || "—";
const label = suggestion.event_type || suggestion.subtype || suggestion.hard_negative_type || verdict;
const rawConfidence = suggestion.confidence ?? suggestion.final_confidence;
const numericConfidence = Number(rawConfidence);
@@ -266,7 +266,7 @@ function renderDetail(detail) {
elements.opportunitySelect.replaceChildren();
const emptyOption = document.createElement("option");
emptyOption.value = "";
emptyOption.textContent = "请选择 preceding release";
emptyOption.textContent = "无/不确定(可选)";
elements.opportunitySelect.append(emptyOption);
for (const option of opportunityOptions) {
const control = document.createElement("option");
@@ -288,12 +288,21 @@ function renderDetail(detail) {
elements.aiLabel.textContent = view.label;
elements.aiMeaning.textContent = view.meaning;
elements.aiRationale.textContent = view.rationale;
elements.acceptRadio.disabled = false;
elements.acceptTitle.textContent = `接受 ${view.verdict.replaceAll("_", " ")}`;
const familyEvent = detail.family === "entry" ? "WEARER_ENTRY" : detail.family === "int" ? "INT_YIELD" : null;
const familyPrefix = detail.family === "entry" ? "ENTRY_" : detail.family === "int" ? "INT_" : null;
const canAccept = currentAiVerdicts.has(view.verdict)
&& (view.verdict !== "POSITIVE" || !familyEvent || suggestion.event_type === familyEvent)
&& (view.verdict !== "HARD_NEGATIVE" || !familyPrefix || suggestion.subtype === "OTHER_HARD_NEGATIVE" || String(suggestion.subtype || "").startsWith(familyPrefix));
elements.acceptRadio.disabled = !canAccept;
elements.acceptTitle.textContent = canAccept
? `接受 ${view.verdict.replaceAll("_", " ")}`
: `旧标签 ${view.verdict.replaceAll("_", " ")}`;
const selected = suggestion.selected_opportunity_uid;
const selectedOption = opportunityOptions.find((option) => option.uid === selected);
const anchorText = selectedOption ? ` · trigger ${formatTime(selectedOption.anchor_seconds)} P${selectedOption.participant_id}` : "";
elements.acceptSummary.textContent = `${view.label}${anchorText} · ${view.meaning}`;
elements.acceptSummary.textContent = canAccept
? `${view.label}${anchorText} · ${view.meaning}`
: "该标签已移除,请改标为正例/硬负例,或选择 Exclude。";
} else {
elements.acceptRadio.disabled = true;
elements.acceptTitle.textContent = "没有 AI 结论";
@@ -537,7 +546,6 @@ function validateReview(review) {
if (review.human_action === "relabel" && !review.human_verdict) missing.push("一级 verdict");
if (review.human_action === "relabel" && review.human_verdict === "POSITIVE" && !review.human_event_type) missing.push("positive event");
const options = state.currentDetail?.candidate?.temporal_evidence?.opportunity_options || [];
if (review.human_action === "relabel" && review.human_verdict === "POSITIVE" && review.human_event_type === "EOT_RESPONSE" && options.length && !review.selected_opportunity_uid) missing.push("EOT opportunity anchor");
if (review.human_action === "relabel" && review.human_verdict === "HARD_NEGATIVE" && !review.human_subtype) missing.push("hard-negative subtype");
if (review.human_action === "exclude" && !review.exclude_reason) missing.push("排除原因");
if (review.human_action === "accept" && !state.currentDetail?.candidate?.ai_verdict && !state.currentDetail?.llm_suggestion?.verdict) missing.push("AI 预标注(尚未运行,需 Relabel)");
@@ -552,7 +560,7 @@ function updateConditionalFields() {
elements.relabelFields.hidden = action !== "relabel";
elements.excludeFields.hidden = action !== "exclude";
elements.eventTypeField.hidden = action !== "relabel" || verdict !== "POSITIVE";
elements.opportunityField.hidden = action !== "relabel" || verdict !== "POSITIVE" || eventType !== "EOT_RESPONSE" || !hasOpportunityOptions;
elements.opportunityField.hidden = action !== "relabel" || verdict !== "POSITIVE" || eventType !== "WEARER_ENTRY" || !hasOpportunityOptions;
elements.subtypeField.hidden = action !== "relabel" || verdict !== "HARD_NEGATIVE";
}