def from_data(cls, floor: element.Element) -> 'Floor': try: return Floor( label=floor.get_text('Label'), nominal_insulation=insulation.Insulation(floor.get('Construction/Type/@nominalInsulation', float)), effective_insulation=insulation.Insulation(floor.get('Construction/Type/@rValue', float)), floor_area=area.Area(floor.get('Measurements/@area', float)), floor_length=distance.Distance(floor.get('Measurements/@length', float)), ) except (ElementGetValueError) as exc: raise InvalidEmbeddedDataTypeError(Floor) from exc
def from_data(cls, header: element.Element) -> 'BasementHeader': try: nominal_insulation = header.get('Construction/Type/@nominalInsulation', float) effective_insulation = header.get('Construction/Type/@rValue', float) height = header.get('Measurements/@height', float) width = header.get('Measurements/@perimeter', float) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError(BasementHeader, 'Invalid/Missing attribute value') from exc return BasementHeader( nominal_insulation=insulation.Insulation(nominal_insulation), effective_insulation=insulation.Insulation(effective_insulation), height=distance.Distance(height), perimeter=distance.Distance(width), )
def from_data(cls, node: element.Element) -> 'Heating': try: label = node.get_text('Label') efficiency = node.get('Type1/*/Specifications/@efficiency', float) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError( Heating, 'Invalid/missing Heating values') from exc heating_type = cls._get_heating_type(node) output_size = cls._get_output_size(node) if heating_type is HeatingType.BASEBOARD: steady_state = 'Steady State' energy_source = EnergySource.ELECTRIC equipment_type = cls._HEATING_TYPE_TRANSLATIONS[ HeatingType.BASEBOARD] else: steady_state = cls._get_steady_state(node) energy_source = cls._get_energy_source(node) equipment_type = cls._get_equipment_type(node) return Heating( label=label, output_size=output_size, efficiency=efficiency, steady_state=steady_state, heating_type=heating_type, energy_source=energy_source, equipment_type=equipment_type, )
def _get_nullable_fields(root: element.Element, path: str) -> typing.Optional[str]: data: typing.Optional[str] try: data = root.get(path, str) except ElementGetValueError: data = None return data
def from_data(cls, wall_code: element.Element) -> 'WallCode': structure_type_english = wall_code.findtext( 'Layers/StructureType/English') structure_type_french = wall_code.findtext( 'Layers/StructureType/French') component_type_size_english = wall_code.findtext( 'Layers/ComponentTypeSize/English') component_type_size_french = wall_code.findtext( 'Layers/ComponentTypeSize/French') try: return WallCode(identifier=wall_code.get('@id', str), label=wall_code.get_text('Label'), tags={ WallCodeTag.STRUCTURE_TYPE: bilingual.Bilingual( english=structure_type_english, french=structure_type_french, ) if structure_type_english and structure_type_french else None, WallCodeTag.COMPONENT_TYPE_SIZE: bilingual.Bilingual( english=component_type_size_english, french=component_type_size_french, ) if component_type_size_english and component_type_size_french else None, }) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError( WallCode, 'Unable to get identifier attributes') from exc
def from_data(cls, door: element.Element) -> 'Door': try: return Door( label=door.get_text('Label'), door_type=bilingual.Bilingual( english=door.get_text('Construction/Type/English'), french=door.get_text('Construction/Type/French'), ), door_insulation=insulation.Insulation( door.get('Construction/Type/@value', float)), height=distance.Distance( door.get('Measurements/@height', float)), width=distance.Distance(door.get('Measurements/@width', float)), ) except (ElementGetValueError) as exc: raise InvalidEmbeddedDataTypeError(Door) from exc
def from_basement(cls, wall: element.Element, wall_perimeter: float) -> typing.List['BasementWall']: interior_wall_sections = wall.xpath('Construction/InteriorAddedInsulation/Composite/Section') exterior_wall_sections = wall.xpath('Construction/ExteriorAddedInsulation/Composite/Section') pony_wall_sections = wall.xpath('Construction/PonyWallType/Composite/Section') try: wall_height = wall.get('Measurements/@height', float) pony_height = wall.get('Measurements/@ponyWallHeight', float) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError(BasementWall, 'Missing/invalid basement wall height') from exc walls = [] sections = (interior_wall_sections, exterior_wall_sections, pony_wall_sections) parsers = ( lambda section, percentage: BasementWall._from_data( section, wall_perimeter, wall_height, WallType.INTERIOR, percentage ), lambda section, percentage: BasementWall._from_data( section, wall_perimeter, wall_height, WallType.EXTERIOR, percentage ), lambda section, percentage: BasementWall._from_data( section, wall_perimeter, pony_height, WallType.PONY, percentage ) ) for parser, wall_sections in zip(parsers, sections): percentages = [wall.attrib.get('percentage') for wall in wall_sections] accounted_for = sum(float(percentage) for percentage in percentages if percentage is not None) walls.extend([parser(wall, 100-accounted_for) for wall in wall_sections]) return walls
def _get_steady_state(node: element.Element) -> str: try: steady_state_value = node.get( 'Type1/*/Specifications/@isSteadyState', str) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError( Heating, 'No isSteadyState property value') from exc return 'Steady State' if steady_state_value == 'true' else 'AFUE'
def _from_data(cls, floor: element.Element, construction_type: str, floor_type: FloorType) -> 'BasementFloor': length: typing.Optional[float] = None width: typing.Optional[float] = None try: rectangular = floor.get('Measurements/@isRectangular', str) == 'true' if rectangular: length = floor.get('Measurements/@length', float) width = floor.get('Measurements/@width', float) perimeter = (2 * length) + (2 * width) floor_area = length * width else: floor_area = floor.get('Measurements/@area', float) perimeter = floor.get('Measurements/@perimeter', float) nominal_insulation_node = floor.xpath(f'Construction/{construction_type}/@nominalInsulation') effective_insulation_node = floor.xpath(f'Construction/{construction_type}/@rValue') nominal_insulation = float(nominal_insulation_node[0]) if nominal_insulation_node else None effective_insulation = float(effective_insulation_node[0]) if effective_insulation_node else None except ValueError as exc: raise InvalidEmbeddedDataTypeError(BasementFloor, 'Invalid insulation attribute values') from exc except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError(BasementFloor, 'Invalid attributes') from exc return BasementFloor( floor_type=floor_type, rectangular=rectangular, nominal_insulation=insulation.Insulation(nominal_insulation) if nominal_insulation is not None else None, effective_insulation=insulation.Insulation(effective_insulation) if effective_insulation is not None else None, length=distance.Distance(length) if length is not None else None, width=distance.Distance(width) if width is not None else None, perimeter=distance.Distance(perimeter), floor_area=area.Area(floor_area), )
def from_data(cls, ceiling: element.Element) -> 'Ceiling': try: return Ceiling( label=ceiling.get_text('Label'), ceiling_type=bilingual.Bilingual( english=ceiling.get_text('Construction/Type/English'), french=ceiling.get_text('Construction/Type/French'), ), nominal_insulation=insulation.Insulation( ceiling.get('Construction/CeilingType/@nominalInsulation', float)), effective_insulation=insulation.Insulation( ceiling.get('Construction/CeilingType/@rValue', float)), ceiling_area=area.Area(ceiling.get('Measurements/@area', float)), ceiling_length=distance.Distance( ceiling.get('Measurements/@length', float)), ) except (ElementGetValueError) as exc: raise InvalidEmbeddedDataTypeError(Ceiling) from exc
def from_data(cls, window: element.Element, window_codes: typing.Dict[str, code.WindowCode]) -> 'Window': code_id = window.xpath('Construction/Type/@idref') window_code = window_codes[code_id[0]] if code_id else None try: return Window( label=window.get_text('Label'), window_code=window_code, window_insulation=insulation.Insulation( window.get('Construction/Type/@rValue', float)), width=distance.Distance( window.get('Measurements/@width', float) / _MILLIMETRES_TO_METRES), height=distance.Distance( window.get('Measurements/@height', float) / _MILLIMETRES_TO_METRES), ) except (ElementGetValueError) as exc: raise InvalidEmbeddedDataTypeError(Window) from exc
def from_data(cls, wall: element.Element, wall_codes: typing.Dict[str, code.WallCode]) -> 'Wall': code_id = wall.xpath('Construction/Type/@idref') wall_code = wall_codes[code_id[0]] if code_id else None try: return Wall( label=wall.get_text('Label'), wall_code=wall_code, nominal_insulation=insulation.Insulation( wall.get('Construction/Type/@nominalInsulation', float)), effective_insulation=insulation.Insulation( wall.get('Construction/Type/@rValue', float)), perimeter=distance.Distance( wall.get('Measurements/@perimeter', float)), height=distance.Distance( wall.get('Measurements/@height', float)), ) except (ElementGetValueError) as exc: raise InvalidEmbeddedDataTypeError(Wall) from exc
def _get_energy_source(cls, node: element.Element) -> EnergySource: try: code = node.get('Type1/*/Equipment/EnergySource/@code', int) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError( Heating, 'No EnergySource heating code') from exc energy_source = cls._ENERGY_SOURCE_CODES.get(code) if energy_source is None: raise InvalidEmbeddedDataTypeError( Heating, f'Unknown energy source: {energy_source}') return energy_source
def _from_data(cls, water_heating: element.Element) -> 'WaterHeating': drain_water_efficiency: typing.Optional[float] = None if water_heating.get('@hasDrainWaterHeatRecovery', str) == 'true': drain_water_efficiency = water_heating.get( 'DrainWaterHeatRecovery/@effectivenessAt9.5', float) try: energy_type = water_heating.get_text('EnergySource/English') tank_type = water_heating.get_text('TankType/English') water_heater_type = cls._TYPE_MAP[(energy_type.lower(), tank_type.lower())] volume = water_heating.get('TankVolume/@value', float) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError( WaterHeating, 'Missing/invalid attribue or text') from exc except KeyError as exc: raise InvalidEmbeddedDataTypeError( WaterHeating, 'Invlaid energy and tank type combination') from exc efficiency_ef_node = water_heating.xpath('EnergyFactor/@value') efficiency_percent_node = water_heating.xpath( 'EnergyFactor/@thermalEfficiency') if not efficiency_ef_node and not efficiency_percent_node: raise InvalidEmbeddedDataTypeError(WaterHeating, 'No efficiency values') return WaterHeating( water_heater_type=water_heater_type, tank_volume=volume, efficiency_ef=float(efficiency_ef_node[0]) if efficiency_ef_node else None, efficiency_percentage=float(efficiency_percent_node[0]) if efficiency_percent_node else None, drain_water_heat_recovery_efficiency_percentage= drain_water_efficiency, )
def _from_data(cls, wall: element.Element, wall_perimeter: float, wall_height: float, tag: WallType, backup_percentage: float) -> 'BasementWall': maybe_percentage = wall.attrib.get('percentage') percentage = float(maybe_percentage) if maybe_percentage else backup_percentage try: nominal_insulation = wall.get('@nominalRsi', float) effective_insulation = wall.get('@rsi', float) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError(BasementWall, 'Invalid insulation attributes') from exc return BasementWall( wall_type=tag, nominal_insulation=insulation.Insulation(nominal_insulation), effective_insulation=insulation.Insulation(effective_insulation), composite_percentage=percentage, wall_area=area.Area(wall_perimeter * wall_height * (percentage / 100)) )
def from_data(cls, basement: element.Element) -> 'Basement': foundation_type = cls._derive_foundation_type(basement.tag) if foundation_type is FoundationType.UNKNOWN: raise InvalidEmbeddedDataTypeError(Basement, f'Invalid foundation type: {basement.tag}') if foundation_type is FoundationType.BASEMENT: floor_from_data = BasementFloor.from_basement wall_from_data = BasementWall.from_basement header_from_data = BasementHeader.from_data elif foundation_type is FoundationType.CRAWLSPACE: floor_from_data = BasementFloor.from_crawlspace wall_from_data = BasementWall.from_crawlspace header_from_data = BasementHeader.from_data else: floor_from_data = BasementFloor.from_slab wall_from_data = lambda *args: [] header_from_data = lambda *args: None floor_nodes = basement.xpath('Floor') header_nodes = basement.xpath('Components/FloorHeader') wall_nodes = basement.xpath('Wall') floors = floor_from_data(floor_nodes[0] if floor_nodes else None) walls = wall_from_data(wall_nodes[0], floors[0].perimeter.metres) if wall_nodes else [] header = header_from_data(header_nodes[0]) if header_nodes else None try: configuration_type = basement.get('Configuration/@type', str) label = basement.get_text('Label') except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError(Basement, 'Missing/invalid foundation attributes') from exc if not configuration_type: raise InvalidEmbeddedDataTypeError(Basement, 'Empty configuration type') return Basement( foundation_type=foundation_type, label=label, configuration_type=configuration_type, walls=walls, floors=floors, header=header, )
def from_crawlspace(cls, wall: element.Element, wall_perimeter: float) -> typing.List['BasementWall']: wall_sections = wall.xpath('Construction/Type/Composite/Section') try: wall_height = wall.get('Measurements/@height', float) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError(BasementWall, 'Missing/invalid wall height') from exc percentages = [wall.attrib.get('percentage') for wall in wall_sections] accounted_for = sum(float(percentage) for percentage in percentages if percentage is not None) return [ BasementWall._from_data( wall_section, wall_perimeter, wall_height, WallType.NOT_APPLICABLE, 100-accounted_for ) for wall_section in wall_sections ]
def test_get_int(fragment_node: element.Element) -> None: result = fragment_node.get('Bar/@id', int) assert result == 1 assert isinstance(result, int)
def test_get_float(fragment_node: element.Element) -> None: result = fragment_node.get('Bar/@id', float) assert result == 1.0 assert isinstance(result, float)
def test_get_str(fragment_node: element.Element) -> None: result = fragment_node.get('Bar/@id', str) assert result == '1' assert isinstance(result, str)
def test_get_raises_when_not_found(fragment_node: element.Element) -> None: with pytest.raises(ElementGetValueError): fragment_node.get('Bar/@foo', int)
def test_get_raises_when_cant_cast(fragment_node: element.Element) -> None: with pytest.raises(ElementGetValueError): fragment_node.get('Bar/text()', int)
def from_data(cls, window_code: element.Element) -> 'WindowCode': glazing_type_english = window_code.findtext( 'Layers/GlazingTypes/English') glazing_type_french = window_code.findtext( 'Layers/GlazingTypes/French') coating_tint_english = window_code.findtext( 'Layers/CoatingsTints/English') coating_tint_french = window_code.findtext( 'Layers/CoatingsTints/French') fill_type_english = window_code.findtext('Layers/FillType/English') fill_type_french = window_code.findtext('Layers/FillType/French') spacer_type_english = window_code.findtext('Layers/SpacerType/English') spacer_type_french = window_code.findtext('Layers/SpacerType/French') window_code_type_english = window_code.findtext('Layers/Type/English') window_code_type_french = window_code.findtext('Layers/Type/French') frame_material_english = window_code.findtext( 'Layers/FrameMaterial/English') frame_material_french = window_code.findtext( 'Layers/FrameMaterial/French') try: return WindowCode( identifier=window_code.get('@id', str), label=window_code.get_text('Label'), tags={ WindowCodeTag.GLAZING_TYPE: bilingual.Bilingual( english=glazing_type_english, french=glazing_type_french, ) if glazing_type_english and glazing_type_french else None, WindowCodeTag.COATING_TINTS: bilingual.Bilingual( english=coating_tint_english, french=coating_tint_french, ) if coating_tint_english and coating_tint_french else None, WindowCodeTag.FILL_TYPE: bilingual.Bilingual( english=fill_type_english, french=fill_type_french, ) if fill_type_english and fill_type_french else None, WindowCodeTag.SPACER_TYPE: bilingual.Bilingual( english=spacer_type_english, french=spacer_type_french, ) if spacer_type_english and spacer_type_french else None, WindowCodeTag.CODE_TYPE: bilingual.Bilingual( english=window_code_type_english, french=window_code_type_french, ) if window_code_type_english and window_code_type_french else None, WindowCodeTag.FRAME_MATERIAL: bilingual.Bilingual( english=frame_material_english, french=frame_material_french, ) if frame_material_english and frame_material_french else None, }) except ElementGetValueError as exc: raise InvalidEmbeddedDataTypeError( WindowCode, 'Unable to get identifier attributes') from exc
def from_data(cls, setting: element.Element) -> 'Upgrade': return Upgrade(upgrade_type=setting.tag, cost=setting.get('@cost', int), priority=setting.get('@priority', int))