Sort_YM/tests/test_genres.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

97 lines
4.2 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.

import pytest
from sort_ym import genres
@pytest.fixture
def catalog():
# Форма реального дерева client.genres(): "rock"/"rusrock"/"hardrock"/"dnb" - подгенры,
# "allrock"/"alternative"/"indie"/"punk"/"rap"/"metal" - настоящие корни (root_id == сам слаг).
# Проверено на живом каталоге (cache/genre_catalog.json).
return {
"allrock": {"title": "Рок", "root_id": "allrock"},
"rock": {"title": "Иностранный рок", "root_id": "allrock"},
"rusrock": {"title": "Русский рок", "root_id": "allrock"},
"hardrock": {"title": "Хард-рок", "root_id": "allrock"},
"alternative": {"title": "Альтернатива", "root_id": "alternative"},
"indie": {"title": "Инди", "root_id": "indie"},
"punk": {"title": "Панк", "root_id": "punk"},
"metal": {"title": "Метал", "root_id": "metal"},
"numetal": {"title": "Ню-метал", "root_id": "metal"},
"rap": {"title": "Рэп и хип-хоп", "root_id": "rap"},
"trap": {"title": "Трэп", "root_id": "rap"},
"electronics": {"title": "Электроника", "root_id": "electronics"},
"dnb": {"title": "Драм-н-бейс", "root_id": "electronics"},
}
@pytest.mark.parametrize(
"genre_raw,expected",
[
("allrock", "rock"),
("rock", "rock"),
("rusrock", "rock"),
("alternative", "indie-alt"),
("rap", "rap"),
("trap", "rap"),
(None, "other"),
("", "other"),
("totally-unknown-slug", "other"),
],
)
def test_bucket_for(catalog, genre_raw, expected):
assert genres.bucket_for(genre_raw, catalog) == expected
def test_flatten_catalog_walks_sub_genres():
class FakeGenre:
def __init__(self, id_, title, sub_genres=None):
self.id = id_
self.title = title
self.sub_genres = sub_genres or []
root = FakeGenre("rock", "Рок", [FakeGenre("rusrock", "Русский рок")])
flat = genres.flatten_catalog([root])
assert flat["rock"] == {"title": "Рок", "root_id": "rock"}
assert flat["rusrock"] == {"title": "Русский рок", "root_id": "rock"}
@pytest.mark.parametrize(
"fines,expected",
[
([], None),
(["indie"], "indie"),
(["indie", "metal", "indie"], "indie"),
(["indie", "metal"], "indie"), # равенство частот -> первый по порядку
],
)
def test_dominant(fines, expected):
assert genres.dominant(fines) == expected
@pytest.mark.parametrize(
"genre_raw,artist_genre_lists,expected",
[
# согласие трека и артиста -> берём фактический (гранулярно по треку)
("punk", [["rock", "punk"]], "punk"),
# конфликт: зонтичный альбомный тег ("rock" в GENERIC_SLUGS) -> доверяем артисту
# (кейс New Politics: альбом стоит "rock", у артиста только "indie")
("rock", [["indie"]], "indie"),
# конфликт: точный альбомный тег (не в GENERIC_SLUGS) -> доверяем треку, даже если
# у артиста другой основной жанр (настоящий эксперимент артиста в ином жанре)
("dnb", [["indie"]], "electronics"),
# пустой genre_raw -> едем по артисту
(None, [["indie"]], "indie"),
("", [["indie"]], "indie"),
# у артиста нет тегов -> едем по треку (genre_raw "rock" резолвится в корень "allrock")
("rock", [[]], "allrock"),
# несколько артистов на треке -> голосование dominant по объединённым тегам
("rock", [["indie"], ["indie", "metal"]], "indie"),
# ничего не помогло
(None, [[]], "other"),
],
)
def test_classify_track(catalog, genre_raw, artist_genre_lists, expected):
assert genres.classify_track(genre_raw, artist_genre_lists, catalog) == expected