def snip_codes(codes: element.Element) -> Codes: wall_codes = codes.xpath('Wall/*/Code') window_codes = codes.xpath('Window/*/Code') return Codes( wall=[node.to_string() for node in wall_codes], window=[node.to_string() for node in window_codes], )
def from_data( cls, water_heating: element.Element) -> typing.List['WaterHeating']: water_heatings = water_heating.xpath( "*[self::Primary or self::Secondary]") return [cls._from_data(heater) for heater in water_heatings]
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 _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, 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 _get_heating_type(cls, node: element.Element) -> HeatingType: candidates = [candidate.tag for candidate in node.xpath('Type1/*')] heating_type: typing.Optional[HeatingType] = None for candidate in candidates: if candidate in cls._HEATING_TYPE_NODE_NAMES: heating_type = cls._HEATING_TYPE_NODE_NAMES[candidate] break if heating_type is None: raise InvalidEmbeddedDataTypeError( Heating, f'Could not identify HeatingType, candidate node names = {[candidates]}' ) return heating_type
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_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 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 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 _extract_nodes(node: element.Element, path: str) -> typing.List[element.Element]: return node.xpath(path)
def test_xpath_returns_elements(fragment_node: element.Element) -> None: output = fragment_node.xpath('Bar') assert len(output) == 2 assert all([isinstance(bar_node, element.Element) for bar_node in output]) assert output[0].attrib['id'] == '1'
def snip_energy_upgrade_order( energy_upgrades: element.Element) -> EnergyUpgradesSnippet: upgrades = energy_upgrades.xpath('EnergyUpgrades/Settings/*') return EnergyUpgradesSnippet( upgrades=[upgrade.to_string() for upgrade in upgrades], )