def score(self, project: scoring_base.ScoringProject) -> float: job_search_length = project.get_search_length_now() # User has not started their job search. if job_search_length <= 0: raise scoring_base.NotEnoughDataException( 'Search not started yet. We cannot say whether this is blocking.') missing_fields: Set[str] = set() if not project.details.total_interview_count: missing_fields.add('projects.0.totalInterviewCount') if not project.details.weekly_applications_estimate: missing_fields.add('projects.0.weeklyApplicationsEstimate') # Either negative or between 0 and 1. interview_score = 1 - max(0, project.details.total_interview_count) / job_search_length if interview_score <= 0: # User has at least one interview per month, they don't need more tips on getting # interviews. return 0 if missing_fields: raise scoring_base.NotEnoughDataException( 'Missing some information about applications', fields=missing_fields) # Varies between 0 and 3. application_score = _APPLICATION_PER_WEEK[project.details.weekly_applications_estimate] / 5 # Varies between 0 and 3. return interview_score * application_score
def score_to_hundred(self, project: scoring_base.ScoringProject) -> float: """Compute a score for the given ScoringProject.""" area_type = project.details.area_type if not area_type: raise scoring_base.NotEnoughDataException() if not project.imt_proto().yearly_avg_offers_per_10_candidates: raise scoring_base.NotEnoughDataException() num_better_departements = project.local_diagnosis( ).num_less_stressful_departements # User is already in one of the top 3 départements. if num_better_departements < 3: raise scoring_base.NotEnoughDataException() # Give a score centered around 50 corresponding to the user's mobility. # The worse the current département is, the more extreme we set the # score. score_range = min(num_better_departements * 1.5, 100) / 2 if area_type >= geo_pb2.COUNTRY: return 50 + score_range if area_type >= geo_pb2.REGION: return 50 if area_type >= geo_pb2.DEPARTEMENT: return 50 - score_range / 2 return 50 - score_range
def score(self, project: scoring_base.ScoringProject) -> float: application_modes = project.job_group_info().application_modes.values() missing_fields: Set[str] = set() # User's job has no application modes info. if not application_modes: rome_id = project.details.target_job.job_group.rome_id missing_fields.add( f'data.job_group_info.{rome_id}.application_modes') raise scoring_base.NotEnoughDataException( "User's job has no application modes info. We cannot say whether this is blocking.", fields=missing_fields) user_application_mode = project.details.preferred_application_mode if not user_application_mode: missing_fields.add('projects.0.preferredApplicationMode') raise scoring_base.NotEnoughDataException( "Missing some information about user's application modes", fields=missing_fields) first_modes = project.get_fap_modes() second_modes = project.get_fap_modes(rank='second') if user_application_mode in first_modes: # User uses the correct application mode. return 0 if user_application_mode in second_modes: # User uses one of the best application modes. return 1 return 3
def score_to_hundred(self, project): """Compute a score for the given ScoringProject.""" area_type = project.details.mobility.area_type if not area_type: raise scoring_base.NotEnoughDataException() num_better_departements = len(_find_best_departements(None, project)) # User is already in one of the top 3 départements. if num_better_departements < 3: raise scoring_base.NotEnoughDataException() # Give a score centered around 50 corresponding to the user's mobility. # The worse the current département is, the more extreme we set the # score. score_range = min(num_better_departements * 1.5, 100) / 2 if area_type >= geo_pb2.COUNTRY: return 50 + score_range if area_type >= geo_pb2.REGION: return 50 if area_type >= geo_pb2.DEPARTEMENT: return 50 - score_range / 2 return 50 - score_range
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: """Compute a score for the given ScoringProject.""" if project.details.job_search_has_not_started: raise scoring_base.NotEnoughDataException() nb_applications = _ESTIMATE_OPTION_TO_NUMBER[ project.details.weekly_applications_estimate] if nb_applications == 0 or nb_applications > 5: raise scoring_base.NotEnoughDataException() # rescale: 0 -> 0, 5 -> 100 return 20 * nb_applications
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: # Only score if user is not sure about this. if (project.details.training_fulfillment_estimate is not project_pb2.TRAINING_FULFILLMENT_NOT_SURE): raise scoring_base.NotEnoughDataException() max_requirement = -1 for diploma in project.job_group_info().requirements.diplomas: if diploma.percent_required > max_requirement: max_requirement = diploma.percent_required if max_requirement >= 0: return 100 - max_requirement raise scoring_base.NotEnoughDataException()
def score_to_hundred(self, project: scoring_base.ScoringProject) -> float: """Compute a score for the given ScoringProject.""" if project.details.weekly_applications_estimate == project_pb2.LESS_THAN_2: raise scoring_base.NotEnoughDataException() if project.details.total_interview_count < 0: raise scoring_base.NotEnoughDataException() search_since_nb_months = project.get_search_length_at_creation() if search_since_nb_months < 0: raise scoring_base.NotEnoughDataException() interviews_per_month = project.details.total_interview_count / search_since_nb_months return interviews_per_month * 15
def score_to_hundred(self, project): """Compute a score for the given ScoringProject.""" if project.details.job_search_has_not_started: raise scoring_base.NotEnoughDataException() nb_applications = _ESTIMATE_OPTION_TO_NUMBER.get( project.details.weekly_applications_estimate) if nb_applications <= 5: # This is just to ensure that too many applications get sanctionned, # we don't look at what happens below. raise scoring_base.NotEnoughDataException() return 0
def score_to_hundred(self, project): """Compute a score for the given ScoringProject.""" if project.details.job_search_has_not_started: raise scoring_base.NotEnoughDataException() if not project.details.HasField('job_search_started_at'): raise scoring_base.NotEnoughDataException() search_since_nb_months = project.get_search_length_at_creation() if search_since_nb_months > 12: return 0 return _interpolate_points(search_since_nb_months, [(0, 100), (3, 50), (6, 30), (12, 0)])
def filter(self, project: scoring_base.ScoringProject) -> bool: """Whether the project should pass or fail the filter.""" super_groups_count = _count_project_super_groups(project) if super_groups_count == 0: # User has no target job. return True if super_groups_count >= 3: # User has too many objectives (jobs belonging to the same super group are part of the # same objective, but here there are too many super groups). return True if len(project.user.projects) > 1: # User has one or two objectives, that they entered willingfully. return False if not project.details.has_clear_project: # Unsure project, we must ask whether the project is clear to the user. raise scoring_base.NotEnoughDataException( 'User only has one project', fields={'projects.0.hasClearProject'}) # User told us whether their project was clear. return project.details.has_clear_project == boolean_pb2.FALSE
def score_to_hundred(self, project: scoring_base.ScoringProject) -> float: market_stress = project.market_stress() if not market_stress: raise scoring_base.NotEnoughDataException() if market_stress == 1000: return 0 return 1 / market_stress * 100
def score_to_hundred(self, project: scoring_base.ScoringProject) -> float: """Compute a percentage score for the given ScoringProject.""" growth_2012_2022 = project.job_group_info().growth_2012_2022 if not growth_2012_2022: raise scoring_base.NotEnoughDataException() return _interpolate_points(growth_2012_2022, [(-.17, 0), (.07, 50), (.29, 100)])
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: """Compute a score for the given ScoringProject.""" if project.details.job_search_has_not_started: raise scoring_base.NotEnoughDataException() try: nb_applications = _ESTIMATE_OPTION_TO_NUMBER[ project.details.weekly_applications_estimate] except KeyError as err: logging.error( '_ESTIMATE_OPTION_TO_NUMBER should have all keys from ' 'project_pb2.NumberOfferEstimateOption') raise scoring_base.NotEnoughDataException() from err if nb_applications <= 5: # This is just to ensure that too many applications get sanctionned, # we don't look at what happens below. raise scoring_base.NotEnoughDataException() return 0
def score_to_hundred(self, project: scoring_base.ScoringProject) -> float: local_diagnosis = project.local_diagnosis() offers_change = local_diagnosis.job_offers_change if local_diagnosis.num_job_offers_previous_year < 5\ and local_diagnosis.num_job_offers_last_year < 5: raise scoring_base.NotEnoughDataException() if offers_change < 0: return 0 # rescale offers: >=0 -> 50, >=10 -> 100 return offers_change * 5 + 50
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: """Compute a percentage score for the given ScoringProject.""" if project.details.network_estimate == 1: return 0 if project.details.network_estimate == 2: return 60 if project.details.network_estimate == 3: return 100 raise scoring_base.NotEnoughDataException()
def score_to_hundred(self, project: scoring_base.ScoringProject) -> float: """Compute a score for the given ScoringProject.""" search_since_nb_months = project.get_search_length_at_creation() if search_since_nb_months < 0: raise scoring_base.NotEnoughDataException() if search_since_nb_months > 12: return 0 return _interpolate_points(search_since_nb_months, [(0, 100), (3, 50), (6, 30), (12, 0)])
def score(self, project: scoring_base.ScoringProject) -> float: if not project.details.target_job.job_group.rome_id: raise scoring_base.NotEnoughDataException( 'Need a job group to determine if the user has enough diplomas', {'projects.0.targetJob.jobGroup.romeId'}) required_diploma = min( (r.diploma.level for r in project.job_group_info().requirements.diplomas), default=job_pb2.UNKNOWN_DEGREE) or \ _DIPLOMA_REQUIREMENTS.get(project.details.target_job.job_group.rome_id[:5]) # TODO(pascal): Check the is_diploma_strictly_required bool. if not required_diploma: raise scoring_base.NotEnoughDataException( 'No information about this job group diploma requirements', {'data.job_group_info.requirements.diplomas'}) if required_diploma == job_pb2.NO_DEGREE: return 0 if project.user_profile.highest_degree >= required_diploma: return 0 return 3
def score_to_hundred(self, project): """Compute a percentage score for the given ScoringProject.""" local_diagnosis = project.local_diagnosis() if not local_diagnosis.unemployment_duration.days: raise scoring_base.NotEnoughDataException() # Rescale unemployment_duration: 0 -> 100, >= 12 months -> 0 # As unemployment_duration has much more low values in DB, use ** .7 to have more variance # in high scores (50% -> 136 ~= median). return 100 * (1 - (local_diagnosis.unemployment_duration.days / 365)**.7)
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: """Compute a percentage score for the given ScoringProject.""" if project.details.passionate_level == project_pb2.ALIMENTARY_JOB: return 0 if project.details.passionate_level == project_pb2.LIKEABLE_JOB: return 50 if project.details.passionate_level == project_pb2.PASSIONATING_JOB: return 80 if project.details.passionate_level == project_pb2.LIFE_GOAL_JOB: return 100 raise scoring_base.NotEnoughDataException()
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: """Compute a percentage score for the given ScoringProject.""" if project.details.seniority == project_pb2.INTERN: return 0 if project.details.seniority == project_pb2.JUNIOR: return 33 if project.details.seniority == project_pb2.INTERMEDIARY: return 67 if project.details.seniority >= project_pb2.SENIOR: return 100 raise scoring_base.NotEnoughDataException()
def score_to_hundred(self, project): """Compute a percentage score for the given ScoringProject.""" if project.details.seniority == project_pb2.UNKNOWN_PROJECT_SENIORITY: raise scoring_base.NotEnoughDataException() if project.details.seniority == project_pb2.INTERNSHIP: return 0 if project.details.seniority == project_pb2.JUNIOR: return 33 if project.details.seniority == project_pb2.INTERMEDIARY: return 67 if project.details.seniority >= project_pb2.SENIOR: return 100
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: """Compute a percentage score for the given ScoringProject.""" if (project.details.training_fulfillment_estimate == project_pb2.TRAINING_FULFILLMENT_NOT_SURE): return 0 if project.details.training_fulfillment_estimate in [ project_pb2.ENOUGH_DIPLOMAS, project_pb2.ENOUGH_EXPERIENCE, project_pb2.NO_TRAINING_REQUIRED ]: return 100 if project.details.training_fulfillment_estimate == project_pb2.CURRENTLY_IN_TRAINING: return 50 raise scoring_base.NotEnoughDataException()
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: """Compute a percentage score for the given ScoringProject.""" if project.details.previous_job_similarity == project_pb2.NEVER_DONE: return 0 if project.details.previous_job_similarity == project_pb2.DONE_SIMILAR: if user_profile_pb2.ATYPIC_PROFILE in project.user_profile.frustrations: return 60 return 50 if project.details.previous_job_similarity == project_pb2.DONE_THIS: if user_profile_pb2.MOTIVATION in project.user_profile.frustrations: return 100 return 90 raise scoring_base.NotEnoughDataException()
def _filter(self, project: scoring_base.ScoringProject) -> bool: super_groups_count = _count_project_super_groups(project) if not super_groups_count or super_groups_count > 2: # User project is not clear. return False if project.details.seniority < project_pb2.SENIOR: # User is not experienced enough. return False if project.user_profile.is_autonomous == boolean_pb2.UNKNOWN_BOOL: raise scoring_base.NotEnoughDataException( "Don't know if user is autonomous.", fields={'profile.isAutonomous'}) return project.user_profile.is_autonomous == boolean_pb2.FALSE
def score_to_hundred(self, project): """Compute a score for the given ScoringProject.""" area_type = project.details.mobility.area_type if not area_type: raise scoring_base.NotEnoughDataException() if area_type >= geo_pb2.COUNTRY: return 100 if area_type >= geo_pb2.REGION: return 70 if area_type >= geo_pb2.DEPARTEMENT: return 50 return 0
def score_and_explain(self, project: scoring_base.ScoringProject) \ -> scoring_base.ExplainedScore: """Compute a score for the given ScoringProject.""" has_any_covid_risk_info = jobs.has_covid_risk_info(project.database) has_any_automation_risk_info = jobs.has_automation_risk_info( project.database) if not has_any_covid_risk_info and not has_any_automation_risk_info: raise scoring_base.NotEnoughDataException( 'No data about jobs being affected by Covid or automation', { 'data.job_group_info.covid_risk', 'data.job_group_info.automation_risk' }) # Total risk from 0 to 100. total_risk = 0 # Covid risk: 0 if safe or no covid data at all, 25 if unknown, 50 if risky. covid_risk = project.job_group_info().covid_risk if covid_risk == job_pb2.COVID_RISKY: total_risk += 50 elif not covid_risk and has_any_covid_risk_info: total_risk += 25 # Automation risk: 0 if super safe or no covid data at all, 25 if unknown, 50 if very risky. automation_risk = project.job_group_info().automation_risk if automation_risk: total_risk += automation_risk // 2 elif has_any_automation_risk_info: total_risk += 25 if total_risk <= 15: # This job is as safe as it can be, no need to explore for more. return scoring_base.NULL_EXPLAINED_SCORE # 81+ => 3 return scoring_base.ExplainedScore(min((total_risk - 15) / 22, 3), [ project.translate_static_string( "il existe des métiers avec peu de risques d'automatisation", ), ])
def score(self, project: scoring_base.ScoringProject) -> float: """Compute a score for the given ScoringProject.""" lang_requirements = _LANGUAGE_REQUIREMENTS.get(project.details.city.departement_id) if not lang_requirements: # Default for France. lang_requirements = {'fr'} lang_knowledge = { knowledge.locale: knowledge for knowledge in project.user_profile.languages if knowledge.locale in lang_requirements } missing_fields = set() # Check that the user speaks at least one of the required languages. for lang in lang_requirements: knowledge = lang_knowledge.get(lang, _UNKNOWN_LANG) if knowledge.has_spoken_knowledge == boolean_pb2.UNKNOWN_BOOL: # No clue about their level. missing_fields.add(f'profile.languages.{lang}.hasSpokenKnowledge') continue if knowledge.has_spoken_knowledge == boolean_pb2.TRUE: # User has at least one required spoken language. missing_fields = set() break else: if not missing_fields: # User does not speak any of the required language. return 3 if missing_fields: raise scoring_base.NotEnoughDataException( 'Need local spoken language knowledge', fields=missing_fields) return 0
def score(self, project: scoring_base.ScoringProject) -> float: # For specific job groups, check that the user speaks or write all the required languages. project_requirements = _get_project_requirements(project) if not project_requirements: return 0 lang_requirements = _LANGUAGE_REQUIREMENTS.get(project.details.city.departement_id) if not lang_requirements: # Default for France. lang_requirements = {'fr'} lang_knowledge = { knowledge.locale: knowledge for knowledge in project.user_profile.languages if knowledge.locale in lang_requirements } missing_fields = set() for lang in lang_requirements: knowledge = lang_knowledge.get(lang, _UNKNOWN_LANG) if project_requirements.is_spoken_required: if knowledge.has_spoken_knowledge == boolean_pb2.FALSE: return 3 if knowledge.has_spoken_knowledge == boolean_pb2.UNKNOWN_BOOL: missing_fields.add(f'profile.languages.{lang}.hasSpokenKnowledge') if project_requirements.is_written_required: if knowledge.has_written_knowledge == boolean_pb2.FALSE: return 3 if knowledge.has_written_knowledge == boolean_pb2.UNKNOWN_BOOL: missing_fields.add(f'profile.languages.{lang}.hasWrittenKnowledge') if missing_fields: raise scoring_base.NotEnoughDataException( 'Need project-specific language knowledge', fields=missing_fields) return 0
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: bmo = project.local_diagnosis().bmo if not bmo.percent_difficult: raise scoring_base.NotEnoughDataException() return bmo.percent_difficult
def score_to_hundred(self, project: scoring_base.ScoringProject) -> int: if self.frustration in project.user_profile.frustrations: return 0 raise scoring_base.NotEnoughDataException()