init: 商品图合规检测工具(豆包初筛 + DeepSeek 复检级联)

- src 标准布局:config/providers/pipeline/report + CLI/Tk GUI 双入口
- 级联省钱:豆包全量初筛,仅无违规/违规不明图进 DeepSeek 复检(含两票复核)
- 输出:时间戳目录 + 分类文件夹图片归档 + Excel 报表
- 30 个单元测试(tests/,测试图片不入库)
This commit is contained in:
yeuimu
2026-09-02 15:28:51 +08:00
commit 08d26710b4
20 changed files with 2513 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
"""CLI 入口:python -m violation_detector <图片文件夹> [选项]"""
import argparse
import asyncio
import sys
from . import __version__
from .config import DEFAULT_CONFIG, load_config, missing_fields
from .pipeline import run_detection
from .report import build_report, organize_output
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="cascade",
help="检测模式: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)
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
missing = missing_fields(cfg)
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=args.mode, verify=not args.no_verify))
except FileNotFoundError as e:
print(f"错误:{e}")
return 1
out_dir = args.output or args.folder
ts_dir = organize_output(rows, args.folder, out_dir)
xlsx = build_report(rows, str(ts_dir), {"mode": args.mode, **{
k: summary.get(k) for k in ("ds_calls", "ds_peak_cost", "ds_idle_cost")}})
print(f"\n分类输出目录:{ts_dir}")
print(f"报表已生成:{xlsx}")
return 0
if __name__ == "__main__":
sys.exit(main())