# 录音日历：每日系统转写输入时长——后端交接

日期：2026-10-08。状态：前端已准备；接口合同待后端同事确认、实现、测试和部署，尚未完成真实时长联调。

## 1. 目标与分工

录音日历已经展示每日人声存储量、转写字数，本次追加「转写输入时长」，用于了解全天候录音的转写工作量、辅助核算单用户成本。默认使用自有云端 GPU 上的系统转写模型，无需为本功能调用豆包。

按以下流程交付：前端交互与显示准备 → 本文交接后端缺口 → 后端同事确认合同并实现、测试、部署 → 返回接口说明和样例 → 前端适配并用真实账号验收。

本次本地曾编写音频 API 和 ASR worker 参考实现，但**没有推送、合并或部署，也没有通过生产数据验收**。这些草稿不是已约定的后端方案，不要求同事采用其代码、数据库结构或消息协议。后端具体实现由后端同事决定。

## 2. 已有能力与需要补齐的内容

| 项目 | 现状与需求 |
| --- | --- |
| 日录音读取 | 插件已调用 `POST /api/v1/audio/one-day-trans`，请求含 `start_at`（本地日零点对应的 Unix 毫秒）与 `tz_offset`（时区偏移毫秒，东区为正）；复用既有鉴权及业务成功判断 |
| 存储、文字、完整录音范围 | 现有接口与前端已经支持；不属于本次新增后端需求 |
| 每条录音的 `duration` | 用于完整录音时间范围，不能直接认定为经过切窗、送入模型的音频量 |
| 说话人累计发言时间 | 不等于模型输入量，不能直接代替本字段 |
| 系统转写输入时长 | 需要后端提供权威时长及完整性/数据来源；先核对是否已有可复用数据，已有则补返回，不必从零重做采集 |
| 真实 Web 预览 | `3100` 已复用客户端登录态，可读真实录音；当前页面时长为暂不可用，只证明前端尚未拿到可用字段，不据此断言线上所有服务完全没有相关数据 |

## 3. 必须对齐的统计口径

- **度量对象**：归属于当前用户的录音，实际送入系统语音转写模型的音频时长。包含其中所有说话人的声音，不限于账号本人讲话。
- **不是文本长度**：输入了 1 小时、没有识别出文字，仍有 1 小时输入量；若系统在模型前过滤成 10 分钟，则按实际输入的 10 分钟计算。
- **与净发言时间分开**：模型窗口中实际包含的静音间隙也算输入。压缩后的 MB 数和说话人显示时长不能替代该口径。
- **日期归属**：按原音频在用户所选录音日内的时间片段归属，跨午夜精确截取。日界线跟随前端所选日期，不擅自使用服务器默认时区。
- **重复与重试**：接口重复返回/消息重复投递不重复累计。建议实际重复执行的转写输入分别累计，因为消耗了资源；请后端明确能覆盖的执行范围。无法覆盖的历史失败、重试不能假称完整。
- **来源**：这是系统转写量，切换查看豆包文本不应改变它。豆包用量统计不在本次范围内。
- **未知与零**：只有确认没有输入，才返回 0；处理中、旧数据缺依据、读取失败都不能当成 0。
- **历史数据**：优先使用已有可靠事实。估算是可选能力，必须明确标注 estimated 并说明算法/覆盖范围；不要求为了日历统计重跑历史录音。
- **成本边界**：该值辅助估算转写工作量，不等于 GPU 运行时间、完整云资源成本或用户套餐扣费时长；本次不要求修改计费系统。

## 4. 建议接口合同（可协商）

当前前端已经按以下建议字段准备了解析与显示。建议扩展既有日接口 `child_ls[]`，不新增逐条请求。如果后端已有等价字段或更合适的日汇总接口，请返回正式合同，前端可以适配，不必迁就本地草稿。

```json
{
  "asr_input_metrics": {
    "state": "ready",
    "basis": "observed",
    "duration_ms": 6000,
    "spans": [[1000, 4000], [3000, 6000]]
  }
}
```

| 字段 | 含义与约束 |
| --- | --- |
| `state` | `ready` 本口径内完整、`partial` 仅部分可统计、`processing` 尚在处理、`unavailable` 无可靠依据 |
| `basis` | 有数值时必填：`observed` 实测、`estimated` 估算。ready + estimated 仍然是估算 |
| `duration_ms` | 非负整数毫秒；等于所有 spans 长度之和。未知返回 null |
| `spans` | 相对 child 音频起点的 `[开始毫秒, 结束毫秒)`，用于前端跨日截取；每段需满足 `0 ≤ start < end ≤ child.duration` |

上例两个模型输入窗口有重叠，但确实分别输入过，因此总量是 6000ms，**不能取并集变成 5000ms**。不同录音文件也分别累计。

状态样例：

```json
{"state":"ready","basis":"observed","duration_ms":0,"spans":[]}
{"state":"partial","basis":"observed","duration_ms":3000,"spans":[[1000,4000]]}
{"state":"ready","basis":"estimated","duration_ms":3000,"spans":[[1000,4000]]}
{"state":"processing","duration_ms":null,"spans":null}
{"state":"unavailable","duration_ms":null,"spans":null}
```

要求保持当前录音可见权限，不让统计读取失败影响转写内容展示。请后端评估批量读取/聚合，避免每个 child 单独查库。

`one-day-trans-v2`、`get-child-by-ids` 如需参与刷新，建议保持同义字段一致；**本轮插件日历实际使用的是 `one-day-trans`**，不要只更新 V2 导致当前页面仍拿不到数据。

若改为后端返回每日合计，合同需明确请求日界线、归属过滤、跨日策略、完整性及实测/估算来源；前端再按正式方案调整。

## 5. 前端已准备的表现

- 在现有灰色统计行追加「转写输入时长 2小时18分36秒」，宽度不足自动换行。
- 历史估算显示「约…（含估算）」；部分实测显示已确认的部分并提示有数据尚不可确认。
- 加载中显示 `—`；字段缺失或未知显示「暂不可用」；确认没有录音/没有输入显示 0 秒。
- 当前 Web 仅更新独立预览目录，不修改已安装 Arkme 客户端。
- 当前解析代码：`src/recording-asr-input-metrics.ts`；展示代码：`src/client/recordings/RecordingDailyMetrics.tsx`。这些字段名是建议合同的适配结果，后端确认后仍需正式联调。

## 6. 请后端交付的内容

1. 是否已有可复用的输入时长数据；哪些数据需新增采集，哪些历史数据可以准确返回或估算。
2. 最终接口路径、字段、单位、鉴权、业务成功/错误码、完整性规则及重试统计范围。
3. 已部署的测试环境地址/版本，以及已完成、无文字、静音、跨日、处理中、失败/重试、历史缺字段等脱敏响应样例。
4. 历史数据覆盖范围和估算说明；如需回填，单独说明方案及影响，不默认授权批量重处理录音。
5. 后端验收结果及上线安排。只给源码或合并记录不能视为接口已可用；提供可读取的环境后前端开始真实数据对接。

## 7. 联调验收清单

| 场景 | 预期 |
| --- | --- |
| 输入音频有时长、识别文字为空 | 时长仍按实际输入统计 |
| 明确未输入模型 | 0；模型初始化前失败不得算成整段已输入 |
| 只有部分录音/尝试有数据 | 只展示已知部分，不能声称完整 |
| 跨午夜窗口 | 两天分配后之和等于该窗口输入量 |
| 重叠窗口/实际重试 | 按后端确认口径累计，重复响应不增加 |
| 历史数据 | 实测、估算、未知正确区分 |
| 不属于当前用户的录音 | 不进入该用户合计，沿用现有权限 |
| 切换系统/豆包文本 | 系统转写输入量保持一致 |
| 旧接口、读取失败、加载中 | 正确降级，存储和文字仍可查看 |
| 空日期 | 确认没有录音时显示 0，不把读取失败视为空日期 |

## 8. 本地参考草稿的边界

此前试写的音频 API / 消费者及 ASR 草稿仅存在于本机独立工作区，不包含在此次插件提交中，也未推送、合并或部署。已做过部分单元测试和编译，数据库及真实模型端到端测试未完成。后端同事可按需索取参考；具体设计及是否采用均由后端同事确认。
