Files
DashBoard/openspec/changes/archive/2026-03-02-reject-history-multi-pareto-layout/tasks.md
egg 2568fd836c 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>
2026-03-02 14:43:35 +08:00

4.6 KiB

1. Backend — Batch Pareto with Cross-Filter

  • 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
  • 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": {...}}
  • 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
  • 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

  • 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
  • 2.2 Update apply_view() and export_csv_from_cache() in reject_dataset_cache.py to pass multi-dimension selections
  • 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)

  • 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
  • 3.2 Add fetchBatchPareto() function — calls GET /api/reject-history/batch-pareto with sel_* params, updates all 6 paretoData entries
  • 3.3 Rewrite onParetoItemToggle(dimension, value) — toggle in paretoSelections[dimension], call fetchBatchPareto() + refreshView(), reset page
  • 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
  • 3.5 Update activeFilterChips computed — loop all 6 dimensions, generate chip per selected value with dimension label
  • 3.6 Update chip removal handler to call onParetoItemToggle(dim, value)

4. Frontend — URL State

  • 4.1 Update updateUrlState() — replace pareto_dimension/pareto_values with sel_reason, sel_package, etc. array params; keep pareto_display_scope for TOP20/ALL
  • 4.2 Update restoreFromUrl() — parse sel_* params into paretoSelections object
  • 4.3 Update buildViewParams() in reject-history-filters.js — replace paretoDimension/paretoValues with paretoSelections dict, emit sel_* params

5. Frontend — Components

  • 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)
  • 5.2 Create ParetoGrid.vue — 3-column grid container rendering 6 ParetoSection instances, props: paretoData, paretoSelections, loading, metricLabel, selectedDates; emit item-toggle(dimension, value)
  • 5.3 Update App.vue template — replace single <ParetoSection> with <ParetoGrid>

6. Frontend — Styling

  • 6.1 Add .pareto-grid CSS class in style.css — 3-column grid with responsive breakpoints (2-col at ≤1200px, 1-col at ≤768px)
  • 6.2 Adjust .pareto-chart-wrap height from 340px to ~240px for compact multi-chart display
  • 6.3 Adjust .pareto-layout for vertical stack (chart above table) in grid context

7. Integration & Wire-Up

  • 7.1 Wire fetchBatchPareto() into loadAllData() flow — call after primary query completes
  • 7.2 Wire supplementary filter changes and trend-date changes to trigger fetchBatchPareto()
  • 7.3 Wire export button to include all 6 dimension selections in export request

8. Testing

  • 8.1 Add unit tests for compute_batch_pareto() cross-filter logic in tests/test_reject_dataset_cache.py
  • 8.2 Add route tests for GET /api/reject-history/batch-pareto endpoint in tests/test_reject_history_routes.py
  • 8.3 Add tests for multi-dimension _apply_pareto_selection_filter() in tests/test_reject_dataset_cache.py