Skip to content

Regole Deterministiche

Luigi Corsaro edited this page Mar 16, 2026 · 1 revision

Spendify β€” Strumenti deterministici

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.


Mappa 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                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1 β€” Rilevamento formato file

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)

2 β€” Classificazione documento β€” Fase 0

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.


3 β€” Normalizzazione

Modulo: core/normalizer.py, core/orchestrator.py Quando: stadio 3, dopo classificazione schema

3a β€” Parsing data

parse_date_safe(valore, formato)

  1. Prova il formato dello schema
  2. 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
  3. Restituisce None se tutto fallisce (riga scartata)

3b β€” Parsing importo

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

3c β€” Convenzione di segno

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.

3d β€” Normalizzazione descrizione

normalize_description(testo) unicodedata.normalize("NFC", testo).casefold().strip() Garantisce confronti case-insensitive stabili; non modifica mai raw_description.

3e β€” Identificatore transazione (idempotency key)

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.

3f β€” Inferenza tipo transazione

_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

3g β€” Rimozione riga saldo carta

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).


4 β€” Dedup check

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)

5 β€” Privacy / PII Redaction

Modulo: core/sanitizer.py Quando: PRIMA di ogni chiamata LLM (pulizia descrizioni + categorizzazione); DOPO per il ripristino nomi proprietari

Regole di redazione

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>

Nomi proprietari β†’ nomi fittizi (per LLM)

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.


6 β€” Rilevamento giroconti [RF-04]

Modulo: core/normalizer.py β†’ detect_internal_transfers() Quando: stadio 6, dopo dedup

Fase 1 β€” Accoppiamento importo + data

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)

Fase 2 β€” Matching nome proprietario

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

Parametri chiave

Parametro Default
epsilon 0.01 €
epsilon_strict 0.005 €
delta_days 5 giorni
delta_days_strict 1 giorno

7 β€” Riconciliazione carte [RF-03]

Modulo: core/normalizer.py β†’ find_card_settlement_matches() Quando: stadio 7, abbina card_settlement (conto corrente) con card_tx (carta)

Fase 1 β€” Finestra temporale

card_tx in [data_addebito βˆ’ 45 giorni, data_addebito + 7 giorni]

Fase 2 β€” Sliding window (sottoinsiemi contigui)

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 βœ“

Fase 3 β€” Subset sum ai bordi (fallback)

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 βœ“

8 β€” Categorizzazione β€” livelli deterministici

Modulo: core/categorizer.py Quando: stadio 8, prima del LLM (livelli 0 e 1)

Livello 0 β€” Regole utente (CategoryRule)

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.

Livello 1 β€” Regole statiche per parola chiave

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

9 β€” Persistenza DB

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

10 β€” Revisione manuale β€” strumenti deterministici

Modulo: db/repository.py, ui/review_page.py

Auto-applicazione regole (pagina Review)

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

Esegui tutte le regole (pagina Regole)

apply_all_rules_to_all_transactions(session, user_rules) Pulsante "▢️ Esegui tutte le regole" nella pagina Regole:

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.

DescriptionRule β€” regole di correzione descrizione in blocco

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.


11 β€” Analytics β€” soglie e filtri

Modulo: ui/analytics_page.py

Tipi esclusi dai grafici

EXCLUDED = {"internal_out", "internal_in", "card_settlement", "aggregate_debit"}

Benchmark di spesa (confronto ISTAT)

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 βšͺ

Riepilogo β€” Tutti gli strumenti per stadio pipeline

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 βœ—

Parametri globali di configurazione

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

Clone this wiki locally