feat: 日报结构化入库(M10 前后端分离数据层)

- 新增 report_db 包: MySQL 连接/建表/幂等写入 (news_report/news_event, myquant 库)
- 新增 report_import 包: 历史 178 份日报 HTML 解析入库, 表头驱动列映射
- reporter.py 完全切换: generate_report 结构化入库, 不再生成/上传 HTML
- CLI: 新增 report-import 子命令
- 依赖: uv add pymysql; 配置: NEWS_DB_* / REPORT_HISTORY_DIR
- 文档: docs/report_db_design.md(实现逻辑), docs/db_schema.md(表结构供 API/前端)
- 测试: 24 个单测通过 (parser/builder/models/importer)
This commit is contained in:
2026-08-03 21:32:07 +08:00
parent f2c80c5a9c
commit 366e60e8a9
25 changed files with 1934 additions and 33 deletions
+27 -8
View File
@@ -18,6 +18,7 @@ from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
from collections import Counter
@@ -394,24 +395,34 @@ def cmd_status(args: argparse.Namespace) -> int: # noqa: ARG001
# --------------------------------------------------------------------------- #
def cmd_report(args: argparse.Namespace) -> int:
"""生成 HTML 日报并上传到 Web 服务器"""
"""生成日报并结构化入库(M10:不再生成 HTML/上传)"""
load_dotenv()
from scheduler.reporter import generate_report
day_str = args.date or _today()
print(f"📊 生成日报: {day_str}")
path = generate_report(day_str, upload=not args.no_upload)
if path is None:
report_id = generate_report(day_str, upload=not args.no_upload)
if report_id is None:
print("⚠️ 无数据或生成失败")
return 1
print(f"✅ 日报已保存: {path}")
if not args.no_upload:
from datetime import date as dt_date
today_str = dt_date.today().strftime("%Y%m%d")
print(f"✅ 已上传: http://doorcome.cn/echart/research/{today_str}/")
print(f"✅ 日报已入库: report_id={report_id}")
return 0
def cmd_report_import(args: argparse.Namespace) -> int:
"""历史日报 HTML 解析入库(M10)。"""
load_dotenv()
from report_import.importer import import_history
report_dir = args.dir or os.environ.get("REPORT_HISTORY_DIR", "data/reports_history")
print(f"📥 导入历史日报: {report_dir} (date={args.date or '全部'} type={args.type or '全部'})")
stats = import_history(report_dir, date_str=args.date, report_type=args.type, force=args.force)
print(f"✅ 扫描 {stats.scanned} | 新导入 {stats.imported} | 跳过 {stats.skipped} | 失败 {stats.failed}")
for err in stats.errors[:20]:
print(f"{err}")
return 0 if stats.failed == 0 else 1
# --------------------------------------------------------------------------- #
# discover — 自动分析站点,生成 sources.yaml 配置建议
# --------------------------------------------------------------------------- #
@@ -917,6 +928,14 @@ def main() -> int:
pl = sp.add_parser("list", help="查看关注列表")
pl.set_defaults(func=cmd_watchlist_list)
# ---- report-import ----
p = sub.add_parser("report-import", help="历史日报 HTML 解析入库 (M10)")
p.add_argument("--dir", default=None, help="日报目录(默认 REPORT_HISTORY_DIR)")
p.add_argument("--date", default=None, help="仅导入指定日期 YYYYMMDD")
p.add_argument("--type", default=None, choices=["finance", "intl"], help="仅导入指定类型")
p.add_argument("--force", action="store_true", help="已存在也覆盖重导")
p.set_defaults(func=cmd_report_import)
# ---- stock-report ----
p = sub.add_parser("stock-report", help="生成关注列表个股日报")
p.add_argument("--no-upload", action="store_true", help="仅生成不上传")