Browse Source

新增科目效度

wangliang 4 hours ago
parent
commit
d9930cb2c6

+ 82 - 0
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/bean/dto/query/ValidAnswerDetailSumScoreDto.java

@@ -0,0 +1,82 @@
+package com.qmth.teachcloud.report.business.bean.dto.query;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.teachcloud.common.contant.SystemConstant;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Objects;
+
+/**
+ * @Description: 有效的作答记录满分和
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2025/7/17
+ */
+public class ValidAnswerDetailSumScoreDto implements Serializable {
+
+    @ApiModelProperty(value = "t_b_exam_record考试记录id")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long examRecordId;
+
+    @ApiModelProperty(value = "t_a_exam_course_record考试记录id")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long taExamRecordId;
+
+    @ApiModelProperty(value = "试卷类型")
+    private String paperType;
+
+    @ApiModelProperty(value = "满分")
+    private BigDecimal sumScore;
+
+    @ApiModelProperty(value = "排名")
+    private int rank;
+
+
+    public Long getExamRecordId() {
+        return examRecordId;
+    }
+
+    public void setExamRecordId(Long examRecordId) {
+        this.examRecordId = examRecordId;
+    }
+
+    public Long getTaExamRecordId() {
+        return taExamRecordId;
+    }
+
+    public void setTaExamRecordId(Long taExamRecordId) {
+        this.taExamRecordId = taExamRecordId;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public BigDecimal getSumScore() {
+        if (Objects.nonNull(sumScore)) {
+            return sumScore.setScale(SystemConstant.CALCULATE_SCALE, BigDecimal.ROUND_HALF_UP);
+        } else {
+            return sumScore;
+        }
+    }
+
+    public void setSumScore(BigDecimal sumScore) {
+        this.sumScore = sumScore;
+    }
+
+    public int getRank() {
+        return rank;
+    }
+
+    public void setRank(int rank) {
+        this.rank = rank;
+    }
+}

+ 11 - 0
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/bean/result/TeachCourseSurveyResult.java

@@ -40,6 +40,17 @@ public class TeachCourseSurveyResult {
     @ApiModelProperty(value = "考察学院集合")
     private String inspectCollegeNames;
 
+    @ApiModelProperty(value = "科目效度")
+    private BigDecimal courseValidity = new BigDecimal(0);
+
+    public BigDecimal getCourseValidity() {
+        return courseValidity;
+    }
+
+    public void setCourseValidity(BigDecimal courseValidity) {
+        this.courseValidity = courseValidity;
+    }
+
     public Long getExamTime() {
         return examTime;
     }

+ 11 - 0
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/entity/TAExamCourse.java

@@ -221,6 +221,17 @@ public class TAExamCourse implements Serializable {
     @TableField(exist = false)
     private Integer currentNotPassCount;
 
+    @ApiModelProperty(value = "科目效度")
+    private BigDecimal courseValidity = new BigDecimal(0);
+
+    public BigDecimal getCourseValidity() {
+        return courseValidity;
+    }
+
+    public void setCourseValidity(BigDecimal courseValidity) {
+        this.courseValidity = courseValidity;
+    }
+
     public String getCurrentNotPassRate() {
         return currentNotPassRate;
     }

+ 2 - 1
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/enums/ValidityEnum.java

@@ -4,7 +4,8 @@ package com.qmth.teachcloud.report.business.enums;
 public enum ValidityEnum {
     VALIDITY_FOR_PERCENT_GRADE(73,27,"百分等级大于等于73的学生和后百分等级小于等于27的学生作为效度参考标准"),
     VALIDITY_FOR_RANK(0.27,0.73,"排名在前27/100的学生和排名在后27/100成绩作为效度参考标准"),
-    ;
+    COURSE_VALIDITY_FOR_RANK(0.33,0.67,"排名在前1/3的学生和排名在后1/3成绩作为满分效度参考标准");
+
     private final double topLimitPercent;
     private final double lowLimitPercent;
     private final String desc;

+ 13 - 2
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/mapper/TBAnswerMapper.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.qmth.teachcloud.report.business.bean.dto.AnswerDetailBean;
 import com.qmth.teachcloud.report.business.bean.dto.query.BasicAnswerDto;
 import com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailDto;
+import com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailSumScoreDto;
 import com.qmth.teachcloud.report.business.bean.result.TBAnswerResult;
 import com.qmth.teachcloud.report.business.entity.TBAnswer;
 import org.apache.ibatis.annotations.Param;
@@ -50,9 +51,19 @@ public interface TBAnswerMapper extends BaseMapper<TBAnswer> {
     /**
      * 查找实际参加考试的详细作答记录
      *
-     * @param examId  考试id
-     * @param courseCode  课程编号
+     * @param examId     考试id
+     * @param courseCode 课程编号
      * @return 键值对
      */
     LinkedList<AnswerDetailBean> findValidAnswerDetailWithPap(@Param("examId") Long examId, @Param("courseCode") String courseCode);
+
+    /**
+     * 查找试卷结构满分效度数据
+     *
+     * @param examId
+     * @param courseCode
+     * @param paperId
+     * @return
+     */
+    List<ValidAnswerDetailSumScoreDto> findValidSumScore(@Param("examId") Long examId, @Param("courseCode") String courseCode, @Param("paperId") Long paperId);
 }

+ 13 - 2
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/service/TBAnswerService.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.qmth.teachcloud.report.business.bean.dto.AnswerDetailBean;
 import com.qmth.teachcloud.report.business.bean.dto.query.BasicAnswerDto;
 import com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailDto;
+import com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailSumScoreDto;
 import com.qmth.teachcloud.report.business.bean.result.TBAnswerResult;
 import com.qmth.teachcloud.report.business.entity.TBAnswer;
 
@@ -49,9 +50,19 @@ public interface TBAnswerService extends IService<TBAnswer> {
     /**
      * 查找实际参加考试的详细作答记录
      *
-     * @param examId  考试id
-     * @param courseCode  课程编号
+     * @param examId     考试id
+     * @param courseCode 课程编号
      * @return 键值对
      */
     LinkedList<AnswerDetailBean> findValidAnswerDetailWithPap(Long examId, String courseCode);
+
+    /**
+     * 查找试卷结构满分效度数据
+     *
+     * @param examId
+     * @param courseCode
+     * @param paperId
+     * @return
+     */
+    List<ValidAnswerDetailSumScoreDto> findValid(Long examId, String courseCode, Long paperId);
 }

+ 129 - 29
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/service/impl/AnalyzeForReportServiceImpl.java

@@ -3,7 +3,6 @@ package com.qmth.teachcloud.report.business.service.impl;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.qmth.teachcloud.common.contant.SpringContextHolder;
 import com.qmth.teachcloud.common.contant.SystemConstant;
-import com.qmth.teachcloud.common.entity.BasicSemester;
 import com.qmth.teachcloud.common.entity.SysOrg;
 import com.qmth.teachcloud.common.enums.AssignEnum;
 import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
@@ -12,6 +11,7 @@ import com.qmth.teachcloud.report.business.bean.dto.AnswerDetailBean;
 import com.qmth.teachcloud.report.business.bean.dto.query.BasicAnswerDto;
 import com.qmth.teachcloud.report.business.bean.dto.query.BasicExamRecordDto;
 import com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailDto;
+import com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailSumScoreDto;
 import com.qmth.teachcloud.report.business.entity.*;
 import com.qmth.teachcloud.report.business.enums.*;
 import com.qmth.teachcloud.report.business.service.*;
@@ -21,6 +21,7 @@ import com.qmth.teachcloud.report.business.utils.MathUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 import javax.annotation.Resource;
 import java.lang.reflect.Field;
@@ -293,7 +294,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
             taExamCourse.setExamId(examId);
             taExamCourse.setExamName(tbExam.getExamName());
             taExamCourse.setCourseCode(effectiveCourseCode);
-            taExamCourse.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+            taExamCourse.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
             taExamCourse.setSchoolId(tbExam.getSchoolId());
             taExamCourse.setTeachCollegeId(teachCollegeId);
             taExamCourse.setTeachCollegeName(teachCollegeName);
@@ -454,7 +455,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                 taExamCourseCollegeInspect.setExamId(examId);
                 taExamCourseCollegeInspect.setSchoolId(tbExamService.getById(examId).getSchoolId());
                 taExamCourseCollegeInspect.setCourseCode(effectiveCourseCode);
-                taExamCourseCollegeInspect.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                taExamCourseCollegeInspect.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                 taExamCourseCollegeInspect.setCollegeId(collegeId);
                 taExamCourseCollegeInspect.setMaxScore(BigDecimal.valueOf(maxScore));
                 taExamCourseCollegeInspect.setMinScore(BigDecimal.valueOf(minScore));
@@ -535,7 +536,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                 taExamCourseClazz.setExamId(examId);
                 taExamCourseClazz.setSchoolId(tbExamService.getById(examId).getSchoolId());
                 taExamCourseClazz.setCourseCode(effectiveCourseCode);
-                taExamCourseClazz.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                taExamCourseClazz.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                 taExamCourseClazz.setClazzId(clazzId);
                 taExamCourseClazz.setMaxScore(BigDecimal.valueOf(maxScore));
                 taExamCourseClazz.setMinScore(BigDecimal.valueOf(minScore));
@@ -674,7 +675,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                         taExamCourseRecordDio.setStudentId(taExamCourseRecord.getStudentId());
                         taExamCourseRecordDio.setStudentCode(taExamCourseRecord.getStudentCode());
                         taExamCourseRecordDio.setCourseCode(effectiveCourseCode);
-                        taExamCourseRecordDio.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                        taExamCourseRecordDio.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                         String[] dimCodeArr = s.split(SystemConstant.HYPHEN);
                         if (dimCodeArr.length != 2) {
                             throw ExceptionResultEnum.ERROR.exception("获得的考查点标识不符合标准,标准为 type-dimensionCode,结果为:  " + s);
@@ -727,7 +728,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                         taExamCourseRecordMod.setExamId(taExamCourseRecord.getExamId());
                         taExamCourseRecordMod.setSchoolId(tbExamService.getById(examId).getSchoolId());
                         taExamCourseRecordMod.setCourseCode(effectiveCourseCode);
-                        taExamCourseRecordMod.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                        taExamCourseRecordMod.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                         taExamCourseRecordMod.setModuleType(s);
                         taExamCourseRecordMod.setTotalScore(BigDecimal.valueOf(studentScore));
                         taExamCourseRecordMod.setScoreRate(BigDecimal.valueOf(rate));
@@ -771,7 +772,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                     taExamCourseDio.setExamId(examId);
                     taExamCourseDio.setSchoolId(tbExamService.getById(examId).getSchoolId());
                     taExamCourseDio.setCourseCode(effectiveCourseCode);
-                    taExamCourseDio.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                    taExamCourseDio.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                     taExamCourseDio.setPaperId(paperId);
                     taExamCourseDio.setDimensionType(dimensionType);
                     taExamCourseDio.setDimensionCode(dimensionCode);
@@ -885,6 +886,25 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
         return "'t_a_exam_course_teacher_college_dio'表构建完成 ";
     }
 
+    /**
+     * 极端分组法‌(区分度针对每小题)
+     * 该方法通过比较高分组与低分组的得分差异计算区分度,具体步骤如下:‌‌
+     * 1‌‌、将考生总分从高到低排序,取前27%作为高分组(P₁),后27%作为低分组(P₂);
+     * 2、计算高分组和低分组在某一题目上的平均分;
+     * <p>
+     * 应用公式:‌D=(P₁−P₂)/小题满分值‌。
+     * <p>
+     * 极端分组法‌(效度针对全卷)
+     * 该方法通过比较高分组与低分组的得分差异计算区分度,具体步骤如下:‌‌
+     * 1‌‌、将考生总分从高到低排序,取前1/3作为高分组(P₁),后1/3作为低分组(P₂);
+     * 2、计算高分组和低分组在某一题目上的平均分;
+     * <p>
+     * 应用公式:‌D=(P₁−P₂)/全卷满分值‌。
+     *
+     * @param examId     考试id
+     * @param courseCode 课程编号
+     * @return
+     */
     @Transactional(rollbackFor = Exception.class)
     @Override
     public String buildAnalyzePaperStruct(Long examId, String courseCode) {
@@ -912,11 +932,14 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                 if (tbPaperStructList.size() == 0) {
                     throw ExceptionResultEnum.ERROR.exception("试卷结构数据异常");
                 }
+                //新增满分效度
+                this.calculateSumScoreValidity(examId, effectiveCourseCode, paperId, tbPaper.getTotalScore());
                 for (TBPaperStruct paperStruct : tbPaperStructList) {
                     String numberType = paperStruct.getNumberType();
                     String mainNumber = paperStruct.getBigQuestionNumber();
                     String subNumber = paperStruct.getSmallQuestionNumber();
 
+                    //过滤题目类型-大题-小题的答题明细
                     List<ValidAnswerDetailDto> oneQuestionAnswerDetailList = answerDetailDtoList
                             .stream().filter(e -> paperType.equals(e.getPaperType()) &&
                                     numberType.equals(e.getNumberType()) &&
@@ -925,6 +948,8 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                     if (oneQuestionAnswerDetailList.size() == 0) {
                         System.out.println("异常");
                     }
+
+                    //判断是否有小题满分的
                     BigDecimal fullScore = paperStruct.getFullScore();
                     PaperStructJudgeEnum paperStructJudgeEnum;
                     if (oneQuestionAnswerDetailList.stream().anyMatch(e -> fullScore.compareTo(e.getScore()) != 0)) {
@@ -933,14 +958,32 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                         paperStructJudgeEnum = PaperStructJudgeEnum.ALL_CORRECT;
                     }
 
+                    /**
+                     * 数据统计的收集器,主要用于对流中的数值进行汇总统计。它返回一个包含计数、总和、平均值、最大值和最小值的汇总对象
+                     * 计数‌:统计流中元素的数量
+                     * ‌总和‌:计算所有数值元素的累加和
+                     * ‌平均值‌:计算数值元素的平均值
+                     * ‌最大值‌:找出流中的最大数值
+                     * ‌最小值‌:找出流中的最小数值
+                     */
                     DoubleSummaryStatistics descriptiveStatistics = oneQuestionAnswerDetailList.stream()
                             .collect(Collectors.summarizingDouble(e -> e.getScore().doubleValue()));
 
-                    double scoreAvg = descriptiveStatistics.getAverage();
+                    double scoreAvg = descriptiveStatistics.getAverage();//平均分
                     BigDecimal standardScoreRate = BigDecimal.valueOf(scoreAvg).divide(fullScore, 4, RoundingMode.HALF_UP);
                     BigDecimal scoreRate = standardScoreRate.setScale(1, RoundingMode.HALF_UP);
+
+                    /**
+                     * 计算小题难易度
+                     * A	[0,0.3)	    难	    0.0	0.3
+                     * B	[0.3,0.7]	中等难度	0.3	0.7
+                     * C	(0.7,1]	    容易	    0.7	1.0
+                     */
                     String difficult = this.analyzeDifficult(examId, effectiveCourseCode, scoreRate.doubleValue());
 
+                    /**
+                     *
+                     */
                     double validity = this.calculateValidity(oneQuestionAnswerDetailList, fullScore.doubleValue());
 
                     TAPaperStruct taPaperStruct = new TAPaperStruct();
@@ -949,7 +992,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                     taPaperStruct.setExamId(examId);
                     taPaperStruct.setSchoolId(tbExamService.getById(examId).getSchoolId());
                     taPaperStruct.setCourseCode(effectiveCourseCode);
-                    taPaperStruct.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                    taPaperStruct.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                     taPaperStruct.setPaperId(paperId);
                     taPaperStruct.setPaperType(paperType);
                     taPaperStruct.setQuestionName(paperStruct.getQuestionName());
@@ -976,6 +1019,63 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
         return " 't_a_paper_struct'表构建成功 ";
     }
 
+    /**
+     * 计算科目满分效度
+     *
+     * @param examId
+     * @param courseCode
+     * @param paperId
+     * @param fullScore
+     */
+    public void calculateSumScoreValidity(Long examId, String courseCode, Long paperId, BigDecimal fullScore) {
+        List<ValidAnswerDetailSumScoreDto> validAnswerDetailSumScoreDtoList = tbAnswerService.findValid(examId, courseCode, paperId);
+        if (!CollectionUtils.isEmpty(validAnswerDetailSumScoreDtoList)) {
+            int rank = 0;//排名
+            int number = 0;//序号
+            double value = 10000;
+            double courseSumScoreValidity = 0D;//科目满分效度
+            for (ValidAnswerDetailSumScoreDto temp : validAnswerDetailSumScoreDtoList) {
+                double compareValue = temp.getSumScore().doubleValue();
+                number++;
+                if (compareValue < value) {
+                    rank = number;
+                    value = compareValue;
+                }
+                temp.setRank(rank);
+            }
+            if (validAnswerDetailSumScoreDtoList.size() == 0) {
+                System.out.println("异常");
+            }
+            double endRank = validAnswerDetailSumScoreDtoList.get(validAnswerDetailSumScoreDtoList.size() - 1).getRank();
+            double topRank = ValidityEnum.COURSE_VALIDITY_FOR_RANK.getTopLimitPercent() * endRank;
+            double lowRank = ValidityEnum.COURSE_VALIDITY_FOR_RANK.getLowLimitPercent() * endRank;
+
+            //计算科目效度
+            double topScopeAvgScoreRate = validAnswerDetailSumScoreDtoList.stream()
+                    .filter(e -> topRank >= e.getRank())
+                    .collect(Collectors.summarizingDouble(e -> e.getSumScore().doubleValue()))
+                    .getAverage() / fullScore.doubleValue(); // 优秀部分统计有效区间平均得分率
+
+            double lowScopeAvgScoreRate = validAnswerDetailSumScoreDtoList.stream()
+                    .filter(e -> lowRank <= e.getRank())
+                    .collect(Collectors.summarizingDouble(e -> e.getSumScore().doubleValue()))
+                    .getAverage() / fullScore.doubleValue(); // 较差部分统计有效区间平均得分率
+
+            courseSumScoreValidity = topScopeAvgScoreRate - lowScopeAvgScoreRate;
+
+            //科目效度值大于则更新
+            if (courseSumScoreValidity > 0D) {
+                QueryWrapper<TAExamCourse> taExamCourseQueryWrapper = new QueryWrapper<>();
+                taExamCourseQueryWrapper.lambda().eq(TAExamCourse::getExamId, examId).eq(TAExamCourse::getCourseCode, courseCode);
+                TAExamCourse taExamCourse = taExamCourseService.getOne(taExamCourseQueryWrapper);
+                if (Objects.nonNull(taExamCourse)) {
+                    taExamCourse.setCourseValidity(new BigDecimal(courseSumScoreValidity).setScale(2, BigDecimal.ROUND_HALF_UP));
+                    taExamCourseService.updateById(taExamCourse);
+                }
+            }
+        }
+    }
+
     @Transactional(rollbackFor = Exception.class)
     @Override
     public String buildAnalyzeCollegePaperStruct(Long examId, String courseCode) {
@@ -1048,7 +1148,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                         taExamCourseCollegePaperStruct.setExamId(examId);
                         taExamCourseCollegePaperStruct.setSchoolId(tbExamService.getById(examId).getSchoolId());
                         taExamCourseCollegePaperStruct.setCourseCode(effectiveCourseCode);
-                        taExamCourseCollegePaperStruct.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                        taExamCourseCollegePaperStruct.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                         taExamCourseCollegePaperStruct.setPaperId(paperId);
                         taExamCourseCollegePaperStruct.setInspectCollegeId(inspectCollegeId);
                         taExamCourseCollegePaperStruct.setInspectCollegeName(inspectCollegeName);
@@ -1150,7 +1250,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                         taExamCourseTeacherPaperStruct.setExamId(examId);
                         taExamCourseTeacherPaperStruct.setSchoolId(tbExamService.getById(examId).getSchoolId());
                         taExamCourseTeacherPaperStruct.setCourseCode(effectiveCourseCode);
-                        taExamCourseTeacherPaperStruct.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                        taExamCourseTeacherPaperStruct.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                         taExamCourseTeacherPaperStruct.setPaperId(paperId);
                         taExamCourseTeacherPaperStruct.setTeacherId(teacherId);
                         taExamCourseTeacherPaperStruct.setTeacherName(teacherName);
@@ -1268,7 +1368,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                             taExamCourseTeacherCollegePaperStruct.setExamId(examId);
                             taExamCourseTeacherCollegePaperStruct.setSchoolId(tbExamService.getById(examId).getSchoolId());
                             taExamCourseTeacherCollegePaperStruct.setCourseCode(effectiveCourseCode);
-                            taExamCourseTeacherCollegePaperStruct.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                            taExamCourseTeacherCollegePaperStruct.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                             taExamCourseTeacherCollegePaperStruct.setPaperId(paperId);
                             taExamCourseTeacherCollegePaperStruct.setTeacherId(teacherId);
                             taExamCourseTeacherCollegePaperStruct.setTeacherName(teacherName);
@@ -1371,7 +1471,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                         taExamCourseDifficult.setId(SystemConstant.getDbUuid());
                         taExamCourseDifficult.setExamId(examId);
                         taExamCourseDifficult.setCourseCode(effectiveCourseCode);
-                        taExamCourseDifficult.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                        taExamCourseDifficult.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                         taExamCourseDifficult.setPaperId(paperId);
                         taExamCourseDifficult.setPaperType(tbPaperService.getById(paperId).getPaperType());
                         taExamCourseDifficult.setCollegeId(collegeId);
@@ -1464,7 +1564,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                         taExamCourseTeacherDifficult.setPaperId(paperId);
                         taExamCourseTeacherDifficult.setPaperType(tbPaperService.getById(paperId).getPaperType());
                         taExamCourseTeacherDifficult.setCourseCode(effectiveCourseCode);
-                        taExamCourseTeacherDifficult.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                        taExamCourseTeacherDifficult.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                         taExamCourseTeacherDifficult.setTeacherId(teacherId);
                         taExamCourseTeacherDifficult.setTeacherName(sysUserService.getById(teacherId).getRealName());
                         taExamCourseTeacherDifficult.setSchoolId(tbExamService.getById(examId).getSchoolId());
@@ -1580,7 +1680,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                             taExamCourseTeacherCollegeDifficult.setPaperId(paperId);
                             taExamCourseTeacherCollegeDifficult.setPaperType(tbPaperService.getById(paperId).getPaperType());
                             taExamCourseTeacherCollegeDifficult.setCourseCode(effectiveCourseCode);
-                            taExamCourseTeacherCollegeDifficult.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                            taExamCourseTeacherCollegeDifficult.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                             taExamCourseTeacherCollegeDifficult.setTeacherId(teacherId);
                             taExamCourseTeacherCollegeDifficult.setTeacherName(sysUserService.getById(teacherId).getRealName());
                             taExamCourseTeacherCollegeDifficult.setInspectCollegeId(collegeId);
@@ -1608,7 +1708,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public String buildAnalyzeExamTotal(Long examId,String courseCode) {
+    public String buildAnalyzeExamTotal(Long examId, String courseCode) {
         // 删除原数据
         taExamTotalService.remove(new QueryWrapper<TAExamTotal>().lambda()
                 .eq(TAExamTotal::getExamId, examId));
@@ -1626,7 +1726,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
         if (collegeCount > 0) {
             for (Long inspectCollegeId : inspectCollegeInfo) {
                 SysOrg college = sysOrgService.getById(inspectCollegeId);
-                if (Objects.nonNull(college)){
+                if (Objects.nonNull(college)) {
                     inspectCollegeNames.append(college.getName()).append(SystemConstant.PAUSE_SIGN);
                 }
             }
@@ -1823,7 +1923,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                     taExamCourseCollegeTeacher.setExamId(examId);
                     taExamCourseCollegeTeacher.setSchoolId(tbExamService.getById(examId).getSchoolId());
                     taExamCourseCollegeTeacher.setCourseCode(effectiveCourseCode);
-                    taExamCourseCollegeTeacher.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                    taExamCourseCollegeTeacher.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                     taExamCourseCollegeTeacher.setTeacherId(teacherId);
                     taExamCourseCollegeTeacher.setTeacherName(sysUserService.getById(teacherId).getRealName());
                     taExamCourseCollegeTeacher.setMinScoreAssign(BigDecimal.valueOf(minScoreAssign));
@@ -1936,7 +2036,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
                 taExamCourseTeacher.setExamId(examId);
                 taExamCourseTeacher.setSchoolId(tbExamService.getById(examId).getSchoolId());
                 taExamCourseTeacher.setCourseCode(effectiveCourseCode);
-                taExamCourseTeacher.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode,schoolId).getName());
+                taExamCourseTeacher.setCourseName(basicCourseService.findByCourseCode(effectiveCourseCode, schoolId).getName());
                 taExamCourseTeacher.setTeacherId(teacherId);
                 taExamCourseTeacher.setTeacherName(sysUserService.getById(teacherId).getRealName());
                 taExamCourseTeacher.setMinScore(BigDecimal.valueOf(minScore));
@@ -1989,7 +2089,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
         analyzeForReportService.buildAnalyzePaperStruct(examId, courseCode);
         analyzeForReportService.buildExamPaperDifficult(examId, courseCode);
         analyzeForReportService.buildExamPaperTeacherDifficult(examId, courseCode);
-        analyzeForReportService.buildAnalyzeExamTotal(examId,courseCode);
+        analyzeForReportService.buildAnalyzeExamTotal(examId, courseCode);
         analyzeForReportService.buildAnalyzeExamCourseCollegeTeacher(examId, courseCode);
         analyzeForReportService.buildAnalyzeExamCourseTeacher(examId, courseCode);
         analyzeForReportService.buildAnalyzeExamCourseTeacherCollegeDio(examId, courseCode);
@@ -2027,7 +2127,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
             throw ExceptionResultEnum.ERROR.exception("【" + status + "】状态的课程无法进行" + "【" + statusDesc + "】");
         }
         // 发布或取消发布清除缓存
-        this.removeRedisCache(tbExamCourse.getSchoolId(),examId,courseCode);
+        this.removeRedisCache(tbExamCourse.getSchoolId(), examId, courseCode);
         tbExamCourseService.updateById(tbExamCourse);
     }
 
@@ -2041,7 +2141,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
         // 获取当前课程下所有学生考试成绩记录
         List<BasicExamRecordDto> basicExamRecordDtoDatasource = tbExamRecordService.findByExamIdAndCourseCodeS(examId, courseCodeList);
         for (String s : courseCodeList) {
-            if (tbExamCourseService.verifyExamCourseCantRun(examId, schoolId, s, basicCourseService.findByCourseCode(s,schoolId).getName())) {
+            if (tbExamCourseService.verifyExamCourseCantRun(examId, schoolId, s, basicCourseService.findByCourseCode(s, schoolId).getName())) {
                 throw ExceptionResultEnum.ERROR.exception("课程编号[" + s + "]的课程分析数据已测试或发布,不能变更基础数据");
             }
             List<BasicExamRecordDto> basicExamRecordDtoList = basicExamRecordDtoDatasource.stream()
@@ -2116,14 +2216,14 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
         // 考察院长报告总览和详情缓存
         List<Long> inspectCollegeIdList = datasource.stream().map(TAExamCourseRecord::getInspectCollegeId).distinct().collect(Collectors.toList());
         for (Long inspectCollegeId : inspectCollegeIdList) {
-            commonCacheService.removeCollegeDeanCache(schoolId,semesterId,examId,inspectCollegeId);
-            commonCacheService.removeCollegeCourseCache(schoolId,semesterId,examId,courseCode,inspectCollegeId);
+            commonCacheService.removeCollegeDeanCache(schoolId, semesterId, examId, inspectCollegeId);
+            commonCacheService.removeCollegeCourseCache(schoolId, semesterId, examId, courseCode, inspectCollegeId);
         }
 
         // 开课课程考试分析缓存清除
         List<Long> teachCollegeIdList = datasource.stream().map(TAExamCourseRecord::getTeachCollegeId).distinct().collect(Collectors.toList());
         for (Long teachCollegeId : teachCollegeIdList) {
-            commonCacheService.removeSurveyTeacherViewCache(schoolId,semesterId,examId,courseCode,teachCollegeId);
+            commonCacheService.removeSurveyTeacherViewCache(schoolId, semesterId, examId, courseCode, teachCollegeId);
         }
     }
 
@@ -2507,7 +2607,7 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
      * @param scoreRate  题目得分率
      * @return 难度
      */
-    private String analyzeDifficult(Long examId, String courseCode, double scoreRate) {
+    public String analyzeDifficult(Long examId, String courseCode, double scoreRate) {
         List<TBCommonLevelConfig> levelConfigList = tbCommonLevelConfigService.list(new QueryWrapper<TBCommonLevelConfig>().lambda()
                 .eq(TBCommonLevelConfig::getExamId, examId)
                 .eq(TBCommonLevelConfig::getCourseCode, courseCode)
@@ -2550,15 +2650,15 @@ public class AnalyzeForReportServiceImpl implements AnalyzeForReportService {
      * @param fullScore                   该小题总分
      * @return 鉴别度
      */
-    private double calculateValidity(List<ValidAnswerDetailDto> oneQuestionAnswerDetailList, double fullScore) {
+    public double calculateValidity(List<ValidAnswerDetailDto> oneQuestionAnswerDetailList, double fullScore) {
         List<ValidAnswerDetailDto> sortList = oneQuestionAnswerDetailList.stream().sorted(((o1, o2) -> {
             double compare1 = o1.getPercentGrade();
             double compare2 = o2.getPercentGrade();
             return Double.compare(compare2, compare1);
         })).collect(Collectors.toList());
 
-        int rank = 0;
-        int number = 0;
+        int rank = 0;//排名
+        int number = 0;//序号
         double value = 10000;
         for (ValidAnswerDetailDto temp : sortList) {
             double compareValue = temp.getPercentGrade();

+ 7 - 1
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/service/impl/ReportCommonServiceImpl.java

@@ -527,6 +527,7 @@ public class ReportCommonServiceImpl implements ReportCommonService {
             for (TBPaperStructResult cell : cellList) {
                 cell.setPaperType(null);
             }
+            //knowledgeDimensionSet:知识维度集合,abilityDimensionSet:能力维度集合
             Set<String> knowledgeDimensionSet = new HashSet<>(), abilityDimensionSet = new HashSet<>();
             // 错题
             List<TBPaperStructResult> errorCellList = cellList.stream().filter(e -> PaperStructJudgeEnum.NOT_QUITE_RIGHT.equals(e.getPaperStructJudge())).collect(Collectors.toList());
@@ -544,9 +545,11 @@ public class ReportCommonServiceImpl implements ReportCommonService {
                 }
             }
             List<String> knowledgeDimensionSort = MathUtil.sortDimension(new ArrayList<>(knowledgeDimensionSet)); // 经过特殊排序的知识点集合
-            List<String> abilityDimensionSort = MathUtil.sortDimension(new ArrayList<>(abilityDimensionSet)); // 经过特殊排序的知识点集合
+            List<String> abilityDimensionSort = MathUtil.sortDimension(new ArrayList<>(abilityDimensionSet)); // 经过特殊排序的能力点集合
 
+            //知识二级维度名称集合
             List<DimensionInfoResult> dimensionList = new ArrayList<>();
+            //过滤知识二级维度名称名称
             for (String dimensionCode : knowledgeDimensionSort) {
                 String dimensionName;
                 List<String> nameTwoList = dimensionDataSource.stream()
@@ -566,7 +569,9 @@ public class ReportCommonServiceImpl implements ReportCommonService {
                 dimensionList.add(new DimensionInfoResult(dimensionCode, dimensionName));
             }
 
+            //能力二级维度名称集合
             List<DimensionInfoResult> abilityDimensionList = new ArrayList<>();
+            //过滤能力二级维度名称名称
             for (String dimensionCode : abilityDimensionSort) {
                 String dimensionName;
                 List<String> nameTwoList = dimensionDataSource.stream()
@@ -699,6 +704,7 @@ public class ReportCommonServiceImpl implements ReportCommonService {
         teachCourseSurveyResult.setCoefficient(taExamCourse.getCoefficient());
         teachCourseSurveyResult.setTeachCollegeName(taExamCourse.getTeachCollegeName());
         teachCourseSurveyResult.setInspectCollegeNames(taExamCourse.getInspectCollegeNames());
+        teachCourseSurveyResult.setCourseValidity(taExamCourse.getCourseValidity());
         return teachCourseSurveyResult;
     }
 

+ 14 - 0
teachcloud-report-business/src/main/java/com/qmth/teachcloud/report/business/service/impl/TBAnswerServiceImpl.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.teachcloud.report.business.bean.dto.AnswerDetailBean;
 import com.qmth.teachcloud.report.business.bean.dto.query.BasicAnswerDto;
 import com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailDto;
+import com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailSumScoreDto;
 import com.qmth.teachcloud.report.business.bean.result.TBAnswerResult;
 import com.qmth.teachcloud.report.business.entity.TBAnswer;
 import com.qmth.teachcloud.report.business.mapper.TBAnswerMapper;
@@ -54,4 +55,17 @@ public class TBAnswerServiceImpl extends ServiceImpl<TBAnswerMapper, TBAnswer> i
     public LinkedList<AnswerDetailBean> findValidAnswerDetailWithPap(Long examId, String courseCode) {
         return tbAnswerMapper.findValidAnswerDetailWithPap(examId, courseCode);
     }
+
+    /**
+     * 查找试卷结构满分效度数据
+     *
+     * @param examId
+     * @param courseCode
+     * @param paperId
+     * @return
+     */
+    @Override
+    public List<ValidAnswerDetailSumScoreDto> findValid(Long examId, String courseCode, Long paperId) {
+        return this.baseMapper.findValidSumScore(examId, courseCode, paperId);
+    }
 }

+ 27 - 0
teachcloud-report-business/src/main/resources/mapper/TBAnswerMapper.xml

@@ -109,4 +109,31 @@
           AND record.absent = 0
           AND record.student_current = 1;
     </select>
+
+    <select id="findValidSumScore" resultType="com.qmth.teachcloud.report.business.bean.dto.query.ValidAnswerDetailSumScoreDto">
+        SELECT
+            answer.exam_record_id AS examRecordId,
+            record.id AS taExamRecordId,
+            record.paper_type AS paperType,
+            sum(answer.score) as sumScore
+        FROM t_b_answer answer
+        INNER JOIN t_a_exam_course_record record ON answer.exam_record_id = record.exam_record_id
+        <where>
+            record.absent = 0 AND record.student_current = 1
+            <if test="examId != null and examId != ''">
+                and record.exam_id = #{examId}
+            </if>
+            <if test="courseCode != null and courseCode != ''">
+                and record.course_code = #{courseCode}
+            </if>
+            <if test="paperId != null and paperId != ''">
+                and record.paper_id = #{paperId}
+            </if>
+        </where>
+        group by
+            answer.exam_record_id,
+            record.id,
+            record.paper_type
+        order by sumScore desc,paperType
+    </select>
 </mapper>

+ 4 - 1
teachcloud-report/install/mysql/upgrade/3.4.0.sql

@@ -13,4 +13,7 @@ ALTER TABLE sys_user
     CHANGE COLUMN pwd_count pwd_count TINYINT NULL DEFAULT '0' COMMENT '密码修改次数,默认为0' AFTER enable;
 
 ALTER TABLE basic_school
-    ADD COLUMN exam_task_instr VARCHAR(500) NULL COMMENT '广药命题界面提示信息' AFTER background_image;
+    ADD COLUMN exam_task_instr VARCHAR(500) NULL COMMENT '广药命题界面提示信息' AFTER background_image;
+
+-- 2025-07-17
+ALTER TABLE t_a_exam_course ADD course_validity DECIMAL(12,4) DEFAULT 0 NOT NULL COMMENT '科目效度';

+ 13 - 0
teachcloud-report/src/main/java/com/qmth/teachcloud/report/api/SysController.java

@@ -16,6 +16,7 @@ import com.qmth.teachcloud.common.util.ResultUtil;
 import com.qmth.teachcloud.common.util.ServletUtil;
 import com.qmth.teachcloud.report.business.bean.params.LoginParam;
 import com.qmth.teachcloud.report.business.bean.result.EditResult;
+import com.qmth.teachcloud.report.business.service.AnalyzeForReportService;
 import com.qmth.teachcloud.report.business.service.ReportCommonService;
 import io.swagger.annotations.*;
 import org.apache.commons.lang3.StringUtils;
@@ -323,4 +324,16 @@ public class SysController {
     public Result getSystemTime() {
         return ResultUtil.ok(System.currentTimeMillis());
     }
+
+    @Resource
+    AnalyzeForReportService analyzeForReportService;
+
+    @ApiOperation(value = "测试")
+    @RequestMapping(value = "/test", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "返回信息", response = EditResult.class)})
+    @Aac(auth = false)
+    public Result test(@ApiParam(value = "考试id", required = true) @RequestParam Long examId,
+                       @ApiParam(value = "科目代码", required = true) @RequestParam String courseCode) {
+        return ResultUtil.ok(analyzeForReportService.buildAnalyzePaperStruct(examId, courseCode));
+    }
 }