Trae Assistant
Initial commit with enhanced functionality
b185c87
import random
import math
import numpy as np
import pandas as pd
import io
from flask import Flask, jsonify, request, render_template
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB limit
# 模拟退火算法参数
INITIAL_TEMP = 1000
COOLING_RATE = 0.995
MIN_TEMP = 1
MAX_ITERATIONS = 5000
class SeatingSolver:
def __init__(self, guests, tables, constraints):
"""
guests: list of {id, name, group, tags}
tables: list of {id, name, capacity}
constraints: list of {guest_id_1, guest_id_2, type: 'must'|'cannot', weight}
"""
self.guests = guests
self.tables = tables
self.constraints = constraints
self.guest_map = {g['id']: g for g in guests}
# 预处理关系矩阵
self.relation_matrix = {} # (id1, id2) -> score
for c in constraints:
key = tuple(sorted([c['g1'], c['g2']]))
w = c.get('weight', 10)
if c['type'] == 'must':
self.relation_matrix[key] = w
elif c['type'] == 'cannot':
self.relation_matrix[key] = -w
def solve(self):
# 初始解:随机分配
current_solution = self._initial_solution()
current_score = self._calculate_score(current_solution)
best_solution = current_solution.copy()
best_score = current_score
temp = INITIAL_TEMP
for i in range(MAX_ITERATIONS):
if temp < MIN_TEMP:
break
# 产生新解:随机移动或交换
new_solution = self._neighbor_solution(current_solution)
new_score = self._calculate_score(new_solution)
# 接受准则
delta = new_score - current_score
if delta > 0 or math.exp(delta / temp) > random.random():
current_solution = new_solution
current_score = new_score
if current_score > best_score:
best_solution = current_solution.copy()
best_score = current_score
temp *= COOLING_RATE
return self._format_result(best_solution, best_score)
def _initial_solution(self):
# 简单的随机分配,尽量填满桌子
solution = {} # guest_id -> table_id
table_slots = []
for t in self.tables:
for _ in range(t['capacity']):
table_slots.append(t['id'])
random.shuffle(table_slots)
for i, guest in enumerate(self.guests):
if i < len(table_slots):
solution[guest['id']] = table_slots[i]
else:
# 没座位的放在 unassigned (None)
solution[guest['id']] = None
return solution
def _neighbor_solution(self, solution):
new_sol = solution.copy()
guest_ids = list(self.guests)
if not guest_ids:
return new_sol
action = random.choice(['move', 'swap'])
if action == 'move':
# 移动一个客人到另一张桌子(如果有空位)
g1 = random.choice(self.guests)['id']
t_target = random.choice(self.tables)['id']
# 检查容量
current_count = sum(1 for gid, tid in new_sol.items() if tid == t_target)
table_cap = next(t['capacity'] for t in self.tables if t['id'] == t_target)
if current_count < table_cap:
new_sol[g1] = t_target
elif action == 'swap':
# 交换两个客人的位置
g1 = random.choice(self.guests)['id']
g2 = random.choice(self.guests)['id']
if g1 != g2:
new_sol[g1], new_sol[g2] = new_sol[g2], new_sol[g1]
return new_sol
def _calculate_score(self, solution):
score = 0
# 1. 约束分数
for (g1, g2), weight in self.relation_matrix.items():
t1 = solution.get(g1)
t2 = solution.get(g2)
if t1 is not None and t2 is not None and t1 == t2:
score += weight * 10 # 权重放大
# 2. 组别分数 (同一组的尽量坐一起)
# 遍历每张桌子
table_guests = {}
for gid, tid in solution.items():
if tid:
if tid not in table_guests: table_guests[tid] = []
table_guests[tid].append(self.guest_map[gid])
for tid, guests in table_guests.items():
groups = [g.get('group') for g in guests if g.get('group')]
# 计算组别一致性
# 如果桌子上大部分人是同一组,加分
if groups:
# 简单做法:每有一对同组人,加 2 分
for i in range(len(groups)):
for j in range(i+1, len(groups)):
if groups[i] == groups[j]:
score += 2
return score
def _format_result(self, solution, score):
# 转换为前端易用的格式
tables_res = []
unassigned = []
table_guests = {t['id']: [] for t in self.tables}
for gid, tid in solution.items():
if tid:
table_guests[tid].append(self.guest_map[gid])
else:
unassigned.append(self.guest_map[gid])
for t in self.tables:
tables_res.append({
'id': t['id'],
'name': t['name'],
'capacity': t['capacity'],
'guests': table_guests[t['id']]
})
return {
'tables': tables_res,
'unassigned': unassigned,
'score': score
}
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/solve', methods=['POST'])
def solve_seating():
data = request.json
guests = data.get('guests', [])
tables = data.get('tables', [])
constraints = data.get('constraints', []) # [{g1: id, g2: id, type: 'must'|'cannot'}]
if not guests or not tables:
return jsonify({'error': 'Missing guests or tables'}), 400
solver = SeatingSolver(guests, tables, constraints)
result = solver.solve()
return jsonify(result)
@app.route('/api/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
try:
# Determine file type
if file.filename.endswith('.csv'):
df = pd.read_csv(file)
elif file.filename.endswith(('.xls', '.xlsx')):
df = pd.read_excel(file)
else:
return jsonify({'error': 'Unsupported file type'}), 400
# Normalize columns
# Expected columns: Name (required), Group (optional), ID (optional)
# Rename columns to standard names if possible
df.columns = [str(c).strip().lower() for c in df.columns]
# Map common Chinese headers to English keys
col_map = {
'姓名': 'name', 'name': 'name',
'组别': 'group', 'group': 'group', '部门': 'group',
'id': 'id', '编号': 'id'
}
# Rename columns
new_cols = {}
for col in df.columns:
if col in col_map:
new_cols[col] = col_map[col]
df = df.rename(columns=new_cols)
if 'name' not in df.columns:
return jsonify({'error': '缺少必要列: 姓名 (Name)'}), 400
guests = []
for i, row in df.iterrows():
guest_id = str(row.get('id', f'g_{i}_{random.randint(1000,9999)}'))
if pd.isna(guest_id) or guest_id == 'nan':
guest_id = f'g_{i}_{random.randint(1000,9999)}'
name = str(row['name'])
if pd.isna(name) or name == 'nan':
continue
group = str(row.get('group', 'Default'))
if pd.isna(group) or group == 'nan':
group = 'Default'
guests.append({
'id': guest_id,
'name': name,
'group': group,
'avatar': f'https://api.dicebear.com/7.x/avataaars/svg?seed={guest_id}'
})
return jsonify({'guests': guests})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/demo-data')
def demo_data():
# 生成演示数据
groups = ['家人', '同事', '大学同学', '高中同学', '合作伙伴']
guests = []
for i in range(50):
group = random.choice(groups)
guests.append({
'id': f'g_{i}',
'name': f'Guest {i+1}',
'group': group,
'avatar': f'https://api.dicebear.com/7.x/avataaars/svg?seed={i}'
})
tables = []
for i in range(6):
tables.append({
'id': f't_{i}',
'name': f'Table {i+1}',
'capacity': 10
})
# 随机生成一些约束
constraints = []
# 必须坐一起
for _ in range(5):
g1 = random.choice(guests)['id']
g2 = random.choice(guests)['id']
if g1 != g2:
constraints.append({'g1': g1, 'g2': g2, 'type': 'must', 'weight': 10})
# 不能坐一起
for _ in range(3):
g1 = random.choice(guests)['id']
g2 = random.choice(guests)['id']
if g1 != g2:
constraints.append({'g1': g1, 'g2': g2, 'type': 'cannot', 'weight': 10})
return jsonify({
'guests': guests,
'tables': tables,
'constraints': constraints
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860, debug=True)