- Fix pipeline granularity: add market_year to cleaned/serving SQL models - Add DuckDB data access layer with async query functions (analytics.py) - Build Chart.js dashboard: supply/demand, STU ratio, top producers, YoY table - Add country comparison page with multi-select picker - Replace items CRUD with read-only commodity API (list, metrics, countries, CSV) - Configure BeanFlows plan tiers (Free/Starter/Pro) with feature gating - Rewrite public pages for coffee market intelligence positioning - Remove boilerplate items schema, update health check for DuckDB - Add test suite: 139 tests passing (dashboard, API, billing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What This Is
A Copier template for generating SaaS applications. The template lives in {{project_slug}}/ with Jinja2-templated files (.jinja extension). Files without .jinja are copied as-is. The copier.yml defines template variables: project_slug, project_name, description, author_name, author_email, base_url, payment_provider (stripe/paddle/lemonsqueezy).
This is NOT a runnable application itself — it generates one via copier copy.
Generated Project Commands
After generation, the project uses uv as package manager:
uv sync # Install dependencies
uv run python -m <slug>.migrations.migrate # Initialize/migrate DB
uv run python -m <slug>.app # Run dev server (port 5000)
uv run python -m <slug>.worker # Run background worker
uv run python -m <slug>.worker scheduler # Run periodic scheduler
uv run pytest # Run tests
uv run ruff check . # Lint
docker compose up -d # Production deploy
Stack
- Quart (async Flask) with Jinja2 templates and Pico CSS (no build step)
- SQLite with WAL mode + aiosqlite (no ORM — plain SQL everywhere)
- Stripe, Paddle, or LemonSqueezy for billing (chosen via
payment_providertemplate variable) - Resend for transactional email
- Litestream for SQLite replication/backups
- Docker + Caddy for deployment
- Hypercorn as ASGI server in production
Architecture
Domain-based flat structure
Each domain is a directory with routes.py + templates/. Routes, SQL queries, and decorators all live together in routes.py — no separate models/services/repositories layers.
src/<slug>/
app.py → Application factory, blueprint registration, middleware
core.py → Config class, DB helpers (fetch_one/fetch_all/execute), email, CSRF, rate limiting
worker.py → SQLite-based background task queue (no Redis)
auth/ → Magic link auth, login_required/subscription_required decorators
billing/ → Checkout/webhooks/portal (Stripe, Paddle, or LemonSqueezy), plan feature/limit checks
dashboard/ → User settings, API key management
public/ → Marketing pages (landing, terms, privacy)
api/ → REST API with Bearer token auth (api_key_required decorator)
admin/ → Password-protected admin panel (ADMIN_PASSWORD env var)
migrations/ → schema.sql + migrate.py (runs full schema idempotently with CREATE IF NOT EXISTS)
Key patterns
- Database access: Use
fetch_one(),fetch_all(),execute()fromcore.py. No ORM. Queries live directly in route files next to the routes that use them. - Auth decorators:
@login_requiredfor user pages,@subscription_required(plans=["pro"])for plan-gated features,@api_key_required(scopes=["read"])for API endpoints. - CSRF:
@csrf_protectdecorator on POST routes. Templates include<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">. - Background tasks:
from ..worker import enqueuethenawait enqueue("task_name", {"key": "value"}). Register handlers with@task("task_name")decorator inworker.py. - Blueprints: Each domain registers as a Quart Blueprint with its own
template_folder. Templates in domain dirs override shared ones. - Soft deletes:
deleted_atcolumn pattern. Usesoft_delete(),restore(),hard_delete()fromcore.py. - Dates: All stored as ISO 8601 TEXT in SQLite, using
datetime.utcnow().isoformat(). - Rate limiting: SQLite-based, no Redis.
@rate_limit()decorator orcheck_rate_limit()function. - Migrations: Single
schema.sqlfile with allCREATE TABLE IF NOT EXISTS. Run viapython -m <slug>.migrations.migrate.
Conditional template blocks
billing/routes.py.jinja conditionally generates the full billing implementation based on the payment_provider variable (stripe/paddle/lemonsqueezy). The .env.example.jinja, core.py.jinja, and schema.sql.jinja similarly adapt per provider.
Design Philosophy
Data-oriented, minimal abstraction. Plain SQL over ORM. Write code first, extract patterns only when repeated 3+ times. SQLite as the default database. Server-rendered HTML with Pico CSS.
Ruff Config
Line length 100, target Python 3.11+, rules: E, F, I, UP (ignoring E501).