---
estimated_steps: 54
estimated_files: 6
skills_used: []
---

# T01: Build review queue API endpoints with Redis mode toggle and integration tests

Create the complete review queue backend: new Pydantic schemas for review actions, a review router with 9 endpoints (list queue, stats, approve, reject, edit, split, merge, get mode, set mode), Redis-backed runtime mode toggle, mount in main.py, and comprehensive integration tests. Follow existing async SQLAlchemy patterns from routers/creators.py.

## Steps

1. Add review-specific Pydantic schemas to `backend/schemas.py`: `ReviewQueueItem` (KeyMomentRead + video title + creator name), `ReviewQueueResponse` (paginated), `ReviewStatsResponse` (counts per status), `MomentEditRequest` (editable fields: title, summary, start_time, end_time, content_type, plugins), `MomentSplitRequest` (split_time: float), `ReviewModeResponse` and `ReviewModeUpdate` (mode: bool).

2. Create `backend/routers/review.py` with these async endpoints:
   - `GET /review/queue` — List key moments filtered by `status` query param (pending/approved/edited/rejected/all), paginated with `offset`/`limit`, joined with SourceVideo.filename and Creator.name. Default filter: pending. Order by created_at desc.
   - `GET /review/stats` — Return counts grouped by review_status (pending, approved, edited, rejected) using SQL count + group by.
   - `POST /review/moments/{moment_id}/approve` — Set review_status=approved, return updated moment. 404 if not found.
   - `POST /review/moments/{moment_id}/reject` — Set review_status=rejected, return updated moment. 404 if not found.
   - `PUT /review/moments/{moment_id}` — Update editable fields from MomentEditRequest, set review_status=edited, return updated moment. 404 if not found.
   - `POST /review/moments/{moment_id}/split` — Split moment at `split_time` into two moments. Validate split_time is between start_time and end_time. Original keeps [start_time, split_time), new gets [split_time, end_time]. Both keep same source_video_id and technique_page_id. Return both moments. 400 on invalid split_time.
   - `POST /review/moments/{moment_id}/merge` — Accept `target_moment_id` in body. Merge two moments: combined summary, min(start_time), max(end_time), delete target, return merged result. Both must belong to same source_video. 400 if different videos. 404 if either not found.
   - `GET /review/mode` — Read current mode from Redis key `chrysopedia:review_mode`. If not in Redis, fall back to `settings.review_mode` default.
   - `PUT /review/mode` — Set mode in Redis key `chrysopedia:review_mode`. Return new mode.

3. Add Redis client helper. Create a small `backend/redis_client.py` module with `get_redis()` async function using `redis.asyncio.Redis.from_url(settings.redis_url)`. Import in review router.

4. Mount the review router in `backend/main.py`: `app.include_router(review.router, prefix="/api/v1")`.

5. Add `redis` (async redis client) to `backend/requirements.txt` if not already present.

6. Create `backend/tests/test_review.py` with integration tests using the established conftest patterns (async client, real PostgreSQL):
   - Test list queue returns empty when no moments exist
   - Test list queue returns moments with video/creator info after seeding
   - Test filter by status works (seed moments with different statuses)
   - Test stats endpoint returns correct counts
   - Test approve sets review_status=approved
   - Test reject sets review_status=rejected
   - Test edit updates fields and sets review_status=edited
   - Test split creates two moments with correct timestamps
   - Test split returns 400 for invalid split_time (outside range)
   - Test merge combines two moments correctly
   - Test merge returns 400 for moments from different videos
   - Test approve/reject/edit return 404 for nonexistent moment
   - Test mode get/set (mock Redis)

## Must-Haves

- [ ] All 9 review endpoints return correct HTTP status codes and response bodies
- [ ] Split validates split_time is strictly between start_time and end_time
- [ ] Merge validates both moments belong to same source_video
- [ ] Mode toggle reads/writes Redis, falls back to config default
- [ ] All review tests pass alongside existing test suite
- [ ] Review router mounted in main.py

## Failure Modes

| Dependency | On error | On timeout | On malformed response |
|------------|----------|-----------|----------------------|
| PostgreSQL | SQLAlchemy raises, FastAPI returns 500 | Connection timeout → 500 | N/A (ORM handles) |
| Redis (mode toggle) | Return 503 with error detail | Timeout → fall back to config default | N/A (simple get/set) |

## Negative Tests

- **Malformed inputs**: split_time outside moment range → 400, merge moments from different videos → 400, edit with empty title → validation error
- **Error paths**: approve/reject/edit/split nonexistent moment → 404, merge with nonexistent target → 404
- **Boundary conditions**: split at exact start_time or end_time → 400, merge moment with itself → 400, empty queue → empty list

## Verification

- `cd backend && python -m pytest tests/test_review.py -v` — all tests pass
- `cd backend && python -m pytest tests/ -v` — no regressions (all existing tests still pass)
- `python -c "from routers.review import router; print(len(router.routes))"` — prints 9 (routes registered)

## Observability Impact

- Signals added: INFO log on each review action (approve/reject/edit/split/merge) with moment_id
- How a future agent inspects: `GET /api/v1/review/stats` shows pending/approved/edited/rejected counts
- Failure state exposed: 404 responses include moment_id that was not found, 400 responses include validation details

## Inputs

- ``backend/models.py` — KeyMoment, SourceVideo, Creator models with review_status enum`
- ``backend/schemas.py` — existing Pydantic schemas to extend`
- ``backend/database.py` — get_session async dependency`
- ``backend/config.py` — Settings with review_mode and redis_url`
- ``backend/main.py` — router mount point`
- ``backend/routers/creators.py` — pattern reference for async SQLAlchemy endpoints`
- ``backend/tests/conftest.py` — test fixtures (db_engine, client, sync_engine, pre_ingested_video)`

## Expected Output

- ``backend/routers/review.py` — review queue API router with 9 endpoints`
- ``backend/redis_client.py` — async Redis client helper`
- ``backend/schemas.py` — extended with review-specific Pydantic schemas`
- ``backend/main.py` — updated to mount review router`
- ``backend/requirements.txt` — updated with redis dependency`
- ``backend/tests/test_review.py` — integration tests for all review endpoints`

## Verification

cd backend && python -m pytest tests/test_review.py -v && python -m pytest tests/ -v
