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:
188
scripts/service/threshold_service.py
Normal file
188
scripts/service/threshold_service.py
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user