Files
finanz-cockpit/AGENTS.md
T
mathias2784 c0adc77f56
Tests / test (push) Successful in 11s
Tests / lint (push) Successful in 11s
initial load of categories
2026-10-18 08:43:45 +02:00

4.9 KiB
Raw Blame History

finanz-cockpit Agent Instructions

Setup Commands

# Install dependencies
uv sync

# Run migrations
alembic upgrade head

# Run all tests
pytest

# 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 (uses global _engine pattern)
    • models/: Pydantic models + SQLAlchemy tables for b4w and comdirect imports
    • ingest/: Data ingestion logic per source
  • tests/: Tests with fixtures in conftest.py
  • alembic/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
pytest tests/ -v --tb=short

# To test without migrations (in-memory SQLite), use DATABASE_URL="sqlite://"
export DATABASE_URL="sqlite://" 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 to import_b4w table + category table
  • comdirect: ComdirectGiro, ComdirectCreditCard, ComdirectDepot, import to comdirect_giro table
  • unified: Transaction, TransactionDb – both ingest pipelines project their staging rows (import_b4w, comdirect_giro) into one transactions table via src/ingest/transactions.py (map_* mappers + consolidate_transactions). Dedup uses a business-key hash_id (source + IBAN + value date + amount + text + counterparty) with a UNIQUE constraint and SQLite INSERT 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:

  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

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

  1. Assuming global engine state: Always use get_session() context manager
  2. Ignoring German date formats: Use _parse_date_german() for CSV parsing
  3. Forgetting migration setup in tests: Tests auto-migrate, but integration tests with real db need explicit upgrade
  4. Wrong Faker locale: Use Faker("de_DE") for realistic names/IBANs

Testing Tips

  • Run single test: 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