Exercise AI — Domain Model & State
Mô tả entity, DTO đa hình, và state machine của runtime do-test. Nguồn: study-hub
features/exercise_ai/dotest.
Progress (aggregate root)
AiUserExerciseProgress extends BaseDocument (id, audit fields, version optimistic lock). Một document =
một phiên làm bài.
| Field | Kiểu | Ý nghĩa |
|---|---|---|
userResourceId | String | Khóa tài nguyên bài trong khóa học (LMS). |
exerciseId | String | Id bài Exercise AI. |
versionKey | DocumentVersionKey | Bản published của bài tại thời điểm bắt đầu. |
exerciseType | AiExerciseType | Dạng bài — discriminator cho content / questions. |
teacherLanguageType | TeacherLanguageType | VIETNAMESE / ENGLISH. |
startMode | UserExerciseStartMode | REAL_TEST (mặc định) / demo. |
status | UserExerciseStatus | IN_PROGRESS → COMPLETED. |
currentIndex | int | Con trỏ câu hiện tại. |
totalQuestion | int | Tổng số câu. |
completedAt | ZonedDateTime | Set một lần khi nộp bài (COMPLETED); null khi đang làm. |
content | BaseAiExerciseProgressContent | Payload đa hình theo exerciseType. |
getQuestions() là accessor @Transient tiện ích trên progress, ủy quyền về content.getQuestions().
Content (đa hình theo dạng bài)
BaseAiExerciseProgressContent (abstract) → getQuestions(): List<? extends BaseAIExerciseQuestionProgress>.
| Subtype | Dạng bài | Field riêng |
|---|---|---|
LinearProgressContent | LINEAR_TOOL | questions, tổng kết submit: totalAnswered, totalSkipped, totalNotAnswer, averageCorrectPercentage. |
TurnBasedProgressContent | SPEAK_A_SENTENCE, IMPROVE_A_SENTENCE | questions. |
GuidedProgressContent | GUIDED_PRACTICE | questions (TBD). |
Các field tổng kết của LinearProgressContent được tính lúc submit qua content mapper factory
(computeSubmitSummary) — xem Linear Tool.
Question (đa hình)
BaseAIExerciseQuestionProgress (abstract): questionId, order, state (QuestionState).
| Subtype | @TypeAlias | Field riêng |
|---|---|---|
LinearQuestionProgress | linearQuestionProgress | snapshots (List<LinearCanvasSnapshot>), currentSnapshotId. Scoring nằm trong từng snapshot (không còn trên question). |
TurnBasedQuestionProgress | turnBasedQuestionProgress | attemptsLeft, turns (List<AnswerTurn>). |
GuidedQuestionProgress | guidedQuestionProgress | — (TBD). |
Snapshot model (LINEAR_TOOL)
LinearQuestionProgress giữ nhiều version canvas qua snapshots + con trỏ currentSnapshotId (đã bỏ mô hình canvas/LinearCanvas đơn). Mỗi LinearCanvasSnapshot: id, ideaCards (List<LinearCard> — đổi tên từ cards), rootCardId, aggregates (total*, fullText), conceptualizedCards (List<LinearCard>), fullConceptualizedText, context, scoring per-snapshot (correctPercentage, speechCorrections, verdict), status (DocumentStatus: EDITING→PUBLISHED), originalSnapshotId, linearMode (LinearMode: IDEA_IMPLEMENTATION mặc định / SPEAKING_PRACTICE — working mode của canvas), createdAt/lastModifiedAt. LinearCard có thêm conceptualizedText và enhance (CardEnhance, @Transient — verdict coaching LIVE, không persist). Bảng field đầy đủ + luật DAG: xem Linear Tool.
DTO response đa hình (song ánh với entity)
BaseQuestionProgressDTO (abstract, chỉ questionId / order / state) → LinearQuestionProgressDTO /
TurnBasedQuestionProgressDTO / GuidedQuestionProgressDTO. Mỗi question mapper trả về đúng subtype DTO
của nó; factory dispatch theo entity. Type của câu không lặp lại trên question DTO — đã biết từ
exerciseType ở mức content (discriminator một tầng phía trên). Đây là response-only nên không cần
@JsonTypeInfo.
LinearQuestionProgressDTO thêm 2 field @Transient read-only: totalDoCoachingMenu, totalDoCoachingLive — số lần coaching (đọc từ base_tracking_seq qua CoachingCountReader, không lưu trên progress).
AnswerTurn (turn-based): turnId, clientTurnId (idempotency), answerText, status (TurnStatus),
verdict, feedback, matchedStructure, transitionLine, markedBy, sendCount, sentMarking,
createdAt, markedAt.
Enums & State machine
UserExerciseStatus
IN_PROGRESS ──submit──▶ COMPLETED (terminal, immutable)Mọi mutation bị từ chối khi đã COMPLETED (ensureProgressEditable → PROGRESS_ALREADY_COMPLETED).
QuestionState
NOT_ANSWER ──▶ ANSWERING ──▶ ANSWERED
└──────────────▶ SKIPPEDLINEAR_TOOL: nói cả câu (/speech-correctionkhông kèmcardId) →ANSWERED.SKIPPED: bỏ qua câu.
SpeechVerdict
correctPercentage >= 70 → GOOD, ngược lại NEED_IMPROVE. (Không còn ACCEPTABLE.)
TurnStatus
PROCESSING (vừa gửi chấm) → DONE (callback đã cập nhật kết quả); FAILED (chấm lỗi không phục hồi).
MarkedBy (provenance chấm): DETERMINISTIC / AI / FALLBACK. SpeakAnswerVerdict: CORRECT,
NOT_FOLLOW_STRUCTURE, WRONG_FILL, OFF_TOPIC, WRONG_LANGUAGE, TOO_SHORT. TurnAction (điều hướng FE):
RETRY / NEXT / DONE.
LinearCardType / LinearCardState
- Type:
QUESTION,ANSWER,DESCRIPTION,CAUSE,EFFECT. - State:
EMPTY,FILLED,ERROR.
Coaching (LINEAR_TOOL)
CoachingMode:MENU(gợi ý card mới) /LIVE(chấm lại toàn card + triển khai bài nói).CoachBandVariant:BAND6(target band, hardcode).CoachingSituationType:KEEP,DIGRESS,TOO_SHORT,VAGUE,SHOULD_BE_CAUSE,SHOULD_BE_DESCRIPTION,EFFECT_NO_RESULT.CoachingVariant(verdict tổng của LIVE, backend suy từ per-card):GOOD(mọi card KEEP → persist + PUBLISHED) /NEED_IMPROVE(không persist). KhácSpeechVerdict.DocumentStatus(snapshot):EDITING→PUBLISHED.- Chi tiết flow coaching: xem Do-Test API — Linear Tool.
AiExerciseDoTestErrorCode
PROGRESS_NOT_FOUND (EAID001), QUESTION_NOT_FOUND (002), QUESTION_NOT_ANSWERABLE (003),
NO_ATTEMPTS_LEFT (004), TURN_NOT_FOUND (005), MAX_TEST_ATTEMPTS_REACHED (006), CARD_NOT_FOUND (007),
PROGRESS_ALREADY_COMPLETED (008), SELECTED_CARD_REQUIRED (009), COACHING_FAILED (010),
SNAPSHOT_NOT_FOUND (011), SNAPSHOT_DELETE_NOT_ALLOWED (012), COACHING_GOOD_LIMIT_REACHED (013).