From 11c479e71a42fad7b77061e2b18a36d42b0e2e0c Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Mon, 1 Jul 2019 09:53:33 +0200 Subject: [PATCH] OO-4124: Respect slider weight in trend diagram --- .../manager/QualityAnalysisServiceImpl.java | 18 ++++- .../manager/StatisticsCalculator.java | 74 ++++++++++++++++++ .../model/RawGroupedStatisticImpl.java | 14 ++-- .../analysis/ui/GroupByController.java | 2 + .../manager/StatisticsCalculatorTest.java | 76 +++++++++++++++++++ 5 files changed, 175 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java b/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java index a97a1d9c2e1..317f8846be8 100644 --- a/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java +++ b/src/main/java/org/olat/modules/quality/analysis/manager/QualityAnalysisServiceImpl.java @@ -321,13 +321,27 @@ public class QualityAnalysisServiceImpl implements QualityAnalysisService { if (groupBy == null || temporalGroupBy == null || rubrics == null || rubrics.size() == 0) return new MultiTrendSeries<>(); List<String> identifiers = rubrics.stream().map(Rubric::getSliders).flatMap(s -> s.stream()).map(Slider::getId).collect(toList()); - List<RawGroupedStatistic> statisticsList = filterDao.loadGroupedStatistic(searchParams, - identifiers, false, groupBy, temporalGroupBy); + List<RawGroupedStatistic> statisticsList; + if (hasWeights(rubrics)) { + statisticsList = filterDao.loadGroupedStatistic(searchParams, identifiers, true, groupBy, temporalGroupBy); + statisticsList = statisticsCalculator.reduceIdentifier(statisticsList, rubrics); + } else { + statisticsList = filterDao.loadGroupedStatistic(searchParams, identifiers, false, groupBy, temporalGroupBy); + } GroupedStatistics<RawGroupedStatistic> rawStatistics = new GroupedStatistics<>(statisticsList); GroupedStatistics<GroupedStatistic> statistics = statisticsCalculator.getGroupedStatistics(rawStatistics, rubrics); return statisticsCalculator.getTrendsByMultiKey(statistics, temporalGroupBy); } + private boolean hasWeights(Set<Rubric> rubrics) { + return rubrics.stream() + .map(Rubric::getSliders) + .flatMap(s -> s.stream()) + .filter(s -> s.getWeight().intValue() != 1) + .findAny() + .isPresent(); + } + @Override public boolean isInsufficient(Rubric rubric, Double avg) { RubricRating rating = evaluationFormManager.getRubricRating(rubric, avg); diff --git a/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java b/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java index 1688a742191..e5ee93d0f0a 100644 --- a/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java +++ b/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; @@ -46,6 +47,7 @@ import org.olat.modules.quality.analysis.TemporalKey; import org.olat.modules.quality.analysis.Trend; import org.olat.modules.quality.analysis.Trend.DIRECTION; import org.olat.modules.quality.analysis.model.GroupedStatisticImpl; +import org.olat.modules.quality.analysis.model.RawGroupedStatisticImpl; import org.olat.modules.quality.analysis.model.TrendImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -111,6 +113,58 @@ public class StatisticsCalculator { log.debug("Grouped statistic: " + statistic.toString()); return statistic; } + + List<RawGroupedStatistic> reduceIdentifier(List<RawGroupedStatistic> statisticsList, Set<Rubric> rubrics) { + Map<String, Integer> sliderToWeight = rubrics.stream() + .map(Rubric::getSliders) + .flatMap(s -> s.stream()) + .collect(Collectors.toMap(Slider::getId, Slider::getWeight)); + List<MultiKey> multiKeys = statisticsList.stream() + .map(RawGroupedStatistic::getMultiKey) + .distinct() + .collect(Collectors.toList()); + List<TemporalKey> temporalKeys = statisticsList.stream() + .map(RawGroupedStatistic::getTemporalKey) + .distinct() + .collect(Collectors.toList()); + + List<RawGroupedStatistic> statistics = new ArrayList<>(); + for (MultiKey multiKey: multiKeys) { + for (TemporalKey temporalKey: temporalKeys) { + List<RawGroupedStatistic> keysStatistics = filterByKey(statisticsList, multiKey, temporalKey); + CountAvg countAvg = getCountAvg(keysStatistics, sliderToWeight); + RawGroupedStatisticImpl reducedStatistic = new RawGroupedStatisticImpl(null, multiKey, temporalKey, + countAvg.getCount(), countAvg.getAvg()); + statistics.add(reducedStatistic); + } + } + return statistics; + } + + private List<RawGroupedStatistic> filterByKey(List<RawGroupedStatistic> statisticsList, MultiKey multiKey, + TemporalKey temporalKey) { + return statisticsList.stream() + .filter(s -> multiKey.equals(s.getMultiKey()) && temporalKey.equals(s.getTemporalKey())) + .collect(Collectors.toList()); + } + + private CountAvg getCountAvg(List<RawGroupedStatistic> keysStatistics, Map<String, Integer> sliderToWeight) { + long count = 0; + int sumCount = 0; + double sumValues = 0; + for (RawGroupedStatistic statistic : keysStatistics) { + long statisticCount = statistic.getCount()!= null? statistic.getCount().longValue(): 0; + count += statisticCount; + Integer weight = sliderToWeight.get(statistic.getIdentifier()); + sumCount += statisticCount * weight; + double statisticAvg = statistic.getRawAvg()!= null? statistic.getRawAvg().doubleValue(): 0; + sumValues += statisticAvg * statisticCount * weight; + } + return count > 0 + ? new CountAvg(count, sumValues / sumCount) + : new CountAvg(null, null); + } + MultiTrendSeries<String> getTrendsByIdentifiers(GroupedStatistics<GroupedStatistic> statistics, TemporalGroupBy temporalGroupBy) { Set<TemporalKey> temporalKeys = new HashSet<>(); @@ -209,5 +263,25 @@ public class StatisticsCalculator { } return direction; } + + private static class CountAvg { + + private final Long count; + private final Double avg; + + private CountAvg(Long count, Double avg) { + this.count = count; + this.avg = avg; + } + + public Long getCount() { + return count; + } + + public Double getAvg() { + return avg; + } + + } } diff --git a/src/main/java/org/olat/modules/quality/analysis/model/RawGroupedStatisticImpl.java b/src/main/java/org/olat/modules/quality/analysis/model/RawGroupedStatisticImpl.java index 9bb08075900..7730b7617ad 100644 --- a/src/main/java/org/olat/modules/quality/analysis/model/RawGroupedStatisticImpl.java +++ b/src/main/java/org/olat/modules/quality/analysis/model/RawGroupedStatisticImpl.java @@ -31,19 +31,19 @@ import org.olat.modules.quality.analysis.TemporalKey; */ public class RawGroupedStatisticImpl implements RawGroupedStatistic { - private final String identitfier; + private final String identifier; private final MultiKey multiKey; private final TemporalKey temporalKey; private final Long count; private final Double rawAvg; - public RawGroupedStatisticImpl(String identitfier, String groupedKey1, String groupedKey2, String groupedKey3, + public RawGroupedStatisticImpl(String identifier, String groupedKey1, String groupedKey2, String groupedKey3, String temporalKey, Long count, Double rawAvg) { - this(identitfier, MultiKey.of(groupedKey1, groupedKey2, groupedKey3), TemporalKey.parse(temporalKey), count, rawAvg); + this(identifier, MultiKey.of(groupedKey1, groupedKey2, groupedKey3), TemporalKey.parse(temporalKey), count, rawAvg); } - public RawGroupedStatisticImpl(String identitfier, MultiKey multiKey, TemporalKey temporalKey, Long count, Double rawAvg) { - this.identitfier = identitfier; + public RawGroupedStatisticImpl(String identifier, MultiKey multiKey, TemporalKey temporalKey, Long count, Double rawAvg) { + this.identifier = identifier; this.multiKey = multiKey; this.temporalKey = temporalKey; this.count = count; @@ -52,7 +52,7 @@ public class RawGroupedStatisticImpl implements RawGroupedStatistic { @Override public String getIdentifier() { - return identitfier; + return identifier; } @Override @@ -79,7 +79,7 @@ public class RawGroupedStatisticImpl implements RawGroupedStatistic { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("RawGroupedStatisticImpl [identitfier="); - builder.append(identitfier); + builder.append(identifier); builder.append(", multiKey="); builder.append(multiKey); builder.append(", temporalKey="); diff --git a/src/main/java/org/olat/modules/quality/analysis/ui/GroupByController.java b/src/main/java/org/olat/modules/quality/analysis/ui/GroupByController.java index d582c07f71d..8b1553a3196 100644 --- a/src/main/java/org/olat/modules/quality/analysis/ui/GroupByController.java +++ b/src/main/java/org/olat/modules/quality/analysis/ui/GroupByController.java @@ -211,9 +211,11 @@ public abstract class GroupByController extends FormBasicController implements F } protected Set<Rubric> getTrendRubrics() { + // All rubrics if (!StringHelper.containsNonWhitespace(rubricId)) { return getSliders().stream().map(SliderWrapper::getRubric).distinct().collect(toSet()); } + // Single rubric Set<Rubric> rubrics = new HashSet<>(); rubrics.add(getRubricById(rubricId)); return rubrics; diff --git a/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java b/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java index f71707df827..afd55141c0e 100644 --- a/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java +++ b/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java @@ -20,7 +20,15 @@ package org.olat.modules.quality.analysis.manager; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; +import static org.olat.test.JunitTestHelper.random; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.assertj.core.api.SoftAssertions; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -29,6 +37,7 @@ import org.mockito.junit.MockitoJUnitRunner; import org.olat.modules.forms.EvaluationFormManager; import org.olat.modules.forms.model.xml.Rubric; import org.olat.modules.forms.model.xml.ScaleType; +import org.olat.modules.forms.model.xml.Slider; import org.olat.modules.quality.analysis.GroupedStatistic; import org.olat.modules.quality.analysis.GroupedStatistics; import org.olat.modules.quality.analysis.MultiKey; @@ -310,5 +319,72 @@ public class StatisticsCalculatorTest { Trend trend3 = series.getTrend(3); assertThat(trend3.getDirection()).isEqualTo(DIRECTION.UP); } + + @Test + public void shouldReduceIdentifier() { + // Rubric 1 + List<Slider> sliders1 = new ArrayList<>(); + Slider slider11 = new Slider(); + slider11.setId(random()); + slider11.setWeight(1); + sliders1.add(slider11); + Slider slider12 = new Slider(); + slider12.setId(random()); + slider12.setWeight(2); + sliders1.add(slider12); + Rubric rubric1 = new Rubric(); + rubric1.setId(random()); + rubric1.setSliders(sliders1); + // Rubric2 + List<Slider> sliders2 = new ArrayList<>(); + Slider slider21 = new Slider(); + slider21.setId(random()); + slider21.setWeight(3); + sliders2.add(slider21); + Rubric rubric2 = new Rubric(); + rubric2.setId(random()); + rubric2.setSliders(sliders2); + Set<Rubric> rubrics = new HashSet<>(); + rubrics.add(rubric1); + rubrics.add(rubric2); + // Raw statistics + MultiKey multiKey1 = MultiKey.of("1"); + MultiKey multiKey2 = MultiKey.of("2"); + TemporalKey temporalKey1 = TemporalKey.of(1); + TemporalKey temporalKey2 = TemporalKey.of(2); + List<RawGroupedStatistic> statisticsList = new ArrayList<>(); + statisticsList.add(new RawGroupedStatisticImpl(slider11.getId(), multiKey1, temporalKey1, 1l, 1.0)); + statisticsList.add(new RawGroupedStatisticImpl(slider12.getId(), multiKey1, temporalKey1, 1l, 1.0)); + statisticsList.add(new RawGroupedStatisticImpl(slider21.getId(), multiKey1, temporalKey1, 1l, 1.0)); + statisticsList.add(new RawGroupedStatisticImpl(slider11.getId(), multiKey1, temporalKey2, 2l, 1.0)); + statisticsList.add(new RawGroupedStatisticImpl(slider12.getId(), multiKey1, temporalKey2, 2l, 2.0)); + statisticsList.add(new RawGroupedStatisticImpl(slider21.getId(), multiKey1, temporalKey2, 2l, 3.0)); + statisticsList.add(new RawGroupedStatisticImpl(slider12.getId(), multiKey2, temporalKey1, 100l, 20.2)); + statisticsList.add(new RawGroupedStatisticImpl(slider11.getId(), multiKey2, temporalKey2, 3l, 3.0)); + statisticsList.add(new RawGroupedStatisticImpl(slider12.getId(), multiKey2, temporalKey2, 2l, 2.0)); + statisticsList.add(new RawGroupedStatisticImpl(slider21.getId(), multiKey2, temporalKey2, 1l, 1.0)); + + List<RawGroupedStatistic> reducedStatistics = sut.reduceIdentifier(statisticsList, rubrics); + + SoftAssertions softly = new SoftAssertions(); + assertReducedIdentifier(softly, reducedStatistics, multiKey1, temporalKey1, 3l, 1); + assertReducedIdentifier(softly, reducedStatistics, multiKey1, temporalKey2, 6l, 2.33); + assertReducedIdentifier(softly, reducedStatistics, multiKey2, temporalKey1, 100l, 20.2); + assertReducedIdentifier(softly, reducedStatistics, multiKey2, temporalKey2, 6l, 2); + softly.assertAll(); + } + + private void assertReducedIdentifier(SoftAssertions softly, List<RawGroupedStatistic> statistics, + MultiKey multiKey, TemporalKey temporalKey, long expectedCount, double expectedAvg) { + for (RawGroupedStatistic statistic : statistics) { + if (multiKey.equals(statistic.getMultiKey()) && temporalKey.equals(statistic.getTemporalKey())) { + softly.assertThat(statistic.getCount()).as("Count of %s, %s is wrong", multiKey, temporalKey).isEqualTo(expectedCount); + softly.assertThat(statistic.getRawAvg()).as("Average of %s, %s is wrong", multiKey, temporalKey).isEqualTo(expectedAvg, offset(0.01)); + return; + } + } + softly.fail("No statistic for %s, %s", multiKey, temporalKey); + } + } -- GitLab