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>
16 KiB
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_dateandend_dateSHALL 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_datetime filter controls - THEN it SHALL include reason filter control
- THEN it SHALL include
WORKCENTER_GROUPfilter 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, andAFFECTED_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_QTYandDEFECT_QTYover 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_PCTandDEFECT_RATE_PCTover 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
loadAllDatabegins - 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.vuecomponent - THEN the KPI summary cards SHALL be a separate
SummaryCards.vuecomponent - THEN the trend chart SHALL be a separate
TrendChart.vuecomponent - THEN the pareto grid SHALL be a separate
ParetoGrid.vuecomponent containing 6ParetoSection.vueinstances - THEN each individual pareto chart+table SHALL be a
ParetoSection.vuecomponent - THEN the detail table with pagination SHALL be a separate
DetailTable.vuecomponent
Scenario: App.vue acts as orchestrator
- WHEN the page runs
- THEN
App.vueSHALL 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_rangescontains 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_failureis true butmeta.failed_rangesis 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-warncolor 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