feat(setbuilder): TrackVibe LLM enrichment + community vibe display (read) (#391)#422
Conversation
… note - community_vibe.py: module docstring notes that override rows are FULL SNAPSHOTS and the v1.1 write endpoint must carry forward unchanged fields; CommunityVibe dataclass docstring defines sample_size. - vibe_resolver.py: dedupe keys via dict.fromkeys before IN-clause queries in _own_overrides and _llm_vibes; community_consensus already deduped in-place. - community_vibe.py: community_consensus dedupes track_keys via dict.fromkeys so tracks sharing a vibe_key don't generate duplicate IN values. - test_setbuilder_vibes.py: three edge-case tests added to TestCommunityConsensus: stddev exactly at threshold is rejected, single-vote with min_sample=1 passes, all-null retraction row supersedes the earlier vote (retraction test).
GET /api/setbuilder/sets/{id}/pool/vibes returns the three-tier vibe
state (own / community / LLM, plus per-field resolution with source
provenance) for every pool track. POST .../pool/vibes/enrich runs the
batch LLM enrichment through the gateway (5/min — each call fans out
to BATCH_SIZE-track dispatches) and returns run stats plus the
refreshed state. NoLlmConfigured maps to a friendly 400. Both
endpoints keep owner-private 404 semantics.
Migration 057 (1899c49) added vibe_consensus_min_sample / vibe_consensus_max_stddev to SystemSettings but the exact-key contract test was not updated, failing on branch since that commit.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds three‑tier TrackVibe support: DB migration and settings, backend consensus/enrichment/resolver services, new setbuilder GET/POST pool-vibes API and OpenAPI/TS surface, VibeTiers UI + CSS, PoolPanel toggle/analyze integration, and extensive tests and docs. ChangesTrackVibe Enrichment Feature
Sequence Diagram(s)sequenceDiagram
participant User
participant PoolPanel
participant API_Get as GET /pool/vibes
participant API_Enrich as POST /pool/vibes/enrich
participant Resolver
participant DB
participant Gateway
User->>PoolPanel: click "Vibes"
PoolPanel->>PoolPanel: toggleVibes()
alt vibes not loaded
PoolPanel->>API_Get: request PoolVibesState
API_Get->>Resolver: build_pool_vibe_states(set_id)
Resolver->>DB: read overrides, community votes, LLM cache
Resolver-->>API_Get: PoolVibesState
API_Get-->>PoolPanel: PoolVibesState
PoolPanel->>PoolPanel: buildVibeMap()
end
User->>PoolPanel: click "Analyze"
PoolPanel->>API_Enrich: request enrichment
API_Enrich->>DB: check cache, determine missing keys
API_Enrich->>Gateway: dispatch batched tool calls for missing tracks
Gateway-->>API_Enrich: tool-call responses
API_Enrich->>DB: persist TrackVibe rows
API_Enrich-->>PoolPanel: VibeEnrichmentResult + refreshed PoolVibesState
PoolPanel->>PoolPanel: update vibes map, show toast
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
server/tests/test_setbuilder_vibes_api.py (1)
59-59: 💤 Low valueConsider adding return type annotation for consistency.
The helper
_llm_vibe_rowis missing a return type annotation, while_fake_responseon line 72 includes one. Adding-> TrackVibewould improve consistency and readability.♻️ Suggested fix
-def _llm_vibe_row(track_id: str = "tidal:0", *, energy: int = 5, confidence: float = 0.8): +def _llm_vibe_row(track_id: str = "tidal:0", *, energy: int = 5, confidence: float = 0.8) -> TrackVibe:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/tests/test_setbuilder_vibes_api.py` at line 59, The helper function _llm_vibe_row lacks a return type annotation—add "-> TrackVibe" to its signature to match the style of _fake_response and improve consistency; update the function definition for _llm_vibe_row(track_id: str = "tidal:0", *, energy: int = 5, confidence: float = 0.8) to include the return type TrackVibe and ensure any imports or type references needed for TrackVibe are available in the test file.dashboard/app/(dj)/setbuilder/setbuilder.module.css (1)
619-619: ⚡ Quick winUse
remunits for font-size to match file conventions and improve accessibility.Line 619 declares
font-size: 10px, but every other font-size in this file usesremunits (e.g., line 359 uses0.625remfor the same 10px equivalent). Usingremrespects user browser font-size preferences and maintains consistency with the codebase.♻️ Proposed fix
.vibeChip { display: inline-flex; align-items: center; gap: 4px; - font-size: 10px; + font-size: 0.625rem; padding: 1px 6px; border-radius: 4px; border: 1px solid var(--border, `#3a3a3a`); color: var(--text-secondary); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@dashboard/app/`(dj)/setbuilder/setbuilder.module.css at line 619, Replace the hard-coded "font-size: 10px" in the affected rule in setbuilder.module.css with the equivalent rem unit used throughout the file (0.625rem) so it matches the project's rem-based convention and respects user/browser font-size settings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@dashboard/app/`(dj)/setbuilder/components/PoolPanel.tsx:
- Around line 163-176: The toggleVibes handler sets vibesLoaded before the async
getPoolVibes completes, so a failed fetch leaves vibesLoaded true and prevents
retries; change the flow in toggleVibes (and related state calls) so you do not
set vibesLoaded(true) up-front — instead call setVibesBusy(true) then kick off
api.getPoolVibes(setId) and on success call setVibes(buildVibeMap(...)) and
setVibesLoaded(true); on failure clear vibesBusy and also ensure vibesLoaded is
false (or not set) and propagate the toast; keep the final finally to clear
vibesBusy. Reference: toggleVibes, api.getPoolVibes, setVibesLoaded,
setVibesBusy, setVibes, setToast.
In `@dashboard/lib/api-types.generated.ts`:
- Around line 11217-11247: The generated operation
enrich_pool_vibes_api_setbuilder_sets__set_id__pool_vibes_enrich_post is missing
the documented 400 response for NoLlmConfigured; update that operation's
responses to include a 400 entry (headers and content "application/json")
pointing to the appropriate error schema (e.g.,
components["schemas"]["HTTPError"] or the specific NoLlmConfigured schema) so
the frontend can type-check and handle the user-facing 400 error.
In `@server/openapi.json`:
- Around line 16492-16512: Add the missing 400 response for POST
/pool/vibes/enrich by updating the route definition to include a 400 response
that references the friendly error schema (e.g., "NoLlmConfigured") and then
regenerate the OpenAPI spec so server/openapi.json includes that response;
specifically, modify the POST /pool/vibes/enrich route source to add a
responses["400"] entry with description "No LLM configured" and content
application/json -> schema -> $ref to the NoLlmConfigured (or equivalent
friendly error) component, then run the OpenAPI generation step to produce an
updated openapi.json containing the 400 entry.
---
Nitpick comments:
In `@dashboard/app/`(dj)/setbuilder/setbuilder.module.css:
- Line 619: Replace the hard-coded "font-size: 10px" in the affected rule in
setbuilder.module.css with the equivalent rem unit used throughout the file
(0.625rem) so it matches the project's rem-based convention and respects
user/browser font-size settings.
In `@server/tests/test_setbuilder_vibes_api.py`:
- Line 59: The helper function _llm_vibe_row lacks a return type annotation—add
"-> TrackVibe" to its signature to match the style of _fake_response and improve
consistency; update the function definition for _llm_vibe_row(track_id: str =
"tidal:0", *, energy: int = 5, confidence: float = 0.8) to include the return
type TrackVibe and ensure any imports or type references needed for TrackVibe
are available in the test file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e53711ae-f2b8-49b1-bf86-9afd0d87139e
📒 Files selected for processing (27)
dashboard/app/(dj)/setbuilder/components/PoolPanel.tsxdashboard/app/(dj)/setbuilder/components/VibeTiers.tsxdashboard/app/(dj)/setbuilder/components/__tests__/PoolPanel.test.tsxdashboard/app/(dj)/setbuilder/components/__tests__/VibeTiers.test.tsxdashboard/app/(dj)/setbuilder/setbuilder.module.cssdashboard/app/admin/settings/__tests__/page.test.tsxdashboard/lib/api-types.generated.tsdashboard/lib/api-types.tsdashboard/lib/api.tsdocs/superpowers/plans/2026-06-10-trackvibe-llm-enrichment.mdserver/alembic/versions/057_add_vibe_consensus_settings.pyserver/app/api/admin.pyserver/app/api/setbuilder.pyserver/app/models/system_settings.pyserver/app/schemas/setbuilder.pyserver/app/schemas/system_settings.pyserver/app/services/llm/base.pyserver/app/services/llm/gateway.pyserver/app/services/setbuilder/community_vibe.pyserver/app/services/setbuilder/vibe_enrichment.pyserver/app/services/setbuilder/vibe_resolver.pyserver/app/services/system_settings.pyserver/openapi.jsonserver/tests/test_api_contracts.pyserver/tests/test_llm_gateway.pyserver/tests/test_setbuilder_vibes.pyserver/tests/test_setbuilder_vibes_api.py
…h endpoint - PoolPanel: only set vibesLoaded after a successful getPoolVibes, so a failed initial fetch can be retried on the next toggle-on; guard with vibesBusy to prevent duplicate in-flight GETs. Regression test added. - Declare the 400 (NoLlmConfigured) response on POST /pool/vibes/enrich and regenerate openapi.json + api-types.generated.ts so the typed contract includes the error case the frontend special-cases. Addresses CodeRabbit review on PR #422.
Closes #391
Summary
services/setbuilder/vibe_enrichment.py): enriches uncached pool tracks via the LLM gateway with forcedtool_use(20 tracks/call,purpose="vibe_enrichment"), writing to the globalTrackVibecache (table pre-existed from the Phase-0 scaffold). 100 tracks cost exactly 5 gateway calls; a second DJ with the same tracks pays zero (test-pinned).services/setbuilder/community_vibe.py): aggregatesTrackVibeOverridevotes (latest row per track+user); energy consensus gated onsample_size >= 3 AND pstdev < 1.5, mood on strict majority — both thresholds admin-tunable via newSystemSettingscolumns (migration 057,down_revision="056").services/setbuilder/vibe_resolver.py): per-field cascade own override → community consensus → LLM cache, withlow_confidenceflagging (confidenceNone or < 0.5).GET /api/setbuilder/sets/{id}/pool/vibes(60/min) andPOST .../pool/vibes/enrich(5/min,NoLlmConfigured→ friendly 400). Owner-private 404 semantics. OpenAPI + generated dashboard types regenerated.Vibestoggle +Analyzebutton; read-onlyVibeTierschips (You / Crowd / AI) per track, sample-size on the community chip, per-field winner highlight, low-confidence AI guesses flagged with dashed-amber ⚠ styling.ChatResponse.providerpopulated from the resolved connector'sconnector_typeso the cache can storellm_provider/llm_modelprovenance.Design decisions
model_hint="fast"deviation:ChatRequesthas no speed-tier concept — dispatch usesmodel=None, so the DJ's connector-configured model governs. A cross-provider fast/smart mapping would mean refactoring the gateway (out of scope); documented in-code.ChatResponsedidn't surface which connector served a call. Minimal additiveproviderfield, populated in the gateway's_attemptsuccess path viamodel_copy— no other gateway behavior changed (streaming path intentionally untouched).SetPoolTrack.track_idis nullable, so the fallback key issig:{dedupe_sig}(normalized artist+title hash) — globally stable, consistent with the namespaced free-formtrack_idconvention, and lets manual tracks share the global cache.TrackViberow matching(track_id, prompt_version, schema_version)counts regardless of provider/model — that's what makes "second DJ pays zero" true across different connectors. Newest row wins at read time; bumpingPROMPT_VERSIONin code lazily invalidates old rows.LlmErrormarks the current batch + all remaining tracks failed and stops (no provider hammering);NoLlmConfiguredmaps to a friendly 400. A mid-batch insert race keeps the paid LLM rows (re-query winners, re-insert only missing — test-pinned).TrackVibeOverrideexisted from the Phase-0 scaffold (migration 046), so no new table was needed; per the issue note, no write endpoints were added (write UX is v1.1) — this PR is the read path only.LlmVibeOut.low_confidence) so the frontend stays dumb; threshold 0.5.PATCH /api/admin/settings(bounds 1–100 / 0.1–5.0, 422-tested); admin-UI form controls deferred.Test plan
alembic upgrade head && alembic check(single head 057), pytest — 2749 passed, coverage 88.38% (≥85%)tsc --noEmit, vitest — 1118 passed, coverage thresholds holdtest_100_tracks_costs_5_calls,test_second_run_fully_cached_even_for_other_dj, resolver tier tests (test_own_override_wins/test_community_beats_llm/test_llm_fallback/test_no_tiers_resolves_none)openapi.json+api-types.generated.tsverified byte-identical to regenerationSummary by CodeRabbit
New Features
Chores
Tests
Docs