Exemple #1
0
 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)
Exemple #2
0
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()
Exemple #3
0
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
Exemple #4
0
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())
Exemple #5
0
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)
Exemple #6
0
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)
Exemple #7
0
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
Exemple #8
0
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)
Exemple #9
0
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)
Exemple #10
0
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
Exemple #11
0
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
Exemple #12
0
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
Exemple #13
0
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
Exemple #14
0
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