Modular on purpose — handlers stay thin.
The interactive preview you are using is a faithful CRM model. The portfolio artifact is the Python package: aiogram 3, tests, Docker, and Alembic. Both share the same lead shape and status vocabulary.
Handlers
app/handlers/customer.py · admin.py · errors.py
Thin Telegram adapters. They parse updates, answer callbacks, and call services. No SQL lives here.
Services
lead · export · notification · validation · auth
Lead creation, duplicate window, status transitions, CSV bytes, admin checks, and outbound notices.
Repositories
app/repositories/lead_repository.py
The only module that talks to SQLAlchemy for leads: create, fetch, list, counts, notes, duplicates.
Persistence
SQLAlchemy 2 async · aiosqlite · Alembic
UTC timestamps, enum columns without native PG enums, human-readable public_id, SQLite for the local demo.
Lead fields
id · public_id · telegram_user_id · username · full_name · phone · service_category · description · preferred_contact_method · status · admin_note · created_at · updated_at