# 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