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)
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), })
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), })
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
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 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()
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
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
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]
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
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)
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, )
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, }
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, }
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, })
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) }
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, })
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) })
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, })
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, })
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), }