- config.ini [run] verify_clean = yes/no(默认 yes),GUI/CLI 均跟随 - CLI --no-verify 仍可强制关闭;非法值回落默认开启 - 模板/示例/README 同步,新增配置测试(共 36 个全部通过)
91 lines
3.8 KiB
Python
91 lines
3.8 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""CLI 入口:python -m violation_detector <图片文件夹> [选项]"""
|
||
import argparse
|
||
import asyncio
|
||
import logging
|
||
import sys
|
||
|
||
from . import __version__
|
||
from .config import DEFAULT_CONFIG, load_config, missing_fields
|
||
from .logutil import log_file, setup_logging
|
||
from .pipeline import run_detection
|
||
from .report import build_report, organize_output
|
||
|
||
logger = logging.getLogger("violation_detector.cli")
|
||
|
||
|
||
def build_parser() -> argparse.ArgumentParser:
|
||
p = argparse.ArgumentParser(
|
||
prog="violation-detector",
|
||
description="服装商品图合规检测:豆包视觉初筛 + DeepSeek 复检级联,输出 Excel 报表")
|
||
p.add_argument("folder", help="待检测图片文件夹")
|
||
p.add_argument("-o", "--output", default=None, help="报表输出目录(默认为图片文件夹)")
|
||
p.add_argument("-c", "--config", default=None, help="配置文件路径(默认 config.ini)")
|
||
p.add_argument("--prompt", default=None, help="提示词文件路径(覆盖配置)")
|
||
p.add_argument("--mode", choices=["cascade", "doubao", "deepseek"], default=None,
|
||
help="检测模式(默认取 config.ini [run] mode,未配置则 cascade):"
|
||
"cascade=豆包初筛+DeepSeek复检,doubao=仅豆包,deepseek=仅 DeepSeek 全量")
|
||
p.add_argument("--workers-ark", type=int, default=None, help="豆包并发数")
|
||
p.add_argument("--workers-ds", type=int, default=None, help="DeepSeek 并发数")
|
||
p.add_argument("--max-tokens", type=int, default=None,
|
||
help="DeepSeek max_tokens(默认 5000,实测零失败最小档)")
|
||
p.add_argument("--no-verify", action="store_true", help="跳过 DeepSeek 无违规两票复核")
|
||
p.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
||
return p
|
||
|
||
|
||
def main(argv=None) -> int:
|
||
args = build_parser().parse_args(argv)
|
||
log_path = setup_logging(console=True)
|
||
logger.info("商品图合规检测工具 v%s 启动,日志文件:%s", __version__, log_path)
|
||
cfg = load_config(args.config)
|
||
if args.prompt:
|
||
cfg.prompt_file = args.prompt
|
||
if args.workers_ark:
|
||
cfg.ark_workers = args.workers_ark
|
||
if args.workers_ds:
|
||
cfg.deepseek_workers = args.workers_ds
|
||
if args.max_tokens:
|
||
cfg.max_tokens = args.max_tokens
|
||
mode = args.mode or cfg.mode
|
||
# 复核开关:CLI --no-verify 强制关;否则跟随 config.ini [run] verify_clean
|
||
verify = cfg.verify_clean and not args.no_verify
|
||
|
||
missing = missing_fields(cfg, mode)
|
||
if missing:
|
||
print(f"配置不完整,请编辑 {DEFAULT_CONFIG} 填写以下必填项:")
|
||
for m in missing:
|
||
print(f" - {m}")
|
||
return 2
|
||
|
||
try:
|
||
rows, summary = asyncio.run(run_detection(
|
||
args.folder, cfg, mode=mode, verify=verify))
|
||
except FileNotFoundError as e:
|
||
logger.error("路径或文件不存在:%s", e)
|
||
return 1
|
||
except KeyboardInterrupt:
|
||
logger.warning("用户中断")
|
||
return 130
|
||
except Exception: # noqa: BLE001 顶层兜底:完整堆栈进日志与控制台
|
||
logger.exception("检测过程发生未预期异常")
|
||
print(f"\n检测失败,完整日志:{log_file()}")
|
||
return 1
|
||
|
||
try:
|
||
out_dir = args.output or args.folder
|
||
ts_dir = organize_output(rows, args.folder, out_dir)
|
||
xlsx = build_report(rows, str(ts_dir), {"mode": mode, **{
|
||
k: summary.get(k) for k in ("ds_calls", "ds_peak_cost", "ds_idle_cost")}})
|
||
except Exception: # noqa: BLE001
|
||
logger.exception("输出组织/报表生成失败")
|
||
print(f"\n报表生成失败,完整日志:{log_file()}")
|
||
return 1
|
||
print(f"\n分类输出目录:{ts_dir}")
|
||
print(f"报表已生成:{xlsx}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|