20 KiB
Firefly Import Preprocessor — Dokumentation
Version: 1.0.0
Datum: 03. Mai 2026
Status: Production Ready
🌐 English
📋 Inhaltsverzeichnis
- Überblick
- Installation & Setup
- Schnellstart
- Konfiguration
- Transformationstypen
- CLI-Referenz
- Debug-Modus
- Firefly III Integration
- Architektur
- 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 CHUR→Coop Pronto Churmigros-rail city→Migros-Rail CityO'NEILL STORE→O'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:00–03: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:
" COOP PRONTO "→ trim →"COOP PRONTO""COOP PRONTO"→ replace →"COOP PRONTO""COOP PRONTO"→ lowercase →"coop pronto""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: ~3–4 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