Files
2025-11-30 08:57:54 +08:00

1811 lines
45 KiB
Markdown

# Unified Rules Guide - Categories & Labels
## Overview
Agent Smith uses a **unified YAML rule system** that handles both transaction categorization and labeling in a single, easy-to-read file.
**Key Features:**
- YAML format - Easy to read and edit
- Two-phase execution - Categories first, then labels
- Pattern matching - Regex patterns with exclusions
- Confidence scoring - 0-100% confidence for auto-apply logic
- Smart labeling - Context-aware labels (account, category, amount)
- LLM fallback - AI categorization when rules don't match
- Template system - Pre-built rule sets for common household types
## Table of Contents
1. [Quick Start](#quick-start)
2. [Rule Types](#rule-types)
3. [Execution Flow](#execution-flow)
4. [Intelligence Modes](#intelligence-modes)
5. [LLM Integration](#llm-integration)
6. [Advanced Patterns](#advanced-patterns)
7. [Best Practices](#best-practices)
8. [Operational Modes](#operational-modes)
9. [Update Strategies](#update-strategies)
10. [Template System](#template-system)
11. [Migration Guide](#migration-guide)
12. [Troubleshooting](#troubleshooting)
## Quick Start
### 1. Choose a Template
Start with a pre-built template that matches your household type:
```bash
uv run python scripts/setup/template_selector.py
```
Available templates:
- **Simple** - Single person, no shared expenses
- **Separated Families** - Divorced/separated parents with shared custody
- **Shared Household** - Couples, roommates, or families
- **Advanced** - Business owners, investors, complex finances
### 2. Customize Your Rules
Edit `data/rules.yaml` to match your specific needs:
```yaml
rules:
# Add your first category rule
- type: category
name: Coffee → Dining Out
patterns: [STARBUCKS, COSTA, CAFE]
category: Food & Dining > Dining Out
confidence: 95
# Add your first label rule
- type: label
name: Personal Coffee
when:
categories: [Dining Out]
accounts: [Personal]
labels: [Discretionary, Personal]
```
### 3. Test Your Rules
Always test before applying to real transactions:
```bash
# Dry run - preview what would happen
uv run python scripts/operations/batch_categorize.py --mode=dry_run --period=2025-11
# Validate - see what would change on existing categorizations
uv run python scripts/operations/batch_categorize.py --mode=validate --period=2025-11
# Apply - actually categorize transactions
uv run python scripts/operations/batch_categorize.py --mode=apply --period=2025-11
```
### 4. Review and Refine
Check the results and refine your rules:
```bash
# See categorization summary
/agent-smith-analyze spending --period=2025-11
# Check uncategorized transactions
/agent-smith-categorize --mode=smart --show-uncategorized
```
## Rule Types
### Category Rules
Categorize transactions based on payee patterns, amounts, and accounts.
**Full Syntax:**
```yaml
- type: category
name: Rule Name (for logging/display)
patterns: [PATTERN1, PATTERN2, PATTERN3] # OR logic
exclude_patterns: [EXCLUDE1, EXCLUDE2] # Optional
category: Category > Subcategory
confidence: 95 # 0-100%
accounts: [Account1, Account2] # Optional filter
amount_operator: ">" # Optional: >, <, >=, <=, ==, !=
amount_value: 100.00 # Required if amount_operator set
```
**Field Descriptions:**
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `type` | Yes | String | Must be "category" |
| `name` | Yes | String | Descriptive name for logs (e.g., "WOOLWORTHS → Groceries") |
| `patterns` | Yes | List[String] | Payee keywords to match (case-insensitive, OR logic) |
| `category` | Yes | String | Category to assign (can include parent: "Parent > Child") |
| `confidence` | No | Integer | Confidence score 0-100% (default: 90) |
| `exclude_patterns` | No | List[String] | Patterns to exclude from match |
| `accounts` | No | List[String] | Only match transactions in these accounts |
| `amount_operator` | No | String | Amount comparison: >, <, >=, <=, ==, != |
| `amount_value` | No | Number | Amount threshold (required if operator set) |
**Examples:**
```yaml
# Basic pattern matching
- type: category
name: WOOLWORTHS → Groceries
patterns: [WOOLWORTHS, COLES, ALDI]
category: Food & Dining > Groceries
confidence: 95
# With exclusions (exclude UBER EATS from UBER)
- type: category
name: UBER → Transport
patterns: [UBER]
exclude_patterns: [UBER EATS]
category: Transport
confidence: 90
# Account-specific rule
- type: category
name: Work Laptop Purchase
patterns: [APPLE STORE, MICROSOFT STORE]
accounts: [Work Credit Card]
category: Work > Equipment
confidence: 90
# Amount-based rule (large purchases)
- type: category
name: Large Electronics
patterns: [JB HI-FI, HARVEY NORMAN]
category: Shopping > Electronics
confidence: 85
amount_operator: ">"
amount_value: 500
```
### Label Rules
Apply labels to transactions based on their category, account, amount, or categorization status.
**Full Syntax:**
```yaml
- type: label
name: Label Rule Name
when:
categories: [Category1, Category2] # Optional (OR logic)
accounts: [Account1, Account2] # Optional (OR logic)
amount_operator: ">" # Optional
amount_value: 100.00 # Required if operator set
uncategorized: true # Optional (true to match uncategorized)
labels: [Label1, Label2, Label3]
```
**Important:** All `when` conditions must match (AND logic), but values within each list use OR logic.
**Field Descriptions:**
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `type` | Yes | String | Must be "label" |
| `name` | Yes | String | Descriptive name for logs |
| `when` | Yes | Object | Conditions that must ALL match |
| `when.categories` | No | List[String] | Match if category contains any of these (OR) |
| `when.accounts` | No | List[String] | Match if account name contains any of these (OR) |
| `when.amount_operator` | No | String | Amount comparison: >, <, >=, <=, ==, != |
| `when.amount_value` | No | Number | Amount threshold |
| `when.uncategorized` | No | Boolean | Match uncategorized transactions (true/false) |
| `labels` | Yes | List[String] | Labels to apply when conditions match |
**Examples:**
```yaml
# Category-based labeling
- type: label
name: Tax Deductible Work Expenses
when:
categories: [Work, Professional Development, Home Office]
labels: [Tax Deductible, ATO: D1]
# Account-based labeling
- type: label
name: Shared Household Expense
when:
accounts: [Shared Bills, Joint Account]
labels: [Shared Expense, Needs Reconciliation]
# Combined conditions (category AND account)
- type: label
name: Personal Coffee Spending
when:
categories: [Dining Out]
accounts: [Personal]
labels: [Discretionary, Personal]
# Amount-based labeling
- type: label
name: Large Purchase Flag
when:
amount_operator: ">"
amount_value: 500
labels: [Large Purchase, Review Required]
# Flag uncategorized transactions
- type: label
name: Needs Categorization
when:
uncategorized: true
labels: [Uncategorized, Needs Review]
# Multi-condition (category AND account AND amount)
- type: label
name: Large Shared Grocery Trip
when:
categories: [Groceries]
accounts: [Shared Bills]
amount_operator: ">"
amount_value: 200
labels: [Shared Expense, Large Purchase, Needs Approval]
```
## Execution Flow
The unified rule engine uses **two-phase execution** to ensure labels can depend on categories assigned in the same run.
### Phase 1: Categorization
1. Iterate through all category rules in order
2. For each transaction, find the FIRST matching rule
3. Apply the category and confidence score
4. **Short-circuit:** Stop at first match (no further category rules evaluated)
5. Update transaction with matched category for Phase 2
**Rule Order Matters!** Specific rules should come before general rules.
### Phase 2: Labeling
1. Using the transaction (now with category from Phase 1)
2. Check ALL label rules
3. Apply labels from EVERY matching rule (additive)
4. Deduplicate and sort labels
**All Matches Applied!** Unlike categories, ALL matching label rules are applied.
### Example Execution
Transaction: `WOOLWORTHS` in `Shared Bills` account, amount `-$127.50`
**Phase 1 - Category Rules:**
```yaml
# Rule 1 matches!
- type: category
name: WOOLWORTHS → Groceries
patterns: [WOOLWORTHS]
category: Food & Dining > Groceries
confidence: 95
# Rule 2 would also match but is NOT evaluated (short-circuit)
- type: category
name: All Food Purchases
patterns: [WOOLWORTHS, COLES, RESTAURANT]
category: Food
confidence: 80
```
**Result after Phase 1:** Category = "Food & Dining > Groceries", Confidence = 95
**Phase 2 - Label Rules:**
```yaml
# Rule 1 matches (category: Groceries, account: Shared Bills)
- type: label
name: Shared Grocery Expense
when:
categories: [Groceries]
accounts: [Shared Bills]
labels: [Shared Expense, Essential]
# Rule 2 matches (amount > 100)
- type: label
name: Large Purchase
when:
amount_operator: ">"
amount_value: 100
labels: [Large Purchase, Review]
# Rule 3 does NOT match (category doesn't contain "Dining Out")
- type: label
name: Discretionary Dining
when:
categories: [Dining Out]
labels: [Discretionary]
```
**Final Result:**
- Category: Food & Dining > Groceries
- Confidence: 95
- Labels: Essential, Large Purchase, Review, Shared Expense (sorted, deduplicated)
## Intelligence Modes
Agent Smith has three intelligence modes that control auto-apply behavior based on confidence scores.
### Conservative Mode
**Never auto-applies** - always asks user for confirmation.
```yaml
Confidence Level: ANY
Action: Ask user for approval
Use when: Learning the system, want full control
```
Example:
```
Transaction: STARBUCKS -$6.50
Rule match: "Dining Out" (95% confidence)
→ [Ask User] Apply category "Dining Out"?
[Yes] [No] [Edit]
```
### Smart Mode (Default)
**Balanced approach** - auto-applies high confidence, asks for medium, skips low.
```yaml
Confidence ≥ 90%: Auto-apply without asking
Confidence 70-89%: Ask user for approval (LLM validates first)
Confidence < 70%: Skip (don't categorize)
```
Example:
```
Transaction: UBER -$25.00
Rule match: "Transport" (95% confidence)
→ [Auto-applied] Category: Transport
Transaction: UBER MEDICAL CENTRE -$80
Rule match: "UBER → Transport" (75% confidence)
→ [LLM Validates] This looks like medical, not transport
→ [Suggests] Medical & Healthcare (90% confidence)
→ [Auto-applied] Category: Medical & Healthcare
```
### Aggressive Mode
**More permissive** - auto-applies medium-high confidence, asks for medium-low.
```yaml
Confidence ≥ 80%: Auto-apply without asking
Confidence 50-79%: Ask user for approval
Confidence < 50%: Skip (don't categorize)
```
Example:
```
Transaction: ACME WIDGETS -$245.00
Rule match: "Business Supplies" (82% confidence)
→ [Auto-applied] Category: Business Supplies
```
### Setting the Mode
**In command:**
```bash
/agent-smith-categorize --mode=smart
```
**In environment (.env):**
```bash
DEFAULT_INTELLIGENCE_MODE=smart
```
**In code:**
```python
from scripts.workflows.categorization import CategorizationWorkflow
workflow = CategorizationWorkflow(
client=client,
mode="smart" # conservative, smart, or aggressive
)
```
## LLM Integration
When rule-based categorization fails, Agent Smith falls back to AI-powered categorization using Claude.
### Fallback Categorization
When no rule matches, Agent Smith asks the LLM to suggest a category.
**Flow:**
1. No category rule matches transaction
2. Build LLM prompt with:
- Full category hierarchy
- Transaction details (payee, amount, date)
- Intelligence mode guidance
3. LLM suggests category with confidence and reasoning
4. Apply intelligence mode thresholds:
- High confidence → Auto-apply (or ask in conservative mode)
- Medium confidence → Ask user
- Low confidence → Skip
**Example:**
```
Transaction: ACME WIDGETS LTD -$245.00
No rule match
→ [LLM] Analyzing transaction...
→ [LLM] Suggests: Business Supplies (85% confidence)
Reasoning: "ACME WIDGETS appears to be a business supplier based on
naming convention and typical transaction amount."
→ [Smart Mode] 85% is above ask threshold (70%) but below auto (90%)
→ [Ask User] Apply category "Business Supplies"?
[Yes] [No] [Create Rule]
```
### Validation
Medium-confidence rule matches (70-89% in smart mode) are validated by the LLM to catch edge cases.
**Flow:**
1. Rule matches with medium confidence
2. Build validation prompt with:
- Transaction details
- Suggested category
- Rule confidence
3. LLM responds: CONFIRM or REJECT
- CONFIRM: Can upgrade confidence → auto-apply
- REJECT: Suggests alternative category
4. Apply validated result
**Example:**
```
Transaction: UBER MEDICAL CENTRE -$80
Rule match: "UBER → Transport" (75% confidence)
→ [LLM] Validating categorization...
→ [LLM] REJECT - This appears to be a medical facility, not transport
Suggests: Medical & Healthcare (90% confidence)
→ [Smart Mode] 90% ≥ auto-apply threshold
→ [Auto-applied] Category: Medical & Healthcare
```
### Learning from LLM Results
After the LLM categorizes transactions, Agent Smith offers to create rules for future use.
**Flow:**
1. LLM categorizes N transactions with same merchant
2. Detect pattern: Same payee → Same category
3. Suggest rule creation
4. User approves, edits, or declines
5. If approved: Add rule to `data/rules.yaml`
**Example:**
```
LLM categorized 12 "ACME WIDGETS" transactions as "Business Supplies"
Suggested rule:
- type: category
name: ACME WIDGETS → Business Supplies
patterns: [ACME WIDGETS]
category: Business Supplies
confidence: 90
[Create Rule] [Edit Rule] [Decline]
→ User selects [Create Rule]
→ Rule added to data/rules.yaml
→ Future ACME WIDGETS transactions auto-categorized (90% confidence)
```
## Advanced Patterns
### Cross-Category Labels
Apply the same label to multiple categories:
```yaml
# Tax deductible categories
- type: label
name: ATO Tax Deductible
when:
categories: [Work, Professional Development, Home Office, Software]
labels: [Tax Deductible, ATO: D1]
# Large purchases across all categories
- type: label
name: Large Purchase Alert
when:
amount_operator: ">"
amount_value: 500
labels: [Large Purchase, Review Required]
```
### Account-Based Workflows
Different labels for same category in different accounts:
```yaml
# Same category rule for all accounts
- type: category
name: Transport
patterns: [UBER, LYFT, TAXI]
category: Transport
confidence: 90
# Personal transport
- type: label
name: Personal Transport
when:
categories: [Transport]
accounts: [Personal]
labels: [Personal, Discretionary]
# Work transport (reimbursable)
- type: label
name: Work Transport
when:
categories: [Transport]
accounts: [Work, Personal] # Can be from either account
amount_operator: ">"
amount_value: 20 # But large amounts suggest work trips
labels: [Work Related, Reimbursable]
```
### Shared Household Expense Tracking
Track who paid for shared expenses:
```yaml
# Shared groceries
- type: category
name: Shared Groceries
patterns: [WOOLWORTHS, COLES]
accounts: [Shared Bills, Joint Account]
category: Food & Dining > Groceries
confidence: 95
- type: label
name: Shared Essential
when:
categories: [Groceries]
accounts: [Shared Bills, Joint Account]
labels: [Shared Expense, Essential, Needs Reconciliation]
# Large shared purchases need approval
- type: label
name: Needs Approval
when:
accounts: [Shared Bills, Joint Account]
amount_operator: ">"
amount_value: 150
labels: [Needs Approval, Review Required]
```
### Tax Deductible Tracking
Flag potential tax deductions with ATO codes:
```yaml
# Work-related expenses
- type: label
name: Work Deduction - D1
when:
categories: [Work, Office Supplies, Professional Development]
labels: [Tax Deductible, ATO: D1, Work-related other expenses]
# Home office expenses
- type: label
name: Home Office Deduction - D2
when:
categories: [Home Office, Internet, Phone]
labels: [Tax Deductible, ATO: D2, Home office expenses]
# Large deductions requiring substantiation
- type: label
name: Requires Receipt (>$300)
when:
labels: [Tax Deductible] # Note: This won't work! Labels can't check labels
amount_operator: ">"
amount_value: 300
labels: [Substantiation Required, Keep Receipt]
```
**Important:** Label rules cannot check for other labels. Use categories or accounts instead.
### Uncategorized Transaction Management
Flag and prioritize uncategorized transactions:
```yaml
# Flag all uncategorized
- type: label
name: Needs Categorization
when:
uncategorized: true
labels: [Uncategorized, Needs Review]
# High-priority uncategorized (large amounts)
- type: label
name: High Priority Uncategorized
when:
uncategorized: true
amount_operator: ">"
amount_value: 100
labels: [Uncategorized, High Priority, Urgent Review]
# Uncategorized in shared account
- type: label
name: Uncategorized Shared Expense
when:
uncategorized: true
accounts: [Shared Bills, Joint Account]
labels: [Uncategorized, Shared Account, Needs Approval]
```
## Best Practices
### 1. Order Rules Specific → General
Rules are evaluated in order. Put specific rules first:
```yaml
# ✓ GOOD: Specific first
- type: category
name: UBER EATS → Dining Out
patterns: [UBER EATS]
category: Food & Dining > Dining Out
confidence: 95
- type: category
name: UBER → Transport
patterns: [UBER]
category: Transport
confidence: 90
# ✗ BAD: General first (UBER catches UBER EATS)
- type: category
name: UBER → Transport
patterns: [UBER]
category: Transport
confidence: 90
- type: category
name: UBER EATS → Dining Out # Never reached!
patterns: [UBER EATS]
category: Food & Dining > Dining Out
confidence: 95
```
**Fix with exclusions:**
```yaml
- type: category
name: UBER → Transport
patterns: [UBER]
exclude_patterns: [UBER EATS]
category: Transport
confidence: 90
```
### 2. Use Visual Grouping
Group related rules with comments for easy scanning:
```yaml
# ═══════════════════════════════════════════════════════════
# GROCERIES WORKFLOW
# ═══════════════════════════════════════════════════════════
- type: category
name: Groceries
patterns: [WOOLWORTHS, COLES, ALDI]
category: Food & Dining > Groceries
confidence: 95
- type: label
name: Essential Spending
when:
categories: [Groceries]
labels: [Essential, Needs]
- type: label
name: Shared Groceries
when:
categories: [Groceries]
accounts: [Shared Bills]
labels: [Shared Expense, Reconciliation]
# ═══════════════════════════════════════════════════════════
# TRANSPORT WORKFLOW
# ═══════════════════════════════════════════════════════════
- type: category
name: Rideshare
patterns: [UBER, LYFT]
exclude_patterns: [UBER EATS]
category: Transport
confidence: 90
```
### 3. Start with High Confidence
Begin with rules you're certain about (95%+):
```yaml
# High confidence - very specific merchants
- type: category
name: WOOLWORTHS → Groceries
patterns: [WOOLWORTHS]
category: Food & Dining > Groceries
confidence: 95
- type: category
name: AGL → Utilities
patterns: [AGL]
category: Bills > Utilities
confidence: 95
```
Add medium-confidence rules (80-90%) later as you verify:
```yaml
# Medium confidence - could be ambiguous
- type: category
name: Amazon Purchases
patterns: [AMAZON]
category: Shopping
confidence: 80 # Could be books, electronics, groceries, etc.
```
### 4. Test with Dry Run
Always test before applying to real transactions:
```bash
# Preview what would happen without making changes
uv run python scripts/operations/batch_categorize.py \
--mode=dry_run \
--period=2025-11 \
--limit=50
# See what would change on existing categorizations
uv run python scripts/operations/batch_categorize.py \
--mode=validate \
--period=2025-11
```
Review the output carefully before running with `--mode=apply`.
### 5. Version Control Your Rules
Commit `data/rules.yaml` to git to track evolution:
```bash
# After adding/modifying rules
git add data/rules.yaml
git commit -m "rules: add coffee shop categorization with personal label"
# View history
git log --oneline data/rules.yaml
# Compare versions
git diff HEAD~1 data/rules.yaml
```
### 6. Review Rule Performance Regularly
Check rule accuracy monthly:
```bash
# Analyze categorization coverage
/agent-smith-analyze rules --period=last-month
# See which rules are matching most often
/agent-smith-analyze rules --sort=matches
# Find low-accuracy rules
/agent-smith-analyze rules --min-accuracy=80
```
Refine rules that have low accuracy or aren't matching as expected.
### 7. Use Templates as Starting Points
Don't start from scratch - use a template:
```bash
uv run python scripts/setup/template_selector.py
```
Then customize by:
1. Updating merchant names for your region (e.g., WOOLWORTHS → KROGER)
2. Adjusting account names to match your PocketSmith setup
3. Adding your specific categories
4. Fine-tuning confidence scores based on your data
### 8. Document Complex Rules
Add comments explaining non-obvious rules:
```yaml
# Complex rule: UBER is transport UNLESS it's UBER EATS or during work hours
# Work hours trips from Personal account are likely work-related (reimbursable)
- type: category
name: UBER Transport (Excluding Food Delivery)
patterns: [UBER]
exclude_patterns: [UBER EATS, UBER EATS MARKETPLACE]
category: Transport
confidence: 90
# Note: Work-related UBER trips need manual review for reimbursement
# They'll get the "Reimbursable" label from the account-based rule below
```
## Operational Modes
The batch processor supports three operational modes for safe rule testing and application.
### DRY_RUN Mode
**Purpose:** Preview what would happen without making any changes.
**Use when:**
- Testing new rules
- Checking rule coverage
- Seeing potential categorizations before committing
**Example:**
```bash
uv run python scripts/operations/batch_categorize.py \
--mode=dry_run \
--period=2025-11
```
**Output:**
```
DRY RUN MODE - No changes will be made
Transaction #12345: WOOLWORTHS -$127.50
→ Would categorize as: Food & Dining > Groceries (95% confidence)
→ Would apply labels: [Essential, Shared Expense]
Transaction #12346: STARBUCKS -$6.50
→ Would categorize as: Food & Dining > Dining Out (90% confidence)
→ Would apply labels: [Discretionary, Personal]
Transaction #12347: ACME WIDGETS -$245.00
→ No rule match
→ Would request LLM categorization
Summary:
Would categorize: 2/3 transactions (66.7%)
LLM fallback needed: 1 transaction
No changes made (DRY RUN)
```
### VALIDATE Mode
**Purpose:** Show what would CHANGE on transactions that already have categories.
**Use when:**
- Checking if new rules conflict with existing categorizations
- Planning to update categories with better rules
- Auditing categorization accuracy
**Example:**
```bash
uv run python scripts/operations/batch_categorize.py \
--mode=validate \
--period=2025-11
```
**Output:**
```
VALIDATE MODE - Showing potential changes
Transaction #12345: WOOLWORTHS -$127.50
Current: Food (80% confidence)
New: Food & Dining > Groceries (95% confidence)
Change: Category would be updated ✓
Transaction #12346: STARBUCKS -$6.50
Current: Food & Dining > Dining Out (90% confidence)
New: Food & Dining > Dining Out (90% confidence)
Change: No change (same category)
Transaction #12347: UBER -$25.00
Current: Dining Out (user-assigned)
New: Transport (90% confidence from rule)
Change: Category would be REPLACED (was user-assigned!)
Summary:
Would update: 2 transactions
Already correct: 1 transaction
Would replace user assignments: 1 transaction ⚠️
No changes made (VALIDATE)
```
### APPLY Mode
**Purpose:** Actually apply categorizations and labels to transactions.
**Use when:**
- Ready to commit changes after testing with DRY_RUN/VALIDATE
- Processing new uncategorized transactions
- Updating categorizations with improved rules
**Example:**
```bash
uv run python scripts/operations/batch_categorize.py \
--mode=apply \
--period=2025-11 \
--update-strategy=skip_existing
```
**Output:**
```
APPLY MODE - Making changes to PocketSmith
Transaction #12345: WOOLWORTHS -$127.50
✓ Categorized as: Food & Dining > Groceries (95%)
✓ Labels applied: [Essential, Shared Expense]
Transaction #12346: STARBUCKS -$6.50
⊘ Skipped (already categorized)
Transaction #12347: ACME WIDGETS -$245.00
→ Requesting LLM categorization...
? Suggested: Business Supplies (85% confidence)
[A]ccept [E]dit [S]kip [C]reate Rule
```
## Update Strategies
Control how the batch processor handles transactions that already have categories.
### SKIP_EXISTING (Default)
Only process uncategorized transactions. Leave existing categorizations unchanged.
**Use when:**
- Processing new transactions
- Don't want to override user-assigned categories
- Preserving manual categorization work
```bash
uv run python scripts/operations/batch_categorize.py \
--mode=apply \
--update-strategy=skip_existing
```
**Behavior:**
- Uncategorized → Apply rules
- Already categorized → Skip
- User-assigned → Skip
### REPLACE_ALL
Replace ALL categorizations, even if they were user-assigned.
**Use when:**
- Rebuilding all categorizations from scratch
- Confident new rules are better than old assignments
- Fixing systemic categorization errors
**⚠️ Warning:** This will override user-assigned categories!
```bash
uv run python scripts/operations/batch_categorize.py \
--mode=apply \
--update-strategy=replace_all
```
**Behavior:**
- Uncategorized → Apply rules
- Already categorized → Replace with rule result
- User-assigned → Replace with rule result (loses user intent!)
### UPGRADE_CONFIDENCE
Replace categorization ONLY if new rule has higher confidence.
**Use when:**
- Improving categorizations with better rules
- Keeping high-confidence assignments
- Upgrading low-confidence auto-categorizations
```bash
uv run python scripts/operations/batch_categorize.py \
--mode=apply \
--update-strategy=upgrade_confidence
```
**Behavior:**
- Uncategorized → Apply rules
- Lower confidence → Replace with higher confidence rule
- Higher confidence → Keep existing
- User-assigned (confidence: 100%) → Never replaced
**Example:**
```
Transaction: WOOLWORTHS -$50
Current: Food (80% confidence from old rule)
New: Food & Dining > Groceries (95% confidence from new rule)
→ REPLACED (95% > 80%)
Transaction: STARBUCKS -$6
Current: Dining Out (95% confidence)
New: Dining Out (90% confidence from new rule)
→ KEPT (95% > 90%)
```
### REPLACE_IF_DIFFERENT
Replace categorization if the category NAME differs.
**Use when:**
- Fixing miscategorized transactions
- Migrating to a new category structure
- Correcting category hierarchies
```bash
uv run python scripts/operations/batch_categorize.py \
--mode=apply \
--update-strategy=replace_if_different
```
**Behavior:**
- Uncategorized → Apply rules
- Same category → Keep existing
- Different category → Replace with rule result
- User-assigned → Still replaced if different!
**Example:**
```
Transaction: WOOLWORTHS -$50
Current: Food
New: Food & Dining > Groceries
→ REPLACED (different category name)
Transaction: STARBUCKS -$6
Current: Dining Out
New: Dining Out
→ KEPT (same category)
```
## Template System
Agent Smith provides pre-built rule templates for common household types. Templates are stored in `data/templates/` and can be applied to create your `data/rules.yaml`.
### Available Templates
#### 1. Simple - Single Person
**File:** `data/templates/simple.yaml`
**Best for:**
- Single person households
- No shared expenses
- Basic income and expense tracking
**Includes:**
- Income categorization (salary, wages)
- Essential expenses (groceries, utilities, rent)
- Discretionary spending (dining out, entertainment)
- Transport categories
- Basic labels (Essential, Discretionary, Large Purchase)
- Uncategorized flagging
**Example rules:**
```yaml
# Income
- type: category
patterns: [SALARY, WAGES, EMPLOYER]
category: Income > Salary
confidence: 95
# Essential groceries
- type: category
patterns: [WOOLWORTHS, COLES, ALDI]
category: Food & Dining > Groceries
confidence: 95
- type: label
when:
categories: [Groceries, Utilities, Rent]
labels: [Essential]
```
#### 2. Separated Families
**File:** `data/templates/separated-families.yaml`
**Best for:**
- Divorced or separated parents
- Shared custody arrangements
- Child support tracking
- Kids' expense management
**Includes:**
- Kids' expense categories (school, activities, clothing, medical)
- Child support tracking
- Contributor labels (Parent A, Parent B)
- Reimbursement workflows
- School term and vacation labels
- Medical and education receipts flagging
**Example rules:**
```yaml
# Child expenses
- type: category
patterns: [SCHOOL, UNIFORM, SCHOOL FEES]
category: Kids > Education
confidence: 90
- type: label
when:
categories: [Kids]
labels: [Child Expense, Needs Documentation]
# Child support tracking
- type: label
when:
patterns: [CHILD SUPPORT]
labels: [Child Support, Parent B Contribution]
# Shared kid expenses requiring reimbursement
- type: label
when:
categories: [Kids]
amount_operator: ">"
amount_value: 50
labels: [Needs Reimbursement, Split 50/50]
```
#### 3. Shared Household
**File:** `data/templates/shared-household.yaml`
**Best for:**
- Couples living together
- Roommates sharing expenses
- Families with joint accounts
**Includes:**
- Shared vs personal expense separation
- Contributor tracking (Person A, Person B)
- Approval workflows (large purchases, discretionary spending)
- Reconciliation labels
- Essential vs discretionary labeling
- Account-based routing (Shared Bills, Personal accounts)
**Example rules:**
```yaml
# Shared essential expenses
- type: category
patterns: [WOOLWORTHS, COLES]
accounts: [Shared Bills, Joint Account]
category: Food & Dining > Groceries
confidence: 95
- type: label
when:
categories: [Groceries]
accounts: [Shared Bills]
labels: [Shared Expense, Essential, Monthly Reconciliation]
# Approval workflow for large shared purchases
- type: label
when:
accounts: [Shared Bills]
amount_operator: ">"
amount_value: 150
labels: [Needs Approval, Review Required]
# Personal vs shared distinction
- type: label
when:
accounts: [Personal, PersonA Account, PersonB Account]
labels: [Personal, Individual]
```
#### 4. Advanced
**File:** `data/templates/advanced.yaml`
**Best for:**
- Business owners
- Investors and traders
- Complex financial situations
- Tax optimization focus
**Includes:**
- Business expense categories (with ATO codes)
- Investment tracking (shares, crypto, property)
- Tax deductible flagging (work, home office, professional development)
- Capital gains tracking
- Substantiation requirements ($300 threshold)
- Instant asset write-off flagging
- GST tracking
- Business vs personal separation
**Example rules:**
```yaml
# Business expenses
- type: category
patterns: [OFFICE, STATIONERY, SUPPLIES]
accounts: [Business, Work]
category: Work > Office Supplies
confidence: 90
- type: label
when:
categories: [Work, Home Office, Professional Development]
labels: [Tax Deductible, ATO: D1, Business Expense]
# Investment purchases
- type: category
patterns: [COMMSEC, SELFWEALTH, STAKE]
category: Investments > Share Purchase
confidence: 90
- type: label
when:
categories: [Investments]
labels: [CGT Event, Track Cost Base]
# Substantiation requirements
- type: label
when:
labels: [Tax Deductible]
amount_operator: ">"
amount_value: 300
labels: [Receipt Required, ATO Substantiation]
```
### Applying a Template
**Interactive selection:**
```bash
uv run python scripts/setup/template_selector.py
```
**Output:**
```
══════════════════════════════════════════════════════════════════
Agent Smith - Rule Template Setup
══════════════════════════════════════════════════════════════════
Available templates:
1. Simple - Single Person
Basic categories for individual financial tracking
Best for: Single person, no shared expenses
2. Separated Families
Kids expenses, child support, contributor tracking
Best for: Divorced/separated parents with shared custody
3. Shared Household
Shared expense tracking with approval workflows
Best for: Couples, roommates, or families
4. Advanced
Tax optimization and investment management
Best for: Business owners, investors, complex finances
Select template (1-4): 3
Applying template: Shared Household
Backed up existing rules to data/rules.yaml.backup
✓ Template applied successfully!
Next steps:
1. Review data/rules.yaml and customize for your needs
2. Update merchant patterns for your region
3. Adjust account names to match your PocketSmith setup
4. Run: /agent-smith-categorize --mode=dry-run to test
```
**Programmatic usage:**
```python
from scripts.setup.template_selector import TemplateSelector
selector = TemplateSelector()
# List templates
templates = selector.list_templates()
for key, info in templates.items():
print(f"{info['name']}: {info['description']}")
# Apply template
selector.apply_template("shared-household", backup=True)
```
### Customizing Templates
After applying a template:
1. **Update merchant patterns** for your region:
```yaml
# Template (Australian)
patterns: [WOOLWORTHS, COLES, ALDI]
# Customize (US)
patterns: [KROGER, SAFEWAY, WHOLE FOODS]
```
2. **Adjust account names** to match your PocketSmith:
```yaml
# Template
accounts: [Shared Bills, Joint Account]
# Your setup
accounts: [Joint Checking, Household Card]
```
3. **Add your specific categories**:
```yaml
# Add new rules
- type: category
name: Pet Expenses
patterns: [VET, PET STORE, PETBARN]
category: Pets > Veterinary
confidence: 90
```
4. **Fine-tune confidence scores** based on your data:
```yaml
# Start conservative
confidence: 70
# After validation, increase
confidence: 90
```
## Migration Guide
### From Platform Rules to Unified YAML
If you have existing platform rules created via the PocketSmith API, you can migrate them to the unified YAML format.
**See:** [Platform to Local Rules Migration Guide](platform-to-local-migration.md)
**Quick summary:**
1. Export platform rules to JSON
2. Convert to unified YAML format
3. Test with dry run
4. Disable platform rules (keep for backup)
5. Enable unified rules
**Migration script:**
```bash
uv run python scripts/migrations/migrate_platform_to_local.py \
--output=data/rules.yaml \
--backup
```
### Adding Labels to Existing Rules
If you have category rules and want to add labels:
1. Keep all existing category rules as-is
2. Add label rules at the bottom
3. Test with dry run to see labels applied
4. Apply with `--update-strategy=skip_existing` to avoid re-categorizing
**Example:**
```yaml
# Existing category rules (don't change)
- type: category
name: WOOLWORTHS → Groceries
patterns: [WOOLWORTHS]
category: Groceries
confidence: 95
# NEW: Add label rules
- type: label
name: Essential Spending
when:
categories: [Groceries]
labels: [Essential, Needs]
- type: label
name: Large Grocery Trip
when:
categories: [Groceries]
amount_operator: ">"
amount_value: 150
labels: [Large Purchase]
```
Run with:
```bash
uv run python scripts/operations/batch_categorize.py \
--mode=apply \
--update-strategy=skip_existing \
--period=2025-11
```
This will:
- Skip already categorized transactions (no re-categorization)
- Apply new labels to all transactions (even already categorized ones)
## Troubleshooting
### Rule Not Matching
**Symptom:** Rule should match but doesn't.
**Check:**
1. **Pattern case sensitivity** - Patterns are case-insensitive, but spacing matters:
```yaml
# Won't match "UBEREATS" (no space)
patterns: [UBER EATS]
# Better: account for variations
patterns: [UBER EATS, UBEREATS]
```
2. **Exclusion patterns blocking** - Check if an exclusion is preventing the match:
```yaml
- type: category
patterns: [UBER]
exclude_patterns: [UBER EATS, MEDICAL] # Blocks "UBER MEDICAL"
category: Transport
```
3. **Account filter too restrictive** - Transaction might be in a different account:
```yaml
# Only matches transactions in "Personal" account
accounts: [Personal]
# Check transaction's actual account name
```
4. **Amount condition incorrect** - Verify the amount operator and value:
```yaml
amount_operator: ">"
amount_value: 100
# Won't match transactions ≤ $100
```
5. **Rule order** - A previous rule might have matched first (short-circuit):
```yaml
# General rule matches first!
- patterns: [UBER]
category: Transport
# Specific rule never reached
- patterns: [UBER EATS]
category: Dining Out # Dead code!
```
**Debug with test script:**
```bash
# Test specific payee
uv run python scripts/operations/test_rules.py \
--payee="EXACT PAYEE NAME" \
--account="Account Name" \
--amount=127.50 \
--debug
```
**Output:**
```
Testing transaction:
Payee: EXACT PAYEE NAME
Account: Account Name
Amount: $127.50
Checking category rules...
✗ Rule 1 "WOOLWORTHS → Groceries": Pattern mismatch
✗ Rule 2 "UBER → Transport": Pattern mismatch
✗ Rule 3 "CAFE → Dining Out": Pattern mismatch
No category match found.
Checking label rules...
(skipped - no category assigned)
Result: No categorization
```
### Multiple Rules Matching
**Symptom:** Worried about multiple rules matching the same transaction.
**This is expected!** Category rules use short-circuit (first match wins), label rules accumulate all matches.
**For category rules:**
```yaml
# Only the FIRST matching rule applies
- patterns: [UBER EATS]
category: Dining Out
# ✓ This matches UBER EATS
- patterns: [UBER]
category: Transport
# ✗ Never reached for UBER EATS (already matched above)
```
**For label rules:**
```yaml
# ALL matching rules apply (additive)
- type: label
when:
categories: [Groceries]
labels: [Essential]
# ✓ Matches
- type: label
when:
amount_operator: ">"
amount_value: 100
labels: [Large Purchase]
# ✓ Also matches
# Result: [Essential, Large Purchase]
```
**Fix unwanted category matches** by adjusting rule order or using exclusions.
**Control label accumulation** by making conditions more specific:
```yaml
# Too broad - applies to ALL transactions
- type: label
when:
amount_operator: ">"
amount_value: 0
labels: [Has Amount] # Not useful!
# Better - specific categories only
- type: label
when:
categories: [Groceries, Dining Out]
amount_operator: ">"
amount_value: 100
labels: [Large Food Purchase]
```
### Labels Not Applying
**Symptom:** Label rule should match but labels aren't applied.
**Check:**
1. **Category must be assigned first** - Labels depend on Phase 1 categorization:
```yaml
# Label requires category "Groceries"
- type: label
when:
categories: [Groceries]
labels: [Essential]
# But transaction wasn't categorized in Phase 1
# → Label rule won't match
```
**Fix:** Ensure a category rule matches the transaction first.
2. **When conditions too restrictive** - All conditions must match (AND logic):
```yaml
- type: label
when:
categories: [Groceries] # Must match
accounts: [Shared Bills] # AND must match
amount_operator: ">" # AND must match
amount_value: 100
labels: [Large Shared Grocery]
# Won't match if ANY condition fails
```
3. **Uncategorized flag incorrect** - Can't combine with other conditions:
```yaml
# This won't work as expected
- type: label
when:
uncategorized: true
categories: [Groceries] # Contradiction! Can't be both uncategorized and have a category
labels: [Invalid]
```
**Fix:** Use `uncategorized: true` alone or with accounts/amount only.
4. **Labels can't check labels** - You can't reference other labels in conditions:
```yaml
# This WON'T work - no way to check existing labels
- type: label
when:
labels: [Tax Deductible] # Not supported!
amount_operator: ">"
amount_value: 300
labels: [Substantiation Required]
```
**Fix:** Use categories or accounts as conditions instead.
**Debug:**
```bash
uv run python scripts/operations/test_rules.py \
--payee="WOOLWORTHS" \
--account="Shared Bills" \
--amount=127.50 \
--category="Groceries" \
--debug
```
### Confidence Scores Unclear
**Symptom:** Not sure what confidence score to use.
**Guidelines:**
| Confidence | When to Use | Example |
|------------|-------------|---------|
| 95-100% | Exact merchant match, no ambiguity | WOOLWORTHS → Groceries |
| 85-94% | Very likely but minor ambiguity | AMAZON → Shopping (could be many subcategories) |
| 75-84% | Likely but context-dependent | UBER → Transport (unless UBER EATS) |
| 70-74% | Moderate confidence, needs validation | Generic patterns like "MARKET" |
| < 70% | Low confidence, probably shouldn't auto-apply | Broad patterns |
**Smart mode thresholds:**
- ≥ 90%: Auto-apply
- 70-89%: Ask user (with LLM validation)
- < 70%: Skip
**Start high (95%), reduce if:**
- LLM frequently suggests different category
- User frequently overrides
- Pattern matches too broadly
### LLM Not Being Used
**Symptom:** Expected LLM fallback but it's not happening.
**Possible causes:**
1. **Rule matched** - LLM only used when NO rule matches:
```
Transaction: ACME WIDGETS
Rule match: "Generic Business" pattern [WIDGETS] (75%)
→ Rule applied, LLM not needed
```
**Fix:** Remove overly broad rules if you want LLM to handle edge cases.
2. **Categories not provided** - LLM needs category list:
```python
workflow.categorize_transaction(
transaction=txn,
available_categories=None # ← LLM can't suggest without categories!
)
```
**Fix:** Pass `available_categories` from PocketSmith API.
3. **Conservative mode + low confidence** - Conservative never auto-applies:
```
Mode: Conservative
LLM suggests: Business Supplies (85%)
→ Asks user (doesn't auto-apply)
```
This is expected! Conservative always asks.
### Performance Issues
**Symptom:** Batch categorization is slow with many rules.
**Optimizations:**
1. **Reduce rule count** - Consolidate similar patterns:
```yaml
# Before: 3 rules
- patterns: [WOOLWORTHS]
category: Groceries
- patterns: [COLES]
category: Groceries
- patterns: [ALDI]
category: Groceries
# After: 1 rule
- patterns: [WOOLWORTHS, COLES, ALDI]
category: Groceries
```
2. **Use account filters** - Skip irrelevant transactions early:
```yaml
# Check account BEFORE pattern matching
- patterns: [WORK PATTERN]
accounts: [Work Credit Card] # Skips 90% of transactions
category: Work Expenses
```
3. **Order by frequency** - Put most common rules first:
```yaml
# Most frequent transaction (groceries) - check first
- patterns: [WOOLWORTHS, COLES]
category: Groceries
# Less frequent - check later
- patterns: [RARE MERCHANT]
category: Rare Category
```
4. **Limit batch size** - Process in smaller chunks:
```bash
# Instead of processing all at once
uv run python scripts/operations/batch_categorize.py --period=2025
# Process month by month
uv run python scripts/operations/batch_categorize.py --period=2025-01
uv run python scripts/operations/batch_categorize.py --period=2025-02
# etc.
```
## Examples
See `docs/examples/` for complete example YAML files:
- **basic-rules.yaml** - Simple category and label rules
- **advanced-patterns.yaml** - Complex rules with exclusions, amounts, accounts
- **household-workflow.yaml** - Complete shared household setup
- **tax-deductible.yaml** - Tax optimization rules with ATO codes
- **migration-example.yaml** - Migrated from platform rules
## Further Reading
- **[Platform to Local Migration Guide](platform-to-local-migration.md)** - Migrate existing platform rules to unified YAML
- **[Backup and Restore Guide](backup-and-restore-guide.md)** - Smart backup system with activity-specific rollbacks
- **[Health Check Guide](health-check-guide.md)** - PocketSmith setup health evaluation
- **[Design Document](../design/2025-11-20-agent-smith-design.md)** - Complete Agent Smith architecture
## Support
For questions or issues:
1. Check this guide's troubleshooting section
2. Review example files in `docs/examples/`
3. Check template files in `data/templates/`
4. Refer to design documentation
5. Create an issue in the repository
---
**Last Updated:** 2025-11-22
**Version:** 1.0.0