The graph of the storm, and its ontology
Kept by incident.graph@web-summit.dinis, 11 November 2026, 07:57 UTC
One graph of Storm Charles: who decided and warned what, when, what was reported, where it was read, and which agent keeps which dataset. Kept by
incident.graph@web-summit.dinis. Built from the incident's JSON files by tools/graph-build.mjs, never by hand. Not a warning service: the graph records what the authorities decided and warned and when we read it; follow IPMA and ANEPC.
Files
| File | What |
|---|---|
| ontology.json | the eight kinds of thing and nine verbs below, each with its inverse; the builder refuses anything else |
| storm.json | the graph: nodes[] and edges[] (one per line), counts, built_from (each input file with its sha256). the actors, the warnings for Lisbon, places and agents; more names the parts below, which a reader joins into one graph |
| storm-decisions.json | part of the graph, same shape: every Decision, with the decides, precedes and affects edges that touch it; its edges point into storm.json (its own part since 11 November, 07:58 UTC) |
| storm-signals.json | part of the graph, same shape: every Signal (a row of the timeline) with the edges that touch it |
| storm-sources.json | part of the graph, same shape: every Source and Dataset, with the cites and produced-by edges that touch them; its edges point into storm.json |
| storm-elsewhere.json | the rest of the graph, same shape: the warnings in force in the other districts, and those districts; its edges point into storm.json |
| tables/timeline.json | the lead-time line: every warning and decision with a time known to the minute, in order, with the hours between them and before the red warnings start |
| tables/decisions.json | decisions and issued warnings: by whom, what, when, status, what each followed |
| tables/warnings.json | warnings: issued, in force (type, level, area, start, end), and the history of when each was first seen |
| tables/signals.json | the timeline, one row per signal, with its publisher |
| tables/sources.json | every URL read, its publisher, how often it is cited |
| tables/first_on_fact.json | who was first on each keyed fact, per outlet, with the minutes after the primary source (from the media watch; analysis, labelled hypothesis) |
| tables/coverage.json | what each outlet published on the storm and when (from the media watch; no by-line names) |
| tables/actors.json | bodies, operators, the organiser, outlets |
| ref/ipma-areas.json | IPMA's warning areas by code (names and coordinates), from IPMA's own list |
| queries.json | the worked SQL queries of the query page |
The ontology on one page
Every node and every edge carries four things: label (fact, evidence or hypothesis), source (an https URL), read (the UTC time it was read) and origin (the file and place it was built from).
| Kind of thing | What it is | Built from |
|---|---|---|
| Actor | someone who decides, warns or reports; kind is body (an authority, the government, a municipality), operator (transport, a venue, schools), organiser (Web Summit) or outlet (press and public social platforms) | decisions/graph.json nodes; outlets from the hosts of cited URLs |
| Decision | something an actor decided, or has not yet announced (status) | decisions/graph.json edges |
| Warning | a warning from a body that warns: issued, in_force, or cap (IPMA's own message, with the time IPMA sent it) | IPMA's and ANEPC's edges; weather/warnings.json; weather/cap.json |
| Signal | a row of the timeline: something published, said or observed | signals.json |
| Source | a URL that was read | decisions/sources.json; any other cited URL; the pages covering the storm (research/weather-sites/sites.json) |
| Place | where a warning applies: an IPMA area, mainland Portugal | weather/warnings.json; ref/ipma-areas.json |
| Dataset | a JSON file of the incident | the files under the incident's data/ |
| Agent | an agent or desk that reads sources and keeps datasets | data/keys.json; read_by in the datasets |
| Verb | Its inverse | From, to | Meaning |
|---|---|---|---|
decides | decided-by | Actor, Decision | the actor made (or owes) this decision |
warns | warned-by | Actor, Warning | the body issued this warning |
reports | reported-by | Actor, Signal or Decision or Warning | the actor published it |
cites | cited-by | Decision or Warning or Signal, Source | the item was read at this URL (or is corroborated by it) |
precedes | follows | Decision or Warning, Decision or Warning | time order between two linked items, with lead_minutes when both times are known to the minute. Order, not cause |
affects | affected-by | Decision or Warning, Actor | the actors a decision touches (the researcher's to) |
located-in | contains | Warning or Place, Place | a warning's area |
produced-by | produces | Dataset or Source, Agent or Actor | who made it |
issued-as | issue-of | Warning, Warning | the same warning, opened: a warning as recorded (or in force) and the body's own message that carries it (IPMA's CAP message, with the time sent) |
Labels. A fact was read on the channel of the body that decides or warns. Evidence is reported by others, with its source. A hypothesis is our analysis, with its reasons, never stated as fact. A derived item takes the label of the item it came from; an outlet, a place, a dataset or an agent is a fact about the file or URL that names it.
Not in the graph: people. No private person is a node; journalists and officials are not modelled here. Forecast values and observations stay in weather/*.json: the graph links the dataset, not each number.
Two hand-kept lists, both in the ontology: own_channels (seven hosts that are plainly an actor's own, two that are never outlets) and public_posts (below). Everything else is derived.
No private person is named or linked. A personal post address on a social platform never enters the graph or a table: the builder keeps the host only, and stops with nothing written if one would reach an output. The exceptions are the researcher's listed official channels and the rows named in public_posts, each with its reason and who decided (today one: a public figure in a public role).
Where it joins the other graphs (crossings.json)
The site has other graphs: the register of sources (sources/), the weather data supply chain (meteorology/), the entities (entities/) and our dated captures (evidence/, Wayback). This graph joins them and copies none of them: each keeps its own kinds, verbs and ids, and none of their nodes is a node here. crossings.json is built with the graph and holds:
edges: each from a node of this graph to<world>/<that world's own id>, withrel(a verb with an inverse, fromontology.json,crossings),href(that world's own page),label,source,read,originandwhy.registered-as(an actor's entry in the register of sources),in-lineage-as(the same thing in the supply chain),has-site(a weather site the actor publishes),publishes-as(its dataset or warning service there),issued-through(a warning to the warning service it came through, and from there down to the models and instruments).captures: once per address this graph was read at, our latest capture of it (evidence/<id>.html) or the reason there is none. The pages put it before every outside link: an item, its source, our capture, the outside address.ids_to_settle: ids that mean one thing here and another in a neighbour's file.
The entity id is the pivot (one id per thing): a crossing is a fact where the other world itself names the entity that names this graph's node, and a hypothesis, with its reason, where the builder matched by host or derived it. Since 05:00 on 11 November meteorology/ states its joins on its own nodes (entity, of, source), so those crossings are facts.
Build
node tools/graph-build.mjs # writes storm.json and tables/*.json
node tools/graph-build.mjs --check # exits 1 if they are not what the inputs give
node tools/graph-build.mjs --out DIR # writes DIR/data/graph/... only; no tracked file is touched (the build: --out _site)
The output has no clock in it: the same inputs give the same bytes; as_of is the newest time in the inputs. In storm.json a node is short (a name of at most 140 characters); the long text is in the tables, joined by id. Each file stays under 256 KB (the builder stops if one would not).
Use it
- Query it in the browser (SQL, no server, nine worked questions): the query page.
- See the ontology drawn: kinds of thing as shapes, verbs as arrows, each with its inverse.
- Lead times: one line of time, every cell a link.
- See it: the graph page (3D, on the engine of web-summit.sgit.ai's Universe, and as a list).
- Read it as an agent: fetch
storm.json; followoriginback to the file each item came from, andsourceto where it was read.