5.1 KiB
5.1 KiB
finanz-cockpit Agent Instructions
Setup Commands
# Install dependencies
uv sync
# Run migrations
uv run alembic upgrade head
# Run all tests
uv run pytest
# Run a single test file
uv run pytest tests/test_model_b4wexport.py -v
Use uv run for project commands so they use the project-managed Python environment and dependencies.
Project Structure
src/: Application codemain.py: CLI entrypoint for CSV ingestiondb.py: Database connection management (uses global_enginepattern)models/: Pydantic models + SQLAlchemy tables for b4w and comdirect importsingest/: Data ingestion logic per source
tests/: Tests with fixtures inconftest.pyalembic/versions/: Database migrations
Key Patterns & Gotchas
Database Configuration
from src.db import get_engine, get_session, set_engine
# Development: use sqlite://
os.environ["DATABASE_URL"] = "sqlite://"
# Production/default: sqlite:///finanz.db
engine = get_engine() # reads DATABASE_URL or uses default
with get_session() as session:
session.query(...).all()
Critical: Tests override engine via conftest.py. Never assume global state - always use get_session() context manager.
CSV Format Expectations
- b4w: German bank export (CSV) from
/mnt/anbau/Software/Python/finanz-cockpit/scripts/dummy_data/b4w_export_dummy_01.csv - comdirect: German bank export (CSV) with fields like
Buchungstag,Umsatz in EUR,Vorgang
Testing Workflow
# Tests automatically run migrations via conftest fixture
uv run pytest tests/ -v --tb=short
# To test without migrations (in-memory SQLite), use DATABASE_URL="sqlite://"
DATABASE_URL="sqlite://" uv run pytest tests/test_db.py
Environment Variables
DATABASE_URL: Database connection string (default:sqlite:///finanz.db)
Data Models
Two sources with shared patterns:
- b4w:
B4wExport,Category, transactional import toimport_b4wtable +categorytable - comdirect:
ComdirectGiro,ComdirectCreditCard,ComdirectDepot, import tocomdirect_girotable - unified:
Transaction,TransactionDb– both ingest pipelines project their staging rows (import_b4w,comdirect_giro) into onetransactionstable viasrc/ingest/transactions.py(map_*mappers +consolidate_transactions). Dedup uses a business-keyhash_id(source + IBAN + value date + amount + text + counterparty) with a UNIQUE constraint and SQLiteINSERT OR IGNORE, so re-imports are idempotent. Amounts are canonical signed (negative = money out). Depot/credit-card/tagesgeld are not projected yet.
Category Budget Management (NEW)
Budget tracking for categories with time-bounded or unbounded periods:
from src.models.category import Category, CategoryBudget
from datetime import date
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/replace budget - handles overlap automatically:
from src.domain_utils import set_category_budget
set_category_budget(db_session, budget)
The set_category_budget() function intelligently handles three scenarios:
- No overlap: Adds new budget alongside existing ones
- Partial overlap: Splits existing budget into non-overlapping parts + adds new budget
- Full overlap: Replaces existing budget entirely
All models inherit from common base in src/models/common.py:
_hash(): SHA256-based deduplication key_parse_date_german(): Handles "07.09.2026" format and values like "offen", "--"_parse_decimal_german(): Converts "1,23" to Decimal
Factory Pattern for Testing
Import from tests/factories.py:
from tests.factories import B4wExportFactory, ComdirectGiroFactory
from src.constants import Currency
B4wExportFactory.__faker__ = Faker("de_DE")
ComdirectGiroFactory.__faker__ = Faker("de_DE")
# Use in tests:
item = B4wExportFactory.build( # build() for partial, create() for full
Amt="50.00",
RmtInf="Supermarket",
)
Common Mistakes to Avoid
- Assuming global engine state: Always use
get_session()context manager - Ignoring German date formats: Use
_parse_date_german()for CSV parsing - Forgetting migration setup in tests: Tests auto-migrate, but integration tests with real db need explicit upgrade
- Wrong Faker locale: Use
Faker("de_DE")for realistic names/IBANs
Testing Tips
- Run single test:
uv run pytest tests/test_model_b4wexport.py -v - View migrations:
ls alembic/versions/ - Test data location:
/scripts/dummy_data/
Type Checking (mypy)
# Static type checking with mypy
uv run mypy src/
# With test coverage
uv run pytest --cov=src --cov-report=term-missing
Note: Some comparisons between date | None types require explicit guards or type assertions to satisfy mypy, even when the logic is correct. See src/domain_utils.py for examples.
Code Style
- Don't be too verbose in docstrings and comments