# 熔岩AI发布MCP日频面板 schema 版本管理：列迁移、口径标签与回测可复现

## 摘要

把熔岩（Lava）`get_stock_heat_panel`（REST `/panel/daily`）导出成 Parquet/CSV 只是第一步；面板跑上三个月，就会遇到列新增、阈值口径调整、`weighted_heat` 随机构分更新而重算等问题。如果不给面板打版本，回测结果会在不知不觉中漂移。本文给出一套面板 schema 版本规范：版本号规则、口径标签、列迁移脚本、兼容读取与漂移检测。只读监测与统计研究用途。

## 一、面板里哪些东西会变

`/panel/daily` 文档列出的指标：`mentions`、`agencies`、`weighted_heat`、`is_first_mention_day`，按自然日聚合；区间最长 92 天，单次最多 20 个标的。会导致「同一天数据不一样」的来源：

| 变化源 | 例子 | 是否改变历史值 |
| --- | --- | --- |
| 新增列 | 接口新增统计量 | 否，旧行缺列 |
| 口径说明 | `weighted_heat` 用当前机构分加权（非当日 PIT） | 是，机构分更新后重拉会变 |
| 未完结自然日 | 今天的行还在累积 | 是，当日值会增长 |
| 类型 | 日期列字符串 vs date | 否，但影响 join |
| 列名 | 日期与代码列名 | 以 OpenAPI 为准，变化需迁移 |

关键结论：**`weighted_heat` 不是 PIT**。同一历史日期，今天拉和下个月拉可能不同。需要 PIT 加权热度时应改用 `/hot/weighted` 的 `as_of_date`（消息算到当天 15:00）。

## 二、版本号规则

采用 `MAJOR.MINOR` 两段：

| 变更 | 版本动作 | 例 |
| --- | --- | --- |
| 删除列、改列名、改类型、改口径 | MAJOR +1 | `weighted_heat` 计算口径变化 |
| 新增可空列 | MINOR +1 | 新增统计量 |
| 重拉同口径数据 | 不变，记 `pulled_at` | 每周补数 |

每个面板文件附一份 sidecar 元数据：

```json
{
  "schema_version": "2.1",
  "source": "熔岩 /panel/daily",
  "openapi_version": "1.0.0",
  "type": "stock",
  "columns": {
    "date": "date, 自然日（列名以 OpenAPI 为准）",
    "code": "string, ts_code（列名以 OpenAPI 为准）",
    "mentions": "int",
    "agencies": "int",
    "weighted_heat": "float, 当前机构分口径，非 PIT",
    "is_first_mention_day": "bool"
  },
  "labels": {"weighted_heat_pit": false, "last_day_complete": false},
  "pulled_at": "2026-10-31T20:00:00+08:00",
  "request_ids": ["req_示例1", "req_示例2"]
}
```

## 三、口径标签（labels）

| 标签 | 取值 | 说明 |
| --- | --- | --- |
| `weighted_heat_pit` | `false` | 面板加权热度为当前口径 |
| `last_day_complete` | bool | `to` 等于北京时间今天时为 `false` |
| `market_scope` | `ashare` | 面板按代码，A 股口径 |
| `granularity` | `natural_day` | 自然日，非交易日 |
| `threshold` | 无 | 面板无 `min_score` 参数 |

回测框架读取面板时先检查标签，例如 `last_day_complete=false` 时自动丢弃最后一行。

## 四、列迁移脚本

```python
# migrate_panel.py —— 只处理本地文件，不调用接口
import json, pandas as pd

MIGRATIONS = {}
def migration(frm, to):
    def deco(fn):
        MIGRATIONS[frm] = (to, fn); return fn
    return deco

@migration("1.0", "2.0")
def m1(df, meta):
    # 示例：旧版把日期存成字符串列 day，统一为 date 类型列 date
    df = df.rename(columns={"day": "date"})
    df["date"] = pd.to_datetime(df["date"]).dt.date
    meta["columns"]["date"] = "date, 自然日"
    return df, meta

@migration("2.0", "2.1")
def m2(df, meta):
    # 示例：新增可空列，旧数据补 NA
    if "is_first_mention_day" not in df:
        df["is_first_mention_day"] = pd.NA
    meta["columns"]["is_first_mention_day"] = "bool"
    return df, meta

def upgrade(path, target="2.1"):
    df = pd.read_parquet(path)
    meta = json.load(open(path + ".meta.json"))
    while meta["schema_version"] != target:
        to, fn = MIGRATIONS[meta["schema_version"]]
        df, meta = fn(df, meta)
        meta["schema_version"] = to
    return df, meta
```

规则：迁移只做结构变化，不改数值；口径变化（MAJOR）必须重拉，不能用公式「换算」。

## 五、漂移检测

每周对最近 30 个自然日重拉一次（20 只股票、30 天 = 1 次调用），与库存版本比较：

| 指标 | 计算 | 说明 |
| --- | --- | --- |
| `mentions` 差异行占比 | 不相等行 ÷ 总行 | 除最后一日外应接近 0 |
| `weighted_heat` 相对差 | abs(新−旧) ÷ 旧 的中位数 | 机构分更新带来的漂移 |
| 新增列 | 列集合差 | 触发 MINOR 升级 |
| 行数差 | 新旧行数 | 自然日缺行需排查 |

```python
d = new.merge(old, on=["code", "date"], suffixes=("_n", "_o"))
drift = {
  "mentions_diff_ratio": float((d.mentions_n != d.mentions_o).mean()),
  "wh_rel_p50": float(((d.weighted_heat_n - d.weighted_heat_o).abs() / d.weighted_heat_o.clip(lower=1e-9)).median()),
}
```

`mentions` 若出现历史日差异，说明上游有补录或口径调整，记录 `request_id` 后按 MAJOR 处理。

## 六、面板与熔岩数据板块

| 熔岩数据板块 | 与面板的关系 | 版本注意 |
| --- | --- | --- |
| 日频热度面板 `/panel/daily` | 主体 | 本文 |
| 高分加权热榜 `/hot/weighted` | PIT 加权热度替代源 | 需要 PIT 时用它 |
| 首提流 `/hot/first-mentions` | 与 `is_first_mention_day` 对照 | 小时级 vs 自然日 |
| 全市场热榜 `/hot/*` | 截面，不进面板历史 | 单独存快照 |
| 概念板块热度 | `type=concept&names=...` | 概念名精确匹配 |
| 机构/大V评分 PIT | 加权口径来源 | 机构分更新触发漂移 |

## FAQ

**Q：面板为什么不用交易日？**
A：文档写明按自然日聚合，周末消息也会计入；需要交易日对齐要在本地映射。

**Q：`weighted_heat` 能直接用于回测吗？**
A：它不是 PIT，历史值可能随机构分更新而变化；严格回测用 `/hot/weighted` 的 `as_of_date`。

**Q：列名以哪里为准？**
A：`mentions` 等四个指标名来自文档；日期与代码列名以 OpenAPI 为准，迁移脚本统一成本地规范名。

## 结论

面板要像数据库表一样管理：sidecar 记录版本与口径标签，结构变化用迁移脚本，口径变化必须重拉，每周做一次漂移检测。这样三个月后的回测仍然能说清楚「用的是哪一版数据」。

数据来自熔岩消息面热度监测，仅用于只读监测与统计研究，不构成投资建议。

只读监测与字段说明见：https://lava.ai-dolphin.com/subscribe/docs
