Skip to content

Commit 711780a

Browse files
wesmclaude
andauthored
fix: Update stats for multi-level drill-down (#62)
The transaction count and in/out totals in the top-right stats bar now correctly reflect all drill-down filters in multi-level navigation. Previously, drilling from e.g. "Amazon > Groceries" would still show stats for all Amazon transactions. The bug was in get_filtered_df() which used an elif chain that only applied the first filter. Changed to sequential if statements to apply all dimension filters. Also updated CLAUDE.md to explicitly prohibit git pull without permission. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5b9292e commit 711780a

File tree

3 files changed

+104
-5
lines changed

3 files changed

+104
-5
lines changed

CLAUDE.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
## CRITICAL: Git Branch Management for AI Assistants
44

5-
**⚠️ NEVER change git branches or create new branches without explicit user permission.**
5+
**⚠️ NEVER change git branches, pull, or create new branches without explicit user permission.**
66

77
-**ALWAYS ask before** `git checkout <branch>`
8+
-**ALWAYS ask before** `git pull` or `git fetch` followed by merge/rebase
89
-**ALWAYS ask before** creating new branches
910
-**Stay on the branch the user checked out** unless they explicitly ask you to switch
1011
-**NEVER run `git checkout` on your own**
12+
-**NEVER run `git pull` on your own**
1113
-**NEVER create branches autonomously**
14+
-**NEVER switch branches when starting a new task** - the user has already set up the branch
1215

1316
**If you need to work on a different branch**, ask the user first:
1417
- "Should I switch to branch X to work on this?"
1518
- "Should I create a new branch for this feature?"
1619

20+
**When the user says a PR is merged**, do NOT automatically checkout main and pull. Wait for explicit instructions.
21+
1722
## CRITICAL: Personal Data Protection
1823

1924
**⚠️ NEVER include user's personal data in code, comments, or documentation.**

moneyflow/state.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -788,15 +788,15 @@ def get_filtered_df(self) -> Optional[pl.DataFrame]:
788788
if self.selected_time_day is not None:
789789
df = df.filter(pl.col("date").dt.day() == self.selected_time_day)
790790

791-
# Apply view-specific filters
791+
# Apply view-specific filters (can have multiple levels in multi-level drill-down)
792792
if self.view_mode == ViewMode.DETAIL:
793793
if self.selected_merchant:
794794
df = df.filter(pl.col("merchant") == self.selected_merchant)
795-
elif self.selected_category:
795+
if self.selected_category:
796796
df = df.filter(pl.col("category") == self.selected_category)
797-
elif self.selected_group:
797+
if self.selected_group:
798798
df = df.filter(pl.col("group") == self.selected_group)
799-
elif self.selected_account:
799+
if self.selected_account:
800800
df = df.filter(pl.col("account") == self.selected_account)
801801

802802
return df

tests/test_state.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,100 @@ def test_get_filtered_df_combined_filters(self, app_state):
551551
assert all("Starbucks" in row["merchant"] for row in filtered.iter_rows(named=True))
552552
assert all(row["group"] != "Transfers" for row in filtered.iter_rows(named=True))
553553

554+
def test_get_filtered_df_multi_level_drill_down(self, app_state):
555+
"""Test multi-level drill-down filters all dimensions correctly.
556+
557+
Regression test: ensures stats (transaction count, in/out totals) are
558+
calculated correctly for multi-level drill-downs like "Amazon > Groceries".
559+
Previously, only the first filter was applied due to elif chain.
560+
"""
561+
data = [
562+
# Amazon Groceries transactions (should be included)
563+
{
564+
"id": "txn_1",
565+
"date": date(2024, 1, 1),
566+
"amount": -50.00,
567+
"merchant": "Amazon",
568+
"merchant_id": "merch_1",
569+
"category": "Groceries",
570+
"category_id": "cat_1",
571+
"group": "Food & Dining",
572+
"account": "Checking",
573+
"account_id": "acc_1",
574+
"notes": "",
575+
"hideFromReports": False,
576+
"pending": False,
577+
"is_recurring": False,
578+
},
579+
{
580+
"id": "txn_2",
581+
"date": date(2024, 1, 15),
582+
"amount": -75.00,
583+
"merchant": "Amazon",
584+
"merchant_id": "merch_1",
585+
"category": "Groceries",
586+
"category_id": "cat_1",
587+
"group": "Food & Dining",
588+
"account": "Checking",
589+
"account_id": "acc_1",
590+
"notes": "",
591+
"hideFromReports": False,
592+
"pending": False,
593+
"is_recurring": False,
594+
},
595+
# Amazon Electronics (excluded - wrong category)
596+
{
597+
"id": "txn_3",
598+
"date": date(2024, 1, 10),
599+
"amount": -200.00,
600+
"merchant": "Amazon",
601+
"merchant_id": "merch_1",
602+
"category": "Electronics",
603+
"category_id": "cat_2",
604+
"group": "Shopping",
605+
"account": "Checking",
606+
"account_id": "acc_1",
607+
"notes": "",
608+
"hideFromReports": False,
609+
"pending": False,
610+
"is_recurring": False,
611+
},
612+
# Target Groceries (excluded - wrong merchant)
613+
{
614+
"id": "txn_4",
615+
"date": date(2024, 1, 20),
616+
"amount": -100.00,
617+
"merchant": "Target",
618+
"merchant_id": "merch_2",
619+
"category": "Groceries",
620+
"category_id": "cat_1",
621+
"group": "Food & Dining",
622+
"account": "Checking",
623+
"account_id": "acc_1",
624+
"notes": "",
625+
"hideFromReports": False,
626+
"pending": False,
627+
"is_recurring": False,
628+
},
629+
]
630+
app_state.transactions_df = pl.DataFrame(data)
631+
632+
# Simulate multi-level drill-down: Amazon > Groceries
633+
app_state.view_mode = ViewMode.DETAIL
634+
app_state.selected_merchant = "Amazon"
635+
app_state.selected_category = "Groceries"
636+
637+
filtered = app_state.get_filtered_df()
638+
639+
# Should only get Amazon + Groceries transactions (2 out of 4)
640+
assert len(filtered) == 2
641+
assert all(row["merchant"] == "Amazon" for row in filtered.iter_rows(named=True))
642+
assert all(row["category"] == "Groceries" for row in filtered.iter_rows(named=True))
643+
644+
# Verify the total matches expected (stats calculation uses this)
645+
total = float(filtered["amount"].sum())
646+
assert total == -125.00 # -50 + -75
647+
554648

555649
class TestNavigation:
556650
"""Test navigation and drill-down functionality."""

0 commit comments

Comments
 (0)