{"id":40892253,"url":"https://github.com/staticaland/beancount-no-amex","last_synced_at":"2026-01-22T02:10:15.344Z","repository":{"id":283841096,"uuid":"953062653","full_name":"staticaland/beancount-no-amex","owner":"staticaland","description":null,"archived":false,"fork":false,"pushed_at":"2026-01-02T18:04:50.000Z","size":176,"stargazers_count":1,"open_issues_count":9,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-19T11:38:38.031Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/staticaland.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-22T13:39:08.000Z","updated_at":"2026-01-02T10:35:34.000Z","dependencies_parsed_at":"2025-03-22T15:29:08.188Z","dependency_job_id":null,"html_url":"https://github.com/staticaland/beancount-no-amex","commit_stats":null,"previous_names":["staticaland/beancount-no-amex"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/staticaland/beancount-no-amex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticaland%2Fbeancount-no-amex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticaland%2Fbeancount-no-amex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticaland%2Fbeancount-no-amex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticaland%2Fbeancount-no-amex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/staticaland","download_url":"https://codeload.github.com/staticaland/beancount-no-amex/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/staticaland%2Fbeancount-no-amex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28650889,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T01:17:37.254Z","status":"online","status_checked_at":"2026-01-22T02:00:07.137Z","response_time":144,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-22T02:10:14.554Z","updated_at":"2026-01-22T02:10:15.331Z","avatar_url":"https://github.com/staticaland.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# beancount-no-amex\n\nA Python library for importing American Express (Amex) (Norway) bank data into Beancount accounting format.\n\n![amex2](https://github.com/user-attachments/assets/0d66903c-28a3-4953-9783-9c83362bb822)\n\n## Quickstart\n\nGet from zero to viewing your Amex transactions in Fava in under 5 minutes.\n\n### 1. Create a new project\n\n```bash\nmkdir finances \u0026\u0026 cd finances\nuv init\n```\n\n### 2. Add dependencies\n\n```bash\n# Add core dependencies\nuv add beancount fava\n\n# Add git-based dependencies\nuv add beangulp --git https://github.com/beancount/beangulp\nuv add beancount-no-amex --git https://github.com/staticaland/beancount-no-amex\n```\n\n### 3. Configure as a package (manual edit needed)\n\nAdd the following to your `pyproject.toml`:\n\n```toml\n[tool.uv]\npackage = true\n\n[project.scripts]\nimport-transactions = \"finances.importers:main\"\n```\n\nThis enables the `import-transactions` command and makes your project installable.\n\nThen sync to apply the changes:\n\n```bash\nuv sync\n```\n\n### 4. Create the importer\n\nCreate `src/finances/importers.py`:\n\n```python\nfrom beangulp import Ingest\nfrom beancount_no_amex import AmexAccountConfig, Importer, match, when, amount\n\n\ndef get_importers():\n    return [\n        Importer(AmexAccountConfig(\n            account_name=\"Liabilities:CreditCard:Amex\",\n            currency=\"NOK\",\n            # Optional: specify account_id to match a specific card\n            # account_id=\"XYZ|12345\",\n            transaction_patterns=[\n                # Simple substring match\n                match(\"SPOTIFY\") \u003e\u003e \"Expenses:Subscriptions:Music\",\n                match(\"NETFLIX\") \u003e\u003e \"Expenses:Subscriptions:Streaming\",\n\n                # Case-insensitive matching\n                match(\"starbucks\").ignorecase \u003e\u003e \"Expenses:Coffee\",\n\n                # Regex pattern (handles variations like \"REMA 1000\", \"REMA1000\")\n                match(r\"REMA\\s*1000\").regex.ignorecase \u003e\u003e \"Expenses:Groceries\",\n\n                # Amount-based rules\n                when(amount \u003c 50) \u003e\u003e \"Expenses:PettyCash\",\n                when(amount.between(50, 200)) \u003e\u003e \"Expenses:Shopping:Medium\",\n\n                # Combined: merchant + amount threshold\n                match(\"VINMONOPOLET\").where(amount \u003e 500) \u003e\u003e \"Expenses:Alcohol:Expensive\",\n\n                # More examples\n                match(\"GITHUB\") \u003e\u003e \"Expenses:Cloud:GitHub\",\n                match(\"AWS\") \u003e\u003e \"Expenses:Cloud:AWS\",\n                match(\"COOP\") \u003e\u003e \"Expenses:Groceries\",\n                match(\"KIWI\") \u003e\u003e \"Expenses:Groceries\",\n            ],\n        )),\n    ]\n\n\ndef main():\n    ingest = Ingest(get_importers())\n    ingest.main()\n\n\nif __name__ == \"__main__\":\n    main()\n```\n\nAlso create `src/finances/__init__.py`:\n\n```bash\nmkdir -p src/finances\ntouch src/finances/__init__.py\n```\n\n### 5. Create the main ledger file\n\nCreate `main.beancount`:\n\n```beancount\noption \"title\" \"My Finances\"\noption \"operating_currency\" \"NOK\"\n\n; Account definitions\n2020-01-01 open Liabilities:CreditCard:Amex NOK\n2020-01-01 open Expenses:Subscriptions:Music NOK\n2020-01-01 open Expenses:Subscriptions:Streaming NOK\n2020-01-01 open Expenses:Groceries NOK\n2020-01-01 open Expenses:PettyCash NOK\n2020-01-01 open Expenses:Shopping:Medium NOK\n2020-01-01 open Expenses:Alcohol:Expensive NOK\n2020-01-01 open Expenses:Cloud:GitHub NOK\n2020-01-01 open Expenses:Cloud:AWS NOK\n2020-01-01 open Expenses:Uncategorized NOK\n\n; Include imported transactions\ninclude \"imports/*.beancount\"\n```\n\nCreate the imports directory:\n\n```bash\nmkdir -p imports\n```\n\n### 6. Download your Amex statement\n\n1. Log in to your American Express account\n2. Go to Statements \u0026 Activity\n3. Download the QBO file (should be named like `activity*.qbo`)\n4. Place it in a `downloads/` folder\n\n### 7. Import transactions\n\n```bash\n# Preview what will be imported\nuv run import-transactions extract downloads/\n\n# Save to a file\nuv run import-transactions extract downloads/ \u003e imports/2024-amex.beancount\n```\n\n### 8. View in Fava\n\n```bash\nuv run fava main.beancount\n```\n\nOpen http://localhost:5000 in your browser.\n\n## Classification for Humans\n\nThe library provides a Pythonic, fluent API for transaction classification:\n\n```python\nfrom beancount_no_amex import match, when, field, shared, amount\n\nrules = [\n    # Simple substring matching\n    match(\"SPOTIFY\") \u003e\u003e \"Expenses:Music\",\n    match(\"NETFLIX\") \u003e\u003e \"Expenses:Entertainment\",\n\n    # Regex patterns\n    match(r\"REMA\\s*1000\").regex \u003e\u003e \"Expenses:Groceries\",\n\n    # Case-insensitive matching\n    match(\"starbucks\").ignorecase \u003e\u003e \"Expenses:Coffee\",\n    match(\"starbucks\").i \u003e\u003e \"Expenses:Coffee\",  # short form\n\n    # Amount-based rules\n    when(amount \u003c 50) \u003e\u003e \"Expenses:PettyCash\",\n    when(amount \u003e 1000) \u003e\u003e \"Expenses:Large\",\n    when(amount.between(100, 500)) \u003e\u003e \"Expenses:Medium\",\n\n    # Combined conditions\n    match(\"VINMONOPOLET\").where(amount \u003e 500) \u003e\u003e \"Expenses:Alcohol:Fine\",\n\n    # Field-based matching (for bank account numbers, transaction types, etc.)\n    field(to_account=\"98712345678\") \u003e\u003e \"Assets:Savings\",\n    field(merchant_code=r\"5411|5412\").regex \u003e\u003e \"Expenses:Groceries\",\n\n    # Split across multiple accounts\n    match(\"COSTCO\") \u003e\u003e [\n        (\"Expenses:Groceries\", 80),\n        (\"Expenses:Household\", 20),\n    ],\n\n    # Shared expenses (tracking what roommates owe you)\n    match(\"GROCERIES\") \u003e\u003e \"Expenses:Groceries\" | shared(\"Assets:Receivables:Alex\", 50),\n]\n```\n\n### API Reference\n\n| Pattern Type | Example | Description |\n|--------------|---------|-------------|\n| Substring | `match(\"SPOTIFY\") \u003e\u003e \"...\"` | Matches if narration contains \"SPOTIFY\" |\n| Regex | `match(r\"REMA\\s*1000\").regex \u003e\u003e \"...\"` | Regex pattern matching |\n| Case-insensitive | `match(\"spotify\").ignorecase \u003e\u003e \"...\"` | Case-insensitive match |\n| Amount less than | `when(amount \u003c 50) \u003e\u003e \"...\"` | Amount under threshold |\n| Amount greater than | `when(amount \u003e 500) \u003e\u003e \"...\"` | Amount over threshold |\n| Amount range | `when(amount.between(100, 500)) \u003e\u003e \"...\"` | Amount within range |\n| Combined | `match(\"STORE\").where(amount \u003e 100) \u003e\u003e \"...\"` | Narration + amount condition |\n| Field match | `field(type=\"ATM\") \u003e\u003e \"...\"` | Match on metadata fields |\n| Split | `match(\"X\") \u003e\u003e [(\"A\", 80), (\"B\", 20)]` | Split across accounts |\n| Shared | `... \u003e\u003e \"X\" \\| shared(\"Receivable\", 50)` | Track shared expenses |\n\n### Traditional API\n\nThe fluent API builds on top of `TransactionPattern`, which you can still use directly:\n\n```python\nfrom beancount_no_amex import TransactionPattern, amount\n\npatterns = [\n    TransactionPattern(narration=\"SPOTIFY\", account=\"Expenses:Music\"),\n    TransactionPattern(narration=r\"REMA\\s*1000\", regex=True, account=\"Expenses:Groceries\"),\n    TransactionPattern(amount_condition=amount \u003c 50, account=\"Expenses:PettyCash\"),\n]\n```\n\n## Features\n\n### Multiple Cards\n\nConfigure separate importers for different Amex cards:\n\n```python\ndef get_importers():\n    return [\n        Importer(AmexAccountConfig(\n            account_name=\"Liabilities:CreditCard:Amex:Personal\",\n            currency=\"NOK\",\n            account_id=\"XYZ|12345\",  # From your QBO file\n            transaction_patterns=[...],\n        )),\n        Importer(AmexAccountConfig(\n            account_name=\"Liabilities:CreditCard:Amex:Business\",\n            currency=\"NOK\",\n            account_id=\"XYZ|67890\",\n            transaction_patterns=[...],\n        )),\n    ]\n```\n\n### Deduplication\n\nTransactions are automatically deduplicated using FITID (Financial Transaction ID). Re-running the import won't create duplicates. To force re-import:\n\n```python\nAmexAccountConfig(\n    account_name=\"...\",\n    currency=\"NOK\",\n    skip_deduplication=True,  # Bypass FITID checking\n)\n```\n\n## Project Structure\n\nAfter setup, your project should look like:\n\n```\nfinances/\n├── pyproject.toml\n├── main.beancount\n├── imports/\n│   └── 2024-amex.beancount\n├── downloads/\n│   └── activity2024.qbo\n└── src/\n    └── finances/\n        ├── __init__.py\n        └── importers.py\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstaticaland%2Fbeancount-no-amex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstaticaland%2Fbeancount-no-amex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstaticaland%2Fbeancount-no-amex/lists"}