arelle-release 2.37.46__py3-none-any.whl → 2.37.48__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 arelle-release might be problematic. Click here for more details.
- arelle/CntlrCmdLine.py +10 -1
- arelle/ErrorManager.py +14 -5
- arelle/ModelObjectFactory.py +18 -2
- arelle/Validate.py +4 -0
- arelle/_version.py +2 -2
- arelle/plugin/validate/DBA/ValidationPluginExtension.py +2 -1
- arelle/plugin/validate/EDINET/ControllerPluginData.py +84 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +0 -114
- arelle/plugin/validate/EDINET/UploadContents.py +17 -0
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py +8 -2
- arelle/plugin/validate/EDINET/__init__.py +5 -0
- arelle/plugin/validate/EDINET/rules/upload.py +66 -75
- arelle/plugin/validate/NL/ValidationPluginExtension.py +3 -1
- arelle/plugin/validate/ROS/ValidationPluginExtension.py +3 -1
- arelle/utils/PluginHooks.py +32 -0
- arelle/utils/validate/ValidationPlugin.py +54 -8
- {arelle_release-2.37.46.dist-info → arelle_release-2.37.48.dist-info}/METADATA +1 -1
- {arelle_release-2.37.46.dist-info → arelle_release-2.37.48.dist-info}/RECORD +22 -106
- arelle/archive/CustomLogger.py +0 -43
- arelle/archive/LoadEFMvalidate.py +0 -32
- arelle/archive/LoadSavePreLbCsv.py +0 -26
- arelle/archive/LoadValidate.cs +0 -31
- arelle/archive/LoadValidate.py +0 -36
- arelle/archive/LoadValidateCmdLine.java +0 -69
- arelle/archive/LoadValidatePostedZip.java +0 -57
- arelle/archive/LoadValidateWebService.java +0 -34
- arelle/archive/SaveTableToExelle.py +0 -140
- arelle/archive/TR3toTR4.py +0 -88
- arelle/archive/plugin/ESEF_2022/__init__.py +0 -47
- arelle/archive/plugin/bigInstance.py +0 -394
- arelle/archive/plugin/cmdWebServerExtension.py +0 -43
- arelle/archive/plugin/crashTest.py +0 -38
- arelle/archive/plugin/functionsXmlCreation.py +0 -106
- arelle/archive/plugin/hello_i18n.pot +0 -26
- arelle/archive/plugin/hello_i18n.py +0 -32
- arelle/archive/plugin/importTestChild1.py +0 -21
- arelle/archive/plugin/importTestChild2.py +0 -22
- arelle/archive/plugin/importTestGrandchild1.py +0 -21
- arelle/archive/plugin/importTestGrandchild2.py +0 -21
- arelle/archive/plugin/importTestImported1.py +0 -23
- arelle/archive/plugin/importTestImported11.py +0 -22
- arelle/archive/plugin/importTestParent.py +0 -48
- arelle/archive/plugin/instanceInfo.py +0 -306
- arelle/archive/plugin/loadFromOIM-2018.py +0 -1282
- arelle/archive/plugin/locale/fr/LC_MESSAGES/hello_i18n.po +0 -25
- arelle/archive/plugin/objectmaker.py +0 -285
- arelle/archive/plugin/packagedImportTest/__init__.py +0 -47
- arelle/archive/plugin/packagedImportTest/importTestChild1.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestChild2.py +0 -22
- arelle/archive/plugin/packagedImportTest/importTestGrandchild1.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestGrandchild2.py +0 -21
- arelle/archive/plugin/packagedImportTest/importTestImported1.py +0 -24
- arelle/archive/plugin/packagedImportTest/importTestImported11.py +0 -21
- arelle/archive/plugin/packagedImportTest/subdir/importTestImported111.py +0 -21
- arelle/archive/plugin/packagedImportTest/subdir/subsubdir/importTestImported1111.py +0 -21
- arelle/archive/plugin/sakaCalendar.py +0 -215
- arelle/archive/plugin/saveInstanceInfoset.py +0 -121
- arelle/archive/plugin/sphinx/FormulaGenerator.py +0 -823
- arelle/archive/plugin/sphinx/SphinxContext.py +0 -404
- arelle/archive/plugin/sphinx/SphinxEvaluator.py +0 -783
- arelle/archive/plugin/sphinx/SphinxMethods.py +0 -1287
- arelle/archive/plugin/sphinx/SphinxParser.py +0 -1093
- arelle/archive/plugin/sphinx/SphinxValidator.py +0 -163
- arelle/archive/plugin/sphinx/US-GAAP Ratios Example.xsr +0 -52
- arelle/archive/plugin/sphinx/__init__.py +0 -285
- arelle/archive/plugin/streamingExtensions.py +0 -335
- arelle/archive/plugin/updateTableLB.py +0 -242
- arelle/archive/plugin/validate/SBRnl/CustomLoader.py +0 -19
- arelle/archive/plugin/validate/SBRnl/DTS.py +0 -305
- arelle/archive/plugin/validate/SBRnl/Dimensions.py +0 -357
- arelle/archive/plugin/validate/SBRnl/Document.py +0 -799
- arelle/archive/plugin/validate/SBRnl/Filing.py +0 -467
- arelle/archive/plugin/validate/SBRnl/__init__.py +0 -75
- arelle/archive/plugin/validate/SBRnl/config.xml +0 -26
- arelle/archive/plugin/validate/SBRnl/sbr-nl-taxonomies.xml +0 -754
- arelle/archive/plugin/validate/USBestPractices.py +0 -570
- arelle/archive/plugin/validate/USCorpAction.py +0 -557
- arelle/archive/plugin/validate/USSecTagging.py +0 -337
- arelle/archive/plugin/validate/XDC/__init__.py +0 -77
- arelle/archive/plugin/validate/XDC/config.xml +0 -20
- arelle/archive/plugin/validate/XFsyntax/__init__.py +0 -64
- arelle/archive/plugin/validate/XFsyntax/xf.py +0 -2227
- arelle/archive/plugin/validate/calc2.py +0 -536
- arelle/archive/plugin/validateSchemaLxml.py +0 -156
- arelle/archive/plugin/validateTableInfoset.py +0 -52
- arelle/archive/us-gaap-dei-docType-extraction-frm.xml +0 -90
- arelle/archive/us-gaap-dei-ratio-cash-frm.xml +0 -150
- arelle/examples/plugin/formulaSuiteConverter.py +0 -212
- arelle/examples/plugin/functionsCustom.py +0 -59
- arelle/examples/plugin/hello_dolly.py +0 -64
- arelle/examples/plugin/multi.py +0 -58
- arelle/examples/plugin/rssSaveOim.py +0 -96
- arelle/examples/plugin/validate/XYZ/DisclosureSystems.py +0 -2
- arelle/examples/plugin/validate/XYZ/PluginValidationDataExtension.py +0 -10
- arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py +0 -49
- arelle/examples/plugin/validate/XYZ/__init__.py +0 -75
- arelle/examples/plugin/validate/XYZ/resources/config.xml +0 -16
- arelle/examples/plugin/validate/XYZ/rules/__init__.py +0 -0
- arelle/examples/plugin/validate/XYZ/rules/rules01.py +0 -110
- arelle/examples/plugin/validate/XYZ/rules/rules02.py +0 -59
- arelle/scripts-macOS/startWebServer.command +0 -3
- arelle/scripts-unix/startWebServer.sh +0 -1
- arelle/scripts-windows/startWebServer.bat +0 -5
- {arelle_release-2.37.46.dist-info → arelle_release-2.37.48.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.46.dist-info → arelle_release-2.37.48.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.46.dist-info → arelle_release-2.37.48.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.46.dist-info → arelle_release-2.37.48.dist-info}/top_level.txt +0 -0
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
'''
|
|
2
|
-
See COPYRIGHT.md for copyright information.
|
|
3
|
-
'''
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
from math import isnan, isinf
|
|
7
|
-
from collections import defaultdict
|
|
8
|
-
from decimal import Decimal
|
|
9
|
-
from arelle import Locale
|
|
10
|
-
from arelle.PythonUtil import OrderedDefaultDict
|
|
11
|
-
from arelle.ValidateXbrlCalcs import ZERO, inferredDecimals, rangeValue
|
|
12
|
-
from arelle.Version import authorLabel, copyrightLabel
|
|
13
|
-
from arelle.XbrlConst import link, xbrli, xl, xlink, domainMember
|
|
14
|
-
|
|
15
|
-
calc2YYYY = "http://xbrl.org/WGWD/YYYY-MM-DD/calculation-2.0"
|
|
16
|
-
calc2 = {calc2YYYY}
|
|
17
|
-
calc2e = "http://xbrl.org/WGWD/YYYY-MM-DD/calculation-2.0/error"
|
|
18
|
-
sectionFact = "http://xbrl.org/arcrole/WGWD/YYYY-MM-DD/section-fact"
|
|
19
|
-
calc2linkroles = "{http://xbrl.org/WGWD/YYYY-MM-DD/calculation-2.0}linkroles"
|
|
20
|
-
summationItem = "http://xbrl.org/arcrole/WGWD/YYYY-MM-DD/summation-item" # calc2 summation-item arc role
|
|
21
|
-
balanceChanges = "http://xbrl.org/arcrole/WGWD/YYYY-MM-DD/balance-changes"
|
|
22
|
-
aggregationDomain = "http://xbrl.org/arcrole/WGWD/YYYY-MM-DD/aggregation-domain"
|
|
23
|
-
calc2Arcroles = (summationItem, balanceChanges, aggregationDomain)
|
|
24
|
-
|
|
25
|
-
def nominalPeriod(duration): # account for month and year lengths
|
|
26
|
-
if 364 < duration.days <= 366: return 365
|
|
27
|
-
if 28 <= duration.days <= 31: return 31
|
|
28
|
-
return duration.days
|
|
29
|
-
|
|
30
|
-
def intervalZero():
|
|
31
|
-
return (Decimal(0), Decimal(0))
|
|
32
|
-
|
|
33
|
-
NIL = "(nil)" # singleton object, use "is" to compare, not the value
|
|
34
|
-
NILinterval = (NIL,NIL)
|
|
35
|
-
|
|
36
|
-
def intervalValue(fact, dec=None): # value in decimals
|
|
37
|
-
if fact.isNil:
|
|
38
|
-
return NILinterval
|
|
39
|
-
if dec is None:
|
|
40
|
-
dec = inferredDecimals(fact)
|
|
41
|
-
return rangeValue(fact.value, dec)
|
|
42
|
-
|
|
43
|
-
def addInterval(boundValues, key, intervalValue, weight=None):
|
|
44
|
-
a, b, _inclA, _inclB = intervalValue
|
|
45
|
-
if a is NIL:
|
|
46
|
-
result = NILinterval
|
|
47
|
-
else:
|
|
48
|
-
r = boundValues[key]
|
|
49
|
-
if r is NILinterval:
|
|
50
|
-
return
|
|
51
|
-
elif weight is not None:
|
|
52
|
-
result = (r[0] + weight * a, r[1] + weight * b)
|
|
53
|
-
else:
|
|
54
|
-
result = (r[0] + a, r[1] + b)
|
|
55
|
-
boundValues[key] = result
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class ValidateXbrlCalc2:
|
|
59
|
-
def __init__(self, val):
|
|
60
|
-
self.val = val
|
|
61
|
-
self.cntlr = val.modelXbrl.modelManager.cntlr
|
|
62
|
-
self.modelXbrl = val.modelXbrl
|
|
63
|
-
self.standardTaxonomiesDict = val.disclosureSystem.standardTaxonomiesDict
|
|
64
|
-
self.eqCntx = {} # contexts which are OIM-aspect equivalent
|
|
65
|
-
self.eqUnit = {} # units which are equivalent
|
|
66
|
-
self.sumInitArcrole = self.perBindArcrole = self.aggBindArcrole = None
|
|
67
|
-
self.sumConceptBindKeys = defaultdict(set)
|
|
68
|
-
self.sumBoundFacts = defaultdict(list)
|
|
69
|
-
self.perConceptBindKeys = defaultdict(set)
|
|
70
|
-
self.perBoundFacts = defaultdict(list)
|
|
71
|
-
self.durationPeriodStarts = defaultdict(set)
|
|
72
|
-
self.aggConceptBindKeys = defaultdict(set)
|
|
73
|
-
self.aggBoundFacts = defaultdict(list)
|
|
74
|
-
self.aggBoundConceptFacts = defaultdict(list)
|
|
75
|
-
self.aggDimInit = set()
|
|
76
|
-
|
|
77
|
-
def validate(self):
|
|
78
|
-
modelXbrl = self.modelXbrl
|
|
79
|
-
if not modelXbrl.contexts or not modelXbrl.facts:
|
|
80
|
-
return # skip if no contexts or facts
|
|
81
|
-
|
|
82
|
-
startedAt = time.time()
|
|
83
|
-
|
|
84
|
-
# check balance attributes and weights, same as XBRL 2.1
|
|
85
|
-
for rel in modelXbrl.relationshipSet(calc2Arcroles).modelRelationships:
|
|
86
|
-
weight = rel.weight
|
|
87
|
-
fromConcept = rel.fromModelObject
|
|
88
|
-
toConcept = rel.toModelObject
|
|
89
|
-
if fromConcept is not None and toConcept is not None:
|
|
90
|
-
if rel.arcrole == aggregationDomain:
|
|
91
|
-
rel.dimension = rel.arcElement.prefixedNameQname(rel.get("dimension"))
|
|
92
|
-
if rel.dimension is None or not modelXbrl.qnameConcepts[rel.dimension].isDimensionItem:
|
|
93
|
-
modelXbrl.error("calc2e:invalidAggregationDimension",
|
|
94
|
-
_("Aggregation-domain relationship has invalid dimension %(dimension)s in link role %(linkrole)s"),
|
|
95
|
-
modelObject=rel,
|
|
96
|
-
dimension=rel.get("dimension"), linkrole=ELR)
|
|
97
|
-
elif fromConcept != toConcept or not fromConcept.isDomainMember:
|
|
98
|
-
modelXbrl.error("calc2e:invalidAggregationDomain",
|
|
99
|
-
_("Calculation relationship has invalid domain %(domain)s in link role %(linkrole)s"),
|
|
100
|
-
modelObject=rel,
|
|
101
|
-
domain=fromConcept, linkrole=ELR)
|
|
102
|
-
continue
|
|
103
|
-
if rel.arcrole == balanceChanges:
|
|
104
|
-
if fromConcept.periodType != "instant" or toConcept.periodType != "duration":
|
|
105
|
-
modelXbrl.error("calc2e:invalidBalanceChangesPeriodType",
|
|
106
|
-
_("Balance-changes relationship must have instant source concept and duration target concept in link role %(linkrole)s"),
|
|
107
|
-
modelObject=rel, linkrole=ELR)
|
|
108
|
-
if weight not in (1, -1):
|
|
109
|
-
modelXbrl.error("calc2e:invalidWeight",
|
|
110
|
-
_("Calculation relationship has invalid weight from %(source)s to %(target)s in link role %(linkrole)s"),
|
|
111
|
-
modelObject=rel,
|
|
112
|
-
source=fromConcept.qname, target=toConcept.qname, linkrole=ELR)
|
|
113
|
-
fromBalance = fromConcept.balance
|
|
114
|
-
toBalance = toConcept.balance
|
|
115
|
-
if fromBalance and toBalance:
|
|
116
|
-
if (fromBalance == toBalance and weight < 0) or \
|
|
117
|
-
(fromBalance != toBalance and weight > 0):
|
|
118
|
-
modelXbrl.error("calc2e:balanceCalcWeightIllegal" +
|
|
119
|
-
("Negative" if weight < 0 else "Positive"),
|
|
120
|
-
_("Calculation relationship has illegal weight %(weight)s from %(source)s, %(sourceBalance)s, to %(target)s, %(targetBalance)s, in link role %(linkrole)s (per 5.1.1.2 Table 6)"),
|
|
121
|
-
modelObject=rel, weight=weight,
|
|
122
|
-
source=fromConcept.qname, target=toConcept.qname, linkrole=rel.linkrole,
|
|
123
|
-
sourceBalance=fromBalance, targetBalance=toBalance,
|
|
124
|
-
messageCodes=("calc2e:balanceCalcWeightIllegalNegative", "calc2:balanceCalcWeightIllegalPositive"))
|
|
125
|
-
if not fromConcept.isNumeric or not toConcept.isNumeric:
|
|
126
|
-
modelXbrl.error("calc2e:nonNumericCalc",
|
|
127
|
-
_("Calculation relationship has illegal concept from %(source)s%(sourceNumericDecorator)s to %(target)s%(targetNumericDecorator)s in link role %(linkrole)s"),
|
|
128
|
-
modelObject=rel,
|
|
129
|
-
source=fromConcept.qname, target=toConcept.qname, linkrole=rel.linkrole,
|
|
130
|
-
sourceNumericDecorator="" if fromConcept.isNumeric else _(" (non-numeric)"),
|
|
131
|
-
targetNumericDecorator="" if toConcept.isNumeric else _(" (non-numeric)"))
|
|
132
|
-
|
|
133
|
-
# identify equal contexts
|
|
134
|
-
uniqueCntxHashes = {}
|
|
135
|
-
self.modelXbrl.profileActivity()
|
|
136
|
-
for cntx in modelXbrl.contexts.values():
|
|
137
|
-
h = hash( (cntx.periodHash, cntx.entityIdentifierHash, cntx.dimsHash) ) # OIM-compatible hash
|
|
138
|
-
if h in uniqueCntxHashes:
|
|
139
|
-
if cntx.isEqualTo(uniqueCntxHashes[h]):
|
|
140
|
-
self.eqCntx[cntx] = uniqueCntxHashes[h]
|
|
141
|
-
else:
|
|
142
|
-
uniqueCntxHashes[h] = cntx
|
|
143
|
-
del uniqueCntxHashes
|
|
144
|
-
self.modelXbrl.profileActivity("... identify aspect equal contexts", minTimeToShow=1.0)
|
|
145
|
-
|
|
146
|
-
# identify equal units
|
|
147
|
-
uniqueUnitHashes = {}
|
|
148
|
-
for unit in self.modelXbrl.units.values():
|
|
149
|
-
h = unit.hash
|
|
150
|
-
if h in uniqueUnitHashes:
|
|
151
|
-
if unit.isEqualTo(uniqueUnitHashes[h]):
|
|
152
|
-
self.eqUnit[unit] = uniqueUnitHashes[h]
|
|
153
|
-
else:
|
|
154
|
-
uniqueUnitHashes[h] = unit
|
|
155
|
-
del uniqueUnitHashes
|
|
156
|
-
self.modelXbrl.profileActivity("... identify equal units", minTimeToShow=1.0)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
sectObjs = sorted(set(rel.fromModelObject # only have numerics with context and unit
|
|
160
|
-
for rel in modelXbrl.relationshipSet(sectionFact).modelRelationships
|
|
161
|
-
if rel.fromModelObject is not None and rel.fromModelObject.concept is not None),
|
|
162
|
-
key=lambda s: (s.concept.label(), s.objectIndex)) # sort into document order for consistent error messages
|
|
163
|
-
if not sectObjs:
|
|
164
|
-
self.modelXbrl.error("calc2e:noSections",
|
|
165
|
-
"Instance contains no sections, nothing to validate.",
|
|
166
|
-
modelObject=modelXbrl)
|
|
167
|
-
|
|
168
|
-
# check by section
|
|
169
|
-
factByConceptCntxUnit = OrderedDefaultDict(list) # sort into document order for consistent error messages
|
|
170
|
-
self.sectionFacts = []
|
|
171
|
-
for sectObj in sectObjs:
|
|
172
|
-
#print ("section {}".format(sectObj.concept.label()))
|
|
173
|
-
self.section = sectObj.concept.label()
|
|
174
|
-
sectLinkRoles = tuple(sectObj.concept.get(calc2linkroles,"").split())
|
|
175
|
-
factByConceptCntxUnit.clear()
|
|
176
|
-
for f in sorted((rel.toModelObject # sort into document order for consistent error messages
|
|
177
|
-
for rel in modelXbrl.relationshipSet(sectionFact,sectLinkRoles).fromModelObject(sectObj)
|
|
178
|
-
if rel.toModelObject is not None and # numeric facts with context and unit
|
|
179
|
-
rel.fromModelObject is not None and
|
|
180
|
-
rel.toModelObject.concept is not None and
|
|
181
|
-
rel.toModelObject.context is not None and
|
|
182
|
-
rel.toModelObject.unit is not None),
|
|
183
|
-
key=lambda f: f.objectIndex):
|
|
184
|
-
factByConceptCntxUnit[f.qname, self.eqCntx.get(f.context,f.context), self.eqUnit.get(f.unit,f.unit)].append(f)
|
|
185
|
-
for fList in factByConceptCntxUnit.values():
|
|
186
|
-
f0 = fList[0]
|
|
187
|
-
if len(fList) == 1:
|
|
188
|
-
self.sectionFacts.append(f0)
|
|
189
|
-
else:
|
|
190
|
-
if any(f.isNil for f in fList):
|
|
191
|
-
_inConsistent = not all(f.isNil for f in fList)
|
|
192
|
-
if _inConsistent: # pick a nil fact for f0 for calc validation
|
|
193
|
-
for f in fList:
|
|
194
|
-
if f.isNil:
|
|
195
|
-
f0 = f
|
|
196
|
-
break
|
|
197
|
-
elif all(inferredDecimals(f) == inferredDecimals(f0) for f in fList[1:]): # same decimals
|
|
198
|
-
v0 = intervalValue(f0)
|
|
199
|
-
_inConsistent = not all(intervalValue(f) == v0 for f in fList[1:])
|
|
200
|
-
else: # not all have same decimals
|
|
201
|
-
d0 = inferredDecimals(f0)
|
|
202
|
-
aMax, bMin, _inclA, _inclB = intervalValue(f0, d0)
|
|
203
|
-
for f in fList[1:]:
|
|
204
|
-
df = inferredDecimals(f0)
|
|
205
|
-
a, b, _inclA, _inclB = intervalValue(f, df)
|
|
206
|
-
if a > aMax: aMax = a
|
|
207
|
-
if b < bMin: bMin = b
|
|
208
|
-
if df > d0: # take most accurate fact in section
|
|
209
|
-
f0 = f
|
|
210
|
-
d0 = df
|
|
211
|
-
_inConsistent = (bMin < aMax)
|
|
212
|
-
if _inConsistent:
|
|
213
|
-
modelXbrl.error("calc2e:inconsistentDuplicateInSection",
|
|
214
|
-
"Section %(section)s contained %(fact)s inconsistent in contexts equivalent to %(contextID)s: values %(values)s",
|
|
215
|
-
modelObject=fList, section=sectObj.concept.label(), fact=f0.qname, contextID=f0.contextID, values=", ".join(strTruncate(f.value, 128) for f in fList))
|
|
216
|
-
self.sectionFacts.append(f0)
|
|
217
|
-
# sectionFacts now in document order and deduplicated
|
|
218
|
-
#print("section {} facts {}".format(sectObj.concept.label(), ", ".join(str(f.qname)+"="+f.value for f in self.sectionFacts)))
|
|
219
|
-
|
|
220
|
-
# depth-first calc tree
|
|
221
|
-
sectCalc2RelSet = modelXbrl.relationshipSet(calc2Arcroles, sectLinkRoles)
|
|
222
|
-
|
|
223
|
-
# indexers for section based on calc2 arcrole
|
|
224
|
-
self.sumInit = False
|
|
225
|
-
self.sumConceptBindKeys.clear()
|
|
226
|
-
self.sumBoundFacts.clear()
|
|
227
|
-
self.perInit = False
|
|
228
|
-
self.perConceptBindKeys.clear()
|
|
229
|
-
self.perBoundFacts.clear()
|
|
230
|
-
self.durationPeriodStarts.clear()
|
|
231
|
-
self.aggDimInit = set()
|
|
232
|
-
self.aggConceptBindKeys.clear()
|
|
233
|
-
self.aggBoundFacts.clear()
|
|
234
|
-
self.aggBoundConceptFacts.clear()
|
|
235
|
-
self.aggDimInit.clear()
|
|
236
|
-
|
|
237
|
-
inferredValues = {}
|
|
238
|
-
for rootConcept in sorted(sectCalc2RelSet.rootConcepts,
|
|
239
|
-
key=lambda r: sectCalc2RelSet.fromModelObject(r)[0].order):
|
|
240
|
-
self.sectTreeRel(rootConcept, 1, sectCalc2RelSet, inferredValues, {rootConcept, None})
|
|
241
|
-
|
|
242
|
-
# recursive depth-first tree descender, returns sum
|
|
243
|
-
def sectTreeRel(self, parentConcept, n, sectCalc2RelSet, inferredParentValues, visited, dimQN=None):
|
|
244
|
-
childRels = sectCalc2RelSet.fromModelObject(parentConcept)
|
|
245
|
-
if childRels:
|
|
246
|
-
visited.add(parentConcept)
|
|
247
|
-
inferredChildValues = {}
|
|
248
|
-
|
|
249
|
-
# setup summation bind keys for child objects
|
|
250
|
-
sumParentBindKeys = self.sumConceptBindKeys[parentConcept]
|
|
251
|
-
boundSumKeys = set() # these are contributing fact keys, parent may be inferred
|
|
252
|
-
boundSums = defaultdict(intervalZero)
|
|
253
|
-
boundSummationItems = defaultdict(list)
|
|
254
|
-
boundPerKeys = set()
|
|
255
|
-
boundPers = defaultdict(intervalZero)
|
|
256
|
-
boundDurationItems = defaultdict(list)
|
|
257
|
-
boundAggKeys = set()
|
|
258
|
-
boundAggs = defaultdict(intervalZero)
|
|
259
|
-
boundAggItems = defaultdict(list)
|
|
260
|
-
boundAggConcepts = defaultdict(set)
|
|
261
|
-
for rel in childRels:
|
|
262
|
-
childConcept = rel.toModelObject
|
|
263
|
-
if childConcept not in visited:
|
|
264
|
-
if rel.arcrole == summationItem:
|
|
265
|
-
if not self.sumInit:
|
|
266
|
-
self.sumBindFacts()
|
|
267
|
-
boundSumKeys |= self.sumConceptBindKeys[childConcept]
|
|
268
|
-
elif rel.arcrole == balanceChanges:
|
|
269
|
-
if not self.perInit:
|
|
270
|
-
self.perBindFacts()
|
|
271
|
-
boundPerKeys |= self.perConceptBindKeys[childConcept] # these are only duration items
|
|
272
|
-
elif rel.arcrole == domainMember:
|
|
273
|
-
boundAggKeys |= self.aggConceptBindKeys[dimQN]
|
|
274
|
-
domQN = parentConcept.qname
|
|
275
|
-
elif rel.arcrole == aggregationDomain: # this is in visited
|
|
276
|
-
dimQN = rel.arcElement.prefixedNameQname(rel.get("dimension"))
|
|
277
|
-
if dimQN not in self.aggDimInit:
|
|
278
|
-
self.aggBindFacts(dimQN) # bind each referenced dimension's contexts
|
|
279
|
-
|
|
280
|
-
# depth-first descent calc tree and process item after descent
|
|
281
|
-
for rel in childRels:
|
|
282
|
-
childConcept = rel.toModelObject
|
|
283
|
-
if childConcept not in visited:
|
|
284
|
-
# depth-first descent
|
|
285
|
-
self.sectTreeRel(childConcept, n+1, sectCalc2RelSet, inferredChildValues, visited, dimQN)
|
|
286
|
-
# post-descent summation (allows use of inferred value)
|
|
287
|
-
if rel.arcrole == summationItem:
|
|
288
|
-
weight = rel.weightDecimal
|
|
289
|
-
for sumKey in boundSumKeys:
|
|
290
|
-
cntx, unit = sumKey
|
|
291
|
-
factKey = (childConcept, cntx, unit)
|
|
292
|
-
if factKey in self.sumBoundFacts:
|
|
293
|
-
for f in self.sumBoundFacts[factKey]:
|
|
294
|
-
addInterval(boundSums, sumKey, intervalValue(f), weight)
|
|
295
|
-
boundSummationItems[sumKey].append(f)
|
|
296
|
-
elif factKey in inferredChildValues:
|
|
297
|
-
addInterval(boundSums, sumKey, inferredChildValues[factKey], weight)
|
|
298
|
-
elif factKey in inferredParentValues:
|
|
299
|
-
addInterval(boundSums, sumKey, inferredParentValues[factKey], weight)
|
|
300
|
-
elif rel.arcrole == balanceChanges:
|
|
301
|
-
weight = rel.weightDecimal
|
|
302
|
-
for perKey in boundPerKeys:
|
|
303
|
-
hCntx, unit, start, end = perKey
|
|
304
|
-
factKey = (childConcept, hCntx, unit, start, end)
|
|
305
|
-
if factKey in self.perBoundFacts:
|
|
306
|
-
for f in self.perBoundFacts[factKey]:
|
|
307
|
-
addInterval(boundPers, perKey, intervalValue(f), weight)
|
|
308
|
-
boundDurationItems[perKey].append(f)
|
|
309
|
-
elif factKey in inferredChildValues:
|
|
310
|
-
addInterval(boundPers, perKey, inferredChildValues[factKey], weight)
|
|
311
|
-
elif factKey in inferredParentValues:
|
|
312
|
-
addInterval(boundPers, perKey, inferredParentValues[factKey], weight)
|
|
313
|
-
elif rel.arcrole == domainMember:
|
|
314
|
-
memQN = childConcept.qname
|
|
315
|
-
for aggKey in boundAggKeys:
|
|
316
|
-
hCntx, unit = aggKey
|
|
317
|
-
dimMemKey = (hCntx, unit, dimQN, memQN)
|
|
318
|
-
if dimMemKey in self.aggBoundFacts:
|
|
319
|
-
for f in self.aggBoundFacts[dimMemKey]:
|
|
320
|
-
a, b, _inclA, _inclB = intervalValue(f)
|
|
321
|
-
factDomKey = (f.concept, hCntx, unit, dimQN, domQN)
|
|
322
|
-
addInterval(boundAggs, factDomKey, intervalValue(f))
|
|
323
|
-
boundAggItems[aggKey].append(f)
|
|
324
|
-
boundAggConcepts[aggKey].add(f.concept)
|
|
325
|
-
elif rel.arcrole == aggregationDomain: # this is in visited
|
|
326
|
-
childRelSet = self.modelXbrl.relationshipSet(domainMember,rel.get("targetRole"))
|
|
327
|
-
self.sectTreeRel(childConcept, n+1, childRelSet, inferredParentValues, {None}, dimQN) # infer global to section
|
|
328
|
-
|
|
329
|
-
# process child items bound to this calc subtree
|
|
330
|
-
for sumKey in boundSumKeys:
|
|
331
|
-
cntx, unit = sumKey
|
|
332
|
-
factKey = (parentConcept, cntx, unit)
|
|
333
|
-
ia, ib = boundSums[sumKey]
|
|
334
|
-
if factKey in self.sumBoundFacts:
|
|
335
|
-
for f in self.sumBoundFacts[factKey]:
|
|
336
|
-
d = inferredDecimals(f)
|
|
337
|
-
sa, sb, _inclA, _inclB = intervalValue(f, d)
|
|
338
|
-
if ((ia is NIL) ^ (sa is NIL)) or ((ia is not NIL) and (sb < ia or sa > ib)):
|
|
339
|
-
self.modelXbrl.log('INCONSISTENCY', "calc2e:summationInconsistency",
|
|
340
|
-
_("Summation inconsistent from %(concept)s in section %(section)s reported sum %(reportedSum)s, computed sum %(computedSum)s context %(contextID)s unit %(unitID)s unreported contributing items %(unreportedContributors)s"),
|
|
341
|
-
modelObject=boundSummationItems[sumKey],
|
|
342
|
-
concept=parentConcept.qname, section=self.section,
|
|
343
|
-
reportedSum=self.formatInterval(sa, sb, d),
|
|
344
|
-
computedSum=self.formatInterval(ia, ib, d),
|
|
345
|
-
contextID=f.context.id, unitID=f.unit.id,
|
|
346
|
-
unreportedContributors=", ".join(str(c.qname) # list the missing/unreported contributors in relationship order
|
|
347
|
-
for r in childRels
|
|
348
|
-
for c in (r.toModelObject,)
|
|
349
|
-
if r.arcrole == summationItem and c is not None and
|
|
350
|
-
(c, cntx, unit) not in self.sumBoundFacts)
|
|
351
|
-
or "none")
|
|
352
|
-
elif inferredParentValues is not None: # value was inferred, return to parent level
|
|
353
|
-
inferredParentValues[factKey] = (ia, ib)
|
|
354
|
-
for perKey in boundPerKeys:
|
|
355
|
-
hCntx, unit, start, end = perKey
|
|
356
|
-
ia, ib = boundPers[perKey]
|
|
357
|
-
endBalA = endBalB = ZERO
|
|
358
|
-
endFactKey = (parentConcept, hCntx, unit, None, end)
|
|
359
|
-
if endFactKey in self.perBoundFacts:
|
|
360
|
-
for f in self.perBoundFacts[endFactKey]:
|
|
361
|
-
if f.isNil:
|
|
362
|
-
endBalA = endBalB = NIL
|
|
363
|
-
d = 0
|
|
364
|
-
break
|
|
365
|
-
d = inferredDecimals(f)
|
|
366
|
-
a, b, _inclA, _inclB = intervalValue(f,d)
|
|
367
|
-
endBalA += a
|
|
368
|
-
endBalB += b
|
|
369
|
-
foundStartingFact = (endBalA is NIL)
|
|
370
|
-
while not foundStartingFact:
|
|
371
|
-
startFactKey = (parentConcept, hCntx, unit, None, start)
|
|
372
|
-
if startFactKey in self.perBoundFacts:
|
|
373
|
-
for f in self.perBoundFacts[startFactKey]:
|
|
374
|
-
if f.isNil:
|
|
375
|
-
endBalA = endBalB = NIL
|
|
376
|
-
foundStartingFact = True
|
|
377
|
-
break
|
|
378
|
-
a, b, _inclA, _inclB = intervalValue(f)
|
|
379
|
-
endBalA -= a
|
|
380
|
-
endBalB -= b
|
|
381
|
-
foundStartingFact = True
|
|
382
|
-
break
|
|
383
|
-
if not foundStartingFact:
|
|
384
|
-
# infer backing up one period
|
|
385
|
-
_nomPer = nominalPeriod(end - start)
|
|
386
|
-
foundEarlierAdjacentPeriodStart = False
|
|
387
|
-
for _start in self.durationPeriodStarts.get(_nomPer, ()):
|
|
388
|
-
if nominalPeriod(start - _start) == _nomPer: # it's preceding period
|
|
389
|
-
end = start
|
|
390
|
-
start = _start
|
|
391
|
-
perKey = hCntx, unit, start, end
|
|
392
|
-
if perKey in boundPerKeys:
|
|
393
|
-
chngs = boundPers[perKey]
|
|
394
|
-
ia += chngs[0]
|
|
395
|
-
ib += chngs[1]
|
|
396
|
-
foundEarlierAdjacentPeriodStart = True
|
|
397
|
-
break
|
|
398
|
-
if not foundEarlierAdjacentPeriodStart:
|
|
399
|
-
break
|
|
400
|
-
|
|
401
|
-
if ((ia is NIL) ^ (endBalA is NIL)) or ((ia is not NIL) and (endBalB < ia or endBalA > ib)):
|
|
402
|
-
self.modelXbrl.log('INCONSISTENCY', "calc2e:balanceInconsistency",
|
|
403
|
-
_("Balance inconsistent from %(concept)s in section %(section)s reported sum %(reportedSum)s, computed sum %(computedSum)s context %(contextID)s unit %(unitID)s unreported contributing items %(unreportedContributors)s"),
|
|
404
|
-
modelObject=boundDurationItems[perKey],
|
|
405
|
-
concept=parentConcept.qname, section=self.section,
|
|
406
|
-
reportedSum=self.formatInterval(endBalA, endBalB, d),
|
|
407
|
-
computedSum=self.formatInterval(ia, ib, d),
|
|
408
|
-
contextID=f.context.id, unitID=f.unit.id,
|
|
409
|
-
unreportedContributors=", ".join(str(c.qname) # list the missing/unreported contributors in relationship order
|
|
410
|
-
for r in childRels
|
|
411
|
-
for c in (r.toModelObject,)
|
|
412
|
-
if r.arcrole == balanceChanges and c is not None and
|
|
413
|
-
(c, hCntx, unit, start, end) not in self.perBoundFacts)
|
|
414
|
-
or "none")
|
|
415
|
-
for aggKey in boundAggKeys:
|
|
416
|
-
hCntx, unit = aggKey
|
|
417
|
-
for concept in sorted(boundAggConcepts[aggKey], key=lambda c:c.objectIndex): # repeatable errors
|
|
418
|
-
factDomKey = (concept, hCntx, unit, dimQN, domQN)
|
|
419
|
-
ia, ib = boundAggs[factDomKey]
|
|
420
|
-
if factDomKey in self.aggBoundConceptFacts:
|
|
421
|
-
for f in self.aggBoundConceptFacts[factDomKey]:
|
|
422
|
-
d = inferredDecimals(f)
|
|
423
|
-
sa, sb, _inclA, _inclB = intervalValue(f, d)
|
|
424
|
-
if ((ia is NIL) ^ (sa is NIL)) or ((ia is not NIL) and (sb < ia or sa > ib)):
|
|
425
|
-
self.modelXbrl.log('INCONSISTENCY', "calc2e:aggregationInconsistency",
|
|
426
|
-
_("Aggregation inconsistent for %(concept)s, domain %(domain)s in section %(section)s reported sum %(reportedSum)s, computed sum %(computedSum)s context %(contextID)s unit %(unitID)s unreported contributing members %(unreportedContributors)s"),
|
|
427
|
-
modelObject=boundAggItems[factDomKey],
|
|
428
|
-
concept=concept.qname,
|
|
429
|
-
domain=parentConcept.qname, section=self.section,
|
|
430
|
-
reportedSum=self.formatInterval(sa, sb, d),
|
|
431
|
-
computedSum=self.formatInterval(ia, ib, d),
|
|
432
|
-
contextID=f.context.id, unitID=f.unit.id,
|
|
433
|
-
unreportedContributors=", ".join(str(c.qname) # list the missing/unreported contributors in relationship order
|
|
434
|
-
for r in childRels
|
|
435
|
-
for c in (r.toModelObject,)
|
|
436
|
-
if r.arcrole == domainMember and c is not None and
|
|
437
|
-
(concept, hCntx, unit, dimQN, c.qname) not in self.aggBoundConceptFacts)
|
|
438
|
-
or "none")
|
|
439
|
-
elif inferredParentValues is not None: # value was inferred, return to parent level
|
|
440
|
-
# allow to be retrieved by factDomKey
|
|
441
|
-
inferredParentValues[factDomKey] = (ia, ib)
|
|
442
|
-
if self.modelXbrl.qnameDimensionDefaults.get(dimQN) == domQN:
|
|
443
|
-
cntxKey = (hCntx, dimQN, domQN)
|
|
444
|
-
if cntxKey in self.eqCntx:
|
|
445
|
-
cntx = self.eqCntx[cntxKey]
|
|
446
|
-
else:
|
|
447
|
-
cntx = self.aggTotalContext(hCntx, dimQN, domQN)
|
|
448
|
-
self.eqCntx[cntxKey] = cntx
|
|
449
|
-
if cntx is not None:
|
|
450
|
-
# allow to be retrieved by fact line item context key
|
|
451
|
-
self.eqCntx[(hCntx, dimQN, domQN)] = cntx
|
|
452
|
-
inferredParentValues[(concept, cntx, unit)] = (ia, ib)
|
|
453
|
-
visited.remove(parentConcept)
|
|
454
|
-
|
|
455
|
-
def sumBindFacts(self):
|
|
456
|
-
# bind facts in section for summation-item
|
|
457
|
-
for f in self.sectionFacts:
|
|
458
|
-
concept = f.concept
|
|
459
|
-
if concept.isNumeric:
|
|
460
|
-
cntx = self.eqCntx.get(f.context,f.context)
|
|
461
|
-
unit = self.eqUnit.get(f.unit,f.unit)
|
|
462
|
-
self.sumConceptBindKeys[concept].add( (cntx,unit) )
|
|
463
|
-
self.sumBoundFacts[concept, cntx, unit].append(f)
|
|
464
|
-
self.sumInit = True
|
|
465
|
-
|
|
466
|
-
def perBindFacts(self):
|
|
467
|
-
# bind facts in section for domain aggreggation
|
|
468
|
-
for f in self.sectionFacts:
|
|
469
|
-
concept = f.concept
|
|
470
|
-
if concept.isNumeric:
|
|
471
|
-
cntx = self.eqCntx.get(f.context,f.context)
|
|
472
|
-
if not cntx.isForeverPeriod:
|
|
473
|
-
hCntx = hash( (cntx.entityIdentifierHash, cntx.dimsHash) )
|
|
474
|
-
unit = self.eqUnit.get(f.unit,f.unit)
|
|
475
|
-
self.perConceptBindKeys[concept].add( (hCntx, unit, cntx.startDatetime, cntx.endDatetime) )
|
|
476
|
-
self.perBoundFacts[concept, hCntx, unit, cntx.startDatetime, cntx.endDatetime].append(f)
|
|
477
|
-
if cntx.isStartEndPeriod:
|
|
478
|
-
self.durationPeriodStarts[nominalPeriod(cntx.endDatetime - cntx.startDatetime)].add(cntx.startDatetime)
|
|
479
|
-
self.perInit = True
|
|
480
|
-
|
|
481
|
-
def aggBindFacts(self, dimQN):
|
|
482
|
-
# bind facts in section for domain aggreggation
|
|
483
|
-
for f in self.sectionFacts:
|
|
484
|
-
concept = f.concept
|
|
485
|
-
if concept.isNumeric:
|
|
486
|
-
cntx = self.eqCntx.get(f.context,f.context)
|
|
487
|
-
hCntx = hash( (cntx.periodHash, cntx.entityIdentifierHash,
|
|
488
|
-
hash(frozenset(dimObj
|
|
489
|
-
for _dimQN, dimObj in cntx.qnameDims.items()
|
|
490
|
-
if _dimQN != dimQN))) )
|
|
491
|
-
unit = self.eqUnit.get(f.unit,f.unit)
|
|
492
|
-
memQN = cntx.dimMemberQname(dimQN, includeDefaults=True)
|
|
493
|
-
if memQN is not None:
|
|
494
|
-
self.aggConceptBindKeys[dimQN].add( (hCntx,unit) )
|
|
495
|
-
self.aggBoundFacts[hCntx, unit, dimQN, memQN].append(f)
|
|
496
|
-
self.aggBoundConceptFacts[f.concept, hCntx, unit, dimQN, memQN].append(f)
|
|
497
|
-
self.aggDimInit.add(dimQN)
|
|
498
|
-
|
|
499
|
-
def aggTotalContext(self, hCntx, dimQN, domQN):
|
|
500
|
-
# find a context for aggregation total usable in line item and balance roll ups
|
|
501
|
-
for cntx in self.modelXbrl.contexts.values():
|
|
502
|
-
hCntx2 = hash( (cntx.periodHash, cntx.entityIdentifierHash,
|
|
503
|
-
hash(frozenset(dimObj
|
|
504
|
-
for _dimQN, dimObj in cntx.qnameDims.items()
|
|
505
|
-
if _dimQN != dimQN))) )
|
|
506
|
-
if hCntx == hCntx2 and cntx.dimMemberQname(dimQN, True) == domQN:
|
|
507
|
-
return cntx
|
|
508
|
-
return None
|
|
509
|
-
|
|
510
|
-
def formatInterval(self, a, b, dec):
|
|
511
|
-
if a is NIL:
|
|
512
|
-
return "(nil)"
|
|
513
|
-
if isnan(dec) or isinf(dec): dec = 4
|
|
514
|
-
if a == b: # not an interval
|
|
515
|
-
return Locale.format_decimal(self.modelXbrl.locale, a, 1, max(dec,0))
|
|
516
|
-
return "[{}, {}]".format( # show as an interval
|
|
517
|
-
Locale.format_decimal(self.modelXbrl.locale, a, 1, max(dec,0)),
|
|
518
|
-
Locale.format_decimal(self.modelXbrl.locale, b, 1, max(dec,0)))
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
def checkCalc2(val, *args, **kwargs):
|
|
522
|
-
ValidateXbrlCalc2(val).validate()
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
__pluginInfo__ = {
|
|
527
|
-
# Do not use _( ) in pluginInfo itself (it is applied later, after loading
|
|
528
|
-
'name': 'Calc2',
|
|
529
|
-
'version': '0.9',
|
|
530
|
-
'description': '''Calculation 2.0 Validation.''',
|
|
531
|
-
'license': 'Apache-2',
|
|
532
|
-
'author': authorLabel,
|
|
533
|
-
'copyright': copyrightLabel,
|
|
534
|
-
# classes of mount points (required)
|
|
535
|
-
'Validate.XBRL.Finally': checkCalc2
|
|
536
|
-
}
|