File size: 18,408 Bytes
f8a0c51
 
 
 
 
6a0aab0
f8a0c51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6a0aab0
f8a0c51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
from datetime import datetime
from pydantic import Field, model_validator, BaseModel
from typing import List, Optional, Union,Type, TypeVar
from bson import ObjectId
import openai
from google import genai
from google.genai import types
import os
from dotenv import load_dotenv
load_dotenv()
GOOGLE_API_KEY=os.getenv("GEMINI_API_KEY")
OPENAI_API_KEY=os.getenv("OPENAI_API_KEY")
T = TypeVar("T", bound=BaseModel)
class AIWrapper:
    def __init__(self, provider='openai'):
        self.provider = provider.lower()

        if self.provider == 'openai':
            openai.api_key = OPENAI_API_KEY
        elif self.provider == 'gemini':
            self.gemini_client = genai.Client(
    api_key=GOOGLE_API_KEY,
    http_options=types.HttpOptions(api_version='v1alpha')
)
        else:
            raise ValueError("Provider must be 'openai' or 'gemini'")

    def chat(self, prompt: str,output_schema:Type[T]) -> T:
        """
        Generate a response from the AI provider and return it parsed into the specified schema.

        Args:
            prompt (str): The input prompt.
            output_schema (Type[T]): A Pydantic model representing the output schema.

        Returns:
            T: Parsed AI response as an instance of the output_schema.
        """
        if self.provider == 'openai':
            return self._openai_chat(prompt)
        elif self.provider == 'gemini':
            return self._gemini_chat(prompt,output_schema=output_schema)

    def _openai_chat(self, prompt: str) -> str:
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=[
                {"role": "user", "content": prompt}
            ]
        )
        return response['choices'][0]['message']['content']

    def _gemini_chat(self, prompt: str, output_schema: Type[T]) -> T:
        response = self.gemini_client.models.generate_content(
    model='gemini-2.0-flash-001',
    contents=prompt,
    config=types.GenerateContentConfig(
        response_mime_type='application/json',
        response_schema=output_schema,
    ),
)

        return response.parsed
    
    
ai = AIWrapper(provider='gemini')

class UserResilienceScoreCreate(BaseModel):

    overallScore: float
    userId: str
    BreakDownByDomainId: str
    FlaggedRiskAreasId: str
    BoostSuggestionsId: str


class UserResilienceScoreUpdate(BaseModel):

    overallScore: Optional[float]=None
    BreakDownByDomainId: Optional[str]=None
    FlaggedRiskAreasId: Optional[str]=None
    BoostSuggestionsId: Optional[str]=None

    
    
class BreakDownByDomainCreate(BaseModel):
    userId:str
    Technical:float
    Creative:float
    Strategy:float
    Collaboration:float
    
    
class BreakDownByDomainUpdate(BaseModel):
    Technical:Optional[float]=None
    Creative:Optional[float]=None
    Strategy:Optional[float]=None
    Collaboration:Optional[float]=None

    
class FlaggedRiskAreasCreate(BaseModel):
    userId:str
    risk_areas:List[str]
    
class FlaggedRiskAreasUpdate(BaseModel):

    risk_areas:Optional[List[str]]=None
    
    
class BoostSuggestionsCreate(BaseModel):

    boost_suggestions:List[str]
   
   

class ProjectExperienceDetails(BaseModel):
    ProjectTitles: str = Field(..., description="The title(s) of the project(s) involved in.")
    descriptions: str = Field(..., description="Detailed description of the project and what it entailed.")
    RoleInTheProject: str = Field(..., description="The specific role played within the project.")

class WorkExperienceDetails(BaseModel):
    JobTitles: str = Field(..., description="The job titles held in past employment.")
    JobDescriptions: str = Field(..., description="Summary of responsibilities and duties in these jobs.")

class SoftTransferableSkills(BaseModel):
    LeadershipAndCollaborationIndicators: str = Field(..., description="Evidence or examples demonstrating leadership and teamwork.")
    CriticalThinkingOrProblemSolvingVerb: str = Field(..., description="Examples of critical thinking or problem solving performed.")
    CommunicationSkills: str = Field(None, description="Description of communication skills and contexts.")
    CrossFunctionalOrInterdisciplinaryExperience: str = Field(..., description="Experience working across teams or disciplines.")
    InitiativeAndAdaptabilityLanguage: str = Field(..., description="Examples of taking initiative and adapting to change.")

class CareerPathInformation(BaseModel):
    CurrentOrIntendedRoleOrField: str = Field(..., description="Current or intended professional role or field of work.")
    IndustryAndSectorContext: str = Field(..., description="Context about the industry and sector related to the career path.")
    CareerTrajectoryTrends: str = Field(..., description="Observed or expected trends in the career trajectory or sector.")

class EvidenceOfUpskillingAndLifelongLearning(BaseModel):
    CertificationsCoursesOrBootcampsListed: Optional[List[str]] = Field(None, description="List of certifications, courses, or bootcamps completed.")
    SelfInitiatedLearningProjectsOrNonDegreeEducationalAchievements: Optional[List[str]] = Field(None, description="List of personal projects or non-degree achievements.")
    ParticipationInHackathonsClubsOrProfessionalCommunities: Optional[List[str]] = Field(None, description="Involvement in hackathons, clubs, or professional groups.")

class AIRelatedKeywords(BaseModel):
    AiToolsAndTechnologies: Optional[List[str]] = Field(
        None,
        description="List of AI tools and technologies mentioned in the resume, e.g., ChatGPT, TensorFlow."
    )
    conceptsAndTechniques: Optional[List[str]] = Field(
        None,
        description="AI concepts or techniques like NLP, computer vision, or reinforcement learning."
    )
    aiIntegratedProjectsMentioned: Optional[List[str]] = Field(
        None,
        description="Names or descriptions of projects where AI was applied."
    )
    usageContextDescriptions: Optional[List[str]] = Field(
        None,
        description="Sentences or phrases describing how AI was used in projects or tasks."
    )

class ResumeData(BaseModel):
    workExperienceDetails:Optional[List[WorkExperienceDetails]]=None
    listOfExplicitTechnicalSkills:Optional[List[str]]=None
    softTransferableSkills:List[SoftTransferableSkills]
    projectExperienceDetails:Optional[List[ProjectExperienceDetails]]=None
    careerPathInformation:CareerPathInformation
    evidenceOfUpskillingAndLifelongLearning:Optional[EvidenceOfUpskillingAndLifelongLearning]=None
    aiRelatedKeywords:AIRelatedKeywords

class RealWorldQuestion(BaseModel):
    question:str

    
    

class AutomationRiskInput(BaseModel):
    # Resume background fields
    job_title: str = Field(..., description="Most recent job title")
    industry: str = Field(..., description="Industry sector (e.g., finance, education, manufacturing)")
    years_experience: int = Field(..., ge=0, description="Years of professional experience")
    education_level: str = Field(..., description="Highest education level (e.g., Bachelors, Masters, PhD)")

    technical_skills: List[str] = Field(default_factory=list, description="List of technical skills")
    soft_skills: List[str] = Field(default_factory=list, description="List of soft skills")
    managerial_experience: bool = Field(..., description="Has managed teams or projects")
    customer_facing_roles: bool = Field(..., description="Has held customer-facing roles")
    domain_specialization: Optional[str] = Field(None, description="Specialized domain (e.g., legal, medical)")
    recent_certifications: List[str] = Field(default_factory=list, description="Certifications obtained recently")

    # Scored traits (all int 0-5)
    repetitiveness_score: int = Field(..., ge=0, le=5, description="Repetitiveness of the tasks performed")
    creativity_score: int = Field(..., ge=0, le=5, description="Creativity required in the role")
    emotional_intelligence_score: int = Field(..., ge=0, le=5, description="Importance of emotional intelligence")
    data_driven_tasks_score: int = Field(..., ge=0, le=5, description="Dependence on data-driven tasks")
    physical_task_score: int = Field(..., ge=0, le=5, description="Amount of physical/manual work")
    decision_making_level: int = Field(..., ge=0, le=5, description="Level of autonomous decision-making")
    strategic_thinking_score: int = Field(..., ge=0, le=5, description="Need for strategic thinking")
    collaboration_score: int = Field(..., ge=0, le=5, description="Collaboration required in the role")
    ai_dependency_score: int = Field(..., ge=0, le=5, description="How much AI tools are already used")
    upskilling_index: int = Field(..., ge=0, le=5, description="Recent evidence of upskilling/adaptability")


class AutomationRiskResult(AutomationRiskInput):
    result: Optional[int] =0
    @model_validator(mode='after')
    def calculate_result(self,cls) -> int:
        """
        Calculate the overall automation risk score (0-100)
        based on the scored traits.
        """

        # Weights for each scored trait (example weights; you can tune these)
        weights = {
            "repetitiveness_score": 15,
            "creativity_score": -10,
            "emotional_intelligence_score": -10,
            "data_driven_tasks_score": 10,
            "physical_task_score": 10,
            "decision_making_level": -10,
            "strategic_thinking_score": -10,
            "collaboration_score": -5,
            "ai_dependency_score": 5,
            "upskilling_index": -5,
        }

        # Sum weighted scores
        score = 0
        for field, weight in weights.items():
            value = getattr(self, field)
            score += value * weight

        # Normalize score to 0-100 range
        # Minimum possible score
        min_score = sum(0 * w for w in weights.values())
        # Maximum possible score
        max_score = sum(5 * w if w > 0 else 0 for w in weights.values()) + \
                    sum(0 * w if w < 0 else 0 for w in weights.values())

        # Because some weights are negative, min/max can be tricky.
        # Let's compute min and max more precisely:

        min_score = sum(0 * w if w > 0 else 5 * w for w in weights.values())
        max_score = sum(5 * w if w > 0 else 0 * w for w in weights.values())

        # Clamp the score between min and max
        score = max(min_score, min(max_score, score))

        # Map score linearly to 0-100
        normalized_score = int((score - min_score) / (max_score - min_score) * 100)

        self.result = normalized_score
        return self

    



class SkillDepthInput(BaseModel):
    # Core scoring fields (all 0-5 integers)
    years_experience_per_skill: int = Field(..., ge=0, le=5, description="Depth of years experience per skill")
    seniority_level: int = Field(..., ge=0, le=5, description="Seniority level in roles held")
    certification_presence: int = Field(..., ge=0, le=5, description="Number and relevance of certifications")
    breadth_of_skills: int = Field(..., ge=0, le=5, description="Variety and diversity of skills")
    technical_skill_depth: int = Field(..., ge=0, le=5, description="Depth in core technical skills")
    leadership_skill_depth: int = Field(..., ge=0, le=5, description="Depth in leadership or management skills")
    complex_projects_involvement: int = Field(..., ge=0, le=5, description="Involvement in complex projects")
    strategic_initiatives_contribution: int = Field(..., ge=0, le=5, description="Contributions to strategic initiatives")
    recent_skill_usage_frequency: int = Field(..., ge=0, le=5, description="Frequency of skill usage in recent roles")
    continuous_learning_evidence: int = Field(..., ge=0, le=5, description="Evidence of continuous learning or upskilling")
    cross_functional_collaboration: int = Field(..., ge=0, le=5, description="Cross-functional collaboration skills")
    recognition_awards: int = Field(..., ge=0, le=5, description="Recognition or awards related to skills")
    public_speaking_training: int = Field(..., ge=0, le=5, description="Public speaking or training experience")
    publications_patents: int = Field(..., ge=0, le=5, description="Publications or patents (if any)")
    industry_expertise_depth: int = Field(..., ge=0, le=5, description="Industry-specific expertise depth")
    mentoring_coaching_experience: int = Field(..., ge=0, le=5, description="Mentoring or coaching experience")
    innovation_ability: int = Field(..., ge=0, le=5, description="Ability to innovate using skills")
    adaptability_to_technologies: int = Field(..., ge=0, le=5, description="Adaptability to new technologies")
    problem_solving_depth: int = Field(..., ge=0, le=5, description="Problem-solving skills depth")
    technical_communication_skills: int = Field(..., ge=0, le=5, description="Communication skills related to technical content")


class SkillDepthResult(SkillDepthInput):
    result: Optional[int] =0
    @model_validator(mode='after')
    def calculate_result(self) -> None:
        fields = [
            self.years_experience_per_skill,
            self.seniority_level,
            self.certification_presence,
            self.breadth_of_skills,
            self.technical_skill_depth,
            self.leadership_skill_depth,
            self.complex_projects_involvement,
            self.strategic_initiatives_contribution,
            self.recent_skill_usage_frequency,
            self.continuous_learning_evidence,
            self.cross_functional_collaboration,
            self.recognition_awards,
            self.public_speaking_training,
            self.publications_patents,
            self.industry_expertise_depth,
            self.mentoring_coaching_experience,
            self.innovation_ability,
            self.adaptability_to_technologies,
            self.problem_solving_depth,
            self.technical_communication_skills,
        ]

        max_total = 5 * len(fields)
        total_score = sum(fields)
        self.result = int((total_score / max_total) * 100)
        return self

    
class AICollabReadinessInput(BaseModel):
    ai_tool_familiarity: int = Field(..., ge=0, le=5, description="Familiarity with AI tools and platforms")
    adaptability_to_ai_workflows: int = Field(..., ge=0, le=5, description="Ability to adapt to AI-enhanced workflows")
    willingness_to_learn_ai_skills: int = Field(..., ge=0, le=5, description="Motivation and willingness to learn AI skills")
    ai_ethics_understanding: int = Field(..., ge=0, le=5, description="Understanding of AI ethics and responsible use")
    collaboration_with_ai: int = Field(..., ge=0, le=5, description="Experience or mindset to collaborate effectively with AI systems")
    problem_solving_with_ai: int = Field(..., ge=0, le=5, description="Skill in using AI to solve complex problems")
    creativity_in_ai_use: int = Field(..., ge=0, le=5, description="Creativity in leveraging AI capabilities")
    ai_learning_speed: int = Field(..., ge=0, le=5, description="Speed of learning new AI technologies")
    communication_about_ai: int = Field(..., ge=0, le=5, description="Ability to communicate AI concepts effectively")
    ai_tool_integration: int = Field(..., ge=0, le=5, description="Skill in integrating AI tools into existing workflows")

class AICollabReadiness(AICollabReadinessInput):
    result: Optional[int] =0
    @model_validator(mode='after')
    def calculate_result(self) -> None:
        fields = [
            self.ai_tool_familiarity,
            self.adaptability_to_ai_workflows,
            self.willingness_to_learn_ai_skills,
            self.ai_ethics_understanding,
            self.collaboration_with_ai,
            self.problem_solving_with_ai,
            self.creativity_in_ai_use,
            self.ai_learning_speed,
            self.communication_about_ai,
            self.ai_tool_integration,
        ]
        max_total = 5 * len(fields)
        total_score = sum(fields)
        self.result = int((total_score / max_total) * 100)
        return self

    
    
    
class BoostSuggestionsUpdate(BaseModel):

    boost_suggestions:List[str]
   


class UserResilienceScoreOut(UserResilienceScoreCreate):
    _id: Optional[ObjectId]=None  # Make sure _id can be Optional
    id:Optional[str]=None
    # To convert MongoDB ObjectId to string
    class Config:
        json_encoders = {
            ObjectId: str
        }

    # Custom validator to handle the ObjectId conversion if needed
    @model_validator(mode='before')
    def handle_objectid(cls, values):
        if '_id' in values and isinstance(values['_id'], ObjectId):
            values['id'] = str(values['_id'])  # Convert ObjectId to string
        return values
    
class BreakDownByDomainOut(BreakDownByDomainCreate):
    _id: Optional[ObjectId]=None  # Make sure _id can be Optional
    id:Optional[str]=None
    # To convert MongoDB ObjectId to string
    class Config:
        json_encoders = {
            ObjectId: str
        }

    # Custom validator to handle the ObjectId conversion if needed
    @model_validator(mode='before')
    def handle_objectid(cls, values):
        if '_id' in values and isinstance(values['_id'], ObjectId):
            values['id'] = str(values['_id'])  # Convert ObjectId to string
        return values
    
class FlaggedRiskAreasOut(FlaggedRiskAreasCreate):
    _id: Optional[ObjectId]=None  # Make sure _id can be Optional
    id:Optional[str]=None
    class Config:
        json_encoders = {
            ObjectId: str
        }

    # Custom validator to handle the ObjectId conversion if needed
    @model_validator(mode='before')
    def handle_objectid(cls, values):
        if '_id' in values and isinstance(values['_id'], ObjectId):
            values['id'] = str(values['_id'])  # Convert ObjectId to string
        return values
class BoostSuggestionsOut(BoostSuggestionsCreate):
    _id: Optional[ObjectId]=None  # Make sure _id can be Optional
    id:Optional[str]=None
    class Config:
        json_encoders = {
            ObjectId: str
        }

    # Custom validator to handle the ObjectId conversion if needed
    @model_validator(mode='before')
    def handle_objectid(cls, values):
        if '_id' in values and isinstance(values['_id'], ObjectId):
            values['id'] = str(values['_id'])  # Convert ObjectId to string
        return values