# S04: Pipeline Compose-or-Create Logic — UAT

**Milestone:** M014
**Written:** 2026-04-03T01:34:24.402Z

## UAT: Pipeline Compose-or-Create Logic

### Preconditions
- PostgreSQL running with current schema (body_sections_format column on technique_pages, technique_page_videos table)
- Stage 5 compose prompt file exists at expected path (stage5_compose.txt)
- At least one creator with processed video data in the database

### Test 1: Fresh synthesis (no existing page)
**Steps:**
1. Process a video for a creator+category combination that has no existing technique page
2. Check the resulting technique_pages row

**Expected:**
- Standard synthesis path runs (no compose log message)
- `body_sections_format = 'v2'` on the created page
- `technique_page_videos` row exists linking the page to the source video

### Test 2: Compose into existing page
**Steps:**
1. Process a second video by the same creator with moments in the same topic category as Test 1
2. Check API logs for compose detection
3. Query technique_pages for that creator+category

**Expected:**
- Log message: `Stage 5: Composing into existing page '<title>' (N existing moments + M new moments)`
- Only one technique_pages row for that creator+category (not duplicated)
- `technique_page_videos` has two rows: one for each video
- Page content reflects merged content from both videos

### Test 3: Case-insensitive category matching
**Steps:**
1. Create a technique page with topic_category = 'Sound Design'
2. Process a new video whose moments are classified as 'sound design' (lowercase)

**Expected:**
- Compose path triggered (not a new page created)
- Case difference does not cause duplicate pages

### Test 4: Multiple matching pages warning
**Steps:**
1. Manually insert two technique pages for the same creator_id + topic_category
2. Process a new video for that creator+category

**Expected:**
- WARNING log about multiple matching pages
- Compose proceeds using the first match
- No crash or error

### Test 5: TechniquePageVideo idempotency
**Steps:**
1. Reprocess a video that already has a technique_page_videos row

**Expected:**
- No duplicate row inserted (on_conflict_do_nothing)
- No error raised

### Test 6: Unit test suite
**Steps:**
1. Run `PYTHONPATH=backend python -m pytest backend/pipeline/test_compose_pipeline.py -v`

**Expected:**
- 12 tests pass covering prompt XML structure, offset indices, empty existing moments, page JSON, moment content, compose branch detection, create fallback, LLM call wiring, v2 format, pg_insert usage, INSERT values, and func.lower matching
