feat: 职称评审核验助手首版 v1.0.1(verify-promotion-metric-default)
All checks were successful
技能自动化发布 / release (push) Successful in 7s
All checks were successful
技能自动化发布 / release (push) Successful in 7s
This commit is contained in:
156
tests/test_verify_rules.py
Normal file
156
tests/test_verify_rules.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""职称硬指标核验内核金标准用例(离线、确定性)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from _support import IsolatedDataRoot
|
||||
|
||||
|
||||
def _metrics(rank="副教授", **overrides) -> dict:
|
||||
base = {
|
||||
"teacherName": "张老师",
|
||||
"department": "化材学院",
|
||||
"applyRank": rank,
|
||||
"presidingProvincial": 1,
|
||||
"corePapers": 4,
|
||||
"teachingHours": 180,
|
||||
"currentTitleYears": 6,
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
class VerifyRuleTestBase(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._root = IsolatedDataRoot()
|
||||
self._root.__enter__()
|
||||
from db.connection import init_db
|
||||
|
||||
init_db()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._root.__exit__(None, None, None)
|
||||
|
||||
def _verify(self, metrics=None, rank="副教授"):
|
||||
from service.verify_service import load_thresholds, verify_metrics
|
||||
|
||||
return verify_metrics(metrics if metrics is not None else _metrics(rank=rank), load_thresholds(rank))
|
||||
|
||||
|
||||
class TestMatrixColors(VerifyRuleTestBase):
|
||||
def test_all_safe(self) -> None:
|
||||
result = self._verify(_metrics(presidingProvincial=2, corePapers=5, teachingHours=200, currentTitleYears=8))
|
||||
self.assertEqual(result["verdict"], "安全")
|
||||
colors = {m["metric_key"]: m["color"] for m in result["matrix"]}
|
||||
self.assertTrue(all(c == "green" for c in colors.values()))
|
||||
|
||||
def test_red_hard_fail(self) -> None:
|
||||
result = self._verify(_metrics(corePapers=1))
|
||||
self.assertEqual(result["verdict"], "硬伤")
|
||||
red = [m for m in result["matrix"] if m["color"] == "red"]
|
||||
self.assertEqual(red[0]["metric_key"], "core_papers")
|
||||
self.assertIn("依据", red[0]["advice"])
|
||||
|
||||
def test_yellow_border_below_10_percent(self) -> None:
|
||||
result = self._verify(_metrics(presidingProvincial=1, corePapers=3, teachingHours=165, currentTitleYears=6))
|
||||
self.assertEqual(result["verdict"], "边缘")
|
||||
colors = {m["metric_key"]: m["color"] for m in result["matrix"]}
|
||||
self.assertEqual(colors["teaching_hours"], "yellow") # 165/160 = 3.1% 余量
|
||||
self.assertEqual(colors["core_papers"], "yellow") # 3/3 = 0 余量
|
||||
|
||||
def test_green_boundary_at_exactly_10_percent(self) -> None:
|
||||
# 恰好 10% 余量(176 = 160×1.10)属绿
|
||||
result = self._verify(_metrics(teachingHours=176))
|
||||
colors = {m["metric_key"]: m["color"] for m in result["matrix"]}
|
||||
self.assertEqual(colors["teaching_hours"], "green")
|
||||
|
||||
def test_r4_missing_metric_pending(self) -> None:
|
||||
result = self._verify(_metrics(corePapers=None))
|
||||
self.assertEqual(result["verdict"], "边缘")
|
||||
colors = {m["metric_key"]: m["color"] for m in result["matrix"]}
|
||||
self.assertEqual(colors["core_papers"], "gray")
|
||||
pending = next(m for m in result["matrix"] if m["metric_key"] == "core_papers")
|
||||
self.assertIn("R4", pending["advice"])
|
||||
|
||||
def test_v1_unconfigured_metric(self) -> None:
|
||||
result = self._verify(_metrics(someExtraAward=3))
|
||||
v1 = [m for m in result["matrix"] if m["metric_key"] == "someExtraAward"]
|
||||
self.assertTrue(v1)
|
||||
self.assertIn("V1", v1[0]["advice"])
|
||||
|
||||
|
||||
class TestVerifyKernel(VerifyRuleTestBase):
|
||||
def _run(self, cases):
|
||||
from service.verify_service import run_verify
|
||||
|
||||
return run_verify(cases)
|
||||
|
||||
def test_run_verify_writes_reports_and_brief(self) -> None:
|
||||
result = self._run([_metrics(presidingProvincial=2), _metrics(teacherName="李老师", corePapers=1)])
|
||||
self.assertEqual(result["count"], 2)
|
||||
self.assertEqual(result["verdict_counts"], {"安全": 1, "硬伤": 1})
|
||||
self.assertTrue(result["brief_file"].endswith(".html"))
|
||||
for report in result["reports"]:
|
||||
self.assertTrue(report["report_file"].endswith(".html"))
|
||||
with open(report["report_file"], encoding="utf-8") as f:
|
||||
html_text = f.read()
|
||||
self.assertIn("职称申报核验报告", html_text)
|
||||
self.assertIn("不构成评审结论", html_text)
|
||||
|
||||
def test_empty_cases_raises(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
self._run([])
|
||||
|
||||
def test_professor_rank_thresholds(self) -> None:
|
||||
result = self._run([_metrics(rank="教授", presidingProvincial=2, corePapers=7, monographs=2)])
|
||||
self.assertEqual(result["reports"][0]["verdict"], "边缘") # core 7/6 余量 16.7% 绿、presiding 2/2 黄
|
||||
|
||||
def test_set_threshold_changes_verdict(self) -> None:
|
||||
from service.threshold_service import upsert_threshold
|
||||
|
||||
upsert_threshold({"rank": "副教授", "metric_key": "core_papers", "metric_name": "核心期刊论文", "min_value": 5})
|
||||
result = self._verify(_metrics(corePapers=4))
|
||||
self.assertEqual(result["verdict"], "硬伤")
|
||||
|
||||
|
||||
class TestThresholdService(VerifyRuleTestBase):
|
||||
def test_upsert_threshold_create_update(self) -> None:
|
||||
from service.threshold_service import list_thresholds, upsert_threshold
|
||||
|
||||
payload = upsert_threshold({"rank": "副教授", "metric_key": "awards", "metric_name": "教学/科研获奖", "min_value": 1})
|
||||
self.assertEqual(payload["action"], "created")
|
||||
payload = upsert_threshold({"rank": "副教授", "metric_key": "awards", "min_value": 2})
|
||||
self.assertEqual(payload["action"], "updated")
|
||||
rows = [t for t in list_thresholds("副教授") if t["metric_key"] == "awards"]
|
||||
self.assertEqual(rows[0]["min_value"], 2.0)
|
||||
|
||||
def test_upsert_threshold_validations(self) -> None:
|
||||
from service.threshold_service import upsert_threshold
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
upsert_threshold({"rank": "院士", "metric_key": "awards", "min_value": 1})
|
||||
with self.assertRaises(ValueError):
|
||||
upsert_threshold({"rank": "副教授", "metric_key": "", "min_value": 1})
|
||||
with self.assertRaises(ValueError):
|
||||
upsert_threshold({"rank": "副教授", "metric_key": "awards", "min_value": -1})
|
||||
|
||||
def test_seeded_thresholds(self) -> None:
|
||||
from service.threshold_service import list_thresholds
|
||||
|
||||
self.assertGreaterEqual(len(list_thresholds("副教授")), 4)
|
||||
self.assertGreaterEqual(len(list_thresholds("教授")), 4)
|
||||
|
||||
def test_stats(self) -> None:
|
||||
from service.threshold_service import stats
|
||||
from service.verify_service import run_verify
|
||||
|
||||
run_verify([_metrics(presidingProvincial=2), _metrics(teacherName="李老师", corePapers=1)])
|
||||
payload = stats()
|
||||
self.assertEqual(payload["case_total"], 2)
|
||||
self.assertEqual(payload["by_verdict"].get("硬伤"), 1)
|
||||
self.assertTrue(payload["red_cases"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user