示例#1
0
 def add_percentage_value(self, numerator, denominator, disaggregations={}):
     return self._replace(
         numerator=ensure_decimal(self.numerator) +
         ensure_decimal(numerator),
         denominator=ensure_decimal(self.denominator) +
         ensure_decimal(denominator),
         disaggregations=self._add_disaggregation(disaggregations))
示例#2
0
    def create(self, validated_data):
        self._validate_disaggregations(
            self._disaggregations_data,
            value=ensure_decimal(validated_data.get('value', 0)),
            numerator=ensure_decimal(validated_data.get('numerator', None)),
            denominator=ensure_decimal(validated_data.get('denominator',
                                                          None)))
        """Over-ridden to handle nested writes."""
        files = validated_data.pop('files', [])
        photos = validated_data.pop('photos', [])
        comments = validated_data.pop('comments', [])
        update = super(IndicatorPeriodDataFrameworkSerializer,
                       self).create(validated_data)
        for disaggregation in self._disaggregations_data:
            disaggregation['update'] = update.id
            if 'type_id' in disaggregation and 'dimension_value' not in disaggregation:
                disaggregation['dimension_value'] = disaggregation['type_id']
            serializer = DisaggregationSerializer(data=disaggregation)
            serializer.is_valid(raise_exception=True)
            serializer.create(serializer.validated_data)
        for file in files:
            IndicatorPeriodDataFile.objects.create(update=update, file=file)
        for photo in photos:
            IndicatorPeriodDataPhoto.objects.create(update=update, photo=photo)
        for comment in comments:
            IndicatorPeriodDataComment.objects.create(
                data=update, user=update.user, comment=comment['comment'])

        return update
示例#3
0
def _transform_contributions_hierarchy(tree, is_percentage):
    contributors = []
    contributor_countries = []
    aggregated_value = Decimal(0) if not is_percentage else None
    aggregated_numerator = Decimal(0) if is_percentage else None
    aggregated_denominator = Decimal(0) if is_percentage else None
    disaggregations = {}
    for node in tree:
        contributor, countries = _transform_contributor_node(
            node, is_percentage)
        if contributor:
            contributors.append(contributor)
            contributor_countries = _merge_unique(contributor_countries,
                                                  countries)
            if not is_percentage:
                aggregated_value += contributor['actual_value']
            else:
                aggregated_numerator += contributor['actual_numerator']
                aggregated_denominator += contributor['actual_denominator']
            disaggregation_contributions = _extract_disaggregation_contributions(
                contributor)
            for key in disaggregation_contributions:
                if key not in disaggregations:
                    disaggregations[key] = disaggregation_contributions[
                        key].copy()
                else:
                    disaggregations[key]['value'] = ensure_decimal(
                        disaggregations[key]['value']) + ensure_decimal(
                            disaggregation_contributions[key]['value'])

    aggregates = (aggregated_value, aggregated_numerator,
                  aggregated_denominator)

    return contributors, contributor_countries, aggregates, disaggregations
示例#4
0
def render_contributor(ws,
                       row,
                       result,
                       indicator,
                       period,
                       contributor,
                       aggregate_targets=False,
                       use_indicator_target=False,
                       disaggregations={},
                       level=1):
    long_text_style = Style(alignment=Alignment(wrap_text=True))
    ws.set_cell_style(row, 1, long_text_style)
    ws.set_cell_value(row, 1, result.title)
    ws.set_cell_style(row, 2, long_text_style)
    ws.set_cell_value(row, 2, result.description)
    ws.set_cell_style(row, 3, long_text_style)
    ws.set_cell_value(row, 3, indicator.title)
    ws.set_cell_value(row, 4, f"{period.period_start} - {period.period_end}")
    ws.set_cell_value(row, 5, level)
    ws.set_cell_style(row, 6, long_text_style)
    ws.set_cell_value(row, 6, contributor.project.title)
    ws.set_cell_style(row, 7, long_text_style)
    ws.set_cell_value(row, 7, contributor.project.subtitle)
    ws.set_cell_style(row, 8, long_text_style)
    ws.set_cell_value(row, 8, contributor.project.country)
    ws.set_cell_style(row, 9, long_text_style)
    ws.set_cell_value(
        row, 9, ', '.join(contributor.project.sectors)
        if contributor.project.sectors else '')
    ws.set_cell_value(row, 10,
                      maybe_decimal(contributor.indicator_baseline_value))
    col = get_dynamic_column_start(aggregate_targets)
    ws.set_cell_value(
        row, col, contributor.indicator_target_value
        if use_indicator_target else ensure_decimal(contributor.target_value))
    col += 2
    ws.set_cell_value(row, col, contributor.actual_value)
    col += 1
    if period.is_quantitative:
        contribution = calculate_percentage(
            ensure_decimal(contributor.updates_value),
            ensure_decimal(period.aggregated_value))
        ws.set_cell_style(row, col,
                          Style(alignment=Alignment(horizontal='right')))
        ws.set_cell_value(row, col, f"{contribution}%")
        col += 1
        for category, types in disaggregations.items():
            for type in [t for t in types.keys()]:
                ws.set_cell_value(
                    row, col,
                    contributor.get_disaggregation_value(category, type) or '')
                col += 1
                ws.set_cell_value(
                    row, col,
                    contributor.get_disaggregation_target_value(
                        category, type) or '')
                col += 1
    return row + 1
示例#5
0
 def get_aggregated_disaggregation_value(self, category, type):
     item = self._select_disaggregation(self.aggregated_disaggregations,
                                        category, type)
     if not item:
         return None
     if self.is_percentage:
         return calculate_percentage(ensure_decimal(item.numerator),
                                     ensure_decimal(item.denominator))
     return item.value
示例#6
0
    def update(self, instance, validated_data):
        self._validate_disaggregations(
            self._disaggregations_data,
            value=ensure_decimal(validated_data.get('value', instance.value)),
            numerator=ensure_decimal(
                validated_data.get('numerator', instance.numerator)),
            denominator=ensure_decimal(
                validated_data.get('denominator', instance.denominator)),
            update=instance)
        """Over-ridden to handle nested updates."""
        files = validated_data.pop('files', [])
        photos = validated_data.pop('photos', [])
        comments = validated_data.pop('comments', [])
        super(IndicatorPeriodDataFrameworkSerializer,
              self).update(instance, validated_data)
        for disaggregation in self._disaggregations_data:
            disaggregation['update'] = instance.id
            serializer = DisaggregationSerializer(data=disaggregation)
            serializer.is_valid(raise_exception=True)
            disaggregation_instance, _ = instance.disaggregations.get_or_create(
                update=instance,
                dimension_value=serializer.validated_data['dimension_value'],
            )
            serializer.update(disaggregation_instance,
                              serializer.validated_data)
        for file in files:
            IndicatorPeriodDataFile.objects.create(update=instance, file=file)
        for photo in photos:
            IndicatorPeriodDataPhoto.objects.create(update=instance,
                                                    photo=photo)
        for comment in comments:
            comment_id = int(comment.get('id', 0))
            comment_txt = str(comment.get('comment', ''))
            if not comment_id:
                IndicatorPeriodDataComment.objects.create(
                    data=instance,
                    user=instance.user,
                    comment=comment['comment'])
            else:
                comment_obj = IndicatorPeriodDataComment.objects.get(
                    id=comment_id)
                if not comment_txt:
                    comment_obj.delete()
                else:
                    comment_obj.comment = comment_txt
                    comment_obj.save()

        return instance._meta.model.objects.select_related(
            'period',
            'user',
            'approved_by',
        ).prefetch_related(
            'comments',
            'disaggregations',
        ).get(id=instance.id)
示例#7
0
def _get_indicator_target(indicator, targets_at=None, aggregate_targets=False):
    if targets_at != 'indicator':
        return None
    if indicator.type == QUALITATIVE:
        return indicator.target_value
    if indicator.measure == PERCENTAGE_MEASURE or not aggregate_targets:
        return ensure_decimal(indicator.target_value)
    hierarchy_ids = _get_indicator_hierarchy_ids(indicator)
    result = Indicator.objects.filter(id__in=hierarchy_ids).aggregate(
        Sum('target_value'))
    return ensure_decimal(result['target_value__sum'])
示例#8
0
def _extract_disaggregation_contributions(contributor):
    disaggregations = {}
    for update in contributor['updates']:
        if update['status']['code'] == 'A':
            for d in update['disaggregations']:
                key = (d['category'], d['type'])
                if key not in disaggregations:
                    disaggregations[key] = d.copy()
                else:
                    disaggregations[key]['value'] = ensure_decimal(
                        disaggregations[key]['value']) + ensure_decimal(
                            d['value'])

    return disaggregations
示例#9
0
 def updates_denominator(self):
     if not self.is_percentage:
         return None
     value = 0
     for update in self.approved_updates:
         value += ensure_decimal(update.denominator)
     return value
示例#10
0
 def pending_denominator(self):
     if not self.is_percentage:
         return None
     value = 0
     for update in self.pending_updates:
         value += ensure_decimal(update.denominator)
     return value
示例#11
0
 def aggregated_denominator(self):
     if not self.is_percentage:
         return None
     value = self.updates_denominator
     for contributor in self.contributors:
         value += ensure_decimal(contributor.aggregated_denominator)
     return value
示例#12
0
 def aggregated_value(self):
     if self.is_percentage or self.is_qualitative:
         return None
     value = self.updates_value
     for contributor in self.contributors:
         value += ensure_decimal(contributor.aggregated_value)
     return value
示例#13
0
 def test_ensure_decimal(self):
     for expected, actual in [
         (Decimal(10), '10'),
         (Decimal(0), ''),
         (Decimal(0), None),
     ]:
         self.assertEqual(expected, ensure_decimal(actual))
示例#14
0
 def updates_value(self):
     if self.is_percentage:
         return None
     value = 0
     for update in self.approved_updates:
         value += ensure_decimal(update.value)
     return value
示例#15
0
def get_indicators_by_country(project):
    results = get_results_framework(project)
    visitors = []
    for result in results:
        for indicator in result.indicators:
            for period in indicator.periods:
                visitors.append(ContributionPerCountryVisitor.collect(result, indicator, period))
    by_countries = {}
    for visitor in visitors:
        indicator = visitor.indicator
        period = visitor.period
        for country_code, contribution in visitor.countries.items():
            if country_code not in by_countries:
                by_countries[country_code] = {}
            by_country = by_countries[country_code]
            if indicator.id not in by_country:
                by_country[indicator.id] = {
                    'is_percentage': indicator.is_percentage,
                    'periods': {}
                }
            by_indicator = by_country[indicator.id]
            by_indicator['periods'][period.id] = {
                'period_start': period.period_start,
                'period_end': period.period_end,
                'period_target': ensure_decimal(period.target_value),
                'aggregated_period_target': period.aggregated_target_value,
                'target': contribution.target,
                'value': contribution.value,
                'numerator': contribution.numerator,
                'denominator': contribution.denominator,
            }
    return by_countries
示例#16
0
 def _validate_disaggregations(self,
                               disaggregations,
                               value,
                               numerator=None,
                               denominator=None,
                               update=None):
     adjustments = {}
     for disaggregation in disaggregations:
         type_id = disaggregation.get(
             'type_id', disaggregation.get('dimension_value', None))
         if type_id is None:
             continue
         if denominator is not None:
             disaggregation_denominator = ensure_decimal(
                 disaggregation.get('denominator', 0))
             if disaggregation_denominator > denominator:
                 raise serializers.ValidationError(
                     "disaggregations denominator should not exceed update denominator"
                 )
         category = IndicatorDimensionValue.objects.get(pk=type_id).name
         if category.id not in adjustments:
             adjustments[category.id] = {
                 'values': 0,
                 'numerators': 0,
                 'type_ids': []
             }
         adjustments[category.id]['values'] += ensure_decimal(
             disaggregation.get('value', 0))
         adjustments[category.id]['numerators'] += ensure_decimal(
             disaggregation.get('numerator', 0))
         adjustments[category.id]['type_ids'].append(type_id)
     for key, adjustment in adjustments.items():
         unmodifieds = Disaggregation.objects.filter(update=update, dimension_value__name=key)\
             .exclude(dimension_value__in=adjustment['type_ids'])\
             .aggregate(values=Sum('value'))
         total = adjustment['values'] + ensure_decimal(
             unmodifieds['values'])
         if numerator is not None and adjustment['numerators'] > numerator:
             raise serializers.ValidationError(
                 "The disaggregation numerator should not exceed update numerator"
             )
         if total > value:
             raise serializers.ValidationError(
                 "The accumulated disaggregations value should not exceed update value"
             )
示例#17
0
    def test_should_be_able_to_patch_disaggregation_targets_values(self):
        _, org = self.create_org_user('*****@*****.**', 'password')
        project = ProjectFixtureBuilder()\
            .with_partner(org, Partnership.IATI_REPORTING_ORGANISATION)\
            .with_disaggregations({
                'Gender': ['Male', 'Female'],
                'Age': ['Children', 'Adults']
            })\
            .with_results([{
                'title': 'Result #1',
                'indicators': [{
                    'title': 'Indicator #1',
                    'periods': [{
                        'period_start': date(2010, 1, 1),
                        'period_end': date(2010, 12, 31),
                        'target_value': 10,
                        'disaggregation_targets': {
                            'Gender': {'Male': 5, 'Female': 5},
                            'Age': {'Adults': 10}
                        }
                    }]
                }]
            }]).build()

        period = project.get_period(period_start=date(2010, 1, 1))
        gender_female_target = period.get_disaggregation_target(
            'Gender', 'Female')
        age_adults_target = period.get_disaggregation_target('Age', 'Adults')

        data = {
            'target_value':
            12,
            'disaggregation_targets': [
                {
                    'dimension_value': gender_female_target.dimension_value.id,
                    'value': 7
                },
                {
                    'dimension_value': age_adults_target.dimension_value.id,
                    'value': 12
                },
            ]
        }
        response = self.send_request(period,
                                     data,
                                     username='******',
                                     password='******')

        self.assertEqual(response.status_code, 200)
        updated_period = project.get_period(period_start=date(2010, 1, 1))
        self.assertEqual(ensure_decimal(updated_period.target_value), 12)
        self.assertEqual(
            updated_period.get_disaggregation_target('Gender', 'Female').value,
            7)
        self.assertEqual(
            updated_period.get_disaggregation_target('Age', 'Adults').value,
            12)
示例#18
0
 def aggregated_value(self):
     if self.is_qualitative:
         return None
     if self.is_percentage:
         return calculate_percentage(self.aggregated_numerator, self.aggregated_denominator)
     value = self.updates_value
     for contributor in self.contributors:
         value += ensure_decimal(contributor.aggregated_value)
     return value
示例#19
0
 def target_value(self):
     if self._target_value is None:
         if self.type == IndicatorType.NARRATIVE:
             self._target_value = self._real.target_value
         elif self.aggregate_targets and self.type != IndicatorType.PERCENTAGE:
             self._target_value = _aggregate_period_targets(
                 self._real, self._children)
         else:
             self._target_value = ensure_decimal(self._real.target_value)
     return self._target_value
示例#20
0
 def aggregated_disaggregation_targets(
         self) -> List[DisaggregationTargetData]:
     items = {}
     for d in self.disaggregation_targets:
         key = (d.category, d.type)
         if key not in items:
             items[key] = None
         items[key] = ensure_decimal(items[key]) + ensure_decimal(d.value)
     for contributor in self.contributors:
         for d in contributor.aggregated_disaggregation_targets:
             key = (d.category, d.type)
             if key not in items:
                 items[key] = None
             items[key] = ensure_decimal(items[key]) + ensure_decimal(
                 d.value)
     return [
         DisaggregationTargetData(None, category, type, value)
         for (category, type), value in items.items()
     ]
示例#21
0
 def pending_value(self):
     if self.is_qualitative:
         return None
     if self.is_percentage:
         return calculate_percentage(self.pending_numerator,
                                     self.pending_denominator)
     value = 0
     for update in self.pending_updates:
         value += ensure_decimal(update.value)
     return value
示例#22
0
 def visit(self, contributor):
     if not contributor.has_updates and contributor.target_value is None:
         return
     country_code = contributor.project.country_code
     if country_code is None:
         return
     if country_code not in self.countries:
         self.countries[country_code] = ContributionValue()
     contribution = self.countries[country_code]
     if contributor.target_value:
         contribution.add_target(ensure_decimal(contributor.target_value))
     if self.period.is_percentage:
         contribution.add_fraction(contributor.updates_numerator, contributor.updates_denominator)
     else:
         contribution.add_value(contributor.updates_value)
示例#23
0
def render_period(ws,
                  row,
                  result,
                  indicator,
                  period,
                  aggregate_targets=False,
                  use_indicator_target=False,
                  disaggregations={}):
    long_text_style = Style(alignment=Alignment(wrap_text=True))
    ws.set_cell_style(row, 1, long_text_style)
    ws.set_cell_value(row, 1, result.title)
    ws.set_cell_style(row, 2, long_text_style)
    ws.set_cell_value(row, 2, result.description)
    ws.set_cell_style(row, 3, long_text_style)
    ws.set_cell_value(row, 3, indicator.title)
    ws.set_cell_value(row, 4, f"{period.period_start} - {period.period_end}")
    ws.set_cell_value(row, 10, maybe_decimal(indicator.baseline_value))
    col = get_dynamic_column_start(aggregate_targets)
    if aggregate_targets:
        ws.set_cell_value(
            row, AGGREGATED_TARGET_VALUE_COLUMN,
            indicator.aggregated_target_value
            if use_indicator_target else period.aggregated_target_value)
    else:
        ws.set_cell_value(
            row, col, indicator.target_value
            if use_indicator_target else ensure_decimal(period.target_value))
    col += 1
    ws.set_cell_value(row, col, period.aggregated_value)
    if period.is_quantitative:
        col += 3
        for category, types in disaggregations.items():
            for type in [t for t in types.keys()]:
                ws.set_cell_value(
                    row, col,
                    period.get_aggregated_disaggregation_value(category, type)
                    or '')
                col += 1
                ws.set_cell_value(
                    row, col,
                    period.get_aggregated_disaggregation_target_value(
                        category, type) or '')
                col += 1
    return row + 1
示例#24
0
    def test_can_create_disaggregation_targets(self):
        org, _ = self.create_org_user('*****@*****.**', 'password')
        project = ProjectFixtureBuilder()\
            .with_partner(org, Partnership.IATI_REPORTING_ORGANISATION)\
            .with_disaggregations({
                'Gender': ['Male', 'Female'],
                'Age': ['Children', 'Adults']
            })\
            .with_results([{
                'title': 'Result #1',
                'indicators': [{
                    'title': 'Indicator #1',
                    'periods': [{
                        'period_start': date(2010, 1, 1),
                        'period_end': date(2010, 12, 31),
                        'target_value': 10,
                    }]
                }]
            }]).build()

        period = project.get_period(period_start=date(2010, 1, 1))
        male = project.get_disaggregation('Gender', 'Male')
        female = project.get_disaggregation('Gender', 'Female')
        data = {
            'target_value': 12,
            'disaggregation_targets': [
                {'value': 8, 'dimension_value': male.id},
                {'value': 10, 'dimension_value': female.id},
            ]
        }
        response = self.send_patch(period, data, username='******', password='******')

        self.assertEqual(response.status_code, 200)
        updated_period = project.get_period(period_start=date(2010, 1, 1))
        self.assertEqual(ensure_decimal(updated_period.target_value), 12)
        self.assertEqual(updated_period.get_disaggregation_target('Gender', 'Male').value, 8)
        self.assertEqual(updated_period.get_disaggregation_target('Gender', 'Female').value, 10)
示例#25
0
 def get_disaggregation_target_of(self, category, type):
     key = (category, type)
     if key not in self.disaggregation_targets:
         return None
     return ensure_decimal(self.disaggregation_targets[key].value)
示例#26
0
 def target_value(self):
     if self._target_value is None:
         self._target_value = ensure_decimal(self._real.target_value)
     return self._target_value
示例#27
0
 def actual_value(self):
     if self._actual_value is None:
         self._actual_value = ensure_decimal(self._real.actual_value)
     return self._actual_value
示例#28
0
 def sum_of_period_values(self):
     value = 0
     for period in self._periods:
         value += ensure_decimal(period.actual_value)
     return value
 def get_value(self, obj):
     return ensure_decimal(obj.value)
示例#30
0
def render_report(request, project_id):
    queryset = Project.objects.prefetch_related(
        'results', 'results__indicators', 'results__indicators__periods')
    project = get_object_or_404(queryset, pk=project_id)
    in_eutf_hierarchy = project.in_eutf_hierarchy()

    wb = Workbook()
    ws = wb.new_sheet('ResultsTable')
    ws.set_col_style(1, Style(size=75))
    ws.set_col_style(2, Style(size=75))
    ws.set_col_style(3, Style(size=41))
    ws.set_col_style(4, Style(size=18.5))
    ws.set_col_style(5, Style(size=34))
    ws.set_col_style(6, Style(size=37.5))
    ws.set_col_style(7, Style(size=47.5))
    ws.set_col_style(8, Style(size=20))
    ws.set_col_style(9, Style(size=20))
    ws.set_col_style(10, Style(size=34))
    ws.set_col_style(11, Style(size=20))
    ws.set_col_style(12, Style(size=20))
    ws.set_col_style(13, Style(size=20))
    ws.set_col_style(14, Style(size=24))
    ws.set_col_style(15, Style(size=20.5))
    ws.set_col_style(16, Style(size=30))
    ws.set_col_style(17, Style(size=22))
    ws.set_col_style(18, Style(size=21))

    # r1
    ws.set_row_style(1, Style(size=36))
    ws.set_cell_style(
        1, 1,
        Style(font=Font(bold=True, size=18, color=Color(255, 255, 255)),
              fill=Fill(background=Color(32, 56, 100)),
              alignment=Alignment(horizontal='center')))
    ws.set_cell_value(1, 1,
                      'Project Results and Indicators simple table report')

    # r2
    ws.set_row_style(2, Style(size=36))
    for i in range(1, 19):
        ws.set_cell_style(
            2, i,
            Style(font=Font(bold=True, size=14),
                  fill=Fill(background=Color(214, 234, 248)),
                  alignment=Alignment(horizontal='center'),
                  borders=Borders(top=Border(color=Color(0, 0, 0)),
                                  bottom=Border(color=Color(0, 0, 0)))))
    ws.set_cell_value(2, 1, 'Project name')
    ws.set_cell_value(2, 2, 'Project subtitle')
    ws.set_cell_value(2, 3, 'Result title')
    ws.set_cell_value(2, 4, 'Result type')
    ws.set_cell_value(2, 5, 'Result description')
    ws.set_cell_value(2, 6, 'Indicator title')
    ws.set_cell_value(2, 7, 'Indicator description')
    ws.set_cell_value(2, 8, 'Baseline year')
    ws.set_cell_value(2, 9, 'Baseline value')
    ws.set_cell_value(2, 10, 'Baseline comment')
    ws.set_cell_value(2, 11, 'Period start')
    ws.set_cell_value(2, 12, 'Period end')
    ws.set_cell_value(2, 13, 'Target value')
    ws.set_cell_value(2, 14, 'Target comment')
    ws.set_cell_value(2, 15, 'Actual value')
    ws.set_cell_value(2, 16, 'Actual comment')
    ws.set_cell_value(2, 17, 'Type')
    ws.set_cell_value(2, 18, 'Aggregation status')

    # r3
    row = 3
    ws.set_cell_value(row, 1, project.title)
    ws.set_cell_value(row, 2, project.subtitle)

    prev_type = ''
    curr_type = ''
    prev_agg_status = ''
    curr_agg_status = ''
    prev_indicator_type = ''
    curr_indicator_type = ''
    for result in project.results.exclude(type__exact='').all():
        ws.set_cell_value(row, 3, result.title)
        curr_type = result.iati_type().name
        if curr_type != prev_type:
            ws.set_cell_value(row, 4, curr_type)
            prev_type = curr_type
        ws.set_cell_style(row, 5, Style(alignment=Alignment(wrap_text=True)))
        ws.set_cell_value(row, 5, result.description)
        curr_agg_status = 'Yes' if result.aggregation_status else 'No'
        if curr_agg_status != prev_agg_status:
            ws.set_cell_value(row, 18, curr_agg_status)
            prev_agg_status = curr_agg_status

        for indicator in result.indicators.all():
            ws.set_cell_style(row, 6,
                              Style(alignment=Alignment(wrap_text=True)))
            ws.set_cell_value(row, 6, indicator.title)
            ws.set_cell_style(row, 7,
                              Style(alignment=Alignment(wrap_text=True)))
            ws.set_cell_value(row, 7, indicator.description)
            ws.set_cell_value(row, 8, indicator.baseline_year)
            ws.set_cell_value(row, 9, indicator.baseline_value)
            ws.set_cell_style(row, 10,
                              Style(alignment=Alignment(wrap_text=True)))
            ws.set_cell_value(row, 10, indicator.baseline_comment)
            curr_indicator_type = 'Qualitative' if indicator.type == '2' else 'Quantitative'
            if curr_indicator_type != prev_indicator_type:
                ws.set_cell_value(row, 17, curr_indicator_type)
                prev_indicator_type = curr_indicator_type

            for period in indicator.periods.all():
                ws.set_cell_value(
                    row, 11, utils.get_period_start(period, in_eutf_hierarchy))
                ws.set_cell_value(
                    row, 12, utils.get_period_end(period, in_eutf_hierarchy))
                ws.set_cell_value(row, 13, period.target_value)
                ws.set_cell_style(row, 14,
                                  Style(alignment=Alignment(wrap_text=True)))
                ws.set_cell_value(row, 14, period.target_comment)
                ws.set_cell_value(row, 15, ensure_decimal(period.actual_value))
                ws.set_cell_style(row, 16,
                                  Style(alignment=Alignment(wrap_text=True)))
                ws.set_cell_value(row, 16, period.actual_comment)

                ws.set_row_style(row, Style(size=68))
                row += 1

    filename = '{}-{}-eutf-project-results-indicators-report.xlsx'.format(
        datetime.today().strftime('%Y%b%d'), project.id)

    return utils.make_excel_response(wb, filename)