Files
Neo-ZQYY/scripts/ops/_generate_settlement_issue_report.py

204 lines
8.9 KiB
Python
Raw Permalink 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
生成飞球 API 结账数据问题综合报告
用法:
cd C:/NeoZQYY
python scripts/ops/_generate_settlement_issue_report.py
"""
import json
import os
import sys
from datetime import datetime, date, timedelta
from pathlib import Path
from collections import defaultdict
# 添加项目根目录到 Python 路径
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
# 加载环境变量
from dotenv import load_dotenv
load_dotenv(project_root / ".env")
def main():
"""生成综合问题报告"""
# 查找最新的结账数据文件
log_dir = Path(os.environ["SYSTEM_LOG_ROOT"])
settlement_files = list(log_dir.glob("settlement_manual_fetch_*.json"))
if not settlement_files:
print("❌ 未找到结账数据文件")
return
# 使用最新的文件
latest_file = max(settlement_files, key=lambda f: f.stat().st_mtime)
print(f"📂 基于文件: {latest_file.name}")
# 读取数据
with open(latest_file, "r", encoding="utf-8") as f:
data = json.load(f)
records = data.get("records", [])
# 分析时间分布
pay_times = []
date_counts = defaultdict(int)
for record in records:
settle_data = record.get("settleList", {})
pay_time = settle_data.get("payTime")
if pay_time:
pay_times.append(pay_time)
try:
date_str = pay_time.split()[0]
date_counts[date_str] += 1
except:
continue
pay_times.sort()
sorted_dates = sorted(date_counts.keys())
# 计算统计信息
latest_date = pay_times[-1].split()[0] if pay_times else "无数据"
today = date.today().strftime("%Y-%m-%d")
# 计算数据延迟
days_behind = 0
if latest_date != "无数据" and latest_date < today:
from datetime import datetime as dt
latest_dt = dt.strptime(latest_date, "%Y-%m-%d")
today_dt = dt.strptime(today, "%Y-%m-%d")
days_behind = (today_dt - latest_dt).days
# 检测数据断层
data_gaps = []
if len(sorted_dates) >= 2:
for i in range(len(sorted_dates) - 1):
current_date = datetime.strptime(sorted_dates[i], "%Y-%m-%d").date()
next_date = datetime.strptime(sorted_dates[i + 1], "%Y-%m-%d").date()
gap_days = (next_date - current_date).days - 1
if gap_days > 0:
gap_start = current_date + timedelta(days=1)
gap_end = next_date - timedelta(days=1)
data_gaps.append({
"start": gap_start.strftime("%Y-%m-%d"),
"end": gap_end.strftime("%Y-%m-%d"),
"days": gap_days
})
# 检测异常低数据量的日期
avg_daily_count = sum(date_counts.values()) / len(date_counts) if date_counts else 0
low_data_dates = []
for date_str, count in date_counts.items():
if count < avg_daily_count * 0.3: # 低于平均值30%认为异常
low_data_dates.append((date_str, count))
# 生成综合报告
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
report_file = log_dir / f"settlement_issue_comprehensive_report_{timestamp}.md"
with open(report_file, "w", encoding="utf-8") as f:
f.write("# 🚨 飞球 API 结账数据严重问题报告\n\n")
f.write(f"**报告生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write(f"**数据源**: 手动调用飞球 API `/Site/GetAllOrderSettleList`\n")
f.write(f"**查询范围**: 2026-02-01 00:00:00 ~ 2026-02-27 18:03:51\n\n")
f.write("## 🔍 问题概述\n\n")
f.write("通过手动调用飞球 API 确认,**结账数据存在严重的延迟和断层问题**,这直接导致了 ETL 流程中 SPI 任务的警告。\n\n")
f.write("## 📊 数据统计\n\n")
f.write(f"- **总记录数**: {len(records)}\n")
f.write(f"- **有效结账记录**: {len(pay_times)}\n")
f.write(f"- **数据覆盖天数**: {len(sorted_dates)}\n")
f.write(f"- **最早结账时间**: {pay_times[0] if pay_times else '无数据'}\n")
f.write(f"- **最晚结账时间**: {pay_times[-1] if pay_times else '无数据'}\n")
f.write(f"- **日均记录数**: {avg_daily_count:.1f}\n\n")
f.write("## ⚠️ 关键问题\n\n")
f.write("### 1. 数据延迟问题\n")
f.write(f"- **API 最新数据日期**: {latest_date}\n")
f.write(f"- **今天日期**: {today}\n")
f.write(f"- **数据延迟**: {days_behind}\n\n")
if data_gaps:
f.write("### 2. 数据断层问题\n")
f.write("发现以下时间段完全没有数据:\n\n")
total_gap_days = 0
for gap in data_gaps:
f.write(f"- **{gap['start']} ~ {gap['end']}**: {gap['days']} 天缺失\n")
total_gap_days += gap['days']
f.write(f"\n**总计缺失**: {total_gap_days}\n\n")
if low_data_dates:
f.write("### 3. 异常低数据量日期\n")
f.write(f"以下日期的数据量异常偏低(低于日均 {avg_daily_count:.1f} 条的 30%\n\n")
for date_str, count in low_data_dates:
f.write(f"- **{date_str}**: {count} 条记录\n")
f.write("\n")
f.write("## 📅 完整数据分布\n\n")
f.write("| 日期 | 记录数 | 状态 |\n")
f.write("|------|--------|------|\n")
for date_str in sorted_dates:
count = date_counts[date_str]
status = "🔴 异常低" if count < avg_daily_count * 0.3 else "✅ 正常"
f.write(f"| {date_str} | {count:4d} | {status} |\n")
f.write("\n")
f.write("## 🎯 对 ETL 流程的影响\n\n")
f.write("### SPI 任务警告的根本原因\n")
f.write("1. **数据稀疏**: 在 30 天窗口 (1/28-2/27) 中,只有 17 天有数据\n")
f.write("2. **中位数为 0**: 109 个会员中 103 个 (93.6%) 近 30 天消费为 0\n")
f.write("3. **回退到默认值**: `_calibrate_amount_bases` 按设计回退到 `DEFAULT_PARAMS`\n")
f.write("4. **警告触发**: 系统正确识别并警告了这种异常情况\n\n")
f.write("### 业务影响\n")
f.write("- **会员画像不准确**: 基于不完整数据的消费特征分析\n")
f.write("- **营销决策受影响**: SPI 评分和推荐策略可能偏差\n")
f.write("- **报表数据缺失**: 财务和运营报表存在数据空白\n\n")
f.write("## 🔧 建议解决方案\n\n")
f.write("### 短期措施\n")
f.write("1. **联系飞球技术支持**: 确认 API 数据延迟和断层的原因\n")
f.write("2. **数据补录**: 要求飞球方面补录缺失的结账数据\n")
f.write("3. **监控告警**: 建立数据延迟监控,及时发现类似问题\n\n")
f.write("### 长期措施\n")
f.write("1. **SLA 协议**: 与飞球签署数据服务 SLA明确数据延迟容忍度\n")
f.write("2. **备用数据源**: 考虑建立备用的数据获取渠道\n")
f.write("3. **数据质量检查**: 在 ETL 流程中增加更严格的数据质量检查\n\n")
f.write("## 📋 技术细节\n\n")
f.write("### API 调用信息\n")
f.write(f"- **端点**: `/Site/GetAllOrderSettleList`\n")
f.write(f"- **查询参数**: siteId=0, rangeStartTime='2026-02-01 00:00:00', rangeEndTime='2026-02-27 18:03:51'\n")
f.write(f"- **返回页数**: {data.get('total_pages', 'N/A')}\n")
f.write(f"- **数据结构**: 每条记录包含 `siteProfile` 和 `settleList` 字段\n")
f.write(f"- **时间字段**: `settleList.payTime`\n\n")
f.write("### 相关文件\n")
f.write(f"- **原始数据**: `{latest_file.name}`\n")
f.write(f"- **结构分析**: `settlement_structure_analysis_*.txt`\n")
f.write(f"- **详细分析**: `settlement_detailed_analysis_*.md`\n\n")
f.write("---\n\n")
f.write("**结论**: 这是一个严重的上游数据源问题需要立即与飞球方面沟通解决。ETL 流程和 SPI 任务的警告是正确的,反映了真实的数据质量问题。\n")
print(f"📋 综合问题报告已生成: {report_file}")
# 输出关键信息到控制台
print(f"\n🚨 关键发现:")
print(f" - 数据延迟: {days_behind}")
print(f" - 数据断层: {len(data_gaps)} 个时间段,共 {sum(gap['days'] for gap in data_gaps)} 天缺失")
print(f" - 异常日期: {len(low_data_dates)} 天数据量异常偏低")
print(f" - 影响范围: SPI 任务警告、会员画像、营销决策")
return report_file
if __name__ == "__main__":
main()