Skip to main content

Reçu

J'ai conçu Reçu pour transformer les SMS de confirmation mobile money en transactions structurées, en restant robuste aux variations de format et sans qu'aucune donnée ne quitte la machine.

Projet 5 du défi mensuel (bibliothèque Python, finances personnelles). État : MVP validé sur un corpus synthétique ET sur 1 294 vrais SMS de l'auteur (mesure locale, aucun message publié). Bibliothèque standard seulement, aucune dépendance, aucun accès réseau. Distribution PyPI : recu-momo (le nom recu est déjà pris sur PyPI) ; on l'importe toujours par import recu.

Le principe : au lieu d'écrire une expression régulière par format de SMS, Reçu apprend les gabarits à partir de messages bruts (induction), les écrit dans un fichier JSON qu'un humain relit, puis s'en sert pour extraire le type, le montant, les frais, le solde, la contrepartie, la référence et la date. Un message qu'il ne comprend pas reste None : il ne devine jamais.

from recu import Parser

p = Parser.learn_from(messages_bruts)          # induction de gabarits ; p.save("gabarits.json") pour les relire
tx = p.parse("Vous avez reçu 5.000 FCFA de JEAN DUPONT (22997123456). Solde: 12 500 F. Frais: 0 F. Ref: MP240101.1234.A56789")
tx.kind, tx.amount, tx.currency, tx.fee, tx.balance   # ('reception', Decimal('5000'), 'XOF', Decimal('0'), Decimal('12500'))
p.parse("Votre code est 4821")                  # None : inconnu, jamais une valeur devinée
python -m recu learn sms.xml --sender momo -o gabarits.json     # apprend et affiche chaque gabarit à relire
python -m recu parse sms.xml --sender momo --templates gabarits.json --format csv > transactions.csv

Ce qui est mesuré sur de VRAIS SMS (les miens, en local : docs/mesures.md, section 5)

1 294 SMS reçus d'opérateurs mobile money (MTN MoMo, Moov Money, Celtiis, y compris leurs publicités, codes de connexion et alertes de forfait). Apprentissage sur les 70 % les plus anciens, test sur les 30 % les plus récents. Pas d'étiquettes : on ne peut pas mesurer directement « le montant est exact ». On mesure donc un invariant indépendant de l'analyseur : la continuité des soldes (nouveau solde = ancien solde ± montant ± frais).

Mesure Résultat
Continuité des soldes, paires sans message de transaction inconnu entre elles 94,2 % (180 sur 191) ; paiement 103/103, envoi 23/23, réception 27/28, retrait 18/24, dépôt 9/12
Part de la boîte reconnue comme transaction 35,6 % de l'ensemble ; 25,2 % des messages récents (le reste : publicités, codes de connexion, alertes de consommation, volontairement inconnus)
Formats MTN MoMo reconnus (d'après les gabarits appris) paiement, transfert envoyé, transfert reçu, retrait via agent, dépôt reçu ; l'activation de forfait est reconnue aussi, mais sa justesse n'est pas vérifiée (aucun solde pour la chaîner)

Ce que ces chiffres ne disent pas : la vraie couverture des transactions (sans étiquettes, je ne sais pas combien de vraies transactions sont restées inconnues) ; ni pourquoi 6 retraits sur 24 et 3 dépôts sur 12 ne font pas la chaîne (règle de frais différente, message manquant d'un autre opérateur, ou lecture fausse : à examiner à la main, un par un). Une seule personne, un seul téléphone, un seul pays.

Ce que la vraie mesure a trouvé, que le corpus synthétique ne pouvait pas montrer (tous corrigés, avec un test chacun) :

  1. « TRANSFERT 5 000 F DE nom » classé envoi alors que c'est une réception : le mot « transfert » ne dit pas le sens, la préposition le dit. La continuité par type l'a révélé (envoi 36/90, réception 0/12 avant correction).
  2. Des publicités reconnues comme transactions : un « retrait » dont le « montant » était un code USSD (*855*5#), des « recharges » issues de mots trop larges (« forfait », « internet »). Désormais : un montant doit être collé à une devise, un code USSD est un mot, et une transaction doit porter au moins une information en plus du montant (solde, référence, contrepartie ou date) ; les frais seuls ne comptent pas.
  3. Le prix d'un forfait pris pour son volume (« forfait 500 Mo activé à 1 000 FCFA ») : le montant est le nombre collé à la devise.

Ce qui est mesuré sur le corpus SYNTHÉTIQUE (docs/mesures.md, sections 1 à 4)

Mesure (1 500 messages d'apprentissage, 1 500 de test jamais vus) Résultat
Couverture des transactions des formats appris 100 % (38 gabarits appris pour 14 formats cachés)
Montants faux / types faux 0 / 0 sur 1 337 reconnus
Non-transactions (codes, publicités) reconnues à tort 0 sur 163
Deux formats retenus à l'apprentissage, présentés au test 0 reconnu à tort sur 600
Exemples nécessaires pour apprendre 14 → 21 % · 56 → 77 % · 112 → 90 % · 300 → 98,7 % · 1 500 → 100 % de couverture
Débit d'analyse environ 4 600 messages/s (médiane de 5 passes, la machine varie du simple au triple) ; apprentissage de 1 500 messages : environ 8 s

Comparaison avec des expressions régulières écrites à la main (14 regex, une par format) : une version « soignée » atteint la même couverture, 100 %, et la même exactitude, avec un débit 4,3 fois plus élevé (environ 20 000 messages/s). Reçu ne bat donc pas des regex bien écrites en précision ni en vitesse. Son avantage mesuré est l'effort humain : il a trouvé seul les gabarits (il en faut une centaine d'exemples pour dépasser 90 %), alors que les regex demandent à un développeur d'écrire du code pour chaque nouveau format. Une version « naïve » (chiffres seuls, casse exacte) ne couvre que 0,3 % des messages bruités : c'est un homme de paille, pas une référence.

Limites (à lire avant de faire confiance)

  • Le corpus synthétique est écrit par moi et circulaire : il prouve la tenue à des variations connues, pas à de vrais SMS. Ses formats sont inspirés de structures publiées (Ghana, M-Pesa), pas copiés. Aucun jeu public de vrais SMS de confirmation n'existe, à ma connaissance (cherché).
  • Sur les vrais SMS, la couverture réelle des transactions est inconnue (pas d'étiquettes) ; seule la cohérence des soldes est mesurée.
  • Pas de gabarit appris pour : les confirmations d'achat de forfait au format « forfait N Mo activé à… », les transactions d'opérateurs absents de ma boîte (Orange Money, Wave, Celtiis en détail). Un format absent des exemples reste inconnu, jamais deviné.
  • Non publié sur PyPI (voir docs/PUBLIER-SUR-PYPI.md) ; langues : français et anglais ; fuseaux horaires non gérés ; SMS longs découpés en plusieurs messages non recollés ; un gabarit à un seul exemple est écarté par défaut (min_support = 2).
  • L'index de reconnaissance est un index inversé sur le premier mot fixe, pas le trie de préfixes du catalogue. La correspondance finale utilise le moteur d'expressions régulières de Python, sans garantie de temps linéaire dans le pire cas.

Comment ça marche (docs/conception.md)

  1. Montants (amounts.py) : « 1.000 FCFA », « 1 000F », « 1,000.00 XOF ». Un point ou une virgule seuls sont ambigus ; la règle (décimale, milliers, ou refus) est écrite et testée, Decimal partout.
  2. Tokenisation typée : nombre, date, heure, identifiant, mot (les codes USSD en sont un), ponctuation.
  3. Regroupement par distance d'édition pondérée : changer un nom propre coûte peu, changer un verbe coûte cher, un texte ajouté en fin de message est presque gratuit.
  4. Alignement sur le message représentatif de chaque groupe : identique devient fixe, variable devient case typée ou texte libre.
  5. Rôles et type déduits du vocabulaire ; sans mot reconnu, le gabarit reste « type indéterminé » et ses messages restent inconnus. Règles de phrase : « X vous a envoyé » et « TRANSFERT … DE » sont des réceptions.

Sécurité et confidentialité

Aucun accès réseau. Lecture XML en flux, entités externes non résolues (testé). --sender filtre avant tout traitement. Le CSV neutralise l'injection de formules (un nom commençant par =, +, - ou @ reçoit une apostrophe), car le nom vient du SMS, donc d'un tiers. Aucun SMS réel dans ce dépôt : le corpus est généré, les exports réels vont dans un dossier hors dépôt, l'outil d'export refuse d'écrire dans un dépôt Git et n'affiche jamais un message, et seuls des chiffres agrégés sont publiés.

Tests et outils

pip install -e .[dev]
python -m pytest                      # 102 tests, dont des tests de propriété (Hypothesis)
python tools/evaluate.py --n 1500 --out docs/mesures-synthetique.json     # corpus synthétique, regex de référence, courbe d'apprentissage
python tools/export_sms_adb.py --sender MoMo --out <hors-dépôt>/real.xml  # téléphone branché ; aucun message affiché
python tools/eval_real.py <hors-dépôt>/real.xml --sender momo             # mesure sur vrais SMS, en local (agrégats seulement)

Licence

MIT, Copyright (c) 2026 Gilles MEDENOU : voir LICENSE.

Metadata

Release files for recu-momo 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for recu-momo 0.1.0
File Size Uploaded Provenance
recu_momo-0.1.0.tar.gz 37.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for recu-momo 0.1.0
File Interpreter ABI Platform
recu_momo-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 64.6 kB

Release files / recu_momo-0.1.0.tar.gz

Download URL recu_momo-0.1.0.tar.gz
Size 37.0 kB
Tags Source
SHA-256 checksum
How to use checksums
3a76e63e253acdb1810e83c7a68c8a4fee351cb34f8273713c25495261f64e8f
BLAKE2b-256 checksum
How to use checksums
a317d841afcab9836a919814525e5acd4bf2f8d70a0d631dbed8ff42efeb1f24
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / recu_momo-0.1.0-py3-none-any.whl

Download URL recu_momo-0.1.0-py3-none-any.whl
Size 27.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
722e899c9edb8c2dee4bb5a81d4cfaafe391e6a48f7f709a9357ab3cc67e7e6f
BLAKE2b-256 checksum
How to use checksums
37a25a6ea611a5b0c6e4a54500442d2b8cf0d8ef44d7027d5d50264ddc32edc2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for recu-momo 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page