<commit_before># coding: utf8

from datetime import datetime
import collections
import logging
import unidecode

from flask import url_for
import elasticsearch
import elasticsearch.exceptions
from slugify import slugify
from backports.functools_lru_cache import lru_cache

from labonneboite.common import geocoding
from labonneboite.common import mapping as mapping_util
from labonneboite.common import sorting
from labonneboite.common import autocomplete
from labonneboite.common import hiring_type_util
from labonneboite.common.pagination import OFFICES_PER_PAGE
from labonneboite.common.models import Office
from labonneboite.common.es import Elasticsearch
from labonneboite.conf import settings
from labonneboite.common.rome_mobilities import ROME_MOBILITIES

logger = logging.getLogger('main')


PUBLIC_ALL = 0
PUBLIC_JUNIOR = 1
PUBLIC_SENIOR = 2
PUBLIC_HANDICAP = 3
PUBLIC_CHOICES = [PUBLIC_ALL, PUBLIC_JUNIOR, PUBLIC_SENIOR, PUBLIC_HANDICAP]

KEY_TO_LABEL_DISTANCES = {
    u'*-10.0': u'less_10_km',
    u'*-30.0': u'less_30_km',
    u'*-50.0': u'less_50_km',
    u'*-100.0': u'less_100_km',
    u'*-3000.0': u'france',
}

FILTERS = ['naf', 'headcount', 'flag_alternance', 'distance']
DISTANCE_FILTER_MAX = 3000



class Location(object):
    def __init__(self, latitude, longitude, commune_id=None):
        self.latitude = latitude
        self.longitude = longitude
        self.commune_id = commune_id

    def __repr__(self):
        return "lon: {} - lat: {} - commune_id: {}".format(self.longitude, self.latitude, self.commune_id)


class CityLocation(object):

    def __init__(self, slug, zipcode):
        self.slug = slug
        self.zipcode = zipcode
        # Location attribute may be None if slug/zipcode combination is incorrect
        self.location = None

        city = geocoding.get_city_by_zipcode(self.zipcode, self.slug)
        if not city:
            logger.debug("unable to retrieve a city for zipcode `%s` and slug `%s`", self.zipcode, self.slug)
        else:
            coordinates = city['coords']
            self.location = Location(coordinates['lat'], coordinates['lon'])

    @property
    def is_location_correct(self):
        return self.location is not None

    @property
    def name(self):
        return self.slug.replace('-', ' ').capitalize()

    @property
    def full_name(self):
        return '%s (%s)' % (self.name, self.zipcode)


class Fetcher(object):

    def __init__(self, search_location, rome=None, distance=None, sort=None, hiring_type=None,
                 from_number=1, to_number=OFFICES_PER_PAGE, flag_alternance=None,
                 public=None, headcount=None, naf=None, naf_codes=None,
                 aggregate_by=None, departments=None, **kwargs):
        """
        This constructor takes many arguments; the goal is to reduce this list
        and to never rely on kwargs.

        Args:
            search_location (Location)
        """
        self.location = search_location

        self.rome = rome
        self.distance = distance
        self.sort = sort
        self.hiring_type = hiring_type

        # Pagination.
        self.from_number = from_number
        self.to_number = to_number

        # Flags.
        self.flag_alternance = flag_alternance
        self.public = public

        # Headcount.
        try:
            self.headcount = int(headcount)
        except (TypeError, ValueError):
            self.headcount = settings.HEADCOUNT_WHATEVER

        # Empty list is needed to handle companies with ROME not related to their NAF
        self.naf = naf
        self.naf_codes = naf_codes or {}
        if self.naf and not self.naf_codes:
            self.naf_codes = mapping_util.map_romes_to_nafs([self.rome], [self.naf])

        # Aggregate_by
        self.aggregate_by = aggregate_by

        # Other properties.
        self.alternative_rome_codes = {}
        self.alternative_distances = collections.OrderedDict()
        self.company_count = None
        self.departments = departments

    @property
    def flag_handicap(self):
        return self.public == PUBLIC_HANDICAP
    @property
    def flag_junior(self):
        return self.public == PUBLIC_JUNIOR
    @property
    def flag_senior(self):
        return self.public == PUBLIC_SENIOR

    def compute_url(self, query_string):
        if not self.company_count >= 1:
            # Always return home URL if zero results
            # (requested by PE.fr)
            return url_for('root.home', _external=True, **query_string)
        elif self.location.commune_id and self.rome:
            # preserve parameters from original API request
            if self.naf_codes:
                query_string['naf'] = self.naf_codes[0]
            query_string.update({
                'from': self.from_number,
                'to': self.to_number,
                'sort': self.sort,
                'd': self.distance,
                'h': self.headcount,
                'p': self.public,
                'f_a': self.flag_alternance,
            })

            return url_for(
                'search.results_by_commune_and_rome',
                commune_id=self.location.commune_id,
                rome_id=self.rome,
                _external=True,
                **query_string
            )
        else:
            # In case of search by longitude+latitude,
            # return home URL since we do not have a URL ready yet.
            # FIXME implement this URL at some point.
            return url_for('root.home', _external=True, **query_string)


    def update_aggregations(self, aggregations):
        if self.headcount and 'headcount' in aggregations:
            aggregations['headcount'] = self.get_headcount_aggregations()
        if self.flag_alternance and 'flag_alternance' in aggregations:
            aggregations['contract'] = self.get_headcount_aggregations()
        if self.distance != DISTANCE_FILTER_MAX and 'distance' in aggregations:
            aggregations['distance'] = self.get_distance_aggregations()
        if self.naf and 'naf' in aggregations:
            aggregations['naf'] = self.get_naf_aggregations()

    def _get_company_count(self, rome_code, distance):

        return count_companies(
            self.naf_codes,
            rome_code,
            self.location.latitude,
            self.location.longitude,
            distance,
            flag_alternance=self.flag_alternance,
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=self.headcount,
            departments=self.departments,
            aggregate_by=None,
        )

    def get_naf_aggregations(self):
        _, _, aggregations = fetch_companies(
            {}, # No naf filter
            self.rome,
            self.location.latitude,
            self.location.longitude,
            self.distance,
            aggregate_by=["naf"], # Only naf aggregate
            flag_alternance=self.flag_alternance,
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=self.headcount,
            departments=self.departments,
        )
        return aggregations['naf']

    def get_headcount_aggregations(self):
        _, _, aggregations = fetch_companies(
            self.naf_codes,
            self.rome,
            self.location.latitude,
            self.location.longitude,
            self.distance,
            aggregate_by=["headcount"], # Only headcount aggregate
            flag_alternance=self.flag_alternance,
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=settings.HEADCOUNT_WHATEVER, # No headcount filter
            departments=self.departments,
        )
        return aggregations['headcount']

    def get_contract_aggregations(self):
        _, _, aggregations = fetch_companies(
            self.naf_codes,
            self.rome,
            self.location.latitude,
            self.location.longitude,
            self.distance,
            aggregate_by=['flag_alternance'], # Only flag_alternance aggregate
            flag_alternance=None, # No flag_alternance filter
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=self.headcount,
            departments=self.departments,
        )
        return aggregations['contract']


    def get_distance_aggregations(self):
        _, _, aggregations = fetch_companies(
            self.naf_codes,
            self.rome,
            self.location.latitude,
            self.location.longitude,
            DISTANCE_FILTER_MAX, # France
            aggregate_by=["distance"],
            flag_alternance=self.flag_alternance,
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=self.headcount,
            departments=self.departments,
        )<commit_msg>hotfix : pass hiring_type parameter correctly everywhere<commit_after># coding: utf8

from datetime import datetime
import collections
import logging
import unidecode

from flask import url_for
import elasticsearch
import elasticsearch.exceptions
from slugify import slugify
from backports.functools_lru_cache import lru_cache

from labonneboite.common import geocoding
from labonneboite.common import mapping as mapping_util
from labonneboite.common import sorting
from labonneboite.common import autocomplete
from labonneboite.common import hiring_type_util
from labonneboite.common.pagination import OFFICES_PER_PAGE
from labonneboite.common.models import Office
from labonneboite.common.es import Elasticsearch
from labonneboite.conf import settings
from labonneboite.common.rome_mobilities import ROME_MOBILITIES

logger = logging.getLogger('main')


PUBLIC_ALL = 0
PUBLIC_JUNIOR = 1
PUBLIC_SENIOR = 2
PUBLIC_HANDICAP = 3
PUBLIC_CHOICES = [PUBLIC_ALL, PUBLIC_JUNIOR, PUBLIC_SENIOR, PUBLIC_HANDICAP]

KEY_TO_LABEL_DISTANCES = {
    u'*-10.0': u'less_10_km',
    u'*-30.0': u'less_30_km',
    u'*-50.0': u'less_50_km',
    u'*-100.0': u'less_100_km',
    u'*-3000.0': u'france',
}

FILTERS = ['naf', 'headcount', 'flag_alternance', 'distance']
DISTANCE_FILTER_MAX = 3000



class Location(object):
    def __init__(self, latitude, longitude, commune_id=None):
        self.latitude = latitude
        self.longitude = longitude
        self.commune_id = commune_id

    def __repr__(self):
        return "lon: {} - lat: {} - commune_id: {}".format(self.longitude, self.latitude, self.commune_id)


class CityLocation(object):

    def __init__(self, slug, zipcode):
        self.slug = slug
        self.zipcode = zipcode
        # Location attribute may be None if slug/zipcode combination is incorrect
        self.location = None

        city = geocoding.get_city_by_zipcode(self.zipcode, self.slug)
        if not city:
            logger.debug("unable to retrieve a city for zipcode `%s` and slug `%s`", self.zipcode, self.slug)
        else:
            coordinates = city['coords']
            self.location = Location(coordinates['lat'], coordinates['lon'])

    @property
    def is_location_correct(self):
        return self.location is not None

    @property
    def name(self):
        return self.slug.replace('-', ' ').capitalize()

    @property
    def full_name(self):
        return '%s (%s)' % (self.name, self.zipcode)


class Fetcher(object):

    def __init__(self, search_location, rome=None, distance=None, sort=None, hiring_type=None,
                 from_number=1, to_number=OFFICES_PER_PAGE, flag_alternance=None,
                 public=None, headcount=None, naf=None, naf_codes=None,
                 aggregate_by=None, departments=None, **kwargs):
        """
        This constructor takes many arguments; the goal is to reduce this list
        and to never rely on kwargs.

        Args:
            search_location (Location)
        """
        self.location = search_location

        self.rome = rome
        self.distance = distance
        self.sort = sort
        self.hiring_type = hiring_type

        # Pagination.
        self.from_number = from_number
        self.to_number = to_number

        # Flags.
        self.flag_alternance = flag_alternance
        self.public = public

        # Headcount.
        try:
            self.headcount = int(headcount)
        except (TypeError, ValueError):
            self.headcount = settings.HEADCOUNT_WHATEVER

        # Empty list is needed to handle companies with ROME not related to their NAF
        self.naf = naf
        self.naf_codes = naf_codes or {}
        if self.naf and not self.naf_codes:
            self.naf_codes = mapping_util.map_romes_to_nafs([self.rome], [self.naf])

        # Aggregate_by
        self.aggregate_by = aggregate_by

        # Other properties.
        self.alternative_rome_codes = {}
        self.alternative_distances = collections.OrderedDict()
        self.company_count = None
        self.departments = departments

    @property
    def flag_handicap(self):
        return self.public == PUBLIC_HANDICAP
    @property
    def flag_junior(self):
        return self.public == PUBLIC_JUNIOR
    @property
    def flag_senior(self):
        return self.public == PUBLIC_SENIOR

    def compute_url(self, query_string):
        if not self.company_count >= 1:
            # Always return home URL if zero results
            # (requested by PE.fr)
            return url_for('root.home', _external=True, **query_string)
        elif self.location.commune_id and self.rome:
            # preserve parameters from original API request
            if self.naf_codes:
                query_string['naf'] = self.naf_codes[0]
            query_string.update({
                'from': self.from_number,
                'to': self.to_number,
                'sort': self.sort,
                'd': self.distance,
                'h': self.headcount,
                'p': self.public,
                'f_a': self.flag_alternance,
            })

            return url_for(
                'search.results_by_commune_and_rome',
                commune_id=self.location.commune_id,
                rome_id=self.rome,
                _external=True,
                **query_string
            )
        else:
            # In case of search by longitude+latitude,
            # return home URL since we do not have a URL ready yet.
            # FIXME implement this URL at some point.
            return url_for('root.home', _external=True, **query_string)


    def update_aggregations(self, aggregations):
        if self.headcount and 'headcount' in aggregations:
            aggregations['headcount'] = self.get_headcount_aggregations()
        if self.flag_alternance and 'flag_alternance' in aggregations:
            aggregations['contract'] = self.get_headcount_aggregations()
        if self.distance != DISTANCE_FILTER_MAX and 'distance' in aggregations:
            aggregations['distance'] = self.get_distance_aggregations()
        if self.naf and 'naf' in aggregations:
            aggregations['naf'] = self.get_naf_aggregations()

    def _get_company_count(self, rome_code, distance):

        return count_companies(
            self.naf_codes,
            rome_code,
            self.location.latitude,
            self.location.longitude,
            distance,
            flag_alternance=self.flag_alternance,
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=self.headcount,
            hiring_type=self.hiring_type,
            departments=self.departments,
            aggregate_by=None,
        )

    def get_naf_aggregations(self):
        _, _, aggregations = fetch_companies(
            {}, # No naf filter
            self.rome,
            self.location.latitude,
            self.location.longitude,
            self.distance,
            aggregate_by=["naf"], # Only naf aggregate
            flag_alternance=self.flag_alternance,
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=self.headcount,
            hiring_type=self.hiring_type,
            departments=self.departments,
        )
        return aggregations['naf']

    def get_headcount_aggregations(self):
        _, _, aggregations = fetch_companies(
            self.naf_codes,
            self.rome,
            self.location.latitude,
            self.location.longitude,
            self.distance,
            aggregate_by=["headcount"], # Only headcount aggregate
            flag_alternance=self.flag_alternance,
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=settings.HEADCOUNT_WHATEVER, # No headcount filter
            hiring_type=self.hiring_type,
            departments=self.departments,
        )
        return aggregations['headcount']

    def get_contract_aggregations(self):
        _, _, aggregations = fetch_companies(
            self.naf_codes,
            self.rome,
            self.location.latitude,
            self.location.longitude,
            self.distance,
            aggregate_by=['flag_alternance'], # Only flag_alternance aggregate
            flag_alternance=None, # No flag_alternance filter
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=self.headcount,
            hiring_type=self.hiring_type,
            departments=self.departments,
        )
        return aggregations['contract']


    def get_distance_aggregations(self):
        _, _, aggregations = fetch_companies(
            self.naf_codes,
            self.rome,
            self.location.latitude,
            self.location.longitude,
            DISTANCE_FILTER_MAX, # France
            aggregate_by=["distance"],
            flag_alternance=self.flag_alternance,
            flag_junior=self.flag_junior,
            flag_senior=self.flag_senior,
            flag_handicap=self.flag_handicap,
            headcount=self.headcount,
            hiring_type=self.hiring_type,
            departments=self.departments,
        )
{'fraction_non_alphanumeric': 0.0849500135098622, 'fraction_numerical': 0.004215077006214537, 'mean_word_length': 2.2782993799822853, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5753762', 'n_tokens_mistral': 5581, 'n_tokens_neox': 5328, 'n_words': 1110}