import json, html, os
from datetime import datetime, timezone
with open("/tmp/bulk_results.json") as f:
results = json.load(f)
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
STATUS_ORDER = {
"significant_mismatch": 0,
"not_on_spotify": 1,
"not_in_insights": 2,
"position_drift": 3,
"small_difference": 4,
"spotify_error": 5,
"up_to_date": 6,
}
STATUS_LABEL = {
"up_to_date": "✅ Up to date",
"position_drift": "⚠️ Position drift",
"small_difference": "⚠️ Small difference",
"significant_mismatch": "❌ Significant mismatch",
"not_in_insights": "❌ Not in Insights",
"not_on_spotify": "❌ Not on Spotify",
"spotify_error": "❓ Spotify error",
}
STATUS_CSS = {
"up_to_date": "green",
"position_drift": "amber",
"small_difference": "amber",
"significant_mismatch": "red",
"not_in_insights": "red",
"not_on_spotify": "red",
"spotify_error": "grey",
}
counts = {}
for r in results:
s = r["status"]
counts[s] = counts.get(s, 0) + 1
ok = counts.get("up_to_date", 0)
warn = counts.get("position_drift", 0) + counts.get("small_difference", 0)
err = counts.get("significant_mismatch", 0) + counts.get("not_in_insights", 0) + counts.get("not_on_spotify", 0)
other = counts.get("spotify_error", 0)
results_sorted = sorted(results, key=lambda r: (STATUS_ORDER.get(r["status"], 99), r["playlist_name"].lower()))
def fmt_last_updated(ts):
if not ts: return "—"
try:
s = str(ts).split(".")[0].replace("T", " ")
return s[:16] # YYYY-MM-DD HH:MM
except Exception:
return str(ts)[:16]
PERSONALIZED_TYPES = {"PERSONALIZED", "ALGORITHMIC", "RADIO"}
def diff_color(d):
if d == 0: return "green-text"
if d <= 5: return "amber-text"
return "red-text"
def type_badge(ptype):
if not ptype: return "—"
if ptype in PERSONALIZED_TYPES:
return f'{ptype}'
return f'{ptype}'
def detail_html(r):
parts = []
missing = r.get("missing_from_insights", [])
extra = r.get("extra_in_insights", [])
pos_detail = r.get("position_detail", [])
variants = r.get("possible_variants", [])
if not (missing or extra or pos_detail or variants):
return ""
if variants:
rows = "".join(
f"
{v['position']}
"
f"
{v['spotify_isrc']}
"
f"
{v['insights_isrc']}
"
f"
{html.escape(v.get('spotify_name') or '')}
"
for v in variants)
parts.append(
f"
"
f"
ℹ️ Possible ISRC variants ({len(variants)}): same track at the same position but different ISRCs in Spotify vs Insights. "
f"These are excluded from the ISRC diff count. Likely the same recording released under a different ISRC (e.g. regional re-release).
"
f"Possible ISRC variants ({len(variants)})"
f"
Pos
Spotify ISRC
Insights ISRC
Track (Spotify name)
{rows}
")
if missing:
rows = "".join(f"
{t['position']}
{t['isrc']}
{html.escape(t.get('name') or '')}
" for t in missing)
parts.append(f"
Missing from Insights ({len(missing)})
Pos
ISRC
Track
{rows}
")
if extra:
rows = "".join(f"
{t['position'] if t['position'] is not None else '—'}
{t['isrc']}
{html.escape(t.get('name') or '')}
" for t in extra)
parts.append(f"
Extra in Insights ({len(extra)})
Pos
ISRC
Track
{rows}
")
if pos_detail:
def pos_row(t):
dcls = "red-text" if abs(t["diff"]) > 5 else "amber-text"
sign = "+" if t["diff"] > 0 else ""
sp_positions = t.get("spotify_positions", [t["spotify_pos"]])
sp_display = ", ".join(str(p) for p in sp_positions)
if len(sp_positions) > 1:
sp_display = f'{sp_display} ⚠️'
return f"
{t['isrc']}
{html.escape(t.get('name') or '')}
{sp_display}
{t['insights_pos']}
{sign}{t['diff']}
"
rows = "".join(pos_row(t) for t in pos_detail)
note = ""
dup_isrcs = r.get("duplicate_isrcs", {})
if dup_isrcs:
dup_list = ", ".join(f"{i} (pos {', '.join(str(p) for p in ps)})" for i, ps in dup_isrcs.items())
note = f"
ℹ️ This playlist contains tracks with duplicate ISRCs on Spotify ({dup_list}). Insights only shows one position per track (duplicates are filtered out). Position comparisons for these tracks will be more meaningful once Insights supports showing duplicate tracks.
"
parts.append(f"
{note}Position mismatches ({len(pos_detail)})
ISRC
Track
Spotify pos(s)
Insights pos
Diff
{rows}
")
return "".join(parts)
rows_html = []
for idx, r in enumerate(results_sorted):
pid = r["playlist_id"]
name = html.escape(r["playlist_name"])
status = r["status"]
ptype = r.get("playlist_type")
label = STATUS_LABEL.get(status, status)
if status == "spotify_error" and r.get("spotify_error_code"):
label = f"❓ Spotify error (HTTP {r['spotify_error_code']})"
cls = STATUS_CSS.get(status, "grey")
sp = r["spotify_count"]
ins = r["insights_count"]
diff = r["isrc_diff"]
only_sp = r["only_spotify"]
only_ins = r["only_insights"]
pos = r["pos_mismatches"]
dcls = diff_color(diff)
no_detail = status in ("not_on_spotify", "spotify_error")
has_insights = status not in ("not_in_insights", "not_on_spotify", "spotify_error")
insights_link = f'(Insights Link)' if has_insights else ""
show_last_updated = status != "up_to_date"
last_updated = fmt_last_updated(r.get("insights_latest")) if show_last_updated else ""
drilldown = detail_html(r)
has_drilldown = bool(drilldown)
clickable = ' class="clickable"' if has_drilldown else ""
rows_html.append(f"""
{name}{' ' + insights_link if insights_link else ""}