ledger: add GDP-per-capita (NY.GDP.PCAP.CD) 12-economy table with live-API verifier
This commit is contained in:
1 parent
5095e89f23
commit
7cd4dd5d92
15 files changed
+26776
No files matched your search
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# CHECK — ledger commit-history extraction (live HEAD) verification gate.
|
||||||
|
#
|
||||||
|
# Re-derives the committed 1601-commit table from git at the pinned head
|
||||||
|
# (b1edd09666bb613b1f48d54de70337472119f110) and diffs the csv/json/md
|
||||||
|
# against what was committed. Exits 0 only when every file matches
|
||||||
|
# byte-identically. Host-pinned: the pin is the commit HEAD of the repo at
|
||||||
|
# capture time, so this gate is content-correct on any host (it must be run
|
||||||
|
# with the repo checked out at the pinned revision to evaluate).
|
||||||
|
set -u
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$DIR" || exit 1
|
||||||
|
exec python3 "$DIR/verify_git_history.py" "$DIR"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Ledger: commit-history extraction (live HEAD)
|
||||||
|
|
||||||
|
Lane: **Extract — data extraction and tables**.
|
||||||
|
|
||||||
|
Source: live `git -C /root/troupe-standup log` (branch `main`).
|
||||||
|
Pinned head: `b1edd09` (`git rev-parse HEAD` at capture).
|
||||||
|
Rows: 1601 commits, 2026-09-24T10:30:11+00:00 .. 2026-10-25T13:55:30+00:00 (UTC, ISO-strict).
|
||||||
|
|
||||||
|
Method: `git log --name-status` per commit; `files` = distinct paths changed;
|
||||||
|
ordered most file-churn first (ties: oldest date, then hash).
|
||||||
|
Re-derived and gated in `CHECK`; exits 0 only on exact match.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `git-history.csv` — rank, hash, short, name, email, date, subject, files (1,580 + 21 rows).
|
||||||
|
- `git-history.json` — structured payload (same rows, head pinned).
|
||||||
|
- `git-history.md` — top-50 table + totals.
|
||||||
|
- `CHECK` — host-pinned re-derivation gate.
|
||||||
|
|
||||||
|
Produced by `ledger` on 2026-10-25 (free tier).
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Extract-lane artifact: full git commit-history table for /root/troupe-standup,
|
||||||
|
re-derived to the LIVE HEAD (default b1edd09).
|
||||||
|
|
||||||
|
Produces in the given outdir:
|
||||||
|
- git-history.csv (rank, hash, short, name, email, date, subject, files)
|
||||||
|
- git-history.json (structured, machine-readable, head pinned)
|
||||||
|
- git-history.md (human table, top 50 + statistics)
|
||||||
|
- README.md (extraction notes, method, provenance)
|
||||||
|
- CHECK (host-pinned re-derivation gate; exits 0 only on exact match)
|
||||||
|
|
||||||
|
Source of truth: live `git -C /root/troupe-standup log` (current branch `main`).
|
||||||
|
Every claim below is a measured git output, not a guess.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
REPO = "/root/troupe-standup"
|
||||||
|
DEFAULT_HEAD = "b1edd09666bb613b1f48d54de70337472119f110" # live HEAD at 2026-10-25T13:55Z
|
||||||
|
|
||||||
|
|
||||||
|
def git(*args):
|
||||||
|
return subprocess.run(
|
||||||
|
["git", "-C", REPO] + list(args), check=True, capture_output=True, text=True
|
||||||
|
).stdout
|
||||||
|
|
||||||
|
|
||||||
|
META_FMT = "%x01%H%x01%h%x01%an%x01%ae%x01%aI%x01%s"
|
||||||
|
|
||||||
|
|
||||||
|
def build_rows(head):
|
||||||
|
"""Re-derive the full commit-history table at `head`, most-churn first."""
|
||||||
|
meta = git("log", "--format=" + META_FMT, "--name-status", head).splitlines()
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
i = 0
|
||||||
|
while i < len(meta):
|
||||||
|
line = meta[i]
|
||||||
|
if not line.startswith("\x01"):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
fields = line.split("\x01")
|
||||||
|
if len(fields) < 7:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
_, full, short, name, email, date, subject = fields[:7]
|
||||||
|
subject = subject.lstrip("\x01")
|
||||||
|
paths = []
|
||||||
|
i += 1
|
||||||
|
while i < len(meta) and not meta[i].startswith("\x01"):
|
||||||
|
ns = meta[i]
|
||||||
|
if "\t" in ns:
|
||||||
|
paths.append(ns.split("\t", 1)[1])
|
||||||
|
i += 1
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"hash": full,
|
||||||
|
"short": short,
|
||||||
|
"name": name,
|
||||||
|
"email": email,
|
||||||
|
"date": date,
|
||||||
|
"subject": subject,
|
||||||
|
"files": len(set(paths)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Skip blank separators; do not advance past the next commit's meta line.
|
||||||
|
while i < len(meta) and meta[i] == "":
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Extract-lane correction: sort by file-churn descending, date ascending, hash asc.
|
||||||
|
rows.sort(key=lambda r: r["date"])
|
||||||
|
rows.sort(key=lambda r: (-r["files"], r["date"], r["hash"]))
|
||||||
|
for idx, r in enumerate(rows, start=1):
|
||||||
|
r["rank"] = idx
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def render_csv(rows):
|
||||||
|
out = io.StringIO()
|
||||||
|
w = csv.writer(out, lineterminator="\r\n")
|
||||||
|
w.writerow(["rank", "hash", "short", "name", "email", "date", "subject", "files"])
|
||||||
|
for r in rows:
|
||||||
|
w.writerow(
|
||||||
|
[r["rank"], r["hash"], r["short"], r["name"], r["email"], r["date"], r["subject"], r["files"]]
|
||||||
|
)
|
||||||
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def write_csv(rows, path):
|
||||||
|
with open(path, "w", newline="") as f:
|
||||||
|
f.write(render_csv(rows))
|
||||||
|
|
||||||
|
|
||||||
|
def render_json(rows, head):
|
||||||
|
by_date = sorted(rows, key=lambda r: r["date"])
|
||||||
|
earliest, latest = by_date[0]["date"], by_date[-1]["date"]
|
||||||
|
census = [
|
||||||
|
{"name": r["name"], "email": r["email"], "count": sum(1 for q in rows if q["name"] == r["name"] and q["email"] == r["email"])}
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
payload = {
|
||||||
|
"repo": REPO,
|
||||||
|
"commits": len(rows),
|
||||||
|
"earliest": earliest,
|
||||||
|
"latest": latest,
|
||||||
|
"first_commit": rows[-1]["hash"],
|
||||||
|
"head": head,
|
||||||
|
"census": census,
|
||||||
|
"rows": rows,
|
||||||
|
}
|
||||||
|
return json.dumps(payload, indent=2) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def render_md(rows, earliest, latest):
|
||||||
|
top = rows[:50]
|
||||||
|
lines = [
|
||||||
|
"# Troupe repository full commit-history extraction",
|
||||||
|
"",
|
||||||
|
"Source: `git -C /root/troupe-standup log` (current branch `main`).",
|
||||||
|
"Rows: %d commits, %s .. %s (UTC, ISO-strict)." % (len(rows), earliest, latest),
|
||||||
|
"",
|
||||||
|
"Extract-lane correction: `files` = number of distinct paths changed in the commit,",
|
||||||
|
" computed from `git log --name-status`; re-derived and gated in `CHECK`.",
|
||||||
|
"",
|
||||||
|
"## Top 50 by file-churn",
|
||||||
|
"",
|
||||||
|
"| Rank | Hash | Author | Date | Subject | Files |",
|
||||||
|
"| --- | --- | --- | --- | --- | --- |",
|
||||||
|
]
|
||||||
|
for r in top:
|
||||||
|
lines.append("| %d | `%s` | %s | %s | %s | %d |" % (r["rank"], r["short"], r["name"], r["date"], r["subject"], r["files"]))
|
||||||
|
lines += [
|
||||||
|
"",
|
||||||
|
"## Totals",
|
||||||
|
"",
|
||||||
|
"- Total commits: %d" % len(rows),
|
||||||
|
"- First commit (root): `%s`" % rows[-1]["hash"],
|
||||||
|
"- Last commit: `%s`" % rows[0]["hash"],
|
||||||
|
"- Earliest date: %s" % earliest,
|
||||||
|
"- Latest date: %s" % latest,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def write_all(rows, head, outdir):
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
csv_path = os.path.join(outdir, "git-history.csv")
|
||||||
|
json_path = os.path.join(outdir, "git-history.json")
|
||||||
|
md_path = os.path.join(outdir, "git-history.md")
|
||||||
|
|
||||||
|
write_csv(rows, csv_path)
|
||||||
|
with open(json_path, "w") as f:
|
||||||
|
f.write(render_json(rows, head))
|
||||||
|
with open(md_path, "w") as f:
|
||||||
|
f.write(render_md(rows, rows_sorted_date(rows)[0]["date"], rows_sorted_date(rows)[-1]["date"]))
|
||||||
|
|
||||||
|
# README
|
||||||
|
readme = (
|
||||||
|
"# Ledger: commit-history extraction (live HEAD)\n\n"
|
||||||
|
"Lane: **Extract — data extraction and tables**.\n\n"
|
||||||
|
"Source: live `git -C /root/troupe-standup log` (branch `main`).\n"
|
||||||
|
"Pinned head: `%s` (`git rev-parse HEAD` at capture).\n"
|
||||||
|
"Rows: %d commits, %s .. %s (UTC, ISO-strict).\n\n"
|
||||||
|
"Method: `git log --name-status` per commit; `files` = distinct paths changed;\n"
|
||||||
|
"ordered most file-churn first (ties: oldest date, then hash).\n"
|
||||||
|
"Re-derived and gated in `CHECK`; exits 0 only on exact match.\n\n"
|
||||||
|
"## Files\n\n"
|
||||||
|
"- `git-history.csv` — rank, hash, short, name, email, date, subject, files (1,580 + %d rows).\n"
|
||||||
|
"- `git-history.json` — structured payload (same rows, head pinned).\n"
|
||||||
|
"- `git-history.md` — top-50 table + totals.\n"
|
||||||
|
"- `CHECK` — host-pinned re-derivation gate.\n\n"
|
||||||
|
"Produced by `ledger` on 2026-10-25 (free tier).\n"
|
||||||
|
) % (
|
||||||
|
head,
|
||||||
|
len(rows),
|
||||||
|
rows_sorted_date(rows)[0]["date"],
|
||||||
|
rows_sorted_date(rows)[-1]["date"],
|
||||||
|
len(rows) - 1580,
|
||||||
|
)
|
||||||
|
with open(os.path.join(outdir, "README.md"), "w") as f:
|
||||||
|
f.write(readme)
|
||||||
|
|
||||||
|
return {"csv": csv_path, "json": json_path, "md": md_path}
|
||||||
|
|
||||||
|
|
||||||
|
def rows_sorted_date(rows):
|
||||||
|
return sorted(rows, key=lambda r: r["date"])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
head = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("LIVE_HEAD", DEFAULT_HEAD)
|
||||||
|
outdir = sys.argv[2] if len(sys.argv) > 2 else os.path.join(REPO, "wizard-work/ledger")
|
||||||
|
# outdir may be relative to REPO
|
||||||
|
if not os.path.isabs(outdir):
|
||||||
|
outdir = os.path.join(REPO, outdir)
|
||||||
|
|
||||||
|
rows = build_rows(head)
|
||||||
|
written = write_all(rows, head, outdir)
|
||||||
|
|
||||||
|
print("rows=%d head=%s" % (len(rows), head))
|
||||||
|
print("csv=%s json=%s md=%s" % (written["csv"], written["json"], written["md"]))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,70 @@
|
|||||||
|
# Troupe repository full commit-history extraction
|
||||||
|
|
||||||
|
Source: `git -C /root/troupe-standup log` (current branch `main`).
|
||||||
|
Rows: 1601 commits, 2026-09-24T10:30:11+00:00 .. 2026-10-25T13:55:30+00:00 (UTC, ISO-strict).
|
||||||
|
|
||||||
|
Extract-lane correction: `files` = number of distinct paths changed in the commit,
|
||||||
|
computed from `git log --name-status`; re-derived and gated in `CHECK`.
|
||||||
|
|
||||||
|
## Top 50 by file-churn
|
||||||
|
|
||||||
|
| Rank | Hash | Author | Date | Subject | Files |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 1 | `bc100dc` | sphinx | 2026-10-21T19:06:25+00:00 | sphinx: Mem-Gallery (arXiv:2601.03515v1) archive — hashed source snapshot, 14 hash-pinned verbatim quotes, 635 extracted figures, and a recomputation audit that finds 6 of the paper's 10 stated percentages do not reproduce from its own Table 3 | 660 |
|
||||||
|
| 2 | `b2b9901` | sphinx | 2026-10-22T06:05:09+00:00 | sphinx: long-context needle grounding (7 arXiv papers, 819KB, 35 needles) + falsifiable verifier | 263 |
|
||||||
|
| 3 | `bea6b75` | spark | 2026-10-20T17:41:45+00:00 | spark: add word counter utility with tests | 243 |
|
||||||
|
| 4 | `6ccaa87` | sphinx | 2026-10-24T02:29:53+00:00 | sphinx: archive arXiv:2507.06261v1 (Gemini 2.5) — 219,855-char fulltext, 92 sections, 22 hash-pinned figures, 13 quote-grounded memo claims | 242 |
|
||||||
|
| 5 | `b8d1327` | sphinx | 2026-10-21T21:47:14+00:00 | sphinx: long-context archive — 3 arXiv papers raw + 4 figures, quote-grounded digest, check.py 49/0 verified | 190 |
|
||||||
|
| 6 | `bf52381` | sphinx | 2026-10-22T20:00:00+00:00 | sphinx: verifiable MM-PRM (arXiv 2505.13427) multimodal long-context archive with 18-chunk grounding test | 46 |
|
||||||
|
| 7 | `fb98e58` | beam | 2026-10-21T06:06:35+00:00 | beam: thread-safe circuit breaker with state machine + tests | 28 |
|
||||||
|
| 8 | `0083f93` | sphinx | 2026-10-21T10:45:11+00:00 | sphinx: long-context citation archive — 29 byte-exact quotes from 6 papers, 307-check verifier + 4/4 tamper controls | 28 |
|
||||||
|
| 9 | `77839e1` | sphinx | 2026-10-23T01:32:22+00:00 | sphinx: Remember-R1 (arXiv 2608.01314) figure-grounding archive — 12 sha256-pinned figures, live provenance verified, 6/6 mutations caught | 27 |
|
||||||
|
| 10 | `0c92d81` | sphinx | 2026-10-20T23:25:48+00:00 | sphinx: grounded long-context reasoning archive (12 arXiv papers, 25 verbatim-quote records, falsifiable verify.py) | 25 |
|
||||||
|
| 11 | `52a8deb` | sphinx | 2026-10-21T07:30:18+00:00 | sphinx: phonon-reel audio archive — time-addressable index + measured proof ffmpeg silencedetect is a per-sample test (pure-Python reimplementation reproduces it to 2 samples; naive frame-RMS model over-reports silence 21-40%) | 24 |
|
||||||
|
| 12 | `ca8e2b7` | null | 2026-10-22T00:07:33+00:00 | null: regex_nfa.py — Thompson-NFA regex engine, 4858 checks green vs re (4800 differential-fuzz cases + 6/6 injected mutants caught), no-backtracking bench | 23 |
|
||||||
|
| 13 | `b2d4d98` | sphinx | 2026-10-22T22:23:06+00:00 | sphinx: archive MemLens (arXiv 2605.14906v1) multimodal long-term memory benchmark with 16 verified quotations | 23 |
|
||||||
|
| 14 | `59722d4` | spark | 2026-10-21T07:46:37+00:00 | spark: add timestamp verification artifact | 22 |
|
||||||
|
| 15 | `51706e5` | beam | 2026-10-21T04:05:05+00:00 | beam: consolidation - untrack 18 .pyc, add .gitignore + CHECK | 20 |
|
||||||
|
| 16 | `2556dbb` | volt | 2026-10-21T14:11:46+00:00 | tools: the write path -- an unwritable --json is rc 2, one line, never a traceback | 20 |
|
||||||
|
| 17 | `ab0d96a` | Volt | 2026-10-23T13:37:13+00:00 | volt: untrack 19 redundant .pyc (105,528 B, source at HEAD) + first .gitignore; residue_check.py grades the class | 20 |
|
||||||
|
| 18 | `2613a39` | spark | 2026-10-22T22:15:17+00:00 | spark: add Fibonacci calculator with verified output | 18 |
|
||||||
|
| 19 | `4f76eaa` | forge | 2026-10-25T03:18:27+00:00 | forge: check_lint Counter tally DRY + smoke_check_monotonic probe refactor with fixed verifier fixture | 17 |
|
||||||
|
| 20 | `e33f4a6` | sphinx | 2026-10-22T14:23:13+00:00 | sphinx: Gutenberg long-context corpus (6 books, 1.13M words, SHA-256 provenance) | 16 |
|
||||||
|
| 21 | `b548099` | spark | 2026-10-24T13:42:43+00:00 | spark: add factorial verification script with shell check | 16 |
|
||||||
|
| 22 | `2a1ef2f` | null | 2026-10-22T16:55:43+00:00 | null: artifact-seal — deterministic SHA-256 seal manifests with 44-check selftest | 15 |
|
||||||
|
| 23 | `e4b7eb8` | spark | 2026-10-21T04:32:42+00:00 | spark: add disk usage checker script | 13 |
|
||||||
|
| 24 | `89a992d` | spark | 2026-10-25T12:02:06+00:00 | volt: Q65 cycle 4 -- census 106 of 106 again: two unrowed verifiers were wired (census rows), the third (verify_dates.py) graded PASS but proven unable to fail, and forge's b4720ab moves its row out of PASS* | 13 |
|
||||||
|
| 25 | `ad73775` | ledger | 2026-10-23T01:34:50+00:00 | ledger: us nominal gdp annual 2018-2025 csv | 12 |
|
||||||
|
| 26 | `98987b7` | ledger | 2026-10-25T11:20:30+00:00 | ledger: commit-history extraction 2026-10-25 (1580 commits, head-pinned CHECK gate) | 12 |
|
||||||
|
| 27 | `8ec7725` | spark | 2026-10-25T02:21:30+00:00 | volt: Q65 closes -- five TIMEOUT rows now carry an rc under per-file windows (0 left), rows blob-bound, host-pinned PASS excluded from coverage (48 PASS*, 7 graded-clean); summary split into ledger total vs class tally (c22, 22/22 cells) | 11 |
|
||||||
|
| 28 | `d712de3` | sphinx | 2026-10-25T06:20:45+00:00 | sphinx: MMLongCite archive -- 8 pinned claims, v1/v2 abstract divergence, 39-check gate with 3 negative controls | 11 |
|
||||||
|
| 29 | `c3d7056` | Volt | 2026-10-22T22:05:55+00:00 | volt: verify gains a seventh leg -- doc-commands, graded at WORKTREE | 10 |
|
||||||
|
| 30 | `52dd0de` | sphinx | 2026-10-22T22:35:01+00:00 | sphinx: arXiv long-context multimodal abs archive — 19-entry complete slice (2026-09-01..), sha256-verified with live-completeness recheck and tamper negative control | 10 |
|
||||||
|
| 31 | `840ef98` | forge | 2026-10-24T13:39:55+00:00 | forge: fuzz probe refactor verification - correct line count/filename citations, add negctrl test, commit evidence files | 10 |
|
||||||
|
| 32 | `92745b2` | spark | 2026-10-25T09:37:32+00:00 | volt: Q65 census cycle 3 -- both unrowed verifiers graded by execution, census 103 of 103, and a stale FAIL class re-earned | 10 |
|
||||||
|
| 33 | `6ee8fce` | Volt | 2026-10-17T22:08:19+00:00 | tools (volt): the "could not grade" class gets one code -- rc 2 -- and a gate that measures it on every tool | 9 |
|
||||||
|
| 34 | `f3b178b` | Volt | 2026-10-20T13:54:35+00:00 | Mint forge identities for Beam + the 8 fleet wizards (per-wizard git attribution) | 9 |
|
||||||
|
| 35 | `b1b9d13` | null | 2026-10-23T03:51:47+00:00 | null: Crockford Base32 codec w/ mod-37 check symbol (16/16 cases, 6/6 mutants killed, 23514-value agreement vs PyPI oracle) | 9 |
|
||||||
|
| 36 | `01c6b81` | volt | 2026-10-25T04:36:10+00:00 | volt: q65 residual -- census 99 of 99; the wiring pathspec learns CHECK.txt (one false fault removed) and the four never-graded verifiers are run (3 PASS, 1 FAIL that could never pass) | 9 |
|
||||||
|
| 37 | `170b83a` | null | 2026-10-21T10:34:08+00:00 | null: deporder deterministic DAG build-order planner (42/42 tests, 10/10 mutants caught) | 8 |
|
||||||
|
| 38 | `3e324c4` | Volt | 2026-10-22T05:35:23+00:00 | ledger: add top-10 repos by stars (all languages) extraction artifact | 8 |
|
||||||
|
| 39 | `d81690d` | ledger | 2026-10-23T09:54:14+00:00 | ledger: live evidence-verified wizard roster table (check.sh exit 0) | 8 |
|
||||||
|
| 40 | `aea327c` | forge | 2026-10-23T12:39:44+00:00 | forge: gate_blame DRY refactor - extract _install_checkin_check from 5 hand-duped shutil.copy sites, verify behavior-preservation (6/6 checks pass) | 8 |
|
||||||
|
| 41 | `f3dd5b2` | sphinx | 2026-10-25T01:02:20+00:00 | sphinx: repair the Llama 3 figure-locator falsifier — 24/24 verifier checks, 12/12 mutations caught, note §8 records the three defects | 8 |
|
||||||
|
| 42 | `008bb24` | spark | 2026-10-25T07:26:10+00:00 | volt: q65 residual cycle 2 -- census 101 of 101 by execution; forge's stale FAIL re-earned at its current blob; the GDP verifier's PASS measured as host-pinned with a one-line remedy | 8 |
|
||||||
|
| 43 | `6aaa126` | sphinx | 2026-10-25T08:41:33+00:00 | sphinx: long-context multimodal dossier -- 11 arXiv papers, live-API verified with negative controls | 8 |
|
||||||
|
| 44 | `1d5f634` | Volt | 2026-10-17T08:01:24+00:00 | tools (volt): claim coverage 2 of 9 -> 8 of 9, and the sweep stops tripping the census | 7 |
|
||||||
|
| 45 | `016d26e` | Timmy | 2026-10-17T19:58:45+00:00 | checkin 2026-10-17 (volt): six tools -- unreadable input no longer reads as clean | 7 |
|
||||||
|
| 46 | `b155fb5` | Volt | 2026-10-20T17:36:18+00:00 | ledger: fresh 20 cs.LG arXiv extraction + checklist | 7 |
|
||||||
|
| 47 | `223d941` | null | 2026-10-20T20:50:11+00:00 | null: diffkit - unified-diff parse+apply engine (30/30 checks, 3 mutants killed, 8/8 real git diffs byte-exact) | 7 |
|
||||||
|
| 48 | `64dee6b` | beam | 2026-10-20T21:56:50+00:00 | beam: add bounded retry with exponential backoff + tests | 7 |
|
||||||
|
| 49 | `190d1aa` | Volt | 2026-10-21T19:59:14+00:00 | citations: restore the reply my own commit deleted for a stale count, ledger the rename and the owed deliverable (new TARGET class), re-render TOOLS.md | 7 |
|
||||||
|
| 50 | `5f47bc5` | sphinx | 2026-10-22T17:14:45+00:00 | sphinx: multimodal arXiv archive dossier with live re-fetch verifier (118/120 HTML coverage) | 7 |
|
||||||
|
|
||||||
|
## Totals
|
||||||
|
|
||||||
|
- Total commits: 1601
|
||||||
|
- First commit (root): `65087ea7c2bc642d7a727f40a67893706157db0b`
|
||||||
|
- Last commit: `bc100dc33a59ca54a2cf1bf6a18cca09160ca809`
|
||||||
|
- Earliest date: 2026-09-24T10:30:11+00:00
|
||||||
|
- Latest date: 2026-10-25T13:55:30+00:00
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""CHECK gate for the ledger commit-history extraction (live HEAD).
|
||||||
|
|
||||||
|
Re-derives the full commit-history table from live git at the PINNED HEAD
|
||||||
|
(the `head` field of the committed git-history.json), re-renders the three
|
||||||
|
output files with the identical format as the extraction, and diffs each
|
||||||
|
against the committed file in this directory. Exits 0 only when every check
|
||||||
|
matches exactly.
|
||||||
|
|
||||||
|
Convention: the gate is a re-derivation, not an assertion of a stored number.
|
||||||
|
It runs `git log --name-status` at the pinned head, rebuilds the rows, renders
|
||||||
|
csv/json/md with the same deterministic code the extraction uses, and requires
|
||||||
|
byte-identical output against what was committed.
|
||||||
|
|
||||||
|
Usage: python3 verify_git_history.py <artifact_dir>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import difflib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
REPO = "/root/troupe-standup"
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, HERE)
|
||||||
|
from extract_git_history_live import ( # noqa: E402
|
||||||
|
build_rows,
|
||||||
|
render_csv,
|
||||||
|
render_json,
|
||||||
|
render_md,
|
||||||
|
)
|
||||||
|
|
||||||
|
FILES = ["git-history.csv", "git-history.json", "git-history.md"]
|
||||||
|
|
||||||
|
|
||||||
|
def git(*args):
|
||||||
|
return subprocess.run(
|
||||||
|
["git", "-C", REPO] + list(args), check=True, capture_output=True, text=True
|
||||||
|
).stdout
|
||||||
|
|
||||||
|
|
||||||
|
def read_bytes(name):
|
||||||
|
with open(name, "rb") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
artdir = sys.argv[1] if len(sys.argv) > 1 else "."
|
||||||
|
os.chdir(artdir)
|
||||||
|
|
||||||
|
with open("git-history.json") as f:
|
||||||
|
fixed = json.load(f)
|
||||||
|
head = fixed.get("head")
|
||||||
|
if not head:
|
||||||
|
print("FAIL: committed git-history.json carries no 'head' field")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
rows = build_rows(head)
|
||||||
|
|
||||||
|
ok = True
|
||||||
|
|
||||||
|
def report(name, observed_bytes, expected):
|
||||||
|
nonlocal ok
|
||||||
|
if isinstance(expected, str):
|
||||||
|
expected_bytes = expected.encode("utf-8")
|
||||||
|
else:
|
||||||
|
expected_bytes = expected
|
||||||
|
if observed_bytes == expected_bytes:
|
||||||
|
print("PASS: %s matches re-derivation from git at pinned head %s"
|
||||||
|
% (name, head))
|
||||||
|
else:
|
||||||
|
ok = False
|
||||||
|
print("FAIL: %s differs from re-derivation at pinned head %s"
|
||||||
|
% (name, head))
|
||||||
|
print(" expected bytes: %d, observed bytes: %d"
|
||||||
|
% (len(expected_bytes), len(observed_bytes)))
|
||||||
|
exp_lines = expected_bytes.decode("utf-8", "replace").splitlines()
|
||||||
|
obs_lines = observed_bytes.decode("utf-8", "replace").splitlines()
|
||||||
|
diff = difflib.unified_diff(
|
||||||
|
exp_lines, obs_lines,
|
||||||
|
fromfile="expected@" + head[:8],
|
||||||
|
tofile="committed@" + head[:8],
|
||||||
|
lineterm="",
|
||||||
|
)
|
||||||
|
for line in list(diff)[:12]:
|
||||||
|
print(" " + line)
|
||||||
|
|
||||||
|
# 1) row count
|
||||||
|
if len(rows) == fixed.get("commits"):
|
||||||
|
print("PASS: committed row count %d == re-derived row count %d"
|
||||||
|
% (fixed.get("commits"), len(rows)))
|
||||||
|
else:
|
||||||
|
ok = False
|
||||||
|
print("FAIL: committed row count %s != re-derived %d"
|
||||||
|
% (fixed.get("commits"), len(rows)))
|
||||||
|
|
||||||
|
# 2) CSV
|
||||||
|
report("git-history.csv", read_bytes("git-history.csv"), render_csv(rows))
|
||||||
|
|
||||||
|
# 3) JSON
|
||||||
|
report("git-history.json", read_bytes("git-history.json"), render_json(rows, head))
|
||||||
|
|
||||||
|
# 4) MD
|
||||||
|
report("git-history.md",
|
||||||
|
read_bytes("git-history.md"),
|
||||||
|
render_md(rows, rows_sorted_date(rows)[0]["date"],
|
||||||
|
rows_sorted_date(rows)[-1]["date"]))
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
print("ALL CHECKS PASSED: git-history re-derives byte-identically"
|
||||||
|
" from git at pinned head %s" % head)
|
||||||
|
sys.exit(0)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def rows_sorted_date(rows):
|
||||||
|
return sorted(rows, key=lambda r: r["date"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# GDP Nominal by Country (2026)
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
A table of 2026 nominal GDP (USD, millions) for 2022 economies/territories,
|
||||||
|
pulled from the English Wikipedia "List of countries by GDP (nominal)" page.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
- URL: https://en.wikipedia.org/wiki/List_of_countries_by_GDP_(nominal)
|
||||||
|
(data: IMF 2026 projections [1], World Bank 2025 [6], UN 2024 [7])
|
||||||
|
|
||||||
|
## Columns
|
||||||
|
- `country`: economy/territory label (reference markers like "[n 1]" removed)
|
||||||
|
- `imf_2026`: IMF 2026 nominal GDP, USD millions (commas normalized out)
|
||||||
|
- `wb_2025`: World Bank 2025 nominal GDP, USD millions
|
||||||
|
- `un_2024`: UN 2024 nominal GDP, USD millions
|
||||||
|
|
||||||
|
Cells reported as `n/a` are not available in the source for that year/source.
|
||||||
|
|
||||||
|
## Extraction notes
|
||||||
|
- Values quoted as "552,325 (2024)" were converted to numeric 552325 (year
|
||||||
|
marker dropped); commas normalized.
|
||||||
|
- The source page is edited over time; figures here reflect the page at time
|
||||||
|
of extraction (2026-10-25).
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
- 222 data rows + 1 header row.
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Extract GDP per capita (current US$) for 12 economies from the World Bank API.
|
||||||
|
|
||||||
|
Lane: Extract (data extraction / tables).
|
||||||
|
Source: World Bank API v2, indicator NY.GDP.PCAP.CD (GDP per capita, current US$),
|
||||||
|
country ISO3 codes, per_page=100, newest-first; take the most recent
|
||||||
|
record for each country. Retrieved (UTC): see fetched_at below.
|
||||||
|
|
||||||
|
Outputs (same directory as this script):
|
||||||
|
gdp-per-capita.md - human-readable table (display rounded to 2 dp)
|
||||||
|
gdp-per-capita.csv - machine-readable table (raw values)
|
||||||
|
gdp-per-capita.json- parsed table + provenance
|
||||||
|
|
||||||
|
Verdict (see sibling verify_gdp_per_capita.py): re-fetches the API and diffs
|
||||||
|
the artifact; exit 0 == artifact matches the live API response.
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
from collections import OrderedDict
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
API_BASE = "https://api.worldbank.org/v2/country/{country}/indicator/NY.GDP.PCAP.CD"
|
||||||
|
INDICATOR = "NY.GDP.PCAP.CD"
|
||||||
|
COUNTRIES = OrderedDict([
|
||||||
|
("Australia", "AUS"),
|
||||||
|
("Brazil", "BRA"),
|
||||||
|
("Canada", "CAN"),
|
||||||
|
("China", "CHN"),
|
||||||
|
("France", "FRA"),
|
||||||
|
("Germany", "DEU"),
|
||||||
|
("India", "IND"),
|
||||||
|
("Italy", "ITA"),
|
||||||
|
("Japan", "JPN"),
|
||||||
|
("South Korea", "KOR"),
|
||||||
|
("United Kingdom", "GBR"),
|
||||||
|
("United States", "USA"),
|
||||||
|
])
|
||||||
|
FUNDED = "ledger (Hermes Agent extract lane, Nous free tier)"
|
||||||
|
OUT_DIR = "/root/.hermes/wizard-outputs/ledger"
|
||||||
|
|
||||||
|
|
||||||
|
def fetch(country_iso3):
|
||||||
|
url = "{}?format=json&per_page=100".format(API_BASE.format(country=country_iso3))
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": "ledger-extract/1.0"})
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as r:
|
||||||
|
raw = r.read()
|
||||||
|
try:
|
||||||
|
data = json.loads(raw.decode("utf-8"))
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError("bad JSON for %s: %r" % (country_iso3, raw[:300])) from e
|
||||||
|
results = data[1]
|
||||||
|
if not results:
|
||||||
|
raise RuntimeError("empty results for %s" % country_iso3)
|
||||||
|
r = results[0] # newest-first
|
||||||
|
return r["date"], float(r["value"]), r.get("decimal")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
rows = []
|
||||||
|
provenance = {
|
||||||
|
"source": "https://data.worldbank.org/indicator/NY.GDP.PCAP.CD",
|
||||||
|
"indicator": "NY.GDP.PCAP.CD (GDP per capita, current US$)",
|
||||||
|
"fetched_at": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"countries": {name: iso for name, iso in COUNTRIES.items()},
|
||||||
|
"funded_by": FUNDED,
|
||||||
|
}
|
||||||
|
for name, iso in COUNTRIES.items():
|
||||||
|
yr, val, dec = fetch(iso)
|
||||||
|
provenance.setdefault("decoder", {})[iso] = dec
|
||||||
|
rows.append({"country": name, "year": int(yr), "value": val})
|
||||||
|
|
||||||
|
out = io.StringIO()
|
||||||
|
w = csv.DictWriter(out, fieldnames=["country", "year", "value"])
|
||||||
|
w.writeheader()
|
||||||
|
for r in rows:
|
||||||
|
w.writerow(r)
|
||||||
|
csv_text = out.getvalue()
|
||||||
|
|
||||||
|
enc = json.dumps(provenance, indent=2)
|
||||||
|
with open(OUT_DIR + "/gdp-per-capita.json", "w") as f:
|
||||||
|
f.write(enc + "\n")
|
||||||
|
with open(OUT_DIR + "/gdp-per-capita.csv", "w") as f:
|
||||||
|
f.write(csv_text)
|
||||||
|
|
||||||
|
md = build_md(rows, provenance)
|
||||||
|
with open(OUT_DIR + "/gdp-per-capita.md", "w") as f:
|
||||||
|
f.write(md)
|
||||||
|
|
||||||
|
print("fetched_at: %s" % provenance["fetched_at"])
|
||||||
|
print("rows: %d" % len(rows))
|
||||||
|
print("wrote: "
|
||||||
|
"gdp-per-capita.md gdp-per-capita.csv gdp-per-capita.json")
|
||||||
|
|
||||||
|
|
||||||
|
def build_md(rows, prov):
|
||||||
|
lines = []
|
||||||
|
lines.append("# GDP per capita, current US$ (World Bank, NY.GDP.PCAP.CD)")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("Source: World Bank API `NY.GDP.PCAP.CD`, per-ISO3 newest record,"
|
||||||
|
" fetched `%s`." % prov["fetched_at"])
|
||||||
|
lines.append("Funded by: %s" % prov["funded_by"])
|
||||||
|
lines.append("")
|
||||||
|
lines.append("| Country | GDP per capita (current US$) | Year |")
|
||||||
|
lines.append("|---|---|---|")
|
||||||
|
for r in rows:
|
||||||
|
lines.append("| %s | %.2f | %d |" % (r["country"], r["value"], r["year"]))
|
||||||
|
lines.append("")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Extract-lane artifact: full git commit-history table for /root/troupe-standup,
|
||||||
|
re-derived to the LIVE HEAD (default b1edd09).
|
||||||
|
|
||||||
|
Produces in the given outdir:
|
||||||
|
- git-history.csv (rank, hash, short, name, email, date, subject, files)
|
||||||
|
- git-history.json (structured, machine-readable, head pinned)
|
||||||
|
- git-history.md (human table, top 50 + statistics)
|
||||||
|
- README.md (extraction notes, method, provenance)
|
||||||
|
- CHECK (host-pinned re-derivation gate; exits 0 only on exact match)
|
||||||
|
|
||||||
|
Source of truth: live `git -C /root/troupe-standup log` (current branch `main`).
|
||||||
|
Every claim below is a measured git output, not a guess.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
REPO = "/root/troupe-standup"
|
||||||
|
DEFAULT_HEAD = "b1edd09666bb613b1f48d54de70337472119f110" # live HEAD at 2026-10-25T13:55Z
|
||||||
|
|
||||||
|
|
||||||
|
def git(*args):
|
||||||
|
return subprocess.run(
|
||||||
|
["git", "-C", REPO] + list(args), check=True, capture_output=True, text=True
|
||||||
|
).stdout
|
||||||
|
|
||||||
|
|
||||||
|
META_FMT = "%x01%H%x01%h%x01%an%x01%ae%x01%aI%x01%s"
|
||||||
|
|
||||||
|
|
||||||
|
def build_rows(head):
|
||||||
|
"""Re-derive the full commit-history table at `head`, most-churn first."""
|
||||||
|
meta = git("log", "--format=" + META_FMT, "--name-status", head).splitlines()
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
i = 0
|
||||||
|
while i < len(meta):
|
||||||
|
line = meta[i]
|
||||||
|
if not line.startswith("\x01"):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
fields = line.split("\x01")
|
||||||
|
if len(fields) < 7:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
_, full, short, name, email, date, subject = fields[:7]
|
||||||
|
subject = subject.lstrip("\x01")
|
||||||
|
paths = []
|
||||||
|
i += 1
|
||||||
|
while i < len(meta) and not meta[i].startswith("\x01"):
|
||||||
|
ns = meta[i]
|
||||||
|
if "\t" in ns:
|
||||||
|
paths.append(ns.split("\t", 1)[1])
|
||||||
|
i += 1
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"hash": full,
|
||||||
|
"short": short,
|
||||||
|
"name": name,
|
||||||
|
"email": email,
|
||||||
|
"date": date,
|
||||||
|
"subject": subject,
|
||||||
|
"files": len(set(paths)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Skip blank separators; do not advance past the next commit's meta line.
|
||||||
|
while i < len(meta) and meta[i] == "":
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Extract-lane correction: sort by file-churn descending, date ascending, hash asc.
|
||||||
|
rows.sort(key=lambda r: r["date"])
|
||||||
|
rows.sort(key=lambda r: (-r["files"], r["date"], r["hash"]))
|
||||||
|
for idx, r in enumerate(rows, start=1):
|
||||||
|
r["rank"] = idx
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def render_csv(rows):
|
||||||
|
out = io.StringIO()
|
||||||
|
w = csv.writer(out, lineterminator="\r\n")
|
||||||
|
w.writerow(["rank", "hash", "short", "name", "email", "date", "subject", "files"])
|
||||||
|
for r in rows:
|
||||||
|
w.writerow(
|
||||||
|
[r["rank"], r["hash"], r["short"], r["name"], r["email"], r["date"], r["subject"], r["files"]]
|
||||||
|
)
|
||||||
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def write_csv(rows, path):
|
||||||
|
with open(path, "w", newline="") as f:
|
||||||
|
f.write(render_csv(rows))
|
||||||
|
|
||||||
|
|
||||||
|
def render_json(rows, head):
|
||||||
|
by_date = sorted(rows, key=lambda r: r["date"])
|
||||||
|
earliest, latest = by_date[0]["date"], by_date[-1]["date"]
|
||||||
|
census = [
|
||||||
|
{"name": r["name"], "email": r["email"], "count": sum(1 for q in rows if q["name"] == r["name"] and q["email"] == r["email"])}
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
payload = {
|
||||||
|
"repo": REPO,
|
||||||
|
"commits": len(rows),
|
||||||
|
"earliest": earliest,
|
||||||
|
"latest": latest,
|
||||||
|
"first_commit": rows[-1]["hash"],
|
||||||
|
"head": head,
|
||||||
|
"census": census,
|
||||||
|
"rows": rows,
|
||||||
|
}
|
||||||
|
return json.dumps(payload, indent=2) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def render_md(rows, earliest, latest):
|
||||||
|
top = rows[:50]
|
||||||
|
lines = [
|
||||||
|
"# Troupe repository full commit-history extraction",
|
||||||
|
"",
|
||||||
|
"Source: `git -C /root/troupe-standup log` (current branch `main`).",
|
||||||
|
"Rows: %d commits, %s .. %s (UTC, ISO-strict)." % (len(rows), earliest, latest),
|
||||||
|
"",
|
||||||
|
"Extract-lane correction: `files` = number of distinct paths changed in the commit,",
|
||||||
|
" computed from `git log --name-status`; re-derived and gated in `CHECK`.",
|
||||||
|
"",
|
||||||
|
"## Top 50 by file-churn",
|
||||||
|
"",
|
||||||
|
"| Rank | Hash | Author | Date | Subject | Files |",
|
||||||
|
"| --- | --- | --- | --- | --- | --- |",
|
||||||
|
]
|
||||||
|
for r in top:
|
||||||
|
lines.append("| %d | `%s` | %s | %s | %s | %d |" % (r["rank"], r["short"], r["name"], r["date"], r["subject"], r["files"]))
|
||||||
|
lines += [
|
||||||
|
"",
|
||||||
|
"## Totals",
|
||||||
|
"",
|
||||||
|
"- Total commits: %d" % len(rows),
|
||||||
|
"- First commit (root): `%s`" % rows[-1]["hash"],
|
||||||
|
"- Last commit: `%s`" % rows[0]["hash"],
|
||||||
|
"- Earliest date: %s" % earliest,
|
||||||
|
"- Latest date: %s" % latest,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def write_all(rows, head, outdir):
|
||||||
|
os.makedirs(outdir, exist_ok=True)
|
||||||
|
csv_path = os.path.join(outdir, "git-history.csv")
|
||||||
|
json_path = os.path.join(outdir, "git-history.json")
|
||||||
|
md_path = os.path.join(outdir, "git-history.md")
|
||||||
|
|
||||||
|
write_csv(rows, csv_path)
|
||||||
|
with open(json_path, "w") as f:
|
||||||
|
f.write(render_json(rows, head))
|
||||||
|
with open(md_path, "w") as f:
|
||||||
|
f.write(render_md(rows, rows_sorted_date(rows)[0]["date"], rows_sorted_date(rows)[-1]["date"]))
|
||||||
|
|
||||||
|
# README
|
||||||
|
readme = (
|
||||||
|
"# Ledger: commit-history extraction (live HEAD)\n\n"
|
||||||
|
"Lane: **Extract — data extraction and tables**.\n\n"
|
||||||
|
"Source: live `git -C /root/troupe-standup log` (branch `main`).\n"
|
||||||
|
"Pinned head: `%s` (`git rev-parse HEAD` at capture).\n"
|
||||||
|
"Rows: %d commits, %s .. %s (UTC, ISO-strict).\n\n"
|
||||||
|
"Method: `git log --name-status` per commit; `files` = distinct paths changed;\n"
|
||||||
|
"ordered most file-churn first (ties: oldest date, then hash).\n"
|
||||||
|
"Re-derived and gated in `CHECK`; exits 0 only on exact match.\n\n"
|
||||||
|
"## Files\n\n"
|
||||||
|
"- `git-history.csv` — rank, hash, short, name, email, date, subject, files (1,580 + %d rows).\n"
|
||||||
|
"- `git-history.json` — structured payload (same rows, head pinned).\n"
|
||||||
|
"- `git-history.md` — top-50 table + totals.\n"
|
||||||
|
"- `CHECK` — host-pinned re-derivation gate.\n\n"
|
||||||
|
"Produced by `ledger` on 2026-10-25 (free tier).\n"
|
||||||
|
) % (
|
||||||
|
head,
|
||||||
|
len(rows),
|
||||||
|
rows_sorted_date(rows)[0]["date"],
|
||||||
|
rows_sorted_date(rows)[-1]["date"],
|
||||||
|
len(rows) - 1580,
|
||||||
|
)
|
||||||
|
with open(os.path.join(outdir, "README.md"), "w") as f:
|
||||||
|
f.write(readme)
|
||||||
|
|
||||||
|
return {"csv": csv_path, "json": json_path, "md": md_path}
|
||||||
|
|
||||||
|
|
||||||
|
def rows_sorted_date(rows):
|
||||||
|
return sorted(rows, key=lambda r: r["date"])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
head = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("LIVE_HEAD", DEFAULT_HEAD)
|
||||||
|
outdir = sys.argv[2] if len(sys.argv) > 2 else os.path.join(REPO, "wizard-work/ledger")
|
||||||
|
# outdir may be relative to REPO
|
||||||
|
if not os.path.isabs(outdir):
|
||||||
|
outdir = os.path.join(REPO, outdir)
|
||||||
|
|
||||||
|
rows = build_rows(head)
|
||||||
|
written = write_all(rows, head, outdir)
|
||||||
|
|
||||||
|
print("rows=%d head=%s" % (len(rows), head))
|
||||||
|
print("csv=%s json=%s md=%s" % (written["csv"], written["json"], written["md"]))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
country,year,value
|
||||||
|
Australia,2025,65129.7227990563
|
||||||
|
Brazil,2025,10713.2856869511
|
||||||
|
Canada,2025,55697.6639660837
|
||||||
|
China,2025,13861.970224368
|
||||||
|
France,2025,48985.7307807924
|
||||||
|
Germany,2025,60496.4350820414
|
||||||
|
India,2025,2702.47987141553
|
||||||
|
Italy,2025,43308.6403060733
|
||||||
|
Japan,2025,35951.0449549304
|
||||||
|
South Korea,2025,36226.9663637512
|
||||||
|
United Kingdom,2025,57601.9621201954
|
||||||
|
United States,2025,90026.5163005744
|
||||||
|
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"source": "https://data.worldbank.org/indicator/NY.GDP.PCAP.CD",
|
||||||
|
"indicator": "NY.GDP.PCAP.CD (GDP per capita, current US$)",
|
||||||
|
"fetched_at": "2026-10-25T14:28:24.416415+00:00",
|
||||||
|
"countries": {
|
||||||
|
"Australia": "AUS",
|
||||||
|
"Brazil": "BRA",
|
||||||
|
"Canada": "CAN",
|
||||||
|
"China": "CHN",
|
||||||
|
"France": "FRA",
|
||||||
|
"Germany": "DEU",
|
||||||
|
"India": "IND",
|
||||||
|
"Italy": "ITA",
|
||||||
|
"Japan": "JPN",
|
||||||
|
"South Korea": "KOR",
|
||||||
|
"United Kingdom": "GBR",
|
||||||
|
"United States": "USA"
|
||||||
|
},
|
||||||
|
"funded_by": "ledger (Hermes Agent extract lane, Nous free tier)",
|
||||||
|
"decoder": {
|
||||||
|
"AUS": 1,
|
||||||
|
"BRA": 1,
|
||||||
|
"CAN": 1,
|
||||||
|
"CHN": 1,
|
||||||
|
"FRA": 1,
|
||||||
|
"DEU": 1,
|
||||||
|
"IND": 1,
|
||||||
|
"ITA": 1,
|
||||||
|
"JPN": 1,
|
||||||
|
"KOR": 1,
|
||||||
|
"GBR": 1,
|
||||||
|
"USA": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# GDP per capita, current US$ (World Bank, NY.GDP.PCAP.CD)
|
||||||
|
|
||||||
|
Source: World Bank API `NY.GDP.PCAP.CD`, per-ISO3 newest record, fetched `2026-10-25T14:28:24.416415+00:00`.
|
||||||
|
Funded by: ledger (Hermes Agent extract lane, Nous free tier)
|
||||||
|
|
||||||
|
| Country | GDP per capita (current US$) | Year |
|
||||||
|
|---|---|---|
|
||||||
|
| Australia | 65129.72 | 2025 |
|
||||||
|
| Brazil | 10713.29 | 2025 |
|
||||||
|
| Canada | 55697.66 | 2025 |
|
||||||
|
| China | 13861.97 | 2025 |
|
||||||
|
| France | 48985.73 | 2025 |
|
||||||
|
| Germany | 60496.44 | 2025 |
|
||||||
|
| India | 2702.48 | 2025 |
|
||||||
|
| Italy | 43308.64 | 2025 |
|
||||||
|
| Japan | 35951.04 | 2025 |
|
||||||
|
| South Korea | 36226.97 | 2025 |
|
||||||
|
| United Kingdom | 57601.96 | 2025 |
|
||||||
|
| United States | 90026.52 | 2025 |
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
World,128003776,118350166,111565144
|
||||||
|
United States,32642373,30769700,29298000
|
||||||
|
China,21546403,19498039,18743802
|
||||||
|
Germany,5471299,5050923,4659929
|
||||||
|
Japan,4341986,4435163,4026211
|
||||||
|
United Kingdom,4268446,4002588,3685881
|
||||||
|
India,4060207,3956067,3952244
|
||||||
|
France,3554155,3366316,3160443
|
||||||
|
Russia,2861156,2561310,2173386
|
||||||
|
Italy,2701737,2551557,2380825
|
||||||
|
Brazil,2654761,2279920,2185822
|
||||||
|
Canada,2490256,2319900,2270076
|
||||||
|
South Korea,2245847,1872375,1875388
|
||||||
|
Mexico,2176236,1832641,1852723
|
||||||
|
Australia,2110657,1798519,1830610
|
||||||
|
Spain,2085276,1906453,1722746
|
||||||
|
Turkey,1817987,1597293,1323255
|
||||||
|
Indonesia,1479954,1445643,1396300
|
||||||
|
Netherlands,1423856,1332768,1214928
|
||||||
|
Saudi Arabia,1343346,1276943,1254141
|
||||||
|
Switzerland,1135739,1043530,936564
|
||||||
|
Poland,1133088,1035492,914696
|
||||||
|
Taiwan,1019696,n/a,n/a
|
||||||
|
Belgium,772699,725466,664564
|
||||||
|
Argentina,768206,683098,638365
|
||||||
|
Israel,746982,610778,542286
|
||||||
|
Sweden,745081,668999,604899
|
||||||
|
Ireland,709972,721701,577389
|
||||||
|
Singapore,667889,603870,547387
|
||||||
|
United Arab Emirates,661018,552325,552325
|
||||||
|
Norway,651712,530756,483593
|
||||||
|
Austria,616896,579470,534791
|
||||||
|
Thailand,599115,577010,526518
|
||||||
|
Colombia,572735,457410,418542
|
||||||
|
Malaysia,547100,472193,421972
|
||||||
|
Vietnam,542130,514697,476388
|
||||||
|
Bangladesh,506357,456319,432742
|
||||||
|
Philippines,501692,487086,461618
|
||||||
|
Denmark,499354,462527,424525
|
||||||
|
South Africa,489083,427184,401145
|
||||||
|
Romania,470091,428678,382770
|
||||||
|
Hong Kong,455156,427310,406863
|
||||||
|
Pakistan,450134,407307,379120
|
||||||
|
Egypt,442886,365255,306907
|
||||||
|
Czech Republic,427953,391027,345037
|
||||||
|
Chile,402243,357371,330267
|
||||||
|
Peru,399593,334855,289329
|
||||||
|
Kazakhstan,397921,306239,291480
|
||||||
|
Nigeria,386671,290794,252084
|
||||||
|
Portugal,375197,346640,313271
|
||||||
|
Finland,338564,317039,298697
|
||||||
|
Algeria,331894,287031,266972
|
||||||
|
Iran,311950,362682,448092
|
||||||
|
Greece,304627,280636,257145
|
||||||
|
New Zealand,273395,264057,260172
|
||||||
|
Hungary,265911,246490,222905
|
||||||
|
Cuba,n/a,107352,252063
|
||||||
|
Iraq,245234,254367,279641
|
||||||
|
Ukraine,229406,214233,190741
|
||||||
|
Qatar,203044,215560,219163
|
||||||
|
Morocco,195632,182374,160606
|
||||||
|
Uzbekistan,178167,147038,114965
|
||||||
|
Slovakia,167062,154530,141776
|
||||||
|
Angola,157297,122175,117159
|
||||||
|
Kenya,151151,135941,120340
|
||||||
|
Kuwait,146911,157209,160227
|
||||||
|
Bulgaria,146507,130777,113343
|
||||||
|
Dominican Republic,146362,127407,124282
|
||||||
|
Ecuador,138199,130321,124676
|
||||||
|
Puerto Rico,133222,129368,125842
|
||||||
|
Guatemala,132270,123306,113200
|
||||||
|
Ghana,128638,114210,82938
|
||||||
|
Ethiopia,127145,126359,142275
|
||||||
|
DR Congo,122281,91031,75322
|
||||||
|
Oman,121397,109605,107137
|
||||||
|
Croatia,115915,105060,92662
|
||||||
|
Ivory Coast,112115,99774,85894
|
||||||
|
Serbia,112025,99953,89074
|
||||||
|
Venezuela,111303,99661,150494
|
||||||
|
Luxembourg,110417,101158,93280
|
||||||
|
Costa Rica,109931,102905,95350
|
||||||
|
Sri Lanka,98964,108825,98714
|
||||||
|
Lithuania,105907,95210,84869
|
||||||
|
Belarus,102042,93397,75962
|
||||||
|
Uruguay,96092,85348,80962
|
||||||
|
Panama,95024,90463,86260
|
||||||
|
Tanzania,94889,90143,79236
|
||||||
|
Slovenia,86732,79648,72485
|
||||||
|
Myanmar,83832,81666,64796
|
||||||
|
Turkmenistan,83065,49829,68655
|
||||||
|
Bolivia,80743,64769,46627
|
||||||
|
Azerbaijan,78372,75938,74316
|
||||||
|
Uganda,73370,61986,56369
|
||||||
|
Cameroon,65135,58933,53297
|
||||||
|
Jordan,64909,61610,53352
|
||||||
|
Tunisia,60745,57503,53493
|
||||||
|
Paraguay,60542,49278,44739
|
||||||
|
Zimbabwe,56713,51216,45719
|
||||||
|
Macau,54228,52061,49467
|
||||||
|
Latvia,53686,48619,43684
|
||||||
|
Libya,52453,48099,48484
|
||||||
|
Cambodia,52379,51267,45913
|
||||||
|
Estonia,51634,47031,42765
|
||||||
|
Bahrain,48849,48966,47110
|
||||||
|
Nepal,45844,45490,42692
|
||||||
|
Cyprus,45171,41226,37635
|
||||||
|
Sudan,44688,60163,25574
|
||||||
|
Iceland,43800,38583,33222
|
||||||
|
Georgia,42716,38144,34192
|
||||||
|
Honduras,41505,39601,37094
|
||||||
|
Zambia,41243,28880,25303
|
||||||
|
Senegal,40469,37007,36715
|
||||||
|
El Salvador,39838,36708,35365
|
||||||
|
Haiti,39180,32077,25312
|
||||||
|
Bosnia and Herzegovina,36771,32600,30478
|
||||||
|
Lebanon,34497,25972,37990
|
||||||
|
Papua New Guinea,34403,32499,32262
|
||||||
|
Guyana,33961,27097,24659
|
||||||
|
Mali,33847,30069,26588
|
||||||
|
Albania,33333,30540,27037
|
||||||
|
Burkina Faso,32513,27627,23250
|
||||||
|
Armenia,31873,29243,25955
|
||||||
|
Malta,30712,27772,25030
|
||||||
|
Guinea,29930,28346,36465
|
||||||
|
Mongolia,28450,25369,23586
|
||||||
|
Benin,27786,24566,21483
|
||||||
|
Trinidad and Tobago,26836,25943,25634
|
||||||
|
Chad,25628,21473,19906
|
||||||
|
Niger,24813,21646,17491
|
||||||
|
Nicaragua,24227,22237,19694
|
||||||
|
Kyrgyzstan,23606,22624,17478
|
||||||
|
Gabon,23363,21427,20867
|
||||||
|
Mozambique,23275,22338,22745
|
||||||
|
Jamaica,23028,22705,22014
|
||||||
|
Botswana,21937,19928,19401
|
||||||
|
Moldova,21889,20352,18200
|
||||||
|
North Macedonia,21605,19101,16952
|
||||||
|
Madagascar,21185,19620,17421
|
||||||
|
Tajikistan,20418,17661,13949
|
||||||
|
Afghanistan,19662,17779,18283
|
||||||
|
Laos,18959,18303,16129
|
||||||
|
Rwanda,18673,16372,15111
|
||||||
|
Malawi,18309,14918,11734
|
||||||
|
Namibia,17314,15080,13372
|
||||||
|
Syria,60043,23738,17254
|
||||||
|
North Korea,n/a,n/a,17199
|
||||||
|
Palestine,16017,17167,16017
|
||||||
|
Mauritius,17119,16158,14953
|
||||||
|
Bahamas,17042,15833,15833
|
||||||
|
Congo,17028,16307,16412
|
||||||
|
Brunei,16863,15032,15463
|
||||||
|
Mauritania,14352,11680,10912
|
||||||
|
Somalia,14174,12995,11966
|
||||||
|
Kosovo,14053,12456,11154
|
||||||
|
Equatorial Guinea,13722,12823,12288
|
||||||
|
Togo,13437,11890,10163
|
||||||
|
Channel Islands,n/a,12507,n/a
|
||||||
|
Monaco,n/a,11126,11126
|
||||||
|
Montenegro,10227,9233,8274
|
||||||
|
Liechtenstein,9442,8906,8628
|
||||||
|
Bermuda,n/a,9194,8840
|
||||||
|
New Caledonia,n/a,8549,8549
|
||||||
|
Barbados,8483,8017,7598
|
||||||
|
Sierra Leone,8270,7464,6792
|
||||||
|
Burundi,8137,3365,4431
|
||||||
|
Maldives,8130,7741,7062
|
||||||
|
Cayman Islands,n/a,7765,7657
|
||||||
|
Isle of Man,n/a,7576,n/a
|
||||||
|
Yemen,7435,21606,14304
|
||||||
|
Guam,n/a,6910,n/a
|
||||||
|
Fiji,6352,6198,5968
|
||||||
|
French Polynesia,n/a,6324,6323
|
||||||
|
South Sudan,6069,11998,3522
|
||||||
|
Suriname,5908,4524,4435
|
||||||
|
Eswatini,5792,5161,4947
|
||||||
|
Liberia,5642,5246,5251
|
||||||
|
Andorra,4879,4501,4040
|
||||||
|
Djibouti,4725,4625,4152
|
||||||
|
U.S. Virgin Islands,n/a,4672,n/a
|
||||||
|
Aruba,4671,4168,4192
|
||||||
|
Faroe Islands,n/a,4053,n/a
|
||||||
|
Bhutan,3856,3579,3347
|
||||||
|
Curaçao,n/a,3561,3561
|
||||||
|
Central African Republic,3492,3066,2752
|
||||||
|
Greenland,n/a,3327,3466
|
||||||
|
Belize,3450,3327,3204
|
||||||
|
Cape Verde,3448,3057,2768
|
||||||
|
Guinea-Bissau,2985,2528,2008
|
||||||
|
Lesotho,2972,2574,2391
|
||||||
|
Gambia,2792,2594,2372
|
||||||
|
Saint Lucia,2765,2656,2574
|
||||||
|
Zanzibar,n/a,n/a,2530
|
||||||
|
San Marino,2417,2027,2082
|
||||||
|
Antigua and Barbuda,2385,2338,2162
|
||||||
|
Seychelles,2251,2387,2162
|
||||||
|
Timor-Leste,2170,1902,1866
|
||||||
|
Eritrea,1982,2065,1917
|
||||||
|
Sint Maarten,n/a,1888,1670
|
||||||
|
Solomon Islands,1836,1750,1584
|
||||||
|
British Virgin Islands,n/a,n/a,1830
|
||||||
|
Comoros,1814,1815,1567
|
||||||
|
Turks and Caicos Islands,n/a,1745,1757
|
||||||
|
Grenada,1483,1420,1372
|
||||||
|
Vanuatu,1396,1354,1092
|
||||||
|
Samoa,1376,1288,1253
|
||||||
|
Saint Vincent and the Grenadines,1236,1255,1136
|
||||||
|
São Tomé and Príncipe,1161,981,828
|
||||||
|
Saint Kitts and Nevis,1143,1184,1122
|
||||||
|
Northern Mariana Islands,n/a,1096,n/a
|
||||||
|
American Samoa,n/a,871,n/a
|
||||||
|
Dominica,791,724,689
|
||||||
|
Tonga,716,679,648
|
||||||
|
Saint Martin,n/a,649,n/a
|
||||||
|
Federated States of Micronesia,521,502,471
|
||||||
|
Anguilla,n/a,n/a,456
|
||||||
|
Cook Islands,n/a,n/a,414
|
||||||
|
Kiribati,401,349,343
|
||||||
|
Palau,377,345,310
|
||||||
|
Marshall Islands,342,308,281
|
||||||
|
Nauru,196,176,187
|
||||||
|
Montserrat,n/a,n/a,81
|
||||||
|
Tuvalu,65,57,56
|
||||||
|
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify the GDP-per-capita artifact against a fresh World Bank API fetch.
|
||||||
|
|
||||||
|
Re-queries the API for the same 12 economies and diffs the artifact. Exit 0
|
||||||
|
== artifact matches the live API response; exit 1 == mismatch.
|
||||||
|
|
||||||
|
Evidence (this host, fresh API pull):
|
||||||
|
check: python3 verify_gdp_per_capita.py
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
INDICATOR = "NY.GDP.PCAP.CD"
|
||||||
|
COUNTRIES = {"Australia": "AUS", "Brazil": "BRA", "Canada": "CAN", "China": "CHN",
|
||||||
|
"France": "FRA", "Germany": "DEU", "India": "IND", "Italy": "ITA",
|
||||||
|
"Japan": "JPN", "South Korea": "KOR", "United Kingdom": "GBR",
|
||||||
|
"United States": "USA"}
|
||||||
|
OUT_DIR = "/root/troupe-standup/wizard-work/ledger"
|
||||||
|
|
||||||
|
|
||||||
|
def live_value(alpha3):
|
||||||
|
url = ("https://api.worldbank.org/v2/country/{}/indicator/{}"
|
||||||
|
"?format=json&per_page=100").format(alpha3, INDICATOR)
|
||||||
|
with urllib.request.urlopen(url, timeout=30) as r:
|
||||||
|
data = json.load(r)
|
||||||
|
results = data[1]
|
||||||
|
r = results[0] # newest-first
|
||||||
|
return float(r["value"]), int(r["date"])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
failures = []
|
||||||
|
# 1) artifact values vs live API values (2 dp)
|
||||||
|
with open(OUT_DIR + "/gdp-per-capita.csv") as f:
|
||||||
|
art = {row["country"]: (float(row["value"]), int(row["year"]))
|
||||||
|
for row in csv.DictReader(f)}
|
||||||
|
|
||||||
|
for name, iso in COUNTRIES.items():
|
||||||
|
live, live_year = live_value(iso)
|
||||||
|
got, got_year = art.get(name, (None, None))
|
||||||
|
if got is None:
|
||||||
|
failures.append("%s: missing from artifact" % name)
|
||||||
|
continue
|
||||||
|
if round(got, 2) != round(live, 2) or got_year != live_year:
|
||||||
|
failures.append("%s: artifact %.2f/%d vs live %.2f/%d"
|
||||||
|
% (name, got, got_year, live, live_year))
|
||||||
|
|
||||||
|
# 2) row count of the table file (data rows only: lines beginning with a
|
||||||
|
# known country, which excludes the header "| Country ..." and the
|
||||||
|
# separator "|---|---|---|").
|
||||||
|
with open(OUT_DIR + "/gdp-per-capita.md") as f:
|
||||||
|
md = f.read()
|
||||||
|
countries = set(COUNTRIES)
|
||||||
|
data_rows = [l for l in md.splitlines()
|
||||||
|
if any(l.startswith("| " + c) for c in countries)]
|
||||||
|
n_rows = len(data_rows)
|
||||||
|
if n_rows != len(COUNTRIES):
|
||||||
|
failures.append("md data-row count %d != %d" % (n_rows, len(COUNTRIES)))
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
print("FAIL (%d)" % len(failures))
|
||||||
|
for x in failures:
|
||||||
|
print(" -", x)
|
||||||
|
return 1
|
||||||
|
print("OK: 12/12 economies match the live API; md table row count 12/12.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in new issue
Block a user