Abbiamo cancellato il nostro parser di spese
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.
| Cosa ha scritto l'utente | Cosa ha fatto il parser | Perché è successo |
|---|---|---|
dinner 12-08 400 | Ha letto l'importo come €12 | Una persona vede una data e un totale. Una regex vede tre numeri e prende il primo. |
ravioli 600 | Ha inserito nella divisione un membro di nome Ravindra | La corrispondenza approssimativa dei soprannomi ha valutato un piatto come una persona. |
kirana 500 | Ha addebitato la spesa a un membro di nome Kiran | Lo stesso confronto, questa volta con il nome di un negozio. |
refund -483.50 | Ha registrato un addebito di €483.50 | Le 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.
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.
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.
| Campo | Parser a regole (cancellato) | amount_only (attivo) | Livello a modello (collegato, senza chiave) |
|---|---|---|---|
| Importo | Indovinato tra più numeri | Una cifra non ambigua, altrimenti vuoto | Letto nel contesto |
| Descrizione, categoria | Corrispondenza da lessico | Sempre nullo | Estratto dalla frase |
| Partecipanti, esclusioni | Corrispondenza approssimativa dei nomi | Sempre vuoto | Risolti in id di membri reali |
| Pagante | Dedotto dalla formulazione | Sempre vuoto | Indicato, con un importo che può essere nullo |
| Confidenza complessiva | Calibrata per campo | Fissa a 0.3 | Per singolo parse |
| Prompt riportato | Nessuno esisteva | Nullo, nessun prompt è stato letto | Id 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.
Articoli più letti
- Il registro append-only per spese condivise esatte9 min di lettura
- Perché modificare una spesa ridefinisce la divisione9 min di lettura
- Sei bug di denaro nelle spese multivaluta9 min di lettura
- Saldare le spese di gruppo in pochi bonifici5 min di lettura
- Come dividere un conto con un piatto non condiviso9 min di lettura