微信小程序页面迁移校验之前 P5任务处理之前

This commit is contained in:
Neo
2026-03-09 01:19:21 +08:00
parent 263bf96035
commit 6e20987d2f
1112 changed files with 153824 additions and 219694 deletions

View File

@@ -0,0 +1,204 @@
#!/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()