#!/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()