diff --git a/.gitignore b/.gitignore index 5294833..c6d0e80 100644 --- a/.gitignore +++ b/.gitignore @@ -31,10 +31,6 @@ Thumbs.db *.log *.txt -# Excel files (示例数据) -老师信息示例.xlsx -学生信息示例.xlsx -匹配结果示例.xlsx # Certificates *.pfx @@ -48,5 +44,4 @@ Thumbs.db *.zip # Documentation (auto-generated) -*.html -*.md \ No newline at end of file +*.html \ No newline at end of file diff --git a/debug_match.py b/debug_match.py new file mode 100644 index 0000000..e9e810e --- /dev/null +++ b/debug_match.py @@ -0,0 +1,105 @@ +import pandas as pd + +df_teachers = pd.read_excel('老师信息示例.xlsx') +df_students = pd.read_excel('学生信息示例.xlsx') + +teachers = [] +for _, row in df_teachers.iterrows(): + availability = {} + for col in df_teachers.columns: + if col not in ['老师姓名', '老师类型']: + val = row.get(col, '') + if pd.notna(val) and str(val).strip(): + availability[col] = str(val) + teachers.append({ + 'id': row['老师姓名'], + 'name': row['老师姓名'], + 'type': row['老师类型'], + 'availability': availability + }) + +students = [] +for _, row in df_students.iterrows(): + history_str = row.get('历史匹配老师', '') + history = [] + if pd.notna(history_str) and str(history_str).strip(): + history = [t.strip() for t in str(history_str).split(',')] + + availability = {} + for col in df_students.columns: + if col not in ['学生姓名', '历史匹配老师']: + val = row.get(col, '') + if pd.notna(val) and str(val).strip(): + availability[col] = str(val) + + students.append({ + 'id': row['学生姓名'], + 'name': row['学生姓名'], + 'availability': availability, + 'history': history + }) + +print('=== 检查数据导入 ===') +print(f'老师数量: {len(teachers)}') +print(f'学生数量: {len(students)}') +print() + +print('=== 检查老师类型分布 ===') +new_count = sum(1 for t in teachers if t['type'] == '新') +old_count = sum(1 for t in teachers if t['type'] == '旧') +print(f'新老师: {new_count}, 旧老师: {old_count}') +print() + +print('=== 检查学生历史记录分布 ===') +history_counts = {} +for s in students: + cnt = len(s['history']) + history_counts[cnt] = history_counts.get(cnt, 0) + 1 +print('学生历史记录数量分布:') +for cnt in sorted(history_counts.keys()): + print(f' {cnt} 个历史老师: {history_counts[cnt]} 人') +print() + +def parse_slots(time_str): + slots = [] + for slot in time_str.split(';'): + slot = slot.strip() + if slot and '-' in slot: + parts = slot.split('-') + if len(parts) == 2: + slots.append((parts[0].strip(), parts[1].strip())) + return slots + +print('=== 检查时间段匹配情况 ===') +for student in students[:5]: + history_count = len(student['history']) + expected_type = '旧' if history_count == 0 else ('新' if history_count % 2 == 1 else '旧') + + print(f'\n学生: {student["name"]}') + print(f' 期望老师类型: {expected_type}') + print(f' 可用时间: {student["availability"]}') + print(f' 历史老师: {student["history"]}') + + found_match = False + for teacher in teachers: + if teacher['type'] != expected_type: + continue + if teacher['id'] in student['history']: + continue + + for day, s_time in student['availability'].items(): + if day not in teacher['availability']: + continue + + t_time = teacher['availability'][day] + s_slots = parse_slots(s_time) + t_slots = parse_slots(t_time) + + for s_start, s_end in s_slots: + for t_start, t_end in t_slots: + if s_start == t_start and s_end == t_end: + print(f' ✓ {teacher["name"]} 在 {day} 有匹配时间段: {s_start}-{s_end}') + found_match = True + + if not found_match: + print(f' ✗ 未找到匹配的老师') \ No newline at end of file diff --git a/匹配结果示例.xlsx b/匹配结果示例.xlsx new file mode 100644 index 0000000..f72a959 Binary files /dev/null and b/匹配结果示例.xlsx differ diff --git a/学生信息示例.xlsx b/学生信息示例.xlsx new file mode 100644 index 0000000..47b1b31 Binary files /dev/null and b/学生信息示例.xlsx differ diff --git a/开发文档.md b/开发文档.md new file mode 100644 index 0000000..174e415 --- /dev/null +++ b/开发文档.md @@ -0,0 +1,558 @@ +# 老师学员匹配系统 - 开发文档 + +## 项目概述 + +一个用于老师和学生智能匹配的桌面应用程序,基于Flet框架开发,支持时间兼容性检查、历史匹配记录规避、新/旧老师交替匹配、每日唯一性约束等功能。 + +## 技术栈 + +| 技术 | 版本 | 用途 | +|------|------|------| +| Python | 3.13+ | 开发语言 | +| Flet | 0.80.4+ | UI框架(基于Flutter) | +| pandas | 2.3+ | Excel数据处理 | +| openpyxl | 3.1+ | Excel文件读写 | +| PyInstaller | 6.16+ | 打包工具 | + +## 项目结构 + +``` +paike/ +├── main.py # 主程序入口 +├── build.bat # 打包脚本 +├── requirements.txt # 依赖列表(可选) +├── 老师信息示例.xlsx # 老师数据示例 +├── 学生信息示例.xlsx # 学生数据示例 +├── generate_test_data.py # 测试数据生成脚本 +├── dist/ # 打包输出目录 +│ ├── main.exe # 打包后的可执行文件 +│ ├── 老师信息示例.xlsx +│ └── 学生信息示例.xlsx +└── build/ # PyInstaller构建缓存 +``` + +## 核心功能需求 + +### 1. 数据导入 +- 从Excel文件导入老师信息(包含老师类型) +- 从Excel文件导入学生信息(包含历史匹配) +- 支持的文件格式:`.xlsx` + +### 2. 匹配规则(按优先级) +| 优先级 | 规则 | 类型 | 说明 | +|-------|------|------|------| +| 1 | 首次上课限制 | 硬约束 | 学生第一次上课只能匹配旧老师 | +| 2 | 交替匹配规则 | 硬约束 | 后续上课按"新老师→旧老师→新老师→..."交替 | +| 3 | 历史匹配规避 | 硬约束 | 学生不能匹配之前上过课的老师 | +| 4 | 每日唯一性 | 硬约束 | 每位老师每天只能接待一位学生 | +| 5 | 均衡分配 | 软约束 | 优先匹配次数少的老师 | + +### 3. 结果导出 +- 导出匹配结果到Excel文件 +- 用户可选择保存位置 +- 中文列标题 + +## 数据格式规范 + +### 老师信息Excel格式 + +| 列名 | 数据类型 | 说明 | 示例 | +|------|----------|------|------| +| 老师姓名 | 字符串 | 老师唯一标识 | 张1老师 | +| 老师类型 | 字符串 | 新老师/旧老师 | 旧老师 | +| 备注 | 字符串 | 备注信息(可选) | 数学老师 | +| 1月1日 | 字符串 | 可用时间段(分号分隔) | 9:00-10:30; 14:00-15:30 | +| 1月2日 | 字符串 | 可用时间段(分号分隔) | 10:30-12:00 | +| ... | ... | ... | ... | + +**注意**: +- 时间段格式:`开始时间-结束时间` +- 多个时间段用分号`;`分隔 +- 没有可用时间的日期留空 + +### 学生信息Excel格式 + +| 列名 | 数据类型 | 说明 | 示例 | +|------|----------|------|------| +| 学生姓名 | 字符串 | 学生唯一标识 | 学生1 | +| 历史匹配老师 | 字符串 | 历史老师姓名(逗号分隔) | 张1老师,张2老师 | +| 备注 | 字符串 | 备注信息(可选) | | +| 1月1日 | 字符串 | 可用时间段(分号分隔) | 9:00-10:30; 14:00-15:30 | +| 1月2日 | 字符串 | 可用时间段(分号分隔) | 10:30-12:00 | +| ... | ... | ... | ... | + +### 匹配结果Excel格式 + +| 列名 | 说明 | +|------|------| +| 学生 | 学生姓名 | +| 老师 | 匹配的老师姓名 | +| 日期 | 匹配日期 | +| 时段 | 匹配时间段 | +| 状态 | 成功/未匹配 | + +## 代码架构 + +### 主类:TeacherStudentMatcher + +```python +class TeacherStudentMatcher: + def __init__(self, page: ft.Page): + # 初始化页面配置 + self.page.title = "老师学员匹配系统" + self.page.window_width = 1400 + self.page.window_height = 800 + + # 数据存储 + self.teachers = [] # 老师列表 + self.students = [] # 学生列表 + self.matches = [] # 匹配结果 +``` + +### 核心数据结构 + +```python +# 老师数据结构 +teacher = { + "id": "老师姓名", + "name": "老师姓名", + "type": "旧老师", # 新增:老师类型 + "availability": { + "1月1日": "9:00-10:30; 14:00-15:30", + "1月2日": "10:30-12:00", + # ... + }, + "other": "备注信息" +} + +# 学生数据结构 +student = { + "id": "学生姓名", + "name": "学生姓名", + "availability": { + "1月1日": "9:00-10:30; 14:00-15:30", + # ... + }, + "history": ["张1老师", "张2老师"], # 历史匹配老师列表 + "other": "备注信息" +} + +# 匹配结果结构 +match = { + "student_name": "学生姓名", + "teacher_name": "老师姓名", + "match_date": "1月1日", + "match_period": "9:00-10:30", + "status": "成功" +} +``` + +### 核心方法 + +#### 1. UI初始化 +```python +def setup_ui(self): + # 创建页面布局:三栏布局 + # - 顶部标题栏 + # - 操作按钮栏 + # - 主内容区(老师信息 | 学生信息 | 匹配结果) +``` + +#### 2. 数据导入 +```python +def import_teachers(self, e): + # 使用tkinter文件对话框选择Excel文件 + # 解析Excel数据,提取老师信息 + # 动态列(日期)作为可用时间段存储 + # 新增:解析"老师类型"字段 + +def import_students(self, e): + # 使用tkinter文件对话框选择Excel文件 + # 解析Excel数据,提取学生信息 + # 历史匹配老师用逗号分隔 + # 动态列(日期)作为可用时间段存储 +``` + +#### 3. 时间段解析 +```python +def parse_time_slots(self, time_str): + # 输入: "9:00-10:30; 14:00-15:30" + # 输出: [("9:00", "10:30"), ("14:00", "15:30")] + # + # 解析逻辑: + # 1. 按分号分割时间段 + # 2. 每个时间段按短横线分割起始和结束时间 + # 3. 返回时间对元组列表 +``` + +#### 4. 匹配算法 +```python +def start_match(self, e): + # 匹配流程: + # 1. 初始化数据结构 + # - teacher_assigned_days: 记录老师已分配的日期(新增) + # - teacher_match_count: 记录每个老师已匹配的学生数 + # + # 2. 遍历每个学生 + # a. 计算期望老师类型(新增逻辑) + # - 历史记录为空 → 旧老师 + # - 历史记录奇数 → 新老师 + # - 历史记录偶数 → 旧老师 + # b. 收集所有可能的匹配 + # - 遍历所有老师 + # - 跳过历史匹配过的老师 + # - 检查老师类型是否符合期望(新增) + # - 检查时间兼容性 + # - 检查老师该日期是否已被分配(新增:按日期而非时段) + # + # c. 选择最优匹配 + # - 按老师已匹配次数升序排序(优先选择匹配次数少的老师) + # - 选择第一个(匹配次数最少的) + # + # d. 更新状态 + # - 记录匹配结果 + # - 更新学生的历史匹配记录 + # - 标记老师该日期已分配(新增:按日期) + # - 增加老师的匹配计数 + # + # e. 处理未匹配情况 + # - 如果没有可用的匹配,记录状态为"未匹配" +``` + +#### 5. 结果导出 +```python +def export_results(self, e): + # 使用tkinter文件对话框让用户选择保存位置 + # 导出为Excel文件,中文列标题 +``` + +## 匹配算法详解 + +### 贪心匹配策略 + +本系统采用贪心算法实现匹配,优先考虑: + +1. **首次上课限制**(硬约束) + - 如果学生没有历史匹配记录,只能匹配旧老师 + - 确保新学生由经验丰富的老师指导 + +2. **交替匹配规则**(硬约束) + - 根据历史匹配次数的奇偶性决定期望老师类型 + - 历史次数为偶数(包括0)→ 期望旧老师 + - 历史次数为奇数 → 期望新老师 + - 实现新/旧老师交替教学 + +3. **历史匹配规避**(硬约束) + - 如果学生与某老师有过匹配记录,跳过该老师 + - 确保每个学生接触不同的老师 + +4. **每日唯一性**(硬约束) + - 每位老师每天只能接待一位学生 + - 使用`teacher_assigned_days`字典追踪已分配日期 + - 老师某天被匹配后,当天其他时段不再接受预约 + +5. **时间兼容性**(硬约束) + - 学生和老师必须在同一天有相同的时间段 + - 时间段必须完全一致(如都是9:00-10:30) + +6. **均衡分配**(软约束/优化目标) + - 优先选择匹配次数较少的老师 + - 让更多老师参与到匹配中 + - 使用`teacher_match_count`字典实现 + +### 伪代码 + +``` +for each student in students: + possible_matches = [] + + # 计算期望老师类型(新增逻辑) + history_count = len(student.history) + if history_count == 0: + expected_type = "旧老师" # 首次上课 + elif history_count % 2 == 1: + expected_type = "新老师" # 第2,4,6...次 + else: + expected_type = "旧老师" # 第3,5,7...次 + + for each teacher in teachers: + if teacher in student.history: + continue # 跳过历史老师 + + if teacher.type != expected_type: + continue # 跳过类型不匹配的老师(新增) + + for each (day, student_time) in student.availability: + if day not in teacher.availability: + continue + + if day in teacher_assigned_days[teacher]: + continue # 老师当天已被预约(新增) + + teacher_time = teacher.availability[day] + + if student_time and teacher_time: + student_slots = parse_time_slots(student_time) + teacher_slots = parse_time_slots(teacher_time) + + for each (s_start, s_end) in student_slots: + for each (t_start, t_end) in teacher_slots: + if s_start == t_start and s_end == t_end: + possible_matches.append((teacher, day, s_start, s_end)) + + if possible_matches: + # 按老师匹配次数升序排序 + possible_matches.sort(key=lambda x: teacher_match_count[x[0]]) + + teacher, day, s_start, s_end = possible_matches[0] + + # 创建匹配记录 + create_match(student, teacher, day, s_start, s_end, "成功") + + # 更新状态 + student.history.append(teacher) + teacher_assigned_days[teacher].add(day) # 标记日期已分配(修改) + teacher_match_count[teacher] += 1 + else: + # 没有可用匹配 + create_match(student, null, "-", "-", "-", "未匹配") +``` + +## UI设计规范 + +### 配色方案 + +| 用途 | 颜色代码 | 说明 | +|------|----------|------| +| 标题文字 | #1a1a2e | 深色标题 | +| 主色调 | #2563eb | 主要操作按钮(蓝色) | +| 成功色 | #10b981 | 成功状态(绿色) | +| 警告色 | #f59e0b | 警告状态(橙色) | +| 错误色 | #ef4444 | 错误状态(红色) | +| 背景色 | #f8f9fa | 页面背景 | +| 卡片背景 | #ffffff | 卡片白色背景 | +| 边框色 | #f0f0f0 | 细微边框 | +| 新老师标识 | #2563eb | 蓝色 | +| 旧老师标识 | #10b981 | 绿色 | + +### 布局结构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 老师学员匹配系统 [按钮组] │ +│ Teacher-Student Matching System │ +├─────────────────────────────────────────────────────────────────┤ +│ 数据操作 │ 导入老师 │ 导入学生 │ 匹配操作 │ 开始匹配 │ 导出 │ 清空 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────┐ ┌─────────────────┐ ┌─────────────┐ │ +│ │ 老师信息 │ │ 学生信息 │ │ 匹配结果 │ │ +│ │ Teacher Info │ │ Student Info │ │ Matching R. │ │ +│ ├─────────────────────┤ ├─────────────────┤ ├─────────────┤ │ +│ │ 姓名 │ 类型 │ 备注 │ │ 姓名│历史│备注 │ │ 学生│老师│...│ │ +│ ├─────────────────────┤ ├─────────────────┤ ├─────────────┤ │ +│ │ │ │ │ │ │ │ │ │ │ │ │ +│ └─────────────────────┘ └─────────────────┘ └─────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 窗口配置 + +```python +self.page.window_width = 1400 # 窗口宽度 +self.page.window_height = 800 # 窗口高度 +self.page.theme_mode = ft.ThemeMode.LIGHT # 浅色主题 +``` + +## Flet API兼容性说明 + +### Flet 0.80.4 版本API + +本项目针对Flet 0.80.4版本编写,使用了以下兼容API: + +```python +# 颜色使用字符串而非ft.colors枚举 +ft.Text("text", color="#333333") # ✓ 正确 +ft.Text("text", color=ft.colors.WHITE) # ✗ 已废弃 + +# 对齐使用Alignment类 +ft.alignment.Alignment(0, 0) # ✓ 正确 +ft.alignment.top_left # ✗ 已废弃 + +# 边框使用Border类 +ft.Border.all(1, "#e0e0e0") # ✓ 正确 +ft.border.all(1, "#e0e0e0") # ✗ 已废弃 + +# 内边距使用Padding类 +ft.Padding.only(left=20, right=20) # ✓ 正确 +ft.padding.only(left=20, right=20) # ✗ 已废弃 + +# 外边距使用Margin类 +ft.Margin.only(left=20, right=20) # ✓ 正确 +ft.margin.only(left=20, right=20) # ✗ 已废弃 + +# 按钮使用Button而非ElevatedButton +ft.Button(...) # ✓ 正确 +ft.ElevatedButton(...) # ✗ 已废弃 + +# SnackBar使用page.snack_bar属性 +self.page.snack_bar = ft.SnackBar(...) +self.page.snack_bar.open = True # ✓ 正确 +self.page.show_snack_bar(...) # ✗ 已废弃 + +# 应用入口使用ft.run +ft.run(main) # ✓ 正确 +ft.app(target=main) # ✗ 已废弃 + +# 表格边框使用BorderSide +ft.border.BorderSide(1, "#e0e0e0") # ✓ 正确 +``` + +## 文件选择对话框 + +由于Flet的FilePicker在不同版本中存在兼容性问题,本项目使用tkinter的filedialog作为替代: + +```python +import threading +from tkinter import filedialog +import tkinter as tk + +def ask_file(): + root = tk.Tk() + root.withdraw() + root.attributes('-topmost', True) # 置顶显示 + file_path = filedialog.askopenfilename( + title="选择文件", + filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")] + ) + result['path'] = file_path + root.destroy() + +thread = threading.Thread(target=ask_file) +thread.start() +thread.join() +file_path = result.get('path', '') +``` + +## 安装和运行 + +### 开发环境运行 +```bash +pip install flet pandas openpyxl +python main.py +``` + +### 打包为exe +```batch +@echo off +pip install pyinstaller pandas openpyxl flet +pyinstaller --onefile --windowed main.py +``` + +### 打包配置优化 + +排除不必要的模块以减小打包体积: +```batch +pyinstaller --onefile --windowed ^ + --exclude-module=torch ^ + --exclude-module=torchvision ^ + --exclude-module=transformers ^ + --exclude-module=scipy ^ + --exclude-module=matplotlib ^ + --exclude-module=pandas.plotting ^ + main.py +``` + +## 测试数据生成 + +```python +import pandas as pd +import random + +random.seed(42) + +days = ['1月1日', '1月2日', '1月3日', '1月4日', '1月5日', '1月6日', '1月7日'] +time_slots = ['9:00-10:30', '10:30-12:00', '14:00-15:30', '15:30-17:00', '18:00-19:30'] + +# 生成10个老师(5旧5新) +teacher_names = [f"张{i}老师" for i in range(1, 11)] +teacher_types = ["旧老师", "旧老师", "旧老师", "新老师", "新老师", "旧老师", "新老师", "旧老师", "新老师", "旧老师"] +teachers = [] +for i, name in enumerate(teacher_names): + teacher = {"老师姓名": name} + teacher["老师类型"] = teacher_types[i] + available_days = random.sample(days, 2) # 每人随机2天空闲 + for day in days: + if day in available_days: + num_slots = random.randint(2, 3) # 每天2-3个时间段 + teacher[day] = '; '.join(random.sample(time_slots, num_slots)) + else: + teacher[day] = '' + teacher["备注"] = "" + teachers.append(teacher) + +df_teachers = pd.DataFrame(teachers) +df_teachers.to_excel('老师信息示例.xlsx', index=False) + +# 生成20个学生 +student_names = [f"学生{i}" for i in range(1, 21)] +students = [] +for i, name in enumerate(student_names): + student = {"学生姓名": name} + available_days = random.sample(days, random.randint(3, 5)) # 3-5天空闲 + for day in days: + if day in available_days: + num_slots = random.randint(2, 3) + student[day] = '; '.join(random.sample(time_slots, num_slots)) + else: + student[day] = '' + + # 每个学生有0-3个历史老师 + history_count = min(3, i) + history_teachers = random.sample([t for t in teacher_names if t != name], history_count) + student["历史匹配老师"] = ', '.join(history_teachers) + student["备注"] = "" + students.append(student) + +df_students = pd.DataFrame(students) +df_students.to_excel('学生信息示例.xlsx', index=False) +``` + +## 常见问题排查 + +### 1. 打包后文件选择对话框不显示 +- 确保使用tkinter的filedialog而非Flet的FilePicker +- 在独立线程中运行对话框 + +### 2. SnackBar不显示 +- 使用`page.snack_bar`属性而非`show_snack_bar()`方法 +- 设置`open = True`来显示 + +### 3. 表格不显示数据 +- 确保调用`page.update()`刷新界面 +- 检查数据是否正确解析 + +### 4. 匹配结果不正确 +- 检查时间段格式是否正确(使用分号分隔) +- 检查历史老师名称是否与老师姓名完全匹配 +- 检查老师类型是否正确标记 + +## 项目交付清单 + +- [x] main.py - 主程序 +- [x] build.bat - 打包脚本 +- [x] 需求文档.md - 需求说明文档 +- [x] 开发文档.md - 技术实现文档 +- [x] 老师信息示例.xlsx - 老师测试数据 +- [x] 学生信息示例.xlsx - 学生测试数据 +- [x] generate_test_data.py - 测试数据生成脚本 +- [x] dist/main.exe - 打包后的可执行文件 + +## 版本历史 + +| 版本 | 日期 | 更新内容 | +|------|------|----------| +| 1.0 | 2026-04-19 | 初始版本,实现核心匹配功能 | +| 1.1 | 2026-04-25 | 新增每日唯一性约束(老师每天只能接待一位学生) | +| 1.2 | 2026-04-28 | 新增老师类型标记、首次上课限制、交替匹配规则 | \ No newline at end of file diff --git a/老师信息示例.xlsx b/老师信息示例.xlsx new file mode 100644 index 0000000..021a630 Binary files /dev/null and b/老师信息示例.xlsx differ diff --git a/需求文档.md b/需求文档.md new file mode 100644 index 0000000..4174929 --- /dev/null +++ b/需求文档.md @@ -0,0 +1,155 @@ +# 老师学员匹配系统需求文档 + +## 1. 项目概述 +本系统旨在实现老师和学员的自动匹配功能,根据双方的可用时间进行智能匹配,并生成匹配结果。 + +## 2. 功能需求 + +### 2.1 基础功能 +- **老师管理**:支持通过Excel导入老师信息,包含老师类型标记 +- **时间数据导入**:通过Excel文件导入老师和学生的可用时间 +- **智能匹配**:根据时间兼容性自动匹配老师和学生 +- **匹配结果导出**:生成Excel格式的匹配结果 +- **历史匹配记录**:记录学生与老师的历史匹配情况,避免重复匹配 + +### 2.2 核心匹配规则(按优先级) +1. **首次上课限制**:学生第一次上课只能匹配旧老师 +2. **交替匹配规则**:后续上课按"新老师→旧老师→新老师→..."交替进行 +3. **历史匹配规避**:学生不能匹配之前上过课的老师 +4. **每日唯一性**:每位老师每天只能接待一位学生 +5. **均衡分配**:优先匹配次数少的老师,让更多老师参与 + +### 2.3 核心流程 +1. 导入老师信息(包含老师类型)和可用时间 +2. 导入学生信息(包含历史匹配)和可用时间 +3. 系统自动匹配老师和学生 +4. 生成匹配结果并导出 +5. 记录匹配历史 + +## 3. 数据结构 + +### 3.1 老师数据结构 +| 字段名 | 数据类型 | 说明 | +|-------|---------|------| +| 老师ID | 字符串 | 唯一标识 | +| 姓名 | 字符串 | 老师姓名 | +| 老师类型 | 字符串 | 新老师/旧老师 | +| 可用时间 | 时间列表 | 每个日期的可用时间段 | +| 其他信息 | 字符串 | 可选的额外信息 | + +### 3.2 学生数据结构 +| 字段名 | 数据类型 | 说明 | +|-------|---------|------| +| 学生ID | 字符串 | 唯一标识 | +| 姓名 | 字符串 | 学生姓名 | +| 可用时间 | 时间列表 | 每个日期的可用时间段 | +| 历史匹配老师 | 老师ID列表 | 曾经匹配过的老师 | +| 其他信息 | 字符串 | 可选的额外信息 | + +### 3.3 匹配结果数据结构 +| 字段名 | 数据类型 | 说明 | +|-------|---------|------| +| 匹配ID | 字符串 | 唯一标识 | +| 学生ID | 字符串 | 学生标识 | +| 老师ID | 字符串 | 老师标识 | +| 匹配日期 | 日期 | 匹配的具体日期 | +| 匹配时段 | 时间段 | 具体上课时间段 | +| 状态 | 字符串 | 匹配状态(成功/未匹配) | + +## 4. Excel格式设计 + +### 4.1 老师信息Excel格式 +| 列名 | 说明 | +|-----|------| +| 老师姓名 | 唯一标识 | +| 老师类型 | 新老师/旧老师 | +| 备注 | 可选的额外信息 | +| 1月1日 | 可用时间段(如:9:00-11:00;14:00-16:00) | +| 1月2日 | 可用时间段 | +| ... | ... | + +### 4.2 学生信息Excel格式 +| 列名 | 说明 | +|-----|------| +| 学生姓名 | 唯一标识 | +| 历史匹配老师 | 曾经匹配过的老师姓名,用逗号分隔 | +| 备注 | 可选的额外信息 | +| 1月1日 | 可用时间段 | +| 1月2日 | 可用时间段 | +| ... | ... | + +### 4.3 匹配结果Excel格式 +| 列名 | 说明 | +|-----|------| +| 学生 | 学生姓名 | +| 老师 | 匹配的老师姓名 | +| 日期 | 匹配日期 | +| 时段 | 匹配的时间段 | +| 状态 | 匹配状态 | + +## 5. 技术要求 + +### 5.1 开发环境 +- 操作系统:Windows +- 开发语言:Python(打包为EXE) +- 依赖库: + - pandas(处理Excel文件) + - openpyxl(Excel文件读写) + - Flet(GUI界面) + - pyinstaller(打包为EXE) + +### 5.2 性能要求 +- 支持至少100名学生和20名老师的匹配 +- 匹配过程响应时间不超过10秒 + +## 6. 界面设计 + +### 6.1 主界面 +- 老师管理区域:显示老师列表(包含类型标识) +- 数据导入区域:提供Excel文件选择和导入按钮 +- 匹配控制区域:开始匹配按钮 +- 结果显示区域:显示匹配结果 +- 导出区域:导出匹配结果按钮 + +### 6.2 操作流程 +1. 用户打开软件 +2. 导入老师Excel文件(包含类型信息) +3. 导入学生Excel文件(包含历史匹配) +4. 点击开始匹配按钮 +5. 查看匹配结果 +6. 导出匹配结果到Excel文件 + +## 7. 其他需要讨论的点 + +1. **匹配算法**:具体的匹配规则和优先级如何确定? +2. **时间冲突处理**:如果多个学生同时匹配到同一个老师的同一天,如何处理? +3. **数据验证**:如何验证导入的Excel数据格式是否正确? +4. **错误处理**:当匹配失败或数据异常时,如何提示用户? +5. **历史数据存储**:历史匹配记录如何存储和管理? +6. **用户权限**:是否需要不同级别的用户权限? +7. **界面语言**:是否需要支持多语言? +8. **系统兼容性**:支持的Windows版本范围? +9. **安装部署**:是否需要安装程序或绿色版? +10. **更新机制**:如何进行系统更新? + +## 8. 项目计划 + +1. **需求分析与设计**:1天 +2. **数据结构实现**:1天 +3. **Excel文件处理**:1天 +4. **匹配算法实现**:2天 +5. **GUI界面开发**:2天 +6. **测试与调试**:2天 +7. **打包与部署**:1天 + +## 9. 验收标准 + +1. 系统能够正确导入老师和学生的Excel文件 +2. 系统能够根据时间兼容性自动匹配老师和学生 +3. 系统能够避免重复匹配历史上已经匹配过的老师和学生 +4. 系统能够确保每位老师每天只接待一位学生 +5. 系统能够按规则分配新/旧老师(首次→旧老师,后续交替) +6. 系统能够优先分配匹配次数少的老师 +7. 系统能够生成正确的匹配结果Excel文件 +8. 系统界面友好,操作简单 +9. 系统运行稳定,无明显错误 \ No newline at end of file