微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
617
scripts/ops/calibration_order_analysis.py
Normal file
617
scripts/ops/calibration_order_analysis.py
Normal file
@@ -0,0 +1,617 @@
|
||||
"""
|
||||
口径验证脚本:从真实订单案例出发,交叉验证 DWD 层金额/时长/绩效字段。
|
||||
|
||||
策略:
|
||||
1. 自动挑选 5 类有代表性的订单样本
|
||||
2. 对每个订单,拉取所有关联子表的实际数值
|
||||
3. 验证金额等式是否成立,标注差异
|
||||
4. 输出 Markdown 报告到 SYSTEM_LOG_ROOT
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
from pathlib import Path
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# ── 环境 ──
|
||||
load_dotenv(Path(__file__).resolve().parents[2] / ".env")
|
||||
DSN = os.environ.get("TEST_DB_DSN") or os.environ.get("PG_DSN")
|
||||
if not DSN:
|
||||
raise RuntimeError("TEST_DB_DSN / PG_DSN 未配置")
|
||||
|
||||
OUTPUT_DIR = Path(os.environ.get("SYSTEM_LOG_ROOT", ""))
|
||||
if not OUTPUT_DIR.is_dir():
|
||||
raise RuntimeError(f"SYSTEM_LOG_ROOT 不存在: {OUTPUT_DIR}")
|
||||
|
||||
D2 = lambda v: Decimal(str(v or 0)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
||||
NOW_STR = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
TODAY = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def q(cur, sql, params=None):
|
||||
"""执行查询,返回 dict 列表"""
|
||||
cur.execute(sql, params)
|
||||
if cur.description is None:
|
||||
return []
|
||||
cols = [d[0] for d in cur.description]
|
||||
return [dict(zip(cols, row)) for row in cur.fetchall()]
|
||||
|
||||
|
||||
def fmt(v):
|
||||
"""格式化值用于 Markdown 输出"""
|
||||
if v is None:
|
||||
return "NULL"
|
||||
if isinstance(v, Decimal):
|
||||
return f"{v:,.2f}"
|
||||
if isinstance(v, (int,)):
|
||||
return f"{v:,}"
|
||||
return str(v)
|
||||
|
||||
|
||||
def secs_to_hm(s):
|
||||
"""秒数转 'Xh Ym' 格式"""
|
||||
if s is None:
|
||||
return "NULL"
|
||||
h, m = divmod(int(s), 3600)
|
||||
m = m // 60
|
||||
return f"{h}h {m}m" if h else f"{m}m"
|
||||
|
||||
|
||||
# ── 第一步:挑选样本订单 ──
|
||||
SAMPLE_QUERIES = {
|
||||
"A_纯台费": """
|
||||
SELECT sh.order_settle_id
|
||||
FROM dwd.dwd_settlement_head sh
|
||||
WHERE sh.table_charge_money > 0
|
||||
AND sh.goods_money = 0
|
||||
AND sh.assistant_pd_money = 0
|
||||
AND sh.assistant_cx_money = 0
|
||||
AND sh.settle_type = 1
|
||||
ORDER BY sh.pay_time DESC NULLS LAST
|
||||
LIMIT 1
|
||||
""",
|
||||
"B_台费加商品": """
|
||||
SELECT sh.order_settle_id
|
||||
FROM dwd.dwd_settlement_head sh
|
||||
WHERE sh.table_charge_money > 0
|
||||
AND sh.goods_money > 0
|
||||
AND sh.assistant_pd_money = 0
|
||||
AND sh.settle_type = 1
|
||||
ORDER BY sh.pay_time DESC NULLS LAST
|
||||
LIMIT 1
|
||||
""",
|
||||
"C_台费加助教": """
|
||||
SELECT sh.order_settle_id
|
||||
FROM dwd.dwd_settlement_head sh
|
||||
WHERE sh.table_charge_money > 0
|
||||
AND sh.assistant_pd_money > 0
|
||||
AND sh.goods_money = 0
|
||||
AND sh.settle_type = 1
|
||||
ORDER BY sh.pay_time DESC NULLS LAST
|
||||
LIMIT 1
|
||||
""",
|
||||
"D_全要素": """
|
||||
SELECT sh.order_settle_id
|
||||
FROM dwd.dwd_settlement_head sh
|
||||
WHERE sh.table_charge_money > 0
|
||||
AND sh.goods_money > 0
|
||||
AND sh.assistant_pd_money > 0
|
||||
AND sh.settle_type = 1
|
||||
ORDER BY sh.pay_time DESC NULLS LAST
|
||||
LIMIT 1
|
||||
""",
|
||||
"E_含退款": """
|
||||
SELECT DISTINCT r.relate_id AS order_settle_id
|
||||
FROM dwd.dwd_refund r
|
||||
ORDER BY r.relate_id DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
# ── 第二步:拉取各子表数据 ──
|
||||
SUB_TABLE_QUERIES = {
|
||||
"settlement_head": """
|
||||
SELECT * FROM dwd.dwd_settlement_head WHERE order_settle_id = %s
|
||||
""",
|
||||
"settlement_head_ex": """
|
||||
SELECT * FROM dwd.dwd_settlement_head_ex WHERE order_settle_id = %s
|
||||
""",
|
||||
"table_fee_log": """
|
||||
SELECT * FROM dwd.dwd_table_fee_log WHERE order_settle_id = %s ORDER BY table_fee_log_id
|
||||
""",
|
||||
"table_fee_log_ex": """
|
||||
SELECT ex.* FROM dwd.dwd_table_fee_log_ex ex
|
||||
JOIN dwd.dwd_table_fee_log t ON t.table_fee_log_id = ex.table_fee_log_id
|
||||
WHERE t.order_settle_id = %s ORDER BY ex.table_fee_log_id
|
||||
""",
|
||||
"assistant_service_log": """
|
||||
SELECT * FROM dwd.dwd_assistant_service_log WHERE order_settle_id = %s ORDER BY assistant_service_id
|
||||
""",
|
||||
"assistant_service_log_ex": """
|
||||
SELECT ex.* FROM dwd.dwd_assistant_service_log_ex ex
|
||||
JOIN dwd.dwd_assistant_service_log a ON a.assistant_service_id = ex.assistant_service_id
|
||||
WHERE a.order_settle_id = %s ORDER BY ex.assistant_service_id
|
||||
""",
|
||||
"store_goods_sale": """
|
||||
SELECT * FROM dwd.dwd_store_goods_sale WHERE order_settle_id = %s ORDER BY store_goods_sale_id
|
||||
""",
|
||||
"store_goods_sale_ex": """
|
||||
SELECT ex.* FROM dwd.dwd_store_goods_sale_ex ex
|
||||
JOIN dwd.dwd_store_goods_sale g ON g.store_goods_sale_id = ex.store_goods_sale_id
|
||||
WHERE g.order_settle_id = %s ORDER BY ex.store_goods_sale_id
|
||||
""",
|
||||
"payment": """
|
||||
SELECT * FROM dwd.dwd_payment WHERE relate_id = %s ORDER BY payment_id
|
||||
""",
|
||||
"refund": """
|
||||
SELECT * FROM dwd.dwd_refund WHERE relate_id = %s ORDER BY refund_id
|
||||
""",
|
||||
"refund_ex": """
|
||||
SELECT ex.* FROM dwd.dwd_refund_ex ex
|
||||
JOIN dwd.dwd_refund r ON r.refund_id = ex.refund_id
|
||||
WHERE r.relate_id = %s ORDER BY ex.refund_id
|
||||
""",
|
||||
"table_fee_adjust": """
|
||||
SELECT * FROM dwd.dwd_table_fee_adjust WHERE order_settle_id = %s ORDER BY table_fee_adjust_id
|
||||
""",
|
||||
"table_fee_adjust_ex": """
|
||||
SELECT ex.* FROM dwd.dwd_table_fee_adjust_ex ex
|
||||
JOIN dwd.dwd_table_fee_adjust a ON a.table_fee_adjust_id = ex.table_fee_adjust_id
|
||||
WHERE a.order_settle_id = %s ORDER BY ex.table_fee_adjust_id
|
||||
""",
|
||||
"groupbuy_redemption": """
|
||||
SELECT * FROM dwd.dwd_groupbuy_redemption WHERE order_settle_id = %s ORDER BY redemption_id
|
||||
""",
|
||||
"member_balance_change": """
|
||||
SELECT * FROM dwd.dwd_member_balance_change
|
||||
WHERE balance_change_id IN (
|
||||
SELECT p.payment_id FROM dwd.dwd_payment p WHERE p.relate_id = %s
|
||||
)
|
||||
ORDER BY balance_change_id
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
# ── 第三步:等式验证逻辑 ──
|
||||
def verify_settlement_equation(sh, sh_ex, tfls, asls, gss, pays, refunds, adjusts):
|
||||
"""验证结算单级金额等式,返回 (检查项列表, 通过数, 失败数)"""
|
||||
checks = []
|
||||
|
||||
# 等式1: consume_money = table_charge_money + goods_money + assistant_pd_money + assistant_cx_money
|
||||
lhs = D2(sh.get("consume_money"))
|
||||
rhs = D2(sh.get("table_charge_money")) + D2(sh.get("goods_money")) + \
|
||||
D2(sh.get("assistant_pd_money")) + D2(sh.get("assistant_cx_money"))
|
||||
diff = lhs - rhs
|
||||
checks.append({
|
||||
"name": "消费总额 = 台费 + 商品 + 助教陪打 + 助教促销",
|
||||
"lhs": f"consume_money = {fmt(lhs)}",
|
||||
"rhs": f"table_charge + goods + asst_pd + asst_cx = {fmt(D2(sh.get('table_charge_money')))} + {fmt(D2(sh.get('goods_money')))} + {fmt(D2(sh.get('assistant_pd_money')))} + {fmt(D2(sh.get('assistant_cx_money')))} = {fmt(rhs)}",
|
||||
"diff": diff,
|
||||
"pass": diff == 0,
|
||||
})
|
||||
|
||||
# 等式2: 台费明细合计 vs settlement_head.table_charge_money
|
||||
tfl_sum = sum(D2(t.get("ledger_amount")) for t in tfls)
|
||||
sh_table = D2(sh.get("table_charge_money"))
|
||||
diff2 = sh_table - tfl_sum
|
||||
checks.append({
|
||||
"name": "结算单台费 vs 台费明细合计(ledger_amount)",
|
||||
"lhs": f"sh.table_charge_money = {fmt(sh_table)}",
|
||||
"rhs": f"SUM(tfl.ledger_amount) = {fmt(tfl_sum)} ({len(tfls)} 条)",
|
||||
"diff": diff2,
|
||||
"pass": diff2 == 0,
|
||||
})
|
||||
|
||||
# 等式3: 助教明细合计 vs settlement_head.assistant_pd_money + assistant_cx_money
|
||||
asl_pd = sum(D2(a.get("ledger_amount")) for a in asls if a.get("order_assistant_type") == 1)
|
||||
asl_cx = sum(D2(a.get("ledger_amount")) for a in asls if a.get("order_assistant_type") == 2)
|
||||
asl_null = sum(D2(a.get("ledger_amount")) for a in asls if a.get("order_assistant_type") is None)
|
||||
sh_pd = D2(sh.get("assistant_pd_money"))
|
||||
sh_cx = D2(sh.get("assistant_cx_money"))
|
||||
diff3_pd = sh_pd - asl_pd
|
||||
diff3_cx = sh_cx - asl_cx
|
||||
checks.append({
|
||||
"name": "结算单助教陪打 vs 助教明细(type=1)合计",
|
||||
"lhs": f"sh.assistant_pd_money = {fmt(sh_pd)}",
|
||||
"rhs": f"SUM(asl.ledger_amount WHERE type=1) = {fmt(asl_pd)} ({sum(1 for a in asls if a.get('order_assistant_type')==1)} 条)" + (f", type=NULL: {fmt(asl_null)} ({sum(1 for a in asls if a.get('order_assistant_type') is None)} 条)" if asl_null else ""),
|
||||
"diff": diff3_pd,
|
||||
"pass": diff3_pd == 0,
|
||||
})
|
||||
if sh_cx > 0 or asl_cx > 0:
|
||||
checks.append({
|
||||
"name": "结算单助教促销 vs 助教明细(type=2)合计",
|
||||
"lhs": f"sh.assistant_cx_money = {fmt(sh_cx)}",
|
||||
"rhs": f"SUM(asl.ledger_amount WHERE type=2) = {fmt(asl_cx)}",
|
||||
"diff": diff3_cx,
|
||||
"pass": diff3_cx == 0,
|
||||
})
|
||||
|
||||
# 等式4: 商品明细合计 vs settlement_head.goods_money
|
||||
gs_sum = sum(D2(g.get("ledger_amount")) for g in gss)
|
||||
sh_goods = D2(sh.get("goods_money"))
|
||||
diff4 = sh_goods - gs_sum
|
||||
checks.append({
|
||||
"name": "结算单商品 vs 商品明细合计(ledger_amount)",
|
||||
"lhs": f"sh.goods_money = {fmt(sh_goods)}",
|
||||
"rhs": f"SUM(gs.ledger_amount) = {fmt(gs_sum)} ({len(gss)} 条)",
|
||||
"diff": diff4,
|
||||
"pass": diff4 == 0,
|
||||
})
|
||||
|
||||
# 等式5: 支付合计 vs settlement_head.pay_amount + balance_amount + gift_card_amount
|
||||
pay_sum = sum(D2(p.get("pay_amount")) for p in pays)
|
||||
sh_pay_total = D2(sh.get("pay_amount")) + D2(sh.get("balance_amount")) + D2(sh.get("gift_card_amount"))
|
||||
diff5 = sh_pay_total - pay_sum
|
||||
checks.append({
|
||||
"name": "结算单(pay+balance+gift) vs 支付流水合计",
|
||||
"lhs": f"sh.(pay+balance+gift) = {fmt(D2(sh.get('pay_amount')))} + {fmt(D2(sh.get('balance_amount')))} + {fmt(D2(sh.get('gift_card_amount')))} = {fmt(sh_pay_total)}",
|
||||
"rhs": f"SUM(payment.pay_amount) = {fmt(pay_sum)} ({len(pays)} 笔)",
|
||||
"diff": diff5,
|
||||
"pass": diff5 == 0,
|
||||
})
|
||||
|
||||
# 等式6: 台费调整合计 vs settlement_head.adjust_amount
|
||||
adj_sum = sum(D2(a.get("ledger_amount")) for a in adjusts)
|
||||
sh_adj = D2(sh.get("adjust_amount"))
|
||||
diff6 = sh_adj - adj_sum
|
||||
checks.append({
|
||||
"name": "结算单调整额 vs 台费调整明细合计",
|
||||
"lhs": f"sh.adjust_amount = {fmt(sh_adj)}",
|
||||
"rhs": f"SUM(adj.ledger_amount) = {fmt(adj_sum)} ({len(adjusts)} 条)",
|
||||
"diff": diff6,
|
||||
"pass": diff6 == 0,
|
||||
})
|
||||
|
||||
passed = sum(1 for c in checks if c["pass"])
|
||||
failed = len(checks) - passed
|
||||
return checks, passed, failed
|
||||
|
||||
|
||||
def verify_table_fee_equations(tfls):
|
||||
"""验证台费明细级等式,返回检查列表"""
|
||||
checks = []
|
||||
for i, t in enumerate(tfls):
|
||||
# ledger_amount ≈ ledger_unit_price × real_table_use_seconds / 3600
|
||||
price = D2(t.get("ledger_unit_price"))
|
||||
secs = int(t.get("real_table_use_seconds") or 0)
|
||||
calc = D2(price * secs / 3600)
|
||||
actual = D2(t.get("ledger_amount"))
|
||||
diff = actual - calc
|
||||
checks.append({
|
||||
"name": f"台费#{i+1} ledger_amount vs 单价×时长/3600",
|
||||
"lhs": f"ledger_amount = {fmt(actual)}",
|
||||
"rhs": f"{fmt(price)} × {secs}s / 3600 = {fmt(calc)}",
|
||||
"diff": diff,
|
||||
"pass": abs(diff) <= Decimal("0.02"), # 允许 ±0.02 四舍五入误差
|
||||
})
|
||||
# real_table_charge_money = ledger_amount - adjust_amount - ...
|
||||
real = D2(t.get("real_table_charge_money"))
|
||||
adj = D2(t.get("adjust_amount"))
|
||||
coup = D2(t.get("coupon_promotion_amount"))
|
||||
mem = D2(t.get("member_discount_amount"))
|
||||
act_disc = D2(t.get("activity_discount_amount"))
|
||||
calc_real = actual - adj - coup - mem - act_disc
|
||||
diff_r = real - calc_real
|
||||
checks.append({
|
||||
"name": f"台费#{i+1} real_charge vs ledger-adj-coup-mem-act",
|
||||
"lhs": f"real_table_charge_money = {fmt(real)}",
|
||||
"rhs": f"{fmt(actual)} - {fmt(adj)} - {fmt(coup)} - {fmt(mem)} - {fmt(act_disc)} = {fmt(calc_real)}",
|
||||
"diff": diff_r,
|
||||
"pass": abs(diff_r) <= Decimal("0.02"),
|
||||
})
|
||||
# ledger_count vs real_table_use_seconds
|
||||
lc = int(t.get("ledger_count") or 0)
|
||||
rts = int(t.get("real_table_use_seconds") or 0)
|
||||
checks.append({
|
||||
"name": f"台费#{i+1} ledger_count vs real_table_use_seconds",
|
||||
"lhs": f"ledger_count = {lc} ({secs_to_hm(lc)})",
|
||||
"rhs": f"real_table_use_seconds = {rts} ({secs_to_hm(rts)})",
|
||||
"diff": lc - rts,
|
||||
"pass": True, # 仅记录,不判定
|
||||
"info_only": True,
|
||||
})
|
||||
return checks
|
||||
|
||||
|
||||
def verify_assistant_equations(asls):
|
||||
"""验证助教服务级等式"""
|
||||
checks = []
|
||||
for i, a in enumerate(asls):
|
||||
price = D2(a.get("ledger_unit_price"))
|
||||
inc_secs = int(a.get("income_seconds") or 0)
|
||||
calc = D2(price * inc_secs / 3600)
|
||||
actual = D2(a.get("ledger_amount"))
|
||||
diff = actual - calc
|
||||
checks.append({
|
||||
"name": f"助教#{i+1} ledger_amount vs 单价×计费秒/3600",
|
||||
"lhs": f"ledger_amount = {fmt(actual)}",
|
||||
"rhs": f"{fmt(price)} × {inc_secs}s / 3600 = {fmt(calc)}",
|
||||
"diff": diff,
|
||||
"pass": abs(diff) <= Decimal("0.02"),
|
||||
})
|
||||
# income_seconds vs real_use_seconds
|
||||
real_secs = int(a.get("real_use_seconds") or 0)
|
||||
checks.append({
|
||||
"name": f"助教#{i+1} income_seconds vs real_use_seconds",
|
||||
"lhs": f"income_seconds = {inc_secs} ({secs_to_hm(inc_secs)})",
|
||||
"rhs": f"real_use_seconds = {real_secs} ({secs_to_hm(real_secs)})",
|
||||
"diff": inc_secs - real_secs,
|
||||
"pass": True,
|
||||
"info_only": True,
|
||||
})
|
||||
# projected_income / ledger_amount = 分成比例
|
||||
proj = D2(a.get("projected_income"))
|
||||
if actual > 0:
|
||||
ratio = proj / actual
|
||||
checks.append({
|
||||
"name": f"助教#{i+1} 分成比例 projected_income/ledger_amount",
|
||||
"lhs": f"projected_income = {fmt(proj)}",
|
||||
"rhs": f"ledger_amount = {fmt(actual)}, 比例 = {ratio:.4f}",
|
||||
"diff": Decimal("0"),
|
||||
"pass": True,
|
||||
"info_only": True,
|
||||
})
|
||||
return checks
|
||||
|
||||
|
||||
def verify_goods_equations(gss):
|
||||
"""验证商品销售级等式"""
|
||||
checks = []
|
||||
for i, g in enumerate(gss):
|
||||
price = D2(g.get("ledger_unit_price"))
|
||||
count = int(g.get("ledger_count") or 0)
|
||||
calc = D2(price * count)
|
||||
actual = D2(g.get("ledger_amount"))
|
||||
diff = actual - calc
|
||||
checks.append({
|
||||
"name": f"商品#{i+1}({g.get('ledger_name','?')}) ledger_amount vs 单价×数量",
|
||||
"lhs": f"ledger_amount = {fmt(actual)}",
|
||||
"rhs": f"{fmt(price)} × {count} = {fmt(calc)}",
|
||||
"diff": diff,
|
||||
"pass": abs(diff) <= Decimal("0.02"),
|
||||
})
|
||||
real = D2(g.get("real_goods_money"))
|
||||
disc = D2(g.get("discount_money"))
|
||||
coup = D2(g.get("coupon_share_money"))
|
||||
calc_real = actual - disc - coup
|
||||
diff_r = real - calc_real
|
||||
checks.append({
|
||||
"name": f"商品#{i+1} real_goods_money vs ledger-disc-coup",
|
||||
"lhs": f"real_goods_money = {fmt(real)}",
|
||||
"rhs": f"{fmt(actual)} - {fmt(disc)} - {fmt(coup)} = {fmt(calc_real)}",
|
||||
"diff": diff_r,
|
||||
"pass": abs(diff_r) <= Decimal("0.02"),
|
||||
})
|
||||
return checks
|
||||
|
||||
|
||||
# ── 第四步:数据新鲜度查询 ──
|
||||
FRESHNESS_SQL = """
|
||||
SELECT 'dwd_settlement_head' AS tbl, MAX(create_time) AS max_ct, MAX(pay_time) AS max_biz, COUNT(*) AS cnt FROM dwd.dwd_settlement_head
|
||||
UNION ALL SELECT 'dwd_table_fee_log', MAX(create_time), MAX(start_use_time), COUNT(*) FROM dwd.dwd_table_fee_log
|
||||
UNION ALL SELECT 'dwd_assistant_service_log', MAX(create_time), MAX(start_use_time), COUNT(*) FROM dwd.dwd_assistant_service_log
|
||||
UNION ALL SELECT 'dwd_store_goods_sale', MAX(create_time), NULL, COUNT(*) FROM dwd.dwd_store_goods_sale
|
||||
UNION ALL SELECT 'dwd_payment', MAX(create_time), MAX(pay_time), COUNT(*) FROM dwd.dwd_payment
|
||||
UNION ALL SELECT 'dwd_refund', MAX(create_time), MAX(pay_time), COUNT(*) FROM dwd.dwd_refund
|
||||
UNION ALL SELECT 'dwd_groupbuy_redemption', MAX(create_time), NULL, COUNT(*) FROM dwd.dwd_groupbuy_redemption
|
||||
UNION ALL SELECT 'dwd_table_fee_adjust', MAX(adjust_time), NULL, COUNT(*) FROM dwd.dwd_table_fee_adjust
|
||||
UNION ALL SELECT 'dwd_recharge_order', MAX(create_time), MAX(pay_time), COUNT(*) FROM dwd.dwd_recharge_order
|
||||
UNION ALL SELECT 'dwd_member_balance_change', MAX(change_time), NULL, COUNT(*) FROM dwd.dwd_member_balance_change
|
||||
ORDER BY tbl
|
||||
"""
|
||||
|
||||
|
||||
# ── 第五步:生成报告 ──
|
||||
def render_checks_table(checks):
|
||||
"""渲染检查结果为 Markdown 表格"""
|
||||
lines = []
|
||||
lines.append("| # | 检查项 | 左侧 | 右侧 | 差异 | 结果 |")
|
||||
lines.append("|---|--------|------|------|------|------|")
|
||||
for i, c in enumerate(checks, 1):
|
||||
info = c.get("info_only", False)
|
||||
if info:
|
||||
result = "ℹ️ 信息"
|
||||
elif c["pass"]:
|
||||
result = "✅ 通过"
|
||||
else:
|
||||
result = "❌ 不等"
|
||||
diff_str = fmt(c["diff"]) if not info else str(c["diff"])
|
||||
lines.append(f"| {i} | {c['name']} | {c['lhs']} | {c['rhs']} | {diff_str} | {result} |")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render_row_detail(label, rows, key_fields):
|
||||
"""渲染子表数据明细"""
|
||||
if not rows:
|
||||
return f"\n**{label}**:无数据\n"
|
||||
lines = [f"\n**{label}**({len(rows)} 条)\n"]
|
||||
# 只输出 key_fields 中存在的字段
|
||||
cols = [f for f in key_fields if f in rows[0]]
|
||||
lines.append("| " + " | ".join(cols) + " |")
|
||||
lines.append("| " + " | ".join(["---"] * len(cols)) + " |")
|
||||
for r in rows:
|
||||
vals = [fmt(r.get(c)) for c in cols]
|
||||
lines.append("| " + " | ".join(vals) + " |")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ── 主流程 ──
|
||||
def main():
|
||||
conn = psycopg2.connect(DSN)
|
||||
conn.set_session(readonly=True)
|
||||
cur = conn.cursor()
|
||||
|
||||
report = []
|
||||
report.append(f"# DWD 口径验证报告(订单案例分析)\n")
|
||||
report.append(f"> 生成时间:{NOW_STR}")
|
||||
report.append(f"> 数据来源:`test_etl_feiqiu` 测试库 `dwd` schema")
|
||||
report.append(f"> 方法:从真实订单出发,拉取各子表实际数值,交叉验证金额等式\n")
|
||||
|
||||
# ── 数据新鲜度 ──
|
||||
report.append("---\n\n## 零、数据新鲜度\n")
|
||||
freshness = q(cur, FRESHNESS_SQL)
|
||||
report.append("| 表 | 记录数 | 最晚 create_time | 最晚业务时间 |")
|
||||
report.append("|---|--------|-----------------|-------------|")
|
||||
for f in freshness:
|
||||
ct = str(f["max_ct"])[:19] if f["max_ct"] else "—"
|
||||
bt = str(f["max_biz"])[:19] if f["max_biz"] else "—"
|
||||
report.append(f"| `{f['tbl']}` | {f['cnt']:,} | {ct} | {bt} |")
|
||||
|
||||
# ── 挑选样本 ──
|
||||
samples = {}
|
||||
for label, sql in SAMPLE_QUERIES.items():
|
||||
rows = q(cur, sql)
|
||||
if rows:
|
||||
samples[label] = rows[0]["order_settle_id"]
|
||||
print(f" 样本 {label}: order_settle_id = {rows[0]['order_settle_id']}")
|
||||
else:
|
||||
print(f" 样本 {label}: 未找到匹配订单")
|
||||
|
||||
total_pass = 0
|
||||
total_fail = 0
|
||||
|
||||
for label, settle_id in samples.items():
|
||||
report.append(f"\n---\n\n## 样本 {label}(order_settle_id = `{settle_id}`)\n")
|
||||
|
||||
# 拉取所有子表
|
||||
data = {}
|
||||
for tbl, sql in SUB_TABLE_QUERIES.items():
|
||||
data[tbl] = q(cur, sql, (settle_id,))
|
||||
|
||||
sh = data["settlement_head"][0] if data["settlement_head"] else {}
|
||||
sh_ex = data["settlement_head_ex"][0] if data["settlement_head_ex"] else {}
|
||||
|
||||
if not sh:
|
||||
report.append("> ⚠️ 结算单主表无数据,跳过\n")
|
||||
continue
|
||||
|
||||
# 概览
|
||||
report.append(f"- 门店:`{sh.get('site_name', '?')}`")
|
||||
report.append(f"- 支付时间:{str(sh.get('pay_time', '?'))[:19]}")
|
||||
report.append(f"- 结算类型:{sh.get('settle_type')}")
|
||||
report.append(f"- 会员:{sh.get('member_name', '无')} ({sh.get('member_phone', '')})")
|
||||
report.append(f"- 消费总额:{fmt(sh.get('consume_money'))}")
|
||||
report.append(f"- 台费明细:{len(data['table_fee_log'])} 条")
|
||||
report.append(f"- 助教服务:{len(data['assistant_service_log'])} 条")
|
||||
report.append(f"- 商品销售:{len(data['store_goods_sale'])} 条")
|
||||
report.append(f"- 支付流水:{len(data['payment'])} 笔")
|
||||
report.append(f"- 退款流水:{len(data['refund'])} 笔")
|
||||
report.append(f"- 台费调整:{len(data['table_fee_adjust'])} 条")
|
||||
report.append(f"- 团购核销:{len(data['groupbuy_redemption'])} 条\n")
|
||||
|
||||
# 结算单级金额全貌
|
||||
report.append("### 结算单金额全貌\n")
|
||||
amt_fields = [
|
||||
("consume_money", "消费总额"), ("table_charge_money", "台费"),
|
||||
("goods_money", "商品原价"), ("real_goods_money", "商品实收"),
|
||||
("assistant_pd_money", "助教陪打"), ("assistant_cx_money", "助教促销"),
|
||||
("adjust_amount", "调整额"), ("pay_amount", "实付"),
|
||||
("balance_amount", "余额支付"), ("gift_card_amount", "赠送卡"),
|
||||
("recharge_card_amount", "充值卡"), ("coupon_amount", "优惠券"),
|
||||
("point_amount", "积分"), ("member_discount_amount", "会员折扣"),
|
||||
("rounding_amount", "抹零"),
|
||||
]
|
||||
report.append("| 字段 | 含义 | 值 |")
|
||||
report.append("|------|------|-----|")
|
||||
for field, desc in amt_fields:
|
||||
report.append(f"| `{field}` | {desc} | {fmt(sh.get(field))} |")
|
||||
|
||||
if sh_ex:
|
||||
ex_fields = [
|
||||
("service_money", "服务费"), ("cash_amount", "现金"),
|
||||
("card_amount", "银行卡"), ("online_amount", "线上"),
|
||||
("refund_amount", "退款"), ("prepay_money", "预付"),
|
||||
]
|
||||
report.append("\n**扩展表(_ex)**\n")
|
||||
report.append("| 字段 | 含义 | 值 |")
|
||||
report.append("|------|------|-----|")
|
||||
for field, desc in ex_fields:
|
||||
report.append(f"| `{field}` | {desc} | {fmt(sh_ex.get(field))} |")
|
||||
|
||||
# 子表明细
|
||||
tfl_keys = ["table_fee_log_id", "ledger_name", "ledger_unit_price", "ledger_count",
|
||||
"real_table_use_seconds", "ledger_amount", "real_table_charge_money",
|
||||
"adjust_amount", "start_use_time", "ledger_end_time", "add_clock_seconds"]
|
||||
report.append(render_row_detail("台费明细", data["table_fee_log"], tfl_keys))
|
||||
|
||||
asl_keys = ["assistant_service_id", "nickname", "skill_name", "order_assistant_type",
|
||||
"level_name", "ledger_unit_price", "income_seconds", "real_use_seconds",
|
||||
"ledger_amount", "projected_income", "add_clock", "start_use_time", "last_use_time"]
|
||||
report.append(render_row_detail("助教服务明细", data["assistant_service_log"], asl_keys))
|
||||
|
||||
gs_keys = ["store_goods_sale_id", "ledger_name", "ledger_unit_price", "ledger_count",
|
||||
"ledger_amount", "discount_money", "real_goods_money", "coupon_share_money", "cost_money"]
|
||||
report.append(render_row_detail("商品销售明细", data["store_goods_sale"], gs_keys))
|
||||
|
||||
pay_keys = ["payment_id", "pay_amount", "payment_method", "pay_time"]
|
||||
report.append(render_row_detail("支付流水", data["payment"], pay_keys))
|
||||
|
||||
ref_keys = ["refund_id", "pay_amount", "payment_method", "pay_time", "channel_fee"]
|
||||
report.append(render_row_detail("退款流水", data["refund"], ref_keys))
|
||||
|
||||
adj_keys = ["table_fee_adjust_id", "ledger_amount", "table_name"]
|
||||
report.append(render_row_detail("台费调整", data["table_fee_adjust"], adj_keys))
|
||||
|
||||
# 等式验证
|
||||
report.append("\n### 等式验证\n")
|
||||
report.append("#### 结算单级\n")
|
||||
checks, p, f = verify_settlement_equation(
|
||||
sh, sh_ex, data["table_fee_log"], data["assistant_service_log"],
|
||||
data["store_goods_sale"], data["payment"], data["refund"], data["table_fee_adjust"]
|
||||
)
|
||||
report.append(render_checks_table(checks))
|
||||
total_pass += p
|
||||
total_fail += f
|
||||
|
||||
if data["table_fee_log"]:
|
||||
report.append("\n#### 台费明细级\n")
|
||||
tc = verify_table_fee_equations(data["table_fee_log"])
|
||||
report.append(render_checks_table(tc))
|
||||
total_pass += sum(1 for c in tc if c["pass"] and not c.get("info_only"))
|
||||
total_fail += sum(1 for c in tc if not c["pass"] and not c.get("info_only"))
|
||||
|
||||
if data["assistant_service_log"]:
|
||||
report.append("\n#### 助教服务级\n")
|
||||
ac = verify_assistant_equations(data["assistant_service_log"])
|
||||
report.append(render_checks_table(ac))
|
||||
total_pass += sum(1 for c in ac if c["pass"] and not c.get("info_only"))
|
||||
total_fail += sum(1 for c in ac if not c["pass"] and not c.get("info_only"))
|
||||
|
||||
if data["store_goods_sale"]:
|
||||
report.append("\n#### 商品销售级\n")
|
||||
gc = verify_goods_equations(data["store_goods_sale"])
|
||||
report.append(render_checks_table(gc))
|
||||
total_pass += sum(1 for c in gc if c["pass"] and not c.get("info_only"))
|
||||
total_fail += sum(1 for c in gc if not c["pass"] and not c.get("info_only"))
|
||||
|
||||
# 汇总
|
||||
report.append(f"\n---\n\n## 汇总\n")
|
||||
report.append(f"- 样本数:{len(samples)}")
|
||||
report.append(f"- 等式检查通过:{total_pass}")
|
||||
report.append(f"- 等式检查失败:{total_fail}")
|
||||
if total_fail > 0:
|
||||
report.append(f"\n> ⚠️ 存在 {total_fail} 项等式不成立,需进一步排查口径定义。")
|
||||
else:
|
||||
report.append(f"\n> ✅ 所有等式验证通过。")
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
# 写入文件
|
||||
out_path = OUTPUT_DIR / f"{TODAY}__calibration_order_analysis.md"
|
||||
out_path.write_text("\n".join(report), encoding="utf-8")
|
||||
print(f"\n报告已写入: {out_path}")
|
||||
|
||||
# 同时写入 docs/reports/ 作为文档归档
|
||||
doc_path = Path(__file__).resolve().parents[2] / "docs" / "reports" / "dwd-calibration-order-analysis.md"
|
||||
doc_path.write_text("\n".join(report), encoding="utf-8")
|
||||
print(f"文档归档: {doc_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user