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

Add end-point for extracting buses from a network #1

Merged
merged 1 commit into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
27 changes: 27 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@
<artifactId>ktor-server-netty-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-serialization-kotlinx-json-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-test-host-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-content-negotiation-jvm</artifactId>
<version>${ktor.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
Expand All @@ -56,6 +71,18 @@
<version>${kotlin.version}</version>

<extensions>true</extensions>
<configuration>
<compilerPlugins>
<plugin>kotlinx-serialization</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-serialization</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
25 changes: 15 additions & 10 deletions src/main/ApiDataModels.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.github.statnett.loadflowservice

import com.powsybl.iidm.network.Network
import kotlinx.serialization.Serializable

/**
* Class for holding properties from the PowsbyBl bus class that are
* returned via the Rest API
*/
@Serializable
data class BusProperties(
val id: String,
val voltage: Double,
Expand All @@ -14,13 +16,16 @@ data class BusProperties(
val reactivePower: Double,
)

fun busPropertiesFromNetwork(network: Network) =
network.getBusView().getBusStream().map {
BusProperties(
id = it.getId(),
voltage = it.getV(),
angle = it.getAngle(),
activePower = it.getP(),
reactivePower = it.getQ(),
)
}
fun busPropertiesFromNetwork(network: Network): List<BusProperties> {
return network.busView.buses
.map { bus ->
BusProperties(
id = bus.id,
voltage = bus.v,
angle = bus.angle,
activePower = bus.p,
reactivePower = bus.q,
)
}
.toList()
}
11 changes: 11 additions & 0 deletions src/main/ApiUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.statnett.loadflowservice

import java.io.ByteArrayInputStream

fun busesFromRequest(
type: String,
body: ByteArray,
): List<BusProperties> {
val network = networkFromStream(type, ByteArrayInputStream(body))
return busPropertiesFromNetwork(network)
}
17 changes: 0 additions & 17 deletions src/main/App.kt

This file was deleted.

52 changes: 52 additions & 0 deletions src/main/Application.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.github.statnett.loadflowservice

import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.http.content.streamProvider
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

fun Application.module() {
install(ContentNegotiation) {
json()
}

routing {
get("/") {
call.respondText("Hello, world!")
}

post("/get-buses") {
var fileName = ""
var fileBytes = byteArrayOf()

val multiPartData = call.receiveMultipart()

multiPartData.forEachPart { part ->
when (part) {
is PartData.FileItem -> {
fileName = part.originalFileName as String
fileBytes = part.streamProvider().readBytes()
}

else -> {}
}
part.dispose()
}

if (fileName == "") {
call.response.status(HttpStatusCode.UnprocessableEntity)
} else {
val busProps = busesFromRequest(fileName, fileBytes)
call.respond(busProps)
}
}
}
}
9 changes: 9 additions & 0 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ktor {
deployment {
port = 8080
port = ${?PORT}
}
application {
modules = [ com.github.statnett.loadflowservice.ApplicationKt.module ]
}
}
7 changes: 3 additions & 4 deletions src/test/ApiDataModelsTest.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import com.github.statnett.loadflowservice.busPropertiesFromNetwork
import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory
import kotlin.test.Test
import kotlin.test.assertEquals
import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory
import com.github.statnett.loadflowservice.busPropertiesFromNetwork

class ApiDataModelTest {

@Test
fun `Should be 14 buses in test network`() {
val network = IeeeCdfNetworkFactory.create14()
val buses = busPropertiesFromNetwork(network)
assertEquals(buses.count(), 14)
}
}
}
75 changes: 75 additions & 0 deletions src/test/ApplicationTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory
import io.ktor.client.request.*
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.*
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.testApplication
import java.io.File
import java.nio.file.Paths
import java.util.Properties
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class ApplicationTest {
@Test
fun testRoot() =
testApplication {
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("Hello, world!", response.bodyAsText())
}

@Test
fun `test get buses returns 422 on missing file content`() =
testApplication {
val response =
client.submitFormWithBinaryData(
url = "/get-buses",
formData =
formData {
append("network", "not file content")
},
)
assertEquals(HttpStatusCode.UnprocessableEntity, response.status)
}

@Test
fun `test receive 14 buses for 14 bus network`() =
testApplication {
// Initialize temporary file
val file = File.createTempFile("network", ".xiidm")
file.deleteOnExit()

IeeeCdfNetworkFactory.create14().write("XIIDM", Properties(), Paths.get(file.path))

val response =
client.submitFormWithBinaryData(
url = "/get-buses",
formData =
formData {
append(
"network",
file.readBytes(),
Headers.build {
append(HttpHeaders.ContentDisposition, "filename=${file.name}")
},
)
},
)
assertEquals(HttpStatusCode.OK, response.status)
assertEquals(response.headers["Content-Type"], "application/json; charset=UTF-8")
val body: String = response.bodyAsText()

// Roughly validate contant
assertTrue(body.startsWith("[{"))
assertTrue(body.endsWith("}]"))

val busString = "{\"id\":\"VL1_0\",\"voltage\":143.1,\"angle\":0.0,\"activePower\":0.0,\"reactivePower\":0.0}"
assertContains(body, busString)
}
}
Loading