|
@@ -0,0 +1,806 @@
|
|
|
+package cn.com.qmth.examcloud.task.service.impl;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+import javax.persistence.criteria.Predicate;
|
|
|
+import javax.transaction.Transactional;
|
|
|
+
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.domain.Page;
|
|
|
+import org.springframework.data.domain.PageRequest;
|
|
|
+import org.springframework.data.domain.Sort;
|
|
|
+import org.springframework.data.domain.Sort.Direction;
|
|
|
+import org.springframework.data.jpa.domain.Specification;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import com.google.common.collect.Lists;
|
|
|
+
|
|
|
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
|
|
|
+import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
|
+import cn.com.qmth.examcloud.core.basic.api.CourseCloudService;
|
|
|
+import cn.com.qmth.examcloud.core.basic.api.OrgCloudService;
|
|
|
+import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
|
|
|
+import cn.com.qmth.examcloud.core.basic.api.bean.OrgBean;
|
|
|
+import cn.com.qmth.examcloud.core.basic.api.request.GetCoursesByIdListReq;
|
|
|
+import cn.com.qmth.examcloud.core.basic.api.request.GetOrgsByIdListReq;
|
|
|
+import cn.com.qmth.examcloud.core.basic.api.response.GetCoursesByIdListResp;
|
|
|
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgsByIdListResp;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamStudentDataCloudService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamStudentDataBean;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamStudentScoreDataBean;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetExamStudentDataReq;
|
|
|
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetExamStudentDataResp;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.ExamCourseDataReportCloudService;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.ExamOrgReportCloudService;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.ProjectCloudService;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.bean.ExamCourseDataReportBean;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.bean.ExamOrgReportBean;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.bean.ProjectInfoBean;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.request.DeleteExamCourseDataReportByProjectReq;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.request.DeleteExamOrgReportByProjectReq;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.request.GetProjectInfoBeanReq;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.request.SaveExamCourseDataReportListReq;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.request.SaveExamOrgReportListReq;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.request.UpdateProjectCourseOrgCountReq;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.request.UpdateProjectStatusByIdsReq;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.request.UpdateProjectStatusReq;
|
|
|
+import cn.com.qmth.examcloud.core.reports.api.response.GetProjectInfoBeanResp;
|
|
|
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
|
|
|
+import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
|
|
|
+import cn.com.qmth.examcloud.examwork.api.request.GetExamsByIdListReq;
|
|
|
+import cn.com.qmth.examcloud.examwork.api.response.GetExamsByIdListResp;
|
|
|
+import cn.com.qmth.examcloud.task.base.bean.ReportsComputeBean;
|
|
|
+import cn.com.qmth.examcloud.task.base.enums.ReportsComputeStatus;
|
|
|
+import cn.com.qmth.examcloud.task.base.util.BatchGetDataUtil;
|
|
|
+import cn.com.qmth.examcloud.task.dao.ReportsComputeRepo;
|
|
|
+import cn.com.qmth.examcloud.task.dao.entity.ReportsComputeEntity;
|
|
|
+import cn.com.qmth.examcloud.task.service.ReportsComputeService;
|
|
|
+import cn.com.qmth.examcloud.task.service.dto.TopicScoreDto;
|
|
|
+import cn.com.qmth.examcloud.task.service.exception.ReportsComputeStopException;
|
|
|
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class ReportsComputeServiceImpl implements ReportsComputeService {
|
|
|
+ private final static int batchSize = 100;
|
|
|
+
|
|
|
+ private final static Map<Long, Boolean> jobStopFlag = new HashMap<Long, Boolean>();
|
|
|
+ @Autowired
|
|
|
+ private CourseCloudService courseCloudService;
|
|
|
+ @Autowired
|
|
|
+ private ExamCloudService examCloudService;
|
|
|
+ @Autowired
|
|
|
+ private OrgCloudService orgCloudService;
|
|
|
+ @Autowired
|
|
|
+ private ReportsComputeRepo reportsComputeRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ProjectCloudService projectCloudService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamStudentDataCloudService examStudentDataCloudService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamCourseDataReportCloudService examCourseDataReportCloudService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamOrgReportCloudService examOrgReportBeanCloudService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<ReportsComputeEntity> findTodoData(Long startId, Integer limit) {
|
|
|
+ return reportsComputeRepo.findTodoData(startId, limit);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public void initReportsCompute() {
|
|
|
+ List<ReportsComputeEntity> cs = reportsComputeRepo.findByStatus(ReportsComputeStatus.COMPUTING);
|
|
|
+ UpdateProjectStatusByIdsReq req = new UpdateProjectStatusByIdsReq();
|
|
|
+ if (cs != null && cs.size() > 0) {
|
|
|
+ reportsComputeRepo.initComputingJob();
|
|
|
+ List<Long> cids = cs.stream().map(c -> c.getProjectId()).collect(Collectors.toList());
|
|
|
+ req.setComputingProjectIds(cids);
|
|
|
+ }
|
|
|
+ List<ReportsComputeEntity> ss = reportsComputeRepo.findByStatus(ReportsComputeStatus.STOPING);
|
|
|
+ if (ss != null && ss.size() > 0) {
|
|
|
+ reportsComputeRepo.initStopingJob();
|
|
|
+ List<Long> sids = ss.stream().map(c -> c.getProjectId()).collect(Collectors.toList());
|
|
|
+ req.setStopingProjectIds(sids);
|
|
|
+ }
|
|
|
+ if (req.getComputingProjectIds() != null || req.getStopingProjectIds() != null) {
|
|
|
+ projectCloudService.updateProjectStatusByIds(req);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ReportsComputeEntity findById(Long id) {
|
|
|
+ ReportsComputeEntity re = GlobalHelper.getEntity(reportsComputeRepo, id, ReportsComputeEntity.class);
|
|
|
+ return re;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public void compute(ReportsComputeEntity et) {
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(et.getId());
|
|
|
+ // 获取项目信息
|
|
|
+ GetProjectInfoBeanReq req = new GetProjectInfoBeanReq();
|
|
|
+ req.setProjectId(et.getProjectId());
|
|
|
+ GetProjectInfoBeanResp resp = projectCloudService.getProjectBean(req);
|
|
|
+ if (resp.getBean() == null) {
|
|
|
+ updateToFail(et, "未找到项目信息");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ProjectInfoBean pro = resp.getBean();
|
|
|
+ // 只处理数据来源是同步
|
|
|
+ if ("SYNC".equals(pro.getDataOrigin())) {
|
|
|
+ computeSync(pro, et);
|
|
|
+ } else if ("IMPORT".equals(pro.getDataOrigin())) {
|
|
|
+ computeImport(pro, et);
|
|
|
+ } else {
|
|
|
+ updateToFail(et, "项目数据来源错误");
|
|
|
+ }
|
|
|
+ // 计算结束清除缓存终止标志
|
|
|
+ jobStopFlag.remove(et.getId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 数据来源是导入
|
|
|
+ *
|
|
|
+ * @param pro
|
|
|
+ * @param et
|
|
|
+ */
|
|
|
+ private void computeImport(ProjectInfoBean pro, ReportsComputeEntity et) {
|
|
|
+ updateToFail(et, "不支持处理数据来源是导入的项目");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 数据来源是同步
|
|
|
+ *
|
|
|
+ * @param pro
|
|
|
+ * @param et
|
|
|
+ */
|
|
|
+ private void computeSync(ProjectInfoBean pro, ReportsComputeEntity et) {
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(et.getId());
|
|
|
+ if (StringUtils.isBlank(pro.getExamIds())) {
|
|
|
+ updateToFail(et, "考试id为空");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Long> examIds = Arrays.asList(pro.getExamIds().split(",")).stream().map(str -> Long.parseLong(str))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ Map<String, Object> result = new HashMap<String, Object>();
|
|
|
+
|
|
|
+ // 学习中心id集合-记录中心数量
|
|
|
+ Set<Long> ordIds = new HashSet<Long>();
|
|
|
+ result.put("ordIds", ordIds);
|
|
|
+
|
|
|
+ // 课程id集合-记录课程数量
|
|
|
+ Set<Long> courseIds = new HashSet<Long>();
|
|
|
+ result.put("courseIds", courseIds);
|
|
|
+
|
|
|
+ // 考试-学习中心数值分析结果
|
|
|
+ Map<String, ExamOrgReportBean> examOrgReport = new HashMap<String, ExamOrgReportBean>();
|
|
|
+ result.put("examOrgReport", examOrgReport);
|
|
|
+ // 课程数值分析结果
|
|
|
+ Map<String, ExamCourseDataReportBean> examCourseDataReport = new HashMap<String, ExamCourseDataReportBean>();
|
|
|
+ result.put("examCourseDataReport", examCourseDataReport);
|
|
|
+ // 分数结果集合-计算标准差
|
|
|
+ Map<String, List<Double>> scores = new HashMap<String, List<Double>>();
|
|
|
+ result.put("scores", scores);
|
|
|
+
|
|
|
+ // 考试课程对应的basePaperId集合-计算难度
|
|
|
+ Map<String, Set<String>> basePapers = new HashMap<String, Set<String>>();
|
|
|
+ result.put("basePapers", basePapers);
|
|
|
+
|
|
|
+ // basePaperId对应的难度系数-计算难度
|
|
|
+ Map<String, Double> basePapersDegree = new HashMap<String, Double>();
|
|
|
+ result.put("basePapersDegree", basePapersDegree);
|
|
|
+
|
|
|
+ // basePaperId对应的小题平均分和满分-计算难度
|
|
|
+ Map<String, Map<String, TopicScoreDto>> basePapersTopicScore = new HashMap<String, Map<String, TopicScoreDto>>();
|
|
|
+ result.put("basePapersTopicScore", basePapersTopicScore);
|
|
|
+
|
|
|
+ for (Long examId : examIds) {
|
|
|
+ // 根据考试id获取数据并计算结果
|
|
|
+ computeByExamId(result, examId, et.getId(), pro);
|
|
|
+ }
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(et.getId());
|
|
|
+ // 计算标准差
|
|
|
+ for (String key : examCourseDataReport.keySet()) {
|
|
|
+ ExamCourseDataReportBean bean = examCourseDataReport.get(key);
|
|
|
+ bean.setStd(std(scores.get(key), bean.getAvgScore()));
|
|
|
+ }
|
|
|
+ // 计算差异系数
|
|
|
+ for (String key : examCourseDataReport.keySet()) {
|
|
|
+ ExamCourseDataReportBean bean = examCourseDataReport.get(key);
|
|
|
+ bean.setCdi(getPercentage(bean.getStd(), bean.getAvgScore()));
|
|
|
+ bean.setAvgScore(getTwoDecimal(bean.getAvgScore()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(et.getId());
|
|
|
+ // 计算basePaperId对应的难度系数
|
|
|
+ for (String basePaperId : basePapersTopicScore.keySet()) {
|
|
|
+ Map<String, TopicScoreDto> temmap = basePapersTopicScore.get(basePaperId);
|
|
|
+ basePapersDegree.put(basePaperId, difficultyDegree(temmap));
|
|
|
+ }
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(et.getId());
|
|
|
+ // 计算平均调卷难度
|
|
|
+ for (String key : examCourseDataReport.keySet()) {
|
|
|
+ ExamCourseDataReportBean bean = examCourseDataReport.get(key);
|
|
|
+ String dataKey = bean.getExamId() + "-" + bean.getCourseId();
|
|
|
+ bean.setAvgDifficultyDegree(avgDifficultyDegree(dataKey, basePapers, basePapersDegree));
|
|
|
+ }
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(et.getId());
|
|
|
+ // 设置中心、课程、考试code和名称
|
|
|
+ fillOrgNameCode(examOrgReport);
|
|
|
+ fillCourseNameCode(examCourseDataReport);
|
|
|
+ fillExamNameCode(examOrgReport, examCourseDataReport);
|
|
|
+
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(et.getId());
|
|
|
+
|
|
|
+ // 清除旧数据
|
|
|
+ DeleteExamOrgReportByProjectReq dreq1 = new DeleteExamOrgReportByProjectReq();
|
|
|
+ dreq1.setProjectId(pro.getId());
|
|
|
+ dreq1.setRootOrgId(pro.getRootOrgId());
|
|
|
+ examOrgReportBeanCloudService.deleteExamOrgReportByProject(dreq1);
|
|
|
+
|
|
|
+ DeleteExamCourseDataReportByProjectReq dreq2 = new DeleteExamCourseDataReportByProjectReq();
|
|
|
+ dreq2.setProjectId(pro.getId());
|
|
|
+ dreq2.setRootOrgId(pro.getRootOrgId());
|
|
|
+ examCourseDataReportCloudService.deleteExamCourseDataReportByProject(dreq2);
|
|
|
+
|
|
|
+ // 保存计算结果
|
|
|
+ SaveExamOrgReportListReq req1 = new SaveExamOrgReportListReq();
|
|
|
+ req1.setBeans(new ArrayList<ExamOrgReportBean>(examOrgReport.values()));
|
|
|
+ examOrgReportBeanCloudService.saveExamOrgReportList(req1);
|
|
|
+
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(et.getId());
|
|
|
+
|
|
|
+ // 保存计算结果
|
|
|
+ SaveExamCourseDataReportListReq req2 = new SaveExamCourseDataReportListReq();
|
|
|
+ req2.setBeans(new ArrayList<ExamCourseDataReportBean>(examCourseDataReport.values()));
|
|
|
+ examCourseDataReportCloudService.saveExamCourseDataReportList(req2);
|
|
|
+
|
|
|
+ // 保存项目课程数量、中心数量
|
|
|
+ UpdateProjectCourseOrgCountReq req = new UpdateProjectCourseOrgCountReq();
|
|
|
+ req.setProjectId(pro.getId());
|
|
|
+ req.setOrgCount(ordIds.size());
|
|
|
+ req.setCourseCount(courseIds.size());
|
|
|
+ projectCloudService.updateProjectCourseOrgCount(req);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void fillOrgNameCode(Map<String, ExamOrgReportBean> examOrgReport) {
|
|
|
+ List<Long> ids = examOrgReport.entrySet().stream().map(dto -> dto.getValue().getOrgId()).distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<OrgBean> orgList = new ArrayList<OrgBean>();
|
|
|
+ GetOrgsByIdListReq req = new GetOrgsByIdListReq();
|
|
|
+ BatchGetDataUtil<OrgBean, Long> tool = new BatchGetDataUtil<OrgBean, Long>() {
|
|
|
+ @Override
|
|
|
+ public List<OrgBean> getData(List<Long> paramList) {
|
|
|
+ req.setOrgIdList(paramList);
|
|
|
+ GetOrgsByIdListResp resp = orgCloudService.getOrgsByIdList(req);
|
|
|
+ return resp.getOrgList();
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+ tool.getDataForBatch(orgList, ids, batchSize);
|
|
|
+ Map<Long, OrgBean> orgMap = orgList.stream()
|
|
|
+ .collect(Collectors.toMap(OrgBean::getId, account -> account, (key1, key2) -> key2));
|
|
|
+ for (ExamOrgReportBean b : examOrgReport.values()) {
|
|
|
+ OrgBean ob = orgMap.get(b.getOrgId());
|
|
|
+ b.setOrgCode(ob.getCode());
|
|
|
+ b.setOrgName(ob.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void fillCourseNameCode(Map<String, ExamCourseDataReportBean> examCourseDataReport) {
|
|
|
+ List<Long> ids = examCourseDataReport.entrySet().stream().map(dto -> dto.getValue().getCourseId()).distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<CourseBean> courseList = new ArrayList<CourseBean>();
|
|
|
+ GetCoursesByIdListReq req = new GetCoursesByIdListReq();
|
|
|
+ BatchGetDataUtil<CourseBean, Long> tool = new BatchGetDataUtil<CourseBean, Long>() {
|
|
|
+ @Override
|
|
|
+ public List<CourseBean> getData(List<Long> paramList) {
|
|
|
+ req.setCourseIdList(paramList);
|
|
|
+ GetCoursesByIdListResp resp = courseCloudService.getCoursesByIdList(req);
|
|
|
+ return resp.getCourseList();
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+ tool.getDataForBatch(courseList, ids, batchSize);
|
|
|
+ Map<Long, CourseBean> map = courseList.stream()
|
|
|
+ .collect(Collectors.toMap(CourseBean::getId, account -> account, (key1, key2) -> key2));
|
|
|
+ for (ExamCourseDataReportBean b : examCourseDataReport.values()) {
|
|
|
+ CourseBean ob = map.get(b.getCourseId());
|
|
|
+ b.setCourseCode(ob.getCode());
|
|
|
+ b.setCourseName(ob.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void fillExamNameCode(Map<String, ExamOrgReportBean> examOrgReport,
|
|
|
+ Map<String, ExamCourseDataReportBean> examCourseDataReport) {
|
|
|
+ List<Long> ids = examOrgReport.entrySet().stream().map(dto -> dto.getValue().getExamId()).distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<ExamBean> list = new ArrayList<ExamBean>();
|
|
|
+ GetExamsByIdListReq req = new GetExamsByIdListReq();
|
|
|
+ BatchGetDataUtil<ExamBean, Long> tool = new BatchGetDataUtil<ExamBean, Long>() {
|
|
|
+ @Override
|
|
|
+ public List<ExamBean> getData(List<Long> paramList) {
|
|
|
+ req.setExamIdList(paramList);
|
|
|
+ GetExamsByIdListResp resp = examCloudService.getExamsByIdList(req);
|
|
|
+ return resp.getExamList();
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+ tool.getDataForBatch(list, ids, batchSize);
|
|
|
+ Map<Long, ExamBean> map = list.stream()
|
|
|
+ .collect(Collectors.toMap(ExamBean::getId, account -> account, (key1, key2) -> key2));
|
|
|
+ for (ExamOrgReportBean b : examOrgReport.values()) {
|
|
|
+ ExamBean ob = map.get(b.getExamId());
|
|
|
+ b.setExamCode(ob.getCode());
|
|
|
+ b.setExamName(ob.getName());
|
|
|
+ }
|
|
|
+ for (ExamCourseDataReportBean b : examCourseDataReport.values()) {
|
|
|
+ ExamBean ob = map.get(b.getExamId());
|
|
|
+ b.setExamCode(ob.getCode());
|
|
|
+ b.setExamName(ob.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Double avgDifficultyDegree(String dataKey, Map<String, Set<String>> basePapers,
|
|
|
+ Map<String, Double> basePapersDegree) {
|
|
|
+ // 考试课程对应的basePapers
|
|
|
+ Set<String> set = basePapers.get(dataKey);
|
|
|
+ if (set == null || set.size() == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ double total = 0.0;
|
|
|
+ // 算平均难度时剔除没有难度系数的试卷
|
|
|
+ int nullCount = 0;
|
|
|
+ for (String s : set) {
|
|
|
+ Double td = basePapersDegree.get(s);
|
|
|
+ if (td == null) {
|
|
|
+ nullCount++;
|
|
|
+ } else {
|
|
|
+ total = total + basePapersDegree.get(s);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (nullCount == set.size()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return getTwoDecimal(total / (set.size() - nullCount));
|
|
|
+ }
|
|
|
+
|
|
|
+ private Double difficultyDegree(Map<String, TopicScoreDto> map) {
|
|
|
+ // 剔除整张卷子没有小题答题的
|
|
|
+ if (map == null || map.size() == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ double totalAvg = 0.0;
|
|
|
+ double totalFull = 0.0;
|
|
|
+ for (String k : map.keySet()) {
|
|
|
+ TopicScoreDto dto = map.get(k);
|
|
|
+ if (dto.getTotal() == 0) {
|
|
|
+ throw new StatusException("100001",
|
|
|
+ "小题满分不能为0 QuestionId:" + dto.getQuestionId() + " QuestionOrder:" + dto.getQuestionOrder());
|
|
|
+ }
|
|
|
+ totalAvg = totalAvg + dto.getAvg();
|
|
|
+ totalFull = totalFull + dto.getTotal();
|
|
|
+ }
|
|
|
+ return totalAvg / totalFull;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double std(List<Double> list, Double average) {
|
|
|
+ if (list == null || list.size() == 0 || average == null) {
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+ double total = 0.0;
|
|
|
+ for (int i = 0; i < list.size(); i++) {
|
|
|
+ total += Math.pow((list.get(i) - average), 2);
|
|
|
+ }
|
|
|
+ double standardDeviation = Math.sqrt(total / list.size());
|
|
|
+ return getTwoDecimal(standardDeviation);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据考试id获取数据并计算
|
|
|
+ *
|
|
|
+ * @param result
|
|
|
+ * @param examId
|
|
|
+ * @param jobId
|
|
|
+ */
|
|
|
+ private void computeByExamId(Map<String, Object> result, Long examId, Long jobId, ProjectInfoBean pro) {
|
|
|
+ Long startId = 0l;
|
|
|
+ GetExamStudentDataReq req = new GetExamStudentDataReq();
|
|
|
+ req.setExamId(examId);
|
|
|
+ req.setSize(200);
|
|
|
+ for (;;) {
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(jobId);
|
|
|
+ req.setStartId(startId);
|
|
|
+ GetExamStudentDataResp res = examStudentDataCloudService.getExamStudentData(req);
|
|
|
+ List<ExamStudentDataBean> list = res.getList();
|
|
|
+ if (list == null || list.size() == 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ startId = res.getNextId();
|
|
|
+ for (ExamStudentDataBean st : list) {
|
|
|
+ // 判断任务终止
|
|
|
+ checkIsStoping(jobId);
|
|
|
+ // 计算考试-学习中心数值分析表
|
|
|
+ computeExamOrgReport(result, st, pro);
|
|
|
+ // 计算课程数值分析表
|
|
|
+ computeExamCourseDataReport(result, st, pro);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算考试-学习中心数值分析表
|
|
|
+ *
|
|
|
+ * @param result
|
|
|
+ * @param st
|
|
|
+ * @param pro
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private void computeExamOrgReport(Map<String, Object> result, ExamStudentDataBean st, ProjectInfoBean pro) {
|
|
|
+ // 学习中心id集合
|
|
|
+ Set<Long> ordIds = (Set<Long>) result.get("ordIds");
|
|
|
+ ordIds.add(st.getOrgId());
|
|
|
+ Map<String, ExamOrgReportBean> examOrgReport = (Map<String, ExamOrgReportBean>) result.get("examOrgReport");
|
|
|
+ String key = st.getExamId() + "-" + st.getOrgId();
|
|
|
+ ExamOrgReportBean bean = examOrgReport.get(key);
|
|
|
+ if (bean == null) {
|
|
|
+ bean = new ExamOrgReportBean();
|
|
|
+ bean.init(pro.getPartitionCount());
|
|
|
+ bean.setProjectId(pro.getId());
|
|
|
+ bean.setExamId(st.getExamId());
|
|
|
+ bean.setOrgId(st.getOrgId());
|
|
|
+ bean.setRootOrgId(pro.getRootOrgId());
|
|
|
+ examOrgReport.put(key, bean);
|
|
|
+ }
|
|
|
+ // 报名人数
|
|
|
+ bean.setSignCount(bean.getSignCount() + 1);
|
|
|
+ // 实考人数
|
|
|
+ if (!st.getAbsent()) {
|
|
|
+ bean.setParticipantCount(bean.getParticipantCount() + 1);
|
|
|
+ }
|
|
|
+ // 及格人数
|
|
|
+ if (!st.getAbsent() && st.getScore() == null) {
|
|
|
+ throw new StatusException("10030", "未缺考的考生分数不能为空 ExamStudentId:" + st.getExamStudentId());
|
|
|
+ }
|
|
|
+ if (!st.getAbsent() && st.getScore() >= pro.getPassScore()) {
|
|
|
+ bean.setPassCount(bean.getPassCount() + 1);
|
|
|
+ }
|
|
|
+ // 分段人数
|
|
|
+ if (!st.getAbsent()) {
|
|
|
+ addPartitionData(st.getScore(), pro, bean.getPartitionData());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算分段人数
|
|
|
+ *
|
|
|
+ * @param score
|
|
|
+ * @param pro
|
|
|
+ * @param partitionData
|
|
|
+ */
|
|
|
+ private void addPartitionData(Double score, ProjectInfoBean pro, List<Long> partitionData) {
|
|
|
+ List<Double> partitionDetails = pro.getPartitionDetails();
|
|
|
+ for (int i = 0; i < partitionDetails.size(); i++) {
|
|
|
+ if (i == 0 || i == partitionDetails.size() - 1) {
|
|
|
+ if (i == 0) {
|
|
|
+ if (score < partitionDetails.get(i)) {
|
|
|
+ partitionData.set(i, partitionData.get(i) + 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (i == partitionDetails.size() - 1) {
|
|
|
+ if (partitionDetails.get(i) <= score && score <= pro.getTotalScore()) {
|
|
|
+ partitionData.set(i+1, partitionData.get(i+1) + 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (partitionDetails.get(i - 1) <= score && score < partitionDetails.get(i)) {
|
|
|
+ partitionData.set(i, partitionData.get(i) + 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算课程数值分析表
|
|
|
+ *
|
|
|
+ * @param result
|
|
|
+ * @param st
|
|
|
+ * @param pro
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private void computeExamCourseDataReport(Map<String, Object> result, ExamStudentDataBean st, ProjectInfoBean pro) {
|
|
|
+ // 课程id集合
|
|
|
+ Set<Long> courseIds = (Set<Long>) result.get("courseIds");
|
|
|
+ courseIds.add(st.getCourseId());
|
|
|
+ Map<String, ExamCourseDataReportBean> examCourseDataReport = (Map<String, ExamCourseDataReportBean>) result
|
|
|
+ .get("examCourseDataReport");
|
|
|
+ String key = st.getExamId() + "-" + st.getCourseId();
|
|
|
+ ExamCourseDataReportBean bean = examCourseDataReport.get(key);
|
|
|
+ if (bean == null) {
|
|
|
+ bean = new ExamCourseDataReportBean();
|
|
|
+ bean.init(pro.getPartitionCount());
|
|
|
+ bean.setProjectId(pro.getId());
|
|
|
+ bean.setExamId(st.getExamId());
|
|
|
+ bean.setCourseId(st.getCourseId());
|
|
|
+ bean.setRootOrgId(pro.getRootOrgId());
|
|
|
+ examCourseDataReport.put(key, bean);
|
|
|
+ }
|
|
|
+ // 设置满分数值
|
|
|
+ if (st.getTotalScore() != null) {
|
|
|
+ bean.setTotalScore(st.getTotalScore());
|
|
|
+ }
|
|
|
+ // 最高分
|
|
|
+ if (!st.getAbsent() && st.getScore() > bean.getMaxScore()) {
|
|
|
+ bean.setMaxScore(st.getScore());
|
|
|
+ }
|
|
|
+ // 最低分
|
|
|
+ if (!st.getAbsent() && (bean.getMinScore() == null || st.getScore() < bean.getMinScore())) {
|
|
|
+ bean.setMinScore(st.getScore());
|
|
|
+ }
|
|
|
+ // 报名人数
|
|
|
+ bean.setSignCount(bean.getSignCount() + 1);
|
|
|
+ // 实考人数
|
|
|
+ if (!st.getAbsent()) {
|
|
|
+ bean.setParticipantCount(bean.getParticipantCount() + 1);
|
|
|
+ }
|
|
|
+ // 满分人数
|
|
|
+ if (!st.getAbsent() && st.getScore() == st.getTotalScore()) {
|
|
|
+ bean.setFullCount(bean.getFullCount() + 1);
|
|
|
+ }
|
|
|
+ // 零分人数
|
|
|
+ if (!st.getAbsent() && st.getScore() == 0.0) {
|
|
|
+ bean.setZeroCount(bean.getZeroCount() + 1);
|
|
|
+ }
|
|
|
+ // 及格人数
|
|
|
+ if (!st.getAbsent() && st.getScore() >= pro.getPassScore()) {
|
|
|
+ bean.setPassCount(bean.getPassCount() + 1);
|
|
|
+ }
|
|
|
+ // 分段人数
|
|
|
+ if (!st.getAbsent()) {
|
|
|
+ addPartitionData(st.getScore(), pro, bean.getPartitionData());
|
|
|
+ }
|
|
|
+ // 平均分
|
|
|
+ if (!st.getAbsent()) {
|
|
|
+ Double avg = bean.getAvgScore();
|
|
|
+ avg += (st.getScore() - avg) / bean.getParticipantCount();
|
|
|
+ bean.setAvgScore(avg);
|
|
|
+ }
|
|
|
+ // 难度系数计算
|
|
|
+ // 考试课程对应的basePaperId集合
|
|
|
+ Map<String, Set<String>> basePapers = (Map<String, Set<String>>) result.get("basePapers");
|
|
|
+
|
|
|
+ // basePaperId对应的小题平均分和满分
|
|
|
+ Map<String, Map<String, TopicScoreDto>> basePapersTopicScore = (Map<String, Map<String, TopicScoreDto>>) result
|
|
|
+ .get("basePapersTopicScore");
|
|
|
+
|
|
|
+ if (!st.getAbsent()) {
|
|
|
+ Map<String, List<Double>> scores = (Map<String, List<Double>>) result.get("scores");
|
|
|
+ // 分数集合-计算标准差
|
|
|
+ List<Double> list = scores.get(key);
|
|
|
+ if (list == null) {
|
|
|
+ list = new ArrayList<Double>();
|
|
|
+ scores.put(key, list);
|
|
|
+ }
|
|
|
+ list.add(st.getScore());
|
|
|
+ // 考试课程对应的basePaperId集合
|
|
|
+ Set<String> set = basePapers.get(key);
|
|
|
+ if (set == null) {
|
|
|
+ set = new HashSet<String>();
|
|
|
+ basePapers.put(key, set);
|
|
|
+ }
|
|
|
+ set.add(st.getBasePaperId());
|
|
|
+
|
|
|
+ // basePaperId对应的小题平均分和满分
|
|
|
+ Map<String, TopicScoreDto> temmap = basePapersTopicScore.get(st.getBasePaperId());
|
|
|
+ if (temmap == null) {
|
|
|
+ temmap = new HashMap<String, TopicScoreDto>();
|
|
|
+ basePapersTopicScore.put(st.getBasePaperId(), temmap);
|
|
|
+ }
|
|
|
+ // 考生小题得分详情
|
|
|
+ List<ExamStudentScoreDataBean> scoreDetails = st.getScoreDetails();
|
|
|
+ if (scoreDetails == null || scoreDetails.size() == 0) {
|
|
|
+ throw new StatusException("10040", "考生小题得分详情不能为空 ExamStudentId:" + st.getExamStudentId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算每个小题的平均分
|
|
|
+ for (ExamStudentScoreDataBean b : scoreDetails) {
|
|
|
+ if (b.getScore() != null) {
|
|
|
+ String topicScoreKey = b.getQuestionId() + "-"
|
|
|
+ + (b.getQuestionOrder() == null ? "" : b.getQuestionOrder());
|
|
|
+ TopicScoreDto tem = temmap.get(topicScoreKey);
|
|
|
+ if (tem == null) {
|
|
|
+ tem = new TopicScoreDto();
|
|
|
+ tem.setQuestionId(b.getQuestionId());
|
|
|
+ tem.setQuestionOrder(b.getQuestionOrder());
|
|
|
+ tem.setTotal(b.getTotalScore());
|
|
|
+ temmap.put(topicScoreKey, tem);
|
|
|
+ }
|
|
|
+ tem.setCount(tem.getCount() + 1);
|
|
|
+ double avg = tem.getAvg();
|
|
|
+ avg += (b.getScore() - avg) / tem.getCount();
|
|
|
+ tem.setAvg(avg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void clearStopingFlag(Long id) {
|
|
|
+ // 清除缓存终止标志
|
|
|
+ jobStopFlag.remove(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void checkIsStoping(Long id) {
|
|
|
+ if (jobStopFlag.get(id) != null) {
|
|
|
+ // 清除缓存终止标志
|
|
|
+ jobStopFlag.remove(id);
|
|
|
+ throw new ReportsComputeStopException();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public void updateToComputing(ReportsComputeEntity et) {
|
|
|
+ et.setStatus(ReportsComputeStatus.COMPUTING);
|
|
|
+ et.setStartTime(new Date());
|
|
|
+ reportsComputeRepo.save(et);
|
|
|
+ UpdateProjectStatusReq req = new UpdateProjectStatusReq();
|
|
|
+ req.setProjectId(et.getProjectId());
|
|
|
+ req.setStatus(2);
|
|
|
+ projectCloudService.updateProjectStatus(req);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public void updateToSuccess(ReportsComputeEntity et) {
|
|
|
+ et.setStatus(ReportsComputeStatus.SUCCESS);
|
|
|
+ et.setEndTime(new Date());
|
|
|
+ reportsComputeRepo.save(et);
|
|
|
+ UpdateProjectStatusReq req = new UpdateProjectStatusReq();
|
|
|
+ req.setProjectId(et.getProjectId());
|
|
|
+ req.setStatus(3);
|
|
|
+ projectCloudService.updateProjectStatus(req);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public void updateToFail(ReportsComputeEntity et, String errMsg) {
|
|
|
+ et.setStatus(ReportsComputeStatus.FAIL);
|
|
|
+ et.setErrorDesc(errMsg);
|
|
|
+ et.setEndTime(new Date());
|
|
|
+ reportsComputeRepo.save(et);
|
|
|
+ UpdateProjectStatusReq req = new UpdateProjectStatusReq();
|
|
|
+ req.setProjectId(et.getProjectId());
|
|
|
+ req.setStatus(4);
|
|
|
+ projectCloudService.updateProjectStatus(req);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public int updateToStoping(ReportsComputeEntity et) {
|
|
|
+ int ret = reportsComputeRepo.updateToStoping(et.getId());
|
|
|
+ if (ret != 0) {
|
|
|
+ jobStopFlag.put(et.getId(), true);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public void updateToStop(ReportsComputeEntity et) {
|
|
|
+ et.setStatus(ReportsComputeStatus.STOP);
|
|
|
+ et.setEndTime(new Date());
|
|
|
+ reportsComputeRepo.save(et);
|
|
|
+ UpdateProjectStatusReq req = new UpdateProjectStatusReq();
|
|
|
+ req.setProjectId(et.getProjectId());
|
|
|
+ req.setStatus(5);
|
|
|
+ projectCloudService.updateProjectStatus(req);
|
|
|
+ // 清除缓存终止标志
|
|
|
+ jobStopFlag.remove(et.getId());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ @Override
|
|
|
+ public void add(Long projectId, Long rootOrgId) {
|
|
|
+ List<ReportsComputeEntity> list = reportsComputeRepo.findByProjectIdAndRootOrgIdAndStatus(projectId, rootOrgId,
|
|
|
+ ReportsComputeStatus.COMPUTING);
|
|
|
+ if (list != null && list.size() > 0) {
|
|
|
+ throw new StatusException("10001", "该项目正在计算中");
|
|
|
+ }
|
|
|
+ ReportsComputeEntity e = new ReportsComputeEntity();
|
|
|
+ e.setProjectId(projectId);
|
|
|
+ e.setRootOrgId(rootOrgId);
|
|
|
+ e.setStatus(ReportsComputeStatus.NONE);
|
|
|
+ reportsComputeRepo.save(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int getTodoDataCount() {
|
|
|
+ return reportsComputeRepo.getTodoDataCount();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Double getPercentage(Double a, Double b) {
|
|
|
+ if (b == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ BigDecimal bd = new BigDecimal(a * 100 / b);
|
|
|
+ Double tem = bd.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
|
|
|
+ return tem;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Double getTwoDecimal(Double b) {
|
|
|
+ if (b == null) {
|
|
|
+ return b;
|
|
|
+ }
|
|
|
+ BigDecimal bd = new BigDecimal(b);
|
|
|
+ Double tem = bd.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
|
|
|
+ return tem;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public PageInfo<ReportsComputeBean> queryPage(Long projectId, Integer pageNo, Integer pageSize, Long rootOrgId) {
|
|
|
+ Specification<ReportsComputeEntity> specification = (root, query, cb) -> {
|
|
|
+ List<Predicate> predicates = new ArrayList<>();
|
|
|
+ predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
|
|
|
+
|
|
|
+ predicates.add(cb.equal(root.get("projectId"), projectId));
|
|
|
+
|
|
|
+ return cb.and(predicates.toArray(new Predicate[predicates.size()]));
|
|
|
+ };
|
|
|
+
|
|
|
+ PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize, new Sort(Direction.DESC, "id"));
|
|
|
+
|
|
|
+ Page<ReportsComputeEntity> projects = reportsComputeRepo.findAll(specification, pageRequest);
|
|
|
+
|
|
|
+ List<ReportsComputeBean> ret = Lists.newArrayList();
|
|
|
+
|
|
|
+ for (ReportsComputeEntity cur : projects) {
|
|
|
+ ReportsComputeBean bean = new ReportsComputeBean();
|
|
|
+ BeanUtils.copyProperties(cur, bean);
|
|
|
+ bean.setStatusName(bean.getStatus().getDesc());
|
|
|
+ ret.add(bean);
|
|
|
+ }
|
|
|
+ return new PageInfo<ReportsComputeBean>(projects, ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<ReportsComputeBean> getList(List<Long> pids, Long rootOrgId) {
|
|
|
+ List<ReportsComputeBean> ret = new ArrayList<ReportsComputeBean>();
|
|
|
+ List<ReportsComputeEntity> list = reportsComputeRepo.getByIds(pids, rootOrgId);
|
|
|
+ if (list != null) {
|
|
|
+ for (ReportsComputeEntity pe : list) {
|
|
|
+ ReportsComputeBean bean = new ReportsComputeBean();
|
|
|
+ BeanUtils.copyProperties(pe, bean);
|
|
|
+ bean.setStatusName(bean.getStatus().getDesc());
|
|
|
+ ret.add(bean);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+}
|