Files
mathias2784 6babc560c7
Tests / test (push) Successful in 16s
Tests / lint (push) Successful in 16s
Category overhaul (#20)
Reviewed-on: #20
Co-authored-by: Mathias <mathias2784@web.de>
2026-10-27 18:08:10 +00:00

153 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# finanz-cockpit Agent Instructions
## Setup Commands
```bash
# 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 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
```python
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
```bash
# 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 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:
```python
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`:
```python
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: `uv run pytest tests/test_model_b4wexport.py -v`
- View migrations: `ls alembic/versions/`
- Test data location: `/scripts/dummy_data/`
### Type Checking (mypy)
```bash
# 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