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:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-02
|
||||
@@ -0,0 +1,59 @@
|
||||
## Context
|
||||
|
||||
The reject history page displays a single Pareto chart with a dropdown to switch between 6 dimensions (reason, package, type, workflow, workcenter, equipment). Users lose cross-dimensional context when switching. The cached dataset in `reject_dataset_cache.py` already supports all 6 dimensions via `compute_dimension_pareto()` and the `_DIM_TO_DF_COLUMN` mapping. The goal is to show all 6 simultaneously in a 3×2 grid with cross-filter linkage.
|
||||
|
||||
Current architecture:
|
||||
- **Backend**: `compute_dimension_pareto()` computes one dimension at a time from a cached Pandas DataFrame (in-memory, populated by the primary Oracle query and keyed by `query_id`). All Pareto computation is performed on this cached DataFrame — no additional Oracle queries are made.
|
||||
- **Frontend**: `App.vue` holds `paretoDimension` (single ref), `selectedParetoValues` (single array), and `paretoDisplayScope`. Client-side reason Pareto is computed via `allParetoItems` computed property from `analyticsRawItems`. Other dimensions call `fetchDimensionPareto()` which hits `GET /api/reject-history/reason-pareto?dimension=X`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Display all 6 Pareto dimensions simultaneously in a 3-column responsive grid
|
||||
- Cross-filter linkage: clicking items in dimension X filters all OTHER dimensions (exclude-self)
|
||||
- Detail table reflects ALL dimension selections (AND logic)
|
||||
- Single batch API call for all 6 dimensions (avoid 6 round-trips)
|
||||
- Unify all Pareto computation on backend (remove client-side reason Pareto)
|
||||
|
||||
**Non-Goals:**
|
||||
- No changes to the primary query SQL or data model
|
||||
- No changes to the cache infrastructure or TTL
|
||||
- No new Oracle fallback paths (batch-pareto is cache-only)
|
||||
- No drag-and-drop or reorderable grid layout
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Single batch API vs 6 separate calls
|
||||
**Decision**: Single `GET /api/reject-history/batch-pareto` endpoint returning all 6 dimensions.
|
||||
**Rationale**: Cross-filter requires all dimensions to see each other's selections. A single call eliminates 6 round-trips and guarantees consistency across all 6 results. The cached DataFrame is shared in-memory so iterating 6 dimensions is negligible overhead.
|
||||
|
||||
### 2. Cross-filter logic: exclude-self pattern
|
||||
**Decision**: When computing dimension X's Pareto, apply selections from all OTHER dimensions but NOT X's own.
|
||||
**Rationale**: If X's own selections were applied, the Pareto would only show selected items — defeating the purpose of showing the full distribution. Exclude-self lets users see "given these TYPE and WORKFLOW selections, what is the REASON distribution?"
|
||||
|
||||
### 3. Remove client-side reason Pareto
|
||||
**Decision**: Remove the `allParetoItems` computed property that builds reason Pareto client-side from `analyticsRawItems`. All 6 dimensions (including reason) computed by backend `compute_batch_pareto()`.
|
||||
**Rationale**: Unifies computation path. The backend already has the logic; duplicating it client-side for one dimension creates divergence risk and doesn't support cross-filtering.
|
||||
|
||||
### 4. Retain TOP20/ALL display scope toggle
|
||||
**Decision**: Keep the TOP20/全部顯示 toggle for applicable dimensions (TYPE, WORKFLOW, 機台). Apply uniformly across all 6 charts via a global control (not per-chart selectors).
|
||||
**Rationale**: Even after 80% cumulative filtering, dimensions like TYPE, WORKFLOW, and 機台 can still produce many items. The TOP20 truncation remains valuable for readability in the compact grid layout. Moving to a single global toggle (instead of per-chart selectors) reduces UI clutter while preserving the functionality.
|
||||
|
||||
### 5. ParetoGrid wrapper vs inline loop
|
||||
**Decision**: New `ParetoGrid.vue` component wrapping 6 `ParetoSection.vue` instances.
|
||||
**Rationale**: Separates grid layout concerns from individual chart rendering. `ParetoSection.vue` is simplified to a pure display component. `ParetoGrid.vue` handles the dimension iteration and event delegation.
|
||||
|
||||
### 6. Multi-dimension selection URL encoding
|
||||
**Decision**: Use `sel_reason`, `sel_package`, `sel_type`, `sel_workflow`, `sel_workcenter`, `sel_equipment` as separate array params (replacing single `pareto_dimension` + `pareto_values`).
|
||||
**Rationale**: Each dimension has independent selections. Encoding them as separate params is explicit, debuggable, and backward-compatible (old URLs with `pareto_dimension` simply won't have `sel_*` params).
|
||||
|
||||
### 7. Multi-dimension detail/export filter
|
||||
**Decision**: Extend `_apply_pareto_selection_filter()` to accept a `pareto_selections: dict` and apply all dimensions cumulatively (AND logic). Keep backward compat with single `pareto_dimension`/`pareto_values` for the existing single-dimension endpoints until they are removed.
|
||||
**Rationale**: Detail table and CSV export need to filter by all 6 dimensions simultaneously. AND logic is the natural interpretation: "show rows matching these reasons AND these types AND these workflows."
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **6 charts on screen may feel crowded on smaller screens** → Responsive breakpoints: 3-col desktop, 2-col tablet, 1-col mobile. Chart height reduced from 340px to ~240px.
|
||||
- **Batch pareto payload is larger (6× single)** → Still small (6 arrays of ≤20 items each). Negligible compared to the primary query.
|
||||
- **Per-chart scope toggles removed in favor of global toggle** → Individual TOP20/ALL selectors inside each chart would clutter the compact grid. A single global TOP20/ALL control in supplementary filters provides the same functionality with cleaner UX.
|
||||
- **Cross-filter causes re-fetch on every click** → Mitigated by cache-only computation (no Oracle queries). Typical response time < 50ms.
|
||||
@@ -0,0 +1,43 @@
|
||||
## Why
|
||||
|
||||
目前報廢歷史的柏拉圖一次只顯示一個維度,需透過下拉選單切換,無法同時看到跨維度的分佈與交叉關係。改為同時顯示 6 個柏拉圖(3×2 grid),並支援跨圖即時聯動篩選——點擊任一柏拉圖的項目後,其餘 5 個柏拉圖即時重新計算(排除自身維度的篩選),下方明細表則套用所有維度的篩選結果。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 前端
|
||||
- 移除維度切換下拉選單(`ParetoSection.vue` 的 dimension selector)
|
||||
- 新增 `ParetoGrid.vue` 元件,以 3 欄 grid 同時渲染 6 個獨立柏拉圖(不良原因、PACKAGE、TYPE、WORKFLOW、站點、機台)
|
||||
- 每個柏拉圖支援多選(現有行為),點擊後即時聯動:
|
||||
- 其他 5 個柏拉圖重新計算(套用來自其他維度的選取,但不套用自身維度的選取)
|
||||
- 明細表套用所有 6 個維度的選取結果
|
||||
- 選取狀態以 chip 顯示在明細表上方,按維度分組
|
||||
|
||||
### 後端
|
||||
- 新增批次柏拉圖 API endpoint(`GET /api/reject-history/batch-pareto`),對快取中的 Pandas DataFrame 進行重算,一次回傳 6 個維度的柏拉圖資料(不重查 Oracle 資料庫)
|
||||
- 每個維度的計算套用「排除自身」的交叉篩選邏輯:計算 Reason Pareto 時套用其他 5 維度的選取,但不套用 Reason 自身的選取
|
||||
- 移除前端 client-side 的 reason Pareto 計算(統一由後端從快取計算)
|
||||
|
||||
### 移除
|
||||
- 移除維度切換選單和 `onDimensionChange` 邏輯
|
||||
- 移除現有的單維度 `fetchDimensionPareto` 流程
|
||||
|
||||
### 保留
|
||||
- 保留 TOP20/全部顯示切換功能(TYPE、WORKFLOW、機台維度在 80% 過濾後仍可能有大量項目,TOP20 截斷對使用者仍有價值)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
(none)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `reject-history-api`: 新增批次柏拉圖 endpoint,支援跨維度交叉篩選
|
||||
- `reject-history-page`: 柏拉圖從單維度切換改為 6 圖同時顯示 + 即時聯動
|
||||
|
||||
## Impact
|
||||
|
||||
- 前端:`App.vue`(狀態管理重構)、`ParetoSection.vue`(改為純展示元件)、新增 `ParetoGrid.vue`
|
||||
- 後端:`reject_dataset_cache.py`(新增批次計算,資料來源為快取的 Pandas DataFrame)、`reject_history_routes.py`(新增 endpoint)
|
||||
- API:新增 `GET /api/reject-history/batch-pareto` endpoint(cache-only,不查 Oracle)
|
||||
- 無資料庫/SQL 變更
|
||||
@@ -0,0 +1,78 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### 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
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Reject History API SHALL provide reason Pareto endpoint
|
||||
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
|
||||
- **THEN** each item SHALL include `reason`, `category`, selected metric value, `pct`, and `cumPct`
|
||||
- **THEN** items SHALL be sorted descending by selected metric
|
||||
|
||||
#### Scenario: Metric mode validation
|
||||
- **WHEN** `metric_mode` is provided
|
||||
- **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`
|
||||
@@ -0,0 +1,92 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### 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
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### 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 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
|
||||
@@ -0,0 +1,51 @@
|
||||
## 1. Backend — Batch Pareto with Cross-Filter
|
||||
|
||||
- [x] 1.1 Add `_apply_cross_filter(df, selections, exclude_dim)` helper in `reject_dataset_cache.py` — applies all dimension selections except `exclude_dim` using `_DIM_TO_DF_COLUMN` mapping
|
||||
- [x] 1.2 Add `compute_batch_pareto()` function in `reject_dataset_cache.py` — iterates all 6 dimensions from cached DataFrame (no Oracle query), applies policy → supplementary → trend-date → cross-filter, supports `pareto_display_scope=top20` truncation for applicable dimensions, returns `{"dimensions": {...}}`
|
||||
- [x] 1.3 Add `_parse_multi_pareto_selections()` helper in `reject_history_routes.py` — parses `sel_reason`, `sel_package`, `sel_type`, `sel_workflow`, `sel_workcenter`, `sel_equipment` from query params
|
||||
- [x] 1.4 Add `GET /api/reject-history/batch-pareto` endpoint in `reject_history_routes.py` — cache-only (no Oracle fallback), accepts `pareto_display_scope` param, calls `compute_batch_pareto()`
|
||||
|
||||
## 2. Backend — Multi-Dimension Detail/Export Filter
|
||||
|
||||
- [x] 2.1 Extend `_apply_pareto_selection_filter()` in `reject_dataset_cache.py` to accept `pareto_selections: dict` (multi-dimension AND logic), keeping backward compat with single `pareto_dimension`/`pareto_values`
|
||||
- [x] 2.2 Update `apply_view()` and `export_csv_from_cache()` in `reject_dataset_cache.py` to pass multi-dimension selections
|
||||
- [x] 2.3 Update `view` and `export-cached` endpoints in `reject_history_routes.py` to parse and forward `sel_*` params
|
||||
|
||||
## 3. Frontend — State Refactor (App.vue)
|
||||
|
||||
- [x] 3.1 Replace single-dimension state (`paretoDimension`, `selectedParetoValues`, `dimensionParetoItems`, `dimensionParetoLoading`) with `paretoSelections` reactive object and `paretoData` reactive object; keep `paretoDisplayScope` ref for global TOP20/ALL toggle
|
||||
- [x] 3.2 Add `fetchBatchPareto()` function — calls `GET /api/reject-history/batch-pareto` with `sel_*` params, updates all 6 `paretoData` entries
|
||||
- [x] 3.3 Rewrite `onParetoItemToggle(dimension, value)` — toggle in `paretoSelections[dimension]`, call `fetchBatchPareto()` + `refreshView()`, reset page
|
||||
- [x] 3.4 Remove dead code: `allParetoItems`, `filteredParetoItems`, `activeParetoItems` computed, `fetchDimensionPareto()`, `refreshDimensionParetoIfActive()`, `onDimensionChange()`, `PARETO_DIMENSION_LABELS`; keep `PARETO_TOP20_DIMENSIONS` and `paretoDisplayScope` for global TOP20/ALL toggle
|
||||
- [x] 3.5 Update `activeFilterChips` computed — loop all 6 dimensions, generate chip per selected value with dimension label
|
||||
- [x] 3.6 Update chip removal handler to call `onParetoItemToggle(dim, value)`
|
||||
|
||||
## 4. Frontend — URL State
|
||||
|
||||
- [x] 4.1 Update `updateUrlState()` — replace `pareto_dimension`/`pareto_values` with `sel_reason`, `sel_package`, etc. array params; keep `pareto_display_scope` for TOP20/ALL
|
||||
- [x] 4.2 Update `restoreFromUrl()` — parse `sel_*` params into `paretoSelections` object
|
||||
- [x] 4.3 Update `buildViewParams()` in `reject-history-filters.js` — replace `paretoDimension`/`paretoValues` with `paretoSelections` dict, emit `sel_*` params
|
||||
|
||||
## 5. Frontend — Components
|
||||
|
||||
- [x] 5.1 Simplify `ParetoSection.vue` — remove dimension selector dropdown and `DIMENSION_OPTIONS`; keep per-chart TOP20 truncation logic (controlled by parent via `displayScope` prop); add dimension label map; emit `item-toggle` with value only (parent handles dimension routing)
|
||||
- [x] 5.2 Create `ParetoGrid.vue` — 3-column grid container rendering 6 `ParetoSection` instances, props: `paretoData`, `paretoSelections`, `loading`, `metricLabel`, `selectedDates`; emit `item-toggle(dimension, value)`
|
||||
- [x] 5.3 Update `App.vue` template — replace single `<ParetoSection>` with `<ParetoGrid>`
|
||||
|
||||
## 6. Frontend — Styling
|
||||
|
||||
- [x] 6.1 Add `.pareto-grid` CSS class in `style.css` — 3-column grid with responsive breakpoints (2-col at ≤1200px, 1-col at ≤768px)
|
||||
- [x] 6.2 Adjust `.pareto-chart-wrap` height from 340px to ~240px for compact multi-chart display
|
||||
- [x] 6.3 Adjust `.pareto-layout` for vertical stack (chart above table) in grid context
|
||||
|
||||
## 7. Integration & Wire-Up
|
||||
|
||||
- [x] 7.1 Wire `fetchBatchPareto()` into `loadAllData()` flow — call after primary query completes
|
||||
- [x] 7.2 Wire supplementary filter changes and trend-date changes to trigger `fetchBatchPareto()`
|
||||
- [x] 7.3 Wire export button to include all 6 dimension selections in export request
|
||||
|
||||
## 8. Testing
|
||||
|
||||
- [x] 8.1 Add unit tests for `compute_batch_pareto()` cross-filter logic in `tests/test_reject_dataset_cache.py`
|
||||
- [x] 8.2 Add route tests for `GET /api/reject-history/batch-pareto` endpoint in `tests/test_reject_history_routes.py`
|
||||
- [x] 8.3 Add tests for multi-dimension `_apply_pareto_selection_filter()` in `tests/test_reject_dataset_cache.py`
|
||||
Reference in New Issue
Block a user