All bars now use the cyan→blue gradient consistently, matching the
electric daily chart. Anomaly information stays available via the
table row tinting and the trend pills below.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the electric-view treatment: a 时段每日加氢量 bar chart sits
between the customer toggle and the table. Bars use the cyan→blue
gradient by default; days where |chainPct| >= 30% render in solid
emerald (positive) or red (negative), giving an at-a-glance view of
anomalous days that's reinforced by the table's row tinting below.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New 本月每日充电 bar chart (蓝青 gradient) sits between KPI row and
table, fixing the previous "wall of numbers" feel
- Day rows now tint emerald/red when |chainPct| >= 30% (matches hydrogen)
- 环比 pill column now also shows on mobile (was desktop-only)
- Today KPI: pill moves to second line alongside kwh via justify-between
so it no longer gets clipped on narrow viewports
- Day labels in table trimmed to MM-DD (parent month row already shows year)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- mock: derive ELECTRIC_KPI month/today from APR_DAYS so card and table
totals always agree (previously ¥8,437 vs ¥9,151 mismatch)
- overview: Top5 bar chart now shows rank badges (1-5) and inline value
labels at bar ends — readable without hover
- overview: donut "年合计 362.43T" moves into the chart center
(previously below as a separate line, defeating the donut hole)
- daily: rows with |chainPct| ≥ 30% get a tinted background
(green for spikes, red for drops) for at-a-glance abnormal-day spotting
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mobile + desktop responsive front-end module with mocked data.
Each task = lint pass + chrome-devtools visual verification (no
test framework in this project).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mobile-first responsive entry under bottom nav. Phase 1: front-end
prototype with mocked data — backend deferred. Mirrors three FineBI
dashboards (TPqB / GBSp / 0iqP) restructured into 氢能/电能 tabs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove the implicit fallback that granted scheduling access to any
FULL_ACCESS role (所有权限 / 数智中心 / BI-Leader). Access now requires
an explicit BI-SCHEDULE-OPT assignment, so the module scope is managed
purely via role assignment rather than piggy-backing on admin roles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
src/server/auth/types.ts imports runtime values (role constants,
canAccessScheduling helper) from src/shared/auth/roles.ts — without
the shared folder in the final stage the server crashes with
ERR_MODULE_NOT_FOUND. Existing shared/scheduling imports survived
only because they were type-only and elided at runtime.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Gate 智能调度 module on BI-SCHEDULE-OPT role (or full-access roles)
via shared canAccessScheduling helper, replacing hardcoded userId allowlist
- Thread roles[] through JWT payload → middleware → frontend nav
- Add router guard that 403s non-authorized users on /api/scheduling/*
- Emit replace_qualified suggestion for every qualified vehicle so list
count matches the 已完成考核目标 card; recalc qualifiedCount /
hopelessCount post-permission-filter for card↔list consistency
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the toggle hid cancelled records, so users who clicked a
record timestamped within 7 days but later cancelled would see nothing.
Now 近7天 filters purely by createdAt; combine with status tabs to
narrow further.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each row in 调度记录 now shows 业务部门(简)/业务负责人/客户 beneath
the plate line, and is clickable to open the reusable SwapPreview
showing the full replacement plan (current mileage, 考核目标, 替换后预测).
Drill-in is only enabled when the suggestion is still in the active
scheduling view; the user can still 取消干预 from the preview.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
Business rule: a running vehicle can hold AT MOST ONE active (sent|executed)
intervention. Switching to a different candidate requires cancelling the
prior one first.
- Server: insertNotification dedup key changes from (suggestion_id,
candidate_plate) to just suggestion_id; 409 response includes the blocking
candidate plate
- Detail modal: shows a banner naming the locked candidate; non-active
candidates render a disabled "该车已有其他干预,请先解除" hint instead
of the action button
- Batch: pickBestCandidate returns null for any suggestion already holding
an active intervention — the whole suggestion is excluded
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Globally rename user-facing 通知 → 干预 (list badge, detail button, batch
modal, CSV header, server response messages, db table comment)
- 已干预 row in detail is now clickable — opens SwapPreview which shows
a read-only summary plus a 取消干预 action (PATCH notify /:id with
status=cancelled). Sending is blocked while already intervened.
- Selected suggestion now follows the latest data snapshot so status
changes from within the detail flow propagate immediately.
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>