Metadata-Version: 2.4
Name: plateau-triage
Version: 0.1.3
Summary: Post-disaster building damage record layer: per-building damage state (danger / caution / inspected / unsurveyed) + confidence + provenance, bound to persistent PLATEAU building IDs. Honest by construction: covered=false ⇒ value=null.
Author-email: "Yodo Labs (PixelX Inc. / ピクセルエックス株式会社)" <pan@yodolabs.jp>
License: Apache-2.0
Project-URL: Homepage, https://yodolabs.jp
Project-URL: Repository, https://github.com/pixelx-jp/plateau_triage
Project-URL: Issues, https://github.com/pixelx-jp/plateau_triage/issues
Keywords: plateau,disaster,physical-ai,record-layer,honesty,damage-assessment,geospatial
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: GIS
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSE-DATA
Requires-Dist: pydantic<3,>=2
Requires-Dist: jsonschema>=4
Requires-Dist: shapely>=2
Requires-Dist: duckdb>=1
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Provides-Extra: shacl
Requires-Dist: pyshacl>=0.26; extra == "shacl"
Requires-Dist: rdflib>=7; extra == "shacl"
Dynamic: license-file

<p align="center">
  <a href="https://yodolabs.jp"><img src="assets/yodo-logo.svg" alt="Yodo Labs" height="64"></a>
</p>

<h1 align="center">plateau-triage 🚩</h1>

<p align="center"><b>English</b> · <a href="README.ja.md">日本語</a></p>

<p align="center">
  <b>Post-disaster building damage record layer</b>
</p>

<p align="center">
  Turn disaster imagery into a per-building, ID-bound record of damage state —
  with confidence, provenance, and, above all, an explicit <b>"not yet surveyed"</b>.
</p>

<p align="center">
  <code>pip install plateau-triage</code> ·
  <a href="docs/">docs</a> ·
  <a href="DESIGN.md">DESIGN</a> ·
  Code Apache-2.0 · Data CC BY 4.0
</p>

<p align="center">
  <a href="https://pypi.org/project/plateau-triage/"><img src="https://img.shields.io/pypi/v/plateau-triage" alt="PyPI"></a>
  <a href="https://github.com/pixelx-jp/plateau_triage/actions/workflows/ci.yml"><img src="https://github.com/pixelx-jp/plateau_triage/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
  <img src="https://img.shields.io/badge/python-3.9%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue" alt="Python 3.9–3.13">
  <img src="https://img.shields.io/badge/code-Apache--2.0-green" alt="Code: Apache-2.0">
  <img src="https://img.shields.io/badge/data-CC%20BY%204.0-green" alt="Data: CC BY 4.0">
  <img src="https://img.shields.io/badge/honesty-covered%3Dfalse%20%E2%87%92%20value%3Dnull-9b9b9b" alt="Honesty invariant">
</p>

> After the quake, the most important thing to know is not which buildings are
> broken — it's which ones you haven't checked yet.

<p align="center">
  <img src="assets/demo.png" alt="plateau-triage Noto demo — red/yellow/green extruded buildings with grey unsurveyed slabs, a coverage meter, and a legend" width="820">
  <br>
  <sub>Noto demo: danger / caution / inspected extruded by severity, unsurveyed buildings as the visible grey gaps, with the coverage meter. <code>make demo &amp;&amp; make viz-dev</code></sub>
</p>

Every building in a stricken city is one **honest record**, in one of four states.
The English names are below; the value actually stored in each record is the Japanese
label from Japan's emergency risk-assessment scheme (応急危険度判定), shown in the
`record` column:

| | state | record |
|---|---|---|
| 🔴 | **danger** | `covered=true, value="危険"` |
| 🟡 | **caution** | `covered=true, value="要注意"` |
| 🟢 | **inspected** (safe) | `covered=true, value="調査済"` |
| ⬜ | **not yet surveyed** (unknown) | `covered=false, value=null` |

The first three mirror the emergency risk-assessment red/yellow/green tags. The fourth —
**not yet surveyed** — is the literal form of the stack-wide honesty invariant
`covered=false ⇒ value=null`: a building nobody has looked at is **never** defaulted to
"safe". That honesty contract — plus binding to a persistent building ID — is the moat,
**not** the damage-classification model.

---

## Quickstart

```bash
pip install plateau-triage          # from PyPI (pydantic, jsonschema, shapely, duckdb)
plateau-triage demo --out out/      # runs on the bundled Copernicus Noto sample
# point at your own data:  plateau-triage demo --input your_footprints.geojson --out out/
# from a clone, for dev:    pip install -e ".[dev,shacl]"   # + ruff, pyshacl/rdflib
```

Program output (the breakdown prints each stored value with its English gloss):

```
plateau-triage — Noto demo
  ingested 12 footprints from the bundled Copernicus Noto sample
  coverage: 8 / 12 assessed; 33% unknown
  state breakdown:
    🔴 危険    danger          3
    🟡 要注意   caution         3
    🟢 調査済   inspected       2
    ⬜ 未調査   unsurveyed      4
  outputs:
    geojson      out/triage.geojson      # red/yellow/green/grey choropleth (plateau-r3f / any viewer)
    coverage     out/coverage.json       # the coverage meter
    duckdb       out/records.duckdb       # the record layer (DuckDB; also writes .parquet on request)
    support_csv  out/support_system.csv  # victim-support-system screening feed
    risai_csv    out/risai_shomei.csv    # disaster-certificate pre-fill candidate (official assessment still required)
```

This is the **Noto go/no-go demo**: zero fieldwork, zero PLATEAU/network dependency.
It uses public Copernicus-shaped damage footprints near Wajima, which is *not* in
PLATEAU — so records bind `dataset_footprint` IDs and render in 2.5D. Binding to a
PLATEAU building ID + 3D is the production path, on covered cities.

> The bundled fixture is a 12-footprint synthetic sample mirroring the Copernicus
> EMS Noto 2024 schema. Point `--input` at the real FeatureCollection to run it for real.

### Visualisation (plateau-r3f)

The red/yellow/green/grey 2.5D choropleth + coverage meter + click-to-trace, with a
bilingual (Japanese / English) UI.

```bash
make demo && make viz-data      # writes the feed into viz/public/demo.geojson
make viz-install                # cd viz && npm install
make viz-dev                    # http://localhost:5173   (or: make viz-build)
```

<p align="center">
  <img src="assets/demo-provenance.png" alt="Click-to-trace provenance panel showing a building's state, confidence, dataset, observed-at and ID-binding confidence" width="820">
  <br>
  <sub>Click-to-trace: state, confidence, dataset, observed-at, ID-binding confidence. An unsurveyed building is shown as "not assumed safe".</sub>
</p>

### Field capture (drone · street)

```bash
plateau-triage capture --out out/   # runs on the bundled drone capture sample
```

A weak or unseen building **abstains to "not yet surveyed"**, never defaulted to safe
(graceful degradation); VSRD/SfM/SAM are pluggable backends.

### Feed existing systems

```bash
plateau-triage export --records out/records.duckdb --out out/feed.csv --format both
```

Emits both a feed for the victim-support system (被災者支援システム) and a *pre-fill
candidate* for the disaster-victim certificate (罹災証明) — never the certificate itself,
since the official damage assessment (被害認定調査) always governs. See
[docs/integration.md](docs/integration.md).

---

## Pipeline

```
   GeoJSON footprints + damage_grade               drone / street capture session
            │  accrue.ingest_batch                          │  accrue.ingest_capture
            │  (missing grade ⇒ not surveyed)               │  (weak / unseen ⇒ not surveyed)
            ▼                                                ▼
        honest Records  ──  accrue.state_extract  (the honesty / abstention policy)
            │  bind  (resolve() → persistent id + geom_binding_conf)
            ▼
   ─ record layer (store: GeoParquet / DuckDB) ─────────────────────────────────────
      • query      RecordIndex: by id / bbox / triage queue / explicit unknown   (caveat-mcp mounts this)
      • coverage   "8 / 12 assessed; 33% unknown"
      • viz        red/yellow/green/grey GeoJSON → plateau-r3f
      • export     victim-support-system feed + disaster-certificate pre-fill
      • eval       self-proving: calibration + symmetric honesty penalty
```

| module | role |
|---|---|
| `plateau_triage.record` | the one honest record contract (states + model + frozen-schema validator) |
| `plateau_triage.accrue.ingest_batch` | batch import of public damage datasets → records |
| `plateau_triage.accrue.ingest_capture` | drone/street capture session → records (unseen ⇒ not surveyed) |
| `plateau_triage.accrue.state_extract` | per-building damage extraction + the honesty/abstention policy |
| `plateau_triage.bind` | pin records to a persistent building id (`Resolver` plug; `StubResolver` for the demo) |
| `plateau_triage.coverage` | the coverage meter + triage ordering |
| `plateau_triage.store` | record-layer persistence (flat GeoParquet / DuckDB) |
| `plateau_triage.query` | honest query surface (by id / bbox / triage queue / explicit unknown) — caveat-mcp mounts it |
| `plateau_triage.viz` | red/yellow/green/grey GeoJSON feed for plateau-r3f |
| `plateau_triage.export` | victim-support-system + disaster-certificate screening feeds |
| `plateau_triage.eval` | self-proving honesty eval (ECE + symmetric penalty) |
| `viz/` | the plateau-r3f front-end (Vite + React Three Fiber, bilingual) |

## Honesty, enforced everywhere

The invariant is guarded independently so drift is a test failure, not a silent
regression — see [docs/honesty-spec.md](docs/honesty-spec.md):

1. **Write side** — `Record` (pydantic) makes it structurally impossible to build
   a `covered=false` record with a value or a confidence.
2. **Wire side (JSON)** — `validate_record()` checks the serialised record against
   the **frozen** `contracts/record.schema.json` shared by all seven repos.
   **Wire side (RDF)** — the optional `[shacl]` extra validates the same invariant
   natively in **SHACL** over a SOSA/PROV-O projection ([docs/standards.md](docs/standards.md)).
3. **Extraction** — field perception abstains to "not surveyed" below its confidence threshold.
4. **Ingest** — an unseen / poorly-localised AOI building is explicitly not surveyed; a
   later "safe" reading can never bury an earlier danger.
5. **Export** — refuses to emit a known/safe claim for an unsurveyed building (`FalseSafeError`).
6. **Query** — answers "unknown, not safe" for a building it has no record of.

## Contract

`record.schema.json` is the frozen, shared "universal honest record" plug (state,
rules and hazard are the same shape — only `attribute` differs). It is vendored at
`plateau_triage/contracts/record.schema.json`; binding to a persistent id is delegated
to `plateau-id-resolve` via the `Resolver` interface. The same record also projects
onto **SOSA + PROV-O** (`record.context.jsonld`) and is checkable in **SHACL**
(`record.shacl.ttl`) — standards alignment, not a new standard
([docs/standards.md](docs/standards.md)).

## Test

```bash
python -m pytest             # 118 tests (incl. SHACL when the extra is installed)
pip install -e ".[shacl]"    # optional: RDF/SHACL honesty check (rdflib + pyshacl)
cd viz && npm run build      # the plateau-r3f front-end builds clean
```

## Docs

- [docs/honesty-spec.md](docs/honesty-spec.md) — the invariant and where it's enforced
- [docs/standards.md](docs/standards.md) — SOSA / PROV-O / nilReason alignment + SHACL
- [docs/data-sources.md](docs/data-sources.md) — data sources, PLATEAU coverage, licensing
- [docs/integration.md](docs/integration.md) — feeding existing systems, agents and viewers

## License

Code **Apache-2.0** ([`LICENSE`](LICENSE)); data **CC BY 4.0**
([`LICENSE-DATA`](LICENSE-DATA), incl. PLATEAU / Copernicus attribution).

A *screening* layer that feeds existing systems — **not** a replacement for the legal
official damage assessment (被害認定調査) a human signs.

---

<p align="center">
  <a href="https://yodolabs.jp"><img src="assets/yodo-logo.svg" alt="Yodo Labs" height="40"></a><br>
  <b>Yodo Labs</b> · PixelX Inc. / ピクセルエックス株式会社<br>
  <a href="https://yodolabs.jp">yodolabs.jp</a> · <a href="mailto:pan@yodolabs.jp">pan@yodolabs.jp</a>
</p>
