# Trouve-KA — types partagés du pipeline # Author: Simon-Pierre Boucher # Contact: contact@spboucher.ai """Modèles Pydantic partagés entre crawler, parser, classifier, indexer et API.""" from datetime import datetime from enum import StrEnum from pydantic import BaseModel, Field class ErrorCode(StrEnum): DNS = "dns" TIMEOUT = "timeout" TLS = "tls" HTTP_4XX = "http_4xx" HTTP_5XX = "http_5xx" ROBOTS_DENIED = "robots_denied" PARSE_FAILED = "parse_failed" UNSUPPORTED_CONTENT = "unsupported_content" TOO_LARGE = "too_large" DUPLICATE = "duplicate" SPAM = "spam" NOT_QUEBEC = "not_quebec" SSRF_BLOCKED = "ssrf_blocked" TOO_MANY_REDIRECTS = "too_many_redirects" CONNECTION = "connection" OK = "ok" class Outcome(StrEnum): INDEXED = "indexed" DUPLICATE = "duplicate" UNCHANGED = "unchanged" ERROR = "error" NOT_QUEBEC = "not_quebec" ROBOTS_BLOCKED = "robots_blocked" REDIRECT = "redirect" class FetchResult(BaseModel): url: str final_url: str status_code: int | None = None error_code: ErrorCode = ErrorCode.OK content_type: str | None = None charset: str | None = None body: bytes | None = None etag: str | None = None last_modified: str | None = None duration_ms: int = 0 redirect_chain: list[str] = Field(default_factory=list) class ExtractedLink(BaseModel): url: str anchor: str = "" nofollow: bool = False class ParsedPage(BaseModel): url: str canonical_url: str | None = None title: str = "" description: str = "" body: str = "" headings: list[str] = Field(default_factory=list) language: str | None = None links: list[ExtractedLink] = Field(default_factory=list) published_at: datetime | None = None modified_at: datetime | None = None author: str | None = None image_url: str | None = None noindex: bool = False nofollow_page: bool = False structured_hints: list[str] = Field(default_factory=list) # textes JSON-LD/OG utiles au scoring class QuebecSignals(BaseModel): score: float = 0.0 locations: list[str] = Field(default_factory=list) reasons: list[str] = Field(default_factory=list) class SearchResult(BaseModel): title: str url: str display_url: str snippet: str domain: str language: str | None = None quebec_score: float = 0.0 badges: list[str] = Field(default_factory=list) published_at: datetime | None = None class SearchResponse(BaseModel): query: str total: int took_ms: int page: int limit: int results: list[SearchResult]