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:] ])
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) ]
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
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
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
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:] ])
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()
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':
"""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')
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='*****@*****.**', ))
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='*****@*****.**', ))
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
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'),
class _AllJobsGenerator(_RandomGenerator): name = i18n.make_translatable_string('Tous les métiers porteurs') max_jobs = None
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': {
'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)
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='*****@*****.**', ))
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'), }