"""T3.1 31 省溯源数据测试 覆盖: 1. 27个省份 JSON 存在 2. 顶层溯源字段完整 3. 分数段 + 推荐条目 schema 合规 4. confidence 在 [0,1] 5. Loader: list_supported_provinces 返回 27 6. Loader: list_provinces 报告 27/27 存在 7. Loader: load_metadata 返回含 8 个元数据字段 8. Loader: load_province 在 confidence<0.5 时发出 UserWarning 9. 反扎堆检测端到端:随机抽取一省,命中一校的频次与 platforms 不空 """ import os import sys import json import warnings sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from data.crowd_db.loader import CrowdDBLoader REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) DATA_DIR = os.path.join(REPO, "data", "crowd_db") _EXCLUDED_FILES = {"special_programs.json"} def _list_json_files(): return sorted( f for f in os.listdir(DATA_DIR) if f.endswith(".json") and f not in _EXCLUDED_FILES ) def test_31_province_files_exist(): """31 省 JSON 全部存在""" files = _list_json_files() assert len(files) == 31, f"expected 31 province JSONs, got {len(files)}" def test_top_level_provenance_fields(): """所有 27 个文件顶层必填字段齐全""" req = { "province", "last_updated", "data_year", "source", "source_type", "confidence", "score_ranges", } for fname in _list_json_files(): d = json.load(open(os.path.join(DATA_DIR, fname), encoding="utf-8")) miss = req - set(d.keys()) assert not miss, f"{fname} 缺字段: {miss}" def test_score_range_schema(): """score_ranges 每个元素含 range+recommendations 且 range 长度为 2""" for fname in _list_json_files(): d = json.load(open(os.path.join(DATA_DIR, fname), encoding="utf-8")) for sr in d.get("score_ranges", []): assert isinstance(sr, dict), f"{fname}: score_range 必须是 dict" assert ( "range" in sr and isinstance(sr["range"], list) and len(sr["range"]) == 2 ), f"{fname}: range 必须是长度为 2 的列表" assert "recommendations" in sr and isinstance( sr["recommendations"], list ), f"{fname}: recommendations 必须是列表" for rec in sr["recommendations"]: rk = {"name", "frequency", "platforms"} assert rk <= set(rec.keys()), ( f"{fname}: rec 缺字段 {rk - set(rec.keys())}" ) def test_confidence_in_unit_interval(): """confidence ∈ [0.0, 1.0]""" for fname in _list_json_files(): d = json.load(open(os.path.join(DATA_DIR, fname), encoding="utf-8")) c = d.get("confidence") assert isinstance(c, (int, float)), f"{fname}: confidence 类型错误" assert 0.0 <= c <= 1.0, f"{fname}: confidence {c} 越界" def test_loader_supported_count_31(): """loader.PROVINCE_FILE_MAP 必须覆盖 31 省份""" loader = CrowdDBLoader() supported = loader.list_supported_provinces() assert len(supported) == 31, f"expected 31 supported, got {len(supported)}" def test_loader_existing_count_31(): """list_provinces 报告 27/27 存在""" loader = CrowdDBLoader(warn_low_confidence=False) existing = [m for m in loader.list_provinces() if m.get("exists")] assert len(existing) == 31, f"expected 31 existing, got {len(existing)}" def test_loader_metadata_hunan(): """load_metadata('湖南') 返回扩展元数据字段且 record_count > 0""" loader = CrowdDBLoader(warn_low_confidence=False) meta = loader.load_metadata("湖南") assert meta is not None for k in ( "province", "last_updated", "data_year", "source", "source_url", "source_type", "confidence", "quality_note", "trusted_sources", "trusted_sources_count", "record_count", ): assert k in meta, f"meta 缺 {k}" assert meta["record_count"] > 0, f"湖南 rec count = {meta['record_count']}" assert meta["confidence"] >= 0.8, ( f"湖南 confidence 应 ≥ 0.8,实际 {meta['confidence']}" ) assert meta["trusted_sources_count"] >= 2 assert isinstance(meta["trusted_sources"], list) and meta["trusted_sources"] def test_loader_low_confidence_warning(monkeypatch): """confidence<0.5 的省份加载时发出 UserWarning。 31 省均已升级到 ≥0.66 confidence,无法通过真实数据触发 warning。 通过 monkeypatch _load_json_file 注入 0.45 的虚构数据来验证 warn_low_confidence 路径。 """ loader = CrowdDBLoader(warn_low_confidence=True) monkeypatch.setattr( loader, "_load_json_file", lambda path: { "province": "新疆", "confidence": 0.45, "data_year": 2025, "source_url": "https://example.com/", }, ) with warnings.catch_warnings(record=True) as caught: warnings.simplefilter("always") loader.load_province("新疆") user_warns = [w for w in caught if issubclass(w.category, UserWarning)] assert user_warns, "应至少发出 1 个 UserWarning" assert any("置信度" in str(w.message) for w in user_warns), ( f"UserWarning 应提及 置信度, got: {[str(w.message) for w in user_warns]}" ) def test_loader_end_to_end_match(): """端到端:用湖南数据 + 一条已知高校名,应当命中""" loader = CrowdDBLoader(warn_low_confidence=False) rec = loader.find_recommendation_by_school("湖南", "长沙理工大学") assert rec is not None, "长沙理工大学 应当在湖南 575 段命中" assert rec["frequency"] >= 1 assert isinstance(rec["platforms"], list) and len(rec["platforms"]) >= 1 def test_all_provinces_have_trusted_sources_and_non_repo_source_url(): """31 省必须补齐可信来源元数据,source_url 不能再用仓库自引用冒充来源。""" for fname in _list_json_files(): d = json.load(open(os.path.join(DATA_DIR, fname), encoding="utf-8")) source_url = d.get("source_url", "") assert isinstance(source_url, str) and source_url.startswith("https://"), ( f"{fname}: source_url 应为 https:// 开头的可信入口, got {source_url!r}" ) assert ( "github.com" not in source_url and "gaokao-volunteer-system/blob" not in source_url ), f"{fname}: source_url 不能是仓库自引用路径, got {source_url}" trusted_sources = d.get("trusted_sources") assert isinstance(trusted_sources, list) and len(trusted_sources) >= 2, ( f"{fname}: trusted_sources 应至少包含 2 个可信来源" ) assert any( (item.get("url") or "").startswith("https://") for item in trusted_sources if isinstance(item, dict) ), f"{fname}: trusted_sources 至少 1 个条目应带 https:// 官方入口" assert isinstance(d.get("quality_note"), str) and d.get("quality_note"), ( f"{fname}: 缺少 quality_note 可信度口径说明" )