youssefreda9 commited on
Commit
9674032
·
1 Parent(s): c1f8f9f

Phase 14: Fix StageLocker over-locking + PatchSet overlap resolution

Browse files

- StageLocker: Grammar now only blocked by spelling locks when >50%
of the grammar diff falls INSIDE the spelling lock. Previously,
any boundary touch (even 1 char) blocked grammar entirely.
This fixes the Collision Dataset bug (32% pass rate) where
spelling locks bled into adjacent words and blocked grammar.

- PatchSet: Relaxed overlap resolution — patches with <50% overlap
of the smaller patch are allowed to coexist. This prevents
punctuation patches from dropping spelling patches on adjacent words.

Both fixes verified with unit tests.

src/nlp/correction_patch.py CHANGED
@@ -96,6 +96,11 @@ class PatchSet:
96
  """
97
  Single owner per range. Deterministic resolution.
98
  Uses ORIGINAL coordinates for overlap detection.
 
 
 
 
 
99
  """
100
  sorted_patches = sorted(
101
  self.patches,
@@ -106,11 +111,24 @@ class PatchSet:
106
  resolved = []
107
 
108
  for patch in sorted_patches:
109
- overlaps = any(
110
- patch.start_original < ce and patch.end_original > cs
111
- for cs, ce in claimed_ranges
112
- )
113
- if not overlaps:
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  resolved.append(patch)
115
  claimed_ranges.append((patch.start_original, patch.end_original))
116
  else:
 
96
  """
97
  Single owner per range. Deterministic resolution.
98
  Uses ORIGINAL coordinates for overlap detection.
99
+
100
+ Phase 14: Relaxed overlap — patches that touch at a boundary or
101
+ have minimal overlap (<= 50% of the smaller patch) are allowed
102
+ to coexist. This prevents punctuation patches from killing spelling
103
+ patches on adjacent words (e.g., 'المشروع.' punct vs 'في' spelling).
104
  """
105
  sorted_patches = sorted(
106
  self.patches,
 
111
  resolved = []
112
 
113
  for patch in sorted_patches:
114
+ has_substantial_overlap = False
115
+ for cs, ce in claimed_ranges:
116
+ # Check if there's any overlap at all
117
+ if patch.start_original < ce and patch.end_original > cs:
118
+ # Calculate overlap amount
119
+ overlap_start = max(patch.start_original, cs)
120
+ overlap_end = min(patch.end_original, ce)
121
+ overlap_width = overlap_end - overlap_start
122
+ # Compare to the smaller patch's width
123
+ patch_width = max(1, patch.end_original - patch.start_original)
124
+ claimed_width = max(1, ce - cs)
125
+ smaller_width = min(patch_width, claimed_width)
126
+ overlap_ratio = overlap_width / smaller_width
127
+ if overlap_ratio > 0.5:
128
+ has_substantial_overlap = True
129
+ break
130
+
131
+ if not has_substantial_overlap:
132
  resolved.append(patch)
133
  claimed_ranges.append((patch.start_original, patch.end_original))
134
  else:
src/nlp/stage_locker.py CHANGED
@@ -81,6 +81,13 @@ class StageLocker:
81
  is_locked_for(0, 5, 'grammar') on spelling lock → False (grammar > spelling)
82
  is_locked_for(0, 5, 'punctuation') on spelling lock → True (spelling > punctuation)
83
  is_locked_for(0, 5, 'grammar') on protection lock → True (protection > grammar)
 
 
 
 
 
 
 
84
  """
85
  req_priority = STAGE_PRIORITY.get(requesting_stage, 0)
86
  for ls, le, owner in self.locked_spans:
@@ -94,6 +101,35 @@ class StageLocker:
94
  f"owner={owner}({owner_priority})"
95
  )
96
  return True # Blocked: owner is same or higher priority
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  else:
98
  if PIPELINE_DEBUG:
99
  logger.debug(
 
81
  is_locked_for(0, 5, 'grammar') on spelling lock → False (grammar > spelling)
82
  is_locked_for(0, 5, 'punctuation') on spelling lock → True (spelling > punctuation)
83
  is_locked_for(0, 5, 'grammar') on protection lock → True (protection > grammar)
84
+
85
+ Phase 14 (StageLocker relaxation):
86
+ Grammar requesting on a spelling lock uses RELAXED overlap:
87
+ only blocked if >50% of the grammar diff falls inside the spelling lock.
88
+ This allows grammar to fix words ADJACENT to spelling-fixed words
89
+ (the "Collision Dataset" bug — spelling locks were bleeding into
90
+ neighboring words and blocking valid grammar corrections).
91
  """
92
  req_priority = STAGE_PRIORITY.get(requesting_stage, 0)
93
  for ls, le, owner in self.locked_spans:
 
101
  f"owner={owner}({owner_priority})"
102
  )
103
  return True # Blocked: owner is same or higher priority
104
+
105
+ # ── Phase 14: Relaxed overlap for grammar on spelling locks ──
106
+ # Grammar (3) outranks spelling (2) in priority, BUT we still
107
+ # want to protect the EXACT word spelling fixed. The relaxation:
108
+ # allow grammar if the overlap is < 50% of the grammar diff's
109
+ # width (i.e., the grammar diff is mostly OUTSIDE the lock).
110
+ # This catches boundary touches and small overlaps that happen
111
+ # when spelling locks bleed past word boundaries.
112
+ if requesting_stage == 'grammar' and owner == 'spelling':
113
+ overlap_start = max(start, ls)
114
+ overlap_end = min(end, le)
115
+ overlap_width = max(0, overlap_end - overlap_start)
116
+ diff_width = max(1, end - start)
117
+ overlap_ratio = overlap_width / diff_width
118
+ if overlap_ratio > 0.5:
119
+ # Grammar diff is mostly inside spelling lock — block
120
+ logger.info(
121
+ f"[StageLocker] Grammar blocked (overlap={overlap_ratio:.0%}): "
122
+ f"grammar[{start}:{end}] vs spelling[{ls}:{le}]"
123
+ )
124
+ return True
125
+ else:
126
+ # Grammar diff is mostly outside — allow
127
+ if PIPELINE_DEBUG or overlap_width > 0:
128
+ logger.info(
129
+ f"[StageLocker] Grammar ALLOWED (overlap={overlap_ratio:.0%}): "
130
+ f"grammar[{start}:{end}] vs spelling[{ls}:{le}]"
131
+ )
132
+ continue
133
  else:
134
  if PIPELINE_DEBUG:
135
  logger.debug(