resume-api / Ars /objects.py
Nattyboi's picture
genai (#12)
6a0aab0 verified
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