Files
multiarch-docker-images/archivist/config.jsonc
T
thomas 6961256538 refactor(config): rename the period helper to frPeriod
The helper renders French period labels (Janvier 2026, T2 2026, S1 2026),
so it carries the fr prefix like frDate. Config example, arch.md and
user-stories.md updated. Configs calling period(...) must switch to
frPeriod(...).
2026-11-04 22:00:36 +01:00

197 lines
9.2 KiB
JSON

{
// Example configuration for the Archivist pipeline.
// Keys in English; extracted values and rendered file names are in
// French, as they appear in the source documents.
// ==========================================================================
// fields: declaration of every field handled by the rules.
// A missing mandatory field at the end of the pipeline marks the document
// invalid and routes it to pending/ for human review.
// "template" is a regular field: rules select the naming template with it.
// ==========================================================================
"fields": [
{ "name": "emittingCompany", "type": "text", "mandatory": true },
{ "name": "documentType", "type": "enum", "mandatory": true },
{ "name": "emissionDate", "type": "date", "mandatory": true },
{ "name": "template", "type": "text", "mandatory": false },
{ "name": "bankAccount", "type": "enum", "mandatory": false },
{ "name": "address", "type": "text", "mandatory": false },
{ "name": "reference", "type": "text", "mandatory": false }
],
// ==========================================================================
// rules: an ordered tree evaluated against the document pages
// (pages array, pages[0] = page 1) and the fields already set or deleted
// by earlier rules.
//
// Each rule contains four keys:
// when (required): expr-lang expression; the rest of the rule runs only
// if it evaluates to true. String literals inside
// expressions are written as raw strings with backticks.
// set : list of assignments
// { "field": "x", "value": <fixed value> } constant
// { "field": "x", "eval": "<expression>" } evaluated in the
// same context
// delete: list of field names to unset
// rules : child rules, evaluated in order only if the parent "when"
// matched; nesting is recursive
//
// Built-in helpers:
// firstMatch(regex, texts?) returns the first match of the regex
// in the document text (first capture group when present); texts? is an
// expression evaluated to the text(s) to search: pages[0] searches
// the first page, pages[0:3] searches pages 1 to 3 (expr slice
// syntax, end exclusive), any list of strings works; omitted = the
// whole document.
// frDate(text) parses a French natural-language date ("le 19 janvier
// 2026"; the day is optional and "1er" / abbreviated months are
// accepted) into a native date, for documents whose date is not
// written in a Go layout format.
// frPeriod(value, unit, shift, layout?) renders a period instead of a
// raw date: unit = `month`|`quarter`|`half`|`year`, shift = -1/0/+1
// for the previous / containing / next period (rent receipt: 0,
// end-of-quarter charges call: +1, bank statement: -1). Returns a
// French string (`Janvier 2026`, `T2 2026`, `S1 2026`, `2026`); the
// optional layout is a Go reference-date layout applied to the
// period's first day with French month names; ignored for
// quarter/half (fixed formats).
// Strings are parsed into native dates with expr's standard date(value,
// layout) builtin (Go layout, e.g. `02/01/2006` for 15/01/2026); the
// format is carried by the rule so it can differ per document type.
// ==========================================================================
"rules": [
// --- Generic extraction, runs for every document -------------------------
// Emission date, e.g. "Paris, le 15/01/2026" -> "15/01/2026" (parsed
// into a native date; named emissionDate to avoid clashing with the
// expr date() builtin).
// Every format probe is guarded by its own "when": an unguarded
// date(firstMatch(...)) on a document that does not carry the format
// is a rule error, and a single rule error routes a perfectly
// classifiable document to pending/ (US-15).
{
"when": "firstMatch(`[0-9]{2}/[0-9]{2}/[0-9]{4}`, pages[0]) != ``",
"set": [
{ "field": "emissionDate", "eval": "date(firstMatch(`[0-9]{2}/[0-9]{2}/[0-9]{4}`, pages[0]), `02/01/2006`)" }
]
},
// Fallback: French natural-language date, e.g. "le 19 janvier 2026",
// when the page carries no numeric date.
{
"when": "firstMatch(`[0-9]{2}/[0-9]{2}/[0-9]{4}`, pages[0]) == `` && firstMatch(`(?:le )?[0-9]{1,2} [A-Za-zÀ-ÿ]{3,} [0-9]{4}`, pages[0]) != ``",
"set": [
{ "field": "emissionDate", "eval": "frDate(firstMatch(`(?:le )?[0-9]{1,2} [A-Za-zÀ-ÿ]{3,} [0-9]{4}`, pages[0]))" }
]
},
// --- Bank statements (relevés de compte) ----------------------------------
{
"when": "pages[0] contains `Relevé de compte`",
"set": [
{ "field": "documentType", "value": "Relevé de compte" },
{ "field": "template", "value": "bank_statement" }
],
"rules": [
// Issuing bank
{
"when": "pages[0] contains `BRED`",
"set": [ { "field": "emittingCompany", "value": "BRED" } ]
},
{
"when": "pages[0] contains `Fortunéo`",
"set": [ { "field": "emittingCompany", "value": "Fortunéo" } ]
},
{
"when": "pages[0] contains `Crédit Mutuel`",
"set": [ { "field": "emittingCompany", "value": "Crédit Mutuel" } ]
},
// Personal accounts, matched on the account number printed on page 1
{
"when": "pages[0] contains `12345678901`",
"set": [ { "field": "bankAccount", "value": "Compte Thomas" } ]
},
{
"when": "pages[0] contains `98765432109`",
"set": [ { "field": "bankAccount", "value": "Compte joint" } ]
}
]
},
// --- Taxe d'habitation, one child rule per address ------------------------
{
"when": "pages[0] contains `Taxe d'habitation`",
"set": [
{ "field": "documentType", "value": "Taxe d'habitation" },
{ "field": "emittingCompany", "value": "DGFiP" },
{ "field": "template", "value": "tax_notice_housing" }
],
"rules": [
{
"when": "pages[0] contains `12 rue des Lilas`",
"set": [ { "field": "address", "value": "12 rue des Lilas, Paris" } ]
},
{
"when": "pages[0] contains `34 avenue Jean Jaurès`",
"set": [ { "field": "address", "value": "34 avenue Jean Jaurès, Bordeaux" } ]
}
]
},
// --- Avis d'impôt sur le revenu --------------------------------------------
{
"when": "pages[0] contains `Impôt sur le revenu` && pages[0] contains `avis d'imposition`",
"set": [
{ "field": "documentType", "value": "Avis d'impôt sur le revenu" },
{ "field": "emittingCompany", "value": "DGFiP" },
{ "field": "template", "value": "tax_notice_income" }
]
},
// --- Generic invoices (e-commerce and others) -----------------------------
{
"when": "pages[0] contains `Facture` && pages[0] contains `TVA`",
"set": [
{ "field": "documentType", "value": "Facture" },
{ "field": "template", "value": "invoice" },
// Invoice number, e.g. "Facture n° F-2026-0123" -> "F-2026-0123"
{ "field": "reference", "eval": "firstMatch(`[Ff]acture n° ?([A-Za-z0-9-]+)`)" }
],
// Fields that do not apply to invoices, in case an earlier rule
// set them
"delete": [ "bankAccount", "address" ],
"rules": [
{
"when": "pages[0] contains `Amazon`",
"set": [ { "field": "emittingCompany", "value": "Amazon" } ]
},
{
"when": "pages[0] contains `LDLC`",
"set": [ { "field": "emittingCompany", "value": "LDLC" } ]
},
{
"when": "pages[0] contains `Cdiscount`",
"set": [ { "field": "emittingCompany", "value": "Cdiscount" } ]
}
]
}
],
// ==========================================================================
// templates: named naming templates, selected by the "template" field.
// A template is a literal string containing {expr} placeholders: the
// inside of each placeholder is an expr-lang/expr expression evaluated in
// the document context (fields, helpers, builtins) and replaced by its
// result, e.g. {emissionDate.Format(`2006-01-02`)} or
// {upper(emittingCompany)}. Unset optional fields evaluate to an empty
// string. The rendered template is the full object path under files/.
// If no rule sets "template", the "default" template is used.
// ==========================================================================
"templates": {
// A bank statement covers the month before its emission date.
"bank_statement": "{emittingCompany}/{frPeriod(emissionDate, `month`, -1)}_{documentType}_{bankAccount}.pdf",
"tax_notice_housing": "Impots/{emissionDate.Format(`2006-01-02`)}_{documentType}_{address}.pdf",
"tax_notice_income": "Impots/{emissionDate.Format(`2006-01-02`)}_{documentType}.pdf",
"invoice": "Factures/{emissionDate.Format(`2006-01-02`)}_{emittingCompany}_{reference}.pdf",
"default": "Divers/{documentType}.pdf"
}
}