Explorar o código

Merge branch 'fix/成果报告命中率取值' of jyx/dcjxb.microservice into master

金逸霄 hai 1 semana
pai
achega
0d2672eb14

+ 8 - 16
abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/AchievementReportContentMapper.java

@@ -16,8 +16,8 @@ final class AchievementReportContentMapper {
 
     static AchievementReportContent toDomainContent(AchievementExamSprintReportPayload payload) {
         Objects.requireNonNull(payload, "payload");
-        String masteryHitRateText = masteryHitRateText(payload.testPaperImprovedWordCount(), payload.studentImproveWordCount());
-        String learningEfficiencyText = learningEfficiencyText(payload.testPaperImprovedWordCount(), payload.studentImproveWordCount());
+        String masteryHitRateText = masteryHitRateText(payload.paperMasteryHitRate());
+        String learningEfficiencyText = learningEfficiencyText(payload.paperMasteryHitRate());
         return new AchievementReportContent(
                 payload.studentName(),
                 reportTitle(payload.stageName()),
@@ -81,22 +81,14 @@ final class AchievementReportContentMapper {
         return value.stripTrailingZeros().toPlainString();
     }
 
-    private static String masteryHitRateText(BigDecimal improvedWordCount, BigDecimal studentImproveWordCount) {
-        if (studentImproveWordCount.signum() == 0) {
-            return "0%";
-        }
-        BigDecimal derivedRate = improvedWordCount
-                .multiply(BigDecimal.valueOf(100))
-                .divide(studentImproveWordCount, 1, RoundingMode.HALF_UP);
-        return derivedRate.toPlainString() + "%";
+    private static String masteryHitRateText(BigDecimal paperMasteryHitRate) {
+        return format(paperMasteryHitRate) + "%";
     }
 
-    private static String learningEfficiencyText(BigDecimal improvedWordCount, BigDecimal studentImproveWordCount) {
-        if (studentImproveWordCount.signum() == 0) {
-            return "0";
-        }
-        BigDecimal derivedEfficiency = improvedWordCount
-                .divide(studentImproveWordCount.multiply(new BigDecimal("0.04")), 6, RoundingMode.HALF_UP);
+    private static String learningEfficiencyText(BigDecimal paperMasteryHitRate) {
+        BigDecimal derivedEfficiency = paperMasteryHitRate
+                .divide(BigDecimal.valueOf(100), 8, RoundingMode.HALF_UP)
+                .divide(new BigDecimal("0.04"), 6, RoundingMode.HALF_UP);
         return format(derivedEfficiency);
     }
 

+ 18 - 13
abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/AchievementReportContentMapperTest.java

@@ -13,7 +13,7 @@ class AchievementReportContentMapperTest {
 
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
-    /** 覆盖调用方 PascalCase 成果报告完整映射场景,当命中真题词数为 4 且学生提分词数为 19 时,应按直接公式展示学习效率。 */
+    /** 覆盖调用方 PascalCase 成果报告完整映射场景,当调用方提供 PaperMasteryHitRate 时,应按该值展示命中率并计算学习效率。 */
     @Test
     void mapsCallerPascalCasePayloadToExistingAchievementContent() {
         AchievementReportContent content = AchievementReportContentMapper.toDomainContent(payload());
@@ -25,8 +25,8 @@ class AchievementReportContentMapperTest {
         assertThat(content.completionSubtitle()).isEqualTo("基于2024真题 · 真实学习效果分析");
         assertThat(content.summaryMetrics().vocabularyGrowthText()).isEqualTo("+19");
         assertThat(content.summaryMetrics().paperKnownWordsGrowthText()).isEqualTo("+4");
-        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("21.1%");
-        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("5.263158");
+        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("1.93%");
+        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("0.4825");
         assertThat(content.vocabularyComparison().beforeValue()).isEqualTo(2328.0);
         assertThat(content.vocabularyComparison().afterText()).isEqualTo("2347");
         assertThat(content.vocabularyComparison().growthText()).isEqualTo("+19");
@@ -45,8 +45,8 @@ class AchievementReportContentMapperTest {
         assertThat(content.testPaperVocabularySummary().masteryRateBeforeText()).isEqualTo("75.49");
         assertThat(content.testPaperVocabularySummary().masteryRateAfterText()).isEqualTo("75.96");
         assertThat(content.testPaperVocabularySummary().masteryRateImprovementText()).isEqualTo("+0.62");
-        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("21.1%");
-        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("5.263158");
+        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("1.93%");
+        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("0.4825");
         assertThat(content.examUnknownWordsHitStatus().unknownWordsBeforeText()).isEqualTo("207");
         assertThat(content.examUnknownWordsHitStatus().unknownWordsAfterText()).isEqualTo("203");
         assertThat(content.examUnknownWordsHitStatus().reducedUnknownWordsText()).isEqualTo("4");
@@ -54,30 +54,35 @@ class AchievementReportContentMapperTest {
                 .containsExactly("number", "bear", "popular", "importance");
     }
 
-    /** 覆盖学生提分词数为零的边界场景,当分母为 0 时,应展示 0% 命中率。 */
+    /** 覆盖学生提分词数为零但调用方提供命中率场景,当分母为 0 时,命中率仍应取 PaperMasteryHitRate。 */
     @Test
-    void mapsZeroMasteryHitRateWhenStudentImproveWordCountIsZero() {
+    void mapsPayloadMasteryHitRateWhenStudentImproveWordCountIsZero() {
         ObjectNode payload = pascalPayload();
         payload.put("StudentImproveWordCount", 0);
         payload.put("PaperMasteryHitRate", 88.88);
 
         AchievementReportContent content = AchievementReportContentMapper.toDomainContent(convert(payload));
 
-        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("0%");
-        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("0%");
+        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("88.88%");
+        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("22.22");
+        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("88.88%");
+        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("22.22");
     }
 
-    /** 覆盖命中率整百分比展示场景,当计算结果为整数百分比时,应保留一位小数。 */
+    /** 覆盖调用方命中率为整百分比场景,当 PaperMasteryHitRate 为 20.00 时,应按 payload 格式化后展示并计算效率。 */
     @Test
-    void mapsWholeNumberMasteryHitRateWithOneDecimalPlace() {
+    void mapsWholeNumberPayloadMasteryHitRate() {
         ObjectNode payload = pascalPayload();
         payload.put("TestPaperImprovedWordCount", 1);
         payload.put("StudentImproveWordCount", 5);
+        payload.put("PaperMasteryHitRate", 20.00);
 
         AchievementReportContent content = AchievementReportContentMapper.toDomainContent(convert(payload));
 
-        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("20.0%");
-        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("20.0%");
+        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("20%");
+        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("5");
+        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("20%");
+        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("5");
     }
 
     /** 覆盖负向增量展示场景,当学生提分词数为负且阶段名已含英语时,应保留负号且不重复英语。 */

+ 17 - 14
abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportApplicationServiceTest.java

@@ -346,16 +346,16 @@ class ExamSprintReportApplicationServiceTest {
         assertThat(content.reportSubtitle()).isEqualTo("2024真题 · 临考突击 · 真实提分效果");
         assertThat(content.summaryMetrics().vocabularyGrowthText()).isEqualTo("+19");
         assertThat(content.summaryMetrics().paperKnownWordsGrowthText()).isEqualTo("+4");
-        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("21.1%");
-        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("5.263158");
+        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("1.93%");
+        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("0.4825");
         assertThat(content.stageVocabularySummary().stageName()).isEqualTo("高考");
         assertThat(content.stageVocabularySummary().stageVocabularyText()).isEqualTo("3500");
         assertThat(content.stageVocabularySummary().masteryImprovementText()).isEqualTo("+0.55");
         assertThat(content.testPaperVocabularySummary().testPaperTitle()).isEqualTo("2024真题");
         assertThat(content.testPaperVocabularySummary().testPaperWordCountText()).isEqualTo("861");
         assertThat(content.testPaperVocabularySummary().masteryRateImprovementText()).isEqualTo("+0.62");
-        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("21.1%");
-        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("5.263158");
+        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("1.93%");
+        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("0.4825");
         assertThat(content.examUnknownWordsHitStatus().hitWords())
                 .containsExactly("number", "bear", "popular", "importance");
     }
@@ -382,9 +382,9 @@ class ExamSprintReportApplicationServiceTest {
                 .doesNotContain("ignored-large-array");
     }
 
-    /** 覆盖成果报告学生提分词数为零的边界场景,当分母为 0 时,应保存 0% 命中率与 0 学习效率。 */
+    /** 覆盖成果报告学生提分词数为零但调用方提供命中率场景,当保存领域内容时,应取 PaperMasteryHitRate 并按该命中率计算学习效率。 */
     @Test
-    void createAchievementReportStoresZeroPercentWhenStudentImproveWordCountIsZero() {
+    void createAchievementReportStoresPayloadMasteryRateWhenStudentImproveWordCountIsZero() {
         TestRepository repository = new TestRepository();
         DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, new TestStorage());
         ObjectNode payload = validAchievementPayload().deepCopy();
@@ -394,26 +394,29 @@ class ExamSprintReportApplicationServiceTest {
         var response = service.createAchievementReport(payload);
 
         AchievementReportContent content = (AchievementReportContent) repository.findById(response.reportId()).orElseThrow().content();
-        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("0%");
-        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("0");
-        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("0%");
-        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("0");
+        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("77.77%");
+        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("19.4425");
+        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("77.77%");
+        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("19.4425");
     }
 
-    /** 覆盖成果报告学习效率舍入边界场景,当直接公式与中间舍入结果不同,应保存最终直接公式的 6 位小数。 */
+    /** 覆盖成果报告学习效率口径场景,当词数比例与 PaperMasteryHitRate 不一致时,应按 PaperMasteryHitRate 除以 0.04 计算。 */
     @Test
-    void createAchievementReportCalculatesLearningEfficiencyWithoutIntermediateRounding() {
+    void createAchievementReportCalculatesLearningEfficiencyFromPayloadMasteryRate() {
         TestRepository repository = new TestRepository();
         DefaultExamSprintReportApplicationService service = service(repository, reportId -> { }, new TestStorage());
         ObjectNode payload = validAchievementPayload().deepCopy();
         payload.put("TestPaperImprovedWordCount", 6);
         payload.put("StudentImproveWordCount", 7);
+        payload.put("PaperMasteryHitRate", 33.333333);
 
         var response = service.createAchievementReport(payload);
 
         AchievementReportContent content = (AchievementReportContent) repository.findById(response.reportId()).orElseThrow().content();
-        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("21.428571");
-        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("21.428571");
+        assertThat(content.summaryMetrics().unknownWordHitRateText()).isEqualTo("33.333333%");
+        assertThat(content.summaryMetrics().learningEfficiencyText()).isEqualTo("8.333333");
+        assertThat(content.examUnknownWordsHitStatus().unknownWordHitRateText()).isEqualTo("33.333333%");
+        assertThat(content.examUnknownWordsHitStatus().learningEfficiencyText()).isEqualTo("8.333333");
     }
 
     /** 覆盖成果报告调用方 PascalCase 类型边界场景,当核心数字字段为 string 时,应在保存前校验失败。 */