Skip to content

Commit

Permalink
quarter-hourly peak validation fix
Browse files Browse the repository at this point in the history
The validation mixed kWh and kW. The timeseries is now converted to kW
before comparison.
  • Loading branch information
Erikvv committed Dec 3, 2024
1 parent 11d5609 commit c351429
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 23 deletions.
24 changes: 15 additions & 9 deletions zummon/src/commonMain/kotlin/companysurvey/Electricity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,29 @@ data class Electricity (
fun getHasConnection(): Boolean {
return hasConnection ?: false
}


/**
* Contracted capacity for delivery of electricity from grid to company.
*/
fun getContractedConnectionCapacityKw(): Double? {
when(kleinverbruikOrGrootverbruik) {
KleinverbruikOrGrootverbruik.GROOTVERBRUIK -> return grootverbruik?.contractedConnectionDeliveryCapacity_kW?.toDouble()
KleinverbruikOrGrootverbruik.KLEINVERBRUIK -> return kleinverbruik?.connectionCapacity?.toKw()
else -> return kleinverbruik?.connectionCapacity?.toKw() ?: grootverbruik?.contractedConnectionDeliveryCapacity_kW?.toDouble()
return when (kleinverbruikOrGrootverbruik) {
KleinverbruikOrGrootverbruik.GROOTVERBRUIK -> grootverbruik?.contractedConnectionDeliveryCapacity_kW?.toDouble()
KleinverbruikOrGrootverbruik.KLEINVERBRUIK -> kleinverbruik?.connectionCapacity?.toKw()
else -> kleinverbruik?.connectionCapacity?.toKw() ?: grootverbruik?.contractedConnectionDeliveryCapacity_kW?.toDouble()
}
}

fun getPhysicalConnectionCapacityKw(): Double? {
when(kleinverbruikOrGrootverbruik) {
KleinverbruikOrGrootverbruik.GROOTVERBRUIK -> return grootverbruik?.physicalCapacityKw?.toDouble()
KleinverbruikOrGrootverbruik.KLEINVERBRUIK -> return kleinverbruik?.connectionCapacity?.toKw()
else -> return kleinverbruik?.connectionCapacity?.toKw() ?: grootverbruik?.physicalCapacityKw?.toDouble()
return when (kleinverbruikOrGrootverbruik) {
KleinverbruikOrGrootverbruik.GROOTVERBRUIK -> grootverbruik?.physicalCapacityKw?.toDouble()
KleinverbruikOrGrootverbruik.KLEINVERBRUIK -> kleinverbruik?.connectionCapacity?.toKw()
else -> kleinverbruik?.connectionCapacity?.toKw() ?: grootverbruik?.physicalCapacityKw?.toDouble()
}
}

/**
* Contracted capacity for feed-in of electricity from company to grid.
*/
fun getContractedFeedInCapacityKw(): Double? {
when (kleinverbruikOrGrootverbruik) {
KleinverbruikOrGrootverbruik.GROOTVERBRUIK -> return grootverbruik?.contractedConnectionFeedInCapacity_kW?.toDouble()
Expand Down
27 changes: 27 additions & 0 deletions zummon/src/commonMain/kotlin/companysurvey/TimeSeries.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.zenmo.zummon.companysurvey
import kotlinx.serialization.Serializable
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuid4
import com.zenmo.zummon.BenasherUuidSerializer
Expand Down Expand Up @@ -166,3 +167,29 @@ enum class TimeSeriesType {
ELECTRICITY_PRODUCTION,
GAS_DELIVERY,
}

/**
* Represents a single point within the time series.
* Improvement: add timestamp
*/
data class DataPoint (
val value: Float,
val unit: TimeSeriesUnit,
val timeStep: kotlin.time.Duration,
) {
fun kWh(): Double {
if (this.unit != TimeSeriesUnit.KWH) {
throw UnsupportedOperationException("Can only get the kWh from a kWh data point")
}

return value.toDouble()
}

fun kW(): Double {
if (this.unit != TimeSeriesUnit.KWH) {
throw UnsupportedOperationException("Can only get the kW from a kWh data point")
}

return value * (1.hours / this.timeStep)
}
}
65 changes: 51 additions & 14 deletions zummon/src/commonMain/kotlin/companysurvey/Validation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -339,25 +339,66 @@ class ElectricityValidator : Validator<Electricity> {
}
}

//peak of delivery should be less than contracted capacity
/**
* peak of delivery should be less than contracted capacity
*/
fun quarterHourlyDeliveryLowContractedCapacity(electricity: Electricity): ValidationResult {
val contractedCapacity = (electricity.getContractedConnectionCapacityKw() ?: 0.0).toFloat()
val pickDelivery = electricity.quarterHourlyDelivery_kWh?.values?.maxOrNull() ?: Float.MIN_VALUE
return if ( pickDelivery <= contractedCapacity) {
ValidationResult(Status.VALID, translate("electricity.quarterHourlyDeliveryLowContractedCapacityKw", contractedCapacity))
val contractedCapacity_kW = electricity.getContractedConnectionCapacityKw()
if (contractedCapacity_kW == null) {
return ValidationResult(Status.MISSING_DATA, message(
en = "Gecontracteerd vermogen levering ontbreek",
nl = "Contracted delivery capacity missing",
))
}

if (electricity.quarterHourlyDelivery_kWh == null) {
return ValidationResult(Status.MISSING_DATA, message(
en = "Kwartierwaarden levering ontbreek",
nl = "Quarter-hourly delivery missing",
))
}

val peakDelivery = electricity.quarterHourlyDelivery_kWh.getPeak()

return if ( peakDelivery.kW() <= contractedCapacity_kW) {
ValidationResult(Status.VALID, message(
en = "Piek van kwartierwaarden levering ${peakDelivery.kWh()} kWh valt binnen gecontracteerd vermogen levering ${contractedCapacity_kW} kW",
nl = "Peak of quarter-hourly delivery ${peakDelivery.kWh()} kWh does not exceed contracted capacity ${contractedCapacity_kW} kW",
))
} else {
ValidationResult(Status.INVALID, translate("electricity.quarterHourlyDeliveryHighContractedCapacityKw", contractedCapacity))
}
}

//peak of feed-in should be less than contracted capacity
fun quarterFeedInLowContractedCapacity(electricity: Electricity): ValidationResult {
val contractedFeedInCapacity = (electricity.getContractedFeedInCapacityKw() ?: 0.0).toFloat()
val pickFeedIn = electricity.quarterHourlyFeedIn_kWh?.values?.maxOrNull() ?: Float.MIN_VALUE
return if (pickFeedIn < contractedFeedInCapacity) {
ValidationResult(Status.VALID, translate("electricity.quarterHourlyDeliveryLowContractedCapacityKw", contractedFeedInCapacity))
val contractedFeedInCapacity_kW = (electricity.getContractedFeedInCapacityKw() ?: 0.0).toFloat()
if (contractedFeedInCapacity_kW == null) {
return ValidationResult(Status.MISSING_DATA, message(
en = "Gecontracteerd vermogen teruglevering ontbreek",
nl = "Contracted feed-in capacity missing",
))
}

if (electricity.quarterHourlyFeedIn_kWh == null) {
// Or not applicable if no solar panels
return ValidationResult(Status.MISSING_DATA, message(
en = "Kwartierwaarden teruglevering ontbreek",
nl = "Quarter-hourly feed-in missing",
))
}

val peakFeedIn = electricity.quarterHourlyFeedIn_kWh.getPeak()
return if (peakFeedIn.kW() < contractedFeedInCapacity_kW) {
ValidationResult(Status.VALID, message(
en = "Piek van kwartierwaarden teruglevering ${peakFeedIn.kWh()} kWh valt binnen gecontracteerd vermogen levering $contractedFeedInCapacity_kW kW",
nl = "Peak of quarter-hourly feed-in ${peakFeedIn.kWh()} kWh does not exceed contracted capacity $contractedFeedInCapacity_kW kW",
))
} else {
ValidationResult(Status.INVALID, translate("electricity.quarterHourlyDeliveryHighContractedCapacityKw", contractedFeedInCapacity))
ValidationResult(Status.INVALID, message(
nl = "Piek van kwartierwaarden teruglevering ${peakFeedIn.kWh()} kWh mag niet hoger zijn dan gecontracteerd vermogen levering $contractedFeedInCapacity_kW kW",
en = "Peak of quarter-hourly feed-in ${peakFeedIn.kWh()} kWh delivery should be below contracted capacity $contractedFeedInCapacity_kW Kw",
))
}
}

Expand Down Expand Up @@ -595,10 +636,6 @@ val translations: Map<Language, Map<String, Map<String, String>>> = mapOf(
"notEnoughValues" to "Not enough values for year: needed %d got %d",
"annualFeedInMismatch" to "Annual feed in (%d) mismatch the total quarter hourly feed in (%d)",
"annualFeedInMismatch" to "Annual feed in (%d) matches the total quarter hourly feed in (%d)",

"quarterHourlyDeliveryLowContractedCapacityKw" to "Quarter-hourly stays lower than the Contracted CapacityKw (%d)",
"quarterHourlyDeliveryHighContractedCapacityKw" to "Quarter-hourly shouldn't go higher than the Contracted CapacityKw (%d)"

),
"grootverbruik" to mapOf(
"notProvided" to "Large consumption data is not provided",
Expand Down
13 changes: 13 additions & 0 deletions zummon/src/commonTest/kotlin/companysurvey/TimeSeriesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,17 @@ class TimeSeriesTest {
assertEquals(300f, yearValues.first())
assertEquals(200f, yearValues.last())
}

@Test
fun testGetPeakKw() {
val timeSeries = TimeSeries(
type = TimeSeriesType.ELECTRICITY_DELIVERY,
start = Instant.parse("2024-01-01T00:00:00+01:00"),
values = floatArrayOf(1f, 2f, 1f)
)

val peak = timeSeries.getPeak()
assertEquals(2.0, peak.kWh())
assertEquals(8.0, peak.kW())
}
}

0 comments on commit c351429

Please sign in to comment.