def _find_best_departements(unused_: Any, project: scoring_base.ScoringProject) \ -> list[project_pb2.DepartementScore]: """Find which are the best departement to relocate for a given job group.""" own_departement_offers = project.imt_proto( ).yearly_avg_offers_per_10_candidates # If we do not have data about our own departement, we choose not to say anything. if not own_departement_offers: return [] best_departements = project.job_group_info().departement_scores result: list[project_pb2.DepartementScore] = [] for dep in itertools.islice(best_departements, 10): if dep.local_stats.imt.yearly_avg_offers_per_10_candidates <= own_departement_offers: return result offer_ratio = \ dep.local_stats.imt.yearly_avg_offers_per_10_candidates / own_departement_offers result.append( project_pb2.DepartementScore(name=project.translate_string( geo.get_departement_name(project.database, dep.departement_id)), offer_ratio=offer_ratio)) return result
def score_and_explain(self, project: scoring_base.ScoringProject) \ -> scoring_base.ExplainedScore: """Compute a score for the given ScoringProject.""" local_jobbing = self.get_local_jobbing(project) if len(local_jobbing.reorient_jobbing_jobs) < 2: return scoring_base.NULL_EXPLAINED_SCORE score_modifier = 0 reasons: list[str] = [] if project.details.passionate_level == project_pb2.LIFE_GOAL_JOB: score_modifier = -2 if project.job_group_info().growth_2012_2022 < .1: score_modifier = -1 if score_modifier >= 0: reasons.append( project.translate_static_string( 'votre métier ne vous tient pas trop à cœur')) if project.user_profile.highest_degree <= job_pb2.CAP_BEP: return scoring_base.ExplainedScore(3 + score_modifier, reasons) if project.user_profile.highest_degree <= job_pb2.BAC_BACPRO: return scoring_base.ExplainedScore(max(2 + score_modifier, 1), reasons) if project.user_profile.highest_degree <= job_pb2.BTS_DUT_DEUG: return scoring_base.ExplainedScore(1, reasons) return scoring_base.NULL_EXPLAINED_SCORE
def _score_and_explain_after_filters(project: scoring_base.ScoringProject) \ -> scoring_base.ExplainedScore: """A helper function to give a score and an explanation for all advices in the module, once some prerequisite filters have been met. """ if project.user_profile.has_car_driving_license != boolean_pb2.FALSE: return scoring_base.NULL_EXPLAINED_SCORE reasons = [] license_required = next( (license.percent_required for license in project.job_group_info().requirements.driving_licenses if license.driving_license == job_pb2.CAR), 0) if license_required: reasons.append( project.translate_static_string( 'le permis est important dans votre métier')) score_modifier = 0 if _license_helps_mobility(project.details): reasons.append( project.translate_static_string( 'le permis augmenterait votre mobilité')) score_modifier = 1 if not reasons: return scoring_base.NULL_EXPLAINED_SCORE score = min( 3, score_modifier + ( # Example at 80% is civil engineer F1106. 3 if license_required > 80 else # Example at 67% is translator E1108. 2 if license_required > 67 else # Example at 50% is chiropractor J1408. 1 if license_required > 50 else 0)) return scoring_base.ExplainedScore(score, reasons)
def _max_monthly_interviews(project: scoring_base.ScoringProject) -> int: """Maximum number of monthly interviews one should have.""" if project.job_group_info( ).application_complexity == job_pb2.COMPLEX_APPLICATION_PROCESS: return 5 return 3
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: 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_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_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 _filter(self, project: scoring_base.ScoringProject) -> bool: """the filtering function for a given ScoringProject.""" application_modes = project.job_group_info().application_modes.values() for fap_modes in application_modes: if fap_modes.modes and fap_modes.modes[ 0].mode == self.application_mode: return True if len(fap_modes.modes) > 1 \ and fap_modes.modes[1].mode == self.application_mode \ and fap_modes.modes[0].percentage - fap_modes.modes[1].percentage <= \ self.delta_percentage: return True return False
def score_and_explain(self, project: scoring_base.ScoringProject) \ -> scoring_base.ExplainedScore: """Compute a score for the given ScoringProject.""" if project.details.network_estimate != self._network_level: return scoring_base.NULL_EXPLAINED_SCORE application_modes = project.job_group_info().application_modes.values() first_modes = set(fap_modes.modes[0].mode for fap_modes in application_modes) first_modes.discard(job_pb2.UNDEFINED_APPLICATION_MODE) if first_modes == {job_pb2.PERSONAL_OR_PROFESSIONAL_CONTACTS}: return scoring_base.ExplainedScore(3, [ 'le réseau est le canal n°1 pour trouver un métier %inDomain' ]) return scoring_base.ExplainedScore(2, [])
def _get_handcrafted_job_requirements(project: scoring_base.ScoringProject) \ -> Optional[job_pb2.JobRequirements]: """Handcrafted job requirements for the target job.""" handcrafted_requirements = job_pb2.JobRequirements() all_requirements = project.job_group_info().requirements handcrafted_fields = [ field for field in job_pb2.JobRequirements.DESCRIPTOR.fields_by_name.keys() if field.endswith('_short_text')] has_requirements = False for field in handcrafted_fields: field_requirements = getattr(all_requirements, field) if field_requirements: has_requirements = True setattr(handcrafted_requirements, field, field_requirements) if not has_requirements: return None return handcrafted_requirements
def score_and_explain(self, project: scoring_base.ScoringProject) \ -> scoring_base.ExplainedScore: """Compute a score for the given ScoringProject.""" application_modes = project.job_group_info().application_modes.values() first_modes = set(fap_modes.modes[0].mode for fap_modes in application_modes) first_modes.discard(job_pb2.UNDEFINED_APPLICATION_MODE) if first_modes == {job_pb2.PERSONAL_OR_PROFESSIONAL_CONTACTS}: return scoring_base.ExplainedScore(2, [ project.translate_string( 'les embauches se font surtout par les contacts personnels ou professionnels dans' ' votre métier') ]) return scoring_base.ExplainedScore(1, [ project.translate_string( "c'est un bon moyen d'étendre votre réseau") ])
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_and_explain(self, project: scoring_base.ScoringProject) \ -> scoring_base.ExplainedScore: """Compute a score for the given ScoringProject.""" application_modes = project.job_group_info().application_modes.values() first_modes = set(fap_modes.modes[0].mode for fap_modes in application_modes if len(fap_modes.modes)) if job_pb2.SPONTANEOUS_APPLICATION in first_modes: return scoring_base.ExplainedScore(3, [ project.translate_string( "c'est le canal de recrutement n°1 pour votre métier") ]) # In the category missing-diploma, we always have the alternance strategy which requires # spontaneous application data. if project.details.diagnostic.category_id == 'missing-diploma': return scoring_base.ExplainedScore(2, [ project.translate_string( "c'est le meilleur moyen de trouver un contrat en alternance" ) ]) second_modes = set(fap_modes.modes[1].mode for fap_modes in application_modes if len(fap_modes.modes) > 1) if job_pb2.SPONTANEOUS_APPLICATION in second_modes: return scoring_base.ExplainedScore(2, [ project.translate_string( "c'est un des meilleurs canaux de recrutement pour votre métier" ) ]) if project.details.diagnostic.category_id == 'bravo' and \ user_pb2.NO_OFFERS in project.user_profile.frustrations: return scoring_base.ExplainedScore(2, [ project.translate_string( "vous nous avez dit ne pas trouver assez d'offres.") ]) return scoring_base.NULL_EXPLAINED_SCORE
def _get_skills(self, project: scoring_base.ScoringProject) \ -> Sequence[skill_pb2.Skill]: """Return a list of skills recommendation for the project's target job.""" return project.job_group_info().skills_for_future
def _what_i_love_about(project: scoring_base.ScoringProject) -> str: return project.user_profile.gender == user_profile_pb2.FEMININE and \ project.job_group_info().what_i_love_about_feminine or \ project.job_group_info().what_i_love_about