Browse Source

质量监控

xiatian 10 hours ago
parent
commit
91adf72800

+ 2 - 0
install/mysql/upgrade/2.0.0.sql

@@ -2,6 +2,8 @@
 
 USE `stmms_ft`;
 
+ALTER TABLE eb_marker ADD COLUMN `wait_arbitrate_count` int(11)     DEFAULT NULL COMMENT '待仲裁数';
+
 DROP TABLE IF EXISTS `m_reject_type`;
 CREATE TABLE `m_reject_type`
 (

+ 56 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/bean/MarkerChatVo.java

@@ -0,0 +1,56 @@
+package cn.com.qmth.stmms.biz.exam.bean;
+
+import java.util.Map;
+
+public class MarkerChatVo {
+
+    private Integer id;
+
+    private String loginName;
+
+    private String name;
+
+    private Map<Double, Double> scorePercent;
+
+    private Map<Double, Long> scoreCount;
+
+    public String getLoginName() {
+        return loginName;
+    }
+
+    public void setLoginName(String loginName) {
+        this.loginName = loginName;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Map<Double, Double> getScorePercent() {
+        return scorePercent;
+    }
+
+    public void setScorePercent(Map<Double, Double> scorePercent) {
+        this.scorePercent = scorePercent;
+    }
+
+    public Map<Double, Long> getScoreCount() {
+        return scoreCount;
+    }
+
+    public void setScoreCount(Map<Double, Long> scoreCount) {
+        this.scoreCount = scoreCount;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+}

+ 39 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/bean/MarkerQualityChatVo.java

@@ -0,0 +1,39 @@
+package cn.com.qmth.stmms.biz.exam.bean;
+
+import java.util.List;
+
+import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
+
+public class MarkerQualityChatVo {
+
+    private MarkGroup group;
+
+    private List<Double> scores;
+
+    private List<MarkerChatVo> markers;
+
+    public MarkGroup getGroup() {
+        return group;
+    }
+
+    public void setGroup(MarkGroup group) {
+        this.group = group;
+    }
+
+    public List<Double> getScores() {
+        return scores;
+    }
+
+    public void setScores(List<Double> scores) {
+        this.scores = scores;
+    }
+
+    public List<MarkerChatVo> getMarkers() {
+        return markers;
+    }
+
+    public void setMarkers(List<MarkerChatVo> markers) {
+        this.markers = markers;
+    }
+
+}

+ 487 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/bean/MarkerQualityVo.java

@@ -0,0 +1,487 @@
+package cn.com.qmth.stmms.biz.exam.bean;
+
+import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
+import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
+import cn.com.qmth.stmms.biz.exam.model.Marker;
+import cn.com.qmth.stmms.biz.utils.Calculator;
+import cn.com.qmth.stmms.common.annotation.ExcelField;
+import cn.com.qmth.stmms.common.enums.MarkMode;
+import io.swagger.annotations.ApiModelProperty;
+
+public class MarkerQualityVo {
+
+    @ApiModelProperty("评卷员id")
+    private Integer id;
+
+    @ApiModelProperty("")
+    private Integer examId;
+
+    @ApiModelProperty("")
+    private String subjectCode;
+
+    private Integer groupNumber;
+
+    @ApiModelProperty("用户id")
+    private Integer userId;
+
+    @ApiModelProperty("启用禁用")
+    private Boolean enable;
+
+    @ApiModelProperty("评卷模式")
+    private MarkMode mode;
+
+    @ApiModelProperty("任务数")
+    private Integer topCount;
+
+    @ExcelField(title = "分组", align = 2, sort = 10)
+    @ApiModelProperty("分组")
+    private String groupText;
+
+    @ExcelField(title = "评卷员", align = 2, sort = 20)
+    @ApiModelProperty("评卷员")
+    private String loginName;
+
+    @ExcelField(title = "姓名", align = 2, sort = 30)
+    @ApiModelProperty("姓名")
+    private String name;
+
+    @ApiModelProperty("")
+    private ExamSubject subject;
+
+    private String title;
+
+    @ApiModelProperty("已评数量")
+    private Long markedCount;
+
+    @ApiModelProperty("")
+    private Long markedCountNa;
+
+    @ApiModelProperty("正在评卷")
+    private Long currentCount;
+
+    @ApiModelProperty("是否正在重置")
+    private Boolean reseting;
+
+    /**
+     * 班级数量
+     */
+    @ApiModelProperty("绑定班级")
+    private Long classCount;
+
+    @ExcelField(title = "完成任务数", align = 2, sort = 40)
+    @ApiModelProperty("完成任务数")
+    private Integer finishCount;
+
+    private Integer validCount;
+
+    @ApiModelProperty(hidden = true)
+    private Integer finishCountNa;
+
+    @ApiModelProperty(hidden = true)
+    private Integer validCountNa;
+
+    @ApiModelProperty(hidden = true)
+    private Double avgSpeedNa;
+
+    @ApiModelProperty(hidden = true)
+    private Double avgScoreNa;
+
+    @ApiModelProperty(hidden = true)
+    private Double stdevScoreNa;
+
+    // 待仲裁数
+    @ApiModelProperty(hidden = true)
+    private Integer waitArbitrateCount;
+
+    /**
+     * 个性化评卷参数设置
+     */
+    @ApiModelProperty(hidden = true)
+    private String markSetting;
+
+    @ApiModelProperty(hidden = true)
+    private String subjectName;
+
+    @ApiModelProperty(hidden = true)
+    private String groupName;
+
+    @ApiModelProperty(hidden = true)
+    private MarkGroup group;
+
+    @ExcelField(title = "仲裁任务数", align = 2, sort = 50)
+    @ApiModelProperty("仲裁任务数")
+    private long arbitrateCount;
+
+    @ExcelField(title = "仲裁率", align = 2, sort = 60)
+    @ApiModelProperty("仲裁率")
+    private String arbitrateRatio;
+
+    @ExcelField(title = "打回次数", align = 2, sort = 70)
+    @ApiModelProperty("打回次数")
+    private Integer rejectCount;
+
+    @ExcelField(title = "评卷采用率", align = 2, sort = 80)
+    @ApiModelProperty("评卷采用率")
+    private String markValidRatio;
+
+    @ExcelField(title = "评卷速度(秒)", align = 2, sort = 90)
+    @ApiModelProperty("评卷速度(秒)")
+    private Double avgSpeed;
+
+    @ExcelField(title = "平均分", align = 2, sort = 100)
+    @ApiModelProperty("平均分")
+    private Double avgScore;
+
+    @ExcelField(title = "标准差", align = 2, sort = 110)
+    @ApiModelProperty("标准差")
+    private Double stdevScore;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Integer examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public Integer getGroupNumber() {
+        return groupNumber;
+    }
+
+    public void setGroupNumber(Integer groupNumber) {
+        this.groupNumber = groupNumber;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+    public MarkMode getMode() {
+        return mode;
+    }
+
+    public void setMode(MarkMode mode) {
+        this.mode = mode;
+    }
+
+    public Integer getTopCount() {
+        return topCount;
+    }
+
+    public void setTopCount(Integer topCount) {
+        this.topCount = topCount;
+    }
+
+    public String getLoginName() {
+        return loginName;
+    }
+
+    public void setLoginName(String loginName) {
+        this.loginName = loginName;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public ExamSubject getSubject() {
+        return subject;
+    }
+
+    public void setSubject(ExamSubject subject) {
+        this.subject = subject;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Long getMarkedCount() {
+        return markedCount;
+    }
+
+    public void setMarkedCount(Long markedCount) {
+        this.markedCount = markedCount;
+    }
+
+    public Long getMarkedCountNa() {
+        return markedCountNa;
+    }
+
+    public void setMarkedCountNa(Long markedCountNa) {
+        this.markedCountNa = markedCountNa;
+    }
+
+    public Long getCurrentCount() {
+        return currentCount;
+    }
+
+    public void setCurrentCount(Long currentCount) {
+        this.currentCount = currentCount;
+    }
+
+    public Boolean getReseting() {
+        return reseting;
+    }
+
+    public void setReseting(Boolean reseting) {
+        this.reseting = reseting;
+    }
+
+    public Long getClassCount() {
+        return classCount;
+    }
+
+    public void setClassCount(Long classCount) {
+        this.classCount = classCount;
+    }
+
+    public Integer getFinishCount() {
+        return finishCount;
+    }
+
+    public void setFinishCount(Integer finishCount) {
+        this.finishCount = finishCount;
+    }
+
+    public Integer getValidCount() {
+        return validCount;
+    }
+
+    public void setValidCount(Integer validCount) {
+        this.validCount = validCount;
+    }
+
+    public Integer getRejectCount() {
+        return rejectCount;
+    }
+
+    public void setRejectCount(Integer rejectCount) {
+        this.rejectCount = rejectCount;
+    }
+
+    public Double getAvgSpeed() {
+        return avgSpeed;
+    }
+
+    public void setAvgSpeed(Double avgSpeed) {
+        this.avgSpeed = avgSpeed;
+    }
+
+    public Double getAvgScore() {
+        return avgScore;
+    }
+
+    public void setAvgScore(Double avgScore) {
+        this.avgScore = avgScore;
+    }
+
+    public Double getStdevScore() {
+        return stdevScore;
+    }
+
+    public void setStdevScore(Double stdevScore) {
+        this.stdevScore = stdevScore;
+    }
+
+    public Integer getFinishCountNa() {
+        return finishCountNa;
+    }
+
+    public void setFinishCountNa(Integer finishCountNa) {
+        this.finishCountNa = finishCountNa;
+    }
+
+    public Integer getValidCountNa() {
+        return validCountNa;
+    }
+
+    public void setValidCountNa(Integer validCountNa) {
+        this.validCountNa = validCountNa;
+    }
+
+    public Double getAvgSpeedNa() {
+        return avgSpeedNa;
+    }
+
+    public void setAvgSpeedNa(Double avgSpeedNa) {
+        this.avgSpeedNa = avgSpeedNa;
+    }
+
+    public Double getAvgScoreNa() {
+        return avgScoreNa;
+    }
+
+    public void setAvgScoreNa(Double avgScoreNa) {
+        this.avgScoreNa = avgScoreNa;
+    }
+
+    public Double getStdevScoreNa() {
+        return stdevScoreNa;
+    }
+
+    public void setStdevScoreNa(Double stdevScoreNa) {
+        this.stdevScoreNa = stdevScoreNa;
+    }
+
+    public Integer getWaitArbitrateCount() {
+        return waitArbitrateCount;
+    }
+
+    public void setWaitArbitrateCount(Integer waitArbitrateCount) {
+        this.waitArbitrateCount = waitArbitrateCount;
+    }
+
+    public String getMarkSetting() {
+        return markSetting;
+    }
+
+    public void setMarkSetting(String markSetting) {
+        this.markSetting = markSetting;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+
+    public String getGroupName() {
+        return groupName;
+    }
+
+    public void setGroupName(String groupName) {
+        this.groupName = groupName;
+    }
+
+    public MarkGroup getGroup() {
+        return group;
+    }
+
+    public void setGroup(MarkGroup group) {
+        this.group = group;
+    }
+
+    public long getArbitrateCount() {
+        return arbitrateCount;
+    }
+
+    public void setArbitrateCount(long arbitrateCount) {
+        this.arbitrateCount = arbitrateCount;
+    }
+
+    public String getArbitrateRatio() {
+        return arbitrateRatio;
+    }
+
+    public void setArbitrateRatio(String arbitrateRatio) {
+        this.arbitrateRatio = arbitrateRatio;
+    }
+
+    public String getMarkValidRatio() {
+        return markValidRatio;
+    }
+
+    public void setMarkValidRatio(String markValidRatio) {
+        this.markValidRatio = markValidRatio;
+    }
+
+    public String getGroupText() {
+        return groupText;
+    }
+
+    public void setGroupText(String groupText) {
+        this.groupText = groupText;
+    }
+
+    public static MarkerQualityVo of(Marker from) {
+        if (from == null) {
+            return null;
+        }
+        MarkerQualityVo ret = new MarkerQualityVo();
+        ret.setId(from.getId());
+        ret.setExamId(from.getExamId());
+        ret.setSubjectCode(from.getSubjectCode());
+        ret.setGroupNumber(from.getGroupNumber());
+        ret.setUserId(from.getUserId());
+        ret.setEnable(from.isEnable());
+        ret.setMode(from.getMode());
+        ret.setTopCount(from.getTopCount());
+        ret.setLoginName(from.getLoginName());
+        ret.setSubject(from.getSubject());
+        ret.setTitle(from.getGroup().getTitle());
+        ret.setMarkedCount(from.getMarkedCount());
+        ret.setMarkedCountNa(from.getMarkedCountNa());
+        ret.setCurrentCount(from.getCurrentCount());
+        ret.setReseting(from.isReseting());
+        ret.setClassCount(from.getClassCount());
+        ret.setFinishCount(from.getFinishCount());
+        ret.setValidCount(from.getValidCount());
+        ret.setRejectCount(from.getRejectCount());
+        ret.setAvgSpeed(from.getAvgSpeed());
+        ret.setAvgScore(from.getAvgScore());
+        ret.setStdevScore(from.getStdevScore());
+        ret.setFinishCountNa(from.getFinishCountNa());
+        ret.setValidCountNa(from.getValidCountNa());
+        ret.setAvgSpeedNa(from.getAvgSpeedNa());
+        ret.setAvgScoreNa(from.getAvgScoreNa());
+        ret.setStdevScoreNa(from.getStdevScoreNa());
+        ret.setWaitArbitrateCount(from.getWaitArbitrateCount());
+        ret.setMarkSetting(from.getMarkSetting());
+        ret.setSubjectName(from.getSubjectName());
+        ret.setGroupName(from.getGroupName());
+        ret.setGroup(from.getGroup());
+        ret.setArbitrateCount(from.getArbitrateCount());
+        ret.setArbitrateRatio(from.getArbitrateRatio());
+        if (from.getFinishCount() != null && from.getRejectCount() != null && from.getFinishCount() > 0
+                && (from.getFinishCount() - from.getRejectCount()) > 0) {
+            ret.setMarkValidRatio(
+                    Calculator.percentage(from.getFinishCount() - from.getRejectCount(), from.getFinishCount(), 3));
+        } else if ((from.getRejectCount() == null || from.getRejectCount() == 0) && from.getFinishCount() != null) {
+            ret.setMarkValidRatio("100%");
+        } else {
+            ret.setMarkValidRatio("0%");
+        }
+        ret.setName(from.getUser().getName());
+        ret.setGroupText(ret.getGroupNumber() + "-" + ret.getTitle());
+        return ret;
+    }
+}

+ 157 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/bean/SubjectQualityVo.java

@@ -0,0 +1,157 @@
+package cn.com.qmth.stmms.biz.exam.bean;
+
+import cn.com.qmth.stmms.common.annotation.ExcelField;
+import io.swagger.annotations.ApiModelProperty;
+
+public class SubjectQualityVo {
+
+    private String subjectCode;
+
+    @ExcelField(title = "科目", align = 2, sort = 10)
+    @ApiModelProperty("科目")
+    private String subjectText;
+
+    private Integer groupNumber;
+
+    private String groupName;
+
+    @ExcelField(title = "分组", align = 2, sort = 20)
+    @ApiModelProperty("分组")
+    private String groupText;
+
+    @ExcelField(title = "任务总量", align = 2, sort = 30)
+    @ApiModelProperty("任务总量")
+    private Integer totalCount = 0;
+
+    @ExcelField(title = "已完成", align = 2, sort = 40)
+    @ApiModelProperty("已完成")
+    private Integer finishCount = 0;
+
+    @ExcelField(title = "仲裁卷数", align = 2, sort = 50)
+    @ApiModelProperty("仲裁卷数")
+    private Integer arbitrateCount = 0;
+
+    @ExcelField(title = "仲裁率", align = 2, sort = 60)
+    @ApiModelProperty("仲裁率")
+    private String arbitrateRatio;
+
+    // 不含仲裁完成数
+    @ApiModelProperty(hidden = true)
+    private Integer finishCountNa = 0;
+
+    @ExcelField(title = "已完成仲裁", align = 2, sort = 70)
+    @ApiModelProperty("已完成仲裁")
+    private Integer finishArbitrateCount = 0;
+
+    @ExcelField(title = "待处理仲裁", align = 2, sort = 80)
+    @ApiModelProperty("待处理仲裁")
+    private Integer waitArbitrateCount = 0;
+
+    @ApiModelProperty(hidden = true)
+    private String subjectName;
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public String getSubjectText() {
+        return subjectText;
+    }
+
+    public void setSubjectText(String subjectText) {
+        this.subjectText = subjectText;
+    }
+
+    public Integer getGroupNumber() {
+        return groupNumber;
+    }
+
+    public void setGroupNumber(Integer groupNumber) {
+        this.groupNumber = groupNumber;
+    }
+
+    public String getGroupName() {
+        return groupName;
+    }
+
+    public void setGroupName(String groupName) {
+        this.groupName = groupName;
+    }
+
+    public String getGroupText() {
+        return groupText;
+    }
+
+    public void setGroupText(String groupText) {
+        this.groupText = groupText;
+    }
+
+    public Integer getTotalCount() {
+        return totalCount;
+    }
+
+    public void setTotalCount(Integer totalCount) {
+        this.totalCount = totalCount;
+    }
+
+    public Integer getFinishCount() {
+        return finishCount;
+    }
+
+    public void setFinishCount(Integer finishCount) {
+        this.finishCount = finishCount;
+    }
+
+    public Integer getFinishCountNa() {
+        return finishCountNa;
+    }
+
+    public void setFinishCountNa(Integer finishCountNa) {
+        this.finishCountNa = finishCountNa;
+    }
+
+    public Integer getWaitArbitrateCount() {
+        return waitArbitrateCount;
+    }
+
+    public void setWaitArbitrateCount(Integer waitArbitrateCount) {
+        this.waitArbitrateCount = waitArbitrateCount;
+    }
+
+    public String getSubjectName() {
+        return subjectName;
+    }
+
+    public void setSubjectName(String subjectName) {
+        this.subjectName = subjectName;
+    }
+
+    public Integer getArbitrateCount() {
+        return arbitrateCount;
+    }
+
+    public void setArbitrateCount(Integer arbitrateCount) {
+        this.arbitrateCount = arbitrateCount;
+    }
+
+    public String getArbitrateRatio() {
+        return arbitrateRatio;
+    }
+
+    public void setArbitrateRatio(String arbitrateRatio) {
+        this.arbitrateRatio = arbitrateRatio;
+    }
+
+    public Integer getFinishArbitrateCount() {
+        return finishArbitrateCount;
+    }
+
+    public void setFinishArbitrateCount(Integer finishArbitrateCount) {
+        this.finishArbitrateCount = finishArbitrateCount;
+    }
+
+}

+ 15 - 16
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/ExamSubjectDao.java

@@ -13,22 +13,20 @@ import cn.com.qmth.stmms.biz.exam.model.ExamSubjectPK;
 
 public interface ExamSubjectDao
         extends JpaRepository<ExamSubject, ExamSubjectPK>, JpaSpecificationExecutor<ExamSubject> {
-	
-	@Modifying
-    @Query(value = "delete es from eb_exam_subject es "
-    		+ "where es.exam_id =?1 "
-    		+ "and not exists (select s.id from eb_exam_student s where s.exam_id=es.exam_id and s.subject_code=es.code) "
-    		+ "and not exists (select q.id from eb_exam_question q where q.exam_id=es.exam_id and q.subject_code=es.code)", nativeQuery = true)
+
+    @Modifying
+    @Query(value = "delete es from eb_exam_subject es " + "where es.exam_id =?1 "
+            + "and not exists (select s.id from eb_exam_student s where s.exam_id=es.exam_id and s.subject_code=es.code) "
+            + "and not exists (select q.id from eb_exam_question q where q.exam_id=es.exam_id and q.subject_code=es.code)", nativeQuery = true)
     public void deleteNoStudentByExamId(int examId);
-	
+
     @Modifying
-    @Query(value = "update eb_exam_subject es "
-    		+ "set es.upload_count=(select count(*) from eb_exam_student s "
-    		+ "where s.exam_id=es.exam_id and s.subject_code=es.code and s.is_upload=1 and s.is_absent=0 and s.is_breach=0) "
-    		+ "where es.exam_id=?1", nativeQuery = true)
+    @Query(value = "update eb_exam_subject es " + "set es.upload_count=(select count(*) from eb_exam_student s "
+            + "where s.exam_id=es.exam_id and s.subject_code=es.code and s.is_upload=1 and s.is_absent=0 and s.is_breach=0) "
+            + "where es.exam_id=?1", nativeQuery = true)
     public void updateUploadCountByExamId(int examId);
 
-    @Query("select s from ExamSubject s where s.pk.examId=?1")
+    @Query("select s from ExamSubject s where s.pk.examId=?1 order by s.code")
     public List<ExamSubject> findByExamId(int examId);
 
     @Query("select s from ExamSubject s where s.pk.examId=?1 and s.pk.code in (?2)")
@@ -101,14 +99,15 @@ public interface ExamSubjectDao
 
     @Query("select s from ExamSubject s where s.pk.examId=?1 and s.objectiveScore>?2")
     List<ExamSubject> listObjectiveScore(Integer examId, double objectiveScoreGt);
-    
+
     @Query(value = "select count(1) from eb_exam_subject t "
-    		+ "where t.exam_id =?1  and t.total_score=0", nativeQuery = true)
+            + "where t.exam_id =?1  and t.total_score=0", nativeQuery = true)
     long getZeroScoreCount(Integer examId);
 
     @Query(value = "select count(*) from eb_exam_subject t left join eb_mark_group f on t.exam_id=f.exam_id and t.code=f.subject_code "
-    		+ "where t.exam_id =?1  and t.subjective_score is not null and t.subjective_score>0 and f.exam_id is null", nativeQuery = true)
-	public long getNoGroupCount(Integer id);
+            + "where t.exam_id =?1  and t.subjective_score is not null and t.subjective_score>0 and f.exam_id is null", nativeQuery = true)
+    public long getNoGroupCount(Integer id);
+
     @Modifying
     @Query("update ExamSubject s set s.enableAllSelective=?3 where s.pk.examId=?1 and s.pk.code=?2")
     void updateEnableAllSelective(int examId, String subjectCode, boolean enableAllSelective);

+ 2 - 2
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/MarkGroupDao.java

@@ -28,7 +28,7 @@ public interface MarkGroupDao
 
     @Query("select count(q) from MarkGroup q where q.pk.examId=?1 and q.pk.subjectCode=?2 and q.status in (?3)")
     long countByExamIdAndSubjectCodeAndStatus(Integer examId, String subjectCode, MarkStatus... status);
-    
+
     @Query("select count(q) from MarkGroup q where q.pk.examId=?1  and q.status in (?2)")
     long countByExamIdAndStatus(Integer examId, MarkStatus... status);
 
@@ -37,7 +37,7 @@ public interface MarkGroupDao
     long countByExamIdAndSubjectCodeAndNumberAndStatus(Integer examId, String subjectCode, Integer groupNumber,
             MarkStatus... status);
 
-    @Query("select q from MarkGroup q where q.pk.examId=?1 order by q.pk.number")
+    @Query("select q from MarkGroup q where q.pk.examId=?1 order by q.pk.subjectCode,q.pk.number")
     List<MarkGroup> findByExamId(Integer examId);
 
     @Query("select q from MarkGroup q where q.pk.examId=?1 and q.pk.subjectCode=?2 order by q.pk.number")

+ 2 - 2
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/MarkerDao.java

@@ -49,10 +49,10 @@ public interface MarkerDao extends PagingAndSortingRepository<Marker, Integer>,
             double stdevScore);
 
     @Modifying
-    @Query("update Marker m set m.finishCountNa=?2, m.validCountNa=?3, m.avgSpeedNa=?4, m.avgScoreNa=?5, m.stdevScoreNa=?6 "
+    @Query("update Marker m set m.finishCountNa=?2, m.validCountNa=?3, m.avgSpeedNa=?4, m.avgScoreNa=?5, m.stdevScoreNa=?6,m.wait_arbitrate_count=?7 "
             + "where m.id=?1")
     public void updateQualityNoArbitrateById(Integer id, int finishCount, int validCount, double avgSpeed,
-            double avgScore, double stdevScore);
+            double avgScore, double stdevScore, int waitArbitrateCount);
 
     @Modifying
     @Query("update Marker m set m.finishCount=null, m.validCount=null, m.avgSpeed=null, m.avgScore=null, m.stdevScore=null, "

+ 59 - 44
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/Marker.java

@@ -58,11 +58,13 @@ public class Marker implements Serializable {
 
     @Column(name = "stdev_score")
     private Double stdevScore;
+
     @Column(name = "finish_count_na")
     private Integer finishCountNa;
 
     @Column(name = "valid_count_na")
     private Integer validCountNa;
+
     @Column(name = "avg_speed_na")
     private Double avgSpeedNa;
 
@@ -72,6 +74,10 @@ public class Marker implements Serializable {
     @Column(name = "stdev_score_na")
     private Double stdevScoreNa;
 
+    // 待仲裁数
+    @Column(name = "wait_arbitrate_count")
+    private Integer waitArbitrateCount;
+
     /**
      * 个性化评卷参数设置
      */
@@ -115,10 +121,10 @@ public class Marker implements Serializable {
      */
     @Transient
     private long classCount;
-    
+
     @Transient
     private long arbitrateCount;
-    
+
     @Transient
     private String arbitrateRatio;
 
@@ -325,61 +331,61 @@ public class Marker implements Serializable {
         this.rejectCount = rejectCount;
     }
 
-	public long getArbitrateCount() {
-		return arbitrateCount;
-	}
+    public long getArbitrateCount() {
+        return arbitrateCount;
+    }
 
-	public void setArbitrateCount(long arbitrateCount) {
-		this.arbitrateCount = arbitrateCount;
-	}
+    public void setArbitrateCount(long arbitrateCount) {
+        this.arbitrateCount = arbitrateCount;
+    }
 
-	public String getArbitrateRatio() {
-		return arbitrateRatio;
-	}
+    public String getArbitrateRatio() {
+        return arbitrateRatio;
+    }
 
-	public void setArbitrateRatio(String arbitrateRatio) {
-		this.arbitrateRatio = arbitrateRatio;
-	}
+    public void setArbitrateRatio(String arbitrateRatio) {
+        this.arbitrateRatio = arbitrateRatio;
+    }
 
-	public Integer getFinishCountNa() {
-		return finishCountNa;
-	}
+    public Integer getFinishCountNa() {
+        return finishCountNa;
+    }
 
-	public void setFinishCountNa(Integer finishCountNa) {
-		this.finishCountNa = finishCountNa;
-	}
+    public void setFinishCountNa(Integer finishCountNa) {
+        this.finishCountNa = finishCountNa;
+    }
 
-	public Integer getValidCountNa() {
-		return validCountNa;
-	}
+    public Integer getValidCountNa() {
+        return validCountNa;
+    }
 
-	public void setValidCountNa(Integer validCountNa) {
-		this.validCountNa = validCountNa;
-	}
+    public void setValidCountNa(Integer validCountNa) {
+        this.validCountNa = validCountNa;
+    }
 
-	public Double getAvgSpeedNa() {
-		return avgSpeedNa;
-	}
+    public Double getAvgSpeedNa() {
+        return avgSpeedNa;
+    }
 
-	public void setAvgSpeedNa(Double avgSpeedNa) {
-		this.avgSpeedNa = avgSpeedNa;
-	}
+    public void setAvgSpeedNa(Double avgSpeedNa) {
+        this.avgSpeedNa = avgSpeedNa;
+    }
 
-	public Double getAvgScoreNa() {
-		return avgScoreNa;
-	}
+    public Double getAvgScoreNa() {
+        return avgScoreNa;
+    }
 
-	public void setAvgScoreNa(Double avgScoreNa) {
-		this.avgScoreNa = avgScoreNa;
-	}
+    public void setAvgScoreNa(Double avgScoreNa) {
+        this.avgScoreNa = avgScoreNa;
+    }
 
-	public Double getStdevScoreNa() {
-		return stdevScoreNa;
-	}
+    public Double getStdevScoreNa() {
+        return stdevScoreNa;
+    }
 
-	public void setStdevScoreNa(Double stdevScoreNa) {
-		this.stdevScoreNa = stdevScoreNa;
-	}
+    public void setStdevScoreNa(Double stdevScoreNa) {
+        this.stdevScoreNa = stdevScoreNa;
+    }
 
     public long getMarkedCountNa() {
         return markedCountNa;
@@ -388,4 +394,13 @@ public class Marker implements Serializable {
     public void setMarkedCountNa(long markedCountNa) {
         this.markedCountNa = markedCountNa;
     }
+
+    public Integer getWaitArbitrateCount() {
+        return waitArbitrateCount;
+    }
+
+    public void setWaitArbitrateCount(Integer waitArbitrateCount) {
+        this.waitArbitrateCount = waitArbitrateCount;
+    }
+
 }

+ 5 - 5
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/query/MarkerSearchQuery.java

@@ -9,10 +9,10 @@ import io.swagger.annotations.ApiModelProperty;
 
 public class MarkerSearchQuery extends BaseQuery<Marker> {
 
-    @ApiModelProperty("")
+    @ApiModelProperty(hidden = true)
     private Integer id;
 
-    @ApiModelProperty("")
+    @ApiModelProperty(hidden = true)
     private Integer examId;
 
     @ApiModelProperty("科目代码")
@@ -27,13 +27,13 @@ public class MarkerSearchQuery extends BaseQuery<Marker> {
     @ApiModelProperty("姓名")
     private String name;
 
-    @ApiModelProperty("")
+    @ApiModelProperty(hidden = true)
     private String subjectCodeIn;
 
-    @ApiModelProperty("")
+    @ApiModelProperty("已评卷")
     private Boolean marked;
 
-    @ApiModelProperty("")
+    @ApiModelProperty("不含仲裁")
     private Boolean noArbitrate;
 
     public void orderById() {

+ 20 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/query/SubjectQualitySearchQuery.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.stmms.biz.exam.query;
+
+import cn.com.qmth.stmms.biz.common.BaseQuery;
+import cn.com.qmth.stmms.biz.exam.model.Marker;
+import io.swagger.annotations.ApiModelProperty;
+
+public class SubjectQualitySearchQuery extends BaseQuery<Marker> {
+
+    @ApiModelProperty("科目代码")
+    private String subjectCode;
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+}

+ 20 - 13
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkServiceImpl.java

@@ -600,7 +600,8 @@ public class MarkServiceImpl implements MarkService {
                 // 仲裁任务直接重置该分组下所有分数被打回也重置
                 markerService.updateRejectCountById(library.getMarkerId());
                 libraryDao.resetById(library.getId(), null, reason, userId, now, LibraryStatus.REJECTED,
-                        LibraryStatus.MARKED, LibraryStatus.PROBLEM, LibraryStatus.INSPECTED, LibraryStatus.ARBITRATED,LibraryStatus.WAIT_ARBITRATE);
+                        LibraryStatus.MARKED, LibraryStatus.PROBLEM, LibraryStatus.INSPECTED, LibraryStatus.ARBITRATED,
+                        LibraryStatus.WAIT_ARBITRATE);
                 rejectHistoryDao.save(rejectHistory);
                 trackDao.deleteByLibraryId(library.getId());
                 specialTagDao.deleteByLibraryId(library.getId());
@@ -609,12 +610,12 @@ public class MarkServiceImpl implements MarkService {
             } else {
                 List<ScoreItem> sList = library.getMarkerScoreItem();
                 for (Integer questionIndex : questionIndexList) {
-                    ExamQuestion question = questions.get(questionIndex-1);
+                    ExamQuestion question = questions.get(questionIndex - 1);
                     trackDao.deleteByLibraryIdAndQuestionNumber(library.getId(),
                             question.getMainNumber() + "." + question.getSubNumber());
                     scoreDao.updateRejected(library.getStudentId(), question.getMainNumber(), question.getSubNumber(),
                             true);
-                    ScoreItem si = sList.get(questionIndex-1);
+                    ScoreItem si = sList.get(questionIndex - 1);
                     si.setScore(null);
                 }
                 List<Object> markerScoreList = new ArrayList<Object>();
@@ -627,8 +628,9 @@ public class MarkServiceImpl implements MarkService {
                     }
                 }
                 if (libraryDao.updateMarkerResult(library.getId(), LibraryStatus.REJECTED, library.getMarkerId(), null,
-                        StringUtils.join(markerScoreList, ","), null, null, reason, userId, now, null,null,LibraryStatus.MARKED,
-                        LibraryStatus.INSPECTED,LibraryStatus.ARBITRATED,LibraryStatus.WAIT_ARBITRATE) == 1) {
+                        StringUtils.join(markerScoreList, ","), null, null, reason, userId, now, null, null,
+                        LibraryStatus.MARKED, LibraryStatus.INSPECTED, LibraryStatus.ARBITRATED,
+                        LibraryStatus.WAIT_ARBITRATE) == 1) {
                     markerService.updateRejectCountById(library.getMarkerId());
                     rejectHistory.setRejectScoreList(StringUtils.join(markerScoreList, ","));
                     rejectHistoryDao.save(rejectHistory);
@@ -822,7 +824,7 @@ public class MarkServiceImpl implements MarkService {
         // 尝试提交评卷结果
         Date now = new Date();
         if (libraryDao.updateMarkerResult(library.getId(), LibraryStatus.MARKED, marker.getId(),
-                result.getMarkerScore(), result.getScoreList(), now, result.getSpent(), null, null, null,null,null,
+                result.getMarkerScore(), result.getScoreList(), now, result.getSpent(), null, null, null, null, null,
                 LibraryStatus.WAITING, LibraryStatus.MARKED, LibraryStatus.INSPECTED, LibraryStatus.REJECTED) == 0) {
             // 条件不符更新失败,直接返回
             return false;
@@ -1684,7 +1686,11 @@ public class MarkServiceImpl implements MarkService {
         double stdevScore = 0;
         double sumSpent = 0;
         double avgSpent = 0;
+        int waitArbitrateCount = 0;
         for (MarkLibrary library : list) {
+            if (library.getStatus() == LibraryStatus.WAIT_ARBITRATE) {
+                waitArbitrateCount++;
+            }
             if (library.getStatus() == LibraryStatus.ARBITRATED
                     || library.getStatus() == LibraryStatus.WAIT_ARBITRATE) {
                 continue;
@@ -1709,7 +1715,7 @@ public class MarkServiceImpl implements MarkService {
             }
         }
         markerDao.updateQualityNoArbitrateById(marker.getId(), finishCount, validCount, avgSpent / 1000, avgScore,
-                stdevScore);
+                stdevScore, waitArbitrateCount);
     }
 
     @Override
@@ -1990,7 +1996,7 @@ public class MarkServiceImpl implements MarkService {
                     headerTagDao.deleteByStudentIdAndGroupNumber(library.getStudentId(), groupNumber);
                     headerTrackDao.deleteByPkStudentIdAndGroupNumber(library.getStudentId(), groupNumber);
                     arbitrateDao.deleteByStudentIdAndGroupNumber(student.getId(), groupNumber);
-                    if(!ArbitrateType.QUESTION.equals(group.getArbitrateType())){
+                    if (!ArbitrateType.QUESTION.equals(group.getArbitrateType())) {
                         // 仲裁任务直接重置 或者该分组下所有分数被打回也重置
                         markerService.updateRejectCountById(library.getMarkerId());
                         libraryDao.resetById(library.getId(), null, reason, userId, now, LibraryStatus.REJECTED,
@@ -2005,7 +2011,8 @@ public class MarkServiceImpl implements MarkService {
                 }
                 if (library.getStatus().equals(LibraryStatus.MARKED)
                         || library.getStatus().equals(LibraryStatus.INSPECTED)
-                        || (library.getStatus().equals(LibraryStatus.ARBITRATED)&&ArbitrateType.QUESTION.equals(group.getArbitrateType()))) {
+                        || (library.getStatus().equals(LibraryStatus.ARBITRATED)
+                                && ArbitrateType.QUESTION.equals(group.getArbitrateType()))) {
                     List<ScoreItem> sList = library.getMarkerScoreItem();
                     for (MarkStepDTO markStepDTO : qList) {
                         trackDao.deleteByLibraryIdAndQuestionNumber(library.getId(),
@@ -2031,8 +2038,8 @@ public class MarkServiceImpl implements MarkService {
                         }
                     }
                     if (libraryDao.updateMarkerResult(library.getId(), LibraryStatus.REJECTED, library.getMarkerId(),
-                            null, StringUtils.join(markerScoreList, ","), null, null, reason, userId, now,null,null,
-                            LibraryStatus.MARKED, LibraryStatus.INSPECTED,LibraryStatus.ARBITRATED) == 1) {
+                            null, StringUtils.join(markerScoreList, ","), null, null, reason, userId, now, null, null,
+                            LibraryStatus.MARKED, LibraryStatus.INSPECTED, LibraryStatus.ARBITRATED) == 1) {
                         markerService.updateRejectCountById(library.getMarkerId());
                         history.setRejectScoreList(StringUtils.join(markerScoreList, ","));
                         rejectHistoryDao.save(history);
@@ -2152,8 +2159,8 @@ public class MarkServiceImpl implements MarkService {
             // }
         }
         if (libraryDao.updateMarkerResult(library.getId(), LibraryStatus.REJECTED, library.getMarkerId(), null,
-                StringUtils.join(markerScoreList, ","), null, null, reason, userId, now,null,null, LibraryStatus.MARKED,
-                LibraryStatus.INSPECTED) == 1) {
+                StringUtils.join(markerScoreList, ","), null, null, reason, userId, now, null, null,
+                LibraryStatus.MARKED, LibraryStatus.INSPECTED) == 1) {
             markerService.updateRejectCountById(library.getMarkerId());
             history.setRejectScoreList(StringUtils.join(markerScoreList, ","));
             rejectHistoryDao.save(history);

+ 539 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/api/controller/admin/MarkQualityController.java

@@ -0,0 +1,539 @@
+package cn.com.qmth.stmms.api.controller.admin;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.qmth.boot.core.collection.PageResult;
+
+import cn.com.qmth.stmms.api.controller.BaseApiController;
+import cn.com.qmth.stmms.biz.exam.bean.MarkerChatVo;
+import cn.com.qmth.stmms.biz.exam.bean.MarkerQualityChatVo;
+import cn.com.qmth.stmms.biz.exam.bean.MarkerQualityVo;
+import cn.com.qmth.stmms.biz.exam.bean.ResultMessage;
+import cn.com.qmth.stmms.biz.exam.bean.SubjectQualityVo;
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
+import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
+import cn.com.qmth.stmms.biz.exam.model.Marker;
+import cn.com.qmth.stmms.biz.exam.query.MarkerSearchQuery;
+import cn.com.qmth.stmms.biz.exam.query.SubjectQualitySearchQuery;
+import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
+import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
+import cn.com.qmth.stmms.biz.exam.service.MarkerService;
+import cn.com.qmth.stmms.biz.exception.StatusException;
+import cn.com.qmth.stmms.biz.lock.LockService;
+import cn.com.qmth.stmms.biz.mark.service.MarkLibraryService;
+import cn.com.qmth.stmms.biz.mark.thread.MarkQualityThread;
+import cn.com.qmth.stmms.biz.user.model.User;
+import cn.com.qmth.stmms.biz.user.service.UserService;
+import cn.com.qmth.stmms.biz.utils.Calculator;
+import cn.com.qmth.stmms.biz.utils.PageUtil;
+import cn.com.qmth.stmms.common.annotation.Logging;
+import cn.com.qmth.stmms.common.domain.ApiUser;
+import cn.com.qmth.stmms.common.enums.LibraryStatus;
+import cn.com.qmth.stmms.common.enums.LockType;
+import cn.com.qmth.stmms.common.enums.LogType;
+import cn.com.qmth.stmms.common.utils.ExportExcel;
+import io.swagger.annotations.ApiOperation;
+
+@Controller("adminMarkQualityController")
+@RequestMapping("/api/admin/exam/quality")
+public class MarkQualityController extends BaseApiController {
+
+    protected static Logger log = LoggerFactory.getLogger(MarkQualityController.class);
+
+    @Autowired
+    private MarkGroupService groupService;
+
+    @Autowired
+    private MarkerService markerService;
+
+    @Autowired
+    private LockService lockService;
+
+    @Qualifier("task-executor")
+    @Autowired
+    private AsyncTaskExecutor taskExecutor;
+
+    @Autowired
+    private ExamSubjectService subjectService;
+
+    @Autowired
+    private MarkLibraryService libraryService;
+
+    @Autowired
+    private ExamQuestionService questionService;
+
+    @Autowired
+    private UserService userService;
+
+    @Logging(menu = "质量监控查询", type = LogType.QUERY)
+    @ApiOperation(value = "按科目分页查询")
+    @RequestMapping(value = "list/subject", method = RequestMethod.POST)
+    @ResponseBody
+    public PageResult<SubjectQualityVo> listSubject(SubjectQualitySearchQuery query) {
+        return pageSubject(query);
+    }
+
+    private PageResult<SubjectQualityVo> pageSubject(SubjectQualitySearchQuery query) {
+        int examId = getSessionExamId();
+        ApiUser wu = getApiUser();
+        List<ExamSubject> subjectList = getExamSubject(examId, wu);
+        query.setTotalCount(subjectList == null ? 0 : subjectList.size());
+        List<ExamSubject> pageList = PageUtil.of(subjectList, query).getResult();
+        List<SubjectQualityVo> ret = new ArrayList<>();
+        for (ExamSubject sub : pageList) {
+            SubjectQualityVo vo = new SubjectQualityVo();
+            vo.setSubjectCode(sub.getCode());
+            vo.setSubjectName(sub.getName());
+            vo.setSubjectText(sub.getCode() + "-" + sub.getName());
+            ret.add(vo);
+        }
+        List<MarkGroup> gs = null;
+        if (StringUtils.isBlank(query.getSubjectCode())) {
+            gs = groupService.findByExam(examId);
+        } else {
+            gs = groupService.findByExamAndSubject(examId, query.getSubjectCode());
+        }
+        if (!CollectionUtils.isEmpty(gs)) {
+            fillTotalCount(ret, gs);
+            MarkerSearchQuery markerQuery = new MarkerSearchQuery();
+            markerQuery.setPageNumber(1);
+            markerQuery.setPageSize(Integer.MAX_VALUE);
+            markerQuery.setExamId(examId);
+            markerQuery.setSubjectCode(query.getSubjectCode());
+            markerQuery = markerService.findByQuery(markerQuery);
+            if (!CollectionUtils.isEmpty(markerQuery.getResult())) {
+                fillDataCountForSubject(ret, markerQuery.getResult());
+            }
+        }
+        return PageUtil.of(ret, query);
+    }
+
+    @ApiOperation(value = "按科目导出")
+    @RequestMapping(value = "list/subject/export", method = RequestMethod.POST)
+    public void exportSubjectFile(SubjectQualitySearchQuery query, HttpServletResponse response) {
+        String fileName = "科目质量监控数据";
+        query.setPageNumber(1);
+        query.setPageSize(Integer.MAX_VALUE);
+        PageResult<SubjectQualityVo> page = pageSubject(query);
+        try {
+            new ExportExcel("科目质量监控数据", SubjectQualityVo.class).setDataList(page.getResult()).write(response, fileName)
+                    .dispose();
+        } catch (IOException e) {
+            throw new StatusException("科目质量监控数据导出出错", e);
+        }
+    }
+
+    private void fillDataCountForSubject(List<SubjectQualityVo> ret, List<Marker> ms) {
+        Map<String, SubjectQualityVo> map = new HashMap<>();
+        for (SubjectQualityVo vo : ret) {
+            map.put(vo.getSubjectCode(), vo);
+        }
+        for (Marker m : ms) {
+            SubjectQualityVo vo = map.get(m.getSubjectCode());
+            vo.setFinishCount(vo.getFinishCount() + m.getFinishCount());
+            vo.setFinishCountNa(vo.getFinishCountNa() + m.getFinishCountNa());
+            vo.setWaitArbitrateCount(vo.getWaitArbitrateCount() + m.getWaitArbitrateCount());
+            vo.setArbitrateCount(vo.getFinishCount() - vo.getFinishCountNa());
+            vo.setFinishArbitrateCount(vo.getArbitrateCount() - vo.getWaitArbitrateCount());
+            if (vo.getFinishCount() == 0) {
+                vo.setArbitrateRatio("0%");
+            } else {
+                vo.setArbitrateRatio(Calculator.percentage(vo.getArbitrateCount(), vo.getFinishCount(), 2));
+            }
+        }
+    }
+
+    private void fillTotalCount(List<SubjectQualityVo> ret, List<MarkGroup> gs) {
+        Map<String, SubjectQualityVo> map = new HashMap<>();
+        for (SubjectQualityVo vo : ret) {
+            map.put(vo.getSubjectCode(), vo);
+        }
+        for (MarkGroup g : gs) {
+            SubjectQualityVo vo = map.get(g.getSubjectCode());
+            vo.setTotalCount(vo.getTotalCount() + g.getLibraryCount());
+        }
+    }
+
+    @Logging(menu = "质量监控查询", type = LogType.QUERY)
+    @ApiOperation(value = "按分组分页查询")
+    @RequestMapping(value = "list/group", method = RequestMethod.POST)
+    @ResponseBody
+    public PageResult<SubjectQualityVo> listGroup(SubjectQualitySearchQuery query) {
+        return pageGroup(query);
+    }
+
+    private PageResult<SubjectQualityVo> pageGroup(SubjectQualitySearchQuery query) {
+        int examId = getSessionExamId();
+        List<MarkGroup> gs = null;
+        if (StringUtils.isBlank(query.getSubjectCode())) {
+            gs = groupService.findByExam(examId);
+        } else {
+            gs = groupService.findByExamAndSubject(examId, query.getSubjectCode());
+        }
+        List<SubjectQualityVo> ret = new ArrayList<>();
+        query.setTotalCount(gs == null ? 0 : gs.size());
+        if (!CollectionUtils.isEmpty(gs)) {
+            Map<String, String> subjectNames = new HashMap<>();
+            List<MarkGroup> pageList = PageUtil.of(gs, query).getResult();
+            for (MarkGroup group : pageList) {
+                List<ExamQuestion> questions = questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId,
+                        group.getSubjectCode(), false, group.getNumber());
+                group.setQuestionList(questions);
+                SubjectQualityVo vo = new SubjectQualityVo();
+                vo.setSubjectCode(group.getSubjectCode());
+                vo.setSubjectName(getSubjectName(subjectNames, examId, group.getSubjectCode()));
+                vo.setSubjectText(vo.getSubjectCode() + "-" + vo.getSubjectName());
+                vo.setGroupNumber(group.getNumber());
+                vo.setGroupName(group.getTitle());
+                vo.setGroupText(vo.getGroupNumber() + "-" + vo.getGroupName());
+                ret.add(vo);
+            }
+            fillTotalCount(ret, gs);
+            MarkerSearchQuery markerQuery = new MarkerSearchQuery();
+            markerQuery.setPageNumber(1);
+            markerQuery.setPageSize(Integer.MAX_VALUE);
+            markerQuery.setExamId(examId);
+            markerQuery.setSubjectCode(query.getSubjectCode());
+            markerQuery = markerService.findByQuery(markerQuery);
+            if (!CollectionUtils.isEmpty(markerQuery.getResult())) {
+                fillDataCountForGroup(ret, markerQuery.getResult());
+            }
+        }
+        return PageUtil.of(ret, query);
+    }
+
+    @ApiOperation(value = "按分组导出")
+    @RequestMapping(value = "list/group/export", method = RequestMethod.POST)
+    public void exportGroupFile(SubjectQualitySearchQuery query, HttpServletResponse response) {
+        String fileName = "分组质量监控数据";
+        query.setPageNumber(1);
+        query.setPageSize(Integer.MAX_VALUE);
+        PageResult<SubjectQualityVo> page = pageGroup(query);
+        try {
+            new ExportExcel("分组质量监控数据", SubjectQualityVo.class).setDataList(page.getResult()).write(response, fileName)
+                    .dispose();
+        } catch (IOException e) {
+            throw new StatusException("分组质量监控数据导出出错", e);
+        }
+    }
+
+    private void fillDataCountForGroup(List<SubjectQualityVo> ret, List<Marker> ms) {
+        Map<String, SubjectQualityVo> map = new HashMap<>();
+        for (SubjectQualityVo vo : ret) {
+            map.put(vo.getSubjectCode() + "-" + vo.getGroupNumber(), vo);
+        }
+        for (Marker m : ms) {
+            SubjectQualityVo vo = map.get(m.getSubjectCode() + "-" + m.getGroupNumber());
+            vo.setFinishCount(vo.getFinishCount() + m.getFinishCount());
+            vo.setFinishCountNa(vo.getFinishCountNa() + m.getFinishCountNa());
+            vo.setWaitArbitrateCount(vo.getWaitArbitrateCount() + m.getWaitArbitrateCount());
+            vo.setArbitrateCount(vo.getFinishCount() - vo.getFinishCountNa());
+            vo.setFinishArbitrateCount(vo.getArbitrateCount() - vo.getWaitArbitrateCount());
+            if (vo.getFinishCount() == 0) {
+                vo.setArbitrateRatio("0%");
+            } else {
+                vo.setArbitrateRatio(Calculator.percentage(vo.getArbitrateCount(), vo.getFinishCount(), 2));
+            }
+        }
+    }
+
+    private String getSubjectName(Map<String, String> subjectNames, int examId, String code) {
+        String name = subjectNames.get(code);
+        if (name != null) {
+            return name;
+        }
+        ExamSubject es = subjectService.find(examId, code);
+        subjectNames.put(code, es.getName());
+        return es.getName();
+    }
+
+    @ApiOperation(value = "按评卷员分页查询")
+    @Logging(menu = "质量监控查询", type = LogType.QUERY)
+    @RequestMapping(value = "list/marker", method = RequestMethod.POST)
+    @ResponseBody
+    public PageResult<MarkerQualityVo> listMarker(MarkerSearchQuery query) {
+        return pageMarker(query);
+    }
+
+    @ApiOperation(value = "按评卷员导出")
+    @RequestMapping(value = "list/marker/export", method = RequestMethod.POST)
+    public void exportMarkerFile(MarkerSearchQuery query, HttpServletResponse response) {
+        String fileName = "评卷员质量监控数据";
+        query.setPageNumber(1);
+        query.setPageSize(Integer.MAX_VALUE);
+        PageResult<MarkerQualityVo> page = pageMarker(query);
+        try {
+            new ExportExcel("评卷员质量监控数据", MarkerQualityVo.class).setDataList(page.getResult()).write(response, fileName)
+                    .dispose();
+        } catch (IOException e) {
+            throw new StatusException("评卷员质量监控数据导出出错", e);
+        }
+    }
+
+    private PageResult<MarkerQualityVo> pageMarker(MarkerSearchQuery query) {
+        int examId = getSessionExamId();
+        ApiUser wu = getApiUser();
+        List<ExamSubject> subjectList = getExamSubject(examId, wu);
+        if (subjectList.isEmpty()) {
+            return PageUtil.emptyPage();
+        }
+        query.setExamId(examId);
+        query.orderById();
+        if (StringUtils.isBlank(query.getSubjectCode()) && !subjectList.isEmpty()) {
+            query.setSubjectCode(subjectList.get(0).getCode());
+        }
+        List<MarkerQualityVo> ret = new ArrayList<>();
+        if (query.getSubjectCode() != null) {
+            query = markerService.findByQuery(query);
+            for (Marker marker : query.getResult()) {
+                marker.setUser(userService.findById(marker.getUserId()));
+                MarkGroup group = groupService.findOne(examId, marker.getSubjectCode(), marker.getGroupNumber());
+                group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId,
+                        marker.getSubjectCode(), false, group.getNumber()));
+                marker.setGroup(group);
+                if (marker.getFinishCount() == null) {
+                    marker.setFinishCount(0);
+                }
+                if (marker.getFinishCountNa() == null) {
+                    marker.setFinishCountNa(0);
+                }
+                if (marker.getRejectCount() == null) {
+                    marker.setRejectCount(0);
+                }
+                if (query.getNoArbitrate() != null && query.getNoArbitrate()) {
+                    marker.setFinishCount(marker.getFinishCountNa());
+                    marker.setValidCount(marker.getValidCountNa());
+                    marker.setAvgScore(marker.getAvgScoreNa());
+                    marker.setAvgSpeed(marker.getAvgSpeedNa());
+                    marker.setStdevScore(marker.getStdevScoreNa());
+                    marker.setArbitrateCount(0);
+                    marker.setArbitrateRatio("0%");
+                } else {
+                    marker.setArbitrateCount(marker.getFinishCount() - marker.getFinishCountNa());
+                    if (marker.getFinishCount() == 0) {
+                        marker.setArbitrateRatio("0%");
+                    } else {
+                        marker.setArbitrateRatio(
+                                Calculator.percentage(marker.getArbitrateCount(), marker.getFinishCount(), 2));
+                    }
+                }
+                ret.add(MarkerQualityVo.of(marker));
+            }
+        }
+        return PageUtil.of(ret, query);
+    }
+
+    @Logging(menu = "重新计算质量监控", type = LogType.UPDATE)
+    @ApiOperation(value = "重新计算")
+    @RequestMapping(value = "update", method = RequestMethod.POST)
+    @ResponseBody
+    public ResultMessage update(@RequestParam String subjectCode, @RequestParam Integer groupNumber) {
+        int examId = getSessionExamId();
+        MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
+        if (group != null) {
+            final String lockKey = getLockKey(examId, subjectCode, groupNumber);
+            if (lockService.trylock(LockType.BATCH_QUALITY, lockKey)) {
+                taskExecutor.submit(new MarkQualityThread(
+                        markerService.findByExamAndSubjectAndGroup(examId, subjectCode, groupNumber), lockKey));
+            }
+        }
+        return resultOk();
+    }
+
+    private String getLockKey(Integer examId, String subjectCode, Integer groupNumber) {
+        return examId + "_" + subjectCode + "_" + groupNumber;
+    }
+
+    @Logging(menu = "查询给分曲线", type = LogType.QUERY)
+    @ApiOperation(value = "给分曲线")
+    @RequestMapping(value = "chart", method = RequestMethod.POST)
+    @ResponseBody
+    public MarkerQualityChatVo chart(@RequestParam String subjectCode, @RequestParam Integer groupNumber,
+            @RequestParam(required = false) Boolean marked, @RequestParam(required = false) Boolean noArbitrate) {
+        int examId = getSessionExamId();
+        MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
+        MarkerQualityChatVo ret = new MarkerQualityChatVo();
+        if (group != null) {
+            List<MarkerChatVo> list = new ArrayList<>();
+            List<Marker> markers = null;
+            if (marked != null && marked) {
+                markers = markerService.findByExamAndSubjectAndGroupMarked(examId, subjectCode, groupNumber);
+            } else {
+                markers = markerService.findByExamAndSubjectAndGroup(examId, subjectCode, groupNumber);
+            }
+            List<Double> scores = null;
+            if (noArbitrate != null && noArbitrate) {
+                scores = libraryService.findScore(examId, subjectCode, groupNumber, LibraryStatus.MARKED,
+                        LibraryStatus.INSPECTED);
+            } else {
+                scores = libraryService.findScore(examId, subjectCode, groupNumber, LibraryStatus.MARKED,
+                        LibraryStatus.INSPECTED, LibraryStatus.ARBITRATED);
+            }
+            for (Marker marker : markers) {
+                List<Object[]> libraries = null;
+                if (noArbitrate != null && noArbitrate) {
+                    libraries = libraryService.findScoreCount(examId, subjectCode, groupNumber, marker.getId(),
+                            LibraryStatus.MARKED, LibraryStatus.INSPECTED);
+                } else {
+                    libraries = libraryService.findScoreCount(examId, subjectCode, groupNumber, marker.getId(),
+                            LibraryStatus.MARKED, LibraryStatus.INSPECTED, LibraryStatus.ARBITRATED);
+                }
+                Map<Double, Long> scoreCount = new HashMap<Double, Long>();
+                for (Object[] array : libraries) {
+                    Double score = (Double) array[0];
+                    Long count = (Long) array[1];
+                    scoreCount.put(score, count);
+                }
+                User user = userService.findById(marker.getUserId());
+                MarkerChatVo vo = new MarkerChatVo();
+                vo.setId(marker.getId());
+                vo.setLoginName(user.getLoginName());
+                vo.setName(user.getName());
+                vo.setScoreCount(scoreCount);
+                list.add(vo);
+            }
+            ret.setScores(scores);
+            ret.setGroup(group);
+            ret.setMarkers(list);
+        }
+        return ret;
+    }
+
+    // @RequestMapping("/getChart")
+    // @ResponseBody
+    // public List<MarkerVO> getChart(HttpServletRequest request, @RequestParam
+    // String subjectCode,
+    // @RequestParam Integer groupNumber, @RequestParam(required = false)
+    // Boolean marked,
+    // @RequestParam(required = false) Boolean noArbitrate) {
+    // int examId = getSessionExamId(request);
+    // List<MarkerVO> list = new ArrayList<MarkerVO>();
+    // List<Marker> markers = null;
+    // if (marked != null && marked) {
+    // markers = markerService.findByExamAndSubjectAndGroupMarked(examId,
+    // subjectCode, groupNumber);
+    // } else {
+    // markers = markerService.findByExamAndSubjectAndGroup(examId, subjectCode,
+    // groupNumber);
+    // }
+    // for (Marker marker : markers) {
+    // List<Object[]> libraries = null;
+    // if (noArbitrate != null && noArbitrate) {
+    // libraries = libraryService.findScoreCount(examId, subjectCode,
+    // groupNumber, marker.getId(),
+    // LibraryStatus.MARKED, LibraryStatus.INSPECTED);
+    // } else {
+    // libraries = libraryService.findScoreCount(examId, subjectCode,
+    // groupNumber, marker.getId(),
+    // LibraryStatus.MARKED, LibraryStatus.INSPECTED, LibraryStatus.ARBITRATED);
+    // }
+    // MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
+    // query.setMarkerId(marker.getId());
+    // query.addStatus(LibraryStatus.MARKED);
+    // query.addStatus(LibraryStatus.INSPECTED);
+    // if (noArbitrate == null || !noArbitrate) {
+    // query.addStatus(LibraryStatus.ARBITRATED);
+    // }
+    // long totalCount = libraryService.countByQuery(query);
+    // Map<Double, Double> scorePercent = new HashMap<Double, Double>();
+    // for (Object[] array : libraries) {
+    // Double score = (Double) array[0];
+    // Long count = (Long) array[1];
+    // double percent = 0;
+    // if (totalCount != 0) {
+    // percent = count * 100 / totalCount;
+    // }
+    // scorePercent.put(score, percent);
+    // }
+    // User user = userService.findById(marker.getUserId());
+    // MarkerVO vo = new MarkerVO();
+    // vo.setId(marker.getId());
+    // vo.setLoginName(user.getLoginName());
+    // vo.setName(user.getName());
+    // vo.setScorePercent(scorePercent);
+    // list.add(vo);
+    // }
+    // return list;
+    // }
+    //
+    // @Logging(menu = "质量监控查询给分记录详情", type = LogType.QUERY)
+    // @RequestMapping(value = "/history", method = RequestMethod.POST)
+    // @ResponseBody
+    // @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN
+    // })
+    // public List<Task> getTask(HttpServletRequest request, @RequestParam
+    // Integer markerId,
+    // @RequestParam Double markerScore, @RequestParam(required = false) Integer
+    // pageNumber,
+    // @RequestParam(required = false) Integer pageSize, @RequestParam(required
+    // = false) Boolean noArbitrate) {
+    // Marker marker = markerService.findById(markerId);
+    // List<Task> list = new ArrayList<>();
+    // MarkGroup group = groupService.findOne(marker.getExamId(),
+    // marker.getSubjectCode(), marker.getGroupNumber());
+    // if (group != null && group.getStatus() != MarkStatus.TRIAL) {
+    // // 正评查找已给分的评卷任务
+    // MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
+    // query.setExamId(marker.getExamId());
+    // query.setSubjectCode(marker.getSubjectCode());
+    // query.setMarkerId(marker.getId());
+    // query.addStatus(LibraryStatus.MARKED);
+    // query.addStatus(LibraryStatus.INSPECTED);
+    // if (noArbitrate == null || !noArbitrate) {
+    // query.addStatus(LibraryStatus.ARBITRATED);
+    // }
+    // query.setGroupNumber(marker.getGroupNumber());
+    // query.setMarkerScore(markerScore);
+    // if (pageNumber != null) {
+    // query.setPageNumber(pageNumber);
+    // }
+    // if (pageSize != null) {
+    // query.setPageSize(pageSize);
+    // }
+    // query.orderByMarkerTimeDesc();
+    //
+    // list = taskService.findByQuery(query);
+    // } else if (group != null && group.getStatus() == MarkStatus.TRIAL) {
+    // // 试评查找给分历史记录
+    // List<TrialLibrary> historyList = new ArrayList<TrialLibrary>();
+    // historyList = trialService.findHistory(marker.getExamId(),
+    // marker.getSubjectCode(), marker.getGroupNumber(),
+    // marker.getId(), pageNumber, pageSize, null, markerScore);
+    // for (TrialLibrary library : historyList) {
+    // Task task = taskService.build(library);
+    // list.add(task);
+    // }
+    // }
+    // int examId = getSessionExamId(request);
+    // Exam exam = examService.findById(examId);
+    // WebUser user = RequestUtils.getWebUser(request);
+    // for (Task task : list) {
+    // if (exam.isForbiddenInfo() && !Role.SCHOOL_ADMIN.equals(user.getRole()))
+    // {
+    // task.setSecretNumber(DEFAULT_SECRET_NUMBER);
+    // }
+    // }
+    // return list;
+    // }
+}