Files
alert-contract-expiring-hr/scripts/util/date_util.py
taojianmin 06c053d7d7 v1.1.0: 实现核心业务逻辑(扫描/任务书/派发/状态机)
新增(scripts/ 分层完整实现):
- scripts/util/{constants,date_util,runtime_paths,logging_setup}.py
- scripts/db/{connection,seed_data,repository}.py(SQLite + 5 张表 + 50 份样例合同)
- scripts/service/{scanner,task_generator,dispatcher,html_renderer}.py
- scripts/cli/app.py(argparse 子命令)
- scripts/templates/task.html.j2(Jinja2 渲染,无 jinja2 时降级到 str.replace)
- examples/{contracts_sample,performance_sample}.csv(本地样例数据)

修改:
- scripts/main.py:argparse + jiangchang_skill_core 降级
- SKILL.md:version 1.0.1 → 1.1.0
- CHANGELOG.md:加 v1.1.0 条目
- .env.example:加 DB_PATH / HTML_OUTPUT_DIR / JIANGCHANG_DATA_DIR

本地验证(9 个 CLI 命令全部 exit code = 0):
- health / version / config-path / init-db:基础 4 命令
- scan-expiring-contracts --thresholds 90,60,30:42 份可续聘 → 红 10 / 橙 10 / 黄 10,生成 30 任务
- render-html:写 output/<task_id>.html(4.9 KB)
- generate-renewal-task:单份合同任务书生成
- mark-contract-renewed:状态机更新 + 关闭 open alerts
- dispatch-task-notice:按单位/月份派发 payload(dry-run 模式)

业务规则(AI 续聘建议本地版,规则顺序判断):
1. 师德 < 60 → non_renew
2. 近 3 年考核有不合格(< 60)→ observe
3. 全部 >= 80 且逐年上升 → renew
4. 其他 → observe

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-14 14:17:43 +08:00

65 lines
1.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""日期工具:距到期日天数、月份匹配、ISO 字符串解析。"""
from __future__ import annotations
from datetime import date, datetime
from typing import Tuple
def parse_iso_date(s: str) -> date:
"""解析 'YYYY-MM-DD' ISO 日期字符串。
容错:
- 已经是 date 实例 → 直接返回
- 含 'T' 的 ISO datetime → 取 date 部分
"""
if isinstance(s, date):
return s
s = str(s).strip()
if "T" in s:
s = s.split("T", 1)[0]
return datetime.strptime(s, "%Y-%m-%d").date()
def days_to(target: date, today: date | None = None) -> int:
"""计算距 target 还有多少天(负数 = 已过期)。"""
today = today or date.today()
return (target - today).days
def classify_alert(days: int, thresholds: Tuple[int, int, int] = (90, 60, 30)) -> str | None:
"""根据距到期天数返回预警等级。
thresholds = (yellow_max, orange_max, red_max),须 yellow_max > orange_max > red_max。
返回:'yellow' / 'orange' / 'red' / None(不在预警范围)。
"""
yellow_max, orange_max, red_max = thresholds
if days > yellow_max:
return None
if days <= red_max:
return "red"
if days <= orange_max:
return "orange"
return "yellow"
def month_match(date_to_check: date, month_str: str) -> bool:
"""判断日期是否落在 month_str('YYYY-MM')月份内。"""
if not month_str:
return True
try:
y, m = month_str.strip().split("-")
return date_to_check.year == int(y) and date_to_check.month == int(m)
except (ValueError, AttributeError):
return False
def today_iso() -> str:
"""返回今天日期的 ISO 字符串。"""
return date.today().isoformat()
def now_iso() -> str:
"""返回当前时间的 ISO 字符串。"""
return datetime.now().isoformat(timespec="seconds")