ledgerworks 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. ledgerworks-0.2.0/.gitignore +13 -0
  2. ledgerworks-0.2.0/LICENSE +21 -0
  3. ledgerworks-0.2.0/PKG-INFO +184 -0
  4. ledgerworks-0.2.0/README.md +147 -0
  5. ledgerworks-0.2.0/examples/codecheck/README.md +220 -0
  6. ledgerworks-0.2.0/examples/codecheck/data/raw/README.md +12 -0
  7. ledgerworks-0.2.0/examples/codecheck/expected/README.md +10 -0
  8. ledgerworks-0.2.0/examples/codecheck/expected/fomc_embeddings/README.md +82 -0
  9. ledgerworks-0.2.0/examples/codecheck/expected/fomc_narrative_scores/README.md +82 -0
  10. ledgerworks-0.2.0/examples/codecheck/expected/fomc_statements/README.md +46 -0
  11. ledgerworks-0.2.0/examples/codecheck/pyproject.toml +67 -0
  12. ledgerworks-0.2.0/examples/codecheck/tests/__init__.py +0 -0
  13. ledgerworks-0.2.0/examples/codecheck/tests/conftest.py +166 -0
  14. ledgerworks-0.2.0/examples/codecheck/tests/test_fetch.py +81 -0
  15. ledgerworks-0.2.0/examples/codecheck/tests/test_model_backend.py +109 -0
  16. ledgerworks-0.2.0/examples/codecheck/tests/test_pipeline.py +206 -0
  17. ledgerworks-0.2.0/pyproject.toml +77 -0
  18. ledgerworks-0.2.0/src/ledgerworks/__init__.py +71 -0
  19. ledgerworks-0.2.0/src/ledgerworks/artifact.py +396 -0
  20. ledgerworks-0.2.0/src/ledgerworks/cli.py +439 -0
  21. ledgerworks-0.2.0/src/ledgerworks/equivalence.py +554 -0
  22. ledgerworks-0.2.0/src/ledgerworks/jobs.py +811 -0
  23. ledgerworks-0.2.0/src/ledgerworks/jobs_cli.py +171 -0
  24. ledgerworks-0.2.0/src/ledgerworks/layout.py +158 -0
  25. ledgerworks-0.2.0/src/ledgerworks/ledger.py +908 -0
  26. ledgerworks-0.2.0/src/ledgerworks/lineage.py +87 -0
  27. ledgerworks-0.2.0/src/ledgerworks/meta.py +419 -0
  28. ledgerworks-0.2.0/src/ledgerworks/periods.py +229 -0
  29. ledgerworks-0.2.0/src/ledgerworks/plugins.py +49 -0
  30. ledgerworks-0.2.0/src/ledgerworks/relink.py +460 -0
  31. ledgerworks-0.2.0/src/ledgerworks/schema.sql +85 -0
  32. ledgerworks-0.2.0/src/ledgerworks/verify.py +362 -0
  33. ledgerworks-0.2.0/tests/__init__.py +0 -0
  34. ledgerworks-0.2.0/tests/test_cli.py +38 -0
  35. ledgerworks-0.2.0/tests/test_groups.py +262 -0
  36. ledgerworks-0.2.0/tests/test_jobs.py +322 -0
  37. ledgerworks-0.2.0/tests/test_jobs_cli.py +182 -0
  38. ledgerworks-0.2.0/tests/test_layout.py +72 -0
  39. ledgerworks-0.2.0/tests/test_ledger.py +1009 -0
  40. ledgerworks-0.2.0/tests/test_lineage.py +49 -0
  41. ledgerworks-0.2.0/tests/test_periods.py +117 -0
  42. ledgerworks-0.2.0/tests/test_plugins.py +40 -0
  43. ledgerworks-0.2.0/tests/test_relink.py +610 -0
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .env
10
+ # example working directories (the committed inputs and expected outputs stay)
11
+ examples/*/work/
12
+ examples/*/ledger/
13
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gabin Taibi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.5
2
+ Name: ledgerworks
3
+ Version: 0.2.0
4
+ Summary: Run, record, verify and trace pipeline artifacts: immutable outputs with provenance sidecars, lineage and a rebuildable index
5
+ Project-URL: Homepage, https://github.com/GabinTB/ledgerworks
6
+ Project-URL: Repository, https://github.com/GabinTB/ledgerworks
7
+ Project-URL: Issues, https://github.com/GabinTB/ledgerworks/issues
8
+ Author: Gabin Taibi
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: artifacts,data-management,lineage,provenance,reproducibility,research
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Classifier: Topic :: Software Development :: Version Control
21
+ Requires-Python: >=3.11
22
+ Provides-Extra: all
23
+ Requires-Dist: numpy>=1.26; extra == 'all'
24
+ Requires-Dist: pandas>=2.0; extra == 'all'
25
+ Requires-Dist: polars>=0.20; extra == 'all'
26
+ Requires-Dist: pyarrow>=14.0; extra == 'all'
27
+ Requires-Dist: python-dotenv>=1.0; extra == 'all'
28
+ Provides-Extra: cli
29
+ Requires-Dist: python-dotenv>=1.0; extra == 'cli'
30
+ Provides-Extra: pandas
31
+ Requires-Dist: pandas>=2.0; extra == 'pandas'
32
+ Provides-Extra: relink
33
+ Requires-Dist: numpy>=1.26; extra == 'relink'
34
+ Requires-Dist: polars>=0.20; extra == 'relink'
35
+ Requires-Dist: pyarrow>=14.0; extra == 'relink'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # ledgerworks
39
+
40
+ Run, record, verify and trace pipeline artifacts.
41
+
42
+ Every output of a pipeline becomes an immutable artifact directory with a
43
+ `meta.json` sidecar that says what it is, what it was built from, which code
44
+ and model produced it, and what every file hashed to when it was written.
45
+ A SQLite index makes lookups fast and can always be rebuilt from the sidecars,
46
+ so the files are the source of truth and the index is a cache.
47
+
48
+ | | |
49
+ |---|---|
50
+ | PyPI | `pip install ledgerworks` |
51
+ | Python | >= 3.11 |
52
+ | Core dependencies | none (stdlib only) |
53
+ | License | MIT |
54
+
55
+ ## What it does
56
+
57
+ | Layer | What you get |
58
+ |---|---|
59
+ | **Record** | A deterministic artifact id (`{kind}__{model}__{version}__{params}__{minute}`), a `meta.json` and `README.md` sidecar per artifact, one `RunRecord` per execution (start, end, git commit, files produced, Python, platform, `uv.lock` digest and direct dependency versions), blake2b hash and size of every output file |
60
+ | **Query** | `latest(kind)`, `get(id)`, `list(...)`, `parents` / `children` / `ancestors` / `descendants`, DuckDB views by kind |
61
+ | **Verify** | Re-hash files, flag crashed (partial) runs and stray temp files, find lineage edges that point to nothing, run per-kind content checks you register |
62
+ | **Run** | `ledgerworks.jobs`: long pipelines as ordered work units with resume, update, pause, a heartbeat lock and a status file |
63
+ | **Migrate** | `ledgerworks.relink`: re-register an artifact's files under new inputs or new code without recomputing, after equivalence checks (needs the `relink` extra) |
64
+
65
+ Guarantees that hold everywhere:
66
+
67
+ - a run is **partial** until it exits cleanly, and `latest()` never returns a partial artifact;
68
+ - nothing is mutated or deleted: new data appends an execution, a wrong artifact is deprecated;
69
+ - the index holds nothing the sidecars lack, so `reindex` recovers it from disk at any time.
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ pip install ledgerworks # core, stdlib only
75
+ pip install "ledgerworks[cli]" # + .env support for the command lines
76
+ pip install "ledgerworks[relink]" # + numpy / polars / pyarrow for relink checks
77
+ pip install "ledgerworks[all]"
78
+ ```
79
+
80
+ For development:
81
+
82
+ ```bash
83
+ git clone https://github.com/GabinTB/ledgerworks
84
+ cd ledgerworks
85
+ uv sync --group dev
86
+ uv run pytest
87
+ ```
88
+
89
+ ## Quick start
90
+
91
+ ```python
92
+ from ledgerworks import Ledger, ModelCard
93
+
94
+ ledger = Ledger("/data/ledger")
95
+
96
+ # 1. register a raw input
97
+ with ledger.run(kind="documents", pipeline="my-project", pipeline_version="v0.1.0",
98
+ hyperparams={"source": "fomc", "start": "2021-01-01"},
99
+ layer="raw") as run:
100
+ write_parquet(run.out_dir / "documents.parquet", docs)
101
+
102
+ # 2. derive something from it, with lineage and a model card
103
+ source = ledger.latest("documents")
104
+ card = ModelCard(model_id="my-embedder", version="1.0", repo="https://hf.co/...",
105
+ weights_public=True, dim=768)
106
+
107
+ with ledger.run(kind="document_embeddings", pipeline="my-project", pipeline_version="v0.1.0",
108
+ hyperparams={"pooling": "mean"}, sources=[source], model_card=card,
109
+ verifier="document_embeddings") as run:
110
+ embed(source.path, run.out_dir)
111
+
112
+ # 3. query
113
+ art = ledger.latest("document_embeddings")
114
+ print(art.artifact_id, art.meta.pipeline_commit, art.file_hashes)
115
+ print(ledger.ancestors(art.artifact_id))
116
+ ```
117
+
118
+ On clean exit the run's record is marked complete, the outputs are hashed and
119
+ the sidecars written. On an exception the record stays partial, so the crash is
120
+ visible instead of silent.
121
+
122
+ The artifact directory:
123
+
124
+ ```
125
+ {root}/derived/document_embeddings/document_embeddings__my-embedder-1.0__v0.1.0__poolingmean__20261014T1530/
126
+ meta.json # the record: identity, hyperparams, sources, model card, runs[], file hashes
127
+ README.md # the same, rendered for humans
128
+ 2021-01.parquet
129
+ ...
130
+ ```
131
+
132
+ ## Command lines
133
+
134
+ ```
135
+ ledgerworks init | ls | show ID | verify | lineage ID | deprecate ID --reason ... | reindex | move | relink
136
+ ledgerworks-jobs start KIND | resume ID | update ID | pause ID | status | list | check ID
137
+ ```
138
+
139
+ The root comes from `--root`, else `$LEDGER_ROOT` (also read from `.env` when
140
+ the `cli` extra is installed).
141
+
142
+ ## Plugging in your own kinds
143
+
144
+ A package contributes jobs, content verifiers and equivalence checks through
145
+ entry points in its `pyproject.toml`:
146
+
147
+ ```toml
148
+ [project.entry-points."ledgerworks.jobs"]
149
+ document_embeddings = "my_project.embed:EmbedJob"
150
+
151
+ [project.entry-points."ledgerworks.verifiers"]
152
+ document_embeddings = "my_project.embed:verify_artifact"
153
+ ```
154
+
155
+ A `Job` declares what it computes (`kind`, `pipeline`, `pipeline_version`,
156
+ `params()`, `sources()`, `units()`, `run_unit()`); `JobRunner` owns how it
157
+ runs. See `ledgerworks/jobs.py` for the contract and `examples/codecheck` for
158
+ a complete job.
159
+
160
+ ## Examples
161
+
162
+ - [`examples/codecheck`](examples/codecheck): a self-contained reproducibility
163
+ package (FOMC statements, an open embedding model, a macro-narrative
164
+ taxonomy) submitted to the University of Twente CodeCheck 2026. Run it with
165
+ one command and compare the outputs to the committed reference.
166
+ Reproduce it: `cd examples/codecheck && uv sync --extra model && uv run codecheck-fomc all`
167
+ (see [`examples/codecheck/README.md`](examples/codecheck/README.md)).
168
+
169
+ ## Development
170
+
171
+ ```bash
172
+ uv run pytest # tests
173
+ uv run ruff check . # lint
174
+ uv run ruff format . # format
175
+ uv build # wheel + sdist
176
+ ```
177
+
178
+ Releases: push a tag `vX.Y.Z` matching the version in `pyproject.toml`. The
179
+ release workflow runs CI, publishes to TestPyPI, installs from there as a
180
+ smoke test, then publishes to PyPI and creates a GitHub release.
181
+
182
+ ## License
183
+
184
+ MIT
@@ -0,0 +1,147 @@
1
+ # ledgerworks
2
+
3
+ Run, record, verify and trace pipeline artifacts.
4
+
5
+ Every output of a pipeline becomes an immutable artifact directory with a
6
+ `meta.json` sidecar that says what it is, what it was built from, which code
7
+ and model produced it, and what every file hashed to when it was written.
8
+ A SQLite index makes lookups fast and can always be rebuilt from the sidecars,
9
+ so the files are the source of truth and the index is a cache.
10
+
11
+ | | |
12
+ |---|---|
13
+ | PyPI | `pip install ledgerworks` |
14
+ | Python | >= 3.11 |
15
+ | Core dependencies | none (stdlib only) |
16
+ | License | MIT |
17
+
18
+ ## What it does
19
+
20
+ | Layer | What you get |
21
+ |---|---|
22
+ | **Record** | A deterministic artifact id (`{kind}__{model}__{version}__{params}__{minute}`), a `meta.json` and `README.md` sidecar per artifact, one `RunRecord` per execution (start, end, git commit, files produced, Python, platform, `uv.lock` digest and direct dependency versions), blake2b hash and size of every output file |
23
+ | **Query** | `latest(kind)`, `get(id)`, `list(...)`, `parents` / `children` / `ancestors` / `descendants`, DuckDB views by kind |
24
+ | **Verify** | Re-hash files, flag crashed (partial) runs and stray temp files, find lineage edges that point to nothing, run per-kind content checks you register |
25
+ | **Run** | `ledgerworks.jobs`: long pipelines as ordered work units with resume, update, pause, a heartbeat lock and a status file |
26
+ | **Migrate** | `ledgerworks.relink`: re-register an artifact's files under new inputs or new code without recomputing, after equivalence checks (needs the `relink` extra) |
27
+
28
+ Guarantees that hold everywhere:
29
+
30
+ - a run is **partial** until it exits cleanly, and `latest()` never returns a partial artifact;
31
+ - nothing is mutated or deleted: new data appends an execution, a wrong artifact is deprecated;
32
+ - the index holds nothing the sidecars lack, so `reindex` recovers it from disk at any time.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install ledgerworks # core, stdlib only
38
+ pip install "ledgerworks[cli]" # + .env support for the command lines
39
+ pip install "ledgerworks[relink]" # + numpy / polars / pyarrow for relink checks
40
+ pip install "ledgerworks[all]"
41
+ ```
42
+
43
+ For development:
44
+
45
+ ```bash
46
+ git clone https://github.com/GabinTB/ledgerworks
47
+ cd ledgerworks
48
+ uv sync --group dev
49
+ uv run pytest
50
+ ```
51
+
52
+ ## Quick start
53
+
54
+ ```python
55
+ from ledgerworks import Ledger, ModelCard
56
+
57
+ ledger = Ledger("/data/ledger")
58
+
59
+ # 1. register a raw input
60
+ with ledger.run(kind="documents", pipeline="my-project", pipeline_version="v0.1.0",
61
+ hyperparams={"source": "fomc", "start": "2021-01-01"},
62
+ layer="raw") as run:
63
+ write_parquet(run.out_dir / "documents.parquet", docs)
64
+
65
+ # 2. derive something from it, with lineage and a model card
66
+ source = ledger.latest("documents")
67
+ card = ModelCard(model_id="my-embedder", version="1.0", repo="https://hf.co/...",
68
+ weights_public=True, dim=768)
69
+
70
+ with ledger.run(kind="document_embeddings", pipeline="my-project", pipeline_version="v0.1.0",
71
+ hyperparams={"pooling": "mean"}, sources=[source], model_card=card,
72
+ verifier="document_embeddings") as run:
73
+ embed(source.path, run.out_dir)
74
+
75
+ # 3. query
76
+ art = ledger.latest("document_embeddings")
77
+ print(art.artifact_id, art.meta.pipeline_commit, art.file_hashes)
78
+ print(ledger.ancestors(art.artifact_id))
79
+ ```
80
+
81
+ On clean exit the run's record is marked complete, the outputs are hashed and
82
+ the sidecars written. On an exception the record stays partial, so the crash is
83
+ visible instead of silent.
84
+
85
+ The artifact directory:
86
+
87
+ ```
88
+ {root}/derived/document_embeddings/document_embeddings__my-embedder-1.0__v0.1.0__poolingmean__20261014T1530/
89
+ meta.json # the record: identity, hyperparams, sources, model card, runs[], file hashes
90
+ README.md # the same, rendered for humans
91
+ 2021-01.parquet
92
+ ...
93
+ ```
94
+
95
+ ## Command lines
96
+
97
+ ```
98
+ ledgerworks init | ls | show ID | verify | lineage ID | deprecate ID --reason ... | reindex | move | relink
99
+ ledgerworks-jobs start KIND | resume ID | update ID | pause ID | status | list | check ID
100
+ ```
101
+
102
+ The root comes from `--root`, else `$LEDGER_ROOT` (also read from `.env` when
103
+ the `cli` extra is installed).
104
+
105
+ ## Plugging in your own kinds
106
+
107
+ A package contributes jobs, content verifiers and equivalence checks through
108
+ entry points in its `pyproject.toml`:
109
+
110
+ ```toml
111
+ [project.entry-points."ledgerworks.jobs"]
112
+ document_embeddings = "my_project.embed:EmbedJob"
113
+
114
+ [project.entry-points."ledgerworks.verifiers"]
115
+ document_embeddings = "my_project.embed:verify_artifact"
116
+ ```
117
+
118
+ A `Job` declares what it computes (`kind`, `pipeline`, `pipeline_version`,
119
+ `params()`, `sources()`, `units()`, `run_unit()`); `JobRunner` owns how it
120
+ runs. See `ledgerworks/jobs.py` for the contract and `examples/codecheck` for
121
+ a complete job.
122
+
123
+ ## Examples
124
+
125
+ - [`examples/codecheck`](examples/codecheck): a self-contained reproducibility
126
+ package (FOMC statements, an open embedding model, a macro-narrative
127
+ taxonomy) submitted to the University of Twente CodeCheck 2026. Run it with
128
+ one command and compare the outputs to the committed reference.
129
+ Reproduce it: `cd examples/codecheck && uv sync --extra model && uv run codecheck-fomc all`
130
+ (see [`examples/codecheck/README.md`](examples/codecheck/README.md)).
131
+
132
+ ## Development
133
+
134
+ ```bash
135
+ uv run pytest # tests
136
+ uv run ruff check . # lint
137
+ uv run ruff format . # format
138
+ uv build # wheel + sdist
139
+ ```
140
+
141
+ Releases: push a tag `vX.Y.Z` matching the version in `pyproject.toml`. The
142
+ release workflow runs CI, publishes to TestPyPI, installs from there as a
143
+ smoke test, then publishes to PyPI and creates a GitHub release.
144
+
145
+ ## License
146
+
147
+ MIT
@@ -0,0 +1,220 @@
1
+ # CodeCheck 2026: FOMC statements scored against a monetary-policy narrative taxonomy
2
+
3
+ A self-contained reproducibility package, submitted to the University of Twente
4
+ CodeCheck / Reproducibility Hackathon (20 October 2026). It is one slice of an
5
+ ongoing PhD project on narrative dynamics in financial markets (Gabin Taibi,
6
+ University of Twente, BMS-IEBIS), rebuilt on open data and an open model so
7
+ that every step can be re-run and checked by a reviewer.
8
+
9
+ **What is reproduced:** the two computational steps at the core of the research
10
+ pipeline, (1) embedding a corpus of documents with a finance-adapted language
11
+ model and (2) scoring every document against a taxonomy of narratives, applied
12
+ to FOMC statements. The outputs are the embedding tables and the score tables
13
+ under `expected/`. There is no paper table to match: this is the "ongoing
14
+ project" route of the CodeCheck call, and the research taxonomy itself is not
15
+ published here (the file in `taxonomy/` is a public stand-in with the same
16
+ structure).
17
+
18
+ **What the reviewer checks:** that `codecheck-fomc all` rebuilds, from the files
19
+ in this directory, outputs equal to `expected/` within the stated tolerances,
20
+ and that every artifact's provenance record (`meta.json`) is intact. The
21
+ package is also the end-to-end example of
22
+ [`ledgerworks`](../../README.md), the artifact-provenance library the project
23
+ is built on.
24
+
25
+ ## Quick start
26
+
27
+ Requirements: Python >= 3.11, [uv](https://docs.astral.sh/uv/), about 2.7 GB of
28
+ disk (measured on macOS, Apple silicon: 1.2 GB for `.venv`, 1.5 GB for the model
29
+ in the Hugging Face cache), a network connection the first time (to
30
+ download the model from the Hugging Face Hub). CPU is enough. On Linux, `uv.lock`
31
+ takes torch from the PyTorch CPU index (`2.14.1+cpu`), not PyPI, whose Linux
32
+ wheel pulls several GB of CUDA libraries a CPU run never loads; macOS and
33
+ Windows use PyPI's torch, which is CPU-only there.
34
+
35
+ ```bash
36
+ git clone https://github.com/GabinTB/ledgerworks
37
+ cd ledgerworks/examples/codecheck
38
+ uv sync --extra model # creates .venv from uv.lock (pinned)
39
+ uv run codecheck-fomc all # ingest, embed, score, verify, compare
40
+ ```
41
+
42
+ The corpus is 46 statements from 2021-01-27 to 2026-09-16, split into 190
43
+ paragraphs. Measured runtime on an Apple M4 Pro (CPU), model already cached:
44
+ about 15 seconds wall clock, almost all of it in the embedding step (a
45
+ 395M-parameter model over 190 paragraphs). The first run also downloads the
46
+ model weights (1.6 GB). The last lines of the output are the comparison with
47
+ `expected/` and a verdict (here, on the machine that produced `expected/`):
48
+
49
+ ```
50
+ fomc_narrative_scores: ...
51
+ ok fomc_narrative_scores/2021.parquet: 80 scores, max|diff| 0.00e+00
52
+ ...
53
+ ok fomc_narrative_scores/2026.parquet: 60 scores, max|diff| 0.00e+00
54
+ ok fomc_narrative_scores/narratives.parquet: 10 vectors, max|diff| 0.00e+00, min cosine 1.000000
55
+ 7/7 files hash-identical to the reference
56
+
57
+ REPRODUCED
58
+ ```
59
+
60
+ The score tables hold 460 rows in total (46 statements × 10 narratives).
61
+
62
+ `uv run codecheck-fomc --help` lists the individual steps. Everything lands in
63
+ `./ledger/` (deleted and rebuilt freely; it is in `.gitignore`).
64
+
65
+ To check the plumbing without the model (seconds, no download):
66
+
67
+ ```bash
68
+ uv sync
69
+ uv run codecheck-fomc --backend hashing all # NOT REPRODUCED is expected here:
70
+ # expected/ was produced with the real model
71
+ uv run pytest # offline tests on a fixture corpus
72
+ ```
73
+
74
+ ## What is in the box
75
+
76
+ | Path | What | Who produced it |
77
+ |---|---|---|
78
+ | `data/raw/html/*.htm` | The FOMC statement pages, byte for byte as served by federalreserve.gov | author, with `fetch_fomc.py` |
79
+ | `data/raw/MANIFEST.json` | URL, date, SHA-256 and retrieval time of every page | `fetch_fomc.py` |
80
+ | `taxonomy/monetary_policy.yaml` | Ten monetary-policy narratives: label, description, seed phrases | author |
81
+ | `codecheck_fomc/` | The pipeline: three `ledgerworks` jobs, the embedding backends, the comparison | author |
82
+ | `expected/` | The reference run: the three artifact directories with their `meta.json` | author, with `codecheck-fomc freeze` |
83
+ | `uv.lock` | Every Python dependency pinned, model stack included | `uv` |
84
+ | `tests/` | Offline tests of the whole pipeline on a fixture corpus (hashing backend) | author |
85
+
86
+ ### Data
87
+
88
+ FOMC statements from January 2021 to the retrieval date, one HTML page per
89
+ meeting statement (other FOMC press releases, such as the August 2025 update of
90
+ the Statement on Longer-Run Goals and Monetary Policy Strategy, are excluded), downloaded from the Board's
91
+ [FOMC calendars page](https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm).
92
+ Content on the Board's website is in the public domain and may be copied and
93
+ distributed without permission
94
+ ([disclaimer](https://www.federalreserve.gov/disclaimer.htm)); the Board of
95
+ Governors of the Federal Reserve System is the source. The committed HTML is the
96
+ immutable input: parsing it into a table is the first job of the pipeline, so
97
+ it is recorded and re-run like every other step. `fetch_fomc.py` is kept for
98
+ provenance; the reviewer does not need to run it.
99
+
100
+ ### Model
101
+
102
+ [`BatuhanECB/FinModernBERT-embed-large-v1`](https://huggingface.co/BatuhanECB/FinModernBERT-embed-large-v1),
103
+ a ModernBERT-large encoder domain-adapted on financial text and contrastively
104
+ fine-tuned for embeddings (Apache-2.0, 395M parameters, 1024 dimensions). It is
105
+ pinned to Hub commit `4fd33d2335e75c79df57b345aee06d49c5f85bc8` in
106
+ `codecheck_fomc/backends.py` and loaded through `sentence-transformers`; the
107
+ exact library versions are in `uv.lock` and in every artifact's model card
108
+ (`meta.json` → `model_card.serving`). It runs on CPU in float32 with
109
+ `torch.use_deterministic_algorithms(True)`.
110
+
111
+ ### Taxonomy
112
+
113
+ `taxonomy/monetary_policy.yaml` holds ten narratives. Where a label coincides
114
+ with a topic of Bybee, Kelly, Manela and Xiu (2024), *Business News and
115
+ Business Cycles*, Journal of Finance 79(5),
116
+ [doi:10.1111/jofi.13377](https://doi.org/10.1111/jofi.13377), the file says so
117
+ (`bkmx: true`); the descriptions and seed phrases are this package's own and are
118
+ the part a researcher swaps for their own taxonomy.
119
+
120
+ ## The pipeline
121
+
122
+ Three jobs, each producing one `ledgerworks` artifact (an immutable directory
123
+ with a `meta.json` record), each partitioned one file per year:
124
+
125
+ | Step | Kind | Input | Output |
126
+ |---|---|---|---|
127
+ | 1 ingest | `fomc_statements` (raw layer) | `data/raw/` | one row per statement: `doc_id`, `date`, `url`, `html_sha256`, `paragraphs`, `text` |
128
+ | 2 embed | `fomc_embeddings` | step 1 | one row per paragraph: `doc_id`, `para_idx`, `text_sha256`, `embedding` (unit norm) |
129
+ | 3 score | `fomc_narrative_scores` | step 2 + taxonomy | one row per statement × narrative: `cos_doc`, `cos_para_mean`, `cos_para_max`; plus `narratives.parquet` (the narrative vectors) |
130
+
131
+ Paragraphs are the embedding unit because FOMC statements are written as short
132
+ self-contained paragraphs and the model was trained at 512 tokens. The parser
133
+ (`codecheck_fomc/corpus.py`, `PARSER_VERSION = "3"`, recorded in the
134
+ `fomc_statements` hyperparameters) keeps only what the Committee says. From the
135
+ `<p>` elements of the press release it drops:
136
+
137
+ - page furniture ("For release at", "Share", "Implementation Note", "Last Update", ...);
138
+ - the release-date line printed above the title ("January 27, 2021");
139
+ - sidebar panels (`<div class="panel ...">`, "Related Information" links);
140
+ - vote paragraphs, in both formats: "Voting for/against the monetary policy
141
+ action ..." (with any dissents inside it), the approval line that opens
142
+ statements from June 2026 ("The Federal Open Market Committee approved the
143
+ following statement for release by a 12 – 0 vote:"), and standalone dissent
144
+ paragraphs ("<Name>, who preferred ...", "<Name> dissented").
145
+ A narrative
146
+ vector is the mean of the embeddings of its description and seed phrases, from
147
+ the same model. `cos_doc` compares the narrative with the mean of a statement's
148
+ paragraph vectors; `cos_para_mean` and `cos_para_max` aggregate paragraph-level
149
+ cosines. These are raw scores; calibration is downstream work and out of scope.
150
+
151
+ Every artifact records: its hyperparameters (which define its id), its input
152
+ artifact (lineage), the model card (id, revision, dimension, pooling, library
153
+ versions, device), the git commit of the code, start and end times, the
154
+ execution environment (Python, platform, `uv.lock` digest, versions of the
155
+ direct dependencies), and the blake2b digest of every output file. `ledgerworks verify --root ledger` re-hashes
156
+ everything and checks the lineage; `ledgerworks lineage ID` prints the graph.
157
+
158
+ ## How the comparison works
159
+
160
+ `codecheck-fomc compare` joins each table of the fresh run with the reference
161
+ on its keys and checks:
162
+
163
+ | Table | Check |
164
+ |---|---|
165
+ | statements | rows identical (text and HTML digest) |
166
+ | embeddings | same paragraphs (text digest); vectors within `--atol 1e-4` element-wise and cosine >= `--min-cos 0.9999` |
167
+ | scores | within `--atol 1e-4` |
168
+
169
+ Neural-network outputs are reproducible to floating-point tolerance across
170
+ machines, not bit for bit (different BLAS kernels, instruction sets and thread
171
+ counts reorder floating-point sums), hence the tolerances. The ingest step and
172
+ the hashing test backend are exact, and `compare` reports how many files are
173
+ hash-identical to the reference. The comparison exits non-zero on any failure;
174
+ with no reference in `expected/` yet (first run) it says so and exits 0.
175
+
176
+ ## Cross-machine check
177
+
178
+ `expected/` was produced on an Apple M4 Pro (macOS, arm64, CPython 3.12.13, PyPI
179
+ torch 2.14.1; Python is pinned by `.python-version`). The manual workflow
180
+ [`reproduce.yml`](../../.github/workflows/reproduce.yml) rebuilds everything on
181
+ GitHub-hosted runners with `uv sync --extra model` and `codecheck-fomc all`, and
182
+ uploads every `meta.json` and the comparison output. Run of 14 October 2026 at
183
+ commit `7619ab4`:
184
+
185
+ | | Linux (ubuntu-latest) | Windows (windows-latest) |
186
+ |---|---|---|
187
+ | Machine | x86_64, CPython 3.12.3, torch 2.14.1+cpu | AMD64, CPython 3.12.10, torch 2.14.1 |
188
+ | Verdict | REPRODUCED | REPRODUCED |
189
+ | statements | 46 identical, 6/6 files hash-identical | 46 identical, 6/6 files hash-identical |
190
+ | embeddings, max\|diff\| | 6.97e-06 | 5.45e-06 |
191
+ | embeddings, min cosine | 1.000000 | 1.000000 |
192
+ | scores, max\|diff\| | 5.01e-06 | 3.76e-06 |
193
+ | narrative vectors, max\|diff\| / min cosine | 3.73e-07 / 1.000000 | 3.80e-07 / 1.000000 |
194
+ | Wall time, `all` (model cached) | 135 s | 175 s |
195
+
196
+ The largest difference is about 14 times below the tolerance (`atol 1e-4`), and
197
+ no file of the embedding or score tables is bit-identical across machines, as
198
+ expected from different CPUs and BLAS kernels. The artifact ids (up to the
199
+ creation minute) and the recorded `uv.lock` digest are the same on all three
200
+ machines: `.gitattributes` keeps text files in LF on every checkout and the raw
201
+ HTML byte for byte.
202
+
203
+ ## Reproducing the reference run (author's procedure)
204
+
205
+ ```bash
206
+ python fetch_fomc.py # network: download the statements
207
+ git add data/raw && git commit -m "data: FOMC statements to <date>"
208
+ uv sync --extra model
209
+ uv run codecheck-fomc all # from a clean tree, so the commit is recorded
210
+ uv run codecheck-fomc freeze # copy the run into expected/
211
+ git add expected && git commit -m "expected: reference run"
212
+ ```
213
+
214
+ A dirty working tree is refused by the jobs unless `--allow-dirty`, and
215
+ `verify` flags artifacts whose recorded commit ends in `-dirty`.
216
+
217
+ ## Contact
218
+
219
+ Gabin Taibi, University of Twente (BMS-IEBIS), gabin.taibi@gmail.com.
220
+ Issues and questions: https://github.com/GabinTB/ledgerworks/issues.
@@ -0,0 +1,12 @@
1
+ # data/raw
2
+
3
+ Produced once by the package author with `python fetch_fomc.py` (see the
4
+ example README), then committed:
5
+
6
+ - `html/monetaryYYYYMMDDa.htm`: each FOMC statement page, byte for byte as
7
+ served by federalreserve.gov (public domain; source: Board of Governors of the
8
+ Federal Reserve System).
9
+ - `MANIFEST.json`: URL, date, SHA-256, size and retrieval time of every page.
10
+
11
+ The pipeline's first job (`codecheck-fomc ingest`) parses these files; it
12
+ refuses a file whose digest no longer matches the manifest.
@@ -0,0 +1,10 @@
1
+ # expected
2
+
3
+ The reference run, produced by the package author with
4
+ `codecheck-fomc all` followed by `codecheck-fomc freeze`: one directory per
5
+ artifact kind (`fomc_statements`, `fomc_embeddings`, `fomc_narrative_scores`),
6
+ each a verbatim copy of the artifact directory including its `meta.json`
7
+ (hyperparameters, inputs, model card, git commit, file digests), plus
8
+ `RUN.json` with the three artifact ids.
9
+
10
+ `codecheck-fomc compare` checks a fresh run against these files.