# คู่มือเตรียมสอบ TourApp — Matching V2

เอกสารนี้อ้างอิงจาก source code และ schema ปัจจุบันของโปรเจกต์ ไม่ใช่เอกสารออกแบบแยกจาก implementation

## A. Architecture Overview

```text
Flutter User App ──HTTP/JSON──> Dart HttpServer/API ──mysql_client_plus──> MariaDB
React Agency/Admin Portal ──HTTP/JSON──> Dart HttpServer/API ──> MariaDB
```

- Backend entry point: `server/gemini_proxy.dart`
- Flutter matching engine: `lib/matching/matching_engine.dart`
- Backend matching persistence: `server/matching_service.dart`
- AI/NLP ใช้แปลงข้อความภาษาธรรมชาติเป็น structured preference ใน `lib/services/ai_match_service.dart` และ proxy route `/api/analyze`
- Gemini ไม่ได้ตัดสิน membership โดยตรง การจับกลุ่มใช้ deterministic `MatchingEngine` และ `MatchingClusterPlanner`
- Agency/Admin API อยู่ใน `server/agency_admin_api.dart` และ MySQL repository อยู่ใน `server/mysql_agency_admin_repository.dart`

## B. Matching Flow

1. User submit preference ผ่าน `SmartSearchPage`/`PreferencesPage` และ `MatchingApiService` ไป `/api/matching/preferences/submit`
2. `MatchingService.submitPreference` บันทึก preference เป็น waiting และเรียกการประเมิน pool
3. `MatchingEngine.sameCountry` ใช้ country เป็น hard eligibility gate
4. Region/city เป็น optional; `destinationFlexible` ทำให้การไม่ระบุเมืองยัง compatible ได้
5. `MatchingEngine.calculate` ตรวจ budget gate และคำนวณ Interests/Budget/Period
6. คะแนนต้องผ่าน `MatchingConfig.minimumCompatibility` (70)
7. ถ้ายังไม่ครบกลุ่ม preference ยังคง waiting
8. `MatchingService._assignToExistingGroups` และ `_createGroups` ใช้ lock/transaction เพื่อ assign สมาชิกอัตโนมัติ
9. ถ้ารอนาน ระบบส่ง alternative recommendation จาก `DestinationRecommendationPolicy`; ผู้ใช้ต้องกดยืนยันผ่าน `/api/matching/alternative/accept`
10. หลัง group เกิด สมาชิกเปิด `/api/destination-polls` เพื่อสร้าง/อ่าน Region/City และ Place/Activity poll จาก catalog เดิม

## C. Formula

```text
Total = Interests(0..100) × 0.40
      + Budget(0..100) × 0.30
      + Travel Period(0..100) × 0.30
```

- Threshold = 70
- Budget difference วัดเทียบกับงบที่มากกว่า
- <=10%: budget score 30/30
- >10% ถึง <=20%: ลดแบบ linear จนเหลือ policy floor 50%
- >20%: budget gate FAIL และห้าม automatic match แม้ total score จากมิติอื่นจะถึง 70

ตัวอย่าง:

1. Interests 100, Budget 100, Period 100 → `40 + 30 + 30 = 100`, MATCH
2. Interests 100, Budget 50, Period 100 → `40 + 15 + 30 = 85`, MATCH ถ้างบต่างไม่เกิน 20%
3. Interests 100, Budget 12, Period 100 → คะแนนอาจสูงจากมิติอื่น แต่ budget difference >20% จึง `NO MATCH`

จุดรวม policy อยู่ใน `lib/matching/matching_engine.dart` (`MatchingConfig`, `CompatibilityResult`)

## D. ถ้าอาจารย์ให้แก้…

| งาน | จุดที่แก้ | วิธีตรวจ |
|---|---|---|
| เปลี่ยน Interests 40% เป็น 50% | `MatchingConfig.interestWeight` และปรับน้ำหนักรวมใน `matching_engine.dart` | รัน matching unit tests และตรวจผลรวม = 1.0 |
| เปลี่ยน threshold 70 เป็น 75 | `MatchingConfig.minimumCompatibility` | เพิ่ม/แก้ scenario ที่ขอบ 75 |
| เปลี่ยน tolerance 20% เป็น 25% | `MatchingConfig.maximumBudgetDifference` และ policy test | ทดสอบ 20%, 25%, >25% |
| เพิ่มประเทศ alternative | `DestinationRecommendationPolicy._alternatives` | ทดสอบ accept/decline และ history |
| เพิ่ม Region/City | catalog ใน `tour_groups` และ `_ensureDefaultPolls` | เปิด Poll ด้วยสมาชิกจริงและตรวจ fallback |
| เพิ่มสถานที่/รูป | `tour_groups.city`, `highlights_json`, `image_asset` | ตรวจ asset errorBuilder |
| เปลี่ยน Place Poll สูงสุด 5 เป็น 3 | `DestinationPollService._ensureDefaultPolls` | ทดสอบ vote เกิน max ได้ 400 |
| เปลี่ยนจำนวนสมาชิกขั้นต่ำ | `MatchingConfig.minimumGroupSize` และ backend planner | ทดสอบ waiting/group assignment และ DB count |

## E. Important Files Map

| Feature | Backend | Flutter | Portal | Database/Test |
|---|---|---|---|---|
| Matching | `server/matching_service.dart` | `lib/matching/matching_engine.dart` | — | `travelin_matching_v2.sql`, `matching_engine_test.dart` |
| Voting | `server/gemini_proxy.dart` | `lib/pages/voting_page.dart` | — | voting migrations/tests |
| Payment | `server/slip_verification_service.dart`, `gemini_proxy.dart` | `lib/pages/payment_page.dart` | — | payment/slip migrations/tests |
| Destination Poll | `server/destination_poll_service.dart` | `lib/pages/destination_poll_page.dart` | — | `travelin_destination_polls.sql`, poll tests |
| Agency | `server/agency_admin_api.dart`, `mysql_agency_admin_repository.dart` | `lib/pages/agency_profile_page.dart` | `portal/src/pages/AgencyMarketplacePages.tsx` | agency tests |
| Admin | `server/agency_admin_api.dart`, `admin_group_service.dart` | — | `portal/src/pages/AdminPages.tsx` | admin migrations/tests |
| Travel Documents | `server/travel_document_service.dart` | `lib/pages/travel_documents_page.dart` | agency traveler pages | travel document migration/tests |
| Trip Hub | `server/trip_hub_service.dart` | `lib/pages/trip_hub_page.dart` | — | trip hub migration/tests |
| Journey | `server/journey_safety_service.dart` | `lib/services/group_service.dart` | agency journey pages | journey migration/tests |

## F. Database Tables

- `users`: account, auth/session linkage, current `group_id`
- `travel_preferences`: destination, budget, month, region/city, original/accepted destination
- `preference_categories`, `travel_categories`: interests
- `tour_groups`: automatic group, destination, status, counts
- `group_members`: authoritative membership and per-member match score
- `bids`, `tour_packages`, `bid_documents`: agency offers and private PDFs
- `group_voting_rounds`, vote tables: voting lifecycle and winner
- `payments`, verification tables: payment and slip verification
- `group_destination_polls`, `destination_poll_options`, `destination_poll_votes`: post-matching planning
- travel document, notification, journey tables: later lifecycle subsystems

## G. API Map

| Endpoint | Purpose | Authorization |
|---|---|---|
| `POST /api/matching/preferences/submit` | Submit structured preference | authenticated user |
| `POST /api/matching/status` | Read matching status | authenticated user |
| `POST /api/matching/alternative/accept` | Accept configured alternative | owner of waiting preference |
| `POST /api/destination-polls` | Read group polls | group member |
| `POST /api/destination-polls/vote` | Replace poll choices | group member |
| `POST /api/agency/leads` | Lead marketplace | approved agency |
| `POST /api/agency/leads/detail` | Lead aggregate + anonymized members | approved agency |
| `POST /api/agency-profile` | Agency profile by bid or finalized winner | group member; agency approved |
| `POST /api/offers` | Offers and voting data | group member |
| `POST /api/votes`, `/api/votes/finalize` | Vote/finalize | group member / lifecycle rules |

Private files are served through authenticated private-document routes, not public static URLs.

## H. Common Exam Questions

- **ทำไม Interests 40%?** เพราะ style เป็นตัวสะท้อนความเข้ากันได้มากที่สุดใน policy ปัจจุบัน และน้ำหนักถูกเก็บรวมใน config เดียว
- **ทำไม Country ไม่คิดเป็นเปอร์เซ็นต์?** เพราะเป็น hard constraint; คนละประเทศไม่ควรถูกจับกลุ่มอัตโนมัติ
- **ทำไมงบต่างเกิน 20% ไม่ Match?** เป็น budget gate เพื่อไม่ให้คะแนนด้านอื่นกลบความเสี่ยงด้านค่าใช้จ่าย
- **Gemini เป็นคนจัดกลุ่มหรือไม่?** ไม่ใช่ Gemini ช่วย NLP extraction; deterministic engine เป็นผู้ตัดสิน compatibility/grouping
- **ถ้า Gemini ล่ม Matching ยังทำงานไหม?** ถ้ามี structured preference อยู่แล้ว matching ยัง deterministic; เฉพาะการวิเคราะห์ข้อความใหม่ที่ได้รับผลกระทบ
- **ทำไมไม่จับคนต่างประเทศเข้ากลุ่มเอง?** เพราะ destination เป็น hard eligibility และต้องให้ user ยอมรับ alternative เอง
- **Alternative ทำงานอย่างไร?** ใช้ map ที่กำหนดไว้, preserve original destination และเปลี่ยน current/accepted destination เมื่อ user ยืนยัน
- **Poll กับ Agency Voting ต่างกันอย่างไร?** Poll เป็น lightweight planning ที่สมาชิกเปลี่ยน choices ได้; Agency Voting มี deadline/winner/payment lifecycle
- **บริษัทเห็นข้อมูลส่วนตัวไหม?** Lead Detail แสดง aggregate และ anonymized preferences เท่านั้น; private documents อยู่คนละ authorization flow
- **User ตรวจสอบบริษัทอย่างไร?** เปิด Agency Profile จาก bid/voting ได้เมื่อ bid อยู่ใน group และ agency approved; หลังชนะยังใช้หน้าเดิม
- **ระบบป้องกันบริษัทปลอมอย่างไร?** agency ต้องผ่าน Admin approval และ server ตรวจ status ทุก action
- **Payment Verification ทำงานอย่างไร?** upload private slip แล้ว backend verifier ตรวจ amount/receiver/reference/duplicate ตาม environment
- **ทำไมไม่มี true Escrow?** implementation ปัจจุบันไม่มี escrow provider; payment เป็นสถานะในระบบ ไม่อ้างว่าเป็น custody ทางการเงิน

## I. Safe Live-Code Exercises

1. เปลี่ยน threshold: หา `MatchingConfig.minimumCompatibility`, แก้ test ขอบเขต, รัน `flutter test test/matching/matching_engine_test.dart`
2. เปลี่ยนน้ำหนัก: แก้ constants ให้รวม 100%, ตรวจ breakdown และ scenario เดิม
3. เพิ่ม interest: เพิ่ม parser/catalog mapping ก่อนแก้ UI และเพิ่ม extraction test
4. เพิ่ม alternative country: แก้ `_alternatives`, เพิ่ม accept/decline test โดยตรวจ history
5. เปลี่ยน max poll: แก้ service config, เพิ่ม test vote เกินจำนวน
6. เปลี่ยนข้อความ UI: แก้เฉพาะ widget/page และรัน widget test
7. เพิ่ม Agency Profile field: ตรวจ schema/query ก่อน เพิ่ม response type และหน้า Flutter
8. เพิ่ม marketplace filter: แก้ `LeadFilter`, server validation, repository query และ Portal form
9. เปลี่ยน budget tolerance: แก้ `maximumBudgetDifference`, เพิ่ม tests <=/ >/threshold
10. เพิ่ม test case: เริ่มจาก pure engine test แล้วค่อยเพิ่ม API/widget test ถ้ามี contract ที่เกี่ยวข้อง

ทุก exercise ควรตรวจ `dart analyze`, `flutter analyze` และ test ที่แตะจริงก่อน merge
