- 新增 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)
88 lines
3.5 KiB
Python
88 lines
3.5 KiB
Python
"""日报结构化组装单元测试(_build_report_data,纯逻辑)。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from datetime import date
|
||
|
||
from scheduler.reporter import _build_report_data
|
||
|
||
|
||
def _fake_event(title: str, importance: int, event_type: str = "其他",
|
||
sentiment: str = "neutral", source_id: str = "cls",
|
||
url: str = "https://x.com/1") -> dict:
|
||
return {
|
||
"title": title,
|
||
"url": url,
|
||
"source_id": source_id,
|
||
"event": {
|
||
"stock_codes": [],
|
||
"company_names": [],
|
||
"industries": [],
|
||
"sentiment": sentiment,
|
||
"importance": importance,
|
||
"event_type": event_type,
|
||
"summary": f"{title}的摘要",
|
||
},
|
||
}
|
||
|
||
|
||
class TestBuildReportData:
|
||
def test_sections_and_ranks(self) -> None:
|
||
news = {
|
||
"total": 2, "hi_threshold": 4,
|
||
"high": [_fake_event("新闻A", 5), _fake_event("新闻B", 4)],
|
||
"sentiments": {"neutral": 2}, "importances": {5: 1, 4: 1},
|
||
"event_types": {"其他": 2},
|
||
}
|
||
cninfo = {
|
||
"total": 1, "hi_threshold": 2,
|
||
"high": [_fake_event("公告C", 3, event_type="公告")],
|
||
"by_day": {"07月10日": 1}, "announcement": 1, "research": 0, "irm": 0,
|
||
}
|
||
pipeline = {"raw_total": 100, "proc": 90}
|
||
xwlb = {"items": [_fake_event("联播D", 4, event_type="新闻联播", source_id="xwlb")],
|
||
"date": "07月10日"}
|
||
|
||
r = _build_report_data(news, cninfo, pipeline, "AI摘要", "20260710", xwlb=xwlb)
|
||
|
||
assert r.report_date == date(2026, 7, 10)
|
||
assert r.report_type == "finance"
|
||
assert r.file_name == ""
|
||
assert r.ai_summary == "AI摘要"
|
||
assert [(e.section, e.rank) for e in r.events] == [
|
||
("news", 1), ("news", 2), ("cninfo", 1), ("xwlb", 1),
|
||
]
|
||
assert r.events[0].source == "cls"
|
||
assert r.events[3].source == "xwlb"
|
||
|
||
def test_stats_snapshot(self) -> None:
|
||
news = {"total": 1, "hi_threshold": 4, "high": [], "sentiments": {},
|
||
"importances": {}, "event_types": {}}
|
||
cninfo = {"total": 0, "hi_threshold": 0, "high": [], "by_day": {},
|
||
"announcement": 0, "research": 0, "irm": 0}
|
||
r = _build_report_data(news, cninfo, {"raw_total": 100}, "s", "20260710")
|
||
# stats 可 JSON 序列化(入库时 json.dumps)
|
||
json.dumps(r.stats, ensure_ascii=False)
|
||
assert r.stats["pipeline"] == {"raw_total": 100}
|
||
assert r.stats["news"]["total"] == 1
|
||
assert "xwlb" not in r.stats
|
||
|
||
def test_title_truncated(self) -> None:
|
||
news = {"total": 1, "hi_threshold": 4,
|
||
"high": [_fake_event("长" * 600, 4)], "sentiments": {},
|
||
"importances": {}, "event_types": {}}
|
||
cninfo = {"total": 0, "hi_threshold": 0, "high": [], "by_day": {},
|
||
"announcement": 0, "research": 0, "irm": 0}
|
||
r = _build_report_data(news, cninfo, {}, "s", "20260710")
|
||
assert len(r.events[0].title) == 512
|
||
|
||
def test_empty_events(self) -> None:
|
||
news = {"total": 0, "hi_threshold": 0, "high": [], "sentiments": {},
|
||
"importances": {}, "event_types": {}}
|
||
cninfo = {"total": 0, "hi_threshold": 0, "high": [], "by_day": {},
|
||
"announcement": 0, "research": 0, "irm": 0}
|
||
r = _build_report_data(news, cninfo, {}, None, "20260710")
|
||
assert r.events == []
|
||
assert r.ai_summary is None
|