from collections import Counter from serato_doctor.matching import MatchingEngine from serato_doctor.models.crate import CrateKind from serato_doctor.models.health import HealthReport from serato_doctor.models.library import Library def analyze_health(library: Library) -> HealthReport: """Calculate defensible health metrics without changing the library.""" dynamic_sources = { crate.path for crate in library.crates if crate.kind is CrateKind.SMART } results = tuple( result for result in library.reconcile_by_filename() if result.reference.source not in dynamic_sources ) missing = [result for result in results if not result.exists_by_filename] healthy_count = len(results) - len(missing) score = ( round(healthy_count / len(results) * 100, 1) if results else None ) disk_name_counts = Counter(track.filename for track in library.tracks) duplicate_counts = [count for count in disk_name_counts.values() if count > 1] referenced_names = {reference.filename for reference in library.references} unused_count = sum( 1 for track in library.tracks if track.filename not in referenced_names ) matcher = MatchingEngine(library.tracks) suggested_count = sum( bool(matcher.candidates_for(result.reference)) for result in missing ) return HealthReport( score=score, total_references=len(library.references), scored_references=len(results), healthy_references=healthy_count, missing_references=len(missing), unique_missing_filenames=len( {result.reference.filename for result in missing} ), disk_tracks=len(library.tracks), duplicate_filename_groups=len(duplicate_counts), duplicate_files=sum(count - 1 for count in duplicate_counts), unused_tracks=unused_count, suggested_matches=suggested_count, static_crates=sum( crate.kind is CrateKind.STATIC for crate in library.crates ), smart_crates=sum( crate.kind is CrateKind.SMART for crate in library.crates ), unknown_crates=sum( crate.kind is CrateKind.UNKNOWN for crate in library.crates ), dynamic_references_excluded=sum( len(crate.references) for crate in library.crates if crate.kind is CrateKind.SMART ), )