fix: 缺省匹配年度收敛最新年度 + 批量坏文件跳过 + 匹配理由据实化 + 职称档位补全(P1+P2)
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
"""赛道匹配内核金标准用例(离线、确定性)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from _support import IsolatedDataRoot
|
||||
@@ -80,13 +83,29 @@ class TestScoring(MatchRuleTestBase):
|
||||
self.assertEqual(top["match_pct"], 100)
|
||||
self.assertEqual(top["tier"], "强烈推荐")
|
||||
|
||||
def test_t1_try_band(self) -> None:
|
||||
def test_coverage_only_below_try_band(self) -> None:
|
||||
result = self._match(_teacher(researchTags="钙钛矿,光伏", recentPapers=""))
|
||||
entry = next(e for e in result["top3"] if e["track"] == "面上项目·光伏材料")
|
||||
# coverage 2/3 → 0.7*0.667=0.4667 → 47% 不建议? + 无 priority → 47%;二维材料 coverage 1/3=0.333→23%
|
||||
# 仅覆盖率 2/3 → 0.7×0.6667=46.7% → 47%,低于 50% 可尝试档 → 不建议
|
||||
self.assertEqual(entry["match_pct"], 47)
|
||||
self.assertEqual(entry["tier"], "不建议")
|
||||
|
||||
def test_t1_try_band_with_supplement_hint(self) -> None:
|
||||
result = self._match(_teacher(researchTags="钙钛矿,光伏,界面调控"))
|
||||
entry = next(e for e in result["top3"] if e["track"] == "面上项目·光伏材料")
|
||||
# 覆盖率 2/3 → 0.7×0.6667=46.7% + 优先方向 1 命中 0.15 = 61.7% → 62%,落在可尝试档
|
||||
self.assertEqual(entry["match_pct"], 62)
|
||||
self.assertEqual(entry["tier"], "可尝试")
|
||||
self.assertTrue(any("建议补充" in r for r in entry["reasons"]))
|
||||
|
||||
def test_zero_match_reason_is_factual(self) -> None:
|
||||
result = self._match(_teacher(researchTags="量子计算", recentPapers="", projectHistory=""))
|
||||
entry = next(e for e in result["top3"] if e["track"] == "面上项目·光伏材料")
|
||||
reasons_text = "".join(entry["reasons"])
|
||||
self.assertEqual(entry["match_pct"], 0)
|
||||
self.assertIn("暂无重合", reasons_text)
|
||||
self.assertNotIn("部分相关性", reasons_text)
|
||||
|
||||
def test_t1_strong_boundary(self) -> None:
|
||||
# 恰好 80% 属强烈推荐
|
||||
result = self._match(_teacher(researchTags="钙钛矿,光伏,半导体", recentPapers=""))
|
||||
@@ -210,5 +229,77 @@ class TestBatchSkipIncomplete(MatchRuleTestBase):
|
||||
run_match([_teacher(researchTags="", recentPapers="")], _tracks(), "2027", skip_incomplete=False)
|
||||
|
||||
|
||||
class TestTrackYearScope(MatchRuleTestBase):
|
||||
"""缺省取赛道库最新年度(actions.json 契约);显式年度不受影响。"""
|
||||
|
||||
def _seed_two_years(self) -> None:
|
||||
from service.profile_service import upsert_track
|
||||
|
||||
upsert_track({"year": 2026, "track_name": "旧方向", "program_name": "面上项目", "track_tags": "钙钛矿,光伏"})
|
||||
upsert_track({"year": 2027, "track_name": "新方向", "program_name": "面上项目", "track_tags": "量子计算"})
|
||||
|
||||
def test_load_tracks_defaults_to_latest_year(self) -> None:
|
||||
from service.match_service import load_tracks
|
||||
|
||||
self._seed_two_years()
|
||||
self.assertEqual({t["year"] for t in load_tracks()}, {2027})
|
||||
|
||||
def test_load_tracks_explicit_year(self) -> None:
|
||||
from service.match_service import load_tracks
|
||||
|
||||
self._seed_two_years()
|
||||
self.assertEqual({t["year"] for t in load_tracks(year=2026)}, {2026})
|
||||
|
||||
def test_load_tracks_empty_library(self) -> None:
|
||||
from service.match_service import load_tracks
|
||||
|
||||
self.assertEqual(load_tracks(), [])
|
||||
|
||||
def test_kernel_default_matches_latest_year_only(self) -> None:
|
||||
from service.match_service import load_teachers, load_tracks, run_match
|
||||
from service.profile_service import upsert_teacher
|
||||
|
||||
self._seed_two_years()
|
||||
upsert_teacher({"teacher_name": "张教授", "title": "教授", "research_tags": "量子计算,量子信息"})
|
||||
payload = run_match(load_teachers(), load_tracks(), "2027")
|
||||
report = payload["reports"][0]
|
||||
all_tracks = [e["track"] for entries in report["tiers"].values() for e in entries]
|
||||
all_tracks += [e["track"] for e in report["top3"]]
|
||||
self.assertIn("面上项目·新方向", all_tracks)
|
||||
self.assertNotIn("面上项目·旧方向", all_tracks)
|
||||
|
||||
|
||||
class TestBatchInputCollection(MatchRuleTestBase):
|
||||
"""input/ 目录收集:坏文件跳过并记录原因,不中断整批。"""
|
||||
|
||||
def test_collect_input_teachers_skips_bad_files(self) -> None:
|
||||
from service.match_service import collect_input_teachers
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
with open(os.path.join(tmp, "a.json"), "w", encoding="utf-8") as f:
|
||||
json.dump([_teacher(), _teacher(teacherName="李教授")], f, ensure_ascii=False)
|
||||
with open(os.path.join(tmp, "single.json"), "w", encoding="utf-8") as f:
|
||||
json.dump({"teacherName": "王教授"}, f, ensure_ascii=False)
|
||||
with open(os.path.join(tmp, "bad.json"), "w", encoding="utf-8") as f:
|
||||
f.write("{not-json")
|
||||
with open(os.path.join(tmp, "scalar.json"), "w", encoding="utf-8") as f:
|
||||
json.dump("just-a-string", f, ensure_ascii=False)
|
||||
|
||||
teachers, skipped = collect_input_teachers(tmp)
|
||||
|
||||
names = [t.get("teacherName") for t in teachers]
|
||||
self.assertEqual(names, ["张教授", "李教授", "王教授"])
|
||||
self.assertEqual({e["file"] for e in skipped}, {"bad.json", "scalar.json"})
|
||||
self.assertTrue(all(e["reason"] for e in skipped))
|
||||
|
||||
def test_collect_input_teachers_empty_dir(self) -> None:
|
||||
from service.match_service import collect_input_teachers
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
teachers, skipped = collect_input_teachers(tmp)
|
||||
self.assertEqual(teachers, [])
|
||||
self.assertEqual(skipped, [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user