From 7313a3fc1af801ab8953761ea6587179c6823777 Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Mon, 23 Mar 2026 17:16:56 +0100 Subject: [PATCH 1/5] Have get_value() return the value of the appropriate type. --- brel/brel_fact.py | 11 ++- brel/reportelements/concept.py | 144 +++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/brel/brel_fact.py b/brel/brel_fact.py index 49b2d00c..e866f87d 100644 --- a/brel/brel_fact.py +++ b/brel/brel_fact.py @@ -126,11 +126,18 @@ def __bool__(self) -> bool: f"Fact {self.__id} does not have a bool value. It has value {self.__value}, which does not resolve to a bool" ) - def get_value(self) -> str: + def get_value(self) -> Any: """ :returns Any: The value of the fact. The type of the value depends on the type of the fact. """ - return self.__value + if self.get_concept().get_value().is_integer(): + return int(self) + elif self.get_concept().get_value().is_numeric(): + return float(self) + elif self.get_concept().get_value().is_boolean(): + return bool(self) + else: + return self.__value def get_precision(self) -> float | None: """ diff --git a/brel/reportelements/concept.py b/brel/reportelements/concept.py index 96ef229c..0943747b 100644 --- a/brel/reportelements/concept.py +++ b/brel/reportelements/concept.py @@ -23,6 +23,122 @@ from brel.resource import BrelLabel from brel.services.translation.translation_service import TranslationService +textual_types = [ + "domainItemType", + "escapedItemType", + "xmlNodesItemType", + "xmlItemType", + "textBlockItemType", + "guidanceItemType", + "noLangTokenItemType", + "noLangStringItemType", + "prefixedContentItemType", + "prefixedContentType", + "SQNameItemType", + "SQNameType", + "gYearListItemType", + "dateTimeItemType", + "fractionItemType", + "stringItemType", + "booleanItemType", + "hexBinaryItemType", + "base64BinaryItemType", + "anyURIItemType", + "QNameItemType", + "durationItemType", + "dateTimeItemType", + "timeItemType", + "dateItemType", + "gYearMonthItemType", + "gYearItemType", + "gMonthDayItemType", + "gDayItemType", + "gMonthItemType", + "normalizedStringItemType", + "tokenItemType", + "languageItemType", + "NameItemType", + "NCNameItemType", + "financialInstrumentGlobalIdentifierItemType", +] + +numeric_types = [ + "percentItemType", + "perShareItemType", + "areaItemType", + "volumeItemType", + "massItemType", + "weightItemType", + "energyItemType", + "powerItemType", + "lengthItemType", + "memoryItemType", + "noDecimalsMonetaryItemType", + "nonNegativeMonetaryItemType", + "nonNegativeNoDecimalsMonetaryItemType", + "insolationItemType", + "temperatureItemType", + "pressureItemType", + "frequencyItemType", + "irradianceItemType", + "speedItemType", + "planeAngleItemType", + "voltageItemType", + "electricCurrentItemType", + "forceItemType", + "electricChargeItemType", + "flowItemType", + "massFlowItemType", + "monetaryPerLengthItemType", + "monetaryPerAreaItemType", + "monetaryPerVolumeItemType", + "monetaryPerDurationItemType", + "monetaryPerEnergyItemType", + "monetaryPerMassItemType", + "ghgEmissionsItemType", + "energyPerMonetaryItemType", + "ghgEmissionsPerMonetaryItemType", + "volumePerMonetaryItemType", + "decimalItemType", + "floatItemType", + "doubleItemType", + "integerItemType", + "nonPositiveIntegerItemType", + "negativeIntegerItemType", + "longItemType", + "intItemType", + "shortItemType", + "byteItemType", + "nonNegativeIntegerItemType", + "unsignedLongItemType", + "unsignedIntItemType", + "unsignedShortItemType", + "unsignedByteItemType", + "positiveIntegerItemType", + "monetaryItemType", + "sharesItemType", + "pureItemType", + "perUnitItemType", +] + +integer_types = [ + "noDecimalsMonetaryItemType", + "nonNegativeNoDecimalsMonetaryItemType", + "integerItemType", + "nonPositiveIntegerItemType", + "negativeIntegerItemType", + "longItemType", + "intItemType", + "shortItemType", + "byteItemType", + "nonNegativeIntegerItemType", + "unsignedLongItemType", + "unsignedIntItemType", + "unsignedShortItemType", + "unsignedByteItemType", + "positiveIntegerItemType", +] + class Concept(IReportElement): """ @@ -107,6 +223,34 @@ def get_data_type(self) -> str: """ return self.__data_type + def is_textual(self) -> bool: + """ + Check if the concept is of a textual type. + :returns bool: True 'IFF' the concept is of a textual type, False otherwise + """ + return self.__data_type.split(":")[-1] in textual_types + + def is_numeric(self) -> bool: + """ + Check if the concept is of a numeric type. + :returns bool: True 'IFF' the concept is of a numeric type, False otherwise + """ + return self.__data_type.split(":")[-1] in numeric_types + + def is_integer(self) -> bool: + """ + Check if the concept is of an integer type. + :returns bool: True 'IFF' the concept is of an integer type, False otherwise + """ + return self.__data_type.split(":")[-1] in integer_types + + def is_boolean(self) -> bool: + """ + Check if the concept is of a boolean type. + :returns bool: True 'IFF' the concept is of a boolean type, False otherwise + """ + return self.__data_type.split(":")[-1] == "booleanItemType" + def get_balance_type(self) -> str | None: """ Get the balance type of the concept. From 0b78fcdbfbe99aa7332cadb25f1157b9d4078937 Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Tue, 24 Mar 2026 13:02:19 +0100 Subject: [PATCH 2/5] Improve test. --- tests/core_tests/test_fact.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core_tests/test_fact.py b/tests/core_tests/test_fact.py index ce1b030e..b94189a9 100644 --- a/tests/core_tests/test_fact.py +++ b/tests/core_tests/test_fact.py @@ -66,7 +66,7 @@ def test_qname_getters(): assert isinstance(int(fact), int), "Expected int as fact value is int" assert int(fact) > 1000000, "Expected apples gross profit to be > 1M" assert isinstance( - float(fact), float + fact.get_value(), float ), "Expected float as fact value is float" assert ( float(fact) > 1000000 From cb73df5942abd33d22000f6484901238d90731c8 Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Tue, 24 Mar 2026 13:18:39 +0100 Subject: [PATCH 3/5] implement str() to cast to string. --- brel/brel_fact.py | 18 +++++------------- tests/core_tests/test_fact.py | 21 ++++++++++----------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/brel/brel_fact.py b/brel/brel_fact.py index 2bf76733..281ad9bc 100644 --- a/brel/brel_fact.py +++ b/brel/brel_fact.py @@ -71,9 +71,7 @@ def get_context(self) -> Context: return self.__context def get_value_as_str(self) -> str: - """ - :returns str: The value of the fact as a string. - """ + """[DEPRECATED] Use str() instead.""" return self.__value def get_value_as_int(self) -> int: @@ -141,27 +139,21 @@ def get_value(self) -> Any: def get_precision(self) -> float | None: """ - :returns Any: The precision of the fact. Only applies to numeric facts. + :returns float: The precision of the fact. Only applies to numeric facts. """ return self.__precision def get_decimals(self) -> float | None: """ - :returns Any: The decimals property of the fact. Only applies to numeric facts. + :returns float: The decimals property of the fact. Only applies to numeric facts. """ return self.__decimals def __str__(self) -> str: """ - :returns str: The fact represented as a string. + :returns str: The fact value as a string. """ - output = "" - for aspect in self.__context.get_aspects(): - aspect_name = aspect.get_name() - aspect_value = self.__context.get_characteristic(aspect) - output += f"{aspect_name}: {aspect_value}, " - output += f"value: {self.__value}" - return output + return self.__value # 2nd class citizens def get_concept(self) -> ConceptCharacteristic: diff --git a/tests/core_tests/test_fact.py b/tests/core_tests/test_fact.py index a195427b..ecca9648 100644 --- a/tests/core_tests/test_fact.py +++ b/tests/core_tests/test_fact.py @@ -9,6 +9,7 @@ """ import brel +import json def test_qname_getters(): @@ -17,12 +18,12 @@ def test_qname_getters(): # check if a fact with value = "true" is parsed correctly as a bool fact = filing.get_facts_by_concept_name("dei:DocumentQuarterlyReport")[0] - assert fact._get_id() == "f-2", "Expected fact id to be 'f-2'" # type: ignore + assert fact.get_id() == "f-2", "Expected fact id to be 'f-2'" # type: ignore context = fact.get_context() - assert context._get_id() == "c-1", "Expected context id to be 'c-1'" # type: ignore + assert context.get_id() == "c-1", "Expected context id to be 'c-1'" # type: ignore - assert fact.get_value_as_str() == "true", "Expected 'true' as fact value is 'true'" + assert str(fact) == "true", "Expected 'true' as fact value is 'true'" assert bool(fact) == True, "Expected True as fact value is 'true'" try: int(fact) @@ -47,17 +48,15 @@ def test_qname_getters(): fact.get_entity() ), "Expected '320193' to be in fact entity string" - fact_str = str(fact) - assert "concept" in fact_str, "Expected 'concept' to be in fact string" - assert "period" in fact_str, "Expected 'period' to be in fact string" - assert "entity" in fact_str, "Expected 'entity' to be in fact string" - assert "unit" not in fact_str, "Expected 'unit' not to be in fact string" + fact_str = json.dumps(dict(fact)) + assert "concept" in fact_str, "Expected 'concept' to be in fact dict" + assert "period" in fact_str, "Expected 'period' to be in fact dict" + assert "entity" in fact_str, "Expected 'entity' to be in fact dict" + assert "unit" not in fact_str, "Expected 'unit' not to be in fact dict" # check if parsing a false fact as bool works fact = filing.get_facts_by_concept_name("dei:AmendmentFlag")[0] - assert ( - fact.get_value_as_str() == "false" - ), "Expected 'false' as fact value is 'false'" + assert str(fact) == "false", "Expected 'false' as fact value is 'false'" assert bool(fact) == False, "Expected False as fact value is 'false'" # check for an integer fact From 47168a236d13de8a0fed43e58d8a390725d0af1f Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Tue, 24 Mar 2026 13:31:45 +0100 Subject: [PATCH 4/5] Simply get_concept() on facts. --- README.md | 2 +- brel/brel_fact.py | 20 +++++++++---------- brel/brel_filing.py | 8 ++------ brel/networks/calculation_network.py | 5 ++--- tests/core_tests/test_fact.py | 2 +- tests/core_tests/test_filing.py | 4 ++-- .../hand_made_report/test_end_to_end_facts.py | 2 +- 7 files changed, 19 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c8f90be3..4a7cc26f 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ You could also use python's built-in `filter` function: ```python assets_facts = list( - filter(lambda fact: fact.get_concept().get_value() == assets_concept, all_facts) + filter(lambda fact: fact.get_concept() == assets_concept, all_facts) ) # or diff --git a/brel/brel_fact.py b/brel/brel_fact.py index 281ad9bc..a6ed8110 100644 --- a/brel/brel_fact.py +++ b/brel/brel_fact.py @@ -18,9 +18,9 @@ from typing import Any, List, Optional, cast from brel import Context +from brel.reportelements import Concept from brel.characteristics import ( Aspect, - ConceptCharacteristic, ICharacteristic, PeriodCharacteristic, UnitCharacteristic, @@ -128,11 +128,11 @@ def get_value(self) -> Any: """ :returns Any: The value of the fact. The type of the value depends on the type of the fact. """ - if self.get_concept().get_value().is_integer(): + if self.get_concept().is_integer(): return int(self) - elif self.get_concept().get_value().is_numeric(): + elif self.get_concept().is_numeric(): return float(self) - elif self.get_concept().get_value().is_boolean(): + elif self.get_concept().is_boolean(): return bool(self) else: return self.__value @@ -156,14 +156,14 @@ def __str__(self) -> str: return self.__value # 2nd class citizens - def get_concept(self) -> ConceptCharacteristic: + def get_concept(self) -> Concept: """ - :returns ConceptCharacteristic: The concept characteristic of the facts context. - Equivalent to calling `fact.get_context().get_concept()` + :returns Concept: The concept of the facts context. + Equivalent to calling `fact.get_context().get_concept().get_value()` """ - concept: ConceptCharacteristic = cast( - ConceptCharacteristic, - self.__context.get_characteristic(Aspect.CONCEPT), + concept: Concept = cast( + Concept, + self.__context.get_characteristic(Aspect.CONCEPT).get_value(), ) return concept diff --git a/brel/brel_filing.py b/brel/brel_filing.py index b48a5ce6..8cd5072a 100644 --- a/brel/brel_filing.py +++ b/brel/brel_filing.py @@ -348,7 +348,7 @@ def get_all_reported_concepts(self) -> List[Concept]: """ reported_concepts: list[Concept] = [] for fact in self.get_all_facts(): - concept = fact.get_concept().get_value() + concept = fact.get_concept() if concept not in reported_concepts: reported_concepts.append(concept) @@ -374,11 +374,7 @@ def get_facts_by_concept_name(self, concept_name: QName | str) -> List[Fact]: concept_name, Concept ) - return [ - fact - for fact in self.get_all_facts() - if fact.get_concept().get_value() == concept - ] + return [fact for fact in self.get_all_facts() if fact.get_concept() == concept] def get_facts_by_concept(self, concept: Concept) -> List[Fact]: """ diff --git a/brel/networks/calculation_network.py b/brel/networks/calculation_network.py index b3255638..4b378812 100644 --- a/brel/networks/calculation_network.py +++ b/brel/networks/calculation_network.py @@ -129,7 +129,7 @@ def is_subnetwork_aggregation_consistent( concept = node.get_concept() node_facts = list( filter( - lambda fact: fact.get_concept().get_value() == concept, + lambda fact: fact.get_concept() == concept, facts, ) ) @@ -153,8 +153,7 @@ def is_subnetwork_aggregation_consistent( if node_aspect == Aspect.CONCEPT: child_facts = list( filter( - lambda fact: fact.get_concept().get_value() - == child_concept, + lambda fact: fact.get_concept() == child_concept, child_facts, ) ) diff --git a/tests/core_tests/test_fact.py b/tests/core_tests/test_fact.py index ecca9648..43a05256 100644 --- a/tests/core_tests/test_fact.py +++ b/tests/core_tests/test_fact.py @@ -21,7 +21,7 @@ def test_qname_getters(): assert fact.get_id() == "f-2", "Expected fact id to be 'f-2'" # type: ignore context = fact.get_context() - assert context.get_id() == "c-1", "Expected context id to be 'c-1'" # type: ignore + assert context._get_id() == "c-1", "Expected context id to be 'c-1'" # type: ignore assert str(fact) == "true", "Expected 'true' as fact value is 'true'" assert bool(fact) == True, "Expected True as fact value is 'true'" diff --git a/tests/core_tests/test_filing.py b/tests/core_tests/test_filing.py index 4c3aafe9..e8c90092 100644 --- a/tests/core_tests/test_filing.py +++ b/tests/core_tests/test_filing.py @@ -101,14 +101,14 @@ def test_filing_getters(): facts = filing.get_facts_by_concept_name("ete:cash") assert_list_of_type(facts, Fact) assert all( - f.get_concept().get_value() == concept for f in facts + f.get_concept() == concept for f in facts ), "Expected all facts to have the cash concept" # check get_facts_by_concept(). all facts should have the cash concept facts = filing.get_facts_by_concept(concept) assert_list_of_type(facts, Fact) assert all( - f.get_concept().get_value() == concept for f in facts + f.get_concept() == concept for f in facts ), "Expected all facts to have the cash concept" # check if get_all_component_uris() contains the uris "http://foo/role/balance", "http://foo/role/hypercube" and "http://foo/role/bad-balance" diff --git a/tests/end_to_end_tests/hand_made_report/test_end_to_end_facts.py b/tests/end_to_end_tests/hand_made_report/test_end_to_end_facts.py index 3fe62350..70074f0e 100644 --- a/tests/end_to_end_tests/hand_made_report/test_end_to_end_facts.py +++ b/tests/end_to_end_tests/hand_made_report/test_end_to_end_facts.py @@ -20,7 +20,7 @@ def test_end_to_end_fact_f013(): fact_dimension = fact.get_characteristic(dimension_aspect) # check that the concept is ete:balance, the period is duration and the entity is 1234 - assert fact_concept.get_value() == filing.get_concept("ete:concept1") + assert fact_concept == filing.get_concept("ete:concept1") assert fact_period is not None assert fact_period.is_instant() is True assert fact_period.get_instant_period() == date(2024, 5, 3) From 22328db25295958117f6f0ed8fddfeb5d893603c Mon Sep 17 00:00:00 2001 From: Ghislain Fourny Date: Tue, 24 Mar 2026 13:39:05 +0100 Subject: [PATCH 5/5] Lint. --- brel/brel_fact.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/brel/brel_fact.py b/brel/brel_fact.py index a6ed8110..ac1d5ed2 100644 --- a/brel/brel_fact.py +++ b/brel/brel_fact.py @@ -22,6 +22,7 @@ from brel.characteristics import ( Aspect, ICharacteristic, + ConceptCharacteristic, PeriodCharacteristic, UnitCharacteristic, EntityCharacteristic, @@ -161,9 +162,12 @@ def get_concept(self) -> Concept: :returns Concept: The concept of the facts context. Equivalent to calling `fact.get_context().get_concept().get_value()` """ + concept_characteristic = cast( + ConceptCharacteristic, self.__context.get_characteristic(Aspect.CONCEPT) + ) concept: Concept = cast( Concept, - self.__context.get_characteristic(Aspect.CONCEPT).get_value(), + concept_characteristic.get_value(), ) return concept