any2human / app /pipeline /syntax_rewrite.py
idnameraj's picture
Upload 81 files
3b361f1 verified
Raw
History Blame Contribute Delete
6.07 kB
"""Rule-based syntactic rewrites using spaCy when available — structure only."""
from __future__ import annotations
import random
import re
from app.pipeline.nlp import get_nlp
from app.pipeline.normalize import split_sentences_regex
from app.pipeline.sentence_transform import transform_sentence
from app.pipeline.tones import is_casual, is_elevated, normalize_tone
def strip_hedges(sentence: str) -> str:
"""Hedges are stripped centrally via ai_phrases.txt in mechanics.scrub_phrases."""
return sentence.strip()
def maybe_split_long(sentence: str, strength: int, rng: random.Random) -> list[str]:
"""Split long sentences on clause-like joins — based on length, not fixed content."""
words = sentence.split()
min_len = {0: 22, 1: 16, 2: 12}.get(strength, 16)
chance = {0: 0.45, 1: 0.7, 2: 0.9}.get(strength, 0.7)
if len(words) < min_len or rng.random() > chance:
return [sentence]
if '"' in sentence or "'" in sentence or "“" in sentence or "”" in sentence:
return [sentence]
for sep in [
"; ",
", and ",
", but ",
", so ",
", which ",
", although ",
", because ",
", while ",
", whereas ",
" although ",
" because ",
" whereas ",
]:
if sep not in sentence:
continue
left, right = sentence.split(sep, 1)
left, right = left.strip(), right.strip()
# Avoid splitting tiny fragments
if len(left.split()) < 5 or len(right.split()) < 5:
continue
if right and right[0].islower():
right = right[0].upper() + right[1:]
if not left.endswith((".", "!", "?")):
left += "."
if not right.endswith((".", "!", "?")):
right += "."
return [left, right]
return [sentence]
def maybe_passive_to_active(sentence: str, strength: int, rng: random.Random) -> str:
"""Light-touch: 'X was VERBed by Y' → 'Y VERBed X' when parse looks safe."""
if strength < 1 or rng.random() > 0.4 * strength:
return sentence
nlp = get_nlp()
if nlp is None:
return sentence
doc = nlp(sentence)
for token in doc:
if token.dep_ == "nsubjpass":
subj = token
verb = token.head
agent = None
for child in verb.children:
if child.dep_ == "agent":
for g in child.children:
if g.dep_ == "pobj":
agent = g
break
if agent is None or verb.tag_ not in {"VBN"}:
continue
if len(doc) > 22:
return sentence
agent_span = agent.text
for chunk in doc.noun_chunks:
if agent in chunk:
agent_span = chunk.text
break
subj_span = subj.text
for chunk in doc.noun_chunks:
if subj in chunk:
subj_span = chunk.text
break
lemma = verb.lemma_
irregular = {
"make": "made",
"write": "wrote",
"take": "took",
"give": "gave",
"find": "found",
"show": "showed",
"do": "did",
}
if lemma in irregular:
verb_out = irregular[lemma]
elif lemma.endswith("e"):
verb_out = lemma + "d"
else:
verb_out = lemma + "ed"
rebuilt = f"{agent_span} {verb_out} {subj_span}."
if rebuilt[0].islower():
rebuilt = rebuilt[0].upper() + rebuilt[1:]
return rebuilt
return sentence
def apply_tone_contractions(text: str, tone: str) -> str:
tone_l = normalize_tone(tone)
if is_elevated(tone_l):
return text
pairs = [
(r"\bdo not\b", "don't"),
(r"\bdoes not\b", "doesn't"),
(r"\bdid not\b", "didn't"),
(r"\bis not\b", "isn't"),
(r"\bare not\b", "aren't"),
(r"\bwas not\b", "wasn't"),
(r"\bwere not\b", "weren't"),
(r"\bwill not\b", "won't"),
(r"\bcannot\b", "can't"),
(r"\bcould not\b", "couldn't"),
(r"\bwould not\b", "wouldn't"),
(r"\bshould not\b", "shouldn't"),
(r"\bit is\b", "it's"),
(r"\bthat is\b", "that's"),
(r"\bthere is\b", "there's"),
(r"\bwe are\b", "we're"),
(r"\bwe have\b", "we've"),
(r"\byou are\b", "you're"),
(r"\bthey are\b", "they're"),
(r"\bI am\b", "I'm"),
(r"\bI have\b", "I've"),
(r"\blet us\b", "let's"),
(r"\bwho is\b", "who's"),
(r"\bwhat is\b", "what's"),
]
# Casual: full contraction set. Neutral: core negatives only (subtler).
use = pairs if is_casual(tone_l) else pairs[:12]
out = text
for pat, repl in use:
out = re.sub(pat, repl, out)
return out
def rewrite_paragraph_structure(
paragraph: str,
strength: int,
rng: random.Random,
*,
tone: str = "Neutral",
) -> str:
"""Structure pass: sentence transforms → passive→active → optional splits."""
tone = normalize_tone(tone)
nlp = get_nlp()
if nlp is not None:
doc = nlp(paragraph)
sentences = [s.text.strip() for s in doc.sents if s.text.strip()]
else:
sentences = split_sentences_regex(paragraph)
out_sents: list[str] = []
for sent in sentences:
s = strip_hedges(sent)
# Sentence-level paraphrase first (tone-aware)
s = transform_sentence(s, tone, strength, rng)
s = maybe_passive_to_active(s, strength, rng)
parts = maybe_split_long(s, strength, rng)
out_sents.extend(parts)
return " ".join(out_sents)