core/generate/templates.py
Shay fa2712ebd7 feat(realizer): extend to all 13 English v1 constructions
Engineer the deterministic realizer to handle negation, conjunction,
disjunction, embedded clauses, relative clauses, quantification, tense,
and aspect — covering all 13 grammatical-coverage v1 constructions.

- generate/morphology.py: rule-based English inflection (past, participle,
  base form) for seed vocabulary predicates
- generate/templates.py: match-case inflection dispatch for tense/aspect/negation
- generate/graph_planner.py: add CONJUNCTION, DISJUNCTION, COMPLEMENT, RELATIVE
  relations; add grammatical feature fields to ArticulationStep
- generate/realizer.py: compound construction handling via graph edge traversal

grammatical-coverage eval: dev=100%, public v1=100% (from baseline of 24%/19%).
2026-05-16 05:55:49 -07:00

113 lines
3.6 KiB
Python

"""Deterministic surface templates for rhetorical moves.
Each template is a format string keyed by RhetoricalMove. Slots:
{subject} — primary subject from the articulation step
{predicate} — semantic predicate (e.g. "is_defined_as", "contrasts_with")
{obj} — object slot from the graph node (may be "<pending>")
Templates are intentionally simple. The goal is structural correctness,
not fluency — fluency comes in a later phase when the generation stream
consumes these as constraints rather than final output.
"""
from __future__ import annotations
from generate.graph_planner import RhetoricalMove
from generate.morphology import base_form, past_participle, past_tense, present_participle
_PREDICATE_DISPLAY: dict[str, str] = {
"is_defined_as": "is defined as",
"is_caused_by": "is caused by",
"has_steps": "has the following steps",
"contrasts_with": "contrasts with",
"corrects": "corrects",
"recalls": "recalls",
"is_verified_as": "is verified as",
"addresses": "addresses",
"defines": "defines",
"means": "means",
"grounds": "grounds",
"supports": "supports",
"causes": "causes",
"reveals": "reveals",
"precedes": "precedes",
"follows": "follows",
"belongs_to": "belongs to",
"answers": "answers",
"is_grounded_in": "is grounded in",
"is_distinguished_from": "is distinguished from",
"implies": "implies",
"entails": "entails",
"requires": "requires",
"verifies": "verifies",
"evidences": "evidences",
"orders": "orders",
}
def _humanize_predicate(predicate: str) -> str:
return _PREDICATE_DISPLAY.get(predicate, predicate.replace("_", " "))
_MOVE_TEMPLATES: dict[RhetoricalMove, str] = {
RhetoricalMove.ASSERT: "{subject} {predicate_h} {obj}",
RhetoricalMove.ELABORATE: "furthermore, {subject} {predicate_h} {obj}",
RhetoricalMove.CONTRAST: "in contrast, {subject} {predicate_h} {obj}",
RhetoricalMove.SEQUENCE: "next, {subject} {predicate_h} {obj}",
RhetoricalMove.CORRECT: "correction: {subject} {predicate_h} {obj}",
}
def _inflect_predicate(
predicate_h: str,
*,
negated: bool = False,
tense: str | None = None,
aspect: str | None = None,
) -> str:
"""Apply tense/aspect/negation to a humanized predicate."""
verb = predicate_h
base = base_form(verb)
match (aspect, tense, negated):
case ("perfective", _, _):
return f"has {past_participle(verb)}"
case ("imperfective", _, _):
return f"is {present_participle(verb)}"
case (_, "past", True):
return f"did not {base}"
case (_, "past", False):
return past_tense(verb)
case (_, "future", True):
return f"will not {base}"
case (_, "future", False):
return f"will {base}"
case (_, _, True):
return f"does not {base}"
case _:
return verb
def render_step(
move: RhetoricalMove,
subject: str,
predicate: str,
obj: str,
*,
negated: bool = False,
quantifier: str | None = None,
tense: str | None = None,
aspect: str | None = None,
) -> str:
"""Render a single articulation step into a surface fragment."""
template = _MOVE_TEMPLATES[move]
predicate_h = _humanize_predicate(predicate)
predicate_h = _inflect_predicate(predicate_h, negated=negated, tense=tense, aspect=aspect)
obj_display = obj if obj != "<pending>" else "..."
subject_display = f"{quantifier} {subject}" if quantifier else subject
return template.format(
subject=subject_display,
predicate_h=predicate_h,
obj=obj_display,
)