Spaces:
Running
Running
Update src/aibom-generator/utils.py
Browse files- src/aibom-generator/utils.py +277 -56
src/aibom-generator/utils.py
CHANGED
@@ -304,19 +304,19 @@ def determine_completeness_profile(aibom: Dict[str, Any], score: float) -> Dict[
|
|
304 |
# Return the highest satisfied profile
|
305 |
if "advanced" in satisfied_profiles:
|
306 |
return {
|
307 |
-
"name": "
|
308 |
"description": COMPLETENESS_PROFILES["advanced"]["description"],
|
309 |
"satisfied": True
|
310 |
}
|
311 |
elif "standard" in satisfied_profiles:
|
312 |
return {
|
313 |
-
"name": "
|
314 |
"description": COMPLETENESS_PROFILES["standard"]["description"],
|
315 |
"satisfied": True
|
316 |
}
|
317 |
elif "basic" in satisfied_profiles:
|
318 |
return {
|
319 |
-
"name": "
|
320 |
"description": COMPLETENESS_PROFILES["basic"]["description"],
|
321 |
"satisfied": True
|
322 |
}
|
@@ -329,6 +329,7 @@ def determine_completeness_profile(aibom: Dict[str, Any], score: float) -> Dict[
|
|
329 |
|
330 |
|
331 |
def apply_completeness_penalties(original_score: float, missing_fields: Dict[str, List[str]]) -> Dict[str, Any]:
|
|
|
332 |
"""
|
333 |
Apply penalties based on missing critical fields.
|
334 |
|
@@ -339,24 +340,26 @@ def apply_completeness_penalties(original_score: float, missing_fields: Dict[str
|
|
339 |
Returns:
|
340 |
Dictionary with penalty information
|
341 |
"""
|
|
|
|
|
342 |
# Count missing fields by tier
|
343 |
missing_critical_count = len(missing_fields["critical"])
|
344 |
missing_important_count = len(missing_fields["important"])
|
345 |
|
|
|
|
|
|
|
346 |
# Calculate penalty based on missing critical fields
|
347 |
if missing_critical_count > 3:
|
348 |
-
penalty_factor
|
349 |
penalty_reason = "Multiple critical fields missing"
|
350 |
-
elif missing_critical_count
|
351 |
-
penalty_factor
|
352 |
penalty_reason = "Some critical fields missing"
|
353 |
-
|
354 |
-
|
|
|
355 |
penalty_reason = "Several important fields missing"
|
356 |
-
else:
|
357 |
-
# No penalty
|
358 |
-
penalty_factor = 1.0
|
359 |
-
penalty_reason = None
|
360 |
|
361 |
adjusted_score = original_score * penalty_factor
|
362 |
|
@@ -735,7 +738,7 @@ def _generate_validation_recommendations(issues: List[Dict[str, Any]]) -> List[s
|
|
735 |
recommendations.append("Add ethical considerations, limitations, and risks to the model card")
|
736 |
|
737 |
if "MISSING_METADATA" in issue_codes:
|
738 |
-
recommendations.append("Add metadata section to the
|
739 |
|
740 |
if "MISSING_TOOLS" in issue_codes:
|
741 |
recommendations.append("Include tools information in the metadata section")
|
@@ -835,7 +838,7 @@ def get_validation_summary(report: Dict[str, Any]) -> str:
|
|
835 |
|
836 |
def calculate_industry_neutral_score(aibom: Dict[str, Any]) -> Dict[str, Any]:
|
837 |
"""
|
838 |
-
Calculate completeness score using industry best practices
|
839 |
|
840 |
Args:
|
841 |
aibom: The AIBOM to score
|
@@ -844,6 +847,8 @@ def calculate_industry_neutral_score(aibom: Dict[str, Any]) -> Dict[str, Any]:
|
|
844 |
Dictionary containing score and recommendations
|
845 |
"""
|
846 |
field_checklist = {}
|
|
|
|
|
847 |
max_scores = {
|
848 |
"required_fields": 20,
|
849 |
"metadata": 20,
|
@@ -852,30 +857,29 @@ def calculate_industry_neutral_score(aibom: Dict[str, Any]) -> Dict[str, Any]:
|
|
852 |
"external_references": 10
|
853 |
}
|
854 |
|
855 |
-
# Track missing fields by tier
|
856 |
missing_fields = {
|
857 |
"critical": [],
|
858 |
"important": [],
|
859 |
"supplementary": []
|
860 |
}
|
861 |
|
862 |
-
#
|
863 |
-
|
864 |
-
max_possible_by_category = {category: 0 for category in max_scores.keys()}
|
865 |
|
|
|
866 |
for field, classification in FIELD_CLASSIFICATION.items():
|
867 |
tier = classification["tier"]
|
868 |
-
weight = classification["weight"]
|
869 |
category = classification["category"]
|
870 |
|
871 |
-
#
|
872 |
-
|
873 |
|
874 |
-
# Check if field is present
|
875 |
-
is_present = check_field_in_aibom(aibom, field)
|
876 |
|
877 |
if is_present:
|
878 |
-
|
879 |
else:
|
880 |
missing_fields[tier].append(field)
|
881 |
|
@@ -883,51 +887,150 @@ def calculate_industry_neutral_score(aibom: Dict[str, Any]) -> Dict[str, Any]:
|
|
883 |
importance_indicator = "★★★" if tier == "critical" else "★★" if tier == "important" else "★"
|
884 |
field_checklist[field] = f"{'✔' if is_present else '✘'} {importance_indicator}"
|
885 |
|
886 |
-
#
|
887 |
-
|
888 |
-
for category in
|
889 |
-
if
|
890 |
-
#
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
|
|
|
|
895 |
|
896 |
-
# Calculate
|
897 |
-
|
898 |
-
for category, score in normalized_scores.items():
|
899 |
-
# Each category contributes its percentage to the total
|
900 |
-
category_weight = max_scores[category] / sum(max_scores.values())
|
901 |
-
total_score += score * category_weight
|
902 |
|
903 |
-
#
|
904 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
905 |
|
906 |
# Ensure score is between 0 and 100
|
907 |
-
|
908 |
|
909 |
# Determine completeness profile
|
910 |
-
profile = determine_completeness_profile(aibom,
|
911 |
-
|
912 |
-
# Apply penalties for missing critical fields
|
913 |
-
penalty_result = apply_completeness_penalties(total_score, missing_fields)
|
914 |
|
915 |
# Generate recommendations
|
916 |
recommendations = generate_field_recommendations(missing_fields)
|
917 |
|
918 |
-
|
919 |
-
|
920 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
921 |
"max_scores": max_scores,
|
922 |
"field_checklist": field_checklist,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
923 |
"field_categorization": get_field_categorization_for_display(aibom),
|
924 |
"field_tiers": {field: info["tier"] for field, info in FIELD_CLASSIFICATION.items()},
|
925 |
"missing_fields": missing_fields,
|
|
|
|
|
|
|
|
|
|
|
926 |
"completeness_profile": profile,
|
927 |
-
"penalty_applied":
|
928 |
-
"penalty_reason":
|
929 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
930 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
931 |
|
932 |
|
933 |
def calculate_completeness_score(aibom: Dict[str, Any], validate: bool = True, use_best_practices: bool = True) -> Dict[str, Any]:
|
@@ -959,6 +1062,7 @@ def calculate_completeness_score(aibom: Dict[str, Any], validate: bool = True, u
|
|
959 |
warning_count = validation_result["summary"]["warning_count"]
|
960 |
|
961 |
# Apply penalties to the score
|
|
|
962 |
if error_count > 0:
|
963 |
# Severe penalty for errors (up to 50% reduction)
|
964 |
error_penalty = min(0.5, error_count * 0.1)
|
@@ -969,7 +1073,7 @@ def calculate_completeness_score(aibom: Dict[str, Any], validate: bool = True, u
|
|
969 |
warning_penalty = min(0.2, warning_count * 0.05)
|
970 |
result["total_score"] = round(result["total_score"] * (1 - warning_penalty), 1)
|
971 |
result["validation_penalty"] = f"-{int(warning_penalty * 100)}% due to {warning_count} schema warnings"
|
972 |
-
|
973 |
result = add_enhanced_field_display_to_result(result, aibom)
|
974 |
|
975 |
return result
|
@@ -1084,7 +1188,34 @@ def calculate_completeness_score(aibom: Dict[str, Any], validate: bool = True, u
|
|
1084 |
"total_score": total_score,
|
1085 |
"section_scores": section_scores,
|
1086 |
"max_scores": max_scores,
|
1087 |
-
"field_checklist": field_checklist
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1088 |
}
|
1089 |
|
1090 |
# Add validation if requested
|
@@ -1097,7 +1228,8 @@ def calculate_completeness_score(aibom: Dict[str, Any], validate: bool = True, u
|
|
1097 |
# Count errors and warnings
|
1098 |
error_count = validation_result["summary"]["error_count"]
|
1099 |
warning_count = validation_result["summary"]["warning_count"]
|
1100 |
-
|
|
|
1101 |
# Apply penalties to the score
|
1102 |
if error_count > 0:
|
1103 |
# Severe penalty for errors (up to 50% reduction)
|
@@ -1109,7 +1241,7 @@ def calculate_completeness_score(aibom: Dict[str, Any], validate: bool = True, u
|
|
1109 |
warning_penalty = min(0.2, warning_count * 0.05)
|
1110 |
result["total_score"] = round(result["total_score"] * (1 - warning_penalty), 1)
|
1111 |
result["validation_penalty"] = f"-{int(warning_penalty * 100)}% due to {warning_count} schema warnings"
|
1112 |
-
|
1113 |
result = add_enhanced_field_display_to_result(result, aibom)
|
1114 |
|
1115 |
return result
|
@@ -1305,3 +1437,92 @@ def add_enhanced_field_display_to_result(result: Dict[str, Any], aibom: Dict[str
|
|
1305 |
enhanced_result = result.copy()
|
1306 |
enhanced_result["field_display"] = get_field_categorization_for_display(aibom)
|
1307 |
return enhanced_result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
304 |
# Return the highest satisfied profile
|
305 |
if "advanced" in satisfied_profiles:
|
306 |
return {
|
307 |
+
"name": "Advanced",
|
308 |
"description": COMPLETENESS_PROFILES["advanced"]["description"],
|
309 |
"satisfied": True
|
310 |
}
|
311 |
elif "standard" in satisfied_profiles:
|
312 |
return {
|
313 |
+
"name": "Standard",
|
314 |
"description": COMPLETENESS_PROFILES["standard"]["description"],
|
315 |
"satisfied": True
|
316 |
}
|
317 |
elif "basic" in satisfied_profiles:
|
318 |
return {
|
319 |
+
"name": "Basic",
|
320 |
"description": COMPLETENESS_PROFILES["basic"]["description"],
|
321 |
"satisfied": True
|
322 |
}
|
|
|
329 |
|
330 |
|
331 |
def apply_completeness_penalties(original_score: float, missing_fields: Dict[str, List[str]]) -> Dict[str, Any]:
|
332 |
+
|
333 |
"""
|
334 |
Apply penalties based on missing critical fields.
|
335 |
|
|
|
340 |
Returns:
|
341 |
Dictionary with penalty information
|
342 |
"""
|
343 |
+
|
344 |
+
|
345 |
# Count missing fields by tier
|
346 |
missing_critical_count = len(missing_fields["critical"])
|
347 |
missing_important_count = len(missing_fields["important"])
|
348 |
|
349 |
+
penalty_factor = 1.0
|
350 |
+
penalty_reason = None
|
351 |
+
|
352 |
# Calculate penalty based on missing critical fields
|
353 |
if missing_critical_count > 3:
|
354 |
+
penalty_factor *= 0.8 # 20% penalty
|
355 |
penalty_reason = "Multiple critical fields missing"
|
356 |
+
elif missing_critical_count >= 2: # if count is 2 - 3
|
357 |
+
penalty_factor *= 0.9 # 10% penalty
|
358 |
penalty_reason = "Some critical fields missing"
|
359 |
+
|
360 |
+
if missing_important_count >= 5:
|
361 |
+
penalty_factor *= 0.95 # 5% penalty
|
362 |
penalty_reason = "Several important fields missing"
|
|
|
|
|
|
|
|
|
363 |
|
364 |
adjusted_score = original_score * penalty_factor
|
365 |
|
|
|
738 |
recommendations.append("Add ethical considerations, limitations, and risks to the model card")
|
739 |
|
740 |
if "MISSING_METADATA" in issue_codes:
|
741 |
+
recommendations.append("Add metadata section to the AI SBOM")
|
742 |
|
743 |
if "MISSING_TOOLS" in issue_codes:
|
744 |
recommendations.append("Include tools information in the metadata section")
|
|
|
838 |
|
839 |
def calculate_industry_neutral_score(aibom: Dict[str, Any]) -> Dict[str, Any]:
|
840 |
"""
|
841 |
+
Calculate completeness score using industry best practices with proper normalization and penalties.
|
842 |
|
843 |
Args:
|
844 |
aibom: The AIBOM to score
|
|
|
847 |
Dictionary containing score and recommendations
|
848 |
"""
|
849 |
field_checklist = {}
|
850 |
+
|
851 |
+
# Maximum points for each category (these are the "weights")
|
852 |
max_scores = {
|
853 |
"required_fields": 20,
|
854 |
"metadata": 20,
|
|
|
857 |
"external_references": 10
|
858 |
}
|
859 |
|
860 |
+
# Track missing fields by tier (for penalty calculation)
|
861 |
missing_fields = {
|
862 |
"critical": [],
|
863 |
"important": [],
|
864 |
"supplementary": []
|
865 |
}
|
866 |
|
867 |
+
# Count fields by category
|
868 |
+
fields_by_category = {category: {"total": 0, "present": 0} for category in max_scores.keys()}
|
|
|
869 |
|
870 |
+
# Process each field and categorize
|
871 |
for field, classification in FIELD_CLASSIFICATION.items():
|
872 |
tier = classification["tier"]
|
|
|
873 |
category = classification["category"]
|
874 |
|
875 |
+
# Count total fields in this category
|
876 |
+
fields_by_category[category]["total"] += 1
|
877 |
|
878 |
+
# Check if field is present (ensure boolean result)
|
879 |
+
is_present = bool(check_field_in_aibom(aibom, field))
|
880 |
|
881 |
if is_present:
|
882 |
+
fields_by_category[category]["present"] += 1
|
883 |
else:
|
884 |
missing_fields[tier].append(field)
|
885 |
|
|
|
887 |
importance_indicator = "★★★" if tier == "critical" else "★★" if tier == "important" else "★"
|
888 |
field_checklist[field] = f"{'✔' if is_present else '✘'} {importance_indicator}"
|
889 |
|
890 |
+
# Calculate category scores using proper normalization
|
891 |
+
category_scores = {}
|
892 |
+
for category, counts in fields_by_category.items():
|
893 |
+
if counts["total"] > 0:
|
894 |
+
# Normalization: (Present Fields / Total Fields) × Maximum Points
|
895 |
+
raw_score = (counts["present"] / counts["total"]) * max_scores[category]
|
896 |
+
# Ensure raw_score is a number before rounding
|
897 |
+
if isinstance(raw_score, (int, float)) and not isinstance(raw_score, bool):
|
898 |
+
category_scores[category] = round(raw_score, 1)
|
899 |
+
else:
|
900 |
+
category_scores[category] = 0.0
|
901 |
|
902 |
+
# Calculate subtotal (sum of rounded category scores)
|
903 |
+
subtotal_score = sum(category_scores.values())
|
|
|
|
|
|
|
|
|
904 |
|
905 |
+
# Count missing fields by tier for penalty calculation
|
906 |
+
missing_critical_count = len(missing_fields["critical"])
|
907 |
+
missing_important_count = len(missing_fields["important"])
|
908 |
+
|
909 |
+
# Apply penalties based on missing critical and important fields
|
910 |
+
penalty_factor = 1.0
|
911 |
+
penalty_reasons = []
|
912 |
+
|
913 |
+
# Critical field penalties
|
914 |
+
if missing_critical_count > 3:
|
915 |
+
penalty_factor *= 0.8 # 20% penalty
|
916 |
+
penalty_reasons.append("Multiple critical fields missing")
|
917 |
+
elif missing_critical_count >= 2: # if count is 2-3
|
918 |
+
penalty_factor *= 0.9 # 10% penalty
|
919 |
+
penalty_reasons.append("Some critical fields missing")
|
920 |
+
# No penalty for missing_critical_count == 1
|
921 |
+
|
922 |
+
# Important field penalties (additional)
|
923 |
+
if missing_important_count >= 5:
|
924 |
+
penalty_factor *= 0.95 # Additional 5% penalty
|
925 |
+
penalty_reasons.append("Several important fields missing")
|
926 |
+
|
927 |
+
# Apply penalty to subtotal
|
928 |
+
final_score = subtotal_score * penalty_factor
|
929 |
+
final_score = round(final_score, 1)
|
930 |
+
|
931 |
+
# Debugging calculation:
|
932 |
+
print(f"DEBUG CATEGORIES:")
|
933 |
+
for category, score in category_scores.items():
|
934 |
+
print(f" {category}: {score}")
|
935 |
+
print(f"DEBUG: category_scores sum = {sum(category_scores.values())}")
|
936 |
+
print(f"DEBUG: subtotal_score = {subtotal_score}")
|
937 |
+
print(f"DEBUG: missing_critical_count = {missing_critical_count}")
|
938 |
+
print(f"DEBUG: missing_important_count = {missing_important_count}")
|
939 |
+
print(f"DEBUG: penalty_factor = {penalty_factor}")
|
940 |
+
print(f"DEBUG: penalty_reasons = {penalty_reasons}")
|
941 |
+
print(f"DEBUG: subtotal_score = {subtotal_score}")
|
942 |
+
print(f"DEBUG: final_score calculation = {subtotal_score} × {penalty_factor} = {subtotal_score * penalty_factor}")
|
943 |
+
print(f"DEBUG: final_score after round = {final_score}")
|
944 |
|
945 |
# Ensure score is between 0 and 100
|
946 |
+
final_score = max(0.0, min(final_score, 100.0))
|
947 |
|
948 |
# Determine completeness profile
|
949 |
+
profile = determine_completeness_profile(aibom, final_score)
|
|
|
|
|
|
|
950 |
|
951 |
# Generate recommendations
|
952 |
recommendations = generate_field_recommendations(missing_fields)
|
953 |
|
954 |
+
# Prepare penalty information
|
955 |
+
penalty_applied = penalty_factor < 1.0
|
956 |
+
penalty_reason = " and ".join(penalty_reasons) if penalty_reasons else None
|
957 |
+
penalty_percentage = round((1.0 - penalty_factor) * 100, 1) if penalty_applied else 0.0
|
958 |
+
|
959 |
+
# DEBUG: Print the result structure before returning
|
960 |
+
print("DEBUG: Final result structure:")
|
961 |
+
print(f" total_score: {final_score}")
|
962 |
+
print(f" section_scores keys: {list(category_scores.keys())}")
|
963 |
+
|
964 |
+
result = {
|
965 |
+
"total_score": final_score,
|
966 |
+
"subtotal_score": subtotal_score,
|
967 |
+
"section_scores": category_scores,
|
968 |
"max_scores": max_scores,
|
969 |
"field_checklist": field_checklist,
|
970 |
+
"category_details": {
|
971 |
+
"required_fields": {
|
972 |
+
"present_fields": fields_by_category["required_fields"]["present"],
|
973 |
+
"total_fields": fields_by_category["required_fields"]["total"],
|
974 |
+
"percentage": round((fields_by_category["required_fields"]["present"] / fields_by_category["required_fields"]["total"]) * 100, 1)
|
975 |
+
},
|
976 |
+
"metadata": {
|
977 |
+
"present_fields": fields_by_category["metadata"]["present"],
|
978 |
+
"total_fields": fields_by_category["metadata"]["total"],
|
979 |
+
"percentage": round((fields_by_category["metadata"]["present"] / fields_by_category["metadata"]["total"]) * 100, 1)
|
980 |
+
},
|
981 |
+
"component_basic": {
|
982 |
+
"present_fields": fields_by_category["component_basic"]["present"],
|
983 |
+
"total_fields": fields_by_category["component_basic"]["total"],
|
984 |
+
"percentage": round((fields_by_category["component_basic"]["present"] / fields_by_category["component_basic"]["total"]) * 100, 1)
|
985 |
+
},
|
986 |
+
"component_model_card": {
|
987 |
+
"present_fields": fields_by_category["component_model_card"]["present"],
|
988 |
+
"total_fields": fields_by_category["component_model_card"]["total"],
|
989 |
+
"percentage": round((fields_by_category["component_model_card"]["present"] / fields_by_category["component_model_card"]["total"]) * 100, 1)
|
990 |
+
},
|
991 |
+
"external_references": {
|
992 |
+
"present_fields": fields_by_category["external_references"]["present"],
|
993 |
+
"total_fields": fields_by_category["external_references"]["total"],
|
994 |
+
"percentage": round((fields_by_category["external_references"]["present"] / fields_by_category["external_references"]["total"]) * 100, 1)
|
995 |
+
}
|
996 |
+
},
|
997 |
"field_categorization": get_field_categorization_for_display(aibom),
|
998 |
"field_tiers": {field: info["tier"] for field, info in FIELD_CLASSIFICATION.items()},
|
999 |
"missing_fields": missing_fields,
|
1000 |
+
"missing_counts": {
|
1001 |
+
"critical": missing_critical_count,
|
1002 |
+
"important": missing_important_count,
|
1003 |
+
"supplementary": len(missing_fields["supplementary"])
|
1004 |
+
},
|
1005 |
"completeness_profile": profile,
|
1006 |
+
"penalty_applied": penalty_applied,
|
1007 |
+
"penalty_reason": penalty_reason,
|
1008 |
+
"penalty_percentage": penalty_percentage,
|
1009 |
+
"penalty_factor": penalty_factor,
|
1010 |
+
"recommendations": recommendations,
|
1011 |
+
"calculation_details": {
|
1012 |
+
"category_breakdown": {
|
1013 |
+
category: {
|
1014 |
+
"present_fields": counts["present"],
|
1015 |
+
"total_fields": counts["total"],
|
1016 |
+
"percentage": round((counts["present"] / counts["total"]) * 100, 1) if counts["total"] > 0 else 0.0,
|
1017 |
+
"points": category_scores[category],
|
1018 |
+
"max_points": max_scores[category]
|
1019 |
+
}
|
1020 |
+
for category, counts in fields_by_category.items()
|
1021 |
+
}
|
1022 |
+
}
|
1023 |
}
|
1024 |
+
|
1025 |
+
# Debug the final result
|
1026 |
+
if 'category_details' in result:
|
1027 |
+
print(f" category_details exists: {list(result['category_details'].keys())}")
|
1028 |
+
print(f" required_fields details: {result['category_details'].get('required_fields')}")
|
1029 |
+
print(f" metadata details: {result['category_details'].get('metadata')}")
|
1030 |
+
else:
|
1031 |
+
print(" category_details: MISSING!")
|
1032 |
+
|
1033 |
+
return result
|
1034 |
|
1035 |
|
1036 |
def calculate_completeness_score(aibom: Dict[str, Any], validate: bool = True, use_best_practices: bool = True) -> Dict[str, Any]:
|
|
|
1062 |
warning_count = validation_result["summary"]["warning_count"]
|
1063 |
|
1064 |
# Apply penalties to the score
|
1065 |
+
"""
|
1066 |
if error_count > 0:
|
1067 |
# Severe penalty for errors (up to 50% reduction)
|
1068 |
error_penalty = min(0.5, error_count * 0.1)
|
|
|
1073 |
warning_penalty = min(0.2, warning_count * 0.05)
|
1074 |
result["total_score"] = round(result["total_score"] * (1 - warning_penalty), 1)
|
1075 |
result["validation_penalty"] = f"-{int(warning_penalty * 100)}% due to {warning_count} schema warnings"
|
1076 |
+
"""
|
1077 |
result = add_enhanced_field_display_to_result(result, aibom)
|
1078 |
|
1079 |
return result
|
|
|
1188 |
"total_score": total_score,
|
1189 |
"section_scores": section_scores,
|
1190 |
"max_scores": max_scores,
|
1191 |
+
"field_checklist": field_checklist,
|
1192 |
+
"category_details": {
|
1193 |
+
"required_fields": {
|
1194 |
+
"present_fields": fields_by_category["required_fields"]["present"],
|
1195 |
+
"total_fields": fields_by_category["required_fields"]["total"],
|
1196 |
+
"percentage": round((fields_by_category["required_fields"]["present"] / fields_by_category["required_fields"]["total"]) * 100, 1)
|
1197 |
+
},
|
1198 |
+
"metadata": {
|
1199 |
+
"present_fields": fields_by_category["metadata"]["present"],
|
1200 |
+
"total_fields": fields_by_category["metadata"]["total"],
|
1201 |
+
"percentage": round((fields_by_category["metadata"]["present"] / fields_by_category["metadata"]["total"]) * 100, 1)
|
1202 |
+
},
|
1203 |
+
"component_basic": {
|
1204 |
+
"present_fields": fields_by_category["component_basic"]["present"],
|
1205 |
+
"total_fields": fields_by_category["component_basic"]["total"],
|
1206 |
+
"percentage": round((fields_by_category["component_basic"]["present"] / fields_by_category["component_basic"]["total"]) * 100, 1)
|
1207 |
+
},
|
1208 |
+
"component_model_card": {
|
1209 |
+
"present_fields": fields_by_category["component_model_card"]["present"],
|
1210 |
+
"total_fields": fields_by_category["component_model_card"]["total"],
|
1211 |
+
"percentage": round((fields_by_category["component_model_card"]["present"] / fields_by_category["component_model_card"]["total"]) * 100, 1)
|
1212 |
+
},
|
1213 |
+
"external_references": {
|
1214 |
+
"present_fields": fields_by_category["external_references"]["present"],
|
1215 |
+
"total_fields": fields_by_category["external_references"]["total"],
|
1216 |
+
"percentage": round((fields_by_category["external_references"]["present"] / fields_by_category["external_references"]["total"]) * 100, 1)
|
1217 |
+
}
|
1218 |
+
}
|
1219 |
}
|
1220 |
|
1221 |
# Add validation if requested
|
|
|
1228 |
# Count errors and warnings
|
1229 |
error_count = validation_result["summary"]["error_count"]
|
1230 |
warning_count = validation_result["summary"]["warning_count"]
|
1231 |
+
|
1232 |
+
"""
|
1233 |
# Apply penalties to the score
|
1234 |
if error_count > 0:
|
1235 |
# Severe penalty for errors (up to 50% reduction)
|
|
|
1241 |
warning_penalty = min(0.2, warning_count * 0.05)
|
1242 |
result["total_score"] = round(result["total_score"] * (1 - warning_penalty), 1)
|
1243 |
result["validation_penalty"] = f"-{int(warning_penalty * 100)}% due to {warning_count} schema warnings"
|
1244 |
+
"""
|
1245 |
result = add_enhanced_field_display_to_result(result, aibom)
|
1246 |
|
1247 |
return result
|
|
|
1437 |
enhanced_result = result.copy()
|
1438 |
enhanced_result["field_display"] = get_field_categorization_for_display(aibom)
|
1439 |
return enhanced_result
|
1440 |
+
|
1441 |
+
|
1442 |
+
def get_score_display_info(score_result: Dict[str, Any]) -> Dict[str, Any]:
|
1443 |
+
"""
|
1444 |
+
Generate user-friendly display information for the score.
|
1445 |
+
|
1446 |
+
Args:
|
1447 |
+
score_result: Result from calculate_industry_neutral_score
|
1448 |
+
|
1449 |
+
Returns:
|
1450 |
+
Dictionary with display-friendly information
|
1451 |
+
"""
|
1452 |
+
display_info = {
|
1453 |
+
"category_display": [],
|
1454 |
+
"penalty_display": None,
|
1455 |
+
"total_display": None
|
1456 |
+
}
|
1457 |
+
|
1458 |
+
# Format category scores for display
|
1459 |
+
for category, score in score_result["section_scores"].items():
|
1460 |
+
max_score = score_result["max_scores"][category]
|
1461 |
+
category_name = category.replace("_", " ").title()
|
1462 |
+
|
1463 |
+
display_info["category_display"].append({
|
1464 |
+
"name": category_name,
|
1465 |
+
"score": f"{score}/{max_score}",
|
1466 |
+
"percentage": round((score / max_score) * 100, 1) if max_score > 0 else 0.0
|
1467 |
+
})
|
1468 |
+
|
1469 |
+
# Format penalty display
|
1470 |
+
if score_result["penalty_applied"]:
|
1471 |
+
display_info["penalty_display"] = {
|
1472 |
+
"message": f"Penalty Applied: -{score_result['penalty_percentage']}% ({score_result['penalty_reason']})",
|
1473 |
+
"subtotal": f"{score_result['subtotal_score']}/100",
|
1474 |
+
"final": f"{score_result['total_score']}/100"
|
1475 |
+
}
|
1476 |
+
|
1477 |
+
# Format total display
|
1478 |
+
display_info["total_display"] = {
|
1479 |
+
"score": f"{score_result['total_score']}/100",
|
1480 |
+
"percentage": round(score_result['total_score'], 1)
|
1481 |
+
}
|
1482 |
+
|
1483 |
+
return display_info
|
1484 |
+
|
1485 |
+
|
1486 |
+
def format_score_summary(score_result: Dict[str, Any]) -> str:
|
1487 |
+
"""
|
1488 |
+
Generate a human-readable summary of the scoring results.
|
1489 |
+
|
1490 |
+
Args:
|
1491 |
+
score_result: Result from calculate_industry_neutral_score
|
1492 |
+
|
1493 |
+
Returns:
|
1494 |
+
Formatted summary string
|
1495 |
+
"""
|
1496 |
+
summary = "AI SBOM Completeness Score Summary\n"
|
1497 |
+
summary += "=" * 40 + "\n\n"
|
1498 |
+
|
1499 |
+
# Category breakdown
|
1500 |
+
summary += "Category Breakdown:\n"
|
1501 |
+
for category, score in score_result["section_scores"].items():
|
1502 |
+
max_score = score_result["max_scores"][category]
|
1503 |
+
category_name = category.replace("_", " ").title()
|
1504 |
+
percentage = round((score / max_score) * 100, 1) if max_score > 0 else 0.0
|
1505 |
+
summary += f"- {category_name}: {score}/{max_score} ({percentage}%)\n"
|
1506 |
+
|
1507 |
+
summary += f"\nSubtotal: {score_result['subtotal_score']}/100\n"
|
1508 |
+
|
1509 |
+
# Penalty information
|
1510 |
+
if score_result["penalty_applied"]:
|
1511 |
+
summary += f"\nPenalty Applied: -{score_result['penalty_percentage']}%\n"
|
1512 |
+
summary += f"Reason: {score_result['penalty_reason']}\n"
|
1513 |
+
summary += f"Final Score: {score_result['total_score']}/100\n"
|
1514 |
+
else:
|
1515 |
+
summary += f"Final Score: {score_result['total_score']}/100 (No penalties applied)\n"
|
1516 |
+
|
1517 |
+
# Missing field counts
|
1518 |
+
summary += f"\nMissing Fields Summary:\n"
|
1519 |
+
summary += f"- Critical: {score_result['missing_counts']['critical']}\n"
|
1520 |
+
summary += f"- Important: {score_result['missing_counts']['important']}\n"
|
1521 |
+
summary += f"- Supplementary: {score_result['missing_counts']['supplementary']}\n"
|
1522 |
+
|
1523 |
+
# Completeness profile
|
1524 |
+
profile = score_result["completeness_profile"]
|
1525 |
+
summary += f"\nCompleteness Profile: {profile['name']}\n"
|
1526 |
+
summary += f"Description: {profile['description']}\n"
|
1527 |
+
|
1528 |
+
return summary
|