|
@@ -1,349 +0,0 @@
|
|
|
-# -*- coding: utf-8 -*-
|
|
|
-
|
|
|
-from abc import abstractmethod
|
|
|
-from typing import Dict, Tuple, Optional
|
|
|
-
|
|
|
-import arrow
|
|
|
-import numpy as np
|
|
|
-import pandas as pd
|
|
|
-from httpx import AsyncClient
|
|
|
-from loguru import logger
|
|
|
-
|
|
|
-from app.controllers.controller import Controller
|
|
|
-from app.resources.params import (
|
|
|
- TEMPERATURE_RELATED_FEEDBACK_WEIGHT,
|
|
|
- TEMPERATURE_RELATED_FEEDBACK,
|
|
|
- CO2_RELATED_FEEDBACK_WEIGHT,
|
|
|
- SWITCH_RELATED_FEEDBACK
|
|
|
-)
|
|
|
-from app.services.platform import DataPlatformService
|
|
|
-from app.services.transfer import SpaceInfoService, Season
|
|
|
-from app.utils.date import get_time_str, get_quarter_minutes, TIME_FMT
|
|
|
-
|
|
|
-
|
|
|
-class TargetController(Controller):
|
|
|
-
|
|
|
- def __init__(
|
|
|
- self,
|
|
|
- realtime_data: float,
|
|
|
- feedback: Dict,
|
|
|
- is_customized: bool,
|
|
|
- is_temporary: bool,
|
|
|
- current_targets: pd.DataFrame,
|
|
|
- ) -> None:
|
|
|
- super(TargetController, self).__init__()
|
|
|
- self._realtime_data = realtime_data
|
|
|
- self._feedback = feedback
|
|
|
- self._is_customized = is_customized
|
|
|
- self._is_temporary = is_temporary
|
|
|
- self._current_targets = current_targets
|
|
|
- self._now_time = arrow.get(get_time_str(), TIME_FMT).time().strftime('%H%M%S')
|
|
|
- self._quarter_time = get_quarter_minutes(get_time_str())
|
|
|
-
|
|
|
- async def calculate_diff(self, weight: Dict) -> float:
|
|
|
- related_feedback = [v for k, v in self._feedback.items() if k in weight]
|
|
|
- related_feedback = np.array(related_feedback)
|
|
|
- weight = np.array(list(weight.values()))
|
|
|
- feedback_count = related_feedback.sum()
|
|
|
- diff = 0
|
|
|
- if feedback_count > 0:
|
|
|
- diff = np.dot(related_feedback, weight) / feedback_count
|
|
|
-
|
|
|
- return diff
|
|
|
-
|
|
|
- @abstractmethod
|
|
|
- async def init_temporary(self):
|
|
|
- pass
|
|
|
-
|
|
|
- @abstractmethod
|
|
|
- async def get_targets(self) -> float:
|
|
|
- pass
|
|
|
-
|
|
|
- async def generate_temporary(self, lower, upper):
|
|
|
- now_str = get_time_str()
|
|
|
- time_index = arrow.get(arrow.get(now_str, TIME_FMT).shift(minutes=15).timestamp
|
|
|
- // (15 * 60) * (15 * 60)).time().strftime('%H%M%S')
|
|
|
- result = {time_index: [lower, upper]}
|
|
|
- self._results.update({'temporary_targets': result})
|
|
|
-
|
|
|
- async def readjust_global(self, latest_change: float, previous_changes: pd.DataFrame):
|
|
|
- previous_changes = pd.concat([
|
|
|
- pd.DataFrame({'timestamp': [self._now_time], 'value': [latest_change]}),
|
|
|
- previous_changes,
|
|
|
- ])
|
|
|
- previous_changes.reset_index(inplace=True)
|
|
|
- previous_changes['weight1'] = previous_changes['index'].apply(lambda x: (1 / (x + 1)) ** 3)
|
|
|
- new_targets = []
|
|
|
- time_index = self._current_targets.reset_index()['time']
|
|
|
- for item in time_index:
|
|
|
- previous_changes['delta'] = previous_changes['timestamp'].apply(
|
|
|
- lambda x: abs(arrow.get(str(x), 'HHmmss') - arrow.get(item, 'HHmmss')).seconds // (15 * 60)
|
|
|
- )
|
|
|
- previous_changes['weight2'] = previous_changes['delta'].apply(lambda x: 0.5 ** x)
|
|
|
- previous_changes['weight'] = previous_changes['weight1'] * previous_changes['weight2']
|
|
|
- new_targets.append(
|
|
|
- (previous_changes['value'] * previous_changes['weight']).sum() / previous_changes['weight'].sum()
|
|
|
- )
|
|
|
- self._current_targets['new_targets'] = new_targets
|
|
|
-
|
|
|
- @abstractmethod
|
|
|
- async def run(self):
|
|
|
- pass
|
|
|
-
|
|
|
-
|
|
|
-class TemperatureTargetController(TargetController):
|
|
|
-
|
|
|
- def __init__(
|
|
|
- self,
|
|
|
- realtime_data: float,
|
|
|
- feedback: Dict,
|
|
|
- is_customized: bool,
|
|
|
- is_temporary: bool,
|
|
|
- current_targets: pd.DataFrame,
|
|
|
- season: Season,
|
|
|
- previous_changes: Optional[pd.DataFrame] = None
|
|
|
- ) -> None:
|
|
|
- super(TemperatureTargetController, self).__init__(
|
|
|
- realtime_data,
|
|
|
- feedback,
|
|
|
- is_customized,
|
|
|
- is_temporary,
|
|
|
- current_targets
|
|
|
- )
|
|
|
- self._season = season
|
|
|
- self._previous_changes = previous_changes
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def _cut(value: float) -> float:
|
|
|
- _LOWER_LIMIT = 22.0
|
|
|
- _UPPER_LIMIT = 28.0
|
|
|
- value = min(value, _UPPER_LIMIT)
|
|
|
- value = max(value, _LOWER_LIMIT)
|
|
|
-
|
|
|
- return value
|
|
|
-
|
|
|
- async def init_temporary(self) -> Tuple[float, float]:
|
|
|
- _VAR = 2
|
|
|
- _RANGE = 1
|
|
|
- new_target = 24.0
|
|
|
- new_lower_bound, new_upper_bound = new_target - 1.0, new_target + 1.0
|
|
|
- if not np.isnan(self._realtime_data):
|
|
|
- if self._season == Season.cooling:
|
|
|
- if ('a little hot' in self._feedback
|
|
|
- or 'so hot' in self._feedback
|
|
|
- or 'switch on' in self._feedback):
|
|
|
- mid = self._realtime_data - _VAR
|
|
|
- mid = self._cut(mid)
|
|
|
- new_lower_bound = mid - _RANGE
|
|
|
- new_upper_bound = mid + _RANGE
|
|
|
- elif self._season == Season.heating:
|
|
|
- if ('a little cold' in self._feedback
|
|
|
- or 'so cold' in self._feedback
|
|
|
- or 'switch on' in self._feedback):
|
|
|
- mid = self._realtime_data + _VAR
|
|
|
- mid = self._cut(mid)
|
|
|
- new_lower_bound = mid - _RANGE
|
|
|
- new_upper_bound = mid + _RANGE
|
|
|
-
|
|
|
- return new_lower_bound, new_upper_bound
|
|
|
-
|
|
|
- async def get_targets(self) -> Tuple[float, float]:
|
|
|
- current_lower_target = self._current_targets['temperatureMin'].loc[self._quarter_time]
|
|
|
- current_upper_target = self._current_targets['temperatureMax'].loc[self._quarter_time]
|
|
|
- # if np.isnan(current_lower_target):
|
|
|
- # current_lower_target = 23.0
|
|
|
- # if np.isnan(current_upper_target):
|
|
|
- # current_upper_target = 25.0
|
|
|
-
|
|
|
- return (current_lower_target + current_upper_target) / 2, current_upper_target - current_lower_target
|
|
|
-
|
|
|
- async def readjust_current(self, current_target: float, extent: float, diff: float) -> float:
|
|
|
- # _RANGE = 2
|
|
|
- new_target = current_target
|
|
|
- if not np.isnan(self._realtime_data):
|
|
|
-
|
|
|
- if (diff < 0 and self._realtime_data > current_target + extent / 2
|
|
|
- or diff > 0 and self._realtime_data < current_target - extent / 2):
|
|
|
- new_target = current_target
|
|
|
- else:
|
|
|
- new_target = self._realtime_data + diff
|
|
|
-
|
|
|
- return new_target
|
|
|
-
|
|
|
- async def generate_global(self, extent: float):
|
|
|
- _RANGE = 1
|
|
|
- new_targets = self._current_targets['new_targets'].apply(lambda x: [self._cut(x) - extent / 2,
|
|
|
- self._cut(x) + extent / 2])
|
|
|
- time_index = self._current_targets.reset_index()['time']
|
|
|
- result = {}
|
|
|
- for i in range(len(time_index)):
|
|
|
- result.update({time_index[i]: new_targets[i]})
|
|
|
- self._results.update({'global_targets': result})
|
|
|
-
|
|
|
- async def run(self):
|
|
|
- diff = await self.calculate_diff(TEMPERATURE_RELATED_FEEDBACK_WEIGHT)
|
|
|
- if diff != 0:
|
|
|
- if not self._is_customized:
|
|
|
- lower_bound, upper_bound = await self.init_temporary()
|
|
|
- await self.generate_temporary(lower_bound, upper_bound)
|
|
|
- else:
|
|
|
- current_target, extent = await self.get_targets()
|
|
|
- new_target = await self.readjust_current(current_target, extent, diff)
|
|
|
- new_target = self._cut(new_target)
|
|
|
- if not self._is_temporary:
|
|
|
- self._results.update({'latest_change': new_target})
|
|
|
- await self.readjust_global(new_target, self._previous_changes)
|
|
|
- await self.generate_global(extent)
|
|
|
- else:
|
|
|
- await self.generate_temporary(new_target - 1.0, new_target + 1.0)
|
|
|
- else:
|
|
|
- return
|
|
|
-
|
|
|
-
|
|
|
-class Co2TargetController(TargetController):
|
|
|
-
|
|
|
- def __init__(
|
|
|
- self,
|
|
|
- realtime_data: float,
|
|
|
- feedback: Dict,
|
|
|
- is_customized: bool,
|
|
|
- is_temporary: bool,
|
|
|
- current_targets: pd.DataFrame,
|
|
|
- previous_changes: Optional[pd.DataFrame] = None
|
|
|
- ) -> None:
|
|
|
- super(Co2TargetController, self).__init__(
|
|
|
- realtime_data,
|
|
|
- feedback,
|
|
|
- is_customized,
|
|
|
- is_temporary,
|
|
|
- current_targets
|
|
|
- )
|
|
|
- self._previous_changes = previous_changes
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def _cut(value: float) -> float:
|
|
|
- _UPPER_LIMIT = 1000.0
|
|
|
- value = min(value, _UPPER_LIMIT)
|
|
|
-
|
|
|
- return value
|
|
|
-
|
|
|
- async def init_temporary(self) -> float:
|
|
|
- new_target = 1000
|
|
|
- diff = await self.calculate_diff(CO2_RELATED_FEEDBACK_WEIGHT)
|
|
|
- if not np.isnan(self._realtime_data):
|
|
|
- new_target += diff
|
|
|
-
|
|
|
- return self._cut(new_target)
|
|
|
-
|
|
|
- async def get_targets(self) -> float:
|
|
|
- current_upper_target = self._current_targets['co2Max'].loc[self._quarter_time]
|
|
|
- if np.isnan(current_upper_target):
|
|
|
- current_upper_target = 500.0
|
|
|
-
|
|
|
- return current_upper_target
|
|
|
-
|
|
|
- async def readjust_current(self, lower: float, upper: float, diff: float) -> float:
|
|
|
- new_target = upper - lower
|
|
|
- if np.isnan(self._realtime_data):
|
|
|
- new_target += diff
|
|
|
- else:
|
|
|
- if (diff > 50 and self._realtime_data + 100 > upper
|
|
|
- or diff < -50 and self._realtime_data - 100 < upper):
|
|
|
- new_target = self._realtime_data + diff
|
|
|
-
|
|
|
- return self._cut(new_target)
|
|
|
-
|
|
|
- async def generate_global(self):
|
|
|
- new_targets = self._current_targets['new_targets'].apply(lambda x: [0, x])
|
|
|
- time_index = self._current_targets.reset_index()['time']
|
|
|
- result = {}
|
|
|
- for i in range(len(time_index)):
|
|
|
- result.update({time_index[i]: new_targets[i]})
|
|
|
- self._results.update({'global_targets': result})
|
|
|
-
|
|
|
- async def run(self):
|
|
|
- diff = await self.calculate_diff(CO2_RELATED_FEEDBACK_WEIGHT)
|
|
|
- if diff != 0:
|
|
|
- if not self._is_customized:
|
|
|
- upper_bound = await self.init_temporary()
|
|
|
- await self.generate_temporary(0, upper_bound)
|
|
|
- else:
|
|
|
- current_upper = await self.get_targets()
|
|
|
- upper_bound = await self.readjust_current(0, current_upper, diff)
|
|
|
- if not self._is_temporary:
|
|
|
- self._results.update({'latest_change': upper_bound})
|
|
|
- await self.readjust_global(upper_bound, self._previous_changes)
|
|
|
- await self.generate_global()
|
|
|
- else:
|
|
|
- await self.generate_temporary(0, upper_bound)
|
|
|
- else:
|
|
|
- return
|
|
|
-
|
|
|
-
|
|
|
-@logger.catch()
|
|
|
-async def readjust_all_target(
|
|
|
- project_id: str,
|
|
|
- space_id: str,
|
|
|
- wechat_time: Optional[str] = None,
|
|
|
- feedback: Optional[Dict] = None
|
|
|
-):
|
|
|
- async with AsyncClient() as client:
|
|
|
- transfer = SpaceInfoService(client, project_id, space_id)
|
|
|
- platform = DataPlatformService(client, project_id)
|
|
|
-
|
|
|
- realtime_temperature = await platform.get_realtime_temperature(space_id)
|
|
|
- current_targets = await transfer.get_custom_target()
|
|
|
- if wechat_time:
|
|
|
- feedback = await transfer.get_feedback(wechat_time)
|
|
|
- is_customized = await transfer.is_customized()
|
|
|
- is_temporary = await transfer.is_temporary()
|
|
|
- season = await transfer.get_season()
|
|
|
- previous_changes = await transfer.env_database_get()
|
|
|
-
|
|
|
- if feedback.get('switch off') and feedback.get('switch off') > 0:
|
|
|
- need_switch_off = True
|
|
|
- for item in SWITCH_RELATED_FEEDBACK:
|
|
|
- if feedback.get(item) and feedback.get(item) > 0:
|
|
|
- need_switch_off = False
|
|
|
- break
|
|
|
- else:
|
|
|
- need_switch_off = False
|
|
|
-
|
|
|
- need_run_room_control = False
|
|
|
- if need_switch_off:
|
|
|
- need_run_room_control = True
|
|
|
- async with AsyncClient() as client:
|
|
|
- transfer = SpaceInfoService(client, project_id, space_id)
|
|
|
- await transfer.set_temporary_custom()
|
|
|
-
|
|
|
- return need_run_room_control
|
|
|
-
|
|
|
- temperature_results = {}
|
|
|
- for item in TEMPERATURE_RELATED_FEEDBACK:
|
|
|
- if feedback.get(item) and feedback.get(item) > 0:
|
|
|
- temperature_controller = TemperatureTargetController(
|
|
|
- realtime_temperature,
|
|
|
- feedback,
|
|
|
- is_customized,
|
|
|
- is_temporary,
|
|
|
- current_targets[['temperatureMin', 'temperatureMax']].copy(),
|
|
|
- season,
|
|
|
- previous_changes['temperature']
|
|
|
- )
|
|
|
- await temperature_controller.run()
|
|
|
- temperature_results = temperature_controller.get_results()
|
|
|
- break
|
|
|
-
|
|
|
- if temperature_results:
|
|
|
- need_run_room_control = True
|
|
|
- async with AsyncClient() as client:
|
|
|
- transfer = SpaceInfoService(client, project_id, space_id)
|
|
|
-
|
|
|
- if temperature_results.get('temporary_targets'):
|
|
|
- await transfer.set_custom_target('temperature', temperature_results.get('temporary_targets'), '0')
|
|
|
- if temperature_results.get('global_targets'):
|
|
|
- await transfer.set_custom_target('temperature', temperature_results.get('global_targets'), '1')
|
|
|
- if temperature_results.get('latest_change'):
|
|
|
- await transfer.env_database_set('temperature', temperature_results.get('latest_change'))
|
|
|
-
|
|
|
- return need_run_room_control
|