sbti-finance-tool 1.0.8__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sbti-finance-tool might be problematic. Click here for more details.

SBTi/configs.py CHANGED
@@ -162,7 +162,9 @@ class PortfolioCoverageTVPConfig(PortfolioAggregationConfig):
162
162
  "current-Companies-Taking-Action.xlsx",
163
163
  )
164
164
  # Temporary URL until the SBTi website is updated
165
- CTA_FILE_URL = "https://sciencebasedtargets.org/download/target-dashboard"
165
+ CTA_FILE_URL = "https://sciencebasedtargets.org/resources/files/companies-excel.xlsx" # Default to per-company
166
+ CTA_FILE_URL_PER_COMPANY = "https://files.sciencebasedtargets.org/production/files/companies-excel.xlsx"
167
+ CTA_FILE_URL_PER_TARGET = "https://sciencebasedtargets.org/resources/files/targets-excel.xlsx"
166
168
  OUTPUT_TARGET_STATUS = "sbti_target_status"
167
169
  OUTPUT_WEIGHTED_TARGET_STATUS = "weighted_sbti_target_status"
168
170
  VALUE_TARGET_NO = "No target"
@@ -181,4 +183,5 @@ class PortfolioCoverageTVPConfig(PortfolioAggregationConfig):
181
183
  COL_COMPANY_ISIN = "ISIN"
182
184
  COL_COMPANY_LEI = "LEI"
183
185
  COL_ACTION = "Action"
184
- COL_TARGET = "Target"
186
+ COL_TARGET = "Target"
187
+ COL_DATE_PUBLISHED = "Date Published"
SBTi/data/sbti.py CHANGED
@@ -1,110 +1,332 @@
1
- from typing import List, Type
1
+ from typing import List, Type, Dict, Tuple, Optional
2
2
  import requests
3
3
  import pandas as pd
4
4
  import warnings
5
-
5
+ import datetime
6
+ import re
6
7
 
7
8
  from SBTi.configs import PortfolioCoverageTVPConfig
8
- from SBTi.interfaces import IDataProviderCompany
9
+ from SBTi.interfaces import IDataProviderCompany, IDataProviderTarget, EScope, ETimeFrames
9
10
 
10
11
 
11
12
  class SBTi:
12
13
  """
13
14
  Data provider skeleton for SBTi. This class only provides the sbti_validated field for existing companies.
15
+ Updated to DEFAULT to per-company format for TR Testing consistency.
16
+ Enhanced to extract detailed target information when available.
14
17
  """
15
18
 
16
19
  def __init__(
17
20
  self, config: Type[PortfolioCoverageTVPConfig] = PortfolioCoverageTVPConfig
18
21
  ):
19
22
  self.c = config
20
- # Fetch CTA file from SBTi website
21
- resp = requests.get(self.c.CTA_FILE_URL)
22
- # Write CTA file to disk
23
- with open(self.c.FILE_TARGETS, 'wb') as output:
24
- output.write(resp.content)
25
- print(f'Status code from fetching the CTA file: {resp.status_code}, 200 = OK')
23
+
24
+ # DEFAULT TO PER-COMPANY FORMAT for consistency with TR Testing baseline
25
+ # Override the config to ensure per-company format is used
26
+ original_url = self.c.CTA_FILE_URL
27
+ self.c.CTA_FILE_URL = "https://files.sciencebasedtargets.org/production/files/companies-excel.xlsx"
28
+
29
+
30
+ fallback_err_log_statement = 'Will read older file from this package version'
31
+ try:
32
+ # Fetch CTA file from SBTi website
33
+ resp = requests.get(self.c.CTA_FILE_URL)
34
+
35
+ # If status code == 200 then write CTA file to disk
36
+ if resp.ok:
37
+ with open(self.c.FILE_TARGETS, 'wb') as output:
38
+ output.write(resp.content)
39
+ print(f'Status code from fetching the CTA file: {resp.status_code}, 200 = OK')
40
+ else:
41
+ print(f'Non-200 status code when fetching the CTA file from the SBTi website: {resp.status_code}')
42
+ print(fallback_err_log_statement)
43
+
44
+ except requests.exceptions.RequestException as e:
45
+ print(f'Exception when fetching the CTA file from the SBTi website: {e}')
46
+ print(fallback_err_log_statement)
47
+
26
48
  # Read CTA file into pandas dataframe
27
- # Suppress warning about openpyxl - check if this is still needed in the released version.
49
+ # Suppress warning about openpyxl
28
50
  warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')
29
51
  self.targets = pd.read_excel(self.c.FILE_TARGETS)
30
52
 
53
+ # Detect and convert column format if needed
54
+ self.targets = self._ensure_compatible_format(self.targets)
55
+
56
+ print(f"CTA format: {getattr(self, 'format_type', 'unknown')} | Companies: {len(self.targets)}")
57
+
58
+ def _detect_format(self, df):
59
+ """
60
+ Detect the format of the CTA file:
61
+ - 'old': Original format with Title Case columns
62
+ - 'new_company': New per-company format (one row per company) - PREFERRED
63
+ - 'new_target': New per-target format (multiple rows per company)
64
+ """
65
+ # Check for key columns to determine format
66
+ if 'Company Name' in df.columns:
67
+ return 'old'
68
+ elif 'company_name' in df.columns:
69
+ # Distinguish between per-company and per-target formats
70
+ if 'near_term_status' in df.columns:
71
+ return 'new_company' # PREFERRED FORMAT
72
+ elif 'target_wording' in df.columns or 'row_entry_id' in df.columns:
73
+ return 'new_target'
74
+ else:
75
+ # Default to per-company if we can't determine
76
+ return 'new_company'
77
+ else:
78
+ raise ValueError("Unrecognized CTA file format")
31
79
 
80
+ def _ensure_compatible_format(self, df):
81
+ """
82
+ Convert CTA file to the format expected by the configs
83
+ OPTIMIZED FOR PER-COMPANY FORMAT
84
+ """
85
+ format_type = self._detect_format(df)
86
+
87
+ if format_type in ['new_target', 'new_company']:
88
+ # Map new format columns to old format for backward compatibility
89
+ column_mapping = {
90
+ 'company_name': self.c.COL_COMPANY_NAME,
91
+ 'isin': self.c.COL_COMPANY_ISIN,
92
+ 'lei': self.c.COL_COMPANY_LEI,
93
+ 'action': self.c.COL_ACTION,
94
+ 'target': self.c.COL_TARGET,
95
+ 'date_published': self.c.COL_DATE_PUBLISHED
96
+ }
97
+
98
+ # Handle different format types
99
+ if format_type == 'new_company':
100
+ # Map near_term_status to create synthetic Action and Target columns
101
+ if 'near_term_status' in df.columns:
102
+ df['action'] = df['near_term_status'].apply(
103
+ lambda x: 'Target' if x == 'Targets set' else 'Commitment'
104
+ )
105
+ df['target'] = df['near_term_status'].apply(
106
+ lambda x: 'Near-term' if x == 'Targets set' else None
107
+ )
108
+
109
+ # Only rename columns that exist
110
+ rename_dict = {}
111
+ for new_col, old_col in column_mapping.items():
112
+ if new_col in df.columns:
113
+ rename_dict[new_col] = old_col
114
+
115
+ df = df.rename(columns=rename_dict)
116
+
117
+ # Store format type for later use
118
+ self.format_type = format_type
119
+
120
+ # Keep additional columns for potential future use
121
+ if 'sbti_id' in df.columns:
122
+ df['SBTI_ID'] = df['sbti_id']
123
+ if 'target_classification_short' in df.columns:
124
+ df['Target Classification'] = df['target_classification_short']
125
+ if 'scope' in df.columns:
126
+ df['Scope'] = df['scope']
127
+ if 'base_year' in df.columns:
128
+ df['Base Year'] = df['base_year']
129
+ if 'target_year' in df.columns:
130
+ df['Target Year'] = df['target_year']
131
+ else:
132
+ self.format_type = 'old'
133
+
134
+ return df
135
+
32
136
  def filter_cta_file(self, targets):
33
137
  """
34
- Filter the CTA file to create a datafram that has on row per company
138
+ Filter the CTA file to create a dataframe that has one row per company
35
139
  with the columns "Action" and "Target".
36
140
  If Action = Target then only keep the rows where Target = Near-term.
141
+
142
+ Handles all three formats: old, new per-company, and new per-target.
37
143
  """
38
144
 
39
145
  # Create a new dataframe with only the columns "Action" and "Target"
40
146
  # and the columns that are needed for identifying the company
41
- targets = targets[
42
- [
43
- self.c.COL_COMPANY_NAME,
44
- self.c.COL_COMPANY_ISIN,
45
- self.c.COL_COMPANY_LEI,
46
- self.c.COL_ACTION,
47
- self.c.COL_TARGET
48
- ]
147
+ required_cols = [
148
+ self.c.COL_COMPANY_NAME,
149
+ self.c.COL_COMPANY_ISIN,
150
+ self.c.COL_COMPANY_LEI,
151
+ self.c.COL_ACTION,
152
+ self.c.COL_TARGET
49
153
  ]
50
154
 
155
+ # Only select columns that exist
156
+ existing_cols = [col for col in required_cols if col in targets.columns]
157
+ targets_filtered = targets[existing_cols].copy()
158
+
51
159
  # Keep rows where Action = Target and Target = Near-term
52
- df_nt_targets = targets[
53
- (targets[self.c.COL_ACTION] == self.c.VALUE_ACTION_TARGET) &
54
- (targets[self.c.COL_TARGET] == self.c.VALUE_TARGET_SET)]
55
-
56
- # Drop duplicates in the dataframe by waterfall.
57
- # Do company name last due to risk of misspelled names
58
- # First drop duplicates on LEI, then on ISIN, then on company name
59
- df_nt_targets = pd.concat([
60
- df_nt_targets[~df_nt_targets[self.c.COL_COMPANY_LEI].isnull()].drop_duplicates(
61
- subset=self.c.COL_COMPANY_LEI, keep='first'
62
- ),
63
- df_nt_targets[df_nt_targets[self.c.COL_COMPANY_LEI].isnull()]
64
- ])
65
-
66
- df_nt_targets = pd.concat([
67
- df_nt_targets[~df_nt_targets[self.c.COL_COMPANY_ISIN].isnull()].drop_duplicates(
68
- subset=self.c.COL_COMPANY_ISIN, keep='first'
69
- ),
70
- df_nt_targets[df_nt_targets[self.c.COL_COMPANY_ISIN].isnull()]
71
- ])
72
-
73
- df_nt_targets.drop_duplicates(subset=self.c.COL_COMPANY_NAME, inplace=True)
74
-
160
+ df_nt_targets = targets_filtered[
161
+ (targets_filtered[self.c.COL_ACTION] == self.c.VALUE_ACTION_TARGET) &
162
+ (targets_filtered[self.c.COL_TARGET] == self.c.VALUE_TARGET_SET)]
163
+
164
+ # For per-target format, we need to deduplicate at company level
165
+ # since there can be multiple target rows per company
166
+ if hasattr(self, 'format_type') and self.format_type == 'new_target':
167
+ print("Processing per-target format - deduplicating companies...")
168
+
169
+ # Drop duplicates in the dataframe by waterfall approach
170
+ # LEI first, then ISIN, then company name
171
+ identifier_cols = [self.c.COL_COMPANY_LEI, self.c.COL_COMPANY_ISIN, self.c.COL_COMPANY_NAME]
172
+
173
+ for identifier_col in identifier_cols:
174
+ if identifier_col in df_nt_targets.columns:
175
+ # Drop duplicates based on this identifier, keeping first occurrence
176
+ before_count = len(df_nt_targets)
177
+ df_nt_targets = df_nt_targets.drop_duplicates(subset=[identifier_col], keep='first')
178
+ after_count = len(df_nt_targets)
179
+ if before_count != after_count and hasattr(self, 'format_type') and self.format_type == 'new_target':
180
+ print(f" Deduplicated {before_count - after_count} entries using {identifier_col}")
181
+
75
182
  return df_nt_targets
76
-
77
- def get_sbti_targets(
78
- self, companies: List[IDataProviderCompany], id_map: dict
183
+
184
+ def get_company_targets(self, company_name: str = None, isin: str = None, lei: str = None):
185
+ """
186
+ Get all targets for a specific company.
187
+ Only works with per-target format.
188
+
189
+ :param company_name: Company name to search for
190
+ :param isin: ISIN to search for
191
+ :param lei: LEI to search for
192
+ :return: DataFrame with all targets for the company
193
+ """
194
+ if hasattr(self, 'format_type') and self.format_type == 'new_target':
195
+ if lei:
196
+ return self.targets[self.targets['lei'] == lei]
197
+ elif isin:
198
+ return self.targets[self.targets['isin'] == isin]
199
+ elif company_name:
200
+ return self.targets[self.targets[self.c.COL_COMPANY_NAME] == company_name]
201
+ return pd.DataFrame() # Empty dataframe if not per-target format
202
+
203
+ def get_companies(
204
+ self, companies: List[IDataProviderCompany], id_map: Dict[str, Tuple[str, str]]
79
205
  ) -> List[IDataProviderCompany]:
80
206
  """
81
- Check for each company if they have an SBTi validated target, first using the company LEI,
82
- if available, and then using the ISIN.
207
+ Get company data from SBTi database and add sbti_validated field.
83
208
 
84
209
  :param companies: A list of IDataProviderCompany instances
85
210
  :param id_map: A map from company id to a tuple of (ISIN, LEI)
86
211
  :return: A list of IDataProviderCompany instances, supplemented with the SBTi information
87
212
  """
88
- # Filter out information about targets
213
+ # Filter targets for validation check
214
+ filtered_targets = self.filter_cta_file(self.targets)
215
+
216
+ # Track matching statistics for debugging
217
+ matched_lei = matched_isin = matched_name = 0
218
+
219
+ for company in companies:
220
+ isin, lei = id_map.get(company.company_id, (None, None))
221
+
222
+ # Skip if no identifiers
223
+ if not isin and not lei and not company.company_name:
224
+ continue
225
+
226
+ # Check lei and length of lei to avoid zeros
227
+ if lei and not lei.lower() == 'nan' and len(str(lei)) > 3:
228
+ targets = filtered_targets[
229
+ filtered_targets[self.c.COL_COMPANY_LEI] == lei
230
+ ]
231
+ if len(targets) > 0:
232
+ company.sbti_validated = True
233
+ matched_lei += 1
234
+ continue
235
+
236
+ if isin and not isin.lower() == 'nan':
237
+ targets = filtered_targets[
238
+ filtered_targets[self.c.COL_COMPANY_ISIN] == isin
239
+ ]
240
+ if len(targets) > 0:
241
+ company.sbti_validated = True
242
+ matched_isin += 1
243
+ continue
244
+
245
+ # Try company name matching as fallback
246
+ if company.company_name:
247
+ targets = filtered_targets[
248
+ filtered_targets[self.c.COL_COMPANY_NAME].str.lower() == company.company_name.lower()
249
+ ]
250
+ if len(targets) > 0:
251
+ company.sbti_validated = True
252
+ matched_name += 1
253
+ continue
254
+
255
+ # No match found
256
+ company.sbti_validated = False
257
+
258
+ total_matched = matched_lei + matched_isin + matched_name
259
+ print(f"SBTi matches: {total_matched}/{len(companies)} (LEI: {matched_lei}, ISIN: {matched_isin}, Name: {matched_name})")
260
+
261
+ return companies
262
+
263
+ def get_sbti_targets(
264
+ self, companies: List[IDataProviderCompany], id_map: Dict[str, Tuple[str, str]]
265
+ ) -> Tuple[List[IDataProviderCompany], Dict[str, List[IDataProviderTarget]]]:
266
+ """
267
+ Enhanced version that returns both validation status AND target details when available.
268
+
269
+ :param companies: A list of IDataProviderCompany instances
270
+ :param id_map: A map from company id to a tuple of (ISIN, LEI)
271
+ :return: A tuple of:
272
+ - List of IDataProviderCompany instances, supplemented with the SBTi information
273
+ - Dictionary mapping company_id to list of IDataProviderTarget instances
274
+ """
275
+ # Store original unfiltered targets for detailed extraction
276
+ original_targets = self.targets.copy()
277
+
278
+ # Filter out information about targets for validation check
89
279
  self.targets = self.filter_cta_file(self.targets)
280
+
281
+ # Dictionary to store detailed target data
282
+ sbti_target_data = {}
90
283
 
91
284
  for company in companies:
92
- isin, lei = id_map.get(company.company_id)
285
+ isin, lei = id_map.get(company.company_id, (None, None))
286
+
287
+ # Skip if no identifiers
288
+ if not isin and not lei:
289
+ continue
290
+
93
291
  # Check lei and length of lei to avoid zeros
94
- if not lei.lower() == 'nan' and len(lei) > 3:
292
+ if lei and not lei.lower() == 'nan' and len(str(lei)) > 3:
95
293
  targets = self.targets[
96
294
  self.targets[self.c.COL_COMPANY_LEI] == lei
97
295
  ]
98
- elif not isin.lower() == 'nan':
296
+ # Get all targets for detailed extraction
297
+ if hasattr(self, 'format_type') and self.format_type == 'new_target':
298
+ all_company_targets = original_targets[
299
+ original_targets[self.c.COL_COMPANY_LEI if self.c.COL_COMPANY_LEI in original_targets.columns else 'lei'] == lei
300
+ ]
301
+ elif isin and not isin.lower() == 'nan':
99
302
  targets = self.targets[
100
303
  self.targets[self.c.COL_COMPANY_ISIN] == isin
101
304
  ]
305
+ # Get all targets for detailed extraction
306
+ if hasattr(self, 'format_type') and self.format_type == 'new_target':
307
+ all_company_targets = original_targets[
308
+ original_targets[self.c.COL_COMPANY_ISIN if self.c.COL_COMPANY_ISIN in original_targets.columns else 'isin'] == isin
309
+ ]
102
310
  else:
103
311
  continue
312
+
104
313
  if len(targets) > 0:
105
- company.sbti_validated = (
106
- self.c.VALUE_TARGET_SET in targets[self.c.COL_TARGET].values
107
- )
108
- return companies
314
+ company.sbti_validated = True
315
+
316
+ # Extract detailed target information if available
317
+ if hasattr(self, 'format_type') and self.format_type == 'new_target' and len(all_company_targets) > 0:
318
+ targets_list = []
319
+ for _, target_row in all_company_targets.iterrows():
320
+ target_data = {
321
+ 'company_id': company.company_id,
322
+ 'target_type': target_row.get('target_classification_short', 'Unknown'),
323
+ 'scope': target_row.get('scope', 'Unknown'),
324
+ 'base_year': target_row.get('base_year'),
325
+ 'target_year': target_row.get('target_year'),
326
+ }
327
+ targets_list.append(target_data)
328
+ sbti_target_data[company.company_id] = targets_list
329
+ else:
330
+ company.sbti_validated = False
109
331
 
110
-
332
+ return companies, sbti_target_data
SBTi/utils.py CHANGED
@@ -164,24 +164,6 @@ def _make_id_map(df_portfolio: pd.DataFrame) -> dict:
164
164
  }
165
165
 
166
166
 
167
- # def _make_isin_map(df_portfolio: pd.DataFrame) -> dict:
168
- # """
169
- # Create a mapping from company_id to ISIN (required for the SBTi matching).
170
-
171
- # :param df_portfolio: The complete portfolio
172
- # :return: A mapping from company_id to ISIN
173
- # """
174
- # return {
175
- # company_id: company[ColumnsConfig.COMPANY_ISIN]
176
- # for company_id, company in df_portfolio[
177
- # [ColumnsConfig.COMPANY_ID, ColumnsConfig.COMPANY_ISIN]
178
- # ]
179
- # .set_index(ColumnsConfig.COMPANY_ID)
180
- # .to_dict(orient="index")
181
- # .items()
182
- # }
183
-
184
-
185
167
  def dataframe_to_portfolio(df_portfolio: pd.DataFrame) -> List[PortfolioCompany]:
186
168
  """
187
169
  Convert a data frame to a list of portfolio company objects.
@@ -199,28 +181,77 @@ def dataframe_to_portfolio(df_portfolio: pd.DataFrame) -> List[PortfolioCompany]
199
181
  ]
200
182
 
201
183
 
184
+ def merge_target_data(
185
+ provider_targets: List[IDataProviderTarget],
186
+ sbti_targets: Dict[str, List[IDataProviderTarget]]
187
+ ) -> List[IDataProviderTarget]:
188
+ """
189
+ Merge targets from data providers with SBTi targets, preferring SBTi data for validated companies.
190
+
191
+ :param provider_targets: List of targets from data providers
192
+ :param sbti_targets: Dictionary mapping company_id to list of SBTi targets
193
+ :return: Merged list of targets
194
+ """
195
+ # Create lookup of provider targets by company
196
+ provider_by_company = {}
197
+ for target in provider_targets:
198
+ if target.company_id not in provider_by_company:
199
+ provider_by_company[target.company_id] = []
200
+ provider_by_company[target.company_id].append(target)
201
+
202
+ # Replace with SBTi targets where available
203
+ for company_id, sbti_company_targets in sbti_targets.items():
204
+ if sbti_company_targets: # Only replace if we have valid SBTi targets
205
+ provider_by_company[company_id] = sbti_company_targets
206
+ logging.getLogger(__name__).info(
207
+ f"Using {len(sbti_company_targets)} SBTi targets for company {company_id}"
208
+ )
209
+
210
+ # Flatten back to list
211
+ merged_targets = []
212
+ for company_targets in provider_by_company.values():
213
+ merged_targets.extend(company_targets)
214
+
215
+ return merged_targets
216
+
217
+
202
218
  def get_data(
203
219
  data_providers: List[data.DataProvider], portfolio: List[PortfolioCompany]
204
220
  ) -> pd.DataFrame:
205
221
  """
206
222
  Get the required data from the data provider(s), validate the targets and return a 9-box grid for each company.
223
+ Enhanced to use SBTi authoritative target data when available.
207
224
 
208
225
  :param data_providers: A list of DataProvider instances
209
226
  :param portfolio: A list of PortfolioCompany models
210
227
  :return: A data frame containing the relevant company-target data
211
228
  """
229
+ logger = logging.getLogger(__name__)
230
+
212
231
  df_portfolio = pd.DataFrame.from_records(
213
232
  [_flatten_user_fields(c) for c in portfolio]
214
233
  )
215
234
  company_data = get_company_data(data_providers, df_portfolio["company_id"].tolist())
216
235
  target_data = get_targets(data_providers, df_portfolio["company_id"].tolist())
217
236
 
237
+ # Supplement the company data with the SBTi target status and get detailed targets
238
+ sbti = SBTi()
239
+ company_data, sbti_targets = sbti.get_sbti_targets(company_data, _make_id_map(df_portfolio))
240
+
241
+ # Log information about SBTi targets found
242
+ if sbti_targets:
243
+ logger.info(f"Found SBTi targets for {len(sbti_targets)} companies")
244
+ for company_id, targets in sbti_targets.items():
245
+ logger.info(f"Company {company_id}: {len(targets)} SBTi targets")
246
+
247
+ # Merge SBTi targets with provider targets
248
+ if sbti_targets:
249
+ target_data = merge_target_data(target_data, sbti_targets)
250
+ logger.info(f"Total targets after merging: {len(target_data)}")
251
+
218
252
  if len(target_data) == 0:
219
253
  raise ValueError("No targets found")
220
254
 
221
- # Supplement the company data with the SBTi target status
222
- company_data = SBTi().get_sbti_targets(company_data, _make_id_map(df_portfolio))
223
-
224
255
  # Prepare the data
225
256
  portfolio_data = TargetProtocol().process(target_data, company_data)
226
257
  portfolio_data = pd.merge(
@@ -280,4 +311,4 @@ def calculate(
280
311
  if anonymize:
281
312
  scores = ts.anonymize_data_dump(scores)
282
313
 
283
- return scores, aggregations
314
+ return scores, aggregations
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 World Resources Institute
3
+ Copyright (c) 2025 World Resources Institute; Science Based Targets initiative
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: sbti-finance-tool
3
- Version: 1.0.8
3
+ Version: 1.1.0
4
4
  Summary: This package helps companies and financial institutions to assess the temperature alignment of current targets, commitments, and investment and lending portfolios, and to use this information to develop targets for official validation by the SBTi.'
5
5
  License: MIT
6
6
  Keywords: Climate,SBTi,Finance
7
7
  Author: sbti
8
- Author-email: finance@sciencebasedtargets.org
8
+ Author-email: financialinstitutions@sciencebasedtargets.org
9
9
  Requires-Python: >=3.9,<4.0
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Intended Audience :: Science/Research
@@ -18,7 +18,8 @@ Classifier: Programming Language :: Python :: 3
18
18
  Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
- Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
22
23
  Classifier: Programming Language :: Python :: 3 :: Only
23
24
  Classifier: Programming Language :: Python :: 3.8
24
25
  Classifier: Topic :: Scientific/Engineering
@@ -27,7 +28,7 @@ Requires-Dist: cleo (>=2.0.1,<3.0.0)
27
28
  Requires-Dist: openpyxl (==3.1.2)
28
29
  Requires-Dist: pandas (==1.5.3)
29
30
  Requires-Dist: pydantic (==1.10.7)
30
- Requires-Dist: requests (==2.29.0)
31
+ Requires-Dist: requests (==2.31.0)
31
32
  Requires-Dist: six (==1.16.0)
32
33
  Requires-Dist: xlrd (==2.0.1)
33
34
  Project-URL: Bug Tracker, https://github.com/ScienceBasedTargets/SBTi-finance-tool/issues
@@ -37,7 +38,7 @@ Description-Content-Type: text/markdown
37
38
 
38
39
  > Visit https://sciencebasedtargets.github.io/SBTi-finance-tool/ for the full documentation
39
40
 
40
- > If you have any additional questions or comments send a mail to: finance@sciencebasedtargets.org
41
+ > If you have any additional questions or comments send a mail to: financialinstitutions@sciencebasedtargets.org
41
42
 
42
43
  # SBTi Temperature Alignment tool
43
44
 
@@ -103,7 +104,7 @@ pip install -e .
103
104
  For installing the latest stable release in PyPi run:
104
105
 
105
106
  ```bash
106
- pip install sbti
107
+ pip install sbti-finance-tool
107
108
  ```
108
109
 
109
110
  ## Development
@@ -116,6 +117,15 @@ poetry install
116
117
 
117
118
  This will create a virtual environment inside the project folder under `.venv`.
118
119
 
120
+ ### SBTi Companies Taking Action (CTA) Data
121
+
122
+ The tool supports multiple formats of the SBTi CTA file:
123
+ - **Per-company format** (default, recommended): One row per company with aggregated target status
124
+ - **Per-target format**: Multiple rows per company with detailed target information
125
+ - **Legacy format**: Original Title Case column format
126
+
127
+ The tool automatically detects and handles all formats, defaulting to the per-company format for consistency.
128
+
119
129
  ### Testing
120
130
 
121
131
  Each class should be unit tested. The unit tests are written using the Nose2 framework.
@@ -1,6 +1,6 @@
1
1
  SBTi/.DS_Store,sha256=W31j55Akn120dS6ubbWGqo8fdXI3MJozXV18EMNoiSw,6148
2
2
  SBTi/__init__.py,sha256=K3V0UVvqCRkD7CIClEMjiSegTGpVW1NUGNrG0vWT8VM,327
3
- SBTi/configs.py,sha256=ZOr809l716jLWaamvtS-mqwK4zbtOGbPiwg-1Pr8Wgw,6782
3
+ SBTi/configs.py,sha256=ajIxuub2SxQxOG6FdQ7fjcHsrxZRr6I09KO2PDCklUo,7069
4
4
  SBTi/data/__init__.py,sha256=sOp_BV-nwBZIS4TfseXWxI8RZ3so4tvTphbxRw235VI,320
5
5
  SBTi/data/bloomberg.py,sha256=nTf3JVIgQe-IEUw0yJ6aoBBeSpCDBg2xaU0KjZVQdbo,2852
6
6
  SBTi/data/cdp.py,sha256=7q30fs9DfCNeKkB6rxtmE5qqRfNIihvd4ZaOURyGHmI,1995
@@ -9,10 +9,10 @@ SBTi/data/data_provider.py,sha256=EaMLz1vqFZoip0--Cwb_Qaeivxs1BPQRPbjrXTxlomI,18
9
9
  SBTi/data/excel.py,sha256=WYzT6ZobQY8AgcSyXKZFgG9qAcAV7ZEafZQfOLEFIJQ,3364
10
10
  SBTi/data/iss.py,sha256=sIHwRwV6QGXV-ePkIO_hX8Z2iUOtNZagW2UTPdQ7omU,1995
11
11
  SBTi/data/msci.py,sha256=A7mmUdaeX1z5ywuR7ZApJHMfxov6EpxlharSB8hXgLw,1997
12
- SBTi/data/sbti.py,sha256=ZdlswavdH89I2ekWo1jDz6CFlyIz_EM-WyFzXNuIABM,4306
12
+ SBTi/data/sbti.py,sha256=HukaCKZoF685nzc_uL5U4MiC9-6nhKIgazddjM8GoLk,14919
13
13
  SBTi/data/trucost.py,sha256=H8ugebVUWgmtKTQ3x3l5drs_F3XzGqiiNugiYVu612s,2003
14
14
  SBTi/data/urgentem.py,sha256=eMnVRcCQQUBeERugClmgwvDMMi0bNqfSqblRV0bBBng,1854
15
- SBTi/inputs/current-Companies-Taking-Action-191.xlsx,sha256=bbpf5yaDoiPXvDkPcueSA1cDiKPkW9mGQmQy8Ik1cGE,525783
15
+ SBTi/inputs/current-Companies-Taking-Action.xlsx,sha256=_Q1F1lktsmOrjRNJYbreyUI00_EWtx08nuTZOlVN1E4,1662705
16
16
  SBTi/inputs/regression_model_summary.xlsx,sha256=LSWCWq0Uy13tT-FWVmVEOwogs-NwdfqLSwlthFtr2MA,182447
17
17
  SBTi/inputs/sr15_mapping.xlsx,sha256=XhCue18kkVwgYDLsx04i5HB6cfJMa5UCW76Z74JsuzA,9922
18
18
  SBTi/interfaces.py,sha256=EJbTwiBnNhTqVt8aXs8HH9OB_T_24aSKAypLCC8gPGM,4331
@@ -20,8 +20,8 @@ SBTi/portfolio_aggregation.py,sha256=a9AfWsS8DA5G76Yrkm68TgX5z1XfVnSD7bG19cEMRjA
20
20
  SBTi/portfolio_coverage_tvp.py,sha256=q9v0VEs-4kFusHpXypNtoFPPThC4xng8GiUgyWaFnVA,1731
21
21
  SBTi/target_validation.py,sha256=UA4nFOjudjmeq8pwLJbYJ1x8Rz0KvDH2ZkytF_doHEU,16148
22
22
  SBTi/temperature_score.py,sha256=QqYAUCNp8gO43owcpTzxgKs_XpujI_XECKeMvee2CCg,27801
23
- SBTi/utils.py,sha256=qiYbuR2Worg7JRs_livnqf92TNGzGLW7xx-PvwEifuo,10182
24
- sbti_finance_tool-1.0.8.dist-info/LICENSE,sha256=NKw6I_eIAxDVIhtSsfeMBnECHTVf5UAGGIdAvFDqdnE,1261
25
- sbti_finance_tool-1.0.8.dist-info/METADATA,sha256=VErLnSuRty1YNDpohpFt9aGuhCtoE-JaLa8_4d5xHZM,6125
26
- sbti_finance_tool-1.0.8.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
27
- sbti_finance_tool-1.0.8.dist-info/RECORD,,
23
+ SBTi/utils.py,sha256=TBt5eW_wilxFfQkEC6vmM9z-JswyXTB4zO8cqa2BHSc,11639
24
+ sbti_finance_tool-1.1.0.dist-info/LICENSE,sha256=UEETGN9GWX7lUA5-u4T2r60VSpzCg5W8L1aRhg_NbsM,1295
25
+ sbti_finance_tool-1.1.0.dist-info/METADATA,sha256=qmLmT370KXXzcz9GSXDvJdUvDejwFj_7Tn1QildAy18,6672
26
+ sbti_finance_tool-1.1.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
+ sbti_finance_tool-1.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.5.2
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any