sbti-finance-tool 1.0.9__tar.gz → 1.1.0__tar.gz
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-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/LICENSE +1 -1
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/PKG-INFO +18 -6
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/README.md +10 -1
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/configs.py +5 -2
- sbti_finance_tool-1.1.0/SBTi/data/sbti.py +332 -0
- sbti_finance_tool-1.1.0/SBTi/inputs/current-Companies-Taking-Action.xlsx +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/utils.py +53 -22
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/pyproject.toml +2 -2
- sbti-finance-tool-1.0.9/SBTi/data/sbti.py +0 -115
- sbti-finance-tool-1.0.9/SBTi/inputs/current-Companies-Taking-Action-191.xlsx +0 -0
- sbti-finance-tool-1.0.9/SBTi/inputs/current-Companies-Taking-Action.xlsx +0 -0
- sbti-finance-tool-1.0.9/setup.py +0 -36
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/.DS_Store +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/__init__.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/__init__.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/bloomberg.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/cdp.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/csv.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/data_provider.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/excel.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/iss.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/msci.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/trucost.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/data/urgentem.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/inputs/regression_model_summary.xlsx +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/inputs/sr15_mapping.xlsx +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/interfaces.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/portfolio_aggregation.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/portfolio_coverage_tvp.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/target_validation.py +0 -0
- {sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/temperature_score.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
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
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: sbti-finance-tool
|
|
3
|
-
Version: 1.0
|
|
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:
|
|
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
|
|
@@ -15,10 +15,13 @@ Classifier: Operating System :: Microsoft :: Windows
|
|
|
15
15
|
Classifier: Operating System :: Unix
|
|
16
16
|
Classifier: Programming Language :: Python
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
24
|
Classifier: Programming Language :: Python :: 3.8
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
22
25
|
Classifier: Topic :: Scientific/Engineering
|
|
23
26
|
Classifier: Topic :: Software Development
|
|
24
27
|
Requires-Dist: cleo (>=2.0.1,<3.0.0)
|
|
@@ -35,7 +38,7 @@ Description-Content-Type: text/markdown
|
|
|
35
38
|
|
|
36
39
|
> Visit https://sciencebasedtargets.github.io/SBTi-finance-tool/ for the full documentation
|
|
37
40
|
|
|
38
|
-
> If you have any additional questions or comments send a mail to:
|
|
41
|
+
> If you have any additional questions or comments send a mail to: financialinstitutions@sciencebasedtargets.org
|
|
39
42
|
|
|
40
43
|
# SBTi Temperature Alignment tool
|
|
41
44
|
|
|
@@ -114,6 +117,15 @@ poetry install
|
|
|
114
117
|
|
|
115
118
|
This will create a virtual environment inside the project folder under `.venv`.
|
|
116
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
|
+
|
|
117
129
|
### Testing
|
|
118
130
|
|
|
119
131
|
Each class should be unit tested. The unit tests are written using the Nose2 framework.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
> Visit https://sciencebasedtargets.github.io/SBTi-finance-tool/ for the full documentation
|
|
2
2
|
|
|
3
|
-
> If you have any additional questions or comments send a mail to:
|
|
3
|
+
> If you have any additional questions or comments send a mail to: financialinstitutions@sciencebasedtargets.org
|
|
4
4
|
|
|
5
5
|
# SBTi Temperature Alignment tool
|
|
6
6
|
|
|
@@ -79,6 +79,15 @@ poetry install
|
|
|
79
79
|
|
|
80
80
|
This will create a virtual environment inside the project folder under `.venv`.
|
|
81
81
|
|
|
82
|
+
### SBTi Companies Taking Action (CTA) Data
|
|
83
|
+
|
|
84
|
+
The tool supports multiple formats of the SBTi CTA file:
|
|
85
|
+
- **Per-company format** (default, recommended): One row per company with aggregated target status
|
|
86
|
+
- **Per-target format**: Multiple rows per company with detailed target information
|
|
87
|
+
- **Legacy format**: Original Title Case column format
|
|
88
|
+
|
|
89
|
+
The tool automatically detects and handles all formats, defaulting to the per-company format for consistency.
|
|
90
|
+
|
|
82
91
|
### Testing
|
|
83
92
|
|
|
84
93
|
Each class should be unit tested. The unit tests are written using the Nose2 framework.
|
|
@@ -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/
|
|
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"
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
from typing import List, Type, Dict, Tuple, Optional
|
|
2
|
+
import requests
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import warnings
|
|
5
|
+
import datetime
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from SBTi.configs import PortfolioCoverageTVPConfig
|
|
9
|
+
from SBTi.interfaces import IDataProviderCompany, IDataProviderTarget, EScope, ETimeFrames
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SBTi:
|
|
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.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self, config: Type[PortfolioCoverageTVPConfig] = PortfolioCoverageTVPConfig
|
|
21
|
+
):
|
|
22
|
+
self.c = config
|
|
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
|
+
|
|
48
|
+
# Read CTA file into pandas dataframe
|
|
49
|
+
# Suppress warning about openpyxl
|
|
50
|
+
warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')
|
|
51
|
+
self.targets = pd.read_excel(self.c.FILE_TARGETS)
|
|
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")
|
|
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
|
+
|
|
136
|
+
def filter_cta_file(self, targets):
|
|
137
|
+
"""
|
|
138
|
+
Filter the CTA file to create a dataframe that has one row per company
|
|
139
|
+
with the columns "Action" and "Target".
|
|
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.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
# Create a new dataframe with only the columns "Action" and "Target"
|
|
146
|
+
# and the columns that are needed for identifying the company
|
|
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
|
|
153
|
+
]
|
|
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
|
+
|
|
159
|
+
# Keep rows where Action = Target and Target = Near-term
|
|
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
|
+
|
|
182
|
+
return df_nt_targets
|
|
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]]
|
|
205
|
+
) -> List[IDataProviderCompany]:
|
|
206
|
+
"""
|
|
207
|
+
Get company data from SBTi database and add sbti_validated field.
|
|
208
|
+
|
|
209
|
+
:param companies: A list of IDataProviderCompany instances
|
|
210
|
+
:param id_map: A map from company id to a tuple of (ISIN, LEI)
|
|
211
|
+
:return: A list of IDataProviderCompany instances, supplemented with the SBTi information
|
|
212
|
+
"""
|
|
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
|
|
279
|
+
self.targets = self.filter_cta_file(self.targets)
|
|
280
|
+
|
|
281
|
+
# Dictionary to store detailed target data
|
|
282
|
+
sbti_target_data = {}
|
|
283
|
+
|
|
284
|
+
for company in companies:
|
|
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
|
+
|
|
291
|
+
# Check lei and length of lei to avoid zeros
|
|
292
|
+
if lei and not lei.lower() == 'nan' and len(str(lei)) > 3:
|
|
293
|
+
targets = self.targets[
|
|
294
|
+
self.targets[self.c.COL_COMPANY_LEI] == lei
|
|
295
|
+
]
|
|
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':
|
|
302
|
+
targets = self.targets[
|
|
303
|
+
self.targets[self.c.COL_COMPANY_ISIN] == isin
|
|
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
|
+
]
|
|
310
|
+
else:
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
if len(targets) > 0:
|
|
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
|
|
331
|
+
|
|
332
|
+
return companies, sbti_target_data
|
|
Binary file
|
|
@@ -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,8 +1,8 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "sbti-finance-tool"
|
|
3
|
-
version = "1.0
|
|
3
|
+
version = "1.1.0"
|
|
4
4
|
description = "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
|
-
authors = ["sbti <
|
|
5
|
+
authors = ["sbti <financialinstitutions@sciencebasedtargets.org>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
classifiers = [
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
from typing import List, Type
|
|
2
|
-
import requests
|
|
3
|
-
import pandas as pd
|
|
4
|
-
import warnings
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from SBTi.configs import PortfolioCoverageTVPConfig
|
|
8
|
-
from SBTi.interfaces import IDataProviderCompany
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class SBTi:
|
|
12
|
-
"""
|
|
13
|
-
Data provider skeleton for SBTi. This class only provides the sbti_validated field for existing companies.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
def __init__(
|
|
17
|
-
self, config: Type[PortfolioCoverageTVPConfig] = PortfolioCoverageTVPConfig
|
|
18
|
-
):
|
|
19
|
-
self.c = config
|
|
20
|
-
# Fetch CTA file from SBTi website
|
|
21
|
-
resp = requests.get(self.c.CTA_FILE_URL)
|
|
22
|
-
# If status code == 200 then Write CTA file to disk
|
|
23
|
-
if resp.status_code == 200:
|
|
24
|
-
with open(self.c.FILE_TARGETS, 'wb') as output:
|
|
25
|
-
output.write(resp.content)
|
|
26
|
-
print(f'Status code from fetching the CTA file: {resp.status_code}, 200 = OK')
|
|
27
|
-
# Read CTA file into pandas dataframe
|
|
28
|
-
# Suppress warning about openpyxl - check if this is still needed in the released version.
|
|
29
|
-
else:
|
|
30
|
-
print('Could not fetch the CTA file from the SBTi website')
|
|
31
|
-
print('Will read older file from this package version')
|
|
32
|
-
|
|
33
|
-
warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')
|
|
34
|
-
self.targets = pd.read_excel(self.c.FILE_TARGETS)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def filter_cta_file(self, targets):
|
|
38
|
-
"""
|
|
39
|
-
Filter the CTA file to create a datafram that has on row per company
|
|
40
|
-
with the columns "Action" and "Target".
|
|
41
|
-
If Action = Target then only keep the rows where Target = Near-term.
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
# Create a new dataframe with only the columns "Action" and "Target"
|
|
45
|
-
# and the columns that are needed for identifying the company
|
|
46
|
-
targets = targets[
|
|
47
|
-
[
|
|
48
|
-
self.c.COL_COMPANY_NAME,
|
|
49
|
-
self.c.COL_COMPANY_ISIN,
|
|
50
|
-
self.c.COL_COMPANY_LEI,
|
|
51
|
-
self.c.COL_ACTION,
|
|
52
|
-
self.c.COL_TARGET
|
|
53
|
-
]
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
# Keep rows where Action = Target and Target = Near-term
|
|
57
|
-
df_nt_targets = targets[
|
|
58
|
-
(targets[self.c.COL_ACTION] == self.c.VALUE_ACTION_TARGET) &
|
|
59
|
-
(targets[self.c.COL_TARGET] == self.c.VALUE_TARGET_SET)]
|
|
60
|
-
|
|
61
|
-
# Drop duplicates in the dataframe by waterfall.
|
|
62
|
-
# Do company name last due to risk of misspelled names
|
|
63
|
-
# First drop duplicates on LEI, then on ISIN, then on company name
|
|
64
|
-
df_nt_targets = pd.concat([
|
|
65
|
-
df_nt_targets[~df_nt_targets[self.c.COL_COMPANY_LEI].isnull()].drop_duplicates(
|
|
66
|
-
subset=self.c.COL_COMPANY_LEI, keep='first'
|
|
67
|
-
),
|
|
68
|
-
df_nt_targets[df_nt_targets[self.c.COL_COMPANY_LEI].isnull()]
|
|
69
|
-
])
|
|
70
|
-
|
|
71
|
-
df_nt_targets = pd.concat([
|
|
72
|
-
df_nt_targets[~df_nt_targets[self.c.COL_COMPANY_ISIN].isnull()].drop_duplicates(
|
|
73
|
-
subset=self.c.COL_COMPANY_ISIN, keep='first'
|
|
74
|
-
),
|
|
75
|
-
df_nt_targets[df_nt_targets[self.c.COL_COMPANY_ISIN].isnull()]
|
|
76
|
-
])
|
|
77
|
-
|
|
78
|
-
df_nt_targets.drop_duplicates(subset=self.c.COL_COMPANY_NAME, inplace=True)
|
|
79
|
-
|
|
80
|
-
return df_nt_targets
|
|
81
|
-
|
|
82
|
-
def get_sbti_targets(
|
|
83
|
-
self, companies: List[IDataProviderCompany], id_map: dict
|
|
84
|
-
) -> List[IDataProviderCompany]:
|
|
85
|
-
"""
|
|
86
|
-
Check for each company if they have an SBTi validated target, first using the company LEI,
|
|
87
|
-
if available, and then using the ISIN.
|
|
88
|
-
|
|
89
|
-
:param companies: A list of IDataProviderCompany instances
|
|
90
|
-
:param id_map: A map from company id to a tuple of (ISIN, LEI)
|
|
91
|
-
:return: A list of IDataProviderCompany instances, supplemented with the SBTi information
|
|
92
|
-
"""
|
|
93
|
-
# Filter out information about targets
|
|
94
|
-
self.targets = self.filter_cta_file(self.targets)
|
|
95
|
-
|
|
96
|
-
for company in companies:
|
|
97
|
-
isin, lei = id_map.get(company.company_id)
|
|
98
|
-
# Check lei and length of lei to avoid zeros
|
|
99
|
-
if not lei.lower() == 'nan' and len(lei) > 3:
|
|
100
|
-
targets = self.targets[
|
|
101
|
-
self.targets[self.c.COL_COMPANY_LEI] == lei
|
|
102
|
-
]
|
|
103
|
-
elif not isin.lower() == 'nan':
|
|
104
|
-
targets = self.targets[
|
|
105
|
-
self.targets[self.c.COL_COMPANY_ISIN] == isin
|
|
106
|
-
]
|
|
107
|
-
else:
|
|
108
|
-
continue
|
|
109
|
-
if len(targets) > 0:
|
|
110
|
-
company.sbti_validated = (
|
|
111
|
-
self.c.VALUE_TARGET_SET in targets[self.c.COL_TARGET].values
|
|
112
|
-
)
|
|
113
|
-
return companies
|
|
114
|
-
|
|
115
|
-
|
|
Binary file
|
|
Binary file
|
sbti-finance-tool-1.0.9/setup.py
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
|
|
4
|
-
packages = \
|
|
5
|
-
['SBTi', 'SBTi.data']
|
|
6
|
-
|
|
7
|
-
package_data = \
|
|
8
|
-
{'': ['*'], 'SBTi': ['inputs/*']}
|
|
9
|
-
|
|
10
|
-
install_requires = \
|
|
11
|
-
['cleo>=2.0.1,<3.0.0',
|
|
12
|
-
'openpyxl==3.1.2',
|
|
13
|
-
'pandas==1.5.3',
|
|
14
|
-
'pydantic==1.10.7',
|
|
15
|
-
'requests==2.31.0',
|
|
16
|
-
'six==1.16.0',
|
|
17
|
-
'xlrd==2.0.1']
|
|
18
|
-
|
|
19
|
-
setup_kwargs = {
|
|
20
|
-
'name': 'sbti-finance-tool',
|
|
21
|
-
'version': '1.0.9',
|
|
22
|
-
'description': "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.'",
|
|
23
|
-
'long_description': "> Visit https://sciencebasedtargets.github.io/SBTi-finance-tool/ for the full documentation\n\n> If you have any additional questions or comments send a mail to: finance@sciencebasedtargets.org\n\n# SBTi Temperature Alignment tool\n\nThis package helps companies and financial institutions to assess the temperature alignment of current\ntargets, commitments, and investment and lending portfolios, and to use this information to develop\ntargets for official validation by the SBTi.\n\nThis tool can be used either as a standalone Python package, a REST API or as a simple webapp which provides a simple skin on the API.\nSo, the SBTi toolkit caters for three types of usage:\n\n- Users can integrate the Python package in their codebase\n- The tool can be included as a Microservice (containerised REST API) in any IT infrastructure (in the cloud or on premise)\n- As an webapp, exposing the functionality with a simple user interface.\n\nTo following diagram provides an overview of the different parts of the toolkit:\n\n +-------------------------------------------------+\n | UI : Simple user interface on top of API |\n | Install: via dockerhub |\n | docker.io/sbti/ui:latest |\n | |\n | +-----------------------------------------+ |\n | | REST API: Dockerized FastAPI/NGINX | |\n | | Source : github.com/OFBDABV/SBTi_api | |\n | | Install: via source or dockerhub | |\n | | docker.io/sbti/sbti/api:latest | |\n | | | |\n | | +---------------------------------+ | |\n | | | | | |\n | | |Core : Python Module | | |\n | | |Source : github.com/ScienceBasedTargets/ |\n | | | SBTi-finance-tool | | |\n | | |Install: via source or PyPi | | |\n | | | | | |\n | | +---------------------------------+ | |\n | +-----------------------------------------+ |\n +-------------------------------------------------+\n\nAs shown above the API is dependent on the Python Repo, in the same way the UI requires the API backend. These dependencies are scripted in the Docker files.\n\n> This repository only contains the Python module. If you'd like to use the REST API, please refer to [this repository](https://github.com/ScienceBasedTargets/SBTi-finance-tool-api) or the same repository on [Dockerhub](https://docker.io/sbti/sbti/api:latest).\n\n## Structure\n\nThe folder structure for this project is as follows:\n\n .\n ├── .github # Github specific files (Github Actions workflows)\n ├── app # FastAPI app files for the API endpoints\n ├── docs # Documentation files (Sphinx)\n ├── config # Config files for the Docker container\n ├── SBTi # The main Python package for the temperature alignment tool\n └── test # Automated unit tests for the SBTi package (Nose2 tests)\n\n## Installation\n\nThe SBTi package may be installed using PIP. If you'd like to install it locally use the following command. For testing or production please see the deployment section for further instructions\n\n```bash\npip install -e .\n```\n\nFor installing the latest stable release in PyPi run:\n\n```bash\npip install sbti-finance-tool\n```\n\n## Development\n\nTo set up the local dev environment with all dependencies, [install poetry](https://python-poetry.org/docs/#osx--linux--bashonwindows-install-instructions) and run\n\n```bash\npoetry install\n```\n\nThis will create a virtual environment inside the project folder under `.venv`.\n\n### Testing\n\nEach class should be unit tested. The unit tests are written using the Nose2 framework.\nThe setup.py script should have already installed Nose2, so now you may run the tests as follows:\n\n```bash\nnose2 -v\n```\n\n### Publish to PyPi\n\nThe package should be published to PyPi when any changes to main are merged.\n\nUpdate package\n\n1. bump version in `pyproject.toml` based on semantic versioning principles\n2. run `poetry build`\n3. run `poetry publish`\n4. check whether package has been successfully uploaded\n\n**Initial Setup**\n\n- Create account on [PyPi](https://pypi.org/)\n",
|
|
24
|
-
'author': 'sbti',
|
|
25
|
-
'author_email': 'finance@sciencebasedtargets.org',
|
|
26
|
-
'maintainer': None,
|
|
27
|
-
'maintainer_email': None,
|
|
28
|
-
'url': None,
|
|
29
|
-
'packages': packages,
|
|
30
|
-
'package_data': package_data,
|
|
31
|
-
'install_requires': install_requires,
|
|
32
|
-
'python_requires': '>=3.9,<4.0',
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
setup(**setup_kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sbti-finance-tool-1.0.9 → sbti_finance_tool-1.1.0}/SBTi/inputs/regression_model_summary.xlsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|