diff --git a/src/main/java/cn/teammodel/common/PK.java b/src/main/java/cn/teammodel/common/PK.java index 0fdfcd5..246227a 100644 --- a/src/main/java/cn/teammodel/common/PK.java +++ b/src/main/java/cn/teammodel/common/PK.java @@ -29,6 +29,8 @@ public interface PK { String WEEK_DUTY_RECORD = "DutyRecord-%s"; String CHAT_APP = "ChatApp"; String NEWS = "News-%s"; + String CLASS_RESULT = "ExamClassResult-%s"; + String EXAM = "Exam-%s"; /** * 构建分区键 diff --git a/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java b/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java new file mode 100644 index 0000000..ee5a39e --- /dev/null +++ b/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java @@ -0,0 +1,32 @@ +package cn.teammodel.controller.admin.controller; + +import cn.teammodel.common.R; +import cn.teammodel.controller.admin.service.ExamService; +import cn.teammodel.controller.admin.service.LaborEducationService; +import cn.teammodel.model.dto.admin.exam.OverViewDto; +import cn.teammodel.model.dto.admin.labor.LaborDto; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.util.Map; + +@RestController +@RequestMapping("admin/labor") +@Api(tags = "管理员端-德育分析") +public class LaborEducationController { + @Resource + private LaborEducationService laborEducationService; + @PostMapping("getLaborAnalysis") + @ApiOperation("获取德育看板详细内容") + public R> getLaborAnalysis(@Valid @RequestBody LaborDto laborDto, HttpServletRequest request) { + Map res = laborEducationService.getAnalysis(laborDto,request); + return R.success(res); + } +} diff --git a/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java b/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java new file mode 100644 index 0000000..ea2f4e1 --- /dev/null +++ b/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java @@ -0,0 +1,12 @@ +package cn.teammodel.controller.admin.service; + +import cn.teammodel.model.dto.admin.exam.OverViewDto; +import cn.teammodel.model.dto.admin.labor.LaborDto; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +public interface LaborEducationService { + Map getAnalysis(LaborDto laborDto, HttpServletRequest request); +} diff --git a/src/main/java/cn/teammodel/controller/admin/service/impl/AdminAppraiseServiceImpl.java b/src/main/java/cn/teammodel/controller/admin/service/impl/AdminAppraiseServiceImpl.java index a89aed1..85d8ca4 100644 --- a/src/main/java/cn/teammodel/controller/admin/service/impl/AdminAppraiseServiceImpl.java +++ b/src/main/java/cn/teammodel/controller/admin/service/impl/AdminAppraiseServiceImpl.java @@ -155,12 +155,17 @@ public class AdminAppraiseServiceImpl implements AdminAppraiseService { startTime = mondayOfCurWeek.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli(); endTime = Instant.now().toEpochMilli(); } - + List classes = classRepository.findClassBySchoolIdAndPeriodId(timeRangeDto.getPeriodId(), String.format(PK.CLASS, schoolId)); + if (classes.isEmpty()) { + return new ArrayList<>(); + } + List classIds = classes.stream().map(ClassInfo::getId).collect(Collectors.toList()); List res = appraiseRecordRepository.latestRecords( String.format(PK.PK_APPRAISE_RECORD, schoolId), academicYearId, startTime, - endTime + endTime, + classIds ); if (res != null) { diff --git a/src/main/java/cn/teammodel/controller/admin/service/impl/LaborEducationServiceImpl.java b/src/main/java/cn/teammodel/controller/admin/service/impl/LaborEducationServiceImpl.java new file mode 100644 index 0000000..ecba4d2 --- /dev/null +++ b/src/main/java/cn/teammodel/controller/admin/service/impl/LaborEducationServiceImpl.java @@ -0,0 +1,970 @@ +package cn.teammodel.controller.admin.service.impl; + +import cn.teammodel.common.ErrorCode; +import cn.teammodel.common.PK; +import cn.teammodel.config.exception.ServiceException; +import cn.teammodel.controller.admin.service.LaborEducationService; +import cn.teammodel.model.dto.admin.common.GroupDto; +import cn.teammodel.model.dto.admin.common.RMember; +import cn.teammodel.model.dto.admin.labor.LaborDto; +import cn.teammodel.model.entity.appraise.Appraise; +import cn.teammodel.model.entity.appraise.AppraiseTreeNode; +import cn.teammodel.model.entity.common.Exam; +import cn.teammodel.model.entity.common.ExamClassResult; +import cn.teammodel.model.entity.school.ClassInfo; +import cn.teammodel.model.entity.school.LessonRecord; +import cn.teammodel.model.vo.appraise.RecordVo; +import cn.teammodel.repository.*; +import cn.teammodel.security.utils.SecurityUtil; +import cn.teammodel.service.impl.EvaluationServiceImpl; +import cn.teammodel.test.LessonRecordQueryService; +import cn.teammodel.utils.GroupUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +@Service +public class LaborEducationServiceImpl implements LaborEducationService { + + @Resource + private LessonRecordRepository lessonRecordRepository; + @Resource + private ClassRepository classRepository; + @Resource + private AppraiseRecordRepository appraiseRecordRepository; + @Resource + private ExamRepository examRepository; + @Resource + private ExamClassResultRepository examClassResultRepository; + @Resource + private AppraiseRepository appraiseRepository; + @Resource + private EvaluationServiceImpl evaluationService; + private static Environment environment; // 静态字段 + @Autowired + private Environment env; // 非静态字段 + + @PostConstruct + public void init() { + LaborEducationServiceImpl.environment = env; // 在初始化时将非静态字段赋值给静态字段 + } + @Override + public Map getAnalysis(LaborDto laborDto, HttpServletRequest request) { + + //根据具体参数查询相关课列内容 + List records; + LessonRecordQueryService queryService = new LessonRecordQueryService(lessonRecordRepository); + String schoolId = SecurityUtil.getLoginUser().getSchoolId(); + String lessonRecordKey = String.format(PK.PK_LESSON_RECORD, laborDto.getCode()); + Long startTime = laborDto.getStartTime(); + Long endTime = laborDto.getEndTime(); + String subjectId = laborDto.getSubjectId(); + String tmdId = laborDto.getTmdId(); + String grade = laborDto.getGrade(); + String periodId = laborDto.getPeriodId(); + String academicYearId = laborDto.getAcademicYearId(); + try { + + records = queryService.queryLessonsInParallel( + lessonRecordKey, startTime, endTime, subjectId, tmdId, grade, periodId + ); + // 获取当前月份的起始时间和结束时间 + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.DAY_OF_MONTH, 1); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long startOfMonth = calendar.getTimeInMillis(); + + calendar.add(Calendar.MONTH, 1); + calendar.add(Calendar.MILLISECOND, -1); + long endOfMonth = calendar.getTimeInMillis(); + + // 筛选出本月的数据量 + int currentRecordsCount = records.stream() + .filter(record -> record.getStartTime() >= startOfMonth && record.getStartTime() <= endOfMonth) + .mapToInt(record -> 1) + .sum(); + + } catch (InterruptedException | ExecutionException e) { + throw new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "数据查询异常"); + } finally { + queryService.shutdown(); + } + //总评价数 + List classes = classRepository.findClassBySchoolIdAndPeriodId(laborDto.getPeriodId(), String.format(PK.CLASS, schoolId)); + if (classes.isEmpty()) { + throw new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "暂无班级"); + } + List classIds = classes.stream().map(ClassInfo::getId).collect(Collectors.toList()); + List res = appraiseRecordRepository.latestRecords( + String.format(PK.PK_APPRAISE_RECORD, schoolId), + academicYearId, + startTime, + endTime, + classIds + ); + //表扬的次数 + int rightCount = (int) res.stream().filter(RecordVo::isPraise).count(); + //批评的次数 + int wrongCount = (int) res.stream().filter(record -> !record.isPraise()).count(); + //已经评价的学生人数 + long uniqueTargetIdCount = res.stream() + .map(RecordVo::getTargetId) + .filter(Objects::nonNull) // 过滤掉 null 值 + .distinct() + .count(); + + List recordIds = new ArrayList<>(); + for (LessonRecord record : records) { + recordIds.add(record.getId()); + } + //examIds.add("c26e5766-597f-45b3-91ad-19e76426323b"); + //获取所有课程下的课中活动 + List exams = examRepository.findExamsByIds(String.format(PK.EXAM, laborDto.getTmdId()), recordIds); + Map>> knowledgeMap = new HashMap<>(); + Map> point = new HashMap<>(); + for (Exam exam : exams) { + if (exam.getPapers() != null) { + if (exam.getPapers().get(0).getKnowledge() != null) + { + knowledgeMap.put(exam.getId(), exam.getPapers().get(0).getKnowledge()); + point.put(exam.getId(), exam.getPapers().get(0).getPoint()); + } + } + } + List examIds = new ArrayList<>(); + for (Exam exam : exams) { + examIds.add(exam.getId()); + } + List examResults = examClassResultRepository.findAll(String.format(PK.CLASS_RESULT, laborDto.getCode()),examIds); + + Map resMap = new HashMap<>(); + //处理德育知识块和知识点的关联关系 + resMap.put("subjectiveCount", res.size()); //主观评价数 + resMap.put("examCount", exams.size());//评测数量 + resMap.put("lessonCount", records.size());//班会课数量 + resMap.put("rightCount", rightCount);//表扬评价数 + resMap.put("wrongCount", wrongCount);//待改进评价数 + resMap.put("uniqueTargetIdCount", uniqueTargetIdCount);//评价学生数 + Appraise appraise = appraiseRepository.findAppraiseBySchoolIdAndPeriodIdAndCode(schoolId, periodId, PK.PK_APPRAISE); + if (appraise != null) { + appraise = evaluationService.buildTree(appraise); + Map> knowledgeBlockToPointsMap = getKnowledgeBlockToPointsMap(appraise); + Map classScoreRate = new HashMap<>(); + Map gradeScoreRate = new HashMap<>(); + if (laborDto.getClassId() != null) { + classScoreRate = calculateKnowledgeScoreRateForClass(laborDto.getClassId(), examResults, knowledgeMap, appraise, point,res,request); + } + if (laborDto.getGrade() != null) { + gradeScoreRate = calculateKnowledgeScoreForGrade(laborDto.getGrade(), examResults, knowledgeMap,appraise, point,res); + } + Map schoolScoreRate = calculateKnowledgeScoreForSchool(examResults, knowledgeMap,appraise, point,res); + resMap.put("gradeScoreRate", gradeScoreRate); + resMap.put("classScoreRate", classScoreRate); + resMap.put("schoolScoreRate", schoolScoreRate); + List> scores = calculateScoresWithDetails(res, appraise); + List> students = combineScoresWithExamResults(scores,examResults,knowledgeMap,point,knowledgeBlockToPointsMap); + resMap.put("scores", students); + } + //处理主观评价内容 + return resMap; + + } + + public List> combineScoresWithExamResults( + List> scores, + List examResults, + Map>> knowledgeMap, + Map> points, + Map> knowledgeBlockToPointsMap) { + + // 1. 计算每个学生的知识点得分 + Map> studentKnowledgePointScores = new HashMap<>(); + for (Map studentResult : scores) { + String studentId = (String) studentResult.get("studentId"); + + // 初始化知识点总分 + Map knowledgeTotalScore = new HashMap<>(); + + // 计算当前学生的知识点得分 + calculateStudentScoreRates(studentId, examResults, knowledgeMap, points, knowledgeTotalScore); + + // 将知识点得分存储到 studentKnowledgePointScores 中 + studentKnowledgePointScores.put(studentId, knowledgeTotalScore); + } + + // 2. 将知识点得分汇总到知识块级别 + Map> studentKnowledgeBlockScores = new HashMap<>(); + for (Map.Entry> entry : studentKnowledgePointScores.entrySet()) { + String studentId = entry.getKey(); + Map pointScores = entry.getValue(); + Map blockScores = studentKnowledgeBlockScores.computeIfAbsent(studentId, k -> new HashMap<>()); + + // 遍历知识块和知识点的映射关系 + for (Map.Entry> blockEntry : knowledgeBlockToPointsMap.entrySet()) { + String knowledgeBlock = blockEntry.getKey(); + List knowledgePoints = blockEntry.getValue(); + + // 计算该知识块的总得分 + double totalScore = 0.0; + for (String knowledgePoint : knowledgePoints) { + if (pointScores.containsKey(knowledgePoint)) { + totalScore += pointScores.get(knowledgePoint); + } + } + blockScores.put(knowledgeBlock, totalScore); + } + } + + // 3. 更新现有的 scores 返回数据 + for (Map studentResult : scores) { + String studentId = (String) studentResult.get("studentId"); + Map subjectiveScores = (Map) studentResult.get("scores"); + + // 查找该学生的知识块客观成绩 + Map knowledgeBlockScores = studentKnowledgeBlockScores.get(studentId); + + // 初始化主观总分和客观总分 + double subjectiveTotal = 0.0; + double objectiveTotal = 0.0; + int knowledgeBlockCount = knowledgeBlockScores.size(); + + // 更新主观成绩,计算主观总分和客观总分 + Map combinedScores = new HashMap<>(); + for (Map.Entry entry : subjectiveScores.entrySet()) { + String knowledgeBlock = entry.getKey(); + int count = entry.getValue(); // 次数 + // 确保 count 不小于0 + if (count < 0) { + count = 0; //次数永远不能为负数 + } + double maxCount = 50.0; // 最大次数(可根据实际情况调整) + + // 将次数转换为 0-100 的分数 + double convertedScore = (count / maxCount) * 100; + subjectiveTotal += convertedScore; + + // 获取对应知识块的客观成绩 + double objectiveScore = knowledgeBlockScores.get(knowledgeBlock); + objectiveTotal += objectiveScore; + + /* + 形成 次数/客观分数 的格式 + String combinedScore = count + "/" + objectiveScore; + */ + int finalCount = count; + combinedScores.put(knowledgeBlock, new HashMap() {{ + put("count", finalCount); + put("objectiveScore", objectiveScore); + }}); + //combinedScores.put(knowledgeBlock, objectiveScore); + + } + + // 计算客观平均分 + double objectiveAverage = (knowledgeBlockCount > 0) ? objectiveTotal : 0.0; + + // 计算综合得分(主观占 60%,客观占 40%) + double compositeScore = (subjectiveTotal * 0.6) + (objectiveAverage * 0.4); + compositeScore = Double.parseDouble(String.format("%.2f", compositeScore)); + + // 更新返回数据 + studentResult.put("scores", combinedScores); + studentResult.put("subjectiveTotal", subjectiveTotal); + studentResult.put("objectiveTotal", objectiveAverage); + studentResult.put("compositeScore", compositeScore); + } + + return scores; + } + + + + public static List> calculateScoresWithDetails(List res, Appraise appraise) { + // 1. 构建知识点到知识块的映射 (一个知识块对应多个知识点) + Map> knowledgeBlockToPointsMap = getKnowledgeBlockToPointsMap(appraise); + + // 2. 构建所有二级知识块名称列表 + Set knowledgeBlocks = new HashSet<>(); + for (AppraiseTreeNode node : appraise.getNodes()) { + if ("德育".equals(node.getName())) { + for (AppraiseTreeNode secondLevelNode : node.getChildren()) { + knowledgeBlocks.add(secondLevelNode.getName()); + } + } + } + + // 3. 构建知识点到知识块的映射 (一个知识点对应一个知识块) + Map knowledgePointToBlockMap = new HashMap<>(); + for (Map.Entry> entry : knowledgeBlockToPointsMap.entrySet()) { + String knowledgeBlock = entry.getKey(); + for (String knowledgePoint : entry.getValue()) { + knowledgePointToBlockMap.put(knowledgePoint, knowledgeBlock); + } + } + + // 4. 遍历评价记录并统计得分 + Map> studentScores = new HashMap<>(); + for (RecordVo record : res) { + String studentId = record.getTargetId(); // 学生 ID + String studentName = record.getTargetName(); // 学生名称 + String className = record.getClassName(); // 班级名称 + String appraiseName = record.getAppraiseName(); // 评价内容 + boolean isPraise = record.isPraise(); // 是否为优点 + + // 获取评价内容对应的第二级节点(知识块) + String knowledgeBlock = knowledgePointToBlockMap.get(appraiseName); + if (knowledgeBlock == null) { + continue; // 如果不在德育知识块中,跳过 + } + + // 初始化学生得分记录 + String studentKey = studentId + "|" + studentName + "|" + className; // 组合学生唯一标识 + studentScores.putIfAbsent(studentKey, new HashMap<>()); + Map studentScoreMap = studentScores.get(studentKey); + + // 计分 +// 计分 + int score = isPraise ? 1 : -1; + int currentScore = studentScoreMap.getOrDefault(knowledgeBlock, 0) + score; + studentScoreMap.put(knowledgeBlock, Math.max(currentScore, 0)); + } + + // 5. 初始化缺失的二级知识块分数 + for (Map.Entry> entry : studentScores.entrySet()) { + Map studentScoreMap = entry.getValue(); + for (String block : knowledgeBlocks) { + studentScoreMap.putIfAbsent(block, 0); + } + } + + // 6. 整理并返回结果 + return getMaps(studentScores); + } + + // 整理并返回结果 + private static List> getMaps(Map> studentScores) { + List> result = new ArrayList<>(); + + for (Map.Entry> entry : studentScores.entrySet()) { + String studentKey = entry.getKey(); + Map scoreMap = entry.getValue(); + + // 解析学生唯一标识 + String[] studentInfo = studentKey.split("\\|"); + String studentId = studentInfo[0]; + String studentName = studentInfo[1]; + String className = studentInfo[2]; + + // 构建返回结果 + Map studentResult = new HashMap<>(); + studentResult.put("studentId", studentId); + studentResult.put("studentName", studentName); + studentResult.put("className", className); + studentResult.put("scores", scoreMap); + + result.add(studentResult); + } + + return result; + } + + // 构建知识块到知识点的映射 (一个知识块对应多个知识点) + private static Map> getKnowledgeBlockToPointsMap(Appraise appraise) { + Map> knowledgeBlockToPointsMap = new HashMap<>(); + + for (AppraiseTreeNode node : appraise.getNodes()) { + if ("德育".equals(node.getName())) { + for (AppraiseTreeNode secondLevelNode : node.getChildren()) { + String knowledgeBlock = secondLevelNode.getName(); + List knowledgePoints = new ArrayList<>(); + + // 假设知识点的信息存储在 secondLevelNode.getChildren() 中 + for (AppraiseTreeNode pointNode : secondLevelNode.getChildren()) { + knowledgePoints.add(pointNode.getName()); + } + + knowledgeBlockToPointsMap.put(knowledgeBlock, knowledgePoints); + } + } + } + + return knowledgeBlockToPointsMap; + } + + + // 知识点得分率结果类 + @Data + @AllArgsConstructor + public static class KnowledgeScoreRate { + public String knowledge; // 知识点 + public double scoreRate; // 得分率 + } + + /** + * 计算某个学生在多次考试中的知识点得分率,并返回其所属节点的父节点得分率 + * + * @param studentId 学生 ID + * @param examResults 学生作答结果集 + * @param knowledgeMap 知识点映射(试卷 ID -> 知识点列表) + * @param appraise 评价对象 + * @return 父节点得分率 + */ + public static Map calculateKnowledgeScoreRateForStudent( + String studentId, List examResults, + Map>> knowledgeMap, Appraise appraise, Map> points) { + Map knowledgeTotalScore = new HashMap<>(); + // 计算学生的知识点得分率 + List studentScoreRates = calculateStudentScoreRates(studentId, examResults, knowledgeMap, points, knowledgeTotalScore); + + + // 查找知识点对应的节点及其父节点 + Map parentNodeScoreRates = new HashMap<>(); + for (KnowledgeScoreRate scoreRate : studentScoreRates) { + AppraiseTreeNode node = findKnowledgeNode(appraise.getNodes(), scoreRate.getKnowledge()); + if (node != null) { + AppraiseTreeNode parentNode = findParentNode(appraise.getNodes(), node.getId()); + if (parentNode != null) { + double parentNodeScoreRate = calculateNodeScoreRate(parentNode, knowledgeTotalScore); + parentNodeScoreRates.put(parentNode.getName(), parentNodeScoreRate); + }else { + // 如果没有找到父节点,初始化一个默认的得分率为0的父节点得分率 + parentNodeScoreRates.put(node.getName(), 0.0); + } + } + } + + // 初始化所有同层的父节点 + for (AppraiseTreeNode node : appraise.getNodes()) { + if (node.getName().equals("德育")) { + for (AppraiseTreeNode child : node.getChildren()) { + if (!parentNodeScoreRates.containsKey(child.getName())) { + parentNodeScoreRates.put(child.getName(), 0.0); + } + } + } + } + + return parentNodeScoreRates; + } + + + /** + * 计算班级每个学生知识点得分率,并返回每个学生所属节点的父节点得分率 + * + * @param classId 班级 ID + * @param examResults 学生作答结果集 + * @param knowledgeMap 知识点映射(试卷 ID -> 知识点列表) + * @param appraise 评价对象 + * @return 学生 ID -> 父节点得分率映射 + */ + public static Map calculateKnowledgeScoreRateForClass( + String classId, + List examResults, + Map>> knowledgeMap, + Appraise appraise, + Map> points, + List res + , HttpServletRequest request) { // 新增主观评价记录 + // 将 classId 转换为 List + List classIds = Collections.singletonList(classId); + GroupDto groupDto = new GroupDto(); + groupDto.setIds(classIds); + groupDto.setSchoolId(appraise.getSchoolId()); + String url = environment.getProperty("ies.server-url-group"); + Map groupId = GroupUtil.getGroupId(groupDto,new GroupUtil(environment), request,url); + List rMembers = new ArrayList<>(); + for (Map.Entry entry : groupId.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (key.equals("members")) { + String jsonGroups = JSON.toJSONString(value); + rMembers = JSON.parseObject(jsonGroups, new TypeReference>() {}); + } + } + + // 1. 获取班级所有学生的客观分数 + Map> studentScoreRates = new HashMap<>(); + Map classScoreRates = new HashMap<>(); + + // 遍历班级中的每个学生 + for (ExamClassResult examResult : examResults) { + if (!examResult.getInfo().getId().equals(classId)) continue; // 过滤出该班级的考试 + List statuses = examResult.getStatus(); + for (int i = 0; i < examResult.getStudentIds().size(); i++) { + String studentId = examResult.getStudentIds().get(i); + String name = rMembers.stream() + .filter(member -> member.getId().equals(studentId)) + .findFirst() + .map(RMember::getName) + .orElse("未知"); + int status = statuses.get(i); + if (status == 1) continue; // 跳过 status 为 1 的记录 + + // 计算父节点得分率(客观分数) + Map scoreRates = calculateKnowledgeScoreRateForStudent( + studentId, examResults, knowledgeMap, appraise, points + ); + studentScoreRates.put(name, scoreRates); + + // 累加班级整体得分率 + for (Map.Entry entry : scoreRates.entrySet()) { + classScoreRates.put(entry.getKey(), classScoreRates.getOrDefault(entry.getKey(), 0.0) + entry.getValue()); + } + } + } + + // 2. 获取班级所有学生的主观分数(次数) + List> subjectiveScoresList = calculateScoresWithDetails(res, appraise); + + // 3. 将主观次数转换为0-100分数 + Map> subjectiveScores = new HashMap<>(); + for (Map studentScore : subjectiveScoresList) { + String studentId = (String) studentScore.get("studentId"); + // 修改这里:假设 scores 是 Map + @SuppressWarnings("unchecked") + Map scores = (Map) studentScore.get("scores"); + Map convertedScores = new HashMap<>(); + + for (Map.Entry entry : scores.entrySet()) { + String block = entry.getKey(); + int count = entry.getValue(); + double maxCount = 50.0; + double convertedScore = (count / maxCount) * 100; + convertedScores.put(block, convertedScore); + } + + subjectiveScores.put(studentId, convertedScores); + } + + // 4. 融合主观和客观分数 + Map> compositeStudentScores = new HashMap<>(); + for (Map.Entry> entry : studentScoreRates.entrySet()) { + String studentId = entry.getKey(); + Map objective = entry.getValue(); + Map subjective = subjectiveScores.getOrDefault(studentId, new HashMap<>()); + + // 计算综合得分 + Map compositeScores = new HashMap<>(); + for (Map.Entry objectiveEntry : objective.entrySet()) { + String block = objectiveEntry.getKey(); + double objectiveScore = objectiveEntry.getValue(); + double subjectiveScore = subjective.getOrDefault(block, 0.0); + + // 按6:4比例计算综合得分 + double compositeScore = (subjectiveScore * 0.6) + (objectiveScore * 0.4); + compositeScore = Double.parseDouble(String.format("%.2f", compositeScore)); + compositeScores.put(block, compositeScore); + } + + compositeStudentScores.put(studentId, compositeScores); + } + + // 5. 计算班级整体得分率 + int studentCount = compositeStudentScores.size(); + Map compositeClassScoreRates = new HashMap<>(); + for (Map studentScores : compositeStudentScores.values()) { + for (Map.Entry entry : studentScores.entrySet()) { + String block = entry.getKey(); + double score = entry.getValue(); + compositeClassScoreRates.put(block, compositeClassScoreRates.getOrDefault(block, 0.0) + score); + } + } + compositeClassScoreRates.replaceAll((k, v) -> Double.parseDouble(String.format("%.2f", v / studentCount))); // 保留小数点后两位 + + // 6. 初始化所有同层的父节点 + for (AppraiseTreeNode node : appraise.getNodes()) { + if (node.getName().equals("德育")) { + for (AppraiseTreeNode child : node.getChildren()) { + if (!compositeClassScoreRates.containsKey(child.getName())) { + compositeClassScoreRates.put(child.getName(), 0.0); + } + } + } + } + + // 7. 返回结果 + Map result = new HashMap<>(); + result.put("classScoreRates", compositeClassScoreRates); // 班级整体得分率 + result.put("studentScoreRates", compositeStudentScores); // 每个学生的综合得分率 + return result; + } + + + + + private static List calculateStudentScoreRates( + String studentId, + List examResults, + Map>> knowledgeMap, + Map> points, + Map knowledgeTotalScore) { + + // 记录每个知识点的考试次数 + Map knowledgeExamCount = new HashMap<>(); + + // 过滤出当前学生的考试结果 + for (ExamClassResult examResult : examResults) { + int studentIndex = examResult.getStudentIds().indexOf(studentId); + if (studentIndex == -1) continue; + int status = examResult.getStatus().get(studentIndex); + if (status == 1) continue; + + // 获取该考试的知识点 + List> knowledgeList = knowledgeMap.get(examResult.getExamId()); + List point = points.get(examResult.getExamId()); + if (knowledgeList == null) continue; + + // 检查 studentScores 是否为空或越界 + List> studentScores = examResult.getStudentScores(); + if (studentIndex >= studentScores.size() || studentScores.get(studentIndex) == null) { + continue; + } + + // 获取当前学生的成绩列表 + List studentScoreList = studentScores.get(studentIndex); + + // 确保知识列表、分数列表和学生成绩列表的大小一致 + if (knowledgeList.size() != point.size() || knowledgeList.size() != studentScoreList.size()) { + continue; + } + + // 遍历每道题的得分和知识点 + for (int i = 0; i < studentScoreList.size(); i++) { + String knowledge = knowledgeList.get(i).get(0); // 每题的第一个知识点 + double studentScore = studentScoreList.get(i); // 学生的得分 + + // 累加知识点总分 + knowledgeTotalScore.put(knowledge, knowledgeTotalScore.getOrDefault(knowledge, 0.0) + studentScore); + + // 记录该知识点的考试次数 + knowledgeExamCount.put(knowledge, knowledgeExamCount.getOrDefault(knowledge, 0) + 1); + } + } + + // 计算每个知识点的平均分 + Map knowledgeAverageScore = new HashMap<>(); + for (Map.Entry entry : knowledgeTotalScore.entrySet()) { + String knowledge = entry.getKey(); + double totalScore = entry.getValue(); + int examCount = knowledgeExamCount.getOrDefault(knowledge, 1); // 避免除零 + double averageScore = totalScore / examCount; + knowledgeAverageScore.put(knowledge, averageScore); + } + + // 返回知识点平均分 + return knowledgeAverageScore.entrySet().stream() + .map(entry -> new KnowledgeScoreRate(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + + + public static double calculateNodeScoreRate(AppraiseTreeNode node, Map knowledgeTotalScore) { + // 获取当前节点的得分 + double totalScore = knowledgeTotalScore.getOrDefault(node.getName(), 0.0); + + // 递归计算子节点的得分并累加 + for (AppraiseTreeNode child : node.getChildren()) { + totalScore += calculateNodeScoreRate(child, knowledgeTotalScore); + } + + return totalScore; + } + + /** + * 计算年级整体得分率,并返回其所属节点的父节点得分率 + * + * @param gradeId 年级 ID + * @param examResults 学生作答结果集 + * @param knowledgeMap 知识点映射(试卷 ID -> 知识点列表) + * @param appraise 评价对象 + * @param points 试卷配分 + * @return 年级所属节点的父节点得分率 + */ + public static Map calculateKnowledgeScoreForGrade( + String gradeId, + List examResults, + Map>> knowledgeMap, + Appraise appraise, + Map> points, + List res) { // 新增主观评价记录 + + // 1. 获取年级所有班级的客观分数 + Map> classScores = new HashMap<>(); + Map gradeScores = new HashMap<>(); + + // 过滤出该年级的考试结果 + List gradeExamResults = examResults.stream() + .filter(examResult -> examResult.getGradeId().equals(gradeId)) + .collect(Collectors.toList()); + + // 2. 获取年级所有学生的主观分数(次数) + List> subjectiveScoresList = calculateScoresWithDetails(res, appraise); + + // 3. 将主观次数转换为0-100分数 + Map> subjectiveScores = new HashMap<>(); + for (Map studentScore : subjectiveScoresList) { + String studentId = (String) studentScore.get("studentId"); + // 修改这里:假设 scores 是 Map + @SuppressWarnings("unchecked") + Map scores = (Map) studentScore.get("scores"); + Map convertedScores = new HashMap<>(); + + for (Map.Entry entry : scores.entrySet()) { + String block = entry.getKey(); + int count = entry.getValue(); + double maxCount = 50.0; + double convertedScore = (count / maxCount) * 100; + convertedScores.put(block, convertedScore); + } + + subjectiveScores.put(studentId, convertedScores); + } + + // 4. 遍历年级下的所有班级 + for (ExamClassResult examResult : gradeExamResults) { + String classId = examResult.getInfo().getId(); + String className = examResult.getInfo().getName(); + Map classScoreSum = new HashMap<>(); + Map classScoreCount = new HashMap<>(); + Map classScoresInner = new HashMap<>(); + + // 获取学生状态列表 + List statuses = examResult.getStatus(); + // 遍历班级中的每个学生 + for (int i = 0; i < examResult.getStudentIds().size(); i++) { + String studentId = examResult.getStudentIds().get(i); + if (statuses.get(i) == 1) { + continue; + } + + // 计算学生的知识点得分(客观分数) + Map studentObjectiveScores = calculateKnowledgeScoreRateForStudent( + studentId, examResults, knowledgeMap, appraise, points + ); + + // 获取学生的主观分数 + Map studentSubjectiveScores = subjectiveScores.getOrDefault(studentId, new HashMap<>()); + + // 计算综合得分(主观60%,客观40%) + Map studentCompositeScores = new HashMap<>(); + for (Map.Entry entry : studentObjectiveScores.entrySet()) { + String block = entry.getKey(); + double objectiveScore = entry.getValue(); + double subjectiveScore = studentSubjectiveScores.getOrDefault(block, 0.0); + double compositeScore = (subjectiveScore * 0.6) + (objectiveScore * 0.4); + studentCompositeScores.put(block, compositeScore); + } + + // 累加班级整体得分 + for (Map.Entry entry : studentCompositeScores.entrySet()) { + String nodeName = entry.getKey(); + double score = entry.getValue(); + classScoreSum.put(nodeName, classScoreSum.getOrDefault(nodeName, 0.0) + score); + classScoreCount.put(nodeName, classScoreCount.getOrDefault(nodeName, 0) + 1); + } + } + + // 计算班级平均得分 + for (Map.Entry entry : classScoreSum.entrySet()) { + String nodeName = entry.getKey(); + double totalScore = entry.getValue(); + int count = classScoreCount.get(nodeName); + classScoresInner.put(nodeName, Double.parseDouble(String.format("%.2f", totalScore / count))); // 保留小数点后两位 + } + + // 初始化所有同层的父节点 + for (AppraiseTreeNode node : appraise.getNodes()) { + if (node.getName().equals("德育")) { + for (AppraiseTreeNode child : node.getChildren()) { + if (!classScoresInner.containsKey(child.getName())) { + classScoresInner.put(child.getName(), 0.0); + } + } + } + } + + // 存储班级的平均得分 + classScores.put(className, classScoresInner); + } + + // 5. 计算年级的平均得分 + Map gradeScoreSum = new HashMap<>(); + Map gradeScoreCount = new HashMap<>(); + for (Map classScore : classScores.values()) { + for (Map.Entry entry : classScore.entrySet()) { + String nodeName = entry.getKey(); + double score = entry.getValue(); + gradeScoreSum.put(nodeName, gradeScoreSum.getOrDefault(nodeName, 0.0) + score); + gradeScoreCount.put(nodeName, gradeScoreCount.getOrDefault(nodeName, 0) + 1); + } + } + for (Map.Entry entry : gradeScoreSum.entrySet()) { + String nodeName = entry.getKey(); + double totalScore = entry.getValue(); + int count = gradeScoreCount.get(nodeName); + gradeScores.put(nodeName, Double.parseDouble(String.format("%.2f", totalScore / count))); // 保留小数点后两位 + } + + // 初始化所有同层的父节点 + for (AppraiseTreeNode node : appraise.getNodes()) { + if (node.getName().equals("德育")) { + for (AppraiseTreeNode child : node.getChildren()) { + if (!gradeScores.containsKey(child.getName())) { + gradeScores.put(child.getName(), 0.0); + } + } + } + } + + // 6. 返回结果 + Map result = new HashMap<>(); + result.put("gradeScores", gradeScores); // 年级平均得分 + result.put("classScores", classScores); // 每个班级的平均得分 + return result; + } + + + + public static Map calculateKnowledgeScoreForSchool( + List examResults, + Map>> knowledgeMap, + Appraise appraise, + Map> points, + List res) { // 新增主观评价记录 + + // 1. 获取全校所有年级的客观分数 + Map> gradeScores = new HashMap<>(); + Map schoolScores = new HashMap<>(); + + // 获取所有年级的 ID + Set gradeIds = examResults.stream() + .map(ExamClassResult::getGradeId) + .collect(Collectors.toSet()); + + // 2. 获取全校所有学生的主观分数(次数) + List> subjectiveScoresList = calculateScoresWithDetails(res, appraise); + + // 3. 将主观次数转换为0-100分数 + Map> subjectiveScores = new HashMap<>(); + for (Map studentScore : subjectiveScoresList) { + String studentId = (String) studentScore.get("studentId"); + // 修改这里:假设 scores 是 Map + @SuppressWarnings("unchecked") + Map scores = (Map) studentScore.get("scores"); + Map convertedScores = new HashMap<>(); + + for (Map.Entry entry : scores.entrySet()) { + String block = entry.getKey(); + int count = entry.getValue(); + double maxCount = 50.0; + double convertedScore = (count / maxCount) * 100; + convertedScores.put(block, convertedScore); + } + + subjectiveScores.put(studentId, convertedScores); + } + + // 4. 遍历所有年级 + for (String gradeId : gradeIds) { + // 计算该年级的平均得分(包含主观和客观分数) + Map gradeResult = calculateKnowledgeScoreForGrade( + gradeId, examResults, knowledgeMap, appraise, points, res + ); + + // 获取该年级的平均得分 + Map gradeScoresInner = (Map) gradeResult.get("gradeScores"); + gradeScores.put(gradeId, gradeScoresInner); + + // 累加全校的得分总和 + Map> classScores = (Map>) gradeResult.get("classScores"); + for (Map classScoresInner : classScores.values()) { + for (Map.Entry entry : classScoresInner.entrySet()) { + String nodeName = entry.getKey(); + double score = entry.getValue(); + schoolScores.put(nodeName, schoolScores.getOrDefault(nodeName, 0.0) + score); + } + } + } + + // 5. 计算全校的平均得分 + if (gradeIds.size() == 1) { + schoolScores.putAll(gradeScores.get(gradeIds.iterator().next())); + } else { + // 计算全校的平均得分 + for (Map.Entry entry : schoolScores.entrySet()) { + String nodeName = entry.getKey(); + double totalScore = entry.getValue(); + int totalCount = gradeIds.size(); // 每个年级视为一个单位 + schoolScores.put(nodeName, Double.parseDouble(String.format("%.2f", totalScore / totalCount))); // 保留小数点后两位 + } + } + + // 6. 初始化所有同层的父节点 + for (AppraiseTreeNode node : appraise.getNodes()) { + if (node.getName().equals("德育")) { + for (AppraiseTreeNode child : node.getChildren()) { + if (!schoolScores.containsKey(child.getName())) { + schoolScores.put(child.getName(), 0.0); + } + } + } + } + + // 7. 返回结果 + Map result = new HashMap<>(); + result.put("schoolScores", schoolScores); // 全校平均得分 + result.put("gradeScores", gradeScores); // 每个年级的平均得分 + return result; + } + + + /** + * 查找知识点对应的节点 + * + * @param nodes 节点列表 + * @param knowledge 学生知识点得分率列表 + * @return 知识点对应的节点 + */ + private static AppraiseTreeNode findKnowledgeNode(List nodes, String knowledge) { + for (AppraiseTreeNode node : nodes) { + if (node.getName().equals("德育")) { + for (AppraiseTreeNode child : node.getChildren()) { + for (AppraiseTreeNode grandchild : child.getChildren()) { + if (grandchild.getName().equals(knowledge)) { + return grandchild; // 返回知识点对应的节点 + } + } + } + } + } + return null; // 未找到匹配的节点 + } + + private static AppraiseTreeNode findParentNode(List nodes, String nodeId) { + for (AppraiseTreeNode node : nodes) { + for (AppraiseTreeNode child : node.getChildren()) { + for (AppraiseTreeNode grandchild : child.getChildren()) { + if (grandchild.getId().equals(nodeId)) { + return child; // 返回父节点 + } + } + } + } + return null; // 未找到父节点 + } + +} diff --git a/src/main/java/cn/teammodel/model/dto/admin/labor/LaborDto.java b/src/main/java/cn/teammodel/model/dto/admin/labor/LaborDto.java new file mode 100644 index 0000000..ce523a7 --- /dev/null +++ b/src/main/java/cn/teammodel/model/dto/admin/labor/LaborDto.java @@ -0,0 +1,26 @@ +package cn.teammodel.model.dto.admin.labor; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class LaborDto { + @ApiModelProperty("学校编码") + public String code; + @ApiModelProperty("TmdId") + public String tmdId; + public Long startTime; + public Long endTime; + @ApiModelProperty("学段Id") + public String periodId; + public Integer year; + @ApiModelProperty("年级") + public String grade; + public String gradeName; + @ApiModelProperty("科目信息") + public String subjectId; + public String semesterId; + private String academicYearId; + private String classId; + +} diff --git a/src/main/java/cn/teammodel/model/entity/appraise/AppraiseTreeNode.java b/src/main/java/cn/teammodel/model/entity/appraise/AppraiseTreeNode.java index 9311d7e..6578d34 100644 --- a/src/main/java/cn/teammodel/model/entity/appraise/AppraiseTreeNode.java +++ b/src/main/java/cn/teammodel/model/entity/appraise/AppraiseTreeNode.java @@ -31,6 +31,7 @@ public class AppraiseTreeNode { */ @JsonProperty("isPraise") boolean isPraise; + double scoreRate; List children = new ArrayList<>(); } diff --git a/src/main/java/cn/teammodel/model/entity/common/Exam.java b/src/main/java/cn/teammodel/model/entity/common/Exam.java index d78bd4b..f21d0df 100644 --- a/src/main/java/cn/teammodel/model/entity/common/Exam.java +++ b/src/main/java/cn/teammodel/model/entity/common/Exam.java @@ -19,6 +19,7 @@ public class Exam extends BaseItem { public String school; public String creatorId; public int stuCount; + public String lessonRecordId; /* //实际考试人数 public int realCount; //平均分 diff --git a/src/main/java/cn/teammodel/model/entity/common/ExamClassResult.java b/src/main/java/cn/teammodel/model/entity/common/ExamClassResult.java new file mode 100644 index 0000000..ee6f406 --- /dev/null +++ b/src/main/java/cn/teammodel/model/entity/common/ExamClassResult.java @@ -0,0 +1,48 @@ +package cn.teammodel.model.entity.common; + +import cn.teammodel.model.entity.BaseItem; +import com.azure.spring.data.cosmos.core.mapping.Container; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Container(containerName = "Common") +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ExamClassResult extends BaseItem { + public String school; + public String examId ; + public String subjectId ; + public String gradeId ; + public int year ; + public ClassInfo info ; + public boolean progress; + public List studentIds ; + public List> studentAnswers ; + //记录学生客观题选项内容,便于学情分析 + public List>> ans ; + public List> studentScores ; + //记录学生作答状态 + //0 已作答 1缺考 2补考 3补考完成 + public List status ; + public String scope ; + public List sum ; + public double average ; + //单科单班得分率 + public double srate ; + //单科单班标准差 + public double standard ; + + @Data + public static class ClassInfo { + public String id; + public String name; + public String periodId ; + public int year ; + public String no; + } +} + diff --git a/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java b/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java index b897a3e..e1a96cc 100644 --- a/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java +++ b/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java @@ -13,6 +13,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Repository; +import java.util.Collection; import java.util.List; /** @@ -71,13 +72,14 @@ public interface AppraiseRecordRepository extends CosmosRepository= @startTime) and " + - "(IS_NULL(@endTime) or n.createTime <= @endTime)" + "(IS_NULL(@endTime) or n.createTime <= @endTime) and " + + "c.classId in (@ids)" ) - List latestRecords(String code, String academicYearId, Long startTime, Long endTime); + List latestRecords(String code, String academicYearId, Long startTime, Long endTime, Collection ids); @Query("select c.classId as id, count(1) as count from Student as c join n in c.nodes where( " + diff --git a/src/main/java/cn/teammodel/repository/ExamClassResultRepository.java b/src/main/java/cn/teammodel/repository/ExamClassResultRepository.java new file mode 100644 index 0000000..4b90800 --- /dev/null +++ b/src/main/java/cn/teammodel/repository/ExamClassResultRepository.java @@ -0,0 +1,16 @@ +package cn.teammodel.repository; + +import cn.teammodel.model.entity.common.ExamClassResult; +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import com.azure.spring.data.cosmos.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Collection; +import java.util.List; +@Repository +public interface ExamClassResultRepository extends CosmosRepository { + @Query("select * from ExamClassResult as s where s.code = @code and s.examId in (@ids) ") + List findAll(String code, Collection ids); + @Query("select * from ExamClassResult as s where s.code = @code and s.info.id = @id and s.examId = @examId") + List findById(String code, String id,String examId); +} diff --git a/src/main/java/cn/teammodel/repository/ExamRepository.java b/src/main/java/cn/teammodel/repository/ExamRepository.java index f93628b..5c3e606 100644 --- a/src/main/java/cn/teammodel/repository/ExamRepository.java +++ b/src/main/java/cn/teammodel/repository/ExamRepository.java @@ -6,6 +6,7 @@ import com.azure.spring.data.cosmos.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.Collection; import java.util.List; @Repository @@ -15,4 +16,6 @@ public interface ExamRepository extends CosmosRepository { List findExamByClassId(@Param("code")String code, @Param("classId")String classId, @Param("periodId")String periodId); @Query("select * from Exam as s where s.code = @code and s.id = @id") List findExamById(@Param("code")String code, @Param("id")String id); + @Query("select s.id,s.papers from Exam as s where s.code = @code and s.lessonRecordId in (@ids)") + List findExamsByIds(String code, Collection ids); }