# Usage Statistics

## Purpose

定义 API 使用统计规范，支持请求统计记录、按供应商/模型/日期范围查询统计、统计聚合以及并发安全的统计记录。

## Requirements

### Requirement: 记录请求统计

网关 SHALL 为每次 API 调用记录请求统计。

#### Scenario: 记录成功请求

- **WHEN** 请求成功转发到供应商
- **THEN** 网关 SHALL 通过 StatsBuffer 增加该供应商和模型的请求计数
- **THEN** 网关 SHALL 异步批量写入数据库（定时或阈值触发）
- **THEN** 网关 SHALL 不阻塞响应

#### Scenario: 记录流式请求

- **WHEN** 流式请求成功完成
- **THEN** 网关 SHALL 通过 StatsBuffer 增加该供应商和模型的请求计数
- **THEN** 网关 SHALL 在流结束后异步记录统计

### Requirement: 使用统计记录统一模型标识

系统 SHALL 使用 providerID 和 modelName（上游模型名）记录使用统计。

#### Scenario: 代理请求统计记录

- **WHEN** 代理请求成功完成
- **THEN** SHALL 记录 provider_id 和 model_name 到 usage_stats 表（参数来自路由结果）
- **THEN** SHALL 异步执行，不阻塞响应

#### Scenario: 查询统计

- **WHEN** 查询统计数据
- **THEN** 支持按 provider_id 和 model_name 过滤

### Requirement: 按供应商查询统计

网关 SHALL 允许按供应商过滤查询统计。

#### Scenario: 查询特定供应商的统计

- **WHEN** 向 `/api/stats?provider_id=<provider_id>` 发送 GET 请求
- **THEN** 网关 SHALL 仅返回指定供应商的统计

**变更说明：** 通过 StatsService 和 StatsRepository 实现。API 接口保持不变。

### Requirement: 按模型查询统计

网关 SHALL 允许按模型过滤查询统计。

#### Scenario: 查询特定模型的统计

- **WHEN** 向 `/api/stats?model_name=<model_name>` 发送 GET 请求
- **THEN** 网关 SHALL 仅返回指定模型的统计

**变更说明：** 通过 StatsService 和 StatsRepository 实现。API 接口保持不变。

### Requirement: 按日期范围查询统计

网关 SHALL 允许在日期范围内查询统计。

#### Scenario: 使用日期范围查询统计

- **WHEN** 向 `/api/stats?start=<start_date>&end=<end_date>` 发送 GET 请求
- **THEN** 网关 SHALL 仅返回指定范围内的日期统计
- **THEN** 日期格式 SHALL 为 YYYY-MM-DD

**变更说明：** 通过 StatsService 和 StatsRepository 实现。API 接口保持不变。

### Requirement: 聚合统计

网关 SHALL 按日期聚合统计。

#### Scenario: 同一天多次请求

- **WHEN** 同一天对同一供应商和模型发起多次请求
- **THEN** 网关 SHALL 为该天维护单条统计记录
- **THEN** 请求计数 SHALL 为所有请求的总和

**变更说明：** 聚合逻辑在 StatsRepository 中实现。API 接口保持不变。

### Requirement: 支持并发统计记录

网关 SHALL 支持并发请求统计记录而无冲突。

#### Scenario: 并发请求

- **WHEN** 同时处理多个并发请求
- **THEN** 网关 SHALL 使用原子操作正确增加每个请求的计数
- **THEN** 不 SHALL 因并发写入而丢失统计
- **THEN** SHALL 使用 upsert 操作保证原子性

#### Scenario: 并发调用 Record 方法

- **WHEN** 多个 goroutine 并发调用 StatsRepository.Record
- **THEN** SHALL 使用 INSERT ... ON DUPLICATE KEY UPDATE (MySQL) 或 INSERT ... ON CONFLICT DO UPDATE (SQLite)
- **THEN** SHALL 保证所有并发调用的计数都被正确累加
- **THEN** 不 SHALL 因 UNIQUE 约束冲突而丢失数据

#### Scenario: 并发调用 BatchUpdate 方法

- **WHEN** 多个 goroutine 并发调用 StatsRepository.BatchUpdate
- **THEN** SHALL 使用 upsert 操作保证原子性
- **THEN** SHALL 正确累加所有 delta 值
- **THEN** 不 SHALL 因并发写入而丢失统计

### Requirement: 使用 service 层处理业务逻辑

Handler SHALL 通过 StatsService 处理业务逻辑。

#### Scenario: 调用 service 方法

- **WHEN** handler 收到统计查询请求
- **THEN** SHALL 调用对应的 StatsService 方法（Get、Aggregate）
- **THEN** SHALL 使用 domain.UsageStats 类型

#### Scenario: 异步记录统计

- **WHEN** 请求完成需要记录统计
- **THEN** SHALL 异步调用 StatsService.Record()
- **THEN** SHALL 不阻塞响应返回

### Requirement: 使用 repository 层访问数据

Service SHALL 通过 StatsRepository 访问数据。

#### Scenario: 调用 repository 方法

- **WHEN** service 处理业务逻辑
- **THEN** SHALL 调用对应的 StatsRepository 方法
- **THEN** SHALL 使用 domain.UsageStats 类型

#### Scenario: 批量更新统计

- **WHEN** StatsBuffer 刷新统计
- **THEN** SHALL 调用 StatsRepository.BatchUpdate
- **THEN** SHALL 使用 upsert 操作更新或创建统计记录
- **THEN** SHALL 支持增量更新（request_count + delta）

#### Scenario: upsert 操作

- **WHEN** 记录统计
- **THEN** SHALL 在 repository 层使用 upsert 操作
- **THEN** SHALL 保证原子性和并发安全

### Requirement: 统计查询优化

统计查询 SHALL 使用索引优化性能。

#### Scenario: 使用索引

- **WHEN** 查询统计
- **THEN** SHALL 使用 (provider_id, model_name, date) 复合索引
- **THEN** SHALL 优化查询性能

### Requirement: 统计数据可接受少量丢失

统计记录方式改为内存缓冲，可接受少量丢失。

#### Scenario: 进程崩溃丢失统计

- **WHEN** 进程崩溃
- **THEN** MAY 丢失最近 flushInterval 内的统计
- **THEN** 统计数据用于监控，可接受少量丢失

#### Scenario: 优雅关闭保证不丢失

- **WHEN** 服务优雅关闭
- **THEN** SHALL 调用 StatsBuffer.Stop
- **THEN** SHALL 等待最后一次刷新完成
- **THEN** SHALL 保证统计数据不丢失

### Requirement: StatsRepository 支持批量更新

StatsRepository SHALL 新增 BatchUpdate 方法支持批量增量更新。

#### Scenario: BatchUpdate 更新现有记录

- **WHEN** 调用 BatchUpdate 且当日记录存在
- **THEN** SHALL 使用 upsert 操作更新 request_count = request_count + delta
- **THEN** SHALL 保证原子性，无竞态条件
- **THEN** SHALL 不创建新记录

#### Scenario: BatchUpdate 创建新记录

- **WHEN** 调用 BatchUpdate 且当日记录不存在
- **THEN** SHALL 创建新记录，request_count = delta
- **THEN** SHALL 使用 upsert 操作保证原子性

#### Scenario: BatchUpdate 并发安全

- **WHEN** 多个 BatchUpdate 调用同时执行
- **THEN** SHALL 保证所有 delta 都被正确累加
- **THEN** SHALL 不因并发冲突而丢失数据
