feat(music-lab): market_trends·trend_reports DB + market.py + /api/music/market 5개 API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 12:26:37 +09:00
parent 3b9dcfe0dd
commit 26b9eea0dc
4 changed files with 303 additions and 0 deletions

102
music-lab/app/market.py Normal file
View File

@@ -0,0 +1,102 @@
# music-lab/app/market.py
import os
from collections import Counter, defaultdict
from typing import Any, Dict, List, Optional
from .db import (
get_latest_trend_report, get_trend_reports,
insert_market_trends, upsert_trend_report,
)
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "")
GENRE_PROMPTS: Dict[str, str] = {
"lo-fi": "lo-fi hip hop, chill, relaxing beats, study music, 85 BPM, jazzy chords",
"phonk": "dark phonk, aggressive 808 bass, Memphis trap, distorted synths, 140 BPM",
"ambient": "ambient, atmospheric, ethereal pads, slow evolving textures, no percussion",
"pop": "upbeat pop, catchy melody, modern production, 120 BPM",
"funk": "baile funk, Brazilian funk, energetic, 150 BPM",
"latin": "reggaeton, latin pop, dembow rhythm, 100 BPM",
"general": "music, modern production, wide appeal",
}
def ingest_trends(trends: List[Dict[str, Any]], report_date: str) -> Dict[str, Any]:
"""agent-office 트렌드 수신 → 저장 + 리포트 생성."""
insert_market_trends(trends)
report = _build_report(trends, report_date)
upsert_trend_report(report)
return report
def _build_report(trends: List[Dict[str, Any]], report_date: str) -> Dict[str, Any]:
genre_scores: Dict[str, float] = defaultdict(float)
genre_countries: Dict[str, set] = defaultdict(set)
keywords: List[str] = []
for t in trends:
g = t.get("genre") or "general"
genre_scores[g] += t.get("score", 0.0)
genre_countries[g].add(t.get("country", ""))
kw = t.get("keyword", "")
if kw:
keywords.append(kw)
top_genres = sorted(
[{"genre": g, "score": round(s, 3), "countries": list(genre_countries[g])}
for g, s in genre_scores.items()],
key=lambda x: x["score"], reverse=True,
)[:10]
kw_counts = Counter(keywords)
top_keywords = [kw for kw, _ in kw_counts.most_common(15)]
recommended_styles = [
{
"genre": g["genre"],
"suno_prompt": GENRE_PROMPTS.get(g["genre"], GENRE_PROMPTS["general"]),
"target_countries": g["countries"][:3],
"reason": f"트렌딩 score {g['score']:.2f}",
}
for g in top_genres[:5]
]
return {
"report_date": report_date,
"top_genres": top_genres,
"top_keywords": top_keywords,
"recommended_styles": recommended_styles,
"insights": _generate_insights(top_genres, top_keywords),
}
def _generate_insights(top_genres: list, top_keywords: list) -> str:
if not top_genres:
return "아직 수집된 트렌드 데이터가 없습니다."
if not ANTHROPIC_API_KEY:
names = ", ".join(g["genre"] for g in top_genres[:3])
return f"이번 주 인기 장르: {names}. 해당 장르 중심 제작을 추천합니다."
import anthropic
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
genre_str = ", ".join(f"{g['genre']}({g['score']:.1f})" for g in top_genres[:5])
kw_str = ", ".join(top_keywords[:10])
try:
msg = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=300,
messages=[{"role": "user", "content":
f"YouTube 음악 트렌드 인사이트를 2-3문장으로 요약.\n"
f"인기 장르: {genre_str}\n인기 키워드: {kw_str}"}],
)
return msg.content[0].text.strip()
except Exception:
names = ", ".join(g["genre"] for g in top_genres[:3])
return f"인기 장르: {names}."
def get_suggestions(limit: int = 5) -> List[Dict[str, Any]]:
report = get_latest_trend_report()
if not report:
return []
return report.get("recommended_styles", [])[:limit]