diff --git a/app/harness/split_selection.py b/app/harness/split_selection.py index c932e83..56d0cc6 100644 --- a/app/harness/split_selection.py +++ b/app/harness/split_selection.py @@ -7,6 +7,8 @@ build_video_records / select_split。 from __future__ import annotations +from dataclasses import dataclass + _EVOLUTION_TARGET = { "extraction_failure": "tool", "search_failure": "skill", @@ -45,3 +47,50 @@ def cell_of(task_type: str, error_type: str) -> tuple[str, str]: (task_type, error_type) 二元组,作为覆盖计数的格子键。 """ return (task_type, error_type) + + +@dataclass(frozen=True) +class SignalLabel: + """诊断信号分层标签(DiagnosisResult 的确定性投影)。 + + 字段: + tier: 信号层级,取值 T0 / T1 / T2 / uncertain(判据见 score_signal)。 + """ + + tier: str + + +def score_signal(*, cause_category: str | None, infra: bool, degraded: bool) -> SignalLabel: + """把诊断产物投影为信号分层 tier(不发明新分类,是确定性投影)。 + + 分层优先级顺序固定(用早返回表达,不用魔法权重): + 先判 INFRA,再判 degraded,然后 defect / lapse,最后兜底 uncertain。 + + 各层判据来源: + T0 — infra=True,即诊断 INFRA 排除(stop_reason ∈ {error, parse_error}), + 基础设施失败先于一切判定,排除出可训练主体。 + uncertain — degraded=True(judge 解析失败)或 cause_category 落不到 + defect/lapse 上(如为 None),信号不可信,排除出 T2。 + T2 — cause_category == "defect",可训练核心,进多样性覆盖与训练主体。 + T1 — cause_category == "lapse",低信号(含无解题),接受但不作训练主体。 + + 参数: + cause_category: 诊断的缺陷归因("defect" / "lapse" / None)。 + infra: 是否被 INFRA 护栏排除(基础设施失败)。 + degraded: judge 是否解析失败导致诊断降级。 + + 返回: + SignalLabel,其 tier 字段为上述四层之一。 + + 实现细节: + 关键字参数强制传入,防止 infra / degraded 两个 bool 位置混淆。 + """ + if infra: + return SignalLabel(tier="T0") + if degraded: + return SignalLabel(tier="uncertain") + if cause_category == "defect": + return SignalLabel(tier="T2") + if cause_category == "lapse": + return SignalLabel(tier="T1") + return SignalLabel(tier="uncertain") diff --git a/tests/unit/test_split_selection.py b/tests/unit/test_split_selection.py index 165bbb4..8f750ee 100644 --- a/tests/unit/test_split_selection.py +++ b/tests/unit/test_split_selection.py @@ -1,6 +1,6 @@ import pytest -from app.harness.split_selection import cell_of, evolution_target_of +from app.harness.split_selection import cell_of, evolution_target_of, score_signal def test_evolution_target_mapping(): @@ -17,3 +17,12 @@ def test_evolution_target_unknown_raises(): def test_cell_is_task_type_x_error_type(): assert cell_of("Counting Problem", "search_failure") == ("Counting Problem", "search_failure") + + +def test_tiers(): + assert score_signal(cause_category="defect", infra=False, degraded=False).tier == "T2" + assert score_signal(cause_category="lapse", infra=False, degraded=False).tier == "T1" + assert ( + score_signal(cause_category="defect", infra=True, degraded=False).tier == "T0" + ) # INFRA 先判 + assert score_signal(cause_category=None, infra=False, degraded=True).tier == "uncertain"