"""扫描器:扫全校合同 → 分级 → 写 alerts + 生成 renewal_tasks。 核心业务入口,cron 每日凌晨 02:00 调用。 """ from __future__ import annotations import sqlite3 from datetime import date from typing import Tuple from db import repository as repo from service.task_generator import generate_task_for_contract from util.constants import ALERT_LEVELS, DEFAULT_THRESHOLDS from util.date_util import classify_alert, days_to, parse_iso_date from util.logging_setup import get_skill_logger logger = get_skill_logger() def scan( conn: sqlite3.Connection, thresholds: Tuple[int, int, int] = DEFAULT_THRESHOLDS, today: date | None = None, scan_id: str | None = None, ) -> dict: """扫描全校合同,返回结构化结果。 thresholds = (yellow_max, orange_max, red_max),默认 (90, 60, 30)。 返回: { "scanned_count": , "yellow_count": , "orange_count": , "red_count": , "generated_task_count": , "scan_id": , "thresholds": [yellow_max, orange_max, red_max], } """ today = today or date.today() scan_id = scan_id or f"S{datetime_now_compact()}" contracts = repo.list_active_contracts(conn) counts = {f"{lvl}_count": 0 for lvl in ALERT_LEVELS} generated = 0 logger.info("scan: start scan_id=%s thresholds=%s today=%s contracts=%d", scan_id, thresholds, today.isoformat(), len(contracts)) with conn: for c in contracts: contract_end = parse_iso_date(c["contract_end"]) days = days_to(contract_end, today) level = classify_alert(days, thresholds) if level is None: continue # 写 alert(alert_id 包含 scan_id 避免同一合同被多次扫描时冲突) alert_id = f"A-{c['contract_id']}-{scan_id}" repo.insert_alert(conn, alert_id, c["contract_id"], level, days, scan_id) counts[f"{level}_count"] += 1 # 生成任务书 task_id = f"T-{c['contract_id']}-{scan_id}" task = generate_task_for_contract(conn, c, alert_id, task_id) if task: generated += 1 result = { "scanned_count": len(contracts), "yellow_count": counts["yellow_count"], "orange_count": counts["orange_count"], "red_count": counts["red_count"], "generated_task_count": generated, "scan_id": scan_id, "thresholds": list(thresholds), "today": today.isoformat(), } logger.info("scan: done %s", result) return result def datetime_now_compact() -> str: """生成紧凑时间字符串,用于 alert_id / task_id / scan_id。""" from datetime import datetime return datetime.now().strftime("%Y%m%d%H%M%S")