Skip to main content

PowerFill Superset Dashboards (Phase 8 Workstream 1)

8 interactive dashboards for the PowerFill module, deployed to PSX Superset (bi.staging.powerseller.com) against PS_DemoData. They surface the same run-output tables that the Phase 7 read APIs return as JSON, but query the tenant DB directly for performance + Superset-native filtering.

Deployed: 2026-04-19 (sentinel phase-8-superset-ready since this commit) Pattern reference: BestEx + Position Recon dashboards Deploy script: infra/superset/deploy-powerfill.py SQL queries: infra/superset/queries/37_*.sql through 44_*.sql

Architectural overview

DecisionChoiceDocumented in
Per-report dashboard structureHybrid: 1 hub + 7 detail dashboards (matches the Desktop App "one report per screen" mental model AND the Phase 7 endpoint structure)This doc + Phase 8 completion report
Per-tenant DB connectionPS_DemoData only for v1 — multi-tenant Superset registration is Phase 9+ work (per A64)A64 in assumptions log
Run scopingLatest-run-wins via direct table query (no run_id URL parameter on dashboards in v1; the Hub dashboard surfaces run history; per-report dashboards always read the current state of pfill_* tables — same semantic as Phase 7's ADR-025 §A7.1 latest-Complete-wins)ADR-025
Existing Disposition sourceDefensive query of upstream tables — PS_DemoData has the legacy WITH ENCRYPTION view that lacks note_rate (per A62); Phase 7 catches the SqlException, but Superset cannot, so the dashboard SQL bypasses the view entirely (per A63)A63 in assumptions log
Allocation Guide + KickoutsOne UNION ALL query with a row_type discriminator so a single dashboard can split allocations vs kickouts via Superset filterThis doc; query 38

A54/A56 carry-over rendering — UPDATED 2026-04-19 (A54 + A56 RESOLVED, A66 NEW)

Pre-A54-fix (Phase 8 W1 launch state, 2026-04-19 morning): PS_DemoData's latest run terminated as Failed at Step 5 (pool_guide) with the documented A54 PK violation. User-facing PowerFill output tables were BR-9-cleared. Hub dashboard rendered 5 historical runs; Cash Trade Slotting rendered 688 rows (per A58 preservation); per-report dashboards 14-19 rendered 0.

Post-A54-fix (2026-04-19 evening, sentinel phase-8-superset-ready-a54-fixed): PS_DemoData runs achieve Complete end-to-end via two surgical fixes within ADR-021's amended §Narrow Bug-Fix Carve-Out (PK extension + pt13 JOIN qualifier extension; see A54). However, A66 (NEW) documents that psp_powerfillUE clears + rebuilds-empty the user-facing run-output tables on syn-trade-empty datasets like PS_DemoData. Empirical post-fix dashboard row counts:

#DashboardPre-fixPost-fixWhy
1Hub511Audit trail accumulates: 5 historical Failed/Cancelled + 6 new (5 transitional Failed during fix-iteration + latest Complete)
2Allocation Guide00UE clears+rebuilds-empty per A66 (no syn-trades in PS_DemoData)
3Trade Recap00A66
4Switching00A66
5Pool Candidates00A66
6Existing Disposition00A63 defensive query unchanged; data-shape PS_DemoData artifact
7Pooling Guide00A66
8Cash Trade Slotting6880UE supersedes Step 4's pfill_cash_market_map per A66 sub-finding

The new canonical proof-of-life is Dashboard 1 (Hub): the latest run shows Status=Complete with output_guide_count=515, visibly different from the prior 5 Failed runs. End-to-end Complete is now demonstrable in ~30s.

Dashboard 8 (Cash Trade Slotting) is no longer the F-7-8 / 688-row canonical — that was an artifact of A56 short-circuiting UE before UE could clear pfill_cash_market_map. With A54+A56 resolved, UE's clear-and-rebuild semantics now empty the table on this dataset.

Phase 9 is what unblocks the empty per-report dashboards. Specifically, Phase 9 needs to either:

  1. Run against a customer DB with real syn-trade opportunities (UE will then populate the tables per its design intent), OR
  2. Investigate whether PS_DemoData has an upstream issue (e.g., A12 deferred Step 1 bx_cash_grids) that prevents syn-trade synthesis when it should occur on this snapshot.

Phase 8 W1's deliverable is the dashboard surface itself; the empirical row counts above are the post-A54-fix snapshot for the demo.


Dashboard 1: Run Overview (Hub)

Purpose: Entry point. Shows every PowerFill run with its lifecycle status, duration, input/output counts, and (on Failed runs) the failure step + message. The PO uses this to answer "what runs exist for this tenant; which is the latest; and if it failed, why?"

Source: pfill_run_history (Phase 6e PSSaaS-only audit table; survives BR-9 cleanup by design)

Phase 7 endpoint counterpart: GET /api/powerfill/runs (paginated)

Superset URL: https://bi.staging.powerseller.com/superset/dashboard/13/

Charts

#ChartTypeInsight
1Runs TableTableFull run history with is_latest flag, status, duration, counters, failure context
2Latest Run KPIsBig numberLatest run's output_guide_count (filtered to is_latest = 1)
3Status DistributionPieCount of runs by status (Pending / Allocating / Complete / Failed / Cancelled / etc.)

A54/A56/A62 carry-over rendering

On PS_DemoData this dashboard shows 5 runs, all in terminal states. The latest run (769245CF-...) is Failed at step pool_guide with failure_message = "SqlException 2627: Violation of PRIMARY KEY constraint 'PK__##cte_po__F0E022A227C84D71'..." (the documented A54 PK violation).

Phase 9 unblocks: when A54 closes, this dashboard's status distribution will start showing Complete runs and the per-report detail dashboards (2-8 below) will populate from the real allocation output.


Dashboard 2: Allocation Guide

Purpose: Per-loan allocation result + kickouts. The "did each loan get allocated to a trade, and if not why?" view.

Source: pfill_powerfill_guide UNION ALL pfill_kickout_guide_01 (with row_type discriminator)

Phase 7 endpoint counterpart: GET /api/powerfill/runs/{run_id}/guide + GET /api/powerfill/runs/{run_id}/kickouts (combined into a single dashboard with row_type filter)

Superset URL: https://bi.staging.powerseller.com/superset/dashboard/14/

Charts

#ChartTypeInsight
1Allocations TableTable (filter: row_type = 'allocation')Per-loan trade placement with prices + carry + tolerance
2Kickouts TableTable (filter: row_type = 'kickout')Per-loan rejection reason + opportunity cost
3Allocations vs KickoutsBarLoan count by row_type — at-a-glance allocation rate
4Loan Amount by TypeBarTotal loan amount allocated vs kicked-out

A54/A56/A62 carry-over rendering

Both source tables are in the BR-9-cleared list. On PS_DemoData (latest run Failed) this dashboard shows 0 rows. Phase 9 unblocks: when A54 closes the Step 5 PK violation, Step 4 still produces ~515 allocations (pre-existing 6a-6e empirical baseline); those rows surface here.


Dashboard 3: Trade Recap

Purpose: Per-trade fulfillment counters. The "how full is each trade's allocation; pre-session vs post-session need/space?" view.

Source: pfill_trade_base (Step 4 output — Phase 6b)

Phase 7 endpoint counterpart: GET /api/powerfill/runs/{run_id}/recap

Superset URL: https://bi.staging.powerseller.com/superset/dashboard/15/

Charts

#ChartTypeInsight
1Trade TableTable35-column per-trade detail (counterparty, dates, fulfillment fields)
2Fulfillment by CounterpartyBarTotal trade amount + designated amount per counterparty
3Pre vs Post SessionScatterPre-session need vs post-session need, grouped by instrument

A54/A56/A62 carry-over rendering

Source table is BR-9-cleared. Dashboard renders 0 rows on the latest Failed run. Phase 9 unblocks when A54 closes.


Dashboard 4: Pool Switching

Purpose: The "switching thumbnail" view — pairs of Swapped In + Swapped Out rows showing every loan that's moving from one pool to another.

Source: pfill_pool_guide filtered by pool_action IN ('Swapped In', 'Swapped Out') per BR-3

Phase 7 endpoint counterpart: GET /api/powerfill/runs/{run_id}/switching

Superset URL: https://bi.staging.powerseller.com/superset/dashboard/16/

Charts

#ChartTypeInsight
1Switching Pairs TableTablePer-loan source pool → target pool with prices
2Volume by PoolBar (stacked by pool_action)Total loan amount switched in/out per destination pool

A54/A56/A62 carry-over rendering

pfill_pool_guide is the table A54 fails to populate. A54-blocked. Dashboard renders 0 rows on PS_DemoData until Phase 9.


Dashboard 5: Pool Candidates

Purpose: All-trade-options surface. For each loan, every trade it could potentially have been allocated to, with carry-cost-aware scoring.

Source: pfill_loan2trade_candy_level_01 (Step 3 output, per A51)

Phase 7 endpoint counterpart: GET /api/powerfill/runs/{run_id}/pool-candidates

Superset URL: https://bi.staging.powerseller.com/superset/dashboard/17/

Charts

#ChartTypeInsight
1Top 100 by ScoreTablePer-loan-per-trade candidacy with prx_plus_carry score
2Score DistributionHistogramDistribution of price-plus-carry scores
3By ProductBar (stacked by market)Candidate count per product

A54/A56/A62 carry-over rendering

Source is BR-9-cleared per Phase 6e. Dashboard renders 0 rows on the latest Failed run. Phase 9 unblocks when A54 closes — Step 3 produced ~1050 candidacy rows on prior 6a-6e PoCs.


Dashboard 6: Existing Pool Disposition

Purpose: Current vs proposed pool state per loan — the "where do my at-risk loans currently sit; where would PowerFill move them?" view.

Source: Upstream tables directly (loan + rmcat_loan + loan_shipped + pfill_pool_guide) — NOT the pfillv_existng_pool_disposition view. Per F-8-BR-1 / A63: PS_DemoData has the legacy WITH ENCRYPTION version of the view that lacks the note_rate column. The Phase 7 service catches the SqlException and falls back (per A62); Superset cannot, so the Phase 8 SQL transcribes the view's logical shape (CTEs cte_epd_0030 through cte_epd_0600) directly against the upstream tables AND OMITS the note_rate column entirely.

Phase 7 endpoint counterpart: GET /api/powerfill/runs/{run_id}/existing-disposition

Superset URL: https://bi.staging.powerseller.com/superset/dashboard/18/

Charts

#ChartTypeInsight
1Detail TableTablePer-loan disposition: pool, recommended pool, action, pool_status, risk_status
2Pool Loan CountsBar (stacked by pool_status)Loan count per pool by status
3Risk Status DistributionPiein_risk vs not_in_risk loan distribution

A54/A56/A62 carry-over rendering

The defensive query DOES execute on PS_DemoData (no schema-drift error — F-8-BR-1 / A63 mitigation works). It returns 0 rows because the at-risk join requires rmcat_loan.segment_name = 'at risk' rows AND the pfill_pool_guide LEFT JOIN can't enrich on a BR-9-cleared table. Phase 9 closes either by (a) deploying 002_CreatePowerFillViews.sql to PS_DemoData (now possible per A30 RESOLVED) and reverting this dashboard to the view, OR (b) populating rmcat_loan segment data + closing A54.


Dashboard 7: Pooling Guide

Purpose: Per-pool aggregate fill view. The "for each recommended pool, how many loans / what amount; broken down by pool_action."

Source: pfill_pool_guide aggregated by trades_pool (recommended pool name)

Phase 7 endpoint counterpart: GET /api/powerfill/runs/{run_id}/pooling-guide

Superset URL: https://bi.staging.powerseller.com/superset/dashboard/19/

Charts

#ChartTypeInsight
1Per-Pool TableTablePer-pool counts (distinct loans, distinct trades) + amount sums + per-pool_action counts (all 8 BR-3 values)
2Pool VolumeBarTotal loan amount per pool
3Action MixBar (stacked)Per-pool breakdown of Remaining / Joining / Leaving / Switching

A54/A56/A62 carry-over rendering

Same source as Dashboard 4 (pfill_pool_guide). A54-blocked on PS_DemoData. Renders 0 rows until Phase 9.


Dashboard 8: Cash Trade Slotting

Purpose: Cash-market-map view. For each (loan_instrument, investor_instrument) pair, which cash grid type applies. F-7-8 canonical pattern: this dashboard renders REAL DATA on PS_DemoData even on Failed runs because pfill_cash_market_map survives BR-9 cleanup per A58.

Source: pfill_cash_market_map (slot fields surfaced as NULL — see notes below)

Phase 7 endpoint counterpart: GET /api/powerfill/runs/{run_id}/cash-trade-slotting

Superset URL: https://bi.staging.powerseller.com/superset/dashboard/20/

Charts

#ChartTypeInsight
1Map TableTablePer (investor_instrument, loan_instrument) pair with cash_grid_type
2By Investor InstrumentBar (stacked by cash_grid_type)Mapping count per investor instrument
3Grid Type DistributionPieDistribution of cash_grid_type values
4Total Mappings KPIBig numberTotal cash market map row count (688 on PS_DemoData)

A54/A56/A62 carry-over rendering

This is the proof-of-life dashboard. 688 real rows render on PS_DemoData even on the latest Failed run. The slot fields (trade_id, rate, sequence_number, price, syntax_name) are intentionally NULL because:

  1. PS_DemoData runs skip Step 1 (psp_pfill_bx_cash_grids) per A12 (bx_price_floor is null), so pscat_trade_cash_grid stays empty.
  2. There's no foreign key from pfill_cash_market_map to pscat_trade_cash_grid in the schema; modeling the join is Phase 9 work.

The Phase 7 service emits the same NULL slot fields to keep the wire shape stable; Phase 8's dashboard mirrors that exactly.


Operational notes

Cross-filtering

All 8 dashboards have cross_filters_enabled: True set in dashboard.json_metadata (per deploy-powerfill.py). Within a dashboard, clicking a chart element filters the others. Cross-dashboard filtering (e.g., from Hub → detail dashboard for a specific run_id) is Phase 9 work — Phase 8 v1 dashboards always read the current state of the pfill_* tables (the latest run wins per ADR-025 §A7.1).

Re-deploy

To re-deploy after changes:

# Superset moved from `psx-staging` to `pss-platform` on 2026-04-19 (Backlog #30).
# Hostname `bi.staging.powerseller.com` unchanged; only the namespace changed.
NS=pss-platform
POD=$(kubectl get pod -n $NS -l app=superset -o jsonpath='{.items[0].metadata.name}')

# Copy script + queries into the Superset pod
kubectl cp infra/superset/deploy-powerfill.py $NS/$POD:/tmp/pfill-deploy/deploy-powerfill.py -c superset
kubectl exec -n $NS $POD -- mkdir -p /tmp/pfill-deploy/queries
for f in 37 38 39 40 41 42 43 44; do
kubectl cp infra/superset/queries/${f}_pfill_*.sql $NS/$POD:/tmp/pfill-deploy/queries/ -c superset
done

# Run the deploy
kubectl exec -n $NS $POD -- python3 /tmp/pfill-deploy/deploy-powerfill.py

Idempotent: re-runs UPDATE existing datasets/charts/dashboards rather than duplicating them.

Multi-tenant Superset (Phase 9+)

V1 ships with a single PS_DemoData connection (DB_ID = 2 in the deploy script). Per A64, per-tenant Superset registration (one connection per tenant DB; or one Superset workspace per tenant via RBAC) is Phase 9+ work. The deploy-script + dashboard structure stay the same — only the database registration multiplies.

SQL pattern: no outer ORDER BY

All 8 PowerFill SQL queries intentionally have no outer ORDER BY clause. Superset wraps virtual datasets in a subquery, and SQL Server rejects ORDER BY in subqueries unless TOP/OFFSET is also specified (Msg 1033). Dashboard chart-level sort controls handle ordering. (See AGENTS.md "Superset wraps virtual dataset SQL in subqueries" lesson + Phase 8 PoC verification — caught by Deploy Verification Gate arm c.)