Files
Neo-ZQYY/scripts/ops/daily_revenue_report.py
2026-03-15 10:15:02 +08:00

177 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""每日经营数据统计报告生成器
统计 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:00Asia/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}")