Readds manual categorization
Also fixes a categorization bug in the Manager, in the DB client method.
This commit is contained in:
parent
86afa99217
commit
c37e7eb37c
@ -87,15 +87,10 @@ def argparser() -> argparse.ArgumentParser:
|
|||||||
parse.add_argument("--bank", nargs=1, type=str)
|
parse.add_argument("--bank", nargs=1, type=str)
|
||||||
parse.add_argument("--creditcard", nargs=1, type=str)
|
parse.add_argument("--creditcard", nargs=1, type=str)
|
||||||
|
|
||||||
"""
|
# Automatic/manual categorization
|
||||||
Categorizing
|
categorize = subparsers.add_parser("categorize").add_subparsers(required=True)
|
||||||
"""
|
categorize.add_parser("auto").set_defaults(op=Operation.Categorize)
|
||||||
categorize = subparsers.add_parser(
|
categorize.add_parser("manual").set_defaults(op=Operation.ManualCategorization)
|
||||||
"categorize",
|
|
||||||
description="Categorizes the transactions in the selected database",
|
|
||||||
parents=[universal],
|
|
||||||
)
|
|
||||||
categorize.set_defaults(op=Operation.Categorize)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Graph
|
Graph
|
||||||
|
|||||||
@ -9,6 +9,7 @@ class Operation(Enum):
|
|||||||
Parse = auto()
|
Parse = auto()
|
||||||
Download = auto()
|
Download = auto()
|
||||||
Categorize = auto()
|
Categorize = auto()
|
||||||
|
ManualCategorization = auto()
|
||||||
Token = auto()
|
Token = auto()
|
||||||
RequisitionId = auto()
|
RequisitionId = auto()
|
||||||
CategoryAdd = auto()
|
CategoryAdd = auto()
|
||||||
|
|||||||
@ -10,6 +10,8 @@ from pfbudget.db.model import (
|
|||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
Transactions = list[Transaction]
|
||||||
|
|
||||||
|
|
||||||
class Categorizer:
|
class Categorizer:
|
||||||
options = {}
|
options = {}
|
||||||
@ -17,25 +19,44 @@ class Categorizer:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.options["null_days"] = 4
|
self.options["null_days"] = 4
|
||||||
|
|
||||||
def categorize(
|
def rules(
|
||||||
self,
|
self,
|
||||||
transactions: list[Transaction],
|
transactions: Transactions,
|
||||||
categories: list[Category],
|
categories: list[Category],
|
||||||
tags: list[Tag],
|
tags: list[Tag],
|
||||||
):
|
):
|
||||||
"""Overarching categorization tool
|
"""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:
|
Args:
|
||||||
transactions (list[Transaction]): uncategorized transactions
|
transactions (list[Transaction]): uncategorized transactions
|
||||||
|
categories (list[Category]): available categories
|
||||||
|
tags (list[Tag]): currently available tags
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._nullify(transactions)
|
self._nullify(transactions)
|
||||||
|
|
||||||
self._rule_based_categories(transactions, categories)
|
self._rule_based_categories(transactions, categories)
|
||||||
self._rule_based_tags(transactions, tags)
|
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
|
count = 0
|
||||||
matching = []
|
matching = []
|
||||||
for transaction in transactions:
|
for transaction in transactions:
|
||||||
@ -66,7 +87,7 @@ class Categorizer:
|
|||||||
print(f"Nullified {count} transactions")
|
print(f"Nullified {count} transactions")
|
||||||
|
|
||||||
def _rule_based_categories(
|
def _rule_based_categories(
|
||||||
self, transactions: list[Transaction], categories: list[Category]
|
self, transactions: Transactions, categories: list[Category]
|
||||||
):
|
):
|
||||||
d = {}
|
d = {}
|
||||||
for category in [c for c in categories if c.rules]:
|
for category in [c for c in categories if c.rules]:
|
||||||
@ -81,6 +102,14 @@ class Categorizer:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# passed all conditions, assign category
|
# passed all conditions, assign category
|
||||||
|
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(
|
transaction.category = TransactionCategory(
|
||||||
category.name, CategorySelector(Selector.rules)
|
category.name, CategorySelector(Selector.rules)
|
||||||
)
|
)
|
||||||
@ -93,7 +122,7 @@ class Categorizer:
|
|||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
print(f"{v}: {k}")
|
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 = {}
|
d = {}
|
||||||
for tag in [t for t in tags if t.rules]:
|
for tag in [t for t in tags if t.rules]:
|
||||||
for rule in tag.rules:
|
for rule in tag.rules:
|
||||||
@ -119,3 +148,20 @@ class Categorizer:
|
|||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
print(f"{v}: {k}")
|
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
|
||||||
|
|||||||
@ -71,10 +71,17 @@ class Manager:
|
|||||||
|
|
||||||
case Operation.Categorize:
|
case Operation.Categorize:
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
uncategorized = session.get(Transaction, ~Transaction.category)
|
uncategorized = session.get(Transaction, ~Transaction.category.has())
|
||||||
categories = session.get(Category)
|
categories = session.get(Category)
|
||||||
tags = session.get(Tag)
|
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:
|
case Operation.BankMod:
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
|
|||||||
@ -54,11 +54,11 @@ class DbClient:
|
|||||||
self.__session.expunge_all()
|
self.__session.expunge_all()
|
||||||
|
|
||||||
def get(self, type, column=None, values=None):
|
def get(self, type, column=None, values=None):
|
||||||
if column:
|
if column is not None:
|
||||||
if values:
|
if values:
|
||||||
stmt = select(type).where(column.in_(values))
|
stmt = select(type).where(column.in_(values))
|
||||||
else:
|
else:
|
||||||
stmt = select(type).where(column.has())
|
stmt = select(type).where(column)
|
||||||
else:
|
else:
|
||||||
stmt = select(type)
|
stmt = select(type)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user