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 _employment_vars( user: user_pb2.User, now: datetime.datetime, **unused_kwargs: Any) \ -> Optional[Dict[str, str]]: """Compute vars for a given user for the employment survey. Returns: a dict with all vars required for the template, or None if no email should be sent. """ registered_months_ago = campaign.get_french_months_ago( user.registered_at.ToDatetime(), now=now) if not registered_months_ago: logging.warning('User registered only recently (%s)', user.registered_at) return None # If the users have already updated their employment status less than one month ago, # ignore them. for status in user.employment_status: if status.created_at.ToDatetime() > _ONE_MONTH_AGO: return None survey_token = parse.quote( auth.create_token(user.user_id, role='employment-status')) redirect_url = parse.quote(f'{campaign.BASE_URL}/statut/en-recherche') return dict( campaign.get_default_vars(user), **{ 'registeredMonthsAgo': registered_months_ago, 'seekingUrl': f'{campaign.BASE_URL}/api/employment-status?user={user.user_id}&token={survey_token}&' f'seeking=STILL_SEEKING&redirect={redirect_url}', 'stopSeekingUrl': f'{campaign.BASE_URL}/api/employment-status?user={user.user_id}&token={survey_token}&' f'seeking=STOP_SEEKING&redirect={redirect_url}', })
def employment_vars(user, unused_db=None): """Compute vars for a given user for the employment survey. Returns: a dict with all vars required for the template, or None if no email should be sent. """ 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 # If the users have already updated their employment status less than one month ago, # ignore them. for status in user.employment_status: if status.created_at.ToDatetime() > _ONE_MONTH_AGO: return None 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 { 'firstName': french.cleanup_firstname(user.profile.name), 'registeredMonthsAgo': registered_months_ago, 'seekingUrl': '{}/api/employment-status?user={}&token={}&seeking={}&redirect={}'. format( campaign.BASE_URL, user.user_id, survey_token, 'STILL_SEEKING', parse.quote('{}/statut/en-recherche'.format(campaign.BASE_URL)), ), 'stopSeekingUrl': '{}/api/employment-status?user={}&token={}&seeking={}&redirect={}'. format( campaign.BASE_URL, user.user_id, survey_token, 'STOP_SEEKING', parse.quote('{}/statut/ne-recherche-plus'.format( campaign.BASE_URL)), ), 'unsubscribeLink': '{}/unsubscribe.html?email={}&auth={}'.format( campaign.BASE_URL, parse.quote(user.profile.email), unsubscribe_token), }
def body_language_vars(user, unused_db=None): """Compute vars for a given user for the body language 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 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 has_read_last_focus_email = any(email.status in _READ_EMAIL_STATUSES for email in user.emails_sent if email.campaign_id.startswith('focus-')) worst_frustration = next( (user_pb2.Frustration.Name(frustration) for frustration in (user_pb2.SELF_CONFIDENCE, user_pb2.INTERVIEW, user_pb2.ATYPIC_PROFILE) if frustration in user.profile.frustrations), '') if not worst_frustration: return None unsubscribe_token = parse.quote( auth.create_token(user.profile.email, role='unsubscribe')) return { 'firstName': french.cleanup_firstname(user.profile.name), 'gender': user_pb2.Gender.Name(user.profile.gender), 'hasReadLastFocusEmail': campaign.as_template_boolean(has_read_last_focus_email), 'registeredMonthsAgo': registered_months_ago, 'unsubscribeLink': '{}/unsubscribe.html?email={}&auth={}'.format( campaign.BASE_URL, parse.quote(user.profile.email), unsubscribe_token), 'worstFrustration': worst_frustration, }
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 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), }
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, })