Docs/Open data/Portfolios (beta)

Portfolios (beta)

Portfolios (beta): build a tenant-side portfolio of U.S. utility-scale assets, override values, create variants, see the audit log live.

The portfolios surface is an architectural diorama for what a tenant layer on top of InfraSure would look like — not a SaaS. You can build a portfolio of US power plants, override values where you know better than EIA, create what-if variants for comparison, and watch the actual database rows being written as you click. Every action is captured in an audit log you can see live.

Open the portfolios surface
No login. Everyone shares one demo workspace. Edits from one browser are visible to all visitors. A "Reset demo" button on /portfolios returns to the seeded sample.

What you can do

  • Create a portfolio — a named collection of plants. Three are seeded: Q3 Outlook, 2050 Stress Test, and Sample Portfolio. Add your own or reset to recover.
  • Add plants — search the full reference index (~15K plants), click Add. Already-added plants are muted.
  • Override values — open an asset, set a different capacity_mw than EIA, save with notes. The effective value reflects the override; the reference stays untouched.
  • Create variants — what-if versions of the same asset (e.g. stress_2050_derate). Names are slugified server-side; baseline is reserved.
  • Compare variants — side-by-side dense columns with a Δ row and a Promote button. Promote runs an atomic transaction: the old baseline gets renamed previous_baseline_<timestamp> and demoted; the variant becomes the new baseline.
  • See the backend — the architecture page renders the schema (live from information_schema), the last 10 portfolio_asset rows, the last 10 audit events with before→after diffs, and per-session action counts. Auto-refreshes every 5 seconds.

Architecture in a paragraph

The reference data (plants, plant_index) is sacred — never modified by the tenant layer, joined at read time, intentionally not FK-targeted. On top of it, five new tables hold tenant state: workspace (the tenant root), portfolio (a named collection), portfolio_asset (the junction — one row per (portfolio, plant, variant_name) with override columns + a baseline flag), audit_event (polymorphic-actor mutation log), and saved_scenario (lineage-pinned snapshots — schema only in v1). The merge is COALESCE(override, reference) per column at read time. Variants are multiple rows of the same junction with different variant_name values.

The full design folder lives at docs/design/done/tenant_layer_demo/ in the repo. It mirrors the upstream vision_enralik design space (kept as a context file in the folder).

What is deliberately NOT here

  • Authentication. One shared workspace. A session cookie UUID acts as actor_id in audit rows so the architecture page can show "your session has performed N actions" without coupling to identity.
  • Active RLS policies. The schema includes workspace_id on every tenant row (architecture-correct); RLS policies are off in v1 because there is no session to filter by.
  • Tenant-only assets (tenant_asset). Every portfolio asset references an EIA plant_id. Customer-private assets are a v2 path.
  • Saved scenarios as a UI feature. The table exists with the lineage columns (reference_data_version, computed_at, etc.) so reproducibility is contractual. No buttons hook it up in v1.
  • Python library packaging. The merge logic lives in web/src/lib/portfolio.ts. When a second consumer appears (Python agent API, analyst notebook), the library extraction is straightforward.

How to read the architecture page

Open the architecture page

Two tabs side by side: /portfolios in one, /portfolios/architecture in the other. Click around in the portfolios tab — the architecture page updates within 5 seconds. Every mutation produces both a data row and an audit_event row in the same DB transaction, with before_state / after_state JSONB capturing exactly what changed.

The audit diff renderer treats null differently in three ways: before=null shows the after value in green (create); after=null shows the before value with strikethrough (delete); both populated shows before → after with amber on the new value.

Status

Demo is v1 complete. The schema is non-throwaway: when auth lands, the table shapes do not change — RLS policies flip on, workspace_membership joins arrive, and getDemoWorkspaceId() in web/src/lib/portfolio.ts is replaced with getWorkspaceFromSession(). No other code in the surface changes.