Skip to content
This repository has been archived by the owner on Mar 30, 2024. It is now read-only.

Commit

Permalink
Initial report implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
romainmoreau committed Feb 1, 2024
1 parent 46f3969 commit 020e083
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 18 deletions.
4 changes: 4 additions & 0 deletions gas-sensor-web/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
<groupId>fr.romainmoreau.gsmmodem</groupId>
<artifactId>gsm-modem-client-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import fr.romainmoreau.gassensor.datamodel.GasSensingInterval;
import fr.romainmoreau.gassensor.datamodel.GasSensingIntervalCategory;
import fr.romainmoreau.gassensor.datamodel.GasSensingUpdate;
import fr.romainmoreau.gassensor.web.common.GasSensingIntervalService;
import fr.romainmoreau.gassensor.web.common.GasSensingUpdateApplicationEvent;
import fr.romainmoreau.gassensor.web.data.GasSensingAlertRepository;
import fr.romainmoreau.gassensor.web.data.GasSensingIntervalRepository;

@Component
@Profile("alert")
public class AlertGasSensingUpdateEventListener {
@Autowired
private GasSensingIntervalRepository gasSensingIntervalRepository;
private GasSensingIntervalService gasSensingIntervalService;

@Autowired
private GasSensingAlertRepository gasSensingAlertRepository;
Expand Down Expand Up @@ -60,9 +58,9 @@ public void onGasSensingUpdateApplicationEvent(GasSensingUpdateApplicationEvent
return;
}
var lastCategory = gasSensingUpdateApplicationEvent.getLastGasSensingUpdate() != null
? getCategory(gasSensingUpdateApplicationEvent.getLastGasSensingUpdate())
? gasSensingIntervalService.getCategory(gasSensingUpdateApplicationEvent.getLastGasSensingUpdate())
: null;
var category = getCategory(gasSensingUpdate);
var category = gasSensingIntervalService.getCategory(gasSensingUpdate);
var alertOn = isAlertOn(category, gasSensingAlert.getThresholdCategory());
if (lastCategory == null || alertOn == isAlertOn(lastCategory, gasSensingAlert.getThresholdCategory())) {
return;
Expand All @@ -77,18 +75,6 @@ public void onGasSensingUpdateApplicationEvent(GasSensingUpdateApplicationEvent
applicationEventPublisher.publishEvent(alertApplicationEvent);
}

private GasSensingIntervalCategory getCategory(GasSensingUpdate gasSensingUpdate) {
var gasSensingIntervalList = gasSensingIntervalRepository
.findByDescriptionAndUnit(gasSensingUpdate.getDescription(), gasSensingUpdate.getUnit());
if (gasSensingIntervalList.isEmpty()) {
return GasSensingIntervalCategory.FINE;
}
return gasSensingIntervalList.stream().filter(
i -> (i.getMinValue() == null || gasSensingUpdate.getReadValue().compareTo(i.getMinValue()) >= 0)
&& (i.getMaxValue() == null || gasSensingUpdate.getReadValue().compareTo(i.getMaxValue()) < 0))
.map(GasSensingInterval::getCategory).findAny().orElse(GasSensingIntervalCategory.FINE);
}

private boolean isAlertOn(GasSensingIntervalCategory category, GasSensingIntervalCategory thresholdCategory) {
return category.ordinal() >= thresholdCategory.ordinal();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fr.romainmoreau.gassensor.web.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import fr.romainmoreau.gassensor.datamodel.GasSensingInterval;
import fr.romainmoreau.gassensor.datamodel.GasSensingIntervalCategory;
import fr.romainmoreau.gassensor.datamodel.GasSensingUpdate;
import fr.romainmoreau.gassensor.web.data.GasSensingIntervalRepository;

@Service
public class GasSensingIntervalService {
@Autowired
private GasSensingIntervalRepository gasSensingIntervalRepository;

public GasSensingIntervalCategory getCategory(GasSensingUpdate gasSensingUpdate) {
var gasSensingIntervalList = gasSensingIntervalRepository
.findByDescriptionAndUnit(gasSensingUpdate.getDescription(), gasSensingUpdate.getUnit());
if (gasSensingIntervalList.isEmpty()) {
return GasSensingIntervalCategory.FINE;
}
return gasSensingIntervalList.stream().filter(
i -> (i.getMinValue() == null || gasSensingUpdate.getReadValue().compareTo(i.getMinValue()) >= 0)
&& (i.getMaxValue() == null || gasSensingUpdate.getReadValue().compareTo(i.getMaxValue()) < 0))
.map(GasSensingInterval::getCategory).findAny().orElse(GasSensingIntervalCategory.FINE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import fr.romainmoreau.gassensor.datamodel.GasSensingUpdatesRange;

Expand All @@ -22,6 +23,7 @@ public class GasSensingUpdatesRangeUpdater {
private GasSensingUpdatesRangeRepository gasSensingUpdatesRangeRepository;

@Scheduled(fixedDelay = 60000)
@Transactional
public void updateGasSensingUpdatesRanges() {
LOGGER.info("Updating gas sensing updates ranges");
List<Object[]> distinctSensorNameDescriptionUnitWithRangeList = gasSensingUpdateRepository
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package fr.romainmoreau.gassensor.web.report;

public class GasSensingReport {
private String sensorName;

private String description;

private String unit;

private Double finePercent;

private Double warningPercent;

private Double severePercent;

private Long longestMillisWithoutUpdate;

public String getSensorName() {
return sensorName;
}

public void setSensorName(String sensorName) {
this.sensorName = sensorName;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getUnit() {
return unit;
}

public void setUnit(String unit) {
this.unit = unit;
}

public Double getFinePercent() {
return finePercent;
}

public void setFinePercent(Double finePercent) {
this.finePercent = finePercent;
}

public Double getWarningPercent() {
return warningPercent;
}

public void setWarningPercent(Double warningPercent) {
this.warningPercent = warningPercent;
}

public Double getSeverePercent() {
return severePercent;
}

public void setSeverePercent(Double severePercent) {
this.severePercent = severePercent;
}

public Long getLongestMillisWithoutUpdate() {
return longestMillisWithoutUpdate;
}

public void setLongestMillisWithoutUpdate(Long longestMillisWithoutUpdate) {
this.longestMillisWithoutUpdate = longestMillisWithoutUpdate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package fr.romainmoreau.gassensor.web.report;

import java.time.LocalDate;
import java.util.Collection;

import org.springframework.context.ApplicationEvent;

public class ReportApplicationEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;

private final Collection<GasSensingReport> gasSensingReports;

private final LocalDate day;

public ReportApplicationEvent(Collection<GasSensingReport> gasSensingReports, LocalDate day) {
super(gasSensingReports);
this.gasSensingReports = gasSensingReports;
this.day = day;
}

public Collection<GasSensingReport> getGasSensingReports() {
return gasSensingReports;
}

public LocalDate getDay() {
return day;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package fr.romainmoreau.gassensor.web.report;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import fr.romainmoreau.gassensor.datamodel.GasSensingIntervalCategory;
import fr.romainmoreau.gassensor.datamodel.GasSensingUpdatesRange;
import fr.romainmoreau.gassensor.web.common.GasSensingIntervalService;
import fr.romainmoreau.gassensor.web.data.GasSensingUpdateRepository;
import fr.romainmoreau.gassensor.web.data.GasSensingUpdatesRangeRepository;

@Component
@Profile("report")
public class ReportScheduler {
@Autowired
private GasSensingUpdatesRangeRepository gasSensingUpdatesRangeRepository;

@Autowired
private GasSensingUpdateRepository gasSensingUpdateRepository;

@Autowired
private GasSensingIntervalService gasSensingIntervalService;

@Autowired
private ApplicationEventPublisher applicationEventPublisher;

@Scheduled(cron = "0 0 12 * * *")
private void sendDailyReport() {
var today = LocalDate.now();
var todayStartOfDay = today.atStartOfDay();
var yesterday = today.minusDays(1);
var yesterdayStartOfDay = today.minusDays(1).atStartOfDay();
applicationEventPublisher.publishEvent(new ReportApplicationEvent(
gasSensingUpdatesRangeRepository.findAll().stream()
.map(range -> getGasSensingReport(todayStartOfDay, yesterdayStartOfDay, range)).toList(),
yesterday));
}

private GasSensingReport getGasSensingReport(LocalDateTime todayStartOfDay, LocalDateTime yesterdayStartOfDay,
GasSensingUpdatesRange gasSensingUpdatesRange) {
var yesterdayUpdates = gasSensingUpdateRepository
.findBySensorNameAndDescriptionAndUnitAndLocalDateTimeBetweenOrderByIdAsc(
gasSensingUpdatesRange.getSensorName(), gasSensingUpdatesRange.getDescription(),
gasSensingUpdatesRange.getUnit(), yesterdayStartOfDay, todayStartOfDay);
var firstBeforeYesterdayUpdate = gasSensingUpdateRepository
.findFirstBySensorNameAndDescriptionAndUnitAndLocalDateTimeLessThanOrderByIdDesc(
gasSensingUpdatesRange.getSensorName(), gasSensingUpdatesRange.getDescription(),
gasSensingUpdatesRange.getUnit(), yesterdayStartOfDay);
var gasSensingReport = new GasSensingReport();
gasSensingReport.setSensorName(gasSensingUpdatesRange.getSensorName());
gasSensingReport.setDescription(gasSensingUpdatesRange.getDescription());
gasSensingReport.setUnit(gasSensingUpdatesRange.getUnit());
var lastUpdate = firstBeforeYesterdayUpdate;
var categoryMillisMap = Stream.of(GasSensingIntervalCategory.values())
.collect(Collectors.toMap(Function.identity(), c -> 0l));
Long longestMillisWithoutUpdate = null;
for (var yesterdayUpdate : yesterdayUpdates) {
if (lastUpdate != null) {
var category = gasSensingIntervalService.getCategory(lastUpdate);
var millis = (lastUpdate == firstBeforeYesterdayUpdate ? yesterdayStartOfDay
: lastUpdate.getLocalDateTime()).until(yesterdayUpdate.getLocalDateTime(), ChronoUnit.MILLIS);
categoryMillisMap.put(category, categoryMillisMap.get(category) + millis);
longestMillisWithoutUpdate = (lastUpdate == firstBeforeYesterdayUpdate ? millis
: Math.max(longestMillisWithoutUpdate, millis));
}
lastUpdate = yesterdayUpdate;
}
if (!yesterdayUpdates.isEmpty()) {
var category = gasSensingIntervalService.getCategory(lastUpdate);
var millis = lastUpdate.getLocalDateTime().until(todayStartOfDay, ChronoUnit.MILLIS);
categoryMillisMap.put(category, categoryMillisMap.get(category) + millis);
longestMillisWithoutUpdate = Math.max(longestMillisWithoutUpdate, millis);
}
var millisSum = categoryMillisMap.values().stream().mapToLong(m -> m).sum();
if (millisSum != 0l) {
gasSensingReport
.setFinePercent((double) categoryMillisMap.get(GasSensingIntervalCategory.FINE) / millisSum);
gasSensingReport
.setWarningPercent((double) categoryMillisMap.get(GasSensingIntervalCategory.WARNING) / millisSum);
gasSensingReport
.setSeverePercent((double) categoryMillisMap.get(GasSensingIntervalCategory.SEVERE) / millisSum);
}
gasSensingReport.setLongestMillisWithoutUpdate(longestMillisWithoutUpdate);
return gasSensingReport;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package fr.romainmoreau.gassensor.web.report.email;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.commons.lang3.time.DurationFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.context.event.EventListener;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import fr.romainmoreau.gassensor.web.report.GasSensingReport;
import fr.romainmoreau.gassensor.web.report.ReportApplicationEvent;

@Component
@Profile("report-email")
public class EmailReportEventListener {
private static final NumberFormat PERCENT_DECIMAL_FORMAT = new DecimalFormat("0.## %");

@Autowired
private JavaMailSender javaMailSender;

@Autowired
private EmailReportProperties emailReportProperties;

@Async
@EventListener(ReportApplicationEvent.class)
public void onReportApplicationEvent(ReportApplicationEvent reportApplicationEvent) {
var simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setTo(emailReportProperties.getTo());
simpleMailMessage.setSubject(reportApplicationEvent.getDay() + " gas sensing report");
simpleMailMessage.setText(reportApplicationEvent.getGasSensingReports().stream().map(report -> getText(report))
.collect(Collectors.joining("\n")));
javaMailSender.send(simpleMailMessage);
}

private String getText(GasSensingReport gasSensingReport) {
var stringBuilder = new StringBuilder();
stringBuilder.append(gasSensingReport.getDescription());
stringBuilder.append(" (");
stringBuilder.append(gasSensingReport.getUnit());
stringBuilder.append(") (");
stringBuilder.append(gasSensingReport.getSensorName());
stringBuilder.append(")\n");
Optional.of(gasSensingReport.getSeverePercent())
.ifPresent(severePercent -> appendPercent("Severe percent", severePercent, stringBuilder));
Optional.of(gasSensingReport.getWarningPercent())
.ifPresent(warningPercent -> appendPercent("Warning percent", warningPercent, stringBuilder));
Optional.of(gasSensingReport.getFinePercent())
.ifPresent(finePercent -> appendPercent("Fine percent", finePercent, stringBuilder));
Optional.of(gasSensingReport.getLongestMillisWithoutUpdate())
.ifPresent(longestMillisWithoutUpdate -> {
stringBuilder.append("Longest duration without update: ");
stringBuilder
.append(DurationFormatUtils.formatDurationWords(longestMillisWithoutUpdate, true, true));
stringBuilder.append("\n");
});
return stringBuilder.toString();
}

private void appendPercent(String name, double percent, StringBuilder stringBuilder) {
stringBuilder.append(name);
stringBuilder.append(": ");
stringBuilder.append(PERCENT_DECIMAL_FORMAT.format(percent));
stringBuilder.append("\n");
}
}
Loading

0 comments on commit 020e083

Please sign in to comment.