Metadata-Version: 2.4
Name: forktex-documents
Version: 0.1.0
Summary: A framework for generating documents from structured data: typed blocks that render themselves, canonicals that compute their own totals, and cited mandates. Document types belong to consumers.
License-Expression: AGPL-3.0-or-later OR LicenseRef-ForkTex-Commercial
License-File: LICENSE
License-File: NOTICE
Author: FORKTEX
Author-email: info@forktex.com
Requires-Python: >=3.14,<4.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Legal Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business
Classifier: Topic :: Office/Business :: Financial :: Accounting
Classifier: Topic :: Printing
Classifier: Topic :: Text Processing :: Markup :: HTML
Classifier: Typing :: Typed
Provides-Extra: pdf
Requires-Dist: forktex (>=0.10,<0.11)
Requires-Dist: jinja2 (>=3.1)
Requires-Dist: pydantic (>=2.12)
Requires-Dist: weasyprint (>=62) ; extra == "pdf"
Project-URL: Bug Tracker, https://github.com/forktex/forktex-documents/issues
Project-URL: Changelog, https://github.com/forktex/forktex-documents/blob/master/CHANGELOG.md
Project-URL: Documentation, https://github.com/forktex/forktex-documents/tree/master/docs
Project-URL: Homepage, https://forktex.com
Project-URL: Repository, https://github.com/forktex/forktex-documents
Description-Content-Type: text/markdown

# forktex-documents

A framework for generating documents from structured data. It ships the
vocabulary and the machinery — **not** document types: an invoice, a contract, a
delivery note are things a consumer defines, because what they must contain is
a fact about a business and a jurisdiction, not about a renderer.

```
data (JSON)  →  canonical (Pydantic)  →  block tree  →  HTML → PDF
                        ↘  read directly for a machine projection (XML, JSON…)
```

A PDF here is a **view over an object**, never a source of truth. The canonical
record is the document; the page is one projection of it. A machine projection
reads the canonical directly rather than the block tree — you cannot recover
"19% VAT on this line" from a table cell that says `19%`, which is why the two
representations both exist.

## Install

```bash
pip install forktex-documents          # canonical, blocks, HTML
pip install "forktex-documents[pdf]"   # + PDF, via WeasyPrint
```

PDF is an extra because WeasyPrint pulls native libraries (cairo, pango) that a
consumer producing only HTML or a machine projection should not have to install.

## What it does

A consumer declares its own canonical, and gets validation, composition and
rendering from the framework:

```python
from forktex_documents import FamilyCanonical, Mandate, ParagraphBlock, known

@known
class DeliveryNote(FamilyCanonical):
    family = "logistics"
    mandates = (
        Mandate(
            path="carrier",
            authority="acme-logistics",
            reference="ops:carrier-is-named",
            reason="A note with no carrier cannot be chased when the goods do not arrive.",
        ),
    )
    reference: str
    carrier: str

    def blocks(self, profile):
        return (ParagraphBlock(text=f"Carried by {self.carrier}."),)

note = DeliveryNote(reference="DN-1", carrier="Speedy")
note.check()                      # raises if a mandate is unsatisfied
note.blocks(profile)              # a typed block tree, ready to project
```

See [`examples/showcase.py`](examples/showcase.py) for a document exercising
every block kind.

## The ideas worth knowing

**Derived values are never stored.** A consumer's totals, VAT subtotals and
expiry dates are `computed_field`s on its own canonical — and this package is
what makes that hold. Every model sets `extra="forbid"`, so a record carrying a
hand-written `total` is a validation error rather than a silent disagreement,
and a stored total that was tampered with is dropped and recomputed on load.
A structural guarantee rather than a review rule.

**Mandates are cited and conditional.** What a jurisdiction requires is data:
a path, the authority, the legal reference, and *why*. Conditions read computed
flags, so a retail stock-reception note demands a selling price while a
cost-method one does not, and a delivery showing quantity differences demands a
three-member committee and a third copy — because those are the rules the form
actually carries.

**Fiscal facts are time-versioned.** VAT rates resolve against the document's
own date, so re-rendering a July 2025 Romanian invoice reproduces 19% rather
than silently restating it at the current 21%. A date no schedule covers raises
instead of guessing.

**Blocks render themselves.** Twenty-five typed block kinds form a closed
discriminated union, each emitting its own HTML. There is no template language
deciding layout — the only templating is sandboxed *text* substitution for
locale fragments, where a clause has placeholders to resolve.

**Identifiers are validated, not merely shaped.** CUI and CNP check digits,
IBAN mod-97 across every country's length rule, per-member-state VAT formats.
A transposed digit in a national id files a real document against the wrong
person; the checksum is what catches it.

## What is *not* here

No invoice, no contract, no delivery note, and no jurisdiction. There is no
`RO_VAT_RATES`, no CUI or CNP check digit, and no default country, language or
currency anywhere — a default jurisdiction is a wrong jurisdiction for everyone
else, quietly. A consumer supplies its own:

- a `LegislationProfile` with its date format, separators, currency and mandates
- a `VatSchedule` with its rates and their validity windows
- national identifier validators, registered into `NATIONAL_TAX_ID_VALIDATORS`
- the numbers already spent, passed to a register as `BURNED`

## Layout

```
domain/     blocks, canonical, mandates, entities, fiscal, localization, theme,
            registry, series  ↑ no I/O — `grep Path domain/` is empty, by test
render/     the HTML projector, inline SVG charts, the asset port
```

Imports run one way: `domain → render → facade`.

## Development

```bash
make install     # poetry install --with dev
make ci          # format, lint, licences, docs, types, audit, tests, build, smoke
make reference   # rewrite committed reference renders — then read the diff
make docs        # render the design records in sources/ into docs/
```

`make ci` re-renders every reference document and diffs it byte-for-byte
against the committed HTML. A generated artefact that was hand-edited, or a
change that altered output nobody intended, fails the build.

The design notes in [`docs/`](docs/) are generated the same way, from JSON
records in [`sources/`](sources/) via
[`forktex-knowledge`](https://pypi.org/project/forktex-knowledge/) — the
library eats its own principle, so a hand-edited page fails the gate rather
than becoming a second author. Start with
[everything-is-a-document](docs/everything-is-a-document.md) for why a page is
never a source of truth, then
[mandates-are-cited-data](docs/mandates-are-cited-data.md) for why a legal
requirement travels with its citation.

## Licence

Dual-licensed: **AGPL-3.0-or-later**, or a commercial licence from FORKTEX
S.R.L. for use in proprietary products, SaaS deployments, or any context where
the AGPL's obligations cannot be met. See `LICENSE` and `NOTICE`, or contact
info@forktex.com.

