fix: 修复代码审查P2/P3问题

P2:
- lottery.py 改为引用 history_loader.py 公共模块,删除独立 Excel 加载逻辑
- 删除 lottery.py 中不再使用的 _normalize_history_format 方法(已迁移到 history_loader)

P3:
- app.py: 删除未使用的 import shutil
- app.py: 删除不可达代码 return stats
- app.py: 修复 api_history 中奇偶形态字段映射错误(奇偶比→奇偶形态)
- app.py: api_history columns 列表补充奇偶比和奇偶形态字段
- 开发文档目录结构补充 history_loader.py 和 web_executor.py 废弃标记

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-07-04 09:21:43 +08:00
parent 5cebbfa433
commit 462f1505ee
3 changed files with 16 additions and 134 deletions
+3 -5
View File
@@ -10,7 +10,6 @@ import os
import sys import sys
import json import json
import uuid import uuid
import shutil
import traceback import traceback
import threading import threading
from datetime import datetime from datetime import datetime
@@ -207,8 +206,6 @@ def get_statistics_data(generator=None):
"""获取统计数据 — 委托公共模块计算""" """获取统计数据 — 委托公共模块计算"""
return compute_statistics(CONFIG['history_file']) return compute_statistics(CONFIG['history_file'])
return stats
# ============================================================ # ============================================================
# API:获取统计数据 # API:获取统计数据
@@ -379,7 +376,8 @@ def api_history():
'蓝球': row['蓝球'], '蓝球': row['蓝球'],
'开机号': str(row['开机号']), '开机号': str(row['开机号']),
'和值': str(row['和值特征']), '和值': str(row['和值特征']),
'奇偶形态': str(row['奇偶比']), '奇偶': str(row['奇偶比']),
'奇偶形态': str(row['奇偶形态']),
'大小比': str(row['大小比']), '大小比': str(row['大小比']),
'跨度': str(row['跨度']), '跨度': str(row['跨度']),
} }
@@ -392,7 +390,7 @@ def api_history():
'total': total, 'total': total,
'page': page, 'page': page,
'page_size': page_size, 'page_size': page_size,
'columns': ['开奖日期', '期号', '红球', '蓝球', '开机号', '和值', '大小比', '跨度'] 'columns': ['开奖日期', '期号', '红球', '蓝球', '开机号', '和值', '奇偶比', '奇偶形态', '大小比', '跨度']
} }
}) })
except Exception as e: except Exception as e:
+6 -4
View File
@@ -28,13 +28,15 @@ lottoData/
├── app.py # Flask 主服务(统一入口) ├── app.py # Flask 主服务(统一入口)
├── index.html # 前端 UI(响应式,4 Tab 页面) ├── index.html # 前端 UI(响应式,4 Tab 页面)
├── lottery.py # 号码生成核心逻辑 ├── lottery.py # 号码生成核心逻辑
├── history_loader.py # 公共 Excel 加载模块(统一格式兼容)
├── fetch_data.py # 历史数据抓取脚本 ├── fetch_data.py # 历史数据抓取脚本
├── web_console.html # 数据抓取控制台前端 ├── web_console.html # 数据抓取控制台前端
├── web_executor.py # [已废弃] 旧版独立抓取服务,功能已整合到 app.py
├── requirements.txt # Python 依赖 ├── requirements.txt # Python 依赖
├── 双色球历史数据.xlsx # 历史数据文件 ├── 双色球历史数据.xlsx # 历史数据文件(不纳入 git
├── lottery/ # 号码生成结果输出目录 ├── lottery/ # 号码生成结果输出目录(不纳入 git
├── .generation_records.json # 生成记录索引(JSON ├── .generation_records.json # 生成记录索引(JSON,不纳入 git
├── .fetch_status.json # 抓取状态文件 ├── .fetch_status.json # 抓取状态文件(不纳入 git
├── deploy/ # 部署相关文件 ├── deploy/ # 部署相关文件
│ ├── DEPLOY.md # 部署说明 │ ├── DEPLOY.md # 部署说明
│ ├── lotto-app.service # systemd 服务文件 │ ├── lotto-app.service # systemd 服务文件
+7 -125
View File
@@ -38,76 +38,24 @@ class DoubleColorBallGenerator:
} }
def load_history_data(self): def load_history_data(self):
"""加载历史数据""" """加载历史数据 — 委托公共模块 history_loader 处理多格式兼容"""
try: try:
# 检查文件是否存在 # 检查文件是否存在
if not os.path.exists(self.history_file): if not os.path.exists(self.history_file):
print(f"错误: 文件 {self.history_file} 不存在") print(f"错误: 文件 {self.history_file} 不存在")
return False return False
# 读取Excel文件
print(f"正在读取文件: {self.history_file}")
try:
raw_df = pd.read_excel(self.history_file, header=None)
except Exception as excel_error:
print(f"读取Excel文件失败: {excel_error}")
return False
# 检查数据是否为空 # 使用公共模块加载历史数据(统一处理格式A/B/C)
if raw_df.empty: from history_loader import load_history_dataframe
print("错误: 历史数据文件为空") self.history_data = load_history_dataframe(self.history_file)
return False
# 兼容多种 Excel 格式:
# 格式Afetch_data.py 当前输出): Row0=新列名(期号|开奖日期|红球1...|蓝球|特别号), Row1=旧列名(开奖时间|期数|号码|...), Row2+=数据
# 格式B(标准格式): Row0=列名(开奖时间|期数|号码|开机号|...), Row1+=数据
# 格式C(分列含旧 header: Row0=旧列名, Row1+=数据 但无"号码"列
# 标准列名(lottery.py 期望的列)
legacy_columns = ['开奖时间', '期数', '号码', '开机号', '和值特征', '奇偶比', '大小比', '奇偶形态', '跨度', '其他']
row0_vals = raw_df.iloc[0].astype(str).tolist() if len(raw_df) > 0 else []
row1_vals = raw_df.iloc[1].astype(str).tolist() if len(raw_df) > 1 else []
# 检测各类格式
has_legacy_header_in_row0 = any(col in row0_vals for col in ['开奖时间', '期数', '号码'])
has_legacy_header_in_row1 = any(col in row1_vals for col in ['开奖时间', '期数', '号码'])
has_new_header_in_row0 = any(col in row0_vals for col in ['期号', '开奖日期', '红球 1'])
if has_new_header_in_row0 and has_legacy_header_in_row1:
# 格式ARow0=新列名, Row1=旧列名, Row2+=数据
# 用旧列名(Row1)作为列名,因为 lottery.py 期望"号码"列
self.history_data = raw_df.iloc[2:].copy()
num_cols = len(self.history_data.columns)
self.history_data.columns = legacy_columns[:min(num_cols, len(legacy_columns))] + [f'col_{i}' for i in range(min(num_cols, len(legacy_columns)), num_cols)]
self.history_data = self.history_data.reset_index(drop=True)
print(f"加载成功(格式A: 新旧 header 双行),共{len(self.history_data)}条历史记录")
print(f"数据列: {list(self.history_data.columns)}")
elif has_legacy_header_in_row0:
# 格式BRow0=标准列名, Row1+=数据
self.history_data = raw_df.iloc[1:].copy()
num_cols = len(self.history_data.columns)
self.history_data.columns = legacy_columns[:min(num_cols, len(legacy_columns))] + [f'col_{i}' for i in range(min(num_cols, len(legacy_columns)), num_cols)]
self.history_data = self.history_data.reset_index(drop=True)
print(f"加载成功(格式B: 标准列名),共{len(self.history_data)}条历史记录")
print(f"数据列: {list(self.history_data.columns)}")
else:
# 格式C:检测不到旧列名,尝试直接用 pandas 读取
self.history_data = pd.read_excel(self.history_file)
print(f"加载成功(默认读取),共{len(self.history_data)}条历史记录")
print(f"数据列: {list(self.history_data.columns)}")
# 如果没有"号码"列但有分列红球,尝试标准化
if '号码' not in self.history_data.columns:
if any(c in self.history_data.columns for c in ['红球 1', '红球1']):
self._normalize_history_format()
if self.history_data.empty: if self.history_data.empty:
print("错误: 历史数据文件为空") print("错误: 历史数据文件为空")
return False return False
print(f"加载成功,共 {len(self.history_data)} 条历史记录")
print(f"数据列: {list(self.history_data.columns)}")
# 解析号码列 # 解析号码列
def parse_numbers(row): def parse_numbers(row):
"""解析单行号码数据 """解析单行号码数据
@@ -211,72 +159,6 @@ class DoubleColorBallGenerator:
print(traceback.format_exc()) print(traceback.format_exc())
return False return False
def _normalize_history_format(self):
"""将格式A(分列红球)转换为格式B(统一号码列 + 标准列名)。
格式A列名: 期号 | 开奖日期 | 红球 1 | 红球 2 | 红球 3 | 红球 4 | 红球 5 | 红球 6 | 蓝球 | 特别号
格式B列名: 开奖时间 | 期数 | 号码 | 开机号 | 和值特征 | 奇偶比 | 大小比 | 奇偶形态 | 跨度 | 其他
在 self.history_data 上原地操作,构建 '号码' 列和标准列名。
"""
df = self.history_data
standard_columns = ['开奖时间', '期数', '号码', '开机号', '和值特征', '奇偶比', '大小比', '奇偶形态', '跨度', '其他']
# 构建号码列:将 红球1~6 + 蓝球 拼接为 14 位字符串
red_cols = [f'红球 {i}' for i in range(1, 7)]
blue_col = '蓝球'
def build_number_string(row):
parts = []
for c in red_cols:
val = row.get(c)
if pd.isna(val):
return None
s = str(int(val)) if isinstance(val, (int, float)) else str(val).strip()
parts.append(s.zfill(2))
blue_val = row.get(blue_col)
if pd.isna(blue_val):
return None
blue_s = str(int(blue_val)) if isinstance(blue_val, (int, float)) else str(blue_val).strip()
return ''.join(parts) + blue_s.zfill(2)
df = df.copy()
df['号码'] = df.apply(build_number_string, axis=1)
# 重命名列到标准列名 — 仅映射有语义对应关系的列
# 格式A -> 格式B 语义映射:
# 期号 -> 开奖时间(实际存的是日期)
# 开奖日期 -> 期数(实际存的是期号数字)
# 特别号 -> 跨度
# 红球 1~6 和 蓝球 用于构建"号码"列后不再映射
# 格式A不含开机号/和值特征/奇偶比/大小比/奇偶形态等字段,留空
rename_map = {}
if '期号' in df.columns:
rename_map['期号'] = '开奖时间'
if '开奖日期' in df.columns:
rename_map['开奖日期'] = '期数'
if '特别号' in df.columns:
rename_map['特别号'] = '跨度'
df = df.rename(columns=rename_map)
# 删除已用于构建号码列的原始分列(避免数据重复)
for col in [f'红球 {i}' for i in range(1, 7)] + ['蓝球']:
if col in df.columns:
df = df.drop(columns=[col])
# 确保所有标准列都存在(格式A缺失的字段留空)
for col in standard_columns:
if col not in df.columns:
df[col] = ''
# 调整列顺序
df = df[[c for c in standard_columns if c in df.columns] + [c for c in df.columns if c not in standard_columns]]
self.history_data = df.reset_index(drop=True)
print(f"已标准化数据格式,共 {len(df)} 条记录")
print(f"标准化后列名: {list(df.columns)}")
def _calculate_statistics(self): def _calculate_statistics(self):
"""计算统计数据""" """计算统计数据"""
if self.history_data is None or len(self.history_data) == 0: if self.history_data is None or len(self.history_data) == 0: