- Python 61.1%
- TeX 38%
- Makefile 0.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| assets | ||
| data | ||
| deliverables | ||
| qa-previews | ||
| scripts | ||
| src | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| Makefile | ||
| README.md | ||
Chamblee 2026 voter booklet
Editable-source + PDF project for the Chamblee 2026 Democratic primary voter booklet.
Active generation pipeline
- Generator script:
scripts/generate_booklet.py - Validation script:
scripts/validate_booklet.py - Editable booklet pages:
src/booklet-pages.tex - Editable imposed print layout:
src/booklet-print.tex - Rendered booklet pages PDF:
build/booklet-pages.pdf - Rendered imposed print PDF:
build/booklet-print.pdf
Prerequisites
- Python 3
ffmpeg/ffprobefor an explicit headshot refresh- LuaLaTeX and
latexmkfor PDF builds qpdfand Poppler (pdfinfo,pdftotext) for digital PDF accessibility checks- Git LFS enabled before building from a fresh clone, so committed image assets are real images rather than pointer files
The included Makefile defaults to the Hermes TeX Live path when available (/opt/hermes-runtime/tools/texlive/2026/bin/x86_64-linux) and otherwise uses latexmk from PATH. Override with LATEXMK=/path/to/latexmk if needed.
Build and validation
git lfs pull # fetch the reviewed, content-addressed media inputs
make generate # offline: refresh data/render_data.json and src/*.tex
make validate # check generated data, assets, and imposition order
make build # build build/booklet-pages.pdf and build/booklet-print.pdf
Equivalent manual build commands:
python3 scripts/generate_booklet.py
python3 scripts/validate_booklet.py
export PATH=/opt/hermes-runtime/tools/texlive/2026/bin/x86_64-linux:$PATH
latexmk -lualatex -interaction=nonstopmode -halt-on-error -jobname=booklet-pages -output-directory=build src/booklet-pages.tex
latexmk -lualatex -interaction=nonstopmode -halt-on-error -jobname=booklet-print -output-directory=build src/booklet-print.tex
Sources and structure
- Authoritative candidate list:
data/sheet_raw.json - Cleaned/render-ready data:
data/render_data.json - Reviewed media provenance:
data/media-manifest.json - Content-addressed candidate QR codes:
assets/media/qrs/ - Content-addressed candidate headshots:
assets/media/headshots/ - Review images:
qa-previews/ - Project repository root: this directory
Binary file handling
This repo uses Git LFS for committed image formats listed in .gitattributes. Clone with Git LFS enabled to fetch preview images and static assets normally.
Current PDF deliverables in deliverables/ are committed as regular files; generated PDFs under build/ are ignored.
PDF accessibility policy
build/booklet-pages.pdf is the digital reading copy. Its build supplies document
metadata and language, tagged headings and figures, neutral name-derived image
alternatives, and clickable destinations matching the printed QR codes. Run
make check-accessibility after building to inspect those behaviors with
pdfinfo, qpdf, and pdftotext.
build/booklet-print.pdf is an imposed production artifact: its page sequence
and two-up geometry are for folding and printing, not linear screen-reader
reading. It is therefore not represented as a tagged accessible document. The
digital pages PDF is the maintained accessible alternative for the same content.
Reviewed media policy
Ordinary generate, validate, and build commands are deterministic and do not
contact campaign sites or the QR service. They resolve only committed media listed
in data/media-manifest.json, verify each file's SHA-256 digest, and fail closed
when an asset is missing, changed, or no longer matches its source URL or crop
parameters.
Media refresh is a separate, deliberate review operation:
make refresh-media
git diff -- data/media-manifest.json assets/media/
make test generate validate build
The refresh command always reacquires remote source/service bytes, even when the
URL, crop, and processor are unchanged. It records both source_sha256 and the
reviewed rendered sha256, with source_provenance: fetched; unchanged source
bytes reuse the digest-verified rendered asset without rerunning the headshot
transform. Headshot source and rendered digests are normally distinct. For QR
entries, source_sha256 hashes the QR service PNG response and equals sha256
because that response is the reviewed asset without another transform.
The initial reviewed assets predate raw-source retention. Their migration is
explicit rather than inferred: headshots use a null source_sha256 with
source_provenance: unavailable-historical-source, so the first explicit refresh
must reacquire and transform source bytes. QR generation is an identity flow, so
the reviewed QR bytes are recorded as both source and render digests with
source_provenance: reviewed-identity-input. A successful refresh replaces either
bootstrap marker with fetched provenance; no historical source digest is fabricated.
Changed inputs are written to new content-addressed paths and the manifest is replaced atomically only after a complete asset is available. Review the new image/QR bytes and manifest diff before committing them. Do not run media refresh in ordinary CI; network download policy is enforced by the downloader and maintained separately from this provenance/cache layer.
Imposition order
The print PDF follows the required 8-page booklet order:
- Sheet A front: page 8 | page 1
- Sheet A back: page 2 | page 7
- Sheet B front: page 6 | page 3
- Sheet B back: page 4 | page 5