mirror of
https://github.com/Horizontal42/Sort_YM.git
synced 2026-11-10 15:42:02 +03:00
Раньше жанр брался только с альбома (одна строка, часто общая: 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 отфильтровываются на записи и дополнительно на чтении кэша.
64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
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)
|