A configuration-driven ETL pipeline for converting FileMaker CSV exports to Darwin Core Archive (DwC-A) output for biodiversity data publishing.
- YAML-based configuration for each pipeline
- Separation of extraction, transformation, and loading logic
- Pure transformation functions with tests
- Darwin Core Archive generation with occurrence and multimedia output
- Optional MySQL/MariaDB upsert support
- Rotating file logging
- Docker support for reproducible runs
etl_filemaker_dwc/
├── config-files/ # YAML configuration files for different datasets
├── extraction/ # CSV extraction module
├── transformation/ # Data transformation functions
├── loading/ # Database and file output handlers
├── utils/ # Logging and utility functions
├── tests/ # Unit and integration tests
├── data/ # Runtime input symlink or mounted path
├── output/ # Runtime output symlink or mounted path
├── logs/ # Runtime log symlink or mounted path
├── main.py # ETL orchestration script
├── requirements.txt # Python dependencies
├── Makefile # Build and run commands
└── Dockerfile # Container definition
- Python 3.12+
- pip
- (Optional) Docker for containerized deployment
-
Clone the repository
git clone <repository-url> cd etl_filemaker_dwc
-
Create virtual environment
python -m venv .venv source .venv/bin/activate # On Windows: .venv\Scripts\activate
-
Install dependencies
pip install -r requirements.txt
-
Configure environment
cp env.template .env # Edit .env with your database credentials
Local execution in the virtualenv:
make run-local CONFIG_FILE=config-files/algae.ymlDirect Python execution:
python main.py config-files/algae.ymlUsing Make:
make run CONFIG_FILE=config-files/algae.ymlUsing Docker:
make build
make run CONFIG_FILE=config-files/algae.ymlmake run expects .env, config-files/, data/, output/, and logs/ to
be available in the working tree. Use env.template as the local environment
template.
Each dataset requires a YAML configuration file with the following sections:
dataset: dataset_name
dwca_metadata:
dataset_name: "Dataset Name"
description: "Archive-level dataset description"
citation: "Preferred citation text"
rights: "Usage rights statement"
license: "https://creativecommons.org/licenses/by/4.0/"
occurrence:
defaults:
basisOfRecord: PreservedSpecimen
institutionCode: S
extract:
delimiter: ;
encoding: utf8
srcFilePath: ./data/input.csv
mapping:
source_column: dwc_term
transformations:
- function: clean_whitespace
params: {}
- function: generate_occ_id_triplet
params: {}
load:
targetFilePath: ./output/occurrence.csv
write_to_file: true
write_to_db: false
write_to_dwca: true
dwcaPath: ./output/archive.zip
batch_size: 1000
multimedia:
# Similar structure for multimedia extension
merges:
- source: katalog
left_on: katalogID
right_on: katalogID
how: leftThe migrated configs currently include:
config-files/afossil.ymlconfig-files/pfossil.ymlconfig-files/EVmain.ymlconfig-files/EVtype.ymlconfig-files/fish.ymlconfig-files/herptiles.ymlconfig-files/mammals.ymlconfig-files/birds.ymlconfig-files/fbo.ymlconfig-files/algae.ymlconfig-files/fungi.ymlconfig-files/mosses.ymlconfig-files/pollen.yml
If write_to_dwca: true, the config must include a top-level dwca_metadata block.
dataset_name,description,citation,rights, andlicenseare required.- These values are archive-level publication metadata, not transformation settings.
- They should be reviewed by the dataset owner or curator before publishing.
- Taxonomic group or collection names can often be inferred from the config, but official publication titles and citation text should not be assumed.
The active transformation modules are:
transformation/generic.pytransformation/domain_pal.pytransformation/coordinates.pytransformation/dates.py
Common functions used by the current configs include:
clean_whitespacegenerate_occ_id_tripletdrop_empty_rowsdrop_duplicate_rowscreate_dateconvert_date_columnsgenerate_dms_coordinates_columnselect_matched_stringdrop_matched_stringmerge_columnsclean_column_sexclean_column_lifestagepal_move_continentspal_move_oceanspal_fix_synonyms
Run the test suite through the Makefile:
make testRun with coverage:
.venv/bin/python -m pytest --cov=. --cov-report=htmlThe ETL process generates:
- CSV Files: Tab-delimited occurrence and multimedia files
- Darwin Core Archive: ZIP file containing:
occurrence.txt- Core occurrence datamultimedia.txt- Multimedia extension (if applicable)meta.xml- Archive metadataeml.xml- Ecological Metadata Language document
- Batch Processing: Configurable batch sizes for database operations (default: 1000 rows)
- Vectorized Operations: Pandas vectorization for efficient data transformation
- Optimized Fuzzy Matching: Unique-value caching for fuzzy string matching
The project follows strict code quality standards:
- Type Hints: All functions use Python type annotations
- Docstrings: NumPy-style documentation
- Linting: Flake8 configuration in
.flake8 - Testing: Comprehensive unit and integration tests
- Purity: Transformation functions are side-effect free
- Add the function to the appropriate module, usually
transformation/generic.py,transformation/domain_pal.py,transformation/coordinates.py, ortransformation/dates.py. - Import and register it in
transformation/transform.pyif it should be callable from YAML. - Add or update tests in
tests/test_transformation.py. - Update the relevant YAML config file.
Missing Files: If a source file is missing, the ETL will log a warning and continue processing other sources.
Database Connection: Ensure .env file contains valid DB_USER and DB_PASSWORD.
DwC-A Metadata: If write_to_dwca: true, the config must include a top-level dwca_metadata block with dataset metadata.
Memory Issues: For large datasets, increase batch size or process in chunks.
Merge Key Errors: If the ETL fails with a merge error such as KeyError: 'art id', confirm the input CSV header exists after extraction and that the
merge keys in the config match the processed column names exactly.
Mixed-Type Whitespace Cleanup: clean_whitespace now preserves non-string
values inside object columns. If you see unexpected values in a transformed
column, inspect the source CSV types rather than assuming string cleanup failed.
Check logs/app.log for detailed execution logs with timestamps and error traces.
[Add your license information]
[Add contributor information]
Built with:
- pandas - Data manipulation
- SQLAlchemy - Database ORM
- dwcahandler - Darwin Core Archive creation
- fuzzywuzzy - Fuzzy string matching