feat(reject-history): multi-pareto 3×2 grid with cross-filter linkage

Replace single-dimension Pareto dropdown with 6 simultaneous Pareto charts
(不良原因, PACKAGE, TYPE, WORKFLOW, 站點, 機台) in a responsive 3-column grid.
Clicking items in one Pareto cross-filters the other 5 (exclude-self logic),
and the detail table applies all dimension selections with AND logic.

Backend:
- Add batch-pareto endpoint (cache-only, no Oracle queries)
- Add _apply_cross_filter() with exclude-self pattern
- Extend view/export endpoints for multi-dimension sel_* params

Frontend:
- New ParetoGrid.vue wrapping 6 ParetoSection instances
- Simplify ParetoSection: remove dimension dropdown, keep TOP20 toggle
- Replace single-dimension state with paretoSelections reactive object
- Adaptive x-axis labels (font size, rotation, hideOverlap) for compact grid
- Responsive grid: 3-col desktop, 2-col tablet, 1-col mobile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-03-02 14:43:35 +08:00
parent e83d8e1a36
commit 2568fd836c
20 changed files with 1558 additions and 534 deletions

View File

@@ -53,7 +53,7 @@ The API SHALL return time-series trend data for quantity and rate metrics.
- **THEN** invalid granularity SHALL return HTTP 400
### Requirement: Reject History API SHALL provide reason Pareto endpoint
The API SHALL return sorted reason distribution data with cumulative percentages.
The API SHALL return sorted reason distribution data with cumulative percentages. The endpoint supports dimension selection via `dimension` parameter for single-dimension queries.
#### Scenario: Pareto response payload
- **WHEN** `GET /api/reject-history/reason-pareto` is called
@@ -65,6 +65,68 @@ The API SHALL return sorted reason distribution data with cumulative percentages
- **THEN** accepted values SHALL be `reject_total` or `defect`
- **THEN** invalid `metric_mode` SHALL return HTTP 400
#### Scenario: Dimension selection
- **WHEN** `dimension` parameter is provided with a valid value (reason, package, type, workflow, workcenter, equipment)
- **THEN** the endpoint SHALL return Pareto data for that dimension
- **WHEN** `dimension` is not provided
- **THEN** the endpoint SHALL default to `reason`
### Requirement: Reject History API SHALL provide batch Pareto endpoint with cross-filter
The API SHALL provide a batch Pareto endpoint that returns all 6 dimension Pareto results in a single response, supporting cross-dimension filtering with exclude-self logic.
#### Scenario: Batch Pareto response structure
- **WHEN** `GET /api/reject-history/batch-pareto` is called with valid `query_id`
- **THEN** response SHALL be `{ success: true, data: { dimensions: { reason: {...}, package: {...}, type: {...}, workflow: {...}, workcenter: {...}, equipment: {...} } } }`
- **THEN** each dimension object SHALL include `items` array with same schema as reason-pareto items (`reason`, `metric_value`, `pct`, `cumPct`, `MOVEIN_QTY`, `REJECT_TOTAL_QTY`, `DEFECT_QTY`, `count`)
#### Scenario: Cross-filter exclude-self logic
- **WHEN** `sel_reason=A&sel_type=X` is provided
- **THEN** reason Pareto SHALL be computed with type=X filter applied (but NOT reason=A filter)
- **THEN** type Pareto SHALL be computed with reason=A filter applied (but NOT type=X filter)
- **THEN** package/workflow/workcenter/equipment Paretos SHALL be computed with both reason=A AND type=X filters applied
#### Scenario: Empty selections return unfiltered Paretos
- **WHEN** batch-pareto is called with no `sel_*` parameters
- **THEN** all 6 dimensions SHALL return their full Pareto distribution (same as calling reason-pareto individually with no cross-filter)
#### Scenario: Cache-only computation
- **WHEN** `query_id` does not exist in cache
- **THEN** the endpoint SHALL return HTTP 400 with error message indicating cache miss
- **THEN** the endpoint SHALL NOT fall back to Oracle query
#### Scenario: Supplementary and policy filters apply
- **WHEN** batch-pareto is called with supplementary filters (packages, workcenter_groups, reason) and policy toggles
- **THEN** all 6 dimension Paretos SHALL be computed after applying policy and supplementary filters first (before cross-filter)
#### Scenario: Data source is cached DataFrame only
- **WHEN** batch-pareto computes dimension Paretos
- **THEN** computation SHALL operate on the in-memory cached Pandas DataFrame (populated by the primary query)
- **THEN** the endpoint SHALL NOT issue any additional Oracle database queries
- **THEN** response time SHALL be sub-100ms since all computation is in-memory
#### Scenario: Display scope (TOP20) support
- **WHEN** `pareto_display_scope=top20` is provided
- **THEN** applicable dimensions (type, workflow, equipment) SHALL truncate results to top 20 items after sorting
- **WHEN** `pareto_display_scope` is omitted or `all`
- **THEN** all items SHALL be returned (subject to pareto_scope 80% filter if active)
### Requirement: Reject History API SHALL support multi-dimension Pareto selection in view and export
The detail view and export endpoints SHALL accept multiple dimension selections simultaneously and apply them with AND logic.
#### Scenario: Multi-dimension filter on view endpoint
- **WHEN** `GET /api/reject-history/view` is called with `sel_reason=A&sel_type=X`
- **THEN** returned rows SHALL match reason=A AND type=X (both filters applied simultaneously)
#### Scenario: Multi-dimension filter on export endpoint
- **WHEN** `GET /api/reject-history/export-cached` is called with `sel_reason=A&sel_type=X`
- **THEN** exported CSV SHALL contain only rows matching reason=A AND type=X
#### Scenario: Backward compatibility with single-dimension params
- **WHEN** `pareto_dimension` and `pareto_values` are provided (legacy format)
- **THEN** the API SHALL still accept and apply them as before
- **WHEN** both `sel_*` params and legacy params are provided
- **THEN** `sel_*` params SHALL take precedence
### Requirement: Reject History API SHALL provide paginated detail endpoint
The API SHALL return paginated detailed rows for the selected filter context.

View File

@@ -74,33 +74,63 @@ The page SHALL show both quantity trend and rate trend to avoid mixing unit scal
- **THEN** rate values SHALL be displayed as percentages
### Requirement: Reject History page SHALL provide reason Pareto analysis
The page SHALL provide a Pareto view for loss reasons and support downstream filtering.
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 SHALL be sorted by selected metric descending
- **THEN** a cumulative percentage line SHALL be shown
- **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: TYPE/WORKFLOW/機台 support display scope selector
- **WHEN** Pareto dimension is `TYPE`, `WORKFLOW`, or `機台`
- **THEN** the UI SHALL provide `全部顯示` and `只顯示 TOP 20` options
- **THEN** `全部顯示` SHALL still respect the current 80% cumulative filter setting
#### 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
- **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 at the same time
- **THEN** detail list SHALL reload using current selected Pareto items as additional filter criteria
- **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 SHALL stay active
- **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 (768px1200px)
- **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.
@@ -119,7 +149,7 @@ 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 Pareto-selected items
- **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
@@ -197,7 +227,8 @@ The page template SHALL delegate sections to focused sub-components, following t
- **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 section (chart + table) SHALL be a separate `ParetoSection.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