We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bug: Cumulative indicator aggregation is incorrect when the previous period gets updated
### What were you doing?

Given there are three periods of a cumulative indicator, the first and second periods have updates, and the total value of the second period is carried over to the third period. 

When the first period gets an additional update, the total value of the first period is carried over to the second and third periods, and the second period is ignored.

### What should've happened?

The additional update should not change the total value of the second period and not be carried over to the third period.

### My environment

_No response_

### Additional context

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `akvo/rsr/models/result/utils.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 # Akvo RSR is covered by the GNU Affero General Public License.
4 # See more details in the license.txt file located at the root folder of the Akvo RSR module.
5 # For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
6 
7 from decimal import Decimal
8 
9 from django.apps import apps
10 from django.db.models import Max, Q
11 from akvo.utils import rsr_image_path
12 
13 PERCENTAGE_MEASURE = '2'
14 QUANTITATIVE = 1
15 QUALITATIVE = 2
16 
17 
18 def calculate_percentage(numerator, denominator):
19     denominator = Decimal(denominator)
20     if denominator == 0:
21         return 0
22     return round(Decimal(numerator) * 100 / Decimal(denominator), 2)
23 
24 
25 def image_path(instance, file_name):
26     """
27     Create a path like 'db/indicator_period/<period.id>/data_photo/<data.id>/image_name.ext'.
28 
29     :param instance; an IndicatorPeriodData instance
30     :param file_name; the name of the file that is to be stored
31     """
32     path = 'db/indicator_period/%d/data_photo/%%(instance_pk)s/%%(file_name)s' % instance.period.pk
33     return rsr_image_path(instance, file_name, path)
34 
35 
36 def file_path(instance, file_name):
37     """
38     Create a path like 'db/indicator_period/<period.id>/data_file/<data.id>/image_name.ext'.
39 
40     :param instance; an IndicatorPeriodData instance
41     :param file_name; the name of the file that is to be stored
42     """
43     path = 'db/indicator_period/%d/data_file/%%(instance_pk)s/%%(file_name)s' % instance.period.pk
44     return rsr_image_path(instance, file_name, path)
45 
46 
47 class MultipleUpdateError(Exception):
48     pass
49 
50 
51 def purge_dimension_name_relations(dimension_name):
52     for dv in dimension_name.dimension_values.all():
53         purge_dimension_value_relations(dv)
54         dv.delete()
55 
56 
57 def purge_dimension_value_relations(dimension_value):
58     Disaggregation = apps.get_model('rsr', 'Disaggregation')
59     DisaggregationTarget = apps.get_model('rsr', 'DisaggregationTarget')
60     IndicatorDisaggregationTarget = apps.get_model('rsr', 'IndicatorDisaggregationTarget')
61 
62     hierarchy = get_dimension_value_hierarchy_flatlist(dimension_value)
63     disaggregations = Disaggregation.objects.filter(dimension_value__in=hierarchy)
64     disaggregation_targets = DisaggregationTarget.objects.filter(dimension_value__in=hierarchy)
65     indicator_disaggregation_targets = IndicatorDisaggregationTarget.objects.filter(dimension_value__in=hierarchy)
66 
67     disaggregations.delete()
68     disaggregation_targets.delete()
69     indicator_disaggregation_targets.delete()
70 
71 
72 def get_dimension_value_hierarchy_flatlist(obj):
73     IndicatorDimensionValue = apps.get_model('rsr', 'IndicatorDimensionValue')
74     family = {obj.id}
75     while True:
76         children = set(IndicatorDimensionValue.objects.filter(parent_dimension_value__in=family).values_list('id', flat=True))
77         if family.union(children) == family:
78             break
79         family = family.union(children)
80 
81     return IndicatorDimensionValue.objects.filter(pk__in=family)
82 
83 
84 def get_per_user_latest_indicator_update_ids(period):
85     IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')
86 
87     return IndicatorPeriodData.objects.filter(
88         status=IndicatorPeriodData.STATUS_APPROVED_CODE,
89         period__indicator=period.indicator,
90     ).filter(
91         Q(period=period) | Q(period__period_end__lt=period.period_end)
92     ).values('user').annotate(id=Max('id')).values('id')
93
```

Path: `akvo/rsr/usecases/period_update_aggregation.py`
Content:
```
1 from __future__ import annotations
2 from decimal import Decimal
3 from typing import Tuple, Optional, TYPE_CHECKING
4 from django.apps import apps
5 from django.db import transaction
6 from django.db.models import QuerySet, Q, Sum
7 from akvo.utils import ensure_decimal
8 
9 if TYPE_CHECKING:
10     from akvo.rsr.models import IndicatorPeriod
11 
12 from akvo.rsr.models.result.utils import PERCENTAGE_MEASURE, calculate_percentage, get_per_user_latest_indicator_update_ids
13 from akvo.rsr.models.result.disaggregation_aggregation import DisaggregationAggregation
14 
15 
16 def get_disaggregation_aggregation():
17     Disaggregation = apps.get_model('rsr', 'Disaggregation')
18     IndicatorPeriodDisaggregation = apps.get_model('rsr', 'IndicatorPeriodDisaggregation')
19     return DisaggregationAggregation(Disaggregation.objects, IndicatorPeriodDisaggregation.objects)
20 
21 
22 @transaction.atomic
23 def aggregate(period: IndicatorPeriod):
24     _aggregate_period_value(period)
25     _aggregate_disaggregation(period)
26 
27 
28 def _aggregate_period_value(period: IndicatorPeriod):
29     value, numerator, denominator = calculate_period_actual_value(period)
30     period.actual_value = str(value) if value else ''
31     if period.indicator.measure == PERCENTAGE_MEASURE:
32         period.numerator = numerator
33         period.denominator = denominator
34     period.save()
35     if period.parent_period \
36             and period.indicator.result.project.aggregate_to_parent \
37             and period.parent_period.indicator.result.project.aggregate_children:
38         _aggregate_period_value(period.parent_period)
39 
40 
41 def _aggregate_disaggregation(period: IndicatorPeriod):
42     Disaggregation = apps.get_model('rsr', 'Disaggregation')
43     IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')
44     IndicatorDimensionValue = apps.get_model('rsr', 'IndicatorDimensionValue')
45 
46     disaggregations = Disaggregation.objects.filter(update__period=period, update__status=IndicatorPeriodData.STATUS_APPROVED_CODE)
47     dimension_values = (
48         IndicatorDimensionValue.objects.filter(name__in=period.indicator.dimension_names.all())
49         | IndicatorDimensionValue.objects.filter(disaggregations__in=disaggregations)
50     ).distinct()
51     for dimension_value in dimension_values:
52         get_disaggregation_aggregation().aggregate(period, dimension_value)
53 
54 
55 def calculate_period_actual_value(period: IndicatorPeriod) -> Tuple[Decimal, Optional[Decimal], Optional[Decimal]]:
56     value, numerator, denominator = sum_updates(period)
57     if period.indicator.measure == PERCENTAGE_MEASURE:
58         contrib_numerator, contrib_denominator = sum_contributed_percentage_value(period)
59         numerator = ensure_decimal(numerator) + ensure_decimal(contrib_numerator)
60         denominator = ensure_decimal(denominator) + ensure_decimal(contrib_denominator)
61         return calculate_percentage(numerator, denominator), numerator, denominator
62 
63     return ensure_decimal(value) + sum_contributed_unit_value(period), None, None
64 
65 
66 def sum_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:
67     return sum_cumulative_updates(period) if period.indicator.is_cumulative() else sum_non_cumulative_updates(period)
68 
69 
70 def sum_cumulative_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:
71     '''
72     This method assumes the user will submit cumulative updates in chronological order as it should.
73     '''
74     IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')
75     latest_per_users = get_per_user_latest_indicator_update_ids(period)
76     value = IndicatorPeriodData.objects.filter(id__in=latest_per_users)\
77         .aggregate(value=Sum('value'))['value']
78     return value, None, None
79 
80 
81 def sum_non_cumulative_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:
82     result = period.approved_updates.aggregate(value=Sum('value'), numerator=Sum('numerator'), denominator=Sum('denominator'))
83     return (result[k] for k in ('value', 'numerator', 'denominator'))
84 
85 
86 def sum_contributed_unit_value(period: IndicatorPeriod) -> Decimal:
87     value = Decimal(0)
88     for contributor in get_contributing_child_periods(period):
89         value += ensure_decimal(contributor.actual_value)
90     return value
91 
92 
93 def sum_contributed_percentage_value(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal]]:
94     result = get_contributing_child_periods(period).aggregate(numerator=Sum('numerator'), denominator=Sum('denominator'))
95     return (result[k] for k in ('numerator', 'denominator'))
96 
97 
98 def get_contributing_child_periods(period: IndicatorPeriod) -> QuerySet:
99     return period.child_periods.exclude(Q(actual_value__isnull=True) | Q(actual_value__exact=''))
100
```
--- 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/akvo/rsr/models/result/utils.py b/akvo/rsr/models/result/utils.py
--- a/akvo/rsr/models/result/utils.py
+++ b/akvo/rsr/models/result/utils.py
@@ -7,7 +7,7 @@
 from decimal import Decimal
 
 from django.apps import apps
-from django.db.models import Max, Q
+from django.db.models import Q
 from akvo.utils import rsr_image_path
 
 PERCENTAGE_MEASURE = '2'
@@ -89,4 +89,6 @@
         period__indicator=period.indicator,
     ).filter(
         Q(period=period) | Q(period__period_end__lt=period.period_end)
-    ).values('user').annotate(id=Max('id')).values('id')
+    ).order_by(
+        'user_id', '-period__period_end', '-created_at'
+    ).distinct('user_id').values('id')
diff --git a/akvo/rsr/usecases/period_update_aggregation.py b/akvo/rsr/usecases/period_update_aggregation.py
--- a/akvo/rsr/usecases/period_update_aggregation.py
+++ b/akvo/rsr/usecases/period_update_aggregation.py
@@ -68,9 +68,6 @@
 
 
 def sum_cumulative_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:
-    '''
-    This method assumes the user will submit cumulative updates in chronological order as it should.
-    '''
     IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')
     latest_per_users = get_per_user_latest_indicator_update_ids(period)
     value = IndicatorPeriodData.objects.filter(id__in=latest_per_users)\

{"golden_diff": "diff --git a/akvo/rsr/models/result/utils.py b/akvo/rsr/models/result/utils.py\n--- a/akvo/rsr/models/result/utils.py\n+++ b/akvo/rsr/models/result/utils.py\n@@ -7,7 +7,7 @@\n from decimal import Decimal\n \n from django.apps import apps\n-from django.db.models import Max, Q\n+from django.db.models import Q\n from akvo.utils import rsr_image_path\n \n PERCENTAGE_MEASURE = '2'\n@@ -89,4 +89,6 @@\n         period__indicator=period.indicator,\n     ).filter(\n         Q(period=period) | Q(period__period_end__lt=period.period_end)\n-    ).values('user').annotate(id=Max('id')).values('id')\n+    ).order_by(\n+        'user_id', '-period__period_end', '-created_at'\n+    ).distinct('user_id').values('id')\ndiff --git a/akvo/rsr/usecases/period_update_aggregation.py b/akvo/rsr/usecases/period_update_aggregation.py\n--- a/akvo/rsr/usecases/period_update_aggregation.py\n+++ b/akvo/rsr/usecases/period_update_aggregation.py\n@@ -68,9 +68,6 @@\n \n \n def sum_cumulative_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:\n-    '''\n-    This method assumes the user will submit cumulative updates in chronological order as it should.\n-    '''\n     IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')\n     latest_per_users = get_per_user_latest_indicator_update_ids(period)\n     value = IndicatorPeriodData.objects.filter(id__in=latest_per_users)\\\n", "issue": "Bug: Cumulative indicator aggregation is incorrect when the previous period gets updated\n### What were you doing?\n\nGiven there are three periods of a cumulative indicator, the first and second periods have updates, and the total value of the second period is carried over to the third period. \r\n\r\nWhen the first period gets an additional update, the total value of the first period is carried over to the second and third periods, and the second period is ignored.\n\n### What should've happened?\n\nThe additional update should not change the total value of the second period and not be carried over to the third period.\n\n### My environment\n\n_No response_\n\n### Additional context\n\n_No response_\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Akvo RSR is covered by the GNU Affero General Public License.\n# See more details in the license.txt file located at the root folder of the Akvo RSR module.\n# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.\n\nfrom decimal import Decimal\n\nfrom django.apps import apps\nfrom django.db.models import Max, Q\nfrom akvo.utils import rsr_image_path\n\nPERCENTAGE_MEASURE = '2'\nQUANTITATIVE = 1\nQUALITATIVE = 2\n\n\ndef calculate_percentage(numerator, denominator):\n    denominator = Decimal(denominator)\n    if denominator == 0:\n        return 0\n    return round(Decimal(numerator) * 100 / Decimal(denominator), 2)\n\n\ndef image_path(instance, file_name):\n    \"\"\"\n    Create a path like 'db/indicator_period/<period.id>/data_photo/<data.id>/image_name.ext'.\n\n    :param instance; an IndicatorPeriodData instance\n    :param file_name; the name of the file that is to be stored\n    \"\"\"\n    path = 'db/indicator_period/%d/data_photo/%%(instance_pk)s/%%(file_name)s' % instance.period.pk\n    return rsr_image_path(instance, file_name, path)\n\n\ndef file_path(instance, file_name):\n    \"\"\"\n    Create a path like 'db/indicator_period/<period.id>/data_file/<data.id>/image_name.ext'.\n\n    :param instance; an IndicatorPeriodData instance\n    :param file_name; the name of the file that is to be stored\n    \"\"\"\n    path = 'db/indicator_period/%d/data_file/%%(instance_pk)s/%%(file_name)s' % instance.period.pk\n    return rsr_image_path(instance, file_name, path)\n\n\nclass MultipleUpdateError(Exception):\n    pass\n\n\ndef purge_dimension_name_relations(dimension_name):\n    for dv in dimension_name.dimension_values.all():\n        purge_dimension_value_relations(dv)\n        dv.delete()\n\n\ndef purge_dimension_value_relations(dimension_value):\n    Disaggregation = apps.get_model('rsr', 'Disaggregation')\n    DisaggregationTarget = apps.get_model('rsr', 'DisaggregationTarget')\n    IndicatorDisaggregationTarget = apps.get_model('rsr', 'IndicatorDisaggregationTarget')\n\n    hierarchy = get_dimension_value_hierarchy_flatlist(dimension_value)\n    disaggregations = Disaggregation.objects.filter(dimension_value__in=hierarchy)\n    disaggregation_targets = DisaggregationTarget.objects.filter(dimension_value__in=hierarchy)\n    indicator_disaggregation_targets = IndicatorDisaggregationTarget.objects.filter(dimension_value__in=hierarchy)\n\n    disaggregations.delete()\n    disaggregation_targets.delete()\n    indicator_disaggregation_targets.delete()\n\n\ndef get_dimension_value_hierarchy_flatlist(obj):\n    IndicatorDimensionValue = apps.get_model('rsr', 'IndicatorDimensionValue')\n    family = {obj.id}\n    while True:\n        children = set(IndicatorDimensionValue.objects.filter(parent_dimension_value__in=family).values_list('id', flat=True))\n        if family.union(children) == family:\n            break\n        family = family.union(children)\n\n    return IndicatorDimensionValue.objects.filter(pk__in=family)\n\n\ndef get_per_user_latest_indicator_update_ids(period):\n    IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')\n\n    return IndicatorPeriodData.objects.filter(\n        status=IndicatorPeriodData.STATUS_APPROVED_CODE,\n        period__indicator=period.indicator,\n    ).filter(\n        Q(period=period) | Q(period__period_end__lt=period.period_end)\n    ).values('user').annotate(id=Max('id')).values('id')\n", "path": "akvo/rsr/models/result/utils.py"}, {"content": "from __future__ import annotations\nfrom decimal import Decimal\nfrom typing import Tuple, Optional, TYPE_CHECKING\nfrom django.apps import apps\nfrom django.db import transaction\nfrom django.db.models import QuerySet, Q, Sum\nfrom akvo.utils import ensure_decimal\n\nif TYPE_CHECKING:\n    from akvo.rsr.models import IndicatorPeriod\n\nfrom akvo.rsr.models.result.utils import PERCENTAGE_MEASURE, calculate_percentage, get_per_user_latest_indicator_update_ids\nfrom akvo.rsr.models.result.disaggregation_aggregation import DisaggregationAggregation\n\n\ndef get_disaggregation_aggregation():\n    Disaggregation = apps.get_model('rsr', 'Disaggregation')\n    IndicatorPeriodDisaggregation = apps.get_model('rsr', 'IndicatorPeriodDisaggregation')\n    return DisaggregationAggregation(Disaggregation.objects, IndicatorPeriodDisaggregation.objects)\n\n\n@transaction.atomic\ndef aggregate(period: IndicatorPeriod):\n    _aggregate_period_value(period)\n    _aggregate_disaggregation(period)\n\n\ndef _aggregate_period_value(period: IndicatorPeriod):\n    value, numerator, denominator = calculate_period_actual_value(period)\n    period.actual_value = str(value) if value else ''\n    if period.indicator.measure == PERCENTAGE_MEASURE:\n        period.numerator = numerator\n        period.denominator = denominator\n    period.save()\n    if period.parent_period \\\n            and period.indicator.result.project.aggregate_to_parent \\\n            and period.parent_period.indicator.result.project.aggregate_children:\n        _aggregate_period_value(period.parent_period)\n\n\ndef _aggregate_disaggregation(period: IndicatorPeriod):\n    Disaggregation = apps.get_model('rsr', 'Disaggregation')\n    IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')\n    IndicatorDimensionValue = apps.get_model('rsr', 'IndicatorDimensionValue')\n\n    disaggregations = Disaggregation.objects.filter(update__period=period, update__status=IndicatorPeriodData.STATUS_APPROVED_CODE)\n    dimension_values = (\n        IndicatorDimensionValue.objects.filter(name__in=period.indicator.dimension_names.all())\n        | IndicatorDimensionValue.objects.filter(disaggregations__in=disaggregations)\n    ).distinct()\n    for dimension_value in dimension_values:\n        get_disaggregation_aggregation().aggregate(period, dimension_value)\n\n\ndef calculate_period_actual_value(period: IndicatorPeriod) -> Tuple[Decimal, Optional[Decimal], Optional[Decimal]]:\n    value, numerator, denominator = sum_updates(period)\n    if period.indicator.measure == PERCENTAGE_MEASURE:\n        contrib_numerator, contrib_denominator = sum_contributed_percentage_value(period)\n        numerator = ensure_decimal(numerator) + ensure_decimal(contrib_numerator)\n        denominator = ensure_decimal(denominator) + ensure_decimal(contrib_denominator)\n        return calculate_percentage(numerator, denominator), numerator, denominator\n\n    return ensure_decimal(value) + sum_contributed_unit_value(period), None, None\n\n\ndef sum_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:\n    return sum_cumulative_updates(period) if period.indicator.is_cumulative() else sum_non_cumulative_updates(period)\n\n\ndef sum_cumulative_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:\n    '''\n    This method assumes the user will submit cumulative updates in chronological order as it should.\n    '''\n    IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')\n    latest_per_users = get_per_user_latest_indicator_update_ids(period)\n    value = IndicatorPeriodData.objects.filter(id__in=latest_per_users)\\\n        .aggregate(value=Sum('value'))['value']\n    return value, None, None\n\n\ndef sum_non_cumulative_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:\n    result = period.approved_updates.aggregate(value=Sum('value'), numerator=Sum('numerator'), denominator=Sum('denominator'))\n    return (result[k] for k in ('value', 'numerator', 'denominator'))\n\n\ndef sum_contributed_unit_value(period: IndicatorPeriod) -> Decimal:\n    value = Decimal(0)\n    for contributor in get_contributing_child_periods(period):\n        value += ensure_decimal(contributor.actual_value)\n    return value\n\n\ndef sum_contributed_percentage_value(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal]]:\n    result = get_contributing_child_periods(period).aggregate(numerator=Sum('numerator'), denominator=Sum('denominator'))\n    return (result[k] for k in ('numerator', 'denominator'))\n\n\ndef get_contributing_child_periods(period: IndicatorPeriod) -> QuerySet:\n    return period.child_periods.exclude(Q(actual_value__isnull=True) | Q(actual_value__exact=''))\n", "path": "akvo/rsr/usecases/period_update_aggregation.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Akvo RSR is covered by the GNU Affero General Public License.\n# See more details in the license.txt file located at the root folder of the Akvo RSR module.\n# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.\n\nfrom decimal import Decimal\n\nfrom django.apps import apps\nfrom django.db.models import Q\nfrom akvo.utils import rsr_image_path\n\nPERCENTAGE_MEASURE = '2'\nQUANTITATIVE = 1\nQUALITATIVE = 2\n\n\ndef calculate_percentage(numerator, denominator):\n    denominator = Decimal(denominator)\n    if denominator == 0:\n        return 0\n    return round(Decimal(numerator) * 100 / Decimal(denominator), 2)\n\n\ndef image_path(instance, file_name):\n    \"\"\"\n    Create a path like 'db/indicator_period/<period.id>/data_photo/<data.id>/image_name.ext'.\n\n    :param instance; an IndicatorPeriodData instance\n    :param file_name; the name of the file that is to be stored\n    \"\"\"\n    path = 'db/indicator_period/%d/data_photo/%%(instance_pk)s/%%(file_name)s' % instance.period.pk\n    return rsr_image_path(instance, file_name, path)\n\n\ndef file_path(instance, file_name):\n    \"\"\"\n    Create a path like 'db/indicator_period/<period.id>/data_file/<data.id>/image_name.ext'.\n\n    :param instance; an IndicatorPeriodData instance\n    :param file_name; the name of the file that is to be stored\n    \"\"\"\n    path = 'db/indicator_period/%d/data_file/%%(instance_pk)s/%%(file_name)s' % instance.period.pk\n    return rsr_image_path(instance, file_name, path)\n\n\nclass MultipleUpdateError(Exception):\n    pass\n\n\ndef purge_dimension_name_relations(dimension_name):\n    for dv in dimension_name.dimension_values.all():\n        purge_dimension_value_relations(dv)\n        dv.delete()\n\n\ndef purge_dimension_value_relations(dimension_value):\n    Disaggregation = apps.get_model('rsr', 'Disaggregation')\n    DisaggregationTarget = apps.get_model('rsr', 'DisaggregationTarget')\n    IndicatorDisaggregationTarget = apps.get_model('rsr', 'IndicatorDisaggregationTarget')\n\n    hierarchy = get_dimension_value_hierarchy_flatlist(dimension_value)\n    disaggregations = Disaggregation.objects.filter(dimension_value__in=hierarchy)\n    disaggregation_targets = DisaggregationTarget.objects.filter(dimension_value__in=hierarchy)\n    indicator_disaggregation_targets = IndicatorDisaggregationTarget.objects.filter(dimension_value__in=hierarchy)\n\n    disaggregations.delete()\n    disaggregation_targets.delete()\n    indicator_disaggregation_targets.delete()\n\n\ndef get_dimension_value_hierarchy_flatlist(obj):\n    IndicatorDimensionValue = apps.get_model('rsr', 'IndicatorDimensionValue')\n    family = {obj.id}\n    while True:\n        children = set(IndicatorDimensionValue.objects.filter(parent_dimension_value__in=family).values_list('id', flat=True))\n        if family.union(children) == family:\n            break\n        family = family.union(children)\n\n    return IndicatorDimensionValue.objects.filter(pk__in=family)\n\n\ndef get_per_user_latest_indicator_update_ids(period):\n    IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')\n\n    return IndicatorPeriodData.objects.filter(\n        status=IndicatorPeriodData.STATUS_APPROVED_CODE,\n        period__indicator=period.indicator,\n    ).filter(\n        Q(period=period) | Q(period__period_end__lt=period.period_end)\n    ).order_by(\n        'user_id', '-period__period_end', '-created_at'\n    ).distinct('user_id').values('id')\n", "path": "akvo/rsr/models/result/utils.py"}, {"content": "from __future__ import annotations\nfrom decimal import Decimal\nfrom typing import Tuple, Optional, TYPE_CHECKING\nfrom django.apps import apps\nfrom django.db import transaction\nfrom django.db.models import QuerySet, Q, Sum\nfrom akvo.utils import ensure_decimal\n\nif TYPE_CHECKING:\n    from akvo.rsr.models import IndicatorPeriod\n\nfrom akvo.rsr.models.result.utils import PERCENTAGE_MEASURE, calculate_percentage, get_per_user_latest_indicator_update_ids\nfrom akvo.rsr.models.result.disaggregation_aggregation import DisaggregationAggregation\n\n\ndef get_disaggregation_aggregation():\n    Disaggregation = apps.get_model('rsr', 'Disaggregation')\n    IndicatorPeriodDisaggregation = apps.get_model('rsr', 'IndicatorPeriodDisaggregation')\n    return DisaggregationAggregation(Disaggregation.objects, IndicatorPeriodDisaggregation.objects)\n\n\n@transaction.atomic\ndef aggregate(period: IndicatorPeriod):\n    _aggregate_period_value(period)\n    _aggregate_disaggregation(period)\n\n\ndef _aggregate_period_value(period: IndicatorPeriod):\n    value, numerator, denominator = calculate_period_actual_value(period)\n    period.actual_value = str(value) if value else ''\n    if period.indicator.measure == PERCENTAGE_MEASURE:\n        period.numerator = numerator\n        period.denominator = denominator\n    period.save()\n    if period.parent_period \\\n            and period.indicator.result.project.aggregate_to_parent \\\n            and period.parent_period.indicator.result.project.aggregate_children:\n        _aggregate_period_value(period.parent_period)\n\n\ndef _aggregate_disaggregation(period: IndicatorPeriod):\n    Disaggregation = apps.get_model('rsr', 'Disaggregation')\n    IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')\n    IndicatorDimensionValue = apps.get_model('rsr', 'IndicatorDimensionValue')\n\n    disaggregations = Disaggregation.objects.filter(update__period=period, update__status=IndicatorPeriodData.STATUS_APPROVED_CODE)\n    dimension_values = (\n        IndicatorDimensionValue.objects.filter(name__in=period.indicator.dimension_names.all())\n        | IndicatorDimensionValue.objects.filter(disaggregations__in=disaggregations)\n    ).distinct()\n    for dimension_value in dimension_values:\n        get_disaggregation_aggregation().aggregate(period, dimension_value)\n\n\ndef calculate_period_actual_value(period: IndicatorPeriod) -> Tuple[Decimal, Optional[Decimal], Optional[Decimal]]:\n    value, numerator, denominator = sum_updates(period)\n    if period.indicator.measure == PERCENTAGE_MEASURE:\n        contrib_numerator, contrib_denominator = sum_contributed_percentage_value(period)\n        numerator = ensure_decimal(numerator) + ensure_decimal(contrib_numerator)\n        denominator = ensure_decimal(denominator) + ensure_decimal(contrib_denominator)\n        return calculate_percentage(numerator, denominator), numerator, denominator\n\n    return ensure_decimal(value) + sum_contributed_unit_value(period), None, None\n\n\ndef sum_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:\n    return sum_cumulative_updates(period) if period.indicator.is_cumulative() else sum_non_cumulative_updates(period)\n\n\ndef sum_cumulative_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:\n    IndicatorPeriodData = apps.get_model('rsr', 'IndicatorPeriodData')\n    latest_per_users = get_per_user_latest_indicator_update_ids(period)\n    value = IndicatorPeriodData.objects.filter(id__in=latest_per_users)\\\n        .aggregate(value=Sum('value'))['value']\n    return value, None, None\n\n\ndef sum_non_cumulative_updates(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal], Optional[Decimal]]:\n    result = period.approved_updates.aggregate(value=Sum('value'), numerator=Sum('numerator'), denominator=Sum('denominator'))\n    return (result[k] for k in ('value', 'numerator', 'denominator'))\n\n\ndef sum_contributed_unit_value(period: IndicatorPeriod) -> Decimal:\n    value = Decimal(0)\n    for contributor in get_contributing_child_periods(period):\n        value += ensure_decimal(contributor.actual_value)\n    return value\n\n\ndef sum_contributed_percentage_value(period: IndicatorPeriod) -> Tuple[Optional[Decimal], Optional[Decimal]]:\n    result = get_contributing_child_periods(period).aggregate(numerator=Sum('numerator'), denominator=Sum('denominator'))\n    return (result[k] for k in ('numerator', 'denominator'))\n\n\ndef get_contributing_child_periods(period: IndicatorPeriod) -> QuerySet:\n    return period.child_periods.exclude(Q(actual_value__isnull=True) | Q(actual_value__exact=''))\n", "path": "akvo/rsr/usecases/period_update_aggregation.py"}]}