chore(derivation): remove Inc3 formatting churn
This commit is contained in:
parent
64eaf43cd8
commit
5ad6c6390b
2 changed files with 128 additions and 378 deletions
|
|
@ -248,7 +248,9 @@ def inject_discrete_count_statement(
|
||||||
anchor, sentence
|
anchor, sentence
|
||||||
)
|
)
|
||||||
elif anchor_kind == "acquisition":
|
elif anchor_kind == "acquisition":
|
||||||
cand = _build_operation_from_discrete_count_acquisition(anchor, sentence)
|
cand = _build_operation_from_discrete_count_acquisition(
|
||||||
|
anchor, sentence
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Unknown anchor_kind — under-admit. Future widenings (e.g.
|
# Unknown anchor_kind — under-admit. Future widenings (e.g.
|
||||||
# "depletion" verbs as CandidateOperation(subtract)) extend
|
# "depletion" verbs as CandidateOperation(subtract)) extend
|
||||||
|
|
@ -294,13 +296,10 @@ def _build_initial_from_discrete_count(
|
||||||
counted_noun = anchor.get("counted_noun")
|
counted_noun = anchor.get("counted_noun")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not isinstance(subject_role, str)
|
not isinstance(subject_role, str) or not subject_role
|
||||||
or not subject_role
|
or not isinstance(count_token, str) or not count_token
|
||||||
or not isinstance(count_token, str)
|
|
||||||
or not count_token
|
|
||||||
or not isinstance(count_kind, str)
|
or not isinstance(count_kind, str)
|
||||||
or not isinstance(counted_noun, str)
|
or not isinstance(counted_noun, str) or not counted_noun
|
||||||
or not counted_noun
|
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -391,15 +390,11 @@ def _build_operation_from_discrete_count_acquisition(
|
||||||
verb_token = anchor.get("verb_token")
|
verb_token = anchor.get("verb_token")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not isinstance(subject_role, str)
|
not isinstance(subject_role, str) or not subject_role
|
||||||
or not subject_role
|
or not isinstance(count_token, str) or not count_token
|
||||||
or not isinstance(count_token, str)
|
|
||||||
or not count_token
|
|
||||||
or not isinstance(count_kind, str)
|
or not isinstance(count_kind, str)
|
||||||
or not isinstance(counted_noun, str)
|
or not isinstance(counted_noun, str) or not counted_noun
|
||||||
or not counted_noun
|
or not isinstance(verb_token, str) or not verb_token
|
||||||
or not isinstance(verb_token, str)
|
|
||||||
or not verb_token
|
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -466,7 +461,10 @@ def _count_token_followed_by_times(sentence: str, count_token: str) -> bool:
|
||||||
admitting path.
|
admitting path.
|
||||||
"""
|
"""
|
||||||
target = count_token.lower()
|
target = count_token.lower()
|
||||||
tokens = [raw.strip(".,;:!?\"'()[]{}").lower() for raw in sentence.split()]
|
tokens = [
|
||||||
|
raw.strip(".,;:!?\"'()[]{}").lower()
|
||||||
|
for raw in sentence.split()
|
||||||
|
]
|
||||||
for i, tok in enumerate(tokens[:-1]):
|
for i, tok in enumerate(tokens[:-1]):
|
||||||
if tok == target and tokens[i + 1] == "times":
|
if tok == target and tokens[i + 1] == "times":
|
||||||
return True
|
return True
|
||||||
|
|
@ -522,11 +520,9 @@ def _locate_possession_verb(sentence: str) -> str | None:
|
||||||
# registers its injector. No global state, no side effects.
|
# registers its injector. No global state, no side effects.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
_WAVE_A_INJECTABLE_ANCHOR_KINDS: frozenset[str] = frozenset(
|
_WAVE_A_INJECTABLE_ANCHOR_KINDS: frozenset[str] = frozenset({
|
||||||
{
|
"multiplicative_aggregate_each_weighing",
|
||||||
"multiplicative_aggregate_each_weighing",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def inject_multiplicative_aggregation(
|
def inject_multiplicative_aggregation(
|
||||||
|
|
@ -675,12 +671,7 @@ def inject_rate_with_currency(
|
||||||
# can still pick an unrelated earlier "a".
|
# can still pick an unrelated earlier "a".
|
||||||
rate_anchor_token = anchor.get("rate_anchor_token")
|
rate_anchor_token = anchor.get("rate_anchor_token")
|
||||||
if not rate_anchor_token or rate_anchor_token not in (
|
if not rate_anchor_token or rate_anchor_token not in (
|
||||||
"per",
|
"per", "each", "every", "a", "an", "one",
|
||||||
"each",
|
|
||||||
"every",
|
|
||||||
"a",
|
|
||||||
"an",
|
|
||||||
"one",
|
|
||||||
):
|
):
|
||||||
# Missing or invalid connector for this rate surface (e.g. absent
|
# Missing or invalid connector for this rate surface (e.g. absent
|
||||||
# token). "one" (from "for one cup") is now supported (Inc 3).
|
# token). "one" (from "for one cup") is now supported (Inc 3).
|
||||||
|
|
|
||||||
|
|
@ -39,51 +39,18 @@ from generate.recognizer_registry import RatifiedRecognizer
|
||||||
# multipliers ("dozen"). Mirrors the Phase A categorizer's
|
# multipliers ("dozen"). Mirrors the Phase A categorizer's
|
||||||
# _NUMBER_WORDS so the matcher's "has any quantity marker" predicate
|
# _NUMBER_WORDS so the matcher's "has any quantity marker" predicate
|
||||||
# is the same shape as Phase A's "has no quantity marker" predicate.
|
# is the same shape as Phase A's "has no quantity marker" predicate.
|
||||||
_NUMBER_WORDS: Final[frozenset[str]] = frozenset(
|
_NUMBER_WORDS: Final[frozenset[str]] = frozenset({
|
||||||
{
|
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
|
||||||
"one",
|
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
|
||||||
"two",
|
"seventeen", "eighteen", "nineteen", "twenty", "thirty", "forty", "fifty",
|
||||||
"three",
|
"sixty", "seventy", "eighty", "ninety",
|
||||||
"four",
|
"hundred", "thousand", "million", "billion",
|
||||||
"five",
|
"dozen", "dozens",
|
||||||
"six",
|
})
|
||||||
"seven",
|
|
||||||
"eight",
|
|
||||||
"nine",
|
|
||||||
"ten",
|
|
||||||
"eleven",
|
|
||||||
"twelve",
|
|
||||||
"thirteen",
|
|
||||||
"fourteen",
|
|
||||||
"fifteen",
|
|
||||||
"sixteen",
|
|
||||||
"seventeen",
|
|
||||||
"eighteen",
|
|
||||||
"nineteen",
|
|
||||||
"twenty",
|
|
||||||
"thirty",
|
|
||||||
"forty",
|
|
||||||
"fifty",
|
|
||||||
"sixty",
|
|
||||||
"seventy",
|
|
||||||
"eighty",
|
|
||||||
"ninety",
|
|
||||||
"hundred",
|
|
||||||
"thousand",
|
|
||||||
"million",
|
|
||||||
"billion",
|
|
||||||
"dozen",
|
|
||||||
"dozens",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
_DIGIT_RE: Final[re.Pattern[str]] = re.compile(r"\d")
|
_DIGIT_RE: Final[re.Pattern[str]] = re.compile(r"\d")
|
||||||
_INDEFINITE_TOKENS: Final[tuple[str, ...]] = (
|
_INDEFINITE_TOKENS: Final[tuple[str, ...]] = (
|
||||||
" some ",
|
" some ", " several ", " a few ", " many ", " any ",
|
||||||
" several ",
|
|
||||||
" a few ",
|
|
||||||
" many ",
|
|
||||||
" any ",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -160,13 +127,7 @@ _TEMPORAL_PATTERNS: Final[tuple[tuple[re.Pattern[str], str], ...]] = (
|
||||||
# Day-of-week enumeration: at least two distinct day names with at
|
# Day-of-week enumeration: at least two distinct day names with at
|
||||||
# least one numeric count. Matches "20 ... Monday, 36 ... Tuesday".
|
# least one numeric count. Matches "20 ... Monday, 36 ... Tuesday".
|
||||||
_DAY_NAMES: Final[tuple[str, ...]] = (
|
_DAY_NAMES: Final[tuple[str, ...]] = (
|
||||||
"monday",
|
"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday",
|
||||||
"tuesday",
|
|
||||||
"wednesday",
|
|
||||||
"thursday",
|
|
||||||
"friday",
|
|
||||||
"saturday",
|
|
||||||
"sunday",
|
|
||||||
)
|
)
|
||||||
_DAY_HIT_RE: Final[re.Pattern[str]] = re.compile(
|
_DAY_HIT_RE: Final[re.Pattern[str]] = re.compile(
|
||||||
r"""(?ix)
|
r"""(?ix)
|
||||||
|
|
@ -267,13 +228,12 @@ def _match_temporal_aggregation(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
anchors: list[Mapping[str, Any]] = []
|
anchors: list[Mapping[str, Any]] = []
|
||||||
|
padded = " " + statement.lower() + " "
|
||||||
|
|
||||||
# Pass 1 — day-of-week enumeration. At least two distinct day
|
# Pass 1 — day-of-week enumeration. At least two distinct day
|
||||||
# names + a count per day yields multi-anchor day-windowed
|
# names + a count per day yields multi-anchor day-windowed
|
||||||
# aggregation.
|
# aggregation.
|
||||||
if "day" in observed_units and (
|
if "day" in observed_units and ("each" in observed_quantifiers or "every" in observed_quantifiers):
|
||||||
"each" in observed_quantifiers or "every" in observed_quantifiers
|
|
||||||
):
|
|
||||||
day_hits: list[tuple[str, str]] = []
|
day_hits: list[tuple[str, str]] = []
|
||||||
for m in _DAY_HIT_RE.finditer(statement):
|
for m in _DAY_HIT_RE.finditer(statement):
|
||||||
day_hits.append((m.group(1), m.group(2).lower()))
|
day_hits.append((m.group(1), m.group(2).lower()))
|
||||||
|
|
@ -282,14 +242,12 @@ def _match_temporal_aggregation(
|
||||||
if len(distinct_days) >= 2:
|
if len(distinct_days) >= 2:
|
||||||
quant = "each" if "each" in observed_quantifiers else "every"
|
quant = "each" if "each" in observed_quantifiers else "every"
|
||||||
for count_token, _day in day_hits:
|
for count_token, _day in day_hits:
|
||||||
anchors.append(
|
anchors.append({
|
||||||
{
|
"kind": "event_count_per_window",
|
||||||
"kind": "event_count_per_window",
|
"count_token": count_token,
|
||||||
"count_token": count_token,
|
"window_unit": "day",
|
||||||
"window_unit": "day",
|
"window_quantifier": quant,
|
||||||
"window_quantifier": quant,
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
if anchors:
|
if anchors:
|
||||||
return (tuple(anchors), "aggregate")
|
return (tuple(anchors), "aggregate")
|
||||||
|
|
||||||
|
|
@ -297,11 +255,7 @@ def _match_temporal_aggregation(
|
||||||
for pat, kind in _TEMPORAL_PATTERNS:
|
for pat, kind in _TEMPORAL_PATTERNS:
|
||||||
for m in pat.finditer(statement):
|
for m in pat.finditer(statement):
|
||||||
if kind == "explicit_quantifier":
|
if kind == "explicit_quantifier":
|
||||||
count_token, quantifier, unit = (
|
count_token, quantifier, unit = m.group(1), m.group(2).lower(), m.group(3).lower()
|
||||||
m.group(1),
|
|
||||||
m.group(2).lower(),
|
|
||||||
m.group(3).lower(),
|
|
||||||
)
|
|
||||||
elif kind == "in_window":
|
elif kind == "in_window":
|
||||||
count_token, quantifier, unit = m.group(1), "per", m.group(2).lower()
|
count_token, quantifier, unit = m.group(1), "per", m.group(2).lower()
|
||||||
else: # adverbial
|
else: # adverbial
|
||||||
|
|
@ -309,11 +263,8 @@ def _match_temporal_aggregation(
|
||||||
adverb = m.group(2).lower()
|
adverb = m.group(2).lower()
|
||||||
# Map adverb → unit.
|
# Map adverb → unit.
|
||||||
unit_map = {
|
unit_map = {
|
||||||
"daily": "day",
|
"daily": "day", "weekly": "week", "monthly": "month",
|
||||||
"weekly": "week",
|
"yearly": "year", "hourly": "hour",
|
||||||
"monthly": "month",
|
|
||||||
"yearly": "year",
|
|
||||||
"hourly": "hour",
|
|
||||||
}
|
}
|
||||||
unit = unit_map[adverb]
|
unit = unit_map[adverb]
|
||||||
quantifier = "per"
|
quantifier = "per"
|
||||||
|
|
@ -321,14 +272,12 @@ def _match_temporal_aggregation(
|
||||||
continue
|
continue
|
||||||
if quantifier not in observed_quantifiers:
|
if quantifier not in observed_quantifiers:
|
||||||
continue
|
continue
|
||||||
anchors.append(
|
anchors.append({
|
||||||
{
|
"kind": "event_count_per_window",
|
||||||
"kind": "event_count_per_window",
|
"count_token": count_token,
|
||||||
"count_token": count_token,
|
"window_unit": unit,
|
||||||
"window_unit": unit,
|
"window_quantifier": quantifier,
|
||||||
"window_quantifier": quantifier,
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if not anchors:
|
if not anchors:
|
||||||
return None
|
return None
|
||||||
|
|
@ -410,16 +359,14 @@ def _match_rate_with_currency(
|
||||||
else:
|
else:
|
||||||
amount_kind = "integer"
|
amount_kind = "integer"
|
||||||
|
|
||||||
anchors.append(
|
anchors.append({
|
||||||
{
|
"kind": "currency_per_unit_rate",
|
||||||
"kind": "currency_per_unit_rate",
|
"currency_symbol": symbol,
|
||||||
"currency_symbol": symbol,
|
"amount": amount_token,
|
||||||
"amount": amount_token,
|
"amount_kind": amount_kind,
|
||||||
"amount_kind": amount_kind,
|
"per_unit": per_unit_lc,
|
||||||
"per_unit": per_unit_lc,
|
"rate_anchor_token": connector.lower() if connector else None,
|
||||||
"rate_anchor_token": connector.lower() if connector else None,
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if not anchors:
|
if not anchors:
|
||||||
return None
|
return None
|
||||||
|
|
@ -559,11 +506,7 @@ def _try_extract_currency_per_unit_composition_anchor(
|
||||||
if composed_value_f != composed_value_f: # NaN guard
|
if composed_value_f != composed_value_f: # NaN guard
|
||||||
return None
|
return None
|
||||||
composed_value: int | float
|
composed_value: int | float
|
||||||
if (
|
if composed_value_f.is_integer() and "." not in count_token and "." not in amount_token:
|
||||||
composed_value_f.is_integer()
|
|
||||||
and "." not in count_token
|
|
||||||
and "." not in amount_token
|
|
||||||
):
|
|
||||||
composed_value = int(composed_value_f)
|
composed_value = int(composed_value_f)
|
||||||
else:
|
else:
|
||||||
composed_value = composed_value_f
|
composed_value = composed_value_f
|
||||||
|
|
@ -781,44 +724,23 @@ def try_extract_cross_sentence_composition_anchor(
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
_PER_UNIT_TOKENS: Final[tuple[str, ...]] = (
|
_PER_UNIT_TOKENS: Final[tuple[str, ...]] = (
|
||||||
" per ",
|
" per ", "/", " an hour", " a hour", " a day", " a week", " a month",
|
||||||
"/",
|
" a year", " for one ", " for each ", " for every ",
|
||||||
" an hour",
|
|
||||||
" a hour",
|
|
||||||
" a day",
|
|
||||||
" a week",
|
|
||||||
" a month",
|
|
||||||
" a year",
|
|
||||||
" for one ",
|
|
||||||
" for each ",
|
|
||||||
" for every ",
|
|
||||||
# RAT-1 — standalone per-item quantifiers. "$400 each" is per-unit
|
# RAT-1 — standalone per-item quantifiers. "$400 each" is per-unit
|
||||||
# framing semantically equivalent to "$400 per item". The detection-
|
# framing semantically equivalent to "$400 per item". The detection-
|
||||||
# only currency_amount matcher must refuse this so the per-unit
|
# only currency_amount matcher must refuse this so the per-unit
|
||||||
# composition path (ME-1 / ME-2 currency_per_unit_composition) gets
|
# composition path (ME-1 / ME-2 currency_per_unit_composition) gets
|
||||||
# a turn at the same statement.
|
# a turn at the same statement.
|
||||||
" each ",
|
" each ", " each.", " apiece ", " apiece.",
|
||||||
" each.",
|
|
||||||
" apiece ",
|
|
||||||
" apiece.",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_TEMPORAL_QUANTIFIER_TOKENS: Final[tuple[str, ...]] = (
|
_TEMPORAL_QUANTIFIER_TOKENS: Final[tuple[str, ...]] = (
|
||||||
" per ",
|
" per ", " each ", " every ", " daily", " weekly", " monthly",
|
||||||
" each ",
|
" yearly", " hourly",
|
||||||
" every ",
|
|
||||||
" daily",
|
|
||||||
" weekly",
|
|
||||||
" monthly",
|
|
||||||
" yearly",
|
|
||||||
" hourly",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_MULTIPLICATIVE_CONNECTIVES: Final[tuple[str, ...]] = (
|
_MULTIPLICATIVE_CONNECTIVES: Final[tuple[str, ...]] = (
|
||||||
" with ",
|
" with ", " each ", " in each ", " per each ",
|
||||||
" each ",
|
|
||||||
" in each ",
|
|
||||||
" per each ",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -921,13 +843,9 @@ def _match_discrete_count_statement(
|
||||||
# CandidateInitial post-init whitelist. Widening to owns/holds/contains
|
# CandidateInitial post-init whitelist. Widening to owns/holds/contains
|
||||||
# requires a coordinated CandidateInitial change and lands in a follow-up
|
# requires a coordinated CandidateInitial change and lands in a follow-up
|
||||||
# PR after the framework's empirical lift is operator-reviewed.
|
# PR after the framework's empirical lift is operator-reviewed.
|
||||||
_POSSESSION_VERBS: Final[frozenset[str]] = frozenset(
|
_POSSESSION_VERBS: Final[frozenset[str]] = frozenset({
|
||||||
{
|
"has", "have", "had",
|
||||||
"has",
|
})
|
||||||
"have",
|
|
||||||
"had",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# ADR-0170 W2 — acquisition verbs: surface verbs that grammatically place
|
# ADR-0170 W2 — acquisition verbs: surface verbs that grammatically place
|
||||||
# the actor as the *gainer* of the operand quantity, NOT as having the
|
# the actor as the *gainer* of the operand quantity, NOT as having the
|
||||||
|
|
@ -948,60 +866,28 @@ _POSSESSION_VERBS: Final[frozenset[str]] = frozenset(
|
||||||
#
|
#
|
||||||
# Widening this set is operator-reviewable per the wrong=0 hazard
|
# Widening this set is operator-reviewable per the wrong=0 hazard
|
||||||
# documented in feedback-wrong-zero-hazard-case-0050.
|
# documented in feedback-wrong-zero-hazard-case-0050.
|
||||||
_ACQUISITION_VERBS: Final[frozenset[str]] = frozenset(
|
_ACQUISITION_VERBS: Final[frozenset[str]] = frozenset({
|
||||||
{
|
"collected", "collects", "collect",
|
||||||
"collected",
|
"received", "receives", "receive",
|
||||||
"collects",
|
"bought", "buys", "buy",
|
||||||
"collect",
|
"got", "gets", "get",
|
||||||
"received",
|
})
|
||||||
"receives",
|
|
||||||
"receive",
|
|
||||||
"bought",
|
|
||||||
"buys",
|
|
||||||
"buy",
|
|
||||||
"got",
|
|
||||||
"gets",
|
|
||||||
"get",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Pronoun subjects refused at extraction (ambiguous referent). The
|
# Pronoun subjects refused at extraction (ambiguous referent). The
|
||||||
# extractor requires a concrete proper-noun subject the source span can
|
# extractor requires a concrete proper-noun subject the source span can
|
||||||
# ground.
|
# ground.
|
||||||
_REFUSED_SUBJECT_TOKENS: Final[frozenset[str]] = frozenset(
|
_REFUSED_SUBJECT_TOKENS: Final[frozenset[str]] = frozenset({
|
||||||
{
|
"he", "she", "they", "it", "we", "you", "i",
|
||||||
"he",
|
"him", "her", "them", "us",
|
||||||
"she",
|
})
|
||||||
"they",
|
|
||||||
"it",
|
|
||||||
"we",
|
|
||||||
"you",
|
|
||||||
"i",
|
|
||||||
"him",
|
|
||||||
"her",
|
|
||||||
"them",
|
|
||||||
"us",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Clause-splitting / enumeration markers. Their presence indicates a
|
# Clause-splitting / enumeration markers. Their presence indicates a
|
||||||
# second clause that may carry operations or additional anchors, so
|
# second clause that may carry operations or additional anchors, so
|
||||||
# v1 refuses extraction (skip-only fallback preserves wrong=0).
|
# v1 refuses extraction (skip-only fallback preserves wrong=0).
|
||||||
_CLAUSE_SPLIT_TOKENS: Final[tuple[str, ...]] = (
|
_CLAUSE_SPLIT_TOKENS: Final[tuple[str, ...]] = (
|
||||||
" but ",
|
" but ", " then ", " however ", " before ", " after ",
|
||||||
" then ",
|
" and ", " or ", " while ", " until ", " unless ",
|
||||||
" however ",
|
", and ", ", but ", ", or ", ", then ",
|
||||||
" before ",
|
|
||||||
" after ",
|
|
||||||
" and ",
|
|
||||||
" or ",
|
|
||||||
" while ",
|
|
||||||
" until ",
|
|
||||||
" unless ",
|
|
||||||
", and ",
|
|
||||||
", but ",
|
|
||||||
", or ",
|
|
||||||
", then ",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Hyphenated compound cardinal: 'twenty-five', 'ninety-nine'. These
|
# Hyphenated compound cardinal: 'twenty-five', 'ninety-nine'. These
|
||||||
|
|
@ -1023,11 +909,11 @@ def _extract_discrete_count_re_for(counted_nouns: list[str]) -> re.Pattern[str]:
|
||||||
noun_alt = "|".join(re.escape(n) for n in options)
|
noun_alt = "|".join(re.escape(n) for n in options)
|
||||||
return re.compile(
|
return re.compile(
|
||||||
r"^\s*"
|
r"^\s*"
|
||||||
r"(?P<subject>(?-i:[A-Z][a-z]+))" # case-sensitive proper noun
|
r"(?P<subject>(?-i:[A-Z][a-z]+))" # case-sensitive proper noun
|
||||||
r"\s+(?P<verb>[A-Za-z]+)" # any word; verified against whitelist
|
r"\s+(?P<verb>[A-Za-z]+)" # any word; verified against whitelist
|
||||||
r"\s+(?P<count>\d+|[A-Za-z\-]+)" # integer or word/hyphenated cardinal
|
r"\s+(?P<count>\d+|[A-Za-z\-]+)" # integer or word/hyphenated cardinal
|
||||||
r"\s+(?P<noun>" + noun_alt + r")"
|
r"\s+(?P<noun>" + noun_alt + r")"
|
||||||
r"(?:\b.*)?$", # optional trailing content
|
r"(?:\b.*)?$", # optional trailing content
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1065,8 +951,7 @@ def _extract_discrete_count_re_open(counted_nouns: list[str]) -> re.Pattern[str]
|
||||||
open_tok = rf"(?-i:(?!(?:{_OPEN_NOUN_STOP})\b)[a-z]+)"
|
open_tok = rf"(?-i:(?!(?:{_OPEN_NOUN_STOP})\b)[a-z]+)"
|
||||||
open_noun = rf"{open_tok}(?:\s+{open_tok}){{0,2}}"
|
open_noun = rf"{open_tok}(?:\s+{open_tok}){{0,2}}"
|
||||||
noun_group = (
|
noun_group = (
|
||||||
rf"(?P<noun>{closed_alt}|{open_noun})"
|
rf"(?P<noun>{closed_alt}|{open_noun})" if closed_alt
|
||||||
if closed_alt
|
|
||||||
else rf"(?P<noun>{open_noun})"
|
else rf"(?P<noun>{open_noun})"
|
||||||
)
|
)
|
||||||
return re.compile(
|
return re.compile(
|
||||||
|
|
@ -1074,7 +959,8 @@ def _extract_discrete_count_re_open(counted_nouns: list[str]) -> re.Pattern[str]
|
||||||
r"(?P<subject>(?-i:[A-Z][a-z]+))"
|
r"(?P<subject>(?-i:[A-Z][a-z]+))"
|
||||||
r"\s+(?P<verb>[A-Za-z]+)"
|
r"\s+(?P<verb>[A-Za-z]+)"
|
||||||
r"\s+(?P<count>\d+|[A-Za-z\-]+)"
|
r"\s+(?P<count>\d+|[A-Za-z\-]+)"
|
||||||
r"\s+" + noun_group + r"(?:\b.*)?$",
|
r"\s+" + noun_group +
|
||||||
|
r"(?:\b.*)?$",
|
||||||
flags=re.IGNORECASE,
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1224,25 +1110,12 @@ def _try_extract_discrete_count_anchor(
|
||||||
# appears in the sentence we refuse the compound extraction; the case
|
# appears in the sentence we refuse the compound extraction; the case
|
||||||
# routes to a future phase that handles those shapes.
|
# routes to a future phase that handles those shapes.
|
||||||
_COMPOUND_REFUSE_SUBSTRINGS: Final[tuple[str, ...]] = (
|
_COMPOUND_REFUSE_SUBSTRINGS: Final[tuple[str, ...]] = (
|
||||||
" times ",
|
" times ", " times.", " times,",
|
||||||
" times.",
|
" as long", " as many", " as much", " as old",
|
||||||
" times,",
|
" greater than", " less than", " more than", " fewer than",
|
||||||
" as long",
|
" half as ", " twice as ", " thrice ",
|
||||||
" as many",
|
"%", " percent",
|
||||||
" as much",
|
" half of ", " quarter of ", " third of ",
|
||||||
" as old",
|
|
||||||
" greater than",
|
|
||||||
" less than",
|
|
||||||
" more than",
|
|
||||||
" fewer than",
|
|
||||||
" half as ",
|
|
||||||
" twice as ",
|
|
||||||
" thrice ",
|
|
||||||
"%",
|
|
||||||
" percent",
|
|
||||||
" half of ",
|
|
||||||
" quarter of ",
|
|
||||||
" third of ",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fraction literal pattern (matched against raw statement, not padded).
|
# Fraction literal pattern (matched against raw statement, not padded).
|
||||||
|
|
@ -1292,7 +1165,10 @@ def _try_extract_compound_discrete_count_anchors(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Must have a conjunctive separator — otherwise this isn't compound
|
# Must have a conjunctive separator — otherwise this isn't compound
|
||||||
has_conjunctive = any(tok in padded_lower for tok in (", and ", " and ", ", "))
|
has_conjunctive = any(
|
||||||
|
tok in padded_lower
|
||||||
|
for tok in (", and ", " and ", ", ")
|
||||||
|
)
|
||||||
if not has_conjunctive:
|
if not has_conjunctive:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -1404,7 +1280,6 @@ def _try_extract_compound_discrete_count_anchors(
|
||||||
|
|
||||||
# HYPOTHESIS_CAP enforcement — refusal-preferring rather than truncate
|
# HYPOTHESIS_CAP enforcement — refusal-preferring rather than truncate
|
||||||
from generate.comprehension.state import HYPOTHESIS_CAP
|
from generate.comprehension.state import HYPOTHESIS_CAP
|
||||||
|
|
||||||
if len(anchors) > HYPOTHESIS_CAP:
|
if len(anchors) > HYPOTHESIS_CAP:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -1455,8 +1330,7 @@ def _match_multiplicative_aggregation(
|
||||||
# two needed to admit a multiplicative shape.
|
# two needed to admit a multiplicative shape.
|
||||||
digit_hits = len(_DIGIT_RE.findall(statement))
|
digit_hits = len(_DIGIT_RE.findall(statement))
|
||||||
word_hits = sum(
|
word_hits = sum(
|
||||||
1
|
1 for token in padded.split()
|
||||||
for token in padded.split()
|
|
||||||
if token.strip(".,;:!?\"'()[]{}").lower() in _NUMBER_WORDS
|
if token.strip(".,;:!?\"'()[]{}").lower() in _NUMBER_WORDS
|
||||||
)
|
)
|
||||||
if (digit_hits + word_hits) < 2:
|
if (digit_hits + word_hits) < 2:
|
||||||
|
|
@ -1571,24 +1445,9 @@ def _try_extract_each_weighing_anchor(
|
||||||
|
|
||||||
# matched_anchor must be in CandidateInitial post-init whitelist.
|
# matched_anchor must be in CandidateInitial post-init whitelist.
|
||||||
outer_verb = m.group("outer_verb").lower()
|
outer_verb = m.group("outer_verb").lower()
|
||||||
matched_anchor = (
|
matched_anchor = outer_verb if outer_verb in {
|
||||||
outer_verb
|
"has", "had", "made", "makes", "buys", "bought", "paid", "earned", "saved", "got", "received"
|
||||||
if outer_verb
|
} else "had"
|
||||||
in {
|
|
||||||
"has",
|
|
||||||
"had",
|
|
||||||
"made",
|
|
||||||
"makes",
|
|
||||||
"buys",
|
|
||||||
"bought",
|
|
||||||
"paid",
|
|
||||||
"earned",
|
|
||||||
"saved",
|
|
||||||
"got",
|
|
||||||
"received",
|
|
||||||
}
|
|
||||||
else "had"
|
|
||||||
)
|
|
||||||
|
|
||||||
composed_initial = CandidateInitial(
|
composed_initial = CandidateInitial(
|
||||||
initial=InitialPossession(
|
initial=InitialPossession(
|
||||||
|
|
@ -1716,10 +1575,7 @@ def _try_extract_additive_composition_anchor(
|
||||||
if unit_a.rstrip("s") != unit_b.rstrip("s"):
|
if unit_a.rstrip("s") != unit_b.rstrip("s"):
|
||||||
return None
|
return None
|
||||||
canonical_unit = unit_a
|
canonical_unit = unit_a
|
||||||
if (
|
if canonical_unit not in observed_units and canonical_unit.rstrip("s") not in observed_units:
|
||||||
canonical_unit not in observed_units
|
|
||||||
and canonical_unit.rstrip("s") not in observed_units
|
|
||||||
):
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
count_a_token = m.group("count_a")
|
count_a_token = m.group("count_a")
|
||||||
|
|
@ -1751,11 +1607,9 @@ def _try_extract_additive_composition_anchor(
|
||||||
# Verb whitelist maps to a CandidateInitial.matched_anchor value
|
# Verb whitelist maps to a CandidateInitial.matched_anchor value
|
||||||
# the post-init guard accepts (existing whitelist includes
|
# the post-init guard accepts (existing whitelist includes
|
||||||
# has/have/had/saved/earned/got/received/bought/made/paid).
|
# has/have/had/saved/earned/got/received/bought/made/paid).
|
||||||
matched_anchor = (
|
matched_anchor = verb if verb in {
|
||||||
verb
|
"saved", "earned", "got", "received", "bought", "made", "paid"
|
||||||
if verb in {"saved", "earned", "got", "received", "bought", "made", "paid"}
|
} else "had"
|
||||||
else "had"
|
|
||||||
)
|
|
||||||
|
|
||||||
composed_initial = CandidateInitial(
|
composed_initial = CandidateInitial(
|
||||||
initial=InitialPossession(
|
initial=InitialPossession(
|
||||||
|
|
@ -1787,22 +1641,10 @@ def _try_extract_additive_composition_anchor(
|
||||||
return ((anchor,), "aggregate")
|
return ((anchor,), "aggregate")
|
||||||
|
|
||||||
|
|
||||||
_ADDITIVE_COMPOSITION_VERBS: Final[frozenset[str]] = frozenset(
|
_ADDITIVE_COMPOSITION_VERBS: Final[frozenset[str]] = frozenset({
|
||||||
{
|
"lost", "gained", "earned", "saved", "made", "paid", "spent",
|
||||||
"lost",
|
"bought", "sold", "added", "removed", "received",
|
||||||
"gained",
|
})
|
||||||
"earned",
|
|
||||||
"saved",
|
|
||||||
"made",
|
|
||||||
"paid",
|
|
||||||
"spent",
|
|
||||||
"bought",
|
|
||||||
"sold",
|
|
||||||
"added",
|
|
||||||
"removed",
|
|
||||||
"received",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -1844,34 +1686,15 @@ _SUBTRACTIVE_TWO_QUANTITY_RE: Final[re.Pattern[str]] = re.compile(
|
||||||
_SUBTRACTIVE_COMPOSITION_SHAPE: Final[str] = "bound(initial) − bound(removed)"
|
_SUBTRACTIVE_COMPOSITION_SHAPE: Final[str] = "bound(initial) − bound(removed)"
|
||||||
|
|
||||||
|
|
||||||
_SUBTRACTIVE_INITIAL_VERBS: Final[frozenset[str]] = frozenset(
|
_SUBTRACTIVE_INITIAL_VERBS: Final[frozenset[str]] = frozenset({
|
||||||
{
|
"had", "has", "got", "owns", "owned", "earned", "saved",
|
||||||
"had",
|
"made", "received", "bought",
|
||||||
"has",
|
})
|
||||||
"got",
|
|
||||||
"owns",
|
|
||||||
"owned",
|
|
||||||
"earned",
|
|
||||||
"saved",
|
|
||||||
"made",
|
|
||||||
"received",
|
|
||||||
"bought",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
_SUBTRACTIVE_REMOVAL_VERBS: Final[frozenset[str]] = frozenset(
|
_SUBTRACTIVE_REMOVAL_VERBS: Final[frozenset[str]] = frozenset({
|
||||||
{
|
"lost", "spent", "gave", "donated", "paid", "removed",
|
||||||
"lost",
|
"sold", "used", "consumed",
|
||||||
"spent",
|
})
|
||||||
"gave",
|
|
||||||
"donated",
|
|
||||||
"paid",
|
|
||||||
"removed",
|
|
||||||
"sold",
|
|
||||||
"used",
|
|
||||||
"consumed",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _try_extract_subtractive_composition_anchor(
|
def _try_extract_subtractive_composition_anchor(
|
||||||
|
|
@ -1944,22 +1767,9 @@ def _try_extract_subtractive_composition_anchor(
|
||||||
from generate.math_candidate_parser import CandidateInitial
|
from generate.math_candidate_parser import CandidateInitial
|
||||||
from generate.math_problem_graph import InitialPossession, Quantity
|
from generate.math_problem_graph import InitialPossession, Quantity
|
||||||
|
|
||||||
matched_anchor = (
|
matched_anchor = verb_a if verb_a in {
|
||||||
verb_a
|
"has", "had", "saved", "earned", "got", "received", "bought", "made", "paid",
|
||||||
if verb_a
|
} else "had"
|
||||||
in {
|
|
||||||
"has",
|
|
||||||
"had",
|
|
||||||
"saved",
|
|
||||||
"earned",
|
|
||||||
"got",
|
|
||||||
"received",
|
|
||||||
"bought",
|
|
||||||
"made",
|
|
||||||
"paid",
|
|
||||||
}
|
|
||||||
else "had"
|
|
||||||
)
|
|
||||||
|
|
||||||
composed_initial = CandidateInitial(
|
composed_initial = CandidateInitial(
|
||||||
initial=InitialPossession(
|
initial=InitialPossession(
|
||||||
|
|
@ -2111,76 +1921,25 @@ def match(
|
||||||
# Cross-sentence subject resolution helper (ME-2).
|
# Cross-sentence subject resolution helper (ME-2).
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
_PROPER_NOUN_SUBJECT_RE: Final[re.Pattern[str]] = re.compile(r"^\s*([A-Z][a-zA-Z]+)\b")
|
_PROPER_NOUN_SUBJECT_RE: Final[re.Pattern[str]] = re.compile(
|
||||||
|
r"^\s*([A-Z][a-zA-Z]+)\b"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_COMMON_DETERMINERS_AT_HEAD: Final[frozenset[str]] = frozenset(
|
_COMMON_DETERMINERS_AT_HEAD: Final[frozenset[str]] = frozenset(
|
||||||
{
|
{
|
||||||
# Articles + demonstratives
|
# Articles + demonstratives
|
||||||
"the",
|
"the", "a", "an", "this", "that", "these", "those",
|
||||||
"a",
|
|
||||||
"an",
|
|
||||||
"this",
|
|
||||||
"that",
|
|
||||||
"these",
|
|
||||||
"those",
|
|
||||||
# Possessives
|
# Possessives
|
||||||
"his",
|
"his", "her", "their", "its", "my", "your", "our",
|
||||||
"her",
|
|
||||||
"their",
|
|
||||||
"its",
|
|
||||||
"my",
|
|
||||||
"your",
|
|
||||||
"our",
|
|
||||||
# Sentence-initial connectors / prepositions that get capitalized
|
# Sentence-initial connectors / prepositions that get capitalized
|
||||||
"after",
|
"after", "before", "when", "while", "if", "then", "so", "but",
|
||||||
"before",
|
"and", "or", "during", "since", "until", "though", "although",
|
||||||
"when",
|
"however", "moreover", "additionally", "first", "next", "later",
|
||||||
"while",
|
"finally", "now", "soon", "today", "tomorrow", "yesterday",
|
||||||
"if",
|
"every", "all", "some", "many", "each", "another", "other",
|
||||||
"then",
|
"in", "on", "at", "by", "for", "from", "with", "without",
|
||||||
"so",
|
"how", "why", "what", "where", "who", "when",
|
||||||
"but",
|
|
||||||
"and",
|
|
||||||
"or",
|
|
||||||
"during",
|
|
||||||
"since",
|
|
||||||
"until",
|
|
||||||
"though",
|
|
||||||
"although",
|
|
||||||
"however",
|
|
||||||
"moreover",
|
|
||||||
"additionally",
|
|
||||||
"first",
|
|
||||||
"next",
|
|
||||||
"later",
|
|
||||||
"finally",
|
|
||||||
"now",
|
|
||||||
"soon",
|
|
||||||
"today",
|
|
||||||
"tomorrow",
|
|
||||||
"yesterday",
|
|
||||||
"every",
|
|
||||||
"all",
|
|
||||||
"some",
|
|
||||||
"many",
|
|
||||||
"each",
|
|
||||||
"another",
|
|
||||||
"other",
|
|
||||||
"in",
|
|
||||||
"on",
|
|
||||||
"at",
|
|
||||||
"by",
|
|
||||||
"for",
|
|
||||||
"from",
|
|
||||||
"with",
|
|
||||||
"without",
|
|
||||||
"how",
|
|
||||||
"why",
|
|
||||||
"what",
|
|
||||||
"where",
|
|
||||||
"who",
|
|
||||||
"when",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue