from typing import List, Dict, Optional, Any, Union, Literal, TypeVar, Generic, Annotated from pydantic import BaseModel, Field, EmailStr, HttpUrl, model_validator # type: ignore from pydantic.types import constr, conint # type: ignore from datetime import datetime, date, UTC from enum import Enum import uuid # Generic type variable T = TypeVar('T') # ============================ # Enums # ============================ class UserType(str, Enum): CANDIDATE = "candidate" EMPLOYER = "employer" GUEST = "guest" class UserGender(str, Enum): FEMALE = "female" MALE = "male" class UserStatus(str, Enum): ACTIVE = "active" INACTIVE = "inactive" PENDING = "pending" BANNED = "banned" class SkillLevel(str, Enum): BEGINNER = "beginner" INTERMEDIATE = "intermediate" ADVANCED = "advanced" EXPERT = "expert" class EmploymentType(str, Enum): FULL_TIME = "full-time" PART_TIME = "part-time" CONTRACT = "contract" INTERNSHIP = "internship" FREELANCE = "freelance" class InterviewType(str, Enum): PHONE = "phone" VIDEO = "video" ONSITE = "onsite" TECHNICAL = "technical" BEHAVIORAL = "behavioral" class ApplicationStatus(str, Enum): APPLIED = "applied" REVIEWING = "reviewing" INTERVIEW = "interview" OFFER = "offer" REJECTED = "rejected" ACCEPTED = "accepted" WITHDRAWN = "withdrawn" class InterviewRecommendation(str, Enum): STRONG_HIRE = "strong_hire" HIRE = "hire" NO_HIRE = "no_hire" STRONG_NO_HIRE = "strong_no_hire" class ChatSenderType(str, Enum): USER = "user" ASSISTANT = "assistant" SYSTEM = "system" class ChatMessageType(str, Enum): ERROR = "error" GENERATING = "generating" INFO = "info" PREPARING = "preparing" PROCESSING = "processing" RESPONSE = "response" SEARCHING = "searching" SYSTEM = "system" THINKING = "thinking" TOOLING = "tooling" USER = "user" class ChatStatusType(str, Enum): INITIALIZING = "initializing" STREAMING = "streaming" DONE = "done" ERROR = "error" class ChatContextType(str, Enum): JOB_SEARCH = "job_search" CANDIDATE_CHAT = "candidate_chat" INTERVIEW_PREP = "interview_prep" RESUME_REVIEW = "resume_review" GENERAL = "general" GENERATE_PERSONA = "generate_persona" GENERATE_PROFILE = "generate_profile" class AIModelType(str, Enum): QWEN2_5 = "qwen2.5" FLUX_SCHNELL = "flux-schnell" class MFAMethod(str, Enum): APP = "app" SMS = "sms" EMAIL = "email" class VectorStoreType(str, Enum): CHROMA = "chroma", # FAISS = "faiss", # PINECONE = "pinecone" # QDRANT = "qdrant" # FAISS = "faiss" # MILVUS = "milvus" # WEAVIATE = "weaviate" class DataSourceType(str, Enum): DOCUMENT = "document" WEBSITE = "website" API = "api" DATABASE = "database" INTERNAL = "internal" class ProcessingStepType(str, Enum): EXTRACT = "extract" TRANSFORM = "transform" CHUNK = "chunk" EMBED = "embed" FILTER = "filter" SUMMARIZE = "summarize" class SearchType(str, Enum): SIMILARITY = "similarity" MMR = "mmr" HYBRID = "hybrid" KEYWORD = "keyword" class ActivityType(str, Enum): LOGIN = "login" SEARCH = "search" VIEW_JOB = "view_job" APPLY_JOB = "apply_job" MESSAGE = "message" UPDATE_PROFILE = "update_profile" CHAT = "chat" class ThemePreference(str, Enum): LIGHT = "light" DARK = "dark" SYSTEM = "system" class NotificationType(str, Enum): EMAIL = "email" PUSH = "push" IN_APP = "in_app" class FontSize(str, Enum): SMALL = "small" MEDIUM = "medium" LARGE = "large" class SalaryPeriod(str, Enum): HOUR = "hour" DAY = "day" MONTH = "month" YEAR = "year" class LanguageProficiency(str, Enum): BASIC = "basic" CONVERSATIONAL = "conversational" FLUENT = "fluent" NATIVE = "native" class SocialPlatform(str, Enum): LINKEDIN = "linkedin" TWITTER = "twitter" GITHUB = "github" DRIBBBLE = "dribbble" BEHANCE = "behance" WEBSITE = "website" OTHER = "other" class ColorBlindMode(str, Enum): PROTANOPIA = "protanopia" DEUTERANOPIA = "deuteranopia" TRITANOPIA = "tritanopia" NONE = "none" class SortOrder(str, Enum): ASC = "asc" DESC = "desc" # ============================ # Supporting Models # ============================ class Tunables(BaseModel): enable_rag: bool = Field(True, alias="enableRAG") enable_tools: bool = Field(True, alias="enableTools") enable_context: bool = Field(True, alias="enableContext") class CandidateQuestion(BaseModel): question: str tunables: Optional[Tunables] = None class Location(BaseModel): city: str state: Optional[str] = None country: str postal_code: Optional[str] = Field(None, alias="postalCode") latitude: Optional[float] = None longitude: Optional[float] = None remote: Optional[bool] = None hybrid_options: Optional[List[str]] = Field(None, alias="hybridOptions") address: Optional[str] = None class Skill(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) name: str category: str level: SkillLevel years_of_experience: Optional[int] = Field(None, alias="yearsOfExperience") class WorkExperience(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) company_name: str = Field(..., alias="companyName") position: str start_date: datetime = Field(..., alias="startDate") end_date: Optional[datetime] = Field(None, alias="endDate") is_current: bool = Field(..., alias="isCurrent") description: str skills: List[str] location: Location achievements: Optional[List[str]] = None class Education(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) institution: str degree: str field_of_study: str = Field(..., alias="fieldOfStudy") start_date: datetime = Field(..., alias="startDate") end_date: Optional[datetime] = Field(None, alias="endDate") is_current: bool = Field(..., alias="isCurrent") gpa: Optional[float] = None achievements: Optional[List[str]] = None location: Optional[Location] = None class Language(BaseModel): language: str proficiency: LanguageProficiency class Certification(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) name: str issuing_organization: str = Field(..., alias="issuingOrganization") issue_date: datetime = Field(..., alias="issueDate") expiration_date: Optional[datetime] = Field(None, alias="expirationDate") credential_id: Optional[str] = Field(None, alias="credentialId") credential_url: Optional[HttpUrl] = Field(None, alias="credentialUrl") class SocialLink(BaseModel): platform: SocialPlatform url: HttpUrl class DesiredSalary(BaseModel): amount: float currency: str period: SalaryPeriod class SalaryRange(BaseModel): min: float max: float currency: str period: SalaryPeriod is_visible: bool = Field(..., alias="isVisible") class PointOfContact(BaseModel): name: str position: str email: EmailStr phone: Optional[str] = None class RefreshToken(BaseModel): token: str expires_at: datetime = Field(..., alias="expiresAt") device: str ip_address: str = Field(..., alias="ipAddress") is_revoked: bool = Field(..., alias="isRevoked") revoked_reason: Optional[str] = Field(None, alias="revokedReason") class Attachment(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) file_name: str = Field(..., alias="fileName") file_type: str = Field(..., alias="fileType") file_size: int = Field(..., alias="fileSize") file_url: str = Field(..., alias="fileUrl") uploaded_at: datetime = Field(..., alias="uploadedAt") is_processed: bool = Field(..., alias="isProcessed") processing_result: Optional[Any] = Field(None, alias="processingResult") thumbnail_url: Optional[str] = Field(None, alias="thumbnailUrl") class MessageReaction(BaseModel): user_id: str = Field(..., alias="userId") reaction: str timestamp: datetime class EditHistory(BaseModel): content: str edited_at: datetime = Field(..., alias="editedAt") edited_by: str = Field(..., alias="editedBy") class CustomQuestion(BaseModel): question: str answer: str class CandidateContact(BaseModel): email: EmailStr phone: Optional[str] = None class ApplicationDecision(BaseModel): status: Literal["accepted", "rejected"] reason: Optional[str] = None date: datetime by: str class SkillAssessment(BaseModel): skill_name: str = Field(..., alias="skillName") score: Annotated[float, Field(ge=0, le=10)] comments: Optional[str] = None class NotificationPreference(BaseModel): type: NotificationType events: List[str] is_enabled: bool = Field(..., alias="isEnabled") class AccessibilitySettings(BaseModel): font_size: FontSize = Field(..., alias="fontSize") high_contrast: bool = Field(..., alias="highContrast") reduce_motion: bool = Field(..., alias="reduceMotion") screen_reader: bool = Field(..., alias="screenReader") color_blind_mode: Optional[ColorBlindMode] = Field(None, alias="colorBlindMode") class ProcessingStep(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) type: ProcessingStepType parameters: Dict[str, Any] order: int depends_on: Optional[List[str]] = Field(None, alias="dependsOn") class RetrievalParameters(BaseModel): search_type: SearchType = Field(..., alias="searchType") top_k: int = Field(..., alias="topK") similarity_threshold: Optional[float] = Field(None, alias="similarityThreshold") reranker_model: Optional[str] = Field(None, alias="rerankerModel") use_keyword_boost: bool = Field(..., alias="useKeywordBoost") filter_options: Optional[Dict[str, Any]] = Field(None, alias="filterOptions") context_window: int = Field(..., alias="contextWindow") class ErrorDetail(BaseModel): code: str message: str details: Optional[Any] = None # ============================ # Main Models # ============================ # Base user model without user_type field class BaseUser(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) email: EmailStr first_name: str = Field(..., alias="firstName") last_name: str = Field(..., alias="lastName") full_name: str = Field(..., alias="fullName") phone: Optional[str] = None location: Optional[Location] = None created_at: datetime = Field(..., alias="createdAt") updated_at: datetime = Field(..., alias="updatedAt") last_login: Optional[datetime] = Field(None, alias="lastLogin") profile_image: Optional[str] = Field(None, alias="profileImage") status: UserStatus model_config = { "populate_by_name": True, # Allow both field names and aliases "use_enum_values": True # Use enum values instead of names } # Generic base user with user_type for API responses class BaseUserWithType(BaseUser): user_type: UserType = Field(..., alias="userType") class Candidate(BaseUser): user_type: Literal[UserType.CANDIDATE] = Field(UserType.CANDIDATE, alias="userType") username: str description: Optional[str] = None resume: Optional[str] = None skills: Optional[List[Skill]] = None experience: Optional[List[WorkExperience]] = None questions: Optional[List[CandidateQuestion]] = None education: Optional[List[Education]] = None preferred_job_types: Optional[List[EmploymentType]] = Field(None, alias="preferredJobTypes") desired_salary: Optional[DesiredSalary] = Field(None, alias="desiredSalary") availability_date: Optional[datetime] = Field(None, alias="availabilityDate") summary: Optional[str] = None languages: Optional[List[Language]] = None certifications: Optional[List[Certification]] = None job_applications: Optional[List["JobApplication"]] = Field(None, alias="jobApplications") has_profile: bool = Field(default=False, alias="hasProfile") # Used for AI generated personas age: Optional[int] = None gender: Optional[UserGender] = None ethnicity: Optional[str] = None class Employer(BaseUser): user_type: Literal[UserType.EMPLOYER] = Field(UserType.EMPLOYER, alias="userType") company_name: str = Field(..., alias="companyName") industry: str description: Optional[str] = None company_size: str = Field(..., alias="companySize") company_description: str = Field(..., alias="companyDescription") website_url: Optional[HttpUrl] = Field(None, alias="websiteUrl") jobs: Optional[List["Job"]] = None company_logo: Optional[str] = Field(None, alias="companyLogo") social_links: Optional[List[SocialLink]] = Field(None, alias="socialLinks") poc: Optional[PointOfContact] = None class Guest(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) session_id: str = Field(..., alias="sessionId") created_at: datetime = Field(..., alias="createdAt") last_activity: datetime = Field(..., alias="lastActivity") converted_to_user_id: Optional[str] = Field(None, alias="convertedToUserId") ip_address: Optional[str] = Field(None, alias="ipAddress") user_agent: Optional[str] = Field(None, alias="userAgent") model_config = { "populate_by_name": True # Allow both field names and aliases } class Authentication(BaseModel): user_id: str = Field(..., alias="userId") password_hash: str = Field(..., alias="passwordHash") salt: str refresh_tokens: List[RefreshToken] = Field(..., alias="refreshTokens") reset_password_token: Optional[str] = Field(None, alias="resetPasswordToken") reset_password_expiry: Optional[datetime] = Field(None, alias="resetPasswordExpiry") last_password_change: datetime = Field(..., alias="lastPasswordChange") mfa_enabled: bool = Field(..., alias="mfaEnabled") mfa_method: Optional[MFAMethod] = Field(None, alias="mfaMethod") mfa_secret: Optional[str] = Field(None, alias="mfaSecret") login_attempts: int = Field(..., alias="loginAttempts") locked_until: Optional[datetime] = Field(None, alias="lockedUntil") model_config = { "populate_by_name": True # Allow both field names and aliases } class AuthResponse(BaseModel): access_token: str = Field(..., alias="accessToken") refresh_token: str = Field(..., alias="refreshToken") user: Candidate | Employer expires_at: int = Field(..., alias="expiresAt") model_config = { "populate_by_name": True # Allow both field names and aliases } class Job(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) title: str description: str responsibilities: List[str] requirements: List[str] preferred_skills: Optional[List[str]] = Field(None, alias="preferredSkills") employer_id: str = Field(..., alias="employerId") location: Location salary_range: Optional[SalaryRange] = Field(None, alias="salaryRange") employment_type: EmploymentType = Field(..., alias="employmentType") date_posted: datetime = Field(..., alias="datePosted") application_deadline: Optional[datetime] = Field(None, alias="applicationDeadline") is_active: bool = Field(..., alias="isActive") applicants: Optional[List["JobApplication"]] = None department: Optional[str] = None reports_to: Optional[str] = Field(None, alias="reportsTo") benefits: Optional[List[str]] = None visa_sponsorship: Optional[bool] = Field(None, alias="visaSponsorship") featured_until: Optional[datetime] = Field(None, alias="featuredUntil") views: int = 0 application_count: int = Field(0, alias="applicationCount") model_config = { "populate_by_name": True # Allow both field names and aliases } class InterviewFeedback(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) interview_id: str = Field(..., alias="interviewId") reviewer_id: str = Field(..., alias="reviewerId") technical_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias="technicalScore") cultural_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias="culturalScore") overall_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias="overallScore") strengths: List[str] weaknesses: List[str] recommendation: InterviewRecommendation comments: str created_at: datetime = Field(..., alias="createdAt") updated_at: datetime = Field(..., alias="updatedAt") is_visible: bool = Field(..., alias="isVisible") skill_assessments: Optional[List[SkillAssessment]] = Field(None, alias="skillAssessments") model_config = { "populate_by_name": True # Allow both field names and aliases } class InterviewSchedule(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) application_id: str = Field(..., alias="applicationId") scheduled_date: datetime = Field(..., alias="scheduledDate") end_date: datetime = Field(..., alias="endDate") interview_type: InterviewType = Field(..., alias="interviewType") interviewers: List[str] location: Optional[Union[str, Location]] = None notes: Optional[str] = None feedback: Optional[InterviewFeedback] = None status: Literal["scheduled", "completed", "cancelled", "rescheduled"] meeting_link: Optional[HttpUrl] = Field(None, alias="meetingLink") model_config = { "populate_by_name": True # Allow both field names and aliases } class JobApplication(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) job_id: str = Field(..., alias="jobId") candidate_id: str = Field(..., alias="candidateId") status: ApplicationStatus applied_date: datetime = Field(..., alias="appliedDate") updated_date: datetime = Field(..., alias="updatedDate") resume_version: str = Field(..., alias="resumeVersion") cover_letter: Optional[str] = Field(None, alias="coverLetter") notes: Optional[str] = None interview_schedules: Optional[List[InterviewSchedule]] = Field(None, alias="interviewSchedules") custom_questions: Optional[List[CustomQuestion]] = Field(None, alias="customQuestions") candidate_contact: Optional[CandidateContact] = Field(None, alias="candidateContact") decision: Optional[ApplicationDecision] = None model_config = { "populate_by_name": True # Allow both field names and aliases } class RagEntry(BaseModel): name: str description: str = "" enabled: bool = True class ChromaDBGetResponse(BaseModel): # Chroma fields ids: List[str] = [] embeddings: List[List[float]] = Field(default=[]) documents: List[str] = [] metadatas: List[Dict[str, Any]] = [] # Additional fields name: str = "" size: int = 0 query: str = "" query_embedding: Optional[List[float]] = Field(default=None, alias="queryEmbedding") umap_embedding_2d: Optional[List[float]] = Field(default=None, alias="umapEmbedding2D") umap_embedding_3d: Optional[List[float]] = Field(default=None, alias="umapEmbedding3D") class ChatContext(BaseModel): type: ChatContextType related_entity_id: Optional[str] = Field(None, alias="relatedEntityId") related_entity_type: Optional[Literal["job", "candidate", "employer"]] = Field(None, alias="relatedEntityType") additional_context: Optional[Dict[str, Any]] = Field(None, alias="additionalContext") model_config = { "populate_by_name": True # Allow both field names and aliases } class ChatOptions(BaseModel): seed: Optional[int] = 8911 num_ctx: Optional[int] = Field(default=None, alias="numCtx") # Number of context tokens temperature: Optional[float] = Field(default=0.7) # Higher temperature to encourage tool usage class ChatMessageMetaData(BaseModel): model: AIModelType = AIModelType.QWEN2_5 temperature: float = 0.7 max_tokens: int = Field(default=8092, alias="maxTokens") top_p: float = Field(default=1, alias="topP") frequency_penalty: Optional[float] = Field(None, alias="frequencyPenalty") presence_penalty: Optional[float] = Field(None, alias="presencePenalty") stop_sequences: Optional[List[str]] = Field(None, alias="stopSequences") tunables: Optional[Tunables] = None rag: List[ChromaDBGetResponse] = Field(default_factory=list) eval_count: int = 0 eval_duration: int = 0 prompt_eval_count: int = 0 prompt_eval_duration: int = 0 options: Optional[ChatOptions] = None tools: Optional[Dict[str, Any]] = None timers: Optional[Dict[str, float]] = None model_config = { "populate_by_name": True # Allow both field names and aliases } class ChatMessageBase(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) session_id: str = Field(..., alias="sessionId") sender_id: Optional[str] = Field(None, alias="senderId") status: ChatStatusType type: ChatMessageType sender: ChatSenderType timestamp: datetime content: str = "" model_config = { "populate_by_name": True # Allow both field names and aliases } class ChatMessageUser(ChatMessageBase): type: ChatMessageType = ChatMessageType.USER class ChatMessage(ChatMessageBase): #attachments: Optional[List[Attachment]] = None #reactions: Optional[List[MessageReaction]] = None #is_edited: bool = Field(False, alias="isEdited") #edit_history: Optional[List[EditHistory]] = Field(None, alias="editHistory") metadata: ChatMessageMetaData = Field(None) class ChatSession(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) user_id: Optional[str] = Field(None, alias="userId") guest_id: Optional[str] = Field(None, alias="guestId") created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias="createdAt") last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC), alias="lastActivity") title: Optional[str] = None context: ChatContext messages: Optional[List[ChatMessage]] = None is_archived: bool = Field(False, alias="isArchived") system_prompt: Optional[str] = Field(None, alias="systemPrompt") model_config = { "populate_by_name": True # Allow both field names and aliases } @model_validator(mode="after") def check_user_or_guest(self) -> "ChatSession": if not self.user_id and not self.guest_id: raise ValueError("Either user_id or guest_id must be provided") return self class DataSourceConfiguration(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) rag_config_id: str = Field(..., alias="ragConfigId") name: str source_type: DataSourceType = Field(..., alias="sourceType") connection_details: Dict[str, Any] = Field(..., alias="connectionDetails") processing_pipeline: List[ProcessingStep] = Field(..., alias="processingPipeline") refresh_schedule: Optional[str] = Field(None, alias="refreshSchedule") last_refreshed: Optional[datetime] = Field(None, alias="lastRefreshed") status: Literal["active", "pending", "error", "processing"] error_details: Optional[str] = Field(None, alias="errorDetails") metadata: Optional[Dict[str, Any]] = None model_config = { "populate_by_name": True # Allow both field names and aliases } class RAGConfiguration(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) user_id: str = Field(..., alias="userId") name: str description: Optional[str] = None data_source_configurations: List[DataSourceConfiguration] = Field(..., alias="dataSourceConfigurations") embedding_model: str = Field(..., alias="embeddingModel") vector_store_type: VectorStoreType = Field(..., alias="vectorStoreType") retrieval_parameters: RetrievalParameters = Field(..., alias="retrievalParameters") created_at: datetime = Field(..., alias="createdAt") updated_at: datetime = Field(..., alias="updatedAt") version: int is_active: bool = Field(..., alias="isActive") model_config = { "populate_by_name": True # Allow both field names and aliases } class UserActivity(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) user_id: Optional[str] = Field(None, alias="userId") guest_id: Optional[str] = Field(None, alias="guestId") activity_type: ActivityType = Field(..., alias="activityType") timestamp: datetime metadata: Dict[str, Any] ip_address: Optional[str] = Field(None, alias="ipAddress") user_agent: Optional[str] = Field(None, alias="userAgent") session_id: Optional[str] = Field(None, alias="sessionId") model_config = { "populate_by_name": True # Allow both field names and aliases } @model_validator(mode="after") def check_user_or_guest(self) -> "ChatSession": if not self.user_id and not self.guest_id: raise ValueError("Either user_id or guest_id must be provided") return self class Analytics(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4())) entity_type: Literal["job", "candidate", "chat", "system", "employer"] = Field(..., alias="entityType") entity_id: str = Field(..., alias="entityId") metric_type: str = Field(..., alias="metricType") value: float timestamp: datetime dimensions: Optional[Dict[str, Any]] = None segment: Optional[str] = None model_config = { "populate_by_name": True # Allow both field names and aliases } class UserPreference(BaseModel): user_id: str = Field(..., alias="userId") theme: ThemePreference notifications: List[NotificationPreference] accessibility: AccessibilitySettings dashboard_layout: Optional[Dict[str, Any]] = Field(None, alias="dashboardLayout") language: str timezone: str email_frequency: Literal["immediate", "daily", "weekly", "never"] = Field(..., alias="emailFrequency") model_config = { "populate_by_name": True # Allow both field names and aliases } # ============================ # API Request/Response Models # ============================ class ChatQuery(BaseModel): prompt: str tunables: Optional[Tunables] = None agent_options: Optional[Dict[str, Any]] = Field(None, alias="agentOptions") model_config = { "populate_by_name": True # Allow both field names and aliases } class PaginatedRequest(BaseModel): page: Annotated[int, Field(ge=1)] = 1 limit: Annotated[int, Field(ge=1, le=100)] = 20 sort_by: Optional[str] = Field(None, alias="sortBy") sort_order: Optional[SortOrder] = Field(None, alias="sortOrder") filters: Optional[Dict[str, Any]] = None model_config = { "populate_by_name": True # Allow both field names and aliases } class SearchQuery(BaseModel): query: str filters: Optional[Dict[str, Any]] = None page: Annotated[int, Field(ge=1)] = 1 limit: Annotated[int, Field(ge=1, le=100)] = 20 sort_by: Optional[str] = Field(None, alias="sortBy") sort_order: Optional[SortOrder] = Field(None, alias="sortOrder") model_config = { "populate_by_name": True # Allow both field names and aliases } class PaginatedResponse(BaseModel): data: List[Any] # Will be typed specifically when used total: int page: int limit: int total_pages: int = Field(..., alias="totalPages") has_more: bool = Field(..., alias="hasMore") model_config = { "populate_by_name": True # Allow both field names and aliases } class ApiResponse(BaseModel): success: bool data: Optional[Any] = None # Will be typed specifically when used error: Optional[ErrorDetail] = None meta: Optional[Dict[str, Any]] = None # Specific typed response models for common use cases class CandidateResponse(BaseModel): success: bool data: Optional[Candidate] = None error: Optional[ErrorDetail] = None meta: Optional[Dict[str, Any]] = None class EmployerResponse(BaseModel): success: bool data: Optional[Employer] = None error: Optional[ErrorDetail] = None meta: Optional[Dict[str, Any]] = None class JobResponse(BaseModel): success: bool data: Optional["Job"] = None error: Optional[ErrorDetail] = None meta: Optional[Dict[str, Any]] = None class CandidateListResponse(BaseModel): success: bool data: Optional[List[Candidate]] = None error: Optional[ErrorDetail] = None meta: Optional[Dict[str, Any]] = None class JobListResponse(BaseModel): success: bool data: Optional[List["Job"]] = None error: Optional[ErrorDetail] = None meta: Optional[Dict[str, Any]] = None # Forward references resolution Candidate.update_forward_refs() Employer.update_forward_refs() ChatSession.update_forward_refs() JobApplication.update_forward_refs() Job.update_forward_refs()