feat(reject-history): fix Pareto datasources, multi-select filtering, and UX enhancements

- Fix dimension Pareto datasources: PJ_TYPE/PRODUCTLINENAME from DW_MES_CONTAINER,
  WORKFLOWNAME from DW_MES_LOTWIPHISTORY via WIPTRACKINGGROUPKEYID, EQUIPMENTNAME
  from LOTREJECTHISTORY only (no WIP fallback), workcenter dimension uses WORKCENTER_GROUP
- Add multi-select Pareto click filtering with chip display and detail list integration
- Add TOP 20 display scope selector for TYPE/WORKFLOW/機台 dimensions
- Pass Pareto selection (dimension + values) through to list/export endpoints
- Enable TRACE_WORKER_ENABLED=true by default in start_server.sh and .env.example
- Archive reject-history-pareto-datasource-fix and reject-history-pareto-ux-enhancements
- Update reject-history-api and reject-history-page specs with new Pareto behaviors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-03-02 13:23:16 +08:00
parent ff37768a15
commit e83d8e1a36
31 changed files with 1251 additions and 286 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-02

View File

@@ -0,0 +1,59 @@
## Context
報廢歷史查詢的 SQL 基底查詢(`performance_daily.sql` / `performance_daily_lot.sql`)目前有一個 `wip_lookup` CTE`DW_MES_WIP`8000 萬筆)以 `ROW_NUMBER()` + `CONTAINERID` 取最新一筆 WIP 記錄,用於 PJ_TYPE、PRODUCTLINENAME、EQUIPMENTS、WORKFLOWNAME 的 fallback。此做法存在多項問題
1. `DW_MES_WIP` 取的是「最新」WIP 步驟,不是報廢發生當下的步驟
2. PJ_TYPE 和 PRODUCTLINENAME 應直接從 `DW_MES_CONTAINER` 取得
3. EQUIPMENTNAME 若 reject history 沒有值就應留空,不需額外查找
4. WORKFLOWNAME 應從 `DW_MES_LOTWIPHISTORY` 透過 `WIPTRACKINGGROUPKEYID` 精確對應
## Goals / Non-Goals
**Goals:**
- 修正 5 個維度柏拉圖的資料來源,使 package/type/workflow/equipment 正確顯示
- workcenter 柏拉圖維度維持使用 WORKCENTER_GROUP
- WORKFLOW 精確對應到報廢發生當下的 WIP 步驟
**Non-Goals:**
- 不變更前端元件邏輯
- 不變更 API 介面或回應結構
- 不新增篩選維度
## Decisions
### D1: 移除 `wip_lookup` CTE改用直接 LEFT JOIN `DW_MES_LOTWIPHISTORY`
移除整個 `wip_lookup` CTE來自 `DW_MES_WIP`),在 `reject_raw` 的 FROM 區段新增:
```sql
LEFT JOIN DWH.DW_MES_LOTWIPHISTORY lwh
ON lwh.WIPTRACKINGGROUPKEYID = r.WIPTRACKINGGROUPKEYID
```
理由:`DW_MES_LOTREJECTHISTORY``DW_MES_LOTWIPHISTORY` 都有 `WIPTRACKINGGROUPKEYID`,兩邊都有索引,直接 JOIN 即可精確對應到報廢事件所在的 WIP 步驟。不需要 CTE、不需要 ROW_NUMBER、不需要子查詢。
### D2: 各欄位來源
| 欄位 | 來源表 | 寫法 |
|---|---|---|
| PJ_TYPE | DW_MES_CONTAINER | `NVL(TRIM(c.PJ_TYPE), '(NA)')` |
| PRODUCTLINENAME | DW_MES_CONTAINER | `NVL(TRIM(c.PRODUCTLINENAME), '(NA)')` |
| EQUIPMENTNAME | DW_MES_LOTREJECTHISTORY | `NVL(TRIM(r.EQUIPMENTNAME), '(NA)')` |
| PRIMARY_EQUIPMENTNAME | DW_MES_LOTREJECTHISTORY | `NVL(TRIM(REGEXP_SUBSTR(r.EQUIPMENTNAME, '[^,]+', 1, 1)), '(NA)')` |
| WORKFLOWNAME | DW_MES_LOTWIPHISTORY | `NVL(TRIM(lwh.WORKFLOWNAME), '(NA)')` |
| WORKCENTERNAME | spec_map (既有) | `NVL(TRIM(sm.WORK_CENTER), NVL(TRIM(r.WORKCENTERNAME), '(NA)'))` |
| WORKCENTER_GROUP | spec_map (既有) | `NVL(TRIM(sm.WORKCENTER_GROUP), NVL(TRIM(r.WORKCENTERNAME), '(NA)'))` |
### D3: Python 維度映射 workcenter 改回 WORKCENTER_GROUP
`reject_dataset_cache.py``_DIM_TO_DF_COLUMN``reject_history_service.py``_DIMENSION_COLUMN_MAP``"workcenter"` 映射改回 `WORKCENTER_GROUP` / `b.WORKCENTER_GROUP`
### D4: 兩支 SQL 同步修改
`performance_daily.sql``performance_daily_lot.sql` 需同步做相同變更,保持一致。
## Risks / Trade-offs
- `DW_MES_LOTWIPHISTORY` 有 5400 萬筆,但 `WIPTRACKINGGROUPKEYID` 有索引JOIN 效率可控
-`WIPTRACKINGGROUPKEYID` 為 NULL 的 reject 記錄WORKFLOWNAME 會顯示 (NA)——這是正確行為
- `DW_MES_CONTAINER` 的 PJ_TYPE / PRODUCTLINENAME 若為 NULL仍會顯示 (NA)——這代表該 container 確實沒有此資訊

View File

@@ -0,0 +1,28 @@
## Why
報廢歷史柏拉圖的多個維度顯示不正確package、type、equipment 全顯示 (NA)workflow 顯示成 spec nameworkcenter 維度應顯示 WORKCENTER_GROUP。原因是 SQL 資料來源選擇錯誤——目前錯誤地使用 `DW_MES_WIP` 作為 fallback應改為正確使用 `DW_MES_CONTAINER` 取 package/type`DW_MES_LOTWIPHISTORY` 取 workflow透過 `WIPTRACKINGGROUPKEYID` 精確對應報廢事件equipment 不做額外查找。
## What Changes
- 移除 `performance_daily.sql``performance_daily_lot.sql` 中的 `wip_lookup` CTE來自 `DW_MES_WIP`
- 新增 `LEFT JOIN DWH.DW_MES_LOTWIPHISTORY` 透過 `WIPTRACKINGGROUPKEYID` 取得報廢當下對應的 WORKFLOWNAME
- PJ_TYPE、PRODUCTLINENAME 還原為僅從 `DW_MES_CONTAINER` 取得
- EQUIPMENTNAME 還原為僅從 `DW_MES_LOTREJECTHISTORY` 取得(空就空,不額外查找)
- WORKFLOWNAME 改為從 `DW_MES_LOTWIPHISTORY` 取得(精確對應報廢事件的 WIP 步驟)
- 柏拉圖 workcenter 維度映射改回 `WORKCENTER_GROUP`Python service 層)
## Capabilities
### New Capabilities
(none)
### Modified Capabilities
- `reject-history-api`: Dimension Pareto 的 SQL 資料來源變更——移除 DW_MES_WIP fallback改用 DW_MES_LOTWIPHISTORY 取 workflowworkcenter 維度映射改回 WORKCENTER_GROUP
## Impact
- SQL: `performance_daily.sql`, `performance_daily_lot.sql` — CTE 結構變更JOIN 變更
- Python: `reject_dataset_cache.py`, `reject_history_service.py` — 維度映射常數調整
- 無前端變更、無 API 介面變更、無新增依賴

View File

@@ -0,0 +1,42 @@
## MODIFIED Requirements
### Requirement: Reject History SQL base query SHALL source dimension columns from correct tables
The base query (`performance_daily.sql`, `performance_daily_lot.sql`) SHALL source each dimension column from its authoritative table.
#### Scenario: PJ_TYPE sourced from DW_MES_CONTAINER
- **WHEN** the base query resolves PJ_TYPE
- **THEN** it SHALL use `DW_MES_CONTAINER.PJ_TYPE` only
- **THEN** it SHALL NOT fall back to `DW_MES_WIP`
#### Scenario: PRODUCTLINENAME sourced from DW_MES_CONTAINER
- **WHEN** the base query resolves PRODUCTLINENAME (package)
- **THEN** it SHALL use `DW_MES_CONTAINER.PRODUCTLINENAME` only
- **THEN** it SHALL NOT fall back to `DW_MES_WIP`
#### Scenario: EQUIPMENTNAME sourced from DW_MES_LOTREJECTHISTORY only
- **WHEN** the base query resolves EQUIPMENTNAME
- **THEN** it SHALL use `DW_MES_LOTREJECTHISTORY.EQUIPMENTNAME` only
- **THEN** it SHALL NOT perform any additional lookup when the value is NULL
#### Scenario: WORKFLOWNAME sourced from DW_MES_LOTWIPHISTORY via WIPTRACKINGGROUPKEYID
- **WHEN** the base query resolves WORKFLOWNAME
- **THEN** it SHALL LEFT JOIN `DW_MES_LOTWIPHISTORY` on `WIPTRACKINGGROUPKEYID`
- **THEN** it SHALL use `DW_MES_LOTWIPHISTORY.WORKFLOWNAME`
- **THEN** it SHALL NOT fall back to SPECNAME or any other field
#### Scenario: No DW_MES_WIP dependency in base query
- **WHEN** the base query CTEs are examined
- **THEN** there SHALL be no CTE or JOIN referencing `DW_MES_WIP`
### Requirement: Dimension Pareto workcenter dimension SHALL use WORKCENTER_GROUP
The workcenter dimension in Pareto analysis SHALL group by `WORKCENTER_GROUP`, not individual `WORKCENTERNAME`.
#### Scenario: Cache-based Pareto workcenter mapping
- **WHEN** `reject_dataset_cache.py` computes workcenter dimension Pareto
- **THEN** the dimension column SHALL be `WORKCENTER_GROUP`
#### Scenario: SQL-based Pareto workcenter mapping
- **WHEN** `reject_history_service.py` builds workcenter dimension Pareto SQL
- **THEN** the dimension column SHALL be `b.WORKCENTER_GROUP`

View File

@@ -0,0 +1,22 @@
## 1. SQL 基底查詢修正 — performance_daily.sql
- [x] 1.1 移除 `wip_lookup` CTE整個 DW_MES_WIP 相關區段)
- [x] 1.2 新增 `LEFT JOIN DWH.DW_MES_LOTWIPHISTORY lwh ON lwh.WIPTRACKINGGROUPKEYID = r.WIPTRACKINGGROUPKEYID`
- [x] 1.3 PJ_TYPE 還原為 `NVL(TRIM(c.PJ_TYPE), '(NA)')`
- [x] 1.4 PRODUCTLINENAME 還原為 `NVL(TRIM(c.PRODUCTLINENAME), '(NA)')`
- [x] 1.5 EQUIPMENTNAME 還原為 `NVL(TRIM(r.EQUIPMENTNAME), '(NA)')`PRIMARY_EQUIPMENTNAME 同步還原
- [x] 1.6 WORKFLOWNAME 改為 `NVL(TRIM(lwh.WORKFLOWNAME), '(NA)')`
## 2. SQL 基底查詢修正 — performance_daily_lot.sql
- [x] 2.1 移除 `wip_lookup` CTE
- [x] 2.2 新增 `LEFT JOIN DWH.DW_MES_LOTWIPHISTORY lwh ON lwh.WIPTRACKINGGROUPKEYID = r.WIPTRACKINGGROUPKEYID`
- [x] 2.3 PJ_TYPE 還原為 `NVL(TRIM(c.PJ_TYPE), '(NA)')`
- [x] 2.4 PRODUCTLINENAME 還原為 `NVL(TRIM(c.PRODUCTLINENAME), '(NA)')`
- [x] 2.5 EQUIPMENTNAME 還原為 `NVL(TRIM(r.EQUIPMENTNAME), '(NA)')`PRIMARY_EQUIPMENTNAME 同步還原
- [x] 2.6 WORKFLOWNAME 改為 `NVL(TRIM(lwh.WORKFLOWNAME), '(NA)')`
## 3. Python 維度映射修正
- [x] 3.1 `reject_dataset_cache.py` `_DIM_TO_DF_COLUMN` — workcenter 改回 `WORKCENTER_GROUP`
- [x] 3.2 `reject_history_service.py` `_DIMENSION_COLUMN_MAP` — workcenter 改回 `b.WORKCENTER_GROUP`

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-03-02

View File

@@ -0,0 +1,3 @@
# reject-history-pareto-ux-enhancements
Add pareto display scope options, multi-select drill-down, and filtered detail CSV export

View File

@@ -0,0 +1,73 @@
## Context
Reject History 已改為兩階段查詢:`/api/reject-history/query` 先查 Oracle 並快取 DataFrame`/api/reject-history/view``/api/reject-history/export-cached` 在快取資料上做補充篩選。現況存在三個 UX/一致性缺口:
1. `TYPE/WORKFLOW/機台` 維度在 80% 範圍下仍可能過多,不易閱讀。
2. Pareto 點選目前僅支援單選原因reason不支援多選且不支援其他維度。
3. 匯出需要和畫面篩選完全一致,但目前缺少 Pareto 多選情境的等價參數傳遞與後端套用。
本變更跨前端 VueFilterPanel/ParetoSection/App與後端 Flask+Pandasroutes/cache service屬跨模組一致性調整。
## Goals / Non-Goals
**Goals:**
-`TYPE/WORKFLOW/機台` 維度提供 Pareto 顯示範圍切換(`全部顯示` / `只顯示 TOP 20`)。
- 將「Pareto 僅顯示累計前 80%」控制移到補充篩選區域,維持預設啟用。
- Pareto 圖表與表格都支援多選,並即時聯動刷新明系列表。
- 匯出 CSV 套用完整篩選上下文主查詢、補充篩選、互動篩選、Pareto 多選)。
- 保持 UTF-8 BOM 與標準 CSV escaping。
**Non-Goals:**
- 不改動 Oracle SQL schema 與資料來源邏輯。
- 不新增新資料表或 Redis key 結構。
- 不重做 Reject History 整體版面,只做既有模組行為擴充。
## Decisions
### 1) Pareto 多選狀態由 App.vue 集中管理
- Decision: 新增 `selectedParetoValues`array`paretoDisplayScope``all`/`top20`)於 `App.vue`
- Why: 既有趨勢日期、補充篩選、明細分頁都由 App 協調;將 Pareto 多選納入同一狀態中心可確保 URL、view、export 一致。
- Alternative considered:
-`ParetoSection.vue` 內部持有選取狀態:會造成匯出與後端參數組裝需要額外同步機制,易出現狀態漂移。
### 2) `TOP 20` 僅在前端呈現層裁切
- Decision: 後端仍回傳完整(或 top80Pareto items`TOP 20` 由前端 computed 再切片。
- Why: `TOP 20` 是視覺呈現策略,不是資料語意;放前端可避免增加 API 分支與快取鍵複雜度。
- Alternative considered:
- API 增加 `display_scope=top20`:可行但會讓同一資料語意被多組 API 參數切分,且對快取命中率不利。
### 3) Pareto 多選篩選在後端 cache service 統一套用
- Decision: `apply_view()``export_csv_from_cache()` 新增 `pareto_dimension` + `pareto_values` 參數,透過共享 helper 套用到對應欄位。
- Why: 明細畫面與匯出都要使用「同一過濾函式」才能保證 parity。
- Alternative considered:
- 前端先過濾當頁明細再匯出:無法涵蓋全資料集,且分頁資料可能不完整。
### 4) 80% toggle 位置調整但語意不變
- Decision: checkbox UI 從主工具列移至補充篩選區塊;預設值與 URL 參數(`pareto_scope_all`)維持既有相容。
- Why: 80% 為二階段視圖篩選,移入補充篩選可降低使用者誤解。
## Risks / Trade-offs
- [Risk] 多選維度欄位映射錯誤(特別是 `equipment` 對應欄位)導致篩選失準。
→ Mitigation: 單元測試覆蓋各維度映射與無效維度 400。
- [Risk] 前端多種互動趨勢日期、reason、pareto 多選)同時作用時狀態難追蹤。
→ Mitigation: `activeFilterChips` 顯示所有活躍條件,並統一經 `refreshView()` + `updateUrlState()`
- [Risk] CSV 匯出和列表排序/篩選不一致造成信任問題。
→ Mitigation: 匯出重用與 view 相同 filter helper並新增 route/service parity 測試。
## Migration Plan
1. 先落地後端 `apply_view/export_csv_from_cache` 共同 Pareto 多選過濾與參數驗證。
2. 再調整前端控制項與事件多選、TOP20、補充篩選區
3. 補上 route/service 單元測試。
4. 驗證目標:`reject-history` 相關測試通過,手動檢查 CSV 編碼與欄位。
Rollback strategy:
- 若上線後出現篩選偏差,可暫時忽略 `pareto_dimension/pareto_values` 參數(後端回退到舊邏輯),不影響既有查詢主路徑。
## Open Questions
- `TOP 20` 是否僅限定 `TYPE/WORKFLOW/機台`(本次採用是),或未來要擴展到全部維度?
- 多選 Pareto 與補充篩選中的 `reason` 同時存在時,是否需要 UI 顯示「交集」提示(本次先不新增提示文案)。

View File

@@ -0,0 +1,26 @@
## Why
目前 Reject History 柏拉圖在 `TYPE``WORKFLOW``機台` 維度上,僅靠累計 80% 顯示時仍可能出現過多項目,影響閱讀與分析效率;同時圖表點選與明細匯出尚未完整對齊,造成追查流程不連續。需要補強互動式篩選與匯出一致性,讓使用者可直接從柏拉圖一路鑽取到可交付的明細結果。
## What Changes
- 在柏拉圖 `TYPE``WORKFLOW``機台` 三個維度新增顯示範圍選項:`全部顯示``只顯示 TOP 20`
- 將「Pareto 僅顯示累計前 80%」移入「補充篩選」區域,並維持預設啟用。
- 柏拉圖支援點選項目多選bar/table並同步套用到下方明系列表。
- 明系列表新增 `匯出 CSV`匯出內容必須與當前明細可見結果完全一致套用主篩選、補充篩選、Pareto 點選篩選、排序/分頁語意)。
- 匯出 CSV 強化字元編碼與欄位轉義處理,避免中文亂碼與欄位錯位。
## Capabilities
### New Capabilities
- `reject-history-detail-export-parity`: 明細匯出與畫面篩選完全一致的 CSV 匯出能力
### Modified Capabilities
- `reject-history-page`: 擴充柏拉圖顯示範圍控制、補充篩選位置調整、Pareto 多選聯動明細
- `reject-history-api`: 匯出端點需保證套用所有有效篩選(含 Pareto 衍生篩選),並提供穩定 CSV 編碼輸出
## Impact
- Frontend: `src/pages/reject-history`FilterPanel、ParetoSection、DetailTable、頁面狀態管理與查詢參數組裝
- Backend/API: reject-history list/export 查詢參數解析與 CSV 產生流程
- Tests: 補齊 page 互動測試(多選/聯動/顯示範圍)與 API 匯出一致性測試filter parity、encoding

View File

@@ -0,0 +1,19 @@
## MODIFIED Requirements
### Requirement: Reject History API SHALL provide CSV export endpoint
The API SHALL provide CSV export using the same filter and metric semantics as list/query APIs.
#### Scenario: Export payload consistency
- **WHEN** `GET /api/reject-history/export` is called with valid filters
- **THEN** CSV headers SHALL include both `REJECT_TOTAL_QTY` and `DEFECT_QTY`
- **THEN** export rows SHALL follow the same semantic definitions as summary/list endpoints
#### Scenario: Cached export supports full detail-filter parity
- **WHEN** `GET /api/reject-history/export-cached` is called with an existing `query_id`
- **THEN** the endpoint SHALL apply primary policy toggles, supplementary filters, trend-date filters, metric filter, and Pareto-selected item filters
- **THEN** returned rows SHALL match the same filtered detail dataset semantics used by `GET /api/reject-history/view`
#### Scenario: CSV encoding and escaping are stable
- **WHEN** either export endpoint returns CSV
- **THEN** response charset SHALL be `utf-8-sig`
- **THEN** values containing commas, quotes, or newlines SHALL be CSV-escaped correctly

View File

@@ -0,0 +1,18 @@
## ADDED Requirements
### Requirement: Cached reject-history export SHALL support Pareto multi-select filter parity
The cached export endpoint SHALL support Pareto multi-select context so that exported rows match the currently drilled-down detail scope.
#### Scenario: Apply selected Pareto dimension values
- **WHEN** export request provides `pareto_dimension` and one or more `pareto_values`
- **THEN** the backend SHALL apply an OR-match filter against the mapped dimension column
- **THEN** only rows matching selected values SHALL be exported
#### Scenario: No Pareto selection keeps existing behavior
- **WHEN** `pareto_values` is absent or empty
- **THEN** export SHALL apply no extra Pareto-selected-item filter
- **THEN** existing supplementary and interactive filters SHALL still apply
#### Scenario: Invalid Pareto dimension is rejected
- **WHEN** `pareto_dimension` is not one of supported dimensions
- **THEN** API SHALL return HTTP 400 with descriptive validation error

View File

@@ -0,0 +1,43 @@
## MODIFIED Requirements
### Requirement: Reject History page SHALL provide reason Pareto analysis
The page SHALL provide a Pareto view for loss reasons and support downstream filtering.
#### Scenario: Pareto rendering and ordering
- **WHEN** Pareto data is loaded
- **THEN** items SHALL be sorted by selected metric descending
- **THEN** a cumulative percentage line SHALL be shown
#### Scenario: Pareto 80% filter is managed in supplementary filters
- **WHEN** the page first loads Pareto
- **THEN** supplementary filters SHALL include "Pareto 僅顯示累計前 80%" control
- **THEN** the control SHALL default to enabled
#### Scenario: TYPE/WORKFLOW/機台 support display scope selector
- **WHEN** Pareto dimension is `TYPE`, `WORKFLOW`, or `機台`
- **THEN** the UI SHALL provide `全部顯示` and `只顯示 TOP 20` options
- **THEN** `全部顯示` SHALL still respect the current 80% cumulative filter setting
#### Scenario: Pareto click filtering supports multi-select
- **WHEN** user clicks Pareto bars or table rows
- **THEN** clicked items SHALL become active selected chips
- **THEN** multiple selected items SHALL be supported at the same time
- **THEN** detail list SHALL reload using current selected Pareto items as additional filter criteria
#### Scenario: Re-click clears selected item only
- **WHEN** user clicks an already selected Pareto item
- **THEN** only that item SHALL be removed from selection
- **THEN** remaining selected items SHALL stay active
### Requirement: Reject History page SHALL support CSV export from current filter context
The page SHALL allow users to export records using the exact active filters.
#### Scenario: Export with all active filters
- **WHEN** user clicks "匯出 CSV"
- **THEN** export request SHALL include current primary filters, supplementary filters, trend-date filters, metric filters, and Pareto-selected items
- **THEN** downloaded file SHALL contain exactly the same rows currently represented by the detail list filter context
#### Scenario: Export remains UTF-8 Excel compatible
- **WHEN** CSV export is downloaded
- **THEN** the file SHALL be encoded in UTF-8 with BOM
- **THEN** Chinese headers and values SHALL render correctly in common spreadsheet tools

View File

@@ -0,0 +1,19 @@
## 1. Frontend Pareto UX Enhancements
- [x] 1.1 在 `FilterPanel.vue` 將「Pareto 僅顯示累計前 80%」移至補充篩選區域並維持預設啟用
- [x] 1.2 在 `ParetoSection.vue` 新增 `全部顯示 / 只顯示 TOP 20` 控制(僅 `TYPE/WORKFLOW/機台` 顯示)
- [x] 1.3 在 `ParetoSection.vue` 支援圖表與表格點選多選、選取高亮與取消選取
- [x] 1.4 在 `App.vue` 新增 Pareto 多選狀態管理與 URL 狀態同步dimension + selected values + display scope
## 2. Backend Filter/Export Parity
- [x] 2.1 在 `reject_dataset_cache.py` 新增 Pareto 維度多選過濾 helper供 view/export 共用
- [x] 2.2 擴充 `apply_view()` 支援 `pareto_dimension` + `pareto_values` 並套用到明細過濾
- [x] 2.3 擴充 `export_csv_from_cache()` 支援與 view 相同的 Pareto 多選過濾語意
- [x] 2.4 更新 `reject_history_routes.py``/view``/export-cached` 參數解析與維度驗證(非法維度回 400
## 3. Validation and Regression Tests
- [x] 3.1 新增/更新 route 測試:驗證 `/view``/export-cached` 會傳遞 Pareto 多選參數且非法維度回 400
- [x] 3.2 新增/更新 cache service 測試:驗證 Pareto 多選在 `apply_view``export_csv_from_cache` 行為一致
- [x] 3.3 執行 reject-history 相關測試並確認無回歸

View File

@@ -87,6 +87,16 @@ The API SHALL provide CSV export using the same filter and metric semantics as l
- **THEN** CSV headers SHALL include both `REJECT_TOTAL_QTY` and `DEFECT_QTY`
- **THEN** export rows SHALL follow the same semantic definitions as summary/list endpoints
#### Scenario: Cached export supports full detail-filter parity
- **WHEN** `GET /api/reject-history/export-cached` is called with an existing `query_id`
- **THEN** the endpoint SHALL apply primary policy toggles, supplementary filters, trend-date filters, metric filter, and Pareto-selected item filters
- **THEN** returned rows SHALL match the same filtered detail dataset semantics used by `GET /api/reject-history/view`
#### Scenario: CSV encoding and escaping are stable
- **WHEN** either export endpoint returns CSV
- **THEN** response charset SHALL be `utf-8-sig`
- **THEN** values containing commas, quotes, or newlines SHALL be CSV-escaped correctly
### Requirement: Reject History API SHALL centralize SQL in reject_history SQL directory
The service SHALL load SQL from dedicated files under `src/mes_dashboard/sql/reject_history/`.

View File

@@ -0,0 +1,22 @@
# reject-history-detail-export-parity Specification
## Purpose
TBD - created by archiving change reject-history-pareto-ux-enhancements. Update Purpose after archive.
## Requirements
### Requirement: Cached reject-history export SHALL support Pareto multi-select filter parity
The cached export endpoint SHALL support Pareto multi-select context so that exported rows match the currently drilled-down detail scope.
#### Scenario: Apply selected Pareto dimension values
- **WHEN** export request provides `pareto_dimension` and one or more `pareto_values`
- **THEN** the backend SHALL apply an OR-match filter against the mapped dimension column
- **THEN** only rows matching selected values SHALL be exported
#### Scenario: No Pareto selection keeps existing behavior
- **WHEN** `pareto_values` is absent or empty
- **THEN** export SHALL apply no extra Pareto-selected-item filter
- **THEN** existing supplementary and interactive filters SHALL still apply
#### Scenario: Invalid Pareto dimension is rejected
- **WHEN** `pareto_dimension` is not one of supported dimensions
- **THEN** API SHALL return HTTP 400 with descriptive validation error

View File

@@ -77,25 +77,30 @@ The page SHALL show both quantity trend and rate trend to avoid mixing unit scal
The page SHALL provide a Pareto view for loss reasons and support downstream filtering.
#### Scenario: Pareto rendering and ordering
- **WHEN** reason Pareto data is loaded
- **WHEN** Pareto data is loaded
- **THEN** items SHALL be sorted by selected metric descending
- **THEN** a cumulative percentage line SHALL be shown
#### Scenario: Default 80% cumulative display mode
#### Scenario: Pareto 80% filter is managed in supplementary filters
- **WHEN** the page first loads Pareto
- **THEN** it SHALL default to "only cumulative top 80%" mode
- **THEN** Pareto SHALL only render categories within the cumulative 80% threshold under current filters
- **THEN** supplementary filters SHALL include "Pareto 僅顯示累計前 80%" control
- **THEN** the control SHALL default to enabled
#### Scenario: Full Pareto toggle mode
- **WHEN** user turns OFF the 80% cumulative display mode
- **THEN** Pareto SHALL render all categories after applying current filters
- **THEN** switching mode SHALL NOT reset existing time/reason/workcenter-group filters
#### Scenario: TYPE/WORKFLOW/機台 support display scope selector
- **WHEN** Pareto dimension is `TYPE`, `WORKFLOW`, or `機台`
- **THEN** the UI SHALL provide `全部顯示` and `只顯示 TOP 20` options
- **THEN** `全部顯示` SHALL still respect the current 80% cumulative filter setting
#### Scenario: Pareto click filtering
- **WHEN** user clicks a Pareto bar or row
- **THEN** the selected reason SHALL become an active filter chip
- **THEN** detail list SHALL reload with that reason
- **THEN** clicking the same reason again SHALL clear the reason filter
#### Scenario: Pareto click filtering supports multi-select
- **WHEN** user clicks Pareto bars or table rows
- **THEN** clicked items SHALL become active selected chips
- **THEN** multiple selected items SHALL be supported at the same time
- **THEN** detail list SHALL reload using current selected Pareto items as additional filter criteria
#### Scenario: Re-click clears selected item only
- **WHEN** user clicks an already selected Pareto item
- **THEN** only that item SHALL be removed from selection
- **THEN** remaining selected items SHALL stay active
### Requirement: Reject History page SHALL show paginated detail rows
The page SHALL provide a paginated detail table for investigation and traceability.
@@ -112,10 +117,15 @@ The page SHALL provide a paginated detail table for investigation and traceabili
### Requirement: Reject History page SHALL support CSV export from current filter context
The page SHALL allow users to export records using the exact active filters.
#### Scenario: Export with current filters
#### Scenario: Export with all active filters
- **WHEN** user clicks "匯出 CSV"
- **THEN** export request SHALL include the current filter state and active reason filter
- **THEN** downloaded file SHALL contain both `REJECT_TOTAL_QTY` and `DEFECT_QTY`
- **THEN** export request SHALL include current primary filters, supplementary filters, trend-date filters, metric filters, and Pareto-selected items
- **THEN** downloaded file SHALL contain exactly the same rows currently represented by the detail list filter context
#### Scenario: Export remains UTF-8 Excel compatible
- **WHEN** CSV export is downloaded
- **THEN** the file SHALL be encoded in UTF-8 with BOM
- **THEN** Chinese headers and values SHALL render correctly in common spreadsheet tools
### Requirement: Reject History page SHALL provide robust feedback states
The page SHALL provide loading, empty, and error states without breaking interactions.