Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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