arelle-release 2.37.46__py3-none-any.whl → 2.38.0__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.
- arelle/BetaFeatures.py +0 -21
- arelle/Cntlr.py +15 -8
- arelle/CntlrCmdLine.py +121 -56
- arelle/CntlrWinMain.py +143 -70
- arelle/DialogFind.py +1 -1
- arelle/DialogPluginManager.py +6 -4
- arelle/DisclosureSystem.py +7 -0
- arelle/ErrorManager.py +21 -6
- arelle/FileSource.py +11 -4
- arelle/FunctionIxt.py +16 -11
- arelle/HtmlUtil.py +5 -4
- arelle/LeiUtil.py +63 -43
- arelle/ModelDocument.py +20 -15
- arelle/ModelDtsObject.py +8 -0
- arelle/ModelInstanceObject.py +1 -1
- arelle/ModelObject.py +16 -18
- arelle/ModelObjectFactory.py +35 -17
- arelle/ModelXbrl.py +28 -11
- arelle/PluginManager.py +130 -105
- arelle/RuntimeOptions.py +1 -0
- arelle/UrlUtil.py +14 -0
- arelle/Validate.py +17 -12
- arelle/ValidateDuplicateFacts.py +3 -1
- arelle/ValidateFileSource.py +38 -0
- arelle/ValidateFilingText.py +3 -3
- arelle/ValidateXbrl.py +5 -2
- arelle/ValidateXbrlCalcs.py +210 -186
- arelle/ValidateXbrlDTS.py +1 -1
- arelle/ViewFile.py +1 -0
- arelle/ViewFileFactTable.py +2 -2
- arelle/ViewWinDTS.py +4 -1
- arelle/WebCache.py +28 -24
- arelle/XbrlConst.py +22 -0
- arelle/XmlUtil.py +16 -21
- arelle/XmlValidate.py +6 -9
- arelle/_version.py +16 -3
- arelle/api/Session.py +11 -2
- arelle/config/disclosuresystems.xsd +2 -0
- arelle/config/rosettaEntitlements.plist +8 -0
- arelle/conformance/CSVTestcaseLoader.py +1 -1
- arelle/formula/XPathContext.py +3 -3
- arelle/logging/formatters/LogFormatter.py +3 -1
- arelle/packages/report/ReportPackage.py +26 -13
- arelle/packages/report/ReportPackageConst.py +0 -1
- arelle/plugin/inlineXbrlDocumentSet.py +19 -5
- arelle/plugin/validate/DBA/DisclosureSystems.py +19 -1
- arelle/plugin/validate/DBA/PluginValidationDataExtension.py +2 -4
- arelle/plugin/validate/DBA/ValidationPluginExtension.py +2 -1
- arelle/plugin/validate/DBA/resources/config.xml +5 -0
- arelle/plugin/validate/DBA/rules/__init__.py +2 -2
- arelle/plugin/validate/DBA/rules/fr.py +19 -2
- arelle/plugin/validate/DBA/rules/tc.py +2 -0
- arelle/plugin/validate/DBA/rules/th.py +6 -0
- arelle/plugin/validate/DBA/rules/tm.py +18 -5
- arelle/plugin/validate/DBA/rules/tr.py +11 -5
- arelle/plugin/validate/EDINET/Constants.py +193 -9
- arelle/plugin/validate/EDINET/ContextRequirement.py +58 -0
- arelle/plugin/validate/EDINET/ControllerPluginData.py +220 -1
- arelle/plugin/validate/EDINET/CoverItemRequirements.py +42 -0
- arelle/plugin/validate/EDINET/DeiRequirements.py +118 -0
- arelle/plugin/validate/EDINET/FilingFormat.py +275 -0
- arelle/plugin/validate/EDINET/FormType.py +134 -0
- arelle/plugin/validate/EDINET/ManifestInstance.py +72 -5
- arelle/plugin/validate/EDINET/NamespaceConfig.py +50 -0
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py +493 -132
- arelle/plugin/validate/EDINET/{InstanceType.py → ReportFolderType.py} +72 -15
- arelle/plugin/validate/EDINET/Statement.py +139 -0
- arelle/plugin/validate/EDINET/TableOfContentsBuilder.py +595 -0
- arelle/plugin/validate/EDINET/UploadContents.py +48 -0
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py +20 -2
- arelle/plugin/validate/EDINET/__init__.py +31 -6
- arelle/plugin/validate/EDINET/resources/config.xml +8 -1
- arelle/plugin/validate/EDINET/resources/cover-item-requirements.json +793 -0
- arelle/plugin/validate/EDINET/resources/dei-requirements.csv +27 -0
- arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml +2 -0
- arelle/plugin/validate/EDINET/rules/contexts.py +375 -14
- arelle/plugin/validate/EDINET/rules/edinet.py +1934 -45
- arelle/plugin/validate/EDINET/rules/frta.py +122 -3
- arelle/plugin/validate/EDINET/rules/gfm.py +1907 -11
- arelle/plugin/validate/EDINET/rules/upload.py +989 -141
- arelle/plugin/validate/ESEF/Const.py +3 -1
- arelle/plugin/validate/ESEF/ESEF_2021/DTS.py +5 -0
- arelle/plugin/validate/ESEF/ESEF_2021/Image.py +2 -2
- arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +23 -20
- arelle/plugin/validate/ESEF/ESEF_Current/DTS.py +47 -14
- arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +100 -25
- arelle/plugin/validate/ESEF/__init__.py +20 -6
- arelle/plugin/validate/ESEF/resources/authority-validations.json +76 -9
- arelle/plugin/validate/ESEF/resources/config.xml +20 -0
- arelle/plugin/validate/NL/DisclosureSystems.py +22 -0
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +27 -9
- arelle/plugin/validate/NL/ValidationPluginExtension.py +51 -7
- arelle/plugin/validate/NL/resources/config.xml +18 -0
- arelle/plugin/validate/NL/rules/br_kvk.py +17 -61
- arelle/plugin/validate/NL/rules/fg_nl.py +7 -38
- arelle/plugin/validate/NL/rules/fr_kvk.py +7 -42
- arelle/plugin/validate/NL/rules/fr_nl.py +31 -147
- arelle/plugin/validate/NL/rules/nl_kvk.py +142 -28
- arelle/plugin/validate/ROS/PluginValidationDataExtension.py +2 -0
- arelle/plugin/validate/ROS/ValidationPluginExtension.py +4 -1
- arelle/plugin/validate/ROS/rules/ros.py +41 -9
- arelle/plugin/validate/UK/ValidateUK.py +130 -66
- arelle/plugin/validate/UK/__init__.py +89 -103
- arelle/utils/EntryPointDetection.py +79 -13
- arelle/utils/PluginHooks.py +125 -0
- arelle/utils/validate/ESEFImage.py +6 -6
- arelle/utils/validate/Validation.py +18 -0
- arelle/utils/validate/ValidationPlugin.py +76 -11
- arelle/utils/validate/ValidationUtil.py +35 -3
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/METADATA +30 -20
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/RECORD +115 -191
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/licenses/LICENSE.md +0 -3
- 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/model/CommentBase.py +0 -9
- arelle/model/ElementBase.py +0 -11
- arelle/model/PIBase.py +0 -10
- arelle/model/__init__.py +0 -15
- 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.38.0.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.46.dist-info → arelle_release-2.38.0.dist-info}/top_level.txt +0 -0
|
@@ -7,12 +7,13 @@ from dataclasses import dataclass
|
|
|
7
7
|
from dataclasses import field
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from functools import cached_property
|
|
10
|
-
from typing import Any, cast
|
|
10
|
+
from typing import Any, cast, Iterable
|
|
11
11
|
|
|
12
12
|
import regex as re
|
|
13
13
|
|
|
14
14
|
from arelle.ModelInstanceObject import ModelFact
|
|
15
15
|
from arelle.ModelXbrl import ModelXbrl
|
|
16
|
+
from arelle.XmlValidateConst import VALID
|
|
16
17
|
|
|
17
18
|
# Error codes
|
|
18
19
|
CH_AUDIT = 'Char.Audit'
|
|
@@ -24,6 +25,7 @@ CO_AUDIT = 'Co.Audit'
|
|
|
24
25
|
CO_AUDIT_NR = 'Co.AuditNR'
|
|
25
26
|
CO_DIR_REP = 'Co.DirReport'
|
|
26
27
|
CO_DIR_RESP = 'Co.DirResp'
|
|
28
|
+
CO_GROUP = 'Co.Group'
|
|
27
29
|
CO_MED_CO = 'Co.MedCo'
|
|
28
30
|
CO_MICRO = 'Co.Micro'
|
|
29
31
|
CO_MISSING_ELEMENT = 'Co.MissingElement'
|
|
@@ -34,6 +36,7 @@ CO_SEC_477 = 'Co.Sec477'
|
|
|
34
36
|
CO_SEC_480 = 'Co.Sec480'
|
|
35
37
|
LP_ABRID = 'Lp.Abrid'
|
|
36
38
|
LP_AUDIT = 'Lp.Audit'
|
|
39
|
+
LP_GROUP = 'Lp.Group'
|
|
37
40
|
LP_MED_LP = 'Lp.MedLp'
|
|
38
41
|
LP_MEM_RESP = 'Lp.MemResp'
|
|
39
42
|
LP_MICRO = 'Lp.Micro'
|
|
@@ -56,19 +59,23 @@ CONCEPT_ACCOUNTS_STATUS_DIMENSION = 'AccountsStatusDimension'
|
|
|
56
59
|
CONCEPT_ACCOUNTS_TYPE_FULL_OR_ABBREVIATED = 'AccountsTypeFullOrAbbreviated' # DEPRECATED IN 2022+ taxonomies. No replacement yet.
|
|
57
60
|
CONCEPT_ACCOUNTS_TYPE_DIMENSION = 'AccountsTypeDimension'
|
|
58
61
|
CONCEPT_ADVERSE_OPINION = 'AdverseOpinion'
|
|
62
|
+
CONCEPT_BALANCE_SHEET_DATE = 'BalanceSheetDate'
|
|
59
63
|
CONCEPT_CHARITY_FUNDS = 'CharityFunds'
|
|
60
64
|
CONCEPT_CHARITY_REGISTRATION_NUMBER_ENGLAND_WALES = 'CharityRegistrationNumberEnglandWales'
|
|
61
65
|
CONCEPT_CHARITY_REGISTRATION_NUMBER_NORTH_IRELAND = 'CharityRegistrationNumberNorthernIreland'
|
|
62
66
|
CONCEPT_CHARITY_REGISTRATION_NUMBER_SCOTLAND = 'CharityRegistrationNumberScotland'
|
|
67
|
+
CONCEPT_CONSOLIDATED = 'Consolidated'
|
|
63
68
|
CONCEPT_DATE_AUDITOR_REPORT = 'DateAuditorsReport'
|
|
64
69
|
CONCEPT_DATE_CHARITY_AUDITORS_REPORT = 'DateCharityAuditorsReport'
|
|
65
70
|
CONCEPT_DATE_SIGNING_DIRECTOR_REPORT = 'DateSigningDirectorsReport'
|
|
66
71
|
CONCEPT_DATE_SIGNING_TRUSTEES_REPORT = 'DateSigningTrusteesAnnualReport'
|
|
67
72
|
CONCEPT_DIRECTOR_SIGNING_DIRECTORS_REPORT = 'DirectorSigningDirectorsReport'
|
|
68
73
|
CONCEPT_DISCLAIMER_OPINION = 'DisclaimerOpinion'
|
|
74
|
+
CONCEPT_END_DATE_FOR_PERIOD_COVERED_BY_REPORT = 'EndDateForPeriodCoveredByReport'
|
|
69
75
|
CONCEPT_ENTITY_DORMANT = 'EntityDormantTruefalse'
|
|
70
76
|
CONCEPT_ENTITY_TRADING_STATUS = 'EntityTradingStatus'
|
|
71
77
|
CONCEPT_ENTITY_TRADING_STATUS_DIMENSION = 'EntityTradingStatusDimension'
|
|
78
|
+
CONCEPT_GROUP_COMPANY_DATA_DIMENSION = 'GroupCompanyDataDimension'
|
|
72
79
|
CONCEPT_LANGUAGES_DIMENSION = 'LanguagesDimension'
|
|
73
80
|
CONCEPT_MEDIUM_COMPANY = 'StatementThatCompanyHasPreparedAccountsUnderProvisionsRelatingToMedium-sizedCompanies'
|
|
74
81
|
CONCEPT_MEDIUM_COMPANIES_REGIME_FOR_ACCOUNTS = 'Medium-sizedCompaniesRegimeForAccounts'
|
|
@@ -430,10 +437,11 @@ class ValidateUK:
|
|
|
430
437
|
_codeResultMap: dict[str, CodeResult] = field(default_factory=dict)
|
|
431
438
|
|
|
432
439
|
def _checkValidFact(self, fact: ModelFact ) -> bool:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
440
|
+
return (
|
|
441
|
+
fact is not None and
|
|
442
|
+
not fact.isNil and
|
|
443
|
+
fact.context is not None
|
|
444
|
+
)
|
|
437
445
|
|
|
438
446
|
def _errorOnMissingFact(self, conceptLocalName: str) -> None:
|
|
439
447
|
"""
|
|
@@ -628,16 +636,12 @@ class ValidateUK:
|
|
|
628
636
|
elif code == CH_CHAR_FUND:
|
|
629
637
|
concept = CONCEPT_CHARITY_FUNDS
|
|
630
638
|
trading = False
|
|
631
|
-
for
|
|
632
|
-
if
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
NotTrading.CONCEPT_ENTITY_NO_LONGER_TRADING.value,
|
|
638
|
-
NotTrading.CONCEPT_ENTITY_HAS_NEVER_TRADED.value,
|
|
639
|
-
}:
|
|
640
|
-
trading = True
|
|
639
|
+
for value in self._getDimensionValues(CONCEPT_ENTITY_TRADING_STATUS, CONCEPT_ENTITY_TRADING_STATUS_DIMENSION):
|
|
640
|
+
if value in {
|
|
641
|
+
NotTrading.CONCEPT_ENTITY_NO_LONGER_TRADING.value,
|
|
642
|
+
NotTrading.CONCEPT_ENTITY_HAS_NEVER_TRADED.value,
|
|
643
|
+
}:
|
|
644
|
+
trading = True
|
|
641
645
|
if not self._getAndCheckValidFacts([concept]) and not trading:
|
|
642
646
|
return CodeResult(
|
|
643
647
|
conceptLocalName=concept,
|
|
@@ -648,6 +652,39 @@ class ValidateUK:
|
|
|
648
652
|
)
|
|
649
653
|
return CodeResult()
|
|
650
654
|
|
|
655
|
+
def _evaluateGroupFacts(self) -> CodeResult:
|
|
656
|
+
"""
|
|
657
|
+
BalanceSheetDate with GroupCompanyDataDimension and Consolidated must equal BalanceSheetDate with default
|
|
658
|
+
dimensions. Both facts must be non-nil.
|
|
659
|
+
"""
|
|
660
|
+
consolidatedFact = None
|
|
661
|
+
defaultFact = None
|
|
662
|
+
endDateFact = None
|
|
663
|
+
for fact in self._getFacts(CONCEPT_END_DATE_FOR_PERIOD_COVERED_BY_REPORT):
|
|
664
|
+
if not fact.context.qnameDims:
|
|
665
|
+
endDateFact = fact
|
|
666
|
+
break
|
|
667
|
+
if endDateFact is None:
|
|
668
|
+
return CodeResult()
|
|
669
|
+
for balanceSheetDateFact in self._getFacts(CONCEPT_BALANCE_SHEET_DATE):
|
|
670
|
+
if self._checkValidFact(balanceSheetDateFact):
|
|
671
|
+
if not balanceSheetDateFact.context.qnameDims and balanceSheetDateFact.context.instantDate == endDateFact.context.instantDate:
|
|
672
|
+
defaultFact = balanceSheetDateFact
|
|
673
|
+
for qname, value in balanceSheetDateFact.context.qnameDims.items():
|
|
674
|
+
if value.xValid < VALID:
|
|
675
|
+
continue
|
|
676
|
+
if qname.localName == CONCEPT_GROUP_COMPANY_DATA_DIMENSION and cast(str, value.xValue.localName) == CONCEPT_CONSOLIDATED:
|
|
677
|
+
consolidatedFact = balanceSheetDateFact
|
|
678
|
+
break
|
|
679
|
+
if consolidatedFact is None or defaultFact is None or consolidatedFact.xValue != defaultFact.xValue:
|
|
680
|
+
return CodeResult(
|
|
681
|
+
conceptLocalName=CONCEPT_BALANCE_SHEET_DATE,
|
|
682
|
+
success=False,
|
|
683
|
+
message="A fact tagged with BalanceSheetDate with the dimension of GroupCompanyDataDimension/Consolidated "
|
|
684
|
+
"must equal a fact tagged with BalanceSheetDate with the default dimension."
|
|
685
|
+
)
|
|
686
|
+
return CodeResult()
|
|
687
|
+
|
|
651
688
|
def _evaluateCode(self, code: str) -> CodeResult:
|
|
652
689
|
"""
|
|
653
690
|
Evaluates whether the conditions associated with the given code pass.
|
|
@@ -669,6 +706,8 @@ class ValidateUK:
|
|
|
669
706
|
result = self._evaluateProfLossOrCharityFundsFact(code)
|
|
670
707
|
elif code == CH_AUDIT:
|
|
671
708
|
result = self._evaluateCharAuditFacts()
|
|
709
|
+
elif code in (CO_GROUP, LP_GROUP):
|
|
710
|
+
result = self._evaluateGroupFacts()
|
|
672
711
|
return self._setCode(code, result)
|
|
673
712
|
|
|
674
713
|
def _getFacts(self, conceptLocalName: str) -> list[ModelFact]:
|
|
@@ -686,13 +725,11 @@ class ValidateUK:
|
|
|
686
725
|
"""
|
|
687
726
|
Determines if the language is set to Welsh, otherwise defaults to English.
|
|
688
727
|
"""
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if value.xValue.localName == CONCEPT_WELSH:
|
|
695
|
-
return HmrcLang.WELSH
|
|
728
|
+
if any(
|
|
729
|
+
lang == CONCEPT_WELSH
|
|
730
|
+
for lang in self._getDimensionValues(CONCEPT_REPORT_PRINCIPAL_LANGUAGE, CONCEPT_LANGUAGES_DIMENSION)
|
|
731
|
+
):
|
|
732
|
+
return HmrcLang.WELSH
|
|
696
733
|
return HmrcLang.ENGLISH
|
|
697
734
|
|
|
698
735
|
def _yieldErrorOrWarning(self, code: str, result: CodeResult) -> None:
|
|
@@ -725,47 +762,19 @@ class ValidateUK:
|
|
|
725
762
|
|
|
726
763
|
@cached_property
|
|
727
764
|
def accountStatus(self) -> str | None:
|
|
728
|
-
|
|
729
|
-
for fact in facts:
|
|
730
|
-
if not self._checkValidFact(fact):
|
|
731
|
-
continue
|
|
732
|
-
for qname, value in fact.context.qnameDims.items():
|
|
733
|
-
if qname.localName == CONCEPT_ACCOUNTS_STATUS_DIMENSION:
|
|
734
|
-
return cast(str, value.xValue.localName)
|
|
735
|
-
return None
|
|
765
|
+
return next(iter(self._getDimensionValues(CONCEPT_ACCOUNTS_STATUS, CONCEPT_ACCOUNTS_STATUS_DIMENSION)), None)
|
|
736
766
|
|
|
737
767
|
@cached_property
|
|
738
768
|
def accountsType(self) -> str | None:
|
|
739
|
-
|
|
740
|
-
for fact in facts:
|
|
741
|
-
if not self._checkValidFact(fact):
|
|
742
|
-
continue
|
|
743
|
-
for qname, value in fact.context.qnameDims.items():
|
|
744
|
-
if qname.localName == CONCEPT_ACCOUNTS_TYPE_DIMENSION:
|
|
745
|
-
return cast(str, value.xValue.localName)
|
|
746
|
-
return None
|
|
769
|
+
return next(iter(self._getDimensionValues(CONCEPT_ACCOUNTS_TYPE_FULL_OR_ABBREVIATED, CONCEPT_ACCOUNTS_TYPE_DIMENSION)), None)
|
|
747
770
|
|
|
748
771
|
@cached_property
|
|
749
772
|
def accountingStandardsApplied(self) -> str | None:
|
|
750
|
-
|
|
751
|
-
for fact in facts:
|
|
752
|
-
if not self._checkValidFact(fact):
|
|
753
|
-
continue
|
|
754
|
-
for qname, value in fact.context.qnameDims.items():
|
|
755
|
-
if qname.localName == CONCEPT_ACCOUNTING_STANDARDS_DIMENSION:
|
|
756
|
-
return cast(str, value.xValue.localName)
|
|
757
|
-
return None
|
|
773
|
+
return next(iter(self._getDimensionValues(CONCEPT_ACCOUNTING_STANDARDS_APPLIED, CONCEPT_ACCOUNTING_STANDARDS_DIMENSION)), None)
|
|
758
774
|
|
|
759
775
|
@cached_property
|
|
760
776
|
def applicableLegislation(self) -> str | None:
|
|
761
|
-
|
|
762
|
-
for fact in facts:
|
|
763
|
-
if not self._checkValidFact(fact):
|
|
764
|
-
continue
|
|
765
|
-
for qname, value in fact.context.qnameDims.items():
|
|
766
|
-
if qname.localName == CONCEPT_APPLICABLE_LEGISLATION_DIMENSION:
|
|
767
|
-
return cast(str, value.xValue.localName)
|
|
768
|
-
return None
|
|
777
|
+
return next(iter(self._getDimensionValues(CONCEPT_APPLICABLE_LEGISLATION, CONCEPT_APPLICABLE_LEGISLATION_DIMENSION)), None)
|
|
769
778
|
|
|
770
779
|
@cached_property
|
|
771
780
|
def isEntityDormant(self) -> bool:
|
|
@@ -776,25 +785,22 @@ class ValidateUK:
|
|
|
776
785
|
|
|
777
786
|
@cached_property
|
|
778
787
|
def legalFormEntity(self) -> str | None:
|
|
779
|
-
|
|
780
|
-
for fact in facts:
|
|
781
|
-
if not self._checkValidFact(fact):
|
|
782
|
-
continue
|
|
783
|
-
for qname, value in fact.context.qnameDims.items():
|
|
784
|
-
if qname.localName == CONCEPT_LEGAL_FORM_ENTIY_DIMENSION:
|
|
785
|
-
return cast(str, value.xValue.localName)
|
|
786
|
-
return None
|
|
788
|
+
return next(iter(self._getDimensionValues(CONCEPT_LEGAL_FORM_ENTIY, CONCEPT_LEGAL_FORM_ENTIY_DIMENSION)), None)
|
|
787
789
|
|
|
788
790
|
@cached_property
|
|
789
791
|
def scopeAccounts(self) -> str | None:
|
|
790
|
-
|
|
792
|
+
return next(iter(self._getDimensionValues(CONCEPT_SCOPE_ACCOUNTS, CONCEPT_SCOPE_ACCOUNTS_DIMENSION)), None)
|
|
793
|
+
|
|
794
|
+
def _getDimensionValues(self, conceptLocalName: str, dimensionLocalName: str) -> Iterable[str]:
|
|
795
|
+
facts = self._getFacts(conceptLocalName)
|
|
791
796
|
for fact in facts:
|
|
792
797
|
if not self._checkValidFact(fact):
|
|
793
798
|
continue
|
|
794
799
|
for qname, value in fact.context.qnameDims.items():
|
|
795
|
-
if
|
|
796
|
-
|
|
797
|
-
|
|
800
|
+
if value.xValid < VALID:
|
|
801
|
+
continue
|
|
802
|
+
if qname.localName == dimensionLocalName:
|
|
803
|
+
yield cast(str, value.xValue.localName)
|
|
798
804
|
|
|
799
805
|
def validate(self) -> None:
|
|
800
806
|
"""
|
|
@@ -866,6 +872,14 @@ class ValidateUK:
|
|
|
866
872
|
self.validateAuditedOtherLLP()
|
|
867
873
|
else:
|
|
868
874
|
self.validateAuditedOtherCompany()
|
|
875
|
+
elif self.scopeAccounts in {
|
|
876
|
+
ScopeAccounts.GROUP_ONLY.value,
|
|
877
|
+
ScopeAccounts.CONSOLIDATED_GROUP.value,
|
|
878
|
+
}:
|
|
879
|
+
if self.legalFormEntity == CONCEPT_LLP:
|
|
880
|
+
self.validateAuditedGroupLLP()
|
|
881
|
+
else:
|
|
882
|
+
self.validateAuditedGroupCompany()
|
|
869
883
|
|
|
870
884
|
def validateCharities(self) -> None:
|
|
871
885
|
"""
|
|
@@ -1306,3 +1320,53 @@ class ValidateUK:
|
|
|
1306
1320
|
result = self._evaluateCode(CO_SM_CO)
|
|
1307
1321
|
if not result.success:
|
|
1308
1322
|
self._errorOnMissingFactText(CO_SM_CO, result)
|
|
1323
|
+
|
|
1324
|
+
def validateAuditedGroupCompany(self) -> None:
|
|
1325
|
+
"""
|
|
1326
|
+
Checks conditions applicable to audited group companies:
|
|
1327
|
+
Co.Audit AND ((Co.DirReport AND Co.ProfLoss AND Co.Group) OR Co.SmCo).
|
|
1328
|
+
"""
|
|
1329
|
+
result = self._evaluateCode(CO_AUDIT)
|
|
1330
|
+
if not result.success:
|
|
1331
|
+
self._yieldErrorOrWarning(CO_AUDIT, result)
|
|
1332
|
+
|
|
1333
|
+
result = self._evaluateCode(CO_SM_CO)
|
|
1334
|
+
if not result.success:
|
|
1335
|
+
failedOr = False
|
|
1336
|
+
dirRepResult = self._evaluateCode(CO_DIR_REP)
|
|
1337
|
+
if not dirRepResult.success:
|
|
1338
|
+
self._yieldErrorOrWarning(CO_DIR_REP, dirRepResult)
|
|
1339
|
+
failedOr = True
|
|
1340
|
+
profLossResult = self._evaluateCode(CO_PROF_LOSS)
|
|
1341
|
+
if not profLossResult.success:
|
|
1342
|
+
self._yieldErrorOrWarning(CO_PROF_LOSS, profLossResult)
|
|
1343
|
+
failedOr = True
|
|
1344
|
+
groupResult = self._evaluateCode(CO_GROUP)
|
|
1345
|
+
if not groupResult.success:
|
|
1346
|
+
self._yieldErrorOrWarning(CO_GROUP, groupResult)
|
|
1347
|
+
failedOr = True
|
|
1348
|
+
if failedOr:
|
|
1349
|
+
self._errorOnMissingFactText(CO_SM_CO, result)
|
|
1350
|
+
|
|
1351
|
+
def validateAuditedGroupLLP(self) -> None:
|
|
1352
|
+
"""
|
|
1353
|
+
Checks conditions applicable to audited group companies:
|
|
1354
|
+
LP.Audit AND ((LP.ProfLoss+LP.Group) OR LP.SmLp)
|
|
1355
|
+
"""
|
|
1356
|
+
result = self._evaluateCode(LP_AUDIT)
|
|
1357
|
+
if not result.success:
|
|
1358
|
+
self._yieldErrorOrWarning(LP_AUDIT, result)
|
|
1359
|
+
|
|
1360
|
+
result = self._evaluateCode(LP_SM_LP)
|
|
1361
|
+
if not result.success:
|
|
1362
|
+
failedOr = False
|
|
1363
|
+
profLossResult = self._evaluateCode(LP_PROF_LOSS)
|
|
1364
|
+
if not profLossResult.success:
|
|
1365
|
+
self._yieldErrorOrWarning(LP_PROF_LOSS, profLossResult)
|
|
1366
|
+
failedOr = True
|
|
1367
|
+
groupResult = self._evaluateCode(LP_GROUP)
|
|
1368
|
+
if not groupResult.success:
|
|
1369
|
+
self._yieldErrorOrWarning(LP_GROUP, groupResult)
|
|
1370
|
+
failedOr = True
|
|
1371
|
+
if failedOr:
|
|
1372
|
+
self._errorOnMissingFactText(LP_SM_LP, result)
|
|
@@ -8,15 +8,16 @@ References:
|
|
|
8
8
|
- [HMRC CT Inline XBRL Style Guide](https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/434588/xbrl-style-guide.pdf)
|
|
9
9
|
"""
|
|
10
10
|
import os
|
|
11
|
-
from math import isnan
|
|
12
11
|
from arelle import ModelDocument, XmlUtil
|
|
13
|
-
from arelle.ModelValue import qname, dateTime, DATE
|
|
14
|
-
from arelle.
|
|
12
|
+
from arelle.ModelValue import qname, dateTime, DATE, dateUnionEqual
|
|
13
|
+
from arelle.ValidateDuplicateFacts import getDuplicateFactSets
|
|
14
|
+
from arelle.ValidateXbrlCalcs import insignificantDigits
|
|
15
15
|
from arelle.Version import authorLabel, copyrightLabel
|
|
16
16
|
from arelle.XbrlConst import xbrli, qnXbrliXbrl
|
|
17
17
|
import regex as re
|
|
18
18
|
import tinycss2.ast
|
|
19
19
|
from collections import defaultdict
|
|
20
|
+
from arelle.XmlValidate import VALID
|
|
20
21
|
|
|
21
22
|
from .ValidateUK import ValidateUK
|
|
22
23
|
|
|
@@ -38,12 +39,18 @@ IMG_URL_CSS_PROPERTIES = frozenset([
|
|
|
38
39
|
EMPTYDICT = {}
|
|
39
40
|
_6_APR_2008 = dateTime("2008-04-06", type=DATE)
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
GENERIC_DIMENSION_VALIDATIONS = {
|
|
43
|
+
# "LocalName": (range of numbers if any, first item name, 2nd choice item name if any)
|
|
44
|
+
"SpecificDiscontinuedOperation": (1, 8, "DescriptionDiscontinuedOperationOrNon-currentAssetsOrDisposalGroupHeldForSale"),
|
|
45
|
+
"SpecificNon-currentAssetsDisposalGroupHeldForSale": (1, 8, "DescriptionDiscontinuedOperationOrNon-currentAssetsOrDisposalGroupHeldForSale"),
|
|
42
46
|
"Chairman": ("NameEntityOfficer",),
|
|
43
47
|
"ChiefExecutive": ("NameEntityOfficer",),
|
|
44
48
|
"ChairmanChiefExecutive": ("NameEntityOfficer",),
|
|
45
49
|
"SeniorPartnerLimitedLiabilityPartnership": ("NameEntityOfficer",),
|
|
46
|
-
"
|
|
50
|
+
"CorporateTrustee": (1, 3, "NameEntityOfficer"),
|
|
51
|
+
"DirectorOfCorporateTrustee": ("NameEntityOfficer",),
|
|
52
|
+
"CustodianTrustee": ("NameEntityOfficer",),
|
|
53
|
+
"Trustee": (1, 20, "NameEntityOfficer",),
|
|
47
54
|
"CompanySecretary": (1, 2, "NameEntityOfficer",),
|
|
48
55
|
"CompanySecretaryDirector": (1, 2, "NameEntityOfficer",),
|
|
49
56
|
"Director": (1, 40, "NameEntityOfficer",),
|
|
@@ -60,7 +67,7 @@ COMMON_GENERIC_DIMENSIONS = {
|
|
|
60
67
|
"UnconsolidatedStructuredEntity": (1, 5, "NameUnconsolidatedStructuredEntity"),
|
|
61
68
|
"IntermediateParent": (1, 5, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
62
69
|
"EntityWithJointControlOrSignificantInfluence": (1, 5, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
63
|
-
"
|
|
70
|
+
"OtherGroupMember": (1, 8, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
64
71
|
"KeyManagementIndividualGroup": (1, 5, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
65
72
|
"CloseFamilyMember": (1, 5, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
66
73
|
"EntityControlledByKeyManagementPersonnel": (1, 5, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
@@ -77,6 +84,7 @@ COMMON_GENERIC_DIMENSIONS = {
|
|
|
77
84
|
"OtherPost-employmentBenefitPlan": (1, 2, "NameDefinedContributionPlan", "NameDefinedBenefitPlan"),
|
|
78
85
|
"OtherContractType": (1, 2, "DescriptionOtherContractType"),
|
|
79
86
|
"OtherDurationType": (1, 2, "DescriptionOtherContractDurationType"),
|
|
87
|
+
"OtherChannelType": (1, 2, "DescriptionOtherSalesChannelType"),
|
|
80
88
|
"SalesChannel": (1, 2, "DescriptionOtherSalesChannelType"),
|
|
81
89
|
}
|
|
82
90
|
|
|
@@ -115,33 +123,6 @@ MUST_HAVE_ONE_ITEM = {
|
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
|
|
118
|
-
GENERIC_DIMENSION_VALIDATION = {
|
|
119
|
-
# "taxonomyType": { "LocalName": (range of numbers if any, first item name, 2nd choice item name if any)
|
|
120
|
-
"ukGAAP": COMMON_GENERIC_DIMENSIONS,
|
|
121
|
-
"ukIFRS": COMMON_GENERIC_DIMENSIONS,
|
|
122
|
-
"charities": {
|
|
123
|
-
**COMMON_GENERIC_DIMENSIONS,
|
|
124
|
-
**{
|
|
125
|
-
"Trustee": (1, 20, "NameEntityOfficer"),
|
|
126
|
-
"CorporateTrustee": (1, 3, "NameEntityOfficer"),
|
|
127
|
-
"CustodianTrustee": (1, 3, "NameEntityOfficer"),
|
|
128
|
-
"Director1CorporateTrustee": ("NameEntityOfficer",),
|
|
129
|
-
"Director2CorporateTrustee": ("NameEntityOfficer",),
|
|
130
|
-
"Director3CorporateTrustee": ("NameEntityOfficer",),
|
|
131
|
-
"TrusteeTrustees": (1, 5, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
132
|
-
"CloseFamilyMemberTrusteeTrustees": (1, 5, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
133
|
-
"EntityControlledTrustees": (1, 5, "NameOrDescriptionRelatedPartyIfNotDefinedByAnotherTag"),
|
|
134
|
-
"Activity": (1, 50, "DescriptionActivity"),
|
|
135
|
-
"MaterialFund": (1, 50, "DescriptionsMaterialFund"),
|
|
136
|
-
"LinkedCharity": (1, 5, "DescriptionActivitiesLinkedCharity"),
|
|
137
|
-
"NameGrantRecipient": (1, 50, "NameSpecificInstitutionalGrantRecipient"),
|
|
138
|
-
"ConcessionaryLoan": (1, 50, "DescriptionConcessionaryLoan"),
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
"FRS": COMMON_GENERIC_DIMENSIONS,
|
|
142
|
-
"FRS-2022": COMMON_GENERIC_DIMENSIONS
|
|
143
|
-
}
|
|
144
|
-
|
|
145
126
|
ALLOWED_IMG_MIME_TYPES = (
|
|
146
127
|
"data:image/gif;base64",
|
|
147
128
|
"data:image/jpeg;base64", "data:image/jpg;base64", # note both jpg and jpeg are in use
|
|
@@ -221,9 +202,9 @@ def validateXbrlFinally(val, *args, **kwargs):
|
|
|
221
202
|
scheme, identifier = c1.entityIdentifier
|
|
222
203
|
if scheme == "http://www.companieshouse.gov.uk/":
|
|
223
204
|
companyReferenceNumberContexts[identifier].append(c1.id)
|
|
224
|
-
atLeastOneFacts =
|
|
205
|
+
atLeastOneFacts = defaultdict(set)
|
|
225
206
|
uniqueFacts = {} # key = (qname, context hash, unit hash, lang)
|
|
226
|
-
mandatoryFacts =
|
|
207
|
+
mandatoryFacts = defaultdict(set)
|
|
227
208
|
mandatoryGDV = defaultdict(set)
|
|
228
209
|
factForConceptContextUnitHash = defaultdict(list)
|
|
229
210
|
hasCompaniesHouseContext = any(cntx.entityIdentifier[0] == "http://www.companieshouse.gov.uk/"
|
|
@@ -243,7 +224,7 @@ def validateXbrlFinally(val, *args, **kwargs):
|
|
|
243
224
|
else:
|
|
244
225
|
l = _memName
|
|
245
226
|
n = None
|
|
246
|
-
gdv =
|
|
227
|
+
gdv = GENERIC_DIMENSION_VALIDATIONS.get(l)
|
|
247
228
|
if (gdv and (n is None or
|
|
248
229
|
(isinstance(gdv[0],int) and isinstance(gdv[1],int) and n >= gdv[0] and n <= gdv[1]))):
|
|
249
230
|
gdvFacts = [f for f in gdv if isinstance(f,str)]
|
|
@@ -270,12 +251,12 @@ def validateXbrlFinally(val, *args, **kwargs):
|
|
|
270
251
|
factNamespaceURI = f.qname.namespaceURI
|
|
271
252
|
factLocalName = f.qname.localName
|
|
272
253
|
if factLocalName in MANDATORY_ITEMS[val.txmyType]:
|
|
273
|
-
mandatoryFacts[factLocalName]
|
|
254
|
+
mandatoryFacts[factLocalName].add(f)
|
|
274
255
|
if val.txmyType in MUST_HAVE_ONE_ITEM and factLocalName in MUST_HAVE_ONE_ITEM[val.txmyType]:
|
|
275
|
-
atLeastOneFacts[factLocalName]
|
|
256
|
+
atLeastOneFacts[factLocalName].add(f)
|
|
276
257
|
if factLocalName == "UKCompaniesHouseRegisteredNumber" and val.isAccounts:
|
|
277
258
|
if hasCompaniesHouseContext:
|
|
278
|
-
mandatoryFacts[factLocalName]
|
|
259
|
+
mandatoryFacts[factLocalName].add(f)
|
|
279
260
|
for _cntx in contextsUsed:
|
|
280
261
|
_scheme, _identifier = _cntx.entityIdentifier
|
|
281
262
|
if _scheme == "http://www.companieshouse.gov.uk/" and f.xValue != _identifier:
|
|
@@ -346,17 +327,72 @@ def validateXbrlFinally(val, *args, **kwargs):
|
|
|
346
327
|
checkFacts(modelXbrl.facts)
|
|
347
328
|
|
|
348
329
|
if val.isAccounts:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
330
|
+
startDate = None
|
|
331
|
+
endDate = None
|
|
332
|
+
_missingItems = []
|
|
333
|
+
for fact in mandatoryFacts.get('EndDateForPeriodCoveredByReport', set()):
|
|
334
|
+
if (fact is not None and
|
|
335
|
+
fact.xValid >= VALID and
|
|
336
|
+
fact.context is not None and
|
|
337
|
+
fact.context.isInstantPeriod and
|
|
338
|
+
dateUnionEqual(fact.context.instantDate, fact.xValue)):
|
|
339
|
+
endDate = fact.xValue
|
|
340
|
+
break
|
|
341
|
+
else:
|
|
342
|
+
_missingItems.append('EndDateForPeriodCoveredByReport')
|
|
343
|
+
|
|
344
|
+
for fact in mandatoryFacts.get('StartDateForPeriodCoveredByReport', set()):
|
|
345
|
+
if (fact is not None and
|
|
346
|
+
fact.xValid >= VALID and
|
|
347
|
+
fact.context is not None and
|
|
348
|
+
fact.context.isInstantPeriod and
|
|
349
|
+
dateUnionEqual(fact.context.instantDate, endDate)):
|
|
350
|
+
startDate = fact.xValue
|
|
351
|
+
break
|
|
352
|
+
else:
|
|
353
|
+
_missingItems.append('StartDateForPeriodCoveredByReport')
|
|
354
|
+
|
|
355
|
+
if startDate is not None and endDate is not None:
|
|
356
|
+
mandatoryConceptsToCheck = MANDATORY_ITEMS[val.txmyType]
|
|
357
|
+
if hasCompaniesHouseContext:
|
|
358
|
+
mandatoryConceptsToCheck = MANDATORY_ITEMS[val.txmyType] | {"UKCompaniesHouseRegisteredNumber"}
|
|
359
|
+
|
|
360
|
+
for mandatoryConcept in mandatoryConceptsToCheck:
|
|
361
|
+
if mandatoryConcept in ['StartDateForPeriodCoveredByReport', 'EndDateForPeriodCoveredByReport']:
|
|
362
|
+
continue
|
|
363
|
+
foundFact = False
|
|
364
|
+
for fact in mandatoryFacts.get(mandatoryConcept, set()):
|
|
365
|
+
if (fact is not None and fact.context is not None and fact.xValid >= VALID and
|
|
366
|
+
((fact.context.isInstantPeriod and dateUnionEqual(fact.context.instantDate, endDate) or
|
|
367
|
+
(fact.context.isStartEndPeriod and dateUnionEqual(fact.context.startDatetime, startDate) and
|
|
368
|
+
dateUnionEqual(fact.context.endDate, endDate, instantEndDate=True))))):
|
|
369
|
+
foundFact = True
|
|
370
|
+
break
|
|
371
|
+
if not foundFact:
|
|
372
|
+
_missingItems.append(mandatoryConcept)
|
|
373
|
+
|
|
352
374
|
if _missingItems:
|
|
353
375
|
modelXbrl.error("JFCVC.3312",
|
|
354
|
-
_("
|
|
376
|
+
_("The following mandatory concepts are either not tagged on a fact or are tagged on facts that "
|
|
377
|
+
"have contexts that do not align with the dates as reported in "
|
|
378
|
+
"'StartDateForPeriodCoveredByReport' and 'EndDateForPeriodCoveredByReport': %(missingItems)s"),
|
|
355
379
|
modelObject=modelXbrl, missingItems=", ".join(sorted(_missingItems)))
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
380
|
+
|
|
381
|
+
if startDate is not None and endDate is not None and val.txmyType in MUST_HAVE_ONE_ITEM:
|
|
382
|
+
foundFact = False
|
|
383
|
+
for mustHaveOneConcept in MUST_HAVE_ONE_ITEM[val.txmyType]:
|
|
384
|
+
for fact in atLeastOneFacts.get(mustHaveOneConcept, set()):
|
|
385
|
+
if (fact is not None and fact.context is not None and fact.xValid >= VALID and
|
|
386
|
+
((fact.context.isInstantPeriod and dateUnionEqual(fact.context.instantDate, endDate) or
|
|
387
|
+
(fact.context.isStartEndPeriod and dateUnionEqual(fact.context.startDatetime, startDate) and
|
|
388
|
+
dateUnionEqual(fact.context.endDate, endDate, instantEndDate=True))))):
|
|
389
|
+
foundFact = True
|
|
390
|
+
break
|
|
391
|
+
|
|
392
|
+
if not foundFact:
|
|
393
|
+
modelXbrl.error("JFCVC.3312.atLeastOne",
|
|
394
|
+
_("At least one of the facts is MANDATORY: %(missingItems)s"),
|
|
395
|
+
modelObject=modelXbrl, missingItems=", ".join(sorted(MUST_HAVE_ONE_ITEM[val.txmyType])))
|
|
360
396
|
|
|
361
397
|
''' removed with JFCVC v4.0 2020-06-09
|
|
362
398
|
f = mandatoryFacts.get("StartDateForPeriodCoveredByReport")
|
|
@@ -374,62 +410,12 @@ def validateXbrlFinally(val, *args, **kwargs):
|
|
|
374
410
|
_("Generic dimension members have no associated name or description item, member names (name or description item): %(memberNames)s"),
|
|
375
411
|
modelObject=modelXbrl, memberNames=", ".join(sorted(memLocalNamesMissing)))
|
|
376
412
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if getattr(f,"xValid", 0) >= 4:
|
|
384
|
-
cuDict = aspectEqualFacts[(f.qname,
|
|
385
|
-
(f.xmlLang or "").lower() if f.concept.type.isWgnStringFactType else None)]
|
|
386
|
-
_matched = False
|
|
387
|
-
for (_cntx,_unit),fList in cuDict.items():
|
|
388
|
-
if (((_cntx is None and f.context is None) or (f.context is not None and f.context.isEqualTo(_cntx))) and
|
|
389
|
-
((_unit is None and f.unit is None) or (f.unit is not None and f.unit.isEqualTo(_unit)))):
|
|
390
|
-
_matched = True
|
|
391
|
-
fList.append(f)
|
|
392
|
-
break
|
|
393
|
-
if not _matched:
|
|
394
|
-
cuDict[(f.context,f.unit)] = [f]
|
|
395
|
-
decVals = {}
|
|
396
|
-
for cuDict in aspectEqualFacts.values(): # dups by qname, lang
|
|
397
|
-
for fList in cuDict.values(): # dups by equal-context equal-unit
|
|
398
|
-
if len(fList) > 1:
|
|
399
|
-
f0 = fList[0]
|
|
400
|
-
if f0.concept.isNumeric:
|
|
401
|
-
if any(f.isNil for f in fList):
|
|
402
|
-
_inConsistent = not all(f.isNil for f in fList)
|
|
403
|
-
else: # not all have same decimals
|
|
404
|
-
_d = inferredDecimals(f0)
|
|
405
|
-
_v = f0.xValue
|
|
406
|
-
_inConsistent = isnan(_v) # NaN is incomparable, always makes dups inconsistent
|
|
407
|
-
decVals[_d] = _v
|
|
408
|
-
aMax, bMin, _inclA, _inclB = rangeValue(_v, _d)
|
|
409
|
-
for f in fList[1:]:
|
|
410
|
-
_d = inferredDecimals(f)
|
|
411
|
-
_v = f.xValue
|
|
412
|
-
if isnan(_v):
|
|
413
|
-
_inConsistent = True
|
|
414
|
-
break
|
|
415
|
-
if _d in decVals:
|
|
416
|
-
_inConsistent |= _v != decVals[_d]
|
|
417
|
-
else:
|
|
418
|
-
decVals[_d] = _v
|
|
419
|
-
a, b, _inclA, _inclB = rangeValue(_v, _d)
|
|
420
|
-
if a > aMax: aMax = a
|
|
421
|
-
if b < bMin: bMin = b
|
|
422
|
-
if not _inConsistent:
|
|
423
|
-
_inConsistent = (bMin < aMax)
|
|
424
|
-
decVals.clear()
|
|
425
|
-
else:
|
|
426
|
-
_inConsistent = any(not f.isVEqualTo(f0) for f in fList[1:])
|
|
427
|
-
if _inConsistent:
|
|
428
|
-
modelXbrl.error("JFCVC.3314",
|
|
429
|
-
"Inconsistent duplicate fact values %(fact)s: %(values)s.",
|
|
430
|
-
modelObject=fList, fact=f0.qname, contextID=f0.contextID, values=", ".join(f.value for f in fList))
|
|
431
|
-
aspectEqualFacts.clear()
|
|
432
|
-
del factForConceptContextUnitHash, aspectEqualFacts
|
|
413
|
+
for duplicateFactSet in getDuplicateFactSets(modelXbrl.facts, includeSingles=False):
|
|
414
|
+
if duplicateFactSet.areAnyInconsistent:
|
|
415
|
+
f0 = duplicateFactSet.facts[0]
|
|
416
|
+
modelXbrl.error("JFCVC.3314",
|
|
417
|
+
"Inconsistent duplicate fact values %(fact)s: %(values)s.",
|
|
418
|
+
modelObject=duplicateFactSet.facts, fact=f0.qname, values=", ".join(f'"{f.value}"' for f in duplicateFactSet))
|
|
433
419
|
|
|
434
420
|
if modelXbrl.modelDocument.type == ModelDocument.Type.INLINEXBRL:
|
|
435
421
|
rootElt = modelXbrl.modelDocument.xmlRootElement
|