959e92552b
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
620 lines
32 KiB
Markdown
620 lines
32 KiB
Markdown
# TieMeasureFlow by Tielogic
|
||
|
||
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:** V3.0.0 (in sviluppo) — branch `V3.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).
|
||
> V3.0.0 lavora sui quindici punti di `TieMeasureFlow_modifiche_2026-07-28.md`: vedi [Novità di V3.0.0](#novità-di-v300).
|
||
|
||
---
|
||
|
||
## Panoramica
|
||
|
||
TieMeasureFlow guida l'operatore attraverso sequenze di misurazione definite dal Maker, registra i valori (da calibro USB HID o numpad touch), e valuta automaticamente la conformità rispetto alle tolleranze configurate. I dati raccolti alimentano una dashboard SPC con indici di capability e control chart, esportabili in PDF.
|
||
|
||
Caratteristiche principali:
|
||
|
||
- Recipe versioning condizionale (copy-on-write se esistono misurazioni, update in-place altrimenti)
|
||
- Calcolo pass/fail/warning su quattro limiti di tolleranza (UTL, UWL, LWL, LTL)
|
||
- Tracciamento del tempo di inserimento per ogni misura (`input_duration_ms`): il client misura quanto tempo l'operatore impiega su ciascun subtask, dall'attivazione al salvataggio, utile per analisi di tempo ciclo e produttività
|
||
- 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
|
||
- 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
|
||
- 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)
|
||
|
||
---
|
||
|
||
## Novità di V3.0.0
|
||
|
||
V3.0.0 lavora sui quindici punti raccolti in `TieMeasureFlow_modifiche_2026-07-28.md`,
|
||
dal sopralluogo del 28/07. Quanto segue è **fatto e in esercizio**.
|
||
|
||
| # | Punto | Cosa cambia |
|
||
|---|---|---|
|
||
| 1 · 6 | **Produzione con una vita propria** | `production_runs` e `production_events` sul server: avvio, fermo linea, ripresa e fine produzione esistono come stato, non come pulsanti. La fine produzione emette il file di statistica dell'intera produzione |
|
||
| 2 | **Tipo di task dichiarato** | `task_type` (`note`, `measure`, `drawing`, `xf_compare`, `camera_measure`) al posto di «ha subtask, quindi è una misura»: un task di misura senza quote non si comporta più come una nota |
|
||
| 3 | **Ciclo di misura** | L'intervallo della ricetta vive sul server, sopravvive al cambio pagina, conta anche il ritardo (in rosso, oltre lo zero) e riporta l'operatore alla misura. Cicalino via WebAudio |
|
||
| 5 | **Fuori tolleranza vincolante** | Una quota fuori tolleranza blocca l'avanzamento finché il capoturno non autorizza — o finché quella stessa quota non viene rimisurata dentro i limiti. L'autorizzazione resta scritta sulla misura e nel CSV |
|
||
| 7 | **Stazioni** | Ricette per stazione, cambio stazione da URL per il collaudo, reset per riga |
|
||
| 8 | **Tracciabilità obbligatoria** | `requires_lot` / `requires_serial` per ricetta, verificati sul server: nessuna porta d'ingresso li aggira, barcode compreso |
|
||
| 9 | **Blocco inserimento manuale** | `allow_manual_input`: con la ricetta a solo calibro il tastierino non viene disegnato affatto, e il server rifiuta comunque un valore digitato a mano |
|
||
| 10 | **Sequenza per l'operatore** | La ricetta si apre sul primo task, non su un elenco. La lista scende a secondo livello e dice quali task sono rimasti incompiuti (`2/3`). «Fine ciclo misura» è visibile da subito e spento finché mancano quote |
|
||
| 11 | **Descrizioni formattate** | `**grassetto**` e a capo nelle descrizioni dei task, con marcatura e non HTML: la sanificazione è per costruzione |
|
||
| 12 | **Funzionamento senza internet** | Tutte le librerie e i font nell'installazione, versioni congelate e verificate per impronta, Content-Security-Policy a sola origine locale |
|
||
| 14 | **Layout stabile** | Una cornice sola per tutte le viste, allineata alla navbar; spazio della barra di scorrimento sempre riservato; schermata di misura su `100dvh`. Vedi [`docs/architecture/LAYOUT.md`](docs/architecture/LAYOUT.md) |
|
||
| 15 | **Statistica separata** | Si registra sempre, si consulta a parte: nessun percorso dell'operatore porta alla statistica (verificato da test) |
|
||
|
||
**Aperto**: il punto 4 (numero di tentativi prima del capoturno) attende le risposte
|
||
del cliente su modalità di autorizzazione e numero di tentativi; il punto 13
|
||
(generazione task dalla scheda tecnica con l'AI) è fuori offerta.
|
||
|
||
---
|
||
|
||
## Architettura
|
||
|
||
```
|
||
Browser/Tablet
|
||
|
|
||
Reverse Proxy (Nginx — sviluppo | Traefik+SSL — produzione)
|
||
|
|
||
Flask Frontend :5000 (rendering server-side, Jinja2 + Alpine.js)
|
||
| X-API-Key + X-Forwarded-For
|
||
FastAPI Backend :8000 (API REST asincrona)
|
||
|
|
||
MySQL 8.0
|
||
```
|
||
|
||
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
|
||
|
||
### Backend (`src/backend/`)
|
||
|
||
| Componente | Versione | Ruolo |
|
||
|---|---|---|
|
||
| FastAPI | ultima stabile | Framework API REST asincrono |
|
||
| SQLAlchemy 2.0 | async | ORM con pool connessioni 10+20 |
|
||
| asyncmy | ultima stabile | Driver MySQL asincrono |
|
||
| MySQL | 8.0 | Database relazionale |
|
||
| Alembic | ultima stabile | Migrazioni schema |
|
||
| Pydantic v2 | v2 | Validazione I/O |
|
||
| WeasyPrint | ultima stabile | Generazione report PDF |
|
||
| Kaleido | ultima stabile | Export grafici Plotly in SVG |
|
||
| bcrypt | ultima stabile | Hashing password |
|
||
| Pillow | ultima stabile | Thumbnail automatici upload |
|
||
|
||
### Frontend (`src/frontend/flask_app/`)
|
||
|
||
| Componente | Versione | Ruolo |
|
||
|---|---|---|
|
||
| Flask | 3.x | Framework web server-side |
|
||
| gunicorn | 21+ | WSGI server (5 workers × 4 thread gthread) |
|
||
| Jinja2 | incluso in Flask | Template engine |
|
||
| Alpine.js | 3.15.12 (locale) | Reattività leggera lato client |
|
||
| TailwindCSS | 3.4.19 (build) | CSS utility-first, compilato nell'immagine |
|
||
| Plotly.js | 2.32.0 (locale) | Grafici SPC interattivi |
|
||
| PDF.js | 3.11.174 (locale) | Visualizzazione disegni PDF, worker incluso |
|
||
| Fabric.js | 5.3.1 (locale) | Editor annotazioni disegni tecnici |
|
||
| Inter + JetBrains Mono | woff2 locali | Font UI e numeri |
|
||
| Flask-Babel | ultima stabile | i18n IT/EN |
|
||
|
||
**Nessuna libreria arriva dalla rete.** Tutte stanno in
|
||
[`src/frontend/flask_app/static/vendor/`](src/frontend/flask_app/static/vendor/VERSIONS.md)
|
||
con versione nel nome e impronta SHA-256 verificata da un test: l'installazione a
|
||
Tràfilo è su rete di produzione isolata, dove una pagina che aspetta un CDN è una
|
||
pagina bianca. Una Content-Security-Policy a sola origine locale (`app.py`) fa sì che
|
||
un tag verso l'esterno aggiunto in futuro venga rifiutato alla scrivania, non in
|
||
reparto.
|
||
|
||
### 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
|
||
|
||
Docker Compose è il metodo raccomandato. Gestisce database, migrazioni, build delle immagini con `uv` e configurazione Nginx in un solo comando.
|
||
|
||
```bash
|
||
# 1. Clona il repository CON il sottomodulo di VisionSuite (vendor/visionsuite):
|
||
# senza --recurse-submodules la cartella resta vuota e la build del worker di
|
||
# visione fallisce. Il sottomodulo è su ssh://git.tielogic.xyz:222: serve una
|
||
# chiave SSH autorizzata su quel repo, sia sulla macchina di sviluppo sia sulla
|
||
# VPS che farà la build.
|
||
git clone --recurse-submodules ssh://git@git.tielogic.xyz:222/Adriano/TieMeasureFlow.git
|
||
cd TieMeasureFlow
|
||
# Chi ha già clonato senza il sottomodulo:
|
||
# git submodule update --init
|
||
|
||
# 2. Configura le variabili d'ambiente
|
||
cp .env.example .env
|
||
# Modifica .env: credenziali DB, chiavi segrete, SETUP_PASSWORD, STATION_CODE per ogni tablet
|
||
|
||
# 3. Valorizza VISION_ENGINE_VERSION PRIMA della build: il worker di visione
|
||
# (Dockerfile.vision) rifiuta di costruirsi senza — vedi "Il worker di
|
||
# visione" più sotto per il motivo. Poi avvia i servizi (ambiente di sviluppo).
|
||
VISION_ENGINE_VERSION=$(git -C vendor/visionsuite rev-parse HEAD) \
|
||
docker compose -f docker-compose.dev.yml up -d --build
|
||
|
||
# 4. Verifica lo stato dei container
|
||
docker compose -f docker-compose.dev.yml ps
|
||
|
||
# 5. Setup iniziale (solo al primo avvio)
|
||
# Apri http://localhost/api/setup nel browser
|
||
# 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:
|
||
|
||
- Frontend: http://localhost
|
||
- API: http://localhost/api
|
||
- 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).
|
||
|
||
---
|
||
|
||
## Setup Iniziale
|
||
|
||
Dopo il primo avvio, la pagina `/api/setup` (protetta da `SETUP_PASSWORD`) permette di:
|
||
|
||
- **Initialize Database** — crea tutte le tabelle
|
||
- **Create Admin User** — crea l'utente amministratore con credenziali da `.env`
|
||
- **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)
|
||
- **Gestione utenti** — crea, modifica, attiva/disattiva account dalla stessa pagina
|
||
|
||
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)
|
||
|
||
### Requisiti
|
||
|
||
- Python 3.11 o superiore
|
||
- Node.js 18 o superiore (per TailwindCSS)
|
||
- MySQL 8.0
|
||
- [uv](https://docs.astral.sh/uv/) installato
|
||
|
||
### 1. Database MySQL
|
||
|
||
```bash
|
||
mysql -u root -p <<'SQL'
|
||
CREATE DATABASE tiemeasureflow CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||
CREATE USER 'tielogic'@'localhost' IDENTIFIED BY 'your_password';
|
||
GRANT ALL PRIVILEGES ON tiemeasureflow.* TO 'tielogic'@'localhost';
|
||
FLUSH PRIVILEGES;
|
||
SQL
|
||
```
|
||
|
||
### 2. Configurazione
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
# Imposta DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, SERVER_SECRET_KEY,
|
||
# CLIENT_SECRET_KEY, SETUP_PASSWORD, STATION_CODE
|
||
```
|
||
|
||
### 3. Installa dipendenze (uv)
|
||
|
||
```bash
|
||
uv sync --extra server --extra client --extra dev
|
||
```
|
||
|
||
### 4. Backend FastAPI
|
||
|
||
```bash
|
||
uv run alembic -c src/backend/migrations/alembic.ini upgrade head
|
||
uv run uvicorn src.backend.main:app --reload --host 0.0.0.0 --port 8000
|
||
```
|
||
|
||
### 5. Frontend Flask
|
||
|
||
```bash
|
||
# 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
|
||
```
|
||
|
||
Accesso diretto (senza Nginx):
|
||
|
||
- Frontend: http://localhost:5000
|
||
- API: http://localhost:8000
|
||
|
||
---
|
||
|
||
## Ruoli Utente
|
||
|
||
I ruoli sono combinabili (array JSON per utente). Il flag `is_admin` è separato dai ruoli funzionali.
|
||
|
||
| 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 |
|
||
| **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`) |
|
||
| **Supervisor** (capoturno) | Autorizza ciò che l'operatore non può decidere da solo: una quota fuori tolleranza che deve restare, il fermo linea, la fine produzione. L'autorizzazione resta scritta sulla misura (`supervisor_id`, `authorised_at`) e finisce nel file di statistica |
|
||
| **Metrologist** | Analisi qualità: dashboard SPC (X-bar, R, Cp, Cpk, Pp, Ppk), filtri multi-dimensionali, export report PDF, analisi capability e control chart. **L'operatore non raggiunge la statistica da nessun percorso** |
|
||
| **Admin** (flag) | Gestione sistema: CRUD utenti, cambio password, attivazione/disattivazione account, **CRUD stazioni e assegnazioni ricette** |
|
||
|
||
---
|
||
|
||
## Struttura Progetto
|
||
|
||
```
|
||
TieMeasureFlow/
|
||
├── pyproject.toml # Dipendenze monorepo (uv)
|
||
├── uv.lock # Lockfile riproducibile
|
||
├── .python-version # 3.11
|
||
├── Dockerfile # Backend (uv + uvicorn)
|
||
├── Dockerfile.frontend # Frontend (uv + gunicorn + Tailwind + Babel)
|
||
├── docker-compose.dev.yml # Sviluppo (Nginx, porta 80)
|
||
├── docker-compose.yml # Produzione (Traefik, SSL)
|
||
├── nginx/ # Config Nginx (dev)
|
||
├── uploads/ # Volume Docker file caricati
|
||
├── scripts/ # Script del progetto (seed ricette di collaudo)
|
||
├── docs/ # Documentazione (vedi indice docs/README.md)
|
||
└── src/
|
||
├── backend/ # FastAPI Backend
|
||
│ ├── main.py # Entry point, lifespan, middleware, 11 router
|
||
│ ├── config.py # Settings (pydantic_settings.BaseSettings)
|
||
│ ├── database.py # SQLAlchemy 2.0 async engine
|
||
│ ├── api/
|
||
│ │ ├── routers/ # auth, users, recipes, tasks, measurements,
|
||
│ │ │ # files, settings, statistics, reports,
|
||
│ │ │ # setup, stations
|
||
│ │ └── middleware/ # api_key, rate_limit, security_headers, logging
|
||
│ ├── models/
|
||
│ │ ├── orm/ # SQLAlchemy: User, Recipe, RecipeVersion,
|
||
│ │ │ # RecipeTask, RecipeSubtask, Measurement,
|
||
│ │ │ # AccessLog, SystemSetting,
|
||
│ │ │ # RecipeVersionAudit, Station,
|
||
│ │ │ # StationRecipeAssignment,
|
||
│ │ │ # ProductionRun, ProductionEvent
|
||
│ │ └── api/ # Pydantic v2 schemas request/response
|
||
│ ├── services/ # recipe_service, measurement_service,
|
||
│ │ # spc_service, report_service,
|
||
│ │ # auth_service, station_service,
|
||
│ │ # production_service,
|
||
│ │ # production_export_service
|
||
│ ├── migrations/ # Alembic (alembic.ini + env.py)
|
||
│ ├── templates/ # Pagina setup (Jinja2)
|
||
│ └── 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 + themes.css
|
||
│ │ # (cornice pagina, temi, scrollbar)
|
||
│ ├── js/ # numpad, caliper, barcode, csv-export,
|
||
│ │ # spc-charts, annotation-editor/viewer,
|
||
│ │ # production-clock, rich-text
|
||
│ └── vendor/ # Alpine, Plotly, PDF.js (+worker), Fabric,
|
||
│ # font woff2 — vedi VERSIONS.md
|
||
├── translations/ # Flask-Babel .po/.mo IT/EN
|
||
└── tests/
|
||
```
|
||
|
||
---
|
||
|
||
## Comandi Utili
|
||
|
||
### Docker
|
||
|
||
| Comando | Descrizione |
|
||
|---|---|
|
||
| `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 |
|
||
| `APP_COMMIT=$(git rev-parse --short HEAD) docker compose build client` | Ricostruisci il client mostrando nel piè di pagina il commit (oltre alla versione di `pyproject.toml`); senza `APP_COMMIT` compare solo la versione |
|
||
| `docker compose logs -f server` | Segui log server in tempo reale |
|
||
| `docker compose logs -f client` | Segui log client in tempo reale |
|
||
| `docker compose ps` | Stato di tutti i servizi |
|
||
| `docker compose build --no-cache` | Ricostruisci immagini senza cache |
|
||
| `docker compose exec mysql mysql -u root -p tiemeasureflow` | CLI MySQL |
|
||
|
||
### Alembic (migrations)
|
||
|
||
`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
|
||
uv run alembic -c src/backend/migrations/alembic.ini upgrade head # Applica migrazioni
|
||
uv run alembic -c src/backend/migrations/alembic.ini revision --autogenerate -m "descrizione" # Genera
|
||
uv run alembic -c src/backend/migrations/alembic.ini downgrade -1 # Rollback ultima
|
||
```
|
||
|
||
Via Docker:
|
||
|
||
```bash
|
||
docker compose exec server uv run alembic -c src/backend/migrations/alembic.ini upgrade head
|
||
```
|
||
|
||
### i18n (Traduzioni)
|
||
|
||
```bash
|
||
cd src/frontend/flask_app
|
||
uv run --project ../../.. pybabel extract -F babel.cfg -k _ -o translations/messages.pot . # Estrai
|
||
uv run --project ../../.. pybabel update -i translations/messages.pot -d translations # Aggiorna
|
||
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
|
||
|
||
Il backend usa SQLite in-memory tramite `aiosqlite`: i test girano senza MySQL installato.
|
||
|
||
```bash
|
||
# Tutti i test (backend + frontend)
|
||
uv run pytest
|
||
|
||
# Solo backend
|
||
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/
|
||
```
|
||
|
||
Il modulo di visione (`src/vision/`) è un albero a parte, con un vincolo di
|
||
versione diverso dal resto del monorepo: le librerie di VisionSuite da cui
|
||
dipende richiedono Python 3.13 o superiore, mentre backend e frontend restano
|
||
su Python 3.11. Per questo motivo l'esecuzione di `uv run pytest` con
|
||
l'interprete di default mostra i test di `src/vision/tests/` come *skipped*,
|
||
non come falliti: il pacchetto viene rilevato ma la sua esecuzione reale
|
||
richiede un ambiente Python 3.13 dedicato, creato con il proprio extra `uv`.
|
||
Per eseguirli davvero:
|
||
|
||
```bash
|
||
# Prepara l'ambiente Python 3.13 con le dipendenze di visione
|
||
uv sync --extra vision --extra dev --python 3.13
|
||
|
||
# Esegue i test del runner di visione in quell'ambiente
|
||
uv run --python 3.13 --extra vision --extra dev pytest src/vision/tests
|
||
```
|
||
|
||
`src/vision_worker/tests/` eredita lo stesso vincolo su Python 3.13, perché
|
||
il worker dipende da `src.vision.runner`, ma aggiunge dipendenze proprie -
|
||
FastAPI, Uvicorn, Pillow, python-multipart - dichiarate in un extra
|
||
separato, `vision-worker`, tenuto distinto da `vision` apposta: un
|
||
consumatore che incorpora solo il runner (come un futuro agente di stazione,
|
||
che non è un servizio web) non deve trascinarsi dietro un server che non gli
|
||
serve. `vision-worker` include comunque `vision`, quindi un solo extra basta
|
||
per avere un worker funzionante:
|
||
|
||
```bash
|
||
uv run --python 3.13 --extra vision-worker --extra dev pytest src/vision_worker/tests
|
||
```
|
||
|
||
Per eseguire entrambe le suite insieme, nominando esplicitamente entrambi
|
||
gli extra:
|
||
|
||
```bash
|
||
uv run --python 3.13 --extra vision --extra vision-worker --extra dev pytest src/vision/tests src/vision_worker/tests
|
||
```
|
||
|
||
### Il worker di visione (`Dockerfile.vision`)
|
||
|
||
Il worker gira in un container separato dal server FastAPI principale, così
|
||
che l'immagine dell'API resti leggera e un aggiornamento di VisionSuite non
|
||
richieda di riavviare il traffico di produzione. Il container non pubblica
|
||
porte verso l'esterno: lo raggiunge solo il server, all'indirizzo interno
|
||
`http://vision:8100` sulla rete `tmflow-net`.
|
||
|
||
Ogni misura riporta il commit di VisionSuite che l'ha prodotta
|
||
(`engine_version`), perché una stazione che misura con un motore diverso da
|
||
quello atteso deve poter essere identificata. Il container, però, non ha
|
||
accesso al repository Git del progetto principale — `vendor/visionsuite` è un
|
||
submodule, e la copia `.git` che lo collega al repository ospitante non
|
||
viene inclusa nel contesto di build — quindi la versione non può essere
|
||
scoperta al volo dentro l'immagine. Va invece **stampata al momento del
|
||
build**, leggendo il commit dalla macchina che esegue `docker compose build`,
|
||
dove il repository Git è presente per intero:
|
||
|
||
```bash
|
||
VISION_ENGINE_VERSION=$(git -C vendor/visionsuite rev-parse HEAD) \
|
||
docker compose -f docker-compose.dev.yml build vision
|
||
```
|
||
|
||
La variabile viene passata come build argument (`ARG VISION_ENGINE_VERSION`
|
||
in `Dockerfile.vision`) e fissata nell'immagine come variabile d'ambiente, in
|
||
modo che il worker la trovi già pronta a ogni avvio senza doverla ricalcolare.
|
||
Fuori da un container, in un checkout di sviluppo locale, la stessa funzione
|
||
ricade su `git rev-parse` se la variabile non è impostata — comodo per
|
||
lavorare sul runner senza Docker. Ma se un'immagine viene costruita senza
|
||
passare `VISION_ENGINE_VERSION`, quel ripiego non ha nulla su cui appoggiarsi:
|
||
il `.git` del submodule non arriva nel contesto di build, e il worker
|
||
risponde con un errore esplicito su `/health` invece di indovinare una
|
||
versione o restituire `"unknown"`. La build va quindi sempre lanciata con la
|
||
variabile impostata, sia in sviluppo sia in produzione, con lo stesso comando
|
||
mostrato sopra (sostituendo `docker-compose.dev.yml` con `docker-compose.yml`
|
||
in produzione).
|
||
|
||
Stato corrente su `V3.0.0`, con `uv run --extra server --extra client --extra
|
||
dev pytest` su Python 3.11 e senza l'extra `vision` (un conteggio senza il
|
||
suo ambiente non dice nulla, regola che vale anche qui): **622 pass, 4
|
||
skip, nessun fallimento** — 410 in `src/backend/tests/`, 212 pass + 2 skip
|
||
in `src/frontend/flask_app/tests/` (geometria propria di
|
||
`test_layout_shell.py`, non legati alla visione), più 2 skip in
|
||
`src/vision/tests/` e `src/vision_worker/tests/` per il vincolo su Python
|
||
3.13 descritto sopra. Eseguendo questi ultimi nel loro ambiente dedicato
|
||
(comando sotto) i test collegati alla visione passano tutti: **16 pass**.
|
||
|
||
Alcuni test frontend non renderizzano niente e leggono i sorgenti, perché guardano
|
||
proprietà che sopravvivono solo se qualcuno le controlla:
|
||
|
||
| File | Cosa impedisce |
|
||
|---|---|
|
||
| `test_offline.py` | Una libreria caricata dalla rete, un worker PDF.js lasciato sul CDN, una libreria sostituita senza aggiornare l'impronta |
|
||
| `test_layout_shell.py` | Una vista che torna a dichiararsi la propria larghezza |
|
||
| `test_template_js_syntax.py` | Una traduzione con l'apostrofo dentro una stringa JS a virgolette singole, che spegne Alpine su tutta la pagina |
|
||
|
||
---
|
||
|
||
## Variabili d'Ambiente
|
||
|
||
Copia `.env.example` in `.env` e configura:
|
||
|
||
| Variabile | Descrizione |
|
||
|---|---|
|
||
| `DB_HOST`, `DB_PORT`, `DB_NAME` | Connessione MySQL |
|
||
| `DB_USER`, `DB_PASSWORD` | Credenziali utente MySQL |
|
||
| `DB_ROOT_PASSWORD` | Password root MySQL (solo Docker) |
|
||
| `SERVER_SECRET_KEY` | Chiave segreta FastAPI |
|
||
| `SERVER_CORS_ORIGINS` | Origini CORS ammesse |
|
||
| `CLIENT_SECRET_KEY` | Chiave segreta Flask (sessioni, CSRF) |
|
||
| `API_SERVER_URL` | URL del backend visto dal client (es. `http://server:8000`) |
|
||
| `STATION_CODE` | **Per-tablet** — codice stazione (es. `ST-001`). Senza, il client mostra errore configurazione. |
|
||
| `VISION_WORKER_URL` | Indirizzo interno del worker di visione (default: `http://vision:8100`, mai esposto fuori da `tmflow-net`) |
|
||
| `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) |
|
||
| `SETUP_PASSWORD` | Password pagina setup (vuota = endpoint disabilitato) |
|
||
| `SSL_CERTFILE`, `SSL_KEYFILE` | Certificato SSL (solo setup manuale) |
|
||
|
||
---
|
||
|
||
## Camera di stazione (demo fiera, #72)
|
||
|
||
Per acquisire il fotogramma da una camera locale (per esempio quella del TC bench) senza passare dall'upload, sul PC della stazione gira un piccolo servizio nativo, fuori da Docker, perché la camera GigE non si raggiunge dal container. Si avvia dalla radice del repository con l'ambiente di VisionSuite (`uv sync --all-packages` in `vendor/visionsuite`):
|
||
|
||
```bash
|
||
vendor/visionsuite/.venv/bin/python scripts/camera_service.py [--host 127.0.0.1] [--port 8765] [--folder <cartella immagini>]
|
||
```
|
||
|
||
Con `--folder` la sorgente `camera:folder` restituisce a rotazione le immagini della cartella, utile per provare senza hardware. Su Linux, perché il container `client` lo raggiunga, si usa `--host 172.17.0.1` (gateway di docker0; il compose di sviluppo mappa `host.docker.internal` su quell'host). Su Windows con Docker Desktop si presume che `host.docker.internal` raggiunga il loopback dell'host, ma va verificato sul PC della demo.
|
||
|
||
⚠️ Il servizio non ha autenticazione: un `--host` diverso dal loopback va usato solo su una rete isolata.
|
||
|
||
Nella schermata Stazioni si compilano `camera_url` (per esempio `http://host.docker.internal:8765`) e `camera_source` (`camera:<seriale>` oppure `camera:folder`). Per avere due postazioni sullo stesso PC si abilita `STATION_SWITCH_ENABLED`.
|
||
|
||
---
|
||
|
||
## Pacchetto Windows
|
||
|
||
Per i PC senza Docker (per esempio il PC della stazione in fiera) TieMeasureFlow si distribuisce come programma di installazione per Windows, `TieMeasureFlow-setup-<versione>.exe`. Funziona completamente offline, non richiede Docker né MySQL, e non contiene codice sorgente Python: il codice del prodotto è compilato con Nuitka. Il programma parte solo con una licenza valida, legata al computer.
|
||
|
||
### Installazione
|
||
|
||
1. Eseguire `TieMeasureFlow-setup-<versione>.exe` (serve un account amministratore; per una prova senza amministratore si può lanciare il setup con `/CURRENTUSER`). Per un'installazione silenziosa: `/VERYSILENT /SUPPRESSMSGBOXES /NORESTART`.
|
||
2. Dal menu Start aprire «Impronta di questo computer» e comunicare l'impronta a Tielogic. L'impronta viene salvata anche in `C:\ProgramData\Tielogic\licenze\impronta-tiemeasureflow.txt`.
|
||
3. Copiare il file di licenza ricevuto in `C:\ProgramData\Tielogic\licenze\tiemeasureflow.json`. La licenza non è inclusa nel setup e non va mai inserita nel repository.
|
||
4. Avviare «TieMeasureFlow» dal menu Start o dal desktop. Senza una licenza valida (mancante, scaduta, di un altro computer o di un altro prodotto) il programma mostra il motivo e l'impronta ed esce.
|
||
|
||
### Primo avvio
|
||
|
||
Il programma gira in un solo processo, senza finestra, e apre il browser. Ascolta solo sul computer locale (`127.0.0.1`); per raggiungere l'interfaccia da altri dispositivi della rete si avvia l'exe con `--host 0.0.0.0` (per esempio da un collegamento personalizzato).
|
||
|
||
| Componente | Indirizzo |
|
||
|---|---|
|
||
| Interfaccia web (Flask) | http://127.0.0.1:8780/ |
|
||
| Backend API (FastAPI) | http://127.0.0.1:8000/ |
|
||
| Worker di visione | http://127.0.0.1:8100/ |
|
||
| Camera della stazione | http://127.0.0.1:8765/ (`/camera/sources`) |
|
||
|
||
Al primo avvio vengono create la cartella dati `C:\ProgramData\Tielogic\TieMeasureFlow` e il file `config.env` con le chiavi segrete generate (`SERVER_SECRET_KEY`, `CLIENT_SECRET_KEY`, `SETUP_PASSWORD`, `DB_PASSWORD`); al riavvio vengono rilette, quindi le sessioni e le password restano valide. Il database è un file SQLite (`tmf.db`); le migrazioni si applicano da sole a ogni avvio.
|
||
|
||
Se il database non contiene ancora utenti, il browser si apre direttamente sulla pagina di setup `http://127.0.0.1:8000/api/setup`. La pagina chiede la `SETUP_PASSWORD`, che si legge in `config.env`, e permette di inizializzare il database e creare il primo amministratore (vedi «Setup Iniziale»). Terminato il setup si accede all'interfaccia su `http://127.0.0.1:8780/`.
|
||
|
||
Per le camere della stazione si compila nella schermata Stazioni `camera_url` = `http://127.0.0.1:8765` e `camera_source` (`camera:<seriale>` oppure `camera:folder`). Con `camera:folder` il servizio restituisce a rotazione le immagini che si copiano in `C:\ProgramData\Tielogic\TieMeasureFlow\camera`. Per le camere GigE/GenTL il driver (producer `.cti`) è quello installato dal produttore della camera.
|
||
|
||
Altre informazioni utili:
|
||
|
||
- I log sono in `C:\ProgramData\Tielogic\TieMeasureFlow\log\tiemeasureflow.log`.
|
||
- Una seconda esecuzione mentre il programma è già attivo apre soltanto il browser.
|
||
- Per fermare il programma si usa «Arresta TieMeasureFlow» nel menu Start (chiude il processo).
|
||
- La disinstallazione non cancella licenze, database, file caricati e `config.env`: sono dati del cliente.
|
||
- I report PDF usano le librerie grafiche incluse nel pacchetto e un browser Chrome o Edge già installato sul computer per i grafici.
|
||
|
||
### Come si costruisce
|
||
|
||
Il setup lo produce la CI Gitea (`.gitea/workflows/rilascio-windows.yml`) quando si crea un tag `v*`, su un runner con etichetta `windows`; il file compare come allegato della release. In locale, dalla radice del repository con il sottomodulo `vendor/visionsuite` inizializzato:
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File packaging\windows\build.ps1 -Versione 3.0.0
|
||
```
|
||
|
||
Servono `uv`, Node.js, Visual Studio con il carico C++ e Inno Setup 6. Il risultato è `dist\TieMeasureFlow-setup-3.0.0.exe`. La prima compilazione richiede decine di minuti. I test dell'avvio si lanciano con l'ambiente Python 3.13 del pacchetto: `uv run --python 3.13 --extra server --extra client --extra vision-worker --group windows --with pytest pytest packaging/windows/tests -q`.
|
||
|
||
---
|
||
|
||
## Documentazione
|
||
|
||
Indice completo: [`docs/README.md`](docs/README.md).
|
||
|
||
### Stato e direzione
|
||
|
||
| Documento | Contenuto |
|
||
|---|---|
|
||
| [`TieMeasureFlow_modifiche_2026-07-28.md`](TieMeasureFlow_modifiche_2026-07-28.md) | I quindici punti del 28/07: cosa deve cambiare, dove intervenire, decisioni in attesa del cliente (D-1…D-9) |
|
||
| [`docs/architecture/STATO_PROGETTO.md`](docs/architecture/STATO_PROGETTO.md) | Snapshot V2.0.0: cosa funziona oggi, test status, decisioni architetturali |
|
||
| [`docs/architecture/ROADMAP.md`](docs/architecture/ROADMAP.md) | Cosa resta da fare (Fasi 2-7 rev04, decisioni cliente aperte, stime) |
|
||
|
||
### 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) |
|
||
| [`docs/architecture/LAYOUT.md`](docs/architecture/LAYOUT.md) | La cornice delle pagine: perché il layout si spostava e la regola che lo tiene fermo |
|
||
| [`src/frontend/flask_app/static/vendor/VERSIONS.md`](src/frontend/flask_app/static/vendor/VERSIONS.md) | Librerie di terze parti in locale: versioni, impronte, come aggiornarle |
|
||
|
||
### 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
|
||
|
||
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.
|