"""评审门槛库 / 核验案例 的维护与查询。""" from __future__ import annotations import json from typing import Any, Optional from db.connection import get_conn, init_db from db.seed_data import KNOWN_RANKS as _KNOWN_RANKS from service.verify_service import _METRIC_ALIASES, _snake, norm_metrics from util.logging_config import get_skill_logger from util.timeutil import now_unix 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, }