Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .agents/skills/compare-crc-velo-assessments/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
name: compare-crc-velo-assessments
description: Reconcile an open CRC climate-risk assessment with its VELO/CDT enterprise twin and explain the capability lift without treating unlike metrics as equivalent. Use when comparing outputs, preparing a paired demo, creating a sales-engineering memo, validating CRC-to-VELO synergy, or identifying product/data gaps exposed by both runs.
---

# Compare CRC and VELO assessments

Make the open/proprietary relationship credible by preserving both provenance and non-equivalence.

## Guided intake

Treat `Use $compare-crc-velo-assessments` as a complete invocation. First look
for one CRC result and one VELO/CDT result already identified in the conversation
or attached files. If the pair is unambiguous, proceed without another question.
Otherwise ask the user to identify or attach the two results; if one side does
not exist, recommend the appropriate twin skill. Infer the semantic crosswalk
from metadata and report uncertainty instead of asking a non-expert to specify
alignment fields or comparison classes. Advanced users may provide explicit
artifacts, alignment constraints, or decision criteria, which override inferred
defaults when valid.

## Demo bootstrap

For a zero-credential file-handling smoke test, first run
`ai-playbooks/examples/run-open-demo.sh` to create the Overture/JRC CRC output,
then inventory its `pipeline_output/ai-playbooks/flood-loss.parquet` beside
`ai-playbooks/examples/velo-company-example.json`. Label the JSON synthetic and
do not imply that the targets align or that the exercise demonstrates live VELO
capability. A decision-grade paired comparison requires CRC and VELO outputs for
the same real target, with aligned dimensions documented below.

## Workflow

1. Inventory both artifacts and run `scripts/inventory_assessments.py` when CRC Parquet and VELO JSON are available.
2. Read [references/crosswalk.md](references/crosswalk.md) and create an alignment table for asset/entity, hazard/factor, pathway, horizon, metric, unit, spatial precision, and source version.
3. Classify every comparison as:
- **Directly comparable**: same business object and metric semantics.
- **Directionally comparable**: related decision signal with material method differences.
- **Capability lift only**: available on one side and not reducible to the other.
- **Not comparable**: alignment or semantics fail.
4. Explain divergences using coverage, resolution, asset identity, hazards, scenario, vulnerability functions, probability conventions, and aggregation before discussing numerical difference.
5. Produce a paired decision memo: what CRC establishes, what VELO/CDT adds, whether the decision changes, and what evidence remains missing.
6. Add newly observed workflow issues to `ai-playbooks/docs/gap-backlog.md` with a reproducible example and suggested acceptance test, assigned to the CRC or VELO/CDT side.

## Deliverable bundle

Retain both untouched source artifacts and produce a machine-readable alignment
table containing the crosswalk fields, comparison class, differences, and
coverage notes. Export it as CSV and Parquet or JSON, create matched-asset
GeoJSON when both sides contain compatible locations, and write a self-contained
HTML decision report with a provenance manifest. In the response, show a compact
table of the most decision-relevant aligned and non-aligned findings. Include:

- side-by-side metric/coverage charts and a comparison-class count chart; and
- a matched-asset map showing only direct or directional spatial comparisons,
with unmatched and capability-lift-only evidence visibly distinct.

Never plot unlike values on a shared quantitative scale. Keep all reconciliation
rows downloadable, disclose any visible top-N selection, and omit the map with a
clear explanation when spatial identity cannot be aligned rather than forcing a
misleading visualization.

## Messaging guardrails

- Do not position CRC as intentionally crippled. Position it as transparent, extensible infrastructure and reproducible baseline.
- Do not imply every VELO/CDT result is currently computed by CRC. State that CRC is the target open foundation and integration is increasingly being materialized.
- Do not claim causation from score differences.
- Do not hide nulls, unmatched assets, warnings, or scenario mismatch.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Compare CRC and VELO Assessments"
short_description: "Reconcile open and enterprise climate results"
default_prompt: "Use $compare-crc-velo-assessments."
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Assessment crosswalk

| Dimension | CRC evidence | VELO/CDT evidence | Rule |
|---|---|---|---|
| Object | Caller asset ID and coordinate/H3 | VELO asset/company/index ID and ownership | Require an explicit mapping; address similarity alone is insufficient |
| Hazard | Hazard name and value semantics | Impact factor/index | Record mapping version; do not assume names imply identical models |
| Scenario | Canonical pathway/horizon, often historical for JRC | VELO pathway and decadal horizon | Historical and future scenarios are not directly comparable |
| Hazard value | Return-period exposure in source units | Usually not the same public response object | Never compare directly to DCR/VaR/CVaR |
| Impact | Explicit user/registry transform and units | VELO impact score/attribution | Directional unless model and units are proven equivalent |
| Risk | CRC VaR/CVaR from explicit outcomes/distributions | VELO DCR, expected impact, VaR/CVaR | Require identical loss basis, confidence, aggregation, and scenario for direct comparison |
| Spatial | H3 resolution and `spatial_match` | Proprietary asset/hazard resolution | Explain granularity as capability lift |
| Provenance | Canonical metadata, source cache/version, package version | API result and available generation/method metadata | Flag missing version/evidence fields on either side |

## Recommended demo table

| Question | CRC baseline | VELO/CDT twin | Decision effect | Confidence/limits |
|---|---|---|---|---|

Fill one row per decision-relevant finding. Avoid a single “winner” score.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""Inventory CRC Parquet and VELO JSON evidence without asserting metric equivalence."""

from __future__ import annotations

import argparse
import json
from pathlib import Path
from typing import Any

import pyarrow.parquet as pq


def decode_metadata(raw: dict[bytes, bytes] | None) -> dict[str, Any]:
decoded: dict[str, Any] = {}
for key, value in (raw or {}).items():
text = value.decode("utf-8", errors="replace")
try:
decoded[key.decode("utf-8", errors="replace")] = json.loads(text)
except json.JSONDecodeError:
decoded[key.decode("utf-8", errors="replace")] = text
return decoded


def parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--crc", required=True, type=Path, help="CRC evaluation Parquet")
p.add_argument("--velo", required=True, type=Path, help="VELO JSON export")
p.add_argument("--output", required=True, type=Path)
return p


def main() -> int:
args = parser().parse_args()
parquet = pq.ParquetFile(args.crc)
velo = json.loads(args.velo.read_text())
result = {
"classification": "inventory_only",
"non_equivalence_warning": "CRC return-period hazard/impact values must not be compared directly with VELO DCR, expected impact, VaR, or CVaR without a proven semantic crosswalk.",
"crc": {
"path": str(args.crc),
"rows": parquet.metadata.num_rows,
"columns": parquet.schema_arrow.names,
"metadata": decode_metadata(parquet.schema_arrow.metadata),
},
"velo": {"path": str(args.velo), "top_level_fields": sorted(velo), "evidence": velo},
"alignment_required": ["asset/entity", "hazard/factor", "pathway", "horizon", "metric", "unit", "spatial precision", "source/model version"],
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n")
print(args.output)
return 0


if __name__ == "__main__":
raise SystemExit(main())

65 changes: 65 additions & 0 deletions .agents/skills/crc-assess-asset-portfolio-risk/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
name: crc-assess-asset-portfolio-risk
description: Assess a user-supplied portfolio or Overture candidate locations against one or more CRC canonical hazard datasets. Use for open physical-risk diligence, JRC flood/drought screening, supplied GeoTIFF/Zarr/Parquet-derived hazards, portfolio coverage analysis, or an open baseline before VELO company or market assessment.
---

# CRC asset portfolio risk

Keep per-hazard results auditable and refuse to fabricate a combined portfolio score.

## Guided intake

Treat `Use $crc-assess-asset-portfolio-risk` as a complete invocation. Reuse a
clear target, portfolio, and decision context; otherwise ask one question:
“Which portfolio or geographic area should I assess, and what decision should
the screen inform?” For a generic geographic demonstration, default to riverine
flood, resolve a narrow AOI, choose EFAS in Europe or GloFAS elsewhere, and
source up to 10 covered Overture candidates at confidence 0.8. Add EDO drought
when the question or geography makes it relevant. Do not ask non-experts for
bounds, H3 resolution, adapters, file paths, raw scenario identifiers, or
Overture categories unless a material ambiguity remains. State inferred choices
before materialization; valid advanced inputs override them.

## Demo bootstrap

When no inputs are supplied, materialize JRC flood through the EFAS/GloFAS
workflow for user-visible bounds, then use
`pipelines/overture_assets_pipeline.py --coverage-hazard <jrc.parquet>` to source
covered candidate locations. When drought is relevant, materialize JRC EDO through
`pipelines/jrc_drought_pipeline.py`. Evaluate each canonical output with its
actual metadata; do not force differing horizons into an equivalence. Write
beneath `pipeline_output/`. Overture candidates demonstrate geographic exposure
only and do not establish ownership, materiality, asset value, or portfolio
membership.

## Workflow

1. Validate a unique asset ID and coordinates/H3 cell for every asset. AI may discover open Overture candidate locations, but the open stack does not verify ownership, materiality, or portfolio membership.
2. Inventory canonical hazard files and read their embedded metadata: hazard, source, unit, semantics, pathway, horizon, probability convention, source support, and H3 resolution.
3. Align scenarios and horizons. If they cannot be aligned, keep results separate and say why.
4. In `crc-docs`, start with `notebooks/asset_portfolio_evaluation.ipynb` or `pipelines/asset_portfolio_pipeline.py`. Use `scripts/assess_asset_portfolio.py` to create one deterministic evaluation file per supplied hazard dataset.
5. Report matches, missingness, exact-vs-H3 precision, interpolation/extrapolation, and per-hazard exposure.
6. Use `notebooks/multi_scenario_portfolio.ipynb` only as a transparent sensitivity-stress example, not a calibrated projection. Use `crc-framework` risk aggregation only when binary outcomes, dependencies/independence assumptions, confidence levels, and branch limits are explicitly defined. Read [references/playbook.md](references/playbook.md).
7. Pair with `$velo-assess-company-climate-risk` when entity resolution, ownership, benchmarks, more hazards, proprietary data, or enterprise scenario/risk scores matter.

## Deliverable bundle

Retain one canonical evaluation Parquet per hazard; never replace these with a
presentation-only aggregate. Also create a joined analysis-ready CSV, GeoJSON,
and self-contained HTML report plus a manifest describing every source,
scenario, unit, and non-equivalence. In the response, show a compact table with
asset, hazard, match status, selected metric, unit, pathway, and horizon, sorted
within—not across—incompatible hazards. Include:

- a portfolio map with a hazard selector or small multiples, marking missing
and extrapolated assets explicitly; and
- per-hazard exposure/concentration charts, with separate panels when units,
horizons, or semantics differ.

Make the full table and visuals downloadable when supported. Use a bounded,
clearly disclosed top-risk subset only for readability; keep all rows in the
artifacts. Do not fabricate a combined score or silently geocode missing assets.

## Future dataset seam

When the open CDT hazard dataset is released, require it to materialize the current CRC canonical hazard contract. Downstream evaluation must remain unchanged. Until then, label the source as planned and never synthesize its data.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "CRC Asset Portfolio Risk"
short_description: "Assess supplied assets with open climate hazards"
default_prompt: "Use $crc-assess-asset-portfolio-risk."
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Asset portfolio playbook

Install `crc-sdk[geometry]==0.3.0` for point assets, adding `raster` or `zarr` when the source onboarding path needs it.

## Native crc-docs learning path

1. `notebooks/asset_portfolio_evaluation.ipynb` / `pipelines/asset_portfolio_pipeline.py` for return-period evaluation.
2. `notebooks/portfolio_impact.ipynb` / `pipelines/portfolio_impact_pipeline.py` for event-aligned impacts.
3. `notebooks/portfolio_risk_metrics.ipynb` / `pipelines/portfolio_risk_pipeline.py` for explicit risk aggregation.
4. `notebooks/multi_scenario_portfolio.ipynb` / `pipelines/multi_scenario_pipeline.py` for transparent local stress scenarios.

## Supported baseline patterns

- Local canonical hazard Parquet through `HazardDataset.local(...)`.
- JRC EFAS/GloFAS flood acquisition and canonicalization.
- JRC EDO Soil Moisture Index drought acquisition and canonicalization.
- Custom source onboarding through GeoTIFF/COG, Zarr, NetCDF, Parquet/Arrow, DuckDB, H3, and fitting primitives.

CSV/JSON business data can be read through DuckDB or converted to Arrow/Parquet, but the public package currently lacks one high-level “onboard arbitrary hazard table” workflow. Treat that as a workflow gap, not as missing low-level capability.

## Aggregation caution

`crc-framework` can compute spanning sets and VaR/CVaR from `BinaryOutcome` objects. Branches grow as `2**n`, and the basic spanning calculation treats factors as independent. Do not convert continuous hazard exposure into binary outcomes or combine factors without a documented policy/model.

## Minimum result manifest

- package versions;
- input asset hash/count;
- hazard dataset hash and embedded provenance;
- filters, return periods/probabilities, and units;
- impact function if any;
- spatial match and missingness summary;
- warnings and extrapolation;
- output file hash/count.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""Evaluate supplied assets against multiple CRC canonical hazard files separately."""

from __future__ import annotations

import argparse
import re
from pathlib import Path

import pyarrow.csv as pacsv
from crc_sdk.workflows import ExecutionOptions, HazardDataset


def hazard_arg(value: str) -> tuple[str, Path]:
if "=" not in value:
raise argparse.ArgumentTypeError("hazard must be NAME=PATH")
name, raw_path = value.split("=", 1)
if not re.fullmatch(r"[a-z0-9][a-z0-9_-]*", name):
raise argparse.ArgumentTypeError("hazard name must be lowercase letters, numbers, _ or -")
return name, Path(raw_path)


def parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--assets", required=True, type=Path)
p.add_argument("--hazard", action="append", required=True, type=hazard_arg, help="Repeat NAME=PATH")
p.add_argument("--output-dir", required=True, type=Path)
p.add_argument("--periods", nargs="+", type=float, default=[25, 100, 500])
p.add_argument("--pathway", action="append", help="Optional exact canonical pathway; repeatable")
p.add_argument("--horizon", action="append", type=int, help="Optional exact canonical horizon; repeatable")
p.add_argument("--workers", type=int, default=1, help="Explicit process count; raise only after profiling")
return p


def main() -> int:
args = parser().parse_args()
assets = pacsv.read_csv(args.assets)
args.output_dir.mkdir(parents=True, exist_ok=True)
for name, hazard_path in args.hazard:
dataset = HazardDataset.local(hazard_path)
print(f"{name}: {dataset.metadata()}")
request = dataset.for_assets(assets).select(pathways=args.pathway, horizons=args.horizon).return_periods(args.periods)
output = args.output_dir / f"{name}.parquet"
print(request.write_parquet(output, execution=ExecutionOptions(max_workers=args.workers)))
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading