def _split_s1s2s3( self, target: IDataProviderTarget ) -> Tuple[IDataProviderTarget, Optional[IDataProviderTarget]]: """ If there is a s1s2s3 scope, split it into two targets with s1s2 and s3 :param target: The input target :return The split targets or the original target and None """ if target.scope == EScope.S1S2S3: s1s2, s3 = target.copy(), None if (not pd.isnull(target.base_year_ghg_s1) and not pd.isnull(target.base_year_ghg_s1)) or \ target.coverage_s1 == target.coverage_s2: s1s2.scope = EScope.S1S2 if not pd.isnull(target.base_year_ghg_s1) and not pd.isnull(target.base_year_ghg_s2) and \ target.base_year_ghg_s1 + target.base_year_ghg_s2 != 0: coverage_percentage = (s1s2.coverage_s1 * s1s2.base_year_ghg_s1 + s1s2.coverage_s2 * s1s2.base_year_ghg_s2) / \ (s1s2.base_year_ghg_s1 + s1s2.base_year_ghg_s2) s1s2.coverage_s1 = coverage_percentage s1s2.coverage_s2 = coverage_percentage if not pd.isnull(target.coverage_s3): s3 = target.copy() s3.scope = EScope.S3 return s1s2, s3 else: return target, None
def _boundary_coverage(self, target: IDataProviderTarget) -> IDataProviderTarget: """ Test on boundary coverage: Option 1: minimal coverage threshold For S1+S2 targets: coverage% must be at or above 95%, for S3 targets coverage must be above 67% Option 2: weighted coverage Thresholds are still 95% and 67%, target is always valid. Below threshold ambition is scaled.* New target ambition = input target ambition * coverage *either here or in tem score module Option 3: default coverage Target is always valid, % uncovered is given default score in temperature score module. :param target: The input target :return: The original target with a weighted reduction ambition, if so required """ if target.scope == EScope.S1S2: if target.coverage_s1 < 0.95: target.reduction_ambition = target.reduction_ambition * target.coverage_s1 elif target.scope == EScope.S3: if target.coverage_s3 < 0.67: target.reduction_ambition = target.reduction_ambition * target.coverage_s3 return target
def _combine_s1_s2(self, target: IDataProviderTarget): """ Check if there is an S2 target that matches this target exactly (if this is a S1 target) and combine them into one target. :param target: The input target :return: The combined target (or the original if no combining was required) """ if target.scope == EScope.S1 and not pd.isnull( target.base_year_ghg_s1): matches = [ t for t in self.s2_targets if t.company_id == target.company_id and t.base_year == target. base_year and t.start_year == target.start_year and t.end_year == target.end_year and t.target_type == target.target_type and t.intensity_metric == target.intensity_metric ] if len(matches) > 0: matches.sort(key=lambda t: t.coverage_s2, reverse=True) s2 = matches[0] combined_coverage = (target.coverage_s1 * target.base_year_ghg_s1 + s2.coverage_s2 * s2.base_year_ghg_s2) / \ (target.base_year_ghg_s1 + s2.base_year_ghg_s2) target.reduction_ambition = target.reduction_ambition * target.coverage_s1 * target.base_year_ghg_s1 + \ s2.reduction_ambition * s2.coverage_s1 * s2.base_year_ghg_s2 / \ (target.base_year_ghg_s1 + s2.base_year_ghg_s1) / combined_coverage target.coverage_s1 = combined_coverage target.coverage_s2 = combined_coverage # We don't need to delete the S2 target as it'll be definition have a lower coverage than the combined # target, therefore it won't be picked for our 9-box grid return target
def _time_frame(self, target: IDataProviderTarget) -> IDataProviderTarget: """ Time frame is forward looking: target year - current year. Less than 5y = short, between 5 and 15 is mid, 15 to 30 is long :param target: The input target :return: The original target with the time_frame field filled out (if so required) """ now = datetime.datetime.now() time_frame = target.end_year - now.year if time_frame <= 4: target.time_frame = ETimeFrames.SHORT elif time_frame <= 15: target.time_frame = ETimeFrames.MID elif time_frame <= 30: target.time_frame = ETimeFrames.LONG return target
def get_targets(self, company_ids: list) -> List[IDataProviderTarget]: """ Get all relevant targets for a list of company ids (ISIN). This method should return a list of IDataProviderTarget instances. :param company_ids: A list of company IDs (ISINs) :return: A list containing the targets """ targets = self.data_targets.to_dict(orient="records") model_targets: List[IDataProviderTarget] = [ IDataProviderTarget.parse_obj(target) for target in targets ] model_targets = [ target for target in model_targets if target.company_id in company_ids ] return model_targets
def _target_df_to_model(self, df_targets): """ transforms target Dataframe into list of IDataProviderTarget instances :param df_targets: pandas Dataframe with targets :return: A list containing the targets """ logger = logging.getLogger(__name__) targets = df_targets.to_dict(orient="records") model_targets: List[IDataProviderTarget] = [] for target in targets: try: model_targets.append(IDataProviderTarget.parse_obj(target)) except ValidationError as e: logger.warning( "(one of) the target(s) of company %s is invalid and will be skipped" % target[self.c.COMPANY_NAME]) pass return model_targets
def setUp(self): company_id = "BaseCompany" self.BASE_COMP_SCORE = 0.43 self.company_base = IDataProviderCompany(company_name=company_id, company_id=company_id, ghg_s1s2=100, ghg_s3=0, company_revenue=100, company_market_cap=100, company_enterprise_value=100, company_total_assets=100, company_cash_equivalents=100, isic='A12') # define targets self.target_base = IDataProviderTarget( company_id=company_id, target_type="abs", scope=EScope.S1S2, coverage_s1=0.95, coverage_s2=0.95, coverage_s3=0, reduction_ambition=0.8, base_year=2019, base_year_ghg_s1=100, base_year_ghg_s2=0, base_year_ghg_s3=0, end_year=2030, ) # pf self.pf_base = PortfolioCompany( company_name=company_id, company_id=company_id, investment_value=100, company_isin=company_id, )
def validate(self, target: IDataProviderTarget) -> bool: """ Validate a target, meaning it should: * Have a valid type * Not be finished * A valid end year :param target: The target to validate :return: True if it's a valid target, false if it isn't """ # Only absolute targets or intensity targets with a valid intensity metric are allowed. target_type = "abs" in target.target_type.lower() or \ ("int" in target.target_type.lower() and target.intensity_metric is not None and target.intensity_metric.lower() != "other") # The target should not have achieved it's reduction yet. target_process = pd.isnull(target.achieved_reduction) or \ target.achieved_reduction is None or \ target.achieved_reduction < 1 # The end year should be greater than the start year. if target.start_year is None or pd.isnull(target.start_year): target.start_year = target.base_year target_end_year = target.end_year > target.start_year # Delete all S1 or S2 targets we can't combine s1 = target.scope != EScope.S1 or ( not pd.isnull(target.coverage_s1) and not pd.isnull(target.base_year_ghg_s1) and not pd.isnull(target.base_year_ghg_s2)) s2 = target.scope != EScope.S2 or ( not pd.isnull(target.coverage_s2) and not pd.isnull(target.base_year_ghg_s1) and not pd.isnull(target.base_year_ghg_s2)) return target_type and target_process and target_end_year and s1 and s2
def _convert_s1_s2(self, target: IDataProviderTarget): """ Convert a S1 or S2 target into a S1+S2 target. :param target: The input target :return: The converted target (or the original if no conversion was required) """ # In both cases the base_year_ghg s1 + s2 should not be zero if target.base_year_ghg_s1 + target.base_year_ghg_s2 != 0: if target.scope == EScope.S1: coverage = target.coverage_s1 * target.base_year_ghg_s1 / ( target.base_year_ghg_s1 + target.base_year_ghg_s2) target.coverage_s1 = coverage target.coverage_s2 = coverage target.scope = EScope.S1S2 elif target.scope == EScope.S2: coverage = target.coverage_s2 * target.base_year_ghg_s2 / ( target.base_year_ghg_s1 + target.base_year_ghg_s2) target.coverage_s1 = coverage target.coverage_s2 = coverage target.scope = EScope.S1S2 return target