docs: update CLAUDE.md with conditional versioning, measurement workflow, and JS components
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,7 +81,7 @@ Il client Flask è un frontend server-side che comunica col backend via REST API
|
|||||||
- **main.py**: entry point, lifespan async (`@asynccontextmanager`), registra middleware e 10 router. Health: `GET /api/health`
|
- **main.py**: entry point, lifespan async (`@asynccontextmanager`), registra middleware e 10 router. Health: `GET /api/health`
|
||||||
- **config.py**: `Settings` (pydantic_settings.BaseSettings), legge da `../.env`. Rate limits: login 5/min, general 100/min
|
- **config.py**: `Settings` (pydantic_settings.BaseSettings), legge da `../.env`. Rate limits: login 5/min, general 100/min
|
||||||
- **database.py**: SQLAlchemy 2.0 async engine con `AsyncSession`, pool 10+20 overflow, `pool_recycle=3600`, `expire_on_commit=False`
|
- **database.py**: SQLAlchemy 2.0 async engine con `AsyncSession`, pool 10+20 overflow, `pool_recycle=3600`, `expire_on_commit=False`
|
||||||
- **routers/**: auth, users, recipes, tasks, measurements, files, settings, statistics, reports, setup
|
- **routers/**: auth, users, recipes, tasks, measurements, files, settings, statistics, reports, setup. Il task router (`tasks.py`) usa URL pattern misti: `/api/recipes/{id}/tasks` (list/create), `/api/tasks/{id}` (get/update/delete), `/api/tasks/reorder`, `/api/tasks/{id}/subtasks`, `/api/subtasks/{id}`
|
||||||
- **services/**: recipe_service (versioning copy-on-write), measurement_service (calcolo pass/fail), spc_service (Cp/Cpk/control chart, puro stdlib senza numpy), report_service (WeasyPrint PDF + Kaleido SVG), auth_service (bcrypt + API key)
|
- **services/**: recipe_service (versioning copy-on-write), measurement_service (calcolo pass/fail), spc_service (Cp/Cpk/control chart, puro stdlib senza numpy), report_service (WeasyPrint PDF + Kaleido SVG), auth_service (bcrypt + API key)
|
||||||
- **middleware/**: api_key.py (auth dependency), rate_limit.py (sliding window 60s per-IP, in-memory dicts), security_headers.py (CSP con `unsafe-eval` per Plotly.js, HSTS solo con SSL), logging.py (audit trail async su DB, esclude /api/health, /docs, /openapi.json)
|
- **middleware/**: api_key.py (auth dependency), rate_limit.py (sliding window 60s per-IP, in-memory dicts), security_headers.py (CSP con `unsafe-eval` per Plotly.js, HSTS solo con SSL), logging.py (audit trail async su DB, esclude /api/health, /docs, /openapi.json)
|
||||||
- **models/**: User, Recipe, RecipeVersion, RecipeTask, RecipeSubtask, Measurement, AccessLog, SystemSetting, RecipeVersionAudit
|
- **models/**: User, Recipe, RecipeVersion, RecipeTask, RecipeSubtask, Measurement, AccessLog, SystemSetting, RecipeVersionAudit
|
||||||
@@ -94,7 +94,7 @@ Il client Flask è un frontend server-side che comunica col backend via REST API
|
|||||||
- **blueprints/**: auth (login/logout/session), maker (editor ricette con Fabric.js), measure (esecuzione misurazioni), statistics (dashboard SPC con Plotly.js)
|
- **blueprints/**: auth (login/logout/session), maker (editor ricette con Fabric.js), measure (esecuzione misurazioni), statistics (dashboard SPC con Plotly.js)
|
||||||
- **services/api_client.py**: singleton `APIClient` — wrapper HTTP (get/post/put/delete) con gestione errori normalizzata, timeout 30s, header X-API-Key da session
|
- **services/api_client.py**: singleton `APIClient` — wrapper HTTP (get/post/put/delete) con gestione errori normalizzata, timeout 30s, header X-API-Key da session
|
||||||
- **templates/**: Jinja2 con `{{ _('string') }}` per i18n. Context processor inietta `current_user`, `current_theme`, `current_language`, `company_logo` in tutti i template
|
- **templates/**: Jinja2 con `{{ _('string') }}` per i18n. Context processor inietta `current_user`, `current_theme`, `current_language`, `company_logo` in tutti i template
|
||||||
- **static/js/**: Alpine.js 3.x (CDN), Fabric.js 5.3.1 (CDN), locales JSON (it.json, en.json), alpine-init.js (theme store)
|
- **static/js/**: Alpine.js 3.x (CDN), Fabric.js 5.3.1 (CDN), locales JSON (it.json, en.json), alpine-init.js (theme store). Componenti specializzati: `numpad.js` (input numerico touch con rilevamento burst USB HID), `caliper.js` (monitor calibro USB passivo), `barcode.js` (scanner QR/barcode con camera via html5-qrcode), `csv-export.js` (export CSV con locale italiano: `;` separatore, `,` decimale), `spc-charts.js` (grafici SPC con Plotly.js), `annotation-editor.js` / `annotation-viewer.js` (editor/viewer annotazioni Fabric.js)
|
||||||
- **translations/**: Flask-Babel, cataloghi .po/.mo per IT/EN. Locale selector: `session["language"]` → Accept-Language → `"it"`
|
- **translations/**: Flask-Babel, cataloghi .po/.mo per IT/EN. Locale selector: `session["language"]` → Accept-Language → `"it"`
|
||||||
- **config.py**: `PERMANENT_SESSION_LIFETIME=28800` (8h), cookie secure in produzione, `BABEL_DEFAULT_TIMEZONE="Europe/Rome"`
|
- **config.py**: `PERMANENT_SESSION_LIFETIME=28800` (8h), cookie secure in produzione, `BABEL_DEFAULT_TIMEZONE="Europe/Rome"`
|
||||||
|
|
||||||
@@ -142,8 +142,14 @@ Storage: `RecipeTask.annotations_json` (colonna JSON), `RecipeTask.file_path`, `
|
|||||||
|
|
||||||
## Pattern Critici
|
## Pattern Critici
|
||||||
|
|
||||||
### Recipe Versioning (Copy-on-Write)
|
### Recipe Versioning (Conditional Copy-on-Write)
|
||||||
Le ricette usano un versioning immutabile: ogni modifica crea una nuova `RecipeVersion` con deep-copy di tasks e subtasks. Le misurazioni restano sempre legate alla versione originale. La versione corrente ha `is_current=True`, le precedenti `False`. Audit trail in `recipe_version_audit` (CREATE, UPDATE, RETIRE). Logica in `server/services/recipe_service.py`.
|
Le ricette usano un versioning condizionale. L'endpoint `PUT /api/recipes/{id}` decide la strategia:
|
||||||
|
- **Se la versione corrente ha measurements** → copy-on-write: crea nuova `RecipeVersion` con deep-copy di tasks e subtasks. Le misurazioni restano legate alla versione originale.
|
||||||
|
- **Se la versione corrente NON ha measurements** → update in-place via `update_current_version()` (nessuna nuova versione creata).
|
||||||
|
|
||||||
|
La stessa logica si applica nel task router: aggiungere un task a una ricetta con measurements crea una nuova versione.
|
||||||
|
|
||||||
|
La versione corrente ha `is_current=True`, le precedenti `False`. Audit trail in `recipe_version_audit` (CREATE, UPDATE, RETIRE). Logica in `server/services/recipe_service.py`.
|
||||||
|
|
||||||
### Calcolo Pass/Fail
|
### Calcolo Pass/Fail
|
||||||
Ogni subtask ha 4 limiti di tolleranza: UTL (upper tolerance), UWL (upper warning), LWL (lower warning), LTL (lower tolerance) più un valore nominale. Il calcolo in `server/services/measurement_service.py`:
|
Ogni subtask ha 4 limiti di tolleranza: UTL (upper tolerance), UWL (upper warning), LWL (lower warning), LTL (lower tolerance) più un valore nominale. Il calcolo in `server/services/measurement_service.py`:
|
||||||
@@ -164,6 +170,15 @@ Calcoli in `server/services/spc_service.py` usando solo `math` e `statistics` st
|
|||||||
### Ruoli Utente
|
### Ruoli Utente
|
||||||
Combinabili (JSON array): **Maker** (crea ricette), **MeasurementTec** (esegue misurazioni), **Metrologist** (dashboard SPC). Flag `is_admin` separato.
|
Combinabili (JSON array): **Maker** (crea ricette), **MeasurementTec** (esegue misurazioni), **Metrologist** (dashboard SPC). Flag `is_admin` separato.
|
||||||
|
|
||||||
|
### Measurement Workflow (Client)
|
||||||
|
Flusso completo per l'operatore MeasurementTec:
|
||||||
|
1. **select_recipe** (`/select`) — scelta ricetta con ricerca testo e barcode scanner. Supporta auto-fill da query params (`?recipe=&lot=&serial=`)
|
||||||
|
2. **task_list** (`/tasks/<recipe_id>`) — lista task della versione corrente. Lotto e seriale persistono in Flask session
|
||||||
|
3. **task_execute** (`/execute/<task_id>`) — esecuzione misurazioni con numpad touch, input USB calibro (burst detection), annotation viewer. Auto-advance al task successivo. Salvataggio AJAX via `POST /save-measurement` (proxy Flask → FastAPI)
|
||||||
|
4. **task_complete** (`/complete/<recipe_id>`) — riepilogo con tutte le misurazioni, deviazioni calcolate client-side se mancanti
|
||||||
|
|
||||||
|
Il blueprint measure include un **file proxy** (`/measure/api/files/<path>`) perché il browser non può inviare l'header `X-API-Key` direttamente al server FastAPI.
|
||||||
|
|
||||||
### File Storage
|
### File Storage
|
||||||
Upload in `uploads/{recipe_id}/{version_id}/`. Tipi ammessi: JPEG, PNG, GIF, WebP, PDF. Limite 50MB. Thumbnail auto-generate per immagini (Pillow). Sanitizzazione filename.
|
Upload in `uploads/{recipe_id}/{version_id}/`. Tipi ammessi: JPEG, PNG, GIF, WebP, PDF. Limite 50MB. Thumbnail auto-generate per immagini (Pillow). Sanitizzazione filename.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user