CleanThunJai — Algorithm Spec
Phase 1 reference for partner technical review. All formulas are auditable and exposed in API responses.
1. Matching Algorithm
Goal: ให้คะแนน 0–100 กับแม่บ้านแต่ละคนสำหรับ booking request หนึ่งใบ แล้วเรียงลำดับ top-N
Hard filters (fail → score = 0):
- มี skill ตรงกับ service ที่ขอ
- ระยะทาง ≤
housekeeper.serviceRadiusKm - เปิดรับงานในช่วงเวลาที่ขอ
- เพศตรงกับ
preferences.femaleOnly - ภาษาตรงกับ
preferences.nativeSpeakerOnly
Weighted score:
score = 100 × ( 0.25 × distanceComp // 1 − d/radius + 0.25 × ratingComp // rating / 5 + 0.20 × reliabilityComp // 0.6·onTimeRate + 0.4·(1−cancelRate) + 0.10 × experienceComp // log10(jobs+1) / 3, capped 1 + 0.08 × languageComp // 1 if shared, 0.4 otherwise + 0.07 × trustComp // 0.5·bgCheck + 0.5·certified + 0.05 × priceComp // 1 − premium/0.5, capped )
น้ำหนักปรับได้ใน POST /api/match field weights
2. Pricing Engine
Formula (transparent line-item):
subtotal = (base + areaSurcharge + addons) × serviceMul × timeMul × urgencyMul × (1 + hkPremium) afterDisc = subtotal × (1 − loyaltyDisc) × (1 − subDisc) totalTHB = afterDisc × (1 + surge) total = totalTHB × FX[currency] platformFee = total × 0.18 // configurable hkEarn = total − platformFee
Key tables (THB):
BASE_HOURLY_RATE = {
general_cleaning: 200, deep_cleaning: 350,
post_renovation: 450, move_in_out: 380,
ironing: 180, laundry: 180, cooking: 250,
childcare_light: 280, elderly_care_light: 320
}
SERVICE_MULTIPLIER (1.00 – 1.25 by complexity)
TIME_MULTIPLIER = 1.30 night (22-06) · 1.10 early (06-08) · 1.00 day
URGENCY = 1.25 if booked < 2hr ahead
LOYALTY_DISC = 2% (3mo) → 10% (24mo+)
SUBSCRIPTION_DISC = lite 5% · plus 12% · pro 20%
AREA_SURCHARGE = max(0, sqm − 50) × 8 THB3. Scheduling & Routing
Heuristic: Greedy insertion + nearest-neighbor. ทดลองแทรกงานใหม่ในทุกตำแหน่ง เลือกที่ Δ travel น้อยสุด
function tryInsert(start, existing, candidate):
baseRoute = planDay(start, existing)
bestDelta = ∞
for i in 0..len(existing):
trial = existing[0:i] + [candidate] + existing[i:]
r = planDay(start, trial)
delta = r.totalTravel − baseRoute.totalTravel
if delta < bestDelta:
bestIndex = i; bestDelta = delta
return route at bestIndexTravel model: Haversine × Bangkok avg 22 km/h (door-to-door, รวม traffic)
Buffer: 15 นาที ระหว่างถึงและเริ่มงาน
Roadmap: Phase 2 จะ upgrade เป็น OR-Tools VRP กับ time windows, multi-day, multi-housekeeper assignment
4. Trust Score
Composite index 0–100 with Bayesian shrinkage:
bayesRating = (Σstars + 4.2 × 5) / (count + 5) // prior μ=4.2, k=5 trust = 100 × ( 0.30 × ratingComp (bayesRating / 5) + 0.20 × completionComp (completed / (completed + cancel + noShow)) + 0.15 × onTimeComp (onTime / completed) + 0.10 × responseComp (1 − avgResponseMin / 60) + 0.10 × tenureComp (days / 365, capped 1) + 0.08 × verificationComp (0.4·id + 0.4·bgCheck + 0.2·certifs/3) + 0.05 × disputeComp (1 − strikes × 0.15) + 0.02 × repeatComp (repeatCustomers / 20) ) tier: Bronze < 55 ≤ Silver < 72 ≤ Gold < 85 ≤ Platinum
Anti-gaming guards:
- Bayesian prior → 5⭐ จาก 1 รีวิว ไม่ทำให้คะแนนพุ่ง
- Disputes resolved-against-housekeeper → hard penalty −15% / strike
- ≥3 confirmed strikes → auto-flag suspension
- Repeat customers ถ่วงน้ำหนัก loyalty (น้อย แต่ป้องกัน fake review burst)
5. Data Model (summary)
เห็นเต็มใน API Playground และ lib/types.ts
Customer { id, location, language, preferences, subscriptionTier, loyaltyMonths }
Housekeeper { id, baseLocation, serviceRadiusKm, skills[], languages[],
rating, jobsCompleted, cancellationRate, onTimeRate,
responseMinutes, backgroundChecked, certified,
femaleOnly, hourlyBaseRate, pricePremium, availability[] }
BookingRequest{ customerId, service, start, durationHours, squareMeters,
location, language, addons[], isUrgent, preferences }6. Roadmap → Phase 2 (Web App)
- Auth + role-based access (Customer / Housekeeper / Admin)
- Real database (Postgres + Prisma) — migrate sample data
- Payment integration (PromptPay + Stripe ASEAN)
- Real-time booking flow (WebSocket) + push notifications
- Background check API integration
- Admin: dispute resolution, payout, surge pricing controls
- i18n (TH + EN first, MY/ID/VN/TL ready in JSON)