"""派发器:按单位/月份汇总任务 → 生成派发 payload JSON → 写派发日志。 v1.1.0 dry-run 模式:payload 写 data/dispatch/{unit}_{month}.json, 但**不真正调用企业微信 API**。开发者后续接入: - 真实 API:调用 WECOM_API_BASE_URL + WECOM_API_TOKEN - 在 dispatch_log 写 status='sent' 而非 'dry_run' """ from __future__ import annotations import json import sqlite3 import uuid from datetime import datetime from pathlib import Path from db import repository as repo from util.date_util import parse_iso_date from util.logging_setup import get_skill_logger from util.runtime_paths import get_dispatch_dir logger = get_skill_logger() def build_payload( conn: sqlite3.Connection, unit_id: str, month: str = "", ) -> dict: """构建派发 payload:含单位、月份、任务清单、收件人、消息正文。""" rows = repo.list_open_alerts_by_unit_month(conn, unit_id, month) # 单位名(取第一条) unit_name = rows[0]["unit_name"] if rows else "(无)" items = [] for r in rows: items.append({ "contract_id": r["contract_id"], "teacher_name": r["teacher_name"], "alert_level": r["alert_level"], "days_to_expire": r["days_to_expire"], "contract_end": r["contract_end"], }) if month: title = f"{unit_name} {month} 合同到期人员清单及续聘建议" else: title = f"{unit_name} 待续聘合同清单" body_lines = [ title, "", f"共 {len(items)} 份合同需在到期前办理续聘手续。", "", ] for it in items: level_zh = {"yellow": "黄色", "orange": "橙色", "red": "红色"}.get( it["alert_level"], it["alert_level"] ) body_lines.append( f"- {it['teacher_name']}(合同至 {it['contract_end']},{level_zh}预警,距到期 {it['days_to_expire']} 天)" ) body_lines.append("") body_lines.append("请人事秘书尽快通知相关教师备齐续聘材料。") payload = { "schema_version": 1, "channel": "wecom", "dry_run": True, "to": { "unit_id": unit_id, "unit_name": unit_name, "role": "人事秘书", }, "title": title, "body": "\n".join(body_lines), "items": items, "generated_at": datetime.now().isoformat(timespec="seconds"), } return payload def dispatch( conn: sqlite3.Connection, unit_id: str, month: str = "", ) -> dict: """派发 dry-run:写 payload 文件 + 写 dispatch_log。""" payload = build_payload(conn, unit_id, month) items = payload["items"] task_ids = [f"T-{it['contract_id']}" for it in items] dispatch_dir = get_dispatch_dir() dispatch_dir.mkdir(parents=True, exist_ok=True) month_part = month or "all" payload_path = dispatch_dir / f"{unit_id}_{month_part}.json" payload_path.write_text( json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8", ) log_id = f"L-{uuid.uuid4().hex[:8]}" repo.insert_dispatch_log( conn, log_id, unit_id, month or "all", task_ids, str(payload_path), status="dry_run", ) conn.commit() logger.info( "dispatcher: unit=%s month=%s items=%d payload=%s log=%s", unit_id, month or "(all)", len(items), payload_path, log_id, ) return { "log_id": log_id, "unit_id": unit_id, "unit_name": payload["to"]["unit_name"], "month": month or "(all)", "item_count": len(items), "payload_path": str(payload_path), "status": "dry_run", }