v1.1.1: 升级 AI 续聘建议为 LLM + Playwright RPA 采集 SmartCampus 平台材料
新增:
- scripts/service/platform_collector.py:Playwright RPA(持久化 browser profile 复用登录态),抓合同表(姓名/单位/到期日/剩余天数/预警/建议),逐行点"续聘材料包"按钮下载 PDF/Word 到 data/materials/
- scripts/service/material_parser.py:PDF/Word 文本提取(pypdf → pdf2image + pytesseract OCR 扫描件兜底;python-docx 抽 docx;antiword 兜底 .doc)
- scripts/service/llm_decision.py:AI 决策统一入口(优先调宿主 jiangchang_skill_core.llm_manager.call_llm_json;本地降级到规则版;输出 decision_source ∈ {llm, rules, rules_no_materials})
- scripts/db/migrations.py:idempotent ALTER/CREATE,建 materials_content 表 + renewal_tasks 加 decision_source / materials_summary 列
修改:
- scripts/db/connection.py:init_db() 自动调 apply_all() 应用 v1.1.1 迁移
- scripts/db/repository.py:加 insert_material / list_materials_for_contract / list_pending_materials / summarize_materials / get_task_full
- scripts/service/task_generator.py:AI 决策统一改为调 llm_decision.decide_suggestion(materials_summary=...);reason 文案加「已结合材料包(X 份文件,Y 字 / 待解析)综合判断:...」
- scripts/service/html_renderer.py:ctx 加 DECISION_SOURCE(LLM 推理 / 本地规则 / 本地规则(无材料))+ MATERIALS_COUNT
- scripts/templates/task.html.j2:footer 加 "AI 建议来源:{{DECISION_SOURCE}} · 已审阅材料 {{MATERIALS_COUNT}} 份"
- scripts/cli/app.py:新增 2 个 CLI(collect-from-platform / parse-materials);现有 9 个不动
- requirements.txt:playwright / pypdf / python-docx / pdf2image / pytesseract / Pillow
- .env.example:SMARTCAMPUS_BASE_URL / SMARTCAMPUS_PROFILE_DIR / MATERIALS_DOWNLOAD_DIR / OCR_LANG / LLM_MODEL_NAME / LLM_API_BASE_URL / LLM_API_TOKEN
- SKILL.md:version 1.1.0 → 1.1.1;用户意图路由表加 2 行;新增「数据来源」段(三层来源 + AI 决策透明化)
- README.md:使用前要准备 → 加 playwright install chromium 步骤
- CHANGELOG.md:v1.1.1 条目
- scripts/util/constants.py:SKILL_VERSION 1.1.0 → 1.1.1
本地验证:11 个 CLI 全部 exit 0;decision_source=rules;reason 含「已结合材料包(6 份文件,待解析)」;HTML footer 含决策来源。
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
"""CLI 命令分发:argparse 子命令 → 调用 service 层。
|
||||
|
||||
9 个子命令(与 assets/actions.json 对齐):
|
||||
- health
|
||||
- version
|
||||
- config-path
|
||||
- init-db
|
||||
- scan-expiring-contracts
|
||||
- generate-renewal-task
|
||||
- dispatch-task-notice
|
||||
- mark-contract-renewed
|
||||
v1.1.1:11 个子命令(在 9 个基础上新增)
|
||||
- collect-from-platform:Playwright RPA 抓 SmartCampus HR 平台数据
|
||||
- parse-materials:解析已下载的材料 PDF/Word → 文本
|
||||
|
||||
原有 9 个:
|
||||
- health / version / config-path / init-db
|
||||
- scan-expiring-contracts / generate-renewal-task
|
||||
- dispatch-task-notice / mark-contract-renewed
|
||||
- render-html
|
||||
"""
|
||||
|
||||
@@ -20,7 +19,8 @@ import sys
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from db.connection import get_connection, init_db
|
||||
from service import dispatcher, html_renderer, scanner, task_generator
|
||||
from db import repository as repo
|
||||
from service import dispatcher, html_renderer, material_parser, platform_collector, scanner, task_generator
|
||||
from util.constants import DEFAULT_THRESHOLDS, SKILL_VERSION
|
||||
from util.logging_setup import get_skill_logger, setup_skill_logging
|
||||
from util.runtime_paths import (
|
||||
@@ -195,6 +195,77 @@ def cmd_render_html(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_collect_platform(args: argparse.Namespace) -> int:
|
||||
"""Playwright RPA 抓 SmartCampus HR 平台数据 → 落库。
|
||||
|
||||
首次需用户手工登录保存 cookie(默认 headless=False 便于交互);
|
||||
后续可用 headless=True 复用 profile。
|
||||
|
||||
退出码:
|
||||
0 = 成功(即使抓到 0 行也返回 0,错误信息在 output["error"])
|
||||
1 = 通用异常
|
||||
"""
|
||||
try:
|
||||
result = platform_collector.collect_all(
|
||||
limit=args.limit,
|
||||
headless=not args.show_browser,
|
||||
)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": "exception", "detail": str(e)},
|
||||
ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_parse_materials(args: argparse.Namespace) -> int:
|
||||
"""对已落库的材料 PDF/Word 跑文本提取(pypdf / python-docx / OCR)。
|
||||
|
||||
--contract-id C001: 仅处理该合同
|
||||
不传:处理所有 parse_method='pending' 或 extracted_text IS NULL 的材料
|
||||
"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
if args.contract_id:
|
||||
rows = repo.list_materials_for_contract(conn, args.contract_id)
|
||||
else:
|
||||
rows = repo.list_pending_materials(conn)
|
||||
|
||||
results: list[dict] = []
|
||||
for r in rows:
|
||||
from pathlib import Path
|
||||
fp = Path(r["file_path"])
|
||||
text, method = material_parser.extract_text(fp)
|
||||
if text:
|
||||
repo.insert_material(
|
||||
conn,
|
||||
r["contract_id"], r["file_name"], r["file_path"],
|
||||
text, method,
|
||||
)
|
||||
else:
|
||||
repo.insert_material(
|
||||
conn,
|
||||
r["contract_id"], r["file_name"], r["file_path"],
|
||||
"", "failed",
|
||||
)
|
||||
results.append({
|
||||
"contract_id": r["contract_id"],
|
||||
"file_name": r["file_name"],
|
||||
"parse_method": method,
|
||||
"text_length": len(text),
|
||||
})
|
||||
|
||||
conn.commit()
|
||||
print(json.dumps({
|
||||
"processed": len(results),
|
||||
"results": results,
|
||||
}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ---------- argparse ----------
|
||||
|
||||
def _parse_thresholds(s: str) -> tuple[int, int, int]:
|
||||
@@ -254,6 +325,23 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
sp.add_argument("--task-id", required=True, help="任务 ID")
|
||||
sp.set_defaults(handler=cmd_render_html)
|
||||
|
||||
sp = sub.add_parser(
|
||||
"collect-from-platform",
|
||||
help="Playwright RPA 抓 SmartCampus HR 平台(合同表 + 材料包)",
|
||||
)
|
||||
sp.add_argument("--limit", type=int, default=0,
|
||||
help="限制抓取行数(0=全量)")
|
||||
sp.add_argument("--show-browser", action="store_true",
|
||||
help="显示浏览器(首次手工登录用,默认 headless)")
|
||||
sp.set_defaults(handler=cmd_collect_platform)
|
||||
|
||||
sp = sub.add_parser(
|
||||
"parse-materials", help="解析已下载的 PDF/Word 材料 → 文本",
|
||||
)
|
||||
sp.add_argument("--contract-id", default="",
|
||||
help="仅解析该合同(留空=处理所有 pending)")
|
||||
sp.set_defaults(handler=cmd_parse_materials)
|
||||
|
||||
return p
|
||||
|
||||
|
||||
@@ -263,7 +351,8 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
print(f"合同到期智能预警与续聘辅助 v{SKILL_VERSION}")
|
||||
print("Commands: health, version, config-path, init-db, "
|
||||
"scan-expiring-contracts, generate-renewal-task, "
|
||||
"dispatch-task-notice, mark-contract-renewed, render-html")
|
||||
"dispatch-task-notice, mark-contract-renewed, render-html, "
|
||||
"collect-from-platform, parse-materials")
|
||||
return 1
|
||||
|
||||
setup_skill_logging()
|
||||
|
||||
Reference in New Issue
Block a user