Spaces:
Sleeping
Sleeping
Download docqa/docxextract/schema.py from validops-east-3/instance-2: direct link, hf CLI and curl.
- Browser
- Download file 3.68 kB
-
https://huggingface.co/spaces/validops-east-3/instance-2/resolve/main/docqa/docxextract/schema.py
- Command line
-
hf download hf://spaces/validops-east-3/instance-2/docqa/docxextract/schema.py
-
curl -L -o schema.py https://huggingface.co/spaces/validops-east-3/instance-2/resolve/main/docqa/docxextract/schema.py
3.68 kB
| """Typed request/response contracts and the error hierarchy.""" | |
| from __future__ import annotations | |
| import enum | |
| from typing import Any | |
| from pydantic import BaseModel, Field, field_validator | |
| class FieldStatus(str, enum.Enum): | |
| """Outcome of a single key extraction. | |
| `EXTRACTED` is only assigned above the confidence threshold; anything else | |
| is surfaced to the caller rather than being silently returned as fact. | |
| This distinction is the whole point of the service: the underlying model | |
| will always emit *some* span, so absence must be modelled explicitly. | |
| """ | |
| EXTRACTED = "extracted" | |
| LOW_CONFIDENCE = "low_confidence" | |
| NOT_FOUND = "not_found" | |
| ERROR = "error" | |
| class SourceType(str, enum.Enum): | |
| PDF_TEXT_LAYER = "pdf_text_layer" | |
| PDF_OCR = "pdf_ocr" | |
| IMAGE_OCR = "image_ocr" | |
| class ExtractedField(BaseModel): | |
| key: str | |
| value: str | None = None | |
| status: FieldStatus = FieldStatus.NOT_FOUND | |
| confidence: float = Field(default=0.0, ge=0.0, le=1.0) | |
| page: int | None = None | |
| source: SourceType | None = None | |
| error: str | None = None | |
| def is_usable(self) -> bool: | |
| return self.status is FieldStatus.EXTRACTED | |
| class DocumentInfo(BaseModel): | |
| document_id: str | |
| page_count: int | |
| pages_processed: int | |
| word_count: int | |
| source_type: SourceType | |
| truncated: bool = False | |
| class ExtractRequest(BaseModel): | |
| """Request body. `file` arrives separately as an UploadFile.""" | |
| keys: list[str] = Field(..., min_length=1) | |
| confidence_threshold: float | None = Field(default=None, ge=0.0, le=1.0) | |
| max_pages: int | None = Field(default=None, ge=1, le=50) | |
| def _validate_keys(cls, v: list[str]) -> list[str]: | |
| cleaned = [k.strip() for k in v if k and k.strip()] | |
| if not cleaned: | |
| raise ValueError("at least one non-empty key is required") | |
| if any(len(k) > 200 for k in cleaned): | |
| raise ValueError("each key must be 200 characters or fewer") | |
| return cleaned | |
| def _dedupe(cls, v: list[str]) -> list[str]: | |
| """Preserve order but drop exact duplicates. | |
| Repeating a key costs a full forward pass and returns an identical | |
| answer, so duplicates are collapsed rather than rejected. | |
| """ | |
| seen: set[str] = set() | |
| out: list[str] = [] | |
| for k in v: | |
| if k not in seen: | |
| seen.add(k) | |
| out.append(k) | |
| return out | |
| class ExtractResponse(BaseModel): | |
| document: DocumentInfo | |
| fields: list[ExtractedField] | |
| latency_ms: float | |
| confidence_threshold: float | |
| def values(self) -> dict[str, str | None]: | |
| return {f.key: f.value for f in self.fields} | |
| # -------------------------------------------------------------------------- | |
| # errors | |
| # -------------------------------------------------------------------------- | |
| class DocExtractError(Exception): | |
| """Base class. `status_code` is what the HTTP layer will return.""" | |
| status_code = 500 | |
| def __init__(self, message: str, detail: Any = None): | |
| super().__init__(message) | |
| self.message = message | |
| self.detail = detail | |
| class ValidationError(DocExtractError): | |
| status_code = 422 | |
| class UnsupportedMediaError(DocExtractError): | |
| status_code = 415 | |
| class PayloadTooLargeError(DocExtractError): | |
| status_code = 413 | |
| class CorruptDocumentError(DocExtractError): | |
| status_code = 422 | |
| class ExtractionTimeoutError(DocExtractError): | |
| status_code = 504 | |
| class ModelUnavailableError(DocExtractError): | |
| status_code = 503 | |