Skip to content

Commit

Permalink
Merge pull request Arelle#1337 from acsone/esef_2024_drt
Browse files Browse the repository at this point in the history
  • Loading branch information
austinmatherne-wk authored Oct 22, 2024
2 parents 652959a + 598662d commit 3cdc2f5
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 11 deletions.
4 changes: 4 additions & 0 deletions arelle/plugin/validate/ESEF/Const.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
qname("{http://www.xbrl.org/dtr/type/2020-01-21}nonnum:domainItemType"),
))

qnDomainItemTypes2024 = frozenset((
qname("{http://www.xbrl.org/dtr/type/2022-03-31}nonnum:domainItemType"),
))


linkbaseRefTypes = {
"http://www.xbrl.org/2003/role/calculationLinkbaseRef": "cal",
Expand Down
54 changes: 49 additions & 5 deletions arelle/plugin/validate/ESEF/ESEF_Current/DTS.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import unicodedata
from collections import defaultdict
from datetime import datetime

import regex as re

Expand All @@ -23,6 +24,7 @@
linkbaseRefTypes,
qnDomainItemTypes,
qnDomainItemTypes2023,
qnDomainItemTypes2024,
)
from ..Util import isChildOfNotes, isExtension, getDisclosureSystemYear

Expand All @@ -39,7 +41,8 @@ def checkFilingDTS(val: ValidateXbrl, modelDocument: ModelDocument, esefNotesCon

isExtensionDoc = isExtension(val, modelDocument)
filenamePattern = filenameRegex = None
anchorAbstractExtensionElements = getDisclosureSystemYear(val.modelXbrl) < 2023 and val.authParam["extensionElementsAnchoring"] == "include abstract"
esefDisclosureSystemYear = getDisclosureSystemYear(val.modelXbrl)
anchorAbstractExtensionElements = esefDisclosureSystemYear < 2023 and val.authParam["extensionElementsAnchoring"] == "include abstract"
allowCapsInLc3Words = val.authParam["LC3AllowCapitalsInWord"]

def lc3wordAdjust(word: str) -> str:
Expand All @@ -49,6 +52,14 @@ def lc3wordAdjust(word: str) -> str:
return word[0].upper() + word[1:]
return word

esefTaxonomyYear = esefDisclosureSystemYear # if the taxonomy isn't recognised, take the disclosure system
for url in val.modelXbrl.namespaceDocs.keys():
match = re.match("http[s]?://www.esma.europa.eu/taxonomy/([0-9]{4}-[0-9]{2}-[0-9]{2})/.*", url)
if match:
date = match.groups()[0]
esefTaxonomyYear = datetime.strptime(date, "%Y-%m-%d").year
break

if not isExtensionDoc:
pass

Expand Down Expand Up @@ -109,7 +120,13 @@ def lc3wordAdjust(word: str) -> str:
val.modelXbrl.error("ESEF.3.4.3.extensionTaxonomyDimensionNotAssignedDefaultMemberInDedicatedPlaceholder",
_("Each dimension in an issuer specific extension taxonomy MUST be assigned to a default member in the ELR with role URI http://www.esma.europa.eu/xbrl/role/core/ifrs-dim_role-990000 defined in esef_cor.xsd schema file. %(qname)s"),
modelObject=modelConcept, qname=modelConcept.qname)
esefDomainItemTypes = qnDomainItemTypes if getDisclosureSystemYear(val.modelXbrl) < 2023 else qnDomainItemTypes2023

if esefDisclosureSystemYear < 2023:
esefDomainItemTypes = qnDomainItemTypes
elif esefDisclosureSystemYear == 2023:
esefDomainItemTypes = qnDomainItemTypes2023
else:
esefDomainItemTypes = qnDomainItemTypes2024
if modelConcept.isDomainMember and modelConcept in val.domainMembers and modelConcept.typeQname not in esefDomainItemTypes:
domainMembersWrongType.append(modelConcept)
if modelConcept.isPrimaryItem and not modelConcept.isAbstract:
Expand Down Expand Up @@ -147,6 +164,23 @@ def lc3wordAdjust(word: str) -> str:
extLineItemsWronglyAnchored.append(modelConcept)
if modelConcept.isMonetary and not modelConcept.balance:
extMonetaryConceptsWithoutBalance.append(modelConcept)
if esefDisclosureSystemYear >= 2024:
widerConcept = widerNarrowerRelSet.fromModelObject(modelConcept)
narrowerConcept = widerNarrowerRelSet.toModelObject(modelConcept)

# Transform the qname to str for the later join()
widerTypes = set(str(r.toModelObject.typeQname) for r in widerConcept)
narrowerTypes = set(str(r.fromModelObject.typeQname) for r in narrowerConcept)

if (narrowerTypes and narrowerTypes != {str(modelConcept.typeQname)}) or (widerTypes and widerTypes != {str(modelConcept.typeQname)}):
widerNarrowerType = "{} {}".format(
"Wider: {}".format(", ".join(widerTypes)) if widerTypes else "",
"Narrower: {}".format(", ".join(narrowerTypes)) if narrowerTypes else ""
)
val.modelXbrl.warning("ESEF.1.4.1.differentExtensionDataType",
_("Issuers should anchor their extension elements to ESEF core taxonomy elements sharing the same data type. Concept: %(qname)s type: %(type)s %(widerNarrowerType)s"),
modelObject=modelConcept, qname=modelConcept.qname, type=modelConcept.typeQname, widerNarrowerType=widerNarrowerType)

# check all lang's of standard label
hasLc3Match = False
lc3names = []
Expand Down Expand Up @@ -204,9 +238,13 @@ def lc3wordAdjust(word: str) -> str:
_("Extension taxonomy MUST NOT define typed dimensions: %(concepts)s."),
modelObject=typedDimsInExtTxmy, concepts=", ".join(str(c.qname) for c in typedDimsInExtTxmy))
if domainMembersWrongType:
xbrlReference322 = "https://www.xbrl.org/dtr/type/2020-01-21/types.xsd"
if getDisclosureSystemYear(val.modelXbrl) < 2023:
if esefDisclosureSystemYear < 2023:
xbrlReference322 = "http://www.xbrl.org/dtr/type/nonNumeric-2009-12-16.xsd"
elif esefDisclosureSystemYear == 2023 or esefTaxonomyYear < 2024:
xbrlReference322 = "https://www.xbrl.org/dtr/type/2020-01-21/types.xsd"
else:
xbrlReference322 = "https://www.xbrl.org/dtr/type/2022-03-31/types.xsd"

val.modelXbrl.error("ESEF.3.2.2.domainMemberWrongDataType",
_("Domain members MUST have domainItemType data type as defined in \"%(xbrlReference)s\": concept %(concepts)s."),
modelObject=domainMembersWrongType, xbrlReference=xbrlReference322,
Expand Down Expand Up @@ -278,6 +316,12 @@ def lc3wordAdjust(word: str) -> str:
if linkEltName == "calculationLink":
val.hasExtensionCal = True
linkbasesFound.add(linkEltName)
if esefDisclosureSystemYear >= 2024:
for arc in linkElt.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}calculationArc"):
if arc.get("{http://www.w3.org/1999/xlink}arcrole") != "https://www.xbrl.org/2023/arcrole/summation-item":
val.modelXbrl.error("ESEF.3.4.1.IncorrectSummationItemArcroleUsed",
_("Starting from the ESEF 2024 taxonomy, only calculation linkbases using the arcrole 'https://www.xbrl.org/2023/arcrole/summation-item' are permitted. Arcrole %(arcrole)s has been found."),
arcrole=arc.get("{http://www.w3.org/1999/xlink}arcrole"))
if linkEltName == "definitionLink":
val.hasExtensionDef = True
linkbasesFound.add(linkEltName)
Expand All @@ -290,7 +334,7 @@ def lc3wordAdjust(word: str) -> str:
if arcrole not in esefDefinitionArcroles:
disallowedArcroles[arcrole].append(arcElt)

if linkEltName in ("definitionLink", ) and getDisclosureSystemYear(val.modelXbrl) == 2023 and val.authParam["validate1_9_1"] in ("true", "True", 1):
if linkEltName in ("definitionLink", ) and esefDisclosureSystemYear == 2023 and val.authParam["validate1_9_1"] in ("true", "True", 1):
for locElt in linkElt.iterchildren("{http://www.xbrl.org/2003/linkbase}loc"):
refObject = locElt.dereference()
if (isinstance(refObject, ModelConcept)
Expand Down
37 changes: 32 additions & 5 deletions arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
import zipfile
from collections import defaultdict
from datetime import datetime
from datetime import datetime, timedelta
from math import isnan
from typing import Any, List, cast

Expand Down Expand Up @@ -146,7 +146,8 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
checkFilingDTS(val, modelXbrl.modelDocument, esefNotesConcepts, [], ifrsNses=_ifrsNses)
modelXbrl.profileActivity("... filer DTS checks", minTimeToShow=1.0)

if getDisclosureSystemYear(modelXbrl) >= 2023:
esefDisclosureSystemYear = getDisclosureSystemYear(modelXbrl)
if esefDisclosureSystemYear >= 2023:
if val.unconsolidated and modelXbrl.fileSource.dir:
instanceNumber = 0
for file in modelXbrl.fileSource.dir:
Expand Down Expand Up @@ -175,7 +176,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
if ifrs_year != esef_year:
# this check isn't precise enough, but the list of available concept isn't available in esef_cor
modelXbrl.warning("ESEF.1.2.IFRSNotYetIncluded",
_("Elements available in the IFRS Taxonomy that were not yet included in the ESEF taxonomy sould not be used."),
_("Elements available in the IFRS Taxonomy that were not yet included in the ESEF taxonomy should not be used."),
modelObject=modelXbrl)

if val.consolidated and not (val.hasExtensionSchema and val.hasExtensionPre and val.hasExtensionCal and val.hasExtensionDef and val.hasExtensionLbl):
Expand Down Expand Up @@ -283,7 +284,7 @@ def checkFootnote(elt: ModelInlineFootnote | ModelResource, text: str) -> None:
modelObject=doc, doctype=docinfo.doctype)

reportIncorrectlyPlacedInPackageRef = "https://www.xbrl.org/Specification/report-package/CR-2023-05-03/report-package-CR-2023-05-03.html"
if getDisclosureSystemYear(modelXbrl) < 2023:
if esefDisclosureSystemYear < 2023:
reportIncorrectlyPlacedInPackageRef = "http://www.xbrl.org/WGN/report-packages/WGN-2018-08-14/report-packages-WGN-2018-08-14.html"

if len(ixdsDocDirs) > 1 and val.consolidated:
Expand Down Expand Up @@ -411,7 +412,7 @@ def checkFootnote(elt: ModelInlineFootnote | ModelResource, text: str) -> None:
modelObject=elt, qname=elt.qname)
elif any(character in elt.stringValue for character in ['&lt;', '&amp;', '&', '<']):
if not (hasattr(elt, 'attrib')) or ('escape' not in elt.attrib or elt.attrib.get('escape').lower() != 'true'):
modelXbrl.error("ESEF.2.2.6.escapedHTMLUsedInBlockTagWithSpecialCharacters" if getDisclosureSystemYear(modelXbrl) < 2023 else "ESEF.2.2.7.escapedHTMLUsedInBlockTagWithSpecialCharacters",
modelXbrl.error("ESEF.2.2.6.escapedHTMLUsedInBlockTagWithSpecialCharacters" if esefDisclosureSystemYear < 2023 else "ESEF.2.2.7.escapedHTMLUsedInBlockTagWithSpecialCharacters",
_("A text block containing '&' or '<' character MUST have an 'escape' attribute: %(qname)s."),
modelObject=elt, qname=elt.qname)
# Check that continuation elements are in the order of html text as rendered to user
Expand Down Expand Up @@ -534,6 +535,7 @@ def checkFootnote(elt: ModelInlineFootnote | ModelResource, text: str) -> None:
contextsWithDisallowedOCEcontent = []
contextsWithPeriodTime: list[ModelContext] = []
contextsWithPeriodTimeZone: list[ModelContext] = []
contextsWithWrongInstantDate: list[ModelContext] = []
contextIdentifiers = defaultdict(list)
nonStandardTypedDimensions: dict[Any, Any] = defaultdict(set)
for context in modelXbrl.contexts.values():
Expand All @@ -548,6 +550,9 @@ def checkFootnote(elt: ModelInlineFootnote | ModelResource, text: str) -> None:
contextsWithPeriodTime.append(context)
if m.group(3):
contextsWithPeriodTimeZone.append(context)

if esefDisclosureSystemYear >= 2024 and context.instantDate and context.instantDate.day == 1 and context.instantDate.month == 1:
contextsWithWrongInstantDate.append(context)
for elt in context.iterdescendants("{http://www.xbrl.org/2003/instance}segment"):
contextsWithDisallowedOCEs.append(context)
break
Expand Down Expand Up @@ -597,6 +602,11 @@ def checkFootnote(elt: ModelInlineFootnote | ModelResource, text: str) -> None:
modelXbrl.error("ESEF.2.1.2.periodWithTimeZone",
_("The xbrli:startDate, xbrli:endDate and xbrli:instant elements MUST identify periods using whole days (i.e. specified without a time zone): %(contextIds)s"),
modelObject=contextsWithPeriodTimeZone, contextIds=", ".join(c.id for c in contextsWithPeriodTimeZone if c.id))
if contextsWithWrongInstantDate:
for context in contextsWithWrongInstantDate:
modelXbrl.error("ESEF.2.1.2.inappropriateInstantDate",
_("Instant date %(actualValue)s in context %(contextID)s shall be replaced by %(expectedValue)s to ensure a better comparability between the facts."),
modelObject=contextsWithWrongInstantDate, actualValue=context.instantDate, expectedValue=context.instantDate - timedelta(days=1), contextID=context.id)

# identify unique contexts and units
mapContext = {}
Expand Down Expand Up @@ -644,11 +654,24 @@ def checkFootnote(elt: ModelInlineFootnote | ModelResource, text: str) -> None:
langsUsedByTextFacts = set()

hasNoFacts = True
factsMissingId = []
for qn, facts in modelXbrl.factsByQname.items():
hasNoFacts = False
if qn in mandatory:
reportedMandatory.add(qn)
for f in facts:
if esefDisclosureSystemYear >= 2024:
if not f.id:
factsMissingId.append(f)
escaped = f.get("escape") in ("true", "1")
if escaped != f.concept.type.isTextBlock:
modelXbrl.error("ESEF.2.2.7.improperApplicationOfEscapeAttribute",
_("Facts with datatype 'dtr-types:textBlockItemType' MUST use the 'escape' attribute set to 'true'. Facts with any other datatype MUST use the 'escape' attribute set to 'false' - fact %(conceptName)s"),
modelObject=f, conceptName=f.concept.qname)
if f.effectiveValue == "0" and f.xValue != 0:
modelXbrl.warning("ESEF.2.2.5.roundedValueBelowScaleNotNull",
_("A value that has been rounded and is below the scale should show a value of zero. It has been found to have the value %(value)s - fact %(conceptName)s"),
modelObject=f, value=f.value, conceptName=f.concept.qname)
if f.precision is not None:
precisionFacts.add(f)
if f.isNumeric and f.concept is not None and getattr(f, "xValid", 0) >= VALID:
Expand Down Expand Up @@ -680,6 +703,10 @@ def checkFootnote(elt: ModelInlineFootnote | ModelResource, text: str) -> None:
# conceptsUsed.add(dim.typedMember)
'''

if esefDisclosureSystemYear >= 2024 and factsMissingId:
modelXbrl.warning("ESEF.2.2.8.missingFactID",
_("All facts should have a unique identifier. Facts %(elements)s have no identifier."),
modelObject=f, elements=", ".join([str(f.qname) for f in factsMissingId]), )
if noLangFacts:
modelXbrl.error("ESEF.2.5.2.undefinedLanguageForTextFact",
_("Each tagged text fact MUST have the 'xml:lang' attribute assigned or inherited."),
Expand Down
2 changes: 1 addition & 1 deletion arelle/plugin/validate/ESEF/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ def modelTestcaseVariationReportPackageIxdsOptions(
"Validate ESMA ESEF-2022",
"validate/ESEF_2022",
],
"version": "1.2023.00",
"version": "1.2024.00",
"description": """ESMA ESEF Filer Manual and RTS Validations.""",
"license": "Apache-2",
"author": authorLabel,
Expand Down
16 changes: 16 additions & 0 deletions arelle/plugin/validate/ESEF/resources/authority-validations.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@
"https://www.esma.europa.eu/taxonomy/2022-03-24/esef_cor.xsd"
]
},
"ESEF-2024": {
"outdatedTaxonomyURLs": [
"http://www.esma.europa.eu/taxonomy/2017-03-31/esef_cor.xsd",
"https://www.esma.europa.eu/taxonomy/2017-03-31/esef_cor.xsd",
"http://www.esma.europa.eu/taxonomy/2019-03-27/esef_cor.xsd",
"https://www.esma.europa.eu/taxonomy/2019-03-27/esef_cor.xsd",
"http://www.esma.europa.eu/taxonomy/2020-03-16/esef_cor.xsd",
"https://www.esma.europa.eu/taxonomy/2020-03-16/esef_cor.xsd",
"http://www.esma.europa.eu/taxonomy/2021-03-24/esef_cor.xsd",
"https://www.esma.europa.eu/taxonomy/2021-03-24/esef_cor.xsd"
],
"effectiveTaxonomyURLs": [
"http://www.esma.europa.eu/taxonomy/2022-03-24/esef_cor.xsd",
"https://www.esma.europa.eu/taxonomy/2022-03-24/esef_cor.xsd"
]
},
"AT": {
"name": "Austria",
"reportPackageMaxMB": "100",
Expand Down
21 changes: 21 additions & 0 deletions arelle/plugin/validate/ESEF/resources/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@
xsi:noNamespaceSchemaLocation="../../../../config/disclosuresystems.xsd">
<!-- see ../../../../config/disclosuresystem.xml for full comments -->

<DisclosureSystem
names="ESMA RTS on ESEF-2024|ESEF-2024|esef-2024"
description="ESMA RTS on ESEF-2024 Validation Checks for Consolidated Financial Statements"
validationType="ESEF"
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
blockDisallowedReferences="false"
validateFileText="false"
contextElement="scenario"
/>

<DisclosureSystem
names="ESMA RTS on ESEF-2024 Unconsolidated|ESEF-Unconsolidated-2024|esef-unconsolidated-2024"
description="ESMA RTS on ESEF-2024 Validation Checks for Unconsolidated Financial Statements"
validationType="ESEF"
exclusiveTypesPattern="EFM|GFM|FERC|HMRC|SBR.NL|EBA|EIOPA|ESEF"
blockDisallowedReferences="false"
validateFileText="false"
contextElement="scenario"
/>


<DisclosureSystem
names="ESMA RTS on ESEF-2023|ESEF-2023|esef-2023|ESEF|esef"
description="ESMA RTS on ESEF-2023 Validation Checks for Consolidated Financial Statements"
Expand Down

0 comments on commit 3cdc2f5

Please sign in to comment.