Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
650 changes: 158 additions & 492 deletions .claude/skills/TransactionSyncing/CategoryRules.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion .claude/skills/TransactionSyncing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ SnapTrade activities SimpleFIN dump (bun run src/dump.ts)
### Smart Categorization

Categorization is executable and runs inside the expense adapter, so the
`category` column arrives pre-filled on every `bank_transactions` row. The rules
`category` column arrives pre-filled on every `bank_transactions` row.
Household merchants (local businesses, the daycare, the church) are private
and live in the instance as `merchant-rules.yaml`, merged into the public table
at sync time; the public table holds national brands only. The rules
live in code at `src/integrations/simplefin/categorize.py`
(`categorize_expense(text, amount)`), which is the source of truth mirroring the
human-readable `CategoryRules.md`. Keep the two in sync when adding patterns.
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ notebooks/
# User's financial profile (any location in repo tree)
fin-guru/data/user-profile.yaml
**/user-profile.yaml
**/merchant-rules.yaml
# Onboarding state and progress (may contain sensitive configuration)
.onboarding-state.json
# Setup progress tracking (user-specific)
Expand Down Expand Up @@ -170,3 +171,7 @@ reports/
# Instance notes and meeting records (household data; live in the instance directory)
/.memory/
/.dev/meeting-notes/

# graft's local graph cache: regenerable, and it mirrors pre-scrub source text.
/graft/
.claude/skills/graft/
1 change: 1 addition & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Finance Guru is a **private, single-user family-office system**. This document d
- Raw Fidelity CSVs (gitignored)
- SSNs, account numbers, and API keys (scrubbed by `src/utils/log.py:ScrubPIIProcessor` before they reach any log)
- `.env` contents
- `merchant-rules.yaml`, your household merchant names (the engine ships only national brands)
- `finance-guru-desktop/` runtime state (also gitignored)

## Security checklist (before publishing the fork)
Expand Down
11 changes: 2 additions & 9 deletions config/fidelity-mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,21 +271,14 @@
"notes": "Credit-card payoff. The spending was already captured at the merchant via the CC statement. Counting these as expenses double-counts spending. Only treat as 'spending' if you are NOT also importing CC statements."
},
"AUTO_LOAN_PAYMENT": {
"patterns": ["DIRECT DEBIT WELLS FARGO AUDRAFT"],
"patterns": ["AUDRAFT"],
"sign": "negative",
"true_spending": true,
"expense_category": "Loan Payment",
"notes": "Wells Fargo 'AUDRAFT' = auto loan payment (not overdraft). Real recurring expense — keep separate from CC pass-throughs."
"notes": "'AUDRAFT' = auto loan payment (not overdraft). Real recurring expense — keep separate from CC pass-throughs."
},
"DIRECT_DEBIT_BILL": {
"patterns": [
"DIRECT DEBIT AES STDNT LOAN",
"DIRECT DEBIT REGENT UNIVERS",
"DIRECT DEBIT BMOBNK",
"DIRECT DEBIT CPENERGY",
"DIRECT DEBIT BRAZORIA",
"DIRECT DEBIT SAFEPOINT",
"DIRECT DEBIT CLICKPAY",
"DIRECT DEBIT"
],
"sign": "negative",
Expand Down
1 change: 1 addition & 0 deletions docs-site/src/content/docs/reference/instance-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The root is the value of `FIN_GURU_DATA_ROOT` when that variable is set and non-
| `user-profile.yaml` | User profile. |
| `config.yaml` | Instance configuration. |
| `snaptrade-accounts.yaml` | [SnapTrade](https://snaptrade.com/) account-routing configuration. |
| `merchant-rules.yaml` | Household merchant patterns for expense categorization, merged into the engine's public brand table at sync time. |
| `system-context.md` | Generated system context. |
| `family_office.db` | Default family-office [SQLite](https://www.sqlite.org/) database. |
| `dividend-schedules.yaml` | Dividend schedule file. |
Expand Down
16 changes: 16 additions & 0 deletions src/cli/instance_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@
__pycache__/
"""

MERCHANT_RULES = """# Household merchant patterns for expense categorization.
#
# The engine's public table only knows national brands. Your own merchants (the
# local dry cleaner, the daycare, the church) go here so they never leave this
# directory. Each key is a category from src/integrations/simplefin/categorize.py
# and each value is a list of lowercase substrings matched against the joined
# payee and bank memo. Within a category, order does not matter; across
# categories, the engine's table order decides ties.
#
# Groceries:
# - corner market
# Family Care:
# - little sprouts daycare
"""

USER_PROFILE = """# Add instance-specific values after creating the instance.
system_ownership: {}
orientation_status: {}
Expand Down Expand Up @@ -268,6 +283,7 @@ def _build_plan(
),
PlanStep(paths.env_file, _write_text(env_content)),
PlanStep(paths.profile, _write_text(USER_PROFILE)),
PlanStep(paths.merchant_rules, _write_text(MERCHANT_RULES)),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
)
)
if not plugin_mode:
Expand Down
5 changes: 5 additions & 0 deletions src/config/instance_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ def snaptrade_accounts(self) -> Path:
"""Return the SnapTrade account-routing configuration path."""
return self.root / "snaptrade-accounts.yaml"

@property
def merchant_rules(self) -> Path:
"""Return the household merchant-rules file for expense categorization."""
return self.root / "merchant-rules.yaml"

@property
def system_context(self) -> Path:
"""Return the generated system context path."""
Expand Down
10 changes: 8 additions & 2 deletions src/integrations/refresh_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ def refresh(database_url: str | None, *, months: int = 12) -> dict[str, Any]:
Returns:
Per-source statuses and the refresh timestamp.
"""
account_config = InstancePaths.resolve().snaptrade_accounts
paths = InstancePaths.resolve()
account_config = paths.snaptrade_accounts
operations: tuple[tuple[str, Callable[[], dict[str, Any]]], ...] = (
("positions", lambda: sync_positions(account_config, database_url)),
("transactions", lambda: sync_transactions(account_config, database_url)),
("expenses", lambda: sync_expenses(database_url, months=months)),
(
"expenses",
lambda: sync_expenses(
database_url, months=months, merchant_rules=paths.merchant_rules
),
),
)
sources: list[dict[str, Any]] = []
for name, operation in operations:
Expand Down
Loading
Loading