Web app per le finanze personali. Carichi l'estratto conto (CSV, Excel o PDF) e Finny lo legge dentro il browser: categorie di spesa, abbonamenti, budget, previsioni e consigli della "vocina", ognuno con la sua fonte.
Non c'è un server: i movimenti restano nel database locale del browser (IndexedDB). L'unica eccezione è l'assistente AI, facoltativo e spento di default.
npm install
npm run dev # http://localhost:5173
npm test # test di parser, categorie e analisi
npm run build # build statica in dist/, pubblicabile su qualsiasi hostingnetlify.toml imposta la build (npm run build, cartella dist, Node 22). Gli header di sicurezza stanno in public/_headers, che finisce dentro dist: valgono quindi con entrambi i metodi. La Content Security Policy impedisce alla pagina di caricare script esterni e di inviare dati a qualsiasi server (connect-src 'self').
Con GitHub (consigliato, si aggiorna a ogni push): su Netlify, Add new site → Import an existing project → GitHub, scegli il repository e il branch da pubblicare. Build command e publish directory vengono letti da netlify.toml.
Senza GitHub: npm install && npm run build, poi trascina la cartella dist su app.netlify.com/drop.
| Sezione | Contenuto |
|---|---|
| Panoramica | Saldo totale (se l'estratto lo riporta), entrate/uscite/risparmio del mese con variazione, i consigli della vocina, flusso di cassa su 12 mesi, categorie, prossimi addebiti, ultimi movimenti |
| Movimenti | Ricerca, filtri per tipo/conto/categoria, dettaglio con causale originale, ricategorizzazione con regola ("applica a tutti i movimenti di…") |
| Analisi | Medie 3/6/12 mesi, ritmo di spesa del mese contro la media, ripartizione 50/30/20, variazione per categoria, giorno della settimana più caro, esercenti principali, tutti i consigli |
| Ricorrenti | Abbonamenti, spese fisse e risparmio automatico rilevati da soli, calendario dei prossimi 30 giorni, rincari |
| Budget | Budget mensili per categoria con anello di avanzamento, ritmo ideale del mese, proposte automatiche dalla media degli ultimi 3 mesi |
| Importa | Drag & drop, rilevamento automatico delle colonne (modificabile), anteprima, deduplica sui reimport |
| Collega conto | Spiega cosa richiede davvero l'open banking PSD2, con un diagramma e una simulazione |
| Chiedi | Assistente AI facoltativo (OpenAI o modello locale) che risponde usando i tuoi numeri e mostra quali dati ha inviato |
| Impostazioni | Tema, assistente AI, backup/ripristino JSON, conti, regole apprese, cancellazione dati |
- CSV: separatore
;,tab o|, codifica UTF-8 o Windows-1252, righe di preambolo prima dell'intestazione, importo unico con segno oppure colonne Dare/Avere (Addebiti/Accrediti, Uscite/Entrate), dategg/mm/aaaa, ISO,gg mese aaaa. Gli export in stile Revolut (colonneStateeFee) sono gestiti. - Excel
.xlsx: stesse regole del CSV. Il vecchio formato.xlsnon è supportato: salvalo come.xlsxo CSV. - PDF (beta): solo PDF con testo selezionabile (non scansioni). Finny riconosce tre impaginazioni:
- una riga per movimento con la sua data, in qualunque colonna (anche solo sulla prima riga del giorno);
- movimenti raggruppati sotto il giorno, come nelle app;
- coppie etichetta/valore ("Data … Importo …"). Gestisce pagine ruotate, testo scritto lettera per lettera, stampe monospazio, date senza anno o in formato ISO, importi con segno, con D/C o in colonne separate. Il segno viene, in ordine di affidabilità:
- dalle colonne Dare/Avere;
- dalla colonna del saldo (ogni saldo meno il precedente);
- dal segno scritto;
- in ultima istanza, dalle parole della causale.
Se l'estratto stampa i saldi, l'anteprima dice se saldo iniziale + movimenti = saldo finale. I test in
src/lib/parse/pdf.test.tsgenerano PDF veri per ciascun caso e li leggono con pdf.js.
- Scansioni e foto (OCR, beta): le pagine di un PDF senza testo e le foto JPG/PNG/WebP vengono lette con il riconoscimento ottico (Tesseract via tesseract.js), nel browser: l'immagine non lascia il dispositivo. Motore e modello per l'italiano (~5 MB) sono serviti dal sito stesso (
dist/ocr/, copiati danode_modulesin build) e scaricati solo la prima volta che serve. Ci vogliono alcuni secondi per pagina; un errore su una cifra è possibile, per questo l'anteprima mostra l'affidabilità e il controllo dei saldi. La CSP include'wasm-unsafe-eval', necessario a compilare WebAssembly (non abilitaeval).
src/
lib/
parse/ values.ts (date, importi) · table.ts (intestazione e colonne) · readers.ts (CSV/XLSX) · pdf.ts · testPdf.ts (PDF di prova)
categorize.ts dizionario di esercenti italiani, pulizia dei nomi, regole utente
analytics.ts statistiche mensili, categorie, 50/30/20, ricorrenti, previsione
insights.ts i consigli della vocina, con fonte dove serve
demo.ts 12 mesi di dati di esempio, generati in modo deterministico
store/ stato (zustand) + persistenza IndexedDB
components/ shell, grafici SVG animati, vocina, primitive UI
pages/ una vista per sezione, caricate on demand
Stack: Vite, React 19, TypeScript, Tailwind CSS 4, Motion, pdf.js, tesseract.js, read-excel-file, Papa Parse.
- Tema scuro "inchiostro" con accento ottone, tema chiaro e automatico. Tutti i colori sono token CSS in
src/index.css. - La palette dei grafici (verde acqua, rame, viola) è validata per daltonismo e contrasto su entrambe le superfici.
- Ogni grafico ha una vista "Tabella" equivalente. Legenda sempre presente con due o più serie, tooltip al passaggio del mouse e da tastiera.
- Animazioni: intro del marchio, transizioni tra pagine, numeri che contano, grafici che si disegnano, bordi che si illuminano sotto il puntatore, tilt 3D, pulsanti magnetici, palette comandi
⌘K. Tutto si riduce da solo con "Riduci movimento" attivo nel sistema.
Sulla Panoramica, con l'AI attiva, la vocina scrive il punto del mese: una sintesi e da 3 a 5 consigli con il risparmio stimato e il collegamento alla sezione giusta. I numeri della dashboard restano calcolati da Finny: il modello riceve solo cifre già calcolate (riepilogo mensile, categorie, esercenti principali, ricorrenti, budget, saldo, segnali) e le interpreta. Il punto si rigenera solo quando cambiano i dati o premi Aggiorna, ed è salvato nel browser. Dopo ogni import, l'AI propone da sola le categorie per gli esercenti in "Altro"; le proposte incoerenti con il verso del movimento vengono scartate dal codice.
La pagina Chiedi risponde a domande libere sui tuoi movimenti ("quanto spendo per mangiare fuori?", "fammi il report del mese", "dove posso risparmiare 200 €?"). In Movimenti, inoltre, un pulsante propone una categoria per gli esercenti finiti in "Altro".
Dove sta la chiave (scegli in Impostazioni → Assistente AI):
- Chiave su Netlify (predefinita). Su Netlify, Site configuration → Environment variables, aggiungi
OPENAI_API_KEYe rifai il deploy. La funzionenetlify/functions/ai.mjs, esposta su/api/ai/*, aggiunge la chiave lato server: il browser non la vede mai. La funzione inoltra soloGET /modelsePOST /chat/completions, accetta solo richieste dal sito stesso e limita la dimensione delle richieste. Se imposti ancheFINNY_PASSPHRASE, ogni richiesta deve portarla (la inserisci nell'app). Facoltative:OPENAI_BASE_URLper un servizio compatibile diverso da OpenAI. - Chiave nel browser. Utile in locale o con un modello su
localhost: la chiave resta nel database del browser e non entra nel backup. - Mai in una variabile
VITE_*: Vite la inserirebbe nel JavaScript pubblico del sito.
Il controllo sull'origine blocca altri siti web, ma non chi chiama la funzione a mano falsificando l'header Origin. La protezione con password del sito Netlify, a quanto risulta, non copre le Functions: se l'indirizzo del sito è noto ad altri, imposta FINNY_PASSPHRASE e un limite di spesa su OpenAI.
Come lavora il modello:
- Il modello non riceve la lista dei movimenti. Chiede a Finny i dati tramite funzioni che girano nel browser (
src/lib/ai/tools.ts): riepilogo mensile, spese per categoria o esercente, movimenti filtrati (max 50), ricorrenti, budget, saldo. Causali originali, IBAN e nomi dei conti non escono mai. Sotto ogni risposta c'è l'elenco esatto dei dati inviati. - Per ricategorizzare vengono inviati solo i nomi degli esercenti e se si tratta di entrata o uscita.
- Opzione "Solo le categorie": al posto dei nomi degli esercenti viene inviata la categoria.
- In alternativa si può usare un modello locale compatibile con l'API OpenAI (per esempio Ollama su
http://localhost:11434/v1): così i dati non escono dal computer. - La CSP consente connessioni solo verso il sito stesso,
api.openai.comelocalhost:11434.
Privacy: la connessione è cifrata (HTTPS), ma il fornitore del modello legge i dati in chiaro per elaborarli. Secondo OpenAI, i dati inviati via API non sono usati per l'addestramento e restano nei log fino a 30 giorni per il controllo degli abusi (Data controls in the OpenAI platform).
L'assistente non funziona nell'anteprima su claude.ai, dove le chiamate verso l'esterno sono bloccate; funziona su Netlify o in locale.
Il collegamento automatico non è attivo e l'app lo dice chiaramente. Per leggere i conti di altre persone in UE serve un prestatore di servizi di informazione sui conti (AISP) autorizzato ai sensi della PSD2, oppure un contratto con un fornitore che lo è. Serve anche un piccolo backend, perché le chiavi API del fornitore non possono stare nel browser. Il consenso dell'utente va rinnovato ogni 180 giorni.
Per attivarlo servono:
- un contratto con un fornitore AISP europeo;
- una funzione serverless che crei la sessione di consenso e scarichi i movimenti;
- un adattatore che trasformi la risposta nel tipo
DraftTx(src/lib/parse/table.ts) e la passi aimportDraftsdello store. Il resto dell'app (categorie, analisi, deduplica) funziona già così.
Nota: GoCardless Bank Account Data (ex Nordigen), l'opzione gratuita più usata dagli sviluppatori indipendenti, non accetta nuove iscrizioni da luglio 2025.
- Regola 50/30/20: E. Warren, A. Warren Tyagi, All Your Worth: The Ultimate Lifetime Money Plan, 2005.
- Fondo emergenza: Banca d'Italia, portale Economia per tutti, "Risparmiare e pianificare: l'importanza della prevenzione…".
- PSD2: Direttiva (UE) 2015/2366.
- Rinnovo del consenso a 180 giorni: Regolamento delegato (UE) 2022/2360.
- GoCardless Bank Account Data, nuove iscrizioni sospese: bankaccountdata.gocardless.com/new-signups-disabled.
- La categorizzazione è a regole e parole chiave: copre bene i grandi esercenti italiani, meno i negozi locali. Le correzioni diventano regole e valgono per i prossimi import.
- I movimenti in valuta estera vengono importati con il loro importo, senza conversione.
- I PDF hanno impaginazioni diverse per ogni banca: se l'anteprima non torna, CSV ed Excel sono più affidabili.
- I dati vivono in un solo browser. Per usarli su un altro dispositivo serve il backup JSON.