test(harness): 补 _ladder_units 直测 + 澄清 gate 观测表 unit_id 口径注释
M1:quadrant_pair / gate_evidence 的 question_id 列注释与 write_* docstring 更正为承载 unit_id(single=question_id、pair=pair_id),提示逐题明细在 predictions 表溯源、按 pair_id join 真实 question 表会 join 不上。 M2:给 _ladder_units 补直接单测——纯非 AR 恒等(unit 序==原题序、 unit_id==question_id)、混格交错保持信息阶梯序(按单元最早出现下标重排、 pair 整锁)、且与 build_units 的 single-first 默认序显式区分(防阶梯序被污染)。
This commit is contained in:
@@ -87,6 +87,8 @@ _HOLDOUT_COLS: dict[str, str] = {
|
|||||||
_QUADRANT_COLS: dict[str, str] = {
|
_QUADRANT_COLS: dict[str, str] = {
|
||||||
"epoch": "INTEGER",
|
"epoch": "INTEGER",
|
||||||
"step": "INTEGER",
|
"step": "INTEGER",
|
||||||
|
# question_id 列承载 unit_id(single=question_id,pair=pair_id);
|
||||||
|
# 逐题明细在 predictions 表溯源,按 pair_id join 真实 question 表会 join 不上。
|
||||||
"question_id": "TEXT",
|
"question_id": "TEXT",
|
||||||
"task_type": "TEXT",
|
"task_type": "TEXT",
|
||||||
"prev_correct": "INTEGER",
|
"prev_correct": "INTEGER",
|
||||||
@@ -98,6 +100,8 @@ _GATE_EVIDENCE_COLS: dict[str, str] = {
|
|||||||
"epoch": "INTEGER",
|
"epoch": "INTEGER",
|
||||||
"step": "INTEGER",
|
"step": "INTEGER",
|
||||||
"task_type": "TEXT",
|
"task_type": "TEXT",
|
||||||
|
# question_id 列承载 unit_id(single=question_id,pair=pair_id);
|
||||||
|
# 逐题明细在 predictions 表溯源,按 pair_id join 真实 question 表会 join 不上。
|
||||||
"question_id": "TEXT",
|
"question_id": "TEXT",
|
||||||
"block_idx": "INTEGER",
|
"block_idx": "INTEGER",
|
||||||
"baseline_correct": "INTEGER",
|
"baseline_correct": "INTEGER",
|
||||||
@@ -275,7 +279,7 @@ def write_quadrant_pairs(
|
|||||||
step: int,
|
step: int,
|
||||||
pairs: list[dict[str, Any]],
|
pairs: list[dict[str, Any]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""落 quadrant_pair 多行:fast gate 后逐题四象限(prev/curr 翻转 + category)落库。
|
"""落 quadrant_pair 多行:fast gate 后按 **unit** 四象限(prev/curr 翻转 + category)落库。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
db_path: SQLite 路径。
|
db_path: SQLite 路径。
|
||||||
@@ -283,7 +287,10 @@ def write_quadrant_pairs(
|
|||||||
epoch: 轮次(1-based)。
|
epoch: 轮次(1-based)。
|
||||||
step: epoch 内 step 序号(0-based)。
|
step: epoch 内 step 序号(0-based)。
|
||||||
pairs: 每条含 question_id/task_type/prev_correct/curr_correct/category;
|
pairs: 每条含 question_id/task_type/prev_correct/curr_correct/category;
|
||||||
prev_correct/curr_correct 为 bool,写库前转 0/1。
|
question_id 字段承载 **unit_id**(single=question_id,pair=pair_id,
|
||||||
|
与 gate e-process 同粒度)——逐题明细在 predictions 表溯源,按 pair_id
|
||||||
|
join 真实 question 表会 join 不上;prev_correct/curr_correct 为 bool,
|
||||||
|
写库前转 0/1。
|
||||||
|
|
||||||
关键实现:
|
关键实现:
|
||||||
用 insert_many 批量落库;pairs 为空时只建表不插入(fast gate 无翻转的极端情况)。
|
用 insert_many 批量落库;pairs 为空时只建表不插入(fast gate 无翻转的极端情况)。
|
||||||
@@ -326,16 +333,18 @@ def write_gate_evidence(
|
|||||||
step: int,
|
step: int,
|
||||||
rows: list[dict[str, Any]],
|
rows: list[dict[str, Any]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""落 gate_evidence 逐题行:CE-Gate 每次决策的可回放审计记录。
|
"""落 gate_evidence 单元行:CE-Gate 每次决策的可回放审计记录(unit 口径)。
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
db_path: SQLite 路径。
|
db_path: SQLite 路径。
|
||||||
run_id: 训练 run ID。
|
run_id: 训练 run ID。
|
||||||
epoch: 该 gate 所属的轮次(1-based)。
|
epoch: 该 gate 所属的轮次(1-based)。
|
||||||
step: epoch 内 step 序号(0-based)。
|
step: epoch 内 step 序号(0-based)。
|
||||||
rows: 每题一行,含 question_id/task_type/block_idx/baseline_correct/
|
rows: 每 **单元** 一行,含 question_id/task_type/block_idx/baseline_correct/
|
||||||
candidate_correct/e_value(该题所在块判定后的累计 e 值)/
|
candidate_correct/e_value(该单元所在块判定后的累计 e 值)/
|
||||||
stop_reason(仅最后一题携带最终 stop_reason,其余空串)。
|
stop_reason(仅最后一单元携带最终 stop_reason,其余空串)。
|
||||||
|
question_id 字段承载 **unit_id**(single=question_id,pair=pair_id)——
|
||||||
|
逐题明细在 predictions 表溯源,按 pair_id join 真实 question 表会 join 不上。
|
||||||
|
|
||||||
关键实现:
|
关键实现:
|
||||||
逐行 insert(非 insert_many),保证每行独立事务。
|
逐行 insert(非 insert_many),保证每行独立事务。
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import pytest
|
|||||||
from app.harness.gate_ladder import BaselineCache, skill_hash
|
from app.harness.gate_ladder import BaselineCache, skill_hash
|
||||||
from app.harness.inference import PREDICTIONS_SCHEMA, InferenceResult
|
from app.harness.inference import PREDICTIONS_SCHEMA, InferenceResult
|
||||||
from app.harness.log import HarnessLog
|
from app.harness.log import HarnessLog
|
||||||
from app.harness.validate import validate_skill_local
|
from app.harness.validate import _ladder_units, validate_skill_local
|
||||||
from core.evolution import GateParams
|
from core.evolution import GateParams
|
||||||
from core.types import GeneratedQuestion
|
from core.types import GeneratedQuestion
|
||||||
|
|
||||||
@@ -136,6 +136,45 @@ def _make_mock_run_inference(
|
|||||||
return mock_fn, call_log
|
return mock_fn, call_log
|
||||||
|
|
||||||
|
|
||||||
|
class TestLadderUnits:
|
||||||
|
"""_ladder_units:阶梯题序聚合为单元并保持信息阶梯序。"""
|
||||||
|
|
||||||
|
def test_pure_non_ar_is_identity(self) -> None:
|
||||||
|
"""纯非 AR 输入 → 单元序恒等(unit 序==原题序、unit_id==question_id)。"""
|
||||||
|
ladder = [_single("s0"), _single("s1"), _single("s2")]
|
||||||
|
units = _ladder_units(ladder)
|
||||||
|
assert [u.kind for u in units] == ["single", "single", "single"]
|
||||||
|
# unit_id 逐一等于原 question_id,且顺序与输入完全一致
|
||||||
|
assert [u.unit_id for u in units] == ["s0", "s1", "s2"]
|
||||||
|
assert [q.question_id for u in units for q in u.questions] == ["s0", "s1", "s2"]
|
||||||
|
|
||||||
|
def test_mixed_preserves_ladder_order(self) -> None:
|
||||||
|
"""混格交错输入 → 按单元最早出现下标重排,pair 整锁、不被 single-first 污染。"""
|
||||||
|
p1o, p1m = _pair("p1")
|
||||||
|
# 交错布置:pair 两成员分居 idx 1、3;single 分居 idx 0、2、4
|
||||||
|
ladder = [_single("s0"), p1o, _single("s1"), p1m, _single("s2")]
|
||||||
|
units = _ladder_units(ladder)
|
||||||
|
# 最早出现下标:s0=0, p1=min(1,3)=1, s1=2, s2=4 → 阶梯序 [s0,p1,s1,s2]
|
||||||
|
assert [u.unit_id for u in units] == ["s0", "p1", "s1", "s2"]
|
||||||
|
# pair 整锁为一个单元(含两成员),不被拆
|
||||||
|
p1_unit = next(u for u in units if u.unit_id == "p1")
|
||||||
|
assert p1_unit.kind == "pair"
|
||||||
|
assert {q.question_id for q in p1_unit.questions} == {"p1_o", "p1_m"}
|
||||||
|
|
||||||
|
def test_mixed_differs_from_build_units_default(self) -> None:
|
||||||
|
"""混格重排必须纠正 build_units 的 single-first 顺序(否则阶梯序被污染)。"""
|
||||||
|
from app.harness.question_units import build_units
|
||||||
|
|
||||||
|
p1o, p1m = _pair("p1")
|
||||||
|
ladder = [_single("s0"), p1o, _single("s1"), p1m, _single("s2")]
|
||||||
|
default_order = [u.unit_id for u in build_units(ladder)]
|
||||||
|
ladder_order = [u.unit_id for u in _ladder_units(ladder)]
|
||||||
|
# build_units 把 pair 排到 single 之后;_ladder_units 恢复信息阶梯序
|
||||||
|
assert default_order == ["s0", "s1", "s2", "p1"]
|
||||||
|
assert ladder_order == ["s0", "p1", "s1", "s2"]
|
||||||
|
assert ladder_order != default_order
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_gate_n_used_counts_units_not_questions(tmp_path: Path) -> None:
|
async def test_gate_n_used_counts_units_not_questions(tmp_path: Path) -> None:
|
||||||
"""混格阶梯(1 pair + 2 single)→ n_used=3 单元,非 4 题。"""
|
"""混格阶梯(1 pair + 2 single)→ n_used=3 单元,非 4 题。"""
|
||||||
|
|||||||
Reference in New Issue
Block a user