# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Specs are the source of truth

**Before implementing anything — new endpoint, table, screen, or convention — read the relevant file in `specs/`.** This is a hard project rule (see `.cursor/rules/specs-as-context.mdc`), not a suggestion. If code and spec disagree, the spec wins unless the user explicitly says otherwise. If a spec marks something as an open decision, don't assume an answer — ask, or flag the pending item.

Exception to "spec wins": `specs/CURRENT_STATE.md` describes what's *actually implemented in code* (a snapshot, not a target) — it exists precisely to track cases where code and other specs disagree (e.g. `AUTHORIZATION_PERMISSIONS.md` documents a 3-role model that was never migrated into code). Treat it as a status report, not a source of truth to implement against.

Index (also in `specs/DEVELOPMENT_GUIDELINES.md`):

| Task | Spec |
|---|---|
| Project/business overview, client scope, what Ciclus is | `PROJECT_OVERVIEW.md` |
| What's actually implemented in code right now (vs. spec'd) | `CURRENT_STATE.md` |
| V1 domain scope / business rules | `SPEC_CICLUS_V1_FUNCIONAL.md` |
| Stack, layers, folder structure | `ARCHITECTURE.md` |
| Tables, naming, migrations | `DATABASE_CONVENTIONS.md` |
| Endpoints, services, API response shape | `BACKEND_PATTERNS.md` |
| Pages, Redux, frontend patterns | `FRONTEND_PATTERNS.md` |
| Login, JWT, password | `AUTHENTICATION.md` |
| Profiles, permissions, menu | `AUTHORIZATION_PERMISSIONS.md` |
| Colors, logo, theme | `BRAND_DESIGN.md` |
| Critical error reporting / SmartOps | `SMARTOPS_INTEGRATION.md` |
| DEV deploy Eveo / SmartOps | `DEPLOY_EVEO.md` |
| Rationale behind architecture decisions (historical, superseded where it conflicts with the specs above) | `historico/ARCHITECTURE_DISCOVERY_REPORT.md`, `historico/SPEC_CICLUS_ARCHITECTURE_DISCOVERY.md` |

Cross-cutting rules that apply everywhere in this repo:
- Backoffice authorization is 3 fixed roles resolved in code (`usuario.usuTipoPerfil`: `smartdata`/`administrador`/`usuario`), not a database-driven permission table — see `AUTHORIZATION_PERMISSIONS.md` (this replaced the earlier `perfil`/`rota`/`perfil_has_rota` model; don't reintroduce a "create new profile" screen without an explicit new product decision).
- No dynamic SQL built by string concatenation — always parameterized ORM queries.
- No hardcoded config (API URLs, third-party keys, credentials) — always environment variables.
- Security never relies on the frontend alone — every sensitive endpoint re-validates the role in the backend (`requer_staff`/`requer_smartdata`) regardless of what the UI hides.
- The backoffice menu (`GET /menu`) is built from a code constant (`app/permissoes.py::MENU_POR_PAPEL`) keyed by role, never queried from a database table.

## Commands

### Backend (FastAPI, in `backend/`)

```bash
# venv already exists at backend/.venv
source backend/.venv/bin/activate
pip install -r backend/requirements.txt

# Run schema migrations, then seed base data (perfis, admin user, menu tree)
# — must run in this order, seed.py assumes the schema already exists
cd backend
alembic upgrade head
python -m app.seed

# Dev server
uvicorn app.main:app --reload --port 8000
```

- New migration: `alembic revision -m "descrição"` (or `--autogenerate`, but review the diff — Alembic is the sole schema authority, `app/seed.py` never calls `Base.metadata.create_all`).
- `python -m app.seed` is idempotent (upsert by name/email) — safe to re-run any time to reorganize existing data (e.g. seed users/clients).
- No test suite exists yet in this repo — don't assume `pytest` config or invent test commands.
- Full local stack (MySQL + backend) via Docker: `docker compose up` (`docker-compose.yml` at repo root). This is for **local development only** — VPS deploy is venv + systemd + Apache via SmartOps (`python_fastapi`), not this compose file (see `ARCHITECTURE.md` / `DEPLOY_EVEO.md`). Container on the VPS is a later evolution.

### Frontend (Vite + React + TS, in `frontend/`)

```bash
cd frontend
npm install
npm run dev       # Vite dev server, port 3000
npm run build      # tsc -b && vite build
npm run preview
```

No test/lint script is currently defined in `package.json`.

### Config

Both apps load from `.env` (copy from `.env.example`); never hardcode URLs/keys/credentials. Frontend only exposes `VITE_`-prefixed vars to the browser and needs a dev-server restart after changes.

## Architecture

### Stack
- **Backend**: Python + FastAPI, SQLAlchemy 2.x (ORM/migrations via Alembic), Pydantic validation, MySQL 8 (PyMySQL driver).
- **Frontend**: React 18 + TypeScript on **Vite** (not CRA — the previous Velzon-template base was abandoned 2026-07-23 for being too heavy/slow: ~1780 npm packages, 922MB `node_modules`). Plain Bootstrap 5 + `reactstrap`, Redux Toolkit only for genuinely global state (today: auth session), Chart.js/`react-chartjs-2` for the sensor dashboard, Bootstrap Icons via CDN `<link>` (no npm icon package). No `formik`/`yup`, no i18next — added only when a concrete screen needs them.
- **DB**: MySQL 8 is the single engine for both registry (cadastro) data and time-series sensor readings. The readings table is partitioned by date (30-day hot window) plus a daily-aggregate table for longer history.
- **Hosting**: own VPS shared with the "German" project and other in-house PHP apps (Apache 2.4). DEV: frontend static under Apache + backend **venv/systemd** (uvicorn), published via **SmartDeploy** (`/var/www/html/smartops`) with deploy type `python_fastapi`. See `specs/DEPLOY_EVEO.md`. Docker on the VPS is deferred.

### Backend layers (`backend/app/`)
Thin layers by real responsibility — don't blur these:
- `routes/` — receives the request, calls a `service`, returns a response `schema`. No business logic here.
- `schemas/` — Pydantic request/response models.
- `services/` — business logic (e.g. "first station link becomes master", alert engine, formula calculations).
- `models/` — SQLAlchemy table mappings only, no business logic.
- `auth/` — user authentication (JWT: login, refresh, `get_current_user` dependency).
- `integrations/` — one module per external service (`smartops.py`, `email.py`, `previsao_tempo.py`), each fails in isolation (try/except at the boundary — an unavailable integration must never take down the main request).
- `lib/` — shared helpers (`filtros.py`, `paginacao.py`).
- There is deliberately **no repository layer** separate from `models/` — SQLAlchemy already is the data mapper.

API responses follow `{ "data": {}, "message": "string", "code": 200 }`; errors use FastAPI's standard `HTTPException`/`detail` shape, documented automatically via OpenAPI.

### Frontend layers (`frontend/src/`)
- `api/client.ts` — single axios instance, `baseURL` from `VITE_API_URL`, request interceptor injects `Authorization: Bearer` from the token in `localStorage`.
- `components/` — layout pieces (`Header`, `Sidebar`, `Footer`, `AuthProtected`, etc.).
- `layouts/` — `MainLayout` (Header+Sidebar+Footer+Outlet), `AuthLayout` (centered card for login).
- `pages/` — top-level pages; a `pages/cadastros/`-style subfolder groups a module's screens.
- `store/` — Redux Toolkit, one slice per genuinely global domain (currently just `authSlice`) — screen-local data (lists, forms) is fetched directly in the page via `useEffect`/`apiClient`, not routed through a slice.
- `styles/custom.scss` — the single theme file (Bootstrap variable overrides + Ciclus brand colors, see `BRAND_DESIGN.md`). Custom CSS classes/tokens use the `sd-`/`--sd-*` prefix (not the `gc-` prefix from the old GC project).

The sidebar fetches `GET /menu`, which returns the route tree already filtered by the logged-in user's role — but the tree itself is a code constant (`app/permissoes.py::MENU_POR_PAPEL`), not a database table. Adding a screen to the menu means editing that constant (+ PR review), not inserting a row via a UI screen — there is no "Rotas" admin screen anymore.

### Two separate authorization models — do not conflate them
1. **Station access, by client** (`usuario.usuCliId` ↔ `estacao.estCliId`): a user sees all stations owned by their client, same access level for all of them. No per-station master/viewer (deferred). Administrador/Smartdata bypass and see everything.
2. **Backoffice roles** (`usuario.usuTipoPerfil`: `smartdata` / `administrador` / `usuario`, fixed set): global per-user, controls what appears in the Cadastros/Sistema module menu/routes. `usuario.usuAdministrador` is a derived boolean bypass flag (`True` iff role is `smartdata`) — never a hardcoded list of admin IDs. See `AUTHORIZATION_PERMISSIONS.md` for the full capability matrix and `app/auth/dependencies.py::requer_staff`/`requer_smartdata`.

Security flow (non-negotiable): **Code (fixed role) → backend authorizes (`Depends`) → API responds → frontend uses the role only for UI.** `AuthProtected` on the frontend is client-side UX only (hide menu, redirect) — it is never the source of truth; every sensitive endpoint re-checks the role in the backend via `Depends`.

### Database conventions (see `DATABASE_CONVENTIONS.md` for full detail)
- Tables `snake_case`; columns `camelCase` with a **3-4 letter table prefix** (`estacao` → `estId`, `estNome`; `usuario` → `usuId`, `usuNome`) — deliberately preserved from the "German" sister project's convention, not a legacy accident. Check the prefix list in `DATABASE_CONVENTIONS.md` before inventing a new one — prefixes must be unique across tables.
- PK: `<prefixo>Id`, surrogate auto-increment, never composite — except `leitura`/`leitura_medida`, where the composite PK is required by MySQL to partition on a non-PK column.
- `BOOLEAN` for flags (never `ENUM('S','N')` or `TINYINT(1)` explicitly). Soft delete via `<prefixo>Ativo`, not row deletion.
- Dates are `DATETIME`, never `TIMESTAMP` (MySQL's `TIMESTAMP` silently converts by session timezone — write UTC from the app).
- `utf8mb4` + `InnoDB` everywhere. Migrations only via Alembic — never hand-run SQL against production schema.
- Time-series readings are split across `leitura` (raw JSON payload, idempotency), `leitura_medida` (`lem`, one numeric row per parameter, partitioned by `TO_DAYS(lemTimestamp)`, the base for metrics/alerts), `arquivo_importado` (`ari`, one row per uploaded `.txt`, cold storage under `DADOS_APP/estacao/{id}/imports/`), and `leitura_agregado_diario` (`lad`, daily min/avg/max for long-range history). Partitions older than 30 days on `leitura_medida` are dropped; aggregates and the on-disk `.txt` files are kept.

### What was deliberately *not* carried over from "German" (the sister PHP project)
- Dynamic filter building via string concatenation (`Models::criarFiltro`) — proven SQL-injection risk; use typed ORM filters.
- Ad-hoc per-controller manual validation — use Pydantic.
- Logging every SQL query to a separate table — use structured application-level event logging instead.
- `ENUM('S','N')` flags and `utf8mb3` — see DB conventions above.
- The Velzon React template as a frontend base (see Stack section).
