ci/woodpecker/push/main Pipeline was successful
- 新增 /api/cma/maps/{id}/dimension-options 与 /{id}/slice(只读)
- 时间:kpi_values.period 月/季/年;flow 求和、ratio 均值、balance/status 取最新
- 产品:product_sales + 逐月源一致性闸门(偏差>1% 不切片)
- 门店:库内无门店级事实 ⇒ available=false + reason,绝不回退总额
- 前端 MapDimensionFilter 挂到 MapCanvas,切换即 emit 切片,清除恢复 /review
47 lines
2.2 KiB
Python
47 lines
2.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""A3 自测:直接调 build_slice(不启服务),校验真实库切片与空态。"""
|
|
import os, sys, json
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
for line in open('/root/cma-management/backend/.env'):
|
|
line = line.strip()
|
|
if '=' in line and not line.startswith('#') and line.split('=')[0].startswith(('CMA_DB', 'ERP_DB')):
|
|
k, v = line.split('=', 1); os.environ.setdefault(k, v)
|
|
|
|
from app.database import get_session_local
|
|
from app.api.map_dimensions import build_slice, _time_options, _store_options, _product_options
|
|
|
|
db = get_session_local()()
|
|
|
|
def brief(r):
|
|
print("slice:", r["slice"], "| empty:", r["empty"], "|", r["empty_reason"])
|
|
for o in r["objectives"]:
|
|
ks = [f'{k["kpi_code"]}={k["actual_value"]}/{k["target_value"]}@{k["achievement_pct"]}%({k["level"]},{k["aggregation"]}){"" if k["available"] else "[空:"+str(k["reason"])[:36]+"]"}' for k in o["kpis"]]
|
|
if ks:
|
|
print(" ", o["dimension_key"], o["name"], "|", "; ".join(ks))
|
|
|
|
print("=== 1) 2026-08 月切片 (map 65)")
|
|
brief(build_slice(db, 1, 65, "2026-08", None, None))
|
|
print("\n=== 2) 2026-Q3 季切片")
|
|
brief(build_slice(db, 1, 65, "2026-Q3", None, None))
|
|
print("\n=== 3) 2026 年切片")
|
|
brief(build_slice(db, 1, 65, "2026", None, None))
|
|
print("\n=== 4) 产品切片 酣客酱酒 @2026-08")
|
|
brief(build_slice(db, 1, 65, "2026-08", None, "酣客酱酒"))
|
|
print("\n=== 5) 产品切片 不存在产品 @2026-08")
|
|
brief(build_slice(db, 1, 65, "2026-08", None, "不存在的产品X"))
|
|
print("\n=== 6) 门店切片 一店 @2026-08")
|
|
brief(build_slice(db, 1, 65, "2026-08", "一店", None))
|
|
print("\n=== 7) entity 隔离: map65 + entity2")
|
|
try:
|
|
build_slice(db, 2, 65, "2026-08", None, None)
|
|
print("FAIL 未拦截")
|
|
except Exception as e:
|
|
print("OK 拦截:", type(e).__name__, getattr(e, "status_code", ""), getattr(e, "detail", e))
|
|
|
|
print("\n=== 8) options map65")
|
|
o = build_slice(db, 1, 65, None, None, None)
|
|
print("time dims:", json.dumps(o["dims"]["time"], ensure_ascii=False)[:300])
|
|
print("store:", json.dumps(o["dims"]["store"], ensure_ascii=False)[:300])
|
|
print("product:", json.dumps(o["dims"]["product"], ensure_ascii=False)[:300])
|
|
db.close()
|