Skip to content

Fact model

Everything that arrives is a file in a drawer. Everything ownpurse knows is a fact about something, stamped with who said it and from what. Everything you see is a query over those facts.

The canvas and console below run against the synthetic month in samples/ (one invented business, eight sources, fifty files), rebuilt in your browser from the log’s own schema.sql and every datom. Click a box for what it is, why it exists, its attributes and an example.

A fact is a datom: [entity, attribute, value, transaction, added]. Nothing is updated. A change is a new transaction that retracts the old value and asserts the new one, so any earlier state can be read back.

Column What
e The entity the fact is about
a The attribute, itself an entity; its name, type, cardinality and documentation are facts (db/ident, db/valueType, db/cardinality, db/doc)
v The value: a reference, text, an exact decimal, a date, an instant, a keyword or a hash
tx The transaction that wrote it
added 1 for an assertion, 0 for a retraction

A transaction is an entity with facts of its own: tx/actor, tx/projector, tx/source, tx/evidence (the drawer files it was read from), tx/observed-at, tx/instant, tx/reason and tx/pressure (where the source did not fit the model). Each transaction carries the hash of the one before; ownpurse-facts verify re-hashes the chain and every cited file. The datom and tx tables are append-only by trigger.

The log never gains a column. An attribute is an entity whose db/ident, db/valueType, db/cardinality and db/doc are datoms, so a new attribute is a few new rows; the month’s log has 799 of them in the same six columns. The kinds on the canvas above are conventions about which attributes appear together, not tables. The views below are queries that lay datoms out as rows: a null in a view is a datom that does not exist, and a view shows only the attributes it was written for, while facts shows them all.

Layer Kind Identity
Drawer Source An id chosen when connected, such as bank:ridgeway-4821
Drawer File The SHA-256 of its bytes
Documents Document The source’s own id; else the first file’s hash
Documents Region The file and a locator: a row, a JSON pointer, a page, a page and a box
Observations Observation The source’s id; else a natural key; else a fingerprint and occurrence; else a position (@n)
Observations Reading The entity, the attribute and the extractor’s field
Links Link Kind, from and to
Links Label, note Minted when written
Books Book, account, entry, posting Minted by ownpurse, or the document mirrored; external ids are ordinary facts
Books Commodity, price, party A code; a commodity, quote and time; a contact id or name

An observation is one item a source states, at a place in a file, about an account, in a commodity, at a time. An event with more than one side has legs: child observations (obs/parent), each with its own account, amount and commodity. Readings carry per-field provenance for anything read by an extractor; the observation keeps the plain value.

Sources are never merged. Rules write links, each with a rule and a confidence, and a wrong link is retracted like any other fact.

Kind Means
same-event Two witnesses of one movement of money (a CSV line and an OFX line)
evidences A receipt or check backs a line
settles A payment pays an invoice or bill, for link/amount
counterpart One invoice as it appears in two companies’ books
mirrors An entry mirrors the document it was built from
Terminal window
ownpurse-facts --log march.sqlite sql "SELECT * FROM observations WHERE kind = 'obs.kind/receipt'"
ownpurse-facts --log march.sqlite --as-of 491 entity doc/id=xero:tallowmere/Invoice/c0ffee03-0000-4000-8000-000000000002
ownpurse-facts --log march.sqlite history doc/id=xero:tallowmere/BankTransaction/c0ffee05-0000-4000-8000-000000000003

The views are facts (what is true now), history (every assertion and retraction), facts_shown (references shown by name), transactions, files, observations, links, postings, attr and label. Sum money with dsum(), which is exact; SQLite’s own sum() converts decimal text to floating point. The questions in samples/queries/ are run by the tests on every change.

The explorer loads site/public/data/month.json. After the fact model or the samples change, regenerate it from a clean checkout and commit the result:

Terminal window
bash site/scripts/month-log.sh

It builds ownpurse-facts, loads samples/ingest.json, runs the link rules, verifies the log and writes month.json and month-<commit>.json. Blog posts load their own pinned copy, so they keep showing the version they were written at. The design notes are in docs/plans/FACTS.md and the findings from phase 1 in docs/plans/FACTS-EDGES.md. For a walk through the model, see The fact model, up close.