# Heat User - Flow & Timeline (Backend Focus)

## 📋 Tổng quan

Document này mô tả **timeline và flow** của hệ thống tracking thời gian học, tập trung vào **phía server**.

---

## 🎯 CASE 1: START → SUBMIT (Basic Flow)

### **Timeline:**

```
┌──────────────────────────────────────────────────────────────────┐
│  User click "BẮT ĐẦU LÀM BÀI"                                    │
└──────────────────────────────────────────────────────────────────┘

[10:00:00] Step 1: FE → EMS
           POST /fe/ems/v1/lmsnew1/mockcontest/session/start
           Request: { id_bai_kiem_tra: 456 }
           Response: { 
             idHistoryContest: 100,
             contest_type: 29 
           }

[10:00:01] Step 2: FE → EMS
           POST /fe/ems/v1/exam/start
           Request: { idHistoryContest: 100 }
           Response: { 
             idHistory: 200,
             questions: [...] 
           }

[10:00:02] Step 3: FE → Heat User (Backend xử lý)
           ┌────────────────────────────────────────────────┐
           │ POST /api/heat-user/study-session/start       │
           │ Request: {                                     │
           │   id_history_contest: 100,                     │
           │   id_history: 200,                             │
           │   id_bai_kiem_tra: 456                         │
           │ }                                              │
           │                                                │
           │ ✅ Backend Processing:                         │
           │ 1. Check existing session (active/auto_ended) │
           │ 2. Create new StudentStudySession:            │
           │    - id_history_contest: 100                   │
           │    - id_history: 200                           │
           │    - status: 'active'                          │
           │    - session_start_at: 10:00:02               │
           │    - last_heartbeat_at: 10:00:02              │
           │ 3. Insert to DB                                │
           │                                                │
           │ Response: {                                    │
           │   success: true,                               │
           │   session_id: 1                                │
           │ }                                              │
           └────────────────────────────────────────────────┘

[10:05:00] FE → Heat User (Heartbeat #1)
           ┌────────────────────────────────────────────────┐
           │ POST /api/heat-user/study-session/heartbeat   │
           │ Request: { session_id: 1 }                     │
           │                                                │
           │ ✅ Backend Processing:                         │
           │ UPDATE student_study_sessions                  │
           │ SET last_heartbeat_at = '10:05:00'            │
           │ WHERE id = 1                                   │
           └────────────────────────────────────────────────┘

[10:10:00] Heartbeat #2 → Update last_heartbeat_at = 10:10:00
[10:15:00] Heartbeat #3 → Update last_heartbeat_at = 10:15:00
[10:20:00] Heartbeat #4 → Update last_heartbeat_at = 10:20:00

[10:25:00] User click "NỘP BÀI"

[10:25:01] Step 1: FE → Heat User
           ┌────────────────────────────────────────────────┐
           │ POST /api/heat-user/study-session/submit      │
           │ Request: { session_id: 1 }                     │
           │                                                │
           │ ✅ Backend Processing:                         │
           │ 1. Find session id = 1                         │
           │ 2. Calculate duration:                         │
           │    - start: 10:00:02                           │
           │    - end: 10:25:01 (now)                       │
           │    - duration: 1499 seconds (24.98 min)        │
           │    - adjusted: 1499 seconds (>= 15 min)        │
           │ 3. UPDATE student_study_sessions:              │
           │    - session_end_at: 10:25:01                  │
           │    - status: 'completed'                       │
           │    - duration_seconds: 1499                    │
           │    - adjusted_duration_seconds: 1499           │
           │ 4. INSERT/UPDATE student_daily_study_time:     │
           │    - study_date: 2025-12-04                    │
           │    - total_minutes: total_minutes + 24.98      │
           │    - session_count: session_count + 1          │
           │                                                │
           │ Response: {                                    │
           │   success: true,                               │
           │   duration_minutes: 24.98                      │
           │ }                                              │
           └────────────────────────────────────────────────┘

[10:25:02] Step 2: FE → EMS
           POST /fe/ems/v1/exam/submit
           Request: { idHistory: 200, answers: [...] }
           Response: { score: 85 }

═══════════════════════════════════════════════════════════════════
📊 DATABASE STATE (after submit):

student_study_sessions:
┌────┬─────────┬──────────────┬────────────┬───────────┬──────────┬──────────────┐
│ id │ student │ id_history_  │ id_history │ status    │ duration │ adjusted_    │
│    │         │ contest      │            │           │ _seconds │ duration_s   │
├────┼─────────┼──────────────┼────────────┼───────────┼──────────┼──────────────┤
│ 1  │ 123     │ 100          │ 200        │ completed │ 1499     │ 1499         │
└────┴─────────┴──────────────┴────────────┴───────────┴──────────┴──────────────┘

student_daily_study_time:
┌────┬─────────┬────────────┬───────────────┬───────────────┐
│ id │ student │ study_date │ total_minutes │ session_count │
├────┼─────────┼────────────┼───────────────┼───────────────┤
│ 1  │ 123     │ 2025-12-04 │ 24.98         │ 1             │
└────┴─────────┴────────────┴───────────────┴───────────────┘
```

---

## 🔄 CASE 2: CLOSE Browser → CONTINUE

### **Timeline:**

```
┌──────────────────────────────────────────────────────────────────┐
│  NGÀY 1: 2025-12-04                                              │
└──────────────────────────────────────────────────────────────────┘

[10:00:00] Start session (như Case 1)
           → session_id: 1, status: active

[10:05:00] Heartbeat → last_heartbeat_at: 10:05:00
[10:10:00] Heartbeat → last_heartbeat_at: 10:10:00

[10:15:00] ❌ User đóng browser (KHÔNG có API call)
           → Session vẫn status = 'active' trên DB

─── Cron Job Auto-End ───

[10:20:00] ⚙️ Scheduler chạy: php artisan sessions:auto-end
           ┌────────────────────────────────────────────────┐
           │ ✅ Backend Processing (Cron Job):              │
           │                                                │
           │ 1. Query inactive sessions:                    │
           │    SELECT * FROM student_study_sessions        │
           │    WHERE status = 'active'                     │
           │    AND last_heartbeat_at < NOW() - 10 minutes  │
           │                                                │
           │    Result: session_id = 1                      │
           │    - last_heartbeat_at: 10:10:00               │
           │    - now: 10:20:00                             │
           │    - diff: 10 minutes → TIMEOUT!               │
           │                                                │
           │ 2. End session:                                │
           │    - start: 10:00:00                           │
           │    - end: 10:10:00 (last_heartbeat_at)         │
           │    - duration: 600 seconds (10 min)            │
           │    - adjusted: 900 seconds (15 min min rule)   │
           │                                                │
           │ 3. UPDATE student_study_sessions:              │
           │    - session_end_at: 10:10:00                  │
           │    - status: 'auto_ended'                      │
           │    - duration_seconds: 600                     │
           │    - adjusted_duration_seconds: 900            │
           │                                                │
           │ 4. UPDATE student_daily_study_time:            │
           │    - study_date: 2025-12-04                    │
           │    - total_minutes: +15                        │
           │    - session_count: +1                         │
           └────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│  NGÀY 2: 2025-12-05 (User quay lại)                             │
└──────────────────────────────────────────────────────────────────┘

[14:00:00] FE check: có session_id: 1 chưa hoàn thành

[14:00:01] FE → Heat User (Continue)
           ┌────────────────────────────────────────────────┐
           │ POST /api/heat-user/study-session/continue    │
           │ Request: { session_id: 1 }                     │
           │                                                │
           │ ✅ Backend Processing:                         │
           │ 1. Find session id = 1                         │
           │    - status: 'auto_ended' ✅                   │
           │    - Đã được end rồi, không cần end lại       │
           │                                                │
           │ 2. Create NEW session:                         │
           │    - id: 2                                     │
           │    - id_history_contest: 100 (SAME)            │
           │    - id_history: 200 (SAME)                    │
           │    - status: 'active'                          │
           │    - session_start_at: 14:00:01               │
           │                                                │
           │ Response: {                                    │
           │   success: true,                               │
           │   session_id: 2,                               │
           │   old_session_duration: 15                     │
           │ }                                              │
           └────────────────────────────────────────────────┘

[14:00:02] FE → EMS
           POST /fe/ems/v1/exam/start
           Request: { 
             idHistoryContest: 100,
             idHistory: 200,  ← SAME (không đổi)
             is_continue: true 
           }
           Response: { questions, saved_answers }

[14:05:00] Heartbeat session 2
[14:10:00] Heartbeat session 2

[14:15:00] Submit session 2
           → duration: 15 phút

═══════════════════════════════════════════════════════════════════
📊 DATABASE STATE:

student_study_sessions:
┌────┬────────────┬───────────┬─────────────┬──────────────┬──────────┐
│ id │ id_history │ status    │ start_at    │ end_at       │ adjusted │
├────┼────────────┼───────────┼─────────────┼──────────────┼──────────┤
│ 1  │ 200        │ auto_ended│ 04 10:00:00 │ 04 10:10:00  │ 900s     │
│ 2  │ 200        │ completed │ 05 14:00:01 │ 05 14:15:00  │ 899s     │
└────┴────────────┴───────────┴─────────────┴──────────────┴──────────┘

student_daily_study_time:
┌────┬────────────┬───────────────┐
│ id │ study_date │ total_minutes │
├────┼────────────┼───────────────┤
│ 1  │ 2025-12-04 │ 15            │
│ 2  │ 2025-12-05 │ 15            │
└────┴────────────┴───────────────┘
```

---

## 🛑 CASE 3: STOP - Dừng hẳn

### **Timeline:**

```
[10:00:00] Start session → session_id: 1

[10:05:00] Heartbeat
[10:10:00] Heartbeat

[10:15:00] User click "DỪNG LÀM BÀI"

[10:15:01] FE → Heat User
           ┌────────────────────────────────────────────────┐
           │ POST /api/heat-user/study-session/stop        │
           │ Request: { session_id: 1 }                     │
           │                                                │
           │ ✅ Backend Processing:                         │
           │ 1. Find session id = 1                         │
           │ 2. End session:                                │
           │    - end_at: NOW (10:15:01)                    │
           │    - duration: 15 min → adjusted: 15 min       │
           │    - status: 'stopped' ⚠️                      │
           │ 3. Update daily stats: +15 min                 │
           │                                                │
           │ Response: {                                    │
           │   success: true,                               │
           │   duration_minutes: 15                         │
           │ }                                              │
           └────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│  SAU ĐÓ: User muốn làm lại                                       │
└──────────────────────────────────────────────────────────────────┘

[15:00:00] FE gọi EMS Step 1 → idHistoryContest: 101 (có thể mới)
[15:00:01] FE gọi EMS Step 2 → idHistory: 300 (MỚI - EMS tạo mới)

[15:00:02] FE → Heat User
           ┌────────────────────────────────────────────────┐
           │ POST /api/heat-user/study-session/start       │
           │ Request: {                                     │
           │   id_history: 300  ← MỚI                       │
           │ }                                              │
           │                                                │
           │ ✅ Backend Processing:                         │
           │ 1. Check existing:                             │
           │    - Query: idHistory = 300, status IN         │
           │      ('active', 'auto_ended')                  │
           │    - Result: KHÔNG TÌM THẤY                    │
           │    - (Session cũ có status = 'stopped' →       │
           │      không được check)                         │
           │                                                │
           │ 2. ✅ Cho phép tạo session mới                 │
           │                                                │
           │ Response: { session_id: 2 }                    │
           └────────────────────────────────────────────────┘

═══════════════════════════════════════════════════════════════════
📊 KEY DIFFERENCE:

CLOSE → CONTINUE:
- idHistory: 200 → 200 (SAME)
- Check existing: TÌM THẤY session cũ
- Action: Continue session cũ

STOP → START:
- idHistory: 200 → 300 (NEW)
- Check existing: KHÔNG TÌM THẤY
- Action: Create new session
```

---

## 🌙 CASE 4: CROSS-DAY (23:59 → 01:00)

### **Timeline:**

```
[2025-12-04 23:59:00] Start session
                      → session_id: 1

[2025-12-05 00:04:00] Heartbeat
[2025-12-05 00:09:00] Heartbeat

[2025-12-05 01:00:00] Submit

┌────────────────────────────────────────────────┐
│ ✅ Backend Processing:                         │
│                                                │
│ 1. Calculate duration:                         │
│    - start: 2025-12-04 23:59:00                │
│    - end: 2025-12-05 01:00:00                  │
│    - total: 61 phút                            │
│                                                │
│ 2. Distribute across days:                     │
│                                                │
│    Day 1 (2025-12-04):                         │
│    - time span: 23:59:00 → 24:00:00            │
│    - actual seconds: 60s                       │
│    - proportion: 60/3660 = 0.0164              │
│    - adjusted time: 61 * 0.0164 = 1 phút       │
│                                                │
│    Day 2 (2025-12-05):                         │
│    - time span: 00:00:00 → 01:00:00            │
│    - actual seconds: 3600s                     │
│    - proportion: 3600/3660 = 0.9836            │
│    - adjusted time: 61 * 0.9836 = 60 phút      │
│                                                │
│ 3. UPDATE student_daily_study_time:            │
│    INSERT (2025-12-04, 1 phút)                 │
│    INSERT (2025-12-05, 60 phút)                │
└────────────────────────────────────────────────┘

═══════════════════════════════════════════════════════════════════
📊 DATABASE STATE:

student_daily_study_time:
┌────┬────────────┬───────────────┐
│ id │ study_date │ total_minutes │
├────┼────────────┼───────────────┤
│ 1  │ 2025-12-04 │ 1.00          │
│ 2  │ 2025-12-05 │ 60.00         │
└────┴────────────┴───────────────┘
```

---

## 🔁 CASE 5: MULTIPLE SUBMISSIONS (contest_type ≠ 29)

### **Contest Type Logic:**

```
EMS Response: { 
  idHistoryContest: 100,
  contest_type: 29 or ≠ 29 
}

contest_type = 29:  ✅ Single submission (Cases 1-4)
contest_type ≠ 29:  ✅ Multiple submissions (Case 5)
```

### **Timeline:**

```
┌──────────────────────────────────────────────────────────────────┐
│  VÍ DỤ: Bài thi IELTS có 4 phần (Reading, Listening, Writing,   │
│         Speaking)                                                │
│  idHistoryContest: 100, contest_type: 30                         │
└──────────────────────────────────────────────────────────────────┘

═══ PART 1: READING ═══

[09:00:00] FE → EMS contest/start
           Response: { idHistoryContest: 100, contest_type: 30 }

[09:00:01] FE → EMS exam/start
           Request: { idHistoryContest: 100, part: 1 }
           Response: { idHistory: 201, part_name: "Reading" }

[09:00:02] FE → Heat User start
           ┌────────────────────────────────────────────────┐
           │ Request: {                                     │
           │   id_history_contest: 100,                     │
           │   id_history: 201  ← Part 1                    │
           │ }                                              │
           │                                                │
           │ ✅ Backend: Create session 1                   │
           │ - id_history_contest: 100                      │
           │ - id_history: 201                              │
           │ - status: active                               │
           └────────────────────────────────────────────────┘

[09:20:00] Submit Part 1
           ┌────────────────────────────────────────────────┐
           │ Heat User: /submit (session 1)                 │
           │ ✅ Backend: End session 1                      │
           │ - duration: 20 phút                            │
           │ - status: completed                            │
           │ - daily stats: +20 phút                        │
           └────────────────────────────────────────────────┘

═══ PART 2: LISTENING ═══

[09:21:00] FE → EMS exam/start
           Request: { 
             idHistoryContest: 100,  ← SAME
             part: 2 
           }
           Response: { idHistory: 202, part_name: "Listening" }

[09:21:01] FE → Heat User start
           ┌────────────────────────────────────────────────┐
           │ Request: {                                     │
           │   id_history_contest: 100,  ← SAME             │
           │   id_history: 202  ← Part 2 (NEW)              │
           │ }                                              │
           │                                                │
           │ ✅ Backend: Create session 2                   │
           │ - id_history_contest: 100 (SAME)               │
           │ - id_history: 202 (NEW)                        │
           │ - status: active                               │
           │                                                │
           │ ⚠️ Check existing:                             │
           │    WHERE id_history = 202  ← Tìm theo 202     │
           │    Không tìm thấy → OK tạo mới                 │
           └────────────────────────────────────────────────┘

[09:35:00] Submit Part 2 → session 2, +14 phút

═══ PART 3: WRITING ═══

[09:36:00] Start → idHistory: 203, session 3
[09:54:00] Submit → +18 phút

═══ PART 4: SPEAKING ═══

[10:00:00] Start → idHistory: 204, session 4
[10:22:00] Submit → +22 phút

═══════════════════════════════════════════════════════════════════
📊 DATABASE STATE (sau khi hoàn thành tất cả):

student_study_sessions:
┌────┬──────────────┬────────────┬───────────┬──────────┐
│ id │ id_history_  │ id_history │ status    │ adjusted │
│    │ contest      │            │           │ duration │
├────┼──────────────┼────────────┼───────────┼──────────┤
│ 1  │ 100          │ 201        │ completed │ 1200s    │
│ 2  │ 100          │ 202        │ completed │ 840s     │
│ 3  │ 100          │ 203        │ completed │ 1080s    │
│ 4  │ 100          │ 204        │ completed │ 1320s    │
└────┴──────────────┴────────────┴───────────┴──────────┘
     ↑ Cùng           ↑ Khác nhau

student_daily_study_time:
┌────┬────────────┬───────────────┬───────────────┐
│ id │ study_date │ total_minutes │ session_count │
├────┼────────────┼───────────────┼───────────────┤
│ 1  │ 2025-12-04 │ 74.00         │ 4             │
└────┴────────────┴───────────────┴───────────────┘
                    ↑ 20+14+18+22

🔑 KEY POINT:
- Mỗi idHistory = 1 session riêng
- Cùng idHistoryContest = cùng contest
- Backend track độc lập từng session
```

---

## 🔄 CASE 6: MULTIPLE PARTS + CLOSE + CONTINUE

### **Timeline:**

```
═══ PART 1 ═══

[09:00:00] Start Part 1 → idHistory: 201, session 1
[09:10:00] Close browser
[09:20:00] Cron auto-end session 1 (status: auto_ended)

[14:00:00] Continue Part 1
           ┌────────────────────────────────────────────────┐
           │ Backend: Continue session 1 → session 2        │
           │ - Old session 1: status = auto_ended           │
           │ - New session 2: idHistory = 201 (SAME)        │
           └────────────────────────────────────────────────┘

[14:20:00] Submit Part 1 (session 2)

═══ PART 2 ═══

[14:21:00] Start Part 2 → idHistory: 202, session 3
[14:35:00] Submit Part 2

═══ PART 3 ═══

[14:36:00] Start Part 3 → idHistory: 203, session 4
[14:40:00] Close browser
[16:00:00] Continue Part 3 → session 5
[16:15:00] Submit Part 3

═══════════════════════════════════════════════════════════════════
📊 DATABASE STATE:

student_study_sessions:
┌────┬────────────┬───────────┬───────────┬─────────────┐
│ id │ id_history │ status    │ start     │ end         │
├────┼────────────┼───────────┼───────────┼─────────────┤
│ 1  │ 201        │ auto_ended│ 09:00:00  │ 09:10:00    │
│ 2  │ 201        │ completed │ 14:00:00  │ 14:20:00    │ ← Part 1
│ 3  │ 202        │ completed │ 14:21:00  │ 14:35:00    │ ← Part 2
│ 4  │ 203        │ auto_ended│ 14:36:00  │ 14:40:00    │
│ 5  │ 203        │ completed │ 16:00:00  │ 16:15:00    │ ← Part 3
└────┴────────────┴───────────┴───────────┴─────────────┘

Total: 5 sessions, 3 parts completed
```

---

## 📊 Backend Logic Summary

### **A. Check Existing Session (start API):**

```sql
SELECT * FROM student_study_sessions
WHERE student_id = :student_id
  AND id_history = :id_history
  AND status IN ('active', 'auto_ended')
LIMIT 1;
```

- Tìm thấy → Return "need_continue"
- Không tìm thấy → Create new session
- ⚠️ Status = 'stopped' KHÔNG được check

---

### **B. Continue Session:**

```sql
-- 1. Get old session
SELECT * FROM student_study_sessions
WHERE id = :session_id
  AND status IN ('active', 'auto_ended');

-- 2. If status = 'active' (chưa auto-end)
--    End it using last_heartbeat_at

-- 3. Create new session with SAME idHistory
INSERT INTO student_study_sessions ...
```

---

### **C. Auto-End (Cron Job):**

```sql
SELECT * FROM student_study_sessions
WHERE status = 'active'
  AND (
    last_heartbeat_at < NOW() - INTERVAL 10 MINUTE
    OR last_heartbeat_at IS NULL
  )
LIMIT 1000;

-- For each session:
-- 1. End using last_heartbeat_at as end_at
-- 2. Apply min 15 minutes rule
-- 3. Update daily stats
```

---

### **D. Daily Time Distribution (cross-day):**

```php
// Pseudo code
function distributeDailyTime($session, $startTime, $endTime) {
    $currentDate = $startTime->startOfDay();
    $endDate = $endTime->startOfDay();
    
    while ($currentDate <= $endDate) {
        $dayStart = $currentDate;
        $dayEnd = $currentDate->endOfDay();
        
        $sessionStartInDay = max($startTime, $dayStart);
        $sessionEndInDay = min($endTime, $dayEnd);
        
        $secondsInDay = $sessionEndInDay - $sessionStartInDay;
        $proportion = $secondsInDay / $totalDuration;
        $adjustedSecondsInDay = $adjustedTotal * $proportion;
        
        updateDailyStudyTime($date, $adjustedSecondsInDay / 60);
        
        $currentDate->addDay();
    }
}
```

---

## ⚙️ Backend APIs - Request/Response

### **1. Start Session**

```
POST /api/heat-user/study-session/start

Request:
{
  "id_history_contest": 100,
  "id_history": 200,
  "id_bai_kiem_tra": 456
}

Response (Success - New):
{
  "success": true,
  "type": "new",
  "session_id": 1
}

Response (Need Continue):
{
  "success": false,
  "type": "need_continue",
  "session_id": 1,
  "status": "auto_ended"
}
```

### **2. Continue Session**

```
POST /api/heat-user/study-session/continue

Request:
{
  "session_id": 1
}

Response:
{
  "success": true,
  "session_id": 2,
  "old_session_duration_minutes": 15
}
```

### **3. Heartbeat**

```
POST /api/heat-user/study-session/heartbeat

Request:
{
  "session_id": 1
}

Response:
{
  "success": true
}
```

### **4. Submit**

```
POST /api/heat-user/study-session/submit

Request:
{
  "session_id": 1
}

Response:
{
  "success": true,
  "duration_minutes": 25.5
}
```

### **5. Stop**

```
POST /api/heat-user/study-session/stop

Request:
{
  "session_id": 1
}

Response:
{
  "success": true,
  "duration_minutes": 15
}
```

### **6. Get Statistics**

```
GET /api/heat-user/study-session/statistics?student_id=123&from_date=2025-01-01&to_date=2025-12-31

Response:
{
  "success": true,
  "data": {
    "student_id": 123,
    "total_minutes": 5400,
    "total_hours": 90,
    "total_days": 180,
    "total_sessions": 250,
    "daily_breakdown": [
      {
        "date": "2025-12-04",
        "total_minutes": 74,
        "session_count": 4
      }
    ]
  }
}
```

---

## 🎯 Key Takeaways

### **1. idHistory là key chính:**
- Mỗi idHistory = 1 session tracking
- Check existing dựa trên idHistory

### **2. Contest Type:**
- **= 29**: 1 idHistory, submit 1 lần
- **≠ 29**: nhiều idHistory, submit nhiều lần

### **3. Continue vs Stop:**
- **Continue**: Same idHistory, create new session
- **Stop**: New idHistory (EMS tạo mới)

### **4. Auto-End:**
- Cron mỗi 5 phút
- Timeout: 10 phút không heartbeat
- End time = last_heartbeat_at

### **5. Daily Stats:**
- Tự động tính khi end session
- Xử lý cross-day
- Min 15 phút rule

---

**Document này dành cho Backend/Server review. Không có code FE.**

