Files
012-kaopeilian/backend/app/utils/score_distributor.py
yuliang_guo 0b7c07eb7f
All checks were successful
continuous-integration/drone/push Build is passing
feat: 添加请求验证错误详细日志
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-01-31 10:03:54 +08:00

219 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
分数分配工具
解决题目分数无法整除的问题,确保:
1. 所有题目分数之和精确等于总分
2. 题目分数差异最小化最多相差1分
3. 支持整数分配和小数分配两种模式
"""
from typing import List, Tuple
from decimal import Decimal, ROUND_HALF_UP
import math
class ScoreDistributor:
"""
智能分数分配器
使用示例:
distributor = ScoreDistributor(total_score=100, question_count=6)
scores = distributor.distribute()
# 结果: [17, 17, 17, 17, 16, 16] 总和=100
"""
def __init__(self, total_score: float, question_count: int):
"""
初始化分配器
Args:
total_score: 总分(如 100
question_count: 题目数量(如 6
"""
if question_count <= 0:
raise ValueError("题目数量必须大于0")
if total_score <= 0:
raise ValueError("总分必须大于0")
self.total_score = total_score
self.question_count = question_count
def distribute_integer(self) -> List[int]:
"""
整数分配模式
将总分分配为整数前面的题目分数可能比后面的多1分
Returns:
分数列表,如 [17, 17, 17, 17, 16, 16]
示例:
100分 / 6题 = [17, 17, 17, 17, 16, 16]
100分 / 7题 = [15, 15, 14, 14, 14, 14, 14]
"""
total = int(self.total_score)
count = self.question_count
# 基础分数(向下取整)
base_score = total // count
# 需要额外加1分的题目数量
extra_count = total % count
# 生成分数列表
scores = []
for i in range(count):
if i < extra_count:
scores.append(base_score + 1)
else:
scores.append(base_score)
return scores
def distribute_decimal(self, decimal_places: int = 1) -> List[float]:
"""
小数分配模式
将总分分配为小数,最后一题用于补齐差额
Args:
decimal_places: 小数位数默认1位
Returns:
分数列表,如 [16.7, 16.7, 16.7, 16.7, 16.7, 16.5]
"""
count = self.question_count
# 计算每题分数并四舍五入
per_score = self.total_score / count
rounded_score = round(per_score, decimal_places)
# 前 n-1 题使用四舍五入的分数
scores = [rounded_score] * (count - 1)
# 最后一题用总分减去前面的和,确保总分精确
last_score = round(self.total_score - sum(scores), decimal_places)
scores.append(last_score)
return scores
def distribute(self, mode: str = "integer") -> List[float]:
"""
分配分数
Args:
mode: 分配模式
- "integer": 整数分配(推荐)
- "decimal": 小数分配
- "decimal_1": 保留1位小数
- "decimal_2": 保留2位小数
Returns:
分数列表
"""
if mode == "integer":
return [float(s) for s in self.distribute_integer()]
elif mode == "decimal" or mode == "decimal_1":
return self.distribute_decimal(1)
elif mode == "decimal_2":
return self.distribute_decimal(2)
else:
return [float(s) for s in self.distribute_integer()]
def get_score_for_question(self, question_index: int, mode: str = "integer") -> float:
"""
获取指定题目的分数
Args:
question_index: 题目索引从0开始
mode: 分配模式
Returns:
该题目的分数
"""
scores = self.distribute(mode)
if 0 <= question_index < len(scores):
return scores[question_index]
raise IndexError(f"题目索引 {question_index} 超出范围 [0, {self.question_count})")
def validate(self) -> Tuple[bool, str]:
"""
验证分配结果
Returns:
(是否有效, 信息)
"""
scores = self.distribute()
total = sum(scores)
if abs(total - self.total_score) < 0.01:
return True, f"分配有效:{scores},总分={total}"
else:
return False, f"分配无效:{scores},总分={total},期望={self.total_score}"
@staticmethod
def format_score(score: float, decimal_places: int = 1) -> str:
"""
格式化分数显示
Args:
score: 分数
decimal_places: 小数位数
Returns:
格式化的分数字符串
"""
if score == int(score):
return str(int(score))
return f"{score:.{decimal_places}f}"
@staticmethod
def calculate_pass_score(total_score: float, pass_rate: float = 0.6) -> float:
"""
计算及格分数
Args:
total_score: 总分
pass_rate: 及格率默认60%
Returns:
及格分数(整数)
"""
return math.ceil(total_score * pass_rate)
def distribute_scores(total_score: float, question_count: int, mode: str = "integer") -> List[float]:
"""
便捷函数:分配分数
Args:
total_score: 总分
question_count: 题目数量
mode: 分配模式integer/decimal
Returns:
分数列表
"""
distributor = ScoreDistributor(total_score, question_count)
return distributor.distribute(mode)
def get_question_score(
total_score: float,
question_count: int,
question_index: int,
mode: str = "integer"
) -> float:
"""
便捷函数:获取指定题目的分数
Args:
total_score: 总分
question_count: 题目数量
question_index: 题目索引从0开始
mode: 分配模式
Returns:
该题目的分数
"""
distributor = ScoreDistributor(total_score, question_count)
return distributor.get_score_for_question(question_index, mode)