Forráskód Böngészése

修改统计分析模块

1.增加小题选项分析计算模块与统计单元
2.增加难度等级与区分度等级计算模块与统计单元
3.重构部分模块并加入班级维度扩展
luoshi 6 éve
szülő
commit
c3a140530d
15 módosított fájl, 651 hozzáadás és 128 törlés
  1. 21 21
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/model/SasConfigItem.java
  2. 15 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/QuestionCalculatorProvider.java
  3. 9 5
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/QuestionGroup.java
  4. 39 4
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/ReportContext.java
  5. 7 66
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectClassGroupModule.java
  6. 84 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectClassQuestionLevelModule.java
  7. 46 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectClassQuestionModule.java
  8. 17 16
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectGroupModule.java
  9. 2 2
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectModule.java
  10. 110 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectQuestionLevelModule.java
  11. 31 14
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectQuestionModule.java
  12. 76 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectQuestionOptionModule.java
  13. 58 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/unit/LevelRange.java
  14. 84 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/unit/OptionCounter.java
  15. 52 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/unit/QuestionCounter.java

+ 21 - 21
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/model/SasConfigItem.java

@@ -75,26 +75,26 @@ public class SasConfigItem {
 
     public static SasConfigItem parse(SasConfigItem configItem, String text) {
         SasConfigItem item = new SasConfigItem(text);
-        if (item.getDifficultyHighScore() != null) {
-            configItem.setDifficultyHighScore(item.getDifficultyHighScore());
+        if (item.getDifficultyHighValue() != null) {
+            configItem.setDifficultyHighScore(item.getDifficultyHighValue());
         }
-        if (item.getDifficultyLowScore() != null) {
-            configItem.setDifficultyLowScore(item.getDifficultyLowScore());
+        if (item.getDifficultyLowValue() != null) {
+            configItem.setDifficultyLowScore(item.getDifficultyLowValue());
         }
-        if (item.getDifficultyMiddleScore() != null) {
-            configItem.setDifficultyMiddleScore(item.getDifficultyMiddleScore());
+        if (item.getDifficultyMiddleValue() != null) {
+            configItem.setDifficultyMiddleScore(item.getDifficultyMiddleValue());
         }
-        if (item.getDiscriminationBadScore() != null) {
-            configItem.setDiscriminationBadScore(item.getDiscriminationBadScore());
+        if (item.getDiscriminationBadValue() != null) {
+            configItem.setDiscriminationBadScore(item.getDiscriminationBadValue());
         }
-        if (item.getDiscriminationExcellentScore() != null) {
-            configItem.setDiscriminationExcellentScore(item.getDiscriminationExcellentScore());
+        if (item.getDiscriminationExcellentValue() != null) {
+            configItem.setDiscriminationExcellentScore(item.getDiscriminationExcellentValue());
         }
-        if (item.getDiscriminationGoodScore() != null) {
-            configItem.setDiscriminationGoodScore(item.getDiscriminationGoodScore());
+        if (item.getDiscriminationGoodValue() != null) {
+            configItem.setDiscriminationGoodScore(item.getDiscriminationGoodValue());
         }
-        if (item.getDiscriminationGeneralScore() != null) {
-            configItem.setDiscriminationGeneralScore(item.getDiscriminationGeneralScore());
+        if (item.getDiscriminationGeneralValue() != null) {
+            configItem.setDiscriminationGeneralScore(item.getDiscriminationGeneralValue());
         }
         if (item.getHighScore() != null) {
             configItem.setHighScore(item.getHighScore());
@@ -127,7 +127,7 @@ public class SasConfigItem {
         this.excellentScore = excellentScore;
     }
 
-    public Double getDiscriminationExcellentScore() {
+    public Double getDiscriminationExcellentValue() {
         return discriminationExcellentScore;
     }
 
@@ -135,7 +135,7 @@ public class SasConfigItem {
         this.discriminationExcellentScore = discriminationExcellentScore;
     }
 
-    public Double getDiscriminationGoodScore() {
+    public Double getDiscriminationGoodValue() {
         return discriminationGoodScore;
     }
 
@@ -143,7 +143,7 @@ public class SasConfigItem {
         this.discriminationGoodScore = discriminationGoodScore;
     }
 
-    public Double getDiscriminationGeneralScore() {
+    public Double getDiscriminationGeneralValue() {
         return discriminationGeneralScore;
     }
 
@@ -151,7 +151,7 @@ public class SasConfigItem {
         this.discriminationGeneralScore = discriminationGeneralScore;
     }
 
-    public Double getDiscriminationBadScore() {
+    public Double getDiscriminationBadValue() {
         return discriminationBadScore;
     }
 
@@ -159,7 +159,7 @@ public class SasConfigItem {
         this.discriminationBadScore = discriminationBadScore;
     }
 
-    public Double getDifficultyHighScore() {
+    public Double getDifficultyHighValue() {
         return difficultyHighScore;
     }
 
@@ -167,7 +167,7 @@ public class SasConfigItem {
         this.difficultyHighScore = difficultyHighScore;
     }
 
-    public Double getDifficultyMiddleScore() {
+    public Double getDifficultyMiddleValue() {
         return difficultyMiddleScore;
     }
 
@@ -175,7 +175,7 @@ public class SasConfigItem {
         this.difficultyMiddleScore = difficultyMiddleScore;
     }
 
-    public Double getDifficultyLowScore() {
+    public Double getDifficultyLowValue() {
         return difficultyLowScore;
     }
 

+ 15 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/QuestionCalculatorProvider.java

@@ -0,0 +1,15 @@
+package cn.com.qmth.stmms.biz.report.utils;
+
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+import cn.com.qmth.stmms.biz.report.utils.unit.BaseCalculatorUnit;
+
+/**
+ * 提供小题统计指标访问的工具类
+ * 
+ * @author luoshi
+ *
+ */
+public interface QuestionCalculatorProvider {
+
+    BaseCalculatorUnit getCalculator(ExamQuestion question, Object... parameter);
+}

+ 9 - 5
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/QuestionGroup.java

@@ -2,13 +2,16 @@ package cn.com.qmth.stmms.biz.report.utils;
 
 public class QuestionGroup {
 
+    private boolean objective;
+
     private int number;
 
     private double totalScore;
 
     private double fullScore;
 
-    public QuestionGroup(int number) {
+    public QuestionGroup(boolean objective, int number) {
+        this.objective = objective;
         this.number = number;
     }
 
@@ -24,10 +27,6 @@ public class QuestionGroup {
         return number;
     }
 
-    public void setNumber(int number) {
-        this.number = number;
-    }
-
     public double getTotalScore() {
         return totalScore;
     }
@@ -43,4 +42,9 @@ public class QuestionGroup {
     public void setFullScore(double fullScore) {
         this.fullScore = fullScore;
     }
+
+    public boolean isObjective() {
+        return objective;
+    }
+
 }

+ 39 - 4
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/ReportContext.java

@@ -18,13 +18,18 @@ import cn.com.qmth.stmms.biz.report.service.ReportSubjectTeacherClassService;
 import cn.com.qmth.stmms.biz.report.service.ReportSubjectTeacherService;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectClassGroupModule;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectClassModule;
+import cn.com.qmth.stmms.biz.report.utils.module.SubjectClassQuestionLevelModule;
+import cn.com.qmth.stmms.biz.report.utils.module.SubjectClassQuestionModule;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectCollegeModule;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectGroupModule;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectModule;
+import cn.com.qmth.stmms.biz.report.utils.module.SubjectQuestionLevelModule;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectQuestionModule;
+import cn.com.qmth.stmms.biz.report.utils.module.SubjectQuestionOptionModule;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectRangeModule;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectTeacherClassModule;
 import cn.com.qmth.stmms.biz.report.utils.module.SubjectTeacherModule;
+import cn.com.qmth.stmms.biz.report.utils.unit.LevelRange;
 import cn.com.qmth.stmms.biz.utils.SpringContextHolder;
 
 public class ReportContext {
@@ -38,7 +43,7 @@ public class ReportContext {
     private Map<String, ExamQuestion> questionMap;
 
     private Map<String, ExamQuestion> groupMap;
-    
+
     private SasConfigItem sasConfigItem;
 
     public ReportContext(Exam exam) {
@@ -51,13 +56,23 @@ public class ReportContext {
         this.modules.add(new SubjectCollegeModule(this));
         this.modules.add(new SubjectTeacherModule(this));
         this.modules.add(new SubjectTeacherClassModule(this));
-        this.modules.add(new SubjectQuestionModule(this));
+        this.modules.add(new SubjectQuestionOptionModule(this));
+
+        SubjectQuestionModule qm = new SubjectQuestionModule(this);
+        this.modules.add(qm);
+        this.modules.add(new SubjectQuestionLevelModule(this, qm));
+
+        SubjectClassQuestionModule cqm = new SubjectClassQuestionModule(this);
+        this.modules.add(cqm);
+        this.modules.add(new SubjectClassQuestionLevelModule(this, cqm));
+
         this.modules.add(new SubjectGroupModule(this));
         this.modules.add(new SubjectClassGroupModule(this));
+
         this.subjectMap = new HashMap<String, ExamSubject>();
         this.questionMap = new HashMap<String, ExamQuestion>();
         this.groupMap = new HashMap<String, ExamQuestion>();
-        this.sasConfigItem= SpringContextHolder.getBean(SasConfigItem.class);
+        this.sasConfigItem = SpringContextHolder.getBean(SasConfigItem.class);
     }
 
     public void process(ExamStudent student) {
@@ -147,7 +162,27 @@ public class ReportContext {
     }
 
     public SasConfigItem getSasConfigItem() {
-        sasConfigItem = SasConfigItem.parse(sasConfigItem,exam.getSasConfig());
+        sasConfigItem = SasConfigItem.parse(sasConfigItem, exam.getSasConfig());
         return sasConfigItem;
     }
+
+    public LevelRange[] getDifficulityLevelConfig() {
+        LevelRange[] levels = new LevelRange[3];
+        levels[0] = new LevelRange(null, null, sasConfigItem.getDifficultyHighValue(), null);
+        levels[1] = new LevelRange(null, sasConfigItem.getDifficultyMiddleValue(), null,
+                sasConfigItem.getDifficultyHighValue());
+        levels[2] = new LevelRange(sasConfigItem.getDifficultyLowValue(), null, null, null);
+        return levels;
+    }
+
+    public LevelRange[] getDiscriminationLevelConfig() {
+        LevelRange[] levels = new LevelRange[4];
+        levels[0] = new LevelRange(null, sasConfigItem.getDiscriminationExcellentValue(), null, null);
+        levels[1] = new LevelRange(null, sasConfigItem.getDiscriminationGoodValue(),
+                sasConfigItem.getDiscriminationExcellentValue(), null);
+        levels[2] = new LevelRange(null, sasConfigItem.getDiscriminationGeneralValue(),
+                sasConfigItem.getDiscriminationGoodValue(), null);
+        levels[3] = new LevelRange(null, null, sasConfigItem.getDiscriminationBadValue(), null);
+        return levels;
+    }
 }

+ 7 - 66
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectClassGroupModule.java

@@ -1,11 +1,5 @@
 package cn.com.qmth.stmms.biz.report.utils.module;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
 import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.report.model.ReportSubjectClassGroup;
 import cn.com.qmth.stmms.biz.report.service.ReportSubjectClassGroupService;
@@ -13,73 +7,29 @@ import cn.com.qmth.stmms.biz.report.utils.Module;
 import cn.com.qmth.stmms.biz.report.utils.QuestionGroup;
 import cn.com.qmth.stmms.biz.report.utils.ReportContext;
 import cn.com.qmth.stmms.biz.report.utils.unit.BaseCalculatorUnit;
-import cn.com.qmth.stmms.biz.utils.ScoreItem;
 import cn.com.qmth.stmms.biz.utils.SpringContextHolder;
 
-public class SubjectClassGroupModule implements Module {
-
-    private Map<String, BaseCalculatorUnit> units;
-
-    private ReportContext context;
+public class SubjectClassGroupModule extends SubjectGroupModule implements Module {
 
     public SubjectClassGroupModule(ReportContext context) {
-        this.units = new HashMap<String, BaseCalculatorUnit>();
-        this.context = context;
+        super(context);
     }
 
-    public void process(ExamStudent student) {
-        if (student.isUpload() && !student.isAbsent() && !student.isBreach() && student.getSubject() != null) {
-            double totalScore = student.getTotalScore();
-            // 遍历客观题
-            process(student.getScoreList(true), student.getObjectiveQuestionList(), student.getSubjectCode(),
-                    student.getClassName(), totalScore, true);
-            // 遍历主观题
-            process(student.getScoreList(false), student.getSubjectiveQuestionList(), student.getSubjectCode(),
-                    student.getClassName(), totalScore, false);
-        }
-    }
-
-    private void process(List<ScoreItem> scoreList, List<ExamQuestion> questionList, String subjectCode,
-            String className, double totalScore, boolean objective) {
-        if (scoreList == null || questionList == null) {
-            return;
-        }
-        Map<Integer, QuestionGroup> map = new HashMap<Integer, QuestionGroup>();
-        int scoreCount = scoreList.size();
-        for (int i = 0; i < questionList.size(); i++) {
-            ExamQuestion question = questionList.get(i);
-            if (question.getTotalScore() == null || question.getTotalScore() == 0) {
-                continue;
-            }
-            ScoreItem item = scoreCount > i ? scoreList.get(i) : null;
-            QuestionGroup group = map.get(question.getMainNumber());
-            if (group == null) {
-                group = new QuestionGroup(question.getMainNumber());
-                map.put(question.getMainNumber(), group);
-            }
-            group.incrTotalScore(item != null ? item.getScore() : 0);
-            group.incrFullScore(question.getTotalScore());
-        }
-        for (Entry<Integer, QuestionGroup> entry : map.entrySet()) {
-            QuestionGroup group = entry.getValue();
-            if (group.getFullScore() == 0) {
-                continue;
-            }
-            findUnit(subjectCode + "\t" + className + "\t" + objective + "\t" + entry.getKey()).process(
-                    group.getTotalScore(), group.getFullScore(), totalScore);
-        }
+    public String getKey(ExamStudent student, QuestionGroup group) {
+        return student.getSubjectCode() + "\t" + student.getClassName() + "\t" + group.isObjective() + "\t"
+                + group.getNumber();
     }
 
     public void save() {
         ReportSubjectClassGroupService service = SpringContextHolder.getBean(ReportSubjectClassGroupService.class);
-        for (String key : this.units.keySet()) {
+        for (String key : this.calculators.keySet()) {
             String s[] = key.split("\t");
             if (s.length > 3) {
                 String subjectCode = s[0];
                 String className = s[1];
                 Boolean objective = Boolean.parseBoolean(s[2]);
                 Integer mainNumber = Integer.parseInt(s[3]);
-                BaseCalculatorUnit unit = units.get(key);
+                BaseCalculatorUnit unit = calculators.get(key);
                 ReportSubjectClassGroup r = new ReportSubjectClassGroup();
                 r.setExamId(this.context.getExamId());
                 r.setSubjectCode(subjectCode);
@@ -95,13 +45,4 @@ public class SubjectClassGroupModule implements Module {
         }
     }
 
-    private BaseCalculatorUnit findUnit(String key) {
-        BaseCalculatorUnit unit = units.get(key);
-        if (unit == null) {
-            unit = new BaseCalculatorUnit();
-            units.put(key, unit);
-        }
-        return unit;
-    }
-
 }

+ 84 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectClassQuestionLevelModule.java

@@ -0,0 +1,84 @@
+package cn.com.qmth.stmms.biz.report.utils.module;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
+import cn.com.qmth.stmms.biz.report.utils.Module;
+import cn.com.qmth.stmms.biz.report.utils.QuestionCalculatorProvider;
+import cn.com.qmth.stmms.biz.report.utils.ReportContext;
+import cn.com.qmth.stmms.biz.report.utils.unit.BaseCalculatorUnit;
+
+/**
+ * 按科目+班级维度统计小题难度等级与区分度等级
+ * 
+ * @author luoshi
+ *
+ */
+public class SubjectClassQuestionLevelModule extends SubjectQuestionLevelModule implements Module {
+
+    protected Map<String, Set<String>> classMap;
+
+    public SubjectClassQuestionLevelModule(ReportContext context, QuestionCalculatorProvider provider) {
+        super(context, provider);
+    }
+
+    @Override
+    public void process(ExamStudent student) {
+        super.process(student);
+
+        Set<String> set = classMap.get(student.getSubjectCode());
+        if (set == null) {
+            set = new HashSet<>();
+            classMap.put(student.getSubjectCode(), set);
+        }
+        set.add(student.getClassName());
+    }
+
+    @Override
+    protected void beforeSave() {
+        for (Entry<String, List<ExamQuestion>> entry : questions.entrySet()) {
+            Set<String> classSet = classMap.get(entry.getKey());
+            if (classSet.isEmpty()) {
+                continue;
+            }
+            for (String className : classSet) {
+                String key = entry.getKey() + "\t" + className;
+                for (ExamQuestion question : entry.getValue()) {
+                    if (question.getTotalScore() == null || question.getTotalScore() == 0) {
+                        continue;
+                    }
+                    BaseCalculatorUnit calculator = provider.getCalculator(question, className);
+                    if (calculator == null) {
+                        continue;
+                    }
+                    // 按全卷维度统计
+                    findDifficulityLevelCounter(key).process(question, calculator.difficulty);
+                    findDiscriminationLevelCounter(key).process(question, calculator.discrimination);
+                    // 按大题维度统计
+                    findDifficulityLevelCounter(getGroupKey(question, className)).process(question,
+                            calculator.difficulty);
+                    findDiscriminationLevelCounter(getGroupKey(question, className)).process(question,
+                            calculator.discrimination);
+                }
+            }
+        }
+    }
+
+    protected String getGroupKey(ExamQuestion question, String className) {
+        return question.getSubjectCode() + "\t" + className + "\t" + question.isObjective() + "\t"
+                + StringUtils.trimToEmpty(question.getPaperType()) + "\t" + +question.getMainNumber();
+    }
+
+    public void save() {
+        super.beforeSave();
+        // TODO
+    }
+
+}

+ 46 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectClassQuestionModule.java

@@ -0,0 +1,46 @@
+package cn.com.qmth.stmms.biz.report.utils.module;
+
+import org.apache.commons.lang.StringUtils;
+
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
+import cn.com.qmth.stmms.biz.report.utils.Module;
+import cn.com.qmth.stmms.biz.report.utils.QuestionCalculatorProvider;
+import cn.com.qmth.stmms.biz.report.utils.ReportContext;
+import cn.com.qmth.stmms.biz.report.utils.unit.BaseCalculatorUnit;
+
+/**
+ * 按科目+班级维度统计小题指标
+ * 
+ * @author luoshi
+ *
+ */
+public class SubjectClassQuestionModule extends SubjectQuestionModule implements Module, QuestionCalculatorProvider {
+
+    public SubjectClassQuestionModule(ReportContext context) {
+        super(context);
+    }
+
+    @Override
+    protected void process(ExamStudent student, ExamQuestion question, double score, double totalScore) {
+        findCalculator(getKey(question, student.getClassName())).process(score, question.getTotalScore(), totalScore);
+    }
+
+    @Override
+    public String getKey(ExamQuestion question, Object... parameter) {
+        return question.getSubjectCode() + "\t" + parameter[0] + "\t" + question.isObjective() + "\t"
+                + StringUtils.trimToEmpty(question.getPaperType()) + "\t" + question.getMainNumber() + "\t"
+                + question.getSubNumber();
+    }
+
+    @Override
+    public BaseCalculatorUnit getCalculator(ExamQuestion question, Object... parameter) {
+        return calculators.get(getKey(question, parameter));
+    }
+
+    @Override
+    public void save() {
+        // 暂时不需要保存到数据库
+    }
+
+}

+ 17 - 16
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectGroupModule.java

@@ -18,12 +18,12 @@ import cn.com.qmth.stmms.biz.utils.SpringContextHolder;
 
 public class SubjectGroupModule implements Module {
 
-    private Map<String, BaseCalculatorUnit> units;
+    protected Map<String, BaseCalculatorUnit> calculators;
 
-    private ReportContext context;
+    protected ReportContext context;
 
     public SubjectGroupModule(ReportContext context) {
-        this.units = new HashMap<String, BaseCalculatorUnit>();
+        this.calculators = new HashMap<String, BaseCalculatorUnit>();
         this.context = context;
     }
 
@@ -31,15 +31,13 @@ public class SubjectGroupModule implements Module {
         if (student.isUpload() && !student.isAbsent() && !student.isBreach() && student.getSubject() != null) {
             double totalScore = student.getTotalScore();
             // 遍历客观题
-            process(student.getScoreList(true), student.getObjectiveQuestionList(), student.getSubjectCode(),
-                    totalScore, true);
+            process(student.getScoreList(true), student.getObjectiveQuestionList(), student, totalScore, true);
             // 遍历主观题
-            process(student.getScoreList(false), student.getSubjectiveQuestionList(), student.getSubjectCode(),
-                    totalScore, false);
+            process(student.getScoreList(false), student.getSubjectiveQuestionList(), student, totalScore, false);
         }
     }
 
-    private void process(List<ScoreItem> scoreList, List<ExamQuestion> questionList, String subjectCode,
+    private void process(List<ScoreItem> scoreList, List<ExamQuestion> questionList, ExamStudent student,
             double totalScore, boolean objective) {
         if (scoreList == null || questionList == null) {
             return;
@@ -54,7 +52,7 @@ public class SubjectGroupModule implements Module {
             ScoreItem item = scoreCount > i ? scoreList.get(i) : null;
             QuestionGroup group = map.get(question.getMainNumber());
             if (group == null) {
-                group = new QuestionGroup(question.getMainNumber());
+                group = new QuestionGroup(objective, question.getMainNumber());
                 map.put(question.getMainNumber(), group);
             }
             group.incrTotalScore(item != null ? item.getScore() : 0);
@@ -65,20 +63,23 @@ public class SubjectGroupModule implements Module {
             if (group.getFullScore() == 0) {
                 continue;
             }
-            findUnit(subjectCode + "\t" + objective + "\t" + entry.getKey()).process(group.getTotalScore(),
-                    group.getFullScore(), totalScore);
+            findCalculator(getKey(student, group)).process(group.getTotalScore(), group.getFullScore(), totalScore);
         }
     }
 
+    public String getKey(ExamStudent student, QuestionGroup group) {
+        return student.getSubjectCode() + "\t" + group.isObjective() + "\t" + group.getNumber();
+    }
+
     public void save() {
         ReportSubjectGroupService service = SpringContextHolder.getBean(ReportSubjectGroupService.class);
-        for (String key : this.units.keySet()) {
+        for (String key : this.calculators.keySet()) {
             String s[] = key.split("\t");
             if (s.length > 2) {
                 String subjectCode = s[0];
                 Boolean objective = Boolean.parseBoolean(s[1]);
                 Integer mainNumber = Integer.parseInt(s[2]);
-                BaseCalculatorUnit unit = units.get(key);
+                BaseCalculatorUnit unit = calculators.get(key);
                 ReportSubjectGroup r = new ReportSubjectGroup();
                 r.setExamId(this.context.getExamId());
                 r.setSubjectCode(subjectCode);
@@ -103,11 +104,11 @@ public class SubjectGroupModule implements Module {
         }
     }
 
-    private BaseCalculatorUnit findUnit(String key) {
-        BaseCalculatorUnit unit = units.get(key);
+    private BaseCalculatorUnit findCalculator(String key) {
+        BaseCalculatorUnit unit = calculators.get(key);
         if (unit == null) {
             unit = new BaseCalculatorUnit();
-            units.put(key, unit);
+            calculators.put(key, unit);
         }
         return unit;
     }

+ 2 - 2
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectModule.java

@@ -32,7 +32,7 @@ public class SubjectModule implements Module {
         if (subject != null) {
             String key = getKey(student);
             if (student.isUpload() && !student.isAbsent() && !student.isBreach()) {
-                findUnit(key).process(student.getTotalScore(), subject.getTotalScore(), student.getTotalScore());
+                findCalculator(key).process(student.getTotalScore(), subject.getTotalScore(), student.getTotalScore());
             }
             findCounter(key).process(student);
         }
@@ -72,7 +72,7 @@ public class SubjectModule implements Module {
         }
     }
 
-    protected BaseCalculatorUnit findUnit(String key) {
+    protected BaseCalculatorUnit findCalculator(String key) {
         BaseCalculatorUnit unit = units.get(key);
         if (unit == null) {
             unit = new BaseCalculatorUnit();

+ 110 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectQuestionLevelModule.java

@@ -0,0 +1,110 @@
+package cn.com.qmth.stmms.biz.report.utils.module;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang.StringUtils;
+
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
+import cn.com.qmth.stmms.biz.report.utils.Module;
+import cn.com.qmth.stmms.biz.report.utils.QuestionCalculatorProvider;
+import cn.com.qmth.stmms.biz.report.utils.ReportContext;
+import cn.com.qmth.stmms.biz.report.utils.unit.BaseCalculatorUnit;
+import cn.com.qmth.stmms.biz.report.utils.unit.LevelRange;
+import cn.com.qmth.stmms.biz.report.utils.unit.QuestionCounter;
+
+/**
+ * 按科目统计全样本小题难度等级与区分度等级
+ * 
+ * @author luoshi
+ *
+ */
+public class SubjectQuestionLevelModule implements Module {
+
+    protected ReportContext context;
+
+    protected QuestionCalculatorProvider provider;
+
+    protected Map<String, List<ExamQuestion>> questions;
+
+    protected Map<String, QuestionCounter> difficulityLevels, discriminationLevels;
+
+    protected LevelRange[] difficulityLevelConfig, discriminationLevelConfig;
+
+    public SubjectQuestionLevelModule(ReportContext context, QuestionCalculatorProvider provider) {
+        this.context = context;
+        this.provider = provider;
+        this.questions = new HashMap<String, List<ExamQuestion>>();
+        this.difficulityLevels = new HashMap<String, QuestionCounter>();
+        this.discriminationLevels = new HashMap<String, QuestionCounter>();
+        this.difficulityLevelConfig = context.getDifficulityLevelConfig();
+        this.discriminationLevelConfig = context.getDiscriminationLevelConfig();
+    }
+
+    @Override
+    public void process(ExamStudent student) {
+        String key = student.getSubjectCode() + "\t" + StringUtils.trimToEmpty(student.getPaperType());
+        List<ExamQuestion> list = questions.get(key);
+        if (list == null) {
+            list = new ArrayList<ExamQuestion>();
+            questions.put(key, list);
+            list.addAll(student.getObjectiveQuestionList());
+            list.addAll(student.getSubjectiveQuestionList());
+        }
+    }
+
+    @Override
+    public void save() {
+        beforeSave();
+        // TODO 补充保存到数据库逻辑
+    }
+
+    protected void beforeSave() {
+        for (Entry<String, List<ExamQuestion>> entry : questions.entrySet()) {
+            String key = entry.getKey();
+            for (ExamQuestion question : entry.getValue()) {
+                if (question.getTotalScore() == null || question.getTotalScore() == 0) {
+                    continue;
+                }
+                BaseCalculatorUnit calculator = provider.getCalculator(question);
+                if (calculator == null) {
+                    continue;
+                }
+                // 按全卷维度统计
+                findDifficulityLevelCounter(key).process(question, calculator.difficulty);
+                findDiscriminationLevelCounter(key).process(question, calculator.discrimination);
+                // 按大题维度统计
+                findDifficulityLevelCounter(getGroupKey(question)).process(question, calculator.difficulty);
+                findDiscriminationLevelCounter(getGroupKey(question)).process(question, calculator.discrimination);
+            }
+        }
+    }
+
+    protected String getGroupKey(ExamQuestion question) {
+        return question.getSubjectCode() + "\t" + question.isObjective() + "\t"
+                + StringUtils.trimToEmpty(question.getPaperType()) + "\t" + question.getMainNumber();
+    }
+
+    protected QuestionCounter findDifficulityLevelCounter(String key) {
+        QuestionCounter counter = difficulityLevels.get(key);
+        if (counter == null) {
+            counter = new QuestionCounter(difficulityLevelConfig);
+            difficulityLevels.put(key, counter);
+        }
+        return counter;
+    }
+
+    protected QuestionCounter findDiscriminationLevelCounter(String key) {
+        QuestionCounter counter = discriminationLevels.get(key);
+        if (counter == null) {
+            counter = new QuestionCounter(discriminationLevelConfig);
+            discriminationLevels.put(key, counter);
+        }
+        return counter;
+    }
+
+}

+ 31 - 14
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectQuestionModule.java

@@ -11,19 +11,26 @@ import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.report.model.ReportSubjectQuestion;
 import cn.com.qmth.stmms.biz.report.service.ReportSubjectQuestionService;
 import cn.com.qmth.stmms.biz.report.utils.Module;
+import cn.com.qmth.stmms.biz.report.utils.QuestionCalculatorProvider;
 import cn.com.qmth.stmms.biz.report.utils.ReportContext;
 import cn.com.qmth.stmms.biz.report.utils.unit.BaseCalculatorUnit;
 import cn.com.qmth.stmms.biz.utils.ScoreItem;
 import cn.com.qmth.stmms.biz.utils.SpringContextHolder;
 
-public class SubjectQuestionModule implements Module {
+/**
+ * 按科目统计全样本小题指标
+ * 
+ * @author luoshi
+ *
+ */
+public class SubjectQuestionModule implements Module, QuestionCalculatorProvider {
 
-    private Map<String, BaseCalculatorUnit> units;
+    protected Map<String, BaseCalculatorUnit> calculators;
 
-    private ReportContext context;
+    protected ReportContext context;
 
     public SubjectQuestionModule(ReportContext context) {
-        this.units = new HashMap<String, BaseCalculatorUnit>();
+        this.calculators = new HashMap<String, BaseCalculatorUnit>();
         this.context = context;
     }
 
@@ -31,13 +38,14 @@ public class SubjectQuestionModule implements Module {
         if (student.isUpload() && !student.isAbsent() && !student.isBreach() && student.getSubject() != null) {
             double totalScore = student.getTotalScore();
             // 遍历客观题
-            process(student.getScoreList(true), student.getObjectiveQuestionList(), totalScore);
+            process(student, student.getScoreList(true), student.getObjectiveQuestionList(), totalScore);
             // 遍历主观题
-            process(student.getScoreList(false), student.getSubjectiveQuestionList(), totalScore);
+            process(student, student.getScoreList(false), student.getSubjectiveQuestionList(), totalScore);
         }
     }
 
-    private void process(List<ScoreItem> scoreList, List<ExamQuestion> questionList, double totalScore) {
+    private void process(ExamStudent student, List<ScoreItem> scoreList, List<ExamQuestion> questionList,
+            double totalScore) {
         if (scoreList == null || questionList == null) {
             return;
         }
@@ -49,11 +57,15 @@ public class SubjectQuestionModule implements Module {
             }
             ScoreItem item = scoreCount > i ? scoreList.get(i) : null;
             double score = item != null ? item.getScore() : 0;
-            findUnit(getKey(question)).process(score, question.getTotalScore(), totalScore);
+            process(student, question, score, totalScore);
         }
     }
 
-    public String getKey(ExamQuestion question) {
+    protected void process(ExamStudent student, ExamQuestion question, double score, double totalScore) {
+        findCalculator(getKey(question)).process(score, question.getTotalScore(), totalScore);
+    }
+
+    protected String getKey(ExamQuestion question, Object... parameter) {
         return question.getSubjectCode() + "\t" + question.isObjective() + "\t"
                 + StringUtils.trimToEmpty(question.getPaperType()) + "\t" + question.getMainNumber() + "\t"
                 + question.getSubNumber();
@@ -61,7 +73,7 @@ public class SubjectQuestionModule implements Module {
 
     public void save() {
         ReportSubjectQuestionService service = SpringContextHolder.getBean(ReportSubjectQuestionService.class);
-        for (String key : this.units.keySet()) {
+        for (String key : this.calculators.keySet()) {
             String s[] = key.split("\t");
             if (s.length > 4) {
                 String subjectCode = s[0];
@@ -69,7 +81,7 @@ public class SubjectQuestionModule implements Module {
                 String paperType = StringUtils.trimToNull(s[2]);
                 Integer mainNumber = Integer.parseInt(s[3]);
                 Integer subNumber = Integer.parseInt(s[4]);
-                BaseCalculatorUnit unit = units.get(key);
+                BaseCalculatorUnit unit = calculators.get(key);
                 ReportSubjectQuestion r = new ReportSubjectQuestion();
                 r.setExamId(this.context.getExamId());
                 r.setSubjectCode(subjectCode);
@@ -96,13 +108,18 @@ public class SubjectQuestionModule implements Module {
         }
     }
 
-    private BaseCalculatorUnit findUnit(String key) {
-        BaseCalculatorUnit unit = units.get(key);
+    protected BaseCalculatorUnit findCalculator(String key) {
+        BaseCalculatorUnit unit = calculators.get(key);
         if (unit == null) {
             unit = new BaseCalculatorUnit();
-            units.put(key, unit);
+            calculators.put(key, unit);
         }
         return unit;
     }
 
+    @Override
+    public BaseCalculatorUnit getCalculator(ExamQuestion question, Object... parameter) {
+        return calculators.get(getKey(question, parameter));
+    }
+
 }

+ 76 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/module/SubjectQuestionOptionModule.java

@@ -0,0 +1,76 @@
+package cn.com.qmth.stmms.biz.report.utils.module;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
+import cn.com.qmth.stmms.biz.report.utils.Module;
+import cn.com.qmth.stmms.biz.report.utils.ReportContext;
+import cn.com.qmth.stmms.biz.report.utils.unit.OptionCounter;
+
+/**
+ * 按科目统计全样本小题选项指标
+ * 
+ * @author luoshi
+ *
+ */
+public class SubjectQuestionOptionModule implements Module {
+
+    private Map<String, OptionCounter> counters;
+
+    private ReportContext context;
+
+    public SubjectQuestionOptionModule(ReportContext context) {
+        this.counters = new HashMap<String, OptionCounter>();
+        this.context = context;
+    }
+
+    public void process(ExamStudent student) {
+        if (student.isUpload() && !student.isAbsent() && !student.isBreach() && student.getSubject() != null) {
+            // 遍历客观题
+            process(student, student.getAnswerList(), student.getObjectiveQuestionList());
+        }
+    }
+
+    private void process(ExamStudent student, List<String> answerList, List<ExamQuestion> questionList) {
+        if (answerList == null || questionList == null) {
+            return;
+        }
+        int answerCount = answerList.size();
+        for (int i = 0; i < questionList.size(); i++) {
+            ExamQuestion question = questionList.get(i);
+            if (question.getTotalScore() == null || question.getTotalScore() == 0) {
+                // 跳过填充试题
+                continue;
+            }
+            // 有实际作答才需要进行选项分析
+            if (answerCount > i) {
+                findCounter(getKey(student, question), question).process(answerList.get(i));
+            }
+        }
+    }
+
+    public String getKey(ExamStudent student, ExamQuestion question) {
+        return question.getSubjectCode() + "\t" + question.isObjective() + "\t"
+                + StringUtils.trimToEmpty(question.getPaperType()) + "\t" + question.getMainNumber() + "\t"
+                + question.getSubNumber();
+    }
+
+    public void save() {
+        // TODO 需要补充选项分析结果保存
+    }
+
+    private OptionCounter findCounter(String key, ExamQuestion question) {
+        OptionCounter counter = counters.get(key);
+        if (counter == null) {
+            counter = new OptionCounter(question.getAnswer().length() > 1);
+            counters.put(key, counter);
+        }
+        return counter;
+    }
+
+}

+ 58 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/unit/LevelRange.java

@@ -0,0 +1,58 @@
+package cn.com.qmth.stmms.biz.report.utils.unit;
+
+/**
+ * 表示某个分数范围的配置工具类
+ * 
+ * @author luoshi
+ *
+ */
+public class LevelRange {
+
+    private Double gt;
+
+    private Double ge;
+
+    private Double lt;
+
+    private Double le;
+
+    public LevelRange(Double gt, Double ge, Double lt, Double le) {
+        this.gt = gt;
+        this.ge = ge;
+        this.lt = lt;
+        this.le = le;
+    }
+
+    public boolean match(double value) {
+        if (gt != null && value <= gt) {
+            return false;
+        }
+        if (ge != null && value < ge) {
+            return false;
+        }
+        if (lt != null && value >= lt) {
+            return false;
+        }
+        if (le != null && value > le) {
+            return false;
+        }
+        return true;
+    }
+
+    public Double getGt() {
+        return gt;
+    }
+
+    public Double getGe() {
+        return ge;
+    }
+
+    public Double getLt() {
+        return lt;
+    }
+
+    public Double getLe() {
+        return le;
+    }
+
+}

+ 84 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/unit/OptionCounter.java

@@ -0,0 +1,84 @@
+package cn.com.qmth.stmms.biz.report.utils.unit;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OptionCounter {
+
+    public static final String BLANK_ANSWER = "#";
+
+    public static int start = (int) 'A';
+
+    private boolean multi;
+
+    private Map<String, Integer> counter;
+
+    public OptionCounter(boolean multi) {
+        this.multi = multi;
+        this.counter = new HashMap<String, Integer>();
+    }
+
+    public void process(String answer) {
+        answer = formatAnswer(answer);
+        int length = answer.length();
+        if (multi && length > 1) {
+            // 支持多选题的选项拆分组合
+            for (int i = 0; i < length; i++) {
+                incr(String.valueOf(answer.charAt(i)));
+            }
+        } else {
+            // 单一选项
+            incr(answer);
+        }
+    }
+
+    private void incr(String key) {
+        Integer count = counter.get(key);
+        if (count == null) {
+            count = 0;
+        }
+        count++;
+        counter.put(key, count);
+    }
+
+    /**
+     * 对答案进行格式化,包括转换为大写,判断空选,去除非英文字母,排序等
+     *
+     * @param answer
+     * @return
+     */
+    private String formatAnswer(String answer) {
+        if (answer == null) {
+            return BLANK_ANSWER;
+        }
+        int[] chars = new int[26];
+        Arrays.fill(chars, 0);
+        for (int i = 0; i < answer.length(); i++) {
+            int index = (int) Character.toUpperCase(answer.charAt(i)) - start;
+            if (index >= 0 && index < 26) {
+                chars[index] = 1;
+            }
+        }
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < 26; i++) {
+            if (chars[i] == 1) {
+                result.append((char) (start + i));
+            }
+        }
+        if (result.length() == 0) {
+            return BLANK_ANSWER;
+        } else {
+            return result.toString();
+        }
+    }
+
+    public Map<String, Integer> getCounter() {
+        return counter;
+    }
+
+    public boolean isMulti() {
+        return multi;
+    }
+
+}

+ 52 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/unit/QuestionCounter.java

@@ -0,0 +1,52 @@
+package cn.com.qmth.stmms.biz.report.utils.unit;
+
+import java.util.Arrays;
+
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+
+/**
+ * 将小题按照等级归类统计的工具类
+ * 
+ * @author luoshi
+ *
+ */
+public class QuestionCounter {
+
+    // 与预设等级数量一样的计数结果
+    public int[] totalCount;
+
+    // 与预设等级数量一样的合计分值
+    public double[] totalScore;
+
+    // 预设置的等级集合
+    public LevelRange[] levels;
+
+    public int levelCount;
+
+    public QuestionCounter(LevelRange[] levels) {
+        this.levels = levels;
+        this.levelCount = levels.length;
+        this.totalCount = new int[levelCount];
+        this.totalScore = new double[levelCount];
+        Arrays.fill(totalCount, 0);
+        Arrays.fill(totalScore, 0d);
+    }
+
+    public void process(ExamQuestion question, double value) {
+        for (int i = 0; i < levels.length; i++) {
+            if (levels[i].match(value)) {
+                totalCount[i]++;
+                totalScore[i] += question.getTotalScore();
+                return;
+            }
+        }
+    }
+
+    public int[] getTotalCount() {
+        return totalCount;
+    }
+
+    public double[] getTotalScore() {
+        return totalScore;
+    }
+}