wbfdm 2.2.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/__init__.py +2 -0
- wbfdm/admin/__init__.py +42 -0
- wbfdm/admin/classifications.py +39 -0
- wbfdm/admin/esg.py +23 -0
- wbfdm/admin/exchanges.py +53 -0
- wbfdm/admin/instrument_lists.py +23 -0
- wbfdm/admin/instrument_prices.py +62 -0
- wbfdm/admin/instrument_requests.py +33 -0
- wbfdm/admin/instruments.py +117 -0
- wbfdm/admin/instruments_relationships.py +25 -0
- wbfdm/admin/options.py +101 -0
- wbfdm/analysis/__init__.py +2 -0
- wbfdm/analysis/esg/__init__.py +0 -0
- wbfdm/analysis/esg/enums.py +82 -0
- wbfdm/analysis/esg/esg_analysis.py +217 -0
- wbfdm/analysis/esg/utils.py +13 -0
- wbfdm/analysis/financial_analysis/__init__.py +1 -0
- wbfdm/analysis/financial_analysis/financial_metric_analysis.py +88 -0
- wbfdm/analysis/financial_analysis/financial_ratio_analysis.py +125 -0
- wbfdm/analysis/financial_analysis/financial_statistics_analysis.py +271 -0
- wbfdm/analysis/financial_analysis/statement_with_estimates.py +558 -0
- wbfdm/analysis/financial_analysis/utils.py +316 -0
- wbfdm/analysis/technical_analysis/__init__.py +1 -0
- wbfdm/analysis/technical_analysis/technical_analysis.py +138 -0
- wbfdm/analysis/technical_analysis/traces.py +165 -0
- wbfdm/analysis/utils.py +32 -0
- wbfdm/apps.py +14 -0
- wbfdm/contrib/__init__.py +0 -0
- wbfdm/contrib/dsws/__init__.py +0 -0
- wbfdm/contrib/dsws/client.py +285 -0
- wbfdm/contrib/internal/__init__.py +0 -0
- wbfdm/contrib/internal/dataloaders/__init__.py +0 -0
- wbfdm/contrib/internal/dataloaders/market_data.py +87 -0
- wbfdm/contrib/metric/__init__.py +0 -0
- wbfdm/contrib/metric/admin/__init__.py +2 -0
- wbfdm/contrib/metric/admin/instruments.py +12 -0
- wbfdm/contrib/metric/admin/metrics.py +43 -0
- wbfdm/contrib/metric/apps.py +10 -0
- wbfdm/contrib/metric/backends/__init__.py +2 -0
- wbfdm/contrib/metric/backends/base.py +159 -0
- wbfdm/contrib/metric/backends/performances.py +265 -0
- wbfdm/contrib/metric/backends/statistics.py +182 -0
- wbfdm/contrib/metric/decorators.py +14 -0
- wbfdm/contrib/metric/dispatch.py +23 -0
- wbfdm/contrib/metric/dto.py +88 -0
- wbfdm/contrib/metric/exceptions.py +6 -0
- wbfdm/contrib/metric/factories.py +33 -0
- wbfdm/contrib/metric/filters.py +28 -0
- wbfdm/contrib/metric/migrations/0001_initial.py +88 -0
- wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py +26 -0
- wbfdm/contrib/metric/migrations/__init__.py +0 -0
- wbfdm/contrib/metric/models.py +180 -0
- wbfdm/contrib/metric/orchestrators.py +94 -0
- wbfdm/contrib/metric/registry.py +80 -0
- wbfdm/contrib/metric/serializers.py +44 -0
- wbfdm/contrib/metric/tasks.py +27 -0
- wbfdm/contrib/metric/tests/__init__.py +0 -0
- wbfdm/contrib/metric/tests/backends/__init__.py +0 -0
- wbfdm/contrib/metric/tests/backends/test_performances.py +152 -0
- wbfdm/contrib/metric/tests/backends/test_statistics.py +48 -0
- wbfdm/contrib/metric/tests/conftest.py +92 -0
- wbfdm/contrib/metric/tests/test_dto.py +73 -0
- wbfdm/contrib/metric/tests/test_models.py +72 -0
- wbfdm/contrib/metric/tests/test_tasks.py +24 -0
- wbfdm/contrib/metric/tests/test_viewsets.py +79 -0
- wbfdm/contrib/metric/urls.py +19 -0
- wbfdm/contrib/metric/viewsets/__init__.py +1 -0
- wbfdm/contrib/metric/viewsets/configs/__init__.py +1 -0
- wbfdm/contrib/metric/viewsets/configs/display.py +92 -0
- wbfdm/contrib/metric/viewsets/configs/menus.py +11 -0
- wbfdm/contrib/metric/viewsets/configs/utils.py +137 -0
- wbfdm/contrib/metric/viewsets/mixins.py +245 -0
- wbfdm/contrib/metric/viewsets/viewsets.py +40 -0
- wbfdm/contrib/msci/__init__.py +0 -0
- wbfdm/contrib/msci/client.py +92 -0
- wbfdm/contrib/msci/dataloaders/__init__.py +0 -0
- wbfdm/contrib/msci/dataloaders/esg.py +87 -0
- wbfdm/contrib/msci/dataloaders/esg_controversies.py +81 -0
- wbfdm/contrib/msci/sync.py +58 -0
- wbfdm/contrib/msci/tests/__init__.py +0 -0
- wbfdm/contrib/msci/tests/conftest.py +1 -0
- wbfdm/contrib/msci/tests/test_client.py +70 -0
- wbfdm/contrib/qa/__init__.py +0 -0
- wbfdm/contrib/qa/apps.py +22 -0
- wbfdm/contrib/qa/database_routers.py +25 -0
- wbfdm/contrib/qa/dataloaders/__init__.py +0 -0
- wbfdm/contrib/qa/dataloaders/adjustments.py +56 -0
- wbfdm/contrib/qa/dataloaders/corporate_actions.py +59 -0
- wbfdm/contrib/qa/dataloaders/financials.py +83 -0
- wbfdm/contrib/qa/dataloaders/market_data.py +117 -0
- wbfdm/contrib/qa/dataloaders/officers.py +59 -0
- wbfdm/contrib/qa/dataloaders/reporting_dates.py +67 -0
- wbfdm/contrib/qa/dataloaders/statements.py +267 -0
- wbfdm/contrib/qa/tasks.py +0 -0
- wbfdm/dataloaders/__init__.py +0 -0
- wbfdm/dataloaders/cache.py +129 -0
- wbfdm/dataloaders/protocols.py +112 -0
- wbfdm/dataloaders/proxies.py +201 -0
- wbfdm/dataloaders/types.py +209 -0
- wbfdm/dynamic_preferences_registry.py +45 -0
- wbfdm/enums.py +657 -0
- wbfdm/factories/__init__.py +13 -0
- wbfdm/factories/classifications.py +56 -0
- wbfdm/factories/controversies.py +27 -0
- wbfdm/factories/exchanges.py +21 -0
- wbfdm/factories/instrument_list.py +22 -0
- wbfdm/factories/instrument_prices.py +79 -0
- wbfdm/factories/instruments.py +63 -0
- wbfdm/factories/instruments_relationships.py +31 -0
- wbfdm/factories/options.py +66 -0
- wbfdm/figures/__init__.py +1 -0
- wbfdm/figures/financials/__init__.py +1 -0
- wbfdm/figures/financials/financial_analysis_charts.py +469 -0
- wbfdm/figures/financials/financials_charts.py +711 -0
- wbfdm/filters/__init__.py +31 -0
- wbfdm/filters/classifications.py +100 -0
- wbfdm/filters/exchanges.py +22 -0
- wbfdm/filters/financials.py +95 -0
- wbfdm/filters/financials_analysis.py +119 -0
- wbfdm/filters/instrument_prices.py +112 -0
- wbfdm/filters/instruments.py +198 -0
- wbfdm/filters/utils.py +44 -0
- wbfdm/import_export/__init__.py +0 -0
- wbfdm/import_export/backends/__init__.py +0 -0
- wbfdm/import_export/backends/cbinsights/__init__.py +2 -0
- wbfdm/import_export/backends/cbinsights/deals.py +44 -0
- wbfdm/import_export/backends/cbinsights/equities.py +41 -0
- wbfdm/import_export/backends/cbinsights/mixin.py +15 -0
- wbfdm/import_export/backends/cbinsights/utils/__init__.py +0 -0
- wbfdm/import_export/backends/cbinsights/utils/classifications.py +4150 -0
- wbfdm/import_export/backends/cbinsights/utils/client.py +217 -0
- wbfdm/import_export/backends/refinitiv/__init__.py +5 -0
- wbfdm/import_export/backends/refinitiv/daily_fundamental.py +36 -0
- wbfdm/import_export/backends/refinitiv/fiscal_period.py +63 -0
- wbfdm/import_export/backends/refinitiv/forecast.py +178 -0
- wbfdm/import_export/backends/refinitiv/fundamental.py +103 -0
- wbfdm/import_export/backends/refinitiv/geographic_segment.py +32 -0
- wbfdm/import_export/backends/refinitiv/instrument.py +55 -0
- wbfdm/import_export/backends/refinitiv/instrument_price.py +77 -0
- wbfdm/import_export/backends/refinitiv/mixin.py +29 -0
- wbfdm/import_export/backends/refinitiv/utils/__init__.py +1 -0
- wbfdm/import_export/backends/refinitiv/utils/controller.py +182 -0
- wbfdm/import_export/handlers/__init__.py +0 -0
- wbfdm/import_export/handlers/instrument.py +253 -0
- wbfdm/import_export/handlers/instrument_list.py +101 -0
- wbfdm/import_export/handlers/instrument_price.py +71 -0
- wbfdm/import_export/handlers/option.py +54 -0
- wbfdm/import_export/handlers/private_equities.py +49 -0
- wbfdm/import_export/parsers/__init__.py +0 -0
- wbfdm/import_export/parsers/cbinsights/__init__.py +0 -0
- wbfdm/import_export/parsers/cbinsights/deals.py +39 -0
- wbfdm/import_export/parsers/cbinsights/equities.py +56 -0
- wbfdm/import_export/parsers/cbinsights/fundamentals.py +45 -0
- wbfdm/import_export/parsers/refinitiv/__init__.py +0 -0
- wbfdm/import_export/parsers/refinitiv/daily_fundamental.py +7 -0
- wbfdm/import_export/parsers/refinitiv/forecast.py +7 -0
- wbfdm/import_export/parsers/refinitiv/fundamental.py +9 -0
- wbfdm/import_export/parsers/refinitiv/geographic_segment.py +7 -0
- wbfdm/import_export/parsers/refinitiv/instrument.py +75 -0
- wbfdm/import_export/parsers/refinitiv/instrument_price.py +26 -0
- wbfdm/import_export/parsers/refinitiv/utils.py +96 -0
- wbfdm/import_export/resources/__init__.py +0 -0
- wbfdm/import_export/resources/classification.py +23 -0
- wbfdm/import_export/resources/instrument_prices.py +33 -0
- wbfdm/import_export/resources/instruments.py +176 -0
- wbfdm/jinja2.py +7 -0
- wbfdm/management/__init__.py +30 -0
- wbfdm/menu.py +11 -0
- wbfdm/migrations/0001_initial.py +71 -0
- wbfdm/migrations/0002_rename_statements_instrumentlookup_financials_and_more.py +144 -0
- wbfdm/migrations/0003_instrument_estimate_backend_and_more.py +34 -0
- wbfdm/migrations/0004_rename_financials_instrumentlookup_statements_and_more.py +86 -0
- wbfdm/migrations/0005_instrument_corporate_action_backend.py +29 -0
- wbfdm/migrations/0006_instrument_officer_backend.py +29 -0
- wbfdm/migrations/0007_instrument_country_instrument_currency_and_more.py +117 -0
- wbfdm/migrations/0008_controversy.py +75 -0
- wbfdm/migrations/0009_alter_controversy_flag_alter_controversy_initiated_and_more.py +85 -0
- wbfdm/migrations/0010_classification_classificationgroup_deal_exchange_and_more.py +1299 -0
- wbfdm/migrations/0011_delete_instrumentlookup_instrument_corporate_actions_and_more.py +169 -0
- wbfdm/migrations/0012_instrumentprice_created_instrumentprice_modified.py +564 -0
- wbfdm/migrations/0013_instrument_is_investable_universe_and_more.py +199 -0
- wbfdm/migrations/0014_alter_controversy_instrument.py +22 -0
- wbfdm/migrations/0015_instrument_instrument_investible_index.py +16 -0
- wbfdm/migrations/0016_instrumenttype_name_repr.py +18 -0
- wbfdm/migrations/0017_instrument_instrument_security_index.py +16 -0
- wbfdm/migrations/0018_instrument_instrument_level_index.py +20 -0
- wbfdm/migrations/0019_alter_controversy_source.py +17 -0
- wbfdm/migrations/0020_optionaggregate_option_and_more.py +249 -0
- wbfdm/migrations/0021_delete_instrumentdailystatistics.py +15 -0
- wbfdm/migrations/0022_instrument_cusip_option_open_interest_20d_and_more.py +91 -0
- wbfdm/migrations/0023_instrument_unique_ric_instrument_unique_rmc_and_more.py +53 -0
- wbfdm/migrations/0024_option_open_interest_10d_option_volume_10d_and_more.py +36 -0
- wbfdm/migrations/0025_instrument_is_primary_and_more.py +29 -0
- wbfdm/migrations/0026_instrument_is_cash_equivalent.py +30 -0
- wbfdm/migrations/0027_remove_instrument_unique_ric_and_more.py +100 -0
- wbfdm/migrations/__init__.py +0 -0
- wbfdm/models/__init__.py +4 -0
- wbfdm/models/esg/__init__.py +1 -0
- wbfdm/models/esg/controversies.py +81 -0
- wbfdm/models/exchanges/__init__.py +1 -0
- wbfdm/models/exchanges/exchanges.py +223 -0
- wbfdm/models/fields.py +117 -0
- wbfdm/models/fk_fields.py +403 -0
- wbfdm/models/indicators.py +0 -0
- wbfdm/models/instruments/__init__.py +19 -0
- wbfdm/models/instruments/classifications.py +265 -0
- wbfdm/models/instruments/instrument_lists.py +120 -0
- wbfdm/models/instruments/instrument_prices.py +540 -0
- wbfdm/models/instruments/instrument_relationships.py +251 -0
- wbfdm/models/instruments/instrument_requests.py +196 -0
- wbfdm/models/instruments/instruments.py +991 -0
- wbfdm/models/instruments/llm/__init__.py +1 -0
- wbfdm/models/instruments/llm/create_instrument_news_relationships.py +78 -0
- wbfdm/models/instruments/mixin/__init__.py +0 -0
- wbfdm/models/instruments/mixin/financials_computed.py +804 -0
- wbfdm/models/instruments/mixin/financials_serializer_fields.py +1407 -0
- wbfdm/models/instruments/mixin/instruments.py +294 -0
- wbfdm/models/instruments/options.py +225 -0
- wbfdm/models/instruments/private_equities.py +59 -0
- wbfdm/models/instruments/querysets.py +73 -0
- wbfdm/models/instruments/utils.py +41 -0
- wbfdm/preferences.py +21 -0
- wbfdm/serializers/__init__.py +4 -0
- wbfdm/serializers/esg.py +36 -0
- wbfdm/serializers/exchanges.py +39 -0
- wbfdm/serializers/instruments/__init__.py +37 -0
- wbfdm/serializers/instruments/classifications.py +139 -0
- wbfdm/serializers/instruments/instrument_lists.py +61 -0
- wbfdm/serializers/instruments/instrument_prices.py +73 -0
- wbfdm/serializers/instruments/instrument_relationships.py +170 -0
- wbfdm/serializers/instruments/instrument_requests.py +61 -0
- wbfdm/serializers/instruments/instruments.py +274 -0
- wbfdm/serializers/instruments/mixins.py +104 -0
- wbfdm/serializers/officers.py +20 -0
- wbfdm/signals.py +7 -0
- wbfdm/sync/__init__.py +0 -0
- wbfdm/sync/abstract.py +31 -0
- wbfdm/sync/runner.py +22 -0
- wbfdm/tasks.py +69 -0
- wbfdm/tests/__init__.py +0 -0
- wbfdm/tests/analysis/__init__.py +0 -0
- wbfdm/tests/analysis/financial_analysis/__init__.py +0 -0
- wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +392 -0
- wbfdm/tests/analysis/financial_analysis/test_utils.py +322 -0
- wbfdm/tests/analysis/test_esg.py +159 -0
- wbfdm/tests/conftest.py +92 -0
- wbfdm/tests/dataloaders/__init__.py +0 -0
- wbfdm/tests/dataloaders/test_cache.py +73 -0
- wbfdm/tests/models/__init__.py +0 -0
- wbfdm/tests/models/test_classifications.py +99 -0
- wbfdm/tests/models/test_exchanges.py +7 -0
- wbfdm/tests/models/test_instrument_list.py +117 -0
- wbfdm/tests/models/test_instrument_prices.py +306 -0
- wbfdm/tests/models/test_instruments.py +202 -0
- wbfdm/tests/models/test_merge.py +99 -0
- wbfdm/tests/models/test_options.py +69 -0
- wbfdm/tests/test_tasks.py +6 -0
- wbfdm/tests/tests.py +10 -0
- wbfdm/urls.py +222 -0
- wbfdm/utils.py +54 -0
- wbfdm/viewsets/__init__.py +10 -0
- wbfdm/viewsets/configs/__init__.py +5 -0
- wbfdm/viewsets/configs/buttons/__init__.py +8 -0
- wbfdm/viewsets/configs/buttons/classifications.py +23 -0
- wbfdm/viewsets/configs/buttons/exchanges.py +9 -0
- wbfdm/viewsets/configs/buttons/instrument_prices.py +49 -0
- wbfdm/viewsets/configs/buttons/instruments.py +283 -0
- wbfdm/viewsets/configs/display/__init__.py +22 -0
- wbfdm/viewsets/configs/display/classifications.py +138 -0
- wbfdm/viewsets/configs/display/esg.py +75 -0
- wbfdm/viewsets/configs/display/exchanges.py +42 -0
- wbfdm/viewsets/configs/display/instrument_lists.py +137 -0
- wbfdm/viewsets/configs/display/instrument_prices.py +199 -0
- wbfdm/viewsets/configs/display/instrument_requests.py +116 -0
- wbfdm/viewsets/configs/display/instruments.py +618 -0
- wbfdm/viewsets/configs/display/instruments_relationships.py +65 -0
- wbfdm/viewsets/configs/display/monthly_performances.py +72 -0
- wbfdm/viewsets/configs/display/officers.py +16 -0
- wbfdm/viewsets/configs/display/prices.py +21 -0
- wbfdm/viewsets/configs/display/statement_with_estimates.py +101 -0
- wbfdm/viewsets/configs/display/statements.py +48 -0
- wbfdm/viewsets/configs/endpoints/__init__.py +41 -0
- wbfdm/viewsets/configs/endpoints/classifications.py +87 -0
- wbfdm/viewsets/configs/endpoints/esg.py +20 -0
- wbfdm/viewsets/configs/endpoints/exchanges.py +6 -0
- wbfdm/viewsets/configs/endpoints/financials_analysis.py +65 -0
- wbfdm/viewsets/configs/endpoints/instrument_lists.py +38 -0
- wbfdm/viewsets/configs/endpoints/instrument_prices.py +51 -0
- wbfdm/viewsets/configs/endpoints/instrument_requests.py +20 -0
- wbfdm/viewsets/configs/endpoints/instruments.py +13 -0
- wbfdm/viewsets/configs/endpoints/instruments_relationships.py +31 -0
- wbfdm/viewsets/configs/endpoints/statements.py +6 -0
- wbfdm/viewsets/configs/menus/__init__.py +9 -0
- wbfdm/viewsets/configs/menus/classifications.py +19 -0
- wbfdm/viewsets/configs/menus/exchanges.py +10 -0
- wbfdm/viewsets/configs/menus/instrument_lists.py +10 -0
- wbfdm/viewsets/configs/menus/instruments.py +20 -0
- wbfdm/viewsets/configs/menus/instruments_relationships.py +33 -0
- wbfdm/viewsets/configs/titles/__init__.py +42 -0
- wbfdm/viewsets/configs/titles/classifications.py +79 -0
- wbfdm/viewsets/configs/titles/esg.py +11 -0
- wbfdm/viewsets/configs/titles/exchanges.py +12 -0
- wbfdm/viewsets/configs/titles/financial_ratio_analysis.py +6 -0
- wbfdm/viewsets/configs/titles/financials_analysis.py +50 -0
- wbfdm/viewsets/configs/titles/instrument_prices.py +50 -0
- wbfdm/viewsets/configs/titles/instrument_requests.py +16 -0
- wbfdm/viewsets/configs/titles/instruments.py +31 -0
- wbfdm/viewsets/configs/titles/instruments_relationships.py +21 -0
- wbfdm/viewsets/configs/titles/market_data.py +13 -0
- wbfdm/viewsets/configs/titles/prices.py +15 -0
- wbfdm/viewsets/configs/titles/statement_with_estimates.py +10 -0
- wbfdm/viewsets/esg.py +72 -0
- wbfdm/viewsets/exchanges.py +63 -0
- wbfdm/viewsets/financial_analysis/__init__.py +3 -0
- wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +85 -0
- wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +85 -0
- wbfdm/viewsets/financial_analysis/statement_with_estimates.py +145 -0
- wbfdm/viewsets/instruments/__init__.py +80 -0
- wbfdm/viewsets/instruments/classifications.py +279 -0
- wbfdm/viewsets/instruments/financials_analysis.py +614 -0
- wbfdm/viewsets/instruments/instrument_lists.py +77 -0
- wbfdm/viewsets/instruments/instrument_prices.py +542 -0
- wbfdm/viewsets/instruments/instrument_requests.py +51 -0
- wbfdm/viewsets/instruments/instruments.py +106 -0
- wbfdm/viewsets/instruments/instruments_relationships.py +235 -0
- wbfdm/viewsets/instruments/utils.py +27 -0
- wbfdm/viewsets/market_data.py +172 -0
- wbfdm/viewsets/mixins.py +9 -0
- wbfdm/viewsets/officers.py +27 -0
- wbfdm/viewsets/prices.py +62 -0
- wbfdm/viewsets/statements/__init__.py +1 -0
- wbfdm/viewsets/statements/statements.py +100 -0
- wbfdm/viewsets/technical_analysis/__init__.py +1 -0
- wbfdm/viewsets/technical_analysis/monthly_performances.py +93 -0
- wbfdm-2.2.1.dist-info/METADATA +15 -0
- wbfdm-2.2.1.dist-info/RECORD +337 -0
- wbfdm-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from django.utils.functional import cached_property
|
|
3
|
+
from rest_framework.exceptions import ParseError
|
|
4
|
+
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
5
|
+
from wbcore.pandas import fields as pf
|
|
6
|
+
from wbcore.pandas.utils import override_number_to_percent
|
|
7
|
+
from wbcore.serializers.fields.types import DisplayMode
|
|
8
|
+
from wbfdm.analysis.financial_analysis.financial_metric_analysis import (
|
|
9
|
+
financial_metric_estimate_analysis,
|
|
10
|
+
financial_metric_growths,
|
|
11
|
+
)
|
|
12
|
+
from wbfdm.enums import Financial
|
|
13
|
+
from wbfdm.filters import GroupKeyFinancialsFilterSet
|
|
14
|
+
from wbfdm.models.instruments import Instrument
|
|
15
|
+
from wbfdm.viewsets.configs.display.statement_with_estimates import (
|
|
16
|
+
StatementWithEstimatesDisplayViewConfig,
|
|
17
|
+
)
|
|
18
|
+
from wbfdm.viewsets.configs.endpoints.statements import StatementsEndpointViewConfig
|
|
19
|
+
from wbfdm.viewsets.configs.titles.statement_with_estimates import (
|
|
20
|
+
StatementTitleViewConfig,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from ..mixins import InstrumentMixin
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FinancialMetricAnalysisPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
27
|
+
queryset = Instrument.objects.none()
|
|
28
|
+
display_config_class = StatementWithEstimatesDisplayViewConfig
|
|
29
|
+
endpoint_config_class = StatementsEndpointViewConfig
|
|
30
|
+
title_config_class = StatementTitleViewConfig
|
|
31
|
+
filterset_class = GroupKeyFinancialsFilterSet
|
|
32
|
+
|
|
33
|
+
def get_queryset(self):
|
|
34
|
+
return Instrument.objects.filter(id=self.instrument.id)
|
|
35
|
+
|
|
36
|
+
def get_pandas_fields(self, request):
|
|
37
|
+
return pf.PandasFields(
|
|
38
|
+
fields=[
|
|
39
|
+
*[pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED) for field in self.columns],
|
|
40
|
+
pf.PKField(key="id", label="ID"),
|
|
41
|
+
pf.CharField(key="financial", label="Financial"),
|
|
42
|
+
pf.JsonField(key="_overwrites", label="Overwrites"),
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def get_dataframe(self, request, queryset, **kwargs):
|
|
47
|
+
if group_keys := request.GET.get("group_keys"):
|
|
48
|
+
try:
|
|
49
|
+
financial = Financial(group_keys.lower())
|
|
50
|
+
except ValueError:
|
|
51
|
+
raise ParseError()
|
|
52
|
+
df, self._estimate_mapping, self._columns = financial_metric_estimate_analysis(
|
|
53
|
+
queryset.first().id, financial
|
|
54
|
+
)
|
|
55
|
+
empty_row = pd.Series([None], dtype="float64", name="empty_row")
|
|
56
|
+
df_growth = financial_metric_growths(queryset.first().id, financial)
|
|
57
|
+
|
|
58
|
+
df = pd.concat([df_growth, df, empty_row]).dropna(how="all")
|
|
59
|
+
if "financial" in df.columns:
|
|
60
|
+
override_number_to_percent(df, df["financial"].str.contains("(%)"))
|
|
61
|
+
|
|
62
|
+
df = df.rename(columns={"index": "id"})
|
|
63
|
+
return df
|
|
64
|
+
|
|
65
|
+
return pd.DataFrame()
|
|
66
|
+
|
|
67
|
+
@cached_property
|
|
68
|
+
def columns(self):
|
|
69
|
+
if not hasattr(self, "_columns"):
|
|
70
|
+
self.get_dataframe(self.request, self.get_queryset())
|
|
71
|
+
return self._columns
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def year_columns(self):
|
|
75
|
+
yield from filter(lambda col: "Y" in col, self.columns)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def interim_columns(self):
|
|
79
|
+
yield from filter(lambda col: "Y" not in col, self.columns)
|
|
80
|
+
|
|
81
|
+
@cached_property
|
|
82
|
+
def estimate_mapping(self):
|
|
83
|
+
if not hasattr(self, "_estimate_mapping"):
|
|
84
|
+
self.get_dataframe(self.request, self.get_queryset())
|
|
85
|
+
return self._estimate_mapping
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import plotly.express as px
|
|
6
|
+
import plotly.graph_objects as go
|
|
7
|
+
from wbcore import viewsets
|
|
8
|
+
from wbcore.utils.date import get_date_interval_from_request
|
|
9
|
+
from wbfdm.analysis.financial_analysis.financial_ratio_analysis import (
|
|
10
|
+
FinancialRatio,
|
|
11
|
+
get_financial_ratios,
|
|
12
|
+
)
|
|
13
|
+
from wbfdm.filters import FinancialRatioFilterSet
|
|
14
|
+
from wbfdm.models.instruments import Instrument
|
|
15
|
+
|
|
16
|
+
from ..configs import ValuationRatioChartTitleConfig
|
|
17
|
+
from ..mixins import InstrumentMixin
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ValuationRatioChartViewSet(InstrumentMixin, viewsets.ChartViewSet):
|
|
21
|
+
queryset = Instrument.objects.all()
|
|
22
|
+
filterset_class = FinancialRatioFilterSet
|
|
23
|
+
title_config_class = ValuationRatioChartTitleConfig
|
|
24
|
+
|
|
25
|
+
def get_queryset(self):
|
|
26
|
+
return super().get_queryset().filter(id=self.instrument.id)
|
|
27
|
+
|
|
28
|
+
def get_plotly(self, queryset):
|
|
29
|
+
pd.options.plotting.backend = "plotly"
|
|
30
|
+
|
|
31
|
+
ratios = [FinancialRatio.PE, FinancialRatio.PS, FinancialRatio.PB, FinancialRatio.PFCF]
|
|
32
|
+
ttm = False if self.request.GET.get("ttm", "true") == "false" else True
|
|
33
|
+
start, end = get_date_interval_from_request(self.request, date_range_fieldname="period")
|
|
34
|
+
|
|
35
|
+
df = get_financial_ratios(
|
|
36
|
+
self.instrument.id,
|
|
37
|
+
ratios,
|
|
38
|
+
from_date=start,
|
|
39
|
+
to_date=end,
|
|
40
|
+
ttm=ttm,
|
|
41
|
+
)
|
|
42
|
+
fig = go.Figure()
|
|
43
|
+
colors = iter(px.colors.qualitative.T10)
|
|
44
|
+
|
|
45
|
+
for ratio, color in zip(ratios, colors):
|
|
46
|
+
with suppress(AttributeError):
|
|
47
|
+
series = getattr(df, ratio.value)
|
|
48
|
+
|
|
49
|
+
fig.add_trace(
|
|
50
|
+
go.Scatter(
|
|
51
|
+
x=series.index,
|
|
52
|
+
y=series,
|
|
53
|
+
line=dict(color=color),
|
|
54
|
+
name=f"{ratio.label} {'TTM' if ttm else 'FTM'}",
|
|
55
|
+
legendgroup=f"{ratio.label} {'TTM' if ttm else 'FTM'}",
|
|
56
|
+
visible="legendonly" if ratio != FinancialRatio.PE else True,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# fig = df.plot()
|
|
61
|
+
fig.update_layout(
|
|
62
|
+
template="plotly_white",
|
|
63
|
+
legend=dict(x=0.02, y=0.98, bgcolor="rgba(0,0,0,0)"),
|
|
64
|
+
margin=dict(l=0, r=0, t=0, b=0),
|
|
65
|
+
showlegend=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
for i, d in enumerate(fig.data):
|
|
69
|
+
with suppress(AttributeError, IndexError, TypeError, OverflowError):
|
|
70
|
+
if (v := d.y[-1]) and not np.isnan(v):
|
|
71
|
+
fig.add_scatter(
|
|
72
|
+
x=[d.x[-1]],
|
|
73
|
+
y=[v],
|
|
74
|
+
name=d.name,
|
|
75
|
+
mode="markers+text",
|
|
76
|
+
text=round(v),
|
|
77
|
+
textfont=dict(color=d.line.color),
|
|
78
|
+
textposition="middle right",
|
|
79
|
+
marker=dict(color=d.line.color, size=12, symbol="circle"),
|
|
80
|
+
legendgroup=d.name,
|
|
81
|
+
showlegend=False,
|
|
82
|
+
visible="legendonly" if i != 0 else True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return fig
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.contrib.messages import warning
|
|
6
|
+
from django.utils.functional import cached_property
|
|
7
|
+
from wbcore.cache.decorators import cache_table
|
|
8
|
+
from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
|
|
9
|
+
from wbcore.pandas import fields as pf
|
|
10
|
+
from wbcore.serializers.fields.types import DisplayMode
|
|
11
|
+
from wbcore.utils.date import get_next_day_timedelta
|
|
12
|
+
from wbfdm.analysis.financial_analysis.statement_with_estimates import (
|
|
13
|
+
StatementWithEstimates,
|
|
14
|
+
)
|
|
15
|
+
from wbfdm.enums import CalendarType
|
|
16
|
+
from wbfdm.filters import StatementWithEstimateFilter
|
|
17
|
+
from wbfdm.models.instruments import Instrument
|
|
18
|
+
from wbfdm.viewsets.configs.display.statement_with_estimates import (
|
|
19
|
+
StatementWithEstimatesDisplayViewConfig,
|
|
20
|
+
)
|
|
21
|
+
from wbfdm.viewsets.configs.endpoints.statements import StatementsEndpointViewConfig
|
|
22
|
+
from wbfdm.viewsets.configs.titles.statement_with_estimates import (
|
|
23
|
+
StatementTitleViewConfig,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from ..mixins import InstrumentMixin
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cache_table(
|
|
30
|
+
timeout=lambda view: get_next_day_timedelta(),
|
|
31
|
+
key_prefix=lambda view: f"_{view.instrument.id}_{view.financial_analysis_key}_{view.calendar_type}",
|
|
32
|
+
)
|
|
33
|
+
class StatementWithEstimatesPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
|
|
34
|
+
queryset = Instrument.objects.none()
|
|
35
|
+
display_config_class = StatementWithEstimatesDisplayViewConfig
|
|
36
|
+
endpoint_config_class = StatementsEndpointViewConfig
|
|
37
|
+
title_config_class = StatementTitleViewConfig
|
|
38
|
+
filterset_class = StatementWithEstimateFilter
|
|
39
|
+
|
|
40
|
+
financial_analysis_mapping = {
|
|
41
|
+
"income": ("income_statement_with_estimate", "Income Statement"),
|
|
42
|
+
"balancesheet": ("balance_sheet_with_estimate", "Balance Sheet"),
|
|
43
|
+
"cashflow": ("cash_flow_statement_with_estimate", "Cash Flow Statement"),
|
|
44
|
+
"ratios": ("ratios_with_estimate", "Ratios"),
|
|
45
|
+
"summary": ("summary_with_estimate", "Summary"),
|
|
46
|
+
"margins": ("margins_with_estimates", "Margins"),
|
|
47
|
+
"cashflow-ratios": ("cashflow_ratios_with_estimates", "Cashflow Ratios"),
|
|
48
|
+
"asset-turnover-ratios": ("asset_turnover_with_estimates", "Asset-Turnover Ratios"),
|
|
49
|
+
"credit": ("credit_with_estimates", "Credit"),
|
|
50
|
+
"long-term-solvency": ("long_term_solvency_with_estimates", "Long-term Solvency"),
|
|
51
|
+
"short-term-liquidity": ("short_term_liquidity_with_estimates", "Short-term Liquidity"),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def get_queryset(self):
|
|
55
|
+
return Instrument.objects.filter(id=self.instrument.id)
|
|
56
|
+
|
|
57
|
+
def get_pandas_fields(self, request):
|
|
58
|
+
return pf.PandasFields(
|
|
59
|
+
fields=[
|
|
60
|
+
*[pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED) for field in self.columns],
|
|
61
|
+
pf.PKField(key="id", label="ID"),
|
|
62
|
+
pf.CharField(key="financial", label="Financial"),
|
|
63
|
+
pf.CharField(key="_group_key", label="Group Key"),
|
|
64
|
+
pf.JsonField(key="_overwrites", label="Overwrites"),
|
|
65
|
+
pf.SparklineField(key="progress", label="Yearly Trend"),
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def get_dataframe(self, request, queryset, **kwargs):
|
|
70
|
+
statement_with_estimate = StatementWithEstimates(self.instrument, calendar_type=self.calendar_type)
|
|
71
|
+
financial_analysis_result = getattr(
|
|
72
|
+
statement_with_estimate, self.financial_analysis_mapping[self.financial_analysis_key][0]
|
|
73
|
+
)
|
|
74
|
+
df = financial_analysis_result.formatted_df
|
|
75
|
+
|
|
76
|
+
year_columns = list(filter(lambda col: "Y" in col, df.columns))
|
|
77
|
+
if year_columns:
|
|
78
|
+
df["progress"] = df[year_columns].replace([np.inf, -np.inf, np.nan], None).apply(list, axis=1)
|
|
79
|
+
|
|
80
|
+
self.extra_cache_kwargs["_estimate_mapping"] = financial_analysis_result.estimated_mapping
|
|
81
|
+
self.extra_cache_kwargs["_columns"] = df.columns
|
|
82
|
+
self.extra_cache_kwargs["_errors"] = financial_analysis_result.errors
|
|
83
|
+
|
|
84
|
+
if financial_analysis_result.errors.get("duplicated_interims", []) and not settings.DEBUG:
|
|
85
|
+
with suppress(ModuleNotFoundError):
|
|
86
|
+
from sentry_sdk import capture_message
|
|
87
|
+
|
|
88
|
+
capture_message(
|
|
89
|
+
f"Duplicate index detected for instrument {self.instrument} (id: {self.instrument.id})",
|
|
90
|
+
level="error",
|
|
91
|
+
)
|
|
92
|
+
return df
|
|
93
|
+
|
|
94
|
+
def add_messages(self, request, instance=None, **kwargs):
|
|
95
|
+
if self.errors:
|
|
96
|
+
message = """
|
|
97
|
+
<p>While gathering the financial data, we detected the following issues from the data vendor:</p>
|
|
98
|
+
<ul>
|
|
99
|
+
"""
|
|
100
|
+
if duplicated_interim := self.errors.get("duplicated_interim", []):
|
|
101
|
+
message += f"""
|
|
102
|
+
<li>Duplicated data for for interim period <strong>{', '.join(duplicated_interim)}</strong>
|
|
103
|
+
<ul>
|
|
104
|
+
<li>First available data was used and the rest was ignored
|
|
105
|
+
</li>
|
|
106
|
+
</ul>
|
|
107
|
+
</li>
|
|
108
|
+
"""
|
|
109
|
+
if missing_data := self.errors.get("missing_data", []):
|
|
110
|
+
for error in missing_data:
|
|
111
|
+
message += f"<li>{error}</li>"
|
|
112
|
+
message += "</ul>"
|
|
113
|
+
warning(request, message, extra_tags="auto_close=0")
|
|
114
|
+
|
|
115
|
+
# Cached attributes as properties
|
|
116
|
+
@cached_property
|
|
117
|
+
def calendar_type(self):
|
|
118
|
+
return CalendarType(self.request.GET.get("calendar_type", CalendarType.FISCAL.value))
|
|
119
|
+
|
|
120
|
+
@cached_property
|
|
121
|
+
def financial_analysis_key(self):
|
|
122
|
+
return self.kwargs.get("statement", "income")
|
|
123
|
+
|
|
124
|
+
@cached_property
|
|
125
|
+
def estimate_mapping(self):
|
|
126
|
+
return getattr(self, "_estimate_mapping", {})
|
|
127
|
+
|
|
128
|
+
@cached_property
|
|
129
|
+
def errors(self):
|
|
130
|
+
return getattr(self, "_errors", {})
|
|
131
|
+
|
|
132
|
+
@cached_property
|
|
133
|
+
def columns(self):
|
|
134
|
+
return self.df.columns
|
|
135
|
+
|
|
136
|
+
def get_ordering_fields(self):
|
|
137
|
+
return self.columns
|
|
138
|
+
|
|
139
|
+
@cached_property
|
|
140
|
+
def year_columns(self):
|
|
141
|
+
return list(filter(lambda col: "Y" in col, self.columns))
|
|
142
|
+
|
|
143
|
+
@cached_property
|
|
144
|
+
def interim_columns(self):
|
|
145
|
+
return list(filter(lambda col: "Y" not in col, self.columns))
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from wbcore.utils.importlib import import_from_dotted_path
|
|
3
|
+
from .instruments import (
|
|
4
|
+
InstrumentModelViewSet,
|
|
5
|
+
InstrumentRepresentationViewSet,
|
|
6
|
+
InstrumentTypeRepresentationViewSet,
|
|
7
|
+
ChildrenInstrumentModelViewSet,
|
|
8
|
+
)
|
|
9
|
+
from .instrument_prices import (
|
|
10
|
+
InstrumentPriceModelViewSet,
|
|
11
|
+
InstrumentPriceInstrumentModelViewSet,
|
|
12
|
+
InstrumentPriceInstrumentStatisticsChartView,
|
|
13
|
+
FinancialStatisticsInstrumentPandasView,
|
|
14
|
+
InstrumentPriceInstrumentDistributionReturnsChartView,
|
|
15
|
+
BestAndWorstReturnsInstrumentPandasView,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .classifications import (
|
|
19
|
+
ClassificationClassificationGroupModelViewSet,
|
|
20
|
+
ClassificationGroupModelViewSet,
|
|
21
|
+
ClassificationGroupRepresentationViewSet,
|
|
22
|
+
ClassificationIcicleChartView,
|
|
23
|
+
ClassificationInstrumentThroughInstrumentModelViewSet,
|
|
24
|
+
ClassificationRepresentationViewSet,
|
|
25
|
+
ClassificationTreeChartView,
|
|
26
|
+
InstrumentClassificationThroughInstrumentModelViewSet,
|
|
27
|
+
InstrumentClassificationThroughModelViewSet,
|
|
28
|
+
)
|
|
29
|
+
from .financials_analysis import (
|
|
30
|
+
CashFlowAnalysisInstrumentBarChartViewSet,
|
|
31
|
+
CashFlowAnalysisInstrumentTableViewSet,
|
|
32
|
+
EarningsInstrumentChartViewSet,
|
|
33
|
+
FinancialsGraphInstrumentChartViewSet,
|
|
34
|
+
NetDebtAndEbitdaInstrumentChartViewSet,
|
|
35
|
+
ProfitabilityRatiosInstrumentChartViewSet,
|
|
36
|
+
SummaryTableInstrumentChartViewSet,
|
|
37
|
+
ValuationRatiosChartView,
|
|
38
|
+
)
|
|
39
|
+
from .instrument_lists import (
|
|
40
|
+
InstrumentListModelViewSet,
|
|
41
|
+
InstrumentListRepresentationModelViewSet,
|
|
42
|
+
InstrumentListThroughModelViewSet,
|
|
43
|
+
InstrumentListThroughModelInstrumentListViewSet,
|
|
44
|
+
InstrumentListThroughModelInstrumentViewSet,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
from .instrument_requests import (
|
|
48
|
+
InstrumentRequestModelViewSet,
|
|
49
|
+
InstrumentRequestRepresentationViewSet,
|
|
50
|
+
)
|
|
51
|
+
from .instruments_relationships import (
|
|
52
|
+
InstrumentClassificationRelatedInstrumentModelViewSet,
|
|
53
|
+
InstrumentClassificationRelatedInstrumentRepresentationViewSet,
|
|
54
|
+
InstrumentFavoriteGroupModelViewSet,
|
|
55
|
+
InstrumentFavoriteGroupRepresentationViewSet,
|
|
56
|
+
RelatedInstrumentThroughInstrumentModelViewSet,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
ClassificationModelViewSet = import_from_dotted_path(
|
|
61
|
+
getattr(
|
|
62
|
+
settings,
|
|
63
|
+
"DEFAULT_CLASSIFICATION_MODEL_VIEWSET",
|
|
64
|
+
"wbfdm.viewsets.instruments.classifications.ClassificationModelViewSet",
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
ChildClassificationParentClassificationModelViewSet = import_from_dotted_path(
|
|
68
|
+
getattr(
|
|
69
|
+
settings,
|
|
70
|
+
"DEFAULT_PARENT_CLASSIFICATION_MODEL_VIEWSET",
|
|
71
|
+
"wbfdm.viewsets.instruments.classifications.ChildClassificationParentClassificationModelViewSet",
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
ClassifiedInstrumentModelViewSet = import_from_dotted_path(
|
|
75
|
+
getattr(
|
|
76
|
+
settings,
|
|
77
|
+
"DEFAULT_CLASSIFICATION_INSTRUMENT_MODEL_VIEWSET",
|
|
78
|
+
"wbfdm.viewsets.instruments.instruments_relationships.ClassifiedInstrumentModelViewSet",
|
|
79
|
+
)
|
|
80
|
+
)
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import plotly.express as px
|
|
3
|
+
import plotly.graph_objects as go
|
|
4
|
+
from django.db.models import Count, Value
|
|
5
|
+
from django.db.models.expressions import OuterRef, Subquery
|
|
6
|
+
from django.db.models.functions import Coalesce
|
|
7
|
+
from django.shortcuts import get_object_or_404
|
|
8
|
+
from django.utils.functional import cached_property
|
|
9
|
+
from reversion.views import RevisionMixin
|
|
10
|
+
from wbcore import viewsets
|
|
11
|
+
from wbcore.cache.decorators import cache_table
|
|
12
|
+
from wbcore.filters import DjangoFilterBackend
|
|
13
|
+
from wbcore.permissions.permissions import InternalUserPermissionMixin
|
|
14
|
+
from wbcore.utils.date import get_next_day_timedelta
|
|
15
|
+
from wbfdm.filters import (
|
|
16
|
+
ClassificationFilter,
|
|
17
|
+
ClassificationTreeChartFilter,
|
|
18
|
+
InstrumentClassificationThroughModelViewFilterSet,
|
|
19
|
+
)
|
|
20
|
+
from wbfdm.models import (
|
|
21
|
+
Classification,
|
|
22
|
+
ClassificationGroup,
|
|
23
|
+
InstrumentClassificationThroughModel,
|
|
24
|
+
)
|
|
25
|
+
from wbfdm.serializers import (
|
|
26
|
+
ClassificationGroupModelSerializer,
|
|
27
|
+
ClassificationGroupRepresentationSerializer,
|
|
28
|
+
ClassificationModelSerializer,
|
|
29
|
+
ClassificationRepresentationSerializer,
|
|
30
|
+
InstrumentClassificationThroughModelSerializer,
|
|
31
|
+
)
|
|
32
|
+
from wbfdm.viewsets.configs import (
|
|
33
|
+
ChildClassificationParentClassificationTitleConfig,
|
|
34
|
+
ClassificationButtonConfig,
|
|
35
|
+
ClassificationClassificationGroupEndpointConfig,
|
|
36
|
+
ClassificationClassificationGroupTitleConfig,
|
|
37
|
+
ClassificationDisplayConfig,
|
|
38
|
+
ClassificationEndpointConfig,
|
|
39
|
+
ClassificationGroupButtonConfig,
|
|
40
|
+
ClassificationGroupDisplayConfig,
|
|
41
|
+
ClassificationGroupTitleConfig,
|
|
42
|
+
ClassificationIcicleChartEndpointConfig,
|
|
43
|
+
ClassificationIcicleChartTitleConfig,
|
|
44
|
+
ClassificationInstrumentThroughInstrumentModelEndpointConfig,
|
|
45
|
+
ClassificationParentClassificationEndpointConfig,
|
|
46
|
+
ClassificationTitleConfig,
|
|
47
|
+
ClassificationTreeChartEndpointConfig,
|
|
48
|
+
ClassificationTreeChartTitleConfig,
|
|
49
|
+
InstrumentClassificationThroughDisplayConfig,
|
|
50
|
+
InstrumentClassificationThroughEndpointConfig,
|
|
51
|
+
InstrumentClassificationThroughInstrumentModelEndpointConfig,
|
|
52
|
+
InstrumentClassificationThroughTitleConfig,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
from ..mixins import InstrumentMixin
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ClassificationRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
59
|
+
IDENTIFIER = "wbfdm:classification"
|
|
60
|
+
queryset = Classification.objects.all()
|
|
61
|
+
serializer_class = ClassificationRepresentationSerializer
|
|
62
|
+
|
|
63
|
+
filterset_class = ClassificationFilter
|
|
64
|
+
ordering_fields = ["code_aggregated"]
|
|
65
|
+
ordering = ["code_aggregated"]
|
|
66
|
+
search_fields = ["code_aggregated", "name"]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ClassificationGroupRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
70
|
+
IDENTIFIER = "wbfdm:classificationgrouprepresentation"
|
|
71
|
+
queryset = ClassificationGroup.objects.all()
|
|
72
|
+
serializer_class = ClassificationGroupRepresentationSerializer
|
|
73
|
+
|
|
74
|
+
filterset_fields = {"is_primary": ["exact"], "max_depth": ["gte", "exact", "lte"]}
|
|
75
|
+
ordering_fields = ["name"]
|
|
76
|
+
ordering = ["name"]
|
|
77
|
+
search_fields = ["name"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ClassificationModelViewSet(InternalUserPermissionMixin, RevisionMixin, viewsets.ModelViewSet):
|
|
81
|
+
IDENTIFIER = "wbfdm:classification"
|
|
82
|
+
|
|
83
|
+
queryset = Classification.objects.select_related("parent", "group")
|
|
84
|
+
serializer_class = ClassificationModelSerializer
|
|
85
|
+
filterset_class = ClassificationFilter
|
|
86
|
+
|
|
87
|
+
ordering_fields = (
|
|
88
|
+
"name",
|
|
89
|
+
"code_aggregated",
|
|
90
|
+
"level_representation",
|
|
91
|
+
)
|
|
92
|
+
ordering = ("level_representation", "name")
|
|
93
|
+
search_fields = ("name", "group__name", "parent__name", "code_aggregated")
|
|
94
|
+
|
|
95
|
+
endpoint_config_class = ClassificationEndpointConfig
|
|
96
|
+
display_config_class = ClassificationDisplayConfig
|
|
97
|
+
title_config_class = ClassificationTitleConfig
|
|
98
|
+
button_config_class = ClassificationButtonConfig
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ClassificationClassificationGroupModelViewSet(ClassificationModelViewSet):
|
|
102
|
+
endpoint_config_class = ClassificationClassificationGroupEndpointConfig
|
|
103
|
+
title_config_class = ClassificationClassificationGroupTitleConfig
|
|
104
|
+
|
|
105
|
+
def get_queryset(self):
|
|
106
|
+
return super().get_queryset().filter(group=self.kwargs["group_id"], level=0)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ChildClassificationParentClassificationModelViewSet(ClassificationModelViewSet):
|
|
110
|
+
endpoint_config_class = ClassificationParentClassificationEndpointConfig
|
|
111
|
+
title_config_class = ChildClassificationParentClassificationTitleConfig
|
|
112
|
+
|
|
113
|
+
def get_queryset(self):
|
|
114
|
+
parent = Classification.objects.get(id=self.kwargs["parent_id"])
|
|
115
|
+
return super().get_queryset().filter(parent=self.kwargs["parent_id"], level=parent.level + 1)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ClassificationGroupModelViewSet(InternalUserPermissionMixin, RevisionMixin, viewsets.ModelViewSet):
|
|
119
|
+
IDENTIFIER = "wbfdm:classificationgroup"
|
|
120
|
+
|
|
121
|
+
queryset = ClassificationGroup.objects.all()
|
|
122
|
+
serializer_class = ClassificationGroupModelSerializer
|
|
123
|
+
filterset_fields = {"is_primary": ["exact"], "max_depth": ["gte", "exact", "lte"]}
|
|
124
|
+
ordering_fields = ("name",)
|
|
125
|
+
ordering = ("name",)
|
|
126
|
+
search_fields = ("name",)
|
|
127
|
+
|
|
128
|
+
display_config_class = ClassificationGroupDisplayConfig
|
|
129
|
+
title_config_class = ClassificationGroupTitleConfig
|
|
130
|
+
button_config_class = ClassificationGroupButtonConfig
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class AbstractClassificationChartView(viewsets.ChartViewSet):
|
|
134
|
+
filter_backends = (DjangoFilterBackend,)
|
|
135
|
+
queryset = Classification.objects.all()
|
|
136
|
+
filterset_class = ClassificationTreeChartFilter
|
|
137
|
+
|
|
138
|
+
@cached_property
|
|
139
|
+
def aggregation_type(self) -> str:
|
|
140
|
+
return self.request.GET.get("aggregation_type", "classification_count")
|
|
141
|
+
|
|
142
|
+
@cached_property
|
|
143
|
+
def classification_group(self) -> ClassificationGroup:
|
|
144
|
+
return ClassificationGroup.objects.get(id=self.kwargs["group_id"])
|
|
145
|
+
|
|
146
|
+
@cached_property
|
|
147
|
+
def top_level(self) -> int:
|
|
148
|
+
top_level = 0
|
|
149
|
+
if top_classification_id := self.request.GET.get("top_classification", None):
|
|
150
|
+
top_level = Classification.objects.get(id=top_classification_id).level
|
|
151
|
+
return top_level
|
|
152
|
+
|
|
153
|
+
def get_df(self, queryset) -> tuple[pd.DataFrame, list[str]]:
|
|
154
|
+
_range = self.classification_group.max_depth + 1 - self.top_level
|
|
155
|
+
df = pd.DataFrame(columns=[self.aggregation_type])
|
|
156
|
+
level_representation = []
|
|
157
|
+
if queryset.exists():
|
|
158
|
+
df = pd.DataFrame(
|
|
159
|
+
queryset.values(*[f"{'parent__' * height}name" for height in range(_range)], self.aggregation_type)
|
|
160
|
+
).fillna(0)
|
|
161
|
+
|
|
162
|
+
level_representation = self.classification_group.get_levels_representation()[:_range]
|
|
163
|
+
df.columns = [*level_representation, self.aggregation_type]
|
|
164
|
+
return df, level_representation
|
|
165
|
+
|
|
166
|
+
def get_queryset(self):
|
|
167
|
+
return (
|
|
168
|
+
super()
|
|
169
|
+
.get_queryset()
|
|
170
|
+
.filter(height=0, group=self.classification_group)
|
|
171
|
+
.annotate(
|
|
172
|
+
instrument_count=Coalesce(
|
|
173
|
+
Subquery(
|
|
174
|
+
InstrumentClassificationThroughModel.objects.filter(classification=OuterRef("pk"))
|
|
175
|
+
.values("classification")
|
|
176
|
+
.annotate(c=Count("classification"))
|
|
177
|
+
.values("c")[:1]
|
|
178
|
+
),
|
|
179
|
+
0,
|
|
180
|
+
),
|
|
181
|
+
classification_count=Value(1),
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def get_plotly(self, queryset):
|
|
186
|
+
return go.Figure()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@cache_table(
|
|
190
|
+
timeout=get_next_day_timedelta(),
|
|
191
|
+
key_prefix=lambda view: f"{view.classification_group.id}_{view.aggregation_type}_{view.top_level}",
|
|
192
|
+
periodic_caching=True,
|
|
193
|
+
periodic_caching_view_kwargs=lambda: [
|
|
194
|
+
{"group_id": group_id} for group_id in ClassificationGroup.objects.values_list("id", flat=True)
|
|
195
|
+
],
|
|
196
|
+
periodic_caching_get_parameters=[
|
|
197
|
+
{"aggregation_type": "classification_count"},
|
|
198
|
+
{"aggregation_type": "instrument_count"},
|
|
199
|
+
],
|
|
200
|
+
)
|
|
201
|
+
class ClassificationTreeChartView(AbstractClassificationChartView):
|
|
202
|
+
IDENTIFIER = "wbfdm:classificationgroup-tree"
|
|
203
|
+
title_config_class = ClassificationTreeChartTitleConfig
|
|
204
|
+
endpoint_config_class = ClassificationTreeChartEndpointConfig
|
|
205
|
+
|
|
206
|
+
def get_plotly(self, queryset):
|
|
207
|
+
df, level_representation = self.get_df(queryset)
|
|
208
|
+
fig = px.treemap(df, path=[*level_representation[::-1]], values=self.aggregation_type, branchvalues="total")
|
|
209
|
+
fig.update_traces(root_color="lightgrey", hovertemplate=f"{self.aggregation_type}: %{{value}}")
|
|
210
|
+
fig.update_layout(margin=dict(t=25, l=25, r=25, b=25))
|
|
211
|
+
return fig
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@cache_table(
|
|
215
|
+
timeout=get_next_day_timedelta(),
|
|
216
|
+
key_prefix=lambda view: f"{view.classification_group.id}_{view.aggregation_type}_{view.top_level}",
|
|
217
|
+
periodic_caching=True,
|
|
218
|
+
periodic_caching_view_kwargs=lambda: [
|
|
219
|
+
{"group_id": group_id} for group_id in ClassificationGroup.objects.values_list("id", flat=True)
|
|
220
|
+
],
|
|
221
|
+
periodic_caching_get_parameters=[
|
|
222
|
+
{"aggregation_type": "classification_count"},
|
|
223
|
+
{"aggregation_type": "instrument_count"},
|
|
224
|
+
],
|
|
225
|
+
)
|
|
226
|
+
class ClassificationIcicleChartView(AbstractClassificationChartView):
|
|
227
|
+
IDENTIFIER = "wbfdm:classificationgroup-iciclechart"
|
|
228
|
+
title_config_class = ClassificationIcicleChartTitleConfig
|
|
229
|
+
endpoint_config_class = ClassificationIcicleChartEndpointConfig
|
|
230
|
+
|
|
231
|
+
def get_plotly(self, queryset):
|
|
232
|
+
df, level_representation = self.get_df(queryset)
|
|
233
|
+
fig = px.icicle(df, path=[*level_representation[::-1]], values=self.aggregation_type)
|
|
234
|
+
fig.update_traces(
|
|
235
|
+
root_color="lightgrey", tiling=dict(orientation="v"), hovertemplate=f"{self.aggregation_type}: %{{value}}"
|
|
236
|
+
)
|
|
237
|
+
fig.update_layout(margin=dict(t=25, l=25, r=25, b=25))
|
|
238
|
+
return fig
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class InstrumentClassificationThroughModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
|
|
242
|
+
serializer_class = InstrumentClassificationThroughModelSerializer
|
|
243
|
+
queryset = InstrumentClassificationThroughModel.objects.select_related(
|
|
244
|
+
"instrument",
|
|
245
|
+
"classification",
|
|
246
|
+
).prefetch_related("related_instruments", "tags")
|
|
247
|
+
search_fields = ("classification__name", "classification__code_aggregated")
|
|
248
|
+
ordering_fields = ordering = ["classification__name"]
|
|
249
|
+
|
|
250
|
+
filterset_class = InstrumentClassificationThroughModelViewFilterSet
|
|
251
|
+
display_config_class = InstrumentClassificationThroughDisplayConfig
|
|
252
|
+
endpoint_config_class = InstrumentClassificationThroughEndpointConfig
|
|
253
|
+
title_config_class = InstrumentClassificationThroughTitleConfig
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class InstrumentClassificationThroughInstrumentModelViewSet(
|
|
257
|
+
InstrumentMixin, InstrumentClassificationThroughModelViewSet
|
|
258
|
+
):
|
|
259
|
+
endpoint_config_class = InstrumentClassificationThroughInstrumentModelEndpointConfig
|
|
260
|
+
search_fields = ["classification__computed_str"]
|
|
261
|
+
ordering_fields = ordering = ["classification__computed_str"]
|
|
262
|
+
|
|
263
|
+
def get_queryset(self):
|
|
264
|
+
return super().get_queryset().filter(instrument__in=self.instrument.get_family())
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class ClassificationInstrumentThroughInstrumentModelViewSet(InstrumentClassificationThroughModelViewSet):
|
|
268
|
+
search_fields = ["instrument__computed_str"]
|
|
269
|
+
ordering_fields = ordering = ["instrument__computed_str"]
|
|
270
|
+
|
|
271
|
+
endpoint_config_class = ClassificationInstrumentThroughInstrumentModelEndpointConfig
|
|
272
|
+
|
|
273
|
+
def get_queryset(self):
|
|
274
|
+
classification = get_object_or_404(Classification, id=self.kwargs["classification_id"])
|
|
275
|
+
return (
|
|
276
|
+
super()
|
|
277
|
+
.get_queryset()
|
|
278
|
+
.filter(classification__in=classification.get_descendants(include_self=True).values("id"))
|
|
279
|
+
)
|