def _convert_to_reorient_jobs(
     self, database: mongo.NoPiiMongoDatabase,
     reorient_jobs: Iterable[job_pb2.RelatedJobGroup],
     market_score_source: float, project: scoring_base.ScoringProject
 ) -> Iterator[reorient_jobbing_pb2.ReorientJob]:
     for job in reorient_jobs:
         # Here the market score improvement
         # (job that the user is searching for vs recommended job)
         # is overly simplified as offers gain.
         # TODO(sil): Find a way to explain the market score improvement to the user.
         # TODO(cyrille): Replace offers_percent_gain by stress_percent_loss to simplify
         #   client-side computations.
         offers_gain = 100 * (
             job.local_stats.imt.yearly_avg_offers_per_10_candidates /
             market_score_source - 1)
         job_group_info = jobs.get_group_proto(database,
                                               job.job_group.rome_id,
                                               project.user_profile.locale)
         is_diploma_required = False
         if job_group_info:
             is_diploma_required = job_group_info.is_diploma_strictly_required
         yield reorient_jobbing_pb2.ReorientJob(
             name=job_group_info and job_group_info.name
             or project.translate_string(job.job_group.name),
             offers_percent_gain=offers_gain,
             is_diploma_strictly_required=is_diploma_required)
示例#2
0
def network_vars(user, database):
    """Compute vars for a given user for the network email.

    Returns:
        a dict with all vars required for the template, or None if no email
        should be sent.
    """

    if not user.projects:
        logging.info('User has no project')
        return None
    project = user.projects[0]

    registered_months_ago = campaign.get_french_months_ago(
        user.registered_at.ToDatetime())
    if not registered_months_ago:
        logging.warning('User registered only recently (%s)',
                        user.registered_at)
        return None

    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    in_target_domain = job_group_info.in_domain
    if not in_target_domain:
        logging.warning('Could not find a target domain (%s)',
                        project.target_job.job_group)
        return None

    worst_frustration = next(
        (f for f in (user_pb2.NO_OFFER_ANSWERS, user_pb2.MOTIVATION)
         if f in user.profile.frustrations), None)

    is_hairdresser_or_in_marseille = \
        project.target_job.job_group.rome_id.startswith('D') or \
        project.mobility.city.departement_id == '13'
    other_job_in_city = 'coiffeur à Marseille'
    if is_hairdresser_or_in_marseille:
        other_job_in_city = 'secrétaire à Lyon'
    return dict(
        campaign.get_default_vars(user), **{
            'registeredMonthsAgo':
            registered_months_ago,
            'inTargetDomain':
            in_target_domain,
            'frustration':
            user_pb2.Frustration.Name(worst_frustration)
            if worst_frustration else '',
            'otherJobInCity':
            other_job_in_city,
            'jobInCity':
            '{} {}'.format(
                french.lower_first_letter(
                    french.genderize_job(project.target_job,
                                         user.profile.gender)),
                french.in_city(strip_district(project.mobility.city.name))),
            'emailInUrl':
            parse.quote(user.profile.email),
            'statusUpdateUrl':
            campaign.get_status_update_link(user.user_id, user.profile),
        })
示例#3
0
def _get_network_vars(user: user_pb2.User,
                      database: Optional[pymongo.database.Database] = None,
                      **unused_kwargs: Any) -> Optional[Dict[str, str]]:
    """Compute vars for a given user for the network email.

    Returns:
        a dict with all vars required for the template, or None if no email
        should be sent.
    """

    project = user.projects[0]

    if project.network_estimate != 1:
        logging.info('User has a good enough network')
        return None

    assert database
    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    if not job_group_info:
        logging.warning('Could not find job group info for "%s"',
                        project.target_job.job_group.rome_id)
        return None

    in_target_domain = job_group_info.in_domain
    if not in_target_domain:
        logging.warning('Could not find a target domain (%s)',
                        project.target_job.job_group)
        return None

    worst_frustration = next(
        (f for f in (user_pb2.NO_OFFER_ANSWERS, user_pb2.MOTIVATION)
         if f in user.profile.frustrations), None)

    is_hairdresser_or_in_marseille = \
        project.target_job.job_group.rome_id.startswith('D') or \
        project.city.departement_id == '13'
    other_job_in_city = 'coiffeur à Marseille'
    if is_hairdresser_or_in_marseille:
        other_job_in_city = 'secrétaire à Lyon'
    job = french.lower_first_letter(
        french.genderize_job(project.target_job, user.profile.gender))
    in_city = french.in_city(strip_district(project.city.name))
    return dict(
        campaign.get_default_coaching_email_vars(user), **{
            'inTargetDomain':
            in_target_domain,
            'frustration':
            user_pb2.Frustration.Name(worst_frustration)
            if worst_frustration else '',
            'otherJobInCity':
            other_job_in_city,
            'jobInCity':
            f'{job} {in_city}',
            'emailInUrl':
            parse.quote(user.profile.email),
        })
示例#4
0
    def job_group_info(self) -> job_pb2.JobGroup:
        """Get the info for job group info."""

        if self._job_group_info is not None:
            return self._job_group_info

        self._job_group_info = jobs.get_group_proto(
            self.database, self._rome_id(), self.user_profile.locale) or job_pb2.JobGroup()
        return self._job_group_info
示例#5
0
 def _has_low_qualif(
         self, scoring_project: scoring.ScoringProject, job: job_pb2.RelatedJobGroup) -> bool:
     job_group = jobs.get_group_proto(scoring_project.database, job.job_group.rome_id)
     if not job_group or not job_group.requirements.diplomas:
         return False
     percent_required_high_diploma = sum(
         diploma.percent_required for diploma in job_group.requirements.diplomas
         if diploma.diploma.level > self.max_level
     )
     return percent_required_high_diploma < 50
示例#6
0
    def test_get_missing_localized_job_group_proto(
            self, mock_warning: mock.MagicMock) -> None:
        """Using a different locale without translation fallsback to main proto."""

        job_group_proto = jobs.get_group_proto(self.database,
                                               'A1234',
                                               locale='de')
        assert job_group_proto
        self.assertEqual('Main Job Group', job_group_proto.name)
        mock_warning.assert_called_once()
示例#7
0
def _get_job_requirements(rome_id):
    no_requirements = job_pb2.JobRequirements()

    if not rome_id:
        return no_requirements

    job_group_info = jobs.get_group_proto(_DB, rome_id)
    if not job_group_info:
        return no_requirements

    return job_group_info.requirements
示例#8
0
def get_job_group_jobs(rome_id):
    """Retrieve information about jobs whithin a job group."""

    job_group = jobs.get_group_proto(_DB, rome_id)
    if not job_group:
        flask.abort(404, 'Groupe de métiers "{}" inconnu.'.format(rome_id))

    result = job_pb2.JobGroup()
    result.jobs.extend(job_group.jobs)
    result.requirements.specific_jobs.extend(
        job_group.requirements.specific_jobs)
    return result
示例#9
0
def get_application_modes(rome_id: str, database: pymongo.database.Database) \
        -> Optional[List[job_pb2.ModePercentage]]:
    """Fetch all possible application modes for all FAP corresponding to the given ROME."""

    job_group_info = jobs.get_group_proto(database, rome_id)
    if not job_group_info:
        return None
    application_modes = job_group_info.application_modes.values()
    fap_modes = [fap_modes.modes for fap_modes in application_modes if fap_modes.modes]
    if not fap_modes:
        return None
    return [mode for modes in fap_modes for mode in modes]
示例#10
0
def _get_in_target_domain(rome_id: str, database: mongo.NoPiiMongoDatabase) -> str:
    if not rome_id:
        raise scoring.NotEnoughDataException(
            "Need a job group to express user's target domain",
            {'projects.0.targetJob.jobGroup.romeId'})
    job_group_info = jobs.get_group_proto(database, rome_id)
    if not job_group_info:
        raise scoring.NotEnoughDataException(
            "Need job group into to express user's target domain",
            {f'data.job_group_info.{rome_id}'})
    in_target_domain = job_group_info.in_domain
    if not in_target_domain:
        raise scoring.NotEnoughDataException(
            "No information about this job group's domain",
            {f'data.job_group_info.{rome_id}.in_target_domain'})
    return in_target_domain
示例#11
0
    def test_get_localized_job_group_proto(self) -> None:
        """Using a different locale gives a translated proto."""

        self.database.job_group_info.insert_one({
            '_id':
            'de:A1234',
            'romeId':
            'A1234',
            'name':
            'Hauptberufsgruppe',
            'jobs': [
                {
                    'codeOgr': '56789',
                    'name': 'Ein Jobname',
                },
            ],
        })
        job_group_proto = jobs.get_group_proto(self.database,
                                               'A1234',
                                               locale='de')
        assert job_group_proto
        self.assertEqual('Hauptberufsgruppe', job_group_proto.name)
示例#12
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,
        )
示例#13
0
def spontaneous_vars(user, previous_email_campaign_id):
    """Compute vars for a given user for the spontaneous email.

    Returns:
        a dict with all vars required for the template, or None if no email
        should be sent.
    """

    if not user.projects:
        logging.info('User has no project')
        return None
    project = user.projects[0]

    job_group_info = jobs.get_group_proto(_DB,
                                          project.target_job.job_group.rome_id)

    def _should_use_spontaneous(modes):
        return any(mode.mode == job_pb2.SPONTANEOUS_APPLICATION
                   and mode.percentage > 20 for mode in modes.modes)

    application_modes = job_group_info.application_modes
    if not any(
            _should_use_spontaneous(modes)
            for modes in application_modes.values()):
        return None

    registered_months_ago = campaign.get_french_months_ago(
        user.registered_at.ToDatetime())
    if not registered_months_ago:
        logging.warning('User registered only recently (%s)',
                        user.registered_at)
        return None

    has_read_previous_email = previous_email_campaign_id and any(
        email.campaign_id == previous_email_campaign_id and email.status in
        (user_pb2.EMAIL_SENT_OPENED, user_pb2.EMAIL_SENT_CLICKED)
        for email in user.emails_sent)

    contact_mode = job_group_info.preferred_application_medium
    if not contact_mode:
        logging.error('There is no contact mode for the job group "%s"',
                      project.target_job.job_group.rome_id)
        return None
    contact_mode = job_pb2.ApplicationMedium.Name(contact_mode).replace(
        'APPLY_', '')

    in_a_workplace = job_group_info.in_a_workplace
    if not in_a_workplace and contact_mode != 'BY_EMAIL':
        logging.error(
            'There is no "in_a_workplace" field for the job group "%s".',
            project.target_job.job_group.rome_id)
        return None

    like_your_workplace = job_group_info.like_your_workplace
    if in_a_workplace and not like_your_workplace:
        logging.error(
            'There is no "like_your_workplace" field for the job group "%s".',
            project.target_job.job_group.rome_id)
        return None

    to_the_workplace = job_group_info.to_the_workplace
    if not to_the_workplace:
        to_the_workplace = "à l'entreprise"

    some_companies = job_group_info.place_plural
    if not some_companies:
        some_companies = 'des entreprises'

    what_i_love_about = job_group_info.what_i_love_about
    if user.profile.gender == user_pb2.FEMININE:
        what_i_love_about_feminine = job_group_info.what_i_love_about_feminine
        if what_i_love_about_feminine:
            what_i_love_about = what_i_love_about_feminine
    if not what_i_love_about and contact_mode == 'BY_EMAIL':
        logging.error(
            'There is no "What I love about" field for the job group "%s".',
            project.target_job.job_group.rome_id)
        return None

    why_specific_company = job_group_info.why_specific_company
    if not why_specific_company:
        logging.error(
            'There is no "Why this specific company" field for the job group "%s".',
            project.target_job.job_group.rome_id)
        return None

    at_various_companies = job_group_info.at_various_companies

    if project.weekly_applications_estimate == project_pb2.SOME:
        weekly_application_count = '5'
    elif project.weekly_applications_estimate > project_pb2.SOME:
        weekly_application_count = '15'
    else:
        weekly_application_count = ''

    survey_token = parse.quote(
        auth.create_token(user.user_id, role='employment-status'))
    unsubscribe_token = parse.quote(
        auth.create_token(user.profile.email, role='unsubscribe'))
    return {
        'applicationComplexity':
        job_pb2.ApplicationProcessComplexity.Name(
            job_group_info.application_complexity),
        'atVariousCompanies':
        at_various_companies,
        'contactMode':
        contact_mode,
        'deepLinkLBB':
        'https://labonneboite.pole-emploi.fr/entreprises/commune/{}/rome/'
        '{}?utm_medium=web&utm_source=bob&utm_campaign=bob-email'.format(
            project.mobility.city.city_id,
            project.target_job.job_group.rome_id),
        'emailInUrl':
        parse.quote(user.profile.email),
        'experienceAsText':
        _EXPERIENCE_AS_TEXT.get(project.seniority, 'peu'),
        'firstName':
        french.cleanup_firstname(user.profile.name),
        'gender':
        user_pb2.Gender.Name(user.profile.gender),
        'hasReadPreviousEmail':
        campaign.as_template_boolean(has_read_previous_email),
        'inWorkPlace':
        in_a_workplace,
        'jobName':
        french.lower_first_letter(
            french.genderize_job(project.target_job, user.profile.gender)),
        'lastName':
        user.profile.last_name,
        'likeYourWorkplace':
        like_your_workplace,
        'registeredMonthsAgo':
        registered_months_ago,
        'someCompanies':
        some_companies,
        # TODO(cyrille): Use campaign.get_status_update_link
        'statusUpdateUrl':
        '{}/statut/mise-a-jour?user={}&token={}&gender={}'.format(
            campaign.BASE_URL, user.user_id, survey_token,
            user_pb2.Gender.Name(user.profile.gender)),
        'toTheWorkplace':
        to_the_workplace,
        'unsubscribeLink':
        '{}/unsubscribe.html?email={}&auth={}'.format(
            campaign.BASE_URL, parse.quote(user.profile.email),
            unsubscribe_token),
        'weeklyApplicationOptions':
        weekly_application_count,
        'whatILoveAbout':
        what_i_love_about,
        'whySpecificCompany':
        why_specific_company,
    }
示例#14
0
def network_plus_vars(
        user: user_pb2.User, *, database: mongo.NoPiiMongoDatabase,
        **unused_kwargs: Any) -> dict[str, str]:
    """Compute vars for a given user for the network email.

    Returns:
        a dict with all vars required for the template, or None if no email
        should be sent.
    """

    if not user.projects:
        raise scoring.NotEnoughDataException('No project yet', {'projects.0'})

    project = user.projects[0]

    if project.network_estimate < 2:
        raise campaign.DoNotSend('User does not have a strong network')

    rome_id = project.target_job.job_group.rome_id
    in_target_domain = _get_in_target_domain(rome_id, database)
    job_group_info = jobs.get_group_proto(database, rome_id)
    assert job_group_info
    application_modes = job_group_info.application_modes.values()

    fap_modes = [fap_modes.modes for fap_modes in application_modes if len(fap_modes.modes)]
    if not fap_modes:
        raise scoring.NotEnoughDataException(
            'No information about application modes for the target job',
            {f'data.job_group_info.{rome_id}.application_modes'})
    flat_fap_modes = [mode for modes in fap_modes for mode in modes]
    network_percentages = [mode.percentage for mode in flat_fap_modes if (
        mode.mode == job_pb2.PERSONAL_OR_PROFESSIONAL_CONTACTS)]
    # We want to focus on the users for which network,
    # as an application mode, has a substantial importance.
    if not network_percentages:
        raise campaign.DoNotSend(
            'User is not targeting a job where networking is a main application mode')
    scoring_project = scoring.ScoringProject(project, user, database=database)
    average_network_percentage = sum(network_percentages) / len(network_percentages)
    if average_network_percentage > 55:
        network_application_importance = scoring_project.translate_static_string('que la majorité')
    elif average_network_percentage >= 45:
        network_application_importance = scoring_project.translate_static_string('que la moitié')
    elif average_network_percentage >= 25:
        network_application_importance = scoring_project.translate_static_string("qu'un tiers")
    else:
        raise campaign.DoNotSend(
            'User is not targeting a job where networking is a main application mode')

    worst_frustration = next(
        (f for f in (user_profile_pb2.SELF_CONFIDENCE, user_profile_pb2.MOTIVATION)
         if f in user.profile.frustrations),
        None)
    has_children = user.profile.family_situation in {
        user_profile_pb2.FAMILY_WITH_KIDS, user_profile_pb2.SINGLE_PARENT_SITUATION}

    age = datetime.date.today().year - user.profile.year_of_birth
    max_young = 35

    try:
        in_departement = geo.get_in_a_departement_text(
            database, project.city.departement_id,
            city_hint=project.city, locale=user.profile.locale)
    except KeyError:
        raise scoring.NotEnoughDataException(
            'Need departement info for phrasing',
            {f'data.departements.{project.city.departement_id}'}) from None

    job_group_name = french.lower_first_letter(project.target_job.job_group.name)

    if (user.profile.locale or 'fr').startswith('fr'):
        in_city = french.in_city(project.city.name)
    else:
        # TODO(pascal): Update the English template so that it follows the logic of "in city" and
        # not "city". For now it's phrased as "near {{inCity}}".
        in_city = project.city.name

    return campaign.get_default_coaching_email_vars(user) | {
        'frustration':
        user_profile_pb2.Frustration.Name(worst_frustration) if worst_frustration else '',
        'hasChildren': campaign.as_template_boolean(has_children),
        'hasHighSchoolDegree': campaign.as_template_boolean(
            user.profile.highest_degree >= job_pb2.BAC_BACPRO),
        'hasLargeNetwork': campaign.as_template_boolean(project.network_estimate >= 2),
        'hasWorkedBefore': campaign.as_template_boolean(
            project.kind != project_pb2.FIND_A_FIRST_JOB),
        'inCity': in_city,
        'inTargetDomain': in_target_domain,
        'isAbleBodied': campaign.as_template_boolean(not user.profile.has_handicap),
        'isYoung': campaign.as_template_boolean(age <= max_young),
        'jobGroupInDepartement': f'{job_group_name} {in_departement}',
        'networkApplicationPercentage': network_application_importance,
    }
示例#15
0
def network_plus_vars(user: user_pb2.User,
                      database: Optional[pymongo.database.Database] = None,
                      **unused_kwargs: Any) -> Optional[Dict[str, str]]:
    """Compute vars for a given user for the network email.

    Returns:
        a dict with all vars required for the template, or None if no email
        should be sent.
    """

    project = user.projects[0]

    if project.network_estimate < 2:
        logging.info('User does not have a strong network')
        return None

    assert database
    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    if not job_group_info:
        logging.warning('Could not find job group info for "%s"',
                        project.target_job.job_group.rome_id)
        return None
    in_target_domain = job_group_info.in_domain
    application_modes = job_group_info.application_modes.values()
    if not in_target_domain:
        logging.warning('Could not find a target domain (%s)',
                        project.target_job.job_group)
        return None

    fap_modes = [
        fap_modes.modes for fap_modes in application_modes
        if len(fap_modes.modes)
    ]
    if not fap_modes:
        return None
    flat_fap_modes = [mode for modes in fap_modes for mode in modes]
    network_percentages = [
        mode.percentage for mode in flat_fap_modes
        if (mode.mode == job_pb2.PERSONAL_OR_PROFESSIONAL_CONTACTS)
    ]
    # We want to focus on the user for which network,
    # as an application mode, has a substantial importance.
    if not network_percentages:
        return None
    average_network_percentage = sum(network_percentages) / len(
        network_percentages)
    if average_network_percentage > 55:
        network_application_importance = 'que la majorité'
    elif average_network_percentage >= 45:
        network_application_importance = 'que la moitié'
    elif average_network_percentage >= 25:
        network_application_importance = "qu'un tiers"
    else:
        return None

    worst_frustration = next(
        (f for f in (user_pb2.SELF_CONFIDENCE, user_pb2.MOTIVATION)
         if f in user.profile.frustrations), None)
    has_children = user.profile.family_situation in {
        user_pb2.FAMILY_WITH_KIDS, user_pb2.SINGLE_PARENT_SITUATION
    }

    age = datetime.date.today().year - user.profile.year_of_birth
    max_young = 35

    try:
        in_departement = geo.get_in_a_departement_text(
            database, project.city.departement_id)
    except KeyError:
        logging.warning('Could not find departement (%s)',
                        project.city.departement_id)
        return None

    job_group_name = french.lower_first_letter(
        project.target_job.job_group.name)

    return dict(
        campaign.get_default_coaching_email_vars(user), **{
            'frustration':
            user_pb2.Frustration.Name(worst_frustration)
            if worst_frustration else '',
            'hasChildren':
            campaign.as_template_boolean(has_children),
            'hasHighSchoolDegree':
            campaign.as_template_boolean(
                user.profile.highest_degree >= job_pb2.BAC_BACPRO),
            'hasLargeNetwork':
            campaign.as_template_boolean(project.network_estimate >= 2),
            'hasWorkedBefore':
            campaign.as_template_boolean(
                project.kind != project_pb2.FIND_A_FIRST_JOB),
            'inCity':
            french.in_city(project.city.name),
            'inTargetDomain':
            in_target_domain,
            'isAbleBodied':
            campaign.as_template_boolean(not user.profile.has_handicap),
            'isYoung':
            campaign.as_template_boolean(age <= max_young),
            'jobGroupInDepartement':
            f'{job_group_name} {in_departement}',
            'networkApplicationPercentage':
            network_application_importance,
        })
示例#16
0
def _open_classrooms_vars(user: user_pb2.User, *,
                          database: mongo.NoPiiMongoDatabase,
                          **unused_kwargs: Any) -> dict[str, str]:
    """Template variables for open classrooms email."""

    if user.registered_at.ToDatetime() < _SIX_MONTHS_AGO:
        raise campaign.DoNotSend('User registered less than 6 months ago.')

    age = datetime.date.today().year - user.profile.year_of_birth
    if age < 18:
        raise campaign.DoNotSend(
            'User too young to subscribe to OpenClassrooms.')
    if age > 54:
        raise campaign.DoNotSend(
            'User too old to subscribe to OpenClassrooms.')
    if user.profile.highest_degree > job_pb2.BAC_BACPRO:
        raise campaign.DoNotSend('User might have higher education.')

    if user.employment_status and user.employment_status[
            -1].seeking != user_pb2.STILL_SEEKING:
        raise campaign.DoNotSend('User is no more seeking for a job.')
    if not (user.projects and user.projects[0]):
        raise scoring.NotEnoughDataException('Project is required.',
                                             fields={'user.projects.0.kind'})

    project = user.projects[0]
    if project.kind != project_pb2.REORIENTATION and not (
            project.kind == project_pb2.FIND_A_NEW_JOB
            and project.passionate_level == project_pb2.ALIMENTARY_JOB):
        raise campaign.DoNotSend(
            'User is happy with their job (no reorientation and enthusiastic about their job).'
        )

    has_children = user.profile.family_situation in {
        user_profile_pb2.FAMILY_WITH_KIDS,
        user_profile_pb2.SINGLE_PARENT_SITUATION,
    }

    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    if not job_group_info:
        raise scoring.NotEnoughDataException(
            'Requires job group info for the difficulty of applying to this kind of job.'
        )

    return campaign.get_default_coaching_email_vars(user) | {
        'hasAtypicProfile':
        campaign.as_template_boolean(
            user_profile_pb2.ATYPIC_PROFILE in user.profile.frustrations),
        'hasFamilyAndManagementIssue':
        campaign.as_template_boolean(
            has_children
            and user_profile_pb2.TIME_MANAGEMENT in user.profile.frustrations),
        'hasSeniority':
        campaign.as_template_boolean(
            project.seniority > project_pb2.INTERMEDIARY),
        'hasSimpleApplication':
        campaign.as_template_boolean(job_group_info.application_complexity ==
                                     job_pb2.SIMPLE_APPLICATION_PROCESS),
        'isReorienting':
        campaign.as_template_boolean(
            project.kind == project_pb2.REORIENTATION),
        'isFrustratedOld':
        campaign.as_template_boolean(age >= 40
                                     and user_profile_pb2.AGE_DISCRIMINATION
                                     in user.profile.frustrations),
        'ofFirstName':
        french.maybe_contract_prefix('de ', "d'", user.profile.name)
    }
示例#17
0
def network_plus_vars(user, database):
    """Compute vars for a given user for the network email.

    Returns:
        a dict with all vars required for the template, or None if no email
        should be sent.
    """

    if not user.projects:
        logging.info('User has no project')
        return None
    project = user.projects[0]

    registered_months_ago = campaign.get_french_months_ago(
        user.registered_at.ToDatetime())
    if not registered_months_ago:
        logging.warning('User registered only recently (%s)',
                        user.registered_at)
        return None

    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    in_target_domain = job_group_info.in_domain
    application_modes = job_group_info.application_modes.values()
    if not in_target_domain:
        logging.warning('Could not find a target domain (%s)',
                        project.target_job.job_group)
        return None

    fap_modes = [
        fap_modes.modes for fap_modes in application_modes
        if len(fap_modes.modes)
    ]
    if not fap_modes:
        return None
    flat_fap_modes = [mode for modes in fap_modes for mode in modes]
    network_percentages = [
        mode.percentage for mode in flat_fap_modes
        if (mode.mode == job_pb2.PERSONAL_OR_PROFESSIONAL_CONTACTS)
    ]
    # We want to focus on the user for which network,
    # as an application mode, has a substantial importance.
    if not network_percentages:
        return None
    average_network_percentage = sum(network_percentages) / len(
        network_percentages)
    if average_network_percentage < 55:
        network_application_importance = 'que la majorité'
    if average_network_percentage >= 45 and average_network_percentage <= 55:
        network_application_importance = 'que la moitié'
    if average_network_percentage >= 25 and average_network_percentage < 45:
        network_application_importance = "qu'un tiers"
    else:
        return None

    worst_frustration = next(
        (f for f in (user_pb2.SELF_CONFIDENCE, user_pb2.MOTIVATION)
         if f in user.profile.frustrations), None)
    has_children = user.profile.family_situation in {
        user_pb2.FAMILY_WITH_KIDS, user_pb2.SINGLE_PARENT_SITUATION
    }

    age = datetime.date.today().year - user.profile.year_of_birth
    max_young = 35

    return dict(
        campaign.get_default_vars(user), **{
            'frustration':
            user_pb2.Frustration.Name(worst_frustration)
            if worst_frustration else '',
            'hasChildren':
            campaign.as_template_boolean(has_children),
            'hasHandicap':
            campaign.as_template_boolean(user.profile.has_handicap),
            'hasHighSchoolDegree':
            campaign.as_template_boolean(
                user.profile.highest_degree >= job_pb2.BAC_BACPRO),
            'hasLargeNetwork':
            campaign.as_template_boolean(project.network_estimate >= 2),
            'hasWorkedBefore':
            campaign.as_template_boolean(
                project.kind != project_pb2.FIND_A_FIRST_JOB),
            'inCity':
            french.in_city(project.mobility.city.name),
            'inTargetDomain':
            in_target_domain,
            'isYoung':
            campaign.as_template_boolean(age <= max_young),
            'jobGroupInDepartement':
            '{} {}'.format(
                french.lower_first_letter(project.target_job.job_group.name),
                geo.get_in_a_departement_text(
                    database, project.mobility.city.departement_id)),
            'networkApplicationPercentage':
            network_application_importance,
        })
示例#18
0
def _open_classrooms_vars(user: user_pb2.User,
                          database: Optional[pymongo.database.Database] = None,
                          **unused_kwargs: Any) -> Optional[Dict[str, str]]:
    """Template variables for viral sharing emails."""

    if user.registered_at.ToDatetime() < _SIX_MONTHS_AGO:
        return None

    age = datetime.date.today().year - user.profile.year_of_birth
    if age < 18 or age > 54:
        return None
    if user.profile.highest_degree > job_pb2.BAC_BACPRO:
        return None

    if user.employment_status and user.employment_status[
            -1].seeking != user_pb2.STILL_SEEKING:
        return None
    # User has not project.
    if not (user.projects and user.projects[0]):
        return None

    # If the user is happy with their job (no reorientation and enthusiastic about their job)
    project = user.projects[0]
    if project.kind != project_pb2.REORIENTATION and not (
            project.kind == project_pb2.FIND_A_NEW_JOB
            and project.passionate_level == project_pb2.ALIMENTARY_JOB):
        return None

    has_children = user.profile.family_situation in {
        user_pb2.FAMILY_WITH_KIDS,
        user_pb2.SINGLE_PARENT_SITUATION,
    }

    assert database
    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    if not job_group_info:
        return None

    return dict(
        campaign.get_default_coaching_email_vars(user), **{
            'hasAtypicProfile':
            campaign.as_template_boolean(
                user_pb2.ATYPIC_PROFILE in user.profile.frustrations),
            'hasFamilyAndManagementIssue':
            campaign.as_template_boolean(
                has_children
                and user_pb2.TIME_MANAGEMENT in user.profile.frustrations),
            'hasSeniority':
            campaign.as_template_boolean(
                project.seniority > project_pb2.INTERMEDIARY),
            'hasSimpleApplication':
            campaign.as_template_boolean(
                job_group_info.application_complexity ==
                job_pb2.SIMPLE_APPLICATION_PROCESS),
            'isReorienting':
            campaign.as_template_boolean(
                project.kind == project_pb2.REORIENTATION),
            'isFrustratedOld':
            campaign.as_template_boolean(
                age >= 40
                and user_pb2.AGE_DISCRIMINATION in user.profile.frustrations),
            'ofFirstName':
            french.maybe_contract_prefix('de ', "d'", user.profile.name)
        })
示例#19
0
def christmas_vars(user: user_pb2.User,
                   now: datetime.datetime,
                   database: Optional[pymongo.database.Database] = None,
                   **unused_kwargs: Any) -> Optional[Dict[str, str]]:
    """Compute all variables required for the Christmas campaign."""

    project = next((p for p in user.projects), project_pb2.Project())

    job_search_started_months_ago = campaign.job_search_started_months_ago(
        project, now)
    if job_search_started_months_ago < 0:
        started_searching_since = ''
    elif job_search_started_months_ago < 2:
        started_searching_since = 'depuis peu'
    else:
        try:
            num_months = french.try_stringify_number(
                round(job_search_started_months_ago))
            started_searching_since = f'depuis {num_months} mois'
        except NotImplementedError:
            started_searching_since = 'depuis un moment'

    # A city to commute to.
    commute_city = next(
        (city for a in project.advices for city in a.commute_data.cities), '')
    if commute_city:
        commute_city = french.in_city(commute_city)
    commute_advice_url = campaign.get_deep_link_advice(user.user_id, project,
                                                       'commute')
    if not commute_advice_url:
        commute_city = ''

    # A departement to relocate to.
    relocate_departement = next(
        (departement.name for a in project.advices
         for departement in a.relocate_data.departement_scores), '')
    assert database
    if relocate_departement:
        try:
            departement_id = geo.get_departement_id(database,
                                                    relocate_departement)
            relocate_departement = geo.get_in_a_departement_text(
                database, departement_id)
        except KeyError:
            relocate_departement = ''
    relocate_advice_url = campaign.get_deep_link_advice(
        user.user_id, project, 'relocate')
    if not relocate_advice_url:
        relocate_departement = ''

    # Whether the job may have freelancers.
    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    could_freelance = job_group_info and job_group_info.has_freelancers

    return dict(
        campaign.get_default_vars(user), **{
            'adviceUrlBodyLanguage':
            campaign.get_deep_link_advice(user.user_id, project,
                                          'body-language'),
            'adviceUrlCommute':
            commute_advice_url,
            'adviceUrlCreateYourCompany':
            campaign.get_deep_link_advice(user.user_id, project,
                                          'create-your-company'),
            'adviceUrlImproveInterview':
            campaign.get_deep_link_advice(user.user_id, project,
                                          'improve-interview'),
            'adviceUrlRelocate':
            relocate_advice_url,
            'couldFreelance':
            campaign.as_template_boolean(could_freelance),
            'emailInUrl':
            parse.quote(user.profile.email),
            'inCommuteCity':
            commute_city,
            'inRelocateDepartement':
            relocate_departement,
            'startedSearchingSince':
            started_searching_since,
        })
示例#20
0
def _get_spontaneous_vars(user: user_pb2.User,
                          now: datetime.datetime,
                          database: Optional[pymongo.database.Database] = None,
                          **unused_kwargs: Any) -> Optional[Dict[str, str]]:
    """Compute vars for a given user for the spontaneous email.

    Returns:
        a dict with all vars required for the template, or None if no email
        should be sent.
    """

    project = user.projects[0]

    job_search_length = campaign.job_search_started_months_ago(project, now)
    if job_search_length < 0:
        logging.info('No info on user search duration')
        return None

    assert database
    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    if not job_group_info:
        logging.warning('Could not find job group info for "%s"',
                        project.target_job.job_group.rome_id)
        return None

    def _should_use_spontaneous(
            modes: job_pb2.RecruitingModesDistribution) -> bool:
        return any(mode.mode == job_pb2.SPONTANEOUS_APPLICATION
                   and mode.percentage > 20 for mode in modes.modes)

    application_modes = job_group_info.application_modes
    if not any(
            _should_use_spontaneous(modes)
            for modes in application_modes.values()):
        return None

    contact_mode = job_group_info.preferred_application_medium
    if not contact_mode:
        logging.error('There is no contact mode for the job group "%s"',
                      project.target_job.job_group.rome_id)
        return None

    in_a_workplace = job_group_info.in_a_workplace
    if not in_a_workplace and contact_mode != job_pb2.APPLY_BY_EMAIL:
        logging.error(
            'There is no "in_a_workplace" field for the job group "%s".',
            project.target_job.job_group.rome_id)
        return None

    like_your_workplace = job_group_info.like_your_workplace
    if in_a_workplace and not like_your_workplace:
        logging.error(
            'There is no "like_your_workplace" field for the job group "%s".',
            project.target_job.job_group.rome_id)
        return None

    to_the_workplace = job_group_info.to_the_workplace
    if not to_the_workplace:
        to_the_workplace = "à l'entreprise"

    some_companies = job_group_info.place_plural
    if not some_companies:
        some_companies = 'des entreprises'

    what_i_love_about = job_group_info.what_i_love_about
    if user.profile.gender == user_pb2.FEMININE:
        what_i_love_about_feminine = job_group_info.what_i_love_about_feminine
        if what_i_love_about_feminine:
            what_i_love_about = what_i_love_about_feminine
    if not what_i_love_about and contact_mode == job_pb2.APPLY_BY_EMAIL:
        logging.error(
            'There is no "What I love about" field for the job group "%s".',
            project.target_job.job_group.rome_id)
        return None

    why_specific_company = job_group_info.why_specific_company
    if not why_specific_company:
        logging.error(
            'There is no "Why this specific company" field for the job group "%s".',
            project.target_job.job_group.rome_id)
        return None

    at_various_companies = job_group_info.at_various_companies

    if project.weekly_applications_estimate == project_pb2.SOME:
        weekly_applications_count = '5'
    elif project.weekly_applications_estimate > project_pb2.SOME:
        weekly_applications_count = '15'
    else:
        weekly_applications_count = ''

    if project.weekly_applications_estimate:
        weekly_applications_option = project_pb2.NumberOfferEstimateOption.Name(
            project.weekly_applications_estimate)
    else:
        weekly_applications_option = ''

    return dict(
        campaign.get_default_coaching_email_vars(user), **{
            'applicationComplexity':
            job_pb2.ApplicationProcessComplexity.Name(
                job_group_info.application_complexity),
            'atVariousCompanies':
            at_various_companies,
            'contactMode':
            job_pb2.ApplicationMedium.Name(contact_mode).replace('APPLY_', ''),
            'deepLinkLBB':
            f'https://labonneboite.pole-emploi.fr/entreprises/commune/{project.city.city_id}/rome/'
            f'{project.target_job.job_group.rome_id}?utm_medium=web&utm_source=bob&'
            'utm_campaign=bob-email',
            'emailInUrl':
            parse.quote(user.profile.email),
            'experienceAsText':
            _EXPERIENCE_AS_TEXT.get(project.seniority, 'peu'),
            'inWorkPlace':
            in_a_workplace,
            'jobName':
            french.lower_first_letter(
                french.genderize_job(project.target_job, user.profile.gender)),
            'lastName':
            user.profile.last_name,
            'likeYourWorkplace':
            like_your_workplace,
            'someCompanies':
            some_companies,
            'toTheWorkplace':
            to_the_workplace,
            'weeklyApplicationsCount':
            weekly_applications_count,
            'weeklyApplicationsOption':
            weekly_applications_option,
            'whatILoveAbout':
            what_i_love_about,
            'whySpecificCompany':
            why_specific_company,
        })
示例#21
0
def _christmas_vars(user: user_pb2.User, *, now: datetime.datetime,
                    database: mongo.NoPiiMongoDatabase,
                    **unused_kwargs: Any) -> dict[str, str]:
    """Compute all variables required for the Christmas campaign."""

    if now.month != 12 and not user.features_enabled.alpha:
        raise campaign.DoNotSend('Only send christmas email in December')

    project = next((p for p in user.projects), project_pb2.Project())

    job_search_started_months_ago = campaign.job_search_started_months_ago(
        project, now)
    if job_search_started_months_ago < 0:
        started_searching_since = ''
    elif job_search_started_months_ago < 2:
        started_searching_since = 'depuis peu'
    else:
        try:
            num_months = french.try_stringify_number(
                round(job_search_started_months_ago))
            started_searching_since = f'depuis {num_months} mois'
        except NotImplementedError:
            started_searching_since = 'depuis un moment'

    # A city to commute to.
    commute_city = next(
        (city for a in project.advices for city in a.commute_data.cities), '')
    if commute_city:
        commute_city = french.in_city(commute_city)
    commute_advice_url = campaign.get_deep_link_advice(user.user_id, project,
                                                       'commute')
    if not commute_advice_url:
        commute_city = ''

    # A departement to relocate to.
    relocate_departement = next(
        (departement.name for a in project.advices
         for departement in a.relocate_data.departement_scores), '')
    if relocate_departement:
        try:
            departement_id = geo.get_departement_id(database,
                                                    relocate_departement)
            relocate_departement = geo.get_in_a_departement_text(
                database, departement_id)
        except KeyError:
            relocate_departement = ''
    relocate_advice_url = campaign.get_deep_link_advice(
        user.user_id, project, 'relocate')
    if not relocate_advice_url:
        relocate_departement = ''

    # Whether the job may have freelancers.
    job_group_info = jobs.get_group_proto(database,
                                          project.target_job.job_group.rome_id)
    could_freelance = job_group_info and job_group_info.has_freelancers

    return campaign.get_default_coaching_email_vars(user) | {
        'adviceUrlBodyLanguage':
        campaign.get_deep_link_advice(user.user_id, project, 'body-language'),
        'adviceUrlCommute':
        commute_advice_url,
        'adviceUrlCreateYourCompany':
        campaign.get_deep_link_advice(user.user_id, project,
                                      'create-your-company'),
        'adviceUrlExploreOtherJobs':
        campaign.get_deep_link_advice(user.user_id, project,
                                      'explore-other-jobs'),
        'adviceUrlImproveInterview':
        campaign.get_deep_link_advice(user.user_id, project,
                                      'improve-interview'),
        'adviceUrlRelocate':
        relocate_advice_url,
        'adviceUrlVolunteer':
        campaign.get_deep_link_advice(user.user_id, project, 'volunteer'),
        'couldFreelance':
        campaign.as_template_boolean(could_freelance),
        'emailInUrl':
        parse.quote(user.profile.email),
        'inCommuteCity':
        commute_city,
        'inRelocateDepartement':
        relocate_departement,
        'nextYear':
        str(now.year + 1),
        'startedSearchingSince':
        started_searching_since,
        'year':
        str(now.year),
    }