Files

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)