diff --git a/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java b/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java index 707673f..655ba36 100644 --- a/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java +++ b/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java @@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; +import java.util.List; import java.util.Map; @RestController @@ -36,4 +37,17 @@ public class LaborEducationController { Map res = laborEducationService.getDetails(findDto,request); return R.success(res); } + @PostMapping("getStudentSemesterScores") + @ApiOperation("分学期比对分析") + public R> getStudentSemesterScores(@Valid @RequestBody LaborDto laborDto, HttpServletRequest request) { + Map res = laborEducationService.getStudentSemesterScores(laborDto,request); + return R.success(res); + } + + @PostMapping("getStudentMonthlyScores") + @ApiOperation("按月比对分析") + public R>> getStudentMonthlyScores(@Valid @RequestBody LaborDto laborDto, HttpServletRequest request) { + List> res = laborEducationService.getStudentMonthlyScores(laborDto); + return R.success(res); + } } diff --git a/src/main/java/cn/teammodel/controller/admin/controller/TeacherController.java b/src/main/java/cn/teammodel/controller/admin/controller/TeacherController.java index 0884d0e..4bf4937 100644 --- a/src/main/java/cn/teammodel/controller/admin/controller/TeacherController.java +++ b/src/main/java/cn/teammodel/controller/admin/controller/TeacherController.java @@ -2,8 +2,6 @@ package cn.teammodel.controller.admin.controller; import cn.teammodel.common.R; import cn.teammodel.controller.admin.service.TeacherService; -import cn.teammodel.model.dto.admin.exam.ExamRecordDto; -import cn.teammodel.model.dto.admin.exam.OverViewDto; import cn.teammodel.model.dto.admin.teacher.TeacherDto; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; diff --git a/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java b/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java index e4fddd7..1f3bf6e 100644 --- a/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java +++ b/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java @@ -6,9 +6,12 @@ import cn.teammodel.model.dto.admin.labor.LaborDto; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; +import java.util.List; import java.util.Map; public interface LaborEducationService { Map getAnalysis(LaborDto laborDto, HttpServletRequest request); Map getDetails(FindDto findDto, HttpServletRequest request); + Map getStudentSemesterScores(LaborDto laborDto, HttpServletRequest request); + List> getStudentMonthlyScores(LaborDto laborDto); } 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 index 03f7600..8d2a587 100644 --- a/src/main/java/cn/teammodel/controller/admin/service/impl/LaborEducationServiceImpl.java +++ b/src/main/java/cn/teammodel/controller/admin/service/impl/LaborEducationServiceImpl.java @@ -38,6 +38,10 @@ import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -529,22 +533,43 @@ public class LaborEducationServiceImpl implements LaborEducationService { Map parentNodeScoreCount = new HashMap<>(); // 记录每个父节点的知识点数量 for (LaborEducationServiceImpl.KnowledgeScoreRate scoreRate : studentScoreRates) { - AppraiseTreeNode node = findKnowledgeNode(appraise.getNodes(), scoreRate.getKnowledge()); + String knowledge = scoreRate.getKnowledge(); + AppraiseTreeNode node = findKnowledgeNode(appraise.getNodes(), knowledge); + + // 1. 查找知识点所属的知识块(假设通过knowledgeMap映射) + String block = findKnowledgeBlock(knowledgeMap, knowledge); // 自定义方法:从knowledgeMap中获取知识点所属的知识块 + + // 2. 查找知识块对应的父节点 + AppraiseTreeNode parentNode = null; + if (block != null) { + parentNode = findParentNodeByBlockName(appraise.getNodes(), block); // 自定义方法:按知识块名称查找父节点 + } + + // 3. 处理匹配到节点的情况 if (node != null) { - AppraiseTreeNode parentNode = findParentNode(appraise.getNodes(), node.getId()); - if (parentNode != null) { - // 累加父节点的得分 - double currentScore = parentNodeScoreRates.getOrDefault(parentNode.getName(), 0.0); - parentNodeScoreRates.put(parentNode.getName(), currentScore + scoreRate.getScoreRate()); - - // 累加父节点的知识点数量 - int currentCount = parentNodeScoreCount.getOrDefault(parentNode.getName(), 0); - parentNodeScoreCount.put(parentNode.getName(), currentCount + 1); + parentNode = findParentNode(appraise.getNodes(), node.getId()); + } else { + // 未匹配知识点:默认60分,归入其所属知识块的父节点 + double defaultScore = 60.0; + if (parentNode == null) { + // 若知识块无对应父节点,归入全局默认块 + parentNodeScoreRates.merge("默认块", defaultScore, Double::sum); + parentNodeScoreCount.merge("默认块", 1, Integer::sum); } else { - // 如果没有找到父节点,直接使用当前知识点的得分 - parentNodeScoreRates.put(node.getName(), scoreRate.getScoreRate()); - parentNodeScoreCount.put(node.getName(), 1); + // 归入知识块对应的父节点 + parentNodeScoreRates.merge(parentNode.getName(), defaultScore, Double::sum); + parentNodeScoreCount.merge(parentNode.getName(), 1, Integer::sum); } + continue; // 跳过后续匹配逻辑 + } + + // 4. 累加得分到父节点(原有逻辑) + if (parentNode != null) { + parentNodeScoreRates.merge(parentNode.getName(), scoreRate.getScoreRate(), Double::sum); + parentNodeScoreCount.merge(parentNode.getName(), 1, Integer::sum); + } else { + parentNodeScoreRates.put(node.getName(), scoreRate.getScoreRate()); + parentNodeScoreCount.put(node.getName(), 1); } } @@ -576,6 +601,30 @@ public class LaborEducationServiceImpl implements LaborEducationService { return parentNodeScoreRates; } + // 从knowledgeMap中查找知识点所属的知识块 + private static String findKnowledgeBlock(Map>> knowledgeMap, String knowledge) { + for (Map.Entry>> entry : knowledgeMap.entrySet()) { + for (List subList : entry.getValue()) { + if (subList.contains(knowledge)) { + return entry.getKey(); + } + } + } + return null; + } + + // 按知识块名称查找父节点 + private static AppraiseTreeNode findParentNodeByBlockName(List nodes, String blockName) { + for (AppraiseTreeNode node : nodes) { + if (node.getName().equals(blockName)) { + return node; + } + AppraiseTreeNode found = findParentNodeByBlockName(node.getChildren(), blockName); + if (found != null) return found; + } + return null; + } + /** * 计算班级每个学生知识点得分率,并返回每个学生所属节点的父节点得分率 @@ -826,7 +875,6 @@ public class LaborEducationServiceImpl implements LaborEducationService { Map>> knowledgeMap, Map> points, Map knowledgeTotalScore) { - Map knowledgeTotalAverage = new HashMap<>(); Map knowledgeExamCount = new HashMap<>(); @@ -1321,4 +1369,673 @@ public class LaborEducationServiceImpl implements LaborEducationService { return null; // 未找到父节点 } + +//获取每月各个知识库综合得分以及整体得分内容 +public List> getStudentMonthlyScores(LaborDto laborDto) { + // 1. 获取基础信息 + String schoolId = SecurityUtil.getLoginUser().getSchoolId(); + String studentId = laborDto.getStudentId(); + + // 2. 获取相关开课记录 + List records = getLessonRecords(laborDto, schoolId); + + //List classIds = Collections.singletonList(laborDto.getClassId()); + List res = appraiseRecordRepository.getStudentRecords( + String.format(PK.PK_APPRAISE_RECORD, schoolId), + laborDto.getStudentId(), + laborDto.getStartTime(), + laborDto.getEndTime(), + "德育" + ); + + + // 3. 获取考试及知识点映射 + Map>> examKnowledgeMap = new HashMap<>(); + Map> points = new HashMap<>(); + List exams = getExamsWithKnowledge(laborDto, records, examKnowledgeMap, points); + + // 4. 获取考试结果并按月份分组 + Map> monthlyResults = getMonthlyExamResults( + schoolId, laborDto.getClassId(), exams, records, examKnowledgeMap, points, res + ); + + // 5. 获取知识块配置 + Appraise appraise = appraiseRepository.findAppraiseBySchoolIdAndPeriodIdAndCode( + schoolId, laborDto.getPeriodId(), PK.PK_APPRAISE + ); + appraise = evaluationService.buildTree(appraise); + Map> knowledgeBlockMap = getKnowledgeBlockToPointsMap(appraise); + + + // 在获取知识块配置后添加 + Map pointToBlockMap = new HashMap<>(); + knowledgeBlockMap.forEach((block, ps) -> { + ps.forEach(point -> pointToBlockMap.put(point, block)); + }); + // 处理主观评价数据,统计每个月的次数 + Map> blockMonthlyCounts = new HashMap<>(); + + Map> finalBlockMonthlyCounts = blockMonthlyCounts; + res.stream() + .filter(vo -> studentId.equals(vo.getTargetId())) + .forEach(vo -> { + // 获取知识点对应的知识块 + String originalPoint = vo.getAppraiseName(); + String block = pointToBlockMap.getOrDefault(originalPoint, "其他"); + + // 计算月份 + int month = getMonthFromTimestamp(vo.getCreateTime()); + + // 更新计数器 + Map monthCounts = finalBlockMonthlyCounts.computeIfAbsent(block, k -> new HashMap<>()); + int current = monthCounts.getOrDefault(month, 0); + monthCounts.put(month, current + (vo.isPraise() ? 1 : -1)); + }); + + // 处理负数和非分类数据 + blockMonthlyCounts = blockMonthlyCounts.entrySet().stream() + .filter(entry -> !"未分类".equals(entry.getKey())) // 过滤掉未映射的评价 + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> Math.max(e.getValue(), 0) + ) + ))); + + // 6. 处理每月数据,传入主观评价统计结果 + List> monthlyData = processMonthlyData(studentId, monthlyResults, knowledgeBlockMap, + getMonthsBetween(laborDto.getStartTime(), laborDto.getEndTime()), blockMonthlyCounts); + + // 7. 计算综合得分并添加到结果 + return addOverallScore(monthlyData, knowledgeBlockMap.keySet()); +} + + + // 新增方法:添加综合得分 + private List> addOverallScore( + List> monthlyData, + Set knowledgeBlocks + ) { + // 创建综合得分容器 + Map totalScores = new HashMap<>(); + Map scoreCounts = new HashMap<>(); + + // 累加所有月份得分 + for (Map monthData : monthlyData) { + for (String block : knowledgeBlocks) { + Double score = (Double) monthData.get(block); + if (score != null) { + totalScores.put(block, totalScores.getOrDefault(block, 0.0) + score); + scoreCounts.put(block, scoreCounts.getOrDefault(block, 0) + 1); + } + } + } + + // 计算平均分 + Map overall = new LinkedHashMap<>(); + overall.put("type", "overall"); + for (String block : knowledgeBlocks) { + int count = scoreCounts.getOrDefault(block, 1); + double avg = totalScores.getOrDefault(block, 0.0) / count; + overall.put(block, Double.parseDouble(String.format("%.2f", avg))); + } + + // 创建新的结果列表(保持原数据不变) + List> result = new ArrayList<>(monthlyData); + result.add(overall); + + return result; + } + + private List getLessonRecords(LaborDto laborDto, String schoolId) { + LessonRecordQueryService queryService = new LessonRecordQueryService(lessonRecordRepository); + try { + return queryService.queryLessonsInParallel( + String.format(PK.PK_LESSON_RECORD, schoolId), + laborDto.getStartTime(), + laborDto.getEndTime(), + laborDto.getSubjectId(), + null, + null, + laborDto.getPeriodId() + ); + } catch (InterruptedException | ExecutionException e) { + throw new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "课程查询异常"); + } finally { + queryService.shutdown(); + } + } + + private List getExamsWithKnowledge(LaborDto laborDto, List records, + Map>> examKnowledgeMap, + Map> points) { + List lessonRecordIds = records.stream().map(LessonRecord::getId).collect(Collectors.toList()); + List exams = examRepository.findExamsByIds(laborDto.getSource(), lessonRecordIds); + + exams.forEach(exam -> { + if (!exam.getPapers().isEmpty() && exam.getPapers().get(0).getKnowledge() != null) { + examKnowledgeMap.put(exam.getId(), exam.getPapers().get(0).getKnowledge()); + points.put(exam.getId(), exam.getPapers().get(0).getPoint()); + } + }); + + return exams.stream() + .filter(exam -> !exam.getClasses().isEmpty()) + .collect(Collectors.toList()); + } + + private Map> getMonthlyExamResults(String schoolId, String classId, + List exams, List records, + Map>> examKnowledgeMap, + Map > points, + List res) { + Map> monthlyResults = new HashMap<>(); + + exams.forEach(exam -> { + List results = examClassResultRepository.findById( + String.format(PK.CLASS_RESULT, schoolId), classId, exam.getId() + ); + + results.forEach(result -> { + records.stream() + .filter(r -> r.getId().equals(exam.getLessonRecordId())) + .findFirst() + .ifPresent(record -> { + int month = getMonthFromTimestamp(record.getStartTime()); + List> knowledge = examKnowledgeMap.get(exam.getId()); + List point = points.get(exam.getId()); + + monthlyResults.computeIfAbsent(month, k -> new ArrayList<>()) + .add(new ExamResultWrapper(exam.getId(),result, knowledge,point)); + }); + }); + }); + + return monthlyResults; + } + + private List> processMonthlyData(String studentId, + Map> monthlyResults, + Map> knowledgeBlockMap, + Set months, + Map> blockMonthlyCounts) { + + return months.stream().map(month -> { + Map monthData = new LinkedHashMap<>(); + monthData.put("month", month); + + List wrappers = monthlyResults.getOrDefault(month, Collections.emptyList()); + + List examResults = wrappers.stream() + .map(ExamResultWrapper::getResult) + .collect(Collectors.toList()); + + Map>> monthlyKnowledgeMap = new HashMap<>(); + Map> monthlyPoints = new HashMap<>(); + wrappers.forEach(wrapper -> { + String examId = wrapper.getExamId(); + monthlyKnowledgeMap.put(examId, wrapper.getKnowledge()); + monthlyPoints.put(examId, wrapper.getPoints()); + }); + + Map knowledgeScores = calculateMonthlyKnowledgeScores( + studentId, examResults, monthlyKnowledgeMap, monthlyPoints + ); + + Set monthlyTestedKnowledges = knowledgeScores.keySet(); + + knowledgeBlockMap.forEach((block, knowledges) -> { + List testedKnowledges = knowledges.stream() + .filter(monthlyTestedKnowledges::contains) + .collect(Collectors.toList()); + + double finalScore; + if (testedKnowledges.isEmpty()) { + finalScore = 60.0; + } else { + double averageCorrectRate = testedKnowledges.stream() + .mapToDouble(k -> knowledgeScores.get(k)) + .average() + .orElse(60.0); + finalScore = 60.0 + (averageCorrectRate * 0.4); + finalScore = Math.min(finalScore, 100.0); + } + finalScore = Double.parseDouble(String.format("%.2f", finalScore)); + + // 处理主观评分(根据知识块获取对应次数) + Map monthlyCounts = blockMonthlyCounts.getOrDefault(block, new HashMap<>()); + int praiseCount = monthlyCounts.getOrDefault(month, 0); + + // 步骤1:计算次数转换分数(0-40分) + final double K = 50.0; // 调整系数控制分数增长速度 + double convertedScore = (praiseCount * 40.0) / (praiseCount + K); + convertedScore = Math.min(convertedScore, 40.0); + convertedScore = Double.parseDouble(String.format("%.2f", convertedScore)); + + // 步骤2:计算主观总分(60分基础 + 转换分) + double subjectiveTotal = 60.0 + convertedScore; + subjectiveTotal = Math.min(subjectiveTotal, 100.0); + subjectiveTotal = Double.parseDouble(String.format("%.2f", subjectiveTotal)); + + // 步骤3:主客观合并(主观60% + 客观40%) + double combinedScore = (subjectiveTotal * 0.6) + (finalScore * 0.4); + combinedScore = Double.parseDouble(String.format("%.2f", combinedScore)); + + monthData.put(block, combinedScore); + }); + + return monthData; + }) + .sorted(Comparator.comparingInt(m -> (Integer) m.get("month"))) + .collect(Collectors.toList()); + } + + private Map calculateMonthlyKnowledgeScores(String studentId, + List examResults, + Map>> knowledgeMap, + Map> points) { + Map knowledgeTotalScore = new HashMap<>(); + Map knowledgeTotalAverage = new HashMap<>(); + Map knowledgeExamCount = new HashMap<>(); + + // 处理无考试数据的情况,初始化为60分 + if (examResults.isEmpty()) { + knowledgeMap.values().stream() + .flatMap(List::stream) + .flatMap(List::stream) + .distinct() + .forEach(knowledge -> knowledgeTotalScore.put(knowledge, 60.0)); + return knowledgeTotalScore; + } + + // 遍历每个考试结果 + for (ExamClassResult examResult : examResults) { + int studentIndex = examResult.getStudentIds().indexOf(studentId); + if (studentIndex == -1) continue; + if (examResult.getStatus().get(studentIndex) == 1) continue; // 跳过无效状态 + + List> knowledgeList = knowledgeMap.get(examResult.getExamId()); + List pointList = points.get(examResult.getExamId()); + if (knowledgeList == null || pointList == null) continue; + + List studentScores = examResult.getStudentScores().get(studentIndex); + if (studentScores == null || knowledgeList.size() != studentScores.size()) continue; + + // 计算当前考试的知识点得分 + Map currentExamScores = new HashMap<>(); + Map currentExamCounts = new HashMap<>(); + + for (int i = 0; i < studentScores.size(); i++) { + List knowledges = knowledgeList.get(i); + double score = studentScores.get(i); + if (knowledges.isEmpty() || score < 0) continue; + + double perKnowledgeScore = score / knowledges.size(); + knowledges.forEach(knowledge -> { + currentExamScores.merge(knowledge, perKnowledgeScore, Double::sum); + currentExamCounts.merge(knowledge, 1, Integer::sum); + }); + } + + // 更新累计数据 + currentExamScores.forEach((knowledge, total) -> { + double average = total / currentExamCounts.get(knowledge); + knowledgeTotalAverage.merge(knowledge, average, Double::sum); + knowledgeExamCount.merge(knowledge, 1, Integer::sum); + }); + } + + // 计算最终得分(基础60 + 实际得分,限制范围) + knowledgeTotalAverage.forEach((knowledge, total) -> { + int count = knowledgeExamCount.get(knowledge); + double finalScore = 60.0 + Math.min(total / count, 40.0); + knowledgeTotalScore.put(knowledge, Math.min(finalScore, 100.0)); + }); + + // 确保所有知识点都有值(处理部分知识点未出现在考试中的情况) + knowledgeMap.values().stream() + .flatMap(List::stream) + .flatMap(List::stream) + .distinct() + .forEach(knowledge -> knowledgeTotalScore.putIfAbsent(knowledge, 60.0)); + + // 保留两位小数 + knowledgeTotalScore.replaceAll((k, v) -> Double.parseDouble(String.format("%.2f", v))); + return knowledgeTotalScore; + } + + + // 辅助类封装考试结果和知识点 + @Data + @AllArgsConstructor + private static class ExamResultWrapper { + private String examId; + private ExamClassResult result; + private List> knowledge; // 每个题目对应的知识点列表 + private List points; + } + + // 时间处理工具方法 + private int getMonthFromTimestamp(Long timestamp) { + return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()) + .getMonthValue(); + } + + private Set getMonthsBetween(Long start, Long end) { + Set months = new TreeSet<>(); + LocalDateTime startDate = Instant.ofEpochMilli(start).atZone(ZoneId.systemDefault()).toLocalDateTime(); + LocalDateTime endDate = Instant.ofEpochMilli(end).atZone(ZoneId.systemDefault()).toLocalDateTime(); + + while (!startDate.isAfter(endDate)) { + months.add(startDate.getMonthValue()); + startDate = startDate.plusMonths(1); + } + return months; + } + + // 学期配置实体(对应JSON结构) + @Data + @AllArgsConstructor + public static class SemesterConfig { + private String name; + private int start; // 1表示学年开始学期 + private int month; // 月份(1-12) + private int day; // 日期(1-31) + private String id; + } + + // 学期时间范围实体 + @Data + @AllArgsConstructor + public static class SemesterPeriod { + private String name; + private LocalDate startDate; + private LocalDate endDate; + + // 转换为时间戳(用于查询) + public Long getStartTimestamp() { + return startDate.atStartOfDay(ZoneId.systemDefault()) + .toInstant().toEpochMilli(); + } + + public Long getEndTimestamp() { + return endDate.atTime(23, 59, 59) + .atZone(ZoneId.systemDefault()) + .toInstant().toEpochMilli(); + } + } + + // 分学期比对,分析 学期数据 + public Map getStudentSemesterScores(LaborDto laborDto,HttpServletRequest request) { + // 1. 获取学期配置 + List semesters = schoolRepository.findSemestersById(laborDto.getCode(), laborDto.getPeriodId()); + List configs = new ArrayList<>(); + for (School.Semester semester : semesters) { + configs.add(new SemesterConfig(semester.getName(), semester.getStart(), semester.getMonth(), semester.getDay(), semester.getId())); + } + // 2. 计算学期时间范围 + Map periods = calculateSemesters(configs); + SemesterPeriod current = periods.get("current"); + SemesterPeriod last = periods.get("last"); + + // 3. 查询并处理数据 + Map currentData = current != null ? + processSemesterData(laborDto, current, request,true) : + Collections.emptyMap(); + + Map lastData = last != null ? + processSemesterData(laborDto, last, request,true) : + Collections.emptyMap(); + + // 4. 组合最终结果 + return combineResults(laborDto, currentData, lastData); + } + + // 核心学期计算逻辑 + private Map calculateSemesters(List semesters) { + LocalDate now = LocalDate.now(); + int currentYear = now.getYear(); + + // 1. 确定学年起始学期 + SemesterConfig startSemester = semesters.stream() + .filter(s -> s.start == 1) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("未配置起始学期")); + + + // 2. 计算当前所属学年 + int academicYear = calculateAcademicYear(startSemester, LocalDate.now()); + + // 3. 生成当前学年学期 + List currentYearSemesters = generateYearSemesters( + semesters, academicYear, startSemester); + + // 4. 确定当前学期 + SemesterPeriod current = currentYearSemesters.stream() + .filter(sp -> !now.isBefore(sp.startDate) && !now.isAfter(sp.endDate)) + .findFirst() + .orElse(null); + + // 5. 确定上学期 + SemesterPeriod last = findLastSemester(current, semesters, academicYear, startSemester); + + return new HashMap() {{ + put("current", current); + put("last", last); + }}; + } + + // 增强学术年度计算方法 + private int calculateAcademicYear(SemesterConfig startSemester, LocalDate currentDate) { + int currentYear = currentDate.getYear(); + int currentMonth = currentDate.getMonthValue(); + + return (currentMonth < startSemester.month) ? currentYear -1 : currentYear; + } + // 生成学年学期时间范围 + private List generateYearSemesters(List semesters, + int academicYear, + SemesterConfig startSemester) { + List periods = new ArrayList<>(); + + // 1. 生成各学期开始日期 + for (SemesterConfig sc : semesters) { + int year = academicYear; + if (sc.month < startSemester.month) { + year += 1; // 跨年学期(如 2024 学年下学期是 2025 年 2 月) + } + LocalDate startDate = LocalDate.of(year, sc.month, sc.day); + periods.add(new SemesterPeriod(sc.name, startDate, null)); + } + + periods.sort(Comparator.comparing(SemesterPeriod::getStartDate)); + // 2. 计算结束日期 + for (int i = 0; i < periods.size(); i++) { + SemesterPeriod curr = periods.get(i); + + if (i < periods.size() - 1) { + // 非最后学期:下一学期开始前 1 天 + curr.setEndDate(periods.get(i + 1).getStartDate().minusDays(1)); + } else { + // 最后学期:学年结束年份的 7 月 31 日(2025-07-31) + int endYear = academicYear + 1; // 关键修改:统一用学年结束年份 + curr.setEndDate(LocalDate.of(endYear, 7, 31)); + } + + // 3. 校验时间顺序 + if (curr.getStartDate().isAfter(curr.getEndDate())) { + throw new IllegalStateException("学期日期无效: " + curr.getName() + + " 开始日期(" + curr.getStartDate() + + ") 不能晚于结束日期(" + curr.getEndDate() + ")"); + } + } + + return periods; + } + + // 查找上学期(含跨学年处理) + private SemesterPeriod findLastSemester(SemesterPeriod current, + List semesters, + int academicYear, + SemesterConfig startSemester) { + if (current == null) return null; + + List currentYearSemesters = generateYearSemesters( + semesters, academicYear, startSemester); + + int index = currentYearSemesters.indexOf(current); + if (index > 0) { + return currentYearSemesters.get(index-1); + } else { + // 获取去年最后一个学期 + List lastYear = generateYearSemesters( + semesters, academicYear-1, startSemester); + return lastYear.isEmpty() ? null : lastYear.get(lastYear.size()-1); + } + } + + // 处理学期数据 + private Map processSemesterData(LaborDto laborDto, + SemesterPeriod semester, + HttpServletRequest request, + boolean isCurrent) { + Map result = new HashMap<>(); + + // 1. 设置时间范围查询条件 + laborDto.setStartTime(convertToTimestamp(semester.startDate)); + laborDto.setEndTime(convertToTimestamp(semester.endDate)); + + // 2. 获取原始分析数据 + Map analysisData = getAnalysis(laborDto, request); + + // 3. 提取知识点次数(仅当前学期) + if (isCurrent) { + List> scores = (List>) analysisData.get("scores"); + Map knowledgeCounts = scores.stream() + .filter(s -> laborDto.getStudentId().equals(s.get("studentId"))) + .findFirst() + .map(s -> (List>) s.get("appraises")) + .map(appraises -> appraises.stream() + .collect(Collectors.toMap( + a -> (String) a.get("appraiseName"), + a -> 1, + Integer::sum + ))) + .orElse(new HashMap<>()); + result.put("knowledgeCounts", knowledgeCounts); + } + + // 4. 提取综合得分 + Map blockScores = extractBlockScores(analysisData, laborDto.getStudentId()); + result.put("blockScores", blockScores); + + // 5. 提取班级均分 + Map>> classScoreRate = (Map>>) analysisData.get("classScoreRate"); + List> classScoreList = classScoreRate.get("classScoreRates"); + Map classAverages = classScoreList.stream() + .findFirst() + .map(c -> (List>) c.get("blocks")) + .map(blocks -> blocks.stream() + .collect(Collectors.toMap( + b -> (String) b.get("name"), + b -> (Double) b.get("score") + ))) + .orElse(new HashMap<>()); + result.put("classAverage", classAverages); + + return result; + } + + private Map extractBlockScores(Map data, String studentId) { + return ((List>) data.get("scores")).stream() + .filter(s -> studentId.equals(s.get("studentId"))) + .findFirst() + .map(scoreMap -> (Map>) scoreMap.get("scores")) // 获取知识块得分对象 + .map(scoresMap -> scoresMap.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, // 知识块名称(如"道德修养") + entry -> (Double) entry.getValue().get("compositeScore") // 提取复合分数 + )) + ) + .orElse(new HashMap<>()); + } + + + // 辅助方法:转换LocalDate为时间戳 + private long convertToTimestamp(LocalDate date) { + return date.atStartOfDay(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli(); + } + + // 结果合并方法 + private Map combineResults(LaborDto laborDto, + Map currentData, + Map lastData) { + Map result = new LinkedHashMap<>(); + result.put("studentId", laborDto.getStudentId()); + + + Appraise appraise = appraiseRepository.findAppraiseBySchoolIdAndPeriodIdAndCode(laborDto.getCode(), laborDto.getPeriodId(), PK.PK_APPRAISE); + // 获取知识块配置 + appraise = evaluationService.buildTree(appraise); + Set knowledgeBlocks = loadKnowledgeBlocks(appraise); + + List> scores = new ArrayList<>(); + + for (String block : knowledgeBlocks) { + Map blockData = new LinkedHashMap<>(); + blockData.put("name", block); + + Map currentBlockScores = (Map) currentData.get("blockScores"); + double moralScore = currentBlockScores.getOrDefault(block, 0.0); // 精确获取目标知识块 + blockData.put("TScore", moralScore); + + Map lastBlockScores = (Map) lastData.get("blockScores"); + double lastScore = lastBlockScores.getOrDefault(block, 0.0); + blockData.put("LScore", lastScore); + + // 班级均分 + Map classAvg = (Map) currentData.get("classAverage"); + blockData.put("classScore", classAvg.getOrDefault(block, 0.0)); + + // 知识点次数 + if (currentData.containsKey("knowledgeCounts")) { + Map counts = (Map) currentData.get("knowledgeCounts"); + Appraise finalAppraise = appraise; + counts.entrySet().stream() + .filter(e -> isBelongToBlock(e.getKey(), block, finalAppraise)) + .forEach(e -> blockData.put(e.getKey(), e.getValue())); + } + + scores.add(blockData); + } + + result.put("scores", scores); + return result; + } + + + private Set loadKnowledgeBlocks(Appraise appraise) { + // 从appraise配置获取所有德育知识块 + Set knowledgePoints = new HashSet<>(); + for (AppraiseTreeNode node : appraise.getNodes()) { + if ("德育".equals(node.getName())) { + for (AppraiseTreeNode secondLevelNode : node.getChildren()) { + knowledgePoints.add(secondLevelNode.getName()); + } + } + } + return knowledgePoints; + } + + private boolean isBelongToBlock(String knowledge, String block,Appraise appraise) { + // 实现知识点归属判断逻辑 + Map> knowledgeBlockToPointsMap = getKnowledgeBlockToPointsMap(appraise); + return knowledgeBlockToPointsMap.getOrDefault(block, Collections.emptyList()) + .contains(knowledge); + } + } 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 index ed1e205..a322d6a 100644 --- a/src/main/java/cn/teammodel/model/dto/admin/labor/LaborDto.java +++ b/src/main/java/cn/teammodel/model/dto/admin/labor/LaborDto.java @@ -25,4 +25,5 @@ public class LaborDto { private String academicYearId; private String classId; private String source = "1"; + private String studentId; } diff --git a/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java b/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java index 7ff28bf..19e5888 100644 --- a/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java +++ b/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java @@ -86,6 +86,16 @@ public interface AppraiseRecordRepository extends CosmosRepository= @startTime) and " + + "(IS_NULL(@endTime) or n.createTime <= @endTime) and " + + "(IS_NULL(@typeName) or array_contains(n.appraiseNode.path ,@typeName)) " + ) + List getStudentRecords(String code, String studentId, Long startTime, Long endTime,String typeName); + + @Query("select c.id as recordId, c.name as targetName, c.avatar, c.classId,c.className, n.id as recordNodeId, c.targetId, c.targetType, n.creator, n.createTime, n.appraiseNode.name as appraiseName, n.appraiseNode.isPraise, n.device,n.appraiseNode.path from Student as c join n in c.nodes where " + "c.code = @code and " + "c.academicYearId = @academicYearId and " +