Provide managers with a dedicated page to analyze hold lots across all stations. Extends existing service functions (get_hold_detail_summary, get_hold_detail_lots, get_wip_matrix) with optional parameters for backward compatibility, adds one new function (get_hold_overview_treemap), and registers the page in the portal navigation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.4 KiB
Context
MES Dashboard 已有 WIP Overview(全局 RUN/QUEUE/HOLD 概況)和 Hold Detail(單一 Hold Reason 明細)。主管需要一個專用頁面,聚焦在 Hold Lot 的全局分析。
現有架構:Vue 3 SFC + Flask + Oracle(DWH.DW_MES_LOT_V),使用 Redis cache + snapshot indexes 加速查詢。經審計,wip_service.py 中已有大量可複用的函數和 cache 基礎設施:
可直接呼叫:
get_wip_matrix(status='HOLD', hold_type=...)— Matrix 查詢,零改動_select_with_snapshot_indexes(status='HOLD', hold_type=...)— 已有wip_status['HOLD']和hold_type['quality'|'non-quality']snapshot indexes_get_wip_dataframe()— L1 process cache (30s) → L2 Redis cache,AGEBYDAYS欄位已在 View 中預先計算
可擴充(向後相容):
get_hold_detail_summary(reason)— 已有 totalLots/totalQty/avgAge/maxAge/workcenterCount,reason 改 optional 即可get_hold_detail_lots(reason, ...)— 已有完整分頁邏輯 + workcenter/package/age_range 過濾,reason 改 optional + 加 hold_type 即可
前端可直接 import:
hold-detail/SummaryCards.vue— props{ totalLots, totalQty, avgAge, maxAge, workcenterCount }完全相容wip-shared/Pagination.vue、useAutoRefresh、core/api.js、wip-shared/constants.js
Goals / Non-Goals
Goals:
- 提供主管一覽各站 Hold Lot 情況的獨立頁面
- TreeMap 視覺化讓嚴重程度一目了然(面積=QTY,顏色=滯留天數)
- Matrix + TreeMap + Table 三層 cascade 篩選,互動流暢
- 預設品質異常 Hold,可切換
- 最大化複用現有 service 函數、cache 基礎設施和前端元件
Non-Goals:
- 不取代或修改現有 Hold Detail 頁面(擴充 service 函數需向後相容)
- 不新增資料庫 view 或 table — 完全複用 DWH.DW_MES_LOT_V
- 不新增 SQL 模板 — 複用現有 summary.sql / matrix.sql / detail.sql + QueryBuilder WHERE clause
- 不實作 autocomplete 搜尋(篩選僅 Hold Type + Reason dropdown)
- 不實作 Lot 點擊展開的 detail panel(明細表為純展示)
Decisions
D1: 擴充現有 service 函數,非新建
決定: 擴充 get_hold_detail_summary() 和 get_hold_detail_lots() 的參數簽名,而非建立新的 get_hold_overview_summary() / get_hold_overview_lots()。唯一全新的函數是 get_hold_overview_treemap()(WC × Reason 聚合邏輯不存在於現有函數)。
擴充方式:
get_hold_detail_summary(reason)→get_hold_detail_summary(reason=None, hold_type=None)reason=None時聚合所有 HOLD lotshold_type='quality'|'non-quality'進一步過濾- 原有 Hold Detail 呼叫
get_hold_detail_summary(reason='xxx')行為不變
get_hold_detail_lots(reason, ...)→get_hold_detail_lots(reason=None, hold_type=None, treemap_reason=None, ...)reason=None時返回所有 HOLD lotstreemap_reason支援 TreeMap 點擊篩選- 現有 Hold Detail 呼叫簽名不受影響
理由: 這兩個函數的核心邏輯(cache path + Oracle fallback + 分頁 + 過濾)完全相同,差異僅在 reason 是否為必填。新建函數會複製 80%+ 相同邏輯,增加維護負擔。
替代方案: 新建獨立函數 — 但會造成大量重複的 cache 查詢路徑和 Oracle fallback 邏輯。
D2: Matrix API 直接呼叫 get_wip_matrix,僅擴充 reason 參數
決定: Hold Overview 的 Matrix API 直接呼叫現有 get_wip_matrix(status='HOLD', hold_type=...) 函數。唯一需要擴充的是新增 optional reason 參數,支援 Filter Bar 的 Reason 篩選。
理由: get_wip_matrix 已支援 status 和 hold_type 參數,能完全滿足 Hold Overview Matrix 的需求。擴充 reason 參數的改動量極小,且 reason=None 時行為與現有完全一致。
替代方案: 複製 matrix 邏輯到新函數 — 違反 DRY,且 matrix 排序/分頁邏輯會分散維護。
D3: TreeMap 聚合是唯一需要全新建立的 service 函數
決定: 新增 get_hold_overview_treemap() 函數,後端返回已聚合的 { workcenter, reason, qty, lots, avgAge } 陣列。此函數使用與其他函數相同的 _select_with_snapshot_indexes() + Oracle fallback 模式。
理由: 現有函數都沒有「按 (WORKCENTER_GROUP, HOLDREASONNAME) 二維聚合」的邏輯。這是 TreeMap 視覺化特有的需求,值得獨立建立。Hold Lot 可能數千筆,前端 groupBy 聚合會造成不必要的資料傳輸和 CPU 開銷。
替代方案: 前端從 lots API 取全部資料後自行聚合 — 資料量大時效能差,且需一次載入所有 lot。
D4: TreeMap 顏色映射使用固定 4 級色階
決定: 平均滯留天數映射到 4 個顏色等級:
< 1 天→ 綠色 (#22c55e)1-3 天→ 黃色 (#eab308)3-7 天→ 橙色 (#f97316)> 7 天→ 紅色 (#ef4444)
ECharts TreeMap 使用 visualMap 組件實現連續色階。
理由: 與 Hold Detail 的 Age Distribution 分段一致(0-1, 1-3, 3-7, 7+),主管認知模型統一。
D5: Filter cascade 為前端狀態管理,不影響 Summary 和 Matrix 的 API 呼叫
決定:
- Filter Bar(Hold Type / Reason)變更 → 呼叫全部 4 支 API
- Matrix 點擊 → 前端設定
matrixFilter,僅呼叫 treemap + lots API - TreeMap 點擊 → 前端設定
treemapFilter,僅呼叫 lots API
理由: Summary 和 Matrix 反映全局數據,不應被 Matrix/TreeMap 的 drilldown 操作影響。這與 WIP Overview 的 StatusCards 不影響 Summary 的模式一致。
D6: 路由結構與 Blueprint 獨立
決定: 新建 hold_overview_routes.py 作為獨立 Blueprint(hold_overview_bp),路由前綴 /api/hold-overview/。頁面路由 GET /hold-overview 由此 Blueprint 提供。
理由: 與 hold_routes.py(Hold Detail)和 wip_routes.py(WIP Overview)平行,職責分離。
D7: 前端元件複用策略 — import > 擴充 > 新建
決定:
| 元件 | 策略 | 理由 |
|---|---|---|
hold-detail/SummaryCards.vue |
直接 import | props 形狀 { totalLots, totalQty, avgAge, maxAge, workcenterCount } 完全相容 |
wip-shared/Pagination.vue |
直接 import | 已由 hold-detail/LotTable 使用,通用元件 |
wip-overview/MatrixTable.vue |
參考新建 HoldMatrix.vue |
需要 cell click + column click + active highlight — 原有只有 row drilldown,改動幅度大不適合直接修改原件 |
hold-detail/LotTable.vue |
參考新建 LotTable.vue |
需加 Hold Reason 欄位 + 移除 Spec 欄位 — hold-detail 不需要 reason 欄(已在 URL 參數中),直接修改會破壞現有頁面 |
HoldTreeMap.vue |
全新 | 無現有 TreeMap 元件 |
FilterBar.vue |
全新 | Hold Type radio + Reason dropdown 是此頁獨有的 UI |
FilterIndicator.vue |
全新 | cascade filter 指示器是此頁獨有的 UI |
理由: 直接修改跨頁面共用的元件有破壞現有頁面的風險。props 完全相容的元件直接 import;需要結構性改動的元件則基於現有程式碼新建,保留一致的 coding pattern 但避免耦合。
D8: ECharts TreeMap 模組 tree-shaking
決定: 前端使用 import { TreemapChart } from 'echarts/charts' 按需導入,搭配現有 vendor-echarts chunk。
理由: 現有 ECharts vendor chunk 已包含 BarChart、LineChart 等。TreemapChart 加入後仍在同一 chunk,不增加額外 HTTP request。
Risks / Trade-offs
- [向後相容] 擴充
get_hold_detail_summary()和get_hold_detail_lots()簽名時,必須確保 reason 參數的預設行為不變 → 使用reason=None預設值,現有呼叫端傳入 reason 的行為完全不變;需補充單元測試覆蓋原有 Hold Detail 的呼叫路徑 - [TreeMap 資料量] 如果 Hold Reason 種類很多(>20),TreeMap 小區塊會難以辨識 → 可考慮只顯示 Top N reason,其餘歸為「其他」
- [Matrix 與 TreeMap 同時篩選] 使用者可能忘記已有 matrix 篩選,誤以為 TreeMap 是全局 → 需要明確的 active filter 指示器和一鍵清除功能
- [ECharts TreeMap 效能] 大量區塊時 TreeMap 渲染可能卡頓 → ECharts TreeMap 有內建 leafDepth 限制,測試時注意超過 200 個葉節點的情境
- [Cache 一致性] Hold Overview 與 WIP Overview 共用同一份 cache,auto-refresh 週期相同(10 分鐘),不需調整 cache 策略