Example #1
0
def _get_self_development_vars(user: user_pb2.User, now: datetime.datetime, **unused_kwargs: Any) \
        -> Optional[Dict[str, str]]:
    """Compute vars for a given user for the self-development 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

    if job_search_length > 12:
        logging.info('User has been searching for too long (%s)',
                     job_search_length)
        return None

    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 dict(
        campaign.get_default_coaching_email_vars(user), **{
            'hasEnoughExperience':
            campaign.as_template_boolean(
                project.seniority > project_pb2.JUNIOR),
            '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_pb2.FEMININE),
            'isYoung':
            campaign.as_template_boolean(age <= max_young),
            'isYoungNotWoman':
            campaign.as_template_boolean(
                age <= max_young and user.profile.gender != user_pb2.FEMININE),
            'jobName':
            genderized_job_name,
            'ofJobName':
            french.maybe_contract_prefix('de ', "d'", genderized_job_name),
        })
Example #2
0
def _get_galita2_vars(user: user_pb2.User,
                      **unused_kwargs: Any) -> Optional[Dict[str, str]]:
    if not user.projects:
        return None
    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:
        return None
    genderized_job_name = french.lower_first_letter(
        french.genderize_job(project.target_job, user.profile.gender))
    return dict(
        campaign.get_default_coaching_email_vars(user), **{
            'isReorienting':
            campaign.as_template_boolean(
                project.kind == project_pb2.REORIENTATION),
            'ofJobName':
            french.maybe_contract_prefix('de ', "d'", genderized_job_name)
        })
Example #3
0
def _send_activation_email(user, project, database, base_url):
    """Send an email to the user just after we have defined their diagnosis."""

    advice_modules = {a.advice_id: a for a in _advice_modules(database)}
    advices = [a for a in project.advices if a.advice_id in advice_modules]
    if not advices:
        logging.error(  # pragma: no-cover
            'Weird: the advices that just got created do not exist in DB.'
        )  # pragma: no-cover
        return  # pragma: no-cover
    data = {
        'baseUrl':
        base_url,
        'projectId':
        project.project_id,
        'firstName':
        user.profile.name,
        'ofProjectTitle':
        french.maybe_contract_prefix(
            'de ', "d'",
            french.lower_first_letter(
                french.genderize_job(project.target_job,
                                     user.profile.gender))),
        'advices': [{
            'adviceId': a.advice_id,
            'title': advice_modules[a.advice_id].title
        } for a in advices],
    }
    response = mail.send_template(
        # https://app.mailjet.com/template/168827/build
        '168827',
        user.profile,
        data,
        dry_run=not _EMAIL_ACTIVATION_ENABLED)
    if response.status_code != 200:
        logging.warning('Error while sending diagnostic email: %s\n%s',
                        response.status_code, response.text)
Example #4
0
def _get_imt_vars(user: user_pb2.User,
                  database: Optional[pymongo.database.Database] = None,
                  **unused_kwargs: Any) -> Optional[Dict[str, Any]]:
    """Compute vars for the "IMT" email."""

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

    genderized_job_name = french.lower_first_letter(
        french.genderize_job(project.target_job, user.profile.gender))

    departement_id = project.city.departement_id
    rome_id = project.target_job.job_group.rome_id
    local_diagnosis = scoring_project.local_diagnosis()
    if not local_diagnosis.HasField('imt'):
        logging.info('User market has no IMT data')
        return None
    imt = local_diagnosis.imt

    shown_sections = []

    market_stress_section = _make_market_stress_section(
        imt.yearly_avg_offers_per_10_candidates)
    if market_stress_section:
        shown_sections.append('marketStress')

    application_modes_section = _make_application_mode_section(
        scoring_project.get_best_application_mode(), project, user.user_id)
    if application_modes_section:
        shown_sections.append('applicationModes')

    departements_section = _make_departements_section(
        departement_id,
        _get_best_departements_for_job_group(rome_id, database),
        project.area_type, database)
    if departements_section:
        shown_sections.append('departements')

    employment_types_section = _make_employment_type_section(
        imt.employment_type_percentages)
    if employment_types_section:
        shown_sections.append('employmentTypes')

    months_section = _make_months_section(imt.active_months)
    if months_section:
        shown_sections.append('months')

    if len(shown_sections) < 3:
        logging.info('Only %d section(s) to be shown for user (%s).',
                     len(shown_sections), shown_sections)
        return None

    imt_link = 'http://candidat.pole-emploi.fr/marche-du-travail/statistiques?' \
        f'codeMetier={project.target_job.code_ogr}&codeZoneGeographique={departement_id}&' \
        'typeZoneGeographique=DEPARTEMENT'

    in_departement = geo.get_in_a_departement_text(database, departement_id)
    job_name_in_departement = f'{genderized_job_name} {in_departement}'

    return dict(
        campaign.get_default_coaching_email_vars(user), **{
            'applicationModes':
            _make_section(application_modes_section),
            'departements':
            _make_section(departements_section),
            'employmentType':
            _make_section(employment_types_section),
            'imtLink':
            imt_link,
            'inCity':
            french.in_city(project.city.name),
            'jobNameInDepartement':
            job_name_in_departement,
            'loginUrl':
            campaign.create_logged_url(user.user_id),
            'marketStress':
            _make_section(market_stress_section),
            'months':
            _make_section(months_section),
            'ofJobNameInDepartement':
            french.maybe_contract_prefix('de ', "d'", job_name_in_departement),
            'ofJobName':
            french.maybe_contract_prefix('de ', "d'", genderized_job_name),
        })
Example #5
0
def _of_job_name(project: ScoringProject) -> str:
    translated = project.translate_string('de {job_name}').format(
        job_name=_job_name(project))
    if translated.startswith('de '):
        return french.maybe_contract_prefix('de ', "d'", _job_name(project))
    return translated
Example #6
0
    def test_empty(self) -> None:
        """Next word is empty."""

        sentence = french.maybe_contract_prefix('foo ', 'bar ', '')
        self.assertEqual('foo ', sentence)
Example #7
0
    def test_upper_case(self) -> None:
        """Next word starts with an upper case vowel."""

        sentence = french.maybe_contract_prefix(
            'foo ', 'bar ', 'A starts with an uppercase A')
        self.assertEqual('bar A starts with an uppercase A', sentence)
Example #8
0
    def test_h(self) -> None:
        """Next word starts with an H."""

        sentence = french.maybe_contract_prefix('foo ', 'bar ',
                                                'h starts with an H')
        self.assertEqual('bar h starts with an H', sentence)
Example #9
0
    def test_accented_vowel(self) -> None:
        """Next word starts with an accented vowel."""

        sentence = french.maybe_contract_prefix('foo ', 'bar ',
                                                'à starts with an A')
        self.assertEqual('bar à starts with an A', sentence)
Example #10
0
    def test_consonant(self) -> None:
        """Next word starts with a consonant."""

        sentence = french.maybe_contract_prefix('foo ', 'bar ',
                                                'starts with an S')
        self.assertEqual('foo starts with an S', sentence)
Example #11
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)
        })
Example #12
0
            'latin-1', 'replace')),
    '%lastName':
    lambda scoring_project: scoring_project.user_profile.last_name,
    '%likeYourWorkplace':
    lambda scoring_project:
    (scoring_project.job_group_info().like_your_workplace),
    '%masculineJobName':
    lambda scoring_project: french.lower_first_letter(
        scoring_project.details.target_job.masculine_name),
    '%name':
    lambda scoring_project: scoring_project.user_profile.name,
    '%ofCity':
    lambda scoring_project: french.of_city(scoring_project.details.mobility.
                                           city.name),
    '%ofJobName':
    lambda scoring_project: french.maybe_contract_prefix(
        'de ', "d'", _job_name(scoring_project)),
    '%placePlural':
    lambda scoring_project: scoring_project.job_group_info().place_plural,
    '%postcode':
    _postcode,
    '%regionId':
    lambda scoring_project: scoring_project.details.mobility.city.region_id,
    '%romeId':
    lambda scoring_project: scoring_project.details.target_job.job_group.
    rome_id,
    '%totalInterviewCount':
    _total_interview_count,
    '%whatILoveAbout':
    lambda scoring_project: scoring_project.job_group_info().what_i_love_about,
}
Example #13
0
def self_development_vars(user, unused_db=None):
    """Compute vars for a given user for the self-development 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.info('User registered only recently (%s)', user.registered_at)
        return None

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

    if job_search_length >= 12:
        logging.info('User has been searching for too long (%s)',
                     job_search_length)
        return None

    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
    unsubscribe_token = parse.quote(
        auth.create_token(user.profile.email, role='unsubscribe'))

    max_young = 30
    min_old = 50

    return {
        'firstName':
        french.cleanup_firstname(user.profile.name),
        'gender':
        user_pb2.Gender.Name(user.profile.gender),
        'hasEnoughExperience':
        campaign.as_template_boolean(project.seniority > project_pb2.JUNIOR),
        '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_pb2.FEMININE),
        'isYoung':
        campaign.as_template_boolean(age <= max_young),
        'isYoungNotWoman':
        campaign.as_template_boolean(
            age <= max_young and user.profile.gender != user_pb2.FEMININE),
        'jobName':
        genderized_job_name,
        'ofJobName':
        french.maybe_contract_prefix('de ', "d'", genderized_job_name),
        'registeredMonthsAgo':
        registered_months_ago,
        'unsubscribeLink':
        '{}/unsubscribe.html?email={}&auth={}'.format(
            campaign.BASE_URL, parse.quote(user.profile.email),
            unsubscribe_token),
    }
Example #14
0
    def test_vowel(self):
        """Next word starts with a vowel."""

        sentence = french.maybe_contract_prefix('foo ', 'bar ',
                                                'a starts with an A')
        self.assertEqual('bar a starts with an A', sentence)
Example #15
0
def imt_vars(user, database):
    """Compute vars for the "IMT" email."""

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

    genderized_job_name = french.lower_first_letter(
        french.genderize_job(project.target_job, user.profile.gender))

    departement_id = project.mobility.city.departement_id
    rome_id = project.target_job.job_group.rome_id
    diagnosis_key = '{}:{}'.format(departement_id, rome_id)
    local_diagnosis = _LOCAL_DIAGNOSIS.get_collection(database).get(
        diagnosis_key)
    if not local_diagnosis:
        logging.info('User market does not exist')
        return None
    imt = local_diagnosis.imt
    if not imt:
        logging.info('User market has no IMT data')
        return None

    shown_sections = 0

    market_stress_section = _make_market_stress_section(
        imt.yearly_avg_offers_per_10_candidates)
    if market_stress_section:
        shown_sections += 1

    application_modes_section = _make_application_mode_section(
        campaign.get_application_modes(rome_id, database), project.advices,
        user.user_id)
    if application_modes_section:
        shown_sections += 1

    departements_section = _make_departements_section(
        project.mobility.city.departement_id,
        _get_best_departements_for_job_group(rome_id, database),
        project.mobility.area_type, database)
    if departements_section:
        shown_sections += 1

    employment_types_section = _make_employment_type_section(
        sorted(imt.employment_type_percentages, key=lambda e: e.percentage))
    if employment_types_section:
        shown_sections += 1

    months_section = _make_months_section(imt.active_months)
    if months_section:
        shown_sections += 1

    if shown_sections < 3:
        logging.info('Only %d section(s) to be shown for user.',
                     shown_sections)
        return None

    imt_link = 'http://candidat.pole-emploi.fr/marche-du-travail/statistiques?' + \
        'codeMetier={}&codeZoneGeographique={}&typeZoneGeographique=DEPARTEMENT'.format(
            project.target_job.code_ogr, departement_id)

    job_name_in_departement = '{} {}'.format(
        genderized_job_name,
        geo.get_in_a_departement_text(database,
                                      project.mobility.city.departement_id))

    return dict(
        campaign.get_default_vars(user), **{
            'applicationModes':
            _make_section(application_modes_section),
            'departements':
            _make_section(departements_section),
            'employmentType':
            _make_section(employment_types_section),
            'imtLink':
            imt_link,
            'inCity':
            french.in_city(project.mobility.city.name),
            'jobNameInDepartement':
            job_name_in_departement,
            'loginUrl':
            campaign.create_logged_url(user.user_id),
            'marketStress':
            _make_section(market_stress_section),
            'months':
            _make_section(months_section),
            'ofJobNameInDepartement':
            french.maybe_contract_prefix('de ', "d'", job_name_in_departement),
            'ofJobName':
            french.maybe_contract_prefix('de ', "d'", genderized_job_name),
            'showPs':
            campaign.as_template_boolean(
                _can_go_to_arles_hotellerie_event(rome_id, project.mobility)),
            'statusUpdateUrl':
            campaign.get_status_update_link(user.user_id, user.profile),
        })
Example #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)
    }