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.
- deepfos/__init__.py +6 -0
- deepfos/_version.py +21 -0
- deepfos/algo/__init__.py +0 -0
- deepfos/algo/graph.py +171 -0
- deepfos/algo/segtree.py +31 -0
- deepfos/api/V1_1/__init__.py +0 -0
- deepfos/api/V1_1/business_model.py +119 -0
- deepfos/api/V1_1/dimension.py +599 -0
- deepfos/api/V1_1/models/__init__.py +0 -0
- deepfos/api/V1_1/models/business_model.py +1033 -0
- deepfos/api/V1_1/models/dimension.py +2768 -0
- deepfos/api/V1_2/__init__.py +0 -0
- deepfos/api/V1_2/dimension.py +285 -0
- deepfos/api/V1_2/models/__init__.py +0 -0
- deepfos/api/V1_2/models/dimension.py +2923 -0
- deepfos/api/__init__.py +0 -0
- deepfos/api/account.py +167 -0
- deepfos/api/accounting_engines.py +147 -0
- deepfos/api/app.py +626 -0
- deepfos/api/approval_process.py +198 -0
- deepfos/api/base.py +983 -0
- deepfos/api/business_model.py +160 -0
- deepfos/api/consolidation.py +129 -0
- deepfos/api/consolidation_process.py +106 -0
- deepfos/api/datatable.py +341 -0
- deepfos/api/deep_pipeline.py +61 -0
- deepfos/api/deepconnector.py +36 -0
- deepfos/api/deepfos_task.py +92 -0
- deepfos/api/deepmodel.py +188 -0
- deepfos/api/dimension.py +486 -0
- deepfos/api/financial_model.py +319 -0
- deepfos/api/journal_model.py +119 -0
- deepfos/api/journal_template.py +132 -0
- deepfos/api/memory_financial_model.py +98 -0
- deepfos/api/models/__init__.py +3 -0
- deepfos/api/models/account.py +483 -0
- deepfos/api/models/accounting_engines.py +756 -0
- deepfos/api/models/app.py +1338 -0
- deepfos/api/models/approval_process.py +1043 -0
- deepfos/api/models/base.py +234 -0
- deepfos/api/models/business_model.py +805 -0
- deepfos/api/models/consolidation.py +711 -0
- deepfos/api/models/consolidation_process.py +248 -0
- deepfos/api/models/datatable_mysql.py +427 -0
- deepfos/api/models/deep_pipeline.py +55 -0
- deepfos/api/models/deepconnector.py +28 -0
- deepfos/api/models/deepfos_task.py +386 -0
- deepfos/api/models/deepmodel.py +308 -0
- deepfos/api/models/dimension.py +1576 -0
- deepfos/api/models/financial_model.py +1796 -0
- deepfos/api/models/journal_model.py +341 -0
- deepfos/api/models/journal_template.py +854 -0
- deepfos/api/models/memory_financial_model.py +478 -0
- deepfos/api/models/platform.py +178 -0
- deepfos/api/models/python.py +221 -0
- deepfos/api/models/reconciliation_engine.py +411 -0
- deepfos/api/models/reconciliation_report.py +161 -0
- deepfos/api/models/role_strategy.py +884 -0
- deepfos/api/models/smartlist.py +237 -0
- deepfos/api/models/space.py +1137 -0
- deepfos/api/models/system.py +1065 -0
- deepfos/api/models/variable.py +463 -0
- deepfos/api/models/workflow.py +946 -0
- deepfos/api/platform.py +199 -0
- deepfos/api/python.py +90 -0
- deepfos/api/reconciliation_engine.py +181 -0
- deepfos/api/reconciliation_report.py +64 -0
- deepfos/api/role_strategy.py +234 -0
- deepfos/api/smartlist.py +69 -0
- deepfos/api/space.py +582 -0
- deepfos/api/system.py +372 -0
- deepfos/api/variable.py +154 -0
- deepfos/api/workflow.py +264 -0
- deepfos/boost/__init__.py +6 -0
- deepfos/boost/py_jstream.py +89 -0
- deepfos/boost/py_pandas.py +20 -0
- deepfos/cache.py +121 -0
- deepfos/config.py +6 -0
- deepfos/core/__init__.py +27 -0
- deepfos/core/cube/__init__.py +10 -0
- deepfos/core/cube/_base.py +462 -0
- deepfos/core/cube/constants.py +21 -0
- deepfos/core/cube/cube.py +408 -0
- deepfos/core/cube/formula.py +707 -0
- deepfos/core/cube/syscube.py +532 -0
- deepfos/core/cube/typing.py +7 -0
- deepfos/core/cube/utils.py +238 -0
- deepfos/core/dimension/__init__.py +11 -0
- deepfos/core/dimension/_base.py +506 -0
- deepfos/core/dimension/dimcreator.py +184 -0
- deepfos/core/dimension/dimension.py +472 -0
- deepfos/core/dimension/dimexpr.py +271 -0
- deepfos/core/dimension/dimmember.py +155 -0
- deepfos/core/dimension/eledimension.py +22 -0
- deepfos/core/dimension/filters.py +99 -0
- deepfos/core/dimension/sysdimension.py +168 -0
- deepfos/core/logictable/__init__.py +5 -0
- deepfos/core/logictable/_cache.py +141 -0
- deepfos/core/logictable/_operator.py +663 -0
- deepfos/core/logictable/nodemixin.py +673 -0
- deepfos/core/logictable/sqlcondition.py +609 -0
- deepfos/core/logictable/tablemodel.py +497 -0
- deepfos/db/__init__.py +36 -0
- deepfos/db/cipher.py +660 -0
- deepfos/db/clickhouse.py +191 -0
- deepfos/db/connector.py +195 -0
- deepfos/db/daclickhouse.py +171 -0
- deepfos/db/dameng.py +101 -0
- deepfos/db/damysql.py +189 -0
- deepfos/db/dbkits.py +358 -0
- deepfos/db/deepengine.py +99 -0
- deepfos/db/deepmodel.py +82 -0
- deepfos/db/deepmodel_kingbase.py +83 -0
- deepfos/db/edb.py +214 -0
- deepfos/db/gauss.py +83 -0
- deepfos/db/kingbase.py +83 -0
- deepfos/db/mysql.py +184 -0
- deepfos/db/oracle.py +131 -0
- deepfos/db/postgresql.py +192 -0
- deepfos/db/sqlserver.py +99 -0
- deepfos/db/utils.py +135 -0
- deepfos/element/__init__.py +89 -0
- deepfos/element/accounting.py +348 -0
- deepfos/element/apvlprocess.py +215 -0
- deepfos/element/base.py +398 -0
- deepfos/element/bizmodel.py +1269 -0
- deepfos/element/datatable.py +2467 -0
- deepfos/element/deep_pipeline.py +186 -0
- deepfos/element/deepconnector.py +59 -0
- deepfos/element/deepmodel.py +1806 -0
- deepfos/element/dimension.py +1254 -0
- deepfos/element/fact_table.py +427 -0
- deepfos/element/finmodel.py +1485 -0
- deepfos/element/journal.py +840 -0
- deepfos/element/journal_template.py +943 -0
- deepfos/element/pyscript.py +412 -0
- deepfos/element/reconciliation.py +553 -0
- deepfos/element/rolestrategy.py +243 -0
- deepfos/element/smartlist.py +457 -0
- deepfos/element/variable.py +756 -0
- deepfos/element/workflow.py +560 -0
- deepfos/exceptions/__init__.py +239 -0
- deepfos/exceptions/hook.py +86 -0
- deepfos/lazy.py +104 -0
- deepfos/lazy_import.py +84 -0
- deepfos/lib/__init__.py +0 -0
- deepfos/lib/_javaobj.py +366 -0
- deepfos/lib/asynchronous.py +879 -0
- deepfos/lib/concurrency.py +107 -0
- deepfos/lib/constant.py +39 -0
- deepfos/lib/decorator.py +310 -0
- deepfos/lib/deepchart.py +778 -0
- deepfos/lib/deepux.py +477 -0
- deepfos/lib/discovery.py +273 -0
- deepfos/lib/edb_lexer.py +789 -0
- deepfos/lib/eureka.py +156 -0
- deepfos/lib/filterparser.py +751 -0
- deepfos/lib/httpcli.py +106 -0
- deepfos/lib/jsonstreamer.py +80 -0
- deepfos/lib/msg.py +394 -0
- deepfos/lib/nacos.py +225 -0
- deepfos/lib/patch.py +92 -0
- deepfos/lib/redis.py +241 -0
- deepfos/lib/serutils.py +181 -0
- deepfos/lib/stopwatch.py +99 -0
- deepfos/lib/subtask.py +572 -0
- deepfos/lib/sysutils.py +703 -0
- deepfos/lib/utils.py +1003 -0
- deepfos/local.py +160 -0
- deepfos/options.py +670 -0
- deepfos/translation.py +237 -0
- deepfos-1.1.60.dist-info/METADATA +33 -0
- deepfos-1.1.60.dist-info/RECORD +175 -0
- deepfos-1.1.60.dist-info/WHEEL +5 -0
- deepfos-1.1.60.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import operator
|
|
3
|
+
import functools
|
|
4
|
+
import weakref
|
|
5
|
+
from collections import defaultdict, UserList
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from typing import List, Dict, Tuple, Iterable, Union, Callable, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from .constants import OPMAP, Instruction
|
|
12
|
+
from .typing import TD_Str_ListStr, TD_Str_Str, T_MaybeCondition
|
|
13
|
+
from .utils import AutoFillSeries
|
|
14
|
+
from deepfos.core.logictable.nodemixin import NodeMixin
|
|
15
|
+
from deepfos.core.dimension.sysdimension import unpack_expr
|
|
16
|
+
from deepfos.lib.decorator import cached_property
|
|
17
|
+
from deepfos.lib.utils import dict_to_expr
|
|
18
|
+
from loguru import logger
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# -----------------------------------------------------------------------------
|
|
22
|
+
# constant
|
|
23
|
+
|
|
24
|
+
K_MEMBER = 'mbr'
|
|
25
|
+
K_EXPR = 'expr'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# -----------------------------------------------------------------------------
|
|
29
|
+
# Base Classes
|
|
30
|
+
class ValNode(NodeMixin):
|
|
31
|
+
"""变量节点"""
|
|
32
|
+
def __init__(self, value):
|
|
33
|
+
self.value = value
|
|
34
|
+
|
|
35
|
+
def prepare(self):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def fix_mbrs(self) -> List[TD_Str_ListStr]:
|
|
40
|
+
"""节点的锁定信息,以 维度名:维度成员列表 格式储存"""
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def fix_exprs(self) -> List[TD_Str_Str]:
|
|
45
|
+
"""节点的锁定信息,以 维度名:维度表达式 格式储存"""
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
def __str__(self):
|
|
49
|
+
return str(self.value)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OpNode(NodeMixin):
|
|
53
|
+
"""运算符节点"""
|
|
54
|
+
def __init__(self, op):
|
|
55
|
+
self.op = op
|
|
56
|
+
|
|
57
|
+
def __str__(self):
|
|
58
|
+
return self.op
|
|
59
|
+
|
|
60
|
+
def solve(self):
|
|
61
|
+
args = []
|
|
62
|
+
for child in self.children:
|
|
63
|
+
if isinstance(child, ValNode):
|
|
64
|
+
args.append(child.value)
|
|
65
|
+
elif isinstance(child, self.__class__):
|
|
66
|
+
args.append(child.solve())
|
|
67
|
+
else:
|
|
68
|
+
raise TypeError(f"Expect type {self.__class__} or {ValNode}, got {type(child)}")
|
|
69
|
+
return self.calc(*args)
|
|
70
|
+
|
|
71
|
+
def calc(self, left, right):
|
|
72
|
+
op = getattr(operator, self.op, None)
|
|
73
|
+
if op is None:
|
|
74
|
+
raise ValueError(f"Unknown operator: {self.op}.")
|
|
75
|
+
|
|
76
|
+
return op(left, right)
|
|
77
|
+
|
|
78
|
+
def get_formula(self):
|
|
79
|
+
args = []
|
|
80
|
+
|
|
81
|
+
for child in self.children:
|
|
82
|
+
if isinstance(child, ValNode):
|
|
83
|
+
args.append(str(child))
|
|
84
|
+
elif isinstance(child, self.__class__):
|
|
85
|
+
args.append(child.get_formula())
|
|
86
|
+
else:
|
|
87
|
+
raise TypeError(f"Expect type {self.__class__} or {ValNode}, got {type(child)}")
|
|
88
|
+
op_repr = f" {OPMAP.get(self.op, self.op)} "
|
|
89
|
+
return "(" + op_repr.join(args) + ")"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class CalcUnit:
|
|
93
|
+
"""计算单元"""
|
|
94
|
+
def __init__(self, op_cls=OpNode):
|
|
95
|
+
self._op_cls = op_cls
|
|
96
|
+
self.root = None
|
|
97
|
+
|
|
98
|
+
@cached_property
|
|
99
|
+
def as_node(self) -> ValNode:
|
|
100
|
+
return NotImplemented
|
|
101
|
+
|
|
102
|
+
def _validate(self, other):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
def _attach(self, other, new_root, reverse=False):
|
|
106
|
+
if isinstance(other, CalcUnit):
|
|
107
|
+
self._validate(other)
|
|
108
|
+
val_node = other.root or other.as_node
|
|
109
|
+
else:
|
|
110
|
+
val_node = ValNode(other)
|
|
111
|
+
|
|
112
|
+
if reverse:
|
|
113
|
+
val_node.set_parent(new_root)
|
|
114
|
+
|
|
115
|
+
if self.root is None:
|
|
116
|
+
self.as_node.set_parent(new_root)
|
|
117
|
+
else:
|
|
118
|
+
self.root.set_parent(new_root)
|
|
119
|
+
|
|
120
|
+
if not reverse:
|
|
121
|
+
val_node.set_parent(new_root)
|
|
122
|
+
|
|
123
|
+
self.root = new_root
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
def __add__(self, other):
|
|
127
|
+
return self._attach(other, self._op_cls('add'))
|
|
128
|
+
|
|
129
|
+
def __sub__(self, other):
|
|
130
|
+
return self._attach(other, self._op_cls('sub'))
|
|
131
|
+
|
|
132
|
+
def __mul__(self, other):
|
|
133
|
+
return self._attach(other, self._op_cls('mul'))
|
|
134
|
+
|
|
135
|
+
def __truediv__(self, other):
|
|
136
|
+
return self._attach(other, self._op_cls('truediv'))
|
|
137
|
+
|
|
138
|
+
def __mod__(self, other):
|
|
139
|
+
return self._attach(other, self._op_cls('mod'))
|
|
140
|
+
|
|
141
|
+
def __pow__(self, other):
|
|
142
|
+
return self._attach(other, self._op_cls('pow'))
|
|
143
|
+
|
|
144
|
+
def __floordiv__(self, other):
|
|
145
|
+
return self._attach(other, self._op_cls('floordiv'))
|
|
146
|
+
|
|
147
|
+
def __radd__(self, other):
|
|
148
|
+
return self._attach(other, self._op_cls('add'), True)
|
|
149
|
+
|
|
150
|
+
def __rsub__(self, other):
|
|
151
|
+
return self._attach(other, self._op_cls('sub'), True)
|
|
152
|
+
|
|
153
|
+
def __rmul__(self, other):
|
|
154
|
+
return self._attach(other, self._op_cls('mul'), True)
|
|
155
|
+
|
|
156
|
+
def __rtruediv__(self, other):
|
|
157
|
+
return self._attach(other, self._op_cls('truediv'), True)
|
|
158
|
+
|
|
159
|
+
def __rmod__(self, other):
|
|
160
|
+
return self._attach(other, self._op_cls('mod'), True)
|
|
161
|
+
|
|
162
|
+
def __rpow__(self, other):
|
|
163
|
+
return self._attach(other, self._op_cls('pow'), True)
|
|
164
|
+
|
|
165
|
+
def __rfloordiv__(self, other):
|
|
166
|
+
return self._attach(other, self._op_cls('floordiv'), True)
|
|
167
|
+
|
|
168
|
+
def __str__(self):
|
|
169
|
+
if self.root is None:
|
|
170
|
+
return str(self.as_node)
|
|
171
|
+
return self.root.get_formula()
|
|
172
|
+
|
|
173
|
+
def iter_node(self):
|
|
174
|
+
if self.root is None:
|
|
175
|
+
node = self.as_node
|
|
176
|
+
if isinstance(node, ValNode):
|
|
177
|
+
yield node
|
|
178
|
+
else:
|
|
179
|
+
for node in self.root.iter_descendants():
|
|
180
|
+
if isinstance(node, ValNode):
|
|
181
|
+
yield node
|
|
182
|
+
|
|
183
|
+
def _node_init(self):
|
|
184
|
+
# 初始化所有计算节点,获取参与计算的数据集
|
|
185
|
+
for node in self.iter_node():
|
|
186
|
+
node.prepare()
|
|
187
|
+
|
|
188
|
+
def solve(self):
|
|
189
|
+
self._node_init()
|
|
190
|
+
if self.root is None:
|
|
191
|
+
# 只有一个赋值语句,没有计算
|
|
192
|
+
return self.as_node.value
|
|
193
|
+
return self.root.solve()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# -----------------------------------------------------------------------------
|
|
197
|
+
# Cube Nodes
|
|
198
|
+
class MemberNode(ValNode):
|
|
199
|
+
"""
|
|
200
|
+
根据第一个入参的不同,存在两种行为。
|
|
201
|
+
1. 如果是一般实数,value即实数值,prepare将无行为
|
|
202
|
+
2. 其他情况,prepare将根据fix条件对DataFrame作必要预处理。获取可以参与计算的数据列。
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
# noinspection PyMissingConstructor
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
cube,
|
|
209
|
+
indicator: str = None,
|
|
210
|
+
hook: Callable = None,
|
|
211
|
+
fix_mbrs: TD_Str_ListStr = None,
|
|
212
|
+
fix_exprs: TD_Str_Str = None,
|
|
213
|
+
calc_dim: str = None,
|
|
214
|
+
calc_mbr: str = None,
|
|
215
|
+
):
|
|
216
|
+
self.hook = hook
|
|
217
|
+
self.indicator = indicator
|
|
218
|
+
self._cube = cube
|
|
219
|
+
self._fix_mbrs = fix_mbrs or {}
|
|
220
|
+
self._fix_exprs = fix_exprs or {}
|
|
221
|
+
self._calc_dim = calc_dim
|
|
222
|
+
self._calc_mbr = calc_mbr
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def fix_mbrs(self) -> List[TD_Str_ListStr]:
|
|
226
|
+
"""
|
|
227
|
+
来自计算节点 `CubeCalcUnit` 的 :attr: `_extra_fix`
|
|
228
|
+
包括计算维度(如Account['Price'])和on条件。
|
|
229
|
+
"""
|
|
230
|
+
return [self._fix_mbrs]
|
|
231
|
+
|
|
232
|
+
@cached_property
|
|
233
|
+
def on(self) -> TD_Str_ListStr:
|
|
234
|
+
fix_mbrs = {**self._fix_mbrs}
|
|
235
|
+
fix_mbrs.pop(self._calc_dim, None)
|
|
236
|
+
return fix_mbrs
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def fix_exprs(self) -> List[TD_Str_Str]:
|
|
240
|
+
"""
|
|
241
|
+
类似于 :attr:`fix_mbrs`
|
|
242
|
+
"""
|
|
243
|
+
return [self._fix_exprs]
|
|
244
|
+
|
|
245
|
+
def _pass(self):
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
def prepare(self):
|
|
249
|
+
self._cube.calc_set.load_fixes(
|
|
250
|
+
self._calc_mbr, self._calc_dim,
|
|
251
|
+
self.on, str(self)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def value(self):
|
|
256
|
+
column = str(self) if self.on else self._calc_mbr
|
|
257
|
+
data_src = self._cube.calc_set.data_proxy
|
|
258
|
+
|
|
259
|
+
if column not in data_src.columns:
|
|
260
|
+
value = AutoFillSeries(np.full(len(data_src), np.NAN))
|
|
261
|
+
else:
|
|
262
|
+
value = AutoFillSeries(data_src[column])
|
|
263
|
+
|
|
264
|
+
value.custom_options = self._cube.option_stack[0] # noqa
|
|
265
|
+
|
|
266
|
+
if self.hook is not None:
|
|
267
|
+
value = self.hook(value)
|
|
268
|
+
|
|
269
|
+
return value
|
|
270
|
+
|
|
271
|
+
def __str__(self):
|
|
272
|
+
return self.indicator
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class FunctionNode(ValNode):
|
|
276
|
+
def __init__(self, func, *args, **kwargs):
|
|
277
|
+
super().__init__(None)
|
|
278
|
+
self.func = func
|
|
279
|
+
self._args = args
|
|
280
|
+
self._kwargs = kwargs
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def fix_mbrs(self) -> List[TD_Str_ListStr]:
|
|
284
|
+
return sum((node.fix_mbrs for node in self._iter_arg_node()), [])
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def fix_exprs(self) -> List[TD_Str_Str]:
|
|
288
|
+
return sum((node.fix_exprs for node in self._iter_arg_node()), [])
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def _resolve_node(arg):
|
|
292
|
+
if isinstance(arg, CalcUnit):
|
|
293
|
+
node = arg.as_node
|
|
294
|
+
if isinstance(node, ValNode):
|
|
295
|
+
node.prepare()
|
|
296
|
+
return node.value
|
|
297
|
+
else:
|
|
298
|
+
return arg
|
|
299
|
+
|
|
300
|
+
def prepare(self):
|
|
301
|
+
"""
|
|
302
|
+
1. 对参数中所有计算节点进行初始化;
|
|
303
|
+
2. 获取所有参数并且进行对齐;
|
|
304
|
+
3. 替换对齐后的参数,调用函数;
|
|
305
|
+
4. 将返回结果包装为 CubeVariable,赋值至value
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
args = list(self._args)
|
|
309
|
+
kwargs = {**self._kwargs}
|
|
310
|
+
|
|
311
|
+
for idx, arg in enumerate(args):
|
|
312
|
+
args[idx] = self._resolve_node(arg)
|
|
313
|
+
|
|
314
|
+
for key, val in self._kwargs.items():
|
|
315
|
+
kwargs[key] = self._resolve_node(val)
|
|
316
|
+
|
|
317
|
+
self.value = self.func(*args, **kwargs)
|
|
318
|
+
|
|
319
|
+
def _iter_arg_node(self) -> Iterable[ValNode]:
|
|
320
|
+
"""遍历包含在函数参数内的所有节点"""
|
|
321
|
+
for item in self._args:
|
|
322
|
+
if isinstance(item, CalcUnit):
|
|
323
|
+
yield from item.iter_node()
|
|
324
|
+
|
|
325
|
+
for item in self._kwargs.values():
|
|
326
|
+
if isinstance(item, CalcUnit):
|
|
327
|
+
yield from item.iter_node()
|
|
328
|
+
|
|
329
|
+
def __str__(self):
|
|
330
|
+
sig = list(map(str, self._args))
|
|
331
|
+
for k, v in self._kwargs.items():
|
|
332
|
+
sig.append(f"{k}={str(v)}")
|
|
333
|
+
|
|
334
|
+
return f"{self.func.__name__}({', '.join(sig)})"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def as_function_node(func):
|
|
338
|
+
"""
|
|
339
|
+
装饰器,将普通函数作为函数节点使用。
|
|
340
|
+
用于cube多维计算。
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
func:
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
|
|
347
|
+
"""
|
|
348
|
+
@functools.wraps(func)
|
|
349
|
+
def wrapper(*args, **kwargs):
|
|
350
|
+
return FuncCalcUnit(func=func, args=args, kwargs=kwargs)
|
|
351
|
+
return wrapper
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# -----------------------------------------------------------------------------
|
|
355
|
+
# Formula elements
|
|
356
|
+
def _get_calc_dim(expr: str, cube) -> Tuple[str, str]:
|
|
357
|
+
dim, member = unpack_expr(expr, silent=True)
|
|
358
|
+
if dim is not None:
|
|
359
|
+
return dim, member
|
|
360
|
+
|
|
361
|
+
if not cube.calc_dim:
|
|
362
|
+
raise ValueError(f"Cannot resolve dimension from expr: {expr}.")
|
|
363
|
+
|
|
364
|
+
dim = cube.calc_dim[-1]
|
|
365
|
+
return dim, member
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class ValueCalcUnit(CalcUnit):
|
|
369
|
+
if TYPE_CHECKING:
|
|
370
|
+
from .cube import CubeBase
|
|
371
|
+
_cube: CubeBase
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
记录计算逻辑,以及各个计算单元的限定条件。
|
|
375
|
+
支持各种四则运算符,但是不立即触发计算。
|
|
376
|
+
|
|
377
|
+
会将所有参与计算的成员包装成CubeValNode, CubeFunctionNode等对象的节点。
|
|
378
|
+
|
|
379
|
+
当调用solve方法时,会调用所有节点的prepare方法,再求解所有算式。
|
|
380
|
+
"""
|
|
381
|
+
def __init__(self, cube, calc_mbr: str):
|
|
382
|
+
super().__init__()
|
|
383
|
+
self.pre_hook = None
|
|
384
|
+
self.post_hook = None
|
|
385
|
+
self.calc_dim, self.calc_mbr = _get_calc_dim(calc_mbr, cube)
|
|
386
|
+
self._cube = cube
|
|
387
|
+
self._extra_fix = {self.calc_dim: [self.calc_mbr]}
|
|
388
|
+
self._fix_str_memo = {self.calc_dim: self.calc_mbr}
|
|
389
|
+
|
|
390
|
+
def on(self, **dim_mbr_map: Union[str, int, float]):
|
|
391
|
+
for dimension, member in self._validate_dims(dim_mbr_map).items():
|
|
392
|
+
member = str(member)
|
|
393
|
+
self._extra_fix[dimension] = [member]
|
|
394
|
+
self._fix_str_memo[dimension] = member
|
|
395
|
+
return self
|
|
396
|
+
|
|
397
|
+
def _to_expr(self):
|
|
398
|
+
return '->'.join(f"{k}{{{v}}}" for k, v in self._fix_str_memo.items())
|
|
399
|
+
|
|
400
|
+
def _validate_dims(self, kwargs):
|
|
401
|
+
rtn = {
|
|
402
|
+
k: v for k, v in kwargs.items()
|
|
403
|
+
if k in self._cube.dimensions}
|
|
404
|
+
|
|
405
|
+
filtered = kwargs.keys() - rtn.keys()
|
|
406
|
+
if filtered:
|
|
407
|
+
raise ValueError(f"Dimensions : {filtered} does not belong to cube.")
|
|
408
|
+
return rtn
|
|
409
|
+
|
|
410
|
+
def set_hook(self, func: Callable, at: str):
|
|
411
|
+
if not callable(func):
|
|
412
|
+
raise TypeError(f"Argument must be callable.")
|
|
413
|
+
at = at.lower()
|
|
414
|
+
if at == 'pre':
|
|
415
|
+
self.pre_hook = func
|
|
416
|
+
elif at == 'post':
|
|
417
|
+
self.post_hook = func
|
|
418
|
+
return self
|
|
419
|
+
|
|
420
|
+
@cached_property
|
|
421
|
+
def as_node(self):
|
|
422
|
+
return MemberNode(
|
|
423
|
+
self._cube,
|
|
424
|
+
indicator=self._to_expr(),
|
|
425
|
+
hook=self.pre_hook,
|
|
426
|
+
fix_mbrs=self._extra_fix,
|
|
427
|
+
fix_exprs=self._fix_str_memo,
|
|
428
|
+
calc_dim=self.calc_dim,
|
|
429
|
+
calc_mbr=self.calc_mbr
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class FuncCalcUnit(CalcUnit):
|
|
434
|
+
def __init__(self, func, args, kwargs):
|
|
435
|
+
super().__init__()
|
|
436
|
+
self._args = args or ()
|
|
437
|
+
self._kwargs = kwargs or {}
|
|
438
|
+
self._func = func
|
|
439
|
+
|
|
440
|
+
@cached_property
|
|
441
|
+
def as_node(self):
|
|
442
|
+
return FunctionNode(
|
|
443
|
+
self._func,
|
|
444
|
+
*self._args,
|
|
445
|
+
**self._kwargs
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class CubeFixer:
|
|
450
|
+
def __init__(self, cube_obj):
|
|
451
|
+
self._cube = cube_obj
|
|
452
|
+
|
|
453
|
+
def __getitem__(self, item: str) -> ValueCalcUnit:
|
|
454
|
+
"""
|
|
455
|
+
cube.fix as 的返回对象在等式右边会调用此方法。
|
|
456
|
+
方法返回计算单元,计算单元将记录维度锁定条件以及
|
|
457
|
+
计算顺序。
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
item: 维度名或者 **单成员的维度表达式**
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
值类型计算单元
|
|
464
|
+
|
|
465
|
+
"""
|
|
466
|
+
return ValueCalcUnit(cube=self._cube, calc_mbr=item)
|
|
467
|
+
|
|
468
|
+
def __setitem__(self, key, value):
|
|
469
|
+
dim, member = _get_calc_dim(key, self._cube)
|
|
470
|
+
self._cube.formulas.append((dim, member, value))
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# -----------------------------------------------------------------------------
|
|
474
|
+
# Fomular Solver
|
|
475
|
+
class _Condition:
|
|
476
|
+
ENDIF = object()
|
|
477
|
+
|
|
478
|
+
def __init__(self, cond: T_MaybeCondition, flag):
|
|
479
|
+
self.cond = cond
|
|
480
|
+
self.flag = flag
|
|
481
|
+
self.filter = None
|
|
482
|
+
self._install_filter(cond)
|
|
483
|
+
|
|
484
|
+
def parse_str_cond(self, dataframe):
|
|
485
|
+
query_set = dataframe.query(self.cond)
|
|
486
|
+
if self.flag is True:
|
|
487
|
+
return query_set
|
|
488
|
+
else:
|
|
489
|
+
return dataframe.loc[dataframe.index.difference(query_set.index)]
|
|
490
|
+
|
|
491
|
+
def parse_callable_cond(self, dataframe):
|
|
492
|
+
query_set = self.cond(dataframe)
|
|
493
|
+
if self.flag is True:
|
|
494
|
+
return query_set
|
|
495
|
+
else:
|
|
496
|
+
return dataframe.loc[dataframe.index.difference(query_set.index)]
|
|
497
|
+
|
|
498
|
+
def _install_filter(self, cond):
|
|
499
|
+
if isinstance(cond, str):
|
|
500
|
+
self.filter = self.parse_str_cond
|
|
501
|
+
elif callable(cond):
|
|
502
|
+
self.filter = self.parse_callable_cond
|
|
503
|
+
|
|
504
|
+
def __str__(self):
|
|
505
|
+
return str(self.cond)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
class FormulaContainer(UserList):
|
|
509
|
+
def __init__(self, cube, initlist=None):
|
|
510
|
+
super().__init__(initlist)
|
|
511
|
+
self.cube = weakref.ref(cube)
|
|
512
|
+
#: 公式左边出现的维度->维度成员集合
|
|
513
|
+
self.left = defaultdict(set)
|
|
514
|
+
self.fix_by_mbrs: List[TD_Str_ListStr] = []
|
|
515
|
+
self._fix_by_expr: List[TD_Str_ListStr] = []
|
|
516
|
+
self.__left_fix_memo: Dict[str, Dict[str, TD_Str_ListStr]] = {}
|
|
517
|
+
|
|
518
|
+
@property
|
|
519
|
+
def query_expr_list(self) -> List[str]:
|
|
520
|
+
"""cube维度查询公式的列表"""
|
|
521
|
+
return [dict_to_expr(item) for item in self._fix_by_expr]
|
|
522
|
+
|
|
523
|
+
def _iter_with_filter(self):
|
|
524
|
+
calc_set = self.cube().calc_set
|
|
525
|
+
|
|
526
|
+
for item in self.data:
|
|
527
|
+
if isinstance(item, _Condition):
|
|
528
|
+
calc_set.add_filter(item)
|
|
529
|
+
elif item is _Condition.ENDIF:
|
|
530
|
+
calc_set.pop_filter()
|
|
531
|
+
elif isinstance(item, Instruction):
|
|
532
|
+
calc_set.add_instruction(item)
|
|
533
|
+
elif callable(item): # callbacks per line
|
|
534
|
+
item()
|
|
535
|
+
else:
|
|
536
|
+
yield item
|
|
537
|
+
|
|
538
|
+
@property
|
|
539
|
+
def _cube_dim_exprs(self) -> TD_Str_ListStr:
|
|
540
|
+
"""返回cube.fix中general_fix的结果。"""
|
|
541
|
+
return {
|
|
542
|
+
dimname: [dim.expr_body]
|
|
543
|
+
for dimname, dim in self.cube().dimensions.items()
|
|
544
|
+
if dim.activated
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
def append(self, item) -> None:
|
|
548
|
+
if isinstance(item, Tuple):
|
|
549
|
+
calc_dim, calc_mbr, right = item
|
|
550
|
+
general_mbr_fix = self.cube().dim_state
|
|
551
|
+
general_expr_fix = self._cube_dim_exprs
|
|
552
|
+
if isinstance(right, CalcUnit):
|
|
553
|
+
self._handle_calc_unit(
|
|
554
|
+
calc_dim,
|
|
555
|
+
calc_mbr,
|
|
556
|
+
general_expr_fix,
|
|
557
|
+
general_mbr_fix,
|
|
558
|
+
right
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
self.left[calc_dim].add(calc_mbr)
|
|
562
|
+
|
|
563
|
+
return self.data.append(item)
|
|
564
|
+
|
|
565
|
+
def _handle_calc_unit(
|
|
566
|
+
self,
|
|
567
|
+
calc_dim: str,
|
|
568
|
+
calc_mbr: str,
|
|
569
|
+
general_expr_fix: TD_Str_ListStr,
|
|
570
|
+
general_mbr_fix: TD_Str_ListStr,
|
|
571
|
+
calc_unit: CalcUnit
|
|
572
|
+
):
|
|
573
|
+
"""
|
|
574
|
+
根据当前提交的计算公式,整合成维度成员的查询表达式,
|
|
575
|
+
原则上生成的维度表达式数量应该尽可能少。
|
|
576
|
+
最终的维度表达式存放在 :attr:`query_expr_list` 中。
|
|
577
|
+
"""
|
|
578
|
+
|
|
579
|
+
for node in calc_unit.iter_node():
|
|
580
|
+
if len(node.fix_exprs) != len(node.fix_exprs):
|
|
581
|
+
raise RuntimeError("Formula is corrupted.")
|
|
582
|
+
|
|
583
|
+
for fix_mbrs, fix_expr in zip(node.fix_mbrs, node.fix_exprs):
|
|
584
|
+
if not fix_mbrs:
|
|
585
|
+
continue
|
|
586
|
+
if calc_dim not in fix_mbrs:
|
|
587
|
+
raise ValueError(
|
|
588
|
+
"Dimension to calculate is not consistent. "
|
|
589
|
+
f"Expect '{calc_dim}' in right-side expression. "
|
|
590
|
+
f"Got '{list(fix_mbrs.keys())}'.")
|
|
591
|
+
|
|
592
|
+
node_mbr = fix_expr[calc_dim]
|
|
593
|
+
|
|
594
|
+
if node_mbr in self.left.get(calc_dim, []):
|
|
595
|
+
# 对于已经在等式左边出现的,即使在右边出现也不需要查询
|
|
596
|
+
continue
|
|
597
|
+
|
|
598
|
+
if len(fix_mbrs) == 1:
|
|
599
|
+
# 仅一个条件则表示没有使用过on, 可以并入到公式左边的计算成员
|
|
600
|
+
if calc_dim not in self.__left_fix_memo:
|
|
601
|
+
self.__left_fix_memo[calc_dim] = fix_memo = {}
|
|
602
|
+
fix_memo[K_MEMBER] = mbr_memo = copy.deepcopy(general_mbr_fix)
|
|
603
|
+
fix_memo[K_EXPR] = expr_memo = copy.deepcopy(general_expr_fix)
|
|
604
|
+
mbr_memo[calc_dim] = []
|
|
605
|
+
expr_memo[calc_dim] = []
|
|
606
|
+
self.fix_by_mbrs.append(mbr_memo)
|
|
607
|
+
self._fix_by_expr.append(expr_memo)
|
|
608
|
+
else:
|
|
609
|
+
mbr_memo = self.__left_fix_memo[calc_dim][K_MEMBER]
|
|
610
|
+
expr_memo = self.__left_fix_memo[calc_dim][K_EXPR]
|
|
611
|
+
mbr_memo[calc_dim].extend(fix_mbrs[calc_dim])
|
|
612
|
+
expr_memo[calc_dim].append(fix_expr[calc_dim])
|
|
613
|
+
else:
|
|
614
|
+
# 使用过on, 需要创建全新的fix条件
|
|
615
|
+
new_mbr_fix = copy.deepcopy(general_mbr_fix)
|
|
616
|
+
for k, v in fix_mbrs.items():
|
|
617
|
+
new_mbr_fix[k] = v
|
|
618
|
+
self.fix_by_mbrs.append(new_mbr_fix)
|
|
619
|
+
|
|
620
|
+
new_expr_fix = copy.deepcopy(general_expr_fix)
|
|
621
|
+
for k, v in fix_expr.items():
|
|
622
|
+
new_expr_fix[k] = [v]
|
|
623
|
+
self._fix_by_expr.append(new_expr_fix)
|
|
624
|
+
|
|
625
|
+
def _assign(self):
|
|
626
|
+
calc_set = self.cube().calc_set
|
|
627
|
+
last_calc_dim = None
|
|
628
|
+
|
|
629
|
+
for calc_dim, mbr, right in self._iter_with_filter():
|
|
630
|
+
logger.debug(f"Start calculation: {calc_dim}{{{mbr}}}={right}")
|
|
631
|
+
|
|
632
|
+
if last_calc_dim is None:
|
|
633
|
+
calc_set.pivot(calc_dim)
|
|
634
|
+
elif last_calc_dim != calc_dim:
|
|
635
|
+
calc_set.unpivot(last_calc_dim)
|
|
636
|
+
calc_set.pivot(calc_dim)
|
|
637
|
+
|
|
638
|
+
last_calc_dim = calc_dim
|
|
639
|
+
|
|
640
|
+
if isinstance(right, CalcUnit):
|
|
641
|
+
value = right.solve()
|
|
642
|
+
post_hook = getattr(right, 'post_hook', None)
|
|
643
|
+
else:
|
|
644
|
+
value = right
|
|
645
|
+
post_hook = None
|
|
646
|
+
|
|
647
|
+
if post_hook:
|
|
648
|
+
value = post_hook(value)
|
|
649
|
+
|
|
650
|
+
calc_set.set_value(mbr, value)
|
|
651
|
+
|
|
652
|
+
if last_calc_dim is not None:
|
|
653
|
+
calc_set.unpivot(last_calc_dim)
|
|
654
|
+
|
|
655
|
+
def solve(self):
|
|
656
|
+
"""
|
|
657
|
+
成员公式求解
|
|
658
|
+
|
|
659
|
+
在进行计算前,会根据所有等式的fix条件查询出本次计算可能涉及的数据,
|
|
660
|
+
以下称为“计算集”.计算集会绑定至Cube对象,并且作为所有计算节点初始化的数据源。
|
|
661
|
+
|
|
662
|
+
对于每一个等式,计算分为两步:
|
|
663
|
+
|
|
664
|
+
1. 调用所有计算节点的prepare方法,该方法会根据fix条件装载需要参与
|
|
665
|
+
计算的 :class:`Series`,并且返回,
|
|
666
|
+
2. :class:`Series` 进行运算,并且将运算结果赋值回计算集,
|
|
667
|
+
以确保前面的计算结果可以正确影响后续计算。
|
|
668
|
+
"""
|
|
669
|
+
# noinspection PyProtectedMember
|
|
670
|
+
self.cube()._load_calc_set(
|
|
671
|
+
self.query_expr_list,
|
|
672
|
+
self.fix_by_mbrs
|
|
673
|
+
)
|
|
674
|
+
self._assign()
|
|
675
|
+
self.clear()
|
|
676
|
+
|
|
677
|
+
def clear(self) -> None:
|
|
678
|
+
"""删除公式列表中所有数据及状态"""
|
|
679
|
+
self.data.clear()
|
|
680
|
+
self.left.clear()
|
|
681
|
+
self.fix_by_mbrs.clear()
|
|
682
|
+
self._fix_by_expr.clear()
|
|
683
|
+
self.__left_fix_memo.clear()
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
class _ConditionWrapper:
|
|
687
|
+
def __init__(
|
|
688
|
+
self,
|
|
689
|
+
formula: FormulaContainer,
|
|
690
|
+
condition: T_MaybeCondition
|
|
691
|
+
):
|
|
692
|
+
self.condition = condition
|
|
693
|
+
self.formulas = formula
|
|
694
|
+
|
|
695
|
+
@property
|
|
696
|
+
@contextmanager
|
|
697
|
+
def true(self):
|
|
698
|
+
self.formulas.append(_Condition(self.condition, True))
|
|
699
|
+
yield
|
|
700
|
+
self.formulas.append(_Condition.ENDIF)
|
|
701
|
+
|
|
702
|
+
@property
|
|
703
|
+
@contextmanager
|
|
704
|
+
def false(self):
|
|
705
|
+
self.formulas.append(_Condition(self.condition, False))
|
|
706
|
+
yield
|
|
707
|
+
self.formulas.append(_Condition.ENDIF)
|