diff --git a/backend/Property.py b/backend/Property.py index 1b73429a..d32feebf 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -3,6 +3,7 @@ import ast from itertools import groupby import pandas as pd import numpy as np +from typing import Set from datetime import datetime, timedelta from etl.epc.Dataset import TrainingDataset @@ -55,12 +56,11 @@ class Property: walls = None windows = None lighting = None - energy_source = None spatial = None base_difference_record = None - DATA_ANOMALY_MATCHES = DATA_ANOMALY_MATCHES + DATA_ANOMALY_MATCHES: Set = DATA_ANOMALY_MATCHES # Surplus information, that can be provided as optional inputs, by a customer n_bathrooms = None @@ -101,8 +101,7 @@ class Property: self.address = address self.postcode = postcode - self.old_data = self.epc_record.get("old_data") - self.property_dimensions = None + self.old_data = self.epc_record.old_data # This is a list of measures that have already been installed in the property, typically found as a result # of the non-invasive surveys. We reflect that this has been installed in the recommendations, but remove the # cost and instead, provide a message that the measure has already been installed @@ -121,17 +120,17 @@ class Property: self.valuation = property_valuation - self.uprn = uprn if uprn is not None else epc_record.get("uprn") - self.uprn_source = self.epc_record.get("uprn-source") + self.uprn = uprn if uprn is not None else epc_record.uprn + self.uprn_source = self.epc_record.uprn_source - self.full_sap_epc = self.epc_record.get("full_sap_epc") + self.full_sap_epc = self.epc_record.full_sap_epc self.in_conservation_area, self.is_listed, self.is_heritage = None, None, None self.restricted_measures = False - self.year_built = epc_record.get("year_built") - self.number_of_rooms = epc_record.get("number_habitable_rooms") - self.age_band = epc_record.get("age_band") - self.construction_age_band = epc_record.get("construction_age_band") - self.number_of_floors = epc_record.get("number_of_floors") + self.year_built = self.epc_record.year_built + self.number_of_rooms = epc_record.number_habitable_rooms + self.age_band = epc_record.age_band + self.construction_age_band = epc_record.construction_age_band + self.number_of_floors = epc_record.number_of_floors self.perimeter = None self.wall_type = None self.floor_type = None @@ -141,61 +140,27 @@ class Property: # when storing the energy, we'll also self.energy = { - "primary_energy_consumption": epc_record.get("energy_consumption_current"), - "epc_co2_emissions": epc_record.get("co2_emissions_current"), + "primary_energy_consumption": epc_record.energy_consumption_current, + "epc_co2_emissions": epc_record.co2_emissions_current, # These will be added in once we estimate the amount of emissions from appliances - using the carbon # intensity of electricity "appliances_co2_emissions": None, "co2_emissions": None } - self.ventilation = { - "ventilation": epc_record.get("mechanical_ventilation"), - } - self.solar_pv = { - "solar_pv": epc_record.get("photo_supply"), - } - self.solar_hot_water = { - "solar_hot_water": self.epc_record.get("solar_water_heating_flag"), - "solar_hot_water_boolean": self.epc_record.get("solar_water_heating_flag_bool"), - } - self.wind_turbine = { - "wind_turbine": self.epc_record.get("wind_turbine_count"), - } - self.number_of_open_fireplaces = { - "number_of_open_fireplaces": self.epc_record.get( - "number_open_fireplaces" - ), - } - self.number_of_extensions = { - "number_of_extensions": self.epc_record.get("extension_count"), - } - self.number_of_storeys = { - "number_of_storeys": self.epc_record.get("flat_storey_count"), - } - self.heat_loss_corridor = { - "heat_loss_corridor": self.epc_record.get("heat_loss_corridor"), - "length": self.epc_record.get("unheated_corridor_length"), - "heat_loss_corridor_boolean": self.epc_record.get("heat_loss_corridor_bool"), - } - self.mains_gas = self.epc_record.get("mains_gas_flag") - self.floor_height = self.epc_record.get("floor_height") + self.mains_gas = self.epc_record.mains_gas_flag + self.floor_height = self.epc_record.floor_height self.insulation_wall_area = None - self.floor_area = self.epc_record.get("total_floor_area") + self.floor_area = self.epc_record.total_floor_area self.roof_area = None self.insulation_floor_area = None - self.number_lighting_outlets = self.epc_record.get("fixed_lighting_outlets_count") + self.number_lighting_outlets = self.epc_record.fixed_lighting_outlets_count self.floor_level = None self.number_of_windows = None self.windows_area = None - self.solar_pv_percentage = None self.current_energy_consumption = None self.current_energy_consumption_heating_hotwater = None self.current_energy_bill = None - self.expected_energy_bill = None - - self.heating_energy_source = None - self.hot_water_energy_source = None self.recommendations_scoring_data = [] self.simulation_epcs = {} @@ -225,6 +190,12 @@ class Property: # Ventilation self.has_ventilation = self.identify_ventilation() + @staticmethod + def _safe_int(value: str | int | float | None) -> int | None: + if value in [None, ""]: + return None + return int(round(float(value) + 1e-5)) + @classmethod def extract_kwargs(cls, kwargs): """ @@ -237,24 +208,24 @@ class Property: # Note - none of this data is contained in an energy asssessment, but we should consider how this is done # as we collect more data from the energy assessment - n_bathrooms = kwargs.get("n_bathrooms", None) + n_bathrooms = kwargs.get("n_bathrooms") # We add on a small value to ensure that the number of bathrooms is rounded up, in case the value is 0.5 - n_bathrooms = int(round(float(n_bathrooms) + 1e-5)) if n_bathrooms not in [None, ""] else None + n_bathrooms = cls._safe_int(n_bathrooms) if n_bathrooms not in [None, ""] else None - n_bedrooms = kwargs.get("n_bedrooms", None) - n_bedrooms = int(round(float(n_bedrooms) + 1e-5)) if n_bedrooms not in [None, ""] else None + n_bedrooms = kwargs.get("n_bedrooms") + n_bedrooms = cls._safe_int(n_bedrooms) if n_bedrooms not in [None, ""] else None - number_of_floors = kwargs.get("number_of_floors", None) - number_of_floors = int(round(float(number_of_floors) + 1e-5)) if number_of_floors not in [None, ""] else None + number_of_floors = kwargs.get("number_of_floors") + number_of_floors = cls._safe_int(number_of_floors) if number_of_floors not in [None, ""] else None - insulation_floor_area = kwargs.get("insulation_floor_area", None) + insulation_floor_area = kwargs.get("insulation_floor_area") insulation_floor_area = float(insulation_floor_area) if insulation_floor_area not in [None, ""] else None - insulation_wall_area = kwargs.get("insulation_wall_area", None) + insulation_wall_area = kwargs.get("insulation_wall_area") insulation_wall_area = float(insulation_wall_area) if insulation_wall_area not in [None, ""] else None # We allow for the asset owner to provide us with total floor area, in the event of it being incorrect - floor_area = kwargs.get("floor_area", None) + floor_area = kwargs.get("floor_area") floor_area = float(floor_area) if floor_area not in [None, ""] else None return { @@ -283,14 +254,11 @@ class Property: It will be the same starting and ending EPC, as we don't have the expected EPC yet """ - fixed_data_col_names = MANDATORY_FIXED_FEATURES + LATEST_FIELD - fixed_data_col_names = [ - x.lower().replace("_", "-") for x in fixed_data_col_names - ] + fixed_data_col_names = [x.lower() for x in MANDATORY_FIXED_FEATURES + LATEST_FIELD] fixed_data = { k.replace("-", "_"): v - for k, v in self.data.items() + for k, v in vars(self.epc_record).items() if k in fixed_data_col_names } @@ -311,7 +279,7 @@ class Property: # If we have variables that have been given to us by the landlord that we know are correct, whereas the EPC # may not be, we use them - if self.owner_floor_area is not None: + if self.owner_floor_area: self.base_difference_record.df["total_floor_area_ending"] = self.floor_area self.base_difference_record.df["estimated_perimeter_ending"] = self.perimeter @@ -410,7 +378,7 @@ class Property: self.recommendations_scoring_data.append(scoring_dict) - simulation_epc = self.epc_record.__dict__.copy() + simulation_epc = vars(self.epc_record).copy() # Insert static values simulation_epc["lodgement_date"] = simulation_lodgment_date simulation_epc = {k.replace("_", "-"): v for k, v in simulation_epc.items()} @@ -487,7 +455,7 @@ class Property: # CO₂ emissions per square metre floor area per year in kg/m². Since CO₂ emissions are in tonnes # per year, we multiply by 1000 to get kg/m² "co2-emiss-curr-per-floor-area": round( - 1000 * (rec_impact["carbon"] / self.epc_record.get("total_floor_area")) + 1000 * (rec_impact["carbon"] / self.epc_record.total_floor_area) ), "co2-emissions-current": rec_impact["carbon"], "current-energy-rating": sap_to_epc(rec_impact["sap"]), @@ -600,15 +568,16 @@ class Property: for description, attribute in cleaned.items(): cleaner_cls = all_cleaner_map[description] + description_underscore = description.replace("-", "_") - if self.epc_record.get(description) in self.DATA_ANOMALY_MATCHES: + if getattr(self.epc_record, description_underscore) in self.DATA_ANOMALY_MATCHES: if description == "lighting-description": cleaner_cls = cleaner_cls("", averages=None) else: cleaner_cls = cleaner_cls("") fill_dict = { - "original_description": self.epc_record.get(description), - "clean_description": self.epc_record.get(description), + "original_description": getattr(self.epc_record, description_underscore), + "clean_description": getattr(self.epc_record, description_underscore), **cleaner_cls.process() } setattr(self, self.ATTRIBUTE_MAP[description], fill_dict) @@ -617,7 +586,7 @@ class Property: attributes = [ x for x in cleaned[description] - if x["original_description"] == self.epc_record.get(description) + if x["original_description"] == getattr(self.epc_record, description_underscore) ] if len(attributes) > 1: @@ -628,11 +597,11 @@ class Property: if len(attributes) == 0: # We attempt to perform the clean on the fly if description == "lighting-description": - cleaner_cls = cleaner_cls(self.epc_record.get(description), averages=None) + cleaner_cls = cleaner_cls(getattr(self.epc_record, description_underscore), averages=None) else: - cleaner_cls = cleaner_cls(self.epc_record.get(description)) + cleaner_cls = cleaner_cls(getattr(self.epc_record, description_underscore)) processed = { - "original_description": self.epc_record.get(description), + "original_description": getattr(self.epc_record, description_underscore), "clean_description": cleaner_cls.description.replace( "(assumed)", "" ) @@ -671,12 +640,12 @@ class Property: # Today's costs todays_lighting_cost = kwh_client.convert_cost_to_today( - original_cost=float(self.data["lighting-cost-current"]), - lodgement_date=pd.Timestamp(self.epc_record.get("lodgement_date")).tz_localize(None) + original_cost=float(self.epc_record.lighting_cost_current), + lodgement_date=pd.Timestamp(self.epc_record.lodgement_date).tz_localize(None) ) # If we have the kwh figures, we don't need to predict them - condition_data = self.energy_assessment_condition_data.copy() + condition_data = self.energy_assessment_condition_data heating_kwh_predictions = kwh_predictions["heating_kwh_predictions"] hotwater_kwh_predictions = kwh_predictions["hotwater_kwh_predictions"] @@ -715,19 +684,13 @@ class Property: } # Sum up the adjusted kwh figures - self.current_energy_consumption = sum(list(unadjusted_kwh_estimates.values())) + self.current_energy_consumption = sum(unadjusted_kwh_estimates.values()) self.current_energy_consumption_heating_hotwater = ( unadjusted_kwh_estimates["heating"] + unadjusted_kwh_estimates["hot_water"] ) self.energy_cost_estimates = { "unadjusted": unadjusted_heating_costs, - # Don't think we need the EPC - # "epc": { - # "heating": float(self.data["heating-cost-current"]), - # "hot_water": float(self.data["hot-water-cost-current"]), - # "lighting": float(self.data["lighting-cost-current"]), - # } } self.energy_consumption_estimates = { @@ -787,7 +750,7 @@ class Property: :return: """ - current_sap_rating = float(self.data["current-energy-efficiency"]) + current_sap_rating = float(self.epc_record.current_energy_efficiency) if needs_rebaselining: current_sap_rating += rebaselining_sap @@ -795,24 +758,24 @@ class Property: property_data = { "creation_status": "READY", - "uprn": int(self.data["uprn"]), + "uprn": int(self.epc_record.uprn), "building_reference_number": ( - int(self.data["building-reference-number"]) if - self.data["building-reference-number"] is not None else None + int(self.epc_record.building_reference_number) if + self.epc_record.building_reference_number is not None else None ), "has_pre_condition_report": True, "has_recommendations": True, - "property_type": self.data["property-type"], - "built_form": self.data["built-form"], - "local_authority": self.data["local-authority-label"], - "constituency": self.data["constituency-label"], + "property_type": self.epc_record.property_type, + "built_form": self.epc_record.built_form, + "local_authority": self.epc_record.local_authority_label, + "constituency": self.epc_record.constituency_label, "number_of_rooms": self.number_of_rooms, "year_built": self.year_built, - "tenure": self.data["tenure"], + "tenure": self.epc_record.tenure, "current_epc_rating": current_epc_rating, "current_sap_points": current_sap_rating, "current_valuation": current_valuation, - "original_sap_points": self.data["current-energy-efficiency"], + "original_sap_points": self.epc_record.current_energy_efficiency, "is_sap_points_adjusted_for_installed_measures": needs_rebaselining, "installed_measures_sap_point_adjustment": rebaselining_sap, } @@ -841,7 +804,7 @@ class Property: raise ValueError("Current energy bill has not been set") # IF we have a SAP05 overwrite, we pull out the relevant information - sap_05_overwritten = self.data.get("sap-05-overwritten", False) + sap_05_overwritten = self.epc_record.sap_05_overwritten sap_05_score, sap_05_epc_rating = None, None if sap_05_overwritten: @@ -854,7 +817,7 @@ class Property: sap_05_score = int(newest_old_epc["current-energy-efficiency"]) sap_05_epc_rating = newest_old_epc["current-energy-rating"] - lodgement_date = self.data["lodgement-date"] + lodgement_date = self.epc_record.lodgement_date # We check if the lodgement date is more than 10 years old is_expired = self.epc_is_expired @@ -876,42 +839,42 @@ class Property: "portfolio_id": portfolio_id, "lodgement_date": datetime.fromisoformat(lodgement_date), "is_expired": is_expired, - "full_address": self.data["address"], - "total_floor_area": float(self.data["total-floor-area"]), + "full_address": self.epc_record.address, + "total_floor_area": float(self.epc_record.total_floor_area), "walls": self.walls["clean_description"], - "walls_rating": self._prepare_rating_field(self.data["walls-energy-eff"]), + "walls_rating": self._prepare_rating_field(self.epc_record.walls_energy_eff), "roof": self.roof["clean_description"], - "roof_rating": self._prepare_rating_field(self.data["roof-energy-eff"]), + "roof_rating": self._prepare_rating_field(self.epc_record.roof_energy_eff), "floor": self.floor["clean_description"], - "floor_rating": self._prepare_rating_field(self.data["floor-energy-eff"]), + "floor_rating": self._prepare_rating_field(self.epc_record.floor_energy_eff), "windows": self.windows["clean_description"], - "windows_rating": self._prepare_rating_field(self.data["windows-energy-eff"]), + "windows_rating": self._prepare_rating_field(self.epc_record.windows_energy_eff), "heating": self.main_heating["clean_description"], - "heating_rating": self._prepare_rating_field(self.data["mainheat-energy-eff"]), + "heating_rating": self._prepare_rating_field(self.epc_record.mainheat_energy_eff), "heating_controls": self.main_heating_controls["clean_description"], - "heating_controls_rating": self._prepare_rating_field(self.data["mainheatc-energy-eff"]), + "heating_controls_rating": self._prepare_rating_field(self.epc_record.mainheatc_energy_eff), "hot_water": self.hotwater["clean_description"], - "hot_water_rating": self._prepare_rating_field(self.data["hot-water-energy-eff"]), + "hot_water_rating": self._prepare_rating_field(self.epc_record.hot_water_energy_eff), "lighting": self.lighting["clean_description"], - "lighting_rating": self._prepare_rating_field(self.data["lighting-energy-eff"]), + "lighting_rating": self._prepare_rating_field(self.epc_record.lighting_energy_eff), "mainfuel": self.main_fuel["clean_description"], - "ventilation": self.ventilation["ventilation"], - "solar_pv": self.solar_pv["solar_pv"], - "solar_hot_water": self.solar_hot_water["solar_hot_water_boolean"], - "wind_turbine": self.wind_turbine["wind_turbine"], + "ventilation": self.epc_record.mechanical_ventilation, + "solar_pv": self.epc_record.photo_supply, + "solar_hot_water": self.epc_record.solar_water_heating_flag_bool, + "wind_turbine": self.epc_record.wind_turbine_count, "floor_height": self.floor_height, - "heat_loss_corridor": self.heat_loss_corridor["heat_loss_corridor_boolean"], - "unheated_corridor_length": self.heat_loss_corridor["length"], - "number_of_open_fireplaces": self.number_of_open_fireplaces["number_of_open_fireplaces"], - "number_of_extensions": self.number_of_extensions["number_of_extensions"], - "number_of_storeys": self.number_of_storeys["number_of_storeys"], + "heat_loss_corridor": self.epc_record.heat_loss_corridor_bool, + "unheated_corridor_length": self.epc_record.unheated_corridor_length, + "number_of_open_fireplaces": self.epc_record.number_open_fireplaces, + "number_of_extensions": self.epc_record.extension_count, + "number_of_storeys": self.epc_record.flat_storey_count, "mains_gas": self.mains_gas, - "energy_tariff": self.data["energy-tariff"], + "energy_tariff": self.epc_record.energy_tariff, "primary_energy_consumption": primary_energy_consumption, "co2_emissions": co2_emissions, "current_energy_demand": current_kwh_demand, # This is kwh - naming is confusing "current_energy_demand_heating_hotwater": current_kwh_heating_hotwater, # This is kwh - "estimated": self.data.get("estimated", False), + "estimated": self.epc_record.estimated, # We indicate if we've overwritten a SAP 05 EPC "sap_05_overwritten": sap_05_overwritten, "sap_05_score": sap_05_score, @@ -974,7 +937,7 @@ class Property: """ result = property_dimensions[ - (property_dimensions["PROPERTY_TYPE"] == self.data["property-type"]) + (property_dimensions["PROPERTY_TYPE"] == self.epc_record.property_type) ] if ( @@ -986,10 +949,10 @@ class Property: ] if ( - self.data["built-form"] not in self.DATA_ANOMALY_MATCHES - and self.data["built-form"] in result["BUILT_FORM"] + self.epc_record.built_form not in self.DATA_ANOMALY_MATCHES + and self.epc_record.built_form in result["BUILT_FORM"] ): - result = result[(result["BUILT_FORM"] == self.data["built-form"])] + result = result[(result["BUILT_FORM"] == self.epc_record.built_form)] return result[ ["NUMBER_HABITABLE_ROOMS", "TOTAL_FLOOR_AREA", "FLOOR_HEIGHT"] @@ -1032,7 +995,7 @@ class Property: num_floors=self.number_of_floors, floor_height=self.floor_height, perimeter=self.perimeter, - built_form=self.data["built-form"], + built_form=self.epc_record.built_form, ) if self.insulation_floor_area is None: @@ -1051,15 +1014,15 @@ class Property: def set_floor_level(self): self.floor_level = ( - FLOOR_LEVEL_MAP[self.data["floor-level"]] - if self.data["floor-level"] not in self.DATA_ANOMALY_MATCHES - and self.data["floor-level"] is not None + FLOOR_LEVEL_MAP[self.epc_record.floor_level] + if self.epc_record.floor_level not in self.DATA_ANOMALY_MATCHES + and self.epc_record.floor_level is not None else None ) if self.floor_level is None: - if self.data["property-type"] != "Flat": + if self.epc_record.property_type != "Flat": return if self.floor["another_property_below"]: @@ -1119,21 +1082,6 @@ class Property: ) self.floor_type = "suspended" - @staticmethod - def _extract_component( - component_data, component_rename_cols, component_drop_cols, rename_prefix=None - ): - for k in component_rename_cols: - component_data[f"{rename_prefix}_{k}"] = component_data.get(k) - - component_data = { - k: v - for k, v in component_data.items() - if k not in component_drop_cols + component_rename_cols - } - - return component_data - def set_windows_count(self): """ Using the estimate_windows function, this method will set the number of windows in the property @@ -1145,8 +1093,8 @@ class Property: self.number_of_windows = int(condition_data["number_of_windows"]) \ if condition_data.get("number_of_windows") is not None \ else estimate_windows( - property_type=self.data["property-type"], - built_form=self.data["built-form"], + property_type=self.epc_record.property_type, + built_form=self.epc_record.built_form, construction_age_band=self.construction_age_band, floor_area=self.floor_area, number_habitable_rooms=self.number_of_rooms, @@ -1166,14 +1114,14 @@ class Property: # If we have a house over a floor area threshold, we recommend an ASHP if ( - self.data["property-type"] in ["House", "Bungalow"] and + self.epc_record.property_type in ["House", "Bungalow"] and self.floor_area > assumptions.ASHP_FLOOR_AREA_THRESHOLD ): return True suitable_property_type = ( - self.data["property-type"] in ["House", "Bungalow"] and - self.data["built-form"] not in ["Enclosed Mid-Terrace", "Enclosed End-Terrace"] + self.epc_record.property_type in ["House", "Bungalow"] and + self.epc_record.built_form not in ["Enclosed Mid-Terrace", "Enclosed End-Terrace"] ) has_air_source_heat_pump = self.main_heating["has_air_source_heat_pump"] @@ -1195,12 +1143,12 @@ class Property: # may be installed such that they are not visible from the street return False - if (self.data["property-type"] in ["House", "Bungalow"]) and ( + if (self.epc_record.property_type in ["House", "Bungalow"]) and ( not pd.isnull(self.roof["thermal_transmittance"]) ): return True - is_valid_property_type = self.data["property-type"] in ["House", "Bungalow", "Maisonette"] + is_valid_property_type = self.epc_record.property_type in ["House", "Bungalow", "Maisonette"] is_valid_roof_type = ( self.roof["is_flat"] or self.roof["is_pitched"] or self.roof["is_roof_room"] ) @@ -1213,7 +1161,7 @@ class Property: "already has solar pv", "roof too small", "no roof" ] else: - has_no_existing_solar_pv = self.data["photo-supply"] in [ + has_no_existing_solar_pv = self.epc_record.photo_supply in [ None, 0, self.DATA_ANOMALY_MATCHES ] @@ -1285,12 +1233,10 @@ class Property: def identify_ventilation(self): - ventilation_descriptions = [ + return self.epc_record.mechanical_ventilation in { 'mechanical, extract only', 'mechanical, supply and extract' - ] - - return self.epc_record.get("mechanical-ventilation") in ventilation_descriptions + } @property def epc_is_expired(self) -> bool: @@ -1299,7 +1245,7 @@ class Property: valid for 10 years. :return: boolean indicating whether the EPC is expired """ - lodgement_date = self.epc_record.get("lodgement-date") + lodgement_date = self.epc_record.lodgement_date return (datetime.now() - pd.to_datetime(lodgement_date)) > timedelta(days=3650) @property @@ -1308,4 +1254,4 @@ class Property: This property indicates that the EPC is estimated, based on the presence of the "estimated" flag in the data :return: boolean indicating whether the EPC is estimated """ - return self.epc_record.get("estimated") + return self.epc_record.estimated diff --git a/backend/engine/engine.py b/backend/engine/engine.py index 339a4236..4f698e18 100644 --- a/backend/engine/engine.py +++ b/backend/engine/engine.py @@ -813,19 +813,11 @@ async def model_engine(body: PlanTriggerRequest): # 1) EPC expired # 2) Missing EPC # 3) Materially different information from landlord vs EPC - # make the landlord remapping dictionary - - needs_rebaselining = p.epc_is_expired | p.epc_is_estimated | (len(differences) > 0) - - p.epc_record.update(differences) + # make the landlord remapping dictionar + needs_rebaselining = p.epc_is_expired | p.epc_is_estimated | (len(p.epc_record.landlord_differences) > 0) # Need to adjust p.data and p.epc_record.df? if needs_rebaselining: - if len(differences): - # Insert into prepared_epc - for k, v in differences.items(): - p.epc_record.prepared_epc[k] = v - p.create_base_difference_epc_record(cleaned_lookup=cleaned) scoring_data = p.base_difference_record.df.copy() rebaselining_scoring_data.append(scoring_data) diff --git a/etl/epc/Record.py b/etl/epc/Record.py index 1ed0fc41..04333b57 100644 --- a/etl/epc/Record.py +++ b/etl/epc/Record.py @@ -308,6 +308,7 @@ class EPCRecord: # ------------------------------------------------------------------ # Indicates if the EPC record has been predicted. By default, false estimated: Optional[bool] = False + sap_05_overwritten: Optional[bool] = False # ------------------------------------------------------------------ # MODEL FLAGS