"""GeneratedQuestion.sub_pattern 字段 + _to_generated_question 透传。""" from app.question_gen.generator_v2 import CandidateQuestion from app.question_gen.pipeline_v2 import _to_generated_question from core.types import GeneratedQuestion def _candidate() -> CandidateQuestion: return CandidateQuestion( question_id="v1_Action Recognition_0001", video_id="v1", task_type="Action Recognition", skill_target="M1_AR", question="厨师最终采用了哪种烹饪方式?", options=("A. 蒸", "B. 炒", "C. 煮", "D. 炸"), answer="A", source_nodes=("n1", "n2"), difficulty="hard", ) def test_generated_question_has_sub_pattern_default_none(): q = GeneratedQuestion( question_id="q1", video_id="v1", task_type="Action Recognition", question="?", options=("A. x",), answer="A", source_nodes=("n1",), difficulty="easy", ) assert q.sub_pattern is None def test_to_generated_question_threads_sub_pattern(): q = _to_generated_question( _candidate(), family="ACTION_RECOGNITION", sub_pattern="premature_evidence_anchoring", ) assert q.sub_pattern == "premature_evidence_anchoring" def test_to_generated_question_sub_pattern_defaults_none(): q = _to_generated_question(_candidate(), family="RETRIEVAL") assert q.sub_pattern is None def test_question_to_entry_includes_sub_pattern(): from tools.generate_questions import _question_to_entry q = GeneratedQuestion( question_id="v1_Action Recognition_0001", video_id="v1", task_type="Action Recognition", question="?", options=("A. 蒸", "B. 炒", "C. 煮", "D. 炸"), answer="A", source_nodes=("n1",), difficulty="hard", family="ACTION_RECOGNITION", skill_target="M1_AR", sub_pattern="temporal_reasoning_failure", ) entry = _question_to_entry(q) assert entry["sub_pattern"] == "temporal_reasoning_failure" assert entry["question_id"] == "v1_Action Recognition_0001" assert entry["options"] == ["A. 蒸", "B. 炒", "C. 煮", "D. 炸"] def test_append_to_json_writes_sub_pattern(tmp_path): from tools.generate_questions import _append_to_json q = GeneratedQuestion( question_id="v1_Action Recognition_0001", video_id="v1", task_type="Action Recognition", question="?", options=("A. 蒸", "B. 炒", "C. 煮", "D. 炸"), answer="A", source_nodes=("n1",), difficulty="hard", family="ACTION_RECOGNITION", skill_target="M1_AR", sub_pattern="temporal_reasoning_failure", ) _append_to_json(tmp_path, q) import json data = json.loads((tmp_path / "v1.json").read_text(encoding="utf-8")) assert data[0]["sub_pattern"] == "temporal_reasoning_failure" def test_load_benchmark_restores_sub_pattern(tmp_path): from app.question_gen.loader import load_benchmark import json entry = { "question_id": "v1_Action Recognition_0001", "video_id": "v1", "task_type": "Action Recognition", "question": "?", "options": ["A. 蒸", "B. 炒", "C. 煮", "D. 炸"], "answer": "A", "source_nodes": ["n1"], "difficulty": "hard", "family": "ACTION_RECOGNITION", "skill_target": "M1_AR", "sub_pattern": "temporal_reasoning_failure", } (tmp_path / "v1.json").write_text(json.dumps([entry]), encoding="utf-8") loaded = load_benchmark(tmp_path) assert loaded[0].sub_pattern == "temporal_reasoning_failure" def test_load_benchmark_sub_pattern_defaults_none_when_absent(tmp_path): from app.question_gen.loader import load_benchmark import json entry = { "question_id": "v1_Object Recognition_0001", "video_id": "v1", "task_type": "Object Recognition", "question": "?", "options": ["A. a", "B. b", "C. c", "D. d"], "answer": "A", "source_nodes": ["n1"], "difficulty": "medium", } (tmp_path / "v1.json").write_text(json.dumps([entry]), encoding="utf-8") loaded = load_benchmark(tmp_path) assert loaded[0].sub_pattern is None