def __init__(self, start, end, measures, accumulated=None, drag_by_periods=True): self.measures = measures[:] self.gaps = [] # Containing the gaps and invalid measures self.adjusted_periods = [] # If a period is adjusted self.start_date = start self.end_date = end self.profile_class = REEProfile assert type( drag_by_periods) == bool, "drag_by_periods must be a Boolean" self.drag_by_periods = drag_by_periods self.accumulated = Decimal(0) if accumulated: assert type(accumulated) == float or isinstance( accumulated, Decimal), "Provided accumulated must be a Decimal or a float" assert accumulated < 1 and accumulated > -1, "Provided accumulated '{}' must be -1 < accumulated < 1".format( accumulated) self.accumulated = accumulated measures_by_date = dict([(m.date, m.measure) for m in measures if m.valid]) # End is included while start <= end: if measures_by_date.pop(TIMEZONE.normalize(start), None) is None: self.gaps.append(start) start += timedelta(hours=1)
def __init__(self, start, end, measures, accumulated=None, drag_by_periods=True): self.measures = measures[:] self.gaps = [] # Containing the gaps and invalid measures self.adjusted_periods = [] # If a period is adjusted self.start_date = start self.end_date = end self.profile_class = REEProfile assert type(drag_by_periods) == bool, "drag_by_periods must be a Boolean" self.drag_by_periods = drag_by_periods self.accumulated = Decimal(0) if accumulated: assert type(accumulated) == float or isinstance(accumulated, Decimal), "Provided accumulated must be a Decimal or a float" assert accumulated < 1 and accumulated > -1, "Provided accumulated '{}' must be -1 < accumulated < 1".format(accumulated) self.accumulated = accumulated measures_by_date = dict( [(m.date, m.measure) for m in measures if m.valid] ) # End is included while start <= end: if measures_by_date.pop(TIMEZONE.normalize(start), None) is None: self.gaps.append(start) start += timedelta(hours=1)
def get_station(dt): assert isinstance(dt, datetime) if not dt.tzinfo: dt = TIMEZONE.localize(dt) if TIMEZONE.normalize(dt).dst(): return 'summer' else: return 'winter'
def get(self, dt): assert isinstance(dt, datetime) if dt.dst() is None: dt = TIMEZONE.localize(dt) dt = TIMEZONE.normalize(dt) pos = bisect.bisect_left(self.coefs, Coefficent(dt, {})) self._check_pos(pos) return self.coefs[pos]
def get(cls, year, month): try: import ssl try: _create_unverified_https_context = ssl._create_unverified_context except AttributeError: pass else: ssl._create_default_https_context = _create_unverified_https_context except ImportError: pass try: cls.down_lock.acquire() import csv import httplib key = '%(year)s%(month)02i' % locals() conn = None if key in cls._CACHE: logger.debug('Using CACHE for REEProfile {0}'.format(key)) return cls._CACHE[key] perff_file = 'PERFF_%(key)s.gz' % locals() conn = httplib.HTTPSConnection(cls.HOST) conn.request('GET', '%s/%s' % (cls.PATH, perff_file)) logger.debug('Downloading REEProfile from {0}/{1}'.format( cls.PATH, perff_file)) r = conn.getresponse() if r.msg.type == 'application/x-gzip': import gzip c = StringIO(r.read()) m = StringIO(gzip.GzipFile(fileobj=c).read()) c.close() reader = csv.reader(m, delimiter=';') header = True cofs = [] coeffs_list = get_tariff_coeffs_list(year, month) for vals in reader: if header: header = False continue if int(vals[3]) == 1: n_hour = 1 dt = datetime(int(vals[0]), int(vals[1]), int(vals[2])) day = TIMEZONE.localize(dt, is_dst=bool(not int(vals[4]))) day += timedelta(hours=n_hour) n_hour += 1 cofs.append( Coefficent( TIMEZONE.normalize(day), dict((k, float(vals[i])) for i, k in enumerate(coeffs_list, 5)))) cls._CACHE[key] = cofs return cofs else: raise Exception('Profiles from REE not found') finally: if conn is not None: conn.close() cls.down_lock.release()
def get_period_by_timestamp(self, timestamp): """ Gets the number of energy period :param timestamp: datetime in format 'YYYY-MM-DD HH' :return: period number """ day, hours = timestamp.split(' ') dt_tz = TIMEZONE.normalize(TIMEZONE.localize(datetime.strptime(day, '%Y-%m-%d')) + timedelta(hours=int(hours))) # "get_period_by_date" expects a local timestamp without timezone # decreases 1 minute because is final datetime open interval (not included) dt = dt_tz.replace(tzinfo=None) - timedelta(minutes=1) return self.get_period_by_date(dt).code
def estimate(self, tariff, balance): assert isinstance(tariff, Tariff) logger.debug('Estimating for tariff: {0}'.format( tariff.code )) measures = [x for x in self.measures if x.valid] start = self.start_date end = self.end_date cofs = self.profile_class.get_range(start, end) cofs = Coefficients(cofs) cofs_per_period = Counter() for gap in self.gaps: period = tariff.get_period_by_date(gap) gap_cof = cofs.get(gap) cofs_per_period[period.code] += gap_cof.cof[tariff.cof] logger.debug('Coefficients per period calculated: {0}'.format( cofs_per_period )) energy_per_period = self.get_estimable_consumption(tariff, balance) energy_per_period_rem = energy_per_period.copy() dragger = Dragger() for idx, gap in enumerate(self.gaps): logger.debug('Gap {0}/{1}'.format( idx + 1, len(self.gaps) )) drag_key = period.code period = tariff.get_period_by_date(gap) gap_cof = cofs.get(gap).cof[tariff.cof] energy = energy_per_period[period.code] # If the balance[period] < energy_profile[period] fill with 0 # the gaps if energy < 0: energy = 0 gap_energy = (energy * gap_cof) / cofs_per_period[period.code] aprox = dragger.drag(gap_energy, key=drag_key) energy_per_period_rem[period.code] -= gap_energy logger.debug( 'Energy for hour {0} is {1}. {2} Energy {3}/{4}'.format( gap, aprox, period.code, energy_per_period_rem[period.code], energy )) pos = bisect.bisect_left(measures, ProfileHour(gap, 0, True)) profile_hour = ProfileHour(TIMEZONE.normalize(gap), aprox, True) measures.insert(pos, profile_hour) profile = Profile(self.start_date, self.end_date, measures) return profile
def get(cls, year, month): try: cls.down_lock.acquire() import csv import httplib key = '%(year)s%(month)02i' % locals() conn = None if key in cls._CACHE: logger.debug('Using CACHE for REEProfile {0}'.format(key)) return cls._CACHE[key] perff_file = 'PERFF_%(key)s.gz' % locals() conn = httplib.HTTPConnection(cls.HOST) conn.request('GET', '%s/%s' % (cls.PATH, perff_file)) logger.debug('Downloading REEProfile from {0}/{1}'.format( cls.PATH, perff_file )) r = conn.getresponse() if r.msg.type == 'application/x-gzip': import gzip c = StringIO(r.read()) m = StringIO(gzip.GzipFile(fileobj=c).read()) c.close() reader = csv.reader(m, delimiter=';') header = True cofs = [] for vals in reader: if header: header = False continue if int(vals[3]) == 1: n_hour = 1 dt = datetime( int(vals[0]), int(vals[1]), int(vals[2]) ) day = TIMEZONE.localize(dt, is_dst=bool(not int(vals[4]))) day += timedelta(hours=n_hour) n_hour += 1 cofs.append(Coefficent( TIMEZONE.normalize(day), dict( (k, float(vals[i])) for i, k in enumerate('ABCD', 5) )) ) cls._CACHE[key] = cofs return cofs else: raise Exception('Profiles from REE not found') finally: if conn is not None: conn.close() cls.down_lock.release()
def __init__(self, start, end, measures): self.measures = measures[:] self.gaps = [] # Containing the gaps and invalid measures self.start_date = start self.end_date = end self.profile_class = REEProfile measures_by_date = dict( [(m.date, m.measure) for m in measures if m.valid] ) # End is included while start <= end: if measures_by_date.pop(TIMEZONE.normalize(start), None) is None: self.gaps.append(start) start += timedelta(hours=1)
def get(cls, m, header): import csv reader = csv.reader(m, delimiter=';') cofs = [] n_hour = 0 for vals in reader: if header: header = False continue if int(vals[3]) == 1: n_hour = 1 dt = datetime( int(vals[0]), int(vals[1]), int(vals[2]) ) day = TIMEZONE.localize(dt, is_dst=bool(not int(vals[4]))) day += timedelta(hours=n_hour) n_hour += 1 cofs.append( (TIMEZONE.normalize(day), dict( (k, float(vals[i])) for i, k in enumerate('ABCD', 5) )) ) return cofs
def get(self, year, month): key = '%(year)s%(month)02i' % locals() if key in self._CACHE: logger.debug('Using CACHE for REEProfile {0}'.format(key)) return self._CACHE[key] with open(self.path, 'r') as m: reader = csv.reader(m, delimiter=';') header = 2 cofs = [] for vals in reader: if header: header -= 1 continue if int(vals[0]) != month: continue dt = datetime(year, int(vals[0]), int(vals[1])) day = TIMEZONE.localize(dt) + timedelta(hours=int(vals[2])) cofs.append( Coefficent( TIMEZONE.normalize(day), dict((k, float(vals[i])) for i, k in enumerate('ABCD', 3)))) self._CACHE[key] = cofs return cofs
def estimate(self, tariff, balance): assert isinstance(tariff, Tariff) logger.debug('Estimating for tariff: {0}'.format( tariff.code )) # Adapt balance for simplified T30A with just one period if isinstance(tariff, T30A_one_period) or isinstance(tariff, T31A_one_period): balance = { "P1": sum([values for values in balance.values()]) } # Get balance for T31ALB if isinstance(tariff, T31A) and tariff.low_voltage_measure: balance = tariff.apply_31A_LB_cof( balance, self.start_date, self.end_date ) measures = [x for x in self.measures if x.valid] start = self.start_date end = self.end_date # - REE cofs get from (year/month) # - Simel cofs get from (year/month/day hour) - can't substract one day if self.first_day_of_month or not issubclass(self.profile_class, REEProfile): cofs = self.profile_class.get_range(start, end) else: cofs = self.profile_class.get_range( start, end - relativedelta(days=1) ) cofs = Coefficients(cofs) cofs_per_period = Counter() for gap in self.gaps: period = tariff.get_period_by_date(gap) gap_cof = cofs.get(gap) cofs_per_period[period.code] += gap_cof.cof[tariff.cof] logger.debug('Coefficients per period calculated: {0}'.format( cofs_per_period )) energy_per_period = self.get_estimable_consumption(tariff, balance) energy_per_period_rem = energy_per_period.copy() dragger = Dragger() # Initialize the Dragger with passed accumulated value if len(self.gaps) > 0: # Drag by_hours if not self.drag_by_periods: init_drag_key = "default" else: init_drag_key = tariff.get_period_by_date(self.gaps[0]).code dragger.drag(self.accumulated, key=init_drag_key) for idx, gap in enumerate(self.gaps): logger.debug('Gap {0}/{1}'.format( idx + 1, len(self.gaps) )) period = tariff.get_period_by_date(gap) drag_key = period.code if not self.drag_by_periods else "default" gap_cof = cofs.get(gap).cof[tariff.cof] energy = energy_per_period[period.code] # If the balance[period] < energy_profile[period] fill with 0 if energy < 0: energy = 0 gap_energy = (energy * gap_cof) / cofs_per_period[period.code] aprox = dragger.drag(gap_energy, key=drag_key) energy_per_period_rem[period.code] -= gap_energy logger.debug( 'Energy for hour {0} is {1}. {2} Energy {3}/{4}'.format( gap, aprox, period.code, energy_per_period_rem[period.code], energy )) pos = bisect.bisect_left(measures, ProfileHour(gap, 0, True, 0.0)) profile_hour = ProfileHour(TIMEZONE.normalize(gap), aprox, True, dragger[drag_key]) measures.insert(pos, profile_hour) profile = Profile(self.start_date, self.end_date, measures) return profile
def estimate(self, tariff, balance): assert isinstance(tariff, Tariff) logger.debug('Estimating for tariff: {0}'.format(tariff.code)) # Adapt balance for simplified T30A with just one period if isinstance(tariff, T30A_one_period) or isinstance( tariff, T31A_one_period): balance = {"P1": sum([values for values in balance.values()])} # Adapt T31A6P adding P4 to P1 if isinstance(tariff, T31A) and balance.get('P4', 0) > 0: balance['P1'] += balance['P4'] balance['P4'] = 0 measures = [x for x in self.measures if x.valid] start = self.start_date end = self.end_date # - REE cofs get from (year/month) # - Simel cofs get from (year/month/day hour) - can't substract one day if self.first_day_of_month or not issubclass(self.profile_class, REEProfile): cofs = self.profile_class.get_range(start, end) else: cofs = self.profile_class.get_range(start, end - relativedelta(days=1)) cofs = Coefficients(cofs) cofs_per_period = Counter() for gap in self.gaps: dt = gap - timedelta(minutes=1) period = tariff.get_period_by_date(dt) gap_cof = cofs.get(dt) cofs_per_period[period.code] += gap_cof.cof[tariff.cof] logger.debug( 'Coefficients per period calculated: {0}'.format(cofs_per_period)) energy_per_period = self.get_estimable_consumption(tariff, balance) energy_per_period_rem = energy_per_period.copy() dragger = Dragger() # Initialize the Dragger with passed accumulated value if len(self.gaps) > 0: # Drag by_hours if not self.drag_by_periods: init_drag_key = "default" else: dt = self.gaps[0] - timedelta(minutes=1) init_drag_key = tariff.get_period_by_date(dt).code dragger.drag(self.accumulated, key=init_drag_key) for idx, gap in enumerate(self.gaps): logger.debug('Gap {0}/{1}'.format(idx + 1, len(self.gaps))) dt = gap - timedelta(minutes=1) period = tariff.get_period_by_date(dt) drag_key = period.code if not self.drag_by_periods else "default" gap_cof = cofs.get(gap).cof[tariff.cof] energy = energy_per_period[period.code] # If the balance[period] < energy_profile[period] fill with 0 if energy < 0: energy = 0 try: gap_energy = (energy * gap_cof) / cofs_per_period[period.code] except ZeroDivisionError as error: gap_energy = 0 logger.debug(error) aprox = dragger.drag(gap_energy, key=drag_key) energy_per_period_rem[period.code] -= gap_energy logger.debug( 'Energy for hour {0} is {1}. {2} Energy {3}/{4}'.format( gap, aprox, period.code, energy_per_period_rem[period.code], energy)) pos = bisect.bisect_left(measures, ProfileHour(gap, 0, True, 0.0)) profile_hour = ProfileHour(TIMEZONE.normalize(gap), aprox, True, dragger[drag_key]) measures.insert(pos, profile_hour) profile = Profile(self.start_date, self.end_date, measures) return profile