2026-04-29-outlook-payload-contract.md 10 KB

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:

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.


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:

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.


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:

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.


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:

{
  "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.


Task 5: Remove stale references and run full verification

Files:

  • Search all Java/resources/docs touched by references.

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.