25 Commits

Author SHA1 Message Date
540862fcad Add profile-aware feedback with vibe context
Feedback buttons now target the listener's profile instead of the
dashboard filter profile. Adds a persistent vibe context input that
replaces the hardcoded "general" vibe. Shows listener profile badge
and track ID on each listen item. Adds manual feedback form for
submitting feedback on any track by ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 09:25:29 -06:00
65e1a3c88b Fix profile create 307 redirect by adding trailing slash
POST /api/profiles was being redirected to /api/profiles/ (307),
which drops the request body. Match the FastAPI route definition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 08:22:22 -06:00
3e66f31df9 Add interactive mutation UI to dashboard
Dashboard now supports all existing mutation APIs: feedback
(thumbs up/down, retract), profile CRUD, speaker mapping,
playlist generation, track discovery, taste rebuild, and
requeue failed embeddings. All controls use vanilla JS fetch
with toast notifications.

New endpoint: POST /api/admin/requeue-failed resets failed
embedding tracks back to pending.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 08:08:23 -06:00
551b4c6ff9 Add profile selector and filtering to dashboard
Profile pill selector at top of page filters Recent Listens,
Feedback Activity, and Vibe Influence by selected profile.
Feedback items show profile badge when viewing all profiles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:03:56 -06:00
8101871877 Add profile-scoped feedback endpoint
New POST /api/profiles/{name}/feedback accepts explicit vibe text and
records feedback against a named profile. GET history endpoint added too.
Scoring now filters feedback by profile_name for profile-aware playlists.
Migration 005 adds profile_name column and makes playlist_id nullable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:41:42 -06:00
af6159a297 Add automatic skip detection for playlist playback
Background poller monitors HA media_player state during playlist sessions.
When a track transition occurs and the previous track was played < 40% of
its duration, automatically records "skip" feedback. Also includes the
previously uncommitted delete_feedback endpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:17:52 -06:00
9f301497df Show vibe text on playlist items in dashboard
The vibe is the core concept of vibe-aware playlists but wasn't
visible anywhere on the dashboard. Now each recent playlist shows
its vibe text alongside track count and age.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 08:24:31 -06:00
31f13b1efb Add vibe-contextual feedback system with dashboard observability
Adds feedback API endpoints that record up/down/skip signals tied to
vibe context (CLAP embeddings + text). Dashboard now shows Feedback
Activity (recent events with signal counts) and Vibe Influence (how
the same track gets rated differently across vibes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 07:34:00 -06:00
ef61e275b2 Redesign status page as full dashboard
Add recent listens, profiles, taste profiles, and recent playlists
to the status page. Two-column responsive grid layout with progress
bar for embeddings and relative timestamps throughout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:52:41 -06:00
094621a9a8 Add named taste profiles for per-person recommendations
Named profiles allow each household member to get personalized
recommendations without polluting each other's taste. Includes
profile CRUD API, speaker→profile auto-attribution, recent listen
history endpoint, and profile param on all existing endpoints.
All endpoints backward compatible (no profile param = "default").

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:14:34 -06:00
1b739fbd20 Add vibe-aware playlists with CLAP text embeddings
Blend taste profile with text-embedded mood descriptions (e.g. "chill
ambient lo-fi") using pre-blended vector search against the existing
HNSW index. New optional `vibe` and `alpha` params on playlist generate
and recommendations endpoints. Backward compatible — no vibe = pure
taste profile (alpha=1.0).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:14:28 -06:00
23fd0e9804 Auto-rebuild taste profile on every new listen event
The taste profile is just a weighted average of 512-dim vectors — trivially
cheap even with thousands of tracks. Rebuilding on every listen event keeps
recommendations always up to date.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:39:32 -06:00
57b6d333e9 Add comprehensive README and fix speaker entity in CLAUDE.md
Full documentation covering architecture, deployment, API endpoints,
speaker entity mapping, pipeline stages, and how recommendations
improve over time. Fixed stale speaker entity reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:36:40 -06:00
c9cdec6680 Deduplicate listen events from multiple HA entities
When a track plays, multiple HA entities (Cast, WiFi, MA) all fire
the automation simultaneously, creating 3x duplicate listen events.
Now skips logging if the same track was recorded within the last 60s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:02:03 -06:00
448be52f4a Fix playlist playback: use MA enqueue and search resolution
The play_playlist_on_speaker function was sending text search queries
to raw Cast entities which can't resolve them. Now uses enqueue: "replace"
for the first track and "add" for subsequent tracks. Added 1s delay between
requests so MA can process each Apple Music search. Increased HTTP timeout
to 30s for search latency.

The caller must pass a Music Assistant entity (_2 suffix) for text-based
search to work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:48:33 -06:00
2c6ba345b1 Fix pgvector embedding format in recommender query
profile.embedding was being passed as str(numpy_array) which produces
scientific notation format. pgvector needs [n1,n2,...] format. Now
explicitly formats as comma-separated float list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:30:02 -06:00
6a2be93556 Fix CLAP embedding extraction for transformers 5.x
In transformers 5.x, ClapModel.get_audio_features() returns a
BaseModelOutputWithPooling instead of a raw tensor. The 512-dim
embedding is in .pooler_output[0], not directly indexed. Added
backward-compatible extraction with hasattr check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:55:38 -06:00
e9cf1e9b17 Fix embedding dimensions and worker session management
Two issues:
1. CLAP model output needed .flatten() to produce a 1-D vector for
   pgvector. Without it, the nested array caused "expected ndim to be 1".
2. Worker now uses a fresh session per track instead of sharing one
   across a batch, preventing PendingRollbackError cascading from one
   failure to the next.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:43:44 -06:00
771f714384 Fix error handling in embedding worker: capture actual exception message
The except clause wasn't binding the exception to a variable, so
str(Exception) stored the class name "<class 'Exception'>" instead of
the actual error message. Now properly captures `as e` and stores str(e).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:34:11 -06:00
07e22550fe Fix CLAP processor: audios -> audio kwarg
transformers 5.x renamed the parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:23:02 -06:00
92a75d6432 Add ForeignKey declarations to SQLAlchemy model columns
SQLAlchemy relationships require ForeignKey on the column definitions,
not just in the migration. Without them, mapper initialization fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:05:50 -06:00
f8c13daa11 Fix SQLAlchemy import: Real -> REAL
SQLAlchemy exports REAL (uppercase), not Real.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 08:59:10 -06:00
179071b817 Fix Dockerfile: copy source before pip install
The pyproject.toml references the package source, so src/ must be
present when pip resolves metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 08:51:45 -06:00
7ff69449d6 Initial haunt-fm implementation
Full music recommendation pipeline: listening history capture via webhook,
Last.fm candidate discovery, iTunes preview download, CLAP audio embeddings
(512-dim), pgvector cosine similarity recommendations, playlist generation
with known/new track interleaving, and Music Assistant playback via HA.

Includes: FastAPI app, SQLAlchemy models, Alembic migrations, Docker Compose
with pgvector/pg17, status dashboard, and all API endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 08:36:36 -06:00
897d0fe1fb Initial commit 2026-02-22 14:04:01 +00:00