Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 563b7789f4 | |||
| e4b29c0b2d |
+2
-6
@@ -28,14 +28,10 @@ htmlcov/
|
|||||||
# Node
|
# Node
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# Local-only notes (Markdown docs are still copied if a Dockerfile asks)
|
# Local-only notes
|
||||||
PIANO_IMPLEMENTAZIONE.md
|
docs/
|
||||||
*.docx
|
*.docx
|
||||||
|
|
||||||
# Legacy Dockerfiles preserved in tree but not needed in builds
|
|
||||||
src/backend/Dockerfile.legacy
|
|
||||||
src/frontend/flask_app/Dockerfile.legacy
|
|
||||||
|
|
||||||
# Uploads are a runtime volume, never baked in.
|
# Uploads are a runtime volume, never baked in.
|
||||||
uploads/
|
uploads/
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# TieMeasureFlow by Tielogic
|
# TieMeasureFlow by Tielogic
|
||||||
|
|
||||||
Sistema di gestione task per misurazioni con calibro manuale. Soluzione tablet-first, multi-ruolo, con statistiche SPC (Statistical Process Control) integrate.
|
Sistema di gestione task per misurazioni con calibro manuale. Soluzione tablet-first, multi-ruolo, con statistiche SPC (Statistical Process Control) integrate, identità per-stazione e rate limiting per-tablet.
|
||||||
|
|
||||||
|
> **Versione corrente:** V2.0.0 (in sviluppo) — branch default `V2.0.0`.
|
||||||
|
> Per stato dettagliato e prossimi passi vedi [`docs/architecture/STATO_PROGETTO.md`](docs/architecture/STATO_PROGETTO.md) e [`docs/architecture/ROADMAP.md`](docs/architecture/ROADMAP.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,8 +17,10 @@ Caratteristiche principali:
|
|||||||
- Calcolo pass/fail/warning su quattro limiti di tolleranza (UTL, UWL, LWL, LTL)
|
- Calcolo pass/fail/warning su quattro limiti di tolleranza (UTL, UWL, LWL, LTL)
|
||||||
- SPC: Cp, Cpk, Pp, Ppk, control chart UCL/LCL, istogramma con curva normale — puro stdlib (no numpy)
|
- SPC: Cp, Cpk, Pp, Ppk, control chart UCL/LCL, istogramma con curva normale — puro stdlib (no numpy)
|
||||||
- Annotazioni grafiche su disegni tecnici (Fabric.js) con viewer sincronizzato all'esecuzione
|
- Annotazioni grafiche su disegni tecnici (Fabric.js) con viewer sincronizzato all'esecuzione
|
||||||
|
- Identità per-stazione (`STATION_CODE`): ogni tablet vede solo le ricette assegnate alla propria stazione, gestione assegnazioni via GUI admin
|
||||||
- Interfaccia completamente localizzata IT/EN, dark mode, ottimizzata per tablet touch
|
- Interfaccia completamente localizzata IT/EN, dark mode, ottimizzata per tablet touch
|
||||||
- Autenticazione API Key, rate limiting sliding window, audit log persistente
|
- Autenticazione API Key, rate limiting sliding window per-IP reale (X-Forwarded-For-aware), audit log persistente
|
||||||
|
- Capacità testata per ~20 tablet contemporanei (gunicorn 5 workers × 4 thread + uvicorn 4 workers async)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -26,25 +31,25 @@ Browser/Tablet
|
|||||||
|
|
|
|
||||||
Reverse Proxy (Nginx — sviluppo | Traefik+SSL — produzione)
|
Reverse Proxy (Nginx — sviluppo | Traefik+SSL — produzione)
|
||||||
|
|
|
|
||||||
Flask Client :5000 (rendering server-side, Jinja2 + Alpine.js)
|
Flask Frontend :5000 (rendering server-side, Jinja2 + Alpine.js)
|
||||||
| X-API-Key
|
| X-API-Key + X-Forwarded-For
|
||||||
FastAPI Server :8000 (API REST asincrona)
|
FastAPI Backend :8000 (API REST asincrona)
|
||||||
|
|
|
|
||||||
MySQL 8.0
|
MySQL 8.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Il client Flask non espone mai le credenziali al browser: ogni chiamata al backend avviene server-side con l'header `X-API-Key` estratto dalla sessione Flask.
|
Il frontend Flask non espone mai le credenziali al browser: ogni chiamata al backend avviene server-side con l'header `X-API-Key` estratto dalla sessione Flask. L'IP reale del tablet è propagato in `X-Forwarded-For` per il rate limiter.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Stack Tecnologico
|
## Stack Tecnologico
|
||||||
|
|
||||||
### Backend (server/)
|
### Backend (`src/backend/`)
|
||||||
|
|
||||||
| Componente | Versione | Ruolo |
|
| Componente | Versione | Ruolo |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| FastAPI | ultima stabile | Framework API REST asincrono |
|
| FastAPI | ultima stabile | Framework API REST asincrono |
|
||||||
| SQLAlchemy 2.0 | async | ORM con pool connessioni |
|
| SQLAlchemy 2.0 | async | ORM con pool connessioni 10+20 |
|
||||||
| asyncmy | ultima stabile | Driver MySQL asincrono |
|
| asyncmy | ultima stabile | Driver MySQL asincrono |
|
||||||
| MySQL | 8.0 | Database relazionale |
|
| MySQL | 8.0 | Database relazionale |
|
||||||
| Alembic | ultima stabile | Migrazioni schema |
|
| Alembic | ultima stabile | Migrazioni schema |
|
||||||
@@ -54,35 +59,47 @@ Il client Flask non espone mai le credenziali al browser: ogni chiamata al backe
|
|||||||
| bcrypt | ultima stabile | Hashing password |
|
| bcrypt | ultima stabile | Hashing password |
|
||||||
| Pillow | ultima stabile | Thumbnail automatici upload |
|
| Pillow | ultima stabile | Thumbnail automatici upload |
|
||||||
|
|
||||||
### Frontend (client/)
|
### Frontend (`src/frontend/flask_app/`)
|
||||||
|
|
||||||
| Componente | Versione | Ruolo |
|
| Componente | Versione | Ruolo |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Flask | 3.x | Framework web server-side |
|
| Flask | 3.x | Framework web server-side |
|
||||||
|
| gunicorn | 21+ | WSGI server (5 workers × 4 thread gthread) |
|
||||||
| Jinja2 | incluso in Flask | Template engine |
|
| Jinja2 | incluso in Flask | Template engine |
|
||||||
| Alpine.js | 3.x (CDN) | Reattivita leggera lato client |
|
| Alpine.js | 3.x (CDN) | Reattività leggera lato client |
|
||||||
| TailwindCSS | 3.x | CSS utility-first |
|
| TailwindCSS | 3.x | CSS utility-first |
|
||||||
| Plotly.js | CDN | Grafici SPC interattivi |
|
| Plotly.js | CDN | Grafici SPC interattivi |
|
||||||
| Fabric.js | 5.3.1 (CDN) | Editor annotazioni disegni tecnici |
|
| Fabric.js | 5.3.1 (CDN) | Editor annotazioni disegni tecnici |
|
||||||
|
| html5-qrcode | CDN | Scanner barcode/QR camera |
|
||||||
| Flask-Babel | ultima stabile | i18n IT/EN |
|
| Flask-Babel | ultima stabile | i18n IT/EN |
|
||||||
|
|
||||||
|
### Tooling
|
||||||
|
|
||||||
|
| Componente | Ruolo |
|
||||||
|
|---|---|
|
||||||
|
| **uv** | Package manager Python (no `requirements.txt`) |
|
||||||
|
| `pyproject.toml` | Dipendenze monorepo con extra `server`/`client`/`dev` |
|
||||||
|
| `uv.lock` | Lockfile per build riproducibili |
|
||||||
|
| `.python-version` | Pin Python 3.11 |
|
||||||
|
| pytest + pytest-asyncio + httpx + aiosqlite | Test stack |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Start con Docker
|
## Quick Start con Docker
|
||||||
|
|
||||||
Docker Compose è il metodo raccomandato. Gestisce database, migrazioni e configurazione Nginx in un solo comando.
|
Docker Compose è il metodo raccomandato. Gestisce database, migrazioni, build delle immagini con `uv` e configurazione Nginx in un solo comando.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Clona il repository
|
# 1. Clona il repository
|
||||||
git clone <repository-url>
|
git clone ssh://git@git.tielogic.xyz:222/Adriano/TieMeasureFlow.git
|
||||||
cd TieMeasureFlow
|
cd TieMeasureFlow
|
||||||
|
|
||||||
# 2. Configura le variabili d'ambiente
|
# 2. Configura le variabili d'ambiente
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Modifica .env: credenziali DB, chiavi segrete, SETUP_PASSWORD
|
# Modifica .env: credenziali DB, chiavi segrete, SETUP_PASSWORD, STATION_CODE per ogni tablet
|
||||||
|
|
||||||
# 3. Avvia i servizi (ambiente di sviluppo)
|
# 3. Avvia i servizi (ambiente di sviluppo)
|
||||||
docker compose -f docker-compose.dev.yml up -d
|
docker compose -f docker-compose.dev.yml up -d --build
|
||||||
|
|
||||||
# 4. Verifica lo stato dei container
|
# 4. Verifica lo stato dei container
|
||||||
docker compose -f docker-compose.dev.yml ps
|
docker compose -f docker-compose.dev.yml ps
|
||||||
@@ -90,6 +107,7 @@ docker compose -f docker-compose.dev.yml ps
|
|||||||
# 5. Setup iniziale (solo al primo avvio)
|
# 5. Setup iniziale (solo al primo avvio)
|
||||||
# Apri http://localhost/api/setup nel browser
|
# Apri http://localhost/api/setup nel browser
|
||||||
# Usa SETUP_PASSWORD configurata in .env
|
# Usa SETUP_PASSWORD configurata in .env
|
||||||
|
# Lo script seed crea anche la stazione ST-DEFAULT con tutte le ricette assegnate
|
||||||
```
|
```
|
||||||
|
|
||||||
L'applicazione sarà disponibile su:
|
L'applicazione sarà disponibile su:
|
||||||
@@ -97,8 +115,9 @@ L'applicazione sarà disponibile su:
|
|||||||
- Frontend: http://localhost
|
- Frontend: http://localhost
|
||||||
- API: http://localhost/api
|
- API: http://localhost/api
|
||||||
- Pagina setup: http://localhost/api/setup
|
- Pagina setup: http://localhost/api/setup
|
||||||
|
- Admin stazioni: http://localhost/admin/stations (solo `is_admin`)
|
||||||
|
|
||||||
Per il deployment in produzione (Traefik + SSL) consulta [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md).
|
Per il deployment in produzione (Traefik + SSL) consulta [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -108,12 +127,14 @@ Dopo il primo avvio, la pagina `/api/setup` (protetta da `SETUP_PASSWORD`) perme
|
|||||||
|
|
||||||
- **Initialize Database** — crea tutte le tabelle
|
- **Initialize Database** — crea tutte le tabelle
|
||||||
- **Create Admin User** — crea l'utente amministratore con credenziali da `.env`
|
- **Create Admin User** — crea l'utente amministratore con credenziali da `.env`
|
||||||
- **Seed Demo Data** — carica ricette, misurazioni e utenti di esempio
|
- **Seed Demo Data** — carica ricette, misurazioni e utenti di esempio + crea stazione `ST-DEFAULT` con tutte le ricette assegnate
|
||||||
- **Reset Database** — elimina e ricrea tutte le tabelle (attenzione: cancella tutti i dati)
|
- **Reset Database** — elimina e ricrea tutte le tabelle (attenzione: cancella tutti i dati)
|
||||||
- **Gestione utenti** — crea, modifica, attiva/disattiva account dalla stessa pagina
|
- **Gestione utenti** — crea, modifica, attiva/disattiva account dalla stessa pagina
|
||||||
|
|
||||||
Se `SETUP_PASSWORD` è vuota o assente nel `.env`, l'endpoint è disabilitato.
|
Se `SETUP_PASSWORD` è vuota o assente nel `.env`, l'endpoint è disabilitato.
|
||||||
|
|
||||||
|
Per gestire stazioni e assegnazioni ricette dopo il setup: `/admin/stations` (richiede login admin).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Setup Manuale (Senza Docker)
|
## Setup Manuale (Senza Docker)
|
||||||
@@ -121,8 +142,9 @@ Se `SETUP_PASSWORD` è vuota o assente nel `.env`, l'endpoint è disabilitato.
|
|||||||
### Requisiti
|
### Requisiti
|
||||||
|
|
||||||
- Python 3.11 o superiore
|
- Python 3.11 o superiore
|
||||||
- Node.js 18 o superiore
|
- Node.js 18 o superiore (per TailwindCSS)
|
||||||
- MySQL 8.0
|
- MySQL 8.0
|
||||||
|
- [uv](https://docs.astral.sh/uv/) installato
|
||||||
|
|
||||||
### 1. Database MySQL
|
### 1. Database MySQL
|
||||||
|
|
||||||
@@ -139,35 +161,37 @@ SQL
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
# Imposta DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, SERVER_SECRET_KEY, CLIENT_SECRET_KEY, SETUP_PASSWORD
|
# Imposta DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, SERVER_SECRET_KEY,
|
||||||
|
# CLIENT_SECRET_KEY, SETUP_PASSWORD, STATION_CODE
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Server FastAPI
|
### 3. Installa dipendenze (uv)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd server
|
uv sync --extra server --extra client --extra dev
|
||||||
python -m venv venv
|
|
||||||
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
||||||
pip install -r requirements.txt
|
|
||||||
alembic -c migrations/alembic.ini upgrade head
|
|
||||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Client Flask
|
### 4. Backend FastAPI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd client
|
uv run alembic -c src/backend/migrations/alembic.ini upgrade head
|
||||||
python -m venv venv
|
uv run uvicorn src.backend.main:app --reload --host 0.0.0.0 --port 8000
|
||||||
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
||||||
pip install -r requirements.txt
|
|
||||||
pybabel compile -d translations
|
|
||||||
flask run --host 0.0.0.0 --port 5000
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. TailwindCSS (watch per sviluppo)
|
### 5. Frontend Flask
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd client
|
# Compila i cataloghi i18n una volta
|
||||||
|
cd src/frontend/flask_app && uv run --project ../../.. pybabel compile -d translations && cd -
|
||||||
|
|
||||||
|
# Avvia (development)
|
||||||
|
cd src/frontend/flask_app && uv run --project ../../.. flask run --host 0.0.0.0 --port 5000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. TailwindCSS (watch per sviluppo)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/frontend/flask_app
|
||||||
npx tailwindcss -i static/css/input.css -o static/css/tailwind.css --watch
|
npx tailwindcss -i static/css/input.css -o static/css/tailwind.css --watch
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -185,9 +209,9 @@ I ruoli sono combinabili (array JSON per utente). Il flag `is_admin` è separato
|
|||||||
| Ruolo | Descrizione |
|
| Ruolo | Descrizione |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Maker** | Crea e gestisce ricette di misurazione: caricamento disegni (PDF/immagini), annotazioni Fabric.js, definizione task/subtask, configurazione tolleranze, versioning copy-on-write |
|
| **Maker** | Crea e gestisce ricette di misurazione: caricamento disegni (PDF/immagini), annotazioni Fabric.js, definizione task/subtask, configurazione tolleranze, versioning copy-on-write |
|
||||||
| **MeasurementTec** | Esegue misurazioni: scansione barcode per selezione ricetta, interfaccia task-driven, input da calibro USB HID o numpad touch, validazione real-time pass/warning/fail |
|
| **MeasurementTec** | Esegue misurazioni: scansione barcode per selezione ricetta, interfaccia task-driven, input da calibro USB HID o numpad touch, validazione real-time pass/warning/fail. Vede solo le ricette assegnate alla propria stazione (`STATION_CODE`) |
|
||||||
| **Metrologist** | Analisi qualità: dashboard SPC (X-bar, R, Cp, Cpk, Pp, Ppk), filtri multi-dimensionali, export report PDF, analisi capability e control chart |
|
| **Metrologist** | Analisi qualità: dashboard SPC (X-bar, R, Cp, Cpk, Pp, Ppk), filtri multi-dimensionali, export report PDF, analisi capability e control chart |
|
||||||
| **Admin** (flag) | Gestione sistema: CRUD utenti, cambio password, attivazione/disattivazione account |
|
| **Admin** (flag) | Gestione sistema: CRUD utenti, cambio password, attivazione/disattivazione account, **CRUD stazioni e assegnazioni ricette** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -195,39 +219,53 @@ I ruoli sono combinabili (array JSON per utente). Il flag `is_admin` è separato
|
|||||||
|
|
||||||
```
|
```
|
||||||
TieMeasureFlow/
|
TieMeasureFlow/
|
||||||
├── server/ # FastAPI Backend
|
├── pyproject.toml # Dipendenze monorepo (uv)
|
||||||
│ ├── main.py # Entry point, lifespan, middleware, 10 router
|
├── uv.lock # Lockfile riproducibile
|
||||||
│ ├── config.py # Settings (pydantic_settings.BaseSettings)
|
├── .python-version # 3.11
|
||||||
│ ├── database.py # SQLAlchemy 2.0 async engine
|
├── Dockerfile # Backend (uv + uvicorn)
|
||||||
│ ├── models/ # ORM: User, Recipe, RecipeVersion, RecipeTask,
|
├── Dockerfile.frontend # Frontend (uv + gunicorn + Tailwind + Babel)
|
||||||
│ │ # RecipeSubtask, Measurement, AccessLog,
|
├── docker-compose.dev.yml # Sviluppo (Nginx, porta 80)
|
||||||
│ │ # SystemSetting, RecipeVersionAudit
|
├── docker-compose.yml # Produzione (Traefik, SSL)
|
||||||
│ ├── schemas/ # Pydantic v2 per validazione I/O API
|
├── nginx/ # Config Nginx (dev)
|
||||||
│ ├── routers/ # auth, users, recipes, tasks, measurements,
|
├── uploads/ # Volume Docker file caricati
|
||||||
│ │ # files, settings, statistics, reports, setup
|
├── docs/ # Documentazione (vedi indice docs/README.md)
|
||||||
│ ├── services/ # recipe_service, measurement_service,
|
└── src/
|
||||||
│ │ # spc_service, report_service, auth_service
|
├── backend/ # FastAPI Backend
|
||||||
│ ├── middleware/ # api_key, rate_limit, security_headers, logging
|
│ ├── main.py # Entry point, lifespan, middleware, 11 router
|
||||||
│ ├── migrations/ # Alembic (alembic.ini + env.py)
|
│ ├── config.py # Settings (pydantic_settings.BaseSettings)
|
||||||
│ └── templates/ # Template HTML pagina setup
|
│ ├── database.py # SQLAlchemy 2.0 async engine
|
||||||
├── client/ # Flask Frontend
|
│ ├── api/
|
||||||
│ ├── app.py # Factory pattern, CSRF, Babel
|
│ │ ├── routers/ # auth, users, recipes, tasks, measurements,
|
||||||
│ ├── blueprints/ # auth, maker, measure, statistics, admin
|
│ │ │ # files, settings, statistics, reports,
|
||||||
│ ├── services/ # APIClient singleton (proxy verso FastAPI)
|
│ │ │ # setup, stations
|
||||||
│ ├── templates/ # Jinja2 + Alpine.js
|
│ │ └── middleware/ # api_key, rate_limit, security_headers, logging
|
||||||
│ ├── static/
|
│ ├── models/
|
||||||
│ │ ├── css/ # TailwindCSS compilato
|
│ │ ├── orm/ # SQLAlchemy: User, Recipe, RecipeVersion,
|
||||||
│ │ └── js/ # numpad, caliper, barcode, csv-export,
|
│ │ │ # RecipeTask, RecipeSubtask, Measurement,
|
||||||
│ │ # spc-charts, annotation-editor/viewer
|
│ │ │ # AccessLog, SystemSetting,
|
||||||
│ └── translations/ # Flask-Babel .po/.mo IT/EN
|
│ │ │ # RecipeVersionAudit, Station,
|
||||||
├── nginx/ # Configurazione Nginx (dev)
|
│ │ │ # StationRecipeAssignment
|
||||||
├── docs/ # Documentazione tecnica
|
│ │ └── api/ # Pydantic v2 schemas request/response
|
||||||
│ ├── API.md # Riferimento API REST
|
│ ├── services/ # recipe_service, measurement_service,
|
||||||
│ ├── DEPLOYMENT.md # Guida deployment VPS (Traefik, SSL, DNS)
|
│ │ # spc_service, report_service,
|
||||||
│ └── USER_GUIDE.md # Manuale utente per ruolo
|
│ │ # auth_service, station_service
|
||||||
├── docker-compose.yml # Produzione (Traefik, SSL)
|
│ ├── migrations/ # Alembic (alembic.ini + env.py)
|
||||||
├── docker-compose.dev.yml # Sviluppo (Nginx, porta 80)
|
│ ├── templates/ # Pagina setup (Jinja2)
|
||||||
└── .env.example # Template variabili d'ambiente
|
│ └── tests/ # pytest + httpx + aiosqlite
|
||||||
|
└── frontend/
|
||||||
|
└── flask_app/ # Flask Frontend
|
||||||
|
├── app.py # Factory + ProxyFix + CSRF + Babel
|
||||||
|
├── config.py # STATION_CODE, API_SERVER_URL, ecc.
|
||||||
|
├── compile_translations.py
|
||||||
|
├── blueprints/ # auth, maker, measure, statistics, admin
|
||||||
|
├── services/ # APIClient (proxy verso FastAPI con XFF)
|
||||||
|
├── templates/ # Jinja2 + Alpine.js
|
||||||
|
├── static/
|
||||||
|
│ ├── css/ # TailwindCSS compilato
|
||||||
|
│ └── js/ # numpad, caliper, barcode, csv-export,
|
||||||
|
│ # spc-charts, annotation-editor/viewer
|
||||||
|
├── translations/ # Flask-Babel .po/.mo IT/EN
|
||||||
|
└── tests/
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -238,7 +276,7 @@ TieMeasureFlow/
|
|||||||
|
|
||||||
| Comando | Descrizione |
|
| Comando | Descrizione |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `docker compose -f docker-compose.dev.yml up -d` | Avvia servizi in sviluppo |
|
| `docker compose -f docker-compose.dev.yml up -d --build` | Avvia servizi in sviluppo (build incluso) |
|
||||||
| `docker compose -f docker-compose.dev.yml down` | Ferma e rimuove i container |
|
| `docker compose -f docker-compose.dev.yml down` | Ferma e rimuove i container |
|
||||||
| `docker compose logs -f server` | Segui log server in tempo reale |
|
| `docker compose logs -f server` | Segui log server in tempo reale |
|
||||||
| `docker compose logs -f client` | Segui log client in tempo reale |
|
| `docker compose logs -f client` | Segui log client in tempo reale |
|
||||||
@@ -248,47 +286,62 @@ TieMeasureFlow/
|
|||||||
|
|
||||||
### Alembic (migrations)
|
### Alembic (migrations)
|
||||||
|
|
||||||
Nota: `alembic.ini` si trova dentro `server/migrations/`, è richiesto il flag `-c`.
|
`alembic.ini` si trova in `src/backend/migrations/`, è richiesto il flag `-c`. `env.py` aggiunge la project root a `sys.path` per risolvere `src.backend.*`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd server
|
uv run alembic -c src/backend/migrations/alembic.ini upgrade head # Applica migrazioni
|
||||||
alembic -c migrations/alembic.ini upgrade head # Applica migrazioni
|
uv run alembic -c src/backend/migrations/alembic.ini revision --autogenerate -m "descrizione" # Genera
|
||||||
alembic -c migrations/alembic.ini revision --autogenerate -m "descrizione" # Genera migrazione
|
uv run alembic -c src/backend/migrations/alembic.ini downgrade -1 # Rollback ultima
|
||||||
alembic -c migrations/alembic.ini downgrade -1 # Rollback ultima
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Via Docker:
|
Via Docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose exec server alembic -c migrations/alembic.ini upgrade head
|
docker compose exec server uv run alembic -c src/backend/migrations/alembic.ini upgrade head
|
||||||
```
|
```
|
||||||
|
|
||||||
### i18n (Traduzioni)
|
### i18n (Traduzioni)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd client
|
cd src/frontend/flask_app
|
||||||
pybabel extract -F babel.cfg -k _ -o translations/messages.pot . # Estrai stringhe
|
uv run --project ../../.. pybabel extract -F babel.cfg -k _ -o translations/messages.pot . # Estrai
|
||||||
pybabel update -i translations/messages.pot -d translations # Aggiorna catalogo
|
uv run --project ../../.. pybabel update -i translations/messages.pot -d translations # Aggiorna
|
||||||
pybabel compile -d translations # Compila .po → .mo
|
uv run --project ../../.. pybabel compile -d translations # Compila .po → .mo
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gestione dipendenze (uv)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv add <pacchetto> # core (entrambi)
|
||||||
|
uv add --optional server <pacchetto> # solo backend
|
||||||
|
uv add --optional client <pacchetto> # solo frontend
|
||||||
|
uv add --optional dev <pacchetto> # solo dev/test
|
||||||
|
uv sync --extra server --extra client --extra dev # reinstalla
|
||||||
|
uv lock # rigenera uv.lock
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
Il server usa SQLite in-memory tramite `aiosqlite`: i test girano senza MySQL installato.
|
Il backend usa SQLite in-memory tramite `aiosqlite`: i test girano senza MySQL installato.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Server
|
# Tutti i test (backend + frontend)
|
||||||
cd server && pytest # Tutti i test
|
uv run pytest
|
||||||
cd server && pytest tests/test_auth.py # Singolo modulo
|
|
||||||
cd server && pytest tests/test_auth.py::test_login_success # Singolo test
|
|
||||||
cd server && pytest --cov # Con copertura
|
|
||||||
|
|
||||||
# Client
|
# Solo backend
|
||||||
cd client && pytest
|
uv run pytest src/backend/tests/
|
||||||
|
uv run pytest src/backend/tests/test_auth.py
|
||||||
|
uv run pytest src/backend/tests/test_auth.py::test_login_success
|
||||||
|
uv run pytest --cov src/backend
|
||||||
|
|
||||||
|
# Solo frontend
|
||||||
|
uv run pytest src/frontend/flask_app/tests/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Stato corrente: **171 pass, 4 fail pre-esistenti** (vedi `docs/architecture/STATO_PROGETTO.md`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Variabili d'Ambiente
|
## Variabili d'Ambiente
|
||||||
@@ -304,8 +357,11 @@ Copia `.env.example` in `.env` e configura:
|
|||||||
| `SERVER_CORS_ORIGINS` | Origini CORS ammesse |
|
| `SERVER_CORS_ORIGINS` | Origini CORS ammesse |
|
||||||
| `CLIENT_SECRET_KEY` | Chiave segreta Flask (sessioni, CSRF) |
|
| `CLIENT_SECRET_KEY` | Chiave segreta Flask (sessioni, CSRF) |
|
||||||
| `API_SERVER_URL` | URL del backend visto dal client (es. `http://server:8000`) |
|
| `API_SERVER_URL` | URL del backend visto dal client (es. `http://server:8000`) |
|
||||||
| `UPLOAD_DIR` | Percorso upload file (default: `uploads`, relativo a `server/`) |
|
| `STATION_CODE` | **Per-tablet** — codice stazione (es. `ST-001`). Senza, il client mostra errore configurazione. |
|
||||||
| `MAX_UPLOAD_SIZE_MB` | Limite dimensione upload |
|
| `UPLOAD_DIR` | Percorso upload file (default: `uploads`, project root) |
|
||||||
|
| `MAX_UPLOAD_SIZE_MB` | Limite dimensione upload (default 50) |
|
||||||
|
| `RATE_LIMIT_LOGIN` | Login req/min/IP (default 5) |
|
||||||
|
| `RATE_LIMIT_GENERAL` | Richieste req/min/IP (default 300, per-tablet) |
|
||||||
| `NGINX_PORT`, `NGINX_SSL_PORT` | Porte Nginx (solo compose dev) |
|
| `NGINX_PORT`, `NGINX_SSL_PORT` | Porte Nginx (solo compose dev) |
|
||||||
| `SETUP_PASSWORD` | Password pagina setup (vuota = endpoint disabilitato) |
|
| `SETUP_PASSWORD` | Password pagina setup (vuota = endpoint disabilitato) |
|
||||||
| `SSL_CERTFILE`, `SSL_KEYFILE` | Certificato SSL (solo setup manuale) |
|
| `SSL_CERTFILE`, `SSL_KEYFILE` | Certificato SSL (solo setup manuale) |
|
||||||
@@ -314,16 +370,35 @@ Copia `.env.example` in `.env` e configura:
|
|||||||
|
|
||||||
## Documentazione
|
## Documentazione
|
||||||
|
|
||||||
|
Indice completo: [`docs/README.md`](docs/README.md).
|
||||||
|
|
||||||
|
### Stato e direzione
|
||||||
|
|
||||||
| Documento | Contenuto |
|
| Documento | Contenuto |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [docs/API.md](docs/API.md) | Riferimento completo API REST (endpoint, parametri, schemi) |
|
| [`docs/architecture/STATO_PROGETTO.md`](docs/architecture/STATO_PROGETTO.md) | Snapshot V2.0.0: cosa funziona oggi, test status, decisioni architetturali |
|
||||||
| [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) | Guida deployment VPS: Docker, Traefik, SSL, DNS, firewall |
|
| [`docs/architecture/ROADMAP.md`](docs/architecture/ROADMAP.md) | Cosa resta da fare (Fasi 2-7 rev04, decisioni cliente aperte, stime) |
|
||||||
| [docs/USER_GUIDE.md](docs/USER_GUIDE.md) | Manuale utente per ruolo (Maker, MeasurementTec, Metrologist) |
|
|
||||||
|
### Riferimenti operativi
|
||||||
|
|
||||||
|
| Documento | Contenuto |
|
||||||
|
|---|---|
|
||||||
|
| [`docs/API.md`](docs/API.md) | Riferimento completo API REST (endpoint, parametri, schemi) |
|
||||||
|
| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) | Guida deployment VPS: Docker, Traefik, SSL, DNS, firewall |
|
||||||
|
| [`docs/USER_GUIDE.md`](docs/USER_GUIDE.md) | Manuale utente per ruolo (Maker, MeasurementTec, Metrologist) |
|
||||||
|
| [`docs/I18N_SETUP.md`](docs/I18N_SETUP.md) | Setup e workflow traduzioni (Flask-Babel + Alpine.js) |
|
||||||
|
|
||||||
|
### Piani dettagliati
|
||||||
|
|
||||||
|
| Documento | Contenuto |
|
||||||
|
|---|---|
|
||||||
|
| [`docs/superpowers/plans/2026-04-17-rev04-master-roadmap.md`](docs/superpowers/plans/2026-04-17-rev04-master-roadmap.md) | Master plan rev04 (M1 + M2, decisioni aperte, stime) |
|
||||||
|
| [`docs/superpowers/plans/2026-04-17-rev04-phase1-stations.md`](docs/superpowers/plans/2026-04-17-rev04-phase1-stations.md) | Piano TDD Fase 1 stazioni (completato) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Licenza
|
## Licenza
|
||||||
|
|
||||||
Proprietary - Tielogic. All rights reserved.
|
Proprietary — Tielogic. All rights reserved.
|
||||||
|
|
||||||
Questo software è di proprietà esclusiva di Tielogic ed è protetto dalle leggi sul copyright. Non è consentita la distribuzione, modifica o utilizzo senza autorizzazione scritta.
|
Questo software è di proprietà esclusiva di Tielogic ed è protetto dalle leggi sul copyright. Non è consentita la distribuzione, modifica o utilizzo senza autorizzazione scritta.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ TieMeasureFlow supports complete internationalization (i18n) with:
|
|||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
client/
|
src/frontend/flask_app/
|
||||||
├── translations/ # Flask-Babel translations
|
├── translations/ # Flask-Babel translations
|
||||||
│ ├── babel.cfg # Extraction config
|
│ ├── babel.cfg # Extraction config
|
||||||
│ ├── it/LC_MESSAGES/
|
│ ├── it/LC_MESSAGES/
|
||||||
@@ -99,7 +99,7 @@ flash(_("Profilo aggiornato con successo"))
|
|||||||
After editing .po files:
|
After editing .po files:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd client
|
cd src/frontend/flask_app
|
||||||
python compile_translations.py
|
python compile_translations.py
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Documentazione TieMeasureFlow
|
||||||
|
|
||||||
|
Indice della documentazione del progetto.
|
||||||
|
|
||||||
|
## Stato e direzione
|
||||||
|
|
||||||
|
| Documento | Scopo |
|
||||||
|
|---|---|
|
||||||
|
| [`architecture/STATO_PROGETTO.md`](architecture/STATO_PROGETTO.md) | Cosa è fatto oggi (V2.0.0). Snapshot del sistema, componenti e capacità. |
|
||||||
|
| [`architecture/ROADMAP.md`](architecture/ROADMAP.md) | Cosa resta da fare. Fasi 2-7 della migrazione rev04 verso V1.1.0/M1 demo cliente. |
|
||||||
|
|
||||||
|
## Riferimenti operativi
|
||||||
|
|
||||||
|
| Documento | Scopo |
|
||||||
|
|---|---|
|
||||||
|
| [`API.md`](API.md) | Riferimento endpoint REST esposti dal backend FastAPI. |
|
||||||
|
| [`DEPLOYMENT.md`](DEPLOYMENT.md) | Guida deploy su VPS (Hostinger/Tielogic con Traefik + Let's Encrypt). |
|
||||||
|
| [`USER_GUIDE.md`](USER_GUIDE.md) | Manuale utente (operatore, maker, metrologist, admin). |
|
||||||
|
| [`I18N_SETUP.md`](I18N_SETUP.md) | Setup e workflow traduzioni (Flask-Babel + Alpine.js). |
|
||||||
|
|
||||||
|
## Piani dettagliati TDD (rev04)
|
||||||
|
|
||||||
|
| Documento | Scopo |
|
||||||
|
|---|---|
|
||||||
|
| [`superpowers/plans/2026-04-17-rev04-master-roadmap.md`](superpowers/plans/2026-04-17-rev04-master-roadmap.md) | Master plan rev04 V1.0.7 → V1.1.0, le 7 fasi e le decisioni aperte. |
|
||||||
|
| [`superpowers/plans/2026-04-17-rev04-phase1-stations.md`](superpowers/plans/2026-04-17-rev04-phase1-stations.md) | Piano TDD dettagliato Fase 1 (stazioni e identità per-tablet). **COMPLETATO.** |
|
||||||
|
|
||||||
|
## Specifiche esterne (binari)
|
||||||
|
|
||||||
|
| Documento | Scopo |
|
||||||
|
|---|---|
|
||||||
|
| [`specs/2026-04-16-schema-sviluppo-rev04.docx`](specs/2026-04-16-schema-sviluppo-rev04.docx) | Schema sviluppo SW TieFlow rev04-2026 fornito dal committente. |
|
||||||
|
|
||||||
|
## Storico
|
||||||
|
|
||||||
|
| Documento | Scopo |
|
||||||
|
|---|---|
|
||||||
|
| [`archive/2026-02-06-piano-implementazione-v1.md`](archive/2026-02-06-piano-implementazione-v1.md) | Piano implementazione V1.0.0 originale (riferimento storico). |
|
||||||
|
|
||||||
|
## Ulteriori riferimenti nel repo
|
||||||
|
|
||||||
|
- [`/CLAUDE.md`](../CLAUDE.md) — guidance per Claude Code (architettura, comandi, pattern critici).
|
||||||
|
- [`/README.md`](../README.md) — entry point del progetto.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Roadmap TieMeasureFlow — V2.0.0 → V1.1.0 (rev04 / M1 demo cliente)
|
||||||
|
|
||||||
|
> Aggiornare ad ogni Fase chiusa.
|
||||||
|
|
||||||
|
## Riferimenti
|
||||||
|
|
||||||
|
- Master plan dettagliato: [`../superpowers/plans/2026-04-17-rev04-master-roadmap.md`](../superpowers/plans/2026-04-17-rev04-master-roadmap.md)
|
||||||
|
- Spec sorgente: [`../specs/2026-04-16-schema-sviluppo-rev04.docx`](../specs/2026-04-16-schema-sviluppo-rev04.docx)
|
||||||
|
- Stato corrente: [`STATO_PROGETTO.md`](STATO_PROGETTO.md)
|
||||||
|
|
||||||
|
## Strategia: due milestone
|
||||||
|
|
||||||
|
| Milestone | Scope | Obiettivo |
|
||||||
|
|---|---|---|
|
||||||
|
| **M1 — Demo cliente** | Fasi 1-5 + deploy "demo" | Sistema testabile end-to-end col cliente per raccogliere feedback |
|
||||||
|
| **M2 — Produzione** | Fasi 6-7 + correzioni post-feedback + GAIA live | Rollout su tablet/PC reali |
|
||||||
|
|
||||||
|
## Stato Fasi (M1)
|
||||||
|
|
||||||
|
| Fase | Scope | Stato | Branch / Commit |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **1** | Stazioni + identità per-tablet | ✅ **COMPLETATA** | `V2.0.0` (merge `ea8e468` da `feature/rev04-phase1-stations`) |
|
||||||
|
| 2 | Ruolo Capoturno (Supervisor) + override token breve | ⏳ Da iniziare | — |
|
||||||
|
| 3 | Editor ricetta a blocchi (preparation + measurement) | ⏳ Da iniziare | — |
|
||||||
|
| 4 | Workflow operatore (retry/timer/autologout/avvio produzione) | ⏳ Da iniziare | — |
|
||||||
|
| 5 (M1) | `ImportOnlyGaiaClient` + UI import dati cliente reali | ⏳ Da iniziare | — |
|
||||||
|
| Deploy M1 | VPS demo (compose + Traefik + LE, no registry) | ⏳ Da iniziare | — |
|
||||||
|
|
||||||
|
## Stato Fasi (M2)
|
||||||
|
|
||||||
|
| Fase | Scope | Stato |
|
||||||
|
|---|---|---|
|
||||||
|
| 5 (M2) | GAIA reale (protocollo TBD, polling, comandi produzione) | ⏳ Bloccata da decisioni cliente (D-0.1, D-0.2) |
|
||||||
|
| 6 | Deploy B industriale (registry privato + Watchtower + STATION_ID per-tablet + CI release) | ⏳ Pianificata |
|
||||||
|
| 7 | Hardening, security review, E2E sito pilota, docs aggiornati, i18n delta | ⏳ Pianificata |
|
||||||
|
|
||||||
|
## Decisioni aperte (bloccanti per M2 / future fasi)
|
||||||
|
|
||||||
|
Da: master plan §0 "Precondizioni e Decisioni Aperte". Da risolvere col cliente prima della Fase 5/6.
|
||||||
|
|
||||||
|
| ID | Decisione | Stato | Bloccante per |
|
||||||
|
|---|---|---|---|
|
||||||
|
| D-0.1 | Protocollo integrazione GAIA (REST / DB shared / OPC-UA / file) | **Aperta** | Fase 5 reale (M2) |
|
||||||
|
| D-0.2 | Credenziali e rete GAIA (VPN / firewall / whitelist IP) | **Aperta** | Fase 5 reale (M2) |
|
||||||
|
| D-0.3 | Target hardware "tablet" (Windows / Linux industriale / Android) | **Aperta** | Fase 6 (deploy B) |
|
||||||
|
| D-0.4 | Cicalino/luce avviso (audio HTML5 / hardware USB / entrambi) | **Rimandata a M2** | Fase 4 finale |
|
||||||
|
| D-0.5 | Parametri runtime modificabili vs versione immutabile | **Aperta** (raccomandato B: separare volatili) | Fase 3 |
|
||||||
|
| D-0.6 | Auth capoturno durante override (modale / PIN / RFID) | **Aperta** | Fase 2 |
|
||||||
|
| D-0.7 | Timeout auto-logout | **Risolta** | — |
|
||||||
|
| D-0.8 | Naming ruolo capoturno | **Proposta:** `Supervisor` | Fase 2 |
|
||||||
|
| D-0.9 | Tag versione immagine docker | **Proposta:** SemVer + `latest` | Fase 6 |
|
||||||
|
| D-0.10 | Registry esposto su Internet o solo VPN | **Proposta:** solo VPN cliente | Fase 6 |
|
||||||
|
|
||||||
|
## Tech debt da chiudere
|
||||||
|
|
||||||
|
| Item | Priorità | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| 3 test backend pre-esistenti rotti (`test_recipes`, `test_tasks`) | Media | Investigare prima di Fase 3 (toccano recipe + task router). |
|
||||||
|
| 1 test client pre-esistente rotto (`test_save_measurement_proxy`) | Bassa | Probabilmente CSRF/payload. Risolvere con Fase 4. |
|
||||||
|
| `.env` rename a convenzione spec (SERVICE_NAME, SERVICE_DOMAIN, API_KEY) | Bassa | Rinviato (impatto deploy). |
|
||||||
|
| Header `X-API-Key` rename a `X-Api-Key` | Bassa | Vedere se M2 lo richiede. |
|
||||||
|
| Envelope risposta `{success,data,error}` | Bassa | Eventuale API v2 in M2. |
|
||||||
|
| `Dockerfile.frontend`: `pybabel compile` via `uv run` non testato in build reale | Alta | Verificare al primo `docker compose build`. |
|
||||||
|
|
||||||
|
## Open per scelta utente prima della prossima sessione
|
||||||
|
|
||||||
|
1. **Quale fase iniziare adesso?** Opzioni: Fase 2 (Supervisor — sblocca workflow), Fase 3 (block editor — independente), Fase 5 (import GAIA dati reali — sblocca demo).
|
||||||
|
2. **Revisione decisioni aperte col cliente** — D-0.1 / D-0.2 / D-0.3 / D-0.6 prima di pianificare Fase 5 e 6.
|
||||||
|
3. **Smoke test Docker** della nuova struttura V2.0.0 (`docker compose -f docker-compose.dev.yml up --build`) per validare i Dockerfile riscritti.
|
||||||
|
4. **Test di carico** (k6/locust) a 20 VU su `/measure/save-measurement` per validare la scalatura worker (capacità annunciata: 20-30 tablet contemporanei).
|
||||||
|
|
||||||
|
## Stima tempi residui M1 (post-Fase 1)
|
||||||
|
|
||||||
|
| Task | Stima full-time |
|
||||||
|
|---|---|
|
||||||
|
| Fase 2 — Supervisor + override | 1 settimana |
|
||||||
|
| Fase 3 — Block editor | 1.5 settimane |
|
||||||
|
| Fase 4 — Workflow operatore | 2 settimane |
|
||||||
|
| Fase 5 (M1) — Import-only GAIA | 1 settimana |
|
||||||
|
| Deploy M1 demo | 0.5 settimane |
|
||||||
|
| **Totale M1 residuo** | **~6 settimane** |
|
||||||
|
|
||||||
|
## Stima tempi M2 (dopo feedback)
|
||||||
|
|
||||||
|
| Task | Stima |
|
||||||
|
|---|---|
|
||||||
|
| Aggiustamenti post-feedback | variabile (1-2 sett.) |
|
||||||
|
| Fase 5 reale GAIA | 1-2 settimane |
|
||||||
|
| Fase 6 deploy B | 1 settimana |
|
||||||
|
| Fase 7 hardening | 1-2 settimane |
|
||||||
|
| **Totale M2** | **~4-7 settimane** |
|
||||||
|
|
||||||
|
**Totale fino a produzione:** ~10-13 settimane full-time da oggi (2026-04-25), assumendo decisioni aperte risolte in tempo utile.
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Stato Progetto TieMeasureFlow — V2.0.0
|
||||||
|
|
||||||
|
> Snapshot al 2026-04-25. Aggiornare ad ogni milestone.
|
||||||
|
|
||||||
|
## Versione corrente
|
||||||
|
|
||||||
|
**V2.0.0** (in sviluppo, branch `V2.0.0` come default su `git.tielogic.xyz`).
|
||||||
|
|
||||||
|
Versione precedente di produzione: `V1.0.7`.
|
||||||
|
|
||||||
|
## Sintesi esecutiva
|
||||||
|
|
||||||
|
Il sistema base (V1.0.7) è completo e collaudato: ricette, task, misurazioni, SPC, report PDF, gestione utenti, dashboard metrologist. La V2.0.0 in corso aggiunge il primo blocco della migrazione **rev04** (stazioni per-tablet) e ristruttura l'intero monorepo secondo lo standard `python-project-spec-design.md` (uv + `src/backend/` + `src/frontend/flask_app/`).
|
||||||
|
|
||||||
|
## Cosa funziona oggi (V2.0.0 — branch corrente)
|
||||||
|
|
||||||
|
### Funzionalità ereditate da V1.0.7
|
||||||
|
- Autenticazione username/password + API key per-utente, ruoli combinabili (Maker, MeasurementTec, Metrologist) + flag `is_admin`.
|
||||||
|
- Recipe versioning copy-on-write: una nuova versione si crea solo se la corrente ha già measurements; altrimenti update in-place.
|
||||||
|
- Editor ricette (Maker) con annotation editor Fabric.js (~1200 LOC, collaudato su tablet).
|
||||||
|
- Workflow operatore tablet: select_recipe → task_list → task_execute → task_complete, con barcode scanner e numpad touch (input USB calibro con burst detection).
|
||||||
|
- Calcolo pass/fail con limiti UTL/UWL/LWL/LTL.
|
||||||
|
- Dashboard SPC: capability (Cp/Cpk/Pp/Ppk), control chart (UCL/LCL = mean ± 3σ), istogramma con curva normale, calcoli puro stdlib (no numpy).
|
||||||
|
- Report PDF (WeasyPrint + Kaleido SVG).
|
||||||
|
- Setup page protetta da `SETUP_PASSWORD` per inizializzazione DB e seed.
|
||||||
|
- i18n IT/EN (Flask-Babel + Alpine.js JSON).
|
||||||
|
- Tema light/dark via `Alpine.store('theme')` + localStorage.
|
||||||
|
|
||||||
|
### Aggiunte V2.0.0 (rev04 Fase 1 — Stazioni per-tablet)
|
||||||
|
- Tabelle `stations` + `station_recipe_assignments` (Alembic migration `002_add_stations.py`).
|
||||||
|
- Modelli ORM: `Station`, `StationRecipeAssignment` con vincolo unique `(station_id, recipe_id)`.
|
||||||
|
- Schemas Pydantic: `StationCreate/Update/Response`, `StationRecipeAssignmentCreate/Response`, `RecipeSummary`.
|
||||||
|
- Service `station_service` (CRUD + assegnazioni + cascade delete).
|
||||||
|
- Router `/api/stations` con CRUD admin + endpoint operatore `GET /api/stations/by-code/{code}/recipes`.
|
||||||
|
- Seed automatico `ST-DEFAULT` con tutte le ricette esistenti (idempotente).
|
||||||
|
- Variabile env client `STATION_CODE` letta da `Config`, helper `APIClient.get_station_recipes()`.
|
||||||
|
- Filtro `select_recipe`: il client mostra solo le ricette assegnate alla propria stazione, errore se `STATION_CODE` non configurato.
|
||||||
|
- **GUI admin completa** in `/admin/stations`: tabella con search, modal create/edit, modal gestione assegnazioni ricette, conferma eliminazione, link in navbar (desktop + mobile).
|
||||||
|
- 47 nuovi test (32 server + 15 client) tutti pass.
|
||||||
|
|
||||||
|
### Aggiunte V2.0.0 (performance + multi-utente)
|
||||||
|
- Gunicorn 5 workers × 4 thread (gthread) — capacità ~20 richieste concorrenti Flask, regge 20+ tablet.
|
||||||
|
- Uvicorn 4 workers + `--proxy-headers --forwarded-allow-ips='*'`.
|
||||||
|
- Rate limit middleware: identificazione IP reale via `X-Forwarded-For` → `X-Real-IP` → `request.client.host`.
|
||||||
|
- Rate limit general 100 → 300 req/min/IP (per-tablet ora, non più condiviso).
|
||||||
|
- Flask `ProxyFix(x_for=1, x_proto=1, x_host=1)` per IP reale dietro Nginx.
|
||||||
|
- `APIClient` propaga `X-Forwarded-For` + `X-Real-IP` (sia JSON che multipart).
|
||||||
|
- 12 test aggiuntivi (7 server + 5 client).
|
||||||
|
|
||||||
|
### Aggiunte V2.0.0 (struttura monorepo)
|
||||||
|
- `pyproject.toml` unico con extra `server`/`client`/`dev`. Niente più `requirements.txt`.
|
||||||
|
- `uv.lock` (77 pacchetti) + `.python-version` (3.11) committati per build riproducibili.
|
||||||
|
- Layout `src/backend/` + `src/frontend/flask_app/` (vedi sotto).
|
||||||
|
- `Dockerfile` (root) + `Dockerfile.frontend` riscritti con `uv sync --frozen --no-dev --extra server|client`.
|
||||||
|
- `docker-compose.{dev,}.yml` con build context `.`.
|
||||||
|
- Alembic env.py aggiunge project root a `sys.path`; `script_location = %(here)s` resta valido.
|
||||||
|
- `.dockerignore` aggiornato.
|
||||||
|
|
||||||
|
## Layout repository (V2.0.0)
|
||||||
|
|
||||||
|
```
|
||||||
|
TieMeasureFlow/
|
||||||
|
├── pyproject.toml + uv.lock + .python-version
|
||||||
|
├── Dockerfile (backend) + Dockerfile.frontend
|
||||||
|
├── docker-compose.dev.yml + docker-compose.yml
|
||||||
|
├── nginx/
|
||||||
|
├── uploads/ # volume Docker
|
||||||
|
├── docs/ # raggruppata e indicizzata
|
||||||
|
│ ├── README.md (indice)
|
||||||
|
│ ├── API.md / DEPLOYMENT.md / USER_GUIDE.md / I18N_SETUP.md
|
||||||
|
│ ├── architecture/ # questo file + ROADMAP.md
|
||||||
|
│ ├── archive/ # piani storici
|
||||||
|
│ ├── specs/ # spec esterne (.docx)
|
||||||
|
│ └── superpowers/plans/ # piani TDD dettagliati
|
||||||
|
└── src/
|
||||||
|
├── backend/
|
||||||
|
│ ├── main.py / config.py / database.py
|
||||||
|
│ ├── api/{routers,middleware}/
|
||||||
|
│ ├── models/{orm,api}/
|
||||||
|
│ ├── services/
|
||||||
|
│ ├── migrations/
|
||||||
|
│ ├── templates/
|
||||||
|
│ └── tests/
|
||||||
|
└── frontend/
|
||||||
|
└── flask_app/
|
||||||
|
├── app.py / config.py / compile_translations.py
|
||||||
|
├── blueprints/ (auth, maker, measure, statistics, admin)
|
||||||
|
├── services/ (api_client.py)
|
||||||
|
├── templates/ + static/ + translations/
|
||||||
|
└── tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test status
|
||||||
|
|
||||||
|
| Suite | Pass | Fail | Note |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Backend (`src/backend/tests/`) | 127 | 3 | Fail pre-esistenti: `test_recipes` (2) + `test_tasks` (1). Nessuno introdotto dalla V2.0.0. |
|
||||||
|
| Frontend (`src/frontend/flask_app/tests/`) | 44 | 1 | Fail pre-esistente: `test_save_measurement_proxy`. |
|
||||||
|
| **Totale** | **171** | **4** | Tutti i fallimenti tracciati come tech debt da risolvere. |
|
||||||
|
|
||||||
|
## Stack confermato
|
||||||
|
|
||||||
|
- **Backend:** FastAPI + SQLAlchemy 2.0 async + MySQL 8 + Alembic + Pydantic v2 + WeasyPrint + Plotly/Kaleido.
|
||||||
|
- **Frontend:** Flask + Jinja2 + Alpine.js + TailwindCSS + Fabric.js 5.3.1 + html5-qrcode + Plotly.js + Flask-Babel.
|
||||||
|
- **Deploy:** Docker Compose. Dev = Nginx; Prod = Traefik + Let's Encrypt SSL.
|
||||||
|
- **Tooling:** uv (package mgmt), pytest + pytest-asyncio + httpx + aiosqlite (test).
|
||||||
|
|
||||||
|
## Decisioni architetturali rilevanti
|
||||||
|
|
||||||
|
| Decisione | Stato | Note |
|
||||||
|
|---|---|---|
|
||||||
|
| Frontend Flask invece di React (deroga vs spec §8) | **Confermata** | Tablet UX server-side, USB calipers/barcode, Fabric.js editor, i18n Babel collaudato. Vedi conversazione 2026-04-25. |
|
||||||
|
| NATS messaging (spec §7) | **Skippato** | Monorepo single-host, no microservizi. Nessuno stub `nats_client/` creato. |
|
||||||
|
| Envelope risposta `{success,data,error}` (spec §6) | **Rimandato** | Costo 4-5gg refactor + rotture client. Eventuale v2 API in M2. |
|
||||||
|
| Header `X-API-Key` vs spec `X-Api-Key` | **Mantenuto attuale** | Rinominare costa 50+ punti di codice + breaking per deploy. Rivedere in M2. |
|
||||||
|
| Variabili `.env` (DB_HOST, SERVER_PORT, ...) | **Mantenute attuali** | Rename a SERVICE_NAME/SERVICE_DOMAIN/API_KEY rinviato (impatta deploy esistenti). |
|
||||||
|
|
||||||
|
## Branch git
|
||||||
|
|
||||||
|
- **Default:** `V2.0.0` (lavoro corrente)
|
||||||
|
- **Mantenuti:** `V1.0.0` … `V1.0.7` (release branches storiche)
|
||||||
|
- **Mergiato e chiuso:** `feature/rev04-phase1-stations` (in `V2.0.0` con commit `ea8e468`)
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
FROM python:3.11-slim
|
|
||||||
|
|
||||||
# Installa dipendenze sistema per WeasyPrint
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
libpango-1.0-0 \
|
|
||||||
libpangocairo-1.0-0 \
|
|
||||||
libcairo2 \
|
|
||||||
libgdk-pixbuf-2.0-0 \
|
|
||||||
libffi-dev \
|
|
||||||
shared-mime-info \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Crea directory uploads
|
|
||||||
RUN mkdir -p uploads/images uploads/pdfs uploads/logos uploads/reports
|
|
||||||
|
|
||||||
EXPOSE 8000
|
|
||||||
|
|
||||||
# Entry point: Alembic upgrade + Uvicorn
|
|
||||||
CMD ["sh", "-c", "alembic -c migrations/alembic.ini upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 --proxy-headers --forwarded-allow-ips='*'"]
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
FROM python:3.11-slim AS base
|
|
||||||
|
|
||||||
# Installa Node.js per Tailwind CSS build
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
curl \
|
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt gunicorn
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Install and build Tailwind CSS
|
|
||||||
RUN npm install tailwindcss@3 && npx tailwindcss -i static/css/input.css -o static/css/tailwind.css --minify
|
|
||||||
|
|
||||||
# Compile Flask-Babel translations
|
|
||||||
RUN pybabel compile -d translations
|
|
||||||
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
CMD ["gunicorn", \
|
|
||||||
"--workers", "5", \
|
|
||||||
"--threads", "4", \
|
|
||||||
"--worker-class", "gthread", \
|
|
||||||
"--timeout", "60", \
|
|
||||||
"--bind", "0.0.0.0:5000", \
|
|
||||||
"--access-logfile", "-", \
|
|
||||||
"--forwarded-allow-ips", "*", \
|
|
||||||
"app:create_app()"]
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Verify i18n setup for TieMeasureFlow."""
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def check_file(path: Path, description: str) -> bool:
|
|
||||||
"""Check if a file exists and report."""
|
|
||||||
exists = path.exists()
|
|
||||||
status = "[OK]" if exists else "[FAIL]"
|
|
||||||
print(f"{status} {description}: {path}")
|
|
||||||
return exists
|
|
||||||
|
|
||||||
def check_json_valid(path: Path) -> bool:
|
|
||||||
"""Check if JSON file is valid."""
|
|
||||||
try:
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
json.load(f)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ERROR: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Verify i18n setup."""
|
|
||||||
print("=== TieMeasureFlow i18n Verification ===\n")
|
|
||||||
|
|
||||||
root = Path(__file__).parent
|
|
||||||
all_good = True
|
|
||||||
|
|
||||||
# Check Flask-Babel files
|
|
||||||
print("Flask-Babel (Server-side):")
|
|
||||||
files = [
|
|
||||||
(root / "translations/babel.cfg", "Babel config"),
|
|
||||||
(root / "translations/it/LC_MESSAGES/messages.po", "Italian .po"),
|
|
||||||
(root / "translations/it/LC_MESSAGES/messages.mo", "Italian .mo"),
|
|
||||||
(root / "translations/en/LC_MESSAGES/messages.po", "English .po"),
|
|
||||||
(root / "translations/en/LC_MESSAGES/messages.mo", "English .mo"),
|
|
||||||
]
|
|
||||||
|
|
||||||
for path, desc in files:
|
|
||||||
if not check_file(path, desc):
|
|
||||||
all_good = False
|
|
||||||
|
|
||||||
# Count messages in .po files
|
|
||||||
if (root / "translations/it/LC_MESSAGES/messages.po").exists():
|
|
||||||
with open(root / "translations/it/LC_MESSAGES/messages.po", 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
msgid_count = content.count('msgid "') - 1 # Exclude header
|
|
||||||
print(f" Italian: {msgid_count} messages")
|
|
||||||
|
|
||||||
if (root / "translations/en/LC_MESSAGES/messages.po").exists():
|
|
||||||
with open(root / "translations/en/LC_MESSAGES/messages.po", 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
msgid_count = content.count('msgid "') - 1
|
|
||||||
print(f" English: {msgid_count} messages")
|
|
||||||
|
|
||||||
# Check Alpine.js i18n files
|
|
||||||
print("\nAlpine.js i18n (Client-side):")
|
|
||||||
json_files = [
|
|
||||||
(root / "static/js/locales/it.json", "Italian locale"),
|
|
||||||
(root / "static/js/locales/en.json", "English locale"),
|
|
||||||
]
|
|
||||||
|
|
||||||
for path, desc in json_files:
|
|
||||||
if check_file(path, desc):
|
|
||||||
if not check_json_valid(path):
|
|
||||||
all_good = False
|
|
||||||
else:
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
keys = count_keys(data)
|
|
||||||
print(f" {keys} total keys")
|
|
||||||
|
|
||||||
# Check app.py integration
|
|
||||||
print("\nApp Integration:")
|
|
||||||
app_py = root / "app.py"
|
|
||||||
if check_file(app_py, "app.py"):
|
|
||||||
with open(app_py, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
checks = [
|
|
||||||
("from flask_babel import Babel", "Flask-Babel imported"),
|
|
||||||
("def get_locale()", "get_locale() function"),
|
|
||||||
("Babel(app, locale_selector=get_locale)", "Babel initialized"),
|
|
||||||
("def set_language(lang)", "set_language endpoint"),
|
|
||||||
]
|
|
||||||
for check_str, check_desc in checks:
|
|
||||||
found = check_str in content
|
|
||||||
status = "[OK]" if found else "[FAIL]"
|
|
||||||
print(f" {status} {check_desc}")
|
|
||||||
if not found:
|
|
||||||
all_good = False
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
print("\n" + "="*40)
|
|
||||||
if all_good:
|
|
||||||
print("[OK] i18n setup verified successfully!")
|
|
||||||
print("\nNext steps:")
|
|
||||||
print("1. Use _() in Python code and templates")
|
|
||||||
print("2. Use $t() in Alpine.js components")
|
|
||||||
print("3. Test language switching: /set-language/en or /set-language/it")
|
|
||||||
else:
|
|
||||||
print("[FAIL] Some checks failed - review errors above")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def count_keys(obj, depth=0):
|
|
||||||
"""Recursively count keys in nested dict."""
|
|
||||||
if not isinstance(obj, dict):
|
|
||||||
return 0
|
|
||||||
count = len(obj)
|
|
||||||
for value in obj.values():
|
|
||||||
if isinstance(value, dict):
|
|
||||||
count += count_keys(value, depth + 1)
|
|
||||||
return count
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
exit(main())
|
|
||||||
Reference in New Issue
Block a user