wbfdm 1.43.2__py2.py3-none-any.whl → 1.44.1__py2.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 wbfdm might be problematic. Click here for more details.
- wbfdm/analysis/financial_analysis/utils.py +3 -1
- wbfdm/contrib/qa/dataloaders/adjustments.py +54 -41
- wbfdm/contrib/qa/dataloaders/corporate_actions.py +47 -37
- wbfdm/contrib/qa/dataloaders/market_data.py +84 -59
- wbfdm/contrib/qa/dataloaders/officers.py +54 -39
- wbfdm/contrib/qa/dataloaders/utils.py +22 -0
- wbfdm/dataloaders/protocols.py +11 -18
- wbfdm/dataloaders/proxies.py +1 -1
- wbfdm/dataloaders/types.py +3 -0
- wbfdm/enums.py +3 -3
- wbfdm/serializers/instruments/instruments.py +1 -0
- wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +1 -1
- wbfdm/urls.py +1 -0
- wbfdm/viewsets/configs/buttons/instruments.py +1 -1
- wbfdm/viewsets/configs/display/__init__.py +1 -0
- wbfdm/viewsets/configs/display/financial_summary.py +133 -0
- wbfdm/viewsets/configs/display/instruments.py +11 -12
- wbfdm/viewsets/financial_analysis/__init__.py +1 -0
- wbfdm/viewsets/financial_analysis/financial_summary.py +266 -0
- wbfdm/viewsets/mixins.py +2 -0
- {wbfdm-1.43.2.dist-info → wbfdm-1.44.1.dist-info}/METADATA +3 -1
- {wbfdm-1.43.2.dist-info → wbfdm-1.44.1.dist-info}/RECORD +23 -20
- {wbfdm-1.43.2.dist-info → wbfdm-1.44.1.dist-info}/WHEEL +0 -0
|
@@ -192,6 +192,7 @@ class InstrumentModelSerializer(InstrumentAdditionalResourcesMixin, InstrumentMo
|
|
|
192
192
|
if not instance.is_managed:
|
|
193
193
|
res.update(
|
|
194
194
|
{
|
|
195
|
+
"fin-summary": reverse("wbfdm:financial-summary-list", args=[instance.id], request=request),
|
|
195
196
|
"swe-income-statement": reverse(
|
|
196
197
|
"wbfdm:statementwithestimates-list", args=[instance.id, "income"], request=request
|
|
197
198
|
),
|
wbfdm/urls.py
CHANGED
|
@@ -195,6 +195,7 @@ instrument_router.register(
|
|
|
195
195
|
)
|
|
196
196
|
instrument_router.register(r"valuation_ratios", viewsets.ValuationRatioChartViewSet, basename="valuation_ratios")
|
|
197
197
|
instrument_router.register(r"prices", viewsets.InstrumentPriceViewSet, basename="prices")
|
|
198
|
+
instrument_router.register(r"financial-summary", viewsets.FinancialSummary, basename="financial-summary")
|
|
198
199
|
|
|
199
200
|
instrument_statement_router = WBCoreRouter()
|
|
200
201
|
instrument_statement_router.register(
|
|
@@ -20,3 +20,4 @@ from .instruments_relationships import (
|
|
|
20
20
|
from .exchanges import ExchangeDisplayConfig
|
|
21
21
|
from .monthly_performances import MonthlyPerformancesInstrumentDisplayViewConfig
|
|
22
22
|
from .esg import InstrumentESGPAIDisplayViewConfig, InstrumentESGControversyDisplayViewConfig
|
|
23
|
+
from .financial_summary import FinancialSummaryDisplayViewConfig
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from wbcore.contrib.color.enums import WBColor
|
|
4
|
+
from wbcore.metadata.configs import display as dp
|
|
5
|
+
from wbcore.metadata.configs.display import DisplayViewConfig
|
|
6
|
+
|
|
7
|
+
if typing.TYPE_CHECKING:
|
|
8
|
+
from wbfdm.viewsets import FinancialSummary
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FinancialSummaryDisplayViewConfig(DisplayViewConfig):
|
|
12
|
+
view: "FinancialSummary"
|
|
13
|
+
|
|
14
|
+
ESTIMATE_COLOR = "#D2E5F6"
|
|
15
|
+
|
|
16
|
+
def get_list_display(self) -> dp.ListDisplay:
|
|
17
|
+
def generate_formatting_rules(col_key: str) -> typing.Iterator[dp.FormattingRule]:
|
|
18
|
+
yield dp.FormattingRule(
|
|
19
|
+
condition=[("==", "eps_growth", "id")],
|
|
20
|
+
style={
|
|
21
|
+
"borderBottom": "1px solid #000000",
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
yield dp.FormattingRule(
|
|
25
|
+
condition=[("==", "roic", "id")],
|
|
26
|
+
style={
|
|
27
|
+
"borderBottom": "1px solid #000000",
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
yield dp.FormattingRule(
|
|
31
|
+
condition=[("==", "interest_coverage_ratio", "id")],
|
|
32
|
+
style={
|
|
33
|
+
"borderBottom": "1px solid #000000",
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
yield dp.FormattingRule(
|
|
37
|
+
condition=[("==", "revenue_growth", "id")],
|
|
38
|
+
style={
|
|
39
|
+
"color": "#9FA0A1",
|
|
40
|
+
"fontStyle": "italic",
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
yield dp.FormattingRule(
|
|
44
|
+
condition=[("==", "net_profit_growth", "id")],
|
|
45
|
+
style={
|
|
46
|
+
"color": "#9FA0A1",
|
|
47
|
+
"fontStyle": "italic",
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
yield dp.FormattingRule(
|
|
51
|
+
condition=[("==", "eps_growth", "id")],
|
|
52
|
+
style={
|
|
53
|
+
"color": "#9FA0A1",
|
|
54
|
+
"fontStyle": "italic",
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
yield dp.FormattingRule(
|
|
58
|
+
condition=[("==", "free_cash_flow_per_share_growth", "id")],
|
|
59
|
+
style={
|
|
60
|
+
"color": "#9FA0A1",
|
|
61
|
+
"fontStyle": "italic",
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
yield dp.FormattingRule(
|
|
65
|
+
condition=[("==", "year", "id")],
|
|
66
|
+
style={
|
|
67
|
+
"fontWeight": "bold",
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
yield dp.FormattingRule(
|
|
71
|
+
style={"color": WBColor.RED_DARK.value},
|
|
72
|
+
condition=[("<", 0)],
|
|
73
|
+
)
|
|
74
|
+
if self.view.estimate_columns.get(col_key, False) is True:
|
|
75
|
+
yield dp.FormattingRule(style={"background-color": self.ESTIMATE_COLOR})
|
|
76
|
+
|
|
77
|
+
def generate_field(col: str) -> dp.Field:
|
|
78
|
+
return dp.Field(
|
|
79
|
+
key=col,
|
|
80
|
+
label=col,
|
|
81
|
+
width=80,
|
|
82
|
+
formatting_rules=generate_formatting_rules(col),
|
|
83
|
+
auto_size=False,
|
|
84
|
+
resizable=False,
|
|
85
|
+
movable=False,
|
|
86
|
+
menu=False,
|
|
87
|
+
size_to_fit=False,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return dp.ListDisplay(
|
|
91
|
+
fields=[
|
|
92
|
+
dp.Field(
|
|
93
|
+
key="label",
|
|
94
|
+
label=" ",
|
|
95
|
+
width=120,
|
|
96
|
+
auto_size=False,
|
|
97
|
+
resizable=False,
|
|
98
|
+
movable=False,
|
|
99
|
+
menu=False,
|
|
100
|
+
size_to_fit=False,
|
|
101
|
+
formatting_rules=[
|
|
102
|
+
dp.FormattingRule(
|
|
103
|
+
condition=[("==", "eps_growth", "id")],
|
|
104
|
+
style={
|
|
105
|
+
"borderBottom": "1px solid #000000",
|
|
106
|
+
},
|
|
107
|
+
),
|
|
108
|
+
dp.FormattingRule(
|
|
109
|
+
condition=[("==", "roic", "id")],
|
|
110
|
+
style={
|
|
111
|
+
"borderBottom": "1px solid #000000",
|
|
112
|
+
},
|
|
113
|
+
),
|
|
114
|
+
dp.FormattingRule(
|
|
115
|
+
condition=[("==", "interest_coverage_ratio", "id")],
|
|
116
|
+
style={
|
|
117
|
+
"borderBottom": "1px solid #000000",
|
|
118
|
+
},
|
|
119
|
+
),
|
|
120
|
+
dp.FormattingRule(style={"background-color": "#ECECEC"}),
|
|
121
|
+
dp.FormattingRule(
|
|
122
|
+
condition=[("==", "year", "id")],
|
|
123
|
+
style={
|
|
124
|
+
"fontWeight": "bold",
|
|
125
|
+
},
|
|
126
|
+
),
|
|
127
|
+
],
|
|
128
|
+
),
|
|
129
|
+
*map(generate_field, self.view.fiscal_columns),
|
|
130
|
+
],
|
|
131
|
+
condensed=True,
|
|
132
|
+
editable=False,
|
|
133
|
+
)
|
|
@@ -17,7 +17,7 @@ from wbcore.metadata.configs.display.instance_display.shortcuts import (
|
|
|
17
17
|
default,
|
|
18
18
|
)
|
|
19
19
|
from wbcore.metadata.configs.display.instance_display.styles import Style
|
|
20
|
-
from wbcore.metadata.configs.display.instance_display.utils import repeat
|
|
20
|
+
from wbcore.metadata.configs.display.instance_display.utils import repeat, repeat_field
|
|
21
21
|
from wbfdm.contrib.metric.viewsets.configs.utils import (
|
|
22
22
|
get_performance_fields,
|
|
23
23
|
get_statistic_field,
|
|
@@ -123,19 +123,18 @@ class InstrumentDisplayConfig(DisplayViewConfig):
|
|
|
123
123
|
layouts={
|
|
124
124
|
default(): Layout(
|
|
125
125
|
grid_template_areas=[
|
|
126
|
-
["name", "
|
|
127
|
-
["instrument_type", "
|
|
128
|
-
["
|
|
129
|
-
["
|
|
130
|
-
[
|
|
131
|
-
[".", ".", "market_data", "valuation_ratios-new"],
|
|
132
|
-
["description", "description", "description", "description"],
|
|
126
|
+
["name", "ticker", "isin", "exchange", "market_data_chart", "valuation_ratios-new"],
|
|
127
|
+
["name_repr", "instrument_type", "currency", "country", "market_data_chart", "valuation_ratios-new"],
|
|
128
|
+
["inception_date", "delisted_date", ".", ".", "market_data_chart", "valuation_ratios-new"],
|
|
129
|
+
["description", "description", "description", "description", "market_data_chart", "valuation_ratios-new"],
|
|
130
|
+
[repeat_field(4, "fin-summary"), repeat_field(2, ".")],
|
|
133
131
|
],
|
|
134
|
-
grid_template_rows=["
|
|
135
|
-
grid_template_columns=[
|
|
132
|
+
grid_template_rows=["min-content"] * 3 + ["1fr", "446px"],
|
|
133
|
+
grid_template_columns=[repeat(4, "183px"), "1fr", "1fr"],
|
|
136
134
|
inlines=[
|
|
137
|
-
Inline(key="
|
|
138
|
-
Inline(key="valuation_ratios-new", endpoint="valuation_ratios-new"),
|
|
135
|
+
Inline(key="market_data_chart", endpoint="market_data", title="Prices"),
|
|
136
|
+
Inline(key="valuation_ratios-new", endpoint="valuation_ratios-new", title="Ratios"),
|
|
137
|
+
Inline(key="fin-summary", endpoint="fin-summary", title="Financial Summary", hide_controls=True),
|
|
139
138
|
],
|
|
140
139
|
),
|
|
141
140
|
},
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from django.contrib.messages import warning
|
|
3
|
+
from django.utils.functional import cached_property
|
|
4
|
+
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
5
|
+
from wbcore.metadata.configs.endpoints import NoEndpointViewConfig
|
|
6
|
+
from wbcore.pandas import fields as pf
|
|
7
|
+
from wbcore.pandas.utils import sanitize_fields, pct_change_with_negative_values
|
|
8
|
+
from wbcore.pandas.utils import (
|
|
9
|
+
override_number_to_decimal,
|
|
10
|
+
override_number_to_percent,
|
|
11
|
+
override_number_to_x,
|
|
12
|
+
override_number_to_integer_without_decorations,
|
|
13
|
+
)
|
|
14
|
+
from wbfdm.enums import Financial
|
|
15
|
+
from wbfdm.models.instruments import Instrument
|
|
16
|
+
|
|
17
|
+
from wbfdm.viewsets.configs.display import (
|
|
18
|
+
FinancialSummaryDisplayViewConfig,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
from ..mixins import InstrumentMixin
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FinancialSummary(InstrumentMixin, ExportPandasAPIViewSet):
|
|
26
|
+
queryset = Instrument.objects.none()
|
|
27
|
+
display_config_class = FinancialSummaryDisplayViewConfig
|
|
28
|
+
ordering_fields = "__all__"
|
|
29
|
+
endpoint_config_class = NoEndpointViewConfig
|
|
30
|
+
|
|
31
|
+
def get_queryset(self):
|
|
32
|
+
return Instrument.objects.filter(id=self.instrument.id)
|
|
33
|
+
|
|
34
|
+
def get_pandas_fields(self, request):
|
|
35
|
+
return pf.PandasFields(
|
|
36
|
+
fields=[
|
|
37
|
+
pf.PKField(key="id", label="ID"),
|
|
38
|
+
pf.CharField(key="label", label="Financial"),
|
|
39
|
+
pf.JsonField(key="_overwrites", label="Overwrites"),
|
|
40
|
+
*[pf.FloatField(key=k, label=k, precision=1) for k in self.fiscal_columns],
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def add_messages(self, request, instance=None, **kwargs):
|
|
45
|
+
super().add_messages(request, instance=instance, **kwargs)
|
|
46
|
+
if self.df.empty:
|
|
47
|
+
warning(request, "There is no data available for the Financial Summary table", extra_tags="auto_close=0")
|
|
48
|
+
|
|
49
|
+
def get_dataframe(self, request, queryset, **kwargs):
|
|
50
|
+
# Get all necessary data from the dataloader and load a dataframe
|
|
51
|
+
df = pd.DataFrame(
|
|
52
|
+
queryset.dl.financials(
|
|
53
|
+
values=self.FINANCIAL_VALUES,
|
|
54
|
+
from_index=-5,
|
|
55
|
+
to_index=3,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
if not df.empty:
|
|
59
|
+
# Pivot the data
|
|
60
|
+
df = df.pivot_table(
|
|
61
|
+
columns="financial",
|
|
62
|
+
index=["year", "period_end_date", "estimate"],
|
|
63
|
+
values="value",
|
|
64
|
+
).rename_axis(columns=None)
|
|
65
|
+
|
|
66
|
+
sanitize_fields(df, map(lambda enum: enum.value, self.FINANCIAL_VALUES))
|
|
67
|
+
|
|
68
|
+
# Compute all necessary fields
|
|
69
|
+
df["revenue_growth"] = df["revenue"].pct_change() * 100
|
|
70
|
+
df["gross_profit_pct"] = df["gross_profit"] / df["revenue"] * 100
|
|
71
|
+
df["ebitda_pct"] = df["ebitda"] / df["revenue"] * 100
|
|
72
|
+
df["ebit_pct"] = df["ebit"] / df["revenue"] * 100
|
|
73
|
+
df["net_income_pct"] = df["net_income"] / df["revenue"] * 100
|
|
74
|
+
df["eps_growth"] = pct_change_with_negative_values(df, "eps") * 100
|
|
75
|
+
|
|
76
|
+
df["net_debt_ebitda"] = df["net_debt"] / df["ebitda"]
|
|
77
|
+
df["debt_assets"] = df["total_debt"] / df["total_assets"] * 100
|
|
78
|
+
df["debt_equity"] = df["total_debt"] / df["total_assets"] * 100
|
|
79
|
+
|
|
80
|
+
df["interest_coverage_ratio"] = df["ebit"] / df["interest_expense"]
|
|
81
|
+
df["free_cash_flow_per_share"] = df["free_cash_flow"] / df["shares_outstanding"]
|
|
82
|
+
df["free_cash_flow_per_share_growth"] = (
|
|
83
|
+
pct_change_with_negative_values(df, "free_cash_flow_per_share") * 100
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Normalize data
|
|
87
|
+
df["revenue"] = df["revenue"] / 1_000_000
|
|
88
|
+
df["net_income"] = df["net_income"] / 1_000_000
|
|
89
|
+
|
|
90
|
+
# Sort the columns into the desired order
|
|
91
|
+
# Reset the index to get the period end date as a column
|
|
92
|
+
# Pivot back to have the dates on top
|
|
93
|
+
df = df[self.FIELDS]
|
|
94
|
+
|
|
95
|
+
# Reset the 2 indices and transpose back
|
|
96
|
+
df = df.reset_index(level=[0, 2]).sort_index()
|
|
97
|
+
|
|
98
|
+
# Adjust the columns to be in a different format
|
|
99
|
+
df.index = df.index.map(lambda x: x.strftime("%b/%y"))
|
|
100
|
+
MAX_ROW = 8
|
|
101
|
+
if df.shape[0] > MAX_ROW:
|
|
102
|
+
df = df.iloc[1:] # remove first row
|
|
103
|
+
df = df.iloc[0 : min([df.shape[0], MAX_ROW])] # keep only 8 row maximum
|
|
104
|
+
|
|
105
|
+
self._estimate_columns = df["estimate"].to_dict()
|
|
106
|
+
df = df.drop(columns=["estimate"], errors="ignore")
|
|
107
|
+
return df
|
|
108
|
+
|
|
109
|
+
def manipulate_dataframe(self, df):
|
|
110
|
+
if not df.empty:
|
|
111
|
+
df = df.T
|
|
112
|
+
# Add labels for human readable output
|
|
113
|
+
df["label"] = self.LABELS
|
|
114
|
+
|
|
115
|
+
override_number_to_percent(
|
|
116
|
+
df,
|
|
117
|
+
*list(
|
|
118
|
+
map(
|
|
119
|
+
lambda x: df.index == x,
|
|
120
|
+
[
|
|
121
|
+
"revenue_growth",
|
|
122
|
+
"gross_profit_pct",
|
|
123
|
+
"ebitda_pct",
|
|
124
|
+
"ebit_pct",
|
|
125
|
+
"net_income_pct",
|
|
126
|
+
"eps_growth",
|
|
127
|
+
"roe",
|
|
128
|
+
"roic",
|
|
129
|
+
"roc",
|
|
130
|
+
"roa",
|
|
131
|
+
"debt_equity",
|
|
132
|
+
"debt_assets",
|
|
133
|
+
"free_cash_flow_per_share_growth",
|
|
134
|
+
],
|
|
135
|
+
)
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
override_number_to_x(
|
|
140
|
+
df,
|
|
141
|
+
*list(
|
|
142
|
+
map(
|
|
143
|
+
lambda x: df.index == x,
|
|
144
|
+
[
|
|
145
|
+
"net_debt_ebitda",
|
|
146
|
+
"interest_coverage_ratio",
|
|
147
|
+
],
|
|
148
|
+
)
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
override_number_to_decimal(
|
|
153
|
+
df,
|
|
154
|
+
*list(
|
|
155
|
+
map(
|
|
156
|
+
lambda x: df.index == x,
|
|
157
|
+
[
|
|
158
|
+
"revenue",
|
|
159
|
+
"net_income",
|
|
160
|
+
],
|
|
161
|
+
)
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
override_number_to_integer_without_decorations(
|
|
166
|
+
df,
|
|
167
|
+
*list(
|
|
168
|
+
map(
|
|
169
|
+
lambda x: df.index == x,
|
|
170
|
+
[
|
|
171
|
+
"year",
|
|
172
|
+
],
|
|
173
|
+
)
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
df = df.reset_index(names="id")
|
|
178
|
+
return df
|
|
179
|
+
|
|
180
|
+
@cached_property
|
|
181
|
+
def fiscal_columns(self) -> list:
|
|
182
|
+
"""Returns the fiscal columns from the dataframe"""
|
|
183
|
+
return self.df.columns.difference(["label", "_overwrites", "id"]).to_list()
|
|
184
|
+
|
|
185
|
+
@cached_property
|
|
186
|
+
def estimate_columns(self) -> dict:
|
|
187
|
+
"""Returns a dictionary with the estimate column for each fiscal column
|
|
188
|
+
The _estimate_columns will be set if the dataframe is constructed.
|
|
189
|
+
"""
|
|
190
|
+
return getattr(self, "_estimate_columns", {})
|
|
191
|
+
|
|
192
|
+
@cached_property
|
|
193
|
+
def FINANCIAL_VALUES(self) -> list[Financial]:
|
|
194
|
+
return [
|
|
195
|
+
Financial.REVENUE, # SAL
|
|
196
|
+
Financial.GROSS_PROFIT, # GRI
|
|
197
|
+
Financial.EBITDA, # EBT
|
|
198
|
+
Financial.EBIT, # EBI
|
|
199
|
+
Financial.NET_INCOME, # NET
|
|
200
|
+
Financial.EPS, # EPS
|
|
201
|
+
Financial.SHAREHOLDERS_EQUITY, # SHE
|
|
202
|
+
Financial.TOTAL_ASSETS, # TAS
|
|
203
|
+
Financial.TAX_RATE, # TAX
|
|
204
|
+
Financial.RETURN_ON_INVESTED_CAPITAL, # RIC
|
|
205
|
+
Financial.NET_DEBT, # NDT
|
|
206
|
+
Financial.TOTAL_DEBT, # TDT
|
|
207
|
+
Financial.INTEREST_EXPENSE, # INE
|
|
208
|
+
Financial.FREE_CASH_FLOW, # FCF
|
|
209
|
+
Financial.SHARES_OUTSTANDING,
|
|
210
|
+
Financial.CURRENT_LIABILITIES, # CRL
|
|
211
|
+
Financial.CASH_EQUIVALENTS,
|
|
212
|
+
Financial.RETURN_ON_EQUITY,
|
|
213
|
+
Financial.RETURN_ON_ASSETS,
|
|
214
|
+
Financial.RETURN_ON_CAPITAL,
|
|
215
|
+
Financial.RETURN_ON_INVESTED_CAPITAL,
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
@cached_property
|
|
219
|
+
def FIELDS(self) -> list[str]:
|
|
220
|
+
return [
|
|
221
|
+
"revenue",
|
|
222
|
+
"revenue_growth",
|
|
223
|
+
"gross_profit_pct",
|
|
224
|
+
"ebitda_pct",
|
|
225
|
+
"ebit_pct",
|
|
226
|
+
"net_income_pct",
|
|
227
|
+
"net_income",
|
|
228
|
+
"eps",
|
|
229
|
+
"eps_growth",
|
|
230
|
+
"roe",
|
|
231
|
+
"roa",
|
|
232
|
+
"roc",
|
|
233
|
+
"roic",
|
|
234
|
+
"net_debt_ebitda",
|
|
235
|
+
"debt_assets",
|
|
236
|
+
"debt_equity",
|
|
237
|
+
"interest_coverage_ratio",
|
|
238
|
+
"free_cash_flow_per_share",
|
|
239
|
+
"free_cash_flow_per_share_growth",
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def LABELS(self) -> list[str]:
|
|
244
|
+
currency_key = self.instrument.currency.key if self.instrument.currency else "N.A."
|
|
245
|
+
return [
|
|
246
|
+
f"in {currency_key} MN",
|
|
247
|
+
"Revenue",
|
|
248
|
+
"Y/Y Change",
|
|
249
|
+
"Gross Margin",
|
|
250
|
+
"EBITDA Margin",
|
|
251
|
+
"EBIT Margin",
|
|
252
|
+
"Net Profit Margin",
|
|
253
|
+
"Net Profit",
|
|
254
|
+
"EPS",
|
|
255
|
+
"Y/Y Change",
|
|
256
|
+
"ROE",
|
|
257
|
+
"ROA",
|
|
258
|
+
"ROC",
|
|
259
|
+
"ROIC",
|
|
260
|
+
"Net Debt/EBITDA",
|
|
261
|
+
"D/A",
|
|
262
|
+
"D/E",
|
|
263
|
+
"Int. Cov. Ratio",
|
|
264
|
+
"FCF per share",
|
|
265
|
+
"Y/Y Change",
|
|
266
|
+
]
|
wbfdm/viewsets/mixins.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wbfdm
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.44.1
|
|
4
4
|
Summary: The workbench module ensures rapid access to diverse financial data (market, fundamental, forecasts, ESG), with features for storing instruments, classifying them, and conducting financial analysis.
|
|
5
5
|
Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
|
|
6
6
|
Requires-Dist: roman==4.*
|
|
7
|
+
Requires-Dist: sentry-sdk==2.*
|
|
7
8
|
Requires-Dist: stockstats==0.6.*
|
|
8
9
|
Requires-Dist: wbcore
|
|
9
10
|
Requires-Dist: wbnews
|
|
@@ -13,3 +14,4 @@ Requires-Dist: requests-cache==1.0.*; extra == 'dsws'
|
|
|
13
14
|
Provides-Extra: qa
|
|
14
15
|
Requires-Dist: jinjasql2==0.1.*; extra == 'qa'
|
|
15
16
|
Requires-Dist: mssql-django==1.4.*; extra == 'qa'
|
|
17
|
+
Requires-Dist: pypika; extra == 'qa'
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
wbfdm/__init__.py,sha256=uyfGiipFnhOCQlqywx7wsgp6d-SYnqPqsPQd_xePZl8,23
|
|
2
2
|
wbfdm/apps.py,sha256=83jiPNQfxUbO7KYKBS5qo5SZNuz25Kmmy_txoVmIKG0,333
|
|
3
3
|
wbfdm/dynamic_preferences_registry.py,sha256=tE9ZxlQDD7rqKM3GXjxyORQqDjf39GTENkOCZFt8Ixo,1299
|
|
4
|
-
wbfdm/enums.py,sha256=
|
|
4
|
+
wbfdm/enums.py,sha256=5AuUouk5uuSNmRc6e-SiBu4FPmHVTN60ol9ftiuVrAc,33041
|
|
5
5
|
wbfdm/jinja2.py,sha256=pkIC1U-0rf6vn0DDEUzZ8dPYiTGEPY8LBTRMi9wYiuc,199
|
|
6
6
|
wbfdm/menu.py,sha256=c_uP0wIxboHvY0BfHbYPI9GI4F41UxSgREqbzcaxzHM,399
|
|
7
7
|
wbfdm/preferences.py,sha256=8ghDcaapOMso1kjtNfKbSFykPUTxzqI5R77gM3BgiMs,927
|
|
8
8
|
wbfdm/signals.py,sha256=9BQKFURv4iLAvQKagrk0K_3iKrgIveqRaN7jvzyC_Lg,427
|
|
9
9
|
wbfdm/tasks.py,sha256=gWilvQeIsbPBpWOPJFlAGPrQUPFLtSG_WTfpDibnRBg,2496
|
|
10
|
-
wbfdm/urls.py,sha256=
|
|
10
|
+
wbfdm/urls.py,sha256=zWtY11MM8YCwtuOakznfQZFMkQhxUAEscI_frzLwjVM,8886
|
|
11
11
|
wbfdm/utils.py,sha256=4cWrCpqXxHIjtSlt4DDPFvmtaqXw_H0nqhM6sGuXx0o,1938
|
|
12
12
|
wbfdm/admin/__init__.py,sha256=Z1VtH_gjD71K79KcD-2Q2Lu_p_7j0akMZj7gNxdz1CQ,1398
|
|
13
13
|
wbfdm/admin/classifications.py,sha256=LwCwHilOO66f4-GSua0eu1RUjtpVdGjN1FevlISwT1A,1140
|
|
@@ -30,7 +30,7 @@ wbfdm/analysis/financial_analysis/financial_metric_analysis.py,sha256=ps1z634JO6
|
|
|
30
30
|
wbfdm/analysis/financial_analysis/financial_ratio_analysis.py,sha256=M1iLyuxE66TXI4gm_etXMxBK8MGpaOZTiBf0l3e2udo,4622
|
|
31
31
|
wbfdm/analysis/financial_analysis/financial_statistics_analysis.py,sha256=oxLT_3VgDIStMVOXSp5IE1VHVs4XWTdEaUdZr-L1D9s,13076
|
|
32
32
|
wbfdm/analysis/financial_analysis/statement_with_estimates.py,sha256=w-V6O3EnO0IZvC8oCzoPJ5jVMDdKCYMmFBrRj7yb2Io,24085
|
|
33
|
-
wbfdm/analysis/financial_analysis/utils.py,sha256=
|
|
33
|
+
wbfdm/analysis/financial_analysis/utils.py,sha256=F8ij4pgkw5Qad4_95S32O_Z1vd0rs-GYh-zZpt6WVgM,13576
|
|
34
34
|
wbfdm/analysis/technical_analysis/__init__.py,sha256=nQdpDl2okzWID6UT7fyfl36RSLNO1ZZzxRt_mkGVVkY,50
|
|
35
35
|
wbfdm/analysis/technical_analysis/technical_analysis.py,sha256=MkK5HrLz9_TCVAv4qDDZjx6EIqsvusMYB7ybg3MvSsg,4961
|
|
36
36
|
wbfdm/analysis/technical_analysis/traces.py,sha256=1wzmW7JcQXki0q14TdG36tacZpIFJ_bO4KHx_5TgNxc,6371
|
|
@@ -96,13 +96,14 @@ wbfdm/contrib/qa/apps.py,sha256=NVH9FSbJh3XnddR5coJ51jka7nd9wXjwnCqO7h8gNXA,528
|
|
|
96
96
|
wbfdm/contrib/qa/database_routers.py,sha256=MIfLEqenhcdgKER4Vl2T-gMudN76rmsDoPrAVesl334,950
|
|
97
97
|
wbfdm/contrib/qa/tasks.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
98
|
wbfdm/contrib/qa/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
|
-
wbfdm/contrib/qa/dataloaders/adjustments.py,sha256=
|
|
100
|
-
wbfdm/contrib/qa/dataloaders/corporate_actions.py,sha256=
|
|
99
|
+
wbfdm/contrib/qa/dataloaders/adjustments.py,sha256=am5_9Pm471qtiKRO5AUtQ_19DutqhF8rHcWziMMNptA,2981
|
|
100
|
+
wbfdm/contrib/qa/dataloaders/corporate_actions.py,sha256=jbfQ4GyyPy9mnpBFUHoJSErf7Y6pMlnErE5UUCVpPJA,2912
|
|
101
101
|
wbfdm/contrib/qa/dataloaders/financials.py,sha256=U2rCUFV0efAPsEEzUq_1gdW0VL-V3fuAM_hpGrJxjko,3474
|
|
102
|
-
wbfdm/contrib/qa/dataloaders/market_data.py,sha256=
|
|
103
|
-
wbfdm/contrib/qa/dataloaders/officers.py,sha256=
|
|
102
|
+
wbfdm/contrib/qa/dataloaders/market_data.py,sha256=6QkOwpOEJdu1d5eYx_d2QEA801hoHUrJdd3GMwS7Itg,5831
|
|
103
|
+
wbfdm/contrib/qa/dataloaders/officers.py,sha256=O14KPWAAtEswOgzRjlN_d-vCyl1unRngejeGYXBu9DU,2942
|
|
104
104
|
wbfdm/contrib/qa/dataloaders/reporting_dates.py,sha256=Nq83u7eAhsPe9b4ePI5xcgvWEgW1XwHLyVzxpbgcdnQ,2492
|
|
105
105
|
wbfdm/contrib/qa/dataloaders/statements.py,sha256=tE5ux6bFmAezivssRkm3cNW51lJLoAjHbUao1gR4JMM,10093
|
|
106
|
+
wbfdm/contrib/qa/dataloaders/utils.py,sha256=EyKDh0KDBtj62J_8UHsj3nwCn_R9zGteENUMGHA7DDw,621
|
|
106
107
|
wbfdm/contrib/qa/jinja2/qa/sql/companies.sql,sha256=RzTkfVjBVOgyirgKxp2rnJdjsKl8d3YM-d2qdjHx9cw,3801
|
|
107
108
|
wbfdm/contrib/qa/jinja2/qa/sql/instruments.sql,sha256=7p31aJDtKusfSzhrjyUo8FlmbPXNyffWtNclm8DZkFM,3863
|
|
108
109
|
wbfdm/contrib/qa/jinja2/qa/sql/quotes.sql,sha256=JEljMTlHM4978cvJkiKnKXn09FrcgcQqq4KkwWQUwcM,3910
|
|
@@ -116,9 +117,9 @@ wbfdm/contrib/qa/sync/instruments.py,sha256=8aTQVJ_cw1phe4FWikn79pjCfUijaTcwkdhQ
|
|
|
116
117
|
wbfdm/contrib/qa/sync/utils.py,sha256=TYZY3QB75POUmme1U71yo6WPzf4AXs4pWpFdI1Hq2GY,8936
|
|
117
118
|
wbfdm/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
118
119
|
wbfdm/dataloaders/cache.py,sha256=K9BeVxT7p-BMvjurINt18bfrUDccp840uIjfDBLJRNk,4841
|
|
119
|
-
wbfdm/dataloaders/protocols.py,sha256=
|
|
120
|
-
wbfdm/dataloaders/proxies.py,sha256=
|
|
121
|
-
wbfdm/dataloaders/types.py,sha256=
|
|
120
|
+
wbfdm/dataloaders/protocols.py,sha256=8ibd-jAdlwu-JvRL2LqJMi5mOcWyWYh1Ll_wD4nPJxE,2994
|
|
121
|
+
wbfdm/dataloaders/proxies.py,sha256=u8vV7HpVrTSueXsRF0lweKvfJlEwJSn8F4Z5T_J6ge8,7071
|
|
122
|
+
wbfdm/dataloaders/types.py,sha256=wtXMGbUV-m32EHBCEbO5p3KPCHI87G_V9o9K0DpVgxM,5463
|
|
122
123
|
wbfdm/factories/__init__.py,sha256=yYxAKBde_ksIr-3g4RjL6d5Wu-nmsuEDdYNyJpgfpQU,660
|
|
123
124
|
wbfdm/factories/classifications.py,sha256=3xGuvhivIxkTKwZK4uHCNkqzRfsHMUHYLl0yQhlah3Y,1826
|
|
124
125
|
wbfdm/factories/controversies.py,sha256=bltnEeDetGXsNsz_BqFQoTmLWuqGhq6oZb-aKVrhS-Y,928
|
|
@@ -248,7 +249,7 @@ wbfdm/serializers/instruments/instrument_lists.py,sha256=fh2HGrVMP0TPVFMmNA54l-q
|
|
|
248
249
|
wbfdm/serializers/instruments/instrument_prices.py,sha256=yU3coJ6M6BWt7PSupBCG509iIIlgRv9zTvnbrKcf5yo,2592
|
|
249
250
|
wbfdm/serializers/instruments/instrument_relationships.py,sha256=0AkA_gOUTQTaV6dgzqR5doc4rTli-7Grb1_So1b3rPw,6653
|
|
250
251
|
wbfdm/serializers/instruments/instrument_requests.py,sha256=vAaObfiRJegM44KfevzSOxP8JsfTBzeCigATpi6f9jU,2175
|
|
251
|
-
wbfdm/serializers/instruments/instruments.py,sha256=
|
|
252
|
+
wbfdm/serializers/instruments/instruments.py,sha256=itZtcatOkG9L22L2_os8vdFWldimmis7thIUMo1_xuc,11607
|
|
252
253
|
wbfdm/serializers/instruments/mixins.py,sha256=kxvjgi6zgAeIDkryDm3x3VYtiwP-rk4mcI825RsVXWM,4394
|
|
253
254
|
wbfdm/sync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
254
255
|
wbfdm/sync/abstract.py,sha256=u1zjHT0ZJabUZpy3_EUCTtEig0p5Fuhlx69O9BsV_Gs,849
|
|
@@ -260,7 +261,7 @@ wbfdm/tests/tests.py,sha256=i0CntYKvaHWmdJe34sZMLeKRiHL4uRfhdM30_4gp08Q,253
|
|
|
260
261
|
wbfdm/tests/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
261
262
|
wbfdm/tests/analysis/test_esg.py,sha256=_xNg98SlU5i0N0qE3aZ9YU06Ec_fMBdu8q8Hmw116l4,7228
|
|
262
263
|
wbfdm/tests/analysis/financial_analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
263
|
-
wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py,sha256=
|
|
264
|
+
wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py,sha256=ZZlNiN9L-QlJuqRo7YA9gfad77Jn7dPLz30QHvJax2s,13664
|
|
264
265
|
wbfdm/tests/analysis/financial_analysis/test_utils.py,sha256=YciCIzfvfqrZECAPv02qtPdxBlKktmAoA2OEuqnAYjw,12474
|
|
265
266
|
wbfdm/tests/dataloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
266
267
|
wbfdm/tests/dataloaders/test_cache.py,sha256=prrjtJ1swX1m9rwQ30dOHvozUuPzJzi-6yJ8eupczis,2472
|
|
@@ -276,7 +277,7 @@ wbfdm/viewsets/__init__.py,sha256=ZM29X0-5AkSH2rzF_gKUI4FoaEWCcMTXPjruJ4Gi_x8,38
|
|
|
276
277
|
wbfdm/viewsets/esg.py,sha256=mw6WF2zqifakXEJZWkfgvOHANqCgcNeMH_GP5Je-u0o,2866
|
|
277
278
|
wbfdm/viewsets/exchanges.py,sha256=w4C50GlG4B6_inkmaDfR8PKOFRxOn-fiucgT-BuBeF4,1784
|
|
278
279
|
wbfdm/viewsets/market_data.py,sha256=9hCg6PmdJtr7Rs0PVzjGwgG8QysjUd-97DO3ZL8Tkm4,6717
|
|
279
|
-
wbfdm/viewsets/mixins.py,sha256=
|
|
280
|
+
wbfdm/viewsets/mixins.py,sha256=nKaoC8u4ZR6eMNrNaKbE4BEAnu8wS9IkLYWTDjM3tfE,324
|
|
280
281
|
wbfdm/viewsets/officers.py,sha256=eE56hOW4xNHytRx3izqkBu2KHEQql2VENT8cmzmzx2E,1058
|
|
281
282
|
wbfdm/viewsets/prices.py,sha256=O7yL4n1danXgnikYboMaRYsfama_-gJjwNuxx6tCROI,2399
|
|
282
283
|
wbfdm/viewsets/configs/__init__.py,sha256=2SZRwO-GJF8yxLi6WLfDhMb5-pmOdjdcspdJb6FKgWM,114
|
|
@@ -284,15 +285,16 @@ wbfdm/viewsets/configs/buttons/__init__.py,sha256=anGBjblMGGvyAUblYbnpmUulXQaulG
|
|
|
284
285
|
wbfdm/viewsets/configs/buttons/classifications.py,sha256=sxfwIItdNGoJ1slnbKmWbxgN5nYq3fzVEUfeNFLebII,823
|
|
285
286
|
wbfdm/viewsets/configs/buttons/exchanges.py,sha256=2swv3ORAaYF525mXRpp_3zmEl760FJSKYZfsR1ai8KM,255
|
|
286
287
|
wbfdm/viewsets/configs/buttons/instrument_prices.py,sha256=Tvbbwl8FmXU7vZdhe9n0ICfYO1QkUlw7pD4WSfdSxp0,1873
|
|
287
|
-
wbfdm/viewsets/configs/buttons/instruments.py,sha256=
|
|
288
|
-
wbfdm/viewsets/configs/display/__init__.py,sha256=
|
|
288
|
+
wbfdm/viewsets/configs/buttons/instruments.py,sha256=p-E-bw3sxC5fitNfLpn_i8wC0nyk-YUId_GKDFczrLk,12305
|
|
289
|
+
wbfdm/viewsets/configs/display/__init__.py,sha256=9ogNTPHvsuxPf53snrEGFhEPFqcO1sth5PkPBrqAfng,1024
|
|
289
290
|
wbfdm/viewsets/configs/display/classifications.py,sha256=xolaLg6WDG7Dtwd3gQXWElmyH10GgaT03tL3a3PhKfE,5206
|
|
290
291
|
wbfdm/viewsets/configs/display/esg.py,sha256=gdo8vz5tW_0Maxd6YTE5ZYakcXm--f89lxwZIxvlIdI,3639
|
|
291
292
|
wbfdm/viewsets/configs/display/exchanges.py,sha256=uo-rh_20FjreGmqAiPbU0zTZ-I2CoV0bBC4uxX4hQbs,1946
|
|
293
|
+
wbfdm/viewsets/configs/display/financial_summary.py,sha256=K8CPV9uy0wsK-yLQIDTqMAXTb-W1-pUp6OCMwii7jYo,4738
|
|
292
294
|
wbfdm/viewsets/configs/display/instrument_lists.py,sha256=8-K3PK8AoI8POPo9aAc_3YtDjuFEByAK_I1jNuu0HzQ,5123
|
|
293
295
|
wbfdm/viewsets/configs/display/instrument_prices.py,sha256=hRXlx1eenm1_LTzWL5xVU1ai4cL2FtYtgg55kMqmFYA,6939
|
|
294
296
|
wbfdm/viewsets/configs/display/instrument_requests.py,sha256=Er5S2aVEwABgG79mdiyMTQUVueHEeqV5bAS01JMUQ-4,5128
|
|
295
|
-
wbfdm/viewsets/configs/display/instruments.py,sha256=
|
|
297
|
+
wbfdm/viewsets/configs/display/instruments.py,sha256=_T4YFpQyL1lT7N8fuqkoXhoYcjTPIxzXjZzxnUH-KTw,17088
|
|
296
298
|
wbfdm/viewsets/configs/display/instruments_relationships.py,sha256=BLAl2ZciWR5Waf3qPd1QQwdiupgIf_wGK6rmYe9byuY,2480
|
|
297
299
|
wbfdm/viewsets/configs/display/monthly_performances.py,sha256=ESsGTLhPmmXz3YjEWNjwaOjA1RNzVDKtJt3JILL2MMc,2973
|
|
298
300
|
wbfdm/viewsets/configs/display/officers.py,sha256=qUwN9HPSSlT85aUSFNY-30550P35j3RZCYqRPj_rFAs,654
|
|
@@ -329,9 +331,10 @@ wbfdm/viewsets/configs/titles/instruments_relationships.py,sha256=vpgQrXekD64P42
|
|
|
329
331
|
wbfdm/viewsets/configs/titles/market_data.py,sha256=YUVC4aFm1oAKFgn_JKqyq--PhXZn-FFRMoFWiB_-1dA,518
|
|
330
332
|
wbfdm/viewsets/configs/titles/prices.py,sha256=O43pAxiZsfRl6CwJX1hX6zABWKD0a9nQLSaywtNZEBM,382
|
|
331
333
|
wbfdm/viewsets/configs/titles/statement_with_estimates.py,sha256=ZL4K0hOM0dVBplu76toF4-U3VXBS31gyV3_eTGQYHjE,407
|
|
332
|
-
wbfdm/viewsets/financial_analysis/__init__.py,sha256=
|
|
334
|
+
wbfdm/viewsets/financial_analysis/__init__.py,sha256=cTTcvGnhz2Ep9RpkyUyTOpjTXJz7BWt1AGAMjiIY9C8,263
|
|
333
335
|
wbfdm/viewsets/financial_analysis/financial_metric_analysis.py,sha256=ZO3iUExFu5BUK8a_wRu9ldOvRKc70l8Jpdjl2_4hp7s,3315
|
|
334
336
|
wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py,sha256=YtsSJUnqHjTpiarZcqo14IhWDXF3UXc_zla-slDpqiU,3097
|
|
337
|
+
wbfdm/viewsets/financial_analysis/financial_summary.py,sha256=GHXIBZ6lHmFGxqAT6YMVB7f86BsGu6K--CbUECj0xfY,9245
|
|
335
338
|
wbfdm/viewsets/financial_analysis/statement_with_estimates.py,sha256=1GiSgKZR8iyNK6yJs79eLSt7bo60KQoTv4o3vBsc9xU,6098
|
|
336
339
|
wbfdm/viewsets/instruments/__init__.py,sha256=uydDdU6oZ6W2lgFkr3-cU7WZU7TeokXAA1J0xEgiLD0,2826
|
|
337
340
|
wbfdm/viewsets/instruments/classifications.py,sha256=pDQG9rlXUbc4LvWgVOGTir0Dcp02z-78AODNH-SjynA,11440
|
|
@@ -346,6 +349,6 @@ wbfdm/viewsets/statements/__init__.py,sha256=odxtFYUDICPmz8WCE3nx93EvKZLSPBEI4d7
|
|
|
346
349
|
wbfdm/viewsets/statements/statements.py,sha256=AVU5A7p63uWITEcMDl95iKyN-fBO5RwJlbxHrJZvz7o,4083
|
|
347
350
|
wbfdm/viewsets/technical_analysis/__init__.py,sha256=qtCIBg0uSiZeJq_1tEQFilnorMBkMe6uCMfqar6-cLE,77
|
|
348
351
|
wbfdm/viewsets/technical_analysis/monthly_performances.py,sha256=8VgafjW4efEJKTfBqUvAu-EG2eNawl2Cmh9QFt4sN8w,3973
|
|
349
|
-
wbfdm-1.
|
|
350
|
-
wbfdm-1.
|
|
351
|
-
wbfdm-1.
|
|
352
|
+
wbfdm-1.44.1.dist-info/METADATA,sha256=DI-CEs_MXLDyJDmT3IotMf4wBDpD9CVO8Y2_-ghaQfU,736
|
|
353
|
+
wbfdm-1.44.1.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
354
|
+
wbfdm-1.44.1.dist-info/RECORD,,
|
|
File without changes
|