def test_advice_spontaneous_application_extra_data(self, mock_get_lbb_companies): """Test that the advisor computes extra data for the "Spontaneous Application" advice.""" project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity(departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.local_diagnosis.insert_one({ '_id': '14:A1234', 'imt': {'applicationModes': {'Foo': {'first': 'SPONTANEOUS_APPLICATION'}}}, }) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'chantier-spontaneous-application', 'extraDataFieldName': 'spontaneous_application_data', 'isReadyForProd': True, }) mock_get_lbb_companies.return_value = iter([ {'name': 'EX NIHILO'}, {'name': 'M.F.P MULTIMEDIA FRANCE PRODUCTIONS'}, ]) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual( ['EX NIHILO', 'M.F.P MULTIMEDIA FRANCE PRODUCTIONS'], [c.name for c in advice.spontaneous_application_data.companies])
def test_module_crashes(self, mock_logger, mock_scoring_models, mock_send_template): """Test that the advisor does not crash if one module does.""" mock_send_template().status_code = 200 mock_send_template.reset_mock() mock_scoring_models['constant(1)'] = mock.MagicMock(spec=['score']) mock_scoring_models['constant(1)'].score.return_value = 1 mock_scoring_models['crash-me'] = mock.MagicMock(spec=['score']) mock_scoring_models['crash-me'].score.side_effect = ValueError('ouch') project = project_pb2.Project() self.database.advice_modules.insert_many([ { 'adviceId': 'other-work-env', 'triggerScoringModel': 'crash-me', 'isReadyForProd': True, }, { 'adviceId': 'network', 'triggerScoringModel': 'constant(1)', 'isReadyForProd': True, }, ]) advisor.maybe_advise(self.user, project, self.database) self.assertEqual(['network'], [a.advice_id for a in project.advices]) mock_send_template.assert_called_once() mock_logger.assert_called_once()
def test_advice_other_work_env_extra_data(self): """Test that the advisor computes extra data for the work environment advice.""" project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup(rome_id='A1234')), ) self.user.features_enabled.alpha = True self.database.job_group_info.insert_one({ '_id': 'A1234', 'workEnvironmentKeywords': { 'structures': ['A', 'B'], 'sectors': ['sector Toise', 'sector Gal'], }, }) self.database.advice_modules.insert_one({ 'adviceId': 'other-work-env', 'triggerScoringModel': 'advice-other-work-env', 'extraDataFieldName': 'other_work_env_advice_data', 'isReadyForProd': True, }) advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'other-work-env') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual( ['A', 'B'], advice.other_work_env_advice_data.work_environment_keywords.structures) self.assertEqual( ['sector Toise', 'sector Gal'], advice.other_work_env_advice_data.work_environment_keywords.sectors)
def test_advice_job_boards_extra_data(self): """Test that the advisor computes extra data for the "Find a Job Board" advice.""" project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup( rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.jobboards.insert_one({ 'title': 'Indeed', 'filters': ['for-departement(14)'] }) self.database.advice_modules.insert_one({ 'adviceId': 'job-boards', 'triggerScoringModel': 'advice-job-boards', 'extraDataFieldName': 'job_boards_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'job-boards') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('Indeed', advice.job_boards_data.job_board_title) self.assertFalse(advice.job_boards_data.is_specific_to_job_group) self.assertTrue(advice.job_boards_data.is_specific_to_region)
def test_recommend_all_modules(self, mock_send_template): """Test that all advice are recommended when all_modules is true even if incompatible.""" project = project_pb2.Project() self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, 'airtableId': 'abc', 'incompatibleAdviceIds': ['def'], }, { 'adviceId': 'other-work-env', 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, 'airtableId': 'def', }, { 'adviceId': 'new-advice', 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, 'airtableId': 'def', 'incompatibleAdviceIds': ['abc'], }, ]) self.user.features_enabled.all_modules = True advisor.maybe_advise(self.user, project, self.database) self.assertEqual( ['spontaneous-application', 'other-work-env', 'new-advice'], [a.advice_id for a in project.advices]) mock_send_template.assert_called_once()
def test_incompatible_advice_modules(self): """Test that the advisor discard incompatible advice modules.""" project = project_pb2.Project() self.database.advice_modules.insert_many([ { 'adviceId': 'other-work-env', 'airtableId': 'abc', 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, 'incompatibleAdviceIds': ['def'], }, { 'adviceId': 'spontaneous-application', 'airtableId': 'def', 'triggerScoringModel': 'constant(3)', 'isReadyForProd': True, 'incompatibleAdviceIds': ['abc'], }, { 'adviceId': 'final-one', 'airtableId': 'ghi', 'triggerScoringModel': 'constant(1)', 'isReadyForProd': True, }, ]) advisor.maybe_advise(self.user, project, self.database) self.assertEqual( ['spontaneous-application', 'final-one'], [a.advice_id for a in project.advices])
def test_no_advice_if_project_incomplete(self, mock_send_template): """Test that the advice do not get populated when the project is marked as incomplete.""" project = project_pb2.Project(is_incomplete=True) advisor.maybe_advise(self.user, project, self.database) self.assertEqual(len(project.advices), 0) mock_send_template.assert_not_called()
def test_missing_module(self): """Test that the advisor does not crash when a module is missing.""" project = project_pb2.Project(advices=[project_pb2.Advice( advice_id='does-not-exist', status=project_pb2.ADVICE_ACCEPTED)]) project_before = str(project) advisor.maybe_advise(self.user, project, self.database) self.assertEqual(project_before, str(project))
def test_advice_better_job_in_group_extra_data(self): """Test that the advisor computes extra data for the "Better Job in Group" advice.""" project = project_pb2.Project( target_job=job_pb2.Job( code_ogr='1234', job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.job_group_info.insert_one({ '_id': 'A1234', 'jobs': [ { 'codeOgr': '1234', 'name': 'Pilote' }, { 'codeOgr': '5678', 'name': 'Pompier' }, { 'codeOgr': '9012', 'name': 'Facteur' }, ], 'requirements': { 'specificJobs': [ { 'codeOgr': '5678', 'percentSuggested': 55, }, { 'codeOgr': '1234', 'percentSuggested': 45, }, ], }, }) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-better-job-in-group', 'extraDataFieldName': 'better_job_in_group_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('Pompier', advice.better_job_in_group_data.better_job.name) self.assertEqual(1, advice.better_job_in_group_data.num_better_jobs)
def test_advice_spontaneous_application_extra_data(self, mock_get_lbb_companies): """Test that the advisor computes extra data for the "Spontaneous Application" advice.""" project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup( rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.job_group_info.insert_one({ '_id': 'A1234', 'applicationModes': { 'R4Z92': { 'modes': [{ 'percentage': 36.38, 'mode': 'SPONTANEOUS_APPLICATION' }, { 'percentage': 29.46, 'mode': 'PERSONAL_OR_PROFESSIONAL_CONTACTS' }, { 'percentage': 18.38, 'mode': 'PLACEMENT_AGENCY' }, { 'percentage': 15.78, 'mode': 'UNDEFINED_APPLICATION_MODE' }], } }, }) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-spontaneous-application', 'extraDataFieldName': 'spontaneous_application_data', 'isReadyForProd': True, }) mock_get_lbb_companies.return_value = iter([ { 'name': 'EX NIHILO' }, { 'name': 'M.F.P MULTIMEDIA FRANCE PRODUCTIONS' }, ]) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual( ['EX NIHILO', 'M.F.P MULTIMEDIA FRANCE PRODUCTIONS'], [c.name for c in advice.spontaneous_application_data.companies])
def test_advice_improve_success_rate_extra_data(self): """Test that the advisor computes extra data for the "Improve Success Rate" advice.""" project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup( rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=6, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.local_diagnosis.insert_one({ '_id': '14:A1234', 'imt': { 'yearlyAvgOffersDenominator': 10, 'yearlyAvgOffersPer10Candidates': 2, }, }) self.database.job_group_info.insert_one({ '_id': 'A1234', 'requirements': { 'skills': [{ 'name': 'Humour' }, { 'name': 'Empathie' }], 'skillsShortText': '**Humour** et **empathie**', }, }) self.database.advice_modules.insert_one({ 'adviceId': 'improve-success', 'triggerScoringModel': 'advice-improve-resume', 'extraDataFieldName': 'improve_success_rate_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'improve-success') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertGreater( advice.improve_success_rate_data.num_interviews_increase, 50) self.assertFalse(advice.improve_success_rate_data.requirements.skills) self.assertEqual( '**Humour** et **empathie**', advice.improve_success_rate_data.requirements.skills_short_text)
def test_advice_events_extra_data(self, mock_now): """Test that the advisor computes extra data for the "Events" advice.""" mock_now.return_value = datetime.datetime(2017, 8, 15) project = project_pb2.Project( target_job=job_pb2.Job( code_ogr='1234', job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='75')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-event', 'extraDataFieldName': 'events_data', 'isReadyForProd': True, }) self.database.events.insert_many([ { 'title': 'AP HEROS CANDIDATS MADIRCOM - BORDEAUX', 'link': 'https://www.workuper.com/events/ap-heros-candidats-madircom-bordeaux', 'organiser': 'MADIRCOM', 'startDate': '2017-08-29', }, { 'title': 'Le Salon du Travail et de la Mobilité Professionnelle', 'link': 'https://www.workuper.com/events/le-salon-du-travail-et-de-la-mobilite-' 'professionnelle', 'organiser': 'Altice Media Events', 'startDate': '2018-01-19', }, ]) advisor.clear_cache() self.user.features_enabled.alpha = True advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('AP HEROS CANDIDATS MADIRCOM - BORDEAUX', advice.events_data.event_name)
def test_recommend_advice_none(self): """Test that the advisor does not recommend anyting if all modules score 0.""" project = project_pb2.Project() self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) advisor.maybe_advise(self.user, project, self.database) self.assertFalse(project.advices)
def test_find_all_pieces_of_advice(self, mock_send_template): """Test that the advisor scores all advice modules.""" mock_send_template().status_code = 200 mock_send_template.reset_mock() project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), ) self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) advisor.maybe_advise(self.user, project, self.database, 'http://base.example.com') self.assertEqual(['spontaneous-application'], [a.advice_id for a in project.advices]) self.assertEqual(project_pb2.ADVICE_RECOMMENDED, project.advices[0].status) mock_send_template.assert_called_once() data = mock_send_template.call_args[0][2] self.assertEqual( ['advices', 'baseUrl', 'firstName', 'ofProjectTitle', 'projectId'], sorted(data.keys())) self.assertEqual('http://base.example.com', data['baseUrl']) self.assertEqual('Margaux', data['firstName']) self.assertEqual("d'hôtesse", data['ofProjectTitle']) self.assertEqual('1234', data['projectId'])
def test_find_all_pieces_of_advice(self): """Test that the advisor scores all advice modules.""" project = project_pb2.Project() self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) advisor.maybe_advise(self.user, project, self.database) self.assertEqual(['spontaneous-application'], [a.advice_id for a in project.advices]) self.assertEqual(project_pb2.ADVICE_RECOMMENDED, project.advices[0].status)
def test_advice_specific_to_job_override(self): """Test that the advisor overrides some advice data with the "Specific to Job" module.""" project = project_pb2.Project(target_job=job_pb2.Job( job_group=job_pb2.JobGroup(rome_id='D1102')), ) self.database.advice_modules.insert_one({ 'adviceId': 'custom-advice-id', 'triggerScoringModel': 'advice-specific-to-job', 'isReadyForProd': True, }) self.database.specific_to_job_advice.insert_one({ 'title': 'Présentez-vous au chef boulanger dès son arrivée tôt le matin', 'filters': ['for-job-group(D1102)', 'not-for-job(12006)'], 'cardText': 'Allez à la boulangerie la veille pour savoir à quelle ' 'heure arrive le chef boulanger.', 'expandedCardHeader': "Voilà ce qu'il faut faire", 'expandedCardItems': [ 'Se présenter aux boulangers entre 4h et 7h du matin.', 'Demander au vendeur / à la vendeuse à quelle heure arrive le chef le matin', 'Contacter les fournisseurs de farine locaux : ils connaissent ' 'tous les boulangers du coin et sauront où il y a des ' 'embauches.', ], }) advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'custom-advice-id') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual( 'Présentez-vous au chef boulanger dès son arrivée tôt le matin', advice.title) self.assertEqual("Voilà ce qu'il faut faire", advice.expanded_card_header) self.assertTrue(advice.card_text) self.assertTrue(advice.expanded_card_items)
def test_advice_volunteer_extra_data(self): """Test that the advisor computes extra data for the "Volunteer" advice.""" project = project_pb2.Project( target_job=job_pb2.Job( code_ogr='1234', job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='75')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.volunteering_missions.insert_one({ '_id': '75', 'missions': [ { 'associationName': 'BackUp Rural' }, { 'associationName': 'Construisons Ensemble Comment Faire' }, ], }) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-volunteer', 'extraDataFieldName': 'volunteer_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual( ['BackUp Rural', 'Construisons Ensemble Comment Faire'], sorted(advice.volunteer_data.association_names))
def test_advice_association_help_extra_data(self): """Test that the advisor computes extra data for the "Find an association" advice.""" project = project_pb2.Project( target_job=job_pb2.Job( code_ogr='1234', job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.associations.insert_many([ { 'name': 'Pôle emploi' }, { 'name': 'SNC', 'filters': ['for-departement(14,15,16)'] }, { 'name': 'Ressort', 'filters': ['for-departement(69)'] }, ]) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-association-help', 'extraDataFieldName': 'associations_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('SNC', advice.associations_data.association_name)
def _save_user(user_data, is_new_user): _tick('Save user start') if is_new_user: previous_user_data = user_data else: _tick('Load old user data') previous_user_data = _get_user_data(user_data.user_id) if user_data.revision and previous_user_data.revision > user_data.revision: # Do not overwrite newer data that was saved already: just return it. return previous_user_data if not previous_user_data.registered_at.seconds: user_data.registered_at.FromDatetime(now.get()) # No need to pollute our DB with super precise timestamps. user_data.registered_at.nanos = 0 # Enable Advisor for new users. if not ADVISOR_DISABLED_FOR_TESTING: user_data.features_enabled.advisor = user_pb2.ACTIVE user_data.features_enabled.advisor_email = user_pb2.ACTIVE user_data.profile.email_days.extend( [user_pb2.MONDAY, user_pb2.WEDNESDAY, user_pb2.FRIDAY]) # Send an NPS email the next day. user_data.features_enabled.net_promoter_score_email = user_pb2.NPS_EMAIL_PENDING else: user_data.registered_at.CopyFrom(previous_user_data.registered_at) if not _is_test_user(previous_user_data): user_data.features_enabled.advisor = previous_user_data.features_enabled.advisor user_data.features_enabled.net_promoter_score_email = \ previous_user_data.features_enabled.net_promoter_score_email _tick('Unverified data zone check start') # TODO(guillaume): Check out how we could not recompute that every time gracefully. if _is_in_unverified_data_zone(user_data.profile, user_data.projects): user_data.app_not_available = True _tick('Unverified data zone check end') _populate_feature_flags(user_data) for project in user_data.projects: if project.is_incomplete: continue _tick('Process project start') rome_id = project.target_job.job_group.rome_id if not project.project_id: # Add ID, timestamp and stats to new projects project.project_id = _create_new_project_id(user_data) project.source = project_pb2.PROJECT_MANUALLY_CREATED project.created_at.FromDatetime(now.get()) _tick('Populate local stats') if not project.HasField('local_stats'): _populate_job_stats_dict({rome_id: project.local_stats}, project.mobility.city) _tick('Advisor') advisor.maybe_advise(user_data, project, _DB, parse.urljoin(flask.request.base_url, '/')[:-1]) _tick('New feedback') if not is_new_user and (project.feedback.text or project.feedback.score): previous_project = next((p for p in previous_user_data.projects if p.project_id == project.project_id), project_pb2.Project()) if project.feedback.score > 2 and not previous_project.feedback.score: score_text = \ ':sparkles: General feedback score: %s' % (':star:' * project.feedback.score) else: score_text = '' if project.feedback.text and not previous_project.feedback.text: _give_feedback(feedback_pb2.Feedback( user_id=str(user_data.user_id), project_id=str(project.project_id), feedback=project.feedback.text, source=feedback_pb2.PROJECT_FEEDBACK), extra_text=score_text) else: _tell_slack(score_text) _tick('Process project end') if not is_new_user: _assert_no_credentials_change(previous_user_data, user_data) _copy_unmodifiable_fields(previous_user_data, user_data) _populate_feature_flags(user_data) user_data.revision += 1 # Modifications on user_data after this point will not be saved. _tick('Save user') user_dict = json_format.MessageToDict(user_data) user_dict.update(_SERVER_TAG) if is_new_user: user_dict['_id'] = _get_unguessable_object_id() result = _DB.user.insert_one(user_dict) user_data.user_id = str(result.inserted_id) else: _DB.user.replace_one({'_id': _safe_object_id(user_data.user_id)}, user_dict) _tick('Return user proto') return user_data
def _save_user(user_data, is_new_user): _tick('Save user start') if is_new_user: previous_user_data = user_data else: _tick('Load old user data') previous_user_data = _get_user_data(user_data.user_id) if not previous_user_data.registered_at.seconds: user_data.registered_at.FromDatetime(now.get()) # No need to pollute our DB with super precise timestamps. user_data.registered_at.nanos = 0 # Enable Advisor for new users. if not ADVISOR_DISABLED_FOR_TESTING: user_data.features_enabled.advisor = user_pb2.ACTIVE user_data.features_enabled.advisor_email = user_pb2.ACTIVE user_data.profile.email_days.extend( [user_pb2.MONDAY, user_pb2.WEDNESDAY, user_pb2.FRIDAY]) # Send an NPS email the next day. user_data.features_enabled.net_promoter_score_email = user_pb2.NPS_EMAIL_PENDING else: user_data.registered_at.CopyFrom(previous_user_data.registered_at) if not _TEST_USER_REGEXP.search(previous_user_data.profile.email): user_data.features_enabled.advisor = previous_user_data.features_enabled.advisor user_data.features_enabled.net_promoter_score_email = \ previous_user_data.features_enabled.net_promoter_score_email _tick('Unverified data zone check start') # TODO(guillaume): Check out how we could not recompute that every time gracefully. if _is_in_unverified_data_zone(user_data.profile, user_data.projects): user_data.app_not_available = True _tick('Unverified data zone check end') _populate_feature_flags(user_data) for project in user_data.projects: if project.is_incomplete: continue _tick('Process project start') rome_id = project.target_job.job_group.rome_id if not project.project_id: # Add ID, timestamp and stats to new projects project.project_id = _create_new_project_id(user_data) project.source = project_pb2.PROJECT_MANUALLY_CREATED project.created_at.FromDatetime(now.get()) _tick('Populate local stats') if not project.HasField('local_stats'): _populate_job_stats_dict({rome_id: project.local_stats}, project.mobility.city) _tick('Advisor') advisor.maybe_advise(user_data, project, _DB) _tick('Stop actions') for current_action in project.actions: if current_action.status in _ACTION_STOPPED_STATUSES: action.stop(current_action, _DB) for past_action in project.past_actions: action.stop(past_action, _DB) for sticky_action in project.sticky_actions: if not sticky_action.HasField('stuck_at'): sticky_action.stuck_at.FromDatetime(now.get()) _tick('Process project end') if not is_new_user: _assert_no_credentials_change(previous_user_data, user_data) _copy_unmodifiable_fields(previous_user_data, user_data) _populate_feature_flags(user_data) # Modifications on user_data after this point will not be saved. _tick('Save user') user_dict = json_format.MessageToDict(user_data) user_dict.update(_SERVER_TAG) if is_new_user: user_dict['_id'] = _get_unguessable_object_id() result = _DB.user.insert_one(user_dict) user_data.user_id = str(result.inserted_id) else: _DB.user.replace_one({'_id': _safe_object_id(user_data.user_id)}, user_dict) _tick('Return user proto') return user_data
def test_seasonal_extra_data(self, mock_now): """Test that the advisor computes extra data for the seasonal-relocate advice.""" mock_now.return_value = datetime.datetime(2017, 2, 15) self.user.profile.year_of_birth = datetime.date.today().year - 28 self.user.profile.highest_degree = job_pb2.BAC_BACPRO self.user.profile.family_situation = user_pb2.SINGLE project = project_pb2.Project( mobility=geo_pb2.Location(area_type=geo_pb2.COUNTRY), job_search_length_months=7, employment_types=[ job_pb2.INTERIM, job_pb2.CDD_LESS_EQUAL_3_MONTHS ]) self.database.advice_modules.insert_one({ 'adviceId': 'seasonal-relocate', 'triggerScoringModel': 'advice-seasonal-relocate', 'extraDataFieldName': 'seasonal_data', 'isReadyForProd': True, }) self.database.seasonal_jobbing.insert_one({ '_id': 2, 'departementStats': [ { 'departementId': '06', 'departementSeasonalOffers': 900, 'jobGroups': [ { 'romeId': 'I1202', 'name': 'Professeur de piano', 'offers': 123, }, { 'romeId': 'I1203', 'name': 'Professeur de guitarre', 'offers': 120, }, ], }, { 'departementId': '2A', 'departementSeasonalOffers': 800, 'jobGroups': [ { 'romeId': 'I1202', 'name': 'Professeur de piano', 'offers': 123, }, { 'romeId': 'I1203', 'name': 'Professeur de guitarre', 'offers': 120, }, ], }, ], }) advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'seasonal-relocate') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual(2, len(advice.seasonal_data.departement_stats)) first_dep = advice.seasonal_data.departement_stats[0] self.assertEqual('06', first_dep.departement_id) self.assertEqual(900, first_dep.departement_seasonal_offers) self.assertEqual(2, len(first_dep.job_groups)) self.assertEqual('I1202', first_dep.job_groups[0].rome_id) self.assertEqual('Professeur de piano', first_dep.job_groups[0].name) self.assertEqual(123, first_dep.job_groups[0].offers)