5.6 KiB
AGENTS.md
Mission
Maintain a deliberately small, self-hosted rota application for a non-profit bar/club. Prefer clarity and operational simplicity over enterprise HR features.
Domain invariants
- Assignments are the source of truth. Recurrence/rotation rules only create assignments that do not already exist.
- There are three seeded schedules:
night: fixed/manual, every day of the month.saturday-lunch: rotating, Saturdays only.sunday-lunch: rotating, Sundays only.
- Saturday and Sunday rotations are independent, each with its own configuration page (
/admin/schedules/<id>); the night schedule is fixed and repeats every month with a single configuration. The/admin/schedulesand/schedulespages are hubs that link to one page per schedule. - Never silently rewrite an existing assignment when rotation membership/order changes.
- A swap/change request only applies if the requester still owns the assignment when it is accepted/approved.
emailandphoneare optional.username/passwordare optional as a pair: a person either signs in or is rota-only. Ausername, when set, is unique.- Email failure must never roll back or block a rota change. Log failures in
notification_logs. - Past assignments cannot be offered for change through the member UI.
Stack
- Python 3.12+
- FastAPI + server-rendered Jinja2
- SQLAlchemy 2.x
- SQLite by default
- No frontend build step or JavaScript framework
- Docker Compose for self-hosting
- Docker Compose
testprofile for running tests independently of the host Python environment
Important files
app/models.py— database schema.app/scheduling.py— recurrence and assignment generation. Treat changes here as high-risk.app/web.py— HTTP routes and workflows.app/notifications.py— SMTP delivery; should fail soft.app/bootstrap.py— initial admin + three default schedules.app/cli.py— database/demo/reminder commands.docs/DOMAIN.md— expected business behavior.
Working rules for agents
- Read
docs/DOMAIN.mdbefore changing scheduling behavior. - Add/update tests for every scheduling or swap-state change.
- Keep existing generated/published assignments stable unless an admin explicitly edits them.
- Do not add payroll, time clocks, attendance, leave management, or HR modules unless requested.
- Keep phone as data-only until SMS/WhatsApp is explicitly implemented.
- Do not commit
.env, database files, credentials, or real member data. - Do not introduce a frontend package manager unless the UI requirements truly need it.
- Schema changes go through Alembic. Edit
app/models.py, thenmake revision m="short description", review the generated file inmigrations/versions/, and never rely oncreate_all(the app runsalembic upgrade headon start).make checkfails if the migrations and the models drift apart.
Validation before handoff
All checks run inside Docker, never on the host:
make check
That is python -m compileall -q app && pytest -ra in the checks service of docker-compose.dev.yml, which bind-mounts app/, tests/ and pyproject.toml so it always validates the working tree.
make dev (host uvicorn with auto-reload, using ./.venv) exists for interactive debugging. Running pytest, python -m compileall or python -m app.cli directly against the host interpreter is not a valid check.
The dev target of the Dockerfile installs requirements-dev.txt on top of the app image; the checks service supplies throwaway settings (SECRET_KEY, DATABASE_URL, INITIAL_ADMIN_PASSWORD), keeps data/ inside the container, and does not read .env, so it is safe on a fresh clone.
docker-compose.yml is the service stack only. Anything development-related (auto-reload, bind mounts, test runner) belongs in docker-compose.dev.yml.
For route/UI changes also start the app and verify /health, /login, the rota, each schedule's configuration page, and the swap workflow.
Security notes
- Passwords use PBKDF2-SHA256 from Python stdlib.
- State-changing forms use a session CSRF token.
- Admin routes must call
_require_admin. - Treat names, email addresses, phone numbers and schedules as private member data.
- Change the default
SECRET_KEYand initial admin password before deployment.
Branding and UI
- The CRR logo is the user-supplied asset at
app/static/logo-crr.png. Keep its aspect ratio and do not redraw, recolor, crop, or replace it unless explicitly requested. - CRR brand red is
#ED1C24and is exposed as--brandinapp/static/app.css. - For controls with white text, use the darker accessible red
--brand-deep(#C81018), not the exact brand red. The exact brand red is used for borders, focus states, highlights, and other identity accents. - Keep branding usable on mobile and preserve visible keyboard focus states.
- Default application name is
Contínuos CRR; deployments can still override it withAPP_NAME.
Language
- Everything a member reads is European Portuguese: templates, flash messages, e-mail subjects and bodies, the seeded schedule names, and the
lang="pt-PT"document attribute. - Weekday and month names, and every rendered date, come from
app/i18n.py. Never usecalendar.month_name,date.strftime("%a"/"%b"/"%A"/"%B")orcalendar.day_namefor user-facing text; the container locale is not Portuguese. - Stored enum values (
assignment.source,swap.status,notification_log.status/event_type) stay English in the database and are translated for display by the Jinja filters registered inapp/main.py. - Code, comments, tests names and
docs/stay in English; only rendered strings are Portuguese.