Skip to content
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

Zoned date time #3

Open
wants to merge 11 commits into
base: option-0-no-api
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ application {
mainClass = "tw.joi.energy.App"
}

tasks.withType<JavaCompile>() {
tasks.withType<JavaCompile> {
this.options.isDeprecation = true
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/tw/joi/energy/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public static void main(String[] args) {
printAllAvailablePricePlans(pricePlanRepository);

printSmartMeterInformation(smartMeterRepository, "Before storing readings...");
var readingsToSave = ElectricityReadingsGenerator.generateElectricityReadingStream(3).toList();
var readingsToSave =
ElectricityReadingsGenerator.generateElectricityReadingStream(3).toList();
meterReadingManager.storeReadings(TEST_SMART_METER, readingsToSave);
printSmartMeterInformation(smartMeterRepository, "After storing readings...");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package tw.joi.energy.config;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;
import tw.joi.energy.domain.ElectricityReading;

public class ElectricityReadingsGenerator {

public static final double AVG_HOURLY_USAGE = 0.3;
public static final double VARIANCE = 0.2;
public static final double MIN_HOURLY_USAGE = AVG_HOURLY_USAGE - VARIANCE;
public static final double MAX_HOURLY_USAGE = AVG_HOURLY_USAGE + VARIANCE;

private ElectricityReadingsGenerator() {}

public static Stream<ElectricityReading> generateElectricityReadingStream(int days) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I buy returning a stream here. The implementation doesn't look much simpler, and every single caller wants a List instead of a Stream.

The name also adds a lot of stutter (unless every caller switches to a static import).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend the static import 😄 which was sort of my assumption anyway.

Lets think a bit more about streams - they're trivially convertible to lists anyway. To be honest, the functions also need some more possible parameters - eg the interval between the readings to generate and the function to generate the values. I hadn't quite finished removing all dependencies on hidden state such as Random...

Expand All @@ -24,15 +22,22 @@ public static Stream<ElectricityReading> generateElectricityReadingStream(int da

// we'll provide hourly readings for the specified number of days assuming 24 hours a day
// we'll assume that a house consumes ca 2700 kWh a year, so about 0.3 kWh per hour
public static Stream<ElectricityReading> generateElectricityReadingStream(Clock clock, BigDecimal initialReading, int days) {

// the assumed starting point is the time on the clock, the ending point 24 hours later - so for 1 day, we'll get 25
// readings
public static Stream<ElectricityReading> generateElectricityReadingStream(
Clock clock, BigDecimal initialReading, int days) {
var now = clock.instant();
var readingRandomiser = new Random();
var seed = new ElectricityReading(now, initialReading);
var lastTimeToBeSupplied = now.plus(days * 24, ChronoUnit.HOURS);
return Stream.iterate(seed, er -> er.time().equals(lastTimeToBeSupplied) || er.time().isAfter(lastTimeToBeSupplied),
er -> {
var hoursWorthOfEnergy = BigDecimal.valueOf(readingRandomiser.nextDouble(0.3 - 0.2, 0.3 + 0.2));
return new ElectricityReading(er.time().plus(1, ChronoUnit.HOURS), er.readingInKwH().add(hoursWorthOfEnergy));
});
var lastTimeToBeSupplied = now.plus(days * 24L, ChronoUnit.HOURS);
return Stream.iterate(
seed, er -> er.time().equals(lastTimeToBeSupplied) || er.time().isBefore(lastTimeToBeSupplied), er -> {
jejking-tw marked this conversation as resolved.
Show resolved Hide resolved
var hoursWorthOfEnergy =
BigDecimal.valueOf(readingRandomiser.nextDouble(MIN_HOURLY_USAGE, MAX_HOURLY_USAGE));
return new ElectricityReading(
er.time().plus(1, ChronoUnit.HOURS),
er.readingInKwH().add(hoursWorthOfEnergy));
});
}
}
24 changes: 20 additions & 4 deletions src/main/java/tw/joi/energy/config/TestData.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,29 @@ public static SmartMeterRepository smartMeterRepository() {
var smartMeterRepository = new SmartMeterRepository();
smartMeterRepository.save("smart-meter-0", new SmartMeter(MOST_EVIL_PRICE_PLAN, emptyList()));
smartMeterRepository.save(
"smart-meter-1", new SmartMeter(RENEWABLES_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(7).toList()));
"smart-meter-1",
new SmartMeter(
RENEWABLES_PRICE_PLAN,
ElectricityReadingsGenerator.generateElectricityReadingStream(7)
.toList()));
smartMeterRepository.save(
"smart-meter-2", new SmartMeter(MOST_EVIL_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(20).toList()));
"smart-meter-2",
new SmartMeter(
MOST_EVIL_PRICE_PLAN,
ElectricityReadingsGenerator.generateElectricityReadingStream(20)
.toList()));
smartMeterRepository.save(
"smart-meter-3", new SmartMeter(STANDARD_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(12).toList()));
"smart-meter-3",
new SmartMeter(
STANDARD_PRICE_PLAN,
ElectricityReadingsGenerator.generateElectricityReadingStream(12)
.toList()));
smartMeterRepository.save(
"smart-meter-4", new SmartMeter(RENEWABLES_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(3).toList()));
"smart-meter-4",
new SmartMeter(
RENEWABLES_PRICE_PLAN,
ElectricityReadingsGenerator.generateElectricityReadingStream(3)
.toList()));
return smartMeterRepository;
}

Expand Down
8 changes: 7 additions & 1 deletion src/main/java/tw/joi/energy/domain/ElectricityReading.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package tw.joi.energy.domain;

import java.math.BigDecimal;
import java.time.Clock;
import java.time.Instant;

/**
* @param time point in time
* @param readingInKwH energy consumed in total to this point in time in kWh
*/
public record ElectricityReading(Instant time, BigDecimal readingInKwH) {}
public record ElectricityReading(Instant time, BigDecimal readingInKwH) {

public ElectricityReading(Clock clock, double readingInKwH) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this constructor meant to be for testing purposes (faking the current time, lighter syntax for the reading) or also for production usage?

this(clock.instant(), BigDecimal.valueOf(readingInKwH));
}
}
6 changes: 6 additions & 0 deletions src/main/java/tw/joi/energy/domain/PeakTimeMultiplier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tw.joi.energy.domain;

import java.math.BigDecimal;
import java.time.DayOfWeek;

public record PeakTimeMultiplier(DayOfWeek dayOfWeek, BigDecimal multiplier) {}
21 changes: 6 additions & 15 deletions src/main/java/tw/joi/energy/domain/PricePlan.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@
import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class PricePlan {

private final String energySupplier;
private final String planName;
private final BigDecimal unitRate; // unit price per kWh
private final Set<PeakTimeMultiplier> peakTimeMultipliers;
private final Map<DayOfWeek, BigDecimal> peakTimeMultipliers;

public PricePlan(
String planName, String energySupplier, BigDecimal unitRate, Set<PeakTimeMultiplier> peakTimeMultipliers) {
String planName, String energySupplier, BigDecimal unitRate, Set<PeakTimeMultiplier> peakTimeMultipliers) {
jejking-tw marked this conversation as resolved.
Show resolved Hide resolved
this.planName = planName;
this.energySupplier = energySupplier;
this.unitRate = unitRate;
this.peakTimeMultipliers = peakTimeMultipliers;
this.peakTimeMultipliers = peakTimeMultipliers.stream()
.collect(Collectors.toUnmodifiableMap(PeakTimeMultiplier::dayOfWeek, PeakTimeMultiplier::multiplier));
jejking-tw marked this conversation as resolved.
Show resolved Hide resolved
}

public String getEnergySupplier() {
Expand All @@ -41,15 +43,4 @@ public BigDecimal getPrice(ZonedDateTime dateTime) {
public String toString() {
return "Name: '" + planName + "', Unit Rate: " + unitRate + ", Supplier: '" + energySupplier + "'";
}

static class PeakTimeMultiplier {

DayOfWeek dayOfWeek;
BigDecimal multiplier;

public PeakTimeMultiplier(DayOfWeek dayOfWeek, BigDecimal multiplier) {
this.dayOfWeek = dayOfWeek;
this.multiplier = multiplier;
}
}
}
1 change: 1 addition & 0 deletions src/main/java/tw/joi/energy/domain/SmartMeter.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.stream.Collectors;

public class SmartMeter {

private final PricePlan pricePlan;
private final List<ElectricityReading> electricityReadings;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package tw.joi.energy.repository;

import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toMap;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package tw.joi.energy.config;

import static org.assertj.core.api.Assertions.assertThat;
import static tw.joi.energy.config.ElectricityReadingsGenerator.MAX_HOURLY_USAGE;
import static tw.joi.energy.config.ElectricityReadingsGenerator.MIN_HOURLY_USAGE;
import static tw.joi.energy.config.ElectricityReadingsGenerator.generateElectricityReadingStream;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import tw.joi.energy.domain.ElectricityReading;

class ElectricityReadingsGeneratorTest {

@Test
@DisplayName("Stream for one day should have 25 entries")
void streamShouldHave25EntriesForOneDay() {
assertThat(generateElectricityReadingStream(1).count()).isEqualTo(25);
jejking-tw marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
@DisplayName("Stream for two days should have 49 entries")
void streamShouldHave49EntriesForTwoDays() {
jejking-tw marked this conversation as resolved.
Show resolved Hide resolved
assertThat(generateElectricityReadingStream(2).count()).isEqualTo(49);
}

@Test
@DisplayName("Stream for one day should end 24 hours after initial entry")
void streamShouldHave24HoursAfterInitialEntry() {
var streamAsList = generateElectricityReadingStream(1).toList();
var firstEntry = streamAsList.getFirst();
var lastEntry = streamAsList.getLast();
assertThat(Duration.between(firstEntry.time(), lastEntry.time())).hasHours(24);
}

@Test
@DisplayName("Stream entries should be one hour apart")
void streamEntriesShouldBeOneHourApart() {
validateOrderedPairsOfEntries(generateElectricityReadingStream(1), (earlierReading, laterReading) -> assertThat(
Duration.between(earlierReading.time(), laterReading.time()))
.hasHours(1));
}

@Test
@DisplayName("Stream entries should have an increasing energy consumption over time")
void streamEntriesShouldHaveAnIncreasingEnergyConsumptionOverTime() {
validateOrderedPairsOfEntries(generateElectricityReadingStream(1), (earlierReading, laterReading) -> assertThat(
laterReading.readingInKwH().compareTo(earlierReading.readingInKwH()))
.isEqualTo(1));
}

@Test
@DisplayName("Stream entries should have an energy consumption in the expected reange")
void streamEntriesShouldHaveAnIncreasingEnergyConsumption() {
var min = BigDecimal.valueOf(MIN_HOURLY_USAGE);
var max = BigDecimal.valueOf(MAX_HOURLY_USAGE);

validateOrderedPairsOfEntries(generateElectricityReadingStream(1), (earlierReading, laterReading) -> {
var energyBetweenReadings = laterReading.readingInKwH().subtract(earlierReading.readingInKwH());
assertThat(energyBetweenReadings.compareTo(min)).isEqualTo(1);
assertThat(energyBetweenReadings.compareTo(max)).isEqualTo(-1);
});
}

private void validateOrderedPairsOfEntries(
Stream<ElectricityReading> stream, BiConsumer<ElectricityReading, ElectricityReading> validator) {
var streamAsList = stream.toList();
for (int i = 1; i <= streamAsList.size() - 1; i++) {
var laterElectricityReading = streamAsList.get(i);
var earlierElectricityReading = streamAsList.get(i - 1);
validator.accept(earlierElectricityReading, laterElectricityReading);
}
}
}
6 changes: 3 additions & 3 deletions src/test/java/tw/joi/energy/domain/PricePlanTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ public void get_energy_supplier_should_return_the_energy_supplier_given_supplier
@Test
@DisplayName("Get price should return price given non-peak date and time")
public void get_price_should_return_the_base_price_given_an_ordinary_date_time() {
ZonedDateTime nonPeakDateTime = ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0),
ZoneId.of("GMT"));
// the price plan has no peak days....
ZonedDateTime nonPeakDateTime =
ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0), ZoneId.of("GMT"));
// the price plan has no peak days, so all times are non-peak
PricePlan pricePlan = new PricePlan("test plan", "test supplier", BigDecimal.ONE, emptySet());

BigDecimal price = pricePlan.getPrice(nonPeakDateTime);
Expand Down
3 changes: 1 addition & 2 deletions src/test/java/tw/joi/energy/domain/SmartMeterTest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package tw.joi.energy.domain;

import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static java.util.Collections.emptyList;

class SmartMeterTest {

@Test
Expand Down
19 changes: 0 additions & 19 deletions src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still being used by CostComparisonTest, which no longer compiles.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works on my machine ... That's possibly because it seems to have disappeared. I'll double check what's happening there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually see that test in the branch option-0-no-api, so I'm a bit puzzled. Could you take another look?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, somehow that one was sitting around from another branch 🤷

This file was deleted.

10 changes: 4 additions & 6 deletions src/test/java/tw/joi/energy/fixture/PricePlanFixture.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package tw.joi.energy.fixture;

import static java.util.Collections.emptySet;

import java.math.BigDecimal;
import tw.joi.energy.domain.PricePlan;

import static java.util.Collections.emptySet;

public class PricePlanFixture {

public static final String WORST_PLAN_ID = "worst-supplier";
Expand All @@ -14,9 +14,7 @@ public class PricePlanFixture {
public static final PricePlan DEFAULT_PRICE_PLAN =
new PricePlan(SECOND_BEST_PLAN_ID, "energy-supplier", BigDecimal.TWO, emptySet());

public static final PricePlan WORST_PRICE_PLAN =
new PricePlan(WORST_PLAN_ID, null, BigDecimal.TEN, emptySet());
public static final PricePlan WORST_PRICE_PLAN = new PricePlan(WORST_PLAN_ID, null, BigDecimal.TEN, emptySet());

public static final PricePlan BEST_PRICE_PLAN =
new PricePlan(BEST_PLAN_ID, null, BigDecimal.ONE, emptySet());
public static final PricePlan BEST_PRICE_PLAN = new PricePlan(BEST_PLAN_ID, null, BigDecimal.ONE, emptySet());
}
Loading