<commit_before>from datetime import datetime
from decimal import Decimal
from django.core.cache import caches
from django_statsd.clients import statsd
import logging

from . import exceptions, settings
from .cache import get_basket_uat

import suds.client
try:
    import soap
except ImportError:
    soap = None

try:
    from raven.contrib.django.raven_compat.models import client as raven_client
except ImportError:
    raven_client = None



logger = logging.getLogger(__name__)
cache = caches[settings.CCH_CACHE_BACKEND]


class CCHTaxCalculator(object):
    precision = settings.CCH_PRECISION
    wsdl = settings.CCH_WSDL
    entity_id = settings.CCH_ENTITY
    divsion_id = settings.CCH_DIVISION


    def estimate_taxes(self, basket, shipping_address):
        cache_key = '%s_tax_%s_%s_%s_%s' % (__name__, basket.id, get_basket_uat(basket), shipping_address.state, shipping_address.postcode)
        cached_basket = cache.get(cache_key)
        statsd.incr('cch.estimate')
        if cached_basket is not None:
            return cached_basket
        statsd.incr('cch.estimate-cache-miss')
        self.apply_taxes(basket, shipping_address)
        cache.set(cache_key, basket, settings.CCH_ESTIMATE_CACHE_SECONDS)
        return basket


    def apply_taxes(self, basket, shipping_address, ignore_cch_fail=False):
        response = self._get_response(basket, shipping_address, ignore_cch_fail)
        if not ignore_cch_fail:
            self._check_response_messages(response)

        # Apply taxes to line items
        for line in basket.all_lines():
            line_id = str(line.id)

            taxes = None
            if response and response.LineItemTaxes:
                taxes = next(filter(lambda item: item.ID == line_id, response.LineItemTaxes.LineItemTax))

            # Taxes come in two forms: quantity and percentage based
            # We need to handle both of those here. The tricky part is that CCH returns data
            # for an entire line item (inclusive quantity), but Oscar needs the tax info for
            # each unit in the line (exclusive quantity). So, we use the details provided to
            # derive the per-unit taxes before applying them.
            line.purchase_info.price.clear_taxes()
            if taxes:
                for tax in taxes.TaxDetails.TaxDetail:
                    unit_fee = Decimal(str(tax.FeeApplied)) / line.quantity
                    unit_tax = Decimal(str(tax.TaxApplied)) / line.quantity
                    line.purchase_info.price.add_tax(
                        authority_name=tax.AuthorityName,
                        tax_name=tax.TaxName,
                        tax_applied=unit_tax,
                        fee_applied=unit_fee)
                # Check our work and make sure the total we arrived at matches the total CCH gave us
                total_line_tax = (line.purchase_info.price.tax * line.quantity).quantize(self.precision)
                total_applied_tax = Decimal(taxes.TotalTaxApplied).quantize(self.precision)
                if total_applied_tax != total_line_tax:
                    statsd.incr('cch.miscalculation')
                    raise RuntimeError("Taxation miscalculation occurred! Details sum to %s, which doesn't match given sum of %s" % (total_line_tax, taxes.TotalTaxApplied))
            else:
                line.purchase_info.price.tax = Decimal('0.00')

        return response


    def _get_response(self, basket, shipping_address, ignore_cch_fail=False):
        """Fetch CCH tax data for the given basket and shipping address"""
        response = None
        try:
            order = self._build_order(basket, shipping_address)
            response = self.client.service.CalculateRequest(self.entity_id, self.divsion_id, order)
        except Exception as e: # It's unclear what exceptions suds will actually throw. There is no suds base exception<commit_msg>Add some more statsd measurements to the Calculator<commit_after>from datetime import datetime
from decimal import Decimal
from django.core.cache import caches
from django_statsd.clients import statsd
import logging

from . import exceptions, settings
from .cache import get_basket_uat

import suds.client
try:
    import soap
except ImportError:
    soap = None

try:
    from raven.contrib.django.raven_compat.models import client as raven_client
except ImportError:
    raven_client = None



logger = logging.getLogger(__name__)
cache = caches[settings.CCH_CACHE_BACKEND]


class CCHTaxCalculator(object):
    precision = settings.CCH_PRECISION
    wsdl = settings.CCH_WSDL
    entity_id = settings.CCH_ENTITY
    divsion_id = settings.CCH_DIVISION


    def estimate_taxes(self, basket, shipping_address):
        cache_key = '%s_tax_%s_%s_%s_%s' % (__name__, basket.id, get_basket_uat(basket), shipping_address.state, shipping_address.postcode)
        cached_basket = cache.get(cache_key)
        statsd.incr('cch.estimate')
        if cached_basket is not None:
            statsd.incr('cch.estimate-cache-hit')
            return cached_basket
        statsd.incr('cch.estimate-cache-miss')
        self.apply_taxes(basket, shipping_address)
        cache.set(cache_key, basket, settings.CCH_ESTIMATE_CACHE_SECONDS)
        return basket


    def apply_taxes(self, basket, shipping_address, ignore_cch_fail=False):
        with statsd.timer('cch.apply-time'):
            response = self._get_response(basket, shipping_address, ignore_cch_fail)

        if not ignore_cch_fail:
            self._check_response_messages(response)

        # Apply taxes to line items
        for line in basket.all_lines():
            line_id = str(line.id)

            taxes = None
            if response and response.LineItemTaxes:
                taxes = next(filter(lambda item: item.ID == line_id, response.LineItemTaxes.LineItemTax))

            # Taxes come in two forms: quantity and percentage based
            # We need to handle both of those here. The tricky part is that CCH returns data
            # for an entire line item (inclusive quantity), but Oscar needs the tax info for
            # each unit in the line (exclusive quantity). So, we use the details provided to
            # derive the per-unit taxes before applying them.
            line.purchase_info.price.clear_taxes()
            if taxes:
                for tax in taxes.TaxDetails.TaxDetail:
                    unit_fee = Decimal(str(tax.FeeApplied)) / line.quantity
                    unit_tax = Decimal(str(tax.TaxApplied)) / line.quantity
                    line.purchase_info.price.add_tax(
                        authority_name=tax.AuthorityName,
                        tax_name=tax.TaxName,
                        tax_applied=unit_tax,
                        fee_applied=unit_fee)
                # Check our work and make sure the total we arrived at matches the total CCH gave us
                total_line_tax = (line.purchase_info.price.tax * line.quantity).quantize(self.precision)
                total_applied_tax = Decimal(taxes.TotalTaxApplied).quantize(self.precision)
                if total_applied_tax != total_line_tax:
                    statsd.incr('cch.miscalculation')
                    raise RuntimeError("Taxation miscalculation occurred! Details sum to %s, which doesn't match given sum of %s" % (total_line_tax, taxes.TotalTaxApplied))
            else:
                line.purchase_info.price.tax = Decimal('0.00')

        return response


    def _get_response(self, basket, shipping_address, ignore_cch_fail=False):
        """Fetch CCH tax data for the given basket and shipping address"""
        response = None
        try:
            order = self._build_order(basket, shipping_address)
            response = self.client.service.CalculateRequest(self.entity_id, self.divsion_id, order)
            statsd.incr('cch.apply-success')
        except Exception as e: # It's unclear what exceptions suds will actually throw. There is no suds base exception
{'fraction_non_alphanumeric': 0.07226048841577959, 'fraction_numerical': 0.0007514088916718848, 'mean_word_length': 2.5414634146341464, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, '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': '461168', 'n_tokens_mistral': 2316, 'n_tokens_neox': 2207, 'n_words': 600}