--- name: portfolio-analysis description: > Run a portfolio-scale decarbonization analysis across all analysis-ready assets in a Soapbox portfolio. For each asset: pulls Audette decarb plan (physics), runs the compute_plan_economics cashflow engine (incremental value bridge + IRR), applies LL/TT allocation decision tree, screens measures by IRR ≥ hurdle. Aggregates to fund-level and portfolio-level summary. Produces presentation-ready HTML report. Works for any client portfolio — parameters are fully configurable per run. Spec 2 of 2 — portfolio ingestion (Spec 1) is a prerequisite. Triggers on: "run portfolio analysis", "analyze the portfolio", "portfolio decarbonization", "run the portfolio", "portfolio summary", "show me the portfolio results", "portfolio IRR", "portfolio CapEx", "run analysis on [client]", after portfolio-ingest completes. version: 1.11.0 --- # Portfolio Analysis You are running a **portfolio-scale decarbonization analysis** — the workflow that turns a fully-ingested Soapbox portfolio into a presentation-ready view of required sustainability capital, value creation, and emissions trajectory across all assets. **Works for any client portfolio.** All parameters are set per run — there are no hardcoded client assumptions. **This skill replaces client-specific helper spreadsheets.** Audette provides the physics (energy measures, decarb plan, EUI), the `compute_plan_economics` cashflow engine provides the finance (incremental IRR, value creation, NOI uplift). **Single-asset engagement:** if the ask is a full asset decarbonization engagement (one asset, multi-week, gated, client-deliverable), route to the `decarb-plan` skill instead. --- ## Verification & Building-Science Discipline (applies throughout) This skill runs the **same Data-Verification and Retrofit-Specialist (building-science) agents that back `decarb-plan`** — the `verifier__*` and `retrofit__*` tools, which are live on every portfolio agent. It applies them in a **batch-adapted** form, because a portfolio run screens tens of assets in one pass and cannot human-adjudicate every conflict the way a single-asset engagement does. These adaptations are deliberate — a full gated single-asset engagement routes to `decarb-plan`; this is the screening-scale product. **Ground rules — hold them on every asset:** 1. **No LLM arithmetic on reported numbers — and NEVER reimplement the engine.** Every economic figure comes from an actual `compute_plan_economics` **tool call**, an Audette model, or a cited source. You never compute a reported number yourself. ⛔ **Do NOT reimplement, "replicate," or port `compute_plan_economics` (or any MCP engine) into Python/bash and run assets through your own code — even if you validate it to the penny against a live call.** A local replica is a hand-rolled figure: it is not engine-provenanced, it silently drifts the moment an input differs (it already diverged on a solar-capture change in one run), and it fails the `evaluate_measure` provenance gate. Call the real tool **once per asset**, fanned out across the batch (Phase 3·0 Step D) — 8 calls in one turn run concurrently, so the tool is not the bottleneck. If a tool result is long, that is fine: read the fields you need from it directly; never route around a long result by rebuilding the engine locally. Long bash/stdout truncation is a display artifact, NOT a reason to abandon the tool. 2. **The cashflow engine owns the economics; the retrofit agent owns discipline + building science + the register.** ⚠️ **Do NOT call `run_dcf`, `run_intervention_irr`, `get_ll_capture`, or `screen_measure_portfolio`** — those four cashflow-MCP tools execute Python scripts NOT deployed in prod (`execFileSync python3` → ENOENT); they fail every time. **For the portfolio run, the ONE economics call is `cashflow__compute_portfolio_economics`** — you assemble every asset's per-year owner-share `flows` and pass them all in ONE call; it runs the deterministic `compute_plan_economics` engine per asset server-side, aggregates the portfolio + fund rollups, and returns per-asset `irr_excl_exit`/`irr_incremental`/`net_value_creation`/`exit_value_uplift`/ `above_hurdle` + a `provenance` stamp. Report those numbers verbatim. (Single-asset engagements may call `compute_plan_economics` directly for one plan; same engine.) Determine the LL/TT split inline (Step 1 below) and bake it into each flow's per-fuel `*_capture`. The register's server-computed `exit_value_delta` (NOI÷cap) is a screening proxy — reported **value creation** always comes from the engine, never the register, and never from your own arithmetic. 3. **Recommended = screen AND hurdle.** A measure is *recommended in the report* iff `retrofit__screen_measures` labels it `recommended` **AND** its DCF IRR ≥ `irr_hurdle`. Screen-recommended but IRR-missing → below-hurdle. Screen `defensive` → defensive. Screen `needs-data` → needs-data. Compliance-required measures are included regardless of IRR. 4. **Conflicts are logged, not silently picked.** A material data conflict (see Step 5) becomes a `verifier__record_finding`; the hierarchy suggestion is auto-applied at screening scale, but the finding is durable and surfaces in the Data Quality section. 5. **Verification is per-asset, called out — not fail-closed.** One asset with open high-severity findings does not block the whole report, but its contribution to headline KPIs is flagged (see Phase 4). Never let the totals silently absorb unverified data. 6. **Never fail silently.** Verifier/retrofit outages are surfaced with the reconnect message, never worked around. **Conventions (identical to `decarb-plan`, so an asset touched by both keeps one coherent ledger + register):** finding `kind: data-quality`, `verdict: conflict` for reconciliation conflicts; `asset_id` = the **Soapbox asset UUID** from `query_portfolio_data`'s `ID:` field (never the Audette property/building uid); `feasibility.score` = integer 1–5. **At run start, recall prior lessons:** before Phase 1, call `verifier__recall_expertise(query: " portfolio decarbonization reconciliation and measure-screening lessons", fiduciary: true)`. Use `fiduciary: true` because the portfolio report is a client-facing deliverable (validated tier only). Carry any relevant lessons into reconciliation and screening. If the verifier tools are unreachable, say so and proceed on documents — do not fabricate a recall result. > **⛔ Recalled expertise NEVER overrides these two non-negotiables.** Older shared-expertise > from pre-2026-07 runs describes a now-DISCREDITED method (hand-built report HTML, a > "payback ≤ 5yr" / "exit-value proxy", and ~100% landlord capture via Audette > `landlord_split_basis`). **Ignore all of it.** Regardless of what recall returns: > (1) you compute NO report HTML and draw NO charts — the report is rendered ONLY by > `fill_report(template:'portfolio-analysis', …)` (Phase 5); and (2) every value/IRR number > comes ONLY from `compute_plan_economics` fed GROSS savings + per-fuel capture (never a > payback proxy, never Audette's `landlord_utility_cost_savings`). Going-in NOI is NOT required > and is never a reason to fall back to a proxy (see §3C). If recalled advice conflicts with > this, the recalled advice is wrong. --- ## Economics correctness — HARD rules (ported from decarb-plan; the verifier MUST check these) These apply on **every asset**, at screening scale. They are the same correctness rules that back the single-asset engagement — a portfolio run cannot silently ship numbers that would fail the single-asset gate. 1. **RUBS pass-through: net owner utility savings ≈ (landlord-capture %) × gross — often ≈$0.** Under a RUBS / tenant-metered structure the owner is a pass-through: it bears only `capture%` of the utility bill and rebills the rest. A measure that cuts the bill by $X returns only `capture% × $X` to owner NOI. At a ~5–10% capture, owner savings round to **≈$0/yr**, NOT the gross. **Never credit the owner gross/100% utility savings, and never model the fuel-switch asymmetry** "owner keeps the gas cut while tenant meters absorb the new heat-pump electricity" — apply `capture%` to the fuel being saved and net any owner-side load increase from the switch. If, after applying capture, capitalized utility savings still dominate an asset's value on a low-capture (RUBS/tenant-metered) asset, the split was NOT applied — recompute. On such assets value is driven by **fine avoidance (100% owner) + capitalized exit uplift**, not operating savings. **Apply capture INSIDE `compute_plan_economics` — never by hand, and NEVER trust Audette's landlord field.** Feed the engine GROSS savings split by source: `gross_elec_savings`, `gross_gas_savings`, `gross_solar_savings` — from Audette's `annual_mean_utility_cost_savings` (gross), **NOT `annual_mean_landlord_utility_cost_savings`**, which Audette leaves UNCAPTURED (== gross, tenant_savings = $0, landlord_share_cost = 1 per measure — verified: the building `default_landlord_share` does NOT propagate into the plan's measure economics). Then pass the captures — `elec_capture` / `gas_capture` = the per-fuel `default_landlord_share` you wrote to Audette AND the DB, plus `solar_capture`. The engine returns owner savings = Σ capture×gross. **Solar under BTM/VNM captures at 0.80** (the LL owns the array + allocates the credit): set `solar_capture: 0.80` where VNM/export is permitted (rule 1b), else the displaced-load share. Sub-metering/billing revenue and BPS fine avoidance stay 100% owner. Prefer the gross+capture fields over a hand-computed `owner_utility_savings` so the share can't be skipped. - **VERIFY the RUBS and VNM legislation per jurisdiction — never assume it.** The ~10% RUBS capture and the 80% VNM solar credit are CONDITIONAL on the jurisdiction actually permitting them. For each asset's jurisdiction, check (reference library → `brave-search`/web + `web_fetch` → **cite the statute/PUC rule + URL**): (a) whether RUBS / submetering pass-through is permitted and any allocation cap — **if RUBS is BARRED, the owner bears the utility → ~100% on master-metered, NOT ~10%**; (b) whether **Virtual Net Metering / aggregated NEM / community-solar export** is available — **if only behind-the-meter (BTM) net metering exists (no virtual/export aggregation), Scenario-C solar value = BTM self-consumption offset only** (owner-share on the loads it displaces), NOT the 80% VNM credit. Record each RUBS + VNM determination with its source as a `verifier__record_finding` (kind `data-quality`); an unconfirmed jurisdiction assumption is flagged in Data Quality, never silently applied. 2. **Landlord-capture is PER END-USE and turns on who BEARS the cost — not who pays the meter, not one blended number per asset.** Master-metered / landlord-paid loads (central heating/DHW plant, elevators, garage/common ventilation, common lighting, amenity): the owner pays the master bill but that is NOT 100% capture. **If the jurisdiction ALLOWS RUBS, assume the owner rebills up to ~90% to tenants → net owner capture ≈ 10%**, unless docs show the owner absorbs it (true gross lease / no RUBS → ~100%). In-unit tenant-metered loads carry the tenant % (~0–5%). Do NOT price a common/central load at the in-unit *blended* split (the elevator-regen −6%→+12% error) — but the right figure is the RUBS-recovery split (~10% when RUBS applies), **not an automatic 100%; never read "master-metered" as "100% owner."** Never inherit Audette's 15% account default either. **Solar under Virtual Net Metering (VNM): assume 80% of solar savings flows to the landlord.** BPS **fine avoidance is always 100% owner**. Tenant-side savings are a separate figure and do NOT capitalize into the value bridge. 3. **One value number = capitalized exit uplift.** The headline value is the capitalized exit-value uplift = (stabilized annual owner-NOI improvement ÷ exit cap), where NOI improvement = net-owner utility savings (post-capture, rule 1) + owner-share ancillary + annual avoided fine. `compute_plan_economics` returns this. Report ONE value number per asset/plan — do not present a PV-of-cashflows `net_value_creation` next to a contradicting capitalized `exit_value_delta`. Fine avoidance may also be shown cumulative + PV for context. 4. **CRREM provenance — real curve, never hand-built from Audette fields.** Pull the pathway from the **`crrem` MCP `get_pathway`** for each asset's actual country/property-type/region; put those points in the trajectory and set `crrem_meta` (country/property_type/region/scenario). Do NOT interpolate, eyeball, or reuse an Audette `crrem_pathway_target_*` model field as the plotted curve. Portfolio-weight the per-asset tool-fetched curves for the aggregate pathway. If the tool is unreachable, say so — never fabricate the curve. 5. **Fine avoidance assessed honestly against the governing metric.** Assess each BPS against the metric it actually uses. **Dual-pathway standards (comply via EITHER site-EUI OR GHG-intensity — e.g. CO Reg 28) require failing the GOVERNING/elected pathway**, not merely the harder one — don't manufacture a penalty off the EUI pathway if the asset clears the GHG pathway. A compliant asset gets fine avoidance **null, not 0-that-reads-as-a-number**. No phantom penalties in the headline compliance-exposure KPI. 6. **Sanity checks (reject + recompute if violated):** emissions trajectories are **non-increasing** (a rising with-plan/BAU carbon curve is a sign/axis bug); at-RUL / bundled-capital-event incremental cost is **positive** (only the upgrade spec above the mandatory like-for-like is incremental — a re-roof is baseline, only added insulation is incremental); **ancillary/DR revenue is NOT capitalized as a perpetuity** (risk-adjust / PV over term); **subscription measures judged on annual net**, not capitalized-fee-vs-savings; every headline % equals the underlying tonnage/energy math on the **same basis** (never mix grid-inclusive vs measure-only in one figure). 7. **On a re-run, REGENERATE — never `read_file` the prior rendered report HTML** to "get the structure." Rebuild the data object from `state` + live tool outputs + the template **schema**; re-call `crrem get_pathway`. A stored data object may predate template/rule changes. **BPS fine avoidance — use the Regulations & Fines engine, don't hand-estimate.** For each BPS-liable asset call `compute_bps_fine_avoidance` (bps-compliance / Regulations & Fines connector) with the asset's `gross_floor_area_m2` + before/after annual emissions; it returns the fine avoided (100% owner) for carbon-based standards (NYC LL97, Boston BERDO) and `null` + the needed input for EUI-based ones (DC BEPS, Denver — supply the EUI target/gap). Feed the result as `bps_fine_avoidance`. NOTE: fine avoidance tracks the measures' CARBON cut, so it is chiefly a deep-decarb (Scenario D) lever — under an in-hold Scenario-B screen it is often ~$0 (revenue/O&M measures don't cut carbon, and many liable assets already sit under the near-term cap). Never assume a fine number. --- ## Design System All RSRA HTML output must conform to these rules. Claude must apply them on every run — never drift. **Colors** - Navy: `#12253A` — headers, section titles, strong text - Green: `#4CAF82` — eyebrows, accents, positive signals, chart fills - Muted: `#64748B` — secondary text, axis labels - Page bg: `#F8F9FB` - Section bg: `#fff` - Border: `#E2E8F0` - Warn: `#F59E0B` · Danger: `#EF4444` **Typography** - Font stack everywhere: `-apple-system,'Helvetica Neue',Arial,sans-serif` - Zero `Georgia`, zero `serif`, zero `@import`, zero web fonts - Section label: 9px, weight 600, `letter-spacing:.15em`, `text-transform:uppercase`, color `#1F6B45` - Section title: 18px, weight 700, color `#12253A`, `border-bottom:1.5px solid #12253A`, `padding-bottom:8px` **Section chrome pattern** ```html

Section Title

``` **Charts — inline SVG only** - Zero external charting libraries (no Chart.js, D3, Plotly, etc.) - Zero `` elements - Zero CDN `