cedikit 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. cedikit-1.0.0/.gitattributes +3 -0
  2. cedikit-1.0.0/.github/workflows/ci.yml +87 -0
  3. cedikit-1.0.0/.gitignore +16 -0
  4. cedikit-1.0.0/.readthedocs.yaml +19 -0
  5. cedikit-1.0.0/CHANGELOG.md +54 -0
  6. cedikit-1.0.0/CONTRIBUTING.md +48 -0
  7. cedikit-1.0.0/LICENSE +21 -0
  8. cedikit-1.0.0/PKG-INFO +175 -0
  9. cedikit-1.0.0/README.md +103 -0
  10. cedikit-1.0.0/docs/cli.md +25 -0
  11. cedikit-1.0.0/docs/contributing-templates.md +78 -0
  12. cedikit-1.0.0/docs/index.md +38 -0
  13. cedikit-1.0.0/docs/modules/evaluation.md +5 -0
  14. cedikit-1.0.0/docs/modules/fees.md +5 -0
  15. cedikit-1.0.0/docs/modules/fraud.md +10 -0
  16. cedikit-1.0.0/docs/modules/ids.md +9 -0
  17. cedikit-1.0.0/docs/modules/integrations.md +11 -0
  18. cedikit-1.0.0/docs/modules/ledger.md +5 -0
  19. cedikit-1.0.0/docs/modules/money.md +5 -0
  20. cedikit-1.0.0/docs/modules/phone.md +5 -0
  21. cedikit-1.0.0/docs/modules/sms.md +7 -0
  22. cedikit-1.0.0/docs/privacy.md +23 -0
  23. cedikit-1.0.0/docs/quickstart.md +82 -0
  24. cedikit-1.0.0/examples/customers.csv +501 -0
  25. cedikit-1.0.0/examples/inbox.csv +131 -0
  26. cedikit-1.0.0/examples/make_demo_data.py +227 -0
  27. cedikit-1.0.0/examples/suspicious.txt +15 -0
  28. cedikit-1.0.0/mkdocs.yml +57 -0
  29. cedikit-1.0.0/notebooks/build_demo.py +120 -0
  30. cedikit-1.0.0/notebooks/demo.ipynb +936 -0
  31. cedikit-1.0.0/pyproject.toml +88 -0
  32. cedikit-1.0.0/src/cedikit/__init__.py +35 -0
  33. cedikit-1.0.0/src/cedikit/cli.py +280 -0
  34. cedikit-1.0.0/src/cedikit/data/prefixes.yaml +34 -0
  35. cedikit-1.0.0/src/cedikit/evaluation.py +197 -0
  36. cedikit-1.0.0/src/cedikit/exceptions.py +42 -0
  37. cedikit-1.0.0/src/cedikit/fees/__init__.py +15 -0
  38. cedikit-1.0.0/src/cedikit/fees/calculator.py +227 -0
  39. cedikit-1.0.0/src/cedikit/fees/tables/levies.yaml +30 -0
  40. cedikit-1.0.0/src/cedikit/fees/tables/mtn.yaml +57 -0
  41. cedikit-1.0.0/src/cedikit/fees/tables/telecel.yaml +44 -0
  42. cedikit-1.0.0/src/cedikit/fraud/__init__.py +17 -0
  43. cedikit-1.0.0/src/cedikit/fraud/classifier.py +83 -0
  44. cedikit-1.0.0/src/cedikit/fraud/rules.py +444 -0
  45. cedikit-1.0.0/src/cedikit/fraud/scam_phrases.yaml +89 -0
  46. cedikit-1.0.0/src/cedikit/ids/__init__.py +15 -0
  47. cedikit-1.0.0/src/cedikit/ids/ghana_card.py +75 -0
  48. cedikit-1.0.0/src/cedikit/ids/gpgps.py +117 -0
  49. cedikit-1.0.0/src/cedikit/ids/regions.yaml +256 -0
  50. cedikit-1.0.0/src/cedikit/integrations/__init__.py +7 -0
  51. cedikit-1.0.0/src/cedikit/integrations/django_validators.py +66 -0
  52. cedikit-1.0.0/src/cedikit/integrations/flask_validators.py +70 -0
  53. cedikit-1.0.0/src/cedikit/integrations/pandas_accessor.py +82 -0
  54. cedikit-1.0.0/src/cedikit/integrations/pydantic_types.py +47 -0
  55. cedikit-1.0.0/src/cedikit/ledger.py +700 -0
  56. cedikit-1.0.0/src/cedikit/money.py +426 -0
  57. cedikit-1.0.0/src/cedikit/phone.py +320 -0
  58. cedikit-1.0.0/src/cedikit/py.typed +0 -0
  59. cedikit-1.0.0/src/cedikit/sms/__init__.py +24 -0
  60. cedikit-1.0.0/src/cedikit/sms/anonymise.py +182 -0
  61. cedikit-1.0.0/src/cedikit/sms/models.py +97 -0
  62. cedikit-1.0.0/src/cedikit/sms/parser.py +351 -0
  63. cedikit-1.0.0/src/cedikit/sms/templates/mtn.yaml +82 -0
  64. cedikit-1.0.0/src/cedikit/sms/templates/telecel.yaml +92 -0
  65. cedikit-1.0.0/tests/conftest.py +22 -0
  66. cedikit-1.0.0/tests/fixtures/sample_messages/genuine.yaml +304 -0
  67. cedikit-1.0.0/tests/fixtures/sample_messages/scam.yaml +63 -0
  68. cedikit-1.0.0/tests/test_anonymise.py +60 -0
  69. cedikit-1.0.0/tests/test_cli.py +129 -0
  70. cedikit-1.0.0/tests/test_evaluation.py +78 -0
  71. cedikit-1.0.0/tests/test_fees.py +90 -0
  72. cedikit-1.0.0/tests/test_fraud.py +210 -0
  73. cedikit-1.0.0/tests/test_ids.py +109 -0
  74. cedikit-1.0.0/tests/test_integrations.py +112 -0
  75. cedikit-1.0.0/tests/test_ledger.py +241 -0
  76. cedikit-1.0.0/tests/test_money.py +221 -0
  77. cedikit-1.0.0/tests/test_phone.py +156 -0
  78. cedikit-1.0.0/tests/test_sms_parser.py +250 -0
@@ -0,0 +1,3 @@
1
+ * text=auto eol=lf
2
+ *.png binary
3
+ *.xlsx binary
@@ -0,0 +1,87 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - run: pip install -e ".[dev]"
22
+ - run: ruff check .
23
+ - run: ruff format --check .
24
+ - run: mypy
25
+ - run: pytest
26
+ - run: pytest --no-cov --doctest-modules src
27
+ - name: Demo commands still work
28
+ run: |
29
+ cedikit phone clean examples/customers.csv --column phone --output /tmp/cleaned.csv
30
+ cedikit sms parse examples/inbox.csv --output /tmp/ledger.xlsx
31
+
32
+ core-only:
33
+ # The core package must work without any optional extras installed.
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: actions/setup-python@v5
38
+ with:
39
+ python-version: "3.12"
40
+ - run: pip install . pytest hypothesis
41
+ - run: >-
42
+ pytest -p no:cacheprovider --no-cov -o addopts=""
43
+ tests/test_phone.py tests/test_money.py tests/test_sms_parser.py tests/test_fraud.py
44
+ tests/test_fees.py tests/test_ids.py tests/test_anonymise.py
45
+
46
+ docs:
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.12"
53
+ - run: pip install -e ".[docs]"
54
+ # Only checks the docs build; the site itself is hosted on Read the Docs.
55
+ - run: mkdocs build --strict
56
+
57
+ build:
58
+ needs: [test, core-only, docs]
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@v4
62
+ - uses: actions/setup-python@v5
63
+ with:
64
+ python-version: "3.12"
65
+ - run: pip install build
66
+ - run: python -m build
67
+ - uses: actions/upload-artifact@v4
68
+ with:
69
+ name: dist
70
+ path: dist/
71
+
72
+ publish:
73
+ # Publishes to PyPI on version tags using trusted publishing.
74
+ # One-time setup: add this repo as a trusted publisher on pypi.org.
75
+ if: startsWith(github.ref, 'refs/tags/v')
76
+ needs: build
77
+ runs-on: ubuntu-latest
78
+ environment: pypi
79
+ permissions:
80
+ id-token: write
81
+ steps:
82
+ - uses: actions/download-artifact@v4
83
+ with:
84
+ name: dist
85
+ path: dist/
86
+ - uses: pypa/gh-action-pypi-publish@release/v1
87
+
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .hypothesis/
12
+ .coverage
13
+ coverage.xml
14
+ htmlcov/
15
+ site/
16
+ .ipynb_checkpoints/
@@ -0,0 +1,19 @@
1
+ # Read the Docs build configuration: https://docs.readthedocs.io/en/stable/config-file/v2.html
2
+ # Builds the MkDocs site at https://cedikit.readthedocs.io/ on every push to main.
3
+ version: 2
4
+
5
+ build:
6
+ os: ubuntu-24.04
7
+ tools:
8
+ python: "3.12"
9
+
10
+ mkdocs:
11
+ configuration: mkdocs.yml
12
+ fail_on_warning: true
13
+
14
+ python:
15
+ install:
16
+ - method: pip
17
+ path: .
18
+ extra_requirements:
19
+ - docs
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/). Data-file updates (prefixes, templates, fee tables)
6
+ are released as patch versions.
7
+
8
+ ## [1.0.0] - Unreleased
9
+
10
+ First complete release. Everything runs offline.
11
+
12
+ ### Added
13
+ - **`cedikit.phone`**: `normalise`, `is_valid`, `format` (e164 / local / pretty /
14
+ international), `likely_network`, `mask`, `clean_column`. Prefix data in `data/prefixes.yaml`.
15
+ - **`cedikit.money`**: `parse`, `format` (symbol / code / compact), `to_words`,
16
+ `round_pesewas` and the float-proof `Cedi` type.
17
+ - **`cedikit.sms`**: template-driven parser for MTN MoMo and Telecel Cash returning a
18
+ `ParseResult` with a `Transaction` and confidence score; unrecognised messages never raise.
19
+ 12 templates built from real messages: Telecel send (same / other network), receive (same /
20
+ other network), cash in, airtime purchase, airtime notice; MTN payment received, payment to
21
+ merchants and loans, cash in, cash out, send to another network, data bundle.
22
+ `Transaction.affects_wallet` marks notices that repeat a transaction. `sms.Parser` loads extra
23
+ template files at runtime.
24
+ - **`cedikit.sms.anonymise`**: replaces names, numbers, IDs, amounts, dates and links while
25
+ keeping wording and keeping balances consistent across a batch.
26
+ - **`cedikit.fraud`**: `check()` returns a `FraudReport` (risk, score, reasons, per-check
27
+ results, advice). Checks: sender ID, genuine format, spelling, scam phrases, accented-letter
28
+ disguises, transaction ID length, balance consistency; optional `ScamClassifier` (scikit-learn).
29
+ Signals combine with a noisy-OR. Scam phrases live in `fraud/scam_phrases.yaml`.
30
+ - **`cedikit.ledger`**: `Ledger.from_messages` / `from_csv`, `summary`, `cash_flow`,
31
+ `top_counterparties`, rule-based `categorise`, `category_totals`, `balance_gaps`, export to
32
+ CSV / Excel / JSON, `to_dataframe`, `plot`.
33
+ - **`cedikit.fees`**: fee and E-Levy estimates from dated tables in which every rule cites its
34
+ evidence; unknown values are `None`.
35
+ - **`cedikit.ids`**: Ghana Card format checks for `GHA` (citizens) and `FGN` (foreign
36
+ nationals) with `card_type()`; GhanaPostGPS parsing with region and district names (218
37
+ district codes, including digit codes such as `A2`), sourced from ghanapostgps.com and the
38
+ Wikipedia postcode table.
39
+ - **`cedikit.evaluation`**: parser accuracy and fraud precision / recall on labelled files.
40
+ - **Integrations**: pandas `.cedikit` accessor, Pydantic types, Django validators, WTForms
41
+ validators.
42
+ - **CLI** (`cedikit`): `phone clean|check`, `money parse|words`, `sms parse|anonymise`,
43
+ `fraud check`, `fees estimate`, `ids check`.
44
+ - Docs site (MkDocs, hosted on Read the Docs), demo notebook, demo data generator,
45
+ anonymised fixtures.
46
+
47
+ ### Notes
48
+ - AT Money is out of scope (little used); AT numbers are still handled by `cedikit.phone`.
49
+ - Data checked against sources on 2026-09-26: E-Levy repeal date (2 April 2025, GRA);
50
+ network prefixes (NCA numbering plan + later MTN assignments; Glo 023 added); MTN cash-out
51
+ and same-network send schedules (third-party tracker, consistent with real messages).
52
+ - Still unconfirmed: MTN cross-network fee, Telecel cash-out fee (the only published table
53
+ contradicts real messages), and district names against GhanaPostGPS's official table.
54
+ - The Ghana Card check digit is not validated: its algorithm is not published.
@@ -0,0 +1,48 @@
1
+ # Contributing to cedikit
2
+
3
+ Thanks for helping! A few ground rules keep the library trustworthy.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ python -m venv .venv
9
+ .venv/Scripts/activate # Windows; use `source .venv/bin/activate` elsewhere
10
+ pip install -e ".[dev]"
11
+ ```
12
+
13
+ Before opening a pull request, all of these must pass:
14
+
15
+ ```bash
16
+ pytest
17
+ pytest --no-cov --doctest-modules src
18
+ ruff check . && ruff format --check .
19
+ mypy
20
+ mkdocs build --strict # needs pip install -e ".[docs]"
21
+ ```
22
+
23
+ ## Rules
24
+
25
+ - **Never commit real personal data.** Phone numbers, names, transaction IDs and balances in tests
26
+ and fixtures must be fake. Anonymise SMS samples *before* they enter the repository with
27
+ `cedikit sms anonymise messages.txt`, then read the output: anything marked `CHECK BY HAND`
28
+ may still contain names.
29
+ - **Money is `Decimal`, never `float`.**
30
+ - **Data lives in data files.** Network prefixes, SMS templates, fee tables and scam phrases belong
31
+ in YAML under `src/cedikit/`, not hard-coded in Python.
32
+ - **Be honest in outputs.** Network guesses are *likely*, fees are *estimates*, fraud results are
33
+ *risk indicators*.
34
+ - Public functions get type hints and a Google-style docstring with an example.
35
+
36
+ ## Updating fee tables and scam phrases
37
+
38
+ - Fee rules (`src/cedikit/fees/tables/*.yaml`) must cite their evidence in `source`: an official
39
+ tariff or real (anonymised) messages. When charges change, add a new rule with a `from:` date
40
+ rather than editing the old one, so historical transactions keep the old rate.
41
+ - New scam phrases (`src/cedikit/fraud/scam_phrases.yaml`) need a scam sample in
42
+ `tests/fixtures/sample_messages/scam.yaml`, and all genuine fixtures must still score LOW.
43
+ Check with `cedikit.evaluation.evaluate_fraud`.
44
+
45
+ ## Updating network prefixes
46
+
47
+ Edit `src/cedikit/data/prefixes.yaml`, bump its `version` date, cite your source (e.g. the NCA
48
+ numbering plan) in the pull request, and add a test for the new prefix.
cedikit-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Francis Kusi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
cedikit-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.5
2
+ Name: cedikit
3
+ Version: 1.0.0
4
+ Summary: A Python toolkit for Ghanaian phone numbers, cedi amounts, and Mobile Money transactions
5
+ Project-URL: Homepage, https://github.com/brainiacweb-tech/cedikit
6
+ Project-URL: Issues, https://github.com/brainiacweb-tech/cedikit/issues
7
+ Project-URL: Documentation, https://cedikit.readthedocs.io/
8
+ Project-URL: Changelog, https://github.com/brainiacweb-tech/cedikit/blob/main/CHANGELOG.md
9
+ Author: Francis Kusi
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: cedi,fintech,ghana,mobile money,momo,phone numbers
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Office/Business :: Financial
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: pyyaml>=6.0
26
+ Requires-Dist: typer>=0.12
27
+ Provides-Extra: all
28
+ Requires-Dist: django>=4.2; extra == 'all'
29
+ Requires-Dist: joblib>=1.3; extra == 'all'
30
+ Requires-Dist: matplotlib>=3.7; extra == 'all'
31
+ Requires-Dist: openpyxl>=3.1; extra == 'all'
32
+ Requires-Dist: pandas>=2.0; extra == 'all'
33
+ Requires-Dist: pydantic>=2.0; extra == 'all'
34
+ Requires-Dist: scikit-learn>=1.3; extra == 'all'
35
+ Requires-Dist: wtforms>=3.0; extra == 'all'
36
+ Provides-Extra: charts
37
+ Requires-Dist: matplotlib>=3.7; extra == 'charts'
38
+ Provides-Extra: dev
39
+ Requires-Dist: django>=4.2; extra == 'dev'
40
+ Requires-Dist: hypothesis>=6; extra == 'dev'
41
+ Requires-Dist: joblib>=1.3; extra == 'dev'
42
+ Requires-Dist: matplotlib>=3.7; extra == 'dev'
43
+ Requires-Dist: mypy>=1.10; extra == 'dev'
44
+ Requires-Dist: openpyxl>=3.1; extra == 'dev'
45
+ Requires-Dist: pandas-stubs; extra == 'dev'
46
+ Requires-Dist: pandas>=2.0; extra == 'dev'
47
+ Requires-Dist: pydantic>=2.0; extra == 'dev'
48
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
49
+ Requires-Dist: pytest>=8; extra == 'dev'
50
+ Requires-Dist: ruff>=0.5; extra == 'dev'
51
+ Requires-Dist: scikit-learn>=1.3; extra == 'dev'
52
+ Requires-Dist: types-pyyaml; extra == 'dev'
53
+ Requires-Dist: wtforms>=3.0; extra == 'dev'
54
+ Provides-Extra: django
55
+ Requires-Dist: django>=4.2; extra == 'django'
56
+ Provides-Extra: docs
57
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
58
+ Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
59
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
60
+ Provides-Extra: excel
61
+ Requires-Dist: openpyxl>=3.1; extra == 'excel'
62
+ Provides-Extra: flask
63
+ Requires-Dist: wtforms>=3.0; extra == 'flask'
64
+ Provides-Extra: ml
65
+ Requires-Dist: joblib>=1.3; extra == 'ml'
66
+ Requires-Dist: scikit-learn>=1.3; extra == 'ml'
67
+ Provides-Extra: pandas
68
+ Requires-Dist: pandas>=2.0; extra == 'pandas'
69
+ Provides-Extra: pydantic
70
+ Requires-Dist: pydantic>=2.0; extra == 'pydantic'
71
+ Description-Content-Type: text/markdown
72
+
73
+ # cedikit
74
+
75
+ **A Python toolkit for Ghanaian phone numbers, cedi amounts, and Mobile Money transactions.**
76
+
77
+ [![PyPI](https://img.shields.io/pypi/v/cedikit)](https://pypi.org/project/cedikit/)
78
+ [![Docs](https://readthedocs.org/projects/cedikit/badge/?version=latest)](https://cedikit.readthedocs.io/)
79
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/brainiacweb-tech/cedikit/blob/main/LICENSE)
80
+
81
+ Every Ghanaian app ends up writing the same code: cleaning phone numbers typed five different
82
+ ways, adding up cedi amounts without floating-point errors, and making sense of MoMo SMS
83
+ alerts. cedikit does this once, carefully, with tests.
84
+
85
+ Everything runs **offline**. No user data leaves the device.
86
+
87
+ ## Install
88
+
89
+ ```bash
90
+ pip install cedikit # core + the `cedikit` command
91
+ pip install "cedikit[all]" # + pandas, Excel export, charts, ML, Pydantic, Django, Flask
92
+ ```
93
+
94
+ Requires Python 3.10+.
95
+
96
+ ## 30-second tour
97
+
98
+ ```python
99
+ from cedikit import phone, money, sms, fraud, Cedi
100
+ from cedikit.ledger import Ledger
101
+
102
+ phone.normalise("024 412 3456") # '+233244123456'
103
+ phone.likely_network("0244123456").network # 'MTN' (likely - numbers can be ported)
104
+
105
+ money.parse("GH₵1.2k") # Decimal('1200.00')
106
+ money.to_words("1200.50") # 'One thousand two hundred Ghana cedis and fifty pesewas'
107
+ sum([Cedi("1.10"), Cedi("2.20")]) # Cedi('3.30') - exact, unlike 1.1 + 2.2
108
+
109
+ tx = sms.parse(message_text, sender="MobileMoney").transaction
110
+ tx.type, tx.amount, tx.counterparty, tx.balance
111
+
112
+ ledger = Ledger.from_messages(inbox, sender="MobileMoney").categorise()
113
+ print(ledger.summary())
114
+ ledger.export("september.xlsx") # Transactions, Summary, Cash flow, Categories
115
+
116
+ print(fraud.check(suspicious_text, sender="+233591234567", history=ledger.transactions))
117
+ # Risk: HIGH (score 0.99)
118
+ # Reasons:
119
+ # - Sent from a personal phone number (+233 59 123 4567), not an official sender ID ...
120
+ # - Claimed balance GHS 640.35 does not follow from your last genuine balance ...
121
+ ```
122
+
123
+ From the command line:
124
+
125
+ ```bash
126
+ cedikit phone clean customers.csv --column phone
127
+ cedikit sms parse inbox.csv --export xlsx
128
+ cedikit fraud check "Cash receive for 200.00 ..." --sender 0543268728
129
+ ```
130
+
131
+ ## Modules
132
+
133
+ | Module | What it does |
134
+ |---|---|
135
+ | `cedikit.phone` | Normalise, validate, format, mask, likely network, bulk clean |
136
+ | `cedikit.money` | `Decimal` parsing, formatting, words, rounding, the `Cedi` type |
137
+ | `cedikit.sms` | MTN MoMo and Telecel Cash SMS → transactions (12 formats); anonymiser |
138
+ | `cedikit.fraud` | Fake-alert detection with reasons; optional ML classifier |
139
+ | `cedikit.ledger` | Summary, cash flow, categories, balance gaps, CSV/Excel/JSON, charts |
140
+ | `cedikit.fees` | Fee and E-Levy estimates from dated, sourced tables |
141
+ | `cedikit.ids` | Ghana Card and GhanaPostGPS format checks |
142
+ | `cedikit.evaluation` | Parser accuracy and fraud precision/recall on labelled data |
143
+ | Integrations | pandas accessor, Pydantic types, Django and Flask validators |
144
+
145
+ Full documentation: [the docs site](https://cedikit.readthedocs.io/). The end-to-end demo is
146
+ [notebooks/demo.ipynb](https://github.com/brainiacweb-tech/cedikit/blob/main/notebooks/demo.ipynb), using the data in [examples/](https://github.com/brainiacweb-tech/cedikit/tree/main/examples).
147
+
148
+ ## Honest outputs
149
+
150
+ - **Network detection is only "likely".** Mobile number portability lets people keep their number
151
+ when they switch networks.
152
+ - **Fees are estimates.** The tables record only charges seen in real messages or published
153
+ rules, each with its source. Unknown charges are reported as unknown, never guessed.
154
+ - **Fraud results are risk indicators, not guarantees.** Always confirm a payment in the official
155
+ Mobile Money app before releasing goods.
156
+ - **Money is never a float.**
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ python -m venv .venv
162
+ .venv/Scripts/activate # Windows; use `source .venv/bin/activate` elsewhere
163
+ pip install -e ".[dev,docs]"
164
+ pytest # tests + coverage (>= 90%)
165
+ pytest --no-cov --doctest-modules src
166
+ ruff check . && ruff format --check .
167
+ mypy
168
+ mkdocs serve # docs at http://127.0.0.1:8000
169
+ ```
170
+
171
+ See [CONTRIBUTING.md](https://github.com/brainiacweb-tech/cedikit/blob/main/CONTRIBUTING.md) and [Adding an SMS template](https://github.com/brainiacweb-tech/cedikit/blob/main/docs/contributing-templates.md).
172
+
173
+ ## Licence
174
+
175
+ MIT © Francis Kusi. Built in Ghana, for Ghana.
@@ -0,0 +1,103 @@
1
+ # cedikit
2
+
3
+ **A Python toolkit for Ghanaian phone numbers, cedi amounts, and Mobile Money transactions.**
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/cedikit)](https://pypi.org/project/cedikit/)
6
+ [![Docs](https://readthedocs.org/projects/cedikit/badge/?version=latest)](https://cedikit.readthedocs.io/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/brainiacweb-tech/cedikit/blob/main/LICENSE)
8
+
9
+ Every Ghanaian app ends up writing the same code: cleaning phone numbers typed five different
10
+ ways, adding up cedi amounts without floating-point errors, and making sense of MoMo SMS
11
+ alerts. cedikit does this once, carefully, with tests.
12
+
13
+ Everything runs **offline**. No user data leaves the device.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install cedikit # core + the `cedikit` command
19
+ pip install "cedikit[all]" # + pandas, Excel export, charts, ML, Pydantic, Django, Flask
20
+ ```
21
+
22
+ Requires Python 3.10+.
23
+
24
+ ## 30-second tour
25
+
26
+ ```python
27
+ from cedikit import phone, money, sms, fraud, Cedi
28
+ from cedikit.ledger import Ledger
29
+
30
+ phone.normalise("024 412 3456") # '+233244123456'
31
+ phone.likely_network("0244123456").network # 'MTN' (likely - numbers can be ported)
32
+
33
+ money.parse("GH₵1.2k") # Decimal('1200.00')
34
+ money.to_words("1200.50") # 'One thousand two hundred Ghana cedis and fifty pesewas'
35
+ sum([Cedi("1.10"), Cedi("2.20")]) # Cedi('3.30') - exact, unlike 1.1 + 2.2
36
+
37
+ tx = sms.parse(message_text, sender="MobileMoney").transaction
38
+ tx.type, tx.amount, tx.counterparty, tx.balance
39
+
40
+ ledger = Ledger.from_messages(inbox, sender="MobileMoney").categorise()
41
+ print(ledger.summary())
42
+ ledger.export("september.xlsx") # Transactions, Summary, Cash flow, Categories
43
+
44
+ print(fraud.check(suspicious_text, sender="+233591234567", history=ledger.transactions))
45
+ # Risk: HIGH (score 0.99)
46
+ # Reasons:
47
+ # - Sent from a personal phone number (+233 59 123 4567), not an official sender ID ...
48
+ # - Claimed balance GHS 640.35 does not follow from your last genuine balance ...
49
+ ```
50
+
51
+ From the command line:
52
+
53
+ ```bash
54
+ cedikit phone clean customers.csv --column phone
55
+ cedikit sms parse inbox.csv --export xlsx
56
+ cedikit fraud check "Cash receive for 200.00 ..." --sender 0543268728
57
+ ```
58
+
59
+ ## Modules
60
+
61
+ | Module | What it does |
62
+ |---|---|
63
+ | `cedikit.phone` | Normalise, validate, format, mask, likely network, bulk clean |
64
+ | `cedikit.money` | `Decimal` parsing, formatting, words, rounding, the `Cedi` type |
65
+ | `cedikit.sms` | MTN MoMo and Telecel Cash SMS → transactions (12 formats); anonymiser |
66
+ | `cedikit.fraud` | Fake-alert detection with reasons; optional ML classifier |
67
+ | `cedikit.ledger` | Summary, cash flow, categories, balance gaps, CSV/Excel/JSON, charts |
68
+ | `cedikit.fees` | Fee and E-Levy estimates from dated, sourced tables |
69
+ | `cedikit.ids` | Ghana Card and GhanaPostGPS format checks |
70
+ | `cedikit.evaluation` | Parser accuracy and fraud precision/recall on labelled data |
71
+ | Integrations | pandas accessor, Pydantic types, Django and Flask validators |
72
+
73
+ Full documentation: [the docs site](https://cedikit.readthedocs.io/). The end-to-end demo is
74
+ [notebooks/demo.ipynb](https://github.com/brainiacweb-tech/cedikit/blob/main/notebooks/demo.ipynb), using the data in [examples/](https://github.com/brainiacweb-tech/cedikit/tree/main/examples).
75
+
76
+ ## Honest outputs
77
+
78
+ - **Network detection is only "likely".** Mobile number portability lets people keep their number
79
+ when they switch networks.
80
+ - **Fees are estimates.** The tables record only charges seen in real messages or published
81
+ rules, each with its source. Unknown charges are reported as unknown, never guessed.
82
+ - **Fraud results are risk indicators, not guarantees.** Always confirm a payment in the official
83
+ Mobile Money app before releasing goods.
84
+ - **Money is never a float.**
85
+
86
+ ## Development
87
+
88
+ ```bash
89
+ python -m venv .venv
90
+ .venv/Scripts/activate # Windows; use `source .venv/bin/activate` elsewhere
91
+ pip install -e ".[dev,docs]"
92
+ pytest # tests + coverage (>= 90%)
93
+ pytest --no-cov --doctest-modules src
94
+ ruff check . && ruff format --check .
95
+ mypy
96
+ mkdocs serve # docs at http://127.0.0.1:8000
97
+ ```
98
+
99
+ See [CONTRIBUTING.md](https://github.com/brainiacweb-tech/cedikit/blob/main/CONTRIBUTING.md) and [Adding an SMS template](https://github.com/brainiacweb-tech/cedikit/blob/main/docs/contributing-templates.md).
100
+
101
+ ## Licence
102
+
103
+ MIT © Francis Kusi. Built in Ghana, for Ghana.
@@ -0,0 +1,25 @@
1
+ # Command line
2
+
3
+ Installing cedikit adds a `cedikit` command. Run `cedikit --help` or `cedikit <group> --help`.
4
+
5
+ | Command | What it does |
6
+ |---|---|
7
+ | `cedikit phone clean FILE.csv --column phone [--style pretty] [--output OUT.csv]` | Normalise a column; adds `_status` and `_note` columns |
8
+ | `cedikit phone check NUMBER` | Formats and likely network of one number |
9
+ | `cedikit money parse TEXT` | `GH₵1.2k` → `1200.00` |
10
+ | `cedikit money words AMOUNT` | Amount in words |
11
+ | `cedikit sms parse FILE [--sender ID] [--export csv\|xlsx\|json] [--output PATH]` | Messages → ledger summary, balance gaps, export |
12
+ | `cedikit sms anonymise FILE [--seed N]` | Anonymise messages before sharing them |
13
+ | `cedikit fraud check TEXT [--sender ID]` | Risk rating with reasons (`-` reads the text from stdin) |
14
+ | `cedikit fees estimate NETWORK KIND AMOUNT [--on YYYY-MM-DD]` | Fee and E-Levy estimate |
15
+ | `cedikit ids check VALUE` | Ghana Card or GhanaPostGPS format check |
16
+
17
+ **Message files** for `sms parse` and `sms anonymise` are either plain text with one message per
18
+ paragraph (messages separated by a blank line), or a CSV with a `text` column.
19
+
20
+ Try it on the demo data in `examples/`:
21
+
22
+ ```bash
23
+ cedikit phone clean examples/customers.csv --column phone
24
+ cedikit sms parse examples/inbox.txt --export xlsx
25
+ ```
@@ -0,0 +1,78 @@
1
+ # Adding an SMS template
2
+
3
+ Telcos change their message wording from time to time. When that happens, cedikit needs a new
4
+ template, not new code. Templates live in `src/cedikit/sms/templates/<network>.yaml`.
5
+
6
+ ## 1. Anonymise the sample first
7
+
8
+ Before a message goes anywhere in the repository, replace **every** name, phone number, transaction
9
+ ID, amount, balance, reference and date with fake values. Keep everything else exactly as it is:
10
+ the wording, capital letters, punctuation, and even odd spacing like `NAME .` or double spaces.
11
+ Those quirks are what the template has to match.
12
+
13
+ ## 2. Add a fixture
14
+
15
+ Append the anonymised message to `tests/fixtures/sample_messages/genuine.yaml` with the fields you
16
+ expect the parser to extract:
17
+
18
+ ```yaml
19
+ - id: mtn_cash_out
20
+ sender: MobileMoney
21
+ text: |-
22
+ Cash Out made for GHS40.00 to ADOM ELECTRICALS . Current Balance: GHS22.10 ...
23
+ expected:
24
+ template: mtn_cash_out
25
+ type: CASH_OUT
26
+ amount: "40.00"
27
+ counterparty_name: ADOM ELECTRICALS
28
+ ```
29
+
30
+ Run `pytest tests/test_sms_parser.py`. The new fixture should fail.
31
+
32
+ ## 3. Write the template
33
+
34
+ ```yaml
35
+ - name: mtn_cash_out
36
+ type: CASH_OUT # RECEIVED SENT CASH_OUT CASH_IN MERCHANT AIRTIME BILL REVERSAL
37
+ source: sample 2026-09 # where the format was seen
38
+ pattern: >-
39
+ Cash Out made for {{amount}} to {{counterparty_name}} ?\.
40
+ Current Balance:? {{balance}}
41
+ Financial Transaction Id: {{transaction_id}}\.
42
+ .*?Fee charged: {{fee}}
43
+ fields:
44
+ transaction_id: '\d{11}' # optional: tighter regex for one placeholder
45
+ ```
46
+
47
+ How matching works:
48
+
49
+ - The message is **cleaned** first: whitespace (including line breaks) collapses to single spaces,
50
+ and `GH₵`, `GH¢`, `GHC` and `₵` all become `GHS`. The `>-` block joins your lines with single
51
+ spaces too, so write the pattern the way the cleaned message reads.
52
+ - **Keep optional groups on the same line as the text before them.** Each line break adds a
53
+ space, so an optional group on its own line leaves a double space when it's absent. Write
54
+ `Balance: {{balance}}\.(?: Reference: {{reference}}\.)?`, not the group on a new line.
55
+ - `pattern` is a regular expression that must match from the **start** of the message. Any text
56
+ after it (adverts, safety tips) is ignored. Escape literal dots as `\.`.
57
+ - `{{placeholders}}` become named capture groups. Money placeholders (`amount`, `fee`, `tax`,
58
+ `balance`, `available_balance`) already include the `GHS ?` prefix. The full list is
59
+ `FIELD_PATTERNS` in `src/cedikit/sms/parser.py`.
60
+ - `extras` are fields that can appear anywhere, e.g. a `Reference:` after an advert:
61
+ `reference: 'Reference: {{reference}} ?\.'`
62
+ - `affects_wallet: false` marks notices that repeat another transaction without moving wallet
63
+ money, e.g. Telecel's "you have received airtime" after an airtime purchase (same transaction
64
+ ID). They still parse, but ledgers and balance checks skip them.
65
+ - `optional` lists fields that are sometimes missing. Missing optional fields don't lower
66
+ confidence.
67
+ - Confidence = (required fields found and valid ÷ required fields) × `weight`. Below 0.8, the
68
+ transaction's `needs_review` is True.
69
+
70
+ ## 4. Check
71
+
72
+ ```bash
73
+ pytest
74
+ ruff check . && mypy
75
+ ```
76
+
77
+ If you can't release yet, apps can load a template file at runtime:
78
+ `sms.Parser(extra_template_files=["my_templates.yaml"])`.