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 _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_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 _of_job_name(project: ScoringProject) -> str: translated = project.translate_string('de {job_name}').format( job_name=_job_name(project)) if translated.startswith('de '): return french.maybe_contract_prefix('de ', "d'", _job_name(project)) return translated
def test_empty(self) -> None: """Next word is empty.""" sentence = french.maybe_contract_prefix('foo ', 'bar ', '') self.assertEqual('foo ', sentence)
def test_upper_case(self) -> None: """Next word starts with an upper case vowel.""" sentence = french.maybe_contract_prefix( 'foo ', 'bar ', 'A starts with an uppercase A') self.assertEqual('bar A starts with an uppercase A', sentence)
def test_h(self) -> None: """Next word starts with an H.""" sentence = french.maybe_contract_prefix('foo ', 'bar ', 'h starts with an H') self.assertEqual('bar h starts with an H', sentence)
def test_accented_vowel(self) -> None: """Next word starts with an accented vowel.""" sentence = french.maybe_contract_prefix('foo ', 'bar ', 'à starts with an A') self.assertEqual('bar à starts with an A', sentence)
def test_consonant(self) -> None: """Next word starts with a consonant.""" sentence = french.maybe_contract_prefix('foo ', 'bar ', 'starts with an S') self.assertEqual('foo starts with an S', sentence)
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) })
'latin-1', 'replace')), '%lastName': lambda scoring_project: scoring_project.user_profile.last_name, '%likeYourWorkplace': lambda scoring_project: (scoring_project.job_group_info().like_your_workplace), '%masculineJobName': lambda scoring_project: french.lower_first_letter( scoring_project.details.target_job.masculine_name), '%name': lambda scoring_project: scoring_project.user_profile.name, '%ofCity': lambda scoring_project: french.of_city(scoring_project.details.mobility. city.name), '%ofJobName': lambda scoring_project: french.maybe_contract_prefix( 'de ', "d'", _job_name(scoring_project)), '%placePlural': lambda scoring_project: scoring_project.job_group_info().place_plural, '%postcode': _postcode, '%regionId': lambda scoring_project: scoring_project.details.mobility.city.region_id, '%romeId': lambda scoring_project: scoring_project.details.target_job.job_group. rome_id, '%totalInterviewCount': _total_interview_count, '%whatILoveAbout': lambda scoring_project: scoring_project.job_group_info().what_i_love_about, }
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 test_vowel(self): """Next word starts with a vowel.""" sentence = french.maybe_contract_prefix('foo ', 'bar ', 'a starts with an A') self.assertEqual('bar a starts with an A', sentence)
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 _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) }