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.
- cedikit-1.0.0/.gitattributes +3 -0
- cedikit-1.0.0/.github/workflows/ci.yml +87 -0
- cedikit-1.0.0/.gitignore +16 -0
- cedikit-1.0.0/.readthedocs.yaml +19 -0
- cedikit-1.0.0/CHANGELOG.md +54 -0
- cedikit-1.0.0/CONTRIBUTING.md +48 -0
- cedikit-1.0.0/LICENSE +21 -0
- cedikit-1.0.0/PKG-INFO +175 -0
- cedikit-1.0.0/README.md +103 -0
- cedikit-1.0.0/docs/cli.md +25 -0
- cedikit-1.0.0/docs/contributing-templates.md +78 -0
- cedikit-1.0.0/docs/index.md +38 -0
- cedikit-1.0.0/docs/modules/evaluation.md +5 -0
- cedikit-1.0.0/docs/modules/fees.md +5 -0
- cedikit-1.0.0/docs/modules/fraud.md +10 -0
- cedikit-1.0.0/docs/modules/ids.md +9 -0
- cedikit-1.0.0/docs/modules/integrations.md +11 -0
- cedikit-1.0.0/docs/modules/ledger.md +5 -0
- cedikit-1.0.0/docs/modules/money.md +5 -0
- cedikit-1.0.0/docs/modules/phone.md +5 -0
- cedikit-1.0.0/docs/modules/sms.md +7 -0
- cedikit-1.0.0/docs/privacy.md +23 -0
- cedikit-1.0.0/docs/quickstart.md +82 -0
- cedikit-1.0.0/examples/customers.csv +501 -0
- cedikit-1.0.0/examples/inbox.csv +131 -0
- cedikit-1.0.0/examples/make_demo_data.py +227 -0
- cedikit-1.0.0/examples/suspicious.txt +15 -0
- cedikit-1.0.0/mkdocs.yml +57 -0
- cedikit-1.0.0/notebooks/build_demo.py +120 -0
- cedikit-1.0.0/notebooks/demo.ipynb +936 -0
- cedikit-1.0.0/pyproject.toml +88 -0
- cedikit-1.0.0/src/cedikit/__init__.py +35 -0
- cedikit-1.0.0/src/cedikit/cli.py +280 -0
- cedikit-1.0.0/src/cedikit/data/prefixes.yaml +34 -0
- cedikit-1.0.0/src/cedikit/evaluation.py +197 -0
- cedikit-1.0.0/src/cedikit/exceptions.py +42 -0
- cedikit-1.0.0/src/cedikit/fees/__init__.py +15 -0
- cedikit-1.0.0/src/cedikit/fees/calculator.py +227 -0
- cedikit-1.0.0/src/cedikit/fees/tables/levies.yaml +30 -0
- cedikit-1.0.0/src/cedikit/fees/tables/mtn.yaml +57 -0
- cedikit-1.0.0/src/cedikit/fees/tables/telecel.yaml +44 -0
- cedikit-1.0.0/src/cedikit/fraud/__init__.py +17 -0
- cedikit-1.0.0/src/cedikit/fraud/classifier.py +83 -0
- cedikit-1.0.0/src/cedikit/fraud/rules.py +444 -0
- cedikit-1.0.0/src/cedikit/fraud/scam_phrases.yaml +89 -0
- cedikit-1.0.0/src/cedikit/ids/__init__.py +15 -0
- cedikit-1.0.0/src/cedikit/ids/ghana_card.py +75 -0
- cedikit-1.0.0/src/cedikit/ids/gpgps.py +117 -0
- cedikit-1.0.0/src/cedikit/ids/regions.yaml +256 -0
- cedikit-1.0.0/src/cedikit/integrations/__init__.py +7 -0
- cedikit-1.0.0/src/cedikit/integrations/django_validators.py +66 -0
- cedikit-1.0.0/src/cedikit/integrations/flask_validators.py +70 -0
- cedikit-1.0.0/src/cedikit/integrations/pandas_accessor.py +82 -0
- cedikit-1.0.0/src/cedikit/integrations/pydantic_types.py +47 -0
- cedikit-1.0.0/src/cedikit/ledger.py +700 -0
- cedikit-1.0.0/src/cedikit/money.py +426 -0
- cedikit-1.0.0/src/cedikit/phone.py +320 -0
- cedikit-1.0.0/src/cedikit/py.typed +0 -0
- cedikit-1.0.0/src/cedikit/sms/__init__.py +24 -0
- cedikit-1.0.0/src/cedikit/sms/anonymise.py +182 -0
- cedikit-1.0.0/src/cedikit/sms/models.py +97 -0
- cedikit-1.0.0/src/cedikit/sms/parser.py +351 -0
- cedikit-1.0.0/src/cedikit/sms/templates/mtn.yaml +82 -0
- cedikit-1.0.0/src/cedikit/sms/templates/telecel.yaml +92 -0
- cedikit-1.0.0/tests/conftest.py +22 -0
- cedikit-1.0.0/tests/fixtures/sample_messages/genuine.yaml +304 -0
- cedikit-1.0.0/tests/fixtures/sample_messages/scam.yaml +63 -0
- cedikit-1.0.0/tests/test_anonymise.py +60 -0
- cedikit-1.0.0/tests/test_cli.py +129 -0
- cedikit-1.0.0/tests/test_evaluation.py +78 -0
- cedikit-1.0.0/tests/test_fees.py +90 -0
- cedikit-1.0.0/tests/test_fraud.py +210 -0
- cedikit-1.0.0/tests/test_ids.py +109 -0
- cedikit-1.0.0/tests/test_integrations.py +112 -0
- cedikit-1.0.0/tests/test_ledger.py +241 -0
- cedikit-1.0.0/tests/test_money.py +221 -0
- cedikit-1.0.0/tests/test_phone.py +156 -0
- cedikit-1.0.0/tests/test_sms_parser.py +250 -0
|
@@ -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
|
+
|
cedikit-1.0.0/.gitignore
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/cedikit/)
|
|
78
|
+
[](https://cedikit.readthedocs.io/)
|
|
79
|
+
[](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.
|
cedikit-1.0.0/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# cedikit
|
|
2
|
+
|
|
3
|
+
**A Python toolkit for Ghanaian phone numbers, cedi amounts, and Mobile Money transactions.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/cedikit/)
|
|
6
|
+
[](https://cedikit.readthedocs.io/)
|
|
7
|
+
[](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"])`.
|