Reference
The data model, and which screen reads what
Reports are the only thing anyone authors. Every screen is a build product. This page is the contract between the two: what a report may say, and what the build emits for the panes to read.
01 · The one rule
Reports are authored. Views are built.
A fact is declared exactly once, by the report that owns it, and every screen is computed from that layer. There is no second copy to drift, and no view file anyone edits by hand. The cost is a build step; the benefit is that the map, the graph and the dependency diagram cannot contradict each other.
reports/ directories mirror model/kb.json's topic tree
commodities/energy/fossil-fuels/
com.crude-oil.info.json physical-system payload — reportType: information
com.crude-oil.info.md/html eleven-section Physical system view
trading-strategies/energy/
com.crude-oil.trade.json market-and-trading payload — reportType: trade
com.crude-oil.trade.md/html seventeen-section Markets & trading view
commodities/metals-minerals/non-ferrous/com.copper.*
infrastructure/maritime/chokepoints/geo.hormuz.*
global/west-central-asia/geo.gulf.* … legacy triples remain valid outside split dossiers
model/
core.json node kinds, edge kinds, metrics, id prefixes
map.json kb.json alpha.json stages · the topic tree · dependency layers
commissions.json every report the corpus intends to hold, written or not
units.json glossary.json permitted units and plain-English definitions
infra/corridors.json waypoints and the route sections they form — reports declare neither
data/ written outside the build; the build validates and re-emits it
news/news.json one pipeline run and the items it published
web/ THE DEPLOYABLE — served as plain files, no build on the host
api/ BUILD OUTPUT. The static research data the browser fetches.
Ids are the join. geo:hormuz is the same string in a chain,
in an edge, in a news tag and in a report URL. Prefixes are chosen so an edge list reads without a
lookup table: com: commodity · site: mine, field, plant, storage ·
geo: country, port, chokepoint, pipeline · org: ·
inst: · ind: · evt: ·
clim: · src: · wp: waypoint ·
sec: route section.
Ids are cheap; renaming one is not, so they are never renamed — only deprecated.
02 · Four primitives
What a report may say
| Field | Type | Notes |
|---|---|---|
| subject | node id | The report is its subject node. No separate report number. |
| topic | topic id | Placement in the tree — the only placement mechanism. |
| asOf, status | date, enum | Stamped on every screen that shows the report. status is a closed claim about the report's own completeness, and the build tests the claim rather than taking it: — |
| confidence | {score,why,weakest} | Shown on the page. There is no merge bar. |
| declares[] | Node[] | Node — a thing with an identity. Permanent, so it holds no numbers. |
| declares[].{id,kind,name,topic} | string | Kind comes from model/core.json and decides the prefix and whether coordinates are required. |
| declares[].at | [lon,lat] | Required for sites, ports, chokepoints, and the linear infrastructure kinds — pipeline, rail, road, waterway — which one representative coordinate places. And for anything a flow edge ends at, so the demand end of a chain is always drawable. |
| declares[].{in,aka,note,spec,layer} | mixed | Container, aliases for tagging, one-sentence tooltip, physical descriptor, DAG column. |
| proposes[] | Node[] | Transient. Must be empty before the report merges. |
| stages[], stageOf | {id,label,sub} | Map columns. Ids are closed (source · terminal · transit · processing · demand) so colour is consistent; labels are per report, because a regas terminal is processing for LNG and a terminal for crude. |
| asserts[] | Edge[] | Edge — a typed relation. One flat list; every graph in the product is a filter over it. |
| observes[] | Measure[] | Measure — a number, detached from its node, because nodes are permanent and numbers expire. |
| evidence[] | {id,url,publisher,date,quote} | Ids are local to the report; the build namespaces them on merge. |
| watch, links, open | mixed | Feeds and keywords for the news tagger; key connections with a why; admitted gaps. |
03 · Edges
— kinds, deliberately few
| Kind | Reads as | Extra fields | Consumed by |
|---|
A flow names the route sections it runs in route,
in travel order — sec:gulf-hormuz runs the water sec:hormuz-gulf
runs the other way. The build joins them into one waypoint chain and resolves that to a polyline, so the map
draws without routing; a section that does not begin where the one before it ends is a build error naming both,
and a sea leg with no course at all is a build error rather than an invented straight line. A makes
edge carries share — the percentage of the source node's own output that goes down that
edge — and a node's outgoing shares, own-use loops included, may not sum above 100. It is what the dependency
diagram's line thickness and its per-root index are computed from. Own-use loops are declared with
loop and excluded from the acyclicity check.
04 · Measures
A closed list, on purpose
Closing the metric vocabulary is how you stop three reports inventing production, output and annual_output for the same thing.
| Metric | Kind | Notes |
|---|
Observed or derived, never both. A report authors observed numbers with evidence.
The build derives the rest: concentration shares against the world total, chokepoint throughput and coverage
summed from flows, the per-root dependency index, tradability, backlinks and topic counts. Authoring a derived
value is a build error, and where a report authors a share the corpus can also derive, the build compares the
two and refuses a disagreement.
The coverage rule. Where both a corpus sum and an authored total exist, the build emits both and the site shows the observed figure. A partial corpus sum is never presented as a world figure.
The coverage rule. Where both a corpus sum and an authored total exist, the build emits both and the site shows the observed figure. A partial corpus sum is never presented as a world figure.
05 · Build output
What the browser actually fetches
The per-node file is the scalability move: an entity page is one small fetch rather than four
global files joined in the browser, and the join is paid once per build instead of once per pageview.
Every pane reads through corpus.js, which is the only file that knows these paths.
06 · Adding a report
What it takes to reach each screen
| To appear in | Emit | Never do |
|---|---|---|
| Flow map | A declaration with at, a stage, and flow edges naming a route of sections | Edit a map file, coin a waypoint, or plot a course the catalogue already names |
| Supply-chain DAG | A layer on the declaration, makes edges in and out with shares; prices or proxies for tradability | Set a tradable flag or a DAG index by hand — both are derived |
| Knowledge base | A topic on the subject, and links with a why | Edit a graph file |
| Report page | The .md sections and the .html page with figures and measure holes | Type a number as a literal |
| Node pages | declares, observes, and a ## node: block for prose | Create a page |
| Gemini chat | A complete built report page; the explicit index command uploads it after a successful build | Put provider data in the report or index from the browser |
| News | Nothing — the pipeline does no tagging. An item reaches your report by naming its entity ids in data/news/news.json, which the build validates against the corpus | Expect watch.keywords to attract items — it is a figure on your own report page |
| Landing counts | Nothing — every figure on the landing page is a data-fig hole the build fills, and it errors on a key with no value behind it | Type a figure into index.html |
If a new report needs anything outside reports/ — beyond appending to
model/kb.json or model/glossary.json — the model has a gap. Say so
rather than working around it.