Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -1685,127 +1685,139 @@ def subject_is_singular(subj_token) -> bool:
|
|
| 1685 |
|
| 1686 |
def fix_subject_verb_agreement(sent: str) -> str:
|
| 1687 |
"""
|
| 1688 |
-
|
| 1689 |
-
|
| 1690 |
-
e.g. "The man are going home." → "The man is going home."
|
| 1691 |
"""
|
| 1692 |
-
|
| 1693 |
-
if not doc:
|
| 1694 |
return sent
|
| 1695 |
|
| 1696 |
-
|
| 1697 |
-
|
| 1698 |
-
if
|
| 1699 |
return sent
|
| 1700 |
|
|
|
|
|
|
|
| 1701 |
subj = None
|
| 1702 |
for t in doc:
|
| 1703 |
-
if t.dep_ in ("nsubj", "nsubjpass") and
|
|
|
|
|
|
|
| 1704 |
subj = t
|
| 1705 |
break
|
| 1706 |
-
|
| 1707 |
-
|
| 1708 |
-
if t.
|
| 1709 |
subj = t
|
| 1710 |
break
|
|
|
|
|
|
|
| 1711 |
if subj is None:
|
| 1712 |
for t in doc:
|
| 1713 |
-
if t.
|
| 1714 |
subj = t
|
| 1715 |
break
|
| 1716 |
if subj is None:
|
| 1717 |
return sent
|
| 1718 |
|
| 1719 |
-
#
|
| 1720 |
-
if any(
|
| 1721 |
return sent
|
| 1722 |
|
| 1723 |
-
# Singular subject?
|
| 1724 |
plural_pronouns = {"i", "you", "we", "they"}
|
| 1725 |
if subj.lower_ in plural_pronouns:
|
| 1726 |
return sent
|
| 1727 |
if subj.tag_ in ("NNS", "NNPS"):
|
| 1728 |
return sent
|
| 1729 |
-
singular = subj.tag_ in ("NN", "NNP", "PRP") or (
|
| 1730 |
-
subj.tag_ in ("DT",) and False
|
| 1731 |
-
)
|
| 1732 |
-
# "the man", "his dog": head noun is NN/NNP
|
| 1733 |
-
if subj.tag_ in ("NN", "NNP", "PRP"):
|
| 1734 |
-
singular = True
|
| 1735 |
-
elif subj.pos_ == "NOUN" and subj.tag_ == "NN":
|
| 1736 |
-
singular = True
|
| 1737 |
-
else:
|
| 1738 |
-
# walk to noun head of subject chunk
|
| 1739 |
-
head = subj
|
| 1740 |
-
for t in subj.subtree:
|
| 1741 |
-
if t.tag_ in ("NN", "NNP") and t.dep_ in ("nsubj", "nsubjpass", "ROOT"):
|
| 1742 |
-
head = t
|
| 1743 |
-
if head.tag_ in ("NNS", "NNPS"):
|
| 1744 |
-
return sent
|
| 1745 |
-
singular = head.tag_ in ("NN", "NNP", "PRP")
|
| 1746 |
|
|
|
|
|
|
|
| 1747 |
if not singular:
|
| 1748 |
return sent
|
| 1749 |
|
| 1750 |
-
|
| 1751 |
-
|
| 1752 |
-
|
| 1753 |
-
|
| 1754 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1755 |
|
|
|
|
| 1756 |
target = None
|
| 1757 |
-
|
| 1758 |
-
|
| 1759 |
-
|
| 1760 |
-
|
| 1761 |
-
|
| 1762 |
-
|
| 1763 |
-
target = root
|
| 1764 |
if target is None:
|
| 1765 |
for t in doc:
|
| 1766 |
-
if t.dep_ in ("aux", "auxpass") and t.
|
|
|
|
|
|
|
| 1767 |
target = t
|
| 1768 |
break
|
|
|
|
|
|
|
| 1769 |
if target is None:
|
| 1770 |
-
|
| 1771 |
-
|
| 1772 |
-
|
| 1773 |
-
|
| 1774 |
-
|
| 1775 |
-
|
| 1776 |
-
"are": "is",
|
| 1777 |
-
"were": "was",
|
| 1778 |
-
"have": "has",
|
| 1779 |
-
"do": "does",
|
| 1780 |
-
"go": "goes",
|
| 1781 |
-
"'re": "'s",
|
| 1782 |
-
"am": "is", # rare after split
|
| 1783 |
-
}
|
| 1784 |
-
# Past "was" already singular; "were" handled above
|
| 1785 |
-
if lower in ("is", "was", "has", "does", "goes"):
|
| 1786 |
-
return sent
|
| 1787 |
-
if target.tag_ == "VBZ":
|
| 1788 |
-
return sent
|
| 1789 |
|
| 1790 |
-
if
|
| 1791 |
-
|
| 1792 |
-
|
| 1793 |
-
lemma = target.lemma_.lower()
|
| 1794 |
-
if lemma in irregular_to_3sg:
|
| 1795 |
-
new = irregular_to_3sg[lemma]
|
| 1796 |
-
elif lemma.endswith(("s", "sh", "ch", "x", "z", "o")):
|
| 1797 |
-
new = lemma + "es"
|
| 1798 |
-
elif lemma.endswith("y") and len(lemma) > 1 and lemma[-2] not in "aeiou":
|
| 1799 |
-
new = lemma[:-1] + "ies"
|
| 1800 |
else:
|
| 1801 |
-
new =
|
| 1802 |
-
|
| 1803 |
-
|
|
|
|
| 1804 |
|
| 1805 |
-
|
| 1806 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1807 |
|
| 1808 |
-
return sent
|
|
|
|
| 1809 |
###############################################################################
|
| 1810 |
|
| 1811 |
###############################################################################
|
|
|
|
| 1685 |
|
| 1686 |
def fix_subject_verb_agreement(sent: str) -> str:
|
| 1687 |
"""
|
| 1688 |
+
Repair singular subject + plural/base present verb after CMN1/SSCCV splits.
|
| 1689 |
+
Handles: John are moving → John is moving; John live → John lives.
|
|
|
|
| 1690 |
"""
|
| 1691 |
+
if not sent or not sent.strip():
|
|
|
|
| 1692 |
return sent
|
| 1693 |
|
| 1694 |
+
sent = sent.strip()
|
| 1695 |
+
doc = nlp(sent)
|
| 1696 |
+
if not doc:
|
| 1697 |
return sent
|
| 1698 |
|
| 1699 |
+
# ----- identify subject head (prefer nsubj of ROOT) -----
|
| 1700 |
+
root = next((t for t in doc if t.dep_ == "ROOT"), doc[0])
|
| 1701 |
subj = None
|
| 1702 |
for t in doc:
|
| 1703 |
+
if t.dep_ in ("nsubj", "nsubjpass") and (
|
| 1704 |
+
t.head == root or t.head.head == root
|
| 1705 |
+
):
|
| 1706 |
subj = t
|
| 1707 |
break
|
| 1708 |
+
if subj is None:
|
| 1709 |
+
for t in doc:
|
| 1710 |
+
if t.dep_ in ("nsubj", "nsubjpass"):
|
| 1711 |
subj = t
|
| 1712 |
break
|
| 1713 |
+
|
| 1714 |
+
# Fallback: first proper/common noun or pronoun
|
| 1715 |
if subj is None:
|
| 1716 |
for t in doc:
|
| 1717 |
+
if t.tag_ in ("NNP", "NN", "PRP") and t.dep_ not in ("punct",):
|
| 1718 |
subj = t
|
| 1719 |
break
|
| 1720 |
if subj is None:
|
| 1721 |
return sent
|
| 1722 |
|
| 1723 |
+
# Coordinated subject → keep plural verb
|
| 1724 |
+
if any(t.dep_ == "cc" for t in subj.subtree):
|
| 1725 |
return sent
|
| 1726 |
|
|
|
|
| 1727 |
plural_pronouns = {"i", "you", "we", "they"}
|
| 1728 |
if subj.lower_ in plural_pronouns:
|
| 1729 |
return sent
|
| 1730 |
if subj.tag_ in ("NNS", "NNPS"):
|
| 1731 |
return sent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1732 |
|
| 1733 |
+
# NNP / NN / singular PRP → singular
|
| 1734 |
+
singular = subj.tag_ in ("NNP", "NN", "PRP")
|
| 1735 |
if not singular:
|
| 1736 |
return sent
|
| 1737 |
|
| 1738 |
+
def to_3sg(word: str) -> str:
|
| 1739 |
+
lower = word.lower()
|
| 1740 |
+
irregular = {
|
| 1741 |
+
"are": "is",
|
| 1742 |
+
"were": "was",
|
| 1743 |
+
"have": "has",
|
| 1744 |
+
"do": "does",
|
| 1745 |
+
"go": "goes",
|
| 1746 |
+
"live": "lives",
|
| 1747 |
+
"move": "moves",
|
| 1748 |
+
"am": "is",
|
| 1749 |
+
}
|
| 1750 |
+
if lower in irregular:
|
| 1751 |
+
new = irregular[lower]
|
| 1752 |
+
elif lower.endswith(("s", "sh", "ch", "x", "z", "o")):
|
| 1753 |
+
new = lower + "es"
|
| 1754 |
+
elif lower.endswith("y") and len(lower) > 1 and lower[-2] not in "aeiou":
|
| 1755 |
+
new = lower[:-1] + "ies"
|
| 1756 |
+
else:
|
| 1757 |
+
new = lower + "s"
|
| 1758 |
+
if word[0].isupper():
|
| 1759 |
+
new = new[0].upper() + new[1:]
|
| 1760 |
+
return new
|
| 1761 |
|
| 1762 |
+
# ----- finite aux or main verb that should agree -----
|
| 1763 |
target = None
|
| 1764 |
+
for t in doc:
|
| 1765 |
+
if t.dep_ in ("aux", "auxpass") and t.tag_ in ("VBP", "VB", "VBD"):
|
| 1766 |
+
# prefer aux of the main clause
|
| 1767 |
+
if t.head == root or t.head.dep_ == "ROOT":
|
| 1768 |
+
target = t
|
| 1769 |
+
break
|
|
|
|
| 1770 |
if target is None:
|
| 1771 |
for t in doc:
|
| 1772 |
+
if t.dep_ in ("aux", "auxpass") and t.lower_ in (
|
| 1773 |
+
"are", "were", "have", "do", "am"
|
| 1774 |
+
):
|
| 1775 |
target = t
|
| 1776 |
break
|
| 1777 |
+
if target is None and root.tag_ in ("VBP", "VB"):
|
| 1778 |
+
target = root
|
| 1779 |
if target is None:
|
| 1780 |
+
for t in doc:
|
| 1781 |
+
if t.tag_ in ("VBP", "VB") and t.lower_ in (
|
| 1782 |
+
"are", "were", "have", "do", "live", "go", "move"
|
| 1783 |
+
):
|
| 1784 |
+
target = t
|
| 1785 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1786 |
|
| 1787 |
+
if target is not None and target.tag_ != "VBZ":
|
| 1788 |
+
if target.lower_ in ("is", "was", "has", "does"):
|
| 1789 |
+
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1790 |
else:
|
| 1791 |
+
new = to_3sg(target.text)
|
| 1792 |
+
sent = sent[: target.idx] + new + sent[target.idx + len(target.text) :]
|
| 1793 |
+
# re-parse if you chain multiple fixes; for one shot this is enough
|
| 1794 |
+
return sent
|
| 1795 |
|
| 1796 |
+
# ----- surface fallback (ungrammatical strings spaCy mis-parses) -----
|
| 1797 |
+
# "John are …" / "Mary live …"
|
| 1798 |
+
m = re.match(
|
| 1799 |
+
r"^((?:The|A|An|His|Her|Their)\s+\w+|[A-Z][\w']*)\s+"
|
| 1800 |
+
r"(are|were|have|do|live|move|go|want|need)\b",
|
| 1801 |
+
sent,
|
| 1802 |
+
flags=re.I,
|
| 1803 |
+
)
|
| 1804 |
+
if m:
|
| 1805 |
+
subj_s, verb = m.group(1), m.group(2)
|
| 1806 |
+
# skip if subject looks plural
|
| 1807 |
+
if re.search(r"\b(and|&)\b", subj_s, re.I):
|
| 1808 |
+
return sent
|
| 1809 |
+
if re.search(r"s\s*$", subj_s) and not re.match(
|
| 1810 |
+
r"^(His|Her|Their|The|A|An)\s", subj_s, re.I
|
| 1811 |
+
):
|
| 1812 |
+
# weak; proper names end without s more often
|
| 1813 |
+
pass
|
| 1814 |
+
new_verb = to_3sg(verb)
|
| 1815 |
+
if verb[0].isupper():
|
| 1816 |
+
new_verb = new_verb[0].upper() + new_verb[1:]
|
| 1817 |
+
sent = sent[: m.start(2)] + new_verb + sent[m.end(2) :]
|
| 1818 |
|
| 1819 |
+
return sent
|
| 1820 |
+
|
| 1821 |
###############################################################################
|
| 1822 |
|
| 1823 |
###############################################################################
|