diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py
index e2bea98d0f..c5e9443916 100644
--- a/src/ifctester/ifctester/facet.py
+++ b/src/ifctester/ifctester/facet.py
@@ -81,6 +81,7 @@ class Facet:
def __init__(self, *parameters):
self.status = None
+ self.passed_entities: set[ifcopenshell.entity_instance] = set()
self.failures: list[FacetFailure] = []
for i, name in enumerate(self.parameters):
setattr(self, name.replace("@", ""), parameters[i])
diff --git a/src/ifctester/ifctester/ids.py b/src/ifctester/ifctester/ids.py
index 5b4e0dc3cd..694af9d374 100644
--- a/src/ifctester/ifctester/ids.py
+++ b/src/ifctester/ifctester/ids.py
@@ -174,6 +174,7 @@ class Specification:
self.instructions = instructions
self.applicable_entities: list[ifcopenshell.entity_instance] = []
+ self.passed_entities: set[ifcopenshell.entity_instance] = set()
self.failed_entities: set[ifcopenshell.entity_instance] = set()
self.status = None
@@ -238,6 +239,7 @@ class Specification:
def reset_status(self):
self.applicable_entities.clear()
+ self.passed_entities: set[ifcopenshell.entity_instance] = set()
self.failed_entities: set[ifcopenshell.entity_instance] = set()
for facet in self.requirements:
facet.status = None
@@ -270,13 +272,19 @@ class Specification:
result = facet(element)
is_pass = bool(result)
if self.maxOccurs != 0: # This is a required or optional specification
- if not is_pass:
+ if is_pass:
+ self.passed_entities.add(element)
+ facet.passed_entities.add(element)
+ else:
self.failed_entities.add(element)
facet.failures.append(FacetFailure(element=element, reason=str(result)))
else: # This is a prohibited specification
if is_pass:
self.failed_entities.add(element)
facet.failures.append(FacetFailure(element=element, reason=str(result)))
+ else:
+ self.passed_entities.add(element)
+ facet.passed_entities.add(element)
self.status = True
for facet in self.requirements:
diff --git a/src/ifctester/ifctester/reporter.py b/src/ifctester/ifctester/reporter.py
index 66029d006b..db4b7b5e90 100644
--- a/src/ifctester/ifctester/reporter.py
+++ b/src/ifctester/ifctester/reporter.py
@@ -96,7 +96,8 @@ class ResultsRequirement(TypedDict):
value: str
description: str
status: bool
- failed_entities: list[ResultsFailedEntity]
+ passed_entities: list[ResultsEntity]
+ failed_entities: list[ResultsEntity]
total_applicable: int
total_pass: int
total_fail: int
@@ -104,8 +105,8 @@ class ResultsRequirement(TypedDict):
# use different syntax because of the "class" key
-ResultsFailedEntity = TypedDict(
- "ResultsFailedEntity",
+ResultsEntity = TypedDict(
+ "ResultsEntity",
{
"reason": str,
"element": str,
@@ -339,6 +340,7 @@ class Json(Reporter):
value=value,
description=requirement.to_string("requirement", specification, requirement),
status=requirement.status,
+ passed_entities=self.report_passed_entities(requirement),
failed_entities=self.report_failed_entities(requirement),
total_applicable=total_applicable,
total_pass=total_pass,
@@ -370,9 +372,27 @@ class Json(Reporter):
requirements=requirements,
)
- def report_failed_entities(self, requirement: Facet) -> list[ResultsFailedEntity]:
+ def report_passed_entities(self, requirement: Facet) -> list[ResultsEntity]:
return [
- ResultsFailedEntity(
+ ResultsEntity(
+ {
+ "element": e,
+ "element_type": ifcopenshell.util.element.get_type(e),
+ "class": e.is_a(),
+ "predefined_type": ifcopenshell.util.element.get_predefined_type(e),
+ "name": getattr(e, "Name", None),
+ "description": getattr(e, "Description", None),
+ "id": e.id(),
+ "global_id": getattr(e, "GlobalId", None),
+ "tag": getattr(e, "Tag", None),
+ }
+ )
+ for e in requirement.passed_entities
+ ]
+
+ def report_failed_entities(self, requirement: Facet) -> list[ResultsEntity]:
+ return [
+ ResultsEntity(
{
"reason": f["reason"],
"element": f["element"],
@@ -413,11 +433,17 @@ class Html(Json):
entity_limit = 100
for spec in self.results["specifications"]:
for requirement in spec["requirements"]:
- total = len(requirement["failed_entities"])
+ total_passed_entities = len(requirement["passed_entities"])
+ total_failed_entities = len(requirement["failed_entities"])
+ requirement["passed_entities"] = requirement["passed_entities"][0:entity_limit]
requirement["failed_entities"] = requirement["failed_entities"][0:entity_limit]
- requirement["has_omitted"] = total > entity_limit
- requirement["total_entities"] = total
- requirement["total_omitted"] = total - entity_limit
+ requirement["total_failed_entities"] = total_failed_entities
+ requirement["total_omitted_failures"] = total_failed_entities - entity_limit
+ requirement["has_omitted_failures"] = total_failed_entities > entity_limit
+ requirement["total_passed_entities"] = total_passed_entities
+ requirement["total_omitted_passes"] = total_passed_entities - entity_limit
+ requirement["has_omitted_passes"] = total_passed_entities > entity_limit
+
def to_string(self) -> str:
import pystache
diff --git a/src/ifctester/ifctester/templates/report.html b/src/ifctester/ifctester/templates/report.html
index 9cd9ce35b6..1ecaaec7dc 100644
--- a/src/ifctester/ifctester/templates/report.html
+++ b/src/ifctester/ifctester/templates/report.html
@@ -59,10 +59,13 @@
.percent { text-align: left; padding-top: 5px; padding-left: 5px; padding-bottom: 5px; color: white; border-radius: 5px; white-space: nowrap; }
.pass { background-color: var(--green); }
.fail { background-color: var(--red); }
- table { width: 100%; border-bottom: 2px solid var(--red); border-spacing: 0; border-radius: 5px; margin-top: 10px; }
- th, td { padding: 5px; }
- thead>tr { background-color: var(--red); font-weight: bold; color: #fff; }
- tbody tr { border-bottom: 1px solid var(--red); }
+ table { width: 100%; border-spacing: 0; border-radius: 5px; margin-top: 10px; }
+ th, td { padding: 5px; color: #000; }
+ table.pass { border-bottom: 2px solid var(--green); }
+ table.fail { border-bottom: 2px solid var(--red); }
+ table thead>tr { font-weight: bold; color: #000; }
+ table.pass tbody tr { border-bottom: 1px solid var(--green); }
+ table.fail tbody tr { border-bottom: 1px solid var(--red); }
tbody tr:nth-child(odd) { background-color: rgba(1, 1, 1, 0.05); }
tbody tr:nth-child(even) { background-color: rgba(1, 1, 1, 0.1); }
tbody tr:hover { background-color: rgba(0, 0, 0, 0); }
@@ -130,8 +133,39 @@
{{description}}
+ {{#total_pass}}
+
+
+
+ | Class |
+ PredefinedType |
+ Name |
+ Description |
+ GlobalId |
+ Tag |
+
+
+
+ {{#passed_entities}}
+
+ | {{class}} |
+ {{predefined_type}} |
+ {{name}} |
+ {{description}} |
+ {{global_id}} |
+ {{tag}} |
+
+ {{/passed_entities}}
+ {{#has_omitted_passes}}
+
+ | ... {{total_omitted_passes}} more passing elements not shown out of {{total_passed_entities}} total ... |
+
+ {{/has_omitted_passes}}
+
+
+ {{/total_pass}}
{{#total_fail}}
-
+
| Class |
@@ -155,11 +189,11 @@
{{tag}} |
{{/failed_entities}}
- {{#has_omitted}}
+ {{#has_omitted_failures}}
- | ... {{total_omitted}} more elements not shown out of {{total_entities}} total ... |
+ ... {{total_omitted_failures}} more failing elements not shown out of {{total_failed_entities}} total ... |
- {{/has_omitted}}
+ {{/has_omitted_failures}}
{{/total_fail}}