Prechádzať zdrojové kódy

fix(exam-sprint): 调整报告入参去单位展示

金逸霄 2 týždňov pred
rodič
commit
a9623dda2a
10 zmenil súbory, kde vykonal 305 pridanie a 114 odobranie
  1. 10 10
      abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/AchievementReportContentMapperTest.java
  2. 13 13
      abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportApplicationServiceTest.java
  3. 7 7
      abilities/exam-sprint/domain/src/test/java/cn/yunzhixue/ability/center/examsprint/domain/report/AchievementReportContentTest.java
  4. 66 17
      abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRenderer.java
  5. 18 13
      abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/OpenHtmlToPdfExamSprintReportPdfGeneratorTest.java
  6. 120 15
      abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRendererTest.java
  7. 13 13
      ability-center-runtime/scripts/achievement-report-demo.sh
  8. 32 0
      ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/examsprint/adapter/http/ExamSprintReportControllerTest.java
  9. 13 13
      ability-center-runtime/src/test/resources/requests/exam-sprint-achievement-report-invalid-request.json
  10. 13 13
      ability-center-runtime/src/test/resources/requests/exam-sprint-achievement-report-request.json

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

@@ -18,8 +18,8 @@ class AchievementReportContentMapperTest {
         assertThat(content.reportTitle()).isEqualTo("高考英语临考突击学习成果报告");
         assertThat(content.summaryMetrics().vocabularyGrowthText()).isEqualTo("+19");
         assertThat(content.vocabularyComparison().beforeValue()).isEqualTo(2328.0);
-        assertThat(content.vocabularyComparison().afterText()).isEqualTo("2347");
-        assertThat(content.paperKnownWordsComparison().growthText()).isEqualTo("+4");
+        assertThat(content.vocabularyComparison().afterText()).isEqualTo("2347");
+        assertThat(content.paperKnownWordsComparison().growthText()).isEqualTo("+4");
         assertThat(content.examUnknownWordsHitStatus().hitWords())
                 .containsExactly("number", "bear", "popular", "importance");
     }
@@ -37,15 +37,15 @@ class AchievementReportContentMapperTest {
                 "2024真题 · 两周专项训练 · 真实提分效果",
                 "恭喜完成两周考前突击专项训练",
                 "基于2024英语真题试卷 · 真实学习效果分析",
-                new AchievementExamSprintReportPayload.SummaryMetrics("+19", "+4", "1.93%", "0.48"),
-                new AchievementExamSprintReportPayload.Comparison(2328.0, 2347.0, "2328", "2347", "+19"),
-                new AchievementExamSprintReportPayload.Comparison(650.0, 654.0, "650", "654", "+4"),
+                new AchievementExamSprintReportPayload.SummaryMetrics("+19", "+4", "0.0193", "0.48"),
+                new AchievementExamSprintReportPayload.Comparison(2328.0, 2347.0, "2328", "2347", "+19"),
+                new AchievementExamSprintReportPayload.Comparison(650.0, 654.0, "650", "654", "+4"),
                 new AchievementExamSprintReportPayload.ExamUnknownWordsHitStatus(
-                        "1.93%",
-                        "0.48",
-                        "207",
-                        "203",
-                        "4",
+                        "0.0193",
+                        "0.48",
+                        "207",
+                        "203",
+                        "4",
                         List.of("number", "bear", "popular", "importance")));
     }
 }

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

@@ -620,26 +620,26 @@ class ExamSprintReportApplicationServiceTest {
                 new AchievementExamSprintReportPayload.SummaryMetrics(
                         "+19",
                         "+4",
-                        "1.93%",
-                        "0.48"),
+                        "0.0193",
+                        "0.48"),
                 new AchievementExamSprintReportPayload.Comparison(
                         2328.0,
                         2347.0,
-                        "2328",
-                        "2347",
-                        "+19"),
+                        "2328",
+                        "2347",
+                        "+19"),
                 new AchievementExamSprintReportPayload.Comparison(
                         650.0,
                         654.0,
-                        "650",
-                        "654",
-                        "+4"),
+                        "650",
+                        "654",
+                        "+4"),
                 new AchievementExamSprintReportPayload.ExamUnknownWordsHitStatus(
-                        "1.93%",
-                        "0.48",
-                        "207",
-                        "203",
-                        "4",
+                        "0.0193",
+                        "0.48",
+                        "207",
+                        "203",
+                        "4",
                         List.of("number", "bear", "popular", "importance"))));
     }
 

+ 7 - 7
abilities/exam-sprint/domain/src/test/java/cn/yunzhixue/ability/center/examsprint/domain/report/AchievementReportContentTest.java

@@ -42,23 +42,23 @@ class AchievementReportContentTest {
                 "2024真题 · 两周专项训练 · 真实提分效果",
                 "恭喜完成两周考前突击专项训练",
                 "基于2024英语真题试卷 · 真实学习效果分析",
-                new AchievementReportContent.SummaryMetrics("+19", "+4", "1.93%", "0.48"),
+                new AchievementReportContent.SummaryMetrics("+19", "+4", "0.0193", "0.48"),
                 comparison(),
                 comparison(),
                 hitStatus(hitWords));
     }
 
     private AchievementReportContent.Comparison comparison() {
-        return new AchievementReportContent.Comparison(2328.0, 2347.0, "2328", "2347", "+19");
+        return new AchievementReportContent.Comparison(2328.0, 2347.0, "2328", "2347", "+19");
     }
 
     private AchievementReportContent.ExamUnknownWordsHitStatus hitStatus(List<String> hitWords) {
         return new AchievementReportContent.ExamUnknownWordsHitStatus(
-                "1.93%",
-                "0.48",
-                "207",
-                "203",
-                "4",
+                "0.0193",
+                "0.48",
+                "207",
+                "203",
+                "4",
                 hitWords);
     }
 }

+ 66 - 17
abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRenderer.java

@@ -13,6 +13,7 @@ import java.io.UncheckedIOException;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.LinkedHashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.regex.Matcher;
@@ -70,21 +71,21 @@ public class ClasspathAchievementExamSprintReportRenderer implements ExamSprintR
         placeholders.put("completionSubtitle", escape(reportContent.completionSubtitle()));
         placeholders.put("vocabularyGrowthText", escape(summary.vocabularyGrowthText()));
         placeholders.put("paperKnownWordsGrowthText", escape(summary.paperKnownWordsGrowthText()));
-        placeholders.put("unknownWordHitRateText", escape(summary.unknownWordHitRateText()));
-        placeholders.put("learningEfficiencyText", escape(summary.learningEfficiencyText()));
+        placeholders.put("unknownWordHitRateText", escape(formatPercentRatio(summary.unknownWordHitRateText())));
+        placeholders.put("learningEfficiencyText", escape(withUnit(summary.learningEfficiencyText(), "倍")));
         placeholders.put("vocabularyComparisonChart", renderVocabularyComparisonChart(vocabulary));
         placeholders.put("paperKnownWordsComparisonChart", renderPaperKnownWordsComparisonChart(paperKnownWords));
-        placeholders.put("vocabularyBeforeText", escape(vocabulary.beforeText()));
-        placeholders.put("vocabularyAfterText", escape(vocabulary.afterText()));
-        placeholders.put("vocabularyGrowthDetailText", escape(vocabulary.growthText()));
-        placeholders.put("paperKnownWordsBeforeText", escape(paperKnownWords.beforeText()));
-        placeholders.put("paperKnownWordsAfterText", escape(paperKnownWords.afterText()));
-        placeholders.put("paperKnownWordsGrowthDetailText", escape(paperKnownWords.growthText()));
-        placeholders.put("unknownWordsBeforeText", escape(hitStatus.unknownWordsBeforeText()));
-        placeholders.put("unknownWordsAfterText", escape(hitStatus.unknownWordsAfterText()));
-        placeholders.put("reducedUnknownWordsText", escape(hitStatus.reducedUnknownWordsText()));
-        placeholders.put("hitStatusUnknownWordHitRateText", escape(hitStatus.unknownWordHitRateText()));
-        placeholders.put("hitStatusLearningEfficiencyText", escape(hitStatus.learningEfficiencyText()));
+        placeholders.put("vocabularyBeforeText", escape(withSeparatedUnit(vocabulary.beforeText(), "词")));
+        placeholders.put("vocabularyAfterText", escape(withSeparatedUnit(vocabulary.afterText(), "词")));
+        placeholders.put("vocabularyGrowthDetailText", escape(withSeparatedUnit(vocabulary.growthText(), "词")));
+        placeholders.put("paperKnownWordsBeforeText", escape(withSeparatedUnit(paperKnownWords.beforeText(), "个")));
+        placeholders.put("paperKnownWordsAfterText", escape(withSeparatedUnit(paperKnownWords.afterText(), "个")));
+        placeholders.put("paperKnownWordsGrowthDetailText", escape(withSeparatedUnit(paperKnownWords.growthText(), "个")));
+        placeholders.put("unknownWordsBeforeText", escape(withSeparatedUnit(hitStatus.unknownWordsBeforeText(), "个")));
+        placeholders.put("unknownWordsAfterText", escape(withSeparatedUnit(hitStatus.unknownWordsAfterText(), "个")));
+        placeholders.put("reducedUnknownWordsText", escape(withSeparatedUnit(hitStatus.reducedUnknownWordsText(), "个")));
+        placeholders.put("hitStatusUnknownWordHitRateText", escape(formatPercentRatio(hitStatus.unknownWordHitRateText())));
+        placeholders.put("hitStatusLearningEfficiencyText", escape(withUnit(hitStatus.learningEfficiencyText(), "倍")));
         placeholders.put("hitWords", renderHitWords(hitStatus));
         return placeholders;
     }
@@ -113,10 +114,10 @@ public class ClasspathAchievementExamSprintReportRenderer implements ExamSprintR
                 "词汇量对比",
                 "训练前",
                 safeNonNegativeFinite(comparison.beforeValue()),
-                comparison.beforeText(),
+                withSeparatedUnit(comparison.beforeText(), "词"),
                 "训练后",
                 safeNonNegativeFinite(comparison.afterValue()),
-                comparison.afterText(),
+                withSeparatedUnit(comparison.afterText(), "词"),
                 "#448aff"
         );
     }
@@ -127,10 +128,10 @@ public class ClasspathAchievementExamSprintReportRenderer implements ExamSprintR
                 "试卷熟词量对比",
                 "训练前",
                 safeNonNegativeFinite(comparison.beforeValue()),
-                comparison.beforeText(),
+                withSeparatedUnit(comparison.beforeText(), "个"),
                 "训练后",
                 safeNonNegativeFinite(comparison.afterValue()),
-                comparison.afterText(),
+                withSeparatedUnit(comparison.afterText(), "个"),
                 "#34a853"
         );
     }
@@ -156,6 +157,54 @@ public class ClasspathAchievementExamSprintReportRenderer implements ExamSprintR
         return value;
     }
 
+    private String withSeparatedUnit(String value, String unit) {
+        String normalized = normalizeDisplayValue(value);
+        if (normalized.isEmpty() || isTemplatePlaceholder(normalized) || normalized.endsWith(unit) || normalized.endsWith(" " + unit)) {
+            return normalized;
+        }
+        return normalized + " " + unit;
+    }
+
+    private String withUnit(String value, String unit) {
+        String normalized = normalizeDisplayValue(value);
+        if (normalized.isEmpty() || isTemplatePlaceholder(normalized) || normalized.endsWith(unit)) {
+            return normalized;
+        }
+        return normalized + unit;
+    }
+
+    private String formatPercentRatio(String value) {
+        String normalized = normalizeDisplayValue(value);
+        if (normalized.isEmpty() || isTemplatePlaceholder(normalized) || normalized.endsWith("%")) {
+            return normalized;
+        }
+        try {
+            double ratio = Double.parseDouble(normalized);
+            if (!Double.isFinite(ratio)) {
+                return normalized;
+            }
+            return trimTrailingZeros(ratio * 100d) + "%";
+        } catch (NumberFormatException ignored) {
+            return normalized;
+        }
+    }
+
+    private String trimTrailingZeros(double value) {
+        if (!Double.isFinite(value)) {
+            return "0";
+        }
+        String formatted = String.format(Locale.ROOT, "%.2f", value);
+        return formatted.replaceAll("\\.0+$", "").replaceAll("(\\.\\d*?)0+$", "$1");
+    }
+
+    private String normalizeDisplayValue(String value) {
+        return value == null ? "" : value.trim();
+    }
+
+    private boolean isTemplatePlaceholder(String value) {
+        return value.startsWith("{{") && value.endsWith("}}");
+    }
+
     private String escape(String value) {
         if (value == null) {
             return "";

+ 18 - 13
abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/OpenHtmlToPdfExamSprintReportPdfGeneratorTest.java

@@ -89,6 +89,11 @@ class OpenHtmlToPdfExamSprintReportPdfGeneratorTest {
                     .contains("模块三:实考生词命中状况")
                     .contains("词汇量提升(个)")
                     .contains("真题生词命中率")
+                    .contains("1.93%")
+                    .contains("0.48倍")
+                    .contains("207个")
+                    .contains("203个")
+                    .contains("4个")
                     .containsAnyOf("number", "bear", "popular", "importance");
         }
     }
@@ -346,29 +351,29 @@ class OpenHtmlToPdfExamSprintReportPdfGeneratorTest {
                   "summaryMetrics": {
                     "vocabularyGrowthText": "+19",
                     "paperKnownWordsGrowthText": "+4",
-                    "unknownWordHitRateText": "1.93%",
-                    "learningEfficiencyText": "0.48"
+                    "unknownWordHitRateText": "0.0193",
+                    "learningEfficiencyText": "0.48"
                   },
                   "vocabularyComparison": {
                     "beforeValue": 2328,
                     "afterValue": 2347,
-                    "beforeText": "2328",
-                    "afterText": "2347",
-                    "growthText": "+19"
+                    "beforeText": "2328",
+                    "afterText": "2347",
+                    "growthText": "+19"
                   },
                   "paperKnownWordsComparison": {
                     "beforeValue": 650,
                     "afterValue": 654,
-                    "beforeText": "650",
-                    "afterText": "654",
-                    "growthText": "+4"
+                    "beforeText": "650",
+                    "afterText": "654",
+                    "growthText": "+4"
                   },
                   "examUnknownWordsHitStatus": {
-                    "unknownWordHitRateText": "1.93%",
-                    "learningEfficiencyText": "0.48",
-                    "unknownWordsBeforeText": "207",
-                    "unknownWordsAfterText": "203",
-                    "reducedUnknownWordsText": "4",
+                    "unknownWordHitRateText": "0.0193",
+                    "learningEfficiencyText": "0.48",
+                    "unknownWordsBeforeText": "207",
+                    "unknownWordsAfterText": "203",
+                    "reducedUnknownWordsText": "4",
                     "hitWords": ["number", "bear", "popular", "importance"]
                   }
                 }

+ 120 - 15
abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRendererTest.java

@@ -70,6 +70,12 @@ class ClasspathAchievementExamSprintReportRendererTest {
                 .contains("模块一:词汇量对比")
                 .contains("模块二:试卷熟词量对比")
                 .contains("模块三:实考生词命中状况")
+                .contains("class=\"result-value\">1.93%</div>")
+                .contains("class=\"result-value\">0.48倍</div>")
+                .contains("class=\"hit-stat-value\">1.93%</div>")
+                .contains("class=\"hit-stat-value\">0.48倍</div>")
+                .contains("class=\"hit-stat-value\">207 个</div>")
+                .contains("class=\"hit-stat-value\">203 个</div>")
                 .contains("class='achievement-bar-chart vocabulary-growth-chart'")
                 .contains("class='achievement-bar-chart paper-known-words-chart'")
                 .contains("chart-y-axis")
@@ -90,7 +96,11 @@ class ClasspathAchievementExamSprintReportRendererTest {
                 .doesNotContain("<script")
                 .doesNotContain("comparison-table")
                 .doesNotContain("chart-cell")
-                .doesNotContain("detail-cell");
+                .doesNotContain("detail-cell")
+                .doesNotContain("class=\"result-value\">0.0193</div>")
+                .doesNotContain("class=\"result-value\">0.48</div>")
+                .doesNotContain("class=\"hit-stat-value\">207</div>")
+                .doesNotContain("class=\"hit-stat-value\">203</div>");
 
         assertThat(countOccurrences(html, "class='chart-grid-line'")).isEqualTo(6);
         assertThat(countOccurrences(html, "class='chart-tick-label'")).isEqualTo(6);
@@ -186,8 +196,103 @@ class ClasspathAchievementExamSprintReportRendererTest {
 
         assertThat(html)
                 .contains("<h1 class=\"report-title\">{{hitWords}}</h1>")
+                .contains("训练前词汇量:<span class=\"highlight\">{{hitWords}}</span><br/>")
                 .contains(">{{hitWords}}</text>")
-                .contains("class=\"word-item\">number</div>");
+                .contains("class=\"word-item\">number</div>")
+                .doesNotContain("{{hitWords}} 词");
+    }
+
+    @Test
+    void renderKeepsLegacyUnitTextIdempotent() throws Exception {
+        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer();
+        AchievementReportContent content = sampleContent();
+        AchievementReportContent mutated = new AchievementReportContent(
+                content.reportTitle(),
+                content.reportSubtitle(),
+                content.completionTitle(),
+                content.completionSubtitle(),
+                new AchievementReportContent.SummaryMetrics(
+                        content.summaryMetrics().vocabularyGrowthText(),
+                        content.summaryMetrics().paperKnownWordsGrowthText(),
+                        "1.93%",
+                        "0.48倍"),
+                new AchievementReportContent.Comparison(
+                        content.vocabularyComparison().beforeValue(),
+                        content.vocabularyComparison().afterValue(),
+                        "2328 词",
+                        "2347词",
+                        "+19 词"),
+                new AchievementReportContent.Comparison(
+                        content.paperKnownWordsComparison().beforeValue(),
+                        content.paperKnownWordsComparison().afterValue(),
+                        "650 个",
+                        "654个",
+                        "+4 个"),
+                new AchievementReportContent.ExamUnknownWordsHitStatus(
+                        "1.93%",
+                        "0.48倍",
+                        "207 个",
+                        "203个",
+                        "4 个",
+                        content.examUnknownWordsHitStatus().hitWords()));
+
+        String html = renderer.render(mutated, Instant.parse("2026-04-25T08:00:00Z"));
+
+        assertThat(html)
+                .contains("class=\"result-value\">1.93%</div>")
+                .contains("class=\"result-value\">0.48倍</div>")
+                .contains("训练前词汇量:<span class=\"highlight\">2328 词</span><br/>")
+                .contains("训练后词汇量:<span class=\"highlight\">2347词</span><br/>")
+                .contains("本次提升:<span class=\"highlight\">+19 词</span>")
+                .contains("训练前熟词量:<span class=\"highlight\">650 个</span><br/>")
+                .contains("训练后熟词量:<span class=\"highlight\">654个</span><br/>")
+                .contains("本次提升:<span class=\"highlight\">+4 个</span>")
+                .contains("class=\"hit-stat-value\">1.93%</div>")
+                .contains("class=\"hit-stat-value\">0.48倍</div>")
+                .contains("class=\"hit-stat-value\">207 个</div>")
+                .contains("class=\"hit-stat-value\">203个</div>")
+                .contains("成功减少生词:<span class=\"highlight\">4 个</span>")
+                .doesNotContain("1.93%%")
+                .doesNotContain("0.48倍倍")
+                .doesNotContain("2328 词 词")
+                .doesNotContain("2347词 词")
+                .doesNotContain("650 个 个")
+                .doesNotContain("654个 个")
+                .doesNotContain("207 个 个")
+                .doesNotContain("203个 个");
+    }
+
+    @Test
+    void renderKeepsNonFinitePercentTextUnchanged() throws Exception {
+        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer();
+        AchievementReportContent content = sampleContent();
+        AchievementReportContent mutated = new AchievementReportContent(
+                content.reportTitle(),
+                content.reportSubtitle(),
+                content.completionTitle(),
+                content.completionSubtitle(),
+                new AchievementReportContent.SummaryMetrics(
+                        content.summaryMetrics().vocabularyGrowthText(),
+                        content.summaryMetrics().paperKnownWordsGrowthText(),
+                        "NaN",
+                        content.summaryMetrics().learningEfficiencyText()),
+                content.vocabularyComparison(),
+                content.paperKnownWordsComparison(),
+                new AchievementReportContent.ExamUnknownWordsHitStatus(
+                        "Infinity",
+                        content.examUnknownWordsHitStatus().learningEfficiencyText(),
+                        content.examUnknownWordsHitStatus().unknownWordsBeforeText(),
+                        content.examUnknownWordsHitStatus().unknownWordsAfterText(),
+                        content.examUnknownWordsHitStatus().reducedUnknownWordsText(),
+                        content.examUnknownWordsHitStatus().hitWords()));
+
+        String html = renderer.render(mutated, Instant.parse("2026-04-25T08:00:00Z"));
+
+        assertThat(html)
+                .contains("class=\"result-value\">NaN</div>")
+                .contains("class=\"hit-stat-value\">Infinity</div>")
+                .doesNotContain("class=\"result-value\">0%</div>")
+                .doesNotContain("class=\"hit-stat-value\">0%</div>");
     }
 
     private AchievementReportContent sampleContent() throws Exception {
@@ -279,29 +384,29 @@ class ClasspathAchievementExamSprintReportRendererTest {
                   "summaryMetrics": {
                     "vocabularyGrowthText": "+19",
                     "paperKnownWordsGrowthText": "+4",
-                    "unknownWordHitRateText": "1.93%",
-                    "learningEfficiencyText": "0.48"
+                    "unknownWordHitRateText": "0.0193",
+                    "learningEfficiencyText": "0.48"
                   },
                   "vocabularyComparison": {
                     "beforeValue": 2328,
                     "afterValue": 2347,
-                    "beforeText": "2328",
-                    "afterText": "2347",
-                    "growthText": "+19"
+                    "beforeText": "2328",
+                    "afterText": "2347",
+                    "growthText": "+19"
                   },
                   "paperKnownWordsComparison": {
                     "beforeValue": 650,
                     "afterValue": 654,
-                    "beforeText": "650",
-                    "afterText": "654",
-                    "growthText": "+4"
+                    "beforeText": "650",
+                    "afterText": "654",
+                    "growthText": "+4"
                   },
                   "examUnknownWordsHitStatus": {
-                    "unknownWordHitRateText": "1.93%",
-                    "learningEfficiencyText": "0.48",
-                    "unknownWordsBeforeText": "207",
-                    "unknownWordsAfterText": "203",
-                    "reducedUnknownWordsText": "4",
+                    "unknownWordHitRateText": "0.0193",
+                    "learningEfficiencyText": "0.48",
+                    "unknownWordsBeforeText": "207",
+                    "unknownWordsAfterText": "203",
+                    "reducedUnknownWordsText": "4",
                     "hitWords": ["number", "bear", "popular", "importance"]
                   }
                 }

+ 13 - 13
ability-center-runtime/scripts/achievement-report-demo.sh

@@ -28,29 +28,29 @@ http_code="$({
   "summaryMetrics": {
     "vocabularyGrowthText": "+19",
     "paperKnownWordsGrowthText": "+4",
-    "unknownWordHitRateText": "1.93%",
-    "learningEfficiencyText": "0.48"
+    "unknownWordHitRateText": "0.0193",
+    "learningEfficiencyText": "0.48"
   },
   "vocabularyComparison": {
     "beforeValue": 2328,
     "afterValue": 2347,
-    "beforeText": "2328",
-    "afterText": "2347",
-    "growthText": "+19"
+    "beforeText": "2328",
+    "afterText": "2347",
+    "growthText": "+19"
   },
   "paperKnownWordsComparison": {
     "beforeValue": 650,
     "afterValue": 654,
-    "beforeText": "650",
-    "afterText": "654",
-    "growthText": "+4"
+    "beforeText": "650",
+    "afterText": "654",
+    "growthText": "+4"
   },
   "examUnknownWordsHitStatus": {
-    "unknownWordHitRateText": "1.93%",
-    "learningEfficiencyText": "0.48",
-    "unknownWordsBeforeText": "207",
-    "unknownWordsAfterText": "203",
-    "reducedUnknownWordsText": "4",
+    "unknownWordHitRateText": "0.0193",
+    "learningEfficiencyText": "0.48",
+    "unknownWordsBeforeText": "207",
+    "unknownWordsAfterText": "203",
+    "reducedUnknownWordsText": "4",
     "hitWords": ["number", "bear", "popular", "importance"]
   }
 }

+ 32 - 0
ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/examsprint/adapter/http/ExamSprintReportControllerTest.java

@@ -117,6 +117,38 @@ class ExamSprintReportControllerTest {
                 .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_PDF));
     }
 
+    @Test
+    void achievementSyncFixtureUsesUnitlessTextInputs() throws Exception {
+        JsonNode payload = objectMapper.readTree(validAchievementRequestJson()).path("payload");
+
+        assertThat(payload.at("/summaryMetrics/vocabularyGrowthText").asText()).isEqualTo("+19");
+        assertThat(payload.at("/summaryMetrics/paperKnownWordsGrowthText").asText()).isEqualTo("+4");
+        assertThat(payload.at("/summaryMetrics/unknownWordHitRateText").asText()).isEqualTo("0.0193");
+        assertThat(payload.at("/summaryMetrics/learningEfficiencyText").asText()).isEqualTo("0.48");
+        assertThat(payload.at("/vocabularyComparison/beforeText").asText()).isEqualTo("2328");
+        assertThat(payload.at("/vocabularyComparison/afterText").asText()).isEqualTo("2347");
+        assertThat(payload.at("/vocabularyComparison/growthText").asText()).isEqualTo("+19");
+        assertThat(payload.at("/paperKnownWordsComparison/beforeText").asText()).isEqualTo("650");
+        assertThat(payload.at("/paperKnownWordsComparison/afterText").asText()).isEqualTo("654");
+        assertThat(payload.at("/paperKnownWordsComparison/growthText").asText()).isEqualTo("+4");
+        assertThat(payload.at("/examUnknownWordsHitStatus/unknownWordHitRateText").asText()).isEqualTo("0.0193");
+        assertThat(payload.at("/examUnknownWordsHitStatus/learningEfficiencyText").asText()).isEqualTo("0.48");
+        assertThat(payload.at("/examUnknownWordsHitStatus/unknownWordsBeforeText").asText()).isEqualTo("207");
+        assertThat(payload.at("/examUnknownWordsHitStatus/unknownWordsAfterText").asText()).isEqualTo("203");
+        assertThat(payload.at("/examUnknownWordsHitStatus/reducedUnknownWordsText").asText()).isEqualTo("4");
+    }
+
+    @Test
+    void outlookSyncFixtureDoesNotDefineTextFieldsRequiringUnitStripping() throws Exception {
+        JsonNode payload = objectMapper.readTree(validRequestJson()).path("payload");
+
+        assertThat(payload.findValuesAsText("learningEfficiencyText")).isEmpty();
+        assertThat(payload.findValuesAsText("unknownWordHitRateText")).isEmpty();
+        assertThat(payload.findValuesAsText("beforeText")).isEmpty();
+        assertThat(payload.findValuesAsText("afterText")).isEmpty();
+        assertThat(payload.findValuesAsText("growthText")).isEmpty();
+    }
+
     @Test
     void createAchievementReportWithInvalidPayloadReturnsValidationError() throws Exception {
         mockMvc.perform(post("/api/exam-sprint/achievement-reports")

+ 13 - 13
ability-center-runtime/src/test/resources/requests/exam-sprint-achievement-report-invalid-request.json

@@ -7,29 +7,29 @@
     "summaryMetrics": {
       "vocabularyGrowthText": "+19",
       "paperKnownWordsGrowthText": "+4",
-      "unknownWordHitRateText": "1.93%",
-      "learningEfficiencyText": "0.48"
+      "unknownWordHitRateText": "0.0193",
+      "learningEfficiencyText": "0.48"
     },
     "vocabularyComparison": {
       "beforeValue": -1,
       "afterValue": 2347,
-      "beforeText": "2328",
-      "afterText": "2347",
-      "growthText": "+19"
+      "beforeText": "2328",
+      "afterText": "2347",
+      "growthText": "+19"
     },
     "paperKnownWordsComparison": {
       "beforeValue": 650,
       "afterValue": 654,
-      "beforeText": "650",
-      "afterText": "654",
-      "growthText": "+4"
+      "beforeText": "650",
+      "afterText": "654",
+      "growthText": "+4"
     },
     "examUnknownWordsHitStatus": {
-      "unknownWordHitRateText": "1.93%",
-      "learningEfficiencyText": "0.48",
-      "unknownWordsBeforeText": "207",
-      "unknownWordsAfterText": "203",
-      "reducedUnknownWordsText": "4",
+      "unknownWordHitRateText": "0.0193",
+      "learningEfficiencyText": "0.48",
+      "unknownWordsBeforeText": "207",
+      "unknownWordsAfterText": "203",
+      "reducedUnknownWordsText": "4",
       "hitWords": ["number", "bear", "popular", "importance"]
     }
   }

+ 13 - 13
ability-center-runtime/src/test/resources/requests/exam-sprint-achievement-report-request.json

@@ -8,29 +8,29 @@
     "summaryMetrics": {
       "vocabularyGrowthText": "+19",
       "paperKnownWordsGrowthText": "+4",
-      "unknownWordHitRateText": "1.93%",
-      "learningEfficiencyText": "0.48"
+      "unknownWordHitRateText": "0.0193",
+      "learningEfficiencyText": "0.48"
     },
     "vocabularyComparison": {
       "beforeValue": 2328,
       "afterValue": 2347,
-      "beforeText": "2328",
-      "afterText": "2347",
-      "growthText": "+19"
+      "beforeText": "2328",
+      "afterText": "2347",
+      "growthText": "+19"
     },
     "paperKnownWordsComparison": {
       "beforeValue": 650,
       "afterValue": 654,
-      "beforeText": "650",
-      "afterText": "654",
-      "growthText": "+4"
+      "beforeText": "650",
+      "afterText": "654",
+      "growthText": "+4"
     },
     "examUnknownWordsHitStatus": {
-      "unknownWordHitRateText": "1.93%",
-      "learningEfficiencyText": "0.48",
-      "unknownWordsBeforeText": "207",
-      "unknownWordsAfterText": "203",
-      "reducedUnknownWordsText": "4",
+      "unknownWordHitRateText": "0.0193",
+      "learningEfficiencyText": "0.48",
+      "unknownWordsBeforeText": "207",
+      "unknownWordsAfterText": "203",
+      "reducedUnknownWordsText": "4",
       "hitWords": ["number", "bear", "popular", "importance"]
     }
   }