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.

Files changed (337) hide show
  1. wbfdm/__init__.py +2 -0
  2. wbfdm/admin/__init__.py +42 -0
  3. wbfdm/admin/classifications.py +39 -0
  4. wbfdm/admin/esg.py +23 -0
  5. wbfdm/admin/exchanges.py +53 -0
  6. wbfdm/admin/instrument_lists.py +23 -0
  7. wbfdm/admin/instrument_prices.py +62 -0
  8. wbfdm/admin/instrument_requests.py +33 -0
  9. wbfdm/admin/instruments.py +117 -0
  10. wbfdm/admin/instruments_relationships.py +25 -0
  11. wbfdm/admin/options.py +101 -0
  12. wbfdm/analysis/__init__.py +2 -0
  13. wbfdm/analysis/esg/__init__.py +0 -0
  14. wbfdm/analysis/esg/enums.py +82 -0
  15. wbfdm/analysis/esg/esg_analysis.py +217 -0
  16. wbfdm/analysis/esg/utils.py +13 -0
  17. wbfdm/analysis/financial_analysis/__init__.py +1 -0
  18. wbfdm/analysis/financial_analysis/financial_metric_analysis.py +88 -0
  19. wbfdm/analysis/financial_analysis/financial_ratio_analysis.py +125 -0
  20. wbfdm/analysis/financial_analysis/financial_statistics_analysis.py +271 -0
  21. wbfdm/analysis/financial_analysis/statement_with_estimates.py +558 -0
  22. wbfdm/analysis/financial_analysis/utils.py +316 -0
  23. wbfdm/analysis/technical_analysis/__init__.py +1 -0
  24. wbfdm/analysis/technical_analysis/technical_analysis.py +138 -0
  25. wbfdm/analysis/technical_analysis/traces.py +165 -0
  26. wbfdm/analysis/utils.py +32 -0
  27. wbfdm/apps.py +14 -0
  28. wbfdm/contrib/__init__.py +0 -0
  29. wbfdm/contrib/dsws/__init__.py +0 -0
  30. wbfdm/contrib/dsws/client.py +285 -0
  31. wbfdm/contrib/internal/__init__.py +0 -0
  32. wbfdm/contrib/internal/dataloaders/__init__.py +0 -0
  33. wbfdm/contrib/internal/dataloaders/market_data.py +87 -0
  34. wbfdm/contrib/metric/__init__.py +0 -0
  35. wbfdm/contrib/metric/admin/__init__.py +2 -0
  36. wbfdm/contrib/metric/admin/instruments.py +12 -0
  37. wbfdm/contrib/metric/admin/metrics.py +43 -0
  38. wbfdm/contrib/metric/apps.py +10 -0
  39. wbfdm/contrib/metric/backends/__init__.py +2 -0
  40. wbfdm/contrib/metric/backends/base.py +159 -0
  41. wbfdm/contrib/metric/backends/performances.py +265 -0
  42. wbfdm/contrib/metric/backends/statistics.py +182 -0
  43. wbfdm/contrib/metric/decorators.py +14 -0
  44. wbfdm/contrib/metric/dispatch.py +23 -0
  45. wbfdm/contrib/metric/dto.py +88 -0
  46. wbfdm/contrib/metric/exceptions.py +6 -0
  47. wbfdm/contrib/metric/factories.py +33 -0
  48. wbfdm/contrib/metric/filters.py +28 -0
  49. wbfdm/contrib/metric/migrations/0001_initial.py +88 -0
  50. wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py +26 -0
  51. wbfdm/contrib/metric/migrations/__init__.py +0 -0
  52. wbfdm/contrib/metric/models.py +180 -0
  53. wbfdm/contrib/metric/orchestrators.py +94 -0
  54. wbfdm/contrib/metric/registry.py +80 -0
  55. wbfdm/contrib/metric/serializers.py +44 -0
  56. wbfdm/contrib/metric/tasks.py +27 -0
  57. wbfdm/contrib/metric/tests/__init__.py +0 -0
  58. wbfdm/contrib/metric/tests/backends/__init__.py +0 -0
  59. wbfdm/contrib/metric/tests/backends/test_performances.py +152 -0
  60. wbfdm/contrib/metric/tests/backends/test_statistics.py +48 -0
  61. wbfdm/contrib/metric/tests/conftest.py +92 -0
  62. wbfdm/contrib/metric/tests/test_dto.py +73 -0
  63. wbfdm/contrib/metric/tests/test_models.py +72 -0
  64. wbfdm/contrib/metric/tests/test_tasks.py +24 -0
  65. wbfdm/contrib/metric/tests/test_viewsets.py +79 -0
  66. wbfdm/contrib/metric/urls.py +19 -0
  67. wbfdm/contrib/metric/viewsets/__init__.py +1 -0
  68. wbfdm/contrib/metric/viewsets/configs/__init__.py +1 -0
  69. wbfdm/contrib/metric/viewsets/configs/display.py +92 -0
  70. wbfdm/contrib/metric/viewsets/configs/menus.py +11 -0
  71. wbfdm/contrib/metric/viewsets/configs/utils.py +137 -0
  72. wbfdm/contrib/metric/viewsets/mixins.py +245 -0
  73. wbfdm/contrib/metric/viewsets/viewsets.py +40 -0
  74. wbfdm/contrib/msci/__init__.py +0 -0
  75. wbfdm/contrib/msci/client.py +92 -0
  76. wbfdm/contrib/msci/dataloaders/__init__.py +0 -0
  77. wbfdm/contrib/msci/dataloaders/esg.py +87 -0
  78. wbfdm/contrib/msci/dataloaders/esg_controversies.py +81 -0
  79. wbfdm/contrib/msci/sync.py +58 -0
  80. wbfdm/contrib/msci/tests/__init__.py +0 -0
  81. wbfdm/contrib/msci/tests/conftest.py +1 -0
  82. wbfdm/contrib/msci/tests/test_client.py +70 -0
  83. wbfdm/contrib/qa/__init__.py +0 -0
  84. wbfdm/contrib/qa/apps.py +22 -0
  85. wbfdm/contrib/qa/database_routers.py +25 -0
  86. wbfdm/contrib/qa/dataloaders/__init__.py +0 -0
  87. wbfdm/contrib/qa/dataloaders/adjustments.py +56 -0
  88. wbfdm/contrib/qa/dataloaders/corporate_actions.py +59 -0
  89. wbfdm/contrib/qa/dataloaders/financials.py +83 -0
  90. wbfdm/contrib/qa/dataloaders/market_data.py +117 -0
  91. wbfdm/contrib/qa/dataloaders/officers.py +59 -0
  92. wbfdm/contrib/qa/dataloaders/reporting_dates.py +67 -0
  93. wbfdm/contrib/qa/dataloaders/statements.py +267 -0
  94. wbfdm/contrib/qa/tasks.py +0 -0
  95. wbfdm/dataloaders/__init__.py +0 -0
  96. wbfdm/dataloaders/cache.py +129 -0
  97. wbfdm/dataloaders/protocols.py +112 -0
  98. wbfdm/dataloaders/proxies.py +201 -0
  99. wbfdm/dataloaders/types.py +209 -0
  100. wbfdm/dynamic_preferences_registry.py +45 -0
  101. wbfdm/enums.py +657 -0
  102. wbfdm/factories/__init__.py +13 -0
  103. wbfdm/factories/classifications.py +56 -0
  104. wbfdm/factories/controversies.py +27 -0
  105. wbfdm/factories/exchanges.py +21 -0
  106. wbfdm/factories/instrument_list.py +22 -0
  107. wbfdm/factories/instrument_prices.py +79 -0
  108. wbfdm/factories/instruments.py +63 -0
  109. wbfdm/factories/instruments_relationships.py +31 -0
  110. wbfdm/factories/options.py +66 -0
  111. wbfdm/figures/__init__.py +1 -0
  112. wbfdm/figures/financials/__init__.py +1 -0
  113. wbfdm/figures/financials/financial_analysis_charts.py +469 -0
  114. wbfdm/figures/financials/financials_charts.py +711 -0
  115. wbfdm/filters/__init__.py +31 -0
  116. wbfdm/filters/classifications.py +100 -0
  117. wbfdm/filters/exchanges.py +22 -0
  118. wbfdm/filters/financials.py +95 -0
  119. wbfdm/filters/financials_analysis.py +119 -0
  120. wbfdm/filters/instrument_prices.py +112 -0
  121. wbfdm/filters/instruments.py +198 -0
  122. wbfdm/filters/utils.py +44 -0
  123. wbfdm/import_export/__init__.py +0 -0
  124. wbfdm/import_export/backends/__init__.py +0 -0
  125. wbfdm/import_export/backends/cbinsights/__init__.py +2 -0
  126. wbfdm/import_export/backends/cbinsights/deals.py +44 -0
  127. wbfdm/import_export/backends/cbinsights/equities.py +41 -0
  128. wbfdm/import_export/backends/cbinsights/mixin.py +15 -0
  129. wbfdm/import_export/backends/cbinsights/utils/__init__.py +0 -0
  130. wbfdm/import_export/backends/cbinsights/utils/classifications.py +4150 -0
  131. wbfdm/import_export/backends/cbinsights/utils/client.py +217 -0
  132. wbfdm/import_export/backends/refinitiv/__init__.py +5 -0
  133. wbfdm/import_export/backends/refinitiv/daily_fundamental.py +36 -0
  134. wbfdm/import_export/backends/refinitiv/fiscal_period.py +63 -0
  135. wbfdm/import_export/backends/refinitiv/forecast.py +178 -0
  136. wbfdm/import_export/backends/refinitiv/fundamental.py +103 -0
  137. wbfdm/import_export/backends/refinitiv/geographic_segment.py +32 -0
  138. wbfdm/import_export/backends/refinitiv/instrument.py +55 -0
  139. wbfdm/import_export/backends/refinitiv/instrument_price.py +77 -0
  140. wbfdm/import_export/backends/refinitiv/mixin.py +29 -0
  141. wbfdm/import_export/backends/refinitiv/utils/__init__.py +1 -0
  142. wbfdm/import_export/backends/refinitiv/utils/controller.py +182 -0
  143. wbfdm/import_export/handlers/__init__.py +0 -0
  144. wbfdm/import_export/handlers/instrument.py +253 -0
  145. wbfdm/import_export/handlers/instrument_list.py +101 -0
  146. wbfdm/import_export/handlers/instrument_price.py +71 -0
  147. wbfdm/import_export/handlers/option.py +54 -0
  148. wbfdm/import_export/handlers/private_equities.py +49 -0
  149. wbfdm/import_export/parsers/__init__.py +0 -0
  150. wbfdm/import_export/parsers/cbinsights/__init__.py +0 -0
  151. wbfdm/import_export/parsers/cbinsights/deals.py +39 -0
  152. wbfdm/import_export/parsers/cbinsights/equities.py +56 -0
  153. wbfdm/import_export/parsers/cbinsights/fundamentals.py +45 -0
  154. wbfdm/import_export/parsers/refinitiv/__init__.py +0 -0
  155. wbfdm/import_export/parsers/refinitiv/daily_fundamental.py +7 -0
  156. wbfdm/import_export/parsers/refinitiv/forecast.py +7 -0
  157. wbfdm/import_export/parsers/refinitiv/fundamental.py +9 -0
  158. wbfdm/import_export/parsers/refinitiv/geographic_segment.py +7 -0
  159. wbfdm/import_export/parsers/refinitiv/instrument.py +75 -0
  160. wbfdm/import_export/parsers/refinitiv/instrument_price.py +26 -0
  161. wbfdm/import_export/parsers/refinitiv/utils.py +96 -0
  162. wbfdm/import_export/resources/__init__.py +0 -0
  163. wbfdm/import_export/resources/classification.py +23 -0
  164. wbfdm/import_export/resources/instrument_prices.py +33 -0
  165. wbfdm/import_export/resources/instruments.py +176 -0
  166. wbfdm/jinja2.py +7 -0
  167. wbfdm/management/__init__.py +30 -0
  168. wbfdm/menu.py +11 -0
  169. wbfdm/migrations/0001_initial.py +71 -0
  170. wbfdm/migrations/0002_rename_statements_instrumentlookup_financials_and_more.py +144 -0
  171. wbfdm/migrations/0003_instrument_estimate_backend_and_more.py +34 -0
  172. wbfdm/migrations/0004_rename_financials_instrumentlookup_statements_and_more.py +86 -0
  173. wbfdm/migrations/0005_instrument_corporate_action_backend.py +29 -0
  174. wbfdm/migrations/0006_instrument_officer_backend.py +29 -0
  175. wbfdm/migrations/0007_instrument_country_instrument_currency_and_more.py +117 -0
  176. wbfdm/migrations/0008_controversy.py +75 -0
  177. wbfdm/migrations/0009_alter_controversy_flag_alter_controversy_initiated_and_more.py +85 -0
  178. wbfdm/migrations/0010_classification_classificationgroup_deal_exchange_and_more.py +1299 -0
  179. wbfdm/migrations/0011_delete_instrumentlookup_instrument_corporate_actions_and_more.py +169 -0
  180. wbfdm/migrations/0012_instrumentprice_created_instrumentprice_modified.py +564 -0
  181. wbfdm/migrations/0013_instrument_is_investable_universe_and_more.py +199 -0
  182. wbfdm/migrations/0014_alter_controversy_instrument.py +22 -0
  183. wbfdm/migrations/0015_instrument_instrument_investible_index.py +16 -0
  184. wbfdm/migrations/0016_instrumenttype_name_repr.py +18 -0
  185. wbfdm/migrations/0017_instrument_instrument_security_index.py +16 -0
  186. wbfdm/migrations/0018_instrument_instrument_level_index.py +20 -0
  187. wbfdm/migrations/0019_alter_controversy_source.py +17 -0
  188. wbfdm/migrations/0020_optionaggregate_option_and_more.py +249 -0
  189. wbfdm/migrations/0021_delete_instrumentdailystatistics.py +15 -0
  190. wbfdm/migrations/0022_instrument_cusip_option_open_interest_20d_and_more.py +91 -0
  191. wbfdm/migrations/0023_instrument_unique_ric_instrument_unique_rmc_and_more.py +53 -0
  192. wbfdm/migrations/0024_option_open_interest_10d_option_volume_10d_and_more.py +36 -0
  193. wbfdm/migrations/0025_instrument_is_primary_and_more.py +29 -0
  194. wbfdm/migrations/0026_instrument_is_cash_equivalent.py +30 -0
  195. wbfdm/migrations/0027_remove_instrument_unique_ric_and_more.py +100 -0
  196. wbfdm/migrations/__init__.py +0 -0
  197. wbfdm/models/__init__.py +4 -0
  198. wbfdm/models/esg/__init__.py +1 -0
  199. wbfdm/models/esg/controversies.py +81 -0
  200. wbfdm/models/exchanges/__init__.py +1 -0
  201. wbfdm/models/exchanges/exchanges.py +223 -0
  202. wbfdm/models/fields.py +117 -0
  203. wbfdm/models/fk_fields.py +403 -0
  204. wbfdm/models/indicators.py +0 -0
  205. wbfdm/models/instruments/__init__.py +19 -0
  206. wbfdm/models/instruments/classifications.py +265 -0
  207. wbfdm/models/instruments/instrument_lists.py +120 -0
  208. wbfdm/models/instruments/instrument_prices.py +540 -0
  209. wbfdm/models/instruments/instrument_relationships.py +251 -0
  210. wbfdm/models/instruments/instrument_requests.py +196 -0
  211. wbfdm/models/instruments/instruments.py +991 -0
  212. wbfdm/models/instruments/llm/__init__.py +1 -0
  213. wbfdm/models/instruments/llm/create_instrument_news_relationships.py +78 -0
  214. wbfdm/models/instruments/mixin/__init__.py +0 -0
  215. wbfdm/models/instruments/mixin/financials_computed.py +804 -0
  216. wbfdm/models/instruments/mixin/financials_serializer_fields.py +1407 -0
  217. wbfdm/models/instruments/mixin/instruments.py +294 -0
  218. wbfdm/models/instruments/options.py +225 -0
  219. wbfdm/models/instruments/private_equities.py +59 -0
  220. wbfdm/models/instruments/querysets.py +73 -0
  221. wbfdm/models/instruments/utils.py +41 -0
  222. wbfdm/preferences.py +21 -0
  223. wbfdm/serializers/__init__.py +4 -0
  224. wbfdm/serializers/esg.py +36 -0
  225. wbfdm/serializers/exchanges.py +39 -0
  226. wbfdm/serializers/instruments/__init__.py +37 -0
  227. wbfdm/serializers/instruments/classifications.py +139 -0
  228. wbfdm/serializers/instruments/instrument_lists.py +61 -0
  229. wbfdm/serializers/instruments/instrument_prices.py +73 -0
  230. wbfdm/serializers/instruments/instrument_relationships.py +170 -0
  231. wbfdm/serializers/instruments/instrument_requests.py +61 -0
  232. wbfdm/serializers/instruments/instruments.py +274 -0
  233. wbfdm/serializers/instruments/mixins.py +104 -0
  234. wbfdm/serializers/officers.py +20 -0
  235. wbfdm/signals.py +7 -0
  236. wbfdm/sync/__init__.py +0 -0
  237. wbfdm/sync/abstract.py +31 -0
  238. wbfdm/sync/runner.py +22 -0
  239. wbfdm/tasks.py +69 -0
  240. wbfdm/tests/__init__.py +0 -0
  241. wbfdm/tests/analysis/__init__.py +0 -0
  242. wbfdm/tests/analysis/financial_analysis/__init__.py +0 -0
  243. wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +392 -0
  244. wbfdm/tests/analysis/financial_analysis/test_utils.py +322 -0
  245. wbfdm/tests/analysis/test_esg.py +159 -0
  246. wbfdm/tests/conftest.py +92 -0
  247. wbfdm/tests/dataloaders/__init__.py +0 -0
  248. wbfdm/tests/dataloaders/test_cache.py +73 -0
  249. wbfdm/tests/models/__init__.py +0 -0
  250. wbfdm/tests/models/test_classifications.py +99 -0
  251. wbfdm/tests/models/test_exchanges.py +7 -0
  252. wbfdm/tests/models/test_instrument_list.py +117 -0
  253. wbfdm/tests/models/test_instrument_prices.py +306 -0
  254. wbfdm/tests/models/test_instruments.py +202 -0
  255. wbfdm/tests/models/test_merge.py +99 -0
  256. wbfdm/tests/models/test_options.py +69 -0
  257. wbfdm/tests/test_tasks.py +6 -0
  258. wbfdm/tests/tests.py +10 -0
  259. wbfdm/urls.py +222 -0
  260. wbfdm/utils.py +54 -0
  261. wbfdm/viewsets/__init__.py +10 -0
  262. wbfdm/viewsets/configs/__init__.py +5 -0
  263. wbfdm/viewsets/configs/buttons/__init__.py +8 -0
  264. wbfdm/viewsets/configs/buttons/classifications.py +23 -0
  265. wbfdm/viewsets/configs/buttons/exchanges.py +9 -0
  266. wbfdm/viewsets/configs/buttons/instrument_prices.py +49 -0
  267. wbfdm/viewsets/configs/buttons/instruments.py +283 -0
  268. wbfdm/viewsets/configs/display/__init__.py +22 -0
  269. wbfdm/viewsets/configs/display/classifications.py +138 -0
  270. wbfdm/viewsets/configs/display/esg.py +75 -0
  271. wbfdm/viewsets/configs/display/exchanges.py +42 -0
  272. wbfdm/viewsets/configs/display/instrument_lists.py +137 -0
  273. wbfdm/viewsets/configs/display/instrument_prices.py +199 -0
  274. wbfdm/viewsets/configs/display/instrument_requests.py +116 -0
  275. wbfdm/viewsets/configs/display/instruments.py +618 -0
  276. wbfdm/viewsets/configs/display/instruments_relationships.py +65 -0
  277. wbfdm/viewsets/configs/display/monthly_performances.py +72 -0
  278. wbfdm/viewsets/configs/display/officers.py +16 -0
  279. wbfdm/viewsets/configs/display/prices.py +21 -0
  280. wbfdm/viewsets/configs/display/statement_with_estimates.py +101 -0
  281. wbfdm/viewsets/configs/display/statements.py +48 -0
  282. wbfdm/viewsets/configs/endpoints/__init__.py +41 -0
  283. wbfdm/viewsets/configs/endpoints/classifications.py +87 -0
  284. wbfdm/viewsets/configs/endpoints/esg.py +20 -0
  285. wbfdm/viewsets/configs/endpoints/exchanges.py +6 -0
  286. wbfdm/viewsets/configs/endpoints/financials_analysis.py +65 -0
  287. wbfdm/viewsets/configs/endpoints/instrument_lists.py +38 -0
  288. wbfdm/viewsets/configs/endpoints/instrument_prices.py +51 -0
  289. wbfdm/viewsets/configs/endpoints/instrument_requests.py +20 -0
  290. wbfdm/viewsets/configs/endpoints/instruments.py +13 -0
  291. wbfdm/viewsets/configs/endpoints/instruments_relationships.py +31 -0
  292. wbfdm/viewsets/configs/endpoints/statements.py +6 -0
  293. wbfdm/viewsets/configs/menus/__init__.py +9 -0
  294. wbfdm/viewsets/configs/menus/classifications.py +19 -0
  295. wbfdm/viewsets/configs/menus/exchanges.py +10 -0
  296. wbfdm/viewsets/configs/menus/instrument_lists.py +10 -0
  297. wbfdm/viewsets/configs/menus/instruments.py +20 -0
  298. wbfdm/viewsets/configs/menus/instruments_relationships.py +33 -0
  299. wbfdm/viewsets/configs/titles/__init__.py +42 -0
  300. wbfdm/viewsets/configs/titles/classifications.py +79 -0
  301. wbfdm/viewsets/configs/titles/esg.py +11 -0
  302. wbfdm/viewsets/configs/titles/exchanges.py +12 -0
  303. wbfdm/viewsets/configs/titles/financial_ratio_analysis.py +6 -0
  304. wbfdm/viewsets/configs/titles/financials_analysis.py +50 -0
  305. wbfdm/viewsets/configs/titles/instrument_prices.py +50 -0
  306. wbfdm/viewsets/configs/titles/instrument_requests.py +16 -0
  307. wbfdm/viewsets/configs/titles/instruments.py +31 -0
  308. wbfdm/viewsets/configs/titles/instruments_relationships.py +21 -0
  309. wbfdm/viewsets/configs/titles/market_data.py +13 -0
  310. wbfdm/viewsets/configs/titles/prices.py +15 -0
  311. wbfdm/viewsets/configs/titles/statement_with_estimates.py +10 -0
  312. wbfdm/viewsets/esg.py +72 -0
  313. wbfdm/viewsets/exchanges.py +63 -0
  314. wbfdm/viewsets/financial_analysis/__init__.py +3 -0
  315. wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +85 -0
  316. wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +85 -0
  317. wbfdm/viewsets/financial_analysis/statement_with_estimates.py +145 -0
  318. wbfdm/viewsets/instruments/__init__.py +80 -0
  319. wbfdm/viewsets/instruments/classifications.py +279 -0
  320. wbfdm/viewsets/instruments/financials_analysis.py +614 -0
  321. wbfdm/viewsets/instruments/instrument_lists.py +77 -0
  322. wbfdm/viewsets/instruments/instrument_prices.py +542 -0
  323. wbfdm/viewsets/instruments/instrument_requests.py +51 -0
  324. wbfdm/viewsets/instruments/instruments.py +106 -0
  325. wbfdm/viewsets/instruments/instruments_relationships.py +235 -0
  326. wbfdm/viewsets/instruments/utils.py +27 -0
  327. wbfdm/viewsets/market_data.py +172 -0
  328. wbfdm/viewsets/mixins.py +9 -0
  329. wbfdm/viewsets/officers.py +27 -0
  330. wbfdm/viewsets/prices.py +62 -0
  331. wbfdm/viewsets/statements/__init__.py +1 -0
  332. wbfdm/viewsets/statements/statements.py +100 -0
  333. wbfdm/viewsets/technical_analysis/__init__.py +1 -0
  334. wbfdm/viewsets/technical_analysis/monthly_performances.py +93 -0
  335. wbfdm-2.2.1.dist-info/METADATA +15 -0
  336. wbfdm-2.2.1.dist-info/RECORD +337 -0
  337. 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
+ )