usawa

Signed, immutable accounting.
Log | Files | Refs | Submodules | LICENSE

commit 463f1768a7dea1904b8468989761f443eda3dafb
parent cb2707e8ac8c4777381b30e852abf63b35f88f29
Author: lash <dev@holbrook.no>
Date:   Wed, 25 Mar 2026 06:29:16 -0600

Reimplement balancer

Diffstat:
Mdummy/tests/balance.py | 26++++++++++----------------
Mdummy/tests/unit.py | 3+--
Mdummy/usawa/balance.py | 48+++++++++++++++++++++++++++++++++++++++---------
Mdummy/usawa/unit.py | 14++++++++------
4 files changed, 58 insertions(+), 33 deletions(-)

diff --git a/dummy/tests/balance.py b/dummy/tests/balance.py @@ -4,7 +4,7 @@ import datetime import os from usawa import EntryPart, Entry, UnitIndex -from usawa.balance import Translator +from usawa.balance import Balancer logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() @@ -22,29 +22,23 @@ class TestBalancer(unittest.TestCase): self.dtreg = datetime.datetime.now() - def test_translate_ex(self): - o = Translator(self.uidx, 'FOO') - o.set_ex('BAR', 230000) - r = o.val('BAR', 42333) - print(r) - # def test_balancer_process(self): # dst = EntryPart('FOO', 'asset', 'foo', 1337) # src = EntryPart('FOO', 'income', 'baz', 1337, debit=True) # entry = Entry(42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg) # entry.add_part(src) # entry.add_part(dst) -# o = Balancer(self.uidx, entry=entry, base='FOO') +# o = Balancer(self.uidx) # self.assertTrue(o.balanced()) # -# -# def test_balancer_parts_simple(self): -# o = Balancer(self.uidx, base='FOO') -# src = EntryPart('FOO', 'income', 'baz', 1337, debit=True) -# o.apply_part(src) -# dst = EntryPart('FOO', 'asset', 'foo', 1337) -# o.apply_part(dst) -# self.assertTrue(o.balanced()) + + def test_balancer_parts_simple(self): + o = Balancer(self.uidx) + src = EntryPart('FOO', 'income', 'baz', 1337, debit=True) + o.apply_part(src) + dst = EntryPart('FOO', 'asset', 'foo', 1337) + o.apply_part(dst) + self.assertTrue(o.balanced()) diff --git a/dummy/tests/unit.py b/dummy/tests/unit.py @@ -54,12 +54,11 @@ class TestUnit(unittest.TestCase): #r = self.uidx_default.val('BAR', 42333) with self.assertRaises(ValueError): self.uidx_default.set_rate('FOO', 230000) - #self.uidx_default.set_rate('BAR', 230000) r = self.uidx_default.val('BAR', 4233300) self.assertEqual(r[0], 9736) self.assertEqual(r[1], 590000) - r = self.uidx_default.set_rate('BAR', 1000000) + self.uidx_default.set_rate('BAR', 1000000) r = self.uidx_default.val('BAR', 4233300) self.assertEqual(r[0], 42333) self.assertEqual(r[1], 0) diff --git a/dummy/usawa/balance.py b/dummy/usawa/balance.py @@ -5,20 +5,50 @@ logg = logging.getLogger('usawa.balancer') class Balancer: - def __init__(self, translator): - self.tr = translator + def __init__(self, unitindex): + self.uidx = unitindex self.r = 0 + self.m = 0 def apply_part(self, part): - precision = self.uidx.get(part.unit) - mod = 10 ** (self.max_precision - precision) - amount = part.amount * mod - ex = self.ex[part.unit] / 1000000000 - amount *= ex + amount = self.uidx.val(part.unit, part.amount) fn = getattr(self, 'handle_' + part.typ) - fn(amount, part.isdebit) - logg.debug('after {} {} (ex {}) = {}'.format(part.unit, amount, ex, self.r)) + fn(amount[0], part.isdebit) + self.m += amount[1] + logg.debug('after {} {} => {} = {}'.format(part.unit, part.amount, amount[0], self.r)) + + + def balanced(self): + return self.r == 0 + + + def handle_income(self, amount, issrc=False): + if issrc: + self.r += amount + else: + self.r -= amount + + + def handle_expense(self, amount, issrc=False): + if issrc: + self.r -= amount + else: + self.r += amount + + + def handle_asset(self, amount, issrc=False): + if issrc: + self.r += amount + else: + self.r -= amount + + + def handle_liability(self, amount, issrc=False): + if issrc: + self.r -= amount + else: + self.r += amount #class Balancer: diff --git a/dummy/usawa/unit.py b/dummy/usawa/unit.py @@ -31,19 +31,20 @@ class UnitIndex: def __init__(self, base=None, precision=None): self.detail = {} self.rate = {} - self.base = base + self.base = None if base != None: if precision == None: precision = UnitIndex.default_precision self.detail = {base: precision} - self.exchange = {base: UnitIndex.default_exchange} + self.set_rate(base, UnitIndex.default_exchange) + self.base = base def clone(self): o = UnitIndex() o.detail = self.detail o.base = self.base - o.exchange = copy.copy(self.exchange) + o.rate = copy.copy(self.rate) return o @@ -51,12 +52,13 @@ class UnitIndex: if unit == self.base: raise ValueError('cannot adjust rate for base') if not isinstance(rate, float): - rate /= 1000000 + rate /= 1000000000 self.rate[unit] = rate def val(self, unit, amount): r = self.rate[unit] * amount + logg.debug('rate {} {} {}'.format(r, amount, self.rate[unit])) # TODO: embed in rate base_precision = self.detail[self.base] adj = base_precision - self.detail[unit] @@ -67,7 +69,7 @@ class UnitIndex: r *= (10 ** adj) v = int(r) - m = int((r - v) * 1000000) + m = int((r - v) * 1000000000) logg.debug('val {} -> {},{} adj {}'.format(r, v, m, adj)) return (v, m,) @@ -83,7 +85,7 @@ class UnitIndex: :param ex: The exchange rate of the unit, relative to the base unit. Default is 1000000000 (1.0). :type ex: int or float """ - def add(self, sym, precision=2, rate=1000000): + def add(self, sym, precision=2, rate=1000000000): self.detail[sym] = precision self.set_rate(sym, rate)