# risk-rtp-service RTP 风控服务

## 服务职责

risk-rtp-service 第一阶段负责读取或接收外部采集链路已经导入的 RTP/反奖 snapshot，并结合商户、游戏、配置版本、赔付表哈希和当前下注/派彩请求，返回 `ALLOW`、`REVIEW`、`BLOCK` 三类风控决策。

重要边界：反奖/RTP 数据不在本服务内盲目生成。本服务不跑采集器、不解析原始游戏结果包、不自造样本，只保存标准化后的 snapshot，并基于 snapshot 做实时评估。

## 当前实现范围

- 标准 Go 服务分层：`cmd`、`internal/bootstrap`、`internal/config`、`internal/domain`、`internal/handler/http`、`internal/model`、`internal/repository/postgres`、`internal/service`、`internal/api`、`api/openapi`、`configs`。
- HTTP 框架：Hertz，经 `common/pkg/httpserver` 提供 `/health` 和 `/ready`。
- 仓储：第一阶段使用内存仓储，包名保留 `repository/postgres`，模型面向后续 Redis 热缓存、PostgreSQL snapshot、ClickHouse 统计读取落地。
- 幂等：`import_idempotency_key` 命中且 payload 一致时返回业务视图 `code=IDEMPOTENT_HIT`；同一幂等键不同 payload 返回 409。
- active 语义：同一 `provider + game_code` 导入新的 active snapshot 时，会把旧 active snapshot 置为 inactive。
- 商户覆盖：snapshot 内支持 `merchant_overrides`，可按商户覆盖 `target_rtp_bps`、review/block 阈值和最小样本量。

## 本地命令

```bash
cd /Users/amumu/Desktop/beifen/golang新架构/游戏服务/risk-rtp-service
go test ./...
go build ./cmd/risk-rtp-service
PORT=8094 go run ./cmd/risk-rtp-service
```

成熟度检查：

```bash
cd /Users/amumu/Desktop/beifen/golang新架构
./scripts/check-go-service-maturity.sh
```

## 接口

### POST /risk/rtp/snapshots

导入外部采集/统计链路已经生成的 RTP snapshot。

请求示例：

```json
{
  "provider": "pg",
  "game_code": "fortune-tiger",
  "config_version": "2026-06-06T10:00:00Z",
  "rtp_bucket": "pg:fortune-tiger:96.5",
  "payout_table_hash": "sha256:payout-table-v1",
  "source_snapshot_id": "collector-snapshot-001",
  "observed_rtp_bps": 9650,
  "target_rtp_bps": 9600,
  "sample_size": 200000,
  "active": true,
  "imported_by": "collector-sync",
  "import_idempotency_key": "rtp-import-001",
  "merchant_overrides": [
    {
      "merchant_id": "MERCHANT_STRICT",
      "target_rtp_bps": 9400,
      "review_threshold_bps": 80,
      "block_threshold_bps": 180,
      "min_sample_size": 1000
    }
  ]
}
```

关键字段：

| 字段 | 说明 |
| --- | --- |
| provider | 游戏供应商，如 `pg` |
| game_code | 游戏编码 |
| config_version | 配置版本，必须与 game-config-service 下发版本一致 |
| rtp_bucket | RTP/反奖分桶，用于下注、结算、报表串联 |
| payout_table_hash | 赔付表哈希，用于校验结算依据 |
| source_snapshot_id | 外部采集/统计 snapshot ID |
| observed_rtp_bps | 外部统计得到的当前 RTP，基点表示，`9650` 表示 `96.50%` |
| target_rtp_bps | 目标 RTP，基点表示 |
| sample_size | 外部采集样本量 |
| merchant_overrides | 商户级目标 RTP、阈值和最小样本覆盖 |
| import_idempotency_key | 导入幂等键 |

### GET /risk/rtp/snapshots/{game_code}

查询当前 active RTP snapshot。用于排查当前服务评估依据，也可供 `game-pg-mahjong` 或调试工具查看当前生效版本。

### POST /risk/rtp/evaluate

按当前下注/派彩请求评估风险。

请求示例：

```json
{
  "trace_id": "trace-001",
  "merchant_id": "MERCHANT_A",
  "player_id": "player-001",
  "round_id": "round-001",
  "bet_order_id": "bet-001",
  "provider": "pg",
  "game_code": "fortune-tiger",
  "config_version": "2026-06-06T10:00:00Z",
  "rtp_bucket": "pg:fortune-tiger:96.5",
  "payout_table_hash": "sha256:payout-table-v1",
  "source_snapshot_id": "collector-snapshot-001",
  "bet_amount": 10000,
  "payout": 9000,
  "currency": "CNY"
}
```

响应示例：

```json
{
  "code": "OK",
  "message": "success",
  "request_id": "trace-001",
  "data": {
    "code": "OK",
    "decision": "ALLOW",
    "reason": "rtp_within_target",
    "merchant_id": "MERCHANT_A",
    "game_code": "fortune-tiger",
    "config_version": "2026-06-06T10:00:00Z",
    "rtp_bucket": "pg:fortune-tiger:96.5",
    "payout_table_hash": "sha256:payout-table-v1",
    "source_snapshot_id": "collector-snapshot-001",
    "observed_rtp_bps": 9650,
    "target_rtp_bps": 9600,
    "sample_size": 200000,
    "review_threshold_bps": 150,
    "block_threshold_bps": 400,
    "current_payout_rtp_bps": 9000,
    "merchant_override_used": false
  }
}
```

决策规则第一阶段为确定性规则：

- snapshot 不存在：返回 404，不本地生成 RTP。
- `config_version`、`rtp_bucket`、`payout_table_hash`、`source_snapshot_id` 与 snapshot 不一致：返回 `BLOCK`，reason 为 `config_snapshot_mismatch`。
- 样本量低于阈值：返回 `REVIEW`，reason 为 `sample_size_below_minimum`。
- `observed_rtp_bps` 或当前单笔 `payout / bet_amount` 超过 block 阈值：返回 `BLOCK`。
- 超过 review 阈值但未达到 block：返回 `REVIEW`。
- 其他情况返回 `ALLOW`。

## 采集数据导入关系

```text
外部采集任务 / 旧系统反奖统计
-> 生成 source_snapshot_id、observed_rtp_bps、sample_size、payout_table_hash
-> game-config-service 导入游戏配置和 RTP snapshot 元数据
-> risk-rtp-service 导入可评估 snapshot 和商户覆盖策略
-> game-pg-mahjong / game-router / settlement-service 携带 config_version、rtp_bucket、payout_table_hash 调用评估或结算
```

第一阶段允许 game-config-service 和 risk-rtp-service 各自保存内存 snapshot，是为了先固定接口契约。后续生产落地建议：

- Redis：缓存当前 active snapshot 和商户覆盖策略。
- PostgreSQL：保存 snapshot 元数据、active 指针、幂等键和导入审计。
- ClickHouse：保存外部采集统计、投注派彩明细、按商户/游戏/配置版本聚合的 RTP 指标。

## 与其他服务边界

- game-config-service：负责游戏配置、赔付表哈希、RTP snapshot 元数据的版本化导入和 active 查询；risk-rtp-service 不替代配置中心。
- settlement-service：负责派彩结算和结果固化；risk-rtp-service 只返回风险建议，不改余额、不写结算终态。
- game-pg-mahjong：当前阶段真实 PG Mahjong Ways 子游戏可在出结果前调用 `POST /risk/rtp/evaluate`，根据 `ALLOW/REVIEW/BLOCK` 决定正常出结果、人工复核或阻断；结果池参考旧系统 `slotsdata`，旧 `dat` 文件通过导入工具转换为新格式，子游戏仍负责玩法适配和结果生成/引用。
- bet-order-service：注单字段中保留 `config_version`、`rtp_bucket`、`result_hash`，用于把下注、风控、结算和报表串起来。

## 测试覆盖

- snapshot 导入幂等命中和幂等冲突。
- snapshot 保留 `provider/game_code/config_version/rtp_bucket/payout_table_hash/source_snapshot_id/observed_rtp_bps/target_rtp_bps/sample_size/merchant_overrides`。
- evaluate `ALLOW`。
- 商户覆盖触发 `REVIEW`。
- 当前派彩尖峰触发 `BLOCK`。
- 配置版本与 snapshot 不匹配触发 `BLOCK`。
- 标准目录架构测试。

## OpenAPI 与配置

- OpenAPI：`游戏服务/risk-rtp-service/api/openapi/risk-rtp-service.openapi.yaml`
- 配置示例：`游戏服务/risk-rtp-service/configs/config.example.yaml`
