"""每日经营数据统计报告生成器 统计 3月1日至当天的每日经营数据,按日切 08:00 ~ 次日 08:00, 输出 markdown 表格到 docs/reports/daily-revenue-latest.md """ import os, sys from pathlib import Path from datetime import date, timedelta from decimal import Decimal from dotenv import load_dotenv load_dotenv(Path(__file__).resolve().parents[2] / ".env") dsn = os.environ.get("TEST_DB_DSN") if not dsn: dsn = os.environ.get("PG_DSN", "") if "etl_feiqiu" in dsn: dsn = dsn.replace("etl_feiqiu", "test_etl_feiqiu") if not dsn: sys.exit("ERROR: TEST_DB_DSN / PG_DSN not set") import psycopg2 # 起始日期固定 3月1日,结束日期为今天+1(覆盖当天营业日) START_DATE = date(2026, 3, 1) today = date.today() END_DATE = today + timedelta(days=1) # 生成日期序列 days = [] d = START_DATE while d <= today: days.append(d) d += timedelta(days=1) sql = """ WITH day_series AS ( SELECT unnest(%(dates)s::date[]) AS day_date ), day_label AS ( SELECT day_date, to_char(day_date, 'MM月DD日') AS day_name, (day_date::text || ' 08:00:00+08')::timestamptz AS day_start, ((day_date + interval '1 day')::date::text || ' 08:00:00+08')::timestamptz AS day_end FROM day_series ), cash_online AS ( SELECT dl.day_date, COALESCE(SUM(h.pay_amount), 0) AS v FROM day_label dl LEFT JOIN dwd.dwd_settlement_head h ON h.create_time >= dl.day_start AND h.create_time < dl.day_end AND h.settle_type IN (1, 3) GROUP BY dl.day_date ), recharge AS ( SELECT dl.day_date, COALESCE(SUM(ro.pay_amount), 0) AS v FROM day_label dl LEFT JOIN dwd.dwd_recharge_order ro ON ro.create_time >= dl.day_start AND ro.create_time < dl.day_end AND ro.settle_type = 5 GROUP BY dl.day_date ), groupbuy AS ( SELECT dl.day_date, COALESCE(SUM(pcr.sale_price * 0.75), 0) AS v FROM day_label dl LEFT JOIN dwd.dwd_platform_coupon_redemption pcr ON pcr.create_time >= dl.day_start AND pcr.create_time < dl.day_end AND pcr.is_delete = 0 GROUP BY dl.day_date ), member_guest AS ( SELECT dl.day_date, COUNT(*) FILTER (WHERE h.member_id > 0) AS member_v, COUNT(*) FILTER (WHERE h.member_id = 0) AS guest_v FROM day_label dl LEFT JOIN dwd.dwd_settlement_head h ON h.create_time >= dl.day_start AND h.create_time < dl.day_end AND h.settle_type IN (1, 3) GROUP BY dl.day_date ), new_member AS ( SELECT dl.day_date, COUNT(DISTINCT m.member_id) AS v FROM day_label dl LEFT JOIN dwd.dim_member m ON m.create_time >= dl.day_start AND m.create_time < dl.day_end AND m.scd2_is_current = 1 GROUP BY dl.day_date ), old_recharge AS ( SELECT dl.day_date, COUNT(DISTINCT ro.member_id) FILTER (WHERE ro.member_id > 0) AS persons, COALESCE(SUM(ro.pay_amount) FILTER (WHERE ro.member_id > 0), 0) AS amount FROM day_label dl LEFT JOIN dwd.dwd_recharge_order ro ON ro.create_time >= dl.day_start AND ro.create_time < dl.day_end AND ro.settle_type = 5 GROUP BY dl.day_date ) SELECT dl.day_name, ROUND(co.v + r.v + gb.v, 2) AS total, co.v AS cash_online, r.v AS recharge, ROUND(gb.v, 2) AS groupbuy, mg.member_v, mg.guest_v, nm.v AS new_members, omr.persons AS old_recharge_persons, omr.amount AS old_recharge_amount FROM day_label dl JOIN cash_online co USING (day_date) JOIN recharge r USING (day_date) JOIN groupbuy gb USING (day_date) JOIN member_guest mg USING (day_date) JOIN new_member nm USING (day_date) JOIN old_recharge omr USING (day_date) ORDER BY dl.day_date; """ conn = psycopg2.connect(dsn) cur = conn.cursor() cur.execute(sql, {"dates": [d.isoformat() for d in days]}) rows = cur.fetchall() conn.close() def fmt_money(v): """格式化金额:千分位,两位小数""" return f"{v:,.2f}" def fmt_int(v): return str(int(v)) # 生成 markdown lines = [] lines.append(f"# 每日经营数据统计(2026-03-01 ~ {today.strftime('%Y-%m-%d')})") lines.append("") lines.append("> 数据来源:test_etl_feiqiu (DWD schema)") lines.append(f"> 统计时间:{today.isoformat()}") lines.append("> 日切规则:每天 08:00 ~ 次日 08:00(Asia/Shanghai)") lines.append(f"> 统计范围:2026-03-01 08:00 ~ {(today + timedelta(days=1)).isoformat()} 08:00(共 {len(days)} 个营业日)") lines.append("") lines.append("## 统计结果") lines.append("") lines.append("| 日期 | 实收合计 | 实收(纸币+线上) | 充值 | 团购结算 | 会员到店人次 | 散客到店人次 | 新会员注册数 | 老会员充值人数 | 老会员充值总额 |") lines.append("|------|--------:|-----------------:|-----:|---------:|------------:|------------:|------------:|--------------:|--------------:|") for row in rows: day_name, total, cash_online, recharge, groupbuy, member_v, guest_v, new_members, old_persons, old_amount = row lines.append( f"| {day_name} | {fmt_money(total)} | {fmt_money(cash_online)} | {fmt_money(recharge)} | {fmt_money(groupbuy)} " f"| {fmt_int(member_v)} | {fmt_int(guest_v)} | {fmt_int(new_members)} | {fmt_int(old_persons)} | {fmt_money(old_amount)} |" ) lines.append("") lines.append('> 金额单位:元。实收合计 = 实收(纸币+线上) + 充值 + 团购结算') lines.append("") lines.append('## "实收"口径定义') lines.append("") lines.append("实收 = 外部渠道实际汇入门店的资金,包括三类:") lines.append("1. 纸币+线上:客户通过银行卡、微信、支付宝、现金直接支付的消费款") lines.append("2. 充值:客户通过外部渠道充入储值卡的金额") lines.append("3. 团购结算:团购平台(美团/抖音)结算给门店的金额") lines.append("") lines.append("以下不计入实收:") lines.append("- 储值卡余额扣款(`balance_amount`)— 平台内部划转,资金在充值时已入账") lines.append("- 团购券面值抵扣(`coupon_amount`)— 券面值 ≠ 实际结算额,门店收到的是平台结算价") lines.append("- 会员折扣、台费调整、抹零 — 减免项,无资金流入") # 写入文件 repo_root = Path(__file__).resolve().parents[2] out_path = repo_root / "docs" / "reports" / "daily-revenue-latest.md" out_path.write_text("\n".join(lines), encoding="utf-8") print(f"报告已生成: {out_path}")