mirror of
https://github.com/Horizontal42/Sort_YM.git
synced 2026-11-10 15:42:02 +03:00
render lyrics digest grouped by mood
This commit is contained in:
parent
8eae62fb63
commit
d0682f8341
2 changed files with 123 additions and 0 deletions
|
|
@ -439,6 +439,56 @@ def render_digest(
|
||||||
return "\n".join(lines).rstrip() + "\n"
|
return "\n".join(lines).rstrip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def _lyrics_track_names(tracks_cache: dict[str, dict]) -> dict[str, str]:
|
||||||
|
names: dict[str, str] = {}
|
||||||
|
for t in tracks_cache.values():
|
||||||
|
names[str(t["id"])] = f"{', '.join(t['artists'])} — {t['title']}"
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def render_lyrics_digest(tracks_cache: dict[str, dict], analysis: dict[str, dict]) -> str:
|
||||||
|
names = _lyrics_track_names(tracks_cache)
|
||||||
|
entries = [(tid, e) for tid, e in analysis.items() if "error" not in e]
|
||||||
|
failed = len(analysis) - len(analyzed_entries(analysis))
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# Разбор текстов (RU-треки)",
|
||||||
|
"",
|
||||||
|
f"Разобрано треков: {len(entries)}. Группировка по основному настроению; "
|
||||||
|
"цитата приводится только если сверена с текстом дословно.",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
by_mood: dict[str, list[tuple[str, dict]]] = {}
|
||||||
|
for tid, entry in entries:
|
||||||
|
by_mood.setdefault(entry["mood"][0], []).append((tid, entry))
|
||||||
|
|
||||||
|
for mood, items in sorted(by_mood.items(), key=lambda kv: (-len(kv[1]), kv[0])):
|
||||||
|
lines.append(f"## {mood} ({len(items)})")
|
||||||
|
lines.append("")
|
||||||
|
for tid, entry in sorted(items, key=lambda kv: names.get(kv[0], kv[0])):
|
||||||
|
lines.append(f"### {names.get(tid, f'трек {tid}')}")
|
||||||
|
lines.append(
|
||||||
|
f"- Настроение: {', '.join(entry['mood'])}; арка: {entry['emotional_arc']}; "
|
||||||
|
f"ракурс: {entry['pov']}; тон: {entry['stance']}; речь: {entry['register']}; "
|
||||||
|
f"конкретность: {entry['concreteness']}"
|
||||||
|
)
|
||||||
|
lines.append(f"- Темы: {', '.join(entry['themes'])}")
|
||||||
|
if entry.get("key_line_verified"):
|
||||||
|
lines.append(f"- Цитата: «{entry['key_line']}»")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(entry["summary"])
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"Чем может цеплять: {entry['resonance']}")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
if failed:
|
||||||
|
lines.append(f"Не разобрано: {failed} (таймаут или сбой модели, повторный `analyze` попробует снова).")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
return "\n".join(lines).rstrip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
def write_digest(text: str, out_dir: Path, filename: str = DIGEST_FILE) -> Path:
|
def write_digest(text: str, out_dir: Path, filename: str = DIGEST_FILE) -> Path:
|
||||||
out_dir.mkdir(parents=True, exist_ok=True)
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
out_file = out_dir / filename
|
out_file = out_dir / filename
|
||||||
|
|
|
||||||
|
|
@ -330,3 +330,76 @@ def test_render_digest_with_analysis_includes_lyrics_section():
|
||||||
text = digest.render_digest(rows, tracks_cache, {}, top_artists=10, top_albums=10, analysis=ANALYSIS)
|
text = digest.render_digest(rows, tracks_cache, {}, top_artists=10, top_albums=10, analysis=ANALYSIS)
|
||||||
|
|
||||||
assert "## Лирика (RU-подмножество)" in text
|
assert "## Лирика (RU-подмножество)" in text
|
||||||
|
|
||||||
|
|
||||||
|
LYRICS_TRACKS = {
|
||||||
|
"100:10": {"id": 100, "album_id": 10, "title": "Тоска", "artists": ["Артист"]},
|
||||||
|
"200:20": {"id": 200, "album_id": 20, "title": "Город", "artists": ["Другой"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
FULL_ANALYSIS = {
|
||||||
|
"100": {
|
||||||
|
"mood": ["melancholy", "longing"],
|
||||||
|
"themes": ["city", "memory"],
|
||||||
|
"emotional_arc": "descent",
|
||||||
|
"pov": "confessional",
|
||||||
|
"register": "conversational",
|
||||||
|
"concreteness": "abstract",
|
||||||
|
"stance": "sincere",
|
||||||
|
"confidence": 0.9,
|
||||||
|
"summary": "Герой бродит по городу.",
|
||||||
|
"resonance": "Узнаваемое чувство одиночества.",
|
||||||
|
"key_line": "А я иду, шагаю по Москве",
|
||||||
|
"key_line_verified": True,
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"mood": ["melancholy"],
|
||||||
|
"themes": ["city"],
|
||||||
|
"emotional_arc": "static",
|
||||||
|
"pov": "narrative",
|
||||||
|
"register": "poetic",
|
||||||
|
"concreteness": "concrete",
|
||||||
|
"stance": "ironic",
|
||||||
|
"confidence": 0.8,
|
||||||
|
"summary": "Наблюдение за городом.",
|
||||||
|
"resonance": "Городская меланхолия.",
|
||||||
|
"key_line": "Пересказ вместо цитаты",
|
||||||
|
"key_line_verified": False,
|
||||||
|
},
|
||||||
|
"300": {"error": "TimeoutError: модель зависла"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_lyrics_digest_lists_tracks_with_narrative():
|
||||||
|
text = digest.render_lyrics_digest(LYRICS_TRACKS, FULL_ANALYSIS)
|
||||||
|
|
||||||
|
assert "Артист — Тоска" in text
|
||||||
|
assert "Герой бродит по городу." in text
|
||||||
|
assert "Узнаваемое чувство одиночества." in text
|
||||||
|
assert "city, memory" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_lyrics_digest_omits_unverified_key_line():
|
||||||
|
text = digest.render_lyrics_digest(LYRICS_TRACKS, FULL_ANALYSIS)
|
||||||
|
|
||||||
|
assert "А я иду, шагаю по Москве" in text
|
||||||
|
assert "Пересказ вместо цитаты" not in text, "непроверенная цитата - вероятный пересказ модели"
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_lyrics_digest_reports_failed_tracks_without_rendering_them():
|
||||||
|
text = digest.render_lyrics_digest(LYRICS_TRACKS, FULL_ANALYSIS)
|
||||||
|
|
||||||
|
assert "TimeoutError" not in text
|
||||||
|
assert "Не разобрано: 1" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_lyrics_digest_groups_by_primary_mood():
|
||||||
|
text = digest.render_lyrics_digest(LYRICS_TRACKS, FULL_ANALYSIS)
|
||||||
|
|
||||||
|
assert "## melancholy (2)" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_lyrics_digest_handles_track_missing_from_cache():
|
||||||
|
text = digest.render_lyrics_digest({}, {"100": FULL_ANALYSIS["100"]})
|
||||||
|
|
||||||
|
assert "трек 100" in text
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue