From a2920365efa7eab356b114d91fa952f25d81b515 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 22:39:49 +0000 Subject: [PATCH 1/4] Setting up GitHub Classroom Feedback From 37678d97d24147dcffaabf3fb78013893671f433 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Fri, 26 Sep 2025 12:19:17 +0200 Subject: [PATCH 2/4] Updated test --- src/test/java/com/example/MainTest.java | 162 ++++++++++++++---------- 1 file changed, 94 insertions(+), 68 deletions(-) diff --git a/src/test/java/com/example/MainTest.java b/src/test/java/com/example/MainTest.java index 6199d951..c61f3589 100644 --- a/src/test/java/com/example/MainTest.java +++ b/src/test/java/com/example/MainTest.java @@ -43,7 +43,8 @@ void getPriser_shouldReturnParsedPrices_whenMockDataIsProvided() { [{"SEK_per_kWh":0.12229,"EUR_per_kWh":0.01112,"EXR":10.997148,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"},{"SEK_per_kWh":0.09886,"EUR_per_kWh":0.00899,"EXR":10.997148,"time_start":"2025-09-04T01:00:00+02:00","time_end":"2025-09-04T02:00:00+02:00"},{"SEK_per_kWh":0.09095,"EUR_per_kWh":0.00827,"EXR":10.997148,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"},{"SEK_per_kWh":0.04201,"EUR_per_kWh":0.00382,"EXR":10.997148,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"},{"SEK_per_kWh":0.04146,"EUR_per_kWh":0.00377,"EXR":10.997148,"time_start":"2025-09-04T04:00:00+02:00","time_end":"2025-09-04T05:00:00+02:00"},{"SEK_per_kWh":0.04465,"EUR_per_kWh":0.00406,"EXR":10.997148,"time_start":"2025-09-04T05:00:00+02:00","time_end":"2025-09-04T06:00:00+02:00"},{"SEK_per_kWh":0.32991,"EUR_per_kWh":0.03,"EXR":10.997148,"time_start":"2025-09-04T06:00:00+02:00","time_end":"2025-09-04T07:00:00+02:00"},{"SEK_per_kWh":0.47123,"EUR_per_kWh":0.04285,"EXR":10.997148,"time_start":"2025-09-04T07:00:00+02:00","time_end":"2025-09-04T08:00:00+02:00"},{"SEK_per_kWh":0.68182,"EUR_per_kWh":0.062,"EXR":10.997148,"time_start":"2025-09-04T08:00:00+02:00","time_end":"2025-09-04T09:00:00+02:00"},{"SEK_per_kWh":0.4125,"EUR_per_kWh":0.03751,"EXR":10.997148,"time_start":"2025-09-04T09:00:00+02:00","time_end":"2025-09-04T10:00:00+02:00"},{"SEK_per_kWh":0.29571,"EUR_per_kWh":0.02689,"EXR":10.997148,"time_start":"2025-09-04T10:00:00+02:00","time_end":"2025-09-04T11:00:00+02:00"},{"SEK_per_kWh":0.06136,"EUR_per_kWh":0.00558,"EXR":10.997148,"time_start":"2025-09-04T11:00:00+02:00","time_end":"2025-09-04T12:00:00+02:00"},{"SEK_per_kWh":0.03662,"EUR_per_kWh":0.00333,"EXR":10.997148,"time_start":"2025-09-04T12:00:00+02:00","time_end":"2025-09-04T13:00:00+02:00"},{"SEK_per_kWh":0.0375,"EUR_per_kWh":0.00341,"EXR":10.997148,"time_start":"2025-09-04T13:00:00+02:00","time_end":"2025-09-04T14:00:00+02:00"},{"SEK_per_kWh":0.26822,"EUR_per_kWh":0.02439,"EXR":10.997148,"time_start":"2025-09-04T14:00:00+02:00","time_end":"2025-09-04T15:00:00+02:00"},{"SEK_per_kWh":0.30429,"EUR_per_kWh":0.02767,"EXR":10.997148,"time_start":"2025-09-04T15:00:00+02:00","time_end":"2025-09-04T16:00:00+02:00"},{"SEK_per_kWh":0.36675,"EUR_per_kWh":0.03335,"EXR":10.997148,"time_start":"2025-09-04T16:00:00+02:00","time_end":"2025-09-04T17:00:00+02:00"},{"SEK_per_kWh":0.58296,"EUR_per_kWh":0.05301,"EXR":10.997148,"time_start":"2025-09-04T17:00:00+02:00","time_end":"2025-09-04T18:00:00+02:00"},{"SEK_per_kWh":0.92145,"EUR_per_kWh":0.08379,"EXR":10.997148,"time_start":"2025-09-04T18:00:00+02:00","time_end":"2025-09-04T19:00:00+02:00"},{"SEK_per_kWh":1.5054,"EUR_per_kWh":0.13689,"EXR":10.997148,"time_start":"2025-09-04T19:00:00+02:00","time_end":"2025-09-04T20:00:00+02:00"},{"SEK_per_kWh":1.00888,"EUR_per_kWh":0.09174,"EXR":10.997148,"time_start":"2025-09-04T20:00:00+02:00","time_end":"2025-09-04T21:00:00+02:00"},{"SEK_per_kWh":0.63179,"EUR_per_kWh":0.05745,"EXR":10.997148,"time_start":"2025-09-04T21:00:00+02:00","time_end":"2025-09-04T22:00:00+02:00"},{"SEK_per_kWh":0.56382,"EUR_per_kWh":0.05127,"EXR":10.997148,"time_start":"2025-09-04T22:00:00+02:00","time_end":"2025-09-04T23:00:00+02:00"},{"SEK_per_kWh":0.52951,"EUR_per_kWh":0.04815,"EXR":10.997148,"time_start":"2025-09-04T23:00:00+02:00","time_end":"2025-09-05T00:00:00+02:00"}]"""; // 2. Set the mock response using the static method. - ElpriserAPI.setMockResponse(fakeJson); + LocalDate today = LocalDate.of(2025, 9, 4); + ElpriserAPI.setMockResponseForDate(today,fakeJson); // 3. Create an instance of the class as a student would. ElpriserAPI api = new ElpriserAPI(false); // Disable caching for predictable tests @@ -109,7 +110,8 @@ void displayMeanPrice_withValidData() { {"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"}, {"SEK_per_kWh":0.40,"EUR_per_kWh":0.04,"EXR":10.0,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"}]"""; - ElpriserAPI.setMockResponse(mockJson); + LocalDate today = LocalDate.of(2025, 9, 4); + ElpriserAPI.setMockResponseForDate(today,mockJson); Main.main(new String[]{"--zone", "SE3", "--date", "2025-09-04"}); @@ -127,7 +129,8 @@ void displayMinMaxPrices_withValidData() { {"SEK_per_kWh":0.80,"EUR_per_kWh":0.08,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"}, {"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"}]"""; - ElpriserAPI.setMockResponse(mockJson); + LocalDate today = LocalDate.of(2025, 9, 4); + ElpriserAPI.setMockResponseForDate(today,mockJson); Main.main(new String[]{"--zone", "SE1", "--date", "2025-09-04"}); @@ -144,13 +147,22 @@ void displayMinMaxPrices_withValidData() { @Test void displaySortedPrices_whenRequested() { - String mockJson = """ - [{"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"}, - {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T01:00:00+02:00","time_end":"2025-09-04T02:00:00+02:00"}, - {"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"}, - {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"}]"""; + // This test ensures charging window can span days when next day data exists + LocalDate today = LocalDate.of(2025, 9, 4); + LocalDate tomorrow = today.plusDays(1); - ElpriserAPI.setMockResponse(mockJson); + String mockJsonToday = """ + [{"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T20:00:00+02:00","time_end":"2025-09-04T21:00:00+02:00"}, + {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T21:00:00+02:00","time_end":"2025-09-04T22:00:00+02:00"}, + {"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T22:00:00+02:00","time_end":"2025-09-04T23:00:00+02:00"}, + {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T23:00:00+02:00","time_end":"2025-09-04T00:00:00+02:00"}]"""; + String mockJsonTomorrow = """ + [{"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-05T00:00:00+02:00","time_end":"2025-09-05T01:00:00+02:00"}, + {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-05T01:00:00+02:00","time_end":"2025-09-05T02:00:00+02:00"}, + {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-05T02:00:00+02:00","time_end":"2025-09-05T03:00:00+02:00"}]"""; + + ElpriserAPI.setMockResponseForDate(today, mockJsonToday); + ElpriserAPI.setMockResponseForDate(tomorrow, mockJsonTomorrow); Main.main(new String[]{"--zone", "SE2", "--date", "2025-09-04", "--sorted"}); @@ -158,10 +170,13 @@ void displaySortedPrices_whenRequested() { // Expected sorted output (ascending by price) List expectedOrder = List.of( - "01-02 10,00 öre", - "03-04 10,00 öre", - "02-03 20,00 öre", - "00-01 30,00 öre" + "20-21 30,00 öre", + "22-23 20,00 öre", + "01-02 15,00 öre", + "02-03 15,00 öre", + "21-22 10,00 öre", + "23-00 10,00 öre", + "00-01 10,00 öre" ); // Extract actual lines that match the pattern @@ -183,7 +198,9 @@ void findOptimalCharging2Hours() { {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-04T03:00:00+02:00","time_end":"2025-09-04T04:00:00+02:00"}, {"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T04:00:00+02:00","time_end":"2025-09-04T05:00:00+02:00"}]"""; - ElpriserAPI.setMockResponse(mockJson); + LocalDate today = LocalDate.of(2025, 9, 4); + + ElpriserAPI.setMockResponseForDate(today, mockJson); Main.main(new String[]{"--zone", "SE3", "--date", "2025-09-04", "--charging", "2h"}); @@ -204,7 +221,9 @@ void findOptimalCharging4Hours() { {"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T04:00:00+02:00","time_end":"2025-09-04T05:00:00+02:00"}, {"SEK_per_kWh":0.30,"EUR_per_kWh":0.03,"EXR":10.0,"time_start":"2025-09-04T05:00:00+02:00","time_end":"2025-09-04T06:00:00+02:00"}]"""; - ElpriserAPI.setMockResponse(mockJson); + LocalDate today = LocalDate.of(2025, 9, 4); + + ElpriserAPI.setMockResponseForDate(today, mockJson); Main.main(new String[]{"--zone", "SE1", "--date", "2025-09-04", "--charging", "4h"}); @@ -223,7 +242,9 @@ void chargingWindowDoesNotUseNextDay_whenNextDayUnavailable() { [{"SEK_per_kWh":0.20,"EUR_per_kWh":0.02,"EXR":10.0,"time_start":"2025-09-04T00:00:00+02:00","time_end":"2025-09-04T01:00:00+02:00"}, {"SEK_per_kWh":0.10,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-04T01:00:00+02:00","time_end":"2025-09-04T02:00:00+02:00"}, {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-04T02:00:00+02:00","time_end":"2025-09-04T03:00:00+02:00"}]"""; - ElpriserAPI.setMockResponse(mockJsonToday); + LocalDate today = LocalDate.of(2025, 9, 4); + ElpriserAPI.setMockResponseForDate(today,mockJsonToday); + Main.main(new String[]{"--zone", "SE3", "--date", "2025-09-04", "--charging", "2h"}); String output = bos.toString(); // Best 2h window should be 01-03 (0.10 + 0.15) @@ -235,7 +256,7 @@ void chargingWindowDoesNotUseNextDay_whenNextDayUnavailable() { void findOptimalCharging8Hours() { // Create mock data with 12 hours to allow for 8-hour window StringBuilder jsonBuilder = new StringBuilder("["); - double[] prices = {0.50, 0.10, 0.05, 0.15, 0.08, 0.12, 0.06, 0.09, 0.25, 0.30, 0.35, 0.40}; + double[] prices = {0.50, 0.10, 0.05, 0.15, 0.08, 0.12, 0.06, 0.09, 0.25, 0.30, 0.35, 0.40, 0.50, 0.10, 0.05, 0.15, 0.08, 0.12, 0.06, 0.09, 0.25, 0.30, 0.35, 0.40}; for (int i = 0; i < prices.length; i++) { if (i > 0) jsonBuilder.append(","); @@ -243,12 +264,20 @@ void findOptimalCharging8Hours() { Locale.US, """ {"SEK_per_kWh":%.2f,"EUR_per_kWh":%.3f,"EXR":10.0,"time_start":"2025-09-04T%02d:00:00+02:00","time_end":"2025-09-04T%02d:00:00+02:00"}""", - prices[i], prices[i] / 10, i, i + 1 + prices[i], prices[i] / 10, i, (i + 1) % 24 )); } jsonBuilder.append("]"); - ElpriserAPI.setMockResponse(jsonBuilder.toString()); + LocalDate today = LocalDate.of(2025, 9, 4); + ElpriserAPI.setMockResponseForDate(today, jsonBuilder.toString()); + + LocalDate tomorrow = today.plusDays(1); + String mockJsonTomorrow = """ + [{"SEK_per_kWh":0.1,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"2025-09-05T00:00:00+02:00","time_end":"2025-09-05T01:00:00+02:00"}, + {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-05T01:00:00+02:00","time_end":"2025-09-05T02:00:00+02:00"}, + {"SEK_per_kWh":0.15,"EUR_per_kWh":0.015,"EXR":10.0,"time_start":"2025-09-05T02:00:00+02:00","time_end":"2025-09-05T03:00:00+02:00"}]"""; + ElpriserAPI.setMockResponseForDate(tomorrow, mockJsonTomorrow); Main.main(new String[]{"--zone", "SE4", "--date", "2025-09-04", "--charging", "8h"}); @@ -359,66 +388,63 @@ void chargingWindowSpansToNextDay_whenCheapestCrossesMidnight() { } @Test - public void testHourlyMinMaxPrices() { - List quarterHourPrices = new ArrayList<>(); - - // Simulate 96 prices: 24 hours, each with 4 quarter-hour prices - for (int i = 0; i < 96; i++) { - quarterHourPrices.add((double) (i % 24)); // repeating hourly pattern - } + void testHourlyMinMaxPrices_with96Entries() { + // --- ARRANGE --- + LocalDate today = LocalDate.of(2025, 9, 4); + StringBuilder jsonBuilder = new StringBuilder("["); - // Expected hourly averages - List hourlyAverages = new ArrayList<>(); - for (int i = 0; i < 24; i++) { - double sum = 0; - for (int j = 0; j < 4; j++) { - sum += quarterHourPrices.get(i * 4 + j); + for (int hour = 0; hour < 24; hour++) { + for (int quarter = 0; quarter < 4; quarter++) { + if (hour > 0 || quarter > 0) { + jsonBuilder.append(","); + } + double price = (hour * 0.1) + (quarter * 0.01) + 0.10; + String time_start = String.format("2025-09-04T%02d:%02d:00+02:00", hour, quarter * 15); + String time_end = String.format("2025-09-04T%02d:%02d:00+02:00", hour, (quarter + 1) * 15); + if (quarter == 3) { // Handle end of hour + time_end = String.format("2025-09-04T%02d:00:00+02:00", (hour + 1) % 24); + } + + jsonBuilder.append(String.format(Locale.US, + """ + {"SEK_per_kWh":%.4f,"EUR_per_kWh":0.01,"EXR":10.0,"time_start":"%s","time_end":"%s"}""", + price, time_start, time_end)); } - hourlyAverages.add(sum / 4.0); } + jsonBuilder.append("]"); + ElpriserAPI.setMockResponseForDate(today, jsonBuilder.toString()); - double expectedMin = Collections.min(hourlyAverages); - double expectedMax = Collections.max(hourlyAverages); + // --- ACT --- + Main.main(new String[]{"--zone", "SE3", "--date", "2025-09-04"}); - // Call your method under test - PriceRange result = PriceCalculator.calculateHourlyMinMax(quarterHourPrices); + // --- ASSERT --- + String output = bos.toString(); + assertThat(output).containsIgnoringCase("lägsta pris"); + assertThat(output).containsIgnoringCase("högsta pris"); + assertThat(output).containsIgnoringCase("medelpris"); - assertThat(result.getMin()).isCloseTo(expectedMin, within(0.001)); - assertThat(result.getMax()).isCloseTo(expectedMax, within(0.001)); + // Expected Min: Hour 0 -> avg(0.10, 0.11, 0.12, 0.13) = 0.115 SEK/kWh = 11,50 öre + // Expected Max: Hour 23 -> avg(2.40, 2.41, 2.42, 2.43) = 2.415 SEK/kWh = 241,50 öre + assertThat(output).contains("00-01"); // Cheapest hour + assertThat(output).contains("23-00"); // Most expensive hour + assertThat(output).contains(formatOre(0.115)); + assertThat(output).contains(formatOre(2.415)); + + // Calculate overall average for the day + double totalSum = 0; + for (int hour = 0; hour < 24; hour++) { + for (int quarter = 0; quarter < 4; quarter++) { + totalSum += (hour * 0.1) + (quarter * 0.01) + 0.10; + } + } + double expectedMean = totalSum / 96; + assertThat(output).contains("Medelpris: " + formatOre(expectedMean) + " öre"); } private String formatOre(double sekPerKWh) { double ore = sekPerKWh * 100.0; - DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(Locale.of("sv", "SE")); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(new Locale("sv", "SE")); DecimalFormat df = new DecimalFormat("0.00", symbols); return df.format(ore); } -} -class PriceRange { - private final double min; - private final double max; - - public PriceRange(double min, double max) { - this.min = min; - this.max = max; - } - - public double getMin() { return min; } - public double getMax() { return max; } -} - -class PriceCalculator { - public static PriceRange calculateHourlyMinMax(List quarterHourPrices) { - List hourlyAverages = new ArrayList<>(); - for (int i = 0; i < 24; i++) { - double sum = 0; - for (int j = 0; j < 4; j++) { - sum += quarterHourPrices.get(i * 4 + j); - } - hourlyAverages.add(sum / 4.0); - } - double min = Collections.min(hourlyAverages); - double max = Collections.max(hourlyAverages); - return new PriceRange(min, max); - } } \ No newline at end of file From 308fbec478d954b350a1ed09c9d151a45f54ce4e Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Fri, 26 Sep 2025 12:20:25 +0200 Subject: [PATCH 3/4] my own notes --- planering.txt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 planering.txt diff --git a/planering.txt b/planering.txt new file mode 100644 index 00000000..6bccdc03 --- /dev/null +++ b/planering.txt @@ -0,0 +1,23 @@ +---------- Planering för uppgiften ---------- +KLAR - hämta priser idag: LocalDate.now() +KLAR - hämta priser imorgon om möjligt: LocalDate.now.plusDays(1) - BARA OM DATAN FINNS annars returnerar 404. + +KLAR - Hämta priser för idag och imorgon (om det finns) +KLAR - Slå ihop listorna om båda finns. Om bara IDAG finns kör vi på den. + +KLAR - Räkna ut medelvärdet för alla priser som vi hämtat (oftast 24h) +KLAR - Om priserna för imorgon finns med, ta med dom också. +KLAR - Summera alla sekPerKwh från listan och dela på antalet element. +KLAR - Skriv ut medelpriset i öre -> pris.sekPerKwh() * 100 +KLAR - Hitta billigaste och dyraste priset + +Ska räkna ut när det är bäst att ladda bilen beroende på hur lång tid som behövs laddas (2h, 4h, 8h) +Sliding window: Titta på alla möjliga fönster av tex 2timmar i följd osv. +Räkna ut summan (medelpriset) för de timmarna. +Flytta fönstret en timme i taget och räkna igen. +Välj det fönster som har lägst total kostnad. + + +Zone ska gå att skicka som command-line. + +OBS: Skriva ut medelpris i öre?? From c075eb8cddd94d21935fa35740698e9783a95b2e Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Fri, 26 Sep 2025 12:22:21 +0200 Subject: [PATCH 4/4] Implement Main.java with CLI arguments, zone selection, sorting and charging window logic --- src/main/java/com/example/Main.java | 211 +++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index 20a692ac..61ad0c96 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -1,9 +1,216 @@ package com.example; - import com.example.api.ElpriserAPI; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.*; +import java.time.LocalDate; +import java.util.stream.Collectors; + public class Main { public static void main(String[] args) { - ElpriserAPI elpriserAPI = new ElpriserAPI(); + + // command-line arguments + String zoneArg = null; + String dateArg = null; + boolean sorted = false; + int chargingHours = 0; + boolean help = false; + + for(int i = 0; i < args.length; i++){ + switch (args[i]){ + case "--zone" -> zoneArg = args[i + 1]; + case "--date" -> dateArg = args[i + 1]; + case "--sorted" -> sorted = true; + case "--charging" -> chargingHours = Integer.parseInt(args[i + 1].replace("h", "")); + case "--help" -> help = true; + } + } + + if (help) { + printHelp(); + return; + } + + // add interactive prompt? + if (zoneArg == null) { + printHelp(); + return; + } + + + + + ElpriserAPI.Prisklass zone; + try { + zone = ElpriserAPI.Prisklass.valueOf(zoneArg.toUpperCase()); + } catch (Exception e) { + System.out.println("Ogiltig zon: " + zoneArg); + return; + } + + LocalDate baseDate; + if(dateArg != null){ + try { + baseDate = LocalDate.parse(dateArg); + } catch (Exception e){ + System.out.println("Ogiltigt datum: " + dateArg); + return; + } + } else { + baseDate = LocalDate.now(); + } + + + // Get prices from api + ElpriserAPI api = new ElpriserAPI(); + List todayPrices = api.getPriser(baseDate, zone); + List tomorrowPrices = api.getPriser(baseDate.plusDays(1), zone); + + List allPrices = new ArrayList<>(todayPrices); + if(!tomorrowPrices.isEmpty()){ + allPrices.addAll(tomorrowPrices); + } + + if(allPrices.isEmpty()){ + System.out.println("Inga priser tillgängliga"); + return; + } + + // collect all prices by theiir starting hour. + Map> groupedByHour = allPrices.stream() + .collect(Collectors.groupingBy( + p -> p.timeStart().getHour(), + TreeMap::new, + Collectors.toList() + )); + + List hourlyAverages = new ArrayList<>(); + for (var entry : groupedByHour.entrySet()) { + List hourPrices = entry.getValue(); + + double avgSekPerKWh = hourPrices.stream() + .mapToDouble(ElpriserAPI.Elpris::sekPerKWh) + .average().orElse(0); + + ElpriserAPI.Elpris first = hourPrices.get(0); + + // adds a new object with the average price from all values within an hour + hourlyAverages.add(new ElpriserAPI.Elpris( + avgSekPerKWh, + first.eurPerKWh(), + first.exr(), + first.timeStart(), + first.timeEnd() + )); + } + + // Format to swedish öre + DecimalFormatSymbols symbols = new DecimalFormatSymbols(new Locale("sv", "SE")); + DecimalFormat oreFormat = new DecimalFormat("#0.00", symbols); + oreFormat.setMaximumFractionDigits(2); + oreFormat.setMinimumFractionDigits(2); + oreFormat.setGroupingUsed(false); + + List toPrint = sorted ? new ArrayList<>(allPrices) : allPrices; + if (sorted) { + toPrint.sort(Comparator.comparing(ElpriserAPI.Elpris::sekPerKWh) + .reversed() + .thenComparing(ElpriserAPI.Elpris::timeStart)); + } + + for (ElpriserAPI.Elpris price : toPrint) { + String ore = oreFormat.format(price.sekPerKWh() * 100); + int startHour = price.timeStart().getHour(); + int endHour = price.timeEnd().getHour(); + if (endHour == startHour) { + endHour = (endHour + 1) % 24; + } + System.out.printf("%02d-%02d %s öre%n", startHour, endHour, ore); + } + + + double averageSekPerKWh = hourlyAverages.stream().mapToDouble(ElpriserAPI.Elpris::sekPerKWh) + .average().orElse(0); + System.out.printf("Medelpris: %s öre%n", oreFormat.format(averageSekPerKWh * 100)); + + + // least expensive hour (then earliest) + ElpriserAPI.Elpris minPrice = hourlyAverages.stream().min(Comparator + .comparing(ElpriserAPI.Elpris::sekPerKWh) + .thenComparing(ElpriserAPI.Elpris::timeStart)) + .orElse(null); + + if (minPrice != null) { + String ore = oreFormat.format(minPrice.sekPerKWh() * 100); + int startHour = minPrice.timeStart().getHour(); + int endHour = minPrice.timeEnd().getHour(); + if (endHour == startHour) { + endHour = (endHour + 1) % 24; + } + System.out.printf("Lägsta pris: %s öre (%02d-%02d)%n", ore, startHour, endHour); + } + + + // most expensive hour (then earliest) + ElpriserAPI.Elpris maxPrice = hourlyAverages.stream() + .max(Comparator.comparing(ElpriserAPI.Elpris::sekPerKWh) + .thenComparing(ElpriserAPI.Elpris::timeStart)) + .orElse(null); + + if (maxPrice != null) { + String ore = oreFormat.format(maxPrice.sekPerKWh() * 100); + int startHour = maxPrice.timeStart().getHour(); + int endHour = maxPrice.timeEnd().getHour(); + if (endHour == startHour) { + endHour = (endHour + 1) % 24; + } + System.out.printf("Högsta pris: %s öre (%02d-%02d)%n", ore, startHour, endHour); + } + + + + // sliding window + if(chargingHours > 0){ + int windowSize = chargingHours; // 2, 4 or 8 + double bestSum = Double.MAX_VALUE; + int bestStartIndex = -1; + + for(int i = 0; i <= hourlyAverages.size() - windowSize; i++){ + double currentSum = 0; + + for(int j = i; j < i + windowSize; j++){ + currentSum += allPrices.get(j).sekPerKWh(); + } + + if(currentSum < bestSum){ + bestSum = currentSum; + bestStartIndex = i; + } + } + + if(bestStartIndex != -1){ + double average = bestSum / windowSize; + + ElpriserAPI.Elpris startHour = allPrices.get(bestStartIndex); + ElpriserAPI.Elpris endHour = allPrices.get(bestStartIndex + windowSize -1); + + System.out.printf("Påbörja laddning kl %s%n", startHour.timeStart().toLocalTime()); + System.out.printf("Medelpris för fönster: %s öre%n", oreFormat.format(average * 100)); + System.out.printf("Fönster: %02d-%02d%n", + startHour.timeStart().getHour(), + endHour.timeEnd().getHour()); + + } + } + } + // help + private static void printHelp(){ + System.out.println("Usage: java -cp target/classes com.example.Main --zone SE1|SE2|SE3|SE4 [options]"); + System.out.println("Options:"); + System.out.println(" --date YYYY-MM-DD Ange datum (default = idag)"); + System.out.println(" --sorted Visa priser i stigande ordning"); + System.out.println(" --charging 2h|4h|8h Hitta optimalt laddfönster"); + System.out.println(" --help Visa denna hjälptext"); } }