---
estimated_steps: 18
estimated_files: 5
skills_used: []
---

# T02: Generalize optimizer, create stage 2-4 fixtures, wire CLI, verify end-to-end

Make OptimizationLoop stage-aware: generalize _load_fixture() to validate stage-specific keys, generalize _score_variant() to call the correct prompt and parse the correct schema per stage, and pass stage-appropriate format markers to the variant generator. Create minimal fixture JSON files for stages 2-4. Remove the stage-5 gate in __main__.py's _run_optimize(), add validation for stages 2-5. Verify all stages import and CLI accepts them.

## Context

The optimizer currently has three stage-5-specific hardcodings:
1. `_load_fixture()` expects `creator_name` and `moments` keys — stages 2-4 have different input shapes
2. `_score_variant()` calls synthesis via `SynthesisResult` schema and formats output as a technique page for the scorer
3. The `run()` method loads `stage{N}_synthesis.txt` — stages 2-4 use different prompt file names

The CLI's `_run_optimize()` rejects `args.stage != 5` with an error.

Stage fixture shapes (from research):
- Stage 2: `{transcript_segments: [{index, start_time, end_time, text}]}` — segments of a transcript
- Stage 3: `{topic_label, segments: [{start_time, end_time, text}]}` — a topic group to extract moments from
- Stage 4: `{moments: [{title, summary, content_type, plugins}], taxonomy: "..."}` — moments to classify
- Stage 5: `{creator_name, moments: [...]}` (existing sample_moments.json)

The STAGE_CONFIGS registry from T01 provides the prompt filename, schema class, dimensions, and format markers per stage. This task uses that registry to dispatch correctly.

After changes, `OptimizationLoop.run()` should:
1. Load the prompt file from `STAGE_CONFIGS[stage]['prompt_file']`
2. Load and validate fixture using `STAGE_CONFIGS[stage]['fixture_keys']`
3. In `_score_variant()`, use the stage's schema to parse LLM output, then format it for the scorer's `score_stage_output()`
4. Pass stage-appropriate format markers to variant generator

## Inputs

- ``backend/pipeline/quality/scorer.py` — STAGE_CONFIGS registry and score_stage_output() from T01`
- ``backend/pipeline/quality/variant_generator.py` — generalized generate() from T01`
- ``backend/pipeline/quality/optimizer.py` — existing OptimizationLoop`
- ``backend/pipeline/quality/__main__.py` — existing _run_optimize with stage-5 gate`
- ``backend/pipeline/quality/fixtures/sample_moments.json` — existing stage 5 fixture for regression check`

## Expected Output

- ``backend/pipeline/quality/optimizer.py` — stage-aware OptimizationLoop with generalized _load_fixture, _score_variant, and format marker dispatch`
- ``backend/pipeline/quality/__main__.py` — _run_optimize accepts stages 2-5, rejects others`
- ``backend/pipeline/quality/fixtures/sample_segments.json` — stage 2 fixture`
- ``backend/pipeline/quality/fixtures/sample_topic_group.json` — stage 3 fixture`
- ``backend/pipeline/quality/fixtures/sample_classifications.json` — stage 4 fixture`

## Verification

cd backend && python -c "from pipeline.quality.optimizer import OptimizationLoop; print('optimizer ok')" && python -c "from pipeline.quality.__main__ import main; print('cli ok')" && python -m pipeline.quality optimize --stage 2 --iterations 1 --file pipeline/quality/fixtures/sample_segments.json --help 2>&1 | head -1 && python -m pipeline.quality optimize --stage 6 --file x 2>&1 | grep -q 'stage' && echo 'stage6 rejected ok'
