Skip to content

Commit 2b3a921

Browse files
committed
增加公平抽取的丰富功能
新增 科目历史记录过滤,支持仅使用当前科目历史记录计算权重,精准锁定学科数据,大幅提升抽取公平性! 新增 课间记录归属,支持将课间时段记录归属到上节课或下节课,灵活适配不同教学节奏! 新增 历史记录课程筛选,支持按课程快速筛选历史记录,一键定位目标数据,效率翻倍!
1 parent 09eaebd commit 2b3a921

22 files changed

Lines changed: 1969 additions & 1696 deletions

CHANGELOG/v2.2.0/CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ v2.0 - Koharu(小鸟游星野) release 3
88
- 新增 **通知渠道**,新增「SecRandom+ClassIsland」选项
99
- 新增 **字体粗细设置**,支持9种字体粗细(极细/特细/细体/常规/中等/半粗/粗体/特粗/极粗)
1010
- 新增 **数量标签显示模式**,支持"总+剩余"、"总数"、"剩余数"、"不显示"四种模式
11-
- 新增 **科目历史记录过滤**支持只使用当前科目历史记录计算权重
12-
- 新增 **课间记录归属**,支持将课间时段记录归属到上节课或下节课
13-
- 新增 **历史记录课程筛选**支持按课程查看历史记录
11+
- 新增 **科目历史记录过滤**支持仅使用当前科目历史记录计算权重,精准锁定学科数据,大幅提升抽取公平性!
12+
- 新增 **课间记录归属**,支持将课间时段记录归属到上节课或下节课,灵活适配不同教学节奏!
13+
- 新增 **历史记录课程筛选**支持按课程快速筛选历史记录,一键定位目标数据,效率翻倍!
1414

1515
## 💡 功能优化
1616

app/Language/modules/history.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@
296296
"description": "选择历史记录的查看方式",
297297
"combo_items": ["全部记录", "按时间查看"],
298298
},
299+
"select_subject": {
300+
"name": "选择课程",
301+
"description": "选择要查看的课程",
302+
"combo_items": ["全部课程"],
303+
},
299304
"HeaderLabels_all_weight": {
300305
"name": ["序号", "名称", "中奖次数", "权重"],
301306
"description": "抽奖历史记录表格列标题(全部记录)",
@@ -323,6 +328,11 @@
323328
"description": "Choose how history is viewed",
324329
"combo_items": {"0": "All history", "1": "View by time"},
325330
},
331+
"select_subject": {
332+
"name": "Select subject",
333+
"description": "Choose a subject to view",
334+
"combo_items": ["All subjects"],
335+
},
326336
"HeaderLabels_all_weight": {
327337
"name": {
328338
"0": "Serial",

app/common/fair_draw/avg_gap_protection.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from typing import List, Dict, Any
66
from loguru import logger
7-
from app.common.history.history import load_history_data
7+
from app.common.history import *
88
from app.tools.settings_access import readme_settings_async
99

1010

@@ -81,16 +81,16 @@ def apply_avg_gap_protection(
8181
draw_count: int,
8282
class_name: str,
8383
history_type: str = "roll_call",
84+
subject_filter: str = "",
8485
) -> List[Dict[str, Any]]:
8586
"""
8687
应用平均值过滤 + 最大差距保护的公平抽取逻辑
87-
8888
Args:
8989
candidates: 候选列表,每个元素包含学生信息
9090
draw_count: 本次要抽取的人数
9191
class_name: 班级名称
9292
history_type: 历史记录类型,默认为"roll_call"
93-
93+
subject_filter: 科目过滤,如果指定则只计算该科目的历史记录
9494
Returns:
9595
处理后的候选池
9696
"""
@@ -115,15 +115,32 @@ def apply_avg_gap_protection(
115115
# Step 1: 获取当前抽取单位的次数
116116
# 加载历史记录
117117
history_data = load_history_data(history_type, class_name)
118-
119118
# 初始化学生抽取次数字典
120119
student_counts = {}
121120
for student in candidates:
122121
student_name = _get_student_name(student)
123122
if student_name:
124123
# 从历史记录中获取该学生的抽取次数
125124
student_history = history_data.get("students", {}).get(student_name, {})
126-
student_counts[student_name] = student_history.get("total_count", 0)
125+
126+
# 如果有科目过滤,使用科目统计
127+
if subject_filter and history_type == "roll_call":
128+
subject_stats = student_history.get("subject_stats", {})
129+
if subject_filter in subject_stats:
130+
student_counts[student_name] = subject_stats[
131+
subject_filter
132+
].get("total_count", 0)
133+
else:
134+
# 如果科目统计中没有该科目,从历史记录中计算
135+
history = student_history.get("history", [])
136+
filtered_count = 0
137+
for record in history:
138+
if record.get("class_name", "") == subject_filter:
139+
filtered_count += 1
140+
student_counts[student_name] = filtered_count
141+
else:
142+
# 没有科目过滤,使用总次数
143+
student_counts[student_name] = student_history.get("total_count", 0)
127144

128145
# 获取所有学生的抽取次数列表
129146
counts = list(student_counts.values())

app/common/history/__init__.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# ==================================================
2+
# 历史记录模块
3+
# ==================================================
4+
# 该模块提供历史记录的保存、加载、统计和权重计算功能
5+
6+
# 文件工具
7+
from app.common.history.file_utils import (
8+
get_history_file_path,
9+
load_history_data,
10+
save_history_data,
11+
get_all_history_names,
12+
)
13+
14+
# 统计函数
15+
from app.common.history.statistics import (
16+
get_name_history,
17+
get_draw_sessions_history,
18+
get_individual_statistics,
19+
)
20+
21+
# 抽奖历史
22+
from app.common.history.lottery_history import save_lottery_history
23+
24+
# 点名历史
25+
from app.common.history.roll_call_history import save_roll_call_history
26+
27+
# 权重工具
28+
from app.common.history.weight_utils import (
29+
format_weight_for_display,
30+
calculate_weight,
31+
)
32+
33+
# 辅助函数
34+
from app.common.history.utils import (
35+
get_all_names,
36+
format_table_item,
37+
create_table_item,
38+
)
39+
40+
# 历史记录读取工具
41+
from app.common.history.history_reader import (
42+
# 点名历史读取
43+
get_roll_call_student_list,
44+
get_roll_call_history_data,
45+
filter_roll_call_history_by_subject,
46+
get_roll_call_student_total_count,
47+
get_roll_call_students_data,
48+
get_roll_call_session_data,
49+
get_roll_call_student_stats_data,
50+
check_class_has_gender_or_group,
51+
# 抽奖历史读取
52+
get_lottery_pool_list,
53+
get_lottery_history_data,
54+
get_lottery_prizes_data,
55+
get_lottery_session_data,
56+
get_lottery_prize_stats_data,
57+
)
58+
59+
__all__ = [
60+
# 文件工具
61+
"get_history_file_path",
62+
"load_history_data",
63+
"save_history_data",
64+
"get_all_history_names",
65+
# 统计函数
66+
"get_name_history",
67+
"get_draw_sessions_history",
68+
"get_individual_statistics",
69+
# 抽奖历史
70+
"save_lottery_history",
71+
# 点名历史
72+
"save_roll_call_history",
73+
# 权重工具
74+
"format_weight_for_display",
75+
"calculate_weight",
76+
# 辅助函数
77+
"get_all_names",
78+
"format_table_item",
79+
"create_table_item",
80+
# 历史记录读取工具
81+
"get_roll_call_student_list",
82+
"get_roll_call_history_data",
83+
"filter_roll_call_history_by_subject",
84+
"get_roll_call_student_total_count",
85+
"get_roll_call_students_data",
86+
"get_roll_call_session_data",
87+
"get_roll_call_student_stats_data",
88+
"check_class_has_gender_or_group",
89+
"get_lottery_pool_list",
90+
"get_lottery_history_data",
91+
"get_lottery_prizes_data",
92+
"get_lottery_session_data",
93+
"get_lottery_prize_stats_data",
94+
]

app/common/history/file_utils.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# ==================================================
2+
# 导入库
3+
# ==================================================
4+
import json
5+
from typing import Dict, List, Any
6+
from pathlib import Path
7+
8+
from loguru import logger
9+
10+
from app.tools.path_utils import get_path
11+
12+
13+
# ==================================================
14+
# 历史记录文件路径处理函数
15+
# ==================================================
16+
def get_history_file_path(history_type: str, file_name: str) -> Path:
17+
"""获取历史记录文件路径
18+
19+
Args:
20+
history_type: 历史记录类型 (roll_call, lottery 等)
21+
file_name: 文件名(不含扩展名)
22+
23+
Returns:
24+
Path: 历史记录文件路径
25+
"""
26+
history_dir = get_path(f"data/history/{history_type}_history")
27+
history_dir.mkdir(parents=True, exist_ok=True)
28+
return history_dir / f"{file_name}.json"
29+
30+
31+
# ==================================================
32+
# 历史记录数据读写函数
33+
# ==================================================
34+
35+
36+
def load_history_data(history_type: str, file_name: str) -> Dict[str, Any]:
37+
"""加载历史记录数据
38+
39+
Args:
40+
history_type: 历史记录类型 (roll_call, lottery 等)
41+
file_name: 文件名(不含扩展名)
42+
43+
Returns:
44+
Dict[str, Any]: 历史记录数据
45+
"""
46+
file_path = get_history_file_path(history_type, file_name)
47+
48+
if not file_path.exists():
49+
return {}
50+
51+
try:
52+
with open(file_path, "r", encoding="utf-8") as f:
53+
return json.load(f)
54+
except Exception as e:
55+
logger.error(f"加载历史记录数据失败: {e}")
56+
return {}
57+
58+
59+
def save_history_data(history_type: str, file_name: str, data: Dict[str, Any]) -> bool:
60+
"""保存历史记录数据
61+
62+
Args:
63+
history_type: 历史记录类型 (roll_call, lottery 等)
64+
file_name: 文件名(不含扩展名)
65+
data: 要保存的数据
66+
67+
Returns:
68+
bool: 保存是否成功
69+
"""
70+
file_path = get_history_file_path(history_type, file_name)
71+
try:
72+
with open(file_path, "w", encoding="utf-8") as f:
73+
json.dump(data, f, ensure_ascii=False, indent=4)
74+
return True
75+
except Exception as e:
76+
logger.error(f"保存历史记录数据失败: {e}")
77+
return False
78+
79+
80+
def get_all_history_names(history_type: str) -> List[str]:
81+
"""获取所有历史记录名称列表
82+
83+
Args:
84+
history_type: 历史记录类型 (roll_call, lottery 等)
85+
86+
Returns:
87+
List[str]: 历史记录名称列表
88+
"""
89+
try:
90+
history_dir = get_path(f"data/history/{history_type}_history")
91+
if not history_dir.exists():
92+
return []
93+
history_files = list(history_dir.glob("*.json"))
94+
names = [file.stem for file in history_files]
95+
names.sort()
96+
return names
97+
except Exception as e:
98+
logger.error(f"获取历史记录名称列表失败: {e}")
99+
return []

0 commit comments

Comments
 (0)