feat(qc-gate): add Package column to LOT detail table + archive 3 completed changes
Add PACKAGE_LEF as a dedicated `package` field in the QC-GATE API payload and display it as a new column after LOT ID in LotTable.vue. Archive qc-gate-lot-package-column, historical-query-slow-connection, and msd-multifactor-backward-tracing changes with their delta specs synced to main specs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-25
|
||||
@@ -0,0 +1,78 @@
|
||||
## Context
|
||||
|
||||
歷史查詢頁面(reject-history、hold-history、resource-history、job-query、excel-query)
|
||||
目前使用 `read_sql_df`(connection pool,55s `call_timeout`),大範圍查詢容易 timeout。
|
||||
前端 AbortController timeout 為 60~120s。Gunicorn worker timeout 為 130s。
|
||||
|
||||
現有 `read_sql_df_slow` 函式(`database.py:573`)已提供獨立連線路徑,
|
||||
但只被 `query_tool_service.py` 的 `full_history` 模式使用,且 timeout 寫死 120s,
|
||||
無並行控制。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 歷史查詢 service 全面遷移到 `read_sql_df_slow`(獨立連線,不佔用 pool)
|
||||
- `read_sql_df_slow` 的 timeout 從寫死 120s 改為 config 驅動(預設 300s)
|
||||
- 加入 global semaphore 限制並行 slow query 數量,保護 Oracle 不被大量連線壓垮
|
||||
- Gunicorn 和前端 timeout 配合調整,確保查詢不會在任何層被過早截斷
|
||||
- 即時監控頁完全不受影響
|
||||
|
||||
**Non-Goals:**
|
||||
- 不改動 `read_sql_df` 本身(pool path 保持 55s)
|
||||
- 不改動 EventFetcher、LineageEngine(小查詢,走 pool 即可)
|
||||
- 不引入非同步任務佇列(Celery 等)
|
||||
- 不對即時監控頁做任何修改
|
||||
- 不修改 Oracle SQL 本身(查詢優化不在此範圍)
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: Import alias 遷移模式
|
||||
|
||||
**決策**:各 service 用 `from ... import read_sql_df_slow as read_sql_df` 取代原本的 `read_sql_df` import。
|
||||
|
||||
**理由**:兩個函式的 `(sql, params)` 簽名相容,alias 後所有 call site 零改動。
|
||||
比在 `read_sql_df` 加 flag 更乾淨——不污染 pool path 的邏輯。
|
||||
|
||||
**替代方案**:在 `read_sql_df` 加 `slow=True` 參數 → 增加 pool path 複雜度,rejected。
|
||||
|
||||
### D2: Semaphore 限制並行數
|
||||
|
||||
**決策**:在 `database.py` 加 module-level `threading.Semaphore`,`read_sql_df_slow` 執行前 acquire,finally release。預設 3 並行(可由 `DB_SLOW_MAX_CONCURRENT` 環境變數調整)。
|
||||
|
||||
**理由**:Gunicorn gthread 模式 2 workers × 4 threads = 8 request threads。
|
||||
限制 3 個 slow 連線確保至少 5 個 threads 仍可服務即時頁。
|
||||
Oracle 端不會同時看到超過 3 個長查詢連線。
|
||||
|
||||
**Semaphore acquire timeout**:60 秒。超時回傳明確錯誤「查詢繁忙,請稍後再試」。
|
||||
|
||||
### D3: Timeout 數值選擇
|
||||
|
||||
| 層 | 修改前 | 修改後 | 理由 |
|
||||
|---|---|---|---|
|
||||
| `read_sql_df_slow` | 120s 寫死 | 300s config | 5 分鐘足夠大多數歷史查詢 |
|
||||
| Gunicorn worker | 130s | 360s | 300s + 60s overhead |
|
||||
| 前端 historical | 60~120s | 360s | 與 Gunicorn 對齊 |
|
||||
|
||||
### D4: excel_query_service 特殊處理
|
||||
|
||||
**決策**:excel_query_service 不用 `read_sql_df`,而是直接用 `get_db_connection()` + cursor。
|
||||
改為在取得連線後設定 `connection.call_timeout = slow_call_timeout_ms`。
|
||||
|
||||
**理由**:保持現有 cursor batch 邏輯不變,只延長 timeout。
|
||||
|
||||
### D5: resource_history_service 並行查詢
|
||||
|
||||
**決策**:`query_summary` 用 `ThreadPoolExecutor(max_workers=3)` 並行 3 條查詢。
|
||||
遷移後每條查詢佔 1 個 semaphore slot(共 3 個),單次請求可能佔滿所有 slot。
|
||||
|
||||
**接受此風險**:3 條查詢並行完成很快,slot 很快釋放。其他 slow 請求最多等 60s。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| 風險 | 緩解措施 |
|
||||
|------|---------|
|
||||
| Semaphore deadlock(exception 未 release) | `finally` block 保證 release |
|
||||
| 3 並行 slot 不夠用(多人同時查歷史) | `DB_SLOW_MAX_CONCURRENT` 可動態調整,無需改 code |
|
||||
| 長查詢佔住 Gunicorn thread 影響即時頁 | Semaphore 限制最多 3 個 thread 被佔用,其餘 5 個可用 |
|
||||
| Circuit breaker 不再保護歷史查詢 | 歷史查詢為使用者手動觸發、非自動化,可接受 |
|
||||
| `resource_history_service` 一次用完 3 slot | 查詢快速完成,slot 迅速釋放;可視需要降低 max_workers |
|
||||
@@ -0,0 +1,42 @@
|
||||
## Why
|
||||
|
||||
歷史查詢頁面(reject-history、query-tool、hold-history、resource-history、job-query、excel-query、mid-section-defect)
|
||||
目前共用 connection pool 的 55s `call_timeout`,前端也設有 60~120s AbortController timeout。
|
||||
大範圍查詢(如長日期區間或大量 LOT)經常逼近或超過 timeout,使用者看到 "signal is aborted without reason" 錯誤。
|
||||
|
||||
歷史查詢屬於使用者手動觸發、非即時、可等待的操作,應採用獨立連線(`read_sql_df_slow`)配合
|
||||
semaphore 並行控制,以「用時間換結果」的方式完成查詢,同時保護 connection pool 不被耗盡。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 歷史查詢 service 從 `read_sql_df`(pool, 55s)遷移到 `read_sql_df_slow`(獨立連線, 300s)
|
||||
- `read_sql_df_slow` 加入 global semaphore 限制並行數(預設 3),避免耗盡 Oracle 連線
|
||||
- `read_sql_df_slow` 的 timeout 預設從寫死 120s 改為 config 驅動(預設 300s)
|
||||
- Gunicorn worker timeout 從 130s 提升到 360s 以容納長查詢
|
||||
- 前端歷史頁 timeout 統一提升到 360s(6 分鐘)
|
||||
- 新增 `DB_SLOW_CALL_TIMEOUT_MS` 和 `DB_SLOW_MAX_CONCURRENT` 環境變數設定
|
||||
- 即時監控頁(wip、hold-overview、resource-status 等)完全不受影響
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `slow-query-concurrency-control`: `read_sql_df_slow` 的 semaphore 並行控制與 config 驅動 timeout
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `reject-history-api`: 底層 DB 查詢從 pooled 改為 dedicated slow connection
|
||||
- `hold-history-api`: 底層 DB 查詢從 pooled 改為 dedicated slow connection
|
||||
- `query-tool-lot-trace`: 移除 `read_sql_df_slow` 寫死的 120s timeout,改用 config 預設
|
||||
- `reject-history-page`: 前端 API_TIMEOUT 從 60s 提升到 360s
|
||||
- `hold-history-page`: 前端 API_TIMEOUT 從 60s 提升到 360s
|
||||
- `resource-history-page`: 前端 API_TIMEOUT 從 60s 提升到 360s,後端遷移至 slow connection
|
||||
- `query-tool-equipment`: 前端 timeout 從 120s 提升到 360s
|
||||
- `progressive-trace-ux`: DEFAULT_STAGE_TIMEOUT_MS 從 60s 提升到 360s
|
||||
|
||||
## Impact
|
||||
|
||||
- **後端 services**:reject_history_service、reject_dataset_cache、hold_history_service、resource_history_service、job_query_service、excel_query_service、query_tool_service
|
||||
- **核心模組**:database.py(semaphore + config)、settings.py(新設定)、gunicorn.conf.py(timeout)
|
||||
- **前端頁面**:reject-history、mid-section-defect、hold-history、resource-history、query-tool(5 個 composable + 1 component)、job-query、excel-query
|
||||
- **不影響**:即時監控頁(wip-overview、wip-detail、hold-overview、hold-detail、resource-status、admin-performance)
|
||||
@@ -0,0 +1,9 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Database query execution path
|
||||
The hold-history service (`hold_history_service.py`) SHALL use `read_sql_df_slow` (dedicated connection) instead of `read_sql_df` (pooled connection) for all Oracle queries.
|
||||
|
||||
#### Scenario: Hold history queries use dedicated connection
|
||||
- **WHEN** any hold-history query is executed (trend, pareto, duration, list)
|
||||
- **THEN** it uses `read_sql_df_slow` which creates a dedicated Oracle connection outside the pool
|
||||
- **AND** the connection has a 300-second call_timeout (configurable)
|
||||
@@ -0,0 +1,8 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Frontend API timeout
|
||||
The hold-history page SHALL use a 360-second API timeout (up from 60 seconds) for all Oracle-backed API calls.
|
||||
|
||||
#### Scenario: Large date range query completes
|
||||
- **WHEN** a user queries hold history for a long date range
|
||||
- **THEN** the frontend does not abort the request for at least 360 seconds
|
||||
@@ -0,0 +1,8 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Trace stage timeout
|
||||
The `useTraceProgress` composable's `DEFAULT_STAGE_TIMEOUT_MS` SHALL be 360000 (360 seconds) to accommodate large-scale trace operations.
|
||||
|
||||
#### Scenario: Large trace operation completes
|
||||
- **WHEN** a trace stage (seed-resolve, lineage, or events) takes up to 300 seconds
|
||||
- **THEN** the frontend does not abort the stage request
|
||||
@@ -0,0 +1,8 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Frontend API timeout
|
||||
The query-tool equipment query, lot detail, lot jobs table, lot resolve, lot lineage, and reverse lineage composables SHALL use a 360-second API timeout for all Oracle-backed API calls.
|
||||
|
||||
#### Scenario: Equipment period query completes
|
||||
- **WHEN** a user queries equipment history for a long period
|
||||
- **THEN** the frontend does not abort the request for at least 360 seconds
|
||||
@@ -0,0 +1,9 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Slow query timeout configuration
|
||||
The query-tool service `read_sql_df_slow` call for full split/merge history SHALL use the config-driven default timeout instead of a hardcoded 120-second timeout.
|
||||
|
||||
#### Scenario: Full history query uses config timeout
|
||||
- **WHEN** `full_history=True` split/merge query is executed
|
||||
- **THEN** it uses `read_sql_df_slow` with the default timeout from `DB_SLOW_CALL_TIMEOUT_MS` (300s)
|
||||
- **AND** the hardcoded `timeout_seconds=120` parameter is removed
|
||||
@@ -0,0 +1,10 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Database query execution path
|
||||
The reject-history service (`reject_history_service.py` and `reject_dataset_cache.py`) SHALL use `read_sql_df_slow` (dedicated connection) instead of `read_sql_df` (pooled connection) for all Oracle queries.
|
||||
|
||||
#### Scenario: Primary query uses dedicated connection
|
||||
- **WHEN** the reject-history primary query is executed
|
||||
- **THEN** it uses `read_sql_df_slow` which creates a dedicated Oracle connection outside the pool
|
||||
- **AND** the connection has a 300-second call_timeout (configurable)
|
||||
- **AND** the connection is subject to the global slow query semaphore
|
||||
@@ -0,0 +1,8 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Frontend API timeout
|
||||
The reject-history page SHALL use a 360-second API timeout (up from 60 seconds) for all Oracle-backed API calls.
|
||||
|
||||
#### Scenario: Large date range query completes
|
||||
- **WHEN** a user queries reject history for a long date range
|
||||
- **THEN** the frontend does not abort the request for at least 360 seconds
|
||||
@@ -0,0 +1,16 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Database query execution path
|
||||
The resource-history service (`resource_history_service.py`) SHALL use `read_sql_df_slow` (dedicated connection) instead of `read_sql_df` (pooled connection) for all Oracle queries.
|
||||
|
||||
#### Scenario: Summary parallel queries use dedicated connections
|
||||
- **WHEN** the resource-history summary query executes 3 parallel queries via ThreadPoolExecutor
|
||||
- **THEN** each query uses `read_sql_df_slow` and acquires a semaphore slot
|
||||
- **AND** all 3 queries complete and release their slots
|
||||
|
||||
### Requirement: Frontend timeout
|
||||
The resource-history page frontend SHALL use a 360-second API timeout for all Oracle-backed API calls.
|
||||
|
||||
#### Scenario: Large date range query completes
|
||||
- **WHEN** a user queries resource history for a 2-year date range
|
||||
- **THEN** the frontend does not abort the request for at least 360 seconds
|
||||
@@ -0,0 +1,53 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Configurable slow query timeout
|
||||
The system SHALL read `DB_SLOW_CALL_TIMEOUT_MS` from environment/config to determine the default `call_timeout` for `read_sql_df_slow`. The default value SHALL be 300000 (300 seconds).
|
||||
|
||||
#### Scenario: Default timeout when no env var set
|
||||
- **WHEN** `DB_SLOW_CALL_TIMEOUT_MS` is not set in environment
|
||||
- **THEN** `read_sql_df_slow` uses 300 seconds as call_timeout
|
||||
|
||||
#### Scenario: Custom timeout from env var
|
||||
- **WHEN** `DB_SLOW_CALL_TIMEOUT_MS` is set to 180000
|
||||
- **THEN** `read_sql_df_slow` uses 180 seconds as call_timeout
|
||||
|
||||
#### Scenario: Caller overrides timeout
|
||||
- **WHEN** caller passes `timeout_seconds=120` to `read_sql_df_slow`
|
||||
- **THEN** the function uses 120 seconds regardless of config value
|
||||
|
||||
### Requirement: Semaphore-based concurrency control
|
||||
The system SHALL use a global `threading.Semaphore` to limit the number of concurrent `read_sql_df_slow` executions. The limit SHALL be configurable via `DB_SLOW_MAX_CONCURRENT` with a default of 3.
|
||||
|
||||
#### Scenario: Concurrent queries within limit
|
||||
- **WHEN** 2 slow queries are running and a 3rd is submitted (limit=3)
|
||||
- **THEN** the 3rd query proceeds immediately
|
||||
|
||||
#### Scenario: Concurrent queries exceed limit
|
||||
- **WHEN** 3 slow queries are running and a 4th is submitted (limit=3)
|
||||
- **THEN** the 4th query waits up to 60 seconds for a slot
|
||||
- **AND** if no slot becomes available, raises RuntimeError with message indicating all slots are busy
|
||||
|
||||
#### Scenario: Semaphore release on query failure
|
||||
- **WHEN** a slow query raises an exception during execution
|
||||
- **THEN** the semaphore slot is released in the finally block
|
||||
|
||||
### Requirement: Slow query active count diagnostic
|
||||
The system SHALL expose the current number of active slow queries via `get_slow_query_active_count()` and include it in `get_pool_status()` as `slow_query_active`.
|
||||
|
||||
#### Scenario: Active count in pool status
|
||||
- **WHEN** 2 slow queries are running
|
||||
- **THEN** `get_pool_status()` returns `slow_query_active: 2`
|
||||
|
||||
### Requirement: Gunicorn timeout accommodates slow queries
|
||||
The Gunicorn worker timeout SHALL be at least 360 seconds to accommodate the maximum slow query duration (300s) plus overhead.
|
||||
|
||||
#### Scenario: Long query does not kill worker
|
||||
- **WHEN** a slow query takes 280 seconds to complete
|
||||
- **THEN** the Gunicorn worker does not timeout and the response is delivered
|
||||
|
||||
### Requirement: Config settings in all environments
|
||||
All environment configs (Config, DevelopmentConfig, ProductionConfig, TestingConfig) SHALL define `DB_SLOW_CALL_TIMEOUT_MS` and `DB_SLOW_MAX_CONCURRENT`.
|
||||
|
||||
#### Scenario: Testing config uses short timeout
|
||||
- **WHEN** running in testing environment
|
||||
- **THEN** `DB_SLOW_CALL_TIMEOUT_MS` defaults to 10000 and `DB_SLOW_MAX_CONCURRENT` defaults to 1
|
||||
@@ -0,0 +1,39 @@
|
||||
## 1. Core Infrastructure
|
||||
|
||||
- [x] 1.1 Add `DB_SLOW_CALL_TIMEOUT_MS` (default 300000) and `DB_SLOW_MAX_CONCURRENT` (default 3) to all config classes in `settings.py` (Config, DevelopmentConfig=2, ProductionConfig=3, TestingConfig=1/10000)
|
||||
- [x] 1.2 Update `get_db_runtime_config()` in `database.py` to include `slow_call_timeout_ms` and `slow_max_concurrent`
|
||||
- [x] 1.3 Add module-level `threading.Semaphore`, active count tracking, and `get_slow_query_active_count()` in `database.py`
|
||||
- [x] 1.4 Refactor `read_sql_df_slow()`: default `timeout_seconds=None` (reads from config), acquire/release semaphore, log active count
|
||||
- [x] 1.5 Update `dispose_engine()` to reset semaphore; add `slow_query_active` to `get_pool_status()`
|
||||
- [x] 1.6 Increase Gunicorn timeout to 360s and graceful_timeout to 120s in `gunicorn.conf.py`
|
||||
|
||||
## 2. Backend Service Migration
|
||||
|
||||
- [x] 2.1 `reject_history_service.py`: change import to `read_sql_df_slow as read_sql_df`
|
||||
- [x] 2.2 `reject_dataset_cache.py`: change import to `read_sql_df_slow as read_sql_df`
|
||||
- [x] 2.3 `hold_history_service.py`: change import to `read_sql_df_slow as read_sql_df` (keep DatabaseCircuitOpenError/DatabasePoolExhaustedError imports)
|
||||
- [x] 2.4 `resource_history_service.py`: change import to `read_sql_df_slow as read_sql_df`
|
||||
- [x] 2.5 `job_query_service.py`: change import to `read_sql_df_slow as read_sql_df` (keep `get_db_connection` import)
|
||||
- [x] 2.6 `excel_query_service.py`: set `connection.call_timeout = runtime["slow_call_timeout_ms"]` on direct connections in `execute_batch_query` and `execute_advanced_batch_query`
|
||||
- [x] 2.7 `query_tool_service.py`: remove hardcoded `timeout_seconds=120` from `read_sql_df_slow` call (line 1131)
|
||||
|
||||
## 3. Frontend Timeout Updates
|
||||
|
||||
- [x] 3.1 `reject-history/App.vue`: `API_TIMEOUT` 60000 → 360000
|
||||
- [x] 3.2 `mid-section-defect/App.vue`: `API_TIMEOUT` 120000 → 360000
|
||||
- [x] 3.3 `hold-history/App.vue`: `API_TIMEOUT` 60000 → 360000
|
||||
- [x] 3.4 `resource-history/App.vue`: `API_TIMEOUT` 60000 → 360000
|
||||
- [x] 3.5 `shared-composables/useTraceProgress.js`: `DEFAULT_STAGE_TIMEOUT_MS` 60000 → 360000
|
||||
- [x] 3.6 `job-query/composables/useJobQueryData.js`: all `timeout: 60000` → 360000 (3 sites)
|
||||
- [x] 3.7 `excel-query/composables/useExcelQueryData.js`: `timeout: 120000` → 360000 (2 sites, lines 135, 255)
|
||||
- [x] 3.8 `query-tool/composables/useLotDetail.js`: `timeout: 120000` → 360000 (3 sites) and `timeout: 60000` → 360000 (1 site)
|
||||
- [x] 3.9 `query-tool/composables/useEquipmentQuery.js`: `timeout: 120000` → 360000 and `timeout: 60000` → 360000
|
||||
- [x] 3.10 `query-tool/composables/useLotResolve.js`: `timeout: 60000` → 360000
|
||||
- [x] 3.11 `query-tool/composables/useLotLineage.js`: `timeout: 60000` → 360000
|
||||
- [x] 3.12 `query-tool/composables/useReverseLineage.js`: `timeout: 60000` → 360000
|
||||
- [x] 3.13 `query-tool/components/LotJobsTable.vue`: `timeout: 60000` → 360000
|
||||
|
||||
## 4. Verification
|
||||
|
||||
- [x] 4.1 Run `python -m pytest tests/ -v` — all existing tests pass (28 pre-existing failures, 1076 passed, 0 new failures)
|
||||
- [x] 4.2 Run `cd frontend && npm run build` — frontend builds successfully
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-24
|
||||
@@ -0,0 +1,107 @@
|
||||
## Context
|
||||
|
||||
「製程不良追溯分析」是 TMTT 測試站不良的回溯歸因工具。目前架構為三階段 staged pipeline(seed-resolve → lineage → events),反向追溯只取 `upstream_history` domain 做機台歸因。
|
||||
|
||||
已有的基礎設施:
|
||||
- **EventFetcher** 已支援 `materials` domain(查 `LOTMATERIALSHISTORY`),可直接複用
|
||||
- **LineageEngine** 已能追溯 split chain 到 root ancestor,`child_to_parent` map 可直接求 root
|
||||
- 前端使用 **ECharts** (vue-echarts) 渲染柏拉圖,組件化完善
|
||||
- 歸因邏輯 `_attribute_defects()` 是通用 pattern:`factor_value → detection_lots mapping → rate calculation`
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 在反向追溯中新增原物料、源頭晶片兩個歸因維度,與現有機台歸因並列
|
||||
- 提升柏拉圖的分析能力(排序切換、80% 標記線、tooltip 強化)
|
||||
- 讓使用者理解歸因計算方式(分析摘要面板)
|
||||
- 明細表嫌疑因子命中呈現,與柏拉圖 Top N 連動
|
||||
- 嫌疑機台的維修上下文面板
|
||||
- 報廢歷史查詢頁面承接產品分布分析(PACKAGE / TYPE / WORKFLOW)
|
||||
|
||||
**Non-Goals:**
|
||||
- 正向追溯改版(後續獨立處理)
|
||||
- 維修事件作為獨立歸因維度(時間交叉模型複雜,本次僅作為嫌疑機台的上下文資訊)
|
||||
- Cross-filter 聯動(點圖表 bar 聯動篩選所有圖表 — 本次不做,僅做嫌疑命中)
|
||||
- 站點層級歸因柏拉圖(產品每站都經過,無鑑別力)
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: 原物料歸因邏輯 — 複用 `_attribute_defects` pattern
|
||||
|
||||
**選擇**:新增 `_attribute_materials()` 函數,邏輯與 `_attribute_defects()` 完全對稱,只是 key 從 `(workcenter_group, equipment_name, equipment_id)` 換成 `(material_part_name, material_lot_name)`。
|
||||
|
||||
**替代方案**:泛化為通用 `_attribute_by_factor(records, key_fn)` 函數。
|
||||
**理由**:各維度的 record 結構不同(upstream_history 有 station/equipment,materials 有 part/lot),強行泛化需要額外 adapter 層。先用對稱複製,後續若新增更多維度再考慮抽象。
|
||||
|
||||
### D2: 原物料資料取得 — EventFetcher materials domain
|
||||
|
||||
**選擇**:在 staged trace events 階段新增請求 `materials` domain。前端的 `useTraceProgress.js` 在 backward 模式時改為 `domains: ['upstream_history', 'materials']`。
|
||||
|
||||
**替代方案**:另開一個獨立 API 查原物料。
|
||||
**理由**:EventFetcher 已有完善的 materials domain(batch、cache、rate limit),staged trace pipeline 已能處理多 domain 並行查詢,無需重複建設。
|
||||
|
||||
### D3: 源頭晶片歸因 — 從 lineage ancestors 提取 root
|
||||
|
||||
**選擇**:在 lineage stage 的 response 中新增 `roots` 欄位(`{seed_cid: root_container_name}`),不需要額外 SQL 查詢。LineageEngine 已有 `child_to_parent` map,遍歷到無 parent 的節點即為 root。後端 `_attribute_wafer_roots()` 以 `root_container_name` 為 key 做歸因。
|
||||
|
||||
**替代方案**:用 SQL CONNECT BY 直接查 root。
|
||||
**理由**:lineage stage 已經完整追溯了 split chain,root 資訊是副產品,不需要額外 DB roundtrip。
|
||||
|
||||
### D4: 柏拉圖排列 — 替換 PACKAGE/TYPE/WORKFLOW
|
||||
|
||||
**選擇**:6 張柏拉圖改為:
|
||||
|
||||
| 位置 | 原本 | 改為 |
|
||||
|------|------|------|
|
||||
| 左上 | 依上游機台歸因 | 依上游機台歸因(保留) |
|
||||
| 右上 | 依不良原因 | 依原物料歸因(新) |
|
||||
| 左中 | 依偵測機台 | 依源頭晶片歸因(新) |
|
||||
| 右中 | 依 WORKFLOW | 依不良原因(保留) |
|
||||
| 左下 | 依 PACKAGE | 依偵測機台(保留) |
|
||||
| 右下 | 依 TYPE | 移除 |
|
||||
|
||||
改為 5 張(2-2-1 排列),最後一行只有偵測機台。或視空間調整為 3-2 或 2-2-2。
|
||||
|
||||
**理由**:PACKAGE / TYPE / WORKFLOW 是「不良分布在哪些產品上」的分析,屬於報廢歷史查詢的範疇。製程不良追溯的核心問題是「不良來自哪個上游因子」。
|
||||
|
||||
### D5: 明細表結構化上游資料
|
||||
|
||||
**選擇**:後端 `_build_detail_table` 的 `UPSTREAM_MACHINES` 欄位改為回傳 list of `{station, machine}` 對象。同時新增 `UPSTREAM_MATERIALS` (list of `{part, lot}`) 和 `UPSTREAM_WAFER_ROOT` (string) 欄位。CSV export 時 flatten 回逗號字串。
|
||||
|
||||
前端明細表不顯示全部上游機台,改為只顯示「嫌疑因子命中」:根據當前柏拉圖(含 inline filter)的 Top N 嫌疑名單,與該 LOT 的上游因子做交叉比對。
|
||||
|
||||
### D6: 嫌疑機台上下文面板 — Popover 或 Side Drawer
|
||||
|
||||
**選擇**:使用 Popover(點擊柏拉圖 bar 時彈出),內容包含:
|
||||
- 歸因數據:不良率、LOT 數、投入/報廢
|
||||
- 設備資訊:站點、機型 (RESOURCEFAMILYNAME)
|
||||
- 近期維修:呼叫 `GET /api/query-tool/lot-associations?type=jobs&container_id=<equipment_id>` 取近期 JOB 紀錄(需要以 equipment_id 查詢的 endpoint,可能需要新增或複用 equipment-period jobs)
|
||||
|
||||
**替代方案**:Side drawer 或 modal。
|
||||
**理由**:Popover 輕量、不離開當前分析上下文。維修資料只需 3-5 筆近期紀錄,不需要完整的 JOB 列表。
|
||||
|
||||
### D7: 報廢歷史查詢頁面新增 Pareto 維度
|
||||
|
||||
**選擇**:在現有 ParetoSection.vue 新增維度下拉選擇器。後端 `reject_history_service.py` 的 reason Pareto 邏輯改為可指定 `dimension` 參數(`reason` / `package` / `type` / `workflow` / `workcenter` / `equipment`)。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### R1: 原物料資料量
|
||||
原物料紀錄可能比 upstream_history 多很多(一個 LOT 可能消耗多種材料)。2000 個 LOT + 血緣的原物料查詢可能返回大量資料。
|
||||
→ **Mitigation**: EventFetcher 已有 batch + cache 機制。若資料量過大,可限制原物料歸因只取前幾種 material_part_name(如前 20 種),其餘歸為「其他」。
|
||||
|
||||
### R2: LineageEngine root 不一定是晶圓
|
||||
Split chain 的 root 可能不總是代表「晶圓批次」,取決於產品結構。某些產品線的 root 可能是其他中間製程的 LOT。
|
||||
→ **Mitigation**: 以 `root_container_name` 顯示,不硬標籤為「晶圓」。UI label 用「源頭批次」而非「晶圓」。
|
||||
|
||||
### R3: Staged trace events 增加 domain 的延遲
|
||||
新增 materials domain 會增加 events stage 的執行時間。
|
||||
→ **Mitigation**: EventFetcher 已支援 concurrent domain 查詢(ThreadPoolExecutor),materials 和 upstream_history 可並行。Cache TTL 300s 也能有效減輕重複查詢。
|
||||
|
||||
### R4: 嫌疑機台維修資料可能需要新的 API
|
||||
目前 query-tool 的 jobs API 是以 container_id 或 equipment + time_range 查詢,沒有「某台設備的近期 N 筆維修」的 endpoint。
|
||||
→ **Mitigation**: 新增一個輕量的 equipment-recent-jobs endpoint,或在前端直接用 equipment-period jobs API 查近 30 天即可。
|
||||
|
||||
### R5: 報廢歷史查詢的 Pareto 維度切換需要後端支援
|
||||
目前 Pareto 只支援按 reason 聚合,新增其他維度需要後端 SQL 重寫。
|
||||
→ **Mitigation**: 報廢歷史查詢使用 two-phase caching pattern(完整 DataFrame 已快取),在 view refresh 階段做不同維度的 groupby 即可,不需重新查 DB。
|
||||
@@ -0,0 +1,76 @@
|
||||
## Why
|
||||
|
||||
「製程不良追溯分析」頁面(mid-section-defect)目前的反向追溯只歸因到**上游機台**這單一維度。品質工程師在實務上需要同時比對多個因子(機台、原物料批號、源頭晶片)才能定位根因。現有的 EventFetcher 和 LineageEngine 已具備查詢原物料與血緣的能力,但分析頁面尚未整合這些資料來源。此外,頁面缺乏對歸因邏輯的透明度說明,使用者不了解數字是如何計算出來的。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 新增多因子歸因維度
|
||||
- 新增「依原物料歸因」柏拉圖:以 `material_part_name + material_lot` 為 key,計算歸因不良率(與機台歸因相同邏輯)
|
||||
- 新增「依源頭晶片歸因」柏拉圖:以 LineageEngine split chain 的 root ancestor (`CONTAINERNAME`) 為 key,計算歸因不良率
|
||||
- 移除原有的「依封裝 (PACKAGE)」與「依 TYPE」柏拉圖,改為上述兩張
|
||||
- 「依製程 (WORKFLOW)」柏拉圖移除(產品分布相關分析由「報廢歷史查詢」頁面負責)
|
||||
|
||||
### 柏拉圖改善
|
||||
- 新增排序 toggle:可切換「依不良數」/「依不良率」排序
|
||||
- 新增 80% 累計標記線(ECharts markLine)
|
||||
- Tooltip 增加顯示「關聯 LOT 數」
|
||||
|
||||
### 分析摘要面板
|
||||
- 在 KPI 卡片上方新增可收合的「分析摘要」區塊
|
||||
- 顯示:查詢條件、資料範圍統計(LOT 總數/投入/報廢 LOT 數/報廢總數/血緣追溯涵蓋數)、歸因邏輯文字說明
|
||||
|
||||
### 明細表嫌疑因子命中
|
||||
- 後端 `_build_detail_table` 改回傳結構化的上游機台資料(list of `{station, machine}` objects),取代扁平化逗號字串
|
||||
- 前端根據當前柏拉圖 Top N 嫌疑因子,在明細表顯示命中狀況(如 `WIRE-03, DIE-01 (2/3)`)
|
||||
- 嫌疑名單跟隨柏拉圖 inline filter 連動
|
||||
|
||||
### 嫌疑機台上下文面板
|
||||
- 點擊柏拉圖的機台 bar 時,顯示該機台的上下文面板
|
||||
- 面板內容:歸因數據摘要、所屬站點/機型、近期維修紀錄(透過 query-tool 的 `get_lot_jobs` API 取得)
|
||||
|
||||
### 後端:多因子歸因引擎
|
||||
- 在 `mid_section_defect_service.py` 新增 `_attribute_materials()` 函數(歸因邏輯與 `_attribute_defects` 相同 pattern)
|
||||
- 在 `mid_section_defect_service.py` 新增 `_attribute_wafer_roots()` 函數(以 root ancestor 為 key)
|
||||
- Staged trace API events stage 新增請求 `materials` domain 的支援(已在 EventFetcher 中支援,只需在 mid_section_defect profile 的 domain 列表中加入)
|
||||
- `_build_all_charts` 改為使用新的 DIMENSION_MAP(移除 by_package / by_pj_type / by_workflow,新增 by_material / by_wafer_root)
|
||||
|
||||
### 報廢歷史查詢頁面增強
|
||||
- 將「依 PACKAGE / TYPE / WORKFLOW」的產品分布分析遷移到報廢歷史查詢頁面
|
||||
- 在報廢歷史查詢頁面新增 Pareto 維度選擇器,支援多維度切換(原因、PACKAGE、TYPE、WORKFLOW、站點、機台)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `msd-multifactor-attribution`: 製程不良追溯分析的多因子歸因引擎(原物料、源頭晶片)及對應的柏拉圖呈現
|
||||
- `msd-analysis-transparency`: 分析摘要面板,顯示查詢條件、資料範圍、歸因邏輯說明
|
||||
- `msd-suspect-context`: 嫌疑機台上下文面板及明細表嫌疑因子命中呈現
|
||||
|
||||
### Modified Capabilities
|
||||
- `reject-history-page`: 新增產品分布 Pareto 維度(PACKAGE / TYPE / WORKFLOW),接收從製程不良追溯頁面遷出的分析責任
|
||||
- `trace-staged-api`: mid_section_defect profile 的 events stage 新增 `materials` domain 請求,aggregation 邏輯新增原物料與晶片歸因
|
||||
|
||||
## Impact
|
||||
|
||||
### Backend
|
||||
- `src/mes_dashboard/services/mid_section_defect_service.py` — 核心改動:新增歸因函數、修改 chart builder、修改 detail table 結構
|
||||
- `src/mes_dashboard/routes/trace_routes.py` — events stage 的 mid_section_defect profile domain 列表擴充
|
||||
- `src/mes_dashboard/routes/mid_section_defect_routes.py` — export 可能需要對應新欄位
|
||||
- `src/mes_dashboard/services/reject_history_service.py` — 新增 Pareto 維度支援
|
||||
- `src/mes_dashboard/routes/reject_history_routes.py` — 新增維度參數
|
||||
|
||||
### Frontend
|
||||
- `frontend/src/mid-section-defect/App.vue` — 主要改動:新增分析摘要、重排柏拉圖、嫌疑命中邏輯
|
||||
- `frontend/src/mid-section-defect/components/ParetoChart.vue` — 新增排序 toggle、80% markLine、tooltip lot_count
|
||||
- `frontend/src/mid-section-defect/components/DetailTable.vue` — 嫌疑命中欄位改版
|
||||
- `frontend/src/mid-section-defect/components/KpiCards.vue` — 可能微調
|
||||
- 新增 `frontend/src/mid-section-defect/components/AnalysisSummary.vue`
|
||||
- 新增 `frontend/src/mid-section-defect/components/SuspectContextPanel.vue`
|
||||
- `frontend/src/reject-history/components/ParetoSection.vue` — 新增維度選擇器
|
||||
- `frontend/src/reject-history/App.vue` — 支援多維度 Pareto
|
||||
|
||||
### SQL
|
||||
- 可能新增 `src/mes_dashboard/sql/mid_section_defect/upstream_materials.sql`(或直接複用 EventFetcher materials domain 的 `query_tool/lot_materials.sql`)
|
||||
|
||||
### Tests
|
||||
- `tests/test_mid_section_defect.py` — 新增原物料/晶片歸因的單元測試
|
||||
- `tests/test_reject_history_routes.py` — 新增維度 Pareto 測試
|
||||
@@ -0,0 +1,48 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Analysis page SHALL display a collapsible analysis summary panel
|
||||
The page SHALL show a summary panel above KPI cards explaining the query context, data scope, and attribution methodology.
|
||||
|
||||
#### Scenario: Summary panel rendering
|
||||
- **WHEN** backward analysis data is loaded
|
||||
- **THEN** a collapsible panel SHALL appear above the KPI cards
|
||||
- **THEN** the panel SHALL be expanded by default on first render
|
||||
- **THEN** the panel SHALL include a toggle control to collapse/expand
|
||||
|
||||
#### Scenario: Query context section
|
||||
- **WHEN** the summary panel is rendered
|
||||
- **THEN** it SHALL display the committed query parameters: detection station name, date range (or container mode info), and selected loss reasons (or「全部」if none selected)
|
||||
|
||||
#### Scenario: Data scope section
|
||||
- **WHEN** the summary panel is rendered
|
||||
- **THEN** it SHALL display:
|
||||
- 偵測站 LOT 總數 (total detection lots count)
|
||||
- 總投入 (total input qty in pcs)
|
||||
- 報廢 LOT 數 (lots with defects matching selected loss reasons)
|
||||
- 報廢總數 (total reject qty in pcs)
|
||||
- 血緣追溯涵蓋上游 LOT 數 (total unique ancestor count)
|
||||
|
||||
#### Scenario: Ancestor count from lineage response
|
||||
- **WHEN** lineage stage returns response
|
||||
- **THEN** the response SHALL include `total_ancestor_count` (number of unique ancestor CIDs across all seeds, excluding seeds themselves)
|
||||
- **THEN** the summary panel SHALL use this value for「血緣追溯涵蓋上游 LOT」
|
||||
|
||||
#### Scenario: Attribution methodology section
|
||||
- **WHEN** the summary panel is rendered
|
||||
- **THEN** it SHALL display a static text block explaining the attribution logic:
|
||||
- All LOTs passing through the detection station (including those with no defects) are included in analysis
|
||||
- Each LOT's upstream lineage (split/merge chain) is traced to identify associated upstream factors
|
||||
- Attribution rate = sum of associated LOTs' reject qty / sum of associated LOTs' input qty × 100%
|
||||
- The same defect can be attributed to multiple upstream factors (non-exclusive)
|
||||
- Pareto bar height = attributed defect count (with overlap), orange line = attributed defect rate
|
||||
|
||||
#### Scenario: Summary panel in container mode
|
||||
- **WHEN** query mode is container mode
|
||||
- **THEN** the query context section SHALL show the input type, resolved count, and not-found count instead of date range
|
||||
- **THEN** the data scope section SHALL still show LOT count and input/reject totals
|
||||
|
||||
#### Scenario: Summary panel collapsed state persistence
|
||||
- **WHEN** user collapses the summary panel
|
||||
- **THEN** the collapsed state SHALL persist within the current session (sessionStorage)
|
||||
- **WHEN** user triggers a new query
|
||||
- **THEN** the panel SHALL remain in its current collapsed/expanded state
|
||||
@@ -0,0 +1,93 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Backward tracing SHALL attribute defects to upstream materials
|
||||
The system SHALL compute material-level attribution using the same pattern as machine attribution: for each material `(part_name, lot_name)` consumed by detection lots or their ancestors, calculate the defect rate among associated detection lots.
|
||||
|
||||
#### Scenario: Materials attribution data flow
|
||||
- **WHEN** backward tracing events stage completes with `upstream_history` and `materials` domains
|
||||
- **THEN** the aggregation engine SHALL build a `material_key → detection_lots` mapping where `material_key = (MATERIALPARTNAME, MATERIALLOTNAME)`
|
||||
- **THEN** for each material key, `attributed_defect_rate = Σ(REJECTQTY of associated detection lots) / Σ(TRACKINQTY of associated detection lots) × 100`
|
||||
|
||||
#### Scenario: Materials domain requested in backward trace
|
||||
- **WHEN** the frontend executes backward tracing with `mid_section_defect` profile
|
||||
- **THEN** the events stage SHALL request domains `['upstream_history', 'materials']`
|
||||
- **THEN** the `materials` domain SHALL use the existing EventFetcher materials domain (querying `LOTMATERIALSHISTORY`)
|
||||
|
||||
#### Scenario: Materials Pareto chart rendering
|
||||
- **WHEN** materials attribution data is available
|
||||
- **THEN** the frontend SHALL render a Pareto chart titled「依原物料歸因」
|
||||
- **THEN** each bar SHALL represent a `material_part_name (material_lot_name)` combination
|
||||
- **THEN** the chart SHALL show Top 10 items sorted by defect_qty, with remaining items grouped as「其他」
|
||||
- **THEN** tooltip SHALL display: material name, material lot, defect count, input count, defect rate, cumulative %, and associated LOT count
|
||||
|
||||
#### Scenario: Material with no lot name
|
||||
- **WHEN** a material record has `MATERIALLOTNAME` as NULL or empty
|
||||
- **THEN** the material key SHALL use `material_part_name` only (without lot suffix)
|
||||
- **THEN** display label SHALL show the part name without parenthetical lot
|
||||
|
||||
### Requirement: Backward tracing SHALL attribute defects to wafer root ancestors
|
||||
The system SHALL compute root-ancestor-level attribution by identifying the split chain root for each detection lot and calculating defect rates per root.
|
||||
|
||||
#### Scenario: Root ancestor identification
|
||||
- **WHEN** lineage stage returns `ancestors` data (child_to_parent map)
|
||||
- **THEN** the backend SHALL identify root ancestors by traversing the parent chain for each seed until reaching a container with no further parent
|
||||
- **THEN** roots SHALL be returned as `{seed_container_id: root_container_name}` in the lineage response
|
||||
|
||||
#### Scenario: Root attribution calculation
|
||||
- **WHEN** root mapping is available
|
||||
- **THEN** the aggregation engine SHALL build a `root_container_name → detection_lots` mapping
|
||||
- **THEN** for each root, `attributed_defect_rate = Σ(REJECTQTY) / Σ(TRACKINQTY) × 100`
|
||||
|
||||
#### Scenario: Wafer root Pareto chart rendering
|
||||
- **WHEN** root attribution data is available
|
||||
- **THEN** the frontend SHALL render a Pareto chart titled「依源頭批次歸因」
|
||||
- **THEN** each bar SHALL represent a root ancestor `CONTAINERNAME`
|
||||
- **THEN** the chart SHALL show Top 10 items with cumulative percentage line
|
||||
|
||||
#### Scenario: Detection lot with no ancestors
|
||||
- **WHEN** a detection lot has no split chain ancestors (it is its own root)
|
||||
- **THEN** the root mapping SHALL map the lot to its own `CONTAINERNAME`
|
||||
|
||||
### Requirement: Backward Pareto layout SHALL show 5 charts in machine/material/wafer/reason/detection arrangement
|
||||
The backward tracing chart section SHALL display exactly 5 Pareto charts replacing the previous 6-chart layout.
|
||||
|
||||
#### Scenario: Chart grid layout
|
||||
- **WHEN** backward analysis data is rendered
|
||||
- **THEN** charts SHALL be arranged as:
|
||||
- Row 1: 依上游機台歸因 | 依原物料歸因
|
||||
- Row 2: 依源頭批次歸因 | 依不良原因
|
||||
- Row 3: 依偵測機台 (full width or single)
|
||||
- **THEN** the previous「依 WORKFLOW」「依 PACKAGE」「依 TYPE」charts SHALL NOT be rendered
|
||||
|
||||
### Requirement: Pareto charts SHALL support sort toggle between defect count and defect rate
|
||||
Each Pareto chart SHALL allow the user to switch between sorting by defect quantity and defect rate.
|
||||
|
||||
#### Scenario: Default sort order
|
||||
- **WHEN** a Pareto chart is first rendered
|
||||
- **THEN** bars SHALL be sorted by `defect_qty` descending (current behavior)
|
||||
|
||||
#### Scenario: Sort by rate toggle
|
||||
- **WHEN** user clicks the sort toggle to「依不良率」
|
||||
- **THEN** bars SHALL re-sort by `defect_rate` descending
|
||||
- **THEN** cumulative percentage line SHALL recalculate based on the new sort order
|
||||
- **THEN** the toggle SHALL visually indicate the active sort mode
|
||||
|
||||
#### Scenario: Sort toggle persistence within session
|
||||
- **WHEN** user changes sort mode on one chart
|
||||
- **THEN** the change SHALL only affect that specific chart (not all charts)
|
||||
|
||||
### Requirement: Pareto charts SHALL display an 80% cumulative reference line
|
||||
Each Pareto chart SHALL include a horizontal dashed line at the 80% cumulative mark.
|
||||
|
||||
#### Scenario: 80% markLine rendering
|
||||
- **WHEN** Pareto chart data is rendered with cumulative percentages
|
||||
- **THEN** the chart SHALL display a horizontal dashed line at y=80 on the percentage axis
|
||||
- **THEN** the line SHALL use a muted color (e.g., `#94a3b8`) with dotted style
|
||||
- **THEN** the line label SHALL display「80%」
|
||||
|
||||
### Requirement: Pareto chart tooltip SHALL include LOT count
|
||||
Each Pareto chart tooltip SHALL show the number of associated detection LOTs.
|
||||
|
||||
#### Scenario: Tooltip with LOT count
|
||||
- **WHEN** user hovers over a Pareto bar
|
||||
- **THEN** the tooltip SHALL display: factor name, 關聯 LOT count (with percentage of total), defect count, input count, defect rate, cumulative percentage
|
||||
@@ -0,0 +1,77 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Detail table SHALL display suspect factor hit counts instead of raw upstream machine list
|
||||
The backward detail table SHALL replace the flat `UPSTREAM_MACHINES` string column with a structured suspect factor hit display that links to the current Pareto Top N.
|
||||
|
||||
#### Scenario: Suspect hit column rendering
|
||||
- **WHEN** backward detail table is rendered
|
||||
- **THEN** the「上游機台」column SHALL be replaced by a「嫌疑命中」column
|
||||
- **THEN** each cell SHALL show the names of upstream machines that appear in the current Pareto Top N suspect list, with a hit ratio (e.g., `WIRE-03, DIE-01 (2/5)`)
|
||||
|
||||
#### Scenario: Suspect list derived from Pareto Top N
|
||||
- **WHEN** the machine Pareto chart displays Top N machines (after any inline station/spec filters)
|
||||
- **THEN** the suspect list SHALL be the set of machine names from those Top N entries
|
||||
- **THEN** changing the Pareto inline filters SHALL update the suspect list and re-render the hit column
|
||||
|
||||
#### Scenario: Full match indicator
|
||||
- **WHEN** a LOT's upstream machines include all machines in the suspect list
|
||||
- **THEN** the cell SHALL display a visual indicator (e.g., star or highlight) marking full match
|
||||
|
||||
#### Scenario: No hits
|
||||
- **WHEN** a LOT's upstream machines include none of the suspect machines
|
||||
- **THEN** the cell SHALL display「-」
|
||||
|
||||
#### Scenario: Upstream machine count column
|
||||
- **WHEN** backward detail table is rendered
|
||||
- **THEN** the「上游LOT數」column SHALL remain as-is (showing ancestor count)
|
||||
- **THEN** a new「上游台數」column SHALL show the total number of unique upstream machines for that LOT
|
||||
|
||||
### Requirement: Backend detail table SHALL return structured upstream data
|
||||
The `_build_detail_table` function SHALL return upstream machines as a structured list instead of a flat comma-separated string.
|
||||
|
||||
#### Scenario: Structured upstream machines response
|
||||
- **WHEN** backward detail API returns LOT records
|
||||
- **THEN** each record's `UPSTREAM_MACHINES` field SHALL be a list of `{"station": "<workcenter_group>", "machine": "<equipment_name>"}` objects
|
||||
- **THEN** the flat comma-separated string SHALL no longer be returned in this field
|
||||
|
||||
#### Scenario: CSV export backward compatibility
|
||||
- **WHEN** CSV export is triggered for backward detail
|
||||
- **THEN** the `UPSTREAM_MACHINES` column in CSV SHALL flatten the structured list back to comma-separated `station/machine` format
|
||||
- **THEN** CSV format SHALL remain unchanged from current behavior
|
||||
|
||||
#### Scenario: Structured upstream materials response
|
||||
- **WHEN** materials attribution is available
|
||||
- **THEN** each detail record SHALL include an `UPSTREAM_MATERIALS` field: list of `{"part": "<material_part_name>", "lot": "<material_lot_name>"}` objects
|
||||
|
||||
#### Scenario: Structured wafer root response
|
||||
- **WHEN** root ancestor attribution is available
|
||||
- **THEN** each detail record SHALL include a `WAFER_ROOT` field: string with root ancestor `CONTAINERNAME`
|
||||
|
||||
### Requirement: Suspect machine context panel SHALL show machine details and recent maintenance
|
||||
Clicking a machine bar in the Pareto chart SHALL open a context popover showing machine attribution details and recent maintenance history.
|
||||
|
||||
#### Scenario: Context panel trigger
|
||||
- **WHEN** user clicks a bar in the「依上游機台歸因」Pareto chart
|
||||
- **THEN** a popover panel SHALL appear near the clicked bar
|
||||
- **WHEN** user clicks outside the popover or clicks the same bar again
|
||||
- **THEN** the popover SHALL close
|
||||
|
||||
#### Scenario: Context panel content - attribution summary
|
||||
- **WHEN** the context panel is displayed
|
||||
- **THEN** it SHALL show: equipment name, workcenter group, resource family (RESOURCEFAMILYNAME), attributed defect rate, attributed defect count, attributed input count, associated LOT count
|
||||
|
||||
#### Scenario: Context panel content - recent maintenance
|
||||
- **WHEN** the context panel is displayed
|
||||
- **THEN** it SHALL fetch recent JOB records for the machine's equipment_id (last 30 days)
|
||||
- **THEN** it SHALL display up to 5 most recent JOB records showing: JOBID, JOBSTATUS, JOBMODELNAME, CREATEDATE, COMPLETEDATE
|
||||
- **WHEN** the machine has no recent JOB records
|
||||
- **THEN** the maintenance section SHALL display「近 30 天無維修紀錄」
|
||||
|
||||
#### Scenario: Context panel loading state
|
||||
- **WHEN** maintenance data is being fetched
|
||||
- **THEN** the maintenance section SHALL show a loading indicator
|
||||
- **THEN** the attribution summary section SHALL render immediately (data already available from attribution)
|
||||
|
||||
#### Scenario: Context panel for non-machine charts
|
||||
- **WHEN** user clicks bars in other Pareto charts (materials, wafer root, loss reason, detection machine)
|
||||
- **THEN** no context panel SHALL appear (machine context only)
|
||||
@@ -0,0 +1,93 @@
|
||||
## 1. Backend: Multi-factor attribution engine
|
||||
|
||||
- [x] 1.1 Add `_attribute_materials()` to `mid_section_defect_service.py` — symmetric to `_attribute_defects()`, keyed by `(MATERIALPARTNAME, MATERIALLOTNAME)`, handles NULL lot name gracefully
|
||||
- [x] 1.2 Add `_attribute_wafer_roots()` to `mid_section_defect_service.py` — keyed by `root_container_name`, builds `root → detection_lots` mapping from lineage roots
|
||||
- [x] 1.3 Update `DIMENSION_MAP` — remove `by_package`, `by_pj_type`, `by_workflow`; add `by_material`, `by_wafer_root`
|
||||
- [x] 1.4 Update `_build_all_charts()` to call the new attribution functions for `by_material` and `by_wafer_root` dimensions
|
||||
- [x] 1.5 Add `lot_count` field to each Pareto bar entry in `_build_chart_data()` (number of associated detection LOTs for that factor)
|
||||
|
||||
## 2. Backend: Lineage root extraction
|
||||
|
||||
- [x] 2.1 Add root identification logic to `lineage_engine.py` — traverse `child_to_parent` map to find the node with no further parent for each seed
|
||||
- [x] 2.2 Include `roots` field (`{seed_cid: root_container_name}`) in lineage stage response
|
||||
- [x] 2.3 Pass `roots` through `build_trace_aggregation_from_events()` into aggregation context
|
||||
|
||||
## 3. Backend: Staged trace materials domain
|
||||
|
||||
- [x] 3.1 In `trace_routes.py` events stage, add `materials` to the domain list for `mid_section_defect` profile backward mode
|
||||
- [x] 3.2 Wire materials domain records through `_flatten_domain_records()` into aggregation input
|
||||
|
||||
## 4. Backend: Structured detail table
|
||||
|
||||
- [x] 4.1 Modify `_build_detail_table()` — change `UPSTREAM_MACHINES` from comma-separated string to list of `{"station": "...", "machine": "..."}` objects
|
||||
- [x] 4.2 Add `UPSTREAM_MATERIALS` field to detail records — list of `{"part": "...", "lot": "..."}` objects (when materials data is available)
|
||||
- [x] 4.3 Add `WAFER_ROOT` field to detail records — root ancestor `CONTAINERNAME` string
|
||||
- [x] 4.4 Add `UPSTREAM_MACHINE_COUNT` field to detail records — count of unique upstream machines per LOT
|
||||
- [x] 4.5 Update CSV export in `mid_section_defect_routes.py` — flatten structured `UPSTREAM_MACHINES` back to comma-separated `station/machine` format for CSV compatibility
|
||||
|
||||
## 5. Backend: Equipment recent jobs endpoint
|
||||
|
||||
- [x] 5.1 Add `GET /api/query-tool/equipment-recent-jobs/<equipment_id>` endpoint in `query_tool_routes.py` — query `DW_MES_JOB` for last 30 days, return top 5 most recent JOB records (JOBID, JOBSTATUS, JOBMODELNAME, CREATEDATE, COMPLETEDATE)
|
||||
- [x] 5.2 Add SQL file `src/mes_dashboard/sql/query_tool/equipment_recent_jobs.sql` for the query
|
||||
|
||||
## 6. Backend: Reject history Pareto dimensions
|
||||
|
||||
- [x] 6.1 Add `dimension` parameter to `query_reason_pareto()` in `reject_history_service.py` — support `reason` (default), `package`, `type`, `workflow`, `workcenter`, `equipment` as groupby keys
|
||||
- [x] 6.2 Update `reject_history_routes.py` to accept and pass `dimension` query parameter
|
||||
- [x] 6.3 Ensure two-phase caching still works (groupby from cached DataFrame, no re-query)
|
||||
|
||||
## 7. Backend: Analysis summary data
|
||||
|
||||
- [x] 7.1 Add `total_ancestor_count` to lineage stage response — count of unique ancestor CIDs (excluding seed CIDs)
|
||||
- [x] 7.2 Ensure backward aggregation response includes summary fields: total detection lots, total input qty, defective lot count, total reject qty, ancestor coverage count
|
||||
|
||||
## 8. Frontend: Multi-factor Pareto charts
|
||||
|
||||
- [x] 8.1 Update `App.vue` backward chart section — replace 6-chart layout with 5-chart layout (2-2-1): machine | material, wafer_root | loss_reason, detection_machine
|
||||
- [x] 8.2 Add chart builder functions for materials and wafer root attribution data (same pattern as `buildMachineChartFromAttribution`)
|
||||
- [x] 8.3 Update `useTraceProgress.js` — in backward mode, request `domains: ['upstream_history', 'materials']`
|
||||
- [x] 8.4 Wire new chart data through session caching (save/load from sessionStorage)
|
||||
|
||||
## 9. Frontend: Pareto chart enhancements (ParetoChart.vue)
|
||||
|
||||
- [x] 9.1 Add sort toggle button (依不良數 / 依不良率) — per-chart state, re-sort data and recalculate cumulative %
|
||||
- [x] 9.2 Add 80% cumulative markLine — horizontal dashed line at y=80 on percentage axis, muted color `#94a3b8`, label「80%」
|
||||
- [x] 9.3 Add `lot_count` to tooltip formatter — show「關聯 LOT 數: N (xx%)」
|
||||
|
||||
## 10. Frontend: Analysis summary panel
|
||||
|
||||
- [x] 10.1 Create `AnalysisSummary.vue` component — collapsible panel with query context, data scope stats, and attribution methodology text
|
||||
- [x] 10.2 Integrate into `App.vue` above KPI cards — pass query params and summary data as props
|
||||
- [x] 10.3 Handle container mode variant (show input type and resolved count instead of date range)
|
||||
- [x] 10.4 Persist collapsed/expanded state in sessionStorage
|
||||
|
||||
## 11. Frontend: Detail table suspect hit column
|
||||
|
||||
- [x] 11.1 Update `DetailTable.vue` — replace「上游機台」column with「嫌疑命中」column
|
||||
- [x] 11.2 Implement suspect list derivation — extract machine names from current Pareto Top N (respecting inline station/spec filters)
|
||||
- [x] 11.3 Render hit cell: show matching machine names with ratio (e.g., `WIRE-03, DIE-01 (2/5)`), star/highlight for full match,「-」for no hits
|
||||
- [x] 11.4 Add「上游台數」column showing total unique upstream machine count per LOT
|
||||
- [x] 11.5 Make suspect list reactive to Pareto inline filter changes
|
||||
|
||||
## 12. Frontend: Suspect machine context panel
|
||||
|
||||
- [x] 12.1 Create `SuspectContextPanel.vue` — popover component with attribution summary section and maintenance section
|
||||
- [x] 12.2 Attribution summary content: equipment name, workcenter group, resource family, defect rate, defect count, input count, LOT count (all available from existing attribution data)
|
||||
- [x] 12.3 Maintenance section: fetch recent JOB records from `/api/query-tool/equipment-recent-jobs/<equipment_id>`, show up to 5 records; loading state while fetching;「近 30 天無維修紀錄」when empty
|
||||
- [x] 12.4 Integrate with ParetoChart.vue — emit click event on bar for「依上游機台歸因」chart only; position popover near clicked bar
|
||||
- [x] 12.5 Close on outside click or re-click of same bar
|
||||
|
||||
## 13. Frontend: Reject history Pareto dimensions
|
||||
|
||||
- [x] 13.1 Add dimension selector dropdown to `ParetoSection.vue` in reject-history — options: 不良原因, PACKAGE, TYPE, WORKFLOW, 站點, 機台
|
||||
- [x] 13.2 Update API call to pass `dimension` parameter
|
||||
- [x] 13.3 Update `App.vue` in reject-history to wire dimension state
|
||||
|
||||
## 14. Tests
|
||||
|
||||
- [x] 14.1 Add unit tests for `_attribute_materials()` in `tests/test_mid_section_defect.py` — verify correct rate calculation, NULL lot name handling
|
||||
- [x] 14.2 Add unit tests for `_attribute_wafer_roots()` — verify root mapping, self-root case
|
||||
- [x] 14.3 Add unit tests for structured `_build_detail_table()` output — verify list format, CSV flatten
|
||||
- [x] 14.4 Add tests for equipment-recent-jobs endpoint in `tests/test_query_tool_routes.py`
|
||||
- [x] 14.5 Add tests for reject history dimension Pareto in `tests/test_reject_history_routes.py`
|
||||
- [x] 14.6 Run full test suite and fix regressions
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-02
|
||||
@@ -0,0 +1,28 @@
|
||||
## Context
|
||||
|
||||
The QC-GATE LOT detail table (`LotTable.vue`) displays 9 columns sourced from the `/api/qc-gate/summary` API. The backend (`qc_gate_service.py`) builds each lot payload from the Redis-cached WIP snapshot of `DW_MES_LOT_V`. The `PACKAGE_LEF` column already exists in the WIP data but is currently only used as a fallback for the `product` field — it is never exposed independently.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Expose `PACKAGE_LEF` as a dedicated `package` field in each lot's API payload.
|
||||
- Display a "Package" column in the LOT detail table, positioned immediately after "LOT ID".
|
||||
- Keep the column sortable, consistent with existing column behavior.
|
||||
|
||||
**Non-Goals:**
|
||||
- Changing the Product column's fallback logic (it still falls through to `PACKAGE_LEF` when `PRODUCT` is null).
|
||||
- Adding chart-level grouping or filtering by package.
|
||||
- Modifying the DB view or Redis cache schema — `PACKAGE_LEF` is already available.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Field name: `package`** — Maps directly to `PACKAGE_LEF` from `DW_MES_LOT_V`. Simple, descriptive, consistent with existing snake_case payload keys (`lot_id`, `wait_hours`).
|
||||
|
||||
2. **Column position: after LOT ID (index 1)** — The user explicitly requested "放在LOT ID之後". Insert into `HEADERS` array at index 1, shifting Product and subsequent columns right.
|
||||
|
||||
3. **No backend query changes** — `PACKAGE_LEF` is already present in the WIP cache DataFrame. We just read it in `_build_lot_payload()`, same as other fields.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Wide table on small screens** → The table already has horizontal scroll (`lot-table-scroll`); adding one more column is acceptable.
|
||||
- **Null values** → Many lots may have `PACKAGE_LEF = NULL`. The existing `formatValue()` helper already renders `'-'` for nulls, so no special handling needed.
|
||||
@@ -0,0 +1,24 @@
|
||||
## Why
|
||||
|
||||
The QC-GATE LOT detail table currently lacks a dedicated Package column. The `PACKAGE_LEF` field from `DW_MES_LOT_V` is only used as a fallback for the Product column, making it invisible when Product has a value. Users need to see the package (lead-frame) information alongside the LOT ID to quickly identify packaging context during QC-GATE monitoring.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a new **Package** column to the QC-GATE LOT detail table, positioned immediately after the LOT ID column.
|
||||
- Expose the `PACKAGE_LEF` field from the WIP cache as a dedicated `package` field in the API response payload.
|
||||
- No existing columns are removed or reordered beyond the insertion point.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
_(none — this is a column addition to an existing capability)_
|
||||
|
||||
### Modified Capabilities
|
||||
- `qc-gate-status`: Add `package` field to LOT payload and display it as a new column after LOT ID in the detail table.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Backend**: `qc_gate_service.py` — `_build_lot_payload()` adds a `package` key.
|
||||
- **Frontend**: `LotTable.vue` — `HEADERS` array gains a new entry; template adds a `<td>` cell.
|
||||
- **API**: `/api/qc-gate/summary` response shape gains `package` in each lot object (additive, non-breaking).
|
||||
- **No DB changes**: `PACKAGE_LEF` already exists in `DW_MES_LOT_V` and is present in the Redis WIP cache.
|
||||
@@ -0,0 +1,50 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: System SHALL provide QC-GATE LOT status API
|
||||
The system SHALL provide an API endpoint that returns real-time LOT status for all QC-GATE stations, with wait time classification.
|
||||
|
||||
#### Scenario: Retrieve QC-GATE summary
|
||||
- **WHEN** user sends GET `/api/qc-gate/summary`
|
||||
- **THEN** the system SHALL return all LOTs whose `SPECNAME` contains both "QC" and "GATE" (case-insensitive)
|
||||
- **THEN** each LOT SHALL include `wait_hours` calculated as `(SYS_DATE - MOVEINTIMESTAMP)` in hours
|
||||
- **THEN** each LOT SHALL be classified into a time bucket: `lt_6h`, `6h_12h`, `12h_24h`, or `gt_24h`
|
||||
- **THEN** each LOT SHALL include a `package` field sourced from the `PACKAGE_LEF` column of `DW_MES_LOT_V`
|
||||
- **THEN** the response SHALL include per-station bucket counts and the full lot list
|
||||
|
||||
#### Scenario: QC-GATE data sourced from WIP cache
|
||||
- **WHEN** the API is called
|
||||
- **THEN** the system SHALL read from the existing WIP Redis cache (not direct Oracle query)
|
||||
- **THEN** the response SHALL include `cache_time` indicating the WIP snapshot timestamp
|
||||
|
||||
#### Scenario: No QC-GATE lots in cache
|
||||
- **WHEN** no LOTs match the QC-GATE SPECNAME pattern
|
||||
- **THEN** the system SHALL return an empty `stations` array with `cache_time`
|
||||
|
||||
### Requirement: QC-GATE report page SHALL display filterable LOT table
|
||||
The page SHALL display a table listing individual LOTs, with click-to-filter interaction from the bar chart.
|
||||
|
||||
#### Scenario: Default table display
|
||||
- **WHEN** the page loads
|
||||
- **THEN** the table SHALL show all QC-GATE LOTs sorted by wait time descending
|
||||
- **THEN** the table SHALL display a "Package" column immediately after the "LOT ID" column
|
||||
|
||||
#### Scenario: Package column displays PACKAGE_LEF value
|
||||
- **WHEN** a LOT has a non-null `PACKAGE_LEF` value
|
||||
- **THEN** the Package column SHALL display the `package` field value
|
||||
|
||||
#### Scenario: Package column with null value
|
||||
- **WHEN** a LOT has a null or empty `PACKAGE_LEF` value
|
||||
- **THEN** the Package column SHALL display a dash (`-`)
|
||||
|
||||
#### Scenario: Package column is sortable
|
||||
- **WHEN** user clicks the "Package" column header
|
||||
- **THEN** the table SHALL sort rows by package value alphabetically (ascending on first click, toggling on subsequent clicks)
|
||||
|
||||
#### Scenario: Click bar chart to filter
|
||||
- **WHEN** user clicks a specific segment of a bar (e.g., QC-GATE-DB's 6-12hr segment)
|
||||
- **THEN** the table SHALL filter to show only LOTs matching that station AND time bucket
|
||||
- **THEN** a filter indicator SHALL be visible showing the active filter
|
||||
|
||||
#### Scenario: Clear filter
|
||||
- **WHEN** user clicks the active filter indicator or clicks the same bar segment again
|
||||
- **THEN** the table SHALL return to showing all QC-GATE LOTs
|
||||
@@ -0,0 +1,13 @@
|
||||
## 1. Backend — Expose package field in API payload
|
||||
|
||||
- [x] 1.1 In `src/mes_dashboard/services/qc_gate_service.py`, add `'package': _safe_value(row.get('PACKAGE_LEF'))` to the dict returned by `_build_lot_payload()`
|
||||
|
||||
## 2. Frontend — Add Package column to LOT detail table
|
||||
|
||||
- [x] 2.1 In `frontend/src/qc-gate/components/LotTable.vue`, insert `{ key: 'package', label: 'Package' }` into the `HEADERS` array at index 1 (after LOT ID)
|
||||
- [x] 2.2 In the `<tbody>` template, add `<td>{{ formatValue(lot.package) }}</td>` after the `lot_id` cell
|
||||
|
||||
## 3. Verification
|
||||
|
||||
- [x] 3.1 Run existing backend tests to confirm no regressions
|
||||
- [x] 3.2 Run existing frontend tests/lint to confirm no regressions
|
||||
Reference in New Issue
Block a user