|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +KVK-Connect is a Python library and Docker microservice suite for integrating with the Dutch Chamber of Commerce (KVK) API. It serves three purposes: |
| 8 | +1. A pip-installable package for fetching KVK data without deep API knowledge |
| 9 | +2. A local mirror of KVK data (basisprofiel, vestigingen, vestigingsprofiel) kept up-to-date in a database |
| 10 | +3. An optional mutation service that polls KVK API for company changes and syncs them to the local mirror |
| 11 | + |
| 12 | +## Commands |
| 13 | + |
| 14 | +Before every commit, always run `just check-all` twice. The first run may auto-fix files (ruff); the second run validates the result is clean. Only commit if the second run passes fully. |
| 15 | + |
| 16 | +```bash |
| 17 | +# Install dependencies |
| 18 | +just install # uv sync |
| 19 | + |
| 20 | +# Quality checks |
| 21 | +just test # pytest |
| 22 | +just cov # pytest with coverage report |
| 23 | +just lint # ruff check + format |
| 24 | +just typing # pyright type checking |
| 25 | +just check-all # all checks (lint, cov, typing, pre-commit) |
| 26 | + |
| 27 | +# Run a single test |
| 28 | +uv run pytest tests/path/to/test_file.py::test_function_name |
| 29 | + |
| 30 | +# Docker |
| 31 | +just docker-build # build containers |
| 32 | +just docker-up # start all services |
| 33 | +just docker-down # stop all services |
| 34 | +``` |
| 35 | + |
| 36 | +## Architecture |
| 37 | + |
| 38 | +### Three Model Layers |
| 39 | + |
| 40 | +Data flows through three distinct model layers (all in `src/kvk_connect/models/`): |
| 41 | +- **API models** (`models/api/`): Raw dataclasses deserialized from KVK API JSON responses. Each has `from_dict()` and `to_dict()` methods. |
| 42 | +- **Domain models** (`models/domain/`): Business logic representations, also dataclasses with `from_dict()`/`to_dict()`. |
| 43 | +- **ORM models** (`models/orm/`): SQLAlchemy mapped tables for database persistence. |
| 44 | + |
| 45 | +### Mappers and Services |
| 46 | + |
| 47 | +`src/kvk_connect/mappers/` contains functions that convert API models → Domain models. `src/kvk_connect/services/record_service.py` (`KVKRecordService`) is the public-facing high-level API that orchestrates fetching and mapping. Both `KVKApiClient` and `KVKRecordService` are exported from `kvk_connect.__init__` as the library's public API. |
| 48 | + |
| 49 | +### Database Layer |
| 50 | + |
| 51 | +`src/kvk_connect/db/` has separate reader (`*_reader.py`) and writer (`*_writer.py`) classes. Schema is auto-initialized via `ensure_database_initialized()` in `db/init.py` — no Alembic. Use direct SQL for any schema changes. |
| 52 | + |
| 53 | +### Docker Apps |
| 54 | + |
| 55 | +Five Docker services in `apps/`, each with a `main.py` and `Dockerfile`: |
| 56 | +- `gateway`: NGINX rate limiter (port 8080) — all apps route API calls through this |
| 57 | +- `basisprofiel`, `vestigingen`, `vestigingsprofiel`: Fetch and persist respective KVK data types |
| 58 | +- `mutatie-reader`: Polls KVK mutation API and writes change signals to DB |
| 59 | + |
| 60 | +Apps depend on each other in order: mutatie-reader → basisprofiel → vestigingen → vestigingsprofiel. Compose files: `docker-compose.local.yaml` (SQLite/local) and `docker-compose.db.yaml` (PostgreSQL). |
| 61 | + |
| 62 | +### Error Handling Pattern |
| 63 | + |
| 64 | +`src/kvk_connect/exceptions.py` defines two error types: |
| 65 | +- `KVKPermanentError`: Company doesn't exist (e.g., error code IPD0005) — long retry delay (24h default) |
| 66 | +- `KVKTemporaryError`: Temporary unavailability (e.g., IPD1002, IPD1003) — short retry delay (10m) |
| 67 | + |
| 68 | +`KVKApiClient` uses `@global_rate_limit()` decorator (`utils/rate_limit.py`) on all API methods. |
| 69 | + |
| 70 | +## Coding Standards |
| 71 | + |
| 72 | +- **Python 3.13+**, PEP 8, full type hints everywhere |
| 73 | +- Use `Mapped[T]` for SQLAlchemy ORM models |
| 74 | +- All dataclasses must have `from_dict()` static method and `to_dict()` using `asdict()` |
| 75 | +- Use `from __future__ import annotations` for forward references in dataclasses |
| 76 | +- Log with lazy `%s` formatting, not f-strings: `logging.info('Fetched %d records', count)` (Pylint W1203) |
| 77 | +- Use `uv` for package management, not `pip` |
| 78 | +- Database agnostic via SQLAlchemy — no Redis, no Alembic |
| 79 | +- Business domain terms in comments/docstrings may use Dutch; all code (variables, functions) in English |
| 80 | + |
| 81 | +### Git Worktrees |
| 82 | + |
| 83 | +When using git worktrees, create them **inside the project folder** (e.g., `.worktrees/`). |
0 commit comments