Chunk failures in BatchQueryEngine were silently discarded — `has_partial_failure` was tracked in Redis but never surfaced to the API response or frontend. Users could see incomplete data without any warning. This commit closes the gap end-to-end: Backend: - Track failed chunk time ranges (`failed_ranges`) in batch engine progress metadata - Add single retry for transient Oracle errors (timeout, connection) in `_execute_single_chunk` - Read `get_batch_progress()` after merge but before `redis_clear_batch()` cleanup - Inject `has_partial_failure`, `failed_chunk_count`, `failed_ranges` into API response meta - Persist partial failure flag to independent Redis key with TTL aligned to data storage layer - Add shared container-resolution policy module with wildcard/expansion guardrails - Refactor reason filter from single-value to multi-select (`reason` → `reasons`) Frontend: - Add client-side date range validation (730-day limit) before API submission - Display amber warning banner on partial failure with specific failed date ranges - Support generic fallback message for container-mode queries without date ranges - Update FilterPanel to support multi-select reason chips Specs & tests: - Create batch-query-resilience spec; update reject-history-api and reject-history-page specs - Add 7 new tests for retry, memory guard, failed ranges, partial failure propagation, TTL - Cross-service regression verified (hold, resource, job, msd — 411 tests pass) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
303 lines
16 KiB
Markdown
303 lines
16 KiB
Markdown
# reject-history-page Specification
|
||
|
||
## Purpose
|
||
TBD - created by archiving change reject-history-query-page. Update Purpose after archive.
|
||
## 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
|
||
|
||
#### Scenario: Header refresh button
|
||
- **WHEN** the page header is rendered
|
||
- **THEN** it SHALL include a "重新整理" button in the header-right area
|
||
- **WHEN** user clicks the refresh button
|
||
- **THEN** all sections SHALL reload with current filters (equivalent to "查詢")
|
||
|
||
### 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 display 6 Pareto charts simultaneously (不良原因, PACKAGE, TYPE, WORKFLOW, 站點, 機台) in a 3-column grid layout with cross-filter linkage, replacing the single-dimension dropdown switcher.
|
||
|
||
#### Scenario: Multi-Pareto grid layout
|
||
- **WHEN** Pareto data is loaded
|
||
- **THEN** 6 Pareto charts SHALL be rendered simultaneously in a 3-column grid (3×2)
|
||
- **THEN** each chart SHALL display one dimension: 不良原因, PACKAGE, TYPE, WORKFLOW, 站點, 機台
|
||
- **THEN** there SHALL be no dimension dropdown selector
|
||
|
||
#### Scenario: Pareto rendering and ordering
|
||
- **WHEN** Pareto data is loaded
|
||
- **THEN** items in each Pareto SHALL be sorted by selected metric descending
|
||
- **THEN** each Pareto SHALL show a cumulative percentage line
|
||
|
||
#### Scenario: Pareto 80% filter is managed in supplementary filters
|
||
- **WHEN** the page first loads Pareto
|
||
- **THEN** supplementary filters SHALL include "Pareto 僅顯示累計前 80%" control
|
||
- **THEN** the control SHALL default to enabled
|
||
- **THEN** the 80% filter SHALL apply uniformly to all 6 Pareto charts
|
||
|
||
#### Scenario: Cross-filter linkage between Pareto charts
|
||
- **WHEN** user clicks an item in one Pareto chart (e.g., selects reason "A")
|
||
- **THEN** the other 5 Pareto charts SHALL recalculate with the selection applied as a filter
|
||
- **THEN** the clicked Pareto chart itself SHALL NOT be filtered by its own selections
|
||
- **THEN** the detail table below SHALL apply ALL selections from ALL dimensions
|
||
|
||
#### Scenario: Pareto click filtering supports multi-select
|
||
- **WHEN** user clicks Pareto bars or table rows in any dimension
|
||
- **THEN** clicked items SHALL become active selected chips
|
||
- **THEN** multiple selected items SHALL be supported within each dimension
|
||
- **THEN** multiple dimensions SHALL support simultaneous selections
|
||
|
||
#### Scenario: Re-click clears selected item only
|
||
- **WHEN** user clicks an already selected Pareto item
|
||
- **THEN** only that item SHALL be removed from selection
|
||
- **THEN** remaining selected items across all dimensions SHALL stay active
|
||
- **THEN** all Pareto charts SHALL recalculate to reflect the updated selections
|
||
|
||
#### Scenario: Filter chips display all dimension selections
|
||
- **WHEN** items are selected across multiple Pareto dimensions
|
||
- **THEN** selected items SHALL be displayed as chips grouped by dimension label
|
||
- **THEN** each chip SHALL show the dimension label and selected value (e.g., "TYPE: X")
|
||
- **THEN** clicking a chip's remove button SHALL deselect that item and trigger recalculation
|
||
|
||
#### Scenario: Responsive grid layout
|
||
- **WHEN** viewport is desktop width (>1200px)
|
||
- **THEN** Pareto charts SHALL render in a 3-column grid
|
||
- **WHEN** viewport is medium width (768px–1200px)
|
||
- **THEN** Pareto charts SHALL render in a 2-column grid
|
||
- **WHEN** viewport is below 768px
|
||
- **THEN** Pareto charts SHALL stack in a single column
|
||
|
||
#### Scenario: TOP20/ALL display scope control
|
||
- **WHEN** Pareto grid is displayed
|
||
- **THEN** supplementary filters SHALL include a global "只顯示 TOP 20" toggle
|
||
- **THEN** when enabled, applicable dimensions (TYPE, WORKFLOW, 機台) SHALL truncate to top 20 items
|
||
- **THEN** the toggle SHALL apply uniformly to all applicable Pareto charts (not per-chart selectors)
|
||
- **THEN** dimensions not in the applicable set (不良原因, PACKAGE, 站點) SHALL be unaffected by this toggle
|
||
|
||
### 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 all active filters
|
||
- **WHEN** user clicks "匯出 CSV"
|
||
- **THEN** export request SHALL include current primary filters, supplementary filters, trend-date filters, metric filters, and all Pareto-selected items from all 6 dimensions
|
||
- **THEN** downloaded file SHALL contain exactly the same rows currently represented by the detail list filter context
|
||
|
||
#### Scenario: Export remains UTF-8 Excel compatible
|
||
- **WHEN** CSV export is downloaded
|
||
- **THEN** the file SHALL be encoded in UTF-8 with BOM
|
||
- **THEN** Chinese headers and values SHALL render correctly in common spreadsheet tools
|
||
|
||
### Requirement: Reject History page SHALL provide robust feedback states
|
||
The page SHALL provide loading, empty, and error states without breaking interactions.
|
||
|
||
#### 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
|
||
|
||
### Requirement: Reject History page SHALL display a loading overlay during initial data load
|
||
The page SHALL show a full-screen loading overlay with spinner during the first data load to provide clear feedback.
|
||
|
||
#### Scenario: Loading overlay on initial mount
|
||
- **WHEN** the page first mounts and `loadAllData` begins
|
||
- **THEN** a loading overlay with spinner SHALL be displayed over the page content
|
||
- **WHEN** all initial API responses complete
|
||
- **THEN** the overlay SHALL be hidden
|
||
|
||
#### Scenario: Subsequent queries do not show overlay
|
||
- **WHEN** the user triggers a re-query after initial load
|
||
- **THEN** no full-screen overlay SHALL appear (inline loading states are sufficient)
|
||
|
||
### Requirement: Detail table rows SHALL highlight on hover
|
||
The detail table and pareto table rows SHALL visually respond to mouse hover for improved readability.
|
||
|
||
#### Scenario: Row hover in detail table
|
||
- **WHEN** user hovers over a row in the detail table
|
||
- **THEN** the row background SHALL change to a subtle highlight color
|
||
|
||
#### Scenario: Row hover in pareto table
|
||
- **WHEN** user hovers over a row in the pareto summary table
|
||
- **THEN** the row background SHALL change to a subtle highlight color
|
||
|
||
### Requirement: Pagination controls SHALL use Chinese labels
|
||
The detail list pagination SHALL display controls in Chinese to match the rest of the page language.
|
||
|
||
#### Scenario: Pagination button labels
|
||
- **WHEN** the pagination controls are rendered
|
||
- **THEN** the previous-page button SHALL display "上一頁"
|
||
- **THEN** the next-page button SHALL display "下一頁"
|
||
- **THEN** the page info text SHALL use Chinese formatting (e.g., "第 1 / 5 頁 · 共 250 筆")
|
||
|
||
### Requirement: Reject History page SHALL be structured as modular sub-components
|
||
The page template SHALL delegate sections to focused sub-components, following the hold-history architecture pattern.
|
||
|
||
#### Scenario: Component decomposition
|
||
- **WHEN** the page source is examined
|
||
- **THEN** the filter panel SHALL be a separate `FilterPanel.vue` component
|
||
- **THEN** the KPI summary cards SHALL be a separate `SummaryCards.vue` component
|
||
- **THEN** the trend chart SHALL be a separate `TrendChart.vue` component
|
||
- **THEN** the pareto grid SHALL be a separate `ParetoGrid.vue` component containing 6 `ParetoSection.vue` instances
|
||
- **THEN** each individual pareto chart+table SHALL be a `ParetoSection.vue` component
|
||
- **THEN** the detail table with pagination SHALL be a separate `DetailTable.vue` component
|
||
|
||
#### Scenario: App.vue acts as orchestrator
|
||
- **WHEN** the page runs
|
||
- **THEN** `App.vue` SHALL hold all reactive state and API logic
|
||
- **THEN** sub-components SHALL receive data via props and communicate via events
|
||
|
||
### Requirement: Reject History page SHALL display partial failure warning banner
|
||
The page SHALL display an amber warning banner when the query result contains partial failures, informing users that displayed data may be incomplete.
|
||
|
||
#### Scenario: Warning banner displayed on partial failure
|
||
- **WHEN** the primary query response includes `meta.has_partial_failure: true`
|
||
- **THEN** an amber warning banner SHALL be displayed below the error banner position
|
||
- **THEN** the warning message SHALL be in Traditional Chinese
|
||
|
||
#### Scenario: Warning banner shows failed date ranges
|
||
- **WHEN** `meta.failed_ranges` contains date range objects
|
||
- **THEN** the warning banner SHALL display the specific failed date ranges (e.g., "以下日期區間的資料擷取失敗:2025-01-01 ~ 2025-01-10")
|
||
|
||
#### Scenario: Warning banner shows generic message without ranges (container mode or missing range data)
|
||
- **WHEN** `meta.has_partial_failure` is true but `meta.failed_ranges` is empty or absent (e.g., container-id batch query)
|
||
- **THEN** the warning banner SHALL display a generic message with the failed chunk count (e.g., "3 個查詢批次的資料擷取失敗")
|
||
|
||
#### Scenario: Warning banner cleared on new query
|
||
- **WHEN** user initiates a new primary query
|
||
- **THEN** the warning banner SHALL be cleared before the new query executes
|
||
- **THEN** if the new query also has partial failures, the warning SHALL update with new failure information
|
||
|
||
#### Scenario: Warning banner coexists with error banner
|
||
- **WHEN** both an error message and a partial failure warning exist
|
||
- **THEN** the error banner SHALL appear first, followed by the warning banner
|
||
|
||
#### Scenario: Warning banner visual style
|
||
- **WHEN** the warning banner is rendered
|
||
- **THEN** it SHALL use amber/orange color scheme (background `#fffbeb`, text `#b45309`)
|
||
- **THEN** the style SHALL be consistent with the existing `.resolution-warn` color pattern
|
||
|
||
### Requirement: Reject History page SHALL validate date range before query submission
|
||
The page SHALL validate the date range on the client side before sending the API request, providing immediate feedback for invalid ranges.
|
||
|
||
#### Scenario: Date range exceeds 730-day limit
|
||
- **WHEN** user selects a date range exceeding 730 days and clicks "查詢"
|
||
- **THEN** the page SHALL display an error message "查詢範圍不可超過 730 天(約兩年)"
|
||
- **THEN** the API request SHALL NOT be sent
|
||
|
||
#### Scenario: Missing start or end date
|
||
- **WHEN** user clicks "查詢" without setting both start_date and end_date (in date_range mode)
|
||
- **THEN** the page SHALL display an error message "請先設定開始與結束日期"
|
||
- **THEN** the API request SHALL NOT be sent
|
||
|
||
#### Scenario: End date before start date
|
||
- **WHEN** user selects an end_date earlier than start_date
|
||
- **THEN** the page SHALL display an error message "結束日期必須大於起始日期"
|
||
- **THEN** the API request SHALL NOT be sent
|
||
|
||
#### Scenario: Valid date range proceeds normally
|
||
- **WHEN** user selects a valid date range within 730 days and clicks "查詢"
|
||
- **THEN** no validation error SHALL be shown
|
||
- **THEN** the API request SHALL proceed normally
|
||
|
||
#### Scenario: Container mode skips date validation
|
||
- **WHEN** query mode is "container" (not "date_range")
|
||
- **THEN** date range validation SHALL be skipped
|
||
|
||
### 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
|
||
|