@tapdb/tapdb-data-analysis 0.1.31 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -65,6 +65,7 @@ python3 tapdb-data-analysis/scripts/tapdb_query.py -r sg list_projects
|
|
|
65
65
|
- "用 TapDB 分析一下 XXXX 游戏近 30 天的留存趋势有没有异常"
|
|
66
66
|
- "用 TapDB 对比 XXX 游戏 和 XXX 游戏 的收入数据"
|
|
67
67
|
- "用 TapDB 查下 XXX 游戏鲸鱼用户排行"
|
|
68
|
+
- "用 TapDB 查一下 XXX 游戏最近 30 天各媒体的买量成本和 ROI"
|
|
68
69
|
|
|
69
70
|
AI 会自动调用 `tapdb_query.py` 脚本查询数据并生成分析报告。
|
|
70
71
|
|
|
@@ -81,4 +82,6 @@ AI 会自动调用 `tapdb_query.py` 脚本查询数据并生成分析报告。
|
|
|
81
82
|
| 用户价值 | `user_value` | LTV (N日贡献) |
|
|
82
83
|
| 鲸鱼用户 | `whale_user` | 高付费用户排行 |
|
|
83
84
|
| 生命周期 | `life_cycle` | 付费转化率/金额/累计 |
|
|
85
|
+
| 买量成本 | `cost` | 花费/展示/点击/获客/留存/ROI(支持 `--measurement-criteria device\|account` 切换统计口径) |
|
|
86
|
+
| 广告投放 | `ad_data` | 广告投放数据(cost 无数据时的回退方案) |
|
|
84
87
|
| 广告变现 | `ad_monet` | 广告收入数据 |
|
package/package.json
CHANGED
|
@@ -3,15 +3,16 @@ name: tapdb-data-analysis
|
|
|
3
3
|
description: >
|
|
4
4
|
TapDB 游戏数据分析技能。用于查询和分析 TapDB 中的游戏运营数据,包括活跃(DAU/WAU/MAU)、
|
|
5
5
|
留存(1日留存-180日留存)、付费(收入/ARPU/ARPPU)、来源(新增/转化)、用户价值(LTV)、版本分布、
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
玩家行为、广告变现、买量成本(CPI/CPA/ROI)等指标。
|
|
7
|
+
当用户需要查询游戏数据、分析运营指标、对比项目表现、检测数据异常、生成数据报告、
|
|
8
|
+
分析买量成本与 ROI 时使用此技能。
|
|
8
9
|
触发关键词:TapDB、DAU、MAU、留存、付费、收入、ARPU、LTV、活跃、新增、来源、玩家行为、
|
|
9
|
-
|
|
10
|
+
版本分布、鲸鱼用户、广告变现、游戏数据分析、买量、投放、广告投放、买量成本、CPI、CPA、ROI、ROAS、花费。
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
# TapDB 数据分析
|
|
13
14
|
|
|
14
|
-
> Skill 版本:v0.1.
|
|
15
|
+
> Skill 版本:v0.1.35
|
|
15
16
|
|
|
16
17
|
通过 Python 脚本调用 TapDB 运营数据查询接口,获取游戏指标数据并分析。
|
|
17
18
|
|
|
@@ -93,6 +94,16 @@ npm view @tapdb/tapdb-data-analysis version --registry https://registry.npmjs.or
|
|
|
93
94
|
2. 候选日期展示给用户确认
|
|
94
95
|
3. 按确认的周期分别查 active/income/retention/source,对比输出
|
|
95
96
|
|
|
97
|
+
### F: 广告投放(买量)数据查询
|
|
98
|
+
|
|
99
|
+
**触发**:"买量数据/投放效果/CPI/CPA/广告花费/ROI/ROAS"
|
|
100
|
+
|
|
101
|
+
#### 优先使用 `cost` 子命令(主路径)
|
|
102
|
+
|
|
103
|
+
#### 当 `cost` 无数据或报错时,切换到 `ad_data` 子命令
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
96
107
|
## 脚本使用
|
|
97
108
|
|
|
98
109
|
### 基础命令
|
|
@@ -141,7 +152,13 @@ python3 <SKILL_DIR>/scripts/tapdb_query.py life_cycle -p 2588 -s 2026-02-01 -e 2
|
|
|
141
152
|
python3 <SKILL_DIR>/scripts/tapdb_query.py whale_user -p 2588 -s 2026-01-01 -e 2026-02-25
|
|
142
153
|
python3 <SKILL_DIR>/scripts/tapdb_query.py version_distri -p 2588 -s 2026-02-01 -e 2026-02-25
|
|
143
154
|
python3 <SKILL_DIR>/scripts/tapdb_query.py player_behavior -p 2588 -s 2026-02-01 -e 2026-02-25 -g time
|
|
155
|
+
python3 <SKILL_DIR>/scripts/tapdb_query.py cost -p 2588 -s 2026-02-01 -e 2026-02-25 -g dt
|
|
156
|
+
python3 <SKILL_DIR>/scripts/tapdb_query.py cost -p 2588 -s 2026-02-01 -e 2026-02-25 -g media
|
|
157
|
+
python3 <SKILL_DIR>/scripts/tapdb_query.py cost -p 2588 -s 2026-02-01 -e 2026-02-25 -g dt --measurement-criteria device
|
|
144
158
|
python3 <SKILL_DIR>/scripts/tapdb_query.py raw /op/active '{"project_id":2588,"start_time":"2026-02-01 00:00:00.000","end_time":"2026-02-25 23:59:59.999","subject":"device","quota":"dau","group":{"col_name":"time","col_alias":"date","is_time":true,"trunc_unit":"day"},"is_de_water":false,"filters":[]}'
|
|
159
|
+
# 广告投放数据(独立参数,不复用通用参数)
|
|
160
|
+
python3 <SKILL_DIR>/scripts/tapdb_query.py ad_data -p 2588 -s 2026-03-01 -e 2026-03-10 --quotas cost,display,activation,activationCost -g time
|
|
161
|
+
python3 <SKILL_DIR>/scripts/tapdb_query.py ad_data -p 2588 -s 2026-03-01 -e 2026-03-10 --quotas cost,activation,day1LTV,day7LTV,day30LTV -g platform_id
|
|
145
162
|
```
|
|
146
163
|
|
|
147
164
|
## 子命令速查
|
|
@@ -157,6 +174,8 @@ python3 <SKILL_DIR>/scripts/tapdb_query.py raw /op/active '{"project_id":2588,"s
|
|
|
157
174
|
| `user_value` | LTV | 通用参数 | `activation_time` |
|
|
158
175
|
| `whale_user` | 鲸鱼用户 | 通用参数 | 无分组 |
|
|
159
176
|
| `life_cycle` | 生命周期 | `--quota payment_amount\|payment_cvs_rate\|payment_cvs\|acc_payment` | `activation_time` |
|
|
177
|
+
| `cost` | 买量成本(全链路) **← 广告数据首选** | 通用参数;分组维度: dt/media/os/country/campaign_id/ad_id 等;`--measurement-criteria device\|account`(统计口径:设备/账户,默认 device) | `dt` |
|
|
178
|
+
| `ad_data` | 广告投放(买量回退) **← cost 无数据时回退** | `--quotas cost,display,...`, `-g platform_id\|time\|...`(独立参数,不复用通用参数) | `time` |
|
|
160
179
|
| `ad_monet` | 广告变现 | 通用参数 | 可能返回 404(未开通或路径不同) |
|
|
161
180
|
|
|
162
181
|
## 数据量控制策略(先小后大,必须遵守)
|
|
@@ -196,6 +215,12 @@ python3 <SKILL_DIR>/scripts/tapdb_query.py raw /op/active '{"project_id":2588,"s
|
|
|
196
215
|
- `life_cycle` 在 `-g activation_os` 时仅支持 `--quota payment_cvs_rate`(其他 quota 会 500)
|
|
197
216
|
- filters 格式: `{"col_name":"...", "data_type":"string|number|bool|date", "calculate_symbol":"include|un_include", "ftv":[...]}`
|
|
198
217
|
- 各接口维度不同,不支持的维度返回 500。**先 `describe` 确认**
|
|
218
|
+
- **`ad_data` 广告投放接口注意**:
|
|
219
|
+
- 走 `/mcp/ad/multiple_display_web`,非 `/mcp/op/*`,独立参数体系
|
|
220
|
+
- 响应为二维数组格式(非 dict),脚本内部自动转为 dict 列表
|
|
221
|
+
- 时间粒度固定为 day,不支持 week/month
|
|
222
|
+
- cost/display/click 等花费类指标仅在 platform_id/advertisement_id/tag_id 等广告维度分组时有意义
|
|
223
|
+
- 需项目已开通 ad_plus 功能,未开通会返回错误
|
|
199
224
|
|
|
200
225
|
## 分析与报告
|
|
201
226
|
|
|
@@ -101,6 +101,54 @@ API 直接返回 DRx_rate/WRx_rate/MRx_rate(小数形式,如 0.7656=76.56%
|
|
|
101
101
|
- ❌ 错误:周总收入(12,230,904) ÷ 周去重活跃(195,663) = 62.46 → 这不是ARPU
|
|
102
102
|
- ✅ 正确:日均收入(1,747,272) ÷ 日均DAU(155,768) = 11.22 → 这才是日均ARPU
|
|
103
103
|
|
|
104
|
+
### 投放 ROI / ROAS(cost 子命令,重要!)
|
|
105
|
+
|
|
106
|
+
cost 接口返回的金额字段有两套口径,计算 ROI 时必须区分:
|
|
107
|
+
|
|
108
|
+
| 指标 | 公式 | 含义 | 使用场景 |
|
|
109
|
+
|------|------|------|----------|
|
|
110
|
+
| **ROI** | `dayX_paid_amount / real_cost` | 付费金额 ÷ 实际消耗 | **默认使用**,投放效果评估 |
|
|
111
|
+
| **真实 ROI** | `dayX_real_income / real_cost` | 扣除渠道分成后的真实收入 ÷ 实际消耗 | 仅在用户明确要求"真实ROI"时使用 |
|
|
112
|
+
|
|
113
|
+
**字段说明**:
|
|
114
|
+
- `cost`:消耗(含平台服务费等)
|
|
115
|
+
- `real_cost`:实际消耗(扣除返点/折扣后的真实花费),**ROI 分母统一用 real_cost**
|
|
116
|
+
- `dayX_paid_amount`:新增设备上,注册后 X 天内累计付费总额(未扣渠道分成)
|
|
117
|
+
- `dayX_real_income`:新增设备上,注册后 X 天内累计真实收入(已扣渠道分成)
|
|
118
|
+
|
|
119
|
+
**硬规则**:
|
|
120
|
+
- ❌ 禁止用 `cost` 作为任何指标的分母或成本基准(应统一使用 `real_cost`)
|
|
121
|
+
- ❌ 禁止用 `dayX_real_income` 计算默认 ROI(那是"真实 ROI")
|
|
122
|
+
- ❌ 禁止在用户未明确要求"真实收入"时使用 `dayX_real_income` 作为收入展示值
|
|
123
|
+
- ✅ 所有涉及成本计算的指标(CPA、ROI、ROAS 等)统一使用 `real_cost` 作为成本值
|
|
124
|
+
- ✅ 展示"消耗/成本"时优先展示 `real_cost`(真实成本),而非 `cost`
|
|
125
|
+
- ✅ **展示"收入/付费金额"时默认使用 `dayX_paid_amount`**,仅在用户明确要求"真实收入"时才使用 `dayX_real_income`
|
|
126
|
+
- ✅ 默认 ROI = `dayX_paid_amount / real_cost`
|
|
127
|
+
- ✅ 真实 ROI = `dayX_real_income / real_cost`(需用户明确要求)
|
|
128
|
+
|
|
129
|
+
### 成本数据统计口径(measurement_criteria)
|
|
130
|
+
|
|
131
|
+
cost 接口支持两种统计口径,通过请求体中的 `measurement_criteria` 字段控制:
|
|
132
|
+
|
|
133
|
+
| 值 | 含义 | 对应表 |
|
|
134
|
+
|---|------|--------|
|
|
135
|
+
| `"account"` | 账户口径 | `dwd_autogrowth_game_agent_retention_charge_fully_hr_v3` |
|
|
136
|
+
| 不传(默认) | 设备口径 | `dwd_autogrowth_game_agent_retention_charge_fully_hr_v2_device` |
|
|
137
|
+
|
|
138
|
+
**项目路由规则(用户未明确指定 account/device 口径时)**:
|
|
139
|
+
- ✅ **香肠派对(705)、火炬之光(2588)**:默认传 `measurement_criteria=account`(走 v3 账户口径表)
|
|
140
|
+
- ✅ 其余项目:不传此字段,走默认 v2 设备口径表
|
|
141
|
+
- ⚠️ 若用户明确要求使用 device 或 account 口径,以用户指定为准
|
|
142
|
+
|
|
143
|
+
**注意**:`cmd_cost` 子命令当前可能无法正确生效 `measurement_criteria`,需使用 `raw /op/omp-cost` 并在 JSON body 中显式传入 `"measurement_criteria":"account"` 以确保查询到 v3 表数据。
|
|
144
|
+
|
|
145
|
+
### 成本/投放数据分析准则(硬规则,必须遵守)
|
|
146
|
+
|
|
147
|
+
1. **所有数值必须来源于查询结果**:CPA、CTR、ROI、留存率等衍生指标必须基于接口返回的原始字段计算,禁止凭经验估算或编造任何数值
|
|
148
|
+
2. **过滤自然量**:分析投放效果时,必须排除 `media` 为"自然量"的数据行(自然量 cost/real_cost 为 0,纳入会严重扭曲 CPA、ROI 等指标)。仅在用户明确要求"包含自然量"或查看"全量数据"时才保留
|
|
149
|
+
3. **零消耗渠道不计算 ROI/CPA**:当某渠道 `real_cost = 0` 时,ROI 和 CPA 无意义,应标注"—"或"无投放",不能输出 0% 或 Inf
|
|
150
|
+
4. **汇总指标以 API 返回为准**:若接口返回了汇总行(分组字段为 `null`),直接使用该行数据,不要在本地重复汇总(可能因截断导致不一致)
|
|
151
|
+
|
|
104
152
|
### LTV (Life Time Value)
|
|
105
153
|
|
|
106
154
|
N日内人均累计付费金额(LTV1/3/7/14/30/60/90)。通过 `user_value` 子命令查询。
|
|
@@ -182,6 +182,38 @@ def _rebuild(resp, path, rows, info):
|
|
|
182
182
|
return result
|
|
183
183
|
|
|
184
184
|
|
|
185
|
+
def _parse_ad_response(resp):
|
|
186
|
+
"""Parse ad API response into (rows_as_dicts, summary_row_or_None).
|
|
187
|
+
|
|
188
|
+
The ad API returns a flat list:
|
|
189
|
+
[ total_count, [header1, header2, ...], [row1_val, ...], ..., [total_val, ...] ]
|
|
190
|
+
or wrapped in {"data": [...]}.
|
|
191
|
+
The first element is a total_count integer, followed by headers, data rows,
|
|
192
|
+
and a summary/total row at the end.
|
|
193
|
+
"""
|
|
194
|
+
data = resp
|
|
195
|
+
if isinstance(resp, dict):
|
|
196
|
+
if resp.get("error"):
|
|
197
|
+
return None, None
|
|
198
|
+
data = resp.get("data", resp)
|
|
199
|
+
if not isinstance(data, list) or len(data) < 2:
|
|
200
|
+
return None, None
|
|
201
|
+
# Skip leading scalar (total_count) if present
|
|
202
|
+
start = 0
|
|
203
|
+
if not isinstance(data[0], list):
|
|
204
|
+
start = 1
|
|
205
|
+
if start >= len(data):
|
|
206
|
+
return None, None
|
|
207
|
+
headers = [str(h) for h in data[start]]
|
|
208
|
+
rows = [dict(zip(headers, r)) for r in data[start + 1:]]
|
|
209
|
+
if not rows:
|
|
210
|
+
return [], None
|
|
211
|
+
# Last row is the summary/total row
|
|
212
|
+
summary = rows[-1]
|
|
213
|
+
rows = rows[:-1]
|
|
214
|
+
return rows, summary
|
|
215
|
+
|
|
216
|
+
|
|
185
217
|
def truncate_response(resp, cmd_type=None, group_alias=None):
|
|
186
218
|
"""Truncate API response to save context window tokens."""
|
|
187
219
|
if not resp or (isinstance(resp, dict) and resp.get("error")):
|
|
@@ -221,10 +253,47 @@ COL_ALIAS_MAP = {
|
|
|
221
253
|
"activation_app_version": "activation_app_version",
|
|
222
254
|
"first_server": "first_server",
|
|
223
255
|
"current_server": "current_server",
|
|
256
|
+
# cost 维度
|
|
257
|
+
"dt": "date",
|
|
258
|
+
"media": "media",
|
|
259
|
+
"media_source": "msrc",
|
|
260
|
+
"campaign_id": "camp",
|
|
261
|
+
"ad_id": "ad",
|
|
262
|
+
"creative_id": "crtv",
|
|
263
|
+
"account_id": "acct",
|
|
264
|
+
"material_id": "mtrl",
|
|
265
|
+
"medium_id": "mdm",
|
|
266
|
+
"ad_platform_id": "adplt",
|
|
267
|
+
"opt_obj": "optobj",
|
|
268
|
+
"scene": "scene",
|
|
269
|
+
"scene_final": "scnf",
|
|
270
|
+
"city": "city",
|
|
271
|
+
"country": "country",
|
|
272
|
+
"country_code": "cycd",
|
|
273
|
+
"province": "province",
|
|
274
|
+
"os": "os",
|
|
275
|
+
"channel": "channel",
|
|
276
|
+
"app_id": "appid",
|
|
277
|
+
"tap_app_id": "tapid",
|
|
278
|
+
"google_opt_obj": "gopt",
|
|
279
|
+
"china_opt_obj": "copt",
|
|
280
|
+
"project_name": "proj",
|
|
224
281
|
}
|
|
225
282
|
|
|
226
283
|
COUNTRY_GROUP_DIMS = {"activation_country", "activation_province"}
|
|
227
284
|
|
|
285
|
+
AD_COL_ALIAS_MAP = {
|
|
286
|
+
"time": "date_",
|
|
287
|
+
"platform_id": "pf",
|
|
288
|
+
"advertisement_id": "ad",
|
|
289
|
+
"tag_id": "tag",
|
|
290
|
+
"first_ad_sub_channel1": "subc",
|
|
291
|
+
"first_ad_sub_channel2": "subc",
|
|
292
|
+
"first_ad_sub_channel3": "subc",
|
|
293
|
+
"first_ad_sub_channel4": "subc",
|
|
294
|
+
"first_ad_sub_channel5": "subc",
|
|
295
|
+
}
|
|
296
|
+
|
|
228
297
|
|
|
229
298
|
# ── 各接口能力描述(基于源码 + 实测验证) ────────────────────
|
|
230
299
|
#
|
|
@@ -475,9 +544,94 @@ ENDPOINT_CAPS = {
|
|
|
475
544
|
},
|
|
476
545
|
"unsupported_note": "分组仅支持 time/activation_time/activation_os;过滤不支持 activation_app_version/first_server/current_server/utmsrc/login_type/payment_source",
|
|
477
546
|
},
|
|
547
|
+
"cost": {
|
|
548
|
+
"description": "买量成本数据: 花费/展示/点击/获客/留存/付费/ROI 全链路",
|
|
549
|
+
"time_field": "dt",
|
|
550
|
+
"returned_metrics": [
|
|
551
|
+
"cost", "real_cost", "show_cnt", "click_cnt", "reserve_cnt",
|
|
552
|
+
"new_device", "new_user", "new_device_safe", "new_user_safe",
|
|
553
|
+
"reattr_new_device", "reattr_new_user", "paid_user_uv",
|
|
554
|
+
"retention_2d", "retention_3d", "retention_7d", "retention_14d",
|
|
555
|
+
"retention_30d", "retention_60d", "retention_90d", "retention_180d",
|
|
556
|
+
"day1_paid_amount", "day2_paid_amount", "day3_paid_amount",
|
|
557
|
+
"day7_paid_amount", "day14_paid_amount", "day30_paid_amount",
|
|
558
|
+
"day60_paid_amount", "day90_paid_amount", "day180_paid_amount",
|
|
559
|
+
"day1_real_income", "day2_real_income", "day3_real_income",
|
|
560
|
+
"day7_real_income", "day14_real_income", "day30_real_income",
|
|
561
|
+
"day60_real_income", "day90_real_income", "day180_real_income",
|
|
562
|
+
],
|
|
563
|
+
"groups": [
|
|
564
|
+
"dt", "media", "media_source", "os", "country", "country_code", "province", "city",
|
|
565
|
+
"channel", "campaign_id", "ad_id", "creative_id",
|
|
566
|
+
"account_id", "material_id", "medium_id", "ad_platform_id",
|
|
567
|
+
"app_id", "tap_app_id", "opt_obj", "scene", "scene_final",
|
|
568
|
+
"google_opt_obj", "china_opt_obj", "project_name",
|
|
569
|
+
],
|
|
570
|
+
"filters": [
|
|
571
|
+
"media", "media_source", "os", "country", "country_code", "province", "city",
|
|
572
|
+
"channel", "campaign_id", "ad_id", "creative_id",
|
|
573
|
+
"account_id", "material_id", "medium_id", "ad_platform_id",
|
|
574
|
+
"app_id", "tap_app_id", "opt_obj", "scene", "scene_final",
|
|
575
|
+
"google_opt_obj", "china_opt_obj", "project_name",
|
|
576
|
+
"material_type", "material_mode",
|
|
577
|
+
"name", "tags_str", "jump_url", "account_alias",
|
|
578
|
+
],
|
|
579
|
+
"unsupported_note": "不支持现有运营维度(activation_channel等),使用广告维度(media/campaign_id等)",
|
|
580
|
+
},
|
|
478
581
|
"ad_monet": {
|
|
479
582
|
"description": "广告变现数据(MCP 代理路径 /mcp/op/ad_monet 返回 404,可能未开通或路径不同)",
|
|
480
583
|
},
|
|
584
|
+
"ad_data": {
|
|
585
|
+
"description": "广告投放(买量)数据: 花费/展示/点击/激活/留存/LTV等(仅用户明确要求广告/买量数据时使用)",
|
|
586
|
+
"quotas": {
|
|
587
|
+
"cost_display": ["cost", "display"],
|
|
588
|
+
"click": ["click", "uClick", "clickRate", "clickCost"],
|
|
589
|
+
"activation": [
|
|
590
|
+
"activation", "activeRate", "activationCost",
|
|
591
|
+
"newUser", "convertRate", "convertDevice",
|
|
592
|
+
"payUserNew", "payRateNew", "payAmountNew",
|
|
593
|
+
],
|
|
594
|
+
"device_retention": [
|
|
595
|
+
"day1Retain", "day1RetainRate",
|
|
596
|
+
"day6Retain", "day6RetainRate",
|
|
597
|
+
"day13Retain", "day13RetainRate",
|
|
598
|
+
"day29Retain", "day29RetainRate",
|
|
599
|
+
],
|
|
600
|
+
"payment": ["income", "payUser", "payRate", "payTimes"],
|
|
601
|
+
"ltv": [
|
|
602
|
+
"day1LTV", "day3LTV", "day7LTV", "day14LTV",
|
|
603
|
+
"day30LTV", "day60LTV", "day90LTV", "day180LTV", "day360LTV",
|
|
604
|
+
],
|
|
605
|
+
},
|
|
606
|
+
"groups": [
|
|
607
|
+
"time", "platform_id", "advertisement_id", "tag_id",
|
|
608
|
+
"first_ad_sub_channel1", "first_ad_sub_channel2",
|
|
609
|
+
"first_ad_sub_channel3", "first_ad_sub_channel4",
|
|
610
|
+
"first_ad_sub_channel5",
|
|
611
|
+
],
|
|
612
|
+
"filters": [
|
|
613
|
+
"activation_os", "activation_country", "activation_continent",
|
|
614
|
+
"activation_province", "activation_network", "activation_provider",
|
|
615
|
+
"activation_device_model", "activation_app_version",
|
|
616
|
+
"first_ad_platform_id", "first_ad_advertiser_id",
|
|
617
|
+
"first_campaign_id", "first_adgroup_id", "first_creative_id",
|
|
618
|
+
"first_ad_conversion_link_id", "activation_channel",
|
|
619
|
+
],
|
|
620
|
+
"extra_params": [
|
|
621
|
+
"ad_increment (bool, 默认true: 仅广告增量)",
|
|
622
|
+
"tz_offset (int, 默认8: 时区偏移)",
|
|
623
|
+
"sort_field / sort_order (排序字段和顺序)",
|
|
624
|
+
"charge_subject (user|device, 默认user)",
|
|
625
|
+
"exchange_to_currency (str, 如 USD/CNY/JPY,默认CNY)",
|
|
626
|
+
],
|
|
627
|
+
"notes": [
|
|
628
|
+
"走 /mcp/ad/multiple_display_web 接口,非 /mcp/op/*",
|
|
629
|
+
"时间粒度固定为 day,不支持 week/month",
|
|
630
|
+
"cost/display/click 等花费类指标仅在 platform_id/advertisement_id/tag_id 分组时有意义",
|
|
631
|
+
"响应格式为二维数组(非 dict),脚本内部会转为 dict 列表",
|
|
632
|
+
"需项目已开通 ad_plus 功能",
|
|
633
|
+
],
|
|
634
|
+
},
|
|
481
635
|
}
|
|
482
636
|
|
|
483
637
|
|
|
@@ -500,8 +654,9 @@ def cmd_describe(args):
|
|
|
500
654
|
if "subjects" in cap:
|
|
501
655
|
info["subjects"] = cap["subjects"]
|
|
502
656
|
if "groups" in cap:
|
|
657
|
+
alias_map = AD_COL_ALIAS_MAP if name == "ad_data" else COL_ALIAS_MAP
|
|
503
658
|
info["supported_groups"] = [
|
|
504
|
-
{"col_name": g, "col_alias":
|
|
659
|
+
{"col_name": g, "col_alias": alias_map.get(g, g),
|
|
505
660
|
**({"note": cap.get("group_notes", {}).get(g)} if g in cap.get("group_notes", {}) else {})}
|
|
506
661
|
for g in cap["groups"]
|
|
507
662
|
]
|
|
@@ -519,6 +674,8 @@ def cmd_describe(args):
|
|
|
519
674
|
info["returned_metrics"] = cap["returned_metrics"]
|
|
520
675
|
if "unsupported_note" in cap:
|
|
521
676
|
info["unsupported_note"] = cap["unsupported_note"]
|
|
677
|
+
if "notes" in cap:
|
|
678
|
+
info["notes"] = cap["notes"]
|
|
522
679
|
result[name] = info
|
|
523
680
|
|
|
524
681
|
output(result)
|
|
@@ -536,6 +693,19 @@ def build_group(group_by, group_unit):
|
|
|
536
693
|
}
|
|
537
694
|
|
|
538
695
|
|
|
696
|
+
def build_ad_group(group_by):
|
|
697
|
+
"""Build group dict for the ad API (fixed day granularity)."""
|
|
698
|
+
if not group_by:
|
|
699
|
+
return None
|
|
700
|
+
is_time = group_by == "time"
|
|
701
|
+
return {
|
|
702
|
+
"col_name": group_by,
|
|
703
|
+
"col_alias": AD_COL_ALIAS_MAP.get(group_by, group_by),
|
|
704
|
+
"is_time": is_time,
|
|
705
|
+
"trunc_unit": "day",
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
|
|
539
709
|
def build_base_body(args):
|
|
540
710
|
body = {"project_id": int(args.project_id)}
|
|
541
711
|
if hasattr(args, "start") and args.start:
|
|
@@ -651,6 +821,67 @@ def cmd_life_cycle(args):
|
|
|
651
821
|
do_query(args, "life_cycle", {"quota": args.quota})
|
|
652
822
|
|
|
653
823
|
|
|
824
|
+
def cmd_cost(args):
|
|
825
|
+
if not args.group_by or args.group_by == "time":
|
|
826
|
+
args.group_by = "dt"
|
|
827
|
+
extra = {}
|
|
828
|
+
mc = getattr(args, "measurement_criteria", None)
|
|
829
|
+
if mc:
|
|
830
|
+
extra["measurement_criteria"] = mc
|
|
831
|
+
do_query(args, "omp-cost", extra=extra or None, cmd_type="cost")
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def cmd_ad_data(args):
|
|
835
|
+
"""查询广告投放(买量)数据 — 走 /mcp/ad/multiple_display_web。"""
|
|
836
|
+
key, base_url = get_config(args.region)
|
|
837
|
+
group_by = args.group_by or "time"
|
|
838
|
+
quotas = [q.strip() for q in args.quotas.split(",") if q.strip()]
|
|
839
|
+
body = {
|
|
840
|
+
"project_id": int(args.project_id),
|
|
841
|
+
"start_time": f"{args.start} 00:00:00",
|
|
842
|
+
"end_time": f"{args.end} 23:59:59",
|
|
843
|
+
"group": build_ad_group(group_by),
|
|
844
|
+
"quotas": quotas,
|
|
845
|
+
"ad_increment": args.ad_increment.lower() != "false",
|
|
846
|
+
"tz_offset": args.tz_offset,
|
|
847
|
+
"charge_subject": args.charge_subject,
|
|
848
|
+
"filters": json.loads(args.filters) if args.filters else [],
|
|
849
|
+
"use_cache": True,
|
|
850
|
+
"page": 1,
|
|
851
|
+
"page_size": 5000,
|
|
852
|
+
}
|
|
853
|
+
if args.sort_field:
|
|
854
|
+
body["sort"] = {"field": args.sort_field, "order": args.sort_order}
|
|
855
|
+
exchange_to = getattr(args, "exchange_to_currency", None)
|
|
856
|
+
if exchange_to and exchange_to.lower() != "none":
|
|
857
|
+
body["exchange_to_currency"] = exchange_to.upper()
|
|
858
|
+
|
|
859
|
+
url = f"{base_url}/mcp/ad/multiple_display_web"
|
|
860
|
+
resp = http_request("POST", url, {"MCP-KEY": key}, body)
|
|
861
|
+
|
|
862
|
+
if isinstance(resp, dict) and resp.get("error"):
|
|
863
|
+
output(resp)
|
|
864
|
+
return
|
|
865
|
+
|
|
866
|
+
rows, summary = _parse_ad_response(resp)
|
|
867
|
+
if rows is None:
|
|
868
|
+
output(resp)
|
|
869
|
+
return
|
|
870
|
+
|
|
871
|
+
group_alias = AD_COL_ALIAS_MAP.get(group_by, group_by)
|
|
872
|
+
if not getattr(args, "no_truncate", False):
|
|
873
|
+
rows, trunc_info = _slim_rows(rows, "ad_data", group_alias=group_alias)
|
|
874
|
+
else:
|
|
875
|
+
trunc_info = None
|
|
876
|
+
|
|
877
|
+
result = {"data": rows}
|
|
878
|
+
if summary is not None:
|
|
879
|
+
result["summary"] = summary
|
|
880
|
+
if trunc_info:
|
|
881
|
+
result["_truncation"] = trunc_info
|
|
882
|
+
output(result)
|
|
883
|
+
|
|
884
|
+
|
|
654
885
|
def cmd_ad_monet(args):
|
|
655
886
|
do_query(args, "ad_monet")
|
|
656
887
|
|
|
@@ -752,6 +983,34 @@ def main():
|
|
|
752
983
|
choices=["payment_cvs_rate", "payment_cvs", "payment_amount", "acc_payment"],
|
|
753
984
|
help="生命周期指标 (默认 payment_amount)")
|
|
754
985
|
|
|
986
|
+
# cost
|
|
987
|
+
p = sub.add_parser("cost", help="买量成本数据: 花费/展示/点击/获客/留存/ROI")
|
|
988
|
+
add_common_args(p)
|
|
989
|
+
p.add_argument("--measurement-criteria", default="device", choices=["device", "account"],
|
|
990
|
+
help="统计口径: device(按设备,默认) | account(按账户)")
|
|
991
|
+
|
|
992
|
+
# ad_data (广告投放/买量)
|
|
993
|
+
p = sub.add_parser("ad_data", help="广告投放(买量)数据: 花费/展示/点击/激活/留存/LTV")
|
|
994
|
+
p.add_argument("-p", "--project-id", required=True, help="项目ID")
|
|
995
|
+
p.add_argument("-s", "--start", required=True, help="开始日期 YYYY-MM-DD")
|
|
996
|
+
p.add_argument("-e", "--end", required=True, help="结束日期 YYYY-MM-DD")
|
|
997
|
+
p.add_argument("-g", "--group-by", default="time",
|
|
998
|
+
help="分组: time/platform_id/advertisement_id/tag_id/first_ad_sub_channel1-5 (默认 time)")
|
|
999
|
+
p.add_argument("--quotas", required=True,
|
|
1000
|
+
help="逗号分隔指标列表 (如 cost,display,activation,activationCost)")
|
|
1001
|
+
p.add_argument("--ad-increment", default="true",
|
|
1002
|
+
help="仅广告增量 (默认 true; false 包含自然量)")
|
|
1003
|
+
p.add_argument("--tz-offset", type=int, default=8,
|
|
1004
|
+
help="时区偏移 (默认 8, 即 UTC+8)")
|
|
1005
|
+
p.add_argument("--sort-field", help="排序字段 (如 cost, activation)")
|
|
1006
|
+
p.add_argument("--sort-order", default="desc", choices=["asc", "desc"],
|
|
1007
|
+
help="排序方向 (默认 desc)")
|
|
1008
|
+
p.add_argument("--filters", help='过滤条件JSON')
|
|
1009
|
+
p.add_argument("--charge-subject", default="user", choices=["user", "device"],
|
|
1010
|
+
help="付费主体 (默认 user)")
|
|
1011
|
+
p.add_argument("--exchange-to-currency", default="CNY",
|
|
1012
|
+
help="金额目标货币 (如 USD/CNY/JPY,默认CNY)")
|
|
1013
|
+
|
|
755
1014
|
# ad_monet
|
|
756
1015
|
p = sub.add_parser("ad_monet", help="广告变现数据")
|
|
757
1016
|
add_common_args(p)
|
|
@@ -779,6 +1038,8 @@ def main():
|
|
|
779
1038
|
"user_value": cmd_user_value,
|
|
780
1039
|
"whale_user": cmd_whale_user,
|
|
781
1040
|
"life_cycle": cmd_life_cycle,
|
|
1041
|
+
"cost": cmd_cost,
|
|
1042
|
+
"ad_data": cmd_ad_data,
|
|
782
1043
|
"ad_monet": cmd_ad_monet,
|
|
783
1044
|
"raw": cmd_raw,
|
|
784
1045
|
}
|