def scoring_project(self, database, now=None): """Creates a new scoring.ScoringProject for this persona.""" return scoring.ScoringProject(project=self.project, user_profile=self.user_profile, features_enabled=self.features_enabled, database=database, now=now)
def project_volunteer(user_id, project_id): """Retrieve a list of job boards for a project.""" user_proto = _get_user_data(user_id) project = _get_project_data(user_proto, project_id) scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB) return scoring_project.volunteering_missions()
def compute_advices_for_project(user, project, database): """Advise on a user project. Args: user: the user's data, mainly used for their profile and features_enabled. project: the project data. It will not be modified. database: access to the MongoDB with market data. Returns: an Advices protobuffer containing a list of recommendations. """ scoring_project = scoring.ScoringProject(project, user.profile, user.features_enabled, database, now=now.get()) scores = {} advice_modules = _advice_modules(database) advice = project_pb2.Advices() for module in advice_modules: if not module.is_ready_for_prod and not user.features_enabled.alpha: continue scoring_model = scoring.get_scoring_model(module.trigger_scoring_model) if scoring_model is None: logging.warning( 'Not able to score advice "%s", the scoring model "%s" is unknown.', module.advice_id, module.trigger_scoring_model) continue if user.features_enabled.all_modules: scores[module.advice_id] = 3 else: try: scores[module.advice_id] = scoring_model.score(scoring_project) except Exception: # pylint: disable=broad-except logging.exception('Scoring "%s" crashed for:\n%s\n%s', module.trigger_scoring_model, scoring_project.user_profile, scoring_project.details) modules = sorted(advice_modules, key=lambda m: (scores.get(m.advice_id, 0), m.advice_id), reverse=True) incompatible_modules = set() for module in modules: if not scores.get(module.advice_id): # We can break as others will have 0 score as well. break if module.airtable_id in incompatible_modules and not user.features_enabled.all_modules: continue piece_of_advice = advice.advices.add() piece_of_advice.advice_id = module.advice_id piece_of_advice.num_stars = scores.get(module.advice_id) incompatible_modules.update(module.incompatible_advice_ids) _compute_extra_data(piece_of_advice, module, scoring_project) _maybe_override_advice_data(piece_of_advice, module, scoring_project) return advice
def project_commute(user_id, project_id): """Retrieve a list of commuting cities for a project.""" user_proto = _get_user_data(user_id) project = _get_project_data(user_proto, project_id) scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB) return commute_pb2.CommutingCities( cities=scoring_project.list_nearby_cities())
def project_jobboards(user_id, project_id): """Retrieve a list of job boards for a project.""" user_proto = _get_user_data(user_id) project = _get_project_data(user_proto, project_id) scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB) jobboards = scoring_project.list_jobboards() sorted_jobboards = sorted(jobboards, key=lambda j: (-len(j.filters), random.random())) return jobboard_pb2.JobBoards(job_boards=sorted_jobboards)
def project_associations(user_id, project_id): """Retrieve a list of associations for a project.""" user_proto = _get_user_data(user_id) project = _get_project_data(user_proto, project_id) scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB) associations = scoring_project.list_associations() sorted_associations = sorted(associations, key=lambda j: (-len(j.filters), random.random())) return association_pb2.Associations(associations=sorted_associations)
def list_all_tips(user, project, piece_of_advice, database, cache=None, filter_tip=None): """List all available tips for a piece of advice. Args: user: the full user info. project: the project to give tips for. piece_of_advice: the piece of advice to give tips for. database: access to the database to get modules and tips. cache: an optional dict that is used across calls to this function to cache data when scoring tips. filter_tip: a function to select which tips to keep, by default (None) keeps all of them. Returns: An iterable of tips for this module. """ if not cache: cache = {} try: module = next(m for m in _advice_modules(database) if m.advice_id == piece_of_advice.advice_id) except StopIteration: logging.warning('Advice module %s does not exist anymore', piece_of_advice.advice_id) return [] # Get tip templates. all_tip_templates = _tip_templates(database) tip_templates = filter(None, (all_tip_templates.get(t) for t in module.tip_template_ids)) # Additional filter from caller. if filter_tip: tip_templates = filter(filter_tip, tip_templates) # Filter tips. scoring_project = cache.get('scoring_project') if not scoring_project: scoring_project = scoring.ScoringProject(project, user.profile, user.features_enabled, database, now=now.get()) cache['scoring_project'] = scoring_project filtered_tips = scoring.filter_using_score(tip_templates, lambda t: t.filters, scoring_project) return filtered_tips
def project_events(user_id, project_id): """Retrieve a list of associations for a project.""" user_proto = _get_user_data(user_id) project = _get_project_data(user_proto, project_id) scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB, now=now.get()) events = scoring_project.list_events() sorted_events = sorted(events, key=lambda j: (j.start_date, -len(j.filters), random.random())) return event_pb2.Events(events=sorted_events)
def _get_expanded_card_data(user_proto, project, advice_id): module = advisor.get_advice_module(advice_id, _DB) if not module or not module.trigger_scoring_model: flask.abort(404, 'Le module "{}" n\'existe pas'.format(advice_id)) model = scoring.get_scoring_model(module.trigger_scoring_model) if not model or not hasattr(model, 'get_expanded_card_data'): flask.abort( 404, 'Le module "{}" n\'a pas de données supplémentaires'.format( advice_id)) scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB, now=now.get()) return model.get_expanded_card_data(scoring_project)
def project_resume_tips(user_id, project_id): """Retrieve a list of resume tips for a project.""" user_proto = _get_user_data(user_id) project = _get_project_data(user_proto, project_id) scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB) resume_tips = scoring_project.list_application_tips() sorted_tips = sorted(resume_tips, key=lambda t: (-len(t.filters), random.random())) tips_proto = application_pb2.ResumeTips( qualities=[ t for t in sorted_tips if t.type == application_pb2.QUALITY ], improvements=[ t for t in sorted_tips if t.type == application_pb2.CV_IMPROVEMENT ]) for tip in itertools.chain(tips_proto.qualities, tips_proto.improvements): tip.ClearField('type') return tips_proto
def project_interview_tips(user_id, project_id): """Retrieve a list of interview tips for a project.""" user_proto = _get_user_data(user_id) project = _get_project_data(user_proto, project_id) scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB) interview_tips = scoring_project.list_application_tips() sorted_tips = sorted(interview_tips, key=lambda t: (-len(t.filters), random.random())) tips_proto = application_pb2.InterviewTips( qualities=[ t for t in sorted_tips if t.type == application_pb2.QUALITY ], preparations=[ t for t in sorted_tips if t.type == application_pb2.INTERVIEW_PREPARATION ]) for tip in itertools.chain(tips_proto.qualities, tips_proto.preparations): tip.ClearField('type') return tips_proto
def _maybe_recommend_advice(user, project, database): if user.features_enabled.advisor != user_pb2.ACTIVE or project.advices: return False scoring_project = scoring.ScoringProject(project, user.profile, user.features_enabled, database) scores = {} advice_modules = _advice_modules(database) for module in advice_modules: if not module.is_ready_for_prod and not user.features_enabled.alpha: continue scoring_model = scoring.get_scoring_model(module.trigger_scoring_model) if scoring_model is None: logging.warning( 'Not able to score advice "%s", the scoring model "%s" is unknown.', module.advice_id, module.trigger_scoring_model) continue scores[module.advice_id] = scoring_model.score(scoring_project).score modules = sorted(advice_modules, key=lambda m: (scores.get(m.advice_id, 0), m.advice_id), reverse=True) incompatible_modules = set() for module in modules: if not scores.get(module.advice_id): # We can break as others will have 0 score as well. break if module.airtable_id in incompatible_modules: continue piece_of_advice = project.advices.add() piece_of_advice.advice_id = module.advice_id piece_of_advice.status = project_pb2.ADVICE_RECOMMENDED piece_of_advice.num_stars = scores.get(module.advice_id) incompatible_modules.update(module.incompatible_advice_ids) _compute_extra_data(piece_of_advice, module, scoring_project) return True
def _add_actions_to_project(user_proto, project, num_adds, use_white_chantiers=False): if num_adds < 0: return False all_chantiers = _chantiers() if use_white_chantiers: activated_chantiers = _white_chantier_ids() else: activated_chantiers = set( chantier_id for chantier_id, activated in project.activated_chantiers.items() if activated and chantier_id in all_chantiers) if not activated_chantiers: logging.warning('No activated chantiers') return False # List all action template IDs for which we already had an action in the # near past. now_instant = now.get() still_hot_action_template_ids = set() for hot_action in itertools.chain(project.actions, project.past_actions, project.sticky_actions): if (hot_action.HasField('end_of_cool_down') and hot_action.end_of_cool_down.ToDatetime() < now_instant): continue still_hot_action_template_ids.add(hot_action.action_template_id) # List all action templates that are at least in one of the activated # chantiers. actions_pool = [ a for action_template_id, a in action.templates(_DB).items() # Do not add an action that was already taken. if action_template_id not in still_hot_action_template_ids and # Only add actions that are meant for these chantiers. activated_chantiers & set(a.chantiers) ] # Filter action templates using the filters field. scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, _DB) filtered_actions_pool = scoring.filter_using_score(actions_pool, lambda a: a.filters, scoring_project) # Split action templates by priority. pools = collections.defaultdict(list) for filtered_action in filtered_actions_pool: pools[filtered_action.priority_level].append(filtered_action) if not pools: logging.warning( 'No action template would match:\n' ' - %d activated chantiers\n' ' - %d total action templates\n' ' - %d action templates still hot\n' ' - %d before filtering', len(activated_chantiers), len(action.templates(_DB)), len(still_hot_action_template_ids), len(actions_pool)) return False added = False for priority in sorted(pools.keys(), reverse=True): pool = pools[priority] # Pick the number of actions to add if enough. if num_adds == 0: return added if len(pool) > num_adds: pool = random.sample(pool, num_adds) num_adds = 0 else: num_adds -= len(pool) random.shuffle(pool) for template in pool: added = True action.instantiate(project.actions.add(), user_proto, project, template, activated_chantiers, _DB, _chantiers()) return added
def instantiate(action, user_proto, project, template, database, for_email=False): """Instantiate a newly created action from a template. Args: action: the action to be populated from the template. user_proto: the whole user data. project: the whole project data. template: the action template to instantiate. database: a MongoDB client to get stats and info. for_email: whether the action is to be sent in an email. Returns: the populated action for chaining. """ action.action_id = '{}-{}-{:x}-{:x}'.format(project.project_id, template.action_template_id, round(time.time()), random.randrange(0x10000)) action.action_template_id = template.action_template_id action.title = template.title action.title_feminine = template.title_feminine action.short_description = template.short_description action.short_description_feminine = template.short_description_feminine scoring_project = scoring.ScoringProject(project, user_proto.profile, user_proto.features_enabled, database) action.link = scoring_project.populate_template(template.link) action.how_to = template.how_to action.status = action_pb2.ACTION_UNREAD action.created_at.FromDatetime(now.get()) action.image_url = template.image_url if for_email: if template.email_title: action.title = template.email_title action.link_label = template.email_link_label action.keyword = template.email_subject_keyword if (template.special_generator == action_pb2.LA_BONNE_BOITE and user_proto.features_enabled.lbb_integration == user_pb2.ACTIVE): _get_company_from_lbb(project, action.apply_to_company) if action.apply_to_company.name: title_match = _ANY_COMPANY_REGEXP.match(action.title) if title_match: company_name = action.apply_to_company.name if action.apply_to_company.city_name: company_name += ' ({})'.format( action.apply_to_company.city_name) else: logging.warning( 'LBB Action %s is missing a city name (user %s).', action.action_id, user_proto.user_id) action.title = title_match.group( 1) + " l'entreprise : " + company_name else: logging.warning( 'LBB Action %s does not have a title that can be updated (user %s).', action.action_id, user_proto.user_id) return action