idnameraj commited on
Commit
7ea9869
·
verified ·
1 Parent(s): 39cfcd1

Upload 105 files

Browse files
app/engine/__pycache__/orchestrator.cpython-311.pyc CHANGED
Binary files a/app/engine/__pycache__/orchestrator.cpython-311.pyc and b/app/engine/__pycache__/orchestrator.cpython-311.pyc differ
 
app/engine/normalize/__init__.py CHANGED
@@ -295,7 +295,9 @@ def mask_protected_spans(text: str) -> tuple[str, dict[str, str]]:
295
 
296
  def _sub(pattern: re.Pattern[str], label: str, s: str) -> str:
297
  def repl(m: re.Match[str]) -> str:
298
- key = f"__PROT_{label}_{counter['n']}__"
 
 
299
  mapping[key] = m.group(0)
300
  counter["n"] += 1
301
  return key
@@ -307,7 +309,7 @@ def mask_protected_spans(text: str) -> tuple[str, dict[str, str]]:
307
  out = _sub(_EMAIL, "EMAIL", out)
308
 
309
  def path_repl(m: re.Match[str]) -> str:
310
- key = f"__PROT_PATH_{counter['n']}__"
311
  mapping[key] = m.group(0)
312
  counter["n"] += 1
313
  return key
 
295
 
296
  def _sub(pattern: re.Pattern[str], label: str, s: str) -> str:
297
  def repl(m: re.Match[str]) -> str:
298
+ # Alphanumeric placeholders stay as one token in spaCy. Underscore
299
+ # placeholders can be split and lose their trailing delimiter.
300
+ key = f"ZZPROTECTED{label}{counter['n']}ZZ"
301
  mapping[key] = m.group(0)
302
  counter["n"] += 1
303
  return key
 
309
  out = _sub(_EMAIL, "EMAIL", out)
310
 
311
  def path_repl(m: re.Match[str]) -> str:
312
+ key = f"ZZPROTECTEDPATH{counter['n']}ZZ"
313
  mapping[key] = m.group(0)
314
  counter["n"] += 1
315
  return key
app/engine/normalize/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/app/engine/normalize/__pycache__/__init__.cpython-311.pyc and b/app/engine/normalize/__pycache__/__init__.cpython-311.pyc differ
 
app/engine/orchestrator.py CHANGED
@@ -108,10 +108,9 @@ def _process_sentence(
108
  generated = generate_from_plan(plan, template_id=cand.template_id)
109
  if not generated:
110
  continue
111
- repaired = repair_sentence(generated) if GRAMMAR_FIX_OUTPUT else generated
112
  safety = check_safety(
113
  original,
114
- repaired,
115
  min_meaning=safety_min,
116
  min_confidence=min_confidence,
117
  use_minilm=use_minilm,
@@ -121,6 +120,26 @@ def _process_sentence(
121
  if not safety.ok:
122
  stats.bump(safety.reasons[0] if safety.reasons else "safety")
123
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  conf = min(cand.confidence, safety.confidence + 0.2)
125
  best = SentenceRecord(
126
  index=index,
 
108
  generated = generate_from_plan(plan, template_id=cand.template_id)
109
  if not generated:
110
  continue
 
111
  safety = check_safety(
112
  original,
113
+ generated,
114
  min_meaning=safety_min,
115
  min_confidence=min_confidence,
116
  use_minilm=use_minilm,
 
120
  if not safety.ok:
121
  stats.bump(safety.reasons[0] if safety.reasons else "safety")
122
  continue
123
+ repaired = repair_sentence(generated) if GRAMMAR_FIX_OUTPUT else generated
124
+ if repaired != generated:
125
+ repaired_safety = check_safety(
126
+ original,
127
+ repaired,
128
+ min_meaning=safety_min,
129
+ min_confidence=min_confidence,
130
+ use_minilm=use_minilm,
131
+ protected_entities=plan.slots.entities if plan.slots else (),
132
+ protected_auxiliaries=plan.slots.auxiliaries if plan.slots else (),
133
+ structural_validation=False,
134
+ )
135
+ if not repaired_safety.ok:
136
+ stats.bump(
137
+ repaired_safety.reasons[0]
138
+ if repaired_safety.reasons
139
+ else "grammar_safety"
140
+ )
141
+ continue
142
+ safety = repaired_safety
143
  conf = min(cand.confidence, safety.confidence + 0.2)
144
  best = SentenceRecord(
145
  index=index,
app/engine/parse/__init__.py CHANGED
@@ -120,9 +120,6 @@ def extract_slots(text: str) -> SentenceSlots:
120
  """Parse subject/verb/object/time/place/manner/negation/entities via spaCy."""
121
  raw = (text or "").strip()
122
  slots = SentenceSlots(text=raw, sentence_type=classify_sentence(raw))
123
- if slots.sentence_type not in {"simple_declarative", "compound"}:
124
- slots.reasons.append(f"skip:{slots.sentence_type}")
125
- return slots
126
 
127
  nlp = get_nlp()
128
  if nlp is None:
@@ -136,6 +133,10 @@ def extract_slots(text: str) -> SentenceSlots:
136
  "PERSON", "ORG", "GPE", "LOC", "DATE", "TIME", "MONEY", "PERCENT", "CARDINAL",
137
  }
138
  ]
 
 
 
 
139
 
140
  root = next((t for t in doc if t.dep_ == "ROOT" and t.pos_ in {"VERB", "AUX"}), None)
141
  if root is None:
@@ -243,7 +244,11 @@ def extract_slots(text: str) -> SentenceSlots:
243
  # Duration under for — skip; bare adjunct DATE/TIME — keep
244
  if under and ent.label_ == "TIME":
245
  continue
246
- time_parts.append(ent.text)
 
 
 
 
247
 
248
  for t in doc:
249
  if _is_frontable_adv(t, root):
@@ -289,6 +294,18 @@ def extract_slots(text: str) -> SentenceSlots:
289
  slots.manner = ""
290
  if slots.manner and slots.time and slots.manner.lower() in slots.time.lower():
291
  slots.manner = ""
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  # If place was absorbed into object, keep object but allow place move
294
  if slots.place and slots.object and slots.place in slots.object:
 
120
  """Parse subject/verb/object/time/place/manner/negation/entities via spaCy."""
121
  raw = (text or "").strip()
122
  slots = SentenceSlots(text=raw, sentence_type=classify_sentence(raw))
 
 
 
123
 
124
  nlp = get_nlp()
125
  if nlp is None:
 
133
  "PERSON", "ORG", "GPE", "LOC", "DATE", "TIME", "MONEY", "PERCENT", "CARDINAL",
134
  }
135
  ]
136
+ if slots.sentence_type not in {"simple_declarative", "compound"}:
137
+ slots.auxiliaries = [token.text for token in doc if token.pos_ == "AUX"]
138
+ slots.reasons.append(f"skip:{slots.sentence_type}")
139
+ return slots
140
 
141
  root = next((t for t in doc if t.dep_ == "ROOT" and t.pos_ in {"VERB", "AUX"}), None)
142
  if root is None:
 
244
  # Duration under for — skip; bare adjunct DATE/TIME — keep
245
  if under and ent.label_ == "TIME":
246
  continue
247
+ governing_prep = ent.root.head
248
+ if governing_prep.dep_ == "prep" and governing_prep.pos_ == "ADP":
249
+ time_parts.append(_span_text(_subtree_tokens(governing_prep)))
250
+ else:
251
+ time_parts.append(ent.text)
252
 
253
  for t in doc:
254
  if _is_frontable_adv(t, root):
 
294
  slots.manner = ""
295
  if slots.manner and slots.time and slots.manner.lower() in slots.time.lower():
296
  slots.manner = ""
297
+ residual_adverbs = [
298
+ token
299
+ for token in early_manner
300
+ if token.text.lower() != slots.manner.lower()
301
+ and not (_is_temporal_tok(token) or token.ent_type_ in {"DATE", "TIME"})
302
+ ]
303
+ if residual_adverbs:
304
+ phrase_tokens = sorted(
305
+ {token.i: token for token in [*verb_toks, *residual_adverbs]}.values(),
306
+ key=lambda token: token.i,
307
+ )
308
+ slots.verb_phrase = _span_text(phrase_tokens)
309
 
310
  # If place was absorbed into object, keep object but allow place move
311
  if slots.place and slots.object and slots.place in slots.object:
app/engine/parse/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/app/engine/parse/__pycache__/__init__.cpython-311.pyc and b/app/engine/parse/__pycache__/__init__.cpython-311.pyc differ
 
app/engine/plan/__init__.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
  from app.engine.classify import classify_sentence, is_rewriteable_type
6
  from app.engine.models import RewritePlan, SentenceSlots, TemplateCandidate
7
  from app.engine.parse import extract_slots
8
- from app.engine.templates import rank_templates
9
 
10
 
11
  def build_plan(text: str, *, min_confidence: float = 0.55) -> RewritePlan:
@@ -26,6 +26,19 @@ def build_plan(text: str, *, min_confidence: float = 0.55) -> RewritePlan:
26
  fixed_spans=[],
27
  )
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  if not is_rewriteable_type(kind):
30
  return RewritePlan(
31
  safe=False,
 
5
  from app.engine.classify import classify_sentence, is_rewriteable_type
6
  from app.engine.models import RewritePlan, SentenceSlots, TemplateCandidate
7
  from app.engine.parse import extract_slots
8
+ from app.engine.templates import can_swap_initial_subordinate, rank_templates
9
 
10
 
11
  def build_plan(text: str, *, min_confidence: float = 0.55) -> RewritePlan:
 
26
  fixed_spans=[],
27
  )
28
 
29
+ if kind == "complex" and can_swap_initial_subordinate(raw):
30
+ slots = extract_slots(raw)
31
+ slots.confidence = 0.76
32
+ return RewritePlan(
33
+ safe=True,
34
+ template_id="complex_clause_swap",
35
+ candidates=[TemplateCandidate("complex_clause_swap", 0.76)],
36
+ confidence=0.76,
37
+ slots=slots,
38
+ movable=["subordinate_clause"],
39
+ fixed_spans=list(slots.entities),
40
+ )
41
+
42
  if not is_rewriteable_type(kind):
43
  return RewritePlan(
44
  safe=False,
app/engine/plan/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/app/engine/plan/__pycache__/__init__.cpython-311.pyc and b/app/engine/plan/__pycache__/__init__.cpython-311.pyc differ
 
app/engine/rewrite/__init__.py CHANGED
@@ -5,7 +5,11 @@ from __future__ import annotations
5
  import re
6
 
7
  from app.engine.models import RewritePlan
8
- from app.engine.templates import fill_template, try_because_front
 
 
 
 
9
 
10
 
11
  def _content_tokens(text: str) -> list[str]:
@@ -72,6 +76,8 @@ def generate_from_plan(
72
 
73
  if tid == "because_front":
74
  return try_because_front(raw)
 
 
75
 
76
  if plan.slots is None:
77
  return None
 
5
  import re
6
 
7
  from app.engine.models import RewritePlan
8
+ from app.engine.templates import (
9
+ fill_template,
10
+ try_because_front,
11
+ try_complex_clause_swap,
12
+ )
13
 
14
 
15
  def _content_tokens(text: str) -> list[str]:
 
76
 
77
  if tid == "because_front":
78
  return try_because_front(raw)
79
+ if tid == "complex_clause_swap":
80
+ return try_complex_clause_swap(raw)
81
 
82
  if plan.slots is None:
83
  return None
app/engine/rewrite/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/app/engine/rewrite/__pycache__/__init__.cpython-311.pyc and b/app/engine/rewrite/__pycache__/__init__.cpython-311.pyc differ
 
app/engine/safety/__init__.py CHANGED
@@ -5,6 +5,7 @@ from __future__ import annotations
5
  import re
6
  from collections.abc import Iterable
7
  from dataclasses import dataclass
 
8
 
9
  from app.pipeline.candidate_validator import validate_candidate
10
  from app.pipeline.meaning_safety import polarity_safe
@@ -83,6 +84,7 @@ def check_safety(
83
  use_minilm: bool = False,
84
  protected_entities: Iterable[str] | None = None,
85
  protected_auxiliaries: Iterable[str] | None = None,
 
86
  ) -> SafetyResult:
87
  """Lightweight similarity/safety gate between original and rewrite."""
88
  reasons: list[str] = []
@@ -119,26 +121,33 @@ def check_safety(
119
  if not _tense_aux_ok(o, c, protected_auxiliaries):
120
  reasons.append("tense")
121
 
122
- # Structural reorder may be near-copy in surface ratio; relax max_surface
123
- vr = validate_candidate(
124
- o,
125
- c,
126
- min_meaning=min_meaning if use_minilm else 0.0,
127
- max_surface=0.995,
128
- min_surface=0.20,
129
- )
130
- # Filter validator reasons that fight structural reorder
131
- ignore = {"too_similar", "identical"}
132
- for r in vr.reasons:
133
- if r in ignore:
134
- continue
135
- if r.startswith("meaning:") and not use_minilm:
136
- continue
137
- if r not in reasons:
138
- reasons.append(r)
 
 
 
 
 
 
 
 
139
 
140
  # Optional MiniLM meaning score when enabled
141
- meaning = vr.meaning
142
  if use_minilm:
143
  try:
144
  from app.pipeline.minilm import score_candidate
@@ -151,7 +160,10 @@ def check_safety(
151
  except Exception:
152
  pass
153
 
154
- confidence = max(0.0, min(1.0, (meaning + (1.0 - abs(vr.surface_sim - 0.7))) / 2))
 
 
 
155
  if reasons:
156
  confidence = min(confidence, 0.4)
157
 
@@ -177,7 +189,7 @@ def check_safety(
177
  }
178
  if hard:
179
  ok = False
180
- elif reasons and vr.surface_sim >= 0.35:
181
  # Soft validator noise on reorders — accept if content preserved
182
  ok = True
183
  confidence = max(confidence, 0.6)
@@ -186,6 +198,6 @@ def check_safety(
186
  ok=ok,
187
  confidence=confidence,
188
  reasons=reasons,
189
- surface_sim=vr.surface_sim,
190
  meaning=float(meaning or 0.0),
191
  )
 
5
  import re
6
  from collections.abc import Iterable
7
  from dataclasses import dataclass
8
+ from difflib import SequenceMatcher
9
 
10
  from app.pipeline.candidate_validator import validate_candidate
11
  from app.pipeline.meaning_safety import polarity_safe
 
84
  use_minilm: bool = False,
85
  protected_entities: Iterable[str] | None = None,
86
  protected_auxiliaries: Iterable[str] | None = None,
87
+ structural_validation: bool = True,
88
  ) -> SafetyResult:
89
  """Lightweight similarity/safety gate between original and rewrite."""
90
  reasons: list[str] = []
 
121
  if not _tense_aux_ok(o, c, protected_auxiliaries):
122
  reasons.append("tense")
123
 
124
+ if structural_validation:
125
+ # Structural reorder may be near-copy in surface ratio; relax max_surface
126
+ vr = validate_candidate(
127
+ o,
128
+ c,
129
+ min_meaning=min_meaning if use_minilm else 0.0,
130
+ max_surface=0.995,
131
+ min_surface=0.20,
132
+ )
133
+ # Filter validator reasons that fight structural reorder
134
+ ignore = {"too_similar", "identical"}
135
+ for r in vr.reasons:
136
+ if r in ignore:
137
+ continue
138
+ if r.startswith("meaning:") and not use_minilm:
139
+ continue
140
+ if r not in reasons:
141
+ reasons.append(r)
142
+ surface_sim = vr.surface_sim
143
+ meaning = vr.meaning
144
+ else:
145
+ # Grammar repair may legitimately change inflection or spelling. Recheck
146
+ # only hard invariants while still reporting a lightweight similarity.
147
+ surface_sim = SequenceMatcher(None, o.lower(), c.lower()).ratio()
148
+ meaning = surface_sim
149
 
150
  # Optional MiniLM meaning score when enabled
 
151
  if use_minilm:
152
  try:
153
  from app.pipeline.minilm import score_candidate
 
160
  except Exception:
161
  pass
162
 
163
+ confidence = max(
164
+ 0.0,
165
+ min(1.0, (meaning + (1.0 - abs(surface_sim - 0.7))) / 2),
166
+ )
167
  if reasons:
168
  confidence = min(confidence, 0.4)
169
 
 
189
  }
190
  if hard:
191
  ok = False
192
+ elif reasons and surface_sim >= 0.35:
193
  # Soft validator noise on reorders — accept if content preserved
194
  ok = True
195
  confidence = max(confidence, 0.6)
 
198
  ok=ok,
199
  confidence=confidence,
200
  reasons=reasons,
201
+ surface_sim=surface_sim,
202
  meaning=float(meaning or 0.0),
203
  )
app/engine/safety/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/app/engine/safety/__pycache__/__init__.cpython-311.pyc and b/app/engine/safety/__pycache__/__init__.cpython-311.pyc differ
 
app/engine/templates/__init__.py CHANGED
@@ -3,8 +3,10 @@
3
  from __future__ import annotations
4
 
5
  import re
 
6
 
7
  from app.engine.models import SentenceSlots, TemplateCandidate
 
8
 
9
 
10
  def rank_templates(slots: SentenceSlots) -> list[TemplateCandidate]:
@@ -86,6 +88,63 @@ def _terminal(text: str) -> str:
86
  return "."
87
 
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  def _manner_before_verb(slots: SentenceSlots) -> bool:
90
  return not slots.verb_starts_with_aux
91
 
 
3
  from __future__ import annotations
4
 
5
  import re
6
+ from functools import lru_cache
7
 
8
  from app.engine.models import SentenceSlots, TemplateCandidate
9
+ from app.pipeline.nlp import get_nlp
10
 
11
 
12
  def rank_templates(slots: SentenceSlots) -> list[TemplateCandidate]:
 
88
  return "."
89
 
90
 
91
+ @lru_cache(maxsize=2048)
92
+ def _initial_subordinate_parts(text: str) -> tuple[str, str, str, bool] | None:
93
+ """Return an initial parsed adverbial clause and its main clause."""
94
+ raw = (text or "").strip()
95
+ if "," not in raw:
96
+ return None
97
+ nlp = get_nlp()
98
+ if nlp is None:
99
+ return None
100
+ try:
101
+ doc = nlp(raw)
102
+ except Exception:
103
+ return None
104
+ comma = next((token for token in doc if token.text == ","), None)
105
+ root = next((token for token in doc if token.dep_ == "ROOT"), None)
106
+ if comma is None or root is None or root.i <= comma.i:
107
+ return None
108
+ clause = next(
109
+ (
110
+ token
111
+ for token in doc
112
+ if token.dep_ == "advcl"
113
+ and min(part.i for part in token.subtree) == 0
114
+ and max(part.i for part in token.subtree) < comma.i
115
+ and any(
116
+ part.dep_ == "mark" or part.pos_ == "SCONJ"
117
+ for part in token.subtree
118
+ )
119
+ ),
120
+ None,
121
+ )
122
+ if clause is None:
123
+ return None
124
+
125
+ end = _terminal(raw)
126
+ core = raw[:-1].rstrip() if raw.endswith((".", "!", "?")) else raw
127
+ subordinate, main = (part.strip() for part in core.split(",", 1))
128
+ if len(subordinate.split()) < 3 or len(main.split()) < 3:
129
+ return None
130
+ return subordinate, main, end, doc[0].dep_ == "mark"
131
+
132
+
133
+ def can_swap_initial_subordinate(text: str) -> bool:
134
+ return _initial_subordinate_parts(text) is not None
135
+
136
+
137
+ def try_complex_clause_swap(text: str) -> str | None:
138
+ """Move a parsed initial adverbial clause behind the main clause."""
139
+ parts = _initial_subordinate_parts(text)
140
+ if parts is None:
141
+ return None
142
+ subordinate, main, end, needs_comma = parts
143
+ continuation = subordinate[0].lower() + subordinate[1:]
144
+ separator = ", " if needs_comma else " "
145
+ return f"{_cap(main)}{separator}{continuation}{end}"
146
+
147
+
148
  def _manner_before_verb(slots: SentenceSlots) -> bool:
149
  return not slots.verb_starts_with_aux
150
 
app/engine/templates/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/app/engine/templates/__pycache__/__init__.cpython-311.pyc and b/app/engine/templates/__pycache__/__init__.cpython-311.pyc differ