feat(rec): audio analyzer and My Wave v1 engine (M4)
- migration 00006: track features and embeddings (pgvector, HNSW), analysis queue filled by a trigger on new tracks, wave sessions and impressions, recommender profiles, model versions - analyzer basic-1 (ADR-014): ffmpeg decode, three 30 s excerpts, 256-d timbre/rhythm embedding, tempo by onset autocorrelation, key, loudness, energy, danceability, acousticness, intro length; worker with SKIP LOCKED claims, retries and re-analysis on version change - wave engine v1: candidates (session/taste similarity, fresh, liked artists, rediscover, popular, exploration, seed), filters (dislikes, played in 3 h, already served), heuristic score, activity and mood fit from contexts.yaml with relaxing thresholds, MMR with one track per artist, exploration share, session vector updated by reactions and a change of course after three skips - POST /internal/wave/batch with the catalog index cached in memory and the session kept in Redis Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
976ffb574b
commit
ef29b85320
20 files changed
+1530
-13
No files matched your search
@@ -1,15 +1,17 @@
|
||||
"""Точка входа: `pipu-rec serve` (онлайн-API) или `pipu-rec analyze` (воркер аудиоанализа)."""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import psycopg
|
||||
import redis.asyncio as aioredis
|
||||
import uvicorn
|
||||
|
||||
from pipu_rec.analysis.worker import Worker
|
||||
from pipu_rec.api.app import create_app
|
||||
from pipu_rec.settings import Settings
|
||||
from pipu_rec.wave.contexts import load_contexts
|
||||
from pipu_rec.wave.service import WaveService
|
||||
|
||||
log = logging.getLogger("pipu_rec")
|
||||
|
||||
@@ -24,19 +26,19 @@ def _serve(settings: Settings) -> None:
|
||||
async def check_redis() -> None:
|
||||
await rdb.ping()
|
||||
|
||||
app = create_app(settings, {"postgres": check_postgres, "redis": check_redis})
|
||||
wave = WaveService(settings.database_url, settings.redis_url, load_contexts(settings.contexts_path))
|
||||
app = create_app(settings, {"postgres": check_postgres, "redis": check_redis}, wave)
|
||||
uvicorn.run(app, host=settings.http_host, port=settings.http_port, log_level=settings.log_level)
|
||||
|
||||
|
||||
def _analyze(settings: Settings, workers: int) -> None:
|
||||
# Аудиоанализ (Essentia) появится в M4; сейчас воркер только держит процесс живым.
|
||||
log.info("analyzer started: workers=%d version=%s (no-op until M4)", workers, settings.version)
|
||||
|
||||
async def idle() -> None:
|
||||
while True:
|
||||
await asyncio.sleep(3600)
|
||||
|
||||
asyncio.run(idle())
|
||||
Worker(
|
||||
settings.database_url,
|
||||
settings.library_root,
|
||||
settings.library_host_root,
|
||||
workers,
|
||||
settings.ffmpeg_path,
|
||||
).run_forever()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
||||
Reference in new issue
Block a user