# Investibook 使用与成本报表获取 Runbook

本文档沉淀 Investibook 运营看板数据的获取、清洗、team 映射、日报/周报输出口径。目标是让 agent 可以稳定地每天拉取数据、画图，并生成周报。

## Usage Reporting Skill

当用户要求分析 Investibook 使用情况、成本、team 行为、top workspace、用户画像或 chat message 质量时，按本节流程执行。这个流程既服务飞书周报，也服务后续前端上传日粒度 JSON 后渲染图表。

核心原则：

- 统计粒度优先按 Asia/Shanghai 自然日落盘，再由日数据聚合成周报、月报或自定义窗口。
- 成本以 prod backend `interaction_token_usage` 为准，并按 runtime 语义计算；ACP interactions 仍以 Prometheus 指标为准。message 分类只用于解释行为，不直接替代成本口径。
- 所有结论都必须区分“总量贡献”和“单次消耗”。高频行为可能烧最多总 token，复杂行为可能单条最贵。
- team 归属每天重新映射，不能长期依赖历史 snapshot。
- 输出给前端的 JSON 应该是稳定结构，不把一次性 Markdown 表格当作唯一结果。

执行顺序：

1. 确认时间窗口，默认按 `YYYY-MM-DD 00:00:00` 到次日 `00:00:00`，timezone 固定 `Asia/Shanghai`。
2. 读取 usage/cost 数据，生成日级 raw JSON。
3. 读取 workspace/chat message 明细，补齐 message count、user turns、assistant turns、message 分类。
4. 合并 team mapping，输出 unknown/ambiguous users。
5. 生成日级 derived JSON，包含 team/user/workspace/category/chart series。
6. 如果用户要求写飞书，只把 derived 结论写入文档；不要把大段 raw JSON 或 raw log 贴进去。
7. 如果用户要求前端渲染，优先交付 derived JSON schema 和图表字段，不在前端重复实现复杂归因逻辑。

## 适用场景

- 每日获取 Investibook 使用量、成本、活跃用户、workspace 趋势。
- 每周按 team、用户、workspace 维度分析使用行为。
- 区分 oncall 临时使用、稳定复用用户、单周事件峰值。
- 生成飞书文档、Markdown 周报或图表。

## 数据源

Grafana dashboard:

```text
https://clinic.pingcap.com/grafana/d/investibook-ops/investibook-yun-ying-kan-ban
```

Prometheus datasource:

```text
name: DevOps-prometheus
id: 80
query endpoint: https://clinic.pingcap.com/grafana/api/datasources/proxy/80/api/v1/query
```

当前指标标签显示该看板只统计 prod 环境：

```text
cluster="prod-devops"
namespace="investibook"
job="backend"
prometheus="monitoring/o11y-vmagent"
```

如果后续要统计 dev/staging，需要先确认这些环境是否上报同名 metric，以及 Grafana datasource 是否覆盖对应 Prometheus。

## 认证方式

读取 Grafana API 时使用本机 Google Chrome 里的 Clinic 登录态。先检查登录态：

```bash
PYTHONPATH=/tmp/codex-pycookiecheat-20260630:agentkit/skills/nutshell-skills/platform/op-clinic-grafana-access/scripts \
/Users/lingtaoli/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 \
agentkit/skills/nutshell-skills/platform/op-clinic-grafana-access/scripts/check_login.py \
--base-url https://clinic.pingcap.com --profile Default
```

如果登录态过期，让用户用 Chrome 打开 Clinic 并重新登录后再执行。不要在日志或报表里输出 cookie、token 或任何认证信息。

依赖如果缺失，可在临时目录安装：

```bash
/Users/lingtaoli/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 \
-m pip install --upgrade --target /tmp/codex-pycookiecheat-20260630 pycookiecheat requests
```

## 时间窗口

所有报表默认使用 Asia/Shanghai 自然时间。

- 日报：`00:00:00` 到次日 `00:00:00`。
- 周报：使用显式 7 天窗口。若用户给出上一期周报链接或日期，按上一期日期顺延；否则默认使用最近一个完整的 Asia/Shanghai 自然周（当前系列通常为周一到周日）。不要继续套用早期“周四到周四”的历史示例，除非用户明确要求复现那段旧口径。
- 需要和历史对比时，显式列出每个窗口，不要使用含糊的“本周/上周”。

示例周窗口：

| Label | Start | End |
|---|---|---|
| 05/28 | 2026-05-28 00:00 Asia/Shanghai | 2026-06-04 00:00 Asia/Shanghai |
| 06/04 | 2026-06-04 00:00 Asia/Shanghai | 2026-06-11 00:00 Asia/Shanghai |
| 06/11 | 2026-06-11 00:00 Asia/Shanghai | 2026-06-18 00:00 Asia/Shanghai |
| 06/18 | 2026-06-18 00:00 Asia/Shanghai | 2026-06-25 00:00 Asia/Shanghai |

## 日粒度 JSON 输入

后续用户可以每天上传从 prod 拉下来的 JSON。建议保留 raw JSON 和 derived JSON 两层：

```text
outputs/usage_reports/YYYY-MM-DD/
  raw_usage.json
  raw_workspaces.json
  raw_messages.json
  raw_token_usage.json
  derived_daily_usage.json
```

### raw_usage.json

`raw_usage.json` 保存从 Grafana/Prometheus 或 backend API 拉到的指标原始结果。字段可以保留 datasource 原始结构，但至少要能解析出这些信息：

```json
{
  "date": "2026-07-03",
  "timezone": "Asia/Shanghai",
  "window_start": "2026-07-03T00:00:00+08:00",
  "window_end": "2026-07-04T00:00:00+08:00",
  "source": "grafana/prometheus",
  "metrics": {
    "total_acp_interactions": 0,
    "total_cost": 0,
    "users": [],
    "workspaces": []
  }
}
```

`metrics.users[]` 建议字段：

| Field | 含义 |
|---|---|
| `user_email` | metric 中的 email，lowercase + trim 后用于 join |
| `channel` | `web` / `lark` / `cli` / `eval` |
| `acp_interactions` | 当日 ACP 交互数 |
| `cost` | 当日成本 |

`metrics.workspaces[]` 建议字段：

| Field | 含义 |
|---|---|
| `workspace_id` | workspace id |
| `user_email` | owner 或主要使用者 |
| `channel` | 入口 |
| `cost` | 当日该 workspace 成本 |
| `acp_interactions` | 如果可取得则填；Prometheus 当前可能没有 workspace 维度 interactions |

### raw_messages.json

`raw_messages.json` 保存 workspace chat 明细。建议结构：

```json
{
  "date": "2026-07-03",
  "timezone": "Asia/Shanghai",
  "messages_by_workspace": {
    "ws-xxx": [
      {
        "id": "msg-xxx",
        "role": "user",
        "created_at": "2026-07-03T10:12:00+08:00",
        "content": "用户消息文本",
        "parts": []
      }
    ]
  }
}
```

如果 raw message 很大，可以只上传脱敏后的 derived message summary，但需要保留：

| Field | 含义 |
|---|---|
| `workspace_id` | workspace id |
| `message_id` | message id |
| `role` | `user` / `assistant` / `tool` / other |
| `created_at` | message 时间 |
| `content_text` | 脱敏后的文本，或截断文本 |
| `content_chars` | 文本长度 |
| `tool_name` | tool message 可选 |
| `tool_status` | tool message 可选 |

### raw_token_usage.json

如果能从后端 `interaction_token_usage` 拉到 token usage，单独保存：

```json
{
  "date": "2026-07-03",
  "timezone": "Asia/Shanghai",
  "records": [
    {
      "workspace_id": "ws-xxx",
      "session_id": "session-xxx",
      "created_at": "2026-07-03T10:12:00+08:00",
      "input_tokens": 0,
      "output_tokens": 0,
      "reasoning_tokens": 0,
      "cache_read_tokens": 0,
      "cache_write_tokens": 0,
      "cost_amount": 0
    }
  ]
}
```

注意：token usage 通常是 session/interaction 级别，不一定能精确到单条 user message。按 message 分类估算 token 时必须标记为 proxy。

## 成本口径

当前 daily JSON、前端周报图表、飞书周报里的 cost 均以 prod backend SQLite `/data/db/investibook.db` 中的 `interaction_token_usage` 为准。

`interaction_token_usage` 需要按 runtime 语义计算：

- OpenCode 的 `session_id` 以 `ses_` 开头，记录是 session 累计快照。日成本计算为 `latest_before_day_end - latest_before_day_start`。
- PI 的 `session_id` 为 UUID 形态，记录是每轮增量。日成本计算为当天窗口内所有 PI 行求和。
- backend 自有任务如 `image-parse:*` 也是增量行，按当天窗口内行求和。

`investibook_acp_session_cost` 仍可用于历史排查或监控参考，但在 PI/OpenCode 混用期间不能作为周报成本主口径，因为 PI gauge 语义可能低估成本。`daily_summary.summary.total_cost`、`users[].cost`、`teams[].cost`、`workspaces[].cost`、`daily_delta.cost` 和 `token_usage.cost_amount` 必须来自同一套 runtime-aware `interaction_token_usage` 贡献。

实时 Grafana 大盘使用 `investibook_acp_interaction_cost_total` counter 展示 live cost；该 metric 由 backend 在写入 `interaction_token_usage` 时按同一 runtime-aware contribution 增加。历史 backfill 和飞书周报仍以 DB/daily JSON 为准，不用 Prometheus 回推成本。

## PromQL 口径

总 ACP 交互次数：

```promql
sum(increase(investibook_acp_session_interactions_total[<range>]))
```

按用户 ACP 交互次数：

```promql
topk(1000, sum by (user_email) (increase(investibook_acp_session_interactions_total{user_email!="unknown"}[<range>])))
```

按用户和入口统计交互：

```promql
topk(2000, sum by (user_email, channel) (increase(investibook_acp_session_interactions_total{user_email!="unknown"}[<range>])))
```

实时成本大盘：

```promql
sum(increase(investibook_acp_interaction_cost_total[<range>]))
```

按用户实时成本：

```promql
topk(1000, sum by (user_email) (increase(investibook_acp_interaction_cost_total{user_email!="unknown"}[<range>])))
```

按 workspace/user/channel 实时成本：

```promql
topk(10000, sum by (workspace_id, user_email, channel) (increase(investibook_acp_interaction_cost_total{workspace_id!="unknown",user_email!="unknown"}[<range>])))
```

注意：`investibook_acp_session_interactions_total` 当前没有 `workspace_id` 标签，所以 workspace 维度从 `interaction_token_usage` 的正成本行里拿。报表里的 `WS Count` 表示有正成本的 distinct workspace 数，不等同于所有发生过交互的 workspace。

## Team 映射

基础成员表：

```text
/Users/lingtaoli/investibook/pingcap_members_by_team.csv
```

列包括：

```text
team_path,team,department_id,name,en_name,email,job_title,employee_type,is_primary_dept,status
```

补充映射表：

```text
docs/guides/investibook-usage-team-overrides.csv
```

历史活跃使用者映射快照：

```text
docs/guides/investibook-usage-team-mapping-snapshot-20260528-20260625.csv
```

每次跑日报/周报都必须重新读取 `pingcap_members_by_team.csv`。这个文件是让新用户自动找到 team 的主数据源；不要用历史 snapshot 当主数据源。

处理顺序：

1. 先按 `pingcap_members_by_team.csv` 的 `email` 精确匹配。
2. 再按 override CSV 的 `usage_email` 覆盖或补齐。
3. 输出所有未匹配用户，按成本和交互次数排序。
4. 只要存在 unknown / unmatched user，就先把列表贴给人类 reviewer，确认是否补 override；不要把 unknown 当成正常数据静默发布。
5. 如果未匹配成本超过总成本 1%，或未匹配成本超过 `$5`，这是 hard gate：不要发布 team 趋势，必须先补 mapping 并重跑。

说明：`pingcap_members_by_team.csv` 是成员/team 基础表；override CSV 是长期维护的补丁表，解决 metric 里的 `user_email` 和成员表 email 不一致、成员表缺 email、历史人员数据不完整等问题；snapshot CSV 不是全员表，而是某段历史分析中“实际出现在 usage 明细里的活跃使用者”在合并 base CSV 和 override 后的映射结果，适合复现 `2026-05-28` 到 `2026-06-25` 的历史报表。

新用户匹配逻辑：

1. 对 usage metric 里的 `user_email` 做 lowercase + trim。
2. 在成员表里用 lowercase 后的 `email` 精确匹配，并优先使用 `is_primary_dept=yes`、`status=active` 的记录。
3. 如果同一 email 有多条 active 记录，优先 primary dept；仍然多条时在 unknown/ambiguous 报告里列出来，不要随便猜。
4. 如果成员表找不到，再查 override CSV。
5. 仍然找不到时，输出 `unmatched_users`，包含 email、cost、interactions、workspace count、top workspace，方便人工补 mapping。

日报 JSON 发布 gate：

1. 每次 collector 生成 `outputs/usage_daily_reports/YYYY-MM-DD/daily_report.json` 后，检查 `summary.unknown_user_count`、`unmatched_users` 和 `teams[].team_path == "(unknown)"`。
2. 如果任一日期出现 unknown，先生成一个人工反馈列表：email、cost、ACP interactions、workspace count、top workspace，以及从 `pingcap_members_by_team.csv` 中按姓名/邮箱 alias 找到的候选 team。
3. 把反馈列表贴给人类确认；确认后补 `docs/guides/investibook-usage-team-overrides.csv`，重跑受影响日期，再同步到 `frontend/data/usage_daily_reports/`。
4. 只有人类明确接受“保留 unknown”，且影响低于阈值时，才可以发布；同时必须在 report notes 里记录剩余 unknown。

历史经验：team 统计一旦漏掉高使用用户会整体失真。Debin 的归属漏匹配时，Storage/KV 的趋势曾被明显低估。

截至 2026-06-30 的已知未解决用户：

| Email | 影响 |
|---|---|
| shenjing.zhao@pingcap.com | 2 rows, total cost about $1.86 |

## 输出文件建议

兼容/原始派生产物可以写到：

```text
outputs/usage_reports/YYYY-MM-DD/
```

前端和周报建议优先读取可递增的 split 目录：

```text
outputs/usage_daily_reports/
  YYYY-MM-DD/
    daily_summary.json
    daily_report.json
    workspace_changes.json
    chart_ready_usage.json
  daily_cost_acp_series.json
  workspace_index.json
  workspace_mode_classification.json
  profile_spend_by_usedby.json
  feedback_demands.json
```

如果前端看板使用静态 JSON，把同一棵目录机械复制到：

```text
frontend/data/usage_daily_reports/
```

`outputs/usage_daily_reports/` 是生成源；`frontend/data/usage_daily_reports/` 是前端静态数据镜像。不要手工改镜像 JSON，避免源目录和前端目录口径漂移。

每日目录建议至少包含：

| File | 内容 |
|---|---|
| `daily_summary.json` | 日期、总成本、ACP interactions、活跃用户数、WS count、unknown mapping summary |
| `daily_report.json` | 当日 team/user/category/chart 汇总，引用 `workspace_changes.json` |
| `workspace_changes.json` | 当日新建 workspace 和已有但当天有 message/token/cost 变化的 workspace |
| `chart_ready_usage.json` | 当日图表 payload |

根目录 supplemental 文件：

| File | 内容 |
|---|---|
| `daily_cost_acp_series.json` | 扫描已有 daily summary 重建的跨日 cost/ACP series |
| `workspace_index.json` | 每日 workspace changes 的可递增索引 |
| `workspace_mode_classification.json` | 从 workspace 活跃天数、消息量、成本等信号派生的模式分类 |
| `profile_spend_by_usedby.json` | 从 Helix API key assignment spend detail 拉取的 Profile/UsedBy 成本 |
| `feedback_demands.json` | 需求反馈列表占位；来自飞书/会议纪要等人工整理，不属于 usage metric |

不要把固定窗口文件（例如 `workspace_usage_2026-06-25_to_2026-07-06.json`）作为前端主数据源提交。新增一天时它无法自然扩充；需要窗口聚合时应从 `workspace_index.json` 引用的 daily `workspace_changes.json` 动态构建。

`workspace_changes.json` 里的每个 workspace 行直接携带场景分类字段：

- `scenario_group`
- `scenario_category`
- `scenario_confidence`
- `scenario_classification_method`
- `scenario_secondary_categories`
- `scenario_evidence`

当前场景分类是基于 workspace title、user/system message 上下文和 user message category 的保守规则，不等价于人工审核结论。上线图表可以直接使用 `scenario_group/scenario_category` 做主分类，用 `scenario_confidence` 和 `scenario_evidence.review_required` 标记需要人工复核的 workspace。

上传或更新 PR 时，除了 JSON 数据，还必须包含生成契约：

- `tools/usage_reporting/collect_daily_usage.py`
- `tools/usage_reporting/message_category_manual_overrides.json`
- `agentkit/skills/builtin/investibook-usage-reporting/SKILL.md`
- `docs/guides/2026-07-03-investibook-usage-reporting.zh.md`
- `outputs/usage_daily_reports/`
- `frontend/data/usage_daily_reports/`（当前端读取静态数据时）

历史分类或 DB 派生字段回刷时，如果 Clinic/Grafana 查询过慢，可以复用已有 split daily JSON 中已经采集过的 ACP interaction：

```bash
PYTHONPATH=/tmp/codex-pycookiecheat-20260630 \
/Users/lingtaoli/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3 \
tools/usage_reporting/collect_daily_usage.py \
--start YYYY-MM-DD \
--end YYYY-MM-DD \
--reuse-existing-prometheus
```

这个模式只适合已有日期的历史回刷：成本、token、workspace、team、message 分类仍从 prod SQLite 重新生成；ACP interaction 沿用现有 daily JSON，不重新打 Grafana。

发布前至少检查：

```bash
python3 -m py_compile tools/usage_reporting/collect_daily_usage.py
python3 - <<'PY'
import json
from pathlib import Path
for path in Path("outputs/usage_daily_reports").rglob("*.json"):
    json.loads(path.read_text())
PY
```

同时确认 `workspace_index.json`、`daily_cost_acp_series.json` 和前端静态镜像覆盖相同日期范围。

## derived_daily_usage.json

`derived_daily_usage.json` 是前端图表和飞书文档的共同输入。推荐 schema：

```json
{
  "date": "2026-07-03",
  "timezone": "Asia/Shanghai",
  "window_start": "2026-07-03T00:00:00+08:00",
  "window_end": "2026-07-04T00:00:00+08:00",
  "summary": {
    "total_cost": 0,
    "total_acp_interactions": 0,
    "active_users": 0,
    "workspace_count": 0,
    "message_count": 0,
    "user_message_count": 0,
    "assistant_message_count": 0,
    "unknown_user_count": 0,
    "unknown_cost": 0
  },
  "teams": [],
  "users": [],
  "workspaces": [],
  "message_categories": [],
  "charts": {}
}
```

`teams[]` 推荐字段：

| Field | 含义 |
|---|---|
| `team_path` | 完整 team path |
| `team` | 展示名 |
| `cost` | 当日成本 |
| `acp_interactions` | 当日 ACP |
| `workspace_count` | distinct workspace 数 |
| `active_users` | distinct user 数 |
| `message_count` | message 总数 |
| `user_message_count` | user message 数 |
| `cost_per_ws` | `cost / workspace_count` |
| `cost_per_acp` | `cost / acp_interactions` |
| `acp_per_ws` | `acp_interactions / workspace_count` |
| `message_per_ws` | `message_count / workspace_count` |
| `message_per_acp` | `message_count / acp_interactions` |
| `correction_message_ratio` | `(诊断纠偏 + 查询纠偏) / user_message_count` |

`users[]` 推荐字段：

| Field | 含义 |
|---|---|
| `user_email` | 用户 email |
| `name` | 中文名 |
| `en_name` | 英文名 |
| `team_path` | team path |
| `cost` | 当日成本 |
| `acp_interactions` | 当日 ACP |
| `workspace_count` | distinct workspace 数 |
| `message_count` | message 总数 |
| `user_message_count` | user message 数 |
| `frequency_bucket` | 高频/低频 |
| `depth_bucket` | 高轮次/低轮次 |
| `behavior_mode` | 高频高轮次/高频低轮次/低频高轮次/低频低轮次 |

`workspaces[]` 推荐字段：

| Field | 含义 |
|---|---|
| `workspace_id` | workspace id |
| `title` | workspace title |
| `url` | workspace URL |
| `user_email` | owner 或主要使用者 |
| `team_path` | team path |
| `channel` | 入口 |
| `cost` | 当日成本 |
| `acp_interactions` | 当日 ACP，如不可得可为空 |
| `message_count` | message 总数 |
| `user_message_count` | user message 数 |
| `message_categories` | user message 分类计数 |
| `scenario_category` | 问题场景分类 |
| `is_inspection` | 是否巡检类 workspace |
| `quality_notes` | 人工或 agent 生成的简短质量备注 |

`message_categories[]` 推荐字段：

| Field | 含义 |
|---|---|
| `category` | message 分类 |
| `user_message_count` | user message 数 |
| `share` | 占全部 user message 比例 |
| `estimated_cost_by_turn` | 按 user turn 平均分摊的成本 |
| `estimated_cost_by_output_weight` | 按后续 assistant 输出长度分摊的成本 |
| `avg_user_chars` | 平均 user message 长度 |
| `avg_next_assistant_chars` | 该 user message 后到下一条 user message 之间的 assistant 输出平均长度 |

`charts` 推荐直接输出前端可画图的 series，避免前端再做复杂 join：

```json
{
  "charts": {
    "daily_cost_acp": [
      { "date": "2026-07-03", "cost": 0, "acp_interactions": 0 }
    ],
    "team_cost_bar": [
      { "team": "GSI / Intelligent Support", "cost": 0, "acp_interactions": 0 }
    ],
    "team_ratio_scatter": [
      { "team": "GSI / Intelligent Support", "x": 0, "y": 0, "size": 0 }
    ],
    "message_category_stack": [
      { "team": "GSI / Intelligent Support", "category": "Ad hoc 查询", "count": 0 }
    ],
    "user_behavior_quadrant": [
      { "user_email": "user@pingcap.com", "x": 0, "y": 0, "cost": 0, "mode": "高频高轮次" }
    ]
  }
}
```

## 周报结构

当前飞书周报优先沿用以下结构。每个主章节都需要 `Summary`，不要只有图表或表格。

1. `本周总结`
   - 写清统计周期、窗口级去重 workspace、用户消息、用户数、workspace 窗口成本、daily trend 加总成本。
   - 与前 1-2 期显式对比 cost、ACP interactions、daily workspace rows、user messages、unique users。
   - 如果存在内部批量干扰项（例如某个用户批量 CLI workspace），先计算影响；按用户要求可在 Summary 中给出剔除后口径。
2. `数据口径`
   - 说明 daily trend 来自 `daily_report.json` / `daily_summary.json` 按天加总。
   - 说明 workspace、team、user、mode、top workspace 使用 `workspace_index.json` + daily `workspace_changes.json` 动态聚合，按 `workspace_id` 去重。
   - 明确场景分布是 workspace 多标签计数：一个 workspace 命中多个 `scenario_category_counts` 时，每个标签各 +1。
3. `开发成本统计`
   - `profile_spend_by_usedby.json` 是 Helix Profile/UsedBy 月累计口径，不要和 usage collector 的周窗口成本混写。
4. `日使用趋势`
   - Summary 必须对比前几周，而不是只描述周内峰谷。
   - 表格至少包含日期、cost、ACP interactions、active users、workspace rows、user messages。
5. `用户画像统计`
   - Summary 要突出每个高用量团队里的关键个人，而不是只列团队总量。
   - 包含 `Top 团队` 和 `Top 用户` 两张表。
6. `行为模式分析`
   - `消息类别分布`：说明 `Ad hoc 查询`、`诊断启动`、`产出物迭代`、`背景补充`、`未归类` 等类别变化。
   - `Workspace 使用模式`：保留一张趋势表，按模式展示最近几周的 `avg user msg/WS` 和当周总成本；不要再加“本周 / 全体均值”列。
7. `Workspace 分析`
   - `高成本 Workspace` 表必须包含 workspace 链接、用户、团队、模式、成本、user messages、主要用途。
   - `Top 5 高成本详情` 必须读取实际 chat message / token usage 后再写，说明问题描述、成本原因、Investibook 是否帮助用户得到想要的结果；不要只根据 title 猜。
   - `本周场景分布` 同时写多标签分布和 primary group（诊断 / 非诊断 / unknown）总体占比。
   - `非诊断使用` 先用环形/总体占比说明非诊断占全部 WS，再用条状表说明非诊断内部分类。
8. `覆盖率分析`
   - 只有在本次已经重新采集 eval / ticket coverage JSON 或可靠图源时才写新数字。
   - 如果没有新 coverage 数据，明确写“本期未重新采集，不复用旧图/旧口径”，不要把上一期截图当成本期结论。

周报正文只写 derived 结论。raw 数据、长 JSON、长日志放附件、本地输出文件或仓库 JSON，不直接写进正文。

### 写飞书周报的执行顺序

1. 确认窗口：优先按用户给的上一期周报日期顺延；否则使用最近完整 7 天自然周。所有日期用绝对日期。
2. 跑 collector 生成/刷新 daily JSON，并检查 `unmatched_users`、`unknown_user_count`、`teams[].team_path == "(unknown)"`。
3. 如有 unknown，高影响时先把候选归属贴给人类确认；确认后补 `investibook-usage-team-overrides.csv` 并重跑受影响日期。
4. 验证 JSON：所有 `outputs/usage_daily_reports/**/*.json` 可解析，`workspace_index.json`、`daily_cost_acp_series.json`、前端镜像日期范围一致。
5. 如果这批数据要上线看板，提交数据 PR：`outputs/usage_daily_reports/`、`frontend/data/usage_daily_reports/`、team override 和必要的 collector/skill/doc 改动一起提交。
6. 写飞书周报：从 daily JSON 动态聚合窗口数据；Top 5 workspace 详情必须回查 prod DB 的 `messages_v2` 和 `interaction_token_usage`，只总结关键消息，不贴大段原文。
7. 飞书写入后 fetch outline 做结构校验，确认主章节齐全。

## 图表建议

- 每日成本和 ACP interactions 双轴折线图。
- 每周 team interactions 堆叠柱状图。
- 每周 team cost 柱状图。
- 用户 active weeks 直方图。
- 用户 WS Count 直方图。
- single-week peak share 直方图。
- Top workspace cost 表格或横向柱状图。

## ACP Interactions 与 Message Count 的区别

ACP interactions 和 chat message 次数不一定成正比。

- `ACP interactions` 表示后端 ACP/OpenCode 完成的一次 agent 交互或回合，更接近真实 agent 使用量。
- `message count` 更接近前端或聊天记录层的消息数量，可能包含用户消息、助手文本、工具事件、重试、系统事件等。

成本、模型消耗、team 使用趋势应优先使用 ACP interactions、cost、active weeks、WS Count。message count 只能作为辅助解释。

常用比值：

| Ratio | 解释 |
|---|---|
| `cost / ws` | 单个 workspace 的平均成本，越高越偏复杂深挖或长产出 |
| `cost / acp` | 单次 ACP 平均成本，越高说明每轮上下文、工具结果或输出更重 |
| `acp / ws` | 单 workspace 平均轮次，衡量对话深度 |
| `message / ws` | 单 workspace 消息密度，衡量 chat 层复杂度 |
| `message / acp` | chat 消息与 agent 交互的相对比例，用于发现流式/tool/event 对 message count 的放大 |
| `correction / user_message` | 用户纠偏比例，衡量 agent 初始结论或证据口径是否稳定 |

历史 top team 分析中，`ACP` 和 `message` 大致不是 1:1，而是约 `1 ACP ~= 2.2-2.3 messages`。这个比例只能作为历史参考，每天仍应从实际数据计算。

## Message 分类

只分类 user message。assistant 和 tool message 用来辅助估算后续输出长度和 token proxy。

分类优先级建议：

1. `查询纠偏`
2. `诊断纠偏`
3. `产出物迭代`
4. `Ad hoc 查询`
5. `诊断启动`
6. `解释追问`
7. `流程控制/确认`
8. `方法论/能力咨询`
9. `背景补充`
10. `未归类`

分类定义：

| Category | 定义 | 典型信号 |
|---|---|---|
| `Ad hoc 查询` | 用户要求查 SQL、日志、指标、workspace、Jira、Clinic、Grafana、cluster 等证据 | “查一下日志”、“拉一下数据”、“这个 SQL”、“看下 Grafana” |
| `诊断启动` | 用户要求开始分析、定位、判断原因、给 RCA 或建议 | “分析下原因”、“帮我诊断”、“根因是什么” |
| `诊断纠偏` | 用户推翻或质疑 agent 的诊断逻辑、因果链、结论 | “这个结论不对”、“不能这么推”、“逻辑不成立” |
| `查询纠偏` | 用户指出查询口径、数据源、过滤条件或证据读取有误 | “口径错了”、“实际有错误日志”、“应该查另一个 datasource” |
| `解释追问` | 用户要求解释机制、关系、为什么，不一定推翻结论 | “为什么会这样”、“这个机制是什么”、“两者什么关系” |
| `产出物迭代` | 用户要求总结、表格、报告、飞书文档、客户回复、prompt、翻译润色 | “写成 md 表格”、“同步到飞书”、“给客户的回复” |
| `流程控制/确认` | 用户推进流程或确认下一步 | “继续”、“先这样”、“再查一下”、“可以开始” |
| `方法论/能力咨询` | 用户询问 Investibook 能力边界、通用方法、排查流程或如何使用 agent 能力 | “你支持 subagent 吗”、“一般怎么判断”、“排查流程是什么” |
| `背景补充` | 用户单独贴上下文材料，包括截图、链接、SQL、error/log、堆栈、case/file/image ID、带标签的 cluster/org/table ID、时间窗、长文本或补充背景，不直接表达下一步动作 | “Uploaded image”、“Cluster ID ...”、“APID-29335 和 GTOC-8688”、单独贴一段 SQL/error/log |
| `未归类` | 单条消息缺少足够动作或上下文，当前规则无法稳定判断；重生 JSON 前应先抽样检查真实未归类消息，只补高置信规则，不强行清零 | “这个呢”、极短指代、无法判断意图的片段 |

说明：

- `背景补充` 不是低质量问题，代表用户在递交证据或补充上下文；单独贴链接、SQL、error/log 时优先归入此类。
- 短 follow-up 要结合动作判断：翻译、精简、Markdown/table 改写、回答草稿、客户回复重写归 `产出物迭代`；retry/new/continue/go ahead 归 `流程控制/确认`。
- 日文/英文问句和 mixed-language 查询要覆盖常见表达，例如 `ですか`、`ますか`、`か` 结尾、`enabled by default`、`give me the doc reference` 等；提到 cluster/log/SQL/CPU/TiDB/TiKV/Grafana/CDC/analyze/binding 等证据对象时优先归 `Ad hoc 查询`。
- 对规则难以稳定覆盖、但已经人工确认的短句或 mixed-language 表达，写入 `tools/usage_reporting/message_category_manual_overrides.json`；collector 会先应用 manual overrides，再走关键词和 fallback。不要把人工 override 用成兜底垃圾桶，只收高置信样本。
- `未归类` 应保持为最后兜底；新增规则前要先检查样本，避免把明确查询或诊断误收到背景类。
- 对未命中固定关键词但明显带问号的消息，可使用兜底规则：包含 cluster/log/sql/cpu/tikv/tidb/metric 等证据词时归入 `Ad hoc 查询`，否则归入 `解释追问`。
- 分类总和必须等于 user message 总数。若报表只展示部分分类，需要显式写明隐藏了哪些类别。
- 当一条消息同时命中多类，按优先级取最高类。例如“你查错了，重新拉日志”归入 `查询纠偏`，不是 `Ad hoc 查询`。

## Message 分类与 Token 消耗

不要直接宣称某一类 message 的精确 token 成本，除非后端已经按 message 记录 usage。当前推荐三种 proxy：

| Proxy | 公式 | 适用场景 |
|---|---|---|
| 按轮次分摊 | `workspace_cost / user_message_count` | 粗看每类总成本 |
| 按输出长度分摊 | `workspace_cost * (next_assistant_chars + 0.25 * user_chars) / workspace_total_weight` | 判断哪类更拉长输出 |
| 按 token usage 分摊 | 如果能拿到 interaction token，按 interaction 对应 user turn 归因 | 最接近真实，但要求数据链路支持 |

解释时同时给两个结论：

- 总量贡献：哪类因为频次高，累计消耗最大。
- 单次消耗：哪类平均每条更贵。

历史经验：

| Category | 总量倾向 | 单条倾向 | 解释 |
|---|---|---|---|
| `Ad hoc 查询` | 高 | 中-高 | 高频、常带 tool 结果，最容易堆出总成本 |
| `诊断启动` | 高 | 高 | 需要读上下文、证据和做完整推理 |
| `产出物迭代` | 中 | 高 | 报告、客户回复、表格输出长 |
| `诊断纠偏` | 中 | 中-高 | 如果触发重查，会明显变贵 |
| `查询纠偏` | 中 | 高风险 | 查错口径后重跑证据链，容易重复消耗 |
| `解释追问` | 中 | 中-高 | 短问题可能触发长机制解释 |
| `流程控制/确认` | 低 | 低 | 本身短，除非触发下一轮重任务 |
| `方法论/能力咨询` | 低-中 | 中 | 通常是能力或方法解释，可能触发较长说明 |
| `背景补充` | 中 | 低-中 | 多为证据输入，成本取决于后续 agent 是否展开读取 |
| `未归类` | 不稳定 | 不稳定 | 混合了极短指代和无法稳定判断的片段 |

## Team 行为分析

分析 team 时不要只看 cost 排名。至少同时看：

| 指标 | 行为含义 |
|---|---|
| `ws_count` 高、`cost/ws` 低 | 高频轻量使用，可能是日常工作台 |
| `ws_count` 低、`cost/ws` 高 | 少数复杂 case 或长报告 |
| `acp/ws` 高 | 多轮深挖，用户与 agent 反复推进 |
| `message/ws` 高 | chat 交互密度高，可能有大量解释、纠偏或产出迭代 |
| `cost/acp` 高 | 单轮很重，通常有大量上下文、工具输出或长文本生成 |
| `correction_message_ratio` 高 | 诊断或查询口径需要改进，用户经常推翻或校准 agent |

Top cost team 必须输出两层解释：

1. `为什么花得多`：高频、深轮次、单轮重、还是产出物多。
2. `这代表什么行为习惯`：日常查询工作台、专家深挖、客户 case 推进、巡检批处理等。

推荐 team 行为模式：

| Mode | 判定信号 | 解释 |
|---|---|---|
| `日常工作台型` | WS 多、cost/ws 低、中低轮次、Ad hoc 查询多 | 用户把 Investibook 当日常查询入口 |
| `专家深挖型` | WS 少或中等、cost/acp 高、解释追问和诊断启动多 | 单个问题技术深度高 |
| `客户 case 推进型` | 产出物迭代多、message/ws 高、纠偏不一定高 | 用于客户回复、Jira、case 生命周期推进 |
| `纠偏校准型` | 诊断纠偏和查询纠偏比例高 | 用户频繁修正 agent 结论或证据口径 |
| `巡检批处理型` | workspace title/source 命中巡检，结构化检查多 | 不能和普通 ad hoc case 混在一起做 top cost 解读 |

## 用户行为画像

用户画像按时间窗口计算。日报可以用当天阈值，周报/月报用窗口内聚合阈值。

基础指标：

| 指标 | 含义 |
|---|---|
| `active_days` | 窗口内活跃天数 |
| `workspace_count` | distinct workspace 数 |
| `acp_interactions` | ACP 交互数 |
| `user_message_count` | user message 数 |
| `cost` | 成本 |
| `avg_acp_per_ws` | `acp_interactions / workspace_count` |
| `avg_message_per_ws` | `message_count / workspace_count` |
| `single_day_peak_share` | 最大单日成本或 ACP / 窗口总量 |

四象限：

| Mode | 频率 | 轮次 | 解释 |
|---|---|---|---|
| `高频高轮次` | workspace 或 active_days 高 | avg_acp_per_ws 高 | 稳定重度用户，常做复杂分析 |
| `高频低轮次` | workspace 或 active_days 高 | avg_acp_per_ws 低 | 高频轻量查询用户 |
| `低频高轮次` | workspace 或 active_days 低 | avg_acp_per_ws 高 | 偶发复杂 case 用户 |
| `低频低轮次` | workspace 或 active_days 低 | avg_acp_per_ws 低 | 试用、偶发或轻量使用 |

阈值建议：

- 日报：用当天用户分布的 median 或 P60 区分高低频；用 `avg_acp_per_ws >= 4` 或当天 P60 区分高低轮次。
- 周报：高频可用 `workspace_count >= 5` 或 `active_days >= 2`；高轮次可用 `avg_acp_per_ws >= 4`。
- 如果样本量小于 20 个用户，不要硬套固定阈值，改用排名和分位数解释。

## Workspace 质量分析

Top workspace 分析分三步：

1. 先按 cost 找 top workspace，同时标记 `is_inspection`。
2. 如果用户要求“去掉巡检”，必须排除 title/source/skill 命中巡检类的 workspace。
3. 对每个 top workspace 读取 chat message，判断 chat 质量和最终结果。

质量问题维度：

| 维度 | 判断问题 |
|---|---|
| 结论稳定性 | 用户是否持续推翻 agent 前序结论 |
| 证据口径 | 是否查错 datasource、cluster、时间窗、日志源、SQL 条件 |
| 诊断逻辑 | 是否从现象跳到根因，中间缺证据 |
| 工具稳定性 | 是否有 Loki、MCP、API、503、权限或 pod 重启导致失败 |
| 用户成功度 | 用户最终是否拿到想要的结论、数据或可发出的材料 |

Top workspace 输出模板：

| Workspace | User | Team | Cost | Message/WS | 主要任务 | Chat 质量 | 用户是否成功 | 问题类型 |
|---|---|---|---:|---:|---|---|---|---|

## 问题场景分类

诊断类 workspace 可以参考：

```text
agentkit/skills/builtin/tidb-clinic-diagnosis/root-cause-classification/SUBSKILL.md
```

当前可归类场景：

| Category | 含义 |
|---|---|
| `sql_plan_or_query` | SQL、执行计划、慢查询、查询写法 |
| `ddl_or_schema_change` | DDL、schema change、DROP/ALTER、DDL job |
| `session_or_process_management` | PROCESSLIST、连接 ID、KILL 会话、会话管理 |
| `transaction_or_lock_contention` | 事务、锁等待、冲突 |
| `tikv_hotspot_or_coprocessor` | TiKV hotspot、coprocessor、region 分布 |
| `storage_or_io_bottleneck` | 存储、IO、磁盘瓶颈 |
| `backup_or_restore` | Backup、Restore、PITR、BR |
| `data_import_or_load` | ImportInto、Import jobs、CSV/LOAD DATA、数据导入失败 |
| `memory_or_oom_pressure` | 内存、OOM |
| `gc_or_allocator_pressure` | Go GC、allocator、runtime 压力 |
| `resource_control_or_capacity` | RU、资源管控、容量不足 |
| `pd_tso_or_scheduling` | PD、TSO、调度 |
| `cdc_or_replication_lag` | CDC、同步延迟 |
| `network_or_connectivity` | 网络、连接 |
| `component_failure_or_bug` | 组件故障、bug |
| `cluster_topology_or_scaling` | 集群拓扑、扩缩容、standby/witness、物理/逻辑集群 |
| `inspection_or_report_generation` | 巡检、健康检查、日报/周报/report 生成 |
| `skill_or_workflow_setup` | skill、workflow、runbook、opencode 等使用流程配置 |
| `eval_or_regression` | eval/regression/system-unavailability 等评测或回归任务 |
| `product_usage_or_admin` | 非 TiDB 诊断，偏 Investibook/飞书/权限/API key/Admin 配置；Lark 仅作为入口，不单独决定该类 |
| `ticket_summary_or_customer_response` | 工单总结、客户回复、邮件/升级材料 |
| `fleet_inventory_or_capacity_analysis` | 跨集群盘点、容量/存储/QPS/RU 指标统计 |
| `support_case_triage_or_customer_response` | 仅有 TCID/TCOC/GTOC/O11Y/ST/Jira case 信号但诊断主题不足 |

多主题和 unknown 的处理规则：

- 不再把 `multi_topic_diagnostic_workbench` 作为原始 `scenario_category` 写入 JSON。单个 workspace 命中多个诊断主题时，保留 primary `scenario_category`，并在 `scenario_category_counts` / `scenario_secondary_categories` 里给每个命中的标签计数；周报场景分布按这些标签各 +1。
- 不再把 `unknown_or_mixed` 作为原始 `scenario_category` 写入 JSON。信息不足时写 `scenario_group="unknown"`、`scenario_category=null`、`scenario_category_counts={}`；展示层可以把它显示成“未分类 / 混合场景”。
- 如果 primary 和 secondary 分数接近，或者命中三类以上诊断主题，设置 `scenario_evidence.review_required=true`，周报中作为人工复核队列说明。

场景分布需要附带“不在这些分类里面”的总结，尤其是产品使用、权限、报表、Feishu/Lark、前端链接、Admin 配置等 Investibook 自身问题。

## 飞书文档写入模板

周报通常在固定知识库目录下新建一篇 `Investibook 周报 MMDD ~ MMDD`。若用户给出上一期周报链接，优先在同目录新建下一期；不要覆盖上一期。写入推荐用 `lark-cli wiki +node-create` 创建 docx，再用 `lark-cli docs +update --command overwrite --content @file.xml` 写入正文，最后 `docs +fetch --scope outline` 校验结构。

当前正文结构见“周报结构”章节。历史“高阶数据分析”追加模板已经过时，只用于复现旧报告，不作为新周报默认结构。

飞书正文只写 derived 结论。raw 数据、长 JSON、长日志放附件或本地输出文件，不直接写进正文。

## 发布前检查

每次写周报前必须检查：

- 时间窗口是否是 Asia/Shanghai，并且起止时间写清楚。
- datasource 是否仍然是 `DevOps-prometheus` id `80`。
- metric 标签是否仍是 prod：`cluster="prod-devops"`、`namespace="investibook"`。
- cost 是否来自 prod SQLite `interaction_token_usage`，并按 OpenCode 累计快照差值、PI/image-parse 增量行求和。
- unknown users 是否已经经过人类反馈确认；高影响 unknown 是否已补 mapping 并重跑。
- WS Count 是否标注为 `interaction_token_usage` 正成本行推导。
- oncall 分析是否使用了正确名单和起止日期。
- Top workspace 是否读取了实际 chat message / token usage 后再判断“成本原因”和“是否有效果”。
- 覆盖率章节是否有本期新数据；没有新 coverage JSON 时只能写待补说明，不能复用旧图。
