Metadata-Version: 2.4
Name: recu-momo
Version: 0.1.0
Summary: Transforme des SMS de confirmation mobile money en transactions structurées, sans qu'aucune donnée ne quitte la machine.
Author: Gilles MEDENOU
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE.md
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: hypothesis>=6; extra == "dev"
Dynamic: license-file

# 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.**

```python
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
```

```bash
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](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](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](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](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

```bash
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](LICENSE).
