Skip to content
Substrata
Join

Every claim should be checkable

See what has a source, what is still a lead, and what is a dated judgement. These counts are computed from the same records used by the atlas and company pages.

Producer rows
How this is computedCount of (bottleneck, producer) pairs across every material in the corpus.Method →Code →
Sourced
How this is computedProducer rows counted by verification state from the corpus: sourced (an accepted primary source is linked) or unverified (none yet). Unchecked pages: open rows in the producer-sourcing queue, counted live from the database.Method →Code →
Unverified
How this is computedProducer rows counted by verification state from the corpus: sourced (an accepted primary source is linked) or unverified (none yet). Unchecked pages: open rows in the producer-sourcing queue, counted live from the database.Method →Code →
Pages found, unchecked
How this is computedProducer rows counted by verification state from the corpus: sourced (an accepted primary source is linked) or unverified (none yet). Unchecked pages: open rows in the producer-sourcing queue, counted live from the database.Method →Code →

Data quality: every dataset scored against written criteria, with each failing row and its source. Freshness: when each feed and file was last checked.

How fresh is this

The research sweep last completed a run on 2026-10-14 08:20 UTC. It has looked at How this is computedCount of rows in the bottleneck corpus: every material plus every non-material chokepoint.Method →Code → bottlenecks at least once. How this is computedLeads in the research sweep’s queue that no person has read yet and that have not expired, counted live from the database.Method →Code → leads are waiting to be read by a person; How this is computedA sweep lead is open while nobody has reviewed it and it was found less than 30 days ago; unreviewed and 30 days or older, it is expired. The review queue is open leads only: late when its oldest has waited more than 7 days, stale past 31.Method →Code → expired, never reviewed — nobody read them within 30 days, so they left the queue but were kept.

The oldest open lead was found How this is computedWaiting: open sweep leads — no reviewer decision, not expired. Oldest: whole days since the earliest open one was found. Drafts ready: waiting leads whose AI draft suggests an event and passed the checks.Method →Code → ago. An AI draft is ready for review on How this is computedWaiting: open sweep leads — no reviewer decision, not expired. Oldest: whole days since the earliest open one was found. Drafts ready: waiting leads whose AI draft suggests an event and passed the checks.Method →Code → of them: the date, the line and the sentence from the page that carries the claim, checked word for word against the page. A draft is unreviewed until a person accepts it.

A lead found by the sweep is not a published record and never appears on a page. The corpus is files in version control, and a row reaches this site when a person has read the source and committed it. That is slower than a feed, on purpose: it is the difference between something that was checked and something that was merely found.

Who spent the AI budget

The free daily AI budget is spent only by readers asking questions (Ask and fact-checks). No scheduled job uses it any more: drafting news into events runs only on a reader’s own AI key. The background column shows the days before that. On 2026-10-12 (UTC), readers used How this is computedTokens per UTC day on this site's free AI keys, summed per class: interactive (Ask and fact-checks, a reader waiting) and background. Background is zero from 2026-09-25: no scheduled job may call the free models, and test/no-free-background-ai.test.ts fails the build if one tries. Drafting runs on readers' own keys and is not on this ledger.Method →Code → tokens and background jobs How this is computedTokens per UTC day on this site's free AI keys, summed per class: interactive (Ask and fact-checks, a reader waiting) and background. Background is zero from 2026-09-25: no scheduled job may call the free models, and test/no-free-background-ai.test.ts fails the build if one tries. Drafting runs on readers' own keys and is not on this ledger.Method →Code →, against an estimated capacity of How this is computedTokens per UTC day on this site's free AI keys, summed per class: interactive (Ask and fact-checks, a reader waiting) and background. Background is zero from 2026-09-25: no scheduled job may call the free models, and test/no-free-background-ai.test.ts fails the build if one tries. Drafting runs on readers' own keys and is not on this ledger.Method →Code →.

How long Ask takes

No Ask question was answered in the last day, so there is no latency to report.

Three different kinds of evidence

A sourced producer row links to an accepted primary source. A candidate source has been found by the research engine but still needs review. An unverified lead has neither. A sourced row establishes the specific claim next to its link; it does not verify the whole company profile.

Judgements are labelled

The How this is computedSum of four judged scores, each 0–3: concentration + substitution + lead time + inelasticity.Method →Code → binding assessments sum four ordinal judgements, each from zero to three: concentration, substitution, lead time and inelasticity. A total of twelve is not a percentage or a probability. The latest assessment date in the corpus is 2026-09-15. Stage relief times are illustrative estimates chosen by hand, not measured lead-time datasets.

Dates and reproducibility

The producer-sourcing engine last completed a run at 2026-10-14 06:53 UTC. Export time tells you when a file was generated, not when its claims were verified. The export includes a SHA-256 content digest so you can identify an exact dataset and reproduce counts.

How each number is computed

Every count, share and score on this site is a function of the corpus. A dotted underline on a number means you can open it: sourced figures link their source, computed ones open the rule below, and estimates say whose they are and when.

Portfolio X-ray: rails a holding touches

Rule: For each resolved holding: the bottlenecks its companies hold (maker, capacity or part supplier), the ones a filing says they need or sell into, and everything upstream of a held or needed rail through recorded "needs" rows.

Tickers resolve only against research/listings.json; a parent listing brings in every subsidiary the directory records under it. Upstream walks start from rails a company makes or runs, never from a part it supplies, and only follow rows in config/substrata-dependencies.ts, each with its source sentence. A rail the corpus does not record is absent, which is not the same as not exposed.

Code: lib/xray/portfolio.ts, lib/xray/holding.ts, lib/xray/resolve.ts, lib/dependencies.ts, config/substrata-dependencies.ts

Portfolio X-ray: share of weight on a rail

Rule: Sum of the weights of the holdings that touch a rail, divided by the sum of the weights of all resolved holdings. With no weights given, every holding counts equally.

It says how much of the pasted portfolio rests on the rail by any recorded route — holding it, needing it, or upstream of what it needs. It is not revenue exposure: a company that touches a rail once counts as fully on it.

Code: lib/xray/portfolio.ts

Portfolio X-ray: concentration by country

Rule: For each country: the share of portfolio weight on a rail whose every recorded maker operates there, and the share on a rail with at least one recorded maker there. Part suppliers are not makers.

Makers are counted from their recorded operating jurisdictions. A bottleneck with no maker rows is placed by its own recorded location. The first column is the one that matters: a rail whose every maker sits in one country is a single-country dependency for everything downstream of it.

Code: lib/xray/portfolio.ts, lib/bottleneck-makers.ts

Scenario propagation

Rule: Direct hits: bottlenecks where the failed node is a maker, a part supplier or the location. Makers wholly inside it are lost; makers also operating elsewhere are partly affected; the rest remain. Downstream: every bottleneck that needs a hit one, transitively, through recorded rows. Exposed: holders of hit and downstream bottlenecks and companies a filing says need or sell into them.

A part supplier is never counted as a remaining maker: Zeiss and Trumpf do not make EUV scanners. "Partly affected" means the row records operations inside and outside the country, not how much of either. Downstream means an input is disrupted, not that the dependent stops; the corpus has no substitution or inventory data to say more.

Code: lib/scenario/propagate.ts, lib/scenario/exposed.ts, lib/bottleneck-makers.ts, config/substrata-dependencies.ts

Scenario: share of recorded world output in the failed country

Rule: The country’s recorded annual production divided by the recorded world total for the same year and unit, from config/substrata-quantities.ts (USGS).

Only for materials with published figures. The figure counts what the source counts (primary refined gallium, mined rare-earth oxide), which may be broader or narrower than the bottleneck; the row says what it describes.

Code: lib/scenario/recovery.ts, lib/quantities.ts, config/substrata-quantities.ts

Quality score per criterion

Rule: Rows that held over rows looked at, pooled across every check of that criterion for that dataset, rounded down to a tenth of a percent.

Each dataset has six written criteria in config/substrata-quality.ts: completeness, correctness, provenance, freshness, link health and consistency. A criterion marked not measured has a stated reason and no score — it is not counted as 100. Network checks score only the rows looked at so far; rows not yet looked at and looks that could not tell (a publisher refusing robots, a PDF that could not be read) are shown beside the score and never counted as passes.

Code: lib/quality/types.ts, lib/quality/report.ts, config/substrata-quality.ts

Dataset quality score

Rule: The mean of the criterion scores that could be measured for the dataset.

An unweighted mean, so a dataset with one weak criterion shows it; the criteria themselves are listed beside it. Measured each scheduled run (every six hours) and stored, which is the trend.

Code: lib/quality/types.ts, lib/quality/run.ts

Rows, sourced, verified, stale, broken links

Rule: Rows: judged by the dataset’s first provenance check. Sourced: share of those that pass it. Verified: rows re-derived from their source by a network look, over every row such a look covers. Stale: rows past their declared age. Broken: source links that failed three tries.

Verified counts only what the scheduled run has confirmed against the live source — a quote found on its page, a ticker found in the SEC file or at OpenFIGI, a USGS value on its PDF row. It starts low and rises as the rotation covers the dataset; it is not a judgement of the rows not yet looked at.

Code: lib/quality/report.ts, lib/quality/network-results.ts, lib/quality/targets.ts

AI spend by class

Rule: Tokens per UTC day on this site's free AI keys, summed per class: interactive (Ask and fact-checks, a reader waiting) and background. Background is zero from 2026-09-25: no scheduled job may call the free models, and test/no-free-background-ai.test.ts fails the build if one tries. Drafting runs on readers' own keys and is not on this ledger.

Capacity is the sum of each keyed free vendor's daily estimate in @bitbaum/ai-kit (set low on purpose). Tokens are the vendor's own count where it reports one and a characters÷4 estimate for streamed answers. Held = background calls refused to protect readers. The keys are shared with other apps, so a vendor can refuse before this ledger reaches capacity.

Code: lib/ai-budget.ts, test/no-free-background-ai.test.ts, lib/chat-agent/turn.ts

Ask latency (p50 / p90)

Rule: Over every Ask question in the last 24 hours that got an answer: the 50th and 90th percentile (continuous, PostgreSQL percentile_cont) of milliseconds from the request reaching the server to the first answer text streamed, and to the finished answer.

One row per question, holding only durations, the number of model calls, lookups planned before them, the serving model and how many free links refused first — never the question or who asked. Timed on the server, so the reader's network is not included. Questions the free budget refused are counted but kept out of the percentiles. Rows older than 30 days are deleted.

Code: lib/ask-timing.ts, lib/chat-agent/loop.ts, app/api/chat/route.ts

Bottlenecks mapped

Rule: Count of rows in the bottleneck corpus: every material plus every non-material chokepoint.

Materials and chokepoints (machines, processes, queues) are kept in two files because they are verified differently, and joined into one list. A row is on the list because it was written into the corpus, not because a search found it.

Code: lib/bottlenecks.ts, lib/bottlenecks-build.ts, config/substrata.ts, config/substrata-coverage.ts

Binding now

Rule: Count of bottlenecks whose assessed horizon is "now".

The horizon is part of the judged assessment (now, within two years, beyond), not a measurement. It is dated and carries a one-line rationale on every bottleneck page.

Code: lib/bottlenecks.ts, lib/bottlenecks-build.ts, config/substrata-assessment.ts

Severity score (0–12)

Rule: Sum of four judged scores, each 0–3: concentration + substitution + lead time + inelasticity.

Concentration: how few suppliers qualify. Substitution: how hard it is to replace. Lead time: decision to new capacity. Inelasticity: whether the buyer can walk away. Each is an ordinal judgement written into the corpus with a date and a rationale; the total is not a percentage or a probability, and 8 is not "twice as bad" as 4.

Code: config/substrata-assessment.ts

Producer rows

Rule: Count of (bottleneck, producer) pairs across every material in the corpus.

A company that makes two bottlenecked materials is two rows. This counts claims of the form "X produces Y", not companies.

Code: lib/atlas.ts, config/substrata-coverage.ts

Sourced and unverified rows, and pages found but unchecked

Rule: Producer rows counted by verification state from the corpus: sourced (an accepted primary source is linked) or unverified (none yet). Unchecked pages: open rows in the producer-sourcing queue, counted live from the database.

A sourced row establishes the specific claim next to its link (that this company makes this thing). It does not verify the rest of the company profile. A page the sourcing engine found is a lead until a person reads it; it never changes a row’s state.

Code: config/substrata-evidence.ts, lib/atlas.ts, lib/source-store.ts

Organisations

Rule: Count of rows in the market-participant directory.

Every company, lab or agency named anywhere in the corpus. Being listed is not an endorsement and, unless the row says so, not a verified fact about the organisation.

Code: lib/participants.ts, lib/participants-build.ts, config/substrata-participants.ts

Bottlenecks an organisation holds

Rule: Count of bottlenecks on which this organisation is recorded as a producer, a holder of the capacity, or a supplier of a critical part.

Material rows come from the producer map; machine, process and capacity rows from the holders listed on each chokepoint. A holder row is evidenced by the organisation’s own directory citation. "No other maker recorded" means none in this corpus, which is not the same as none in the world.

Code: lib/company-profile.ts, lib/participants-build.ts, config/substrata-coverage.ts

Events on an organisation

Rule: Accepted events that name this organisation, plus (counted separately) accepted events on a bottleneck it holds that do not name it.

Both are read and accepted by a person with a source. The second group is news about what the organisation holds, not about the organisation, and is labelled so on the page.

Code: lib/company-profile.ts, config/substrata-events.ts

Rules tracked

Rule: Count of policy instruments in the corpus; each one links its official text.

An instrument is a law, regulation, export control or programme with an official source and a quoted passage. "Slows building" and "speeds it" are the direction recorded on each row, a judgement about its effect on the bottleneck it names.

Code: config/substrata-policy.ts

Possible fixes

Rule: Count of science entries: technologies recorded as able to relieve a named bottleneck.

Each entry carries a readiness judgement with its reasoning, and cites a source where one exists. Inclusion means the technology is argued to relieve a constraint, not that it will.

Code: config/substrata-science.ts

Recent events

Rule: Accepted events whose date falls within the last N days of today.

Events are dated observations (capacity, prices, outages, filings) read and accepted by a person, each with its source. Leads found by the automated sweep are not events until accepted.

Code: config/substrata-events.ts

Leads awaiting review

Rule: Leads in the research sweep’s queue that no person has read yet and that have not expired, counted live from the database.

The automated sweep proposes leads; this counts the ones nobody has read. They are never shown as findings. The database queue is the only one; there is no committed copy.

Code: lib/sweep-store.ts

Review queue: waiting, oldest, drafts ready

Rule: Waiting: open sweep leads — no reviewer decision, not expired. Oldest: whole days since the earliest open one was found. Drafts ready: waiting leads whose AI draft suggests an event and passed the checks.

A draft passes only if its quote occurs word for word on the fetched page and it names bottlenecks and participants the site already has. A ready draft is still unreviewed: it reaches a page only after a person accepts it and the row is committed to the corpus file.

Code: lib/event-draft-store.ts, lib/event-draft.ts, lib/event-rules.ts

Filter counts

Rule: Count of bottlenecks tagged with that technology, industry or stage.

Tags come from the classification in the corpus. A bottleneck can carry several, so the counts overlap and do not sum to the total.

Code: config/substrata-taxonomy.ts, lib/bottlenecks-build.ts, lib/bottlenecks-board.ts

Countries

Rule: Number of distinct country codes on the rows being counted (makers, or rules).

A maker row lists the countries it operates in; a rule lists the jurisdiction that issued it. The count is of distinct codes, so a country with ten makers counts once.

Code: lib/bottlenecks.ts, config/substrata-policy.ts

Rules that slow or speed building

Rule: Rules counted by the effect recorded on each: "tightens" slows building, "loosens" speeds it; a rule recorded as both ways is counted in neither.

The effect is a judgement about the rule’s effect on the bottleneck it names, written next to the official text and a quoted passage. It is not a claim about the rule’s purpose.

Code: config/substrata-policy.ts

Rules with a named backer

Rule: Rules with at least one proponent: an organisation that asked for it in a document linked on the row.

A backer is only recorded when their own words can be linked. Absence means none was found, not that nobody asked.

Code: config/substrata-policy.ts

Readiness (1–9)

Rule: A judged level on a nine-step scale from "idea with a physical basis" (1) to "in production at scale" (9).

Each technology carries its level, the date it was judged, the reasoning, and a source where one exists. 1–4 is "in the lab", 5–7 "being proven", 8–9 "reaching production". It is an ordinal judgement, not a measured maturity.

Code: config/substrata-science.ts

Science counts

Rule: Technologies tracked = science entries; bottlenecks addressed = distinct bottlenecks named as relieved by any entry; reaching production = entries judged 8 or 9; sourced = entries whose readiness cites a source.

"Addressed" means an entry argues it would relieve that bottleneck, not that it has. Fronts are the technology tags the entries carry.

Code: config/substrata-science.ts, app/science/page.tsx

Capital counts

Rule: Kinds = rows in the capital-kinds list; providers = named funders, each linking its own mandate page; assessed = bottlenecks with a funding judgement; "money is not it" = those judged "not-money".

The funding judgement asks whether more money would loosen the constraint. It is dated, carries its reasoning, and is arguable like every judgement here.

Code: config/substrata-capital.ts

Calls: open, resolved, overdue, hit rate

Rule: Open = calls without a resolution; resolved = right + wrong + unclear; overdue = past the resolve-by date and not yet marked; hit rate = right ÷ (right + wrong), published only once enough calls have resolved.

Each call is dated when made and names the observation that settles it. Unclear resolutions are excluded from the hit rate rather than counted either way.

Code: config/substrata-calls.ts

Mapped and sourced per stage

Rule: Bottlenecks on that stage of the loop, and how many of them have every producer row sourced.

A row is "sourced" only when its weakest producer row is. A stage with nothing mapped is a gap in coverage, not a stage without constraints.

Code: lib/atlas.ts, config/substrata-stages.ts

Worst judgement on a loop

Rule: The highest severity score among the bottlenecks recorded as gates on the loop.

Scores are not added together: a loop is as slow as its worst gate. See the severity method for how each score is made.

Code: lib/kpi/loops.ts

Explainers and terms

Rule: Count of explainer files in content/learn, and of entries in the glossary.

Reading time is the word count at 200 words a minute, rounded, with a one-minute minimum (bip-kit readingTime).

Code: app/learn/page.tsx, lib/notes.ts, config/substrata-glossary.ts

Share of world output

Rule: A place's annual production ÷ the published world total, same unit and year.

Both inputs come from the same source row (USGS Mineral Commodity Summaries unless the row says otherwise). No share is shown when there is no world total in the same unit, or when USGS prints the total as "more than" a number — a share of a lower bound would overstate the country.

Code: lib/resources/choropleth.ts, lib/quantities.ts, research/usgs-mcs.json

Concentration index (HHI)

Rule: Sum of the squares of each producer’s share of world output (0–1).

The Herfindahl–Hirschman index competition authorities use. 1.0 is a single supplier; 0.1 is a fragmented market. Computed only over producers with a published share, so it can understate concentration when the tail is missing (USGS folds small producers into "Other countries", which is left out rather than counted as one producer).

Code: lib/resources/choropleth.ts, lib/quantities.ts

Reserves-to-production (years)

Rule: Published reserves ÷ annual production, same place, unit and year.

"At today’s rate with today’s reserves" — not a countdown. Reserves are re-estimated as prices and technology move. USGS reserves are the economically extractable part of what is known (roughly proved + probable), not the larger resource, so a short figure is not a depletion date.

Code: lib/quantities.ts

Net pressure on a bottleneck

Rule: Accepted events in the last 90 days that tighten it, minus those that loosen it. Neutral events count for neither.

A direction, not a magnitude: one plant closure and one expansion net to zero whatever their sizes. It counts only reviewed events, never unreviewed web leads, so a quiet number can mean nothing happened or that nothing has been reviewed yet.

Code: lib/exposure.ts

Exposure rows

Rule: One row per (bottleneck, holder) after the screen’s filters; the bottleneck count is the distinct bottlenecks among those rows.

Holders are the producer rows on materials and the recorded makers, capacity holders and part suppliers on chokepoints. A bottleneck with no recorded holder has no row.

Code: lib/exposure.ts, lib/exposure-query.ts

Holders with a listing

Rule: Organisations recorded on a bottleneck (maker, capacity holder or part supplier) whose shares, or a parent’s, trade on an exchange found in the SEC ticker file or OpenFIGI.

Listings come from research/listings.json, generated by scripts/research/listings.ts. A parent listing is a diluted exposure: Hitachi Energy trades only as part of Hitachi.

Code: lib/exposure.ts, lib/listings.ts, scripts/research/listings.ts

Science pipeline counts

Rule: Items per stage = papers, preprints and grants collected for the bottleneck whose relevance score is 2 or more, placed on a stage by the rule in lib/science.ts; judgements are counted separately.

Items come from OpenAlex, arXiv, NSF, OpenAIRE and USAspending (DOE), searched with the phrases in config/substrata-pipeline.ts. Relevance: 3 per phrase in the title, 1 per phrase in the abstract, 1 per context word in the title. The stage rule reads grant programmes and laboratory or pilot wording; it never places an item beyond pilot. Nobody has reviewed these rows. A count is what the feeds found, not all research on the subject.

Code: lib/science.ts, lib/science-store.ts, lib/science-read.ts, config/substrata-pipeline.ts

New in the science feed

Rule: Items first collected in the last 7 days whose publication or award date is within the last 30 days.

Both conditions, so a first search that brings in two years of papers does not read as a week of news. Dates are the source’s own.

Code: lib/science-read.ts

Institutions and companies in the science feed

Rule: Items per institution per bottleneck, counting each item once per institution named on it by the source; a company is matched to the directory only when every distinguishing word of both names agrees.

Institution names are exactly as OpenAlex, arXiv, NSF or USAspending report them. The directory match uses the strict rule in lib/listing-match.ts, against the company’s name or the listed parent it trades through, so a near-miss stays unmatched rather than getting someone else’s ticker.

Code: lib/science-pipeline.ts, lib/science-read.ts, lib/listing-match.ts

Change vs the prior point

Rule: The latest value in a series minus the value before it, divided by the earlier value, same series and unit.

Points are compared as published, never interpolated: when the prior point is a year earlier, so is the comparison, and the badge names the period it compares against. Whether a rise tightens or loosens the bottleneck is set per series (a longer lead time tightens; more capacity loosens). A move smaller than 3% either way is shown with its sign but called flat, not tightening or loosening: surveys and revisions move by that much without anything changing. A target or forecast is never called either way.

Code: lib/series.ts

Series and points

Rule: Count of dated values in a series, or of series matching the filters, across the committed corpus and the official statistics fetched on schedule.

Corpus points live in research/series.json with a source link and a quote each; official points come from the BLS public API into the database. A count covers what is held, not everything published.

Code: lib/series.ts, lib/series-store.ts, research/series.json

Open roles

Rule: Count of postings currently listed on the public job boards Substrata reads, after the page’s filters. By default software and business roles that name no bottleneck are left out; "Every role" puts them back.

Boards are read once a day from the Greenhouse, Lever and Ashby public job-board APIs, for directory companies whose careers page was checked to use one. A posting that disappears from its board is closed. Companies on Workday, SuccessFactors or their own site are linked, not counted — so this is a count of what Substrata can read, never of how many people an industry is hiring.

Code: lib/careers-store.ts, lib/careers-query.ts, research/job-boards.json

Postings per bottleneck and role family

Rule: A posting is filed under a bottleneck when its title names one of that bottleneck’s terms, or its description names them at least twice (title only for software and business roles); its role family is the first family whose title terms its title names.

The terms are published in config/careers-terms.ts and config/careers-roles.ts and matched as whole words. Nobody reads the postings: a rule can file a posting wrongly, and one posting can sit under several bottlenecks, so the per-bottleneck counts add up to more than the total. Postings naming no bottleneck (most software and business roles) are counted in the total only.

Code: lib/careers.ts, config/careers-terms.ts, config/careers-roles.ts

Skills and credentials named in postings

Rule: For each skill or credential in the published list, the number of open postings whose title or description names any of its terms.

A count of mentions, not of requirements: "PhD asked" also counts a posting that says a PhD is not needed. The list is Substrata’s, chosen for these chains; a skill outside it is never counted. The occupational skills beside it come from O*NET and ESCO, not from postings.

Code: lib/careers.ts, config/careers-terms.ts, lib/careers-query.ts

Learning paths

Rule: Count of programmes in research/learning-paths.json, each with the official page it was checked on.

A programme is listed only after its official page was opened and matched what the entry says; format and entry facts are copied from that page. The list is a start, not a census of every course in the world, and a listing is not an endorsement.

Code: research/learning-paths.json, lib/learning-paths.ts

Fresh, late, stale, failing

Rule: A feed is fresh within one scheduled interval plus half an interval (at least 20 minutes) of its last good run, late up to three intervals, stale beyond; failing when its newest failure is newer than its last good run.

A failure is a run that never finished within 30 minutes, or one in which every look failed; a run where only some looks failed still counts as good. Intervals are declared per feed in config/substrata-freshness.ts to match the server timers. A committed dataset is fresh for three quarters of its declared maximum age, late until the maximum, and stale past it — at which point the build fails.

Code: lib/freshness/status.ts, lib/freshness/read.ts, config/substrata-freshness.ts

Age of a feed or dataset

Rule: Now minus the finish time of the last good run (feeds), or whole UTC days since the date the committed file records it was checked (datasets).

A dataset’s date is the one it states about itself (checkedOn, generatedOn or generatedAt), and for files with a date per row the oldest row. It says when the contents were last checked, not when the file was last edited.

Code: lib/freshness/status.ts, config/substrata-freshness.ts

Open and expired sweep leads

Rule: A sweep lead is open while nobody has reviewed it and it was found less than 30 days ago; unreviewed and 30 days or older, it is expired. The review queue is open leads only: late when its oldest has waited more than 7 days, stale past 31.

Expiry is applied when the table is read: no job rewrites or deletes a row, an expired lead keeps its empty verdict and stays listed, and a reviewer can still decide on it. Without it the queue’s age only grew, so “stale” meant “nobody reviewed”. With it the oldest open lead cannot pass the expiry, so a stale queue means the expiry or the queue query is broken. A reader’s desk window may reach further back; an expired lead there is labelled “expired, never reviewed”, never as awaiting review.

Code: lib/lead-expiry.ts, lib/event-draft-store.ts, config/substrata-freshness.ts

World rank

Rule: Position of a country when the countries USGS lists individually are sorted by the same column, largest first; equal values share a rank.

Only countries with their own row in the USGS table are ranked. Producers USGS folds into "Other countries" are not ranked, and a withheld (W) figure cannot be ranked, so the United States is often missing from a ranking it would place in. Rank 3 means third among those listed, not third in the world with certainty.

Code: lib/resources/choropleth.ts, research/usgs-mcs.json

Change on the year before

Rule: Latest year ÷ the year before − 1, same country, same column of the same table (USGS or EIA).

USGS publishes the latest year as an estimate (marked e) and revises it the following February, so a change computed on it is provisional. No change is shown when either year is withheld, missing or zero.

Code: lib/resources/country.ts

Which resources a country is listed for first

Rule: A country's resources are ordered by its largest share of world output on any production column of each table (e.g. palladium within PGMs).

An ordering, not a score: it says where the country weighs most in world supply on the published figures. Resources with reserves but no production sort last; resources the directory names but no table lists are shown apart, never as zero.

Code: lib/resources/country.ts

Correct the record

Send the claim, a public source, its date, and the proposed correction to Substrata chat. Contributions enter review; sending a message does not turn it into a verified fact.

Use the data

Download versioned JSON · Download bottlenecks as CSV · Explore the atlas