api-web-crawler / app /models /visa_data.py
Muhammad Risqi Firdaus
feat: new endpoint
f188029
Raw
History Blame Contribute Delete
5.04 kB
"""
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)