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_local_jobbing(self, project: scoring_base.ScoringProject) \ -> reorient_jobbing_pb2.JobbingReorientJobs: """Get the jobbing opportunities for the departement.""" recommended_jobs = reorient_jobbing_pb2.JobbingReorientJobs() departement_id = project.details.city.departement_id gender = project.user_profile.gender top_unqualified_jobs = self.list_reorient_jobbing_jobs(project) current_job_market_score = project.imt_proto( ).yearly_avg_offers_per_10_candidates if departement_id in top_unqualified_jobs: for job in top_unqualified_jobs[ departement_id].departement_job_stats.jobs: if current_job_market_score: if job.market_score / current_job_market_score < 1 and \ not project.features_enabled.all_modules: break offers_gain = ( job.market_score / current_job_market_score - 1) * 100 else: if job.market_score < 8: # User is in an unknown market, we only show jobbing jobs that have at least # some job offers. break offers_gain = 0 recommended_jobs.reorient_jobbing_jobs.add( name=project.translate_string( french.genderize_job(job, gender)), offers_percent_gain=offers_gain) return recommended_jobs
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_name( scoring_project: scoring_base.ScoringProject, forced_gender: 'user_profile_pb2.Gender.V' = user_profile_pb2. UNKNOWN_GENDER ) -> str: locale = scoring_project.user_profile.locale or 'fr' gender = forced_gender or scoring_project.user_profile.gender return french.genderize_job(scoring_project.details.target_job, gender, is_lowercased=locale.startswith('fr'))
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 compute_extra_data(self, project): """Compute extra data for this module to render a card in the client.""" # The number of job names is restricted to 2. all_close_jobs = self.get_close_jobs(project) recommended_jobs = itertools.islice( itertools.chain(all_close_jobs.close_jobs, all_close_jobs.evolution_jobs), 2) return project_pb2.ReorientData(jobs=[ job_pb2.Job( name=french.genderize_job(job, project.user_profile.gender)) for job in recommended_jobs ])
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), })
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) })
def get_local_jobbing(self, project: scoring_base.ScoringProject) \ -> reorient_jobbing_pb2.JobbingReorientJobs: """Get the jobbing opportunities for the departement.""" recommended_jobs = reorient_jobbing_pb2.JobbingReorientJobs() departement_id = project.details.city.departement_id gender = project.user_profile.gender top_unqualified_jobs = self.list_reorient_jobbing_jobs(project) current_job_market_score = project.imt_proto().yearly_avg_offers_per_10_candidates if current_job_market_score and departement_id in top_unqualified_jobs: for job in top_unqualified_jobs[departement_id].departement_job_stats.jobs: if job.market_score / current_job_market_score < 1: break offers_gain = (job.market_score / current_job_market_score - 1) * 100 recommended_jobs.reorient_jobbing_jobs.add( name=french.genderize_job(job, gender), offers_percent_gain=offers_gain) return recommended_jobs
def _get_network_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 != 1: raise campaign.DoNotSend('User has a good enough network') in_target_domain = _get_in_target_domain(project.target_job.job_group.rome_id, database) worst_frustration = next( (f for f in (user_profile_pb2.NO_OFFER_ANSWERS, user_profile_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 campaign.get_default_coaching_email_vars(user) | { 'inTargetDomain': in_target_domain, 'frustration': user_profile_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 get_local_jobbing(self, project): """Get the jobbing opportunities for the departement.""" recommended_jobs = reorient_jobbing_pb2.JobbingReorientJobs() departement_id = project.details.mobility.city.departement_id gender = project.user_profile.gender top_unqualified_jobs = self.list_reorient_jobbing_jobs(project) local_diagnosis = project.local_diagnosis() # TODO(marielaure): Move to yearly offers instead of two-year offers. num_current_job_offers = local_diagnosis.num_job_offers_last_year +\ local_diagnosis.num_job_offers_previous_year if num_current_job_offers and departement_id in top_unqualified_jobs: for job in top_unqualified_jobs[ departement_id].departement_job_stats.jobs: if job.offers <= num_current_job_offers: break offers_gain = (job.offers / num_current_job_offers - 1) * 100 recommended_jobs.reorient_jobbing_jobs.add( name=french.genderize_job(job, gender), offers_percent_gain=offers_gain) return recommended_jobs
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)
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 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), })
def _job_name(scoring_project: ScoringProject) -> str: return french.genderize_job(scoring_project.details.target_job, scoring_project.user_profile.gender, is_lowercased=True)
def _get_spontaneous_vars(user: user_pb2.User, *, now: datetime.datetime, database: mongo.NoPiiMongoDatabase, **unused_kwargs: Any) -> dict[str, str]: """Computes vars for a given user for the spontaneous email. Returns a dict with all vars required for the template. """ 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_search_length = scoring_project.get_search_length_now() if job_search_length < 0: raise campaign.DoNotSend('No info on user search duration') rome_id = project.target_job.job_group.rome_id if not rome_id: raise campaign.DoNotSend('User has no target job yet') job_group_info = scoring_project.job_group_info() if not job_group_info.rome_id: raise scoring.NotEnoughDataException( 'Requires job group info to check if spontaneous application is a good channel.', fields={'projects.0.targetJob.jobGroup.romeId'}) application_modes = job_group_info.application_modes if not application_modes: raise scoring.NotEnoughDataException( 'Requires application modes to check if spontaneous application is a good channel.', fields={f'data.job_group_info.{rome_id}.application_modes'}) 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) if not any( _should_use_spontaneous(modes) for modes in application_modes.values()): raise campaign.DoNotSend( "Spontaneous isn't bigger than 20% of interesting channels.") contact_mode = job_group_info.preferred_application_medium if not contact_mode: raise scoring.NotEnoughDataException( 'Contact mode is required to push people to apply spontaneously', fields={ f'data.job_group_info.{rome_id}.preferred_application_medium' }) in_a_workplace = job_group_info.in_a_workplace if not in_a_workplace and contact_mode != job_pb2.APPLY_BY_EMAIL: raise scoring.NotEnoughDataException( 'To apply in person, the %inAWorkplace template is required', fields={f'data.job_group_info.{rome_id}.in_a_workplace'}) like_your_workplace = job_group_info.like_your_workplace if in_a_workplace and not like_your_workplace: raise scoring.NotEnoughDataException( 'The template %likeYourWorkplace is required', fields={f'data.job_group_info.{rome_id}.like_your_workplace'}) to_the_workplace = job_group_info.to_the_workplace if not to_the_workplace: to_the_workplace = scoring_project.translate_static_string( "à l'entreprise") some_companies = job_group_info.place_plural if not some_companies: some_companies = scoring_project.translate_static_string( 'des entreprises') what_i_love_about = scoring_project.translate_string( job_group_info.what_i_love_about, is_genderized=True) # TODO(cyrille): Drop this behaviour once phrases are translated with gender. if user.profile.gender == user_profile_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: raise scoring.NotEnoughDataException( 'An example about "What I love about" a company is required', fields={f'data.job_group_info.{rome_id}.what_i_love_about'}) why_specific_company = job_group_info.why_specific_company if not why_specific_company: raise scoring.NotEnoughDataException( 'An example about "Why this specific company" is required', fields={f'data.job_group_info.{rome_id}.why_specific_company'}) 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 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 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 _job_name(scoring_project): return french.lower_first_letter( french.genderize_job(scoring_project.details.target_job, scoring_project.user_profile.gender))
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), })
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), }