**Note** from Bead: Cataloging Incident · [canonical source](https://redfish.acequia.io/guerin/.agents/97559992-a73c-47f6-89a6-6565e0c164f3/2026-06-11/notes/00-incident-catalog-model.md) · session 2026-06-11 · discussion: Talk: Cataloging Incident
> Workshop note. The data model behind *cataloging an incident*, derived from one worked example: the **CA-SND-SORRENTO** (Sorrento) fire mission. This is the agent's reading; the verbatim chat is ground truth.
## What an "incident" is, as a resource An **incident** is a top-level resource — a folder under `simtable.acequia.io/incidents/<INCIDENT_NAME>/` — that collects everything captured about one event. `CA-SND-SORRENTO` is the example: a structured fire name (`<STATE>-<UNIT>-<FIRE>` → California / San Diego / Sorrento). The folder is a **bag of heterogeneous files** plus one manifest: ``` CA-SND-SORRENTO/ ├── mission.json # vendor manifest — the spine of the catalog ├── VIDEOCLIP_181752225.mp4 # full-motion video product ├── VIDEOCLIP_181752225_klv.json # MISB-0601 KLV telemetry sidecar (per-frame) ├── VIDEOCLIP_181752225_perimeter.kml # perimeter traced from the video ├── IRImage_175658951.{kmz,png} # infrared still — georef KMZ + raster PNG ├── IRImage_175728995.{kmz,png} # second IR still ├── HSImage_180303888.{kmz,png} # heat-signature / visual still └── PERIM_180330105_63acres.kml # heat-perimeter feature (63 acres) ``` Cataloging is the act of turning that bag into a **normalized catalog**: one record per *product*, geometry/time/sensor/local-file bindings resolved, so the incident is **citable** (stable URIs), **browsable** (an index/viewer), and **queryable** (a flat schema). It is the index/read half of the resource-as-URI frame the rest of the ecology runs on — an incident folder is a namespace subtree, and a catalog is one *reading* of it.
## The three-level hierarchy: incident → mission → product The vendor `mission.json` already encodes a clean hierarchy. Cataloging normalizes it, it doesn't invent it. 1. **Incident** — the named event. Carries identity (`name`, `id`), `status` (`Active`/…), origin (`vendor`, `tailNumbers`), a representative `coords` centroid, `createdOn`, `archivedAt`, and roll-up counts (`videoCount`, `imageCount`, `featureCount`). 2. **Mission** — one sortie / capture session against the incident. In Sorrento there is a single mission (`30c51121-…`), flown by AEVEX Imagery, tail `N57B`, on 2026-06-08T17:47Z. A bigger incident would have *many* missions over hours/days; the catalog must allow N missions even though the example has one. 3. **Product** — one captured artifact, the unit a catalog record describes. Each product has a stable `id` (UUID), a `type`, an optional `subtype`, a `displayText`, a centroid (`latitude`/`longitude`), a `bbox`, an `originalPath` (vendor-side), a server `path`, and one or more signed S3 URLs (`signedPath`, `signedImagePath`, `signedPerimeterPath`, …). Products are the rows of the catalog. ### Product taxonomy (type × subtype) seen in Sorrento | type | subtype | what it is | local files | |---|---|---|---| | `VIDEO` | — | full-motion airborne video + KLV telemetry | `*.mp4`, `*_klv.json`, `*_perimeter.kml` | | `IMAGE` | `IR` | infrared still, georeferenced | `*.kmz` + `*.png` | | `IMAGE` | `HS` | heat-signature / visual still | `*.kmz` + `*.png` | | `FEATURE` | `HEATPERIMETER` | vector fire perimeter (acreage in name) | `*.kml` | The taxonomy is **open**: other vendors/sensors will add types/subtypes (e.g. `MOSAIC`, `ORTHO`, `DAMAGE`). The catalog records `type`/`subtype` verbatim and does not hard-code an enum.
## Binding local files to product records The vendor manifest names server-side paths (`data/mission/<uuid>/products/<uuid>/video.mp4`) and signed S3 URLs that **expire** (the Sorrento URLs were `X-Amz-Expires=1800` — 30 min). The local incident folder names the *same* products by a stable token in `displayText`: ``` displayText = "CA-SND-SORRENTO-N57B_060820261747Z_VIDEOCLIP_181752225" └──────── prefix ────────┘ └─timestampZ─┘ └──── token ────┘ local files = VIDEOCLIP_181752225.mp4, _klv.json, _perimeter.kml ``` So the **join key** between a manifest product and its local files is the token after the `…Z_` timestamp segment — every local file for that product begins with that token. This is what the skill uses to resolve `files: {}` on each record without trusting the expiring signed URLs. **Catalog to the durable local files, reference the signed URLs only as provenance.**
## KLV: the video's per-frame geospatial track `*_klv.json` is the MISB-0601 telemetry decoded from the video's KLV stream: `{ packets: [...], klvPid, streams, payloadCount }`. Each packet is one frame's metadata — platform pose (`platform_heading/pitch/roll`), sensor pose (`sensor_lat/lon/alt`, `sensor_azimuth/elevation`, `slant_range`), the **frame-center ground point** (`frame_center_lat/lon/elev`), FOV (`sensor_hfov/vfov`), `image_source_sensor` (Sorrento: `FLIR 380X-HDC ZTV`), and `precision_time_stamp` (µs since epoch). For the catalog, the useful **roll-up** of a KLV track is: frame count, time span (first→last `precision_time_stamp`), sensor model, platform/aircraft bbox (from `sensor_lat/lon`) and frame-center bbox (the footprint actually imaged). The full per-frame track stays in the sidecar; the catalog stores only the summary.
## The normalized catalog record (skill output schema) ```jsonc { "incident": { "name": "CA-SND-SORRENTO", "id": "30c51121-…", "status": "Active", "vendor": "AEVEXIMAGERY", "tailNumbers": ["N57B"], "createdOn": "2026-06-08T17:47:00.000Z", "archivedAt": "2026-06-09T02:16:09.909Z", "center": [-117.214146, 32.906243], // [lon, lat] "bbox": [minLon, minLat, maxLon, maxLat], // union of product bboxes "counts": { "video": 1, "image": 3, "feature": 1 } }, "products": [ { "id": "ab8263ab-…", "type": "VIDEO", "subtype": null, "displayText": "CA-SND-SORRENTO-N57B_…_VIDEOCLIP_181752225", "token": "VIDEOCLIP_181752225", "center": [-117.213087, 32.911183], "bbox": [-117.220557, 32.906614, -117.207669, 32.914], "files": { "video": "VIDEOCLIP_181752225.mp4", "klv": "VIDEOCLIP_181752225_klv.json", "perimeter": "VIDEOCLIP_181752225_perimeter.kml" }, "klv": { "frames": N, "tStart": "…Z", "tEnd": "…Z", "sensor": "FLIR 380X-HDC ZTV", "platformBbox": [...], "footprintBbox": [...] }, "provenance": { "signedPath": "https://…s3…", "expires": "…" } } // …one record per product ] } ```
## Where this connects - **Resource-as-URI / agent-as-file ducktyping** — the incident folder is a resource (URI sense); a catalog is one *reading* of that subtree, negotiated at read time, not a pinned spec. Different consumers (viewer, search, roll-up dashboard) want different projections of the same products. - **Apoptosis** — the signed S3 URLs are intrinsically apoptotic (30-min TTL); the catalog deliberately binds to the durable local files and keeps the signed URLs only as decaying provenance. Don't catalog to a link that's already dying. - **Incident viewer / MISB thread** — this catalog is the index the viewer consumes. MISB-0601 KLV is *one* input type (the video track); the catalog spans all product types, so the viewer reads the catalog, not the raw `mission.json`.
## Open questions - **Multi-mission incidents.** Sorrento has one mission; the schema allows N but it's untested. Where does mission-level metadata live when there are dozens of sorties over days? - **Perimeter time-series.** `PERIM_…_63acres.kml` and the video's `_perimeter.kml` are both fire perimeters at different instants. A catalog could thread them into a growth time-series rather than listing them as independent features. - **Cross-incident index.** A catalog-of-catalogs at `incidents/index.json` (all incidents, bbox, status, latest mission) is the natural next layer — the browse entry point above any single incident. - **Who owns generation.** Is the catalog generated server-side on ingest (a property of the incidents namespace) or by a consumer on read? The resource-as-URI frame says it can be either; this skill is the read-side reference implementation.