Example #1
0
class _RandomGenerator(_Generator):

    name = i18n.make_translatable_string('Des métiers au hasard')
    max_jobs: Optional[int] = 30

    def get_jobs(
            self, *, allowed_job_ids: Set[str], **unused_kwargs: Any) -> Optional[_ComputedSection]:
        seed = _create_random_seed()
        randomizer = random.Random(seed)
        num_jobs = min(self._num_jobs_for_first_batch, len(allowed_job_ids))
        return _ComputedSection(
            [
                upskilling_pb2.Job(job_group=job_pb2.JobGroup(rome_id=rome_id))
                for rome_id in randomizer.sample(allowed_job_ids, num_jobs)
            ],
            state=seed)

    def get_more_jobs(
            self, *, scoring_project: scoring.ScoringProject, section_id: str,  # pylint: disable=unused-argument
            state: str) -> upskilling_pb2.Section:
        """Generate more jobs for a given section."""

        randomizer = random.Random(state)
        good_jobs = jobs.get_all_good_job_group_ids(scoring_project.database)
        num_jobs = len(good_jobs) if self.max_jobs is None else min(self.max_jobs, len(good_jobs))
        return upskilling_pb2.Section(jobs=[
            upskilling_pb2.Job(job_group=job_pb2.JobGroup(rome_id=rome_id))
            for rome_id in randomizer.sample(good_jobs, num_jobs)[self._num_jobs_for_first_batch:]
        ])
Example #2
0
class _BestSalariesLowQualifGenerator(_BestJobsGenerator):

    name = i18n.make_translatable_string(
        'Des métiers avec un bon salaire accessibles avec un Bac+2 ou moins %inDepartement')

    max_level: 'job_pb2.DegreeLevel.V' = job_pb2.BTS_DUT_DEUG

    def _create_job(self, related_job_group: job_pb2.RelatedJobGroup) -> upskilling_pb2.Job:
        shown_metric = related_job_group.local_stats.imt.junior_salary.short_text or \
            related_job_group.local_stats.salary.short_text
        return upskilling_pb2.Job(
            job_group=related_job_group.job_group,
            shown_metric=shown_metric,
        )

    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

    def _get_all_section_jobs(
            self, *, best_jobs: job_pb2.BestJobsInArea, scoring_project: scoring.ScoringProject) \
            -> Sequence[job_pb2.RelatedJobGroup]:
        return [
            best_job
            for best_job in best_jobs.best_salaries_jobs
            if self._has_low_qualif(scoring_project, best_job)
        ]
Example #3
0
class _BestRelativeScoreJobsGenerator(_BestJobsGenerator):

    name = i18n.make_translatable_string('Des métiers qui recrutent bien %inDepartement')

    def _get_all_section_jobs(
            self, *, best_jobs: job_pb2.BestJobsInArea, **unused_kwargs: Any) \
            -> Sequence[job_pb2.RelatedJobGroup]:
        return best_jobs.best_relative_score_jobs
Example #4
0
class _BestLocalMarketScoreGenerator(_BestJobsGenerator):

    name = i18n.make_translatable_string('Des métiers avec peu de concurrence %inDepartement')

    def _get_all_section_jobs(
            self, *, best_jobs: job_pb2.BestJobsInArea, **unused_kwargs: Any) \
            -> Sequence[job_pb2.RelatedJobGroup]:
        return best_jobs.best_local_market_score_jobs
Example #5
0
class _BestSalariesGenerator(_BestJobsGenerator):

    name = i18n.make_translatable_string('Des métiers avec un bon salaire %inDepartement')

    def _create_job(self, related_job_group: job_pb2.RelatedJobGroup) -> upskilling_pb2.Job:
        shown_metric = related_job_group.local_stats.imt.junior_salary.short_text or \
            related_job_group.local_stats.salary.short_text
        return upskilling_pb2.Job(
            job_group=related_job_group.job_group,
            shown_metric=shown_metric,
        )

    def _get_all_section_jobs(
            self, *, best_jobs: job_pb2.BestJobsInArea, **unused_kwargs: Any) \
            -> Sequence[job_pb2.RelatedJobGroup]:
        return best_jobs.best_salaries_jobs
Example #6
0
class _LowAutomationRiskGenerator(_Generator):

    name = i18n.make_translatable_string('Des métiers qui ne seront pas remplacés par des robots')

    max_jobs: Optional[int] = 30

    automation_risk_threshold = 25

    def get_jobs(
            self, *, scoring_project: scoring.ScoringProject, **unused_kwargs: Any,
    ) -> Optional[_ComputedSection]:
        seed = _create_random_seed()
        safe_jobs = jobs.get_all_good_job_group_ids(
            scoring_project.database, automation_risk_threshold=self.automation_risk_threshold,
            unknown_risk_value=50)
        randomizer = random.Random(seed)
        num_jobs = min(self._num_jobs_for_first_batch, len(safe_jobs))
        return _ComputedSection(
            [
                upskilling_pb2.Job(job_group=job_pb2.JobGroup(rome_id=rome_id))
                for rome_id in randomizer.sample(safe_jobs, num_jobs)
            ],
            state=seed)

    def get_more_jobs(
            self, *, scoring_project: scoring.ScoringProject, section_id: str,  # pylint: disable=unused-argument
            state: str) -> upskilling_pb2.Section:
        """Generate more jobs for a given section."""

        randomizer = random.Random(state)
        safe_jobs = jobs.get_all_good_job_group_ids(
            scoring_project.database, automation_risk_threshold=self.automation_risk_threshold,
            unknown_risk_value=50)
        num_jobs = len(safe_jobs) if self.max_jobs is None else min(self.max_jobs, len(safe_jobs))
        return upskilling_pb2.Section(jobs=[
            upskilling_pb2.Job(job_group=job_pb2.JobGroup(rome_id=rome_id))
            for rome_id in randomizer.sample(safe_jobs, num_jobs)[self._num_jobs_for_first_batch:]
        ])
Example #7
0
import os
from typing import KeysView, Optional

from algoliasearch import exceptions
from algoliasearch import search_client
from algoliasearch import search_index
from google.protobuf import json_format

from bob_emploi.frontend.server import i18n
from bob_emploi.frontend.server import mongo
from bob_emploi.frontend.server import proto
from bob_emploi.frontend.api import geo_pb2

_DEPARTEMENTS: proto.MongoCachedCollection[geo_pb2.Departement] = \
    proto.MongoCachedCollection(geo_pb2.Departement, 'departements')
_IN_DEPARTEMENT = i18n.make_translatable_string('en {departement_name}')


@functools.lru_cache()
def _get_algolia_index() -> search_index.SearchIndex:
    return search_client.SearchClient.create(
        os.getenv('ALGOLIA_APP_ID', 'K6ACI9BKKT'),
        os.getenv('ALGOLIA_API_KEY',
                  'da4db0bf437e37d6d49cefcb8768c67a')).init_index('cities')


def list_all_departements(database: mongo.NoPiiMongoDatabase) -> KeysView[str]:
    """List all French département IDs."""

    return _DEPARTEMENTS.get_collection(database).keys()
Example #8
0
import datetime
from typing import Any
from urllib import parse

from bob_emploi.frontend.api import user_pb2
from bob_emploi.frontend.server import auth_token
from bob_emploi.frontend.server import i18n
from bob_emploi.frontend.server import jobs
from bob_emploi.frontend.server import mongo
from bob_emploi.frontend.server import product
from bob_emploi.frontend.server import scoring
from bob_emploi.frontend.server.mail import campaign

_COACH_FIRST_NAME = 'Tabitha'
_PRODUCT = 'Jobflix'
_SENDER_NAME = i18n.make_translatable_string("{0} et l'équipe de {1}").\
    format(_COACH_FIRST_NAME, _PRODUCT)


def get_default_vars(user: user_pb2.User,
                     **unused_kwargs: Any) -> dict[str, Any]:
    """Default template variables for a Jobflix campaign."""

    return {
        'productName':
        product.bob.get_plugin_config('jobflix', 'productName', _PRODUCT),
        'productUrl':
        parse.urljoin(
            product.bob.base_url,
            product.bob.get_plugin_config('jobflix', 'productUrl',
                                          'https://www.jobflix.app')),
        'senderFirstName':
Example #9
0
"""Common function to handle jobs."""

import logging
import os
import typing
from typing import KeysView, Mapping, Iterable, Optional, Set

from bob_emploi.frontend.api import job_pb2
from bob_emploi.frontend.server import cache
from bob_emploi.frontend.server import i18n
from bob_emploi.frontend.server import mongo
from bob_emploi.frontend.server import proto

EMPLOYMENT_TYPES = {
    job_pb2.INTERNSHIP:
    i18n.make_translatable_string('stage'),
    job_pb2.CDI:
    i18n.make_translatable_string('CDI'),
    job_pb2.CDD_OVER_3_MONTHS:
    i18n.make_translatable_string('CDD de plus de 3 mois'),
    job_pb2.CDD_LESS_EQUAL_3_MONTHS:
    i18n.make_translatable_string('CDD de moins de 3 mois'),
    job_pb2.INTERIM:
    i18n.make_translatable_string('intérim'),
    job_pb2.ANY_CONTRACT_LESS_THAN_A_MONTH:
    i18n.make_translatable_string("contrat de moins d'un mois"),
}

# Cache (from MongoDB) of job group info.
_JOB_GROUPS_INFO: proto.MongoCachedCollection[job_pb2.JobGroup] = \
    proto.MongoCachedCollection(job_pb2.JobGroup, 'job_group_info')
Example #10
0
campaign.register_campaign(
    campaign.Campaign(
        campaign_id=_CAMPAIGN_ID,
        mongo_filters={
            'projects': {
                '$elemMatch': {
                    'jobSearchHasNotStarted': {
                        '$ne': True
                    },
                    'isIncomplete': {
                        '$ne': True
                    },
                    'actions.0': {
                        '$exists': True
                    },
                }
            },
            'emailsSent': {
                '$not': {
                    '$elemMatch': {
                        'campaignId': _CAMPAIGN_ID
                    }
                }
            },
        },
        get_vars=_get_vars,
        sender_name=i18n.make_translatable_string(
            "{{var:firstTeamMember}} et l'équipe de {{var:productName}}"),
        sender_email='*****@*****.**',
    ))
Example #11
0
        goal = 'décrocher votre prochain emploi'

    return campaign.get_default_coaching_email_vars(user) | {
        'goal': goal,
        'numberUsers': '250\u00A0000',
        'lastYear': str(now.year - 1),
        'year': str(now.year),
    }


campaign.register_campaign(
    campaign.Campaign(
        campaign_id='christmas',
        mongo_filters={},
        get_vars=_christmas_vars,
        is_coaching=True,
        is_big_focus=True,
        sender_name=i18n.make_translatable_string(
            'Joanna de {{var:productName}}'),
        sender_email='*****@*****.**',
    ))
campaign.register_campaign(
    campaign.Campaign(
        campaign_id='new-year',
        mongo_filters={},
        get_vars=_new_year_vars,
        sender_name=i18n.make_translatable_string(
            'Joanna de {{var:productName}}'),
        sender_email='*****@*****.**',
    ))
Example #12
0
class _BestSalariesNoQualifGenerator(_BestSalariesLowQualifGenerator):

    name = i18n.make_translatable_string(
        'Des métiers avec un bon salaire accessibles sans diplôme %inDepartement')

    max_level = job_pb2.NO_DEGREE
Example #13
0
from typing import Any, Iterable, Mapping, Optional, Sequence

from bob_emploi.frontend.server import i18n
from bob_emploi.frontend.server import geo
from bob_emploi.frontend.server import jobs
from bob_emploi.frontend.server import mongo
from bob_emploi.frontend.server import proto
from bob_emploi.frontend.api import geo_pb2
from bob_emploi.frontend.api import job_pb2
from bob_emploi.frontend.api import project_pb2
from bob_emploi.frontend.api import user_pb2
from bob_emploi.frontend.server import scoring
from bob_emploi.frontend.server.mail import campaign


_FRENCH_MONTHS = i18n.make_translatable_string(
    'janvier/février/mars/avril/mai/juin/juillet/août/septembre/octobre/novembre/décembre')
_BOB_DEPLOYMENT = os.getenv('BOB_DEPLOYMENT', 'fr')

_APPLICATION_MODES_SHORT = {
    job_pb2.SPONTANEOUS_APPLICATION: i18n.make_translatable_string('Les Candidatures Spontanées'),
    job_pb2.PLACEMENT_AGENCY: i18n.make_translatable_string('Les Intermédiaires'),
    job_pb2.PERSONAL_OR_PROFESSIONAL_CONTACTS: i18n.make_translatable_string('Le Réseau'),
}

_EMPLOYMENT_TYPES_TITLE = {
    job_pb2.INTERNSHIP: i18n.make_translatable_string('Stage'),
    job_pb2.CDI: i18n.make_translatable_string('CDI'),
    job_pb2.CDD_OVER_3_MONTHS: i18n.make_translatable_string('CDD long'),
    job_pb2.CDD_LESS_EQUAL_3_MONTHS: i18n.make_translatable_string('CDD court'),
    job_pb2.INTERIM: i18n.make_translatable_string('Intérim'),
    job_pb2.ANY_CONTRACT_LESS_THAN_A_MONTH: i18n.make_translatable_string('Contrat court'),
Example #14
0
class _AllJobsGenerator(_RandomGenerator):

    name = i18n.make_translatable_string('Tous les métiers porteurs')
    max_jobs = None
Example #15
0
 campaign.Campaign(
     campaign_id='focus-self-develop',
     mongo_filters={
         'projects': {
             '$elemMatch': {
                 'jobSearchHasNotStarted': {
                     '$ne': True
                 },
                 'isIncomplete': {
                     '$ne': True
                 },
             }
         }
     },
     get_vars=_get_self_development_vars,
     sender_name=i18n.make_translatable_string(
         "Joanna et l'équipe de {{var:productName}}"),
     sender_email='*****@*****.**',
     is_coaching=True,
     is_big_focus=True,
 ),
 campaign.Campaign(
     campaign_id='focus-body-language',
     mongo_filters={
         'projects': {
             '$elemMatch': {
                 'isIncomplete': {
                     '$ne': True
                 },
             }
         },
         'profile.frustrations': {
Example #16
0
                        'gender': user_profile_pb2.Gender.Name(
                            user.profile.gender),
                        'mainChallenge': main_challenge_id,
                    }),
            }),
    }


_FFS_CAMPAIGN = campaign.Campaign(
    campaign_id=_CAMPAIGN_ID,
    mongo_filters={
        'emailsSent': {
            '$not': {
                '$elemMatch': {
                    'campaignId': _CAMPAIGN_ID
                }
            }
        },
        # Don't bug very old users, they wouldn't pass the date check anyway.
        'registeredAt': {
            '$gt': '2022-02-01'
        },
    },
    get_vars=_get_ffs_vars,
    sender_name=i18n.make_translatable_string(
        'Tabitha de {{var:productName}}'),
    sender_email='*****@*****.**',
)

campaign.register_campaign(_FFS_CAMPAIGN)
Example #17
0
from bob_emploi.frontend.server import proto
from bob_emploi.frontend.server.mail import campaign
from bob_emploi.frontend.server.mail import mail_send

_TWO_YEARS_AGO_STRING = \
    proto.datetime_to_json_string(datetime.datetime.now() - datetime.timedelta(730))


def _account_deletion_notice_vars(user: user_pb2.User, **unused_kwargs: Any) -> dict[str, str]:
    return dict(
        campaign.get_default_vars(user),
        loginUrl=campaign.create_logged_url(user.user_id))


campaign.register_campaign(campaign.Campaign(
    campaign_id='account-deletion-notice',
    mongo_filters={
        # User hasn't been on Bob for two years.
        'registeredAt': {'$lt': _TWO_YEARS_AGO_STRING},
        'requestedByUserAtDate': {'$not': {'$gt': _TWO_YEARS_AGO_STRING}},
        # User hasn't read any email we sent to them in the last two years.
        'emailsSent': {'$not': {'$elemMatch': {
            'sentAt': {'$gt': _TWO_YEARS_AGO_STRING},
            'status': {'$in': list(mail_send.READ_EMAIL_STATUS_STRINGS)},
        }}},
    },
    get_vars=_account_deletion_notice_vars,
    sender_name=i18n.make_translatable_string("Joanna et l'équipe de {{var:productName}}"),
    sender_email='*****@*****.**',
))
Example #18
0
import unidecode

from bob_emploi.frontend.api import geo_pb2
from bob_emploi.frontend.api import job_pb2
from bob_emploi.frontend.api import project_pb2
from bob_emploi.frontend.api import user_profile_pb2
from bob_emploi.frontend.server import french
from bob_emploi.frontend.server import geo
from bob_emploi.frontend.server import i18n
from bob_emploi.frontend.server import scoring_base

# This is to be put in a sentence related to several people:
# "Les gens trouvent surtout un emploi grâce à ..."
APPLICATION_MODES = {
    job_pb2.SPONTANEOUS_APPLICATION:
    i18n.make_translatable_string('une candidature spontanée'),
    job_pb2.PLACEMENT_AGENCY:
    i18n.make_translatable_string('un intermédiaire du placement'),
    job_pb2.PERSONAL_OR_PROFESSIONAL_CONTACTS:
    i18n.make_translatable_string('leur réseau personnel ou professionnel'),
}

_EXPERIENCE_DURATION_AGO = {
    project_pb2.INTERN: i18n.make_translatable_string('il y a peu de temps'),
    project_pb2.JUNIOR: i18n.make_translatable_string('il y a peu de temps'),
    project_pb2.INTERMEDIARY:
    i18n.make_translatable_string('il y a plus de 2 ans'),
    project_pb2.SENIOR: i18n.make_translatable_string('il y a plus de 6 ans'),
    project_pb2.EXPERT: i18n.make_translatable_string('il y a plus de 10 ans'),
}