Upload 107 files
Browse files
app/pipeline/__pycache__/orchestrator.cpython-311.pyc
CHANGED
|
Binary files a/app/pipeline/__pycache__/orchestrator.cpython-311.pyc and b/app/pipeline/__pycache__/orchestrator.cpython-311.pyc differ
|
|
|
app/pipeline/orchestrator.py
CHANGED
|
@@ -174,43 +174,51 @@ def _paraphrase_hard_ok(source_unit: str, trial: str) -> bool:
|
|
| 174 |
return True
|
| 175 |
|
| 176 |
|
| 177 |
-
def
|
| 178 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
src = (source_unit or "").strip()
|
| 183 |
cand = (candidate or "").strip()
|
| 184 |
if not src or not cand:
|
| 185 |
return False
|
| 186 |
if cand.lower().rstrip(".!?") == src.lower().rstrip(".!?"):
|
| 187 |
return False
|
| 188 |
-
# Same content-word bag → reorder only (not a complete rewrite)
|
| 189 |
-
src_bag = _content_word_bag(src)
|
| 190 |
-
cand_bag = _content_word_bag(cand)
|
| 191 |
-
if src_bag and src_bag == cand_bag:
|
| 192 |
-
return False
|
| 193 |
if not _paraphrase_hard_ok(src, cand):
|
| 194 |
return False
|
| 195 |
sim = _similarity_ratio(src, cand)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
jacc = _stem_jaccard(src, cand)
|
| 197 |
-
|
| 198 |
-
if jacc >= 0.88:
|
| 199 |
return False
|
| 200 |
-
if sim > 0.
|
| 201 |
return False
|
| 202 |
return True
|
| 203 |
|
| 204 |
|
| 205 |
-
def _content_word_bag(text: str) -> frozenset[str]:
|
| 206 |
-
"""Lowercased content words (len≥4), for detecting same-word shuffles."""
|
| 207 |
-
return frozenset(
|
| 208 |
-
w.strip("'")
|
| 209 |
-
for w in re.findall(r"[a-zA-Z']+", (text or "").lower())
|
| 210 |
-
if len(w.strip("'")) >= 4
|
| 211 |
-
)
|
| 212 |
-
|
| 213 |
-
|
| 214 |
def _safe_paraphrase_trial(source_unit: str, trial: str) -> tuple[bool, float]:
|
| 215 |
"""Return (is_true_rewrite, surface_sim)."""
|
| 216 |
t = (trial or "").strip()
|
|
@@ -223,15 +231,13 @@ def _safe_paraphrase_trial(source_unit: str, trial: str) -> tuple[bool, float]:
|
|
| 223 |
|
| 224 |
|
| 225 |
def _rewrite_score(source_unit: str, candidate: str) -> float:
|
| 226 |
-
"""
|
| 227 |
sim = _similarity_ratio(source_unit, candidate)
|
|
|
|
|
|
|
|
|
|
| 228 |
jacc = _stem_jaccard(source_unit, candidate)
|
| 229 |
-
|
| 230 |
-
0.15
|
| 231 |
-
if _content_word_bag(source_unit) != _content_word_bag(candidate)
|
| 232 |
-
else 0.0
|
| 233 |
-
)
|
| 234 |
-
return (1.0 - sim) + 0.85 * (1.0 - jacc) + bag_bonus
|
| 235 |
|
| 236 |
|
| 237 |
# Phrase-level meaning-preserving rewrites (change wording, not just order).
|
|
@@ -288,6 +294,104 @@ _PHRASE_REWRITES: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
| 288 |
)
|
| 289 |
|
| 290 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
def _apply_phrase_rewrites(text: str, *, max_swaps: int = 4) -> list[str]:
|
| 292 |
"""Build increasingly rewritten phrase variants."""
|
| 293 |
out: list[str] = []
|
|
@@ -419,12 +523,13 @@ _LM_SHIP_MAX_SIM = 0.85
|
|
| 419 |
|
| 420 |
|
| 421 |
def _surface_humanize_variants(text: str, rng: random.Random) -> list[str]:
|
| 422 |
-
"""
|
| 423 |
out: list[str] = []
|
| 424 |
base = (text or "").strip()
|
| 425 |
if not base:
|
| 426 |
return out
|
| 427 |
|
|
|
|
| 428 |
out.extend(_apply_phrase_rewrites(base))
|
| 429 |
out.extend(_esl_structural_variants(base))
|
| 430 |
|
|
@@ -483,6 +588,9 @@ def _visible_unit_paraphrase(
|
|
| 483 |
structured = base
|
| 484 |
|
| 485 |
trials: list[str] = []
|
|
|
|
|
|
|
|
|
|
| 486 |
for seed in (base, structured):
|
| 487 |
trials.extend(_apply_phrase_rewrites(seed))
|
| 488 |
trials.extend(_esl_structural_variants(seed))
|
|
|
|
| 174 |
return True
|
| 175 |
|
| 176 |
|
| 177 |
+
def _content_word_bag(text: str) -> frozenset[str]:
|
| 178 |
+
"""Lowercased content words (len≥4)."""
|
| 179 |
+
return frozenset(
|
| 180 |
+
w.strip("'")
|
| 181 |
+
for w in re.findall(r"[a-zA-Z']+", (text or "").lower())
|
| 182 |
+
if len(w.strip("'")) >= 4
|
| 183 |
+
)
|
| 184 |
|
| 185 |
+
|
| 186 |
+
def _token_seq(text: str) -> tuple[str, ...]:
|
| 187 |
+
return tuple(re.findall(r"[a-zA-Z']+", (text or "").lower()))
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def _is_structural_reorder(source: str, candidate: str) -> bool:
|
| 191 |
+
"""Same content words, different order (user-expected sentence reorder)."""
|
| 192 |
+
src_bag = _content_word_bag(source)
|
| 193 |
+
cand_bag = _content_word_bag(candidate)
|
| 194 |
+
if not src_bag or src_bag != cand_bag:
|
| 195 |
+
return False
|
| 196 |
+
return _token_seq(source) != _token_seq(candidate)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _is_true_rewrite(source_unit: str, candidate: str) -> bool:
|
| 200 |
+
"""Accept structural reorder OR lexical wording change — not identity."""
|
| 201 |
src = (source_unit or "").strip()
|
| 202 |
cand = (candidate or "").strip()
|
| 203 |
if not src or not cand:
|
| 204 |
return False
|
| 205 |
if cand.lower().rstrip(".!?") == src.lower().rstrip(".!?"):
|
| 206 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
if not _paraphrase_hard_ok(src, cand):
|
| 208 |
return False
|
| 209 |
sim = _similarity_ratio(src, cand)
|
| 210 |
+
# Preferred path: reorder same words into a new structure
|
| 211 |
+
if _is_structural_reorder(src, cand):
|
| 212 |
+
return sim < 0.995
|
| 213 |
+
# Lexical path: different content words
|
| 214 |
jacc = _stem_jaccard(src, cand)
|
| 215 |
+
if jacc >= 0.92:
|
|
|
|
| 216 |
return False
|
| 217 |
+
if sim > 0.90:
|
| 218 |
return False
|
| 219 |
return True
|
| 220 |
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
def _safe_paraphrase_trial(source_unit: str, trial: str) -> tuple[bool, float]:
|
| 223 |
"""Return (is_true_rewrite, surface_sim)."""
|
| 224 |
t = (trial or "").strip()
|
|
|
|
| 231 |
|
| 232 |
|
| 233 |
def _rewrite_score(source_unit: str, candidate: str) -> float:
|
| 234 |
+
"""Prefer structural reorder; otherwise reward lexical difference."""
|
| 235 |
sim = _similarity_ratio(source_unit, candidate)
|
| 236 |
+
if _is_structural_reorder(source_unit, candidate):
|
| 237 |
+
# Strong preference for clear reorders (user expectation)
|
| 238 |
+
return 1.25 + (1.0 - sim)
|
| 239 |
jacc = _stem_jaccard(source_unit, candidate)
|
| 240 |
+
return (1.0 - sim) + 0.85 * (1.0 - jacc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
|
| 243 |
# Phrase-level meaning-preserving rewrites (change wording, not just order).
|
|
|
|
| 294 |
)
|
| 295 |
|
| 296 |
|
| 297 |
+
def _sentence_reorder_variants(text: str) -> list[str]:
|
| 298 |
+
"""Structural reorders that keep the same words (user-expected style).
|
| 299 |
+
|
| 300 |
+
Example:
|
| 301 |
+
Ram went to school yesterday happily.
|
| 302 |
+
→ Yesterday, Ram happily went to school.
|
| 303 |
+
"""
|
| 304 |
+
out: list[str] = []
|
| 305 |
+
base = (text or "").strip()
|
| 306 |
+
if not base:
|
| 307 |
+
return out
|
| 308 |
+
end_m = re.search(r"[.!?]+$", base)
|
| 309 |
+
end = end_m.group(0) if end_m else "."
|
| 310 |
+
core = base[: end_m.start()] if end_m else base
|
| 311 |
+
|
| 312 |
+
# Subj + motion-verb + to + place + time + manner-ly
|
| 313 |
+
m = re.match(
|
| 314 |
+
r"^(?P<subj>[A-Za-z][\w'-]*)\s+"
|
| 315 |
+
r"(?P<verb>went|go|goes|walked|ran|drove|came|moved)\s+to\s+"
|
| 316 |
+
r"(?P<place>.+?)\s+"
|
| 317 |
+
r"(?P<time>yesterday|today|tomorrow|earlier|later|recently)\s+"
|
| 318 |
+
r"(?P<manner>\w+ly)$",
|
| 319 |
+
core,
|
| 320 |
+
flags=re.I,
|
| 321 |
+
)
|
| 322 |
+
if m:
|
| 323 |
+
subj = m.group("subj")
|
| 324 |
+
verb = m.group("verb")
|
| 325 |
+
place = m.group("place").strip()
|
| 326 |
+
time = m.group("time")
|
| 327 |
+
manner = m.group("manner")
|
| 328 |
+
# Preferred: Yesterday, Ram happily went to school.
|
| 329 |
+
out.append(
|
| 330 |
+
f"{_cap_first(time)}, {subj} {manner} {verb.lower()} to {place}{end}"
|
| 331 |
+
)
|
| 332 |
+
out.append(
|
| 333 |
+
f"{_cap_first(time)}, {subj} {verb.lower()} to {place} {manner}{end}"
|
| 334 |
+
)
|
| 335 |
+
out.append(f"{subj} {manner} {verb.lower()} to {place} {time}{end}")
|
| 336 |
+
|
| 337 |
+
# Trailing time → front: "... yesterday."
|
| 338 |
+
m = re.match(
|
| 339 |
+
r"^(?P<main>.+?)\s+"
|
| 340 |
+
r"(?P<time>yesterday|today|tomorrow|earlier|later|recently|now)"
|
| 341 |
+
r"$",
|
| 342 |
+
core,
|
| 343 |
+
flags=re.I,
|
| 344 |
+
)
|
| 345 |
+
if m and len(m.group("main").split()) >= 3:
|
| 346 |
+
main = m.group("main").rstrip(" ,")
|
| 347 |
+
time = m.group("time")
|
| 348 |
+
out.append(f"{_cap_first(time)}, {_lower_first(main)}{end}")
|
| 349 |
+
|
| 350 |
+
# Trailing manner -ly → before main verb (simple Subj ... ly)
|
| 351 |
+
m = re.match(
|
| 352 |
+
r"^(?P<subj>[A-Za-z][\w'-]*)\s+(?P<mid>.+?)\s+(?P<manner>\w+ly)$",
|
| 353 |
+
core,
|
| 354 |
+
flags=re.I,
|
| 355 |
+
)
|
| 356 |
+
if m and " to " in f" {m.group('mid')} ":
|
| 357 |
+
subj, mid, manner = m.group("subj"), m.group("mid").strip(), m.group("manner")
|
| 358 |
+
# Insert manner after subject
|
| 359 |
+
out.append(f"{subj} {manner} {mid}{end}")
|
| 360 |
+
|
| 361 |
+
# "Y instead of X" → "Instead of X, Y"
|
| 362 |
+
m = re.match(
|
| 363 |
+
r"^(?P<main>.+?)\s+instead of\s+(?P<alt>.+)$",
|
| 364 |
+
core,
|
| 365 |
+
flags=re.I,
|
| 366 |
+
)
|
| 367 |
+
if m:
|
| 368 |
+
main = m.group("main").rstrip(" ,")
|
| 369 |
+
alt = m.group("alt").rstrip(" .!?")
|
| 370 |
+
out.append(f"Instead of {alt}, {_lower_first(main)}{end}")
|
| 371 |
+
|
| 372 |
+
# "X because Y" → "Because Y, X"
|
| 373 |
+
m = re.match(
|
| 374 |
+
r"^(?P<main>.+?)\s+because\s+(?P<reason>.+)$",
|
| 375 |
+
core,
|
| 376 |
+
flags=re.I,
|
| 377 |
+
)
|
| 378 |
+
if m and len(m.group("main").split()) >= 3:
|
| 379 |
+
main = m.group("main").rstrip(" ,")
|
| 380 |
+
reason = m.group("reason").rstrip(" .!?")
|
| 381 |
+
out.append(f"Because {reason}, {_lower_first(main)}{end}")
|
| 382 |
+
|
| 383 |
+
# Dedup
|
| 384 |
+
seen: set[str] = set()
|
| 385 |
+
uniq: list[str] = []
|
| 386 |
+
for t in out:
|
| 387 |
+
key = t.strip().lower()
|
| 388 |
+
if not key or key in seen:
|
| 389 |
+
continue
|
| 390 |
+
seen.add(key)
|
| 391 |
+
uniq.append(t.strip())
|
| 392 |
+
return uniq
|
| 393 |
+
|
| 394 |
+
|
| 395 |
def _apply_phrase_rewrites(text: str, *, max_swaps: int = 4) -> list[str]:
|
| 396 |
"""Build increasingly rewritten phrase variants."""
|
| 397 |
out: list[str] = []
|
|
|
|
| 523 |
|
| 524 |
|
| 525 |
def _surface_humanize_variants(text: str, rng: random.Random) -> list[str]:
|
| 526 |
+
"""Reorder + phrase + light surface variants."""
|
| 527 |
out: list[str] = []
|
| 528 |
base = (text or "").strip()
|
| 529 |
if not base:
|
| 530 |
return out
|
| 531 |
|
| 532 |
+
out.extend(_sentence_reorder_variants(base))
|
| 533 |
out.extend(_apply_phrase_rewrites(base))
|
| 534 |
out.extend(_esl_structural_variants(base))
|
| 535 |
|
|
|
|
| 588 |
structured = base
|
| 589 |
|
| 590 |
trials: list[str] = []
|
| 591 |
+
# Prefer structural reorder first (user expectation)
|
| 592 |
+
for seed in (base, structured):
|
| 593 |
+
trials.extend(_sentence_reorder_variants(seed))
|
| 594 |
for seed in (base, structured):
|
| 595 |
trials.extend(_apply_phrase_rewrites(seed))
|
| 596 |
trials.extend(_esl_structural_variants(seed))
|
scripts/__pycache__/test_phase_a_guards.cpython-311.pyc
CHANGED
|
Binary files a/scripts/__pycache__/test_phase_a_guards.cpython-311.pyc and b/scripts/__pycache__/test_phase_a_guards.cpython-311.pyc differ
|
|
|
scripts/test_phase_a_guards.py
CHANGED
|
@@ -106,7 +106,7 @@ def test_sleep_style_must_visibly_rewrite() -> None:
|
|
| 106 |
|
| 107 |
|
| 108 |
def test_exercise_tv_must_not_be_reorder_only() -> None:
|
| 109 |
-
"""
|
| 110 |
import random
|
| 111 |
from unittest.mock import patch
|
| 112 |
|
|
@@ -130,17 +130,9 @@ def test_exercise_tv_must_not_be_reorder_only() -> None:
|
|
| 130 |
):
|
| 131 |
out, stats, _ = _hybrid_rewrite(g, "Neutral", 1, rng, can_generate=True)
|
| 132 |
low = out.lower()
|
| 133 |
-
assert
|
| 134 |
-
# Not the weak template / reorder-only patterns users rejected
|
| 135 |
-
assert "very important part of activities that keeps" not in low
|
| 136 |
-
assert not (
|
| 137 |
-
"instead of doing physical activities, unfortunately" in low
|
| 138 |
-
and "watching television" in low
|
| 139 |
-
)
|
| 140 |
-
# Claims should survive in some form
|
| 141 |
assert "tv" in low or "television" in low
|
| 142 |
assert "fit" in low or "exercise" in low or "work out" in low or "workout" in low
|
| 143 |
-
# Spot-check units are true rewrites vs grammar source units
|
| 144 |
from app.pipeline.alignment import iter_source_units
|
| 145 |
|
| 146 |
src_units = [u for _i, u in iter_source_units(g)]
|
|
@@ -150,35 +142,37 @@ def test_exercise_tv_must_not_be_reorder_only() -> None:
|
|
| 150 |
assert true_n >= max(1, len(src_units) - 1), (true_n, out)
|
| 151 |
print("exercise/TV rewrite OK:", out)
|
| 152 |
print(" stats", stats)
|
|
|
|
| 153 |
|
| 154 |
|
| 155 |
def test_complete_wording_rewrite_not_shuffle() -> None:
|
| 156 |
-
"""
|
| 157 |
import random
|
| 158 |
|
| 159 |
from app.pipeline.orchestrator import (
|
| 160 |
-
|
| 161 |
_is_true_rewrite,
|
| 162 |
_require_visible_rewrite,
|
| 163 |
-
|
| 164 |
)
|
| 165 |
|
| 166 |
src = "Ram went to school yesterday happily."
|
| 167 |
-
|
| 168 |
-
assert
|
| 169 |
-
assert
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
out = _require_visible_rewrite(
|
| 172 |
-
src, src, "Neutral", random.Random(5), target_sim=0.
|
| 173 |
)
|
| 174 |
assert _is_true_rewrite(src, out), out
|
| 175 |
-
assert
|
| 176 |
-
|
| 177 |
-
assert "
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
assert _similarity_ratio(src, out) < 0.90
|
| 181 |
-
print("Ram complete wording rewrite OK:", out)
|
| 182 |
|
| 183 |
|
| 184 |
def test_rejects_hollow_ons_bills() -> None:
|
|
|
|
| 106 |
|
| 107 |
|
| 108 |
def test_exercise_tv_must_not_be_reorder_only() -> None:
|
| 109 |
+
"""Exercise/TV sample should change via reorder and/or wording; keep claims."""
|
| 110 |
import random
|
| 111 |
from unittest.mock import patch
|
| 112 |
|
|
|
|
| 130 |
):
|
| 131 |
out, stats, _ = _hybrid_rewrite(g, "Neutral", 1, rng, can_generate=True)
|
| 132 |
low = out.lower()
|
| 133 |
+
assert out.strip() != g.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
assert "tv" in low or "television" in low
|
| 135 |
assert "fit" in low or "exercise" in low or "work out" in low or "workout" in low
|
|
|
|
| 136 |
from app.pipeline.alignment import iter_source_units
|
| 137 |
|
| 138 |
src_units = [u for _i, u in iter_source_units(g)]
|
|
|
|
| 142 |
assert true_n >= max(1, len(src_units) - 1), (true_n, out)
|
| 143 |
print("exercise/TV rewrite OK:", out)
|
| 144 |
print(" stats", stats)
|
| 145 |
+
_ = _similarity_ratio # kept for debugging imports
|
| 146 |
|
| 147 |
|
| 148 |
def test_complete_wording_rewrite_not_shuffle() -> None:
|
| 149 |
+
"""User expects structural reorder, e.g. time/manner fronting."""
|
| 150 |
import random
|
| 151 |
|
| 152 |
from app.pipeline.orchestrator import (
|
| 153 |
+
_is_structural_reorder,
|
| 154 |
_is_true_rewrite,
|
| 155 |
_require_visible_rewrite,
|
| 156 |
+
_sentence_reorder_variants,
|
| 157 |
)
|
| 158 |
|
| 159 |
src = "Ram went to school yesterday happily."
|
| 160 |
+
expected_style = "Yesterday, Ram happily went to school."
|
| 161 |
+
assert _is_true_rewrite(src, expected_style), "reorder must count as rewrite"
|
| 162 |
+
assert _is_structural_reorder(src, expected_style)
|
| 163 |
+
|
| 164 |
+
variants = _sentence_reorder_variants(src)
|
| 165 |
+
assert any("yesterday, ram happily went to school" in v.lower() for v in variants), variants
|
| 166 |
|
| 167 |
out = _require_visible_rewrite(
|
| 168 |
+
src, src, "Neutral", random.Random(5), target_sim=0.95
|
| 169 |
)
|
| 170 |
assert _is_true_rewrite(src, out), out
|
| 171 |
+
assert "ram" in out.lower() and "school" in out.lower()
|
| 172 |
+
# Prefer the time-fronted reorder, not synonym thrash
|
| 173 |
+
assert "yesterday" in out.lower()
|
| 174 |
+
assert "happily" in out.lower() or "went" in out.lower()
|
| 175 |
+
print("Ram sentence reorder OK:", out)
|
|
|
|
|
|
|
| 176 |
|
| 177 |
|
| 178 |
def test_rejects_hollow_ons_bills() -> None:
|