Readds manual categorization

Also fixes a categorization bug in the Manager, in the DB client method.
This commit is contained in:
Luís Murta 2023-01-10 21:32:08 +00:00
parent 86afa99217
commit c37e7eb37c
Signed by: satprog
GPG Key ID: 169EF1BBD7049F94
5 changed files with 71 additions and 22 deletions

View File

@ -87,15 +87,10 @@ def argparser() -> argparse.ArgumentParser:
parse.add_argument("--bank", nargs=1, type=str)
parse.add_argument("--creditcard", nargs=1, type=str)
"""
Categorizing
"""
categorize = subparsers.add_parser(
"categorize",
description="Categorizes the transactions in the selected database",
parents=[universal],
)
categorize.set_defaults(op=Operation.Categorize)
# Automatic/manual categorization
categorize = subparsers.add_parser("categorize").add_subparsers(required=True)
categorize.add_parser("auto").set_defaults(op=Operation.Categorize)
categorize.add_parser("manual").set_defaults(op=Operation.ManualCategorization)
"""
Graph

View File

@ -9,6 +9,7 @@ class Operation(Enum):
Parse = auto()
Download = auto()
Categorize = auto()
ManualCategorization = auto()
Token = auto()
RequisitionId = auto()
CategoryAdd = auto()

View File

@ -10,6 +10,8 @@ from pfbudget.db.model import (
from datetime import timedelta
Transactions = list[Transaction]
class Categorizer:
options = {}
@ -17,25 +19,44 @@ class Categorizer:
def __init__(self):
self.options["null_days"] = 4
def categorize(
def rules(
self,
transactions: list[Transaction],
transactions: Transactions,
categories: list[Category],
tags: list[Tag],
):
"""Overarching categorization tool
Receives a list of transactions (by ref) and updates their category
Receives a list of transactions (by ref) and updates their category according
to the rules defined for each category
Args:
transactions (list[Transaction]): uncategorized transactions
categories (list[Category]): available categories
tags (list[Tag]): currently available tags
"""
self._nullify(transactions)
self._rule_based_categories(transactions, categories)
self._rule_based_tags(transactions, tags)
def _nullify(self, transactions: list[Transaction]):
def manual(
self,
transactions: Transactions,
categories: list[Category],
tags: list[Tag],
):
"""Manual categorization input
Args:
transactions (list[Transaction]): uncategorized transactions
categories (list[Category]): available categories
tags (list[Tag]): currently available tags
"""
self._manual(transactions)
def _nullify(self, transactions: Transactions):
count = 0
matching = []
for transaction in transactions:
@ -66,7 +87,7 @@ class Categorizer:
print(f"Nullified {count} transactions")
def _rule_based_categories(
self, transactions: list[Transaction], categories: list[Category]
self, transactions: Transactions, categories: list[Category]
):
d = {}
for category in [c for c in categories if c.rules]:
@ -81,9 +102,17 @@ class Categorizer:
continue
# passed all conditions, assign category
transaction.category = TransactionCategory(
category.name, CategorySelector(Selector.rules)
)
if transaction.category:
if (
input(f"Overwrite {transaction} with {category}? (y/n)")
== "y"
):
transaction.category.name = category.name
transaction.category.selector.selector = Selector.rules
else:
transaction.category = TransactionCategory(
category.name, CategorySelector(Selector.rules)
)
if rule in d:
d[rule] += 1
@ -93,7 +122,7 @@ class Categorizer:
for k, v in d.items():
print(f"{v}: {k}")
def _rule_based_tags(self, transactions: list[Transaction], tags: list[Tag]):
def _rule_based_tags(self, transactions: Transactions, tags: list[Tag]):
d = {}
for tag in [t for t in tags if t.rules]:
for rule in tag.rules:
@ -119,3 +148,20 @@ class Categorizer:
for k, v in d.items():
print(f"{v}: {k}")
def _manual(self, transactions: Transactions):
uncategorized = [t for t in transactions if not t.category]
print(f"{len(uncategorized)} transactions left to categorize")
for transaction in uncategorized:
while True:
category = input(f"{transaction} category: ")
if category == "quit":
return
if not category:
print("{category} doesn't exist")
continue
transaction.category = TransactionCategory(
category, CategorySelector(Selector.manual)
)
break

View File

@ -71,10 +71,17 @@ class Manager:
case Operation.Categorize:
with self.db.session() as session:
uncategorized = session.get(Transaction, ~Transaction.category)
uncategorized = session.get(Transaction, ~Transaction.category.has())
categories = session.get(Category)
tags = session.get(Tag)
Categorizer().categorize(uncategorized, categories, tags)
Categorizer().rules(uncategorized, categories, tags)
case Operation.ManualCategorization:
with self.db.session() as session:
uncategorized = session.get(Transaction, ~Transaction.category.has())
categories = session.get(Category)
tags = session.get(Tag)
Categorizer().manual(uncategorized, categories, tags)
case Operation.BankMod:
with self.db.session() as session:

View File

@ -54,11 +54,11 @@ class DbClient:
self.__session.expunge_all()
def get(self, type, column=None, values=None):
if column:
if column is not None:
if values:
stmt = select(type).where(column.in_(values))
else:
stmt = select(type).where(column.has())
stmt = select(type).where(column)
else:
stmt = select(type)