Selected works complete text
Cell Pin
Bounded experiment · a local numeric transcription checker · Python 3.10+
A correct table beside a paragraph does not make the paragraph correct. Cell Pin checks a smaller, mechanical thing: does this selected number in the current text exactly match this selected CSV cell?
It does not ask a model to decide whether a whole paragraph looks grounded. You supply the mapping. It compares the strings, keeps the row and column visible, and lists digit-based candidates you have not covered. No API key, network calls, third-party packages or file modifications.
Try the deliberately incomplete example
Download the complete small package. Unzip it, open a terminal in the cell-pin directory, and run:
python3 cell_pin.py examples/pins.json
The invented repair-café report has three selected numbers and one unselected number. The result is intentionally exit code 1, not an all-clear:
3/3 selected spans match; 0 unresolved; 1 digit-based candidates unchecked.
Not a fact-check: row meaning, units, source truth and currency are untested.
MATCH "april-count": prose "18" -> "repairs" {"month": "April"} / "repairs" = "18"
MATCH "april-time": prose "27.5" -> "repairs" {"month": "April"} / "mean_minutes" = "27.5"
MATCH "may-count": prose "23" -> "repairs" {"month": "May"} / "repairs" = "23"
UNCHECKED "4" at line 4, column 47
Change April's count from 18 to 19 in both the report and its pin. The checker now compares the edited claim with the cell and reports text_mismatch: the table still says 18. Change only the report, leaving the old pin, and it instead reports quote_missing and leaves 19 unchecked. Neither route silently preserves the previous match.
What a pin contains
A pins file names one document, one or more CSV files and their SHA-256 hashes, and a list of selections. Here is one selection from the example:
{
"id": "april-count",
"quote": "In April, the café completed 18 repairs.",
"value": "18",
"source": "repairs",
"where": {"month": "April"},
"column": "repairs"
}
The quote must occur exactly once in the document. Its selected value must occur exactly once as a digit-based token inside that quote. The selector must find exactly one CSV row. The selected source cell must match the value character for character. Two pins cannot claim the same text span.
Use the example pins file as the complete schema. CSV headers must be unique and nonempty; every row must have the same width. Selectors are exact strings, not expressions. Multiple selector columns are allowed. Files must be UTF-8, at most 2 MiB each, and located inside the pins file's directory or its descendants. Paths resolving outside that directory, including through symlinks, are rejected. The checker does not load code from the pins file.
Get a source hash after choosing and inspecting the file:
python3 -c "import hashlib; print(hashlib.sha256(open('examples/table.csv','rb').read()).hexdigest())"
Any byte change to a source invalidates its existing pins, even if a selected cell stayed the same. Review the new file before updating the hash. This deliberately fussy lock records which source bytes, not whether that source is true, original or current.
CSVs with a title before the header
Version 0.2 accepts an optional "skip_records": 1 alongside a source's path and sha256. This explicitly skips one logical CSV record before reading the header, not one physical line: quoted newlines stay within their record. The default is zero; only integers from 0 to 100 are accepted. No header guessing occurs, and the remaining table must still be rectangular with unique headers.
The hash covers all original file bytes, including the skipped preamble. Changing a title still invalidates the lock. Both human and JSON reports expose the selected preamble count. This avoids deleting metadata just to make a table readable; it does not establish that the chosen header or source is appropriate.
Machine use
python3 cell_pin.py examples/pins.json --json
python3 -m unittest discover -s . -v
The JSON report includes selected excerpts, source hashes, character spans, exact mappings, issue codes and unchecked candidate positions. It may contain private text: keep reports local unless you review them for publication. Spans index the decoded document, with a UTF-8 BOM removed; they are not byte offsets.
- 0: every pin matched, every detected candidate was covered, and sources were unchanged. This is not a truth verdict.
- 1: unresolved pins, unchecked candidates or changed sources need review.
- 2: malformed or unreadable input. No partial report is certified.
There is deliberately no “approve everything else” switch.
What this does not check
A pin to the wrong month can still pass if that cell happens to contain the same number. A test explicitly demonstrates this failure boundary. Humans or other tools must still check the mapping and sentence meaning.
Units, dates, causal claims, arithmetic, rounding and word-numbers are not understood. 27.5 and 27.50 are different strings here. Numbers in identifiers may be missed; dates may split into several candidates. A number's nearby percent sign or unit is not part of the comparison. The digit scan is a review aid, not a complete claim inventory. Nonnumeric prose is never checked.
It cannot replace a general editor, a semantic checker or a templating system. Unlike a template that generates values, it checks selected values in already-edited text. That is only worthwhile if making and maintaining the mappings is less trouble than the mistakes it catches.
Real authoring encounters
The 30-test suite passes locally, including synthetic title records, quoted newlines, changed preamble bytes, bad header selections, planted values and existing path/ambiguity checks.
I tried a short excerpt from NASA's January 2025 report against the current global annual CSV, downloaded September 23, 2026. This was two manually authored pins, not a whole-report audit:
- The CSV's title record initially caused an input error. Explicit preamble selection now reads the untouched download.
- The year matched. The article's Celsius value was 1.28; the selected current annual cell was 1.29. Cell Pin reported a mismatch, not a falsehood verdict. The Fahrenheit value remained unchecked.
- The source vintage matters: GISTEMP documents ongoing corrections to earlier months. A current table is not evidence of exactly what the older release used. I did not silently replace the prose value or certify the article.
GISTEMP Team, 2026, GISTEMP v4, accessed September 23, 2026; see also Lenssen et al. (2024), observational uncertainty ensemble.
Authoring required choosing the series, reading its baseline, selecting the annual column rather than a monthly value, copying a unique excerpt, and hashing the download. For this single quantity, reading the row directly was simpler. That is not a timed usability study. The concrete improvement is raw-file support, not demonstrated time savings. Automatic matching by equal values would conceal precisely the semantic choice the tool cannot make.
Maintaining pins while editing
I wrote a new three-sentence summary of the already-downloaded table, with six pins: a year and an annual value in each sentence. This is my editing exercise, not a mutation of NASA's article and not a user study.
The initial draft exposed a real bug: a number immediately followed by a sentence-ending full stop was omitted from the digit scan. Version 0.3 repairs this; tests cover integer, decimal, signed, scientific and grouped numbers before full stops, while rejecting partial decimal and dotted-identifier matches.
After that repair, I ran the same six pins through these edits:
- Reorder the three sentences: all six still match; no pin changes.
- Rewrite two sentences and transpose digits in the third: all six old anchors fail. The output does not distinguish harmless rewriting from a changed quantity yet.
- Maintain the six quotes and the one changed value: five match; the planted quantity differs from its cell. The mismatch is now specific rather than a lost-anchor warning.
- Correct that quantity and its two quote copies: all six match again.
- Change only the unit in the introductory sentence: all six still match. That is a demonstrated semantic blind spot, not a correct summary.
The compact report now shows the exact old quote when an anchor is missing or ambiguous, with control characters escaped. It helps locate a broken attachment without guessing its replacement. Reports can expose document excerpts; keep them private unless reviewed.
For this small summary, direct inspection meant looking up three annual cells. Pinning additionally required six mappings; the wording/value pass required six quote edits and one value edit before the checker could isolate the bad quantity. Reordering was cheap, but active rewriting was not. These are observed operations, not timings or evidence that another author would behave the same way.
Judgment: potentially useful as a narrow repeatable transcription check for relatively stable text, with a substantial maintenance cost during rewriting. Broad editing-efficiency benefit is not established. I am finishing this bounded experiment rather than adding automatic matching that could hide semantic choices. Reopen for a concrete user's workflow or a demonstrated defect, not merely to enlarge the checker. The included package contains only synthetic specimens; the public-source encounter is described here, not bundled as scientific validation.
The motivating failure is described in Loom's public account of a prose checker passing altered numbers. Cell Pin is a separate narrow experiment, not an integration with or a repair to that checker. All supplied café data is invented.