average price endpoint
This commit is contained in:
@@ -2,8 +2,11 @@ package com.frankenergie.smartasset.controller
|
||||
|
||||
import com.frankenergie.smartasset.model.OrderUpdateRequest
|
||||
import com.frankenergie.smartasset.model.OrderUpdateResponse
|
||||
import com.frankenergie.smartasset.model.PurchaseSummary
|
||||
import com.frankenergie.smartasset.service.OrderBookService
|
||||
import com.frankenergie.smartasset.service.PurchaseTrackerService
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
@@ -11,11 +14,20 @@ import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
class OrderUpdateController(private val orderBookService: OrderBookService) {
|
||||
class OrderUpdateController(
|
||||
private val orderBookService: OrderBookService,
|
||||
private val purchaseTrackerService: PurchaseTrackerService
|
||||
) {
|
||||
|
||||
@PostMapping("/orderupdate")
|
||||
fun orderUpdate(@RequestBody request: OrderUpdateRequest): ResponseEntity<OrderUpdateResponse> {
|
||||
val response = orderBookService.processOrder(request)
|
||||
return ResponseEntity.ok(response)
|
||||
}
|
||||
|
||||
@GetMapping("/purchases/average")
|
||||
fun getPurchaseAverage(): ResponseEntity<PurchaseSummary> {
|
||||
val summary = purchaseTrackerService.getSummary()
|
||||
return ResponseEntity.ok(summary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.frankenergie.smartasset.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class ExecutedPurchase(
|
||||
val groupId: String,
|
||||
val deliveryStart: LocalDateTime,
|
||||
val deliveryEnd: LocalDateTime,
|
||||
val quantity: BigDecimal,
|
||||
val pricePerMWh: BigDecimal,
|
||||
val timestamp: Instant = Instant.now()
|
||||
) {
|
||||
val totalCost: BigDecimal get() = quantity * pricePerMWh
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.frankenergie.smartasset.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class PurchaseSummary(
|
||||
@JsonProperty("total_mwh") val totalMWh: BigDecimal,
|
||||
@JsonProperty("total_cost") val totalCost: BigDecimal,
|
||||
@JsonProperty("average_price_per_mwh") val averagePricePerMWh: BigDecimal
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.frankenergie.smartasset.service
|
||||
|
||||
import com.frankenergie.smartasset.model.ExecutedPurchase
|
||||
import com.frankenergie.smartasset.model.PurchaseSummary
|
||||
import org.springframework.stereotype.Service
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
@Service
|
||||
class PurchaseTrackerService {
|
||||
|
||||
private val purchases: ConcurrentLinkedQueue<ExecutedPurchase> = ConcurrentLinkedQueue()
|
||||
|
||||
fun recordPurchase(purchase: ExecutedPurchase) {
|
||||
purchases.add(purchase)
|
||||
}
|
||||
|
||||
fun getAllPurchases(): List<ExecutedPurchase> = purchases.toList()
|
||||
|
||||
fun getSummary(): PurchaseSummary {
|
||||
val allPurchases = purchases.toList()
|
||||
val totalMWh = allPurchases.sumOf { it.quantity }
|
||||
val totalCost = allPurchases.sumOf { it.totalCost }
|
||||
val averagePrice = if (totalMWh > BigDecimal.ZERO) {
|
||||
totalCost.divide(totalMWh, 2, RoundingMode.HALF_UP)
|
||||
} else {
|
||||
BigDecimal.ZERO
|
||||
}
|
||||
|
||||
return PurchaseSummary(totalMWh, totalCost, averagePrice)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
purchases.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.frankenergie.smartasset.service
|
||||
|
||||
import com.frankenergie.smartasset.model.ExecutedPurchase
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class PurchaseTrackerServiceTest {
|
||||
|
||||
private lateinit var tracker: PurchaseTrackerService
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
tracker = PurchaseTrackerService()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSummary returns zeros when no purchases`() {
|
||||
val summary = tracker.getSummary()
|
||||
|
||||
assertEquals(BigDecimal.ZERO, summary.totalMWh)
|
||||
assertEquals(BigDecimal.ZERO, summary.totalCost)
|
||||
assertEquals(BigDecimal.ZERO, summary.averagePricePerMWh)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSummary calculates average price correctly`() {
|
||||
tracker.recordPurchase(
|
||||
ExecutedPurchase(
|
||||
groupId = "A",
|
||||
deliveryStart = LocalDateTime.of(2024, 1, 15, 10, 0),
|
||||
deliveryEnd = LocalDateTime.of(2024, 1, 15, 10, 15),
|
||||
quantity = BigDecimal("10"),
|
||||
pricePerMWh = BigDecimal("50.00")
|
||||
)
|
||||
)
|
||||
tracker.recordPurchase(
|
||||
ExecutedPurchase(
|
||||
groupId = "B",
|
||||
deliveryStart = LocalDateTime.of(2024, 1, 15, 10, 15),
|
||||
deliveryEnd = LocalDateTime.of(2024, 1, 15, 10, 30),
|
||||
quantity = BigDecimal("10"),
|
||||
pricePerMWh = BigDecimal("60.00")
|
||||
)
|
||||
)
|
||||
|
||||
val summary = tracker.getSummary()
|
||||
|
||||
assertEquals(BigDecimal("20"), summary.totalMWh)
|
||||
assertEquals(BigDecimal("1100.00"), summary.totalCost)
|
||||
assertEquals(BigDecimal("55.00"), summary.averagePricePerMWh)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSummary calculates weighted average correctly`() {
|
||||
tracker.recordPurchase(
|
||||
ExecutedPurchase(
|
||||
groupId = "A",
|
||||
deliveryStart = LocalDateTime.of(2024, 1, 15, 10, 0),
|
||||
deliveryEnd = LocalDateTime.of(2024, 1, 15, 10, 15),
|
||||
quantity = BigDecimal("5"),
|
||||
pricePerMWh = BigDecimal("40.00")
|
||||
)
|
||||
)
|
||||
tracker.recordPurchase(
|
||||
ExecutedPurchase(
|
||||
groupId = "A",
|
||||
deliveryStart = LocalDateTime.of(2024, 1, 15, 10, 15),
|
||||
deliveryEnd = LocalDateTime.of(2024, 1, 15, 10, 30),
|
||||
quantity = BigDecimal("15"),
|
||||
pricePerMWh = BigDecimal("60.00")
|
||||
)
|
||||
)
|
||||
|
||||
val summary = tracker.getSummary()
|
||||
|
||||
// 5 * 40 + 15 * 60 = 200 + 900 = 1100
|
||||
// 1100 / 20 = 55
|
||||
assertEquals(BigDecimal("20"), summary.totalMWh)
|
||||
assertEquals(BigDecimal("1100.00"), summary.totalCost)
|
||||
assertEquals(BigDecimal("55.00"), summary.averagePricePerMWh)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAllPurchases returns all recorded purchases`() {
|
||||
tracker.recordPurchase(
|
||||
ExecutedPurchase(
|
||||
"A",
|
||||
LocalDateTime.of(2024, 1, 15, 10, 0),
|
||||
LocalDateTime.of(2024, 1, 15, 10, 15),
|
||||
BigDecimal("10"),
|
||||
BigDecimal("50.00")
|
||||
)
|
||||
)
|
||||
tracker.recordPurchase(
|
||||
ExecutedPurchase(
|
||||
"B",
|
||||
LocalDateTime.of(2024, 1, 15, 10, 15),
|
||||
LocalDateTime.of(2024, 1, 15, 10, 30),
|
||||
BigDecimal("5"),
|
||||
BigDecimal("55.00")
|
||||
)
|
||||
)
|
||||
|
||||
val purchases = tracker.getAllPurchases()
|
||||
|
||||
assertEquals(2, purchases.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clear removes all purchases`() {
|
||||
tracker.recordPurchase(
|
||||
ExecutedPurchase(
|
||||
"A",
|
||||
LocalDateTime.of(2024, 1, 15, 10, 0),
|
||||
LocalDateTime.of(2024, 1, 15, 10, 15),
|
||||
BigDecimal("10"),
|
||||
BigDecimal("50.00")
|
||||
)
|
||||
)
|
||||
|
||||
tracker.clear()
|
||||
|
||||
assertTrue(tracker.getAllPurchases().isEmpty())
|
||||
assertEquals(BigDecimal.ZERO, tracker.getSummary().totalMWh)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user