-
Notifications
You must be signed in to change notification settings - Fork 1
Regole Deterministiche
Inventario completo di tutte le regole, algoritmi e trasformazioni non-LLM implementate nel sistema. Per ogni strumento: dove si trova, cosa fa, le regole hardcoded e il punto di applicazione nella pipeline.
FILE (CSV / XLSX)
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. RILEVAMENTO FORMATO β ββ DETERMINISTICO
β detect_encoding Β· detect_delimiter β
β detect_header_row Β· detect_best_sheet β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1b. PRE-PROCESSING Phase 0 β ββ DETERMINISTICO
β detect_and_strip_preheader_rows β
β drop_low_variability_columns β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. CLASSIFICAZIONE DOCUMENTO β Fase 0 β ββ DETERMINISTICO
β sinonimi colonne Β· ispezione segni β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β LLM per campi ambigui (Fase 1)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. NORMALIZZAZIONE β ββ DETERMINISTICO
β parse_date_safe Β· parse_amount Β· apply_sign_convention β
β normalize_description Β· compute_transaction_id (SHA-256)β
β _infer_tx_type Β· remove_card_balance_row β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β ID calcolato qui da valori grezzi
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. DEDUP CHECK β ββ DETERMINISTICO
β get_existing_tx_ids (repository.py) β
β β abort se tutte giΓ in DB, zero LLM calls sprecate β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β solo tx nuove proseguono
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. PULIZIA DESCRIZIONI β
β PRIVACY / PII REDACTION ββ DETERMINISTICO β
β redact_pii Β· restore_owner_placeholders β
β (applicato PRIMA e DOPO ogni chiamata LLM) β
β ββ LLM (estrazione controparte) β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 6. RILEVAMENTO GIROCONTI [RF-04] β ββ DETERMINISTICO
β detect_internal_transfers β
β Fase 1: accoppiamento importo+data β
β Fase 2: matching nome proprietario β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 7. RICONCILIAZIONE CARTE [RF-03] β ββ DETERMINISTICO
β find_card_settlement_matches β
β sliding window Β· subset sum β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 8. CATEGORIZZAZIONE β Livello 0 e 1 β ββ DETERMINISTICO
β Liv. 0: regole utente (CategoryRule.matches) β
β Liv. 1: regole statiche per parola chiave β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β LLM solo se nessuna regola fa match
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 9. PERSISTENZA DB β ββ DETERMINISTICO
β upsert idempotente Β· SHA-256 per file e transazione β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 10. REVISIONE β auto-applicazione regole β ββ DETERMINISTICO
β apply_rules_to_review_transactions (to_review=True) β
β apply_all_rules_to_all_transactions (tutte le tx) β
β bulk description rules Β· DescriptionRule β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Modulo: core/normalizer.py
Quando: stadio 1, prima di qualsiasi parsing
| Funzione | Regola hardcoded |
|---|---|
detect_encoding(raw_bytes) |
chardet β normalizza alias (ascii β utf-8) |
detect_delimiter(content) |
conta frequenza di , ; \t | β vince il piΓΉ frequente |
detect_header_row(lines) |
prima riga con β₯ 2 campi non-numerici; pattern numerico: ^[\d\.\,\-\+\sβ¬$Β£%]+$
|
detect_best_sheet(workbook) |
esclude fogli con nome summary|totale|riepilogo; punteggio = righe + (colonne numeriche Γ 10) |
Modulo: core/classifier.py
Quando: stadio 2 (Flow 2), solo se sorgente senza schema in DB
Risolve i campi colonna senza LLM tramite sinonimi:
| Campo | Sinonimi riconosciuti |
|---|---|
date_col |
data, date, data operazione, booking date, buchungsdatum, β¦ |
amount_col |
importo, amount, betrag, montant, somme, β¦ |
debit_col |
dare, addebiti, uscite, debit, ausgaben, β¦ |
credit_col |
avere, accrediti, entrate, credit, einnahmen, β¦ |
description_col |
descrizione, causale, memo, payee, bezeichnung, libellΓ©, β¦ |
Ispezione segni (Fase 0.5):
Se amount_col semantica "neutral" β legge i dati reali; se qualsiasi valore < 0 β invert_sign=False certo, nessun LLM necessario.
Modulo: core/normalizer.py, core/orchestrator.py
Quando: stadio 3, dopo classificazione schema
parse_date_safe(valore, formato)
- Prova il formato dello schema
- Fallback su formati comuni (in ordine):
%d/%m/%YΒ·%d-%m-%YΒ·%d/%m/%yΒ·%d-%m-%yΒ·%Y-%m-%dΒ·%Y/%m/%dΒ·%m/%d/%YΒ·%m/%d/%y - Restituisce
Nonese tutto fallisce (riga scartata)
parse_amount(valore)
Strip simboli: β¬ $ Β£ (spazi)
Euristica separatori:
"1.234,56" β punto = migliaia, virgola = decimale β 1234.56
"1,234.56" β virgola = migliaia, punto = decimale β 1234.56
"1234,56" β virgola sola con β€ 2 decimali β 1234.56
"1234.56" β punto sola con β€ 2 decimali β 1234.56
apply_sign_convention(riga, convention)
| Convenzione | Regola |
|---|---|
signed_single |
usa amount_col così com'è |
debit_positive |
credito β debito (entrambi positivi nel CSV) |
credit_negative |
credito as-is positivo; debito negato |
Dopo: se invert_sign=True (tipico per carte) β moltiplica per β1.
normalize_description(testo)
unicodedata.normalize("NFC", testo).casefold().strip()
Garantisce confronti case-insensitive stabili; non modifica mai raw_description.
compute_transaction_id(account_label, data, importo, descrizione)
SHA-256[:24] della stringa: {account_label}|{data ISO}|{importo}|{descrizione_raw}
Usato su valori grezzi β stabile tra versioni di normalizzazione.
compute_file_hash(raw_bytes)
SHA-256 completo del file β dedup a livello di importazione.
_infer_tx_type(importo, doc_type, descrizione, pattern_interni)
1. descrizione matcha pattern_interni (lista da DB) β internal_out / internal_in
2. doc_type in {credit_card, debit_card, prepaid_card} β card_tx
3. importo β₯ 0 β income
4. importo < 0 β expense
remove_card_balance_row(txs, epsilon, owner_label)
Rileva la riga il cui |importo| β Ξ£|altri importi| (entro epsilon 0.01 β¬).
Con owner_label β rinomina la descrizione (il rilevamento giroconti la cattura).
Senza owner_label β rimuove la riga (evita doppio conteggio).
Modulo: db/repository.py β get_existing_tx_ids()
Quando: stadio 4, dopo normalizzazione e prima della pulizia descrizioni (LLM)
PerchΓ©: l'ID SHA-256 Γ¨ calcolato al passo 3 da valori grezzi β possiamo scartare i duplicati senza sprecare token LLM
ids_esistenti = SELECT id FROM transaction WHERE id IN (tutti_gli_id_del_batch)
β filtra le tx giΓ presenti
β se tutte presenti β abort early (file giΓ importato)
Modulo: core/sanitizer.py
Quando: PRIMA di ogni chiamata LLM (pulizia descrizioni + categorizzazione); DOPO per il ripristino nomi proprietari
| Pattern | Regex | Sostituito con |
|---|---|---|
| IBAN | [A-Z]{2}\d{2}[A-Z0-9]{4,30} |
<ACCOUNT_ID> |
| PAN / carta (13-19 cifre) | \d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{1,7} |
<CARD_ID> |
| Carta mascherata | [\*X]{4}[\s\-]?\d{4} |
<CARD_ID> |
| Codici transazione | (CAU|NDS|TRN|CRO|RIF|ID TRANSAZIONE)\s*[\d\-]+ |
<TX_CODE> |
| Codice fiscale IT | [A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z] |
<FISCAL_ID> |
| Pattern aggiuntivi utente | configurabile | <REDACTED> |
I nomi reali vengono sostituiti con nomi plausibili ma falsi (il LLM puΓ² ancora riconoscerli come persone ed estrarli correttamente). Dopo la risposta LLM, restore_owner_placeholders() rimette i nomi reali.
| Lingua | Pool nomi fittizi |
|---|---|
| IT | Carlo Brambilla, Marta Pellegrino, Alberto Marini, Giovanna Ferrara, β¦ |
| EN | James Fletcher, Helen Norris, David Lawson, Susan Palmer, β¦ |
| DE | Klaus Hartmann, Monika Braun, Stefan Richter, Ingrid Weber, β¦ |
| FR | Pierre Dumont, Claire Lebrun, Michel Garnier, Sophie Renard, β¦ |
| ES | Carlos Navarro, Elena Vega, Miguel Torres, Isabel Molina, β¦ |
Guardia finale: assert_sanitized(testo) β lancia ValueError se IBAN o PAN ancora presenti.
Modulo: core/normalizer.py β detect_internal_transfers()
Quando: stadio 6, dopo dedup
Per ogni coppia (i, j) con account_label_i β account_label_j:
amount_match = |importo_i + importo_j| β€ epsilon
date_match = |data_i β data_j| β€ delta_days
Se entrambi verificati:
high_symmetry = amount β€ epsilon_strict AND date β€ delta_days_strict
Confidenza:
HIGH β keyword della lista pattern_interni trovata in descrizione
MEDIUM β high_symmetry senza keyword
Se require_keyword_confirmation=True AND confidenza=MEDIUM:
β segna transfer_pair_id, NON aggiorna tx_type (va in revisione)
Altrimenti:
β tx_type: internal_out (uscita) / internal_in (entrata)
Per ogni tx non ancora accoppiata:
Se descrizione contiene un nome proprietario
(regex con tutte le permutazioni dei token del nome):
β tx_type = internal_out / internal_in
β transfer_confidence = HIGH
| Parametro | Default |
|---|---|
epsilon |
0.01 β¬ |
epsilon_strict |
0.005 β¬ |
delta_days |
5 giorni |
delta_days_strict |
1 giorno |
Modulo: core/normalizer.py β find_card_settlement_matches()
Quando: stadio 7, abbina card_settlement (conto corrente) con card_tx (carta)
card_tx in [data_addebito β 45 giorni, data_addebito + 7 giorni]
Per ogni sottoinsieme contiguo [i..j]:
verifica: gap tra tx consecutive β€ max_gap_days (5 gg)
somma = Ξ£ |importo[i..j]|
Se |somma β importo_addebito| β€ epsilon β MATCH β
Prende k=10 tx prima + k=10 dopo la data addebito (max 20 tx)
Ricerca esaustiva: tutti i sottoinsiemi β 2^20 β 1M combinazioni (sicuro)
Prima combinazione che somma all'importo β MATCH β
Modulo: core/categorizer.py
Quando: stadio 8, prima del LLM (livelli 0 e 1)
Salvate in DB, ordinate per prioritΓ decrescente. Prima che fa match vince.
CategoryRule.matches(descrizione, doc_type):
| Tipo | Logica |
|---|---|
exact |
descrizione.casefold() == pattern.casefold() |
contains |
pattern.casefold() IN descrizione.casefold() |
regex |
re.search(pattern, descrizione, IGNORECASE) |
Se doc_type specificato nella regola β deve coincidere con quello della transazione.
Hardcoded nel codice, direction-aware (spese/entrate separate):
SPESE:
| Pattern (regex, case-insensitive) | Categoria | Sottocategoria |
|---|---|---|
conad|coop|esselunga|lidl|carrefour|eurospin|aldi|penny|pam |
Alimentari | Spesa supermercato |
farmacia|pharma |
Salute | Farmaci |
eni|shell|q8|tamoil|ip|api|agip |
Trasporti | Carburante |
telepass|autostrad |
Trasporti | Parcheggio / ZTL |
trenitalia|italo|frecciarossa|frecciargento |
Trasporti | Trasporto pubblico |
enel|iren|a2a|hera|eni gas |
Casa | Energia elettrica |
netflix|spotify|amazon prime|disney+|apple tv |
Svago | Streaming / abbonamenti digitali |
commissione|canone conto|spese tenuta |
Finanza | Commissioni bancarie |
ENTRATE:
| Pattern | Categoria | Sottocategoria |
|---|---|---|
stipendio|salary|busta paga |
Lavoro dipendente | Stipendio |
pensione|inps rendita |
Prestazioni sociali | Pensione / rendita |
Modulo: db/repository.py
Quando: stadio 9, tutto idempotente
| Funzione | Regola idempotenza |
|---|---|
upsert_transaction(tx) |
se tx.id esiste β skip |
create_import_batch(sha256) |
se sha256 esiste β return existing |
upsert_document_schema(schema) |
se source_identifier esiste β aggiorna |
create_reconciliation_link(sid, did) |
se coppia (sid, did) esiste β skip |
create_transfer_link(out_id, in_id) |
se coppia esiste β skip |
update_transaction_category() |
imposta sempre: confidence=high, source=manual, to_review=False
|
Modulo: db/repository.py, ui/review_page.py
apply_rules_to_review_transactions(session, user_rules)
Ad ogni caricamento della pagina Review:
Per ogni tx con to_review=True:
Per ogni regola (ordinate per prioritΓ DESC):
Se regola.matches(tx.description, tx.doc_type):
β aggiorna categoria, source=rule, to_review=False
β passa alla tx successiva
apply_all_rules_to_all_transactions(session, user_rules)
Pulsante "
Applica tutte le regole a TUTTE le transazioni (non solo to_review=True):
Regole ordinate per prioritΓ DESC
Per ogni tx:
Per ogni regola:
Se regola.matches(tx.description, tx.doc_type):
β aggiorna categoria, subcategory, source=rule, confidence=high
β se tx.to_review=True β imposta to_review=False (n_cleared++)
β passa alla tx successiva (primo match vince)
Restituisce (n_matched, n_cleared_review)
Richiede conferma tramite checkbox prima dell'esecuzione.
Salvate in DB (description_rule). Pattern su raw_description:
| Tipo | Logica |
|---|---|
exact |
raw_description.lower() == pattern.lower() |
contains |
pattern.lower() IN raw_description.lower() |
regex |
re.search(pattern, raw_description, IGNORECASE) |
Applicazione: aggiorna description β ri-categorizza con LLM.
Modulo: ui/analytics_page.py
EXCLUDED = {"internal_out", "internal_in", "card_settlement", "aggregate_debit"}Soglie applicate per ogni categoria rispetto al benchmark familiare di riferimento:
| Segnale | Condizione | Icona |
|---|---|---|
| Spesa anomala alta | spesa > 1.5 Γ benchmark | π΄ |
| Spesa anomala bassa | spesa < 0.5 Γ benchmark | π΅ |
| Spesa normale | tra 0.5Γ e 1.5Γ | π’ |
| Assente | nessuna spesa in categoria | βͺ |
| Stadio | Strumento | Modulo | LLM? |
|---|---|---|---|
| 1. Formato file | detect_encoding / detect_delimiter / detect_header_row / detect_best_sheet | normalizer.py | β |
| 1b. Pre-processing | detect_and_strip_preheader_rows / drop_low_variability_columns | normalizer.py | β |
| 2. Schema β Fase 0 | sinonimi colonne, ispezione segni | classifier.py | β |
| 2. Schema β Fase 1 | classificazione doc_type, date_format, sign_convention | classifier.py | β LLM |
| 3. Normalizzazione | parse_date_safe / parse_amount / apply_sign_convention / normalize_description / compute_transaction_id / _infer_tx_type / remove_card_balance_row | normalizer.py + orchestrator.py | β |
| 4. Dedup | get_existing_tx_ids | repository.py | β |
| 5. Privacy | redact_pii / restore_owner_placeholders | sanitizer.py | β |
| 5. Pulizia descrizioni | clean_descriptions_batch | description_cleaner.py | β LLM |
| 6. Giroconti | detect_internal_transfers (Fase 1 + Fase 2) | normalizer.py | β |
| 7. Riconciliazione carte | find_card_settlement_matches (3 fasi) | normalizer.py | β |
| 8. Categorizzazione Liv. 0 | CategoryRule.matches (regole utente) | categorizer.py | β |
| 8. Categorizzazione Liv. 1 | _apply_static_rules (keyword hardcoded) | categorizer.py | β |
| 8. Categorizzazione Liv. 3 | categorize_batch (LLM) | categorizer.py | β LLM |
| 9. Persistenza | upsert_transaction / persist_import_result | repository.py | β |
| 10. Auto-regole | apply_rules_to_review_transactions | repository.py | β |
| 10. Esegui tutte le regole | apply_all_rules_to_all_transactions | repository.py | β |
| 10. Bulk descrizioni | DescriptionRule + _apply_description_rule_bulk | repository.py + review_page.py | β LLM (ri-cat.) |
| Analytics | EXCLUDED / benchmark ISTAT 0.5Γβ1.5Γ | analytics_page.py | β |
Tutti i default sono in ProcessingConfig (core/orchestrator.py):
| Parametro | Default | Usato da |
|---|---|---|
tolerance |
0.01 β¬ | rilevamento giroconti, riconciliazione carte |
tolerance_strict |
0.005 β¬ | giroconti high-symmetry |
settlement_days |
5 gg | finestra accoppiamento giroconti |
settlement_days_strict |
1 gg | finestra strict giroconti |
window_days |
45 gg | finestra temporale riconciliazione carte |
max_gap_days |
5 gg | sliding window carte |
boundary_pre_post |
10 tx | subset sum riconciliazione |
confidence_threshold |
0.80 | soglia LLM β to_review |
require_keyword_confirmation |
True | giroconti medium β to_review se no keyword |
batch_size (descrizioni) |
30 tx/call | clean_descriptions_batch |
batch_size (categorie) |
20 tx/call | categorize_batch |