@@ -59,7 +59,7 @@ async def _async_update_data(self) -> dict:
5959 uncategorised_count = await self ._fetch_uncategorised_count (session , user_id )
6060 categories = await self ._fetch_categories (session , user_id )
6161 budget = await self ._fetch_budget (session , user_id )
62- monthly_transactions = await self ._fetch_monthly_transactions (session , user_id )
62+ monthly_transactions , monthly_transaction_counts = await self ._fetch_monthly_transactions (session , user_id )
6363 monthly_events = await self ._fetch_monthly_events (session , user_id )
6464 except aiohttp .ClientError as err :
6565 raise UpdateFailed (
@@ -70,7 +70,7 @@ async def _async_update_data(self) -> dict:
7070 "PocketSmith API request timed out. Check your network connection."
7171 ) from err
7272
73- enriched_categories = self ._build_enriched_categories (categories , budget , monthly_transactions , monthly_events )
73+ enriched_categories = self ._build_enriched_categories (categories , budget , monthly_transactions , monthly_events , monthly_transaction_counts )
7474
7575 return {
7676 "user_id" : user_id ,
@@ -80,6 +80,7 @@ async def _async_update_data(self) -> dict:
8080 "categories" : categories ,
8181 "budget" : budget ,
8282 "monthly_transactions" : monthly_transactions ,
83+ "monthly_transaction_counts" : monthly_transaction_counts ,
8384 "monthly_events" : monthly_events ,
8485 "enriched_categories" : enriched_categories ,
8586 "forecast_last_updated" : dt_util .utcnow (),
@@ -372,8 +373,8 @@ async def _fetch_budget(self, session: aiohttp.ClientSession, user_id: int) -> l
372373 _LOGGER .debug ("Fetched budget for user %s" , user_id )
373374 return budget
374375
375- async def _fetch_monthly_transactions (self , session : aiohttp .ClientSession , user_id : int ) -> dict :
376- """Return actual spend totals grouped by category ID for the current month."""
376+ async def _fetch_monthly_transactions (self , session : aiohttp .ClientSession , user_id : int ) -> tuple [ dict , dict ] :
377+ """Return actual spend totals and transaction counts grouped by category ID for the current month."""
377378 today = date .today ()
378379 start_date = date (today .year , today .month , 1 ).isoformat ()
379380 last_day = calendar .monthrange (today .year , today .month )[1 ]
@@ -387,6 +388,7 @@ async def _fetch_monthly_transactions(self, session: aiohttp.ClientSession, user
387388 base_url = "%s/users/%s/transactions" % (_API_BASE , user_id )
388389 url = "%s?start_date=%s&end_date=%s&per_page=1000" % (base_url , start_date , end_date )
389390 totals : dict [int , float ] = {}
391+ counts : dict [int , int ] = {}
390392 transaction_count = 0
391393
392394 while url :
@@ -447,6 +449,7 @@ async def _fetch_monthly_transactions(self, session: aiohttp.ClientSession, user
447449 cat_id = category .get ("id" )
448450 amount = t .get ("amount" , 0 )
449451 totals [cat_id ] = totals .get (cat_id , 0 ) + abs (amount )
452+ counts [cat_id ] = counts .get (cat_id , 0 ) + 1
450453 transaction_count += 1
451454
452455 url = next_url
@@ -455,7 +458,7 @@ async def _fetch_monthly_transactions(self, session: aiohttp.ClientSession, user
455458 "Fetched %s monthly transactions across %s categories for user %s" ,
456459 transaction_count , len (totals ), user_id ,
457460 )
458- return totals
461+ return totals , counts
459462
460463 async def _fetch_monthly_events (self , session : aiohttp .ClientSession , user_id : int ) -> dict :
461464 """Return budgeted amount totals grouped by category ID for the current month."""
@@ -533,7 +536,7 @@ async def _fetch_monthly_events(self, session: aiohttp.ClientSession, user_id: i
533536 )
534537 return totals
535538
536- def _build_enriched_categories (self , categories : list , budget : list , monthly_transactions : dict , monthly_events : dict ) -> list :
539+ def _build_enriched_categories (self , categories : list , budget : list , monthly_transactions : dict , monthly_events : dict , monthly_transaction_counts : dict ) -> list :
537540 """Return a flat enriched list of all categories with monthly budget data.
538541
539542 For each non-transfer category:
@@ -580,6 +583,10 @@ def _flatten(cats, flat):
580583 # Actual: sourced from monthly transactions
581584 actual = monthly_transactions .get (cat_id )
582585
586+ # Default actual to 0 for categories that have budget data
587+ if actual is None and cat_id in budget_by_category :
588+ actual = 0
589+
583590 # Budgeted: depends on whether this is a bill category
584591 if cat .get ("is_bill" ):
585592 budgeted = monthly_events .get (cat_id )
@@ -623,9 +630,9 @@ def _flatten(cats, flat):
623630 b = budgeted or 0
624631 a = actual or 0
625632 over_budget = a > b
626- over_by = a - b if over_budget else None
627- remaining = b - a if not over_budget else None
628- percentage_used = (a / b * 100 ) if b > 0 else None
633+ over_by = a - b if over_budget else 0
634+ remaining = b - a if not over_budget else 0
635+ percentage_used = round (a / b * 100 , 2 ) if b > 0 else None
629636
630637 result .append ({
631638 "category_id" : cat_id ,
@@ -641,6 +648,7 @@ def _flatten(cats, flat):
641648 "over_budget" : over_budget ,
642649 "percentage_used" : percentage_used ,
643650 "currency" : currency ,
651+ "transaction_count" : monthly_transaction_counts .get (cat_id , 0 ),
644652 })
645653
646654 return result
0 commit comments