-
Notifications
You must be signed in to change notification settings - Fork 0
Backend/readings #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Backend/readings #32
Changes from all commits
3e480fc
85a5ea3
62cdfd8
1261532
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package backend.fullstack.temperature.api.dto; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| public record TemperatureReadingDeviationResponse( | ||
| Long id, | ||
| Long unitId, | ||
| String unitName, | ||
| Double temperature, | ||
| Double threshold, | ||
| LocalDateTime timestamp | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package backend.fullstack.temperature.api.dto; | ||
|
|
||
| public enum TemperatureReadingStatsGroupBy { | ||
| HOUR, | ||
| DAY, | ||
| WEEK | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package backend.fullstack.temperature.api.dto; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| public record TemperatureReadingStatsPointResponse( | ||
| LocalDateTime timestamp, | ||
| Double avgTemperature, | ||
| boolean isDeviation | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package backend.fullstack.temperature.api.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record TemperatureReadingStatsResponse( | ||
| List<TemperatureReadingStatsSeriesResponse> series, | ||
| List<TemperatureReadingDeviationResponse> deviations | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package backend.fullstack.temperature.api.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record TemperatureReadingStatsSeriesResponse( | ||
| Long unitId, | ||
| String unitName, | ||
| List<TemperatureReadingStatsPointResponse> dataPoints | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,11 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package backend.fullstack.temperature.application; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.temporal.TemporalAdjusters; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Comparator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.data.domain.Page; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.data.domain.Pageable; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -12,9 +16,14 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.exceptions.ResourceNotFoundException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.exceptions.UnitInactiveException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.exceptions.UnitNotFoundException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.api.dto.TemperatureReadingDeviationResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.api.dto.TemperatureReadingMapper; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.api.dto.TemperatureReadingRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.api.dto.TemperatureReadingResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.api.dto.TemperatureReadingStatsGroupBy; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.api.dto.TemperatureReadingStatsPointResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.api.dto.TemperatureReadingStatsResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.api.dto.TemperatureReadingStatsSeriesResponse; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.domain.TemperatureReading; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.temperature.infrastructure.TemperatureReadingRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.fullstack.units.domain.TemperatureUnit; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -69,6 +78,36 @@ public Page<TemperatureReadingResponse> listReadings( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(readingMapper::toResponse); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional(readOnly = true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public TemperatureReadingStatsResponse getReadingStats( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Long organizationId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Long> unitIds, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime from, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime to, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TemperatureReadingStatsGroupBy groupBy | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<Long> distinctUnitIds = sanitizeUnitIds(unitIds); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<TemperatureReading> readings = distinctUnitIds == null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? readingRepository.findForStatsByOrganizationAndRange(organizationId, from, to) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : readingRepository.findForStatsByOrganizationAndUnitIdsAndRange(organizationId, distinctUnitIds, from, to); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Map<Long, List<TemperatureReading>> readingsByUnit = readings.stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .collect(Collectors.groupingBy(reading -> reading.getUnit().getId())); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<TemperatureReadingStatsSeriesResponse> series = readingsByUnit.values().stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .sorted(Comparator.comparing(unitReadings -> unitReadings.get(0).getUnit().getName(), String.CASE_INSENSITIVE_ORDER)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(unitReadings -> toSeries(unitReadings, groupBy)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<TemperatureReadingDeviationResponse> deviations = readings.stream() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter(TemperatureReading::isDeviation) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .sorted(Comparator.comparing(TemperatureReading::getRecordedAt).reversed()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(this::toDeviationResponse) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new TemperatureReadingStatsResponse(series, deviations); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+81
to
+109
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional(readOnly = true) | |
| public TemperatureReadingStatsResponse getReadingStats( | |
| Long organizationId, | |
| List<Long> unitIds, | |
| LocalDateTime from, | |
| LocalDateTime to, | |
| TemperatureReadingStatsGroupBy groupBy | |
| ) { | |
| List<Long> distinctUnitIds = sanitizeUnitIds(unitIds); | |
| List<TemperatureReading> readings = distinctUnitIds == null | |
| ? readingRepository.findForStatsByOrganizationAndRange(organizationId, from, to) | |
| : readingRepository.findForStatsByOrganizationAndUnitIdsAndRange(organizationId, distinctUnitIds, from, to); | |
| Map<Long, List<TemperatureReading>> readingsByUnit = readings.stream() | |
| .collect(Collectors.groupingBy(reading -> reading.getUnit().getId())); | |
| List<TemperatureReadingStatsSeriesResponse> series = readingsByUnit.values().stream() | |
| .sorted(Comparator.comparing(unitReadings -> unitReadings.get(0).getUnit().getName(), String.CASE_INSENSITIVE_ORDER)) | |
| .map(unitReadings -> toSeries(unitReadings, groupBy)) | |
| .toList(); | |
| List<TemperatureReadingDeviationResponse> deviations = readings.stream() | |
| .filter(TemperatureReading::isDeviation) | |
| .sorted(Comparator.comparing(TemperatureReading::getRecordedAt).reversed()) | |
| .map(this::toDeviationResponse) | |
| .toList(); | |
| return new TemperatureReadingStatsResponse(series, deviations); | |
| } | |
| @Transactional(readOnly = true) | |
| public TemperatureReadingStatsResponse getReadingStats( | |
| Long organizationId, | |
| List<Long> unitIds, | |
| LocalDateTime from, | |
| LocalDateTime to, | |
| TemperatureReadingStatsGroupBy groupBy | |
| ) { | |
| List<Long> distinctUnitIds = sanitizeUnitIds(unitIds); | |
| List<TemperatureReading> readings = distinctUnitIds == null | |
| ? readingRepository.findForStatsByOrganizationAndRange(organizationId, from, to) | |
| : readingRepository.findForStatsByOrganizationAndUnitIdsAndRange(organizationId, distinctUnitIds, from, to); | |
| Map<Long, List<TemperatureReading>> readingsByUnit = readings.stream() | |
| .collect(Collectors.groupingBy(reading -> reading.getUnit().getId())); | |
| List<TemperatureReadingStatsSeriesResponse> series = readingsByUnit.values().stream() | |
| .sorted(Comparator.comparing(unitReadings -> unitReadings.get(0).getUnit().getName(), String.CASE_INSENSITIVE_ORDER)) | |
| .map(unitReadings -> toSeries(unitReadings, groupBy)) | |
| .toList(); | |
| List<TemperatureReadingDeviationResponse> deviations = readings.stream() | |
| .filter(TemperatureReading::isDeviation) | |
| .sorted(Comparator.comparing(TemperatureReading::getRecordedAt).reversed()) | |
| .map(this::toDeviationResponse) | |
| .toList(); | |
| return new TemperatureReadingStatsResponse(series, deviations); | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,36 @@ List<TemperatureReading> findByOrganizationAndUnitAndRecordedAtBetween( | |
| @Param("to") LocalDateTime to | ||
| ); | ||
|
|
||
| @Query(""" | ||
| SELECT r | ||
| FROM TemperatureReading r | ||
| WHERE r.organization.id = :organizationId | ||
| AND (:from IS NULL OR r.recordedAt >= :from) | ||
| AND (:to IS NULL OR r.recordedAt <= :to) | ||
| ORDER BY r.unit.name ASC, r.recordedAt ASC | ||
| """) | ||
| List<TemperatureReading> findForStatsByOrganizationAndRange( | ||
| @Param("organizationId") Long organizationId, | ||
| @Param("from") LocalDateTime from, | ||
| @Param("to") LocalDateTime to | ||
| ); | ||
|
Comment on lines
+58
to
+70
|
||
|
|
||
| @Query(""" | ||
| SELECT r | ||
| FROM TemperatureReading r | ||
| WHERE r.organization.id = :organizationId | ||
| AND r.unit.id IN :unitIds | ||
| AND (:from IS NULL OR r.recordedAt >= :from) | ||
| AND (:to IS NULL OR r.recordedAt <= :to) | ||
| ORDER BY r.unit.name ASC, r.recordedAt ASC | ||
| """) | ||
| List<TemperatureReading> findForStatsByOrganizationAndUnitIdsAndRange( | ||
| @Param("organizationId") Long organizationId, | ||
| @Param("unitIds") List<Long> unitIds, | ||
| @Param("from") LocalDateTime from, | ||
| @Param("to") LocalDateTime to | ||
| ); | ||
|
Comment on lines
+72
to
+86
|
||
|
|
||
| long countByOrganization_IdAndIsDeviationTrue(Long organizationId); | ||
|
|
||
| long countByOrganization_IdAndIsDeviationTrueAndRecordedAtAfter(Long organizationId, LocalDateTime since); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getReadingStatsfetches all readings in the requested range and aggregates them in-memory. For large ranges/orgs this can become slow and memory-heavy; consider moving bucketing/AVG aggregation to the DB (and fetching deviations via a separate bounded query) to keep response time predictable.