""" Pure diff functions for impact analysis. Implements FR-8.2: Structural diff-based impact measurement. Uses difflib.SequenceMatcher for computing change magnitude between old and new content versions. """ import difflib from typing import Optional def structural_diff_ratio(old_content: str, new_content: str) -> float: """ Calculate structural similarity ratio between old and new content. Uses SequenceMatcher to compute a ratio of matching blocks. Returns the *change* ratio (1.0 - similarity), so higher values mean more change. Args: old_content: Previous content new_content: Current content Returns: Change ratio from 0.0 (identical) to 1.0 (completely different) """ if old_content == new_content: return 0.0 if not old_content and not new_content: return 0.0 if not old_content or not new_content: return 1.0 matcher = difflib.SequenceMatcher(None, old_content, new_content) similarity = matcher.ratio() return 1.0 - similarity def line_diff_ratio(old_content: str, new_content: str) -> float: """ Calculate line-level change ratio between old and new content. Splits content into lines and computes SequenceMatcher ratio at the line level. Returns the change ratio. Args: old_content: Previous content new_content: Current content Returns: Change ratio from 0.0 (identical) to 1.0 (completely different) """ if old_content == new_content: return 0.0 if not old_content and not new_content: return 0.0 if not old_content or not new_content: return 1.0 old_lines = old_content.splitlines() new_lines = new_content.splitlines() matcher = difflib.SequenceMatcher(None, old_lines, new_lines) similarity = matcher.ratio() return 1.0 - similarity def calculate_change_magnitude( old_content: Optional[str], new_content: Optional[str], method: str = "structural", ) -> float: """ Calculate the magnitude of a change between two content versions. This is the primary entry point for impact measurement (FR-8.2). Args: old_content: Previous content (None for created artifacts) new_content: Current content (None for deleted artifacts) method: Diff method to use ("structural" or "line") Returns: Change magnitude from 0.0 (no change) to 1.0 (complete change) """ # Handle None cases (creation/deletion) if old_content is None and new_content is None: return 0.0 if old_content is None or new_content is None: return 1.0 if method == "line": return line_diff_ratio(old_content, new_content) return structural_diff_ratio(old_content, new_content)