| """ |
| feedback/diff_engine.py — difflib-based diff with change_ratio |
| |
| Computes structured diffs between original and edited draft text. |
| Filters out trivially small edits (< 5% change) to avoid learning |
| from minor typo fixes. |
| """ |
|
|
| import difflib |
| from models import EditDiff |
|
|
|
|
| class DiffEngine: |
| """Compute structured diffs between original and edited text.""" |
|
|
| def compute(self, original: str, edited: str) -> EditDiff: |
| """ |
| Compute the diff between original and edited text. |
| |
| Returns an EditDiff with additions, deletions, and change_ratio. |
| change_ratio ranges from 0.0 (identical) to 1.0 (completely different). |
| """ |
| orig_lines = original.splitlines() |
| edit_lines = edited.splitlines() |
|
|
| opcodes = difflib.SequenceMatcher( |
| None, orig_lines, edit_lines |
| ).get_opcodes() |
|
|
| additions: list[str] = [] |
| deletions: list[str] = [] |
|
|
| for tag, i1, i2, j1, j2 in opcodes: |
| if tag == "insert": |
| additions.append("\n".join(edit_lines[j1:j2])) |
| elif tag == "delete": |
| deletions.append("\n".join(orig_lines[i1:i2])) |
| elif tag == "replace": |
| deletions.append("\n".join(orig_lines[i1:i2])) |
| additions.append("\n".join(edit_lines[j1:j2])) |
|
|
| |
| change_ratio = 1.0 - difflib.SequenceMatcher( |
| None, original, edited |
| ).ratio() |
|
|
| return EditDiff( |
| additions=additions, |
| deletions=deletions, |
| original=original, |
| edited=edited, |
| change_ratio=round(change_ratio, 3), |
| ) |
|
|