We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Update docstrings in the Python backend code to follow the style guide.
We've been a bit lax about adding docstrings to code in the backend. Instead, we should be following the style guide: https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments -- docstrings should define args, return values, exceptions, etc. The aim of this issue is to ensure that the backend code is properly documented; we will tackle this on a file-by-file basis.

Note: if you're a new contributor, this issue is a particularly good introductory one to tackle, since it will require reading, understanding and documenting the purpose of a file in the codebase, and help you get acquainted with how the Oppia backend is structured. It's important that you're able to communicate and write clearly, though!

Some useful tips:
-  Please follow our [style guide](https://github.com/oppia/oppia/wiki/Coding-style-guide#general) (and also the [Google Python style guide](https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments)) when writing docstrings. Make sure to follow the patterns established there as closely as possible, including spacing, capitalization and punctuation. Also, it's a good idea to look at some of the existing files (see below) to get a sense of the correct format -- we recommend following this closely in order to maintain uniformity across the codebase.
- If applicable, make sure that the docstrings for all functions correctly specify the args, return values, etc. If a function has no args, you can omit the Args section; if it has no return value, you can omit the Returns section.
- If a docstring is not present in the code, or the existing one is too vague for you to understand what's going on just by looking at it, then it should be rewritten to be more clear. You'll often need to read the code as well, and do a grep on the callsites to see how it's being used, in order to get a clear understanding of what it's doing. This is good practice for learning how to navigate the codebase, and is an intended part of this project.
- If you feel that parts of the codebase are unclear or incorrectly factored, please feel free to bring this up! The whole point of docstrings is to improve code health and accessibility for new developers, so if you spot other things that can be done in service of that aim, we should also look into doing that.

**Completed:**
- [x] core.controllers.base @shaz13 
- [x] core.controllers.cron @saeedjassani
- [x] core.controllers.reader @96mohitm
- [x] core.domain.activity_domain @andromfins
- [x] core.domain.activity_services @souravsingh
- [x] core.domain.classifier_services @andromfins 
- [x] core.domain.collection_domain @domevit
- [x] core.domain.collection_services @kahkhang 
- [x] core.domain.config_services @vibhor98 
- [x] core.domain.email_manager @Oishikatta 
- [x] core.domain.exp_domain @domevit
- [x] core.domain.exp_services @DubeySandeep
- [x] core.domain.feedback_domain @terrameijar 
- [x] core.domain.feedback_jobs_continuous
- [x] core.domain.feedback_services @andimeo
- [x] core.domain.fs_domain @kartik-25 
- [x] core.domain.html_cleaner @seanlip
- [x] core.domain.param_domain @rohan-gulati
- [x] core.domain.rating_services @BojOnTheBeat 
- [x] core.domain.rights_manager @ayejay
- [x] core.domain.stats_domain @Arunabh98
- [x] core.domain.stats_jobs_continuous @Arunabh98 
- [x] core.domain.stats_jobs_one_off @zpuller
- [x] core.domain.stats_services @manoj-makkuboy 
- [x] core.domain.subscription_services @vibhor98 
- [x] core.domain.summary_services @domevit 
- [x] core.domain.user_jobs_continuous @shubha1593 
- [x] core.domain.user_services @Nalinc 
- [x] core.domain.value_generators_domain @meetsha 
- [x] core.jobs @brianrodri
- [x] core.platform.app_identity.gae_app_identity_services @ubhisat
- [x] core.platform.models @pranavsid98 
- [x] core.platform.transactions.gae_transaction_services @ubhisat
- [x] core.storage.base_model.gae_models @vojtechjelinek
- [x] core.storage.collection.gae_models @kaggis
- [x] core.storage.email.gae_models @rajan-garg
- [x] core.storage.exploration.gae_models @kirankonduru
- [x] core.storage.feedback.gae_models @naveenshukla
- [x] core.storage.file.gae_models @Ian91 
- [x] core.storage.job.gae_models @sasacocic
- [x] core.storage.statistics.gae_models @abhishekarya286
- [x] core.storage.user.gae_models @Anthony-Alridge 
- [x] core.tests.test_util_jobs @seanlip
- [x] core.tests.test_utils @vibhor98
- [x] utils @yashLadha 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `core/domain/stats_jobs_one_off.py`
Content:
```
1 # Copyright 2014 The Oppia Authors. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS-IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """Jobs for statistics views."""
16 
17 import ast
18 import collections
19 
20 from core import jobs
21 from core.domain import stats_jobs_continuous
22 from core.platform import models
23 
24 (stats_models,) = models.Registry.import_models([
25     models.NAMES.statistics
26 ])
27 
28 
29 class StatisticsAudit(jobs.BaseMapReduceJobManager):
30     """Performs a brief audit of exploration completions and state hit counts to
31     make sure they match counts stored in StateCounterModel. It also checks for
32     some possible error cases like negative counts.
33     """
34     _STATE_COUNTER_ERROR_KEY = 'State Counter ERROR'
35 
36     @classmethod
37     def entity_classes_to_map_over(cls):
38         return [
39             stats_models.ExplorationAnnotationsModel,
40             stats_models.StateCounterModel]
41 
42     @staticmethod
43     def map(item):
44         if isinstance(item, stats_models.StateCounterModel):
45             if item.first_entry_count < 0:
46                 yield (
47                     StatisticsAudit._STATE_COUNTER_ERROR_KEY,
48                     'Less than 0: %s %d' % (item.key, item.first_entry_count))
49             return
50         # Older versions of ExplorationAnnotations didn't store exp_id
51         # This is short hand for making sure we get ones updated most recently
52         else:
53             if item.exploration_id is not None:
54                 yield (item.exploration_id, {
55                     'version': item.version,
56                     'starts': item.num_starts,
57                     'completions': item.num_completions,
58                     'state_hit': item.state_hit_counts
59                 })
60 
61     @staticmethod
62     def reduce(key, stringified_values):
63         if key == StatisticsAudit._STATE_COUNTER_ERROR_KEY:
64             for value_str in stringified_values:
65                 yield (value_str,)
66             return
67 
68         # If the code reaches this point, we are looking at values that
69         # correspond to each version of a particular exploration.
70 
71         # These variables correspond to the VERSION_ALL version.
72         all_starts = 0
73         all_completions = 0
74         all_state_hit = collections.defaultdict(int)
75 
76         # These variables correspond to the sum of counts for all other
77         # versions besides VERSION_ALL.
78         sum_starts = 0
79         sum_completions = 0
80         sum_state_hit = collections.defaultdict(int)
81 
82         for value_str in stringified_values:
83             value = ast.literal_eval(value_str)
84             if value['starts'] < 0:
85                 yield (
86                     'Negative start count: exp_id:%s version:%s starts:%s' %
87                     (key, value['version'], value['starts']),)
88 
89             if value['completions'] < 0:
90                 yield (
91                     'Negative completion count: exp_id:%s version:%s '
92                     'completions:%s' %
93                     (key, value['version'], value['completions']),)
94 
95             if value['completions'] > value['starts']:
96                 yield ('Completions > starts: exp_id:%s version:%s %s>%s' % (
97                     key, value['version'], value['completions'],
98                     value['starts']),)
99 
100             if value['version'] == stats_jobs_continuous.VERSION_ALL:
101                 all_starts = value['starts']
102                 all_completions = value['completions']
103                 for (state_name, counts) in value['state_hit'].iteritems():
104                     all_state_hit[state_name] = counts['first_entry_count']
105             else:
106                 sum_starts += value['starts']
107                 sum_completions += value['completions']
108                 for (state_name, counts) in value['state_hit'].iteritems():
109                     sum_state_hit[state_name] += counts['first_entry_count']
110 
111         if sum_starts != all_starts:
112             yield (
113                 'Non-all != all for starts: exp_id:%s sum: %s all: %s'
114                 % (key, sum_starts, all_starts),)
115         if sum_completions != all_completions:
116             yield (
117                 'Non-all != all for completions: exp_id:%s sum: %s all: %s'
118                 % (key, sum_completions, all_completions),)
119 
120         for state_name in all_state_hit:
121             if (state_name not in sum_state_hit and
122                     all_state_hit[state_name] != 0):
123                 yield (
124                     'state hit count not same exp_id:%s state:%s, '
125                     'all:%s sum: null' % (
126                         key, state_name, all_state_hit[state_name]),)
127             elif all_state_hit[state_name] != sum_state_hit[state_name]:
128                 yield (
129                     'state hit count not same exp_id: %s state: %s '
130                     'all: %s sum:%s' % (
131                         key, state_name, all_state_hit[state_name],
132                         sum_state_hit[state_name]),)
133
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/core/domain/stats_jobs_one_off.py b/core/domain/stats_jobs_one_off.py
--- a/core/domain/stats_jobs_one_off.py
+++ b/core/domain/stats_jobs_one_off.py
@@ -27,7 +27,9 @@
 
 
 class StatisticsAudit(jobs.BaseMapReduceJobManager):
-    """Performs a brief audit of exploration completions and state hit counts to
+    """A one-off statistics audit.
+
+    Performs a brief audit of exploration completions and state hit counts to
     make sure they match counts stored in StateCounterModel. It also checks for
     some possible error cases like negative counts.
     """
@@ -41,6 +43,39 @@
 
     @staticmethod
     def map(item):
+        """Implements the map function. Must be declared @staticmethod.
+
+        Args:
+            item: ExplorationAnnotationsModel or
+                StateCounterModel.
+
+        Yields:
+            tuple. For StateCounterModel, a 2-tuple in the form
+                (_STATE_COUNTER_ERROR_KEY, error message).
+            tuple. For ExplorationAnnotationModel, a 2-tuple in the form
+                ('exploration_id', value).
+                'exploration_id': str. the id of the exploration.
+                'value': a dict, whose structure is as follows:
+                    {
+                        'version': str. version of the exploration.
+                        'starts': int. # of times exploration was started.
+                        'completions': int. # of times exploration was
+                            completed.
+                        'state_hit': a dict containing the hit counts for the
+                            states in the exploration. It is formatted as
+                            follows:
+                            {
+                                state_name: {
+                                    'first_entry_count': int. # of sessions
+                                        which hit this state.
+                                    'total_entry_count': int. # of total hits
+                                        for this state.
+                                    'no_answer_count': int. # of hits with no
+                                        answer for this state.
+                                }
+                            }
+                    }
+        """
         if isinstance(item, stats_models.StateCounterModel):
             if item.first_entry_count < 0:
                 yield (
@@ -60,6 +95,36 @@
 
     @staticmethod
     def reduce(key, stringified_values):
+        """Updates statistics for the given exploration.
+
+        Args:
+            key: str. The id of the exploration.
+            stringified_values: list(str). A list of stringified values
+                associated with the given key. An element of stringified_values
+                would be of the form:
+                    {
+                        'version': str. version of the exploration.
+                        'starts': int. # of times exploration was started.
+                        'completions': int. # of times exploration was
+                            completed.
+                        'state_hit': dict. a dict containing the hit counts
+                            for the states in the exploration. It is formatted
+                            as follows:
+                            {
+                                state_name: {
+                                    'first_entry_count': int. # of sessions
+                                        which hit this state.
+                                    'total_entry_count': int. # of total
+                                        hits for this state.
+                                    'no_answer_count': int. # of hits with
+                                        no answer for this state.
+                                }
+                            }
+                    }
+
+        Yields:
+            tuple(str). A 1-tuple whose only element is an error message.
+        """
         if key == StatisticsAudit._STATE_COUNTER_ERROR_KEY:
             for value_str in stringified_values:
                 yield (value_str,)

{"golden_diff": "diff --git a/core/domain/stats_jobs_one_off.py b/core/domain/stats_jobs_one_off.py\n--- a/core/domain/stats_jobs_one_off.py\n+++ b/core/domain/stats_jobs_one_off.py\n@@ -27,7 +27,9 @@\n \n \n class StatisticsAudit(jobs.BaseMapReduceJobManager):\n-    \"\"\"Performs a brief audit of exploration completions and state hit counts to\n+    \"\"\"A one-off statistics audit.\n+\n+    Performs a brief audit of exploration completions and state hit counts to\n     make sure they match counts stored in StateCounterModel. It also checks for\n     some possible error cases like negative counts.\n     \"\"\"\n@@ -41,6 +43,39 @@\n \n     @staticmethod\n     def map(item):\n+        \"\"\"Implements the map function. Must be declared @staticmethod.\n+\n+        Args:\n+            item: ExplorationAnnotationsModel or\n+                StateCounterModel.\n+\n+        Yields:\n+            tuple. For StateCounterModel, a 2-tuple in the form\n+                (_STATE_COUNTER_ERROR_KEY, error message).\n+            tuple. For ExplorationAnnotationModel, a 2-tuple in the form\n+                ('exploration_id', value).\n+                'exploration_id': str. the id of the exploration.\n+                'value': a dict, whose structure is as follows:\n+                    {\n+                        'version': str. version of the exploration.\n+                        'starts': int. # of times exploration was started.\n+                        'completions': int. # of times exploration was\n+                            completed.\n+                        'state_hit': a dict containing the hit counts for the\n+                            states in the exploration. It is formatted as\n+                            follows:\n+                            {\n+                                state_name: {\n+                                    'first_entry_count': int. # of sessions\n+                                        which hit this state.\n+                                    'total_entry_count': int. # of total hits\n+                                        for this state.\n+                                    'no_answer_count': int. # of hits with no\n+                                        answer for this state.\n+                                }\n+                            }\n+                    }\n+        \"\"\"\n         if isinstance(item, stats_models.StateCounterModel):\n             if item.first_entry_count < 0:\n                 yield (\n@@ -60,6 +95,36 @@\n \n     @staticmethod\n     def reduce(key, stringified_values):\n+        \"\"\"Updates statistics for the given exploration.\n+\n+        Args:\n+            key: str. The id of the exploration.\n+            stringified_values: list(str). A list of stringified values\n+                associated with the given key. An element of stringified_values\n+                would be of the form:\n+                    {\n+                        'version': str. version of the exploration.\n+                        'starts': int. # of times exploration was started.\n+                        'completions': int. # of times exploration was\n+                            completed.\n+                        'state_hit': dict. a dict containing the hit counts\n+                            for the states in the exploration. It is formatted\n+                            as follows:\n+                            {\n+                                state_name: {\n+                                    'first_entry_count': int. # of sessions\n+                                        which hit this state.\n+                                    'total_entry_count': int. # of total\n+                                        hits for this state.\n+                                    'no_answer_count': int. # of hits with\n+                                        no answer for this state.\n+                                }\n+                            }\n+                    }\n+\n+        Yields:\n+            tuple(str). A 1-tuple whose only element is an error message.\n+        \"\"\"\n         if key == StatisticsAudit._STATE_COUNTER_ERROR_KEY:\n             for value_str in stringified_values:\n                 yield (value_str,)\n", "issue": "Update docstrings in the Python backend code to follow the style guide.\nWe've been a bit lax about adding docstrings to code in the backend. Instead, we should be following the style guide: https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments -- docstrings should define args, return values, exceptions, etc. The aim of this issue is to ensure that the backend code is properly documented; we will tackle this on a file-by-file basis.\r\n\r\nNote: if you're a new contributor, this issue is a particularly good introductory one to tackle, since it will require reading, understanding and documenting the purpose of a file in the codebase, and help you get acquainted with how the Oppia backend is structured. It's important that you're able to communicate and write clearly, though!\r\n\r\nSome useful tips:\r\n-  Please follow our [style guide](https://github.com/oppia/oppia/wiki/Coding-style-guide#general) (and also the [Google Python style guide](https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments)) when writing docstrings. Make sure to follow the patterns established there as closely as possible, including spacing, capitalization and punctuation. Also, it's a good idea to look at some of the existing files (see below) to get a sense of the correct format -- we recommend following this closely in order to maintain uniformity across the codebase.\r\n- If applicable, make sure that the docstrings for all functions correctly specify the args, return values, etc. If a function has no args, you can omit the Args section; if it has no return value, you can omit the Returns section.\r\n- If a docstring is not present in the code, or the existing one is too vague for you to understand what's going on just by looking at it, then it should be rewritten to be more clear. You'll often need to read the code as well, and do a grep on the callsites to see how it's being used, in order to get a clear understanding of what it's doing. This is good practice for learning how to navigate the codebase, and is an intended part of this project.\r\n- If you feel that parts of the codebase are unclear or incorrectly factored, please feel free to bring this up! The whole point of docstrings is to improve code health and accessibility for new developers, so if you spot other things that can be done in service of that aim, we should also look into doing that.\r\n\r\n**Completed:**\r\n- [x] core.controllers.base @shaz13 \r\n- [x] core.controllers.cron @saeedjassani\r\n- [x] core.controllers.reader @96mohitm\r\n- [x] core.domain.activity_domain @andromfins\r\n- [x] core.domain.activity_services @souravsingh\r\n- [x] core.domain.classifier_services @andromfins \r\n- [x] core.domain.collection_domain @domevit\r\n- [x] core.domain.collection_services @kahkhang \r\n- [x] core.domain.config_services @vibhor98 \r\n- [x] core.domain.email_manager @Oishikatta \r\n- [x] core.domain.exp_domain @domevit\r\n- [x] core.domain.exp_services @DubeySandeep\r\n- [x] core.domain.feedback_domain @terrameijar \r\n- [x] core.domain.feedback_jobs_continuous\r\n- [x] core.domain.feedback_services @andimeo\r\n- [x] core.domain.fs_domain @kartik-25 \r\n- [x] core.domain.html_cleaner @seanlip\r\n- [x] core.domain.param_domain @rohan-gulati\r\n- [x] core.domain.rating_services @BojOnTheBeat \r\n- [x] core.domain.rights_manager @ayejay\r\n- [x] core.domain.stats_domain @Arunabh98\r\n- [x] core.domain.stats_jobs_continuous @Arunabh98 \r\n- [x] core.domain.stats_jobs_one_off @zpuller\r\n- [x] core.domain.stats_services @manoj-makkuboy \r\n- [x] core.domain.subscription_services @vibhor98 \r\n- [x] core.domain.summary_services @domevit \r\n- [x] core.domain.user_jobs_continuous @shubha1593 \r\n- [x] core.domain.user_services @Nalinc \r\n- [x] core.domain.value_generators_domain @meetsha \r\n- [x] core.jobs @brianrodri\r\n- [x] core.platform.app_identity.gae_app_identity_services @ubhisat\r\n- [x] core.platform.models @pranavsid98 \r\n- [x] core.platform.transactions.gae_transaction_services @ubhisat\r\n- [x] core.storage.base_model.gae_models @vojtechjelinek\r\n- [x] core.storage.collection.gae_models @kaggis\r\n- [x] core.storage.email.gae_models @rajan-garg\r\n- [x] core.storage.exploration.gae_models @kirankonduru\r\n- [x] core.storage.feedback.gae_models @naveenshukla\r\n- [x] core.storage.file.gae_models @Ian91 \r\n- [x] core.storage.job.gae_models @sasacocic\r\n- [x] core.storage.statistics.gae_models @abhishekarya286\r\n- [x] core.storage.user.gae_models @Anthony-Alridge \r\n- [x] core.tests.test_util_jobs @seanlip\r\n- [x] core.tests.test_utils @vibhor98\r\n- [x] utils @yashLadha \n", "before_files": [{"content": "# Copyright 2014 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Jobs for statistics views.\"\"\"\n\nimport ast\nimport collections\n\nfrom core import jobs\nfrom core.domain import stats_jobs_continuous\nfrom core.platform import models\n\n(stats_models,) = models.Registry.import_models([\n    models.NAMES.statistics\n])\n\n\nclass StatisticsAudit(jobs.BaseMapReduceJobManager):\n    \"\"\"Performs a brief audit of exploration completions and state hit counts to\n    make sure they match counts stored in StateCounterModel. It also checks for\n    some possible error cases like negative counts.\n    \"\"\"\n    _STATE_COUNTER_ERROR_KEY = 'State Counter ERROR'\n\n    @classmethod\n    def entity_classes_to_map_over(cls):\n        return [\n            stats_models.ExplorationAnnotationsModel,\n            stats_models.StateCounterModel]\n\n    @staticmethod\n    def map(item):\n        if isinstance(item, stats_models.StateCounterModel):\n            if item.first_entry_count < 0:\n                yield (\n                    StatisticsAudit._STATE_COUNTER_ERROR_KEY,\n                    'Less than 0: %s %d' % (item.key, item.first_entry_count))\n            return\n        # Older versions of ExplorationAnnotations didn't store exp_id\n        # This is short hand for making sure we get ones updated most recently\n        else:\n            if item.exploration_id is not None:\n                yield (item.exploration_id, {\n                    'version': item.version,\n                    'starts': item.num_starts,\n                    'completions': item.num_completions,\n                    'state_hit': item.state_hit_counts\n                })\n\n    @staticmethod\n    def reduce(key, stringified_values):\n        if key == StatisticsAudit._STATE_COUNTER_ERROR_KEY:\n            for value_str in stringified_values:\n                yield (value_str,)\n            return\n\n        # If the code reaches this point, we are looking at values that\n        # correspond to each version of a particular exploration.\n\n        # These variables correspond to the VERSION_ALL version.\n        all_starts = 0\n        all_completions = 0\n        all_state_hit = collections.defaultdict(int)\n\n        # These variables correspond to the sum of counts for all other\n        # versions besides VERSION_ALL.\n        sum_starts = 0\n        sum_completions = 0\n        sum_state_hit = collections.defaultdict(int)\n\n        for value_str in stringified_values:\n            value = ast.literal_eval(value_str)\n            if value['starts'] < 0:\n                yield (\n                    'Negative start count: exp_id:%s version:%s starts:%s' %\n                    (key, value['version'], value['starts']),)\n\n            if value['completions'] < 0:\n                yield (\n                    'Negative completion count: exp_id:%s version:%s '\n                    'completions:%s' %\n                    (key, value['version'], value['completions']),)\n\n            if value['completions'] > value['starts']:\n                yield ('Completions > starts: exp_id:%s version:%s %s>%s' % (\n                    key, value['version'], value['completions'],\n                    value['starts']),)\n\n            if value['version'] == stats_jobs_continuous.VERSION_ALL:\n                all_starts = value['starts']\n                all_completions = value['completions']\n                for (state_name, counts) in value['state_hit'].iteritems():\n                    all_state_hit[state_name] = counts['first_entry_count']\n            else:\n                sum_starts += value['starts']\n                sum_completions += value['completions']\n                for (state_name, counts) in value['state_hit'].iteritems():\n                    sum_state_hit[state_name] += counts['first_entry_count']\n\n        if sum_starts != all_starts:\n            yield (\n                'Non-all != all for starts: exp_id:%s sum: %s all: %s'\n                % (key, sum_starts, all_starts),)\n        if sum_completions != all_completions:\n            yield (\n                'Non-all != all for completions: exp_id:%s sum: %s all: %s'\n                % (key, sum_completions, all_completions),)\n\n        for state_name in all_state_hit:\n            if (state_name not in sum_state_hit and\n                    all_state_hit[state_name] != 0):\n                yield (\n                    'state hit count not same exp_id:%s state:%s, '\n                    'all:%s sum: null' % (\n                        key, state_name, all_state_hit[state_name]),)\n            elif all_state_hit[state_name] != sum_state_hit[state_name]:\n                yield (\n                    'state hit count not same exp_id: %s state: %s '\n                    'all: %s sum:%s' % (\n                        key, state_name, all_state_hit[state_name],\n                        sum_state_hit[state_name]),)\n", "path": "core/domain/stats_jobs_one_off.py"}], "after_files": [{"content": "# Copyright 2014 The Oppia Authors. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS-IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Jobs for statistics views.\"\"\"\n\nimport ast\nimport collections\n\nfrom core import jobs\nfrom core.domain import stats_jobs_continuous\nfrom core.platform import models\n\n(stats_models,) = models.Registry.import_models([\n    models.NAMES.statistics\n])\n\n\nclass StatisticsAudit(jobs.BaseMapReduceJobManager):\n    \"\"\"A one-off statistics audit.\n\n    Performs a brief audit of exploration completions and state hit counts to\n    make sure they match counts stored in StateCounterModel. It also checks for\n    some possible error cases like negative counts.\n    \"\"\"\n    _STATE_COUNTER_ERROR_KEY = 'State Counter ERROR'\n\n    @classmethod\n    def entity_classes_to_map_over(cls):\n        return [\n            stats_models.ExplorationAnnotationsModel,\n            stats_models.StateCounterModel]\n\n    @staticmethod\n    def map(item):\n        \"\"\"Implements the map function. Must be declared @staticmethod.\n\n        Args:\n            item: ExplorationAnnotationsModel or\n                StateCounterModel.\n\n        Yields:\n            tuple. For StateCounterModel, a 2-tuple in the form\n                (_STATE_COUNTER_ERROR_KEY, error message).\n            tuple. For ExplorationAnnotationModel, a 2-tuple in the form\n                ('exploration_id', value).\n                'exploration_id': str. the id of the exploration.\n                'value': a dict, whose structure is as follows:\n                    {\n                        'version': str. version of the exploration.\n                        'starts': int. # of times exploration was started.\n                        'completions': int. # of times exploration was\n                            completed.\n                        'state_hit': a dict containing the hit counts for the\n                            states in the exploration. It is formatted as\n                            follows:\n                            {\n                                state_name: {\n                                    'first_entry_count': int. # of sessions\n                                        which hit this state.\n                                    'total_entry_count': int. # of total hits\n                                        for this state.\n                                    'no_answer_count': int. # of hits with no\n                                        answer for this state.\n                                }\n                            }\n                    }\n        \"\"\"\n        if isinstance(item, stats_models.StateCounterModel):\n            if item.first_entry_count < 0:\n                yield (\n                    StatisticsAudit._STATE_COUNTER_ERROR_KEY,\n                    'Less than 0: %s %d' % (item.key, item.first_entry_count))\n            return\n        # Older versions of ExplorationAnnotations didn't store exp_id\n        # This is short hand for making sure we get ones updated most recently\n        else:\n            if item.exploration_id is not None:\n                yield (item.exploration_id, {\n                    'version': item.version,\n                    'starts': item.num_starts,\n                    'completions': item.num_completions,\n                    'state_hit': item.state_hit_counts\n                })\n\n    @staticmethod\n    def reduce(key, stringified_values):\n        \"\"\"Updates statistics for the given exploration.\n\n        Args:\n            key: str. The id of the exploration.\n            stringified_values: list(str). A list of stringified values\n                associated with the given key. An element of stringified_values\n                would be of the form:\n                    {\n                        'version': str. version of the exploration.\n                        'starts': int. # of times exploration was started.\n                        'completions': int. # of times exploration was\n                            completed.\n                        'state_hit': dict. a dict containing the hit counts\n                            for the states in the exploration. It is formatted\n                            as follows:\n                            {\n                                state_name: {\n                                    'first_entry_count': int. # of sessions\n                                        which hit this state.\n                                    'total_entry_count': int. # of total\n                                        hits for this state.\n                                    'no_answer_count': int. # of hits with\n                                        no answer for this state.\n                                }\n                            }\n                    }\n\n        Yields:\n            tuple(str). A 1-tuple whose only element is an error message.\n        \"\"\"\n        if key == StatisticsAudit._STATE_COUNTER_ERROR_KEY:\n            for value_str in stringified_values:\n                yield (value_str,)\n            return\n\n        # If the code reaches this point, we are looking at values that\n        # correspond to each version of a particular exploration.\n\n        # These variables correspond to the VERSION_ALL version.\n        all_starts = 0\n        all_completions = 0\n        all_state_hit = collections.defaultdict(int)\n\n        # These variables correspond to the sum of counts for all other\n        # versions besides VERSION_ALL.\n        sum_starts = 0\n        sum_completions = 0\n        sum_state_hit = collections.defaultdict(int)\n\n        for value_str in stringified_values:\n            value = ast.literal_eval(value_str)\n            if value['starts'] < 0:\n                yield (\n                    'Negative start count: exp_id:%s version:%s starts:%s' %\n                    (key, value['version'], value['starts']),)\n\n            if value['completions'] < 0:\n                yield (\n                    'Negative completion count: exp_id:%s version:%s '\n                    'completions:%s' %\n                    (key, value['version'], value['completions']),)\n\n            if value['completions'] > value['starts']:\n                yield ('Completions > starts: exp_id:%s version:%s %s>%s' % (\n                    key, value['version'], value['completions'],\n                    value['starts']),)\n\n            if value['version'] == stats_jobs_continuous.VERSION_ALL:\n                all_starts = value['starts']\n                all_completions = value['completions']\n                for (state_name, counts) in value['state_hit'].iteritems():\n                    all_state_hit[state_name] = counts['first_entry_count']\n            else:\n                sum_starts += value['starts']\n                sum_completions += value['completions']\n                for (state_name, counts) in value['state_hit'].iteritems():\n                    sum_state_hit[state_name] += counts['first_entry_count']\n\n        if sum_starts != all_starts:\n            yield (\n                'Non-all != all for starts: exp_id:%s sum: %s all: %s'\n                % (key, sum_starts, all_starts),)\n        if sum_completions != all_completions:\n            yield (\n                'Non-all != all for completions: exp_id:%s sum: %s all: %s'\n                % (key, sum_completions, all_completions),)\n\n        for state_name in all_state_hit:\n            if (state_name not in sum_state_hit and\n                    all_state_hit[state_name] != 0):\n                yield (\n                    'state hit count not same exp_id:%s state:%s, '\n                    'all:%s sum: null' % (\n                        key, state_name, all_state_hit[state_name]),)\n            elif all_state_hit[state_name] != sum_state_hit[state_name]:\n                yield (\n                    'state hit count not same exp_id: %s state: %s '\n                    'all: %s sum:%s' % (\n                        key, state_name, all_state_hit[state_name],\n                        sum_state_hit[state_name]),)\n", "path": "core/domain/stats_jobs_one_off.py"}]}