feat(mid-section-defect): full-line bidirectional defect trace center with dual query mode

Transform /mid-section-defect from TMTT-only backward analysis into a full-line
bidirectional defect traceability center supporting all detection stations.

Key changes:
- Parameterized station detection: any workcenter group as detection station
- Bidirectional tracing: backward (upstream attribution) + forward (downstream reject rates)
- Dual query mode: date range OR LOT/工單/WAFER container-based seed resolution
- Multi-select filters for upstream station, equipment model (RESOURCEFAMILYNAME), and loss reasons
- Progressive 3-stage trace pipeline (seed-resolve → lineage → events) with streaming UI
- Equipment model lookup via resource cache instead of SPECNAME
- Session caching, auto-refresh, searchable MultiSelect with fuzzy matching
- Remove legacy tmtt-defect module (fully superseded)
- Archive openspec change artifacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-02-24 16:16:33 +08:00
parent bb58a0e119
commit f14591c7dc
67 changed files with 2957 additions and 2931 deletions

View File

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

View File

@@ -0,0 +1,75 @@
## Context
`/mid-section-defect` currently runs a 3-stage backward-only pipeline hardcoded to TMTT (測試) station:
1. `tmtt_detection.sql` — fetch defective lots at TMTT station
2. `LineageEngine.resolve_full_genealogy()` — find ancestor container IDs
3. `upstream_history.sql` — get WIP records at upstream stations → attribute defects to machines
The detection SQL has `LIKE '%TMTT%'` hardcoded on line 38. All internal naming uses `TMTT_` prefix. The page serves one direction (backward) for one station.
This change generalizes to any of the 12 workcenter groups as detection station, adds forward tracing direction, and removes the superseded `/tmtt-defect` page.
## Goals / Non-Goals
**Goals:**
- Parameterize detection station: replace TMTT hardcode with `{{ STATION_FILTER }}` built from `workcenter_groups.py` patterns
- Add forward tracing pipeline: detection rejects → forward lineage → downstream WIP + rejects → forward attribution
- Direction-aware UI: FilterBar station dropdown + direction toggle, KPI/charts/detail switch by direction
- Backward compatibility: `station=測試, direction=backward` produces identical results (renamed columns)
- Remove `/tmtt-defect` page and all associated code
**Non-Goals:**
- No changes to LineageEngine internals (already supports both `resolve_full_genealogy` and `resolve_forward_tree`)
- No changes to `reject-history` or `query-tool` pages
- No new caching strategy (reuse existing L1/L2 cache with station+direction in key)
- No multi-station or multi-direction in a single query
## Decisions
### D1: Parameterized SQL via template substitution (not dynamic SQL builder)
Use `SQLLoader.load_with_params()` with `{{ STATION_FILTER }}` placeholder — the same pattern already used by `upstream_history.sql`'s `{{ ANCESTOR_FILTER }}`. The filter is built in Python from `WORKCENTER_GROUPS[station]['patterns']` as OR-LIKE clauses with bind parameters.
**Alternative considered:** Dynamic SQL builder class. Rejected — adds abstraction for a simple OR-LIKE pattern; template substitution is established in the codebase.
### D2: Separate `station_detection.sql` instead of modifying `tmtt_detection.sql`
Create new `station_detection.sql` as a generalized copy. The old `tmtt_detection.sql` will be deleted when `/tmtt-defect` is removed. Clean separation avoids merge conflicts with any in-flight tmtt-defect work.
**Alternative considered:** Modify in-place. Rejected — the old file is deleted anyway and renaming avoids ambiguity.
### D3: Forward attribution uses TRACKINQTY as denominator
Forward reject rate = `REJECT_TOTAL_QTY / TRACKINQTY × 100` at each downstream station. TRACKINQTY comes from `upstream_history.sql` (needs adding to SELECT). This gives a per-station defect rate for lots that survived the detection station.
**Alternative considered:** Use lot count as denominator. Rejected — TRACKINQTY accounts for partial quantities (split/merge lots) and gives a more accurate rate.
### D4: Direction dispatch at service layer, not route layer
`query_analysis()` gains `station` and `direction` params and dispatches to `_run_backward_pipeline()` or `_run_forward_pipeline()` internally. Routes just pass through. This keeps route handlers thin and testable.
### D5: Forward pipeline reuses upstream_history.sql for WIP records
Both directions need WIP records at various stations. The existing `upstream_history.sql` (with added TRACKINQTY) serves both — just with different container ID sets (ancestors for backward, descendants for forward).
### D6: New `downstream_rejects` event domain in EventFetcher
Forward tracing needs reject records at downstream stations. Add `downstream_rejects` as a new domain in `EventFetcher._build_domain_sql()`, loading `downstream_rejects.sql` with batched IN clause. This follows the established domain pattern.
### D7: Frontend direction toggle — button group, not dropdown
Two discrete states (backward/forward) fit a toggle button group better than a dropdown. Matches the existing btn-primary pattern in the page's CSS.
### D8: Remove `/tmtt-defect` entirely
The generalized traceability center with `station=測試 + lossReasons=[276_腳型不良, 277_印字不良]` reproduces all tmtt-defect functionality. Remove: `frontend/src/tmtt-defect/`, backend routes/services/SQL, test files, and `nativeModuleRegistry.js` registration.
## Risks / Trade-offs
- **Forward pipeline performance for early stations** — Selecting `station=切割 (order=0), direction=forward` could produce a very large descendant tree (all lots flow downstream). → Mitigation: The existing `resolve_forward_tree()` already handles large sets; add a result count warning in UI if > 5000 tracked lots.
- **TRACKINQTY NULL values** — Some WIP records may have NULL TRACKINQTY. → Mitigation: COALESCE to 0 in SQL; skip lots with zero input in attribution to avoid division by zero.
- **TMTT removal breaks bookmarks** — Users with `/tmtt-defect` bookmarks get 404. → Mitigation: Low risk — page was in dev status, not released. No redirect needed.
- **Rename TMTT_ → DETECTION_ in API response keys** — Frontend consumers (CSV export, chart keys) reference these field names. → Mitigation: All consumers are within this page's code; rename consistently in one pass.

View File

@@ -0,0 +1,32 @@
## Why
`/mid-section-defect` 目前僅支援 TMTT 測試站的反向不良追溯,偵測站硬編碼在 SQL 中。實務上需要從任意站點偵測不良並雙向追溯:後段不良回推上游集中機台(反向),前段報廢後倖存批次的下游表現(正向)。將此頁面升級為全線雙向追溯中心,覆蓋 12 個 workcenter group × 2 方向的分析需求。同時移除功能被完全取代的 `/tmtt-defect`TMTT印字腳型不良分析頁面。
## What Changes
- **偵測站泛化**:將硬編碼的 TMTT 站點篩選改為參數化,使用者可從 12 個 workcenter group 中選擇任意偵測站
- **反向追溯泛化**:現有 TMTT → 上游機台歸因邏輯保留,但偵測站改為可選(預設仍為「測試」)
- **新增正向追溯**:偵測站報廢批次 → 追蹤倖存批次往下游走 → 各下游站的額外報廢率(判斷部分報廢後剩餘品是否仍有問題)
- **UI 改版**FilterBar 新增偵測站下拉 + 方向切換KPI/圖表/明細表依方向動態切換
- **重新命名**:頁面標題從「中段製程不良追溯」改為「製程不良追溯分析」,內部 TMTT_ 前綴統一改為 DETECTION_
- **移除 TMTT 印字腳型不良分析**`/tmtt-defect` 頁面功能已被泛化後的追溯中心完全覆蓋(選偵測站=測試 + 篩選不良原因=276_腳型不良/277_印字不良移除前後端代碼與路由註冊
## Capabilities
### New Capabilities
- `defect-trace-station-detection`: 參數化偵測站 SQL 與篩選邏輯,支援任意 workcenter group 作為偵測起點
- `defect-trace-forward-pipeline`: 正向追溯 pipeline — 偵測站報廢批次 → forward lineage → 下游 WIP + 下游報廢記錄 → 正向歸因引擎
- `defect-trace-bidirectional-ui`: 雙向追溯前端 — 偵測站選擇器、方向切換、方向感知的 KPI/圖表/明細表/CSV 匯出
### Modified Capabilities
- `progressive-trace-ux`: 需擴展支援 direction 參數lineage stage 依方向選擇 ancestor 或 forward tree
- `event-fetcher-unified`: 新增 `downstream_rejects` event domain
## Impact
- **Backend**: `mid_section_defect_service.py`(主要重構)、`mid_section_defect_routes.py``trace_routes.py``event_fetcher.py`
- **SQL**: 新增 `station_detection.sql``downstream_rejects.sql`;修改 `upstream_history.sql`(加 TRACKINQTY
- **Frontend**: `FilterBar.vue``App.vue``KpiCards.vue``DetailTable.vue``useTraceProgress.js``style.css`
- **Config**: `page_status.json`(頁面名稱更新 + 移除 tmtt-defect 條目)
- **API**: 所有 `/api/mid-section-defect/*` 端點新增 `station` + `direction` 參數;新增 `/station-options` 端點
- **移除**: `frontend/src/tmtt-defect/`(整個目錄)、`src/mes_dashboard/routes/tmtt_defect_routes.py``src/mes_dashboard/services/tmtt_defect_service.py``src/mes_dashboard/sql/tmtt_defect/`、相關測試檔案、`nativeModuleRegistry.js` 中的 tmtt-defect 註冊

View File

@@ -0,0 +1,94 @@
## ADDED Requirements
### Requirement: FilterBar SHALL include station dropdown
FilterBar SHALL display a `<select>` dropdown populated from `GET /api/mid-section-defect/station-options` on mount. The dropdown SHALL default to '測試' and emit `station` via the `update-filters` mechanism.
#### Scenario: Station dropdown loads on mount
- **WHEN** the FilterBar component mounts
- **THEN** it SHALL fetch station options from the API and populate the dropdown with 12 workcenter groups
- **THEN** the default selection SHALL be '測試'
#### Scenario: Station selection updates filters
- **WHEN** user selects a different station
- **THEN** `update-filters` SHALL emit with the new `station` value
### Requirement: FilterBar SHALL include direction toggle
FilterBar SHALL display a toggle button group with two options: '反向追溯' (`backward`) and '正向追溯' (`forward`). Default SHALL be `backward`.
#### Scenario: Direction toggle switches direction
- **WHEN** user clicks '正向追溯'
- **THEN** `update-filters` SHALL emit with `direction: 'forward'`
- **THEN** the active button SHALL visually indicate the selected direction
### Requirement: KPI cards SHALL display direction-aware labels
KpiCards component SHALL accept `direction` and `stationLabel` props and switch card labels between backward and forward modes.
#### Scenario: Backward KPI labels
- **WHEN** `direction='backward'`
- **THEN** KPI cards SHALL display existing labels: 偵測批次數, 偵測不良數, 上游追溯批次數, 上游站點數, etc.
#### Scenario: Forward KPI labels
- **WHEN** `direction='forward'`
- **THEN** KPI cards SHALL display: 偵測批次數, 偵測不良數, 追蹤批次數, 下游到達站數, 下游不良總數, 下游不良率
### Requirement: Chart layout SHALL switch by direction
App.vue SHALL render direction-appropriate chart sets.
#### Scenario: Backward chart layout
- **WHEN** `direction='backward'`
- **THEN** SHALL render 6 Pareto charts: by_station, by_loss_reason, by_machine, by_detection_machine, by_workflow, by_package
#### Scenario: Forward chart layout
- **WHEN** `direction='forward'`
- **THEN** SHALL render 4 Pareto charts: by_downstream_station, by_downstream_loss_reason, by_downstream_machine, by_detection_machine
### Requirement: Detail table columns SHALL switch by direction
DetailTable component SHALL accept a `direction` prop and render direction-appropriate columns.
#### Scenario: Backward detail columns
- **WHEN** `direction='backward'`
- **THEN** columns SHALL match existing backward layout (CONTAINERID, station history, upstream machine attribution, etc.)
#### Scenario: Forward detail columns
- **WHEN** `direction='forward'`
- **THEN** columns SHALL include: CONTAINERID, 偵測設備, 偵測投入, 偵測不良, 下游到達站數, 下游不良總數, 下游不良率, 最差下游站
### Requirement: Page header SHALL reflect station and direction
Page title SHALL be '製程不良追溯分析'. Subtitle SHALL dynamically reflect station and direction.
#### Scenario: Backward subtitle
- **WHEN** `station='電鍍', direction='backward'`
- **THEN** subtitle SHALL indicate: `電鍍站不良 → 回溯上游機台歸因`
#### Scenario: Forward subtitle
- **WHEN** `station='成型', direction='forward'`
- **THEN** subtitle SHALL indicate: `成型站不良批次 → 追蹤倖存批次下游表現`
### Requirement: CSV export SHALL include direction-appropriate columns
Export SHALL produce CSV with columns matching the current direction's detail table.
#### Scenario: Forward CSV export
- **WHEN** user exports with `direction='forward'`
- **THEN** CSV SHALL contain forward-specific columns (detection equipment, downstream stats)
### Requirement: Page metadata SHALL be updated
`page_status.json` SHALL update the page name from '中段製程不良追溯' to '製程不良追溯分析'.
#### Scenario: Page name in page_status.json
- **WHEN** the page metadata is read
- **THEN** the name for `mid-section-defect` SHALL be '製程不良追溯分析'
## REMOVED Requirements
### Requirement: TMTT印字腳型不良分析 page
**Reason**: Functionality fully superseded by generalized traceability center (station=測試 + loss reasons filter for 276_腳型不良/277_印字不良)
**Migration**: Use `/mid-section-defect` with station=測試 and filter loss reasons to 276_腳型不良 or 277_印字不良
#### Scenario: TMTT defect page removal
- **WHEN** the change is complete
- **THEN** `frontend/src/tmtt-defect/` directory SHALL be removed
- **THEN** `src/mes_dashboard/routes/tmtt_defect_routes.py` SHALL be removed
- **THEN** `src/mes_dashboard/services/tmtt_defect_service.py` SHALL be removed
- **THEN** `src/mes_dashboard/sql/tmtt_defect/` directory SHALL be removed
- **THEN** `nativeModuleRegistry.js` SHALL have tmtt-defect registration removed
- **THEN** `page_status.json` SHALL have tmtt-defect entry removed

View File

@@ -0,0 +1,76 @@
## ADDED Requirements
### Requirement: Forward pipeline SHALL trace surviving lots downstream
When `direction=forward`, the system SHALL execute a forward tracing pipeline: detection station rejects → forward lineage (descendants) → downstream WIP + downstream rejects → forward attribution engine.
#### Scenario: Forward pipeline stages
- **WHEN** `query_analysis(station='成型', direction='forward')` is called
- **THEN** the pipeline SHALL execute in order:
1. Fetch detection data at 成型 station (lots with rejects in date range)
2. Resolve forward lineage via `LineageEngine.resolve_forward_tree(detection_cids)`
3. Collect tracked CIDs = detection CIDs all descendants
4. Fetch WIP history for tracked CIDs (with TRACKINQTY)
5. Fetch downstream reject records for tracked CIDs
6. Run forward attribution engine
7. Build KPI, charts, detail table, and trend data
#### Scenario: No descendants found
- **WHEN** forward lineage returns an empty descendants map
- **THEN** KPI SHALL show zero downstream rejects and zero downstream stations reached
- **THEN** charts and detail table SHALL be empty arrays
### Requirement: downstream_rejects.sql SHALL query reject records for tracked lots
`downstream_rejects.sql` SHALL query `DW_MES_LOTREJECTHISTORY` for batched CONTAINERIDs with the standard `WORKCENTER_GROUP` CASE WHEN classification.
#### Scenario: Downstream rejects query output columns
- **WHEN** the SQL is executed
- **THEN** it SHALL return: `CONTAINERID`, `WORKCENTERNAME`, `WORKCENTER_GROUP`, `LOSSREASONNAME`, `EQUIPMENTNAME`, `REJECT_TOTAL_QTY`, `TXNDATE`
#### Scenario: Batched IN clause for large CID sets
- **WHEN** tracked CIDs exceed 1000
- **THEN** the system SHALL batch queries in groups of 1000 (same pattern as `upstream_history.sql`)
### Requirement: upstream_history.sql SHALL include TRACKINQTY
The `upstream_history.sql` query SHALL include `h.TRACKINQTY` in both the `ranked_history` CTE and the final SELECT output.
#### Scenario: TRACKINQTY in output
- **WHEN** the SQL is executed
- **THEN** each row SHALL include `TRACKINQTY` representing the input quantity at that station
- **THEN** NULL values SHALL be handled as 0 via COALESCE
### Requirement: Forward attribution engine SHALL compute per-station reject rates
The forward attribution engine SHALL aggregate reject data by downstream station (stations with order > detection station's order) and compute reject rates using TRACKINQTY as denominator.
#### Scenario: Forward attribution calculation
- **WHEN** tracked lots reach downstream station Y with total TRACKINQTY=1000 and REJECT_TOTAL_QTY=50
- **THEN** station Y's reject rate SHALL be `50 / 1000 × 100 = 5.0%`
#### Scenario: Only downstream stations included
- **WHEN** detection station is 成型 (order=4)
- **THEN** attribution SHALL only include stations with order > 4 (去膠, 水吹砂, 電鍍, 移印, 切彎腳, 元件切割, 測試)
- **THEN** stations with order ≤ 4 SHALL be excluded from forward attribution
#### Scenario: Zero input quantity guard
- **WHEN** a downstream station has TRACKINQTY sum = 0 for tracked lots
- **THEN** reject rate SHALL be 0 (not division error)
### Requirement: Forward KPI SHALL summarize downstream impact
Forward direction KPI SHALL include: detection lot count, detection defect quantity, tracked lot count (detection + descendants), downstream stations reached, downstream total rejects, and overall downstream reject rate.
#### Scenario: Forward KPI fields
- **WHEN** forward analysis completes
- **THEN** KPI SHALL contain `detection_lot_count`, `detection_defect_qty`, `tracked_lot_count`, `downstream_stations_reached`, `downstream_total_reject`, `downstream_reject_rate`
### Requirement: Forward charts SHALL show downstream distribution
Forward direction charts SHALL include: by_downstream_station (Pareto by station reject qty), by_downstream_machine (Pareto by equipment), by_downstream_loss_reason (Pareto by reason), by_detection_machine (Pareto by detection station equipment).
#### Scenario: Forward chart keys
- **WHEN** forward analysis completes
- **THEN** charts SHALL contain keys: `by_downstream_station`, `by_downstream_machine`, `by_downstream_loss_reason`, `by_detection_machine`
### Requirement: Forward detail table SHALL show per-lot downstream tracking
Forward direction detail table SHALL show one row per detection lot with downstream tracking summary.
#### Scenario: Forward detail columns
- **WHEN** forward detail is requested
- **THEN** each row SHALL include: CONTAINERID, DETECTION_EQUIPMENTNAME, TRACKINQTY (at detection), detection reject qty, downstream stations reached count, downstream total rejects, downstream reject rate, worst downstream station (highest reject rate)

View File

@@ -0,0 +1,53 @@
## ADDED Requirements
### Requirement: Detection SQL SHALL be parameterized by workcenter group
The system SHALL replace hardcoded TMTT station filtering with a `{{ STATION_FILTER }}` template placeholder in `station_detection.sql`. The filter SHALL be built from `WORKCENTER_GROUPS[station]['patterns']` and `['exclude']` defined in `workcenter_groups.py`, generating OR-LIKE clauses with bind parameters.
#### Scenario: Station filter built from workcenter group patterns
- **WHEN** `station='電鍍'` is requested
- **THEN** the system SHALL build a SQL fragment: `UPPER(h.WORKCENTERNAME) LIKE :wc_p0 OR UPPER(h.WORKCENTERNAME) LIKE :wc_p1 OR ...` with bind values `['%掛鍍%', '%滾鍍%', '%條鍍%', '%電鍍%', '%補鍍%', '%TOTAI%', '%BANDL%']`
#### Scenario: Station filter respects exclude patterns
- **WHEN** `station='切割'` is requested (which has `exclude: ['元件切割', 'PKG_SAW']`)
- **THEN** the filter SHALL include patterns for '切割' AND exclude patterns via `AND UPPER(h.WORKCENTERNAME) NOT LIKE :wc_ex0 AND NOT LIKE :wc_ex1`
#### Scenario: Default station is 測試
- **WHEN** no `station` parameter is provided
- **THEN** the system SHALL default to `station='測試'` (patterns: `['TMTT', '測試']`)
- **THEN** results SHALL be equivalent to the previous hardcoded TMTT behavior
### Requirement: station_detection.sql SHALL generalize tmtt_detection.sql
`station_detection.sql` SHALL be a new SQL file that replaces `tmtt_detection.sql` with parameterized station filtering. Column aliases SHALL use `DETECTION_` prefix instead of `TMTT_` prefix.
#### Scenario: SQL column renaming
- **WHEN** `station_detection.sql` is executed
- **THEN** output columns SHALL include `DETECTION_EQUIPMENTID` and `DETECTION_EQUIPMENTNAME` (not `TMTT_EQUIPMENTID` / `TMTT_EQUIPMENTNAME`)
#### Scenario: Both WIP and reject CTEs use station filter
- **WHEN** the SQL is executed
- **THEN** both the WIP history CTE and the reject history CTE SHALL apply `{{ STATION_FILTER }}` to filter by the selected station
### Requirement: Station options endpoint SHALL return all workcenter groups
`GET /api/mid-section-defect/station-options` SHALL return the 12 workcenter groups from `WORKCENTER_GROUPS` as an ordered list with `name` and `order` fields.
#### Scenario: Station options response format
- **WHEN** the endpoint is called
- **THEN** it SHALL return a JSON array of 12 objects: `[{"name": "切割", "order": 0}, {"name": "焊接_DB", "order": 1}, ...]` sorted by order
### Requirement: All API endpoints SHALL accept station and direction parameters
All `/api/mid-section-defect/*` endpoints (`/analysis`, `/analysis/detail`, `/loss-reasons`, `/export`) SHALL accept `station` (string, default `'測試'`) and `direction` (string, `'backward'` | `'forward'`, default `'backward'`) query parameters.
#### Scenario: Parameters passed to service layer
- **WHEN** `/api/mid-section-defect/analysis?station=成型&direction=forward` is called
- **THEN** `query_analysis()` SHALL receive `station='成型'` and `direction='forward'`
#### Scenario: Invalid station rejected
- **WHEN** a station name not in `WORKCENTER_GROUPS` is provided
- **THEN** the endpoint SHALL return HTTP 400 with an error message
### Requirement: Cache key SHALL include station and direction
The cache key for analysis results SHALL include `station` and `direction` to prevent cross-contamination between different query contexts.
#### Scenario: Different station/direction combinations cached separately
- **WHEN** `station=測試, direction=backward` is queried, then `station=成型, direction=forward` is queried
- **THEN** each SHALL have its own independent cache entry

View File

@@ -0,0 +1,25 @@
## MODIFIED Requirements
### Requirement: EventFetcher SHALL provide unified cached event querying across domains
`EventFetcher` SHALL encapsulate batch event queries with L1/L2 layered cache and rate limit bucket configuration, supporting domains: `history`, `materials`, `rejects`, `holds`, `jobs`, `upstream_history`, `downstream_rejects`.
#### Scenario: Cache miss for event domain query
- **WHEN** `EventFetcher` is called for a domain with container IDs and no cache exists
- **THEN** the domain query SHALL execute against Oracle via `read_sql_df()`
- **THEN** the result SHALL be stored in L2 Redis cache with key format `evt:{domain}:{sorted_cids_hash}`
- **THEN** L1 memory cache SHALL also be populated (aligned with `core/cache.py` LayeredCache pattern)
#### Scenario: Cache hit for event domain query
- **WHEN** `EventFetcher` is called for a domain and L2 Redis cache contains a valid entry
- **THEN** the cached result SHALL be returned without executing Oracle query
- **THEN** DB connection pool SHALL NOT be consumed
#### Scenario: Rate limit bucket per domain
- **WHEN** `EventFetcher` is used from a route handler
- **THEN** each domain SHALL have a configurable rate limit bucket aligned with `configured_rate_limit()` pattern
- **THEN** rate limit configuration SHALL be overridable via environment variables
#### Scenario: downstream_rejects domain query
- **WHEN** `EventFetcher` is called with domain `downstream_rejects`
- **THEN** it SHALL load `mid_section_defect/downstream_rejects.sql` via `SQLLoader.load_with_params()` with `DESCENDANT_FILTER` set to the batched IN clause condition
- **THEN** the query SHALL return reject records with `WORKCENTER_GROUP` classification

View File

@@ -0,0 +1,32 @@
## MODIFIED Requirements
### Requirement: query-tool lineage tab SHALL load on-demand
The query-tool lineage tree SHALL auto-fire lineage API calls after lot resolution with concurrency-limited parallel requests and progressive rendering, while preserving on-demand expand/collapse for tree navigation.
The mid_section_defect profile SHALL support a `direction` parameter that controls lineage resolution direction: `backward` uses `resolve_full_genealogy()` (ancestors), `forward` uses `resolve_forward_tree()` (descendants).
`useTraceProgress.js` `PROFILE_DOMAINS` for `mid_section_defect` SHALL include `'upstream_history'` for backward and `['upstream_history', 'downstream_rejects']` for forward. Domain selection SHALL be handled by the backend based on `direction` in params.
`collectAllContainerIds()` SHALL support forward direction by collecting descendants from `children_map` (instead of ancestors) when `direction='forward'` is present in params.
#### Scenario: Auto-fire lineage after resolve
- **WHEN** lot resolution completes with N resolved lots
- **THEN** lineage SHALL be fetched via `POST /api/trace/lineage` for each lot automatically
- **THEN** concurrent requests SHALL be limited to 3 at a time to respect rate limits (10/60s)
- **THEN** response time SHALL be ≤3s per individual lot
#### Scenario: Multiple lots lineage results cached
- **WHEN** lineage data has been fetched for multiple lots
- **THEN** each lot's lineage data SHALL be preserved independently (not re-fetched)
- **WHEN** a new resolve query is executed
- **THEN** all cached lineage data SHALL be cleared
#### Scenario: Mid-section defect backward lineage
- **WHEN** profile is `mid_section_defect` and direction is `backward`
- **THEN** lineage stage SHALL call `resolve_full_genealogy()` to get ancestor container IDs
- **THEN** `collectAllContainerIds()` SHALL merge seed IDs with ancestor IDs
#### Scenario: Mid-section defect forward lineage
- **WHEN** profile is `mid_section_defect` and direction is `forward`
- **THEN** lineage stage SHALL call `resolve_forward_tree()` to get descendant container IDs
- **THEN** `collectAllContainerIds()` SHALL merge seed IDs with descendant IDs from `children_map`

View File

@@ -0,0 +1,62 @@
## 1. SQL Layer
- [x] 1.1 Create `station_detection.sql` — copy `tmtt_detection.sql`, replace hardcoded TMTT filter with `{{ STATION_FILTER }}` / `{{ STATION_FILTER_REJECTS }}` placeholders, rename `TMTT_EQUIPMENTID/NAME``DETECTION_EQUIPMENTID/NAME`
- [x] 1.2 Create `downstream_rejects.sql` — query `DW_MES_LOTREJECTHISTORY` for batched CONTAINERIDs with `WORKCENTER_GROUP` CASE WHEN, returning CONTAINERID, WORKCENTERNAME, WORKCENTER_GROUP, LOSSREASONNAME, EQUIPMENTNAME, REJECT_TOTAL_QTY, TXNDATE
- [x] 1.3 Modify `upstream_history.sql` — add `h.TRACKINQTY` (with COALESCE to 0) to `ranked_history` CTE and final SELECT
## 2. Backend Service — Station Parameterization
- [x] 2.1 Add `_build_station_filter(station_name, column_prefix)` to `mid_section_defect_service.py` — reads `WORKCENTER_GROUPS` patterns/exclude, builds OR-LIKE SQL with bind params
- [x] 2.2 Replace `_fetch_tmtt_data()` with `_fetch_station_detection_data(start_date, end_date, station)` — uses `station_detection.sql` + `_build_station_filter()`
- [x] 2.3 Update all public API signatures (`query_analysis`, `query_analysis_detail`, `export_csv`, `resolve_trace_seed_lots`, `build_trace_aggregation_from_events`) to accept `station` and `direction` params (default `'測試'`/`'backward'`)
- [x] 2.4 Add station+direction to cache keys
- [x] 2.5 Rename all internal `TMTT_``DETECTION_` references (variables, dict keys, DIMENSION_MAP entries)
## 3. Backend Service — Forward Pipeline
- [x] 3.1 Extract existing backward logic into `_run_backward_pipeline(start_date, end_date, station, loss_reasons)`
- [x] 3.2 Add `_fetch_downstream_rejects(tracked_cids)` — batch query using `downstream_rejects.sql`
- [x] 3.3 Implement `_attribute_forward_defects(detection_df, detection_cids, downstream_wip, downstream_rejects, station_order)` — per-station reject rate using TRACKINQTY denominator
- [x] 3.4 Implement `_run_forward_pipeline(start_date, end_date, station, loss_reasons)` — full 8-stage pipeline (detection → forward lineage → downstream WIP+rejects → attribution → KPI/charts/detail)
- [x] 3.5 Implement `_build_forward_kpi()`, `_build_forward_charts()`, `_build_forward_detail_table()` builders
- [x] 3.6 Add direction dispatch in `query_analysis()`: backward → `_run_backward_pipeline()`, forward → `_run_forward_pipeline()`
- [x] 3.7 Add `query_station_options()` — returns ordered workcenter groups list
## 4. Backend Routes & EventFetcher
- [x] 4.1 Update `mid_section_defect_routes.py` — add `station` + `direction` query params to all endpoints, add station validation, add `GET /station-options` endpoint
- [x] 4.2 Update `trace_routes.py``_seed_resolve_mid_section_defect()` passes `station`; lineage stage uses direction to choose `resolve_full_genealogy()` vs `resolve_forward_tree()`; events stage passes direction for domain selection
- [x] 4.3 Add `downstream_rejects` domain to `event_fetcher.py` — in `SUPPORTED_EVENT_DOMAINS` and `_build_domain_sql()`, loading `mid_section_defect/downstream_rejects.sql`
## 5. Frontend — FilterBar & App
- [x] 5.1 Update `FilterBar.vue` — add station `<select>` dropdown (fetches from `/station-options` on mount), add direction toggle button group (反向追溯/正向追溯), emit station+direction via `update-filters`
- [x] 5.2 Update `App.vue` — add `station: '測試'` and `direction: 'backward'` to filters reactive, include in `buildFilterParams()`, add computed `isForward`, switch chart layout by direction, update page header to '製程不良追溯分析' with dynamic subtitle
- [x] 5.3 Update `useTraceProgress.js` — add `downstream_rejects` to `PROFILE_DOMAINS.mid_section_defect` for forward, update `collectAllContainerIds()` to support `children_map` for forward direction
## 6. Frontend — Direction-Aware Components
- [x] 6.1 Update `KpiCards.vue` — accept `direction` + `stationLabel` props, switch card labels between backward/forward modes
- [x] 6.2 Update `DetailTable.vue` — accept `direction` prop, switch column definitions between backward (existing) and forward (偵測設備, 偵測投入, 偵測不良, 下游到達站數, 下游不良總數, 下游不良率, 最差下游站)
- [x] 6.3 Add `.direction-toggle` styles to `style.css`
## 7. Remove TMTT Defect Page
- [x] 7.1 Delete `frontend/src/tmtt-defect/` directory
- [x] 7.2 Delete `src/mes_dashboard/routes/tmtt_defect_routes.py`
- [x] 7.3 Delete `src/mes_dashboard/services/tmtt_defect_service.py`
- [x] 7.4 Delete `src/mes_dashboard/sql/tmtt_defect/` directory
- [x] 7.5 Remove tmtt-defect registration from `nativeModuleRegistry.js`, `routeContracts.js`, `vite.config.js`, `page_status.json`, `routes/__init__.py`, `app.py`, `page_registry.py`, and all migration baseline/config files
- [x] 7.6 Delete related test files and update remaining tests referencing tmtt-defect
## 8. Config & Metadata
- [x] 8.1 Update `page_status.json` — rename mid-section-defect page name from '中段製程不良追溯' to '製程不良追溯分析', remove tmtt-defect entry
## 9. Verification
- [x] 9.1 Run `python -m pytest tests/test_mid_section_defect_*.py -v` — all 22 tests pass
- [x] 9.2 Run `cd frontend && node --test` — 69/69 frontend tests pass
- [x] 9.3 Run all change-relevant backend tests (app_factory, navigation_contract, full_modernization_gates, page_registry, portal_shell_wave_b_native_smoke) — 64/64 pass
- [x] 9.4 Verify backward compat: `station=測試, direction=backward` produces identical data (renamed columns) — 25,415 detail rows, DETECTION_EQUIPMENTNAME columns (no TMTT_), KPI/charts/genealogy all correct
- [x] 9.5 Verify forward basic: `station=成型 (order=4), direction=forward` → 8 downstream stations, 1,673 detail rows, downstream reject distribution: 測試 1.67%, 水吹砂 0.03%, 切彎腳 0.03%, 去膠 0.02%, 電鍍 0.01%, 移印 0.01%