Untitled
unknown
plain_text
5 months ago
17 kB
2
Indexable
package uk.tw.energy.controller; import java.util.List; import java.util.Optional; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import uk.tw.energy.domain.ElectricityReading; import uk.tw.energy.domain.MeterReadings; import uk.tw.energy.service.MeterReadingService; @RestController @RequestMapping("/readings") public class MeterReadingController { private final MeterReadingService meterReadingService; public MeterReadingController(MeterReadingService meterReadingService) { this.meterReadingService = meterReadingService; } @PostMapping("/store") public ResponseEntity storeReadings(@RequestBody MeterReadings meterReadings) { if (!isMeterReadingsValid(meterReadings)) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } meterReadingService.storeReadings(meterReadings.smartMeterId(), meterReadings.electricityReadings()); return ResponseEntity.ok().build(); } private boolean isMeterReadingsValid(MeterReadings meterReadings) { String smartMeterId = meterReadings.smartMeterId(); List<ElectricityReading> electricityReadings = meterReadings.electricityReadings(); return smartMeterId != null && !smartMeterId.isEmpty() && electricityReadings != null && !electricityReadings.isEmpty(); } @GetMapping("/read/{smartMeterId}") public ResponseEntity readReadings(@PathVariable String smartMeterId) { Optional<List<ElectricityReading>> readings = meterReadingService.getReadings(smartMeterId); return readings.isPresent() ? ResponseEntity.ok(readings.get()) : ResponseEntity.notFound().build(); } } package uk.tw.energy.controller; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import uk.tw.energy.service.AccountService; import uk.tw.energy.service.PricePlanService; @RestController @RequestMapping("/price-plans") public class PricePlanComparatorController { public static final String PRICE_PLAN_ID_KEY = "pricePlanId"; public static final String PRICE_PLAN_COMPARISONS_KEY = "pricePlanComparisons"; private final PricePlanService pricePlanService; private final AccountService accountService; public PricePlanComparatorController(PricePlanService pricePlanService, AccountService accountService) { this.pricePlanService = pricePlanService; this.accountService = accountService; } @GetMapping("/compare-all/{smartMeterId}") public ResponseEntity<Map<String, Object>> calculatedCostForEachPricePlan(@PathVariable String smartMeterId) { String pricePlanId = accountService.getPricePlanIdForSmartMeterId(smartMeterId); Optional<Map<String, BigDecimal>> consumptionsForPricePlans = pricePlanService.getConsumptionCostOfElectricityReadingsForEachPricePlan(smartMeterId); if (!consumptionsForPricePlans.isPresent()) { return ResponseEntity.notFound().build(); } Map<String, Object> pricePlanComparisons = new HashMap<>(); pricePlanComparisons.put(PRICE_PLAN_ID_KEY, pricePlanId); pricePlanComparisons.put(PRICE_PLAN_COMPARISONS_KEY, consumptionsForPricePlans.get()); return consumptionsForPricePlans.isPresent() ? ResponseEntity.ok(pricePlanComparisons) : ResponseEntity.notFound().build(); } @GetMapping("/recommend/{smartMeterId}") public ResponseEntity<List<Map.Entry<String, BigDecimal>>> recommendCheapestPricePlans( @PathVariable String smartMeterId, @RequestParam(value = "limit", required = false) Integer limit) { Optional<Map<String, BigDecimal>> consumptionsForPricePlans = pricePlanService.getConsumptionCostOfElectricityReadingsForEachPricePlan(smartMeterId); if (!consumptionsForPricePlans.isPresent()) { return ResponseEntity.notFound().build(); } List<Map.Entry<String, BigDecimal>> recommendations = new ArrayList<>(consumptionsForPricePlans.get().entrySet()); recommendations.sort(Comparator.comparing(Map.Entry::getValue)); if (limit != null && limit < recommendations.size()) { recommendations = recommendations.subList(0, limit); } return ResponseEntity.ok(recommendations); } } package uk.tw.energy.domain; import java.math.BigDecimal; import java.time.Instant; /** * @param reading kW */ public record ElectricityReading(Instant time, BigDecimal reading) {} package uk.tw.energy.domain; import java.util.List; public record MeterReadings(String smartMeterId, List<ElectricityReading> electricityReadings) {} package uk.tw.energy.domain; import java.math.BigDecimal; import java.time.DayOfWeek; import java.time.LocalDateTime; import java.util.List; public class PricePlan { private final String energySupplier; private final String planName; private final BigDecimal unitRate; // unit price per kWh private final List<PeakTimeMultiplier> peakTimeMultipliers; public PricePlan( String planName, String energySupplier, BigDecimal unitRate, List<PeakTimeMultiplier> peakTimeMultipliers) { this.planName = planName; this.energySupplier = energySupplier; this.unitRate = unitRate; this.peakTimeMultipliers = peakTimeMultipliers; } public String getEnergySupplier() { return energySupplier; } public String getPlanName() { return planName; } public BigDecimal getUnitRate() { return unitRate; } public BigDecimal getPrice(LocalDateTime dateTime) { return peakTimeMultipliers.stream() .filter(multiplier -> multiplier.dayOfWeek.equals(dateTime.getDayOfWeek())) .findFirst() .map(multiplier -> unitRate.multiply(multiplier.multiplier)) .orElse(unitRate); } static class PeakTimeMultiplier { DayOfWeek dayOfWeek; BigDecimal multiplier; public PeakTimeMultiplier(DayOfWeek dayOfWeek, BigDecimal multiplier) { this.dayOfWeek = dayOfWeek; this.multiplier = multiplier; } } } package uk.tw.energy.generator; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.Instant; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Random; import uk.tw.energy.domain.ElectricityReading; public class ElectricityReadingsGenerator { public List<ElectricityReading> generate(int number) { List<ElectricityReading> readings = new ArrayList<>(); Instant now = Instant.now(); Random readingRandomiser = new Random(); for (int i = 0; i < number; i++) { double positiveRandomValue = Math.abs(readingRandomiser.nextGaussian()); BigDecimal randomReading = BigDecimal.valueOf(positiveRandomValue).setScale(4, RoundingMode.CEILING); ElectricityReading electricityReading = new ElectricityReading(now.minusSeconds(i * 10L), randomReading); readings.add(electricityReading); } readings.sort(Comparator.comparing(ElectricityReading::time)); return readings; } } package uk.tw.energy.service; import java.util.Map; import org.springframework.stereotype.Service; @Service public class AccountService { private final Map<String, String> smartMeterToPricePlanAccounts; public AccountService(Map<String, String> smartMeterToPricePlanAccounts) { this.smartMeterToPricePlanAccounts = smartMeterToPricePlanAccounts; } public String getPricePlanIdForSmartMeterId(String smartMeterId) { return smartMeterToPricePlanAccounts.get(smartMeterId); } } package uk.tw.energy.service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import org.springframework.stereotype.Service; import uk.tw.energy.domain.ElectricityReading; @Service public class MeterReadingService { private final Map<String, List<ElectricityReading>> meterAssociatedReadings; public MeterReadingService(Map<String, List<ElectricityReading>> meterAssociatedReadings) { this.meterAssociatedReadings = meterAssociatedReadings; } public Optional<List<ElectricityReading>> getReadings(String smartMeterId) { return Optional.ofNullable(meterAssociatedReadings.get(smartMeterId)); } public void storeReadings(String smartMeterId, List<ElectricityReading> electricityReadings) { if (!meterAssociatedReadings.containsKey(smartMeterId)) { meterAssociatedReadings.put(smartMeterId, new ArrayList<>()); } meterAssociatedReadings.get(smartMeterId).addAll(electricityReadings); } } package uk.tw.energy.service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.Duration; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import org.springframework.stereotype.Service; import uk.tw.energy.domain.ElectricityReading; import uk.tw.energy.domain.PricePlan; @Service public class PricePlanService { private final List<PricePlan> pricePlans; private final MeterReadingService meterReadingService; public PricePlanService(List<PricePlan> pricePlans, MeterReadingService meterReadingService) { this.pricePlans = pricePlans; this.meterReadingService = meterReadingService; } public Optional<Map<String, BigDecimal>> getConsumptionCostOfElectricityReadingsForEachPricePlan( String smartMeterId) { Optional<List<ElectricityReading>> electricityReadings = meterReadingService.getReadings(smartMeterId); if (!electricityReadings.isPresent()) { return Optional.empty(); } return Optional.of(pricePlans.stream() .collect(Collectors.toMap(PricePlan::getPlanName, t -> calculateCost(electricityReadings.get(), t)))); } private BigDecimal calculateCost(List<ElectricityReading> electricityReadings, PricePlan pricePlan) { BigDecimal average = calculateAverageReading(electricityReadings); BigDecimal timeElapsed = calculateTimeElapsed(electricityReadings); BigDecimal averagedCost = average.divide(timeElapsed, RoundingMode.HALF_UP); return averagedCost.multiply(pricePlan.getUnitRate()); } private BigDecimal calculateAverageReading(List<ElectricityReading> electricityReadings) { BigDecimal summedReadings = electricityReadings.stream() .map(ElectricityReading::reading) .reduce(BigDecimal.ZERO, (reading, accumulator) -> reading.add(accumulator)); return summedReadings.divide(BigDecimal.valueOf(electricityReadings.size()), RoundingMode.HALF_UP); } private BigDecimal calculateTimeElapsed(List<ElectricityReading> electricityReadings) { ElectricityReading first = electricityReadings.stream() .min(Comparator.comparing(ElectricityReading::time)) .get(); ElectricityReading last = electricityReadings.stream() .max(Comparator.comparing(ElectricityReading::time)) .get(); return BigDecimal.valueOf(Duration.between(first.time(), last.time()).getSeconds() / 3600.0); } } package uk.tw.energy; import static java.util.Collections.emptyList; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import uk.tw.energy.domain.ElectricityReading; import uk.tw.energy.domain.PricePlan; import uk.tw.energy.generator.ElectricityReadingsGenerator; @Configuration public class SeedingApplicationDataConfiguration { private static final String MOST_EVIL_PRICE_PLAN_ID = "price-plan-0"; private static final String RENEWABLES_PRICE_PLAN_ID = "price-plan-1"; private static final String STANDARD_PRICE_PLAN_ID = "price-plan-2"; @Bean public List<PricePlan> pricePlans() { final List<PricePlan> pricePlans = new ArrayList<>(); pricePlans.add(new PricePlan(MOST_EVIL_PRICE_PLAN_ID, "Dr Evil's Dark Energy", BigDecimal.TEN, emptyList())); pricePlans.add(new PricePlan(RENEWABLES_PRICE_PLAN_ID, "The Green Eco", BigDecimal.valueOf(2), emptyList())); pricePlans.add(new PricePlan(STANDARD_PRICE_PLAN_ID, "Power for Everyone", BigDecimal.ONE, emptyList())); return pricePlans; } @Bean public Map<String, List<ElectricityReading>> perMeterElectricityReadings() { final Map<String, List<ElectricityReading>> readings = new HashMap<>(); final ElectricityReadingsGenerator electricityReadingsGenerator = new ElectricityReadingsGenerator(); smartMeterToPricePlanAccounts() .keySet() .forEach(smartMeterId -> readings.put(smartMeterId, electricityReadingsGenerator.generate(20))); return readings; } @Bean public Map<String, String> smartMeterToPricePlanAccounts() { final Map<String, String> smartMeterToPricePlanAccounts = new HashMap<>(); smartMeterToPricePlanAccounts.put("smart-meter-0", MOST_EVIL_PRICE_PLAN_ID); smartMeterToPricePlanAccounts.put("smart-meter-1", RENEWABLES_PRICE_PLAN_ID); smartMeterToPricePlanAccounts.put("smart-meter-2", MOST_EVIL_PRICE_PLAN_ID); smartMeterToPricePlanAccounts.put("smart-meter-3", STANDARD_PRICE_PLAN_ID); smartMeterToPricePlanAccounts.put("smart-meter-4", RENEWABLES_PRICE_PLAN_ID); return smartMeterToPricePlanAccounts; } @Bean @Primary public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); return objectMapper; } } Welcome to PowerDale PowerDale is a small town with around 100 residents. Most houses have a smart meter installed that can save and send information about how much power a house is drawing/using. There are three major providers of energy in town that charge different amounts for the power they supply. Dr Evil's Dark Energy The Green Eco Power for Everyone Introducing JOI Energy JOI Energy is a new start-up in the energy industry. Rather than selling energy they want to differentiate themselves from the market by recording their customers' energy usage from their smart meters and recommending the best supplier to meet their needs. You have been placed into their development team, whose current goal is to produce an API which their customers and smart meters will interact with. Unfortunately, two members of the team are on annual leave, and another one has called in sick! You are left with another ThoughtWorker to progress with the current user stories on the story wall. This is your chance to make an impact on the business, improve the code base and deliver value. Users To trial the new JOI software 5 people from the JOI accounts team have agreed to test the service and share their energy data. User Smart Meter ID Power Supplier Sarah smart-meter-0 Dr Evil's Dark Energy Peter smart-meter-1 The Green Eco Charlie smart-meter-2 Dr Evil's Dark Energy Andrea smart-meter-3 Power for Everyone Alex smart-meter-4 The Green Eco These values are used in the code and in the following examples too.
Editor is loading...
Leave a Comment