[Refactor] Creates Tagger transformer

Move the tag rules based transformer to its own class.
Verified with unit tests.
This commit is contained in:
Luís Murta 2023-04-21 15:32:40 +01:00
parent 2843c66453
commit 2c7c527ea9
Signed by: satprog
GPG Key ID: 169EF1BBD7049F94
6 changed files with 57 additions and 34 deletions

View File

@ -29,6 +29,7 @@ from pfbudget.extract.parsers import parse_data
from pfbudget.extract.psd2 import PSD2Extractor from pfbudget.extract.psd2 import PSD2Extractor
from pfbudget.transform.categorizer import Categorizer from pfbudget.transform.categorizer import Categorizer
from pfbudget.transform.nullifier import Nullifier from pfbudget.transform.nullifier import Nullifier
from pfbudget.transform.tagger import Tagger
class Manager: class Manager:
@ -107,6 +108,9 @@ class Manager:
Categorizer().rules(uncategorized, categories, tags, params[0]) Categorizer().rules(uncategorized, categories, tags, params[0])
rules = [rule for tag in tags for rule in tag.rules]
Tagger(rules).transform_inplace(uncategorized)
case Operation.BankMod: case Operation.BankMod:
with self.db.session() as session: with self.db.session() as session:
session.update(Bank, params) session.update(Bank, params)

View File

View File

@ -31,7 +31,6 @@ class Categorizer:
categories = [cat for cat in categories if cat.name != "null"] categories = [cat for cat in categories if cat.name != "null"]
self._rule_based_categories(transactions, categories) self._rule_based_categories(transactions, categories)
self._rule_based_tags(transactions, tags)
@Timer(name="categoryrules") @Timer(name="categoryrules")
def _rule_based_categories( def _rule_based_categories(
@ -77,33 +76,3 @@ class Categorizer:
for k, v in d.items(): for k, v in d.items():
print(f"{v}: {k}") print(f"{v}: {k}")
@Timer(name="tagrules")
def _rule_based_tags(
self, transactions: Sequence[t.BankTransaction], tags: Sequence[t.Tag]
):
print(f"Tagging {len(transactions)} transactions")
d = {}
for tag in [t for t in tags if len(t.rules) > 0]:
for rule in tag.rules:
# for transaction in [t for t in transactions if not t.category]:
for transaction in [
t
for t in transactions
if tag.name not in [tag.tag for tag in t.tags]
]:
if not rule.matches(transaction):
continue
if not transaction.tags:
transaction.tags = {t.TransactionTag(tag.name)}
else:
transaction.tags.add(t.TransactionTag(tag.name))
if rule in d:
d[rule] += 1
else:
d[rule] = 1
for k, v in d.items():
print(f"{v}: {k}")

View File

@ -0,0 +1,30 @@
from copy import deepcopy
from typing import Sequence
from pfbudget.db.model import TagRule, Transaction, TransactionTag
from .transform import Transformer
class Tagger(Transformer):
def __init__(self, rules: Sequence[TagRule]):
self.rules = rules
def transform(self, transactions: Sequence[Transaction]) -> Sequence[Transaction]:
result = deepcopy(transactions)
self.transform_inplace(result)
return result
def transform_inplace(self, transactions: Sequence[Transaction]) -> None:
for rule in self.rules:
for transaction in transactions:
if rule.tag in transaction.tags:
continue
if not rule.matches(transaction):
continue
if not transaction.tags:
transaction.tags = {TransactionTag(rule.tag)}
else:
transaction.tags.add(TransactionTag(rule.tag))

View File

@ -1,11 +1,15 @@
from decimal import Decimal from decimal import Decimal
from pfbudget.db.model import Category, CategoryRule from pfbudget.db.model import Category, CategoryRule, Tag, TagRule
category_null = Category("null", None, set()) category_null = Category("null", None, set())
category_cat1 = Category( category1 = Category(
"cat#1", "cat#1",
None, None,
{CategoryRule(None, None, "desc#1", None, None, None, Decimal(0), "cat#1")}, {CategoryRule(None, None, "desc#1", None, None, None, Decimal(0), "cat#1")},
) )
tag_1 = Tag(
"tag#1", {TagRule(None, None, "desc#1", None, None, None, Decimal(0), "tag#1")}
)

View File

@ -10,9 +10,11 @@ from pfbudget.db.model import (
CategorySelector, CategorySelector,
Selector_T, Selector_T,
TransactionCategory, TransactionCategory,
TransactionTag,
) )
from pfbudget.transform.categorizer import Categorizer from pfbudget.transform.categorizer import Categorizer
from pfbudget.transform.nullifier import Nullifier from pfbudget.transform.nullifier import Nullifier
from pfbudget.transform.tagger import Tagger
from pfbudget.transform.transform import Transformer from pfbudget.transform.transform import Transformer
@ -77,6 +79,20 @@ class TestTransform:
"null", CategorySelector(Selector_T.nullifier) "null", CategorySelector(Selector_T.nullifier)
) )
def test_tagger(self):
transactions = [
BankTransaction(date(2023, 1, 1), "desc#1", Decimal("-10"), "Bank#1")
]
for t in transactions:
assert not t.category
categorizer = Tagger(mock.tag_1.rules)
transactions = categorizer.transform(transactions)
for t in transactions:
assert TransactionTag("tag#1") in t.tags
def test_categorize(self): def test_categorize(self):
transactions = [ transactions = [
BankTransaction(date(2023, 1, 1), "desc#1", Decimal("-10"), "Bank#1") BankTransaction(date(2023, 1, 1), "desc#1", Decimal("-10"), "Bank#1")
@ -86,7 +102,7 @@ class TestTransform:
assert not t.category assert not t.category
categorizer = Categorizer() categorizer = Categorizer()
categorizer.rules(transactions, [mock.category_cat1], []) categorizer.rules(transactions, [mock.category1], [])
for t in transactions: for t in transactions:
assert t.category == TransactionCategory( assert t.category == TransactionCategory(