SOS Scoring Engine
Scoring Engine tính toán điểm SOS cho mỗi Seller theo 5 thành phần, áp dụng trọng số (weight), xác định tier, và xử lý grace period cho seller mới.
Tổng Quan Quy Trình
1. Load configs (weights, tiers, grace period, exceptions)
2. Calculate 5 components in parallel:
├── calcPScore() → Planning & Forecast
├── calcOScore() → Order Operations
├── calcTScore() → Ticket SLA
├── calcFScore() → Finance/Payment
└── calcIScore() → Inventory Health
3. Apply exceptions (score overrides)
4. Apply weights → weighted scores
5. Sum → Total SOS
6. Grace period check (min floor for new sellers)
7. Determine tier from sos_tier_config
8. Return ScoreResult
P-Score: Planning & Forecast (Default 25%)
Dữ liệu nguồn: sos_planning_raw (aggregated từ forecast submissions)
Logic tính điểm:
- Lấy tất cả planning records cho seller trong period
- Mỗi record có
on_time_points(0–50) vàaccuracy_points(0–50) - P-Score = Average(on_time + accuracy) across all campaigns
Config (sos_p_score_config):
| Field | Description | Default |
|---|---|---|
campaign_type | Loại campaign (major/mini) | — |
required_lead_days | Số ngày phải submit trước campaign | — |
on_time_points | Điểm max cho submit đúng hạn | 50 |
late_penalty_per_day | Trừ điểm mỗi ngày trễ | — |
accuracy_base_points | Điểm base cho forecast accuracy | 50 |
accuracy_good_range_min/max | Ngưỡng % dự báo chính xác | — |
accuracy_penalty_per_10pct | Trừ điểm mỗi 10% sai lệch | — |
O-Score: Order Operations (Default 20%)
Dữ liệu nguồn: sos_order_operation_raw (từ OMS sync POST /api/sync/orders)
Logic tính điểm:
late_pct = orders_late / total_orders × 100
if (late_pct <= acceptable_late_pct):
O-Score = base_points (100)
else:
over = late_pct - acceptable_late_pct
penalty = floor(over) × penalty_per_1pct_over
O-Score = clamp(base_points - penalty, 0, 100)
Config (sos_o_score_config):
| Field | Description | Default |
|---|---|---|
base_points | Điểm khởi đầu | 100 |
acceptable_late_pct | % trễ được phép (không bị trừ) | 3% |
penalty_per_1pct_over | Trừ điểm mỗi 1% vượt ngưỡng | 5 |
max_delay_minutes | Ngưỡng coi là "trễ" (phút) | 30 |
T-Score: Ticket SLA (Default 20%)
Dữ liệu nguồn: sos_ticket_sla_raw (từ Frappe Helpdesk sync POST /api/sync/tickets)
Logic tính điểm:
- Lấy
avg_response_time_hourstừ raw data - So khớp với tier config theo thứ tự
tier_index:- Nếu
avg_hours >= min AND (max IS NULL OR avg_hours < max)→ trả vềpoints
- Nếu
- Fallback: tier cuối cùng (worst)
Config (sos_t_score_config):
| tier_index | response_hours_min | response_hours_max | points |
|---|---|---|---|
| 1 | 0 | 4 | 100 |
| 2 | 4 | 8 | 80 |
| 3 | 8 | 16 | 60 |
| 4 | 16 | 24 | 40 |
| 5 | 24 | NULL | 20 |
F-Score: Finance & Payment (Default 20%)
Dữ liệu nguồn: sos_payment_raw (từ Accounting sync POST /api/sync/payments)
Logic tính điểm:
- Lấy
worst_days_late(số ngày trễ thanh toán tệ nhất) - So khớp với tier config theo
tier_index:- Nếu
worst_late >= min AND (max IS NULL OR worst_late <= max)→ trả vềpoints
- Nếu
- Fallback: tier cuối cùng (worst = 0 điểm)
Config (sos_f_score_config):
| tier_index | days_late_min | days_late_max | points |
|---|---|---|---|
| 1 | 0 | 0 | 100 |
| 2 | 1 | 7 | 80 |
| 3 | 8 | 15 | 60 |
| 4 | 16 | 20 | 30 |
| 5 | 21 | NULL | 0 |
I-Score: Inventory Health (Default 15%)
Dữ liệu nguồn: sos_inventory_health_raw (từ WMS sync POST /api/sync/inventory)
Logic tính điểm:
aging_pct = MAX(aging_pct_by_cbm, aging_pct_by_qty)
if (aging_pct <= base_good_pct):
I-Score = base_points (100)
else:
over = aging_pct - base_good_pct
penalty_steps = floor(over / 5)
score = base_points - (penalty_steps × penalty_per_5pct)
# Severe aging multiplier
if (aging_over_180d_pct > severe_threshold):
score = round(score / severe_multiplier)
I-Score = clamp(score, 0, 100)
Config (sos_i_score_config):
| Field | Description | Default |
|---|---|---|
base_points | Điểm khởi đầu | 100 |
base_good_pct | % tồn kho aging được phép | 5% |
penalty_per_5pct | Trừ điểm mỗi 5% vượt ngưỡng | 10 |
severe_aging_days | Ngưỡng aging nghiêm trọng (ngày) | 180 |
severe_aging_pct_threshold | % tồn kho > 180 ngày kích hoạt multiplier | 30% |
severe_storage_multiplier | Hệ số nhân phạt nặng | 1.5 |
Weighted Scoring & Total SOS
Total SOS = P_raw × W_p + O_raw × W_o + T_raw × W_t + F_raw × W_f + I_raw × W_i
Ví dụ:
| Component | Raw Score | Weight | Weighted |
|---|---|---|---|
| P-Score | 85 | 25% | 21.25 |
| O-Score | 90 | 20% | 18.00 |
| T-Score | 80 | 20% | 16.00 |
| F-Score | 100 | 20% | 20.00 |
| I-Score | 70 | 15% | 10.50 |
| Total SOS | 85.75 |
Weights có thể cấu hình qua sos_score_weight_config. Default weights: P=25%, O=20%, T=20%, F=20%, I=15%.
Tier Determination
Sau khi tính Total SOS, hệ thống so khớp với sos_tier_config:
| Tier | Min Score | Max Score | Ý nghĩa |
|---|---|---|---|
| Platinum | 90 | 100 | Seller xuất sắc |
| Gold | 80 | 89 | Seller tốt |
| Silver | 70 | 79 | Seller trung bình |
| Bronze | 50 | 69 | Cần cải thiện |
| Warning | 0 | 49 | Cảnh báo nghiêm trọng |
Grace Period (Seller Mới)
Seller mới ký hợp đồng được hưởng grace period (ưu đãi điểm tối thiểu):
Điều kiện áp dụng:
1. months_since_contract <= grace_period_months (default: 2)
2. cumulative_orders < min_orders_threshold (default: 30)
Nếu đủ điều kiện VÀ total_sos < min_score_floor (default: 70):
→ total_sos = min_score_floor
→ grace_floor_applied = true
→ original_sos_before_floor = [giá trị gốc]
Config qua seller_grace_period_config.
Score Exceptions
Quản trị viên có thể tạo exception để override điểm cho một seller cụ thể:
- Áp dụng theo component:
p_score,o_score,t_score,f_score,i_score, hoặcall - Rule format:
{ "set_score": 100 }— set điểm raw thành giá trị cố định - Thời hạn hiệu lực:
effective_from/effective_to
Exceptions được kiểm tra tự động khi chạy scoring engine.
Monthly Scoring Flow
# 1. Trigger monthly scoring
POST /api/sos/monthly/calculate
Body: { "period": "2026-02" }
→ Tính điểm cho TẤT CẢ active sellers
# 2. Review monthly scores
GET /api/sos/monthly?period=2026-02
→ Danh sách scores (status: draft)
# 3. Finalize individual score
PATCH /api/sos/monthly/:id/finalize
Body: { "reviewed_by": "admin", "notes": "OK" }
→ status: draft → final
→ Webhook: seller.score_finalized
→ Webhook: seller.tier_changed (nếu tier thay đổi)