Weave templates and generated data into realistic documents with a computed golden dataset
Project description
docsynth
Weave templates and generated data into realistic synthetic documents — and get a computed golden dataset alongside them, exact to the cent. The primary intended use is testing AI/OCR document-extraction pipelines: generate a large, varied corpus of synthetic documents whose every field value is known in advance, then score an extraction model against that ground truth.
The first document type is invoices: hundreds of thousands of them, across dozens of business types, billing models (flat, tiered, metered, subscription, usage-based), jurisdictions, languages, and capture conditions (clean, scanned, handwritten). The architecture is not invoice-shaped, though — a document-agnostic kernel does the weaving and a pack supplies one document type. Contracts and legal documents are additional packs, not forks.
Core principles
Five principles are the spine of the project; every part of the system is meant to serve them, and new features are designed against them.
- Realistic — but no PII, and never for fraud. Generated documents are synthetic: they look real, so a corpus is worth testing against, but they never carry personally identifiable information. Identifying details (addresses, phones, tax IDs) are derived deterministically at generation and never stored, and LLM-generated descriptions are screened for PII and quality (regex gates — a flagged description falls back to procedural text) — so there is no PII-shaped field to leak. The project creates no identity documents for fraudulent purposes, and no documents intended to enable fraudulent activity — its purpose is producing test data with a known ground truth, not passing anything off as genuine.
- Provider-agnostic. LLMs, cloud providers, and infrastructure backends are pluggable — storage, run-state, and export are chosen by URI scheme; models are a weighted, config-driven provider mix — with no change to calling code.
- Locally capable when possible. Seed data generates documents offline — no
cloud account, no API key — whenever the document type allows. It is a
capability each pack declares (
ContentMode/local_first), not a blanket promise. - High throughput. Concurrency is enabled on all job types — document generation and catalogue building alike — coordinated without a broker or a leader, so one run scales across a fleet.
- Extensible. Extending generation is simple, at three levels: within a pack (a business type, billing model, locale, or template is a table/file edit); a new document type is a new pack, no kernel fork; and variants of a new type (a bank statement vs a credit-card statement) are an internal axis of one pack, sharing a base by composition. See Extending for the boundary rule.
The sections below are these principles made concrete.
Running it
# From source (works today; `pip install docsynth` once 0.1.0 ships)
git clone https://github.com/kashodev/docsynth.git && cd docsynth
python -m venv .venv && source .venv/bin/activate
pip install -e '.[studio]'
playwright install chromium # one-time, for PDF rendering
docsynth studio # interactive wizard — pick the local target
→ Full walk-through: Quickstart.
The infrastructure is local by default and cloud-optional — the same interfaces carry either, dispatched by URI scheme, so the calling code does not change when you scale:
| Concern | Default (local) | Cloud (optional) |
|---|---|---|
| Storage | file:// filesystem |
gs:// · s3:// |
| Run state | sqlite:// |
firestore:// |
| Golden export | parquet:// · duckdb:// |
bigquery:// |
Point a config at cloud URIs and install the matching extra
(pip install 'docsynth[gcp]') when you want to scale.
Local-first is a property of the pack, not a platform promise
How a pack generates its content is what decides whether it can run without a cloud account or API key:
- Invoices are local-first. The invoice pack draws from a procedural, built-in seed catalogue and computes every figure deterministically — no LLM, no keys — so it produces a full dataset of hundreds of thousands of documents on a laptop. This is the pack that ships today.
- Text-heavy packs are not, at scale. A contract's realistic clauses, recitals, and defined terms are generated natural language; there is no procedural substitute at quality, so an LLM is effectively required. Those packs run against an LLM provider (batched for cost) — a network and a key — even though the storage/state/export infrastructure around them still defaults to local.
So "no cloud account" is true end to end for invoices, and remains true for the infrastructure of any pack; it is not a guarantee that every future document type generates offline.
This is a declared contract, not a footnote — every pack states its mode, so the kernel can report and gate on it:
>>> get_pack("invoice").content_capability
ContentCapability(mode=<ContentMode.PROCEDURAL: 'procedural'>, ...)
>>> get_pack("invoice").content_capability.local_first
True
An LLM_BACKED pack additionally implements LlmContentBuilder (say what to
generate, take the text back), and build_catalogue(pack, mix, budget=...) runs
that one-time offline step through the weighted provider mix — batching the
batch-capable slice through the Anthropic Batch API. The document run itself
still makes no LLM calls either way: it reads the finished catalogue.
The golden set is exact
A value that is Decimal("327.02") in the record is still exactly 327.02
after a round trip through Parquet and out of SQL. Evaluation is one join:
SELECT g.template_id, g.locale,
COUNTIF(e.grand_total = g.grand_total) / COUNT(*) AS accuracy
FROM golden.invoices g JOIN extracted.invoices e USING (invoice_id)
GROUP BY 1, 2
The same query runs against a local DuckDB view and a BigQuery external table.
Extensibility runs at three levels:
- Within a pack — a new business type, billing model, locale, sub-category, or template is a table entry or a small file in the pack; no kernel change.
- A new document type — a new pack implementing
DocumentPack+GoldenRecord. Register it in-tree or ship it asdocsynth-yourpackvia thedocsynth.packsentry-point group. - Variants of a document type — e.g. a
statementpack producing bank, credit-card, and brokerage statements: one base document with per-variant field overlays, not three packs. Model these as an internal variant axis of a single pack (a base golden record + optional variant fields, shared builders the variants compose), the same way the invoice pack carries business types and archetypes under one registry entry.
Two rules keep this from eroding the design:
- The kernel stays document-agnostic — its power is its ignorance. Shared
logic comes in three tiers: cross-document (seeding, claim/lease, rendering,
GoldenRecord, money, locale, providers) belongs in the kernel; domain-shared (a statement's account header, transaction ledger, running-balance math) belongs in the pack, never hoisted into core to avoid duplication; variant-specific logic is a variant overlay in the pack. - Composition over a base pack. Draw a pack boundary where the contract with the kernel is uniform (one golden-schema shape, one content mode, one renderer family). Split into a second pack only when that contract genuinely diverges — and then share code through a library dependency, not an inherited base pack (has-a, not is-a), extracted only when a second pack actually needs it.
See the documentation site — built from site-docs/ and
deployed to GitHub Pages — for the full architecture and rationale, the
concurrency & coordination model (how one run scales across a fleet without a
broker or a leader), and per-platform deployment (local, GCP, AWS, Azure) with the
URIs to point at.
Status
Early. Kernel, invoice pack, localisation, PDF rendering, and the local storage/state/sink layer are built and tested (250 tests). Generation orchestration runs end to end locally; catalogue generation and cloud adapters are in progress.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file docsynth-0.1.0.tar.gz.
File metadata
- Download URL: docsynth-0.1.0.tar.gz
- Upload date:
- Size: 5.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38c868316762f019d3594321de993e4a1fa1edaea46a823f4ba6f949bf8276f7
|
|
| MD5 |
d810458f5e04a769a0b4c705b1060215
|
|
| BLAKE2b-256 |
d0a5ddcebe997c570e10d30a611fecd6572b92018db15b8be20715fbdaa19907
|
Provenance
The following attestation bundles were made for docsynth-0.1.0.tar.gz:
Publisher:
release.yml on kashodev/docsynth
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
docsynth-0.1.0.tar.gz -
Subject digest:
38c868316762f019d3594321de993e4a1fa1edaea46a823f4ba6f949bf8276f7 - Sigstore transparency entry: 2262734189
- Sigstore integration time:
-
Permalink:
kashodev/docsynth@707a46b826d42d6afc2a84ce6a3525b21c5d51d5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/kashodev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@707a46b826d42d6afc2a84ce6a3525b21c5d51d5 -
Trigger Event:
release
-
Statement type:
File details
Details for the file docsynth-0.1.0-py3-none-any.whl.
File metadata
- Download URL: docsynth-0.1.0-py3-none-any.whl
- Upload date:
- Size: 644.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37be8e566463383dac082fbb123e743718ffbed4e26217ca755f93f0fcfefb57
|
|
| MD5 |
2a9f204e8c9ef4265855e9f03ed66e98
|
|
| BLAKE2b-256 |
1e4dbe5bb791f943f3b84695d967755b5041ebd072e01f450da56525a6a21d4e
|
Provenance
The following attestation bundles were made for docsynth-0.1.0-py3-none-any.whl:
Publisher:
release.yml on kashodev/docsynth
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
docsynth-0.1.0-py3-none-any.whl -
Subject digest:
37be8e566463383dac082fbb123e743718ffbed4e26217ca755f93f0fcfefb57 - Sigstore transparency entry: 2262734328
- Sigstore integration time:
-
Permalink:
kashodev/docsynth@707a46b826d42d6afc2a84ce6a3525b21c5d51d5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/kashodev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@707a46b826d42d6afc2a84ce6a3525b21c5d51d5 -
Trigger Event:
release
-
Statement type: