Sort_YM/tests/test_report.py
Andrew Pimenov 0902d86bcd classify tracks by combined track + artist genre signals
Раньше жанр брался только с альбома (одна строка, часто общая:
rock/alternative/pop) - разные по звучанию артисты сваливались в одну
крупную корзину. Теперь classify_track сводит жанр трека и жанры
артиста (client.artists(), несколько тегов на артиста) на уровне
под-жанра: при согласии сигналов или отсутствии одного из них берём
имеющийся; при конфликте - если альбомный тег зонтичный (GENERIC_SLUGS)
доверяем артисту, если точный - треку (вероятный осознанный эксперимент
артиста в другом жанре). Один и тот же артист с разными по духу треками
теперь раскладывается по разным плейлистам.

Под-жанровые группы меньше config.small_group_min схлопываются в
родительскую крупную корзину (report.build_rows, двухпроходно,
без циклов - coarse_of детерминирована от fine).

Попутно: artist_ids трека может быть None (артист без привязки к
каталогу Яндекса, встречается у стороннего/пиратского аплоада) - раньше
это улетало в client.artists() как строка "None" и валилось
BadRequestError на батче; теперь такие id отфильтровываются на записи
и дополнительно на чтении кэша.
2026-07-06 14:26:49 +03:00

64 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from sort_ym import report
CATALOG: dict = {} # classify_track с пустыми artist_genre_lists использует только genre_raw
def _track(id_, title, artists, genre_raw, artist_ids=None):
return {
"id": id_,
"album_id": id_ * 10,
"title": title,
"artists": artists,
"artist_ids": artist_ids or [],
"genre_raw": genre_raw,
"lyrics_available": False,
}
def test_small_fine_group_collapses_to_coarse_bucket():
# 15 "punk"-треков (>= small_group_min=12) остаются отдельным под-жанром,
# 2 "indie"-трека (< 12) схлопываются в родительскую крупную корзину "indie-alt".
tracks_cache = {}
for i in range(15):
tid = f"{i}:{i}"
tracks_cache[tid] = _track(i, f"Punk track {i}", ["Punk Artist"], "punk")
for i in range(15, 17):
tid = f"{i}:{i}"
tracks_cache[tid] = _track(i, f"Indie track {i}", ["Indie Artist"], "indie")
rows = report.build_rows(
tracks_cache=tracks_cache,
lang_cache={},
genre_catalog=CATALOG,
artist_genres={},
small_group_min=12,
)
punk_rows = [r for r in rows if r["genre_raw"] == "punk"]
indie_rows = [r for r in rows if r["genre_raw"] == "indie"]
assert len(punk_rows) == 15
assert all(r["bucket"] == "punk" for r in punk_rows), "крупная под-группа - отдельный плейлист"
assert all(r["fine_bucket"] == "punk" for r in punk_rows)
assert len(indie_rows) == 2
assert all(r["bucket"] == "indie-alt" for r in indie_rows), "мелкая под-группа схлопнута в крупную корзину"
assert all(r["fine_bucket"] == "indie" for r in indie_rows), "fine_bucket сохраняет исходную классификацию"
def test_collapse_is_single_pass_and_terminates():
# Группа ровно на границе порога (== small_group_min) НЕ схлопывается (строгое <).
tracks_cache = {
f"{i}:{i}": _track(i, f"T{i}", ["Artist"], "punk") for i in range(12)
}
rows = report.build_rows(
tracks_cache=tracks_cache,
lang_cache={},
genre_catalog=CATALOG,
artist_genres={},
small_group_min=12,
)
assert all(r["bucket"] == "punk" for r in rows)