{"id":"f5dd339d-31c5-4b2c-9a64-2bb4ecbb6173","entityType":"agent","slug":"clawhub-trendex-rapprochement-bancaire","name":"Rapprochement Bancaire","canonicalUrl":"https://www.xpersona.co/agent/clawhub-trendex-rapprochement-bancaire","canonicalPath":"/agent/clawhub-trendex-rapprochement-bancaire","generatedAt":"2026-10-10T05:40:21.417Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-09T21:02:54.938Z","emptyReason":null},"description":"Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valid... Skill: Rapprochement Bancaire Owner: trendex Summary: Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valid... Tags: latest:1.0.2 Version history: v1.0.2 | 2026-06-03T16:16:30.543Z | user Version 1.0.2 - Nettoyage majeur : suppression de fichiers inutilisés (scripts annexes, fichier méta, skill-card). - Ajout d’un","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 2K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s171rnymrzgb46f7qxnxd5034586ma2f:rapprochement-bancaire","sourceUrl":"https://clawhub.ai/trendex/rapprochement-bancaire","homepage":"https://clawhub.ai/trendex/skills/rapprochement-bancaire","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/trendex/rapprochement-bancaire","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/trendex/skills/rapprochement-bancaire","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":66,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valid..."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T21:02:54.938Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T21:02:54.938Z","emptyReason":null},"stars":null,"forks":null,"downloads":1999,"packageName":null,"latestVersion":"1.0.2","tractionLabel":"2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T21:02:54.938Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T21:02:54.938Z","lastCrawledAt":"2026-10-09T21:02:54.938Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T21:02:54.938Z","lastVerifiedAt":null,"highlights":[{"version":"1.0.2","createdAt":"2026-06-03T16:16:30.543Z","changelog":"Version 1.0.2 - Nettoyage majeur : suppression de fichiers inutilisés (scripts annexes, fichier méta, skill-card). - Ajout d’un fichier .gitignore. - La documentation est allégée et recentrée : précisions sur l’exécution du script, les statuts, anomalies et relances. - La section extraction/vision détaillée et mode supervisé sont retirés du SKILL.md.","fileCount":9,"zipByteSize":28549},{"version":"1.0.1","createdAt":"2026-06-02T15:07:05.226Z","changelog":"Version 1.0.1 — Retour au système de fichiers sidecar par type (followup, relances, anomalies). Extraction et rapprochement bancaire améliorés. - Remise en œuvre du système multi-fichiers (`followup.json`, `relances.json`, `anomalies.json`, `needs_vision.json`) à la place du fichier unique `rapprochement.json`. - Extraction bancaire robuste : découpage par colonne explicite (gestion fiable des colonnes solde/débit/crédit), profils par banque, et ajout de la file de documents à retraiter par vision (OCR). - Ajout de scripts utilitaires : dédoublonnage, export CSV, résolution sur fichiers vision, et tests d’extraction. - L’humain reste au centre du process : les relances sont des propositions, la supervision humaine reste indispensable. - Documentation complètement réécrite pour refléter la nouvelle architecture.","fileCount":13,"zipByteSize":57080},{"version":"1.0.0","createdAt":"2026-05-21T08:36:31.783Z","changelog":"Major update","fileCount":9,"zipByteSize":31021},{"version":"0.2.0","createdAt":"2026-05-13T12:08:04.310Z","changelog":"Réécriture déterministe : scripts/main.py + extract.py, rapprochement 2-passes (REF/FACT direct puis fuzzy montant±1€ + similarité nom), validation TVA, anomalies bloquantes/non-bloquantes, sortie JSON par client + rapport consolidé","fileCount":8,"zipByteSize":27310},{"version":"0.1.0","createdAt":"2026-05-12T12:26:20.662Z","changelog":"Initial release: bank reconciliation for French accounting firms.","fileCount":6,"zipByteSize":15877}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s171rnymrzgb46f7qxnxd5034586ma2f:rapprochement-bancaire","setupComplexity":"low","setupSteps":["Setup complexity is LOW. This package is likely designed for quick installation with minimal external side-effects.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T05:40:21.413Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-trendex-rapprochement-bancaire/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-09T21:02:54.938Z","emptyReason":null},"readme":"Skill: Rapprochement Bancaire\n\nOwner: trendex\n\nSummary: Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valid...\n\nTags: latest:1.0.2\n\nVersion history:\n\nv1.0.2 | 2026-06-03T16:16:30.543Z | user\n\nVersion 1.0.2\n\n- Nettoyage majeur : suppression de fichiers inutilisés (scripts annexes, fichier méta, skill-card).\n- Ajout d’un fichier .gitignore.\n- La documentation est allégée et recentrée : précisions sur l’exécution du script, les statuts, anomalies et relances.\n- La section extraction/vision détaillée et mode supervisé sont retirés du SKILL.md.\n\nv1.0.1 | 2026-06-02T15:07:05.226Z | user\n\nVersion 1.0.1 — Retour au système de fichiers sidecar par type (followup, relances, anomalies). Extraction et rapprochement bancaire améliorés.\n\n- Remise en œuvre du système multi-fichiers (`followup.json`, `relances.json`, `anomalies.json`, `needs_vision.json`) à la place du fichier unique `rapprochement.json`.\n- Extraction bancaire robuste : découpage par colonne explicite (gestion fiable des colonnes solde/débit/crédit), profils par banque, et ajout de la file de documents à retraiter par vision (OCR).\n- Ajout de scripts utilitaires : dédoublonnage, export CSV, résolution sur fichiers vision, et tests d’extraction.\n- L’humain reste au centre du process : les relances sont des propositions, la supervision humaine reste indispensable.\n- Documentation complètement réécrite pour refléter la nouvelle architecture.\n\nv1.0.0 | 2026-05-21T08:36:31.783Z | user\n\nMajor update\n\nv0.2.0 | 2026-05-13T12:08:04.310Z | user\n\nRéécriture déterministe : scripts/main.py + extract.py, rapprochement 2-passes (REF/FACT direct puis fuzzy montant±1€ + similarité nom), validation TVA, anomalies bloquantes/non-bloquantes, sortie JSON par client + rapport consolidé\n\nv0.1.0 | 2026-05-12T12:26:20.662Z | user\n\nInitial release: bank reconciliation for French accounting firms.\n\nArchive index:\n\nArchive v1.0.2: 9 files, 28549 bytes\n\nFiles: README.md (7040b), references/csv-schema.md (7202b), references/matching-rules.md (9018b), references/structure-cible.md (14917b), scripts/extract.py (12883b), scripts/main.py (16432b), skill-card.md (2313b), SKILL.md (7893b), _meta.json (141b)\n\nFile v1.0.2:SKILL.md\n\n---\nname: rapprochement-bancaire\ndescription: Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valide la TVA, maintient followup.json / relances.json / anomalies.json. Le travail réel est fait par scripts/main.py (qui appelle scripts/extract.py). Ne traite jamais les e-mails, ne classe jamais de documents.\nlicense: Interne — usage privé OpenClaw\n---\n\n# Skill `rapprochement-bancaire`\n\n> Moteur d'état. Travaille uniquement sur l'arborescence `clients/<slug>/...` produite par `organisation-documents`.\n> **Le travail réel est fait par `scripts/main.py`** (qui appelle `scripts/extract.py` pour toute l'extraction de texte). Ce skill = quand le lancer + comment rendre compte.\n\n---\n\n## ⚠️ Règle absolue — EXÉCUTION DU SCRIPT (non négociable)\n\n**Pour chaque invocation, la SEULE action correcte est d'exécuter la commande :**\n\n```bash\npython3 scripts/main.py <racine_clients>\n```\n\npuis de lire les `followup.json` / `relances.json` / `anomalies.json` par client + le `compta_batch_report_<date>.json` consolidé, et de relayer au comptable.\n\n### ❌ INTERDIT\n\n- **Réimplémenter le rapprochement en Python inline / pseudo-code.** La logique (Pass 1 par référence, Pass 2 fuzzy, validation TVA, détection des anomalies, statuts overdue/partial, génération des relances) est dans `scripts/main.py`. La refaire à la main est garanti de diverger.\n- **Écrire `followup.json` / `relances.json` / `anomalies.json` à la main.** C'est le script qui le fait, dans son format exact (liste d'objets avec les champs `invoice_id`, `type`, `amount`, `status`, `bank_matched`, `matched_tx`, …). Toute autre forme (dict keyé, champs `payments` / `amount_paid` ad hoc) casse les lectures ultérieures.\n- **Modifier le format des fichiers de sortie.** Le format est contractuel entre ce skill et d'éventuels skills aval (`relances`, dashboards).\n\n### ✅ OBLIGATOIRE\n\n1. Exécuter la commande shell ci-dessus.\n2. Lire les fichiers produits.\n3. Relayer au comptable un tableau lisible par client (factures payées / en attente / partielles / overdue, anomalies — en mettant en avant les **bloquantes**).\n\n### Si le script échoue\n\nSignale l'erreur exacte (stderr). Ne rejoue PAS le batch à la main.\n\n> Pourquoi : on a déjà eu un run où l'agent a produit un `followup.json` au mauvais format (dict avec clés `\"in/N\"`, champs `payments`/`amount_paid`/`amount_remaining` au lieu de `bank_matched`/`status`). Conséquence : aucun outil ni skill aval ne pouvait l'exploiter. Le script génère le bon format à tous les coups.\n\n---\n\n## Exécution\n\n```bash\npython3 scripts/main.py [<racine_clients>]\n# racine_clients par défaut : ~/.openclaw/workspace/clients\n```\n\nÀ lancer :\n- une fois par jour (idéalement la nuit), sur tous les clients ;\n- ou immédiatement après un trigger `rapprochement-bancaire` émis par `organisation-documents`.\n\nLe script :\n\n1. **Parcourt** `clients/*` en ignorant les dossiers techniques `_*` (`_a-identifier`, `_incomplet`, `_non-attribue`, `_cabinet`).\n2. **Périodes traitées** : mois courant + mois précédent, plus tout mois ancien non verrouillé (`batch.lock.json` absent). Un mois verrouillé n'est pas retraité.\n3. **Factures** : nom de fichier conventionnel (`AAAA-MM-JJ_N°Facture_Contrepartie_MontantTTC.pdf`) → `invoice_id` + montant. Si le nom n'est pas exploitable, lecture du contenu via `extract.py`. Si toujours rien → anomalie `facture_illisible`.\n4. **Relevés bancaires** : transactions extraites ligne par ligne via `extract.py` (`DATE | LIBELLÉ | MONTANT | CR/DB`), avec la référence facture si le libellé contient `REF <id>` ou `FACT <id>`. Aucune transaction extractible → anomalie `releve_non_parseable`.\n5. **Rapprochement** (montant comparé en valeur absolue : une facture `out` est encaissée par un crédit, une `in` réglée par un débit) :\n   - **Pass 1** — réf facture : transaction dont `invoice_ref == facture.invoice_id`. Montant exact (±1 €) → `paid` ; montant inférieur → `partial` (conserve `amount_paid`, `amount_remaining`) ; supérieur → `paid` + `overpaid_by`.\n   - **Pass 2** — fuzzy : `|montant| ±1 €` ET similarité libellé / contrepartie ≥ 0.6.\n   - aucun match + échéance dépassée → `overdue`.\n6. **Validation TVA** de chaque facture : si `|TVA déclarée − TVA attendue| / TVA attendue > 5 %` (TVA attendue = `TOTAL HT × taux`) → anomalie `tva_incorrecte` (TVA 0 % / exonération ignorée).\n7. **Anomalies** : voir tableau ci-dessous.\n8. **Relances** : `overdue` / `partial` / `unpaid` hors délai → step selon ancienneté.\n9. Écrit `clients/<slug>/followup.json`, `relances.json`, `anomalies.json`, et un rapport consolidé `compta_batch_report_<date>.json`.\n\n**Après l'exécution**, lire le rapport et relayer au comptable un tableau lisible par client (factures payées / en attente, relances, anomalies — en mettant en avant les anomalies **bloquantes**). Jamais de chemins techniques.\n\n---\n\n## Statuts de facture (`followup.json`)\n\n| Statut | Sens |\n|--------|------|\n| `unpaid` | non échue, aucun paiement |\n| `paid` | paiement confirmé (rapprochement réussi) |\n| `partial` | paiement partiel — `amount_paid` + `amount_remaining` conservés |\n| `overdue` | échéance dépassée, aucun paiement |\n\n---\n\n## Anomalies (`anomalies.json`)\n\n**Bloquantes** (empêchent la clôture de la période) :\n\n| Type | Condition |\n|------|-----------|\n| `doublon_paiement` | même date + montant + libellé |\n| `tva_incorrecte` | écart TVA calculée / déclarée > 5 % |\n| `facture_manquante` | une ligne du relevé cite un n° de facture (`REF`/`FACT`) absent du dossier, montant > 1 000 € |\n| `paiement_orphelin` | crédit > 1 000 € sans aucune référence ni facture |\n\n**Non bloquantes** (signalées, clôture possible) :\n\n| Type | Condition |\n|------|-----------|\n| `facture_manquante` | n° de facture cité au relevé mais absent, montant ≤ 1 000 € |\n| `paiement_orphelin` | crédit ≤ 1 000 € sans référence |\n| `releve_non_parseable` | aucune transaction extractible d'un relevé |\n| `facture_illisible` | facture dont ni le nom ni le contenu ne donnent n° + montant |\n| `invoice_overdue` | facture non payée, échéance dépassée |\n\n> `facture_manquante` ≠ `paiement_orphelin` : le premier = paiement qui **cite** un n° de facture qu'on n'a pas reçu (le client a oublié de transmettre la pièce) ; le second = encaissement sans aucune référence.\n\n---\n\n## Relances (`relances.json`)\n\n| Ancienneté du retard | Step |\n|----------------------|------|\n| ≤ 30 j | 1 |\n| ≤ 60 j | 2 |\n| ≤ 90 j | 3 |\n| > 90 j | escalation |\n\nPour `partial` : mention explicite `\"Solde restant dû : X,XX €\"`.\n\n---\n\n## Clôture de période\n\nLe script crée `clients/<slug>/<AAAA>/<MM>/batch.lock.json` quand : aucune anomalie **bloquante** sur la période, hash des fichiers stable depuis 7 jours, aucun statut `unpaid`/`overdue` non justifié. Les périodes verrouillées ne sont jamais retraitées sauf changement de hash.\n\n---\n\n## Règles critiques\n\n- Ne jamais supprimer de données. Ne jamais retraiter un mois verrouillé.\n- `followup.json` est un **cache reconstructible** : les PDFs classés restent la vérité. Le batch peut tout recalculer depuis zéro.\n- `clients.json` est lu seulement (maintenu par `organisation-documents`), jamais écrit ici.\n- Toute l'extraction de texte passe par `scripts/extract.py` — source unique, pas de logique de parsing dupliquée ici.\n\n---\n\n## Philosophie\n\n```\norganisation-documents  →  classe les pièces, déduit clients.json\nrapprochement-bancaire            →  rapproche, valide, reconstruit l'état comptable  (scripts/main.py)\nrelances                →  décision différée\n```\n\nLe système doit pouvoir être recalculé intégralement à partir des documents classés.\n\nFile v1.0.2:README.md\n\n# rapprochement-bancaire\n\n> Claude skill for **French accounting firms** (`cabinets comptables`). Reads the `clients/<slug>/...` tree produced by [`organisation-documents`](https://github.com/developers-trendex/organisation-documents), reconciles each invoice with its bank-statement transaction, validates VAT, and writes one `followup.json` / `relances.json` / `anomalies.json` per client plus a consolidated report. Deterministic script-driven.\n\n## What it does\n\nFor each invocation, the skill runs **one command** :\n\n```bash\npython3 scripts/main.py <clients_root>\n```\n\nThat script:\n\n1. Walks every client folder under `<clients_root>` (skipping `_a-identifier/`, `_incomplet/`, `_non-attribue/`, `_cabinet/`).\n2. For each active period (current + previous month, plus any older month without `batch.lock.json`) :\n   - Reads invoices from `invoices/in/` and `invoices/out/`.\n   - Reads transactions from `bank-statements/` via `scripts/extract.py` (one line per transaction, with the `REF`/`FACT <id>` reference captured when present).\n3. Reconciles in two passes (amount compared in absolute value — an `out` invoice is settled by a credit, an `in` by a debit) :\n   - **Pass 1** — direct match by invoice reference (`REF` / `FACT` in the bank label). Exact amount (±1 €) → `paid` ; lower amount → `partial` with `amount_paid` and `amount_remaining` ; higher → `paid` with `overpaid_by`.\n   - **Pass 2** — fuzzy : `|amount| ±1 €` **and** counterparty-name similarity ≥ 0.6.\n   - No match + due date passed → `overdue`.\n4. Validates VAT on each invoice : if `|VAT declared − TOTAL HT × rate| / expected > 5 %`, flags a blocking `tva_incorrecte` anomaly (exempted-VAT invoices are silently ignored).\n5. Detects other anomalies (see below).\n6. Generates relances based on lateness (`overdue`, `partial`, `unpaid` past due).\n7. Writes per-client JSON files + `compta_batch_report_<date>.json` consolidated.\n\nThe agent only **reads the JSON files and relays the summary to the accountant** — payments, late invoices, anomalies (highlighting blocking ones), no technical paths.\n\n## Statuses (`followup.json`)\n\n| Status     | Meaning                                                                   |\n| ---------- | ------------------------------------------------------------------------- |\n| `unpaid`   | Not yet due, no payment                                                   |\n| `paid`     | Payment confirmed                                                         |\n| `partial`  | Partial payment — `amount_paid` + `amount_remaining` kept                 |\n| `overdue`  | Due date passed, no payment                                               |\n\n## Anomalies (`anomalies.json`)\n\n**Blocking** (period cannot be locked) :\n\n| Type                          | Condition                                                                                       |\n| ----------------------------- | ----------------------------------------------------------------------------------------------- |\n| `doublon_paiement`            | same date + amount + label                                                                      |\n| `tva_incorrecte`              | calculated/declared VAT gap > 5 %                                                                |\n| `facture_manquante`           | a bank line references an invoice number (`REF`/`FACT`) absent from the folder, amount > 1 000 €|\n| `paiement_orphelin`           | credit > 1 000 € with no reference and no matching invoice                                       |\n\n**Non-blocking** (signaled, period can still be locked) :\n\n| Type                          | Condition                                                                                       |\n| ----------------------------- | ----------------------------------------------------------------------------------------------- |\n| `facture_manquante`           | as above, amount ≤ 1 000 €                                                                      |\n| `paiement_orphelin`           | credit ≤ 1 000 € with no reference                                                              |\n| `releve_non_parseable`        | no transaction extractable from a statement PDF                                                  |\n| `facture_illisible`           | neither filename nor PDF content yield a number + amount                                         |\n| `invoice_overdue`             | unpaid past due date                                                                            |\n\n> `facture_manquante` ≠ `paiement_orphelin` : the former is a payment that **cites** an invoice number we never received (client forgot to send it) ; the latter is a credit with no reference at all.\n\n## Relances (`relances.json`)\n\n| Lateness                | Step          |\n| ----------------------- | ------------- |\n| ≤ 30 days               | 1             |\n| ≤ 60 days               | 2             |\n| ≤ 90 days               | 3             |\n| > 90 days               | `escalation`  |\n\nA `partial` invoice gets a relance with explicit `Solde restant dû : X,XX €`.\n\n## Period closing\n\nA period is locked (`batch.lock.json`) when : no blocking anomaly, file hashes stable for 7 days, no unjustified `unpaid` / `overdue`. Locked periods are never reprocessed unless a file hash changes.\n\n## Prerequisite\n\nThe script calls `pdftotext` (package `poppler-utils`). Install it once on the runtime :\n\n```bash\napt install poppler-utils\n```\n\n## Output files\n\n```\nclients/<slug>/\n├── followup.json         ← all invoices with their reconciliation status\n├── relances.json         ← invoices needing follow-up, with step + suggested next contact date\n├── anomalies.json        ← all anomalies for this client (blocking flag included)\n└── <AAAA>/<MM>/batch.lock.json   ← present when the period is closed\n```\n\nPlus a consolidated `compta_batch_report_<YYYY-MM-DD>.json` at the workspace root.\n\n## Companion skill\n\n[`organisation-documents`](https://github.com/developers-trendex/organisation-documents) — receives, identifies the firm's clients, and classifies documents into the tree this skill consumes.\n\n## Files\n\n| File                              | Purpose                                                                |\n| --------------------------------- | ---------------------------------------------------------------------- |\n| `SKILL.md`                        | Skill definition (French) — the \"when and how to relay\" layer          |\n| `scripts/main.py`                 | Entrypoint — reconciliation, anomalies, relances, report               |\n| `scripts/extract.py`              | Deterministic extractor (only place that touches `pdftotext`)          |\n| `references/matching-rules.md`    | Reference — matching cascade, normalization, edge cases                |\n| `references/structure-cible.md`   | Path / naming contract (duplicated from `organisation-documents`)      |\n\n## License\n\nInternal — OpenClaw private use.\n\nFile v1.0.2:_meta.json\n\n{\n  \"ownerId\": \"kn7ethsmh4zep86d7ew9wsns2x86jwhr\",\n  \"slug\": \"rapprochement-bancaire\",\n  \"version\": \"1.0.2\",\n  \"publishedAt\": 1780503390543\n}\n\nFile v1.0.2:references/csv-schema.md\n\n# Référence — Schéma CSV de rapprochement & état persisté\n\n> Référence chargée à la demande par `rapprochement-bancaire`. Format du CSV, enum `anomalie`, schéma de `rapprochement-state.json`.\n\n---\n\n## Localisation du CSV\n\n```\nclients/<slug>/<AAAA>/<MM>/rapprochement.csv\n```\n\nUn fichier par (client, mois). Réécrit en entier à chaque run du mois concerné (idempotent). Pas d'append.\n\n---\n\n## Encodage et locale\n\n- **Encoding** : UTF-8 avec BOM (`﻿` en tête) — pour Excel français qui sinon mojibake.\n- **Séparateur** : `;` (semicolon) — convention française, évite la confusion avec décimales `,`.\n- **Décimal** : `,` (virgule).\n- **Date** : `AAAA-MM-JJ` (ISO 8601, lisible par tout tableur).\n- **Saut de ligne** : `\\r\\n` (CRLF) — compatibilité Windows / Excel.\n- **Quotes** : `\"` autour des champs qui contiennent `;`, `\"`, ou `\\r\\n`. Doubler les `\"` internes (`\"\"`).\n\n---\n\n## Colonnes du CSV\n\n| #  | Colonne                | Type          | Description                                                                                  |\n| -- | ---------------------- | ------------- | -------------------------------------------------------------------------------------------- |\n| 1  | `ligne`                | int           | Numéro de ligne séquentiel (1, 2, 3, …) — facilite la référence dans les conversations       |\n| 2  | `source`               | enum          | `transaction` (ligne de relevé), `facture_non_payee` (facture sans transaction)              |\n| 3  | `compte`               | string        | Banque ou compte (ex : `BNP-courant`, `Qonto-pro`) — vide si `source=facture_non_payee`      |\n| 4  | `date`                 | date          | Date transaction OU date d'émission de la facture                                            |\n| 5  | `libelle`              | string        | Libellé brut du relevé ou raison sociale + n° facture                                       |\n| 6  | `montant`              | decimal       | Signé (`+` crédit, `-` débit)                                                                |\n| 7  | `categorie_auto`       | enum          | `match` / `frais_bancaires` / `rh_charges` / `transfert_interne` / `non_categorise`         |\n| 8  | `match_facture`        | string        | Numéro de facture matchée — vide si non-match                                                |\n| 9  | `match_emetteur`       | string        | Raison sociale de la facture matchée                                                         |\n| 10 | `match_mois_origine`   | string        | `AAAA-MM` du mois d'origine de la facture si différent du mois courant — vide sinon          |\n| 11 | `confidence`           | enum          | `fort` / `moyen` / `faible` / `aucun`                                                        |\n| 12 | `anomalie`             | enum          | cf. table ci-dessous — vide si aucune                                                        |\n| 13 | `montant_attendu`      | decimal       | Montant TTC de la facture matchée — vide si non-match                                        |\n| 14 | `ecart`                | decimal       | `montant - montant_attendu` — vide si non-match                                              |\n| 15 | `candidats`            | string        | Liste de numéros de facture séparés par `|` si `anomalie=match_ambigu`                       |\n| 16 | `commentaire`          | string        | Champ libre, éditable par le comptable. Préservé entre runs                                  |\n\n---\n\n## Enum `anomalie`\n\n| Valeur                      | Sens                                                                                                |\n| --------------------------- | --------------------------------------------------------------------------------------------------- |\n| (vide)                      | Aucune anomalie                                                                                     |\n| `paiement_orphelin`         | Transaction débit sans facture in correspondante                                                    |\n| `encaissement_sans_facture` | Transaction crédit sans facture out correspondante                                                  |\n| `facture_non_payee`         | Facture out avec échéance dépassée et pas de paiement détecté                                       |\n| `montant_incoherent`        | Match trouvé mais `abs(ecart) > tolerance` (typiquement escompte ou frais)                          |\n| `doublon_paiement`          | Plusieurs transactions matchent la même facture avec somme > `montantTTC`                           |\n| `paiement_partiel`          | Transaction crédit < `montantTTC` de la facture matchée                                             |\n| `match_ambigu`              | Plusieurs candidats avec scores proches (écart < 0.10) — non choisi automatiquement                 |\n| `facture_double_classement` | La facture matchée est référencée dans 2 mois différents de `index.json` — inconsistance à corriger |\n| `libelle_suspect`           | Mots-clés louches (URL raccourcie, IBAN inconnu) — flag manuel, pas bloquant                        |\n\n---\n\n## Tri des lignes\n\n1. `date` croissante.\n2. À date égale, `source=transaction` avant `source=facture_non_payee`.\n3. À source égale, `compte` alphabétique puis `ligne` (ordre du relevé).\n\nPermet une lecture chronologique pour le comptable.\n\n---\n\n## Préservation du `commentaire`\n\nLa colonne `commentaire` est la **seule** qui survit entre les runs. À chaque réécriture :\n\n1. Lire le CSV existant en mémoire.\n2. Indexer les commentaires par `(date, montant, libelle_hash)`.\n3. Après le matching, restaurer les commentaires sur les lignes qui correspondent.\n4. Les lignes nouvelles ont `commentaire` vide.\n\nSi le comptable édite manuellement d'autres colonnes → écrasées au prochain run. Une bannière en tête du fichier le rappelle :\n\n```\n# rapprochement.csv — ACME SA — 2026/04\n# Généré par rapprochement-bancaire — réécrit à chaque run.\n# SEULE la colonne 'commentaire' est préservée entre runs. Autres modifs perdues.\n```\n\n---\n\n## Exemple de CSV\n\n```csv\nligne;source;compte;date;libelle;montant;categorie_auto;match_facture;match_emetteur;match_mois_origine;confidence;anomalie;montant_attendu;ecart;candidats;commentaire\n1;transaction;BNP-courant;2026-04-03;\"VIR FAVEUR TRENDEX TECH F-2026-03-007\";5800,00;match;F-2026-03-007;Trendex Tech;2026-03;fort;;5800,00;0,00;;\n2;transaction;BNP-courant;2026-04-05;\"PRLV ORANGE PRO F-2026-04-1287\";-348,50;match;F-2026-04-1287;Orange Pro;;fort;;-348,50;0,00;;\n3;transaction;BNP-courant;2026-04-12;\"VIR FAVEUR FOO SAS\";1248,00;match;F-2026-03-008;Foo SAS;2026-03;moyen;;1248,00;0,00;;\n4;transaction;BNP-courant;2026-04-15;\"CB SNCF PARIS-LYON\";-84,50;match;;SNCF;;faible;;;;;\n5;transaction;BNP-courant;2026-04-22;\"PRLV URSSAF\";-1870,00;rh_charges;;;;aucun;;;;;\n6;transaction;BNP-courant;2026-04-28;\"CHQ N° 4521 - FOURN MAT\";-720,00;non_categorise;;;;aucun;paiement_orphelin;;;;à investiguer\n7;facture_non_payee;;2026-04-15;\"F-2026-04-013 - ACME Corp\";5800,00;;F-2026-04-013;ACME Corp;;aucun;facture_non_payee;5800,00;;;\n```\n\nFile v1.0.2:references/matching-rules.md\n\n# Référence — Règles de matching transactions ↔ factures\n\n> Référence chargée à la demande par `rapprochement-bancaire`. Détaille la cascade, la fenêtre de date, le scoring de confiance et les cas limites.\n\n---\n\n## Normalisation préalable\n\n### Côté transaction (extraite du relevé)\n\n| Champ                | Normalisation                                                                                                |\n| -------------------- | ------------------------------------------------------------------------------------------------------------ |\n| `montant`            | Décimal, signé (`+` crédit, `-` débit), arrondi 0,01 €                                                       |\n| `date`               | ISO 8601 (`YYYY-MM-DD`)                                                                                       |\n| `libelle`            | Trim, espaces multiples → simple espace, lowercase                                                            |\n| `libelle_tokens`     | `libelle` splitté par espaces / ponctuation, tokens ≥ 3 caractères, sans stop-words bancaires (`vir`, `prlv`, `cb`, `chq`, `faveur`, `de`, `du`, `de la`) |\n\n### Côté facture (extraite de l'index)\n\n| Champ                | Normalisation                                                                              |\n| -------------------- | ------------------------------------------------------------------------------------------ |\n| `montantTTC`         | Décimal positif (le sens vient du `categorie`: `achat` = débit, `vente` = crédit)         |\n| `dateEmission`       | ISO 8601                                                                                   |\n| `emetteur_normalise` | lowercase, accents retirés, suffixes juridiques retirés (`SA`, `SAS`, `SARL`, `EURL`, `Ltd`) |\n| `numeroFacture`      | Conservé tel quel + variante numérique pure (`F-2026-04-1287` → aussi `20260412 87`)      |\n\n---\n\n## Cascade de matching (par transaction)\n\nPour chaque transaction, parcourir les factures du mois (et du mois précédent + suivant — cf. fenêtre date) et calculer un **score** :\n\n```\nscore = match_montant × poids_montant\n      + match_label × poids_label\n      + match_date × poids_date\n```\n\nPoids par défaut :\n\n- `poids_montant` : 0.5\n- `poids_label` : 0.3\n- `poids_date` : 0.2\n\n### Score `match_montant`\n\n| Condition                                                           | Score |\n| ------------------------------------------------------------------- | ----- |\n| `abs(montant - montantTTC) ≤ 0.01`                                  | 1.0   |\n| `abs(montant - montantTTC) / montantTTC < 0.02` (escompte typique)  | 0.6   |\n| Autre                                                               | 0.0   |\n\n**Sens** : `montant > 0` → ne peut matcher qu'une facture `vente`. `montant < 0` → ne peut matcher qu'une facture `achat`. Pas de cross-match.\n\n### Score `match_label`\n\nCascade — première règle qui matche :\n\n| Condition                                                                    | Score |\n| ---------------------------------------------------------------------------- | ----- |\n| `libelle` contient `numeroFacture` (variante numérique pure incluse)         | 1.0   |\n| `libelle_tokens` contient un token de `emetteur_normalise` (longueur ≥ 4)    | 0.8   |\n| `libelle_tokens` contient un préfixe / suffixe de l'émetteur (Levenshtein ≤ 2) | 0.5   |\n| Aucun                                                                        | 0.0   |\n\n### Score `match_date`\n\n| Condition                                            | Score |\n| ---------------------------------------------------- | ----- |\n| `abs(date - dateEmission) ≤ 3 j`                     | 1.0   |\n| `abs(date - dateEmission) ≤ 30 j`                    | 0.7   |\n| `abs(date - dateEmission) ≤ 60 j`                    | 0.3   |\n| Au-delà                                              | 0.0   |\n\n---\n\n## Classification du score final\n\n| Score agrégé | Confiance | Action                                                            |\n| ------------ | --------- | ----------------------------------------------------------------- |\n| ≥ 0.85       | `fort`    | Ligne CSV matchée + update `followup.md` (`payée <date>`)         |\n| 0.65 – 0.85  | `moyen`   | Ligne CSV matchée + update `followup.md` (`payée ? <date>`, avec `?`) |\n| 0.45 – 0.65  | `faible`  | Ligne CSV `match suggéré` mais **pas** d'update followup          |\n| < 0.45       | aucun     | Transaction reste `unmatched`                                     |\n\nSi une transaction a **plusieurs candidats ex-aequo** (écart < 0.10 entre top-2) → marquer `match_ambigu` dans le CSV et ne pas écrire dans followup. Lister les N candidats dans la colonne `candidats`.\n\n---\n\n## Fenêtre de date et débordement de mois\n\nUne facture émise le 28/03 peut être payée le 02/04. Donc :\n\n- Charger les factures des **3 mois** : précédent, courant, suivant, autour du mois de la transaction.\n- Si match avec une facture d'un autre mois → l'écrire dans le CSV du mois de la **transaction**, mais annoter `facture_mois_origine` = mois de la facture.\n- Mettre à jour le `followup.md` du **mois de la facture** (pas celui de la transaction).\n\n---\n\n## Cas limites\n\n### Frais bancaires / commissions\n\nTransaction avec `libelle` contenant un mot-clé d'agios :\n\n- `frais`, `commission`, `cotisation`, `agios`, `interets`, `tenue de compte`\n\n→ Pas de matching tenté. Annoter `categorie_auto: frais_bancaires` dans le CSV. Ne pas alerter (comportement normal).\n\n### Salaires / charges sociales\n\nMots-clés : `salaire`, `urssaf`, `dsn`, `cipav`, `madelin`, `mutuelle`, `prevoyance`.\n\n→ Pas de matching. Annoter `categorie_auto: rh_charges`. Pas d'alerte par défaut, mais flag possible si montant aberrant vs historique (v0.3+).\n\n### Virements internes\n\nMots-clés : `virement interne`, `vir entre comptes`, `transfert`.\n\n→ Pas de matching. Annoter `categorie_auto: transfert_interne`. Pas d'alerte.\n\n### Encaissements partiels\n\nSi `montant < montantTTC` ET `montant > 0` ET match label + date OK :\n\n- Annoter `paiement_partiel` dans le CSV.\n- Calculer pourcentage : `(montant / montantTTC) × 100`.\n- Update `followup.md` : `partielle (X%)`.\n- Au prochain run, chercher les transactions complémentaires pour atteindre 100 %.\n\n### Multi-paiements pour une facture\n\nUne facture peut être payée en plusieurs fois (acompte + solde). Si plusieurs transactions matchent la même facture avec sens cohérent et somme = `montantTTC` → OK, listées comme un groupe dans le CSV avec un identifiant `match_group`.\n\nSi somme > `montantTTC` → anomalie `doublon_paiement`.\n\n### Devise étrangère\n\nHors scope v0.1. Si une facture USD et un encaissement EUR matchent par hasard sur le montant → faux positif. Mitigation v0.2 : taux de change + tolérance plus large.\n\n### Relevés multi-comptes\n\nUn client peut avoir plusieurs comptes (cf. `bank-statements/2026-04_BNP-courant.pdf` et `bank-statements/2026-04_Qonto-pro.pdf`). Toutes les transactions de tous les comptes du mois sont mises en pool commun pour le matching. Le CSV indique la source dans une colonne `compte`.\n\n### Reclassement d'une facture après matching\n\nSi une facture est reclassée par `organisation-documents` (mauvais client, mauvaise catégorie) APRÈS un matching :\n\n- Le `rapprochement-state.json` détecte le changement de chemin et marque le mois `dirty: true`.\n- Au prochain run, le matching est recalculé pour ce mois → le CSV est régénéré, `followup.md` mis à jour.\n- L'ancien match est purgé de l'état précédent.\n\n---\n\n## Anti-faux-positifs\n\nSources connues de faux positifs et garde-fous :\n\n| Source                                                          | Garde-fou                                                                                  |\n| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| 2 fournisseurs facturent le même montant rond (`100,00 €`)      | Required : label OU date proche, jamais montant seul                                       |\n| Abonnement mensuel identique → faux match sur le mauvais mois   | Fenêtre date prioritaire si plusieurs candidats même montant                               |\n| Émetteur générique (`Amazon`) qui matche partout                | Liste noire de tokens trop génériques : `paiement`, `achat`, `commande`, `service`, `web` |\n| Transaction de l'année précédente reposté (relevé re-généré)    | Si date < `mois - 90 j` → ignorée                                                          |\n\n**Règle absolue** : un match `fort` ne doit jamais écraser une donnée déjà éditée par le comptable dans `followup.md` (statut paiement, prochaine relance). En cas de conflit, garder la valeur humaine et lister le conflit dans le CSV avec `anomalie=libelle_suspect` + commentaire automatique « override manuel respecté ».\n\nFile v1.0.2:references/structure-cible.md\n\n# Référence — Structure cible & conventions de nommage\n\n> Référence chargée à la demande par `organisation-documents`. Spec exhaustive du classement.\n> Source de vérité pour les autres skills qui écrivent dans le même arbre (`relances`, `facturation`, `rapprochement-bancaire`).\n\n---\n\n## Arborescence complète\n\n```\n~/.openclaw/workspace/\n├── clients/\n│   ├── <slug>/                                # un dossier = un client réel (existant ou auto-créé)\n│   │   ├── contrats/                          # niveau client — contrats pluriannuels\n│   │   │   └── <AAAA-MM-JJ>_<Type>_<Contrepartie>.<ext>\n│   │   ├── <AAAA>/\n│   │   │   └── <MM>/\n│   │   │       ├── bank-statements/           # toujours un dossier (multi-comptes possibles)\n│   │   │       │   └── <AAAA-MM>_<BanqueOuCompte>.<ext>\n│   │   │       ├── invoices/\n│   │   │       │   ├── in/                    # factures reçues (achats)\n│   │   │       │   │   └── <AAAA-MM-JJ>_<Émetteur>_<MontantTTC>.<ext>\n│   │   │       │   └── out/                   # factures émises (ventes)\n│   │   │       │       └── <AAAA-MM-JJ>_<Destinataire>_<MontantTTC>.<ext>\n│   │   │       ├── notes-de-frais/\n│   │   │       │   └── <AAAA-MM-JJ>_<Collaborateur>_<Émetteur>_<MontantTTC>.<ext>\n│   │   │       ├── autres/\n│   │   │       │   └── <AAAA-MM-JJ>_<Description>.<ext>\n│   │   │       ├── relances.md                # maintenu par skill `relances`\n│   │   │       └── followup.md                # maintenu par skill `facturation`\n│   │   ├── _archive-suppression/              # RGPD soft-delete (grâce 30 j)\n│   │   ├── index.json                         # index par client\n│   │   └── audit.log                          # déplacements / renommages\n│   └── index-global.json\n├── .pending-attribution/                      # parking technique HORS clients/\n│   ├── <AAAA-MM-JJ-réception>_<hash-court>.<ext>\n│   └── pending-attribution.json\n└── clients.json\n```\n\n### Pas de slug réservé dans `clients/`\n\nTout dossier sous `clients/` correspond à un client réel — existant ou auto-créé en `draft-auto-created`. Aucune entité technique (`_cabinet`, `_non-attribue`, etc.) ne pollue cet arbre.\n\n### `.pending-attribution/`\n\nParking technique pour les documents que la cascade d'identification n'a pas su attribuer (expéditeur générique sans signal exploitable). Vit à la racine du workspace, **hors** `clients/`.\n\n| Élément                          | Rôle                                                                              |\n| -------------------------------- | --------------------------------------------------------------------------------- |\n| `<date-réception>_<hash>.<ext>`  | Le fichier physique, renommé pour la traçabilité (pas le filename d'origine)      |\n| `pending-attribution.json`       | Métadonnées extraites partielles + question à poser à l'utilisateur               |\n\nUne fois la réponse utilisateur reçue (« rattacher à `acme-sa` » ou « créer client `nouveau-client` »), le fichier est **déplacé** vers son chemin cible définitif dans `clients/<slug>/…` et l'entrée correspondante de `pending-attribution.json` est purgée.\n\n---\n\n## Mapping `categorie` → dossier\n\n| `categorie`      | Dossier cible                            | Vocab comptable    | Niveau   |\n| ---------------- | ---------------------------------------- | ------------------ | -------- |\n| `achat`          | `<AAAA>/<MM>/invoices/in/`               | Achats             | mois     |\n| `vente`          | `<AAAA>/<MM>/invoices/out/`              | Ventes             | mois     |\n| `bank-statement` | `<AAAA>/<MM>/bank-statements/`           | Relevés bancaires  | mois     |\n| `note-de-frais`  | `<AAAA>/<MM>/notes-de-frais/`            | Notes de frais     | mois     |\n| `contrat`        | `contrats/` (racine `<slug>/`)           | Contrats           | client   |\n| `autre`          | `<AAAA>/<MM>/autres/`                    | Autres             | mois     |\n\nL'enum machine est en kebab-case singulier (`achat`, pas `Achats`). Le vocab capitalisé pluriel est réservé aux **messages affichés au comptable** ; il n'apparaît jamais dans un chemin ni un payload.\n\n---\n\n## Conventions de nommage\n\n### Slug client\n\n- lowercase\n- accents retirés (`é` → `e`, `ç` → `c`, etc.)\n- espaces → `-`\n- caractères non alphanumériques → `-`\n- pas de `-` consécutifs, pas de `-` en début/fin\n- dérivé du **domaine** de l'expéditeur, pas de l'email (`trendex.tech` → `trendex-tech`)\n\n### Composants de nom de fichier\n\n| Token            | Règle                                                                                                  |\n| ---------------- | ------------------------------------------------------------------------------------------------------ |\n| `AAAA-MM-JJ`     | `dateEmission` du document (ISO 8601), pas date de réception                                          |\n| `AAAA-MM`        | `dateEmission` tronquée au mois — utilisé pour les relevés bancaires (couvre généralement un mois)    |\n| `Émetteur`       | Raison sociale source, 10 premiers caractères significatifs, sans accents ni espaces (`Orange Pro` → `OrangePro`) |\n| `Destinataire`   | Idem, pour les factures émises (`out/`) — c'est le client final du client du cabinet                  |\n| `Collaborateur`  | Prénom ou prénom+initiale, sans accents (`Thomas`, `ThomasM`)                                         |\n| `MontantTTC`     | Sans séparateur de milliers, point décimal, sans symbole (`348.50`, pas `348,50 €`)                   |\n| `BanqueOuCompte` | Nom court de banque + suffixe compte optionnel (`BNP-courant`, `Qonto-pro`)                           |\n| `Type` (contrat) | `MSA`, `NDA`, `Bail`, `CDI`, `Avenant`, etc. — vocabulaire court figé                                 |\n| `Contrepartie`   | Slug ou raison sociale courte de l'autre partie au contrat                                            |\n| `Description`    | Slug court décrivant le document quand aucune catégorie ne s'applique (`Statuts`, `KBis`, `Procuration`) |\n\n### Séparateur\n\n`_` entre composants. `-` à l'intérieur d'un composant. Jamais d'espace.\n\n### Extension\n\nPréservée du document source en lowercase (`.pdf`, `.jpg`, `.png`, `.eml`, `.csv`, `.ofx`, `.xml`).\n\n---\n\n## Cas spéciaux\n\n### Multi-comptes bancaires\n\nUn client peut avoir plusieurs comptes (courant, livret pro, devises). `bank-statements/` est toujours un dossier, jamais un fichier unique :\n\n```\nclients/acme-sa/2026/04/bank-statements/\n├── 2026-04_BNP-courant.pdf\n├── 2026-04_BNP-livret-pro.pdf\n└── 2026-04_Qonto-usd.pdf\n```\n\n### Multi-pages relevé / cas hebdomadaires\n\nSi un relevé couvre plusieurs périodes ou est fragmenté, suffixer avec la plage :\n\n```\nclients/acme-sa/2026/04/bank-statements/2026-04-01_2026-04-15_BNP-courant.pdf\n```\n\n### Contrats pluriannuels\n\nLes contrats vivent à `<slug>/contrats/`, indexés sur la date de signature, pas la date de réception. Un avenant garde le même `<Type>` préfixé `Avenant-` :\n\n```\nclients/acme-sa/contrats/2024-01-15_MSA_TrendexTech.pdf\nclients/acme-sa/contrats/2026-03-01_Avenant-MSA_TrendexTech.pdf\n```\n\n### Notes de frais — toujours rattachées à un client\n\nUne note de frais appartient toujours à un client (celui dont l'employé doit être remboursé). L'émetteur du document est le commerçant (SNCF, restaurant…) ; le **bénéficiaire** — identifié dans le composant `<Collaborateur>` du nom — est l'employé d'un client.\n\n```\nclients/<client>/<AAAA>/<MM>/notes-de-frais/<AAAA-MM-JJ>_<Collaborateur>_<Émetteur>_<MontantTTC>.<ext>\n```\n\nLes frais perso de l'utilisateur lui-même **ne sont pas dans le scope** de ce skill. Si un document de ce type est détecté (typiquement : ticket envoyé depuis l'adresse perso de l'utilisateur, sans contexte client) → traité comme `non attribuable` → `.pending-attribution/`.\n\n### Documents non datables\n\nSi `dateEmission` est introuvable ET non déductible (carte de visite, capture d'écran, etc.) → date de réception en fallback, et alerte `extraction_incomplete`. Décision auto-rétrogradée à `needs_review`.\n\n### Reclassement (correction d'un mauvais classement)\n\nQuand le comptable corrige une attribution (mauvais client, mauvaise catégorie), le skill :\n\n1. Déplace le fichier vers le nouveau chemin (sans renommage si le nouveau nom est identique).\n2. Met à jour `index.json` des deux clients concernés (ancien + nouveau).\n3. Logue l'opération dans `audit.log` du nouveau client avec `motif: reclassement-manuel`.\n4. Ne touche jamais à `_archive-suppression/` (la suppression est un autre flux).\n\n---\n\n## `relances.md` — schéma\n\n> Maintenu par le skill `relances`. Lu par `organisation-documents` en cas de classement d'une réponse à relance.\n\nUn fichier par mois et par client : `clients/<slug>/<AAAA>/<MM>/relances.md`.\n\n**Contenu** — un fichier = deux tableaux markdown :\n\n```markdown\n# Relances — <Raison sociale client> — <AAAA>/<MM>\n\n> Maintenu par le skill `relances`. Toute modification manuelle est tolérée mais peut être écrasée à la prochaine exécution.\n\n## À envoyer\n\n| Date prévue | Facture       | Destinataire | Montant TTC | Échéance dépassée | Canal | Type   | Statut    |\n| ----------- | ------------- | ------------ | ----------- | ----------------- | ----- | ------ | --------- |\n| 2026-05-15  | F-2026-04-012 | TrendexTech  | 2 400,00 €  | 15 j              | email | R1     | planifiée |\n| 2026-05-30  | F-2026-04-012 | TrendexTech  | 2 400,00 €  | 30 j              | email | R2     | planifiée |\n\n## Effectuées\n\n| Date envoi | Facture       | Destinataire | Montant TTC | Canal   | Type | Réponse client | Suite             |\n| ---------- | ------------- | ------------ | ----------- | ------- | ---- | -------------- | ----------------- |\n| 2026-04-15 | F-2026-03-008 | ACME Corp    | 1 248,00 €  | email   | R1   | promesse 15/05 | R2 planifié 30/05 |\n| 2026-04-30 | F-2026-03-005 | Foo SAS      | 850,00 €    | courrier | R3   | aucune         | escalade huissier |\n```\n\n**Colonnes obligatoires** — toute autre colonne est facultative.\n\n**Règles d'écriture** :\n\n- Le skill `relances` est seul autorisé à modifier ces fichiers. `organisation-documents` les lit uniquement.\n- L'ordre des lignes dans `À envoyer` : chronologique croissant (la prochaine en haut).\n- L'ordre des lignes dans `Effectuées` : chronologique décroissant (la plus récente en haut).\n- Quand une relance planifiée est envoyée, le skill `relances` la migre de `À envoyer` vers `Effectuées` dans le fichier du mois courant (pas du mois de la facture).\n\n---\n\n## `followup.md` — schéma\n\n> Maintenu par le skill `facturation`. Lu par `organisation-documents` en cas de classement d'une facture entrante (paiement) ou sortante (émise).\n\nUn fichier par mois et par client : `clients/<slug>/<AAAA>/<MM>/followup.md`.\n\n```markdown\n# Followup factures — <Raison sociale client> — <AAAA>/<MM>\n\n> Maintenu par le skill `facturation`. Vue mois en cours.\n\n## À émettre\n\n| Date prévue | Destinataire | Description        | Montant TTC prévu | Statut         |\n| ----------- | ------------ | ------------------ | ----------------- | -------------- |\n| 2026-04-01  | TrendexTech  | Forfait avril      | 2 400,00 €        | à préparer     |\n| 2026-04-15  | ACME Corp    | Prestation projet X | 5 800,00 €        | brouillon prêt |\n\n## Émises ce mois\n\n| Date émission | N° facture    | Destinataire | Montant TTC | Échéance   | Statut paiement | Prochaine relance |\n| ------------- | ------------- | ------------ | ----------- | ---------- | --------------- | ----------------- |\n| 2026-04-01    | F-2026-04-012 | TrendexTech  | 2 400,00 €  | 2026-04-30 | non payée       | R1 le 2026-05-15  |\n| 2026-04-15    | F-2026-04-013 | ACME Corp    | 5 800,00 €  | 2026-05-15 | partielle (50%) | suivi 2026-05-15  |\n| 2026-04-22    | F-2026-04-014 | Foo SAS      | 720,00 €    | 2026-05-22 | payée 2026-04-28 | —                |\n```\n\n**Statut paiement** — enum : `non payée` | `partielle` | `payée` | `litige` | `irrécouvrable`.\n\n**Mise à jour automatique** — quand `organisation-documents` classe un relevé bancaire dans `bank-statements/`, il devrait théoriquement déclencher un rapprochement (`rapprochement-bancaire` skill) qui mettra à jour `followup.md` avec les paiements détectés. Hors scope de ce skill, mais le contrat de chemin est figé ici pour permettre l'intégration.\n\n---\n\n## Pourquoi cette structure\n\n| Décision                                    | Alternative rejetée                          | Raison                                                                                  |\n| ------------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------- |\n| `<AAAA>/<MM>/` (deux niveaux)               | `<AAAA-MM>/` (un niveau)                     | Conservation 10 ans = 120 dossiers à plat ingérables sur Drive/Finder                   |\n| `invoices/in` + `invoices/out` séparés      | `invoices/` unique                           | TVA et PCG différents — un compta ne mélange jamais                                     |\n| `contrats/` à la racine client              | `contrats/` au mois                          | Contrats pluriannuels — les enterrer dans un mois rend la lecture impossible            |\n| `bank-statements/` toujours un dossier      | Fichier `bank-statement.pdf` direct          | Multi-comptes très courant, dossier dès le départ évite la migration                    |\n| `relances.md` + `followup.md` au mois       | Fichier rolling à la racine client           | Le comptable raisonne par mois (clôtures) — état mensuel plus utile que log global      |\n| Slug dérivé du domaine                      | Slug dérivé de l'email                       | 1 client = N employés = N emails — mais 1 dossier client                                |\n| Aucun slug réservé dans `clients/`          | `_cabinet` + `_non-attribue` pseudo-clients   | Un slug = un client réel. Le reste vit dans `.pending-attribution/` hors `clients/`.   |\n| Auto-création par défaut                    | Toujours demander à l'utilisateur            | Trop interruptif au quotidien. Confirmation opt-in via `clientCreation: \"confirm\"`.    |\n| Enum `categorie` kebab-case singulier       | Capitalisé pluriel                           | Aligné sur les noms de dossiers (`invoices/in` etc.) — pas de mismatch machine/affiché |\n\nFile v1.0.2:skill-card.md\n\n## Description:\n\nA script-driven skill for French accounting firms that reconciles invoices with bank-statement transactions, validates VAT, and summarizes follow-up, reminder, anomaly, and batch-report JSON files for accountant review.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[trendex](https://clawhub.ai/user/trendex)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nFrench accounting firms and accountants use this skill to run daily reconciliation over already organized client folders and review paid, unpaid, partial, and overdue invoices, VAT issues, reminder candidates, and blocking anomalies.\n\n### Deployment Geography for Use:\n\nFrance\n\n## Known Risks and Mitigations:\n\nRisk: The matching code can incorrectly mark invoices as paid, which may affect closing, reminders, or client decisions.\n\nMitigation: Use the generated paid, unpaid, partial, and overdue results only after human accountant review, and do not rely on them for closing or reminders until the matching issues are fixed.\n\nRisk: Running the skill against the wrong or unreviewed client root can produce misleading accounting reports.\n\nMitigation: Install it only for the intended French accounting workflow and first run it on a test copy or a reviewed client root.\n\n## Reference(s):\n\n- [CSV reconciliation schema](references/csv-schema.md)\n- [Transaction-to-invoice matching rules](references/matching-rules.md)\n- [Target folder structure and naming conventions](references/structure-cible.md)\n- [organisation-documents companion skill](https://github.com/developers-trendex/organisation-documents)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, JSON, Markdown, Guidance]\n\n**Output Format:** [Markdown summary with generated JSON reconciliation reports]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Produces per-client followup.json, relances.json, anomalies.json, and a consolidated compta_batch_report_<date>.json for accountant review.]\n\n## Skill Version(s):\n\n1.0.2 (source: server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.0.1: 13 files, 57080 bytes\n\nFiles: _meta.json (141b), README.md (7040b), references/csv-schema.md (7202b), references/matching-rules.md (9018b), references/structure-cible.md (14917b), scripts/dedupe_disk.py (2494b), scripts/export_csv.py (2920b), scripts/extract.py (47268b), scripts/main.py (26346b), scripts/resolve_vision.py (2913b), scripts/test_extract.py (14608b), skill-card.md (2971b), SKILL.md (16192b)\n\nFile v1.0.1:SKILL.md\n\n---\nname: rapprochement-bancaire\ndescription: Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valide la TVA, maintient followup.json / relances.json / anomalies.json. Le travail réel est fait par scripts/main.py (qui appelle scripts/extract.py). Ne traite jamais les e-mails, ne classe jamais de documents.\nlicense: Interne — usage privé OpenClaw\n---\n\n# Skill `rapprochement-bancaire`\n\n> Moteur d'état. Travaille uniquement sur l'arborescence `clients/<slug>/...` produite par `organisation-documents`.\n> **Le travail réel est fait par `scripts/main.py`** (qui appelle `scripts/extract.py` pour toute l'extraction de texte). Ce skill = quand le lancer + comment rendre compte.\n\n---\n\n## ⚠️ Règle absolue — EXÉCUTION DU SCRIPT (non négociable)\n\n**Pour chaque invocation, la SEULE action correcte est d'exécuter la commande :**\n\n```bash\npython3 scripts/main.py <racine_clients>\n```\n\npuis de lire les `followup.json` / `relances.json` / `anomalies.json` par client + le `compta_batch_report_<date>.json` consolidé, et de relayer au comptable.\n\n### ❌ INTERDIT\n\n- **Réimplémenter le rapprochement en Python inline / pseudo-code.** La logique (Pass 1 par référence, Pass 2 fuzzy, validation TVA, détection des anomalies, statuts overdue/partial, génération des relances) est dans `scripts/main.py`. La refaire à la main est garanti de diverger.\n- **Écrire `followup.json` / `relances.json` / `anomalies.json` à la main.** C'est le script qui le fait, dans son format exact (liste d'objets avec les champs `invoice_id`, `type`, `amount`, `status`, `bank_matched`, `matched_tx`, …). Toute autre forme (dict keyé, champs `payments` / `amount_paid` ad hoc) casse les lectures ultérieures.\n- **Modifier le format des fichiers de sortie.** Le format est contractuel entre ce skill et d'éventuels skills aval (`relances`, dashboards).\n\n### ✅ OBLIGATOIRE\n\n1. Exécuter la commande shell ci-dessus.\n2. Lire les fichiers produits.\n3. Relayer au comptable un tableau lisible par client (factures payées / en attente / partielles / overdue, anomalies — en mettant en avant les **bloquantes**).\n\n### Si le script échoue\n\nSignale l'erreur exacte (stderr). Ne rejoue PAS le batch à la main.\n\n> Pourquoi : on a déjà eu un run où l'agent a produit un `followup.json` au mauvais format (dict avec clés `\"in/N\"`, champs `payments`/`amount_paid`/`amount_remaining` au lieu de `bank_matched`/`status`). Conséquence : aucun outil ni skill aval ne pouvait l'exploiter. Le script génère le bon format à tous les coups.\n\n---\n\n## Exécution\n\n```bash\npython3 scripts/main.py [<racine_clients>]\n# racine_clients par défaut : ~/.openclaw/workspace/clients\n```\n\nÀ lancer :\n- une fois par jour (idéalement la nuit), sur tous les clients ;\n- ou immédiatement après un trigger `rapprochement-bancaire` émis par `organisation-documents`.\n\nLe script :\n\n1. **Parcourt** `clients/*` en ignorant les dossiers techniques `_*` (`_a-identifier`, `_incomplet`, `_non-attribue`, `_cabinet`).\n2. **Périodes traitées** : mois courant + mois précédent, plus tout mois ancien non verrouillé (`batch.lock.json` absent). Un mois verrouillé n'est pas retraité.\n3. **Factures** : nom de fichier conventionnel (`AAAA-MM-JJ_N°Facture_Contrepartie_MontantTTC.pdf`) → `invoice_id` + montant. Si le nom n'est pas exploitable, lecture du contenu via `extract.py`. Si toujours rien → anomalie `facture_illisible`.\n4. **Relevés bancaires** : transactions extraites via `extract.py`. Le parsing est **conscient des colonnes** (comme le mapping du skill chinois) : il détecte l'en-tête (`Date | Libellé | Débit | Crédit | Montant | Solde`), **écarte la colonne Solde** (ne la confond plus avec le montant) et gère les colonnes **Débit/Crédit séparées**. Le **signe** est déduit par ordre de fiabilité : variation du solde → flag CR/DB → position de colonne → signe du token. La référence facture est captée si le libellé contient `REF <id>` ou `FACT <id>`. Aucune transaction extractible → anomalie `releve_non_parseable` **+ mise en file vision**.\n5. **Rapprochement** (montant comparé en valeur absolue : une facture `out` est encaissée par un crédit, une `in` réglée par un débit) :\n   - **Pass 1** — réf facture : transaction dont `invoice_ref == facture.invoice_id`. Montant exact (±1 €) → `paid` ; montant inférieur → `partial` (conserve `amount_paid`, `amount_remaining`) ; supérieur → `paid` + `overpaid_by`.\n   - **Pass 2** — fuzzy : `|montant| ±1 €` ET similarité libellé / contrepartie ≥ 0.6.\n   - aucun match + échéance dépassée → `overdue`.\n6. **Validation TVA** de chaque facture : si `|TVA déclarée − TVA attendue| / TVA attendue > 5 %` (TVA attendue = `TOTAL HT × taux`) → anomalie `tva_incorrecte` (TVA 0 % / exonération ignorée).\n7. **Anomalies** : voir tableau ci-dessous.\n8. **Relances** : `overdue` / `partial` / `unpaid` hors délai → step selon ancienneté.\n9. Écrit `clients/<slug>/followup.json`, `relances.json`, `anomalies.json`, `needs_vision.json`, et un rapport consolidé `compta_batch_report_<date>.json`.\n\n**Après l'exécution** :\n1. **Traiter `needs_vision.json`** (voir section dédiée ci-dessous) — sinon les docs douteux restent mal lus.\n2. Lire le rapport et relayer au comptable un tableau lisible par client (factures payées / en attente, relances, anomalies — en mettant en avant les anomalies **bloquantes**). Jamais de chemins techniques.\n\n---\n\n## 🚦 Mode supervisé (production) — règles d'exploitation\n\nCe skill est déployé comme **copilote comptable supervisé**, PAS comme robot autonome. Trois règles non négociables :\n\n1. **`relances.json` = PROPOSITIONS, jamais envoyées automatiquement.** Une relance ne part qu'après **validation humaine** explicite. Raison : la précision du matching (montant ±1 € + nom ≥ 0.6) n'est pas auditée → un faux « impayé » enverrait une relance à tort à un client. Le comptable revoit la liste avant tout envoi.\n2. **`needs_vision.json` doit être traité à CHAQUE run** (passe vision sur les docs flaggés), sinon les relevés durs s'accumulent sans être lus. Aujourd'hui ~3 banques (LCL, Société Générale, BNP) en dépendent.\n3. **Ne jamais présenter un résultat flaggé comme fiable.** Un relevé `needs_vision` ou une anomalie bloquante se signale **en évidence** au comptable ; on ne « lisse » pas.\n\n> Invariant garanti par le code : rien n'est validé sans réconcilier (`ouverture + Σ = clôture`), sidecars vision inclus. Le rôle de l'humain en mode supervisé = arbitrer ce qui est **flaggé**, pas re-vérifier ce qui réconcilie.\n\n> Avant d'élargir le périmètre : faire **un run pilote sur 1 mois réel d'un client**, vérifier à la main les rapprochements + anomalies, et seulement ensuite généraliser.\n\n---\n\n## 🔍 Extraction en deux temps — script déterministe, puis VISION\n\n`extract.py` extrait d'abord par méthode déterministe :\n1. **Texte** `pdftotext -layout` (colonnes préservées). Si la couche texte est inexploitable (PDF scanné, ou texte « explosé » caractère par caractère comme certains BNP) → **OCR géométrique** : `tesseract -l fra` en sortie **TSV** (coordonnées de chaque mot) → reconstruction d'un texte **aligné en colonnes** (`_tsv_to_layout`). On préserve ainsi la géométrie du tableau (donc les colonnes Débit/Crédit) au lieu d'un texte à plat.\n2. **Profils par banque** (`BANK_PROFILES`, comme les mappings du skill chinois) : chaque banque (BNP, Banque Postale, CIC, Crédit Mutuel/Agricole, Société Générale, Caisse d'Épargne, LCL, Qonto) a sa détection et ses patterns de solde ouverture/clôture. Banque inconnue → moteur générique.\n3. **Parsing par colonnes** : dates `JJ/MM/AAAA` **et** `JJ/MM` (année déduite période/document), montants FR `1 234,56` et anglo `+ 20.00 EUR`, dates valeur, libellés multi-lignes, préfixes parasites (filigranes). **Signe toujours GÉOMÉTRIQUE, jamais par libellé** : delta de solde → signe `+/-` explicite → section (`Sous-total : +/-`) → **colonne Crédit** (en-tête, ou frontière **inférée** par la position des montants quand il n'y a pas d'en-tête, ex. scans) → flag.\n3. **Contrôle de justesse (gate « sans erreurs »)** : un relevé n'est **pleinement validé que s'il réconcilie** (`ouverture + Σ opérations = clôture`). Sinon — ou si les soldes ne sont pas imprimés (donc non vérifiable) — il pose `needs_vision: true` et `main.py` le liste dans `clients/<slug>/needs_vision.json`. **Aucun résultat non vérifié n'est trusté silencieusement.**\n\n> **Prérequis système** : `poppler-utils` (pdftotext, pdftoppm) **et** `tesseract-ocr` + `tesseract-ocr-fra` (OCR français). Sans tesseract, les PDF scannés tombent directement en `needs_vision` (lecture vision pure).\n\n> **Transcription `.md`** : à chaque extraction, `main.py` écrit un `<pdf>.md` lisible à côté de chaque PDF (texte brut `pdftotext`/OCR), pour relecture humaine. Idempotent ; la source de vérité reste le PDF. Ces `.md` (et les `.extract.json`/`.vision.json`) sont des sidecars — ils ne sont **jamais** relus comme des documents.\n\n> **Dé-duplication** : un même PDF réimporté sous plusieurs noms (`X.pdf`, `X__Banque.pdf`…) est compté **une seule fois** par client (clé = hash du contenu), donc plus de factures/opérations dédoublées dans `followup`/`relances`. Pour nettoyer les copies sur disque : `python3 scripts/dedupe_disk.py <racine_clients> --apply` (sans `--apply` = dry-run ; ne supprime que des fichiers byte-identiques d'un même dossier, en gardant le nom canonique).\n\n| `vision_reason` | Sens |\n|-----------------|------|\n| `no_text_layer` | PDF scanné / texte explosé → aucune couche texte exploitable (OCR indisponible ou insuffisant) |\n| `solde_non_reconcilie` | relevé : `ouverture + Σ opérations ≠ clôture` → un montant/signe est faux |\n| `solde_non_verifiable` | relevé sans solde ouverture+clôture imprimés → justesse non vérifiable |\n| `aucune_operation` | relevé reconnu mais aucune ligne extraite |\n| `invoice_id_absent` / `total_ttc_absent` | facture : champ obligatoire illisible |\n| `type_indetermine` | ni facture ni relevé reconnu |\n\n### Boucle vision (rend `needs_vision.json` ACTIONNABLE — il ne « pourrit » pas)\n\n> **Qui crée le sidecar, et quand ?** `main.py` ne le crée jamais (Python pur, pas de vision). Le `<pdf>.vision.json` est écrit par **cette passe vision** (modèle multimodal, ex. gemini), **entre deux runs** du batch. Tant qu'il n'existe pas, le doc reste dans `needs_vision.json`.\n\n**Chemin recommandé — un seul appel pour toutes les files :**\n```bash\npython3 scripts/resolve_vision.py <racine_clients>\n```\nRenvoie un JSON `to_resolve: [...]` listant, pour chaque doc douteux de tous les clients : `images` (PNG des pages), `skeleton` (à corriger), `sidecar_path` (où sauver). Pour traiter **un seul** document à la main : `python3 scripts/extract.py \"<source_file>\" --vision-kit /tmp/vision_<slug>`.\n\nPour **chaque** entrée :\n\n1. Récupérer ses `images` et son `skeleton` (via `resolve_vision.py` ci-dessus).\n2. **Lire les images `images[…]` en vision** et **corriger le `skeleton`** — relevé : chaque opération `date / label / amount signé (crédit +, débit −) / invoice_ref` + `opening_balance` / `closing_balance` ; facture : `invoice_id`, `total_ht`, `tva_rate`, `tva_amount`, `total_ttc`, `issue_date`, `due_date`, `recipient`. **Champ illisible → `null`, jamais inventé.** Pour un relevé, vérifier `ouverture + Σ amounts = clôture` avant de sauver.\n3. **Sauver** le squelette corrigé **tel quel** dans `sidecar_path` (= `<source_file>.vision.json`).\n4. **Relancer** `python3 scripts/main.py <racine_clients>`.\n\nAu re-run, `main.py` **préfère le sidecar** à son extraction regex : le document corrigé alimente le rapprochement et **sort de `needs_vision.json`**.\n\n> ⚠️ **Le sidecar d'un relevé ne court-circuite PAS le gate de réconciliation.** Un relevé relu en vision doit lui aussi tomber juste (`ouverture + Σ = clôture`). S'il ne réconcilie toujours pas, ses opérations restent utilisées pour le matching **mais le doc est RESIGNALÉ** (`vision_reason: [\"vision_solde_non_reconcilie\"]`) au lieu d'être trusté en silence. Piège fréquent : un **solde d'ouverture imprimé dans la colonne Débit = compte débiteur → valeur négative** (ex. caisse-épargne `SOLDE PRECEDENT 549,04` en Débit ⇒ `opening_balance: -549.04`). Une facture vision, elle, est acceptée telle quelle (pas de gate arithmétique).\n\n> Le sidecar est un cache de correction **persistant** : il survit aux runs suivants (le doc n'est plus jamais relu en vision tant qu'il existe, **s'il réconcilie**). Le supprimer force une nouvelle extraction par le script.\n\n### Contenu d'un sidecar (exemple relevé)\n\n```json\n{\n  \"kind\": \"bank-statement\",\n  \"holder\": \"Garage Martin\",\n  \"bank\": \"CREDIT AGRICOLE\",\n  \"opening_balance\": 5000.0,\n  \"closing_balance\": 5100.0,\n  \"operations\": [\n    {\"date\": \"2026-05-02\", \"label\": \"ACHAT PIECES AUTO\", \"amount\": -1200.0, \"invoice_ref\": null},\n    {\"date\": \"2026-05-09\", \"label\": \"VIREMENT CLIENT DURAND\", \"amount\": 1300.0, \"invoice_ref\": null}\n  ]\n}\n```\n\n---\n\n## Statuts de facture (`followup.json`)\n\n| Statut | Sens |\n|--------|------|\n| `unpaid` | non échue, aucun paiement |\n| `paid` | paiement confirmé (rapprochement réussi) |\n| `partial` | paiement partiel — `amount_paid` + `amount_remaining` conservés |\n| `overdue` | échéance dépassée, aucun paiement |\n\n---\n\n## Anomalies (`anomalies.json`)\n\n**Bloquantes** (empêchent la clôture de la période) :\n\n| Type | Condition |\n|------|-----------|\n| `doublon_paiement` | même date + montant + libellé |\n| `tva_incorrecte` | écart TVA calculée / déclarée > 5 % |\n| `facture_manquante` | une ligne du relevé cite un n° de facture (`REF`/`FACT`) absent du dossier, montant > 1 000 € |\n| `paiement_orphelin` | crédit > 1 000 € sans aucune référence ni facture |\n\n**Non bloquantes** (signalées, clôture possible) :\n\n| Type | Condition |\n|------|-----------|\n| `facture_manquante` | n° de facture cité au relevé mais absent, montant ≤ 1 000 € |\n| `paiement_orphelin` | crédit ≤ 1 000 € sans référence |\n| `releve_non_parseable` | aucune transaction extractible d'un relevé |\n| `facture_illisible` | facture dont ni le nom ni le contenu ne donnent n° + montant |\n| `invoice_overdue` | facture non payée, échéance dépassée |\n\n> `facture_manquante` ≠ `paiement_orphelin` : le premier = paiement qui **cite** un n° de facture qu'on n'a pas reçu (le client a oublié de transmettre la pièce) ; le second = encaissement sans aucune référence.\n\n---\n\n## Relances (`relances.json`)\n\n| Ancienneté du retard | Step |\n|----------------------|------|\n| ≤ 30 j | 1 |\n| ≤ 60 j | 2 |\n| ≤ 90 j | 3 |\n| > 90 j | escalation |\n\nPour `partial` : mention explicite `\"Solde restant dû : X,XX €\"`.\n\n---\n\n## Clôture de période\n\nLe script crée `clients/<slug>/<AAAA>/<MM>/batch.lock.json` quand : aucune anomalie **bloquante** sur la période, hash des fichiers stable depuis 7 jours, aucun statut `unpaid`/`overdue` non justifié. Les périodes verrouillées ne sont jamais retraitées sauf changement de hash.\n\n---\n\n## Règles critiques\n\n- Ne jamais supprimer de données. Ne jamais retraiter un mois verrouillé.\n- `followup.json` est un **cache reconstructible** : les PDFs classés restent la vérité. Le batch peut tout recalculer depuis zéro.\n- `clients.json` est lu seulement (maintenu par `organisation-documents`), jamais écrit ici.\n- Toute l'extraction de texte passe par `scripts/extract.py` — source unique, pas de logique de parsing dupliquée ici.\n\n---\n\n## Philosophie\n\n```\norganisation-documents  →  classe les pièces, déduit clients.json\nrapprochement-bancaire            →  rapproche, valide, reconstruit l'état comptable  (scripts/main.py)\nrelances                →  décision différée\n```\n\nLe système doit pouvoir être recalculé intégralement à partir des documents classés.\n\nFile v1.0.1:README.md\n\n# rapprochement-bancaire\n\n> Claude skill for **French accounting firms** (`cabinets comptables`). Reads the `clients/<slug>/...` tree produced by [`organisation-documents`](https://github.com/developers-trendex/organisation-documents), reconciles each invoice with its bank-statement transaction, validates VAT, and writes one `followup.json` / `relances.json` / `anomalies.json` per client plus a consolidated report. Deterministic script-driven.\n\n## What it does\n\nFor each invocation, the skill runs **one command** :\n\n```bash\npython3 scripts/main.py <clients_root>\n```\n\nThat script:\n\n1. Walks every client folder under `<clients_root>` (skipping `_a-identifier/`, `_incomplet/`, `_non-attribue/`, `_cabinet/`).\n2. For each active period (current + previous month, plus any older month without `batch.lock.json`) :\n   - Reads invoices from `invoices/in/` and `invoices/out/`.\n   - Reads transactions from `bank-statements/` via `scripts/extract.py` (one line per transaction, with the `REF`/`FACT <id>` reference captured when present).\n3. Reconciles in two passes (amount compared in absolute value — an `out` invoice is settled by a credit, an `in` by a debit) :\n   - **Pass 1** — direct match by invoice reference (`REF` / `FACT` in the bank label). Exact amount (±1 €) → `paid` ; lower amount → `partial` with `amount_paid` and `amount_remaining` ; higher → `paid` with `overpaid_by`.\n   - **Pass 2** — fuzzy : `|amount| ±1 €` **and** counterparty-name similarity ≥ 0.6.\n   - No match + due date passed → `overdue`.\n4. Validates VAT on each invoice : if `|VAT declared − TOTAL HT × rate| / expected > 5 %`, flags a blocking `tva_incorrecte` anomaly (exempted-VAT invoices are silently ignored).\n5. Detects other anomalies (see below).\n6. Generates relances based on lateness (`overdue`, `partial`, `unpaid` past due).\n7. Writes per-client JSON files + `compta_batch_report_<date>.json` consolidated.\n\nThe agent only **reads the JSON files and relays the summary to the accountant** — payments, late invoices, anomalies (highlighting blocking ones), no technical paths.\n\n## Statuses (`followup.json`)\n\n| Status     | Meaning                                                                   |\n| ---------- | ------------------------------------------------------------------------- |\n| `unpaid`   | Not yet due, no payment                                                   |\n| `paid`     | Payment confirmed                                                         |\n| `partial`  | Partial payment — `amount_paid` + `amount_remaining` kept                 |\n| `overdue`  | Due date passed, no payment                                               |\n\n## Anomalies (`anomalies.json`)\n\n**Blocking** (period cannot be locked) :\n\n| Type                          | Condition                                                                                       |\n| ----------------------------- | ----------------------------------------------------------------------------------------------- |\n| `doublon_paiement`            | same date + amount + label                                                                      |\n| `tva_incorrecte`              | calculated/declared VAT gap > 5 %                                                                |\n| `facture_manquante`           | a bank line references an invoice number (`REF`/`FACT`) absent from the folder, amount > 1 000 €|\n| `paiement_orphelin`           | credit > 1 000 € with no reference and no matching invoice                                       |\n\n**Non-blocking** (signaled, period can still be locked) :\n\n| Type                          | Condition                                                                                       |\n| ----------------------------- | ----------------------------------------------------------------------------------------------- |\n| `facture_manquante`           | as above, amount ≤ 1 000 €                                                                      |\n| `paiement_orphelin`           | credit ≤ 1 000 € with no reference                                                              |\n| `releve_non_parseable`        | no transaction extractable from a statement PDF                                                  |\n| `facture_illisible`           | neither filename nor PDF content yield a number + amount                                         |\n| `invoice_overdue`             | unpaid past due date                                                                            |\n\n> `facture_manquante` ≠ `paiement_orphelin` : the former is a payment that **cites** an invoice number we never received (client forgot to send it) ; the latter is a credit with no reference at all.\n\n## Relances (`relances.json`)\n\n| Lateness                | Step          |\n| ----------------------- | ------------- |\n| ≤ 30 days               | 1             |\n| ≤ 60 days               | 2             |\n| ≤ 90 days               | 3             |\n| > 90 days               | `escalation`  |\n\nA `partial` invoice gets a relance with explicit `Solde restant dû : X,XX €`.\n\n## Period closing\n\nA period is locked (`batch.lock.json`) when : no blocking anomaly, file hashes stable for 7 days, no unjustified `unpaid` / `overdue`. Locked periods are never reprocessed unless a file hash changes.\n\n## Prerequisite\n\nThe script calls `pdftotext` (package `poppler-utils`). Install it once on the runtime :\n\n```bash\napt install poppler-utils\n```\n\n## Output files\n\n```\nclients/<slug>/\n├── followup.json         ← all invoices with their reconciliation status\n├── relances.json         ← invoices needing follow-up, with step + suggested next contact date\n├── anomalies.json        ← all anomalies for this client (blocking flag included)\n└── <AAAA>/<MM>/batch.lock.json   ← present when the period is closed\n```\n\nPlus a consolidated `compta_batch_report_<YYYY-MM-DD>.json` at the workspace root.\n\n## Companion skill\n\n[`organisation-documents`](https://github.com/developers-trendex/organisation-documents) — receives, identifies the firm's clients, and classifies documents into the tree this skill consumes.\n\n## Files\n\n| File                              | Purpose                                                                |\n| --------------------------------- | ---------------------------------------------------------------------- |\n| `SKILL.md`                        | Skill definition (French) — the \"when and how to relay\" layer          |\n| `scripts/main.py`                 | Entrypoint — reconciliation, anomalies, relances, report               |\n| `scripts/extract.py`              | Deterministic extractor (only place that touches `pdftotext`)          |\n| `references/matching-rules.md`    | Reference — matching cascade, normalization, edge cases                |\n| `references/structure-cible.md`   | Path / naming contract (duplicated from `organisation-documents`)      |\n\n## License\n\nInternal — OpenClaw private use.\n\nFile v1.0.1:_meta.json\n\n{\n  \"ownerId\": \"kn7ethsmh4zep86d7ew9wsns2x86jwhr\",\n  \"slug\": \"rapprochement-bancaire\",\n  \"version\": \"1.0.1\",\n  \"publishedAt\": 1780412825226\n}\n\nFile v1.0.1:references/csv-schema.md\n\n# Référence — Schéma CSV de rapprochement & état persisté\n\n> Référence chargée à la demande par `rapprochement-bancaire`. Format du CSV, enum `anomalie`, schéma de `rapprochement-state.json`.\n\n---\n\n## Localisation du CSV\n\n```\nclients/<slug>/<AAAA>/<MM>/rapprochement.csv\n```\n\nUn fichier par (client, mois). Réécrit en entier à chaque run du mois concerné (idempotent). Pas d'append.\n\n---\n\n## Encodage et locale\n\n- **Encoding** : UTF-8 avec BOM (`﻿` en tête) — pour Excel français qui sinon mojibake.\n- **Séparateur** : `;` (semicolon) — convention française, évite la confusion avec décimales `,`.\n- **Décimal** : `,` (virgule).\n- **Date** : `AAAA-MM-JJ` (ISO 8601, lisible par tout tableur).\n- **Saut de ligne** : `\\r\\n` (CRLF) — compatibilité Windows / Excel.\n- **Quotes** : `\"` autour des champs qui contiennent `;`, `\"`, ou `\\r\\n`. Doubler les `\"` internes (`\"\"`).\n\n---\n\n## Colonnes du CSV\n\n| #  | Colonne                | Type          | Description                                                                                  |\n| -- | ---------------------- | ------------- | -------------------------------------------------------------------------------------------- |\n| 1  | `ligne`                | int           | Numéro de ligne séquentiel (1, 2, 3, …) — facilite la référence dans les conversations       |\n| 2  | `source`               | enum          | `transaction` (ligne de relevé), `facture_non_payee` (facture sans transaction)              |\n| 3  | `compte`               | string        | Banque ou compte (ex : `BNP-courant`, `Qonto-pro`) — vide si `source=facture_non_payee`      |\n| 4  | `date`                 | date          | Date transaction OU date d'émission de la facture                                            |\n| 5  | `libelle`              | string        | Libellé brut du relevé ou raison sociale + n° facture                                       |\n| 6  | `montant`              | decimal       | Signé (`+` crédit, `-` débit)                                                                |\n| 7  | `categorie_auto`       | enum          | `match` / `frais_bancaires` / `rh_charges` / `transfert_interne` / `non_categorise`         |\n| 8  | `match_facture`        | string        | Numéro de facture matchée — vide si non-match                                                |\n| 9  | `match_emetteur`       | string        | Raison sociale de la facture matchée                                                         |\n| 10 | `match_mois_origine`   | string        | `AAAA-MM` du mois d'origine de la facture si différent du mois courant — vide sinon          |\n| 11 | `confidence`           | enum          | `fort` / `moyen` / `faible` / `aucun`                                                        |\n| 12 | `anomalie`             | enum          | cf. table ci-dessous — vide si aucune                                                        |\n| 13 | `montant_attendu`      | decimal       | Montant TTC de la facture matchée — vide si non-match                                        |\n| 14 | `ecart`                | decimal       | `montant - montant_attendu` — vide si non-match                                              |\n| 15 | `candidats`            | string        | Liste de numéros de facture séparés par `|` si `anomalie=match_ambigu`                       |\n| 16 | `commentaire`          | string        | Champ libre, éditable par le comptable. Préservé entre runs                                  |\n\n---\n\n## Enum `anomalie`\n\n| Valeur                      | Sens                                                                                                |\n| --------------------------- | --------------------------------------------------------------------------------------------------- |\n| (vide)                      | Aucune anomalie                                                                                     |\n| `paiement_orphelin`         | Transaction débit sans facture in correspondante                                                    |\n| `encaissement_sans_facture` | Transaction crédit sans facture out correspondante                                                  |\n| `facture_non_payee`         | Facture out avec échéance dépassée et pas de paiement détecté                                       |\n| `montant_incoherent`        | Match trouvé mais `abs(ecart) > tolerance` (typiquement escompte ou frais)                          |\n| `doublon_paiement`          | Plusieurs transactions matchent la même facture avec somme > `montantTTC`                           |\n| `paiement_partiel`          | Transaction crédit < `montantTTC` de la facture matchée                                             |\n| `match_ambigu`              | Plusieurs candidats avec scores proches (écart < 0.10) — non choisi automatiquement                 |\n| `facture_double_classement` | La facture matchée est référencée dans 2 mois différents de `index.json` — inconsistance à corriger |\n| `libelle_suspect`           | Mots-clés louches (URL raccourcie, IBAN inconnu) — flag manuel, pas bloquant                        |\n\n---\n\n## Tri des lignes\n\n1. `date` croissante.\n2. À date égale, `source=transaction` avant `source=facture_non_payee`.\n3. À source égale, `compte` alphabétique puis `ligne` (ordre du relevé).\n\nPermet une lecture chronologique pour le comptable.\n\n---\n\n## Préservation du `commentaire`\n\nLa colonne `commentaire` est la **seule** qui survit entre les runs. À chaque réécriture :\n\n1. Lire le CSV existant en mémoire.\n2. Indexer les commentaires par `(date, montant, libelle_hash)`.\n3. Après le matching, restaurer les commentaires sur les lignes qui correspondent.\n4. Les lignes nouvelles ont `commentaire` vide.\n\nSi le comptable édite manuellement d'autres colonnes → écrasées au prochain run. Une bannière en tête du fichier le rappelle :\n\n```\n# rapprochement.csv — ACME SA — 2026/04\n# Généré par rapprochement-bancaire — réécrit à chaque run.\n# SEULE la colonne 'commentaire' est préservée entre runs. Autres modifs perdues.\n```\n\n---\n\n## Exemple de CSV\n\n```csv\nligne;source;compte;date;libelle;montant;categorie_auto;match_facture;match_emetteur;match_mois_origine;confidence;anomalie;montant_attendu;ecart;candidats;commentaire\n1;transaction;BNP-courant;2026-04-03;\"VIR FAVEUR TRENDEX TECH F-2026-03-007\";5800,00;match;F-2026-03-007;Trendex Tech;2026-03;fort;;5800,00;0,00;;\n2;transaction;BNP-courant;2026-04-05;\"PRLV ORANGE PRO F-2026-04-1287\";-348,50;match;F-2026-04-1287;Orange Pro;;fort;;-348,50;0,00;;\n3;transaction;BNP-courant;2026-04-12;\"VIR FAVEUR FOO SAS\";1248,00;match;F-2026-03-008;Foo SAS;2026-03;moyen;;1248,00;0,00;;\n4;transaction;BNP-courant;2026-04-15;\"CB SNCF PARIS-LYON\";-84,50;match;;SNCF;;faible;;;;;\n5;transaction;BNP-courant;2026-04-22;\"PRLV URSSAF\";-1870,00;rh_charges;;;;aucun;;;;;\n6;transaction;BNP-courant;2026-04-28;\"CHQ N° 4521 - FOURN MAT\";-720,00;non_categorise;;;;aucun;paiement_orphelin;;;;à investiguer\n7;facture_non_payee;;2026-04-15;\"F-2026-04-013 - ACME Corp\";5800,00;;F-2026-04-013;ACME Corp;;aucun;facture_non_payee;5800,00;;;\n```\n\nFile v1.0.1:references/matching-rules.md\n\n# Référence — Règles de matching transactions ↔ factures\n\n> Référence chargée à la demande par `rapprochement-bancaire`. Détaille la cascade, la fenêtre de date, le scoring de confiance et les cas limites.\n\n---\n\n## Normalisation préalable\n\n### Côté transaction (extraite du relevé)\n\n| Champ                | Normalisation                                                                                                |\n| -------------------- | ------------------------------------------------------------------------------------------------------------ |\n| `montant`            | Décimal, signé (`+` crédit, `-` débit), arrondi 0,01 €                                                       |\n| `date`               | ISO 8601 (`YYYY-MM-DD`)                                                                                       |\n| `libelle`            | Trim, espaces multiples → simple espace, lowercase                                                            |\n| `libelle_tokens`     | `libelle` splitté par espaces / ponctuation, tokens ≥ 3 caractères, sans stop-words bancaires (`vir`, `prlv`, `cb`, `chq`, `faveur`, `de`, `du`, `de la`) |\n\n### Côté facture (extraite de l'index)\n\n| Champ                | Normalisation                                                                              |\n| -------------------- | ------------------------------------------------------------------------------------------ |\n| `montantTTC`         | Décimal positif (le sens vient du `categorie`: `achat` = débit, `vente` = crédit)         |\n| `dateEmission`       | ISO 8601                                                                                   |\n| `emetteur_normalise` | lowercase, accents retirés, suffixes juridiques retirés (`SA`, `SAS`, `SARL`, `EURL`, `Ltd`) |\n| `numeroFacture`      | Conservé tel quel + variante numérique pure (`F-2026-04-1287` → aussi `20260412 87`)      |\n\n---\n\n## Cascade de matching (par transaction)\n\nPour chaque transaction, parcourir les factures du mois (et du mois précédent + suivant — cf. fenêtre date) et calculer un **score** :\n\n```\nscore = match_montant × poids_montant\n      + match_label × poids_label\n      + match_date × poids_date\n```\n\nPoids par défaut :\n\n- `poids_montant` : 0.5\n- `poids_label` : 0.3\n- `poids_date` : 0.2\n\n### Score `match_montant`\n\n| Condition                                                           | Score |\n| ------------------------------------------------------------------- | ----- |\n| `abs(montant - montantTTC) ≤ 0.01`                                  | 1.0   |\n| `abs(montant - montantTTC) / montantTTC < 0.02` (escompte typique)  | 0.6   |\n| Autre                                                               | 0.0   |\n\n**Sens** : `montant > 0` → ne peut matcher qu'une facture `vente`. `montant < 0` → ne peut matcher qu'une facture `achat`. Pas de cross-match.\n\n### Score `match_label`\n\nCascade — première règle qui matche :\n\n| Condition                                                                    | Score |\n| ---------------------------------------------------------------------------- | ----- |\n| `libelle` contient `numeroFacture` (variante numérique pure incluse)         | 1.0   |\n| `libelle_tokens` contient un token de `emetteur_normalise` (longueur ≥ 4)    | 0.8   |\n| `libelle_tokens` contient un préfixe / suffixe de l'émetteur (Levenshtein ≤ 2) | 0.5   |\n| Aucun                                                                        | 0.0   |\n\n### Score `match_date`\n\n| Condition                                            | Score |\n| ---------------------------------------------------- | ----- |\n| `abs(date - dateEmission) ≤ 3 j`                     | 1.0   |\n| `abs(date - dateEmission) ≤ 30 j`                    | 0.7   |\n| `abs(date - dateEmission) ≤ 60 j`                    | 0.3   |\n| Au-delà                                              | 0.0   |\n\n---\n\n## Classification du score final\n\n| Score agrégé | Confiance | Action                                                            |\n| ------------ | --------- | ----------------------------------------------------------------- |\n| ≥ 0.85       | `fort`    | Ligne CSV matchée + update `followup.md` (`payée <date>`)         |\n| 0.65 – 0.85  | `moyen`   | Ligne CSV matchée + update `followup.md` (`payée ? <date>`, avec `?`) |\n| 0.45 – 0.65  | `faible`  | Ligne CSV `match suggéré` mais **pas** d'update followup          |\n| < 0.45       | aucun     | Transaction reste `unmatched`                                     |\n\nSi une transaction a **plusieurs candidats ex-aequo** (écart < 0.10 entre top-2) → marquer `match_ambigu` dans le CSV et ne pas écrire dans followup. Lister les N candidats dans la colonne `candidats`.\n\n---\n\n## Fenêtre de date et débordement de mois\n\nUne facture émise le 28/03 peut être payée le 02/04. Donc :\n\n- Charger les factures des **3 mois** : précédent, courant, suivant, autour du mois de la transaction.\n- Si match avec une facture d'un autre mois → l'écrire dans le CSV du mois de la **transaction**, mais annoter `facture_mois_origine` = mois de la facture.\n- Mettre à jour le `followup.md` du **mois de la facture** (pas celui de la transaction).\n\n---\n\n## Cas limites\n\n### Frais bancaires / commissions\n\nTransaction avec `libelle` contenant un mot-clé d'agios :\n\n- `frais`, `commission`, `cotisation`, `agios`, `interets`, `tenue de compte`\n\n→ Pas de matching tenté. Annoter `categorie_auto: frais_bancaires` dans le CSV. Ne pas alerter (comportement normal).\n\n### Salaires / charges sociales\n\nMots-clés : `salaire`, `urssaf`, `dsn`, `cipav`, `madelin`, `mutuelle`, `prevoyance`.\n\n→ Pas de matching. Annoter `categorie_auto: rh_charges`. Pas d'alerte par défaut, mais flag possible si montant aberrant vs historique (v0.3+).\n\n### Virements internes\n\nMots-clés : `virement interne`, `vir entre comptes`, `transfert`.\n\n→ Pas de matching. Annoter `categorie_auto: transfert_interne`. Pas d'alerte.\n\n### Encaissements partiels\n\nSi `montant < montantTTC` ET `montant > 0` ET match label + date OK :\n\n- Annoter `paiement_partiel` dans le CSV.\n- Calculer pourcentage : `(montant / montantTTC) × 100`.\n- Update `followup.md` : `partielle (X%)`.\n- Au prochain run, chercher les transactions complémentaires pour atteindre 100 %.\n\n### Multi-paiements pour une facture\n\nUne facture peut être payée en plusieurs fois (acompte + solde). Si plusieurs transactions matchent la même facture avec sens cohérent et somme = `montantTTC` → OK, listées comme un groupe dans le CSV avec un identifiant `match_group`.\n\nSi somme > `montantTTC` → anomalie `doublon_paiement`.\n\n### Devise étrangère\n\nHors scope v0.1. Si une facture USD et un encaissement EUR matchent par hasard sur le montant → faux positif. Mitigation v0.2 : taux de change + tolérance plus large.\n\n### Relevés multi-comptes\n\nUn client peut avoir plusieurs comptes (cf. `bank-statements/2026-04_BNP-courant.pdf` et `bank-statements/2026-04_Qonto-pro.pdf`). Toutes les transactions de tous les comptes du mois sont mises en pool commun pour le matching. Le CSV indique la source dans une colonne `compte`.\n\n### Reclassement d'une facture après matching\n\nSi une facture est reclassée par `organisation-documents` (mauvais client, mauvaise catégorie) APRÈS un matching :\n\n- Le `rapprochement-state.json` détecte le changement de chemin et marque le mois `dirty: true`.\n- Au prochain run, le matching est recalculé pour ce mois → le CSV est régénéré, `followup.md` mis à jour.\n- L'ancien match est purgé de l'état précédent.\n\n---\n\n## Anti-faux-positifs\n\nSources connues de faux positifs et garde-fous :\n\n| Source                                                          | Garde-fou                                                                                  |\n| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| 2 fournisseurs facturent le même montant rond (`100,00 €`)      | Required : label OU date proche, jamais montant seul                                       |\n| Abonnement mensuel identique → faux match sur le mauvais mois   | Fenêtre date prioritaire si plusieurs candidats même montant                               |\n| Émetteur générique (`Amazon`) qui matche partout                | Liste noire de tokens trop génériques : `paiement`, `achat`, `commande`, `service`, `web` |\n| Transaction de l'année précédente reposté (relevé re-généré)    | Si date < `mois - 90 j` → ignorée                                                          |\n\n**Règle absolue** : un match `fort` ne doit jamais écraser une donnée déjà éditée par le comptable dans `followup.md` (statut paiement, prochaine relance). En cas de conflit, garder la valeur humaine et lister le conflit dans le CSV avec `anomalie=libelle_suspect` + commentaire automatique « override manuel respecté ».\n\nFile v1.0.1:references/structure-cible.md\n\n# Référence — Structure cible & conventions de nommage\n\n> Référence chargée à la demande par `organisation-documents`. Spec exhaustive du classement.\n> Source de vérité pour les autres skills qui écrivent dans le même arbre (`relances`, `facturation`, `rapprochement-bancaire`).\n\n---\n\n## Arborescence complète\n\n```\n~/.openclaw/workspace/\n├── clients/\n│   ├── <slug>/                                # un dossier = un client réel (existant ou auto-créé)\n│   │   ├── contrats/                          # niveau client — contrats pluriannuels\n│   │   │   └── <AAAA-MM-JJ>_<Type>_<Contrepartie>.<ext>\n│   │   ├── <AAAA>/\n│   │   │   └── <MM>/\n│   │   │       ├── bank-statements/           # toujours un dossier (multi-comptes possibles)\n│   │   │       │   └── <AAAA-MM>_<BanqueOuCompte>.<ext>\n│   │   │       ├── invoices/\n│   │   │       │   ├── in/                    # factures reçues (achats)\n│   │   │       │   │   └── <AAAA-MM-JJ>_<Émetteur>_<MontantTTC>.<ext>\n│   │   │       │   └── out/                   # factures émises (ventes)\n│   │   │       │       └── <AAAA-MM-JJ>_<Destinataire>_<MontantTTC>.<ext>\n│   │   │       ├── notes-de-frais/\n│   │   │       │   └── <AAAA-MM-JJ>_<Collaborateur>_<Émetteur>_<MontantTTC>.<ext>\n│   │   │       ├── autres/\n│   │   │       │   └── <AAAA-MM-JJ>_<Description>.<ext>\n│   │   │       ├── relances.md                # maintenu par skill `relances`\n│   │   │       └── followup.md                # maintenu par skill `facturation`\n│   │   ├── _archive-suppression/              # RGPD soft-delete (grâce 30 j)\n│   │   ├── index.json                         # index par client\n│   │   └── audit.log                          # déplacements / renommages\n│   └── index-global.json\n├── .pending-attribution/                      # parking technique HORS clients/\n│   ├── <AAAA-MM-JJ-réception>_<hash-court>.<ext>\n│   └── pending-attribution.json\n└── clients.json\n```\n\n### Pas de slug réservé dans `clients/`\n\nTout dossier sous `clients/` correspond à un client réel — existant ou auto-créé en `draft-auto-created`. Aucune entité technique (`_cabinet`, `_non-attribue`, etc.) ne pollue cet arbre.\n\n### `.pending-attribution/`\n\nParking technique pour les documents que la cascade d'identification n'a pas su attribuer (expéditeur générique sans signal exploitable). Vit à la racine du workspace, **hors** `clients/`.\n\n| Élément                          | Rôle                                                                              |\n| -------------------------------- | --------------------------------------------------------------------------------- |\n| `<date-réception>_<hash>.<ext>`  | Le fichier physique, renommé pour la traçabilité (pas le filename d'origine)      |\n| `pending-attribution.json`       | Métadonnées extraites partielles + question à poser à l'utilisateur               |\n\nUne fois la réponse utilisateur reçue (« rattacher à `acme-sa` » ou « créer client `nouveau-client` »), le fichier est **déplacé** vers son chemin cible définitif dans `clients/<slug>/…` et l'entrée correspondante de `pending-attribution.json` est purgée.\n\n---\n\n## Mapping `categorie` → dossier\n\n| `categorie`      | Dossier cible                            | Vocab comptable    | Niveau   |\n| ---------------- | ---------------------------------------- | ------------------ | -------- |\n| `achat`          | `<AAAA>/<MM>/invoices/in/`               | Achats             | mois     |\n| `vente`          | `<AAAA>/<MM>/invoices/out/`              | Ventes             | mois     |\n| `bank-statement` | `<AAAA>/<MM>/bank-statements/`           | Relevés bancaires  | mois     |\n| `note-de-frais`  | `<AAAA>/<MM>/notes-de-frais/`            | Notes de frais     | mois     |\n| `contrat`        | `contrats/` (racine `<slug>/`)           | Contrats           | client   |\n| `autre`          | `<AAAA>/<MM>/autres/`                    | Autres             | mois     |\n\nL'enum machine est en kebab-case singulier (`achat`, pas `Achats`). Le vocab capitalisé pluriel est réservé aux **messages affichés au comptable** ; il n'apparaît jamais dans un chemin ni un payload.\n\n---\n\n## Conventions de nommage\n\n### Slug client\n\n- lowercase\n- accents retirés (`é` → `e`, `ç` → `c`, etc.)\n- espaces → `-`\n- caractères non alphanumériques → `-`\n- pas de `-` consécutifs, pas de `-` en début/fin\n- dérivé du **domaine** de l'expéditeur, pas de l'email (`trendex.tech` → `trendex-tech`)\n\n### Composants de nom de fichier\n\n| Token            | Règle                                                                                                  |\n| ---------------- | ------------------------------------------------------------------------------------------------------ |\n| `AAAA-MM-JJ`     | `dateEmission` du document (ISO 8601), pas date de réception                                          |\n| `AAAA-MM`        | `dateEmission` tronquée au mois — utilisé pour les relevés bancaires (couvre généralement un mois)    |\n| `Émetteur`       | Raison sociale source, 10 premiers caractères significatifs, sans accents ni espaces (`Orange Pro` → `OrangePro`) |\n| `Destinataire`   | Idem, pour les factures émises (`out/`) — c'est le client final du client du cabinet                  |\n| `Collaborateur`  | Prénom ou prénom+initiale, sans accents (`Thomas`, `ThomasM`)                                         |\n| `MontantTTC`     | Sans séparateur de milliers, point décimal, sans symbole (`348.50`, pas `348,50 €`)                   |\n| `BanqueOuCompte` | Nom court de banque + suffixe compte optionnel (`BNP-courant`, `Qonto-pro`)                           |\n| `Type` (contrat) | `MSA`, `NDA`, `Bail`, `CDI`, `Avenant`, etc. — vocabulaire court figé                                 |\n| `Contrepartie`   | Slug ou raison sociale courte de l'autre partie au contrat                                            |\n| `Description`    | Slug court décrivant le document quand aucune catégorie ne s'applique (`Statuts`, `KBis`, `Procuration`) |\n\n### Séparateur\n\n`_` entre composants. `-` à l'intérieur d'un composant. Jamais d'espace.\n\n### Extension\n\nPréservée du document source en lowercase (`.pdf`, `.jpg`, `.png`, `.eml`, `.csv`, `.ofx`, `.xml`).\n\n---\n\n## Cas spéciaux\n\n### Multi-comptes bancaires\n\nUn client peut avoir plusieurs comptes (courant, livret pro, devises). `bank-statements/` est toujours un dossier, jamais un fichier unique :\n\n```\nclients/acme-sa/2026/04/bank-statements/\n├── 2026-04_BNP-courant.pdf\n├── 2026-04_BNP-livret-pro.pdf\n└── 2026-04_Qonto-usd.pdf\n```\n\n### Multi-pages relevé / cas hebdomadaires\n\nSi un relevé couvre plusieurs périodes ou est fragmenté, suffixer avec la plage :\n\n```\nclients/acme-sa/2026/04/bank-statements/2026-04-01_2026-04-15_BNP-courant.pdf\n```\n\n### Contrats pluriannuels\n\nLes contrats vivent à `<slug>/contrats/`, indexés sur la date de signature, pas la date de réception. Un avenant garde le même `<Type>` préfixé `Avenant-` :\n\n```\nclients/acme-sa/contrats/2024-01-15_MSA_TrendexTech.pdf\nclients/acme-sa/contrats/2026-03-01_Avenant-MSA_TrendexTech.pdf\n```\n\n### Notes de frais — toujours rattachées à un client\n\nUne note de frais appartient toujours à un client (celui dont l'employé doit être remboursé). L'émetteur du document est le commerçant (SNCF, restaurant…) ; le **bénéficiaire** — identifié dans le composant `<Collaborateur>` du nom — est l'employé d'un client.\n\n```\nclients/<client>/<AAAA>/<MM>/notes-de-frais/<AAAA-MM-JJ>_<Collaborateur>_<Émetteur>_<MontantTTC>.<ext>\n```\n\nLes frais perso de l'utilisateur lui-même **ne sont pas dans le scope** de ce skill. Si un document de ce type est détecté (typiquement : ticket envoyé depuis l'adresse perso de l'utilisateur, sans contexte client) → traité comme `non attribuable` → `.pending-attribution/`.\n\n### Documents non datables\n\nSi `dateEmission` est introuvable ET non déductible (carte de visite, capture d'écran, etc.) → date de réception en fallback, et alerte `extraction_incomplete`. Décision auto-rétrogradée à `needs_review`.\n\n### Reclassement (correction d'un mauvais classement)\n\nQuand le comptable corrige une attribution (mauvais client, mauvaise catégorie), le skill :\n\n1. Déplace le fichier vers le nouveau chemin (sans renommage si le nouveau nom est identique).\n2. Met à jour `index.json` des deux clients concernés (ancien + nouveau).\n3. Logue l'opération dans `audit.log` du nouveau client avec `motif: reclassement-manuel`.\n4. Ne touche jamais à `_archive-suppression/` (la suppression est un autre flux).\n\n---\n\n## `relances.md` — schéma\n\n> Maintenu par le skill `relances`. Lu par `organisation-documents` en cas de classement d'une réponse à relance.\n\nUn fichier par mois et par client : `clients/<slug>/<AAAA>/<MM>/relances.md`.\n\n**Contenu** — un fichier = deux tableaux markdown :\n\n```markdown\n# Relances — <Raison sociale client> — <AAAA>/<MM>\n\n> Maintenu par le skill `relances`. Toute modification manuelle est tolérée mais peut être écrasée à la prochaine exécution.\n\n## À envoyer\n\n| Date prévue | Facture       | Destinataire | Montant TTC | Échéance dépassée | Canal | Type   | Statut    |\n| ----------- | ------------- | ------------ | ----------- | ----------------- | ----- | ------ | --------- |\n| 2026-05-15  | F-2026-04-012 | TrendexTech  | 2 400,00 €  | 15 j              | email | R1     | planifiée |\n| 2026-05-30  | F-2026-04-012 | TrendexTech  | 2 400,00 €  | 30 j              | email | R2     | planifiée |\n\n## Effectuées\n\n| Date envoi | Facture       | Destinataire | Montant TTC | Canal   | Type | Réponse client | Suite             |\n| ---------- | ------------- | ------------ | ----------- | ------- | ---- | -------------- | ----------------- |\n| 2026-04-15 | F-2026-03-008 | ACME Corp    | 1 248,00 €  | email   | R1   | promesse 15/05 | R2 planifié 30/05 |\n| 2026-04-30 | F-2026-03-005 | Foo SAS      | 850,00 €    | courrier | R3   | aucune         | escalade huissier |\n```\n\n**Colonnes obligatoires** — toute autre colonne est facultative.\n\n**Règles d'écriture** :\n\n- Le skill `relances` est seul autorisé à modifier ces fichiers. `organisation-documents` les lit uniquement.\n- L'ordre des lignes dans `À envoyer` : chronologique croissant (la prochaine en haut).\n- L'ordre des lignes dans `Effectuées` : chronologique décroissant (la plus récente en haut).\n- Quand une relance planifiée est envoyée, le skill `relances` la migre de `À envoyer` vers `Effectuées` dans le fichier du mois courant (pas du mois de la facture).\n\n---\n\n## `followup.md` — schéma\n\n> Maintenu par le skill `facturation`. Lu par `organisation-documents` en cas de classement d'une facture entrante (paiement) ou sortante (émise).\n\nUn fichier par mois et par client : `clients/<slug>/<AAAA>/<MM>/followup.md`.\n\n```markdown\n# Followup factures — <Raison sociale client> — <AAAA>/<MM>\n\n> Maintenu par le skill `facturation`. Vue mois en cours.\n\n## À émettre\n\n| Date prévue | Destinataire | Description        | Montant TTC prévu | Statut         |\n| ----------- | ------------ | ------------------ | ----------------- | -------------- |\n| 2026-04-01  | TrendexTech  | Forfait avril      | 2 400,00 €        | à préparer     |\n| 2026-04-15  | ACME Corp    | Prestation projet X | 5 800,00 €        | brouillon prêt |\n\n## Émises ce mois\n\n| Date émission | N° facture    | Destinataire | Montant TTC | Échéance   | Statut paiement | Prochaine relance |\n| ------------- | ------------- | ------------ | ----------- | ---------- | --------------- | ----------------- |\n| 2026-04-01    | F-2026-04-012 | TrendexTech  | 2 400,00 €  | 2026-04-30 | non payée       | R1 le 2026-05-15  |\n| 2026-04-15    | F-2026-04-013 | ACME Corp    | 5 800,00 €  | 2026-05-15 | partielle (50%) | suivi 2026-05-15  |\n| 2026-04-22    | F-2026-04-014 | Foo SAS      | 720,00 €    | 2026-05-22 | payée 2026-04-28 | —                |\n```\n\n**Statut paiement** — enum : `non payée` | `partielle` | `payée` | `litige` | `irrécouvrable`.\n\n**Mise à jour automatique** — quand `organisation-documents` classe un relevé bancaire dans `bank-statements/`, il devrait théoriquement déclencher un rapprochement (`rapprochement-bancaire` skill) qui mettra à jour `followup.md` avec les paiements détectés. Hors scope de ce skill, mais le contrat de chemin est figé ici pour permettre l'intégration.\n\n---\n\n## Pourquoi cette structure\n\n| Décision                                    | Alternative rejetée                          | Raison                                                                                  |\n| ------------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------- |\n| `<AAAA>/<MM>/` (deux niveaux)               | `<AAAA-MM>/` (un niveau)                     | Conservation 10 ans = 120 dossiers à plat ingérables sur Drive/Finder                   |\n| `invoices/in` + `invoices/out` séparés      | `invoices/` unique                           | TVA et PCG différents — un compta ne mélange jamais                                     |\n| `contrats/` à la racine client              | `contrats/` au mois                          | Contrats pluriannuels — les enterrer dans un mois rend la lecture impossible            |\n| `bank-statements/` toujours un dossier      | Fichier `bank-statement.pdf` direct          | Multi-comptes très courant, dossier dès le départ évite la migration                    |\n| `relances.md` + `followup.md` au mois       | Fichier rolling à la racine client           | Le comptable raisonne par mois (clôtures) — état mensuel plus utile que log global      |\n| Slug dérivé du domaine                      | Slug dérivé de l'email                       | 1 client = N employés = N emails — mais 1 dossier client                                |\n| Aucun slug réservé dans `clients/`          | `_cabinet` + `_non-attribue` pseudo-clients   | Un slug = un client réel. Le reste vit dans `.pending-attribution/` hors `clients/`.   |\n| Auto-création par défaut                    | Toujours demander à l'utilisateur            | Trop interruptif au quotidien. Confirmation opt-in via `clientCreation: \"confirm\"`.    |\n| Enum `categorie` kebab-case singulier       | Capitalisé pluriel                           | Aligné sur les noms de dossiers (`invoices/in` etc.) — pas de mismatch machine/affiché |\n\nFile v1.0.1:skill-card.md\n\n## Description: <br>\nRuns a deterministic accounting reconciliation workflow for French accounting firms, matching invoices with bank-statement transactions, validating VAT, and producing client follow-up, reminder, anomaly, vision-queue, and batch-report files. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[trendex](https://clawhub.ai/user/trendex) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nAccounting staff and supervised agents use this skill to run daily reconciliation over an existing client document tree, then review paid, unpaid, partial, overdue, and anomalous items before taking action. <br>\n\n### Deployment Geography for Use: <br>\nFrance <br>\n\n## Known Risks and Mitigations: <br>\nRisk: The workflow handles sensitive client invoices, bank statements, and derived accounting sidecars. <br>\nMitigation: Install only where the configured clients root is the intended accounting workspace, and apply appropriate retention and access controls to generated JSON, Markdown, cache, report, and vision sidecar files. <br>\nRisk: Vision-resolution artifacts and temporary images may contain financial data. <br>\nMitigation: Review access controls and cleanup practices for vision sidecars and temporary vision images, especially under /tmp. <br>\nRisk: Duplicate cleanup can remove local files when run with the apply flag. <br>\nMitigation: Run duplicate cleanup in dry-run mode first, review the proposed changes, and use backups before running scripts/dedupe_disk.py with --apply. <br>\nRisk: Reminder outputs are proposals and may be wrong if a reconciliation is uncertain or an anomaly is blocking. <br>\nMitigation: Require human accounting review before sending reminders or relying on flagged results. <br>\n\n\n## Reference(s): <br>\n- [ClawHub Skill Page](https://clawhub.ai/trendex/rapprochement-bancaire) <br>\n- [CSV Schema Reference](references/csv-schema.md) <br>\n- [Matching Rules Reference](references/matching-rules.md) <br>\n- [Target Folder Structure Reference](references/structure-cible.md) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance] <br>\n**Output Format:** [Markdown summaries and shell commands, with generated JSON, Markdown transcription, cache, report, and vision sidecar files produced by the bundled scripts.] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [The skill reads invoices and bank statements from a local clients root and writes per-client follow-up, reminder, anomaly, vision-queue, and consolidated batch-report files.] <br>\n\n## Skill Version(s): <br>\n1.0.1 (source: server release metadata) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v1.0.0: 9 files, 31021 bytes\n\nFiles: README.md (8694b), references/csv-schema.md (7202b), references/matching-rules.md (9018b), references/structure-cible.md (14917b), scripts/extract.py (12883b), scripts/main.py (19447b), skill-card.md (3215b), SKILL.md (10614b), _meta.json (141b)\n\nFile v1.0.0:SKILL.md\n\n---\nname: rapprochement-bancaire\ndescription: Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valide la TVA, maintient un unique `rapprochement.json` par client (factures + lignes bancaires non rapprochées + anomalies + relances, groupés par période mensuelle). Le travail réel est fait par scripts/main.py (qui appelle scripts/extract.py). Ne traite jamais les e-mails, ne classe jamais de documents.\nlicense: Interne — usage privé OpenClaw\n---\n\n# Skill `rapprochement-bancaire`\n\n> Moteur d'état. Travaille uniquement sur l'arborescence `clients/<slug>/...` produite par `organisation-documents`.\n> **Le travail réel est fait par `scripts/main.py`** (qui appelle `scripts/extract.py` pour toute l'extraction de texte). Ce skill = quand le lancer + comment rendre compte.\n\n---\n\n## ⚠️ Règle absolue — EXÉCUTION DU SCRIPT (non négociable)\n\n**Pour chaque invocation, la SEULE action correcte est d'exécuter la commande :**\n\n```bash\npython3 scripts/main.py <racine_clients>\n```\n\npuis de lire le `rapprochement.json` de chaque client + le `compta_batch_report_<date>.json` consolidé, et de relayer au comptable.\n\n### ❌ INTERDIT\n\n- **Réimplémenter le rapprochement en Python inline / pseudo-code.** La logique (Pass 1 par référence, Pass 2 fuzzy, validation TVA, détection des anomalies, statuts overdue/partial, génération des relances, regroupement par période) est dans `scripts/main.py`. La refaire à la main est garanti de diverger.\n- **Écrire `rapprochement.json` à la main.** C'est le script qui le fait, dans son format exact (objet `{ client, generated_at, periods[] }` ; chaque période contient `invoices[]`, `unmatched_bank_lines[]`, `period_anomalies[]`, `relances[]` ; chaque facture porte son champ `anomalies[]`). Toute autre forme (liste plate, dict keyé par `invoice_id`, champs `payments` / `amount_paid` ad hoc) casse les lectures ultérieures.\n- **Modifier le format du fichier de sortie.** Le format est contractuel entre ce skill et ses consommateurs (backend de provisioning, agent OpenClaw, dashboards).\n- **Recréer les anciens fichiers `followup.json` / `relances.json` / `anomalies.json`.** Ils ont été remplacés par le `rapprochement.json` unique. Le script les supprime à chaque run s'ils traînent.\n\n### ✅ OBLIGATOIRE\n\n1. Exécuter la commande shell ci-dessus.\n2. Lire le fichier produit.\n3. Relayer au comptable un tableau lisible par client (factures payées / en attente / partielles / overdue, relances, anomalies — en mettant en avant les **bloquantes**).\n\n### Si le script échoue\n\nSignale l'erreur exacte (stderr). Ne rejoue PAS le batch à la main.\n\n> Pourquoi : on a déjà eu un run où l'agent a produit le fichier de sortie au mauvais format (dict avec clés `\"in/N\"`, champs `payments`/`amount_paid`/`amount_remaining` au lieu de `bank_matched`/`status`). Conséquence : aucun outil ni skill aval ne pouvait l'exploiter. Le script génère le bon format à tous les coups.\n\n---\n\n## Exécution\n\n```bash\npython3 scripts/main.py [<racine_clients>]\n# racine_clients par défaut : ~/.openclaw/workspace/clients\n```\n\nÀ lancer :\n- une fois par jour (idéalement la nuit), sur tous les clients ;\n- ou immédiatement après un trigger `rapprochement-bancaire` émis par `organisation-documents`.\n\nLe script :\n\n1. **Parcourt** `clients/*` en ignorant les dossiers techniques `_*` (`_a-identifier`, `_incomplet`, `_non-attribue`, `_cabinet`).\n2. **Périodes traitées** : mois courant + mois précédent, plus tout mois ancien non verrouillé (`batch.lock.json` absent). Un mois verrouillé n'est pas retraité.\n3. **Factures** : nom de fichier conventionnel (`AAAA-MM-JJ_N°Facture_Contrepartie_MontantTTC.pdf`) → `invoice_id` + montant. Si le nom n'est pas exploitable, lecture du contenu via `extract.py`. Si toujours rien → anomalie de période `facture_illisible`.\n4. **Relevés bancaires** : transactions extraites ligne par ligne via `extract.py` (`DATE | LIBELLÉ | MONTANT | CR/DB`), avec la référence facture si le libellé contient `REF <id>` ou `FACT <id>`. Aucune transaction extractible → anomalie de période `releve_non_parseable`.\n5. **Rapprochement** (montant comparé en valeur absolue : une facture `out` est encaissée par un crédit, une `in` réglée par un débit) :\n   - **Pass 1** — réf facture : transaction dont `invoice_ref == facture.invoice_id`. Montant exact (±1 €) → `paid` ; montant inférieur → `partial` (conserve `amount_paid`, `amount_remaining`) ; supérieur → `paid` + `overpaid_by`.\n   - **Pass 2** — fuzzy : `|montant| ±1 €` ET similarité libellé / contrepartie ≥ 0.6.\n   - Aucun match + échéance dépassée → `overdue`.\n6. **Validation TVA** de chaque facture : si `|TVA déclarée − TVA attendue| / TVA attendue > 5 %` (TVA attendue = `TOTAL HT × taux`) → anomalie `tva_incorrecte` rattachée à la facture (TVA 0 % / exonération ignorée).\n7. **Anomalies** : voir tableau ci-dessous. Les anomalies *rattachables à une facture* (`tva_incorrecte`, `invoice_overdue`) vivent dans le champ `anomalies[]` de la facture. Les anomalies *non rattachables* (`doublon_paiement`, `releve_non_parseable`, `facture_illisible`) vont dans `period_anomalies[]` de leur période. Les lignes bancaires non rapprochées (`facture_manquante`, `paiement_orphelin`) vont dans `unmatched_bank_lines[]` de la période où la transaction a été observée.\n8. **Relances** : `overdue` / `partial` / `unpaid` hors délai → step selon ancienneté, dans `relances[]` de la période de la facture.\n9. Écrit `clients/<slug>/rapprochement.json` (un seul fichier par client) et un rapport consolidé `compta_batch_report_<date>.json`.\n\n**Après l'exécution**, lire le fichier et relayer au comptable un tableau lisible par client (factures payées / en attente, relances, anomalies — en mettant en avant les anomalies **bloquantes**). Jamais de chemins techniques.\n\n---\n\n## Format de sortie (`rapprochement.json`)\n\n```json\n{\n  \"client\": \"acme-sa\",\n  \"generated_at\": \"2026-05-20\",\n  \"periods\": [\n    {\n      \"period\": \"2026-05\",\n      \"locked\": false,\n      \"invoices\": [\n        {\n          \"invoice_id\": \"F-001\",\n          \"type\": \"out\",\n          \"amount\": 1200.00,\n          \"status\": \"paid\",\n          \"bank_matched\": true,\n          \"matched_tx\": \"VIR ACME REF F-001\",\n          \"issued_date\": \"2026-05-03\",\n          \"due_date\": \"2026-06-02\",\n          \"counterparty\": \"ACME\",\n          \"counterparty_name\": \"ACME SA\",\n          \"source_file\": \"clients/acme-sa/2026/05/invoices/out/...\",\n          \"anomalies\": []\n        }\n      ],\n      \"unmatched_bank_lines\": [\n        {\n          \"type\": \"facture_manquante\",\n          \"invoice_ref\": \"F-099\",\n          \"label\": \"VIR REF F-099\",\n          \"amount\": 2500,\n          \"date\": \"2026-05-12\",\n          \"blocking\": true\n        }\n      ],\n      \"period_anomalies\": [\n        { \"type\": \"doublon_paiement\", \"label\": \"VIR ACME\", \"amount\": 850, \"date\": \"2026-05-09\", \"blocking\": true }\n      ],\n      \"relances\": [\n        { \"invoice_id\": \"F-007\", \"counterparty\": \"DUPONT\",\n          \"amount\": 450, \"due_date\": \"2026-04-02\", \"days_late\": 48,\n          \"step\": 2, \"status\": \"pending\", \"next_action_date\": \"2026-05-25\" }\n      ]\n    }\n  ]\n}\n```\n\n---\n\n## Statuts de facture\n\n| Statut | Sens |\n|--------|------|\n| `unpaid` | non échue, aucun paiement |\n| `paid` | paiement confirmé (rapprochement réussi) |\n| `partial` | paiement partiel — `amount_paid` + `amount_remaining` conservés |\n| `overdue` | échéance dépassée, aucun paiement |\n\n---\n\n## Anomalies\n\n**Bloquantes** (empêchent la clôture de la période) :\n\n| Type | Où | Condition |\n|------|----|-----------|\n| `doublon_paiement` | `period_anomalies` | même date + montant + libellé |\n| `tva_incorrecte` | `invoices[].anomalies` | écart TVA calculée / déclarée > 5 % |\n| `facture_manquante` | `unmatched_bank_lines` | une ligne du relevé cite un n° de facture (`REF`/`FACT`) absent du dossier, montant > 1 000 € |\n| `paiement_orphelin` | `unmatched_bank_lines` | crédit > 1 000 € sans aucune référence ni facture |\n\n**Non bloquantes** (signalées, clôture possible) :\n\n| Type | Où | Condition |\n|------|----|-----------|\n| `facture_manquante` | `unmatched_bank_lines` | n° de facture cité au relevé mais absent, montant ≤ 1 000 € |\n| `paiement_orphelin` | `unmatched_bank_lines` | crédit ≤ 1 000 € sans référence |\n| `releve_non_parseable` | `period_anomalies` | aucune transaction extractible d'un relevé |\n| `facture_illisible` | `period_anomalies` | facture dont ni le nom ni le contenu ne donnent n° + montant |\n| `invoice_overdue` | `invoices[].anomalies` | facture non payée, échéance dépassée |\n\n> `facture_manquante` ≠ `paiement_orphelin` : le premier = paiement qui **cite** un n° de facture qu'on n'a pas reçu (le client a oublié de transmettre la pièce) ; le second = encaissement sans aucune référence.\n\n---\n\n## Relances (`periods[].relances`)\n\n| Ancienneté du retard | Step |\n|----------------------|------|\n| ≤ 30 j | 1 |\n| ≤ 60 j | 2 |\n| ≤ 90 j | 3 |\n| > 90 j | escalation |\n\nPour `partial` : mention explicite `\"Solde restant dû : X,XX €\"`.\n\n---\n\n## Clôture de période\n\nLe script crée `clients/<slug>/<AAAA>/<MM>/batch.lock.json` quand : aucune anomalie **bloquante** sur la période, hash des fichiers stable depuis 7 jours, aucun statut `unpaid`/`overdue` non justifié. Les périodes verrouillées ne sont jamais retraitées sauf changement de hash.\n\n---\n\n## Règles critiques\n\n- Ne jamais supprimer de données. Ne jamais retraiter un mois verrouillé.\n- `rapprochement.json` est un **cache reconstructible** : les PDFs classés restent la vérité. Le batch peut tout recalculer depuis zéro.\n- `clients.json` est lu seulement (maintenu par `organisation-documents`), jamais écrit ici.\n- Toute l'extraction de texte passe par `scripts/extract.py` — source unique, pas de logique de parsing dupliquée ici.\n- Le matching peut être inter-période : un paiement de mai peut solder une facture de janvier. La facture reste dans la période de son `issued_date` (status `paid`), la transaction n'apparaît pas en `unmatched_bank_lines` de mai.\n\n---\n\n## Philosophie\n\n```\norganisation-documents  →  classe les pièces, déduit clients.json\nrapprochement-bancaire  →  rapproche, valide, reconstruit l'état comptable  (scripts/main.py)\nrelances                →  décision différée\n```\n\nLe système doit pouvoir être recalculé intégralement à partir des documents classés.\n\nFile v1.0.0:README.md\n\n# rapprochement-bancaire\n\n> Claude skill for **French accounting firms** (`cabinets comptables`). Reads the `clients/<slug>/...` tree produced by [`organisation-documents`](https://github.com/developers-trendex/organisation-documents), reconciles each invoice with its bank-statement transaction, validates VAT, and writes a single `rapprochement.json` per client (invoices + unmatched bank lines + anomalies + relances, grouped by monthly period) plus a consolidated report. Deterministic script-driven.\n\n## What it does\n\nFor each invocation, the skill runs **one command** :\n\n```bash\npython3 scripts/main.py <clients_root>\n```\n\nThat script:\n\n1. Walks every client folder under `<clients_root>` (skipping `_a-identifier/`, `_incomplet/`, `_non-attribue/`, `_cabinet/`).\n2. For each active period (current + previous month, plus any older month without `batch.lock.json`) :\n   - Reads invoices from `invoices/in/` and `invoices/out/`.\n   - Reads transactions from `bank-statements/` via `scripts/extract.py` (one line per transaction, with the `REF`/`FACT <id>` reference captured when present).\n3. Reconciles in two passes (amount compared in absolute value — an `out` invoice is settled by a credit, an `in` by a debit) :\n   - **Pass 1** — direct match by invoice reference (`REF` / `FACT` in the bank label). Exact amount (±1 €) → `paid` ; lower amount → `partial` with `amount_paid` and `amount_remaining` ; higher → `paid` with `overpaid_by`.\n   - **Pass 2** — fuzzy : `|amount| ±1 €` **and** counterparty-name similarity ≥ 0.6.\n   - No match + due date passed → `overdue`.\n4. Validates VAT on each invoice : if `|VAT declared − TOTAL HT × rate| / expected > 5 %`, flags a blocking `tva_incorrecte` anomaly on the invoice (exempted-VAT invoices are silently ignored).\n5. Detects other anomalies (see below).\n6. Generates relances based on lateness (`overdue`, `partial`, `unpaid` past due).\n7. Writes one `rapprochement.json` per client + `compta_batch_report_<date>.json` consolidated.\n\nThe agent only **reads the JSON file and relays the summary to the accountant** — payments, late invoices, anomalies (highlighting blocking ones), no technical paths.\n\n## Output format\n\nA single `rapprochement.json` per client, grouped by monthly period:\n\n```json\n{\n  \"client\": \"acme-sa\",\n  \"generated_at\": \"2026-05-20\",\n  \"periods\": [\n    {\n      \"period\": \"2026-05\",\n      \"locked\": false,\n      \"invoices\": [\n        { \"invoice_id\": \"F-001\", \"type\": \"out\", \"amount\": 1200.00,\n          \"status\": \"paid\", \"bank_matched\": true,\n          \"matched_tx\": \"VIR ACME REF F-001\",\n          \"issued_date\": \"2026-05-03\", \"due_date\": \"2026-06-02\",\n          \"counterparty\": \"ACME\", \"counterparty_name\": \"ACME SA\",\n          \"anomalies\": [] }\n      ],\n      \"unmatched_bank_lines\": [\n        { \"type\": \"facture_manquante\", \"invoice_ref\": \"F-099\",\n          \"label\": \"VIR REF F-099\", \"amount\": 2500, \"date\": \"2026-05-12\",\n          \"blocking\": true }\n      ],\n      \"period_anomalies\": [\n        { \"type\": \"doublon_paiement\", \"label\": \"VIR ACME\",\n          \"amount\": 850, \"date\": \"2026-05-09\", \"blocking\": true }\n      ],\n      \"relances\": [\n        { \"invoice_id\": \"F-007\", \"step\": 2, \"days_late\": 48,\n          \"amount\": 450, \"next_action_date\": \"2026-05-25\", \"status\": \"pending\" }\n      ]\n    }\n  ]\n}\n```\n\nCross-month matching is supported (a May payment can settle a January invoice). The invoice stays in its issue-month period with `status: paid` ; the transaction is not reported as orphan in May.\n\n## Statuses\n\n| Status     | Meaning                                                                   |\n| ---------- | ------------------------------------------------------------------------- |\n| `unpaid`   | Not yet due, no payment                                                   |\n| `paid`     | Payment confirmed                                                         |\n| `partial`  | Partial payment — `amount_paid` + `amount_remaining` kept                 |\n| `overdue`  | Due date passed, no payment                                               |\n\n## Anomalies\n\n**Blocking** (period cannot be locked) :\n\n| Type                | Field                          | Condition                                                                                       |\n| ------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------- |\n| `doublon_paiement`  | `period_anomalies`             | same date + amount + label                                                                      |\n| `tva_incorrecte`    | `invoices[].anomalies`         | calculated/declared VAT gap > 5 %                                                                |\n| `facture_manquante` | `unmatched_bank_lines`         | a bank line references an invoice number (`REF`/`FACT`) absent from the folder, amount > 1 000 €|\n| `paiement_orphelin` | `unmatched_bank_lines`         | credit > 1 000 € with no reference and no matching invoice                                       |\n\n**Non-blocking** (signaled, period can still be locked) :\n\n| Type                  | Field                  | Condition                                                                                       |\n| --------------------- | ---------------------- | ----------------------------------------------------------------------------------------------- |\n| `facture_manquante`   | `unmatched_bank_lines` | as above, amount ≤ 1 000 €                                                                      |\n| `paiement_orphelin`   | `unmatched_bank_lines` | credit ≤ 1 000 € with no reference                                                              |\n| `releve_non_parseable`| `period_anomalies`     | no transaction extractable from a statement PDF                                                  |\n| `facture_illisible`   | `period_anomalies`     | neither filename nor PDF content yield a number + amount                                         |\n| `invoice_overdue`     | `invoices[].anomalies` | unpaid past due date                                                                            |\n\n> `facture_manquante` ≠ `paiement_orphelin` : the former is a payment that **cites** an invoice number we never received (client forgot to send it) ; the latter is a credit with no reference at all.\n\n## Relances\n\n| Lateness                | Step          |\n| ----------------------- | ------------- |\n| ≤ 30 days               | 1             |\n| ≤ 60 days               | 2             |\n| ≤ 90 days               | 3             |\n| > 90 days               | `escalation`  |\n\nA `partial` invoice gets a relance with explicit `Solde restant dû : X,XX €`.\n\n## Period closing\n\nA period is locked (`batch.lock.json`) when : no blocking anomaly, file hashes stable for 7 days, no unjustified `unpaid` / `overdue`. Locked periods are never reprocessed unless a file hash changes.\n\n## Prerequisite\n\nThe script calls `pdftotext` (package `poppler-utils`). Install it once on the runtime :\n\n```bash\napt install poppler-utils\n```\n\n## Output files\n\n```\nclients/<slug>/\n├── rapprochement.json            ← single file: invoices + unmatched bank lines + anomalies + relances, grouped by period\n└── <AAAA>/<MM>/batch.lock.json   ← present when the period is closed\n```\n\nPlus a consolidated `compta_batch_report_<YYYY-MM-DD>.json` at the workspace root.\n\n> **Migration note.** Previous versions produced three separate files (`followup.json`, `relances.json`, `anomalies.json`). The current script removes them on each run if they linger.\n\n## Companion skill\n\n[`organisation-documents`](https://github.com/developers-trendex/organisation-documents) — receives, identifies the firm's clients, and classifies documents into the tree this skill consumes.\n\n## Files\n\n| File                              | Purpose                                                                |\n| --------------------------------- | ---------------------------------------------------------------------- |\n| `SKILL.md`                        | Skill definition (French) — the \"when and how to relay\" layer          |\n| `scripts/main.py`                 | Entrypoint — reconciliation, anomalies, relances, report               |\n| `scripts/extract.py`              | Deterministic extractor (only place that touches `pdftotext`)          |\n| `references/matching-rules.md`    | Reference — matching cascade, normalization, edge cases                |\n| `references/structure-cible.md`   | Path / naming contract (duplicated from `organisation-documents`)      |\n\n## License\n\nInternal — OpenClaw private use.\n\nFile v1.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn7ethsmh4zep86d7ew9wsns2x86jwhr\",\n  \"slug\": \"rapprochement-bancaire\",\n  \"version\": \"1.0.0\",\n  \"publishedAt\": 1779352591783\n}\n\nFile v1.0.0:references/csv-schema.md\n\n# Référence — Schéma CSV de rapprochement & état persisté\n\n> Référence chargée à la demande par `rapprochement-bancaire`. Format du CSV, enum `anomalie`, schéma de `rapprochement-state.json`.\n\n---\n\n## Localisation du CSV\n\n```\nclients/<slug>/<AAAA>/<MM>/rapprochement.csv\n```\n\nUn fichier par (client, mois). Réécrit en entier à chaque run du mois concerné (idempotent). Pas d'append.\n\n---\n\n## Encodage et locale\n\n- **Encoding** : UTF-8 avec BOM (`﻿` en tête) — pour Excel français qui sinon mojibake.\n- **Séparateur** : `;` (semicolon) — convention française, évite la confusion avec décimales `,`.\n- **Décimal** : `,` (virgule).\n- **Date** : `AAAA-MM-JJ` (ISO 8601, lisible par tout tableur).\n- **Saut de ligne** : `\\r\\n` (CRLF) — compatibilité Windows / Excel.\n- **Quotes** : `\"` autour des champs qui contiennent `;`, `\"`, ou `\\r\\n`. Doubler les `\"` internes (`\"\"`).\n\n---\n\n## Colonnes du CSV\n\n| #  | Colonne                | Type          | Description                                                                                  |\n| -- | ---------------------- | ------------- | -------------------------------------------------------------------------------------------- |\n| 1  | `ligne`                | int           | Numéro de ligne séquentiel (1, 2, 3, …) — facilite la référence dans les conversations       |\n| 2  | `source`               | enum          | `transaction` (ligne de relevé), `facture_non_payee` (facture sans transaction)              |\n| 3  | `compte`               | string        | Banque ou compte (ex : `BNP-courant`, `Qonto-pro`) — vide si `source=facture_non_payee`      |\n| 4  | `date`                 | date          | Date transaction OU date d'émission de la facture                                            |\n| 5  | `libelle`              | string        | Libellé brut du relevé ou raison sociale + n° facture                                       |\n| 6  | `montant`              | decimal       | Signé (`+` crédit, `-` débit)                                                                |\n| 7  | `categorie_auto`       | enum          | `match` / `frais_bancaires` / `rh_charges` / `transfert_interne` / `non_categorise`         |\n| 8  | `match_facture`        | string        | Numéro de facture matchée — vide si non-match                                                |\n| 9  | `match_emetteur`       | string        | Raison sociale de la facture matchée                                                         |\n| 10 | `match_mois_origine`   | string        | `AAAA-MM` du mois d'origine de la facture si différent du mois courant — vide sinon          |\n| 11 | `confidence`           | enum          | `fort` / `moyen` / `faible` / `aucun`                                                        |\n| 12 | `anomalie`             | enum          | cf. table ci-dessous — vide si aucune                                                        |\n| 13 | `montant_attendu`      | decimal       | Montant TTC de la facture matchée — vide si non-match                                        |\n| 14 | `ecart`                | decimal       | `montant - montant_attendu` — vide si non-match                                              |\n| 15 | `candidats`            | string        | Liste de numéros de facture séparés par `|` si `anomalie=match_ambigu`                       |\n| 16 | `commentaire`          | string        | Champ libre, éditable par le comptable. Préservé entre runs                                  |\n\n---\n\n## Enum `anomalie`\n\n| Valeur                      | Sens                                                                                                |\n| --------------------------- | --------------------------------------------------------------------------------------------------- |\n| (vide)                      | Aucune anomalie                                                                                     |\n| `paiement_orphelin`         | Transaction débit sans facture in correspondante                                                    |\n| `encaissement_sans_facture` | Transaction crédit sans facture out correspondante                                                  |\n| `facture_non_payee`         | Facture out avec échéance dépassée et pas de paiement détecté                                       |\n| `montant_incoherent`        | Match trouvé mais `abs(ecart) > tolerance` (typiquement escompte ou frais)                          |\n| `doublon_paiement`          | Plusieurs transactions matchent la même facture avec somme > `montantTTC`                           |\n| `paiement_partiel`          | Transaction crédit < `montantTTC` de la facture matchée                                             |\n| `match_ambigu`              | Plusieurs candidats avec scores proches (écart < 0.10) — non choisi automatiquement                 |\n| `facture_double_classement` | La facture matchée est référencée dans 2 mois différents de `index.json` — inconsistance à corriger |\n| `libelle_suspect`           | Mots-clés louches (URL raccourcie, IBAN inconnu) — flag manuel, pas bloquant                        |\n\n---\n\n## Tri des lignes\n\n1. `date` croissante.\n2. À date égale, `source=transaction` avant `source=facture_non_payee`.\n3. À source égale, `compte` alphabétique puis `ligne` (ordre du relevé).\n\nPermet une lecture chronologique pour le comptable.\n\n---\n\n## Préservation du `commentaire`\n\nLa colonne `commentaire` est la **seule** qui survit entre les runs. À chaque réécriture :\n\n1. Lire le CSV existant en mémoire.\n2. Indexer les commentaires par `(date, montant, libelle_hash)`.\n3. Après le matching, restaurer les commentaires sur les lignes qui correspondent.\n4. Les lignes nouvelles ont `commentaire` vide.\n\nSi le comptable édite manuellement d'autres colonnes → écrasées au prochain run. Une bannière en tête du fichier le rappelle :\n\n```\n# rapprochement.csv — ACME SA — 2026/04\n# Généré par rapprochement-bancaire — réécrit à chaque run.\n# SEULE la colonne 'commentaire' est préservée entre runs. Autres modifs perdues.\n```\n\n---\n\n## Exemple de CSV\n\n```csv\nligne;source;compte;date;libelle;montant;categorie_auto;match_facture;match_emetteur;match_mois_origine;confidence;anomalie;montant_attendu;ecart;candidats;commentaire\n1;transaction;BNP-courant;2026-04-03;\"VIR FAVEUR TRENDEX TECH F-2026-03-007\";5800,00;match;F-2026-03-007;Trendex Tech;2026-03;fort;;5800,00;0,00;;\n2;transaction;BNP-courant;2026-04-05;\"PRLV ORANGE PRO F-2026-04-1287\";-348,50;match;F-2026-04-1287;Orange Pro;;fort;;-348,50;0,00;;\n3;transaction;BNP-courant;2026-04-12;\"VIR FAVEUR FOO SAS\";1248,00;match;F-2026-03-008;Foo SAS;2026-03;moyen;;1248,00;0,00;;\n4;transaction;BNP-courant;2026-04-15;\"CB SNCF PARIS-LYON\";-84,50;match;;SNCF;;faible;;;;;\n5;transaction;BNP-courant;2026-04-22;\"PRLV URSSAF\";-1870,00;rh_charges;;;;aucun;;;;;\n6;transaction;BNP-courant;2026-04-28;\"CHQ N° 4521 - FOURN MAT\";-720,00;non_categorise;;;;aucun;paiement_orphelin;;;;à investiguer\n7;facture_non_payee;;2026-04-15;\"F-2026-04-013 - ACME Corp\";5800,00;;F-2026-04-013;ACME Corp;;aucun;facture_non_payee;5800,00;;;\n```\n\nFile v1.0.0:references/matching-rules.md\n\n# Référence — Règles de matching transactions ↔ factures\n\n> Référence chargée à la demande par `rapprochement-bancaire`. Détaille la cascade, la fenêtre de date, le scoring de confiance et les cas limites.\n\n---\n\n## Normalisation préalable\n\n### Côté transaction (extraite du relevé)\n\n| Champ                | Normalisation                                                                                                |\n| -------------------- | ------------------------------------------------------------------------------------------------------------ |\n| `montant`            | Décimal, signé (`+` crédit, `-` débit), arrondi 0,01 €                                                       |\n| `date`               | ISO 8601 (`YYYY-MM-DD`)                                                                                       |\n| `libelle`            | Trim, espaces multiples → simple espace, lowercase                                                            |\n| `libelle_tokens`     | `libelle` splitté par espaces / ponctuation, tokens ≥ 3 caractères, sans stop-words bancaires (`vir`, `prlv`, `cb`, `chq`, `faveur`, `de`, `du`, `de la`) |\n\n### Côté facture (extraite de l'index)\n\n| Champ                | Normalisation                                                                              |\n| -------------------- | ------------------------------------------------------------------------------------------ |\n| `montantTTC`         | Décimal positif (le sens vient du `categorie`: `achat` = débit, `vente` = crédit)         |\n| `dateEmission`       | ISO 8601                                                                                   |\n| `emetteur_normalise` | lowercase, accents retirés, suffixes juridiques retirés (`SA`, `SAS`, `SARL`, `EURL`, `Ltd`) |\n| `numeroFacture`      | Conservé tel quel + variante numérique pure (`F-2026-04-1287` → aussi `20260412 87`)      |\n\n---\n\n## Cascade de matching (par transaction)\n\nPour chaque transaction, parcourir les factures du mois (et du mois précédent + suivant — cf. fenêtre date) et calculer un **score** :\n\n```\nscore = match_montant × poids_montant\n      + match_label × poids_label\n      + match_date × poids_date\n```\n\nPoids par défaut :\n\n- `poids_montant` : 0.5\n- `poids_label` : 0.3\n- `poids_date` : 0.2\n\n### Score `match_montant`\n\n| Condition                                                           | Score |\n| ------------------------------------------------------------------- | ----- |\n| `abs(montant - montantTTC) ≤ 0.01`                                  | 1.0   |\n| `abs(montant - montantTTC) / montantTTC < 0.02` (escompte typique)  | 0.6   |\n| Autre                                                               | 0.0   |\n\n**Sens** : `montant > 0` → ne peut matcher qu'une facture `vente`. `montant < 0` → ne peut matcher qu'une facture `achat`. Pas de cross-match.\n\n### Score `match_label`\n\nCascade — première règle qui matche :\n\n| Condition                                                                    | Score |\n| ---------------------------------------------------------------------------- | ----- |\n| `libelle` contient `numeroFacture` (variante numérique pure incluse)         | 1.0   |\n| `libelle_tokens` contient un token de `emetteur_normalise` (longueur ≥ 4)    | 0.8   |\n| `libelle_tokens` contient un préfixe / suffixe de l'émetteur (Levenshtein ≤ 2) | 0.5   |\n| Aucun                                                                        | 0.0   |\n\n### Score `match_date`\n\n| Condition                                            | Score |\n| ---------------------------------------------------- | ----- |\n| `abs(date - dateEmission) ≤ 3 j`                     | 1.0   |\n| `abs(date - dateEmission) ≤ 30 j`                    | 0.7   |\n| `abs(date - dateEmission) ≤ 60 j`                    | 0.3   |\n| Au-delà                                              | 0.0   |\n\n---\n\n## Classification du score final\n\n| Score agrégé | Confiance | Action                                                            |\n| ------------ | --------- | ----------------------------------------------------------------- |\n| ≥ 0.85       | `fort`    | Ligne CSV matchée + update `followup.md` (`payée <date>`)         |\n| 0.65 – 0.85  | `moyen`   | Ligne CSV matchée + update `followup.md` (`payée ? <date>`, avec `?`) |\n| 0.45 – 0.65  | `faible`  | Ligne CSV `match suggéré` mais **pas** d'update followup          |\n| < 0.45       | aucun     | Transaction reste `unmatched`                                     |\n\nSi une transaction a **plusieurs candidats ex-aequo** (écart < 0.10 entre top-2) → marquer `match_ambigu` dans le CSV et ne pas écrire dans followup. Lister les N candidats dans la colonne `candidats`.\n\n---\n\n## Fenêtre de date et débordement de mois\n\nUne facture émise le 28/03 peut être payée le 02/04. Donc :\n\n- Charger les factures des **3 mois** : précédent, courant, suivant, autour du mois de la transaction.\n- Si match avec une facture d'un autre mois → l'écrire dans le CSV du mois de la **transaction**, mais annoter `facture_mois_origine` = mois de la facture.\n- Mettre à jour le `followup.md` du **mois de la facture** (pas celui de la transaction).\n\n---\n\n## Cas limites\n\n### Frais bancaires / commissions\n\nTransaction avec `libelle` contenant un mot-clé d'agios :\n\n- `frais`, `commission`, `cotisation`, `agios`, `interets`, `tenue de compte`\n\n→ Pas de matching tenté. Annoter `categorie_auto: frais_bancaires` dans le CSV. Ne pas alerter (comportement normal).\n\n### Salaires / charges sociales\n\nMots-clés : `salaire`, `urssaf`, `dsn`, `cipav`, `madelin`, `mutuelle`, `prevoyance`.\n\n→ Pas de matching. Annoter `categorie_auto: rh_charges`. Pas d'alerte par défaut, mais flag possible si montant aberrant vs historique (v0.3+).\n\n### Virements internes\n\nMots-clés : `virement interne`, `vir entre comptes`, `transfert`.\n\n→ Pas de matching. Annoter `categorie_auto: transfert_interne`. Pas d'alerte.\n\n### Encaissements partiels\n\nSi `montant < montantTTC` ET `montant > 0` ET match label + date OK :\n\n- Annoter `paiement_partiel` dans le CSV.\n- Calculer pourcentage : `(montant / montantTTC) × 100`.\n- Update `followup.md` : `partielle (X%)`.\n- Au prochain run, chercher les transactions complémentaires pour atteindre 100 %.\n\n### Multi-paiements pour une facture\n\nUne facture peut être payée en plusieurs fois (acompte + solde). Si plusieurs transactions matchent la même facture avec sens cohérent et somme = `montantTTC` → OK, listées comme un groupe dans le CSV avec un identifiant `match_group`.\n\nSi somme > `montantTTC` → anomalie `doublon_paiement`.\n\n### Devise étrangère\n\nHors scope v0.1. Si une facture USD et un encaissement EUR matchent par hasard sur le montant → faux positif. Mitigation v0.2 : taux de change + tolérance plus large.\n\n### Relevés multi-comptes\n\nUn client peut avoir plusieurs comptes (cf. `bank-statements/2026-04_BNP-courant.pdf` et `bank-statements/2026-04_Qonto-pro.pdf`). Toutes les transactions de tous les comptes du mois sont mises en pool commun pour le matching. Le CSV indique la source dans une colonne `compte`.\n\n### Reclassement d'une facture après matching\n\nSi une facture est reclassée par `organisation-documents` (mauvais client, mauvaise catégorie) APRÈS un matching :\n\n- Le `rapprochement-state.json` détecte le changement de chemin et marque le mois `dirty: true`.\n- Au prochain run, le matching est recalculé pour ce mois → le CSV est régénéré, `followup.md` mis à jour.\n- L'ancien match est purgé de l'état précédent.\n\n---\n\n## Anti-faux-positifs\n\nSources connues de faux positifs et garde-fous :\n\n| Source                                                          | Garde-fou                                                                                  |\n| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |\n| 2 fournisseurs facturent le même montant rond (`100,00 €`)      | Required : label OU date proche, jamais montant seul                                       |\n| Abonnement mensuel identique → faux match sur le mauvais mois   | Fenêtre date prioritaire si plusieurs candidats même montant                               |\n| Émetteur générique (`Amazon`) qui matche partout                | Liste noire de tokens trop génériques : `paiement`, `achat`, `commande`, `service`, `web` |\n| Transaction de l'année précédente reposté (relevé re-généré)    | Si date < `mois - 90 j` → ignorée                                                          |\n\n**Règle absolue** : un match `fort` ne doit jamais écraser une donnée déjà éditée par le comptable dans `followup.md` (statut paiement, prochaine relance). En cas de conflit, garder la valeur humaine et lister le conflit dans le CSV avec `anomalie=libelle_suspect` + commentaire automatique « override manuel respecté ».\n\nFile v1.0.0:references/structure-cible.md\n\n# Référence — Structure cible & conventions de nommage\n\n> Référence chargée à la demande par `organisation-documents`. Spec exhaustive du classement.\n> Source de vérité pour les autres skills qui écrivent dans le même arbre (`relances`, `facturation`, `rapprochement-bancaire`).\n\n---\n\n## Arborescence complète\n\n```\n~/.openclaw/workspace/\n├── clients/\n│   ├── <slug>/                                # un dossier = un client réel (existant ou auto-créé)\n│   │   ├── contrats/                          # niveau client — contrats pluriannuels\n│   │   │   └── <AAAA-MM-JJ>_<Type>_<Contrepartie>.<ext>\n│   │   ├── <AAAA>/\n│   │   │   └── <MM>/\n│   │   │       ├── bank-statements/           # toujours un dossier (multi-comptes possibles)\n│   │   │       │   └── <AAAA-MM>_<BanqueOuCompte>.<ext>\n│   │   │       ├── invoices/\n│   │   │       │   ├── in/                    # factures reçues (achats)\n│   │   │       │   │   └── <AAAA-MM-JJ>_<Émetteur>_<MontantTTC>.<ext>\n│   │   │       │   └── out/                   # factures émises (ventes)\n│   │   │       │       └── <AAAA-MM-JJ>_<Destinataire>_<MontantTTC>.<ext>\n│   │   │       ├── notes-de-frais/\n│   │   │       │   └── <AAAA-MM-JJ>_<Collaborateur>_<Émetteur>_<MontantTTC>.<ext>\n│   │   │       ├── autres/\n│   │   │       │   └── <AAAA-MM-JJ>_<Description>.<ext>\n│   │   │       ├── relances.md                # maintenu par skill `relances`\n│   │   │       └── followup.md                # maintenu par skill `facturation`\n│   │   ├── _archive-suppression/              # RGPD soft-delete (grâce 30 j)\n│   │   ├── index.json                         # index par client\n│   │   └── audit.log                          # déplacements / renommages\n│   └── index-global.json\n├── .pending-attribution/                      # parking technique HORS clients/\n│   ├── <AAAA-MM-JJ-réception>_<hash-c\n\nArchive v0.2.0: 8 files, 27310 bytes\n\nFiles: README.md (7040b), references/csv-schema.md (7202b), references/matching-rules.md (9018b), references/structure-cible.md (14917b), scripts/extract.py (12883b), scripts/main.py (16432b), SKILL.md (7893b), _meta.json (141b)\n\nArchive v0.1.0: 6 files, 15877 bytes\n\nFiles: README.md (3454b), references/csv-schema.md (7202b), references/matching-rules.md (9018b), references/structure-cible.md (14917b), SKILL.md (5659b), _meta.json (141b)","readmeExcerpt":"Skill: Rapprochement Bancaire Owner: trendex Summary: Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valid... Tags: latest:1.0.2 Version history: v1.0.2 | 2026-06-03T16:16:30.543Z | user Version 1.0.2 - Nettoyage majeur : suppression de fichiers inutilisés (scripts annexes, fichier méta, skill-card). - Ajout d’un ","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"python3 scripts/main.py <racine_clients>"},{"language":"bash","snippet":"python3 scripts/main.py [<racine_clients>]\n# racine_clients par défaut : ~/.openclaw/workspace/clients"},{"language":"text","snippet":"organisation-documents  →  classe les pièces, déduit clients.json\nrapprochement-bancaire            →  rapproche, valide, reconstruit l'état comptable  (scripts/main.py)\nrelances                →  décision différée"},{"language":"bash","snippet":"python3 scripts/main.py <clients_root>"},{"language":"bash","snippet":"apt install poppler-utils"},{"language":"text","snippet":"clients/<slug>/\n├── followup.json         ← all invoices with their reconciliation status\n├── relances.json         ← invoices needing follow-up, with step + suggested next contact date\n├── anomalies.json        ← all anomalies for this client (blocking flag included)\n└── <AAAA>/<MM>/batch.lock.json   ← present when the period is closed"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: rapprochement-bancaire\ndescription: Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valide la TVA, maintient followup.json / relances.json / anomalies.json. Le travail réel est fait par scripts/main.py (qui appelle scripts/extract.py). Ne traite jamais les e-mails, ne classe jamais de documents.\nlicense: Interne — usage privé OpenClaw\n---\n\n# Skill `rapprochement-bancaire`\n\n> Moteur d'état. Travaille uniquement sur l'arborescence `clients/<slug>/...` produite par `organisation-documents`.\n> **Le travail réel est fait par `scripts/main.py`** (qui appelle `scripts/extract.py` pour toute l'extraction de texte). Ce skill = quand le lancer + comment rendre compte.\n\n---\n\n## ⚠️ Règle absolue — EXÉCUTION DU SCRIPT (non négociable)\n\n**Pour chaque invocation, la SEULE action correcte est d'exécuter la commande :**\n\n```bash\npython3 scripts/main.py <racine_clients>\n```\n\npuis de lire les `followup.json` / `relances.json` / `anomalies.json` par client + le `compta_batch_report_<date>.json` consolidé, et de relayer au comptable.\n\n### ❌ INTERDIT\n\n- **Réimplémenter le rapprochement en Python inline / pseudo-code.** La logique (Pass 1 par référence, Pass 2 fuzzy, validation TVA, détection des anomalies, statuts overdue/partial, génération des relances) est dans `scripts/main.py`. La refaire à la main est garanti de diverger.\n- **Écrire `followup.json` / `relances.json` / `anomalies.json` à la main.** C'est le script qui le fait, dans son format exact (liste d'objets avec les champs `invoice_id`, `type`, `amount`, `status`, `bank_matched`, `matched_tx`, …). Toute autre forme (dict keyé, champs `payments` / `amount_paid` ad hoc) casse les lectures ultérieures.\n- **Modifier le format des fichiers de sortie.** Le format est contractuel entre ce skill et d'éventuels skills aval (`relances`, dashboards).\n\n### ✅ OBLIGATOIRE\n\n1. Exécuter la commande shell ci-dessus.\n2. Lire les fichiers produits.\n3. Relayer au comptable un tableau lisible par client (factures payées / en attente / partielles / overdue, anomalies — en mettant en avant les **bloquantes**).\n\n### Si le script échoue\n\nSignale l'erreur exacte (stderr). Ne rejoue PAS le batch à la main.\n\n> Pourquoi : on a déjà eu un run où l'agent a produit un `followup.json` au mauvais format (dict avec clés `\"in/N\"`, champs `payments`/`amount_paid`/`amount_remaining` au lieu de `bank_matched`/`status`). Conséquence : aucun outil ni skill aval ne pouvait l'exploiter. Le script génère le bon format à tous les coups.\n\n---\n\n## Exécution\n\n```bash\npython3 scripts/main.py [<racine_clients>]\n# racine_clients par défaut : ~/.openclaw/workspace/clients\n```\n\nÀ lancer :\n- une fois par jour (idéalement la nuit), sur tous les clients ;\n- ou immédiatement après un trigger `rapprochement-bancaire` émis par `organisation-documents`.\n\nLe script :\n\n1. **Parcourt** `clients/*` en ignorant les dossiers techniques `_*` (`_a-identifier`, "},{"path":"README.md","content":"# rapprochement-bancaire\n\n> Claude skill for **French accounting firms** (`cabinets comptables`). Reads the `clients/<slug>/...` tree produced by [`organisation-documents`](https://github.com/developers-trendex/organisation-documents), reconciles each invoice with its bank-statement transaction, validates VAT, and writes one `followup.json` / `relances.json` / `anomalies.json` per client plus a consolidated report. Deterministic script-driven.\n\n## What it does\n\nFor each invocation, the skill runs **one command** :\n\n```bash\npython3 scripts/main.py <clients_root>\n```\n\nThat script:\n\n1. Walks every client folder under `<clients_root>` (skipping `_a-identifier/`, `_incomplet/`, `_non-attribue/`, `_cabinet/`).\n2. For each active period (current + previous month, plus any older month without `batch.lock.json`) :\n   - Reads invoices from `invoices/in/` and `invoices/out/`.\n   - Reads transactions from `bank-statements/` via `scripts/extract.py` (one line per transaction, with the `REF`/`FACT <id>` reference captured when present).\n3. Reconciles in two passes (amount compared in absolute value — an `out` invoice is settled by a credit, an `in` by a debit) :\n   - **Pass 1** — direct match by invoice reference (`REF` / `FACT` in the bank label). Exact amount (±1 €) → `paid` ; lower amount → `partial` with `amount_paid` and `amount_remaining` ; higher → `paid` with `overpaid_by`.\n   - **Pass 2** — fuzzy : `|amount| ±1 €` **and** counterparty-name similarity ≥ 0.6.\n   - No match + due date passed → `overdue`.\n4. Validates VAT on each invoice : if `|VAT declared − TOTAL HT × rate| / expected > 5 %`, flags a blocking `tva_incorrecte` anomaly (exempted-VAT invoices are silently ignored).\n5. Detects other anomalies (see below).\n6. Generates relances based on lateness (`overdue`, `partial`, `unpaid` past due).\n7. Writes per-client JSON files + `compta_batch_report_<date>.json` consolidated.\n\nThe agent only **reads the JSON files and relays the summary to the accountant** — payments, late invoices, anomalies (highlighting blocking ones), no technical paths.\n\n## Statuses (`followup.json`)\n\n| Status     | Meaning                                                                   |\n| ---------- | ------------------------------------------------------------------------- |\n| `unpaid`   | Not yet due, no payment                                                   |\n| `paid`     | Payment confirmed                                                         |\n| `partial`  | Partial payment — `amount_paid` + `amount_remaining` kept                 |\n| `overdue`  | Due date passed, no payment                                               |\n\n## Anomalies (`anomalies.json`)\n\n**Blocking** (period cannot be locked) :\n\n| Type                          | Condition                                                                                       |\n| ----------------------------- | ----------------------------------------------------------------------------------------------- |\n| `doub"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7ethsmh4zep86d7ew9wsns2x86jwhr\",\n  \"slug\": \"rapprochement-bancaire\",\n  \"version\": \"1.0.2\",\n  \"publishedAt\": 1780503390543\n}"},{"path":"references/csv-schema.md","content":"# Référence — Schéma CSV de rapprochement & état persisté\n\n> Référence chargée à la demande par `rapprochement-bancaire`. Format du CSV, enum `anomalie`, schéma de `rapprochement-state.json`.\n\n---\n\n## Localisation du CSV\n\n```\nclients/<slug>/<AAAA>/<MM>/rapprochement.csv\n```\n\nUn fichier par (client, mois). Réécrit en entier à chaque run du mois concerné (idempotent). Pas d'append.\n\n---\n\n## Encodage et locale\n\n- **Encoding** : UTF-8 avec BOM (`﻿` en tête) — pour Excel français qui sinon mojibake.\n- **Séparateur** : `;` (semicolon) — convention française, évite la confusion avec décimales `,`.\n- **Décimal** : `,` (virgule).\n- **Date** : `AAAA-MM-JJ` (ISO 8601, lisible par tout tableur).\n- **Saut de ligne** : `\\r\\n` (CRLF) — compatibilité Windows / Excel.\n- **Quotes** : `\"` autour des champs qui contiennent `;`, `\"`, ou `\\r\\n`. Doubler les `\"` internes (`\"\"`).\n\n---\n\n## Colonnes du CSV\n\n| #  | Colonne                | Type          | Description                                                                                  |\n| -- | ---------------------- | ------------- | -------------------------------------------------------------------------------------------- |\n| 1  | `ligne`                | int           | Numéro de ligne séquentiel (1, 2, 3, …) — facilite la référence dans les conversations       |\n| 2  | `source`               | enum          | `transaction` (ligne de relevé), `facture_non_payee` (facture sans transaction)              |\n| 3  | `compte`               | string        | Banque ou compte (ex : `BNP-courant`, `Qonto-pro`) — vide si `source=facture_non_payee`      |\n| 4  | `date`                 | date          | Date transaction OU date d'émission de la facture                                            |\n| 5  | `libelle`              | string        | Libellé brut du relevé ou raison sociale + n° facture                                       |\n| 6  | `montant`              | decimal       | Signé (`+` crédit, `-` débit)                                                                |\n| 7  | `categorie_auto`       | enum          | `match` / `frais_bancaires` / `rh_charges` / `transfert_interne` / `non_categorise`         |\n| 8  | `match_facture`        | string        | Numéro de facture matchée — vide si non-match                                                |\n| 9  | `match_emetteur`       | string        | Raison sociale de la facture matchée                                                         |\n| 10 | `match_mois_origine`   | string        | `AAAA-MM` du mois d'origine de la facture si différent du mois courant — vide sinon          |\n| 11 | `confidence`           | enum          | `fort` / `moyen` / `faible` / `aucun`                                                        |\n| 12 | `anomalie`             | enum          | cf. table ci-dessous — vide si aucune                                                        |\n| 13 | `montant_attendu`      | decimal       | Montant TTC de la facture matchée — vide si non-match    "},{"path":"references/matching-rules.md","content":"# Référence — Règles de matching transactions ↔ factures\n\n> Référence chargée à la demande par `rapprochement-bancaire`. Détaille la cascade, la fenêtre de date, le scoring de confiance et les cas limites.\n\n---\n\n## Normalisation préalable\n\n### Côté transaction (extraite du relevé)\n\n| Champ                | Normalisation                                                                                                |\n| -------------------- | ------------------------------------------------------------------------------------------------------------ |\n| `montant`            | Décimal, signé (`+` crédit, `-` débit), arrondi 0,01 €                                                       |\n| `date`               | ISO 8601 (`YYYY-MM-DD`)                                                                                       |\n| `libelle`            | Trim, espaces multiples → simple espace, lowercase                                                            |\n| `libelle_tokens`     | `libelle` splitté par espaces / ponctuation, tokens ≥ 3 caractères, sans stop-words bancaires (`vir`, `prlv`, `cb`, `chq`, `faveur`, `de`, `du`, `de la`) |\n\n### Côté facture (extraite de l'index)\n\n| Champ                | Normalisation                                                                              |\n| -------------------- | ------------------------------------------------------------------------------------------ |\n| `montantTTC`         | Décimal positif (le sens vient du `categorie`: `achat` = débit, `vente` = crédit)         |\n| `dateEmission`       | ISO 8601                                                                                   |\n| `emetteur_normalise` | lowercase, accents retirés, suffixes juridiques retirés (`SA`, `SAS`, `SARL`, `EURL`, `Ltd`) |\n| `numeroFacture`      | Conservé tel quel + variante numérique pure (`F-2026-04-1287` → aussi `20260412 87`)      |\n\n---\n\n## Cascade de matching (par transaction)\n\nPour chaque transaction, parcourir les factures du mois (et du mois précédent + suivant — cf. fenêtre date) et calculer un **score** :\n\n```\nscore = match_montant × poids_montant\n      + match_label × poids_label\n      + match_date × poids_date\n```\n\nPoids par défaut :\n\n- `poids_montant` : 0.5\n- `poids_label` : 0.3\n- `poids_date` : 0.2\n\n### Score `match_montant`\n\n| Condition                                                           | Score |\n| ------------------------------------------------------------------- | ----- |\n| `abs(montant - montantTTC) ≤ 0.01`                                  | 1.0   |\n| `abs(montant - montantTTC) / montantTTC < 0.02` (escompte typique)  | 0.6   |\n| Autre                                                               | 0.0   |\n\n**Sens** : `montant > 0` → ne peut matcher qu'une facture `vente`. `montant < 0` → ne peut matcher qu'une facture `achat`. Pas de cross-match.\n\n### Score `match_label`\n\nCascade — première règle qui matche :\n\n| Condition                                                                    | Sco"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valid... Skill: Rapprochement Bancaire Owner: trendex Summary: Moteur comptable quotidien du cabinet. Analyse les dossiers clients déjà classés par organisation-documents, rapproche les paiements avec les factures, valid... Tags: latest:1.0.2 Version history: v1.0.2 | 2026-06-03T16:16:30.543Z | user Version 1.0.2 - Nettoyage majeur : suppression de fichiers inutilisés (scripts annexes, fichier méta, skill-card). - Ajout d’un","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1741,"uniquenessScore":54,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T21:02:54.938Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T21:02:54.938Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:40:21.417Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}