def _consolidate_multipart_stats(self): """Sums the values of multipart polygons together to display only one. """ LOGGER.debug("Consolidating multipart postprocessing results") # copy needed because of # self.output[postprocessor].pop(corrected_index) output = self.output # iterate postprocessors for postprocessor, results_list in output.iteritems(): # see self._generateTables to see details about results_list checked_polygon_names = {} parts_to_delete = [] polygon_index = 0 # iterate polygons for polygon_name, results in results_list: if polygon_name in checked_polygon_names.keys(): for result_name, result in results.iteritems(): first_part_index = checked_polygon_names[polygon_name] first_part = self.output[postprocessor][first_part_index] first_part_results = first_part[1] first_part_result = first_part_results[result_name] # FIXME one of the parts was 'No data', # is it matematically correct to do no_data = 0? # see http://irclogs.geoapt.com/inasafe/ # %23inasafe.2013-08-09.log (at 22.29) no_data = self.aggregator.get_default_keyword("NO_DATA") # both are No data value = first_part_result["value"] result_value = result["value"] if value == no_data and result_value == no_data: new_result = no_data else: # one is No data if value == no_data: value = 0 # the other is No data elif result_value == no_data: result_value = 0 # here none is No data new_result = unhumanize_number(value) + unhumanize_number(result_value) first_part_result["value"] = format_int(new_result) parts_to_delete.append(polygon_index) else: # add polygon to checked list checked_polygon_names[polygon_name] = polygon_index polygon_index += 1 # http://stackoverflow.com/questions/497426/ # deleting-multiple-elements-from-a-list results_list = [res for j, res in enumerate(results_list) if j not in parts_to_delete] self.output[postprocessor] = results_list
def _sort_no_data(self, data): """Check if the value field of the postprocessor is NO_DATA. This is used for sorting, it returns -1 if the value is NO_DATA, so that no data items can be put at the end of a list :param data: Value to be checked. :type data: list :returns: -1 if the value is NO_DATA else the value :rtype: int, float """ post_processor = self.output[self.current_output_postprocessor] # get the key position of the value field try: key = post_processor[0][1].keys()[0] except IndexError: return -1 # get the value # data[1] is the orderedDict # data[1][myFirstKey] is the 1st indicator in the orderedDict if (data[1][key]['value'] == self.aggregator.get_default_keyword( 'NO_DATA')): position = -1 else: position = data[1][key]['value'] position = unhumanize_number(position) return position
def _sortNoData(self, data): """Check if the value field of the postprocessor is NO_DATA. this is used for sorting, it returns -1 if the value is NO_DATA, so that no data items can be put at the end of a list Args: list - data Returns: returns -1 if the value is NO_DATA else the value """ myPostprocessor = self.postProcessingOutput[ self._currentOutputPostprocessor] #get the key position of the value field myValueKey = myPostprocessor[0][1].keyAt(0) #get the value # data[1] is the orderedDict # data[1][myFirstKey] is the 1st indicator in the orderedDict if data[1][myValueKey]['value'] == self.aggregator.defaults['NO_DATA']: myPosition = -1 else: myPosition = data[1][myValueKey]['value'] myPosition = unhumanize_number(myPosition) return myPosition
def _generate_data(self, aoi_mode=True): """Parses the postprocessing output as dictionary. :param aoi_mode: adds a Total in aggregation areas row to the calculated table :type aoi_mode: bool :returns: The dictionary of postprocessing. :rtype: dict """ result = {} for processor, results_list in self.output.iteritems(): self.current_output_postprocessor = processor # results_list is for example: # [ # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([ # (u'Total', {'value': 977536, 'metadata': {}}), # (u'Female population', {'value': 508319, 'metadata': {}}), # (u'Weekly hygiene packs', {'value': 403453, 'metadata': { # 'description': 'Females hygiene packs for weekly use'}}) # ])) # ] # sorting using the first indicator of a postprocessor sorted_results = sorted(results_list, key=self._sort_no_data, reverse=True) # init table has_no_data = False table = {'notes': []} name = get_postprocessor_human_name(processor).lower() translated_name = tr(name) if name == 'building type': table['caption'] = tr('Closed buildings') elif name == 'road type': table['caption'] = tr('Closed roads') elif name == 'people': table['caption'] = tr('Affected people') # Dirty hack to make "evacuated" come out in the report. # Currently only MinimumNeeds that calculate from evacuation # percentage. if processor == 'MinimumNeeds': if 'evacuation_percentage' in self.function_parameters.keys(): table['caption'] = tr( 'Detailed %s report ' '(for people needing evacuation)') % translated_name else: table['caption'] = tr( 'Detailed %s report ' '(affected people)') % translated_name if processor in ['Gender', 'Age']: table['caption'] = tr('Detailed %s report ' '(affected people)') % translated_name try: empty_table = not sorted_results[0][1] except IndexError: empty_table = True if empty_table: # The table is empty. # Due to an error or because every lines were removed. table['attributes'] = [] table['fields'] = [] table['notes'].append( tr('The report "%s" is empty.') % translated_name) result['processor'] = table continue header = [str(self.attribute_title).capitalize()] for calculation_name in sorted_results[0][1]: header.append(self.tr(calculation_name)) table['attributes'] = header # used to calculate the totals row as per issue #690 postprocessor_totals = OrderedDict() null_index = 0 # counting how many null value in the data fields = [] for zone_name, calc in sorted_results: if isinstance(zone_name, QPyNullVariant): # I have made sure that the zone_name won't be Null in # run method. But just in case there is something wrong. zone_name = tr('Unnamed Area %s' % null_index) null_index += 1 if name == 'road type': # We add the unit 'meter' as we are counting roads. zone_name = tr('%(zone_name)s (m)' % {'zone_name': zone_name}) row = [zone_name] for indicator, calculation_data in calc.iteritems(): value = calculation_data['value'] value = str(unhumanize_number(value)) if value == self.aggregator.get_default_keyword('NO_DATA'): has_no_data = True try: postprocessor_totals[indicator] += 0 except KeyError: postprocessor_totals[indicator] = 0 else: value = int(value) try: postprocessor_totals[indicator] += value except KeyError: postprocessor_totals[indicator] = value row.append(value) fields.append(row) if not aoi_mode: # add the totals row row = [self.tr('Total in aggregation areas')] for _, total in postprocessor_totals.iteritems(): row.append(total) fields.append(row) table['fields'] = fields if has_no_data: table['notes'].append( self. tr('"%s" values mean that there where some problems while ' 'calculating them. This did not affect the other ' 'values.') % (self.aggregator.get_default_keyword('NO_DATA'))) table['notes'].append( self.tr( 'Columns and rows containing only 0 or "%s" values are ' 'excluded from the tables.' % self.aggregator.get_default_keyword('NO_DATA'))) result[processor] = table return result
def _consolidate_multipart_stats(self): """Sums the values of multipart polygons together to display only one. """ LOGGER.debug('Consolidating multipart postprocessing results') # copy needed because of # self.output[postprocessor].pop(corrected_index) output = self.output # iterate postprocessors for postprocessor, results_list in output.iteritems(): # see self._generateTables to see details about results_list checked_polygon_names = {} parts_to_delete = [] polygon_index = 0 # iterate polygons for polygon_name, results in results_list: if polygon_name in checked_polygon_names.keys(): for result_name, result in results.iteritems(): first_part_index = checked_polygon_names[polygon_name] first_part = self.output[postprocessor][ first_part_index] first_part_results = first_part[1] first_part_result = first_part_results[result_name] # FIXME one of the parts was 'No data', # is it matematically correct to do no_data = 0? # see http://irclogs.geoapt.com/inasafe/ # %23inasafe.2013-08-09.log (at 22.29) no_data = \ self.aggregator.get_default_keyword('NO_DATA') # both are No data value = first_part_result['value'] result_value = result['value'] if value == no_data and result_value == no_data: new_result = no_data else: # one is No data if value == no_data: value = 0 # the other is No data elif result_value == no_data: result_value = 0 # here none is No data new_result = (unhumanize_number(value) + unhumanize_number(result_value)) first_part_result['value'] = format_int(new_result) parts_to_delete.append(polygon_index) else: # add polygon to checked list checked_polygon_names[polygon_name] = polygon_index polygon_index += 1 # http://stackoverflow.com/questions/497426/ # deleting-multiple-elements-from-a-list results_list = [ res for j, res in enumerate(results_list) if j not in parts_to_delete ] self.output[postprocessor] = results_list
def _generate_tables(self, aoi_mode=True): """Parses the postprocessing output as one table per postprocessor. TODO: This should rather return json and then have a helper method to make html from the JSON. :param aoi_mode: adds a Total in aggregation areas row to the calculated table :type aoi_mode: bool :returns: The html. :rtype: str """ message = m.Message() for processor, results_list in self.output.iteritems(): self.current_output_postprocessor = processor # results_list is for example: # [ # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([ # (u'Total', {'value': 977536, 'metadata': {}}), # (u'Female population', {'value': 508319, 'metadata': {}}), # (u'Weekly hygiene packs', {'value': 403453, 'metadata': { # 'description': 'Females hygiene packs for weekly use'}}) # ])) # ] # sorting using the first indicator of a postprocessor sorted_results = sorted(results_list, key=self._sort_no_data, reverse=True) # init table has_no_data = False table = m.Table(style_class='table table-condensed table-striped') name = get_postprocessor_human_name(processor).lower() translated_name = tr(name) if name == 'building type': table.caption = tr('Closed buildings') elif name == 'road type': table.caption = tr('Closed roads') elif name == 'people': table.caption = tr('Affected people') # Dirty hack to make "evacuated" come out in the report. # Currently only MinimumNeeds that calculate from evacuation # percentage. if processor == 'MinimumNeeds': if 'evacuation_percentage' in self.function_parameters.keys(): table.caption = tr( 'Detailed %s report ' '(for people needing evacuation)') % translated_name else: table.caption = tr('Detailed %s report ' '(affected people)') % translated_name if processor in ['Gender', 'Age']: table.caption = tr('Detailed %s report ' '(affected people)') % translated_name empty_table = not sorted_results[0][1] if empty_table: # Due to an error? The table is empty. message.add(table) message.add( m.EmphasizedText( tr('Could not compute the %s report.') % translated_name)) continue header = m.Row() header.add(str(self.attribute_title).capitalize()) for calculation_name in sorted_results[0][1]: header.add(self.tr(calculation_name)) table.add(header) # used to calculate the totals row as per issue #690 postprocessor_totals = OrderedDict() null_index = 0 # counting how many null value in the data for zone_name, calc in sorted_results: if isinstance(zone_name, QPyNullVariant): # I have made sure that the zone_name won't be Null in # run method. But just in case there is something wrong. zone_name = tr('Unnamed Area %s' % null_index) null_index += 1 if name == 'road type': # We add the unit 'meter' as we are counting roads. # proper format for i186 zone_name = tr('%(zone_name)s (m)') % { 'zone_name': tr(zone_name) } row = m.Row(zone_name) for indicator, calculation_data in calc.iteritems(): value = calculation_data['value'] value = str(unhumanize_number(value)) if value == self.aggregator.get_default_keyword('NO_DATA'): has_no_data = True try: postprocessor_totals[indicator] += 0 except KeyError: postprocessor_totals[indicator] = 0 else: value = int(value) try: postprocessor_totals[indicator] += value except KeyError: postprocessor_totals[indicator] = value row.add(format_int(value)) table.add(row) if not aoi_mode: # add the totals row row = m.Row(self.tr('Total in aggregation areas')) for _, total in postprocessor_totals.iteritems(): row.add(format_int(total)) table.add(row) # add table to message message.add(table) if has_no_data: message.add( m.EmphasizedText( self. tr('"%s" values mean that there where some problems while ' 'calculating them. This did not affect the other ' 'values.') % (self.aggregator.get_default_keyword('NO_DATA')))) caption = m.EmphasizedText( self.tr( 'Columns and rows containing only 0 or "%s" values are ' 'excluded from the tables.' % self.aggregator.get_default_keyword('NO_DATA'))) message.add(m.Paragraph(caption, style_class='caption')) return message
def _generate_tables(self, aoi_mode=True): """Parses the postprocessing output as one table per postprocessor. TODO: This should rather return json and then have a helper method to make html from the JSON. :param aoi_mode: adds a Total in aggregation areas row to the calculated table :type aoi_mode: bool :returns: The html. :rtype: str """ message = m.Message() for processor, results_list in self.output.iteritems(): self.current_output_postprocessor = processor # results_list is for example: # [ # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([ # (u'Total', {'value': 977536, 'metadata': {}}), # (u'Female population', {'value': 508319, 'metadata': {}}), # (u'Weekly hygiene packs', {'value': 403453, 'metadata': { # 'description': 'Females hygiene packs for weekly use'}}) # ])) # ] # sorting using the first indicator of a postprocessor sorted_results = sorted( results_list, key=self._sort_no_data, reverse=True) # init table has_no_data = False table = m.Table( style_class='table table-condensed table-striped') table.caption = self.tr('Detailed %s report') % (tr( get_postprocessor_human_name(processor)).lower()) # Dirty hack to make "evacuated" comes out in the report. # Currently only MinimumNeeds that calculate from evacuation # percentage. if processor == 'MinimumNeeds': if 'evacuation_percentage' in self.function_parameters.keys(): table.caption = self.tr( 'Detailed %s report (for people needing ' 'evacuation)') % ( tr(get_postprocessor_human_name(processor)).lower() ) else: table.caption = self.tr( 'Detailed %s report (affected people)') % ( tr(get_postprocessor_human_name(processor)).lower() ) if processor in ['Gender', 'Age']: table.caption = self.tr( 'Detailed %s report (affected people)') % ( tr(get_postprocessor_human_name(processor)).lower()) header = m.Row() header.add(str(self.attribute_title).capitalize()) for calculation_name in sorted_results[0][1]: header.add(self.tr(calculation_name)) table.add(header) # used to calculate the totals row as per issue #690 postprocessor_totals = OrderedDict() for zone_name, calc in sorted_results: row = m.Row(zone_name) for indicator, calculation_data in calc.iteritems(): value = calculation_data['value'] value = str(unhumanize_number(value)) if value == self.aggregator.get_default_keyword('NO_DATA'): has_no_data = True value += ' *' try: postprocessor_totals[indicator] += 0 except KeyError: postprocessor_totals[indicator] = 0 else: value = int(value) try: postprocessor_totals[indicator] += value except KeyError: postprocessor_totals[indicator] = value row.add(format_int(value)) table.add(row) if not aoi_mode: # add the totals row row = m.Row(self.tr('Total in aggregation areas')) for _, total in postprocessor_totals.iteritems(): row.add(format_int(total)) table.add(row) # add table to message message.add(table) if has_no_data: message.add(m.EmphasizedText(self.tr( '* "%s" values mean that there where some problems while ' 'calculating them. This did not affect the other ' 'values.') % ( self.aggregator.get_default_keyword( 'NO_DATA')))) return message
def _generate_tables(self, aoi_mode=True): """Parses the postprocessing output as one table per postprocessor. TODO: This should rather return json and then have a helper method to make html from the JSON. :param aoi_mode: adds a Total in aggregation areas row to the calculated table :type aoi_mode: bool :returns: The html. :rtype: str """ message = m.Message() for processor, results_list in self.output.iteritems(): self.current_output_postprocessor = processor # results_list is for example: # [ # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([ # (u'Total', {'value': 977536, 'metadata': {}}), # (u'Female population', {'value': 508319, 'metadata': {}}), # (u'Weekly hygiene packs', {'value': 403453, 'metadata': { # 'description': 'Females hygiene packs for weekly use'}}) # ])) # ] # sorting using the first indicator of a postprocessor sorted_results = sorted( results_list, key=self._sort_no_data, reverse=True) # init table has_no_data = False table = m.Table( style_class='table table-condensed table-striped') name = get_postprocessor_human_name(processor).lower() if name == 'building type': table.caption = self.tr('Closed buildings') elif name == 'road type': table.caption = self.tr('Closed roads') elif name == 'people': table.caption = self.tr('Affected people') # Dirty hack to make "evacuated" come out in the report. # Currently only MinimumNeeds that calculate from evacuation # percentage. if processor == 'MinimumNeeds': if 'evacuation_percentage' in self.function_parameters.keys(): table.caption = self.tr( 'Detailed %s report (for people needing ' 'evacuation)') % ( tr(get_postprocessor_human_name(processor)).lower() ) else: table.caption = self.tr( 'Detailed %s report (affected people)') % ( tr(get_postprocessor_human_name(processor)).lower() ) if processor in ['Gender', 'Age']: table.caption = self.tr( 'Detailed %s report (affected people)') % ( tr(get_postprocessor_human_name(processor)).lower()) empty_table = not sorted_results[0][1] if empty_table: # Due to an error? The table is empty. message.add(table) message.add(m.EmphasizedText(self.tr( 'Could not compute the %s report.' % tr(get_postprocessor_human_name(processor)).lower()))) continue header = m.Row() header.add(str(self.attribute_title).capitalize()) for calculation_name in sorted_results[0][1]: header.add(self.tr(calculation_name)) table.add(header) # used to calculate the totals row as per issue #690 postprocessor_totals = OrderedDict() null_index = 0 # counting how many null value in the data for zone_name, calc in sorted_results: if isinstance(zone_name, QPyNullVariant): # I have made sure that the zone_name won't be Null in # run method. But just in case there is something wrong. zone_name = 'Unnamed Area %s' % null_index null_index += 1 if name == 'road type': # We add the unit 'meter' as we are counting roads. zone_name = '%s (m)' % zone_name row = m.Row(zone_name) for indicator, calculation_data in calc.iteritems(): value = calculation_data['value'] value = str(unhumanize_number(value)) if value == self.aggregator.get_default_keyword('NO_DATA'): has_no_data = True value += ' *' try: postprocessor_totals[indicator] += 0 except KeyError: postprocessor_totals[indicator] = 0 else: value = int(value) try: postprocessor_totals[indicator] += value except KeyError: postprocessor_totals[indicator] = value row.add(format_int(value)) table.add(row) if not aoi_mode: # add the totals row row = m.Row(self.tr('Total in aggregation areas')) for _, total in postprocessor_totals.iteritems(): row.add(format_int(total)) table.add(row) # add table to message message.add(table) if has_no_data: message.add(m.EmphasizedText(self.tr( '* "%s" values mean that there where some problems while ' 'calculating them. This did not affect the other ' 'values.') % ( self.aggregator.get_default_keyword( 'NO_DATA')))) caption = m.EmphasizedText(self.tr( 'Columns containing exclusively 0 and "%s" ' 'have not been shown in the table.' % self.aggregator.get_default_keyword('NO_DATA'))) message.add( m.Paragraph( caption, style_class='caption') ) return message
def _consolidate_multipart_stats(self): """Sums the values of multipart polygons together to display only one. Args: None Returns: None Raises: None """ LOGGER.debug('Consolidating multipart postprocessing results') # copy needed because of # self.postProcessingOutput[proc].pop(corrected_index) postProcessingOutput = self.postProcessingOutput # iterate postprocessors for proc, results_list in postProcessingOutput.iteritems(): #see self._generateTables to see details about results_list checked_polygon_names = {} parts_to_delete = [] polygon_index = 0 # iterate polygons for polygon_name, results in results_list: if polygon_name in checked_polygon_names.keys(): LOGGER.debug('%s postprocessor found multipart polygon ' 'with name %s' % (proc, polygon_name)) for result_name, result in results.iteritems(): first_part_index = checked_polygon_names[polygon_name] first_part = self.postProcessingOutput[proc][ first_part_index] first_part_results = first_part[1] first_part_result = first_part_results[result_name] # FIXME one of the parts was 'No data', # see http://irclogs.geoapt.com/inasafe/ # %23inasafe.2013-08-09.log (at 22.29) no_data = self.aggregator.defaults['NO_DATA'] # both are No data value = first_part_result['value'] result_value = result['value'] if value == no_data and result_value == no_data: new_result = no_data else: # one is No data if value == no_data and result_value != no_data: first_part_result['value'] = 0 # the other is No data elif value != no_data and result_value == no_data: result['value'] = 0 #if we got here, none is No data new_result = ( unhumanize_number(value) + unhumanize_number(result_value)) first_part_result['value'] = format_int(new_result) parts_to_delete.append(polygon_index) else: # add polygon to checked list checked_polygon_names[polygon_name] = polygon_index polygon_index += 1 # http://stackoverflow.com/questions/497426/ # deleting-multiple-elements-from-a-list results_list = [res for j, res in enumerate(results_list) if j not in parts_to_delete] self.postProcessingOutput[proc] = results_list
def _generate_data(self, aoi_mode=True): """Parses the postprocessing output as dictionary. :param aoi_mode: adds a Total in aggregation areas row to the calculated table :type aoi_mode: bool :returns: The dictionary of postprocessing. :rtype: dict """ result = {} for processor, results_list in self.output.iteritems(): self.current_output_postprocessor = processor # results_list is for example: # [ # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([ # (u'Total', {'value': 977536, 'metadata': {}}), # (u'Female population', {'value': 508319, 'metadata': {}}), # (u'Weekly hygiene packs', {'value': 403453, 'metadata': { # 'description': 'Females hygiene packs for weekly use'}}) # ])) # ] # sorting using the first indicator of a postprocessor sorted_results = sorted( results_list, key=self._sort_no_data, reverse=True) # init table has_no_data = False table = {'notes': []} name = get_postprocessor_human_name(processor).lower() translated_name = tr(name) if name == 'building type': table['caption'] = tr('Closed buildings') elif name == 'road type': table['caption'] = tr('Closed roads') elif name == 'people': table['caption'] = tr('Affected people') # Dirty hack to make "evacuated" come out in the report. # Currently only MinimumNeeds that calculate from evacuation # percentage. if processor == 'MinimumNeeds': if 'evacuation_percentage' in self.function_parameters.keys(): table['caption'] = tr( 'Detailed %s report ' '(for people needing evacuation)') % translated_name else: table['caption'] = tr( 'Detailed %s report ' '(affected people)') % translated_name if processor in ['Gender', 'Age']: table['caption'] = tr( 'Detailed %s report ' '(affected people)') % translated_name try: empty_table = not sorted_results[0][1] except IndexError: empty_table = True if empty_table: # The table is empty. # Due to an error or because every lines were removed. table['attributes'] = [] table['fields'] = [] table['notes'].append( tr('The report "%s" is empty.') % translated_name) result['processor'] = table continue header = [str(self.attribute_title).capitalize()] for calculation_name in sorted_results[0][1]: header.append(self.tr(calculation_name)) table['attributes'] = header # used to calculate the totals row as per issue #690 postprocessor_totals = OrderedDict() null_index = 0 # counting how many null value in the data fields = [] for zone_name, calc in sorted_results: if isinstance(zone_name, QPyNullVariant): # I have made sure that the zone_name won't be Null in # run method. But just in case there is something wrong. zone_name = tr('Unnamed Area %s' % null_index) null_index += 1 if name == 'road type': # We add the unit 'meter' as we are counting roads. zone_name = tr( '%(zone_name)s (m)' % {'zone_name': zone_name}) row = [zone_name] for indicator, calculation_data in calc.iteritems(): value = calculation_data['value'] value = str(unhumanize_number(value)) if value == self.aggregator.get_default_keyword('NO_DATA'): has_no_data = True try: postprocessor_totals[indicator] += 0 except KeyError: postprocessor_totals[indicator] = 0 else: value = int(value) try: postprocessor_totals[indicator] += value except KeyError: postprocessor_totals[indicator] = value row.append(value) fields.append(row) if not aoi_mode: # add the totals row row = [self.tr('Total in aggregation areas')] for _, total in postprocessor_totals.iteritems(): row.append(total) fields.append(row) table['fields'] = fields if has_no_data: table['notes'].append(self.tr( '"%s" values mean that there where some problems while ' 'calculating them. This did not affect the other ' 'values.') % ( self.aggregator.get_default_keyword('NO_DATA'))) table['notes'].append(self.tr( 'Columns and rows containing only 0 or "%s" values are ' 'excluded from the tables.' % self.aggregator.get_default_keyword('NO_DATA'))) result[processor] = table return result
def _generate_tables(self, aoi_mode=True): """Parses the postprocessing output as one table per postprocessor. TODO: This should rather return json and then have a helper method to make html from the JSON. :param aoi_mode: adds a Total in aggregation areas row to the calculated table :type aoi_mode: bool :returns: The html. :rtype: str """ message = m.Message() for processor, results_list in self.output.iteritems(): self.current_output_postprocessor = processor # results_list is for example: # [ # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([ # (u'Total', {'value': 977536, 'metadata': {}}), # (u'Female population', {'value': 508319, 'metadata': {}}), # (u'Weekly hygiene packs', {'value': 403453, 'metadata': { # 'description': 'Females hygiene packs for weekly use'}}) # ])) # ] # sorting using the first indicator of a postprocessor sorted_results = sorted( results_list, key=self._sort_no_data, reverse=True) # init table has_no_data = False table = m.Table( style_class='table table-condensed table-striped') table.caption = self.tr('Detailed %s report') % (safeTr( get_postprocessor_human_name(processor)).lower()) header = m.Row() header.add(str(self.attribute_title).capitalize()) for calculation_name in sorted_results[0][1]: header.add(self.tr(calculation_name)) table.add(header) # used to calculate the totals row as per issue #690 postprocessor_totals = OrderedDict() for zone_name, calc in sorted_results: row = m.Row(zone_name) for indicator, calculation_data in calc.iteritems(): value = calculation_data['value'] value = str(unhumanize_number(value)) if value == self.aggregator.get_default_keyword('NO_DATA'): has_no_data = True value += ' *' try: postprocessor_totals[indicator] += 0 except KeyError: postprocessor_totals[indicator] = 0 else: value = int(value) try: postprocessor_totals[indicator] += value except KeyError: postprocessor_totals[indicator] = value row.add(format_int(value)) table.add(row) if not aoi_mode: # add the totals row row = m.Row(self.tr('Total in aggregation areas')) for _, total in postprocessor_totals.iteritems(): row.add(format_int(total)) table.add(row) # add table to message message.add(table) if has_no_data: message.add(m.EmphasizedText(self.tr( '* "%s" values mean that there where some problems while ' 'calculating them. This did not affect the other ' 'values.') % ( self.aggregator.get_default_keyword('NO_DATA')))) return message