Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

56 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Climate Tech Financial Durability Engine

Does climate tech survive on its own, or does it depend on government subsidies and cheap financing? This engine tests that question through three scored mechanisms: SEC-filed balance-sheet leverage, the underlying engineering economics (LCOE) of the technology itself, and a regime comparison of price performance around the IRA's passage. Plus one descriptive mechanism (raw market-regime context around interest-rate cycles) that couldn't clear a genuine significance test and is reported honestly as color, not dressed up as a verdict. The disagreements between lenses are usually the interesting finding, not the noise.


1. Setup

# Backend
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirement.txt
cp .env.example .env   # optional, only needed for the AI features (see §6); add your GEMINI_API_KEY
python main.py

# Frontend (separate terminal)
cd frontend
npm install
cp .env.example .env   # optional, only needed if the backend isn't on 127.0.0.1:8000
npm run dev

No API keys needed for the four analytical lenses: yfinance, SEC EDGAR, and NREL's ATB data lake are all unauthenticated. The AI features (methodology chatbot + live cross-lens narrative, see §6) do need a free GEMINI_API_KEY in backend/.env; everything else works without it.

Backend tests (pure scoring/analysis logic, no network calls) live in backend/tests/:

cd backend
source venv/bin/activate
python -m pytest

2. Repository Layout

climateTechAnalysis/
├── backend/
│   ├── api/
│   │   ├── analysis.py         # /statistics, /fundamentals, /policy, /physical, /{vertical}/explain
│   │   ├── data.py             # Chart endpoints for all four lenses
│   │   └── chat.py             # /chat: methodology + live-data chatbot
│   ├── config/
│   │   ├── baskets.py          # Pure-play equity baskets + GSPC/XLE benchmark tickers
│   │   └── policy_events.py    # Curated US federal policy event table (now used only for the
│   │                            # Market chart's event markers, see Policy lens note below)
│   ├── services/
│   │   ├── market/          # fetch.py (weekly price fetch + benchmark matrix, disk caching),
│   │   │                    # analysis.py (RegimeAnalyticsEngine: ZIRP-vs-hiking comparison,
│   │   │                    # descriptive), visualization.py (regime-comparison +
│   │   │                    # cumulative-performance charts)
│   │   ├── fundamentals/    # fetch.py (SEC EDGAR client), analysis.py (leverage verdict + charts)
│   │   ├── policy/          # analysis.py (PolicyRegimeEngine: pre/post-IRA comparison, scored)
│   │   ├── physical/        # fetch.py (NREL ATB client), analysis.py (LCOE verdict + charts)
│   │   ├── chat/            # gemini_client.py (Gemini wrapper), context.py (shared grounding:
│   │   │                    # static docs + live lens data), service.py (chatbot), explain.py
│   │   │                    # (per-vertical narrative + cache), see §6
│   │   └── scoring.py       # Shared Dependency Score (tanh) + significance-to-confidence math
│   ├── schemas/
│   │   ├── analysis.py         # Pydantic response models for all four lenses
│   │   └── chat.py             # Chat + explain request/response models
│   └── main.py
└── frontend/
    └── src/                    # Dashboard: 4 lens sections per card + consensus indicator,
                                 # ChatWidget (floating methodology assistant) + ExplainPanel
                                 # (per-vertical cross-lens narrative), see §6

3. The Four Lenses

Three lenses produce a scored Dependency Score; the Market lens is deliberately descriptive (see below for why). None of them rely on the others, and none get the final word. The dashboard shows all four side by side with a consensus indicator built from the three scored lenses, rather than collapsing everything into one number.

Lens Question it asks Method Scored? Source
Market How has a vertical's performance vs. the S&P 500 shifted between the ZIRP era (2009-2021) and the hiking/higher-for-longer era (2022-present)? Regime comparison of weekly relative-to-GSPC returns No, descriptive context Yahoo Finance
Fundamentals Can the company cover its own debt without help? Median interest coverage & Debt/EBITDA from actual filings Yes SEC EDGAR (XBRL)
Policy Has a vertical's performance vs. the S&P 500 shifted since the IRA's signing (2022-08-16)? Regime comparison of weekly relative-to-GSPC returns, split pre/post-IRA Yes Yahoo Finance
Physical Does the technology's own cost of energy depend on cheap financing? LCOE sensitivity to WACC ±200bps, using NREL's own published cost/performance assumptions Yes NREL Annual Technology Baseline

Full methodology, data-source rationale, and the verified bugs/fixes that shaped this design are in docs/DATA_SOURCING_PLAN.md and docs/REVIEW_AND_ROADMAP.md, including a documented case where the original single-ETF, single-regression version of this engine had a data-fabrication bug that mechanically biased every verdict toward "durable," and a later case where a whole-period HAC regression turned out to be hiding decades of regime change (Grid's rolling beta) behind one misleading static coefficient. Both are the reason this project moved away from static regressions toward the regime-comparison design described below.

Why Market is descriptive but Policy isn't, even though both compare performance across two regimes relative to GSPC: verified directly against real data (not assumed) that Market's ZIRP-vs-hiking delta doesn't clear a genuine significance test for any vertical, at any data frequency tried (weekly or monthly). The large-looking annualized deltas don't survive contact with how volatile these stocks actually are, given the available sample sizes. Policy's pre/post-IRA split uses the same mechanics but happens to produce one real, robust result (Solar, p=0.093 weekly / p=0.061 monthly, checked both, consistent), so it keeps the project's standard significance-gated Dependency Score. Forcing Market's data through that same machinery would just make every vertical "Inconclusive," which defeats the purpose. So it's reported as honest, caveated context instead of a confidence score the data doesn't support.

Note on verdicts: each scored lens's verdict is a continuous Dependency Score (−100 fully durable to +100 fully dependent, see backend/services/scoring.py), derived from a documented threshold (e.g. Policy's 10-percentage-point/year regime-delta threshold, roughly the market's own long-run equity risk premium) graded via tanh instead of a hard cliff, discounted by a genuine significance test where one exists. Scores within ±20 of zero render as Inconclusive rather than being forced into a binary label, a real correction, not cosmetic: forcing every non-significant result to a confident-sounding label is a failure to reject the null, not evidence of durability.

Not every lens applies to every vertical, and that's reported explicitly rather than hidden: Grid has no Physical-lens entry (NREL's ATB doesn't model grid/T&D infrastructure on an LCOE basis), and Storage has no Physical-lens entry either (ATB only publishes CAPEX-side costs for batteries, no capacity factor, WACC, or LCOE, since storage doesn't "generate" energy the way solar/wind do).


4. Case Study: Prior Opinion, Verdict & Limitations

This engine exists to test a specific thesis, stated and scored like an experiment rather than narrated after the fact. Full per-vertical reasoning lives in docs/CASE_STUDY_MEMO.md; this section is the condensed version.

Thesis under test: government subsidies via the IRA and global net-zero commitments are channeling trillions into solar, wind, battery storage, and grid modernization. Which of these verticals have durable economics, and which are purely subsidy-dependent? (Scope note: a deliberate US-listed-equity case study within that broader global trend, see Limitations.)

Prior opinion (stated before running the analysis)

Position: Solar and Battery Storage have durable, self-sustaining economics. Wind and Grid Modernization are capital-heavy and structurally more dependent on favorable financing conditions.

  • Solar: durable on operating economics (no fuel input, steep cost-decline curve, diversifying demand).
  • Storage: durable on a trajectory argument (falling lithium-ion costs), but flagged in advance as the vertical most likely to show strained near-term financials regardless of that long-run direction.
  • Wind: capital-heavy by nature; expected real WACC sensitivity at both the project and company level.
  • Grid: flagged upfront as the one vertical where "capital heavy" and "financing dependent" could decouple. This data covers equipment/EPC suppliers, not the asset-owning utilities that actually bear financing risk.

Falsifiable predictions, stated in advance: confirms the thesis if Solar/Storage show strong coverage and low leverage while Wind/Grid show elevated leverage and high WACC-sensitivity; contradicts it if that pattern reverses; complicates it (a real outcome, not a failure) if durability diverges between company-level and project-level economics for the same vertical.

Verdict: is the thesis supported?

Partially, and unevenly across mechanisms. That unevenness is itself the finding.

Vertical Prior view Company-level (Fundamentals) Technology/price-based (Physical, Policy, Market) Outcome
Solar Durable Confirms Contradicts (2 of 3 secondary reads) Complicated
Storage Durable Contradicts, unambiguously Inconclusive / consistent-but-not-significant Contradicted
Wind Dependent Confirms Confirms (where measurable) Confirmed
Grid Dependent Contradicts Inconclusive / mildly durable-leaning Contradicted (as anticipated)

"Durable vs. subsidy-dependent" turns out not to be a property of a vertical, but of a company's position in that vertical's value chain and which economic layer you're measuring (balance sheet vs. project economics vs. market pricing). Solar and Grid make the point most directly: Solar's companies are solvent while its project economics and post-IRA pricing both lean dependent; Grid's companies look durable specifically because the basket is suppliers, not the capital-intensive asset owners the "grid modernization is capital-heavy" intuition is really about. Storage's negative interest coverage needs no statistical inference at all: a plain balance-sheet fact, and the single strongest finding in the whole analysis. Per-vertical numbers behind this table are in §5 below.

Limitations

  • US-listed scope. The baskets are US-domiciled companies (with 2 non-US exceptions currently live in the data: DNNGY/Denmark in Wind, NVT/Ireland in Grid; see docs/REVIEW_AND_ROADMAP.md Phase 7). Makes a global theme tractable, but excludes genuine global supply-chain names (Vestas for wind, in particular).
  • Small baskets. Storage (3 tickers) and Wind (3 tickers) are thin; a single constituent's idiosyncratic move can matter more than in Grid's 6-7-name basket.
  • Market has no scored verdict anywhere (see §3 for why). Its descriptive reads are directionally informative, not confirmed findings.
  • Policy's one significant result (Solar) is confounded by design. A multi-year pre/post split can't fully separate an IRA-specific effect from the concurrent rate-hiking cycle and sector-specific competitive dynamics (Chinese panel oversupply).
  • Physical has no entry for Grid or Storage (data-availability gap in NREL's ATB, see §3), so those two verticals lean more heavily on Fundamentals.

5. Current Snapshot (as of this write-up)

The numbers behind §4's verdict table. Dependency Score in parentheses for scored lenses (−100 fully durable to +100 fully dependent; |score| ≤ 20 reads as Inconclusive). Market has no score; its column reports the raw regime read.

Vertical Market (context) Fundamentals Policy Physical
Solar Edge reversed in high-rate era (ZIRP +5.5pp/yr → hiking −10.1pp/yr, not significant) Self-sufficient (−74.7) Post-IRA Underperformance (+33.9, p=0.093) Capital-Intensity Dependent (+59.8)
Storage Edge reversed in high-rate era (ZIRP +44.9pp/yr → hiking −12.6pp/yr, not significant) Financing Dependent (+100.0, ICR −9.1x) Inconclusive (0.0, p=0.86) N/A, no LCOE concept applies
Wind No material regime shift (weak in both eras) Financing Dependent (+34.7, ICR 1.5x, D/E 5.4x) Inconclusive (0.0, p=0.22) Capital-Intensity Dependent (+56.3)
Grid No material regime shift (flat ZIRP, modest hiking-era edge) Self-sufficient (−64.0) Inconclusive (0.0, p=0.58) N/A, no LCOE concept applies

These numbers move whenever the underlying data does: market prices update continuously, SEC filings update quarterly, NREL's ATB updates annually. Use the dashboard's refresh button for the current live values rather than treating this table as a snapshot in time.


6. AI Features

Two Gemini-powered features layer natural-language output on top of the four lenses. The goal isn't just convenience, it's accessibility and transparency: making the methodology and current results legible to someone who doesn't want to read six markdown files, without asking that person to trust an opaque AI-generated verdict in exchange. Every answer is grounded in content a reader could just as easily read themselves, and both features are built to say "not covered by this data" rather than fill a gap with something that merely sounds plausible.

Feature What it does Endpoint
Methodology Chatbot Free-form Q&A about how the engine works and what its current results mean, grounded in both the static methodology docs and the four lenses' live output, not a frozen snapshot POST /api/v1/chat
Explain This Disagreement One click on a vertical's card generates a short narrative (the live equivalent of a docs/CASE_STUDY_MEMO.md entry) naming where that vertical's lenses currently agree or disagree GET /api/v1/analysis/{vertical}/explain

How the transparency goal is enforced, not just claimed: both features share the same grounding context (backend/services/chat/context.py): the full methodology-doc corpus (small enough to include in full on every request, no embeddings/vector DB, so nothing is silently dropped or approximated) plus a live snapshot of what each lens currently returns, pulled from the same cached engines the dashboard's own charts use. The system prompt requires every claim to cite which lens it comes from, forbids asserting causality beyond what the docs already claim (e.g. "Post-IRA underperformance," never "caused by the IRA," the same restraint this project applies to itself elsewhere), and instructs the model to say plainly when a question falls outside the provided context instead of guessing. Neither feature computes a new verdict or score. They describe the existing ones, they don't grade them, so nothing an AI generates can quietly become a source of truth the numeric lenses didn't already produce.

The narrative endpoint additionally caches its output in-memory per (vertical, current score signature), so repeat requests don't re-trigger a Gemini call unless the underlying lens numbers actually changed. The narrative shown always matches the numbers on the card next to it, never a stale generation from an earlier state.

Setup: requires a free GEMINI_API_KEY in backend/.env (see backend/.env.example). This is the only feature in this project that needs an API key; everything else works without one. Get a key at aistudio.google.com/apikey. GEMINI_MODEL defaults to gemini-flash-latest, a Google-maintained alias that tracks their current flash model rather than a dated snapshot that can be retired mid-project (as gemini-2.5-flash was, here).


7. Tech Stack

  • Data: yfinance (equities, GSPC, XLE), SEC EDGAR (XBRL filings), NREL ATB (technology cost/performance data)
  • Analysis: pandas, scipy (Welch's t-tests for the regime comparisons), pyarrow (NREL Parquet data)
  • AI: google-genai (Gemini API): methodology chatbot + live cross-lens narrative (see §6)
  • Backend: FastAPI + Uvicorn, with on-disk caching so repeat requests don't re-hit external APIs
  • Frontend: Vite + React + TypeScript + Tailwind CSS v4 + Lucide React
  • Charts: react-plotly.js, rendering Plotly figures generated server-side per lens

About

Analyze US climate tech investments. Determine which climate verticals are economically durable vs subsidy-dependent.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages