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