# 配置规则和技术实现

本文档包含数数看板生成的配置规则、脚本使用说明、技术实现细节。

## 配置规则

### 1. 分组字段规则

**不确定值的字段都需要添加到分组项中**，包括但不限于：
- ID类字段：rid、owner、lottery_id、group_id、message_id 等
- 枚举类字段：game_type、scope、join_way、btn_name 等
- 数值类字段：user_num、gamer_num、event_duration、time_start、status 等
- 其他动态字段：label_type、room_type、screen_name、gift_id 等

### 2. 筛选条件规则

**固定值的筛选条件添加到 events.filts 中**：
```json
"filts": [{
  "filts": [{
    "ftv": ["语音房抽奖弹窗"],
    "comparator": "equal",
    "columnName": "screen_name"
  }],
  "filterType": "COMPOUND"
}]
```

### 3. 全局筛选规则

**所有报表都包含 app_region 全局筛选**，筛选值根据用户选择的区服动态设置：

**华语服 (C)**：
```json
"eventView": {
  "filts": [{
    "ftv": ["C"],
    "comparator": "equal",
    "columnName": "app_region"
  }]
}
```

**日服 (J)**：
```json
"eventView": {
  "filts": [{
    "ftv": ["J"],
    "comparator": "equal",
    "columnName": "app_region"
  }]
}
```

**会玩 ("")**：
```json
"eventView": {
  "filts": [{
    "ftv": [""],
    "comparator": "equal",
    "columnName": "app_region"
  }]
}
```

## 脚本使用说明

### 脚本位置

所有脚本位于 `testcase_generation/scripts/` 目录下：
- **生成脚本**：`scripts/generate_shushu_json.py`
- **校验脚本**：`scripts/validate_shushu_json.py`

### 输出位置

生成的文件保存在 `testcase_generation/docs/` 目录下：
- JSON 配置文件：`docs/<文档名>_数数看板_<区服名>.json`
- 飞书文档缓存：`docs/feishu_doc.md`

### 使用方法

**重要**：必须在 `testcase_generation` 目录下运行脚本

#### ⚠️ 关键提醒：必须指定飞书文档来源

**从飞书文档生成看板时，必须使用 `--from-feishu` 参数！**

```bash
# 1. 切换到 testcase_generation 目录
cd ai-platform/skills/testing/data_testing/testcase_generation

# 2. 生成数数看板配置（必须指定 --from-feishu 参数）
python3 scripts/generate_shushu_json.py "C" "活动名称" --from-feishu docs/feishu_doc.md

# 3. 如需单独校验
python3 scripts/validate_shushu_json.py docs/活动名称_数数看板_华语服.json

# 4. 查看生成的文件
ls -lh docs/*.json
```

#### ❌ 常见错误

**错误示例**（缺少 `--from-feishu` 参数）：
```bash
# ❌ 这样会使用默认的示例配置（播棋），而不是飞书文档内容
python3 scripts/generate_shushu_json.py "C" "[五周年]导航页"
```

**正确示例**：
```bash
# ✅ 必须添加 --from-feishu 参数
python3 scripts/generate_shushu_json.py "C" "[五周年]导航页" --from-feishu docs/feishu_doc.md
```

**后果**：如果忘记 `--from-feishu` 参数，生成的配置会使用硬编码的示例事件（如播棋相关的 AppClick、EnterRoom 等），而不是飞书文档中的实际埋点事件。

### 参数说明

**完整脚本参数**：
```bash
python3 scripts/generate_shushu_json.py <区服代码> [文档标题] --from-feishu <飞书文档路径>
```

- `<区服代码>`：必填，如 "M"（马尼服）、"C"（华语服）等
- `[文档标题]`：可选，默认为 "播棋埋点"
- `--from-feishu <路径>`：**必需参数**，指定飞书文档的 Markdown 文件路径

**标准使用流程**：
```bash
# 步骤1：获取飞书文档（如果还没有）
FEISHU_APP_ID=xxx FEISHU_APP_SECRET=xxx \
python3 ../../../common/feishu2md/scripts/feishu2md.py \
https://wepie.feishu.cn/wiki/xxxxx docs/feishu_doc.md

# 步骤2：生成看板配置（必须指定 --from-feishu）
python3 scripts/generate_shushu_json.py "C" "活动名称" --from-feishu docs/feishu_doc.md

# 步骤3：验证生成的配置
python3 scripts/validate_shushu_json.py docs/活动名称_数数看板_华语服.json
```

**示例**：
```bash
# 生成华语服看板（从飞书文档）
python3 scripts/generate_shushu_json.py "C" "[五周年]导航页" --from-feishu docs/feishu_doc.md

# 生成日服看板（从飞书文档）
python3 scripts/generate_shushu_json.py "J" "[五周年]导航页" --from-feishu docs/feishu_doc.md

# 生成韩服看板（从飞书文档）
python3 scripts/generate_shushu_json.py "K" "[五周年]导航页" --from-feishu docs/feishu_doc.md
```

## 技术实现

### 核心脚本
- **生成**：`scripts/generate_shushu_json.py` - 解析文档并生成看板配置
- **校验**：`scripts/validate_shushu_json.py` - 验证JSON格式完整性
- **输出**：`testcase_generation/docs/` - 自动保存生成的配置

### 依赖工具
- **feishu2md**：`ai-platform/skills/common/feishu2md/` - 飞书文档转换
- **模板**：`examples/shushu_dashboard_template_verified.json` - 标准配置参考

**区服映射表（内置）**：
```python
APP_REGION_MAP = [
    ("", "会玩"),
    ("A", "阿语服"),
    ("Q", "土语服"),
    ("P", "菲律宾服"),
    ("V", "越南服"),
    ("C", "华语服"),
    ("O", "JK服"),
    ("R", "俄语服"),
    ("B", "葡语服"),
    ("T", "泰服"),
    ("M", "马尼服"),
    ("U", "美服"),
    ("J", "日服"),
    ("K", "韩服"),
    ("I", "印度服"),
    ("S", "西语服"),
    ("N", "巴基斯坦服"),
    ("F", "法语服"),
    ("G", "德语服"),
]
```

## 成功导入的关键要素

### ⚠️ 重要：必需字段检查

根据实际导入经验，数数平台对JSON格式有严格要求。**缺少任何必需字段都会导致"导入成功但创建报表失败"**。

#### 1. reportMappings 必需字段

每个 reportMapping 对象**必须**包含以下7个字段：

```json
{
  "aggregate_data": "{\"show\":false,\"typeList\":[]}",
  "date_outside": -1,
  "index_order": 0,
  "report_graph_shape": "L0",
  "report_id": 70001,
  "report_width": 0,
  "table_format": 0
}
```

**字段说明**：
- `aggregate_data`：聚合数据配置（**必须为字符串格式**）
  - ✅ 正确：`"{\"show\":false,\"typeList\":[]}"`
  - ❌ 错误：缺少该字段或为空对象 `{}`
- `date_outside`：日期外部标识（固定为 `-1`）
- `index_order`：报表显示顺序（从0开始递增）
- `report_graph_shape`：图表形状（**必须为 "L0"，不能为空字符串 ""**）
  - ✅ 正确：`"L0"`
  - ❌ 错误：`""` 或 `null`
- `report_id`：报表ID（唯一标识）
- `report_width`：报表宽度（固定为 `0`）
- `table_format`：表格格式（固定为 `0`）

#### 2. dashboard 必需字段

每个 dashboard 对象**必须**包含 `ui_config` 字段：

```json
{
  "ui_config": "{\"layout\":[{\"i\":\"70001\",\"x\":0,\"y\":0,\"w\":10,\"h\":10}],\"version\":\"3.4.0\"}"
}
```

**ui_config 说明**：
- **必须为字符串格式的JSON**，不能是对象
- 包含报表布局信息（layout数组）
- 每个报表占用的位置（x, y, w, h）
- 布局版本信息（version: "3.4.0"）

**布局规则**：
- 每行放2个报表（`x` 值为 0 或 10）
- 每个报表高度和宽度都是10（`w: 10, h: 10`）
- `y` 值每行递增10（第一行 y=0，第二行 y=10，以此类推）

**示例（3个报表的布局）**：
```json
{
  "layout": [
    {"i": "70001", "x": 0, "y": 0, "w": 10, "h": 10},   // 第一行左边
    {"i": "70002", "x": 10, "y": 0, "w": 10, "h": 10},  // 第一行右边
    {"i": "70003", "x": 0, "y": 10, "w": 10, "h": 10}   // 第二行左边
  ],
  "version": "3.4.0"
}
```

### 🔍 诊断方法

#### 方法1：对比模板文件

如果导入失败，**第一步应该是对比成功的模板文件**：

1. **准备模板文件**：
   - 从数数平台导出一个能成功工作的看板
   - 或使用项目中的验证模板：`examples/shushu_dashboard_template_verified.json` ⭐ 推荐

2. **对比关键字段**：
   ```bash
   # 检查 reportMappings 字段
   python3 << 'EOF'
   import json

   # 读取验证模板和生成的文件
   with open('examples/shushu_dashboard_template_verified.json') as f:
       template = json.load(f)

   with open('docs/生成的文件.json') as f:
       generated = json.load(f)

   # 对比 reportMapping 字段
   template_mapping = template['dashboardFolders'][0]['dashboards'][0]['reportMappings'][0]
   generated_mapping = generated['dashboardFolders'][0]['dashboards'][0]['reportMappings'][0]

   print("模板字段:", sorted(template_mapping.keys()))
   print("生成字段:", sorted(generated_mapping.keys()))
   print("\n缺失字段:", set(template_mapping.keys()) - set(generated_mapping.keys()))
   EOF
   ```

3. **检查 ui_config**：
   ```bash
   # 检查是否包含 ui_config
   python3 -c "
   import json
   with open('docs/生成的文件.json') as f:
       data = json.load(f)
   dashboard = data['dashboardFolders'][0]['dashboards'][0]
   if 'ui_config' in dashboard:
       print('✅ 包含 ui_config')
       ui_config = json.loads(dashboard['ui_config'])
       print(f'   报表布局数: {len(ui_config[\"layout\"])} 个')
   else:
       print('❌ 缺少 ui_config')
   "
   ```

#### 方法2：使用校验脚本

运行校验脚本检查格式：

```bash
cd testcase_generation
python3 scripts/validate_shushu_json.py docs/生成的文件.json
```

校验脚本会检查：
- ✅ 顶层结构完整性
- ✅ reportMappings 字段完整性
- ✅ dashboard ui_config 存在性
- ✅ 所有必需字段

### 📋 常见错误及解决方案

| 错误症状 | 可能原因 | 解决方案 |
|---------|---------|---------|
| **导入成功，但提示"未成功创建报表"** | reportMappings 缺少字段 | 补充所有7个必需字段（特别是 aggregate_data 和 report_graph_shape） |
| **报表显示异常** | ui_config 缺失或格式错误 | 添加完整的 ui_config 字段，包含 layout 和 version |
| **report_graph_shape 为空** | 字段值设置为 "" | 改为 "L0" |
| **aggregate_data 为对象** | 字段格式错误 | 必须为字符串格式：`"{\"show\":false,\"typeList\":[]}"` |
| **拆分后仍然失败** | 拆分脚本未保留原始字段 | 确保拆分时复制完整的 reportMapping 对象 |

### 🎯 最佳实践

#### 1. 生成配置后立即验证

```bash
# 生成配置后立即验证
python3 scripts/validate_shushu_json.py docs/生成的文件.json
```

#### 2. 拆分看板的注意事项

**数数平台限制**：每个看板最多**30个报表**

如果报表数超过30个，必须拆分：

```bash
# 拆分成每30个报表一个文件
python3 scripts/split_dashboard.py docs/原始文件.json 30
```

**拆分脚本必须保证**：
- ✅ 复制完整的 reportMapping 字段（包括所有7个必需字段）
- ✅ 重新生成 ui_config（为当前批次的报表生成布局）
- ✅ 更新 index_order（从0开始重新编号）

#### 3. 导入前的最终检查

使用以下命令快速检查关键字段：

```bash
# 检查 reportMappings 字段数量
python3 -c "
import json
with open('docs/批次1.json') as f:
    data = json.load(f)
mapping = data['dashboardFolders'][0]['dashboards'][0]['reportMappings'][0]
print('reportMapping 字段数:', len(mapping))
print('字段列表:', sorted(mapping.keys()))
print('report_graph_shape 值:', repr(mapping.get('report_graph_shape')))
"
```

**预期输出**：
```
reportMapping 字段数: 7
字段列表: ['aggregate_data', 'date_outside', 'index_order', 'report_graph_shape', 'report_id', 'report_width', 'table_format']
report_graph_shape 值: 'L0'
```

### 💡 经验总结

1. **对比是最快的诊断方法**：遇到导入失败，第一时间对比成功模板的字段
2. **字段完整性是关键**：reportMappings 和 ui_config 缺一不可
3. **字段值不能随意**：report_graph_shape 必须是 "L0"，不能是空字符串
4. **拆分要注意保留**：拆分看板时必须保留原始对象的所有字段
5. **验证要及时**：生成配置后立即验证，避免导入时才发现问题

## 配置示例

### 完整的报表配置示例

```json
{
  "reportId": 70001,
  "reportName": "完成任务",
  "openQuery": {
    "events": [
      {
        "eventName": "ActivityTotal",
        "eventDisplayName": "ActivityTotal",
        "filts": [
          {
            "columnName": "action",
            "comparator": "equal",
            "ftv": ["complete_task"],
            "function": "eq",
            "relation": "and"
          }
        ],
        "cols": [
          {
            "columnName": "act_id",
            "columnDesc": "act_id",
            "tableType": "event"
          },
          {
            "columnName": "task_id",
            "columnDesc": "task_id",
            "tableType": "event"
          }
        ],
        "measures": [
          {
            "aggregator": "TOTAL_COUNT",
            "columnName": "#event_id",
            "columnDesc": "总次数"
          }
        ]
      }
    ],
    "eventView": {
      "filts": [
        {
          "ftv": ["C"],
          "comparator": "equal",
          "columnName": "app_region",
          "filterType": "SIMPLE"
        }
      ],
      "groupBy": [
        {
          "columnName": "act_id",
          "columnDesc": "act_id",
          "tableType": "event"
        },
        {
          "columnName": "task_id",
          "columnDesc": "task_id",
          "tableType": "event"
        }
      ],
      "timeParticleSize": "day",
      "recentDay": "0-7"
    }
  },
  "frontConfig": {
    "displayQuotas": [
      {
        "eventName": "ActivityTotal",
        "analyseType": "TOTAL_COUNT"
      }
    ],
    "displayGroups": [
      {
        "columnName": "act_id",
        "showMode": "SHOW"
      },
      {
        "columnName": "task_id",
        "showMode": "SHOW"
      }
    ],
    "visualInfo": {
      "chartType": "table"
    }
  }
}
```

### 分组字段配置示例

**基础分组**（2-3个字段）：
```json
"groupBy": [
  {"columnName": "act_id", "tableType": "event"},
  {"columnName": "btn_name", "tableType": "event"}
]
```

**中等分组**（5-8个字段）：
```json
"groupBy": [
  {"columnName": "game_type", "tableType": "event"},
  {"columnName": "rid", "tableType": "event"},
  {"columnName": "user_num", "tableType": "event"},
  {"columnName": "scope", "tableType": "event"},
  {"columnName": "gamer_num", "tableType": "event"}
]
```

**复杂分组**（10+个字段）：
```json
"groupBy": [
  {"columnName": "activity_type", "tableType": "event"},
  {"columnName": "action", "tableType": "event"},
  {"columnName": "act_id", "tableType": "event"},
  {"columnName": "activity", "tableType": "event"},
  {"columnName": "activity_name", "tableType": "event"},
  {"columnName": "game_type", "tableType": "event"},
  {"columnName": "rid", "tableType": "event"},
  {"columnName": "room_type", "tableType": "event"},
  {"columnName": "user_num", "tableType": "event"},
  {"columnName": "gift_id", "tableType": "event"}
]
```

### 筛选条件配置示例

**单一筛选条件**：
```json
"filts": [
  {
    "columnName": "action",
    "comparator": "equal",
    "ftv": ["complete_task"],
    "function": "eq",
    "relation": "and"
  }
]
```

**多个筛选条件**（AND 关系）：
```json
"filts": [
  {
    "columnName": "activity_type",
    "comparator": "equal",
    "ftv": ["lottery"],
    "function": "eq",
    "relation": "and"
  },
  {
    "columnName": "action",
    "comparator": "equal",
    "ftv": ["do_wheel_lottery"],
    "function": "eq",
    "relation": "and"
  }
]
```

**COMPOUND 格式筛选**：
```json
"filts": [
  {
    "filts": [
      {
        "ftv": ["语音房抽奖弹窗"],
        "comparator": "equal",
        "columnName": "screen_name"
      }
    ],
    "filterType": "COMPOUND"
  }
]
```

## 区服配置

### 支持的所有区服

| 序号 | 区服代码 | 区服名称 | 序号 | 区服代码 | 区服名称 |
|-----|---------|---------|-----|---------|---------|
| 1 | "" | 会玩 | 11 | "M" | 马尼服 |
| 2 | "A" | 阿语服 | 12 | "U" | 美服 |
| 3 | "Q" | 土语服 | 13 | "J" | 日服 |
| 4 | "P" | 菲律宾服 | 14 | "K" | 韩服 |
| 5 | "V" | 越南服 | 15 | "I" | 印度服 |
| 6 | "C" | 华语服 | 16 | "S" | 西语服 |
| 7 | "O" | JK服 | 17 | "N" | 巴基斯坦服 |
| 8 | "R" | 俄语服 | 18 | "F" | 法语服 |
| 9 | "B" | 葡语服 | 19 | "G" | 德语服 |
| 10 | "T" | 泰服 | | | |

### 区服筛选配置模板

**模板**：
```json
{
  "ftv": ["<区服代码>"],
  "comparator": "equal",
  "columnName": "app_region",
  "filterType": "SIMPLE"
}
```

**示例**：

**华语服**：
```json
{"ftv": ["C"], "comparator": "equal", "columnName": "app_region", "filterType": "SIMPLE"}
```

**日服**：
```json
{"ftv": ["J"], "comparator": "equal", "columnName": "app_region", "filterType": "SIMPLE"}
```

**韩服**：
```json
{"ftv": ["K"], "comparator": "equal", "columnName": "app_region", "filterType": "SIMPLE"}
```

**会玩服**（特殊：空字符串）：
```json
{"ftv": [""], "comparator": "equal", "columnName": "app_region", "filterType": "SIMPLE"}
```

## 数据类型说明

### 字段类型

数数平台支持的字段类型：

| 类型 | 说明 | 示例 |
|------|------|------|
| STRING | 字符串 | `"btn_name": "立即参与"` |
| NUMBER | 数字 | `"user_num": 10` |
| BOOL | 布尔值 | `"is_vip": true` |
| DATE | 日期时间 | `"#event_time": "2026-03-20 10:00:00"` |

### 分析类型

报表支持的分析类型：

| 类型 | 说明 | 用途 |
|------|------|------|
| TOTAL_COUNT | 总次数 | 事件发生的总次数 |
| TOTAL_USER | 总人数 | 触发事件的用户数（去重） |
| PER_USER_AVG | 人均次数 | 平均每个用户触发的次数 |
| TRIGGER_USER_COUNT | 触发用户数 | 触发过该事件的用户总数 |

### 时间粒度

支持的时间粒度选项：

| 粒度 | 说明 |
|------|------|
| day | 按天统计 |
| hour | 按小时统计 |
| minute | 按分钟统计 |
| week | 按周统计 |
| month | 按月统计 |

## 附录

### 有用的命令

**检查JSON格式**：
```bash
python3 -m json.tool docs/xxx.json > /dev/null && echo "✅ JSON格式正确" || echo "❌ JSON格式错误"
```

**统计报表数量**：
```bash
python3 -c "import json; data=json.load(open('docs/xxx.json')); print(f'报表数量: {len(data[\"reports\"])}')"
```

**提取所有报表名称**：
```bash
python3 -c "
import json
with open('docs/xxx.json') as f:
    data = json.load(f)
for i, r in enumerate(data['reports'], 1):
    print(f'{i}. {r[\"reportName\"]}')
"
```

**检查区服配置**：
```bash
python3 -c "
import json
with open('docs/xxx.json') as f:
    data = json.load(f)
app_region = data['reports'][0]['openQuery']['eventView']['filts'][0]['ftv'][0]
print(f'区服: {app_region}')
"
```

### 参考资料

- **验证模板**：`examples/shushu_dashboard_template_verified.json`
- **事件配置示例**：`examples/events_config_example.json`
- **脚本文档**：`scripts/README.md`（如有）
- **数数平台文档**：（内部链接）
