Skip to content
Snippets Groups Projects

Badge generation intial version

All threads resolved!

Files

package nl.tudelft.ewi.auta.core.benchmarking;
import com.google.gson.Gson;
import freemarker.template.Configuration;
import freemarker.template.TemplateException;
import nl.tudelft.ewi.auta.common.model.entity.ProjectEntity;
import nl.tudelft.ewi.auta.core.database.BenchmarkingDataRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* The service used to generate badges for a project entity using a benchmarking data container,
* which contains the tables that will be used to map {@literal metrics -> risks -> final score}.
*
* The badges that are generated are formatted SVGs, which look like: AuTA score : 1.0/5.0
*/
@Service
public class BadgeGenerator {
private static final Logger logger = LoggerFactory.getLogger(BadgeGenerator.class);
/**
* The maximum rank. This means a score will be generated that will be (1.0 to 5.0)/5.0.
*/
private static final int MAX_RANK = 5;
/**
* The freemarker configuration object. Used to fetch templates.
*/
private final Configuration config;
/**
* The benchmarker, which is used to aggregate metric scores in the project entity into bins
* of equal value.
*/
private final Benchmarker benchmarker;
/**
* The repository that is used to look up benchmarking data containers.
*/
private final BenchmarkingDataRepository dataRepository;
/**
* The gson object, used to decode the default table (which is saved as a json).
*/
private final Gson gson;
/**
* Creates a new badge generator.
*
* @param configurer the freemarker configurer
* @param benchmarker the benchmarker service, used to aggregate metric scores
* @param dataRepository the data repository, where benchmarking data containers are stored
* @param gson the json decoder
*/
public BadgeGenerator(final FreeMarkerConfigurer configurer, final Benchmarker benchmarker,
final BenchmarkingDataRepository dataRepository,
final Gson gson) {
this.config = configurer.getConfiguration();
this.benchmarker = benchmarker;
this.dataRepository = dataRepository;
this.gson = gson;
}
/**
* Generates a badge for an entity.
*
* First, the corresponding benchmarking data container is looked up in the repository.
* If it does not exist, default to an existing data container. The data container contains a
* list of metrics, with corresponding risk assignment tables (which assign metric values to
* a risk score) and system score tables (which are used per metric to map the total relative
* weight in each risk category to an overal rank (0 - 4, where 0 is best and 4 is worst).
* Finally, this is mapped to an overal system score by calculating a weighted average rank
* (and converting to a score out of 5.0, where 5.0 is best.
*
* @param projectEntity the project entity, which contains the results of analysis
* @param aid the assignment id the project entity belongs to
* @return a rendered badge as an SVG, which contains a system score out of 5.0
*
* @throws IOException if the freemarker template could not be loaded
* @throws TemplateException if an error occurs while rendering the freemarker template
*/
public String generateBadge(final ProjectEntity projectEntity, final String aid)
throws IOException, TemplateException {
final var dataContainer = this.dataRepository.findByAid(aid)
.orElse(this.getDefaultContainer());
logger.debug("Generating badge for assignment id {}", aid);
var sum = 0.0;
final var weights = dataContainer.getScoreTable().getWeights();
// For each benchmarking data object in the container, aggregate the values for the
// corresponding metric, calculate a system ranking and add this to the sum object
// (score*weight)
for (final var data : dataContainer.getDataSet()) {
final var metricName = data.getMetricName();
final var aggregatedMetric = this.benchmarker
.aggregate(projectEntity, metricName, data.getEntityLevel());
// Converts the fraction to a percentage
final var percentages = new HashMap<Integer, Double>();
aggregatedMetric.forEach((key, value) -> percentages.put(key, value * 100.0));
final var rank = data.getSystemRanking().getRank(data.getRiskCategories(),
percentages);
logger.trace("Calculated rank for {}:{}", metricName.name(), rank);
sum += (MAX_RANK - rank) * weights.getOrDefault(metricName, 1.0);
}
// Calculate average score, as sum / sum of weights
final var averageRank = sum / weights.values().stream().mapToDouble(d -> d).sum();
try (var stringWriter = new StringWriter()) {
final var template = this.config.getTemplate("badge.ftlh");
final var model = Map.of("score", averageRank);
template.process(model, stringWriter);
return stringWriter.toString();
}
}
/**
* Gets the default container, which is stored in the resource folder.
*
* The default container contains presets for metrics which can be used to generate a badge.
*
* @return the default benchmarking data container.
*
* @throws IOException if somethign goes wrong while reading the file.
*/
private BenchmarkingDataContainer getDefaultContainer() throws IOException {
try (var reader = new InputStreamReader(BadgeGenerator.class.getResourceAsStream(
"defaultTables.json"
), StandardCharsets.UTF_8)) {
return this.gson.fromJson(reader, BenchmarkingDataContainer.class);
}
}
}
Loading