# Project export: Morro

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: The geoengineering operating system for climate renewal. Predict natural disasters, pick from a suite of rain/cloud seeding interventions, and prevent their destructive impacts.
- Devpost: https://devpost.com/software/morro
- GitHub: https://github.com/Thaarak/graphcast.git
- Demo: https://graphcastdeploy.vercel.app/
- Video: https://www.youtube.com/embed/BBR3-OXCjtU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner ([Stanford Ecopreneurship] Sustainability: Best incorporation of broader context (e.g. regulatory, competitive landscape) to improve venture viability)
- Team: 1 GitHub contributor(s) — Thaarak (4 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# Google DeepMind GraphCast and GenCast

This package contains example code to run and train the weather models used in the research papers [GraphCast](https://www.science.org/doi/10.1126/science.adi2336) and [GenCast](https://arxiv.org/abs/2312.15796).

It also provides pretrained model weights, normalization statistics and example input data on [Google Cloud Bucket](https://console.cloud.google.com/storage/browser/dm_graphcast).

Full model training requires downloading the
[ERA5](https://www.ecmwf.int/en/forecasts/datasets/reanalysis-datasets/era5)
dataset, available from [ECMWF](https://www.ecmwf.int/). This can best be
accessed as Zarr from [Weatherbench2's ERA5 data](https://weatherbench2.readthedocs.io/en/latest/data-guide.html#era5).

Data for operational fine-tuning can similarly be accessed at [Weatherbench2's HRES 0th frame data](https://weatherbench2.readthedocs.io/en/latest/data-guide.html#ifs-hres-t-0-analysis).

These datasets may be governed by separate terms and conditions or license provisions. Your use of such third-party materials is subject to any such terms and you should check that you can comply with any applicable restrictions or terms and conditions before use.

## Overview of files common to models

*   `autoregressive.py`: Wrapper used to run (and train) the one-step predictions
    to produce a sequence of predictions by auto-regressively feeding the
    outputs back as inputs at each step, in JAX a differentiable way.
*   `checkpoint.py`: Utils to serialize and deserialize trees.
*   `data_utils.py`: Utils for data preprocessing.
*   `deep_typed_graph_net.py`: General purpose deep graph neural network (GNN)
    that operates on `TypedGraph`'s where both inputs and outputs are flat
    vectors of features for each of the nodes and edges.
*   `grid_mesh_connectivity.py`: Tools for converting between regular grids on a
    sphere and triangular meshes.
*   `icosahedral_mesh.py`: Definition of an icosahedral multi-mesh.
*   `losses.py`: Loss computations, including latitude-weighting.
*   `mlp.py`: Utils for building MLPs with norm conditioning layers.
*   `model_utils.py`: Utilities to produce flat node and edge vector features
    from input grid data, and to manipulate the node output vectors back
    into a multilevel grid data.
*   `normalization.py`: Wrapper used to normalize inputs according to historical
    values, and targets according to historical time differences.
*   `predictor_base.py`: Defines the interface of the predictor, which models
    and all of the wrappers implement.
*   `rollout.py`: Similar to `autoregressive.py` but used only at inference time
    using a python loop to produce longer, but non-differentiable trajectories.
*   `typed_graph.py`: Definition of `TypedGraph`'s.
*   `typed_graph_net.py`: Implementation of simple graph neural network
    building blocks defined over `TypedGraph`'s that can be combined to build
    deeper models.
*   `xarray_jax.py`: A wrapper to let JAX work with `xarray`s.
*   `xarray_tree.py`: An implementation of tree.map_structure that works with
    `xarray`s.

## GenCast: Diffusion-based ensemble forecasting for medium-range weather

This package provides four pretrained models:

1.  `GenCast 0p25deg <2019`, GenCast model at 0.25deg resolution with 13
pressure levels and a 6 times refined icosahedral mesh. This model is trained on
ERA5 data from 1979 to 2018 (inclusive), and can be causally evaluated on 2019
and later years. This model was described in the paper
`GenCast: Diffusion-based ensemble forecasting for medium-range weather`
(https://arxiv.org/abs/2312.15796)

2.  `GenCast 0p25deg Operational <2022`, GenCast model at 0.25deg resolution, with 13 pressure levels and a 6
times refined icosahedral mesh. This model is trained on ERA5 data from
1979 to 2018, and fine-tuned on HRES-fc0 data from
2016 to 2021 and can be causally evaluated on 2022 and later years.
This model can make predictions in an operational setting (i.e., initialised
from HRES-fc0)

3.  `GenCast 1p0deg <2019`, GenCast model at 1deg resolution, with 13 pressure
levels and a 5 times refined icosahedral mesh. This model is
trained on ERA5 data from 1979 to 2018, and can be causally evaluated on 2019 and later years.
This model has a smaller memory footprint than the 0.25deg models

4. `GenCast 1p0deg Mini <2019`, GenCast model at 1deg resolution, with 13 pressure levels and a
4 times refined icosahedral mesh. This model is trained on ERA5 data
from 1979 to 2018, and can be causally evaluated on 2019 and later years.
This model has the smallest memory footprint of those provided and has been
provided to enable low cost demonstrations (for example, it is runnable in a free Colab notebook).
While its performance is reasonable, it is not representative of the performance
of the GenCast models (1-3) above. For reference, a scorecard comparing its performance to ENS can be found in [docs/](https://github.com/google-deepmind/graphcast/blob/main/docs/GenCast_1p0deg_Mini_ENS_scorecard.png). Note that in this scorecard,
GenCast Mini only uses 8 member ensembles (vs. ENS' 50) so we use the fair (unbiased)
CRPS to allow for fair comparison.

The best starting point is to open `gencast_mini_demo.ipynb` in [Colaboratory](https://colab.research.google.com/github/deepmind/graphcast/blob/master/gencast_mini_demo.ipynb), which gives an
example of loading data, generating random weights or loading a `GenCast 1p0deg Mini <2019`
snapshot, generating predictions, computing the loss and computing gradients.
The one-step implementation of GenCast architecture is provided in
`gencast.py` and the relevant data, weights and statistics are in the `gencast/`
subdir of the Google Cloud Bucket.

### Instructions for running GenCast on Google Cloud compute

[cloud_vm_setup.md](https://github.com/google-deepmind/graphcast/blob/main/docs/cloud_vm_setup.md)
contains detailed instructions on launching a Google Cloud TPU VM. This provides
a means of running models (1-3) in the separate `gencast_demo_cloud_vm.ipynb` through [Colaboratory](https://colab.research.google.com/github/deepmind/graphcast/blob/master/gencast_demo_cloud_vm.ipynb).

The document also provides [instructions](https://github.com/google-deepmind/graphcast/blob/main/docs/cloud_vm_setup.md#running-inference-on-gpu) for running GenCast on a GPU. This requires using a different attention implementation.

### Brief description of relevant library files

*   `denoiser.py`: The GenCast denoiser for one step predictions.
*   `denoisers_base.py`: Defines the interface of the denoiser.
*   `dpm_solver_plus_plus_2s.py`: Sampler using DPM-Solver++ 2S from [1].
*   `gencast.py`: Combines the GenCast model architecture, wrapped as a
    denoiser, with a sampler to generate predictions.
*   `nan_cleaning.py`: Wraps a predictor to allow it to work with data
    cleaned of NaNs. Used to remove NaNs from sea surface temperature.
*   `samplers_base.py`: Defines the interface of the sampler.
*   `samplers_utils.py`: Utility methods for the sampler.
*   `sparse_transformer.py`: General purpose sparse transformer that
    operates on `TypedGraph`'s where both inputs and outputs are flat vectors of
    features for each of the nodes and edges. `predictor.py` uses one of these
    for the mesh GNN.
*   `sparse_transformer_utils.py`: Utility methods for the sparse
    transformer.
*   `transformer.py`: Wraps the mesh transformer, swapping the leading
    two axes of the nodes in the input graph.

[1] DPM-Solver++: Fast Solver for Guided Sampling of Diffusion Probabilistic
  Models, https://arxiv.org/abs/2211.01095

## GraphCast: Learning skillful medium-range global weather forecasting

This package provides three pretrained models:

1.  `GraphCast`, the high-resolution model used in the GraphCast paper (0.25 degree
resolution, 37 pressure levels), trained on ERA5 data from 1979 to 2017,

2.  `GraphCast_small`, a smaller, low-resolution version of GraphCast (1 degree
resolution, 13 pre

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 128 recognized source files, 1222 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 142)

```
.DS_Store
CLAUDE.md
climate-intervention-dashboard/.gitignore
climate-intervention-dashboard/app/extreme/page.tsx
climate-intervention-dashboard/app/globals.css
climate-intervention-dashboard/app/layout.tsx
climate-intervention-dashboard/app/page.tsx
climate-intervention-dashboard/CLAUDE.md
climate-intervention-dashboard/components.json
climate-intervention-dashboard/components/dashboard/data-summary-panel.tsx
climate-intervention-dashboard/components/dashboard/header.tsx
climate-intervention-dashboard/components/dashboard/kpi-panel.tsx
climate-intervention-dashboard/components/dashboard/map-view.tsx
climate-intervention-dashboard/components/dashboard/single-video-view.tsx
climate-intervention-dashboard/components/dashboard/strategy-cards.tsx
climate-intervention-dashboard/components/dashboard/timeline.tsx
climate-intervention-dashboard/components/dashboard/video-comparison.tsx
climate-intervention-dashboard/components/theme-provider.tsx
climate-intervention-dashboard/components/ui/accordion.tsx
climate-intervention-dashboard/components/ui/alert-dialog.tsx
climate-intervention-dashboard/components/ui/alert.tsx
climate-intervention-dashboard/components/ui/aspect-ratio.tsx
climate-intervention-dashboard/components/ui/avatar.tsx
climate-intervention-dashboard/components/ui/badge.tsx
climate-intervention-dashboard/components/ui/breadcrumb.tsx
climate-intervention-dashboard/components/ui/button.tsx
climate-intervention-dashboard/components/ui/calendar.tsx
climate-intervention-dashboard/components/ui/card.tsx
climate-intervention-dashboard/components/ui/carousel.tsx
climate-intervention-dashboard/components/ui/chart.tsx
climate-intervention-dashboard/components/ui/checkbox.tsx
climate-intervention-dashboard/components/ui/collapsible.tsx
climate-intervention-dashboard/components/ui/command.tsx
climate-intervention-dashboard/components/ui/context-menu.tsx
climate-intervention-dashboard/components/ui/dialog.tsx
climate-intervention-dashboard/components/ui/drawer.tsx
climate-intervention-dashboard/components/ui/dropdown-menu.tsx
climate-intervention-dashboard/components/ui/form.tsx
climate-intervention-dashboard/components/ui/hover-card.tsx
climate-intervention-dashboard/components/ui/input-otp.tsx
climate-intervention-dashboard/components/ui/input.tsx
climate-intervention-dashboard/components/ui/label.tsx
climate-intervention-dashboard/components/ui/menubar.tsx
climate-intervention-dashboard/components/ui/navigation-menu.tsx
climate-intervention-dashboard/components/ui/pagination.tsx
climate-intervention-dashboard/components/ui/popover.tsx
climate-intervention-dashboard/components/ui/progress.tsx
climate-intervention-dashboard/components/ui/radio-group.tsx
climate-intervention-dashboard/components/ui/resizable.tsx
climate-intervention-dashboard/components/ui/scroll-area.tsx
climate-intervention-dashboard/components/ui/select.tsx
climate-intervention-dashboard/components/ui/separator.tsx
climate-intervention-dashboard/components/ui/sheet.tsx
climate-intervention-dashboard/components/ui/sidebar.tsx
climate-intervention-dashboard/components/ui/skeleton.tsx
climate-intervention-dashboard/components/ui/slider.tsx
climate-intervention-dashboard/components/ui/sonner.tsx
climate-intervention-dashboard/components/ui/switch.tsx
climate-intervention-dashboard/components/ui/table.tsx
climate-intervention-dashboard/components/ui/tabs.tsx
climate-intervention-dashboard/components/ui/textarea.tsx
climate-intervention-dashboard/components/ui/toast.tsx
climate-intervention-dashboard/components/ui/toaster.tsx
climate-intervention-dashboard/components/ui/toggle-group.tsx
climate-intervention-dashboard/components/ui/toggle.tsx
climate-intervention-dashboard/components/ui/tooltip.tsx
climate-intervention-dashboard/components/ui/use-mobile.tsx
climate-intervention-dashboard/components/ui/use-toast.ts
climate-intervention-dashboard/hooks/use-mobile.tsx
climate-intervention-dashboard/hooks/use-toast.ts
climate-intervention-dashboard/lib/utils.ts
climate-intervention-dashboard/next-env.d.ts
climate-intervention-dashboard/next.config.mjs
climate-intervention-dashboard/package.json
climate-intervention-dashboard/postcss.config.mjs
climate-intervention-dashboard/styles/globals.css
climate-intervention-dashboard/tailwind.config.ts
climate-intervention-dashboard/tsconfig.json
CONTRIBUTING.md
docs/cloud_vm_setup.md
drought_detector.py
drought_map.py
extreme_drought_map.py
graphcast_weather_map.py
graphcast/autoregressive.py
graphcast/casting.py
graphcast/checkpoint_test.py
graphcast/checkpoint.py
graphcast/data_utils_test.py
graphcast/data_utils.py
graphcast/deep_typed_graph_net.py
graphcast/denoiser.py
graphcast/denoisers_base.py
graphcast/dpm_solver_plus_plus_2s.py
graphcast/gencast.py
graphcast/graphcast.py
graphcast/grid_mesh_connectivity_test.py
graphcast/grid_mesh_connectivity.py
graphcast/icosahedral_mesh_test.py
graphcast/icosahedral_mesh.py
graphcast/losses.py
graphcast/mlp.py
graphcast/model_utils.py
graphcast/nan_cleaning.py
graphcast/normalization.py
graphcast/predictor_base.py
graphcast/rollout.py
graphcast/samplers_base.py
graphcast/samplers_utils.py
graphcast/solar_radiation_test.py
graphcast/solar_radiation.py
graphcast/sparse_transformer_utils.py
graphcast/sparse_transformer.py
graphcast/transformer.py
graphcast/typed_graph_net.py
graphcast/typed_graph.py
graphcast/xarray_jax_test.py
graphcast/xarray_jax.py
graphcast/xarray_tree_test.py
graphcast/xarray_tree.py
[22 more files omitted for size]
```

### Dependencies

- climate-intervention-dashboard/package.json: @hookform/resolvers@^3.9.1, @radix-ui/react-accordion@1.2.2, @radix-ui/react-alert-dialog@1.1.4, @radix-ui/react-aspect-ratio@1.1.1, @radix-ui/react-avatar@1.1.2, @radix-ui/react-checkbox@1.1.3, @radix-ui/react-collapsible@1.1.2, @radix-ui/react-context-menu@2.2.4, @radix-ui/react-dialog@1.1.4, @radix-ui/react-dropdown-menu@2.1.4, @radix-ui/react-hover-card@1.1.4, @radix-ui/react-label@2.1.1, @radix-ui/react-menubar@1.1.4, @radix-ui/react-navigation-menu@1.2.3, @radix-ui/react-popover@1.1.4, @radix-ui/react-progress@1.1.1, @radix-ui/react-radio-group@1.2.2, @radix-ui/react-scroll-area@1.2.2, @radix-ui/react-select@2.1.4, @radix-ui/react-separator@1.1.1, @radix-ui/react-slider@1.2.2, @radix-ui/react-slot@1.1.1, @radix-ui/react-switch@1.1.2, @radix-ui/react-tabs@1.1.2, @radix-ui/react-toast@1.2.4, @radix-ui/react-toggle@1.1.1, @radix-ui/react-toggle-group@1.1.1, @radix-ui/react-tooltip@1.1.6, @tailwindcss/postcss@^4.1.13, @types/node@^22, @types/react@19.2.7, @types/react-dom@19.2.3, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@1.1.1, date-fns@4.1.0, embla-carousel-react@8.5.1, input-otp@1.4.1, lucide-react@^0.544.0, next@16.1.6, next-themes@^0.4.6, postcss@^8.5, react@19.2.3, react-day-picker@8.10.1, react-dom@19.2.3, react-hook-form@^7.54.1, react-resizable-panels@^2.1.7, recharts@2.15.0, sonner@^1.7.1, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, typescript@5.7.3, vaul@^1.1.2, zod@^3.24.1

### Recent commits (newest first)

- kieran's stuff
- for judges
- Merge pull request #1 from Thaarak/ts-214
- changes
- Update contact information with privacy policy.
- Update test for `jax_pmap_shmap_merge=True`.
- Replace `jax.stages.OutInfo` with `jax.ShapeDtypeStruct`
- Clean up math in `get_rotation_matrices_to_local_coordinates`.
- Allowing any leading dims in einsum.
- Explicitly inject numpy and scipy.spatial.transform in some model utils.
- Adding an `edge_normalization_factor` argument and utils for converting between lat-lon and cartesian.
- Adds tree_map_with_dims to xarray_jax.
- Small data_utils.py refactor
- No public description
- Add xarray_jax.scan, which wraps jax.lax.scan to handle xarray inputs/outputs.
- Update xarray_jax to register xarray.DataTree with jax.tree_util.
- Avoid call to "_get_next_inputs" in the last step of a rollout.
- Update xarray_jax to support AOT compilation via `jax.jit(...).lower(...).compile()`, and `jax.eval_shape`.
- Reconfigure JAX in Colab to use latest image.
- Ensure sqrt operand does not resolve to negative in apply_stochastic_churn.

## Key source files (fetched from GitHub, selected and truncated for size)

### CONTRIBUTING.md

```markdown
# How to Contribute

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

## Community Guidelines

This project follows [Google's Open Source Community
Guidelines](https://opensource.google/conduct/).

```

### CLAUDE.md

```markdown
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

GraphCast is Google DeepMind's weather forecasting system implementing two AI models:
- **GraphCast**: Deterministic medium-range weather forecasting using graph neural networks
- **GenCast**: Diffusion-based ensemble forecasting for probabilistic predictions

Both models operate on global weather data at various resolutions (0.25° and 1.0°) using an icosahedral mesh representation.

## Build & Installation

```bash
pip install -e .
```

Dependencies are defined in `setup.py`. Key requirements: JAX, Haiku, Jraph, XArray, NumPy, SciPy.

## Running Tests

Tests use `absl.testing.absltest`. Run individual tests:
```bash
python graphcast/checkpoint_test.py
python graphcast/xarray_jax_test.py
python graphcast/data_utils_test.py
```

## Architecture

### Core Prediction Flow
1. **Input Processing** (`data_utils.py`, `model_utils.py`): Convert xarray weather data to flat node/edge features on the mesh
2. **Graph Neural Network** (`deep_typed_graph_net.py`, `typed_graph_net.py`): Process on `TypedGraph` structures with heterogeneous node/edge types
3. **Autoregressive Wrapper** (`autoregressive.py`): Chain single-step predictions for multi-step forecasts (differentiable)
4. **Rollout** (`rollout.py`): Non-differentiable inference loop for longer trajectories

### Mesh System
- `icosahedral_mesh.py`: Defines multi-resolution icosahedral mesh hierarchy
- `grid_mesh_connectivity.py`: Bidirectional mapping between lat/lon grids and triangular mesh
- `typed_graph.py`: Core `TypedGraph` dataclass for heterogeneous graphs

### Model-Specific Components
**GraphCast** (`graphcast.py`):
- Encoder-processor-decoder architecture
- Single deterministic prediction per step
- `casting.py`: BFloat16 precision wrapper

**GenCast** (`gencast.py`, `denoiser.py`):
- Diffusion model with denoising network
- `sparse_transformer.py`: Attention mechanism on mesh
- `dpm_solver_plus_plus_2s.py`: DPM-Solver++ sampler for inference
- `nan_cleaning.py`: Handles NaN values in sea surface temperature

### JAX/XArray Integration
- `xarray_jax.py`: Makes xarray compatible with JAX transformations (jit, grad, vmap)
- `xarray_tree.py`: Tree operations for xarray structures
- `normalization.py`: Statistical normalization using historical data

### Supporting Modules
- `losses.py`: Loss functions with latitude weighting
- `solar_radiation.py`: TOA solar radiation forcing computation
- `checkpoint.py`: Serialization for model weights and configs

## Data Sources

- Training data: ERA5 reanalysis from ECMWF (via WeatherBench2 Zarr)
- Operational fine-tuning: HRES-fc0 data
- Pretrained weights and stats: `gs://dm_graphcast/` Google Cloud Bucket

## Demo Notebooks

- `graphcast_demo.ipynb`: GraphCast inference example
- `gencast_mini_demo.ipynb`: GenCast with small model (runnable in free Colab)
- `gencast_demo_cloud_vm.ipynb`: GenCast on Cloud TPU/GPU

```

### climate-intervention-dashboard/package.json

```
{
  "name": "my-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbo",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.1",
    "@radix-ui/react-accordion": "1.2.2",
    "@radix-ui/react-alert-dialog": "1.1.4",
    "@radix-ui/react-aspect-ratio": "1.1.1",
    "@radix-ui/react-avatar": "1.1.2",
    "@radix-ui/react-checkbox": "1.1.3",
    "@radix-ui/react-collapsible": "1.1.2",
    "@radix-ui/react-context-menu": "2.2.4",
    "@radix-ui/react-dialog": "1.1.4",
    "@radix-ui/react-dropdown-menu": "2.1.4",
    "@radix-ui/react-hover-card": "1.1.4",
    "@radix-ui/react-label": "2.1.1",
    "@radix-ui/react-menubar": "1.1.4",
    "@radix-ui/react-navigation-menu": "1.2.3",
    "@radix-ui/react-popover": "1.1.4",
    "@radix-ui/react-progress": "1.1.1",
    "@radix-ui/react-radio-group": "1.2.2",
    "@radix-ui/react-scroll-area": "1.2.2",
    "@radix-ui/react-select": "2.1.4",
    "@radix-ui/react-separator": "1.1.1",
    "@radix-ui/react-slider": "1.2.2",
    "@radix-ui/react-slot": "1.1.1",
    "@radix-ui/react-switch": "1.1.2",
    "@radix-ui/react-tabs": "1.1.2",
    "@radix-ui/react-toast": "1.2.4",
    "@radix-ui/react-toggle": "1.1.1",
    "@radix-ui/react-toggle-group": "1.1.1",
    "@radix-ui/react-tooltip": "1.1.6",
    "autoprefixer": "^10.4.20",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "1.1.1",
    "date-fns": "4.1.0",
    "embla-carousel-react": "8.5.1",
    "input-otp": "1.4.1",
    "lucide-react": "^0.544.0",
    "next": "16.1.6",
    "next-themes": "^0.4.6",
    "react": "19.2.3",
    "react-day-picker": "8.10.1",
    "react-dom": "19.2.3",
    "react-hook-form": "^7.54.1",
    "react-resizable-panels": "^2.1.7",
    "recharts": "2.15.0",
    "sonner": "^1.7.1",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^1.1.2",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4.1.13",
    "@types/node": "^22",
    "@types/react": "19.2.7",
    "@types/react-dom": "19.2.3",
    "postcss": "^8.5",
    "tailwindcss": "^3.4.17",
    "typescript": "5.7.3"
  },
  "pnpm": {
    "overrides": {
      "@types/react": "19.2.7",
      "@types/react-dom": "19.2.3"
    }
  }
}

```

### climate-intervention-dashboard/app/layout.tsx

```typescript
import type { Metadata, Viewport } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'

import './globals.css'

const _geist = Geist({ subsets: ['latin'] })
const _geistMono = Geist_Mono({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Atmospheric Orchestrator',
  description: 'Climate Intervention Dashboard — Hyper-minimalist geospatial strategy platform.',
}

export const viewport: Viewport = {
  themeColor: '#ffffff',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <body suppressHydrationWarning className="font-sans antialiased overflow-hidden">{children}</body>
    </html>
  )
}

```

### climate-intervention-dashboard/app/page.tsx

```typescript
"use client"

import { useState, useCallback } from "react"
import { DashboardHeader, type ModelType } from "@/components/dashboard/header"
import { StrategyCards, type Strategy } from "@/components/dashboard/strategy-cards"
import { VideoComparison } from "@/components/dashboard/video-comparison"
import { SingleVideoView } from "@/components/dashboard/single-video-view"
import { DataSummaryPanel } from "@/components/dashboard/data-summary-panel"

// Hurricane Katrina data for wind speed reduction analysis
const katrinaData = {
  event: "Hurricane Katrina",
  forecastPeriod: "10 Days",
  windSpeedReduction: {
    initial: -1.25,
    peak: -2.0,
    average: -1.5,
    peakHour: 175,
  },
}

// Cyclone experiment data (experimental variant)
const cycloneExperimentData = {
  event: "Cyclone Simulation",
  forecastPeriod: "10 Days",
  avgWindReduction: 20, // m/s average across forecast (from deltas: -35, -20, -10, ~0, 0)
}

// Drought analysis data from results_mideast.json
const droughtData = {
  region: "Middle East",
  forecastDate: "2022-01-01",
  control: {
    drought_cells: 24237,
    severity_score: 84337,
  },
  interventions: {
    electric_ionization: {
      precip_increase_pct: 15.7,
      cells_resolved: 21,
      cells_improved: 91,
      cells_worsened: 0,
      isBest: true,
    },
    glaciogenic_static: {
      precip_increase_pct: 4.1,
      cells_resolved: 7,
      cells_improved: 40,
      cells_worsened: 0,
      isBest: false,
    },
    hygroscopic_enhancement: {
      precip_increase_pct: 8.1,
      cells_resolved: 15,
      cells_improved: 56,
      cells_worsened: 0,
      isBest: false,
    },
    laser_induced_condensation: {
      precip_increase_pct: 6.7,
      cells_resolved: 12,
      cells_improved: 51,
      cells_worsened: 0,
      isBest: false,
    },
  },
}

// Mapping from strategy id to intervention key in droughtData
const strategyToInterventionKey: Record<string, string> = {
  "electric-ionization": "electric_ionization",
  "glaciogenic": "glaciogenic_static",
  "hygroscopic": "hygroscopic_enhancement",
  "laser-induced": "laser_induced_condensation",
}

const graphcastStrategies: Strategy[] = [
  {
    id: "electric-ionization",
    name: "Electric Ionization",
    description: "",
    icon: "cloud",
    isOptimal: false,
    kpis: {
      soilMoisture: "+12%",
      windPeak: "-20kts",
      confidence: "94%",
    },
    mitigationLevel: 85,
  },
  {
    id: "glaciogenic",
    name: "Glaciogenic",
    description: "",
    icon: "droplets",
    isOptimal: false,
    kpis: {
      soilMoisture: "+10%",
      windPeak: "-15kts",
      confidence: "89%",
    },
    mitigationLevel: 75,
  },
  {
    id: "hygroscopic",
    name: "Hygroscopic",
    description: "",
    icon: "wind",
    isOptimal: false,
    kpis: {
      soilMoisture: "+8%",
      windPeak: "-12kts",
      confidence: "85%",
    },
    mitigationLevel: 70,
  },
  {
    id: "laser-induced",
    name: "Laser Induced Condensation",
    description: "",
    icon: "cloud",
    isOptimal: false,
    kpis: {
      soilMoisture: "+5%",
      windPeak: "-10kts",
      confidence: "82%",
    },
    mitigationLevel: 65,
  },
]

const earth2studioStrategies: Strategy[] = [
  {
    id: "cloud-seeding",
    name: "Cloud Seeding",
    description: "",
    icon: "cloud",
    isOptimal: true,
    kpis: {
      soilMoisture: "+15%",
      windPeak: "-18kts",
      confidence: "91%",
    },
    mitigationLevel: 80,
    variants: [
      { id: "experimental", name: "Experimental" },
      { id: "katrina-control", name: "Katrina Control" },
      { id: "katrina-seeded", name: "Katrina Seeded" }
    ],
  },
]

export default function DashboardPage() {
  const [activeStrategy, setActiveStrategy] = useState("cloud-seeding")
  const [selectedModel, setSelectedModel] = useState<ModelType>("earth2studio")
  const [selectedVariant, setSelectedVariant] = useState<string | null>(null)
  const [modelRun, setModelRun] = useState(false)
  const [checkedStrategies, setCheckedStrategies] = useState<Set<string>>(new Set())

  const strategies = selectedModel === "earth2studio" ? earth2studioStrategies : graphcastStrategies
  const current = strategies.find((s) => s.id === activeStrategy) ?? strategies[0]

  const handleSelect = useCallback((id: string) => {
    setActiveStrategy(id)
  }, [])

  const handleModelChange = useCallback((model: ModelType) => {
    setSelectedModel(model)
    // Reset to first strategy of the new model
    setActiveStrategy(model === "earth2studio" ? "cloud-seeding" : "electric-ionization")
    setModelRun(false)
    setCheckedStrategies(new Set())
  }, [])

  const handleCheckChange = useCallback((id: string, checked: boolean) => {
    setCheckedStrategies(prev => {
      const next = new Set(prev)
      if (checked) {
        next.add(id)
      } else {
        next.delete(id)
      }
      return next
    })
  }, [])

  const allStrategiesChecked = checkedStrategies.size === graphcastStrategies.length
  const onlyElectricIonization = checkedStrategies.size === 1 && checkedStrategies.has("electric-ionization")
  const onlyGlaciogenic = checkedStrategies.size === 1 && checkedStrategies.has("glaciogenic")
  const onlyHygroscopic = checkedStrategies.size === 1 && checkedStrategies.has("hygroscopic")
  const onlyLaserInduced = checkedStrategies.size === 1 && checkedStrategies.has("laser-induced")

  const getGraphcastVideoSrc = () => {
    if (onlyElectricIonization) return "/videos/control_vs_electric_ionization.mp4"
    if (onlyGlaciogenic) return "/videos/control_vs_glaciogenic_static.mp4"
    if (onlyHygroscopic) return "/videos/control_vs_hygroscopic_enhancement.mp4"
    if (onlyLaserInduced) return "/videos/control_vs_laser_induced_condensation.mp4"
    if (allStrategiesChecked) return "/videos/all_interventions_comparison.mp4"
    return null
  }

  // Get the active intervention data for single strategy view
  const getActiveIntervention = () => {
    if (checkedStrategies.size !== 1) r
[truncated — 3629 more characters]
```

### climate-intervention-dashboard/app/extreme/page.tsx

```typescript
export { default } from "../page"

```

### setup.py

```python
# Copyright 2023 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module setuptools script."""

from setuptools import setup

description = (
    "GraphCast: Learning skillful medium-range global weather forecasting"
)

setup(
    name="graphcast",
    version="0.2.0.dev",
    description=description,
    long_description=description,
    author="DeepMind",
    license="Apache License, Version 2.0",
    keywords="GraphCast Weather Prediction",
    url="https://github.com/deepmind/graphcast",
    packages=["graphcast"],
    install_requires=[
        "cartopy",
        "chex",
        "colabtools",
        "dask",
        "dinosaur-dycore",
        "dm-haiku",
        "dm-tree",
        "jax",
        "jraph",
        "matplotlib",
        "numpy",
        "pandas",
        "rtree",
        "scipy",
        "trimesh",
        "typing_extensions",
        "xarray",
        "xarray_tensorstore"
    ],
    classifiers=[
        "Development Status :: 3 - Alpha",
        "Intended Audience :: Science/Research",
        "License :: OSI Approved :: Apache Software License",
        "Operating System :: POSIX :: Linux",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
        "Topic :: Scientific/Engineering :: Artificial Intelligence",
        "Topic :: Scientific/Engineering :: Atmospheric Science",
        "Topic :: Scientific/Engineering :: Physics",
    ],
)

```

### run_graphcast_10day.py

```python
#!/usr/bin/env python3
"""Run 10-day weather forecast using GraphCast_small model.

This script runs GraphCast_small for a 10-day (40 timestep) forecast.
"""

import dataclasses
import functools

from google.cloud import storage
from graphcast import autoregressive
from graphcast import casting
from graphcast import checkpoint
from graphcast import data_utils
from graphcast import graphcast
from graphcast import normalization
from graphcast import rollout
import haiku as hk
import jax
import numpy as np
import xarray


# GraphCast_small checkpoint
PARAMS_FILE = (
    "GraphCast_small - ERA5 1979-2015 - resolution 1.0 - "
    "pressure levels 13 - mesh 2to5 - precipitation input and output.npz"
)

# 40-step dataset for 10-day forecast
DATASET_FILE = "source-era5_date-2022-01-01_res-1.0_levels-13_steps-40.nc"

GCS_BUCKET = "dm_graphcast"
DIR_PREFIX = "graphcast/"


def load_checkpoint(gcs_bucket):
    print(f"Loading checkpoint: {PARAMS_FILE}")
    with gcs_bucket.blob(f"{DIR_PREFIX}params/{PARAMS_FILE}").open("rb") as f:
        ckpt = checkpoint.load(f, graphcast.CheckPoint)
    print(f"Model description: {ckpt.description}")
    return ckpt


def load_dataset(gcs_bucket):
    print(f"Loading dataset: {DATASET_FILE}")
    with gcs_bucket.blob(f"{DIR_PREFIX}dataset/{DATASET_FILE}").open("rb") as f:
        example_batch = xarray.load_dataset(f, decode_timedelta=True).compute()
    print(f"Dataset dimensions: {dict(example_batch.sizes)}")
    return example_batch


def load_normalization_stats(gcs_bucket):
    print("Loading normalization statistics...")
    with gcs_bucket.blob(f"{DIR_PREFIX}stats/diffs_stddev_by_level.nc").open("rb") as f:
        diffs_stddev_by_level = xarray.load_dataset(f).compute()
    with gcs_bucket.blob(f"{DIR_PREFIX}stats/mean_by_level.nc").open("rb") as f:
        mean_by_level = xarray.load_dataset(f).compute()
    with gcs_bucket.blob(f"{DIR_PREFIX}stats/stddev_by_level.nc").open("rb") as f:
        stddev_by_level = xarray.load_dataset(f).compute()
    return diffs_stddev_by_level, mean_by_level, stddev_by_level


def construct_wrapped_graphcast(
    model_config, task_config,
    diffs_stddev_by_level, mean_by_level, stddev_by_level,
):
    predictor = graphcast.GraphCast(model_config, task_config)
    predictor = casting.Bfloat16Cast(predictor)
    predictor = normalization.InputsAndResiduals(
        predictor,
        diffs_stddev_by_level=diffs_stddev_by_level,
        mean_by_level=mean_by_level,
        stddev_by_level=stddev_by_level,
    )
    predictor = autoregressive.Predictor(predictor, gradient_checkpointing=True)
    return predictor


def main():
    print("=" * 60)
    print("GraphCast 10-Day Weather Forecast")
    print("=" * 60)
    print()

    # Connect to GCS
    print("Connecting to Google Cloud Storage...")
    gcs_client = storage.Client.create_anonymous_client()
    gcs_bucket = gcs_client.get_bucket(GCS_BUCKET)

    # Load checkpoint
    ckpt = load_checkpoint(gcs_bucket)
    params = ckpt.params
    state = {}
    model_config = ckpt.model_config
    task_config = ckpt.task_config

    print(f"Model config: resolution={model_config.resolution}, "
          f"mesh_size={model_config.mesh_size}")
    print()

    # Load 40-step dataset
    example_batch = load_dataset(gcs_bucket)

    # Load normalization stats
    diffs_stddev_by_level, mean_by_level, stddev_by_level = load_normalization_stats(gcs_bucket)

    # Extract inputs/targets/forcings for all prediction steps
    # 40 total steps - 2 input steps = 38 prediction steps (9.5 days)
    eval_steps = example_batch.sizes["time"] - 2
    print(f"\nPreparing {eval_steps}-step prediction ({eval_steps * 6} hours = {eval_steps * 6 / 24:.1f} days)...")

    eval_inputs, eval_targets, eval_forcings = data_utils.extract_inputs_targets_forcings(
        example_batch,
        target_lead_times=slice("6h", f"{eval_steps * 6}h"),
        **dataclasses.asdict(task_config),
    )

    print(f"Inputs:   {dict(eval_inputs.sizes)}")
    print(f"Targets:  {dict(eval_targets.sizes)}")
    print(f"Forcings: {dict(eval_forcings.sizes)}")

    # Build predictor
    @hk.transform_with_state
    def run_forward(model_config, task_config, inputs, targets_template, forcings):
        predictor = construct_wrapped_graphcast(
            model_config, task_config,
            diffs_stddev_by_level, mean_by_level, stddev_by_level,
        )
        return predictor(inputs, targets_template=targets_template, forcings=forcings)

    def with_configs(fn):
        return functools.partial(fn, model_config=model_config, task_config=task_config)

    def with_params(fn):
        return functools.partial(fn, params=params, state=state)

    def drop_state(fn):
        return lambda **kw: fn(**kw)[0]

    run_forward_jitted = drop_state(
        with_params(jax.jit(with_configs(run_forward.apply)))
    )

    # Run prediction
    print("\n" + "=" * 60)
    print("Running 10-day autoregressive prediction...")
    print("(This will take a while - JIT compilation + 38 steps)")
    print("=" * 60)

    predictions = rollout.chunked_prediction(
        run_forward_jitted,
        rng=jax.random.PRNGKey(0),
        inputs=eval_inputs,
        targets_template=eval_targets * np.nan,
        forcings=eval_forcings,
    )

    print(f"\nPredictions: {dict(predictions.sizes)}")
    print(f"Variables: {list(predictions.data_vars.keys())}")

    # Save
    output_file = "predictions_10day_real.nc"
    print(f"\nSaving to {output_file}...")
    predictions.to_netcdf(output_file)
    print(f"Saved!")

    # Summary stats
    print("\n" + "=" * 60)
    print("Prediction Summary")
    print("=" * 60)
    for var in ["2m_temperature", "total_precipitation_6hr", "mean_sea_level_pressure"]:
        if var in predictions.data_vars:
            data = predictions[var].values
            print(f"{var}: min={np.nanmin(data):.2f}, max={np.nanmax(data):.2f}, mean={np.nanmean(data):.2f}")

    print("\nDone! Now run: python drought_detector
[truncated — 72 more characters]
```

### run_graphcast_small.py

```python
#!/usr/bin/env python3
"""Run weather forecast simulation using GraphCast_small model.

This script downloads the GraphCast_small checkpoint (1° resolution, 13 pressure
levels), loads matching example data and normalization statistics, runs inference
using rollout.chunked_prediction, and saves predictions to a NetCDF file.

Usage:
    python run_graphcast_small.py
"""

import dataclasses
import functools

from google.cloud import storage
from graphcast import autoregressive
from graphcast import casting
from graphcast import checkpoint
from graphcast import data_utils
from graphcast import graphcast
from graphcast import normalization
from graphcast import rollout
import haiku as hk
import jax
import numpy as np
import xarray


# GraphCast_small checkpoint filename
PARAMS_FILE = (
    "GraphCast_small - ERA5 1979-2015 - resolution 1.0 - "
    "pressure levels 13 - mesh 2to5 - precipitation input and output.npz"
)

# Dataset matching 1.0° resolution and 13 pressure levels
DATASET_FILE = "source-era5_date-2022-01-01_res-1.0_levels-13_steps-04.nc"

# GCS bucket and prefix
GCS_BUCKET = "dm_graphcast"
DIR_PREFIX = "graphcast/"


def load_checkpoint(gcs_bucket):
    """Load the GraphCast_small model checkpoint from GCS."""
    print(f"Loading checkpoint: {PARAMS_FILE}")
    with gcs_bucket.blob(f"{DIR_PREFIX}params/{PARAMS_FILE}").open("rb") as f:
        ckpt = checkpoint.load(f, graphcast.CheckPoint)
    print(f"Model description: {ckpt.description}")
    return ckpt


def load_dataset(gcs_bucket):
    """Load the example dataset from GCS."""
    print(f"Loading dataset: {DATASET_FILE}")
    with gcs_bucket.blob(f"{DIR_PREFIX}dataset/{DATASET_FILE}").open("rb") as f:
        example_batch = xarray.load_dataset(f, decode_timedelta=True).compute()
    print(f"Dataset dimensions: {dict(example_batch.sizes)}")
    return example_batch


def load_normalization_stats(gcs_bucket):
    """Load normalization statistics from GCS."""
    print("Loading normalization statistics...")
    with gcs_bucket.blob(f"{DIR_PREFIX}stats/diffs_stddev_by_level.nc").open("rb") as f:
        diffs_stddev_by_level = xarray.load_dataset(f).compute()
    with gcs_bucket.blob(f"{DIR_PREFIX}stats/mean_by_level.nc").open("rb") as f:
        mean_by_level = xarray.load_dataset(f).compute()
    with gcs_bucket.blob(f"{DIR_PREFIX}stats/stddev_by_level.nc").open("rb") as f:
        stddev_by_level = xarray.load_dataset(f).compute()
    return diffs_stddev_by_level, mean_by_level, stddev_by_level


def construct_wrapped_graphcast(
    model_config: graphcast.ModelConfig,
    task_config: graphcast.TaskConfig,
    diffs_stddev_by_level: xarray.Dataset,
    mean_by_level: xarray.Dataset,
    stddev_by_level: xarray.Dataset,
):
    """Constructs and wraps the GraphCast Predictor."""
    # Core one-step predictor
    predictor = graphcast.GraphCast(model_config, task_config)

    # Cast to/from BFloat16 for efficiency
    predictor = casting.Bfloat16Cast(predictor)

    # Apply normalization to inputs and predict residuals for targets
    predictor = normalization.InputsAndResiduals(
        predictor,
        diffs_stddev_by_level=diffs_stddev_by_level,
        mean_by_level=mean_by_level,
        stddev_by_level=stddev_by_level,
    )

    # Wrap to enable autoregressive multi-step prediction
    predictor = autoregressive.Predictor(predictor, gradient_checkpointing=True)
    return predictor


def main():
    print("=" * 60)
    print("GraphCast_small Weather Forecast Simulation")
    print("=" * 60)
    print()

    # Initialize GCS client (anonymous access)
    print("Connecting to Google Cloud Storage...")
    gcs_client = storage.Client.create_anonymous_client()
    gcs_bucket = gcs_client.get_bucket(GCS_BUCKET)

    # Load checkpoint
    ckpt = load_checkpoint(gcs_bucket)
    params = ckpt.params
    state = {}
    model_config = ckpt.model_config
    task_config = ckpt.task_config

    print(f"Model config: resolution={model_config.resolution}, "
          f"mesh_size={model_config.mesh_size}, "
          f"latent_size={model_config.latent_size}")
    print()

    # Load example dataset
    example_batch = load_dataset(gcs_bucket)

    # Load normalization statistics
    diffs_stddev_by_level, mean_by_level, stddev_by_level = load_normalization_stats(gcs_bucket)

    # Extract inputs, targets, and forcings
    # Use all available time steps minus the 2 input steps
    eval_steps = example_batch.sizes["time"] - 2
    print(f"\nExtracting inputs/targets/forcings for {eval_steps} prediction steps...")

    eval_inputs, eval_targets, eval_forcings = data_utils.extract_inputs_targets_forcings(
        example_batch,
        target_lead_times=slice("6h", f"{eval_steps * 6}h"),
        **dataclasses.asdict(task_config),
    )

    print(f"Eval Inputs:   {dict(eval_inputs.sizes)}")
    print(f"Eval Targets:  {dict(eval_targets.sizes)}")
    print(f"Eval Forcings: {dict(eval_forcings.sizes)}")

    # Build the wrapped predictor with haiku transform
    @hk.transform_with_state
    def run_forward(model_config, task_config, inputs, targets_template, forcings):
        predictor = construct_wrapped_graphcast(
            model_config,
            task_config,
            diffs_stddev_by_level,
            mean_by_level,
            stddev_by_level,
        )
        return predictor(inputs, targets_template=targets_template, forcings=forcings)

    # Create jitted function with configs baked in
    def with_configs(fn):
        return functools.partial(fn, model_config=model_config, task_config=task_config)

    def with_params(fn):
        return functools.partial(fn, params=params, state=state)

    def drop_state(fn):
        return lambda **kw: fn(**kw)[0]

    run_forward_jitted = drop_state(
        with_params(jax.jit(with_configs(run_forward.apply)))
    )

    # Verify resolution matches
    data_resolution = 360.0 / eval_inputs.sizes["lon"]
    assert model_config.resolution in (0, data_resolution), (
        f"Model resolu
[truncated — 1437 more characters]
```

### graphcast_weather_map.py

```python
"""GraphCast Animated Weather Map.

Downloads GraphCast_small and 5-day example data, runs a 20-step (120h)
autoregressive forecast, then renders an animated weather map saved as .mov.
"""

import dataclasses
import os

from google.cloud import storage
import haiku as hk
import jax
import matplotlib as mpl
mpl.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.colors as mcolors
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import numpy as np
import xarray as xr

from graphcast import (
    autoregressive,
    casting,
    checkpoint,
    data_utils,
    graphcast,
    normalization,
)

# ---------------------------------------------------------------------------
# GCS config
# ---------------------------------------------------------------------------
GCS_BUCKET = "dm_graphcast"
PARAMS_FILE = (
    "params/GraphCast_small - ERA5 1979-2015 - resolution 1.0 "
    "- pressure levels 13 - mesh 2to5 - precipitation input and output.npz"
)
DATASET_FILE = (
    "dataset/source-era5_date-2022-01-01_res-1.0_levels-13_steps-20.nc"
)
STATS_FILES = {
    "diffs_stddev_by_level": "stats/diffs_stddev_by_level.nc",
    "mean_by_level": "stats/mean_by_level.nc",
    "stddev_by_level": "stats/stddev_by_level.nc",
}
CACHE_DIR = os.path.expanduser("~/graphcast_data")
OUTPUT_FILE = os.path.expanduser("~/graphcast_forecast.mov")


# ---------------------------------------------------------------------------
# Download helpers
# ---------------------------------------------------------------------------
def _download(blob_name: str, dest_path: str) -> None:
    if os.path.exists(dest_path):
        print(f"  [cached] {os.path.basename(dest_path)}")
        return
    os.makedirs(os.path.dirname(dest_path), exist_ok=True)
    print(f"  Downloading {blob_name} ...")
    client = storage.Client.create_anonymous_client()
    bucket = client.bucket(GCS_BUCKET)
    bucket.blob(blob_name).download_to_filename(dest_path)
    print(f"  Done ({os.path.getsize(dest_path) / 1e6:.1f} MB)")


def download_all():
    print("Downloading data ...")
    params_path = os.path.join(CACHE_DIR, "params.npz")
    _download(PARAMS_FILE, params_path)

    dataset_path = os.path.join(CACHE_DIR, "example_data_20steps.nc")
    _download(DATASET_FILE, dataset_path)

    stats_paths = {}
    for name, blob in STATS_FILES.items():
        p = os.path.join(CACHE_DIR, f"{name}.nc")
        _download(blob, p)
        stats_paths[name] = p
    return params_path, dataset_path, stats_paths


# ---------------------------------------------------------------------------
# Model inference
# ---------------------------------------------------------------------------
def run_graphcast(params_path, dataset_path, stats_paths):
    """Run 20-step GraphCast prediction and return predictions + targets."""
    # Load checkpoint
    print("Loading checkpoint ...")
    with open(params_path, "rb") as f:
        ckpt = checkpoint.load(f, graphcast.CheckPoint)
    model_config = ckpt.model_config
    task_config = ckpt.task_config
    params = ckpt.params
    state = {}

    # Load stats
    stats = {k: xr.load_dataset(v).compute() for k, v in stats_paths.items()}

    # Load example data
    print("Loading example dataset (20-step) ...")
    example_ds = xr.load_dataset(dataset_path).compute()
    if "batch" not in example_ds.dims:
        example_ds = example_ds.expand_dims("batch")

    # Extract inputs / targets / forcings for all 20 steps
    n_steps = 20
    print(f"Extracting inputs/targets/forcings for {n_steps} steps ...")
    eval_inputs, eval_targets, eval_forcings = data_utils.extract_inputs_targets_forcings(
        example_ds,
        target_lead_times=slice("6h", f"{6 * n_steps}h"),
        **dataclasses.asdict(task_config),
    )

    # Build haiku model
    diffs_stddev = stats["diffs_stddev_by_level"]
    mean = stats["mean_by_level"]
    stddev = stats["stddev_by_level"]

    @hk.transform_with_state
    def run_forward(inputs, targets_template, forcings):
        predictor = graphcast.GraphCast(model_config, task_config)
        predictor = casting.Bfloat16Cast(predictor)
        predictor = normalization.InputsAndResiduals(
            predictor,
            diffs_stddev_by_level=diffs_stddev,
            mean_by_level=mean,
            stddev_by_level=stddev,
        )
        predictor = autoregressive.Predictor(predictor, gradient_checkpointing=True)
        return predictor(inputs, targets_template=targets_template, forcings=forcings)

    run_forward_jitted = jax.jit(run_forward.apply)
    rng = jax.random.PRNGKey(0)

    print("JIT-compiling + running 20-step forecast (may take several minutes on CPU) ...")
    predictions, _ = run_forward_jitted(
        params, state, rng,
        eval_inputs,
        eval_targets * np.nan,
        eval_forcings,
    )
    jax.block_until_ready(predictions)
    print("Prediction complete!")

    # Get datetime for labels
    base_datetime = example_ds.coords["datetime"].values[0, 0]  # batch=0, first time
    return predictions, eval_targets, base_datetime, task_config


# ---------------------------------------------------------------------------
# Visualization
# ---------------------------------------------------------------------------

def _kelvin_to_f(k):
    return (k - 273.15) * 9 / 5 + 32


def _pa_to_hpa(pa):
    return pa / 100.0


def _wind_speed(u, v):
    return np.sqrt(u**2 + v**2)


def create_animation(predictions, targets, base_datetime, output_path):
    """Create a broadcast-style animated weather map."""
    n_steps = predictions.sizes["time"]
    lats = predictions.coords["lat"].values
    lons = predictions.coords["lon"].values

    # Pre-extract all data (squeeze batch dim)
    t2m_pred = predictions["2m_temperature"].values[:, 0]  # (time, lat, lon)
    mslp_pred = predictions["mean_sea_level_pressure"].values[:, 0]
    u10_pred = predictions["10m_u_component_of_wind"].values[:, 0]
    v10_pred = prediction
[truncated — 8904 more characters]
```

[116 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]