Upload 107 files
Browse files
app/pipeline/__pycache__/candidate_validator.cpython-311.pyc
CHANGED
|
Binary files a/app/pipeline/__pycache__/candidate_validator.cpython-311.pyc and b/app/pipeline/__pycache__/candidate_validator.cpython-311.pyc differ
|
|
|
app/pipeline/__pycache__/generative.cpython-311.pyc
CHANGED
|
Binary files a/app/pipeline/__pycache__/generative.cpython-311.pyc and b/app/pipeline/__pycache__/generative.cpython-311.pyc differ
|
|
|
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/generative.py
CHANGED
|
@@ -37,7 +37,7 @@ _backend: str | None = None
|
|
| 37 |
_backend_kind: str | None = None # seq2seq | causal
|
| 38 |
|
| 39 |
_SYSTEM_REWRITE = (
|
| 40 |
-
"You rewrite ONE English sentence with clearly
|
| 41 |
"Output exactly one sentence — no second sentence, no preamble. "
|
| 42 |
"Keep EVERY claim and the same polarity. "
|
| 43 |
"Do not flip negatives or antonyms. "
|
|
@@ -46,7 +46,8 @@ _SYSTEM_REWRITE = (
|
|
| 46 |
"Do not add reasons, examples, education, awareness, routines, "
|
| 47 |
"workouts, diets, or new facts. "
|
| 48 |
"Do not summarize or drop content. "
|
| 49 |
-
"Do NOT
|
|
|
|
| 50 |
"Return only the rewritten sentence."
|
| 51 |
)
|
| 52 |
|
|
@@ -56,7 +57,7 @@ _SHARED_RULES = (
|
|
| 56 |
"Do not add reasons, education, awareness, routines, workouts, diets, "
|
| 57 |
"or extra clauses.\n"
|
| 58 |
"Keep multiword terms intact (e.g. fast food, junk food, physical activity).\n"
|
| 59 |
-
"MUST use different wording
|
| 60 |
"Keep polarity. Preserve names, numbers, dates, and quotations. Correct grammar.\n"
|
| 61 |
"Return only the rewritten text."
|
| 62 |
)
|
|
@@ -81,22 +82,22 @@ _TONE_PROMPTS: dict[str, str] = {
|
|
| 81 |
"Paraphrase:"
|
| 82 |
),
|
| 83 |
"Neutral": (
|
| 84 |
-
"Paraphrase this text in clear natural English
|
| 85 |
f"{_SHARED_RULES}\n"
|
| 86 |
"Keep polarity (e.g. unhealthy stays unhealthy; "
|
| 87 |
"don't realize stays a lack of awareness — without adding 'education').\n"
|
| 88 |
-
"Do not return a grammar-only edit of the same
|
| 89 |
"Text:\n{text}\n\n"
|
| 90 |
"Paraphrase:"
|
| 91 |
),
|
| 92 |
}
|
| 93 |
|
| 94 |
_RETRY_PROMPT = (
|
| 95 |
-
"Rewrite this as ONE sentence
|
| 96 |
"but the exact same meaning and polarity. Do not add or remove facts. "
|
| 97 |
"Keep fixed phrases like 'fast food'. "
|
| 98 |
"Do not invent workouts, diets, or routines. Do not add a second sentence. "
|
| 99 |
-
"Do not return a near-copy
|
| 100 |
"Tone: {tone}.\n\n"
|
| 101 |
"Text:\n{text}\n\n"
|
| 102 |
"Rewrite:"
|
|
|
|
| 37 |
_backend_kind: str | None = None # seq2seq | causal
|
| 38 |
|
| 39 |
_SYSTEM_REWRITE = (
|
| 40 |
+
"You rewrite ONE English sentence with clearly DIFFERENT WORDS. "
|
| 41 |
"Output exactly one sentence — no second sentence, no preamble. "
|
| 42 |
"Keep EVERY claim and the same polarity. "
|
| 43 |
"Do not flip negatives or antonyms. "
|
|
|
|
| 46 |
"Do not add reasons, examples, education, awareness, routines, "
|
| 47 |
"workouts, diets, or new facts. "
|
| 48 |
"Do not summarize or drop content. "
|
| 49 |
+
"Do NOT rearrange the same words. Do NOT copy almost verbatim. "
|
| 50 |
+
"Replace verbs, adverbs, and phrases with natural synonyms. "
|
| 51 |
"Return only the rewritten sentence."
|
| 52 |
)
|
| 53 |
|
|
|
|
| 57 |
"Do not add reasons, education, awareness, routines, workouts, diets, "
|
| 58 |
"or extra clauses.\n"
|
| 59 |
"Keep multiword terms intact (e.g. fast food, junk food, physical activity).\n"
|
| 60 |
+
"MUST use different wording — never rearrange the same words only.\n"
|
| 61 |
"Keep polarity. Preserve names, numbers, dates, and quotations. Correct grammar.\n"
|
| 62 |
"Return only the rewritten text."
|
| 63 |
)
|
|
|
|
| 82 |
"Paraphrase:"
|
| 83 |
),
|
| 84 |
"Neutral": (
|
| 85 |
+
"Paraphrase this text in clear natural English using different words.\n"
|
| 86 |
f"{_SHARED_RULES}\n"
|
| 87 |
"Keep polarity (e.g. unhealthy stays unhealthy; "
|
| 88 |
"don't realize stays a lack of awareness — without adding 'education').\n"
|
| 89 |
+
"Do not return a grammar-only edit or a shuffle of the same words.\n\n"
|
| 90 |
"Text:\n{text}\n\n"
|
| 91 |
"Paraphrase:"
|
| 92 |
),
|
| 93 |
}
|
| 94 |
|
| 95 |
_RETRY_PROMPT = (
|
| 96 |
+
"Rewrite this as ONE sentence using DIFFERENT WORDS (not a word shuffle) "
|
| 97 |
"but the exact same meaning and polarity. Do not add or remove facts. "
|
| 98 |
"Keep fixed phrases like 'fast food'. "
|
| 99 |
"Do not invent workouts, diets, or routines. Do not add a second sentence. "
|
| 100 |
+
"Do not return a near-copy, grammar-only edit, or rearranged same words. "
|
| 101 |
"Tone: {tone}.\n\n"
|
| 102 |
"Text:\n{text}\n\n"
|
| 103 |
"Rewrite:"
|
app/pipeline/orchestrator.py
CHANGED
|
@@ -177,7 +177,7 @@ def _paraphrase_hard_ok(source_unit: str, trial: str) -> bool:
|
|
| 177 |
def _is_true_rewrite(source_unit: str, candidate: str) -> bool:
|
| 178 |
"""True rewrite = different wording, not grammar-fix or clause shuffle.
|
| 179 |
|
| 180 |
-
Pure reorder
|
| 181 |
"""
|
| 182 |
src = (source_unit or "").strip()
|
| 183 |
cand = (candidate or "").strip()
|
|
@@ -185,18 +185,32 @@ def _is_true_rewrite(source_unit: str, candidate: str) -> bool:
|
|
| 185 |
return False
|
| 186 |
if cand.lower().rstrip(".!?") == src.lower().rstrip(".!?"):
|
| 187 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
if not _paraphrase_hard_ok(src, cand):
|
| 189 |
return False
|
| 190 |
sim = _similarity_ratio(src, cand)
|
| 191 |
jacc = _stem_jaccard(src, cand)
|
| 192 |
-
#
|
| 193 |
-
if jacc >= 0.
|
| 194 |
return False
|
| 195 |
-
if sim > 0.
|
| 196 |
return False
|
| 197 |
return True
|
| 198 |
|
| 199 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
def _safe_paraphrase_trial(source_unit: str, trial: str) -> tuple[bool, float]:
|
| 201 |
"""Return (is_true_rewrite, surface_sim)."""
|
| 202 |
t = (trial or "").strip()
|
|
@@ -212,7 +226,12 @@ def _rewrite_score(source_unit: str, candidate: str) -> float:
|
|
| 212 |
"""Higher = more different while still usable (lower sim + lower stem overlap)."""
|
| 213 |
sim = _similarity_ratio(source_unit, candidate)
|
| 214 |
jacc = _stem_jaccard(source_unit, candidate)
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
|
| 218 |
# Phrase-level meaning-preserving rewrites (change wording, not just order).
|
|
@@ -254,6 +273,18 @@ _PHRASE_REWRITES: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
| 254 |
(re.compile(r"\bkeeps our body fit\b", re.I), "helps the body stay fit"),
|
| 255 |
(re.compile(r"\bfind it harder to\b", re.I), "struggle more to"),
|
| 256 |
(re.compile(r"\binstead of\b", re.I), "rather than"),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
)
|
| 258 |
|
| 259 |
|
|
@@ -303,9 +334,10 @@ def _esl_structural_variants(text: str) -> list[str]:
|
|
| 303 |
if m:
|
| 304 |
topic = m.group(1).strip().rstrip(".!?")
|
| 305 |
if topic:
|
| 306 |
-
|
| 307 |
-
out.append(f"
|
| 308 |
-
out.append(f"{_cap_first(topic)}
|
|
|
|
| 309 |
|
| 310 |
m = re.match(r"^Without enough\s+(\w+),\s*(.+)$", core, flags=re.I)
|
| 311 |
if m:
|
|
@@ -382,8 +414,8 @@ _SAFE_SURFACE_SWAPS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
| 382 |
(re.compile(r"\bhelp communities\b", re.I), "support communities"),
|
| 383 |
)
|
| 384 |
|
| 385 |
-
_VISIBLE_TARGET_SIM = 0.
|
| 386 |
-
_LM_SHIP_MAX_SIM = 0.
|
| 387 |
|
| 388 |
|
| 389 |
def _surface_humanize_variants(text: str, rng: random.Random) -> list[str]:
|
|
@@ -891,7 +923,11 @@ def _hybrid_rewrite(
|
|
| 891 |
by_para.setdefault(unit.paragraph_index, []).append(cand_u)
|
| 892 |
continue
|
| 893 |
safe = _safe_classical_unit(src_u, cand_u)
|
| 894 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
| 895 |
stats.reverted_source += 1
|
| 896 |
else:
|
| 897 |
stats.classical_kept += 1
|
|
|
|
| 177 |
def _is_true_rewrite(source_unit: str, candidate: str) -> bool:
|
| 178 |
"""True rewrite = different wording, not grammar-fix or clause shuffle.
|
| 179 |
|
| 180 |
+
Pure reorder of the same words must fail (e.g. Ram/school shuffle example).
|
| 181 |
"""
|
| 182 |
src = (source_unit or "").strip()
|
| 183 |
cand = (candidate or "").strip()
|
|
|
|
| 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 |
+
# Near-identical stem set → not enough wording change
|
| 198 |
+
if jacc >= 0.88:
|
| 199 |
return False
|
| 200 |
+
if sim > 0.88:
|
| 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()
|
|
|
|
| 226 |
"""Higher = more different while still usable (lower sim + lower stem overlap)."""
|
| 227 |
sim = _similarity_ratio(source_unit, candidate)
|
| 228 |
jacc = _stem_jaccard(source_unit, candidate)
|
| 229 |
+
bag_bonus = (
|
| 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).
|
|
|
|
| 273 |
(re.compile(r"\bkeeps our body fit\b", re.I), "helps the body stay fit"),
|
| 274 |
(re.compile(r"\bfind it harder to\b", re.I), "struggle more to"),
|
| 275 |
(re.compile(r"\binstead of\b", re.I), "rather than"),
|
| 276 |
+
# General complete-rewrite lexical swaps (not reorder)
|
| 277 |
+
(re.compile(r"\bwent to\b", re.I), "headed to"),
|
| 278 |
+
(re.compile(r"\bgo to\b", re.I), "head to"),
|
| 279 |
+
(re.compile(r"\bgoes to\b", re.I), "heads to"),
|
| 280 |
+
(re.compile(r"\bhappily\b", re.I), "cheerfully"),
|
| 281 |
+
(re.compile(r"\bquickly\b", re.I), "in a hurry"),
|
| 282 |
+
(re.compile(r"\bslowly\b", re.I), "at a slow pace"),
|
| 283 |
+
(re.compile(r"\byesterday\b", re.I), "the day before"),
|
| 284 |
+
(re.compile(r"\btomorrow\b", re.I), "the next day"),
|
| 285 |
+
(re.compile(r"\bstarted\b", re.I), "began"),
|
| 286 |
+
(re.compile(r"\bfinished\b", re.I), "completed"),
|
| 287 |
+
(re.compile(r"\bhelped\b", re.I), "assisted"),
|
| 288 |
)
|
| 289 |
|
| 290 |
|
|
|
|
| 334 |
if m:
|
| 335 |
topic = m.group(1).strip().rstrip(".!?")
|
| 336 |
if topic:
|
| 337 |
+
# Keep topic head "thing" so topic_anchor passes; change other wording
|
| 338 |
+
out.append(f"{_cap_first(topic)} remains an important thing{end}")
|
| 339 |
+
out.append(f"{_cap_first(topic)} is still an important thing{end}")
|
| 340 |
+
out.append(f"A further important thing is {_lower_first(topic)}{end}")
|
| 341 |
|
| 342 |
m = re.match(r"^Without enough\s+(\w+),\s*(.+)$", core, flags=re.I)
|
| 343 |
if m:
|
|
|
|
| 414 |
(re.compile(r"\bhelp communities\b", re.I), "support communities"),
|
| 415 |
)
|
| 416 |
|
| 417 |
+
_VISIBLE_TARGET_SIM = 0.82
|
| 418 |
+
_LM_SHIP_MAX_SIM = 0.85
|
| 419 |
|
| 420 |
|
| 421 |
def _surface_humanize_variants(text: str, rng: random.Random) -> list[str]:
|
|
|
|
| 923 |
by_para.setdefault(unit.paragraph_index, []).append(cand_u)
|
| 924 |
continue
|
| 925 |
safe = _safe_classical_unit(src_u, cand_u)
|
| 926 |
+
if strength >= 1 and len(src_u.split()) >= 4:
|
| 927 |
+
safe = _require_visible_rewrite(
|
| 928 |
+
src_u, safe, tone, rng, target_sim=_VISIBLE_TARGET_SIM
|
| 929 |
+
)
|
| 930 |
+
if _similarity_ratio(src_u, safe) > 0.92:
|
| 931 |
stats.reverted_source += 1
|
| 932 |
else:
|
| 933 |
stats.classical_kept += 1
|
scripts/test_phase_a_guards.py
CHANGED
|
@@ -152,6 +152,35 @@ def test_exercise_tv_must_not_be_reorder_only() -> None:
|
|
| 152 |
print(" stats", stats)
|
| 153 |
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
def test_rejects_hollow_ons_bills() -> None:
|
| 156 |
src = (
|
| 157 |
"People should save money for emergencies instead of spending every paycheck."
|
|
@@ -464,6 +493,7 @@ if __name__ == "__main__":
|
|
| 464 |
test_skip_short_and_light()
|
| 465 |
test_sleep_style_must_visibly_rewrite()
|
| 466 |
test_exercise_tv_must_not_be_reorder_only()
|
|
|
|
| 467 |
test_rejects_hollow_ons_bills()
|
| 468 |
test_assembly_preserves_unit_count()
|
| 469 |
test_live_bug_patterns_rejected()
|
|
|
|
| 152 |
print(" stats", stats)
|
| 153 |
|
| 154 |
|
| 155 |
+
def test_complete_wording_rewrite_not_shuffle() -> None:
|
| 156 |
+
"""Complete rewrite = new words; same-word shuffle must not count."""
|
| 157 |
+
import random
|
| 158 |
+
|
| 159 |
+
from app.pipeline.orchestrator import (
|
| 160 |
+
_content_word_bag,
|
| 161 |
+
_is_true_rewrite,
|
| 162 |
+
_require_visible_rewrite,
|
| 163 |
+
_similarity_ratio,
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
src = "Ram went to school yesterday happily."
|
| 167 |
+
shuffle = "Yesterday, Ram happily went to school."
|
| 168 |
+
assert not _is_true_rewrite(src, shuffle), "word shuffle must fail"
|
| 169 |
+
assert _content_word_bag(src) == _content_word_bag(shuffle)
|
| 170 |
+
|
| 171 |
+
out = _require_visible_rewrite(
|
| 172 |
+
src, src, "Neutral", random.Random(5), target_sim=0.82
|
| 173 |
+
)
|
| 174 |
+
assert _is_true_rewrite(src, out), out
|
| 175 |
+
assert _content_word_bag(src) != _content_word_bag(out)
|
| 176 |
+
assert "ram" in out.lower()
|
| 177 |
+
assert "school" in out.lower()
|
| 178 |
+
# Should use different wording (headed/cheerfully/day before), not shuffle
|
| 179 |
+
assert "yesterday, ram happily went" not in out.lower()
|
| 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:
|
| 185 |
src = (
|
| 186 |
"People should save money for emergencies instead of spending every paycheck."
|
|
|
|
| 493 |
test_skip_short_and_light()
|
| 494 |
test_sleep_style_must_visibly_rewrite()
|
| 495 |
test_exercise_tv_must_not_be_reorder_only()
|
| 496 |
+
test_complete_wording_rewrite_not_shuffle()
|
| 497 |
test_rejects_hollow_ons_bills()
|
| 498 |
test_assembly_preserves_unit_count()
|
| 499 |
test_live_bug_patterns_rejected()
|