예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
    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
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
 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()
예제 #7
0
    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
예제 #8
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()
        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
예제 #9
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)])
예제 #10
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
예제 #11
0
 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
예제 #12
0
    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)])
예제 #13
0
    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
예제 #14
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
예제 #15
0
    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()
예제 #16
0
    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)])
예제 #17
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
예제 #18
0
    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)
예제 #19
0
    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()
예제 #20
0
    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()
예제 #21
0
    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
예제 #22
0
    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()
예제 #23
0
    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()
예제 #24
0
    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
예제 #25
0
    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
예제 #26
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",
            ),
        ])
예제 #27
0
    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
예제 #28
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
예제 #29
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
예제 #30
0
 def score_to_hundred(self, project: scoring_base.ScoringProject) -> int:
     if self.frustration in project.user_profile.frustrations:
         return 0
     raise scoring_base.NotEnoughDataException()