Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
43dd077
Setting up GitHub Classroom Feedback
github-classroom[bot] Sep 9, 2025
22cefb5
Uppdaterade ElpriserAPI men den nya varianten från teams och la till …
gitnes94 Sep 26, 2025
18a420d
La till ArgParser fil för att hantera information från terminalen.
gitnes94 Sep 26, 2025
ae6f7ca
Testversion
gitnes94 Sep 28, 2025
ca527d4
Ändrade pom.xml java version till en äldre variant för att den inte v…
gitnes94 Sep 28, 2025
f7e9ba7
Ändrade pom.xml java version till en äldre variant för att den inte v…
gitnes94 Sep 28, 2025
7645001
Ändrade pom.xml java version till en äldre variant för att den inte v…
gitnes94 Sep 28, 2025
dea2f00
Ändrade pom.xml java version till en äldre variant för att den inte v…
gitnes94 Sep 28, 2025
807121e
Ändrade pom.xml java version till en äldre variant för att den inte v…
gitnes94 Sep 28, 2025
5e2ecea
Ändrade pom.xml java version till en äldre variant för att den inte v…
gitnes94 Sep 28, 2025
967ca1f
v2
gitnes94 Sep 28, 2025
537728e
v2
gitnes94 Sep 28, 2025
749a4ec
v3
gitnes94 Sep 28, 2025
cf88b71
v3
gitnes94 Sep 28, 2025
a0e5dfd
v3
gitnes94 Sep 28, 2025
2026890
v3
gitnes94 Sep 28, 2025
759b269
v3
gitnes94 Sep 28, 2025
0b24dd8
v3
gitnes94 Sep 28, 2025
5fbfba8
v3
gitnes94 Sep 28, 2025
654e408
v3
gitnes94 Sep 28, 2025
9296094
v3
gitnes94 Sep 29, 2025
422d349
v3
gitnes94 Sep 29, 2025
1c28387
-v4
gitnes94 Sep 29, 2025
30aeffd
-v4
gitnes94 Sep 29, 2025
9e68369
-v4
gitnes94 Sep 29, 2025
05621e8
-v4
gitnes94 Sep 29, 2025
095fcd5
-v4
gitnes94 Sep 29, 2025
0a98727
-v4
gitnes94 Sep 29, 2025
a653ce6
-v4
gitnes94 Sep 29, 2025
ad29f77
-v4
gitnes94 Sep 29, 2025
da12bf3
-v4
gitnes94 Sep 29, 2025
472ef60
-v4
gitnes94 Sep 29, 2025
94a7c3f
-v4
gitnes94 Sep 29, 2025
d2d0f84
-v4
gitnes94 Sep 29, 2025
9aa8503
-v4
gitnes94 Sep 29, 2025
bcb1b6d
-v4
gitnes94 Sep 29, 2025
8d13d52
-v4
gitnes94 Sep 29, 2025
2c0c3a3
-v4
gitnes94 Sep 29, 2025
ab4c004
-v4
gitnes94 Sep 29, 2025
374ef66
-v4
gitnes94 Sep 29, 2025
2e0f1cc
-v4
gitnes94 Sep 29, 2025
5c73d83
-v4
gitnes94 Sep 29, 2025
c21274a
-v4
gitnes94 Sep 29, 2025
0bbb691
-v4
gitnes94 Sep 29, 2025
7424c94
-v4
gitnes94 Sep 29, 2025
bd116d9
-v4
gitnes94 Sep 29, 2025
1c5af19
Tog bort min argparser
gitnes94 Sep 29, 2025
a6e9d74
Tog bort min argparser
gitnes94 Sep 29, 2025
568cd83
Tog bort min argparser
gitnes94 Sep 29, 2025
321f8e8
logik för kvart intervall
gitnes94 Oct 2, 2025
32f8e4e
logik för kvart intervall
gitnes94 Oct 2, 2025
588fcc6
- refactoring
gitnes94 Oct 2, 2025
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
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.release>24</maven.compiler.release>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.jupiter.version>5.13.4</junit.jupiter.version>
<assertj.core.version>3.27.4</assertj.core.version>
<mockito.version>5.19.0</mockito.version>
<assertj.core.version>3.27.6</assertj.core.version>
<mockito.version>5.20.0</mockito.version>
</properties>
<dependencies>
<dependency>
Expand Down
216 changes: 214 additions & 2 deletions src/main/java/com/example/Main.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,221 @@
package com.example;

import com.example.api.ElpriserAPI;
import com.example.api.ElpriserAPI.Elpris;
import com.example.api.ElpriserAPI.Prisklass;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

public class Main {

public static void main(String[] args) {
ElpriserAPI elpriserAPI = new ElpriserAPI();

if (args.length == 0 || (args.length == 1 && args[0].equals("--help"))) {
printHelp();
return;
}

String zone = null;
String dateStr = null;
boolean sorted = false;
int chargingHours = 0;

//Hantera CLI argument

for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "--zone" -> {
if (i + 1 < args.length) zone = args[++i];
}
case "--date" -> {
if (i + 1 < args.length) dateStr = args[++i];
}
case "--sorted" -> sorted = true;
case "--charging" -> {
if (i + 1 < args.length) {
String argValue = args[++i];
try {
if (argValue.endsWith("h")) {
chargingHours = Integer.parseInt(argValue.substring(0, argValue.length() - 1));
} else {
chargingHours = Integer.parseInt(argValue);
}
} catch (NumberFormatException e) {
System.out.println("Fel: Ogiltigt format för --charging. Använd t.ex. 2h, 4h eller 8h.");
return;
}
}
}
case "--help" -> {
printHelp();
return;
}
}
}

if (zone == null) {
System.out.println("Fel: Argumentet --zone är obligatoriskt.");
printHelp();
return;
}

Prisklass prisklass;
try {
prisklass = Prisklass.valueOf(zone.toUpperCase());
} catch (IllegalArgumentException e) {
System.out.println("Fel: Ogiltig zon. Använd SE1, SE2, SE3 eller SE4.");
return;
}

LocalDate datum = LocalDate.now();
if (dateStr != null) {
try {
datum = LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE);
} catch (DateTimeParseException e) {
System.out.println("Fel: Ogiltigt datumformat. Använd YYYY-MM-DD.");
return;
}
}

//Använda mock data eller ta fram riktiga priser

ElpriserAPI api = new ElpriserAPI(false);

List<Elpris> priser = new ArrayList<>(api.getPriser(datum, prisklass));

if (chargingHours > 0) {
priser.addAll(api.getPriser(datum.plusDays(1), prisklass));
}

if (priser.isEmpty()) {
System.out.println("Ingen data tillgänglig för zon: " + zone + " datum: " + datum);
return;
}

DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.forLanguageTag("sv-SE"));
symbols.setDecimalSeparator(',');
DecimalFormat df = new DecimalFormat("#0.00", symbols);

DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("HH");

if (chargingHours > 0) {
if (chargingHours < 2 || chargingHours > 8) {
System.out.println("Fel: Laddningsfönster måste vara 2h, 4h eller 8h.");
return;
}

if (chargingHours > priser.size()) {
System.out.println("Fel: Kan inte ladda längre än " + priser.size() + " tillgängliga timmar.");
return;
}

double minSum = Double.MAX_VALUE;
int bestStart = -1;

for (int i = 0; i <= priser.size() - chargingHours; i++) {
double currentSum = 0;
for (int j = 0; j < chargingHours; j++) {
currentSum += priser.get(i + j).sekPerKWh();
}

if (currentSum < minSum) {
minSum = currentSum;
bestStart = i;
}
}

if (bestStart == -1) {
System.out.println("Kunde inte hitta ett optimalt laddningsfönster.");
return;
}

Elpris start = priser.get(bestStart);
Elpris end = priser.get(bestStart + chargingHours - 1);

double totalCostOre = minSum * 100;
double avgOre = totalCostOre / chargingHours;

System.out.println("Påbörja laddning");

System.out.println("Optimalt laddningsfönster (" + chargingHours + "h):");
System.out.println("Starttid: kl " + start.timeStart().format(timeFormatter));
System.out.println("Sluttid: kl " + end.timeEnd().format(timeFormatter));
System.out.println("Total kostnad: " + df.format(totalCostOre) + " öre");
System.out.println("Medelpris för fönster: " + df.format(avgOre) + " öre");

return;
}

List<Double> priserOre = new ArrayList<>();
double minPrice = Double.MAX_VALUE;
double maxPrice = Double.MIN_VALUE;
double sumPrice = 0.0;

for (Elpris pris : priser) {
double ore = pris.sekPerKWh() * 100;
priserOre.add(ore);

if (ore < minPrice) {
minPrice = ore;
}
if (ore > maxPrice) {
maxPrice = ore;
}
sumPrice += ore;
}

double min = minPrice;
double max = maxPrice;
double avg = priserOre.isEmpty() ? 0.0 : sumPrice / priserOre.size();

System.out.println("\nElpriser för " + prisklass + " den " + datum.format(DateTimeFormatter.ISO_DATE) + ":");
System.out.println("----------------------------------------");

List<Elpris> priserForDisplay = new ArrayList<>(priser);
if (sorted) {
priserForDisplay.sort(Comparator.comparingDouble(Elpris::sekPerKWh));
}

for (Elpris pris : priserForDisplay) {
String startHour = pris.timeStart().format(hourFormatter);
String endHour = pris.timeEnd().format(hourFormatter);

String timeRange = startHour + "-" + endHour;
double ore = pris.sekPerKWh() * 100;
System.out.println(timeRange + " " + df.format(ore) + " öre");
}

System.out.println("----------------------------------------");
System.out.println("Lägsta pris: " + df.format(min) + " öre");
System.out.println("Högsta pris: " + df.format(max) + " öre");
System.out.println("Medelpris: " + df.format(avg) + " öre");
}

private static void printHelp() {
System.out.println("""
⚡ Electricity Price Optimizer CLI

Hjälper dig optimera energianvändningen baserat på timpriser.

Användning (usage):
java -cp target/classes com.example.Main --zone SE3 --date 2025-09-29
java -cp target/classes com.example.Main --zone SE1 --charging 4h

Argument:
--zone SE1|SE2|SE3|SE4 (obligatoriskt) Välj elprisområde.
--date YYYY-MM-DD (valfritt, standard = idag) Datum att hämta priser för.
--sorted (valfritt) Visar prislistan sorterad från billigast till dyrast.
--charging 2h|4h|8h (valfritt) Hittar de billigaste N sammanhängande timmarna för laddning.
--help (valfritt) Visar denna hjälp.
""");
}
}
}
52 changes: 26 additions & 26 deletions src/main/java/com/example/api/ElpriserAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public final class ElpriserAPI {

// En återanvändbar HttpClient-instans
private final HttpClient httpClient;

// Flagga för att styra cachlagring
private final boolean cachingEnabled;

// Ett enkelt minnes-cache. Nyckeln är en kombination av datum och prisklass, t.ex. "2025-08-30_SE3"
private final Map<String, List<Elpris>> inMemoryCache;

Expand All @@ -39,11 +39,11 @@ public final class ElpriserAPI {
* Användningen av 'record' genererar automatiskt constructor, getters, equals, hashCode och toString.
*/
public record Elpris(
double sekPerKWh,
double eurPerKWh,
double exr,
ZonedDateTime timeStart,
ZonedDateTime timeEnd
double sekPerKWh,
double eurPerKWh,
double exr,
ZonedDateTime timeStart,
ZonedDateTime timeEnd
) {}

/**
Expand All @@ -59,7 +59,7 @@ public enum Prisklass {
* use the String it provides instead of making a real HTTP call.
*/
private static Supplier<String> mockResponseSupplier = null;

// New: map mock responses per date, so tests can provide different JSON per day
private static java.util.Map<LocalDate, String> datedMockResponses = new java.util.HashMap<>();

Expand All @@ -71,7 +71,7 @@ public enum Prisklass {
public static void setMockResponse(String jsonResponse) {
mockResponseSupplier = () -> jsonResponse;
}

/**
* FOR TESTS ONLY: Sets a mock JSON response for a specific date. This allows
* tests to simulate availability for one day but not another.
Expand Down Expand Up @@ -152,9 +152,9 @@ public List<Elpris> getPriser(LocalDate datum, Prisklass prisklass) {
// Steg 2: Försök ladda från disk-cache (framtida implementation)
var priserFrånDisk = loadFromDiskCache(cacheKey);
if (cachingEnabled && priserFrånDisk != null && !priserFrånDisk.isEmpty()) {
System.out.println("Hämtar från disk-cache för " + cacheKey);
inMemoryCache.put(cacheKey, priserFrånDisk); // Lägg i minnes-cachen för snabbare åtkomst nästa gång
return priserFrånDisk;
System.out.println("Hämtar från disk-cache för " + cacheKey);
inMemoryCache.put(cacheKey, priserFrånDisk); // Lägg i minnes-cachen för snabbare åtkomst nästa gång
return priserFrånDisk;
}

// Check for a mock response before making a network call ---
Expand Down Expand Up @@ -185,8 +185,8 @@ public List<Elpris> getPriser(LocalDate datum, Prisklass prisklass) {
return Collections.emptyList();
}
if (response.statusCode() != 200) {
System.err.println("Misslyckades med att hämta priser. Statuskod: " + response.statusCode());
return Collections.emptyList();
System.err.println("Misslyckades med att hämta priser. Statuskod: " + response.statusCode());
return Collections.emptyList();
}

List<Elpris> priser = parseSimpleJson(response.body());
Expand All @@ -212,7 +212,7 @@ private String buildUrl(LocalDate datum, Prisklass prisklass) {
String formattedDate = datum.format(URL_DATE_FORMATTER);
return String.format("%s/%s_%s.json", API_BASE_URL, formattedDate, prisklass.name());
}

private String getCacheKey(LocalDate datum, Prisklass prisklass) {
return datum.format(DateTimeFormatter.ISO_LOCAL_DATE) + "_" + prisklass.name();
}
Expand All @@ -239,7 +239,7 @@ private List<Elpris> parseSimpleJson(String json) {
for (String objStr : objects) {
// Rensa bort resterande { och }
String cleanObjStr = objStr.replace("{", "").replace("}", "");

try {
// Skapa en temporär map för att hålla värdena för ett objekt
Map<String, String> valueMap = new java.util.HashMap<>();
Expand All @@ -253,11 +253,11 @@ private List<Elpris> parseSimpleJson(String json) {

// Skapa ett Elpris-objekt från värdena i mappen
priser.add(new Elpris(
Double.parseDouble(valueMap.get("SEK_per_kWh")),
Double.parseDouble(valueMap.get("EUR_per_kWh")),
Double.parseDouble(valueMap.get("EXR")),
ZonedDateTime.parse(valueMap.get("time_start")),
ZonedDateTime.parse(valueMap.get("time_end"))
Double.parseDouble(valueMap.get("SEK_per_kWh")),
Double.parseDouble(valueMap.get("EUR_per_kWh")),
Double.parseDouble(valueMap.get("EXR")),
ZonedDateTime.parse(valueMap.get("time_start")),
ZonedDateTime.parse(valueMap.get("time_end"))
));
} catch (Exception e) {
// Hoppa över objekt som inte kan parsas, logga ett fel
Expand All @@ -266,9 +266,9 @@ private List<Elpris> parseSimpleJson(String json) {
}
return priser;
}

// --- Stub-metoder för disk-cache ---

/**
* STUB: Spara data till en fil i en dold katalog i användarens hemkatalog.
* Oimplementerad tills vidare.
Expand Down Expand Up @@ -314,9 +314,9 @@ public static void main(String[] args) {
} else {
System.out.println("\nDagens elpriser för " + Prisklass.SE3 + " (" + dagensPriser.size() + " st värden):");
// Skriv bara ut de 3 första för att hålla utskriften kort
dagensPriser.stream().limit(3).forEach(pris ->
System.out.printf("Tid: %s, Pris: %.4f SEK/kWh\n",
pris.timeStart().toLocalTime(), pris.sekPerKWh())
dagensPriser.stream().limit(3).forEach(pris ->
System.out.printf("Tid: %s, Pris: %.4f SEK/kWh\n",
pris.timeStart().toLocalTime(), pris.sekPerKWh())
);
if(dagensPriser.size() > 3) System.out.println("...");
}
Expand Down
Loading