|
@@ -1,7 +1,6 @@
|
|
|
package cn.yunzhixue.ability.center.examsprint.infrastructure.report.rendering.outlook;
|
|
package cn.yunzhixue.ability.center.examsprint.infrastructure.report.rendering.outlook;
|
|
|
|
|
|
|
|
import cn.yunzhixue.ability.center.examsprint.contracts.report.OutlookExamSprintReportPayload;
|
|
import cn.yunzhixue.ability.center.examsprint.contracts.report.OutlookExamSprintReportPayload;
|
|
|
-import cn.yunzhixue.ability.center.examsprint.contracts.report.OutlookStudentVocabularyReportPayload;
|
|
|
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportRenderer;
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportRenderer;
|
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportContent;
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportContent;
|
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
|
|
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
|
|
@@ -52,16 +51,15 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
throw new IllegalArgumentException("Outlook renderer requires unmodeled OUTLOOK JsonNode content");
|
|
throw new IllegalArgumentException("Outlook renderer requires unmodeled OUTLOOK JsonNode content");
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
- OutlookExamSprintReportPayload reportPayload = isStudentVocabularyOutlookPayload(payload)
|
|
|
|
|
- ? adaptStudentVocabularyPayload(objectMapper.treeToValue(payload, OutlookStudentVocabularyReportPayload.class))
|
|
|
|
|
- : objectMapper.treeToValue(payload, OutlookExamSprintReportPayload.class);
|
|
|
|
|
|
|
+ OutlookExamSprintReportPayload payloadContract = objectMapper.treeToValue(payload, OutlookExamSprintReportPayload.class);
|
|
|
|
|
+ OutlookReportViewModel reportPayload = adaptPayload(payloadContract);
|
|
|
return loadTemplate()
|
|
return loadTemplate()
|
|
|
- .replace("{{syllabusMasteryChart}}", renderSyllabusMasteryChart(reportPayload.syllabusMasteryChart()))
|
|
|
|
|
- .replace("{{pastPaperVocabularyChart}}", renderPastPaperVocabularyChart(reportPayload.pastPaperVocabularyChart()))
|
|
|
|
|
- .replace("{{highFrequencyVocabularyChart}}", renderHighFrequencyVocabularyChart(reportPayload.highFrequencyVocabularyChart()))
|
|
|
|
|
- .replace("{{vocabularyFrequencyBandChart}}", renderVocabularyFrequencyBandChart(reportPayload.vocabularyFrequencyBandChart()))
|
|
|
|
|
- .replace("{{studySuggestionSection}}", renderStudySuggestionSection(reportPayload.frequencyPlan()))
|
|
|
|
|
- .replace("{{scoreImprovementCaseStudy}}", renderScoreImprovementCaseStudy(reportPayload.scoreImprovementCaseStudy()));
|
|
|
|
|
|
|
+ .replace("{{syllabusMasterySection}}", renderSyllabusMasteryChart(reportPayload.syllabusMasterySection()))
|
|
|
|
|
+ .replace("{{pastPaperVocabularySection}}", renderPastPaperVocabularyChart(reportPayload.pastPaperVocabularySection()))
|
|
|
|
|
+ .replace("{{highFrequencyVocabularySection}}", renderHighFrequencyVocabularyChart(reportPayload.highFrequencyVocabularySection()))
|
|
|
|
|
+ .replace("{{frequencyBandSection}}", renderVocabularyFrequencyBandChart(reportPayload.frequencyBandSection()))
|
|
|
|
|
+ .replace("{{studySuggestionSection}}", renderStudySuggestionSection(reportPayload.studyPlan()))
|
|
|
|
|
+ .replace("{{caseStudySection}}", renderScoreImprovementCaseStudy(reportPayload.caseStudy()));
|
|
|
} catch (IOException exception) {
|
|
} catch (IOException exception) {
|
|
|
throw new UncheckedIOException("Failed to load outlook exam sprint report template", exception);
|
|
throw new UncheckedIOException("Failed to load outlook exam sprint report template", exception);
|
|
|
} catch (Exception exception) {
|
|
} catch (Exception exception) {
|
|
@@ -69,113 +67,93 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private boolean isStudentVocabularyOutlookPayload(JsonNode payload) {
|
|
|
|
|
- return payload != null
|
|
|
|
|
- && payload.isObject()
|
|
|
|
|
- && payload.hasNonNull("StudentWordsLatest")
|
|
|
|
|
- && payload.hasNonNull("StudentName")
|
|
|
|
|
- && payload.hasNonNull("StageVocabulary")
|
|
|
|
|
- && payload.hasNonNull("StageExaminName");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private OutlookExamSprintReportPayload adaptStudentVocabularyPayload(OutlookStudentVocabularyReportPayload payload) {
|
|
|
|
|
|
|
+ private OutlookReportViewModel adaptPayload(OutlookExamSprintReportPayload payload) {
|
|
|
int stageVocabulary = payload.stageVocabulary();
|
|
int stageVocabulary = payload.stageVocabulary();
|
|
|
int masteredWordCount = payload.mastedWordCount();
|
|
int masteredWordCount = payload.mastedWordCount();
|
|
|
int unmasteredWordCount = payload.unMastedWordCount();
|
|
int unmasteredWordCount = payload.unMastedWordCount();
|
|
|
int masteryPercent = (int) Math.round(percentage(masteredWordCount, stageVocabulary));
|
|
int masteryPercent = (int) Math.round(percentage(masteredWordCount, stageVocabulary));
|
|
|
- List<OutlookStudentVocabularyReportPayload.StudentWordLatest> words = participatingWords(payload);
|
|
|
|
|
|
|
+ List<OutlookExamSprintReportPayload.StudentWordLatest> words = participatingWords(payload);
|
|
|
|
|
|
|
|
int basicUpper = (int) Math.ceil(stageVocabulary * 0.2d);
|
|
int basicUpper = (int) Math.ceil(stageVocabulary * 0.2d);
|
|
|
int frequentUpper = (int) Math.ceil(stageVocabulary * 0.6d);
|
|
int frequentUpper = (int) Math.ceil(stageVocabulary * 0.6d);
|
|
|
int highUpper = (int) Math.ceil(stageVocabulary * 0.3d);
|
|
int highUpper = (int) Math.ceil(stageVocabulary * 0.3d);
|
|
|
int midUpper = (int) Math.ceil(stageVocabulary * 0.7d);
|
|
int midUpper = (int) Math.ceil(stageVocabulary * 0.7d);
|
|
|
|
|
|
|
|
- return new OutlookExamSprintReportPayload(
|
|
|
|
|
- new OutlookExamSprintReportPayload.ReportMetadata(
|
|
|
|
|
- "2026 词汇展望报告",
|
|
|
|
|
- payload.studentName(),
|
|
|
|
|
- payload.stageExaminName(),
|
|
|
|
|
- payload.stageName() + "词汇冲刺",
|
|
|
|
|
- "Ability Bot"),
|
|
|
|
|
- new OutlookExamSprintReportPayload.ReadinessOverview(
|
|
|
|
|
- "词汇能力进入提分窗口,适合围绕考纲和高频场景做集中突破。",
|
|
|
|
|
- payload.stageName(),
|
|
|
|
|
- "高频与常考词群是提分关键。",
|
|
|
|
|
- masteryPercent),
|
|
|
|
|
- new OutlookExamSprintReportPayload.SyllabusMasteryChart(
|
|
|
|
|
|
|
+ return new OutlookReportViewModel(
|
|
|
|
|
+ new SyllabusMasteryChart(
|
|
|
stageVocabulary,
|
|
stageVocabulary,
|
|
|
masteredWordCount,
|
|
masteredWordCount,
|
|
|
unmasteredWordCount,
|
|
unmasteredWordCount,
|
|
|
masteryPercent,
|
|
masteryPercent,
|
|
|
"考纲词汇掌握概览",
|
|
"考纲词汇掌握概览",
|
|
|
"优先补齐高频和核心常考词。"),
|
|
"优先补齐高频和核心常考词。"),
|
|
|
- new OutlookExamSprintReportPayload.PastPaperVocabularyChart(
|
|
|
|
|
|
|
+ new PastPaperVocabularyChart(
|
|
|
payload.testPaperWordCount(),
|
|
payload.testPaperWordCount(),
|
|
|
payload.testPaperUnMasterWords().size(),
|
|
payload.testPaperUnMasterWords().size(),
|
|
|
payload.testPaperUnMasterWords().size(),
|
|
payload.testPaperUnMasterWords().size(),
|
|
|
"预计提分5-15分",
|
|
"预计提分5-15分",
|
|
|
"先压降真题生词占比。"),
|
|
"先压降真题生词占比。"),
|
|
|
- new OutlookExamSprintReportPayload.HighFrequencyVocabularyChart(
|
|
|
|
|
|
|
+ new HighFrequencyVocabularyChart(
|
|
|
roundedMasteryPercent(words, 0, basicUpper),
|
|
roundedMasteryPercent(words, 0, basicUpper),
|
|
|
roundedMasteryPercent(words, basicUpper, frequentUpper),
|
|
roundedMasteryPercent(words, basicUpper, frequentUpper),
|
|
|
roundedMasteryPercent(words, frequentUpper, stageVocabulary),
|
|
roundedMasteryPercent(words, frequentUpper, stageVocabulary),
|
|
|
"拉分词是提分核心突破项"),
|
|
"拉分词是提分核心突破项"),
|
|
|
- new OutlookExamSprintReportPayload.VocabularyFrequencyBandChart(
|
|
|
|
|
|
|
+ new VocabularyFrequencyBandChart(
|
|
|
List.of(
|
|
List.of(
|
|
|
- new OutlookExamSprintReportPayload.VocabularyFrequencyBar(
|
|
|
|
|
|
|
+ new VocabularyFrequencyBar(
|
|
|
"高频词", masteryPercent(words, 0, highUpper), "优先学习", "#448aff"),
|
|
"高频词", masteryPercent(words, 0, highUpper), "优先学习", "#448aff"),
|
|
|
- new OutlookExamSprintReportPayload.VocabularyFrequencyBar(
|
|
|
|
|
|
|
+ new VocabularyFrequencyBar(
|
|
|
"中频词", masteryPercent(words, highUpper, midUpper), "重点突破", "#4caf50"),
|
|
"中频词", masteryPercent(words, highUpper, midUpper), "重点突破", "#4caf50"),
|
|
|
- new OutlookExamSprintReportPayload.VocabularyFrequencyBar(
|
|
|
|
|
|
|
+ new VocabularyFrequencyBar(
|
|
|
"低频词", masteryPercent(words, midUpper, stageVocabulary), "酌情学习", "#ff9800"))),
|
|
"低频词", masteryPercent(words, midUpper, stageVocabulary), "酌情学习", "#ff9800"))),
|
|
|
staticFrequencyPlan(),
|
|
staticFrequencyPlan(),
|
|
|
staticScoreImprovementCaseStudy());
|
|
staticScoreImprovementCaseStudy());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private List<OutlookStudentVocabularyReportPayload.StudentWordLatest> participatingWords(OutlookStudentVocabularyReportPayload payload) {
|
|
|
|
|
|
|
+ private List<OutlookExamSprintReportPayload.StudentWordLatest> participatingWords(OutlookExamSprintReportPayload payload) {
|
|
|
int stageVocabulary = payload.stageVocabulary();
|
|
int stageVocabulary = payload.stageVocabulary();
|
|
|
return payload.studentWordsLatest().stream()
|
|
return payload.studentWordsLatest().stream()
|
|
|
.filter(word -> word.wordFrequency() >= 1 && word.wordFrequency() <= stageVocabulary)
|
|
.filter(word -> word.wordFrequency() >= 1 && word.wordFrequency() <= stageVocabulary)
|
|
|
.toList();
|
|
.toList();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private int roundedMasteryPercent(List<OutlookStudentVocabularyReportPayload.StudentWordLatest> words,
|
|
|
|
|
|
|
+ private int roundedMasteryPercent(List<OutlookExamSprintReportPayload.StudentWordLatest> words,
|
|
|
int lowerExclusive,
|
|
int lowerExclusive,
|
|
|
int upperInclusive) {
|
|
int upperInclusive) {
|
|
|
return (int) Math.round(masteryPercent(words, lowerExclusive, upperInclusive));
|
|
return (int) Math.round(masteryPercent(words, lowerExclusive, upperInclusive));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private double masteryPercent(List<OutlookStudentVocabularyReportPayload.StudentWordLatest> words,
|
|
|
|
|
|
|
+ private double masteryPercent(List<OutlookExamSprintReportPayload.StudentWordLatest> words,
|
|
|
int lowerExclusive,
|
|
int lowerExclusive,
|
|
|
int upperInclusive) {
|
|
int upperInclusive) {
|
|
|
- List<OutlookStudentVocabularyReportPayload.StudentWordLatest> matchedWords = words.stream()
|
|
|
|
|
|
|
+ List<OutlookExamSprintReportPayload.StudentWordLatest> matchedWords = words.stream()
|
|
|
.filter(word -> word.wordFrequency() > lowerExclusive && word.wordFrequency() <= upperInclusive)
|
|
.filter(word -> word.wordFrequency() > lowerExclusive && word.wordFrequency() <= upperInclusive)
|
|
|
.toList();
|
|
.toList();
|
|
|
if (matchedWords.isEmpty()) {
|
|
if (matchedWords.isEmpty()) {
|
|
|
return 0d;
|
|
return 0d;
|
|
|
}
|
|
}
|
|
|
return matchedWords.stream()
|
|
return matchedWords.stream()
|
|
|
- .mapToDouble(OutlookStudentVocabularyReportPayload.StudentWordLatest::mastery)
|
|
|
|
|
|
|
+ .mapToDouble(OutlookExamSprintReportPayload.StudentWordLatest::mastery)
|
|
|
.average()
|
|
.average()
|
|
|
.orElse(0d) * 100d;
|
|
.orElse(0d) * 100d;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private OutlookExamSprintReportPayload.FrequencyPlan staticFrequencyPlan() {
|
|
|
|
|
- return new OutlookExamSprintReportPayload.FrequencyPlan(
|
|
|
|
|
|
|
+ private FrequencyPlan staticFrequencyPlan() {
|
|
|
|
|
+ return new FrequencyPlan(
|
|
|
List.of(
|
|
List.of(
|
|
|
- new OutlookExamSprintReportPayload.FrequencyPlanCard(1, "+5分", 38, false, "稳健", "①"),
|
|
|
|
|
- new OutlookExamSprintReportPayload.FrequencyPlanCard(2, "+10分", 55, false, "均衡", "②"),
|
|
|
|
|
- new OutlookExamSprintReportPayload.FrequencyPlanCard(3, "+15分", 72, true, "推荐", "③"),
|
|
|
|
|
- new OutlookExamSprintReportPayload.FrequencyPlanCard(5, "+20分", 88, false, "冲刺", "④")),
|
|
|
|
|
|
|
+ new FrequencyPlanCard(1, "+5分", 38, false, "稳健", "①"),
|
|
|
|
|
+ new FrequencyPlanCard(2, "+10分", 55, false, "均衡", "②"),
|
|
|
|
|
+ new FrequencyPlanCard(3, "+15分", 72, true, "推荐", "③"),
|
|
|
|
|
+ new FrequencyPlanCard(5, "+20分", 88, false, "冲刺", "④")),
|
|
|
"💡建议策略",
|
|
"💡建议策略",
|
|
|
"7 天提分冲刺是首选节奏,按词频优先级记忆,不浪费时间;只攻克高频/中频核心词,2周15小时速记500-800必考词,快速缩小生词缺口。",
|
|
"7 天提分冲刺是首选节奏,按词频优先级记忆,不浪费时间;只攻克高频/中频核心词,2周15小时速记500-800必考词,快速缩小生词缺口。",
|
|
|
List.of(
|
|
List.of(
|
|
|
- new OutlookExamSprintReportPayload.PhaseSuggestion("考前半个月·核心突击期", "围绕高频词建立记忆闭环。"),
|
|
|
|
|
- new OutlookExamSprintReportPayload.PhaseSuggestion("考前半小时·临阵巩固期", "结合真题词做循环巩固。")));
|
|
|
|
|
|
|
+ new PhaseSuggestion("考前半个月·核心突击期", "围绕高频词建立记忆闭环。"),
|
|
|
|
|
+ new PhaseSuggestion("考前半小时·临阵巩固期", "结合真题词做循环巩固。")));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private OutlookExamSprintReportPayload.ScoreImprovementCaseStudy staticScoreImprovementCaseStudy() {
|
|
|
|
|
- return new OutlookExamSprintReportPayload.ScoreImprovementCaseStudy(
|
|
|
|
|
|
|
+ private ScoreImprovementCaseStudy staticScoreImprovementCaseStudy() {
|
|
|
|
|
+ return new ScoreImprovementCaseStudy(
|
|
|
"真实提分 · 效果可复制",
|
|
"真实提分 · 效果可复制",
|
|
|
"王雷宇",
|
|
"王雷宇",
|
|
|
"考前半个月·核心突击期",
|
|
"考前半个月·核心突击期",
|
|
@@ -187,13 +165,87 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
19);
|
|
19);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private record OutlookReportViewModel(
|
|
|
|
|
+ SyllabusMasteryChart syllabusMasterySection,
|
|
|
|
|
+ PastPaperVocabularyChart pastPaperVocabularySection,
|
|
|
|
|
+ HighFrequencyVocabularyChart highFrequencyVocabularySection,
|
|
|
|
|
+ VocabularyFrequencyBandChart frequencyBandSection,
|
|
|
|
|
+ FrequencyPlan studyPlan,
|
|
|
|
|
+ ScoreImprovementCaseStudy caseStudy) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record SyllabusMasteryChart(
|
|
|
|
|
+ int totalWordCount,
|
|
|
|
|
+ int masteredWordCount,
|
|
|
|
|
+ int unmasteredWordCount,
|
|
|
|
|
+ int masteryPercent,
|
|
|
|
|
+ String summaryLabel,
|
|
|
|
|
+ String recommendation) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record PastPaperVocabularyChart(
|
|
|
|
|
+ int totalWordCount,
|
|
|
|
|
+ int unknownWordCountBeforeSprint,
|
|
|
|
|
+ int unknownWordCountAfterSprint,
|
|
|
|
|
+ String projectedScoreGainLabel,
|
|
|
|
|
+ String recommendation) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record HighFrequencyVocabularyChart(
|
|
|
|
|
+ int basicCorePercent,
|
|
|
|
|
+ int frequentCorePercent,
|
|
|
|
|
+ int advancedScorePercent,
|
|
|
|
|
+ String highlightLabel) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record VocabularyFrequencyBandChart(List<VocabularyFrequencyBar> bars) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record VocabularyFrequencyBar(
|
|
|
|
|
+ String bandLabel,
|
|
|
|
|
+ double currentValue,
|
|
|
|
|
+ String priorityLabel,
|
|
|
|
|
+ String themeColor) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record FrequencyPlan(
|
|
|
|
|
+ List<FrequencyPlanCard> cards,
|
|
|
|
|
+ String recommendationTitle,
|
|
|
|
|
+ String recommendationSummary,
|
|
|
|
|
+ List<PhaseSuggestion> phaseSuggestions) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record FrequencyPlanCard(
|
|
|
|
|
+ int cadencePerWeek,
|
|
|
|
|
+ String scoreGainLabel,
|
|
|
|
|
+ int winRatePercent,
|
|
|
|
|
+ boolean recommended,
|
|
|
|
|
+ String badgeLabel,
|
|
|
|
|
+ String emphasisIcon) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record PhaseSuggestion(String title, String description) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private record ScoreImprovementCaseStudy(
|
|
|
|
|
+ String headline,
|
|
|
|
|
+ String learnerName,
|
|
|
|
|
+ String studyPeriodLabel,
|
|
|
|
|
+ int memorizedWordCount,
|
|
|
|
|
+ int examHitWordCount,
|
|
|
|
|
+ double hitRatePercent,
|
|
|
|
|
+ String baselineScoreLabel,
|
|
|
|
|
+ int finalScore,
|
|
|
|
|
+ int scoreGain) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private String loadTemplate() throws IOException {
|
|
private String loadTemplate() throws IOException {
|
|
|
try (InputStream inputStream = new ClassPathResource(TEMPLATE_RESOURCE).getInputStream()) {
|
|
try (InputStream inputStream = new ClassPathResource(TEMPLATE_RESOURCE).getInputStream()) {
|
|
|
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
|
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private String renderSyllabusMasteryChart(OutlookExamSprintReportPayload.SyllabusMasteryChart chart) {
|
|
|
|
|
|
|
+ private String renderSyllabusMasteryChart(SyllabusMasteryChart chart) {
|
|
|
double masteredPercent = percentage(chart.masteredWordCount(), chart.totalWordCount());
|
|
double masteredPercent = percentage(chart.masteredWordCount(), chart.totalWordCount());
|
|
|
double unmasteredPercent = percentage(chart.unmasteredWordCount(), chart.totalWordCount());
|
|
double unmasteredPercent = percentage(chart.unmasteredWordCount(), chart.totalWordCount());
|
|
|
double endAngle = -90 + (Math.max(0d, Math.min(100d, masteredPercent)) * 3.6);
|
|
double endAngle = -90 + (Math.max(0d, Math.min(100d, masteredPercent)) * 3.6);
|
|
@@ -225,7 +277,7 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
.toString();
|
|
.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private String renderPastPaperVocabularyChart(OutlookExamSprintReportPayload.PastPaperVocabularyChart chart) {
|
|
|
|
|
|
|
+ private String renderPastPaperVocabularyChart(PastPaperVocabularyChart chart) {
|
|
|
int axisMax = roundUpToStep(Math.max(chart.totalWordCount(), chart.unknownWordCountBeforeSprint()), 250);
|
|
int axisMax = roundUpToStep(Math.max(chart.totalWordCount(), chart.unknownWordCountBeforeSprint()), 250);
|
|
|
int totalHeight = barHeight(chart.totalWordCount(), axisMax);
|
|
int totalHeight = barHeight(chart.totalWordCount(), axisMax);
|
|
|
int unknownHeight = barHeight(chart.unknownWordCountBeforeSprint(), axisMax);
|
|
int unknownHeight = barHeight(chart.unknownWordCountBeforeSprint(), axisMax);
|
|
@@ -262,7 +314,7 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
.toString();
|
|
.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private String renderHighFrequencyVocabularyChart(OutlookExamSprintReportPayload.HighFrequencyVocabularyChart chart) {
|
|
|
|
|
|
|
+ private String renderHighFrequencyVocabularyChart(HighFrequencyVocabularyChart chart) {
|
|
|
int axisMax = 100;
|
|
int axisMax = 100;
|
|
|
int basicHeight = barHeight(chart.basicCorePercent(), axisMax);
|
|
int basicHeight = barHeight(chart.basicCorePercent(), axisMax);
|
|
|
int frequentHeight = barHeight(chart.frequentCorePercent(), axisMax);
|
|
int frequentHeight = barHeight(chart.frequentCorePercent(), axisMax);
|
|
@@ -298,10 +350,10 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
.toString();
|
|
.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private String renderVocabularyFrequencyBandChart(OutlookExamSprintReportPayload.VocabularyFrequencyBandChart chart) {
|
|
|
|
|
- List<OutlookExamSprintReportPayload.VocabularyFrequencyBar> bars = chart.bars();
|
|
|
|
|
|
|
+ private String renderVocabularyFrequencyBandChart(VocabularyFrequencyBandChart chart) {
|
|
|
|
|
+ List<VocabularyFrequencyBar> bars = chart.bars();
|
|
|
double highest = bars.stream()
|
|
double highest = bars.stream()
|
|
|
- .mapToDouble(OutlookExamSprintReportPayload.VocabularyFrequencyBar::currentValue)
|
|
|
|
|
|
|
+ .mapToDouble(VocabularyFrequencyBar::currentValue)
|
|
|
.max()
|
|
.max()
|
|
|
.orElse(1d);
|
|
.orElse(1d);
|
|
|
int axisMax = roundUpToStep((int) Math.ceil(highest), 50);
|
|
int axisMax = roundUpToStep((int) Math.ceil(highest), 50);
|
|
@@ -320,7 +372,7 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
int[] xPositions = {70, 160, 250};
|
|
int[] xPositions = {70, 160, 250};
|
|
|
String[] columnClasses = {"high-band-column", "mid-band-column", "low-band-column"};
|
|
String[] columnClasses = {"high-band-column", "mid-band-column", "low-band-column"};
|
|
|
for (int index = 0; index < Math.min(3, bars.size()); index++) {
|
|
for (int index = 0; index < Math.min(3, bars.size()); index++) {
|
|
|
- OutlookExamSprintReportPayload.VocabularyFrequencyBar bar = bars.get(index);
|
|
|
|
|
|
|
+ VocabularyFrequencyBar bar = bars.get(index);
|
|
|
int height = barHeight((int) Math.round(bar.currentValue() * 10d), maxScaled);
|
|
int height = barHeight((int) Math.round(bar.currentValue() * 10d), maxScaled);
|
|
|
int x = xPositions[index];
|
|
int x = xPositions[index];
|
|
|
|
|
|
|
@@ -338,7 +390,7 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
.append("<div class='data-text'>");
|
|
.append("<div class='data-text'>");
|
|
|
|
|
|
|
|
for (int index = 0; index < Math.min(3, bars.size()); index++) {
|
|
for (int index = 0; index < Math.min(3, bars.size()); index++) {
|
|
|
- OutlookExamSprintReportPayload.VocabularyFrequencyBar bar = bars.get(index);
|
|
|
|
|
|
|
+ VocabularyFrequencyBar bar = bars.get(index);
|
|
|
if (index > 0) {
|
|
if (index > 0) {
|
|
|
builder.append("<br/>");
|
|
builder.append("<br/>");
|
|
|
}
|
|
}
|
|
@@ -355,18 +407,18 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
return builder.toString();
|
|
return builder.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private String renderStudySuggestionSection(OutlookExamSprintReportPayload.FrequencyPlan frequencyPlan) {
|
|
|
|
|
|
|
+ private String renderStudySuggestionSection(FrequencyPlan studyPlan) {
|
|
|
StringBuilder builder = new StringBuilder();
|
|
StringBuilder builder = new StringBuilder();
|
|
|
builder.append("<div class='suggest-item'>")
|
|
builder.append("<div class='suggest-item'>")
|
|
|
.append("<h4>🎯 练习学案频率与提分规划</h4>")
|
|
.append("<h4>🎯 练习学案频率与提分规划</h4>")
|
|
|
.append("<p class='text-desc'>")
|
|
.append("<p class='text-desc'>")
|
|
|
- .append(escape(frequencyPlan.recommendationSummary()))
|
|
|
|
|
|
|
+ .append(escape(studyPlan.recommendationSummary()))
|
|
|
.append("</p>")
|
|
.append("</p>")
|
|
|
.append("</div>")
|
|
.append("</div>")
|
|
|
.append("<table class='frequency-table' role='presentation'><tr class='frequency-row'>");
|
|
.append("<table class='frequency-table' role='presentation'><tr class='frequency-row'>");
|
|
|
|
|
|
|
|
- for (int index = 0; index < frequencyPlan.cards().size(); index++) {
|
|
|
|
|
- OutlookExamSprintReportPayload.FrequencyPlanCard card = frequencyPlan.cards().get(index);
|
|
|
|
|
|
|
+ for (int index = 0; index < studyPlan.cards().size(); index++) {
|
|
|
|
|
+ FrequencyPlanCard card = studyPlan.cards().get(index);
|
|
|
int columnNumber = index + 1;
|
|
int columnNumber = index + 1;
|
|
|
builder.append("<td class='frequency-cell frequency-cell-").append(columnNumber).append("'>")
|
|
builder.append("<td class='frequency-cell frequency-cell-").append(columnNumber).append("'>")
|
|
|
.append("<div class='freq-card card-").append(columnNumber);
|
|
.append("<div class='freq-card card-").append(columnNumber);
|
|
@@ -398,13 +450,13 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
|
|
|
|
|
builder.append("</tr></table>")
|
|
builder.append("</tr></table>")
|
|
|
.append("<div class='suggest-note'><strong>")
|
|
.append("<div class='suggest-note'><strong>")
|
|
|
- .append(escape(frequencyPlan.recommendationTitle()))
|
|
|
|
|
|
|
+ .append(escape(studyPlan.recommendationTitle()))
|
|
|
.append(":</strong>")
|
|
.append(":</strong>")
|
|
|
- .append(escape(frequencyPlan.recommendationSummary()))
|
|
|
|
|
|
|
+ .append(escape(studyPlan.recommendationSummary()))
|
|
|
.append("</div>")
|
|
.append("</div>")
|
|
|
.append("<div class='suggest-box'>");
|
|
.append("<div class='suggest-box'>");
|
|
|
|
|
|
|
|
- for (OutlookExamSprintReportPayload.PhaseSuggestion suggestion : frequencyPlan.phaseSuggestions()) {
|
|
|
|
|
|
|
+ for (PhaseSuggestion suggestion : studyPlan.phaseSuggestions()) {
|
|
|
builder.append("<div class='suggest-item'>")
|
|
builder.append("<div class='suggest-item'>")
|
|
|
.append("<h4>").append(escape(suggestion.title())).append("</h4>")
|
|
.append("<h4>").append(escape(suggestion.title())).append("</h4>")
|
|
|
.append("<p>").append(escape(suggestion.description())).append("</p>")
|
|
.append("<p>").append(escape(suggestion.description())).append("</p>")
|
|
@@ -415,7 +467,7 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
|
|
|
return builder.toString();
|
|
return builder.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private String renderScoreImprovementCaseStudy(OutlookExamSprintReportPayload.ScoreImprovementCaseStudy caseStudy) {
|
|
|
|
|
|
|
+ private String renderScoreImprovementCaseStudy(ScoreImprovementCaseStudy caseStudy) {
|
|
|
double progressPercent = Math.max(0d, Math.min(100d, caseStudy.hitRatePercent()));
|
|
double progressPercent = Math.max(0d, Math.min(100d, caseStudy.hitRatePercent()));
|
|
|
double endAngle = -90 + (progressPercent * 3.6);
|
|
double endAngle = -90 + (progressPercent * 3.6);
|
|
|
|
|
|