# CLAUDE.md

This file provides guidance to Claude Code when working with the Suno Recs recommendation system.

## Quick Start

```bash
# Test hook recommendations with real data
uv run modal run suno_recs/worker/modal_recs_orchestrator.py

# if cache issues, try running this first
# uv run modal run suno_recs/worker/modal_hooks_recommender_stub.py::warm_bucket_cache


# View debug report (after getting request_id from above)
# Deploy viewer once: modal deploy suno_recs/worker/modal_report_viewer.py
# Then visit: https://[your-modal-app-url]/view_report?request_id=<request_id>

# Always run before committing
uv run ruff format suno_recs/
uv run ruff check suno_recs/
uv run pyright
```

## Architecture: 7-Bucket Hook Recommendations

The system (`modal_hooks_recommender_stub.py`) uses 7 parallel retrieval buckets:

1. **follow** - Hooks from followed creators
2. **video_similarity** - Visual/theme similarity using description embeddings
   - Seeds: Liked hooks (2x boost) + positive interaction hooks (1x boost)
3. **audio_similarity** - Combined audio similarity from all user signals
   - Single KNN query with mixed seeds for efficiency
   - Seeds: Liked hooks (3x) + positive hooks (1x) + liked clips (2x) + listening clips (1x)
4. **genre_similarity** - Genre matching based on user's creation tags
5. **fresh** - New content discovery
6. **popular** - Popular quality content
7. **manual_champion** - Manually labeled champion content
8. **creator_champion** - Hooks from manually curated champion creators

### Key Features

- **Boost-Based Seed Selection**: Uses configurable boost scores for weighted seed draws
  - Video: `{"liked_hooks": 2, "positive_hooks": 1}` for 2:1 ratio
  - Audio: `{"liked_hooks": 3, "positive_hooks": 1, "liked_clips": 2, "listening_clips": 1}`
- **Efficient KNN**: Only 2 parallel KNN queries (video + audio) instead of 5
- **Per-Result Attribution**: Tracks which seed led to each recommendation
- **Genre Similarity**: Matches hooks based on user's creation tags
- **Result Shuffling**: Optional shuffle while preserving top N positions
- **Dual Diversity**: Enforces song and creator diversity within configurable distances
- **RRF Fusion**: Combines buckets using Reciprocal Rank Fusion with per-bucket weights

### Processing Pipeline

1. Parallel bucket retrieval from Elasticsearch
2. Filter out watched/liked hooks (from Redis watch history)
3. RRF fusion (deduplication + scoring)
4. Apply diversity rules (song + creator)
5. Optional result shuffling
6. Return top N recommendations

## Output Format

```json
{
    "request_id": "abc-123-def",
    "recommendations": [{
        "hook_id": "...",
        "recommendation_item_id": "...",  // Unique tracking ID
        "reasons": ["follow", "genre_similarity"],  // Contributing buckets
        "rrf_rank": 1,  // Original rank
        "display_position": 1,  // Final position (after shuffle)
        "creator_id": "...",
        "gemini_rating": 3
    }]
}
```

## Key Configuration

See `suno_recs/worker/constants.py` for the complete configuration including:
- Hook signal weights (source of truth for available signal types)
- Clip signal weights
- Seed selection limits
- Bucket weights for RRF fusion
- Diversity and shuffling parameters

When adding new signals:
1. Add to `hook_signal_weights` in constants.py
2. Add Redis key pattern to hooks_signals.py
3. Add validation to validate_redis_hooks.py

## Important Implementation Notes

- **Modal Proxy**: Uses `modal.Proxy.from_name("data-layer-proxy")` for internal services
- **Redis Clients**: Multiple specialized clients for watch history, reactions, creation tags, etc.
- **Redis Caching with Thundering Herd Protection**:
  - Cache warmer runs every 5 min with `force_fetch=True` to always refresh TTL & data
  - Workers use `fetch_on_miss=False` to avoid expensive ES queries on cache miss
  - Only the warmer manages cache; workers are read-only to prevent thundering herd
- **Full Reports**: Stored in Modal Dict for 7 days, accessible via web viewer
- **Test User**: ID `62406164` with real followed creators and liked hooks in `main()`

## Troubleshooting

| Issue | Solution |
|-------|----------|
| No recommendations | Check user has liked hooks/followed creators |
| Missing embeddings | Logged as warnings, doesn't break pipeline |
| Modal secrets | Ensure `redis-recs-url` secret configured |