Get a free API key

This page as Markdown, byte for byte: /analyse.md

Analyse an object: the pinned record, at your own exposure#

POST /objects/{id}/analyse runs named analyses over a canonical risk object and records what they found on that object's provenance. It is a job: the call returns 202 with a job id, you poll GET /jobs/{id}, and the finished job carries the artifacts.

Two analyses today:

analyses name What it does
point_hazard Looks the pinned record up at each of the object's own coordinates — the same document [GET /hazard](hazard.md) serves, once per location.
event_intersection Selects the catalogue events that fall in the box the exposure spans — the same document [GET /events/{peril}](events.md) serves, filtered by the same selector a backtest filters with.

Nothing here is new arithmetic. Both analyses are the routes above aimed at an object instead of at coordinates you typed, which is the point: the numbers on an artifact and the numbers you get by calling those routes yourself are the same numbers, read from the same pinned bytes.

Read the quickstart first for the two values you export (CEDE_BASE_URL, CEDE_API_KEY) and how to get a key from POST /signup.

What it is for#

A price is a number computed from a record on a particular day. Six months later, the question a reader has is not "what is the price" but "what did the data say when this was priced" — and by then the catalogue has a new vintage and the answer is unrecoverable unless somebody wrote it down. This verb writes it down, on the object, in provenance.analysis_runs:

Provenance is append-only. A second run adds a second entry; nothing already recorded is rewritten, including a run that said something different about the same coordinates.

What the object needs#

The object must carry Because
peril.code Every analysis here is a reading of one peril. PATCH /objects/{id} writes it.
exposure.locations[] with latitude and longitude Every analysis here is a reading at a place. Ingest never geocodes an address, so a schedule of addresses reaches this route with nowhere to look.
at most 25 located locations A run embeds a full hazard document per location. Above the cap the run is refused, never trimmed: an artifact that quietly described part of an exposure would be read as describing all of it.

Refusals are structured and name what would have worked: unknown_analysis (422) lists every analysis that can be asked for, unanalysable_object (422) names the block to write and the route that writes it. Both are in the error reference.


Walk it#

The block below stores a two-location Kanto schedule, runs both analyses over it, reads the artifacts out of the finished job, reads the provenance back off the object, and then asks for an analysis that does not exist so you can see the refusal.

set -euo pipefail

: "${CEDE_BASE_URL:?export CEDE_BASE_URL first — see the quickstart}"
: "${CEDE_API_KEY:?export CEDE_API_KEY first — see the quickstart}"

# 1 — An object to analyse: two located sites, one peril.
cat > schedule.json <<'JSON'
{
  "schema_version": "0.1.0",
  "status": "draft",
  "exposure": {
    "kind": "location_schedule",
    "currency": "JPY",
    "locations": [
      {
        "ref": "LOC-0001",
        "name": "Shinagawa distribution centre",
        "latitude": 35.6284,
        "longitude": 139.7387,
        "geocode": {"resolution": "rooftop", "confidence": 0.96},
        "values": {"building": {"amount": 8400000000, "currency": "JPY"}}
      },
      {
        "ref": "LOC-0002",
        "name": "Chiba assembly plant",
        "latitude": 35.5687,
        "longitude": 140.1247,
        "geocode": {"resolution": "street", "confidence": 0.83},
        "values": {"building": {"amount": 5100000000, "currency": "JPY"}}
      }
    ],
    "source_fidelity": {
      "unmapped_columns": [],
      "guessed_units": [],
      "ambiguous_rows": []
    }
  },
  "peril": {"code": "earthquake"}
}
JSON

curl -sS "$CEDE_BASE_URL/objects" \
  -H "Authorization: Bearer $CEDE_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @schedule.json > object.json

OBJECT_ID="$(python3 -c 'import json; print(json.load(open("object.json"))["id"])')"
echo "object: $OBJECT_ID"

# 2 — Run both analyses. A job comes back.
curl -sS "$CEDE_BASE_URL/objects/$OBJECT_ID/analyse" \
  -H "Authorization: Bearer $CEDE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"analyses": ["point_hazard", "event_intersection"]}' > accepted.json

JOB_ID="$(python3 -c 'import json; print(json.load(open("accepted.json"))["id"])')"
echo "job:    $JOB_ID"

# 3 — Poll it to a terminal state.
for _ in $(seq 1 60); do
  curl -sS "$CEDE_BASE_URL/jobs/$JOB_ID" \
    -H "Authorization: Bearer $CEDE_API_KEY" > job.json
  STATUS="$(python3 -c 'import json; print(json.load(open("job.json"))["status"])')"
  [ "$STATUS" = "queued" ] || [ "$STATUS" = "running" ] || break
  sleep 1
done
echo "status: $STATUS"
test "$STATUS" = "succeeded"

# 4 — The artifacts, each with its assumptions and the vintages behind it.
python3 -c 'import json
run = json.load(open("job.json"))["result"]["analysis"]
print("run:      ", run["run_id"], "at", run["completed_at"])
print("analyses: ", ", ".join(run["analyses"]))
for artifact in run["artifacts"]:
    print()
    print("==", artifact["analysis"])
    print("  ", artifact["summary"])
    for vintage in artifact["data_vintages"]:
        print("   read:", vintage["id"], vintage["version"],
              "vintage", vintage["vintage"], vintage["sha256"][:12] + "…")
    if artifact["analysis"] == "point_hazard":
        for reading in artifact["locations"]:
            hazard = reading["hazard"]
            print("   %-22s %s,%s -> %s over %s years"
                  % (reading.get("ref", reading["location_index"]),
                     reading["point"]["latitude"], reading["point"]["longitude"],
                     hazard["metrics"]["basis"],
                     hazard["assumptions"]["period_of_record"]["years"]))
        for missed in artifact["not_analysed"]:
            print("   not read:", missed["location_index"], missed["reason"])
    else:
        box = artifact["box"]
        print("   box:  %s-%sN %s-%sE (exposure extent + %s deg)"
              % (box["min_latitude"], box["max_latitude"],
                 box["min_longitude"], box["max_longitude"],
                 box["cell_half_width_degrees"]))
        print("   events:", artifact["counts"]["intersecting"], "of",
              artifact["counts"]["events_in_snapshot"], "in the snapshot")
        for event in artifact["catalogue"]["events"][:5]:
            print("     ", event["time"], "M" + str(event["magnitude"]),
                  event["event_id"])
print()
print(run["disclosure"])'

# 5 — The record on the object itself. This is what a reader finds months later.
curl -sS "$CEDE_BASE_URL/objects/$OBJECT_ID" \
  -H "Authorization: Bearer $CEDE_API_KEY" > analysed.json

python3 -c 'import json
document = json.load(open("analysed.json"))
print("status:", document["status"])
for entry in document["provenance"]["analysis_runs"]:
    print("run", entry["run_id"], "->", entry["links"]["job"])
    for artifact in entry["artifacts"]:
        print("  ", artifact["analysis"] + ":", artifact["summary"])
        print("     method:", artifact["assumptions"]["method"][:90] + "…")
        for vintage in artifact["data_vintages"]:
            print("     vintage:", vintage["id"], vintage["version"],
                  vintage["sha256"][:12] + "…")'

# 6 — An analysis that does not exist. The refusal names the whole valid set,
#     and this block demands the status rather than just printing it: if the
#     route ever answered something else, this example would stop exiting 0.
status=$(curl -sS -o refused.json -w '%{http_code}' \
  "$CEDE_BASE_URL/objects/$OBJECT_ID/analyse" \
  -H "Authorization: Bearer $CEDE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"analyses": ["ground_motion"]}')

python3 -c 'import json
error = json.load(open("refused.json"))["error"]
print(error["status"], error["code"])
print(error["message"])'

test "$status" = 422

Reading the artifacts#

**point_hazard** holds one entry per location that could be read, each carrying the full hazard document for that coordinate — its metrics, its method, its period of record and the snapshot it came from. Locations that carried coordinates no pinned snapshot covers are listed under not_analysed with the edge they fell outside, rather than dropped: an exposure half of which was read must never read as an exposure that was read.

**event_intersection** holds the box (the exposure's own extent, grown by the half-degree cell a point lookup counts in), the counts, and a page of the catalogue events inside it. The box is not a structure's trigger box: a cat-in-a-box trigger carries its own region on trigger.index, and a backtest of that structure selects against that region instead.

What this is, and is not#

The published schema is analysis.v0.schema.json, listed in the API contract document with every other response shape; the run travels in the job at result.analysis. Validate against it while holding no Craton code.

Where to go next#