Coverage for narrative_harm_classifier/api/routes/validate.py: 58%

24 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-20 13:25 +0000

1""" 

2api/routes/validate.py — Validation endpoints. 

3Runs held-out sample validation and returns performance reports. 

4""" 

5 

6from fastapi import APIRouter, Depends, HTTPException 

7from narrative_harm_classifier.core.models import ValidationSample, ValidationReport 

8from narrative_harm_classifier.core.config import get_settings, Settings 

9from narrative_harm_classifier.classifier.factory import build_validator 

10from narrative_harm_classifier.classifier.validators.performance import ( 

11 PerformanceValidator, 

12 DEHUMANIZATION_VALIDATION_SAMPLES, 

13) 

14 

15router = APIRouter() 

16 

17 

18def get_validator(settings: Settings = Depends(get_settings)) -> PerformanceValidator: 

19 return build_validator(settings) 

20 

21 

22@router.post( 

23 "/dehumanization", 

24 response_model=ValidationReport, 

25 summary="Run Phase 1 end-to-end validation for priority category: dehumanization", 

26 description=( 

27 "Validates the dehumanization category against the built-in held-out sample set. " 

28 "Checks Precision ≥ 0.70, Recall ≥ 0.65, FPR ≤ 0.20. " 

29 "This is the Phase 1 milestone validation gate." 

30 ), 

31) 

32def validate_dehumanization( 

33 validator: PerformanceValidator = Depends(get_validator), 

34) -> ValidationReport: 

35 try: 

36 return validator.validate_category( 

37 category_name="dehumanization", 

38 samples=DEHUMANIZATION_VALIDATION_SAMPLES, 

39 ) 

40 except Exception as e: 

41 raise HTTPException(status_code=500, detail=str(e)) 

42 

43 

44@router.post( 

45 "/custom", 

46 response_model=ValidationReport, 

47 summary="Run validation against a custom sample set", 

48) 

49def validate_custom( 

50 category: str, 

51 samples: list[ValidationSample], 

52 validator: PerformanceValidator = Depends(get_validator), 

53) -> ValidationReport: 

54 if not samples: 

55 raise HTTPException(status_code=400, detail="At least one sample required") 

56 try: 

57 return validator.validate_category(category_name=category, samples=samples) 

58 except ValueError as e: 

59 raise HTTPException(status_code=404, detail=str(e)) 

60 except Exception as e: 

61 raise HTTPException(status_code=500, detail=str(e))