From cb7496930107007cd246aa12cf54c46900900bcd Mon Sep 17 00:00:00 2001 From: "hhb@hotmail.com" Date: Wed, 26 Mar 2025 15:34:25 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E6=8A=A5=E5=91=8A=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=AE=97=E6=B3=95=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LaborEducationController.java | 2 +- .../controller/MoralEducationController.java | 14 + .../admin/service/LaborEducationService.java | 2 +- .../admin/service/MoralEducationService.java | 3 + .../impl/LaborEducationServiceImpl.java | 601 ++++++++--- .../impl/MoralEducationServiceImpl.java | 993 +++++++++++++++++- .../model/dto/admin/labor/LaborDto.java | 4 +- .../repository/ExamClassResultRepository.java | 3 + 8 files changed, 1469 insertions(+), 153 deletions(-) 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 655ba36..f971677 100644 --- a/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java +++ b/src/main/java/cn/teammodel/controller/admin/controller/LaborEducationController.java @@ -47,7 +47,7 @@ public class LaborEducationController { @PostMapping("getStudentMonthlyScores") @ApiOperation("按月比对分析") public R>> getStudentMonthlyScores(@Valid @RequestBody LaborDto laborDto, HttpServletRequest request) { - List> res = laborEducationService.getStudentMonthlyScores(laborDto); + List> res = laborEducationService.getStudentMonthlyScores(laborDto,request); return R.success(res); } } diff --git a/src/main/java/cn/teammodel/controller/admin/controller/MoralEducationController.java b/src/main/java/cn/teammodel/controller/admin/controller/MoralEducationController.java index c525df4..389cba4 100644 --- a/src/main/java/cn/teammodel/controller/admin/controller/MoralEducationController.java +++ b/src/main/java/cn/teammodel/controller/admin/controller/MoralEducationController.java @@ -14,6 +14,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 @@ -34,4 +35,17 @@ public class MoralEducationController { Map res = moralEducationService.getDetails(findDto,request); return R.success(res); } + @PostMapping("getStudentSemesterScores") + @ApiOperation("分学期比对分析") + public R> getStudentSemesterScores(@Valid @RequestBody LaborDto laborDto, HttpServletRequest request) { + Map res = moralEducationService.getStudentSemesterScores(laborDto,request); + return R.success(res); + } + + @PostMapping("getStudentMonthlyScores") + @ApiOperation("按月比对分析") + public R>> getStudentMonthlyScores(@Valid @RequestBody LaborDto laborDto, HttpServletRequest request) { + List> res = moralEducationService.getStudentMonthlyScores(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 index 1f3bf6e..2a62fd9 100644 --- a/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java +++ b/src/main/java/cn/teammodel/controller/admin/service/LaborEducationService.java @@ -13,5 +13,5 @@ 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); + List> getStudentMonthlyScores(LaborDto laborDto,HttpServletRequest request); } diff --git a/src/main/java/cn/teammodel/controller/admin/service/MoralEducationService.java b/src/main/java/cn/teammodel/controller/admin/service/MoralEducationService.java index 955f2eb..6644ee5 100644 --- a/src/main/java/cn/teammodel/controller/admin/service/MoralEducationService.java +++ b/src/main/java/cn/teammodel/controller/admin/service/MoralEducationService.java @@ -4,9 +4,12 @@ import cn.teammodel.model.dto.admin.labor.FindDto; import cn.teammodel.model.dto.admin.labor.LaborDto; import javax.servlet.http.HttpServletRequest; +import java.util.List; import java.util.Map; public interface MoralEducationService { Map getAnalysis(LaborDto laborDto, HttpServletRequest request); Map getDetails(FindDto findDto, HttpServletRequest request); + Map getStudentSemesterScores(LaborDto laborDto, HttpServletRequest request); + List> getStudentMonthlyScores(LaborDto laborDto,HttpServletRequest request); } 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 8d2a587..879e112 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 @@ -30,7 +30,9 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import lombok.AllArgsConstructor; import lombok.Data; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @@ -38,6 +40,8 @@ import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -45,6 +49,7 @@ import java.time.ZoneId; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import java.util.stream.IntStream; @Service public class LaborEducationServiceImpl implements LaborEducationService { @@ -1371,23 +1376,59 @@ public class LaborEducationServiceImpl implements LaborEducationService { //获取每月各个知识库综合得分以及整体得分内容 -public List> getStudentMonthlyScores(LaborDto laborDto) { +public List> getStudentMonthlyScores(LaborDto laborDto,HttpServletRequest request) { // 1. 获取基础信息 String schoolId = SecurityUtil.getLoginUser().getSchoolId(); String studentId = laborDto.getStudentId(); - + List classStudentIds = Collections.emptyList(); // 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(), - "德育" - ); - + List res; + if (laborDto.getClassId() == null) { + res = appraiseRecordRepository.getStudentRecords( + String.format(PK.PK_APPRAISE_RECORD, schoolId), + laborDto.getStudentId(), + laborDto.getStartTime(), + laborDto.getEndTime(), + "德育" + ); + }else { + //当班级ID 存在时 获取该班级下所有名单即学生Id + List classIds = Collections.singletonList(laborDto.getClassId()); + GroupDto groupDto = new GroupDto(); + groupDto.setIds(classIds); + groupDto.setSchoolId(laborDto.getCode()); + String url = environment.getProperty("ies.server-url-group"); + Map groupId = GroupUtil.getGroupId(groupDto, new GroupUtil(environment), request, url); + List rGroupList = new ArrayList<>(); + List rMembers = new ArrayList<>(); + for (Map.Entry entry : groupId.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (key.equals("groups")) { + String jsonGroups = JSON.toJSONString(value); + rGroupList = JSON.parseObject(jsonGroups, new TypeReference>() {}); + } + } + rMembers = rGroupList.stream() + .flatMap(rGroupList1 -> rGroupList1.getMembers().stream()) + .filter(rMember -> rMember.getClassId().equals(laborDto.getClassId())) + .collect(Collectors.toList()); + classStudentIds = rMembers.stream().map(RMember::getId).collect(Collectors.toList()); + /*String className = rGroupList.stream() + .filter(rGroupList1 -> rGroupList1.getId().equals(classId)) + .findFirst() + .map(RGroupList::getName) + .orElse("未知班级"); // 如果未找到则返回默认值 "未知班级"*/ + res = appraiseRecordRepository.latestRecords( + String.format(PK.PK_APPRAISE_RECORD, schoolId), + laborDto.getAcademicYearId(), + laborDto.getStartTime(), + laborDto.getEndTime(), + "德育", + classIds + ); + } // 3. 获取考试及知识点映射 Map>> examKnowledgeMap = new HashMap<>(); @@ -1395,8 +1436,12 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { List exams = getExamsWithKnowledge(laborDto, records, examKnowledgeMap, points); // 4. 获取考试结果并按月份分组 + List targetStudents = laborDto.getClassId() != null ? + classStudentIds : Collections.singletonList(studentId); + Map> monthlyResults = getMonthlyExamResults( - schoolId, laborDto.getClassId(), exams, records, examKnowledgeMap, points, res + schoolId, laborDto.getClassId(), exams, records, + examKnowledgeMap, points, targetStudents // 传入目标学生列表 ); // 5. 获取知识块配置 @@ -1417,7 +1462,9 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { Map> finalBlockMonthlyCounts = blockMonthlyCounts; res.stream() - .filter(vo -> studentId.equals(vo.getTargetId())) + .filter(vo -> laborDto.getClassId() != null ? + targetStudents.contains(vo.getTargetId()) : + studentId.equals(vo.getTargetId())) .forEach(vo -> { // 获取知识点对应的知识块 String originalPoint = vo.getAppraiseName(); @@ -1445,11 +1492,14 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { ))); // 6. 处理每月数据,传入主观评价统计结果 - List> monthlyData = processMonthlyData(studentId, monthlyResults, knowledgeBlockMap, - getMonthsBetween(laborDto.getStartTime(), laborDto.getEndTime()), blockMonthlyCounts); - // 7. 计算综合得分并添加到结果 - return addOverallScore(monthlyData, knowledgeBlockMap.keySet()); + return processMonthlyData( + targetStudents, + monthlyResults, + knowledgeBlockMap, + getMonthsBetween(laborDto.getStartTime(), laborDto.getEndTime()), + blockMonthlyCounts + ); } @@ -1526,110 +1576,209 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { .collect(Collectors.toList()); } - private Map> getMonthlyExamResults(String schoolId, String classId, - List exams, List records, - Map>> examKnowledgeMap, - Map > points, - List res) { + private Map> getMonthlyExamResults( + String schoolId, String classId, List exams, + List records, Map>> examKnowledgeMap, + Map> points, List studentIds) { // 新增studentIds参数 + Map> monthlyResults = new HashMap<>(); - exams.forEach(exam -> { - List results = examClassResultRepository.findById( - String.format(PK.CLASS_RESULT, schoolId), classId, exam.getId() - ); + if (classId == null) { + exams.forEach(exam -> { + List results = examClassResultRepository.findByStudentId( + String.format(PK.CLASS_RESULT, schoolId), studentIds.get(0), 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()); + 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()); + + // 处理所有相关学生 + result.getStudentIds().stream() + .filter(studentIds::contains) // 仅处理班级学生 + .forEach(studentId -> { + monthlyResults.computeIfAbsent(month, k -> new ArrayList<>()) + .add(new ExamResultWrapper( + exam.getId(), + result, + knowledge, + point, + studentId // 新增学生ID字段 + )); + }); + }); + }); + }); + }else { + exams.forEach(exam -> { + List results = examClassResultRepository.findById( + String.format(PK.CLASS_RESULT, schoolId), classId, exam.getId() + ); - monthlyResults.computeIfAbsent(month, k -> new ArrayList<>()) - .add(new ExamResultWrapper(exam.getId(),result, knowledge,point)); - }); + 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()); + + // 处理所有相关学生 + result.getStudentIds().stream() + .filter(studentIds::contains) // 仅处理班级学生 + .forEach(studentId -> { + monthlyResults.computeIfAbsent(month, k -> new ArrayList<>()) + .add(new ExamResultWrapper( + exam.getId(), + result, + knowledge, + point, + studentId // 新增学生ID字段 + )); + }); + }); + }); }); - }); + + } return monthlyResults; } - private List> processMonthlyData(String studentId, - Map> monthlyResults, - Map> knowledgeBlockMap, - Set months, - Map> blockMonthlyCounts) { + private List> processMonthlyData( + List targetStudentIds, + 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) + Map monthEntry = new LinkedHashMap<>(); + monthEntry.put("month", month); + List> scoreList = new ArrayList<>(); + + // 按学生分组考试结果 + Map> studentResults = monthlyResults + .getOrDefault(month, Collections.emptyList()) + .stream() + .filter(wrapper -> targetStudentIds.contains(wrapper.getStudentId())) + .collect(Collectors.groupingBy(ExamResultWrapper::getStudentId)); + + // 初始化知识块总分计数器 + Map blockTotalScores = new HashMap<>(); + knowledgeBlockMap.keySet().forEach(block -> blockTotalScores.put(block, 0.0)); + + // 遍历所有目标学生 + targetStudentIds.forEach(studentId -> { + List wrappers = studentResults.getOrDefault(studentId, Collections.emptyList()); + List examResults = wrappers.stream() + .map(ExamResultWrapper::getResult) .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)); + // 1. 计算知识点得分(保留原始逻辑) + Map knowledgeScores = calculateMonthlyKnowledgeScores( + studentId, + examResults, + wrappers.stream().collect(Collectors.toMap( + ExamResultWrapper::getExamId, + ExamResultWrapper::getKnowledge)), + wrappers.stream().collect(Collectors.toMap( + ExamResultWrapper::getExamId, + ExamResultWrapper::getPoints)) + ); + + // 2. 计算每个知识块的客观得分(新增关键逻辑) + Map objectiveScores = new HashMap<>(); + knowledgeBlockMap.forEach((block, knowledges) -> { + // 获取该学生当前月份被测试的知识点 + Set monthlyTestedKnowledges = knowledgeScores.keySet(); + 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)); + objectiveScores.put(block, finalScore); + }); - // 处理主观评分(根据知识块获取对应次数) - Map monthlyCounts = blockMonthlyCounts.getOrDefault(block, new HashMap<>()); - int praiseCount = monthlyCounts.getOrDefault(month, 0); + // 3. 处理主观评价得分 + Map subjectiveScores = calculateSubjectiveScores( + knowledgeBlockMap, + blockMonthlyCounts, + month, + studentId + ); + + // 4. 合并主客观得分(调整权重) + knowledgeBlockMap.keySet().forEach(block -> { + double objectiveScore = objectiveScores.getOrDefault(block, 60.0); + double subjectiveScore = subjectiveScores.getOrDefault(block, 60.0); + double combinedScore = (subjectiveScore * 0.6) + (objectiveScore * 0.4); + blockTotalScores.merge(block, combinedScore, Double::sum); + }); + }); - // 步骤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)); + // 计算班级平均分 + int studentCount = targetStudentIds.size(); + knowledgeBlockMap.keySet().forEach(block -> { + double avg = studentCount > 0 ? + blockTotalScores.get(block) / studentCount : 0.0; + Map scoreEntry = new LinkedHashMap<>(); + scoreEntry.put("name", block); + scoreEntry.put("score", Double.parseDouble(String.format("%.2f", avg))); + scoreList.add(scoreEntry); + }); - // 步骤2:计算主观总分(60分基础 + 转换分) - double subjectiveTotal = 60.0 + convertedScore; - subjectiveTotal = Math.min(subjectiveTotal, 100.0); - subjectiveTotal = Double.parseDouble(String.format("%.2f", subjectiveTotal)); + // 按知识块名称排序(可选) + scoreList.sort(Comparator.comparing(m -> (String) m.get("name"))); - // 步骤3:主客观合并(主观60% + 客观40%) - double combinedScore = (subjectiveTotal * 0.6) + (finalScore * 0.4); - combinedScore = Double.parseDouble(String.format("%.2f", combinedScore)); + monthEntry.put("types", scoreList); + return monthEntry; + }).sorted(Comparator.comparingInt(m -> (Integer) m.get("month"))) + .collect(Collectors.toList()); + } - monthData.put(block, combinedScore); - }); + // 新增方法:计算主观评分转换后的得分 + private Map calculateSubjectiveScores( + Map> knowledgeBlockMap, + Map> blockMonthlyCounts, + int month, + String studentId) { + + Map subjectiveScores = new HashMap<>(); + knowledgeBlockMap.keySet().forEach(block -> { + // 获取该学生本月的评价次数 + int praiseCount = blockMonthlyCounts.getOrDefault(block, new HashMap<>()) + .getOrDefault(month, 0); + + // 次数转分数公式 + final double K = 50.0; + double convertedScore = (praiseCount * 40.0) / (praiseCount + K); + convertedScore = Math.min(convertedScore, 40.0); + + // 主观总分 = 基础60 + 转换分 + double totalScore = 60.0 + convertedScore; + subjectiveScores.put(block, Double.parseDouble(String.format("%.2f", totalScore))); + }); - return monthData; - }) - .sorted(Comparator.comparingInt(m -> (Integer) m.get("month"))) - .collect(Collectors.toList()); + return subjectiveScores; } private Map calculateMonthlyKnowledgeScores(String studentId, @@ -1715,6 +1864,7 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { private ExamClassResult result; private List> knowledge; // 每个题目对应的知识点列表 private List points; + private String studentId; // 新增字段 } // 时间处理工具方法 @@ -1780,17 +1930,145 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { SemesterPeriod current = periods.get("current"); SemesterPeriod last = periods.get("last"); - // 3. 查询并处理数据 - Map currentData = current != null ? - processSemesterData(laborDto, current, request,true) : - Collections.emptyMap(); + // 学生数据处理 + Map studentResult = Collections.emptyMap(); + if (StringUtils.isNotBlank(laborDto.getStudentId())) { + studentResult = processIndividual(laborDto, current, last, request); + } + // 班级数据处理 + Map classResult = Collections.emptyMap(); + if (StringUtils.isNotBlank(laborDto.getClassId())) { + classResult = processClass(laborDto, current, last, request); + } + + // 组合最终结果 + return combineAllResults(laborDto, studentResult, classResult); + } + + private Map combineAllResults(LaborDto dto, + Map studentResult, + Map classResult) { + Map finalResult = new LinkedHashMap<>(); + + // 学生维度数据 + finalResult.put("student", studentResult); + + // 班级维度数据 + if (!classResult.isEmpty()) { + finalResult.put("class", classResult); + } + + return finalResult; + } - Map lastData = last != null ? - processSemesterData(laborDto, last, request,true) : - Collections.emptyMap(); + private Map processIndividual(LaborDto dto, + SemesterPeriod current, + SemesterPeriod last, + HttpServletRequest request) { + // 原有学生处理逻辑 + Map currentData = processSemesterData(dto, current, request, true, false); + Map lastData = processSemesterData(dto, last, request, false,false); + return combineResults(dto, currentData, lastData); + } - // 4. 组合最终结果 - return combineResults(laborDto, currentData, lastData); + private Map processClass(LaborDto dto, + SemesterPeriod current, + SemesterPeriod last, + HttpServletRequest request) { + // 克隆DTO并清除学生ID + LaborDto classDto = new LaborDto(); + classDto.setCode(dto.getCode()); + classDto.setPeriodId(dto.getPeriodId()); + classDto.setClassId(dto.getClassId()); + classDto.setSubjectId(dto.getSubjectId()); + classDto.setSemesterId(dto.getSemesterId()); + classDto.setStartTime(dto.getStartTime()); + classDto.setEndTime(dto.getEndTime()); + classDto.setAcademicYearId(dto.getAcademicYearId()); + classDto.setTargetType(dto.getTargetType()); + classDto.setYear(dto.getYear()); + classDto.setGrade(dto.getGrade()); + classDto.setGradeName(dto.getGradeName()); + classDto.setSource(dto.getSource()); + classDto.setTmdId(dto.getTmdId()); + classDto.setStudentId(null); + + Map currentData = processSemesterData(classDto, current, request, true,true); + Map lastData = processSemesterData(classDto, last, request, false,true); + return combineClassResults(classDto, currentData, lastData); + } + + private Map combineClassResults(LaborDto dto, + Map current, + Map last) { + Map result = new LinkedHashMap<>(); + result.put("classId", dto.getClassId()); + + // 构建班级数据结构(类似学生结构) + List> scores = buildClassScoreBlocks(dto, current, last); + result.put("scores", scores); + + return result; + } + + private List> buildClassScoreBlocks(LaborDto dto, + Map current, + Map last) { + + Map result = new LinkedHashMap<>(); + result.put("classId", dto.getClassId()); + + + Appraise appraise = appraiseRepository.findAppraiseBySchoolIdAndPeriodIdAndCode(dto.getCode(), dto.getPeriodId(), PK.PK_APPRAISE); + // 获取知识块配置 + appraise = evaluationService.buildTree(appraise); + Set knowledgeBlocks = loadKnowledgeBlocks(appraise); + + // 类似学生数据结构,但含班级统计信息 + Appraise finalAppraise = appraise; + return knowledgeBlocks.stream().map(block -> { + Map blockData = new LinkedHashMap<>(); + blockData.put("name", block); + + // 处理当前学期班级平均分(空值安全+格式化) + double currentScore = Optional.ofNullable(current) + .map(c -> (Map) c.get("blockScores")) + .map(scores -> scores.getOrDefault(block, 0.0)) + .orElse(0.0); + currentScore = new BigDecimal(currentScore).setScale(2, RoundingMode.HALF_UP).doubleValue(); + blockData.put("classCurrentScore", currentScore); + + // 处理上学期班级平均分(空值安全+格式化) + double lastScore = Optional.ofNullable(last) + .map(l -> (Map) l.get("blockScores")) + .map(scores -> scores.getOrDefault(block, 0.0)) + .orElse(0.0); + lastScore = new BigDecimal(lastScore).setScale(2, RoundingMode.HALF_UP).doubleValue(); + blockData.put("classLastScore", lastScore); + + // 知识点出现总次数 + ((Map)current.get("knowledgeCounts")).entrySet().stream() + .filter(e -> isBelongToBlock(e.getKey(), block, finalAppraise)) + .forEach(e -> blockData.put(e.getKey(), e.getValue())); + + // 处理知识点次数 + List> knowledgePoints = new ArrayList<>(); + if (current.containsKey("knowledgeCounts")) { + Map counts = (Map) current.get("knowledgeCounts"); + counts.entrySet().stream() + .filter(e -> isBelongToBlock(e.getKey(), block, finalAppraise)) + .forEach(e -> { + Map point = new HashMap<>(); + point.put("name", e.getKey()); + point.put("count", e.getValue() != null ? e.getValue() : 0); + knowledgePoints.add(point); + }); + } + blockData.put("knowledgePoints", knowledgePoints); + + + return blockData; + }).collect(Collectors.toList()); } // 核心学期计算逻辑 @@ -1900,7 +2178,8 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { private Map processSemesterData(LaborDto laborDto, SemesterPeriod semester, HttpServletRequest request, - boolean isCurrent) { + boolean isCurrent, + boolean isClassMod) { Map result = new HashMap<>(); // 1. 设置时间范围查询条件 @@ -1910,44 +2189,70 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { // 2. 获取原始分析数据 Map analysisData = getAnalysis(laborDto, request); - // 3. 提取知识点次数(仅当前学期) - if (isCurrent) { + if (isClassMod) { + // 班级知识点统计 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() + .flatMap(List::stream) + .collect(Collectors.toMap( + a -> (String) a.get("appraiseName"), + a -> 1, + Integer::sum + )); + result.put("knowledgeCounts", knowledgeCounts); + // 班级版块平均分 + Map classBlockAverages = calculateClassAverages(analysisData); + result.put("blockScores", classBlockAverages); + } else { + // 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( - a -> (String) a.get("appraiseName"), - a -> 1, - Integer::sum + b -> (String) b.get("name"), + b -> (Double) b.get("score") ))) .orElse(new HashMap<>()); - result.put("knowledgeCounts", knowledgeCounts); + result.put("classAverage", classAverages); } - - // 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 calculateClassAverages(Map analysisData) { + return ((List>) analysisData.get("scores")).stream() + .map(s -> (Map>) s.get("scores")) + .flatMap(m -> m.entrySet().stream()) + .collect(Collectors.groupingBy( + Map.Entry::getKey, + Collectors.averagingDouble(e -> (Double)e.getValue().get("compositeScore")) + )); + } + private Map extractBlockScores(Map data, String studentId) { return ((List>) data.get("scores")).stream() .filter(s -> studentId.equals(s.get("studentId"))) @@ -2001,14 +2306,22 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { Map classAvg = (Map) currentData.get("classAverage"); blockData.put("classScore", classAvg.getOrDefault(block, 0.0)); - // 知识点次数 + // 处理知识点次数 + List> knowledgePoints = new ArrayList<>(); 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())); + .forEach(e -> { + Map point = new HashMap<>(); + point.put("name", e.getKey()); + point.put("count", e.getValue() != null ? e.getValue() : 0); + knowledgePoints.add(point); + }); } + blockData.put("knowledgePoints", knowledgePoints); + scores.add(blockData); } @@ -2024,7 +2337,7 @@ public List> getStudentMonthlyScores(LaborDto laborDto) { for (AppraiseTreeNode node : appraise.getNodes()) { if ("德育".equals(node.getName())) { for (AppraiseTreeNode secondLevelNode : node.getChildren()) { - knowledgePoints.add(secondLevelNode.getName()); + knowledgePoints.add(secondLevelNode.getName()); } } } diff --git a/src/main/java/cn/teammodel/controller/admin/service/impl/MoralEducationServiceImpl.java b/src/main/java/cn/teammodel/controller/admin/service/impl/MoralEducationServiceImpl.java index c466ed6..f082b23 100644 --- a/src/main/java/cn/teammodel/controller/admin/service/impl/MoralEducationServiceImpl.java +++ b/src/main/java/cn/teammodel/controller/admin/service/impl/MoralEducationServiceImpl.java @@ -30,6 +30,7 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import lombok.AllArgsConstructor; import lombok.Data; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -38,6 +39,12 @@ import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.math.RoundingMode; +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; @@ -522,13 +529,13 @@ public class MoralEducationServiceImpl implements MoralEducationService { Map knowledgeTotalScore = new HashMap<>(); // 计算学生的知识点得分率 - List studentScoreRates = calculateStudentScoreRates(studentId, examResults, knowledgeMap, points, knowledgeTotalScore); + List studentScoreRates = calculateStudentScoreRates(studentId, examResults, knowledgeMap, points, knowledgeTotalScore); // 查找知识点对应的节点及其父节点 Map parentNodeScoreRates = new HashMap<>(); Map parentNodeScoreCount = new HashMap<>(); // 记录每个父节点的知识点数量 - for (LaborEducationServiceImpl.KnowledgeScoreRate scoreRate : studentScoreRates) { + for (MoralEducationServiceImpl.KnowledgeScoreRate scoreRate : studentScoreRates) { AppraiseTreeNode node = findKnowledgeNode(appraise.getNodes(), scoreRate.getKnowledge()); if (node != null) { AppraiseTreeNode parentNode = findParentNode(appraise.getNodes(), node.getId()); @@ -820,7 +827,7 @@ public class MoralEducationServiceImpl implements MoralEducationService { } - private static List calculateStudentScoreRates( + private static List calculateStudentScoreRates( String studentId, List examResults, Map>> knowledgeMap, @@ -839,7 +846,7 @@ public class MoralEducationServiceImpl implements MoralEducationService { knowledgeTotalScore.put(knowledge, 60.0); // 默认60分 } return knowledgeTotalScore.entrySet().stream() - .map(entry -> new LaborEducationServiceImpl.KnowledgeScoreRate(entry.getKey(), entry.getValue())) + .map(entry -> new MoralEducationServiceImpl.KnowledgeScoreRate(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); } @@ -920,7 +927,7 @@ public class MoralEducationServiceImpl implements MoralEducationService { } return knowledgeTotalScore.entrySet().stream() - .map(entry -> new LaborEducationServiceImpl.KnowledgeScoreRate(entry.getKey(), entry.getValue())) + .map(entry -> new MoralEducationServiceImpl.KnowledgeScoreRate(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); } @@ -1321,4 +1328,980 @@ public class MoralEducationServiceImpl implements MoralEducationService { return null; // 未找到父节点 } + //获取每月各个知识库综合得分以及整体得分内容 + public List> getStudentMonthlyScores(LaborDto laborDto,HttpServletRequest request) { + // 1. 获取基础信息 + String schoolId = SecurityUtil.getLoginUser().getSchoolId(); + String studentId = laborDto.getStudentId(); + List classStudentIds = Collections.emptyList(); + // 2. 获取相关开课记录 + List records = getLessonRecords(laborDto, schoolId); + List res; + if (laborDto.getClassId() == null) { + res = appraiseRecordRepository.getStudentRecords( + String.format(PK.PK_APPRAISE_RECORD, schoolId), + laborDto.getStudentId(), + laborDto.getStartTime(), + laborDto.getEndTime(), + "劳育" + ); + }else { + //当班级ID 存在时 获取该班级下所有名单即学生Id + List classIds = Collections.singletonList(laborDto.getClassId()); + GroupDto groupDto = new GroupDto(); + groupDto.setIds(classIds); + groupDto.setSchoolId(laborDto.getCode()); + String url = environment.getProperty("ies.server-url-group"); + Map groupId = GroupUtil.getGroupId(groupDto, new GroupUtil(environment), request, url); + List rGroupList = new ArrayList<>(); + List rMembers = new ArrayList<>(); + for (Map.Entry entry : groupId.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (key.equals("groups")) { + String jsonGroups = JSON.toJSONString(value); + rGroupList = JSON.parseObject(jsonGroups, new TypeReference>() {}); + } + } + rMembers = rGroupList.stream() + .flatMap(rGroupList1 -> rGroupList1.getMembers().stream()) + .filter(rMember -> rMember.getClassId().equals(laborDto.getClassId())) + .collect(Collectors.toList()); + classStudentIds = rMembers.stream().map(RMember::getId).collect(Collectors.toList()); + /*String className = rGroupList.stream() + .filter(rGroupList1 -> rGroupList1.getId().equals(classId)) + .findFirst() + .map(RGroupList::getName) + .orElse("未知班级"); // 如果未找到则返回默认值 "未知班级"*/ + res = appraiseRecordRepository.latestRecords( + String.format(PK.PK_APPRAISE_RECORD, schoolId), + laborDto.getAcademicYearId(), + laborDto.getStartTime(), + laborDto.getEndTime(), + "劳育", + classIds + ); + } + + // 3. 获取考试及知识点映射 + Map>> examKnowledgeMap = new HashMap<>(); + Map> points = new HashMap<>(); + List exams = getExamsWithKnowledge(laborDto, records, examKnowledgeMap, points); + + // 4. 获取考试结果并按月份分组 + List targetStudents = laborDto.getClassId() != null ? + classStudentIds : Collections.singletonList(studentId); + + Map> monthlyResults = getMonthlyExamResults( + schoolId, laborDto.getClassId(), exams, records, + examKnowledgeMap, points, targetStudents // 传入目标学生列表 + ); + + // 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 -> laborDto.getClassId() != null ? + targetStudents.contains(vo.getTargetId()) : + 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. 处理每月数据,传入主观评价统计结果 + + return processMonthlyData( + targetStudents, + monthlyResults, + knowledgeBlockMap, + getMonthsBetween(laborDto.getStartTime(), laborDto.getEndTime()), + blockMonthlyCounts + ); + } + + + // 新增方法:添加综合得分 + 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 studentIds) { // 新增studentIds参数 + + Map> monthlyResults = new HashMap<>(); + + if (classId == null) { + exams.forEach(exam -> { + List results = examClassResultRepository.findByStudentId( + String.format(PK.CLASS_RESULT, schoolId), studentIds.get(0), 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()); + + // 处理所有相关学生 + result.getStudentIds().stream() + .filter(studentIds::contains) // 仅处理班级学生 + .forEach(studentId -> { + monthlyResults.computeIfAbsent(month, k -> new ArrayList<>()) + .add(new MoralEducationServiceImpl.ExamResultWrapper( + exam.getId(), + result, + knowledge, + point, + studentId // 新增学生ID字段 + )); + }); + }); + }); + }); + }else { + 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()); + + // 处理所有相关学生 + result.getStudentIds().stream() + .filter(studentIds::contains) // 仅处理班级学生 + .forEach(studentId -> { + monthlyResults.computeIfAbsent(month, k -> new ArrayList<>()) + .add(new MoralEducationServiceImpl.ExamResultWrapper( + exam.getId(), + result, + knowledge, + point, + studentId // 新增学生ID字段 + )); + }); + }); + }); + }); + + } + + return monthlyResults; + } + + private List> processMonthlyData( + List targetStudentIds, + Map> monthlyResults, + Map> knowledgeBlockMap, + Set months, + Map> blockMonthlyCounts) { + + return months.stream().map(month -> { + Map monthEntry = new LinkedHashMap<>(); + monthEntry.put("month", month); + List> scoreList = new ArrayList<>(); + + // 按学生分组考试结果 + Map> studentResults = monthlyResults + .getOrDefault(month, Collections.emptyList()) + .stream() + .filter(wrapper -> targetStudentIds.contains(wrapper.getStudentId())) + .collect(Collectors.groupingBy(MoralEducationServiceImpl.ExamResultWrapper::getStudentId)); + + // 初始化知识块总分计数器 + Map blockTotalScores = new HashMap<>(); + knowledgeBlockMap.keySet().forEach(block -> blockTotalScores.put(block, 0.0)); + + // 遍历所有目标学生 + targetStudentIds.forEach(studentId -> { + List wrappers = studentResults.getOrDefault(studentId, Collections.emptyList()); + List examResults = wrappers.stream() + .map(MoralEducationServiceImpl.ExamResultWrapper::getResult) + .collect(Collectors.toList()); + + // 1. 计算知识点得分(保留原始逻辑) + Map knowledgeScores = calculateMonthlyKnowledgeScores( + studentId, + examResults, + wrappers.stream().collect(Collectors.toMap( + MoralEducationServiceImpl.ExamResultWrapper::getExamId, + MoralEducationServiceImpl.ExamResultWrapper::getKnowledge)), + wrappers.stream().collect(Collectors.toMap( + MoralEducationServiceImpl.ExamResultWrapper::getExamId, + MoralEducationServiceImpl.ExamResultWrapper::getPoints)) + ); + + // 2. 计算每个知识块的客观得分(新增关键逻辑) + Map objectiveScores = new HashMap<>(); + knowledgeBlockMap.forEach((block, knowledges) -> { + // 获取该学生当前月份被测试的知识点 + Set monthlyTestedKnowledges = knowledgeScores.keySet(); + 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)); + objectiveScores.put(block, finalScore); + }); + + // 3. 处理主观评价得分 + Map subjectiveScores = calculateSubjectiveScores( + knowledgeBlockMap, + blockMonthlyCounts, + month, + studentId + ); + + // 4. 合并主客观得分(调整权重) + knowledgeBlockMap.keySet().forEach(block -> { + double objectiveScore = objectiveScores.getOrDefault(block, 60.0); + double subjectiveScore = subjectiveScores.getOrDefault(block, 60.0); + double combinedScore = (subjectiveScore * 0.6) + (objectiveScore * 0.4); + blockTotalScores.merge(block, combinedScore, Double::sum); + }); + }); + + // 计算班级平均分 + int studentCount = targetStudentIds.size(); + knowledgeBlockMap.keySet().forEach(block -> { + double avg = studentCount > 0 ? + blockTotalScores.get(block) / studentCount : 0.0; + Map scoreEntry = new LinkedHashMap<>(); + scoreEntry.put("name", block); + scoreEntry.put("score", Double.parseDouble(String.format("%.2f", avg))); + scoreList.add(scoreEntry); + }); + + // 按知识块名称排序(可选) + scoreList.sort(Comparator.comparing(m -> (String) m.get("name"))); + + monthEntry.put("types", scoreList); + return monthEntry; + }).sorted(Comparator.comparingInt(m -> (Integer) m.get("month"))) + .collect(Collectors.toList()); + } + + // 新增方法:计算主观评分转换后的得分 + private Map calculateSubjectiveScores( + Map> knowledgeBlockMap, + Map> blockMonthlyCounts, + int month, + String studentId) { + + Map subjectiveScores = new HashMap<>(); + knowledgeBlockMap.keySet().forEach(block -> { + // 获取该学生本月的评价次数 + int praiseCount = blockMonthlyCounts.getOrDefault(block, new HashMap<>()) + .getOrDefault(month, 0); + + // 次数转分数公式 + final double K = 50.0; + double convertedScore = (praiseCount * 40.0) / (praiseCount + K); + convertedScore = Math.min(convertedScore, 40.0); + + // 主观总分 = 基础60 + 转换分 + double totalScore = 60.0 + convertedScore; + subjectiveScores.put(block, Double.parseDouble(String.format("%.2f", totalScore))); + }); + + return subjectiveScores; + } + + 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 String studentId; // 新增字段 + } + + // 时间处理工具方法 + 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 MoralEducationServiceImpl.SemesterConfig(semester.getName(), semester.getStart(), semester.getMonth(), semester.getDay(), semester.getId())); + } + // 2. 计算学期时间范围 + Map periods = calculateSemesters(configs); + MoralEducationServiceImpl.SemesterPeriod current = periods.get("current"); + MoralEducationServiceImpl.SemesterPeriod last = periods.get("last"); + + // 学生数据处理 + Map studentResult = Collections.emptyMap(); + if (StringUtils.isNotBlank(laborDto.getStudentId())) { + studentResult = processIndividual(laborDto, current, last, request); + } + // 班级数据处理 + Map classResult = Collections.emptyMap(); + if (StringUtils.isNotBlank(laborDto.getClassId())) { + classResult = processClass(laborDto, current, last, request); + } + + // 组合最终结果 + return combineAllResults(laborDto, studentResult, classResult); + } + + private Map combineAllResults(LaborDto dto, + Map studentResult, + Map classResult) { + Map finalResult = new LinkedHashMap<>(); + + // 学生维度数据 + finalResult.put("student", studentResult); + + // 班级维度数据 + if (!classResult.isEmpty()) { + finalResult.put("class", classResult); + } + + return finalResult; + } + + private Map processIndividual(LaborDto dto, + MoralEducationServiceImpl.SemesterPeriod current, + MoralEducationServiceImpl.SemesterPeriod last, + HttpServletRequest request) { + // 原有学生处理逻辑 + Map currentData = processSemesterData(dto, current, request, true, false); + Map lastData = processSemesterData(dto, last, request, false,false); + return combineResults(dto, currentData, lastData); + } + + private Map processClass(LaborDto dto, + MoralEducationServiceImpl.SemesterPeriod current, + MoralEducationServiceImpl.SemesterPeriod last, + HttpServletRequest request) { + // 克隆DTO并清除学生ID + LaborDto classDto = new LaborDto(); + classDto.setCode(dto.getCode()); + classDto.setPeriodId(dto.getPeriodId()); + classDto.setClassId(dto.getClassId()); + classDto.setSubjectId(dto.getSubjectId()); + classDto.setSemesterId(dto.getSemesterId()); + classDto.setStartTime(dto.getStartTime()); + classDto.setEndTime(dto.getEndTime()); + classDto.setAcademicYearId(dto.getAcademicYearId()); + classDto.setTargetType(dto.getTargetType()); + classDto.setYear(dto.getYear()); + classDto.setGrade(dto.getGrade()); + classDto.setGradeName(dto.getGradeName()); + classDto.setSource(dto.getSource()); + classDto.setTmdId(dto.getTmdId()); + classDto.setStudentId(null); + + Map currentData = processSemesterData(classDto, current, request, true,true); + Map lastData = processSemesterData(classDto, last, request, false,true); + return combineClassResults(classDto, currentData, lastData); + } + + private Map combineClassResults(LaborDto dto, + Map current, + Map last) { + Map result = new LinkedHashMap<>(); + result.put("classId", dto.getClassId()); + + // 构建班级数据结构(类似学生结构) + List> scores = buildClassScoreBlocks(dto, current, last); + result.put("scores", scores); + + return result; + } + + private List> buildClassScoreBlocks(LaborDto dto, + Map current, + Map last) { + + Map result = new LinkedHashMap<>(); + result.put("classId", dto.getClassId()); + + + Appraise appraise = appraiseRepository.findAppraiseBySchoolIdAndPeriodIdAndCode(dto.getCode(), dto.getPeriodId(), PK.PK_APPRAISE); + // 获取知识块配置 + appraise = evaluationService.buildTree(appraise); + Set knowledgeBlocks = loadKnowledgeBlocks(appraise); + + // 类似学生数据结构,但含班级统计信息 + Appraise finalAppraise = appraise; + return knowledgeBlocks.stream().map(block -> { + Map blockData = new LinkedHashMap<>(); + blockData.put("name", block); + + // 处理当前学期班级平均分(空值安全+格式化) + double currentScore = Optional.ofNullable(current) + .map(c -> (Map) c.get("blockScores")) + .map(scores -> scores.getOrDefault(block, 0.0)) + .orElse(0.0); + currentScore = new BigDecimal(currentScore).setScale(2, RoundingMode.HALF_UP).doubleValue(); + blockData.put("classCurrentScore", currentScore); + + // 处理上学期班级平均分(空值安全+格式化) + double lastScore = Optional.ofNullable(last) + .map(l -> (Map) l.get("blockScores")) + .map(scores -> scores.getOrDefault(block, 0.0)) + .orElse(0.0); + lastScore = new BigDecimal(lastScore).setScale(2, RoundingMode.HALF_UP).doubleValue(); + blockData.put("classLastScore", lastScore); + + // 知识点出现总次数 + ((Map)current.get("knowledgeCounts")).entrySet().stream() + .filter(e -> isBelongToBlock(e.getKey(), block, finalAppraise)) + .forEach(e -> blockData.put(e.getKey(), e.getValue())); + + // 处理知识点次数 + List> knowledgePoints = new ArrayList<>(); + if (current.containsKey("knowledgeCounts")) { + Map counts = (Map) current.get("knowledgeCounts"); + counts.entrySet().stream() + .filter(e -> isBelongToBlock(e.getKey(), block, finalAppraise)) + .forEach(e -> { + Map point = new HashMap<>(); + point.put("name", e.getKey()); + point.put("count", e.getValue() != null ? e.getValue() : 0); + knowledgePoints.add(point); + }); + } + blockData.put("knowledgePoints", knowledgePoints); + + + return blockData; + }).collect(Collectors.toList()); + } + + // 核心学期计算逻辑 + private Map calculateSemesters(List semesters) { + LocalDate now = LocalDate.now(); + int currentYear = now.getYear(); + + // 1. 确定学年起始学期 + MoralEducationServiceImpl.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. 确定当前学期 + MoralEducationServiceImpl.SemesterPeriod current = currentYearSemesters.stream() + .filter(sp -> !now.isBefore(sp.startDate) && !now.isAfter(sp.endDate)) + .findFirst() + .orElse(null); + + // 5. 确定上学期 + MoralEducationServiceImpl.SemesterPeriod last = findLastSemester(current, semesters, academicYear, startSemester); + + return new HashMap() {{ + put("current", current); + put("last", last); + }}; + } + + // 增强学术年度计算方法 + private int calculateAcademicYear(MoralEducationServiceImpl.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, + MoralEducationServiceImpl.SemesterConfig startSemester) { + List periods = new ArrayList<>(); + + // 1. 生成各学期开始日期 + for (MoralEducationServiceImpl.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 MoralEducationServiceImpl.SemesterPeriod(sc.name, startDate, null)); + } + + periods.sort(Comparator.comparing(MoralEducationServiceImpl.SemesterPeriod::getStartDate)); + // 2. 计算结束日期 + for (int i = 0; i < periods.size(); i++) { + MoralEducationServiceImpl.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 MoralEducationServiceImpl.SemesterPeriod findLastSemester(MoralEducationServiceImpl.SemesterPeriod current, + List semesters, + int academicYear, + MoralEducationServiceImpl.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, + MoralEducationServiceImpl.SemesterPeriod semester, + HttpServletRequest request, + boolean isCurrent, + boolean isClassMod) { + Map result = new HashMap<>(); + + // 1. 设置时间范围查询条件 + laborDto.setStartTime(convertToTimestamp(semester.startDate)); + laborDto.setEndTime(convertToTimestamp(semester.endDate)); + + // 2. 获取原始分析数据 + Map analysisData = getAnalysis(laborDto, request); + + if (isClassMod) { + // 班级知识点统计 + List> scores = (List>) analysisData.get("scores"); + Map knowledgeCounts = scores.stream() + .map(s -> (List>) s.get("appraises")) + .flatMap(List::stream) + .collect(Collectors.toMap( + a -> (String) a.get("appraiseName"), + a -> 1, + Integer::sum + )); + result.put("knowledgeCounts", knowledgeCounts); + // 班级版块平均分 + Map classBlockAverages = calculateClassAverages(analysisData); + result.put("blockScores", classBlockAverages); + } else { + // 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 calculateClassAverages(Map analysisData) { + return ((List>) analysisData.get("scores")).stream() + .map(s -> (Map>) s.get("scores")) + .flatMap(m -> m.entrySet().stream()) + .collect(Collectors.groupingBy( + Map.Entry::getKey, + Collectors.averagingDouble(e -> (Double)e.getValue().get("compositeScore")) + )); + } + + 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)); + + // 处理知识点次数 + List> knowledgePoints = new ArrayList<>(); + 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 -> { + Map point = new HashMap<>(); + point.put("name", e.getKey()); + point.put("count", e.getValue() != null ? e.getValue() : 0); + knowledgePoints.add(point); + }); + } + blockData.put("knowledgePoints", knowledgePoints); + + + 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 a322d6a..5fc5b5e 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 @@ -1,10 +1,9 @@ package cn.teammodel.model.dto.admin.labor; import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; import lombok.Data; -import java.util.List; - @Data public class LaborDto { @ApiModelProperty("学校编码") @@ -26,4 +25,5 @@ public class LaborDto { private String classId; private String source = "1"; private String studentId; + private String targetType; // "student" 或 "class" } diff --git a/src/main/java/cn/teammodel/repository/ExamClassResultRepository.java b/src/main/java/cn/teammodel/repository/ExamClassResultRepository.java index 4b90800..a7b54a4 100644 --- a/src/main/java/cn/teammodel/repository/ExamClassResultRepository.java +++ b/src/main/java/cn/teammodel/repository/ExamClassResultRepository.java @@ -13,4 +13,7 @@ public interface ExamClassResultRepository extends CosmosRepository 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); + @Query("select * from ExamClassResult as s where s.code = @code and array_contains(s.studentIds ,@studentId) and s.examId = @examId") + List findByStudentId(String code, String studentId,String examId); + }