feat(reject-history): ship report page and archive openspec change
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-13
|
||||
@@ -0,0 +1,169 @@
|
||||
## Context
|
||||
|
||||
目前 `query-tool` 僅提供單點查詢 reject 資訊,沒有針對歷史趨勢、原因分布與績效指標的完整頁面。`DW_MES_LOTREJECTHISTORY` 存在同一 `HISTORYMAINLINEID` 對應多筆原因紀錄的特性,直接彙總 `MOVEINQTY` 會造成分母膨脹,讓報廢率失真。另一方面,現有語意中「reject 五欄合計」與 `DEFECTQTY` 曾被混用,導致跨頁面解讀不一致。新增可用資料表 `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE` 也帶來新政策需求:`ENABLE_FLAG='Y'` 的報廢原因應預設不納入良率計算,並允許使用者切換是否納入。
|
||||
|
||||
此變更需要跨越前端(新報表頁)、後端(新 API + service + SQL)、與治理層(route contract、drawer/page registry、coverage test),屬於跨模組整合型設計。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 建立 `/reject-history` 專用報表頁,提供篩選、KPI、趨勢、Pareto、明細與匯出。
|
||||
- 固化兩條指標語義並列:
|
||||
- 扣帳報廢 `REJECT_TOTAL_QTY`
|
||||
- 不扣帳報廢 `DEFECT_QTY`
|
||||
- 將 `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE`(`ENABLE_FLAG='Y'`)納入良率排除政策,且提供 UI/API 可切換納入模式。
|
||||
- 以事件層級去重 `MOVEIN_QTY`,避免因 `HISTORYMAINLINEID` 多筆造成比率失真。
|
||||
- 完整納入現有 pure Vite + portal-shell + route contract 治理流程。
|
||||
- 在視覺上清楚區分 reject 與 defect,避免操作端誤判。
|
||||
|
||||
**Non-Goals:**
|
||||
- 不改寫既有 `query-tool` 與其 API 欄位契約。
|
||||
- 不新增第三方套件(沿用 Vue 3、ECharts、Flask、SQLLoader)。
|
||||
- 不在此變更內建立新的資料倉儲實體表(先以查詢層與 service 聚合實作)。
|
||||
- 不重構非 reject-history 相關頁面的 UI 風格。
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: 以單一「日粒度基底查詢」作為 API 共同資料來源
|
||||
|
||||
**Decision:** 以 `src/mes_dashboard/sql/reject_history/performance_daily.sql` 作為基底查詢,summary/trend/pareto/list/export 都由 service 在同一語義上二次聚合。
|
||||
|
||||
**Why:**
|
||||
- 可避免各 endpoint 重複實作計算邏輯造成語意漂移。
|
||||
- 已在 SQL 中明確定義五欄合計與 `DEFECTQTY` 分離,符合業務規則。
|
||||
- 可先解決語義一致性,再視流量評估是否做 materialization。
|
||||
|
||||
**Alternatives considered:**
|
||||
- 各 API 各自寫 SQL:開發快但長期高風險,易出現欄位定義不一致。
|
||||
- 先建資料表再查詢:效能可控但前置成本高,超出本次提案節奏。
|
||||
|
||||
### D2: `MOVEIN_QTY` 以事件去重策略統一計算
|
||||
|
||||
**Decision:** 以 `HISTORYMAINLINEID` 為首選去重鍵;若缺值,退回 `CONTAINERID + TXNDATE(second) + SPECID` 組合鍵;只對事件首筆計入 `MOVEIN_QTY`。
|
||||
|
||||
**Why:**
|
||||
- 原始資料存在一對多原因拆分,若不去重分母會被重複計算。
|
||||
- 去重規則集中在基底 SQL,前後端不用再各自補丁。
|
||||
|
||||
**Alternatives considered:**
|
||||
- 以 `CONTAINERID` 去重:會誤合併同 lot 不同事件。
|
||||
- 不去重:報表比率不可用。
|
||||
|
||||
### D3: API 採「頁面導向」端點設計
|
||||
|
||||
**Decision:** 提供 `summary`、`trend`、`reason-pareto`、`list`、`export` 五個端點,參數模型一致(日期 + 維度過濾),由 route 層統一做驗證與邊界控制。
|
||||
|
||||
**Why:**
|
||||
- 前端可平行載入、獨立重刷局部區塊。
|
||||
- 可針對高成本端點(list/export)單獨做 rate limit。
|
||||
- 與 `hold-history`/`resource-history` 現有模式一致。
|
||||
|
||||
### D4: 前端視覺採「雙軸語義敘事」布局
|
||||
|
||||
**Decision:** 在單頁內明確分離「扣帳報廢」與「不扣帳報廢」兩條視覺敘事線,避免混讀。
|
||||
|
||||
**Visual structure:**
|
||||
- Header:漸層標題區,顯示頁名、資料更新時間、語義說明 badge(扣帳/不扣帳)。
|
||||
- Filter Card:至少提供三個核心篩選器並含查詢/清除動作:
|
||||
- 時間篩選(`start_date`/`end_date`)
|
||||
- 原因篩選(`LOSSREASONNAME`)
|
||||
- `WORKCENTER_GROUP` 篩選(沿用既有頁面篩選體驗與資料來源)
|
||||
- KPI Row(8 卡):`MOVEIN_QTY`、`REJECT_TOTAL_QTY`、`DEFECT_QTY`、兩種 rate、`REJECT_SHARE_PCT`、受影響 lot/workorder。
|
||||
- Trend Row:
|
||||
- 左圖:`REJECT_TOTAL_QTY` vs `DEFECT_QTY`(量)
|
||||
- 右圖:`REJECT_RATE_PCT` vs `DEFECT_RATE_PCT`(率)
|
||||
- Pareto + Detail:報廢量 vs 報廢原因 Pareto(支援 metric mode)與可分頁明細表。
|
||||
- Pareto 預設啟用「僅顯示累計前 80%」模式(以目前篩選後資料集計算)。
|
||||
- 提供開關切換完整 Pareto;關閉前 80% 模式時,顯示篩選後的全部原因。
|
||||
- 視覺與互動可參考 `WIP OVER VIEW` 既有 Pareto 呈現方式,保持使用者認知一致。
|
||||
|
||||
**Visual semantics:**
|
||||
- Reject(扣帳)使用暖色語義(紅/橘系)
|
||||
- Defect(不扣帳)使用冷色語義(藍/青系)
|
||||
- `MOVEIN_QTY`、總計與背景採中性灰藍語義
|
||||
- 互動態(hover/active/filter chip)沿用既有 shared style token,確保與現有報表視覺一致
|
||||
|
||||
**Alternatives considered:**
|
||||
- 只保留單圖單指標:無法傳達兩種語義並列。
|
||||
- 新建完全獨立視覺主題:與既有 portal 風格落差大、維運成本高。
|
||||
|
||||
### D5: 新頁採 pure Vite entry + Flask static route + shell native loader
|
||||
|
||||
**Decision:** `frontend/src/reject-history/index.html` 作為 entry,build 後由 Flask `send_from_directory` 提供 `/reject-history`,並註冊到 `routeContracts` 與 `nativeModuleRegistry`。
|
||||
|
||||
**Why:**
|
||||
- 與既有 in-scope page 架構一致,降低整合風險。
|
||||
- 保留 shell canonical route 與 direct-entry 相容策略。
|
||||
|
||||
### D6: 匯出欄位以「語義明示」優先於歷史相容命名
|
||||
|
||||
**Decision:** CSV 欄位明確輸出 `REJECT_TOTAL_QTY` 與 `DEFECT_QTY`,並可附五個 reject 組成欄位;不使用易混淆別名。
|
||||
|
||||
**Why:**
|
||||
- 報表是對外分析依據,語義清晰優先於短期縮寫便利。
|
||||
- 與 field-name-consistency 治理要求一致。
|
||||
|
||||
### D7: 納入「不計良率報廢」政策並提供可切換模式
|
||||
|
||||
**Decision:** 以 `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE` 中 `ENABLE_FLAG='Y'` 為政策清單,預設排除該類報廢於良率相關計算,並新增 `include_excluded_scrap` 參數(預設 `false`)讓使用者可選擇納入。
|
||||
|
||||
**Policy scope:**
|
||||
- 影響 summary/trend/reason-pareto/list/export 的同一套查詢語義。
|
||||
- 預設模式下,標記為排除的報廢原因不進入良率計算;切換納入後,回到完整資料集計算。
|
||||
- API 回應 `meta` 顯示目前是否啟用排除政策,前端在 filter 區顯示「納入不計良率報廢」切換開關。
|
||||
|
||||
**Why:**
|
||||
- 業務規則已明示 `ENABLE_FLAG='Y'` 代表不納入良率計算,應在報表層落地。
|
||||
- 提供切換能兼顧日常看板(排除模式)與稽核追查(納入模式)。
|
||||
|
||||
**Alternatives considered:**
|
||||
- 永久硬排除:簡單但不利追溯與跨單位對帳。
|
||||
- 完全不排除:違反既有良率定義。
|
||||
|
||||
### D8: 排除清單採「L2 Redis + L1 記憶體」每日全表快取
|
||||
|
||||
**Decision:** 新增 `scrap_reason_exclusion_cache` 模組,採現有快取分層模式:Redis 作為跨 worker 共用快取(L2),process memory 作為快速讀取層(L1);每日全表刷新一次,啟動時先載入。
|
||||
|
||||
**Refresh policy:**
|
||||
- 啟動時進行首次載入。
|
||||
- 每 24 小時刷新一次(可由環境變數覆寫)。
|
||||
- Redis 不可用時,自動退化為 in-memory 快取,並在健康檢查/日誌揭露降級狀態。
|
||||
|
||||
**Why:**
|
||||
- 表筆數小(目前約 36 筆),適合全表快取,不需每次 query join DB。
|
||||
- 共享式 Redis 可避免多 gunicorn worker 間資料不一致。
|
||||
- 延續專案既有快取策略,降低維運認知成本。
|
||||
|
||||
**Alternatives considered:**
|
||||
- 僅記憶體快取:實作最簡單,但多 worker 會各自持有版本。
|
||||
- 每次即時查表:邏輯單純,但額外增加 Oracle 往返成本。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[基底 SQL 單一來源造成查詢負載偏高]** → 先以日期與維度條件收斂、list/export 加 rate limit,必要時再追加快取或物化。
|
||||
- **[使用者沿用舊語意理解 defect]** → UI 顯示語義說明 badge + tooltip,匯出欄位採顯式命名。
|
||||
- **[Pareto 指標切換造成理解成本]** → 預設以 `REJECT_TOTAL_QTY` 顯示,並保留清楚的 toggle label。
|
||||
- **[報廢原因對應鍵格式不一致]** → 在 service 層加入 reason normalization 規則(trim/大小寫一致化,必要時切取代碼前綴),並在測試覆蓋。
|
||||
- **[排除政策切換導致跨報表數值差異爭議]** → API/前端都回傳並顯示 `include_excluded_scrap` 狀態與政策提示文字。
|
||||
- **[Redis 不可用導致快取行為不一致]** → 採 L1 fallback,並透過 health/admin 狀態揭露快取降級。
|
||||
- **[路由治理漏登記導致 shell 無法導航]** → contract parity test + page_status 驗證列為必做任務。
|
||||
- **[明細資料量大造成前端卡頓]** → 後端分頁、預設 `per_page=50`,並避免一次性全量載入。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 建立後端 SQL/service/routes(先讓 API 可單獨驗證)。
|
||||
2. 建立 `scrap_reason_exclusion_cache`(全表快取 + 每日刷新 + fallback)。
|
||||
3. 建立前端 reject-history 頁面與元件(先接 summary/trend,再接 pareto/list/export)。
|
||||
4. 整合 shell 治理資產:`routeContracts`、`nativeModuleRegistry`、`page_status`、Flask page route。
|
||||
5. 補齊測試:service、routes、cache、route-contract parity、前端 smoke。
|
||||
6. 先以 `dev` 狀態上線到抽屜,完成 UAT 後調整為 `released`。
|
||||
|
||||
**Rollback strategy:**
|
||||
- 將 `/reject-history` 從 page registry 標記為隱藏或 `dev` 並停用導航入口。
|
||||
- 保留已上線的既有頁面與 API,不影響既有報表路徑。
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 趨勢圖預設粒度是否固定「日」,或需同頁支援週/月切換?
|
||||
- Pareto 預設排序基準是否固定 `REJECT_TOTAL_QTY`,是否要允許切換為 `DEFECT_QTY`?
|
||||
- 匯出是否要同時提供「彙總版」與「明細版」兩種檔案型態,或先只提供明細版?
|
||||
@@ -0,0 +1,60 @@
|
||||
## Why
|
||||
|
||||
目前專案僅在 `query-tool` 提供偏即時/點查型的報廢資訊,缺少可追蹤趨勢與績效的「報廢歷史」專用報表。資料評估也顯示 `DW_MES_LOTREJECTHISTORY` 同一 `HISTORYMAINLINEID` 會對應多筆原因紀錄,若直接加總 `MOVEINQTY` 會造成分母重複、報廢率失真;同時既有查詢對 reject/defect 命名語義不一致,容易誤解指標。另 IT 新開放 `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE`,其中 `ENABLE_FLAG='Y'` 代表「不納入良率計算」報廢原因,提案需把此政策納入計算流程。現在應在既有 portal-shell + Vite + route contract 架構下,建立一個語義明確且可治理的歷史報表頁。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增 `報廢歷史查詢` 頁面路由 `/reject-history`,採用既有 pure Vite + portal-shell native route 模式,納入抽屜導航與頁面治理。
|
||||
- 新增後端 `reject-history` API 群組(摘要 KPI、日/週趨勢、原因 Pareto、明細、匯出),提供前端報表所需資料。
|
||||
- 新增 `reject-history` service + SQL 模組,建立一致指標定義並明確拆分兩條指標線:
|
||||
- 扣帳報廢:`REJECT_TOTAL_QTY = REJECTQTY + STANDBYQTY + QTYTOPROCESS + INPROCESSQTY + PROCESSEDQTY`
|
||||
- 不扣帳報廢:`DEFECT_QTY = DEFECTQTY`
|
||||
- 以事件層級去重規則處理分母(`MOVEIN_QTY` 以 `HISTORYMAINLINEID` 為主鍵去重),避免多原因拆單導致比率失真。
|
||||
- 納入 `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE` 政策(`ENABLE_FLAG='Y'`):
|
||||
- 預設排除「不納入良率計算」報廢原因
|
||||
- 提供可選開關讓使用者決定是否納入該類報廢
|
||||
- 新增排除清單全表快取(每日一次)機制,採共享快取優先策略,降低每次查詢重複讀表成本。
|
||||
- 明確定義 UI/API/匯出欄位語義,避免沿用「defect=五欄合計」這類歷史命名混淆,確保報表對外語意一致。
|
||||
- 不變更既有 `query-tool` 現有頁面行為與既有 API 回應欄位(此變更先聚焦新頁能力)。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `reject-history-page`: 新增報廢歷史查詢頁面,提供篩選、KPI、趨勢、原因分析、明細查詢與匯出。
|
||||
- `reject-history-api`: 新增報廢歷史 API 能力與資料聚合邏輯,支援報表層的摘要、趨勢、Pareto、明細資料來源。
|
||||
- `reject-metric-semantics`: 新增 reject/defect 指標語義規範,要求五個 reject 欄位合計與 `DEFECTQTY` 必須分開呈現、分開計算、分開命名。
|
||||
- `reject-yield-exclusion-policy`: 新增「不納入良率計算」政策能力,依 `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE.ENABLE_FLAG='Y'` 控制預設排除,並支援使用者切換是否納入。
|
||||
|
||||
### Modified Capabilities
|
||||
- `unified-shell-route-coverage`: 新增 `/reject-history` 後,路由契約清單與前後端契約對照規則需同步更新。
|
||||
- `vue-vite-page-architecture`: 新頁面需納入 Vite entry/output 與 Flask static HTML 服務規範,保持既有純 Vite 頁治理一致性。
|
||||
- `field-name-consistency`: reject/defect 相關欄位在 UI、API、匯出命名需維持一致語義,避免跨頁面誤用。
|
||||
|
||||
## Impact
|
||||
|
||||
- 前端:
|
||||
- 新增 `frontend/src/reject-history/`(`App.vue`、`main.js`/`index.html`、components、composables、style)
|
||||
- 更新 `frontend/src/portal-shell/nativeModuleRegistry.js`
|
||||
- 更新 `frontend/src/portal-shell/routeContracts.js`
|
||||
- 更新 `frontend/vite.config.js`
|
||||
- 後端:
|
||||
- 新增 `src/mes_dashboard/routes/reject_history_routes.py`
|
||||
- 新增 `src/mes_dashboard/services/reject_history_service.py`
|
||||
- 新增 `src/mes_dashboard/services/scrap_reason_exclusion_cache.py`
|
||||
- 新增 `src/mes_dashboard/sql/reject_history/*.sql`
|
||||
- 更新 `src/mes_dashboard/routes/__init__.py`
|
||||
- 更新 `src/mes_dashboard/app.py`(`/reject-history` 靜態頁 route)
|
||||
- 導航/治理:
|
||||
- 更新 `data/page_status.json`(drawer 與頁面可見性)
|
||||
- 更新 shell route contract 對應治理資產與測試基準
|
||||
- 測試:
|
||||
- 新增 `tests/test_reject_history_service.py`
|
||||
- 新增 `tests/test_reject_history_routes.py`
|
||||
- 新增 `tests/test_scrap_reason_exclusion_cache.py`
|
||||
- 補充 route coverage / contract parity / e2e smoke
|
||||
- 資料語義:
|
||||
- 報表需同時呈現 `REJECT_TOTAL_QTY`(扣帳報廢)與 `DEFECT_QTY`(不扣帳報廢)
|
||||
- `ENABLE_FLAG='Y'` 報廢原因預設不納入良率計算,且可由使用者選擇改為納入
|
||||
- 不以單一欄位混用兩種語義,避免誤判製程損失
|
||||
- 依賴:
|
||||
- 不新增第三方套件,沿用現有 Flask + Vue + Vite + SQLLoader + QueryBuilder 架構
|
||||
@@ -0,0 +1,22 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Reject and defect metric names SHALL remain semantically consistent across UI/API/export
|
||||
The system SHALL use explicit, stable names for charge-off reject and non-charge-off defect metrics across all output surfaces.
|
||||
|
||||
#### Scenario: UI and API key alignment
|
||||
- **WHEN** summary/trend/list payloads are rendered on reject-history page
|
||||
- **THEN** UI labels for reject metrics SHALL map to `REJECT_TOTAL_QTY` and related reject-rate fields
|
||||
- **THEN** UI labels for defect metrics SHALL map to `DEFECT_QTY` and defect-rate fields
|
||||
|
||||
#### Scenario: Export header alignment
|
||||
- **WHEN** reject-history CSV export is generated
|
||||
- **THEN** CSV headers SHALL include both `REJECT_TOTAL_QTY` and `DEFECT_QTY`
|
||||
- **THEN** header names SHALL preserve the same semantic meaning as API fields
|
||||
|
||||
### Requirement: Reject component columns SHALL be explicitly distinguished from defect columns
|
||||
The system SHALL prevent ambiguous naming that collapses reject components and defect into a single term.
|
||||
|
||||
#### Scenario: Component and aggregate coexistence
|
||||
- **WHEN** detailed records are presented
|
||||
- **THEN** reject component fields (`REJECTQTY`, `STANDBYQTY`, `QTYTOPROCESS`, `INPROCESSQTY`, `PROCESSEDQTY`) SHALL be distinguishable from `DEFECT_QTY`
|
||||
- **THEN** aggregate `REJECT_TOTAL_QTY` SHALL be clearly identified as component sum, not defect
|
||||
@@ -0,0 +1,113 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Reject History API SHALL validate required query parameters
|
||||
The API SHALL validate date parameters and basic paging bounds before executing database work.
|
||||
|
||||
#### Scenario: Missing required dates
|
||||
- **WHEN** a reject-history endpoint requiring date range is called without `start_date` or `end_date`
|
||||
- **THEN** the API SHALL return HTTP 400 with a descriptive validation error
|
||||
|
||||
#### Scenario: Invalid date order
|
||||
- **WHEN** `end_date` is earlier than `start_date`
|
||||
- **THEN** the API SHALL return HTTP 400 and SHALL NOT run SQL queries
|
||||
|
||||
### Requirement: Reject History API SHALL provide summary metrics endpoint
|
||||
The API SHALL provide aggregated summary metrics for the selected filter context.
|
||||
|
||||
#### Scenario: Summary response payload
|
||||
- **WHEN** `GET /api/reject-history/summary` is called with valid filters
|
||||
- **THEN** response SHALL be `{ success: true, data: { ... } }`
|
||||
- **THEN** data SHALL include `MOVEIN_QTY`, `REJECT_TOTAL_QTY`, `DEFECT_QTY`, `REJECT_RATE_PCT`, `DEFECT_RATE_PCT`, `REJECT_SHARE_PCT`, `AFFECTED_LOT_COUNT`, and `AFFECTED_WORKORDER_COUNT`
|
||||
|
||||
### Requirement: Reject History API SHALL support yield-exclusion policy toggle
|
||||
The API SHALL support excluding or including policy-marked scrap reasons through a shared query parameter.
|
||||
|
||||
#### Scenario: Default policy mode
|
||||
- **WHEN** reject-history endpoints are called without `include_excluded_scrap`
|
||||
- **THEN** `include_excluded_scrap` SHALL default to `false`
|
||||
- **THEN** rows mapped to `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE.ENABLE_FLAG='Y'` SHALL be excluded from yield-related calculations
|
||||
|
||||
#### Scenario: Explicitly include policy-marked scrap
|
||||
- **WHEN** `include_excluded_scrap=true` is provided
|
||||
- **THEN** policy-marked rows SHALL be included in summary/trend/pareto/list/export calculations
|
||||
- **THEN** API response `meta` SHALL include the effective `include_excluded_scrap` value
|
||||
|
||||
#### Scenario: Invalid toggle value
|
||||
- **WHEN** `include_excluded_scrap` is not parseable as boolean
|
||||
- **THEN** the API SHALL return HTTP 400 with a descriptive validation error
|
||||
|
||||
### Requirement: Reject History API SHALL provide trend endpoint
|
||||
The API SHALL return time-series trend data for quantity and rate metrics.
|
||||
|
||||
#### Scenario: Trend response structure
|
||||
- **WHEN** `GET /api/reject-history/trend` is called
|
||||
- **THEN** response SHALL be `{ success: true, data: { items: [...] } }`
|
||||
- **THEN** each trend item SHALL contain bucket date, `REJECT_TOTAL_QTY`, `DEFECT_QTY`, `REJECT_RATE_PCT`, and `DEFECT_RATE_PCT`
|
||||
|
||||
#### Scenario: Trend granularity
|
||||
- **WHEN** `granularity` is provided as `day`, `week`, or `month`
|
||||
- **THEN** the API SHALL aggregate by the requested granularity
|
||||
- **THEN** invalid granularity SHALL return HTTP 400
|
||||
|
||||
### Requirement: Reject History API SHALL provide reason Pareto endpoint
|
||||
The API SHALL return sorted reason distribution data with cumulative percentages.
|
||||
|
||||
#### Scenario: Pareto response payload
|
||||
- **WHEN** `GET /api/reject-history/reason-pareto` is called
|
||||
- **THEN** each item SHALL include `reason`, `category`, selected metric value, `pct`, and `cumPct`
|
||||
- **THEN** items SHALL be sorted descending by selected metric
|
||||
|
||||
#### Scenario: Metric mode validation
|
||||
- **WHEN** `metric_mode` is provided
|
||||
- **THEN** accepted values SHALL be `reject_total` or `defect`
|
||||
- **THEN** invalid `metric_mode` SHALL return HTTP 400
|
||||
|
||||
### Requirement: Reject History API SHALL provide paginated detail endpoint
|
||||
The API SHALL return paginated detailed rows for the selected filter context.
|
||||
|
||||
#### Scenario: List response payload
|
||||
- **WHEN** `GET /api/reject-history/list?page=1&per_page=50` is called
|
||||
- **THEN** response SHALL include `{ items: [...], pagination: { page, perPage, total, totalPages } }`
|
||||
- **THEN** each row SHALL include date, process dimensions, reason fields, `MOVEIN_QTY`, `REJECT_TOTAL_QTY`, `DEFECT_QTY`, and reject component columns
|
||||
|
||||
#### Scenario: Paging bounds
|
||||
- **WHEN** `page < 1`
|
||||
- **THEN** page SHALL be treated as 1
|
||||
- **WHEN** `per_page > 200`
|
||||
- **THEN** `per_page` SHALL be capped at 200
|
||||
|
||||
### 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
|
||||
|
||||
### 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/`.
|
||||
|
||||
#### Scenario: SQL file loading
|
||||
- **WHEN** reject-history service executes queries
|
||||
- **THEN** SQL SHALL be loaded from files in `sql/reject_history`
|
||||
- **THEN** user-supplied filters SHALL be passed through bind parameters
|
||||
- **THEN** user input SHALL NOT be interpolated into SQL strings directly
|
||||
|
||||
### Requirement: Reject History API SHALL use cached exclusion-policy source
|
||||
The API SHALL read exclusion-policy reasons from cached `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE` data instead of querying Oracle on every request.
|
||||
|
||||
#### Scenario: Enabled exclusions only
|
||||
- **WHEN** exclusion-policy data is loaded
|
||||
- **THEN** only rows with `ENABLE_FLAG='Y'` SHALL be treated as active exclusions
|
||||
|
||||
#### Scenario: Daily full-table cache refresh
|
||||
- **WHEN** exclusion cache is initialized
|
||||
- **THEN** the full table SHALL be loaded and refreshed at least once per 24 hours
|
||||
- **THEN** Redis SHOULD be used as shared cache when available, with in-memory fallback when unavailable
|
||||
|
||||
### Requirement: Reject History API SHALL apply rate limiting on expensive endpoints
|
||||
The API SHALL rate-limit high-cost endpoints to protect Oracle and application resources.
|
||||
|
||||
#### Scenario: List and export rate limiting
|
||||
- **WHEN** `/api/reject-history/list` or `/api/reject-history/export` receives excessive requests
|
||||
- **THEN** configured rate limiting SHALL reject requests beyond the threshold within the time window
|
||||
@@ -0,0 +1,138 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Reject History page SHALL provide filterable historical query controls
|
||||
The page SHALL provide a filter area for date range and major production dimensions to drive all report sections.
|
||||
|
||||
#### Scenario: Default filter values
|
||||
- **WHEN** the page is first loaded
|
||||
- **THEN** `start_date` and `end_date` SHALL default to a valid recent range
|
||||
- **THEN** all other dimension filters SHALL default to empty (no restriction)
|
||||
|
||||
#### Scenario: Apply and clear filters
|
||||
- **WHEN** user clicks "查詢"
|
||||
- **THEN** summary, trend, pareto, and list sections SHALL reload with the same filter set
|
||||
- **WHEN** user clicks "清除條件"
|
||||
- **THEN** all filters SHALL reset to defaults and all sections SHALL reload
|
||||
|
||||
#### Scenario: Required core filters are present
|
||||
- **WHEN** the filter panel is rendered
|
||||
- **THEN** it SHALL include `start_date/end_date` time filter controls
|
||||
- **THEN** it SHALL include reason filter control
|
||||
- **THEN** it SHALL include `WORKCENTER_GROUP` filter control
|
||||
|
||||
### Requirement: Reject History page SHALL expose yield-exclusion toggle control
|
||||
The page SHALL let users decide whether to include policy-marked scrap in yield calculations.
|
||||
|
||||
#### Scenario: Default toggle state
|
||||
- **WHEN** the page is first loaded
|
||||
- **THEN** "納入不計良率報廢" toggle SHALL default to OFF
|
||||
- **THEN** requests SHALL be sent with `include_excluded_scrap=false`
|
||||
|
||||
#### Scenario: Toggle affects all sections
|
||||
- **WHEN** user turns ON/OFF the toggle and clicks "查詢"
|
||||
- **THEN** summary, trend, pareto, and list sections SHALL reload under the selected policy mode
|
||||
- **THEN** export action SHALL use the same toggle state
|
||||
|
||||
#### Scenario: Policy status visibility
|
||||
- **WHEN** data is rendered
|
||||
- **THEN** the UI SHALL show a clear badge/text indicating whether policy-marked scrap is currently excluded or included
|
||||
|
||||
### Requirement: Reject History page SHALL present KPI cards with split reject/defect semantics
|
||||
The page SHALL display KPI cards that simultaneously show charge-off reject and non-charge-off defect metrics.
|
||||
|
||||
#### Scenario: KPI cards render core metrics
|
||||
- **WHEN** summary data is loaded
|
||||
- **THEN** cards SHALL include `MOVEIN_QTY`, `REJECT_TOTAL_QTY`, `DEFECT_QTY`, `REJECT_RATE_PCT`, `DEFECT_RATE_PCT`, `REJECT_SHARE_PCT`, `AFFECTED_LOT_COUNT`, and `AFFECTED_WORKORDER_COUNT`
|
||||
- **THEN** numbers SHALL use zh-TW formatting
|
||||
|
||||
#### Scenario: Visual distinction for semantic lanes
|
||||
- **WHEN** KPI cards are rendered
|
||||
- **THEN** reject-related cards SHALL use a warm-color visual lane
|
||||
- **THEN** defect-related cards SHALL use a cool-color visual lane
|
||||
- **THEN** page legend/badge text SHALL explicitly indicate charge-off vs non-charge-off meaning
|
||||
|
||||
### Requirement: Reject History page SHALL display quantity and rate trends in separate charts
|
||||
The page SHALL show both quantity trend and rate trend to avoid mixing unit scales.
|
||||
|
||||
#### Scenario: Quantity trend chart
|
||||
- **WHEN** trend data is loaded
|
||||
- **THEN** the quantity trend chart SHALL show `REJECT_TOTAL_QTY` and `DEFECT_QTY` over time
|
||||
- **THEN** the chart SHALL use a shared X-axis by date bucket
|
||||
|
||||
#### Scenario: Rate trend chart
|
||||
- **WHEN** trend data is loaded
|
||||
- **THEN** the rate trend chart SHALL show `REJECT_RATE_PCT` and `DEFECT_RATE_PCT` over time
|
||||
- **THEN** rate values SHALL be displayed as percentages
|
||||
|
||||
### 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** reason 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
|
||||
- **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
|
||||
|
||||
#### 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: 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
|
||||
|
||||
### Requirement: Reject History page SHALL show paginated detail rows
|
||||
The page SHALL provide a paginated detail table for investigation and traceability.
|
||||
|
||||
#### Scenario: Detail columns
|
||||
- **WHEN** list data is loaded
|
||||
- **THEN** each row SHALL include date, workcenter group, workcenter, product dimensions, reason/category, `MOVEIN_QTY`, `REJECT_TOTAL_QTY`, `DEFECT_QTY`, and component reject columns
|
||||
|
||||
#### Scenario: Pagination behavior
|
||||
- **WHEN** total records exceed per-page size
|
||||
- **THEN** Prev/Next and page summary SHALL be shown
|
||||
- **THEN** changing any filter SHALL reset page to 1
|
||||
|
||||
### 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
|
||||
- **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`
|
||||
|
||||
### Requirement: Reject History page SHALL provide robust feedback states
|
||||
The page SHALL provide loading, empty, and error states without breaking interactions.
|
||||
|
||||
#### Scenario: Initial loading
|
||||
- **WHEN** first query is running
|
||||
- **THEN** a loading overlay or skeleton SHALL be visible until required data sections are ready
|
||||
|
||||
#### Scenario: API failure
|
||||
- **WHEN** any section API fails
|
||||
- **THEN** a visible error banner SHALL be shown
|
||||
- **THEN** already loaded sections SHALL remain interactive
|
||||
|
||||
#### Scenario: Empty dataset
|
||||
- **WHEN** query returns no rows
|
||||
- **THEN** chart and table areas SHALL show explicit empty-state messages
|
||||
|
||||
### Requirement: Reject History page SHALL maintain responsive visual hierarchy
|
||||
The page SHALL keep the same semantic grouping across desktop and mobile layouts.
|
||||
|
||||
#### Scenario: Desktop layout
|
||||
- **WHEN** viewport is desktop width
|
||||
- **THEN** KPI cards SHALL render in multi-column layout
|
||||
- **THEN** trend and pareto sections SHALL render as two-column analytical panels
|
||||
|
||||
#### Scenario: Mobile layout
|
||||
- **WHEN** viewport width is below responsive breakpoint
|
||||
- **THEN** cards and chart panels SHALL stack in a single column
|
||||
- **THEN** filter controls SHALL remain operable without horizontal overflow
|
||||
@@ -0,0 +1,73 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Charge-off reject metric SHALL be computed from five reject component columns
|
||||
The system SHALL compute `REJECT_TOTAL_QTY` as the sum of five reject-related quantity columns.
|
||||
|
||||
#### Scenario: Reject total formula
|
||||
- **WHEN** a source record is transformed
|
||||
- **THEN** `REJECT_TOTAL_QTY` SHALL equal `REJECTQTY + STANDBYQTY + QTYTOPROCESS + INPROCESSQTY + PROCESSEDQTY`
|
||||
- **THEN** null component values SHALL be treated as zero
|
||||
|
||||
### Requirement: Defect metric SHALL remain independent from reject total
|
||||
The system SHALL compute `DEFECT_QTY` only from `DEFECTQTY` and SHALL NOT merge it into `REJECT_TOTAL_QTY`.
|
||||
|
||||
#### Scenario: Defect independence
|
||||
- **WHEN** a record has `DEFECTQTY > 0` and reject component sum equals 0
|
||||
- **THEN** `DEFECT_QTY` SHALL be non-zero
|
||||
- **THEN** `REJECT_TOTAL_QTY` SHALL remain 0
|
||||
|
||||
### Requirement: Yield-exclusion policy SHALL follow ERP exclusion table
|
||||
The system SHALL use `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE` as the policy source for "not included in yield" scrap reasons.
|
||||
|
||||
#### Scenario: Enabled policy rows
|
||||
- **WHEN** exclusion policy is evaluated
|
||||
- **THEN** only rows with `ENABLE_FLAG='Y'` SHALL be considered exclusion rules
|
||||
|
||||
#### Scenario: Default exclusion behavior
|
||||
- **WHEN** `include_excluded_scrap=false` (default)
|
||||
- **THEN** source rows matching enabled exclusion reasons SHALL be excluded before computing yield-related metrics
|
||||
|
||||
#### Scenario: Optional inclusion override
|
||||
- **WHEN** `include_excluded_scrap=true`
|
||||
- **THEN** the same matched rows SHALL be included back into metric calculations
|
||||
|
||||
### Requirement: Move-in denominator SHALL be deduplicated at event level
|
||||
The system SHALL deduplicate `MOVEIN_QTY` by event key before rate calculations.
|
||||
|
||||
#### Scenario: Primary dedupe key
|
||||
- **WHEN** `HISTORYMAINLINEID` is present
|
||||
- **THEN** only one row per `HISTORYMAINLINEID` SHALL contribute `MOVEIN_QTY`
|
||||
|
||||
#### Scenario: Fallback dedupe key
|
||||
- **WHEN** `HISTORYMAINLINEID` is missing
|
||||
- **THEN** fallback dedupe key SHALL use a deterministic composite key from transaction context
|
||||
|
||||
### Requirement: Reject and defect rates SHALL use the same deduplicated denominator
|
||||
The system SHALL calculate percentage rates from deduplicated `MOVEIN_QTY` to ensure comparability.
|
||||
|
||||
#### Scenario: Reject rate formula
|
||||
- **WHEN** `MOVEIN_QTY > 0`
|
||||
- **THEN** `REJECT_RATE_PCT` SHALL equal `REJECT_TOTAL_QTY / MOVEIN_QTY * 100`
|
||||
|
||||
#### Scenario: Defect rate formula
|
||||
- **WHEN** `MOVEIN_QTY > 0`
|
||||
- **THEN** `DEFECT_RATE_PCT` SHALL equal `DEFECT_QTY / MOVEIN_QTY * 100`
|
||||
|
||||
#### Scenario: Zero denominator handling
|
||||
- **WHEN** `MOVEIN_QTY = 0`
|
||||
- **THEN** both rate fields SHALL return 0 and SHALL NOT raise divide-by-zero errors
|
||||
|
||||
### Requirement: Reject share SHALL describe reject proportion within total loss
|
||||
The system SHALL calculate reject share against combined reject and defect loss quantities.
|
||||
|
||||
#### Scenario: Reject share formula
|
||||
- **WHEN** `REJECT_TOTAL_QTY + DEFECT_QTY > 0`
|
||||
- **THEN** `REJECT_SHARE_PCT` SHALL equal `REJECT_TOTAL_QTY / (REJECT_TOTAL_QTY + DEFECT_QTY) * 100`
|
||||
|
||||
### Requirement: Metric naming SHALL preserve semantic meaning across transformations
|
||||
The system SHALL keep explicit names for charge-off reject and non-charge-off defect metrics.
|
||||
|
||||
#### Scenario: No ambiguous remapping
|
||||
- **WHEN** service or export fields are generated
|
||||
- **THEN** `REJECT_TOTAL_QTY` SHALL NOT be renamed to `DEFECT_QTY`
|
||||
- **THEN** `DEFECT_QTY` SHALL refer only to `DEFECTQTY`
|
||||
@@ -0,0 +1,24 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Reject History route SHALL be included in governed shell route inventory
|
||||
The `/reject-history` route SHALL be represented in shell route contracts with complete governance metadata.
|
||||
|
||||
#### Scenario: Frontend route contract entry
|
||||
- **WHEN** route contract validation runs against `frontend/src/portal-shell/routeContracts.js`
|
||||
- **THEN** `/reject-history` SHALL exist with route id, title, owner, render mode, visibility policy, scope, and compatibility policy
|
||||
|
||||
#### Scenario: Native loader coverage
|
||||
- **WHEN** native module loader registry is validated
|
||||
- **THEN** `/reject-history` SHALL be resolvable in `nativeModuleRegistry`
|
||||
|
||||
### Requirement: Reject History governance metadata SHALL be parity-validated across sources
|
||||
Shell governance checks SHALL enforce parity for `/reject-history` between frontend and backend contract inventories.
|
||||
|
||||
#### Scenario: Contract parity for reject-history route
|
||||
- **WHEN** contract parity checks execute
|
||||
- **THEN** frontend and backend route inventories SHALL both include `/reject-history`
|
||||
- **THEN** metadata mismatch or missing route SHALL fail governance checks
|
||||
|
||||
#### Scenario: Navigation visibility governance
|
||||
- **WHEN** page status/navigation config is evaluated
|
||||
- **THEN** `/reject-history` SHALL have governed drawer assignment and ordering metadata
|
||||
@@ -0,0 +1,40 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Reject History page SHALL be a pure Vite HTML entry
|
||||
The reject-history page SHALL be built from an HTML entry and emitted as static dist assets.
|
||||
|
||||
#### Scenario: Vite entry registration
|
||||
- **WHEN** Vite config inputs are evaluated
|
||||
- **THEN** `reject-history` SHALL map to `frontend/src/reject-history/index.html`
|
||||
|
||||
#### Scenario: Build output artifacts
|
||||
- **WHEN** `vite build` completes
|
||||
- **THEN** output SHALL include `reject-history.html`, `reject-history.js`, and `reject-history.css` in `static/dist/`
|
||||
|
||||
### Requirement: Reject History route SHALL serve static dist HTML
|
||||
The Flask route for `/reject-history` SHALL serve pre-built static HTML through `send_from_directory`.
|
||||
|
||||
#### Scenario: Static page serving
|
||||
- **WHEN** user navigates to `/reject-history`
|
||||
- **THEN** Flask SHALL serve `static/dist/reject-history.html` when the file exists
|
||||
- **THEN** HTML SHALL NOT be rendered through Jinja template interpolation
|
||||
|
||||
#### Scenario: Dist fallback response
|
||||
- **WHEN** `reject-history.html` is missing in dist
|
||||
- **THEN** route SHALL return a minimal fallback HTML that still references `/static/dist/reject-history.js`
|
||||
|
||||
### Requirement: Reject History shell integration SHALL use native module loading
|
||||
The page SHALL integrate with portal-shell native module loading policy.
|
||||
|
||||
#### Scenario: Native module registration
|
||||
- **WHEN** shell resolves a route component for `/reject-history`
|
||||
- **THEN** it SHALL dynamically import `frontend/src/reject-history/App.vue`
|
||||
- **THEN** the route style bundle SHALL be loaded via registered style loaders
|
||||
|
||||
### Requirement: Reject History page SHALL call APIs through shared core API module
|
||||
The page SHALL call backend APIs via `frontend/src/core/api.js` without legacy global dependencies.
|
||||
|
||||
#### Scenario: API call path
|
||||
- **WHEN** reject-history page executes GET or export requests
|
||||
- **THEN** requests SHALL use shared API utilities (`apiGet`/equivalent)
|
||||
- **THEN** page behavior SHALL NOT depend on `window.MesApi`
|
||||
@@ -0,0 +1,60 @@
|
||||
## 1. Contract and Skeleton Setup
|
||||
|
||||
- [x] 1.1 Create backend blueprint scaffold `src/mes_dashboard/routes/reject_history_routes.py` and register it in `src/mes_dashboard/routes/__init__.py`
|
||||
- [x] 1.2 Create service scaffold `src/mes_dashboard/services/reject_history_service.py` with SQL loader helpers
|
||||
- [x] 1.3 Create frontend entry scaffold `frontend/src/reject-history/index.html`, `frontend/src/reject-history/main.js`, and `frontend/src/reject-history/App.vue`
|
||||
- [x] 1.4 Add Vite input for `reject-history` in `frontend/vite.config.js`
|
||||
|
||||
## 2. SQL and Metric Semantics Implementation
|
||||
|
||||
- [x] 2.1 Finalize base query `src/mes_dashboard/sql/reject_history/performance_daily.sql` for five-reject-sum + defect separation
|
||||
- [x] 2.2 Add API-specific SQL files in `src/mes_dashboard/sql/reject_history/` (summary, trend, reason_pareto, list, export)
|
||||
- [x] 2.3 Implement `MOVEIN_QTY` dedupe by `HISTORYMAINLINEID` with deterministic fallback key
|
||||
- [x] 2.4 Implement consistent rate calculations (`REJECT_RATE_PCT`, `DEFECT_RATE_PCT`, `REJECT_SHARE_PCT`) with zero-denominator handling
|
||||
- [x] 2.5 Integrate `ERP_PJ_WIP_SCRAP_REASONS_EXCLUDE` policy mapping (`ENABLE_FLAG='Y'`) into reject-history aggregation flow
|
||||
- [x] 2.6 Create `src/mes_dashboard/services/scrap_reason_exclusion_cache.py` with daily full-table refresh (Redis preferred + in-memory fallback)
|
||||
|
||||
## 3. Backend API Routes
|
||||
|
||||
- [x] 3.1 Implement `GET /api/reject-history/summary` with date/filter validation
|
||||
- [x] 3.2 Implement `GET /api/reject-history/trend` with `granularity` validation (`day|week|month`)
|
||||
- [x] 3.3 Implement `GET /api/reject-history/reason-pareto` with `metric_mode` validation (`reject_total|defect`)
|
||||
- [x] 3.4 Implement `GET /api/reject-history/list` with paging bounds and reason/category filters
|
||||
- [x] 3.5 Implement `GET /api/reject-history/export` and CSV output contract
|
||||
- [x] 3.6 Apply configured rate limiting to list/export endpoints
|
||||
- [x] 3.7 Add shared query param `include_excluded_scrap` (default false) and return effective policy mode in response meta
|
||||
|
||||
## 4. Frontend Visual and Interaction Implementation
|
||||
|
||||
- [x] 4.1 Build page header with title, data timestamp, and semantic badges for charge-off reject vs non-charge-off defect
|
||||
- [x] 4.2 Build filter panel with required controls (`start_date/end_date`, reason, `WORKCENTER_GROUP`) plus query/clear actions, and wire it to all API calls
|
||||
- [x] 4.3 Implement KPI card row (8 cards) with warm/cool semantic color lanes and zh-TW number formatting
|
||||
- [x] 4.4 Implement dual trend charts (quantity trend + rate trend) using ECharts with synchronized date buckets
|
||||
- [x] 4.5 Implement reason Pareto chart/table with `metric_mode` switch and cumulative percentage line, referencing WIP OVER VIEW interaction pattern
|
||||
- [x] 4.6 Add Pareto mode toggle: default "top cumulative 80%" and optional "show all filtered categories"
|
||||
- [x] 4.7 Implement detail table with pagination, active filter chips, and empty/error states
|
||||
- [x] 4.8 Implement CSV export action using current filter context
|
||||
- [x] 4.9 Add responsive rules so filter/cards/charts/table stay usable on tablet/mobile widths
|
||||
- [x] 4.10 Add "納入不計良率報廢" toggle in filter panel and wire to all API calls + export
|
||||
|
||||
## 5. Shell and Route Governance Integration
|
||||
|
||||
- [x] 5.1 Add `/reject-history` contract entry to `frontend/src/portal-shell/routeContracts.js`
|
||||
- [x] 5.2 Add `/reject-history` loader to `frontend/src/portal-shell/nativeModuleRegistry.js`
|
||||
- [x] 5.3 Add `/reject-history` page metadata (drawer/order/status) to `data/page_status.json`
|
||||
- [x] 5.4 Add Flask page route `/reject-history` using `send_from_directory` with dist fallback HTML
|
||||
|
||||
## 6. Tests and Quality Gates
|
||||
|
||||
- [x] 6.1 Add service tests in `tests/test_reject_history_service.py` covering formulas, dedupe, and edge cases
|
||||
- [x] 6.2 Add route tests in `tests/test_reject_history_routes.py` covering validation, payload shape, and rate-limit behavior
|
||||
- [x] 6.3 Add/extend route-contract parity and shell coverage tests for `/reject-history`
|
||||
- [x] 6.4 Add frontend smoke/integration test for query flow and major visual sections
|
||||
- [x] 6.5 Add exclusion-policy tests (`ENABLE_FLAG` handling, default exclude, include override, cache fallback path)
|
||||
|
||||
## 7. Documentation and Rollout
|
||||
|
||||
- [x] 7.1 Update implementation notes under `docs/reject_history_performance.md` to match API/UI field names
|
||||
- [x] 7.2 Document exclusion-policy behavior and user toggle semantics in reject-history docs
|
||||
- [x] 7.3 Document rollout policy (`dev` visibility first, then `released`) and rollback path
|
||||
- [x] 7.4 Run end-to-end verification checklist and capture evidence before implementation handoff
|
||||
Reference in New Issue
Block a user