This folder contains one-shot harvest scripts that stream canonical MusicBrainz data from mbdump and load SQLite tables used by Tagminder master-data workflows.
These scripts are designed to be run directly with uv. They are not part of the numbered ETL pipeline shown by tm-cli list.
The harvesters in this folder are scoped to canonical entity and relationship metadata only.
Included:
- artist canonical entities
- artist-artist relationships
- work canonical entities (works, aliases, ISWCs, language, artist roles, work-work relationships — all in one pass)
- Wikidata music identity enrichment
- contributor disambiguation and export
- AllMusic lookup export for contributor MNIDs missing from
amg_artists
Not part of canonical work-identification flow:
- recording canonical entities (MBID->recording-id bridge)
- recording-work relationships (recording->work bridge)
Excluded by contract:
- release-layer metadata and non-work serving entities (tracks, releases, release-groups)
Work-identification core:
- canonical_works_metadata
- Python environment synced via uv
- MusicBrainz dump archive available locally (mbdump.tar.bz2)
- writable SQLite output path(s)
Configuration is loaded from harvest_master_data.toml.
Resolution order used by these scripts:
- current working directory
- parent directories of the script path
- fail with FileNotFoundError
Required keys:
- [musicbrainz].dump_archive
- [musicbrainz].contributors_db
Run from repo root (recommended):
uv run python scripts/mdm/harvest/harvest_mb_artists.py
uv run python scripts/mdm/harvest/harvest_mb_artist_relationships.py
uv run python scripts/mdm/harvest/harvest_mb_works.py
uv run python scripts/mdm/harvest/harvest_wikimedia.py
uv run python scripts/mdm/harvest/emit_contributors.py
uv run python scripts/mdm/harvest/amg_todo_list.py
uv run python scripts/mdm/harvest/check_master_data_readiness.py --strictRecording bridge scripts (excluded from canonical run order):
uv run python scripts/mdm/harvest/harvest_mb_recordings.py
uv run python scripts/mdm/harvest/harvest_mb_recording_work_relationships.pyRecommended order:
- harvest_mb_artists.py
- harvest_mb_artist_relationships.py
- harvest_mb_works.py
- harvest_wikimedia.py
- emit_contributors.py
- amg_todo_list.py
- check_master_data_readiness.py --strict
Strict dependencies:
- Run harvest_mb_artists.py before harvest_mb_artist_relationships.py.
- Run harvest_mb_artists.py before harvest_mb_works.py if you want contributor display names in role columns.
- Run harvest_mb_artists.py and harvest_wikimedia.py before emit_contributors.py.
Recording bridge scripts are maintained in this folder but intentionally excluded from the canonical work-identification run order.
harvest_mb_artists.py
- musicbrainz_artists
harvest_mb_artist_relationships.py
- musicbrainz_artist_relationships
- musicbrainz_artist_relationship_attributes
harvest_mb_works.py (single-pass tar scan; consolidates works, aliases, ISWCs, language, roles, and work-work relationships)
- canonical_works_metadata
harvest_wikimedia.py
- wikidata_music_identity
emit_contributors.py
- contributors_unified_disambiguated
- contributors_unified_namesakes
- wikimedia_data_quality_issues
- unmatched_wikidata_music_identity
- unmatched_amg_artists
- EXCEPTION_wikidata_music_identity_mbid_not_in_musicbrainz_artists_review
amg_todo_list.py
- amg_lookups.tsv (TSV export; overwritten on each run)
amg_todo_list.py output contract
- Purpose: backlog of AllMusic MNIDs referenced by contributor outputs but missing from
amg_artists. - Scans all three MNID sources from both
contributors_unified_disambiguatedandcontributors_unified_namesakes:allmusic_mnidmusicbrainz_allmusic_mnidwikimedia_allmusic_mnid
- Emits one unified TSV with source identifiers:
mnid_sourcesource_tableallmusic_mnidallmusic_artistallmusic_urlallmusic_genres_jsonallmusic_styles_json
harvest_mb_recordings.py (recording bridge — not in canonical run order)
- musicbrainz_recordings
harvest_mb_recording_work_relationships.py (recording bridge — not in canonical run order)
- musicbrainz_recording_work_relationships
- musicbrainz_recording_work_relationship_attributes
- musicbrainz_work_work_relationship_attributes
harvest_mb_recording_work_relationships.py
- musicbrainz_recording_work_relationships
- musicbrainz_recording_work_relationship_attributes
build_mb_work_lookup.py
- canonical_works_lookup
Each harvester drops and recreates its own output tables on each run, then repopulates from source dump streams.
Implication:
- Re-running a script replaces that script's table set completely.
- If you need cross-table consistency for a domain, run both scripts for that domain in sequence.
Use the database path from harvest_master_data.toml ([musicbrainz].contributors_db).
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_artists', COUNT(*) FROM musicbrainz_artists;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_artist_relationships', COUNT(*) FROM musicbrainz_artist_relationships;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_artist_relationship_attributes', COUNT(*) FROM musicbrainz_artist_relationship_attributes;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_works', COUNT(*) FROM musicbrainz_works;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_recordings', COUNT(*) FROM musicbrainz_recordings;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_work_aliases', COUNT(*) FROM musicbrainz_work_aliases;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_work_identifiers', COUNT(*) FROM musicbrainz_work_identifiers;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_work_artist_relationships', COUNT(*) FROM musicbrainz_work_artist_relationships;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_work_artist_relationship_attributes', COUNT(*) FROM musicbrainz_work_artist_relationship_attributes;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_work_work_relationships', COUNT(*) FROM musicbrainz_work_work_relationships;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_work_work_relationship_attributes', COUNT(*) FROM musicbrainz_work_work_relationship_attributes;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_recording_work_relationships', COUNT(*) FROM musicbrainz_recording_work_relationships;"
sqlite3 /tmp/amg/master-data.db "SELECT 'musicbrainz_recording_work_relationship_attributes', COUNT(*) FROM musicbrainz_recording_work_relationship_attributes;"
sqlite3 /tmp/amg/master-data.db "SELECT 'canonical_works_lookup', COUNT(*) FROM canonical_works_lookup;"- Relationship tables include a normalized attributes table plus a single per-edge attributes_json cache column.
- The design avoids duplicating equivalent JSON payloads across multiple tables.
- The flattened table
canonical_works_lookupis a serving layer for fast vectorized enrichment joins; canonical truth remains in normalized tables.