feat: 职称评审核验助手首版 v1.0.1(verify-promotion-metric-default)
All checks were successful
技能自动化发布 / release (push) Successful in 7s

This commit is contained in:
chenfuqiang
2026-09-13 16:05:20 +08:00
commit 04026d0372
206 changed files with 29350 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
"""评审门槛库 / 核验案例 的维护与查询。"""
from __future__ import annotations
import json
from typing import Any, Optional
from db.connection import get_conn, init_db
from service.verify_service import _METRIC_ALIASES, _snake, norm_metrics
from util.logging_config import get_skill_logger
from util.timeutil import now_unix
_KNOWN_RANKS = ("副教授", "教授")
def _log():
try:
return get_skill_logger()
except RuntimeError:
return None
def upsert_threshold(fields: dict[str, Any]) -> dict[str, Any]:
"""按(级别+指标键)幂等写入门槛。"""
normalized = norm_metrics({str(k): v for k, v in fields.items()})
rank = str(normalized.get("rank") or fields.get("rank") or "").strip()
metric_key = str(normalized.get("metricKey") or fields.get("metric_key") or fields.get("metricKey") or "").strip()
metric_name = str(normalized.get("metricName") or fields.get("metric_name") or "").strip()
rank = rank if rank in _KNOWN_RANKS else (str(fields.get("rank") or "").strip() or rank)
if rank not in _KNOWN_RANKS:
raise ValueError(f"申报级别只接受 {'、'.join(_KNOWN_RANKS)}:{rank}")
if not metric_key:
raise ValueError("指标键(metric_key)不能为空")
min_value = fields.get("min_value", fields.get("minValue"))
try:
min_value = float(min_value)
except (TypeError, ValueError) as exc:
raise ValueError(f"最低要求必须为数字:{min_value}") from exc
if min_value < 0:
raise ValueError(f"最低要求必须非负:{min_value}")
metric_name = metric_name or str(_METRIC_ALIASES.get(metric_key, (metric_key,))[0])
values = {
"rank": rank,
"metric_key": metric_key,
"metric_name": metric_name,
"min_value": min_value,
"unit": str(fields.get("unit") or "").strip(),
"basis_doc": str(fields.get("basis_doc") or fields.get("basisDoc") or "").strip(),
"clause": str(fields.get("clause") or "").strip(),
}
init_db()
conn = get_conn()
try:
cur = conn.cursor()
cur.execute("SELECT id FROM threshold_rules WHERE rank = ? AND metric_key = ?", (rank, metric_key))
row = cur.fetchone()
if row:
threshold_id = int(row[0])
cur.execute(
"""
UPDATE threshold_rules
SET metric_name = ?, min_value = ?, unit = ?, basis_doc = ?, clause = ?, updated_at = ?
WHERE id = ?
""",
(values["metric_name"], values["min_value"], values["unit"], values["basis_doc"], values["clause"], now_unix(), threshold_id),
)
action = "updated"
else:
cur.execute(
"""
INSERT INTO threshold_rules
(rank, metric_key, metric_name, min_value, unit, basis_doc, clause, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(values["rank"], values["metric_key"], values["metric_name"], values["min_value"], values["unit"], values["basis_doc"], values["clause"], now_unix(), now_unix()),
)
threshold_id = int(cur.lastrowid)
action = "created"
conn.commit()
finally:
conn.close()
log = _log()
if log:
log.info("threshold_upserted rank=%s metric=%s action=%s stage=threshold", rank, metric_key, action)
return {"ok": True, "rank": rank, "metric_key": metric_key, "id": threshold_id, "action": action}
def import_thresholds(rows: list[dict[str, Any]]) -> dict[str, Any]:
if not isinstance(rows, list) or not rows:
raise ValueError("导入内容应为非空 JSON 数组(门槛)")
if len(rows) > 200:
raise ValueError(f"单次导入条数过多({len(rows)}),上限 200 条")
results = [upsert_threshold(row) for row in rows]
return {
"ok": True,
"count": len(results),
"created": sum(1 for r in results if r["action"] == "created"),
"updated": sum(1 for r in results if r["action"] == "updated"),
"items": results,
}
def list_thresholds(rank: str = "") -> list[dict[str, Any]]:
init_db()
conn = get_conn()
try:
cur = conn.cursor()
if rank:
cur.execute("SELECT * FROM threshold_rules WHERE rank = ? ORDER BY id", (rank,))
else:
cur.execute("SELECT * FROM threshold_rules ORDER BY rank, id")
columns = [d[0] for d in cur.description]
rows = [dict(zip(columns, row)) for row in cur.fetchall()]
finally:
conn.close()
return rows
def list_cases(limit: int = 20, verdict: str = "") -> list[dict[str, Any]]:
init_db()
limit = max(1, min(int(limit), 200))
conn = get_conn()
try:
cur = conn.cursor()
if verdict:
cur.execute(
"SELECT id, batch_no, teacher_name, department, apply_rank, verdict, report_file, created_at FROM verify_cases WHERE verdict = ? ORDER BY id DESC LIMIT ?",
(verdict, limit),
)
else:
cur.execute(
"SELECT id, batch_no, teacher_name, department, apply_rank, verdict, report_file, created_at FROM verify_cases ORDER BY id DESC LIMIT ?",
(limit,),
)
columns = [d[0] for d in cur.description]
rows = [dict(zip(columns, row)) for row in cur.fetchall()]
finally:
conn.close()
return rows
def get_case(case_id: int) -> dict[str, Any]:
init_db()
conn = get_conn()
try:
cur = conn.cursor()
cur.execute("SELECT * FROM verify_cases WHERE id = ?", (case_id,))
row = cur.fetchone()
if not row:
raise ValueError(f"核验案例不存在:{case_id}")
columns = [d[0] for d in cur.description]
case = dict(zip(columns, row))
finally:
conn.close()
for key in ("matrix_json", "metrics_json"):
try:
case[key[:-5]] = json.loads(case.get(key) or "[]")
except (TypeError, ValueError):
case[key[:-5]] = [] if key == "matrix_json" else {}
return case
def stats() -> dict[str, Any]:
init_db()
conn = get_conn()
try:
cur = conn.cursor()
cur.execute("SELECT verdict, COUNT(*) FROM verify_cases GROUP BY verdict")
by_verdict = {str(r[0]): int(r[1]) for r in cur.fetchall()}
cur.execute("SELECT COUNT(*) FROM threshold_rules")
threshold_total = int(cur.fetchone()[0])
cur.execute("SELECT COUNT(*) FROM verify_cases")
case_total = int(cur.fetchone()[0])
cur.execute(
"SELECT teacher_name, department, apply_rank FROM verify_cases WHERE verdict = '硬伤' ORDER BY id DESC LIMIT 5"
)
red_cases = [{"teacher_name": r[0], "department": r[1], "apply_rank": r[2]} for r in cur.fetchall()]
finally:
conn.close()
return {
"ok": True,
"case_total": case_total,
"by_verdict": by_verdict,
"threshold_total": threshold_total,
"red_cases": red_cases,
}