Example #1
0
        return 50 - score_range


class _ProfileMobilityScorerModel(scoring_base.ModelHundredBase):
    """A model that scores the level of mobility of the user."""
    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


scoring_base.register_model('advice-relocate', _AdviceRelocateScoringModel())
scoring_base.register_model('profile-mobility-scorer',
                            _ProfileMobilityScorerModel())
scoring_base.register_model('project-mobility-score', _GoodMobilityModel())
        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 get_expanded_card_data(self, project: scoring_base.ScoringProject) \
            -> reorient_jobbing_pb2.JobbingReorientJobs:
        """Retrieve data for the expanded card."""

        return self.get_local_jobbing(project)


scoring_base.register_model('advice-reorient-jobbing',
                            _AdviceReorientJobbing())
Example #3
0
            for knowledge in project.user_profile.languages
            if knowledge.locale in lang_requirements
        }

        if not lang_knowledge:
            # We have no clue of user's language so we cannot say whether this is a big problem or
            # not: neutral relevance.
            return 1

        project_requirements = _get_project_requirements(project)

        # Check that the user speaks at least one of the required languages or, if there are some
        # project requirements, that those are met for all the required languages.
        for lang in lang_requirements:
            if not lang_knowledge.get(lang):
                # No clue about their level.
                if project_requirements:
                    # User has an unknown level in a required language.
                    return 1
                continue
            if lang_knowledge[
                    lang].has_spoken_knowledge and not project_requirements:
                # User has at least one required spoken language.
                return 3

        return 3


scoring_base.register_model('for-missing-language', _MissingLanguage())
scoring_base.register_model('language-relevance', _LanguageRelevance())
Example #4
0
                "c'est un bon moyen d'étendre votre réseau")
        ])

    @scoring_base.ScoringProject.cached('events')
    def list_events(
            self,
            project: scoring_base.ScoringProject) -> List[event_pb2.Event]:
        """List all events close to the project's target."""

        today = project.now.strftime('%Y-%m-%d')
        all_events = [
            e for e in self._db.get_collection(project.database)
            if e.start_date >= today
        ]
        return list(
            scoring_base.filter_using_score(all_events, lambda e: e.filters,
                                            project))

    def get_expanded_card_data(
            self, project: scoring_base.ScoringProject) -> event_pb2.Events:
        """Retrieve data for the expanded card."""

        events = self.list_events(project)
        sorted_events = sorted(
            events,
            key=lambda j: (j.start_date, -len(j.filters), random.random()))
        return event_pb2.Events(events=sorted_events)


scoring_base.register_model('advice-event', _AdviceEventScoringModel())
Example #5
0
            -> network_pb2.ContactLeads:
        """Retrieve data for the expanded card."""

        contact_leads = self._list_contact_leads(project)
        sorted_leads = sorted(contact_leads,
                              key=lambda l: (-len(l.filters), random.random()))
        return network_pb2.ContactLeads(leads=[
            network_pb2.ContactLead(
                name=project.populate_template(
                    project.translate_string(template.name)),
                email_example=project.populate_template(
                    project.translate_string(template.email_template)),
                contact_tip=project.translate_string(template.contact_tip))
            for template in sorted_leads
        ])

    @scoring_base.ScoringProject.cached('contact-leads')
    def _list_contact_leads(self, project: scoring_base.ScoringProject) \
            -> Iterator[network_pb2.ContactLeadTemplate]:
        return scoring_base.filter_using_score(
            self._db.get_collection(project.database), lambda l: l.filters,
            project)


scoring_base.register_model('advice-better-network',
                            _ImproveYourNetworkScoringModel(2))
scoring_base.register_model('advice-improve-network',
                            _ImproveYourNetworkScoringModel(1))
scoring_base.register_model('advice-use-good-network',
                            _ImproveYourNetworkScoringModel(3))
Example #6
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


class _MethodsToInterviewRelevance(scoring_base.RelevanceModelBase):
    """A scoring model for the relevance of the category enhance-method-to-interview."""

    def score_relevance(self, project: scoring_base.ScoringProject) \
            -> diagnostic_pb2.CategoryRelevance:
        try:
            project.score('category-enhance-methods-to-interview')
        except scoring_base.NotEnoughDataException:
            return diagnostic_pb2.NEUTRAL_RELEVANCE
        return diagnostic_pb2.RELEVANT_AND_GOOD


scoring_base.register_model('advice-fresh-resume', _AdviceFreshResume())
scoring_base.register_model('advice-improve-interview', _AdviceImproveInterview())
scoring_base.register_model('advice-improve-resume', _AdviceImproveResume())
scoring_base.register_model('category-enhance-methods-to-interview', _EnhanceMethodsToInterview())
scoring_base.register_model(
    'relevance-enhance-methods-to-interview', _MethodsToInterviewRelevance())
Example #7
0
                             _OldUserFilter, 'for-old(50)')
# Matches strings like "for-frustrated-old(50)".
scoring_base.register_regexp(re.compile(r'^for-frustrated-old\(([0-9]+)\)$'),
                             _FrustratedOldUserFilter,
                             'for-frustrated-old(50)')
# Matches strings like "for-frustrated(INTERVIEW)".
scoring_base.register_regexp(re.compile(r'^for-frustrated\((\w*)\)$'),
                             _FrustrationFilter, 'for-frustrated(INTERVIEW)')
# Matches strings like "for-young(25)".
scoring_base.register_regexp(re.compile(r'^for-young\(([0-9]+)\)$'),
                             _YoungUserFilter, 'for-young(25)')
scoring_base.register_regexp(re.compile(r'^for-passionate\((\w+)\)$'),
                             _PassionateFilter,
                             'for-passionate(LIFE_GOAL_JOB)')

scoring_base.register_model('advice-body-language', _AdviceBodyLanguage())
scoring_base.register_model('advice-follow-up', _AdviceFollowupEmail())
scoring_base.register_model('advice-less-applications',
                            _AdviceLessApplications())
scoring_base.register_model('advice-life-balance',
                            _AdviceLifeBalanceScoringModel())
scoring_base.register_model(
    'advice-more-offer-answers',
    scoring_base.LowPriorityAdvice(user_pb2.NO_OFFER_ANSWERS))
scoring_base.register_model('advice-other-work-env', _AdviceOtherWorkEnv())
scoring_base.register_model('advice-vae', _AdviceVae())
scoring_base.register_model('advice-senior', _AdviceSenior())
scoring_base.register_model('advice-specific-to-job', _AdviceSpecificToJob())
scoring_base.register_model(
    'advice-wow-baker',
    _JobGroupWithoutJobFilter(job_groups={'D1102'}, exclude_jobs={'12006'}))
Example #8
0
        """Compute the score for a given project and explains it."""

        skills = self._get_skills(project)

        if not skills.skills:
            return scoring_base.NULL_EXPLAINED_SCORE

        return scoring_base.ExplainedScore(2, [])

    def get_expanded_card_data(self, project: scoring_base.ScoringProject) \
            -> skill_pb2.JobSkills:
        """Retrieve data for the expanded card."""

        return self._get_skills(project)

    @scoring_base.ScoringProject.cached('skill-for-future')
    def _get_skills(self, project: scoring_base.ScoringProject) \
            -> skill_pb2.JobSkills:
        """Return a list of skills recommendation for the project's target job."""

        rome_id = project.details.target_job.job_group.rome_id
        skills_per_rome_prefix = self._db.get_collection(project.database)
        for prefix_len in (5, 3, 1):
            skills = skills_per_rome_prefix.get(rome_id[:prefix_len])
            if skills:
                return skills
        return skill_pb2.JobSkills()


scoring_base.register_model('advice-skill-for-future', _SkillForFuture())
Example #9
0
_DIPLOMA_REQUIREMENTS = {
    # "Promotion d'artistes et de spectacles", this has relevant master degrees that are enough.
    'L1203': job_pb2.DEA_DESS_MASTER_PHD,
}


class _RequiredDiplomasScoringModel(scoring_base.ModelBase):
    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


scoring_base.register_model('missing-required-diploma',
                            _RequiredDiplomasScoringModel())
Example #10
0
"""Modules for scoring models related to job seniority."""

from bob_emploi.frontend.api import project_pb2
from bob_emploi.frontend.server import scoring_base


class _YoungInexperiencedModel(scoring_base.ModelBase):
    def score(self, project: scoring_base.ScoringProject) -> float:
        # If seniority is unknown, it usually means that previous job similarity is NEVER_DONE.
        if project.details.seniority > project_pb2.INTERN:
            return 0

        return max(0, min(3, (25 - project.get_user_age())))


class _DisillusionedOldCareerManModel(scoring_base.ModelBase):
    def score(self, project: scoring_base.ScoringProject) -> float:
        # TODO(cyrille): Find a way to make sure they're really desillusioned.
        if project.get_search_length_now() < 3:
            # User is probably not disillusioned yet.
            return 0
        if project.details.seniority < project_pb2.CARREER:
            return 0

        return max(0, min(3, project.get_user_age() - 50))


scoring_base.register_model('young-inexperienced', _YoungInexperiencedModel())
scoring_base.register_model('old-too-experienced',
                            _DisillusionedOldCareerManModel())
Example #11
0

class _RequiredDiplomasScoringModel(scoring_base.ModelBase):

    def score(self, project: scoring_base.ScoringProject) -> float:
        rome_id = project.details.target_job.job_group.rome_id
        if not rome_id:
            raise scoring_base.NotEnoughDataException(
                'Need a job group to determine if the user has enough diplomas',
                {'projects.0.targetJob.jobGroup.romeId'})
        if project.user_profile.highest_degree >= job_pb2.DEA_DESS_MASTER_PHD:
            return 0
        required_diploma = min(
            (r.diploma.level for r in project.job_group_info().requirements.diplomas),
            default=job_pb2.UNKNOWN_DEGREE) or _DIPLOMA_REQUIREMENTS.get(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',
                {f'data.job_group_info.{rome_id}.requirements.diplomas'})
        if required_diploma == job_pb2.NO_DEGREE:
            return 0
        if project.user_profile.highest_degree >= required_diploma:
            return 0
        return 3


scoring_base.register_model('missing-required-diploma', _RequiredDiplomasScoringModel())
scoring_base.register_model('for-foreign-diploma', scoring_base.BaseFilter(
    lambda project: user_profile_pb2.FOREIGN_QUALIFICATIONS in project.user_profile.frustrations))
Example #12
0
                    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


class _LanguageRelevance(scoring_base.ModelBase):

    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:
            # Language is not relevant for this city.
            return 0

        try:
            project.score('for-missing-language')
        except scoring_base.NotEnoughDataException:
            return 1
        return 3


scoring_base.register_model('for-missing-language', _MissingLanguage())
scoring_base.register_model('for-missing-job-language', _MissingJobLanguage())
scoring_base.register_model('language-relevance', _LanguageRelevance())
scoring_base.register_model('for-foreign-language', scoring_base.BaseFilter(
    lambda project: user_profile_pb2.LANGUAGE in project.user_profile.frustrations))
        return [
            project.translate_string(
                "vous nous avez dit avoir passé beaucoup d'entretiens sans succès"
            )
        ]

    def get_expanded_card_data(self, project):  # pylint: disable=no-self-use
        """Retrieve data for the expanded card."""

        interview_tips = project.list_application_tips()
        sorted_tips = sorted(interview_tips,
                             key=lambda t: (-len(t.filters), random.random()))
        tips_proto = application_pb2.InterviewTips(
            qualities=[
                t for t in sorted_tips if t.type == application_pb2.QUALITY
            ],
            preparations=[
                t for t in sorted_tips
                if t.type == application_pb2.INTERVIEW_PREPARATION
            ])
        for tip in itertools.chain(tips_proto.qualities,
                                   tips_proto.preparations):
            tip.ClearField('type')
        return tips_proto


scoring_base.register_model('advice-fresh-resume', _AdviceFreshResume())
scoring_base.register_model('advice-improve-interview',
                            _AdviceImproveInterview())
scoring_base.register_model('advice-improve-resume', _AdviceImproveResume())
        specific_jobs = project.requirements().specific_jobs

        if not specific_jobs:
            return None

        extra_data = project_pb2.BetterJobInGroupData()
        try:
            extra_data.num_better_jobs = next(
                i for i, job in enumerate(specific_jobs)
                if job.code_ogr == project.details.target_job.code_ogr)
        except StopIteration:
            # Target job is not mentionned in the specific jobs, do not mention
            # the number of better jobs.
            pass

        all_jobs = project.job_group_info().jobs
        try:
            best_job = next(job for job in all_jobs
                            if job.code_ogr == specific_jobs[0].code_ogr)
            extra_data.better_job.CopyFrom(best_job)
        except StopIteration:
            logging.warning('Better job "%s" is not listed in the group "%s"',
                            specific_jobs[0].code_ogr,
                            project.job_group_info().rome_id)

        return extra_data


scoring_base.register_model('advice-better-job-in-group',
                            _AdviceBetterJobInGroup())
        if project.get_user_age() >= 45:
            return scoring_base.NULL_EXPLAINED_SCORE
        if project.details.passionate_level >= project_pb2.PASSIONATING_JOB:
            score_modifier = -1
        else:
            reasons.append(
                project.translate_string(
                    "vous n'êtes pas trop attaché à votre métier"))
        if project.details.job_search_has_not_started or search_since_nb_months <= 1:
            return scoring_base.ExplainedScore(2 + score_modifier, reasons)
        reasons = [
            project.translate_string('vous cherchez depuis {} mois').format(
                search_since_nb_months)
        ]
        if search_since_nb_months >= 12:
            return scoring_base.ExplainedScore(3, reasons)
        if search_since_nb_months >= 9:
            return scoring_base.ExplainedScore(2, reasons)
        if search_since_nb_months >= 6:
            return scoring_base.ExplainedScore(1, reasons)
        return scoring_base.NULL_EXPLAINED_SCORE

    def get_expanded_card_data(self, project):
        """Retrieve data for the expanded card."""

        return self.get_close_jobs(project)


scoring_base.register_model('advice-reorient-to-close-job',
                            _AdviceReorientToClose())
Example #16
0
        one_euro_program = driving_license_pb2.OneEuroProgram(
            partner_banks=banks)
        if project.get_user_age() <= 18:
            one_euro_program.mission_locale.CopyFrom(
                project.mission_locale_data())
        if user_specific_list:
            one_euro_program.school_list_link = user_specific_list
        return one_euro_program


class _DrivingLicenseWrittenScoringModel(scoring_base.ModelBase):
    """A scoring model for the "Driving license written examination" advice."""
    def score_and_explain(self, project):
        """Compute the score for a given project and explains it."""

        age = project.get_user_age()
        if age < 16:
            return scoring_base.NULL_EXPLAINED_SCORE
        score, reasons = _score_and_explain_after_filters(project)
        if not score:
            return scoring_base.NULL_EXPLAINED_SCORE
        return scoring_base.ExplainedScore(max(1, score - 1), reasons)


scoring_base.register_model('advice-driving-license-low-income',
                            _DrivingLicenseLowIncomeScoringModel())
scoring_base.register_model('advice-driving-license-euro',
                            _DrivingLicenseOneEuroScoringModel())
scoring_base.register_model('advice-driving-license-written',
                            _DrivingLicenseWrittenScoringModel())
Example #17
0
        return self.get_local_missions(project)

    def score_and_explain(self, project):
        """Compute the score for a given project and explains it.

        Requirements are:
        - being between 16 and 30 y.o if having a handicap or between 16 and 25 otherwise
        - having low or no experience (internship maximum)
        """

        age = project.get_user_age()
        seniority = project.details.seniority
        reasons = []
        if age < 16 or seniority > project_pb2.INTERNSHIP:
            return scoring_base.NULL_EXPLAINED_SCORE
        if project.user_profile.has_handicap and age <= 30:
            reasons = [
                project.translate_string('vous avez entre 16 et 30 ans')
            ]
        if age <= 25:
            reasons = [
                project.translate_string('vous avez entre 16 et 25 ans')
            ]
        if not reasons:
            return scoring_base.NULL_EXPLAINED_SCORE
        return scoring_base.ExplainedScore(2, reasons)


scoring_base.register_model('advice-civic-service', _AdviceCivicService())
Example #18
0
        # TODO(guillaume): Cache this to increase speed.
        top_departements = proto.create_from_mongo(
            project.database.seasonal_jobbing.find_one(
                {'_id': project.now.month}),
            seasonal_jobbing_pb2.MonthlySeasonalJobbingStats)

        for departement in top_departements.departement_stats:
            # TODO(guillaume): If we don't use deeper jobgroups by october 1st 2017, trim the db.
            del departement.job_groups[6:]

            try:
                departement.departement_in_name = geo.get_in_a_departement_text(
                    project.database, departement.departement_id)
            except KeyError:
                logging.exception(
                    'Prefix or name not found for departement: %s',
                    departement.departement_id)
                continue

        for i, departement in enumerate(
                top_departements.departement_stats[::-1]):
            if not departement.departement_in_name:
                del top_departements.departement_stats[i]

        return top_departements or []


scoring_base.register_model('advice-seasonal-relocate',
                            _AdviceSeasonalRelocate())
            return scoring_base.ExplainedScore(3, [search_length_reason])
        return scoring_base.ExplainedScore(2, [
            project.translate_string(
                "l'accompagnement humain peut beaucoup apporter")
        ])

    def compute_extra_data(self, project):
        """Compute extra data for this module to render a card in the client."""

        associations = self.list_associations(project)
        if not associations:
            return None
        sorted_associations = sorted(associations,
                                     key=lambda j:
                                     (-len(j.filters), random.random()))
        return project_pb2.AssociationsData(
            association_name=sorted_associations[0].name)

    def get_expanded_card_data(self, project):
        """Retrieve data for the expanded card."""

        associations = self.list_associations(project)
        sorted_associations = sorted(associations,
                                     key=lambda j:
                                     (-len(j.filters), random.random()))
        return association_pb2.Associations(associations=sorted_associations)


scoring_base.register_model('advice-association-help',
                            _AdviceAssociationHelp())
Example #20
0
    def score_and_explain(self, project):
        """Compute a score for the given ScoringProject."""

        if project.details.previous_job_similarity != project_pb2.NEVER_DONE or \
                project.get_user_age() > 25:
            return scoring_base.NULL_EXPLAINED_SCORE

        explanations = []
        score = 2

        if project.details.network_estimate <= 2:
            explanations.append(
                project.translate_string(
                    'ça vous aide à développer votre réseau'))
            score += .5

        if project.details.passionate_level >= project_pb2.PASSIONATING_JOB:
            explanations.append(
                project.translate_string('ça montre votre motivation'))
            score += .5

        return scoring_base.ExplainedScore(score, explanations)

    def get_expanded_card_data(self, project):
        """Retrieve data for the expanded card."""

        return project.mission_locale_data()


scoring_base.register_model('advice-immersion-milo', _ImmersionMissionLocale())
Example #21
0
    def __init__(self, application_mode, delta_percentage):
        super(_GreatApplicationModeFilter, self).__init__(self._filter)
        self.application_mode = application_mode
        self.delta_percentage = delta_percentage

    def _filter(self, project):
        """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


scoring_base.register_model(
    'advice-spontaneous-application', _SpontaneousApplicationScoringModel())
scoring_base.register_model(
    'for-mainly-hiring-through-network(±15%)', _GreatApplicationModeFilter(
        application_mode=job_pb2.PERSONAL_OR_PROFESSIONAL_CONTACTS, delta_percentage=15))
scoring_base.register_model(
    'for-mainly-hiring-through-spontaneous(±15%)', _GreatApplicationModeFilter(
        application_mode=job_pb2.SPONTANEOUS_APPLICATION, delta_percentage=15))
Example #22
0
    """A filter that passes when a given number of diagnostic submetrics have a good score."""
    def __init__(self, count: int) -> None:
        self._count = count
        super().__init__(self._filter)

    def _filter(self, project: scoring_base.ScoringProject) -> bool:
        """The filter function for this scoring model."""

        sub_diagnostics = project.details.diagnostic.sub_diagnostics
        valid_sub_diagnostics = [
            sub for sub in sub_diagnostics if sub.score >= 60
        ]
        return len(valid_sub_diagnostics) >= self._count


scoring_base.register_model('age-score', _AgeScoringModel())
scoring_base.register_model('hiring-difficulty-score',
                            _HiringNeedScoringModel())
scoring_base.register_model('for-good-diagnostic-submetrics(+4)',
                            _GoodDiagnosticCountFilter(4))
scoring_base.register_model(
    'for-good-diagnostic-submetrics(ALL)',
    _GoodDiagnosticCountFilter(
        len(diagnostic_pb2.DiagnosticTopic.values()) - 1))
scoring_base.register_model(
    'frustration-atypic-scorer',
    _FrustrationScoringModel(user_profile_pb2.ATYPIC_PROFILE))
scoring_base.register_model(
    'frustration-interview-scorer',
    _FrustrationScoringModel(user_profile_pb2.INTERVIEW))
scoring_base.register_model(
Example #23
0
    def _explain(self, project: scoring_base.ScoringProject) -> list[str]:
        """Compute a score for the given ScoringProject, and with why it's received this score."""

        if self._main_frustration in project.user_profile.frustrations:
            return [
                project.translate_static_string(
                    "vous nous avez dit ne pas trouver assez d'offres")
            ]
        return []

    @scoring_base.ScoringProject.cached('jobboards')
    def list_jobboards(self, project: scoring_base.ScoringProject) \
            -> list[jobboard_pb2.JobBoard]:
        """List all job boards for this project."""

        return list(list_jobboards(project))

    def get_expanded_card_data(self, project: scoring_base.ScoringProject) \
            -> jobboard_pb2.JobBoards:
        """Retrieve data for the expanded card."""

        jobboards = self.list_jobboards(project)
        sorted_jobboards = sorted(jobboards,
                                  key=lambda j:
                                  (-len(j.filters), random.random()))
        return jobboard_pb2.JobBoards(job_boards=sorted_jobboards)


scoring_base.register_model('advice-job-boards', _AdviceJobBoards())
Example #24
0
        # Merge data.
        project_missions = association_pb2.VolunteeringMissions()
        for scope in [departement_id, '']:
            for mission in volunteering_missions_dict[scope].missions:
                mission.is_available_everywhere = not scope
                project_missions.missions.add().CopyFrom(mission)

        return project_missions

    def score_and_explain(self, project: scoring_base.ScoringProject) \
            -> scoring_base.ExplainedScore:
        """Compute a score for the given ScoringProject."""

        missions = self.volunteering_missions(project).missions
        if not missions:
            return scoring_base.NULL_EXPLAINED_SCORE
        if project.get_search_length_at_creation() < 9:
            return scoring_base.ExplainedScore(1, [])
        return scoring_base.ExplainedScore(2, [
            'ça fait du bien de garder une activité sociale dans une recherche longue'
        ])

    def get_expanded_card_data(self, project: scoring_base.ScoringProject) \
            -> association_pb2.VolunteeringMissions:
        """Retrieve data for the expanded card."""

        return self.volunteering_missions(project)


scoring_base.register_model('advice-volunteer', _AdviceVolunteer())
Example #25
0
            for testimonial in filtered_testimonials if _is_in_job_group(
                job_group_id, testimonial.preferred_job_group_ids)
        ]

        if not same_job_testimonials:
            return testimonial_pb2.Testimonials(
                testimonials=filtered_testimonials[:max_testimonial_num])
        return testimonial_pb2.Testimonials(
            testimonials=same_job_testimonials[:max_testimonial_num])

    def get_expanded_card_data(self, project: scoring_base.ScoringProject) \
            -> create_company_expanded_data_pb2.CreateCompanyExpandedData:
        """Retrieve data for the expanded card."""

        events = self._find_closest_events(project)
        testimonials = self._find_relevant_testimonials(project)
        return create_company_expanded_data_pb2.CreateCompanyExpandedData(
            close_by_events=events, related_testimonials=testimonials)


def _is_in_job_group(target_job_group_id: str,
                     job_group_ids: Iterable[str]) -> bool:
    for job_group_id in job_group_ids:
        if target_job_group_id.startswith(job_group_id):
            return True
    return False


scoring_base.register_model('advice-create-your-company',
                            _AdviceCreateYourCompany())
Example #26
0
        if target_job_group_id.startswith(job_group_id):
            return True
    return False


def _might_move_to(project: project_pb2.Project, location: geo_pb2.Location) -> bool:
    """Returns whether a user with the given mobility would find an offer at the given location
    interesting.

    This means either the location is within the mobility range, or large enough to include the
    mobility center.
    For instance, an offer in a departement is assumed to be anywhere in that departement, so a user
    that lives in it could be interested, even if they don't want to move from their city.

    Location is assumed to have an area_type and an id for all enclosing areas, if it's smaller than
    COUNTRY. For instance {area_type: 'DEPARTEMENT', city: {region_id: '84', departement_id: '69'}}.
    Mobility is as given from a scoring project, with an area_type and a city with all necessary
    ids.
    """

    area_type = project.area_type
    if not area_type:
        # We know nothing of user's mobility, let's filter nothing out.
        return True
    area_type = max(area_type, location.area_type)
    get_area_type_id = _AREA_TYPE_TO_ID_GETTER.get(area_type, lambda unused_city: '')
    return get_area_type_id(location.city) == get_area_type_id(project.city)


scoring_base.register_model('advice-online-salons', _AdviceOnlineSalons())
Example #27
0
    """
    def __init__(self) -> None:
        super().__init__(self._filter)

    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


scoring_base.register_model('cant-sell-self', _CantSellSelf())
scoring_base.register_model('for-unclear-project', _UnclearProject())
scoring_base.register_model(
    'for-no-clear-project',
    scoring_base.BaseFilter(
        lambda p: len(p.user.projects) <= 1 and _UnclearProject().filter(p)))
scoring_base.register_model(
    'for-too-many-objectives',
    scoring_base.BaseFilter(lambda p: _count_project_super_groups(p) >= 3))
Example #28
0
        interesting_cities = [
            city for city in sorted_commuting_cities
            if city.distance_km >= _MIN_CITY_DISTANCE
        ]

        # If there is only one city nearby and no obvious city, the nearby city becomes obvious, so
        # we do not recommend it.
        if len(interesting_cities) == 1 and not obvious_cities:
            return []

        return interesting_cities


def _compute_square_distance(city_a: geo_pb2.FrenchCity,
                             city_b: geo_pb2.FrenchCity) -> float:
    """Compute the approximative distance between two cities.

    Since we only look at short distances, we can approximate that:
     - 1° latitude = 111km
     - 1° longitude = 111km * cos(lat)
    """

    delta_y = (city_a.latitude - city_b.latitude) * 111
    mean_latitude = (city_a.latitude + city_b.latitude) / 2
    delta_x = (city_a.longitude - city_b.longitude) * 111 * math.cos(
        math.radians(mean_latitude))
    return delta_x * delta_x + delta_y * delta_y


scoring_base.register_model('advice-commute', _AdviceCommuteScoringModel())
Example #29
0
        ])

    def get_expanded_card_data(self, project: scoring_base.ScoringProject) \
            -> job_pb2.SafeJobGroups:
        """Retrieve data for the expanded card."""

        has_any_covid_risk_info = jobs.has_covid_risk_info(project.database)
        has_any_automation_risk_info = jobs.has_automation_risk_info(
            project.database)

        good_jobs = jobs.get_all_good_job_group_ids(
            project.database, automation_risk_threshold=30)
        return job_pb2.SafeJobGroups(
            job_groups=[
                job_pb2.JobGroup(
                    name=typing.cast(
                        job_pb2.JobGroup,
                        jobs.get_group_proto(project.database,
                                             job_group_id)).name,
                    rome_id=job_group_id,
                ) for job_group_id in random.sample(good_jobs,
                                                    min(20, len(good_jobs)))
            ],
            is_safe_from_automation=has_any_automation_risk_info,
            is_safe_from_covid=has_any_covid_risk_info,
        )


scoring_base.register_model('advice-explore-safe-jobs',
                            _AdviceExploreSafeJobs())