// Package synch ports the bank-sync deduplication helper from // scripts/sync_fio_to_sheets.py. package synch import ( "crypto/sha256" "encoding/hex" "math" "strconv" "strings" ) // Transaction is the projection of a Fio transaction that participates // in the Sync ID hash. Other fields (ks, ss, sender_account, …) are // intentionally excluded — they are not part of the Python hash. // // Currency: leave "" to inherit the Python default of "CZK" (matches // the HTML scraper path which omits the key entirely). type Transaction struct { Date string Amount float64 Currency string Sender string VS string Message string BankID string } // GenerateSyncID returns the lowercase SHA-256 hex digest of // "date|amount|currency|sender|vs|message|bank_id" (lower-cased), used // as the dedup key in column K of the payments sheet. // // Byte-stable with scripts/sync_fio_to_sheets.py generate_sync_id. func GenerateSyncID(tx Transaction) string { currency := tx.Currency if currency == "" { currency = "CZK" } raw := strings.ToLower(strings.Join([]string{ tx.Date, formatAmount(tx.Amount), currency, tx.Sender, tx.VS, tx.Message, tx.BankID, }, "|")) sum := sha256.Sum256([]byte(raw)) return hex.EncodeToString(sum[:]) } // formatAmount mimics Python's str(float) for Fio transaction amounts. // Python uses decimal notation for abs(f) in [1e-4, 1e16) and scientific // notation outside that range, always adding ".0" to whole-valued decimals. func formatAmount(f float64) string { abs := math.Abs(f) if abs != 0 && (abs < 1e-4 || abs >= 1e16) { return strconv.FormatFloat(f, 'e', -1, 64) } s := strconv.FormatFloat(f, 'f', -1, 64) if !strings.ContainsRune(s, '.') { s += ".0" } return s }