🤖 每日自动备份 - 2026-04-23 08:00:02

This commit is contained in:
小溪 2026-04-23 08:00:02 +08:00
parent 0d85c270aa
commit 88fe4c9946
10 changed files with 775 additions and 0 deletions

View File

@ -12,3 +12,4 @@ vala-component-practice-stat 8e768e2641019d27bd41f4647d2d90f24182a0554dad5ad9f41
cron-schedule e103cbb1806b28c891b9c856963325086ecaff32edec208f0a841865f26e8f3e
refund-user-learning-analysis 648fd4ae2b29167fd66eab4245bdaaef00242db3131f4919cc02f07ca2a9b59c
phone-chapter-query ac429b4da5a89db16efdf1066edf4ecb1c050b93aff20dd4c652af5f5568e44f
vala-order-amortization-stat 9a07c4466a98a55e62d0b0c4948ec709fda7a8e607c9497a0cfba32b77046c50

View File

@ -64,6 +64,9 @@
- `course_lesson`:课时,如 L01、L02、L03、L04、L05每单元固定5节课
- 示例L1 S0 U00 L01 → id=343L2 S0 U00 L01 → id=55
- [李承龙确认] 以后匹配课程统一使用此表,不再从 MySQL 配置表手动查找
- 订单表与退费表关联规则:
- `bi_vala_order.trade_no``bi_refund_order.trade_no` 关联
- `bi_vala_order.out_trade_no``bi_refund_order.out_trade_no` 关联
## 业务知识库
- **已收集13个常用SQL查询模板**

View File

@ -0,0 +1,29 @@
import psycopg2
# 读取密码
DB_PASSWORD = ""
with open('/root/.openclaw/workspace/secrets.env', 'r') as f:
for line in f:
if line.startswith('PG_ONLINE_PASSWORD='):
DB_PASSWORD = line.strip().split('=', 1)[1].strip('"\'')
break
# 数据库连接
conn = psycopg2.connect(
host='bj-postgres-16pob4sg.sql.tencentcdb.com',
port=28591,
user='ai_member',
password=DB_PASSWORD,
database='vala_bi'
)
cur = conn.cursor()
# 查询表结构
cur.execute("SELECT column_name FROM information_schema.columns WHERE table_name = 'bi_vala_seasonal_ticket'")
columns = cur.fetchall()
print("bi_vala_seasonal_ticket表字段")
for col in columns:
print(col[0])
cur.close()
conn.close()

30
scripts/check_tables.py Normal file
View File

@ -0,0 +1,30 @@
import psycopg2
# 读取密码
DB_PASSWORD = ""
with open('/root/.openclaw/workspace/secrets.env', 'r') as f:
for line in f:
if line.startswith('PG_ONLINE_PASSWORD='):
DB_PASSWORD = line.strip().split('=', 1)[1].strip('"\'')
break
# 数据库连接
conn = psycopg2.connect(
host='bj-postgres-16pob4sg.sql.tencentcdb.com',
port=28591,
user='ai_member',
password=DB_PASSWORD,
database='vala_bi'
)
cur = conn.cursor()
# 查询所有表
cur.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
tables = cur.fetchall()
print("vala_bi库public模式下的表")
for table in tables:
if 'seasonal' in table[0] or 'ticket' in table[0]:
print(f"{table[0]}")
cur.close()
conn.close()

View File

@ -0,0 +1,47 @@
import psycopg2
import pandas as pd
# 读取密码
DB_PASSWORD = ""
with open('/root/.openclaw/workspace/secrets.env', 'r') as f:
for line in f:
if line.startswith('PG_ONLINE_PASSWORD='):
DB_PASSWORD = line.strip().split('=', 1)[1].strip('"\'')
break
# 数据库连接
conn = psycopg2.connect(
host='bj-postgres-16pob4sg.sql.tencentcdb.com',
port=28591,
user='ai_member',
password=DB_PASSWORD,
database='vala_bi'
)
# 查询2月下单3月退费的订单
sql = """
SELECT
o.out_trade_no AS order_no,
o.created_at AS order_create_time,
o.pay_amount_int / 100 AS pay_amount,
o.order_status,
r.updated_at AS refund_time,
CAST(r.refund_amount AS NUMERIC) / 100 AS refund_amount,
CASE r.refund_type WHEN 1 THEN '仅退款' WHEN 2 THEN '全额退货退款' WHEN 3 THEN '部分退货退款' END AS refund_type
FROM bi_vala_order o
JOIN bi_refund_order r ON o.out_trade_no = r.trade_no
WHERE
o.created_at BETWEEN '2026-02-01 00:00:00' AND '2026-02-28 23:59:59'
AND r.updated_at BETWEEN '2026-03-01 00:00:00' AND '2026-03-31 23:59:59'
AND r.status = 3
"""
df = pd.read_sql(sql, conn)
conn.close()
print("🔍 2026年2月下单、2026年3月退费的订单列表")
print(f"共找到{len(df)}条符合条件的记录:")
if not df.empty:
print(df.to_string(index=False))
else:
print("无符合条件的记录")

View File

@ -0,0 +1,47 @@
import psycopg2
import pandas as pd
# 读取密码
DB_PASSWORD = ""
with open('/root/.openclaw/workspace/secrets.env', 'r') as f:
for line in f:
if line.startswith('PG_ONLINE_PASSWORD='):
DB_PASSWORD = line.strip().split('=', 1)[1].strip('"\'')
break
# 数据库连接
conn = psycopg2.connect(
host='bj-postgres-16pob4sg.sql.tencentcdb.com',
port=28591,
user='ai_member',
password=DB_PASSWORD,
database='vala_bi'
)
# 查询2025年9月到2026年2月下单3月退费的订单
sql = """
SELECT
o.out_trade_no AS order_no,
DATE(o.created_at) AS order_create_date,
o.pay_amount_int / 100 AS pay_amount,
o.order_status,
DATE(r.updated_at) AS refund_date,
CAST(r.refund_amount AS NUMERIC) / 100 AS refund_amount,
CASE r.refund_type WHEN 1 THEN '仅退款' WHEN 2 THEN '全额退货退款' WHEN 3 THEN '部分退货退款' END AS refund_type
FROM bi_vala_order o
JOIN bi_refund_order r ON o.out_trade_no = r.trade_no
WHERE
o.created_at BETWEEN '2025-09-01 00:00:00' AND '2026-02-28 23:59:59'
AND r.updated_at BETWEEN '2026-03-01 00:00:00' AND '2026-03-31 23:59:59'
AND r.status = 3
"""
df = pd.read_sql(sql, conn)
conn.close()
print("🔍 2025年9月-2026年2月下单、2026年3月退费的订单列表")
print(f"共找到{len(df)}条符合条件的记录:")
if not df.empty:
print(df.to_string(index=False))
else:
print("无符合条件的记录")

View File

@ -0,0 +1,51 @@
import psycopg2
import pandas as pd
# 读取密码
DB_PASSWORD = ""
with open('/root/.openclaw/workspace/secrets.env', 'r') as f:
for line in f:
if line.startswith('PG_ONLINE_PASSWORD='):
DB_PASSWORD = line.strip().split('=', 1)[1].strip('"\'')
break
# 数据库连接
conn = psycopg2.connect(
host='bj-postgres-16pob4sg.sql.tencentcdb.com',
port=28591,
user='ai_member',
password=DB_PASSWORD,
database='vala_bi'
)
# 查询指定订单的所有退费记录(不限制状态)
order_no = "dd202602111013471770776027390252"
sql = """
SELECT
r.id AS refund_id,
r.trade_no AS order_no,
r.created_at AS refund_apply_time,
r.updated_at AS refund_update_time,
CAST(r.refund_amount AS NUMERIC) / 100 AS refund_amount,
r.status AS refund_status_code,
CASE r.status
WHEN 0 THEN '待审核'
WHEN 1 THEN '审核通过'
WHEN 2 THEN '审核拒绝'
WHEN 3 THEN '退款成功'
WHEN 4 THEN '退款失败'
ELSE '其他状态'
END AS refund_status_name,
CASE r.refund_type WHEN 1 THEN '仅退款' WHEN 2 THEN '全额退货退款' WHEN 3 THEN '部分退货退款' END AS refund_type
FROM bi_refund_order r
WHERE r.trade_no = %s
"""
df = pd.read_sql(sql, conn, params=(order_no,))
conn.close()
if df.empty:
print(f"订单号 {order_no} 无任何退费申请记录")
else:
print(f"🔍 订单 {order_no} 的所有退费记录:")
print(df.to_string(index=False))

View File

@ -0,0 +1,53 @@
import psycopg2
import pandas as pd
# 读取密码
DB_PASSWORD = ""
with open('/root/.openclaw/workspace/secrets.env', 'r') as f:
for line in f:
if line.startswith('PG_ONLINE_PASSWORD='):
DB_PASSWORD = line.strip().split('=', 1)[1].strip('"\'')
break
# 数据库连接
conn = psycopg2.connect(
host='bj-postgres-16pob4sg.sql.tencentcdb.com',
port=28591,
user='ai_member',
password=DB_PASSWORD,
database='vala_bi'
)
# 查询指定订单
order_no = "dd202602111013471770776027390252"
sql = """
SELECT
o.out_trade_no AS order_no,
o.created_at AS order_create_time,
o.pay_amount_int / 100 AS pay_amount,
o.order_status,
r.updated_at AS refund_finish_time,
CAST(r.refund_amount AS NUMERIC) / 100 AS refund_amount,
CASE r.refund_type WHEN 1 THEN '仅退款' WHEN 2 THEN '全额退货退款' WHEN 3 THEN '部分退货退款' END AS refund_type
FROM bi_vala_order o
LEFT JOIN bi_refund_order r ON o.out_trade_no = r.trade_no AND r.status = 3
WHERE o.out_trade_no = %s
"""
df = pd.read_sql(sql, conn, params=(order_no,))
conn.close()
if df.empty:
print(f"未找到订单号为 {order_no} 的记录")
else:
row = df.iloc[0]
print(f"订单号:{row['order_no']}")
print(f"下单时间:{row['order_create_time'].strftime('%Y-%m-%d %H:%M:%S') if pd.notna(row['order_create_time']) else ''}")
print(f"订单金额:{row['pay_amount']}")
print(f"订单状态:{row['order_status']}")
if pd.notna(row['refund_finish_time']):
print(f"退款完成时间:{row['refund_finish_time'].strftime('%Y-%m-%d %H:%M:%S')}")
print(f"退款金额:{row['refund_amount']}")
print(f"退款类型:{row['refund_type']}")
else:
print("该订单无退费记录")

View File

@ -0,0 +1,371 @@
import os
import pandas as pd
import psycopg2
from datetime import datetime, timedelta
# 手动读取secrets.env获取数据库密码
DB_PASSWORD = ""
with open('/root/.openclaw/workspace/secrets.env', 'r') as f:
for line in f:
if line.startswith('PG_ONLINE_PASSWORD='):
DB_PASSWORD = line.strip().split('=', 1)[1].strip('"\'')
break
# 数据库连接配置
DB_CONFIG = {
'host': 'bj-postgres-16pob4sg.sql.tencentcdb.com',
'port': 28591,
'user': 'ai_member',
'password': DB_PASSWORD,
'database': 'vala_bi'
}
# 账期参数
ACCOUNT_START = datetime(2026, 3, 1).date()
ACCOUNT_END = datetime(2026, 3, 31).date()
def get_db_connection():
"""获取数据库连接"""
return psycopg2.connect(**DB_CONFIG)
def query_order_base():
"""查询符合条件的订单基础信息"""
sql = """
SELECT
o.id AS order_id,
o.out_trade_no AS order_no,
o.pay_amount_int / 100 AS pay_amount,
o.created_at,
DATE(o.created_at) + INTERVAL '7 day' AS amortization_start_date,
o.order_status,
CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_rate,
(o.pay_amount_int / 100) * CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_amount,
(o.pay_amount_int / 100) * (1 - CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END) AS after_tax_amount
FROM bi_vala_order o
JOIN bi_vala_app_account a ON o.account_id = a.id
WHERE
o.created_at >= '2025-09-01'
AND o.order_status IN (3,4)
AND o.pay_amount_int >= 1000
AND a.status = 1
"""
conn = get_db_connection()
df = pd.read_sql(sql, conn)
conn.close()
# 处理日期格式
df['created_at'] = pd.to_datetime(df['created_at']).dt.date
df['amortization_start_date'] = pd.to_datetime(df['amortization_start_date']).dt.date
# 标记订单是否为账期内新增
df['is_new_in_account'] = df['created_at'].between(ACCOUNT_START, ACCOUNT_END)
# 标记是否为正式订单(截止账期结束日已过试用期)
df['is_formal'] = df['amortization_start_date'] <= ACCOUNT_END
return df
def query_order_cycle(order_nos):
"""查询订单总均摊周期"""
if not order_nos:
return pd.DataFrame(columns=['order_no', 'total_cycle_days'])
order_no_str = "', '".join(order_nos)
sql = f"""
SELECT
out_trade_no AS order_no,
COUNT(DISTINCT id) * 90 AS total_cycle_days
FROM bi_vala_seasonal_ticket
WHERE status != -1 AND out_trade_no IN ('{order_no_str}')
GROUP BY out_trade_no
"""
conn = get_db_connection()
df = pd.read_sql(sql, conn)
conn.close()
return df
def query_refund_records():
"""查询账期内的退费记录"""
sql = """
SELECT
r.out_trade_no AS order_no,
CAST(r.refund_amount AS NUMERIC) / 100.0 AS refund_amount,
r.refund_type,
DATE(r.updated_at) AS refund_date
FROM bi_refund_order r
WHERE
r.status = 3
AND r.updated_at BETWEEN %s AND %s
"""
conn = get_db_connection()
df = pd.read_sql(sql, conn, params=(ACCOUNT_START, ACCOUNT_END))
conn.close()
return df
def calculate_amortization_days(amort_start, total_cycle, period_start, period_end):
"""计算账期内的均摊天数"""
if pd.isna(amort_start) or pd.isna(total_cycle) or total_cycle <= 0:
return 0
amort_end = amort_start + timedelta(days=total_cycle - 1)
# 计算实际均摊区间和账期的交集
start = max(amort_start, period_start)
end = min(amort_end, period_end)
if start > end:
return 0
return (end - start).days + 1
def main():
print("开始计算2026年3月订单均摊数据...")
# 1. 获取订单基础数据
order_base_df = query_order_base()
if order_base_df.empty:
print("无符合条件的订单数据")
return
# 2. 获取退费记录
refund_df = query_refund_records()
# 处理重复订单退费记录,保留最新的一条
if not refund_df.empty:
refund_df = refund_df.sort_values('refund_date', ascending=False).drop_duplicates('order_no', keep='first')
# 转成字典方便查询
refund_dict = refund_df.set_index('order_no').to_dict('index')
# 新增规则:过滤账期内下单且账期内全额退费的订单,直接排除
# 账期内下单且账期内部分退费的订单,直接计算剩余金额,不需要冲销
exclude_order_nos = []
for _, order_row in order_base_df.iterrows():
order_no = order_row['order_no']
is_new_in_account = order_row['is_new_in_account']
if order_no in refund_dict and is_new_in_account:
refund_type = refund_dict[order_no]['refund_type']
if refund_type == 2:
# 账期内下单+账期内全额退费:直接排除
exclude_order_nos.append(order_no)
elif refund_type == 3:
# 账期内下单+账期内部分退费:直接更新税后金额为剩余金额
refund_amount = refund_dict[order_no]['refund_amount']
order_base_df.loc[order_base_df['order_no'] == order_no, 'after_tax_amount'] = (order_row['pay_amount'] - refund_amount) * (1 - order_row['tax_rate'])
order_base_df.loc[order_base_df['order_no'] == order_no, 'tax_amount'] = (order_row['pay_amount'] - refund_amount) * order_row['tax_rate']
# 剔除全额退费的订单
order_base_df = order_base_df[~order_base_df['order_no'].isin(exclude_order_nos)].reset_index(drop=True)
# 3. 获取订单均摊周期
order_cycle_df = query_order_cycle(order_base_df['order_no'].tolist())
order_df = pd.merge(order_base_df, order_cycle_df, on='order_no', how='left')
order_df['total_cycle_days'] = order_df['total_cycle_days'].fillna(0)
# 4. 计算基础指标
# 订单类指标
total_orders = order_df[order_df['is_new_in_account']].shape[0]
formal_orders = order_df[(order_df['is_new_in_account']) & (order_df['is_formal'])].shape[0]
trial_orders = total_orders - formal_orders
# 计算冲销前税费
pre_writeoff_tax = order_df[(order_df['is_new_in_account']) & (order_df['is_formal'])]['tax_amount'].sum()
# 计算冲销税费
writeoff_tax = 0
# 计算冲销均摊金额
writeoff_amort = 0
# 计算补充税费和补充均摊金额
supplement_tax = 0
supplement_amort = 0
# 处理退费订单:仅处理历史订单(非账期内下单的订单),账期内订单已提前处理
for _, refund_row in refund_df.iterrows():
order_no = refund_row['order_no']
refund_type = refund_row['refund_type']
refund_amount = refund_row['refund_amount']
order_info = order_df[order_df['order_no'] == order_no]
if order_info.empty:
continue
order_info = order_info.iloc[0]
# 跳过账期内下单的订单,已提前处理
if order_info['is_new_in_account']:
continue
# 累加冲销税费
writeoff_tax += order_info['pay_amount'] * order_info['tax_rate']
# 计算该订单历史均摊金额2025-09-01至2026-02-28的均摊
amort_start = order_info['amortization_start_date']
total_cycle = order_info['total_cycle_days']
history_amort_days = calculate_amortization_days(amort_start, total_cycle, datetime(2025,9,1).date(), datetime(2026,2,28).date())
daily_amort = order_info['after_tax_amount'] / total_cycle if total_cycle > 0 else 0
history_amort_amount = history_amort_days * daily_amort
writeoff_amort += history_amort_amount
# 部分退费处理
if refund_type == 3:
remaining_amount = order_info['pay_amount'] - refund_amount
remaining_after_tax = remaining_amount * (1 - order_info['tax_rate'])
# 补充税费
supplement_tax += remaining_amount * order_info['tax_rate']
# 补充均摊:从转正日到账期最后一日的均摊,应用最后一天补差规则
days = calculate_amortization_days(amort_start, total_cycle, amort_start, ACCOUNT_END)
daily_amort_partial = remaining_after_tax / total_cycle if total_cycle > 0 else 0
if total_cycle > 0 and days >= total_cycle:
supplement_amort += round(remaining_after_tax, 2)
else:
supplement_amort += round(days * daily_amort_partial, 2)
# 计算冲销后税费
after_writeoff_tax = pre_writeoff_tax - writeoff_tax + supplement_tax
# 计算冲销前均摊金额
pre_writeoff_amort = 0
# 历史未退费订单的账期均摊(排除历史退费订单)
refund_order_nos = list(refund_dict.keys())
for _, order_row in order_df[~order_df['order_no'].isin(refund_order_nos)].iterrows():
if not order_row['is_formal'] or order_row['total_cycle_days'] <= 0:
continue
amort_start = order_row['amortization_start_date']
days = calculate_amortization_days(amort_start, order_row['total_cycle_days'], ACCOUNT_START, ACCOUNT_END)
daily_amort = order_row['after_tax_amount'] / order_row['total_cycle_days']
pre_writeoff_amort += days * daily_amort
# 账期内正式订单的账期均摊
for _, order_row in order_df[order_df['is_new_in_account'] & order_df['is_formal']].iterrows():
if order_row['total_cycle_days'] <= 0:
continue
amort_start = order_row['amortization_start_date']
days = calculate_amortization_days(amort_start, order_row['total_cycle_days'], ACCOUNT_START, ACCOUNT_END)
daily_amort = order_row['after_tax_amount'] / order_row['total_cycle_days']
pre_writeoff_amort += days * daily_amort
# 计算冲销后均摊金额
after_writeoff_amort = pre_writeoff_amort - writeoff_amort + supplement_amort
# 5. 生成汇总表
summary_df = pd.DataFrame([{
'指标': '订单数',
'数值': total_orders
}, {
'指标': '正式订单数',
'数值': formal_orders
}, {
'指标': '试用订单数',
'数值': trial_orders
}, {
'指标': '冲销前税费(元)',
'数值': round(pre_writeoff_tax, 2)
}, {
'指标': '冲销税费(元)',
'数值': round(writeoff_tax, 2)
}, {
'指标': '补充税费(元)',
'数值': round(supplement_tax, 2)
}, {
'指标': '冲销后税费(元)',
'数值': round(after_writeoff_tax, 2)
}, {
'指标': '冲销前均摊金额(税后,元)',
'数值': round(pre_writeoff_amort, 2)
}, {
'指标': '冲销均摊金额(税后,元)',
'数值': round(writeoff_amort, 2)
}, {
'指标': '补充均摊金额(税后,元)',
'数值': round(supplement_amort, 2)
}, {
'指标': '冲销后均摊金额(税后净收入,元)',
'数值': round(after_writeoff_amort, 2)
}])
# 6. 生成订单明细表
order_detail_list = []
for _, order_row in order_df.iterrows():
# 计算历史均摊金额2025-09-01至2026-02-28
amort_start = order_row['amortization_start_date']
total_cycle = order_row['total_cycle_days']
history_days = calculate_amortization_days(amort_start, total_cycle, datetime(2025,9,1).date(), datetime(2026,2,28).date())
daily_amort = order_row['after_tax_amount'] / total_cycle if total_cycle > 0 else 0
# 周期最后一天补差规则
if total_cycle > 0 and history_days >= total_cycle:
history_amort = round(order_row['after_tax_amount'], 2)
else:
history_amort = round(history_days * daily_amort, 2)
# 计算账期均摊金额
account_days = calculate_amortization_days(amort_start, total_cycle, ACCOUNT_START, ACCOUNT_END)
total_used_days = history_days + account_days
# 周期最后一天补差规则
if total_cycle > 0 and total_used_days >= total_cycle:
# 如果累计天数超过总周期,账期均摊=总金额-历史均摊
account_amort = round(order_row['after_tax_amount'] - history_amort, 2)
if account_amort < 0:
account_amort = 0
else:
account_amort = round(account_days * daily_amort, 2)
# 未确认收入
total_amort = history_amort + account_amort
unconfirmed_income = round(order_row['after_tax_amount'] - total_amort, 2)
if unconfirmed_income < 0:
unconfirmed_income = 0
# 剩余周期
used_days = history_days + account_days
remaining_cycle = max(0, total_cycle - used_days)
# 新增字段:下单时间
create_time = order_row['created_at'].strftime('%Y-%m-%d')
# 新增字段:退费时间
refund_time = ""
refund_type = None
if order_row['order_no'] in refund_dict:
refund_time = refund_dict[order_row['order_no']]['refund_date'].strftime('%Y-%m-%d')
refund_type = refund_dict[order_row['order_no']]['refund_type']
# 新增字段:订单分类
order_category = ""
if refund_type is not None:
if refund_type == 2:
order_category = "历史新增全额退费订单"
elif refund_type == 3:
order_category = "历史新增部分退费订单"
else:
if order_row['is_new_in_account']:
if order_row['is_formal']:
order_category = "账期新增正式订单"
else:
order_category = "账期新增试用订单"
else:
order_category = "历史正式订单"
order_detail_list.append({
'订单号': order_row['order_no'],
'下单时间': create_time,
'退费时间': refund_time,
'订单分类': order_category,
'订单金额(元)': round(order_row['pay_amount'], 2),
'税率': f"{order_row['tax_rate'] * 100}%",
'税额(元)': round(order_row['tax_amount'], 2),
'税后金额(元)': round(order_row['after_tax_amount'], 2),
'总均摊周期(天)': int(total_cycle),
'已均摊天数(天)': used_days,
'历史均摊金额(元)': history_amort,
'账期均摊金额(元)': account_amort,
'未确认收入(元)': unconfirmed_income,
'剩余周期(天)': remaining_cycle
})
detail_df = pd.DataFrame(order_detail_list)
# 7. 写入Excel
output_path = f"/root/.openclaw/workspace/output/订单均摊结算报表_{ACCOUNT_START}_{ACCOUNT_END}.xlsx"
with pd.ExcelWriter(output_path) as writer:
summary_df.to_excel(writer, sheet_name='汇总表', index=False)
detail_df.to_excel(writer, sheet_name='订单明细', index=False)
print(f"报表生成完成,保存路径:{output_path}")
print("\n📊 账期2026-03-01至2026-03-31均摊结算结果")
print(f"总订单数:{total_orders}")
print(f"正式订单数:{formal_orders}")
print(f"试用订单数:{trial_orders}")
print(f"冲销后税费:{round(after_writeoff_tax, 2)}")
print(f"冲销后均摊金额(税后净收入):{round(after_writeoff_amort, 2)}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,143 @@
# SKILL.md - vala-order-amortization-stat 订单均摊结算统计技能
## 技能描述
用于统计指定账期内的订单均摊收入、退费冲销,按天计算均摊金额,支持部分退费场景的剩余金额和周期自动计算。
## 触发场景
当用户提到以下关键词组合时激活本技能:
1. 订单均摊、按天均摊、收入均摊
2. 账期结算、月度结算、季度结算
3. 退费冲销、部分退款均摊
## 前置依赖
1. 已连接线上PostgreSQL数据库 vala_bi 库
2. 依赖表:
- bi_vala_order订单主表
- bi_refund_order退费订单表
- bi_vala_seasonal_ticket季卡周期表
- bi_vala_app_account用户账户表用于剔除测试账号
3. 表关联规则:
- bi_vala_order.trade_no ↔ bi_refund_order.trade_no 关联
- bi_vala_order.out_trade_no ↔ bi_refund_order.out_trade_no 关联
## 操作流程
### 步骤1确认核心参数
执行前必须向用户确认以下参数:
- 账期起始日期格式YYYY-MM-DD
- 账期结束日期格式YYYY-MM-DD
- 默认输出维度:账期整体汇总,无需按天/周/月拆分,无需分渠道统计
### 步骤2数据过滤规则
1. 订单范围:
- 2025-09-01 至 账期结束日 内创建的所有订单
- bi_vala_order.status IN (3,4)(已完成、已退款订单)
- 订单实际支付金额≥10元bi_vala_order.pay_amount_int ≥ 1000单位
- 关联bi_vala_app_account表仅保留bi_vala_app_account.status = 1的非测试账号订单
2. 退费范围:
- 退费完成时间落在账期内的所有退款成功记录判定条件bi_refund_order.updated_at退款处理完成时间在账期起止时间范围内
- bi_refund_order.status = 3退款处理完成
3. 账期前退费订单处理规则:
- 退费完成时间在账期起始日之前的订单,不会纳入本账期的冲销逻辑:
- 全额退费订单:冲销动作已在退费发生的对应账期执行完毕,本账期完全不统计该订单的任何数据
- 部分退费订单:冲销动作、剩余金额/剩余周期调整已在退费发生的对应账期完成,本账期仅按调整后的剩余金额计算均摊,不产生额外冲销金额
### 步骤3核心计算逻辑
#### 3.1 周期计算
- 每个订单的总均摊周期 = bi_vala_seasonal_ticket中同一order_no下status != -1的不同id数量 × 90天
- 转正日期规则试用期7天包含下单日
- 均摊起始日(转正日期)= 订单下单日 + 7天下单日取bi_vala_order.created_at的日期部分
- 示例4月22日下单4月29日00:00:00为转正日即均摊起始日
- 税率规则:
- 订单下单时间在2026年5月1日之前税率1%
- 订单下单时间在2026年5月1日及之后税率6%
- 订单税后金额 = (bi_vala_order.pay_amount_int / 100) × (1 - 税率)(单位:元)
- 订单税费金额 = (bi_vala_order.pay_amount_int / 100) × 税率(单位:元)
- **均摊规则更新**:所有均摊计算均基于税后金额进行,含税金额和税费单独统计
- 日均摊金额(税后)= 订单税后金额 / 总均摊周期
- 🔹 周期最后一天补差规则:订单均摊周期的最后一天,均摊金额不按日均摊计算,采用补差方式确保总额完全匹配:
- 正常订单:最后一天均摊金额 = 订单税后总金额 - 前(总均摊周期-1天累计已均摊金额
- 部分退费订单:最后一天均摊金额 = 订单剩余税后金额 - 剩余均摊周期前(剩余天数-1天累计已均摊金额
避免浮点精度导致的金额尾差
#### 3.2 退费场景计算
所有退费冲销金额均为税后金额,税费同步对应冲销
1. **所有退费订单(全额/部分)通用计算**
- 历史退费订单冲销均摊金额从2026年9月1日起至账期起始日之前该订单已产生的全部税后均摊金额统一显示为负数用于冲销
- 历史退费订单冲销税费金额:上述历史均摊金额对应的税费(统一显示为负数,用于冲销)
2. **全额退费bi_refund_order.refund_type = 2**:无需额外计算补充均摊,仅执行上述通用冲销逻辑
3. **部分退费bi_refund_order.refund_type = 3**:在通用冲销逻辑基础上,额外计算:
- 历史部分退费订单补充均摊金额:部分退费后剩余的待均摊税后金额,在本账期内产生的均摊收入
- 历史部分退费订单补充税费金额:上述补充均摊金额对应的税费
#### 3.3 账期内收入计算
账期内总收入 = 账期内所有正常订单的日均摊金额总和 + 账期内所有退费冲销金额总和
## 核心SQL模板
```sql
-- 步骤1获取所有符合条件的订单基础信息
WITH order_base AS (
SELECT
o.id AS order_id,
o.order_no,
o.pay_amount_int / 100 AS pay_amount,
-- 计算税率、税费、税后金额
CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_rate,
(o.pay_amount_int / 100) * CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_amount,
(o.pay_amount_int / 100) * (1 - CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END) AS after_tax_amount,
DATE_ADD(DATE(o.created_at), INTERVAL 7 DAY) AS amortization_start_date,
o.status AS order_status,
a.id AS account_id,
o.key_from,
o.sale_channel
FROM bi_vala_order o
JOIN bi_vala_app_account a ON o.account_id = a.id
WHERE
o.created_at >= '2025-09-01'
AND o.status IN (3,4)
AND o.pay_amount_int >= 1000
AND a.status = 1
),
-- 步骤2计算每个订单的总均摊周期
order_cycle AS (
SELECT
order_no,
COUNT(DISTINCT id) * 90 AS total_cycle_days
FROM bi_vala_seasonal_ticket
WHERE status != -1
GROUP BY order_no
),
-- 步骤3获取账期内的退费记录
refund_records AS (
SELECT
r.out_trade_no AS order_no,
CAST(r.refund_amount AS NUMERIC) / 100 AS refund_amount,
r.refund_type,
DATE(r.updated_at) AS refund_date
FROM bi_refund_order r
JOIN order_base o ON r.out_trade_no = o.order_no
WHERE
r.status = 3
AND r.updated_at BETWEEN '${账期起始日}' AND '${账期结束日}'
)
-- 后续按需求聚合维度计算日均摊金额和冲销金额
```
## 输出格式
1. 优先输出Excel报表存放于output/目录下,命名格式:`订单均摊结算报表_${账期起始日}_${账期结束日}.xlsx`
2. 报表包含以下Sheet
- 汇总表:订单数、正式订单数、试用订单数、冲销前税费、冲销税费、补充税费、冲销后税费、冲销前均摊金额、冲销均摊金额、补充均摊金额、冲销后均摊金额
- 订单明细:订单号、订单金额、税率、税额、税后金额、总均摊周期、已均摊天数、历史均摊金额、账期均摊金额、未确认收入、剩余周期
3. 文字回复核心指标:
> 📊 账期${账期起始日}至${账期结束日}均摊结算结果:
> 总订单数XXX单
> 正式订单数XXX单
> 试用订单数XXX单
> 冲销后税费XXX元
> 冲销后均摊金额税后净收入XXX元
## 汇总指标计算逻辑(所有均摊金额均为税后金额)
1. **订单数**:账期内(下单日落在账期起止时间范围内)新增的所有符合条件的订单总数量
2. **正式订单数**账期内新增的订单中截止账期结束日已过7天试用期下单日+7天 ≤ 账期结束日)的订单数量,仅正式订单开始参与均摊计算
3. **试用订单数**账期内新增的订单中截止账期结束日仍处于7天试用期内下单日+7天 > 账期结束日)的订单数量,试用订单未开始均摊,不参与金额计算
4. **冲销前税费**:账期内所有正式订单的含税订单总金额 × 各订单对应税率 的总和
5. **冲销税费**:本账期内发生退费的所有订单的含税订单总金额 × 各订单对应税率 的总和(正数展示,计算时扣除)
6. **补充税费**:本账期内发生部分退费的订单,退费后剩余的含税订单金额 × 各订单对应税率 的总和,其中:部分退费订单剩余含税金额 = 原订单含税金额 - 退费含税金额
7. **冲销后税费**:账期内最终确认的总税费,计算公式:`冲销后税费 = 冲销前税费 - 冲销税费 + 补充税费`
8. **冲销前均摊金额(税后)**:历史未退费订单 + 账期内正式订单 在本账期内产生的税后均摊收入总和
9. **冲销均摊金额(税后)**:本账期内发生退费的所有订单,需要冲销的历史均摊金额总和(正数展示,计算时扣除)
10. **补充均摊金额(税后)**:本账期内发生部分退费的订单,从转正日起至账期最后一日产生的税后均摊金额总和
11. **冲销后均摊金额(税后)**:账期内最终确认的税后净收入,计算公式:`冲销后均摊金额 = 冲销前均摊金额 - 冲销均摊金额 + 补充均摊金额`
## 注意事项
1. 所有金额保留2位小数百分比保留1位小数
2. 部分退费场景需确保bi_vala_seasonal_ticket中对应退费部分的status已更新为-1
3. 自动剔除测试账号,无需额外过滤
4. 冲销金额统一显示为负数