Source code for memote.suite.reporting.report
# -*- coding: utf-8 -*-
# Copyright 2017 Novo Nordisk Foundation Center for Biosustainability,
# Technical University of Denmark.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Provide an abstract report base class that sets the interface."""
from __future__ import absolute_import
import logging
try:
from importlib.resources import files
except ImportError:
from importlib_resources import files
from string import Template
from pandas import DataFrame
from six import iteritems, itervalues
import memote.suite.templates as templates
from memote.utils import jsonify
[docs]LOGGER = logging.getLogger(__name__)
[docs]class Report(object):
"""
Determine the abstract report interface.
Attributes
----------
result : memote.MemoteResult
The dictionary structure of results.
configuration : memote.MemoteConfiguration
A memote configuration structure.
"""
def __init__(self, result, configuration, **kwargs):
"""
Fuse a collective result with a report configuration.
Parameters
----------
result : memote.MemoteResult
The dictionary structure of results.
configuration : memote.MemoteConfiguration
A memote configuration structure.
"""
super(Report, self).__init__(**kwargs)
self.result = result
self.config = configuration
self._report_type = None
self._template = Template(
files(templates).joinpath("index.html").read_text(encoding="utf-8")
)
[docs] def render_json(self, pretty=False):
"""
Render the report results as JSON.
Parameters
----------
pretty : bool, optional
Whether to format the resulting JSON in a more legible way (
default False).
"""
return jsonify(self.result, pretty=pretty)
[docs] def render_html(self):
"""Render an HTML report."""
return self._template.safe_substitute(
report_type=self._report_type, results=self.render_json()
)
[docs] def determine_miscellaneous_tests(self):
"""
Identify tests not explicitly configured in test organization.
List them as an additional card called `Misc`, which is where they will
now appear in the report.
"""
tests_on_cards = self.get_configured_tests()
self.config["cards"].setdefault("misc", dict())
self.config["cards"]["misc"]["title"] = "Misc. Tests"
self.config["cards"]["misc"]["cases"] = list(
set(self.result.cases) - set(tests_on_cards)
)
[docs] def compute_score(self):
"""Calculate the overall test score using the configuration."""
# LOGGER.info("Begin scoring")
cases = self.get_configured_tests() | set(self.result.cases)
scores = DataFrame({"score": 0.0, "max": 1.0}, index=sorted(cases))
self.result.setdefault("score", dict())
self.result["score"]["sections"] = list()
# Calculate the scores for each test individually.
for test, result in iteritems(self.result.cases):
# LOGGER.info("Calculate score for test: '%s'.", test)
# Test metric may be a dictionary for a parametrized test.
metric = result["metric"]
if hasattr(metric, "items"):
result["score"] = test_score = dict()
total = 0.0
for key, value in iteritems(metric):
value = 1.0 - value
total += value
test_score[key] = value
# For some reason there are parametrized tests without cases.
if len(metric) == 0:
metric = 0.0
else:
metric = total / len(metric)
else:
metric = 1.0 - metric
scores.at[test, "score"] = metric
scores.loc[test, :] *= self.config["weights"].get(test, 1.0)
score = 0.0
maximum = 0.0
# Calculate the scores for each section considering the individual test
# case scores.
for section_id, card in iteritems(self.config["cards"]["scored"]["sections"]):
# LOGGER.info("Calculate score for section: '%s'.", section_id)
cases = card.get("cases", None)
if cases is None:
continue
card_score = scores.loc[cases, "score"].sum()
card_total = scores.loc[cases, "max"].sum()
# Format results nicely to work immediately with Vega Bar Chart.
section_score = {"section": section_id, "score": card_score / card_total}
self.result["score"]["sections"].append(section_score)
# Calculate the final score for the entire model.
weight = card.get("weight", 1.0)
score += card_score * weight
maximum += card_total * weight
self.result["score"]["total_score"] = score / maximum