Esempio n. 1
0
class SourceComponentBase(odin.Resource):
    """Base class for archival components.

    Subclassed by SourceArchivalObject and SourceResource.

    Both language and lang_material need to exist in order to accomodate
    ArchivesSpace API changes between v2.6 and v2.7.
    """
    class Meta:
        abstract = True

    COMPONENT_TYPES = (('archival_object', 'Archival Object'), ('resource',
                                                                'Resource'))

    dates = odin.ArrayOf(SourceDate)
    extents = odin.ArrayOf(SourceExtent)
    external_ids = odin.ArrayOf(SourceExternalId)
    group = odin.DictAs(SourceGroup)
    jsonmodel_type = odin.StringField(choices=COMPONENT_TYPES)
    lang_materials = odin.ArrayOf(SourceLangMaterial, null=True)
    language = odin.StringField(null=True)
    level = odin.StringField()
    linked_agents = odin.ArrayOf(SourceLinkedAgent)
    notes = odin.ArrayOf(SourceNote)
    publish = odin.BooleanField()
    subjects = odin.ArrayOf(SourceRef)
    suppressed = odin.StringField()
    title = odin.StringField(null=True)
    uri = odin.StringField()
Esempio n. 2
0
class Level1(odin.Resource):
    class Meta:
        namespace = 'odin.traversal'

    name = odin.StringField()
    level2 = odin.DictAs(Level2)
    level2s = odin.DictOf(Level2)
Esempio n. 3
0
class SourceTransfer(odin.Resource):
    metadata = odin.DictAs(SourceMetadata)
    url = odin.StringField()
    rights_statements = odin.ArrayOf(SourceRightsStatement)
    resource = odin.StringField()
    parent = odin.StringField(null=True)
    linked_agents = odin.ArrayOf(SourceLinkedCreator, null=True)
    level = odin.StringField()
Esempio n. 4
0
class SourceSubject(odin.Resource):
    """A topical term."""
    external_ids = odin.ArrayOf(SourceExternalId)
    group = odin.DictAs(SourceGroup)
    publish = odin.BooleanField()
    source = odin.StringField(choices=configs.SUBJECT_SOURCE_CHOICES)
    terms = odin.ArrayOf(SourceTerm)
    title = odin.StringField()
    uri = odin.StringField()
Esempio n. 5
0
class SourceArchivalObject(SourceComponentBase):
    """A component of a SourceResource."""
    position = odin.IntegerField()
    ref_id = odin.StringField()
    component_id = odin.StringField(null=True)
    display_string = odin.StringField()
    restrictions_apply = odin.BooleanField()
    ancestors = odin.ArrayOf(SourceAncestor)
    resource = odin.DictAs(SourceRef)
    has_unpublished_ancestor = odin.BooleanField()
    instances = odin.ArrayOf(SourceInstance)
Esempio n. 6
0
class SourceAgentBase(odin.Resource):
    """A base class for agents.

    Subclassed by SourceAgentFamily, SourceAgentPerson and
    SourceAgentCorporateEntity.
    """
    class Meta:
        abstract = True

    AGENT_TYPES = (('agent_corporate_entity', 'Organization'),
                   ('agent_family', 'Family'), ('agent_person', 'Person'))

    agent_record_identifiers = odin.ArrayOf(SourceAgentRecordIdentifier,
                                            null=True)
    dates_of_existence = odin.ArrayField(null=True)
    group = odin.DictAs(SourceGroup)
    jsonmodel_type = odin.StringField(choices=AGENT_TYPES)
    notes = odin.ArrayOf(SourceNote)
    publish = odin.BooleanField()
    title = odin.StringField()
    uri = odin.StringField()
Esempio n. 7
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
Esempio n. 8
0
class ArchivesSpaceArchivalObject(ArchivesSpaceComponentBase):
    """Groups of records that are part of collections."""
    language = odin.StringField(null=True)
    level = odin.StringField(choices=resource_configs.LEVEL_CHOICES)
    resource = odin.DictAs(ArchivesSpaceRef)
    parent = odin.DictField(null=True)
Esempio n. 9
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
Esempio n. 10
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)
Esempio n. 11
0
class SourceStructuredDate(odin.Resource):
    """An alternative representation of dates, currently associated only with agents."""
    date_label = odin.StringField(choices=configs.DATE_LABEL_CHOICES)
    date_type_structured = odin.StringField(choices=configs.DATE_TYPE_CHOICES)
    structured_date_single = odin.DictAs(SourceStructuredDateSingle, null=True)
    structured_date_range = odin.DictAs(SourceStructuredDateRange, null=True)
Esempio n. 12
0
class SourceInstance(odin.Resource):
    """The physical or digital instantiation of a group of records."""
    instance_type = odin.StringField(choices=configs.INSTANCE_TYPE_CHOICES)
    is_representative = odin.BooleanField()
    sub_container = odin.DictAs(SourceSubcontainer, null=True)
    digital_object = odin.DictAs(SourceRef, null=True)
Esempio n. 13
0
class SourceAgentFamily(SourceAgentBase):
    """A family."""
    names = odin.ArrayOf(SourceNameFamily)
    display_name = odin.DictAs(SourceNameFamily)
Esempio n. 14
0
class SourceAgentCorporateEntity(SourceAgentBase):
    """An organization."""
    names = odin.ArrayOf(SourceNameCorporateEntity)
    display_name = odin.DictAs(SourceNameCorporateEntity)
Esempio n. 15
0
class ArchivesSpaceLangMaterial(odin.Resource):
    """Records information about the languages of archival records.

    Applies to resources post-ArchivesSpace v2.7 only.
    """
    language_and_script = odin.DictAs(ArchivesSpaceLanguageAndScript, null=True)
Esempio n. 16
0
class SourceAgentPerson(SourceAgentBase):
    """A person."""
    names = odin.ArrayOf(SourceNamePerson)
    display_name = odin.DictAs(SourceNamePerson)
Esempio n. 17
0
class SourceSubcontainer(odin.Resource):
    """Provides detailed container information."""
    indicator_2 = odin.StringField(null=True)
    type_2 = odin.StringField(choices=configs.CONTAINER_TYPE_CHOICES,
                              null=True)
    top_container = odin.DictAs(SourceRef)