"""Pydantic models for NuExtract benchmark and evaluation. Defines structured result types, incremental value reporting, and promotion gate thresholds. Requirement: 6.6 """ from __future__ import annotations from typing import Any, Literal from pydantic import BaseModel, Field class ExtractedField(BaseModel): """A single field extracted by NuExtract.""" name: str value: Any start_char: int | None = None end_char: int | None = None confidence: float = 0.0 class NuExtractResult(BaseModel): """Result from NuExtract 1.5 Smol extraction. Contains extracted fields with spans, confidence scores, model lineage, and latency tracking. """ fields: list[ExtractedField] = Field(default_factory=list) spans: list[dict[str, Any]] = Field(default_factory=list) confidence: float = 0.0 model_version: str = "numind/NuExtract-1.5-smol" latency_ms: float = 0.0 memory_mb: float = 0.0 document_type: str = "" schema_used: dict[str, Any] = Field(default_factory=dict) error: str | None = None class IncrementalValueReport(BaseModel): """Report comparing NuExtract vs GLiNER2 + deterministic parsing per document type. Tracks F1 scores for both approaches and computes the delta to determine if NuExtract adds incremental value. """ document_type: Literal["filing", "transcript", "article", "press_release", "macro_event"] gliner_f1: float = Field(ge=0.0, le=1.0) nuextract_f1: float = Field(ge=0.0, le=1.0) delta: float = Field( description="nuextract_f1 - gliner_f1; positive means NuExtract is better" ) nuextract_latency_ms: float = 0.0 nuextract_memory_mb: float = 0.0 gliner_latency_ms: float = 0.0 gliner_memory_mb: float = 0.0 sample_count: int = 0 promoted: bool = False class PromotionGate(BaseModel): """Gate thresholds for promoting NuExtract for a document class. NuExtract is only promoted for document classes where it beats GLiNER2 + deterministic parsing by the configured minimums AND stays within resource bounds. """ min_f1_improvement: float = Field( default=0.05, ge=0.0, le=1.0, description="Minimum F1 delta required for promotion", ) max_latency_ms: float = Field( default=5000.0, gt=0.0, description="Maximum acceptable p95 latency in milliseconds", ) max_memory_mb: float = Field( default=2048.0, gt=0.0, description="Maximum acceptable peak memory usage in MB", ) min_sample_count: int = Field( default=50, ge=1, description="Minimum sample count required for statistical confidence", ) class BenchmarkReport(BaseModel): """Full benchmark report across all evaluated document types.""" reports: list[IncrementalValueReport] = Field(default_factory=list) gate: PromotionGate = Field(default_factory=PromotionGate) promoted_types: list[str] = Field(default_factory=list) overall_nuextract_f1: float = 0.0 overall_gliner_f1: float = 0.0 overall_delta: float = 0.0 total_documents: int = 0