"""Deterministic surface rendering for a propositional ``EntailmentTrace`` (deduction-serve arc, Phase 1). Renders the four ``Entailment`` outcomes into user-facing prose. Deterministic templates only — no LLM, no synthesis; every visible token is either fixed prose or a formula string ``to_deductive_logic`` produced directly from the user's own text. This is the ONLY renderer for propositional-deduction serving; it must not be confused with ``generate.determine.render`` (which renders realized-structure DETERMINE answers — a different gear entirely). """ from __future__ import annotations from generate.proof_chain.entail import ( INCONSISTENT_PREMISES, Entailment, EntailmentTrace, ) #: A/E/I/O categorical form → an English sentence template over (subject, predicate). _CATEGORICAL_PHRASE = { "A": "all {s} are {p}", "E": "no {s} are {p}", "I": "some {s} are {p}", "O": "some {s} are not {p}", } def render_entailment( trace: EntailmentTrace, premises: tuple[str, ...], query: str ) -> str: """The user-facing surface for a propositional deduction verdict. Every branch is a fixed template around the literal premise/query formula strings — no fabricated content. ``trace.outcome`` selects the template; REFUSED further branches on ``trace.reason`` since an inconsistent-premises refusal and an out-of-regime refusal warrant different honest phrasing. """ given = "; ".join(premises) if trace.outcome is Entailment.ENTAILED: return f"Given: {given}. Your premises entail: {query}." if trace.outcome is Entailment.REFUTED: return ( f"Given: {given}. Your premises entail the opposite of " f"{query} — it cannot hold." ) if trace.outcome is Entailment.UNKNOWN: return ( f"Given: {given}. Your premises don't settle whether " f"{query} — it holds in some cases and fails in others." ) # REFUSED if trace.reason == INCONSISTENT_PREMISES: return ( f"Given: {given}. Those premises are inconsistent — they " f"can't all be true, so I won't assert anything from them." ) return f"Given: {given}. I can't evaluate {query} from that as stated." def _categorical_clause(prop: dict) -> str: """One categorical proposition dict → its English clause.""" template = _CATEGORICAL_PHRASE.get(prop.get("form", ""), "{s} ~ {p}") return template.format(s=prop.get("subject", "?"), p=prop.get("predicate", "?")) def render_syllogism(trace: EntailmentTrace, structure: dict, query: dict) -> str: """The user-facing surface for a categorical (syllogism) verdict. Deterministic templates over the argument's own clauses — no synthesis. The ``EntailmentTrace`` is the propositional-lowering decision (``ENTAILED`` ⇒ valid; ``UNKNOWN``/``REFUTED`` ⇒ invalid; ``REFUSED`` ⇒ inconsistent). """ premises = "; ".join( _categorical_clause(p) for p in structure.get("premises", []) ) conclusion = _categorical_clause(query.get("conclusion", {})) if trace.outcome is Entailment.ENTAILED: return f"Given: {premises}. That's valid — {conclusion} follows." if trace.outcome is Entailment.REFUSED and trace.reason == INCONSISTENT_PREMISES: return ( f"Given: {premises}. Those premises are inconsistent — they can't all " f"be true, so I won't assert anything from them." ) if trace.outcome is Entailment.REFUSED: return f"Given: {premises}. I can't evaluate {conclusion} from that as stated." # UNKNOWN or REFUTED — the conclusion is not forced by the premises. return ( f"Given: {premises}. That doesn't follow — {conclusion} isn't guaranteed " f"by those premises." ) __all__ = ["render_entailment", "render_syllogism"]