def test_layer_hazard_classification(self): """Test layer_hazard_classification method. .. versionadded:: 4.0 """ layer_paths = self.layer_paths_list expected_classifications = [ generic_hazard_classes, earthquake_mmi_scale, tsunami_hazard_classes, cyclone_au_bom_hazard_classes, None, None, None, None, ] for layer_path, expected_classification in zip( layer_paths, expected_classifications): path = standard_data_path(*layer_path) layer, _ = load_layer(path) # inject classification keyword if expected_classification: layer.keywords['classification'] = ( expected_classification['key']) actual_classification = layer_hazard_classification(layer) try: self.assertEqual(expected_classification, actual_classification) except Exception as e: LOGGER.error('Layer path: {path}'.format(path=path)) LOGGER.error( 'Expected {name}'.format(**expected_classification)) LOGGER.error('Actual {name}'.format(**actual_classification)) raise e
def test_layer_hazard_classification(self): """Test layer_hazard_classification method. .. versionadded:: 4.0 """ layer_paths = self.layer_paths_list expected_classifications = [ generic_hazard_classes, earthquake_mmi_scale, tsunami_hazard_classes, cyclone_au_bom_hazard_classes, None, None, None, None, ] for layer_path, expected_classification in zip( layer_paths, expected_classifications): path = standard_data_path(*layer_path) layer, _ = load_layer(path) # inject classification keyword if expected_classification: layer.keywords['classification'] = ( expected_classification['key']) actual_classification = layer_hazard_classification(layer) try: self.assertEqual( expected_classification, actual_classification) except Exception as e: LOGGER.error('Layer path: {path}'.format( path=path)) LOGGER.error('Expected {name}'.format( **expected_classification)) LOGGER.error('Actual {name}'.format( **actual_classification)) raise e
def general_report_extractor(impact_report, component_metadata): """Extracting general analysis result from the impact layer. :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} extra_args = component_metadata.extra_args # figure out analysis report type hazard_layer = impact_report.hazard exposure_layer = impact_report.exposure analysis_layer = impact_report.analysis provenance = impact_report.impact_function.provenance debug_mode = impact_report.impact_function.debug_mode exposure_type = layer_definition_type(exposure_layer) # Only round the number when it is population exposure and it is not # in debug mode is_rounded = not debug_mode is_population = exposure_type is exposure_population # find hazard class summary = [] analysis_feature = analysis_layer.getFeatures().next() analysis_inasafe_fields = analysis_layer.keywords['inasafe_fields'] exposure_unit = exposure_type['units'][0] hazard_header = resolve_from_dictionary(extra_args, 'hazard_header') if exposure_unit['abbreviation']: value_header = u'{measure} ({abbreviation})'.format(**exposure_unit) else: value_header = u'{name}'.format(**exposure_unit) # in case there is a classification if 'classification' in hazard_layer.keywords: # retrieve hazard classification from hazard layer hazard_classification = layer_hazard_classification(hazard_layer) # classified hazard must have hazard count in analysis layer hazard_stats = [] for hazard_class in hazard_classification['classes']: # hazard_count_field is a dynamic field with hazard class # as parameter field_key_name = hazard_count_field['key'] % ( hazard_class['key'], ) try: # retrieve dynamic field name from analysis_fields keywords # will cause key error if no hazard count for that particular # class field_name = analysis_inasafe_fields[field_key_name] field_index = analysis_layer.fieldNameIndex(field_name) # Hazard label taken from translated hazard count field # label, string-formatted with translated hazard class label hazard_label = hazard_class['name'] hazard_value = format_number(analysis_feature[field_index], enable_rounding=is_rounded, is_population=is_population) stats = { 'key': hazard_class['key'], 'name': hazard_label, 'value': hazard_value } except KeyError: # in case the field was not found hazard_label = hazard_class['name'] stats = { 'key': hazard_class['key'], 'name': hazard_label, 'value': 0, } hazard_stats.append(stats) # find total field try: field_name = analysis_inasafe_fields[total_field['key']] total = value_from_field_name(field_name, analysis_layer) total = format_number(total, enable_rounding=is_rounded, is_population=is_population) stats = { 'key': total_field['key'], 'name': total_field['name'], 'as_header': True, 'value': total } hazard_stats.append(stats) except KeyError: pass summary.append({ 'header_label': hazard_header, 'value_label': value_header, 'rows': hazard_stats }) # retrieve affected column report_stats = [] reported_fields = resolve_from_dictionary(extra_args, 'reported_fields') for item in reported_fields: header = item['header'] field = item['field'] if field['key'] in analysis_inasafe_fields: field_index = analysis_layer.fieldNameIndex(field['field_name']) if field == fatalities_field: # For fatalities field, we show a range of number # instead row_value = fatalities_range(analysis_feature[field_index]) else: row_value = format_number(analysis_feature[field_index], enable_rounding=is_rounded, is_population=is_population) row_stats = { 'key': field['key'], 'name': header, 'value': row_value } report_stats.append(row_stats) # Give report section exposure_type = layer_definition_type(exposure_layer) header_label = exposure_type['name'] summary.append({ 'header_label': header_label, # This should depend on exposure unit # TODO: Change this so it can take the unit dynamically 'value_label': value_header, 'rows': report_stats }) header = resolve_from_dictionary(extra_args, ['header']) table_header_format = resolve_from_dictionary(extra_args, 'table_header_format') table_header = table_header_format.format( title=provenance['map_legend_title'], unit=hazard_classification['classification_unit']) # Section notes note_format = resolve_from_dictionary(extra_args, ['concept_notes', 'note_format']) if is_population: concepts = resolve_from_dictionary( extra_args, ['concept_notes', 'population_concepts']) else: concepts = resolve_from_dictionary( extra_args, ['concept_notes', 'general_concepts']) notes = [] for concept in concepts: note = note_format.format(**concept) notes.append(note) context['header'] = header context['summary'] = summary context['table_header'] = table_header context['notes'] = notes return context
def infographic_people_section_notes_extractor( impact_report, component_metadata): """Extracting notes for people section in the infographic. :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.2 """ hazard_layer = impact_report.hazard extra_args = component_metadata.extra_args context = {} context['notes'] = [] note = { 'title': None, 'description': resolve_from_dictionary(extra_args, 'extra_note'), 'citations': None } context['notes'].append(note) concept_keys = ['affected_people', 'displaced_people'] for key in concept_keys: note = { 'title': concepts[key].get('name'), 'description': concepts[key].get('description'), 'citations': concepts[key].get('citations')[0]['text'] } context['notes'].append(note) hazard_classification = layer_hazard_classification(hazard_layer) # generate rate description displacement_rates_note_format = resolve_from_dictionary( extra_args, 'hazard_displacement_rates_note_format') displacement_rates_note = [] for hazard_class in hazard_classification['classes']: hazard_class['classification_unit'] = ( hazard_classification['classification_unit']) displacement_rates_note.append( displacement_rates_note_format.format(**hazard_class)) rate_description = ', '.join(displacement_rates_note) note = { 'title': concepts['displacement_rate'].get('name'), 'description': rate_description, 'citations': concepts['displacement_rate'].get('citations')[0]['text'] } context['notes'].append(note) return context
def analysis_detail_extractor(impact_report, component_metadata): """Extracting analysis result from the impact layer. :param impact_report: The impact report that acts as a proxy to fetch all the data that extractor needed. :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: The component metadata. Used to obtain information about the component we want to render. :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: Context for rendering phase. :rtype: dict .. versionadded:: 4.0 """ context = {} extra_args = component_metadata.extra_args hazard_layer = impact_report.hazard exposure_layer = impact_report.exposure analysis_layer = impact_report.analysis analysis_layer_fields = analysis_layer.keywords['inasafe_fields'] analysis_feature = analysis_layer.getFeatures().next() exposure_summary_table = impact_report.exposure_summary_table if exposure_summary_table: exposure_summary_table_fields = exposure_summary_table.keywords[ 'inasafe_fields'] provenance = impact_report.impact_function.provenance debug_mode = impact_report.impact_function.debug_mode """Initializations""" # Get hazard classification hazard_classification = layer_hazard_classification(hazard_layer) # Get exposure type definition exposure_type = layer_definition_type(exposure_layer) # Only round the number when it is population exposure and it is not # in debug mode is_rounding = not debug_mode is_population = exposure_type is exposure_population # Analysis detail only applicable for breakable exposure types: itemizable_exposures_all = [ exposure for exposure in exposure_all if exposure.get('classifications') ] if exposure_type not in itemizable_exposures_all: return context # Get breakdown field breakdown_field = None # I'm not sure what's the difference # It is possible to have exposure_type_field or exposure_class_field # at the moment breakdown_fields = [exposure_type_field, exposure_class_field] for field in breakdown_fields: if field['key'] in exposure_summary_table_fields: breakdown_field = field break """Create detail header""" headers = [] # breakdown header breakdown_header_template = '' if breakdown_field == exposure_type_field: breakdown_header_template = resolve_from_dictionary( extra_args, 'breakdown_header_type_format') elif breakdown_field == exposure_class_field: breakdown_header_template = resolve_from_dictionary( extra_args, 'breakdown_header_class_format') # check if there is header type associations type_header_mapping = resolve_from_dictionary( extra_args, 'exposure_type_header_mapping') if exposure_type['key'] in type_header_mapping: exposure_header = type_header_mapping[exposure_type['key']] else: exposure_header = exposure_type['name'] headers.append(breakdown_header_template.format(exposure=exposure_header)) # this is mapping for customizing double header for # affected/not affected hazard classes hazard_class_header_mapping = resolve_from_dictionary( extra_args, 'hazard_class_header_mapping') # hazard header # TODO: we need to get affected and not_affected key from # definitions concept header_hazard_group = { 'affected': { 'hazards': [], 'total': [] }, 'not_affected': { 'hazards': [], 'total': [] } } for key, group in header_hazard_group.iteritems(): if key in hazard_class_header_mapping: header_hazard_group[key].update(hazard_class_header_mapping[key]) affected_header_index = None for index, hazard_class in enumerate(hazard_classification['classes']): # the tuple format would be: # (class name, is it affected, header background color hazard_class_name = hazard_class['name'] affected = hazard_class.get('affected') if not affected and not affected_header_index: affected_header_index = index + 1 affected_status = 'not_affected' elif affected: affected_status = 'affected' else: affected_status = 'not_affected' header_hazard_group[affected_status]['hazards'].append( hazard_class_name) headers.append(hazard_class_name) if affected_header_index: not_affected_header_index = len(hazard_classification['classes']) + 2 else: affected_header_index = len(hazard_classification['classes']) + 1 not_affected_header_index = affected_header_index + 2 headers.insert(affected_header_index, total_affected_field['name']) headers.insert(not_affected_header_index, total_not_affected_field['name']) header_hazard_group['affected']['total'].append( total_affected_field['name']) header_hazard_group['not_affected']['total'].append( total_not_affected_field['name']) # affected, not affected, not exposed, total header report_fields = [ total_affected_field, total_not_affected_field, total_not_exposed_field, total_field ] for report_field in report_fields[2:]: headers.append(report_field['name']) """Create detail rows""" details = [] for feat in exposure_summary_table.getFeatures(): row = [] # Get breakdown name exposure_summary_table_field_name = breakdown_field['field_name'] field_index = exposure_summary_table.fieldNameIndex( exposure_summary_table_field_name) class_key = feat[field_index] row.append(class_key) # Get hazard count for hazard_class in hazard_classification['classes']: # hazard_count_field is a dynamic field with hazard class # as parameter field_key_name = hazard_count_field['key'] % ( hazard_class['key'], ) group_key = None for key, group in header_hazard_group.iteritems(): if hazard_class['name'] in group['hazards']: group_key = key break try: # retrieve dynamic field name from analysis_fields keywords # will cause key error if no hazard count for that particular # class field_name = exposure_summary_table_fields[field_key_name] field_index = exposure_summary_table.fieldNameIndex(field_name) # exposure summary table is in csv format, so the field # returned is always in text format count_value = int(float(feat[field_index])) count_value = format_number(count_value, enable_rounding=is_rounding, is_population=is_population) row.append({'value': count_value, 'header_group': group_key}) except KeyError: # in case the field was not found # assume value 0 row.append({'value': 0, 'header_group': group_key}) skip_row = False for field in report_fields: group_key = None for key, group in header_hazard_group.iteritems(): if field['name'] in group['total']: group_key = key break field_index = exposure_summary_table.fieldNameIndex( field['field_name']) total_count = int(float(feat[field_index])) total_count = format_number(total_count, enable_rounding=is_rounding, is_population=is_population) # we comment below code because now we want to show all rows, # we can uncomment if we want to remove the rows with zero total # if total_count == '0' and field == total_affected_field: # skip_row = True # break if group_key: if field == total_affected_field: row.insert(affected_header_index, { 'value': total_count, 'header_group': group_key }) elif field == total_not_affected_field: row.insert(not_affected_header_index, { 'value': total_count, 'header_group': group_key }) else: row.append({ 'value': total_count, 'header_group': group_key }) else: row.append(total_count) if skip_row: continue details.append(row) # retrieve classes definitions exposure_classes_lists = retrieve_exposure_classes_lists(exposure_layer) # sort detail rows based on class order # create function to sort def sort_classes(_row): """Sort method to retrieve exposure class key index.""" # class key is first column _class_key = _row[0] # find index in class list for i, _exposure_class in enumerate(exposure_classes_lists): if _class_key == _exposure_class['key']: index = i break else: index = -1 return index # sort details = sorted(details, key=sort_classes) # retrieve breakdown name from classes list for row in details: class_key = row[0] for exposure_class in exposure_classes_lists: if class_key == exposure_class['key']: breakdown_name = exposure_class['name'] break else: # attempt for dynamic translations breakdown_name = tr(class_key.capitalize()) # replace class_key with the class name row[0] = breakdown_name """create total footers""" # create total header footers = [total_field['name']] # total for hazard save_total_affected_field = False for hazard_class in hazard_classification['classes']: # hazard_count_field is a dynamic field with hazard class # as parameter field_key_name = hazard_count_field['key'] % (hazard_class['key'], ) if not hazard_class.get('affected'): save_total_affected_field = True group_key = None for key, group in header_hazard_group.iteritems(): if hazard_class['name'] in group['hazards']: group_key = key break try: # retrieve dynamic field name from analysis_fields keywords # will cause key error if no hazard count for that particular # class field_name = analysis_layer_fields[field_key_name] field_index = analysis_layer.fieldNameIndex(field_name) count_value = format_number(analysis_feature[field_index], enable_rounding=is_rounding, is_population=is_population) except KeyError: # in case the field was not found # assume value 0 count_value = '0' if count_value == '0': # if total affected for hazard class is zero, delete entire # column column_index = len(footers) + int(save_total_affected_field) # delete header column headers = headers[:column_index] + headers[column_index + 1:] for row_idx in range(0, len(details)): row = details[row_idx] row = row[:column_index] + row[column_index + 1:] details[row_idx] = row # reduce total affected and not affected column index by 1 # since we are removing a column if group_key == affected_field['field_name']: affected_header_index -= 1 else: not_affected_header_index -= 1 continue footers.append({'value': count_value, 'header_group': group_key}) # for footers for field in report_fields: group_key = None for key, group in header_hazard_group.iteritems(): if field['name'] in group['total']: group_key = key break total_count = value_from_field_name(field['field_name'], analysis_layer) total_count = format_number(total_count, enable_rounding=is_rounding, is_population=is_population) if group_key: if field == total_affected_field: footers.insert(affected_header_index, { 'value': total_count, 'header_group': group_key }) elif field == total_not_affected_field: footers.insert(not_affected_header_index, { 'value': total_count, 'header_group': group_key }) else: footers.append({ 'value': total_count, 'header_group': group_key }) else: footers.append(total_count) header = resolve_from_dictionary(extra_args, 'header') notes = resolve_from_dictionary(extra_args, 'notes') context['header'] = header context['group_border_color'] = resolve_from_dictionary( extra_args, 'group_border_color') context['notes'] = notes breakdown_header_index = 0 # we want to include total affected and not affected as a group # to its class so len(report_fields) - 2 total_header_index = len(headers) - (len(report_fields) - 2) context['detail_header'] = { 'header_hazard_group': header_hazard_group, 'breakdown_header_index': breakdown_header_index, 'total_header_index': total_header_index } # modify headers to include double header affected_headers = [] last_group = 0 for i in range(breakdown_header_index, total_header_index): hazard_class_name = headers[i] group_key = None for key, group in header_hazard_group.iteritems(): if hazard_class_name in group['hazards'] or (hazard_class_name in group['total']): group_key = key break if group_key and group_key not in affected_headers: affected_headers.append(group_key) headers[i] = { 'name': hazard_class_name, 'start': True, 'header_group': group_key, 'colspan': 1 } last_group = i header_hazard_group[group_key]['start_index'] = i elif group_key: colspan = headers[last_group]['colspan'] headers[last_group]['colspan'] = colspan + 1 headers[i] = { 'name': hazard_class_name, 'start': False, 'header_group': group_key } table_header_format = resolve_from_dictionary(extra_args, 'table_header_format') # check unit units = exposure_type['units'] if units: unit = units[0] abbreviation = unit['abbreviation'] if abbreviation: unit_string = '({abbreviation})'.format(abbreviation=abbreviation) else: unit_string = '' else: unit_string = '' table_header = table_header_format.format( title=provenance['map_legend_title'], unit=unit_string, exposure=exposure_header) table_header = ' '.join(table_header.split()) context['detail_table'] = { 'table_header': table_header, 'headers': headers, 'details': details, 'footers': footers, } context['extra_table'] = {} # extra table for specific exposure if exist extra_fields = resolve_from_dictionary(extra_args, 'exposure_extra_fields') if exposure_type['key'] in extra_fields.keys(): # create header for the extra table extra_table_header_format = resolve_from_dictionary( extra_args, 'extra_table_header_format') extra_table_header = extra_table_header_format.format( exposure=exposure_header) # headers headers = [] headers.append( breakdown_header_template.format(exposure=exposure_header)) current_unit = None currency_unit = setting('currency', expected_type=str) for field in extra_fields[exposure_type['key']]: field_index = exposure_summary_table.fieldNameIndex( field['field_name']) if field_index < 0: LOGGER.debug('Field name not found: %s, field index: %s' % (field['field_name'], field_index)) continue units = field.get('units') if units: for unit in units: if currency_unit == unit['key']: current_unit = unit['name'] break if not current_unit: current_unit = units[0]['name'] header_format = '{header} ({unit})' headers.append( header_format.format(header=field['header_name'], unit=current_unit)) # rows details = [] for feat in exposure_summary_table.getFeatures(): row = [] # Get breakdown name exposure_summary_table_field_name = breakdown_field['field_name'] field_index = exposure_summary_table.fieldNameIndex( exposure_summary_table_field_name) class_key = feat[field_index] row.append(class_key) for field in extra_fields[exposure_type['key']]: field_index = exposure_summary_table.fieldNameIndex( field['field_name']) # noinspection PyBroadException try: total_count = int(float(feat[field_index])) except: LOGGER.debug('Field name not found: %s, field index: %s' % (field['field_name'], field_index)) continue total_count = format_number(total_count, enable_rounding=is_rounding, is_population=is_population) row.append(total_count) details.append(row) details = sorted(details, key=sort_classes) context['extra_table'] = { 'table_header': extra_table_header, 'headers': headers, 'details': details, } return context
def population_chart_extractor(impact_report, component_metadata): """Creating population donut chart. :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} extra_args = component_metadata.extra_args hazard_layer = impact_report.hazard analysis_layer = impact_report.analysis analysis_layer_fields = analysis_layer.keywords['inasafe_fields'] """Generate Donut chart for affected population""" # create context for the donut chart # retrieve hazard classification from hazard layer hazard_classification = layer_hazard_classification(hazard_layer) if not hazard_classification: return context data = [] labels = [] colors = [] for hazard_class in hazard_classification['classes']: # Skip if it is not affected hazard class if not hazard_class['affected']: continue # hazard_count_field is a dynamic field with hazard class # as parameter field_key_name = hazard_count_field['key'] % ( hazard_class['key'],) try: # retrieve dynamic field name from analysis_fields keywords # will cause key error if no hazard count for that particular # class field_name = analysis_layer_fields[field_key_name] # Hazard label taken from translated hazard count field # label, string-formatted with translated hazard class label hazard_value = value_from_field_name(field_name, analysis_layer) hazard_value = round_affected_number( hazard_value, enable_rounding=True, use_population_rounding=True) except KeyError: # in case the field was not found continue data.append(hazard_value) labels.append(hazard_class['name']) colors.append(hazard_class['color'].name()) # add total not affected try: field_name = analysis_layer_fields[total_not_affected_field['key']] hazard_value = value_from_field_name(field_name, analysis_layer) hazard_value = round_affected_number( hazard_value, enable_rounding=True, use_population_rounding=True) data.append(hazard_value) labels.append(total_not_affected_field['name']) colors.append(green.name()) except KeyError: # in case the field is not there pass # add number for total not affected chart_title = resolve_from_dictionary(extra_args, 'chart_title') total_header = resolve_from_dictionary(extra_args, 'total_header') donut_context = DonutChartContext( data=data, labels=labels, colors=colors, inner_radius_ratio=0.5, stroke_color='#fff', title=chart_title, total_header=total_header, as_file=True) context['context'] = donut_context return context
def population_chart_extractor(impact_report, component_metadata): """Creating population donut chart. :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} extra_args = component_metadata.extra_args hazard_layer = impact_report.hazard analysis_layer = impact_report.analysis analysis_layer_fields = analysis_layer.keywords['inasafe_fields'] """Generate Donut chart for affected population""" # create context for the donut chart # retrieve hazard classification from hazard layer hazard_classification = layer_hazard_classification(hazard_layer) if not hazard_classification: return context data = [] labels = [] colors = [] for hazard_class in hazard_classification['classes']: # Skip if it is not affected hazard class if not hazard_class['affected']: continue # hazard_count_field is a dynamic field with hazard class # as parameter field_key_name = hazard_count_field['key'] % (hazard_class['key'], ) try: # retrieve dynamic field name from analysis_fields keywords # will cause key error if no hazard count for that particular # class field_name = analysis_layer_fields[field_key_name] # Hazard label taken from translated hazard count field # label, string-formatted with translated hazard class label hazard_value = value_from_field_name(field_name, analysis_layer) hazard_value = round_affected_number(hazard_value, enable_rounding=True, use_population_rounding=True) except KeyError: # in case the field was not found continue data.append(hazard_value) labels.append(hazard_class['name']) colors.append(hazard_class['color'].name()) # add total not affected try: field_name = analysis_layer_fields[total_not_affected_field['key']] hazard_value = value_from_field_name(field_name, analysis_layer) hazard_value = round_affected_number(hazard_value, enable_rounding=True, use_population_rounding=True) data.append(hazard_value) labels.append(total_not_affected_field['name']) colors.append(green.name()) except KeyError: # in case the field is not there pass # add number for total not affected chart_title = resolve_from_dictionary(extra_args, 'chart_title') total_header = resolve_from_dictionary(extra_args, 'total_header') donut_context = DonutChartContext(data=data, labels=labels, colors=colors, inner_radius_ratio=0.5, stroke_color='#fff', title=chart_title, total_header=total_header, as_file=True) context['context'] = donut_context return context
def population_infographic_extractor(impact_report, component_metadata): """Extracting aggregate result of demographic. :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} extra_args = component_metadata.extra_args """Initializations""" hazard_layer = impact_report.hazard analysis_layer = impact_report.analysis analysis_layer_fields = analysis_layer.keywords['inasafe_fields'] icons = component_metadata.extra_args.get('icons') # this report sections only applies if it is a population report. population_fields = [ population_count_field['key'], exposure_count_field['key'] % (exposure_population['key'], ), ] + [f['key'] for f in minimum_needs_fields] for item in population_fields: if item in analysis_layer_fields: break else: return context # We try to get total affected field # if it didn't exists, check other fields to show total_affected_fields = [ total_affected_field['key'], # We might want to check other fields, but turn it off until further # discussion population_count_field['key'], exposure_count_field['key'] % (exposure_population['key'], ), ] for item in total_affected_fields: if item in analysis_layer_fields: total_affected = value_from_field_name(analysis_layer_fields[item], analysis_layer) total_affected_field_used = item break else: return context if displaced_field['key'] in analysis_layer_fields: total_displaced = value_from_field_name( analysis_layer_fields[displaced_field['key']], analysis_layer) else: return context sections = OrderedDict() """People Section""" # Take default value from definitions people_header = resolve_from_dictionary(extra_args, ['sections', 'people', 'header']) people_items = resolve_from_dictionary(extra_args, ['sections', 'people', 'items']) # create context for affected infographic sub_header = resolve_from_dictionary(people_items[0], 'sub_header') # retrieve relevant header based on the fields we showed. sub_header = sub_header[total_affected_field_used] affected_infographic = PeopleInfographicElement( header=sub_header, icon=icons.get(total_affected_field['key']), number=total_affected) # create context for displaced infographic sub_header = resolve_from_dictionary(people_items[1], 'sub_header') sub_header_note_format = resolve_from_dictionary(people_items[1], 'sub_header_note_format') rate_description_format = resolve_from_dictionary( people_items[1], 'rate_description_format') rate_description = [] hazard_classification = layer_hazard_classification(hazard_layer) for hazard_class in hazard_classification['classes']: displacement_rate = hazard_class.get('displacement_rate', 0) if displacement_rate: rate_description.append( rate_description_format.format(**hazard_class)) rate_description_string = ', '.join(rate_description) sub_header_note = sub_header_note_format.format( rate_description=rate_description_string) displaced_infographic = PeopleInfographicElement( header=sub_header, header_note=sub_header_note, icon=icons.get(displaced_field['key']), number=total_displaced) sections['people'] = { 'header': people_header, 'items': [affected_infographic, displaced_infographic] } """Vulnerability Section""" # Take default value from definitions vulnerability_items = resolve_from_dictionary( extra_args, ['sections', 'vulnerability', 'items']) vulnerability_section_header = resolve_from_dictionary( extra_args, ['sections', 'vulnerability', 'header']) vulnerability_section_sub_header_format = resolve_from_dictionary( extra_args, ['sections', 'vulnerability', 'sub_header_format']) infographic_elements = [] for group in vulnerability_items: fields = group['fields'] group_header = group['sub_group_header'] bootstrap_column = group['bootstrap_column'] element_column = group['element_column'] headers = group['headers'] elements = [] for field, header in zip(fields, headers): field_key = field['key'] try: field_name = analysis_layer_fields[field_key] value = value_from_field_name(field_name, analysis_layer) except KeyError: # It means the field is not there continue if value: value_percentage = value * 100.0 / total_displaced else: value_percentage = 0 infographic_element = PeopleVulnerabilityInfographicElement( header=header, icon=icons.get(field_key), number=value, percentage=value_percentage) elements.append(infographic_element) if elements: infographic_elements.append({ 'group_header': group_header, 'bootstrap_column': bootstrap_column, 'element_column': element_column, 'items': elements }) total_displaced_rounded = format_number(total_displaced, enable_rounding=True, is_population=True) sections['vulnerability'] = { 'header': vulnerability_section_header, 'small_header': vulnerability_section_sub_header_format.format( number_displaced=total_displaced_rounded), 'items': infographic_elements } """Minimum Needs Section""" minimum_needs_header = resolve_from_dictionary( extra_args, ['sections', 'minimum_needs', 'header']) empty_unit_string = resolve_from_dictionary( extra_args, ['sections', 'minimum_needs', 'empty_unit_string']) items = [] for item in minimum_needs_fields: need = item['need_parameter'] if isinstance(need, ResourceParameter): needs_count = value_from_field_name(item['field_name'], analysis_layer) if need.unit.abbreviation: unit_string = '{unit}/{frequency}'.format( unit=need.unit.abbreviation, frequency=need.frequency) else: unit_string = empty_unit_string item = PeopleMinimumNeedsInfographicElement(header=item['name'], icon=icons.get( item['key']), number=needs_count, unit=unit_string) items.append(item) # TODO: get from impact function provenance later needs_profile = NeedsProfile() sections['minimum_needs'] = { 'header': minimum_needs_header, 'small_header': needs_profile.provenance, 'items': items, } """Population Charts""" population_donut_path = impact_report.component_absolute_output_path( 'population-chart-png') css_label_classes = [] try: population_chart_context = impact_report.metadata.component_by_key( 'population-chart').context['context'] """ :type: safe.report.extractors.infographic_elements.svg_charts. DonutChartContext """ for pie_slice in population_chart_context.slices: label = pie_slice['label'] if not label: continue css_class = label.replace(' ', '').lower() css_label_classes.append(css_class) except KeyError: population_chart_context = None sections['population_chart'] = { 'img_path': resource_url(population_donut_path), 'context': population_chart_context, 'css_label_classes': css_label_classes } context['brand_logo'] = resource_url( resources_path('img', 'logos', 'inasafe-logo-white.png')) context['sections'] = sections context['title'] = analysis_layer.title() or value_from_field_name( analysis_name_field['field_name'], analysis_layer) return context
def notes_assumptions_extractor(impact_report, component_metadata): """Extracting notes and assumptions of the exposure layer :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} hazard_layer = impact_report.hazard exposure_layer = impact_report.exposure provenance = impact_report.impact_function.provenance extra_args = component_metadata.extra_args exposure_type = layer_definition_type(exposure_layer) analysis_note_dict = resolve_from_dictionary(extra_args, 'analysis_notes') context['items'] = [analysis_note_dict] context['header'] = resolve_from_dictionary(extra_args, 'header') context['items'] += provenance['notes'] # Get hazard classification hazard_classification = layer_hazard_classification(hazard_layer) # Check hazard affected class affected_classes = [] for hazard_class in hazard_classification['classes']: if hazard_class.get('affected', False): affected_classes.append(hazard_class) if affected_classes: affected_note_dict = resolve_from_dictionary(extra_args, 'affected_note_format') # generate hazard classes hazard_classes = ', '.join([c['name'] for c in affected_classes]) for index, affected_note in enumerate(affected_note_dict['item_list']): affected_note_dict['item_list'][index] = (affected_note.format( hazard_classes=hazard_classes)) context['items'].append(affected_note_dict) # Check hazard have displacement rate for hazard_class in hazard_classification['classes']: if hazard_class.get('displacement_rate', 0) > 0: have_displacement_rate = True break else: have_displacement_rate = False # Only show displacement note if analysis about population exposure if have_displacement_rate and exposure_type == exposure_population: # add notes for displacement rate used displacement_note_dict = resolve_from_dictionary( extra_args, 'displacement_rates_note_format') # generate rate description displacement_rates_note_format = resolve_from_dictionary( extra_args, 'hazard_displacement_rates_note_format') displacement_rates_note = [] for hazard_class in hazard_classification['classes']: displacement_rates_note.append( displacement_rates_note_format.format(**hazard_class)) rate_description = ', '.join(displacement_rates_note) for index, displacement_note in enumerate( displacement_note_dict['item_list']): displacement_note_dict['item_list'][index] = ( displacement_note.format(rate_description=rate_description)) context['items'].append(displacement_note_dict) # Check hazard have displacement rate for hazard_class in hazard_classification['classes']: if hazard_class.get('fatality_rate', 0) > 0: have_fatality_rate = True break else: have_fatality_rate = False if have_fatality_rate and exposure_type == exposure_population: # add notes for fatality rate used fatality_note_dict = resolve_from_dictionary( extra_args, 'fatality_rates_note_format') # generate rate description fatality_rates_note_format = resolve_from_dictionary( extra_args, 'hazard_fatality_rates_note_format') fatality_rates_note = [] for hazard_class in hazard_classification['classes']: # we make a copy here because we don't want to # change the real value. copy_of_hazard_class = dict(hazard_class) if not copy_of_hazard_class['fatality_rate'] > 0: copy_of_hazard_class['fatality_rate'] = 0 else: # we want to show the rate as a scientific notation copy_of_hazard_class['fatality_rate'] = ( html_scientific_notation_rate( copy_of_hazard_class['fatality_rate'])) fatality_rates_note.append( fatality_rates_note_format.format(**copy_of_hazard_class)) rate_description = ', '.join(fatality_rates_note) for index, fatality_note in enumerate(fatality_note_dict['item_list']): fatality_note_dict['item_list'][index] = (fatality_note.format( rate_description=rate_description)) context['items'].append(fatality_note_dict) return context
def general_report_extractor(impact_report, component_metadata): """Extracting general analysis result from the impact layer. :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} extra_args = component_metadata.extra_args # figure out analysis report type hazard_layer = impact_report.hazard exposure_layer = impact_report.exposure analysis_layer = impact_report.analysis provenance = impact_report.impact_function.provenance debug_mode = impact_report.impact_function.debug_mode exposure_type = layer_definition_type(exposure_layer) # Only round the number when it is population exposure and it is not # in debug mode is_rounded = not debug_mode is_population = exposure_type is exposure_population # find hazard class summary = [] analysis_feature = analysis_layer.getFeatures().next() analysis_inasafe_fields = analysis_layer.keywords['inasafe_fields'] exposure_unit = exposure_type['units'][0] hazard_header = resolve_from_dictionary(extra_args, 'hazard_header') if exposure_unit['abbreviation']: value_header = u'{measure} ({abbreviation})'.format(**exposure_unit) else: value_header = u'{name}'.format(**exposure_unit) # in case there is a classification if 'classification' in hazard_layer.keywords: # retrieve hazard classification from hazard layer hazard_classification = layer_hazard_classification(hazard_layer) # classified hazard must have hazard count in analysis layer hazard_stats = [] for hazard_class in hazard_classification['classes']: # hazard_count_field is a dynamic field with hazard class # as parameter field_key_name = hazard_count_field['key'] % ( hazard_class['key'], ) try: # retrieve dynamic field name from analysis_fields keywords # will cause key error if no hazard count for that particular # class field_name = analysis_inasafe_fields[field_key_name] field_index = analysis_layer.fieldNameIndex(field_name) # Hazard label taken from translated hazard count field # label, string-formatted with translated hazard class label hazard_label = hazard_class['name'] hazard_value = format_number( analysis_feature[field_index], enable_rounding=is_rounded, is_population=is_population) stats = { 'key': hazard_class['key'], 'name': hazard_label, 'value': hazard_value } except KeyError: # in case the field was not found hazard_label = hazard_class['name'] stats = { 'key': hazard_class['key'], 'name': hazard_label, 'value': 0, } hazard_stats.append(stats) # find total field try: field_name = analysis_inasafe_fields[total_field['key']] total = value_from_field_name(field_name, analysis_layer) total = format_number( total, enable_rounding=is_rounded, is_population=is_population) stats = { 'key': total_field['key'], 'name': total_field['name'], 'as_header': True, 'value': total } hazard_stats.append(stats) except KeyError: pass summary.append({ 'header_label': hazard_header, 'value_label': value_header, 'rows': hazard_stats }) # retrieve affected column report_stats = [] reported_fields = resolve_from_dictionary( extra_args, 'reported_fields') for item in reported_fields: header = item['header'] field = item['field'] if field['key'] in analysis_inasafe_fields: field_index = analysis_layer.fieldNameIndex( field['field_name']) if field == fatalities_field: # For fatalities field, we show a range of number # instead row_value = fatalities_range(analysis_feature[field_index]) else: row_value = format_number( analysis_feature[field_index], enable_rounding=is_rounded, is_population=is_population) row_stats = { 'key': field['key'], 'name': header, 'value': row_value } report_stats.append(row_stats) # Give report section exposure_type = layer_definition_type(exposure_layer) header_label = exposure_type['name'] summary.append({ 'header_label': header_label, # This should depend on exposure unit # TODO: Change this so it can take the unit dynamically 'value_label': value_header, 'rows': report_stats }) header = resolve_from_dictionary(extra_args, ['header']) table_header_format = resolve_from_dictionary( extra_args, 'table_header_format') table_header = table_header_format.format( title=provenance['map_legend_title'], unit=hazard_classification['classification_unit']) context['header'] = header context['summary'] = summary context['table_header'] = table_header return context
def analysis_detail_extractor(impact_report, component_metadata): """Extracting analysis result from the impact layer. :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} extra_args = component_metadata.extra_args hazard_layer = impact_report.hazard exposure_layer = impact_report.exposure analysis_layer = impact_report.analysis analysis_layer_fields = analysis_layer.keywords['inasafe_fields'] analysis_feature = analysis_layer.getFeatures().next() exposure_summary_table = impact_report.exposure_summary_table if exposure_summary_table: exposure_summary_table_fields = exposure_summary_table.keywords[ 'inasafe_fields'] provenance = impact_report.impact_function.provenance debug_mode = impact_report.impact_function.debug_mode """Initializations""" # Get hazard classification hazard_classification = layer_hazard_classification(hazard_layer) # Get exposure type definition exposure_type = layer_definition_type(exposure_layer) # Only round the number when it is population exposure and it is not # in debug mode is_rounding = not debug_mode is_population = exposure_type is exposure_population # Analysis detail only applicable for breakable exposure types: itemizable_exposures_all = [ exposure for exposure in exposure_all if exposure.get('classifications')] if exposure_type not in itemizable_exposures_all: return context # Get breakdown field breakdown_field = None # I'm not sure what's the difference # It is possible to have exposure_type_field or exposure_class_field # at the moment breakdown_fields = [ exposure_type_field, exposure_class_field ] for field in breakdown_fields: if field['key'] in exposure_summary_table_fields: breakdown_field = field break """Create detail header""" headers = [] # breakdown header breakdown_header_template = '' if breakdown_field == exposure_type_field: breakdown_header_template = resolve_from_dictionary( extra_args, 'breakdown_header_type_format') elif breakdown_field == exposure_class_field: breakdown_header_template = resolve_from_dictionary( extra_args, 'breakdown_header_class_format') # check if there is header type associations type_header_mapping = resolve_from_dictionary( extra_args, 'exposure_type_header_mapping') if exposure_type['key'] in type_header_mapping: exposure_header = type_header_mapping[exposure_type['key']] else: exposure_header = exposure_type['name'] headers.append( breakdown_header_template.format(exposure=exposure_header)) # this is mapping for customizing double header for # affected/not affected hazard classes hazard_class_header_mapping = resolve_from_dictionary( extra_args, 'hazard_class_header_mapping') # hazard header # TODO: we need to get affected and not_affected key from # definitions concept header_hazard_group = { 'affected': { 'hazards': [], 'total': [] }, 'not_affected': { 'hazards': [], 'total': [] } } for key, group in header_hazard_group.iteritems(): if key in hazard_class_header_mapping: header_hazard_group[key].update(hazard_class_header_mapping[key]) affected_header_index = None for index, hazard_class in enumerate(hazard_classification['classes']): # the tuple format would be: # (class name, is it affected, header background color hazard_class_name = hazard_class['name'] affected = hazard_class.get('affected') if not affected and not affected_header_index: affected_header_index = index + 1 affected_status = 'not_affected' elif affected: affected_status = 'affected' else: affected_status = 'not_affected' header_hazard_group[affected_status]['hazards'].append( hazard_class_name) headers.append(hazard_class_name) if affected_header_index: not_affected_header_index = len(hazard_classification['classes']) + 2 else: affected_header_index = len(hazard_classification['classes']) + 1 not_affected_header_index = affected_header_index + 2 headers.insert(affected_header_index, total_affected_field['name']) headers.insert(not_affected_header_index, total_not_affected_field['name']) header_hazard_group['affected']['total'].append( total_affected_field['name']) header_hazard_group['not_affected']['total'].append( total_not_affected_field['name']) # affected, not affected, not exposed, total header report_fields = [ total_affected_field, total_not_affected_field, total_not_exposed_field, total_field ] for report_field in report_fields[2:]: headers.append(report_field['name']) """Create detail rows""" details = [] for feat in exposure_summary_table.getFeatures(): row = [] # Get breakdown name exposure_summary_table_field_name = breakdown_field['field_name'] field_index = exposure_summary_table.fieldNameIndex( exposure_summary_table_field_name) class_key = feat[field_index] row.append(class_key) # Get hazard count for hazard_class in hazard_classification['classes']: # hazard_count_field is a dynamic field with hazard class # as parameter field_key_name = hazard_count_field['key'] % ( hazard_class['key'], ) group_key = None for key, group in header_hazard_group.iteritems(): if hazard_class['name'] in group['hazards']: group_key = key break try: # retrieve dynamic field name from analysis_fields keywords # will cause key error if no hazard count for that particular # class field_name = exposure_summary_table_fields[field_key_name] field_index = exposure_summary_table.fieldNameIndex(field_name) # exposure summary table is in csv format, so the field # returned is always in text format count_value = int(float(feat[field_index])) count_value = format_number( count_value, enable_rounding=is_rounding, is_population=is_population) row.append({ 'value': count_value, 'header_group': group_key }) except KeyError: # in case the field was not found # assume value 0 row.append({ 'value': 0, 'header_group': group_key }) skip_row = False for field in report_fields: group_key = None for key, group in header_hazard_group.iteritems(): if field['name'] in group['total']: group_key = key break field_index = exposure_summary_table.fieldNameIndex( field['field_name']) total_count = int(float(feat[field_index])) total_count = format_number( total_count, enable_rounding=is_rounding, is_population=is_population) # we comment below code because now we want to show all rows, # we can uncomment if we want to remove the rows with zero total # if total_count == '0' and field == total_affected_field: # skip_row = True # break if group_key: if field == total_affected_field: row.insert( affected_header_index, { 'value': total_count, 'header_group': group_key }) elif field == total_not_affected_field: row.insert( not_affected_header_index, { 'value': total_count, 'header_group': group_key }) else: row.append({ 'value': total_count, 'header_group': group_key }) else: row.append(total_count) if skip_row: continue details.append(row) # retrieve classes definitions exposure_classes_lists = retrieve_exposure_classes_lists(exposure_layer) # sort detail rows based on class order # create function to sort def sort_classes(_row): """Sort method to retrieve exposure class key index.""" # class key is first column _class_key = _row[0] # find index in class list for i, _exposure_class in enumerate(exposure_classes_lists): if _class_key == _exposure_class['key']: index = i break else: index = -1 return index # sort details = sorted(details, key=sort_classes) # retrieve breakdown name from classes list for row in details: class_key = row[0] for exposure_class in exposure_classes_lists: if class_key == exposure_class['key']: breakdown_name = exposure_class['name'] break else: # attempt for dynamic translations breakdown_name = tr(class_key.capitalize()) # replace class_key with the class name row[0] = breakdown_name """create total footers""" # create total header footers = [total_field['name']] # total for hazard save_total_affected_field = False for hazard_class in hazard_classification['classes']: # hazard_count_field is a dynamic field with hazard class # as parameter field_key_name = hazard_count_field['key'] % ( hazard_class['key'],) if not hazard_class.get('affected'): save_total_affected_field = True group_key = None for key, group in header_hazard_group.iteritems(): if hazard_class['name'] in group['hazards']: group_key = key break try: # retrieve dynamic field name from analysis_fields keywords # will cause key error if no hazard count for that particular # class field_name = analysis_layer_fields[field_key_name] field_index = analysis_layer.fieldNameIndex(field_name) count_value = format_number( analysis_feature[field_index], enable_rounding=is_rounding, is_population=is_population) except KeyError: # in case the field was not found # assume value 0 count_value = '0' if count_value == '0': # if total affected for hazard class is zero, delete entire # column column_index = len(footers) + int(save_total_affected_field) # delete header column headers = headers[:column_index] + headers[column_index + 1:] for row_idx in range(0, len(details)): row = details[row_idx] row = row[:column_index] + row[column_index + 1:] details[row_idx] = row continue footers.append({ 'value': count_value, 'header_group': group_key }) # for footers for field in report_fields: group_key = None for key, group in header_hazard_group.iteritems(): if field['name'] in group['total']: group_key = key break total_count = value_from_field_name( field['field_name'], analysis_layer) total_count = format_number( total_count, enable_rounding=is_rounding, is_population=is_population) if group_key: if field == total_affected_field: footers.insert( affected_header_index, { 'value': total_count, 'header_group': group_key }) elif field == total_not_affected_field: footers.insert( not_affected_header_index, { 'value': total_count, 'header_group': group_key }) else: footers.append({ 'value': total_count, 'header_group': group_key }) else: footers.append(total_count) header = resolve_from_dictionary( extra_args, 'header') notes = resolve_from_dictionary( extra_args, 'notes') context['header'] = header context['group_border_color'] = resolve_from_dictionary( extra_args, 'group_border_color') context['notes'] = notes breakdown_header_index = 0 # we want to include total affected and not affected as a group # to its class so len(report_fields) - 2 total_header_index = len(headers) - (len(report_fields) - 2) context['detail_header'] = { 'header_hazard_group': header_hazard_group, 'breakdown_header_index': breakdown_header_index, 'total_header_index': total_header_index } # modify headers to include double header affected_headers = [] last_group = 0 for i in range(breakdown_header_index, total_header_index): hazard_class_name = headers[i] group_key = None for key, group in header_hazard_group.iteritems(): if hazard_class_name in group['hazards'] or ( hazard_class_name in group['total']): group_key = key break if group_key and group_key not in affected_headers: affected_headers.append(group_key) headers[i] = { 'name': hazard_class_name, 'start': True, 'header_group': group_key, 'colspan': 1 } last_group = i header_hazard_group[group_key]['start_index'] = i elif group_key: colspan = headers[last_group]['colspan'] headers[last_group]['colspan'] = colspan + 1 headers[i] = { 'name': hazard_class_name, 'start': False, 'header_group': group_key } table_header_format = resolve_from_dictionary( extra_args, 'table_header_format') # check unit units = exposure_type['units'] if units: unit = units[0] abbreviation = unit['abbreviation'] if abbreviation: unit_string = '({abbreviation})'.format(abbreviation=abbreviation) else: unit_string = '' else: unit_string = '' table_header = table_header_format.format( title=provenance['map_legend_title'], unit=unit_string, exposure=exposure_header) table_header = ' '.join(table_header.split()) context['detail_table'] = { 'table_header': table_header, 'headers': headers, 'details': details, 'footers': footers, } return context
def population_infographic_extractor(impact_report, component_metadata): """Extracting aggregate result of demographic. :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} extra_args = component_metadata.extra_args """Initializations""" hazard_layer = impact_report.hazard analysis_layer = impact_report.analysis analysis_layer_fields = analysis_layer.keywords['inasafe_fields'] icons = component_metadata.extra_args.get('icons') # this report sections only applies if it is a population report. population_fields = [ population_count_field['key'], exposure_count_field['key'] % (exposure_population['key'], ), ] + [f['key'] for f in minimum_needs_fields] for item in population_fields: if item in analysis_layer_fields: break else: return context # We try to get total affected field # if it didn't exists, check other fields to show total_affected_fields = [ total_affected_field['key'], # We might want to check other fields, but turn it off until further # discussion population_count_field['key'], exposure_count_field['key'] % (exposure_population['key'], ), ] for item in total_affected_fields: if item in analysis_layer_fields: total_affected = value_from_field_name( analysis_layer_fields[item], analysis_layer) total_affected_field_used = item break else: return context if displaced_field['key'] in analysis_layer_fields: total_displaced = value_from_field_name( analysis_layer_fields[displaced_field['key']], analysis_layer) else: return context sections = OrderedDict() """People Section""" # Take default value from definitions people_header = resolve_from_dictionary( extra_args, ['sections', 'people', 'header']) people_items = resolve_from_dictionary( extra_args, ['sections', 'people', 'items']) # create context for affected infographic sub_header = resolve_from_dictionary( people_items[0], 'sub_header') # retrieve relevant header based on the fields we showed. sub_header = sub_header[total_affected_field_used] affected_infographic = PeopleInfographicElement( header=sub_header, icon=icons.get( total_affected_field['key']), number=total_affected) # create context for displaced infographic sub_header = resolve_from_dictionary( people_items[1], 'sub_header') sub_header_note_format = resolve_from_dictionary( people_items[1], 'sub_header_note_format') rate_description_format = resolve_from_dictionary( people_items[1], 'rate_description_format') rate_description = [] hazard_classification = layer_hazard_classification(hazard_layer) for hazard_class in hazard_classification['classes']: displacement_rate = hazard_class.get('displacement_rate', 0) if displacement_rate: rate_description.append( rate_description_format.format(**hazard_class)) rate_description_string = ', '.join(rate_description) sub_header_note = sub_header_note_format.format( rate_description=rate_description_string) displaced_infographic = PeopleInfographicElement( header=sub_header, header_note=sub_header_note, icon=icons.get( displaced_field['key']), number=total_displaced) sections['people'] = { 'header': people_header, 'items': [ affected_infographic, displaced_infographic ] } """Vulnerability Section""" # Take default value from definitions vulnerability_items = resolve_from_dictionary( extra_args, ['sections', 'vulnerability', 'items']) vulnerability_section_header = resolve_from_dictionary( extra_args, ['sections', 'vulnerability', 'header']) vulnerability_section_sub_header_format = resolve_from_dictionary( extra_args, ['sections', 'vulnerability', 'sub_header_format']) infographic_elements = [] for group in vulnerability_items: fields = group['fields'] group_header = group['sub_group_header'] bootstrap_column = group['bootstrap_column'] element_column = group['element_column'] headers = group['headers'] elements = [] for field, header in zip(fields, headers): field_key = field['key'] try: field_name = analysis_layer_fields[field_key] value = value_from_field_name( field_name, analysis_layer) except KeyError: # It means the field is not there continue if value: value_percentage = value * 100.0 / total_displaced else: value_percentage = 0 infographic_element = PeopleVulnerabilityInfographicElement( header=header, icon=icons.get(field_key), number=value, percentage=value_percentage ) elements.append(infographic_element) if elements: infographic_elements.append({ 'group_header': group_header, 'bootstrap_column': bootstrap_column, 'element_column': element_column, 'items': elements }) total_displaced_rounded = format_number( total_displaced, enable_rounding=True, is_population=True) sections['vulnerability'] = { 'header': vulnerability_section_header, 'small_header': vulnerability_section_sub_header_format.format( number_displaced=total_displaced_rounded), 'items': infographic_elements } """Minimum Needs Section""" minimum_needs_header = resolve_from_dictionary( extra_args, ['sections', 'minimum_needs', 'header']) empty_unit_string = resolve_from_dictionary( extra_args, ['sections', 'minimum_needs', 'empty_unit_string']) items = [] for item in minimum_needs_fields: need = item['need_parameter'] if isinstance(need, ResourceParameter): needs_count = value_from_field_name( item['field_name'], analysis_layer) if need.unit.abbreviation: unit_string = '{unit}/{frequency}'.format( unit=need.unit.abbreviation, frequency=need.frequency) else: unit_string = empty_unit_string item = PeopleMinimumNeedsInfographicElement( header=item['name'], icon=icons.get( item['key']), number=needs_count, unit=unit_string) items.append(item) # TODO: get from impact function provenance later needs_profile = NeedsProfile() sections['minimum_needs'] = { 'header': minimum_needs_header, 'small_header': needs_profile.provenance, 'items': items, } """Population Charts""" population_donut_path = impact_report.component_absolute_output_path( 'population-chart-png') css_label_classes = [] try: population_chart_context = impact_report.metadata.component_by_key( 'population-chart').context['context'] """ :type: safe.report.extractors.infographic_elements.svg_charts. DonutChartContext """ for pie_slice in population_chart_context.slices: label = pie_slice['label'] if not label: continue css_class = label.replace(' ', '').lower() css_label_classes.append(css_class) except KeyError: population_chart_context = None sections['population_chart'] = { 'img_path': resource_url(population_donut_path), 'context': population_chart_context, 'css_label_classes': css_label_classes } context['brand_logo'] = resource_url( resources_path('img', 'logos', 'inasafe-logo-white.png')) context['sections'] = sections context['title'] = analysis_layer.title() or value_from_field_name( analysis_name_field['field_name'], analysis_layer) return context
def notes_assumptions_extractor(impact_report, component_metadata): """Extracting notes and assumptions of the exposure layer :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} hazard_layer = impact_report.hazard exposure_layer = impact_report.exposure provenance = impact_report.impact_function.provenance extra_args = component_metadata.extra_args exposure_type = layer_definition_type(exposure_layer) context['header'] = resolve_from_dictionary(extra_args, 'header') context['items'] = provenance['notes'] # Get hazard classification hazard_classification = layer_hazard_classification(hazard_layer) # Check hazard affected class affected_classes = [] for hazard_class in hazard_classification['classes']: if hazard_class.get('affected', False): affected_classes.append(hazard_class) if affected_classes: affected_note_format = resolve_from_dictionary(extra_args, 'affected_note_format') # generate hazard classes hazard_classes = ', '.join([c['name'] for c in affected_classes]) context['items'].append( affected_note_format.format(hazard_classes=hazard_classes)) # Check hazard have displacement rate for hazard_class in hazard_classification['classes']: if hazard_class.get('displacement_rate', 0) > 0: have_displacement_rate = True break else: have_displacement_rate = False # Only show displacement note if analysis about population exposure if have_displacement_rate and exposure_type == exposure_population: # add notes for displacement rate used displacement_note_format = resolve_from_dictionary( extra_args, 'displacement_rates_note_format') # generate rate description hazard_note_format = resolve_from_dictionary( extra_args, 'hazard_displacement_rates_note_format') hazard_note = [] for hazard_class in hazard_classification['classes']: hazard_note.append(hazard_note_format.format(**hazard_class)) rate_description = ', '.join(hazard_note) context['items'].append( displacement_note_format.format(rate_description=rate_description)) return context
def notes_assumptions_extractor(impact_report, component_metadata): """Extracting notes and assumptions of the exposure layer :param impact_report: the impact report that acts as a proxy to fetch all the data that extractor needed :type impact_report: safe.report.impact_report.ImpactReport :param component_metadata: the component metadata. Used to obtain information about the component we want to render :type component_metadata: safe.report.report_metadata. ReportComponentsMetadata :return: context for rendering phase :rtype: dict .. versionadded:: 4.0 """ context = {} hazard_layer = impact_report.hazard exposure_layer = impact_report.exposure provenance = impact_report.impact_function.provenance extra_args = component_metadata.extra_args exposure_type = layer_definition_type(exposure_layer) analysis_note_dict = resolve_from_dictionary(extra_args, 'analysis_notes') context['items'] = [analysis_note_dict] context['header'] = resolve_from_dictionary(extra_args, 'header') context['items'] += provenance['notes'] # Get hazard classification hazard_classification = layer_hazard_classification(hazard_layer) # Check hazard affected class affected_classes = [] for hazard_class in hazard_classification['classes']: if hazard_class.get('affected', False): affected_classes.append(hazard_class) if affected_classes: affected_note_dict = resolve_from_dictionary( extra_args, 'affected_note_format') # generate hazard classes hazard_classes = ', '.join([ c['name'] for c in affected_classes ]) for index, affected_note in enumerate(affected_note_dict['item_list']): affected_note_dict['item_list'][index] = ( affected_note.format(hazard_classes=hazard_classes) ) context['items'].append(affected_note_dict) # Check hazard have displacement rate for hazard_class in hazard_classification['classes']: if hazard_class.get('displacement_rate', 0) > 0: have_displacement_rate = True break else: have_displacement_rate = False # Only show displacement note if analysis about population exposure if have_displacement_rate and exposure_type == exposure_population: # add notes for displacement rate used displacement_note_dict = resolve_from_dictionary( extra_args, 'displacement_rates_note_format') # generate rate description displacement_rates_note_format = resolve_from_dictionary( extra_args, 'hazard_displacement_rates_note_format') displacement_rates_note = [] for hazard_class in hazard_classification['classes']: displacement_rates_note.append( displacement_rates_note_format.format(**hazard_class)) rate_description = ', '.join(displacement_rates_note) for index, displacement_note in enumerate( displacement_note_dict['item_list']): displacement_note_dict['item_list'][index] = ( displacement_note.format(rate_description=rate_description) ) context['items'].append(displacement_note_dict) # Check hazard have displacement rate for hazard_class in hazard_classification['classes']: if hazard_class.get('fatality_rate', 0) > 0: have_fatality_rate = True break else: have_fatality_rate = False if have_fatality_rate and exposure_type == exposure_population: # add notes for fatality rate used fatality_note_dict = resolve_from_dictionary( extra_args, 'fatality_rates_note_format') # generate rate description fatality_rates_note_format = resolve_from_dictionary( extra_args, 'hazard_fatality_rates_note_format') fatality_rates_note = [] for hazard_class in hazard_classification['classes']: # we make a copy here because we don't want to # change the real value. copy_of_hazard_class = dict(hazard_class) if not copy_of_hazard_class['fatality_rate'] > 0: copy_of_hazard_class['fatality_rate'] = 0 else: # we want to show the rate as a scientific notation copy_of_hazard_class['fatality_rate'] = ( html_scientific_notation_rate( copy_of_hazard_class['fatality_rate'])) fatality_rates_note.append( fatality_rates_note_format.format(**copy_of_hazard_class)) rate_description = ', '.join(fatality_rates_note) for index, fatality_note in enumerate(fatality_note_dict['item_list']): fatality_note_dict['item_list'][index] = ( fatality_note.format(rate_description=rate_description) ) context['items'].append(fatality_note_dict) return context