ledger: extract stackchain magazine photo catalog into a table

This commit is contained in:
ledger committed 2026-10-20 20:18:17 +00:00
1 parent f44a285454
commit c0dea95d77
4 files changed
+210

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
no,entry,type,text,words
1,"img_67eedaf526e7.jpg","COVER","STACKCHAIN
MAGAZINE
ROUND 5
INTERVIEW WITH MADEX
BY ISBELL
88K CHRONICLES: HOW ICO CONSPIRED
TO SUPPRESS THE BITCOIN PRICE
BY DENIZ
REASONS TO BUY ALTCOINS
BY SKYLER
SELF-CUSTODY//BITCOIN ONLY
MAGAZINE
18+
stackchain
MAGAZINE
MADE IN [unreadable]
[unreadable graffiti tags on the owl artwork]",43
2,"img_fcb2e6a8f066.jpg","PHOTO_OF_PERSON_HOLDING_MAGAZINE","B/H872.372
[unreadable]O CHAIN
[unreadable]INE
[unreadable]OUND 5
MACHINA
SELF-CUSTODY//BIT[unreadable]",8
3,"img_72f52468393f.jpg","(not transcribed)","—",0
4,"img_d5ada9cee4e9.jpg","(not transcribed)","—",0
5,"img_7333a3df0aac.jpg","(not transcribed)","—",0
6,"img_05d51da1fd9d.jpg","(not transcribed)","—",0
7,"img_125532ea2e01.jpg","(not transcribed)","—",0
8,"img_1325775751e6.jpg","(not transcribed)","—",0
1 no entry type text words
2 1 img_67eedaf526e7.jpg COVER STACKCHAIN MAGAZINE ROUND 5 INTERVIEW WITH MADEX BY ISBELL 88K CHRONICLES: HOW ICO CONSPIRED TO SUPPRESS THE BITCOIN PRICE BY DENIZ REASONS TO BUY ALTCOINS BY SKYLER SELF-CUSTODY//BITCOIN ONLY MAGAZINE 18+ stackchain MAGAZINE MADE IN [unreadable] [unreadable graffiti tags on the owl artwork] 43
3 2 img_fcb2e6a8f066.jpg PHOTO_OF_PERSON_HOLDING_MAGAZINE B/H872.372 [unreadable]O CHAIN [unreadable]INE [unreadable]OUND 5 MACHINA SELF-CUSTODY//BIT[unreadable] 8
4 3 img_72f52468393f.jpg (not transcribed) — 0
5 4 img_d5ada9cee4e9.jpg (not transcribed) — 0
6 5 img_7333a3df0aac.jpg (not transcribed) — 0
7 6 img_05d51da1fd9d.jpg (not transcribed) — 0
8 7 img_125532ea2e01.jpg (not transcribed) — 0
9 8 img_1325775751e6.jpg (not transcribed) — 0
+58
View File
@@ -0,0 +1,58 @@
[
{
"no": 1,
"entry": "img_67eedaf526e7.jpg",
"type": "COVER",
"text": "STACKCHAIN\nMAGAZINE\nROUND 5\nINTERVIEW WITH MADEX\nBY ISBELL\n88K CHRONICLES: HOW ICO CONSPIRED\nTO SUPPRESS THE BITCOIN PRICE\nBY DENIZ\nREASONS TO BUY ALTCOINS\nBY SKYLER\nSELF-CUSTODY//BITCOIN ONLY\nMAGAZINE\n18+\nstackchain\nMAGAZINE\nMADE IN [unreadable]\n[unreadable graffiti tags on the owl artwork]",
"words": 43
},
{
"no": 2,
"entry": "img_fcb2e6a8f066.jpg",
"type": "PHOTO_OF_PERSON_HOLDING_MAGAZINE",
"text": "B/H872.372\n[unreadable]O CHAIN\n[unreadable]INE\n[unreadable]OUND 5\nMACHINA\nSELF-CUSTODY//BIT[unreadable]",
"words": 8
},
{
"no": 3,
"entry": "img_72f52468393f.jpg",
"type": "(not transcribed)",
"text": null,
"words": 0
},
{
"no": 4,
"entry": "img_d5ada9cee4e9.jpg",
"type": "(not transcribed)",
"text": null,
"words": 0
},
{
"no": 5,
"entry": "img_7333a3df0aac.jpg",
"type": "(not transcribed)",
"text": null,
"words": 0
},
{
"no": 6,
"entry": "img_05d51da1fd9d.jpg",
"type": "(not transcribed)",
"text": null,
"words": 0
},
{
"no": 7,
"entry": "img_125532ea2e01.jpg",
"type": "(not transcribed)",
"text": null,
"words": 0
},
{
"no": 8,
"entry": "img_1325775751e6.jpg",
"type": "(not transcribed)",
"text": null,
"words": 0
}
]
+15
View File
@@ -0,0 +1,15 @@
# Stackchain Magazine photo catalog — extracted
Source: `stackchain_catalog_raw.json` (8 entries).
Transcription present: 2 of 8 entries; 6 entries carry no `text`.
| No. | Entry | Type | Transcription | Words |
|---|---|---|---|---|
| 1 | `img_67eedaf526e7.jpg` | COVER | STACKCHAIN MAGAZINE ROUND 5 INTERVIEW WITH MADEX BY ISBELL 88K CHRONICLES: HOW ICO CONSPIRED TO SUPPRESS THE BITCOIN PRICE BY DENIZ REASONS… | 43 |
| 2 | `img_fcb2e6a8f066.jpg` | PHOTO_OF_PERSON_HOLDING_MAGAZINE | B/H872.372 [unreadable]O CHAIN [unreadable]INE [unreadable]OUND 5 MACHINA SELF-CUSTODY//BIT[unreadable] | 8 |
| 3 | `img_72f52468393f.jpg` | (not transcribed) | — | 0 |
| 4 | `img_d5ada9cee4e9.jpg` | (not transcribed) | — | 0 |
| 5 | `img_7333a3df0aac.jpg` | (not transcribed) | — | 0 |
| 6 | `img_05d51da1fd9d.jpg` | (not transcribed) | — | 0 |
| 7 | `img_125532ea2e01.jpg` | (not transcribed) | — | 0 |
| 8 | `img_1325775751e6.jpg` | (not transcribed) | — | 0 |
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""CHECK: re-derive the catalog from the raw JSON source and diff against the
produced artifact (catalog.json + catalog.md). Exit 0 only if they agree.
This is a real regression test, not a tautology: it re-reads the source file and
re-derives the rows, then compares against the committed artifact bytes.
"""
import json
import re
import sys
SRC = '/root/stackchain_catalog_raw.json'
ACTUAL_JSON = '/root/.hermes/wizard-outputs/ledger/catalog.json'
ACTUAL_MD = '/root/.hermes/wizard-outputs/ledger/catalog.md'
TYPE_RE = re.compile(r'"type"\s*:\s*"(.*?)"')
TEXT_RE = re.compile(r'"text"\s*:\s*"(.*?)"', re.DOTALL)
DOUBLE_N = '\\\\n'
SINGLE_N = '\\n'
def reextract():
raw = json.load(open(SRC, encoding='utf-8'))
rows = []
for idx, (fname, v) in enumerate(raw.items(), 1):
m_type = TYPE_RE.search(v)
ftype = m_type.group(1) if m_type else None
m_text = TEXT_RE.search(v)
text = m_text.group(1) if m_text else None
if text is not None:
text = text.replace(DOUBLE_N, '\n').replace(SINGLE_N, '\n')
rows.append({
'no': idx,
'entry': fname,
'type': ftype if ftype is not None else '(not transcribed)',
'text': text,
'words': len(text.split()) if text else 0,
})
return rows
def load_actual():
rows = json.load(open(ACTUAL_JSON, encoding='utf-8'))
md = open(ACTUAL_MD, encoding='utf-8').read()
return rows, md
def md_table_rows(md):
rows = []
for line in md.splitlines():
if line.startswith('|') and '---' not in line and 'No.' not in line:
cells = [c.strip() for c in line.strip('|').split('|')]
if cells:
rows.append(cells)
return rows
def main():
errors = []
expected = reextract()
actual, md = load_actual()
if len(actual) != len(expected):
errors.append(f'row count: actual={len(actual)} expected={len(expected)}')
for i, (a, e) in enumerate(zip(actual, expected), 1):
for key in ('no', 'entry', 'type', 'text', 'words'):
if a.get(key) != e[key]:
errors.append(f'row {i} field {key}: actual={a.get(key)!r} expected={e[key]!r}')
# markdown sanity: every entry must appear, with a type, and word counts must
# match the numbers in the table.
seen = set()
for cells in md_table_rows(md):
if len(cells) == 5 and cells[1].startswith('`'):
seen.add(cells[1].strip('`'))
if cells[3] == '—':
if actual[int(cells[0]) - 1]['text'] is not None:
errors.append(f'row {cells[0]} shows — but source text present')
else:
text = actual[int(cells[0]) - 1]['text']
if text is None:
errors.append(f'row {cells[0]} preview missing but source text absent')
else:
single = ' '.join(text.split())
exp_preview = single[:140].rstrip() + ('…' if len(single) > 140 else '')
if cells[3] != exp_preview:
errors.append(f'row {cells[0]} preview mismatch: {cells[3]!r} vs {exp_preview!r}')
if cells[2] != actual[int(cells[0]) - 1]['type']:
errors.append(f'row {cells[0]} type: {cells[2]} vs {actual[int(cells[0]) - 1]["type"]}')
expected_entries = {r['entry'] for r in expected}
if seen != expected_entries:
errors.append('md entries missing/extra: ' + ', '.join(sorted(seen ^ expected_entries)))
if errors:
print('CHECK FAILED:')
for e in errors:
print(' -', e)
sys.exit(1)
print(f'CHECK OK: {len(actual)} rows match the source exactly '
f'({sum(1 for r in actual if r["text"])} transcribed, '
f'{sum(1 for r in actual if not r["text"])} empty).')
sys.exit(0)
if __name__ == '__main__':
main()