Restore 替换建议 card and add a new emerald 近期已干预 card. Clicking
opens the history modal pre-filtered to the last 7 days (excluding
cancelled) via a toggle chip users can switch off.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add 调度记录 modal: lists notifications by status, supports 标记已执行 (with
after-mileage + notes) and 取消 for open records
- Add CSV export of filtered suggestions (UTF-8 BOM for Excel); top candidate
per row picked by same-region > can-qualify preference
- Compute customer 7-day average alongside 30-day baseline in a single query;
show trend indicator (up/down/flat) next to 客户日均 in list and detail card
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add tab_scheduling_notifications table with bootstrap via ensureSchedulingTables()
- Notify endpoint rewritten: dedup by (suggestion_id, candidate_plate), history list, PATCH /:id for execute/cancel lifecycle
- Batch notify endpoint returns success/skipped/failed counts
- Suggestions response now carries notificationId + notificationStatus per candidate (joined from active-notification map)
- UI: select mode with checkboxes, floating action bar, confirmation modal listing each swap; already-notified items are dimmed and skipped
- Detail view badges show sent/executed state, preventing duplicate notify
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Extract shared types to src/shared/scheduling/types.ts (client/server both re-export)
- Convert SchedulingSuggestion.reason from string to structured { lines, conclusion }
- Remove hard region filter; algorithm keeps cross-region candidates with isSameRegion flag
- SuggestionDetail renders same-region vs cross-region sections with a divider
- Close detail modal when selected suggestion no longer exists in data
- Unify estimatedGain definition (strict canQualifyAfterSwap) between algorithm and API layers
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Different assessment targets have different end dates. Previously all
candidates used the current vehicle's daysLeft, causing wrong predictions.
Now each inventory vehicle computes its own daysLeft from its assessment
target's current_year_assessment_end_date. predictedAfterSwap uses the
candidate's own daysLeft instead of the current vehicle's.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For rescue_hopeless (换走) scenario, completely rethought candidate logic:
Before: showed biggest-gap candidates (0 mileage) → pointless, customer can't
drive them to target
After: prioritize candidates where customer's remaining driving can push them
over the target line (canQualifyAfterSwap), sorted by smallest gap first
Example: customer drives 178km/day × 57 days = ~1万km remaining.
- 粤AGR6869 (缺口 1990km) → 换后 3.8万, 可达标 ✅ (shown first)
- 浙FF58720 (缺口 6万km) → 换后 1万, 远不达标 (no longer shown first)
Also updated reason text to explain the math:
"该客户剩余57天还能跑约1万km,足以帮缺口小的车冲线"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For rescue_hopeless (换走) scenario, filter out inventory candidates
where totalMileage/yearTarget >= 80%. These are already near target
and swapping them in adds no value.
Instead, prioritize candidates with biggest mileage gaps — they benefit
most from accumulating any mileage, even at a low-mileage customer.
Before: showed 粤AGR6869 (93% done, 缺口 1990) as "可达标" — pointless
After: shows 浙FF58720 (0% done, 缺口 60000) — genuinely needs mileage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add department and manager fields to backend types and suggestions API
- Add department/manager to advanced filter panel
- Refine card colors: orange (换下) / blue (换走) / dark slate (全部)
- Selected card uses solid bg color, inactive uses gradient
- Batch pills use dark slate, confirm button uses dark slate
- Background changed to #F0F4F8 for subtle cool tone
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add currentYearMileage to SchedulingVehicleInfo (backend + frontend)
- Compute completionRate as currentYearMileage/yearTarget (year-based)
instead of using overall completion_rate from DB
- Display "本年已跑" instead of "累计" in detail modal
- Fix reason text to show year completion rate
Before: 累计 4.6万, 考核 3.0万, 完成率 12.1% (mismatched periods)
After: 本年已跑 8.3万, 考核 3.0万, 完成率 275% (consistent year-based)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- classifyVehicleType now parses dic_type.dic_name (e.g. "4.5吨冷链车") instead of raw model code
- Remove overly strict completionRate >= 0.8 filter for hopeless candidates
- Use vehicle's yearTarget as fallback when inventory has no assessment target
- Filter out suggestions with no candidates (not actionable)
- estimatedGain counts rescue_hopeless suggestions as potential gains
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>