Repository: akvo/akvo-rsr

Issue:
Visual changes to the results framework
Various visual changes need to be made to standardise and improve the results framework and editor.



Discussion:

Resolving patch:
diff --git a/akvo/rsr/models/indicator.py b/akvo/rsr/models/indicator.py
--- a/akvo/rsr/models/indicator.py
+++ b/akvo/rsr/models/indicator.py
@@ -171,6 +171,22 @@ def last_updated(self):
         period_updates = ProjectUpdate.objects.filter(indicator_period__indicator=self)
         return period_updates.order_by('-created_at')[0].time_gmt if period_updates else None
 
+    @property
+    def baseline(self):
+        """
+        Returns the baseline value of the indicator, if it can be converted to a number. Otherwise
+        it'll return 0.
+        """
+        baseline = 0
+
+        if self.baseline_value:
+            baseline = self.baseline_value  
+
+        try:
+            return Decimal(baseline)
+        except (InvalidOperation, TypeError):
+            return Decimal(1)   
+
     class Meta:
         app_label = 'rsr'
         verbose_name = _(u'indicator')
@@ -346,6 +362,20 @@ def child_periods(self):
             period_end=self.period_end
         )
 
+    def adjacent_period(self, next_period=True):
+        """
+        Returns the next or previous indicator period, if we can find one with a start date,
+        and we have a start date ourselves.
+        """
+        if not self.period_start:
+            return None
+        elif next_period:
+            return self.indicator.periods.exclude(period_start=None).filter(
+                period_start__gt=self.period_start).order_by('period_start').first()
+        else:
+            return self.indicator.periods.exclude(period_start=None).filter(
+                period_start__lt=self.period_start).order_by('-period_start').first()
+
     def update_actual_value(self, update_value):
         """
         :param update_value; String or Integer that should be castable to Decimal
@@ -364,6 +394,11 @@ def update_actual_value(self, update_value):
         if parent:
             parent.update_actual_value(update_value)
 
+        # Update next period
+        next_period = self.adjacent_period()
+        if next_period and next_period.actual_value:
+            next_period.update_actual_value(update_value)
+
     @property
     def percent_accomplishment(self):
         """
@@ -374,10 +409,10 @@ def percent_accomplishment(self):
             return 0
 
         actual_value = self.actual_value if self.actual_value else self.baseline
-        baseline = self.baseline
+        baseline = self.indicator.baseline_value
         try:
             return round(
-                (Decimal(actual_value) - Decimal(baseline)) /
+                (Decimal(actual_value) - Decimal(self.indicator.baseline_value)) /
                 (Decimal(self.target_value) - Decimal(baseline)) *
                 100, 1
             )
@@ -417,22 +452,17 @@ def target(self):
     @property
     def baseline(self):
         """
-        Returns the baseline value of the indicator, if it can be converted to a number. Otherwise
-        it'll return 0.
-        """
-        baseline = 0
+        Returns the baseline value of the indicator. The baseline is a calculated value:
 
-        ordered_periods = self.indicator.periods.exclude(period_start=None).order_by('period_start')
-        if ordered_periods.exists() and self == ordered_periods[0]:
+        - If the period has no previous periods, then it's the baseline value of the indicator
+        - If the period has a previous period, then it's the actual value of that period
+        - In all other cases, the baseline defaults to 0
+        """
+        previous_period = self.adjacent_period(False)
+        if not previous_period:
             baseline = self.indicator.baseline_value
-        elif self in ordered_periods:
-            prev_period = None
-            for period in ordered_periods:
-                if not self == period:
-                    prev_period = period
-                else:
-                    baseline = prev_period.actual
-                    break
+        else:
+            baseline = previous_period.actual
 
         try:
             return Decimal(baseline)


Test patch: