diff --git a/src/templates/result.html b/src/templates/result.html index 610d712..82836e4 100644 --- a/src/templates/result.html +++ b/src/templates/result.html @@ -47,8 +47,9 @@

📋  AIBOM Summary

- {% set score_percent = (completeness_score.total_score if completeness_score.total_score != 'Undefined' else 0) - | float %} + {% set total_max_score = completeness_score.max_scores.values() | sum if completeness_score.max_scores else 0 %} + {% set total_points = completeness_score.total_score if completeness_score.total_score != 'Undefined' else 0 %} + {% set score_percent = ((total_points / total_max_score * 100) if total_max_score else 0) | float %} {% if score_percent >= 90 %} {% set score_class = 'progress-excellent' %} {% set score_label = 'Excellent' %} @@ -329,8 +330,11 @@

Required Fields Category

- {% set required_fields = ['bomFormat', 'specVersion', 'serialNumber', 'version'] %} - {% for field in required_fields %} + {% set required_fields = completeness_score.category_fields_list.required_fields + if completeness_score and completeness_score.category_fields_list else [] %} + {% for field_item in required_fields %} + {% set field = field_item.name %} + {% set tier = field_item.tier %} {% if completeness_score.field_checklist.get(field, '').startswith('✔') %} @@ -342,12 +346,12 @@

Required Fields Category

{{ field }} {% if completeness_score.field_checklist.get(field, '').startswith('✔') %} - $.{{ field }} + {{ field_item.path }} {% else %} Not found {% endif %} - Critical + {{ tier }} {% set f_type = completeness_score.field_types.get(field, 'Unknown') %} {% set f_url = completeness_score.reference_urls.get(field, '') if @@ -370,7 +374,8 @@

Required Fields Category

({{ completeness_score.category_details.required_fields.percentage if completeness_score.category_details else 'N/A' }}%) = {{ completeness_score.section_scores.required_fields if completeness_score.section_scores - else 'N/A' }}/20 points + else 'N/A' }}/{{ completeness_score.category_details.required_fields.max_points if + completeness_score.category_details else 'N/A' }} points @@ -388,14 +393,11 @@

Metadata Category

- {% set metadata_fields = [ - ('primaryPurpose', 'Critical'), - ('suppliedBy', 'Critical'), - ('standardCompliance', 'Supplementary'), - ('domain', 'Supplementary'), - ('autonomyType', 'Supplementary') - ] %} - {% for field, tier in metadata_fields %} + {% set metadata_fields = completeness_score.category_fields_list.metadata + if completeness_score and completeness_score.category_fields_list else [] %} + {% for field_item in metadata_fields %} + {% set field = field_item.name %} + {% set tier = field_item.tier %} {% if completeness_score.field_checklist.get(field, '').startswith('✔') %} @@ -407,13 +409,7 @@

Metadata Category

{{ field }} {% if completeness_score.field_checklist.get(field, '').startswith('✔') %} - {% if field == 'primaryPurpose' %} - $.components[0].modelCard.modelParameters.task - {% elif field == 'suppliedBy' %} - $.components[0].supplier.name - {% else %} - $.components[0].modelCard.properties[name="{{ field }}"] - {% endif %} + {{ field_item.path }} {% else %} Not found {% endif %} @@ -441,7 +437,8 @@

Metadata Category

({{ completeness_score.category_details.metadata.percentage if completeness_score.category_details else 'N/A' }}%) = {{ completeness_score.section_scores.metadata if completeness_score.section_scores else - 'N/A' }}/20 points + 'N/A' }}/{{ completeness_score.category_details.metadata.max_points if + completeness_score.category_details else 'N/A' }} points @@ -459,15 +456,11 @@

Component Basic Category

- {% set component_basic_fields = [ - ('name', 'Critical'), - ('type', 'Critical'), - ('component_version', 'Critical'), - ('purl', 'Important'), - ('description', 'Important'), - ('licenses', 'Important') - ] %} - {% for field, tier in component_basic_fields %} + {% set component_basic_fields = completeness_score.category_fields_list.component_basic + if completeness_score and completeness_score.category_fields_list else [] %} + {% for field_item in component_basic_fields %} + {% set field = field_item.name %} + {% set tier = field_item.tier %} {% if completeness_score.field_checklist.get(field, '').startswith('✔') %} @@ -476,22 +469,13 @@

Component Basic Category

✘ {% endif %} - {% if field == 'component_version' %}version{% else %}{{ field }}{% endif %} - + {{ field }} {% if completeness_score.field_checklist.get(field, '').startswith('✔') %} - {% if field == 'component_version' %} - $.components[0].version - {% else %} - $.components[0].{{ field }} - {% endif %} - {% else %} - {% if field == 'description' %} - Not found in component level + {{ field_item.path }} {% else %} Not found {% endif %} - {% endif %} {{ tier }} @@ -516,7 +500,8 @@

Component Basic Category

({{ completeness_score.category_details.component_basic.percentage if completeness_score.category_details else 'N/A' }}%) = {{ completeness_score.section_scores.component_basic if completeness_score.section_scores - else 'N/A' }}/20 points + else 'N/A' }}/{{ completeness_score.category_details.component_basic.max_points if + completeness_score.category_details else 'N/A' }} points @@ -578,7 +563,9 @@

Component Model Card Category

({{ completeness_score.category_details.component_model_card.percentage if completeness_score.category_details else 'N/A' }}%) = {{ completeness_score.section_scores.component_model_card if - completeness_score.section_scores else 'N/A' }}/30 points + completeness_score.section_scores else 'N/A' }}/{{ + completeness_score.category_details.component_model_card.max_points if + completeness_score.category_details else 'N/A' }} points @@ -640,7 +627,9 @@

External References Category

({{ completeness_score.category_details.external_references.percentage if completeness_score.category_details else 'N/A' }}%) = {{ completeness_score.section_scores.external_references if - completeness_score.section_scores else 'N/A' }}/10 points + completeness_score.section_scores else 'N/A' }}/{{ + completeness_score.category_details.external_references.max_points if + completeness_score.category_details else 'N/A' }} points @@ -657,8 +646,8 @@

📊  Completeness Score Report

-
{{ (completeness_score.total_score if completeness_score.total_score != - "Undefined" else 0)|round(1) }}/100
+
{{ total_points|round(1) }}/{{ total_max_score if total_max_score else + 'N/A' }}
Your AIBOM Breakdown {% if completeness_score.category_details and completeness_score.section_scores %} {% set categories = [ - ('Required Fields', 'required_fields', 20), - ('Metadata', 'metadata', 20), - ('Component Basic', 'component_basic', 20), - ('Model Card', 'component_model_card', 30), - ('External References', 'external_references', 10) + ('Required Fields', 'required_fields'), + ('Metadata', 'metadata'), + ('Component Basic', 'component_basic'), + ('Model Card', 'component_model_card'), + ('External References', 'external_references') ] %} - {% for display_name, key, max_score in categories %} + {% for display_name, key in categories %} {{ display_name }} {{ completeness_score.category_details[key].present_fields }}/{{ completeness_score.category_details[key].total_fields }} - {{ completeness_score.section_scores[key]|round(1) }}/{{ max_score }} + {{ completeness_score.section_scores[key]|round(1) }}/{{ + completeness_score.category_details[key].max_points }}
{% set percentage = completeness_score.category_details[key].percentage %} @@ -735,9 +725,11 @@

Your AIBOM Breakdown

{% for category, score in completeness_score.section_scores.items() %} {{ score|round(1) }}{% if not loop.last %} + {% endif %} {% endfor %} - = {{ completeness_score.subtotal_score|round(1) }}/100 + = {{ completeness_score.subtotal_score|round(1) }}/{{ total_max_score if total_max_score + else 'N/A' }} {% else %} - {{ completeness_score.subtotal_score|round(1) }}/100 + {{ completeness_score.subtotal_score|round(1) }}/{{ total_max_score if total_max_score + else 'N/A' }} {% endif %}

@@ -745,11 +737,12 @@

Your AIBOM Breakdown

Penalty Applied: -{{ completeness_score.penalty_percentage }}% ({{ completeness_score.penalty_reason }})

Final Score: {{ completeness_score.subtotal_score|round(1) }} × {{ - completeness_score.penalty_factor }} = {{ completeness_score.total_score|round(1) - }}/100

+ completeness_score.penalty_factor }} = {{ completeness_score.total_score|round(1) }}/{{ + total_max_score if total_max_score else 'N/A' }}

{% else %}

No penalties applied

-

Final Score: {{ completeness_score.total_score|round(1) }}/100

+

Final Score: {{ completeness_score.total_score|round(1) }}/{{ total_max_score if + total_max_score else 'N/A' }}

{% endif %}
diff --git a/tests/test_result_template.py b/tests/test_result_template.py new file mode 100644 index 0000000..dc7438d --- /dev/null +++ b/tests/test_result_template.py @@ -0,0 +1,131 @@ +import re +import unittest +from pathlib import Path + +from jinja2 import Environment, FileSystemLoader + + +class ResultTemplateScoreTests(unittest.TestCase): + def test_category_point_totals_come_from_scoring_payload(self): + template = ( + Path(__file__).resolve().parents[1] / "src" / "templates" / "result.html" + ).read_text(encoding="utf-8") + + self.assertIsNone( + re.search(r"/\d+ points", template), + "category summaries must not contain hard-coded point totals", + ) + self.assertIsNone( + re.search(r"\bmax_score\b", template), + "score report must not keep a separate hard-coded maximum", + ) + self.assertRegex( + template, + re.compile( + r"section_scores\[key\]\|round\(1\)\s*}}/{{\s*" + r"completeness_score\.category_details\[key\]\.max_points\s*}}", + re.DOTALL, + ), + "score report denominator must use the current category payload", + ) + categories = ( + "required_fields", + "metadata", + "component_basic", + "component_model_card", + "external_references", + ) + for category in categories: + self.assertIn( + f"category_details.{category}.max_points", + template, + f"{category} summary must use scoring payload max_points", + ) + self.assertIn( + f"('{self._display_name(category)}', '{category}')", + template, + f"{category} score-report tuple must not contain a numeric maximum", + ) + + def test_rendered_fields_and_totals_come_from_scoring_payload(self): + template_root = Path(__file__).resolve().parents[1] / "src" / "templates" + template = Environment( + loader=FileSystemLoader(template_root), + autoescape=True, + ).get_template("result.html") + categories = ( + "required_fields", + "metadata", + "component_basic", + "component_model_card", + "external_references", + ) + max_scores = {category: 41 + index for index, category in enumerate(categories)} + probes = {f"probe_{category}": "✘ missing" for category in categories} + score = { + "total_score": 50, + "subtotal_score": 50, + "completeness_profile": {"name": "Test", "description": "Test"}, + "field_checklist": probes, + "field_types": {}, + "reference_urls": {}, + "missing_fields": {}, + "missing_counts": {}, + "max_scores": max_scores, + "category_details": { + category: { + "present_fields": 0, + "total_fields": 1, + "max_points": max_scores[category], + "percentage": 0, + } + for category in categories + }, + "section_scores": dict.fromkeys(categories, 0), + "category_fields_list": { + category: [ + { + "name": f"probe_{category}", + "tier": "Critical", + "path": f"probe.{category}", + } + ] + for category in categories + }, + "penalty_applied": False, + "penalty_reason": "", + "recommendations": [], + } + aibom = { + "metadata": {"timestamp": "2026-08-06T00:00:00Z"}, + "bomFormat": "CycloneDX", + "serialNumber": "urn:uuid:test", + "components": [], + } + + rendered = template.render( + model_id="test/model", + completeness_score=score, + aibom=aibom, + result={}, + metadata={}, + ) + + for category, max_points in max_scores.items(): + self.assertIn(f"probe_{category}", rendered) + self.assertIn(f"/{max_points}", rendered) + self.assertIn(f"/{sum(max_scores.values())}", rendered) + + @staticmethod + def _display_name(category): + return { + "required_fields": "Required Fields", + "metadata": "Metadata", + "component_basic": "Component Basic", + "component_model_card": "Model Card", + "external_references": "External References", + }[category] + + +if __name__ == "__main__": + unittest.main()