Skip to content

Commit f3ce6ef

Browse files
Claude Codeclaude
andcommitted
feat(reportgen): complete Phase 8/9 charts and fix JS init crash
- Add 13 new query functions in queries_phase8.go: latency histogram, scatter, token throughput heatmap, upstream share/latency trend, cost-per-token, I/O ratio, model token box plots, source node dist, streaming box plot, peak RPM, model daily trend, user tier dist, user token percentiles - Fix QueryModelRadarData adoption score (was copying throughput score; now uses COUNT(DISTINCT user_id) share) - Add KPI.PeakRPM and 15 new ReportData fields in types.go - Wire all Phase 8/9 queries in generator.go - Add ~20 new ECharts init functions and chart divs in report.html - Fix initRetentionCurve missing mkChart() call that crashed init() and caused error/slow request tables to not render - Update README to v2.26.0 and mark design doc as complete Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 146dc47 commit f3ce6ef

8 files changed

Lines changed: 1733 additions & 67 deletions

File tree

docs/REPORT_DASHBOARD_DESIGN.md

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# PairProxy 分析报告大屏设计文档
22

3-
> 版本: v1.0 | 日期: 2026-04-04
4-
> 状态: 设计阶段
3+
> 版本: v1.0 | 日期: 2026-04-04
4+
> 状态: 已完成 (v2.25.0)
55
66
---
77

@@ -682,35 +682,36 @@ func percentile(sorted []int64, p float64) int64 {
682682

683683
## 八、实现优先级与路线图
684684

685-
### Phase 1:核心报告(MVP)
686-
687-
- [ ] CLI 框架 + 数据库连接
688-
- [ ] 核心聚合查询(15 个基础查询)
689-
- [ ] HTML 模板(ECharts 嵌入)
690-
- [ ] KPI 数字卡片 + 环比变化
691-
- [ ] 趋势图(Token、费用、请求量)
692-
- [ ] TOP10 用户横向柱状图
693-
- [ ] 模型分布饼图
694-
- [ ] 基础文字洞察(环比、TOP贡献者)
695-
696-
### Phase 2:深度分析
697-
698-
- [ ] 箱线图(延迟分布、Token 分布)
699-
- [ ] 热力图(24h × 7天 请求密度)
700-
- [ ] 直方图(用户频次、I/O 比率)
701-
- [ ] 散点图(Input vs Output)
702-
- [ ] 帕累托图
703-
- [ ] 雷达图(模型多维度对比)
704-
- [ ] 留存曲线
705-
- [ ] 异常检测算法
706-
707-
### Phase 3:智能洞察
708-
709-
- [ ] 成本预测(线性外推)
710-
- [ ] 模型替代建议(高价→低价)
711-
- [ ] 配额耗尽预测
712-
- [ ] 用户参与度评分
713-
- [ ] Prompt 效率建议
685+
### Phase 1:核心报告(MVP)✅
686+
687+
- [x] CLI 框架 + 数据库连接
688+
- [x] 核心聚合查询(15 个基础查询)
689+
- [x] HTML 模板(ECharts 嵌入)
690+
- [x] KPI 数字卡片 + 环比变化
691+
- [x] 趋势图(Token、费用、请求量)
692+
- [x] TOP10 用户横向柱状图
693+
- [x] 模型分布饼图
694+
- [x] 基础文字洞察(环比、TOP贡献者)
695+
696+
### Phase 2:深度分析 ✅
697+
698+
- [x] 箱线图(延迟分布、Token 分布)
699+
- [x] 热力图(24h × 7天 请求密度)
700+
- [x] 直方图(用户频次、I/O 比率)
701+
- [x] 散点图(Input vs Output)
702+
- [x] 帕累托图
703+
- [x] 雷达图(模型多维度对比)
704+
- [x] 留存曲线
705+
- [x] 异常检测算法
706+
707+
### Phase 3:智能洞察 ✅
708+
709+
- [x] 成本预测(线性外推)
710+
- [x] 模型替代建议(高价→低价)
711+
- [x] 配额耗尽预测
712+
- [x] 用户参与度评分
713+
- [x] Prompt 效率建议
714+
- [x] LLM 深度分析(Anthropic/OpenAI,AES-GCM Key 解密,自动降级重试)
714715

715716
---
716717

tools/reportgen/README.md

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
reportgen 是 PairProxy 的可视化分析报告生成工具,能够从 SQLite 数据库中提取使用数据,生成交互式 HTML 报告。报告包含 16+ 个可视化卡片,覆盖用户、运维和管理三个视角的分析需求。
66

7-
**最新版本**: v2.24.0
8-
**发布日期**: 2026-04-04
7+
**最新版本**: v2.26.0
8+
**发布日期**: 2026-04-07
99

1010
---
1111

@@ -231,6 +231,44 @@ CREATE TABLE usage_logs (
231231

232232
---
233233

234+
## LLM 智能洞察
235+
236+
除规则洞察外,reportgen 支持调用上游 LLM(Anthropic 或 OpenAI)对完整报告数据进行深度分析,生成三视角(使用者/运维/管理者)的中文洞察报告。
237+
238+
### 启用条件
239+
240+
1. **数据库中存在活跃 LLM 目标**`llm_targets` 表需有 `is_active=1``provider``anthropic``openai` 的行,并关联有效的 `api_keys` 记录。
241+
2. **设置环境变量**:API Key 在数据库中以 AES-GCM 加密存储,解密需提供密钥加密密钥:
242+
243+
```bash
244+
export KEY_ENCRYPTION_KEY="your-key-encryption-key"
245+
./reportgen -db pairproxy.db -from 2026-04-01 -to 2026-04-07
246+
```
247+
248+
### 工作原理
249+
250+
- reportgen 读取第一个活跃的 LLM 目标,解密 API Key。
251+
- 将完整报告 JSON 发送给 LLM,要求其从三个视角各给出 3~5 条洞察。
252+
- 若报告 JSON 超出 LLM 上下文窗口,自动去除 `error_requests``slow_requests``io_scatter_plot``retention_data` 等大数组后重试。
253+
- 洞察以纯文本形式附加到报告末尾的"🤖 AI 智能洞察"面板。
254+
255+
### 模型选择
256+
257+
| Provider | 使用模型 | 说明 |
258+
|---|---|---|
259+
| Anthropic | `claude-haiku-4-5-20251001` | 速度快、成本低,适合分析任务 |
260+
| OpenAI | `gpt-4o-mini` | 成本较低的替代方案 |
261+
262+
### 跳过 LLM 洞察
263+
264+
若不需要 LLM 洞察,不设置 `KEY_ENCRYPTION_KEY` 环境变量即可。reportgen 会在 stderr 打印提示并继续生成规则洞察:
265+
266+
```
267+
⚠️ LLM insights skipped: KEY_ENCRYPTION_KEY env var not set; skipping LLM insights
268+
```
269+
270+
---
271+
234272
## 进阶使用
235273

236274
### 构建自定义模板
@@ -442,9 +480,10 @@ tools/reportgen/
442480
├── queries.go # Phase 1-2 查询 (基础 + 延迟)
443481
├── queries_phase3.go # Phase 3 查询 (留存 + 成本)
444482
├── queries_phase4.go # Phase 4 查询 (趋势 + 配额)
445-
├── queries_phase6.go # Phase 6 查询 (雷达 + 采用率)
483+
├── queries_phase6.go # Phase 6 查询 (雷达 + 采用率 + 请求统计)
446484
├── types.go # 数据结构定义
447-
├── insights.go # 洞察计算 (分层、Pareto等)
485+
├── insights.go # 规则洞察计算 (分层、Pareto等)
486+
├── insights_llm.go # LLM 智能洞察 (Anthropic/OpenAI)
448487
├── templates/
449488
│ └── report.html # HTML 模板
450489
├── cmd/test_db/
@@ -510,6 +549,16 @@ A: 当前报告是全局视角。扩展功能可参考"开发和扩展"章节。
510549

511550
## 变更日志
512551

552+
### v2.26.0 (2026-04-07)
553+
- ✨ 新增模型每日用量堆叠面积图(按模型×日期)
554+
- ✨ 新增峰值 RPM KPI 卡片
555+
- ✨ 全部设计文档特性补全,16 类图表 100% 实现
556+
557+
### v2.25.0 (2026-04-07)
558+
- ✨ 新增 LLM 智能洞察 (Anthropic/OpenAI 双提供商,AES-GCM API Key 解密,上下文超限自动重试)
559+
- ✨ Phase 7: 用户请求次数箱线图统计
560+
- 📝 更新使用手册,补充 LLM 洞察配置说明
561+
513562
### v2.24.0 (2026-04-04)
514563
- ✨ 补充 6 阶段可视化覆盖 (从 52% → 90%)
515564
- ✨ 新增 Pareto 分析、用户分层、采用率等高级分析
@@ -529,4 +578,4 @@ A: 当前报告是全局视角。扩展功能可参考"开发和扩展"章节。
529578

530579
---
531580

532-
**文档版本**: v2.24.0 | **最后更新**: 2026-04-04
581+
**文档版本**: v2.26.0 | **最后更新**: 2026-04-07

tools/reportgen/generator.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,25 @@ func GenerateReport(params QueryParams, templatePath, outputPath string) error {
7878
// Phase 7: Request-count analytics
7979
data.UserRequestBoxPlot, _ = q.QueryUserRequestBoxPlot(params.From, params.To)
8080

81+
// Phase 8: Missing/partial features
82+
data.LatencyHistogram, _ = q.QueryLatencyHistogram(params.From, params.To)
83+
data.LatencyScatter, _ = q.QueryLatencyScatter(params.From, params.To, 1000)
84+
data.TokenThroughputHeatmap, _ = q.QueryTokenThroughputHeatmap(params.From, params.To)
85+
data.UpstreamShare, _ = q.QueryUpstreamShare(params.From, params.To)
86+
data.UpstreamLatencyTrend, _ = q.QueryUpstreamLatencyTrend(params.From, params.To)
87+
data.CostPerTokenTrend, _ = q.QueryCostPerTokenTrend(params.From, params.To)
88+
data.IORatioTrend, _ = q.QueryIORatioTrend(params.From, params.To)
89+
data.ModelInputBoxPlots, _ = q.QueryModelTokenBoxPlots(params.From, params.To, "input_tokens")
90+
data.ModelOutputBoxPlots, _ = q.QueryModelTokenBoxPlots(params.From, params.To, "output_tokens")
91+
data.SourceNodeDist, _ = q.QuerySourceNodeDist(params.From, params.To)
92+
data.StreamingBoxPlot, _ = q.QueryStreamingBoxPlot(params.From, params.To)
93+
data.ModelDailyTrend, _ = q.QueryModelDailyTrend(params.From, params.To)
94+
data.KPI.PeakRPM, _ = q.QueryPeakRPM(params.From, params.To)
95+
96+
// Phase 9: remaining gaps
97+
data.UserTierDist, _ = q.QueryUserTierDist(params.From, params.To)
98+
data.UserTokenPercentiles, _ = q.QueryUserTokenPercentiles(params.From, params.To)
99+
81100
// Generate rule-based insights
82101
data.Insights = GenerateInsights(&data)
83102

tools/reportgen/queries_phase6.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ func (q *Querier) QueryModelRadarData(from, to time.Time) ([]ModelRadarData, err
2020
COALESCE(AVG(duration_ms), 0) as avg_lat,
2121
COALESCE(SUM(cost_usd), 0) as total_cost,
2222
COALESCE(SUM(total_tokens), 0) as total_tokens,
23-
SUM(CASE WHEN status_code NOT IN (200,201,204) THEN 1 ELSE 0 END) as errors
23+
SUM(CASE WHEN status_code NOT IN (200,201,204) THEN 1 ELSE 0 END) as errors,
24+
COUNT(DISTINCT user_id) as distinct_users
2425
FROM usage_logs
2526
WHERE created_at >= ? AND created_at < ?
2627
GROUP BY model
@@ -31,25 +32,30 @@ func (q *Querier) QueryModelRadarData(from, to time.Time) ([]ModelRadarData, err
3132
defer rows.Close()
3233

3334
type modelStats struct {
34-
model string
35-
count int64
36-
avgLatency float64
37-
totalCost float64
38-
totalTokens int64
39-
errors int64
35+
model string
36+
count int64
37+
avgLatency float64
38+
totalCost float64
39+
totalTokens int64
40+
errors int64
41+
distinctUsers int64
4042
}
4143
var stats []modelStats
4244
var totalRequests int64
45+
var totalUsers int64
4346
var maxLatency float64
4447
var minCostPerToken float64 = math.MaxFloat64
4548

4649
for rows.Next() {
4750
var m modelStats
48-
if err := rows.Scan(&m.model, &m.count, &m.avgLatency, &m.totalCost, &m.totalTokens, &m.errors); err != nil {
51+
if err := rows.Scan(&m.model, &m.count, &m.avgLatency, &m.totalCost, &m.totalTokens, &m.errors, &m.distinctUsers); err != nil {
4952
continue
5053
}
5154
stats = append(stats, m)
5255
totalRequests += m.count
56+
if m.distinctUsers > totalUsers {
57+
totalUsers = m.distinctUsers
58+
}
5359

5460
if m.avgLatency > maxLatency {
5561
maxLatency = m.avgLatency
@@ -105,8 +111,10 @@ func (q *Querier) QueryModelRadarData(from, to time.Time) ([]ModelRadarData, err
105111
rd.ReliabilityScore = math.Round((1 - errorRate) * 100 * 10) / 10
106112
}
107113

108-
// 5. Adoption Score: same as throughput score (request volume percentage)
109-
rd.AdoptionScore = rd.ThroughputScore
114+
// 5. Adoption Score: distinct users using this model / max distinct users any model
115+
if totalUsers > 0 {
116+
rd.AdoptionScore = math.Round(float64(ms.distinctUsers) / float64(totalUsers) * 100 * 10) / 10
117+
}
110118

111119
result = append(result, rd)
112120
}

0 commit comments

Comments
 (0)