redrob-ranker / src /core /models.py
MrNK2107
Fix remote candidate search returning 0 results by mapping preferred_work_mode
3cae53e
Raw
History Blame Contribute Delete
11.6 kB
from __future__ import annotations
import uuid
from datetime import UTC, datetime
from enum import StrEnum
from pydantic import BaseModel, Field
class ProfileSource(StrEnum):
LINKEDIN = "linkedin"
NAUKRI = "naukri"
GITHUB = "github"
RESUME_PDF = "resume_pdf"
CAREER_PAGE = "career_page"
MANUAL = "manual"
REDROB = "redrob"
class SkillCategory(StrEnum):
PROGRAMMING_LANGUAGE = "programming_language"
FRAMEWORK = "framework"
TOOL = "tool"
SOFT_SKILL = "soft_skill"
DOMAIN_KNOWLEDGE = "domain_knowledge"
CERTIFICATION = "certification"
class ProficiencyLevel(StrEnum):
BEGINNER = "beginner"
INTERMEDIATE = "intermediate"
ADVANCED = "advanced"
EXPERT = "expert"
class SkillImportance(StrEnum):
REQUIRED = "required"
PREFERRED = "preferred"
NICE_TO_HAVE = "nice_to_have"
class EmploymentType(StrEnum):
FULL_TIME = "full_time"
PART_TIME = "part_time"
CONTRACT = "contract"
FREELANCE = "freelance"
STUDENT = "student"
class MatchRecommendation(StrEnum):
STRONG = "strong_match"
GOOD = "good_match"
POTENTIAL = "potential_match"
WEAK = "weak_match"
class SearchMethod(StrEnum):
HYBRID = "hybrid"
VECTOR_ONLY = "vector_only"
KEYWORD_ONLY = "keyword_only"
class Location(BaseModel):
city: str | None = None
state: str | None = None
country: str = "India"
is_remote_ok: bool = False
class PersonalInfo(BaseModel):
name: str
location: Location = Field(default_factory=Location)
languages_spoken: list[str] = Field(default_factory=list)
native_language: str | None = None
class ProfessionalInfo(BaseModel):
current_title: str | None = None
current_company: str | None = None
total_experience_years: float | None = None
industry: str | None = None
employment_type: EmploymentType | None = None
seniority_level: int | None = None
class Skill(BaseModel):
name: str
category: SkillCategory = SkillCategory.TOOL
proficiency: ProficiencyLevel | None = None
years_used: float | None = None
evidence: str | None = None
confidence: float = Field(default=1.0, ge=0.0, le=1.0)
class WorkExperience(BaseModel):
title: str
company: str
start_date: str | None = None
end_date: str | None = None
is_current: bool = False
description: str = ""
highlights: list[str] = Field(default_factory=list)
skills_demonstrated: list[str] = Field(default_factory=list)
location: str | None = None
class Education(BaseModel):
institution: str
degree: str | None = None
field: str | None = None
start_date: str | None = None
end_date: str | None = None
gpa: float | None = None
class Signals(BaseModel):
"""Behavioral signals from the Redrob platform — 20+ dimensions."""
is_passive: bool = False
last_active_date: str | None = None
open_to_work: bool | None = None
github_activity_score: float | None = None
has_portfolio: bool = False
certifications: list[str] = Field(default_factory=list)
publications: list[str] = Field(default_factory=list)
speaking_engagements: list[str] = Field(default_factory=list)
# Full redrob_signals enrichment
profile_completeness_score: float | None = None
recruiter_response_rate: float | None = None
avg_response_time_hours: float | None = None
saved_by_recruiters_30d: int | None = None
profile_views_received_30d: int | None = None
applications_submitted_30d: int | None = None
connection_count: int | None = None
endorsements_received: int | None = None
search_appearance_30d: int | None = None
interview_completion_rate: float | None = None
offer_acceptance_rate: float | None = None
notice_period_days: int | None = None
preferred_work_mode: str | None = None
willing_to_relocate: bool | None = None
verified_email: bool | None = None
verified_phone: bool | None = None
expected_salary_min: float | None = None
expected_salary_max: float | None = None
linkedin_connected: bool | None = None
skill_assessment_scores: dict[str, float] = Field(default_factory=dict)
class ProfileMetadata(BaseModel):
language_detected: str = "en"
original_language: str = "en"
was_translated: bool = False
translation_confidence: float | None = None
created_at: str = Field(default_factory=lambda: datetime.now(UTC).isoformat())
updated_at: str = Field(default_factory=lambda: datetime.now(UTC).isoformat())
data_quality_score: float = Field(default=0.0, ge=0.0, le=1.0)
class Profile(BaseModel):
profile_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
source: ProfileSource = ProfileSource.MANUAL
raw_text: str = ""
personal: PersonalInfo
professional: ProfessionalInfo = Field(default_factory=ProfessionalInfo)
skills: list[Skill] = Field(default_factory=list)
experience: list[WorkExperience] = Field(default_factory=list)
education: list[Education] = Field(default_factory=list)
signals: Signals = Field(default_factory=Signals)
metadata: ProfileMetadata = Field(default_factory=ProfileMetadata)
class RequiredSkill(BaseModel):
name: str
importance: SkillImportance = SkillImportance.REQUIRED
min_proficiency: ProficiencyLevel | None = None
min_years: float | None = None
class PreferredSkill(BaseModel):
name: str
importance: SkillImportance = SkillImportance.NICE_TO_HAVE
weight: float = Field(default=0.5, ge=0.0, le=1.0)
class ExperienceRequirements(BaseModel):
min_years: float | None = None
max_years: float | None = None
industry: str | None = None
class LocationRequirements(BaseModel):
city: str | None = None
state: str | None = None
country: str | None = None
remote_ok: bool = False
hybrid_ok: bool = False
class EducationRequirements(BaseModel):
min_degree: str | None = None
field: str | None = None
class SalaryRequirements(BaseModel):
min: float | None = None
max: float | None = None
currency: str = "INR"
class QueryFilters(BaseModel):
exclude_companies: list[str] = Field(default_factory=list)
include_companies: list[str] = Field(default_factory=list)
must_have_certifications: list[str] = Field(default_factory=list)
languages_required: list[str] = Field(default_factory=list)
class ParsedQuery(BaseModel):
required_skills: list[RequiredSkill] = Field(default_factory=list)
preferred_skills: list[PreferredSkill] = Field(default_factory=list)
subskills: dict[str, list[str]] = Field(default_factory=dict)
experience: ExperienceRequirements = Field(default_factory=ExperienceRequirements)
location: LocationRequirements = Field(default_factory=LocationRequirements)
education: EducationRequirements = Field(default_factory=EducationRequirements)
salary: SalaryRequirements = Field(default_factory=SalaryRequirements)
filters: QueryFilters = Field(default_factory=QueryFilters)
original_query: str = ""
class JobQuery(BaseModel):
query_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
raw_query: str
parsed: ParsedQuery = Field(default_factory=ParsedQuery)
language: str = "en"
class MatchScores(BaseModel):
overall: float = Field(default=0.0, ge=0.0, le=1.0)
semantic_similarity: float = Field(default=0.0, ge=0.0, le=1.0)
keyword_match: float = Field(default=0.0, ge=0.0, le=1.0)
skill_match: float = Field(default=0.0, ge=0.0, le=1.0)
experience_match: float = Field(default=0.0, ge=0.0, le=1.0)
location_match: float | None = Field(default=None, ge=0.0, le=1.0)
education_match: float | None = Field(default=None, ge=0.0, le=1.0)
cross_encoder_score: float | None = Field(default=None, ge=0.0, le=1.0)
behavioral_score: float | None = Field(default=None, ge=0.0, le=1.0)
career_trajectory_score: float | None = Field(default=None, ge=0.0, le=1.0)
skill_proficiency_score: float | None = Field(default=None, ge=0.0, le=1.0)
confidence: float = Field(default=0.0, ge=0.0, le=1.0)
class SkillDetail(BaseModel):
skill: str
required: bool
found: bool
proficiency_match: bool
evidence: str = ""
class Rationale(BaseModel):
summary: str = ""
strengths: list[str] = Field(default_factory=list)
gaps: list[str] = Field(default_factory=list)
skill_details: list[SkillDetail] = Field(default_factory=list)
experience_analysis: str = ""
recommendation: MatchRecommendation = MatchRecommendation.GOOD
class MatchMetadata(BaseModel):
search_method: SearchMethod = SearchMethod.HYBRID
reranked: bool = False
language_matched: bool = False
passive_candidate: bool = False
processing_time_ms: int = 0
translation_fallback: bool = False
class MatchResult(BaseModel):
match_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
query_id: str
profile_id: str
rank: int
name: str = ""
current_title: str | None = None
current_company: str | None = None
location: str | None = None
experience_years: float | None = None
scores: MatchScores
matched_skills: list[str] = Field(default_factory=list)
missing_skills: list[str] = Field(default_factory=list)
rationale: Rationale = Field(default_factory=Rationale)
metadata: MatchMetadata = Field(default_factory=MatchMetadata)
class SearchFilters(BaseModel):
location: str | None = None
min_experience_years: float | None = None
max_experience_years: float | None = None
remote_ok: bool = False
exclude_companies: list[str] = Field(default_factory=list)
include_companies: list[str] = Field(default_factory=list)
class SearchRequest(BaseModel):
query: str = Field(min_length=1, max_length=2000)
filters: SearchFilters = Field(default_factory=SearchFilters)
max_results: int = Field(default=10, ge=1, le=100)
include_rationale: bool = True
language: str | None = None
use_turbo: bool = False
slider_weights: dict[str, float] = Field(default_factory=dict)
class SearchResultItem(BaseModel):
rank: int
profile_id: str
name: str
current_title: str | None = None
current_company: str | None = None
location: str | None = None
experience_years: float | None = None
scores: MatchScores
matched_skills: list[str] = Field(default_factory=list)
missing_skills: list[str] = Field(default_factory=list)
rationale: Rationale
passive_candidate: bool = False
language_matched: bool = False
class SearchMetadata(BaseModel):
methods_used: list[str] = Field(default_factory=list)
replan_count: int = 0
total_time_ms: int = 0
listwise_ranked: bool = False
pii_anonymized: bool = True
total_profiles_in_index: int = 0
class SearchResponse(BaseModel):
query_id: str
total_candidates_searched: int
results: list[SearchResultItem] = Field(default_factory=list)
message: str | None = None
suggestions: list[str] = Field(default_factory=list)
processing_time_ms: int = 0
search_metadata: SearchMetadata = Field(default_factory=SearchMetadata)
class IngestResponse(BaseModel):
total_profiles: int
successful: int
failed: int
language_distribution: dict[str, int] = Field(default_factory=dict)
errors: list[str] = Field(default_factory=list)
class HealthResponse(BaseModel):
status: str
version: str
index_size: int
models_loaded: dict[str, bool] = Field(default_factory=dict)
last_updated: str | None = None