Skip to content

feat: prefer videoOwnerChannelTitle for YT artist (0.8) - #12

Merged
zeikar merged 4 commits into
mainfrom
feat/yt-videoowner-channel-0.8
May 14, 2026
Merged

feat: prefer videoOwnerChannelTitle for YT artist (0.8)#12
zeikar merged 4 commits into
mainfrom
feat/yt-videoowner-channel-0.8

Conversation

@zeikar

@zeikar zeikar commented May 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Swap _youtube_to_record's artist source from snippet.channelTitle (playlist owner — always empty for playlistItems) to snippet.videoOwnerChannelTitle (actual uploader) with channelTitle fallback and whitespace-only handling.
  • Strip the ytmusic-auto-generated " - Topic" suffix from the artist field only; classifier still sees the unstripped channel so the - Topic music-signal heuristic in classify.py:34-35 continues to fire.
  • 8 new _youtube_to_record unit tests covering preference, fallback chain, whitespace, Topic strip variants, both-missing, and classifier preservation.
  • Docs: 0.8 row added to README.md version table, 0.8 roadmap entry added to docs/ARCHITECTURE.md, sync-state-model bullet added to CLAUDE.md describing the one-time migration spike behaviour.

Why

All 1128 YT tracks in the live DB had artists=[] because channelTitle in playlistItems is the playlist owner (= user, always empty). Real uploader is videoOwnerChannelTitle. Probe-verified — see .hyperclaude/plans/20260513-2150-likesurgeon-0-8-yt-channeltitle-to-videoowner.md.

Live measurement (operator e2e run on real DB, backup: ~/.like-surgeon/like-surgeon.sqlite.pre-0.8.bak, PRAGMA integrity_check = ok)

Metric Before 0.8 (YT #29) After 0.8 (YT #33)
YT snapshot size 1128 1128
youtube_music_count (classifier output) 954 954 (preserved)
Empty-artists, scoped to rows w/ videoOwnerChannelTitle 1128 0
Empty-artists, unscoped 1128 0
Artists ending in " - Topic" n/a 0 (strip working)
Stage 1 / 2 / 3 / 4 matches 947 / 0 / 0 / 0 947 / 0 / 0 / 0
stage4_ratio (stage4_enrichment / total_matched) 0.0000 0.0000

Stage distribution is flat because stage-1 (video_id) already catches 947/954 music items on this dataset — the new artist signal has no headroom to help here. The fix lays the groundwork for stage-2/3 gains on datasets with more video_id misses.

metadata_drift one-time migration spike (verified)

Compare run YT snapshots compared metadata_drift count
Diagnosis #19 (first post-0.8) #29 (pre-0.8, empty artists) vs #33 (post-0.8, populated) 1128 ← migration noise
Diagnosis #20 (second post-0.8) #33 vs #34 (both post-0.8) 0 ← real upstream drift signal

Reason text on the spike rows is artists: [] → ['<artist>'] with title unchanged (sim 1.00), confirming pure ingestion-change noise. This matches the operator-gotcha bullet added to CLAUDE.md.

Validation target f9DzbpmWbMo (irucaice — Rainy Step feat. Hatsune Miku)

The plan predicted this would flip from possibly_missing_from_ytmusic to a stage-3 match after the fix. It did not flip. Honest read:

  • artists now populated correctly: ["irucaice / いるかアイス"] (was [])
  • but fuzz.token_set_ratio against the YTM equivalent (RANoZl9UXJM, "Rainy Step (feat. Hatsune Miku)" by ["irucaice"]) is 81.7, below the default threshold of 85
  • the YT title carries a "irucaice - " structural prefix the YTM title lacks, plus the artist normalization differs ("irucaice / いるかアイス" vs "irucaice")

So this finding remains in possibly_missing_from_ytmusic. The fuzz threshold tuning is a separate decision deferred to 0.9.

Test plan

  • uv run pytest tests/ — 303 passed (8 new _youtube_to_record tests)
  • uv run ruff format --check . — clean
  • uv run ruff check — clean
  • Manual: scan youtube-likes against live DB → snapshot #33 (1128 videos, 954 music-like, 4 unavailable) — classifier counts identical to pre-0.8
  • Manual: scoped empty-artists query against snapshot_items returns 0 post-scan
  • Manual: compare-likes → Diagnosis #19 (first post-0.8) shows the predicted 1128 metadata_drift spike
  • Manual: second scan + compare-likes → Diagnosis #20 shows 0 metadata_drift (spike cleared, as documented in CLAUDE.md)

Operator notes (post-merge)

Anyone with an existing DB must run a one-time scan youtube-likes to repopulate artists. The first subsequent compare-likes will show a large metadata_drift spike that clears on the next scan + compare-likes round. CLAUDE.md sync-state-model section has the verbatim gotcha.

Copilot AI review requested due to automatic review settings May 14, 2026 09:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves YouTube snapshot ingestion so the inferred artists field reflects the actual uploader (videoOwnerChannelTitle) instead of the playlist owner (channelTitle), while preserving the existing “Topic channel” music-candidate signal used by the YouTube classifier.

Changes:

  • Update _youtube_to_record to prefer snippet.videoOwnerChannelTitle, with channelTitle fallback and whitespace-only handling.
  • Strip the trailing " - Topic" suffix from the stored artists (and therefore canonical_key) while still classifying using the unstripped channel.
  • Add targeted unit tests for the new channel-resolution and Topic-stripping behavior; update docs for the 0.8 release/operator migration note.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/likesurgeon/snapshot.py Changes YouTube channel/artist resolution, adds Topic suffix stripping helper, preserves classifier input behavior.
tests/test_snapshot.py Adds unit tests covering owner-title preference, fallback chain, whitespace handling, Topic stripping, and classifier behavior.
README.md Documents 0.8 behavior and the one-time migration/drift spike expectation.
docs/ARCHITECTURE.md Adds 0.8 roadmap entry describing ingestion change and expected migration effects.
CLAUDE.md Adds an operator “gotcha” note about the one-time scan + drift spike behavior post-upgrade.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +97 to +105
def _strip_topic_suffix(name: str) -> str:
"""Strip a trailing ' - Topic' suffix (case-sensitive) from a channel name.

Whitespace is trimmed from the right before checking. No-op if absent.
"""
stripped = name.rstrip()
if stripped.endswith(" - Topic"):
return stripped[: -len(" - Topic")]
return stripped
@zeikar
zeikar merged commit c508877 into main May 14, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants