Documentation and repo-hygiene pass following the client/server split:
Plan drift (audited all other active plans against current code):
- oikos-gaps-and-improvements.md: mark Section C and D.5 resolved (both
described cmd/hermes, renamed to cmd/nomos with a real LLM loop since);
refresh ~10 stale file:line citations; fix tool-count (33, not 28).
- liveness-drift-and-ux-cohesion.md: fix stale default-model claim (now
deepseek-v4-pro since 2026-07-10) and "not yet deployed" status.
- nomos-agent-code-review.md: fix C1's citation (one unauthenticated route
to nomos now, not two, after the client/server split).
- wails-desktop-app.md: record the production deploy outcome.
Repo structure: added missing directories to README/CONTRIBUTING layout
tables (checks/, tools/, cmd/webhook/, docs/operations/), fixed a broken
link, added ADR 0015 documenting the auth/CORS/client-split model (there
wasn't one despite CONTRIBUTING's own process requiring it), normalized
ADR 0013/0014's format drift, added an Authentication section to
AGENTS.md/CLIENTS.md (every example call was missing the now-required
bearer header).
Retired the Goose+Nomos workstation flow (bootstrap.sh --with-nomos,
tools/setup-nomos-soul.sh, .agents/operations/nomos-agent.md) and the
Caveman auto-install tooling (tools/setup-caveman.sh, tools/caveman/) —
both superseded by the production containerized Nomos agent, which has
never used either. Kept .agents/shared/caveman.md itself (the terse
writing-style convention agents still follow by reading it).
Deleted the orphaned legacy Python oikos/ directory — nothing imports it,
and bin/homelab (the CLI it was kept for) no longer exists in the repo.
Rewrote .agents/operations/agent-enrollment.md (365 -> ~110 lines) and
commands.md to match the current architecture instead of the retired
`homelab` CLI; migrated the still-true networking prerequisites (Netbird,
split-horizon DNS, SSH key distribution) into the knowledge base as a
runbook via upsert_knowledge rather than duplicating them in markdown.
Updated all 10 .agents/skills/ runbooks referencing the dead CLI with
their real MCP tool / REST API equivalents, or flagged them as needing
verification where no equivalent is confirmed yet.
Two real bugs found and fixed, not just docs:
- The tools/setup-*.sh auto-setup glob was tools/*.setup.sh in THREE
places (tools/post-pull.sh, bootstrap.sh, and internal/httpapi/impl.go's
GetClientContext handler) since the mechanism's introduction on
2026-06-02 — never matched any real filename, so no client has ever
picked up an auto-setup script via git-pull or the context-poller sync.
Fixed all three; the Go server-side fix is the one that actually matters
since it's what the current context-poller mechanism depends on.
- bootstrap.sh removed dead vestigial --gitea-token/--gitea-user flags
(parsed, never consumed) left over from an earlier clone-based model.
Also flagged, not fixed (documented as an open gap in
client-enrollment/SKILL.md): bootstrap.sh tells a freshly-enrolled client
to call POST /api/v1/clients/{slug}/activate to finish enrollment, but
that route doesn't exist in api/openapi.yaml — EnrollClient sets entities
to provisioning and nothing currently transitions them to active.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
210 lines
7.7 KiB
Markdown
210 lines
7.7 KiB
Markdown
---
|
||
name: budget-import-from-csv
|
||
risk_class: config_mutation
|
||
inputs: [csv_file]
|
||
references: [containers/129-house.md]
|
||
---
|
||
|
||
# Runbook: Budget import from N26 CSV → Yuvomi
|
||
|
||
Distil a bank-export CSV into Yuvomi's Budget and Subscriptions modules using
|
||
the `yuvomi-mcp` tools. Run this whenever a new CSV period needs to be
|
||
summarised into targets and fixed costs.
|
||
|
||
---
|
||
|
||
## Prerequisites
|
||
|
||
- `yuvomi-mcp` is running on LXC 129 and connected as an MCP server in Claude.
|
||
- The CSV is an N26 export (columns: Booking Date, Value Date, Partner Name,
|
||
Partner Iban, Type, Payment Reference, Account Name, Amount (EUR), …).
|
||
- API token: `yuvomi-api-token`, via Infisical (primary) or `oikos secret` (SOPS fallback).
|
||
- Direct API base: `https://house.hubris.network/api/v1`
|
||
|
||
---
|
||
|
||
## API quirks (Yuvomi ≤ 0.77.x)
|
||
|
||
- **Subscriptions live under `/budget/subscriptions`**, NOT `/subscriptions/`.
|
||
A top-level `/subscriptions` route returns 404.
|
||
- `GET /budget/subscriptions` → `{ data: { subscriptions: [...], summary: {...} } }`
|
||
- `GET /budget/subscriptions/meta` → `{ data: { categories: [...], payment_methods: [...] } }`
|
||
- `POST /budget/subscriptions` → create a subscription (name, amount, billing_cycle,
|
||
cycle_interval, next_payment_date, currency, category_id, payment_method_id required)
|
||
- `GET /budget/` (no month) → returns only **non-recurring** base entries.
|
||
Use `GET /budget/?month=YYYY-MM` to get all entries (recurring + one-time) for a month.
|
||
- `GET /budget/categories` → expense category keys + income category names (German keys
|
||
like `"Erwerbseinkommen"`, `"Sozialleistungen"`, `"Geschenke & Transfers"`).
|
||
- Budget entries: `amount` positive = income, negative = expense.
|
||
- Recurring entries: set `is_recurring: 1` + `recurrence_interval: "monthly"`.
|
||
The `date` field sets the start month.
|
||
- `recurrence_virtual: 1` smooths non-monthly amounts across all months in the summary
|
||
(e.g. 55.08 € quarterly → shows as ~18.36 €/month).
|
||
- Custom RRULE strings (`recurrence_rule`) are **not accepted** by the API — use
|
||
`cycle_interval` on the subscription instead, or `recurrence_interval` on budget entries.
|
||
|
||
---
|
||
|
||
## Subscription category IDs (as of 2026-06-26)
|
||
|
||
| id | name | budget_subcategory_key |
|
||
|---|---|---|
|
||
| 1 | Entertainment | subscription_entertainment |
|
||
| 2 | Productivity | subscription_productivity |
|
||
| 3 | Utilities | subscription_utilities |
|
||
| 4 | Health | subscription_health |
|
||
| 5 | Education | subscription_education |
|
||
| 6 | Other | subscription_other |
|
||
|
||
## Payment method IDs
|
||
|
||
| id | name |
|
||
|---|---|
|
||
| 1 | Credit Card |
|
||
| 2 | Debit Card |
|
||
| 3 | PayPal |
|
||
| 6 | Bank Transfer / SEPA |
|
||
| 7 | Other |
|
||
|
||
---
|
||
|
||
## Step 1 — Categorise the transactions
|
||
|
||
Skip these as internal/already-covered:
|
||
- Fixed costs you'll enter as **subscriptions** (Miete, SWM, SYNVIA, Hundefutter,
|
||
Netflix, Grover, Rundfunk ARD, KuKita, Lillydoo)
|
||
- Internal transfers (The Joy Pot ↔ Cookie, Hauptkonto, Tagesgeldkonto splits)
|
||
- Identified income (Cookie Share, Kindergeld, Pocket Money credits, Distributor)
|
||
- Fun Money pass-throughs (in and out same month → net zero)
|
||
|
||
**Variable expense taxonomy:**
|
||
|
||
| Category key | Subcategory key | Examples |
|
||
|---|---|---|
|
||
| `food` | `groceries` | E-Center, Knuspr, EDEKA, Tegut, VollCorner, Lidl, Netto, REWE, KoRo, Roast Market |
|
||
| `food` | `restaurants_bars` | Restaurants, Lieferando, Cafes, Zeit für Brot, Baobab, Höflinger |
|
||
| `personal_health` | `beauty_cosmetics` | DM, Rossmann |
|
||
| `personal_health` | `pharmacy` | Apotheke, MVZ Dermatologie |
|
||
| `transport` | `apps_taxi` | Uber, RYD GMBH, MVG, Handyparken |
|
||
| `shopping_clothing` | `gifts` | Children products: Schlummersack, Catchy Kids, SP EVERY., Dukal, Berger-Lernwelt |
|
||
| `shopping_clothing` | `clothes_shoes` | Zalando, Ernsting's, Schuhmair, Thalia, Vinted, Airbnb, Hotel at Booking.com |
|
||
| `shopping_clothing` | `electronics` | Amazon, AMZN Mktp DE |
|
||
| `housing` | `renovation_maintenance` | IKEA, Markus Festl, Granit, Sostrene Grene, Mol* tischdecken, Gaertnerei, Dehner |
|
||
| `education` | `courses_college` | Kathrin Orlob (PEKiP), Nerina Aupperle |
|
||
| `leisure` | `streaming` | WOW wowtv.de |
|
||
| `financial_other` | `bank_fees` | Unidentified PayPal, Ratepay, N26 fees |
|
||
| `Geschenke & Transfers` | *(income)* | One-off incoming transfers |
|
||
|
||
---
|
||
|
||
## Step 2 — Create subscriptions
|
||
|
||
```
|
||
get_subscriptions_meta() ← get category_id and payment_method_id
|
||
```
|
||
|
||
**Standard Cookie household subscriptions (as of 2026-07):**
|
||
|
||
| Name | Amount | billing_cycle | cycle_interval | category_id | payment_method_id |
|
||
|---|---|---|---|---|---|
|
||
| Miete | 1080.00 | monthly | 1 | 6 (Other) | 6 (Bank Transfer) |
|
||
| Strom (SWM) | 79.00 | monthly | 1 | 3 (Utilities) | 6 |
|
||
| Internet / TV / Telefon | 29.99 | monthly | 1 | 3 (Utilities) | 6 |
|
||
| Hundefutter | 75.00 | monthly | 1 | 6 (Other) | 6 |
|
||
| Netflix | 8.00 | monthly | 1 | 1 (Entertainment) | 6 |
|
||
| Grover | 16.90 | monthly | 1 | 6 (Other) | 2 (Debit Card) |
|
||
| Rundfunk ARD / ZDF | 55.08 | monthly | 3 | 1 (Entertainment) | 6 |
|
||
| KuKita Daycare (Leon) | 503.00 | monthly | 1 | 5 (Education) | 6 |
|
||
| Lillydoo diapers | 56.70 | monthly | 2 | 4 (Health) | 3 (PayPal) |
|
||
|
||
Monthly equivalent total: **1,838.60 €** (Yuvomi applies cycle_interval to prorate).
|
||
|
||
---
|
||
|
||
## Step 3 — Add recurring income entries
|
||
|
||
```
|
||
stage_add_budget_entry(
|
||
title="Kindergeld",
|
||
amount=55.00,
|
||
category="Sozialleistungen",
|
||
date="YYYY-MM-01",
|
||
is_recurring=True,
|
||
recurrence_interval="monthly",
|
||
)
|
||
commit_pending(pending_id)
|
||
```
|
||
|
||
**Standard recurring income:**
|
||
|
||
| Title | Amount | category |
|
||
|---|---|---|
|
||
| Kindergeld | +55.00 | Sozialleistungen |
|
||
| Cookie Share | +2650.00 | Erwerbseinkommen *(see recommended amount below)* |
|
||
|
||
---
|
||
|
||
## Step 4 — Post variable transactions
|
||
|
||
For each non-skipped CSV row, call `stage_add_budget_entry` with the mapped
|
||
category/subcategory and the actual transaction amount and date. Use the Partner
|
||
Name + Payment Reference as the title (truncate to 100 chars).
|
||
|
||
---
|
||
|
||
## Step 5 — Verify
|
||
|
||
```
|
||
get_budget_summary("YYYY-MM")
|
||
list_subscriptions()
|
||
```
|
||
|
||
Expected for a full month with KuKita:
|
||
- Fixed expenses ≥ 1,838 € (subscriptions)
|
||
- Variable expenses ≥ 500 € (groceries alone)
|
||
|
||
---
|
||
|
||
## Cookie Share: how much to transfer monthly
|
||
|
||
Calculated from Jan–Jun 2026 data (Cookie account, one-offs stripped):
|
||
|
||
| | €/month |
|
||
|---|---|
|
||
| **Fixed costs (subscriptions)** | **1,839** |
|
||
| Miete | 1,080 |
|
||
| KuKita *(permanent from Jul 2026)* | 503 |
|
||
| Strom + SYNVIA + Rundfunk + Netflix + Grover + Hundefutter + Lillydoo | 256 |
|
||
| **Variable (6-month averages)** | **1,032** |
|
||
| Groceries | 595 |
|
||
| Children products | 142 |
|
||
| Dining & cafes | 100 |
|
||
| Transport | 66 |
|
||
| Drugstore | 52 |
|
||
| Clothing, Amazon, Pharmacy | 77 |
|
||
| **Total monthly spend** | **≈ 2,871** |
|
||
| Minus Kindergeld (fixed income) | −55 |
|
||
| Minus Pocket Money (conservative ~600 €) | −600 |
|
||
| **→ Recommended Cookie Share** | **≈ 2,650 €** |
|
||
| With 200 € buffer | **≈ 2,850 €** |
|
||
|
||
**Current Cookie Share (Jun 2026): 1,995 € — shortfall ~655 €.**
|
||
|
||
The gap was covered by irregular Pocket Money top-ups (avg 962 €/mo over 6 months, but
|
||
highly variable: 121 €–3,000 €). KuKita starting in June is the biggest step-up; raising
|
||
Cookie Share to **2,650 €** makes the budget self-sufficient without relying on top-ups.
|
||
|
||
---
|
||
|
||
## Changelog
|
||
|
||
### 2026-06-29 — Corrections from first real import
|
||
- Subscriptions endpoint is `/budget/subscriptions`, NOT `/subscriptions/` (404).
|
||
- `recurrence_rule` RRULE strings are rejected by the API; use `cycle_interval` instead.
|
||
- `GET /budget/` (no filter) returns only non-recurring entries; use `?month=` for full view.
|
||
- Added Cookie Share recommendation (2,650 €/month) based on 6-month expense analysis.
|
||
- Added full category taxonomy table.
|
||
|
||
### 2026-06-29 — Initial runbook
|
||
Created from Jan–Jun 2026 N26 Cookie account analysis.
|