Install
openclaw skills install organisation-documentsSkill central de l'assistant comptable. Réceptionne, classe et nomme automatiquement les documents comptables (factures, relevés bancaires) par client / année / mois / nature, et déduit clients.json depuis les relevés bancaires. À INVOQUER SYSTÉMATIQUEMENT et SANS DEMANDER L'AUTORISATION dès que (a) un e-mail arrive avec une PJ PDF/image, (b) un e-mail contient un mot-clé comptable (facture, invoice, reçu, paiement, TVA, total, acompte, avoir, relevé), (c) le comptable demande de traiter / classer / organiser un document, (d) un fichier est déposé dans l'inbox, (e) le comptable répond à une question d'identification de client. L'extraction et le classement sont faits par scripts/main.py + scripts/extract.py — aucun champ n'est jamais deviné à l'œil.
openclaw skills install organisation-documentsorganisation-documentsMoteur d'entrée du domaine comptable. Réception → identification du client → classement → indexation → rapport. Le travail réel est fait par
scripts/main.py(qui appellescripts/extract.py). Ce skill = quand le lancer + comment dialoguer avec le comptable.
Pour chaque invocation, la SEULE action de classement correcte est d'exécuter la commande :
python3 scripts/main.py <dossier_inbox> <racine_clients>
puis de lire <racine_clients>/_report.json et de le relayer au comptable.
invoice_id = "N", "des", "um-rix" au lieu de F1-2026-0001 ; total_ttc = 0.00 au lieu du vrai montant). Ces erreurs cassent ensuite tout le rapprochement de rapprochement-bancaire.<racine_clients>/ à la main (move, copy, write). C'est le script qui le fait.invoice_id quand le PDF n'en contient pas de lisible. Si extract.py ne le trouve pas, le script écrit SANS-NUM ; n'essaie PAS de fabriquer mieux à partir du nom de l'émetteur ou de la description._report.json produit.questions n'est pas vide.Signale l'erreur exacte (stderr) au comptable. NE PAS "rattraper" en classant manuellement — c'est précisément ce qui produit les filenames cassés. Si le binaire pdftotext manque (poppler-utils non installé), demande-le et stoppe.
Pourquoi cette règle est aussi stricte : on a déjà eu plusieurs runs où l'agent a improvisé le classement et produit des
invoice_idbidons (N,des,um-rix). À chaque fois,rapprochement-bancaireflagge ensuite desfacture_manquantequi n'en sont pas, le comptable perd du temps à investiguer. Le script ne fait pas ces erreurs.
Réflexe par défaut face à tout document entrant :
Ne pas utiliser pour : FEC (→ fec-parser), relances (→ relances), rapprochement bancaire (→ rapprochement-bancaire).
python3 scripts/main.py <dossier_inbox> [<racine_clients>]
# racine_clients par défaut : ~/.openclaw/workspace/clients
Le script :
_index.json).scripts/extract.py (pdftotext -layout + règles déterministes). Aucun montant, numéro ou nom n'est inventé.clients.json (statut: auto-from-bank-statement), classe le relevé dans <slug>/<AAAA>/<MM>/bank-statements/.clients.json (exact puis fuzzy ≥ 0.82).
invoices/out/, destinataire ≈ client → invoices/in/._a-identifier/ + une question dans le rapport (cf. onboarding ci-dessous)._a-identifier/ + question (cas rare)._incomplet/ + ligne dans le rapport._non-attribue/.clients/clients.json, clients/_index.json, clients/_report.json, et imprime un résumé.Après l'exécution, lire clients/_report.json et :
_report.json → questions n'est pas vide → poser ces questions au comptable (cf. format) ;_report.json → incomplete n'est pas vide → signaler ces pièces ;_a-identifier/_incomplet), considérer le post-traitement rapprochement-bancaire déclenché (le batch repassera de toute façon).Trois signaux fiables, dans l'ordre (gérés par le script) :
contacts[].email, domaine ∈ domains, ou SIREN du document ∈ siren d'un client.clients.json — seulement si un seul des deux côtés matche.Si aucun signal fiable → on ne devine pas. Le document part en _a-identifier/ et le comptable tranche une fois.
Une facture contient toujours deux entreprises (émetteur, destinataire). Quand aucune n'est connue — et qu'aucun relevé ne couvre l'une d'elles — le script ne choisit pas : il met la pièce dans _a-identifier/ et ajoute une question.
Étape 1 (automatique) — la question apparaît dans _report.json → questions :
« Document : facture
TUYO-2024-087(348,50 € TTC). Émetteur « TUYO SARL », destinataire « Corse Plomberie ». Lequel est votre client ? »
L'agent la relaie au comptable. Aucune écriture dans clients.json à ce stade.
Étape 2 (à la réponse du comptable) — quand le comptable répond « c'est X » :
clients.json de X :
{ "slug": "<slug X>", "raisonSociale": "X", "statut": "confirmed",
"confiance": 1.0, "aValider": false,
"siren": ["<si lisible sur la pièce>"], "contacts": [{"email": "<expéditeur>"}],
"domains": ["<si e-mail pro>"], "sources": ["accountant-confirmation"] }
python3 scripts/main.py clients/_a-identifier <racine_clients> : avec X désormais dans clients.json, les pièces de _a-identifier/ liées sont classées (sens in/out déterminé) et sortent du dossier.À partir de là, tout document futur du même expéditeur (même e-mail) est attribué automatiquement.
Déplacer la pièce de _a-identifier/ vers _non-attribue/. Pas de suivi comptable.
clients.json — structure[
{
"slug": "corse-plomberie",
"raisonSociale": "Corse Plomberie",
"statut": "auto-from-bank-statement | confirmed",
"confiance": 0.9,
"aValider": false,
"siren": ["812345678"],
"contacts": [{ "email": "jeanmichel@gmail.com" }],
"domains": ["corseplomberie.fr"],
"sources": ["bank-statement"]
}
]
Fichier construit et maintenu par ce skill seul (via scripts/main.py + ajouts manuels lors de l'onboarding). Aucun autre skill ne l'écrit. Le comptable ne l'édite pas à la main : il répond aux questions.
clients/
├── clients.json ← liste des clients (déduite des relevés + confirmations)
├── _index.json ← sha256 → chemin classé (dédup)
├── _report.json ← rapport de la dernière exécution
├── _a-identifier/ ← factures dont le client n'est pas confirmé (onboarding en cours)
├── _incomplet/ ← pièces dont l'extraction a échoué (date / montant manquant)
├── _non-attribue/ ← ni facture ni relevé exploitable
├── _cabinet/ ← documents internes du cabinet
└── <slug>/
└── <AAAA>/<MM>/
├── bank-statements/
│ └── <AAAA-MM>_<banque>.pdf
└── invoices/
├── in/
│ └── <AAAA-MM-JJ>_<N°Facture>_<Contrepartie>_<MontantTTC>.pdf
└── out/
└── <AAAA-MM-JJ>_<N°Facture>_<Contrepartie>_<MontantTTC>.pdf
Convention de nom (produite par le script, jamais à la main) :
N°Facture : numéro réel extrait du PDF (ex F1-2026-0003), alphanumérique+tirets ; SANS-NUM si absent.Contrepartie : 14 premiers caractères significatifs du nom de l'autre partie, sans accents/espaces.MontantTTC : point décimal, sans séparateur de milliers, sans symbole (ex 3336.78).AAAA-MM-JJ : date d'émission du document.extract.py)RELEVÉ DE COMPTE/EXTRAIT DE COMPTE/ACCOUNT STATEMENT, ou nom de banque + RELEVÉ, ou Solde d'ouverture/Solde de clôture. Exception : si le document contient aussi (FACTURE/INVOICE) ET (SIRET/TVA) → c'est une facture.FACTURE/INVOICE/BON DE FACTURATION · bloc SIRET/TVA/SIREN · bloc destinataire (FACTURÉ À/DESTINATAIRE/BILL TO) · TOTAL HT/TOTAL TTC/NET À PAYER._non-attribue/.pdftotext, regex, SHA-256, pipeline, extract.py, chemins absolus système._a-identifier/, _incomplet/, _non-attribue/ conservent les pièces._incomplet/ plutôt qu'une valeur inventée.Pour chaque facture / relevé classé dans un vrai dossier client (pas _a-identifier/ ni _incomplet/), le moteur d'état rapprochement-bancaire reprendra. Émettre :
{ "trigger": "rapprochement-bancaire", "client": "<slug>" }
(Le batch repasse de toute façon sur tous les clients ; ce trigger ne fait qu'accélérer.)
Relances : ne jamais appeler directement. Produire au plus :
{ "trigger_suggestion": "relances", "reason": "invoice status may require update" }
organisation-documents = moteur d'entrée — classe les pièces, identifie les clients (relevé ou question), maintient clients.json
rapprochement-bancaire = moteur d'état — rapproche, reconstruit l'état comptable
relances = moteur de décision différée
Le travail mécanique est dans les scripts. Ce skill décide quand les lancer et comment en parler au comptable.