trustrender 0.2.0__tar.gz → 0.3.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 (87) hide show
  1. trustrender-0.3.0/PKG-INFO +246 -0
  2. trustrender-0.3.0/README.md +204 -0
  3. {trustrender-0.2.0 → trustrender-0.3.0}/pyproject.toml +21 -11
  4. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/__init__.py +109 -11
  5. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/cli.py +397 -104
  6. trustrender-0.3.0/src/trustrender/invoice_aliases.py +275 -0
  7. trustrender-0.3.0/src/trustrender/invoice_ingest.py +1002 -0
  8. trustrender-0.3.0/src/trustrender/invoice_schema.py +171 -0
  9. trustrender-0.3.0/src/trustrender/playground/assets/index-Bsf6YnD1.css +2 -0
  10. trustrender-0.3.0/src/trustrender/playground/assets/index-C6K0JsLw.js +97 -0
  11. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/playground/index.html +2 -2
  12. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/server.py +42 -3
  13. trustrender-0.3.0/src/trustrender.egg-info/PKG-INFO +246 -0
  14. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender.egg-info/SOURCES.txt +8 -2
  15. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender.egg-info/requires.txt +13 -5
  16. trustrender-0.3.0/tests/test_adversarial_corpus.py +278 -0
  17. trustrender-0.3.0/tests/test_invoice_ingest.py +646 -0
  18. trustrender-0.3.0/tests/test_real_payloads.py +101 -0
  19. trustrender-0.2.0/PKG-INFO +0 -274
  20. trustrender-0.2.0/README.md +0 -239
  21. trustrender-0.2.0/src/trustrender/playground/assets/index--cOwr7nw.css +0 -2
  22. trustrender-0.2.0/src/trustrender/playground/assets/index-CiBSP9E3.js +0 -81
  23. trustrender-0.2.0/src/trustrender.egg-info/PKG-INFO +0 -274
  24. {trustrender-0.2.0 → trustrender-0.3.0}/LICENSE +0 -0
  25. {trustrender-0.2.0 → trustrender-0.3.0}/setup.cfg +0 -0
  26. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/einvoice.j2.typ +0 -0
  27. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/einvoice_data.json +0 -0
  28. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/invoice.j2.typ +0 -0
  29. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/invoice_data.json +0 -0
  30. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/letter.j2.typ +0 -0
  31. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/letter_data.json +0 -0
  32. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/receipt.j2.typ +0 -0
  33. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/receipt_data.json +0 -0
  34. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/report.j2.typ +0 -0
  35. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/report_data.json +0 -0
  36. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/statement.j2.typ +0 -0
  37. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/builtin_templates/statement_data.json +0 -0
  38. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/contract.py +0 -0
  39. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/dashboard.py +0 -0
  40. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/doctor.py +0 -0
  41. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/engine.py +0 -0
  42. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/errors.py +0 -0
  43. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/filters.py +0 -0
  44. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/fingerprint.py +0 -0
  45. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/fonts/Inter/Inter-Bold.ttf +0 -0
  46. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/fonts/Inter/Inter-BoldItalic.ttf +0 -0
  47. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/fonts/Inter/Inter-Italic.ttf +0 -0
  48. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/fonts/Inter/Inter-Regular.ttf +0 -0
  49. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/fonts/Inter/LICENSE.txt +0 -0
  50. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/playground/assets/pdf.worker-C1breYqn.mjs +0 -0
  51. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/playground/favicon.svg +0 -0
  52. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/playground/icons.svg +0 -0
  53. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/provenance.py +0 -0
  54. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/readiness.py +0 -0
  55. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/regression.py +0 -0
  56. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/semantic.py +0 -0
  57. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/templates.py +0 -0
  58. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/trace.py +0 -0
  59. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender/zugferd.py +0 -0
  60. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender.egg-info/dependency_links.txt +0 -0
  61. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender.egg-info/entry_points.txt +0 -0
  62. {trustrender-0.2.0 → trustrender-0.3.0}/src/trustrender.egg-info/top_level.txt +0 -0
  63. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_audit_e2e.py +0 -0
  64. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_cli.py +0 -0
  65. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_contract.py +0 -0
  66. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_doctor.py +0 -0
  67. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_engine.py +0 -0
  68. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_error_pipeline.py +0 -0
  69. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_errors.py +0 -0
  70. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_filters.py +0 -0
  71. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_fingerprint.py +0 -0
  72. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_fingerprint_stress.py +0 -0
  73. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_fonts.py +0 -0
  74. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_pagination.py +0 -0
  75. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_provenance.py +0 -0
  76. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_readiness.py +0 -0
  77. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_regression.py +0 -0
  78. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_regression_stress.py +0 -0
  79. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_render.py +0 -0
  80. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_semantic.py +0 -0
  81. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_semantic_stress.py +0 -0
  82. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_server.py +0 -0
  83. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_templates.py +0 -0
  84. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_trace.py +0 -0
  85. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_ugly_data.py +0 -0
  86. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_ugly_data_pressure.py +0 -0
  87. {trustrender-0.2.0 → trustrender-0.3.0}/tests/test_zugferd.py +0 -0
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: trustrender
3
+ Version: 0.3.0
4
+ Summary: Validate and normalize Stripe, Shopify, and custom billing data for Factur-X/ZUGFeRD
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://trustrender.dev
7
+ Project-URL: Documentation, https://trustrender.dev
8
+ Project-URL: Source, https://github.com/verityengine/trustrender
9
+ Project-URL: Issues, https://github.com/verityengine/trustrender/issues
10
+ Project-URL: Changelog, https://github.com/verityengine/trustrender/releases
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Office/Business :: Financial
17
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: drafthorse>=2024.0
24
+ Provides-Extra: render
25
+ Requires-Dist: typst>=0.14; extra == "render"
26
+ Requires-Dist: jinja2>=3.1; extra == "render"
27
+ Requires-Dist: pypdf>=4.0; extra == "render"
28
+ Provides-Extra: serve
29
+ Requires-Dist: starlette>=0.40; extra == "serve"
30
+ Requires-Dist: uvicorn>=0.30; extra == "serve"
31
+ Provides-Extra: zugferd
32
+ Requires-Dist: factur-x>=3.0; extra == "zugferd"
33
+ Provides-Extra: all
34
+ Requires-Dist: trustrender[render,serve,zugferd]; extra == "all"
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=8.0; extra == "dev"
37
+ Requires-Dist: ruff>=0.4; extra == "dev"
38
+ Requires-Dist: httpx>=0.27; extra == "dev"
39
+ Requires-Dist: factur-x>=3.0; extra == "dev"
40
+ Requires-Dist: trustrender[all]; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # TrustRender
44
+
45
+ Validate and normalize billing data from Stripe, Shopify, and custom systems before Factur-X/ZUGFeRD embedding.
46
+
47
+ If you're bridging a non-compliant billing platform into EU e-invoicing, libraries like [factur-x](https://github.com/akretion/factur-x) and [drafthorse](https://github.com/pretix/python-drafthorse) assume your data is already correct. TrustRender catches arithmetic mismatches, field misalignment, missing required fields, and structural problems before compliant XML gets embedded.
48
+
49
+ ```
50
+ pip install trustrender
51
+ trustrender validate invoice.json
52
+ ```
53
+
54
+ ## What it does
55
+
56
+ Takes invoice JSON from Stripe, Shopify, custom billing APIs, or legacy exports and tells you whether it's safe to embed as Factur-X/ZUGFeRD.
57
+
58
+ ```
59
+ $ trustrender validate quickbooks_invoice.json
60
+
61
+ Invoice: INV-2026-5541
62
+ From: Summit Analytics Co.
63
+ To: Horizon Financial
64
+ Items: 2
65
+ Total: $10,524.50
66
+
67
+ Normalizations (25):
68
+ DocNumber → invoice_number CompanyName → sender.name
69
+ TxnDate → invoice_date customer.Name → recipient.name
70
+ Line → items SubTotal → subtotal
71
+ ... and 19 more
72
+
73
+ PASS — invoice data is valid
74
+
75
+ Safe to embed in Factur-X/ZUGFeRD PDF.
76
+ ```
77
+
78
+ Bad data gets blocked:
79
+
80
+ ```
81
+ $ trustrender validate ocr_extracted_invoice.json
82
+
83
+ BLOCKED — 2 problem(s)
84
+
85
+ items[1] total is wrong
86
+ You entered $459.00 but math says $450.00
87
+ Fix the line total or the price/quantity.
88
+
89
+ Subtotal is wrong
90
+ Lines add up to $12,459.00 but you listed $12,450.00
91
+
92
+ This invoice cannot be processed until the problems above are fixed.
93
+ ```
94
+
95
+ ## Install
96
+
97
+ ```
98
+ pip install trustrender
99
+ ```
100
+
101
+ Core install requires only `drafthorse`. No Typst, no browser, no heavy deps.
102
+
103
+ Optional extras:
104
+ ```
105
+ pip install "trustrender[zugferd]" # XSD/Schematron validation
106
+ pip install "trustrender[render]" # PDF rendering via Typst
107
+ pip install "trustrender[all]" # everything
108
+ ```
109
+
110
+ Requires Python 3.11+.
111
+
112
+ ## Python API
113
+
114
+ ```python
115
+ from trustrender import validate_invoice
116
+
117
+ result = validate_invoice({
118
+ "invoiceNo": "INV-001",
119
+ "vendor": {"companyName": "Acme Corp"},
120
+ "customer": {"Name": "Client Inc"},
121
+ "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
122
+ "SubTotal": 100,
123
+ "tax": 8.50,
124
+ "TotalAmt": 108.50,
125
+ }, zugferd=True)
126
+
127
+ if result["render_ready"] and result.get("zugferd_ready"):
128
+ # safe to call factur-x / drafthorse
129
+ print("All checks passed")
130
+ else:
131
+ for error in result["errors"]:
132
+ print(f"BLOCKED: {error['message']}")
133
+ ```
134
+
135
+ `validate_invoice()` returns:
136
+ - `status`: "ready" | "ready_with_warnings" | "blocked"
137
+ - `render_ready`: bool
138
+ - `canonical`: normalized invoice dict (all fields in canonical names)
139
+ - `errors`: list of blocking issues with rule_id, path, expected/actual
140
+ - `warnings`: advisory issues
141
+ - `normalizations`: field-level provenance (what was renamed, coerced, computed)
142
+ - `zugferd_ready`: bool (if `zugferd=True`)
143
+
144
+ ## What it normalizes
145
+
146
+ 90+ vendor field aliases across QuickBooks, Xero, Stripe, and generic CSV/ERP formats:
147
+
148
+ | Source field | Canonical field |
149
+ |---|---|
150
+ | `DocNumber`, `invoiceNo`, `inv_no`, `ref` | `invoice_number` |
151
+ | `CompanyName`, `account_name`, `bill_from_name` | `sender.name` |
152
+ | `customer`, `billTo`, `Contact` | `recipient` |
153
+ | `Line`, `LineItems`, `entries`, `products` | `items` |
154
+ | `UnitPrice`, `cost`, `rate`, `unitCost` | `unit_price` |
155
+ | `Amount`, `LineAmount`, `line_total` | `line_total` |
156
+ | `SubTotal`, `net_total`, `sub_total` | `subtotal` |
157
+ | `TotalAmt`, `grand_total`, `amount_due` | `total` |
158
+
159
+ Plus: type coercion (`"$1,234.56"` → `1234.56`), date parsing (`"April 10, 2026"` → `2026-04-10`), computed defaults (missing `line_total` = `qty × price`), near-match typo detection (`invioce_number` → suggests `invoice_number`).
160
+
161
+ ## What it checks
162
+
163
+ 7 deterministic semantic checks, all arithmetic:
164
+
165
+ | Check | What it catches |
166
+ |---|---|
167
+ | `identity.invoice_number` | Missing or empty invoice number |
168
+ | `identity.sender_name` | Missing vendor/sender name |
169
+ | `identity.recipient_name` | Missing recipient/buyer name |
170
+ | `items.non_empty` | No line items |
171
+ | `arithmetic.line_total` | line_total ≠ qty × unit_price |
172
+ | `arithmetic.subtotal` | subtotal ≠ sum of line_totals |
173
+ | `arithmetic.total` | total ≠ subtotal + tax_amount |
174
+
175
+ No AI. No heuristics. Every check is deterministic and objectively verifiable.
176
+
177
+ ## CLI
178
+
179
+ ```
180
+ trustrender validate <data.json> [--zugferd] [--format text|json]
181
+ trustrender ingest <data.json> [-o canonical.json]
182
+ trustrender render <template> <data.json> -o <output.pdf> [--zugferd en16931]
183
+ trustrender preflight <template> <data.json> [--zugferd en16931]
184
+ trustrender serve --templates <dir> [--port 8190]
185
+ trustrender doctor [--smoke]
186
+ ```
187
+
188
+ ## Integration with factur-x / drafthorse
189
+
190
+ TrustRender validates and normalizes. You generate and embed with the library of your choice.
191
+
192
+ ```python
193
+ from trustrender import validate_invoice
194
+
195
+ # Step 1: Validate with TrustRender
196
+ result = validate_invoice(messy_data, zugferd=True)
197
+ if not result["render_ready"] or not result["zugferd_ready"]:
198
+ raise ValueError(f"Invoice blocked: {result['errors']}")
199
+
200
+ # Step 2: Use the canonical payload with drafthorse or factur-x
201
+ canonical = result["canonical"]
202
+ # ... your existing ZUGFeRD generation code here
203
+ ```
204
+
205
+ ## EN 16931 e-invoicing (narrow scope)
206
+
207
+ Catches many document-level and ZUGFeRD/EN 16931 readiness issues before embedding. Currently supports:
208
+
209
+ - **Domestic German B2B invoices** with standard VAT, EUR, SEPA payment
210
+ - Single or mixed VAT rates (7% + 19%)
211
+ - Invoice type 380 and credit note 381
212
+ - PDF/A-3b with embedded CII XML (requires `trustrender[render]`)
213
+
214
+ Not supported (fails loudly): reverse charge, cross-border, allowances/charges, non-EUR currencies.
215
+
216
+ See [docs/einvoice-scope.md](docs/einvoice-scope.md) for the full scope matrix.
217
+
218
+ ## Optional: PDF rendering
219
+
220
+ If you also want TrustRender to generate PDFs (not just validate):
221
+
222
+ ```
223
+ pip install "trustrender[render]"
224
+ trustrender render invoice.j2.typ data.json -o invoice.pdf --zugferd en16931
225
+ ```
226
+
227
+ Rendering uses Typst — no browser, no Chromium. Fast and deterministic.
228
+
229
+ ## What this is not
230
+
231
+ - Not a full AP automation platform
232
+ - Not an e-invoice compliance certification
233
+ - Not an AI-powered data fixer (all corrections are deterministic)
234
+ - Not a replacement for factur-x or drafthorse — it's the validation layer you run before them
235
+
236
+ ## Development
237
+
238
+ ```
239
+ pip install -e ".[dev]"
240
+ trustrender doctor --smoke
241
+ pytest
242
+ ```
243
+
244
+ ## License
245
+
246
+ MIT
@@ -0,0 +1,204 @@
1
+ # TrustRender
2
+
3
+ Validate and normalize billing data from Stripe, Shopify, and custom systems before Factur-X/ZUGFeRD embedding.
4
+
5
+ If you're bridging a non-compliant billing platform into EU e-invoicing, libraries like [factur-x](https://github.com/akretion/factur-x) and [drafthorse](https://github.com/pretix/python-drafthorse) assume your data is already correct. TrustRender catches arithmetic mismatches, field misalignment, missing required fields, and structural problems before compliant XML gets embedded.
6
+
7
+ ```
8
+ pip install trustrender
9
+ trustrender validate invoice.json
10
+ ```
11
+
12
+ ## What it does
13
+
14
+ Takes invoice JSON from Stripe, Shopify, custom billing APIs, or legacy exports and tells you whether it's safe to embed as Factur-X/ZUGFeRD.
15
+
16
+ ```
17
+ $ trustrender validate quickbooks_invoice.json
18
+
19
+ Invoice: INV-2026-5541
20
+ From: Summit Analytics Co.
21
+ To: Horizon Financial
22
+ Items: 2
23
+ Total: $10,524.50
24
+
25
+ Normalizations (25):
26
+ DocNumber → invoice_number CompanyName → sender.name
27
+ TxnDate → invoice_date customer.Name → recipient.name
28
+ Line → items SubTotal → subtotal
29
+ ... and 19 more
30
+
31
+ PASS — invoice data is valid
32
+
33
+ Safe to embed in Factur-X/ZUGFeRD PDF.
34
+ ```
35
+
36
+ Bad data gets blocked:
37
+
38
+ ```
39
+ $ trustrender validate ocr_extracted_invoice.json
40
+
41
+ BLOCKED — 2 problem(s)
42
+
43
+ items[1] total is wrong
44
+ You entered $459.00 but math says $450.00
45
+ Fix the line total or the price/quantity.
46
+
47
+ Subtotal is wrong
48
+ Lines add up to $12,459.00 but you listed $12,450.00
49
+
50
+ This invoice cannot be processed until the problems above are fixed.
51
+ ```
52
+
53
+ ## Install
54
+
55
+ ```
56
+ pip install trustrender
57
+ ```
58
+
59
+ Core install requires only `drafthorse`. No Typst, no browser, no heavy deps.
60
+
61
+ Optional extras:
62
+ ```
63
+ pip install "trustrender[zugferd]" # XSD/Schematron validation
64
+ pip install "trustrender[render]" # PDF rendering via Typst
65
+ pip install "trustrender[all]" # everything
66
+ ```
67
+
68
+ Requires Python 3.11+.
69
+
70
+ ## Python API
71
+
72
+ ```python
73
+ from trustrender import validate_invoice
74
+
75
+ result = validate_invoice({
76
+ "invoiceNo": "INV-001",
77
+ "vendor": {"companyName": "Acme Corp"},
78
+ "customer": {"Name": "Client Inc"},
79
+ "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
80
+ "SubTotal": 100,
81
+ "tax": 8.50,
82
+ "TotalAmt": 108.50,
83
+ }, zugferd=True)
84
+
85
+ if result["render_ready"] and result.get("zugferd_ready"):
86
+ # safe to call factur-x / drafthorse
87
+ print("All checks passed")
88
+ else:
89
+ for error in result["errors"]:
90
+ print(f"BLOCKED: {error['message']}")
91
+ ```
92
+
93
+ `validate_invoice()` returns:
94
+ - `status`: "ready" | "ready_with_warnings" | "blocked"
95
+ - `render_ready`: bool
96
+ - `canonical`: normalized invoice dict (all fields in canonical names)
97
+ - `errors`: list of blocking issues with rule_id, path, expected/actual
98
+ - `warnings`: advisory issues
99
+ - `normalizations`: field-level provenance (what was renamed, coerced, computed)
100
+ - `zugferd_ready`: bool (if `zugferd=True`)
101
+
102
+ ## What it normalizes
103
+
104
+ 90+ vendor field aliases across QuickBooks, Xero, Stripe, and generic CSV/ERP formats:
105
+
106
+ | Source field | Canonical field |
107
+ |---|---|
108
+ | `DocNumber`, `invoiceNo`, `inv_no`, `ref` | `invoice_number` |
109
+ | `CompanyName`, `account_name`, `bill_from_name` | `sender.name` |
110
+ | `customer`, `billTo`, `Contact` | `recipient` |
111
+ | `Line`, `LineItems`, `entries`, `products` | `items` |
112
+ | `UnitPrice`, `cost`, `rate`, `unitCost` | `unit_price` |
113
+ | `Amount`, `LineAmount`, `line_total` | `line_total` |
114
+ | `SubTotal`, `net_total`, `sub_total` | `subtotal` |
115
+ | `TotalAmt`, `grand_total`, `amount_due` | `total` |
116
+
117
+ Plus: type coercion (`"$1,234.56"` → `1234.56`), date parsing (`"April 10, 2026"` → `2026-04-10`), computed defaults (missing `line_total` = `qty × price`), near-match typo detection (`invioce_number` → suggests `invoice_number`).
118
+
119
+ ## What it checks
120
+
121
+ 7 deterministic semantic checks, all arithmetic:
122
+
123
+ | Check | What it catches |
124
+ |---|---|
125
+ | `identity.invoice_number` | Missing or empty invoice number |
126
+ | `identity.sender_name` | Missing vendor/sender name |
127
+ | `identity.recipient_name` | Missing recipient/buyer name |
128
+ | `items.non_empty` | No line items |
129
+ | `arithmetic.line_total` | line_total ≠ qty × unit_price |
130
+ | `arithmetic.subtotal` | subtotal ≠ sum of line_totals |
131
+ | `arithmetic.total` | total ≠ subtotal + tax_amount |
132
+
133
+ No AI. No heuristics. Every check is deterministic and objectively verifiable.
134
+
135
+ ## CLI
136
+
137
+ ```
138
+ trustrender validate <data.json> [--zugferd] [--format text|json]
139
+ trustrender ingest <data.json> [-o canonical.json]
140
+ trustrender render <template> <data.json> -o <output.pdf> [--zugferd en16931]
141
+ trustrender preflight <template> <data.json> [--zugferd en16931]
142
+ trustrender serve --templates <dir> [--port 8190]
143
+ trustrender doctor [--smoke]
144
+ ```
145
+
146
+ ## Integration with factur-x / drafthorse
147
+
148
+ TrustRender validates and normalizes. You generate and embed with the library of your choice.
149
+
150
+ ```python
151
+ from trustrender import validate_invoice
152
+
153
+ # Step 1: Validate with TrustRender
154
+ result = validate_invoice(messy_data, zugferd=True)
155
+ if not result["render_ready"] or not result["zugferd_ready"]:
156
+ raise ValueError(f"Invoice blocked: {result['errors']}")
157
+
158
+ # Step 2: Use the canonical payload with drafthorse or factur-x
159
+ canonical = result["canonical"]
160
+ # ... your existing ZUGFeRD generation code here
161
+ ```
162
+
163
+ ## EN 16931 e-invoicing (narrow scope)
164
+
165
+ Catches many document-level and ZUGFeRD/EN 16931 readiness issues before embedding. Currently supports:
166
+
167
+ - **Domestic German B2B invoices** with standard VAT, EUR, SEPA payment
168
+ - Single or mixed VAT rates (7% + 19%)
169
+ - Invoice type 380 and credit note 381
170
+ - PDF/A-3b with embedded CII XML (requires `trustrender[render]`)
171
+
172
+ Not supported (fails loudly): reverse charge, cross-border, allowances/charges, non-EUR currencies.
173
+
174
+ See [docs/einvoice-scope.md](docs/einvoice-scope.md) for the full scope matrix.
175
+
176
+ ## Optional: PDF rendering
177
+
178
+ If you also want TrustRender to generate PDFs (not just validate):
179
+
180
+ ```
181
+ pip install "trustrender[render]"
182
+ trustrender render invoice.j2.typ data.json -o invoice.pdf --zugferd en16931
183
+ ```
184
+
185
+ Rendering uses Typst — no browser, no Chromium. Fast and deterministic.
186
+
187
+ ## What this is not
188
+
189
+ - Not a full AP automation platform
190
+ - Not an e-invoice compliance certification
191
+ - Not an AI-powered data fixer (all corrections are deterministic)
192
+ - Not a replacement for factur-x or drafthorse — it's the validation layer you run before them
193
+
194
+ ## Development
195
+
196
+ ```
197
+ pip install -e ".[dev]"
198
+ trustrender doctor --smoke
199
+ pytest
200
+ ```
201
+
202
+ ## License
203
+
204
+ MIT
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trustrender"
7
- version = "0.2.0"
8
- description = "Fast, code-first PDF generation from structured data. No browser, no Chromium."
7
+ version = "0.3.0"
8
+ description = "Validate and normalize Stripe, Shopify, and custom billing data for Factur-X/ZUGFeRD"
9
9
  license = "MIT"
10
10
  classifiers = [
11
11
  "Development Status :: 3 - Alpha",
@@ -15,17 +15,14 @@ classifiers = [
15
15
  "Programming Language :: Python :: 3.11",
16
16
  "Programming Language :: Python :: 3.12",
17
17
  "Topic :: Office/Business :: Financial",
18
- "Topic :: Text Processing :: Markup",
18
+ "Topic :: Office/Business :: Financial :: Accounting",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ "Topic :: Software Development :: Quality Assurance",
19
21
  ]
20
22
  readme = "README.md"
21
23
  requires-python = ">=3.11"
22
24
  dependencies = [
23
- "typst>=0.14",
24
- "jinja2>=3.1",
25
- "starlette>=0.40",
26
- "uvicorn>=0.30",
27
25
  "drafthorse>=2024.0",
28
- "pypdf>=4.0",
29
26
  ]
30
27
 
31
28
  [project.urls]
@@ -39,14 +36,27 @@ Changelog = "https://github.com/verityengine/trustrender/releases"
39
36
  trustrender = "trustrender.cli:main"
40
37
 
41
38
  [project.optional-dependencies]
39
+ render = [
40
+ "typst>=0.14",
41
+ "jinja2>=3.1",
42
+ "pypdf>=4.0",
43
+ ]
44
+ serve = [
45
+ "starlette>=0.40",
46
+ "uvicorn>=0.30",
47
+ ]
48
+ zugferd = [
49
+ "factur-x>=3.0",
50
+ ]
51
+ all = [
52
+ "trustrender[render,serve,zugferd]",
53
+ ]
42
54
  dev = [
43
55
  "pytest>=8.0",
44
56
  "ruff>=0.4",
45
57
  "httpx>=0.27",
46
58
  "factur-x>=3.0",
47
- ]
48
- zugferd = [
49
- "factur-x>=3.0",
59
+ "trustrender[all]",
50
60
  ]
51
61
 
52
62
  [tool.setuptools.packages.find]
@@ -1,4 +1,13 @@
1
- """TrustRender: fast, code-first PDF generation from structured data."""
1
+ """TrustRender — validate and normalize messy invoice data for Factur-X/ZUGFeRD compliance.
2
+
3
+ Core API (always available, no rendering deps required):
4
+ validate_invoice(data, zugferd=False) → structured validation result
5
+ ingest_invoice(data) → IngestionReport with canonical payload + provenance
6
+
7
+ Rendering API (requires `pip install trustrender[render]`):
8
+ render(template, data, ...) → PDF bytes
9
+ audit(template, data, ...) → AuditResult with fingerprint + drift detection
10
+ """
2
11
 
3
12
  from __future__ import annotations
4
13
 
@@ -7,8 +16,16 @@ import os
7
16
  from dataclasses import dataclass
8
17
  from pathlib import Path
9
18
 
10
- from .engine import CompileBackend, compile_typst, compile_typst_file
11
19
  from .errors import ErrorCode, TrustRenderError
20
+ from .invoice_ingest import IngestionReport, ingest_invoice
21
+
22
+ # Rendering imports — optional, guarded
23
+ try:
24
+ from .engine import CompileBackend, compile_typst, compile_typst_file
25
+ from .templates import render_template
26
+ _HAS_RENDER = True
27
+ except ImportError:
28
+ _HAS_RENDER = False
12
29
 
13
30
 
14
31
  @dataclass
@@ -18,18 +35,83 @@ class RenderResult:
18
35
  pdf_bytes: bytes
19
36
  trace_id: str | None = None
20
37
 
21
- # Re-export for public API
22
- from .errors import ErrorCode as ErrorCode # noqa: F811
23
- from .templates import render_template
24
38
 
25
- __version__ = "0.2.0"
39
+ __version__ = "0.3.0"
26
40
 
27
41
  __all__ = [
42
+ # Core API (always available)
43
+ "validate_invoice", "ingest_invoice", "IngestionReport",
44
+ # Rendering API (requires [render] extras)
28
45
  "render", "audit", "AuditResult",
29
- "TrustRenderError", "ErrorCode", "__version__", "bundled_font_dir",
46
+ # Errors
47
+ "TrustRenderError", "ErrorCode",
48
+ # Meta
49
+ "__version__", "bundled_font_dir",
30
50
  ]
31
51
 
32
52
 
53
+ # ── Core API: validation (always available) ──────────────────────────
54
+
55
+ def validate_invoice(data: dict, *, zugferd: bool = False) -> dict:
56
+ """Validate and normalize messy invoice data. Returns structured result.
57
+
58
+ This is the primary API for using TrustRender as a validation layer
59
+ before Factur-X/ZUGFeRD embedding. No rendering deps required.
60
+
61
+ Args:
62
+ data: Messy invoice data as a dict (any vendor format — QuickBooks,
63
+ Xero, Stripe, CSV, OCR output, etc.).
64
+ zugferd: If True, also run ZUGFeRD EN 16931 readiness checks
65
+ on the canonical output.
66
+
67
+ Returns:
68
+ Dict with keys:
69
+ - status: "ready" | "ready_with_warnings" | "blocked"
70
+ - render_ready: bool
71
+ - canonical: normalized invoice dict
72
+ - errors: list of blocking issues
73
+ - warnings: list of advisory issues
74
+ - normalizations: list of field changes with provenance
75
+ - computed_fields: list of auto-computed fields
76
+ - unknown_fields: list of unrecognized fields with classification
77
+ - zugferd_ready: bool (only if zugferd=True)
78
+ - zugferd_errors: list (only if zugferd=True)
79
+
80
+ Example::
81
+
82
+ from trustrender import validate_invoice
83
+
84
+ result = validate_invoice({
85
+ "invoiceNo": "INV-001",
86
+ "vendor": {"companyName": "Acme Corp"},
87
+ "customer": {"Name": "Client Inc"},
88
+ "LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
89
+ "SubTotal": 100, "tax": 8.50, "TotalAmt": 108.50,
90
+ }, zugferd=True)
91
+
92
+ if result["render_ready"] and result.get("zugferd_ready"):
93
+ print("Safe to embed — all checks passed")
94
+ else:
95
+ for error in result["errors"]:
96
+ print(f"BLOCKED: {error['message']}")
97
+ """
98
+ report = ingest_invoice(data)
99
+ result = report.to_dict()
100
+
101
+ if zugferd and report.render_ready:
102
+ from .zugferd import validate_zugferd_invoice_data
103
+ zug_errors = validate_zugferd_invoice_data(report.canonical)
104
+ result["zugferd_errors"] = [
105
+ {"path": e.path, "message": e.message} for e in zug_errors
106
+ ]
107
+ result["zugferd_ready"] = len(zug_errors) == 0
108
+ elif zugferd:
109
+ result["zugferd_errors"] = []
110
+ result["zugferd_ready"] = False # can't be zugferd-ready if base validation failed
111
+
112
+ return result
113
+
114
+
33
115
  # Resolved once at import time — deterministic across local, test, and container.
34
116
  # Check multiple locations: env var, installed package, dev layout.
35
117
  def _find_bundled_fonts() -> Path | None:
@@ -85,6 +167,17 @@ def _build_font_paths(
85
167
  return result or None
86
168
 
87
169
 
170
+ def _require_render():
171
+ """Raise a clear error if rendering dependencies are not installed."""
172
+ if not _HAS_RENDER:
173
+ raise TrustRenderError(
174
+ "Rendering requires additional dependencies. "
175
+ "Install with: pip install trustrender[render]",
176
+ code=ErrorCode.BACKEND_ERROR,
177
+ stage="import",
178
+ )
179
+
180
+
88
181
  def _render_document_pipeline(
89
182
  template_path: Path,
90
183
  data: dict,
@@ -94,7 +187,7 @@ def _render_document_pipeline(
94
187
  validate: bool = True,
95
188
  zugferd: str | None = None,
96
189
  provenance: bool = False,
97
- backend: CompileBackend | None = None,
190
+ backend: "CompileBackend | None" = None,
98
191
  timeout: float | None = None,
99
192
  display_name: str | None = None,
100
193
  ) -> RenderResult:
@@ -117,6 +210,8 @@ def _render_document_pipeline(
117
210
  If ``TRUSTRENDER_HISTORY`` is set, a stage-by-stage RenderTrace is
118
211
  recorded to the trace store after each render (success or failure).
119
212
  """
213
+ _require_render()
214
+
120
215
  import hashlib
121
216
  import time
122
217
 
@@ -574,9 +669,12 @@ def _resolve_data(data: dict | str | os.PathLike) -> dict:
574
669
  path = Path(data) if not isinstance(data, str) else None
575
670
  if path is None:
576
671
  # Could be a JSON string or a file path string
577
- candidate = Path(data)
578
- if candidate.exists() and candidate.suffix == ".json":
579
- path = candidate
672
+ try:
673
+ candidate = Path(data)
674
+ if candidate.exists() and candidate.suffix == ".json":
675
+ path = candidate
676
+ except OSError:
677
+ pass # String too long to be a path — treat as JSON below
580
678
 
581
679
  if path is not None and path.exists():
582
680
  with open(path) as f: