"""Automatische Formaterkennung für CSV-Auszüge. Bisher musste man bei jedem Import Trennzeichen, Datumsformat und Spaltenzuordnung von Hand eintragen. Das ist die häufigste Fehlerquelle gewesen: Ein falsches Datumsformat liess alle Zeilen durchfallen, ein falscher Dezimaltrenner verzehnfachte Beträge. """ from app.services.csv_import import erkenne_format, parse_amount def _erkenne(text: str) -> dict: return erkenne_format(text.encode("utf-8")) DEUTSCH = ( "Buchungstag;Wert;Auftraggeber/Empfänger;Verwendungszweck;Umsatz;Saldo\n" "15.03.2026;15.03.2026;REWE Markt GmbH;Einkauf Filiale 42;-52,30;1.204,70\n" "18.03.2026;18.03.2026;Arbeitgeber AG;Gehalt März;3.000,00;4.204,70\n" "20.03.2026;20.03.2026;Stadtwerke;Abschlag Strom;-89,00;4.115,70\n" ) def test_unbekannte_bank_wird_inhaltsbasiert_erkannt(): r = _erkenne(DEUTSCH) assert r["ok"] is True c = r["config"] assert c["delimiter"] == ";" assert c["date_format"] == "%d.%m.%Y" assert c["column_map"]["date"] == 0 assert c["column_map"]["amount"] == 4 assert c["column_map"]["payee"] == 2 assert c["column_map"]["purpose"] == 3 assert c["column_map"]["balance"] == 5 def test_datumsspalten_verfaelschen_den_dezimaltrenner_nicht(): """„15.03.2026“ steckt voller Punkte – und der Auszug führt zwei solche Spalten. Ohne sie herauszurechnen käme „.“ als Dezimalzeichen heraus und aus 1.204,70 würden 1.20 CHF.""" c = _erkenne(DEUTSCH)["config"] assert c["decimal_sep"] == "," assert parse_amount("1.204,70", c["decimal_sep"]) == 120470 assert parse_amount("-52,30", c["decimal_sep"]) == -5230 def test_datumsformat_ohne_uhrzeit_gewinnt_wenn_keine_da_ist(): """parse_date ist absichtlich nachsichtig; beim Erkennen darf es das nicht sein, sonst steht hinterher ein Format mit Uhrzeit in den Einstellungen.""" c = _erkenne("Tag;Betrag\n01.02.2026;-10,00\n02.02.2026;-20,00\n")["config"] assert c["date_format"] == "%d.%m.%Y" def test_englisch_mit_tabulator(): r = _erkenne("Date\tDescription\tAmount\tBalance\n" "2026-03-15\tCOFFEE SHOP\t-4.50\t120.10\n" "2026-03-16\tSALARY\t2500.00\t2620.10\n") c = r["config"] assert c["delimiter"] == "\t" assert c["decimal_sep"] == "." assert c["date_format"] == "%Y-%m-%d" assert c["column_map"]["payee"] == 1 def test_kurs_und_referenzspalten_werden_nicht_fuer_betraege_gehalten(): """Ganzzahlen ohne Vorzeichen sind verdächtig: Eine Referenznummer als Betrag zu nehmen wäre ein stiller, teurer Fehler.""" r = _erkenne("Datum;Referenz;Empfänger;Betrag\n" "01.03.2026;998877;Coop;-12,50\n" "02.03.2026;998878;Migros;-8,20\n") assert r["config"]["column_map"]["amount"] == 3 def test_datei_ohne_datumsspalte_wird_ehrlich_abgelehnt(): r = _erkenne("Name;Wert\nAlpha;12,00\nBeta;9,00\n") assert r["ok"] is False assert "Datum" in r["grund"] def test_leere_datei(): assert _erkenne("")["ok"] is False assert _erkenne("Nur;Kopfzeile\n")["ok"] is False