any2human / app /engine /force /__init__.py
idnameraj's picture
Upload 3155 files
67f284e verified
Raw
History Blame Contribute Delete
3.22 kB
"""Explicit high-coverage rule fallback for declarative sentences."""
from __future__ import annotations
import re
from app.pipeline.nlp import get_nlp
_SUBJECT_DEPS = frozenset({"nsubj", "nsubjpass", "csubj", "csubjpass"})
_PROTECTED = re.compile(r"ZZPROTECTED[A-Z]+\d+ZZ", re.I)
_QUOTES = frozenset({'"', "“", "”", "‘", "’"})
def _lower_continuation(text: str, subject_head) -> str:
value = text.strip()
if not value:
return value
if (
subject_head.pos_ == "PROPN"
or subject_head.text == "I"
):
return value
return value[:1].lower() + value[1:]
def force_cleft_rewrite(text: str) -> str | None:
"""Create a subject cleft while retaining the original predicate."""
raw = (text or "").strip()
if (
not raw
or raw.endswith("?")
or any(mark in raw for mark in _QUOTES)
or _PROTECTED.search(raw)
):
return None
nlp = get_nlp()
if nlp is None:
return None
try:
doc = nlp(raw)
except Exception:
return None
root = next((token for token in doc if token.dep_ == "ROOT"), None)
subjects = [token for token in doc if token.dep_ in _SUBJECT_DEPS]
if root is None:
return None
# Gerund subjects with coordinated objects are often parsed as the root.
# The first finite auxiliary provides a reliable dynamic boundary.
finite_aux = next(
(
token
for token in doc
if token.pos_ == "AUX" and token.i > 0 and token.head.i > token.i
),
None,
)
if doc[0].tag_ == "VBG" and finite_aux is not None:
subject = doc[0]
start = doc[0].idx
end = finite_aux.idx
else:
if not subjects:
return None
subject = next(
(
token
for token in subjects
if token.head == root or root in tuple(token.ancestors)
),
subjects[0],
)
subject_tokens = sorted(subject.subtree, key=lambda token: token.i)
if not subject_tokens:
return None
start = subject_tokens[0].idx
last = subject_tokens[-1]
end = last.idx + len(last.text)
prefix = raw[:start].strip()
subject_text = raw[start:end].strip(" ,")
predicate = raw[end:].strip()
predicate = predicate.lstrip(" ,")
terminal = "."
if predicate.endswith(("!", ".")):
terminal = predicate[-1]
predicate = predicate[:-1].rstrip()
if not subject_text or not predicate:
return None
if predicate.lower().startswith(("is it ", "was it ")):
return None
subject_text = _lower_continuation(subject_text, subject)
core = f"it is {subject_text} that {predicate}{terminal}"
if prefix:
prefix = prefix.rstrip(" ,")
candidate = f"{prefix}, {core}"
else:
candidate = core[:1].upper() + core[1:]
candidate = re.sub(r"\s+", " ", candidate).strip()
if candidate.lower().rstrip(".!?") == raw.lower().rstrip(".!?"):
return None
return candidate