"""Analyzer for list fields (artist_ids, cover_ids, playlist_ids, stems).""" from typing import List, Dict, Any from collections import Counter import logging from .base_analyzer import BaseAnalyzer logger = logging.getLogger(__name__) class ListsAnalyzer(BaseAnalyzer): """Analyze list fields like artist_ids, cover_ids, playlist_ids, and stems.""" def __init__(self, output_dir): super().__init__("lists", output_dir) self.list_fields = ["artist_ids", "cover_ids", "playlist_ids", "stems"] def process_chunk(self, records: List[Dict[str, Any]]) -> Dict[str, Any]: """Process a chunk of records for list fields analysis.""" results = {} compound_combinations = Counter() for field in self.list_fields: counts = [] missing = 0 empty = 0 for record in records: if field in record: value = record[field] if value: # For stems, count keys in dict; for others, count list length if field == "stems": counts.append(len(value) if isinstance(value, dict) else 0) else: counts.append(len(value) if isinstance(value, list) else 0) else: empty += 1 else: missing += 1 results[field] = {"counts": counts, "missing": missing, "empty": empty} # Analyze compound combinations for record in records: # Check which fields are present and non-empty present_fields = [] # Check artist_ids if record.get("artist_ids"): present_fields.append("artist") # Check cover_ids if record.get("cover_ids"): present_fields.append("cover") # Check playlist_ids if record.get("playlist_ids"): present_fields.append("playlist") # Check stems stems = record.get("stems") if stems and (isinstance(stems, dict) and len(stems) > 0): present_fields.append("stems") # Create compound key if present_fields: compound_key = "_".join(sorted(present_fields)) compound_combinations[compound_key] += 1 else: compound_combinations["none"] += 1 results["compound_combinations"] = dict(compound_combinations) results["total_records"] = len(records) return results def aggregate(self, chunk_results: List[Dict[str, Any]]) -> Dict[str, Any]: """Aggregate results from all chunks.""" from ..utils.stats_utils import aggregate_counters aggregated = {} total_records = sum(chunk.get("total_records", 0) for chunk in chunk_results) # Aggregate individual field statistics for field in self.list_fields: all_counts = [] total_missing = 0 total_empty = 0 for chunk in chunk_results: if field in chunk: all_counts.extend(chunk[field].get("counts", [])) total_missing += chunk[field].get("missing", 0) total_empty += chunk[field].get("empty", 0) # Calculate distribution distribution = self.calculate_distribution(all_counts) if all_counts else {} # Create histogram for counts count_histogram = {} for count in all_counts: count_str = str(count) if count < 20 else "20+" count_histogram[count_str] = count_histogram.get(count_str, 0) + 1 aggregated[field] = { "summary": { "total_records": total_records, "records_with_field": total_records - total_missing, "missing": total_missing, "empty": total_empty, "non_empty": len(all_counts), }, "count_distribution": distribution, "count_histogram": dict( sorted( count_histogram.items(), key=lambda x: (int(x[0]) if x[0] != "20+" else 999, x[0]), ) ), } # Aggregate compound combinations all_compound_combinations = [chunk.get("compound_combinations", {}) for chunk in chunk_results] total_compound_combinations = aggregate_counters(all_compound_combinations) # Sort by frequency sorted_combinations = sorted( total_compound_combinations.items(), key=lambda x: x[1], reverse=True ) # Calculate percentages compound_with_percentages = [] for combo, count in sorted_combinations: percentage = (count / total_records * 100) if total_records > 0 else 0 compound_with_percentages.append( {"combination": combo, "count": count, "percentage": round(percentage, 2)} ) # Overall summary aggregated["summary"] = { "total_records": total_records, "fields_analyzed": self.list_fields, "unique_compound_combinations": len(total_compound_combinations), } aggregated["compound_combinations"] = { "summary": { "total_combinations": len(total_compound_combinations), "most_common": sorted_combinations[0] if sorted_combinations else None, "least_common": sorted_combinations[-1] if sorted_combinations else None, }, "all_combinations": compound_with_percentages, "raw_counts": total_compound_combinations, } return aggregated