---
estimated_steps: 8
estimated_files: 3
skills_used: []
---

# T01: Multi-token AND keyword search

Refactor `SearchService.keyword_search()` in `backend/search_service.py`:
1. Tokenize query by whitespace: `tokens = query.split()`
2. For each token, build an OR condition across: TechniquePage.title, TechniquePage.summary, TechniquePage.topic_category, and a joined Creator.name. Also check if token appears in topic_tags array (use `func.array_to_string(TechniquePage.topic_tags, ' ').ilike(pattern)`).
3. AND all per-token conditions together so every token must match at least one field.
4. Same pattern for KeyMoment search: join Creator via SourceVideo, check title/summary/creator.name.
5. For Creator search: each token must match name or genres.
6. Add `partial_matches` logic: if zero AND results, re-query with individual tokens, score results by how many tokens they match, return top 5 as partial_matches.
7. Extend SearchResponse schema with `partial_matches: list[SearchResultItem]` (default empty).

## Inputs

- `backend/search_service.py`
- `backend/schemas.py`

## Expected Output

- `backend/search_service.py (refactored keyword_search with multi-token AND)`
- `backend/schemas.py (SearchResponse with partial_matches)`

## Verification

Run targeted curl: `curl 'http://localhost:8001/api/v1/search?q=keota+snare'` returns results matching both tokens. `curl 'http://localhost:8001/api/v1/search?q=xyznonexistent+snare'` returns empty items but partial_matches with snare-only results.
