IfcTester now supports much more granular statistic reports and more detailed / beautiful HTML reports.

This commit is contained in:
Dion Moult
2023-09-01 16:46:49 +10:00
parent 68025d8204
commit 283961fc72
3 changed files with 201 additions and 156 deletions
+2
View File
@@ -168,6 +168,8 @@ class Specification:
def parse(self, ids_dict):
self.name = ids_dict.get("@name", "")
self.description = ids_dict.get("@description", "")
self.instructions = ids_dict.get("@instructions", "")
self.minOccurs = ids_dict["@minOccurs"]
self.maxOccurs = ids_dict["@maxOccurs"]
self.ifcVersion = ids_dict["@ifcVersion"]
+81 -18
View File
@@ -159,31 +159,85 @@ class Json(Reporter):
def report(self):
self.results["title"] = self.ids.info.get("title", "Untitled IDS")
self.results["date"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
total_specifications = 0
total_specifications_pass = 0
total_requirements = 0
total_requirements_pass = 0
total_checks = 0
total_checks_pass = 0
status = True
self.results["specifications"] = []
for specification in self.ids.specifications:
self.results["specifications"].append(self.report_specification(specification))
specification_report = self.report_specification(specification)
self.results["specifications"].append(specification_report)
total_specifications += 1
total_specifications_pass += 1 if specification_report["status"] else 0
total_requirements += len(specification_report["requirements"])
total_requirements_pass += len([r for r in specification_report["requirements"] if r["status"]])
total_checks += specification_report["total_checks"]
total_checks_pass += specification_report["total_checks_pass"]
if not specification_report["status"]:
status = False
self.results["status"] = status
self.results["total_specifications"] = total_specifications
self.results["total_specifications_pass"] = total_specifications_pass
self.results["total_specifications_fail"] = total_specifications - total_specifications_pass
self.results["percent_specifications_pass"] = (
math.floor((total_specifications_pass / total_specifications) * 100) if total_specifications else "N/A"
)
self.results["total_requirements"] = total_requirements
self.results["total_requirements_pass"] = total_requirements_pass
self.results["total_requirements_fail"] = total_requirements - total_requirements_pass
self.results["percent_requirements_pass"] = (
math.floor((total_requirements_pass / total_requirements) * 100) if total_requirements else "N/A"
)
self.results["total_checks"] = total_checks
self.results["total_checks_pass"] = total_checks_pass
self.results["total_checks_fail"] = total_checks - total_checks_pass
self.results["percent_checks_pass"] = (
math.floor((total_checks_pass / total_checks) * 100) if total_checks else "N/A"
)
return self.results
def report_specification(self, specification):
applicability = [a.to_string("applicability") for a in specification.applicability]
total_applicable = len(specification.applicable_entities)
total_checks = 0
total_checks_pass = 0
requirements = []
for requirement in specification.requirements:
total_fail = len(requirement.failed_entities)
total_checks += total_applicable
total_checks_pass += total_applicable - total_fail
requirements.append(
{
"description": requirement.to_string("requirement"),
"status": requirement.status,
"failed_entities": self.report_failed_entities(requirement),
"total_applicable": total_applicable,
"total_pass": total_applicable - total_fail,
"total_fail": total_fail,
}
)
total = len(specification.applicable_entities)
total_successes = total - len(specification.failed_entities)
percentage = math.floor((total_successes / total) * 100) if total else "N/A"
total_applicable_pass = total_applicable - len(specification.failed_entities)
percent_applicable_pass = (
math.floor((total_applicable_pass / total_applicable) * 100) if total_applicable else "N/A"
)
percent_checks_pass = math.floor((total_checks_pass / total_checks) * 100) if total_checks else "N/A"
return {
"name": specification.name,
"description": specification.description,
"instructions": specification.instructions,
"status": specification.status,
"total_successes": total_successes,
"total": total,
"percentage": percentage,
"total_applicable": total_applicable,
"total_applicable_pass": total_applicable_pass,
"total_applicable_fail": total_applicable - total_applicable_pass,
"percent_applicable_pass": percent_applicable_pass,
"total_checks": total_checks,
"total_checks_pass": total_checks_pass,
"total_checks_fail": total_checks - total_checks_pass,
"percent_checks_pass": percent_checks_pass,
"required": specification.minOccurs != 0,
"applicability": applicability,
"requirements": requirements,
@@ -223,12 +277,15 @@ class Html(Json):
self.results = {}
def report(self):
self.results["title"] = self.ids.info.get("title", "Untitled IDS")
self.results["time"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.results["specifications"] = []
for specification in self.ids.specifications:
self.results["specifications"].append(self.report_specification(specification))
return self.results
super().report()
entity_limit = 100
for spec in self.results["specifications"]:
for requirement in spec["requirements"]:
total = len(requirement["failed_entities"])
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
def to_string(self):
import pystache
@@ -272,7 +329,7 @@ class Ods(Json):
table = Table(name=self.results["title"])
tr = TableRow()
for header in ["Specification", "Status", "Total Compliant", "Total Applicable", "Percentage Compliant"]:
for header in ["Specification", "Status", "Total Pass", "Total Checks", "Percentage Pass"]:
tc = TableCell(valuetype="string", stylename="h")
tc.addElement(P(text=header))
tr.addElement(tc)
@@ -284,9 +341,9 @@ class Ods(Json):
[
specification["name"],
"Pass" if specification["status"] else "Fail",
str(specification["total_successes"]),
str(specification["total"]),
str(specification["percentage"]),
str(specification["total_checks_pass"]),
str(specification["total_checks"]),
str(specification["percent_checks_pass"]),
]
)
@@ -309,7 +366,7 @@ class Ods(Json):
continue
table = Table(name=specification["name"])
tr = TableRow()
for header in ["Requirement", "Problem", "Element"]:
for header in ["Requirement", "Problem", "Class", "PredefinedType", "Name", "Description", "GlobalId", "Tag", "Element"]:
tc = TableCell(valuetype="string", stylename="h")
tc.addElement(P(text=header))
tr.addElement(tc)
@@ -321,6 +378,12 @@ class Ods(Json):
row = [
requirement["description"],
failure.get("reason", "No reason provided"),
failure["class"],
failure["predefined_type"],
failure["name"],
failure["description"],
failure["global_id"],
failure["tag"],
str(failure.get("element", "No element found")),
]
tr = TableRow()
+118 -138
View File
@@ -26,173 +26,153 @@
<title>{{name}}</title>
<link href="https://fonts.googleapis.com/css?family=Comfortaa|Inconsolata|Open+Sans&display=swap" rel="stylesheet">
<style>
:root {
--green: #97cc64;
--light-green: #b6cca1;
--red: #fb5a3e;
--light-red: #fbb4a8;
}
body { font-family: 'Arial', sans-serif; padding: 10px 40px; }
section { padding: 15px; border-radius: 5px; border: 1px solid #eee; margin-bottom: 15px; }
section>h2 { margin-top: 0px; }
span.time { color: #999; font-style: italic; float: right; }
span.step-time { float: right; color: #555; font-size: 0.8em; font-style: italic; }
span.success { background-color: #97cc64; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
span.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #FFF; font-weight: bold; }
p.failure { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #fff; }
span.item { padding: 5px; border-radius: 5px; margin-right: 5px; border: 1px solid #eee; }
span.item.pass, span.item.fail { color: #FFF; font-weight: bold; border: 0px; }
p.fail { background-color: #fb5a3e; padding: 5px; border-radius: 5px; color: #fff; }
p.unspecified { background-color: #994f00; padding: 5px; border-radius: 5px; color: #fff; }
p.skipped { background-color: #8b8d8f; padding: 5px; border-radius: 5px; color: #fff; }
p.description { background-color: #eee; border-radius: 5px; padding: 20px; margin-left: auto; margin-right: auto; display: inline-block; font-weight: bold;}
li { padding: 10px; font-family: monospace; }
li.success { background-color: #b6cca1; color: #333; }
li.failure { background-color: #fbb4a8; color: #900; }
li.pass { background-color: var(--light-green); color: #333; }
li.fail { background-color: var(--light-red); color: #900; }
li.unspecified { background-color: #ffd37f; color: #a30; }
li.skipped { background-color: #f5f5f5; color: #333; }
li p { margin-bottom: 0px; }
footer { color: #999; font-size: 0.8em; }
header { text-align: center; }
hr { margin: 20px; margin-left: 0px; margin-right: 0px; border: none; border-top: 1px solid #ccc; }
details { user-select: none; }
details>summary span.icon { width: 24px; height: 24px; transition: all 0.3s; margin-left: auto; }
summary { display: flex; cursor: pointer; }
summary::-webkit-details-marker { display: none; }
* {box-sizing:border-box}
.container { width: 100%; background-color: #ddd; border-radius: 5px}
.completion { text-align: left; padding-top: 5px; padding-left: 5px; padding-bottom: 5px; color: white; }
.percent0 {width: 0%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent1 {width: 1%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent2 {width: 2%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent3 {width: 3%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent4 {width: 4%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent5 {width: 5%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent6 {width: 6%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent7 {width: 7%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent8 {width: 8%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent9 {width: 9%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent10 {width: 10%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent11 {width: 11%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent12 {width: 12%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent13 {width: 13%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent14 {width: 14%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent15 {width: 15%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent16 {width: 16%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent17 {width: 17%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent18 {width: 18%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent19 {width: 19%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent20 {width: 20%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent21 {width: 21%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent22 {width: 22%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent23 {width: 23%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent24 {width: 24%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent25 {width: 25%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent26 {width: 26%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent27 {width: 27%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent28 {width: 28%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent29 {width: 29%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent30 {width: 30%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent31 {width: 31%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent32 {width: 32%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent33 {width: 33%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent34 {width: 34%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent35 {width: 35%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent36 {width: 36%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent37 {width: 37%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent38 {width: 38%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent39 {width: 39%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent40 {width: 40%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent41 {width: 41%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent42 {width: 42%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent43 {width: 43%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent44 {width: 44%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent45 {width: 45%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent46 {width: 46%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent47 {width: 47%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent48 {width: 48%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent49 {width: 49%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent50 {width: 50%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent51 {width: 51%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent52 {width: 52%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent53 {width: 53%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent54 {width: 54%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent55 {width: 55%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent56 {width: 56%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent57 {width: 57%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent58 {width: 58%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent59 {width: 59%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent60 {width: 60%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent61 {width: 61%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent62 {width: 62%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent63 {width: 63%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent64 {width: 64%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent65 {width: 65%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent66 {width: 66%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent67 {width: 67%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent68 {width: 68%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent69 {width: 69%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent70 {width: 70%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent71 {width: 71%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent72 {width: 72%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent73 {width: 73%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent74 {width: 74%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent75 {width: 75%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent76 {width: 76%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent77 {width: 77%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent78 {width: 78%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent79 {width: 79%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent80 {width: 80%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent81 {width: 81%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent82 {width: 82%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent83 {width: 83%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent84 {width: 84%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent85 {width: 85%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent86 {width: 86%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent87 {width: 87%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent88 {width: 88%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent89 {width: 89%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent90 {width: 90%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent91 {width: 91%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent92 {width: 92%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent93 {width: 93%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent94 {width: 94%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent95 {width: 95%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent96 {width: 96%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent97 {width: 97%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent98 {width: 98%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent99 {width: 99%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
.percent100 {width: 100%; background-color: #97cc64; border-radius: 5px; font-weight: bold}
summary { display: flex; cursor: pointer; }
summary::-webkit-details-marker { display: none; }
* {box-sizing:border-box}
.container { width: 100%; background-color: #ddd; border-radius: 5px}
.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); }
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); }
</style>
</head>
<body>
<header>
<h1>{{title}}</h1>
<p><strong>{{time}}</strong></p>
<p><strong>{{date}}</strong></p>
</header>
<h2>Summary</h2>
<div class="container">
<div class="{{#status}}pass{{/status}}{{^status}}fail{{/status}} percent" style="width: {{percent_checks_pass}}%;">{{percent_checks_pass}}%</div>
</div>
<p>
<span class="item {{#status}}pass{{/status}}{{^status}}fail{{/status}}">{{#status}}Pass{{/status}}{{^status}}Fail{{/status}}</span>
<span class="item">
Specifications passed: <strong>{{total_specifications_pass}}</strong> / <strong>{{total_specifications}}</strong>
</span>
<span class="item">
Requirements passed: <strong>{{total_requirements_pass}}</strong> / <strong>{{total_requirements}}</strong>
</span>
<span class="item">
Checks passed: <strong>{{total_checks_pass}}</strong> / <strong>{{total_checks}}</strong>
</span>
</p>
<hr>
{{#specifications}}
<section>
<h2>{{name}}</h2>
{{#description}}
<p>{{description}}</p>
{{/description}}
{{#instructions}}
<p><em>{{instructions}}</em></p>
{{/instructions}}
<div class="container">
<div class="completion percent{{percentage}}">{{percentage}}%</div>
<div class="{{#status}}pass{{/status}}{{^status}}fail{{/status}} percent" style="width: {{percent_checks_pass}}%;">{{percent_checks_pass}}%</div>
</div>
<details>
<summary>
<p>
<span class="{{#status}}success{{/status}}{{^status}}failure{{/status}}">{{#status}}Pass{{/status}}{{^status}}Fail{{/status}}</span>
Passed: <strong>{{total_successes}} / {{total}}</strong> ({{percentage}}%) - click here to see details
</p>
</summary>
<ol>
{{#requirements}}
<li class="{{#status}}success{{/status}}{{^status}}failure{{/status}}">
{{description}}
{{^status}}{{#total}}
<p class="failure{{#is_unspecified}} unspecified{{/is_unspecified}}{{#is_skipped}} skipped{{/is_skipped}}">
{{#failed_entities}}
{{reason}} <em style="color: #fbb4a8;">{{element}}</em><br />
{{/failed_entities}}
</p>
{{/total}}{{/status}}
</li>
{{/requirements}}
</ol>
</details>
<p>
<span class="item {{#status}}pass{{/status}}{{^status}}fail{{/status}}">{{#status}}Pass{{/status}}{{^status}}Fail{{/status}}</span>
<span class="item">
Checks passed: <strong>{{total_checks_pass}}</strong> / <strong>{{total_checks}}</strong>
</span>
<span class="item">
Elements passed: <strong>{{total_applicable_pass}}</strong> / <strong>{{total_applicable}}</strong>
</span>
</p>
<p>
<strong>Applicability</strong>
</p>
<ul>
{{#applicability}}
<li>{{.}}</li>
{{/applicability}}
</ul>
<p>
<strong>Requirements</strong>
</p>
<ol>
{{#requirements}}
<li class="{{#status}}pass{{/status}}{{^status}}fail{{/status}}">
<details>
<summary>
{{description}}
</summary>
{{#total_fail}}
<table>
<thead>
<tr>
<th>Class</th>
<th>PredefinedType</th>
<th>Name</th>
<th>Description</th>
<th>Warning</th>
<th>GlobalId</th>
<th>Tag</th>
</tr>
</thead>
<tbody>
{{#failed_entities}}
<tr>
<td>{{class}}</td>
<td>{{predefined_type}}</td>
<td>{{name}}</td>
<td>{{description}}</td>
<td>{{reason}}</td>
<td>{{global_id}}</td>
<td>{{tag}}</td>
</tr>
{{/failed_entities}}
{{#has_omitted}}
<tr>
<td colspan="7"> ... {{total_omitted}} more elements not shown out of {{total_entities}} total ...</td>
</tr>
{{/has_omitted}}
</tbody>
</table>
{{/total_fail}}
</details>
</li>
{{/requirements}}
</ol>
</section>
{{/specifications}}
<hr>
<footer>
<p>
Report by <a href="https://blenderbim.org/">BlenderBIM</a> and <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
Report by the <a href="https://blenderbim.org/">BlenderBIM Add-on</a> and <a href="http://ifcopenshell.org/">IfcOpenShell</a>.
</p>
</footer>
</body>