Migration history and reports¶
The History workspace reopens any migration owned by the current user. It extends the existing statistics view with account context, selected collections, lifecycle timestamps, duration, acknowledged warnings, final outcome, target links, item-level results, prior review decisions, and the terminal error when a job failed.
Inspecting item history¶
GET /api/migrations/{job_id}/items returns entity-typed ledger rows ordered by
type, collection/entity name, and position. Existing clients can omit paging
parameters and continue receiving the full array. The History UI sends limit and
offset; the response includes the matching total in X-Total-Count.
Supported filters:
| Query parameter | Meaning |
|---|---|
source_playlist_id |
Exact source collection ID |
entity_type |
Repeatable track, album, or artist filter |
status |
Repeatable exact status filter |
min_confidence / max_confidence |
Inclusive 0.0–1.0 match confidence |
reason |
Case-insensitive literal substring |
title |
Case-insensitive literal substring |
artist |
Case-insensitive literal substring |
problem_only |
Restrict to needs_review, skipped, and failed |
limit / offset |
Optional paging; limit is capped at 500 |
The endpoint returns 404 when the job is not owned by the current user and 410
when item detail has expired.
Downloading reports¶
GET /api/migrations/{job_id}/report generates a report on demand and never stores
an exported file.
format=csv|jsonscope=all|problems- Every item filter above is also accepted.
The UI provides one-click CSV and JSON downloads for all rows and for problem rows. Active entity, collection, status, confidence, title, artist, and reason filters are included in the download URL. Both formats stream from a server-side database cursor in configurable batches, so the complete result is never loaded into application memory.
CSV is UTF-8 with a byte-order mark for spreadsheet compatibility. Every field is
quoted, commas/quotes/newlines use standard CSV escaping, and text beginning with a
spreadsheet formula marker (=, +, -, or @) is prefixed with an apostrophe.
JSON is a streamed object with report/job/filter metadata and an items array.
Stable report fields¶
Report schema version 2 adds explicit mixed-entity identity fields and uses the
following columns. JSON preserves
source_metadata and job_warnings as structured values; CSV serializes them as
compact JSON strings.
| Field | Description |
|---|---|
report_version |
Stable report schema version |
job_id |
Migration job ID |
job_status |
Persisted worker status |
job_outcome |
pending, running, completed, partial, or failed |
job_error |
Terminal job error, if any |
job_warnings |
Warnings acknowledged before starting |
job_created_at |
Job creation timestamp |
job_started_at |
Worker start timestamp |
job_completed_at |
Terminal timestamp |
detail_expires_at |
Item-detail retention deadline |
source_provider |
Source provider key |
source_account_id |
Historical source account ID |
target_provider |
Target provider key |
target_account_id |
Historical target account ID |
item_id |
Ledger item ID |
entity_type |
track, album, or artist |
source_entity_id |
Generic source entity ID; falls back to source metadata for tracks |
source_entity_name |
Generic source display name |
target_entity_id |
Generic matched/written target entity ID |
source_playlist_id |
Source collection ID for track items; empty for albums/artists |
source_playlist_name |
Source collection name for track items |
target_playlist_id |
Mapped target collection ID for track items |
position |
Source position |
title |
Track/album title or artist name |
artist |
Track/album artist; artist items repeat the source name in the raw ledger |
album |
Track album; empty for album/artist entities |
duration_s |
Track duration in seconds |
release_year |
Source release year |
explicit |
Explicit-content flag |
isrc |
Source ISRC |
source_track_id |
Provider source track ID; empty for albums/artists |
source_item_id |
Provider source collection-entry ID |
source_uri |
Source provider URI |
source_metadata |
Complete universal Track, Album, or Artist metadata |
target_uri |
Selected/written target URI |
target_id |
Provider item ID parsed from the target URI |
confidence |
Match confidence from 0.0 to 1.0 |
status |
Final item status |
reason |
Match, skip, or failure reason |
review_action |
Prior approve or skip action |
review_original_status |
Status before the review decision |
review_original_reason |
Reason shown before the review decision |
reviewed_at |
Review decision timestamp |
item_created_at |
Ledger-row creation timestamp |
item_updated_at |
Last ledger-row update timestamp |
Reports never include encrypted credentials, refresh/access tokens, raw provider authentication responses, or another user's jobs.
Retention¶
OPE_MIGRATION_HISTORY_RETENTION_DAYS defaults to 90.
0retains item detail indefinitely.- Job summaries, per-entity counts, warnings, lifecycle timestamps, selected collection mappings, and terminal errors are retained indefinitely.
- After the deadline, item and report endpoints return
410immediately. - The ARQ worker runs bounded cleanup hourly at minute 17. It snapshots the ledger
summary, then deletes expired
job_itemandoperation_ledgerrows in batches controlled byOPE_MIGRATION_HISTORY_CLEANUP_BATCH_SIZE. - Accepted track, album, and artist review decisions are retained separately so cleanup does not make future migrations forget previously approved matches. Decisions are scoped by user, provider/account history, and entity type; a validated album or artist decision can never become a track candidate.
OPE_MIGRATION_REPORT_BATCH_SIZEcontrols the database streaming batch size.
Changing the retention setting applies to new jobs and to older terminal jobs that do not yet have a persisted deadline.
API contract¶
The backend FastAPI contract is checked in at
openapi/open-playlist-engine.json. Regenerate it and the frontend TypeScript schema
after changing an API model or route:
cd backend
.venv/bin/python - <<'PY' > ../openapi/open-playlist-engine.json
import json
from app.main import app
print(json.dumps(app.openapi(), indent=2, sort_keys=True))
PY
cd ../frontend
npm run gen:api