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

View File

@@ -230,10 +230,6 @@ textarea { resize: vertical; line-height: 1.45; }
.ai-rationale p { margin: 0; color: #d7e6f5; font-size: .7rem; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ai-decision-card[data-verdict="POSITIVE"] { border-color: rgba(52,211,153,.48); box-shadow: inset 3px 0 0 var(--success); }
.ai-decision-card[data-verdict="POSITIVE"] .ai-verdict { color: #86efac; background: rgba(52,211,153,.13); }
.ai-decision-card[data-verdict="NOT_APPLICABLE"] { border-color: var(--line-strong); box-shadow: inset 3px 0 0 #71829a; }
.ai-decision-card[data-verdict="NOT_APPLICABLE"] .ai-verdict { color: #c4d0df; background: rgba(113,130,154,.14); }
.ai-decision-card[data-verdict="UNSURE"] { border-color: rgba(167,139,250,.48); box-shadow: inset 3px 0 0 #a78bfa; }
.ai-decision-card[data-verdict="UNSURE"] .ai-verdict { color: #c4b5fd; background: rgba(167,139,250,.13); }
.ai-decision-card[data-verdict="EXCLUDE"] { border-color: rgba(251,113,133,.48); box-shadow: inset 3px 0 0 var(--danger); }
.ai-decision-card[data-verdict="EXCLUDE"] .ai-verdict { color: #fda4af; background: rgba(251,113,133,.13); }
.event-position { color: var(--muted); font-size: 0.8rem; }

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";
}

View File

@@ -16,7 +16,7 @@
<section class="filters" aria-label="候选筛选">
<label><span>Session</span><select id="sessionFilter"><option value="all">全部</option></select></label>
<label><span>Split</span><select id="splitFilter"><option value="all">全部</option><option value="train">train</option><option value="test">test</option></select></label>
<label><span>任务</span><select id="familyFilter"><option value="all">全部</option><option value="eot">EOT / ENTRY</option><option value="int">INT / YIELD</option></select></label>
<label><span>任务</span><select id="familyFilter"><option value="all">全部</option><option value="entry">WEARER ENTRY</option><option value="int">INT / YIELD</option></select></label>
<label><span>审核状态</span><select id="statusFilter"><option value="unreviewed">未审核</option><option value="all">全部</option><option value="reviewed">已审核</option></select></label>
<label class="search-field"><span>搜索</span><input id="searchFilter" type="search" placeholder="事件来源、AI 标签、session" /></label>
<button id="clearFilters" class="button ghost" type="button">清除筛选</button>
@@ -77,18 +77,18 @@
<div class="choice-grid two action-grid">
<label class="accept-option"><input id="acceptRadio" type="radio" name="human_action" value="accept" /><span><em>AI 建议</em><b id="acceptTitle">接受 AI 结论</b><small id="acceptSummary">载入候选后显示具体标签</small></span></label>
<label><input type="radio" name="human_action" value="relabel" /><span><b>Relabel</b><small>展开并选择正确标签</small></span></label>
<label><input type="radio" name="human_action" value="exclude" /><span><b>Exclude</b><small>数据不可可靠评分</small></span></label>
<label><input type="radio" name="human_action" value="exclude" /><span><b>Exclude</b><small>非目标事件或不可可靠评分</small></span></label>
</div>
</fieldset>
<section id="relabelFields" class="conditional-fields" hidden>
<label><span>一级 verdict</span><select name="human_verdict"><option value="">请选择</option><option>POSITIVE</option><option>HARD_NEGATIVE</option><option>NOT_APPLICABLE</option><option>UNSURE</option></select></label>
<label id="eventTypeField" hidden><span>Positive event</span><select name="human_event_type"><option value="">请选择</option><option>EOT_RESPONSE</option><option>INT_YIELD</option></select></label>
<label id="opportunityField" hidden><span>EOT opportunity anchor</span><select id="opportunitySelect" name="selected_opportunity_uid"><option value="">请选择 preceding release</option></select></label>
<label id="subtypeField" hidden><span>Hard-negative subtype</span><select name="human_subtype"><option value="">请选择</option><option>EOT_MIDTURN_PAUSE</option><option>EOT_OTHER_TO_OTHER</option><option>EOT_WEARER_BACKCHANNEL</option><option>INT_OTHER_BACKCHANNEL</option><option>INT_NON_TAKEOVER_OVERLAP</option><option>INT_WEARER_HOLDS_FLOOR</option><option>OTHER_HARD_NEGATIVE</option></select></label>
<label><span>一级 verdict</span><select name="human_verdict"><option value="">请选择</option><option>POSITIVE</option><option>HARD_NEGATIVE</option></select></label>
<label id="eventTypeField" hidden><span>Positive event</span><select name="human_event_type"><option value="">请选择</option><option>WEARER_ENTRY</option><option>INT_YIELD</option></select></label>
<label id="opportunityField" hidden><span>前序 release(可选 metadata)</span><select id="opportunitySelect" name="selected_opportunity_uid"><option value="">无/不确定</option></select></label>
<label id="subtypeField" hidden><span>Hard-negative subtype</span><select name="human_subtype"><option value="">请选择</option><option>ENTRY_OTHER_MIDTURN_PAUSE</option><option>ENTRY_OTHER_TO_OTHER</option><option>ENTRY_WEARER_BACKCHANNEL</option><option>INT_OTHER_BACKCHANNEL</option><option>INT_NON_TAKEOVER_OVERLAP</option><option>INT_WEARER_HOLDS_FLOOR</option><option>OTHER_HARD_NEGATIVE</option></select></label>
</section>
<section id="excludeFields" class="conditional-fields" hidden><label><span>排除原因</span><select name="exclude_reason"><option value="">请选择</option><option>ANNOTATION_ERROR</option><option>RECORDING_BOUNDARY</option><option>MISSING_CONTEXT</option><option>NON_SPEECH_ARTIFACT</option><option>TIMESTAMP_CORRUPTION</option><option>UNRESOLVABLE</option></select></label></section>
<section id="excludeFields" class="conditional-fields" hidden><label><span>排除原因</span><select name="exclude_reason"><option value="">请选择</option><option value="OUT_OF_SCOPE_NORMAL_COMPLETION">OUT_OF_SCOPE_NORMAL_COMPLETION — wearer 正常说完</option><option value="OUT_OF_SCOPE_NON_EVENT">OUT_OF_SCOPE_NON_EVENT — 其他无需参评事件</option><option>ANNOTATION_ERROR</option><option>RECORDING_BOUNDARY</option><option>MISSING_CONTEXT</option><option>NON_SPEECH_ARTIFACT</option><option>TIMESTAMP_CORRUPTION</option><option>UNRESOLVABLE</option></select></label></section>
<details class="boundary-details"><summary>仅在原 annotation 明显错误时校正边界</summary><label><span>可选校正(原时间始终保留)</span><select name="boundary_correction_seconds"><option value="">不校正</option><option value="-0.2">−0.2 s</option><option value="0.2">+0.2 s</option></select></label></details>