# 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 git clone ssh://git@git.tielogic.xyz:222/Adriano/TieMeasureFlow.git cd TieMeasureFlow # 2. Configura le variabili d'ambiente cp .env.example .env # Modifica .env: credenziali DB, chiavi segrete, SETUP_PASSWORD, STATION_CODE per ogni tablet # 3. Avvia i servizi (ambiente di sviluppo) 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 | | `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 # core (entrambi) uv add --optional server # solo backend uv add --optional client # solo frontend uv add --optional dev # 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 ``` Lo stesso vale per `src/vision_worker/tests/`, il piccolo servizio FastAPI che espone il runner: dipende da `src.vision.runner` e quindi eredita lo stesso vincolo su Python 3.13. ```bash uv run --python 3.13 --extra vision --extra dev pytest 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`: **360 pass, 0 fail** (212 backend + 148 frontend). 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) | --- ## 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.