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,152 @@
1
+ from datetime import timedelta
2
+ from decimal import Decimal
3
+
4
+ import pandas as pd
5
+ import pytest
6
+ from django.contrib.contenttypes.models import ContentType
7
+ from faker import Faker
8
+ from pandas.tseries.offsets import BDay
9
+ from wbfdm.models import Instrument, RelatedInstrumentThroughModel
10
+
11
+ fake = Faker()
12
+
13
+
14
+ @pytest.mark.django_db
15
+ class TestInstrumentPerformanceMetricBackend:
16
+ def test_compute_metrics(self, weekday, instrument, instrument_price_factory):
17
+ from wbfdm.contrib.metric.backends.performances import ( # we import locally to avoid database access pytest error
18
+ InstrumentPerformanceMetricBackend,
19
+ )
20
+
21
+ backend = InstrumentPerformanceMetricBackend(weekday)
22
+
23
+ price_today = instrument_price_factory.create(instrument=instrument, calculated=False, date=weekday)
24
+ price_yesterday = instrument_price_factory.create(
25
+ instrument=instrument, calculated=False, date=weekday - BDay(1)
26
+ )
27
+ price_last_week = instrument_price_factory.create(
28
+ instrument=instrument, calculated=False, date=(weekday - timedelta(days=7)) - BDay(0)
29
+ )
30
+ price_last_month = instrument_price_factory.create(
31
+ instrument=instrument, calculated=False, date=(weekday - timedelta(days=30)) - BDay(1)
32
+ )
33
+ price_last_quarter = instrument_price_factory.create(
34
+ instrument=instrument, calculated=False, date=(weekday - timedelta(days=120)) - BDay(1)
35
+ )
36
+ price_last_year = instrument_price_factory.create(
37
+ instrument=instrument, calculated=False, date=(weekday - timedelta(days=365)) - BDay(1)
38
+ )
39
+ price_end_last_year = instrument_price_factory.create(
40
+ instrument=instrument, calculated=False, date=weekday - pd.tseries.offsets.BYearEnd(1)
41
+ )
42
+ price_inception = instrument_price_factory.create(
43
+ instrument=instrument, calculated=False, date=price_last_year.date - BDay(1)
44
+ )
45
+
46
+ instrument_price_factory.create(
47
+ instrument=instrument, calculated=True, date=price_inception.date - BDay(1)
48
+ ) # add noise
49
+ instrument.inception_date = price_inception.date.date()
50
+ instrument.save()
51
+ instrument.update_last_valuation_date()
52
+ base_metric = next(backend.compute_metrics(instrument))
53
+
54
+ # Check base metric metadata
55
+ assert base_metric.date is None
56
+ assert base_metric.basket_id == instrument.id
57
+ assert base_metric.basket_content_type_id == ContentType.objects.get_for_model(Instrument).id
58
+ assert base_metric.key == "performance"
59
+ assert base_metric.instrument_id is None
60
+
61
+ assert base_metric.metrics["is_estimated"] is False
62
+ assert base_metric.metrics["daily"] == pytest.approx(
63
+ float(price_today.net_value / price_yesterday.net_value - Decimal(1)), abs=1e-5
64
+ )
65
+ assert base_metric.metrics["weekly"] == pytest.approx(
66
+ float(price_today.net_value / price_last_week.net_value - Decimal(1)), abs=1e-5
67
+ )
68
+ assert base_metric.metrics["monthly"] == pytest.approx(
69
+ float(price_today.net_value / price_last_month.net_value - Decimal(1)), abs=1e-5
70
+ )
71
+ assert base_metric.metrics["quarterly"] == pytest.approx(
72
+ float(price_today.net_value / price_last_quarter.net_value - Decimal(1)), abs=1e-5
73
+ )
74
+
75
+ assert base_metric.metrics["yearly"] == pytest.approx(
76
+ float(price_today.net_value / price_last_year.net_value - Decimal(1)), abs=1e-5
77
+ )
78
+
79
+ assert base_metric.metrics["year_to_date"] == pytest.approx(
80
+ float(price_today.net_value / price_end_last_year.net_value - Decimal(1)), abs=1e-5
81
+ )
82
+ assert base_metric.metrics["inception"] == pytest.approx(
83
+ float(price_today.net_value / price_inception.net_value - Decimal(1)), abs=1e-5
84
+ )
85
+
86
+ def test_compute_metrics_estimated(self, weekday, instrument, instrument_price_factory):
87
+ from wbfdm.contrib.metric.backends.performances import ( # we import locally to avoid database access pytest error
88
+ InstrumentPerformanceMetricBackend,
89
+ )
90
+
91
+ backend = InstrumentPerformanceMetricBackend(weekday)
92
+
93
+ price_today = instrument_price_factory.create(instrument=instrument, calculated=True, date=weekday)
94
+ price_yesterday = instrument_price_factory.create(
95
+ instrument=instrument, calculated=False, date=weekday - BDay(1)
96
+ )
97
+ instrument.inception_date = price_yesterday.date.date()
98
+ instrument.save()
99
+ instrument.update_last_valuation_date()
100
+ base_metric = next(backend.compute_metrics(instrument))
101
+
102
+ assert base_metric.metrics["is_estimated"] is True
103
+ assert base_metric.metrics["daily"] == pytest.approx(
104
+ float(price_today.net_value / price_yesterday.net_value - Decimal(1)), abs=1e-5
105
+ )
106
+
107
+ def test_compute_metrics_with_peers(self, weekday, instrument_factory, instrument_price_factory):
108
+ from wbfdm.contrib.metric.backends.performances import ( # we import locally to avoid database access pytest error
109
+ InstrumentPerformanceMetricBackend,
110
+ )
111
+
112
+ instrument = instrument_factory.create()
113
+ peer_1 = instrument_factory.create()
114
+ RelatedInstrumentThroughModel.objects.create(
115
+ related_type="PEER", instrument=instrument, related_instrument=peer_1
116
+ )
117
+ peer_2 = instrument_factory.create()
118
+ RelatedInstrumentThroughModel.objects.create(
119
+ related_type="PEER", instrument=instrument, related_instrument=peer_2
120
+ )
121
+
122
+ backend = InstrumentPerformanceMetricBackend(weekday)
123
+
124
+ price_today = instrument_price_factory.create(instrument=instrument, calculated=False, date=weekday)
125
+ price_yesterday = instrument_price_factory.create(
126
+ instrument=instrument, calculated=False, date=weekday - BDay(1)
127
+ )
128
+ instrument.inception_date = price_yesterday.date.date()
129
+ instrument.save()
130
+ peer_1_price_today = instrument_price_factory.create(instrument=peer_1, calculated=False, date=weekday)
131
+ peer_1_price_yesterday = instrument_price_factory.create(
132
+ instrument=peer_1, calculated=False, date=weekday - BDay(1)
133
+ )
134
+ peer_1.inception_date = peer_1_price_yesterday.date.date()
135
+ peer_1.save()
136
+ peer_2_price_today = instrument_price_factory.create(instrument=peer_2, calculated=False, date=weekday)
137
+ peer_2_price_yesterday = instrument_price_factory.create(
138
+ instrument=peer_2, calculated=False, date=weekday - BDay(1)
139
+ )
140
+ peer_2.inception_date = peer_2_price_yesterday.date.date()
141
+ peer_2.save()
142
+ instrument.update_last_valuation_date()
143
+ peer_1.update_last_valuation_date()
144
+ peer_2.update_last_valuation_date()
145
+ base_metric = next(backend.compute_metrics(instrument))
146
+ daily_instrument_perf = float(price_today.net_value / price_yesterday.net_value - Decimal(1))
147
+ assert base_metric.metrics["daily"] == pytest.approx(daily_instrument_perf, abs=1e-5)
148
+ daily_peer_1_perf = float(peer_1_price_today.net_value / peer_1_price_yesterday.net_value - Decimal(1))
149
+ daily_peer_2_perf = float(peer_2_price_today.net_value / peer_2_price_yesterday.net_value - Decimal(1))
150
+ avg_daily_peer_perf = (daily_peer_1_perf + daily_peer_2_perf) / 2
151
+ r = daily_instrument_perf - avg_daily_peer_perf
152
+ assert base_metric.metrics["peer_daily"] == pytest.approx(r, abs=0.00001)
@@ -0,0 +1,48 @@
1
+ from decimal import Decimal
2
+ from unittest.mock import patch
3
+
4
+ import pytest
5
+ from faker import Faker
6
+ from wbfdm.dataloaders.proxies import InstrumentDataloaderProxy
7
+
8
+ fake = Faker()
9
+
10
+
11
+ @pytest.mark.django_db
12
+ class TestInstrumentFinancialStatisticsMetricBackend:
13
+ @patch.object(InstrumentDataloaderProxy, "financials")
14
+ def test_default_daily_statistics(self, mock_fct, weekday, instrument, instrument_price_factory):
15
+ from wbfdm.contrib.metric.backends.statistics import ( # we import locally to avoid database access pytest error
16
+ InstrumentFinancialStatisticsMetricBackend,
17
+ )
18
+
19
+ backend = InstrumentFinancialStatisticsMetricBackend(weekday)
20
+
21
+ revenue_y_1 = fake.pyfloat()
22
+ revenue_y0 = fake.pyfloat()
23
+ revenue_y1 = fake.pyfloat()
24
+ market_capitalization = fake.pyfloat()
25
+ price = Decimal(150)
26
+ volume_50d = fake.pyfloat()
27
+ instrument_price_factory.create(
28
+ instrument=instrument,
29
+ calculated=False,
30
+ date=weekday,
31
+ net_value=price,
32
+ volume_50d=volume_50d,
33
+ market_capitalization=market_capitalization,
34
+ )
35
+ instrument.update_last_valuation_date()
36
+ mock_fct.return_value = [
37
+ {"year": weekday.year - 1, "value": revenue_y_1, "instrument_id": instrument.id},
38
+ {"year": weekday.year, "value": revenue_y0, "instrument_id": instrument.id},
39
+ {"year": weekday.year + 1, "value": revenue_y1, "instrument_id": instrument.id},
40
+ ]
41
+ metrics = next(backend.compute_metrics(instrument)).metrics
42
+
43
+ assert metrics["revenue_y_1"] == revenue_y_1
44
+ assert metrics["revenue_y0"] == revenue_y0
45
+ assert metrics["revenue_y1"] == revenue_y1
46
+ assert metrics["market_capitalization"] == market_capitalization
47
+ assert metrics["price"] == price
48
+ assert metrics["volume_50d"] == volume_50d
@@ -0,0 +1,92 @@
1
+ from wbcore.tests.conftest import * # type: ignore # isort:skip
2
+ from datetime import date
3
+
4
+ from django.apps import apps
5
+ from django.db.models.signals import pre_migrate
6
+ from faker import Faker
7
+ from pandas.tseries.offsets import BDay
8
+ from pytest_factoryboy import register
9
+ from wbcore.contrib.authentication.factories import (
10
+ InternalUserFactory,
11
+ SuperUserFactory,
12
+ UserFactory,
13
+ )
14
+ from wbcore.contrib.currency.factories import CurrencyFactory, CurrencyFXRatesFactory
15
+ from wbcore.contrib.directory.factories.entries import (
16
+ CompanyFactory,
17
+ CompanyTypeFactory,
18
+ CustomerStatusFactory,
19
+ EntryFactory,
20
+ PersonFactory,
21
+ )
22
+ from wbcore.contrib.geography.factories import (
23
+ CityFactory,
24
+ ContinentFactory,
25
+ CountryFactory,
26
+ StateFactory,
27
+ )
28
+ from wbcore.contrib.geography.tests.signals import app_pre_migration
29
+ from wbcore.contrib.io.factories import (
30
+ CrontabScheduleFactory,
31
+ DataBackendFactory,
32
+ ImportSourceFactory,
33
+ ParserHandlerFactory,
34
+ ProviderFactory,
35
+ SourceFactory,
36
+ )
37
+ from wbfdm.factories import (
38
+ ClassificationFactory,
39
+ ClassificationGroupFactory,
40
+ ExchangeFactory,
41
+ InstrumentClassificationThroughModelFactory,
42
+ InstrumentFactory,
43
+ InstrumentFavoriteGroupFactory,
44
+ InstrumentListFactory,
45
+ InstrumentPriceFactory,
46
+ InstrumentTypeFactory,
47
+ ParentClassificationFactory,
48
+ RelatedInstrumentThroughModelFactory,
49
+ )
50
+
51
+ from ..factories import InstrumentMetricFactory
52
+
53
+ fake = Faker()
54
+ register(ImportSourceFactory)
55
+ register(DataBackendFactory)
56
+ register(ProviderFactory)
57
+ register(SourceFactory)
58
+ register(ParserHandlerFactory)
59
+ register(CrontabScheduleFactory)
60
+
61
+ register(ClassificationFactory)
62
+ register(InstrumentClassificationThroughModelFactory)
63
+ register(ParentClassificationFactory)
64
+ register(ClassificationGroupFactory)
65
+ register(ExchangeFactory)
66
+ register(InstrumentFactory)
67
+ register(InstrumentTypeFactory)
68
+ register(InstrumentPriceFactory)
69
+ register(InstrumentFavoriteGroupFactory)
70
+ register(RelatedInstrumentThroughModelFactory)
71
+ register(CurrencyFXRatesFactory)
72
+ register(InstrumentListFactory)
73
+
74
+ register(CurrencyFactory)
75
+ register(CityFactory)
76
+ register(StateFactory)
77
+ register(CountryFactory)
78
+ register(ContinentFactory)
79
+
80
+ register(CompanyFactory)
81
+ register(PersonFactory)
82
+ register(InternalUserFactory)
83
+ register(EntryFactory)
84
+ register(CustomerStatusFactory)
85
+ register(CompanyTypeFactory)
86
+
87
+ register(UserFactory)
88
+ register(SuperUserFactory, "superuser")
89
+
90
+ register(InstrumentMetricFactory)
91
+
92
+ pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("metric"))
@@ -0,0 +1,73 @@
1
+ import pytest
2
+ from faker import Faker
3
+
4
+ from ..backends.performances import InstrumentMetricBaseBackend
5
+ from ..dto import MetricField, MetricKey
6
+
7
+ fake = Faker()
8
+
9
+
10
+ @pytest.mark.django_db
11
+ class TestMetricKey:
12
+ @pytest.fixture
13
+ def metric_key(self):
14
+ label = fake.word()
15
+
16
+ return MetricKey(
17
+ key=label.lower(),
18
+ label=label,
19
+ additional_prefixes=[fake.word()],
20
+ subfields=[
21
+ MetricField(
22
+ key=label.lower(),
23
+ label=label,
24
+ decorators=[{"position": "left", "value": "&", "type": "text"}],
25
+ serializer_kwargs={"percent": fake.boolean(), "precision": fake.pyint(min_value=1, max_value=4)},
26
+ )
27
+ for label in map(lambda x: fake.word(), range(3))
28
+ ],
29
+ )
30
+
31
+ def test_get_fields(self, metric_key):
32
+ fields = list(metric_key.get_fields())
33
+ assert fields[0] == (
34
+ f"{metric_key.key}_{metric_key.subfields[0].key}",
35
+ f"{metric_key.label} {metric_key.subfields[0].label}",
36
+ )
37
+ assert fields[1] == (
38
+ f"{metric_key.key}_{metric_key.subfields[1].key}",
39
+ f"{metric_key.label} {metric_key.subfields[1].label}",
40
+ )
41
+ assert fields[2] == (
42
+ f"{metric_key.key}_{metric_key.subfields[2].key}",
43
+ f"{metric_key.label} {metric_key.subfields[2].label}",
44
+ )
45
+
46
+ def test_get_fields_with_prefixed_key(self, metric_key):
47
+ fields = list(metric_key.get_fields(with_prefixed_key=True))
48
+ prefix = metric_key.additional_prefixes[0]
49
+ assert fields[1] == (
50
+ f"{metric_key.key}_{prefix}_{metric_key.subfields[0].key}",
51
+ f"{metric_key.label} {metric_key.subfields[0].label} ({prefix.title()})",
52
+ )
53
+ assert fields[3] == (
54
+ f"{metric_key.key}_{prefix}_{metric_key.subfields[1].key}",
55
+ f"{metric_key.label} {metric_key.subfields[1].label} ({prefix.title()})",
56
+ )
57
+ assert fields[5] == (
58
+ f"{metric_key.key}_{prefix}_{metric_key.subfields[2].key}",
59
+ f"{metric_key.label} {metric_key.subfields[2].label} ({prefix.title()})",
60
+ )
61
+
62
+ def test_fields_map(self, metric_key):
63
+ keys = set(map(lambda x: x[0], metric_key.get_fields(with_prefixed_key=True)))
64
+ assert keys == set(metric_key.subfields_filter_map.keys())
65
+
66
+ def test_get_serializer_field_attr(self, weekday, metric_key):
67
+ metric_field = metric_key.subfields[0]
68
+ backend = InstrumentMetricBaseBackend(weekday)
69
+ assert backend.get_serializer_field_attr(metric_field)["percent"] == metric_field.serializer_kwargs["percent"]
70
+ assert (
71
+ backend.get_serializer_field_attr(metric_field)["precision"] == metric_field.serializer_kwargs["precision"]
72
+ )
73
+ assert backend.get_serializer_field_attr(metric_field)["decorators"] == metric_field.decorators
@@ -0,0 +1,72 @@
1
+ import pytest
2
+ from django.contrib.contenttypes.models import ContentType
3
+ from wbfdm.models import Instrument
4
+
5
+ from ..dto import Metric
6
+ from ..models import InstrumentMetric
7
+ from ..registry import backend_registry
8
+
9
+
10
+ @pytest.mark.django_db
11
+ class TestInstrumentMetric:
12
+ def test_save(self, instrument_metric):
13
+ assert instrument_metric.basket_repr == str(instrument_metric.basket)
14
+
15
+ def test_update_or_create_from_metric(self, weekday, instrument_factory):
16
+ i1 = instrument_factory.create()
17
+ i2 = instrument_factory.create()
18
+ i3 = instrument_factory.create()
19
+ key = "key"
20
+ basket_content_type_id = ContentType.objects.get_for_model(i1).id
21
+ level1_dto_metric = Metric(
22
+ basket_id=i1.id,
23
+ basket_content_type_id=basket_content_type_id,
24
+ key=key,
25
+ metrics={"a": "a"},
26
+ date=weekday,
27
+ )
28
+ level1_dto_metric = Metric(
29
+ basket_id=i2.id,
30
+ basket_content_type_id=basket_content_type_id,
31
+ key=key,
32
+ metrics={"b": "b"},
33
+ date=weekday,
34
+ dependency_metrics=[level1_dto_metric],
35
+ )
36
+ base_dto_metric = Metric(
37
+ basket_id=i3.id,
38
+ basket_content_type_id=basket_content_type_id,
39
+ key=key,
40
+ metrics={"c": "c"},
41
+ date=weekday,
42
+ dependency_metrics=[level1_dto_metric],
43
+ )
44
+ InstrumentMetric.update_or_create_from_metric(base_dto_metric)
45
+
46
+ level2_metric = InstrumentMetric.objects.get(
47
+ basket_id=i1.id, basket_content_type_id=basket_content_type_id, date=weekday, key=key
48
+ )
49
+ level1_metric = InstrumentMetric.objects.get(
50
+ basket_id=i2.id, basket_content_type_id=basket_content_type_id, date=weekday, key=key
51
+ )
52
+ base_metric = InstrumentMetric.objects.get(
53
+ basket_id=i3.id, basket_content_type_id=basket_content_type_id, date=weekday, key=key
54
+ )
55
+
56
+ assert level2_metric.metrics == {"a": "a"}
57
+ assert level2_metric.parent_metric == level1_metric
58
+
59
+ assert level1_metric.metrics == {"b": "b"}
60
+ assert level1_metric.parent_metric == base_metric
61
+
62
+ assert base_metric.metrics == {"c": "c"}
63
+
64
+ def test_annotate_with_metrics(self, instrument_metric):
65
+ qs = Instrument.objects.filter(id=instrument_metric.basket_id)
66
+ qs = InstrumentMetric.annotate_with_metrics(
67
+ qs, backend_registry._metric_key_map[instrument_metric.key], Instrument, val_date=instrument_metric.date
68
+ )
69
+ for field in instrument_metric.metrics.keys():
70
+ qs_field = instrument_metric.key + "_" + field
71
+ res = list(qs.values_list(qs_field, flat=True))[0]
72
+ assert res == instrument_metric.metrics[field]
@@ -0,0 +1,24 @@
1
+ from unittest.mock import patch
2
+
3
+ from faker import Faker
4
+
5
+ from ..dispatch import compute_metrics
6
+ from ..orchestrators import MetricOrchestrator
7
+ from ..tasks import compute_metrics_as_task
8
+
9
+ fake = Faker()
10
+
11
+
12
+ @patch.object(MetricOrchestrator, "process")
13
+ def test_dispatch_compute_metrics(mock_fct):
14
+ key = "performance"
15
+ basket = object()
16
+ val_date = fake.date_object()
17
+ compute_metrics(val_date, key=key, basket=basket)
18
+ mock_fct.assert_called_once()
19
+
20
+
21
+ @patch.object(MetricOrchestrator, "process")
22
+ def test_periodically_compute_all_metrics(mock_fct):
23
+ compute_metrics_as_task()
24
+ mock_fct.assert_called_once()
@@ -0,0 +1,79 @@
1
+ from datetime import date
2
+
3
+ import pytest
4
+ from faker import Faker
5
+ from rest_framework.test import APIRequestFactory
6
+ from wbcore import viewsets
7
+ from wbcore.utils.strings import get_aggregate_symbol
8
+ from wbfdm.contrib.metric.backends.performances import (
9
+ PERFORMANCE_METRIC,
10
+ InstrumentPerformanceMetricBackend,
11
+ )
12
+ from wbfdm.contrib.metric.models import InstrumentMetric
13
+ from wbfdm.models import Instrument
14
+ from wbfdm.serializers import InstrumentModelSerializer
15
+
16
+ from ..viewsets.mixins import InstrumentMetricMixin
17
+
18
+ fake = Faker()
19
+
20
+
21
+ @pytest.mark.django_db
22
+ class TestInstrumentMetricMixin:
23
+ @pytest.fixture
24
+ def viewset(self, instrument_metric_factory):
25
+ metric = instrument_metric_factory.create(key="performance")
26
+
27
+ class InstrumentWithMetricModelViewSet(InstrumentMetricMixin, viewsets.ModelViewSet):
28
+ METRIC_KEYS = (PERFORMANCE_METRIC,)
29
+ METRIC_WITH_PREFIXED_KEYS = fake.boolean()
30
+ serializer_class = InstrumentModelSerializer
31
+ queryset = Instrument.objects.all()
32
+
33
+ @property
34
+ def metric_basket(self):
35
+ return metric.basket
36
+
37
+ @property
38
+ def metric_date(self) -> date | None:
39
+ return metric.date
40
+
41
+ request = APIRequestFactory().get("")
42
+ return InstrumentWithMetricModelViewSet(request=request, format_kwarg={})
43
+
44
+ def test_metric_serializer_fields(self, weekday, viewset):
45
+ assert (
46
+ viewset._metric_serializer_fields.keys()
47
+ == InstrumentPerformanceMetricBackend(weekday)
48
+ .get_serializer_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS)
49
+ .keys()
50
+ )
51
+
52
+ def test_get_ordering_fields(self, viewset):
53
+ ordering_fields = viewset.get_ordering_fields()
54
+ for key, _ in PERFORMANCE_METRIC.get_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS):
55
+ assert key in ordering_fields
56
+
57
+ def test_get_queryset(self, viewset):
58
+ queryset = viewset.get_queryset()
59
+ annotations = list(queryset.query.annotations.keys())
60
+ for key, _ in PERFORMANCE_METRIC.get_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS):
61
+ assert key in annotations
62
+
63
+ def test_get_serializer(self, viewset):
64
+ serializer = viewset.get_serializer()
65
+ for key, _ in PERFORMANCE_METRIC.get_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS):
66
+ assert key in serializer.fields
67
+
68
+ def test_get_aggregates(self, viewset):
69
+ queryset = viewset.get_queryset()
70
+ aggregates = viewset.get_aggregates(queryset, queryset)
71
+ metric = InstrumentMetric.objects.first()
72
+ assert metric is not None
73
+ for key, _ in PERFORMANCE_METRIC.get_fields(with_prefixed_key=viewset.METRIC_WITH_PREFIXED_KEYS):
74
+ key_filter = PERFORMANCE_METRIC.subfields_filter_map[key]
75
+ subfield = PERFORMANCE_METRIC.subfields_map[key]
76
+ assert subfield.aggregate is not None
77
+
78
+ if key_filter in metric.metrics:
79
+ assert aggregates[key] == {get_aggregate_symbol(subfield.aggregate.name): metric.metrics[key_filter]}
@@ -0,0 +1,19 @@
1
+ from django.urls import include, path
2
+ from wbcore.routers import WBCoreRouter
3
+
4
+ from . import viewsets
5
+
6
+ router = WBCoreRouter()
7
+
8
+ # Representations
9
+ router.register(
10
+ r"instrumentmetricrepresentation",
11
+ viewsets.InstrumentMetricRepresentationViewSet,
12
+ basename="instrumentmetricrepresentation",
13
+ )
14
+
15
+ router.register(r"instrumentmetric", viewsets.InstrumentMetricViewSet, basename="instrumentmetric")
16
+
17
+ urlpatterns = [
18
+ path("", include(router.urls)),
19
+ ]
@@ -0,0 +1 @@
1
+ from .viewsets import InstrumentMetricRepresentationViewSet, InstrumentMetricViewSet
@@ -0,0 +1 @@
1
+ from .display import InstrumentMetricDisplayConfig
@@ -0,0 +1,92 @@
1
+ from typing import Any, Optional
2
+
3
+ from rest_framework.reverse import reverse
4
+ from wbcore.metadata.configs import display as dp
5
+ from wbcore.metadata.configs.display.instance_display import Display
6
+ from wbcore.metadata.configs.display.instance_display.shortcuts import (
7
+ create_simple_display,
8
+ create_simple_section,
9
+ )
10
+ from wbcore.metadata.configs.display.instance_display.utils import repeat_field
11
+ from wbcore.metadata.configs.display.view_config import DisplayViewConfig
12
+ from wbfdm.contrib.metric.dto import MetricField
13
+
14
+
15
+ class InstrumentMetricDisplayConfig(DisplayViewConfig):
16
+ def get_list_display(self) -> Optional[dp.ListDisplay]:
17
+ return dp.ListDisplay(
18
+ fields=[
19
+ dp.Field(key="key", label="Key"),
20
+ dp.Field(key="date", label="Date"),
21
+ dp.Field(key="instrument", label="Instrument"),
22
+ dp.Field(key="parent_metric", label="Parent Metric"),
23
+ ],
24
+ tree=True,
25
+ tree_group_pinned="left",
26
+ tree_group_field="basket_repr",
27
+ tree_group_label="Basket",
28
+ tree_group_level_options=[
29
+ dp.TreeGroupLevelOption(
30
+ filter_key="parent_metric",
31
+ filter_depth=1,
32
+ # lookup="id_repr",
33
+ clear_filter=True,
34
+ list_endpoint=reverse(
35
+ "metric:instrumentmetric-list",
36
+ args=[],
37
+ request=self.request,
38
+ ),
39
+ )
40
+ ],
41
+ )
42
+
43
+ def get_instance_display(self) -> Display:
44
+ children_metrics_section = create_simple_section(
45
+ "children_metrics_section", "Child Metrics", [["children_metrics"]], "children_metrics", collapsed=True
46
+ )
47
+ return create_simple_display(
48
+ [
49
+ ["basket_content_type", "basket_id", "basket_repr"],
50
+ ["date", "key", "instrument"],
51
+ [repeat_field(3, "metrics")],
52
+ [repeat_field(3, "children_metrics")],
53
+ ],
54
+ [children_metrics_section],
55
+ )
56
+
57
+
58
+ class InstrumentMetricPivotedListDisplayConfig(DisplayViewConfig):
59
+ """
60
+ Instrument metric Class to register automatically metrics fields into the list display.
61
+
62
+ The view attribute is expected to inherit from InstrumentMetricMixin
63
+ """
64
+
65
+ def _get_metric_field_attr(self, field: MetricField) -> dict[str, Any]:
66
+ attrs = dict(width=75)
67
+ attrs.update(field.list_display_kwargs)
68
+ return attrs
69
+
70
+ def _get_metric_list_display(self) -> dp.ListDisplay:
71
+ metrics = []
72
+
73
+ for metric_key in self.view.metric_keys:
74
+ metric_label = metric_key.label
75
+ fields = []
76
+ for subfield in metric_key.subfields:
77
+ field_key = f"{metric_key.key}_{subfield.key}"
78
+ fields.append(dp.Field(key=field_key, label=subfield.label, **self._get_metric_field_attr(subfield)))
79
+ metrics.append(dp.Field(key=None, label=metric_label, children=fields))
80
+
81
+ return dp.ListDisplay(fields=[dp.Field(label="Metrics", open_by_default=False, key=None, children=metrics)])
82
+
83
+ def _get_metadata(self) -> Any:
84
+ display = self.get_metadata()
85
+ if "list" in display:
86
+ list_display = self._get_metric_list_display()
87
+ if isinstance(list_display, Display):
88
+ metrics_list_display = list_display.serialize()
89
+ else:
90
+ metrics_list_display = dict(list_display or {})
91
+ display["list"]["fields"].extend(metrics_list_display["fields"])
92
+ return display
@@ -0,0 +1,11 @@
1
+ from wbcore.menus import ItemPermission, MenuItem
2
+ from wbcore.permissions.shortcuts import is_internal_user
3
+
4
+ INSTRUMENTMETRIC_MENUITEM = MenuItem(
5
+ label="Metrics",
6
+ endpoint="metric:instrumentmetric-list",
7
+ endpoint_get_parameters={"parent_metric__isnull": True},
8
+ permission=ItemPermission(
9
+ permissions=["metric.view_instrumentmetric"], method=lambda request: is_internal_user(request.user)
10
+ ),
11
+ )