1919
2020from lunchable import LunchMoney , TransactionUpdateObject
2121from lunchable ._config .logging_config import set_up_logging
22- from lunchable .models import TransactionObject
22+ from lunchable .models import CategoriesObject , TransactionObject
2323
2424logger = logging .getLogger (__name__ )
2525
@@ -32,7 +32,7 @@ class PrimeLunch:
3232 def __init__ (
3333 self ,
3434 file_path : str | os .PathLike ,
35- time_window : int = 5 ,
35+ time_window : int = 7 ,
3636 access_token : Optional [str ] = None ,
3737 ) -> None :
3838 """
@@ -43,6 +43,7 @@ def __init__(
4343 self .transaction_map : Dict [int , TransactionObject ] = {}
4444 self .cached = False
4545 self .lunch = LunchMoney (access_token = access_token )
46+ self .categories : dict [int , CategoriesObject ] = {}
4647
4748 @staticmethod
4849 def transactions_to_df (transactions : List [TransactionObject ]) -> pd .DataFrame :
@@ -169,7 +170,7 @@ def deduplicate_matched(cls, df: pd.DataFrame) -> pd.DataFrame:
169170
170171 @classmethod
171172 def merge_transactions (
172- cls , amazon : pd .DataFrame , transactions : pd .DataFrame , time_range : int = 5
173+ cls , amazon : pd .DataFrame , transactions : pd .DataFrame , time_range : int = 7
173174 ) -> pd .DataFrame :
174175 """
175176 Merge Amazon Transactions and LunchMoney Transaction
@@ -186,16 +187,20 @@ def merge_transactions(
186187 -------
187188 pd.DataFrame
188189 """
190+ refunded_data = amazon [amazon ["refund" ] > 0 ].copy ()
191+ refunded_data ["total" ] = - refunded_data ["refund" ]
192+ refunded_data ["items" ] = "REFUND: " + refunded_data ["items" ]
193+ complete_amazon_data = pd .concat ([amazon , refunded_data ], ignore_index = True )
189194 merged_data = transactions .copy ()
190195 merged_data = merged_data .merge (
191- amazon ,
196+ complete_amazon_data ,
192197 how = "inner" ,
193198 left_on = ["amount" ],
194199 right_on = ["total" ],
195200 suffixes = (None , "_amazon" ),
196201 )
197202 merged_data ["start_date" ] = merged_data ["date_amazon" ]
198- merged_data ["end_date" ] = merged_data ["start_date " ] + datetime .timedelta (
203+ merged_data ["end_date" ] = merged_data ["date_amazon " ] + datetime .timedelta (
199204 days = time_range
200205 )
201206 merged_data .query (
@@ -232,14 +237,14 @@ def cache_transactions(
232237 start_date = start_date , end_date = end_cache_date
233238 )
234239 transaction_map = {item .id : item for item in transactions }
240+ self .categories = {item .id : item for item in self .lunch .get_categories ()}
235241 self .transaction_map = transaction_map
236242 self .cached = True
237243 logger .info ("%s transactions returned from LunchMoney" , len (transactions ))
238244 return transaction_map
239245
240- @classmethod
241246 def print_transaction (
242- cls , transaction : TransactionObject , former_transaction : TransactionObject
247+ self , transaction : TransactionObject , former_transaction : TransactionObject
243248 ) -> None :
244249 """
245250 Print a Transaction for interactive input
@@ -250,8 +255,12 @@ def print_transaction(
250255 transaction_table .add_row ("🏦 Payee" , former_transaction .payee )
251256 transaction_table .add_row ("📅 Date" , str (former_transaction .date ))
252257 transaction_table .add_row (
253- "💰 Amount" , "$" + str ( round ( former_transaction .amount , 2 ) )
258+ "💰 Amount" , self . format_currency ( amount = former_transaction .amount )
254259 )
260+ if former_transaction .category_id is not None :
261+ transaction_table .add_row (
262+ "📊 Category" , self .categories [former_transaction .category_id ].name
263+ )
255264 if (
256265 former_transaction .original_name is not None
257266 and former_transaction .original_name != former_transaction .payee
@@ -329,7 +338,9 @@ def process_transactions(self, confirm: bool = True):
329338 )
330339 amazon_transaction_df = self .filter_amazon_transactions (df = transaction_df )
331340 merged_data = self .merge_transactions (
332- transactions = amazon_transaction_df , amazon = amazon_df , time_range = 5
341+ transactions = amazon_transaction_df ,
342+ amazon = amazon_df ,
343+ time_range = self .time_window ,
333344 )
334345 updated_transactions = self .df_to_transactions (df = merged_data )
335346 responses = []
@@ -339,6 +350,26 @@ def process_transactions(self, confirm: bool = True):
339350 responses .append (resp )
340351 logger .info ("%s LunchMoney transactions updated" , len (responses ))
341352
353+ @staticmethod
354+ def format_currency (amount : float ) -> str :
355+ """
356+ Format currency amounts to be pleasant and human readable
357+
358+ Parameters
359+ ----------
360+ amount: float
361+ Float Amount to be converted into a string
362+
363+ Returns
364+ -------
365+ str
366+ """
367+ if amount < 0 :
368+ float_string = "[bold red]$ ({:,.2f})[/bold red]" .format (float (abs (amount )))
369+ else :
370+ float_string = "[bold green]$ {:,.2f}[/bold green]" .format (float (amount ))
371+ return float_string
372+
342373
343374@click .command ("run" )
344375@click .option (
@@ -356,7 +387,7 @@ def process_transactions(self, confirm: bool = True):
356387 type = click .INT ,
357388 help = "Allowable time window between Amazon transaction date and "
358389 "credit card transaction date" ,
359- default = 5 ,
390+ default = 7 ,
360391)
361392@click .option (
362393 "-a" ,
0 commit comments