# API Data Gaps

> So sánh data API trả về vs data các screen cần hiển thị — Cập nhật: 2026-05-26

---

## Tổng quan

| Mức độ | Số lượng |
|--------|----------|
| Đủ data | 5 flow |
| Thiếu field | 3 |
| Data absent (UX) | 1 |

---

## Đủ data (Không cần thay đổi)

| Flow | API | Ghi chú |
|------|-----|---------|
| FreeTalk conversation | `POST /conversations`, `POST /conversations/{id}/messages` (SSE) | Trả đủ: content, ttsAudioUrl, grammarCorrection, suggestedResponse, newVocab |
| Scenario list | `GET /cms/lessons?page=1&pageSize=100` | Trả đủ: documentId, title, level, cardPreview.mediaUrl |
| Lesson detail | `GET /cms/lessons/{documentId}` | Trả đủ: title, content.dialogues, targetWords |
| Word error detection | `POST /learning/word-error` | Trả đủ: wrongWords, totalWords, errorCount |
| Lesson plan generation | `POST /learning/lesson-plan` | Trả đủ: title, level, content.dialogues, targetWords |

---

## Thiếu data (Cần server bổ sung)

### GAP-1: `grammarCorrection` thiếu `explanation`

- **API endpoint:** `POST /conversations/{id}/messages` — SSE event `stream_end`
- **API trả:** `{ "wrong": "...", "correct": "..." }`
- **Screen cần:** `{ "wrong": "...", "correct": "...", "explanation": "..." }`
- **Ảnh hưởng:** `AISpeakingResultScreen` — mục Grammar Corrections hiển thị `explanation` luôn trống
- **Code liên quan:** `AISpeakingViewModel.kt:414` hardcode `explanation = ""`
- **Đề xuất server:** Thêm field `explanation` vào `grammarCorrection` trong SSE `stream_end`:
  ```json
  {
    "grammarCorrection": {
      "wrong": "I wants to order",
      "correct": "I want to order",
      "explanation": "'want' không chia 's' với chủ ngữ 'I'"
    }
  }
  ```

---

### GAP-2: `newVocab` thiếu `definition`, `meanings`, `examples`, `relatedWords`

- **API endpoint:** `POST /conversations/{id}/messages` — SSE event `stream_end`
- **API trả:** `{ "word": "iced", "pos": "adj", "ipa": "/aɪst/" }`
- **Screen cần:** `word`, `pos`, `ipa` + `definition`, `meanings`, `examples`, `relatedWords`
- **Ảnh hưởng:**
  - `VocabScreen` — từ vựng từ FreeTalk chỉ có tên + phiên âm, không có nghĩa
  - `FlashcardScreen` / `FlashcardDetailScreen` — flashcard thiếu nội dung học
- **Code liên quan:** `VocabRepositoryImpl.saveVocabFromFreeTalk()` — save với `definition = ""`
- **Đề xuất server:** Mở rộng `newVocab` trong SSE `stream_end`:
  ```json
  {
    "newVocab": [
      {
        "word": "iced",
        "pos": "adj",
        "ipa": "/aɪst/",
        "definition": "made very cold",
        "meanings": ["làm lạnh", "ướp đá"],
        "examples": ["I'd like an iced coffee.", "Iced tea is refreshing."],
        "relatedWords": ["frozen", "chilled"]
      }
    ]
  }
  ```

---

### GAP-3: CMS `targetWords` chỉ là comma-separated string

- **API endpoint:** `GET /cms/lessons/{documentId}`, `POST /learning/lesson-plan`
- **API trả:** `"targetWords": "coffee, iced, medium, sugar, pay"`
- **Screen cần:** Mỗi từ cần `word`, `pronunciation`, `type`, `definition`, `meanings`, `examples`, `relatedWords`
- **Ảnh hưởng:**
  - `RoleplayResultScreen` — vocab words chỉ hiển thị tên từ, các field còn lại trống
  - `VocabScreen` — từ vựng từ Roleplay thiếu tất cả metadata
- **Code liên quan:** `RoleplayApiServiceImpl.parseTargetWords()` — chỉ parse ra `word`, còn lại empty
- **Đề xuất server:** Đổi `targetWords` từ string sang array of objects:
  ```json
  {
    "targetWords": [
      {
        "word": "coffee",
        "pos": "noun",
        "ipa": "/ˈkɒfi/",
        "definition": "a hot drink made from coffee beans"
      }
    ]
  }
  ```
  Hoặc giữ nguyên `targetWords` string và thêm API riêng: `GET /learning/vocab/enrich?words=coffee,iced,medium`

---

## Data absent (UX impact)

### GAP-4: `cardPreview` có thể absent trên nhiều lessons

- **API endpoint:** `GET /cms/lessons?page=1&pageSize=100`
- **Vấn đề:** API doc ghi rõ *"cardPreview is only present on lessons that have a thumbnail configured in CMS — it may be absent on many items"*
- **Ảnh hưởng:** `AIRoleplayScreen` — nhiều scenario hiển thị cùng 1 ảnh mặc định `img_scenario_cover_1`
- **Hiện tại:** Đã xử lý fallback trong `ScenarioCard` với `AsyncImage` error/placeholder
- **Đề xuất:** CMS team upload thumbnail cho tất cả lessons, hoặc server tự generate thumbnail

---

## Tổng kết ưu tiên

| Ưu tiên | GAP | Lý do |
|---------|-----|-------|
| Cao | GAP-2: `newVocab` thiếu metadata | Ảnh hưởng trực tiếp VocabScreen + FlashcardScreen — core feature của app |
| Cao | GAP-3: `targetWords` chỉ là string | Ảnh hưởng RoleplayResult + VocabScreen — từ vựng là feature chính |
| Trung bình | GAP-1: `grammarCorrection` thiếu `explanation` | AISpeakingResult hiện được nhưng thiếu context giải thích |
| Thấp | GAP-4: `cardPreview` absent | Đã có fallback, chỉ ảnh hưởng UX |
