本页的导航已翻译;规范本身为英文,以英文版为准: English

hypgraph/1 · 草案 1

记录格式

编写 HypGraph 记录的另一个实现、验证器或查看器所需的一切。

Status: draft 3 (HypGraph 0.3). Licence: CC BY 4.0. This page is enough to write another implementation, a verifier or a viewer.

1. Model

A record is a set of nodes (typed, versioned), edges (typed links between two nodes), withdrawals, retractions (of edges), leases (who is working on a node) and events. Every table is append-only: a correction is a new row, never an edited one.

  • A node has an id, a type, a display code (H1, CL3 …, unique per type within its investigation), the

investigation it belongs to (inv, empty for an investigation itself), created_at and created_by.

  • Its content lives in versions (node, v, at, by, reason, body). body is the canonical JSON (§3) of the

node's fields. The current version is the highest v.

  • An edge records the version of each end when it was made (src_v, dst_v). A review or decision is about that

version.

2. Vocabulary

typecodefields (* required)what it is
investigationINVtitle\, objective\, success_criteria, scope, state, reviewers, constants, strict_claims, keywords, projects, doi, note, legacy_caseThe study: an objective, its scope, who reviews it. Every other node belongs to one.
questionQtext*, whySomething you want to know.
hypothesisHstatement\, assumptions, predictions\, distinguishing, open_questionsA possible answer, with the predictions that would show it wrong.
claimCLstatement*, scope, limitations, kind, measurements, evidence_note, exposure, exposure_unit, legacyWhat you assert is known, with its scope and its numbers bound to results.
experimentXaim\, protocol\, controls, success_criteria*, method, budget, power_check, cost_minutes, seedA planned test: aim, protocol, controls, success criteria, and what each hypothesis expects.
runRUNkind*, job_id, name, state, code, inputs, outputs, args, config, seed, env, started, finished, exit_code, log_sha256, noteOne execution: code, inputs and outputs hashed, seed, state. kind=lab only from an instrument.
resultRSsummary*, kind, outcome, values, expected, limitations, power, evidence_note, legacyWhat a run (or an observation, or a citation) found: values, outcome, power.
artifactAsha256*, path, size, formatExact bytes, by sha256 — data, code, a model, a figure.
sourceScitation*, doi, url, passage, retrieved, says, kind, reliability, record, node, version, hashOutside literature or data: citation, DOI, the passage that says it. Or another record's node, pinned: record (its URL), node, version and hash (sha256 of that version's canonical body) — all four or none.
reviewRVverdict\, method\, notes*, officialA verdict on one version of a claim, result, run, experiment or hypothesis.
decisionDchoice\, alternatives, reasons\, revisit_if, meetingA choice made on the evidence; flagged when what it was based on changes.
taskTkanban_id*, titleA unit of work in your tracker, linked to what it works on.
relationfromtomeaning
part_ofany but investigationinvestigation, questionbelongs to an investigation or question
answersclaim, hypothesisquestionoffers an answer to a question
alternative_tohypothesishypothesiscompetes with another hypothesis
testsexperimentclaim, hypothesisan experiment tests a hypothesis or claim (data.expected: what it should show if true)
executesrunexperimenta run carries out an experiment
producedrunartifact, resulta run produced a result or artifact
usesrun, experimentartifacta run used an artifact (data.role: data / code / model / config); an experiment is designed on one
derived_fromartifact, claim, resultartifact, result, run, sourcecomputed or taken from
supportsclaim, result, sourceclaim, hypothesisevidence for (why required)
challengesclaim, result, sourceclaim, hypothesisevidence against (why required)
requiresclaim, hypothesisclaimrests on another claim as a premise
replicatesrunruna run repeats another (data.differs: same_code / new_seed / new_impl / new_data / new_env)
reviewsreviewclaim, experiment, hypothesis, result, runa review of a node (written by review())
supersedesany but artifact, review, investigationthe same typereplaces an older node of the same type
correctsclaim, decision, resultthe same typefixes an error in a node of the same type
motivated_byexperiment, hypothesis, questionanyprompted by
citesclaim, experiment, hypothesis, resultsourcecites a source
based_ondecisionclaim, hypothesis, resulta decision rests on this evidence (pinned to its version)
works_ontaskany but task, artifact, reviewa task works on a node
blocked_bytasktaska task waits on another task

Field kinds and lengths: hypgraph schema --json (or /schema/hypgraph-1.json on the site). A claim's measurements are {name, value | k & n | lo & hi, result: <result id>, key: <field of that result's values>}.

Verdicts: positive checked, pass, reproduced, replicated, separate_implementation, generalizes; negative fail, not_reproduced, not_replicated, does_not_generalize; and objection, resolved, inconclusive, comment.

3. Canonical JSON and the event chain

canon(x) is JSON with keys sorted at every level, separators , and : with no spaces, and non-ASCII characters written as-is (UTF-8). In Python: json.dumps(x, sort_keys=True, ensure_ascii=False, separators=(",", ":")).

Every write appends one or more events. An event's hash is

hash = sha256_hex( canon({"at", "actor", "kind", "ref", "payload", "prev"}) )

where prev is the previous event's hash (64 zeros for the first). Kinds and payloads:

kindrefpayload
createthe new node{type, code, inv, v: 1, body_sha, reason_sha, as?}; body_sha = sha256 of the version's stored body string, reason_sha of its stored reason
revisethe node{v, reason, body_sha, reason_sha}; reason is the first 300 characters
reviewthe reviewed node{review: <review node id>, verdict, on_v, official, body_sha, code, inv, as?}
linkthe edge's source{edge, rel, dst, src_v, dst_v, data_sha, why_sha}; data_sha = sha256 of the stored edge data string, why_sha of its stored reason
retractthe edge's source{edge, why_sha}
withdrawthe node{why, why_sha, as?}; why is the first 300 characters, why_sha covers the stored reason
lease_take / lease_releasethe node{task, expires, note_sha, for?}; for = the agent holding the lease when it isn't the actor

Any event may carry task in its payload: the unit of work that wrote it. as appears only when the importer credits a node or withdrawal to another author; from any other actor it is invalid.

Verifying a record means recomputing every hash in order and checking each prev, then checking the rows against the events both ways: every row was written by an event and matches it field by field, every row an event wrote is present, and no key appears twice. The fields each event fixes:

tablekeymust equal
nodescreate.ref, review.payload.reviewtype, code, inv from the payload (a review: type review); created_at = the event's at; created_by = the actor, or as
versionsnode + vat, by = the actor, sha256 of body = body_sha, sha256 of reason = reason_sha (a review's: empty)
edgespayload.edgesrc = ref, rel, dst, src_v, dst_v, at, by = the actor, sha256 of data = data_sha, of why = why_sha
withdrawalsthe nodeat, by = the actor or as, sha256 of why = why_sha
retractionspayload.edgeat, by = the actor, sha256 of why = why_sha
leasesin id order, one per lease event in ordernode = ref, agent = for or the actor, task, kind (take / release), at, expires, sha256 of note = note_sha

Records written before these fields existed (before 0.7.13) carry events without reason_sha, why_sha, note_sha, as, or a review's code and inv. A verifier checks what those events do carry (a revision's or a withdrawal's first 300 characters, a lease note given in full, a review's investigation from its target) and reports every other non-empty field as not covered, by table and field, rather than passing it silently. Without as, a row the importer wrote may credit any author; that is reported too. A verifier states at the top level whether every row field was vouched for by its event (coverage: complete) or not (partial: fields not covered, or redacted events).

What a verifier trusts. Who the importer is decides which older rows may credit another author, so the importer belongs to what a verifier trusts, and the chain doesn't cover a bundle's policy. A verifier must not take the importer, or any other policy field, from the bundle: it uses a policy its caller supplies (from the record's publisher or host), and the default importer name importer otherwise. Every field the caller supplies must equal the bundle's stated one. When the bundle names a different importer, the verifier says so.

Sequence numbers (seq) label events but aren't hashed: the order is fixed by prev. A verifier requires them to run 1 to n, so a relabelled or gapped log fails.

4. Bundles — hypgraph/1-bundle

A portable record. Every event is carried as the exact string that was hashed (body), and every version body and edge data as the exact stored string. A verifier therefore never has to reproduce canonical JSON: it hashes strings and compares.

{"format": "hypgraph/1-bundle", "generator": "hypgraph 0.1.0", "exported_at": "…",
 "complete": true, "redacted_events": 0, "scope": {"inv": "", "title": "whole graph"},
 "policy": {"reviewers": [], "instrument": "lab", "operator": "operator", "importer": "importer", "system": "system"},
 "head": "<hash of the last event>",
 "events": [{"seq": 1, "hash": "…", "body": "{\"actor\":…,\"at\":…,\"kind\":…,\"payload\":{…},\"prev\":…,\"ref\":…}"}],
 "nodes": [{"id": "…", "type": "…", "code": "…", "inv": "…", "created_at": "…", "created_by": "…"}],
 "versions": [{"node": "…", "v": 1, "at": "…", "by": "…", "reason": "", "body": "<stored canonical string>"}],
 "edges": [{"id": 1, "src": "…", "src_v": 1, "rel": "…", "dst": "…", "dst_v": 1, "data": "<stored string>", "…": "…"}],
 "withdrawals": [], "retractions": [], "leases": [],
 "derived": {"…": "views for viewers; their facts are checked against the rows, their standing is not"}}

A scoped bundle (one investigation out of a shared record) replaces events outside it with {"seq", "hash", "prev", "redacted": true}. The chain still links, but the content of those events can't be checked, so a change hidden among them would not show. Verifiers must report complete: false. To publish a study, keep it in a record of its own.

scope isn't covered by the chain, so a verifier requires the one shape an export writes: scope.inv names a node of type investigation, and every node row is that investigation or belongs to it; a bundle without a scope has no redacted events. Otherwise any node could pose as the investigation, or a whole record pass as one investigation, and the views would hide the rest beside a verified chain. A redacted event keeps only its hash, so that it lies outside the investigation is the bundle's word: only the full record, verified against the published head, rules out a change hidden among them. scope.title is a label; a viewer names the scope from the verified investigation.

What a verifier vouches for. ok means the chain links and every row and event agree, both ways. coverage: complete means every row field was vouched for by its event and nothing is redacted (partial otherwise). views: checked means every fact the derived views show that the rows fix is present and agrees with the rows, and the scope is a real one; views: none means the bundle carries no views. views: historical and views: unconfirmed mean the views verify but some lifecycles in them couldn't be checked (below), and the warning names each one: a viewer must not present them as checked. None of them vouches for standing, the report, stages or work orders (as exported), for who an author really is, or that the head was published.

Anchoring. A bundle proves only its own consistency. To prove it wasn't rebuilt from scratch, publish its head hash somewhere independent at the time (a paper, a preregistration, a repository tag). A later bundle whose chain passes through that hash can't have rewritten anything before it. A verifier given a published head (the full hash, or a prefix of at least 16 hex characters) fails unless the chain ends there: hypgraph verify FILE --head HEX, verify_bundle(b, head=HEX). A bundle without a head field is checked for consistency only. How a head is published and signed, and what each check does and doesn't show: verification.md.

Derived views. derived is a convenience for viewers, and a viewer shows it beside a verified chain, so a verifier must check every fact in it that the rows fix: which investigations it covers (the scope's, or every investigation node), which investigations the list shows (each once), which nodes each investigation's graph shows (all of them, unless capped at 5,000) and which live (unretracted) links among them, each node's type, code, inv, by, at, v, title, kind and state.lifecycle, and each link's id, ends, versions, rel, data, at, by and why. Every one of these must be present, not only equal where present: a viewer fills a missing fact with a default, and a withdrawn claim without its lifecycle would read as active. The lifecycle is derived from the verified rows (withdrawn when a withdrawal exists; superseded when a live supersedes or corrects replacement is active or, since 0.4, itself superseded; an investigation's own state otherwise). A verifier checks the lifecycle against the current rule. A lifecycle that only 0.3's rule gives (it counted an active replacement only) verifies, since a 0.3 export shows it so, but the chain doesn't record which version exported a bundle, so the views are historical, not checked. A scoped bundle with redacted events leaves out links from other investigations, and one of them may replace a node: a node shown superseded with no replacement in the rows verifies only if its type can be replaced at all (every type but investigation and review) and it isn't withdrawn, and the views are unconfirmed, since that replacement is among the redacted events. A bundle with no redacted events holds every link, scoped or not, so any other lifecycle the rows don't give fails. scope isn't covered by the chain, so naming one never loosens a check on its own. A bundle exported without derived views verifies, and a verifier says it has none (views: none). Standing (review state, support, at-risk, the sentence) can't be checked from the rows alone: it is what the exporting engine derived, and so are the report, the workflow stages, completion, the question board and work-order status. A verifier reports them as not checked, or re-derives them with the same engine version (hypgraph verify FILE --standing, under the caller's policy with --policy) and compares. A viewer must label them as exported wherever it shows them from a bundle.

5. Derived standing (normative)

Implementations must derive these, never store them:

  • lifecycle: withdrawn if withdrawn; superseded if a live node of the same type supersedes / corrects

it; an investigation's own state; else active.

  • review: computed over reviews by the board (the policy's reviewers ∪ the investigation's reviewers ∪ the

operator and importer), excluding comments. An objection stays open until its author records resolved. The operator, the importer and the investigation's reviewers may resolve anyone's. Then, on the current version: any negative verdict or open objection → contested; else any positive verdict → checked; else a positive verdict only on an older version → needs_revalidation; else unchecked.

  • fallen: withdrawn; or a contested claim / result / hypothesis; or a run whose state is failed / timeout /

cancelled / interrupted; or a result whose outcome is failed. Superseded is not fallen.

  • at_risk: the fallen nodes reachable upstream through requires, derived_from, based_on and the run that

produced it.

  • independent lines: live supporters, grouped when their roots overlap. Roots are producing runs, the data /

model artifacts those runs use, and sources. Supporters with no provenance at all form one group between them.

  • evidence: the best live supporter's profile, ranked `unverified < agent_reported < lab_recorded <

project_rerun < separate_implementation < new_data < external`.

    • A source is external.
    • A result produced by a kind=lab run that the instrument filed is lab_recorded.
    • A replication edge on the run raises the profile according to its differs.
    • A result of kind=cited is external only if it cites a source node.
  • basis_changed (decisions): a based_on edge whose target's version has moved past the pinned dst_v.
  • written blind (results): a work order with blind: true opens a window at its creation event and closes it at

the first release (return or cancel) or withdrawal of any order in its rework chain. A result its assignee created inside the window, and has not revised since it closed, is written blind under that order, provided the assignee had created nothing in the record before the window opened. While the window is open the result is sealed. Once it is closed, earlier results whose numbers it repeats are agrees_blind, not restates.

  • blind lines (claims, hypotheses): independent lines whose group contains a result written blind under a

closed window.

6. Write rules (normative for writers)

  • supports / challenges need a why of at least 10 characters. tests needs data.expected. replicates

needs data.differs. uses needs data.role.

  • Only the instrument files a kind=lab run, and only the instrument records what that run produced.
  • Claims must pass the checks in §7, unless written by the operator or importer, or in an investigation with

strict_claims: false.

  • Nobody officially reviews their own node.
  • Writes into an archived or published investigation are refused (reserved authors excepted).
  • Only the importer may set a write's time. Everyone else's comes from the writer's clock.
  • A writer sealed from nodes (it holds an open blind work order, or a host passes its reader's view with

Graph.sealed_from) may link to a sealed node only to say its own work supports, challenges or tests a hypothesis. It may not bind a measurement to a sealed result, rest a claim on sealed work, review or revise a sealed node, or issue a work order on sealed work, and a seat holding a blind order issues no orders. These are refused with one message, before any check reads the sealed material, so that no answer depends on what is sealed.

7. Claim checks

A claim is checked before it enters the record. Each failed rule is reported with a stable code, a message that does the arithmetic, a suggested fix, and the words that triggered it. Checks are skipped for the operator and importer, and in an investigation with strict_claims: false. Leads and conventions skip rules 7–9.

Arithmetic. For a count of k in n trials, the 95% interval is the Wilson score interval. At k = 0 (or k = n) it is the exact one-sided bound 1 − 0.05^(1/n), the "rule of three" (≈ 3/n). "Never, in n trials" means at most that rate, and never zero. Two counts are compared with Newcombe's hybrid score interval and a pooled two-proportion z-test. The smallest detectable difference uses 80% power at two-sided α = 0.05.

#CodeRefused when
1SCOPE_MISSINGa finding or exclusion has a scope shorter than 10 characters
2EVIDENCE_MISSINGa finding, exclusion or inconclusive claim has no supporting node
3MEASUREMENT_MALFORMED / MEASUREMENT_UNKNOWN_FIELD / MEASUREMENT_MISMATCHa measurement doesn't name a result and a key present in its values, or states a value / k / n different from the result's at the precision written (a fraction ≤ 1 may be written as a percentage)
4UNBOUND_NUMBERa number in the statement (digits, or the words two to twenty; digits in quotes are names) is not one of: a bound measurement's numbers; for a count, its rate, complement or n − k; for any two measurements, their difference, ratios, relative change or sum; the scope's numbers; the investigation's constants; or the conventions 95 / 99 / 90 / 80 / 0.05 / 0.01 when the statement speaks of intervals, bounds, confidence, power or significance. Statistical quantities (a rate's 95% bounds; for two counts the difference interval, p-value and detectable difference) count only when written to at least one decimal place.
5ABSOLUTE_WITHOUT_COUNTan absolute word (never, always, all, every, none, impossible, cannot, proves, certainly, guaranteed, eliminates, entirely, completely, universally, consistently, perfect, solves, zero, 100%, …) with no bound count where k is 0 or n
5aABSOLUTE_UNRELATED_COUNTthe counts with k ∈ {0, n} share no content word with the statement in their name or key
5bABSOLUTE_UNDERPOWEREDthe 95% bound on the opposite rate is above 5% and the statement doesn't say n ("never leaks" rather than "no leaks in 50 prompts" — a statement that gives its n is scoped to what was seen, and accepted). The message gives the honest wording and the trials needed for a 1-in-1,000 bound.
5cABSOLUTE_HIDES_DENOMINATORthe count is enough, but n appears in neither the statement nor the scope
6CONVENTION_HEDGEDa convention hedges (could, might, suggests, consistent with, …)
7COMPARATIVE_WITHOUT_COMPARISONa comparative (better, beats, outperforms, higher than, improves, exceeds, …) with fewer than two measured sides, no bound p-value or interval, and no supporting claim that binds two sides
8SIGNIFICANCE_WITHOUT_TEST / SIGNIFICANCE_NOT_SHOWN"significantly" / "statistically" with no bound p-value and fewer than two counts, or when the bound p-value, or the test the record runs on the first two counts, is not below 0.05
9CAUSAL_WITHOUT_INTERVENTIONa causal word (causes, leads to, results in, drives, due to, because of, responsible for, …) when no supporting result was produced by a run that executes an experiment
10EXCLUSION_WITHOUT_POWERkind: exclusion, or wording that says an effect is absent (no effect, no difference, makes no difference, does not affect, rules out, ineffective, …) in a finding, without a supporting result with power: demonstrated or one produced by a run of an experiment with a power_check. With two counts, the message gives the smallest detectable difference.
11UNCORRECTED_EFFECTa finding asserts an effect (changes, lowers, differs, affects, significant, …) on an outcome, while a live result in the investigation holds that outcome's corrected p-value (a field named with holm / bonferroni / fdr / adjusted / corrected …) at or above 0.05, or a count of 0 outcomes surviving correction (n_holm_sig), and the effect word is neither qualified within 20 words as uncorrected (uncorrected, unadjusted, nominally, without correction, …) nor names the correction with a corrected value bound as a measurement. A family-wide value (min_adjusted_p) applies when the claim binds a nominal p < 0.05. Thresholds (bonferroni_alpha_threshold) are not evidence. Honest forms: bind the corrected p and say "not significant after correction", or file kind: lead.

Exposure. A claim may set exposure (how many uses, users or units it will be applied to) and exposure_unit. Absolute refusals then state how many failures the bound leaves unruled out at that scale. An accepted claim's standing carries bounds (per count: k, n, rate, lo, hi and, with exposure, unruled_out), its sentence reads "'never' = at most 0.1% (0 of 3,000, 95%), up to 998 of 1,000,000 not ruled out", and a "significantly" claim carries test (the difference, its interval and p).

Blast radius. Withdrawing a node, or a verdict that makes it fall, returns blast_radius: every node resting on it by type, with claims and decisions named and, if a spend meter is connected, the cost of the work behind them.

8. The refusal log

A refused claim never enters the record, so its trace lives in a separate append-only table, refusal_log, with its own SHA-256 chain over canon({at, kind, author, inv, node, op, statement, codes, detail, ref, prev}). The record's own chain and verify() are unaffected.

  • kind: refused: the author, the investigation, the statement (≤ 1,500 characters; a result's summary), the

sorted codes, and the problems (code, message, fix) it was refused with; detail holds the whole body, the support it had and the node type (claim, result, or a typed node). op is create or revise; node is set for a revision.

  • A refusal on shape — a field the type doesn't have (SHAPE_UNKNOWN_FIELD), a value outside an enum

(SHAPE_NOT_AN_OPTION), a missing, too long or too short field, a wrong type, or a link the relation grammar refuses (LINK_NOT_ALLOWED, LINK_UNKNOWN_RELATION, LINK_NO_NODE) — is logged the same way with detail.type shape or link. Its message carries the fix: where the field goes (code on a result belongs to the run; an experiment's expected goes on its tests link) or what each option means. The report counts these apart from overclaims and the replay skips them.

  • kind: fixed: a later claim, result or revision by the same author in the same investigation, accepted within

24 hours, that says the same thing. "The same" means the same node for a revision; otherwise a Jaccard overlap ≥ 0.3 of the statements' content words, or, for a result, at least half of the refused result's value keys carried again. ref is the refusal it fixes.

A report aggregates per author (refused, fixed, open, codes) and per rule. A replay runs every logged refusal through the checker as it is now — claims through the claim and profile checks, results through the value and profile result checks as their author — and says for each whether it would be refused the same way, differently, or accepted; RESULT_RESTATES and typed-node refusals depend on the record's state at the time and are counted as skipped. A change to a rule is judged on that replay over the real corpus before it ships.

9. Checking a write-up against the record

A checker reads markdown sentence by sentence and checks it against the record. Code blocks, inline code, URLs, link targets and HTML comments are skipped, except a fenced block whose info string is answers: that is the report's machine-read answer (a JSON object, keyed by question code or flat), and every number or true / false in it is checked against the claims. Quoted passages and negated words ("cannot be proven") are not treated as the writer's assertion. Codes are matched within the investigation being reported on.

levelcodewhen
errorREPORT_CITES_WITHDRAWNa sentence cites a withdrawn node and doesn't say so (withdrawn, retracted, failed, superseded, earlier, …)
errorREPORT_OVERSTATES_STANDINGsettled language (confirmed, established, shown, proven, holds, …) about a claim or hypothesis that is unchecked, stale, contested or at risk, or a result that is contested or at risk
errorREPORT_UNKNOWN_CODEan INV / RUN / CL / RS / RV code the investigation doesn't have
errorREPORT_ABSOLUTE_UNBACKEDnever / always / impossible / guaranteed / eliminates / proven / 100% asserted, not in an instruction, with no cited claim carrying a count with k ∈ {0, n}
errorREPORT_ABSENCE_UNBACKED"no effect" / "rules out" / "doesn't matter" without citing a claim filed as exclusion or inconclusive
errorREPORT_SIGNIFICANCE_UNBACKED"significantly" without a p-value or interval in the sentence and without citing a node that carries a test (a negated "not significant" is a warning)
errorREPORT_ANSWER_STALEa value in the answers block that only stale, superseded or withdrawn claims (or stale results) on that question give, or that a current claim binds to a different value under the same field while an old one binds it to this; or a question whose every claim is old
errorREPORT_ANSWER_UNBACKEDa value in the answers block that no current claim on the question states or binds, at the precision the answer gives it and with its sign (a count k of n, bound or written as "k of n", backs its rate); or a question answered with no claim at all. A number a claim names only as a figure that no longer holds ("the earlier count of 789 is superseded") doesn't back it. URLs, paths and hashes in the block are provenance and aren't checked.
errorREPORT_ANSWERS_UNREADABLEthe answers block isn't valid JSON
warningREPORT_UNSOURCED_NUMBERa number not contained in or implied by the investigation's results, live claims' measurements, statements and scopes, the investigation's scope and constants, or experiment and run settings. Integers ≤ 12, years, dates, times and 7-digit+ ids are skipped. Statistical quantities need one decimal.
warningREPORT_OMITS_DISPUTEcites a contested or at-risk node without saying so
warningREPORT_CITES_SUPERSEDEDcites a superseded node without saying so
warningREPORT_CAUSAL_UNCITEDcausal language with no node cited
noteREPORT_BOUNDan absolute backed by a cited count: what the count supports

Calibrated on 24 real write-ups checked against the live record they report on.