def format_impact_summary(self):
        """Format impact summary.

        :returns: The impact summary.
        :rtype: safe.messaging.Message
        """
        message = m.Message(style_class='container')
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None

        if 'headings' in self.impact_summary.keys():
            row = m.Row()
            row.add(m.Cell(self.impact_summary['headings'][0], header=True))
            row.add(
                m.Cell(self.impact_summary['headings'][1],
                       header=True,
                       align='right'))
            table.add(row)

        for category in self.impact_summary['fields']:
            row = m.Row()
            row.add(m.Cell(category[0], header=True))
            row.add(m.Cell(self.format_int(category[1]), align='right'))
            # For value field, if existed
            if len(category) > 2:
                row.add(m.Cell(self.format_int(category[2]), align='right'))
            table.add(row)
        message.add(table)
        return message
Exemple #2
0
def _start_glossary_table(group):
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Term')), header_flag=True)
    row.add(m.Cell(tr('Description')), header_flag=True)
    table.add(row)
    return table
    def minimum_needs_breakdown(self):
        """Breakdown by population.

        :returns: The population breakdown report.
        :rtype: list
        """
        message = m.Message(style_class='container')
        message.add(
            m.Heading(tr('Evacuated population minimum needs'),
                      **styles.INFO_STYLE))
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None
        total_needs = self.total_needs
        for frequency, needs in total_needs.items():
            row = m.Row()
            row.add(
                m.Cell(tr('Relief items to be provided %s' % frequency),
                       header=True))
            row.add(m.Cell(tr('Total'), header=True, align='right'))
            table.add(row)
            for resource in needs:
                row = m.Row()
                row.add(m.Cell(tr(resource['table name'])))
                row.add(
                    m.Cell(tr(format_int(resource['amount'])), align='right'))
                table.add(row)
        message.add(table)
        return message
Exemple #4
0
    def impact_summary(self):
        """The impact summary as per category

        :returns: The impact summary.
        :rtype: safe.message.Message
        """
        affected_categories = self.affected_road_categories

        message = m.Message(style_class='container')
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None

        row = m.Row()
        row.add(m.Cell(tr('Summary by road type'), header=True))
        for _ in affected_categories:
            # Add empty cell as many as affected_categories
            row.add(m.Cell('', header=True))

        if self.add_unaffected_column:
            # Add empty cell for un-affected road
            row.add(m.Cell('', header=True))

        # Add empty cell for total column
        row.add(m.Cell('', header=True))
        table.add(row)

        row = m.Row()
        row.add(m.Cell(tr('Road Type'), header=True))
        for affected_category in affected_categories:
            row.add(m.Cell(affected_category, header=True, align='right'))

        if self.add_unaffected_column:
            row.add(m.Cell(tr('Unaffected'), header=True, align='right'))

        row.add(m.Cell(tr('Total'), header=True, align='right'))
        table.add(row)

        total_affected = [0] * len(affected_categories)
        for (category, road_breakdown) in self.affected_road_lengths.items():
            number_affected = sum(road_breakdown.values())
            count = affected_categories.index(category)
            total_affected[count] = number_affected

        row = m.Row()
        row.add(m.Cell(tr('All (m)')))
        for total_affected_value in total_affected:
            row.add(
                m.Cell(format_int(int(total_affected_value)), align='right'))
        if self.add_unaffected_column:
            row.add(
                m.Cell(format_int(
                    int(self.total_road_length - sum(total_affected))),
                       align='right'))

        row.add(m.Cell(format_int(int(self.total_road_length)), align='right'))
        table.add(row)

        message.add(table)

        return message
Exemple #5
0
    def generate_analysis_result_html(self):
        """Return a HTML table of the analysis result

        :return: A file path to the html file saved to disk.
        """
        message = m.Message(style_class='report')
        # Table for affected population
        table = m.Table(style_class='table table-condensed table-striped')
        row = m.Row()
        total_people = self.tr('%s') % format_int(
            population_rounding(self.impact_data.total_affected_population))
        estimates_idp = self.tr('%s') % format_int(
            population_rounding(self.impact_data.estimates_idp))
        row.add(
            m.Cell(self.tr('Total affected population (people)'), header=True))
        row.add(m.Cell(total_people, style_class="text-right"))
        table.add(row)
        row = m.Row()
        row.add(m.Cell(self.tr('Estimates of IDP (people)'), header=True))
        row.add(m.Cell(estimates_idp, style_class="text-right"))
        table.add(row)
        message.add(table)
        # Table for minimum needs
        for k, v in self.impact_data.minimum_needs.iteritems():
            section = self.tr('Relief items to be provided %s :') % k
            # text = m.Text(section)
            row = m.Row(style_class='alert-info')
            row.add(m.Cell(section, header=True, attributes='colspan=2'))
            # message.add(text)
            table = m.Table(header=row,
                            style_class='table table-condensed table-striped')
            for e in v:
                row = m.Row()
                need_name = self.tr(e['name'])
                need_number = format_int(population_rounding(e['amount']))
                need_unit = self.tr(e['unit']['abbreviation'])
                if need_unit:
                    need_string = '%s (%s)' % (need_name, need_unit)
                else:
                    need_string = need_name
                row.add(m.Cell(need_string, header=True))
                row.add(m.Cell(need_number, style_class="text-right"))
                table.add(row)
            message.add(table)

        path = self.write_html_table('impact_analysis_report.html', message)
        return path
Exemple #6
0
def _start_glossary_table(group):
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    # first col for icons if present
    row.add(m.Cell(tr('Term'), header=True))
    row.add(m.Cell(tr('Description'), header=True))
    row.add(m.Cell(tr(''), header=True))
    table.add(row)
    return table
    def impact_summary(self):
        """The impact summary as per category

        :returns: The impact summary.
        :rtype: safe.messaging.Message
        """
        message = m.Message(style_class='container')
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None

        row = m.Row()
        row = self.head_row(row)
        table.add(row)

        second_row = m.Row()
        second_row.add(m.Cell(tr('All')))
        second_row = self.total_row(second_row)
        table.add(second_row)

        break_row = m.Row()
        break_row.add(
            m.Cell(tr('Breakdown by Area'), header=True, align='right'))
        # intentionally empty right cells
        break_row.add(m.Cell('', header=True))
        break_row.add(m.Cell('', header=True))
        break_row.add(m.Cell('', header=True))
        break_row.add(m.Cell('', header=True))
        break_row.add(m.Cell('', header=True))
        break_row.add(m.Cell('', header=True))
        table.add(break_row)

        table = self.impact_calculation(table)
        last_row = m.Row()
        last_row.add(m.Cell(tr('Total')))
        table.add(self.total_row(last_row))

        hazard_table = m.Table(
            style_class='table table-condensed table-striped')
        hazard_table = self.hazard_table(hazard_table)

        message.add(hazard_table)
        message.add(table)

        return message
    def format_postprocessing(self):
        """Format postprocessing.

        :returns: The postprocessing.
        :rtype: safe.messaging.Message
        """
        # List of post processor that can't be sum up. Issue #3118
        no_total = ['Age', 'Gender', 'MinimumNeeds']

        if not self.postprocessing:
            return False
        message = m.Message()
        for postprocessor, v in self.postprocessing.items():
            table = m.Table(style_class='table table-condensed table-striped')
            table.caption = v['caption']
            attributes = v['attributes']

            if attributes:
                header = m.Row()
                # Bold and align left the 1st one.

                header.add(m.Cell(attributes[0], header=True, align='left'))
                for attribute in attributes[1:]:
                    # Bold and align right.
                    header.add(m.Cell(attribute, header=True, align='right'))
                if postprocessor not in no_total:
                    header.add(m.Cell('Total', header=True, align='right'))
                table.add(header)

                for field in v['fields']:
                    row = m.Row()
                    # First column is string
                    row.add(m.Cell(field[0]))
                    total = 0
                    for value in field[1:]:
                        try:
                            val = int(value)
                            total += val
                            # Align right integers.
                            row.add(m.Cell(self.format_int(val),
                                           align='right'))
                        except ValueError:
                            # Catch no data value. Align left strings.
                            row.add(m.Cell(value, align='left'))
                    if postprocessor not in no_total:
                        row.add(
                            m.Cell(self.format_int(round(total)),
                                   align='right'))
                    table.add(row)

            message.add(table)

            for note in v['notes']:
                message.add(m.EmphasizedText(note))

        return message
Exemple #9
0
def _create_fields_table():
    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('Field Name'), header=True))
    row.add(m.Cell(tr('Type'), header=True))
    row.add(m.Cell(tr('Length'), header=True))
    row.add(m.Cell(tr('Precision'), header=True))
    table.add(row)
    return table
Exemple #10
0
    def _dict_to_row(keyword_value):
        """Helper to make a message row from a keyword where value is a dict.

        .. versionadded:: 3.2

        Use this when constructing a table from keywords to display as
        part of a message object. This variant will unpack the dict and
        present it nicely in the keyword value area as a nested table in the
        cell.

        We are expecting keyword value would be something like this:

            "{'high': ['Kawasan Rawan Bencana III'], "
            "'medium': ['Kawasan Rawan Bencana II'], "
            "'low': ['Kawasan Rawan Bencana I']}"

        Or by passing a python dict object with similar layout to above.

        i.e. A string representation of a dict where the values are lists.

        :param keyword_value: Value of the keyword to be rendered. This must
            be a string representation of a dict, or a dict.
        :type keyword_value: basestring, dict

        :returns: A table to be added into a cell in the keywords table.
        :rtype: safe.messaging.items.table
        """
        if isinstance(keyword_value, basestring):
            keyword_value = literal_eval(keyword_value)
        table = m.Table(style_class='table table-condensed')
        for key, value in keyword_value.items():
            row = m.Row()
            # First the heading
            if definition(key):
                name = definition(key)['name']
            else:
                name = tr(key.capitalize())
            row.add(m.Cell(m.ImportantText(name)))
            # Then the value. If it contains more than one element we
            # present it as a bullet list, otherwise just as simple text
            if isinstance(value, (tuple, list, dict, set)):
                if len(value) > 1:
                    bullets = m.BulletedList()
                    for item in value:
                        bullets.add(item)
                    row.add(m.Cell(bullets))
                elif len(value) == 0:
                    row.add(m.Cell(""))
                else:
                    row.add(m.Cell(value[0]))
            else:
                row.add(m.Cell(value))

            table.add(row)
        return table
Exemple #11
0
def _make_defaults_exposure_table():
    """Build headers for a table related to exposure classes.

    :return: A table with headers.
    :rtype: m.Table
    """
    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('Default values'), header=True))
    table.add(row)
    return table
Exemple #12
0
def _create_post_processor_subtable(item_list):
    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('Description'), header=True))
    table.add(row)
    for item in item_list:
        row = m.Row()
        row.add(m.Cell(item['key']))
        row.add(m.Cell(item['description']))
        table.add(row)
    return table
Exemple #13
0
def _make_defaults_table():
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    # first row is for colour - we dont use a header here as some tables
    # do not have colour...
    row.add(m.Cell(tr('')), header_flag=True)
    row.add(m.Cell(tr('Name')), header_flag=True)
    row.add(m.Cell(tr('Affected')), header_flag=True)
    row.add(m.Cell(tr('Displacement rate')), header_flag=True)
    row.add(m.Cell(tr('Default values')), header_flag=True)
    row.add(m.Cell(tr('Default min')), header_flag=True)
    row.add(m.Cell(tr('Default max')), header_flag=True)
    table.add(row)
    return table
Exemple #14
0
    def impact_summary(self):
        """The impact summary as per category.

        :returns: The impact summary.
        :rtype: safe.messaging.Message
        """
        affect_types = self._impact_breakdown
        message = m.Message(style_class='container')
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None
        row = m.Row()
        row.add(m.Cell('', header=True))  # intentionally empty top left cell
        row.add(m.Cell('Buildings affected', header=True))
        for (category, building_breakdown) in self.affected_buildings.items():
            total_affected = [0] * len(affect_types)
            for affected_breakdown in building_breakdown.values():
                for affect_type, number_affected in affected_breakdown.items():
                    count = affect_types.index(affect_type)
                    total_affected[count] += number_affected
            row = m.Row()
            row.add(m.Cell(tr(category), header=True))
            for affected in total_affected:
                row.add(m.Cell(format_int(affected), align='right'))
            table.add(row)

        if len(self._affected_categories) > 1:
            row = m.Row()
            row.add(m.Cell(tr('Affected buildings'), header=True))
            row.add(
                m.Cell(format_int(self.total_affected_buildings),
                       align='right'))
            table.add(row)

        # Only show not affected building row if the IF does not use custom
        # affected categories
        if self._affected_categories == self.affected_buildings.keys():
            row = m.Row()
            row.add(m.Cell(tr('Not affected buildings'), header=True))
            row.add(
                m.Cell(format_int(self.total_unaffected_buildings),
                       align='right'))
            table.add(row)

        row = m.Row()
        row.add(m.Cell(tr('Total'), header=True))
        row.add(m.Cell(format_int(self.total_buildings), align='right'))
        table.add(row)
        message.add(table)
        return message
Exemple #15
0
 def _tabulate_zero_impact(self):
     thresholds = self.parameters['thresholds'].value
     message = m.Message()
     table = m.Table(style_class='table table-condensed table-striped')
     row = m.Row()
     label = m.ImportantText(
         tr('People in %.1f m of water') % thresholds[-1])
     content = '%s' % format_int(self.total_evacuated)
     row.add(m.Cell(label))
     row.add(m.Cell(content))
     table.add(row)
     table.caption = self.question
     message.add(table)
     message = message.to_html(suppress_newlines=True)
     return message
    def format_impact_table(self):
        """Impact detailed report.

        :returns: The detailed report.
        :rtype: safe.messaging.Message
        """
        message = m.Message(style_class='container')
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None

        # Table header
        row = m.Row()
        attributes = self.impact_table['attributes']
        # Bold and align left the 1st one.
        row.add(m.Cell(attributes[0], header=True, align='left'))
        for attribute in attributes[1:]:
            # Bold and align right.
            row.add(m.Cell(attribute, header=True, align='right'))
        table.add(row)

        # Fields
        for record in self.impact_table['fields'][:-1]:
            row = m.Row()
            # Bold and align left the 1st one.
            row.add(m.Cell(record[0], header=True, align='left'))
            for content in record[1:-1]:
                # Align right.
                row.add(m.Cell(format_int(int(content)), align='right'))
            # Bold and align right the last one.
            row.add(
                m.Cell(format_int(int(record[-1])), header=True,
                       align='right'))
            table.add(row)

        # Total Row
        row = m.Row()
        last_row = self.impact_table['fields'][-1]
        # Bold and align left the 1st one.
        row.add(m.Cell(last_row[0], header=True, align='left'))
        for content in last_row[1:]:
            # Bold and align right.
            row.add(
                m.Cell(format_int(int(content)), header=True, align='right'))
        table.add(row)

        message.add(table)

        return message
    def impact_summary(self):
        """The impact summary as per category

        :returns: The impact summary.
        :rtype: safe.messaging.Message
        """
        message = m.Message(style_class='container')
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None
        row = m.Row()
        row.add(
            m.Cell(tr('Population needing evacuation <sup>1</sup>'),
                   header=True))
        evacuated = format_int(population_rounding(self.total_evacuated))
        row.add(m.Cell(evacuated, align='right'))
        table.add(row)
        if len(self.impact_category_ordering):
            table.add(m.Row())  # add a blank line
            row = m.Row()
            row.add(m.Cell(tr('Total affected population'), header=True))
            affected = format_int(
                population_rounding(self.total_affected_population))
            row.add(m.Cell(affected, align='right'))
            table.add(row)

            for category in self.impact_category_ordering:
                population_in_category = self.lookup_category(category)
                population_in_category = format_int(
                    population_rounding(population_in_category))
                row = m.Row()
                row.add(m.Cell(tr(category), header=True))
                row.add(m.Cell(population_in_category, align='right'))
                table.add(row)

        table.add(m.Row())  # add a blank line

        row = m.Row()
        unaffected = format_int(population_rounding(
            self.unaffected_population))
        row.add(m.Cell(tr('Unaffected population'), header=True))
        row.add(m.Cell(unaffected, align='right'))
        table.add(row)
        message.add(table)
        return message
Exemple #18
0
def _make_defaults_hazard_table():
    """Build headers for a table related to hazard classes.

    :return: A table with headers.
    :rtype: m.Table
    """
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    # first row is for colour - we dont use a header here as some tables
    # do not have colour...
    row.add(m.Cell(tr(''), header=True))
    row.add(m.Cell(tr('Name'), header=True))
    row.add(m.Cell(tr('Affected'), header=True))
    row.add(m.Cell(tr('Fatality rate'), header=True))
    row.add(m.Cell(tr('Displacement rate'), header=True))
    row.add(m.Cell(tr('Default values'), header=True))
    row.add(m.Cell(tr('Default min'), header=True))
    row.add(m.Cell(tr('Default max'), header=True))
    table.add(row)
    return table
Exemple #19
0
def no_population_impact_message(question):
    """Create a message that indicates that no population were impacted.

    :param question: A question sentence that will be used as the table
        caption.
    :type question: basestring

    :returns: An html document containing a nice message saying nobody was
        impacted.
    :rtype: basestring
    """
    message = m.Message()
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    label = m.ImportantText(tr('People impacted'))
    content = 0
    row.add(m.Cell(label))
    row.add(m.Cell(content))
    table.add(row)
    table.caption = question
    message.add(table)
    message = message.to_html(suppress_newlines=True)
    return message
Exemple #20
0
    def _generate_tables(self, aoi_mode=True):
        """Parses the postprocessing output as one table per postprocessor.

        TODO: This should rather return json and then have a helper method to
        make html from the JSON.

        :param aoi_mode: adds a Total in aggregation areas
        row to the calculated table
        :type aoi_mode: bool

        :returns: The html.
        :rtype: str
        """
        message = m.Message()

        for processor, results_list in self.output.iteritems():
            self.current_output_postprocessor = processor
            # results_list is for example:
            # [
            # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([
            #        (u'Total', {'value': 977536, 'metadata': {}}),
            #        (u'Female population', {'value': 508319, 'metadata': {}}),
            #        (u'Weekly hygiene packs', {'value': 403453, 'metadata': {
            #         'description': 'Females hygiene packs for weekly use'}})
            #    ]))
            # ]
            # sorting using the first indicator of a postprocessor
            sorted_results = sorted(results_list,
                                    key=self._sort_no_data,
                                    reverse=True)

            # init table
            has_no_data = False
            table = m.Table(style_class='table table-condensed table-striped')
            name = get_postprocessor_human_name(processor).lower()
            translated_name = tr(name)

            if name == 'building type':
                table.caption = tr('Closed buildings')
            elif name == 'road type':
                table.caption = tr('Closed roads')
            elif name == 'people':
                table.caption = tr('Affected people')

            # Dirty hack to make "evacuated" come out in the report.
            # Currently only MinimumNeeds that calculate from evacuation
            # percentage.
            if processor == 'MinimumNeeds':
                if 'evacuation_percentage' in self.function_parameters.keys():
                    table.caption = tr(
                        'Detailed %s report '
                        '(for people needing evacuation)') % translated_name
                else:
                    table.caption = tr('Detailed %s report '
                                       '(affected people)') % translated_name

            if processor in ['Gender', 'Age']:
                table.caption = tr('Detailed %s report '
                                   '(affected people)') % translated_name

            empty_table = not sorted_results[0][1]
            if empty_table:
                # Due to an error? The table is empty.
                message.add(table)
                message.add(
                    m.EmphasizedText(
                        tr('Could not compute the %s report.') %
                        translated_name))
                continue

            header = m.Row()
            header.add(str(self.attribute_title).capitalize())
            for calculation_name in sorted_results[0][1]:
                header.add(self.tr(calculation_name))
            table.add(header)

            # used to calculate the totals row as per issue #690
            postprocessor_totals = OrderedDict()

            null_index = 0  # counting how many null value in the data
            for zone_name, calc in sorted_results:
                if isinstance(zone_name, QPyNullVariant):
                    # I have made sure that the zone_name won't be Null in
                    # run method. But just in case there is something wrong.
                    zone_name = tr('Unnamed Area %s' % null_index)
                    null_index += 1
                if name == 'road type':
                    # We add the unit 'meter' as we are counting roads.
                    # proper format for i186
                    zone_name = tr('%(zone_name)s (m)') % {
                        'zone_name': tr(zone_name)
                    }
                row = m.Row(zone_name)

                for indicator, calculation_data in calc.iteritems():
                    value = calculation_data['value']
                    value = str(unhumanize_number(value))
                    if value == self.aggregator.get_default_keyword('NO_DATA'):
                        has_no_data = True
                        try:
                            postprocessor_totals[indicator] += 0
                        except KeyError:
                            postprocessor_totals[indicator] = 0
                    else:
                        value = int(value)
                        try:
                            postprocessor_totals[indicator] += value
                        except KeyError:
                            postprocessor_totals[indicator] = value
                    row.add(format_int(value))
                table.add(row)

            if not aoi_mode:
                # add the totals row
                row = m.Row(self.tr('Total in aggregation areas'))
                for _, total in postprocessor_totals.iteritems():
                    row.add(format_int(total))
                table.add(row)

            # add table to message
            message.add(table)
            if has_no_data:
                message.add(
                    m.EmphasizedText(
                        self.
                        tr('"%s" values mean that there where some problems while '
                           'calculating them. This did not affect the other '
                           'values.') %
                        (self.aggregator.get_default_keyword('NO_DATA'))))
            caption = m.EmphasizedText(
                self.tr(
                    'Columns and rows containing only 0 or "%s" values are '
                    'excluded from the tables.' %
                    self.aggregator.get_default_keyword('NO_DATA')))
            message.add(m.Paragraph(caption, style_class='caption'))

        return message
Exemple #21
0
    def _generate_tables(self, aoi_mode=True):
        """Parses the postprocessing output as one table per postprocessor.

        TODO: This should rather return json and then have a helper method to
        make html from the JSON.

        :param aoi_mode: adds a Total in aggregation areas
        row to the calculated table
        :type aoi_mode: bool

        :returns: The html.
        :rtype: str
        """
        message = m.Message()

        for processor, results_list in self.output.iteritems():

            self.current_output_postprocessor = processor
            # results_list is for example:
            # [
            # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([
            #        (u'Total', {'value': 977536, 'metadata': {}}),
            #        (u'Female population', {'value': 508319, 'metadata': {}}),
            #        (u'Weekly hygiene packs', {'value': 403453, 'metadata': {
            #         'description': 'Females hygiene packs for weekly use'}})
            #    ]))
            # ]

            # sorting using the first indicator of a postprocessor
            sorted_results = sorted(
                results_list,
                key=self._sort_no_data,
                reverse=True)

            # init table
            has_no_data = False
            table = m.Table(
                style_class='table table-condensed table-striped')
            table.caption = self.tr('Detailed %s report') % (tr(
                get_postprocessor_human_name(processor)).lower())

            # Dirty hack to make "evacuated" comes out in the report.
            # Currently only MinimumNeeds that calculate from evacuation
            # percentage.
            if processor == 'MinimumNeeds':
                if 'evacuation_percentage' in self.function_parameters.keys():
                    table.caption = self.tr(
                        'Detailed %s report (for people needing '
                        'evacuation)') % (
                            tr(get_postprocessor_human_name(processor)).lower()
                        )
                else:
                    table.caption = self.tr(
                        'Detailed %s report (affected people)') % (
                            tr(get_postprocessor_human_name(processor)).lower()
                        )

            if processor in ['Gender', 'Age']:
                table.caption = self.tr(
                    'Detailed %s report (affected people)') % (
                        tr(get_postprocessor_human_name(processor)).lower())

            header = m.Row()
            header.add(str(self.attribute_title).capitalize())
            for calculation_name in sorted_results[0][1]:
                header.add(self.tr(calculation_name))
            table.add(header)

            # used to calculate the totals row as per issue #690
            postprocessor_totals = OrderedDict()

            for zone_name, calc in sorted_results:
                row = m.Row(zone_name)

                for indicator, calculation_data in calc.iteritems():
                    value = calculation_data['value']
                    value = str(unhumanize_number(value))
                    if value == self.aggregator.get_default_keyword('NO_DATA'):
                        has_no_data = True
                        value += ' *'
                        try:
                            postprocessor_totals[indicator] += 0
                        except KeyError:
                            postprocessor_totals[indicator] = 0
                    else:
                        value = int(value)
                        try:
                            postprocessor_totals[indicator] += value
                        except KeyError:
                            postprocessor_totals[indicator] = value
                    row.add(format_int(value))
                table.add(row)

            if not aoi_mode:
                # add the totals row
                row = m.Row(self.tr('Total in aggregation areas'))
                for _, total in postprocessor_totals.iteritems():
                    row.add(format_int(total))
                table.add(row)

            # add table to message
            message.add(table)
            if has_no_data:
                message.add(m.EmphasizedText(self.tr(
                    '* "%s" values mean that there where some problems while '
                    'calculating them. This did not affect the other '
                    'values.') % (
                        self.aggregator.get_default_keyword(
                            'NO_DATA'))))

        return message
Exemple #22
0
def field_mapping_help_content():
    """Helper method that returns just the content in extent mode.

    This method was added so that the text could be reused in the
    wizard.

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    message = m.Message()
    paragraph = m.Paragraph(
        tr('Field mapping describes the process of matching one or more fields '
           'in an attribute table to a concept in InaSAFE. The field mappings '
           'tool InaSAFE allows you to match concepts such as "elderly", '
           '"disabled people", "pregnant" and so on to their counterpart fields '
           'in either an aggregation layer or an exposure population vector '
           'layer.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        m.ImportantText(
            'Note: It is not possible to use this tool with raster population '
            'exposure data, but ratios defined in aggregation layers will be '
            'used when raster exposure population data is used.'))
    message.add(paragraph)

    paragraph = m.Paragraph(m.Image('file:///%s/img/screenshots/'
                                    'demographic-concepts-screenshot.png' %
                                    resources_path()),
                            style_class='text-center')
    message.add(paragraph)

    paragraph = m.Paragraph(
        tr('The illustration above shows the principle behind InaSAFE\'s '
           'demographic breakdown reporting system. The idea here is to support '
           'the production of a detailed demographic breakdown when carrying out '
           'an analysis with a population exposure vector dataset. So for '
           'example instead of simply reporting on the total number of people '
           'exposed to a hazard, we want to break down the affected population '
           'into distinct demographic groups. In InaSAFE by default we consider '
           'three groups:'))
    message.add(paragraph)

    bullets = m.BulletedList()
    bullets.add(
        m.Paragraph(
            m.ImportantText(tr('Gender: ')),
            tr('The gender group reports on gender specific demographics '
               'including things like the number of women of child bearing age, '
               'number of pregnant women, number of lactating women and so on.'
               )))
    bullets.add(
        m.Paragraph(
            m.ImportantText(tr('Age: ')),
            tr('The age group reports on age specific demographics including '
               'things like the number of infants, children, young adults, '
               'adults elderly people and so on.')))
    bullets.add(
        m.Paragraph(
            m.ImportantText(tr('Vulnerable people: ')),
            tr('The vulnerable people group reports on specific demographics '
               'relating to vulnerability including things like the number of '
               'infants, elderly people, disabled people and so on.')))
    message.add(bullets)
    paragraph = m.Paragraph(
        tr('In the diagram above, you can see that we have an "age" group '
           '(column on the right) which, for purposes of illustration, has two '
           'age classes: "infant" and "child" (center column). These age classes '
           'are defined in InaSAFE metadata and there are actually five classes '
           'in a default installation. In the left hand column you can see a '
           'number of columns listed from the attribute table. In this example '
           'our population data contains columns for different age ranges ('
           '0-1, 1-2, 2-4, 4-6). The field mapping tool can be used in order '
           'to combine the data in the "0 - 1" and "1 - 2" columns into a '
           'new column called "infant". In the next section of this document we '
           'enumerate the different groups and concepts that InaSAFE supports '
           'when generating demographic breakdowns.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('When the tool is used, it will write additional data to the '
           'exposure or aggregation layer keywords so that your preferred '
           'concept mappings will be used when reports are generated after the '
           'analysis is carried out. You should note the following special '
           'characteristics of the field mapping tool when used for aggregation '
           'datasets versus when used for vector population exposure datasets:'
           ))
    message.add(paragraph)
    paragraph = m.Paragraph(
        m.ImportantText(tr('Aggregation datasets: ')),
        tr('For aggregation datasets, the field mapping tool uses global '
           'defaults (see the InaSAFE Options Dialog documentation for more '
           'details) or dataset level defaults to determine which ratios '
           'should be used to calculate concept values. For example, in the '
           'age group the aggregation dataset may specify that infants '
           'should by calculated as a ratio of 0.1% of the total population. '
           'Note that for aggregation datasets you can only use ratios, '
           'not counts.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        m.ImportantText(tr('Vector population exposure datasets: ')),
        tr('For exposure datasets, ratios are not supported, only counts. '
           'The field mappings carried out here will be used to generate '
           'new columns during a pre-processing step before the actual '
           'analysis is carried out.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('The interplay between default ratios, aggregation layer '
           'provided ratios and population exposure layers is illustrated '
           'in the table below.'))
    message.add(paragraph)

    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Aggregation'), header=True))
    row.add(m.Cell(tr('Raster'), header=True))
    row.add(m.Cell(tr('Vector, no counts'), header=True))
    row.add(m.Cell(tr('Vector with counts'), header=True))
    row.add(m.Cell(tr('Notes'), header=True))
    table.add(row)
    row = m.Row([
        tr('No aggregation'),
        tr('Use global default ratio'),
        tr('Use global default ratio'),
        tr('Use count to determine ratio'),
        tr(''),
    ])
    table.add(row)
    row = m.Row([
        tr('Aggregation, ratio not set'),
        tr('Use global default ratio'),
        tr('Do nothing'),
        tr('Use count to determine ratio'),
        tr(''),
    ])
    table.add(row)
    row = m.Row([
        tr('Aggregation, ratio value set'),
        tr('Use aggregation layer ratio'),
        tr('Use aggregation layer ratio'),
        tr('Use count to determine ratio'),
        tr(''),
    ])
    table.add(row)
    row = m.Row([
        tr('Aggregation, ratio field mapping set'),
        tr('Use aggregation layer ratio'),
        tr('Use aggregation layer ratio'),
        tr('Use count to determine ratio'),
        tr(''),
    ])
    table.add(row)
    message.add(table)

    return message
Exemple #23
0
    def _value_maps_row(value_maps_keyword):
        """Helper to make a message row from a value maps.

        Expected keywords:
        'value_maps': {
            'structure': {
                'ina_structure_flood_hazard_classification': {
                    'classes': {
                        'low': [1, 2, 3],
                        'medium': [4],
                        'high': [5, 6]
                    },
                    'active': True
                },
                'ina_structure_flood_hazard_4_class_classification':
                {
                    'classes': {
                        'low': [1],
                        'medium': [2, 3, 4],
                        'high': [5, 6, 7],
                        'very_high': [8]
                    },
                    'active': False

                }
            },
            'population': {
                'ina_population_flood_hazard_classification': {
                    'classes': {
                        'low': [1],
                        'medium': [2, 3],
                        'high': [4, 5, 6]
                    },
                    'active': False
                },
                'ina_population_flood_hazard_4_class_classification':
                {
                    'classes': {
                        'low': [1, 2],
                        'medium': [3, 4],
                        'high': [4, 5, 6],
                        'very_high': [6, 7, 8]
                    },
                    'active': True
                }
            },
        }

        :param value_maps_keyword: Value of the keyword to be rendered. This
            must be a string representation of a dict, or a dict.
        :type value_maps_keyword: basestring, dict

        :returns: A table to be added into a cell in the keywords table.
        :rtype: safe.messaging.items.table
        """
        if isinstance(value_maps_keyword, basestring):
            value_maps_keyword = literal_eval(value_maps_keyword)

        table = m.Table(style_class='table table-condensed table-striped')

        i = 0
        for exposure_key, classifications in value_maps_keyword.items():
            i += 1
            exposure = definition(exposure_key)
            exposure_row = m.Row()
            exposure_row.add(m.Cell(m.ImportantText(tr('Exposure'))))
            exposure_row.add(m.Cell(exposure['name']))
            table.add(exposure_row)

            classification_row = m.Row()
            classification_row.add(
                m.Cell(m.ImportantText(tr('Classification'))))
            active_classification = None
            for classification, value in classifications.items():
                if value.get('active'):
                    active_classification = definition(classification)
                    if active_classification.get('name'):
                        classification_row.add(
                            m.Cell(active_classification['name']))
                    break

            if not active_classification:
                classification_row.add(m.Cell(tr('No classifications set.')))
                continue
            table.add(classification_row)

            header = m.Row()
            header.add(m.Cell(tr('Class name')))
            header.add(m.Cell(tr('Values')))
            table.add(header)
            classes = active_classification.get('classes')
            # Sort by value, put the lowest first
            classes = sorted(classes, key=lambda k: k['value'])
            for the_class in classes:
                value_map = classifications[
                    active_classification['key']]['classes'].get(
                        the_class['key'], [])
                row = m.Row()
                row.add(m.Cell(the_class['name']))
                row.add(m.Cell(', '.join([str(v) for v in value_map])))
                table.add(row)

            if i < len(value_maps_keyword):
                # Empty row
                empty_row = m.Row()
                empty_row.add(m.Cell(''))
                empty_row.add(m.Cell(''))
                table.add(empty_row)

        return table
Exemple #24
0
    def _threshold_to_row(thresholds_keyword):
        """Helper to make a message row from a threshold

        We are expecting something like this:

        {
            'thresholds': {
                'structure': {
                    'ina_structure_flood_hazard_classification': {
                        'classes': {
                            'low': [1, 2],
                            'medium': [3, 4],
                            'high': [5, 6]
                        },
                        'active': True
                    },
                    'ina_structure_flood_hazard_4_class_classification':
                    {
                        'classes': {
                            'low': [1, 2],
                            'medium': [3, 4],
                            'high': [5, 6],
                            'very_high': [7, 8]
                        },
                        'active': False

                    }
                },
                'population': {
                    'ina_population_flood_hazard_classification': {
                        'classes': {
                            'low': [1, 2.5],
                            'medium': [2.5, 4.5],
                            'high': [4.5, 6]
                        },
                        'active': False
                    },
                    'ina_population_flood_hazard_4_class_classification':
                    {
                        'classes': {
                            'low': [1, 2.5],
                            'medium': [2.5, 4],
                            'high': [4, 6],
                            'very_high': [6, 8]
                        },
                        'active': True
                    }
                },
            },

        Each value is a list with exactly two element [a, b], where a <= b.

        :param thresholds_keyword: Value of the keyword to be rendered. This
            must be a string representation of a dict, or a dict.
        :type thresholds_keyword: basestring, dict

        :returns: A table to be added into a cell in the keywords table.
        :rtype: safe.messaging.items.table
        """
        if isinstance(thresholds_keyword, basestring):
            thresholds_keyword = literal_eval(thresholds_keyword)

        for k, v in thresholds_keyword.items():
            # If the v is not dictionary, it should be the old value maps.
            # To handle thresholds in the Impact Function.
            if not isinstance(v, dict):
                table = m.Table(style_class='table table-condensed')

                for key, value in thresholds_keyword.items():
                    row = m.Row()
                    name = definition(key)['name'] if definition(key) else key
                    row.add(m.Cell(m.ImportantText(name)))
                    pretty_value = tr('%s to %s' % (value[0], value[1]))
                    row.add(m.Cell(pretty_value))

                    table.add(row)
                return table

        table = m.Table(style_class='table table-condensed table-striped')

        i = 0
        for exposure_key, classifications in thresholds_keyword.items():
            i += 1
            exposure = definition(exposure_key)
            exposure_row = m.Row()
            exposure_row.add(m.Cell(m.ImportantText('Exposure')))
            exposure_row.add(m.Cell(m.Text(exposure['name'])))
            exposure_row.add(m.Cell(''))
            table.add(exposure_row)

            active_classification = None
            classification_row = m.Row()
            classification_row.add(m.Cell(m.ImportantText('Classification')))
            for classification, value in classifications.items():
                if value.get('active'):
                    active_classification = definition(classification)
                    classification_row.add(
                        m.Cell(active_classification['name']))
                    classification_row.add(m.Cell(''))
                    break

            if not active_classification:
                classification_row.add(m.Cell(tr('No classifications set.')))
                classification_row.add(m.Cell(''))
                continue

            table.add(classification_row)

            header = m.Row()
            header.add(m.Cell(tr('Class name')))
            header.add(m.Cell(tr('Minimum')))
            header.add(m.Cell(tr('Maximum')))
            table.add(header)
            classes = active_classification.get('classes')
            # Sort by value, put the lowest first
            classes = sorted(classes, key=lambda k: k['value'])
            for the_class in classes:
                threshold = classifications[
                    active_classification['key']]['classes'][the_class['key']]
                row = m.Row()
                row.add(m.Cell(the_class['name']))
                row.add(m.Cell(threshold[0]))
                row.add(m.Cell(threshold[1]))
                table.add(row)

            if i < len(thresholds_keyword):
                # Empty row
                empty_row = m.Row()
                empty_row.add(m.Cell(''))
                empty_row.add(m.Cell(''))
                table.add(empty_row)

        return table
Exemple #25
0
    def to_message(self, keywords=None, show_header=True):
        """Format keywords as a message object.

        .. versionadded:: 3.2

        .. versionchanged:: 3.3 - default keywords to None

        The message object can then be rendered to html, plain text etc.

        :param keywords: Keywords to be converted to a message. Optional. If
            not passed then we will attempt to get keywords from self.layer
            if it is not None.
        :type keywords: dict

        :param show_header: Flag indicating if InaSAFE logo etc. should be
            added above the keywords table. Default is True.
        :type show_header: bool

        :returns: A safe message object containing a table.
        :rtype: safe.messaging.Message
        """
        if keywords is None and self.layer is not None:
            keywords = self.read_keywords(self.layer)
        # This order was determined in issue #2313
        preferred_order = [
            'title',
            'layer_purpose',
            'exposure',
            'hazard',
            'hazard_category',
            'layer_geometry',
            'layer_mode',
            'classification',
            'exposure_unit',
            'continuous_hazard_unit',
            'value_map',  # attribute values
            'thresholds',  # attribute values
            'value_maps',  # attribute values
            'inasafe_fields',
            'inasafe_default_values',
            'resample',
            'source',
            'url',
            'scale',
            'license',
            'date',
            'keyword_version'
        ]  # everything else in arbitrary order
        report = m.Message()
        if show_header:
            logo_element = m.Brand()
            report.add(logo_element)
            report.add(
                m.Heading(tr('Layer keywords:'), **styles.BLUE_LEVEL_4_STYLE))
            report.add(
                m.Text(
                    tr('The following keywords are defined for the active layer:'
                       )))

        table = m.Table(style_class='table table-condensed table-striped')
        # First render out the preferred order keywords
        for keyword in preferred_order:
            if keyword in keywords:
                value = keywords[keyword]
                row = self._keyword_to_row(keyword, value)
                keywords.pop(keyword)
                table.add(row)

        # now render out any remaining keywords in arbitrary order
        for keyword in keywords:
            value = keywords[keyword]
            row = self._keyword_to_row(keyword, value)
            table.add(row)

        # If the keywords class was instantiated with a layer object
        # we can add some context info not stored in the keywords themselves
        # but that is still useful to see...
        if self.layer:
            # First the CRS
            keyword = tr('Reference system')
            value = self.layer.crs().authid()
            row = self._keyword_to_row(keyword, value)
            table.add(row)
            # Next the data source
            keyword = tr('Layer source')
            value = self.layer.source()
            row = self._keyword_to_row(keyword, value, wrap_slash=True)
            table.add(row)

        # Finalise the report
        report.add(table)
        return report
Exemple #26
0
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    other contexts.

    .. versionadded:: 3.2.2

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """

    message = m.Message()
    paragraph = m.Paragraph(
        m.Image(
            'file:///%s/img/screenshots/'
            'batch-calculator-screenshot.png' % resources_path()),
        style_class='text-center'
    )
    message.add(paragraph)

    message.add(m.Paragraph(tr(
        'With this tool you can set up numerous scenarios and run them all in '
        'one go. A typical use case may be where you define a number of e.g. '
        'flood impact scenarios all using a standard data set e.g. '
        'flood.shp. As new flood data becomes available you replace flood.shp '
        'and rerun the scenarios using the batch runner. Using this approach '
        'you can quickly produce regional contingency plans as your '
        'understanding of hazards changes. When you run the batch of '
        'scenarios, pdf reports are generated automatically and all placed in '
        'a single common directory making it easy for you to browse and '
        'disseminate the reports produced.')))

    message.add(m.Paragraph(tr(
        'When the batch process completes, it will also produce a summary '
        'report like this:')))

    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row(m.Cell(tr('InaSAFE Batch Report File')), header=True)
    table.add(row)
    table.add(m.Row(m.Cell('P: gempa bumi Sumatran fault (Mw7.8)')))
    table.add(m.Row(m.Cell('P: gempa di Yogya tahun 2006')))
    table.add(m.Row(m.Cell('P: banjir jakarta 2007')))
    table.add(m.Row(m.Cell('P: Tsunami di Maumere (Mw 8.1)')))
    table.add(m.Row(m.Cell('P: gempa Mw6.5 Palu-Koro Fault')))
    table.add(m.Row(m.Cell('P: gunung merapi meletus')))
    table.add(m.Row(m.Cell('-----------------------------')))
    table.add(m.Row(m.Cell(tr('Total passed: 6'))))
    table.add(m.Row(m.Cell(tr('Total failed: 0'))))
    table.add(m.Row(m.Cell(tr('Total tasks: 6'))))

    message.add(table)

    # message.add(m.Paragraph(tr(
    #     'For advanced users there is also the ability to batch run python '
    #     'scripts using this tool, but this should be considered an '
    #     'experimental</strong> feature still at this stage.')))

    message.add(m.Paragraph(tr(
        'Before running the Batch Runner you might want to use the \'save '
        'scenario\' tool to first save some scenarios on which you '
        'can let the batch runner do its work. This tool lets you run saved '
        'scenarios in one go. It lets you select scenarios or let run all '
        'scenarios in one go.')))
    return message
Exemple #27
0
    def show_current_state(self):
        """Setup the UI for QTextEdit to show the current state."""
        right_panel_heading = QLabel(tr('Status'))
        right_panel_heading.setFont(big_font)
        right_panel_heading.setSizePolicy(
            QSizePolicy.Maximum, QSizePolicy.Maximum)
        self.right_layout.addWidget(right_panel_heading)

        message = m.Message()
        if self.layer_mode == layer_mode_continuous:
            title = tr('Thresholds')
        else:
            title = tr('Value maps')

        message.add(m.Heading(title, **INFO_STYLE))

        for i in range(len(self.exposures)):
            message.add(m.Text(self.exposure_labels[i]))

            classification = self.get_classification(
                self.exposure_combo_boxes[i])
            if self.layer_mode == layer_mode_continuous:
                thresholds = self.thresholds.get(self.exposures[i]['key'])
                if not thresholds or not classification:
                    message.add(m.Paragraph(tr('No classifications set.')))
                    continue
                table = m.Table(
                    style_class='table table-condensed table-striped')
                header = m.Row()
                header.add(m.Cell(tr('Class name')))
                header.add(m.Cell(tr('Minimum')))
                header.add(m.Cell(tr('Maximum')))
                table.add(header)
                classes = classification.get('classes')
                # Sort by value, put the lowest first
                classes = sorted(classes, key=lambda k: k['value'])
                for the_class in classes:
                    threshold = thresholds[classification['key']]['classes'][
                        the_class['key']]
                    row = m.Row()
                    row.add(m.Cell(the_class['name']))
                    row.add(m.Cell(threshold[0]))
                    row.add(m.Cell(threshold[1]))
                    table.add(row)
            else:
                value_maps = self.value_maps.get(self.exposures[i]['key'])
                if not value_maps or not classification:
                    message.add(m.Paragraph(tr('No classifications set.')))
                    continue
                table = m.Table(
                    style_class='table table-condensed table-striped')
                header = m.Row()
                header.add(m.Cell(tr('Class name')))
                header.add(m.Cell(tr('Value')))
                table.add(header)
                classes = classification.get('classes')
                # Sort by value, put the lowest first
                classes = sorted(classes, key=lambda k: k['value'])
                for the_class in classes:
                    value_map = value_maps[classification['key']][
                        'classes'].get(the_class['key'], [])
                    row = m.Row()
                    row.add(m.Cell(the_class['name']))
                    row.add(m.Cell(', '.join([str(v) for v in value_map])))
                    table.add(row)
            message.add(table)

        # status_text_edit = QTextBrowser(None)
        status_text_edit = QWebView(None)
        status_text_edit.setSizePolicy(
            QSizePolicy.Ignored,
            QSizePolicy.Ignored)

        status_text_edit.page().mainFrame().setScrollBarPolicy(
            Qt.Horizontal,
            Qt.ScrollBarAlwaysOff)
        html_string = html_header() + message.to_html() + html_footer()
        status_text_edit.setHtml(html_string)
        self.right_layout.addWidget(status_text_edit)
Exemple #28
0
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 4.0.0

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    # We will store a contents section at the top for easy navigation
    table_of_contents = m.Message()
    # and this will be the main message that we create
    message = m.Message()

    _create_section_header(message,
                           table_of_contents,
                           'overview',
                           tr('Overview'),
                           heading_level=1)
    ##
    # Credits and disclaimers ...
    ##
    _create_section_header(message,
                           table_of_contents,
                           'disclaimer',
                           tr('Disclaimer'),
                           heading_level=2)
    message.add(m.Paragraph(definitions.messages.disclaimer()))

    _create_section_header(message,
                           table_of_contents,
                           'limitations',
                           tr('Limitations and License'),
                           heading_level=2)
    bullets = m.BulletedList()
    for item in definitions.limitations():
        bullets.add(item)
    message.add(bullets)

    ##
    # Basic concepts ...
    ##
    ##
    # Help dialog contents ...
    ##
    _create_section_header(message,
                           table_of_contents,
                           'glossary',
                           tr('Glossary of terms'),
                           heading_level=1)

    last_group = None
    table = None
    for key, value in definitions.concepts.iteritems():
        current_group = value['group']
        if current_group != last_group:
            if last_group is not None:
                message.add(table)
            _create_section_header(message,
                                   table_of_contents,
                                   current_group.replace(' ', '-'),
                                   current_group,
                                   heading_level=2)
            table = _start_glossary_table(current_group)
            last_group = current_group
        row = m.Row()
        term = value['key'].replace('_', ' ').title()
        description = m.Message(value['description'])
        for citation in value['citations']:
            if citation['text'] in [None, '']:
                continue
            if citation['link'] in [None, '']:
                description.add(m.Paragraph(citation['text']))
            else:
                description.add(
                    m.Paragraph(m.Link(citation['link'], citation['text'])))
        row.add(m.Cell(term))
        row.add(m.Cell(description))
        url = _definition_icon_url(value)
        if url:
            row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE)))
        else:
            row.add(m.Cell(''))
        table.add(row)
    # ensure the last group's table is added
    message.add(table)

    ##
    # Help dialog contents ...
    ##
    _create_section_header(message,
                           table_of_contents,
                           'core-functionality',
                           tr('Core functionality and tools'),
                           heading_level=1)

    _create_section_header(message,
                           table_of_contents,
                           'dock',
                           tr('The InaSAFE Dock'),
                           heading_level=2)
    message.add(dock_help())

    _create_section_header(message,
                           table_of_contents,
                           'reports',
                           tr('InaSAFE Reports'),
                           heading_level=2)
    message.add(report_help())

    _create_section_header(
        message,
        table_of_contents,
        'extents',
        tr('Managing analysis extents with the extents selector'),
        heading_level=2)
    message.add(extent_help())

    _create_section_header(message,
                           table_of_contents,
                           'options',
                           tr('InaSAFE Options'),
                           heading_level=2)
    message.add(options_help())

    _create_section_header(message,
                           table_of_contents,
                           'batch-runner',
                           tr('The Batch Runner'),
                           heading_level=2)
    message.add(batch_help())

    _create_section_header(message,
                           table_of_contents,
                           'osm-downloader',
                           tr('The OpenStreetmap Downloader'),
                           heading_level=2)
    message.add(osm_help())

    _create_section_header(message,
                           table_of_contents,
                           'petabencana-downloader',
                           tr('The PetaBencana Downloader'),
                           heading_level=2)
    message.add(petabencana_help())

    _create_section_header(message,
                           table_of_contents,
                           'shakemap-converter',
                           tr('The Shakemap Converter'),
                           heading_level=2)
    message.add(shakemap_help())

    _create_section_header(message,
                           table_of_contents,
                           'multi-buffer-tool',
                           tr('The Multi Buffer Tool'),
                           heading_level=2)
    message.add(multi_buffer_help())

    # Field mapping tool has a few added bits to enumerate the groups
    _create_section_header(message,
                           table_of_contents,
                           'field-mapping-tool',
                           tr('The Field Mapping Tool'),
                           heading_level=2)
    message.add(field_mapping_tool_help())

    _create_section_header(message,
                           table_of_contents,
                           'exposure-groups',
                           tr('Exposure Groups'),
                           heading_level=3)
    message.add(
        m.Paragraph(
            'The following demographic groups apply only to vector population '
            'exposure layers:'))
    for group in population_field_groups:
        definition_to_message(group,
                              message,
                              table_of_contents,
                              heading_level=4)

    _create_section_header(message,
                           table_of_contents,
                           'aggregation-groups',
                           tr('Aggregation Groups'),
                           heading_level=3)
    message.add(
        m.Paragraph(
            'The following demographic groups apply only to aggregation layers:'
        ))
    for group in aggregation_field_groups:
        definition_to_message(group,
                              message,
                              table_of_contents,
                              heading_level=4)

    # End of field mapping tool help

    # Keep this last in the tool section please as it has subsections
    # and so uses the top level section style
    _create_section_header(message,
                           table_of_contents,
                           'minimum-needs',
                           tr('Minimum Needs'),
                           heading_level=2)
    _create_section_header(message,
                           table_of_contents,
                           'minimum-needs-tool',
                           tr('The minimum needs tool'),
                           heading_level=3)
    message.add(needs_help())
    _create_section_header(message,
                           table_of_contents,
                           'minimum-manager',
                           tr('The minimum needs manager'),
                           heading_level=3)
    message.add(needs_manager_help())

    ##
    #  Analysis workflow
    ##

    _create_section_header(message,
                           table_of_contents,
                           'analysis-steps',
                           tr('Analysis steps'),
                           heading_level=1)
    _create_section_header(message,
                           table_of_contents,
                           'analysis-internal-process',
                           tr('Analysis internal process'),
                           heading_level=2)
    analysis = definitions.concepts['analysis']
    message.add(analysis['description'])
    url = _definition_screenshot_url(analysis)
    if url:
        message.add(m.Paragraph(m.Image(url), style_class='text-center'))

    _create_section_header(message,
                           table_of_contents,
                           'analysis-progress-reporting',
                           tr('Progress reporting steps'),
                           heading_level=2)
    steps = definitions.analysis_steps.values()
    for step in steps:
        definition_to_message(step,
                              message,
                              table_of_contents,
                              heading_level=3)

    ##
    #  Hazard definitions
    ##

    _create_section_header(message,
                           table_of_contents,
                           'hazards',
                           tr('Hazard Concepts'),
                           heading_level=1)

    hazard_category = definitions.hazard_category
    definition_to_message(hazard_category,
                          message,
                          table_of_contents,
                          heading_level=2)

    hazards = definitions.hazards
    definition_to_message(hazards, message, table_of_contents, heading_level=2)

    ##
    #  Exposure definitions
    ##

    _create_section_header(message,
                           table_of_contents,
                           'exposures',
                           tr('Exposure Concepts'),
                           heading_level=1)
    exposures = definitions.exposures

    definition_to_message(exposures,
                          message,
                          table_of_contents,
                          heading_level=2)

    ##
    #  Defaults
    ##

    _create_section_header(message,
                           table_of_contents,
                           'defaults',
                           tr('InaSAFE Defaults'),
                           heading_level=1)
    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('Default value'), header=True))
    row.add(m.Cell(tr('Default min'), header=True))
    row.add(m.Cell(tr('Default max'), header=True))
    row.add(m.Cell(tr('Description'), header=True))
    table.add(row)
    defaults = [
        definitions.youth_ratio_default_value,
        definitions.adult_ratio_default_value,
        definitions.elderly_ratio_default_value,
        definitions.female_ratio_default_value,
        definitions.feature_rate_default_value
    ]
    for default in defaults:
        row = m.Row()
        row.add(m.Cell(default['name']))
        row.add(m.Cell(default['default_value']))
        row.add(m.Cell(default['min_value']))
        row.add(m.Cell(default['max_value']))
        row.add(m.Cell(default['description']))
        table.add(row)
    message.add(table)

    ##
    #  All Fields
    ##

    _create_section_header(message,
                           table_of_contents,
                           'all-fields',
                           tr('Fields'),
                           heading_level=1)
    _create_section_header(message,
                           table_of_contents,
                           'input-fields',
                           tr('Input dataset fields'),
                           heading_level=2)
    _create_fields_section(message, table_of_contents, tr('Exposure fields'),
                           definitions.exposure_fields)
    _create_fields_section(message, table_of_contents, tr('Hazard fields'),
                           definitions.hazard_fields)
    _create_fields_section(message, table_of_contents,
                           tr('Aggregation fields'),
                           definitions.aggregation_fields)
    _create_section_header(message,
                           table_of_contents,
                           'output-fields',
                           tr('Output dataset fields'),
                           heading_level=2)
    _create_fields_section(message, table_of_contents, tr('Impact fields'),
                           definitions.impact_fields)
    _create_fields_section(message, table_of_contents,
                           tr('Aggregate hazard fields'),
                           definitions.aggregate_hazard_fields)
    _create_fields_section(message, table_of_contents,
                           tr('Aggregation summary fields'),
                           definitions.aggregation_summary_fields)
    _create_fields_section(message, table_of_contents,
                           tr('Exposure summary table fields'),
                           definitions.exposure_summary_table_fields)
    _create_fields_section(message, table_of_contents, tr('Analysis fields'),
                           definitions.analysis_fields)

    ##
    #  Geometries
    ##

    _create_section_header(message,
                           table_of_contents,
                           'geometries',
                           tr('Layer Geometry Types'),
                           heading_level=1)
    _create_section_header(message,
                           table_of_contents,
                           'vector-geometries',
                           tr('Vector'),
                           heading_level=2)
    definition_to_message(definitions.layer_geometry_point,
                          message,
                          table_of_contents,
                          heading_level=3)
    definition_to_message(definitions.layer_geometry_line,
                          message,
                          table_of_contents,
                          heading_level=3)
    definition_to_message(definitions.layer_geometry_polygon,
                          message,
                          table_of_contents,
                          heading_level=3)
    _create_section_header(message,
                           table_of_contents,
                           'raster-geometries',
                           tr('Raster'),
                           heading_level=2)
    definition_to_message(definitions.layer_geometry_raster,
                          message,
                          table_of_contents,
                          heading_level=3)

    ##
    #  Layer Modes
    ##

    _create_section_header(message,
                           table_of_contents,
                           'layer-modes',
                           tr('Layer Modes'),
                           heading_level=1)
    definition_to_message(definitions.layer_mode,
                          message,
                          table_of_contents,
                          heading_level=2)

    ##
    #  Layer Purposes
    ##

    _create_section_header(message,
                           table_of_contents,
                           'layer-purposes',
                           tr('Layer Purposes'),
                           heading_level=1)
    definition_to_message(definitions.layer_purpose_hazard,
                          message,
                          table_of_contents,
                          heading_level=2)
    definition_to_message(definitions.layer_purpose_exposure,
                          message,
                          table_of_contents,
                          heading_level=2)
    definition_to_message(definitions.layer_purpose_aggregation,
                          message,
                          table_of_contents,
                          heading_level=2)
    definition_to_message(definitions.layer_purpose_exposure_summary,
                          message,
                          table_of_contents,
                          heading_level=2)
    definition_to_message(definitions.layer_purpose_aggregate_hazard_impacted,
                          message,
                          table_of_contents,
                          heading_level=2)
    definition_to_message(definitions.layer_purpose_aggregation_summary,
                          message,
                          table_of_contents,
                          heading_level=2)
    definition_to_message(definitions.layer_purpose_exposure_summary_table,
                          message,
                          table_of_contents,
                          heading_level=2)
    definition_to_message(definitions.layer_purpose_profiling,
                          message,
                          table_of_contents,
                          heading_level=2)

    ##
    # All units
    ##

    _create_section_header(message,
                           table_of_contents,
                           'all-units',
                           tr('All Units'),
                           heading_level=1)
    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 definitions.units_all:
        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)

    ##
    #  Post processors
    ##

    _create_section_header(message,
                           table_of_contents,
                           'post-processors',
                           tr('Post Processors'),
                           heading_level=1)
    _create_section_header(message,
                           table_of_contents,
                           'post-processor-input-types',
                           tr('Post Processor Input Types'),
                           heading_level=2)
    table = _create_post_processor_subtable(post_processor_input_types)
    message.add(table)

    _create_section_header(message,
                           table_of_contents,
                           'post-processor-input-values',
                           tr('Post Processor Input Values'),
                           heading_level=2)
    table = _create_post_processor_subtable(post_processor_input_values)
    message.add(table)

    _create_section_header(message,
                           table_of_contents,
                           'post-processor-process-values',
                           tr('Post Processor Process Types'),
                           heading_level=2)
    table = _create_post_processor_subtable(
        definitions.post_processor_process_types)
    message.add(table)

    _create_section_header(message,
                           table_of_contents,
                           'post-processors',
                           tr('Post Processors'),
                           heading_level=2)
    post_processors = safe.definitions.post_processors
    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('Input Fields'), header=True))
    row.add(m.Cell(tr('Output Fields'), header=True))
    table.add(row)
    for post_processor in post_processors:
        row = m.Row()
        row.add(m.Cell(post_processor['name']))
        # Input fields
        bullets = m.BulletedList()
        for key, value in post_processor['input'].iteritems():
            bullets.add(key)
        row.add(m.Cell(bullets))
        # Output fields
        bullets = m.BulletedList()
        for key, value in post_processor['output'].iteritems():
            name = value['value']['name']
            formula_type = value['type']['key']
            if formula_type == 'formula':
                formula = value['formula']
            else:
                # We use python introspection because the processor
                # uses a python function for calculations
                formula = value['function'].__name__
                formula += ' ('
                formula += value['function'].__doc__
                formula += ')'
            bullets.add('%s  %s. : %s' % (name, formula_type, formula))

        row.add(m.Cell(bullets))
        table.add(row)
        # Add the descriptions
        row = m.Row()
        row.add(m.Cell(''))
        row.add(m.Cell(post_processor['description'], span=2))
        table.add(row)
    message.add(table)

    ##
    # Developer documentation
    ##

    _create_section_header(message,
                           table_of_contents,
                           'developer-guide',
                           tr('Developer Guide'),
                           heading_level=1)
    message.add(developer_help())

    # Finally we add the table of contents at the top
    full_message = m.Message()
    # Contents is not a link so reset style
    style = SECTION_STYLE
    style['element_id'] = ''
    header = m.Heading(tr('Contents'), **style)
    full_message.add(header)
    full_message.add(table_of_contents)
    full_message.add(message)
    return full_message
Exemple #29
0
    def buildings_breakdown(self):
        """Breakdown by building type.

        :returns: The buildings breakdown report.
        :rtype: safe.messaging.Message
        """
        message = m.Message(style_class='container')
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None
        impact_names = self.affected_buildings.keys()  # e.g. flooded, wet, dry

        row = m.Row()
        row.add(m.Cell('Building type', header=True))
        for name in impact_names:
            row.add(m.Cell(tr(name), header=True, align='right'))
        # Only show not affected building row if the IF does not use custom
        # affected categories
        if self._affected_categories == self.affected_buildings.keys():
            row.add(m.Cell(tr('Not Affected'), header=True, align='right'))
        row.add(m.Cell(tr('Total'), header=True, align='right'))
        table.add(row)

        # Let's sort alphabetically first
        building_types = [building_type for building_type in self.buildings]
        building_types.sort()
        impact_totals = []  # Used to store the total for each impact name
        # Initialise totals with zeros
        for _ in impact_names:
            impact_totals.append(0)
        # Only show not affected building row if the IF does not use custom
        # affected categories
        if self._affected_categories == self.affected_buildings.keys():
            # And one extra total for the unaffected column
            impact_totals.append(0)
        # And one extra total for the cumulative total column
        impact_totals.append(0)
        # Now build the main table
        for building_type in building_types:
            row = m.Row()
            building_type_name = building_type.replace('_', ' ')
            impact_subtotals = []
            for name in impact_names:
                if building_type in self.affected_buildings[name]:
                    impact_subtotals.append(self.affected_buildings[name]
                                            [building_type].values()[0])
                else:
                    impact_subtotals.append(0)
            row.add(m.Cell(building_type_name.capitalize(), header=True))
            # Only show not affected building row if the IF does not use custom
            # affected categories
            if self._affected_categories == self.affected_buildings.keys():
                # Add not affected subtotals
                impact_subtotals.append(self.buildings[building_type] -
                                        sum(impact_subtotals))
            # list out the subtotals for this category per impact type
            for value in impact_subtotals:
                row.add(m.Cell(format_int(value), align='right'))

            # totals column
            line_total = format_int(self.buildings[building_type])
            impact_subtotals.append(self.buildings[building_type])
            row.add(m.Cell(line_total, header=True, align='right'))
            table.add(row)
            # add the subtotal to the cumulative total
            # see http://stackoverflow.com/questions/18713321/element
            #     -wise-addition-of-2-lists-in-python
            # pylint: disable=bad-builtin
            impact_totals = map(add, impact_totals, impact_subtotals)

        # list out the TOTALS for this category per impact type
        row = m.Row()
        row.add(m.Cell(tr('Total'), header=True))
        for value in impact_totals:
            row.add(m.Cell(format_int(value), align='right', header=True))
        table.add(row)

        message.add(table)

        return message
Exemple #30
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