576 lines
16 KiB
Python
576 lines
16 KiB
Python
#!/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) |