Spaces:
Build error
Build error
| """ | |
| Canonical Pydantic models for the visa service automation pipeline. | |
| Aligned with SPUN's Visa Guideline Template. | |
| """ | |
| from datetime import datetime | |
| from enum import StrEnum | |
| from typing import Dict, List, Optional | |
| from pydantic import BaseModel, Field | |
| class RequirementType(StrEnum): | |
| VISA_FREE = "VISA_FREE" | |
| VISA_REQUIRED = "VISA_REQUIRED" | |
| EVISA = "EVISA" | |
| VOA = "VOA" # Visa on Arrival | |
| ETA = "ETA" # Electronic Travel Authorization | |
| NOT_ADMITTED = "NOT_ADMITTED" | |
| class MatchStatus(StrEnum): | |
| NEW = "NEW" | |
| EXISTS_IDENTICAL = "EXISTS_IDENTICAL" | |
| EXISTS_OUTDATED = "EXISTS_OUTDATED" | |
| # --- Eligibility --- | |
| class VisaEligibility(BaseModel): | |
| requirement_type: RequirementType | |
| max_stay_days: Optional[int] = None | |
| source: str | |
| confidence: float = Field(ge=0, le=1) | |
| # --- Document Requirements --- | |
| class DocumentRequirement(BaseModel): | |
| name: str = Field(..., description="e.g. 'Paspor', 'Foto', 'Hotel booking'") | |
| is_required: bool = Field(..., description="True = wajib (*), False = optional") | |
| needs_physical: bool = Field(default=False, description="'butuh fisik'") | |
| format: str = Field(default="digital", description="'digital', 'fisik', 'e-statement'") | |
| notes: Optional[str] = None | |
| class PersonaDocuments(BaseModel): | |
| """Documents conditional on persona (occupation, marital, sponsorship, age).""" | |
| occupation: Dict[str, List[DocumentRequirement]] = Field( | |
| default_factory=dict, | |
| description="Employee, Entrepreneur, Self-Employed, Students, Retired, Not working", | |
| ) | |
| marital_status: Dict[str, List[DocumentRequirement]] = Field( | |
| default_factory=dict, | |
| description="Single, Married, Divorced, Widowed", | |
| ) | |
| sponsorship: Dict[str, List[DocumentRequirement]] = Field( | |
| default_factory=dict, | |
| description="Self-sponsored, Individual-sponsored, Institution-sponsored", | |
| ) | |
| age_group: Dict[str, List[DocumentRequirement]] = Field( | |
| default_factory=dict, | |
| description="Adult, Children", | |
| ) | |
| # --- Form Questions --- | |
| class FormQuestion(BaseModel): | |
| question: str | |
| applies_to: Optional[str] = Field( | |
| None, description="None = general, or persona category" | |
| ) | |
| # --- Visa Guideline (maps 1:1 to SPUN's template) --- | |
| class VisaGuideline(BaseModel): | |
| """Maps 1:1 to SPUN's visa guideline template.""" | |
| # Header | |
| visa_name: str | |
| visa_code: Optional[str] = None | |
| # Application info | |
| application_method: str = Field( | |
| ..., description="Hybrid / Offline / Online" | |
| ) | |
| application_location: Optional[str] = None | |
| client_nationality: str | |
| options: List[str] = Field( | |
| default_factory=list, description="Regular / Express" | |
| ) | |
| # Content | |
| description: str = "" | |
| eligibility_requirements: List[str] = Field(default_factory=list) | |
| # Documents | |
| general_documents: List[DocumentRequirement] = Field(default_factory=list) | |
| persona_documents: PersonaDocuments = Field(default_factory=PersonaDocuments) | |
| # Validity | |
| validity_period: Optional[str] = None | |
| stay_period: Optional[str] = None | |
| # Processing | |
| processing_time_min_days: Optional[int] = None | |
| processing_time_max_days: Optional[int] = None | |
| # Contact | |
| embassy_contact: Optional[str] = None | |
| # Pricing | |
| government_fee: Optional[str] = None | |
| cogs: Optional[str] = None | |
| publish_rate: Optional[str] = None | |
| # Process steps | |
| application_steps: List[str] = Field(default_factory=list) | |
| preferred_supply_partner: Optional[str] = None | |
| # Sources | |
| source_urls: List[str] = Field(default_factory=list) | |
| # Form questions | |
| general_form_questions: List[str] = Field(default_factory=list) | |
| persona_form_questions: Dict[str, List[FormQuestion]] = Field( | |
| default_factory=dict | |
| ) | |
| # --- E-Visa Assessment --- | |
| class EVisaAssessment(BaseModel): | |
| is_evisa: bool | |
| portal_url: Optional[str] = None | |
| portal_accessible: bool = False | |
| feasibility_score: float = Field(0.0, ge=0, le=1) | |
| blockers: List[str] = Field(default_factory=list) | |
| # --- Service Candidate (pipeline output) --- | |
| class ServiceCandidate(BaseModel): | |
| guideline: VisaGuideline | |
| evisa_assessment: Optional[EVisaAssessment] = None | |
| match_status: MatchStatus = MatchStatus.NEW | |
| existing_service_id: Optional[int] = None | |
| confidence_score: float = Field(0.0, ge=0, le=1) | |
| scraped_at: datetime = Field(default_factory=datetime.utcnow) | |
| # --- Pipeline Request/Response --- | |
| class PipelineRequest(BaseModel): | |
| """Input to the pipeline orchestrator.""" | |
| nationality: str | |
| destination: str | |
| residency: Optional[str] = None | |
| purpose: Optional[str] = None | |
| class PipelineResult(BaseModel): | |
| """Output from the pipeline for a single nationality/destination pair.""" | |
| request: PipelineRequest | |
| eligibility: Optional[VisaEligibility] = None | |
| candidates: List[ServiceCandidate] = Field(default_factory=list) | |
| errors: List[str] = Field(default_factory=list) | |