# Outlook Payload Contract Implementation Plan > **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. --- ### Task 1: Replace the outlook contract type **Files:** - Modify: `abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/OutlookExamSprintReportPayload.java` - Delete: `abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/OutlookStudentVocabularyReportPayload.java` **Step 1: Write the failing test/check** Run: ```bash 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: ```java 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: ```bash 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`. --- ### Task 2: Simplify application-layer validation **Files:** - Modify: `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/DefaultExamSprintReportApplicationService.java` - Modify tests: `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportApplicationServiceTest.java` **Step 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: ```java 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: ```bash mvn -pl abilities/exam-sprint/application -am test ``` Expected: PASS after test/helper updates. --- ### Task 3: Convert renderer to one public payload plus internal view model **Files:** - Modify: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.java` - Modify tests: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.java` - Modify tests: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGeneratorTest.java` **Step 1: Update renderer tests first** Delete/replace legacy structured-path tests: - Remove `renderKeepsStructuredPayloadPathWhenOnlyStageVocabularyFieldIsAdded`. - Remove `renderSupportsLegacyHighScorePercentForStructuredOutlookPayload`. - Keep and strengthen the upstream vocabulary payload assertions. - Update escaping tests to use fields now reachable from the official payload, e.g. `StudentName`, `StageName`, `StageExaminName`, `TestPaperUnMasterWords` if rendered, or remove assertions for old-only fields that are no longer rendered. - Update invalid theme color tests if theme color is no longer externally supplied; delete if not applicable. **Step 2: Implement internal view model** Inside `ClasspathOutlookExamSprintReportRenderer`, introduce private nested records such as: ```java 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: ```java 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: ```bash mvn -pl abilities/exam-sprint/infrastructure -am test ``` Expected: PASS. --- ### Task 4: Update runtime fixtures and demo script **Files:** - Modify: `ability-center-runtime/src/test/resources/requests/exam-sprint-outlook-report-request.json` - Modify: `ability-center-runtime/scripts/outlook-report-demo.sh` - Modify tests if needed: `ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/examsprint/adapter/http/ExamSprintReportControllerWebMvcTest.java` - Modify tests if needed: `ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/examsprint/adapter/http/ExamSprintReportControllerTest.java` **Step 1: Update request fixture** Change `payload` to the upstream vocabulary shape with fields: ```json { "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: ```bash mvn -pl ability-center-runtime -am test ``` Expected: PASS. --- ### Task 5: Remove stale references and run full verification **Files:** - Search all Java/resources/docs touched by references. **Step 1: Check stale references** Run: ```bash 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: ```bash 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: ```bash 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.