194 lines
5.1 KiB
Markdown
194 lines
5.1 KiB
Markdown
# 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
|