This commit is contained in:
AndrewTrieu
2023-05-03 01:23:34 +03:00
parent cbe14c2277
commit 0fdaceb28a
2 changed files with 413 additions and 68 deletions

122
README.md
View File

@@ -1,3 +1,123 @@
# Renewable Energy Plant System Application # Renewable Energy Plant System Application
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, nisl quis tincidunt aliquam, nunc nisl ultricies nunc, ut aliquet quam nisl quis nunc. Sed euismod, nisl quis tincidunt aliquam, nunc nisl ultricies nunc, ut aliquet quam nisl quis nunc. Sed euismod, nisl quis tincidunt aliquam, nunc nisl ultricies nunc, ut aliquet quam nisl quis nunc. Sed euismod, nisl quis tincidunt aliquam, nunc nisl ultricies nunc, ut aliquet quam nisl quis nunc. Sed euismod, nisl quis tincidunt aliquam, nunc nisl ultricies nunc, ut aliquet quam nisl quis nunc. This is a Scala program to simulate a renewable energy plant.
## Use Cases
The program can be used to simulate the following use cases:
- The system is designed to monitor and control the power plant's renewable energy sources, including wind turbines, hydroelectric power plants, and nuclear power plants. The system can display and modify the number of active power plants.
```mermaid
sequenceDiagram
participant User
participant System
participant File
User->>System: Monitor power plants
System-->>User: Display number of active power plants
User->>System: Modify number of active power plants
System->>File: Update number of active power plants
```
- The system is capable of collecting data related to the energy generated by renewable sources and storing it in a file.
```mermaid
sequenceDiagram
participant User
participant System
participant Fingrid API
participant Files
User->>System: Collect data
System->>Fingrid API: Get data
Fingrid API-->>System: Return data
System->>Files: Store data in files
```
- The system provides a view of the power plant's energy generation and storage capacity, allowing operators to adjust the power plant's operation as needed. This view shows the data stored in the file.
```mermaid
sequenceDiagram
participant User
participant System
participant Files
User->>System: View data
System->>Files: Read data from files
System-->>User: Ask for filters and sorting
User->>System: Provide filters and sorting
System->>System: Filter and sort data
System-->>User: Display data
```
- The system can analyze the data collected from renewable sources. It can filter the data on an hourly, daily, weekly and monthly basis. It can sort the data where possible. User can search for required data stored in the system. Data Analysis includes:
- Mean
- Median
- Mode
- Range
- Midrange
```mermaid
sequenceDiagram
participant User
participant System
participant Files
User->>System: Analyze data
System->>Files: Read data from files
System-->>User: Ask for filters
System->>System: Filter data
System->>System: Analyze data
System-->>User: Display results
```
- The system should be able to detect and handle issues with renewable energy sources, such as low energy output, and generating alerts for the operators accordingly.
```mermaid
sequenceDiagram
participant User
participant System
participant Files
System->>Files: Read data from Files
System->>User: Alert user
```
## Class Diagram
```mermaid
classDiagram
Main <|-- Date
Main <|-- URL
Main <|-- HttpURLConnection
Main <|-- Paths
Main <|-- Files
Main <|-- SimpleDateFormat
Main <|-- Try
Main <|-- readLine
Main <|-- Source
Main <|-- ListMap
Main <|-- PrintWriter
class Main{
+String API_KEY
+Map[Int, String] dataSources
+String energySources
+Double alertThreshold
+collectData()
+modifySources(lines: List[String], newValue: String, sourceType: String)
+readSources(modificationFn: (List[String], String, String) => List[String])
+analyzeData(data: Map[Date, Double])
+compareTime(lastTimestamp: Date, data: List[String], filter: Int, format: SimpleDateFormat)
+filterData(data: List[String], compareFn: (Date, List[String], Int, SimpleDateFormat) => Map[Date, Double])
+readData()
+sortData(data: Map[Date, Double])
+printData(data: Map[Date, Double])
+searchData(data: List[String])
+alertUser()
+scanSystem(data: List[String])
}
```

View File

@@ -1,22 +1,36 @@
import java.net.{HttpURLConnection, URL} import java.net.{HttpURLConnection, URL}
import java.nio.file.{Files, Paths} import java.nio.file.{Files, Paths}
import java.util.Date
import java.text.SimpleDateFormat
import scala.util.Try import scala.util.Try
import scala.io.StdIn.readLine import scala.io.StdIn.readLine
import scala.io.Source import scala.io.Source
import scala.collection.immutable.ListMap
val API_KEY = Source.fromFile("apiKey.txt").getLines().mkString val API_KEY = Source.fromFile("apiKey.txt").getLines().mkString
val dataSources = Map(
188 -> "nuclear.csv",
191 -> "hydro.csv",
181 -> "wind.csv",
192 -> "data.csv"
)
val energySources = "sources.csv"
val alertThreshold = 1000.0
object Main { object Main {
// Collect data from the API // Collect data from the API
def collectData( def collectData(): Unit = {
dataSources: Map[Int, String]
): Unit = {
// Get the current time and truncate it to seconds // Get the current time and truncate it to seconds
val now = (java.time.LocalDateTime.now) val now = (java.time.LocalDateTime.now)
.truncatedTo(java.time.temporal.ChronoUnit.SECONDS) .truncatedTo(java.time.temporal.ChronoUnit.SECONDS)
// Collect data from the API // Collect data from the API
println("Collecting data...") println("Collecting data...")
// Iterate over the data sources, not following functional programming paradigm
for ((k, v) <- dataSources) { for ((k, v) <- dataSources) {
val url = new URL( val url = new URL(
s"https://api.fingrid.fi/v1/variable/$k/events/csv?start_time=${now s"https://api.fingrid.fi/v1/variable/$k/events/csv?start_time=${now
@@ -52,23 +66,40 @@ object Main {
println("Collection completed") println("Collection completed")
} }
// Read data from a file // Modify the energy sources
def readData(filePath: String): Unit = { def modifySources(
println("Reading data...") lines: List[String],
val bufferedSource = io.Source.fromFile(filePath) newValue: String,
for (line <- bufferedSource.getLines) { sourceType: String
val cols = line.split(",").map(_.trim) ): List[String] = {
println(s"${cols(0)}\t${cols(1)}\t${cols(2)}") val temp = lines(1).split(",")
// Match the source type and create a line with the new value
val newLine = sourceType match {
case "Wind" => s"$newValue,${temp(1)},${temp(2)}"
case "Hydro" => s"${temp(0)},$newValue,${temp(2)}"
case "Nuclear" => s"${temp(0)},${temp(1)},$newValue"
} }
bufferedSource.close lines.updated(1, newLine)
} }
// Modify the energy sources // Read the energy sources
def modifySources(filePath: String): Unit = { def readSources(
modificationFn: (List[String], String, String) => List[String]
): Unit = {
// Read the energy sources // Read the energy sources
readData(filePath) println("Reading sources...")
val bufferedSource = Source.fromFile(energySources)
def readSourcesHelper(lines: Iterator[String]): Unit = {
if (lines.hasNext) {
val line = lines.next()
val cols = line.split(",").map(_.trim)
println(s"${cols(0)}\t${cols(1)}\t${cols(2)}")
readSourcesHelper(lines)
}
}
readSourcesHelper(bufferedSource.getLines)
bufferedSource.close
// Modify the energy sources
print("Enter your choice:\n1) Modify\n2) Exit\n") print("Enter your choice:\n1) Modify\n2) Exit\n")
val choice = readLine() val choice = readLine()
choice match { choice match {
@@ -79,26 +110,27 @@ object Main {
val choice2 = readLine() val choice2 = readLine()
print("Enter the new value: ") print("Enter the new value: ")
val newValue = readLine() val newValue = readLine()
val lines = Source.fromFile(filePath).getLines.toList val lines = Source.fromFile(energySources).getLines.toList
val temp = lines(1).split(",") val pw = new java.io.PrintWriter(energySources)
val pw = new java.io.PrintWriter(filePath) val newLines = choice2 match {
choice2 match {
case "1" => case "1" =>
val newLines = lines.updated(1, s"$newValue,${temp(1)},${temp(2)}") modificationFn(lines, newValue, "Wind")
newLines.foreach(pw.println)
case "2" => case "2" =>
val newLines = lines.updated(1, s"${temp(0)},$newValue,${temp(2)}") modificationFn(lines, newValue, "Hydro")
newLines.foreach(pw.println)
case "3" => case "3" =>
val newLines = lines.updated(1, s"${temp(0)},${temp(1)},$newValue") modificationFn(lines, newValue, "Nuclear")
newLines.foreach(pw.println)
case "4" => case "4" =>
return return List.empty[String]
case _ => case _ =>
println("Invalid choice") println("Invalid choice")
List.empty[String]
}
// Write the new lines to the file
if (newLines.nonEmpty) {
newLines.foreach(pw.println)
pw.close()
println("Value updated")
} }
pw.close()
println("Value updated")
case "2" => case "2" =>
return return
case _ => case _ =>
@@ -107,29 +139,30 @@ object Main {
} }
// Calculate the mean, median, mode, range and midrange of the data // Calculate the mean, median, mode, range and midrange of the data
def calculateData(filePath: String): Unit = { def analyzeData(data: Map[Date, Double]): Unit = {
var data = List[Double]() if (data == null) {
val bufferedSource = io.Source.fromFile(filePath) println("No data to analyze")
for (line <- bufferedSource.getLines.drop(1)) { return
val cols = line.split(",").map(_.trim)
data = data :+ cols(2).toDouble
} }
bufferedSource.close
// Convert the data to a list
val convertedData = data.values.toList
// Calculate the mean // Calculate the mean
val mean = data.sum / data.length val mean = convertedData.sum / convertedData.length
println(s"Mean: $mean") println(s"Mean: $mean")
// Calculate the median // Calculate the median
val median = { val median = {
val (lower, upper) = data.sortWith(_ < _).splitAt(data.length / 2) val (lower, upper) =
if (data.length % 2 == 0) (lower.last + upper.head) / 2.0 convertedData.sortWith(_ < _).splitAt(convertedData.length / 2)
if (convertedData.length % 2 == 0) (lower.last + upper.head) / 2.0
else upper.head else upper.head
} }
println(s"Median: $median") println(s"Median: $median")
// Calculate the mode // Calculate the mode
val mode = data val mode = convertedData
.groupBy(identity) .groupBy(identity)
.view .view
.mapValues(_.size) .mapValues(_.size)
@@ -138,65 +171,257 @@ object Main {
println(s"Mode: $mode") println(s"Mode: $mode")
// Calculate the range // Calculate the range
val range = data.max - data.min val range = convertedData.max - convertedData.min
println(s"Range: $range") println(s"Range: $range")
// Calculate the midrange // Calculate the midrange
val midrange = (data.max + data.min) / 2 val midrange = (convertedData.max + convertedData.min) / 2
println(s"Midrange: $midrange") println(s"Midrange: $midrange")
} }
// Analyze the data // Compare timestamps
def analyzeData(): Unit = {
def compareTime(
lastTimestamp: Date,
data: List[String],
filter: Int,
format: SimpleDateFormat
): Map[Date, Double] = {
// Helper function
def compareTimeHelper(
data: List[String],
interval: Date,
acc: Map[Date, Double]
): Map[Date, Double] = {
if (data.isEmpty) {
acc
} else {
val cols = data.head.split(",").map(_.trim)
val timestamp = cols(0)
val date = format.parse(timestamp)
if (date.compareTo(interval) >= 0) {
compareTimeHelper(
data.tail,
interval,
acc + (date -> cols(2).toDouble)
)
} else {
compareTimeHelper(data.tail, interval, acc)
}
}
}
// Calculate the interval for last hour, last 24-hour, last week, last month or all time
val interval = filter match {
case 1 => new Date(lastTimestamp.getTime - 60 * 60 * 1000)
case 2 => new Date(lastTimestamp.getTime - 24 * 60 * 60 * 1000)
case 3 => new Date(lastTimestamp.getTime - 7 * 24 * 60 * 60 * 1000)
case 4 => new Date(lastTimestamp.getTime - 30 * 24 * 60 * 60 * 1000)
case 5 => new Date(0)
}
compareTimeHelper(data.drop(1), interval, Map.empty)
}
// Filter the data
def filterData(
data: List[String],
compareFn: (
Date,
List[String],
Int,
SimpleDateFormat
) => Map[Date, Double]
): Map[Date, Double] = {
// Get last timestamp
val format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
val lastTimestamp = format.parse(data.last)
print( print(
"Enter source to analyze:\n1) Wind\n2) Hydro\n3) Nuclear\n4) All\n5) Exit\n" "Filter by:\n1) Last hour\n2) Last 24 hours\n3) Last week\n4) Last month\n5) All\n6) Exit\nEnter choice: "
) )
val choice = readLine() val choice = readLine()
choice match { val filteredTimestamps = choice match {
case "1" => case "1" =>
println("Analyzing wind data...") compareFn(lastTimestamp, data, 1, format)
calculateData("wind.csv")
case "2" => case "2" =>
println("Analyzing hydro data...") compareFn(lastTimestamp, data, 2, format)
calculateData("hydro.csv")
case "3" => case "3" =>
println("Analyzing nuclear data...") compareFn(lastTimestamp, data, 3, format)
calculateData("nuclear.csv")
case "4" => case "4" =>
println("Analyzing all data...") compareFn(lastTimestamp, data, 4, format)
calculateData("data.csv")
case "5" => case "5" =>
return compareFn(lastTimestamp, data, 5, format)
case "6" =>
return Map.empty[Date, Double]
case _ => case _ =>
println("Invalid choice") println("Invalid choice")
Map.empty[Date, Double]
}
filteredTimestamps
}
// Read data from a file
def readData(): List[String] = {
print(
"Sources:\n1) Wind\n2) Hydro\n3) Nuclear\n4) All\n5) Exit\nEnter your choice: "
)
val choice = readLine()
val bufferedSource = choice match {
case "1" =>
Source.fromFile("wind.csv")
case "2" =>
Source.fromFile("hydro.csv")
case "3" =>
Source.fromFile("nuclear.csv")
case "4" =>
Source.fromFile("data.csv")
case "5" =>
return null
case _ =>
println("Invalid choice")
null
}
val data = bufferedSource.getLines.toList
bufferedSource.close
data
}
// Sort the data by timestamp or value
def sortData(data: Map[Date, Double]): Map[Date, Double] = {
print(
"Sort by:\n1) Timestamp (Ascending)\n2) Timestamp (Descending)\n3) Value (Ascending)\n4) Value (Descending)\n5) Exit\nEnter choice: "
)
val choice = readLine()
val sortedData = choice match {
case "1" =>
// Sort data by ascending timestamp
println("Sorting data by ascending timestamp...")
ListMap(data.toSeq.sortBy(_._1): _*)
case "2" =>
// Sort data by descending timestamp
println("Sorting data by descending timestamp...")
ListMap(data.toSeq.sortBy(_._1).reverse: _*)
case "3" =>
// Sort data by ascending value
println("Sorting data by ascending value...")
ListMap(data.toSeq.sortBy(_._2): _*)
case "4" =>
// Sort data by descending value
println("Sorting data by descending value...")
ListMap(data.toSeq.sortBy(_._2).reverse: _*)
case "5" =>
return Map.empty[Date, Double]
case _ =>
println("Invalid choice")
Map.empty[Date, Double]
}
sortedData
}
// Print data
def printData(data: Map[Date, Double]): Unit = {
def printDataHelper(data: Map[Date, Double]): Unit = {
if (data.nonEmpty) {
val (k, v) = data.head
println(s"${k}: \t${v}MW")
printDataHelper(data.tail)
}
}
println("\tTimestamp\t\tValue")
if (data.nonEmpty) {
printDataHelper(data)
} else {
println("No data to print")
} }
} }
// Main function // Search data
def searchData(data: List[String]): Map[Date, Double] = {
val format = new SimpleDateFormat("yyyy-MM-dd")
print("Enter the date (yyyy-MM-dd) to search for: ")
val date = readLine()
val searchDate = format.parse(date)
// Iterate through the data and add the data to the map if the date matches
val newData =
data.drop(1).foldLeft(Map.empty[Date, Double]) { (acc, line) =>
val cols = line.split(",").map(_.trim)
val timestamp = cols(0)
val value = cols(2).toDouble
if (value < alertThreshold) {
acc + ((new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"))
.parse(timestamp) -> value)
} else {
acc
}
}
newData
}
// Alert user if energy production is below the threshold
def alertUser(): Unit = {
val sources = dataSources.values.toList.dropRight(1)
sources.map { source =>
val bufferedSource = Source.fromFile(source)
val data = bufferedSource.getLines.toList
bufferedSource.close
val alert = data.drop(1).foldLeft(false) { (acc, line) =>
val cols = line.split(",").map(_.trim)
val value = cols(2).toDouble
if (value < alertThreshold) {
true
} else {
acc
}
}
if (alert) {
println(
s"ALERT: ${source} has production values below the threshold of ${alertThreshold}MW. Please scan systems for details!"
)
}
}
}
// Scan data for anomalies
def scanSystem(data: List[String]): Map[Date, Double] = {
val newData =
data.drop(1).foldLeft(Map.empty[Date, Double]) { (acc, line) =>
val cols = line.split(",").map(_.trim)
val timestamp = cols(0)
val value = cols(2).toDouble
if (value < alertThreshold) {
acc + ((new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"))
.parse(timestamp) -> value)
} else {
acc
}
}
newData
}
// Main function
def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
while (true) { while (true) {
alertUser()
print( print(
"REPS management system:\n1) Check energy sources\n2) Collect data\n3) View data\n4) Analyze data\n5) Exit\nEnter your choice: " "REPS management system:\n1) Check energy sources\n2) Collect data\n3) View data\n4) Analyze data\n5) Search data\n6) Scan system\n0) Exit\nEnter your choice: "
) )
val choice = readLine() val choice = readLine()
choice match { choice match {
case "1" => case "1" =>
modifySources("sources.csv") readSources(modifySources)
case "2" => case "2" =>
collectData( collectData()
Map(
188 -> "nuclear.csv",
191 -> "hydro.csv",
181 -> "wind.csv",
192 -> "data.csv"
)
)
case "3" => case "3" =>
readData("data.csv") printData(sortData(filterData(readData(), compareTime)))
case "4" => case "4" =>
analyzeData() analyzeData(filterData(readData(), compareTime))
case "5" => case "5" =>
printData(sortData(searchData(readData())))
case "6" =>
printData(sortData(scanSystem(readData())))
case "0" =>
println("Exiting...") println("Exiting...")
System.exit(0) System.exit(0)
case _ => case _ =>