Beispiel #1
0
def _get_first_actions_vars(user: user_pb2.User,
                            database: mongo.NoPiiMongoDatabase,
                            **unused_kwargs: Any) -> dict[str, Any]:
    job_list = list(
        filter(None, (jobs.get_job_proto(database, p.target_job.code_ogr,
                                         p.target_job.job_group.rome_id)
                      for p in user.projects)))
    if not job_list:
        raise campaign.DoNotSend('Need to have at least one job.')
    job_names = [job.name for job in job_list]
    quoted_jobs = parse.quote(' '.join(job_names))
    scoring_project = scoring.ScoringProject(user.projects[0], user, database)
    return get_default_vars(user) | {
        'departements':
        ','.join({p.city.departement_id
                  for p in user.projects}),
        'hasSeveralJobs':
        campaign.as_template_boolean(len(job_list) > 1),
        'jobIds':
        ','.join({job.job_group.rome_id
                  for job in job_list}),
        'jobs':
        job_names,
        'ofJobName':
        scoring_project.populate_template('%ofJobName'),
        'optLink':
        'https://www.orientation-pour-tous.fr/spip.php?'
        f'page=recherche&rubrique=metiers&recherche={quoted_jobs}',
    }
Beispiel #2
0
def _get_improve_cv_vars(user: user_pb2.User, *, now: datetime.datetime,
                         **unused_kwargs: Any) -> dict[str, Any]:
    """Compute vars for the "Improve your CV" email."""

    if user_profile_pb2.RESUME not in user.profile.frustrations:
        raise campaign.DoNotSend('User is not frustrated by its CV')

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

    project = user.projects[0]
    if project.kind == project_pb2.FIND_A_FIRST_JOB:
        has_experience = 'False'
    elif project.kind in (project_pb2.FIND_A_NEW_JOB,
                          project_pb2.FIND_ANOTHER_JOB):
        has_experience = 'True'
    else:
        has_experience = ''

    deep_link_advice_url = \
        campaign.get_deep_link_advice(user.user_id, project, 'improve-resume') or \
        campaign.get_deep_link_advice(user.user_id, project, 'fresh-resume')

    return campaign.get_default_coaching_email_vars(user) | {
        'deepLinkAdviceUrl': deep_link_advice_url,
        'hasExperience': has_experience,
        'isSeptember': campaign.as_template_boolean(now.month == 9),
        'loginUrl': campaign.create_logged_url(user.user_id)
    }
def _get_prepare_your_application_vars(user: user_pb2.User, **unused_kwargs: Any) -> dict[str, Any]:
    """Compute vars for the "Prepare your application" email."""

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

    project = user.projects[0]

    deep_link_motivation_email_url = \
        campaign.get_deep_link_advice(user.user_id, project, 'motivation-email')

    return campaign.get_default_coaching_email_vars(user) | {
        'deepLinkMotivationEmailUrl': deep_link_motivation_email_url,
        'hasInterviewFrustration':
        campaign.as_template_boolean(user_profile_pb2.INTERVIEW in user.profile.frustrations),
        'hasSelfConfidenceFrustration':
        campaign.as_template_boolean(user_profile_pb2.SELF_CONFIDENCE in user.profile.frustrations),
        'loginUrl': campaign.create_logged_url(user.user_id, f'/projet/{project.project_id}'),
    }
Beispiel #4
0
def _make_months_section(
        months: Iterable['job_pb2.Month.V'],
        month_names_as_string: str) -> Optional[dict[str, str]]:
    month_names_map = _get_months_map(month_names_as_string)
    active_months = [
        month_names_map[month] for month in months if month in month_names_map]
    if not active_months:
        return None
    return {
        'activeMonths': ' - '.join(active_months),
        'onlyOneMonth': campaign.as_template_boolean(len(active_months) == 1),
    }
Beispiel #5
0
def _get_self_development_vars(
        user: user_pb2.User, *, now: datetime.datetime, **unused_kwargs: Any) \
        -> dict[str, str]:
    """Computes vars for a given user for the self-development email.

    Returns a dict with all vars required for the template.
    """

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

    has_video = (user.profile.locale or 'fr').startswith('fr')

    project = user.projects[0]

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

    if job_search_length > 12:
        raise campaign.DoNotSend(
            f'User has been searching for too long ({job_search_length:.2f}).')

    genderized_job_name = french.lower_first_letter(
        french.genderize_job(project.target_job, user.profile.gender))
    age = datetime.date.today().year - user.profile.year_of_birth

    max_young = 30
    min_old = 50

    return campaign.get_default_coaching_email_vars(user) | {
        'hasEnoughExperience':
        campaign.as_template_boolean(project.seniority > project_pb2.JUNIOR),
        'hasVideo':
        campaign.as_template_boolean(has_video),
        'isAdministrativeAssistant':
        campaign.as_template_boolean(
            project.target_job.job_group.name == 'Secrétariat'),
        'isOld':
        campaign.as_template_boolean(age >= min_old),
        'isOldNotWoman':
        campaign.as_template_boolean(
            age >= min_old
            and user.profile.gender != user_profile_pb2.FEMININE),
        'isYoung':
        campaign.as_template_boolean(age <= max_young),
        'isYoungNotWoman':
        campaign.as_template_boolean(
            age <= max_young
            and user.profile.gender != user_profile_pb2.FEMININE),
        'jobName':
        genderized_job_name,
    }
def _get_prepare_your_application_short_vars(user: user_pb2.User, **unused_kwargs: Any)\
        -> dict[str, Any]:
    """Compute vars for the "Prepare your application short" email."""

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

    if (user.profile.locale or 'fr').startswith('fr'):
        advice_page_url = 'https://labonneboite.pole-emploi.fr/comment-faire-une-candidature-spontanee'
    elif user.profile.locale.startswith('en'):
        advice_page_url = 'https://www.theguardian.com/careers/speculative-applications'
    else:
        logging.warning(
            'No advice webpage given for campaign spontaneous-short in "%s"', user.profile.locale)
        advice_page_url = ''

    return campaign.get_default_coaching_email_vars(user) | {
        'advicePageUrl': advice_page_url,
        'hasInterviewFrustration':
        campaign.as_template_boolean(user_profile_pb2.INTERVIEW in user.profile.frustrations),
        'hasSelfConfidenceFrustration':
        campaign.as_template_boolean(user_profile_pb2.SELF_CONFIDENCE in user.profile.frustrations),
    }
Beispiel #7
0
def _get_short_spontaneous_vars(user: user_pb2.User, *, now: datetime.datetime,
                                database: mongo.NoPiiMongoDatabase,
                                **unused_kwargs: Any) -> dict[str, str]:

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

    project = user.projects[0]
    scoring_project = scoring.ScoringProject(project, user, database, now)

    job_group_info = scoring_project.job_group_info()

    why_specific_company = job_group_info.why_specific_company
    if not why_specific_company:
        why_specific_company = scoring_project.translate_static_string(
            'vous vous reconnaissez dans leurs valeurs, leur équipe, leur service client ou ce '
            "qu'elles vendent")

    some_companies = job_group_info.place_plural
    if not some_companies:
        some_companies = scoring_project.translate_static_string(
            'des entreprises')

    if (user.profile.locale or 'fr').startswith('fr'):
        advice_page_url = 'https://labonneboite.pole-emploi.fr/comment-faire-une-candidature-spontanee'
    elif user.profile.locale.startswith('en'):
        advice_page_url = 'https://www.theguardian.com/careers/speculative-applications'
    else:
        logging.warning(
            'No advice webpage given for campaign spontaneous-short in "%s"',
            user.profile.locale)
        advice_page_url = ''

    # If the user receives the email less than 2 months after they registered on Bob and are
    # searching for less than 3 months, we can be happily surprised if they found a job.
    is_job_found_surprising = scoring_project.get_search_length_now() < 3 and \
        (scoring_project.details.created_at.ToDatetime() - now).days / 30 < 2

    return campaign.get_default_coaching_email_vars(user) | {
        'advicePageUrl':
        advice_page_url,
        'atVariousCompanies':
        job_group_info.at_various_companies,
        'isJobFoundSurprising':
        campaign.as_template_boolean(is_job_found_surprising),
        'someCompanies':
        some_companies,
        'whySpecificCompany':
        why_specific_company,
    }
Beispiel #8
0
def _get_galita2_vars(user: user_pb2.User,
                      **unused_kwargs: Any) -> dict[str, str]:
    if not user.projects:
        raise scoring.NotEnoughDataException(
            'Project is required for galita-2.',
            fields={'user.projects.0.kind'})
    project = user.projects[0]
    if project.kind not in {project_pb2.FIND_A_FIRST_JOB, project_pb2.REORIENTATION} and \
            project.previous_job_similarity != project_pb2.NEVER_DONE:
        raise campaign.DoNotSend(
            'User is not searching a job in a profession new to them.')
    return campaign.get_default_coaching_email_vars(user) | {
        'isReorienting':
        campaign.as_template_boolean(project.kind == project_pb2.REORIENTATION)
    }
Beispiel #9
0
def _get_switch_vars(user: user_pb2.User, *, now: datetime.datetime,
                     **unused_kwargs: Any) -> dict[str, str]:
    """Compute all variables required for the Switch campaign."""

    if now.year - user.profile.year_of_birth < 22:
        raise campaign.DoNotSend('User is too young')

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

    if project.seniority <= project_pb2.INTERMEDIARY:
        raise campaign.DoNotSend("User doesn't have enough experience")

    return campaign.get_default_coaching_email_vars(user) | {
        'isConverting':
        campaign.as_template_boolean(
            project.kind == project_pb2.REORIENTATION),
    }
Beispiel #10
0
def _make_departements_section(
        user_departement_id: str, best_departements: list[str],
        area_type: 'geo_pb2.AreaType.V', database: mongo.NoPiiMongoDatabase,
        scoring_project: scoring.ScoringProject) -> Optional[dict[str, str]]:
    if area_type < geo_pb2.COUNTRY or not best_departements:
        return None
    best_departements_title = '<br />'.join(
        geo.get_departement_name(database, dep) for dep in best_departements)
    try:
        best_departements.remove(user_departement_id)
        is_best_departement = True
    except ValueError:
        is_best_departement = False
    best_departements_sentence = scoring_project.translate_static_string(' et ').join(
        geo.get_in_a_departement_text(database, dep) for dep in best_departements)
    return {
        'count': str(len(best_departements)),
        'isInBest': campaign.as_template_boolean(is_best_departement),
        'title': best_departements_title,
        'sentence': best_departements_sentence,
    }
Beispiel #11
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),
    }
Beispiel #12
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,
    }
Beispiel #13
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)
    }
Beispiel #14
0
def _make_section(values: Optional[dict[str, str]]) -> dict[str, str]:
    return {'showSection': campaign.as_template_boolean(bool(values))} | (values or {})