"""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 "") 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 != "" else "..." subject_display = f"{quantifier} {subject}" if quantifier else subject return template.format( subject=subject_display, predicate_h=predicate_h, obj=obj_display, )