aark 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aark/__init__.py +3 -0
- aark/ncm/__init__.py +1 -0
- aark/ncm/sched.py +511 -0
- aark-0.1.0.dist-info/METADATA +532 -0
- aark-0.1.0.dist-info/RECORD +7 -0
- aark-0.1.0.dist-info/WHEEL +4 -0
- aark-0.1.0.dist-info/licenses/LICENSE +28 -0
aark/__init__.py
ADDED
aark/ncm/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""National Calculation Methodology (NCM): https://www.uk-ncm.org.uk/."""
|
aark/ncm/sched.py
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""Convert NCM schedules into epJSON objects.
|
|
2
|
+
|
|
3
|
+
Caveats:
|
|
4
|
+
- a non-leap year is assumed
|
|
5
|
+
- other gains schedules are not converted
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Iterable, Sequence
|
|
13
|
+
|
|
14
|
+
import pyodbc
|
|
15
|
+
|
|
16
|
+
type PyODBCRows = list[pyodbc.Row]
|
|
17
|
+
type PyODBCCursor = pyodbc.Cursor
|
|
18
|
+
type SchedMap = dict[int, dict[str, set[str]]]
|
|
19
|
+
type epJSONObjBody = dict[str, object] # noqa: N816, PYI042
|
|
20
|
+
type epJSONObjs = dict[str, epJSONObjBody] # noqa: N816, PYI042
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ACTIVITY_SCHED_COLUMN_NAMES = (
|
|
24
|
+
"OCCUPANCY_SCH",
|
|
25
|
+
"LIGHTING_SCH",
|
|
26
|
+
"EQUIPMENT_SCH",
|
|
27
|
+
"COOL_SET_SCH",
|
|
28
|
+
"HEAT_SET_SCH",
|
|
29
|
+
# "OTHER_GAINS_SCH", # secondary schedule id to look up in [activity_other_gains]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
WEEKLY_SCHED_DAY_TYPES = (
|
|
33
|
+
"MONDAY",
|
|
34
|
+
"TUESDAY",
|
|
35
|
+
"WEDNESDAY",
|
|
36
|
+
"THURSDAY",
|
|
37
|
+
"FRIDAY",
|
|
38
|
+
"SATURDAY",
|
|
39
|
+
"SUNDAY",
|
|
40
|
+
"HOLIDAY",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
MONTH_ABBR2NUM = {
|
|
44
|
+
"Jan": 1,
|
|
45
|
+
"Feb": 2,
|
|
46
|
+
"Mar": 3,
|
|
47
|
+
"Apr": 4,
|
|
48
|
+
"May": 5,
|
|
49
|
+
"Jun": 6,
|
|
50
|
+
"Jul": 7,
|
|
51
|
+
"Aug": 8,
|
|
52
|
+
"Sep": 9,
|
|
53
|
+
"Oct": 10,
|
|
54
|
+
"Nov": 11,
|
|
55
|
+
"Dec": 12,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
SCHED_TYPE_LIMITS_OBJS = {
|
|
59
|
+
"FRACTION": {
|
|
60
|
+
"lower_limit_value": 0,
|
|
61
|
+
"upper_limit_value": 1,
|
|
62
|
+
"numeric_type": "Continuous",
|
|
63
|
+
"unit_type": "Dimensionless",
|
|
64
|
+
},
|
|
65
|
+
"ON/OFF": {
|
|
66
|
+
"lower_limit_value": 0,
|
|
67
|
+
"upper_limit_value": 1,
|
|
68
|
+
"numeric_type": "Discrete",
|
|
69
|
+
"unit_type": "Availability",
|
|
70
|
+
},
|
|
71
|
+
"TEMPERATURE": {
|
|
72
|
+
"lower_limit_value": -100,
|
|
73
|
+
"upper_limit_value": 100,
|
|
74
|
+
"numeric_type": "Continuous",
|
|
75
|
+
"unit_type": "Temperature",
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _fetch_filter(
|
|
81
|
+
table_name: str,
|
|
82
|
+
column_name: str,
|
|
83
|
+
column_values: Iterable[object],
|
|
84
|
+
cursor: PyODBCCursor,
|
|
85
|
+
) -> PyODBCRows:
|
|
86
|
+
"""Fetch rows of a table filtered by values in a given column.
|
|
87
|
+
|
|
88
|
+
NOTE: this fetch and filter approach is used instead of a SQL query with a WHERE
|
|
89
|
+
clause, because some ODBC driver apears to have a limit on the number of column
|
|
90
|
+
values to be filtered via WHERE.
|
|
91
|
+
"""
|
|
92
|
+
column_values = set(column_values)
|
|
93
|
+
|
|
94
|
+
cursor.execute(f"SELECT * FROM [{table_name}]")
|
|
95
|
+
rows = cursor.fetchall()
|
|
96
|
+
return [row for row in rows if getattr(row, column_name) in column_values]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _next_month_day(month: int, day: int) -> tuple[int, int]:
|
|
100
|
+
"""Return the next month and day given a month and day.
|
|
101
|
+
|
|
102
|
+
NOTE: this function hardcodes a non-leap year.
|
|
103
|
+
"""
|
|
104
|
+
date = datetime.date(2026, month, day) + datetime.timedelta(days=1)
|
|
105
|
+
return date.month, date.day
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _add_sched_map(
|
|
109
|
+
sched_map: SchedMap, annual_sched_id: int, ep_obj_type: str, ep_obj_name: str
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Add mapping to an epJSON object name by annual schedule id and EP object type."""
|
|
112
|
+
sched_map.setdefault(annual_sched_id, {})
|
|
113
|
+
sched_map[annual_sched_id].setdefault(ep_obj_type, set())
|
|
114
|
+
sched_map[annual_sched_id][ep_obj_type].add(ep_obj_name)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _add_epjson_obj(
|
|
118
|
+
epjson_objs: epJSONObjs, ep_obj_name: str, epjson_obj_body: epJSONObjBody
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Add an epJSON object."""
|
|
121
|
+
if (ep_obj_name in epjson_objs) and (epjson_objs[ep_obj_name] != epjson_obj_body):
|
|
122
|
+
raise ValueError(f"object key collision with different content: {ep_obj_name}")
|
|
123
|
+
|
|
124
|
+
epjson_objs.update({ep_obj_name: epjson_obj_body})
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def read_scheds(
|
|
128
|
+
activity_rows: PyODBCRows, cursor: PyODBCCursor
|
|
129
|
+
) -> tuple[PyODBCRows, PyODBCRows, PyODBCRows, PyODBCRows, PyODBCRows]:
|
|
130
|
+
"""Read NCM activity schedule data.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
activity_rows : PyODBCRows
|
|
135
|
+
Rows of the `[activity]` table.
|
|
136
|
+
cursor : PyODBCCursor
|
|
137
|
+
Open cursor of the NCM activity database.
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
tuple[PyODBCRows, PyODBCRows, PyODBCRows, PyODBCRows, PyODBCRows]
|
|
142
|
+
A tuple containing:
|
|
143
|
+
- rows of the `[annual_schedules]` table.
|
|
144
|
+
- rows of the `[annual_weekly_schedules]` table.
|
|
145
|
+
- rows of the `[weekly_schedules]` table.
|
|
146
|
+
- rows of the `[daily_schedules]` table.
|
|
147
|
+
- rows of the `[schedules_type]` table.
|
|
148
|
+
"""
|
|
149
|
+
# get schedule type rows
|
|
150
|
+
cursor.execute("SELECT * FROM [schedules_type]")
|
|
151
|
+
sched_type_rows = cursor.fetchall()
|
|
152
|
+
|
|
153
|
+
# get annual schedule ids used in the [activity] table
|
|
154
|
+
annual_sched_ids = {
|
|
155
|
+
getattr(row, column_name)
|
|
156
|
+
for row in activity_rows
|
|
157
|
+
for column_name in ACTIVITY_SCHED_COLUMN_NAMES
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# get annual schedule rows
|
|
161
|
+
annual_sched_rows = _fetch_filter(
|
|
162
|
+
"annual_schedules", "ID", annual_sched_ids, cursor
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# get annual weekly schedule rows
|
|
166
|
+
# note that annual schedules work differently from weekly and daily schedules
|
|
167
|
+
# as they have an indefinite number of segments
|
|
168
|
+
annual_weekly_sched_rows = _fetch_filter(
|
|
169
|
+
"annual_weekly_schedules", "ANNUAL_SCHEDULE", annual_sched_ids, cursor
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# get weekly schedule ids used in the [annual_weekly_schedules] table
|
|
173
|
+
weekly_sched_ids = {row.WEEKLY_SCHEDULE for row in annual_weekly_sched_rows}
|
|
174
|
+
|
|
175
|
+
# get weekly schedule rows
|
|
176
|
+
weekly_sched_rows = _fetch_filter(
|
|
177
|
+
"weekly_schedules", "ID", weekly_sched_ids, cursor
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# get daily schedule ids used in the [weekly_schedules] table
|
|
181
|
+
daily_sched_ids = {
|
|
182
|
+
getattr(row, day_type)
|
|
183
|
+
for row in weekly_sched_rows
|
|
184
|
+
for day_type in WEEKLY_SCHED_DAY_TYPES
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# get daily schedule rows
|
|
188
|
+
daily_sched_rows = _fetch_filter("daily_schedules", "ID", daily_sched_ids, cursor)
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
annual_sched_rows,
|
|
192
|
+
annual_weekly_sched_rows,
|
|
193
|
+
weekly_sched_rows,
|
|
194
|
+
daily_sched_rows,
|
|
195
|
+
sched_type_rows,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def convert_scheds( # noqa: PLR0915
|
|
200
|
+
annual_sched_rows: PyODBCRows,
|
|
201
|
+
annual_weekly_sched_rows: PyODBCRows,
|
|
202
|
+
weekly_sched_rows: PyODBCRows,
|
|
203
|
+
daily_sched_rows: PyODBCRows,
|
|
204
|
+
sched_type_rows: PyODBCRows,
|
|
205
|
+
) -> tuple[SchedMap, epJSONObjs]:
|
|
206
|
+
"""Convert NCM schedule data into an epJSON schedule library.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
annual_sched_rows : PyODBCRows
|
|
211
|
+
Rows of the `[annual_schedules]` table.
|
|
212
|
+
annual_weekly_sched_rows : PyODBCRows
|
|
213
|
+
Rows of the `[annual_weekly_schedules]` table.
|
|
214
|
+
weekly_sched_rows : PyODBCRows
|
|
215
|
+
Rows of the `[weekly_schedules]` table.
|
|
216
|
+
daily_sched_rows : PyODBCRows
|
|
217
|
+
Rows of the `[daily_schedules]` table.
|
|
218
|
+
sched_type_rows : PyODBCRows
|
|
219
|
+
Rows of the `[schedules_type]` table.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
tuple[SchedMap, epJSONObjs]
|
|
224
|
+
An epJSON schedule library containing:
|
|
225
|
+
- a map of annual schedule ids to epJSON object names grouped by EP object type.
|
|
226
|
+
- a map of epJSON object names to epJSON object bodies.
|
|
227
|
+
"""
|
|
228
|
+
# -----------------------------------------------------------------------------
|
|
229
|
+
# 1) create maps of ids to names
|
|
230
|
+
# -----------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
# create a map of schedule type ids to names
|
|
233
|
+
sched_type_id2name = {row.ID: row.COD for row in sched_type_rows}
|
|
234
|
+
|
|
235
|
+
# create a map of ncm ids to ep object names for annual schedules
|
|
236
|
+
annual_sched_id2name = {
|
|
237
|
+
row.ID: f"ncm-annual-{row.ID}-{row.NAME}" for row in annual_sched_rows
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# create a map of ncm ids to ep object names for weekly schedules
|
|
241
|
+
weekly_sched_id2name = {
|
|
242
|
+
row.ID: f"ncm-weekly-{row.ID}-{row.NAME}" for row in weekly_sched_rows
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# create a map of ncm ids to ep object names for daily schedules
|
|
246
|
+
daily_sched_id2name = {
|
|
247
|
+
row.ID: f"ncm-daily-{row.ID}-{row.NAME}" for row in daily_sched_rows
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
# -----------------------------------------------------------------------------
|
|
251
|
+
# 2) create a map of annual schedule ids to epJSON object names by object type
|
|
252
|
+
# -----------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
sched_map: SchedMap = {}
|
|
255
|
+
|
|
256
|
+
for annual_sched_row in annual_sched_rows:
|
|
257
|
+
# get and add the annual schedule's ep object name
|
|
258
|
+
# to the `Schedule:Year` set
|
|
259
|
+
annual_sched_id = annual_sched_row.ID
|
|
260
|
+
annual_sched_name = annual_sched_id2name[annual_sched_id]
|
|
261
|
+
|
|
262
|
+
_add_sched_map(sched_map, annual_sched_id, "Schedule:Year", annual_sched_name)
|
|
263
|
+
|
|
264
|
+
# get and add the annual schedule type's ep object name
|
|
265
|
+
annual_sched_type_name = sched_type_id2name[annual_sched_row.TYPE]
|
|
266
|
+
_add_sched_map(
|
|
267
|
+
sched_map, annual_sched_id, "ScheduleTypeLimits", annual_sched_type_name
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
for annual_weekly_sched_row in annual_weekly_sched_rows:
|
|
271
|
+
if annual_weekly_sched_row.ANNUAL_SCHEDULE != annual_sched_id:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
# get and add the weekly schedule's ep object name
|
|
275
|
+
# to the `Schedule:Week:Daily` set
|
|
276
|
+
weekly_sched_id = annual_weekly_sched_row.WEEKLY_SCHEDULE
|
|
277
|
+
weekly_sched_name = weekly_sched_id2name[weekly_sched_id]
|
|
278
|
+
|
|
279
|
+
_add_sched_map(
|
|
280
|
+
sched_map, annual_sched_id, "Schedule:Week:Daily", weekly_sched_name
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# get the weekly schedule row coincident with the weekly schedule id
|
|
284
|
+
(weekly_sched_row,) = (
|
|
285
|
+
row for row in weekly_sched_rows if row.ID == weekly_sched_id
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
for day_type in WEEKLY_SCHED_DAY_TYPES:
|
|
289
|
+
# get and add the daily schedule's ep object name
|
|
290
|
+
# to the `Schedule:Day:Hourly` set
|
|
291
|
+
daily_sched_id = getattr(weekly_sched_row, day_type)
|
|
292
|
+
daily_sched_name = daily_sched_id2name[daily_sched_id]
|
|
293
|
+
|
|
294
|
+
_add_sched_map(
|
|
295
|
+
sched_map, annual_sched_id, "Schedule:Day:Hourly", daily_sched_name
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# get the daily schedule row coincident with the daily schedule id
|
|
299
|
+
(daily_sched_row,) = (
|
|
300
|
+
row for row in daily_sched_rows if row.ID == daily_sched_id
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# get and add the daily schedule type's ep object name
|
|
304
|
+
daily_sched_type_name = sched_type_id2name[daily_sched_row.TYPE]
|
|
305
|
+
_add_sched_map(
|
|
306
|
+
sched_map,
|
|
307
|
+
annual_sched_id,
|
|
308
|
+
"ScheduleTypeLimits",
|
|
309
|
+
daily_sched_type_name,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# -----------------------------------------------------------------------------
|
|
313
|
+
# 3) convert annual, weekly and daily schedules to epJSON objects
|
|
314
|
+
# -----------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
epjson_objs: epJSONObjs = {}
|
|
317
|
+
|
|
318
|
+
# add epJSON `ScheduleTypeLimits` objects
|
|
319
|
+
for sched_type_name, epjson_obj_body in SCHED_TYPE_LIMITS_OBJS.items():
|
|
320
|
+
_add_epjson_obj(epjson_objs, sched_type_name, epjson_obj_body)
|
|
321
|
+
|
|
322
|
+
# convert annual schedules to epJSON `Schedule:Year` objects
|
|
323
|
+
for annual_sched_row in annual_sched_rows:
|
|
324
|
+
annual_sched_id = annual_sched_row.ID
|
|
325
|
+
annual_sched_name = annual_sched_id2name[annual_sched_id]
|
|
326
|
+
|
|
327
|
+
# create the list of schedule weeks given the annual schedule
|
|
328
|
+
# add placeholders for start month and start day
|
|
329
|
+
# sort the schedule weeks by end month and end day
|
|
330
|
+
sched_weeks = []
|
|
331
|
+
|
|
332
|
+
for annual_weekly_sched_row in annual_weekly_sched_rows:
|
|
333
|
+
if annual_weekly_sched_row.ANNUAL_SCHEDULE != annual_sched_id:
|
|
334
|
+
continue
|
|
335
|
+
|
|
336
|
+
sched_weeks.append(
|
|
337
|
+
{
|
|
338
|
+
"schedule_week_name": weekly_sched_id2name[
|
|
339
|
+
annual_weekly_sched_row.WEEKLY_SCHEDULE
|
|
340
|
+
],
|
|
341
|
+
"start_month": -1,
|
|
342
|
+
"start_day": -1,
|
|
343
|
+
"end_month": MONTH_ABBR2NUM[annual_weekly_sched_row.END_MONTH],
|
|
344
|
+
"end_day": int(annual_weekly_sched_row.END_DAY),
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
sched_weeks.sort(key=lambda x: (x["end_month"], x["end_day"]))
|
|
349
|
+
|
|
350
|
+
# check that the last end month and end day are 12 and 31
|
|
351
|
+
assert sched_weeks[-1]["end_month"] == 12
|
|
352
|
+
assert sched_weeks[-1]["end_day"] == 31
|
|
353
|
+
|
|
354
|
+
# update start month and start day for each schedule week
|
|
355
|
+
# based on the end month and end day of the previous schedule week
|
|
356
|
+
for i, sched_week in enumerate(sched_weeks):
|
|
357
|
+
if i == 0:
|
|
358
|
+
start_month, start_day = 1, 1
|
|
359
|
+
else:
|
|
360
|
+
start_month, start_day = _next_month_day(
|
|
361
|
+
sched_weeks[i - 1]["end_month"], # type: ignore[arg-type]
|
|
362
|
+
sched_weeks[i - 1]["end_day"], # type: ignore[arg-type]
|
|
363
|
+
)
|
|
364
|
+
sched_week["start_month"] = start_month
|
|
365
|
+
sched_week["start_day"] = start_day
|
|
366
|
+
|
|
367
|
+
# get schedule type name
|
|
368
|
+
sched_type_name = sched_type_id2name[annual_sched_row.TYPE]
|
|
369
|
+
|
|
370
|
+
# create and add the epJSON `Schedule:Year` object
|
|
371
|
+
epjson_obj_body = {
|
|
372
|
+
"schedule_type_limits_name": sched_type_name,
|
|
373
|
+
"schedule_weeks": sched_weeks,
|
|
374
|
+
}
|
|
375
|
+
_add_epjson_obj(epjson_objs, annual_sched_name, epjson_obj_body)
|
|
376
|
+
|
|
377
|
+
# convert weekly schedules to epJSON `Schedule:Week:Daily` objects
|
|
378
|
+
for weekly_sched_row in weekly_sched_rows:
|
|
379
|
+
weekly_sched_name = weekly_sched_id2name[weekly_sched_row.ID]
|
|
380
|
+
|
|
381
|
+
# create and add the epJSON `Schedule:Week:Daily` object
|
|
382
|
+
epjson_obj_body = {
|
|
383
|
+
f"{day_type.lower()}_schedule_day_name": daily_sched_id2name[
|
|
384
|
+
getattr(weekly_sched_row, day_type)
|
|
385
|
+
]
|
|
386
|
+
for day_type in WEEKLY_SCHED_DAY_TYPES
|
|
387
|
+
}
|
|
388
|
+
# TODO: customday1 and customday2 can probably be safely ignored
|
|
389
|
+
# but summerdesignday and winterdesignday need a better way to be handled
|
|
390
|
+
fallback_daily_sched_name = epjson_obj_body["holiday_schedule_day_name"]
|
|
391
|
+
epjson_obj_body |= {
|
|
392
|
+
"summerdesignday_schedule_day_name": fallback_daily_sched_name,
|
|
393
|
+
"winterdesignday_schedule_day_name": fallback_daily_sched_name,
|
|
394
|
+
"customday1_schedule_day_name": fallback_daily_sched_name,
|
|
395
|
+
"customday2_schedule_day_name": fallback_daily_sched_name,
|
|
396
|
+
}
|
|
397
|
+
_add_epjson_obj(epjson_objs, weekly_sched_name, epjson_obj_body)
|
|
398
|
+
|
|
399
|
+
# convert daily schedules to epJSON `Schedule:Day:Hourly` objects
|
|
400
|
+
for daily_sched_row in daily_sched_rows:
|
|
401
|
+
daily_sched_name = daily_sched_id2name[daily_sched_row.ID]
|
|
402
|
+
|
|
403
|
+
# get schedule type name
|
|
404
|
+
sched_type_name = sched_type_id2name[daily_sched_row.TYPE]
|
|
405
|
+
|
|
406
|
+
# create and add the epJSON `Schedule:Day:Hourly` object
|
|
407
|
+
epjson_obj_body = {"sched_type_limits_name": sched_type_name}
|
|
408
|
+
epjson_obj_body |= {
|
|
409
|
+
f"hour_{i + 1}": getattr(daily_sched_row, f"h{i:02d}") for i in range(24)
|
|
410
|
+
}
|
|
411
|
+
_add_epjson_obj(epjson_objs, daily_sched_name, epjson_obj_body)
|
|
412
|
+
|
|
413
|
+
return sched_map, epjson_objs
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def pick_scheds(
|
|
417
|
+
room_names: Sequence[str],
|
|
418
|
+
sched_categories: Sequence[str],
|
|
419
|
+
sched_map: SchedMap,
|
|
420
|
+
epjson_objs: epJSONObjs,
|
|
421
|
+
activity_rows: PyODBCRows,
|
|
422
|
+
) -> dict[str, epJSONObjs]:
|
|
423
|
+
"""Pick epJSON schedule objects given rooms and schedule categories.
|
|
424
|
+
|
|
425
|
+
Parameters
|
|
426
|
+
----------
|
|
427
|
+
room_names : Sequence[str]
|
|
428
|
+
NCM room names of interest.
|
|
429
|
+
sched_categories : Sequence[str]
|
|
430
|
+
NCM schedule categories of interest.
|
|
431
|
+
sched_map : SchedMap
|
|
432
|
+
Map of annual schedule ids to epJSON object names grouped by EP object type.
|
|
433
|
+
epjson_objs : epJSONObjs
|
|
434
|
+
Map of epJSON object names to epJSON object bodies.
|
|
435
|
+
activity_rows : PyODBCRows
|
|
436
|
+
Rows of the `[activity]` table.
|
|
437
|
+
|
|
438
|
+
Returns
|
|
439
|
+
-------
|
|
440
|
+
dict[str, epJSONObjs]
|
|
441
|
+
An epJSON of schedules.
|
|
442
|
+
"""
|
|
443
|
+
annual_sched_ids = {
|
|
444
|
+
getattr(row, column_name)
|
|
445
|
+
for row in activity_rows
|
|
446
|
+
if row.NAME in room_names
|
|
447
|
+
for column_name in sched_categories
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
sched_epjson: dict[str, epJSONObjs] = {
|
|
451
|
+
"ScheduleTypeLimits": {},
|
|
452
|
+
"Schedule:Day:Hourly": {},
|
|
453
|
+
"Schedule:Week:Daily": {},
|
|
454
|
+
"Schedule:Year": {},
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
for annual_sched_id in annual_sched_ids:
|
|
458
|
+
for ep_obj_type, ep_obj_names in sched_map[annual_sched_id].items():
|
|
459
|
+
for ep_obj_name in sorted(ep_obj_names):
|
|
460
|
+
sched_epjson[ep_obj_type][ep_obj_name] = epjson_objs[ep_obj_name]
|
|
461
|
+
|
|
462
|
+
return sched_epjson
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def get_scheds(
|
|
466
|
+
room_names: Sequence[str], sched_categories: Sequence[str], cursor: PyODBCCursor
|
|
467
|
+
) -> dict[str, epJSONObjs]:
|
|
468
|
+
"""Get an epJSON of schedules given rooms and schedule categories.
|
|
469
|
+
|
|
470
|
+
This is a helper function that streamlines `read_scheds`, `convert_scheds` and
|
|
471
|
+
`pick_scheds` into a single function call.
|
|
472
|
+
|
|
473
|
+
Parameters
|
|
474
|
+
----------
|
|
475
|
+
room_names : Sequence[str]
|
|
476
|
+
NCM room names of interest.
|
|
477
|
+
sched_categories : Sequence[str]
|
|
478
|
+
NCM schedule category column names of interest.
|
|
479
|
+
cursor : PyODBCCursor
|
|
480
|
+
Open cursor of the NCM activity database.
|
|
481
|
+
|
|
482
|
+
Returns
|
|
483
|
+
-------
|
|
484
|
+
dict[str, epJSONObjs]
|
|
485
|
+
An epJSON of schedules.
|
|
486
|
+
"""
|
|
487
|
+
# get activity rows
|
|
488
|
+
activity_rows = _fetch_filter("activity", "NAME", room_names, cursor)
|
|
489
|
+
|
|
490
|
+
# read NCM schedule data
|
|
491
|
+
(
|
|
492
|
+
annual_sched_rows,
|
|
493
|
+
annual_weekly_sched_rows,
|
|
494
|
+
weekly_sched_rows,
|
|
495
|
+
daily_sched_rows,
|
|
496
|
+
sched_type_rows,
|
|
497
|
+
) = read_scheds(activity_rows, cursor)
|
|
498
|
+
|
|
499
|
+
# convert NCM schedule data into an epJSON schedule library
|
|
500
|
+
sched_map, epjson_objs = convert_scheds(
|
|
501
|
+
annual_sched_rows,
|
|
502
|
+
annual_weekly_sched_rows,
|
|
503
|
+
weekly_sched_rows,
|
|
504
|
+
daily_sched_rows,
|
|
505
|
+
sched_type_rows,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
# pick epJSON schedule objects given rooms and schedule categories
|
|
509
|
+
return pick_scheds(
|
|
510
|
+
room_names, sched_categories, sched_map, epjson_objs, activity_rows
|
|
511
|
+
)
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aark
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: airallergy's research kit
|
|
5
|
+
Project-URL: repository, https://github.com/airallergy/aark
|
|
6
|
+
Author-email: Cheng Cui <cheng.cui.95@gmail.com>
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.14
|
|
19
|
+
Requires-Dist: pyodbc==5.*
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# `aark`
|
|
23
|
+
|
|
24
|
+
airallergy’s research kit
|
|
25
|
+
|
|
26
|
+
`aark` is a collection of Python tools for my research in built environment.
|
|
27
|
+
|
|
28
|
+
**Table of contents**
|
|
29
|
+
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [NCM](#ncm)
|
|
32
|
+
- [Get activity schedules in the epJSON format](#get-activity-schedules-in-the-epjson-format)
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### NCM
|
|
37
|
+
|
|
38
|
+
Tools for analysing the [National Calculation Methodology (NCM) database](https://www.uk-ncm.org.uk/), tested primarily on data related to English dwellings.
|
|
39
|
+
|
|
40
|
+
> [!TIP]
|
|
41
|
+
> To access the NCM database, the NCM modules depend on `pyodbc`, which in turn depends on an ODBC driver and driver manager for Microsoft Access. See [`pyodbc`'s installation guide](https://github.com/mkleehammer/pyodbc/wiki/Install) for details.
|
|
42
|
+
|
|
43
|
+
#### Get activity schedules in the epJSON format
|
|
44
|
+
|
|
45
|
+
The `aark.ncm.sched` module provides functions for extracting NCM activity schedules and converting them into the epJSON format for EnergyPlus. In particular, the `aark.ncm.sched.get_scheds` function takes a sequence of NCM room names, a sequence of NCM schedule categories and a `pyodbc.Cursor` object for the NCM activity database, and returns an epJSON of schedule objects ready for EnergyPlus simulations.
|
|
46
|
+
|
|
47
|
+
> [!IMPORTANT]
|
|
48
|
+
> This module currently has the following caveats:
|
|
49
|
+
>
|
|
50
|
+
> - A non-leap year is assumed.
|
|
51
|
+
> - Other gains schedules are not converted.
|
|
52
|
+
|
|
53
|
+
For instance, the following script retrieves the occupancy and heating set point schedules for the bedroom and the lounge.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import pyodbc
|
|
57
|
+
|
|
58
|
+
import aark.ncm.sched
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
room_names = ["Dwell_DomBed", "Dwell_DomLounge"]
|
|
62
|
+
sched_categories = ["OCCUPANCY_SCH", "HEAT_SET_SCH"]
|
|
63
|
+
|
|
64
|
+
with pyodbc.connect(
|
|
65
|
+
Driver="Microsoft Access Driver (*.mdb, *.accdb)", DBQ="/path/to/the/database"
|
|
66
|
+
) as con:
|
|
67
|
+
cur = con.cursor()
|
|
68
|
+
|
|
69
|
+
sched_epjson = aark.ncm.sched.get_scheds(
|
|
70
|
+
room_names=room_names, sched_categories=sched_categories, cursor=cur
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The `sched_epjson` variable contains the following epJSON of schedule objects.
|
|
75
|
+
|
|
76
|
+
<details>
|
|
77
|
+
<summary>Full epJSON</summary>
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"ScheduleTypeLimits": {
|
|
82
|
+
"TEMPERATURE": {
|
|
83
|
+
"lower_limit_value": -100,
|
|
84
|
+
"upper_limit_value": 100,
|
|
85
|
+
"numeric_type": "Continuous",
|
|
86
|
+
"unit_type": "Temperature"
|
|
87
|
+
},
|
|
88
|
+
"FRACTION": {
|
|
89
|
+
"lower_limit_value": 0,
|
|
90
|
+
"upper_limit_value": 1,
|
|
91
|
+
"numeric_type": "Continuous",
|
|
92
|
+
"unit_type": "Dimensionless"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"Schedule:Day:Hourly": {
|
|
96
|
+
"ncm-daily-9344-Dwell_DomBed_Heat_Wkdy": {
|
|
97
|
+
"sched_type_limits_name": "TEMPERATURE",
|
|
98
|
+
"hour_1": 18,
|
|
99
|
+
"hour_2": 18,
|
|
100
|
+
"hour_3": 18,
|
|
101
|
+
"hour_4": 18,
|
|
102
|
+
"hour_5": 18,
|
|
103
|
+
"hour_6": 18,
|
|
104
|
+
"hour_7": 18,
|
|
105
|
+
"hour_8": 18,
|
|
106
|
+
"hour_9": 18,
|
|
107
|
+
"hour_10": 12,
|
|
108
|
+
"hour_11": 12,
|
|
109
|
+
"hour_12": 12,
|
|
110
|
+
"hour_13": 12,
|
|
111
|
+
"hour_14": 12,
|
|
112
|
+
"hour_15": 12,
|
|
113
|
+
"hour_16": 12,
|
|
114
|
+
"hour_17": 12,
|
|
115
|
+
"hour_18": 12,
|
|
116
|
+
"hour_19": 12,
|
|
117
|
+
"hour_20": 12,
|
|
118
|
+
"hour_21": 18,
|
|
119
|
+
"hour_22": 18,
|
|
120
|
+
"hour_23": 18,
|
|
121
|
+
"hour_24": 18
|
|
122
|
+
},
|
|
123
|
+
"ncm-daily-9345-Dwell_DomBed_Heat_Wknd": {
|
|
124
|
+
"sched_type_limits_name": "TEMPERATURE",
|
|
125
|
+
"hour_1": 18,
|
|
126
|
+
"hour_2": 18,
|
|
127
|
+
"hour_3": 18,
|
|
128
|
+
"hour_4": 18,
|
|
129
|
+
"hour_5": 18,
|
|
130
|
+
"hour_6": 18,
|
|
131
|
+
"hour_7": 18,
|
|
132
|
+
"hour_8": 18,
|
|
133
|
+
"hour_9": 18,
|
|
134
|
+
"hour_10": 12,
|
|
135
|
+
"hour_11": 12,
|
|
136
|
+
"hour_12": 12,
|
|
137
|
+
"hour_13": 12,
|
|
138
|
+
"hour_14": 12,
|
|
139
|
+
"hour_15": 12,
|
|
140
|
+
"hour_16": 12,
|
|
141
|
+
"hour_17": 12,
|
|
142
|
+
"hour_18": 12,
|
|
143
|
+
"hour_19": 12,
|
|
144
|
+
"hour_20": 12,
|
|
145
|
+
"hour_21": 18,
|
|
146
|
+
"hour_22": 18,
|
|
147
|
+
"hour_23": 18,
|
|
148
|
+
"hour_24": 18
|
|
149
|
+
},
|
|
150
|
+
"ncm-daily-9346-Dwell_DomBed_Heat_Hol": {
|
|
151
|
+
"sched_type_limits_name": "TEMPERATURE",
|
|
152
|
+
"hour_1": 18,
|
|
153
|
+
"hour_2": 18,
|
|
154
|
+
"hour_3": 18,
|
|
155
|
+
"hour_4": 18,
|
|
156
|
+
"hour_5": 18,
|
|
157
|
+
"hour_6": 18,
|
|
158
|
+
"hour_7": 18,
|
|
159
|
+
"hour_8": 18,
|
|
160
|
+
"hour_9": 18,
|
|
161
|
+
"hour_10": 12,
|
|
162
|
+
"hour_11": 12,
|
|
163
|
+
"hour_12": 12,
|
|
164
|
+
"hour_13": 12,
|
|
165
|
+
"hour_14": 12,
|
|
166
|
+
"hour_15": 12,
|
|
167
|
+
"hour_16": 12,
|
|
168
|
+
"hour_17": 12,
|
|
169
|
+
"hour_18": 12,
|
|
170
|
+
"hour_19": 12,
|
|
171
|
+
"hour_20": 12,
|
|
172
|
+
"hour_21": 18,
|
|
173
|
+
"hour_22": 18,
|
|
174
|
+
"hour_23": 18,
|
|
175
|
+
"hour_24": 18
|
|
176
|
+
},
|
|
177
|
+
"ncm-daily-9395-Dwell_DomLounge_Occ_Wkdy": {
|
|
178
|
+
"sched_type_limits_name": "FRACTION",
|
|
179
|
+
"hour_1": 0,
|
|
180
|
+
"hour_2": 0,
|
|
181
|
+
"hour_3": 0,
|
|
182
|
+
"hour_4": 0,
|
|
183
|
+
"hour_5": 0,
|
|
184
|
+
"hour_6": 0,
|
|
185
|
+
"hour_7": 0,
|
|
186
|
+
"hour_8": 0,
|
|
187
|
+
"hour_9": 0,
|
|
188
|
+
"hour_10": 0,
|
|
189
|
+
"hour_11": 0,
|
|
190
|
+
"hour_12": 0,
|
|
191
|
+
"hour_13": 0,
|
|
192
|
+
"hour_14": 0,
|
|
193
|
+
"hour_15": 0,
|
|
194
|
+
"hour_16": 0,
|
|
195
|
+
"hour_17": 0.5,
|
|
196
|
+
"hour_18": 0.5,
|
|
197
|
+
"hour_19": 1,
|
|
198
|
+
"hour_20": 1,
|
|
199
|
+
"hour_21": 1,
|
|
200
|
+
"hour_22": 1,
|
|
201
|
+
"hour_23": 0.666666667,
|
|
202
|
+
"hour_24": 0
|
|
203
|
+
},
|
|
204
|
+
"ncm-daily-9396-Dwell_DomLounge_Occ_Wknd": {
|
|
205
|
+
"sched_type_limits_name": "FRACTION",
|
|
206
|
+
"hour_1": 0,
|
|
207
|
+
"hour_2": 0,
|
|
208
|
+
"hour_3": 0,
|
|
209
|
+
"hour_4": 0,
|
|
210
|
+
"hour_5": 0,
|
|
211
|
+
"hour_6": 0,
|
|
212
|
+
"hour_7": 0,
|
|
213
|
+
"hour_8": 0,
|
|
214
|
+
"hour_9": 0,
|
|
215
|
+
"hour_10": 0,
|
|
216
|
+
"hour_11": 0,
|
|
217
|
+
"hour_12": 0,
|
|
218
|
+
"hour_13": 0,
|
|
219
|
+
"hour_14": 0,
|
|
220
|
+
"hour_15": 0,
|
|
221
|
+
"hour_16": 0,
|
|
222
|
+
"hour_17": 0.5,
|
|
223
|
+
"hour_18": 0.5,
|
|
224
|
+
"hour_19": 1,
|
|
225
|
+
"hour_20": 1,
|
|
226
|
+
"hour_21": 1,
|
|
227
|
+
"hour_22": 1,
|
|
228
|
+
"hour_23": 0.666666667,
|
|
229
|
+
"hour_24": 0
|
|
230
|
+
},
|
|
231
|
+
"ncm-daily-9397-Dwell_DomLounge_Occ_Hol": {
|
|
232
|
+
"sched_type_limits_name": "FRACTION",
|
|
233
|
+
"hour_1": 0,
|
|
234
|
+
"hour_2": 0,
|
|
235
|
+
"hour_3": 0,
|
|
236
|
+
"hour_4": 0,
|
|
237
|
+
"hour_5": 0,
|
|
238
|
+
"hour_6": 0,
|
|
239
|
+
"hour_7": 0,
|
|
240
|
+
"hour_8": 0,
|
|
241
|
+
"hour_9": 0,
|
|
242
|
+
"hour_10": 0,
|
|
243
|
+
"hour_11": 0,
|
|
244
|
+
"hour_12": 0,
|
|
245
|
+
"hour_13": 0,
|
|
246
|
+
"hour_14": 0,
|
|
247
|
+
"hour_15": 0,
|
|
248
|
+
"hour_16": 0,
|
|
249
|
+
"hour_17": 0.5,
|
|
250
|
+
"hour_18": 0.5,
|
|
251
|
+
"hour_19": 1,
|
|
252
|
+
"hour_20": 1,
|
|
253
|
+
"hour_21": 1,
|
|
254
|
+
"hour_22": 1,
|
|
255
|
+
"hour_23": 0.666666667,
|
|
256
|
+
"hour_24": 0
|
|
257
|
+
},
|
|
258
|
+
"ncm-daily-9404-Dwell_DomLounge_Heat_Wkdy": {
|
|
259
|
+
"sched_type_limits_name": "TEMPERATURE",
|
|
260
|
+
"hour_1": 12,
|
|
261
|
+
"hour_2": 12,
|
|
262
|
+
"hour_3": 12,
|
|
263
|
+
"hour_4": 12,
|
|
264
|
+
"hour_5": 12,
|
|
265
|
+
"hour_6": 12,
|
|
266
|
+
"hour_7": 12,
|
|
267
|
+
"hour_8": 12,
|
|
268
|
+
"hour_9": 12,
|
|
269
|
+
"hour_10": 12,
|
|
270
|
+
"hour_11": 12,
|
|
271
|
+
"hour_12": 12,
|
|
272
|
+
"hour_13": 12,
|
|
273
|
+
"hour_14": 12,
|
|
274
|
+
"hour_15": 21,
|
|
275
|
+
"hour_16": 21,
|
|
276
|
+
"hour_17": 21,
|
|
277
|
+
"hour_18": 21,
|
|
278
|
+
"hour_19": 21,
|
|
279
|
+
"hour_20": 21,
|
|
280
|
+
"hour_21": 21,
|
|
281
|
+
"hour_22": 21,
|
|
282
|
+
"hour_23": 21,
|
|
283
|
+
"hour_24": 12
|
|
284
|
+
},
|
|
285
|
+
"ncm-daily-9405-Dwell_DomLounge_Heat_Wknd": {
|
|
286
|
+
"sched_type_limits_name": "TEMPERATURE",
|
|
287
|
+
"hour_1": 12,
|
|
288
|
+
"hour_2": 12,
|
|
289
|
+
"hour_3": 12,
|
|
290
|
+
"hour_4": 12,
|
|
291
|
+
"hour_5": 12,
|
|
292
|
+
"hour_6": 12,
|
|
293
|
+
"hour_7": 12,
|
|
294
|
+
"hour_8": 12,
|
|
295
|
+
"hour_9": 12,
|
|
296
|
+
"hour_10": 12,
|
|
297
|
+
"hour_11": 12,
|
|
298
|
+
"hour_12": 12,
|
|
299
|
+
"hour_13": 12,
|
|
300
|
+
"hour_14": 12,
|
|
301
|
+
"hour_15": 21,
|
|
302
|
+
"hour_16": 21,
|
|
303
|
+
"hour_17": 21,
|
|
304
|
+
"hour_18": 21,
|
|
305
|
+
"hour_19": 21,
|
|
306
|
+
"hour_20": 21,
|
|
307
|
+
"hour_21": 21,
|
|
308
|
+
"hour_22": 21,
|
|
309
|
+
"hour_23": 21,
|
|
310
|
+
"hour_24": 12
|
|
311
|
+
},
|
|
312
|
+
"ncm-daily-9406-Dwell_DomLounge_Heat_Hol": {
|
|
313
|
+
"sched_type_limits_name": "TEMPERATURE",
|
|
314
|
+
"hour_1": 12,
|
|
315
|
+
"hour_2": 12,
|
|
316
|
+
"hour_3": 12,
|
|
317
|
+
"hour_4": 12,
|
|
318
|
+
"hour_5": 12,
|
|
319
|
+
"hour_6": 12,
|
|
320
|
+
"hour_7": 12,
|
|
321
|
+
"hour_8": 12,
|
|
322
|
+
"hour_9": 12,
|
|
323
|
+
"hour_10": 12,
|
|
324
|
+
"hour_11": 12,
|
|
325
|
+
"hour_12": 12,
|
|
326
|
+
"hour_13": 12,
|
|
327
|
+
"hour_14": 12,
|
|
328
|
+
"hour_15": 21,
|
|
329
|
+
"hour_16": 21,
|
|
330
|
+
"hour_17": 21,
|
|
331
|
+
"hour_18": 21,
|
|
332
|
+
"hour_19": 21,
|
|
333
|
+
"hour_20": 21,
|
|
334
|
+
"hour_21": 21,
|
|
335
|
+
"hour_22": 21,
|
|
336
|
+
"hour_23": 21,
|
|
337
|
+
"hour_24": 12
|
|
338
|
+
},
|
|
339
|
+
"ncm-daily-9335-Dwell_DomBed_Occ_Wkdy": {
|
|
340
|
+
"sched_type_limits_name": "FRACTION",
|
|
341
|
+
"hour_1": 1,
|
|
342
|
+
"hour_2": 1,
|
|
343
|
+
"hour_3": 1,
|
|
344
|
+
"hour_4": 1,
|
|
345
|
+
"hour_5": 1,
|
|
346
|
+
"hour_6": 1,
|
|
347
|
+
"hour_7": 1,
|
|
348
|
+
"hour_8": 0.5,
|
|
349
|
+
"hour_9": 0.25,
|
|
350
|
+
"hour_10": 0,
|
|
351
|
+
"hour_11": 0,
|
|
352
|
+
"hour_12": 0,
|
|
353
|
+
"hour_13": 0,
|
|
354
|
+
"hour_14": 0,
|
|
355
|
+
"hour_15": 0,
|
|
356
|
+
"hour_16": 0,
|
|
357
|
+
"hour_17": 0,
|
|
358
|
+
"hour_18": 0,
|
|
359
|
+
"hour_19": 0,
|
|
360
|
+
"hour_20": 0,
|
|
361
|
+
"hour_21": 0,
|
|
362
|
+
"hour_22": 0,
|
|
363
|
+
"hour_23": 0.25,
|
|
364
|
+
"hour_24": 0.75
|
|
365
|
+
},
|
|
366
|
+
"ncm-daily-9336-Dwell_DomBed_Occ_Wknd": {
|
|
367
|
+
"sched_type_limits_name": "FRACTION",
|
|
368
|
+
"hour_1": 1,
|
|
369
|
+
"hour_2": 1,
|
|
370
|
+
"hour_3": 1,
|
|
371
|
+
"hour_4": 1,
|
|
372
|
+
"hour_5": 1,
|
|
373
|
+
"hour_6": 1,
|
|
374
|
+
"hour_7": 1,
|
|
375
|
+
"hour_8": 0.5,
|
|
376
|
+
"hour_9": 0.25,
|
|
377
|
+
"hour_10": 0,
|
|
378
|
+
"hour_11": 0,
|
|
379
|
+
"hour_12": 0,
|
|
380
|
+
"hour_13": 0,
|
|
381
|
+
"hour_14": 0,
|
|
382
|
+
"hour_15": 0,
|
|
383
|
+
"hour_16": 0,
|
|
384
|
+
"hour_17": 0,
|
|
385
|
+
"hour_18": 0,
|
|
386
|
+
"hour_19": 0,
|
|
387
|
+
"hour_20": 0,
|
|
388
|
+
"hour_21": 0,
|
|
389
|
+
"hour_22": 0,
|
|
390
|
+
"hour_23": 0.25,
|
|
391
|
+
"hour_24": 0.75
|
|
392
|
+
},
|
|
393
|
+
"ncm-daily-9337-Dwell_DomBed_Occ_Hol": {
|
|
394
|
+
"sched_type_limits_name": "FRACTION",
|
|
395
|
+
"hour_1": 1,
|
|
396
|
+
"hour_2": 1,
|
|
397
|
+
"hour_3": 1,
|
|
398
|
+
"hour_4": 1,
|
|
399
|
+
"hour_5": 1,
|
|
400
|
+
"hour_6": 1,
|
|
401
|
+
"hour_7": 1,
|
|
402
|
+
"hour_8": 0.5,
|
|
403
|
+
"hour_9": 0.25,
|
|
404
|
+
"hour_10": 0,
|
|
405
|
+
"hour_11": 0,
|
|
406
|
+
"hour_12": 0,
|
|
407
|
+
"hour_13": 0,
|
|
408
|
+
"hour_14": 0,
|
|
409
|
+
"hour_15": 0,
|
|
410
|
+
"hour_16": 0,
|
|
411
|
+
"hour_17": 0,
|
|
412
|
+
"hour_18": 0,
|
|
413
|
+
"hour_19": 0,
|
|
414
|
+
"hour_20": 0,
|
|
415
|
+
"hour_21": 0,
|
|
416
|
+
"hour_22": 0,
|
|
417
|
+
"hour_23": 0.25,
|
|
418
|
+
"hour_24": 0.75
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
"Schedule:Week:Daily": {
|
|
422
|
+
"ncm-weekly-3530-Dwell_DomBed_Heat_Wk1": {
|
|
423
|
+
"monday_schedule_day_name": "ncm-daily-9344-Dwell_DomBed_Heat_Wkdy",
|
|
424
|
+
"tuesday_schedule_day_name": "ncm-daily-9344-Dwell_DomBed_Heat_Wkdy",
|
|
425
|
+
"wednesday_schedule_day_name": "ncm-daily-9344-Dwell_DomBed_Heat_Wkdy",
|
|
426
|
+
"thursday_schedule_day_name": "ncm-daily-9344-Dwell_DomBed_Heat_Wkdy",
|
|
427
|
+
"friday_schedule_day_name": "ncm-daily-9344-Dwell_DomBed_Heat_Wkdy",
|
|
428
|
+
"saturday_schedule_day_name": "ncm-daily-9345-Dwell_DomBed_Heat_Wknd",
|
|
429
|
+
"sunday_schedule_day_name": "ncm-daily-9345-Dwell_DomBed_Heat_Wknd",
|
|
430
|
+
"holiday_schedule_day_name": "ncm-daily-9346-Dwell_DomBed_Heat_Hol",
|
|
431
|
+
"summerdesignday_schedule_day_name": "ncm-daily-9346-Dwell_DomBed_Heat_Hol",
|
|
432
|
+
"winterdesignday_schedule_day_name": "ncm-daily-9346-Dwell_DomBed_Heat_Hol",
|
|
433
|
+
"customday1_schedule_day_name": "ncm-daily-9346-Dwell_DomBed_Heat_Hol",
|
|
434
|
+
"customday2_schedule_day_name": "ncm-daily-9346-Dwell_DomBed_Heat_Hol"
|
|
435
|
+
},
|
|
436
|
+
"ncm-weekly-3557-Dwell_DomLounge_Occ_Wk1": {
|
|
437
|
+
"monday_schedule_day_name": "ncm-daily-9395-Dwell_DomLounge_Occ_Wkdy",
|
|
438
|
+
"tuesday_schedule_day_name": "ncm-daily-9395-Dwell_DomLounge_Occ_Wkdy",
|
|
439
|
+
"wednesday_schedule_day_name": "ncm-daily-9395-Dwell_DomLounge_Occ_Wkdy",
|
|
440
|
+
"thursday_schedule_day_name": "ncm-daily-9395-Dwell_DomLounge_Occ_Wkdy",
|
|
441
|
+
"friday_schedule_day_name": "ncm-daily-9395-Dwell_DomLounge_Occ_Wkdy",
|
|
442
|
+
"saturday_schedule_day_name": "ncm-daily-9396-Dwell_DomLounge_Occ_Wknd",
|
|
443
|
+
"sunday_schedule_day_name": "ncm-daily-9396-Dwell_DomLounge_Occ_Wknd",
|
|
444
|
+
"holiday_schedule_day_name": "ncm-daily-9397-Dwell_DomLounge_Occ_Hol",
|
|
445
|
+
"summerdesignday_schedule_day_name": "ncm-daily-9397-Dwell_DomLounge_Occ_Hol",
|
|
446
|
+
"winterdesignday_schedule_day_name": "ncm-daily-9397-Dwell_DomLounge_Occ_Hol",
|
|
447
|
+
"customday1_schedule_day_name": "ncm-daily-9397-Dwell_DomLounge_Occ_Hol",
|
|
448
|
+
"customday2_schedule_day_name": "ncm-daily-9397-Dwell_DomLounge_Occ_Hol"
|
|
449
|
+
},
|
|
450
|
+
"ncm-weekly-3555-Dwell_DomLounge_Heat_Wk1": {
|
|
451
|
+
"monday_schedule_day_name": "ncm-daily-9404-Dwell_DomLounge_Heat_Wkdy",
|
|
452
|
+
"tuesday_schedule_day_name": "ncm-daily-9404-Dwell_DomLounge_Heat_Wkdy",
|
|
453
|
+
"wednesday_schedule_day_name": "ncm-daily-9404-Dwell_DomLounge_Heat_Wkdy",
|
|
454
|
+
"thursday_schedule_day_name": "ncm-daily-9404-Dwell_DomLounge_Heat_Wkdy",
|
|
455
|
+
"friday_schedule_day_name": "ncm-daily-9404-Dwell_DomLounge_Heat_Wkdy",
|
|
456
|
+
"saturday_schedule_day_name": "ncm-daily-9405-Dwell_DomLounge_Heat_Wknd",
|
|
457
|
+
"sunday_schedule_day_name": "ncm-daily-9405-Dwell_DomLounge_Heat_Wknd",
|
|
458
|
+
"holiday_schedule_day_name": "ncm-daily-9406-Dwell_DomLounge_Heat_Hol",
|
|
459
|
+
"summerdesignday_schedule_day_name": "ncm-daily-9406-Dwell_DomLounge_Heat_Hol",
|
|
460
|
+
"winterdesignday_schedule_day_name": "ncm-daily-9406-Dwell_DomLounge_Heat_Hol",
|
|
461
|
+
"customday1_schedule_day_name": "ncm-daily-9406-Dwell_DomLounge_Heat_Hol",
|
|
462
|
+
"customday2_schedule_day_name": "ncm-daily-9406-Dwell_DomLounge_Heat_Hol"
|
|
463
|
+
},
|
|
464
|
+
"ncm-weekly-3532-Dwell_DomBed_Occ_Wk1": {
|
|
465
|
+
"monday_schedule_day_name": "ncm-daily-9335-Dwell_DomBed_Occ_Wkdy",
|
|
466
|
+
"tuesday_schedule_day_name": "ncm-daily-9335-Dwell_DomBed_Occ_Wkdy",
|
|
467
|
+
"wednesday_schedule_day_name": "ncm-daily-9335-Dwell_DomBed_Occ_Wkdy",
|
|
468
|
+
"thursday_schedule_day_name": "ncm-daily-9335-Dwell_DomBed_Occ_Wkdy",
|
|
469
|
+
"friday_schedule_day_name": "ncm-daily-9335-Dwell_DomBed_Occ_Wkdy",
|
|
470
|
+
"saturday_schedule_day_name": "ncm-daily-9336-Dwell_DomBed_Occ_Wknd",
|
|
471
|
+
"sunday_schedule_day_name": "ncm-daily-9336-Dwell_DomBed_Occ_Wknd",
|
|
472
|
+
"holiday_schedule_day_name": "ncm-daily-9337-Dwell_DomBed_Occ_Hol",
|
|
473
|
+
"summerdesignday_schedule_day_name": "ncm-daily-9337-Dwell_DomBed_Occ_Hol",
|
|
474
|
+
"winterdesignday_schedule_day_name": "ncm-daily-9337-Dwell_DomBed_Occ_Hol",
|
|
475
|
+
"customday1_schedule_day_name": "ncm-daily-9337-Dwell_DomBed_Occ_Hol",
|
|
476
|
+
"customday2_schedule_day_name": "ncm-daily-9337-Dwell_DomBed_Occ_Hol"
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
"Schedule:Year": {
|
|
480
|
+
"ncm-annual-3232-Dwell_DomBed_Heat": {
|
|
481
|
+
"schedule_type_limits_name": "TEMPERATURE",
|
|
482
|
+
"schedule_weeks": [
|
|
483
|
+
{
|
|
484
|
+
"schedule_week_name": "ncm-weekly-3530-Dwell_DomBed_Heat_Wk1",
|
|
485
|
+
"start_month": 1,
|
|
486
|
+
"start_day": 1,
|
|
487
|
+
"end_month": 12,
|
|
488
|
+
"end_day": 31
|
|
489
|
+
}
|
|
490
|
+
]
|
|
491
|
+
},
|
|
492
|
+
"ncm-annual-3249-Dwell_DomLounge_Occ": {
|
|
493
|
+
"schedule_type_limits_name": "FRACTION",
|
|
494
|
+
"schedule_weeks": [
|
|
495
|
+
{
|
|
496
|
+
"schedule_week_name": "ncm-weekly-3557-Dwell_DomLounge_Occ_Wk1",
|
|
497
|
+
"start_month": 1,
|
|
498
|
+
"start_day": 1,
|
|
499
|
+
"end_month": 12,
|
|
500
|
+
"end_day": 31
|
|
501
|
+
}
|
|
502
|
+
]
|
|
503
|
+
},
|
|
504
|
+
"ncm-annual-3252-Dwell_DomLounge_Heat": {
|
|
505
|
+
"schedule_type_limits_name": "TEMPERATURE",
|
|
506
|
+
"schedule_weeks": [
|
|
507
|
+
{
|
|
508
|
+
"schedule_week_name": "ncm-weekly-3555-Dwell_DomLounge_Heat_Wk1",
|
|
509
|
+
"start_month": 1,
|
|
510
|
+
"start_day": 1,
|
|
511
|
+
"end_month": 12,
|
|
512
|
+
"end_day": 31
|
|
513
|
+
}
|
|
514
|
+
]
|
|
515
|
+
},
|
|
516
|
+
"ncm-annual-3229-Dwell_DomBed_Occ": {
|
|
517
|
+
"schedule_type_limits_name": "FRACTION",
|
|
518
|
+
"schedule_weeks": [
|
|
519
|
+
{
|
|
520
|
+
"schedule_week_name": "ncm-weekly-3532-Dwell_DomBed_Occ_Wk1",
|
|
521
|
+
"start_month": 1,
|
|
522
|
+
"start_day": 1,
|
|
523
|
+
"end_month": 12,
|
|
524
|
+
"end_day": 31
|
|
525
|
+
}
|
|
526
|
+
]
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
</details>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
aark/__init__.py,sha256=6Rym56FY3h16K5RGHbFuOtI2FZPdVuxAZtJu2D7HlF4,56
|
|
2
|
+
aark/ncm/__init__.py,sha256=N2HgG0qMbj8bMWN7oxT2LzVa35awcaLHFnsyif_PwIo,74
|
|
3
|
+
aark/ncm/sched.py,sha256=_wQ75iNo_H7Aid0UjcZjPNS5bkSe4z7Yf2UslRWJxec,17610
|
|
4
|
+
aark-0.1.0.dist-info/METADATA,sha256=uB9s9G8xVIOlIwSj7gb0JpFXJA4BN9GOpOqdWcYZrko,15554
|
|
5
|
+
aark-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
6
|
+
aark-0.1.0.dist-info/licenses/LICENSE,sha256=JTVcXvF_ZZiquXM-HlNz4fk7GpbNzHhVkzrMBK5iqzY,1496
|
|
7
|
+
aark-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Cheng Cui
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|