For contributors
The data model
What the tables are, how they differ from the legacy schema, and what has not been migrated yet.
The schema is derived from the previous system's, which had roughly sixty tables in five clusters. Most of it carries over unchanged in meaning. Three things were changed deliberately, and one list is deliberately still open.
The clusters
Topology. nets, vertices and edges, plus net_relations for
derivation between nets. nets.name is unique and is the identifier.
Coordination sequences and fractional coordinates live on the vertex.
edges carries something the legacy schema did not: the periodic image the
far end of each edge sits in. Legacy had a redundant conn and a
non-redundant edge table, but neither recorded which neighbouring cell an
edge crossed into — so nothing could draw a net without re-parsing the uploaded
topology file, and any drawing that did not re-parse it was wrong. Vertices,
edges and coordination sequences are all read or computed from the deposited
.mfpx at deposition, which keeps the record and the file it serves in step.
nets also carries provenance columns the legacy schema had no equivalent of —
source, field_sources, rcsr_index and complexity_rank among them. The
last two are two different orderings and are routinely mistaken for each other;
before using either, read
the two orderings a net carries, which also
records which source supplies which field and which columns are NULL on every
ingested record.
net_analyses is the fourth table in this cluster and the newest. One row per
(net, kind, basis), where kind is periodicity, rings, invariants or
systre and basis is the graph analysed — mofplus-deposit or
rcsr-archive-key. Each row carries the tool and its version, the status
(ok, refused, failed, timed-out) with a reason where it is not ok, the
JSON result, the seconds it took, and input_checksum: the checksum of the
graph that was actually analysed. That last column is what makes a result
auditable — a row computed before the net's deposit changed can be told from one
computed after, and a stale row is counted rather than used. UNIQUE(net_id, kind, basis) keeps one answer per question. 19 196 rows cover the collection
today; see the 2026-09-12 analysis report, docs/reports/net-analysis-2026-09-12.md
in the repository (the dated reports are repository records and are not served
as site pages).
Three layers, deliberately kept apart
The nets side of this schema is written by three steps that must not be confused, because they make different kinds of claim:
| Layer | Writes | Claim |
|---|---|---|
Ingest (ingest.py, reconcile.py) |
nets, vertices, edges |
what the sources said |
Analyse (analysis.py, mofpp analyse-nets) |
net_analyses only |
what the graph says, per kind and per basis, never merged |
Apply (analysis_apply.py, mofpp apply-analysis) |
selected nets columns + field_sources |
which computed value is authoritative enough to filter and sort on |
Operational order is ingest-nets → analyse-nets → apply-analysis. The
analyse step never writes nets or vertices; the apply step never overwrites a
source column on a vertex, so cs and vs stay the source's own strings and the
computed values are served beside them under their own keys. A re-ingest may
reset columns, and apply is simply re-run. The separation is what lets a record
say "MOF+ sent this, and the graph says that" instead of silently presenting one
as the other.
A fourth thing is not a layer, because it writes nothing. Where those two
statements contradict each other the served record carries a curation call:
what the source claims, what was computed, the evidence, and the decision
somebody has to take. A call is assembled in services/nets.py when the record
is served, from field_sources and the net_analyses rows the record already
carries. There is no table for it and no column, nothing is written because of
one, and no list of affected names exists to fall out of date — a record that
stops meeting a rule stops carrying the call at its next request. A ruling on a
call is the one part that cannot be derived, so it is recorded as a literal,
nets.CURATION_DECISIONS, keyed to the exact question it answered rather than
to a record; a decision changes what is presented and still writes nothing.
The two orderings a net carries has the
rules, and the API reference the served shape.
Building blocks. bbs, keyed by a name unique across organic and
inorganic together.
Frameworks. frameworks (the legacy mofs), each optionally pointing at
the net it is built on, with structures hanging off it — one row per geometry
per level of theory, because one framework legitimately has several.
Force fields. atypes, ff_frags, ff_refs, ff, ff_fits, params,
plus the join tables that connect fragments and atom types to reference
systems, and the topoqeq pair. See
How force-field data is organised.
Accounts. users, groups and their join, plus tokens and
legacy_auth_events.
Three deliberate departures
1. Every record carries a licence. The legacy schema had no licence column anywhere. Here it is non-null and set at deposition, per record — see Identifiers and citation.
2. Four parameter tables became one. The legacy schema had onebody,
twobody, threebody and fourbody — four tables differing only in how many
atom-type columns they had. They are one params table now, with the atom
types and fragments as lists. Which table a row used to live in is just
len(atypes).
The wire contract did not change: parameters were already addressed as
params/{ric}/{fitid}, so no client noticed. Four near-identical tables
becoming one is exactly the kind of simplification a two-person team should
take when it is free.
3. File columns are paths. Legacy stored uploads under mangled generated filenames. Here a file column holds a relative path under the file store, so the store can be listed, rsynced and backed up without the database.
What has not been migrated
Some legacy tables have no counterpart here yet. That list is not a judgement about whether they matter — it is a list of things whose migration is still open:
supercell,scaledtopo,orients— needed byget_scaledtopoandget_orients, which currently answer an explicit fault naming this.prop_skal,prop_xy,prop_type— computed properties of structures.bbshapes,special_conn,scvertices,shapes,geoms— connection geometry, used by the assembly step.- the job queue —
firejobs,fireweaver,firerta,fireanalyzer,cputime. questionmarks,software, and the CMS tables.articleis superseded by the Markdown files these pages are written in.
Some of those are certainly dormant. Which ones is a question that gets
answered by counting rows in a database dump, not by reading the schema — a
sweep of row counts, latest timestamps, and reachability from nets, bbs,
frameworks and ff. Until that has been run, this list stays as it is:
open, and honest about being open.
Where documentation lives now
Not in the database. These pages are Markdown files in the repository, foldered by Diátaxis, rendered at request time. The previous system kept documentation in a text column edited through an admin panel, which meant it was unversioned, unreviewable and edited live in production. A correction is now a pull request.