同一个模型,外层卡片显示 83%,点进分组详情却看到 98%、100% 和 0%。这里的数字只是便于说明口径差异的确定性样例,并非线上实测数据。它们不一定说明数据错了,也不应该直接把详情页数字复制到外层。
真正需要确认的是:两层页面是否在回答同一个问题。外层可能展示分组健康度,详情可能展示真实请求成功率;外层可能读取 24 小时摘要,详情可能读取实时窗口。只要口径不同,数字就必然不同。
下面给出一套从数据源到 UI 的完整排查顺序。
第一步:给每个数字写出完整定义
不要只记录"成功率 83%",而要把指标写成一个可复算合同:
text
指标名称:模型分组健康度
统计对象:有请求样本的可用分组
时间窗口:最近 24 小时
聚合方法:每个分组先映射健康档位,再等权汇总
更新时间:客户端将查询结果视为新鲜数据 60 秒,服务端按时间桶聚合
详情页如果是另一套定义,也要完整写出:
text
指标名称:分组真实请求成功率
统计对象:该分组内所有有效请求
时间窗口:最近 24 小时
聚合方法:成功请求数 / 请求总数
这一步经常能直接发现:看起来相同的"成功率",其实一个是健康摘要,一个是按请求数计算的业务指标。
第二步:核对服务端的两条聚合链
当前工程里,模型总体成功率先合并请求计数,再用成功数除以请求数。这是按真实请求量加权的结果:
go
successRate := float64(total.successCount) / float64(total.requestCount) * 100
同时,服务端还会保留每个分组的独立成功率,避免高流量路线完全覆盖低流量路线的风险信号:
go
for _, group := range groups {
rates = append(rates, math.Round(successRate(groupTotals[group])*100)/100)
}
如果外层卡片使用 group_success_rates,详情页使用单个分组的 success_rate,两者本来就不是同一个字段。排查时先看接口响应,而不是先怀疑 CSS 或四舍五入。
第三步:检查前端是否做了二次转换
很多不一致来自前端把百分比再次映射成档位。例如当前逻辑中,90% 及以上映射成 6 格,70% 到 90% 映射成 4 格,低于 70% 映射成 2 格,然后再对各分组格数取平均。
ts
const averageBars =
validRates.reduce((total, rate) => total + getSuccessRateBarCount(rate), 0) /
validRates.length
const displayRate = (averageBars / 6) * 100
这类数字应叫"健康度"或"状态评分"。如果 UI 仍写"成功率",用户自然会拿它与详情页真实百分比比较。
修复方向是明确标签:
总体请求成功率:按所有请求计算;分组健康度:各分组等权反映风险;分组成功率:详情页单个分组的真实请求比例。
第四步:逐项排除窗口、缓存和过滤差异
如果两边理论上应该一致,再检查下面五项。
时间窗口
外层 24 小时、详情 1 小时,是最常见原因。还要检查窗口起点是滑动时间还是整点桶,以及服务器和浏览器是否使用同一时区。
历史数据补位
有些摘要在当前窗口没有数据时,会读取最近一条历史记录;详情页可能只展示当前窗口。此时外层应标记"历史",不能让用户误认为是实时状态。
缓存更新时间
服务端摘要、React Query 缓存和详情请求可能有不同的更新时间。记录接口返回时间、页面请求时间和缓存失效时间,避免拿一分钟前的卡片与刚刷新的详情比较。
请求过滤
一边排除取消请求,另一边保留;一边只统计已启用通道,另一边含已停用分组,都会改变分母。过滤条件应该进入指标合同和测试用例。
重试与去重
用户一次调用触发三次上游尝试时,模型卡片可能按最终用户请求统计,分组详情可能按每次尝试统计。应同时保存 request_id 与 attempt,明确哪一层去重。
第五步:用一组可手算数据做回归
不要用随机大数据验证聚合。准备三组确定样本:
| 分组 | 请求数 | 成功数 | 成功率 |
|---|---|---|---|
| A | 100 | 100 | 100% |
| B | 10 | 5 | 50% |
| C | 0 | 0 | 暂无数据 |
应该明确得到:
- 总体请求成功率为
105 / 110 = 95.45%; - 分组成功率列表为
[100, 50]; - C 不进入有效分组汇总;
- 如果健康度采用档位映射,结果必须以"健康度"展示,不冒充 95.45%。
这组样本适合同时保护后端聚合、接口 JSON 和前端格式化逻辑。
产品页怎么改才不误导
最小改法通常包括四项:
- 外层卡片把"成功率"改成"分组健康度";
- 悬浮说明写清等权或请求量加权;
- 详情页显示请求数,避免只有百分比没有分母;
- 历史补位、无样本和实时数据使用不同状态。
如果需要对照模型的近期公开状态,可查看 FishAI 模型状态页。这类入口应放在读者需要继续核验的位置,而不是在文末堆叠品牌和网址。
结论
外层卡片与分组详情不一致时,排查顺序应是:指标定义 → 服务端字段 → 前端二次转换 → 时间窗口 → 缓存与过滤 → 确定样本回归。
数字不同不一定是 bug,但同名指标采用不同算法一定是产品问题。把"真实请求成功率"和"分组健康度"分开,既保留总体体验,也不会放过低流量分组的风险。