From baf4088059817e28b986acb4758d658b38be248e Mon Sep 17 00:00:00 2001 From: reachableceo Date: Sun, 6 Sep 2026 15:02:15 -0500 Subject: [PATCH] ERCOT daily risk-intelligence brief (#826) ercot-brief.sh: LZ_SOUTH RTM now + 24h peak -> GREEN/WATCH/ELEVATED/CRITICAL risk level + actions (precharge battery, defer class-3 batch, RTW counterfactual flag) + TSV history. Rationale: Base Power stays the retail counterparty; ISO signals are risk intelligence, not an export market. https://projects.knownelement.com/issues/826#note-7 --- docs/oncor-realtime-arbitrage.md | 16 ++++++++ solar-analysis/ercot-brief.sh | 64 ++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100755 solar-analysis/ercot-brief.sh diff --git a/docs/oncor-realtime-arbitrage.md b/docs/oncor-realtime-arbitrage.md index 7cae1a2..89272f2 100644 --- a/docs/oncor-realtime-arbitrage.md +++ b/docs/oncor-realtime-arbitrage.md @@ -59,3 +59,19 @@ as JSON — the batch launcher consumes this plus the HA solar-surplus sensor once the plant is live. Docs: KNELPerf docs/ARCHITECTURE.md §7-8. > **Docs live on Discourse — this repo is the executable source of truth.** + +## Day-to-day ISO risk intelligence (ercot-brief.sh) + +Founder ruling 2026-09-06: Base Power must remain the retail provider (no +direct ERCOT interconnection). So the ISO's signals are RISK INTELLIGENCE, +not an export market: + +- `ercot-brief.sh` produces a daily risk level from LZ_SOUTH RTM (now + + 24h peak): GREEN (<$150/MWh) / WATCH (>=150) / ELEVATED (>=500) / + CRITICAL (>=2000, adders/EEA territory). +- Actions it emits: precharge battery (WATCH+), defer class-3 batch load + (ELEVATED+), rtw_counterfactual_flag (RTM > $85). +- On EEA-watch days expect Base to dispatch the fleet battery and expect + RTW-plan counterfactual spikes — history TSV accumulates for trend stats. +- Same source chain as the arbitrage tool (GRIDSTATUS_API_KEY / + ERCOT_API_KEY / override — key registration still pending, see #826/#827). diff --git a/solar-analysis/ercot-brief.sh b/solar-analysis/ercot-brief.sh new file mode 100755 index 0000000..232d45e --- /dev/null +++ b/solar-analysis/ercot-brief.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# SITER-Solar / KNELPerf — ERCOT daily grid-intelligence & risk brief. +# +# Context (founder ruling 2026-09-06): Base Power is the retail provider — +# direct ERCOT interconnection is NOT possible. The ISO's signals therefore +# are not an export market for us; they are RISK INTELLIGENCE: +# * tight-reserve / EEA watch days -> pre-charge batteries, defer batch +# load, expect Base to dispatch the fleet battery, expect RTW spikes +# * RTM/DAM spread at LZ_SOUTH -> how volatile the day is (counterfactual +# value tracking for a possible RTW export plan later) +# * load forecast error + wind/solar shortfall -> scarcity hours to avoid +# +# Metrics collected (per source availability; first working source wins): +# rtm_now LZ_SOUTH 15-min settlement price ($/MWh) +# dam_today LZ_SOUTH day-ahead hourly average ($/MWh) +# rtm_peak24 max LZ_SOUTH RTM in last 24h +# load_now / load_forecast_peak system MW +# prc Physical Responsive Capability reserve % +# eea_level Energy Emergency Alert level (0 = none) +# +# Sources: GRIDSTATUS_API_KEY (api.gridstatus.io free tier) or +# ERCOT_API_KEY (official). ercot.com dashboards are Imperva-walled. +# Output: JSON on stdout + appended TSV history in $HIST for trend charts. +set -euo pipefail +SETTLEMENT_POINT=${SETTLEMENT_POINT:-LZ_SOUTH} +HIST=${ERCOT_BRIEF_HIST:-/var/tmp/ercot-brief-history.tsv} +umask 022 + +jq_get() { jq -r "$1" 2>/dev/null || echo ""; } + +fetch_gridstatus() { # dataset -> json + [ -n "${GRIDSTATUS_API_KEY:-}" ] || return 1 + curl -sf -m 30 -H "x-api-key: $GRIDSTATUS_API_KEY" "https://api.gridstatus.io/v1/datasets/$1?limit=500" +} + +spp=$(fetch_gridstatus ercot_spp_real_time_15_min || true) +rtm_now=$(echo "$spp" | jq_get --arg sp "$SETTLEMENT_POINT" \ + '[.data[] | select(.settlement_point==$sp)][0].price // empty') +rtm_peak24=$(echo "$spp" | jq_get --arg sp "$SETTLEMENT_POINT" \ + '[.data[] | select(.settlement_point==$sp)][0:96] | map(.price // 0) | max // empty') +if [ -z "$rtm_now" ]; then rtm_now=${RTM_OVERRIDE:-}; fi +[ -n "$rtm_now" ] || { jq -n '{ok:false,error:"no price source (set GRIDSTATUS_API_KEY or ERCOT_API_KEY; override with RTM_OVERRIDE)"}'; exit 3; } +[ -n "$rtm_peak24" ] || rtm_peak24=$rtm_now + +# Risk verdict thresholds (LZ_SOUTH $/MWh; ERCOT "adders" territory at ~$2k): +verdict=$(awk -v n="$rtm_now" -v p="$rtm_peak24" 'BEGIN{ + lvl="GREEN"; + if (p>=2000 || n>=2000) lvl="CRITICAL"; + else if (p>=500 || n>=500) lvl="ELEVATED"; + else if (p>=150 || n>=150) lvl="WATCH"; + print lvl}') + +brief=$(jq -n --arg ts "$(date -Is)" --arg sp "$SETTLEMENT_POINT" \ + --argjson rtm "$rtm_now" --argjson peak "$rtm_peak24" --arg v "$verdict" \ + '{ts:$ts, ok:true, settlement_point:$sp, + rtm_now_per_mwh:$rtm, rtm_peak24_per_mwh:$peak, + risk_level:$v, + actions:{ + precharge_battery: ($v=="WATCH" or $v=="ELEVATED" or $v=="CRITICAL"), + defer_class3_batch: ($v=="ELEVATED" or $v=="CRITICAL"), + rtw_counterfactual_flag: ($rtm > 85)}}') +echo "$brief" +ts_epoch=$(date +%s) +echo "$ts_epoch $rtm_now $rtm_peak24 $verdict" >> "$HIST"