{"id":51316055,"url":"https://github.com/anym001/pocketlog-importer","last_synced_at":"2026-07-01T07:04:33.451Z","repository":{"id":364637617,"uuid":"1266518711","full_name":"anym001/pocketlog-importer","owner":"anym001","description":"Companion importer for PocketLog — parses bank CSV exports, applies a rules whitelist, imports via the PocketLog API.","archived":false,"fork":false,"pushed_at":"2026-06-23T17:37:02.000Z","size":167,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T18:27:25.545Z","etag":null,"topics":["automation","bank-statements","csv","docker","docker-image","etl","importer","personal-finance","pocketlog","python","self-hosted"],"latest_commit_sha":null,"homepage":"https://github.com/anym001/pocketlog-importer/pkgs/container/pocketlog-importer","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anym001.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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":"2026-06-11T17:36:33.000Z","updated_at":"2026-06-15T09:57:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/anym001/pocketlog-importer","commit_stats":null,"previous_names":["anym001/pocketlog-importer"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/anym001/pocketlog-importer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anym001%2Fpocketlog-importer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anym001%2Fpocketlog-importer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anym001%2Fpocketlog-importer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anym001%2Fpocketlog-importer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anym001","download_url":"https://codeload.github.com/anym001/pocketlog-importer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anym001%2Fpocketlog-importer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34996299,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"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":["automation","bank-statements","csv","docker","docker-image","etl","importer","personal-finance","pocketlog","python","self-hosted"],"created_at":"2026-07-01T07:04:32.682Z","updated_at":"2026-07-01T07:04:33.433Z","avatar_url":"https://github.com/anym001.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PocketLog Importer\n\n[![Tests](https://img.shields.io/github/actions/workflow/status/anym001/pocketlog-importer/test.yml?label=Tests)](https://github.com/anym001/pocketlog-importer/actions/workflows/test.yml)\n[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://github.com/anym001/pocketlog-importer/blob/HEAD/LICENSE)\n[![Release](https://img.shields.io/github/v/release/anym001/pocketlog-importer?label=Release)](https://github.com/anym001/pocketlog-importer/releases)\n[![GHCR](https://img.shields.io/badge/GHCR-pocketlog--importer-2496ED?logo=docker\u0026logoColor=white)](https://github.com/anym001/pocketlog-importer/pkgs/container/pocketlog-importer)\n[![Docker Hub](https://img.shields.io/badge/Docker%20Hub-pocketlog--importer-2496ED?logo=docker\u0026logoColor=white)](https://hub.docker.com/r/anym001/pocketlog-importer)\n\nA small Docker container that turns bank CSV exports (**easybank**, **dadat**)\ninto [PocketLog](https://github.com/anym001/pocketlog) transactions. You drop a\nCSV into a folder, a rules whitelist decides what gets imported (description,\ncategory, tags), and the result is pushed to PocketLog via its CSV import API.\n\nImages are published to **GHCR** and **Docker Hub** — use whichever you prefer\n(`image:` in your compose file):\n\n```\nghcr.io/anym001/pocketlog-importer:\u003ctag\u003e\nanym001/pocketlog-importer:\u003ctag\u003e      # Docker Hub\n```\n\n## How it works\n\n```\nbank export ─▶ /data/input ─▶ parse ─▶ rules.yaml (whitelist) ─▶ /data/output ─▶ PocketLog API\n                                            │\n                                            └─ no match ─▶ \u003cbank\u003e.unmatched.csv (review)\n```\n\n1. The container runs an **internal scheduler** (cron, default hourly).\n2. Each `*.csv` in `/data/input` is auto-detected (easybank / dadat), parsed and\n   normalised (amount always positive, direction in `type`).\n3. Every booking is matched against `rules.yaml` (regex, case-insensitive,\n   first match wins). **A booking that matches no rule is dropped** — only\n   curated bookings reach PocketLog. Dropped bookings are written to a\n   `*.unmatched.csv` for review so you can add a rule later.\n4. Matched bookings are written to `/data/output/\u003cbank\u003e-\u003cts\u003e.csv` and imported\n   via `POST /api/import/csv`. PocketLog deduplicates, so re-runs are safe.\n   Transient API failures (network errors, 5xx, 429) are retried with\n   exponential backoff before a file counts as failed.\n5. The processed original is moved to `/data/processed/`. Files that fail to\n   parse or import go to `/data/failed/`.\n\n## Quick start\n\n1. **Create an API key** in PocketLog (UI → API keys) with the **`import`** scope.\n2. Prepare the host folders and start the container (see\n   `docker/docker-compose.example.yml`):\n   ```sh\n   mkdir -p config data/input\n   POCKETLOG_API_KEY=plk_xxx docker compose -f docker/docker-compose.example.yml up -d\n   ```\n   On first start the container seeds `config/config.yaml` and\n   `config/rules.yaml` from the bundled examples if they are missing (it logs a\n   WARNING). Then edit `config/config.yaml` → set `pocketlog.base_url`, edit\n   `config/rules.yaml` to match your bookings, and restart.\n   \u003e To configure **before** the first start instead, copy the examples\n   \u003e yourself: `cp config/config.example.yaml config/config.yaml` and\n   \u003e `cp config/rules.example.yaml config/rules.yaml`.\n3. Drop a bank CSV into `data/input/`. The scheduler picks it up; or trigger it\n   immediately:\n   ```sh\n   docker exec pocketlog-importer pocketlog-import --once\n   ```\n\n### Try it safely first (dry-run)\n\n`--dry-run` writes the output CSVs but does **not** import anything:\n\n```sh\ndocker exec pocketlog-importer pocketlog-import --once --dry-run\n```\n\n## Triggering\n\nThree equivalent ways to run the pipeline:\n\n| Method | Command |\n|---|---|\n| Automatic | internal scheduler (`schedule.cron` in `config.yaml`) |\n| On demand | `docker exec pocketlog-importer pocketlog-import --once` |\n| Test | `... pocketlog-import --once --dry-run` |\n\nThe `--once` path is ideal for **Unraid User Scripts**. A file lock prevents a\nmanual run from overlapping with a scheduler tick.\n\n### Container health\n\nEvery run (idle ones included) touches a heartbeat file. The image's\n`HEALTHCHECK` runs `pocketlog-import --healthcheck`, which reports unhealthy\nonce the heartbeat is older than ~2 cron intervals — so a wedged scheduler\nshows up directly in `docker ps` / the Unraid dashboard instead of going\nunnoticed. The threshold adapts to `schedule.cron` automatically.\n\n## Configuration\n\n`config/config.yaml` — see [`config/config.example.yaml`](https://github.com/anym001/pocketlog-importer/blob/HEAD/config/config.example.yaml).\nThe PocketLog **API key is never stored in YAML**; provide it via the\n`POCKETLOG_API_KEY` environment variable.\n\n### Rules\n\n`config/rules.yaml` — see [`config/rules.example.yaml`](https://github.com/anym001/pocketlog-importer/blob/HEAD/config/rules.example.yaml).\n\n```yaml\nrules:\n  - match: \"STREAMINGCO\"            # regex, case-insensitive, tested against booking text\n    description: \"Streaming Service\" # overrides description (default: raw booking text)\n    category: \"Entertainment\"        # PocketLog category (auto-created if new)\n    tags: [subscription]             # tags (auto-created if new)\n    # type: in                       # optional, overrides the amount-sign direction\n    # bank: easybank                 # optional, restrict to one parser\n```\n\nRules are evaluated top to bottom; the **first** matching rule wins.\n\n### Notifications\n\nOptional push notifications about run outcomes via any **Gotify-compatible**\nendpoint — this includes [PushBits](https://github.com/pushbits/server)\n(relays to Matrix) and Gotify itself. Off unless `notify.url` is set:\n\n```yaml\nnotify:\n  type: gotify                       # PushBits + Gotify\n  url: https://pushbits.example.com\n  events: problems                   # problems (default) | always\n```\n\nThe application token goes into the `NOTIFY_TOKEN` environment variable —\nnever into YAML. `events: problems` notifies only on failed files, unmatched\nbookings, or a crashed run (high priority); `events: always` also reports\nclean runs. Idle runs (empty input directory) and dry-runs never notify, and\nnotifications carry only counters and filenames — no booking data.\nNotification delivery is best-effort: a failed push is logged and never\naffects the import itself.\n\n## Environment variables\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `POCKETLOG_API_KEY` | — | **Required** for real imports (`import` scope key) |\n| `POCKETLOG_BASE_URL` | — | Optional override of `pocketlog.base_url` |\n| `NOTIFY_TOKEN` | — | Application token for `notify.url` (PushBits/Gotify) |\n| `PUID` / `PGID` | `1000` | Ownership of `/config` + `/data` (Unraid: `99` / `100`) |\n| `LOG_LEVEL` | `INFO` | Log verbosity |\n| `LOG_FORMAT` | `text` | Log format: `text` or `json` (one JSON object per line) |\n| `LOG_FILE` | — | Optional rotating log file, e.g. `/config/logs/importer.log` |\n| `LOG_FILE_MAX_BYTES` | `1048576` | Rotation size |\n| `LOG_FILE_BACKUPS` | `5` | Rotated copies kept |\n\n## Volumes\n\n| Path | Contents |\n|---|---|\n| `/config` | `config.yaml`, `rules.yaml`, optional `logs/` |\n| `/data/input` | drop bank CSVs here |\n| `/data/output` | generated PocketLog CSVs + `*.unmatched.csv` |\n| `/data/processed` | successfully processed originals, one subdirectory per run |\n| `/data/failed` | files that failed to parse or import, one subdirectory per run |\n\n## Supported banks\n\n| Bank | File | Format |\n|---|---|---|\n| easybank | `EASYBANK_Umsatzliste_*.csv` | no header, 6 cols, `DD.MM.YYYY`, `-13,99` |\n| dadat | `umsaetzegirokonto_*.csv` | header, 27 cols, `YYYY-MM-DD`, `-200,00` |\n\nAdding a bank = a new parser in `pocketlog_importer/parsers/` (implement `sniff` +\n`parse`) registered in `parsers/__init__.py`.\n\n## Development\n\n```sh\npython -m venv .venv \u0026\u0026 . .venv/bin/activate\npip install -r requirements-dev.txt \u0026\u0026 pip install -e .\n```\n\nLint and test commands (= CI) and the branching/release flow are in\n[`CONTRIBUTING.md`](https://github.com/anym001/pocketlog-importer/blob/HEAD/CONTRIBUTING.md).\n\n### Contract tests\n\n`tests/integration/` runs the real pipeline against a real PocketLog container\nand pins the import API contract (round-trip, dedup idempotency, per-row error\nformat, auth scopes). Requires Docker; excluded from the default `pytest -q`\nrun:\n\n```sh\npytest -m integration                                          # released image\nPOCKETLOG_IMAGE=ghcr.io/anym001/pocketlog:dev pytest -m integration\n```\n\nCI runs them on every PR against the released image, and nightly against\n`:latest` + `:dev` (`contract.yml`) to catch contract drift from the PocketLog\nside before it is released.\n\n## License\n\nLicensed under the GNU Affero General Public License v3.0 or later\n(AGPL-3.0-or-later), the same license as the companion\n[`pocketlog`](https://github.com/anym001/pocketlog) project. See\n[`LICENSE`](https://github.com/anym001/pocketlog-importer/blob/HEAD/LICENSE) for the full text.\n\n---\n\nBuilt with [Claude Code](https://claude.com/claude-code).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanym001%2Fpocketlog-importer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanym001%2Fpocketlog-importer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanym001%2Fpocketlog-importer/lists"}