189 lines
7.0 KiB
Python
189 lines
7.0 KiB
Python
"""评审门槛库 / 核验案例 的维护与查询。"""
|
||
|
||
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,
|
||
}
|