feat: 一键下单与投注历史页面文案风格统一为早餐铺趣味化风格,修复推荐注数等核心体验问题
This commit is contained in:
parent
715b2ea85b
commit
ff9d9a857e
71
backend/app/api/endpoints/bet_history.py
Normal file
71
backend/app/api/endpoints/bet_history.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
from update_lottery import LotteryUpdater
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from ...core.database import get_db
|
||||||
|
from ...models.lottery import SSQLotteryBetRecord, DLTLotteryBetRecord
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)))))
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{lottery_type}")
|
||||||
|
def get_bet_history(
|
||||||
|
lottery_type: str,
|
||||||
|
page: int = 1,
|
||||||
|
size: int = 20,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""获取投注历史记录"""
|
||||||
|
if lottery_type not in ['ssq', 'dlt']:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid lottery type")
|
||||||
|
|
||||||
|
# 选择对应的模型
|
||||||
|
BetModel = SSQLotteryBetRecord if lottery_type == 'ssq' else DLTLotteryBetRecord
|
||||||
|
|
||||||
|
# 计算偏移量
|
||||||
|
offset = (page - 1) * size
|
||||||
|
|
||||||
|
# 查询记录
|
||||||
|
records = db.query(BetModel).order_by(
|
||||||
|
BetModel.created_at.desc()
|
||||||
|
).offset(offset).limit(size).all()
|
||||||
|
|
||||||
|
# 查询总数
|
||||||
|
total = db.query(BetModel).count()
|
||||||
|
|
||||||
|
# 格式化返回数据
|
||||||
|
formatted_records = []
|
||||||
|
for record in records:
|
||||||
|
formatted_records.append({
|
||||||
|
'id': record.id,
|
||||||
|
'batch_id': record.batch_id,
|
||||||
|
'issue': record.issue,
|
||||||
|
'numbers': record.numbers,
|
||||||
|
'recommend_type': record.recommend_type,
|
||||||
|
'created_at': record.created_at.isoformat() if record.created_at else None,
|
||||||
|
'is_winner': record.is_winner,
|
||||||
|
'win_level': record.win_level,
|
||||||
|
'win_amount': record.win_amount
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'records': formatted_records,
|
||||||
|
'total': total,
|
||||||
|
'page': page,
|
||||||
|
'size': size,
|
||||||
|
'pages': (total + size - 1) // size
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/check-win/{lottery_type}")
|
||||||
|
def check_win(lottery_type: str, db: Session = Depends(get_db)):
|
||||||
|
"""手动触发比对并更新中奖信息"""
|
||||||
|
if lottery_type not in ['ssq', 'dlt']:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid lottery type")
|
||||||
|
updater = LotteryUpdater()
|
||||||
|
updater.check_and_update_bet_wins(lottery_type, db)
|
||||||
|
return {"success": True, "message": f"{lottery_type}投注中奖比对已完成"}
|
||||||
@ -3,6 +3,12 @@ from sqlalchemy.orm import Session
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
from ...core.database import get_db
|
from ...core.database import get_db
|
||||||
from ...services.prediction_service import PredictionService
|
from ...services.prediction_service import PredictionService
|
||||||
|
from ...models.lottery import SSQLotteryBetRecord, DLTLotteryBetRecord, SSQLottery, DLTLottery
|
||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
from ...services.telegram_bot import send_message
|
||||||
|
from ...services.analysis_service import LotteryAnalysisService
|
||||||
|
import os
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@ -61,3 +67,148 @@ def get_ensemble_prediction(
|
|||||||
|
|
||||||
service = PredictionService(db)
|
service = PredictionService(db)
|
||||||
return service.get_ensemble_prediction(lottery_type, periods)
|
return service.get_ensemble_prediction(lottery_type, periods)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/recommend/today")
|
||||||
|
def recommend_today(db: Session = Depends(get_db)):
|
||||||
|
"""
|
||||||
|
每日推荐:一三六推荐大乐透,二四日推荐双色球,固定投注1注+推荐4注,共5注,推送内容合并快乐8。
|
||||||
|
"""
|
||||||
|
# 固定投注号码(写死)
|
||||||
|
FIXED_SSQ = {'red': [4, 6, 7, 12, 18, 19], 'blue': [9]}
|
||||||
|
FIXED_DLT = {'red': [4, 6, 9, 18, 19], 'blue': [7, 12]}
|
||||||
|
# 判断彩种
|
||||||
|
weekday = datetime.datetime.now().isoweekday()
|
||||||
|
if weekday in [1, 3, 6]:
|
||||||
|
lottery_type = 'dlt'
|
||||||
|
BetModel = DLTLotteryBetRecord
|
||||||
|
recommend_type = '集成预测-每日推荐'
|
||||||
|
|
||||||
|
def number_format(red, blue): return ' '.join(
|
||||||
|
f"{n:02d}" for n in red) + ' 蓝球:' + ' '.join(f"{n:02d}" for n in blue)
|
||||||
|
LotteryModel = DLTLottery
|
||||||
|
fixed = FIXED_DLT
|
||||||
|
title = "你好,帮忙买下大乐透。"
|
||||||
|
dlt_append = True
|
||||||
|
else:
|
||||||
|
lottery_type = 'ssq'
|
||||||
|
BetModel = SSQLotteryBetRecord
|
||||||
|
recommend_type = '集成预测-每日推荐'
|
||||||
|
|
||||||
|
def number_format(red, blue): return ' '.join(
|
||||||
|
f"{n:02d}" for n in red) + ' 蓝球:' + ' '.join(f"{n:02d}" for n in blue)
|
||||||
|
LotteryModel = SSQLottery
|
||||||
|
fixed = FIXED_SSQ
|
||||||
|
title = "你好,帮忙买下双色球。"
|
||||||
|
dlt_append = False
|
||||||
|
# 生成batch_id
|
||||||
|
today = datetime.datetime.now().strftime('%Y%m%d')
|
||||||
|
batch_id = f"{today}-{random.randint(1000, 9999)}"
|
||||||
|
# 1. 推荐4注(集成预测+智能选号补足)
|
||||||
|
service = PredictionService(db)
|
||||||
|
pred = service.get_ensemble_prediction(lottery_type, periods=200)
|
||||||
|
if not pred.get('success'):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500, detail=pred.get('message', '预测失败'))
|
||||||
|
recs = pred.get('recommendations', [])
|
||||||
|
results = []
|
||||||
|
used_numbers = set()
|
||||||
|
# 先取集成预测推荐
|
||||||
|
for rec in recs:
|
||||||
|
if len(results) >= 4:
|
||||||
|
break
|
||||||
|
red = rec.get('numbers')
|
||||||
|
blue = [rec.get('blue')] if rec.get(
|
||||||
|
'blue') is not None else rec.get('blues', [])
|
||||||
|
if not red or not blue:
|
||||||
|
continue
|
||||||
|
numbers = ','.join(f"{n:02d}" for n in red) + \
|
||||||
|
'|' + ','.join(f"{n:02d}" for n in blue)
|
||||||
|
if numbers in used_numbers:
|
||||||
|
continue
|
||||||
|
used_numbers.add(numbers)
|
||||||
|
last = db.query(LotteryModel).order_by(
|
||||||
|
LotteryModel.open_time.desc()).first()
|
||||||
|
issue = last.issue if last else ''
|
||||||
|
bet = BetModel(
|
||||||
|
batch_id=batch_id,
|
||||||
|
issue=issue,
|
||||||
|
numbers=numbers,
|
||||||
|
recommend_type=rec.get('method', recommend_type)
|
||||||
|
)
|
||||||
|
db.add(bet)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(bet)
|
||||||
|
results.append({
|
||||||
|
'id': bet.id,
|
||||||
|
'batch_id': batch_id,
|
||||||
|
'issue': issue,
|
||||||
|
'numbers': numbers,
|
||||||
|
'red': red,
|
||||||
|
'blue': blue,
|
||||||
|
'recommend_type': rec.get('method', recommend_type)
|
||||||
|
})
|
||||||
|
# 补足到4注
|
||||||
|
analysis_service = LotteryAnalysisService(db)
|
||||||
|
while len(results) < 4:
|
||||||
|
smart = analysis_service.generate_smart_numbers(
|
||||||
|
lottery_type, strategy='balanced', count=1)
|
||||||
|
if not smart:
|
||||||
|
break
|
||||||
|
red = smart[0].get('red_numbers')
|
||||||
|
blue = smart[0].get('blue_numbers')
|
||||||
|
numbers = ','.join(f"{n:02d}" for n in red) + \
|
||||||
|
'|' + ','.join(f"{n:02d}" for n in blue)
|
||||||
|
if numbers in used_numbers:
|
||||||
|
continue
|
||||||
|
used_numbers.add(numbers)
|
||||||
|
last = db.query(LotteryModel).order_by(
|
||||||
|
LotteryModel.open_time.desc()).first()
|
||||||
|
issue = last.issue if last else ''
|
||||||
|
bet = BetModel(
|
||||||
|
batch_id=batch_id,
|
||||||
|
issue=issue,
|
||||||
|
numbers=numbers,
|
||||||
|
recommend_type='智能选号补足'
|
||||||
|
)
|
||||||
|
db.add(bet)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(bet)
|
||||||
|
results.append({
|
||||||
|
'id': bet.id,
|
||||||
|
'batch_id': batch_id,
|
||||||
|
'issue': issue,
|
||||||
|
'numbers': numbers,
|
||||||
|
'red': red,
|
||||||
|
'blue': blue,
|
||||||
|
'recommend_type': '智能选号补足'
|
||||||
|
})
|
||||||
|
# 2. 组装推送内容(固定投注+4注推荐)
|
||||||
|
msg_lines = [title]
|
||||||
|
# 固定投注
|
||||||
|
msg_lines.append(f"红球:{' '.join(f'{n:02d}' for n in fixed['red'])} 蓝球:{
|
||||||
|
' '.join(f'{n:02d}' for n in fixed['blue'])}")
|
||||||
|
# 推荐号码
|
||||||
|
for rec in results:
|
||||||
|
msg_lines.append(f"红球:{' '.join(f'{n:02d}' for n in rec['red'])} 蓝球:{
|
||||||
|
' '.join(f'{n:02d}' for n in rec['blue'])}")
|
||||||
|
if dlt_append:
|
||||||
|
msg_lines.append("都追加,谢谢。")
|
||||||
|
# 3. 快乐8推送(3注十选,合并到主消息)
|
||||||
|
msg_lines.append("\n快乐8,单式,选十")
|
||||||
|
for _ in range(3):
|
||||||
|
nums = sorted(random.sample(range(1, 81), 10))
|
||||||
|
msg_lines.append(' '.join(f"{n:02d}" for n in nums))
|
||||||
|
# 4. 推送到Telegram
|
||||||
|
try:
|
||||||
|
token = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||||
|
chat_id = os.getenv("TELEGRAM_CHAT_ID")
|
||||||
|
send_message('\n'.join(msg_lines), token=token, chat_id=chat_id)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[WARN] Telegram推送失败: {e}")
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'lottery_type': lottery_type,
|
||||||
|
'batch_id': batch_id,
|
||||||
|
'recommend': results
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from app.api.v1.lottery import router as lottery_router
|
|||||||
from app.api.endpoints.analysis import router as analysis_router
|
from app.api.endpoints.analysis import router as analysis_router
|
||||||
from app.api.endpoints.advanced_analysis import router as advanced_analysis_router
|
from app.api.endpoints.advanced_analysis import router as advanced_analysis_router
|
||||||
from app.api.endpoints.prediction import router as prediction_router
|
from app.api.endpoints.prediction import router as prediction_router
|
||||||
|
from app.api.endpoints.bet_history import router as bet_history_router
|
||||||
from app.core.database import Base, engine
|
from app.core.database import Base, engine
|
||||||
|
|
||||||
# 创建数据库表
|
# 创建数据库表
|
||||||
@ -33,6 +34,8 @@ app.include_router(
|
|||||||
advanced_analysis_router, prefix=f"{settings.API_V1_STR}/advanced-analysis", tags=["advanced-analysis"])
|
advanced_analysis_router, prefix=f"{settings.API_V1_STR}/advanced-analysis", tags=["advanced-analysis"])
|
||||||
app.include_router(
|
app.include_router(
|
||||||
prediction_router, prefix=f"{settings.API_V1_STR}/prediction", tags=["prediction"])
|
prediction_router, prefix=f"{settings.API_V1_STR}/prediction", tags=["prediction"])
|
||||||
|
app.include_router(
|
||||||
|
bet_history_router, prefix=f"{settings.API_V1_STR}/lottery/bet-history", tags=["bet-history"])
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
@ -36,3 +36,47 @@ class DLTLottery(Base):
|
|||||||
created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间")
|
created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间")
|
||||||
updated_at = Column(TIMESTAMP, server_default=func.now(),
|
updated_at = Column(TIMESTAMP, server_default=func.now(),
|
||||||
onupdate=func.now(), comment="更新时间")
|
onupdate=func.now(), comment="更新时间")
|
||||||
|
|
||||||
|
|
||||||
|
class SSQLotteryBetRecord(Base):
|
||||||
|
__tablename__ = "ssq_bet_record"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||||
|
batch_id = Column(String(32), index=True, nullable=False, comment="批次ID")
|
||||||
|
issue = Column(String(10), index=True, nullable=False, comment="期号")
|
||||||
|
bet_time = Column(TIMESTAMP, server_default=func.now(), comment="投注时间")
|
||||||
|
numbers = Column(String(64), nullable=False,
|
||||||
|
comment="投注号码,格式01,02,03,04,05,06|07")
|
||||||
|
recommend_type = Column(String(32), nullable=True, comment="推荐方式")
|
||||||
|
telegram_msg_id = Column(String(64), nullable=True, comment="推送消息ID")
|
||||||
|
is_winner = Column(Integer, default=0, comment="是否中奖 0否 1是")
|
||||||
|
win_level = Column(String(32), nullable=True, comment="中奖等级")
|
||||||
|
win_amount = Column(String(32), nullable=True, comment="中奖金额")
|
||||||
|
open_code = Column(String(64), nullable=True, comment="开奖号码")
|
||||||
|
open_date = Column(Date, nullable=True, comment="开奖日期")
|
||||||
|
prize_detail = Column(String(1024), nullable=True, comment="开奖详情JSON")
|
||||||
|
created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间")
|
||||||
|
updated_at = Column(TIMESTAMP, server_default=func.now(),
|
||||||
|
onupdate=func.now(), comment="更新时间")
|
||||||
|
|
||||||
|
|
||||||
|
class DLTLotteryBetRecord(Base):
|
||||||
|
__tablename__ = "dlt_bet_record"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||||
|
batch_id = Column(String(32), index=True, nullable=False, comment="批次ID")
|
||||||
|
issue = Column(String(10), index=True, nullable=False, comment="期号")
|
||||||
|
bet_time = Column(TIMESTAMP, server_default=func.now(), comment="投注时间")
|
||||||
|
numbers = Column(String(64), nullable=False,
|
||||||
|
comment="投注号码,格式01,02,03,04,05|06,07")
|
||||||
|
recommend_type = Column(String(32), nullable=True, comment="推荐方式")
|
||||||
|
telegram_msg_id = Column(String(64), nullable=True, comment="推送消息ID")
|
||||||
|
is_winner = Column(Integer, default=0, comment="是否中奖 0否 1是")
|
||||||
|
win_level = Column(String(32), nullable=True, comment="中奖等级")
|
||||||
|
win_amount = Column(String(32), nullable=True, comment="中奖金额")
|
||||||
|
open_code = Column(String(64), nullable=True, comment="开奖号码")
|
||||||
|
open_date = Column(Date, nullable=True, comment="开奖日期")
|
||||||
|
prize_detail = Column(String(1024), nullable=True, comment="开奖详情JSON")
|
||||||
|
created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间")
|
||||||
|
updated_at = Column(TIMESTAMP, server_default=func.now(),
|
||||||
|
onupdate=func.now(), comment="更新时间")
|
||||||
|
|||||||
@ -59,3 +59,59 @@ class LotteryQuery(BaseModel):
|
|||||||
issue: Optional[str] = None
|
issue: Optional[str] = None
|
||||||
page: int = 1
|
page: int = 1
|
||||||
page_size: int = 20
|
page_size: int = 20
|
||||||
|
|
||||||
|
|
||||||
|
class SSQLotteryBetRecordBase(BaseModel):
|
||||||
|
batch_id: str
|
||||||
|
issue: str
|
||||||
|
bet_time: Optional[datetime] = None
|
||||||
|
numbers: str
|
||||||
|
recommend_type: Optional[str] = None
|
||||||
|
telegram_msg_id: Optional[str] = None
|
||||||
|
is_winner: Optional[int] = 0
|
||||||
|
win_level: Optional[str] = None
|
||||||
|
win_amount: Optional[str] = None
|
||||||
|
open_code: Optional[str] = None
|
||||||
|
open_date: Optional[date] = None
|
||||||
|
prize_detail: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class SSQLotteryBetRecordCreate(SSQLotteryBetRecordBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SSQLotteryBetRecord(SSQLotteryBetRecordBase):
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class DLTLotteryBetRecordBase(BaseModel):
|
||||||
|
batch_id: str
|
||||||
|
issue: str
|
||||||
|
bet_time: Optional[datetime] = None
|
||||||
|
numbers: str
|
||||||
|
recommend_type: Optional[str] = None
|
||||||
|
telegram_msg_id: Optional[str] = None
|
||||||
|
is_winner: Optional[int] = 0
|
||||||
|
win_level: Optional[str] = None
|
||||||
|
win_amount: Optional[str] = None
|
||||||
|
open_code: Optional[str] = None
|
||||||
|
open_date: Optional[date] = None
|
||||||
|
prize_detail: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class DLTLotteryBetRecordCreate(DLTLotteryBetRecordBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DLTLotteryBetRecord(DLTLotteryBetRecordBase):
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|||||||
@ -415,15 +415,7 @@ class PredictionService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_ensemble_prediction(self, lottery_type: str, periods: int = 100) -> Dict:
|
def get_ensemble_prediction(self, lottery_type: str, periods: int = 100) -> Dict:
|
||||||
"""集成预测(结合多种方法)
|
"""集成预测(结合多种方法)"""
|
||||||
|
|
||||||
Args:
|
|
||||||
lottery_type: 彩票类型 ('ssq' 或 'dlt')
|
|
||||||
periods: 分析期数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict: 预测结果
|
|
||||||
"""
|
|
||||||
# 机器学习预测
|
# 机器学习预测
|
||||||
ml_result = self.predict_next_numbers(lottery_type, periods)
|
ml_result = self.predict_next_numbers(lottery_type, periods)
|
||||||
|
|
||||||
@ -440,6 +432,7 @@ class PredictionService:
|
|||||||
# 综合推荐
|
# 综合推荐
|
||||||
recommendations = []
|
recommendations = []
|
||||||
|
|
||||||
|
# 机器学习推荐
|
||||||
if ml_result.get('success'):
|
if ml_result.get('success'):
|
||||||
recommendations.append({
|
recommendations.append({
|
||||||
'method': '机器学习',
|
'method': '机器学习',
|
||||||
@ -449,6 +442,7 @@ class PredictionService:
|
|||||||
'confidence': '高'
|
'confidence': '高'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 热冷号分析推荐
|
||||||
if freq_result:
|
if freq_result:
|
||||||
max_red = 33 if lottery_type == 'ssq' else 35
|
max_red = 33 if lottery_type == 'ssq' else 35
|
||||||
max_blue = 16 if lottery_type == 'ssq' else 12
|
max_blue = 16 if lottery_type == 'ssq' else 12
|
||||||
@ -501,10 +495,9 @@ class PredictionService:
|
|||||||
selected_numbers.add(num)
|
selected_numbers.add(num)
|
||||||
remaining_numbers.remove(num)
|
remaining_numbers.remove(num)
|
||||||
|
|
||||||
# 生成蓝球
|
# 生成蓝球/后区
|
||||||
if lottery_type == 'ssq':
|
if lottery_type == 'ssq':
|
||||||
if 'hot_blues' in freq_result and freq_result['hot_blues']:
|
if 'hot_blues' in freq_result and freq_result['hot_blues']:
|
||||||
# 从热门蓝球中随机选择
|
|
||||||
blue_prediction = random.choice(
|
blue_prediction = random.choice(
|
||||||
freq_result['hot_blues'][:3])
|
freq_result['hot_blues'][:3])
|
||||||
else:
|
else:
|
||||||
@ -520,19 +513,15 @@ class PredictionService:
|
|||||||
# 大乐透后区号码选择
|
# 大乐透后区号码选择
|
||||||
blue_predictions = []
|
blue_predictions = []
|
||||||
if 'hot_blues' in freq_result and freq_result['hot_blues']:
|
if 'hot_blues' in freq_result and freq_result['hot_blues']:
|
||||||
# 从热门后区号码中选择
|
available_blues = freq_result['hot_blues'][:4]
|
||||||
available_blues = freq_result['hot_blues'][:4] # 取前4个热门号码
|
|
||||||
while len(blue_predictions) < 2 and available_blues:
|
while len(blue_predictions) < 2 and available_blues:
|
||||||
num = random.choice(available_blues)
|
num = random.choice(available_blues)
|
||||||
blue_predictions.append(num)
|
blue_predictions.append(num)
|
||||||
available_blues.remove(num)
|
available_blues.remove(num)
|
||||||
|
|
||||||
# 如果还不够2个,随机补充
|
|
||||||
while len(blue_predictions) < 2:
|
while len(blue_predictions) < 2:
|
||||||
num = random.randint(1, max_blue)
|
num = random.randint(1, max_blue)
|
||||||
if num not in blue_predictions:
|
if num not in blue_predictions:
|
||||||
blue_predictions.append(num)
|
blue_predictions.append(num)
|
||||||
|
|
||||||
recommendations.append({
|
recommendations.append({
|
||||||
'method': '热冷号分析',
|
'method': '热冷号分析',
|
||||||
'numbers': sorted(list(selected_numbers)),
|
'numbers': sorted(list(selected_numbers)),
|
||||||
@ -540,6 +529,7 @@ class PredictionService:
|
|||||||
'confidence': '中'
|
'confidence': '中'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 模式分析推荐
|
||||||
if pattern_result and pattern_result.get('success'):
|
if pattern_result and pattern_result.get('success'):
|
||||||
recommendations.append({
|
recommendations.append({
|
||||||
'method': '模式分析',
|
'method': '模式分析',
|
||||||
|
|||||||
20
backend/app/services/telegram_bot.py
Normal file
20
backend/app/services/telegram_bot.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import requests
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def send_message(text, token=None, chat_id=None):
|
||||||
|
token = token or os.getenv(
|
||||||
|
"TELEGRAM_BOT_TOKEN", "1034761353:AAG8AydVpLCCURPhWAUfBX7I4MyDOb-9H8M")
|
||||||
|
chat_id = chat_id or os.getenv("TELEGRAM_CHAT_ID", "43709453")
|
||||||
|
url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||||||
|
data = {
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"text": text,
|
||||||
|
"parse_mode": "Markdown"
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
resp = requests.post(url, data=data, timeout=10)
|
||||||
|
return resp.json()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Telegram推送失败: {e}")
|
||||||
|
return None
|
||||||
@ -9,7 +9,7 @@ from requests.adapters import HTTPAdapter
|
|||||||
from requests.packages.urllib3.util.retry import Retry
|
from requests.packages.urllib3.util.retry import Retry
|
||||||
from sqlalchemy import create_engine, desc
|
from sqlalchemy import create_engine, desc
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from app.models.lottery import SSQLottery, DLTLottery
|
from app.models.lottery import SSQLottery, DLTLottery, SSQLotteryBetRecord, DLTLotteryBetRecord
|
||||||
from app.core.database import engine, SessionLocal
|
from app.core.database import engine, SessionLocal
|
||||||
|
|
||||||
# 配置日志
|
# 配置日志
|
||||||
@ -244,6 +244,9 @@ class LotteryUpdater:
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
# 在开奖数据入库后自动调用
|
||||||
|
self.check_and_update_bet_wins(lottery_type, db)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"更新{lottery_type}数据失败: {str(e)}")
|
logger.error(f"更新{lottery_type}数据失败: {str(e)}")
|
||||||
logger.exception(e) # 打印完整的错误堆栈
|
logger.exception(e) # 打印完整的错误堆栈
|
||||||
@ -254,6 +257,101 @@ class LotteryUpdater:
|
|||||||
logger.info(f"开始更新{self.lottery_types[lottery_type]['name']}数据...")
|
logger.info(f"开始更新{self.lottery_types[lottery_type]['name']}数据...")
|
||||||
self.update_lottery_data(lottery_type)
|
self.update_lottery_data(lottery_type)
|
||||||
|
|
||||||
|
def check_and_update_bet_wins(self, lottery_type: str, db):
|
||||||
|
"""批量比对投注记录并更新中奖信息,支持ssq和dlt"""
|
||||||
|
if lottery_type == 'ssq':
|
||||||
|
LotteryModel = SSQLottery
|
||||||
|
BetModel = SSQLotteryBetRecord
|
||||||
|
else:
|
||||||
|
LotteryModel = DLTLottery
|
||||||
|
BetModel = DLTLotteryBetRecord
|
||||||
|
|
||||||
|
# 查询所有已开奖的期号和开奖号码
|
||||||
|
open_draws = {d.issue: d for d in db.query(LotteryModel).all()}
|
||||||
|
# 查询所有未比对或未中奖的投注记录
|
||||||
|
bets = db.query(BetModel).filter(BetModel.is_winner == 0).all()
|
||||||
|
for bet in bets:
|
||||||
|
draw = open_draws.get(bet.issue)
|
||||||
|
if not draw:
|
||||||
|
continue # 未开奖
|
||||||
|
# 解析投注号码
|
||||||
|
bet_red, bet_blue = [], []
|
||||||
|
try:
|
||||||
|
red_str, blue_str = bet.numbers.split('|')
|
||||||
|
bet_red = [int(x) for x in red_str.split(',') if x]
|
||||||
|
bet_blue = [int(x) for x in blue_str.split(',') if x]
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
# 解析开奖号码
|
||||||
|
if lottery_type == 'ssq':
|
||||||
|
open_red = [draw.red_ball_1, draw.red_ball_2, draw.red_ball_3,
|
||||||
|
draw.red_ball_4, draw.red_ball_5, draw.red_ball_6]
|
||||||
|
open_blue = [draw.blue_ball]
|
||||||
|
else:
|
||||||
|
open_red = [draw.front_ball_1, draw.front_ball_2,
|
||||||
|
draw.front_ball_3, draw.front_ball_4, draw.front_ball_5]
|
||||||
|
open_blue = [draw.back_ball_1, draw.back_ball_2]
|
||||||
|
# 计算命中数
|
||||||
|
red_hit = len(set(bet_red) & set(open_red))
|
||||||
|
blue_hit = len(set(bet_blue) & set(open_blue))
|
||||||
|
# 判断中奖等级和金额
|
||||||
|
win_level, win_amount = self.calc_prize(
|
||||||
|
lottery_type, red_hit, blue_hit, bet_red, bet_blue, open_red, open_blue)
|
||||||
|
is_winner = 1 if win_level else 0
|
||||||
|
# 更新投注记录
|
||||||
|
bet.is_winner = is_winner
|
||||||
|
bet.win_level = win_level
|
||||||
|
bet.win_amount = str(win_amount) if win_amount else None
|
||||||
|
bet.open_code = ','.join(
|
||||||
|
[str(x) for x in open_red]) + '|' + ','.join([str(x) for x in open_blue])
|
||||||
|
bet.open_date = draw.open_time
|
||||||
|
db.add(bet)
|
||||||
|
db.commit()
|
||||||
|
print(f"已完成{lottery_type}投注中奖比对与更新")
|
||||||
|
|
||||||
|
def calc_prize(self, lottery_type, red_hit, blue_hit, bet_red, bet_blue, open_red, open_blue):
|
||||||
|
"""根据命中数判断中奖等级和金额,返回(等级, 金额)"""
|
||||||
|
# 双色球规则
|
||||||
|
if lottery_type == 'ssq':
|
||||||
|
# 中奖规则(简化版,实际可查官方)
|
||||||
|
if red_hit == 6 and blue_hit == 1:
|
||||||
|
return '一等奖', 10000000
|
||||||
|
elif red_hit == 6 and blue_hit == 0:
|
||||||
|
return '二等奖', 5000000
|
||||||
|
elif red_hit == 5 and blue_hit == 1:
|
||||||
|
return '三等奖', 3000
|
||||||
|
elif red_hit == 5 and blue_hit == 0:
|
||||||
|
return '四等奖', 200
|
||||||
|
elif red_hit == 4 and blue_hit == 1:
|
||||||
|
return '四等奖', 200
|
||||||
|
elif red_hit == 4 and blue_hit == 0:
|
||||||
|
return '五等奖', 10
|
||||||
|
elif red_hit == 3 and blue_hit == 1:
|
||||||
|
return '五等奖', 10
|
||||||
|
elif blue_hit == 1:
|
||||||
|
return '六等奖', 5
|
||||||
|
else:
|
||||||
|
return None, None
|
||||||
|
# 大乐透规则
|
||||||
|
else:
|
||||||
|
# 中奖规则(简化版,实际可查官方)
|
||||||
|
if red_hit == 5 and blue_hit == 2:
|
||||||
|
return '一等奖', 10000000
|
||||||
|
elif red_hit == 5 and blue_hit == 1:
|
||||||
|
return '二等奖', 5000000
|
||||||
|
elif red_hit == 5 and blue_hit == 0:
|
||||||
|
return '三等奖', 3000
|
||||||
|
elif red_hit == 4 and blue_hit == 2:
|
||||||
|
return '四等奖', 200
|
||||||
|
elif red_hit == 4 and blue_hit == 1:
|
||||||
|
return '五等奖', 10
|
||||||
|
elif red_hit == 3 and blue_hit == 2:
|
||||||
|
return '五等奖', 10
|
||||||
|
elif (red_hit == 2 and blue_hit == 2) or (red_hit == 1 and blue_hit == 2) or (red_hit == 0 and blue_hit == 2):
|
||||||
|
return '六等奖', 5
|
||||||
|
else:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
updater = LotteryUpdater()
|
updater = LotteryUpdater()
|
||||||
|
|||||||
@ -165,5 +165,25 @@ export const lotteryApi = {
|
|||||||
|
|
||||||
updateAllLotteryData() {
|
updateAllLotteryData() {
|
||||||
return api.post('/update/all')
|
return api.post('/update/all')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取投注历史记录
|
||||||
|
getBetHistory(lotteryType, params = {}) {
|
||||||
|
return request({
|
||||||
|
url: `/api/v1/lottery/bet-history/${lotteryType}`,
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
page: params.page || 1,
|
||||||
|
size: params.size || 20
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 手动触发中奖比对
|
||||||
|
checkWin(lotteryType) {
|
||||||
|
return request({
|
||||||
|
url: `/api/v1/lottery/bet-history/check-win/${lotteryType}`,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,4 +34,12 @@ export function getEnsemblePrediction(lotteryType, periods = 100) {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
params: { periods }
|
params: { periods }
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 今日推荐
|
||||||
|
export function recommendToday() {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/prediction/recommend/today',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<el-form :inline="true" class="prediction-form">
|
<el-form :inline="true" class="prediction-form">
|
||||||
<el-form-item label="早点类型">
|
<el-form-item label="早点类型">
|
||||||
<el-select v-model="lotteryType">
|
<el-select v-model="lotteryType" placeholder="请选择" style="width: 120px">
|
||||||
<el-option label="红蓝球煎饼" value="ssq" />
|
<el-option label="红蓝球煎饼" value="ssq" />
|
||||||
<el-option label="大乐斗豆浆" value="dlt" />
|
<el-option label="大乐斗豆浆" value="dlt" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@ -442,6 +442,9 @@ export default {
|
|||||||
|
|
||||||
// 组件挂载时自动训练模型
|
// 组件挂载时自动训练模型
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (!lotteryType.value) {
|
||||||
|
lotteryType.value = 'ssq'
|
||||||
|
}
|
||||||
trainModel()
|
trainModel()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user