Skip to content

Commit

Permalink
Merge pull request #8 from codeforboston/require_utc
Browse files Browse the repository at this point in the history
Require UTC
  • Loading branch information
AdamFinkle authored Jul 11, 2024
2 parents 82f1fc2 + 947ace2 commit b190be2
Show file tree
Hide file tree
Showing 21 changed files with 387,270 additions and 387,140 deletions.
25 changes: 25 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
black:
black --check .

isort:
isort --check .

pydocstyle:
pydocstyle .

lint: black isort pydocstyle

mypy:
mypy .

gen_examples:
python .\tests\test_rules_engine\generate_example_data.py

test:
pytest .

build:
pip install -q build
python -m build

all: lint mypy gen_examples test build
97 changes: 55 additions & 42 deletions src/greenbutton_objects/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from enum import Enum


class AccumulationBehaviourType(Enum):
notApplicable = 0
bulkQuantity = 1
Expand All @@ -11,7 +12,8 @@ class AccumulationBehaviourType(Enum):
indicating = 6
summation = 9
instantaneous = 12



class CommodityType(Enum):
notApplicable = 0
electricity = 1
Expand All @@ -23,7 +25,8 @@ class CommodityType(Enum):
wastewater = 11
heatingFluid = 12
coolingFluid = 13



class ConsumptionTierType(Enum):
notApplicable = 0
blockTier1 = 1
Expand All @@ -42,7 +45,8 @@ class ConsumptionTierType(Enum):
blockTier14 = 14
blockTier15 = 15
blockTier16 = 16



class CurrencyCode(Enum):
na = 0
aus = 36
Expand All @@ -53,24 +57,27 @@ class CurrencyCode(Enum):
@property
def symbol(self):
if self in [CurrencyCode.aus, CurrencyCode.cad, CurrencyCode.usd]:
return '$'
return "$"
elif self is CurrencyCode.eur:
return '€'
return "€"
else:
return '¤'

return "¤"


class DataQualifierType(Enum):
notApplicable = 0
average = 2
maximum = 8
minimum = 9
normal = 12



class FlowDirectionType(Enum):
notApplicable = 0
forward = 1
reverse = 19



class KindType(Enum):
notApplicable = 0
currency = 3
Expand All @@ -87,7 +94,8 @@ class KindType(Enum):
voltageAngle = 55
distortionPowerFactor = 64
volumetricFlow = 155



class PhaseCode(Enum):
notApplicable = 0
c = 32
Expand All @@ -104,7 +112,8 @@ class PhaseCode(Enum):
s1n = 513
s1s2 = 768
s1s2n = 769



class QualityOfReading(Enum):
valid = 0
manuallyEdited = 7
Expand All @@ -119,7 +128,8 @@ class QualityOfReading(Enum):
other = 16
validated = 17
verified = 18



class ServiceKind(Enum):
electricity = 0
naturalGas = 1
Expand All @@ -130,6 +140,7 @@ class ServiceKind(Enum):
communication = 7
time = 8


class TimeAttributeType(Enum):
notApplicable = 0
tenMinutes = 1
Expand All @@ -145,6 +156,7 @@ class TimeAttributeType(Enum):
forTheSpecifiedPeriod = 32
daily30MinuteFixedBlock = 79


class UomType(Enum):
notApplicable = 0
amps = 5
Expand Down Expand Up @@ -177,35 +189,36 @@ class UomType(Enum):
absolutePascals = 155
therms = 169


UOM_SYMBOLS = {
UomType.notApplicable: '',
UomType.amps: 'A',
UomType.volts: 'V',
UomType.joules: 'J',
UomType.hertz: 'Hz',
UomType.watts: 'W',
UomType.cubicMeters: 'm³',
UomType.voltAmps: 'VA',
UomType.voltAmpsReactive: 'VAr',
UomType.cosine: 'cos',
UomType.voltsSquared: 'V²',
UomType.ampsSquared: 'A²',
UomType.voltAmpHours: 'VAh',
UomType.wattHours: 'Wh',
UomType.voltAmpReactiveHours: 'VArh',
UomType.ampHours: 'Ah',
UomType.cubicFeet: 'ft³',
UomType.cubicFeetPerHour: 'ft³/h',
UomType.cubicMetersPerHour: 'm³/h',
UomType.usGallons: 'US gal',
UomType.usGallonsPerHour: 'US gal/h',
UomType.imperialGallons: 'IMP gal',
UomType.imperialGallonsPerHour: 'IMP gal/h',
UomType.britishThermalUnits: 'BTU',
UomType.britishThermalUnitsPerHour: 'BTU/h',
UomType.liters: 'L',
UomType.litersPerHour: 'L/h',
UomType.gaugePascals: 'Pag',
UomType.absolutePascals: 'Pa',
UomType.therms: 'thm',
UomType.notApplicable: "",
UomType.amps: "A",
UomType.volts: "V",
UomType.joules: "J",
UomType.hertz: "Hz",
UomType.watts: "W",
UomType.cubicMeters: "m³",
UomType.voltAmps: "VA",
UomType.voltAmpsReactive: "VAr",
UomType.cosine: "cos",
UomType.voltsSquared: "V²",
UomType.ampsSquared: "A²",
UomType.voltAmpHours: "VAh",
UomType.wattHours: "Wh",
UomType.voltAmpReactiveHours: "VArh",
UomType.ampHours: "Ah",
UomType.cubicFeet: "ft³",
UomType.cubicFeetPerHour: "ft³/h",
UomType.cubicMetersPerHour: "m³/h",
UomType.usGallons: "US gal",
UomType.usGallonsPerHour: "US gal/h",
UomType.imperialGallons: "IMP gal",
UomType.imperialGallonsPerHour: "IMP gal/h",
UomType.britishThermalUnits: "BTU",
UomType.britishThermalUnitsPerHour: "BTU/h",
UomType.liters: "L",
UomType.litersPerHour: "L/h",
UomType.gaugePascals: "Pag",
UomType.absolutePascals: "Pa",
UomType.therms: "thm",
}
95 changes: 63 additions & 32 deletions src/greenbutton_objects/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,92 @@
@functools.total_ordering
class DateTimeInterval:
def __init__(self, entity):
self.duration = utils.getEntity(entity, 'espi:duration',
lambda e: datetime.timedelta(seconds=int(e.text)))
self.start = utils.getEntity(entity, 'espi:start',
lambda e: datetime.datetime.fromtimestamp(int(e.text)))

self.start = utils.getEntity(
entity,
"espi:start",
lambda e: datetime.datetime.fromtimestamp(int(e.text)).astimezone(
datetime.timezone.utc
),
)
self.duration = utils.getEntity(
entity, "espi:duration", lambda e: datetime.timedelta(seconds=int(e.text))
)

def __repr__(self):
return '<DateTimeInterval (%s, %s)>' % (self.start, self.duration)
return "<DateTimeInterval (%s, %s)>" % (self.start, self.duration)

def __eq__(self, other):
if not isinstance(other, DateTimeInterval):
return False
return (self.start, self.duration) == (other.start, other.duration)

def __lt__(self, other):
if not isinstance(other, DateTimeInterval):
return False
return (self.start, self.duration) < (other.start, other.duration)


@functools.total_ordering
class IntervalReading:
def __init__(self, entity, parent):
self.intervalBlock = parent
self.cost = utils.getEntity(entity, 'espi:cost', lambda e: int(e.text) / 100000.0)
self.timePeriod = utils.getEntity(entity, 'espi:timePeriod',
lambda e: DateTimeInterval(e))
self._value = utils.getEntity(entity, 'espi:value', lambda e: int(e.text))
self.cost = utils.getEntity(
entity, "espi:cost", lambda e: int(e.text) / 100000.0
)
self.timePeriod = utils.getEntity(
entity, "espi:timePeriod", lambda e: DateTimeInterval(e)
)
self._value = utils.getEntity(entity, "espi:value", lambda e: int(e.text))

self.readingQualities = set(
[
ReadingQuality(rq, self)
for rq in entity.findall("espi:ReadingQuality", utils.ns)
]
)

self.readingQualities = set([ReadingQuality(rq, self) for rq in entity.findall('espi:ReadingQuality', utils.ns)])

def __repr__(self):
return '<IntervalReading (%s, %s: %s %s)>' % (self.timePeriod.start, self.timePeriod.duration, self.value, self.value_symbol)
return "<IntervalReading (%s, %s: %s %s)>" % (
self.timePeriod.start,
self.timePeriod.duration,
self.value,
self.value_symbol,
)

def __eq__(self, other):
if not isinstance(other, IntervalReading):
return False
return (self.timePeriod, self.value) == (other.timePeriod, other.value)

def __lt__(self, other):
if not isinstance(other, IntervalReading):
return False
return (self.timePeriod, self.value) < (other.timePeriod, other.value)

@property
def value(self):
if self.intervalBlock is not None and \
self.intervalBlock.meterReading is not None and \
self.intervalBlock.meterReading.readingType is not None and \
self.intervalBlock.meterReading.readingType.powerOfTenMultiplier is not None:
multiplier = 10 ** self.intervalBlock.meterReading.readingType.powerOfTenMultiplier
if (
self.intervalBlock is not None
and self.intervalBlock.meterReading is not None
and self.intervalBlock.meterReading.readingType is not None
and self.intervalBlock.meterReading.readingType.powerOfTenMultiplier
is not None
):
multiplier = (
10**self.intervalBlock.meterReading.readingType.powerOfTenMultiplier
)
else:
multiplier = 1
return self._value * multiplier

@property
def cost_units(self):
if self.intervalBlock is not None and \
self.intervalBlock.meterReading is not None and \
self.intervalBlock.meterReading.readingType is not None and \
self.intervalBlock.meterReading.readingType.currency is not None:
if (
self.intervalBlock is not None
and self.intervalBlock.meterReading is not None
and self.intervalBlock.meterReading.readingType is not None
and self.intervalBlock.meterReading.readingType.currency is not None
):
return self.intervalBlock.meterReading.readingType.currency
else:
return enums.CurrencyCode.na
Expand All @@ -80,19 +106,24 @@ def cost_symbol(self):

@property
def value_units(self):
if self.intervalBlock is not None and \
self.intervalBlock.meterReading is not None and \
self.intervalBlock.meterReading.readingType is not None and \
self.intervalBlock.meterReading.readingType.uom is not None:
if (
self.intervalBlock is not None
and self.intervalBlock.meterReading is not None
and self.intervalBlock.meterReading.readingType is not None
and self.intervalBlock.meterReading.readingType.uom is not None
):
return self.intervalBlock.meterReading.readingType.uom
else:
return enums.UomType.notApplicable

@property
def value_symbol(self):
return enums.UOM_SYMBOLS[self.value_units]



class ReadingQuality:
def __init__(self, entity, parent):
self.intervalReading = parent
self.quality = utils.getEntity(entity, 'espi:quality', lambda e: enums.QualityOfReading(int(e.text)))
self.quality = utils.getEntity(
entity, "espi:quality", lambda e: enums.QualityOfReading(int(e.text))
)
Loading

0 comments on commit b190be2

Please sign in to comment.