deepfos 1.1.60__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.
Files changed (175) hide show
  1. deepfos/__init__.py +6 -0
  2. deepfos/_version.py +21 -0
  3. deepfos/algo/__init__.py +0 -0
  4. deepfos/algo/graph.py +171 -0
  5. deepfos/algo/segtree.py +31 -0
  6. deepfos/api/V1_1/__init__.py +0 -0
  7. deepfos/api/V1_1/business_model.py +119 -0
  8. deepfos/api/V1_1/dimension.py +599 -0
  9. deepfos/api/V1_1/models/__init__.py +0 -0
  10. deepfos/api/V1_1/models/business_model.py +1033 -0
  11. deepfos/api/V1_1/models/dimension.py +2768 -0
  12. deepfos/api/V1_2/__init__.py +0 -0
  13. deepfos/api/V1_2/dimension.py +285 -0
  14. deepfos/api/V1_2/models/__init__.py +0 -0
  15. deepfos/api/V1_2/models/dimension.py +2923 -0
  16. deepfos/api/__init__.py +0 -0
  17. deepfos/api/account.py +167 -0
  18. deepfos/api/accounting_engines.py +147 -0
  19. deepfos/api/app.py +626 -0
  20. deepfos/api/approval_process.py +198 -0
  21. deepfos/api/base.py +983 -0
  22. deepfos/api/business_model.py +160 -0
  23. deepfos/api/consolidation.py +129 -0
  24. deepfos/api/consolidation_process.py +106 -0
  25. deepfos/api/datatable.py +341 -0
  26. deepfos/api/deep_pipeline.py +61 -0
  27. deepfos/api/deepconnector.py +36 -0
  28. deepfos/api/deepfos_task.py +92 -0
  29. deepfos/api/deepmodel.py +188 -0
  30. deepfos/api/dimension.py +486 -0
  31. deepfos/api/financial_model.py +319 -0
  32. deepfos/api/journal_model.py +119 -0
  33. deepfos/api/journal_template.py +132 -0
  34. deepfos/api/memory_financial_model.py +98 -0
  35. deepfos/api/models/__init__.py +3 -0
  36. deepfos/api/models/account.py +483 -0
  37. deepfos/api/models/accounting_engines.py +756 -0
  38. deepfos/api/models/app.py +1338 -0
  39. deepfos/api/models/approval_process.py +1043 -0
  40. deepfos/api/models/base.py +234 -0
  41. deepfos/api/models/business_model.py +805 -0
  42. deepfos/api/models/consolidation.py +711 -0
  43. deepfos/api/models/consolidation_process.py +248 -0
  44. deepfos/api/models/datatable_mysql.py +427 -0
  45. deepfos/api/models/deep_pipeline.py +55 -0
  46. deepfos/api/models/deepconnector.py +28 -0
  47. deepfos/api/models/deepfos_task.py +386 -0
  48. deepfos/api/models/deepmodel.py +308 -0
  49. deepfos/api/models/dimension.py +1576 -0
  50. deepfos/api/models/financial_model.py +1796 -0
  51. deepfos/api/models/journal_model.py +341 -0
  52. deepfos/api/models/journal_template.py +854 -0
  53. deepfos/api/models/memory_financial_model.py +478 -0
  54. deepfos/api/models/platform.py +178 -0
  55. deepfos/api/models/python.py +221 -0
  56. deepfos/api/models/reconciliation_engine.py +411 -0
  57. deepfos/api/models/reconciliation_report.py +161 -0
  58. deepfos/api/models/role_strategy.py +884 -0
  59. deepfos/api/models/smartlist.py +237 -0
  60. deepfos/api/models/space.py +1137 -0
  61. deepfos/api/models/system.py +1065 -0
  62. deepfos/api/models/variable.py +463 -0
  63. deepfos/api/models/workflow.py +946 -0
  64. deepfos/api/platform.py +199 -0
  65. deepfos/api/python.py +90 -0
  66. deepfos/api/reconciliation_engine.py +181 -0
  67. deepfos/api/reconciliation_report.py +64 -0
  68. deepfos/api/role_strategy.py +234 -0
  69. deepfos/api/smartlist.py +69 -0
  70. deepfos/api/space.py +582 -0
  71. deepfos/api/system.py +372 -0
  72. deepfos/api/variable.py +154 -0
  73. deepfos/api/workflow.py +264 -0
  74. deepfos/boost/__init__.py +6 -0
  75. deepfos/boost/py_jstream.py +89 -0
  76. deepfos/boost/py_pandas.py +20 -0
  77. deepfos/cache.py +121 -0
  78. deepfos/config.py +6 -0
  79. deepfos/core/__init__.py +27 -0
  80. deepfos/core/cube/__init__.py +10 -0
  81. deepfos/core/cube/_base.py +462 -0
  82. deepfos/core/cube/constants.py +21 -0
  83. deepfos/core/cube/cube.py +408 -0
  84. deepfos/core/cube/formula.py +707 -0
  85. deepfos/core/cube/syscube.py +532 -0
  86. deepfos/core/cube/typing.py +7 -0
  87. deepfos/core/cube/utils.py +238 -0
  88. deepfos/core/dimension/__init__.py +11 -0
  89. deepfos/core/dimension/_base.py +506 -0
  90. deepfos/core/dimension/dimcreator.py +184 -0
  91. deepfos/core/dimension/dimension.py +472 -0
  92. deepfos/core/dimension/dimexpr.py +271 -0
  93. deepfos/core/dimension/dimmember.py +155 -0
  94. deepfos/core/dimension/eledimension.py +22 -0
  95. deepfos/core/dimension/filters.py +99 -0
  96. deepfos/core/dimension/sysdimension.py +168 -0
  97. deepfos/core/logictable/__init__.py +5 -0
  98. deepfos/core/logictable/_cache.py +141 -0
  99. deepfos/core/logictable/_operator.py +663 -0
  100. deepfos/core/logictable/nodemixin.py +673 -0
  101. deepfos/core/logictable/sqlcondition.py +609 -0
  102. deepfos/core/logictable/tablemodel.py +497 -0
  103. deepfos/db/__init__.py +36 -0
  104. deepfos/db/cipher.py +660 -0
  105. deepfos/db/clickhouse.py +191 -0
  106. deepfos/db/connector.py +195 -0
  107. deepfos/db/daclickhouse.py +171 -0
  108. deepfos/db/dameng.py +101 -0
  109. deepfos/db/damysql.py +189 -0
  110. deepfos/db/dbkits.py +358 -0
  111. deepfos/db/deepengine.py +99 -0
  112. deepfos/db/deepmodel.py +82 -0
  113. deepfos/db/deepmodel_kingbase.py +83 -0
  114. deepfos/db/edb.py +214 -0
  115. deepfos/db/gauss.py +83 -0
  116. deepfos/db/kingbase.py +83 -0
  117. deepfos/db/mysql.py +184 -0
  118. deepfos/db/oracle.py +131 -0
  119. deepfos/db/postgresql.py +192 -0
  120. deepfos/db/sqlserver.py +99 -0
  121. deepfos/db/utils.py +135 -0
  122. deepfos/element/__init__.py +89 -0
  123. deepfos/element/accounting.py +348 -0
  124. deepfos/element/apvlprocess.py +215 -0
  125. deepfos/element/base.py +398 -0
  126. deepfos/element/bizmodel.py +1269 -0
  127. deepfos/element/datatable.py +2467 -0
  128. deepfos/element/deep_pipeline.py +186 -0
  129. deepfos/element/deepconnector.py +59 -0
  130. deepfos/element/deepmodel.py +1806 -0
  131. deepfos/element/dimension.py +1254 -0
  132. deepfos/element/fact_table.py +427 -0
  133. deepfos/element/finmodel.py +1485 -0
  134. deepfos/element/journal.py +840 -0
  135. deepfos/element/journal_template.py +943 -0
  136. deepfos/element/pyscript.py +412 -0
  137. deepfos/element/reconciliation.py +553 -0
  138. deepfos/element/rolestrategy.py +243 -0
  139. deepfos/element/smartlist.py +457 -0
  140. deepfos/element/variable.py +756 -0
  141. deepfos/element/workflow.py +560 -0
  142. deepfos/exceptions/__init__.py +239 -0
  143. deepfos/exceptions/hook.py +86 -0
  144. deepfos/lazy.py +104 -0
  145. deepfos/lazy_import.py +84 -0
  146. deepfos/lib/__init__.py +0 -0
  147. deepfos/lib/_javaobj.py +366 -0
  148. deepfos/lib/asynchronous.py +879 -0
  149. deepfos/lib/concurrency.py +107 -0
  150. deepfos/lib/constant.py +39 -0
  151. deepfos/lib/decorator.py +310 -0
  152. deepfos/lib/deepchart.py +778 -0
  153. deepfos/lib/deepux.py +477 -0
  154. deepfos/lib/discovery.py +273 -0
  155. deepfos/lib/edb_lexer.py +789 -0
  156. deepfos/lib/eureka.py +156 -0
  157. deepfos/lib/filterparser.py +751 -0
  158. deepfos/lib/httpcli.py +106 -0
  159. deepfos/lib/jsonstreamer.py +80 -0
  160. deepfos/lib/msg.py +394 -0
  161. deepfos/lib/nacos.py +225 -0
  162. deepfos/lib/patch.py +92 -0
  163. deepfos/lib/redis.py +241 -0
  164. deepfos/lib/serutils.py +181 -0
  165. deepfos/lib/stopwatch.py +99 -0
  166. deepfos/lib/subtask.py +572 -0
  167. deepfos/lib/sysutils.py +703 -0
  168. deepfos/lib/utils.py +1003 -0
  169. deepfos/local.py +160 -0
  170. deepfos/options.py +670 -0
  171. deepfos/translation.py +237 -0
  172. deepfos-1.1.60.dist-info/METADATA +33 -0
  173. deepfos-1.1.60.dist-info/RECORD +175 -0
  174. deepfos-1.1.60.dist-info/WHEEL +5 -0
  175. deepfos-1.1.60.dist-info/top_level.txt +1 -0
@@ -0,0 +1,532 @@
1
+ import asyncio
2
+ from typing import Optional, List, Union, Iterable, Container, Dict, Any, Callable, Set
3
+ from enum import Enum, auto
4
+ import pandas as pd
5
+ from loguru import logger
6
+
7
+ from ._base import CubeBase, CalcSet
8
+ from .constants import NAME_DFLT, PNAME_DFLT, WEIGHT
9
+ from .utils import Zero, dict_to_sql
10
+ from deepfos.core.dimension import Dimension, SysDimension, DimMember, ElementDimension
11
+ from deepfos.api.models.dimension import ElementQueryBaseDto
12
+ from deepfos.api.dimension import DimensionAPI
13
+ from deepfos.element.finmodel import FinancialCube
14
+ from deepfos.lib.constant import DFLT_DATA_COLUMN, VIEW, ROOT, SHAREDMEMBER
15
+ from deepfos.lib.utils import get_ignore_case, dict_to_expr, unpack_expr, CIEnum, expr_to_dict
16
+ from deepfos.lib.asynchronous import evloop
17
+ from deepfos.lib.concurrency import ThreadCtxExecutor
18
+
19
+ T_IStr = Union[str, Iterable[str]]
20
+ T_AggFunc = Callable[
21
+ [pd.DataFrame, DimMember, Container[str], Set[str]],
22
+ Optional[pd.Series]
23
+ ]
24
+
25
+
26
+ class DimensionType(CIEnum):
27
+ element = 'element'
28
+ lazy = 'lazy'
29
+ complete = 'complete'
30
+
31
+
32
+ class ClearStrategy(Enum):
33
+ recursive = auto()
34
+ non_recursvie = auto()
35
+
36
+
37
+ class SysCube(CubeBase):
38
+ """系统财务Cube
39
+
40
+ 包含成员公式,自动汇总功能。
41
+
42
+ Args:
43
+ cube_name: 财务模型名称
44
+ folder_id: 元素所在的文件夹id
45
+ path: 元素所在的文件夹绝对路径
46
+ fetch_dim: 是否提前获取完整维度树
47
+ extra_dim_attrs: 维度需要额外查询的属性
48
+ data_col: 数据列列名
49
+ use_dim_element: 是否使用维度元素,在仅使用fix功能时,开启此选项会大幅提高性能
50
+
51
+ Note:
52
+ :class:`SysCube` 需要在 ``python`` 中解析维度表达式以实现它的一些算法。
53
+ 因此在实例化时,会尝试获取所有维度的成员信息。
54
+ 为了减少网络及内存开销,默认在查询维度时,只会查询
55
+ ``name, parent_name, aggweight`` 三个属性。这种情况下,
56
+ 如果你在 ``fix`` 表达式中使用了 ``ud`` 作为维度的筛选条件,
57
+ 例如 ``fix`` 中包含:
58
+
59
+ ``Entity{AndFilter(Base(#root,0),Attr(ud1,"WL"))}``
60
+
61
+ 将导致报错。为了解决上述问题,引入 ``extra_dim_attrs`` 用于指定维度需要额外查询的属性。
62
+ 传入如下参数即可:
63
+
64
+ .. code-block:: python
65
+
66
+ extra_dim_attrs = {
67
+ "Entity": ["ud1"]
68
+ }
69
+
70
+ """
71
+ def __init__(
72
+ self,
73
+ cube_name: str,
74
+ folder_id: str = None,
75
+ path: str = None,
76
+ fetch_dim: bool = True,
77
+ extra_dim_attrs: Dict[str, List[str]] = None,
78
+ data_col: str = DFLT_DATA_COLUMN,
79
+ use_dim_element: bool = False,
80
+ **options
81
+ ):
82
+ super().__init__(cube_name, data_col, **options)
83
+
84
+ self.cube = FinancialCube(cube_name, folder_id, path)
85
+ extra_dim_attrs = extra_dim_attrs or {}
86
+ if use_dim_element:
87
+ self.dim_type = DimensionType.element
88
+ elif fetch_dim:
89
+ self.dim_type = DimensionType.complete
90
+ else:
91
+ self.dim_type = DimensionType.lazy
92
+
93
+ evloop.run(self._initialize_dimensions(extra_dim_attrs))
94
+
95
+ async def _initialize_dimensions(self, extra_dim_attrs):
96
+ cube = self.cube
97
+ jobs = [
98
+ self._dim_init(dim_name, cube, self.dim_type, extra_dim_attrs.get(dim_name, []))
99
+ for dim_name in cube.dim_elements.keys()
100
+ ]
101
+ jobs.append(self._dim_init_view(cube))
102
+
103
+ await asyncio.gather(*jobs)
104
+
105
+ async def _dim_init(self, dim_name: str, cube: FinancialCube, dim_type: DimensionType, extra_attr: List[str]):
106
+ dim_column = cube.dim_col_map[dim_name]
107
+ dim_element = cube.dim_elements[dim_name]
108
+
109
+ if dim_type == DimensionType.complete:
110
+ data = await dim_element.table_dimension.select_raw(
111
+ [NAME_DFLT, PNAME_DFLT, SHAREDMEMBER, WEIGHT] + extra_attr
112
+ )
113
+ dim = Dimension.from_json(dim_name, data, extra_info=extra_attr)
114
+ dim.root.name = ROOT
115
+ elif dim_type == DimensionType.lazy:
116
+ dim = SysDimension(
117
+ dim_name,
118
+ tbl_closure=dim_element.table_closure,
119
+ tbl_dim=dim_element.table_dimension,
120
+ name_only=True
121
+ )
122
+ else:
123
+ dim = ElementDimension(dim_element)
124
+
125
+ self.dimensions[dim_name, dim_column] = dim
126
+
127
+ async def _dim_init_view(self, cube: FinancialCube):
128
+ """初始化View维度"""
129
+ if not cube._meta.autoCalculation:
130
+ return
131
+
132
+ period = get_ignore_case(cube.dimensions, 'Period', None)
133
+ if period is None:
134
+ raise ValueError(
135
+ "Cannot not initialize dimension 'View' because "
136
+ "no dimension named 'Period'.")
137
+
138
+ api = await DimensionAPI(module_id=period.moduleId, sync=False)
139
+ view_info = await api.query.get_view_by_period(ElementQueryBaseDto(
140
+ elementName=period.name,
141
+ folderId=period.folderId,
142
+ showAll=True
143
+ ))
144
+
145
+ view = Dimension(dim_name=VIEW, root=DimMember(ROOT))
146
+ root = view.root
147
+ for item in view_info:
148
+ view.attach(DimMember(item.name), root)
149
+
150
+ self.dimensions[VIEW, VIEW] = view
151
+
152
+ def _load_fix_single(self, fix: str) -> pd.DataFrame:
153
+ return self.cube.query(fix, compact=False)
154
+
155
+ def submit_calc_result(self):
156
+ """提交计算结果
157
+
158
+ 将fix上下文中成员公式的计算结果提交至系统。
159
+ 会清空计算结果。
160
+ """
161
+ if self.calc_set is None:
162
+ return
163
+ self.calc_set.submit(self.cube)
164
+ self.calc_set: Optional[CalcSet] = None
165
+
166
+ def _load_fix_data(self, fix_exprs: List[str]) -> pd.DataFrame:
167
+ return self.cube.queries(fix_exprs, drop_duplicates=False)
168
+
169
+ def load_expr(self, cube_expr: str):
170
+ if self.dim_type != DimensionType.element:
171
+ return super().load_expr(cube_expr)
172
+ for dim_expr in cube_expr.split('->'):
173
+ dimname, expr = unpack_expr(dim_expr)
174
+ if dimname not in self.dimensions:
175
+ raise ValueError(f"Given Dimension '{dimname}' doesn't belong to cube.")
176
+
177
+ self.dimensions[dimname].load_expr(expr)
178
+
179
+ return self
180
+
181
+ # -----------------------------------------------------------------------------
182
+ # 聚合逻辑
183
+ def _find_parent_mbrs(
184
+ self,
185
+ dimname: str,
186
+ members: T_IStr,
187
+ strategy: ClearStrategy
188
+ ) -> Dict[str, List[str]]:
189
+ dim = self.dimensions[dimname]
190
+ if isinstance(members, str):
191
+ members = [members]
192
+
193
+ parent_mbs = set()
194
+ for mbr in members:
195
+ if strategy is ClearStrategy.recursive:
196
+ if mbr == ROOT:
197
+ all_mbrs = dim[mbr].Descendant
198
+ else:
199
+ all_mbrs = dim[mbr].IDescendant
200
+ parent_mbs.update(all_mbrs.where('and', is_leaf=False).data)
201
+ else:
202
+ parent_mbs.add(mbr)
203
+
204
+ return {dimname: list(parent_mbs)}
205
+
206
+ def _agg_impl(
207
+ self,
208
+ dim2mbr: Dict[str, T_IStr],
209
+ hierarchy: str,
210
+ agg: T_AggFunc,
211
+ fix: str,
212
+ submit: bool,
213
+ clear_strategy: ClearStrategy = None,
214
+ ):
215
+ """汇总函数实现
216
+
217
+ 因为idescendant和ichildren逻辑太像,
218
+ 简单抽出这个实现函数,没有设计。
219
+ """
220
+ fix_as_dict = expr_to_dict(fix)
221
+
222
+ def merge_fix(dim: str, mbr: T_IStr):
223
+ if dim in fix_as_dict:
224
+ new_fix = fix_as_dict.copy()
225
+ new_fix.pop(dim)
226
+ fix_str = dict_to_expr(new_fix)
227
+ else:
228
+ fix_str = fix
229
+ return f"{fix_str}->{dict_to_expr({dim: mbr}, hierarchy=hierarchy)}"
230
+
231
+ def get_clear_expr(dim: str, mbr: T_IStr):
232
+ expr_dict = self._find_parent_mbrs(dim, mbr, clear_strategy)
233
+
234
+ for dimname, expr in fix_as_dict.items():
235
+ if dimname == dim:
236
+ continue
237
+ dimension = self.dimensions[dimname]
238
+ with dimension.load_expr_temporary(expr):
239
+ expr_dict[dimname] = dimension.data
240
+
241
+ return expr_dict
242
+
243
+ if len(dim2mbr) == 1:
244
+ dim, mbr = list(dim2mbr.items())[0]
245
+ expr = merge_fix(dim, mbr)
246
+ data = self.cube.query(expr, compact=False)
247
+ if clear_strategy:
248
+ self.cube.insert_null(get_clear_expr(dim, mbr))
249
+ else:
250
+ exprs = (
251
+ merge_fix(k, v)
252
+ for k, v in dim2mbr.items()
253
+ )
254
+ data = self.cube.queries(exprs, drop_duplicates=True)
255
+
256
+ if clear_strategy:
257
+ with ThreadCtxExecutor() as executor:
258
+ for k, v in dim2mbr.items():
259
+ executor.submit(self.cube.insert_null, get_clear_expr(k, v))
260
+
261
+ if data.empty:
262
+ if not submit:
263
+ return data
264
+ return
265
+
266
+ # 用于过滤父节点,即最后要提交的数据
267
+ filter_map = {}
268
+ dim_col_map = self.cube.dim_col_map
269
+
270
+ for dim, members in dim2mbr.items():
271
+ if isinstance(members, str):
272
+ members = [members]
273
+
274
+ # 维度名需要转化为列名
275
+ filter_key = dim_col_map.get(dim, dim)
276
+ filter_map[filter_key] = pnodes = set()
277
+ for member in members:
278
+ data = self._agg_single(
279
+ agg, data,
280
+ self.dimensions[dim], pnodes, member
281
+ )
282
+
283
+ query = f"`{self.data_col}` != @Zero and " + \
284
+ dict_to_sql(filter_map, '==', concat='or', bracket=True)
285
+
286
+ logger.debug(f"Filter submit data with condition: {query}")
287
+ save_data = data.query(query, engine='python')
288
+ if not submit:
289
+ return save_data
290
+ return self.cube.save(save_data)
291
+
292
+ def _agg_single(
293
+ self,
294
+ agg: T_AggFunc,
295
+ data: pd.DataFrame,
296
+ dimension: Dimension,
297
+ parent_nodes: Set[str],
298
+ from_member: str = ROOT,
299
+ ) -> pd.DataFrame:
300
+ """单维度聚合
301
+
302
+ Args:
303
+ agg: 聚合函数
304
+ data: cube数据
305
+ dimension: 聚合维度
306
+ parent_nodes: 空集合,存储聚合过程中产生的父节点
307
+ from_member: 聚合的目标成员
308
+ """
309
+ pivot_col = self.cube.dim_col_map[dimension.name]
310
+ index_cols = data.columns.difference({DFLT_DATA_COLUMN, pivot_col}).tolist()
311
+
312
+ data = data.pivot_table(
313
+ index=index_cols, values=DFLT_DATA_COLUMN,
314
+ columns=pivot_col, aggfunc='first', fill_value=Zero
315
+ ).reset_index()
316
+
317
+ mbr_cols = data.columns.difference(index_cols)
318
+ # inplace 聚合data
319
+ agg(data, dimension[from_member], mbr_cols, parent_nodes)
320
+
321
+ # ---------------------------- unpivot ----------------------------------
322
+ # 有新增的父节点列,需要更新
323
+ total_mbr_cols = data.columns.difference(index_cols)
324
+ data = data.melt(
325
+ id_vars=index_cols, value_vars=total_mbr_cols,
326
+ var_name=pivot_col, value_name=DFLT_DATA_COLUMN
327
+ )
328
+ return data
329
+
330
+ def _agg_top_down(
331
+ self,
332
+ data: pd.DataFrame,
333
+ mbr: DimMember,
334
+ valid_cols: Container[str],
335
+ parent_nodes: Set[str],
336
+ ) -> Optional[pd.Series]:
337
+ """自顶向下递归聚合父节点
338
+
339
+ Important:
340
+ 这段代码的执行顺序很重要,不要乱改。
341
+ 自定义Zero用于处理不存在数据的成员,避免算出多余的0
342
+ """
343
+ name = mbr.name
344
+
345
+ if mbr.is_leaf:
346
+ if name in valid_cols:
347
+ return data[name]
348
+ else:
349
+ return Zero
350
+
351
+ # 算过的共享节点不需要再次计算
352
+ if name in parent_nodes:
353
+ logger.debug(f"Skip member: {name}. As it has been calculated.")
354
+ return data[name]
355
+
356
+ series = [
357
+ self._agg_top_down(data, child, valid_cols, parent_nodes)
358
+ *
359
+ getattr(child, WEIGHT, 1.0)
360
+ for child in mbr.children
361
+ ]
362
+ # root节点应该隐藏
363
+ if mbr.is_root:
364
+ return
365
+
366
+ sum_ = sum(series, Zero)
367
+ if sum_ is not Zero:
368
+ data[name] = sum_
369
+ parent_nodes.add(name)
370
+ return sum_
371
+
372
+ def idescendant(
373
+ self,
374
+ fix: str,
375
+ dimensions: Union[T_IStr, Dict[str, T_IStr]],
376
+ submit: bool = True,
377
+ clear_agg_nodes: bool = False,
378
+ ) -> Union[pd.DataFrame, Any]:
379
+ """递归汇总维度
380
+
381
+ 根据维度表达式确定的数据范围、以及需要汇总的维度和成员。
382
+ 按顺序递归汇总所有目标节点下的非叶子节点。
383
+
384
+ Args:
385
+ fix: 维度表达式,确定汇总涉及的数据范围
386
+ dimensions: 需要汇总的维度
387
+ submit: 是否将汇总结果直接入库
388
+ clear_agg_nodes: 是否在保存前先删除汇总节点的数据
389
+
390
+ Hint:
391
+ 入参 ``dimension`` 支持三种格式
392
+
393
+ - :obj:`str` : 维度名称,表示汇总至该维度的根节点
394
+ - :obj:`Iterable[str]` : 多个维度名称,表示按顺序进行汇总,汇总至根节点
395
+ - :obj:`Dict` : 维度名称 -> 维度成员(可多个),表示按顺序汇总到指定的单个或多个成员
396
+
397
+ Important:
398
+ - 对于 ``fix`` 中没有出现的维度,会以 ``Base(#root,0)`` 自动补全表达式。
399
+ - 最终保存的数据将只包含本次参与汇总的父节点,叶子(Base)节点不会参与保存。
400
+
401
+ .. admonition:: 示例
402
+
403
+ .. code-block:: python
404
+
405
+ fix = 'Year{2021;2022}->Entiy{Base(TotalEntity,0)}'
406
+ cube = SysCube('example')
407
+ # 汇总至Account维度根节点
408
+ cube.idescendant(fix, 'Account')
409
+ # 依次汇总Account维度,Version维度至根节点
410
+ cube.idescendant(fix, ['Account', 'Version'])
411
+ # 先汇总Account至CostA, CostB,再汇总Version维度至Version1
412
+ cube.idescendant(fix, {
413
+ 'Account': ['CostA', 'CostB'],
414
+ 'Version': 'Version1'
415
+ })
416
+
417
+ Returns:
418
+ - 如果不提交汇总结果,将返回汇总结果,无汇总数据返回空 :obj:`DataFrame`
419
+ - 如果提交汇总结果,将返回数据保存结果,无汇总数据返回 :obj:`None`
420
+
421
+ See Also:
422
+ 如果不需要递归汇总,请使用 :meth:`ichildren`
423
+
424
+ """
425
+ if self.dim_type != DimensionType.complete:
426
+ raise RuntimeError(
427
+ "Failed to call 'idescendant' because dimension tree "
428
+ "is not properly fetched. Set `fetch_dim=True` "
429
+ "while instantiate a SysCube.")
430
+
431
+ if isinstance(dimensions, dict):
432
+ dim2mbr = dimensions
433
+ elif isinstance(dimensions, str):
434
+ dim2mbr = {dimensions: ROOT}
435
+ else:
436
+ dim2mbr = {dim: ROOT for dim in dimensions}
437
+
438
+ return self._agg_impl(
439
+ dim2mbr,
440
+ 'Base',
441
+ self._agg_top_down,
442
+ fix,
443
+ submit,
444
+ clear_strategy=ClearStrategy.recursive if clear_agg_nodes and submit else None
445
+ )
446
+
447
+ # noinspection PyMethodMayBeStatic
448
+ def _agg_children(
449
+ self,
450
+ data: pd.DataFrame,
451
+ mbr: DimMember,
452
+ valid_cols: Container[str],
453
+ parent_nodes: Set[str],
454
+ ) -> Optional[pd.Series]:
455
+ """聚合父节点"""
456
+ if mbr.is_root:
457
+ raise RuntimeError("Cannot execute ichildren for root member.")
458
+
459
+ series = []
460
+
461
+ for child in mbr.children:
462
+ if (cname := child.name) in valid_cols:
463
+ series.append(data[cname] * getattr(child, WEIGHT, 1.0))
464
+ else:
465
+ series.append(Zero)
466
+
467
+ sum_ = sum(series, Zero)
468
+ if sum_ is not Zero:
469
+ data[mbr.name] = sum_
470
+ parent_nodes.add(mbr.name)
471
+ return sum_
472
+
473
+ def ichildren(
474
+ self,
475
+ fix: str,
476
+ dimensions: Dict[str, T_IStr],
477
+ submit: bool = True,
478
+ clear_agg_nodes: bool = False,
479
+ ) -> Union[pd.DataFrame, Any]:
480
+ """维度自动汇总
481
+
482
+ 根据维度表达式确定的数据范围、以及需要汇总的维度和成员。
483
+ 按顺序汇总所有目标节点。汇总依据仅为目标节点的子节点,
484
+ 如果子节点无数据,则不触发汇总,不会递归向下寻找叶子节点。
485
+
486
+ Args:
487
+ fix: 维度表达式,确定汇总涉及的数据范围
488
+ dimensions: 需要汇总的维度
489
+ submit: 是否将汇总结果直接入库
490
+ clear_agg_nodes: 是否在保存前先删除汇总节点的数据
491
+
492
+ Hint:
493
+ 入参 ``dimension`` 需符合格式:
494
+ 维度名称 -> 维度成员(可多个),表示按顺序汇总到指定的单个或多个成员
495
+
496
+ Important:
497
+ - 对于 ``fix`` 中没有出现的维度,会以 ``Base(#root,0)`` 自动补全表达式。
498
+ - 最终保存的数据将只包含本次参与汇总的父节点。
499
+
500
+ .. admonition:: 示例
501
+
502
+ .. code-block:: python
503
+
504
+ fix = 'Year{2021;2022}->Entiy{Base(TotalEntity,0)}'
505
+ cube = SysCube('example')
506
+ # 先汇总Account至CostA, CostB,再汇总Version维度至Version1
507
+ cube.ichildren(fix, {
508
+ 'Account': ['CostA', 'CostB'],
509
+ 'Version': 'Version1'
510
+ })
511
+
512
+ Returns:
513
+ - 如果不提交汇总结果,将返回汇总结果,无汇总数据返回空 :obj:`DataFrame`
514
+ - 如果提交汇总结果,将返回数据保存结果,无汇总数据返回 :obj:`None`
515
+
516
+ See Also:
517
+ 如果需要递归汇总,请使用 :meth:`idescendant`
518
+ """
519
+ if self.dim_type != DimensionType.complete:
520
+ raise RuntimeError(
521
+ "Failed to call 'ichildren' because dimension tree "
522
+ "is not properly fetched. Set `fetch_dim=True` "
523
+ "while instantiate a SysCube.")
524
+
525
+ return self._agg_impl(
526
+ dimensions,
527
+ 'Children',
528
+ self._agg_children,
529
+ fix,
530
+ submit,
531
+ clear_strategy=ClearStrategy.non_recursvie if clear_agg_nodes and submit else None
532
+ )
@@ -0,0 +1,7 @@
1
+ from typing import List, Dict, Union, Callable
2
+
3
+ TD_Str_ListStr = Dict[str, List[str]]
4
+ TD_Str_Str = Dict[str, str]
5
+ T_MaybeCondition = Union[str, Callable]
6
+
7
+