世界杯预测系统 v1.0.0 - 初始版本
This commit is contained in:
576
backend/app.py
Normal file
576
backend/app.py
Normal file
@@ -0,0 +1,576 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
世界杯预测系统 - 后端API
|
||||
端口: 16015
|
||||
"""
|
||||
|
||||
from flask import Flask, jsonify, request, send_from_directory
|
||||
from flask_cors import CORS
|
||||
import sqlite3
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import os
|
||||
|
||||
app = Flask(__name__, static_folder='../frontend')
|
||||
CORS(app)
|
||||
|
||||
DB_PATH = os.path.join(os.path.dirname(__file__), 'worldcup.db')
|
||||
|
||||
|
||||
def get_db():
|
||||
"""获取数据库连接"""
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
|
||||
def init_db():
|
||||
"""初始化数据库"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 预测算法表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS algorithms (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
accuracy REAL DEFAULT 0,
|
||||
total_predictions INTEGER DEFAULT 0,
|
||||
correct_predictions INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active INTEGER DEFAULT 1
|
||||
)
|
||||
''')
|
||||
|
||||
# 比赛表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS matches (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
match_code TEXT NOT NULL UNIQUE,
|
||||
home_team TEXT NOT NULL,
|
||||
away_team TEXT NOT NULL,
|
||||
match_time TIMESTAMP,
|
||||
home_score INTEGER,
|
||||
away_score INTEGER,
|
||||
status TEXT DEFAULT 'upcoming',
|
||||
round_name TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 预测结果表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS predictions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
match_id INTEGER NOT NULL,
|
||||
algorithm_id INTEGER NOT NULL,
|
||||
predicted_home_score INTEGER,
|
||||
predicted_away_score INTEGER,
|
||||
predicted_winner TEXT,
|
||||
confidence REAL,
|
||||
predicted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_correct INTEGER,
|
||||
FOREIGN KEY (match_id) REFERENCES matches(id),
|
||||
FOREIGN KEY (algorithm_id) REFERENCES algorithms(id),
|
||||
UNIQUE(match_id, algorithm_id)
|
||||
)
|
||||
''')
|
||||
|
||||
# 系统配置表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
config_key TEXT NOT NULL UNIQUE,
|
||||
config_value TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 插入默认配置
|
||||
default_configs = [
|
||||
('prediction_cycle_hours', '24'),
|
||||
('last_prediction_time', datetime.now().isoformat()),
|
||||
('world_cup_year', '2026')
|
||||
]
|
||||
|
||||
for key, value in default_configs:
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO system_config (config_key, config_value)
|
||||
VALUES (?, ?)
|
||||
''', (key, value))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def calculate_algorithm_stats(algorithm_id):
|
||||
"""计算算法统计数据"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN is_correct = 1 THEN 1 ELSE 0 END) as correct
|
||||
FROM predictions
|
||||
WHERE algorithm_id = ? AND is_correct IS NOT NULL
|
||||
''', (algorithm_id,))
|
||||
|
||||
result = cursor.fetchone()
|
||||
total = result['total'] or 0
|
||||
correct = result['correct'] or 0
|
||||
accuracy = (correct / total * 100) if total > 0 else 0
|
||||
|
||||
cursor.execute('''
|
||||
UPDATE algorithms
|
||||
SET total_predictions = ?, correct_predictions = ?, accuracy = ?
|
||||
WHERE id = ?
|
||||
''', (total, correct, accuracy, algorithm_id))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
# ==================== 前端页面路由 ====================
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""主页"""
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
|
||||
@app.route('/admin')
|
||||
def admin():
|
||||
"""管理后台"""
|
||||
return send_from_directory(app.static_folder, 'admin.html')
|
||||
|
||||
|
||||
@app.route('/css/<path:filename>')
|
||||
def css(filename):
|
||||
return send_from_directory(os.path.join(app.static_folder, 'css'), filename)
|
||||
|
||||
|
||||
@app.route('/js/<path:filename>')
|
||||
def js(filename):
|
||||
return send_from_directory(os.path.join(app.static_folder, 'js'), filename)
|
||||
|
||||
|
||||
# ==================== API路由 ====================
|
||||
|
||||
@app.route('/api/matches', methods=['GET'])
|
||||
def get_matches():
|
||||
"""获取所有比赛"""
|
||||
status = request.args.get('status', 'all')
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
if status == 'upcoming':
|
||||
cursor.execute('''
|
||||
SELECT * FROM matches
|
||||
WHERE status = 'upcoming' OR status IS NULL
|
||||
ORDER BY match_time ASC
|
||||
''')
|
||||
elif status == 'finished':
|
||||
cursor.execute('''
|
||||
SELECT * FROM matches
|
||||
WHERE status = 'finished'
|
||||
ORDER BY match_time DESC
|
||||
''')
|
||||
else:
|
||||
cursor.execute('SELECT * FROM matches ORDER BY match_time DESC')
|
||||
|
||||
matches = [dict(row) for row in cursor.fetchall()]
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True, 'data': matches})
|
||||
|
||||
|
||||
@app.route('/api/matches/<int:match_id>', methods=['GET'])
|
||||
def get_match(match_id):
|
||||
"""获取单场比赛详情"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT * FROM matches WHERE id = ?', (match_id,))
|
||||
match = cursor.fetchone()
|
||||
|
||||
if not match:
|
||||
conn.close()
|
||||
return jsonify({'success': False, 'message': '比赛不存在'}), 404
|
||||
|
||||
# 获取该比赛的所有预测
|
||||
cursor.execute('''
|
||||
SELECT p.*, a.name as algorithm_name, a.accuracy as algorithm_accuracy
|
||||
FROM predictions p
|
||||
JOIN algorithms a ON p.algorithm_id = a.id
|
||||
WHERE p.match_id = ?
|
||||
''', (match_id,))
|
||||
|
||||
predictions = [dict(row) for row in cursor.fetchall()]
|
||||
conn.close()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'match': dict(match),
|
||||
'predictions': predictions
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/matches', methods=['POST'])
|
||||
def add_match():
|
||||
"""添加比赛"""
|
||||
data = request.json
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute('''
|
||||
INSERT INTO matches (match_code, home_team, away_team, match_time, round_name, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
data['match_code'],
|
||||
data['home_team'],
|
||||
data['away_team'],
|
||||
data.get('match_time'),
|
||||
data.get('round_name', '小组赛'),
|
||||
data.get('status', 'upcoming')
|
||||
))
|
||||
|
||||
match_id = cursor.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True, 'data': {'id': match_id}})
|
||||
except sqlite3.IntegrityError:
|
||||
conn.close()
|
||||
return jsonify({'success': False, 'message': '比赛代码已存在'}), 400
|
||||
|
||||
|
||||
@app.route('/api/matches/<int:match_id>', methods=['PUT'])
|
||||
def update_match(match_id):
|
||||
"""更新比赛"""
|
||||
data = request.json
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT id FROM matches WHERE id = ?', (match_id,))
|
||||
if not cursor.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'success': False, 'message': '比赛不存在'}), 404
|
||||
|
||||
update_fields = []
|
||||
values = []
|
||||
|
||||
for field in ['home_team', 'away_team', 'match_time', 'home_score', 'away_score', 'status', 'round_name']:
|
||||
if field in data:
|
||||
update_fields.append(f'{field} = ?')
|
||||
values.append(data[field])
|
||||
|
||||
if update_fields:
|
||||
values.append(match_id)
|
||||
cursor.execute(f'''
|
||||
UPDATE matches SET {', '.join(update_fields)}
|
||||
WHERE id = ?
|
||||
''', values)
|
||||
conn.commit()
|
||||
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/matches/<int:match_id>', methods=['DELETE'])
|
||||
def delete_match(match_id):
|
||||
"""删除比赛"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('DELETE FROM predictions WHERE match_id = ?', (match_id,))
|
||||
cursor.execute('DELETE FROM matches WHERE id = ?', (match_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/algorithms', methods=['GET'])
|
||||
def get_algorithms():
|
||||
"""获取所有算法"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT * FROM algorithms ORDER BY accuracy DESC')
|
||||
algorithms = [dict(row) for row in cursor.fetchall()]
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True, 'data': algorithms})
|
||||
|
||||
|
||||
@app.route('/api/algorithms/<int:algorithm_id>', methods=['GET'])
|
||||
def get_algorithm(algorithm_id):
|
||||
"""获取单个算法详情"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT * FROM algorithms WHERE id = ?', (algorithm_id,))
|
||||
algorithm = cursor.fetchone()
|
||||
|
||||
if not algorithm:
|
||||
conn.close()
|
||||
return jsonify({'success': False, 'message': '算法不存在'}), 404
|
||||
|
||||
# 获取该算法的预测历史
|
||||
cursor.execute('''
|
||||
SELECT p.*, m.home_team, m.away_team, m.home_score, m.away_score, m.match_time
|
||||
FROM predictions p
|
||||
JOIN matches m ON p.match_id = m.id
|
||||
WHERE p.algorithm_id = ?
|
||||
ORDER BY m.match_time DESC
|
||||
LIMIT 50
|
||||
''', (algorithm_id,))
|
||||
|
||||
history = [dict(row) for row in cursor.fetchall()]
|
||||
conn.close()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'algorithm': dict(algorithm),
|
||||
'history': history
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/algorithms', methods=['POST'])
|
||||
def add_algorithm():
|
||||
"""添加算法"""
|
||||
data = request.json
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute('''
|
||||
INSERT INTO algorithms (name, description)
|
||||
VALUES (?, ?)
|
||||
''', (data['name'], data.get('description', '')))
|
||||
|
||||
algorithm_id = cursor.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True, 'data': {'id': algorithm_id}})
|
||||
except sqlite3.IntegrityError:
|
||||
conn.close()
|
||||
return jsonify({'success': False, 'message': '算法名称已存在'}), 400
|
||||
|
||||
|
||||
@app.route('/api/algorithms/<int:algorithm_id>', methods=['PUT'])
|
||||
def update_algorithm(algorithm_id):
|
||||
"""更新算法"""
|
||||
data = request.json
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT id FROM algorithms WHERE id = ?', (algorithm_id,))
|
||||
if not cursor.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'success': False, 'message': '算法不存在'}), 404
|
||||
|
||||
update_fields = []
|
||||
values = []
|
||||
|
||||
for field in ['name', 'description', 'is_active']:
|
||||
if field in data:
|
||||
update_fields.append(f'{field} = ?')
|
||||
values.append(data[field])
|
||||
|
||||
if update_fields:
|
||||
values.append(algorithm_id)
|
||||
cursor.execute(f'''
|
||||
UPDATE algorithms SET {', '.join(update_fields)}
|
||||
WHERE id = ?
|
||||
''', values)
|
||||
conn.commit()
|
||||
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/algorithms/<int:algorithm_id>', methods=['DELETE'])
|
||||
def delete_algorithm(algorithm_id):
|
||||
"""删除算法"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('DELETE FROM predictions WHERE algorithm_id = ?', (algorithm_id,))
|
||||
cursor.execute('DELETE FROM algorithms WHERE id = ?', (algorithm_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/predictions', methods=['POST'])
|
||||
def add_prediction():
|
||||
"""添加预测"""
|
||||
data = request.json
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO predictions
|
||||
(match_id, algorithm_id, predicted_home_score, predicted_away_score, predicted_winner, confidence)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
data['match_id'],
|
||||
data['algorithm_id'],
|
||||
data.get('predicted_home_score'),
|
||||
data.get('predicted_away_score'),
|
||||
data.get('predicted_winner'),
|
||||
data.get('confidence', 0.5)
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return jsonify({'success': False, 'message': str(e)}), 400
|
||||
|
||||
|
||||
@app.route('/api/predictions/evaluate', methods=['POST'])
|
||||
def evaluate_predictions():
|
||||
"""评估预测结果"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 获取所有已完成的比赛
|
||||
cursor.execute('''
|
||||
SELECT m.*, p.id as prediction_id, p.predicted_home_score, p.predicted_away_score,
|
||||
p.algorithm_id, p.predicted_winner
|
||||
FROM matches m
|
||||
JOIN predictions p ON m.id = p.match_id
|
||||
WHERE m.status = 'finished' AND m.home_score IS NOT NULL AND m.away_score IS NOT NULL
|
||||
AND p.is_correct IS NULL
|
||||
''')
|
||||
|
||||
predictions = cursor.fetchall()
|
||||
|
||||
for pred in predictions:
|
||||
# 判断预测是否正确
|
||||
is_correct = 0
|
||||
|
||||
if pred['predicted_home_score'] and pred['predicted_away_score']:
|
||||
# 比分预测
|
||||
if pred['predicted_home_score'] == pred['home_score'] and pred['predicted_away_score'] == pred['away_score']:
|
||||
is_correct = 1
|
||||
elif pred['predicted_winner']:
|
||||
# 胜负预测
|
||||
actual_winner = 'draw' if pred['home_score'] == pred['away_score'] else (
|
||||
pred['home_team'] if pred['home_score'] > pred['away_score'] else pred['away_team']
|
||||
)
|
||||
if pred['predicted_winner'] == actual_winner:
|
||||
is_correct = 1
|
||||
|
||||
cursor.execute('''
|
||||
UPDATE predictions SET is_correct = ? WHERE id = ?
|
||||
''', (is_correct, pred['prediction_id']))
|
||||
|
||||
# 更新算法统计
|
||||
calculate_algorithm_stats(pred['algorithm_id'])
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True, 'evaluated': len(predictions)})
|
||||
|
||||
|
||||
@app.route('/api/config', methods=['GET'])
|
||||
def get_config():
|
||||
"""获取系统配置"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT config_key, config_value FROM system_config')
|
||||
configs = {row['config_key']: row['config_value'] for row in cursor.fetchall()}
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True, 'data': configs})
|
||||
|
||||
|
||||
@app.route('/api/config', methods=['POST'])
|
||||
def update_config():
|
||||
"""更新系统配置"""
|
||||
data = request.json
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
for key, value in data.items():
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO system_config (config_key, config_value, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
''', (key, str(value), datetime.now().isoformat()))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/stats/overview', methods=['GET'])
|
||||
def get_overview_stats():
|
||||
"""获取总体统计"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 比赛统计
|
||||
cursor.execute('SELECT COUNT(*) as total FROM matches')
|
||||
total_matches = cursor.fetchone()['total']
|
||||
|
||||
cursor.execute('SELECT COUNT(*) as total FROM matches WHERE status = "finished"')
|
||||
finished_matches = cursor.fetchone()['total']
|
||||
|
||||
# 算法统计
|
||||
cursor.execute('SELECT COUNT(*) as total FROM algorithms WHERE is_active = 1')
|
||||
active_algorithms = cursor.fetchone()['total']
|
||||
|
||||
# 预测统计
|
||||
cursor.execute('SELECT COUNT(*) as total FROM predictions')
|
||||
total_predictions = cursor.fetchone()['total']
|
||||
|
||||
cursor.execute('SELECT COUNT(*) as total FROM predictions WHERE is_correct = 1')
|
||||
correct_predictions = cursor.fetchone()['total']
|
||||
|
||||
overall_accuracy = (correct_predictions / total_predictions * 100) if total_predictions > 0 else 0
|
||||
|
||||
conn.close()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'total_matches': total_matches,
|
||||
'finished_matches': finished_matches,
|
||||
'upcoming_matches': total_matches - finished_matches,
|
||||
'active_algorithms': active_algorithms,
|
||||
'total_predictions': total_predictions,
|
||||
'correct_predictions': correct_predictions,
|
||||
'overall_accuracy': round(overall_accuracy, 2)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_db()
|
||||
app.run(host='0.0.0.0', port=16015, debug=False)
|
||||
2
backend/requirements.txt
Normal file
2
backend/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
flask>=2.0.0
|
||||
flask-cors>=3.0.0
|
||||
BIN
backend/worldcup.db
Normal file
BIN
backend/worldcup.db
Normal file
Binary file not shown.
Reference in New Issue
Block a user