mirror of https://github.com/bvn13/covid19-ru.git
added previous stats data to api responses - per each stats data provided
parent
c70993ebff
commit
3006a00ebb
|
@ -24,7 +24,7 @@ import java.time.LocalDateTime;
|
||||||
@Data
|
@Data
|
||||||
@Entity
|
@Entity
|
||||||
@Table(schema = "covid", name = "cvd_stats")
|
@Table(schema = "covid", name = "cvd_stats")
|
||||||
public class CovidStat {
|
public class CovidStat implements StatsProvider {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "cvd_stats_seq")
|
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "cvd_stats_seq")
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
Copyright [2020] [bvn13]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.bvn13.covid19.model.entities;
|
||||||
|
|
||||||
|
public interface StatsProvider {
|
||||||
|
long getSick();
|
||||||
|
long getHealed();
|
||||||
|
long getDied();
|
||||||
|
}
|
|
@ -1,11 +1,30 @@
|
||||||
|
/*
|
||||||
|
Copyright [2020] [bvn13]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.bvn13.covid19.site.controllers;
|
package com.bvn13.covid19.site.controllers;
|
||||||
|
|
||||||
import com.bvn13.covid19.model.entities.CovidStat;
|
import com.bvn13.covid19.model.entities.CovidStat;
|
||||||
|
import com.bvn13.covid19.model.entities.CovidUpdate;
|
||||||
|
import com.bvn13.covid19.model.entities.Region;
|
||||||
|
import com.bvn13.covid19.model.entities.StatsProvider;
|
||||||
import com.bvn13.covid19.site.model.CovidAllStats;
|
import com.bvn13.covid19.site.model.CovidAllStats;
|
||||||
import com.bvn13.covid19.site.model.CovidData;
|
|
||||||
import com.bvn13.covid19.site.model.CovidDayStats;
|
import com.bvn13.covid19.site.model.CovidDayStats;
|
||||||
import com.bvn13.covid19.site.service.CovidStatsMaker;
|
import com.bvn13.covid19.site.repositories.RegionsRepository;
|
||||||
import com.bvn13.covid19.site.service.CovidStatsResponseMaker;
|
import com.bvn13.covid19.site.service.StatisticsPreparator;
|
||||||
|
import com.bvn13.covid19.site.service.StatisticsAggregator;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
@ -16,10 +35,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@ -27,8 +43,9 @@ import java.util.stream.Collectors;
|
||||||
@RequestMapping("/stats")
|
@RequestMapping("/stats")
|
||||||
public class AllStatsController {
|
public class AllStatsController {
|
||||||
|
|
||||||
private final CovidStatsMaker covidStatsMaker;
|
private final StatisticsPreparator statisticsPreparator;
|
||||||
private final CovidStatsResponseMaker covidStatsResponseMaker;
|
private final StatisticsAggregator statisticsAggregator;
|
||||||
|
private final RegionsRepository regionsRepository;
|
||||||
|
|
||||||
@Value("${app.zone-id}")
|
@Value("${app.zone-id}")
|
||||||
private String zoneIdStr;
|
private String zoneIdStr;
|
||||||
|
@ -51,45 +68,53 @@ public class AllStatsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private CovidAllStats constructResponseForRegion(String regionName) {
|
private CovidAllStats constructResponseForRegion(String regionName) {
|
||||||
return CovidAllStats.builder()
|
return regionsRepository.findFirstByName(regionName)
|
||||||
.regions(Collections.singletonList(regionName))
|
.map(region -> CovidAllStats.builder()
|
||||||
.progress(covidStatsMaker.findAllLastUpdatesPerDay().stream()
|
.regions(Collections.singletonList(region.getName()))
|
||||||
.map(covidUpdate -> CovidDayStats.builder()
|
.progress(compoundProgressForRegions(Collections.singletonList(region)))
|
||||||
.datetime(covidUpdate.getDatetime())
|
.build()
|
||||||
.updatedOn(covidUpdate.getCreatedOn().atZone(zoneId))
|
)
|
||||||
.stats(convertStatsToData(findCovidStatsByUpdateIdAndRegion(covidUpdate.getId(), regionName)))
|
.orElse(CovidAllStats.builder()
|
||||||
.build())
|
.regions(Collections.singletonList(regionName))
|
||||||
.sorted(CovidDayStats::compareTo)
|
.progress(Collections.emptyList())
|
||||||
.collect(Collectors.toList()))
|
.build()
|
||||||
.build();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CovidAllStats constructResponseForAllRegions() {
|
private CovidAllStats constructResponseForAllRegions() {
|
||||||
return CovidAllStats.builder()
|
return CovidAllStats.builder()
|
||||||
.regions(covidStatsMaker.findAllRegionsNames())
|
.regions(statisticsPreparator.findAllRegionsNames())
|
||||||
.progress(covidStatsMaker.findAllLastUpdatesPerDay().stream()
|
.progress(compoundProgressForRegions(regionsRepository.findAll()))
|
||||||
.map(covidUpdate -> CovidDayStats.builder()
|
|
||||||
.datetime(covidUpdate.getDatetime())
|
|
||||||
.updatedOn(covidUpdate.getCreatedOn().atZone(zoneId))
|
|
||||||
.stats(convertStatsToData(findCovidStatsByUpdateId(covidUpdate.getId())))
|
|
||||||
.build())
|
|
||||||
.sorted(CovidDayStats::compareTo)
|
|
||||||
.collect(Collectors.toList()))
|
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CovidData> convertStatsToData(Collection<CovidStat> stats) {
|
private List<CovidDayStats> compoundProgressForRegions(List<Region> regions) {
|
||||||
return covidStatsResponseMaker.convertStats(stats);
|
return statisticsPreparator.findAllLastUpdatesPerDay().stream()
|
||||||
}
|
.map(covidUpdate -> prepareDayStats(covidUpdate, regions))
|
||||||
|
.sorted(CovidDayStats::compareTo)
|
||||||
private List<CovidStat> findCovidStatsByUpdateIdAndRegion(long updateId, String region) {
|
|
||||||
return covidStatsMaker.findCovidStatsByUpdateInfoId(updateId).stream()
|
|
||||||
.filter(covidStat -> region.equals(covidStat.getRegion().getName()))
|
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CovidStat> findCovidStatsByUpdateId(long updateId) {
|
private CovidDayStats prepareDayStats(CovidUpdate currentUpdate, List<Region> regions) {
|
||||||
return new ArrayList<>(covidStatsMaker.findCovidStatsByUpdateInfoId(updateId));
|
List<CovidStat> currentStats = findCovidStatsByUpdateIdAndRegion(currentUpdate.getId(), regions);
|
||||||
|
Optional<CovidUpdate> prevUpdate = statisticsPreparator.findPrevUpdateByDate(currentUpdate.getDatetime());
|
||||||
|
Map<Region, StatsProvider> prevStats = prevUpdate
|
||||||
|
.map(covidUpdate -> statisticsPreparator.findCovidStatsByUpdateInfoId(covidUpdate.getId()).stream()
|
||||||
|
.collect(Collectors.toMap(CovidStat::getRegion, (cs) -> (StatsProvider) cs))
|
||||||
|
)
|
||||||
|
.orElseGet(() -> new HashMap<>(0));
|
||||||
|
|
||||||
|
return CovidDayStats.builder()
|
||||||
|
.datetime(currentUpdate.getDatetime())
|
||||||
|
.updatedOn(currentUpdate.getCreatedOn().atZone(zoneId))
|
||||||
|
.stats(statisticsAggregator.prepareStats(currentStats, prevStats))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CovidStat> findCovidStatsByUpdateIdAndRegion(long updateId, List<Region> regions) {
|
||||||
|
return statisticsPreparator.findCovidStatsByUpdateInfoId(updateId).stream()
|
||||||
|
.filter(covidStat -> regions.contains(covidStat.getRegion()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,13 @@ limitations under the License.
|
||||||
|
|
||||||
package com.bvn13.covid19.site.controllers;
|
package com.bvn13.covid19.site.controllers;
|
||||||
|
|
||||||
|
import com.bvn13.covid19.model.entities.CovidStat;
|
||||||
|
import com.bvn13.covid19.model.entities.CovidUpdate;
|
||||||
|
import com.bvn13.covid19.model.entities.Region;
|
||||||
|
import com.bvn13.covid19.model.entities.StatsProvider;
|
||||||
import com.bvn13.covid19.site.model.CovidDayStats;
|
import com.bvn13.covid19.site.model.CovidDayStats;
|
||||||
import com.bvn13.covid19.site.service.CovidStatsMaker;
|
import com.bvn13.covid19.site.service.StatisticsPreparator;
|
||||||
import com.bvn13.covid19.site.service.CovidStatsResponseMaker;
|
import com.bvn13.covid19.site.service.StatisticsAggregator;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
@ -27,14 +31,19 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/stats")
|
@RequestMapping("/stats")
|
||||||
public class LastStatsController {
|
public class LastStatsController {
|
||||||
|
|
||||||
private final CovidStatsMaker covidStatsMaker;
|
private final StatisticsPreparator statisticsPreparator;
|
||||||
private final CovidStatsResponseMaker covidStatsResponseMaker;
|
private final StatisticsAggregator statisticsAggregator;
|
||||||
|
|
||||||
@Value("${app.zone-id}")
|
@Value("${app.zone-id}")
|
||||||
private String zoneIdStr;
|
private String zoneIdStr;
|
||||||
|
@ -47,13 +56,24 @@ public class LastStatsController {
|
||||||
|
|
||||||
@GetMapping("/last")
|
@GetMapping("/last")
|
||||||
public CovidDayStats getStatistics() {
|
public CovidDayStats getStatistics() {
|
||||||
return covidStatsMaker.findLastUpdate()
|
Optional<CovidUpdate> lastUpdate = statisticsPreparator.findLastUpdate();
|
||||||
.map(covidUpdate -> CovidDayStats.builder()
|
if (lastUpdate.isPresent()) {
|
||||||
.datetime(covidUpdate.getDatetime())
|
Collection<CovidStat> currentStats = statisticsPreparator.findCovidStatsByUpdateInfoId(lastUpdate.get().getId());
|
||||||
.updatedOn(covidUpdate.getCreatedOn().atZone(zoneId))
|
Optional<CovidUpdate> prevUpdate = statisticsPreparator.findPrevUpdateByDate(lastUpdate.get().getDatetime());
|
||||||
.stats(covidStatsResponseMaker.convertStats(covidStatsMaker.findCovidStatsByUpdateInfoId(covidUpdate.getId())))
|
Map<Region, StatsProvider> prevStats = prevUpdate
|
||||||
.build())
|
.map(covidUpdate -> statisticsPreparator.findCovidStatsByUpdateInfoId(covidUpdate.getId()).stream()
|
||||||
.orElse(CovidDayStats.builder().build());
|
.collect(Collectors.toMap(CovidStat::getRegion, (cs) -> (StatsProvider) cs))
|
||||||
|
)
|
||||||
|
.orElseGet(() -> new HashMap<>(0));
|
||||||
|
|
||||||
|
return CovidDayStats.builder()
|
||||||
|
.datetime(lastUpdate.get().getDatetime())
|
||||||
|
.updatedOn(lastUpdate.get().getCreatedOn().atZone(zoneId))
|
||||||
|
.stats(statisticsAggregator.prepareStats(currentStats, prevStats))
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
return CovidDayStats.builder().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright [2020] [bvn13]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package com.bvn13.covid19.site.controllers;
|
package com.bvn13.covid19.site.controllers;
|
||||||
|
|
||||||
import com.bvn13.covid19.site.service.CovidStatsMaker;
|
import com.bvn13.covid19.site.service.StatisticsPreparator;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
@ -13,11 +29,11 @@ import java.util.List;
|
||||||
@RequestMapping("/regions")
|
@RequestMapping("/regions")
|
||||||
public class RegionsController {
|
public class RegionsController {
|
||||||
|
|
||||||
private final CovidStatsMaker covidStatsMaker;
|
private final StatisticsPreparator statisticsPreparator;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public List<String> getAllRegions() {
|
public List<String> getAllRegions() {
|
||||||
return covidStatsMaker.findAllRegionsNames();
|
return statisticsPreparator.findAllRegionsNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,27 @@ limitations under the License.
|
||||||
|
|
||||||
package com.bvn13.covid19.site.model;
|
package com.bvn13.covid19.site.model;
|
||||||
|
|
||||||
|
import com.bvn13.covid19.model.entities.StatsProvider;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
@Builder
|
@Builder(toBuilder = true)
|
||||||
@Value
|
@Value
|
||||||
public class CovidData {
|
public class CovidData implements StatsProvider {
|
||||||
|
|
||||||
String region;
|
String region;
|
||||||
long sick;
|
long sick;
|
||||||
long healed;
|
long healed;
|
||||||
long died;
|
long died;
|
||||||
|
|
||||||
|
Delta previous;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Delta implements StatsProvider {
|
||||||
|
long sick;
|
||||||
|
long healed;
|
||||||
|
long died;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,12 @@ import com.bvn13.covid19.site.dtos.CovidUpdateInfoDto;
|
||||||
import com.bvn13.covid19.model.entities.CovidUpdate;
|
import com.bvn13.covid19.model.entities.CovidUpdate;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -32,6 +35,9 @@ public interface CovidUpdatesRepository extends JpaRepository<CovidUpdate, Long>
|
||||||
@Query("select new com.bvn13.covid19.site.dtos.CovidUpdateInfoDto(max(U.createdOn)) from CovidUpdate U")
|
@Query("select new com.bvn13.covid19.site.dtos.CovidUpdateInfoDto(max(U.createdOn)) from CovidUpdate U")
|
||||||
Optional<CovidUpdateInfoDto> findLastUpdate();
|
Optional<CovidUpdateInfoDto> findLastUpdate();
|
||||||
|
|
||||||
|
@Query("select U from CovidUpdate U where U.datetime >= :date1 and U.datetime < :date2")
|
||||||
|
Optional<CovidUpdate> findByDateOfUpdate(@Param("date1") ZonedDateTime date1, @Param("date2") ZonedDateTime date2);
|
||||||
|
|
||||||
@Query("select U from CovidUpdate U where U.id in (select max(U1.id) from CovidUpdate U1 group by U1.datetime)")
|
@Query("select U from CovidUpdate U where U.id in (select max(U1.id) from CovidUpdate U1 group by U1.datetime)")
|
||||||
Collection<CovidUpdate> findAllLastUpdatesPerDay();
|
Collection<CovidUpdate> findAllLastUpdatesPerDay();
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@ import com.bvn13.covid19.model.entities.Region;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface RegionsRepository extends JpaRepository<Region, Long> {
|
public interface RegionsRepository extends JpaRepository<Region, Long> {
|
||||||
|
Optional<Region> findFirstByName(String name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
package com.bvn13.covid19.site.service;
|
|
||||||
|
|
||||||
import com.bvn13.covid19.model.entities.CovidStat;
|
|
||||||
import com.bvn13.covid19.site.model.CovidData;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class CovidStatsResponseMaker {
|
|
||||||
|
|
||||||
public List<CovidData> convertStats(Collection<CovidStat> stats) {
|
|
||||||
return stats.stream()
|
|
||||||
.map(stat -> CovidData.builder()
|
|
||||||
.region(stat.getRegion().getName())
|
|
||||||
.sick(stat.getSick())
|
|
||||||
.healed(stat.getHealed())
|
|
||||||
.died(stat.getDied())
|
|
||||||
.build())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
Copyright [2020] [bvn13]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.bvn13.covid19.site.service;
|
||||||
|
|
||||||
|
import com.bvn13.covid19.model.entities.CovidStat;
|
||||||
|
import com.bvn13.covid19.model.entities.Region;
|
||||||
|
import com.bvn13.covid19.model.entities.StatsProvider;
|
||||||
|
import com.bvn13.covid19.site.model.CovidData;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.util.Pair;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
public class StatisticsAggregator {
|
||||||
|
|
||||||
|
private static final StatsProvider zero = CovidData.Delta.builder()
|
||||||
|
.sick(0L)
|
||||||
|
.healed(0L)
|
||||||
|
.died(0L)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
public List<CovidData> prepareStats(Collection<CovidStat> current, Map<Region, StatsProvider> previous) {
|
||||||
|
return current.stream()
|
||||||
|
.map(stat -> Pair.of(stat, Optional.ofNullable(previous.get(stat.getRegion()))))
|
||||||
|
.map(statPair -> CovidData.builder()
|
||||||
|
.region(statPair.getFirst().getRegion().getName())
|
||||||
|
.sick(statPair.getFirst().getSick())
|
||||||
|
.healed(statPair.getFirst().getHealed())
|
||||||
|
.died(statPair.getFirst().getDied())
|
||||||
|
.previous(CovidData.Delta.builder()
|
||||||
|
.sick(statPair.getSecond().orElse(zero).getSick())
|
||||||
|
.healed(statPair.getSecond().orElse(zero).getHealed())
|
||||||
|
.died(statPair.getSecond().orElse(zero).getDied())
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,10 +23,17 @@ import com.bvn13.covid19.site.repositories.CovidStatsRepository;
|
||||||
import com.bvn13.covid19.site.repositories.CovidUpdatesRepository;
|
import com.bvn13.covid19.site.repositories.CovidUpdatesRepository;
|
||||||
import com.bvn13.covid19.site.repositories.RegionsRepository;
|
import com.bvn13.covid19.site.repositories.RegionsRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.time.temporal.TemporalUnit;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -35,12 +42,30 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Component
|
@Component
|
||||||
public class CovidStatsMaker {
|
public class StatisticsPreparator {
|
||||||
|
|
||||||
private final CovidStatsRepository statsRepository;
|
private final CovidStatsRepository statsRepository;
|
||||||
private final CovidUpdatesRepository updatesRepository;
|
private final CovidUpdatesRepository updatesRepository;
|
||||||
private final RegionsRepository regionsRepository;
|
private final RegionsRepository regionsRepository;
|
||||||
|
|
||||||
|
private LocalDate projectStartDate;
|
||||||
|
private ZonedDateTime projectStartZonedDate;
|
||||||
|
@Value("${app.zone-id}")
|
||||||
|
private String zoneIdStr;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
ZoneId zoneId = ZoneId.of(zoneIdStr);
|
||||||
|
projectStartZonedDate = projectStartDate.atTime(0, 0, 0).atZone(zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${app.project-start-date}")
|
||||||
|
private void setProjectStartDate(String ld) {
|
||||||
|
if (ld != null && !ld.isEmpty()) {
|
||||||
|
projectStartDate = LocalDate.parse(ld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Collection<CovidStat> getLastCovidStats() {
|
public Collection<CovidStat> getLastCovidStats() {
|
||||||
return updatesRepository.findLastUpdate()
|
return updatesRepository.findLastUpdate()
|
||||||
|
@ -59,6 +84,26 @@ public class CovidStatsMaker {
|
||||||
.flatMap(updateInfo -> updatesRepository.findFirstByCreatedOn(updateInfo.getCreatedOn()));
|
.flatMap(updateInfo -> updatesRepository.findFirstByCreatedOn(updateInfo.getCreatedOn()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Cacheable(
|
||||||
|
cacheNames = "covid-prev-update-by-date",
|
||||||
|
unless = "#result == null"
|
||||||
|
)
|
||||||
|
public Optional<CovidUpdate> findPrevUpdateByDate(ZonedDateTime date) {
|
||||||
|
if (date.isBefore(projectStartZonedDate)) {
|
||||||
|
return Optional.empty();
|
||||||
|
} else {
|
||||||
|
Optional<CovidUpdate> update = updatesRepository.findByDateOfUpdate(
|
||||||
|
dateWithTime(prevDate(date), 0, 0, 0),
|
||||||
|
dateWithTime(date, 0, 0, 0)
|
||||||
|
);
|
||||||
|
if (update.isPresent()) {
|
||||||
|
return update;
|
||||||
|
} else {
|
||||||
|
return findPrevUpdateByDate(prevDate(date));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Cacheable(
|
@Cacheable(
|
||||||
cacheNames = "covid-stats-by-update-info-id",
|
cacheNames = "covid-stats-by-update-info-id",
|
||||||
condition = "#updateInfoId > 0",
|
condition = "#updateInfoId > 0",
|
||||||
|
@ -84,4 +129,15 @@ public class CovidStatsMaker {
|
||||||
return regionsRepository.findAll().stream().map(Region::getName).collect(Collectors.toList());
|
return regionsRepository.findAll().stream().map(Region::getName).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ZonedDateTime prevDate(ZonedDateTime date) {
|
||||||
|
return date.minus(1, ChronoUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ZonedDateTime dateWithTime(ZonedDateTime date, int hour, int minute, int second) {
|
||||||
|
return date
|
||||||
|
.withHour(hour)
|
||||||
|
.withMinute(minute)
|
||||||
|
.withSecond(second);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ server:
|
||||||
app:
|
app:
|
||||||
zone-id: Europe/Moscow
|
zone-id: Europe/Moscow
|
||||||
main-url: http://localhost:8080
|
main-url: http://localhost:8080
|
||||||
|
project-start-date: 2020-03-01
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
|
@ -13,7 +14,7 @@ spring:
|
||||||
type: caffeine
|
type: caffeine
|
||||||
caffeine:
|
caffeine:
|
||||||
spec: expireAfterWrite=15m
|
spec: expireAfterWrite=15m
|
||||||
cache-names: covid-last-update, covid-all-days-updates, covid-stats-by-update-info-id, covid-regions
|
cache-names: covid-last-update, covid-all-days-updates, covid-stats-by-update-info-id, covid-regions, covid-prev-update-by-date
|
||||||
|
|
||||||
flyway:
|
flyway:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html xmlns:th="http://www.thymeleaf.org">
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Home page</title>
|
<title>Статистика COVID19 в России</title>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
|
||||||
|
@ -28,6 +28,13 @@
|
||||||
<canvas id="chart"></canvas>
|
<canvas id="chart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/bvn13/covid19-ru">Исходный код</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://twitter.com/bvn13">@bvn13 - Автор проекта</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
var mainUrl = [[${@mainConfig.getMainUrl()}]];
|
var mainUrl = [[${@mainConfig.getMainUrl()}]];
|
||||||
|
|
||||||
|
@ -68,34 +75,73 @@
|
||||||
function showData(json) {
|
function showData(json) {
|
||||||
var labels = _.map(json.progress, (progress) => progress.datetime.substr(0, 10));
|
var labels = _.map(json.progress, (progress) => progress.datetime.substr(0, 10));
|
||||||
var sick = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].sick : 0);
|
var sick = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].sick : 0);
|
||||||
|
var sickDeltas = _.map(json.progress, (progress) =>
|
||||||
|
progress.stats.length > 0
|
||||||
|
? progress.stats[0].sick - progress.stats[0].previous.sick
|
||||||
|
: 0
|
||||||
|
);
|
||||||
var healed = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].healed : 0);
|
var healed = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].healed : 0);
|
||||||
|
var healedDeltas = _.map(json.progress, (progress) =>
|
||||||
|
progress.stats.length > 0
|
||||||
|
? progress.stats[0].healed - progress.stats[0].previous.healed
|
||||||
|
: 0
|
||||||
|
);
|
||||||
var died = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].died : 0);
|
var died = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].died : 0);
|
||||||
|
var diedDeltas = _.map(json.progress, (progress) =>
|
||||||
|
progress.stats.length > 0
|
||||||
|
? progress.stats[0].died - progress.stats[0].previous.died
|
||||||
|
: 0
|
||||||
|
);
|
||||||
|
|
||||||
var myChart = new Chart(ctx, {
|
var myChart = new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Всего',
|
label: 'Всего (чел.)',
|
||||||
data: sick,
|
data: sick,
|
||||||
backgroundColor: 'red',
|
backgroundColor: 'red',
|
||||||
borderColor: 'red',
|
borderColor: 'red',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
fill: false
|
fill: false
|
||||||
}, {
|
}, {
|
||||||
label: 'Выздоровело',
|
label: 'Выздоровело (чел.)',
|
||||||
data: healed,
|
data: healed,
|
||||||
backgroundColor: 'green',
|
backgroundColor: 'green',
|
||||||
borderColor: 'green',
|
borderColor: 'green',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
fill: false
|
fill: false
|
||||||
}, {
|
}, {
|
||||||
label: 'Умерло',
|
label: 'Умерло (чел.)',
|
||||||
data: died,
|
data: died,
|
||||||
backgroundColor: 'black',
|
backgroundColor: 'black',
|
||||||
borderColor: 'black',
|
borderColor: 'black',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
fill: false
|
fill: false
|
||||||
|
}, {
|
||||||
|
label: 'Всего (дельта чел.)',
|
||||||
|
data: sickDeltas,
|
||||||
|
backgroundColor: 'red',
|
||||||
|
borderColor: 'red',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderDash: [5, 15],
|
||||||
|
fill: false
|
||||||
|
}, {
|
||||||
|
label: 'Выздоровело (дельта чел.)',
|
||||||
|
data: healedDeltas,
|
||||||
|
backgroundColor: 'green',
|
||||||
|
borderColor: 'green',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderDash: [5, 15],
|
||||||
|
fill: false
|
||||||
|
}, {
|
||||||
|
label: 'Умерло (дельта чел.)',
|
||||||
|
data: diedDeltas,
|
||||||
|
backgroundColor: 'black',
|
||||||
|
borderColor: 'black',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderDash: [5, 15],
|
||||||
|
fill: false
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
|
Loading…
Reference in New Issue