def definition_to_message( definition, message=None, table_of_contents=None, heading_level=None): """Helper function to render a definition to a message. :param definition: A definition dictionary (see definitions package). :type definition: dict :param message: The message that the definition should be appended to. :type message: parameters.message.Message :param table_of_contents: Table of contents that the headings should be included in. :type message: parameters.message.Message :param heading_level: Optional style to apply to the definition heading. See HEADING_LOOKUPS :type heading_level: int :returns: Message :rtype: str """ if message is None: message = m.Message() if table_of_contents is None: table_of_contents = m.Message() if heading_level: _create_section_header( message, table_of_contents, definition['name'].replace(' ', '-'), definition['name'], heading_level=heading_level) else: header = m.Paragraph(m.ImportantText(definition['name'])) message.add(header) # If the definition has an icon, we put the icon and description side by # side in a table otherwise just show the description as a paragraph url = _definition_icon_url(definition) if url is None: message.add(m.Paragraph(definition['description'])) if 'citations' in definition: _citations_to_message(message, definition) else: LOGGER.info('Creating mini table for definition description: ' + url) table = m.Table(style_class='table table-condensed') row = m.Row() row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE))) row.add(m.Cell(definition['description'])) table.add(row) for citation in definition['citations']: if citation['text'] in [None, '']: continue row = m.Row() row.add(m.Cell('')) if citation['link'] in [None, '']: row.add(m.Cell(citation['text'])) else: row.add(m.Cell(m.Link(citation['link'], citation['text']))) table.add(row) message.add(table) url = _definition_screenshot_url(definition) if url: message.add(m.Paragraph(m.Image(url), style_class='text-center')) # types contains e.g. hazard_all if 'types' in definition: for sub_definition in definition['types']: definition_to_message( sub_definition, message, table_of_contents, heading_level=3) # # Notes section if available # if 'notes' in definition: # Start a notes details group too since we have an exposure message.add(m.Heading( tr('Notes:'), **DETAILS_STYLE)) message.add(m.Heading( tr('General notes:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['notes']: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'citations' in definition: _citations_to_message(message, definition) # This only for EQ if 'earthquake_fatality_models' in definition: current_function = current_earthquake_model_name() paragraph = m.Paragraph(tr( 'The following earthquake fatality models are available in ' 'InaSAFE. Note that you need to set one of these as the ' 'active model in InaSAFE Options. The currently active model ' 'is: '), m.ImportantText(current_function) ) message.add(paragraph) models_definition = definition['earthquake_fatality_models'] for model in models_definition: message.add(m.Heading(model['name'], **DETAILS_SUBGROUP_STYLE)) if 'description' in model: paragraph = m.Paragraph(model['description']) message.add(paragraph) for note in model['notes']: paragraph = m.Paragraph(note) message.add(paragraph) _citations_to_message(message, model) for exposure in exposure_all: extra_exposure_notes = specific_notes(definition, exposure) if extra_exposure_notes: title = tr(u'Notes for exposure : {exposure_name}').format( exposure_name=exposure['name']) message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in extra_exposure_notes: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'continuous_notes' in definition: message.add(m.Heading( tr('Notes for continuous datasets:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['continuous_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'classified_notes' in definition: message.add(m.Heading( tr('Notes for classified datasets:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['classified_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'single_event_notes' in definition: message.add( m.Heading(tr('Notes for single events'), **DETAILS_STYLE)) if len(definition['single_event_notes']) < 1: message.add(m.Paragraph(tr('No single event notes defined.'))) else: bullets = m.BulletedList() for note in definition['single_event_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'multi_event_notes' in definition: message.add( m.Heading( tr('Notes for multi events / scenarios:'), **DETAILS_STYLE)) if len(definition['multi_event_notes']) < 1: message.add(m.Paragraph(tr('No multi-event notes defined.'))) else: bullets = m.BulletedList() for note in definition['multi_event_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'actions' in definition: message.add(m.Paragraph(m.ImportantText(tr('Actions:')))) bullets = m.BulletedList() for note in definition['actions']: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) for exposure in exposure_all: extra_exposure_actions = specific_actions(definition, exposure) if extra_exposure_actions: title = tr(u'Actions for exposure : {exposure_name}').format( exposure_name=exposure['name']) message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in extra_exposure_actions: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'continuous_hazard_units' in definition: message.add(m.Paragraph(m.ImportantText(tr('Units:')))) table = m.Table(style_class='table table-condensed table-striped') row = m.Row() row.add(m.Cell(tr('Name'), header=True)) row.add(m.Cell(tr('Plural'), header=True)) row.add(m.Cell(tr('Abbreviation'), header=True)) row.add(m.Cell(tr('Details'), header=True)) table.add(row) for unit in definition['continuous_hazard_units']: row = m.Row() row.add(m.Cell(unit['name'])) row.add(m.Cell(unit['plural_name'])) row.add(m.Cell(unit['abbreviation'])) row.add(m.Cell(unit['description'])) table.add(row) message.add(table) if 'fields' in definition: message.add(m.Paragraph(m.ImportantText(tr('Fields:')))) table = _create_fields_table() if 'extra_fields' in definition: all_fields = definition['fields'] + definition['extra_fields'] else: all_fields = definition['fields'] for field in all_fields: _add_field_to_table(field, table) message.add(table) if 'classifications' in definition: message.add(m.Heading( tr('Hazard classifications'), **DETAILS_STYLE)) message.add(m.Paragraph( definitions.hazard_classification['description'])) for inasafe_class in definition['classifications']: definition_to_message( inasafe_class, message, table_of_contents, heading_level=3) if 'classes' in definition: message.add(m.Paragraph(m.ImportantText(tr('Classes:')))) is_hazard = definition['type'] == hazard_classification_type if is_hazard: table = _make_defaults_hazard_table() else: table = _make_defaults_exposure_table() for inasafe_class in definition['classes']: row = m.Row() if is_hazard: # name() on QColor returns its hex code if 'color' in inasafe_class: colour = inasafe_class['color'].name() row.add(m.Cell( u'', attributes='style="background: %s;"' % colour)) else: row.add(m.Cell(u' ')) row.add(m.Cell(inasafe_class['name'])) if is_hazard: if 'affected' in inasafe_class: row.add(m.Cell(tr(inasafe_class['affected']))) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: if inasafe_class.get('fatality_rate') > 0: # we want to show the rate as a scientific notation rate = html_scientific_notation_rate( inasafe_class['fatality_rate']) rate = u'%s%%' % rate row.add(m.Cell(rate)) elif inasafe_class.get('fatality_rate') == 0: row.add(m.Cell('0%')) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: if 'displacement_rate' in inasafe_class: rate = inasafe_class['displacement_rate'] * 100 rate = u'%.0f%%' % rate row.add(m.Cell(rate)) else: row.add(m.Cell(tr('unspecified'))) if 'string_defaults' in inasafe_class: defaults = None for default in inasafe_class['string_defaults']: if defaults: defaults += ',%s' % default else: defaults = default row.add(m.Cell(defaults)) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: # Min may be a single value or a dict of values so we need # to check type and deal with it accordingly if 'numeric_default_min' in inasafe_class: if isinstance(inasafe_class['numeric_default_min'], dict): bullets = m.BulletedList() minima = inasafe_class['numeric_default_min'] for key, value in sorted(minima.iteritems()): bullets.add(u'%s : %s' % (key, value)) row.add(m.Cell(bullets)) else: row.add(m.Cell(inasafe_class['numeric_default_min'])) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: # Max may be a single value or a dict of values so we need # to check type and deal with it accordingly if 'numeric_default_max' in inasafe_class: if isinstance(inasafe_class['numeric_default_max'], dict): bullets = m.BulletedList() maxima = inasafe_class['numeric_default_max'] for key, value in sorted(maxima.iteritems()): bullets.add(u'%s : %s' % (key, value)) row.add(m.Cell(bullets)) else: row.add(m.Cell(inasafe_class['numeric_default_max'])) else: row.add(m.Cell(tr('unspecified'))) table.add(row) # Description goes in its own row with spanning row = m.Row() row.add(m.Cell('')) row.add(m.Cell(inasafe_class['description'], span=7)) table.add(row) # For hazard classes we also add the 'not affected' class manually: if definition['type'] == definitions.hazard_classification_type: row = m.Row() colour = definitions.not_exposed_class['color'].name() row.add(m.Cell( u'', attributes='style="background: %s;"' % colour)) description = definitions.not_exposed_class['description'] row.add(m.Cell(description, span=7)) table.add(row) message.add(table) if 'affected' in definition: if definition['affected']: message.add(m.Paragraph(tr( 'Exposure entities in this class ARE considered affected'))) else: message.add(m.Paragraph(tr( 'Exposure entities in this class are NOT considered ' 'affected'))) if 'optional' in definition: if definition['optional']: message.add(m.Paragraph(tr( 'This class is NOT required in the hazard keywords.'))) else: message.add(m.Paragraph(tr( 'This class IS required in the hazard keywords.'))) return message
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 = {} provenance = impact_report.impact_function.provenance extra_args = component_metadata.extra_args hazard_keywords = provenance['hazard_keywords'] exposure_keywords = provenance['exposure_keywords'] exposure_type = definition(exposure_keywords['exposure']) analysis_note_dict = resolve_from_dictionary(extra_args, 'analysis_notes') context['items'] = [analysis_note_dict] context['component_key'] = component_metadata.key context['header'] = resolve_from_dictionary(extra_args, 'header') context['items'] += provenance['notes'] # Get hazard classification hazard_classification = definition( active_classification(hazard_keywords, exposure_keywords['exposure'])) # Check hazard affected class affected_classes = [] for hazard_class in hazard_classification['classes']: if exposure_keywords['exposure'] == exposure_population['key']: # Taking from profile is_affected_class = is_affected( hazard=hazard_keywords['hazard'], classification=hazard_classification['key'], hazard_class=hazard_class['key'], ) if is_affected_class: affected_classes.append(hazard_class) else: 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']: the_hazard_class = deepcopy(hazard_class) the_hazard_class['displacement_rate'] = get_displacement_rate( hazard=hazard_keywords['hazard'], classification=hazard_classification['key'], hazard_class=the_hazard_class['key'] ) displacement_rates_note.append( displacement_rates_note_format.format(**the_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 have_fatality_rate = False for hazard_class in hazard_classification['classes']: if hazard_class.get('fatality_rate', None) is not None and \ hazard_class.get('fatality_rate', 0) > 0: have_fatality_rate = True break 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 copy_of_hazard_class['fatality_rate'] is None or \ 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 definition_to_message(definition, message=None, table_of_contents=None, heading_level=None): """Helper function to render a definition to a message. :param definition: A definition dictionary (see definitions package). :type definition: dict :param message: The message that the definition should be appended to. :type message: safe_extras.parameters.message.Message :param table_of_contents: Table of contents that the headings should be included in. :type message: safe_extras.parameters.message.Message :param heading_level: Optional style to apply to the definition heading. See HEADING_LOOKUPS :type heading_level: int :returns: Message :rtype: str """ if message is None: message = m.Message() if table_of_contents is None: table_of_contents = m.Message() if heading_level: _create_section_header(message, table_of_contents, definition['name'].replace(' ', '-'), definition['name'], heading_level=heading_level) else: header = m.Paragraph(m.ImportantText(definition['name'])) message.add(header) # If the definition has an icon, we put the icon and description side by # side in a table otherwise just show the description as a paragraph url = _definition_icon_url(definition) if url is None: message.add(m.Paragraph(definition['description'])) if 'citations' in definition: _citations_to_message(message, definition) else: LOGGER.info('Creating mini table for definition description: ' + url) table = m.Table(style_class='table table-condensed') row = m.Row() row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE))) row.add(m.Cell(definition['description'])) table.add(row) for citation in definition['citations']: if citation['text'] in [None, '']: continue row = m.Row() row.add(m.Cell('')) if citation['link'] in [None, '']: row.add(m.Cell(citation['text'])) else: row.add(m.Cell(m.Link(citation['link'], citation['text']))) table.add(row) message.add(table) url = _definition_screenshot_url(definition) if url: message.add(m.Paragraph(m.Image(url), style_class='text-center')) # types contains e.g. hazard_all if 'types' in definition: for sub_definition in definition['types']: definition_to_message(sub_definition, message, table_of_contents, heading_level=3) # # Notes section if available # if 'notes' in definition: # Start a notes details group too since we have an exposure message.add(m.Heading(tr('Notes:'), **DETAILS_STYLE)) message.add(m.Heading(tr('General notes:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['notes']: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'citations' in definition: _citations_to_message(message, definition) # This only for EQ if 'earthquake_fatality_models' in definition: current_function = current_earthquake_model_name() paragraph = m.Paragraph( 'The following earthquake fatality models are available in ' 'InaSAFE. Note that you need to set one of these as the ' 'active model in InaSAFE Options. The currently active model is: ', m.ImportantText(current_function)) message.add(paragraph) models_definition = definition['earthquake_fatality_models'] for model in models_definition: message.add(m.Heading(model['name'], **DETAILS_SUBGROUP_STYLE)) if 'description' in model: paragraph = m.Paragraph(model['description']) message.add(paragraph) for note in model['notes']: paragraph = m.Paragraph(note) message.add(paragraph) _citations_to_message(message, model) for exposure in exposure_all: extra_exposure_notes = specific_notes(definition, exposure) if extra_exposure_notes: title = tr(u'Notes for exposure : {exposure_name}').format( exposure_name=exposure['name']) message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in extra_exposure_notes: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'continuous_notes' in definition: message.add( m.Heading(tr('Notes for continuous datasets:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['continuous_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'classified_notes' in definition: message.add( m.Heading(tr('Notes for classified datasets:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['classified_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'single_event_notes' in definition: message.add(m.Heading(tr('Notes for single events'), **DETAILS_STYLE)) if len(definition['single_event_notes']) < 1: message.add(m.Paragraph(tr('No single event notes defined.'))) else: bullets = m.BulletedList() for note in definition['single_event_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'multi_event_notes' in definition: message.add( m.Heading(tr('Notes for multi events / scenarios:'), **DETAILS_STYLE)) if len(definition['multi_event_notes']) < 1: message.add(m.Paragraph(tr('No multi-event notes defined.'))) else: bullets = m.BulletedList() for note in definition['multi_event_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'actions' in definition: message.add(m.Paragraph(m.ImportantText(tr('Actions:')))) bullets = m.BulletedList() for note in definition['actions']: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) for exposure in exposure_all: extra_exposure_actions = specific_actions(definition, exposure) if extra_exposure_actions: title = tr(u'Actions for exposure : {exposure_name}').format( exposure_name=exposure['name']) message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in extra_exposure_actions: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'continuous_hazard_units' in definition: message.add(m.Paragraph(m.ImportantText(tr('Units:')))) table = m.Table(style_class='table table-condensed table-striped') row = m.Row() row.add(m.Cell(tr('Name'), header=True)) row.add(m.Cell(tr('Plural'), header=True)) row.add(m.Cell(tr('Abbreviation'), header=True)) row.add(m.Cell(tr('Details'), header=True)) table.add(row) for unit in definition['continuous_hazard_units']: row = m.Row() row.add(m.Cell(unit['name'])) row.add(m.Cell(unit['plural_name'])) row.add(m.Cell(unit['abbreviation'])) row.add(m.Cell(unit['description'])) table.add(row) message.add(table) if 'fields' in definition: message.add(m.Paragraph(m.ImportantText(tr('Fields:')))) table = _create_fields_table() if 'extra_fields' in definition: all_fields = definition['fields'] + definition['extra_fields'] else: all_fields = definition['fields'] for field in all_fields: _add_field_to_table(field, table) message.add(table) if 'classifications' in definition: message.add(m.Heading(tr('Hazard classifications'), **DETAILS_STYLE)) message.add( m.Paragraph(definitions.hazard_classification['description'])) for inasafe_class in definition['classifications']: definition_to_message(inasafe_class, message, table_of_contents, heading_level=3) if 'classes' in definition: message.add(m.Paragraph(m.ImportantText(tr('Classes:')))) is_hazard = definition['type'] == hazard_classification_type if is_hazard: table = _make_defaults_hazard_table() else: table = _make_defaults_exposure_table() for inasafe_class in definition['classes']: row = m.Row() if is_hazard: # name() on QColor returns its hex code if 'color' in inasafe_class: colour = inasafe_class['color'].name() row.add( m.Cell(u'', attributes='style="background: %s;"' % colour)) else: row.add(m.Cell(u' ')) row.add(m.Cell(inasafe_class['name'])) if is_hazard: if 'affected' in inasafe_class: row.add(m.Cell(tr(inasafe_class['affected']))) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: if inasafe_class.get('fatality_rate') > 0: # we want to show the rate as a scientific notation rate = html_scientific_notation_rate( inasafe_class['fatality_rate']) rate = u'%s%%' % rate row.add(m.Cell(rate)) elif inasafe_class.get('fatality_rate') == 0: row.add(m.Cell('0%')) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: if 'displacement_rate' in inasafe_class: rate = inasafe_class['displacement_rate'] * 100 rate = u'%.0f%%' % rate row.add(m.Cell(rate)) else: row.add(m.Cell(tr('unspecified'))) if 'string_defaults' in inasafe_class: defaults = None for default in inasafe_class['string_defaults']: if defaults: defaults += ',%s' % default else: defaults = default row.add(m.Cell(defaults)) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: # Min may be a single value or a dict of values so we need # to check type and deal with it accordingly if 'numeric_default_min' in inasafe_class: if isinstance(inasafe_class['numeric_default_min'], dict): bullets = m.BulletedList() minima = inasafe_class['numeric_default_min'] for key, value in minima.iteritems(): bullets.add(u'%s : %s' % (key, value)) row.add(m.Cell(bullets)) else: row.add(m.Cell(inasafe_class['numeric_default_min'])) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: # Max may be a single value or a dict of values so we need # to check type and deal with it accordingly if 'numeric_default_max' in inasafe_class: if isinstance(inasafe_class['numeric_default_max'], dict): bullets = m.BulletedList() maxima = inasafe_class['numeric_default_max'] for key, value in maxima.iteritems(): bullets.add(u'%s : %s' % (key, value)) row.add(m.Cell(bullets)) else: row.add(m.Cell(inasafe_class['numeric_default_max'])) else: row.add(m.Cell(tr('unspecified'))) table.add(row) # Description goes in its own row with spanning row = m.Row() row.add(m.Cell('')) row.add(m.Cell(inasafe_class['description'], span=7)) table.add(row) # For hazard classes we also add the 'not affected' class manually: if definition['type'] == definitions.hazard_classification_type: row = m.Row() colour = definitions.not_exposed_class['color'].name() row.add(m.Cell(u'', attributes='style="background: %s;"' % colour)) description = definitions.not_exposed_class['description'] row.add(m.Cell(description, span=7)) table.add(row) message.add(table) if 'affected' in definition: if definition['affected']: message.add( m.Paragraph( tr('Exposure entities in this class ARE considered affected' ))) else: message.add( m.Paragraph( tr('Exposure entities in this class are NOT considered ' 'affected'))) if 'optional' in definition: if definition['optional']: message.add( m.Paragraph( tr('This class is NOT required in the hazard keywords.'))) else: message.add( m.Paragraph( tr('This class IS required in the hazard keywords.'))) return message