# S04: Expand to Pipeline Stages 2-4 — UAT

**Milestone:** M013
**Written:** 2026-04-01T09:26:23.089Z

# S04 UAT: Expand to Pipeline Stages 2-4

## Preconditions
- Working directory: `backend/`
- Python environment with project dependencies available
- No live LLM connection required (import/structure tests only)

## Test Cases

### TC1: STAGE_CONFIGS registry completeness
**Steps:**
1. Run: `cd backend && python -c "from pipeline.quality.scorer import STAGE_CONFIGS; assert sorted(STAGE_CONFIGS.keys()) == [2,3,4,5]; print('pass')"`
**Expected:** Prints `pass`. All four stages registered.

### TC2: Per-stage dimensions are distinct
**Steps:**
1. Run: `cd backend && python -c "from pipeline.quality.scorer import STAGE_CONFIGS; dims = {s: cfg.dimensions for s, cfg in STAGE_CONFIGS.items()}; assert dims[2] != dims[5]; assert 'voice_preservation' in dims[5]; assert 'boundary_accuracy' in dims[2]; print('pass')"`
**Expected:** Prints `pass`. Stage 2 has segmentation-specific dims, stage 5 has synthesis-specific dims.

### TC3: ScoreResult backward compatibility
**Steps:**
1. Run: `cd backend && python -c "from pipeline.quality.scorer import ScoreResult; r = ScoreResult(scores={'structural': 0.9, 'readability': 0.7}, composite=0.8); assert r.structural == 0.9; assert r.readability == 0.7; assert r.composite == 0.8; print('pass')"`
**Expected:** Prints `pass`. Named property access works on generalized scores dict.

### TC4: ScoreResult with non-stage-5 dimensions
**Steps:**
1. Run: `cd backend && python -c "from pipeline.quality.scorer import ScoreResult; r = ScoreResult(scores={'coverage_completeness': 0.85, 'boundary_accuracy': 0.6}, composite=0.725); assert r.scores['coverage_completeness'] == 0.85; print('pass')"`
**Expected:** Prints `pass`. Arbitrary dimension names work in scores dict.

### TC5: Stage schema resolution
**Steps:**
1. Run: `cd backend && python -c "from pipeline.quality.scorer import STAGE_CONFIGS; schemas = {s: cfg.get_schema().__name__ for s, cfg in STAGE_CONFIGS.items()}; assert schemas == {2: 'SegmentationResult', 3: 'ExtractionResult', 4: 'ClassificationResult', 5: 'SynthesisResult'}; print('pass')"`
**Expected:** Prints `pass`. Each stage resolves to its correct Pydantic schema class.

### TC6: Variant generator accepts stage parameter
**Steps:**
1. Run: `cd backend && python -c "from pipeline.quality.variant_generator import PromptVariantGenerator; print('pass')"`
**Expected:** Prints `pass`. Generator imports without error.

### TC7: Optimizer accepts all valid stages
**Steps:**
1. Run: `cd backend && python -c "from unittest.mock import MagicMock; from pipeline.quality.optimizer import OptimizationLoop; c=MagicMock(); [OptimizationLoop(stage=s, fixture_path='x', iterations=1, variants_per_iter=1, client=c) for s in [2,3,4,5]]; print('pass')"`
**Expected:** Prints `pass`. Constructor succeeds for stages 2-5.

### TC8: Optimizer rejects invalid stages
**Steps:**
1. Run: `cd backend && python -c "from unittest.mock import MagicMock; from pipeline.quality.optimizer import OptimizationLoop; c=MagicMock(); OptimizationLoop(stage=1, fixture_path='x', iterations=1, variants_per_iter=1, client=c)" 2>&1`
**Expected:** Raises error mentioning invalid/unsupported stage.

### TC9: Fixture loading validates per-stage keys
**Steps:**
1. Run: `cd backend && python -c "from unittest.mock import MagicMock; from pipeline.quality.optimizer import OptimizationLoop; c=MagicMock(); l=OptimizationLoop(stage=2, fixture_path='pipeline/quality/fixtures/sample_segments.json', iterations=1, variants_per_iter=1, client=c); d=l._load_fixture(); assert 'transcript_segments' in d; print('pass')"`
2. Run: `cd backend && python -c "from unittest.mock import MagicMock; from pipeline.quality.optimizer import OptimizationLoop; c=MagicMock(); l=OptimizationLoop(stage=3, fixture_path='pipeline/quality/fixtures/sample_topic_group.json', iterations=1, variants_per_iter=1, client=c); d=l._load_fixture(); assert 'topic_segments' in d; print('pass')"`
3. Run: `cd backend && python -c "from unittest.mock import MagicMock; from pipeline.quality.optimizer import OptimizationLoop; c=MagicMock(); l=OptimizationLoop(stage=4, fixture_path='pipeline/quality/fixtures/sample_classifications.json', iterations=1, variants_per_iter=1, client=c); d=l._load_fixture(); assert 'extracted_moments' in d and 'taxonomy' in d; print('pass')"`
**Expected:** All three print `pass`. Each stage's fixture contains the expected keys.

### TC10: CLI rejects stage 6
**Steps:**
1. Run: `cd backend && python -m pipeline.quality optimize --stage 6 --file x 2>&1`
**Expected:** Error message containing "stage" and exits non-zero.

### TC11: CLI accepts stage 3 with help
**Steps:**
1. Run: `cd backend && python -m pipeline.quality optimize --stage 3 --iterations 5 --help 2>&1 | head -1`
**Expected:** Shows usage line (not an error about invalid stage).

## Edge Cases

### EC1: Stage 5 backward compatibility preserved
**Steps:**
1. Run: `cd backend && python -c "from unittest.mock import MagicMock; from pipeline.quality.optimizer import OptimizationLoop; c=MagicMock(); l=OptimizationLoop(stage=5, fixture_path='pipeline/quality/fixtures/sample_moments.json', iterations=1, variants_per_iter=1, client=c); d=l._load_fixture(); assert 'moments' in d and 'creator_name' in d; print('pass')"`
**Expected:** Prints `pass`. Stage 5 still works with existing fixture format.
