<commit_before>"""
Model code for completion API, including django models and facade classes
wrapping progress extension models.
"""

from __future__ import absolute_import, division, print_function, unicode_literals

from django.core.cache import cache
from edx_solutions_api_integration.courses.views import (
    _get_course_progress_metrics,
)
from edx_solutions_api_integration.utils import get_aggregate_exclusion_user_ids

from lazy import lazy
from opaque_keys.edx.keys import UsageKey
from progress.models import CourseModuleCompletion

from lms.djangoapps.course_blocks.api import get_course_blocks
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview


AGGREGATE_CATEGORIES = {
    'course',
    'chapter',
    'sequential',
    'vertical',
}

IGNORE_CATEGORIES = {
    # Non-completable types
    'discussion-course',
    'group-project',
    'discussion-forum',
    'eoc-journal',

    # GP v2 categories
    'gp-v2-project',
}


class CompletionDataMixin(object):
    """
    Common calculations for completion values of courses or blocks within
    courses.

    Classes using this mixin must implement:

        * self.stats (_Stats): An object representing the earned and possible
          values.
    """

    @lazy
    def earned(self):
        """
        The number of earned completions within self.block.
        """
        return float(self.stats.earned)

    @lazy
    def possible(self):
        """
        Return the maximum number of completions the user could earn in the
        course.
        """
        return float(self.stats.possible)

    @lazy
    def ratio(self):
        """
        Return the fraction of the course completed by the user.

        Ratio is returned as a float in the range [0.0, 1.0].
        """
        if self.possible == 0.0:
            ratio = 1.0
        else:
            ratio = self.earned / self.possible
        return ratio


class CourseCompletionFacade(CompletionDataMixin, object):
    """
    Facade wrapping progress.models.StudentProgress model.
    """

    _all_stats = None

    MEAN_CACHE_KEY_FORMAT = 'completion-api-v0.mean-completion.{course_key}'

    def __init__(self, inner):
        self._inner = inner
        self._completions_in_category = {}

    @lazy
    def stats(self):
        """
        Completion statistics for the current block
        """
        return self.all_stats[self.block_key]

    @lazy
    def block_key(self):
        """
        The block key for the course.
        """
        return CourseOverview.load_from_module_store(self.course_key).location

    @lazy
    def blocks(self):
        """
        Return an
        `openedx.core.lib.block_structure.block_structure.BlockStructureBlockData`
        collection which behaves as dict that maps
        `opaque_keys.edx.locator.BlockUsageLocator`s to
        `openedx.core.lib.block_structure.block_structure.BlockData` objects
        for all blocks in the course.
        """
        return get_course_blocks(
            self.user,
            self.block_key,
        )

    def _recurse_all_stats(self, block):
        """<commit_msg>Progress API: only update CourseOverview if needed<commit_after>"""
Model code for completion API, including django models and facade classes
wrapping progress extension models.
"""

from __future__ import absolute_import, division, print_function, unicode_literals

from django.core.cache import cache
from edx_solutions_api_integration.courses.views import (
    _get_course_progress_metrics,
)
from edx_solutions_api_integration.utils import get_aggregate_exclusion_user_ids

from lazy import lazy
from opaque_keys.edx.keys import UsageKey
from progress.models import CourseModuleCompletion

from lms.djangoapps.course_blocks.api import get_course_blocks
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview


AGGREGATE_CATEGORIES = {
    'course',
    'chapter',
    'sequential',
    'vertical',
}

IGNORE_CATEGORIES = {
    # Non-completable types
    'discussion-course',
    'group-project',
    'discussion-forum',
    'eoc-journal',

    # GP v2 categories
    'gp-v2-project',
}


class CompletionDataMixin(object):
    """
    Common calculations for completion values of courses or blocks within
    courses.

    Classes using this mixin must implement:

        * self.stats (_Stats): An object representing the earned and possible
          values.
    """

    @lazy
    def earned(self):
        """
        The number of earned completions within self.block.
        """
        return float(self.stats.earned)

    @lazy
    def possible(self):
        """
        Return the maximum number of completions the user could earn in the
        course.
        """
        return float(self.stats.possible)

    @lazy
    def ratio(self):
        """
        Return the fraction of the course completed by the user.

        Ratio is returned as a float in the range [0.0, 1.0].
        """
        if self.possible == 0.0:
            ratio = 1.0
        else:
            ratio = self.earned / self.possible
        return ratio


class CourseCompletionFacade(CompletionDataMixin, object):
    """
    Facade wrapping progress.models.StudentProgress model.
    """

    _all_stats = None

    MEAN_CACHE_KEY_FORMAT = 'completion-api-v0.mean-completion.{course_key}'

    def __init__(self, inner):
        self._inner = inner
        self._completions_in_category = {}

    @lazy
    def stats(self):
        """
        Completion statistics for the current block
        """
        return self.all_stats[self.block_key]

    @lazy
    def block_key(self):
        """
        The block key for the course.
        """
        return CourseOverview.get_from_id(self.course_key).location

    @lazy
    def blocks(self):
        """
        Return an
        `openedx.core.lib.block_structure.block_structure.BlockStructureBlockData`
        collection which behaves as dict that maps
        `opaque_keys.edx.locator.BlockUsageLocator`s to
        `openedx.core.lib.block_structure.block_structure.BlockData` objects
        for all blocks in the course.
        """
        return get_course_blocks(
            self.user,
            self.block_key,
        )

    def _recurse_all_stats(self, block):
        """
{'fraction_non_alphanumeric': 0.08232868020304568, 'fraction_numerical': 0.0034898477157360407, 'mean_word_length': 2.5722379603399435, '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': '6632350', 'n_tokens_mistral': 1850, 'n_tokens_neox': 1766, 'n_words': 474}