241 lines
11 KiB
Python
241 lines
11 KiB
Python
"""CLI:argparse 与分发(结题报告初稿助手)。
|
||
|
||
只负责参数解析并转交 service;业务内核在 draft_service / ledger_service / docx_service,
|
||
勿在此按入口复制逻辑。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import sys
|
||
from typing import List, Optional
|
||
|
||
from service.task_service import (
|
||
cmd_add_achievement,
|
||
cmd_add_expense,
|
||
cmd_add_project,
|
||
cmd_config_path,
|
||
cmd_draft_batch_pending,
|
||
cmd_draft_report,
|
||
cmd_health,
|
||
cmd_import,
|
||
cmd_init_db,
|
||
cmd_list,
|
||
cmd_list_drafts,
|
||
cmd_render_html,
|
||
cmd_run,
|
||
cmd_show_draft,
|
||
cmd_stats,
|
||
cmd_version,
|
||
)
|
||
from util.config_bootstrap import bootstrap_skill_config
|
||
from util.argparse_zh import ZhArgumentParser
|
||
from util.constants import LOG_LOGGER_NAME, SKILL_SLUG
|
||
from util.logging_config import get_skill_logger, setup_skill_logging
|
||
|
||
|
||
def _cli_str_or_none(raw: Optional[str]) -> Optional[str]:
|
||
if raw is None:
|
||
return None
|
||
v = str(raw).strip()
|
||
return v or None
|
||
|
||
|
||
def _cli_float_or_none(raw: Optional[str]) -> Optional[float]:
|
||
raw = _cli_str_or_none(raw)
|
||
if raw is None:
|
||
return None
|
||
try:
|
||
return float(raw)
|
||
except ValueError:
|
||
return None
|
||
|
||
|
||
def _handle_run(args: argparse.Namespace) -> int:
|
||
tail = [str(x).strip() for x in (args.run_tail or []) if str(x).strip()]
|
||
if len(tail) > 2:
|
||
print("参数过多。")
|
||
print("用法:python main.py run [target [input_id]] | run [-t target] [-i input_id]")
|
||
return 1
|
||
t_target: Optional[str] = None
|
||
t_input: Optional[str] = None
|
||
if len(tail) == 2:
|
||
t_target, t_input = tail[0], tail[1]
|
||
elif len(tail) == 1:
|
||
if tail[0].isdigit():
|
||
t_input = tail[0]
|
||
else:
|
||
t_target = tail[0]
|
||
target = _cli_str_or_none(getattr(args, "target", None)) or t_target
|
||
input_id = _cli_str_or_none(getattr(args, "input_id", None)) or t_input
|
||
return cmd_run(target=target, input_id=input_id)
|
||
|
||
|
||
def _handle_add_project(args: argparse.Namespace) -> int:
|
||
return cmd_add_project(
|
||
project_no=str(args.project_no or "").strip(),
|
||
name=_cli_str_or_none(args.name) or "",
|
||
pi=_cli_str_or_none(args.pi) or "",
|
||
department=_cli_str_or_none(args.department) or "",
|
||
program=_cli_str_or_none(args.program) or "",
|
||
start=_cli_str_or_none(args.start) or "",
|
||
end=_cli_str_or_none(args.end) or "",
|
||
expected_json=_cli_str_or_none(args.expected_json) or "",
|
||
excerpt=_cli_str_or_none(args.excerpt) or "",
|
||
status=_cli_str_or_none(args.status) or "",
|
||
)
|
||
|
||
|
||
def _handle_import(args: argparse.Namespace) -> int:
|
||
return cmd_import(
|
||
projects_json=_cli_str_or_none(args.projects_json) or "",
|
||
achievements_json=_cli_str_or_none(args.achievements_json) or "",
|
||
expenses_json=_cli_str_or_none(args.expenses_json) or "",
|
||
)
|
||
|
||
|
||
def _handle_list(args: argparse.Namespace) -> int:
|
||
return cmd_list(
|
||
kind=_cli_str_or_none(args.kind) or "projects",
|
||
project_no=_cli_str_or_none(args.project_no) or "",
|
||
status=_cli_str_or_none(args.status) or "",
|
||
)
|
||
|
||
|
||
def _handle_render_html(args: argparse.Namespace) -> int:
|
||
report_id = getattr(args, "report_id", None)
|
||
return cmd_render_html(report_id=int(report_id) if report_id else None)
|
||
|
||
|
||
def _print_full_usage() -> None:
|
||
print("结题报告初稿助手(main.py)可用命令:")
|
||
print(" python main.py add-project --project-no X --name Y [--pi 张三] [--program 国自然青年] [--start D] [--end D]")
|
||
print(" [--expected-json '[{\"type\":\"论文\",\"target\":3,\"unit\":\"篇\"}]'] [--excerpt 摘句] [--status 待结题]")
|
||
print(" python main.py add-achievement --project-no X --type 论文 --title T [--venue 期刊] [--date D] [--representative 1]")
|
||
print(" python main.py add-expense --project-no X --category 差旅费 --amount 23000 [--date D] [--description 摘要]")
|
||
print(" python main.py import --projects-json F [--achievements-json F] [--expenses-json F]")
|
||
print(" python main.py list [projects|achievements|expenses] [--project-no X] [--status 待结题]")
|
||
print(" python main.py draft-report --project-no X # 主命令:生成 Word 初稿 + HTML 预览")
|
||
print(" python main.py draft-batch-pending # 批量生成全部待结题初稿")
|
||
print(" python main.py list-drafts [--limit N] | show-draft --id N | render-html [--report-id N]")
|
||
print(" python main.py stats")
|
||
print(" python main.py run [target [input_id]] [-t target] [-i input_id]")
|
||
print(" python main.py health | config-path | version | init-db")
|
||
|
||
|
||
def build_parser() -> ZhArgumentParser:
|
||
p = ZhArgumentParser(
|
||
prog="main.py",
|
||
description="结题报告初稿助手:对比预期指标与实际成果生成完成情况章节,决算明细转规范描述,组装可修改的 Word 版《结题报告(初稿)》。",
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
)
|
||
sub = p.add_subparsers(dest="cmd", required=True, parser_class=ZhArgumentParser)
|
||
|
||
sp = sub.add_parser("add-project", help="录入/更新结题项目(按项目编号幂等)")
|
||
sp.add_argument("--project-no", required=True, metavar="项目编号", dest="project_no")
|
||
sp.add_argument("--name", default=None, metavar="项目名称")
|
||
sp.add_argument("--pi", default=None, metavar="负责人")
|
||
sp.add_argument("--department", default=None, metavar="所属学院")
|
||
sp.add_argument("--program", default=None, metavar="项目类型")
|
||
sp.add_argument("--start", default=None, metavar="执行期开始")
|
||
sp.add_argument("--end", default=None, metavar="执行期结束")
|
||
sp.add_argument("--expected-json", default=None, metavar="预期指标JSON", dest="expected_json")
|
||
sp.add_argument("--excerpt", default=None, metavar="年度报告摘句")
|
||
sp.add_argument("--status", default=None, metavar="状态(待结题/初稿已生成/已提交)")
|
||
sp.set_defaults(handler=_handle_add_project)
|
||
|
||
sp = sub.add_parser("add-achievement", help="录入成果")
|
||
sp.add_argument("--project-no", required=True, metavar="项目编号", dest="project_no")
|
||
sp.add_argument("--type", required=True, metavar="类型(论文/专利/人才培养/其他)", dest="ach_type")
|
||
sp.add_argument("--title", required=True, metavar="成果名称")
|
||
sp.add_argument("--venue", default=None, metavar="期刊/专利号")
|
||
sp.add_argument("--date", default=None, metavar="日期")
|
||
sp.add_argument("--representative", default=None, metavar="代表性(1/0)")
|
||
sp.add_argument("--note", default=None, metavar="备注")
|
||
sp.set_defaults(
|
||
handler=lambda a: cmd_add_achievement(
|
||
project_no=str(a.project_no), ach_type=str(a.ach_type), title=str(a.title),
|
||
venue=_cli_str_or_none(a.venue) or "", date=_cli_str_or_none(a.date) or "",
|
||
representative=_cli_str_or_none(a.representative) or "", note=_cli_str_or_none(a.note) or "",
|
||
)
|
||
)
|
||
|
||
sp = sub.add_parser("add-expense", help="录入决算明细")
|
||
sp.add_argument("--project-no", required=True, metavar="项目编号", dest="project_no")
|
||
sp.add_argument("--category", required=True, metavar="科目")
|
||
sp.add_argument("--amount", required=True, type=float, metavar="金额(元)")
|
||
sp.add_argument("--date", default=None, metavar="发生日期")
|
||
sp.add_argument("--description", default=None, metavar="原始摘要")
|
||
sp.set_defaults(
|
||
handler=lambda a: cmd_add_expense(
|
||
project_no=str(a.project_no), category=str(a.category), amount=float(a.amount),
|
||
date=_cli_str_or_none(a.date) or "", description=_cli_str_or_none(a.description) or "",
|
||
)
|
||
)
|
||
|
||
sp = sub.add_parser("import", help="批量导入(projects/achievements/expenses JSON)")
|
||
sp.add_argument("--projects-json", default=None, metavar="项目JSON(file|-)", dest="projects_json")
|
||
sp.add_argument("--achievements-json", default=None, metavar="成果JSON(file|-)", dest="achievements_json")
|
||
sp.add_argument("--expenses-json", default=None, metavar="决算JSON(file|-)", dest="expenses_json")
|
||
sp.set_defaults(handler=_handle_import)
|
||
|
||
sp = sub.add_parser("list", help="查询台账(projects/achievements/expenses)")
|
||
sp.add_argument("--kind", default="projects", metavar="projects|achievements|expenses", dest="kind")
|
||
sp.add_argument("--project-no", default=None, metavar="项目编号", dest="project_no")
|
||
sp.add_argument("--status", default=None, metavar="状态筛选")
|
||
sp.set_defaults(handler=_handle_list)
|
||
|
||
sp = sub.add_parser("draft-report", help="生成结题报告初稿(主命令)")
|
||
sp.add_argument("--project-no", required=True, metavar="项目编号", dest="project_no")
|
||
sp.set_defaults(handler=lambda a: cmd_draft_report(project_no=str(a.project_no)))
|
||
|
||
sp = sub.add_parser("draft-batch-pending", help="批量生成全部待结题项目初稿")
|
||
sp.set_defaults(handler=lambda _a: cmd_draft_batch_pending())
|
||
|
||
sp = sub.add_parser("list-drafts", help="查询初稿记录")
|
||
sp.add_argument("--limit", default=20, metavar="条数")
|
||
sp.set_defaults(handler=lambda a: cmd_list_drafts(limit=int(a.limit or 20)))
|
||
|
||
sp = sub.add_parser("show-draft", help="查看初稿记录详情")
|
||
sp.add_argument("--id", required=True, type=int, metavar="记录编号", dest="draft_id")
|
||
sp.set_defaults(handler=lambda a: cmd_show_draft(draft_id=int(a.draft_id)))
|
||
|
||
sp = sub.add_parser("render-html", help="重建 HTML 预览(缺省最近 10 份)")
|
||
sp.add_argument("--report-id", default=None, metavar="记录编号", dest="report_id")
|
||
sp.set_defaults(handler=_handle_render_html)
|
||
|
||
sp = sub.add_parser("stats", help="统计")
|
||
sp.set_defaults(handler=lambda _a: cmd_stats())
|
||
|
||
sp = sub.add_parser("run", help="任务执行命令骨架")
|
||
sp.add_argument("--target", "-t", default=None, metavar="目标")
|
||
sp.add_argument("--input-id", "-i", default=None, metavar="输入id", dest="input_id")
|
||
sp.add_argument("run_tail", nargs="*", metavar="位置参数")
|
||
sp.set_defaults(handler=_handle_run)
|
||
|
||
sp = sub.add_parser("health", help="健康检查")
|
||
sp.set_defaults(handler=lambda _a: cmd_health())
|
||
|
||
sp = sub.add_parser("config-path", help="输出用户 .env 与模板路径")
|
||
sp.set_defaults(handler=lambda _a: cmd_config_path())
|
||
|
||
sp = sub.add_parser("version", help="版本信息(JSON)")
|
||
sp.set_defaults(handler=lambda _a: cmd_version())
|
||
|
||
sp = sub.add_parser("init-db", help="幂等初始化本地数据库与数据管理元数据")
|
||
sp.set_defaults(handler=lambda _a: cmd_init_db())
|
||
return p
|
||
|
||
|
||
def main(argv: Optional[List[str]] = None) -> int:
|
||
argv = argv if argv is not None else sys.argv[1:]
|
||
bootstrap_skill_config()
|
||
setup_skill_logging(SKILL_SLUG, LOG_LOGGER_NAME)
|
||
get_skill_logger().info("cli_start argv=%s", sys.argv)
|
||
if not argv:
|
||
_print_full_usage()
|
||
return 1
|
||
parser = build_parser()
|
||
args = parser.parse_args(argv)
|
||
return int(args.handler(args))
|