diff --git a/backend/app/services/heatmap_service.py b/backend/app/services/heatmap_service.py index a6672d5..d39c657 100644 --- a/backend/app/services/heatmap_service.py +++ b/backend/app/services/heatmap_service.py @@ -259,36 +259,30 @@ def build_threat_actor_layer( if is_actor_technique and score < min_score: continue - if is_actor_technique: - tc = test_counts.get(tech.id, 0) - rc = rule_counts.get(tech.mitre_id, 0) - metadata = [ - {"name": "tests_count", "value": str(tc)}, - {"name": "detection_rules", "value": str(rc)}, - ] - if tech.last_review_date: - metadata.append( - {"name": "last_validated", "value": tech.last_review_date.strftime("%Y-%m-%d")} - ) - layer["techniques"].append({ - "techniqueID": tech.mitre_id, - "tactic": _format_tactic(tech.tactic), - "color": _score_to_color(score), - "score": score, - "comment": f"Used by {actor.name} - Coverage: {tech.status_global.value}", - "enabled": True, - "metadata": metadata, - }) - else: - layer["techniques"].append({ - "techniqueID": tech.mitre_id, - "tactic": _format_tactic(tech.tactic), - "color": "", - "score": 0, - "comment": "", - "enabled": False, - "metadata": [], - }) + # Only include techniques actually used by this actor — skip the rest + # so that tactics with no actor techniques are hidden in the matrix. + if not is_actor_technique: + continue + + tc = test_counts.get(tech.id, 0) + rc = rule_counts.get(tech.mitre_id, 0) + metadata = [ + {"name": "tests_count", "value": str(tc)}, + {"name": "detection_rules", "value": str(rc)}, + ] + if tech.last_review_date: + metadata.append( + {"name": "last_validated", "value": tech.last_review_date.strftime("%Y-%m-%d")} + ) + layer["techniques"].append({ + "techniqueID": tech.mitre_id, + "tactic": _format_tactic(tech.tactic), + "color": _score_to_color(score), + "score": score, + "comment": f"Used by {actor.name} - Coverage: {tech.status_global.value}", + "enabled": True, + "metadata": metadata, + }) return layer