dimesum

Home / Blog / Ingegneria

Ingegneria

Abbiamo cancellato il nostro parser di spese

· 9 min di lettura ·

Il nostro parser a regole è durato una settimana, poi quattro frasi comuni hanno addebitato il denaro alla persona sbagliata e una regola di rifiuto ha sostituito l'intera pila di protezioni.

Abbiamo cancellato un parser che avevamo appena finito di scrivere. Il primo tentativo di Dimesum di interpretare le spese in linguaggio naturale era un motore a regole, e quattro frasi comuni lo hanno mandato in pensione: dinner 12-08 400 veniva letto come €12, ravioli 600 inseriva nella divisione un membro di nome Ravindra, kirana 500 addebitava la spesa a un membro di nome Kiran, e refund -483.50 diventava un addebito di €483.50.

Comprendere una frase è compito del modello e di nessun altro. Al posto del motore a regole c'è un solo gradino chiamato amount_only: un singolo numero, restituito solo quando il testo contiene esattamente un candidato monetario non ambiguo, e mai un'affermazione sulle persone. Due candidati significano nessun importo. Una sola regola ha sostituito una pila crescente di protezioni.

Quattro frasi hanno chiuso il parser a regole

Il parser cancellato copriva l'intero insieme di entità specificato dal nostro documento di design AI: corrispondenza di membri e soprannomi, un lessico di esclusioni, inferenza del pagante, un lessico di categorie, e livelli di confidenza calibrati campo per campo. Ognuno dei quattro errori qui sotto aveva una correzione ovvia. Ogni correzione era una nuova protezione con il proprio punto cieco.

I quattro errori che hanno mandato in pensione il parser a regole di Dimesum, registrati il 2026-08-20 in docs/tech/10-ai-design.md.
Cosa ha scritto l'utenteCosa ha fatto il parserPerché è successo
dinner 12-08 400Ha letto l'importo come €12Una persona vede una data e un totale. Una regex vede tre numeri e prende il primo.
ravioli 600Ha inserito nella divisione un membro di nome RavindraLa corrispondenza approssimativa dei soprannomi ha valutato un piatto come una persona.
kirana 500Ha addebitato la spesa a un membro di nome KiranLo stesso confronto, questa volta con il nome di un negozio.
refund -483.50Ha registrato un addebito di €483.50Le cifre sono sopravvissute e il segno no, così il denaro puntava nella direzione opposta.

Due dei quattro sono lo stesso difetto in abiti diversi. La corrispondenza approssimativa non sa distinguere un piatto da una persona o un negozio da una persona, perché a livello di caratteri ravioli e Ravindra si assomigliano davvero. Se pretendi una corrispondenza di prefisso più lunga, rompi ravi, che è proprio il caso per cui il confronto esiste.

Ogni protezione che aggiungi fa emergere altre due formulazioni

Un parser a regole fallisce in una forma precisa: risponde con sicurezza e in modo sbagliato. Un campo vuoto costa all'utente un solo tocco. Una divisione sbagliata costa la fiducia nel registro, e in un'app per la condivisione delle spese il registro è il prodotto. Le quattro righe qui sopra non sono errori sfiorati, sono errori sul denaro.

Il vero argomento è la ruota che gira all'infinito, non un singolo difetto. Aggiungi una protezione per le date e arriva il caso del numero d'ordine. Aggiungi una protezione per il numero d'ordine e arrivano i numeri di appartamento, poi le quantità, poi i numeri dei tavoli. L'elenco delle protezioni cresce e non converge mai, perché il linguaggio naturale non ha un insieme finito di formulazioni da elencare.

Prima e dopo: una pila crescente di protezioni, sostituita da una sola regola di rifiuto PRIMA: PARSER A REGOLE (CANCELLATO) dinner 12-08 400 regex dell'importo protezione data protezione numero d'ordine confronto soprannomi lessico di esclusioni legge €12, e la registra Ogni correzione ha fatto emergere altre due formulazioni. DOPO: amount_only dinner 12-08 400 esattamente un candidato monetario non ambiguo? no, tre numeri l'importo resta vuoto sì, una cifra restituita in centesimi Il gradino non dice nulla sulle persone, quindi non può addebitare il denaro alla persona sbagliata.
Il parser cancellato rispondeva a ogni frase. Il gradino che lo ha sostituito risponde con un numero o con nulla.
La decisione, con data

L'inserimento in linguaggio naturale è una funzione AI, e nulla nel repository di Dimesum la riconosce per analogia di pattern. La decisione è arrivata il 2026-08-20, messa per iscritto insieme ai quattro errori, così nessuno ricostruisce le protezioni per sbaglio.

Ciò che è stato rilasciato è un numero e un rifiuto

amount_only è il gradino degradato del nostro documento di design AI implementato alla lettera. Quel gradino recita "il modulo semplice con precompilazione dell'importo tramite regex lato client", quindi il livello restituisce un importo e nient'altro: nessuna descrizione, nessuna categoria, nessun pagante, nessun partecipante, nessuna esclusione. Non costa nulla e non chiama nessuno, ed è per questo che test e CI girano su di esso.

L'ambiguità è un rifiuto, non uno spareggio

L'intera regola vive in una sola funzione, extract_amount_minor. Una cifra torna solo quando il testo ne contiene esattamente un candidato, quindi order 90210 dinner 400 e flat 402 rent 15000 restituiscono un campo vuoto invece di scegliere un vincitore. Una cifra con simbolo di valuta conta come non ambigua anche accanto a numeri nudi, ed è per questo che split 3 ways €1,200 legge comunque €1,200.

Una cifra negata non è affatto un importo, non il suo valore assoluto. -500, minus 200 e il (500) dei contabili tornano tutti vuoti, perché il campo è un addebito e tenere le cifre lasciando cadere il segno fa puntare il denaro nella direzione opposta al testo. Le parentesi contano solo quando si chiudono sulla cifra stessa, quindi (500 each) resta un inciso.

Come amount_only sceglie tra una cifra e nessuna risposta raccogli ogni cifra che potrebbe essere denaro nessuna trovata, o una di esse con segno negativo? no esattamente una cifra con simbolo di valuta, come €1,200 o 500/-? no nessun simbolo, ed esattamente un solo numero nudo? amount_minor: unità minori intere, tramite l'esponente ISO 4217 no nessun importo il campo appare vuoto
Tre domande, due delle quali finiscono in un campo vuoto. Scegliere tra due importi plausibili ha prodotto il dinner da €12.

La valuta decide l'aritmetica

Le unità minori sono l'unica rappresentazione che il denaro assume in Dimesum, quindi il livello converte con una tabella di esponenti ISO 4217 invece che con una moltiplicazione per 100 scritta a mano. Lo yen giapponese non ha alcuna sotto-unità, e il ×100 gonfia una ricevuta da ¥1,200 di cento volte. Una cifra più fine dell'unità più piccola della valuta viene rifiutata invece che arrotondata, perché arrotondare un importo che qualcuno ha digitato significa inventarne uno.

Le parole numeriche indiane fanno parte della scrittura di una cifra, non della comprensione di una frase. 1.2k, 2 lakh e 500/- si risolvono tutte, come 1,200. Qualsiasi valore oltre l'importo massimo del registro torna vuoto, così una cifra digitata male svuota un campo invece di mandare in overflow un intero più a valle.

Cosa a ogni gradino di parsing è consentito affermare, al 29 agosto 2026.
CampoParser a regole (cancellato)amount_only (attivo)Livello a modello (collegato, senza chiave)
ImportoIndovinato tra più numeriUna cifra non ambigua, altrimenti vuotoLetto nel contesto
Descrizione, categoriaCorrispondenza da lessicoSempre nulloEstratto dalla frase
Partecipanti, esclusioniCorrispondenza approssimativa dei nomiSempre vuotoRisolti in id di membri reali
PaganteDedotto dalla formulazioneSempre vuotoIndicato, con un importo che può essere nullo
Confidenza complessivaCalibrata per campoFissa a 0.3Per singolo parse
Prompt riportatoNessuno esistevaNullo, nessun prompt è stato lettoId e versione del prompt

Il gradino attivo non supera mai la soglia utile di 0.6

Il nostro contratto di parsing abbandona un parse sotto 0.6 di confidenza complessiva e riporta l'utente al modulo semplice. amount_only riporta 0.3 su ogni risposta, e la costante è strutturale, non calibrata. Un importo isolato non è un parse, quindi il gradino resta a metà della soglia qualunque cosa abbia trovato. Una sola costante per ogni risposta è ciò che lo tiene lì: un punteggio caso per caso è un punteggio che prima o poi qualcuno spinge verso l'alto.

Il livello inoltre non riporta alcun prompt. Sia prompt_id sia prompt_version tornano nulli, perché il gradino non ha letto alcun prompt. Indicarne uno attribuirebbe ogni risultato di valutazione a una versione di prompt che il livello non ha mai visto, e il banco di valutazione è l'unico strumento autorizzato a promuovere un gradino a suggerimento con un tocco, con una precisione del 95% su importo e partecipanti insieme.

Altri due livelli sono dichiarati e nessuno dei due è attivo. Il livello cheap-fast ha un adattatore Groq per openai/gpt-oss-120b e nessuna chiave; il livello intermedio non ha adattatore. Selezionare l'uno o l'altro fallisce all'avvio invece che alla prima richiesta di un utente, perché un LLM fattura a chiamata e una dipendenza fatturabile deve fallire in modo chiuso.

Nulla si registra da solo, quindi un campo vuoto costa un tocco

Un campo vuoto costa così poco solo perché nessuna acquisizione in Dimesum può scrivere denaro. Un'acquisizione crea un suggerimento, una persona lo conferma, e la conferma è ciò che crea la spesa. La decisione D5 del brief enuncia la regola, e .go-arch-lint.yml la impone: al contesto di ingestione è negata qualsiasi dipendenza da expense o ledger, così un'acquisizione non può registrare una voce di giornale nemmeno per errore. La CI fa fallire l'import, cosa che abbiamo verificato aggiungendone uno.

L'acquisizione conserva il testo grezzo qualunque cosa faccia il parser, e indica quali campi restano irrisolti. Il client evidenzia quei campi vuoti invece di mostrare una bozza inventata, ed è questa la differenza tra un parser che non dice nulla e uno che indovina. La conferma deriva il proprio id di spesa dall'id del suggerimento, così un doppio tocco si ripete invece di addebitare due volte.

La regola che vale la pena rubare

Conta le protezioni, non gli errori. Un elenco di protezioni che cresce ogni settimana ti sta dicendo che il compito è la comprensione, e la comprensione spetta a un modello. Il nostro prossimo passo è far girare il golden set in contracts/parse_expense/eval/ contro un vero livello a modello, perché nulla qui diventa un suggerimento con un tocco senza quel verdetto.

Domande frequenti

Perché Dimesum ha cancellato il suo parser di spese a regole?

Dimesum lo ha cancellato perché quattro frasi comuni producevano errori sul denaro e ogni protezione aggiunta faceva emergere altre due formulazioni. dinner 12-08 400 veniva letto come €12, ravioli 600 aggiungeva un membro di nome Ravindra, kirana 500 addebitava la spesa a un membro di nome Kiran, e refund -483.50 diventava un addebito di €483.50. Comprendere una frase è compito del modello.

Cosa restituisce davvero il livello amount_only?

Il livello amount_only restituisce un solo numero e nient'altro. Risponde con un importo solo quando il testo contiene esattamente un candidato monetario non ambiguo, e non indica mai un partecipante, un pagante, una descrizione o una categoria. Due candidati significano nessun importo. Tutto il resto del contratto di parsing aspetta un livello a modello.

Perché amount_only riporta una confidenza di 0.3 invece di un punteggio reale?

Lo 0.3 è strutturale, non calibrato. Il nostro contratto di parsing abbandona un parse sotto 0.6 e riporta l'utente al modulo semplice, e un importo isolato non è un parse, quindi il gradino resta a metà di quella soglia qualunque cosa abbia trovato. Una sola costante fissa per ogni risposta impedisce che un punteggio caso per caso venga poi spinto verso l'alto.

Un'acquisizione in linguaggio naturale può scrivere sul registro senza una persona?

No. Un'acquisizione crea un suggerimento e una persona lo conferma, secondo la decisione D5 del brief. La regola è imposta in .go-arch-lint.yml, che nega al contesto di ingestione qualsiasi dipendenza da expense o ledger, così la CI fa fallire l'import se un'acquisizione tenta mai di raggiungere un giornale. La conferma è ciò che crea la spesa.

Cosa succede all'interpretazione delle spese in linguaggio naturale quando nessun livello a modello è disponibile?

Dimesum degrada a amount_only e mostra campi vuoti. Il livello cheap-fast è collegato a openai/gpt-oss-120b di Groq ed è rilasciato senza chiave, e il livello intermedio non ha adattatore, quindi selezionare l'uno o l'altro fallisce all'avvio invece che alla prima richiesta di un utente. L'acquisizione conserva comunque il testo grezzo.