Skip to the content.

@amuseapp/caa-generator

Generatore di pittogrammi CAA e di testo facile da leggere per il patrimonio culturale. Nato per gli itinerari accessibili di amuseapp, gira con una sola chiave Google.

English version

           
castello torre edificio piazza statua amare
castello torre edificio piazza statua amare

Sei pittogrammi del set amuseapp (300 px), con licenza CC BY 4.0: vedi Licenza.

Che cos’è

Due strumenti che lavorano insieme sui contenuti per chi ha difficoltà di lettura:

  1. Un generatore di pittogrammi. Dato un concetto (per esempio “fossato”) e la frase in cui compare, descrive il disegno, lo genera in uno stile unico, lo fa passare da un cancello automatico di qualità e lo salva in una libreria locale con tutti i dati del controllo.
  2. Un kit per il testo facile. Il prompt di semplificazione dei testi museali, il glossario di itinerario, un controllo meccanico delle regole di scrittura (L1-L12, con il contatore delle subordinate L13) e la politica dei simboli (quali parole non ricevono mai un pittogramma).

Non è un’app: è una libreria TypeScript con una riga di comando (caa-gen). La persistenza, il modello di testo, quello di immagini e quello di visione sono interfacce: puoi sostituirle con le tue.

Quello che non è incluso: il simbolizzatore end to end (la scelta dei pittogrammi per le parole chiave di un intero itinerario) e la pubblicazione su una webapp o su un database. Il kit espone i pezzi (simplifyPoiText, lintSymbols, shouldSymbolize, ensurePictogram) ma non un comando che li orchestra. Vedi anche i limiti noti.

Perché

Come funziona

lemma + frase di contesto
        |
        v
  descrizione del concetto  (TextModel)
  classe, descrizione del disegno, alias, parole accettate, distrattori, parole vicine
        |
        v
  immagine nello stile unico "A3"  (ImageModel)
        |
        v
  cancello automatico  (VisionModel)
  ritaglio della cornice, vincoli, riconoscimento a 72 px
        |
   passa? ---- no ----> una sola rigenerazione con descrizione irrigidita ---- no ----> bozza, mai usata
        |
       si
        v
  PNG 300 e 72 px + manifest nello store (FileStore, MemoryStore o il tuo)

La descrizione del concetto

Il modello di testo riceve il lemma e la frase in cui compare (il simbolo rappresenta il concetto che la parola ha in quella frase, non la parola isolata) e restituisce, validato con zod:

Lo stile unico

Lo stile a3-2026-10 è un testo fisso passato a ogni generazione: pittogramma piatto con contorno nero uniforme di spessore medio, forme geometriche, riempimenti piatti, una tavolozza terrosa (terracotta, ocra, verde oliva, azzurro, crema, grigi) con la regola che il colore reale del materiale vince quando identifica il concetto (oro delle monete, bianco del marmo), stile da segnaletica per adulti, un solo soggetto isolato, nessuna figura umana salvo che il concetto sia una persona, un ruolo o un’azione. I vincoli fissi sono: quadrato, sfondo bianco pieno, soggetto all’80 per cento dell’area, nessun testo, nessuna ombra, nessuna cornice, leggibile a 72 px. Il testo è in src/pictogram/style.ts. Se lo cambi, cambia anche l’identificatore: la libreria registra con quale stile è stata generata ogni riga.

Il cancello automatico, senza approvazione umana

È la scelta di progetto più importante: nessuna persona approva i pittogrammi prima dell’uso, quindi il cancello è l’unico arbitro e deve essere severo. Una riga diventa approvato solo se:

  1. Ritaglio della cornice. Il modello di immagini disegna talvolta una cornice nonostante il divieto (in prove interne di amuseapp circa 4 immagini su 10 con il modello Flash; dato non pubblicato). Un rilevatore ad anelli la trova, la ritaglia senza toccare il soggetto e riporta l’immagine a un quadrato bianco con margine uniforme. Se resta una cornice, anche sottile o grigio chiara, l’immagine è scartata.
  2. Vincoli. Immagine quadrata, sfondo bianco sul bordo, nessun testo, lettera o numero (lo verifica il modello di visione).
  3. Riconoscimento a 72 px, in due passi:
    • parola libera (trasparenza): al modello di visione si mostra il disegno ridotto a 72 px e si chiede una sola parola; deve essere uguale (dopo normalizzazione, mai per contenimento, mai per iperonimo) a una delle parole accettate;
    • scelta multipla (traslucenza): solo se la parola libera cade nel campo del concetto (è tra le parole vicine) e non coincide con un distrattore, si chiede di scegliere tra il lemma, i quattro distrattori e “nessuna di queste”. Passa solo se sceglie il lemma. Se la risposta libera coincide con un distrattore (per esempio “uscire” per “entrare”) il disegno è respinto subito: potrebbe mostrare il concetto opposto.

Se il cancello fallisce si rigenera una sola volta con una descrizione irrigidita (“il disegno è stato letto come X invece di Y”). Se fallisce ancora la riga resta bozza e nessun consumatore la usa: meglio nessun simbolo di un simbolo sbagliato. Non allargare le parole accettate per far passare più immagini: è l’errore che il cancello esiste per impedire.

Il tetto di spesa (SpendMeter) conta ogni generazione e può avere una scadenza: nella CLI il default è 8 immagini per comando (CAA_MAX_GENERATIONS). Le generazioni passano da una coda a singolo slot, perché con più richieste in parallelo la chiave Google riceve 429.

La convenzione ARASAAC, facoltativa

Con --arasaac-hint il generatore chiede a ARASAAC come rappresenta il concetto (quali elementi, quale posa, quali segni come frecce o linee di movimento) e passa solo la frase al modello di testo. ARASAAC è usata come fonte di convenzioni, non di disegni: il PNG scaricato serve a farne scrivere la frase, non viene salvato né dato al generatore di immagini.

I criteri della letteratura che rispetta

Le regole vengono da due fonti principali: le regole europee per l’informazione facile da leggere di Inclusion Europe (“Informazioni per tutti”, edizione italiana 2012, con Anffas) e la guida Widgit al buon contenuto con simboli (“Guide to Good Symbol Content”, 2021). ARASAAC (Governo d’Aragona) è fonte di convenzioni compositive, non di disegni.

Simboli (S1-S10)

Criterio Dove vive nel codice
S1 il simbolo rappresenta il concetto che la parola ha in quella frase describeConcept riceve sempre la frase come contesto
S2 simboli il più possibile trasparenti cancello: parola libera a 72 px (trasparenza), poi scelta multipla (traslucenza) solo nel campo del concetto; l’opaco è respinto
S3 stesso concetto, stesso simbolo; coppie per gli opposti una riga di libreria per lemma, con alias; i distrattori includono il contrario; lintSymbols segnala lo stesso lemma con più simboli e lo stesso simbolo per lemmi non sinonimi
S4 pochi simboli per frase, solo parole portanti massimo 3 per frase; parole generiche, numeri, qualificatori e frasi negative esclusi (shouldSymbolize)
S5 prova “copri il testo” per simbolo, è il riconoscimento a 72 px; per frase resta la revisione umana
S6 sequenza che segue la frase withOffsets ordina i simboli come le parole
S7 concetto vicino con la parola giusta sotto, senza cambiare il senso alias solo nella stessa famiglia, iperonimi generici vietati
S8 niente qualificatori di plurale o tempo un solo soggetto, nessun elemento aggiunto
S9 adatto agli adulti, chiaro, una cosa sola da guardare stile da segnaletica per adulti, soggetto isolato, nessuna scena, cornici ritagliate, niente testo
S10 nomi propri senza simbolo collectProperNames e lintSymbols (C5)

La politica dei simboli (src/text/policy/symbols.ts) elenca anche i controlli C1-C10 di lintSymbols: stesso lemma con più simboli, stesso simbolo per lemmi diversi, nomi propri, qualificatori astratti, frasi negative con simboli, ripetizioni.

Testo facile (L1-L13)

caa-gen lint-text e lintEasyText controllano a macchina un testo italiano. Gli errori (fail) provocherebbero un nuovo tentativo di generazione, gli avvisi (warn) si riportano soltanto.

Regola Cosa controlla
L1 lunghezza della frase: avviso oltre 12 parole, errore oltre 15, errore da 13 se c’è una subordinata
L2 numeri: niente numeri romani (“Ercole II”), migliaia con il punto, numeri grandi (gli anni restano), percentuali
L3 forma passiva e impersonale (“è stato costruito”, “si vede”)
L4 negazioni a inizio frase e due frasi di fila con “non”
L5 congiuntivo, condizionale, passato remoto (errori), gerundio a inizio frase (avviso)
L6 parole difficili non spiegate; termini del glossario alla prima comparsa senza “cioè”
L7 metafore e modi di dire (lista)
L8 sinonimi per lo stesso concetto (castello e fortezza, percorso e itinerario)
L9 pronomi a inizio frase con più di un possibile antecedente
L10 nessuna frase che si rivolge al lettore; voce plurale (“noi”, “vi”) vietata
L11 fedeltà all’originale: nomi propri, anni e confronti non devono andare persi
L12 struttura: la prima frase nomina il luogo, la prima tappa invita a cominciare, l’ultima chiude il percorso
L13 countSubordinates: il contatore delle subordinate usato da L1

Il prompt di semplificazione (buildSimplifyPrompt) incorpora le stesse regole: una sola idea per frase, al massimo 12 parole, presente o passato prossimo, mai passivo, parole comuni con “cioè” per i termini difficili, stessa parola per lo stesso concetto, niente metafore, informazioni positive prima delle negazioni, anni in cifre e niente numeri romani, solo ciò che dice il testo sorgente.

I limiti noti

Eseguirlo con la tua chiave Google

Serve Node 20.12 o superiore (per eseguire i test serve Node 22.12 o superiore) e una chiave di Google AI Studio. Una sola chiave copre testo, immagini e riconoscimento.

git clone https://github.com/amuseapp-labs/caa-generator
cd caa-generator
npm install
cp .env.example .env        # scrivi la tua chiave in GOOGLE_API_KEY
npm run build
npx caa-gen --help

Genera un pittogramma:

npm run caa-gen -- pictogram fossato --context "Il fossato circonda il castello." --out ./library

Con la convenzione compositiva ARASAAC (facoltativa):

npm run caa-gen -- pictogram entrare --context "Entra dal portone." --arasaac-hint

Il comando scrive in ./library il manifest library.json e i file {id}_300.png e {id}_72.png, poi stampa l’esito: Approvato (exit 0), Bozza, non approvata dal cancello (exit 2) oppure un errore (exit 1). Un lemma già approvato non costa nulla: viene ritrovato in libreria, anche per alias.

Guarda la libreria:

npm run caa-gen -- library list --out ./library
npm run caa-gen -- library sheet --out ./library        # foglio di contatto, serve ImageMagick; se manca, lo salta

Controlla un testo facile italiano (una frase per riga o separate dalla punteggiatura). Il file examples/testo-facile-ferrara.txt contiene sei frasi facili su un castello, con un errore voluto: l’ultima usa il passato remoto (“fu costruito”), vietato dalla regola L5. Il comando lo trova e termina con exit 1:

npm run caa-gen -- lint-text examples/testo-facile-ferrara.txt --place-name "Il Castello Estense"
# ERRORE  L5  frase 6: Passato remoto "fu": usa il presente o il passato prossimo.
# 6 frasi, 1 errori, 0 avvisi

Con “è stato costruito” al posto di “fu costruito” l’errore sparisce e resta un avviso sulla forma passiva (L3).

npm run build compila in dist/: npx caa-gen (dalla cartella del progetto) esegue la versione compilata, che è anche il comando bin installato da npm. Come libreria:

import { ensurePictogram, FileStore, GeminiTextModel, GeminiImageModel, GeminiVisionModel, SpendMeter } from '@amuseapp/caa-generator'

const result = await ensurePictogram({
  lemma: 'fossato',
  context: 'Il fossato circonda il castello.',
  languageCode: 'it',
  meter: new SpendMeter(8),
  store: new FileStore('./library'),
  text: new GeminiTextModel(),     // gemini-2.5-flash
  image: new GeminiImageModel(),   // gemini-2.5-flash-image
  vision: new GeminiVisionModel(), // gemini-2.5-flash
})
// result.status: 'approved' | 'draft' | 'exhausted' | 'error' | 'skipped'

Variabili d’ambiente: GOOGLE_API_KEY (o GOOGLE_GENAI_API_KEY), e facoltativamente CAA_TEXT_MODEL, CAA_IMAGE_MODEL, CAA_MAX_GENERATIONS. Nessuna chiave è scritta nel codice: .env è ignorato da git.

Per portarlo su un database o un bucket implementa PictogramStore (lookup, create, update, list, putImages); per usare un altro provider implementa TextModel, ImageModel o VisionModel.

Come contribuire

Licenza