1313
1414from mitreattack .diffStix .core .contributor_tracker import ContributorTracker
1515from mitreattack .diffStix .core .domain_statistics import DomainStatistics
16+ from mitreattack .diffStix .core .statistics_collector import StatisticsCollector
1617from mitreattack .diffStix .formatters .json_generator import JsonGenerator
1718from mitreattack .diffStix .formatters .layer_generator import LayerGenerator
1819from mitreattack .diffStix .formatters .markdown_generator import MarkdownGenerator
@@ -197,7 +198,8 @@ def __init__(
197198
198199 self .load_data ()
199200
200- # Initialize formatters after data is loaded
201+ # Initialize components after data is loaded
202+ self ._statistics_collector = StatisticsCollector (self )
201203 self ._markdown_generator = MarkdownGenerator (self )
202204 self ._layer_generator = LayerGenerator (self )
203205 self ._json_generator = JsonGenerator (self )
@@ -1060,8 +1062,7 @@ def placard(self, stix_object: dict, section: str, domain: str) -> str:
10601062 return self ._markdown_generator .placard (stix_object , section , domain )
10611063
10621064 def _collect_domain_statistics (self , datastore : MemoryStore , domain_name : str ) -> DomainStatistics :
1063- """
1064- Collect statistics for a single domain from a STIX datastore.
1065+ """Collect statistics for a single domain from a STIX datastore.
10651066
10661067 Parameters
10671068 ----------
@@ -1075,42 +1076,10 @@ def _collect_domain_statistics(self, datastore: MemoryStore, domain_name: str) -
10751076 DomainStatistics
10761077 Statistics for the domain.
10771078 """
1078- # Create MitreAttackData instance from the datastore
1079- data = MitreAttackData (src = datastore )
1080-
1081- # Get all object types, removing revoked and deprecated
1082- tactics = data .get_tactics (remove_revoked_deprecated = True )
1083- techniques = data .get_techniques (include_subtechniques = False , remove_revoked_deprecated = True )
1084- subtechniques = data .get_subtechniques (remove_revoked_deprecated = True )
1085- groups = data .get_groups (remove_revoked_deprecated = True )
1086- software = data .get_software (remove_revoked_deprecated = True )
1087- campaigns = data .get_campaigns (remove_revoked_deprecated = True )
1088- mitigations = data .get_mitigations (remove_revoked_deprecated = True )
1089- assets = data .get_assets (remove_revoked_deprecated = True )
1090- datasources = data .get_datasources (remove_revoked_deprecated = True )
1091- detectionstrategies = data .get_detectionstrategies (remove_revoked_deprecated = True )
1092- analytics = data .get_analytics (remove_revoked_deprecated = True )
1093- datacomponents = data .get_datacomponents (remove_revoked_deprecated = True )
1094-
1095- return DomainStatistics (
1096- name = domain_name ,
1097- tactics = len (tactics ),
1098- techniques = len (techniques ),
1099- subtechniques = len (subtechniques ),
1100- groups = len (groups ),
1101- software = len (software ),
1102- campaigns = len (campaigns ),
1103- mitigations = len (mitigations ),
1104- assets = len (assets ),
1105- datasources = len (datasources ),
1106- detectionstrategies = len (detectionstrategies ),
1107- analytics = len (analytics ),
1108- datacomponents = len (datacomponents ),
1109- )
1079+ return self ._statistics_collector .collect_domain_statistics (datastore , domain_name )
11101080
11111081 def _collect_unique_object_counts (self , datastore_version : str ) -> dict [str , int ]:
1112- """
1113- Collect counts of unique objects across all domains for a specific version.
1082+ """Collect counts of unique objects across all domains for a specific version.
11141083
11151084 Some objects (Software, Groups, Campaigns) may appear in multiple domains.
11161085 This function counts unique objects to avoid double-counting.
@@ -1125,31 +1094,10 @@ def _collect_unique_object_counts(self, datastore_version: str) -> dict[str, int
11251094 dict of str to int
11261095 Counts of unique software, groups, and campaigns.
11271096 """
1128- all_software_ids = set ()
1129- all_groups_ids = set ()
1130- all_campaigns_ids = set ()
1131-
1132- for domain in self .domains :
1133- datastore = self .data [datastore_version ][domain ]["stix_datastore" ]
1134- data = MitreAttackData (src = datastore )
1135-
1136- software = data .get_software (remove_revoked_deprecated = True )
1137- groups = data .get_groups (remove_revoked_deprecated = True )
1138- campaigns = data .get_campaigns (remove_revoked_deprecated = True )
1139-
1140- all_software_ids .update (obj ["id" ] for obj in software )
1141- all_groups_ids .update (obj ["id" ] for obj in groups )
1142- all_campaigns_ids .update (obj ["id" ] for obj in campaigns )
1143-
1144- return {
1145- "software" : len (all_software_ids ),
1146- "groups" : len (all_groups_ids ),
1147- "campaigns" : len (all_campaigns_ids ),
1148- }
1097+ return self ._statistics_collector .collect_unique_object_counts (datastore_version )
11491098
11501099 def get_statistics_section (self , datastore_version : str = "new" ) -> str :
1151- """
1152- Generate a markdown section with ATT&CK statistics for all domains.
1100+ """Generate a markdown section with ATT&CK statistics for all domains.
11531101
11541102 Parameters
11551103 ----------
@@ -1162,30 +1110,7 @@ def get_statistics_section(self, datastore_version: str = "new") -> str:
11621110 str
11631111 Markdown-formatted statistics section.
11641112 """
1165- # Collect unique object counts across all domains
1166- unique_counts = self ._collect_unique_object_counts (datastore_version )
1167-
1168- # Collect statistics for each domain
1169- domain_stats = []
1170- for domain in self .domains :
1171- datastore = self .data [datastore_version ][domain ]["stix_datastore" ]
1172- domain_label = self .domain_to_domain_label [domain ]
1173- stats = self ._collect_domain_statistics (datastore , domain_label )
1174- domain_stats .append (stats )
1175-
1176- # Build the statistics section
1177- output = "## Statistics\n \n "
1178- output += (
1179- f"This version of ATT&CK contains { unique_counts ['software' ]} Software, "
1180- f"{ unique_counts ['groups' ]} Groups, and { unique_counts ['campaigns' ]} Campaigns.\n \n "
1181- )
1182- output += "Broken out by domain:\n \n "
1183-
1184- for stats in domain_stats :
1185- output += stats .format_output () + "\n "
1186-
1187- output += "\n "
1188- return output
1113+ return self ._statistics_collector .generate_statistics_section (datastore_version )
11891114
11901115 def get_markdown_section_data (self , groupings , section : str , domain : str ) -> str :
11911116 """Parse a list of STIX objects in a section and return a string for the whole section."""
0 commit comments