from __future__ import annotations from enum import StrEnum from typing import Any from pydantic import BaseModel, ConfigDict, Field, field_validator class Category(StrEnum): LINEAR_DIMENSION = "Linear dimension" DIAMETER = "Diameter" RADIUS = "Radius" ANGLE = "Angle" GD_AND_T = "GD&T" THREAD = "Thread" SURFACE_FINISH = "Surface finish" GENERAL_TOLERANCE = "General tolerance" MATERIAL = "Material" NOTE = "Note" OTHER = "Other" class ReviewStatus(StrEnum): NEEDS_REVIEW = "Needs review" VERIFIED = "Verified" CORRECTED = "Corrected" REJECTED = "Rejected" NEEDS_MAPPING = "Needs mapping" class Characteristic(BaseModel): """One drawing requirement linked to one balloon identifier.""" model_config = ConfigDict(extra="ignore", validate_assignment=True) record_id: str = "" balloon_id: str = "" page: int = 1 zone: str = "" category: Category = Category.OTHER feature: str = "" requirement: str = "" nominal: str = "" upper_tolerance: str = "" lower_tolerance: str = "" upper_limit: str = "" lower_limit: str = "" quantity: int | None = None unit: str = "mm" modifier: str = "" gdandt_symbol: str = "" gdandt_tolerance: str = "" datum_primary: str = "" datum_secondary: str = "" datum_tertiary: str = "" thread: str = "" surface_finish: str = "" source_text: str = "" source_bbox: list[float] = Field(default_factory=list) balloon_center: list[float] = Field(default_factory=list) confidence: float = 0.0 status: ReviewStatus = ReviewStatus.NEEDS_REVIEW validation_issues: list[str] = Field(default_factory=list) @field_validator( "record_id", "balloon_id", "zone", "feature", "requirement", "nominal", "upper_tolerance", "lower_tolerance", "upper_limit", "lower_limit", "unit", "modifier", "gdandt_symbol", "gdandt_tolerance", "datum_primary", "datum_secondary", "datum_tertiary", "thread", "surface_finish", "source_text", mode="before", ) @classmethod def stringify(cls, value: Any) -> str: if value is None: return "" return str(value).strip() @field_validator("confidence", mode="before") @classmethod def clamp_confidence(cls, value: Any) -> float: try: number = float(value) except (TypeError, ValueError): return 0.0 if number > 1 and number <= 100: number /= 100 return max(0.0, min(1.0, number)) class ExtractionResult(BaseModel): model_config = ConfigDict(extra="ignore") source_name: str = "" model_id: str = "" page_count: int = 0 drawing_metadata: dict[str, Any] = Field(default_factory=dict) general_notes: list[str] = Field(default_factory=list) characteristics: list[Characteristic] = Field(default_factory=list) raw_outputs: list[dict[str, Any]] = Field(default_factory=list) warnings: list[str] = Field(default_factory=list)