KHNP OPEN API¶
발전소 구분 WS : 월성 KR : 고리 YK : 한빛 UJ : 한울
월단위 원자력발전소 상태 : NuclearPlantStates
분단위 실시간 폐수 수질현황: WasteWater
10분단위 실시간 주변 방사선 량: RadioRate
10분단위 실시간 온배수 현황: ThermalWasteWater
본부 구분(2100:고리본부,2200:월성본부,2300:한빛본부,2400:한울본부,2800:새울본부)
024년 10월 이후 월단위 방사성 폐기물 발생량: RadioActiveWaste
In [1]:
%use dataframe
%use datetime
In [2]:
%use kandy
%use lets-plot
In [3]:
import kotlinx.datetime.*
import kotlin.time.Duration.Companion.minutes
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.format
import kotlinx.datetime.format.FormatStringsInDatetimeFormats
import kotlinx.datetime.format.byUnicodePattern
import kotlinx.datetime.minus
import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime
분단위 실시간 폐수 수질현황: WasteWater¶
In [4]:
val url_Wastewater = "http://192.168.35.107:7788/khnp/wastewater"
val df_Wastewater = DataFrame.readJson(url_Wastewater)
In [5]:
import kotlin.math.round
val convertValue_Wastewater = df_Wastewater
.convert { tm001 and tm002 }.with {
val value = it?.toString()?.toDoubleOrNull() ?: 0.0
round(value * 10) / 10.0
}.convert {
time and tm001_time and tm002_time
}.with {
LocalDateTime.parse(it.toString().replace(" ", "T")).toInstant(TimeZone.of("Asia/Seoul"))
}
convertValue_Wastewater.describe()
Out[5]:
DataFrame: rowsCount = 6, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| time | kotlinx.datetime.Instant | 189 | 63 | 0 | 2026-04-22T21:54:00Z | 3 | null | null | 2026-04-22T21:54:00Z | 2026-04-23T00:50:00Z | 2026-04-23T03:45:00Z |
| genName | String | 189 | 3 | 0 | KR | 63 | null | null | KR | WS | YK |
| tm001 | Double | 189 | 57 | 0 | 0.000000 | 112 | 22.776190 | 36.372041 | 0.000000 | 0.000000 | 110.000000 |
| tm001_time | kotlinx.datetime.Instant | 189 | 112 | 0 | 2026-04-22T10:55:00Z | 63 | null | null | 2026-04-22T10:55:00Z | 2026-04-22T23:17:00Z | 2026-04-23T03:44:00Z |
| tm002 | Double | 189 | 5 | 0 | 0.000000 | 63 | 4.724868 | 3.350558 | 0.000000 | 7.000000 | 7.200000 |
| tm002_time | kotlinx.datetime.Instant | 189 | 94 | 0 | 2026-04-22T10:55:00Z | 63 | null | null | 2026-04-22T10:55:00Z | 2026-04-22T23:17:00Z | 2026-04-23T03:44:00Z |
In [6]:
val checkTime_Wastewater = 10.minutes
val checked_Wastewater = convertValue_Wastewater.filter {
(time - tm001_time).absoluteValue <= checkTime_Wastewater &&
(time - tm002_time).absoluteValue <= checkTime_Wastewater
}
In [7]:
val filterTime_Wastewater = Clock.System.now().minus(6, DateTimeUnit.HOUR)
val final_Wastewater = checked_Wastewater
.filter{ time >= filterTime_Wastewater }
.convert {
time and tm001_time and tm002_time
}.with {
it.toLocalDateTime(TimeZone.of("Asia/Seoul")).format(LocalDateTime.Format{byUnicodePattern("yyyy-MM-dd HH:mm")})
}
final_Wastewater.describe()
Out[7]:
DataFrame: rowsCount = 6, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| time | String | 126 | 63 | 0 | 2026-04-23 06:54 | 2 | null | null | 2026-04-23 06:54 | 2026-04-23 09:50 | 2026-04-23 12:45 |
| genName | String | 126 | 2 | 0 | KR | 63 | null | null | KR | KR | WS |
| tm001 | Double | 126 | 57 | 0 | 0.000000 | 49 | 34.164286 | 39.968751 | 0.000000 | 33.050000 | 110.000000 |
| tm001_time | String | 126 | 111 | 0 | 2026-04-23 06:53 | 2 | null | null | 2026-04-23 06:53 | 2026-04-23 09:48 | 2026-04-23 12:44 |
| tm002 | Double | 126 | 4 | 0 | 7.100000 | 63 | 7.087302 | 0.083890 | 6.900000 | 7.100000 | 7.200000 |
| tm002_time | String | 126 | 93 | 0 | 2026-04-23 06:53 | 2 | null | null | 2026-04-23 06:53 | 2026-04-23 09:49 | 2026-04-23 12:44 |
In [8]:
val data = final_Wastewater.toMap()
In [9]:
letsPlot( data ) +
labs( title = "분단위 실시간 폐수 수질현황", y="Wastewater Quality (PH)", x="수집시간", color="발전소") +
geomPoint( alpha = 0.6){
x = "time"
y = "tm002"
color="genName"
size="tm001"
} +
scaleYContinuous(limits=Pair(6.0, 8.0)) +
// %Y: 년, %m: 월, %d: 일, %H: 시, %M: 분
//scaleXDateTime(format = "%m-%d %H:%M" ) +
scaleSize(range=Pair(0, 15), name="유량") +
theme( axisTextX= elementText( angle=45, hjust = 1.0)) +
ggsize( width = 2400, height = 450)
Out[9]:
10분단위 실시간 온배수 현황: ThermalWasteWater¶
In [10]:
val url_ThermalWasteWater = "http://192.168.35.107:7788/khnp/thermalwastewater"
val df_ThermalWasteWater = DataFrame.readJson(url_ThermalWasteWater)
df_ThermalWasteWater.describe()
Out[10]:
DataFrame: rowsCount = 10, columnsCount = 10
| name | type | count | unique | nulls | top | freq | min | median | max |
|---|---|---|---|---|---|---|---|---|---|
| time | String | 252 | 63 | 0 | 2026-04-23 06:54 | 4 | 2026-04-23 06:54 | 2026-04-23 09:50 | 2026-04-23 12:45 |
| genName | String | 252 | 4 | 0 | KR | 63 | KR | UJ | YK |
| rm001 | String | 252 | 20 | 0 | 0 | 63 | 0 | 11.6 | 16.600000381469727 |
| rm001_time | String | 252 | 120 | 0 | 2026-02-04 18:37 | 63 | 2026-02-04 18:37 | 2026-04-23 08:45 | 2026-04-23 12:45 |
| rm002 | String | 252 | 13 | 0 | 0 | 63 | 0 | 32.5 | 34.20000076293945 |
| rm002_time | String | 252 | 122 | 0 | 2026-02-04 18:37 | 63 | 2026-02-04 18:37 | 2026-04-23 08:45 | 2026-04-23 12:45 |
| rm005 | String | 252 | 20 | 0 | 0 | 63 | 0 | 13.7 | 24.3 |
| rm005_time | String | 252 | 120 | 0 | 2026-02-04 18:37 | 63 | 2026-02-04 18:37 | 2026-04-23 08:45 | 2026-04-23 12:45 |
| rm006 | String | 252 | 15 | 0 | 0 | 63 | 0 | 32.400001525878906 | 33.8 |
| rm006_time | String | 252 | 107 | 0 | 2026-02-04 18:37 | 63 | 2026-02-04 18:37 | 2026-04-23 08:45 | 2026-04-23 12:45 |
In [11]:
val converted_ThermalWasteWater = df_ThermalWasteWater
.convert {
time and rm001_time and rm002_time and rm005_time and rm006_time
}.with {
LocalDateTime.parse(it.toString().replace(" ", "T")).toInstant(TimeZone.of("Asia/Seoul"))
}.convert{ rm001 and rm002 and rm005 and rm006}.with {
val value = it?.toString()?.toDoubleOrNull() ?: 0.0
round(value * 10) / 10.0
}
converted_ThermalWasteWater.describe()
Out[11]:
DataFrame: rowsCount = 10, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| time | kotlinx.datetime.Instant | 252 | 63 | 0 | 2026-04-22T21:54:00Z | 4 | null | null | 2026-04-22T21:54:00Z | 2026-04-23T00:50:00Z | 2026-04-23T03:45:00Z |
| genName | String | 252 | 4 | 0 | KR | 63 | null | null | KR | UJ | YK |
| rm001 | Double | 252 | 16 | 0 | 0.000000 | 63 | 10.299603 | 6.197407 | 0.000000 | 12.550000 | 16.600000 |
| rm001_time | kotlinx.datetime.Instant | 252 | 120 | 0 | 2026-02-04T09:37:00Z | 63 | null | null | 2026-02-04T09:37:00Z | 2026-04-22T23:45:00Z | 2026-04-23T03:45:00Z |
| rm002 | Double | 252 | 9 | 0 | 0.000000 | 63 | 25.052778 | 14.508010 | 0.000000 | 33.000000 | 34.200000 |
| rm002_time | kotlinx.datetime.Instant | 252 | 122 | 0 | 2026-02-04T09:37:00Z | 63 | null | null | 2026-02-04T09:37:00Z | 2026-04-22T23:45:00Z | 2026-04-23T03:45:00Z |
| rm005 | Double | 252 | 18 | 0 | 0.000000 | 63 | 13.577778 | 8.719894 | 0.000000 | 15.100000 | 24.300000 |
| rm005_time | kotlinx.datetime.Instant | 252 | 120 | 0 | 2026-02-04T09:37:00Z | 63 | null | null | 2026-02-04T09:37:00Z | 2026-04-22T23:45:00Z | 2026-04-23T03:45:00Z |
| rm006 | Double | 252 | 12 | 0 | 0.000000 | 63 | 24.686905 | 14.286617 | 0.000000 | 32.650000 | 33.800000 |
| rm006_time | kotlinx.datetime.Instant | 252 | 107 | 0 | 2026-02-04T09:37:00Z | 63 | null | null | 2026-02-04T09:37:00Z | 2026-04-22T23:45:00Z | 2026-04-23T03:45:00Z |
In [12]:
val checkTime_ThermalWasteWater = 20.minutes
val checked_ThermalWasteWater = converted_ThermalWasteWater.filter {
(time - rm001_time).absoluteValue <= checkTime_ThermalWasteWater &&
(time - rm002_time).absoluteValue <= checkTime_ThermalWasteWater &&
(time - rm005_time).absoluteValue <= checkTime_ThermalWasteWater &&
(time - rm006_time).absoluteValue <= checkTime_ThermalWasteWater
}
In [13]:
val filterTime_ThermalWasteWater = Clock.System.now().minus(6, DateTimeUnit.HOUR)
val final_ThermalWasteWater = checked_ThermalWasteWater.filter{ time >= filterTime_ThermalWasteWater }
.convert {
time and rm001_time and rm002_time and rm005_time and rm006_time
}.with {
it.toLocalDateTime(TimeZone.of("Asia/Seoul")).format(LocalDateTime.Format{byUnicodePattern("yyyy-MM-dd HH:mm")})
}
final_ThermalWasteWater.describe()
Out[13]:
DataFrame: rowsCount = 10, columnsCount = 12
| name | type | count | unique | nulls | top | freq | mean | std | min | median | max |
|---|---|---|---|---|---|---|---|---|---|---|---|
| time | String | 186 | 63 | 0 | 2026-04-23 06:54 | 3 | null | null | 2026-04-23 06:54 | 2026-04-23 09:44 | 2026-04-23 12:45 |
| genName | String | 186 | 3 | 0 | UJ | 63 | null | null | UJ | WS | YK |
| rm001 | Double | 186 | 15 | 0 | 13.600000 | 38 | 13.736559 | 1.985531 | 11.300000 | 13.600000 | 16.600000 |
| rm001_time | String | 186 | 119 | 0 | 2026-04-23 10:10 | 5 | null | null | 2026-04-23 06:45 | 2026-04-23 09:40 | 2026-04-23 12:45 |
| rm002 | Double | 186 | 8 | 0 | 34.200000 | 58 | 33.390860 | 0.761345 | 32.300000 | 33.600000 | 34.200000 |
| rm002_time | String | 186 | 121 | 0 | 2026-04-23 10:10 | 5 | null | null | 2026-04-23 06:40 | 2026-04-23 09:40 | 2026-04-23 12:45 |
| rm005 | Double | 186 | 17 | 0 | 13.500000 | 31 | 18.177957 | 4.371072 | 13.500000 | 16.800000 | 24.300000 |
| rm005_time | String | 186 | 119 | 0 | 2026-04-23 10:10 | 5 | null | null | 2026-04-23 06:45 | 2026-04-23 09:40 | 2026-04-23 12:45 |
| rm006 | Double | 186 | 11 | 0 | 32.900000 | 59 | 32.916129 | 0.451613 | 32.300000 | 32.900000 | 33.800000 |
| rm006_time | String | 186 | 106 | 0 | 2026-04-23 10:10 | 5 | null | null | 2026-04-23 06:40 | 2026-04-23 09:40 | 2026-04-23 12:45 |
In [14]:
val data_ThermalWasteWater = final_ThermalWasteWater.toMap()
In [15]:
letsPlot(data_ThermalWasteWater) +
geomRibbon(
alpha = 0.2,
tooltips = layerTooltips()
.title("@genName") // 툴팁 제목에 발전소 이름 표시
.line("시간|@time") // '라벨|값' 형식으로 표시
.line("취수 수온|@rm001 °C")
.line("배수 수온|@rm005 °C")
.format("@rm001", ".1f") // 소수점 첫째 자리까지 표시
.format("@rm005", ".1f")
){
x="time"
ymin = "rm001"
ymax = "rm005"
fill = "genName"
} +
scaleYContinuous(limits=Pair(5.0, 30.0)) +
labs( title = "발전소별 실시간 취수/배수 수온 현황", y="수온(°C)", x="수집시간", fill="발전소") +
theme(
axisTextX= elementText( angle=45),
panelGridMajorX = elementLine(color = "#e0e0e0", size = 0.5)
) +
ggsize( width = 2400, height = 450)
Out[15]:
In [ ]: