Fix EBA-DEC-002 false positives on integer metrics with pure unit#111
Open
magnumog wants to merge 1 commit intoMeaningful-Data:mainfrom
Open
Fix EBA-DEC-002 false positives on integer metrics with pure unit#111magnumog wants to merge 1 commit intoMeaningful-Data:mainfrom
magnumog wants to merge 1 commit intoMeaningful-Data:mainfrom
Conversation
Fact.metric is stored in Clark notation ({namespace}localname), but
_build_metric_type_map() was keyed on prefix notation (eba_met:qXYZ)
taken from the module. Every lookup missed, and the validator fell
back to unit-based inference which classifies every pure-unit fact as
a percentage — including integer counters such as qAZH, qCCG, qDGB.
- Add Fact.metric_qname property that exposes the metric in the same
prefix form the module uses (via the existing _normalize_metric_value
helper). Fact.metric is left unchanged for backward compatibility.
- Update DEC-001, DEC-002 and the realistic rule to use metric_qname.
- Update DEC-003 to use metric_qname AND remove the unit-based fallback:
integer classification is taxonomy-driven or it does not happen. The
unit (xbrli:pure) carries no signal that distinguishes integer from
percentage metrics, and EBA Filing Rules §2.18 ties classification to
the primary item, not the unit.
- Add debug log when a module is loaded but the metric is not in its
type_map — surfaces data-quality / taxonomy-mismatch issues.
- Add 12 regression tests across 5 classes covering the failure mode,
the positive paths (percentage + monetary still flagged correctly),
metric_qname normalisation, and fallback logging.
- CHANGELOG entry under [Unreleased] → Fixed.
Tests: 962 passed in full suite; 65 passed in tests/test_eba_decimals.py
(53 pre-existing + 12 new). ruff check clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes false-positive EBA-DEC-002 findings raised on integer-typed metrics that use the
pureunit (e.g. COREP "number of exposures", ALM counters). In the XBRL-XML path, every such fact is currently flagged as a percentage with insufficient decimals, producing hundreds to thousands of spurious errors on real COREP / FINREP / ALM filings. The spurious errors also block theconvertpipeline when--validate/--ebais enabled, preventing XBRL-XML → XBRL-CSV conversion of otherwise-valid filings.Root cause:
Fact.metricis stored in Clark notation ({http://www.eba.europa.eu/xbrl/crr/dict/met}qAZH) as returned by lxml, but_build_metric_type_map()keys the lookup on prefix notation (eba_met:qAZH) taken from the module. The two strings never collide, so every fact falls through to_infer_type_from_unit(), which classifies anyxbrli:purefact as a percentage — including integer counters.The fix adds a
Fact.metric_qnameproperty that exposes the metric in the same prefix form the module uses, and updates the four XML decimals rules to look up that property. For EBA-DEC-003 the unit-based fallback has been removed entirely: integer classification is taxonomy-driven or it does not happen.Conformance to the EBA Filing Rules
Cross-checked against EBA Filing Rules v5.7 (24 November 2025), §2.18 "Interpretation of the decimals setting". The Accuracy Requirements table on p. 33 defines the allowed
@decimalsvalues by the metric's Data Type, not by its unit:>= -3(or>= -4,>= -6for FP/ESG/P3/REM)42563.26>= 40.1234(= 12.34 %)0126Footnote 17 to the same rule is explicit that the classification is taxonomy-driven, not unit-driven:
Since both Percentage and Integer metrics use
xbrli:pureas their unit, the unit carries no information that can distinguish them — the validator must consult the taxonomy's primary-item classification. That is exactly what this patch now does:_build_metric_type_map()is derived from the module'sVariable._attributes($decimalsMonetary/$decimalsPercentage/$decimalsInteger/$decimalsDecimals), and each fact is resolved via its prefix-form QName. The previous code silently failed this lookup for every fact and then guessed from the unit — which is not a mechanism sanctioned anywhere in §2.18.Source: eba_filing_rules_v5.7_2025_11_24.pdf, §2.18 "Interpretation of the decimals setting", pp. 31–34.
Real-world impact
On production COREP ALM filings (EBA Framework 4.2, submission date 2026-03-31), this bug caused several hundred spurious EBA-DEC-002 findings per instance, concentrated on integer counters such as
qAZH,qCCG, andqDGB. Every one of these facts was correctly reported withdecimals="0"on apureunit, in conformance with §2.18, but xbridge misclassified them as percentages and demandeddecimals >= 4.Downstream consequence: any XBRL-XML → XBRL-CSV conversion invoked with validation enabled (
convert --validate/convert --eba, or the equivalent programmaticconvert_instance(..., eba=True)) aborted withValidationErrorbefore producing the CSV report package, even though the input instance was a valid EBA filing. After this fix, the same instances convert cleanly and the post-conversion CSV output is unaffected (no CSV-side rules were modified).Type of Change
Related Issues
Closes #
Related to #
Changes Made
src/xbridge/instance.py— addedFact.metric_qnameproperty that returns the fact's metric normalised to prefix form (eba_met:qAZH) via the existing_normalize_metric_value()helper. The rawFact.metric(Clark notation) is left unchanged for backward compatibility. Result is cached on the instance and invalidated on re-parse().src/xbridge/validation/rules/eba_decimals.py_build_metric_type_map()to state that its keys are the prefix form used by the module._lookup_metric_type()as the single resolution helper used by DEC-001 (monetary) and DEC-002 (percentage). Lookup order: module type_map viaFact.metric_qname, then unit-based inference as a fallback when no module is loaded._logger.debugdiagnostic when a module is loaded but a metric is not present in the type_map — surfaces data-quality / taxonomy-mismatch issues without spamming normal runs.check_integer_decimals_xmlnow uses the taxonomy type_map only — the unit-based fallback that caused the false positives has been removed. Integer classification without a module is a no-op (matching the original intent of EBA-DEC-003).check_realistic_decimals_xmlswitched tometric_qnamefor consistent finding context.tests/test_eba_decimals.py— added 12 regression tests across five new classes:TestEBADEC002IntegerMetricRegression— the exact failure mode (integer metric +pureunit +decimals="0"/"4"): must not trigger DEC-002 and, with a module loaded, triggers DEC-003 only when appropriate.TestEBADEC002PercentageMetricWithModule— genuine percentage metrics still trigger DEC-002 when a module is loaded (confirms the prefix path works end-to-end, not just that Clark matches fail).TestEBADEC001MonetaryMetricWithModule— monetary metrics still trigger DEC-001.TestFactMetricNormalisation— verifiesFact.metricremains Clark-form,Fact.metric_qnameis prefix-form and is cached per-fact.TestLookupMetricTypeFallbackLogging— confirms the debug log is emitted when (and only when) a module is loaded and a metric is missing from it.CHANGELOG.md— entry added under## [Unreleased]→### Fixed.Testing
Tests Added
Testing Performed
Test results:
test_eba_decimals.py(53 pre-existing + 12 new).Documentation
_build_metric_type_map, newmetric_qnameproperty, new_lookup_metric_typehelper, andcheck_integer_decimals_xmlexplain the prefix-keyed lookup and why the integer rule does not fall back to units.Code Quality
ruff checkandruff format— clean.mypytype checking — no new errors introduced (pre-existing missing-stubs warnings forpandas/lxmlunchanged).Breaking Changes
None.
Impact:
Fact.metricretains its existing Clark-notation value; no callers are forced to migrate.Fact.metric_qnameis a new additive property.Migration guide: N/A.
Screenshots (if applicable)
N/A — validator-only change.
Checklist
Additional Notes
Why not normalise
Fact.metricitself to prefix form?Doing so would silently change the value of a public attribute that callers (and downstream projects) may rely on — a behavioural breaking change with no visible signal. Adding a new
metric_qnameproperty is purely additive and lets callers opt in.Why remove the unit-based fallback only in the integer rule?
For monetary and percentage facts, the unit (
iso4217:*,xbrli:pure) is an adequate correctness signal even in the absence of a module — a monetary fact always has a currency unit and a percentage fact almost always haspure. No such signal exists for integer counters: they share thepureunit with percentages, which is exactly the ambiguity that produced this bug. Falling back to units there is guessing, and the guess is wrong for every integer metric. This is also consistent with EBA Filing Rules §2.18 footnote 17, which ties classification to the primary item (taxonomy data type), not the unit.Reviewer Notes
Areas to focus on:
_lookup_metric_type()ineba_decimals.py— single entry point for all three module-driven rules; confirm the fallback ordering matches your expectations.check_integer_decimals_xml— this is the one semantics change in the patch. Integer classification is now strictly taxonomy-driven.Fact.metric_qnamecache invalidation inFact.parse()— ensures re-parse (if it ever happens) produces fresh values.Questions for reviewers:
Fact.metricoutside the decimals rules that would benefit from migrating tometric_qnamein a follow-up?DEBUGtoINFO/ a dedicated warning rule? Keeping it atDEBUGfor now to avoid noise on filings that intentionally use out-of-module metrics.