firefly-import-preprocessor/README.de.md
2026-05-06 23:17:54 +02:00

20 KiB
Raw Blame History

Firefly Import Preprocessor — Dokumentation

Version: 1.0.0
Datum: 03. Mai 2026
Status: Production Ready

🌐 English


📋 Inhaltsverzeichnis

  1. Überblick
  2. Installation & Setup
  3. Schnellstart
  4. Konfiguration
  5. Transformationstypen
  6. CLI-Referenz
  7. Debug-Modus
  8. Firefly III Integration
  9. Architektur
  10. Fehlerbehandlung

Überblick

Der Firefly Import Preprocessor ist ein produktionsreifer PHP-Preprocessor für Banken-CSV-Exportdateien. Er transformiert Bankdaten in ein standardisiertes Format und kann sie optional in Firefly III importieren.

Kernfeatures

Vollständige CSV-Transformation mit komplexen Pipelines
Metadaten-Extraktion mit Regex (IBAN, Währung, Kontoname)
14 Transformationstypen für flexible Datenverarbeitung
Firefly III Integration — CLI, Docker und HTTP-Upload
Debug-Modus für Transparenz bei Verarbeitung
Production Ready mit vollständiger Fehlerbehandlung
Zero Dependencies für Core-Funktionalität

Workflow

Input CSV
    ↓
Metadaten extrahieren (Regex)
    ↓
Datenzeilen transformieren (Pipeline)
    ↓
Output CSV schreiben
    ↓
[Optional] In Firefly III importieren

Installation & Setup

Voraussetzungen

  • PHP 8.1+
  • Composer (empfohlen)
  • [Optional] Docker für Firefly III Integration

Installation

# 1. Repository clonen/kopieren
cd ff-imp-preprocessor

# 2. Abhängigkeiten installieren (optional)
composer install

# 3. Konfiguration erstellen
cp config/config.example.json config/config.json
# Bearbeite config/config.json mit deinen Einstellungen

# 4. Directories erstellen
mkdir -p config/import/{source,output,archive,error}
chmod 755 config/import/{source,output,archive,error}

# 5. Test durchführen
php bin/transformer.php validate config/config.json input.csv

Schnellstart

1. Konfiguration anpassen

Bearbeite config/config.json und stelle sicher, dass die Extraction-Rules zu deinem CSV-Format passen:

{
  "metadata": {
    "extractionRules": [
      {
        "name": "account_iban",
        "lineNumber": 2,
        "regex": "IBAN:\\s*([A-Z0-9 ]+)",
        "captureGroup": 1
      }
    ]
  },
  "csvStructure": {
    "headerLine": 5,
    "delimiter": ";",
    "encoding": "UTF-8"
  }
}

2. CSV validieren

php bin/transformer.php validate config/config.json input.csv

3. Transformation durchführen

php bin/transformer.php transform input.csv config/config.json

# Mit Debug-Modus für Fehlersuche
php bin/transformer.php transform input.csv config/config.json --debug

4. Output prüfen

php bin/transformer.php test input.csv config/config.json --debug
# Zeigt max. 10 transformierte Zeilen und Debug-Logs

Konfiguration

config.json Struktur

metadata - Metadaten-Extraktion

{
  "metadata": {
    "extractionRules": [
      {
        "name": "account_iban",
        "lineNumber": 2,
        "regex": "IBAN:\\s*([A-Z0-9 ]+)",
        "captureGroup": 1
      },
      {
        "name": "currency_code",
        "lineNumber": 3,
        "regex": "Währung:\\s*([A-Z]{3})",
        "captureGroup": 1
      }
    ]
  }
}
Feld Typ Beschreibung
name string Name der Metadaten-Variable (verwendet in constantvalue)
lineNumber int Zeilennummer in CSV (1-basiert, menschenlesbar)
regex string Regex-Pattern zur Extraktion (ohne Delimiter)
captureGroup int Nummer der Klammer-Gruppe (0=komplett, 1=erste Klammer, etc.)

Beispiel Regex:

  • Pattern: IBAN:\s*([A-Z0-9 ]+)
  • Input: IBAN: CH93 0077 2020 6262 5252 7
  • Capture Group 1: CH93 0077 2020 6262 5252 7

csvStructure - CSV-Format

{
  "csvStructure": {
    "headerLine": 5,
    "delimiter": ";",
    "encoding": "UTF-8",
    "hasBom": false
  }
}
Feld Typ Default Beschreibung
headerLine int 5 Zeilennummer der Header (1-basiert)
delimiter string ; CSV-Delimiter
encoding string UTF-8 Zeichenkodierung (UTF-8, ISO-8859-1, CP1252)
hasBom bool false Hat die Datei BOM (Byte Order Mark)?

columnTransformations - Spalten-Transformationen

{
  "columnTransformations": [
    {
      "sourceColumn": "Buchungsdatum",
      "transformations": [
        {
          "type": "dateformat",
          "fromFormat": "d.m.Y",
          "toFormat": "Y-m-d"
        }
      ],
      "outputColumn": "date",
      "outputAction": "overwrite"
    }
  ]
}

outputAction:

Wert Verhalten
overwrite Ziel-Spalte mit dem Transformations-Ergebnis überschreiben (Standard)
create Ergebnis in eine neue Ausgabe-Spalte schreiben
append Ergebnis ans Ende des bestehenden Spalten-Werts anhängen. Mit "appendDelimiter": " " (beliebige Zeichenkette) wird ein Trennzeichen zwischen bestehendem und neuem Wert eingefügt — der Trennzeichen entfällt, wenn die Ziel-Spalte noch leer ist
append-if-not-empty Wie append (inkl. appendDelimiter), aber überspringt die Operation vollständig, wenn das Transformations-Ergebnis leer ist — geeignet für optionale Werte wie Tags oder Notiz-Zeilen
append-line Wie append, aber als Trennzeichen wird immer ein Zeilenumbruch \n verwendet; kein führender Zeilenumbruch wenn die Ziel-Spalte leer ist
overwrite-if-empty Ergebnis nur schreiben, wenn die Ziel-Spalte aktuell leer ist
overwrite-if-not-empty Ergebnis nur schreiben, wenn das Transformations-Ergebnis nicht leer ist

directories - Dateisystem

{
  "directories": {
    "source": "/opt/ff-imp-preprocessor/import/source",
    "output": "/opt/ff-imp-preprocessor/import/output",
    "archive": "/opt/ff-imp-preprocessor/import/archive",
    "error": "/opt/ff-imp-preprocessor/import/error"
  }
}
Feld Beschreibung
source Eingabe-Verzeichnis
output Ausgabe-Verzeichnis
archive Archiv für verarbeitete Dateien
error Error-Verzeichnis für ungültige Dateien

fireflyImport - Firefly III Integration

Optional. Mit dem Flag --do-import beim transform-Kommando (oder via auto-import) wird der Firefly III Data Importer nach dem Schreiben der Output-CSV aufgerufen.

Details und vollständige Beispiele: Firefly III Integration.


Transformationstypen

Es gibt 14 unterstützte Transformationstypen, die als Pipeline kombiniert werden können:

1. trim - Leerzeichen entfernen

{ "type": "trim" }
  • Input: Coop Pronto → Output: Coop Pronto

2. lowercase - Zu Kleinbuchstaben

{ "type": "lowercase" }
  • Input: COOP PRONTO CHUR → Output: coop pronto chur

3. uppercase - Zu Grossbuchstaben

{ "type": "uppercase" }
  • Input: Coop Pronto Chur → Output: COOP PRONTO CHUR

4. ucwordsfirst - Grossschreibung nach Trennzeichen

{ "type": "ucwordsfirst" }
  • COOP PRONTO CHURCoop Pronto Chur
  • migros-rail cityMigros-Rail City
  • O'NEILL STOREO'Neill Store

Trennzeichen: Leerzeichen, Bindestrich, Apostroph, Slash, Punkt, Komma, Semikolon, Doppelpunkt, Klammern.

Guard: Wenn der Eingabe-String bereits sowohl Groß- als auch Kleinbuchstaben enthält (gemischte Groß-/Kleinschreibung), wird er unverändert zurückgegeben. So werden bereits korrekt formatierte Strings wie "Coop pronto chur" nicht verändert. Vollständig groß- oder kleingeschriebene Strings werden weiterhin verarbeitet.


5. replace - String-Replacement

{ "type": "replace", "search": "  ", "replace": " " }
  • Input: Coop Pronto → Output: Coop Pronto

6. split - Spalte teilen

{ "type": "split", "delimiter": ";", "part": 0 }
  • Input: Coop Pronto Chur;7007 Chur → Output: Coop Pronto Chur

7. regex - Regex-Ersetzung

{ "type": "regex", "pattern": "^(.*?);.*$", "replace": "$1" }
  • Kein Match → Originalwert bleibt unverändert (pipeline-sicher)

8. regexextract - Regex-Extraktion

{ "type": "regexextract", "pattern": "(\\d{4,} [^;]+)" }
  • Kein Match → leerer String (nicht pipeline-sicher)

9. dateformat - Datum-Umformat

{ "type": "dateformat", "fromFormat": "d.m.Y", "toFormat": "Y-m-d" }
  • Input: 10.12.2025 → Output: 2025-12-10

10. truncate - String kürzen

{ "type": "truncate", "maxLength": 100 }

11. constantvalue - Konstanten-Wert aus Metadaten

{
  "sourceColumn": "_constant_",
  "transformations": [{ "type": "constantvalue", "metadataKey": "account_iban" }],
  "outputColumn": "account_iban",
  "outputAction": "create"
}

12. map - Spalte kopieren

{ "type": "map" }

13. pipeline - Verschachtelte Pipeline

{
  "type": "pipeline",
  "steps": [
    { "type": "trim" },
    { "type": "lowercase" },
    { "type": "ucwordsfirst" }
  ]
}

14. timeperiod - Zeit einer Tagesperiode zuordnen

Parst eine Zeitangabe und gibt das Label des passenden Perioden-Bereichs zurück. Unterstützt mitternachtübergreifende Bereiche (z. B. 22:0003:59). Gibt default (standardmäßig leer) zurück, wenn keine Periode passt oder die Eingabe ungültig ist.

{
  "type": "timeperiod",
  "timeFormat": "H:i:s",
  "periods": [
    { "from": "04:00:00", "to": "08:59:59", "label": "Morgen" },
    { "from": "09:00:00", "to": "10:59:59", "label": "Vormittag" },
    { "from": "11:00:00", "to": "13:59:59", "label": "Mittag" },
    { "from": "14:00:00", "to": "17:59:59", "label": "Nachmittag" },
    { "from": "18:00:00", "to": "21:59:59", "label": "Abend" },
    { "from": "22:00:00", "to": "03:59:59", "label": "Nacht" }
  ],
  "default": ""
}
  • "09:30:00""Vormittag"
  • "23:00:00""Nacht" (mitternachtübergreifender Bereich)
  • "02:00:00""Nacht" (mitternachtübergreifender Bereich)
  • "" oder nicht parsbare Eingabe → ""

timeFormat folgt der PHP-Syntax DateTime::createFromFormat (Standard: H:i:s).


Zeilen-Filterung — skipIf

Zeilen können durch einen Top-Level-Schlüssel skipIf in der Konfiguration ausgeschlossen werden. Der Wert ist ein Filter-Knoten — entweder eine einzelne Bedingung oder eine verschachtelte and/or-Gruppe.

Einzelne Bedingung:

"skipIf": { "column": "Buchungstext", "operator": "equals", "value": "Saldovortrag" }

AND-Gruppe:

"skipIf": {
  "and": [
    { "column": "Beschreibung1", "operator": "empty" },
    { "column": "Beschreibung2", "operator": "empty" }
  ]
}

Verschachtelte AND/OR-Gruppen:

"skipIf": {
  "or": [
    { "column": "Amount", "operator": "gt", "value": "10000" },
    {
      "and": [
        { "column": "Type", "operator": "equals", "value": "Saldo" },
        { "column": "Notes", "operator": "empty" }
      ]
    }
  ]
}

Unterstützte Operatoren:

Operator Passt wenn…
empty Spaltenwert ist leer
not-empty Spaltenwert ist nicht leer
equals Spaltenwert gleich "value"
not-equals Spaltenwert ungleich "value"
contains Spaltenwert enthält "value"
not-contains Spaltenwert enthält "value" nicht
matches Spaltenwert entspricht Regex "pattern"
not-matches Spaltenwert entspricht Regex "pattern" nicht
gt (float) Spalte > (float) value
gte (float) Spalte >= (float) value
lt (float) Spalte < (float) value
lte (float) Spalte <= (float) value

Pipeline-Beispiel

{
  "sourceColumn": "Buchungstext",
  "transformations": [
    { "type": "trim" },
    { "type": "replace", "search": "  ", "replace": " " },
    { "type": "lowercase" },
    { "type": "ucwordsfirst" }
  ],
  "outputColumn": "description",
  "outputAction": "overwrite"
}

Verarbeitung:

  1. " COOP PRONTO " → trim → "COOP PRONTO"
  2. "COOP PRONTO" → replace → "COOP PRONTO"
  3. "COOP PRONTO" → lowercase → "coop pronto"
  4. "coop pronto" → ucwordsfirst → "Coop Pronto"

CLI-Referenz

php bin/transformer.php <command> [input] [config] [options]

Kommandos

Kommando Beschreibung
test Test-Run (max. 10 Zeilen)
transform Vollständige Transformation
validate Konfiguration validieren
auto-import Verzeichnis-Überwachung
help Hilfe anzeigen

Optionen

Option Beschreibung
--debug, -d Debug-Modus aktivieren
--rows=N Max. N Zeilen (test-Kommando)
--output=FILE, -o Output-Pfad
--do-import Nach der Transformation in Firefly III importieren (transform)
--strict Strikte Validierung
--watch Kontinuierliche Überwachung
--interval=SEC Prüfintervall in Sekunden
--dry-run Simulationsmodus

Debug-Modus

php bin/transformer.php test input.csv config/config.json --debug

Der Debug-Modus protokolliert Ereignisse in folgenden Kategorien:

Kategorie Wann
transformer Anfang/Ende Transformation
csv_reader Beim CSV lesen
metadata Bei Metadaten-Extraktion
metadata_warning Bei Problemen
transformation Bei jeder Transformation
csv_writer Beim CSV schreiben

Firefly III Integration

Drei Betriebsmodi decken alle typischen Deployment-Szenarien ab.

chunkSize (optional, Standard: 0 = deaktiviert): Die Output-CSV wird vor dem Import in Blöcke von maximal N Datenzeilen aufgeteilt. Jeder Block wird als separate Anfrage gesendet. Das verhindert serverseitige Timeouts bei grossen Dateien (Faustregel: ~34 s/Transaktion im HTTP-Modus). Der timeout-Wert gilt pro Block, nicht für den gesamten Lauf.

chunkRetries (optional, Standard: 0 = kein Retry): Anzahl zusätzlicher Importversuche pro Block nach dem ersten. Bei einem Fehler wiederholt der Importer den Upload bis zu dieser Anzahl, bevor er abbricht. Nur wirksam wenn chunkSize > 0.

chunkRetryDelay (optional, Standard: 0 = keine Pause): Pause in Sekunden vor jedem Block-Request ab dem zweiten Block sowie zwischen Wiederholungsversuchen desselben fehlgeschlagenen Blocks. Ein einziger Wert für Cooldown und Retry-Back-off. Nur wirksam wenn chunkSize > 0.

connectionTimeout (optional, Standard: 10): Maximale Wartezeit in Sekunden für den Aufbau der TCP-Verbindung zum Importer-Server. Unabhängig von timeout (der die gesamte Übertragungsdauer begrenzt). Nur im Modus http.

Modus cli

Transformer und Importer auf demselben Server.

"fireflyImport": {
  "mode": "cli",
  "jsonConfig": "/opt/firefly-data-importer/storage/configurations/ubs-import.json",
  "importerCommand": "php /opt/firefly-data-importer/artisan importer:import",
  "chunkSize": 50,
  "chunkRetries": 3,
  "chunkRetryDelay": 10,
  "timeout": 300,
  "environment": {
    "FIREFLY_III_URL": "https://localhost",
    "FIREFLY_III_ACCESS_TOKEN": "your-token-here"
  }
}

Modus docker

Transformer lokal, Importer in Docker. Das Ausgabeverzeichnis muss als Volume eingebunden sein. jsonConfig ist der Pfad innerhalb des Containers.

"fireflyImport": {
  "mode": "docker",
  "jsonConfig": "/import/configs/ubs-import.json",
  "importerCommand": "docker exec firefly-importer php artisan importer:import",
  "chunkSize": 50,
  "chunkRetries": 3,
  "chunkRetryDelay": 10,
  "timeout": 300
}

Modus http

Transformer lokal, Importer über HTTP(S) erreichbar. Benötigt ext-curl.

Voraussetzungen auf dem Importer-Server:

CAN_POST_FILES=true
AUTO_IMPORT_SECRET=<secret>  # mindestens 16 Zeichen
"fireflyImport": {
  "mode": "http",
  "importerUrl": "https://importer.your-server.com",
  "personalSecret": "your-auto-import-secret-min-16-chars",
  "accessToken": "your-firefly-iii-personal-access-token",
  "jsonConfig": "config/ubs-import.json",
  "chunkSize": 50,
  "chunkRetries": 3,
  "chunkRetryDelay": 10,
  "connectionTimeout": 10,
  "timeout": 300
}

Die Anfrage geht an POST {importerUrl}/autoupload?secret={personalSecret} mit CSV und JSON-Config als Multipart-Felder. accessToken wird als Authorization: Bearer gesendet. Falls FIREFLY_III_ACCESS_TOKEN bereits in der Importer-Umgebung gesetzt ist, kann accessToken weggelassen werden.


Serverseitige Konfiguration

Bei grossen Importen liegt der Engpass meist auf dem Firefly III Data Importer-Server, nicht im Transformer. Die folgenden Einstellungen gehören in die Umgebung des Importers (.env oder docker-compose.yml):

Einstellung Empfohlener Wert Hinweis
PHP_MEMORY_LIMIT 512M 2048M Docker-Umgebungsvariable. Erhöhen, wenn PHP mit „Allowed memory size exhausted" abbricht.
CONNECTION_TIMEOUT 60 Sekunden für den TCP-Verbindungsaufbau zu Firefly III. Standard ~31 s (π × 10).
IGNORE_DUPLICATE_ERRORS true Doppelte Transaktionswarnungen bei Wiederholungsimporten unterdrücken.

nginx Reverse Proxy (falls vorhanden):

proxy_read_timeout  600s;   # muss länger sein als der längste Einzelblock-Import
client_max_body_size 64M;   # muss die grösste Chunk-CSV abdecken

Docker Compose Beispiel:

services:
  firefly-importer:
    environment:
      - PHP_MEMORY_LIMIT=1024M
      - CONNECTION_TIMEOUT=60
      - IGNORE_DUPLICATE_ERRORS=true

Verwendung

# Nur transformieren (kein Import)
php bin/transformer.php transform input.csv config/config.json

# Transformieren und in Firefly III importieren
php bin/transformer.php transform input.csv config/config.json --do-import

# Watch-Modus: automatisch transformieren und importieren bei neuer CSV
php bin/transformer.php auto-import config/config.json --watch

Architektur

bin/transformer.php (CLI Entry Point)
  ↓
TransformerEngine (Orchestrierung)
  ├─ ConfigurationLoader (Config laden/validieren)
  ├─ CsvReader (CSV einlesen)
  ├─ MetadataExtractor (Metadaten mit Regex)
  ├─ ColumnTransformer (Transformationen anwenden)
  ├─ CsvWriter (CSV schreiben)
  ├─ FireflyImporter (Firefly III Integration)
  └─ DebugLogger (Debug-Protokolle)
Klasse Verantwortung
TransformerEngine Orchestriert gesamten Workflow
ConfigurationLoader Lädt und validiert JSON-Konfiguration
CsvReader Liest CSV mit Metadaten
MetadataExtractor Extrahiert Metadaten mit Regex
ColumnTransformer Transformiert Spalten (Pipeline)
CsvWriter Schreibt CSV
FireflyImporter Importiert in Firefly III
DebugLogger Statischer Logger für Debug

Fehlerbehandlung

Häufige Fehler

"Input file not found"

# Prüfe Dateipfad
ls -la input.csv

# Nutze absoluten Pfad wenn relativ nicht funktioniert
php bin/transformer.php transform /absolute/path/input.csv config.json

"Missing metadata: account_iban"

# Prüfe erste Zeilen des CSV
head -5 input.csv

# Überprüfe lineNumber und regex in config.json
php bin/transformer.php validate config.json input.csv --debug

"Invalid JSON"

php -r "json_decode(file_get_contents('config/config.json'), true) or die('JSON invalid');"

"Configuration: 'csvStructure.headerLine' required"

diff config/config.json config/config.example.json

Version & Änderungen

v1.0.0 (03. Mai 2026)

  • Initial Release
  • 14 Transformationstypen
  • Metadaten-Extraktion mit Regex
  • Debug-Modus
  • Firefly III Integration (cli / docker / http)
  • Vollständige Dokumentation

Lizenz: GPL-3.0
Author: PHP CSV Transformer Project
Repository: git.andare.ch/david.reindl/ff-imp-preprocessor