# finanz-cockpit Financial tracking application for German bank exports (b4w, comdirect) with unified transaction processing and category-based budget management. ## Features - **Dual-source ingestion**: Import transactions from b4w and comdirect CSV exports - **Unified transaction table**: All transactions are consolidated into a single `transactions` table using business-key deduplication - **Category hierarchy**: Support for nested category structures (e.g., `"Lebenshaltung:Essen:Supermarkt"`) - **Budget tracking per category**: Set and manage budgets with time-bounded or unbounded validity periods - **Automatic overlap handling**: Smart budget management handles partial/full overlaps automatically ## Installation ```bash uv sync alembic upgrade head ``` ## Usage ### Import b4w Export ```python from src.ingest.b4w import ingest_b4w from src.db import get_session from pathlib import Path csv_path = Path("/mnt/anbau/Software/Python/finanz-cockpit/scripts/dummy_data/b4w_export_dummy_01.csv") with get_session() as session: ingest_b4w(csv_path, session) ``` ### Import comdirect Export ```python from src.ingest.comdirect import ingest_comdirect from src.db import get_session from pathlib import Path csv_path = Path("/mnt/anbau/Software/Python/finanz-cockpit/scripts/dummy_data/comdirect_giro_01.csv") with get_session() as session: ingest_comdirect( session, csv_path, account_iban="DE89370400440532013000", account_currency="EUR", ) ``` ### Set Category Budgets ```python from src.models.category import Category, CategoryBudget from datetime import date from src.domain_utils import set_category_budget from src.db import get_session # Create category hierarchy cat = Category.from_str("Lebenshaltung:Essen:Supermarkt") # Set budget for October-November 2026 budget = CategoryBudget( category=cat, amount_eur=150.00, valid_from=date(2026, 10, 1), valid_to=date(2026, 11, 30), ) with get_session() as session: set_category_budget(session, budget) session.commit() # Budgets can also be unbounded (no dates specified) budget = CategoryBudget( category=cat, amount_eur=200.00, # Monthly average ) set_category_budget(session, budget) session.commit() ``` ## Testing ```bash # Run all tests pytest # Run with coverage pytest --cov=src --cov-report=term-missing # Run a single test file pytest tests/test_model_b4wexport.py -v ``` ## Project Structure - **`src/`**: Application code - `main.py`: CLI entrypoint for CSV ingestion - `db.py`: Database connection management - `models/`: Pydantic models + SQLAlchemy tables - `ingest/`: Data ingestion logic per source (b4w, comdirect) - `domain_utils.py`: Domain-level utilities (budget overlap handling) - **`tests/`**: Tests with fixtures in `conftest.py` - **`alembic/versions/`**: Database migrations ## Data Models ### Transaction Sources - **b4w**: `B4wExport`, `Category` - **comdirect**: `ComdirectGiro`, `ComdirectCreditCard`, `ComdirectDepot` - **unified**: `Transaction`, `TransactionDb` – consolidates all sources into one table All models inherit from common base in `src/models/common.py`: - `hash_id()`: SHA256-based deduplication key - `_parse_date_german()`: Handles "07.09.2026" format and values like "offen", "--" - `_parse_decimal_german()`: Converts "1,23" to Decimal ### Category Budgets Budget tracking for categories with time-bounded or unbounded periods: ```python from src.models.category import Category, CategoryBudget cat = Category(name="Lebenshaltung") budget = CategoryBudget( category=cat, amount_eur=150.00, valid_from=date(2026, 10, 1), # Optional: None for unbounded start valid_to=date(2026, 11, 30), # Optional: None for unbounded end ) set_category_budget(db_session, budget) ``` The `set_category_budget()` function handles overlap scenarios automatically: 1. **No overlap**: Adds new budget alongside existing ones 2. **Partial overlap**: Splits existing budget into non-overlapping parts + adds new budget 3. **Full overlap**: Replaces existing budget entirely ## Development ### Setup ```bash # Install dependencies uv sync # Run migrations alembic upgrade head # Run all tests pytest # Run with coverage report pytest --cov=src --cov-report=html:htmlcov ``` ### Common Patterns **Database Session**: Always use `get_session()` context manager: ```python from src.db import get_session with get_session() as session: # query operations here session.commit() ``` **CSV Format**: - **b4w**: German bank export (CSV) - **comdirect**: CSV with fields like `Buchungstag`, `Umsatz in EUR`, `Vorgang` ### Testing Workflow Tests automatically run migrations via `conftest.py`. For testing without migrations: ```bash export DATABASE_URL="sqlite://" pytest tests/test_db.py ``` ## Common Mistakes 1. **Global engine state**: Always use `get_session()` context manager 2. **German date formats**: Use `_parse_date_german()` for CSV parsing 3. **Migration setup**: Tests auto-migrate, but integration tests need explicit `alembic upgrade head` 4. **Faker locale**: Use `Faker("de_DE")` for realistic names/IBANs ## License MIT