For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Make the upstream vocabulary payload the only official outlook report request contract by renaming OutlookStudentVocabularyReportPayload to OutlookExamSprintReportPayload and removing legacy structured payload compatibility.
Architecture: The public contract module exposes one outlook payload record matching the upstream StudentName/StudentWordsLatest JSON shape. Application validation deserializes only that contract. The infrastructure renderer adapts that public contract into an internal view model used by existing rendering methods, preserving report output while deleting the old dual-payload branch.
Tech Stack: Java 17 records, Jackson @JsonProperty, Jakarta Bean Validation, JUnit 5, AssertJ, Maven multi-module build.
Files:
abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/OutlookExamSprintReportPayload.javaabilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/OutlookStudentVocabularyReportPayload.javaStep 1: Write the failing test/check
Run:
mvn -pl abilities/exam-sprint/contracts -DskipTests compile
Expected before implementation: compile still succeeds, but old contract remains. The implementation will make downstream references fail until updated.
Step 2: Implement the contract replacement
Replace OutlookExamSprintReportPayload contents with the fields currently in OutlookStudentVocabularyReportPayload, including nested StudentWordLatest and all @JsonProperty annotations:
public record OutlookExamSprintReportPayload(
@JsonProperty("StudentName") @NotBlank String studentName,
@JsonProperty("StudentStage") @NotNull @Min(0) Integer studentStage,
@JsonProperty("StageName") @NotBlank String stageName,
@JsonProperty("StageVocabulary") @NotNull @Min(0) Integer stageVocabulary,
@JsonProperty("StageExaminName") @NotBlank String stageExaminName,
@JsonProperty("StageImportant") @NotNull @Min(0) Integer stageImportant,
@JsonProperty("StudentWordsLatest") @NotEmpty List<@NotNull @Valid StudentWordLatest> studentWordsLatest,
@JsonProperty("MastedWordCount") @NotNull @Min(0) Integer mastedWordCount,
@JsonProperty("UnMastedWordCount") @NotNull @Min(0) Integer unMastedWordCount,
@JsonProperty("ExamineStrangeWordCount") @NotNull @Min(0) Integer examineStrangeWordCount,
@JsonProperty("TestPaperWordIdArray") @NotNull List<@NotNull @Min(0) Integer> testPaperWordIdArray,
@JsonProperty("TestPaperTitle") @NotBlank String testPaperTitle,
@JsonProperty("TestPaperUnMasterWords") @NotNull List<@NotBlank String> testPaperUnMasterWords,
@JsonProperty("TestPaperMastedWords") @NotNull List<@NotBlank String> testPaperMastedWords,
@JsonProperty("TestPaperMastedWordCount") @NotNull @Min(0) Integer testPaperMastedWordCount,
@JsonProperty("TestPaperWordCount") @NotNull @Min(0) Integer testPaperWordCount,
@JsonProperty("Complex") @NotNull Boolean complex) {
}
Delete OutlookStudentVocabularyReportPayload.java.
Step 3: Run compile to expose downstream references
Run:
mvn -pl abilities/exam-sprint/contracts,abilities/exam-sprint/application,abilities/exam-sprint/infrastructure,ability-center-runtime -am -DskipTests compile
Expected: FAIL on references to removed nested legacy view-model records and OutlookStudentVocabularyReportPayload.
Files:
abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/DefaultExamSprintReportApplicationService.javaabilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportApplicationServiceTest.javaStep 1: Update tests to use the new official payload
Replace helper construction of old structured OutlookExamSprintReportPayload with JSON/ObjectNode matching upstream fields. Ensure tests still assert createOutlookReport stores UnmodeledReportContent with StudentName and StageVocabulary fields.
Step 2: Implement validation simplification
Remove import and usage of OutlookStudentVocabularyReportPayload. Replace validateOutlookPayload with a single read/validate path:
private void validateOutlookPayload(JsonNode payload) {
OutlookExamSprintReportPayload reportPayload = readPayload(payload, OutlookExamSprintReportPayload.class);
validatePayload(reportPayload);
}
Delete isStudentVocabularyOutlookPayload from the application service.
Step 3: Run application tests
Run:
mvn -pl abilities/exam-sprint/application -am test
Expected: PASS after test/helper updates.
Files:
abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.javaabilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.javaabilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGeneratorTest.javaStep 1: Update renderer tests first
Delete/replace legacy structured-path tests:
renderKeepsStructuredPayloadPathWhenOnlyStageVocabularyFieldIsAdded.renderSupportsLegacyHighScorePercentForStructuredOutlookPayload.StudentName, StageName, StageExaminName, TestPaperUnMasterWords if rendered, or remove assertions for old-only fields that are no longer rendered.Step 2: Implement internal view model
Inside ClasspathOutlookExamSprintReportRenderer, introduce private nested records such as:
private record OutlookReportViewModel(
SyllabusMasteryChart syllabusMasteryChart,
PastPaperVocabularyChart pastPaperVocabularyChart,
HighFrequencyVocabularyChart highFrequencyVocabularyChart,
VocabularyFrequencyBandChart vocabularyFrequencyBandChart,
FrequencyPlan frequencyPlan,
ScoreImprovementCaseStudy scoreImprovementCaseStudy) {}
Move the old renderer-only records into private nested records. Keep existing rendering methods, but change their parameter types from OutlookExamSprintReportPayload.* to the private view-model record types.
Step 3: Replace dual-payload render branch
Deserialize only:
OutlookExamSprintReportPayload payload = objectMapper.treeToValue(json, OutlookExamSprintReportPayload.class);
OutlookReportViewModel reportPayload = adaptPayload(payload);
Rename adaptStudentVocabularyPayload to adaptPayload and make it return OutlookReportViewModel.
Step 4: Run renderer/PDF tests
Run:
mvn -pl abilities/exam-sprint/infrastructure -am test
Expected: PASS.
Files:
ability-center-runtime/src/test/resources/requests/exam-sprint-outlook-report-request.jsonability-center-runtime/scripts/outlook-report-demo.shability-center-runtime/src/test/java/cn/yunzhixue/ability/center/examsprint/adapter/http/ExamSprintReportControllerWebMvcTest.javaability-center-runtime/src/test/java/cn/yunzhixue/ability/center/examsprint/adapter/http/ExamSprintReportControllerTest.javaStep 1: Update request fixture
Change payload to the upstream vocabulary shape with fields:
{
"StudentName": "20260318测试",
"StudentStage": 2,
"StageName": "初中",
"StageVocabulary": 10,
"StageExaminName": "中考",
"StageImportant": 3,
"StudentWordsLatest": [...],
"MastedWordCount": 4,
"UnMastedWordCount": 6,
"ExamineStrangeWordCount": 3,
"TestPaperWordIdArray": [1, 2, 3, 4, 5],
"TestPaperTitle": "文章2.jpg",
"TestPaperUnMasterWords": ["lot", "father", "catch"],
"TestPaperMastedWords": ["a", "the"],
"TestPaperMastedWordCount": 2,
"TestPaperWordCount": 5,
"Complex": false
}
Step 2: Update controller assertions
Change assertions from reportMetadata.learnerName and targetExamName to StudentName and StageExaminName.
Step 3: Update demo script
Make outlook-report-demo.sh send the same official payload shape directly to /api/exam-sprint/outlook-reports.
Step 4: Run runtime tests
Run:
mvn -pl ability-center-runtime -am test
Expected: PASS.
Files:
Step 1: Check stale references
Run:
rg "OutlookStudentVocabularyReportPayload|reportMetadata|highScorePercent|vocabularyFrequencyBandChart" abilities ability-center-runtime
Expected: no OutlookStudentVocabularyReportPayload references. Remaining old structured payload field names should only exist in unrelated docs or intentionally retained generated output tests; remove/update runtime fixtures and active tests.
Step 2: Run relevant full test suite
Run:
mvn -pl abilities/exam-sprint/contracts,abilities/exam-sprint/application,abilities/exam-sprint/infrastructure,ability-center-runtime -am test
Expected: BUILD SUCCESS.
Step 3: Inspect git diff
Run:
git diff --stat
git diff -- abilities/exam-sprint/contracts abilities/exam-sprint/application abilities/exam-sprint/infrastructure ability-center-runtime
Expected: contract rename, app validation simplification, renderer single-payload adaptation, updated fixtures/tests/scripts only.