# US Earnings Calls（美股电话会议）

两个 dataset 提供美股电话会议的事件与逐句转录，经共享 ClickHouse 查询：

- `datagate.earnings_call_events`：每场电话会一行（事件表）
- `datagate.earnings_call_transcripts`：每句一行（转录表）

两表经 `call_id` 关联。当前数据源为 stockanalysis.com（Quartr 数据镜像），
`rights=["internal-research"]`，仅供内部研究；对下游分发需更换许可源
（S&P CIQ 接入后字段不变，`call_id` 前缀区分来源）。

> 2026-09-11 起本家族切换为三库中间件存储（`earnings-calls-lake-v1`，对齐
> us-inflation-policy 路线）：查询真源是共享 ClickHouse，上游原件在 MinIO
> `earnings-calls/raw/`（content-addressed），处理台账在 PostgreSQL
> `earnings_call_pull_*`。**不再提供 cell 的 WebSocket 推送 / REST 查询 /
> Published Parquet**；如需公开 API 须另立 derived 合同。内部权威边界见
> `human/docs/arch/earnings-calls-architecture.md`。

## Schema

行内 `raw_json` 携带完整契约行；下表中的列同时是 ClickHouse typed 列
（未建 typed 列的契约字段——`company_name`/`has_transcript`/`audio_url`/
`summary`/`release_date`/`sentiment`/`fetched_at_ms`——只在 `raw_json` 里）。
每行另有 `raw_object_key` + `payload_sha256` 溯源指针（MinIO 原件）与
`ingested_at_ms`（replacing version）。

### datagate.earnings_call_events

| 列 | 类型 | 说明 |
|---|---|---|
| `call_id` | String | 主键。`sa:{SYMBOL}:{fiscalYear}Q{n}`（sa=stockanalysis） |
| `symbol` | LowCardinality(String) | 美股代码 |
| `period_date` | String | 电话会日期 `YYYY-MM-DD`（America/New_York） |
| `ts` | Int64 | 会议日期时间的 epoch ms（未来预告为预定会议时间） |
| `timezone` | LowCardinality(String) | `America/New_York` |
| `event_type` | LowCardinality(String) | `earnings_call` |
| `fiscal_year` / `fiscal_quarter` | Nullable(Int32/UInt8) | 财年/财季标注（上游口径） |
| `time_of_day` | LowCardinality(String) | `BMO`（盘前）/ `AMC`（盘后）/ `DMH`（盘中）；未知为空串 |
| `eps_estimate` / `eps_actual` / `eps_surprise_pct` | Nullable(Float64) | 市场一致预期/实际/超预期% |
| `revenue_estimate` / `revenue_actual` / `revenue_surprise_pct` | Nullable(Float64) | 同上，营收 |
| `source_url` | String | 溯源 URL |
| `event_day` | Date | MATERIALIZED 分区列（由 `ts` 派生） |

含未来已预告的事件（`eps_actual` 为空），可直接当 earnings 日历用。
引擎 `ReplacingMergeTree(ingested_at_ms)`，唯一键 `(cell_id, dataset_id,
call_id)`：上游原位修订（预期值、改期、会后补实际值）新者胜。

### datagate.earnings_call_transcripts

| 列 | 类型 | 说明 |
|---|---|---|
| `call_id` | String | 主键之一，关联事件表 |
| `segment_seq` | UInt32 | 主键之一，全文句子序号（0 起） |
| `turn_seq` | Nullable(UInt32) | 发言轮次序号（同一 speaker 连续发言为一轮） |
| `symbol` / `period_date` / `ts` / `timezone` | — | 冗余自事件（`ts` 为当日 00:00 ET，精确时间查事件表） |
| `speaker_name` / `speaker_role` / `speaker_company` | Nullable(String) | 发言人姓名/职务/机构（分析师为券商名） |
| `section` | LowCardinality(String) | `prepared_remarks` / `qna`；段落级旧页为空串 |
| `text` | String | 句子原文 |
| `start_sec` / `end_sec` | Nullable(Float64) | 句级音频时间戳（秒） |
| `source_url` | String | 溯源 URL |
| `call_day` | Date | MATERIALIZED 分区列（由 `ts` 派生） |

唯一键 `(cell_id, dataset_id, call_id, segment_seq)`；**first-wins**：已入库
句行不被上游修订覆盖（插入前 anti-join）。

## 查询（ClickHouse）

连接参数从部署环境获取（共享集群，库 `datagate`）。**去重是读取侧义务**：
带 `FINAL`（或 `argMax` 按 `ingested_at_ms`）。

```sql
-- 某股票最近的电话会事件
SELECT call_id, period_date, time_of_day, eps_estimate, eps_actual
FROM datagate.earnings_call_events FINAL
WHERE symbol = {symbol:String}
ORDER BY ts DESC LIMIT 4;

-- 一场会的完整转写（句级）
SELECT segment_seq, section, speaker_name, speaker_role, text, start_sec, end_sec
FROM datagate.earnings_call_transcripts FINAL
WHERE call_id = {call_id:String}
ORDER BY segment_seq;

-- 转写可用性（事件 left join 转写计数）
SELECT e.call_id, e.period_date, count(t.segment_seq) AS sentences
FROM datagate.earnings_call_events e
LEFT JOIN datagate.earnings_call_transcripts t ON t.call_id = e.call_id
GROUP BY e.call_id, e.period_date
ORDER BY e.period_date DESC;

-- 某位分析师在 Q&A 的提问历史
SELECT call_id, text FROM datagate.earnings_call_transcripts FINAL
WHERE speaker_name = {name:String} AND section = 'qna'
ORDER BY ts DESC, segment_seq;
```

溯源与对账：行内 `raw_object_key` 指回 MinIO 原件（读取须 sha256 校验）；
各源最近拉取状态查 PostgreSQL `earnings_call_pull_runs`（按 `source_id,
started_at_ms DESC`）。

## 口径与已知限制

- 事件表覆盖约 2012 年至今 + 未来预告；转录覆盖约 2016 年起（源上限）。
- `section` 是启发式切分（首个非发行人公司发言轮起算 Q&A），个别会议
  （如有多公司联合发言）可能提前切换；对切分敏感的用法请自行复核。
- 转录表 `ts` 为会议日 00:00 ET（详情页无具体时刻）；精确时间在事件表。
- 约 2021 年前的旧转写页（Quartr 未重新精加工的）没有句级 turns，只有纯文本
  全文：这些会以**段落级**行入库（一段一行），`turn_seq`/`speaker_*`/
  `start_sec`/`end_sec` 均为 null、`section` 为空串。句级字段只对较新的
  会议有保证。
- 上游是两阶段发布：新会先出纯文本全文（会后约 1 小时），句级 turns 后到
  （实测 ≤20 小时）。为避免 first-wins 把会议**永久冻结在段落级**，默认
  3 天内（`legacy_grace_days` 可调）的纯文本页会先跳过、逐轮重试，直到
  句级版本出现——所以最新会议在库里可见的时间以句级落地为准，不是会后
  1 小时。
- 同一公司同一财季多场电话会会撞 `call_id`（events parse 对批内重复
  call_id 直接报错，出现后按新 lineage 处理）。
- 事件表是 upsert 真源：上游**修订**新者胜，但上游**删除**不会在
  ClickHouse 消失（上游日历原位修订是常态、删除极罕见，接受此偏差）。
- `FixedString(64)` 的 `payload_sha256` 可能带 NUL padding，比较前 trim。

## 验收记录

### 2026-09-11，lake 模式（三库中间件）

- 首轮 run（本机 E2E cell，watchlist AAPL/MSFT/IBM/NVDA/ADBE/ORCL/KR）：
  事件表 445 行（含未来预告）与转写 12,825 句（25 场，max_new_per_run
  限流）落入 ClickHouse；MinIO `earnings-calls/raw/` 39 个对象（7 事件索引
  + 7 转写索引 + 25 详情页）；PostgreSQL 两源各一条 published run 账本。
- 抽查：事件行 actuals（ADBE eps 5.84→6.13）与 `raw_object_key` 溯源指针、
  转写句级 speaker/section/音频时间戳均完好；`FINAL` 去重读正常。
- 历史回填同日排干（300s 周期 × 25 场自排水，已入库 call_id 经
  `lake_prepare` 从 ClickHouse 跳过）：**261 场 / 130,186 句**全部落库，
  覆盖 2016-01-26 起（IBM/KR 源内起始较晚）；MinIO 346 对象 / 108 MiB，
  PG 账本 13 条 run 无 failed；事件表修订（actuals 补全）触发过一次
  整表 replacing 重发，后续周期稳定 no_op。

### 2026-09-09/10，published 模式（历史，存储路线已切换）

- 拉取：watchlist AAPL/MSFT/IBM，经 7890 代理全部 200；事件 189 行（含未来
  预告）+ 转录 3,216 句（6 场）published；后续 watchlist 扩至 7 家、回填
  至 2016 年共 261 场约 12.7 万句。
- 幂等：两源复跑均 no_op（events full_refresh 内容 digest 未变、transcripts
  增量 first-wins 无新键）。
- DuckDB 直读 / REST join / WS dataset_update 推送三通道当时均验证通过
  （lake 切换后此三面由共享 ClickHouse 取代）。
