def test_update_func(self) -> None: """Test use with an update function.""" self._db.basic.insert_many([ { '_id': 'A123', 'name': 'First one' }, { '_id': 'A124', 'name': 'Second one' }, ]) def _update_func(message: test_pb2.Simple, message_id: str) -> None: message.multiple_words = f'{message_id} = {message.name}' collection: proto.MongoCachedCollection[test_pb2.Simple] = \ proto.MongoCachedCollection(test_pb2.Simple, 'basic', update_func=_update_func) cache = collection.get_collection(self._db) cache_a124 = cache.get('A124') assert cache_a124 self.assertEqual('Second one', cache_a124.name) self.assertEqual('A124 = Second one', cache_a124.multiple_words)
def setUp(self): """Set up mock environment.""" super(CacheMongoTestCase, self).setUp() self._db = mongomock.MongoClient().get_database('test') self._collection = proto.MongoCachedCollection(job_pb2.JobGroup, 'basic')
def setUp(self) -> None: """Set up mock environment.""" super().setUp() self._db = mongo.NoPiiMongoDatabase( mongomock.MongoClient().get_database('test')) self._collection: proto.MongoCachedCollection[test_pb2.Simple] = \ proto.MongoCachedCollection(test_pb2.Simple, 'basic')
# 5 applications they do; a value of 1/15 would make us recommend a large # impact advice (3 impact points) or 3 small ones if auser gets an interview # for every 15 applications. _SCORE_PER_INTERVIEW_RATIO = 1 / 5 # Average number of days per month. _DAYS_PER_MONTH = 365.25 / 12 # Average number of weeks per month. _WEEKS_PER_MONTH = 52 / 12 # Maximum of the estimation scale for English skills, or office tools. _ESTIMATION_SCALE_MAX = 3 _APPLICATION_TIPS: proto.MongoCachedCollection[application_pb2.ApplicationTip] = \ proto.MongoCachedCollection(application_pb2.ApplicationTip, 'application_tips') _REGIONS: proto.MongoCachedCollection[geo_pb2.Region] = \ proto.MongoCachedCollection(geo_pb2.Region, 'regions') _SPECIFIC_TO_JOB_ADVICE: proto.MongoCachedCollection[advisor_pb2.DynamicAdvice] = \ proto.MongoCachedCollection(advisor_pb2.DynamicAdvice, 'specific_to_job_advice') _EXPERIENCE_DURATION = { project_pb2.INTERN: 'peu', project_pb2.JUNIOR: 'peu', project_pb2.INTERMEDIARY: 'plus de 2 ans', project_pb2.SENIOR: 'plus de 6 ans', project_pb2.EXPERT: 'plus de 10 ans', } # Matches variables that need to be replaced by populate_template.
def __init__(self) -> None: super().__init__() self.testimonial_db: proto.MongoCachedCollection[testimonial_pb2.Testimonial] = \ proto.MongoCachedCollection(testimonial_pb2.Testimonial, 'adie_testimonials') self.event_db: proto.MongoCachedCollection[event_pb2.Event] = \ proto.MongoCachedCollection(event_pb2.Event, 'adie_events')
def __init__(self) -> None: super().__init__() self._db: proto.MongoCachedCollection[association_pb2.VolunteeringMissions] = \ proto.MongoCachedCollection(association_pb2.VolunteeringMissions, 'local_missions')
def __init__(self) -> None: super().__init__() self._salon_db: proto.MongoCachedCollection[online_salon_pb2.OnlineSalon] = \ proto.MongoCachedCollection(online_salon_pb2.OnlineSalon, 'online_salons')
def __init__(self): super(_AdviceEventScoringModel, self).__init__() self._db = proto.MongoCachedCollection(event_pb2.Event, 'events')
import typing from typing import Dict, List, Optional, Tuple import pymongo from bob_emploi.frontend.api import diagnostic_pb2 from bob_emploi.frontend.api import project_pb2 from bob_emploi.frontend.api import strategy_pb2 from bob_emploi.frontend.api import user_pb2 from bob_emploi.frontend.server import diagnostic from bob_emploi.frontend.server import proto from bob_emploi.frontend.server import scoring _STRATEGY_ADVICE_TEMPLATES: proto.MongoCachedCollection[strategy_pb2.StrategyAdviceTemplate] = \ proto.MongoCachedCollection(strategy_pb2.StrategyAdviceTemplate, 'strategy_advice_templates') _STRATEGY_MODULES: proto.MongoCachedCollection[strategy_pb2.StrategyModule] = \ proto.MongoCachedCollection(strategy_pb2.StrategyModule, 'strategy_modules') _STRATEGY_MODULES_WITH_TEMPLATES: List[Tuple[pymongo.database.Database, Dict[ str, List[strategy_pb2.StrategyModule]]]] = [] _SPECIFIC_TO_JOB_ADVICE_ID = 'specific-to-job' # Strategy with score less than that is considered less relevant to the user. _MAXIMUM_SECONDARY_SCORE = 10 def clear_cache() -> None: """Clear cached strategy modules with their advice."""
def __init__(self) -> None: super().__init__() self._db: proto.MongoCachedCollection[skill_pb2.JobSkills] = \ proto.MongoCachedCollection(skill_pb2.JobSkills, 'skills_for_future')
"""Common function to handle jobs.""" from bob_emploi.frontend.api import job_pb2 from bob_emploi.frontend.server import proto # Cache (from MongoDB) of job group info. _JOB_GROUPS_INFO = proto.MongoCachedCollection(job_pb2.JobGroup, 'job_group_info') def get_group_proto(database, rome_id): """Get a JobGroup proto corresponding to the ROME job group ID.""" return _JOB_GROUPS_INFO.get_collection(database).get(rome_id) def get_job_proto(database, job_id, rome_id): """Get a Job proto corresponding to the job ID if it is found in the ROME job group.""" job_group = get_group_proto(database, rome_id) if not job_group or not job_id: return None for job_proto in job_group.jobs: if job_proto.code_ogr == job_id: job = job_pb2.Job() job.CopyFrom(job_proto) job.job_group.rome_id = job_group.rome_id job.job_group.name = job_group.name return job
expected_salary = project.salary_estimation() if age < 25 or project.get_search_length_now( ) < 6 or expected_salary > 20000: return scoring_base.NULL_EXPLAINED_SCORE return _score_and_explain_after_filters(project) def get_expanded_card_data( self, project: scoring_base.ScoringProject) -> geo_pb2.FrenchCity: """Retrieve data for the expanded card.""" return geo.get_city_location(project.database, project.details.city.city_id) or \ geo_pb2.FrenchCity() _PARTNER_BANKS: proto.MongoCachedCollection[driving_license_pb2.OneEuroProgramPartnerBank] = \ proto.MongoCachedCollection( driving_license_pb2.OneEuroProgramPartnerBank, 'banks_one_euro_driving_license') _PARTNER_SCHOOLS: proto.MongoCachedCollection[driving_license_pb2.DrivingSchool] = \ proto.MongoCachedCollection( driving_license_pb2.DrivingSchool, 'schools_one_euro_driving_license') class _DrivingLicenseOneEuroScoringModel(scoring_base.ModelBase): """A scoring model for the "Driving license at 1 euro / day" advice.""" def score_and_explain(self, project: scoring_base.ScoringProject) \ -> scoring_base.ExplainedScore: """Compute the score for a given project and explains it.""" age = project.get_user_age() if age < 16 or age >= 25:
# small impact advice (1 impact point) if a user gets an interview for every # 5 applications they do; a value of 1/15 would make us recommend a large # impact advice (3 impact points) or 3 small ones if auser gets an interview # for every 15 applications. _SCORE_PER_INTERVIEW_RATIO = 1 / 5 # Average number of days per month. _DAYS_PER_MONTH = 365.25 / 12 # Average number of weeks per month. _WEEKS_PER_MONTH = 52 / 12 # Maximum of the estimation scale for English skills, or office tools. _ESTIMATION_SCALE_MAX = 3 _APPLICATION_TIPS = proto.MongoCachedCollection(application_pb2.ApplicationTip, 'application_tips') _REGIONS = proto.MongoCachedCollection(geo_pb2.Region, 'regions') _SPECIFIC_TO_JOB_ADVICE = proto.MongoCachedCollection( advisor_pb2.DynamicAdvice, 'specific_to_job_advice') _EXPERIENCE_DURATION = { project_pb2.INTERNSHIP: 'peu', project_pb2.JUNIOR: 'peu', project_pb2.INTERMEDIARY: 'plus de 2 ans', project_pb2.SENIOR: 'plus de 6 ans', project_pb2.EXPERT: 'plus de 10 ans', } # Matches variables that need to be replaced by populate_template. _TEMPLATE_VAR = re.compile('.*%[a-z]+')
def __init__(self, network_level): super(_ImproveYourNetworkScoringModel, self).__init__() self._db = proto.MongoCachedCollection(network_pb2.ContactLeadTemplate, 'contact_lead') self._network_level = network_level
i18n.make_translatable_string('stage'), job_pb2.CDI: i18n.make_translatable_string('CDI'), job_pb2.CDD_OVER_3_MONTHS: i18n.make_translatable_string('CDD de plus de 3 mois'), job_pb2.CDD_LESS_EQUAL_3_MONTHS: i18n.make_translatable_string('CDD de moins de 3 mois'), job_pb2.INTERIM: i18n.make_translatable_string('intérim'), job_pb2.ANY_CONTRACT_LESS_THAN_A_MONTH: i18n.make_translatable_string("contrat de moins d'un mois"), } # Cache (from MongoDB) of job group info. _JOB_GROUPS_INFO: proto.MongoCachedCollection[job_pb2.JobGroup] = \ proto.MongoCachedCollection(job_pb2.JobGroup, 'job_group_info') # TODO(cyrille): Find a way to properly merge translations depending on locale. def get_group_proto(database: mongo.NoPiiMongoDatabase, rome_id: str, locale: str = 'fr') \ -> Optional[job_pb2.JobGroup]: """Get a JobGroup proto corresponding to the ROME job group ID.""" locale_prefix = '' if locale: locale_prefix = '' if locale.startswith('fr') else f'{locale[:2]}:' all_job_groups = _JOB_GROUPS_INFO.get_collection(database) translated = all_job_groups.get(f'{locale_prefix}{rome_id}') if translated: return translated if locale_prefix:
def _get_advice_data(project, advice_id): try: return next(advice for advice in project.advices if advice.advice_id == advice_id) except StopIteration: flask.abort(404, 'Conseil "{}" inconnu.'.format(advice_id)) _ACTION_STOPPED_STATUSES = frozenset([ action_pb2.ACTION_SNOOZED, action_pb2.ACTION_DONE, action_pb2.ACTION_STICKY_DONE, action_pb2.ACTION_DECLINED ]) # Cache (from MongoDB) of known chantiers. _CHANTIERS = proto.MongoCachedCollection(chantier_pb2.Chantier, 'chantiers') def _chantiers(): """Returns a list of known chantiers as protos.""" return _CHANTIERS.get_collection(_DB) def _get_authenticator(): authenticator = auth.Authenticator( _USER_DB, _DB, lambda u: _save_user(u, is_new_user=True), _update_returning_user, )
def __init__(self) -> None: super().__init__() self._db: proto.MongoCachedCollection[reorient_jobbing_pb2.LocalJobbingStats] = \ proto.MongoCachedCollection(reorient_jobbing_pb2.LocalJobbingStats, 'reorient_jobbing')
def __init__(self): super(_AdviceReorientJobbing, self).__init__() self._db = proto.MongoCachedCollection( reorient_jobbing_pb2.LocalJobbingStats, 'reorient_jobbing')
field=diagnostic_pb2.EMPLOYMENT_TYPE_FIELD, is_before_question=True, comment=_create_bolded_string( comment.format( percentage=str( int(main_employment_type_percentage.percentage) ), employment_type=employment_type, )), ) return response _DIAGNOSTIC_OVERALL: proto.MongoCachedCollection[diagnostic_pb2.DiagnosticTemplate] = \ proto.MongoCachedCollection( diagnostic_pb2.DiagnosticTemplate, 'diagnostic_overall', sort_key='_order') _DIAGNOSTIC_RESPONSES: proto.MongoCachedCollection[diagnostic_pb2.DiagnosticResponse] = \ proto.MongoCachedCollection( diagnostic_pb2.DiagnosticResponse, 'diagnostic_responses', sort_key='order') def _compute_diagnostic_overall( project: scoring.ScoringProject, diagnostic: diagnostic_pb2.Diagnostic, main_challenge: diagnostic_pb2.DiagnosticMainChallenge ) -> diagnostic_pb2.Diagnostic: all_overalls = _DIAGNOSTIC_OVERALL.get_collection(project.database) restricted_overalls = [ o for o in all_overalls if o.category_id == main_challenge.category_id ] try: overall_template = next(
def __init__(self): super(_AdviceJobBoards, self).__init__(user_pb2.NO_OFFERS) self._db = proto.MongoCachedCollection(jobboard_pb2.JobBoard, 'jobboards')
) < 6 or expected_salary > 20000: return scoring_base.NULL_EXPLAINED_SCORE return _score_and_explain_after_filters(project) def get_expanded_card_data(self, project): """Retrieve data for the expanded card.""" # TODO(cyrille): Cache coordinates in ScoringProject. return proto.create_from_mongo( project.database.cities.find_one( {'_id': project.details.mobility.city.city_id}), geo_pb2.FrenchCity) _PARTNER_BANKS = proto.MongoCachedCollection( driving_license_pb2.OneEuroProgramPartnerBank, 'banks_one_euro_driving_license') _PARTNER_SCHOOLS = proto.MongoCachedCollection( driving_license_pb2.DrivingSchool, 'schools_one_euro_driving_license') class _DrivingLicenseOneEuroScoringModel(scoring_base.ModelBase): """A scoring model for the "Driving license at 1 euro / day" advice.""" def score_and_explain(self, project): """Compute the score for a given project and explains it.""" age = project.get_user_age() if age < 16 or age >= 25: return scoring_base.NULL_EXPLAINED_SCORE return _score_and_explain_after_filters(project)
from bob_emploi.frontend.api import project_pb2 from bob_emploi.frontend.api import stats_pb2 from bob_emploi.frontend.api import user_pb2 from bob_emploi.frontend.api import user_profile_pb2 from bob_emploi.frontend.server import mongo from bob_emploi.frontend.server import proto from bob_emploi.frontend.server import scoring from bob_emploi.frontend.server.asynchronous import report from bob_emploi.frontend.server.mail import focus from bob_emploi.frontend.server.mail import mail_send from bob_emploi.frontend.server.mail import all_campaigns _ALL_COACHING_EMAILS = all_campaigns.campaign.get_coaching_campaigns().keys() _CHALLENGE_ACTIONS: proto.MongoCachedCollection[stats_pb2.ChallengeAction] = \ proto.MongoCachedCollection( stats_pb2.ChallengeAction, 'challenge_actions', id_field='action_id') _AWS_ES_URL_PATTERN = re.compile(r'.*\.(?P<region>\w+)\.es\.amazonaws\.com$') _NETWORK_ESTIMATE_OPTIONS = { 0: 'UNKNOWN_NETWORK_LEVEL', 1: 'LOW', 2: 'MEDIUM', 3: 'HIGH', } @functools.lru_cache() def get_challenge_actions() -> dict[str, dict[str, float]]: """Compute the score of each action for a given challenge.
def __init__(self, network_level: int) -> None: super().__init__() self._db: proto.MongoCachedCollection[network_pb2.ContactLeadTemplate] = \ proto.MongoCachedCollection(network_pb2.ContactLeadTemplate, 'contact_lead') self._network_level = network_level
import logging import os from typing import KeysView, Optional from algoliasearch import exceptions from algoliasearch import search_client from algoliasearch import search_index from google.protobuf import json_format from bob_emploi.frontend.server import i18n from bob_emploi.frontend.server import mongo from bob_emploi.frontend.server import proto from bob_emploi.frontend.api import geo_pb2 _DEPARTEMENTS: proto.MongoCachedCollection[geo_pb2.Departement] = \ proto.MongoCachedCollection(geo_pb2.Departement, 'departements') _IN_DEPARTEMENT = i18n.make_translatable_string('en {departement_name}') @functools.lru_cache() def _get_algolia_index() -> search_index.SearchIndex: return search_client.SearchClient.create( os.getenv('ALGOLIA_APP_ID', 'K6ACI9BKKT'), os.getenv('ALGOLIA_API_KEY', 'da4db0bf437e37d6d49cefcb8768c67a')).init_index('cities') def list_all_departements(database: mongo.NoPiiMongoDatabase) -> KeysView[str]: """List all French département IDs.""" return _DEPARTEMENTS.get_collection(database).keys()
# pylint: enable=unused-import from bob_emploi.frontend.server.mail.templates import mailjet_templates _EMAIL_PERIOD_DAYS = { email_pb2.EMAIL_ONCE_A_MONTH: 30, email_pb2.EMAIL_MAXIMUM: 6, } # Time of the day (as a number of hours since midnight) at which focus emails are sent. # Only used for simulation, the actual timing is handled by scheduled tasks. _FOCUS_EMAIL_SENDING_TIME = 9 _FOCUS_CAMPAIGNS = campaign.get_coaching_campaigns() _DURATION_BEFORE_FIRST_EMAIL = datetime.timedelta(days=3) # A list of campaigns coming from our DB so that we can decide dynamically to NOT send some of them. _CAMPAIGNS_DB: proto.MongoCachedCollection[email_pb2.Campaign] = \ proto.MongoCachedCollection(email_pb2.Campaign, 'focus_emails') def get_possible_campaigns( database: mongo.NoPiiMongoDatabase, restricted_campaigns: Optional[Iterable[mailjet_templates.Id]] = None) \ -> dict[mailjet_templates.Id, email_pb2.Campaign]: """List all the available campaigns.""" restricted_campaigns_set: Optional[Set[mailjet_templates.Id]] if restricted_campaigns: if isinstance(restricted_campaigns, set): restricted_campaigns_set = restricted_campaigns else: restricted_campaigns_set = set(restricted_campaigns) else:
def __init__(self): super(_AdviceCivicService, self).__init__() self._db = proto.MongoCachedCollection( association_pb2.VolunteeringMissions, 'local_missions')
def __init__(self) -> None: super().__init__() self._db: proto.MongoCachedCollection[event_pb2.Event] = \ proto.MongoCachedCollection(event_pb2.Event, 'events')
"""Module to advise the user on specific jobboard they might not be aware of.""" import random from typing import Iterator from bob_emploi.frontend.server import proto from bob_emploi.frontend.server import scoring_base from bob_emploi.frontend.api import jobboard_pb2 from bob_emploi.frontend.api import user_profile_pb2 _JOBBOARDS: proto.MongoCachedCollection[jobboard_pb2.JobBoard] = \ proto.MongoCachedCollection(jobboard_pb2.JobBoard, 'jobboards') def list_jobboards( project: scoring_base.ScoringProject ) -> Iterator[jobboard_pb2.JobBoard]: """List all job boards for this project.""" all_job_boards = _JOBBOARDS.get_collection(project.database) for job_board_template in scoring_base.filter_using_score( all_job_boards, lambda j: j.filters, project): job_board = jobboard_pb2.JobBoard() job_board.CopyFrom(job_board_template) job_board.link = project.populate_template(job_board.link) yield job_board class _AdviceJobBoards(scoring_base.LowPriorityAdvice): """A scoring model to trigger the "Find job boards" advice.""" def __init__(self) -> None:
"""Advice module to recommend seasonal jobs in other départements.""" import logging from typing import List from bob_emploi.frontend.server import geo from bob_emploi.frontend.server import proto from bob_emploi.frontend.server import scoring_base from bob_emploi.frontend.api import geo_pb2 from bob_emploi.frontend.api import job_pb2 from bob_emploi.frontend.api import seasonal_jobbing_pb2 from bob_emploi.frontend.api import user_pb2 _SEASONAL_JOBBING: proto.MongoCachedCollection[seasonal_jobbing_pb2.MonthlySeasonalJobbingStats] = \ proto.MongoCachedCollection( seasonal_jobbing_pb2.MonthlySeasonalJobbingStats, 'seasonal_jobbing') class _AdviceSeasonalRelocate(scoring_base.ModelBase): """A scoring model for the "seasonal relocate" advice module.""" def score_and_explain(self, project: scoring_base.ScoringProject) \ -> scoring_base.ExplainedScore: """Compute a score for the given ScoringProject.""" reasons: List[str] = [] # For now we just match for people willing to move to the whole country. # There might be cases where we should be able to recommend to people who want to move to # their own region, but it would add complexity to find them. is_not_ready_to_move = project.details.area_type < geo_pb2.COUNTRY
"""The imt email campaign""" import logging from bob_emploi.frontend.server import french from bob_emploi.frontend.server import geo from bob_emploi.frontend.server import proto from bob_emploi.frontend.api import geo_pb2 from bob_emploi.frontend.api import job_pb2 from bob_emploi.frontend.server.asynchronous.mail import campaign # Cache (from MongoDB) of local diagnosis. _LOCAL_DIAGNOSIS = proto.MongoCachedCollection(job_pb2.LocalJobStats, 'local_diagnosis') _FRENCH_MONTHS = { job_pb2.JANUARY: 'Janvier', job_pb2.FEBRUARY: 'Février', job_pb2.MARCH: 'Mars', job_pb2.APRIL: 'Avril', job_pb2.JUNE: 'Juin', job_pb2.JULY: 'Juillet', job_pb2.AUGUST: 'Août', job_pb2.SEPTEMBER: 'Septembre', job_pb2.OCTOBER: 'Octobre', job_pb2.NOVEMBER: 'Novembre', job_pb2.DECEMBER: 'Décembre', } _APPLICATION_MODES_SHORT = { job_pb2.SPONTANEOUS_APPLICATION: 'Les Candidatures Spontanées',