feat(scripts): 支持动态指标和维度参数配置
为 build_studio_urls.py 添加指标和维度的可配置支持,包括: - 新增 metrics.json 和 dimensions.json 映射文件加载 - 支持中文/英文别名映射及指标代码透传 - 列别名扩展以识别"指标"和"维度"列 - 空值时自动回退到 CONFIG 默认值
This commit is contained in:
@@ -19,7 +19,8 @@ ASSET_XLSX = ROOT / "assets" / "需求输入示例.xlsx"
|
||||
HEADERS = ["所有者名称", "所有者ID", "实体类型", "实体名称", "实体ID", "数据周期", "国家"]
|
||||
|
||||
OUTPUT_COLUMNS = ["所有者名称", "所有者ID", "实体类型", "实体名称", "实体ID",
|
||||
"数据周期", "国家", "国家代码", "开始时间戳", "结束时间戳", "URL"]
|
||||
"数据周期", "国家", "国家代码", "指标", "指标代码", "维度", "维度代码",
|
||||
"开始时间戳", "结束时间戳", "URL"]
|
||||
|
||||
|
||||
def run_builder(args):
|
||||
@@ -183,6 +184,123 @@ class TestHappyPath:
|
||||
assert len(read_output(out)) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 指标(每行可选)
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestMetricColumn:
|
||||
def test_metric_selected_and_default(self, tmp_path):
|
||||
"""指标列:一行选"观看时长",一行留空走 CONFIG 默认。"""
|
||||
src = tmp_path / "需求.csv"
|
||||
pd.DataFrame([
|
||||
{"所有者ID": "MC123", "实体ID": "G001", "数据周期": "2026.07.01-2026.08.01",
|
||||
"指标": "观看时长"},
|
||||
{"所有者ID": "MC123", "实体ID": "G002", "数据周期": "2026.07.01-2026.08.01",
|
||||
"指标": ""},
|
||||
]).to_csv(src, index=False, encoding="utf-8-sig")
|
||||
out = tmp_path / "out.csv"
|
||||
r = run_builder(["-i", str(src), "-o", str(out)])
|
||||
assert r.returncode == 0, r.stderr
|
||||
df = read_output(out)
|
||||
assert list(df.columns) == OUTPUT_COLUMNS
|
||||
assert df.loc[0, "指标"] == "观看时长"
|
||||
assert df.loc[0, "指标代码"] == "EXTERNAL_WATCH_TIME"
|
||||
assert "metric=EXTERNAL_WATCH_TIME" in df.loc[0, "URL"]
|
||||
assert "o_column=EXTERNAL_WATCH_TIME" in df.loc[0, "URL"]
|
||||
# 空白行:回退 CONFIG 默认
|
||||
assert df.loc[1, "指标代码"] == "SUBSCRIBERS_NET_CHANGE"
|
||||
assert "metric=SUBSCRIBERS_NET_CHANGE" in df.loc[1, "URL"]
|
||||
|
||||
def test_unknown_metric_exits_2(self, tmp_path):
|
||||
src = tmp_path / "需求.csv"
|
||||
src.write_text(
|
||||
"所有者ID,实体ID,数据周期,指标\nMC123,G001,2026.07.01-2026.08.01,神秘指标\n",
|
||||
encoding="utf-8-sig",
|
||||
)
|
||||
out = tmp_path / "out.csv"
|
||||
r = run_builder(["-i", str(src), "-o", str(out)])
|
||||
assert r.returncode == 2
|
||||
assert "未识别的指标" in r.stderr
|
||||
assert "第2行" in r.stderr
|
||||
|
||||
def test_metric_code_passthrough(self, tmp_path):
|
||||
src = tmp_path / "需求.csv"
|
||||
src.write_text(
|
||||
"所有者ID,实体ID,数据周期,指标\nMC123,G001,2026.07.01-2026.08.01,external_views\n",
|
||||
encoding="utf-8-sig",
|
||||
)
|
||||
out = tmp_path / "out.csv"
|
||||
r = run_builder(["-i", str(src), "-o", str(out)])
|
||||
assert r.returncode == 0, r.stderr
|
||||
df = read_output(out)
|
||||
assert df.loc[0, "指标代码"] == "EXTERNAL_VIEWS"
|
||||
assert "metric=EXTERNAL_VIEWS" in df.loc[0, "URL"]
|
||||
|
||||
def test_metric_alias_tax_revenue(self, tmp_path):
|
||||
"""「税前收益/税前收入」别名 -> TOTAL_ESTIMATED_EARNINGS。"""
|
||||
src = tmp_path / "需求.csv"
|
||||
src.write_text(
|
||||
"所有者ID,实体ID,数据周期,指标\nMC123,G001,2026.07.01-2026.08.01,税前收益\n",
|
||||
encoding="utf-8-sig",
|
||||
)
|
||||
out = tmp_path / "out.csv"
|
||||
r = run_builder(["-i", str(src), "-o", str(out)])
|
||||
assert r.returncode == 0, r.stderr
|
||||
df = read_output(out)
|
||||
assert df.loc[0, "指标代码"] == "TOTAL_ESTIMATED_EARNINGS"
|
||||
assert "metric=TOTAL_ESTIMATED_EARNINGS" in df.loc[0, "URL"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 维度(每行可选)
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestDimensionColumn:
|
||||
def test_dimension_selected_and_default(self, tmp_path):
|
||||
"""维度列:一行选"地理位置",一行留空走 CONFIG 默认。"""
|
||||
src = tmp_path / "需求.csv"
|
||||
pd.DataFrame([
|
||||
{"所有者ID": "MC123", "实体ID": "G001", "数据周期": "2026.07.01-2026.08.01",
|
||||
"维度": "地理位置"},
|
||||
{"所有者ID": "MC123", "实体ID": "G002", "数据周期": "2026.07.01-2026.08.01",
|
||||
"维度": ""},
|
||||
]).to_csv(src, index=False, encoding="utf-8-sig")
|
||||
out = tmp_path / "out.csv"
|
||||
r = run_builder(["-i", str(src), "-o", str(out)])
|
||||
assert r.returncode == 0, r.stderr
|
||||
df = read_output(out)
|
||||
assert list(df.columns) == OUTPUT_COLUMNS
|
||||
assert df.loc[0, "维度"] == "地理位置"
|
||||
assert df.loc[0, "维度代码"] == "COUNTRY"
|
||||
assert "dimension=COUNTRY" in df.loc[0, "URL"]
|
||||
# 空白行:回退 CONFIG 默认
|
||||
assert df.loc[1, "维度代码"] == "USER"
|
||||
assert "dimension=USER" in df.loc[1, "URL"]
|
||||
|
||||
def test_unknown_dimension_exits_2(self, tmp_path):
|
||||
src = tmp_path / "需求.csv"
|
||||
src.write_text(
|
||||
"所有者ID,实体ID,数据周期,维度\nMC123,G001,2026.07.01-2026.08.01,神秘维度\n",
|
||||
encoding="utf-8-sig",
|
||||
)
|
||||
out = tmp_path / "out.csv"
|
||||
r = run_builder(["-i", str(src), "-o", str(out)])
|
||||
assert r.returncode == 2
|
||||
assert "未识别的维度" in r.stderr
|
||||
assert "第2行" in r.stderr
|
||||
|
||||
def test_dimension_code_passthrough(self, tmp_path):
|
||||
src = tmp_path / "需求.csv"
|
||||
src.write_text(
|
||||
"所有者ID,实体ID,数据周期,维度\nMC123,G001,2026.07.01-2026.08.01,video\n",
|
||||
encoding="utf-8-sig",
|
||||
)
|
||||
out = tmp_path / "out.csv"
|
||||
r = run_builder(["-i", str(src), "-o", str(out)])
|
||||
assert r.returncode == 0, r.stderr
|
||||
df = read_output(out)
|
||||
assert df.loc[0, "维度代码"] == "VIDEO"
|
||||
assert "dimension=VIDEO" in df.loc[0, "URL"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 失败行与退出码
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user