Skip to content

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.