From 020e0837307485359a708d587ac053b894312599 Mon Sep 17 00:00:00 2001 From: Romain Moreau Date: Thu, 1 Feb 2024 12:44:33 +0100 Subject: [PATCH] Initial report implementation --- gas-sensor-web/pom.xml | 4 + .../AlertGasSensingUpdateEventListener.java | 22 +---- .../web/common/GasSensingIntervalService.java | 27 ++++++ .../data/GasSensingUpdatesRangeUpdater.java | 2 + .../web/report/GasSensingReport.java | 73 ++++++++++++++ .../web/report/ReportApplicationEvent.java | 28 ++++++ .../gassensor/web/report/ReportScheduler.java | 96 +++++++++++++++++++ .../email/EmailReportEventListener.java | 72 ++++++++++++++ .../report/email/EmailReportProperties.java | 21 ++++ .../report/sms/SmsReportEventListener.java | 45 +++++++++ .../web/report/sms/SmsReportProperties.java | 21 ++++ 11 files changed, 393 insertions(+), 18 deletions(-) create mode 100644 gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/common/GasSensingIntervalService.java create mode 100644 gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/GasSensingReport.java create mode 100644 gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/ReportApplicationEvent.java create mode 100644 gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/ReportScheduler.java create mode 100644 gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/email/EmailReportEventListener.java create mode 100644 gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/email/EmailReportProperties.java create mode 100644 gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/sms/SmsReportEventListener.java create mode 100644 gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/sms/SmsReportProperties.java diff --git a/gas-sensor-web/pom.xml b/gas-sensor-web/pom.xml index 4840a54..6a1afa4 100644 --- a/gas-sensor-web/pom.xml +++ b/gas-sensor-web/pom.xml @@ -63,6 +63,10 @@ fr.romainmoreau.gsmmodem gsm-modem-client-web + + org.apache.commons + commons-lang3 + diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/alert/AlertGasSensingUpdateEventListener.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/alert/AlertGasSensingUpdateEventListener.java index a518806..017ad7f 100644 --- a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/alert/AlertGasSensingUpdateEventListener.java +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/alert/AlertGasSensingUpdateEventListener.java @@ -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; @@ -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; @@ -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(); } diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/common/GasSensingIntervalService.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/common/GasSensingIntervalService.java new file mode 100644 index 0000000..9a1fad2 --- /dev/null +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/common/GasSensingIntervalService.java @@ -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); + } +} diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/data/GasSensingUpdatesRangeUpdater.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/data/GasSensingUpdatesRangeUpdater.java index 4a136ba..c9a17a2 100644 --- a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/data/GasSensingUpdatesRangeUpdater.java +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/data/GasSensingUpdatesRangeUpdater.java @@ -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; @@ -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 distinctSensorNameDescriptionUnitWithRangeList = gasSensingUpdateRepository diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/GasSensingReport.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/GasSensingReport.java new file mode 100644 index 0000000..c68b1ab --- /dev/null +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/GasSensingReport.java @@ -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; + } +} diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/ReportApplicationEvent.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/ReportApplicationEvent.java new file mode 100644 index 0000000..82ccfc3 --- /dev/null +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/ReportApplicationEvent.java @@ -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 gasSensingReports; + + private final LocalDate day; + + public ReportApplicationEvent(Collection gasSensingReports, LocalDate day) { + super(gasSensingReports); + this.gasSensingReports = gasSensingReports; + this.day = day; + } + + public Collection getGasSensingReports() { + return gasSensingReports; + } + + public LocalDate getDay() { + return day; + } +} diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/ReportScheduler.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/ReportScheduler.java new file mode 100644 index 0000000..0732e60 --- /dev/null +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/ReportScheduler.java @@ -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; + } +} diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/email/EmailReportEventListener.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/email/EmailReportEventListener.java new file mode 100644 index 0000000..a9954ea --- /dev/null +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/email/EmailReportEventListener.java @@ -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"); + } +} diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/email/EmailReportProperties.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/email/EmailReportProperties.java new file mode 100644 index 0000000..54ff358 --- /dev/null +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/email/EmailReportProperties.java @@ -0,0 +1,21 @@ +package fr.romainmoreau.gassensor.web.report.email; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Profile; + +import jakarta.validation.constraints.NotNull; + +@Profile("report-email") +@ConfigurationProperties("report.email") +public class EmailReportProperties { + @NotNull + private String to; + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } +} diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/sms/SmsReportEventListener.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/sms/SmsReportEventListener.java new file mode 100644 index 0000000..384b183 --- /dev/null +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/sms/SmsReportEventListener.java @@ -0,0 +1,45 @@ +package fr.romainmoreau.gassensor.web.report.sms; + +import java.util.Objects; + +import org.apache.commons.lang3.time.DurationFormatUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.context.event.EventListener; +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; +import fr.romainmoreau.gsmmodem.client.api.GsmModemClient; + +@Component +@Profile("report-sms") +public class SmsReportEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SmsReportEventListener.class); + + @Autowired + private GsmModemClient gsmModemClient; + + @Autowired + private SmsReportProperties smsReportProperties; + + @Async + @EventListener(ReportApplicationEvent.class) + public void onReportApplicationEvent(ReportApplicationEvent reportApplicationEvent) { + try { + gsmModemClient + .sendSms(smsReportProperties.getGsmNumber(), + reportApplicationEvent.getGasSensingReports().stream() + .map(GasSensingReport::getLongestMillisWithoutUpdate).filter(Objects::nonNull) + .max(Long::compareTo) + .map(m -> "Worst sensor duration without update: " + + DurationFormatUtils.formatDurationWords(m, true, true)) + .orElse("No data found")); + } catch (Exception e) { + LOGGER.error("Exception while sending SMS report", e); + } + } +} diff --git a/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/sms/SmsReportProperties.java b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/sms/SmsReportProperties.java new file mode 100644 index 0000000..7f0a40a --- /dev/null +++ b/gas-sensor-web/src/main/java/fr/romainmoreau/gassensor/web/report/sms/SmsReportProperties.java @@ -0,0 +1,21 @@ +package fr.romainmoreau.gassensor.web.report.sms; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Profile; + +import jakarta.validation.constraints.NotNull; + +@Profile("report-sms") +@ConfigurationProperties("report.sms") +public class SmsReportProperties { + @NotNull + private String gsmNumber; + + public String getGsmNumber() { + return gsmNumber; + } + + public void setGsmNumber(String gsmNumber) { + this.gsmNumber = gsmNumber; + } +}