Ledger
Only transactions that move wallet money are counted. Notices (e.g. "you have received airtime") go to ledger.notices, unknown messages to ledger.unrecognised, and repeated transaction IDs are dropped.
cedikit.ledger
Turn Mobile Money transactions into a ledger with summaries, cash flow and exports.
Example
from cedikit.ledger import Ledger ledger = Ledger.from_messages([ ... ("Cash In received for GHS 100.00 from ADOM VENTURES . Current Balance GHS 120.00 " ... "Available Balance GHS 120.00. Transaction ID: 10000000001. Fee charged: GHS 0.", ... "MobileMoney"), ... ]) ledger.summary().total_in Decimal('100.00')
DEFAULT_RULES = (Rule('loan repayment', name_contains='loan'), Rule('airtime & data', types=frozenset({T.AIRTIME})), Rule('bills', types=frozenset({T.BILL})), Rule('reversals', types=frozenset({T.REVERSAL})), Rule('cash deposit', types=frozenset({T.CASH_IN})), Rule('cash withdrawal', types=frozenset({T.CASH_OUT})), Rule('payments & supplies', types=frozenset({T.MERCHANT})), Rule('sales & money received', types=frozenset({T.RECEIVED})), Rule('money sent', types=frozenset({T.SENT})))
module-attribute
Applied after your own rules. The first matching rule wins.
Rule
dataclass
A categorisation rule. Every condition given must match.
Example
Rule("stock", name_contains="wholesale").category 'stock'
from_dict(data)
classmethod
Build a rule from plain data, e.g. one entry of a YAML rules file.
LedgerSummary
dataclass
Totals for a ledger. net is money in minus money out, fees and taxes.
BalanceGap
dataclass
Two consecutive transactions whose balances don't add up.
Usually a message is missing between them (e.g. an automatic loan deduction that wasn't imported); occasionally one of them is fake.
Ledger
An ordered collection of wallet transactions.
Only transactions that move wallet money are included: notices such as
"you have received airtime" are kept in :attr:notices, unrecognised
messages in :attr:unrecognised, and repeated transaction IDs are dropped
(counted in :attr:duplicates).
Transactions are sorted by time when all of them have a timestamp; otherwise they keep the order you gave them in (phones list SMS oldest first, so pass them that way).
from_messages(messages, sender=None, *, parser=None)
classmethod
Parse SMS messages into a ledger.
Each message can be the text alone, a (text, sender) tuple, or a
mapping with text and optional sender and received_at keys.
sender applies to messages that don't name their own.
from_csv(path)
classmethod
Load a ledger saved with :meth:export as CSV.
summary()
Money in, money out, fees, taxes and net flow.
cash_flow(period='month')
Money in and out per day, week (from Monday) or month.
Transactions without a timestamp are left out; see summary().undated.
top_counterparties(n=5, by='count')
The people and businesses you deal with most, by number or value.
categorise(rules=(), *, include_defaults=True)
Return a copy with a category on every transaction.
Your rules are tried first, then :data:DEFAULT_RULES. The first
match wins; anything unmatched is "uncategorised".
Example
rules = [{"category": "stock", "name_contains": "wholesale"}] categorised = ledger.categorise(rules) # doctest: +SKIP
category_totals()
Total amount per category, largest first. Call :meth:categorise first.
balance_gaps(tolerance=Decimal('0.01'))
Consecutive transactions (per network) whose balances don't add up.
to_rows()
One flat dict of strings per transaction (the CSV export format).
to_dataframe()
A pandas DataFrame with Decimal money columns and parsed timestamps.
export(path, format=None)
Save as CSV, Excel (.xlsx) or JSON; the format follows the extension.
Excel files get Transactions, Summary, Cash flow and (if categorised) Categories sheets.
plot(kind='cash_flow', period='month', path=None)
Draw a quick chart; saved to path if given. Needs matplotlib.
expected_balance(previous, amount, direction, fee=None, tax=None)
The wallet balance after a transaction, given the balance before it.
Fees and taxes always leave the wallet, whichever way the amount moves.
Example
expected_balance(Decimal("100.00"), Decimal("40.00"), "out", Decimal("0.20")) Decimal('59.80')
split_messages(text)
Split pasted text into messages: one message per paragraph (blank line between).
Example
split_messages("first message\n\n second\nmessage \n\n\n") ['first message', 'second\nmessage']
read_messages(path)
Read messages from a file, ready for :meth:Ledger.from_messages.
A .csv file needs a text column, and may have sender and
received_at (ISO date-time) columns. Any other file is plain text with
one message per paragraph.