Installazione¶
Requisiti¶
| Modalità | Serve |
|---|---|
| Docker (consigliata) | Docker Engine 24+ con Compose v2 |
| Senza Docker | Python 3.12, pip (oppure uv) |
| Registrazione video | Playwright (Chromium) e ffmpeg |
| Sito di documentazione | pip install zensical |
Con Docker¶
git clone <url-del-repository> spazio-comune && cd spazio-comune
cp .env.example .env # luogo, Comune, segreti: vedi "Configurazione (.env)"
docker compose up --build -d
Apri http://localhost:8000.
| Pagina | Indirizzo | Accesso |
|---|---|---|
| Cittadino | / |
libero; prenotazione con SPID/CIE (simulato) |
| Le mie prenotazioni | /mie-prenotazioni |
SPID/CIE (simulato) |
| Ufficio Sport | /ufficio |
Basic auth UFFICIO_USER / UFFICIO_PASSWORD |
| Custode (smartphone) | /custode |
Basic auth utente custode / CUSTODE_PASSWORD |
| API e documentazione OpenAPI | /api/docs |
libero; /api/v1/ufficio/* con le credenziali dell'ufficio |
| Server MCP | http://localhost:8090/mcp |
solo dalla macchina locale, vedi Server MCP |
Profili e container¶
Ogni servizio ha un nome di container univoco, così è facile riconoscerlo con docker ps e docker logs.
| Comando | Container | Porta |
|---|---|---|
docker compose up -d |
spazio-comune-app, spazio-comune-inference, spazio-comune-mcp |
8000, 127.0.0.1:8090 (inferenza solo interna) |
docker compose --profile demo up -d |
+ spazio-comune-demo |
8001, con dati fittizi |
docker compose --profile llm up -d |
+ spazio-comune-ollama, spazio-comune-ollama-pull |
solo interna |
docker compose --profile test run --rm test |
spazio-comune-test |
— |
Istanza demo con dati fittizi¶
docker compose --profile demo up --build -d # http://localhost:8001
docker compose restart demo # riparte da zero
L'istanza demo ha un orologio che parte da lunedì 9 novembre 2026 alle 18:35 (si cambia con DEMO_ORA)
e un database popolato con cittadini inventati: metà circa degli slot occupati, riserve per società sportive e
scuole, una prenotazione in attesa di pagamento allo sportello e tre ingressi per la sera, così il custode ha
qualcosa da verificare. Venerdì alle 20 è già preso, per vedere l'assistente proporre gli orari vicini.
Percorso di prova manuale
- Su
:8001scrivi all'assistente calcetto venerdì sera alle 20 e scegli un orario proposto. - Entra con SPID → Paga con pagoPA → Paga: ottieni il ticket con il QR.
- Apri
/ufficio(ufficio.sport/cambiami): la nuova prenotazione compare con il suo IUV. - Apri
/custode(custode/custode), inserisci il codice della prenotazione delle 19:00 di oggi (lo trovi in/ufficio): esito Valida, ingresso registrato. Riprova: QR già utilizzato.
Un luogo per installazione¶
Ogni installazione gestisce un solo luogo, descritto nel .env. Per un secondo luogo dello stesso Comune si
clona il repository in un'altra cartella, con il suo .env. Si cambia anche la porta pubblicata, oppure lo si
mette dietro un altro nome host del reverse proxy:
git clone https://github.com/agent-engineering-studio/spazio-comune palestra-roma && cd palestra-roma
cp .env.example .env # SPAZIO_NOME, SPAZIO_INDIRIZZO, CODICE_PREFISSO, BASE_URL…
docker compose -p palestra-roma up -d
I nomi dei container sono fissi (spazio-comune-*), quindi due copie sulla stessa macchina vanno avviate su
host diversi, oppure si cambia container_name nel docker-compose.yml della copia.
Il database¶
Il database è PostgreSQL 17 nel container spazio-comune-db (volume pgdati, solo rete interna), creato
al primo avvio con la password POSTGRES_PASSWORD del .env. La tabella slot riporta per ogni orario il
numero di prenotazione che lo occupa. Dettagli e modello dati in
Architettura.
docker exec -it spazio-comune-db psql -U spazio -d spazio_comune -c \
"SELECT inizio, stato, codice_prenotazione, cognome FROM v_occupazione WHERE codice_prenotazione IS NOT NULL ORDER BY inizio"
# backup e ripristino
docker exec spazio-comune-db pg_dump -U spazio -Fc spazio_comune > spazio-comune-$(date +%F).dump
docker exec -i spazio-comune-db pg_restore -U spazio -d spazio_comune --clean < spazio-comune-AAAA-MM-GG.dump
Con un PostgreSQL gestito basta impostare DATABASE_URL nel .env: l'app crea da sola tabelle, vincoli e
vista al primo avvio.
Comandi utili¶
docker compose ps # stato dei container
docker logs -f spazio-comune-app # log dell'app
docker compose down # ferma tutto (i dati restano nel volume "pgdati")
docker compose down -v # ferma e cancella anche i dati
Senza Docker¶
python3.12 -m venv .venv && . .venv/bin/activate
pip install -r app/requirements.txt -r inference/requirements.txt -r mcp_server/requirements.txt pytest
python app/scripts/fetch_bootstrap_italia.py # solo se manca app/prenota/static/bootstrap-italia
# un PostgreSQL 17 raggiungibile (anche solo il container: docker compose up -d db, con una porta pubblicata)
export DATABASE_URL=postgresql://spazio:<password>@localhost:5432/spazio_comune
# server di inferenza
(cd inference && uvicorn server:app --port 8080) &
# app
(cd app && INFERENCE_URL=http://127.0.0.1:8080 uvicorn --factory prenota.main:create_app --port 8000)
Istanza demo in locale: INFERENCE_URL=http://127.0.0.1:8080 python app/scripts/demo.py (usa lo schema demo).
Test¶
La suite copre:
- il dominio: doppia prenotazione impossibile, scadenza dell'attesa, pagamento tardivo, annullamento entro 24 ore, check-in;
- il luogo da
.enve la tabellaslot: numero di prenotazione registrato, liberato e ripreso, 40 prenotazioni simultanee sullo stesso slot, migrazione dalla prima versione; - il web: flusso completo, CSRF, open redirect, accesso al ticket, slot impegnati visibili, griglia dell'ufficio;
- l'inferenza: 11 casi di intenti in italiano, FAQ con fonte, redazione dei dati personali, fallback dell'LLM;
- il server MCP: tutti gli strumenti contro l'app reale, tramite il protocollo MCP.
Simulazione¶
app/scripts/simula.py fa prenotare in parallelo dei cittadini virtuali attraverso il sito vero: SPID
simulato, scelta dello slot, pagamento pagoPA simulato. Metà di loro punta di proposito agli stessi tre orari.
Alla fine lo script controlla sul database, tramite l'API dell'ufficio, che ogni slot occupato porti un solo
numero di prenotazione.
docker compose --profile demo up -d && docker compose restart demo
python app/scripts/simula.py --url http://localhost:8001 --cittadini 30
30 cittadini, 17 slot liberi, 3 slot contesi da metà dei cittadini
Prenotazioni confermate: 10 rifiutate (slot già preso): 20
Slot contesi: chi ha vinto
slot richieste numero di prenotazione registrato
2026-11-13T15:00:00+01:00 6 GDC-MWX5-RLU6 (1 confermata)
2026-11-11T18:00:00+01:00 5 GDC-EWDX-EC6M (1 confermata)
2026-11-12T17:00:00+01:00 7 GDC-CU7C-VB58 (1 confermata)
OK: 10 prenotazioni, ognuna registrata sul proprio slot; nessuno slot venduto due volte.
Pubblicare questa documentazione¶
Il sito è generato con Zensical da zensical.toml e dalla cartella docs/.
pip install zensical
zensical serve -a 127.0.0.1:8002 # anteprima (la 8000 è occupata dall'app)
zensical build # genera ./site
Su GitHub il workflow .github/workflows/docs.yml pubblica il sito a ogni push su main.
Per un fork basta attivarlo una volta (Settings › Pages › Build and deployment › Source: GitHub Actions)
e aggiornare site_url e repo_url in zensical.toml.