# 日报结构化入库：数据库表结构与数据契约

> 版本：v1.0 | 2026-08-03
> 用途：供 API / 前端对接读取日报数据。表位于 MySQL `myquant` 库，表前缀 `news_`。
> 连接：`192.168.1.10:13306`（pi 上 autossh 隧道 → doorcome.cn:3306 MariaDB 10.11），用户 `myquant`（密码在服务器 `.env` 的 `NEWS_DB_PASSWORD`）。

---

## 1. 表结构

### 1.1 news_report（日报主表，一行 = 一份日报）

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| id | BIGINT UNSIGNED PK | 自增主键 |
| report_date | DATE | 日报日期 |
| report_type | VARCHAR(16) | `finance`=A 股日报 / `intl`=国际财经日报 |
| file_name | VARCHAR(160) | 历史文件源文件名；**新生成日报为空字符串 `""`** |
| generated_at | DATETIME | 生成时间 |
| ai_summary | TEXT | AI 摘要全文（含换行，按条目分行） |
| stats | JSON | 数据总览统计快照（见第 3 节），可为 NULL |
| created_at | DATETIME | 入库时间 |

唯一键：`(report_date, report_type, file_name)` —— 历史同一天多次生成（intl 一日 3 次）保留多行；新生成日报 `file_name=''` 每天每类型仅一行，重复生成覆盖。

### 1.2 news_event（日报事件明细，一行 = 一条事件）

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| id | BIGINT UNSIGNED PK | 自增主键 |
| report_id | BIGINT UNSIGNED | FK → news_report.id |
| section | VARCHAR(16) | 板块：`xwlb`=新闻联播 / `news`=财经新闻 / `cninfo`=公告调研 / `intl`=国际重要事件 |
| rank | INT | 板块内序号（1 起） |
| importance | INT NULL | 重要度 1-5 |
| event_type | VARCHAR(64) NULL | 事件类型（如 宏观经济/地缘政治/新闻联播/公告） |
| title | VARCHAR(512) | 标题 |
| summary | TEXT NULL | 摘要/正文 |
| sentiment | VARCHAR(8) NULL | `positive` / `negative` / `neutral` |
| source | VARCHAR(64) NULL | 来源（如 `cls`、`investinglive.com`） |
| url | VARCHAR(512) NULL | 原文链接（新闻联播为空） |
| created_at | DATETIME | 入库时间 |

索引：`idx_report_section (report_id, section)`。

---

## 2. 数据契约

- **幂等语义**：同一 `(report_date, report_type, file_name)` 重复写入会覆盖主表并全量替换事件（DELETE + INSERT），不会产生重复行。
- **取最新**：同一天存在多份时（历史 intl 一日 3 次），前端按 `generated_at` 取最新；新日报 `file_name=''` 每天唯一。
- **板块差异**：finance 日报含 `xwlb`+`news`+`cninfo` 三板块；intl 日报仅 `intl` 板块。前端按 `section` 过滤展示。
- **历史覆盖范围**：2026-06-16 ~ 2026-08-03，共 177 行（finance 49 + intl 128；finance 少 1 因为两个目录存在同名文件被幂等合并）。事件总计 4222 条。

---

## 3. stats JSON 结构

`news_report.stats` 为数据总览快照，前端自行解析。finance 与 intl 的 key 集合不同：

| key | finance | intl | 内容 |
| --- | --- | --- | --- |
| `pipeline` | ✅ | ✅ | M1→M6 管道各环节数量：`{label: 数量}` |
| `sources` | ✅ | — | 各新闻源文章数：`{源名: 数量}` |
| `news` | ✅ | — | 新闻统计：`{total, hi_threshold, sentiments, importances, event_types}` |
| `cninfo` | ✅ | — | 公告调研统计：`{total, hi_threshold, by_day, announcement, research, irm}` |
| `xwlb` | ✅ | — | 联播统计：`{total, date}`（有数据时才有） |
| `sentiment` | ✅ | ✅ | 情绪分布（历史文件为图例文本列表；新生成在 `news.sentiments`） |
| `importance` | ✅ | ✅ | 重要度分布：`[{重要度, 数量}, ...]` |
| `event_types` | ✅ | ✅ | 事件类型 TOP：`[{事件类型, 数量}, ...]` |
| `source_dist` | — | ✅ | 文章来源分布：`[{来源, 文章数}, ...]` |

> 历史文件与新生成日报的 stats 结构存在差异（历史为 HTML 解析快照，新生成为结构化组装），前端建议按 key 防御性读取。

---

## 4. 常用查询示例（API 实现参考）

```sql
-- 某类型日报列表（取每天最新一份）
SELECT r.* FROM news_report r
JOIN (
    SELECT report_date, report_type, MAX(generated_at) AS g
    FROM news_report GROUP BY report_date, report_type
) t ON r.report_date = t.report_date AND r.report_type = t.report_type
   AND r.generated_at = t.g
WHERE r.report_type = 'finance' AND r.report_date >= '2026-07-01'
ORDER BY r.report_date DESC;

-- 某日报的全部事件（按板块）
SELECT section, rank, importance, event_type, title, summary, sentiment, source, url
FROM news_event WHERE report_id = ? ORDER BY section, rank;

-- 最近 N 天重要事件聚合（跨日报检索）
SELECT e.* FROM news_event e
JOIN news_report r ON r.id = e.report_id
WHERE r.report_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
  AND e.importance >= 4
ORDER BY e.importance DESC, r.report_date DESC;
```

---

## 5. 相关命令（数据生产侧）

```bash
uv run a-share report --date YYYYMMDD     # 生成当日日报并入库（finance）
uv run a-share report-import              # 历史 HTML 全量解析入库（幂等）
uv run a-share report-import --date YYYYMMDD --type intl
```

代码：`report_db/`（连接/写入）、`report_import/`（历史解析/导入）、`scheduler/reporter.py`（日报生成）。
