thermal_comfort.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. from abc import ABC, abstractmethod
  2. from typing import Optional
  3. import numpy as np
  4. from loguru import logger
  5. from app.models.domain.diagnosis import ThermalComfortDiagnosisRequest
  6. from app.schemas.diagnosis import FaultCategory
  7. from app.schemas.equipment import AHU, FCU, VAVBox, VRF
  8. from app.services.transfer import Season
  9. class DiagnotorBase(ABC):
  10. def __init__(self):
  11. self._result = FaultCategory()
  12. def controller_check(
  13. self,
  14. setting_value: float,
  15. feedback_value: float,
  16. is_categorical: bool = True,
  17. tolerance_pct: float = 0.0,
  18. ) -> None:
  19. if is_categorical:
  20. if setting_value != feedback_value:
  21. self._result.controller_err = True
  22. else:
  23. if (
  24. not setting_value * (1 - tolerance_pct)
  25. <= feedback_value
  26. <= setting_value * (1 + tolerance_pct)
  27. ):
  28. self._result.controller_err = True
  29. class ThermalComfortDiagnotor(DiagnotorBase):
  30. def __init__(self):
  31. super(ThermalComfortDiagnotor, self).__init__()
  32. class FCUDiagnotor(DiagnotorBase):
  33. def __init__(
  34. self,
  35. fcu: FCU,
  36. ahu: AHU,
  37. duration: float,
  38. target_temp: float,
  39. realtime_temp: float,
  40. season: Season,
  41. ):
  42. super(FCUDiagnotor, self).__init__()
  43. self._fcu = fcu
  44. self._ahu = ahu
  45. self._duration = duration
  46. self._target_temp = target_temp
  47. self._realtime_temp = realtime_temp
  48. self._season = season
  49. def low_in_heating(self) -> None:
  50. if self._fcu.air_valve_speed == "off":
  51. if self._fcu.speed_limit == "off":
  52. self._result.over_constrained = True
  53. else:
  54. self._result.control_logic_err = True
  55. else:
  56. if self._fcu.air_valve_speed == self._fcu.recommended_speed:
  57. self.controller_check(
  58. self._fcu.air_valve_speed_set, self._fcu.air_valve_speed
  59. )
  60. if not self._result.controller_err:
  61. if self._fcu.air_valve_speed_set == "high":
  62. if self._duration > 120:
  63. if self._fcu.supply_air_temperature < self._target_temp + 1:
  64. self._result.insufficient_heating = True
  65. else:
  66. self._result.undefined_err = True
  67. else:
  68. self._result.no_problems_found = True
  69. else:
  70. self._result.control_logic_err = True
  71. else:
  72. if self._fcu.speed_limit == self._fcu.air_valve_speed:
  73. self._result.over_constrained = True
  74. else:
  75. self._result.obj_info_err = True
  76. def low_in_cooling(self) -> None:
  77. if self._fcu.recommended_speed == "off":
  78. if self._fcu.air_valve_speed == self._fcu.recommended_speed:
  79. self.controller_check(
  80. self._fcu.air_valve_speed_set, self._fcu.air_valve_speed
  81. )
  82. if not self._result.controller_err:
  83. if self._duration > 120:
  84. self._result.undefined_err = True
  85. else:
  86. self._result.no_problems_found = True
  87. else:
  88. self._result.obj_info_err = True
  89. else:
  90. self._result.control_logic_err = True
  91. def high_in_heating(self) -> None:
  92. self.low_in_cooling()
  93. def high_in_cooling(self) -> None:
  94. self.low_in_heating()
  95. if self._result.insufficient_heating:
  96. self._result.insufficient_heating = False
  97. self._result.insufficient_cooling = True
  98. def run(self) -> None:
  99. if (
  100. self._season == Season.heating
  101. or self._fcu.supply_air_temperature > self._target_temp
  102. ):
  103. if self._realtime_temp < self._target_temp - 1:
  104. self.low_in_heating()
  105. elif self._realtime_temp > self._target_temp + 1:
  106. self.high_in_heating()
  107. elif (
  108. self._season == Season.cooling
  109. or self._fcu.supply_air_temperature < self._target_temp
  110. ):
  111. if self._realtime_temp < self._target_temp - 1:
  112. self.low_in_cooling()
  113. elif self._realtime_temp > self._target_temp + 1:
  114. self.high_in_cooling()
  115. class VAVDiagnotor(DiagnotorBase):
  116. def __init__(
  117. self,
  118. vav: VAVBox,
  119. ahu: AHU,
  120. known_err: FaultCategory,
  121. duration: float,
  122. season: Season,
  123. ):
  124. super(VAVDiagnotor, self).__init__()
  125. self._vav = vav
  126. self._ahu = ahu
  127. self._known_err = known_err
  128. self._duration = duration
  129. self._season = season
  130. def low_in_heating(self) -> None:
  131. self.controller_check(
  132. self._vav.supply_air_flow_set,
  133. self._vav.supply_air_flow,
  134. is_categorical=False,
  135. tolerance_pct=0.1,
  136. )
  137. if (
  138. self._vav.recommended_supply_air_flow
  139. >= self._vav.supply_air_flow_upper_limit
  140. ):
  141. if self._vav.recommended_supply_air_flow == self._vav.supply_air_flow_set:
  142. if not self._result.controller_err:
  143. if self._duration > 120.0:
  144. if (
  145. self._vav.supply_air_temperature
  146. < self._vav.virtual_target_temperature + 1
  147. or self._ahu.supply_air_temperature
  148. < self._vav.virtual_target_temperature + 2
  149. ):
  150. self._result.insufficient_heating = True
  151. else:
  152. if self._vav.supply_air_flow_upper_limit < 1200.0:
  153. self._result.unreasonable_vav_flow_limit = True
  154. else:
  155. if not self._known_err.sensor_err:
  156. self._result.undefined_err = True
  157. else:
  158. self._result.no_problems_found = True
  159. else:
  160. self._result.obj_info_err = True
  161. else:
  162. if self._vav.recommended_supply_air_flow != self._vav.supply_air_flow_set:
  163. self._result.obj_info_err = True
  164. def low_in_cooling(self) -> None:
  165. if self._vav.recommended_supply_air_flow == self._vav.supply_air_flow_set:
  166. self.controller_check(
  167. self._vav.supply_air_flow_set,
  168. self._vav.supply_air_flow,
  169. is_categorical=False,
  170. tolerance_pct=0.1,
  171. )
  172. if self._vav.supply_air_flow_set <= self._vav.supply_air_flow_lower_limit:
  173. if not self._result.controller_err:
  174. if self._duration >= 120.0:
  175. if self._vav.supply_air_flow_lower_limit > 400:
  176. self._result.unreasonable_vav_flow_limit = True
  177. else:
  178. if not self._known_err.sensor_err:
  179. if (
  180. self._vav.supply_air_temperature < 18
  181. or self._ahu.supply_air_temperature < 18
  182. ):
  183. self._result.excessive_cooling = True
  184. else:
  185. self._result.obj_info_err = True
  186. else:
  187. self._result.no_problems_found = True
  188. else:
  189. self._result.no_problems_found = True
  190. else:
  191. self._result.obj_info_err = True
  192. def high_in_heating(self) -> None:
  193. self.low_in_cooling()
  194. if self._result.excessive_cooling:
  195. self._result.excessive_cooling = False
  196. self._result.excessive_heating = True
  197. def high_in_cooling(self) -> None:
  198. self.low_in_heating()
  199. if (
  200. self._vav.recommended_supply_air_flow
  201. >= self._vav.supply_air_flow_upper_limit
  202. ):
  203. if self._vav.recommended_supply_air_flow == self._vav.supply_air_flow_set:
  204. if not self._result.controller_err:
  205. if self._duration > 120.0:
  206. if (
  207. self._vav.supply_air_temperature
  208. > self._vav.virtual_target_temperature + 1
  209. or self._ahu.supply_air_temperature
  210. > self._vav.virtual_target_temperature + 2
  211. ):
  212. self._result.insufficient_cooling = True
  213. def run(self) -> None:
  214. if (
  215. self._season == Season.heating
  216. or self._vav.supply_air_temperature > self._vav.virtual_target_temperature
  217. ):
  218. if (
  219. self._vav.virtual_realtime_temperature
  220. < self._vav.virtual_target_temperature - 1
  221. ):
  222. self.low_in_heating()
  223. elif (
  224. self._vav.virtual_realtime_temperature
  225. > self._vav.virtual_target_temperature + 1
  226. ):
  227. self.high_in_heating()
  228. elif (
  229. self._season == Season.cooling
  230. or self._vav.supply_air_temperature < self._vav.virtual_target_temperature
  231. ):
  232. if (
  233. self._vav.virtual_realtime_temperature
  234. < self._vav.virtual_target_temperature - 1
  235. ):
  236. self.low_in_cooling()
  237. elif (
  238. self._vav.virtual_realtime_temperature
  239. > self._vav.virtual_target_temperature + 1
  240. ):
  241. self.high_in_cooling()
  242. class VRFDiagnotor(DiagnotorBase):
  243. def __init__(self, vrf: VRF):
  244. super(VRFDiagnotor, self).__init__()
  245. self._vrf = vrf
  246. def get_diagnosis_result(params: ThermalComfortDiagnosisRequest) -> FaultCategory:
  247. if params.fcu_list:
  248. for fcu in params.fcu_list:
  249. fcu_diagnotor = FCUDiagnotor(
  250. fcu,
  251. params.ahu,
  252. params.duration_minutes,
  253. params.target_temp,
  254. params.realtime_temp,
  255. params.season,
  256. )