Esempio n. 1
0
    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)
Esempio n. 2
0
    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')
Esempio n. 3
0
    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')
Esempio n. 4
0
# 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.
Esempio n. 5
0
 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')
Esempio n. 6
0
 def __init__(self) -> None:
     super().__init__()
     self._db: proto.MongoCachedCollection[association_pb2.VolunteeringMissions] = \
         proto.MongoCachedCollection(association_pb2.VolunteeringMissions, 'local_missions')
Esempio n. 7
0
 def __init__(self) -> None:
     super().__init__()
     self._salon_db: proto.MongoCachedCollection[online_salon_pb2.OnlineSalon] = \
         proto.MongoCachedCollection(online_salon_pb2.OnlineSalon, 'online_salons')
Esempio n. 8
0
 def __init__(self):
     super(_AdviceEventScoringModel, self).__init__()
     self._db = proto.MongoCachedCollection(event_pb2.Event, 'events')
Esempio n. 9
0
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."""
Esempio n. 10
0
 def __init__(self) -> None:
     super().__init__()
     self._db: proto.MongoCachedCollection[skill_pb2.JobSkills] = \
         proto.MongoCachedCollection(skill_pb2.JobSkills, 'skills_for_future')
Esempio n. 11
0
"""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
Esempio n. 12
0
        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:
Esempio n. 13
0
# 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]+')
Esempio n. 14
0
 def __init__(self, network_level):
     super(_ImproveYourNetworkScoringModel, self).__init__()
     self._db = proto.MongoCachedCollection(network_pb2.ContactLeadTemplate,
                                            'contact_lead')
     self._network_level = network_level
Esempio n. 15
0
    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:
Esempio n. 16
0
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,
    )
Esempio n. 17
0
 def __init__(self) -> None:
     super().__init__()
     self._db: proto.MongoCachedCollection[reorient_jobbing_pb2.LocalJobbingStats] = \
         proto.MongoCachedCollection(reorient_jobbing_pb2.LocalJobbingStats, 'reorient_jobbing')
Esempio n. 18
0
 def __init__(self):
     super(_AdviceReorientJobbing, self).__init__()
     self._db = proto.MongoCachedCollection(
         reorient_jobbing_pb2.LocalJobbingStats, 'reorient_jobbing')
Esempio n. 19
0
                    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(
Esempio n. 20
0
 def __init__(self):
     super(_AdviceJobBoards, self).__init__(user_pb2.NO_OFFERS)
     self._db = proto.MongoCachedCollection(jobboard_pb2.JobBoard,
                                            'jobboards')
Esempio n. 21
0
        ) < 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.
Esempio n. 23
0
 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
Esempio n. 24
0
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()
Esempio n. 25
0
# 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:
Esempio n. 26
0
 def __init__(self):
     super(_AdviceCivicService, self).__init__()
     self._db = proto.MongoCachedCollection(
         association_pb2.VolunteeringMissions, 'local_missions')
Esempio n. 27
0
 def __init__(self) -> None:
     super().__init__()
     self._db: proto.MongoCachedCollection[event_pb2.Event] = \
         proto.MongoCachedCollection(event_pb2.Event, 'events')
Esempio n. 28
0
"""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:
Esempio n. 29
0
"""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
Esempio n. 30
0
"""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',