[Refactor] Categorizer now implements Transform
Test adapted for new interface. Exchanged manual input functionality for throwing an exception. Removed timer at transformer level.
This commit is contained in:
parent
2c7c527ea9
commit
b1de4d519a
@ -103,10 +103,11 @@ class Manager:
|
|||||||
categories = session.get(Category)
|
categories = session.get(Category)
|
||||||
tags = session.get(Tag)
|
tags = session.get(Tag)
|
||||||
|
|
||||||
null_rules = [cat.rules for cat in categories if cat.name == "null"]
|
rules = [cat.rules for cat in categories if cat.name == "null"]
|
||||||
Nullifier(null_rules).transform_inplace(uncategorized)
|
Nullifier(rules).transform_inplace(uncategorized)
|
||||||
|
|
||||||
Categorizer().rules(uncategorized, categories, tags, params[0])
|
rules = [rule for cat in categories for rule in cat.rules]
|
||||||
|
Categorizer(rules).transform_inplace(uncategorized)
|
||||||
|
|
||||||
rules = [rule for tag in tags for rule in tag.rules]
|
rules = [rule for tag in tags for rule in tag.rules]
|
||||||
Tagger(rules).transform_inplace(uncategorized)
|
Tagger(rules).transform_inplace(uncategorized)
|
||||||
|
|||||||
@ -1,78 +1,36 @@
|
|||||||
from codetiming import Timer
|
from copy import deepcopy
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
||||||
import pfbudget.db.model as t
|
from pfbudget.db.model import (
|
||||||
|
CategoryRule,
|
||||||
|
CategorySelector,
|
||||||
|
Selector_T,
|
||||||
|
Transaction,
|
||||||
|
TransactionCategory,
|
||||||
|
)
|
||||||
|
from .exceptions import TransactionCategorizedError
|
||||||
|
from .transform import Transformer
|
||||||
|
|
||||||
|
|
||||||
class Categorizer:
|
class Categorizer(Transformer):
|
||||||
options = {}
|
def __init__(self, rules: Sequence[CategoryRule]):
|
||||||
|
self.rules = rules
|
||||||
|
|
||||||
def __init__(self):
|
def transform(self, transactions: Sequence[Transaction]) -> Sequence[Transaction]:
|
||||||
self.options["null_days"] = 3
|
result = deepcopy(transactions)
|
||||||
|
self.transform_inplace(result)
|
||||||
|
|
||||||
def rules(
|
return result
|
||||||
self,
|
|
||||||
transactions: Sequence[t.BankTransaction],
|
|
||||||
categories: Sequence[t.Category],
|
|
||||||
tags: Sequence[t.Tag],
|
|
||||||
nullify: bool = True
|
|
||||||
):
|
|
||||||
"""Overarching categorization tool
|
|
||||||
|
|
||||||
Receives a list of transactions (by ref) and updates their category according
|
def transform_inplace(self, transactions: Sequence[Transaction]) -> None:
|
||||||
to the rules defined for each category
|
for rule in self.rules:
|
||||||
|
for transaction in transactions:
|
||||||
|
if transaction.category:
|
||||||
|
raise TransactionCategorizedError(transaction)
|
||||||
|
|
||||||
Args:
|
if not rule.matches(transaction):
|
||||||
transactions (Sequence[BankTransaction]): uncategorized transactions
|
continue
|
||||||
categories (Sequence[Category]): available categories
|
|
||||||
tags (Sequence[Tag]): currently available tags
|
|
||||||
"""
|
|
||||||
|
|
||||||
categories = [cat for cat in categories if cat.name != "null"]
|
transaction.category = TransactionCategory(
|
||||||
|
rule.name, CategorySelector(Selector_T.rules)
|
||||||
self._rule_based_categories(transactions, categories)
|
)
|
||||||
|
|
||||||
@Timer(name="categoryrules")
|
|
||||||
def _rule_based_categories(
|
|
||||||
self,
|
|
||||||
transactions: Sequence[t.BankTransaction],
|
|
||||||
categories: Sequence[t.Category],
|
|
||||||
):
|
|
||||||
print(f"Categorizing {len(transactions)} transactions")
|
|
||||||
d = {}
|
|
||||||
for category in [c for c in categories if c.rules]:
|
|
||||||
for rule in category.rules:
|
|
||||||
# for transaction in [t for t in transactions if not t.category]:
|
|
||||||
for transaction in [
|
|
||||||
t
|
|
||||||
for t in transactions
|
|
||||||
if not t.category or t.category.name != "null"
|
|
||||||
]:
|
|
||||||
if not rule.matches(transaction):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# passed all conditions, assign category
|
|
||||||
if transaction.category:
|
|
||||||
if transaction.category.name == category.name:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if (
|
|
||||||
input(
|
|
||||||
f"Overwrite {transaction} with {category.name}? (y/n)"
|
|
||||||
)
|
|
||||||
== "y"
|
|
||||||
):
|
|
||||||
transaction.category.name = category.name
|
|
||||||
transaction.category.selector.selector = t.Selector_T.rules
|
|
||||||
else:
|
|
||||||
transaction.category = t.TransactionCategory(
|
|
||||||
category.name, t.CategorySelector(t.Selector_T.rules)
|
|
||||||
)
|
|
||||||
|
|
||||||
if rule in d:
|
|
||||||
d[rule] += 1
|
|
||||||
else:
|
|
||||||
d[rule] = 1
|
|
||||||
|
|
||||||
for k, v in d.items():
|
|
||||||
print(f"{v}: {k}")
|
|
||||||
|
|||||||
@ -1,2 +1,6 @@
|
|||||||
class MoreThanOneMatchError(Exception):
|
class MoreThanOneMatchError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionCategorizedError(Exception):
|
||||||
|
pass
|
||||||
|
|||||||
@ -71,7 +71,7 @@ class TestTransform:
|
|||||||
assert not t.category
|
assert not t.category
|
||||||
|
|
||||||
rules.append(CategoryRule(None, None, None, None, "Bank#2", None, None, "null"))
|
rules.append(CategoryRule(None, None, None, None, "Bank#2", None, None, "null"))
|
||||||
categorizer: Transformer = Nullifier(rules)
|
categorizer = Nullifier(rules)
|
||||||
transactions = categorizer.transform(transactions)
|
transactions = categorizer.transform(transactions)
|
||||||
|
|
||||||
for t in transactions:
|
for t in transactions:
|
||||||
@ -87,7 +87,7 @@ class TestTransform:
|
|||||||
for t in transactions:
|
for t in transactions:
|
||||||
assert not t.category
|
assert not t.category
|
||||||
|
|
||||||
categorizer = Tagger(mock.tag_1.rules)
|
categorizer: Transformer = Tagger(mock.tag_1.rules)
|
||||||
transactions = categorizer.transform(transactions)
|
transactions = categorizer.transform(transactions)
|
||||||
|
|
||||||
for t in transactions:
|
for t in transactions:
|
||||||
@ -101,8 +101,8 @@ class TestTransform:
|
|||||||
for t in transactions:
|
for t in transactions:
|
||||||
assert not t.category
|
assert not t.category
|
||||||
|
|
||||||
categorizer = Categorizer()
|
categorizer: Transformer = Categorizer(mock.category1.rules)
|
||||||
categorizer.rules(transactions, [mock.category1], [])
|
transactions: Transformer = categorizer.transform(transactions)
|
||||||
|
|
||||||
for t in transactions:
|
for t in transactions:
|
||||||
assert t.category == TransactionCategory(
|
assert t.category == TransactionCategory(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user