예제 #1
0
class CostItem(odin.Resource):
    name = odin.StringField(null=True)
    code = odin.StringField(null=True)
    type = odin.StringField(null=True)
    season = odin.StringField(null=True)
    time = odin.StringField(null=True)
    cost = odin.FloatField()
예제 #2
0
class Book(odin.Resource):
    title = odin.StringField(name="Title")
    num_pages = odin.IntegerField(name="Num Pages")
    rrp = odin.FloatField(name="RRP")
    genre = odin.StringField(name="Genre",
                             choices=(
                                 ('sci-fi', 'Science Fiction'),
                                 ('fantasy', 'Fantasy'),
                                 ('others', 'Others'),
                             ),
                             null=True)
    author = odin.StringField(name="Author")
    publisher = odin.StringField(name="Publisher")
    language = odin.StringField(name="Language", null=True)

    def extra_attrs(self, attrs):
        self.extras = attrs

    def __eq__(self, other):
        return (self.title == other.title and self.num_pages == other.num_pages
                and self.rrp == other.rrp
                and (self.genre == other.genre or
                     (not self.genre and not other.genre))
                and self.author == other.author
                and self.publisher == other.publisher
                and (self.language == other.language or
                     (not self.language and not other.language)))
예제 #3
0
class Book(odin.Resource):
    title = odin.StringField(name="Title")
    num_pages = odin.IntegerField(name="Num Pages")
    rrp = odin.FloatField(name="RRP")
    genre = odin.StringField(name="Genre", choices=(
        ('sci-fi', 'Science Fiction'),
        ('fantasy', 'Fantasy'),
        ('others', 'Others'),
    ))
    author = odin.StringField(name="Author")
    publisher = odin.StringField(name="Publisher")
예제 #4
0
class Charge(odin.Resource):
    """A charge component of a tariff structure"""
    rate = odin.FloatField(null=True)
    rate_bands = odin.ArrayOf(RateBand, null=True)
    rate_schedule = odin.ArrayOf(ScheduleItem, null=True)
    time = odin.ObjectAs(Time, null=True)
    season = odin.ObjectAs(Season, null=True)
    type = odin.StringField(choices=CHARGE_TYPE_CHOICES,
                            null=True,
                            default='consumption',
                            use_default_if_not_provided=True)
    meter = odin.StringField(null=True,
                             default='electricity_imported',
                             use_default_if_not_provided=True)
예제 #5
0
class OldBook(LibraryBook):
    name = odin.StringField(key=True)
    num_pages = odin.IntegerField()
    price = odin.FloatField()
    genre = odin.StringField(key=True,
                             choices=(
                                 ('sci-fi', 'Science Fiction'),
                                 ('fantasy', 'Fantasy'),
                                 ('biography', 'Biography'),
                                 ('others', 'Others'),
                                 ('computers-and-tech',
                                  'Computers & technology'),
                             ))
    published = odin.DateTimeField()
    author = odin.ObjectAs(Author)
    publisher = odin.ObjectAs(Publisher)
예제 #6
0
class ToResource(odin.Resource):
    # Auto matched
    title = odin.StringField()
    count = odin.IntegerField()
    child = odin.ObjectAs(ChildResource)
    children = odin.ArrayOf(ChildResource)
    # Excluded
    excluded1 = odin.FloatField()
    # Mappings
    to_field1 = odin.StringField()
    to_field2 = odin.IntegerField()
    to_field3 = odin.IntegerField()
    same_but_different = odin.StringField()
    # Custom mappings
    to_field_c1 = odin.StringField()
    to_field_c2 = odin.StringField()
    to_field_c3 = odin.StringField()
    not_auto_c5 = odin.StringField()
    array_string = odin.TypedArrayField(odin.StringField())
    assigned_field = odin.StringField()
예제 #7
0
class Charge(odin.Resource):
    """A charge component of a tariff structure"""
    code = odin.StringField(null=True)
    rate = odin.FloatField(null=True)
    rate_bands = odin.ArrayOf(RateBand, null=True)
    rate_schedule = odin.ArrayOf(ScheduleItem, null=True)
    time = odin.ObjectAs(Time, null=True)
    season = odin.ObjectAs(Season, null=True)
    type = odin.StringField(choices=CHARGE_TYPE_CHOICES,
                            null=True,
                            default='consumption',
                            use_default_if_not_provided=True)
    meter = odin.StringField(null=True)

    @odin.calculated_field
    def name(self):
        season = self.season.name if self.season else None
        time = self.time.name if self.time else None
        scheduled = 'scheduled' if self.rate_schedule else None
        return str(self.code or '') + str(self.type or '') + str(
            season or '') + str(time or '') + str(scheduled or '')
예제 #8
0
class FromResource(odin.Resource):
    # Auto matched
    title = odin.StringField()
    count = odin.StringField()
    child = odin.ObjectAs(ChildResource)
    children = odin.ArrayOf(ChildResource)
    # Excluded
    excluded1 = odin.FloatField()
    # Mappings
    from_field1 = odin.StringField()
    from_field2 = odin.StringField()
    from_field3 = odin.IntegerField()
    from_field4 = odin.IntegerField()
    same_but_different = odin.StringField()
    # Custom mappings
    from_field_c1 = odin.StringField()
    from_field_c2 = odin.StringField()
    from_field_c3 = odin.StringField()
    from_field_c4 = odin.StringField()
    not_auto_c5 = odin.StringField()
    comma_separated_string = odin.StringField()
    # Virtual fields
    constant_field = odin.ConstantField(value=10)
예제 #9
0
class Book(LibraryBook):
    class Meta:
        key_field_name = 'isbn'

    title = odin.StringField()
    isbn = odin.StringField()
    num_pages = odin.IntegerField()
    rrp = odin.FloatField(default=20.4, use_default_if_not_provided=True)
    fiction = odin.BooleanField(is_attribute=True)
    genre = odin.StringField(choices=(
        ('sci-fi', 'Science Fiction'),
        ('fantasy', 'Fantasy'),
        ('biography', 'Biography'),
        ('others', 'Others'),
        ('computers-and-tech', 'Computers & technology'),
    ))
    published = odin.TypedArrayField(odin.DateTimeField())
    authors = odin.ArrayOf(Author, use_container=True)
    publisher = odin.DictAs(Publisher, null=True)

    def __eq__(self, other):
        if other:
            return vars(self) == vars(other)
        return False
예제 #10
0
class RateBand(odin.Resource):
    """A specific band within a block pricing rate structure"""
    limit = odin.FloatField(null=True,
                            default=9999999.9,
                            use_default_if_not_provided=True)
    rate = odin.FloatField()
예제 #11
0
class Tariff(odin.Resource):
    """A collection of charges associated with a specific utility service"""
    name = odin.StringField(null=True)
    code = odin.StringField(null=True)
    utility_name = odin.StringField(null=True)
    utility_code = odin.StringField(null=True)
    service = odin.StringField(choices=SERVICE_CHOICES, null=True)
    sector = odin.StringField(choices=SECTOR_CHOICES, null=True)
    description = odin.StringField(null=True)
    currency = odin.StringField(null=True)
    timezone = odin.StringField(null=True)
    min_consumption = odin.FloatField(null=True)
    max_consumption = odin.FloatField(null=True)
    min_demand = odin.FloatField(null=True)
    max_demand = odin.FloatField(null=True)
    charges = odin.ArrayOf(Charge, null=True)
    monthly_charge = odin.FloatField(null=True)
    minimum_charge = odin.FloatField(null=True)
    times = odin.DictAs(Times, null=True)
    seasons = odin.DictAs(Seasons, null=True)
    net_metering = odin.BooleanField(null=True)
    billing_period = odin.StringField(choices=PERIOD_CHOICES,
                                      null=True,
                                      default='monthly',
                                      use_default_if_not_provided=True)
    demand_window = odin.StringField(choices=DEMAND_WINDOW_CHOICES,
                                     null=True,
                                     default='30min',
                                     use_default_if_not_provided=True)
    consumption_unit = odin.StringField(choices=CONSUMPTION_UNIT_CHOICES,
                                        null=True)
    demand_unit = odin.StringField(choices=DEMAND_UNIT_CHOICES, null=True)

    @odin.calculated_field
    def charge_types(self):
        charge_types = set()
        for charge in self.charges:
            if charge.season:
                charge_types.add('seasonal')
            if charge.time:
                charge_types.add('tou')
            if charge.rate_bands:
                charge_types.add('block')
            if charge.rate_schedule:
                charge_types.add('scheduled')
            if charge.type == 'demand':
                charge_types.add('demand')
            if charge.type == 'consumption':
                charge_types.add('consumption')

        return list(charge_types)

    def calc_charge(self, name, row, charge, charge_array, block_accum_dict):
        if charge.rate:
            charge_array[name].append(charge.rate * float(row[charge.meter]))
        if charge.rate_bands:
            charge_time_step = float()
            for rate_band_index, rate_band in enumerate(charge.rate_bands):
                if block_accum_dict[name] > rate_band.limit:
                    continue
                block_usage = max((min(
                    (rate_band.limit - block_accum_dict[name],
                     row[charge.meter] - block_accum_dict[name])), 0.0))
                charge_time_step += rate_band.rate * block_usage
                block_accum_dict[name] += block_usage
            charge_array[name].append(charge_time_step)

        return charge_array, block_accum_dict

    def apply_by_charge_type(self, meter_data, charge_type='consumption'):
        """
            Calculates the cost of energy given a tariff and load.

            :param meter_data: a three-column pandas array with datetime, imported energy (kwh), exported energy (kwh)
            :param step: the time step in minutes of the meter data
            :param start: an optional datetime to select the commencement of the bill calculation
            :param end: an optional datetime to select the termination of the bill calculation
            :return: a dictionary containing the charge components (e.g. off_peak, shoulder, peak, total)
        """

        charge_array = defaultdict(list)
        block_accum_dict = defaultdict(float)

        for dt, row in meter_data.iterrows():
            time = datetime.time(hour=dt.hour, minute=dt.minute)

            # If the billing cycle changes over, reset block charge accumulations
            if (charge_type == 'consumption'
                    and self.billing_period == 'monthly' and dt.day == 1
                    and dt.hour == 0 and dt.minute == 0
                    or self.billing_period == 'quarterly' and dt.month % 3
                    and dt.day == 1 and dt.hour == 0 and dt.minute == 0
                    or self.billing_period == 'annually' and dt.month == 1
                    and dt.day == 1 and dt.hour == 0 and dt.minute == 0):
                block_accum_dict = defaultdict(float)
            if charge_type == 'demand':
                block_accum_dict = defaultdict(float)

            if self.charges:
                for charge in self.charges:
                    if charge.type == charge_type:
                        if charge.time and charge.season:
                            found = False
                            if datetime.date(year=dt.year, month=charge.season.from_month, day=charge.season.from_day) \
                                    < dt.date() <= datetime.date(year=dt.year, month=charge.season.to_month,
                                                                 day=charge.season.to_day):
                                for period in charge.time.periods:
                                    if datetime.time(
                                            hour=period.from_hour,
                                            minute=period.from_minute
                                    ) < time <= datetime.time(
                                            hour=period.to_hour,
                                            minute=period.to_minute):
                                        charge_array, block_accum_dict = self.calc_charge(
                                            self.service + charge_type +
                                            charge.season.name +
                                            charge.time.name, row, charge,
                                            charge_array, block_accum_dict)
                                        found = True
                                        break
                            if not found:
                                charge_array[self.service + charge_type +
                                             charge.season.name +
                                             charge.time.name].append(0.0)
                        elif charge.season and not charge.time:
                            if datetime.date(year=dt.year, month=charge.season.from_month, day=charge.season.from_day)\
                                    < dt.date() <= datetime.date(year=dt.year, month=charge.season.to_month,
                                                                 day=charge.season.to_day):
                                charge_array, block_accum_dict = self.calc_charge(
                                    self.service + charge_type +
                                    charge.season.name, row, charge,
                                    charge_array, block_accum_dict)
                            else:
                                charge_array[self.service + charge_type +
                                             charge.season.name].append(0.0)
                        elif charge.time and not charge.season:
                            found = False
                            for period in charge.time.periods:
                                if datetime.time(hour=period.from_hour
                                                 ) < time <= datetime.time(
                                                     hour=period.to_hour):
                                    charge_array, block_accum_dict = self.calc_charge(
                                        self.service + charge_type +
                                        charge.time.name, row, charge,
                                        charge_array, block_accum_dict)
                                    found = True
                            if not found:
                                charge_array[self.service + charge_type +
                                             charge.time.name].append(0.0)
                        elif charge.rate_schedule:
                            for schedule_item in charge.rate_schedule:
                                if dt <= schedule_item.datetime:
                                    charge_array[self.service + charge_type +
                                                 'scheduled'].append(
                                                     schedule_item.rate *
                                                     float(row[charge.meter]))
                                    break
                        else:
                            charge_array, block_accum_dict = self.calc_charge(
                                self.service + charge_type, row, charge,
                                charge_array, block_accum_dict)

        return charge_array

    def apply(self, meter_data, start=None, end=None, output_format='total'):
        """
            Calculates the cost of energy given a tariff and load.

            :param meter_data: a three-column pandas array with datetime, imported energy (kwh), exported energy (kwh)
            :param step: the time step in minutes of the meter data
            :param start: an optional datetime to select the commencement of the bill calculation
            :param end: an optional datetime to select the termination of the bill calculation
            :return: a dictionary containing the charge components (e.g. off_peak, shoulder, peak, total)
        """
        meter_data.truncate(before=start, after=end)
        charge_array = defaultdict(list)
        if 'consumption' in self.charge_types:
            consumption_data = meter_data
            # Resample meter data if finer resolution data not required by the specified tariff types
            if 'tou' not in self.charge_types and output_format != 'input-timestep' and output_format != 'input-timestep-components':
                if 'seasonal' in self.charge_types:
                    consumption_data = meter_data.resample('D').sum()
                else:
                    consumption_data = meter_data.resample(
                        PERIOD_TO_TIMESTEP[self.billing_period]).sum()

            consumption_charges = self.apply_by_charge_type(
                consumption_data, 'consumption')
            charge_array.update(consumption_charges)

        if 'demand' in self.charge_types:
            if output_format == 'input-timestep' or output_format == 'input-timestep-components':
                raise UserWarning(
                    "The output_format cannot be specified as 'input-timestep' if demand charges have "
                    "been assigned.")
            # Resample meter data to the demand window and then take the maximum for each billing period
            demand_data = meter_data.resample(
                PERIOD_TO_TIMESTEP[self.demand_window]).mean()
            peak_monthly = demand_data.resample(
                PERIOD_TO_TIMESTEP[self.billing_period]).max()
            demand_charges = self.apply_by_charge_type(peak_monthly, 'demand')
            charge_array.update(demand_charges)

        # Transform the output data into the specified output format
        if output_format == 'total':
            output = 0.0
            for v in charge_array.values():
                output += sum(v)
        elif output_format == 'total-components':
            output = dict()
            for k, v in charge_array.items():
                output[k] = sum(v)
        else:
            df = pandas.DataFrame.from_dict(data=charge_array)
            df.index = meter_data.index
            if output_format == 'billing-period':
                output = df.resample(
                    PERIOD_TO_TIMESTEP[self.billing_period].sum()).sum(1)
            elif output_format == 'billing-period-components':
                output = df.resample(
                    PERIOD_TO_TIMESTEP[self.billing_period].sum())
            elif output_format == 'input-timestep':
                output = df.sum(1)
            elif output_format == 'input-timestep-components':
                output = df
            else:
                raise UserWarning('Unsupported output format: %s' %
                                  output_format)

        return output
예제 #12
0
class ScheduleItem(odin.Resource):
    """An item in a scheduled or real-time pricing rate structure"""
    datetime = odin.NaiveDateTimeField()
    rate = odin.FloatField()
예제 #13
0
class Tariff(odin.Resource):
    """A collection of charges associated with a specific utility service"""
    name = odin.StringField(null=True)
    code = odin.StringField(null=True)
    utility_name = odin.StringField(null=True)
    utility_code = odin.StringField(null=True)
    service = odin.StringField(choices=SERVICE_CHOICES, null=True)
    sector = odin.StringField(choices=SECTOR_CHOICES, null=True)
    description = odin.StringField(null=True)
    currency = odin.StringField(null=True)
    timezone = odin.StringField(null=True)
    min_consumption = odin.FloatField(null=True)
    max_consumption = odin.FloatField(null=True)
    min_demand = odin.FloatField(null=True)
    max_demand = odin.FloatField(null=True)
    charges = odin.ArrayOf(Charge, null=True)
    monthly_charge = odin.FloatField(null=True)
    minimum_charge = odin.FloatField(null=True)
    times = odin.DictAs(Times, null=True)
    seasons = odin.DictAs(Seasons, null=True)
    net_metering = odin.BooleanField(null=True)
    billing_period = odin.StringField(choices=PERIOD_CHOICES,
                                      null=True,
                                      default='monthly',
                                      use_default_if_not_provided=True)
    demand_window = odin.StringField(choices=DEMAND_WINDOW_CHOICES,
                                     null=True,
                                     default='30min',
                                     use_default_if_not_provided=True)
    consumption_unit = odin.StringField(choices=CONSUMPTION_UNIT_CHOICES,
                                        null=True)
    demand_unit = odin.StringField(choices=DEMAND_UNIT_CHOICES, null=True)

    @odin.calculated_field
    def charge_types(self):
        charge_types = set()
        for charge in self.charges:
            if charge.season:
                charge_types.add('seasonal')
            if charge.time:
                charge_types.add('tou')
            if charge.rate_bands:
                charge_types.add('block')
            if charge.rate_schedule:
                charge_types.add('scheduled')
            if charge.type == 'demand':
                charge_types.add('demand')
            if charge.type == 'consumption':
                charge_types.add('consumption')
            if charge.type == 'generation':
                charge_types.add('generation')
            if charge.type == 'fixed':
                charge_types.add('fixed')

        return list(charge_types)

    def calc_charge(self, row, charge, cost_items, block_accum_dict):
        if charge.rate:
            try:
                cost_items[charge.name]['cost'] += charge.rate * float(
                    row[charge.meter or charge.type])
            except KeyError:
                raise Exception(
                    "The specified meter data file has no field named %s" %
                    str(charge.meter or charge.type))
        if charge.rate_bands:
            charge_time_step = float()
            prev_block_usage = 0.0
            for rate_band_index, rate_band in enumerate(charge.rate_bands):
                if block_accum_dict[charge.name] > rate_band.limit:
                    continue
                try:
                    block_usage = max((min(
                        (rate_band.limit - block_accum_dict[charge.name],
                         row[charge.meter or charge.type] - prev_block_usage)),
                                       0.0))
                except KeyError:
                    raise Exception(
                        "The specified meter data file has no field named %s" %
                        str(charge.meter or charge.type))
                prev_block_usage += block_usage
                charge_time_step += rate_band.rate * block_usage
                block_accum_dict[charge.name] += block_usage
            cost_items[charge.name]['cost'] += charge_time_step

        return cost_items, block_accum_dict

    def apply_by_charge_type(self,
                             meter_data,
                             cost_items,
                             charge_type='consumption'):
        """
            Calculates the cost of energy given a tariff and load.

            :param meter_data: a three-column pandas array with datetime, imported energy (kwh), exported energy (kwh)
            :param cost_items: a dictionary containing the charge components (e.g. off_peak, shoulder, peak, total)
            :param charge_type: a string specifying the charge type, options include 'consumption', 'demand' etc
            :return: a dictionary containing the charge components (e.g. off_peak, shoulder, peak, total)
        """

        block_accum_dict = defaultdict(float)

        for dt, row in meter_data.iterrows():
            time = datetime.time(hour=dt.hour, minute=dt.minute)

            # If the billing cycle changes over, reset block charge accumulations
            if (charge_type == 'consumption'
                    and self.billing_period == 'monthly' and dt.day == 1
                    and dt.hour == 0 and dt.minute == 0
                    or self.billing_period == 'quarterly' and dt.month % 3
                    and dt.day == 1 and dt.hour == 0 and dt.minute == 0
                    or self.billing_period == 'annually' and dt.month == 1
                    and dt.day == 1 and dt.hour == 0 and dt.minute == 0):
                block_accum_dict = defaultdict(float)
            if charge_type == 'demand':
                block_accum_dict = defaultdict(float)

            if self.charges:
                for charge in self.charges:
                    if charge.type == charge_type or (
                            charge_type == 'consumption'
                            and charge.type == 'generation'):
                        if charge.time and charge.season:
                            if datetime.date(year=dt.year, month=charge.season.from_month, day=charge.season.from_day) \
                                    <= dt.date() <= datetime.date(year=dt.year, month=charge.season.to_month,
                                                                  day=charge.season.to_day):
                                for period in charge.time.periods:
                                    if period.from_weekday <= dt.dayofweek <= period.to_weekday and datetime.time(
                                            hour=period.from_hour,
                                            minute=period.from_minute
                                    ) <= time <= datetime.time(
                                            hour=period.to_hour,
                                            minute=period.to_minute):
                                        cost_items, block_accum_dict = self.calc_charge(
                                            row, charge, cost_items,
                                            block_accum_dict)
                                        break
                        elif charge.season:
                            if datetime.date(year=dt.year, month=charge.season.from_month, day=charge.season.from_day)\
                                    <= dt.date() <= datetime.date(year=dt.year, month=charge.season.to_month,
                                                                  day=charge.season.to_day):
                                cost_items, block_accum_dict = self.calc_charge(
                                    row, charge, cost_items, block_accum_dict)
                        elif charge.time:
                            for period in charge.time.periods:
                                if period.from_weekday <= dt.dayofweek <= period.to_weekday and datetime.time(
                                        hour=period.from_hour,
                                        minute=period.from_minute
                                ) <= time <= datetime.time(
                                        hour=period.to_hour,
                                        minute=period.to_minute):
                                    cost_items, block_accum_dict = self.calc_charge(
                                        row, charge, cost_items,
                                        block_accum_dict)
                        elif charge.rate_schedule:
                            for schedule_item in charge.rate_schedule:
                                if dt.to_pydatetime() < schedule_item.datetime:
                                    cost_items[charge.type + 'scheduled'][
                                        'cost'] += schedule_item.rate * float(
                                            row[charge.type])
                                    break
                        else:
                            cost_items, block_accum_dict = self.calc_charge(
                                row, charge, cost_items, block_accum_dict)

        return cost_items

    def apply(self, meter_data, start=None, end=None):
        """
            Calculates the cost of energy given a tariff and load.

            :param meter_data: a three-column pandas array with datetime, imported energy (kwh), exported energy (kwh)
            :param start: an optional datetime to select the commencement of the bill calculation
            :param end: an optional datetime to select the termination of the bill calculation
            :return: a dictionary containing the charge components (e.g. off_peak, shoulder, peak, total)
        """
        meter_data.truncate(before=start, after=end)

        # Create empty dict to hold calculated cost items
        cost_items = dict()
        for charge in self.charges:
            season = charge.season.name if charge.season else None
            time = charge.time.name if charge.time else None
            cost_items[charge.name] = {
                'name': charge.name,
                'code': charge.code,
                'type': charge.type,
                'season': season,
                'time': time,
                'cost': 0.0,
            }

        if 'consumption' in self.charge_types or 'generation' in self.charge_types:
            consumption_data = meter_data
            # Resample meter data if finer resolution data not required by the specified tariff types
            if 'tou' not in self.charge_types:
                if 'seasonal' in self.charge_types:
                    consumption_data = meter_data.resample('D').sum()
                else:
                    consumption_data = meter_data.resample(
                        PERIOD_TO_TIMESTEP[self.billing_period]).sum()

            cost_items.update(
                self.apply_by_charge_type(consumption_data, cost_items,
                                          'consumption'))

        if 'demand' in self.charge_types:
            # Resample meter data to the demand window and then take the maximum for each billing period
            demand_data = meter_data.resample(
                PERIOD_TO_TIMESTEP[self.demand_window]).mean()
            peak_monthly = demand_data.resample(
                PERIOD_TO_TIMESTEP[self.billing_period]).max()
            cost_items.update(
                self.apply_by_charge_type(peak_monthly, cost_items, 'demand'))

        if 'fixed' in self.charge_types:
            billing_periods = len(
                meter_data.resample(
                    PERIOD_TO_TIMESTEP[self.billing_period]).mean())
            for charge in self.charges:
                if charge.type == 'fixed':
                    cost_items[
                        charge.name]['cost'] = billing_periods * charge.rate

        # Transform the output data into the specified output format
        cost_dict = {
            'name': self.name,
            'code': self.code,
            'items': cost_items.values()
        }

        return dict_codec.load(cost_dict, Cost)