数据模型
三层定义保持一致:本体 schema(
src/ontology.py::build_schema(),驱动/ontology页与 RDF 导出)↔ SQLite 实例库(data/xgb_wiki.db)↔ RDF 导出(src/rdf_export.py)。本页按 SQLite 实际表结构(2026-09-05 实测)整理。 概念含义见 名词解释;模型取舍的设计动机见 设计方案 §2。
1. 实体表(8 张)
chain 产业链
| 列 | 类型 | 说明 |
|---|---|---|
| id | TEXT PK | 链标识(slug),即本体 chainId |
| name | TEXT | 如"AI算力" |
| description | TEXT |
segment 环节
| 列 | 类型 | 说明 |
|---|---|---|
| id | TEXT PK | 环节标识,即本体 segmentId |
| chain_id | TEXT | 所属链(→ chain.id,segment_part_of_chain) |
| name | TEXT | 自由命名,用研报原文词汇 |
| segment_type | TEXT | 角色类型,开放字典(默认六类,见 §3) |
| stage_tag | TEXT | 派生粗分组(上游/中游/下游/配套) |
| order_no | INTEGER | 链内顺序(工序型链有意义) |
| is_bottleneck | INTEGER | 卡脖子标记 0/1 |
theme 题材
| 列 | 类型 | 说明 |
|---|---|---|
| plate_id | INTEGER PK | 板块 ID,即本体 plateId |
| name | TEXT NOT NULL | |
| classification | TEXT | 概念 / 行业 / 风格(+ 待审核/未知) |
| occurrence_count | INTEGER | 出现次数(全库聚合) |
| first_seen_at / last_seen_at | TEXT | 首次/最近提及 |
| decay_score | REAL | 衰减热度(见设计方案 §2.4) |
| cnt_5d / cnt_10d / cnt_20d / cnt_30d | INTEGER | 滚动时间窗出现次数(自然日口径,截至最新研报日;src/trends.py 重算) |
| streak_days | INTEGER | 后端指标(已不用于展示):从最后一次提及往前数的连续数据日数。页面口径已改为活跃天数=所选窗口内出现过的数据日数(src/trends.theme_active_days 现算,不落列) |
theme_daily 每日题材(时间轴基础表)
| 列 | 类型 | 说明 |
|---|---|---|
| day + theme_id | PK | 数据日(YYYY-MM-DD)× 题材 |
| cnt | INTEGER | 当日提及篇数 |
| core_cnt | INTEGER | 当日强证据提及数(is_core=1) |
任意时间窗(非仅 5/10/20/30)与逐日时间轴视图均由本表现算;scripts/refresh_trends.py 可脱离 MySQL 单独重算。
report_section 观点单元(每日简报基础表)
| 列 | 类型 | 说明 |
|---|---|---|
| report_id + seq | PK | 所属研报 × 小节序号(枚举位置;live/兜底单卡 seq=0) |
| heading | TEXT | 小节标题(digest 正文编号小节;live 为空) |
| viewpoint | TEXT | 观点句(≤70 字:信号句 → 首句 → 标题兜底) |
| evidence | TEXT | 小节正文摘录(≤300 字,截去文末引用块) |
| reason_type | TEXT | 催化类型(同 report_stock 口径,开放字典) |
| is_core | INTEGER | 命中 is_core=1 提及(强股/编辑打标)时为 1 |
| broker_name / source_title | TEXT | 可选增强:券商名出现在节内文本时归属,否则为空 |
配套关联表 section_stock(report_id, seq, stock_code) / section_theme(report_id, seq, theme_id)。
口径:个股/题材以小节内文本匹配为主源(report_stock/report_theme 是编辑打标口径,"关注"清单入库率仅 20%);
live 研报为单卡(标题即观点),个股/题材取编辑打标作交叉引用。由 src/daily.py 解析,
同步管线自动回填(近 7 日,幂等);存量回填用 python scripts/refresh_daily.py。实现细节表,不入本体 schema。
stock 个股
| 列 | 类型 | 说明 |
|---|---|---|
| code | TEXT PK | 股票代码 |
| name | TEXT |
broker 券商
| 列 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK 自增 | 即本体 brokerId |
| name | TEXT UNIQUE | 如"东吴证券" |
report 研报
| 列 | 类型 | 说明 |
|---|---|---|
| id | TEXT PK | "{src_type}-{src_id}" 组合,即本体 reportId |
| src_type | TEXT NOT NULL | live / digest(两源合一表) |
| src_id | INTEGER NOT NULL | 源表主键 |
| column_name | TEXT | 栏目(config.py 栏目字典映射) |
| title / summary / display_time | TEXT | 标题/摘要/展示时间 |
| is_premium | INTEGER | APP 端标记(库里存全文,仅存档用) |
| content | TEXT | 正文 HTML(渲染原文页用) |
rating_event 评级事件
| 列 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK 自增 | 即本体 eventId |
| event_type | TEXT | 开放字典(见 §3) |
| event_date | TEXT | 评级行动日期(来源块日期) |
| analyst_name / analyst_license | TEXT | 分析师姓名 / S 开头执业编号 |
| broker_id | INTEGER | 发出方(→ broker.id,rating_event_issued_by_broker) |
| stock_code / stock_name | TEXT | 针对标的(stock_code 关联 stock.code,stock_name 为冗余显示名) |
| source_title | TEXT | 原始研报标题(事件识别依据) |
| report_id | TEXT | 引用自哪篇脱水研报(→ report.id) |
注:评级事件的三条关系(targets/issuedBy/citedIn)以本表外键列物化,不再建关联表。
image_asset 图片
| 列 | 类型 | 说明 |
|---|---|---|
| id | TEXT PK | URL 哈希,即本体 imageId |
| url | TEXT UNIQUE | image.xuangubao.cn 直链 |
| context_text | TEXT | 正文上下文(前后各 100 字) |
| parse_status | TEXT | unparsed / pending / parsed / failed |
| parsed_type | TEXT | 解析归类:产业链图 / 数据图表 / 其他 |
| parsed_content | TEXT | 解析文本结果 |
| parsed_at | TEXT | 解析时间(运维字段,不进本体 schema) |
2. 关系表(7 张 + 3 条物化)
全库关联总览(实体只列主键/外键与识别列,全量字段见 §1;多对多边的标签即中间表名,边上的关系属性见下方表格):
erDiagram
chain {
TEXT id PK "链 slug"
TEXT name "如 AI算力"
}
segment {
TEXT id PK
TEXT chain_id FK
TEXT segment_type "开放字典 §3"
}
theme {
INTEGER plate_id PK
TEXT name
TEXT classification "概念/行业/风格"
}
theme_daily {
TEXT day PK "数据日"
INTEGER theme_id PK
}
stock {
TEXT code PK
TEXT name
}
broker {
INTEGER id PK
TEXT name
}
report {
TEXT id PK "live-或digest-前缀"
TEXT src_type "live / digest"
INTEGER src_id "源表主键"
}
report_section {
TEXT report_id PK
INTEGER seq PK "小节序号"
TEXT viewpoint "观点句"
}
rating_event {
INTEGER id PK
TEXT event_type "开放字典 §3"
INTEGER broker_id FK
TEXT stock_code FK
TEXT report_id FK
}
image_asset {
TEXT id PK "URL 哈希"
TEXT url "image.xuangubao.cn 直链"
}
chain ||--o{ segment : segment_part_of_chain
theme }o--o{ segment : theme_segment
theme }o--o{ stock : theme_stock
report }o--o{ theme : report_theme
report }o--o{ stock : report_stock 推荐个股
report }o--o{ broker : report_broker
report ||--o{ image_asset : report_image
image_asset }o--o{ theme : image_theme
broker ||--o{ rating_event : issued_by
stock ||--o{ rating_event : targets
report ||--o{ rating_event : cited_in
report ||--o{ report_section : 正文分节
report_section }o--o{ stock : section_stock
report_section }o--o{ theme : section_theme
theme ||--o{ theme_daily : 每日题材
| 本体关系 | SQLite 表 | 基数 | 关系属性 |
|---|---|---|---|
| segment_part_of_chain | segment.chain_id 列 | 多对一 | — |
| theme_located_at_segment | theme_segment | 多对多 | evidence(来源出处) |
| theme_associates_stock | theme_stock | 多对多 | occurrence_count, first_seen_at, last_seen_at, decay_score, core_count(核心提及数) |
| report_mentions_theme | report_theme | 多对多 | reason, reason_type, evidence_quote, is_core, weight |
| report_mentions_stock | report_stock | 多对多 | 同上 |
| report_cites_broker | report_broker | 多对多 | analyst_name, analyst_license, source_title, cited_date |
| rating_event_targets_stock | rating_event.stock_code 列 | 多对一 | — |
| rating_event_issued_by_broker | rating_event.broker_id 列 | 多对一 | — |
| rating_event_cited_in_report | rating_event.report_id 列 | 多对一 | — |
| report_cites_image | report_image | 一对多 | — |
| image_depicts_theme | image_theme | 多对多 | — |
提及关系(report_theme / report_stock)即推荐观点记录:一行 = "某研报在某时间以某原因提及某标的"。weight 为该次提及的热度权重(核心 1.0 / 共现 0.3,写库时由 config 固化),is_core 保留原始证据强度供重新加权。
辅助表 etl_meta(key, value):built_at(构建时间)、ref_date(源数据基准时间)、
wm_live_id / wm_digest_id(增量同步水位 = 源表主键最大 id,含已删除行;旧库缺失时按
report.MAX(src_id) 自动引导)。
同步日志表 sync_log(src/sync.py 写入,每次同步一条,含失败记录):
started_at/finished_at/mode(full|incremental)/status(success|env_failed|failed_rolled_back)/
duration_s/new_live/new_digest/deleted_live/reports_before/reports_after/wm_before/wm_after/
warnings/errors。同名文件日志 data/xgb_wiki.sync.log(gitignore)同步追加,库不可写时仍留存。
3. 开放字典(data/*.json)
三个字典同构:types 为已转正类型(有序,键即枚举值),_pending 为待审区。
// data/segment_types.json —— 额外带 stageTag 映射与展示方式
{
"types": {
"material": {"label": "材料资源", "stageTag": "上游", "display": "group"},
"process": {"label": "工艺制造", "stageTag": "中游", "display": "flow"},
"component": {"label": "核心部件", "stageTag": "中游", "display": "group"},
"infrastructure": {"label": "配套设施", "stageTag": "配套", "display": "group"},
"service": {"label": "服务", "stageTag": "配套", "display": "group"},
"application": {"label": "下游应用", "stageTag": "下游", "display": "group"}
},
"_pending": {}
}
// data/reason_types.json —— 催化类型:业绩/订单/政策/技术进展/供需价格/产能/事件催化/资金动向
// data/event_types.json —— 评级事件:首次覆盖/上调/下调/目标价/增持/维持
// 两者条目格式:{"types": {"<值>": {"label": "<中文名>"}}, "_pending": {}}
display: flow 的类型按 orderNo 排横向流程带;group 类型按 stageTag 分组卡片。字典修改后:/ontology 页与 RDF 导出的枚举实时跟随,存量数据需重跑 build_wiki.py 重新归类。
4. 链映射文件(data/chain_map.json)
{
"磷化铟": { // 键 = 题材名
"chain": "AI算力", // 链名(slugs 化后入 chain.id)
"segment": "上游材料", // 环节名(原文词汇)
"segmentType": "material", // 角色类型(对齐开放字典)
"order": 1, "isBottleneck": true,
"manual": true, // 人工确认位:重跑 LLM 不覆盖
"evidence": [ {"reportId": 4342, "quote": "最上游的铟,95%以上是锌矿冶炼的副产品…"} ]
}
}
当前 300 个题材已映射,全部 manual: true;ETL 载入时据此生成 chain / segment 实例与 theme_segment 关联。
5. 本体 ↔ 存储对照要点
- 本体标识属性 → SQLite 主键一一对应(chainId→chain.id、plateId→theme.plate_id 等)
- 实体/关系显示名中文化(name="产业链"/"提及题材"…),技术 id 保留英文(chain、report_mentions_theme);SQLite 表名、RDF 类名、代码引用均走英文 id
- 本体 enum → SQLite TEXT 存字符串(加类型零迁移),枚举值在渲染时从字典读取
- 本体关系属性 → 关联表列(提及关系的观点五属性 + weight)
image_asset.parsed_at、rating_event.stock_name、etl_meta为实现细节,不在本体 schema 中暴露- RDF 导出(
data/output/xgb-a-chain/)只含 schema 层(类/属性/关系定义),实例数据不导出——实例由 Flask Web 展示(两层的分工见 设计方案 §5)
6. 源库 → 本地映射(ETL 入库口径)
选股宝脱水研报源库(内网 MySQL xuangubao,只读)库内共 42 张表(上游文档口径),同步管线只读其中 5 张,结构与连接关系如下(实线 = SQL JOIN,虚线 = 字符串内 ID 的逻辑关联):
erDiagram
live_tuoshui_news {
BIGINT id PK "同步水位 wm_live_id"
BIGINT msg_id "消息ID 接标签表"
VARCHAR title "研报标题"
MEDIUMTEXT summary "摘要"
MEDIUMTEXT content "正文 HTML"
VARCHAR stocks "推荐个股 代码,名称;…"
VARCHAR plates "题材 板块ID,名称;…"
TINYINT is_deleted "1=源端已删除"
DATETIME msg_display_time "展示时间"
}
live_tuoshui_tag_related {
BIGINT live_msg_id "= live_tuoshui_news.msg_id"
BIGINT tag_id "栏目 LIVE_TAG_COLUMNS"
}
tuoshui_msgs {
BIGINT id PK "同步水位 wm_digest_id"
BIGINT msg_id "消息ID 接标签表"
VARCHAR title "研报标题"
TEXT summary "摘要"
TEXT content "正文 HTML"
VARCHAR related_stocks "推荐个股 代码,名称;…"
VARCHAR plates "题材 板块ID,名称;…"
TINYINT is_premium "APP 端付费标记"
DATETIME msg_created_at "创建时间"
}
tuoshui_tag_related {
BIGINT msg_id "= tuoshui_msgs.msg_id"
BIGINT tag_id "栏目 DIGEST_TAG_COLUMNS"
}
plate_rank_infos {
BIGINT plate_id PK "即本地 theme.plate_id"
VARCHAR plate_name "题材名"
VARCHAR classification "概念/行业/风格"
}
live_tuoshui_news ||--o{ live_tuoshui_tag_related : "n.msg_id = t.live_msg_id 取栏目"
tuoshui_msgs ||--o{ tuoshui_tag_related : "m.msg_id = t.msg_id 取栏目"
live_tuoshui_news }o..o{ plate_rank_infos : "plates 内板块 ID 逻辑关联"
tuoshui_msgs }o..o{ plate_rank_infos : "plates 内板块 ID 逻辑关联"
要点:
- 字段口径:同步依赖列以
src/sync.py::REQUIRED_MYSQL_COLUMNS为准——每次同步启动前对 information_schema 做存在性校验(最近一次通过 2026-09-03);连接关系按src/etl.py的LIVE_SQL/DIGEST_SQL实际 JOIN 绘制,两处标签表均经 msg_id 关联(不是主键 id)。 - stocks / plates 是分隔符字符串(
"代码,名称;…"/"板块ID,名称;…"),源库无个股/题材外键——实体与关系由 ETL 解析字符串后落库,plate_rank_infos 提供 plate_id → 名称/分类的主数据。 - ⚠️ 上游 2_xuangubao 项目《脱水研报数据库Wiki文档》部分列名已过时:其写
live_tuoshui_tag_related.news_id、tuoshui_tag_related.tuoshui_id、tuoshui_msgs.stocks,实际为live_msg_id、msg_id、related_stocks(且缺is_premium)。参考该文档时以本节同步校验口径为准。 messages、original_msgs等其余表本管线不读取。
各表如何落到本地 SQLite——取数 SQL 与解析在 src/etl.py(LIVE_SQL / DIGEST_SQL / apply_live_deletions),同步前置校验所需列清单在 src/sync.py::REQUIRED_MYSQL_COLUMNS:
| 源(MySQL xuangubao) | → 本地(SQLite) | 说明 |
|---|---|---|
live_tuoshui_news(is_deleted=0)⨝ live_tuoshui_tag_related(经 msg_id,tag_id→栏目字典 LIVE_TAG_COLUMNS) |
report(src_type='live') |
display_time 取 msg_display_time;tag_id 缺省按未分类 |
tuoshui_msgs ⨝ tuoshui_tag_related(经 msg_id,tag_id→DIGEST_TAG_COLUMNS) |
report(src_type='digest') |
is_premium 直传;display_time 取 msg_created_at |
live_tuoshui_news.stocks / tuoshui_msgs.related_stocks(解析 "代码,名称;…") |
stock + report_stock(is_core=0, weight=0.3) |
编辑打标"关注"清单,入库率约 20% |
| digest 每日强股结构段(tag_id=4,正文解析) | report_stock(is_core=1, weight=1.0, evidence_quote) |
强证据推荐 |
*.plates(解析 "ID,名称;…")+ plate_rank_infos(plate_id→name/classification) |
theme + report_theme(is_core=0, weight=0.3) |
题材主数据来自 plate_rank_infos |
研报标题正则(extract_title_broker) |
broker + report_broker |
券商引用 |
正文图片直链(extract_images) |
image_asset + report_image |
待解析队列 |
| 正文评级段解析 | rating_event |
事件日期/分析师/券商 |
正文分节 + 提及原因提取(src/daily.py 分节、src/viewpoint.py 归因;同步自动回填近 7 日,幂等;全量 python scripts/refresh_daily.py) |
report_section + section_stock + section_theme,并 UPDATE report_stock / report_theme 的 reason/reason_type |
观点单元口径,正文文本匹配为主源 |
live_tuoshui_news.is_deleted=1 |
删除本地对应 report(连同 report_theme/stock/broker/image、rating_event、report_section、section_stock/theme) |
增量同步检测源端删除 |
| —(本地派生/配置,无源表) | theme_stock 聚合与 theme.occurrence_count/decay_score(src/etl.py 重算);theme_daily、theme.cnt_*(src/trends.py);chain/segment/theme_segment(data/chain_map.json);etl_meta(水位);sync_log(同步日志) |
聚合、链映射配置与运维字段 |