lumnik. / scenarios All scenarios · Français

A CFONB statement with the sign hidden in the last digit

Goal Read the CFONB 120 bank statement as a table: every movement signed, tied to its statement, with its payer and invoice.

French banks still send statements in CFONB 120.

Treasury wants each movement signed, with its complements.

RELEVE_00098765432_20261017.txt
0112345    00001EUR2 00098765432  151026  151026                                          0000000012500{                
0412345    00001EUR2 0009876543205161026  161026PRLV SEPA ENERGIE DU SUD         0000101  0000000000845}ECH-2026-10     
0512345    00001EUR2 0009876543205161026     LIBECHEANCE OCTOBRE CONTRAT 4471                                           
0412345    00001EUR2 0009876543205161026  161026VIR SEPA RECU                    0000102  0000000012000{F-2026-118      
0512345    00001EUR2 0009876543205161026     NPYMARTIN SARL                                                             
0512345    00001EUR2 0009876543205161026     RCNFACTURE F-2026-118                                                      
0412345    00001EUR2 0009876543205161026  161026CARTE 1510 STATION LYON          0000103  0000000000362}                
0712345    00001EUR2 00098765432  161026  161026                                          0000000023293{                
0112345    00001EUR2 00098765432  161026  161026                                          0000000023293{                
0412345    00001EUR2 0009876543205171026  171026FRAIS TENUE DE COMPTE            0000104  0000000000100}                
0712345    00001EUR2 00098765432  171026  171026                                          0000000023193{                
manifests/20-statement-line.yaml
kind: Entityname: statement_line_blabel: Ligne de relevédescription: "One record of a CFONB 120 bank statement: its code, its statement, the movement it belongs to, its amount."match: [line]scopes: [treasury-b]columns:  line: { label: "Enregistrement" }  record: { label: "Code" }  statement_date: { label: "Relevé du" }  account: { label: "Compte" }  movement: { label: "Mouvement" }  op_date: { label: "Date d'opération" }  label: { label: "Libellé" }  qualifier: { label: "Qualifiant" }  info: { label: "Complément" }  amount: { label: "Montant" }sources:  - table: t_scenario_cfonb_b    map: { line: line, record: record, statement_date: statement_date, account: account, movement: movement, op_date: op_date,           label: label, qualifier: qualifier, info: info, amount: amount }
$ lm entity get statement_line_b --filter movement=0000102
LINE                                                                                                                      RECORD  STATEMENT_DATE  ACCOUNT      MOVEMENT  OP_DATE     LABEL          QUALIFIER  INFO                AMOUNT  _CONFLICTS
0412345    00001EUR2 0009876543205161026  161026VIR SEPA RECU                    0000102  0000000012000{F-2026-118        04      2026-10-15      00098765432  0000102   2026-10-16  VIR SEPA RECU                                 1200    {}
0512345    00001EUR2 0009876543205161026     NPYMARTIN SARL                                                               05      2026-10-15      00098765432  0000102                              NPY        MARTIN SARL                 {}
0512345    00001EUR2 0009876543205161026     RCNFACTURE F-2026-118                                                        05      2026-10-15      00098765432  0000102                              RCN        FACTURE F-2026-118          {}

Limits

  • The sign is decoded by an operator's Kotlin script: no shipped hook reads an amount with an embedded sign.
  • A movement's complements are a filtered read (--filter movement=…); nothing folds them onto one row.
  • Synthetic file, fictitious bank, account and payers.

Treasury · bank statement

Who paid this transfer?

The CFONB statement says so, in 120 characters.

No delimiter, the sign hidden in the last digit.

Synthetic file, fictitious bank and payers

The problem

The statement is not a table.

  • 0000000000845}: −84.50.
  • The payer is on the next line.
  • Nothing ties that line to the transfer.

Nothing is changed

The bank's file stays as is.

  • lumnik cuts each record.
  • It signs each amount.
  • It ties payer and invoice to the transfer.

What it took to write

- fill-down:
    columns: [movement]
    reset-when: { column: record,
                  in: ["01", "07"] }

The complement meets its movement.

The whole scenario fits in 3 files, 69 lines, including a 14-line script.

The answers

Every movement signed, the balances right.

  • 1,250.00 − 84.50 + 1,200.00 − 36.20 = 2,329.30.
  • 0000102: MARTIN SARL, invoice F-2026-118.

The limit

The sign takes a script.

  • No shipped rule reads an embedded sign.
  • Complements stay rows, read by filter.

Try it

The scenario replays end to end.

One connector, one script, one entity: each step is published with its output.

This scenario runs on lumnik Open, free and open source.

Try lumnik Open →