Chargement...
Chargement...
TallyComment facturer 50 millions d'événements/mois par client sans perdre 18 % de marge en frais Stripe et sans envoyer la mauvaise facture à 200 entreprises ?
L'éditeur (~80 clients enterprise, prix usage-based à 4 dimensions) avait construit sa facturation en 2022 sur un script Python + Stripe SDK qui pushait chaque événement comme InvoiceItem. À 50M+ d'événements/mois, les frais Stripe par opération bouffaient toute la marge sur les petits comptes. Et les gros clients voyaient des écarts de facturation parce que les retries de leur publisher dédupliquaient mal. Tally remplace cette couche par un service .NET 10 : ingestion bulk-friendly (bulk endpoint accepte 1000 events par appel), idempotence garantie par index unique Postgres sur (tenant, meter, idempotency_key), agrégation à la fin du cycle (pas en streaming), pricing par paliers gradués (pas par palier global), et pre-bill quotidien envoyé au portail client pour qu'aucune facture mensuelle ne soit jamais une surprise. La détection d'anomalies (z-score glissant sur 14 jours) flag les pics avant qu'ils deviennent un litige. Le repo public est une reconstruction from scratch — pas de code client, mais l'architecture, les invariants domaine, le pipeline anomalie, et l'invariant cité-ou-rejeté sur le rate card sont exactement ceux livrés en prod.
Le client perdait 18 % de marge brute en frais Stripe sur les petits comptes parce que leur métering envoyait une requête par événement. J'ai voulu prouver qu'une refonte propre — idempotence par construction, agrégation par cycle, pricing gradué — était livrable en 6 semaines.
5 projets : Tally.Domain (pur C#) → Application (use cases + ports) → Infrastructure (EF Core + ML.NET + Stripe.net). Tally.Api expose minimal APIs JSON + Scalar UI. Tally.Cli boote la démo offline. Idempotence par index PG unique. RateCard stocké en JSON dans la colonne meter (owned navigation EF Core 9). Anomaly detector pluggable via IAnomalyDetector — l'implémentation statistique est le défaut, l'SR-CNN reste discutée dans le README.
tests verts (Domain invariants, handlers, anomaly, API smoke)
démo offline complète : `dotnet run --project src/Tally.Cli -- demo`
des ingests soit acceptés, soit explicitement marqués duplicate (zéro double-billing)
scope mission : domaine + facturation + anomalie + portail interne
Trois leçons : 1) L'idempotence DOIT être au niveau base (unique index), jamais dans le code applicatif. 2) Pour 95 % des cas d'anomalie sur séries temporelles métering, un z-score glissant bat largement ML.NET en transparence et en débuggabilité. 3) Owned navigations EF Core 9 + record positional constructors ne se mélangent pas — il faut basculer en classes sealed avec init properties.