File size: 11,584 Bytes
bb6b241
 
 
66788e6
bb6b241
 
 
 
 
 
 
 
 
 
 
 
7a88eff
bb6b241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e01a2b0
bb6b241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5c40e56
bb6b241
 
 
 
 
 
 
 
5c40e56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bb6b241
 
 
 
 
 
 
66788e6
 
bb6b241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3cae53e
bb6b241
 
 
 
 
1f02c7c
bb6b241
 
 
 
 
 
 
 
 
 
1ef35b6
 
 
 
 
bb6b241
 
 
5c40e56
 
 
1ef35b6
bb6b241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1ef35b6
 
bb6b241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72224ba
 
 
bb6b241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
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