{"id":"27fbcdf2-0332-4865-86d5-288e97e3dacd","entityType":"agent","slug":"clawhub-patello-financial-categorizer","name":"financial-categorizer","canonicalUrl":"https://www.xpersona.co/agent/clawhub-patello-financial-categorizer","canonicalPath":"/agent/clawhub-patello-financial-categorizer","generatedAt":"2026-10-09T21:52:53.659Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-09T17:24:50.367Z","emptyReason":null},"description":"Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views. Skill: financial-categorizer Owner: patello Summary: Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views. Tags: latest:1.16.0 Version history: v1.16.0 | 2026-10-04T11:08:52.434Z | user - fix: keep genuinely identical transactions instead of collapsing them v1.15.0 | 2026-09-05T20:09:18.744Z | user -","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 2.2K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s178t29f61bmma8whfnmacavhx83eknq:financial-categorizer","sourceUrl":"https://clawhub.ai/patello/financial-categorizer","homepage":"https://clawhub.ai/patello/skills/financial-categorizer","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/patello/financial-categorizer","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/patello/skills/financial-categorizer","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":67,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical dat"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T17:24:50.367Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T17:24:50.367Z","emptyReason":null},"stars":null,"forks":null,"downloads":2221,"packageName":null,"latestVersion":"1.16.0","tractionLabel":"2.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T17:24:50.367Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T17:24:50.367Z","lastCrawledAt":"2026-10-09T17:24:50.367Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T17:24:50.367Z","lastVerifiedAt":null,"highlights":[{"version":"1.16.0","createdAt":"2026-10-04T11:08:52.434Z","changelog":"- fix: keep genuinely identical transactions instead of collapsing them","fileCount":16,"zipByteSize":85742},{"version":"1.15.0","createdAt":"2026-09-05T20:09:18.744Z","changelog":"- tune: raise inexact floor to 85%, document pending lifecycle intent - feat: cleanup-pending inexact matches and --force-id manual override - feat: inexact settlement matching and pending disappearance warnings","fileCount":16,"zipByteSize":84784},{"version":"1.14.0","createdAt":"2026-09-05T13:08:09.614Z","changelog":"- feat: cleanup-pending command to delete ghost reservations - fix: settle reservations across umlaut variants and split authorizations","fileCount":16,"zipByteSize":80144},{"version":"1.13.0","createdAt":"2026-09-03T05:40:55.120Z","changelog":"- feat: period projection tables for burn-down estimates","fileCount":15,"zipByteSize":76185},{"version":"1.12.0","createdAt":"2026-08-31T12:11:08.472Z","changelog":"- Add confirmation gates to remove-recurring and discover-recurring (ClawHub security audit); document flags in README/SKILL - Fix estimate-period actual income/expense split: GROUP BY alias collided with accounts.type","fileCount":15,"zipByteSize":73890},{"version":"1.11.0","createdAt":"2026-08-01T20:33:57.217Z","changelog":"- Add --raw-split stats mode (ownership split without reimbursement adjustments) - Fix: exclude zero-adjusted transactions from uncategorized lists","fileCount":15,"zipByteSize":73398},{"version":"1.10.0","createdAt":"2026-07-17T19:40:50.055Z","changelog":"- Fix Issue #22: Emit warning when matching transaction falls outside amount bounds - Fix Issue #24: Use current date as reference in estimate-period - fix: identify drifting ~30-day recurring payments as days interval","fileCount":15,"zipByteSize":73053},{"version":"1.9.1","createdAt":"2026-07-13T18:36:05.900Z","changelog":"- fix: declare python3 dependency in SKILL.md metadata","fileCount":15,"zipByteSize":72365}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s178t29f61bmma8whfnmacavhx83eknq:financial-categorizer","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-09T21:52:53.655Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-financial-categorizer/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-09T17:24:50.367Z","emptyReason":null},"readme":"Skill: financial-categorizer\n\nOwner: patello\n\nSummary: Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\n\nTags: latest:1.16.0\n\nVersion history:\n\nv1.16.0 | 2026-10-04T11:08:52.434Z | user\n\n- fix: keep genuinely identical transactions instead of collapsing them\n\nv1.15.0 | 2026-09-05T20:09:18.744Z | user\n\n- tune: raise inexact floor to 85%, document pending lifecycle intent\n- feat: cleanup-pending inexact matches and --force-id manual override\n- feat: inexact settlement matching and pending disappearance warnings\n\nv1.14.0 | 2026-09-05T13:08:09.614Z | user\n\n- feat: cleanup-pending command to delete ghost reservations\n- fix: settle reservations across umlaut variants and split authorizations\n\nv1.13.0 | 2026-09-03T05:40:55.120Z | user\n\n- feat: period projection tables for burn-down estimates\n\nv1.12.0 | 2026-08-31T12:11:08.472Z | user\n\n- Add confirmation gates to remove-recurring and discover-recurring (ClawHub security audit); document flags in README/SKILL\n- Fix estimate-period actual income/expense split: GROUP BY alias collided with accounts.type\n\nv1.11.0 | 2026-08-01T20:33:57.217Z | user\n\n- Add --raw-split stats mode (ownership split without reimbursement adjustments)\n- Fix: exclude zero-adjusted transactions from uncategorized lists\n\nv1.10.0 | 2026-07-17T19:40:50.055Z | user\n\n- Fix Issue #22: Emit warning when matching transaction falls outside amount bounds\n- Fix Issue #24: Use current date as reference in estimate-period\n- fix: identify drifting ~30-day recurring payments as days interval\n\nv1.9.1 | 2026-07-13T18:36:05.900Z | user\n\n- fix: declare python3 dependency in SKILL.md metadata\n\nv1.9.0 | 2026-07-13T18:17:11.462Z | user\n\n- feat: add estimate-period and set-estimate-level CLI commands\n- feat: implement period spending estimation core logic\n\nv1.8.0 | 2026-07-12T19:11:24.075Z | user\n\n- Implement recurring payments tracking, auto-discovery, stats, and salary period harmonization\n\nv1.7.1 | 2026-07-11T20:35:19.496Z | user\n\n- docs: implement template-based README-to-SKILL sync before publishing\n\nv1.7.0 | 2026-07-11T11:59:00.336Z | user\n\n- Implement mutually exclusive --net and --unsplit flags for transaction listings\n- Implement mutually exclusive --unsplit and --gross flags for stats subcommands\n\nv1.6.0 | 2026-06-24T20:02:36.844Z | user\n\n- feat: implement importer account auto-detection and validation mismatch checks\n- docs: clarify CLI-based querying of unmatched reimbursements in SKILL.md\n- feat: enhance manual-match and manual-unmatch command input flexibility and safety\n- perf: optimize transaction categorization with batch commits and rule caching\n\nv1.5.1 | 2026-06-22T20:36:11.519Z | user\n\n- fix: filter import CLI display to newly imported rows only, preventing historical database entries from showing up as NEW\n\nv1.5.0 | 2026-06-22T20:19:50.248Z | user\n\n- feat: add quiet, compact, and verbose options to transaction import\n\nv1.4.0 | 2026-06-18T20:31:19.269Z | user\n\n- feat: add manual-unmatch command to remove manual categorization overrides\n- feat: extend rules command to explain transaction categorization\n\nv1.3.0 | 2026-06-18T20:07:39.802Z | user\n\n- feat: add --non-zero flag to uncategorized and document pending reimbursement workflows (Stage 2)\n- feat: add transactions search command and query backend (Stage 1)\n- Add .agents/ to .gitignore to exclude local agent workspace configurations\n- Revert: Remove workspace agent rules file\n- Add workspace agent rules for composite transactions and manual adjustments\n\nv1.2.1 | 2026-06-18T17:03:00.651Z | user\n\n- Add workspace agent rules for composite transactions and manual adjustments\n\nv1.2.0 | 2026-06-18T16:42:31.651Z | user\n\n- Add multi-mode transaction linking, preview, dry-run support, and guide to SKILL.md\n- docs: clarify shared-expense reimbursement math and auto-linking in SKILL.md\n\nv1.1.2 | 2026-06-17T19:31:22.935Z | user\n\n- fix: drop views dynamically before table DDL rebuilds to prevent broken view references\n\nv1.1.1 | 2026-06-17T18:43:30.657Z | user\n\n- fix: add self-healing database migration to repair foreign keys pointing to accounts_old\n- Implement Phase 2: Cash Flow Calculations & Liquidity Boundaries\n- implement phase 1 of external account and cash flow transfer tracking\n- Scale reimbursement credit by target account ownership ratio\n- Support period-type parameter globally across all stats commands with dynamic defaults\n- Implement configurable salary periods and config CLI commands\n\nv1.1.0 | 2026-06-17T13:47:41.015Z | user\n\n- Implement Phase 2: Cash Flow Calculations & Liquidity Boundaries\n- implement phase 1 of external account and cash flow transfer tracking\n- Scale reimbursement credit by target account ownership ratio\n- Support period-type parameter globally across all stats commands with dynamic defaults\n- Implement configurable salary periods and config CLI commands\n\nv1.0.7 | 2026-06-17T10:49:30.394Z | user\n\n- fix: prevent unique constraint failures with pre-checks on pending/settled updates\n\nv1.0.6 | 2026-06-17T08:11:18.663Z | user\n\n- ci: remove all tracked non-production files before publishing\n\nv1.0.5 | 2026-06-17T07:58:49.500Z | user\n\n- ci: remove test files before publishing to ClawHub\n\nv1.0.4 | 2026-06-13T17:28:00.623Z | user\n\n- fix(security): add warning and confirmation prompt to auto-link\n- Configure dynamic changelog generation for ClawHub publishing workflow\n\nv1.0.3 | 2026-06-13T10:48:20.976Z | user\n\nAutomated release from git tag 1.0.3\n\nv1.0.2 | 2026-06-12T20:58:54.263Z | user\n\nAutomated release from git tag 1.0.2\n\nArchive index:\n\nArchive v1.16.0: 16 files, 85742 bytes\n\nFiles: _meta.json (141b), changelog.txt (72b), cli.py (107353b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (28063b), financial_categorizer/db_handler.py (62757b), financial_categorizer/importer.py (29587b), financial_categorizer/matching.py (3507b), financial_categorizer/recurring.py (43187b), financial_categorizer/stats.py (61275b), LICENSE (1072b), README.md (13374b), requirements.txt (134b), setup.py (249b), skill-card.md (1636b), SKILL.md (24477b)\n\nFile v1.16.0:SKILL.md\n\n---\nname: financial-categorizer\ndescription: \"Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\"\nmetadata:\n  openclaw:\n    requires:\n      bins:\n        - python3\n---\n\n\n# financial-categorizer\n\nProcess bank transaction CSV exports, auto-categorize transactions using configurable rules, manage transaction links, and generate analytical SQLite database views.\n\n## Quick Start\n\nRun the CLI tool from your terminal pointing to your database path:\n\n```bash\n# 1. Add your main checking account\npython cli.py --db ../data/finance.db add-account \"Nordea Checking\" --type tracked --ownership 1.0\n\n# 2. Add hierarchical categories\npython cli.py --db ../data/finance.db add-category \"Food\"\npython cli.py --db ../data/finance.db add-category \"Groceries\" --parent 1\n\n# 3. Add auto-categorization rules\npython cli.py --db ../data/finance.db add-rule 2 \"ICA MAXI\" --type contains\npython cli.py --db ../data/finance.db add-rule 1 \"^Hyra\" --type regex\n\n# 4. Import transactions from a bank CSV file\npython cli.py --db ../data/finance.db import transactions.csv --account \"Nordea Checking\"\n\n# 5. Run auto-categorization over uncategorized transactions\npython cli.py --db ../data/finance.db categorize\n\n# 6. View monthly summary statistics\npython cli.py --db ../data/finance.db stats-summary\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/financial-categorizer/   # Portable skill (shareable)\n│   ├── SKILL.md\n│   ├── cli.py\n│   ├── setup.py\n│   └── financial_categorizer/\n└── data/                           # Your private data\n    ├── finance.db\n    └── exports/\n        ├── Nordea_Checking.csv\n        └── ICA_Shared.csv\n```\n\nThe skill provides logic. Your data stays private and portable.\n\n## Security & Data Integrity\n\nThis tool modifies your local SQLite database. To prevent accidental data loss, please observe the following guidelines:\n\n> [!WARNING]\n> Always make a backup of your database before performing database cleanup, auto-linking, or destructive operations:\n> ```bash\n> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n> ```\n\n### Destructive Operations & Confirmation Prompts\nDestructive commands require interactive confirmation `[y/N]` when run in a terminal (TTY). If you are running these commands in automated scripts or non-interactive shells, you must pass the `--yes` or `-y` flag to bypass the prompt; otherwise, the command will abort with an error.\n\nThe following commands require confirmation:\n- `delete-account <id> [--yes]`\n- `delete-category <id> [--yes] [--reassign <id>] [--force]`\n- `remove-rule <id> [--yes]`\n- `unlink <id> [--yes]`\n- `db-cleanup [--yes] [--dry-run]`\n- `cleanup-pending [--yes] [--dry-run] [--force-id <id>...]`\n- `remove-transfer-rule <id> [--yes]`\n- `auto-link [--yes] [--dry-run]`\n\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `import <files>` | Import bank CSV transactions |\n| `accounts` | List all registered bank accounts |\n| `add-account <name>` | Create a new bank account |\n| `update-account <id>` | Update account ownership ratio, type, name, etc. |\n| `delete-account <id> [--yes]` | Delete a bank account (requires confirmation or `-y`) |\n| `categories` | List all categories in tree view |\n| `add-category <name> [--associated-account <name_or_id>]` | Create a new category, optionally associated with an external account |\n| `update-category <id> [--associated-account <name_or_id>]` | Update category parents or fields (use `none` to clear association) |\n| `delete-category <id> [--yes]` | Delete a category (requires confirmation or `-y`) |\n| `rules [txn_id]` | List all match rules, or show the matching rule for a specific transaction |\n| `add-rule <cat_id> <pattern>` | Add a categorization rule (regex, contains, exact) |\n| `remove-rule <id> [--yes]` | Remove an auto-categorization rule (requires confirmation or `-y`) |\n| `preview <pattern>` | Preview which transactions match a pattern before adding a rule |\n| `categorize [--all]` | Run auto-categorization rules |\n| `uncategorized [--group] [--non-zero] [--net | --unsplit]` | List all uncategorized transactions (supports `--net` or `--unsplit`) |\n| `transactions [--category <name>] [--uncategorized] [--non-zero] [--account <name>] [--limit <n>] [--net | --unsplit]` | Search, list, and filter transactions (supports `--net` or `--unsplit`) |\n| `manual-match <txn_id> <cat_id>` | Manually assign a category override to a transaction |\n| `manual-unmatch <txn_id>` | Remove a manual categorization override |\n| `stats-summary [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly summary of income, expenses, and net (supports `--unsplit` or `--gross`) |\n| `stats-category <name> [--month <YYYY-MM>] [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Category total with subcategory rollups (supports `--unsplit` or `--gross`) |\n| `stats-trend <name> [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Monthly trend for a category (supports `--unsplit` or `--gross`) |\n| `stats-top [--month <YYYY-MM>] [--limit <n>] [--period-type <type>] [--unsplit | --gross]` | Top spending categories sorted by total expenses (supports `--unsplit` or `--gross`) |\n| `stats-transfers [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Net capital transfers to external accounts (supports `--unsplit` or `--gross`) |\n| `stats-compare [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Month-over-month comparison (supports `--unsplit` or `--gross`) |\n| `stats-cashflow [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly cash flow summary (Operating, Transfers, Net; supports `--unsplit` or `--gross`) |\n| `link <from_id> [to_id] --type [--to-account <name_or_id>] [--ratio <val> \\| --ratio-to <val> \\| --amount <val>] [--dry-run]` | Link transactions (specify `--to-account` for external transfers, or ratio/amount options to customize values; `--dry-run` to preview) |\n| `unlink <id> [--yes]` | Remove a link (requires confirmation or `-y`) |\n| `links` | List all transaction links |\n| `auto-link [--dry-run] [--yes]` | Auto-detect and link internal transfers using transfer rules (requires confirmation or `-y` when not running dry-run) |\n| `recalculate` | Manually recalculate adjusted amounts for all transactions |\n| `db-cleanup [--dry-run] [--yes]` | Purge orphaned transaction links and rules (Integrity Cleanup) (requires confirmation or `-y` when not running dry-run) |\n| `cleanup-pending [--dry-run] [--yes] [--force-id <id>...]` | Delete ghost pending reservations whose settled counterpart already exists (individual, split-authorization, or inexact amount matches — e.g. merchants that authorize a buffer and settle a different final amount; inexact matches fire only when unambiguous). Unresolved pendings are kept, listed with nearby same-merchant candidates, flagged as probable cancellations when old with no counterpart, and can be deleted explicitly via `--force-id` (requires confirmation or `-y` when not running dry-run) |\n| `remove-transfer-rule <id> [--yes]` | Remove a transfer detection rule (requires confirmation or `-y`) |\n| `salary-config` | Show current salary period configuration |\n| `set-salary-mode <mode>` | Set the salary period mode (`calendar`, `fixed`, `salary`) |\n| `set-salary-day <day>` | Set the fixed boundary day of the month (1-28) |\n| `set-salary-category <name>` | Set the category name used to scan for salary paydays |\n| `recurring [--status <active|cancelled|all>]` | List recurring payments configurations |\n| `add-recurring <name> <pattern> [options] [--dry-run]` | Manually create a recurring payment config and link transactions |\n| `update-recurring <id> [options] [--dry-run]` | Update recurring config fields and re-run transaction linking |\n| `remove-recurring <id> [--hard] [--date <YYYY-MM-DD>] [--yes]` | Cancel (soft-close with end date) or hard-delete a recurring payment configuration (requires confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode) |\n| `discover-recurring [--dry-run] [--yes]` | Auto-discover recurring transaction patterns and auto-close dead configurations (saving, re-linking, and auto-closing require confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode — run `--dry-run` first to preview) |\n| `stats-recurring [query] [--month <YYYY-MM>] [--period-type <type>]` | Display subscription stats dashboard or detail reports for matching subscriptions |\n| `estimate-period [--days <int>] [--level <0|1|2>]` | Project spending and estimate total outflow remaining in current period (rollup levels: 0=none, 1=top, 2=detailed) |\n| `set-estimate-level <0|1|2>` | Set default category rollup level configuration for spending estimation |\n\n\n\n\n\n## Configuring Salary Periods\n\nBy default, the salary period boundary is fixed to the 25th of the month. You can customize this grouping behavior using the salary configuration commands.\n\n### Available Modes:\n1. **`calendar`**: Group transactions by calendar months (1st to the last day).\n2. **`fixed`**: Group transactions by a static day of the month (e.g., the 25th). Transactions on or after this day are grouped into the next month's period.\n3. **`salary`**: Group transactions by automatically detecting the primary salary deposit date in each month (the transaction under the salary category with the largest positive amount).\n\n### CLI Configuration Commands:\n```bash\n# View current configuration\npython cli.py salary-config\n\n# Change mode to salary (automatic payday detection)\npython cli.py set-salary-mode salary\n\n# Set the category name used to search for paydays (default is \"Salary\")\npython cli.py set-salary-category \"Salary\"\n\n# Change mode to a fixed day of the month (e.g. 27th)\npython cli.py set-salary-mode fixed\npython cli.py set-salary-day 27\n```\n\n> [!WARNING]\n> If you choose the **`fixed`** day mode, be aware that bank deposits and transactions can shift early or late due to weekends and holidays.\n> - Ensure your fixed day is configured early or late enough so that fluctuations in actual payday do not cause two salary deposits to fall into the same period (which would result in one month showing double income and the next showing zero income).\n> - Alternatively, use the **`salary`** mode, which automatically detects the actual deposit transaction dates and shifts the boundaries dynamically.\n\n### Querying Statistics by Salary Period\nAll statistics and breakdown commands support the `--period-type` parameter:\n* `calendar` — Force standard calendar month boundaries.\n* `salary` — Force salary period boundaries (using the active `salary-config` settings).\n* `default` — Dynamically resolve to your active `salary-config` mode:\n  - If mode is `calendar`, defaults to calendar months.\n  - If mode is `fixed` or `salary`, defaults to salary periods.\n\nFor example, to query your housing category spending using the active salary period:\n```bash\npython cli.py stats-category Housing --period-type salary --month 2026-06\n```\n\nIf you do not specify a `--period-type` flag, it will automatically default to the setting configured via `set-salary-mode`.\n\n## Tracking Recurring Payments & Subscriptions\n\nThis tool supports advanced, automated tracking and lifecycle management of recurring payments (e.g. Netflix, Spotify, broadband, utility bills) and income (e.g. Salary).\n\n### Core Concepts\n\n1. **Recurring Payments Table (`recurring_payments`)**\n   Defines the rules, intervals, expected days, amount ranges, and lifespans for each recurring item.\n2. **Subscription Lifecycle & Runs**\n   Resumed subscriptions (after cancellation) are tracked as separate runs/rows in `recurring_payments`.\n   * **Resumption**: If a transaction matches a pattern of a cancelled subscription (after its `end_date`), it automatically spawns a new run/configuration for the resumption.\n   * **Auto-Closing**: Active configurations that are missing expected payments are automatically closed (`end_date` is set to the last matched payment date) when running `discover-recurring` or passing the `--close` flag to `import` / `categorize`.\n   * **Confirmation Gates**: `remove-recurring` and `discover-recurring` (without `--dry-run`) prompt for confirmation before making changes, like the other destructive commands. Use `--yes`/`-y` to bypass (mandatory in non-interactive mode), and `discover-recurring --dry-run` to preview first.\n3. **Flexible Date Intervals**\n   Supports strict date/day checking with a configured tolerance window:\n   * **Monthly**: Expected day of month (e.g. 25th, last day `-1`).\n   * **Weekly**: Expected weekday.\n   * **Yearly**: Expected month and day.\n   * **Days**: Custom interval (e.g. every 90 days).\n   * **Tolerance**: Tolerates shifts due to weekends/holidays (default 4 days).\n\n### Common Workflows\n\n#### 1. Auto-Discover Recurring Candidates\nScan transaction history to auto-identify recurring items (such as monthly subscriptions or utility bills) and automatically save them:\n```bash\n# Preview candidates without writing to the database\npython cli.py discover-recurring --dry-run\n\n# Run auto-discovery and save configurations (prompts for confirmation; --yes to bypass)\npython cli.py discover-recurring --yes\n```\n\n#### 2. Manually Add/Update Configurations\n```bash\n# Add a monthly Netflix subscription\npython cli.py add-recurring Netflix \"netflix.com\" --amount-min -149 --amount-max -189 --interval monthly --day-of-month 6 --category Media\n\n# Dry-run update previewing matches\npython cli.py update-recurring 1 --amount-max -219 --dry-run\n```\n\n#### 3. View Outflow Dashboard & Stats\n```bash\n# Active subscriptions monthly cost summary and expected next dates\npython cli.py stats-recurring\n\n# Detailed subscription history across active/cancelled runs and transaction lists\npython cli.py stats-recurring \"Disney Plus\"\n```\n\n## Common Workflows\n\n\n### Handling Shared-Expense Reimbursements\n\nIf you make a shared purchase (e.g., from the `Gemensamt` account, 50% ownership) and get reimbursed by an external person (e.g., via Swish to your `Personligt` account, 100% ownership) and subsequently transfer the payback to the shared account:\n\n1. **Reimburse the shared expense**: Link the reimbursement transaction (the Swish inflow) directly to the original expense transaction (the shared purchase):\n   ```bash\n   python cli.py --db data/finance.db link <swish_transaction_id> <expense_transaction_id> --type reimbursement --ratio 1.0\n   ```\n   * *Effect*: The Swish transaction is fully neutralized to `0.00` adjusted amount, and the credit to the expense transaction is automatically scaled by the shared account's ownership ratio (e.g., 50%), reducing your net cost correctly.\n   * *Note*: The credit is scaled by the target account's ownership ratio (e.g., 50%) because the benefit of the payback is shared between the joint account owners.\n\n2. **Link the account transfer**: Link the outflow from your main account to the inflow on your joint account as an internal transfer:\n   ```bash\n   python cli.py --db data/finance.db link <transfer_out_id> <transfer_in_id> --type internal_transfer\n   ```\n   * *Effect*: Both sides of the transfer are neutralized to `0.00`, ensuring no false income or outflows are recorded.\n    * *Note*: This step is skipped if the transfer has already been auto-linked.\n\n### Managing Pending Reimbursements (Unlinked Inflows)\n\n#### Option A: Flat List Workflow (Keeping Them Uncategorized)\nUse the `--non-zero` flag on the `uncategorized` command to show pending actions (positive inflows to link, negative expenses to categorize):\n```bash\npython cli.py uncategorized --non-zero\n```\n\n#### Option B: Dedicated Category Workflow (Filtering Positive Inflows Only)\nTo auto-route only positive inflows (like Swish reimbursements) to a category (e.g., ID `9`) while leaving negative outflows uncategorized, add a rule with a minimum amount filter:\n```bash\npython cli.py add-rule 9 \"Swish\" --type contains --amount-min 0.01\n```\n\nQuery unlinked/pending reimbursements using:\n```bash\npython cli.py transactions --category Reimbursements --non-zero\n```\n*(Once linked, the adjusted amount drops to `0.00` and the transaction disappears from both lists).*\n\n### How to Think About Reimbursements & Composite Transactions\n\nWhen working with transaction links, it is crucial to distinguish between the **raw bank ledger amount** (actual cash flow) and the **effective category/budgetary amount** (represented by the `adjusted_amount` column).\n\n#### The Core Principle\nReimbursements are not new income; they are a return of capital.\n* If an expense is reimbursed, the net expense is zero.\n* The incoming reimbursement money is not labor/investment income; it simply offsets the expense.\n\nIf you don't link them, your gross income and gross expenses will both be overstated by the reimbursement amount, distorting your reports.\n\n#### Composite Transactions (e.g. Reimbursement Baked into Salary)\nOften, a reimbursement is not a standalone transaction (like a Swish payment), but is packaged/baked into a larger composite transaction, such as a salary payment.\nFor example, if your employer pays you a single amount of `50,000 SEK`, which contains:\n* `45,000 SEK` of actual labor income\n* `5,000 SEK` of expense reimbursement for a credit card charge\n\nTo avoid distorting both income and expenses, you must split this composite transaction. In this system, you do this using **transaction links** with fractional ratios.\n\n#### Link Ratio Calculation Modes\nThe `link` command provides three modes to simplify this:\n\n1. **Source Ratio (`--ratio <float>`)** - *Default*\n   Calculates the ratio relative to the source (`from_id`) transaction. Use when you want to allocate a direct fraction of the source.\n\n2. **Destination Ratio (`--ratio-to <float>`)**\n   Calculates the ratio relative to the destination (`to_id`) transaction.\n   * For example, to fully reimburse/zero out the `First Card` expense of `-5,000 SEK` from your salary, use:\n     ```bash\n     python cli.py link <salary_txn_id> <expense_txn_id> --type reimbursement --ratio-to 1.0\n     ```\n   * This automatically calculates the exact ratio ($5000 / 50000 = 0.10$). It reduces the salary's `adjusted_amount` to `45,000 SEK` (reflecting your true labor income) and increases the credit card expense's `adjusted_amount` to `0.00 SEK` (reflecting your true net expense).\n\n3. **Exact Cash (`--amount <float>`)**\n   Specify the exact cash amount in SEK being reimbursed.\n   * For example, to link exactly `5,000 SEK`:\n     ```bash\n     python cli.py link <salary_txn_id> <expense_txn_id> --type reimbursement --amount 5000\n     ```\n\n\n#### Dry-run Previews\nAlways run with the `--dry-run` flag first to preview the downstream `adjusted_amount` effects before committing changes to the database:\n```bash\npython cli.py link <from_id> <to_id> --type reimbursement --ratio-to 1.0 --dry-run\n```\n\n## Tracking External Accounts\n\nYou can track capital transfers from your tracked accounts to untracked external accounts (such as savings or stock brokerage accounts).\n\n### Setup and Workflow:\n1. **Create the External Account**:\n   ```bash\n   python cli.py add-account \"Avanza Brokerage\" --type external\n   ```\n2. **Associate a Category**:\n   Create a category of type `transfer` associated with this external account:\n   ```bash\n   python cli.py add-category \"Brokerage Transfer\" --type transfer --associated-account \"Avanza Brokerage\"\n   ```\n3. **Add a Categorization Rule**:\n   Add a match rule to auto-categorize transfers:\n   ```bash\n   python cli.py add-rule <category_id> \"AVANZA\" --type contains\n   ```\n4. **Auto-linking**:\n   When transactions are categorized (via `categorize` or manual overrides), if they match a transfer category linked to an external account, an `external_transfer` link is created automatically.\n\n### Manual Linking:\nFor one-off transfers, you can link a transaction directly to an external account:\n```bash\npython cli.py link <transaction_id> --type external_transfer --to-account \"Avanza Brokerage\"\n```\n\n### Querying Statistics:\nUse the `stats-transfers` command to view net capital movements per external account:\n```bash\npython cli.py stats-transfers --month 2026-06\n```\n\n## Skill Contents\n\n```\nfinancial-categorizer/\n├── SKILL.md                    # This file\n├── requirements.txt            # pip dependencies\n├── setup.py                    # setuptools configuration\n├── cli.py                      # Main entrypoint\n└── financial_categorizer/      # Package code\n    ├── __init__.py\n    ├── categorizer.py          # Auto-categorization & rule engine\n    ├── db_handler.py           # Database CRUD & raw schema setup\n    ├── importer.py             # CSV Parser (Nordea & ICA formats)\n    ├── matching.py             # Shared matching helpers (diacritic folding, aggregate tolerance)\n    └── stats.py                # SQL View registers and stats math\n```\n\n## SQLite Views\n\nFor analytical reporting (e.g. dashboards, Grafana), the following views are registered in the database:\n\n1. **`v_effective_transactions`** — Joins transactions with accounts to factor in ownership ratios and transfer link adjustments. Includes `adjusted_amount`, `unsplit_amount`, and `raw_amount` columns.\n2. **`v_monthly_summary`** — Calculates net income/expenses by month (includes unsplit and gross aggregations).\n3. **`v_category_monthly`** — Calculates monthly spending by category (includes unsplit and gross aggregations).\n4. **`v_daily_spending`** — Daily expense aggregation.\n5. **`v_cumulative_spending_monthly`** — Running month-to-date daily cumulative spending.\n6. **`v_daily_spending_moving_average`** — 30-day moving average of daily spending.\n7. **`v_category_monthly_averages`** — Average monthly spending by category.\n8. **`v_salary_period_summary`** — Expense/income summary grouped by salary periods (using the active salary config: fixed or salary).\n9. **`v_breakout_categories`** — Groups monthly spending into high-level categories (Groceries, Loans, Housing, Leisure, Car, etc.).\n10. **`v_uncategorized_groups`** — Groups uncategorized transactions by normalized Swish/Card payment descriptions to identify potential new rules.\n\n---\n\n## Querying Unmatched Reimbursements via CLI\n\nUnmatched reimbursements (pending paybacks or refunds) are incoming transactions on tracked accounts that have not yet been neutralized by a transaction link. They can be queried and filtered using the CLI:\n\n### Workflow:\n\n1. **Query the transactions** depending on the categorization workflow in use:\n   * **If using Uncategorized / Flat List (Option A)**:\n     ```bash\n     python cli.py uncategorized --non-zero\n     ```\n   * **If using a Dedicated Category (Option B)**:\n     ```bash\n     python cli.py transactions --category Reimbursements --non-zero --limit 100\n     ```\n\n2. **Identify candidates from the output**:\n   * **Inflows**: Look for transactions with positive amounts (`amount > 0`).\n   * **Regular Income Exclusions**: Filter out regular income sources (e.g., `\"Lön\"`, `\"Salary\"`, `\"BARNBDR\"`).\n   * **Adjusted Amount**: Verify that the adjusted amount is non-zero (linked/neutralized transactions show `adjusted_amount = 0.00`).\n\n### Example Filter Logic:\nIf the CLI output shows:\n```\nTransactions (4):\n  [12] 2026-06-23   25000.00 SEK                      Personligt      [Uncategorized]     Lön\n  [15] 2026-06-18    1250.00 SEK                      Personligt      [Uncategorized]     BARNBDR\n  [18] 2026-06-16     500.00 SEK                      Personligt      [Uncategorized]     Swish inbetalning DOE, JOHN\n  [21] 2026-05-20      50.00 SEK                      Personligt      [Uncategorized]     Swish inbetalning DOE, JOHN\n```\n* **Include**: `[18]` (+500.00) and `[21]` (+50.00) (positive Swish payments from an individual are reimbursement candidates).\n* **Exclude**: `[12]` (Lön/Salary) and `[15]` (barnbidrag/regular benefit payment).\n\n\n## Dependencies\n\n- `pytest` - For testing suite\n- Standard library modules: `sqlite3`, `csv`, `datetime`, `logging`, `re`, `argparse`, `os`\n\nInstall: `pip install -e .`\n\nFile v1.16.0:README.md\n\n# financial-categorizer\n\nPersonal finance transaction categorization with a SQLite backend.\n\nImports bank CSV files, auto-categorizes transactions using configurable rules, and provides SQL views for dashboards and analysis.\n\n## Features\n\n- **Multi-account support** — tracked active accounts and external savings/investments with ownership ratios\n- **Auto-categorization** — regex, exact, and contains match rules with priority ordering and manual overrides\n- **Transaction linking** — mark transfers and reimbursements between transactions; adjusted amounts are pre-computed\n- **SQL views** — ready-to-query views for monthly summaries, category breakdowns, and daily spending\n- **CSV import** — auto-detects Nordea and ICA formats, handles pending transactions (umlaut-variant, split-authorization, and inexact buffer settlement; see [Pending Transactions](#pending-transactions-reservations))\n- **CLI** — full command-line interface for all operations\n\n## Install\n\n```bash\npip install -e .\n```\n\nRequires Python 3.10+.\n\n## Quick Start\n\n```bash\n# Import transactions from a CSV\nfinancial-categorizer import transactions.csv --account \"Nordea Checking\"\n\n# Add categorization rules\nfinancial-categorizer add-rule 3 \"ICA MAXI\" --type contains\nfinancial-categorizer add-rule 4 \"^Hyra\" --type regex\n\n# Categorize uncategorized transactions\nfinancial-categorizer categorize\n\n# View stats\nfinancial-categorizer stats-summary --month 2026-04\nfinancial-categorizer stats-top --limit 10\nfinancial-categorizer stats-category Food --month 2026-04\n\n# Link a transfer between accounts\nfinancial-categorizer link 1 2 --type internal_transfer\n\n# Recalculate adjusted amounts\nfinancial-categorizer recalculate\n```\n\n## Architecture\n\nAll data lives in a single SQLite database (`data/finance.db` by default).\n\n### Core tables\n- **accounts** — bank accounts with type and ownership ratio\n- **transactions** — imported transactions with `adjusted_amount` (pre-computed)\n- **categories** — hierarchical categories (parent/child)\n- **match_rules** — patterns for auto-categorization\n- **transaction_links** — connects transfers and reimbursements\n\n### Projection tables\n- **period_projection** — persisted projection of the current period (income-anchored burn-down), refreshed automatically after data mutations (import, categorize, manual match/unmatch, link/unlink); the latest refresh wins\n- **period_projection_items** — itemized expected occurrences (date, name, amount) covering the full period, with an `upcoming` flag (date > as-of); zero-amount rows filtered\n\n### Views\n- `v_effective_transactions` — all transactions with adjusted, unsplit, and raw amounts\n- `v_monthly_summary` — income, expenses, net per month (includes unsplit and gross aggregations)\n- `v_category_monthly` — category totals per month (includes unsplit and gross aggregations)\n- `v_daily_spending` — daily spending breakdown\n- `v_burn_down` — income-anchored burn-down derived from `period_projection`\n\n### Adjusted, Unsplit, and Gross amounts\n\n1. `adjusted_amount` (Personal Share): Your share of the transaction. Calculated as `amount * account.ownership_ratio` (base), then adjusted by transfers and reimbursements.\n2. `unsplit_amount` (Household Net): Full household cost net of reimbursements. Calculated as `adjusted_amount / account.ownership_ratio`. Enabled in stats with the `--unsplit` flag.\n3. `raw_amount` (Household Raw): Full raw household cost before split and before reimbursements (i.e. the raw bank statement amount). Enabled in stats with the `--gross` flag.\n\nStats and views read these columns directly. Run `recalculate` to refresh after any manual changes.\n\n## Pending Transactions (Reservations)\n\nCard reservations (`Reserverat` rows) are temporary holds the bank places before a merchant settles the final charge. The intent behind the pending lifecycle is simple: **every reservation goes somewhere** — it settles (at the same amount, as a re-authorized group, or at a different final amount) or it was cancelled. A reservation should never linger silently in the database as a second copy of a purchase that already settled.\n\nAt import, a settled charge resolves an outstanding reservation through three passes, in order:\n\n1. **Exact** — settled amount within 1.0 SEK of the reservation.\n2. **Split-authorization** — 2–4 reservations for the same merchant that sum to the settled charge (within `max(1.0 SEK, 0.5%)`).\n3. **Inexact** — for merchants that authorize a buffer and settle a different final amount (weighed goods, fuel pre-auths, tips, currency conversion). The settled amount must be within **85%–105%** of the reservation (same sign). The floor is deliberately conservative: typical buffer haircuts are modest and auto-resolve, while a settlement far below the reservation — which could be a genuinely separate purchase from the same merchant — is left for manual review instead of an automatic guess.\n\nInexact matching only fires when the pairing is **mutually unique**: exactly one outstanding reservation lies inside the settled charge's amount band, AND no other settled charge in the database can claim that reservation. Any ambiguity — two candidate reservations, or two candidate settled charges — means no automatic action; the case is surfaced for review instead.\n\nAdditional lifecycle guarantees:\n\n- **Re-listed reservations are skipped, not re-inserted** — an outstanding reservation that appears again in a later export is the same pending, not a new transaction.\n- **Disappearance warnings** — a pending reservation that is absent from a current export, at least 14 days old, with no settled counterpart, triggers an import warning: it most likely was cancelled or settled under a different amount, and belongs in `cleanup-pending`.\n- **`cleanup-pending`** deletes provable ghosts (labeled exact / split / inexact), lists unresolved reservations with nearby same-merchant settled charges as candidate hints, flags long-outstanding pendings with no candidates as probable cancellations, and accepts `--force-id <id>` (repeatable) to delete a specific pending explicitly after review.\n\n## Security & Data Integrity\n\nThis tool modifies your local SQLite database. To prevent accidental data loss, please observe the following guidelines:\n\n> [!WARNING]\n> Always make a backup of your database before performing database cleanup, auto-linking, or destructive operations:\n> ```bash\n> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n> ```\n\n### Destructive Operations & Confirmation Prompts\nDestructive commands require interactive confirmation `[y/N]` when run in a terminal (TTY). If you are running these commands in automated scripts or non-interactive shells, you must pass the `--yes` or `-y` flag to bypass the prompt; otherwise, the command will abort with an error.\n\nThe following commands require confirmation:\n- `delete-account <id> [--yes]`\n- `delete-category <id> [--yes] [--reassign <id>] [--force]`\n- `remove-rule <id> [--yes]`\n- `unlink <id> [--yes]`\n- `db-cleanup [--yes] [--dry-run]`\n- `cleanup-pending [--yes] [--dry-run] [--force-id <id>...]`\n- `remove-transfer-rule <id> [--yes]`\n- `auto-link [--yes] [--dry-run]`\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `import <files>` | Import bank CSV transactions |\n| `accounts` | List all registered bank accounts |\n| `add-account <name>` | Create a new bank account |\n| `update-account <id>` | Update account ownership ratio, type, name, etc. |\n| `delete-account <id> [--yes]` | Delete a bank account (requires confirmation or `-y`) |\n| `categories` | List all categories in tree view |\n| `add-category <name> [--associated-account <name_or_id>]` | Create a new category, optionally associated with an external account |\n| `update-category <id> [--associated-account <name_or_id>]` | Update category parents or fields (use `none` to clear association) |\n| `delete-category <id> [--yes]` | Delete a category (requires confirmation or `-y`) |\n| `rules [txn_id]` | List all match rules, or show the matching rule for a specific transaction |\n| `add-rule <cat_id> <pattern>` | Add a categorization rule (regex, contains, exact) |\n| `remove-rule <id> [--yes]` | Remove an auto-categorization rule (requires confirmation or `-y`) |\n| `preview <pattern>` | Preview which transactions match a pattern before adding a rule |\n| `categorize [--all]` | Run auto-categorization rules |\n| `uncategorized [--group] [--non-zero] [--net | --unsplit]` | List all uncategorized transactions (supports `--net` or `--unsplit`) |\n| `transactions [--category <name>] [--uncategorized] [--non-zero] [--account <name>] [--limit <n>] [--net | --unsplit]` | Search, list, and filter transactions (supports `--net` or `--unsplit`) |\n| `manual-match <txn_id> <cat_id>` | Manually assign a category override to a transaction |\n| `manual-unmatch <txn_id>` | Remove a manual categorization override |\n| `stats-summary [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly summary of income, expenses, and net (supports `--unsplit` or `--gross`) |\n| `stats-category <name> [--month <YYYY-MM>] [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Category total with subcategory rollups (supports `--unsplit` or `--gross`) |\n| `stats-trend <name> [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Monthly trend for a category (supports `--unsplit` or `--gross`) |\n| `stats-top [--month <YYYY-MM>] [--limit <n>] [--period-type <type>] [--unsplit | --gross]` | Top spending categories sorted by total expenses (supports `--unsplit` or `--gross`) |\n| `stats-transfers [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Net capital transfers to external accounts (supports `--unsplit` or `--gross`) |\n| `stats-compare [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Month-over-month comparison (supports `--unsplit` or `--gross`) |\n| `stats-cashflow [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly cash flow summary (Operating, Transfers, Net; supports `--unsplit` or `--gross`) |\n| `link <from_id> [to_id] --type [--to-account <name_or_id>] [--ratio <val> \\| --ratio-to <val> \\| --amount <val>] [--dry-run]` | Link transactions (specify `--to-account` for external transfers, or ratio/amount options to customize values; `--dry-run` to preview) |\n| `unlink <id> [--yes]` | Remove a link (requires confirmation or `-y`) |\n| `links` | List all transaction links |\n| `auto-link [--dry-run] [--yes]` | Auto-detect and link internal transfers using transfer rules (requires confirmation or `-y` when not running dry-run) |\n| `recalculate` | Manually recalculate adjusted amounts for all transactions |\n| `db-cleanup [--dry-run] [--yes]` | Purge orphaned transaction links and rules (Integrity Cleanup) (requires confirmation or `-y` when not running dry-run) |\n| `cleanup-pending [--dry-run] [--yes] [--force-id <id>...]` | Delete ghost pending reservations whose settled counterpart already exists (individual, split-authorization, or inexact amount matches — e.g. merchants that authorize a buffer and settle a different final amount; inexact matches fire only when unambiguous). Unresolved pendings are kept, listed with nearby same-merchant candidates, flagged as probable cancellations when old with no counterpart, and can be deleted explicitly via `--force-id` (requires confirmation or `-y` when not running dry-run) |\n| `remove-transfer-rule <id> [--yes]` | Remove a transfer detection rule (requires confirmation or `-y`) |\n| `salary-config` | Show current salary period configuration |\n| `set-salary-mode <mode>` | Set the salary period mode (`calendar`, `fixed`, `salary`) |\n| `set-salary-day <day>` | Set the fixed boundary day of the month (1-28) |\n| `set-salary-category <name>` | Set the category name used to scan for salary paydays |\n| `recurring [--status <active|cancelled|all>]` | List recurring payments configurations |\n| `add-recurring <name> <pattern> [options] [--dry-run]` | Manually create a recurring payment config and link transactions |\n| `update-recurring <id> [options] [--dry-run]` | Update recurring config fields and re-run transaction linking |\n| `remove-recurring <id> [--hard] [--date <YYYY-MM-DD>] [--yes]` | Cancel (soft-close with end date) or hard-delete a recurring payment configuration (requires confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode) |\n| `discover-recurring [--dry-run] [--yes]` | Auto-discover recurring transaction patterns and auto-close dead configurations (saving, re-linking, and auto-closing require confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode — run `--dry-run` first to preview) |\n| `stats-recurring [query] [--month <YYYY-MM>] [--period-type <type>]` | Display subscription stats dashboard or detail reports for matching subscriptions |\n| `estimate-period [--days <int>] [--level <0|1|2>]` | Project spending and estimate total outflow remaining in current period (rollup levels: 0=none, 1=top, 2=detailed) |\n| `set-estimate-level <0|1|2>` | Set default category rollup level configuration for spending estimation |\n\n\n\n\n## Testing\n\n```bash\npip install pytest\npytest tests/\n```\n\nFile v1.16.0:_meta.json\n\n{\n  \"ownerId\": \"kn7cxaw1m27d1fe93wbz40vqkd8309h7\",\n  \"slug\": \"financial-categorizer\",\n  \"version\": \"1.16.0\",\n  \"publishedAt\": 1791112132434\n}\n\nFile v1.16.0:skill-card.md\n\n## Description:\n\nProcess bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[patello](https://clawhub.ai/user/patello)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nPeople managing personal finances use this skill to import bank CSVs, categorize transactions, link transfers and reimbursements, and review spending in a local SQLite database.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Cleanup, auto-linking, recurring discovery, category deletion, and other mutating commands can change or remove records in the local finance database.\n\nMitigation: Back up the database first, preview with dry-run options when available, and review changes before using --yes.\n\n## Reference(s):\n\n- [Financial Categorizer on ClawHub](https://clawhub.ai/patello/skills/financial-categorizer)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Configuration guidance]\n\n**Output Format:** [Markdown with CLI commands and usage guidance]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Commands operate on a user-selected local SQLite database.]\n\n## Skill Version(s):\n\n1.16.0 (source: server-resolved ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v1.16.0:changelog.txt\n\n- fix: keep genuinely identical transactions instead of collapsing them\n\nFile v1.16.0:LICENSE\n\nMIT License\n\nCopyright (c) 2021 Patrik Ekenberg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nFile v1.16.0:requirements.txt\n\n# financial-categorizer has no external dependencies\n# All requirements are Python stdlib (sqlite3, csv, argparse, datetime, logging)\n\nArchive v1.15.0: 16 files, 84784 bytes\n\nFiles: _meta.json (141b), changelog.txt (212b), cli.py (107353b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (28063b), financial_categorizer/db_handler.py (58455b), financial_categorizer/importer.py (27869b), financial_categorizer/matching.py (3507b), financial_categorizer/recurring.py (43187b), financial_categorizer/stats.py (61275b), LICENSE (1072b), README.md (13374b), requirements.txt (134b), setup.py (249b), skill-card.md (2110b), SKILL.md (24477b)\n\nFile v1.15.0:SKILL.md\n\n---\nname: financial-categorizer\ndescription: \"Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\"\nmetadata:\n  openclaw:\n    requires:\n      bins:\n        - python3\n---\n\n\n# financial-categorizer\n\nProcess bank transaction CSV exports, auto-categorize transactions using configurable rules, manage transaction links, and generate analytical SQLite database views.\n\n## Quick Start\n\nRun the CLI tool from your terminal pointing to your database path:\n\n```bash\n# 1. Add your main checking account\npython cli.py --db ../data/finance.db add-account \"Nordea Checking\" --type tracked --ownership 1.0\n\n# 2. Add hierarchical categories\npython cli.py --db ../data/finance.db add-category \"Food\"\npython cli.py --db ../data/finance.db add-category \"Groceries\" --parent 1\n\n# 3. Add auto-categorization rules\npython cli.py --db ../data/finance.db add-rule 2 \"ICA MAXI\" --type contains\npython cli.py --db ../data/finance.db add-rule 1 \"^Hyra\" --type regex\n\n# 4. Import transactions from a bank CSV file\npython cli.py --db ../data/finance.db import transactions.csv --account \"Nordea Checking\"\n\n# 5. Run auto-categorization over uncategorized transactions\npython cli.py --db ../data/finance.db categorize\n\n# 6. View monthly summary statistics\npython cli.py --db ../data/finance.db stats-summary\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/financial-categorizer/   # Portable skill (shareable)\n│   ├── SKILL.md\n│   ├── cli.py\n│   ├── setup.py\n│   └── financial_categorizer/\n└── data/                           # Your private data\n    ├── finance.db\n    └── exports/\n        ├── Nordea_Checking.csv\n        └── ICA_Shared.csv\n```\n\nThe skill provides logic. Your data stays private and portable.\n\n## Security & Data Integrity\n\nThis tool modifies your local SQLite database. To prevent accidental data loss, please observe the following guidelines:\n\n> [!WARNING]\n> Always make a backup of your database before performing database cleanup, auto-linking, or destructive operations:\n> ```bash\n> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n> ```\n\n### Destructive Operations & Confirmation Prompts\nDestructive commands require interactive confirmation `[y/N]` when run in a terminal (TTY). If you are running these commands in automated scripts or non-interactive shells, you must pass the `--yes` or `-y` flag to bypass the prompt; otherwise, the command will abort with an error.\n\nThe following commands require confirmation:\n- `delete-account <id> [--yes]`\n- `delete-category <id> [--yes] [--reassign <id>] [--force]`\n- `remove-rule <id> [--yes]`\n- `unlink <id> [--yes]`\n- `db-cleanup [--yes] [--dry-run]`\n- `cleanup-pending [--yes] [--dry-run] [--force-id <id>...]`\n- `remove-transfer-rule <id> [--yes]`\n- `auto-link [--yes] [--dry-run]`\n\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `import <files>` | Import bank CSV transactions |\n| `accounts` | List all registered bank accounts |\n| `add-account <name>` | Create a new bank account |\n| `update-account <id>` | Update account ownership ratio, type, name, etc. |\n| `delete-account <id> [--yes]` | Delete a bank account (requires confirmation or `-y`) |\n| `categories` | List all categories in tree view |\n| `add-category <name> [--associated-account <name_or_id>]` | Create a new category, optionally associated with an external account |\n| `update-category <id> [--associated-account <name_or_id>]` | Update category parents or fields (use `none` to clear association) |\n| `delete-category <id> [--yes]` | Delete a category (requires confirmation or `-y`) |\n| `rules [txn_id]` | List all match rules, or show the matching rule for a specific transaction |\n| `add-rule <cat_id> <pattern>` | Add a categorization rule (regex, contains, exact) |\n| `remove-rule <id> [--yes]` | Remove an auto-categorization rule (requires confirmation or `-y`) |\n| `preview <pattern>` | Preview which transactions match a pattern before adding a rule |\n| `categorize [--all]` | Run auto-categorization rules |\n| `uncategorized [--group] [--non-zero] [--net | --unsplit]` | List all uncategorized transactions (supports `--net` or `--unsplit`) |\n| `transactions [--category <name>] [--uncategorized] [--non-zero] [--account <name>] [--limit <n>] [--net | --unsplit]` | Search, list, and filter transactions (supports `--net` or `--unsplit`) |\n| `manual-match <txn_id> <cat_id>` | Manually assign a category override to a transaction |\n| `manual-unmatch <txn_id>` | Remove a manual categorization override |\n| `stats-summary [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly summary of income, expenses, and net (supports `--unsplit` or `--gross`) |\n| `stats-category <name> [--month <YYYY-MM>] [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Category total with subcategory rollups (supports `--unsplit` or `--gross`) |\n| `stats-trend <name> [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Monthly trend for a category (supports `--unsplit` or `--gross`) |\n| `stats-top [--month <YYYY-MM>] [--limit <n>] [--period-type <type>] [--unsplit | --gross]` | Top spending categories sorted by total expenses (supports `--unsplit` or `--gross`) |\n| `stats-transfers [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Net capital transfers to external accounts (supports `--unsplit` or `--gross`) |\n| `stats-compare [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Month-over-month comparison (supports `--unsplit` or `--gross`) |\n| `stats-cashflow [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly cash flow summary (Operating, Transfers, Net; supports `--unsplit` or `--gross`) |\n| `link <from_id> [to_id] --type [--to-account <name_or_id>] [--ratio <val> \\| --ratio-to <val> \\| --amount <val>] [--dry-run]` | Link transactions (specify `--to-account` for external transfers, or ratio/amount options to customize values; `--dry-run` to preview) |\n| `unlink <id> [--yes]` | Remove a link (requires confirmation or `-y`) |\n| `links` | List all transaction links |\n| `auto-link [--dry-run] [--yes]` | Auto-detect and link internal transfers using transfer rules (requires confirmation or `-y` when not running dry-run) |\n| `recalculate` | Manually recalculate adjusted amounts for all transactions |\n| `db-cleanup [--dry-run] [--yes]` | Purge orphaned transaction links and rules (Integrity Cleanup) (requires confirmation or `-y` when not running dry-run) |\n| `cleanup-pending [--dry-run] [--yes] [--force-id <id>...]` | Delete ghost pending reservations whose settled counterpart already exists (individual, split-authorization, or inexact amount matches — e.g. merchants that authorize a buffer and settle a different final amount; inexact matches fire only when unambiguous). Unresolved pendings are kept, listed with nearby same-merchant candidates, flagged as probable cancellations when old with no counterpart, and can be deleted explicitly via `--force-id` (requires confirmation or `-y` when not running dry-run) |\n| `remove-transfer-rule <id> [--yes]` | Remove a transfer detection rule (requires confirmation or `-y`) |\n| `salary-config` | Show current salary period configuration |\n| `set-salary-mode <mode>` | Set the salary period mode (`calendar`, `fixed`, `salary`) |\n| `set-salary-day <day>` | Set the fixed boundary day of the month (1-28) |\n| `set-salary-category <name>` | Set the category name used to scan for salary paydays |\n| `recurring [--status <active|cancelled|all>]` | List recurring payments configurations |\n| `add-recurring <name> <pattern> [options] [--dry-run]` | Manually create a recurring payment config and link transactions |\n| `update-recurring <id> [options] [--dry-run]` | Update recurring config fields and re-run transaction linking |\n| `remove-recurring <id> [--hard] [--date <YYYY-MM-DD>] [--yes]` | Cancel (soft-close with end date) or hard-delete a recurring payment configuration (requires confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode) |\n| `discover-recurring [--dry-run] [--yes]` | Auto-discover recurring transaction patterns and auto-close dead configurations (saving, re-linking, and auto-closing require confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode — run `--dry-run` first to preview) |\n| `stats-recurring [query] [--month <YYYY-MM>] [--period-type <type>]` | Display subscription stats dashboard or detail reports for matching subscriptions |\n| `estimate-period [--days <int>] [--level <0|1|2>]` | Project spending and estimate total outflow remaining in current period (rollup levels: 0=none, 1=top, 2=detailed) |\n| `set-estimate-level <0|1|2>` | Set default category rollup level configuration for spending estimation |\n\n\n\n\n\n## Configuring Salary Periods\n\nBy default, the salary period boundary is fixed to the 25th of the month. You can customize this grouping behavior using the salary configuration commands.\n\n### Available Modes:\n1. **`calendar`**: Group transactions by calendar months (1st to the last day).\n2. **`fixed`**: Group transactions by a static day of the month (e.g., the 25th). Transactions on or after this day are grouped into the next month's period.\n3. **`salary`**: Group transactions by automatically detecting the primary salary deposit date in each month (the transaction under the salary category with the largest positive amount).\n\n### CLI Configuration Commands:\n```bash\n# View current configuration\npython cli.py salary-config\n\n# Change mode to salary (automatic payday detection)\npython cli.py set-salary-mode salary\n\n# Set the category name used to search for paydays (default is \"Salary\")\npython cli.py set-salary-category \"Salary\"\n\n# Change mode to a fixed day of the month (e.g. 27th)\npython cli.py set-salary-mode fixed\npython cli.py set-salary-day 27\n```\n\n> [!WARNING]\n> If you choose the **`fixed`** day mode, be aware that bank deposits and transactions can shift early or late due to weekends and holidays.\n> - Ensure your fixed day is configured early or late enough so that fluctuations in actual payday do not cause two salary deposits to fall into the same period (which would result in one month showing double income and the next showing zero income).\n> - Alternatively, use the **`salary`** mode, which automatically detects the actual deposit transaction dates and shifts the boundaries dynamically.\n\n### Querying Statistics by Salary Period\nAll statistics and breakdown commands support the `--period-type` parameter:\n* `calendar` — Force standard calendar month boundaries.\n* `salary` — Force salary period boundaries (using the active `salary-config` settings).\n* `default` — Dynamically resolve to your active `salary-config` mode:\n  - If mode is `calendar`, defaults to calendar months.\n  - If mode is `fixed` or `salary`, defaults to salary periods.\n\nFor example, to query your housing category spending using the active salary period:\n```bash\npython cli.py stats-category Housing --period-type salary --month 2026-06\n```\n\nIf you do not specify a `--period-type` flag, it will automatically default to the setting configured via `set-salary-mode`.\n\n## Tracking Recurring Payments & Subscriptions\n\nThis tool supports advanced, automated tracking and lifecycle management of recurring payments (e.g. Netflix, Spotify, broadband, utility bills) and income (e.g. Salary).\n\n### Core Concepts\n\n1. **Recurring Payments Table (`recurring_payments`)**\n   Defines the rules, intervals, expected days, amount ranges, and lifespans for each recurring item.\n2. **Subscription Lifecycle & Runs**\n   Resumed subscriptions (after cancellation) are tracked as separate runs/rows in `recurring_payments`.\n   * **Resumption**: If a transaction matches a pattern of a cancelled subscription (after its `end_date`), it automatically spawns a new run/configuration for the resumption.\n   * **Auto-Closing**: Active configurations that are missing expected payments are automatically closed (`end_date` is set to the last matched payment date) when running `discover-recurring` or passing the `--close` flag to `import` / `categorize`.\n   * **Confirmation Gates**: `remove-recurring` and `discover-recurring` (without `--dry-run`) prompt for confirmation before making changes, like the other destructive commands. Use `--yes`/`-y` to bypass (mandatory in non-interactive mode), and `discover-recurring --dry-run` to preview first.\n3. **Flexible Date Intervals**\n   Supports strict date/day checking with a configured tolerance window:\n   * **Monthly**: Expected day of month (e.g. 25th, last day `-1`).\n   * **Weekly**: Expected weekday.\n   * **Yearly**: Expected month and day.\n   * **Days**: Custom interval (e.g. every 90 days).\n   * **Tolerance**: Tolerates shifts due to weekends/holidays (default 4 days).\n\n### Common Workflows\n\n#### 1. Auto-Discover Recurring Candidates\nScan transaction history to auto-identify recurring items (such as monthly subscriptions or utility bills) and automatically save them:\n```bash\n# Preview candidates without writing to the database\npython cli.py discover-recurring --dry-run\n\n# Run auto-discovery and save configurations (prompts for confirmation; --yes to bypass)\npython cli.py discover-recurring --yes\n```\n\n#### 2. Manually Add/Update Configurations\n```bash\n# Add a monthly Netflix subscription\npython cli.py add-recurring Netflix \"netflix.com\" --amount-min -149 --amount-max -189 --interval monthly --day-of-month 6 --category Media\n\n# Dry-run update previewing matches\npython cli.py update-recurring 1 --amount-max -219 --dry-run\n```\n\n#### 3. View Outflow Dashboard & Stats\n```bash\n# Active subscriptions monthly cost summary and expected next dates\npython cli.py stats-recurring\n\n# Detailed subscription history across active/cancelled runs and transaction lists\npython cli.py stats-recurring \"Disney Plus\"\n```\n\n## Common Workflows\n\n\n### Handling Shared-Expense Reimbursements\n\nIf you make a shared purchase (e.g., from the `Gemensamt` account, 50% ownership) and get reimbursed by an external person (e.g., via Swish to your `Personligt` account, 100% ownership) and subsequently transfer the payback to the shared account:\n\n1. **Reimburse the shared expense**: Link the reimbursement transaction (the Swish inflow) directly to the original expense transaction (the shared purchase):\n   ```bash\n   python cli.py --db data/finance.db link <swish_transaction_id> <expense_transaction_id> --type reimbursement --ratio 1.0\n   ```\n   * *Effect*: The Swish transaction is fully neutralized to `0.00` adjusted amount, and the credit to the expense transaction is automatically scaled by the shared account's ownership ratio (e.g., 50%), reducing your net cost correctly.\n   * *Note*: The credit is scaled by the target account's ownership ratio (e.g., 50%) because the benefit of the payback is shared between the joint account owners.\n\n2. **Link the account transfer**: Link the outflow from your main account to the inflow on your joint account as an internal transfer:\n   ```bash\n   python cli.py --db data/finance.db link <transfer_out_id> <transfer_in_id> --type internal_transfer\n   ```\n   * *Effect*: Both sides of the transfer are neutralized to `0.00`, ensuring no false income or outflows are recorded.\n    * *Note*: This step is skipped if the transfer has already been auto-linked.\n\n### Managing Pending Reimbursements (Unlinked Inflows)\n\n#### Option A: Flat List Workflow (Keeping Them Uncategorized)\nUse the `--non-zero` flag on the `uncategorized` command to show pending actions (positive inflows to link, negative expenses to categorize):\n```bash\npython cli.py uncategorized --non-zero\n```\n\n#### Option B: Dedicated Category Workflow (Filtering Positive Inflows Only)\nTo auto-route only positive inflows (like Swish reimbursements) to a category (e.g., ID `9`) while leaving negative outflows uncategorized, add a rule with a minimum amount filter:\n```bash\npython cli.py add-rule 9 \"Swish\" --type contains --amount-min 0.01\n```\n\nQuery unlinked/pending reimbursements using:\n```bash\npython cli.py transactions --category Reimbursements --non-zero\n```\n*(Once linked, the adjusted amount drops to `0.00` and the transaction disappears from both lists).*\n\n### How to Think About Reimbursements & Composite Transactions\n\nWhen working with transaction links, it is crucial to distinguish between the **raw bank ledger amount** (actual cash flow) and the **effective category/budgetary amount** (represented by the `adjusted_amount` column).\n\n#### The Core Principle\nReimbursements are not new income; they are a return of capital.\n* If an expense is reimbursed, the net expense is zero.\n* The incoming reimbursement money is not labor/investment income; it simply offsets the expense.\n\nIf you don't link them, your gross income and gross expenses will both be overstated by the reimbursement amount, distorting your reports.\n\n#### Composite Transactions (e.g. Reimbursement Baked into Salary)\nOften, a reimbursement is not a standalone transaction (like a Swish payment), but is packaged/baked into a larger composite transaction, such as a salary payment.\nFor example, if your employer pays you a single amount of `50,000 SEK`, which contains:\n* `45,000 SEK` of actual labor income\n* `5,000 SEK` of expense reimbursement for a credit card charge\n\nTo avoid distorting both income and expenses, you must split this composite transaction. In this system, you do this using **transaction links** with fractional ratios.\n\n#### Link Ratio Calculation Modes\nThe `link` command provides three modes to simplify this:\n\n1. **Source Ratio (`--ratio <float>`)** - *Default*\n   Calculates the ratio relative to the source (`from_id`) transaction. Use when you want to allocate a direct fraction of the source.\n\n2. **Destination Ratio (`--ratio-to <float>`)**\n   Calculates the ratio relative to the destination (`to_id`) transaction.\n   * For example, to fully reimburse/zero out the `First Card` expense of `-5,000 SEK` from your salary, use:\n     ```bash\n     python cli.py link <salary_txn_id> <expense_txn_id> --type reimbursement --ratio-to 1.0\n     ```\n   * This automatically calculates the exact ratio ($5000 / 50000 = 0.10$). It reduces the salary's `adjusted_amount` to `45,000 SEK` (reflecting your true labor income) and increases the credit card expense's `adjusted_amount` to `0.00 SEK` (reflecting your true net expense).\n\n3. **Exact Cash (`--amount <float>`)**\n   Specify the exact cash amount in SEK being reimbursed.\n   * For example, to link exactly `5,000 SEK`:\n     ```bash\n     python cli.py link <salary_txn_id> <expense_txn_id> --type reimbursement --amount 5000\n     ```\n\n\n#### Dry-run Previews\nAlways run with the `--dry-run` flag first to preview the downstream `adjusted_amount` effects before committing changes to the database:\n```bash\npython cli.py link <from_id> <to_id> --type reimbursement --ratio-to 1.0 --dry-run\n```\n\n## Tracking External Accounts\n\nYou can track capital transfers from your tracked accounts to untracked external accounts (such as savings or stock brokerage accounts).\n\n### Setup and Workflow:\n1. **Create the External Account**:\n   ```bash\n   python cli.py add-account \"Avanza Brokerage\" --type external\n   ```\n2. **Associate a Category**:\n   Create a category of type `transfer` associated with this external account:\n   ```bash\n   python cli.py add-category \"Brokerage Transfer\" --type transfer --associated-account \"Avanza Brokerage\"\n   ```\n3. **Add a Categorization Rule**:\n   Add a match rule to auto-categorize transfers:\n   ```bash\n   python cli.py add-rule <category_id> \"AVANZA\" --type contains\n   ```\n4. **Auto-linking**:\n   When transactions are categorized (via `categorize` or manual overrides), if they match a transfer category linked to an external account, an `external_transfer` link is created automatically.\n\n### Manual Linking:\nFor one-off transfers, you can link a transaction directly to an external account:\n```bash\npython cli.py link <transaction_id> --type external_transfer --to-account \"Avanza Brokerage\"\n```\n\n### Querying Statistics:\nUse the `stats-transfers` command to view net capital movements per external account:\n```bash\npython cli.py stats-transfers --month 2026-06\n```\n\n## Skill Contents\n\n```\nfinancial-categorizer/\n├── SKILL.md                    # This file\n├── requirements.txt            # pip dependencies\n├── setup.py                    # setuptools configuration\n├── cli.py                      # Main entrypoint\n└── financial_categorizer/      # Package code\n    ├── __init__.py\n    ├── categorizer.py          # Auto-categorization & rule engine\n    ├── db_handler.py           # Database CRUD & raw schema setup\n    ├── importer.py             # CSV Parser (Nordea & ICA formats)\n    ├── matching.py             # Shared matching helpers (diacritic folding, aggregate tolerance)\n    └── stats.py                # SQL View registers and stats math\n```\n\n## SQLite Views\n\nFor analytical reporting (e.g. dashboards, Grafana), the following views are registered in the database:\n\n1. **`v_effective_transactions`** — Joins transactions with accounts to factor in ownership ratios and transfer link adjustments. Includes `adjusted_amount`, `unsplit_amount`, and `raw_amount` columns.\n2. **`v_monthly_summary`** — Calculates net income/expenses by month (includes unsplit and gross aggregations).\n3. **`v_category_monthly`** — Calculates monthly spending by category (includes unsplit and gross aggregations).\n4. **`v_daily_spending`** — Daily expense aggregation.\n5. **`v_cumulative_spending_monthly`** — Running month-to-date daily cumulative spending.\n6. **`v_daily_spending_moving_average`** — 30-day moving average of daily spending.\n7. **`v_category_monthly_averages`** — Average monthly spending by category.\n8. **`v_salary_period_summary`** — Expense/income summary grouped by salary periods (using the active salary config: fixed or salary).\n9. **`v_breakout_categories`** — Groups monthly spending into high-level categories (Groceries, Loans, Housing, Leisure, Car, etc.).\n10. **`v_uncategorized_groups`** — Groups uncategorized transactions by normalized Swish/Card payment descriptions to identify potential new rules.\n\n---\n\n## Querying Unmatched Reimbursements via CLI\n\nUnmatched reimbursements (pending paybacks or refunds) are incoming transactions on tracked accounts that have not yet been neutralized by a transaction link. They can be queried and filtered using the CLI:\n\n### Workflow:\n\n1. **Query the transactions** depending on the categorization workflow in use:\n   * **If using Uncategorized / Flat List (Option A)**:\n     ```bash\n     python cli.py uncategorized --non-zero\n     ```\n   * **If using a Dedicated Category (Option B)**:\n     ```bash\n     python cli.py transactions --category Reimbursements --non-zero --limit 100\n     ```\n\n2. **Identify candidates from the output**:\n   * **Inflows**: Look for transactions with positive amounts (`amount > 0`).\n   * **Regular Income Exclusions**: Filter out regular income sources (e.g., `\"Lön\"`, `\"Salary\"`, `\"BARNBDR\"`).\n   * **Adjusted Amount**: Verify that the adjusted amount is non-zero (linked/neutralized transactions show `adjusted_amount = 0.00`).\n\n### Example Filter Logic:\nIf the CLI output shows:\n```\nTransactions (4):\n  [12] 2026-06-23   25000.00 SEK                      Personligt      [Uncategorized]     Lön\n  [15] 2026-06-18    1250.00 SEK                      Personligt      [Uncategorized]     BARNBDR\n  [18] 2026-06-16     500.00 SEK                      Personligt      [Uncategorized]     Swish inbetalning DOE, JOHN\n  [21] 2026-05-20      50.00 SEK                      Personligt      [Uncategorized]     Swish inbetalning DOE, JOHN\n```\n* **Include**: `[18]` (+500.00) and `[21]` (+50.00) (positive Swish payments from an individual are reimbursement candidates).\n* **Exclude**: `[12]` (Lön/Salary) and `[15]` (barnbidrag/regular benefit payment).\n\n\n## Dependencies\n\n- `pytest` - For testing suite\n- Standard library modules: `sqlite3`, `csv`, `datetime`, `logging`, `re`, `argparse`, `os`\n\nInstall: `pip install -e .`\n\nFile v1.15.0:README.md\n\n# financial-categorizer\n\nPersonal finance transaction categorization with a SQLite backend.\n\nImports bank CSV files, auto-categorizes transactions using configurable rules, and provides SQL views for dashboards and analysis.\n\n## Features\n\n- **Multi-account support** — tracked active accounts and external savings/investments with ownership ratios\n- **Auto-categorization** — regex, exact, and contains match rules with priority ordering and manual overrides\n- **Transaction linking** — mark transfers and reimbursements between transactions; adjusted amounts are pre-computed\n- **SQL views** — ready-to-query views for monthly summaries, category breakdowns, and daily spending\n- **CSV import** — auto-detects Nordea and ICA formats, handles pending transactions (umlaut-variant, split-authorization, and inexact buffer settlement; see [Pending Transactions](#pending-transactions-reservations))\n- **CLI** — full command-line interface for all operations\n\n## Install\n\n```bash\npip install -e .\n```\n\nRequires Python 3.10+.\n\n## Quick Start\n\n```bash\n# Import transactions from a CSV\nfinancial-categorizer import transactions.csv --account \"Nordea Checking\"\n\n# Add categorization rules\nfinancial-categorizer add-rule 3 \"ICA MAXI\" --type contains\nfinancial-categorizer add-rule 4 \"^Hyra\" --type regex\n\n# Categorize uncategorized transactions\nfinancial-categorizer categorize\n\n# View stats\nfinancial-categorizer stats-summary --month 2026-04\nfinancial-categorizer stats-top --limit 10\nfinancial-categorizer stats-category Food --month 2026-04\n\n# Link a transfer between accounts\nfinancial-categorizer link 1 2 --type internal_transfer\n\n# Recalculate adjusted amounts\nfinancial-categorizer recalculate\n```\n\n## Architecture\n\nAll data lives in a single SQLite database (`data/finance.db` by default).\n\n### Core tables\n- **accounts** — bank accounts with type and ownership ratio\n- **transactions** — imported transactions with `adjusted_amount` (pre-computed)\n- **categories** — hierarchical categories (parent/child)\n- **match_rules** — patterns for auto-categorization\n- **transaction_links** — connects transfers and reimbursements\n\n### Projection tables\n- **period_projection** — persisted projection of the current period (income-anchored burn-down), refreshed automatically after data mutations (import, categorize, manual match/unmatch, link/unlink); the latest refresh wins\n- **period_projection_items** — itemized expected occurrences (date, name, amount) covering the full period, with an `upcoming` flag (date > as-of); zero-amount rows filtered\n\n### Views\n- `v_effective_transactions` — all transactions with adjusted, unsplit, and raw amounts\n- `v_monthly_summary` — income, expenses, net per month (includes unsplit and gross aggregations)\n- `v_category_monthly` — category totals per month (includes unsplit and gross aggregations)\n- `v_daily_spending` — daily spending breakdown\n- `v_burn_down` — income-anchored burn-down derived from `period_projection`\n\n### Adjusted, Unsplit, and Gross amounts\n\n1. `adjusted_amount` (Personal Share): Your share of the transaction. Calculated as `amount * account.ownership_ratio` (base), then adjusted by transfers and reimbursements.\n2. `unsplit_amount` (Household Net): Full household cost net of reimbursements. Calculated as `adjusted_amount / account.ownership_ratio`. Enabled in stats with the `--unsplit` flag.\n3. `raw_amount` (Household Raw): Full raw household cost before split and before reimbursements (i.e. the raw bank statement amount). Enabled in stats with the `--gross` flag.\n\nStats and views read these columns directly. Run `recalculate` to refresh after any manual changes.\n\n## Pending Transactions (Reservations)\n\nCard reservations (`Reserverat` rows) are temporary holds the bank places before a merchant settles the final charge. The intent behind the pending lifecycle is simple: **every reservation goes somewhere** — it settles (at the same amount, as a re-authorized group, or at a different final amount) or it was cancelled. A reservation should never linger silently in the database as a second copy of a purchase that already settled.\n\nAt import, a settled charge resolves an outstanding reservation through three passes, in order:\n\n1. **Exact** — settled amount within 1.0 SEK of the reservation.\n2. **Split-authorization** — 2–4 reservations for the same merchant that sum to the settled charge (within `max(1.0 SEK, 0.5%)`).\n3. **Inexact** — for merchants that authorize a buffer and settle a different final amount (weighed goods, fuel pre-auths, tips, currency conversion). The settled amount must be within **85%–105%** of the reservation (same sign). The floor is deliberately conservative: typical buffer haircuts are modest and auto-resolve, while a settlement far below the reservation — which could be a genuinely separate purchase from the same merchant — is left for manual review instead of an automatic guess.\n\nInexact matching only fires when the pairing is **mutually unique**: exactly one outstanding reservation lies inside the settled charge's amount band, AND no other settled charge in the database can claim that reservation. Any ambiguity — two candidate reservations, or two candidate settled charges — means no automatic action; the case is surfaced for review instead.\n\nAdditional lifecycle guarantees:\n\n- **Re-listed reservations are skipped, not re-inserted** — an outstanding reservation that appears again in a later export is the same pending, not a new transaction.\n- **Disappearance warnings** — a pending reservation that is absent from a current export, at least 14 days old, with no settled counterpart, triggers an import warning: it most likely was cancelled or settled under a different amount, and belongs in `cleanup-pending`.\n- **`cleanup-pending`** deletes provable ghosts (labeled exact / split / inexact), lists unresolved reservations with nearby same-merchant settled charges as candidate hints, flags long-outstanding pendings with no candidates as probable cancellations, and accepts `--force-id <id>` (repeatable) to delete a specific pending explicitly after review.\n\n## Security & Data Integrity\n\nThis tool modifies your local SQLite database. To prevent accidental data loss, please observe the following guidelines:\n\n> [!WARNING]\n> Always make a backup of your database before performing database cleanup, auto-linking, or destructive operations:\n> ```bash\n> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n> ```\n\n### Destructive Operations & Confirmation Prompts\nDestructive commands require interactive confirmation `[y/N]` when run in a terminal (TTY). If you are running these commands in automated scripts or non-interactive shells, you must pass the `--yes` or `-y` flag to bypass the prompt; otherwise, the command will abort with an error.\n\nThe following commands require confirmation:\n- `delete-account <id> [--yes]`\n- `delete-category <id> [--yes] [--reassign <id>] [--force]`\n- `remove-rule <id> [--yes]`\n- `unlink <id> [--yes]`\n- `db-cleanup [--yes] [--dry-run]`\n- `cleanup-pending [--yes] [--dry-run] [--force-id <id>...]`\n- `remove-transfer-rule <id> [--yes]`\n- `auto-link [--yes] [--dry-run]`\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `import <files>` | Import bank CSV transactions |\n| `accounts` | List all registered bank accounts |\n| `add-account <name>` | Create a new bank account |\n| `update-account <id>` | Update account ownership ratio, type, name, etc. |\n| `delete-account <id> [--yes]` | Delete a bank account (requires confirmation or `-y`) |\n| `categories` | List all categories in tree view |\n| `add-category <name> [--associated-account <name_or_id>]` | Create a new category, optionally associated with an external account |\n| `update-category <id> [--associated-account <name_or_id>]` | Update category parents or fields (use `none` to clear association) |\n| `delete-category <id> [--yes]` | Delete a category (requires confirmation or `-y`) |\n| `rules [txn_id]` | List all match rules, or show the matching rule for a specific transaction |\n| `add-rule <cat_id> <pattern>` | Add a categorization rule (regex, contains, exact) |\n| `remove-rule <id> [--yes]` | Remove an auto-categorization rule (requires confirmation or `-y`) |\n| `preview <pattern>` | Preview which transactions match a pattern before adding a rule |\n| `categorize [--all]` | Run auto-categorization rules |\n| `uncategorized [--group] [--non-zero] [--net | --unsplit]` | List all uncategorized transactions (supports `--net` or `--unsplit`) |\n| `transactions [--category <name>] [--uncategorized] [--non-zero] [--account <name>] [--limit <n>] [--net | --unsplit]` | Search, list, and filter transactions (supports `--net` or `--unsplit`) |\n| `manual-match <txn_id> <cat_id>` | Manually assign a category override to a transaction |\n| `manual-unmatch <txn_id>` | Remove a manual categorization override |\n| `stats-summary [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly summary of income, expenses, and net (supports `--unsplit` or `--gross`) |\n| `stats-category <name> [--month <YYYY-MM>] [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Category total with subcategory rollups (supports `--unsplit` or `--gross`) |\n| `stats-trend <name> [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Monthly trend for a category (supports `--unsplit` or `--gross`) |\n| `stats-top [--month <YYYY-MM>] [--limit <n>] [--period-type <type>] [--unsplit | --gross]` | Top spending categories sorted by total expenses (supports `--unsplit` or `--gross`) |\n| `stats-transfers [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Net capital transfers to external accounts (supports `--unsplit` or `--gross`) |\n| `stats-compare [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Month-over-month comparison (supports `--unsplit` or `--gross`) |\n| `stats-cashflow [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly cash flow summary (Operating, Transfers, Net; supports `--unsplit` or `--gross`) |\n| `link <from_id> [to_id] --type [--to-account <name_or_id>] [--ratio <val> \\| --ratio-to <val> \\| --amount <val>] [--dry-run]` | Link transactions (specify `--to-account` for external transfers, or ratio/amount options to customize values; `--dry-run` to preview) |\n| `unlink <id> [--yes]` | Remove a link (requires confirmation or `-y`) |\n| `links` | List all transaction links |\n| `auto-link [--dry-run] [--yes]` | Auto-detect and link internal transfers using transfer rules (requires confirmation or `-y` when not running dry-run) |\n| `recalculate` | Manually recalculate adjusted amounts for all transactions |\n| `db-cleanup [--dry-run] [--yes]` | Purge orphaned transaction links and rules (Integrity Cleanup) (requires confirmation or `-y` when not running dry-run) |\n| `cleanup-pending [--dry-run] [--yes] [--force-id <id>...]` | Delete ghost pending reservations whose settled counterpart already exists (individual, split-authorization, or inexact amount matches — e.g. merchants that authorize a buffer and settle a different final amount; inexact matches fire only when unambiguous). Unresolved pendings are kept, listed with nearby same-merchant candidates, flagged as probable cancellations when old with no counterpart, and can be deleted explicitly via `--force-id` (requires confirmation or `-y` when not running dry-run) |\n| `remove-transfer-rule <id> [--yes]` | Remove a transfer detection rule (requires confirmation or `-y`) |\n| `salary-config` | Show current salary period configuration |\n| `set-salary-mode <mode>` | Set the salary period mode (`calendar`, `fixed`, `salary`) |\n| `set-salary-day <day>` | Set the fixed boundary day of the month (1-28) |\n| `set-salary-category <name>` | Set the category name used to scan for salary paydays |\n| `recurring [--status <active|cancelled|all>]` | List recurring payments configurations |\n| `add-recurring <name> <pattern> [options] [--dry-run]` | Manually create a recurring payment config and link transactions |\n| `update-recurring <id> [options] [--dry-run]` | Update recurring config fields and re-run transaction linking |\n| `remove-recurring <id> [--hard] [--date <YYYY-MM-DD>] [--yes]` | Cancel (soft-close with end date) or hard-delete a recurring payment configuration (requires confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode) |\n| `discover-recurring [--dry-run] [--yes]` | Auto-discover recurring transaction patterns and auto-close dead configurations (saving, re-linking, and auto-closing require confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode — run `--dry-run` first to preview) |\n| `stats-recurring [query] [--month <YYYY-MM>] [--period-type <type>]` | Display subscription stats dashboard or detail reports for matching subscriptions |\n| `estimate-period [--days <int>] [--level <0|1|2>]` | Project spending and estimate total outflow remaining in current period (rollup levels: 0=none, 1=top, 2=detailed) |\n| `set-estimate-level <0|1|2>` | Set default category rollup level configuration for spending estimation |\n\n\n\n\n## Testing\n\n```bash\npip install pytest\npytest tests/\n```\n\nFile v1.15.0:_meta.json\n\n{\n  \"ownerId\": \"kn7cxaw1m27d1fe93wbz40vqkd8309h7\",\n  \"slug\": \"financial-categorizer\",\n  \"version\": \"1.15.0\",\n  \"publishedAt\": 1788638958744\n}\n\nFile v1.15.0:skill-card.md\n\n## Description:\n\nProcess bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[patello](https://clawhub.ai/user/patello)\n\n### License/Terms of Use:\n\nMIT License\n\n## Use Case:\n\nDevelopers and agents use this skill to operate a local personal-finance CLI that imports bank CSV exports, manages categorization rules and transaction links, and produces SQLite-backed spending and cash-flow analysis.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can modify a local finance SQLite database.\n\nMitigation: Keep database backups and run dry-run or preview commands before cleanup, auto-linking, recurring discovery, or other data-changing operations.\n\nRisk: Automated use of confirmation bypass flags can make destructive operations easier to run unintentionally.\n\nMitigation: Avoid giving agents blanket permission to pass --yes; require explicit approval for cleanup, deletion, unlinking, and auto-linking workflows.\n\nRisk: Bad regular-expression rules can hang or crash local categorization workflows.\n\nMitigation: Preview new matching rules first and keep patterns narrow before applying them to the finance database.\n\n## Reference(s):\n\n- [ClawHub skill page](https://clawhub.ai/patello/skills/financial-categorizer)\n\n## Skill Output:\n\n**Output Type(s):** [Shell commands, Configuration, Text, Guidance]\n\n**Output Format:** [Markdown guidance with inline shell commands and CLI text output]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Operates on local CSV files and a local SQLite finance database.]\n\n## Skill Version(s):\n\n1.15.0 (source: server release metadata and artifact _meta.json)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v1.15.0:changelog.txt\n\n- tune: raise inexact floor to 85%, document pending lifecycle intent\n- feat: cleanup-pending inexact matches and --force-id manual override\n- feat: inexact settlement matching and pending disappearance warnings\n\nFile v1.15.0:LICENSE\n\nMIT License\n\nCopyright (c) 2021 Patrik Ekenberg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nFile v1.15.0:requirements.txt\n\n# financial-categorizer has no external dependencies\n# All requirements are Python stdlib (sqlite3, csv, argparse, datetime, logging)\n\nArchive v1.14.0: 16 files, 80144 bytes\n\nFiles: _meta.json (141b), changelog.txt (136b), cli.py (105789b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (28063b), financial_categorizer/db_handler.py (54070b), financial_categorizer/importer.py (21308b), financial_categorizer/matching.py (2110b), financial_categorizer/recurring.py (43187b), financial_categorizer/stats.py (61275b), LICENSE (1072b), README.md (10503b), requirements.txt (134b), setup.py (249b), skill-card.md (2315b), SKILL.md (24167b)\n\nFile v1.14.0:SKILL.md\n\n---\nname: financial-categorizer\ndescription: \"Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\"\nmetadata:\n  openclaw:\n    requires:\n      bins:\n        - python3\n---\n\n\n# financial-categorizer\n\nProcess bank transaction CSV exports, auto-categorize transactions using configurable rules, manage transaction links, and generate analytical SQLite database views.\n\n## Quick Start\n\nRun the CLI tool from your terminal pointing to your database path:\n\n```bash\n# 1. Add your main checking account\npython cli.py --db ../data/finance.db add-account \"Nordea Checking\" --type tracked --ownership 1.0\n\n# 2. Add hierarchical categories\npython cli.py --db ../data/finance.db add-category \"Food\"\npython cli.py --db ../data/finance.db add-category \"Groceries\" --parent 1\n\n# 3. Add auto-categorization rules\npython cli.py --db ../data/finance.db add-rule 2 \"ICA MAXI\" --type contains\npython cli.py --db ../data/finance.db add-rule 1 \"^Hyra\" --type regex\n\n# 4. Import transactions from a bank CSV file\npython cli.py --db ../data/finance.db import transactions.csv --account \"Nordea Checking\"\n\n# 5. Run auto-categorization over uncategorized transactions\npython cli.py --db ../data/finance.db categorize\n\n# 6. View monthly summary statistics\npython cli.py --db ../data/finance.db stats-summary\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/financial-categorizer/   # Portable skill (shareable)\n│   ├── SKILL.md\n│   ├── cli.py\n│   ├── setup.py\n│   └── financial_categorizer/\n└── data/                           # Your private data\n    ├── finance.db\n    └── exports/\n        ├── Nordea_Checking.csv\n        └── ICA_Shared.csv\n```\n\nThe skill provides logic. Your data stays private and portable.\n\n## Security & Data Integrity\n\nThis tool modifies your local SQLite database. To prevent accidental data loss, please observe the following guidelines:\n\n> [!WARNING]\n> Always make a backup of your database before performing database cleanup, auto-linking, or destructive operations:\n> ```bash\n> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n> ```\n\n### Destructive Operations & Confirmation Prompts\nDestructive commands require interactive confirmation `[y/N]` when run in a terminal (TTY). If you are running these commands in automated scripts or non-interactive shells, you must pass the `--yes` or `-y` flag to bypass the prompt; otherwise, the command will abort with an error.\n\nThe following commands require confirmation:\n- `delete-account <id> [--yes]`\n- `delete-category <id> [--yes] [--reassign <id>] [--force]`\n- `remove-rule <id> [--yes]`\n- `unlink <id> [--yes]`\n- `db-cleanup [--yes] [--dry-run]`\n- `cleanup-pending [--yes] [--dry-run]`\n- `remove-transfer-rule <id> [--yes]`\n- `auto-link [--yes] [--dry-run]`\n\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `import <files>` | Import bank CSV transactions |\n| `accounts` | List all registered bank accounts |\n| `add-account <name>` | Create a new bank account |\n| `update-account <id>` | Update account ownership ratio, type, name, etc. |\n| `delete-account <id> [--yes]` | Delete a bank account (requires confirmation or `-y`) |\n| `categories` | List all categories in tree view |\n| `add-category <name> [--associated-account <name_or_id>]` | Create a new category, optionally associated with an external account |\n| `update-category <id> [--associated-account <name_or_id>]` | Update category parents or fields (use `none` to clear association) |\n| `delete-category <id> [--yes]` | Delete a category (requires confirmation or `-y`) |\n| `rules [txn_id]` | List all match rules, or show the matching rule for a specific transaction |\n| `add-rule <cat_id> <pattern>` | Add a categorization rule (regex, contains, exact) |\n| `remove-rule <id> [--yes]` | Remove an auto-categorization rule (requires confirmation or `-y`) |\n| `preview <pattern>` | Preview which transactions match a pattern before adding a rule |\n| `categorize [--all]` | Run auto-categorization rules |\n| `uncategorized [--group] [--non-zero] [--net | --unsplit]` | List all uncategorized transactions (supports `--net` or `--unsplit`) |\n| `transactions [--category <name>] [--uncategorized] [--non-zero] [--account <name>] [--limit <n>] [--net | --unsplit]` | Search, list, and filter transactions (supports `--net` or `--unsplit`) |\n| `manual-match <txn_id> <cat_id>` | Manually assign a category override to a transaction |\n| `manual-unmatch <txn_id>` | Remove a manual categorization override |\n| `stats-summary [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly summary of income, expenses, and net (supports `--unsplit` or `--gross`) |\n| `stats-category <name> [--month <YYYY-MM>] [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Category total with subcategory rollups (supports `--unsplit` or `--gross`) |\n| `stats-trend <name> [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Monthly trend for a category (supports `--unsplit` or `--gross`) |\n| `stats-top [--month <YYYY-MM>] [--limit <n>] [--period-type <type>] [--unsplit | --gross]` | Top spending categories sorted by total expenses (supports `--unsplit` or `--gross`) |\n| `stats-transfers [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Net capital transfers to external accounts (supports `--unsplit` or `--gross`) |\n| `stats-compare [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Month-over-month comparison (supports `--unsplit` or `--gross`) |\n| `stats-cashflow [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly cash flow summary (Operating, Transfers, Net; supports `--unsplit` or `--gross`) |\n| `link <from_id> [to_id] --type [--to-account <name_or_id>] [--ratio <val> \\| --ratio-to <val> \\| --amount <val>] [--dry-run]` | Link transactions (specify `--to-account` for external transfers, or ratio/amount options to customize values; `--dry-run` to preview) |\n| `unlink <id> [--yes]` | Remove a link (requires confirmation or `-y`) |\n| `links` | List all transaction links |\n| `auto-link [--dry-run] [--yes]` | Auto-detect and link internal transfers using transfer rules (requires confirmation or `-y` when not running dry-run) |\n| `recalculate` | Manually recalculate adjusted amounts for all transactions |\n| `db-cleanup [--dry-run] [--yes]` | Purge orphaned transaction links and rules (Integrity Cleanup) (requires confirmation or `-y` when not running dry-run) |\n| `cleanup-pending [--dry-run] [--yes]` | Delete ghost pending reservations whose settled counterpart already exists (individual or split-authorization matches); unresolved pendings are kept and listed for manual review (requires confirmation or `-y` when not running dry-run) |\n| `remove-transfer-rule <id> [--yes]` | Remove a transfer detection rule (requires confirmation or `-y`) |\n| `salary-config` | Show current salary period configuration |\n| `set-salary-mode <mode>` | Set the salary period mode (`calendar`, `fixed`, `salary`) |\n| `set-salary-day <day>` | Set the fixed boundary day of the month (1-28) |\n| `set-salary-category <name>` | Set the category name used to scan for salary paydays |\n| `recurring [--status <active|cancelled|all>]` | List recurring payments configurations |\n| `add-recurring <name> <pattern> [options] [--dry-run]` | Manually create a recurring payment config and link transactions |\n| `update-recurring <id> [options] [--dry-run]` | Update recurring config fields and re-run transaction linking |\n| `remove-recurring <id> [--hard] [--date <YYYY-MM-DD>] [--yes]` | Cancel (soft-close with end date) or hard-delete a recurring payment configuration (requires confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode) |\n| `discover-recurring [--dry-run] [--yes]` | Auto-discover recurring transaction patterns and auto-close dead configurations (saving, re-linking, and auto-closing require confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode — run `--dry-run` first to preview) |\n| `stats-recurring [query] [--month <YYYY-MM>] [--period-type <type>]` | Display subscription stats dashboard or detail reports for matching subscriptions |\n| `estimate-period [--days <int>] [--level <0|1|2>]` | Project spending and estimate total outflow remaining in current period (rollup levels: 0=none, 1=top, 2=detailed) |\n| `set-estimate-level <0|1|2>` | Set default category rollup level configuration for spending estimation |\n\n\n\n\n\n## Configuring Salary Periods\n\nBy default, the salary period boundary is fixed to the 25th of the month. You can customize this grouping behavior using the salary configuration commands.\n\n### Available Modes:\n1. **`calendar`**: Group transactions by calendar months (1st to the last day).\n2. **`fixed`**: Group transactions by a static day of the month (e.g., the 25th). Transactions on or after this day are grouped into the next month's period.\n3. **`salary`**: Group transactions by automatically detecting the primary salary deposit date in each month (the transaction under the salary category with the largest positive amount).\n\n### CLI Configuration Commands:\n```bash\n# View current configuration\npython cli.py salary-config\n\n# Change mode to salary (automatic payday detection)\npython cli.py set-salary-mode salary\n\n# Set the category name used to search for paydays (default is \"Salary\")\npython cli.py set-salary-category \"Salary\"\n\n# Change mode to a fixed day of the month (e.g. 27th)\npython cli.py set-salary-mode fixed\npython cli.py set-salary-day 27\n```\n\n> [!WARNING]\n> If you choose the **`fixed`** day mode, be aware that bank deposits and transactions can shift early or late due to weekends and holidays.\n> - Ensure your fixed day is configured early or late enough so that fluctuations in actual payday do not cause two salary deposits to fall into the same period (which would result in one month showing double income and the next showing zero income).\n> - Alternatively, use the **`salary`** mode, which automatically detects the actual deposit transaction dates and shifts the boundaries dynamically.\n\n### Querying Statistics by Salary Period\nAll statistics and breakdown commands support the `--period-type` parameter:\n* `calendar` — Force standard calendar month boundaries.\n* `salary` — Force salary period boundaries (using the active `salary-config` settings).\n* `default` — Dynamically resolve to your active `salary-config` mode:\n  - If mode is `calendar`, defaults to calendar months.\n  - If mode is `fixed` or `salary`, defaults to salary periods.\n\nFor example, to query your housing category spending using the active salary period:\n```bash\npython cli.py stats-category Housing --period-type salary --month 2026-06\n```\n\nIf you do not specify a `--period-type` flag, it will automatically default to the setting configured via `set-salary-mode`.\n\n## Tracking Recurring Payments & Subscriptions\n\nThis tool supports advanced, automated tracking and lifecycle management of recurring payments (e.g. Netflix, Spotify, broadband, utility bills) and income (e.g. Salary).\n\n### Core Concepts\n\n1. **Recurring Payments Table (`recurring_payments`)**\n   Defines the rules, intervals, expected days, amount ranges, and lifespans for each recurring item.\n2. **Subscription Lifecycle & Runs**\n   Resumed subscriptions (after cancellation) are tracked as separate runs/rows in `recurring_payments`.\n   * **Resumption**: If a transaction matches a pattern of a cancelled subscription (after its `end_date`), it automatically spawns a new run/configuration for the resumption.\n   * **Auto-Closing**: Active configurations that are missing expected payments are automatically closed (`end_date` is set to the last matched payment date) when running `discover-recurring` or passing the `--close` flag to `import` / `categorize`.\n   * **Confirmation Gates**: `remove-recurring` and `discover-recurring` (without `--dry-run`) prompt for confirmation before making changes, like the other destructive commands. Use `--yes`/`-y` to bypass (mandatory in non-interactive mode), and `discover-recurring --dry-run` to preview first.\n3. **Flexible Date Intervals**\n   Supports strict date/day checking with a configured tolerance window:\n   * **Monthly**: Expected day of month (e.g. 25th, last day `-1`).\n   * **Weekly**: Expected weekday.\n   * **Yearly**: Expected month and day.\n   * **Days**: Custom interval (e.g. every 90 days).\n   * **Tolerance**: Tolerates shifts due to weekends/holidays (default 4 days).\n\n### Common Workflows\n\n#### 1. Auto-Discover Recurring Candidates\nScan transaction history to auto-identify recurring items (such as monthly subscriptions or utility bills) and automatically save them:\n```bash\n# Preview candidates without writing to the database\npython cli.py discover-recurring --dry-run\n\n# Run auto-discovery and save configurations (prompts for confirmation; --yes to bypass)\npython cli.py discover-recurring --yes\n```\n\n#### 2. Manually Add/Update Configurations\n```bash\n# Add a monthly Netflix subscription\npython cli.py add-recurring Netflix \"netflix.com\" --amount-min -149 --amount-max -189 --interval monthly --day-of-month 6 --category Media\n\n# Dry-run update previewing matches\npython cli.py update-recurring 1 --amount-max -219 --dry-run\n```\n\n#### 3. View Outflow Dashboard & Stats\n```bash\n# Active subscriptions monthly cost summary and expected next dates\npython cli.py stats-recurring\n\n# Detailed subscription history across active/cancelled runs and transaction lists\npython cli.py stats-recurring \"Disney Plus\"\n```\n\n## Common Workflows\n\n\n### Handling Shared-Expense Reimbursements\n\nIf you make a shared purchase (e.g., from the `Gemensamt` account, 50% ownership) and get reimbursed by an external person (e.g., via Swish to your `Personligt` account, 100% ownership) and subsequently transfer the payback to the shared account:\n\n1. **Reimburse the shared expense**: Link the reimbursement transaction (the Swish inflow) directly to the original expense transaction (the shared purchase):\n   ```bash\n   python cli.py --db data/finance.db link <swish_transaction_id> <expense_transaction_id> --type reimbursement --ratio 1.0\n   ```\n   * *Effect*: The Swish transaction is fully neutralized to `0.00` adjusted amount, and the credit to the expense transaction is automatically scaled by the shared account's ownership ratio (e.g., 50%), reducing your net cost correctly.\n   * *Note*: The credit is scaled by the target account's ownership ratio (e.g., 50%) because the benefit of the payback is shared between the joint account owners.\n\n2. **Link the account transfer**: Link the outflow from your main account to the inflow on your joint account as an internal transfer:\n   ```bash\n   python cli.py --db data/finance.db link <transfer_out_id> <transfer_in_id> --type internal_transfer\n   ```\n   * *Effect*: Both sides of the transfer are neutralized to `0.00`, ensuring no false income or outflows are recorded.\n    * *Note*: This step is skipped if the transfer has already been auto-linked.\n\n### Managing Pending Reimbursements (Unlinked Inflows)\n\n#### Option A: Flat List Workflow (Keeping Them Uncategorized)\nUse the `--non-zero` flag on the `uncategorized` command to show pending actions (positive inflows to link, negative expenses to categorize):\n```bash\npython cli.py uncategorized --non-zero\n```\n\n#### Option B: Dedicated Category Workflow (Filtering Positive Inflows Only)\nTo auto-route only positive inflows (like Swish reimbursements) to a category (e.g., ID `9`) while leaving negative outflows uncategorized, add a rule with a minimum amount filter:\n```bash\npython cli.py add-rule 9 \"Swish\" --type contains --amount-min 0.01\n```\n\nQuery unlinked/pending reimbursements using:\n```bash\npython cli.py transactions --category Reimbursements --non-zero\n```\n*(Once linked, the adjusted amount drops to `0.00` and the transaction disappears from both lists).*\n\n### How to Think About Reimbursements & Composite Transactions\n\nWhen working with transaction links, it is crucial to distinguish between the **raw bank ledger amount** (actual cash flow) and the **effective category/budgetary amount** (represented by the `adjusted_amount` column).\n\n#### The Core Principle\nReimbursements are not new income; they are a return of capital.\n* If an expense is reimbursed, the net expense is zero.\n* The incoming reimbursement money is not labor/investment income; it simply offsets the expense.\n\nIf you don't link them, your gross income and gross expenses will both be overstated by the reimbursement amount, distorting your reports.\n\n#### Composite Transactions (e.g. Reimbursement Baked into Salary)\nOften, a reimbursement is not a standalone transaction (like a Swish payment), but is packaged/baked into a larger composite transaction, such as a salary payment.\nFor example, if your employer pays you a single amount of `50,000 SEK`, which contains:\n* `45,000 SEK` of actual labor income\n* `5,000 SEK` of expense reimbursement for a credit card charge\n\nTo avoid distorting both income and expenses, you must split this composite transaction. In this system, you do this using **transaction links** with fractional ratios.\n\n#### Link Ratio Calculation Modes\nThe `link` command provides three modes to simplify this:\n\n1. **Source Ratio (`--ratio <float>`)** - *Default*\n   Calculates the ratio relative to the source (`from_id`) transaction. Use when you want to allocate a direct fraction of the source.\n\n2. **Destination Ratio (`--ratio-to <float>`)**\n   Calculates the ratio relative to the destination (`to_id`) transaction.\n   * For example, to fully reimburse/zero out the `First Card` expense of `-5,000 SEK` from your salary, use:\n     ```bash\n     python cli.py link <salary_txn_id> <expense_txn_id> --type reimbursement --ratio-to 1.0\n     ```\n   * This automatically calculates the exact ratio ($5000 / 50000 = 0.10$). It reduces the salary's `adjusted_amount` to `45,000 SEK` (reflecting your true labor income) and increases the credit card expense's `adjusted_amount` to `0.00 SEK` (reflecting your true net expense).\n\n3. **Exact Cash (`--amount <float>`)**\n   Specify the exact cash amount in SEK being reimbursed.\n   * For example, to link exactly `5,000 SEK`:\n     ```bash\n     python cli.py link <salary_txn_id> <expense_txn_id> --type reimbursement --amount 5000\n     ```\n\n\n#### Dry-run Previews\nAlways run with the `--dry-run` flag first to preview the downstream `adjusted_amount` effects before committing changes to the database:\n```bash\npython cli.py link <from_id> <to_id> --type reimbursement --ratio-to 1.0 --dry-run\n```\n\n## Tracking External Accounts\n\nYou can track capital transfers from your tracked accounts to untracked external accounts (such as savings or stock brokerage accounts).\n\n### Setup and Workflow:\n1. **Create the External Account**:\n   ```bash\n   python cli.py add-account \"Avanza Brokerage\" --type external\n   ```\n2. **Associate a Category**:\n   Create a category of type `transfer` associated with this external account:\n   ```bash\n   python cli.py add-category \"Brokerage Transfer\" --type transfer --associated-account \"Avanza Brokerage\"\n   ```\n3. **Add a Categorization Rule**:\n   Add a match rule to auto-categorize transfers:\n   ```bash\n   python cli.py add-rule <category_id> \"AVANZA\" --type contains\n   ```\n4. **Auto-linking**:\n   When transactions are categorized (via `categorize` or manual overrides), if they match a transfer category linked to an external account, an `external_transfer` link is created automatically.\n\n### Manual Linking:\nFor one-off transfers, you can link a transaction directly to an external account:\n```bash\npython cli.py link <transaction_id> --type external_transfer --to-account \"Avanza Brokerage\"\n```\n\n### Querying Statistics:\nUse the `stats-transfers` command to view net capital movements per external account:\n```bash\npython cli.py stats-transfers --month 2026-06\n```\n\n## Skill Contents\n\n```\nfinancial-categorizer/\n├── SKILL.md                    # This file\n├── requirements.txt            # pip dependencies\n├── setup.py                    # setuptools configuration\n├── cli.py                      # Main entrypoint\n└── financial_categorizer/      # Package code\n    ├── __init__.py\n    ├── categorizer.py          # Auto-categorization & rule engine\n    ├── db_handler.py           # Database CRUD & raw schema setup\n    ├── importer.py             # CSV Parser (Nordea & ICA formats)\n    ├── matching.py             # Shared matching helpers (diacritic folding, aggregate tolerance)\n    └── stats.py                # SQL View registers and stats math\n```\n\n## SQLite Views\n\nFor analytical reporting (e.g. dashboards, Grafana), the following views are registered in the database:\n\n1. **`v_effective_transactions`** — Joins transactions with accounts to factor in ownership ratios and transfer link adjustments. Includes `adjusted_amount`, `unsplit_amount`, and `raw_amount` columns.\n2. **`v_monthly_summary`** — Calculates net income/expenses by month (includes unsplit and gross aggregations).\n3. **`v_category_monthly`** — Calculates monthly spending by category (includes unsplit and gross aggregations).\n4. **`v_daily_spending`** — Daily expense aggregation.\n5. **`v_cumulative_spending_monthly`** — Running month-to-date daily cumulative spending.\n6. **`v_daily_spending_moving_average`** — 30-day moving average of daily spending.\n7. **`v_category_monthly_averages`** — Average monthly spending by category.\n8. **`v_salary_period_summary`** — Expense/income summary grouped by salary periods (using the active salary config: fixed or salary).\n9. **`v_breakout_categories`** — Groups monthly spending into high-level categories (Groceries, Loans, Housing, Leisure, Car, etc.).\n10. **`v_uncategorized_groups`** — Groups uncategorized transactions by normalized Swish/Card payment descriptions to identify potential new rules.\n\n---\n\n## Querying Unmatched Reimbursements via CLI\n\nUnmatched reimbursements (pending paybacks or refunds) are incoming transactions on tracked accounts that have not yet been neutralized by a transaction link. They can be queried and filtered using the CLI:\n\n### Workflow:\n\n1. **Query the transactions** depending on the categorization workflow in use:\n   * **If using Uncategorized / Flat List (Option A)**:\n     ```bash\n     python cli.py uncategorized --non-zero\n     ```\n   * **If using a Dedicated Category (Option B)**:\n     ```bash\n     python cli.py transactions --category Reimbursements --non-zero --limit 100\n     ```\n\n2. **Identify candidates from the output**:\n   * **Inflows**: Look for transactions with positive amounts (`amount > 0`).\n   * **Regular Income Exclusions**: Filter out regular income sources (e.g., `\"Lön\"`, `\"Salary\"`, `\"BARNBDR\"`).\n   * **Adjusted Amount**: Verify that the adjusted amount is non-zero (linked/neutralized transactions show `adjusted_amount = 0.00`).\n\n### Example Filter Logic:\nIf the CLI output shows:\n```\nTransactions (4):\n  [12] 2026-06-23   25000.00 SEK                      Personligt      [Uncategorized]     Lön\n  [15] 2026-06-18    1250.00 SEK                      Personligt      [Uncategorized]     BARNBDR\n  [18] 2026-06-16     500.00 SEK                      Personligt      [Uncategorized]     Swish inbetalning DOE, JOHN\n  [21] 2026-05-20      50.00 SEK                      Personligt      [Uncategorized]     Swish inbetalning DOE, JOHN\n```\n* **Include**: `[18]` (+500.00) and `[21]` (+50.00) (positive Swish payments from an individual are reimbursement candidates).\n* **Exclude**: `[12]` (Lön/Salary) and `[15]` (barnbidrag/regular benefit payment).\n\n\n## Dependencies\n\n- `pytest` - For testing suite\n- Standard library modules: `sqlite3`, `csv`, `datetime`, `logging`, `re`, `argparse`, `os`\n\nInstall: `pip install -e .`\n\nFile v1.14.0:README.md\n\n# financial-categorizer\n\nPersonal finance transaction categorization with a SQLite backend.\n\nImports bank CSV files, auto-categorizes transactions using configurable rules, and provides SQL views for dashboards and analysis.\n\n## Features\n\n- **Multi-account support** — tracked active accounts and external savings/investments with ownership ratios\n- **Auto-categorization** — regex, exact, and contains match rules with priority ordering and manual overrides\n- **Transaction linking** — mark transfers and reimbursements between transactions; adjusted amounts are pre-computed\n- **SQL views** — ready-to-query views for monthly summaries, category breakdowns, and daily spending\n- **CSV import** — auto-detects Nordea and ICA formats, handles pending transactions (umlaut-variant and split-authorization settlement)\n- **CLI** — full command-line interface for all operations\n\n## Install\n\n```bash\npip install -e .\n```\n\nRequires Python 3.10+.\n\n## Quick Start\n\n```bash\n# Import transactions from a CSV\nfinancial-categorizer import transactions.csv --account \"Nordea Checking\"\n\n# Add categorization rules\nfinancial-categorizer add-rule 3 \"ICA MAXI\" --type contains\nfinancial-categorizer add-rule 4 \"^Hyra\" --type regex\n\n# Categorize uncategorized transactions\nfinancial-categorizer categorize\n\n# View stats\nfinancial-categorizer stats-summary --month 2026-04\nfinancial-categorizer stats-top --limit 10\nfinancial-categorizer stats-category Food --month 2026-04\n\n# Link a transfer between accounts\nfinancial-categorizer link 1 2 --type internal_transfer\n\n# Recalculate adjusted amounts\nfinancial-categorizer recalculate\n```\n\n## Architecture\n\nAll data lives in a single SQLite database (`data/finance.db` by default).\n\n### Core tables\n- **accounts** — bank accounts with type and ownership ratio\n- **transactions** — imported transactions with `adjusted_amount` (pre-computed)\n- **categories** — hierarchical categories (parent/child)\n- **match_rules** — patterns for auto-categorization\n- **transaction_links** — connects transfers and reimbursements\n\n### Projection tables\n- **period_projection** — persisted projection of the current period (income-anchored burn-down), refreshed automatically after data mutations (import, categorize, manual match/unmatch, link/unlink); the latest refresh wins\n- **period_projection_items** — itemized expected occurrences (date, name, amount) covering the full period, with an `upcoming` flag (date > as-of); zero-amount rows filtered\n\n### Views\n- `v_effective_transactions` — all transactions with adjusted, unsplit, and raw amounts\n- `v_monthly_summary` — income, expenses, net per month (includes unsplit and gross aggregations)\n- `v_category_monthly` — category totals per month (includes unsplit and gross aggregations)\n- `v_daily_spending` — daily spending breakdown\n- `v_burn_down` — income-anchored burn-down derived from `period_projection`\n\n### Adjusted, Unsplit, and Gross amounts\n\n1. `adjusted_amount` (Personal Share): Your share of the transaction. Calculated as `amount * account.ownership_ratio` (base), then adjusted by transfers and reimbursements.\n2. `unsplit_amount` (Household Net): Full household cost net of reimbursements. Calculated as `adjusted_amount / account.ownership_ratio`. Enabled in stats with the `--unsplit` flag.\n3. `raw_amount` (Household Raw): Full raw household cost before split and before reimbursements (i.e. the raw bank statement amount). Enabled in stats with the `--gross` flag.\n\nStats and views read these columns directly. Run `recalculate` to refresh after any manual changes.\n\n## Security & Data Integrity\n\nThis tool modifies your local SQLite database. To prevent accidental data loss, please observe the following guidelines:\n\n> [!WARNING]\n> Always make a backup of your database before performing database cleanup, auto-linking, or destructive operations:\n> ```bash\n> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n> ```\n\n### Destructive Operations & Confirmation Prompts\nDestructive commands require interactive confirmation `[y/N]` when run in a terminal (TTY). If you are running these commands in automated scripts or non-interactive shells, you must pass the `--yes` or `-y` flag to bypass the prompt; otherwise, the command will abort with an error.\n\nThe following commands require confirmation:\n- `delete-account <id> [--yes]`\n- `delete-category <id> [--yes] [--reassign <id>] [--force]`\n- `remove-rule <id> [--yes]`\n- `unlink <id> [--yes]`\n- `db-cleanup [--yes] [--dry-run]`\n- `cleanup-pending [--yes] [--dry-run]`\n- `remove-transfer-rule <id> [--yes]`\n- `auto-link [--yes] [--dry-run]`\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `import <files>` | Import bank CSV transactions |\n| `accounts` | List all registered bank accounts |\n| `add-account <name>` | Create a new bank account |\n| `update-account <id>` | Update account ownership ratio, type, name, etc. |\n| `delete-account <id> [--yes]` | Delete a bank account (requires confirmation or `-y`) |\n| `categories` | List all categories in tree view |\n| `add-category <name> [--associated-account <name_or_id>]` | Create a new category, optionally associated with an external account |\n| `update-category <id> [--associated-account <name_or_id>]` | Update category parents or fields (use `none` to clear association) |\n| `delete-category <id> [--yes]` | Delete a category (requires confirmation or `-y`) |\n| `rules [txn_id]` | List all match rules, or show the matching rule for a specific transaction |\n| `add-rule <cat_id> <pattern>` | Add a categorization rule (regex, contains, exact) |\n| `remove-rule <id> [--yes]` | Remove an auto-categorization rule (requires confirmation or `-y`) |\n| `preview <pattern>` | Preview which transactions match a pattern before adding a rule |\n| `categorize [--all]` | Run auto-categorization rules |\n| `uncategorized [--group] [--non-zero] [--net | --unsplit]` | List all uncategorized transactions (supports `--net` or `--unsplit`) |\n| `transactions [--category <name>] [--uncategorized] [--non-zero] [--account <name>] [--limit <n>] [--net | --unsplit]` | Search, list, and filter transactions (supports `--net` or `--unsplit`) |\n| `manual-match <txn_id> <cat_id>` | Manually assign a category override to a transaction |\n| `manual-unmatch <txn_id>` | Remove a manual categorization override |\n| `stats-summary [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly summary of income, expenses, and net (supports `--unsplit` or `--gross`) |\n| `stats-category <name> [--month <YYYY-MM>] [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Category total with subcategory rollups (supports `--unsplit` or `--gross`) |\n| `stats-trend <name> [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Monthly trend for a category (supports `--unsplit` or `--gross`) |\n| `stats-top [--month <YYYY-MM>] [--limit <n>] [--period-type <type>] [--unsplit | --gross]` | Top spending categories sorted by total expenses (supports `--unsplit` or `--gross`) |\n| `stats-transfers [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Net capital transfers to external accounts (supports `--unsplit` or `--gross`) |\n| `stats-compare [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Month-over-month comparison (supports `--unsplit` or `--gross`) |\n| `stats-cashflow [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly cash flow summary (Operating, Transfers, Net; supports `--unsplit` or `--gross`) |\n| `link <from_id> [to_id] --type [--to-account <name_or_id>] [--ratio <val> \\| --ratio-to <val> \\| --amount <val>] [--dry-run]` | Link transactions (specify `--to-account` for external transfers, or ratio/amount options to customize values; `--dry-run` to preview) |\n| `unlink <id> [--yes]` | Remove a link (requires confirmation or `-y`) |\n| `links` | List all transaction links |\n| `auto-link [--dry-run] [--yes]` | Auto-detect and link internal transfers using transfer rules (requires confirmation or `-y` when not running dry-run) |\n| `recalculate` | Manually recalculate adjusted amounts for all transactions |\n| `db-cleanup [--dry-run] [--yes]` | Purge orphaned transaction links and rules (Integrity Cleanup) (requires confirmation or `-y` when not running dry-run) |\n| `cleanup-pending [--dry-run] [--yes]` | Delete ghost pending reservations whose settled counterpart already exists (individual or split-authorization matches); unresolved pendings are kept and listed for manual review (requires confirmation or `-y` when not running dry-run) |\n| `remove-transfer-rule <id> [--yes]` | Remove a transfer detection rule (requires confirmation or `-y`) |\n| `salary-config` | Show current salary period configuration |\n| `set-salary-mode <mode>` | Set the salary period mode (`calendar`, `fixed`, `salary`) |\n| `set-salary-day <day>` | Set the fixed boundary day of the month (1-28) |\n| `set-salary-category <name>` | Set the category name used to scan for salary paydays |\n| `recurring [--status <active|cancelled|all>]` | List recurring payments configurations |\n| `add-recurring <name> <pattern> [options] [--dry-run]` | Manually create a recurring payment config and link transactions |\n| `update-recurring <id> [options] [--dry-run]` | Update recurring config fields and re-run transaction linking |\n| `remove-recurring <id> [--hard] [--date <YYYY-MM-DD>] [--yes]` | Cancel (soft-close with end date) or hard-delete a recurring payment configuration (requires confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode) |\n| `discover-recurring [--dry-run] [--yes]` | Auto-discover recurring transaction patterns and auto-close dead configurations (saving, re-linking, and auto-closing require confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode — run `--dry-run` first to preview) |\n| `stats-recurring [query] [--month <YYYY-MM>] [--period-type <type>]` | Display subscription stats dashboard or detail reports for matching subscriptions |\n| `estimate-period [--days <int>] [--level <0|1|2>]` | Project spending and estimate total outflow remaining in current period (rollup levels: 0=none, 1=top, 2=detailed) |\n| `set-estimate-level <0|1|2>` | Set default category rollup level configuration for spending estimation |\n\n\n\n\n## Testing\n\n```bash\npip install pytest\npytest tests/\n```\n\nFile v1.14.0:_meta.json\n\n{\n  \"ownerId\": \"kn7cxaw1m27d1fe93wbz40vqkd8309h7\",\n  \"slug\": \"financial-categorizer\",\n  \"version\": \"1.14.0\",\n  \"publishedAt\": 1788613689614\n}\n\nFile v1.14.0:skill-card.md\n\n## Description:\n\nProcess bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[patello](https://clawhub.ai/user/patello)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nExternal users and agents use this skill to manage personal finance transaction data by importing bank CSV exports, defining categorization and linking rules, and generating SQLite-backed reports for analysis.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The local CLI can modify or damage the selected finance database during imports, cleanup, auto-linking, recurring discovery, or first use on an existing database.\n\nMitigation: Keep database backups before these operations, use documented dry-run modes where available, and rely on confirmation prompts unless intentionally passing --yes or -y.\n\nRisk: Crafted or complex regex categorization rules can stall transaction matching.\n\nMitigation: Prefer contains or exact match rules unless the regex is understood, and preview matching behavior before adding rules.\n\nRisk: Opening SQLite databases from other people or untrusted locations can expose the user to database integrity and operational risk.\n\nMitigation: Use trusted local databases only and avoid running the tool against untrusted SQLite files.\n\n## Reference(s):\n\n- [ClawHub skill page](https://clawhub.ai/patello/skills/financial-categorizer)\n- [README.md](artifact/README.md)\n- [SKILL.md](artifact/SKILL.md)\n\n## Skill Output:\n\n**Output Type(s):** [text, shell commands, configuration, guidance]\n\n**Output Format:** [CLI text output and Markdown guidance with shell command examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Modifies a user-selected local SQLite database and can create or update analytical database views.]\n\n## Skill Version(s):\n\n1.14.0 (source: server release evidence and artifact _meta.json)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v1.14.0:changelog.txt\n\n- feat: cleanup-pending command to delete ghost reservations\n- fix: settle reservations across umlaut variants and split authorizations\n\nFile v1.14.0:LICENSE\n\nMIT License\n\nCopyright (c) 2021 Patrik Ekenberg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nFile v1.14.0:requirements.txt\n\n# financial-categorizer has no external dependencies\n# All requirements are Python stdlib (sqlite3, csv, argparse, datetime, logging)\n\nArchive v1.13.0: 15 files, 76185 bytes\n\nFiles: _meta.json (141b), changelog.txt (57b), cli.py (103690b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (28063b), financial_categorizer/db_handler.py (48207b), financial_categorizer/importer.py (19364b), financial_categorizer/recurring.py (43187b), financial_categorizer/stats.py (61275b), LICENSE (1072b), README.md (10132b), requirements.txt (134b), setup.py (249b), skill-card.md (2126b), SKILL.md (23743b)\n\nFile v1.13.0:SKILL.md\n\n---\nname: financial-categorizer\ndescription: \"Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\"\nmetadata:\n  openclaw:\n    requires:\n      bins:\n        - python3\n---\n\n\n# financial-categorizer\n\nProcess bank transaction CSV exports, auto-categorize transactions using configurable rules, manage transaction links, and generate analytical SQLite database views.\n\n## Quick Start\n\nRun the CLI tool from your terminal pointing to your database path:\n\n```bash\n# 1. Add your main checking account\npython cli.py --db ../data/finance.db add-account \"Nordea Checking\" --type tracked --ownership 1.0\n\n# 2. Add hierarchical categories\npython cli.py --db ../data/finance.db add-category \"Food\"\npython cli.py --db ../data/finance.db add-category \"Groceries\" --parent 1\n\n# 3. Add auto-categorization rules\npython cli.py --db ../data/finance.db add-rule 2 \"ICA MAXI\" --type contains\npython cli.py --db ../data/finance.db add-rule 1 \"^Hyra\" --type regex\n\n# 4. Import transactions from a bank CSV file\npython cli.py --db ../data/finance.db import transactions.csv --account \"Nordea Checking\"\n\n# 5. Run auto-categorization over uncategorized transactions\npython cli.py --db ../data/finance.db categorize\n\n# 6. View monthly summary statistics\npython cli.py --db ../data/finance.db stats-summary\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/financial-categorizer/   # Portable skill (shareable)\n│   ├── SKILL.md\n│   ├── cli.py\n│   ├── setup.py\n│   └── financial_categorizer/\n└── data/                           # Your private data\n    ├── finance.db\n    └── exports/\n        ├── Nordea_Checking.csv\n        └── ICA_Shared.csv\n```\n\nThe skill provides logic. Your data stays private and portable.\n\n## Security & Data Integrity\n\nThis tool modifies your local SQLite database. To prevent accidental data loss, please observe the following guidelines:\n\n> [!WARNING]\n> Always make a backup of your database before performing database cleanup, auto-linking, or destructive operations:\n> ```bash\n> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n> ```\n\n### Destructive Operations & Confirmation Prompts\nDestructive commands require interactive confirmation `[y/N]` when run in a terminal (TTY). If you are running these commands in automated scripts or non-interactive shells, you must pass the `--yes` or `-y` flag to bypass the prompt; otherwise, the command will abort with an error.\n\nThe following commands require confirmation:\n- `delete-account <id> [--yes]`\n- `delete-category <id> [--yes] [--reassign <id>] [--force]`\n- `remove-rule <id> [--yes]`\n- `unlink <id> [--yes]`\n- `db-cleanup [--yes] [--dry-run]`\n- `remove-transfer-rule <id> [--yes]`\n- `auto-link [--yes] [--dry-run]`\n\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `import <files>` | Import bank CSV transactions |\n| `accounts` | List all registered bank accounts |\n| `add-account <name>` | Create a new bank account |\n| `update-account <id>` | Update account ownership ratio, type, name, etc. |\n| `delete-account <id> [--yes]` | Delete a bank account (requires confirmation or `-y`) |\n| `categories` | List all categories in tree view |\n| `add-category <name> [--associated-account <name_or_id>]` | Create a new category, optionally associated with an external account |\n| `update-category <id> [--associated-account <name_or_id>]` | Update category parents or fields (use `none` to clear association) |\n| `delete-category <id> [--yes]` | Delete a category (requires confirmation or `-y`) |\n| `rules [txn_id]` | List all match rules, or show the matching rule for a specific transaction |\n| `add-rule <cat_id> <pattern>` | Add a categorization rule (regex, contains, exact) |\n| `remove-rule <id> [--yes]` | Remove an auto-categorization rule (requires confirmation or `-y`) |\n| `preview <pattern>` | Preview which transactions match a pattern before adding a rule |\n| `categorize [--all]` | Run auto-categorization rules |\n| `uncategorized [--group] [--non-zero] [--net | --unsplit]` | List all uncategorized transactions (supports `--net` or `--unsplit`) |\n| `transactions [--category <name>] [--uncategorized] [--non-zero] [--account <name>] [--limit <n>] [--net | --unsplit]` | Search, list, and filter transactions (supports `--net` or `--unsplit`) |\n| `manual-match <txn_id> <cat_id>` | Manually assign a category override to a transaction |\n| `manual-unmatch <txn_id>` | Remove a manual categorization override |\n| `stats-summary [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly summary of income, expenses, and net (supports `--unsplit` or `--gross`) |\n| `stats-category <name> [--month <YYYY-MM>] [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Category total with subcategory rollups (supports `--unsplit` or `--gross`) |\n| `stats-trend <name> [--from <date>] [--to <date>] [--period-type <type>] [--unsplit | --gross]` | Monthly trend for a category (supports `--unsplit` or `--gross`) |\n| `stats-top [--month <YYYY-MM>] [--limit <n>] [--period-type <type>] [--unsplit | --gross]` | Top spending categories sorted by total expenses (supports `--unsplit` or `--gross`) |\n| `stats-transfers [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Net capital transfers to external accounts (supports `--unsplit` or `--gross`) |\n| `stats-compare [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Month-over-month comparison (supports `--unsplit` or `--gross`) |\n| `stats-cashflow [--month <YYYY-MM>] [--period-type <type>] [--unsplit | --gross]` | Monthly cash flow summary (Operating, Transfers, Net; supports `--unsplit` or `--gross`) |\n| `link <from_id> [to_id] --type [--to-account <name_or_id>] [--ratio <val> \\| --ratio-to <val> \\| --amount <val>] [--dry-run]` | Link transactions (specify `--to-account` for external transfers, or ratio/amount options to customize values; `--dry-run` to preview) |\n| `unlink <id> [--yes]` | Remove a link (requires confirmation or `-y`) |\n| `links` | List all transaction links |\n| `auto-link [--dry-run] [--yes]` | Auto-detect and link internal transfers using transfer rules (requires confirmation or `-y` when not running dry-run) |\n| `recalculate` | Manually recalculate adjusted amounts for all transactions |\n| `db-cleanup [--dry-run] [--yes]` | Purge orphaned transaction links and rules (Integrity Cleanup) (requires confirmation or `-y` when not running dry-run) |\n| `remove-transfer-rule <id> [--yes]` | Remove a transfer detection rule (requires confirmation or `-y`) |\n| `salary-config` | Show current salary period configuration |\n| `set-salary-mode <mode>` | Set the salary period mode (`calendar`, `fixed`, `salary`) |\n| `set-salary-day <day>` | Set the fixed boundary day of the month (1-28) |\n| `set-salary-category <name>` | Set the category name used to scan for salary paydays |\n| `recurring [--status <active|cancelled|all>]` | List recurring payments configurations |\n| `add-recurring <name> <pattern> [options] [--dry-run]` | Manually create a recurring payment config and link transactions |\n| `update-recurring <id> [options] [--dry-run]` | Update recurring config fields and re-run transaction linking |\n| `remove-recurring <id> [--hard] [--date <YYYY-MM-DD>] [--yes]` | Cancel (soft-close with end date) or hard-delete a recurring payment configuration (requires confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode) |\n| `discover-recurring [--dry-run] [--yes]` | Auto-discover recurring transaction patterns and auto-close dead configurations (saving, re-linking, and auto-closing require confirmation; use `--yes`/`-y` to bypass, mandatory in non-interactive mode — run `--dry-run` first to preview) |\n| `stats-recurring [query] [--month <YYYY-MM>] [--period-type <type>]` | Display subscription stats dashboard or detail reports for matching subscriptions |\n| `estimate-period [--days <int>] [--level <0|1|2>]` | Project spending and estimate total outflow remaining in current period (rollup levels: 0=none, 1=top, 2=detailed) |\n| `set-estimate-level <0|1|2>` | Set default category rollup level configuration for spending estimation |\n\n\n\n\n\n## Configuring Salary Periods\n\nBy default, the salary period boundary is fixed to the 25th of the month. You can customize this grouping behavior using the salary configuration commands.\n\n### Available Modes:\n1. **`calendar`**: Group transactions by calendar months (1st to the last day).\n2. **`fixed`**: Group transactions by a static day of the month (e.g., the 25th). Transactions on or after this day are grouped into the next month's period.\n3. **`salary`**: Group transactions by automatically detecting the primary salary deposit date in each month (the transaction under the salary category with the largest positive amount).\n\n### CLI Configuration Commands:\n```bash\n# View current configuration\npython cli.py salary-config\n\n# Change mode to salary (automatic payday detection)\npython cli.py set-salary-mode salary\n\n# Set the category name used to search for paydays (default is \"Salary\")\npython cli.py set-salary-category \"Salary\"\n\n# Change mode to a fixed day of the month (e.g. 27th)\npython cli.py set-salary-mode fixed\npython cli.py set-salary-day 27\n```\n\n> [!WARNING]\n> If you choose the **`fixed`** day mode, be aware that bank deposits and transactions can shift early or late due to weekends and holidays.\n> - Ensure your fixed day is configured early or late enough so that fluctuations in actual payday do not cause two salary deposits to fall into the same period (which would result in one month showing double income and the next showing zero income).\n> - Alternatively, use the **`salary`** mode, which automatically detects the actual deposit transaction dates and shifts the boundaries dynamically.\n\n### Querying Statistics by Salary Period\nAll statistics and breakdown commands support the `--period-type` parameter:\n* `calendar` — Force standard calendar month boundaries.\n* `salary` — Force salary period boundaries (using the active `salary-config` settings).\n* `default` — Dynamically resolve to your active `salary-config` mode:\n  - If mode is `calendar`, defaults to calendar months.\n  - If mode is `fixed` or `salary`, defaults to salary periods.\n\nFor example, to query your housing category spending using the active salary period:\n```bash\npython cli.py stats-category Housing --period-type salary --month 2026-06\n```\n\nIf you do not specify a `--period-type` flag, it will automatically default to the setting configured via `set-salary-mode`.\n\n## Tracking Recurring Payments & Subscriptions\n\nThis tool supports advanced, automated tracking and lifecycle management of recurring payments (e.g. Netflix, Spotify, broadband, utility bills) and income (e.g. Salary).\n\n### Core Concepts\n\n1. **Recurring Payments Table (`recurring_payments`)**\n   Defines the rules, intervals, expected days, amount ranges, and lifespans for each recurring item.\n2. **Subscription Lifecycle & Runs**\n   Resumed subscriptions (after cancellation) are tracked as separate runs/rows in `recurring_payments`.\n   * **Resumption**: If a transaction matches a pattern of a cancelled subscription (after its `end_date`), it automatically spawns a new run/configuration for the resumption.\n   * **Auto-Closing**: Active configurations that are missing expected payments are automatically closed (`end_date` is set to the last matched payment date) when running `discover-recurring` or passing the `--close` flag to `import` / `categorize`.\n   * **Confirmation Gates**: `remove-recurring` and `discover-recurring` (without `--dry-run`) prompt for confirmation before making changes, like the other destructive commands. Use `--yes`/`-y` to bypass (mandatory in non-interactive mode), and `discover-recurring --dry-run` to preview first.\n3. **Flexible Date Intervals**\n   Supports strict date/day checking with a configured tolerance window:\n   * **Monthly**: Expected day of month (e.g. 25th, last day `-1`).\n   * **Weekly**: Expected weekday.\n   * **Yearly**: Expected month and day.\n   * **Days**: Custom interval (e.g. every 90 days).\n   * **Tolerance**: Tolerates shifts due to weekends/holidays (default 4 days).\n\n### Common Workflows\n\n#### 1. Auto-Discover Recurring Candidates\nScan transaction history to auto-identify recurring items (such as monthly subscriptions or utility bills) and automatically save them:\n```bash\n# Preview candidates without writing to the database\npython cli.py discover-recurring --dry-run\n\n# Run auto-discovery and save configurations (prompts for confirmation; --yes to bypass)\npython cli.py discover-recurring --yes\n```\n\n#### 2. Manually Add/Update Configurations\n```bash\n# Add a monthly Netflix subscription\npython cli.py add-recurring Netflix \"netflix.com\" --amount-min -149 --amount-max -189 --interval monthly --day-of-month 6 --category Media\n\n# Dry-run update previewing matches\npython cli.py update-recurring 1 --amount-max -219 --dry-run\n```\n\n#### 3. View Outflow Dashboard & Stats\n```bash\n# Active subscriptions monthly cost summary and expected next dates\npython cli.py stats-recurring\n\n# Detailed subscription history across active/cancelled runs and transaction lists\npython cli.py stats-recurring \"Disney Plus\"\n```\n\n## Common Workflows\n\n\n### Handling Shared-Expense Reimbursements\n\nIf you make a shared purchase (e.g., from the `Gemensamt` account, 50% ownership) and get reimbursed by an external person (e.g., via Swish to your `Personligt` account, 100% ownership) and subsequently transfer the payback to the shared account:\n\n1. **Reimburse the shared expense**: Link the reimbursement transaction (the Swish inflow) directly to the original expense transaction (the shared purchase):\n   ```bash\n   python cli.py --db data/finance.db link <swish_transaction_id> <expense_transaction_id> --type reimbursement --ratio 1.0\n   ```\n   * *Effect*: The Swish transaction is fully neutralized to `0.00` adjusted amount, and the credit to the expense transaction is automatically scaled by the shared account's ownership ratio (e.g., 50%), reducing your net cost correctly.\n   * *Note*: The credit is scaled by the target account's ownership ratio (e.g., 50%) because the benefit of the payback is shared between the joint account owners.\n\n2. **Link the account transfer**: Link the outflow from your main account to the inflow on your joint account as an internal transfer:\n   ```bash\n   python cli.py --db data/finance.db link <transfer_out_id> <transfer_in_id> --type internal_transfer\n   ```\n   * *Effect*: Both sides of the transfer are neutralized to `0.00`, ensuring no false income or outflows are recorded.\n    * *Note*: This step is skipped if the transfer has already been auto-linked.\n\n### Managing Pending Reimbursements (Unlinked Inflows)\n\n#### Option A: Flat List Workflow (Keeping Them Uncategorized)\nUse the `--non-zero` flag on the `uncategorized` command to show pending actions (positive inflows to link, negative expenses to categorize):\n```bash\npython cli.py uncategorized --non-zero\n```\n\n#### Option B: Dedicated Category Workflow (Filtering Positive Inflows Only)\nTo auto-route only positive inflows (like Swish reimbursements) to a category (e.g., ID `9`) while leaving negative outflows uncategorized, add a rule with a minimum amount filter:\n```bash\npython cli.py add-rule 9 \"Swish\" --type contains --amount-min 0.01\n```\n\nQuery unlinked/pending reimbursements using:\n```bash\npython cli.py transactions --category Reimbursements --non-zero\n```\n*(Once linked, the adjusted amount drops to `0.00` and the transaction disappears from both lists).*\n\n### How to Think About Reimbursements & Composite Transactions\n\nWhen working with transaction links, it is crucial to distinguish between the **raw bank ledger amount** (actual cash flow) and the **effective category/budgetary amount** (represented by the `adjusted_amount` column).\n\n#### The Core Principle\nReimbursements are not new income; they are a return of capital.\n* If an expense is reimbursed, the net expense is zero.\n* The incoming reimbursement money is not labor/investment income; it simply offsets the expense.\n\nIf you don't link them, your gross income and gross expenses will both be overstated by the reimbursement amount, distorting your reports.\n\n#### Composite Transactions (e.g. Reimbursement Baked into Salary)\nOften, a reimbursement is not a standalone transaction (like a Swish payment), but is packaged/baked into a larger composite transaction, such as a salary payment.\nFor example, if your employer pays you a single amount of `50,000 SEK`, which contains:\n* `45,000 SEK` of actual labor income\n* `5,000 SEK` of expense reimbursement for a credit card charge\n\nTo avoid distorting both income and expenses, you must split this composite transaction. In this system, you do this using **transaction links** with fractional ratios.\n\n#### Link Ratio Calculation Modes\nThe `link` command provides three modes to simplify this:\n\n1. **Source Ratio (`--ratio <float>`)** - *Default*\n   Calculates the ratio relative to the source (`from_id`) transaction. Use when you want to allocate a direct fraction of the source.\n\n2. **Destination Ratio (`--ratio-to <float>`)**\n   Calculates the ratio relative to the destination (`to_id`) transaction.\n   * For example, to fully reimburse/zero out the `First Card` expense of `-5,000 SEK` from your salary, use:\n     ```bash\n     python cli.py link <salary_txn_id> <expense_txn_id> --type reimbursement --ratio-to 1.0\n     ```\n   * This automatically calculates the exact ratio ($5000 / 50000 = 0.10$). It reduces the salary's `adjusted_amount` to `45,000 SEK` (reflecting your true labor income) and increases the credit card expense's `adjusted_amount` to `0.00 SEK` (reflecting your true net expense).\n\n3. **Exact Cash (`--amount <float>`)**\n   Specify the exact cash amount in SEK being reimbursed.\n   * For example, to link exactly `5,000 SEK`:\n     ```bash\n     python cli.py link <salary_txn_id> <expense_txn_id> --type reimbursement --amount 5000\n     ```\n\n\n#### Dry-run Previews\nAlways run with the `--dry-run` flag first to preview the downstream `adjusted_amount` effects before committing changes to the database:\n```bash\npython cli.py link <from_id> <to_id> --type reimbursement --ratio-to 1.0 --dry-run\n```\n\n## Tracking External Accounts\n\nYou can track capital transfers from your tracked accounts to untracked external accounts (such as savings or stock brokerage accounts).\n\n### Setup and Workflow:\n1. **Create the External Account**:\n   ```bash\n   python cli.py add-account \"Avanza Brokerage\" --type external\n   ```\n2. **Associate a Category**:\n   Create a category of type `transfer` associated with this external account:\n   ```bash\n   python cli.py add-category \"Brokerage Transfer\" --type transfer --associated-account \"Avanza Brokerage\"\n   ```\n3. **Add a Categorization Rule**:\n   Add a match rule to auto-categorize transfers:\n   ```bash\n   python cli.py add-rule <category_id> \"AVANZA\" --type contains\n   ```\n4. **Auto-linking**:\n   When transactions are categorized (via `categorize` or manual overrides), if they match a transfer category linked to an external account, an `external_transfer` link is created automatically.\n\n### Manual Linking:\nFor one-off transfers, you can link a transaction directly to an external account:\n```bash\npython cli.py link <transaction_id> --type external_transfer --to-account \"Avanza Brokerage\"\n```\n\n### Querying Statistics:\nUse the `stats-transfers` command to view net capital movements per external account:\n```bash\npython cl\n\nArchive v1.12.0: 15 files, 73890 bytes\n\nFiles: _meta.json (141b), changelog.txt (219b), cli.py (103136b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (28063b), financial_categorizer/db_handler.py (48207b), financial_categorizer/importer.py (19364b), financial_categorizer/recurring.py (42735b), financial_categorizer/stats.py (53844b), LICENSE (1072b), README.md (9627b), requirements.txt (134b), setup.py (249b), skill-card.md (1988b), SKILL.md (23743b)\n\nArchive v1.11.0: 15 files, 73398 bytes\n\nFiles: _meta.json (141b), changelog.txt (148b), cli.py (102313b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (28063b), financial_categorizer/db_handler.py (48207b), financial_categorizer/importer.py (19364b), financial_categorizer/recurring.py (42735b), financial_categorizer/stats.py (53610b), LICENSE (1072b), README.md (9364b), requirements.txt (134b), setup.py (249b), skill-card.md (2464b), SKILL.md (23134b)\n\nArchive v1.10.0: 15 files, 73053 bytes\n\nFiles: _meta.json (141b), changelog.txt (219b), cli.py (100611b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (27772b), financial_categorizer/db_handler.py (48207b), financial_categorizer/importer.py (19364b), financial_categorizer/recurring.py (42735b), financial_categorizer/stats.py (51327b), LICENSE (1072b), README.md (9364b), requirements.txt (134b), setup.py (249b), skill-card.md (2526b), SKILL.md (23134b)\n\nArchive v1.9.1: 15 files, 72365 bytes\n\nFiles: _meta.json (140b), changelog.txt (55b), cli.py (99806b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (27772b), financial_categorizer/db_handler.py (48207b), financial_categorizer/importer.py (19364b), financial_categorizer/recurring.py (41199b), financial_categorizer/stats.py (51327b), LICENSE (1072b), README.md (9364b), requirements.txt (134b), setup.py (249b), skill-card.md (2037b), SKILL.md (23134b)\n\nArchive v1.9.0: 15 files, 72391 bytes\n\nFiles: _meta.json (140b), changelog.txt (120b), cli.py (99806b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (27772b), financial_categorizer/db_handler.py (48207b), financial_categorizer/importer.py (19364b), financial_categorizer/recurring.py (41199b), financial_categorizer/stats.py (51327b), LICENSE (1072b), README.md (9364b), requirements.txt (134b), setup.py (249b), skill-card.md (2029b), SKILL.md (23067b)\n\nArchive v1.8.0: 15 files, 68037 bytes\n\nFiles: _meta.json (140b), changelog.txt (96b), cli.py (95640b), financial_categorizer/__init__.py (503b), financial_categorizer/categorizer.py (27772b), financial_categorizer/db_handler.py (48207b), financial_categorizer/importer.py (19364b), financial_categorizer/recurring.py (35500b), financial_categorizer/stats.py (38005b), LICENSE (1072b), README.md (9083b), requirements.txt (134b), setup.py (249b), skill-card.md (2292b), SKILL.md (22786b)","readmeExcerpt":"Skill: financial-categorizer Owner: patello Summary: Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views. Tags: latest:1.16.0 Version history: v1.16.0 | 2026-10-04T11:08:52.434Z | user - fix: keep genuinely identical transactions instead of collapsing them v1.15.0 | 2026-09-05T20:09:18.744Z | user -","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"# 1. Add your main checking account\npython cli.py --db ../data/finance.db add-account \"Nordea Checking\" --type tracked --ownership 1.0\n\n# 2. Add hierarchical categories\npython cli.py --db ../data/finance.db add-category \"Food\"\npython cli.py --db ../data/finance.db add-category \"Groceries\" --parent 1\n\n# 3. Add auto-categorization rules\npython cli.py --db ../data/finance.db add-rule 2 \"ICA MAXI\" --type contains\npython cli.py --db ../data/finance.db add-rule 1 \"^Hyra\" --type regex\n\n# 4. Import transactions from a bank CSV file\npython cli.py --db ../data/finance.db import transactions.csv --account \"Nordea Checking\"\n\n# 5. Run auto-categorization over uncategorized transactions\npython cli.py --db ../data/finance.db categorize\n\n# 6. View monthly summary statistics\npython cli.py --db ../data/finance.db stats-summary"},{"language":"text","snippet":"workspace-finance/\n├── skills/financial-categorizer/   # Portable skill (shareable)\n│   ├── SKILL.md\n│   ├── cli.py\n│   ├── setup.py\n│   └── financial_categorizer/\n└── data/                           # Your private data\n    ├── finance.db\n    └── exports/\n        ├── Nordea_Checking.csv\n        └── ICA_Shared.csv"},{"language":"bash","snippet":"> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n>"},{"language":"bash","snippet":"# View current configuration\npython cli.py salary-config\n\n# Change mode to salary (automatic payday detection)\npython cli.py set-salary-mode salary\n\n# Set the category name used to search for paydays (default is \"Salary\")\npython cli.py set-salary-category \"Salary\"\n\n# Change mode to a fixed day of the month (e.g. 27th)\npython cli.py set-salary-mode fixed\npython cli.py set-salary-day 27"},{"language":"bash","snippet":"python cli.py stats-category Housing --period-type salary --month 2026-06"},{"language":"bash","snippet":"# Preview candidates without writing to the database\npython cli.py discover-recurring --dry-run\n\n# Run auto-discovery and save configurations (prompts for confirmation; --yes to bypass)\npython cli.py discover-recurring --yes"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: financial-categorizer\ndescription: \"Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\"\nmetadata:\n  openclaw:\n    requires:\n      bins:\n        - python3\n---\n\n\n# financial-categorizer\n\nProcess bank transaction CSV exports, auto-categorize transactions using configurable rules, manage transaction links, and generate analytical SQLite database views.\n\n## Quick Start\n\nRun the CLI tool from your terminal pointing to your database path:\n\n```bash\n# 1. Add your main checking account\npython cli.py --db ../data/finance.db add-account \"Nordea Checking\" --type tracked --ownership 1.0\n\n# 2. Add hierarchical categories\npython cli.py --db ../data/finance.db add-category \"Food\"\npython cli.py --db ../data/finance.db add-category \"Groceries\" --parent 1\n\n# 3. Add auto-categorization rules\npython cli.py --db ../data/finance.db add-rule 2 \"ICA MAXI\" --type contains\npython cli.py --db ../data/finance.db add-rule 1 \"^Hyra\" --type regex\n\n# 4. Import transactions from a bank CSV file\npython cli.py --db ../data/finance.db import transactions.csv --account \"Nordea Checking\"\n\n# 5. Run auto-categorization over uncategorized transactions\npython cli.py --db ../data/finance.db categorize\n\n# 6. View monthly summary statistics\npython cli.py --db ../data/finance.db stats-summary\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/financial-categorizer/   # Portable skill (shareable)\n│   ├── SKILL.md\n│   ├── cli.py\n│   ├── setup.py\n│   └── financial_categorizer/\n└── data/                           # Your private data\n    ├── finance.db\n    └── exports/\n        ├── Nordea_Checking.csv\n        └── ICA_Shared.csv\n```\n\nThe skill provides logic. Your data stays private and portable.\n\n## Security & Data Integrity\n\nThis tool modifies your local SQLite database. To prevent accidental data loss, please observe the following guidelines:\n\n> [!WARNING]\n> Always make a backup of your database before performing database cleanup, auto-linking, or destructive operations:\n> ```bash\n> # Simple file copy backup\n> cp data/finance.db data/finance.db.bak\n> \n> # Safe SQLite backup command\n> sqlite3 data/finance.db \".backup data/finance.db.bak\"\n> ```\n\n### Destructive Operations & Confirmation Prompts\nDestructive commands require interactive confirmation `[y/N]` when run in a terminal (TTY). If you are running these commands in automated scripts or non-interactive shells, you must pass the `--yes` or `-y` flag to bypass the prompt; otherwise, the command will abort with an error.\n\nThe following commands require confirmation:\n- `delete-account <id> [--yes]`\n- `delete-category <id> [--yes] [--reassign <id>] [--force]`\n- `remove-rule <id> [--yes]`\n- `unlink <id> [--yes]`\n- `db-cleanup [--yes] [--dry-run]`\n- `cleanup-pending [--yes] [--dry-run] [--force-id <id>...]`\n- `remove-transfer-rule <id> ["},{"path":"README.md","content":"# financial-categorizer\n\nPersonal finance transaction categorization with a SQLite backend.\n\nImports bank CSV files, auto-categorizes transactions using configurable rules, and provides SQL views for dashboards and analysis.\n\n## Features\n\n- **Multi-account support** — tracked active accounts and external savings/investments with ownership ratios\n- **Auto-categorization** — regex, exact, and contains match rules with priority ordering and manual overrides\n- **Transaction linking** — mark transfers and reimbursements between transactions; adjusted amounts are pre-computed\n- **SQL views** — ready-to-query views for monthly summaries, category breakdowns, and daily spending\n- **CSV import** — auto-detects Nordea and ICA formats, handles pending transactions (umlaut-variant, split-authorization, and inexact buffer settlement; see [Pending Transactions](#pending-transactions-reservations))\n- **CLI** — full command-line interface for all operations\n\n## Install\n\n```bash\npip install -e .\n```\n\nRequires Python 3.10+.\n\n## Quick Start\n\n```bash\n# Import transactions from a CSV\nfinancial-categorizer import transactions.csv --account \"Nordea Checking\"\n\n# Add categorization rules\nfinancial-categorizer add-rule 3 \"ICA MAXI\" --type contains\nfinancial-categorizer add-rule 4 \"^Hyra\" --type regex\n\n# Categorize uncategorized transactions\nfinancial-categorizer categorize\n\n# View stats\nfinancial-categorizer stats-summary --month 2026-04\nfinancial-categorizer stats-top --limit 10\nfinancial-categorizer stats-category Food --month 2026-04\n\n# Link a transfer between accounts\nfinancial-categorizer link 1 2 --type internal_transfer\n\n# Recalculate adjusted amounts\nfinancial-categorizer recalculate\n```\n\n## Architecture\n\nAll data lives in a single SQLite database (`data/finance.db` by default).\n\n### Core tables\n- **accounts** — bank accounts with type and ownership ratio\n- **transactions** — imported transactions with `adjusted_amount` (pre-computed)\n- **categories** — hierarchical categories (parent/child)\n- **match_rules** — patterns for auto-categorization\n- **transaction_links** — connects transfers and reimbursements\n\n### Projection tables\n- **period_projection** — persisted projection of the current period (income-anchored burn-down), refreshed automatically after data mutations (import, categorize, manual match/unmatch, link/unlink); the latest refresh wins\n- **period_projection_items** — itemized expected occurrences (date, name, amount) covering the full period, with an `upcoming` flag (date > as-of); zero-amount rows filtered\n\n### Views\n- `v_effective_transactions` — all transactions with adjusted, unsplit, and raw amounts\n- `v_monthly_summary` — income, expenses, net per month (includes unsplit and gross aggregations)\n- `v_category_monthly` — category totals per month (includes unsplit and gross aggregations)\n- `v_daily_spending` — daily spending breakdown\n- `v_burn_down` — income-anchored burn-down derived from `period_projection`\n\n### Adjusted, Unsplit, and Gross amo"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7cxaw1m27d1fe93wbz40vqkd8309h7\",\n  \"slug\": \"financial-categorizer\",\n  \"version\": \"1.16.0\",\n  \"publishedAt\": 1791112132434\n}"},{"path":"skill-card.md","content":"## Description:\n\nProcess bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[patello](https://clawhub.ai/user/patello)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nPeople managing personal finances use this skill to import bank CSVs, categorize transactions, link transfers and reimbursements, and review spending in a local SQLite database.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Cleanup, auto-linking, recurring discovery, category deletion, and other mutating commands can change or remove records in the local finance database.\n\nMitigation: Back up the database first, preview with dry-run options when available, and review changes before using --yes.\n\n## Reference(s):\n\n- [Financial Categorizer on ClawHub](https://clawhub.ai/patello/skills/financial-categorizer)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Configuration guidance]\n\n**Output Format:** [Markdown with CLI commands and usage guidance]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Commands operate on a user-selected local SQLite database.]\n\n## Skill Version(s):\n\n1.16.0 (source: server-resolved ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."},{"path":"changelog.txt","content":"- fix: keep genuinely identical transactions instead of collapsing them"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views. Skill: financial-categorizer Owner: patello Summary: Process bank transaction CSV exports (Nordea, ICA), auto-categorize transactions using configurable rules, manage transaction links, and generate analytical database views. Tags: latest:1.16.0 Version history: v1.16.0 | 2026-10-04T11:08:52.434Z | user - fix: keep genuinely identical transactions instead of collapsing them v1.15.0 | 2026-09-05T20:09:18.744Z | user -","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1249,"uniquenessScore":53,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T17:24:50.367Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T17:24:50.367Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-09T21:52:53.659Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}