any2human / scripts /test_phase_a_guards.py
idnameraj's picture
Upload 142 files
8f6d79d verified
Raw
History Blame Contribute Delete
25.3 kB
"""Phase A guards: shape, invention, no dropped units."""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from app.pipeline.alignment import iter_source_units
from app.pipeline.candidate_validator import validate_candidate
from app.pipeline.grammar_fix import correct_text
from app.pipeline.orchestrator import _accept_lm_unit, _looks_needs_lm, rewrite_text
def test_rejects_multi_sentence_invention() -> None:
src = (
"Eating fast foods is becoming very common and people don't realize "
"how much it affects their health."
)
bad = (
"Fast food is becoming increasingly prevalent, and many individuals aren't "
"aware of its detrimental impact on their health due to a lack of awareness "
"and education."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "invention" in v.reasons or "shape" in v.reasons or "length" in v.reasons
ok, reasons = _accept_lm_unit(src, bad)
assert not ok
print("multi-sentence/invention reject OK:", v.reasons, reasons)
def test_rejects_two_sentence_for_one() -> None:
src = "Exercise is one of the most important activities that keeps our body fit."
bad = (
"In addition to our exercise routines, keeping our bodies fit is also crucial. "
"Exercise remains essential."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "shape" in v.reasons
print("shape reject OK")
def test_skip_short_and_light() -> None:
assert not _looks_needs_lm("Yes it is.", 1) # <4 words
assert not _looks_needs_lm(
"Nowadays many people are living an unhealthy life because they don't have enough time.",
0,
)
assert _looks_needs_lm(
"Nowadays many people are living an unhealthy life because they don't have enough time.",
1,
)
# Short claims must still go to LM at Normal (was ≥8 — grammar-only bug)
assert _looks_needs_lm("Another important thing is sleeping.", 1)
assert _looks_needs_lm("Without enough sleep, the brain works poorly.", 1)
print("skip heuristics OK")
def test_sleep_style_must_visibly_rewrite() -> None:
"""User expectation: not grammar-only; wording must clearly change."""
import random
from unittest.mock import patch
from app.pipeline.orchestrator import (
_is_true_rewrite,
_require_visible_rewrite,
_similarity_ratio,
_stem_jaccard,
)
rng = random.Random(7)
src = "Another important thing is sleeping."
out = _require_visible_rewrite(src, src, "Neutral", rng, target_sim=0.85)
assert _is_true_rewrite(src, out), out
assert _similarity_ratio(src, out) < 0.90
assert _stem_jaccard(src, out) < 0.92
assert "sleep" in out.lower()
print("sleep opener rewrite OK:", out)
src2 = "Without enough sleep, the brain."
out2 = _require_visible_rewrite(src2, src2, "Neutral", rng, target_sim=0.85)
assert _is_true_rewrite(src2, out2), out2
assert "sleep" in out2.lower()
print("without-enough rewrite OK:", out2)
raw = "Another important thing are sleeping. Without enough sleep, the brain"
g = correct_text(raw)
with patch(
"app.pipeline.orchestrator.rewrite_unit",
side_effect=lambda u, **_k: u,
):
from app.pipeline.orchestrator import _hybrid_rewrite
out, _stats, _ = _hybrid_rewrite(
g, "Neutral", 1, rng, can_generate=True
)
assert _similarity_ratio(g, out) < 0.90
assert "sleep" in out.lower()
# Must not be grammar-only near-copy of the classic paste
assert "another important thing is sleeping" not in out.lower()
print("sleep document hybrid rewrite OK:", out)
def test_exercise_tv_must_not_be_reorder_only() -> None:
"""Exercise/TV sample should change via reorder and/or wording; keep claims."""
import random
from unittest.mock import patch
from app.pipeline.orchestrator import (
_hybrid_rewrite,
_is_true_rewrite,
_similarity_ratio,
)
raw = (
"Exercise is one of the most important activity that keeps our body fit. "
"If a person exercise regularly, they will have less chance to gets diseases. "
"Unfortunately, many peoples prefers watching television instead of doing "
"physical activities."
)
g = correct_text(raw)
rng = random.Random(11)
with patch(
"app.pipeline.orchestrator.rewrite_unit",
side_effect=lambda u, **_k: u,
):
out, stats, _ = _hybrid_rewrite(g, "Neutral", 1, rng, can_generate=True)
low = out.lower()
assert out.strip() != g.strip()
assert "tv" in low or "television" in low
assert "fit" in low or "exercise" in low or "work out" in low or "workout" in low
from app.pipeline.alignment import iter_source_units
src_units = [u for _i, u in iter_source_units(g)]
out_units = [u for _i, u in iter_source_units(out)]
assert len(out_units) == len(src_units)
true_n = sum(1 for s, o in zip(src_units, out_units) if _is_true_rewrite(s, o))
assert true_n >= max(1, len(src_units) - 1), (true_n, out)
print("exercise/TV rewrite OK:", out)
print(" stats", stats)
_ = _similarity_ratio # kept for debugging imports
def test_complete_wording_rewrite_not_shuffle() -> None:
"""User expects structural reorder, e.g. time/manner fronting."""
import random
from app.pipeline.orchestrator import (
_is_structural_reorder,
_is_true_rewrite,
_require_visible_rewrite,
_sentence_reorder_variants,
_try_restructure_unit,
)
from app.pipeline.restructure import restructure_sentence
src = "Ram went to school yesterday happily."
expected_style = "Yesterday, Ram happily went to school."
assert _is_true_rewrite(src, expected_style), "reorder must count as rewrite"
assert _is_structural_reorder(src, expected_style)
rr = restructure_sentence(src)
assert rr is not None, "restructure_sentence must handle Ram"
assert _is_structural_reorder(src, rr.text), rr.text
assert "yesterday" in rr.text.lower()
assert "happily" in rr.text.lower()
assert "headed" not in rr.text.lower()
assert "cheerfully" not in rr.text.lower()
assert "day before" not in rr.text.lower()
try_out = _try_restructure_unit(src)
assert try_out is not None
assert _is_structural_reorder(src, try_out), try_out
variants = _sentence_reorder_variants(src)
assert any("yesterday, ram happily went to school" in v.lower() for v in variants), variants
out = _require_visible_rewrite(
src, src, "Neutral", random.Random(5), target_sim=0.95
)
assert _is_true_rewrite(src, out), out
assert _is_structural_reorder(src, out), out
assert "ram" in out.lower() and "school" in out.lower()
assert "yesterday" in out.lower()
assert "happily" in out.lower()
assert "went" in out.lower()
# Must not synonym-thrash the Ram sentence
assert "headed" not in out.lower()
assert "cheerfully" not in out.lower()
assert "day before" not in out.lower()
print("Ram sentence reorder OK:", out)
def test_restructure_time_only_and_manner() -> None:
"""Additional reorder templates: time front; manner before verb."""
from app.pipeline.orchestrator import (
_is_structural_reorder,
_try_restructure_unit,
)
from app.pipeline.restructure import restructure_sentence
# Time-only → front time
src_time = "She finished the report yesterday."
rr_time = restructure_sentence(src_time)
assert rr_time is not None, "time-only restructure"
assert rr_time.text.lower().startswith("yesterday"), rr_time.text
assert _is_structural_reorder(src_time, rr_time.text) or "yesterday" in rr_time.text.lower()
assert "finished" in rr_time.text.lower() and "report" in rr_time.text.lower()
print("time-only reorder OK:", rr_time.text)
# Manner + place → manner before verb
src_manner = "They walked to the park slowly."
out_m = _try_restructure_unit(src_manner) or (
restructure_sentence(src_manner).text if restructure_sentence(src_manner) else None
)
assert out_m is not None, "manner reorder"
low = out_m.lower()
assert "slowly" in low and "park" in low and "walked" in low
# Manner should appear before the verb when template applies
if "slowly walked" in low or low.index("slowly") < low.index("walked"):
pass
else:
# Accept time/place template variants as long as words preserved
assert _is_structural_reorder(src_manner, out_m) or set(
w for w in low.replace(".", "").split() if len(w) > 2
) >= {"they", "walked", "park", "slowly"}
print("manner reorder OK:", out_m)
# Subject manner verb place style
src_happy = "Maya drove to work quickly."
rr_h = restructure_sentence(src_happy)
assert rr_h is not None
assert "maya" in rr_h.text.lower() and "quickly" in rr_h.text.lower()
assert "work" in rr_h.text.lower()
print("manner+place reorder OK:", rr_h.text)
def test_require_visible_keeps_structural_reorder() -> None:
"""Existing structural reorder must not be synonym-polished away."""
import random
from app.pipeline.orchestrator import (
_is_structural_reorder,
_require_visible_rewrite,
)
src = "Ram went to school yesterday happily."
cand = "Yesterday, Ram happily went to school."
out = _require_visible_rewrite(
src, cand, "Neutral", random.Random(1), target_sim=0.5
)
assert _is_structural_reorder(src, out), out
assert "headed" not in out.lower()
assert "cheerfully" not in out.lower()
print("keep structural reorder OK:", out)
def test_rejects_degree_adv_junk_reorder() -> None:
"""Do not ship 'more makes' style degree-adverb reorders."""
from app.pipeline.orchestrator import _try_restructure_unit
from app.pipeline.restructure import restructure_sentence
src = "Online learning makes education more accessible for students in remote areas."
rr = restructure_sentence(src)
if rr is not None:
assert "more makes" not in rr.text.lower(), rr.text
try_out = _try_restructure_unit(src)
if try_out is not None:
assert "more makes" not in try_out.lower(), try_out
print("degree-adv junk reorder rejected OK:", rr, try_out)
def test_because_front_preserves_clauses() -> None:
from app.pipeline.restructure import restructure_sentence
src = (
"Nowadays many people are living an unhealthy life "
"because they don't have enough time."
)
rr = restructure_sentence(src)
assert rr is not None, "because_front should apply"
assert rr.template_id == "because_front"
low = rr.text.lower()
assert low.startswith("because")
assert "unhealthy" in low and "enough time" in low
assert "nowadays" in low
print("because_front OK:", rr.text)
def test_esl_wording_avoids_synonym_thrash() -> None:
import random
from app.pipeline.orchestrator import _require_visible_rewrite
sleep = "Sleeping is still an important thing."
out_s = _require_visible_rewrite(
sleep, sleep, "Neutral", random.Random(3), target_sim=0.95
)
low_s = out_s.lower()
assert "endeavor" not in low_s and "nevertheless" not in low_s
assert "sleep" in low_s or "sleeping" in low_s
print("sleep wording OK:", out_s)
climate = "Climate change is causing more extreme weather events around the world."
out_c = _require_visible_rewrite(
climate, climate, "Neutral", random.Random(7), target_sim=0.95
)
low_c = out_c.lower()
assert "uttermost" not in low_c and "upwind" not in low_c
assert "climate" in low_c and "weather" in low_c
print("climate wording OK:", out_c)
def test_rejects_hollow_ons_bills() -> None:
src = (
"People should save money for emergencies instead of spending every paycheck."
)
bad = (
"People should save money for emergencies instead of spending it ONS bills or bills."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "broken" in v.reasons or "key_content" in v.reasons
print("hollow ONS bills reject OK:", v.reasons)
def test_assembly_preserves_unit_count() -> None:
raw = (
"Nowadays many peoples are living unhealthy life because they don't have enough times. "
"Eating fast foods are becoming very common and peoples don't realizes how much it affect their health.\n\n"
"Exercise is one of the most important activity that keeps our body fit. "
"If a person exercise regularly, they will have less chance to gets diseases. "
"Unfortunately, many peoples prefers watching television instead of doing physical activities"
)
source = correct_text(raw)
n_in = len(iter_source_units(source))
r = rewrite_text(raw, tone="Neutral", strength=1, ml_polish=False)
# Classical path still grammar-fixes; count sentences in output vs source units
n_out = len(iter_source_units(r.text))
assert n_out >= n_in - 1 # allow minor splitter variance after grammar
assert "television" in r.text.lower() or "physical" in r.text.lower()
print("unit preservation OK in=", n_in, "out=", n_out)
def test_live_bug_patterns_rejected() -> None:
src = (
"Nowadays many people are living an unhealthy life because they don't have enough time."
)
invented = (
"Many people these days live unfulfilling unhealthy lives due to lack of "
"awareness and education."
)
v = validate_candidate(src, invented, min_meaning=0.5)
assert not v.ok
assert "invention" in v.reasons
print("live-bug invention OK")
def test_rejects_fast_food_collocation_drop() -> None:
src = (
"Eating fast foods is becoming very common and people don't realize "
"how much it affects their health."
)
# FLAN slip: drops "fast", keeps bare "Food"
bad = (
"Food is becoming very common, and people don't realize how much it "
"affects their health."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "collocation" in v.reasons
ok, reasons = _accept_lm_unit(src, bad)
assert not ok
print("fast-food collocation reject OK:", v.reasons, reasons)
def test_rejects_workout_program_invention() -> None:
src = (
"Unfortunately, many people prefer watching television instead of "
"doing physical activities."
)
bad = (
"Unfortunately, many people prefer watching television instead of "
"following a workout program."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "invention" in v.reasons
ok, reasons = _accept_lm_unit(src, bad)
assert not ok
print("workout-program invention reject OK:", v.reasons, reasons)
def test_keeps_fast_food_when_preserved() -> None:
src = (
"Eating fast foods is becoming very common and people don't realize "
"how much it affects their health."
)
good = (
"Eating fast food is getting very common, and people don't realize "
"how much it harms their health."
)
from app.pipeline.candidate_validator import _protected_collocations_ok
assert _protected_collocations_ok(src, good)
print("fast-food keep OK")
def test_rejects_live_mangled_online_learning() -> None:
src = (
"Many students prefer to study from home because it saves time and money."
)
bad = (
"It takes less time to study from home than it would at home."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert any(r in v.reasons for r in ("key_content", "broken", "coverage", "drift"))
ok, reasons = _accept_lm_unit(src, bad)
assert not ok
print("online-learning mangle reject OK:", v.reasons, reasons)
def test_rejects_dropped_online_modifier() -> None:
src = "Online learning has become more popular during recent years."
bad = "Learning is becoming more popular in recent years."
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "key_content" in v.reasons
print("online modifier reject OK:", v.reasons)
def test_rejects_mangled_climate_claim() -> None:
src = (
"Climate change is a serious problem that affects every country."
)
bad = "The impact of climate change has every country."
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert any(r in v.reasons for r in ("key_content", "coverage", "drift"))
print("climate mangle reject OK:", v.reasons)
def test_accepts_faithful_paraphrase() -> None:
src = (
"Many students prefer to study from home because it saves time and money."
)
good = (
"A lot of students prefer studying at home since it saves them time and money."
)
from app.pipeline.candidate_validator import _key_content_ok, _broken_rewrite_ok
assert _key_content_ok(src, good)
assert _broken_rewrite_ok(src, good)
print("faithful paraphrase key-content OK")
def test_rejects_dropped_books_and_cities() -> None:
src = "Reading books regularly improves vocabulary and critical thinking."
bad = "Reading helps improve vocabulary and critical thinking."
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "key_content" in v.reasons
print("books drop reject OK:", v.reasons)
src2 = "Public transport is cheaper than owning a car in big cities."
bad2 = "Public transport is cheaper than owning a car."
v2 = validate_candidate(src2, bad2, min_meaning=0.5)
assert not v2.ok
assert "key_content" in v2.reasons or "length" in v2.reasons
print("cities drop reject OK:", v2.reasons)
def test_rejects_save_spend_flip() -> None:
src = "Many students prefer to study from home because it saves time and money."
bad = (
"Many students prefer to study from home because it allows them to "
"spend their time and money."
)
from app.pipeline.meaning_safety import polarity_safe
assert not polarity_safe(src, bad)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "polarity" in v.reasons
print("save/spend polarity reject OK:", v.reasons)
def test_rejects_garbage_dontstet() -> None:
src = (
"If companies don't reduce packaging and people don't recycle properly, "
"the problem will become worse every year."
)
bad = (
"If companies don't reduce packaging, people don'tstet recycle properly, "
"the problem will grow."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "broken" in v.reasons or "key_content" in v.reasons
print("dontstet garbage reject OK:", v.reasons)
def test_rejects_recycle_negation_flip() -> None:
src = (
"If companies don't reduce packaging and people don't recycle properly, "
"the problem will become worse every year."
)
bad = (
"If people recycle and companies don't reduce their packaging and products, "
"the problem will become worse every year."
)
from app.pipeline.meaning_safety import polarity_safe
assert not polarity_safe(src, bad)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "polarity" in v.reasons
print("recycle negation flip reject OK:", v.reasons)
def test_assemble_preserves_two_units() -> None:
from app.pipeline.orchestrator import _assemble_hybrid_units
units = [
(0, "Reading books regularly improves vocabulary and critical thinking."),
(0, "Children who don't read enough often struggle with writing essays later in school."),
]
outs = [
"Reading books regularly improves vocabulary and critical thinking",
"Children who don't read enough often struggle with writing essays later in school",
]
text = _assemble_hybrid_units(units, outs)
assert "books" in text.lower()
assert "children" in text.lower()
assert text.count(".") >= 2
print("assemble two units OK:", text)
def test_rejects_comparison_swap() -> None:
src = "Electric cars produce less air pollution than petrol cars."
bad = "Petrol cars make less air pollution than electric cars."
from app.pipeline.meaning_safety import polarity_safe
assert not polarity_safe(src, bad)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "polarity" in v.reasons
ok, reasons = _accept_lm_unit(src, bad)
assert not ok
print("comparison swap reject OK:", v.reasons, reasons)
def test_rejects_learning_to_read_invention() -> None:
src = "Learning a second language opens more career opportunities."
bad = "Learning to read a second language opens many career opportunities."
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "invention" in v.reasons
print("learning-to-read invention reject OK:", v.reasons)
def test_rejects_check_with_teachers_invention() -> None:
src = (
"Teachers should check progress regularly instead of grading only "
"the final report."
)
bad = (
"Teachers should check with teachers about progress instead of "
"grading the final report."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "invention" in v.reasons
print("check-with-teachers invention reject OK:", v.reasons)
def test_rejects_libraries_to_schools_swap() -> None:
src = (
"If cities don't fund them properly, many young people lose access "
"to learning resources."
)
bad = (
"If cities don't fund their schools correctly, many young people lose "
"access to learning resources."
)
v = validate_candidate(src, bad, min_meaning=0.5)
assert not v.ok
assert "entity_inject" in v.reasons
print("libraries/them->schools inject reject OK:", v.reasons)
src2 = "Public libraries offer free books and quiet spaces for study."
bad2 = "Public schools offer free books and quiet spaces for study."
v2 = validate_candidate(src2, bad2, min_meaning=0.5)
assert not v2.ok
assert "topic_anchor" in v2.reasons or "key_content" in v2.reasons
print("libraries->schools topic reject OK:", v2.reasons)
def test_clamp_rejects_multi_sentence() -> None:
from app.pipeline.orchestrator import (
_accept_lm_unit,
_clamp_to_source_shape,
_sentence_count_simple,
)
src = "Learning a second language opens more career opportunities."
bad = (
"Learning a second language opens more career opportunities. "
"Travel becomes easier too."
)
clamped = _clamp_to_source_shape(src, bad)
assert _sentence_count_simple(clamped) == 1
ok, reasons = _accept_lm_unit(src, bad)
assert not ok
assert "shape" in reasons
print("multi-sentence clamp/reject OK:", reasons)
if __name__ == "__main__":
test_rejects_multi_sentence_invention()
test_rejects_two_sentence_for_one()
test_skip_short_and_light()
test_sleep_style_must_visibly_rewrite()
test_exercise_tv_must_not_be_reorder_only()
test_complete_wording_rewrite_not_shuffle()
test_restructure_time_only_and_manner()
test_require_visible_keeps_structural_reorder()
test_rejects_degree_adv_junk_reorder()
test_because_front_preserves_clauses()
test_esl_wording_avoids_synonym_thrash()
test_rejects_hollow_ons_bills()
test_assembly_preserves_unit_count()
test_live_bug_patterns_rejected()
test_rejects_fast_food_collocation_drop()
test_rejects_workout_program_invention()
test_keeps_fast_food_when_preserved()
test_rejects_live_mangled_online_learning()
test_rejects_dropped_online_modifier()
test_rejects_mangled_climate_claim()
test_accepts_faithful_paraphrase()
test_rejects_dropped_books_and_cities()
test_rejects_save_spend_flip()
test_rejects_garbage_dontstet()
test_rejects_recycle_negation_flip()
test_assemble_preserves_two_units()
test_rejects_comparison_swap()
test_rejects_learning_to_read_invention()
test_rejects_check_with_teachers_invention()
test_rejects_libraries_to_schools_swap()
test_clamp_rejects_multi_sentence()
print("\nALL PHASE A TESTS PASSED")