| """Rewrite planning — decide safety and ranked template candidates."""
|
|
|
| from __future__ import annotations
|
|
|
| from app.engine.classify import classify_sentence, is_rewriteable_type
|
| from app.engine.models import RewritePlan, SentenceSlots, TemplateCandidate
|
| from app.engine.parse import extract_slots
|
| from app.engine.templates import can_swap_initial_subordinate, rank_templates
|
| from app.engine.voice import active_to_passive
|
|
|
|
|
| def build_plan(text: str, *, min_confidence: float = 0.55) -> RewritePlan:
|
| """Create a rewrite plan without generating text yet."""
|
| raw = (text or "").strip()
|
| if not raw:
|
| return RewritePlan(safe=False, skip_reason="empty")
|
|
|
| kind = classify_sentence(raw)
|
| if kind == "because_clause":
|
| return RewritePlan(
|
| safe=True,
|
| template_id="because_front",
|
| candidates=[TemplateCandidate("because_front", 0.85)],
|
| confidence=0.85,
|
| slots=SentenceSlots(text=raw, sentence_type=kind, confidence=0.85),
|
| movable=["because_clause"],
|
| fixed_spans=[],
|
| )
|
|
|
| if kind == "complex":
|
| if can_swap_initial_subordinate(raw):
|
| slots = extract_slots(raw)
|
| slots.confidence = 0.76
|
| return RewritePlan(
|
| safe=True,
|
| template_id="complex_clause_swap",
|
| candidates=[TemplateCandidate("complex_clause_swap", 0.76)],
|
| confidence=0.76,
|
| slots=slots,
|
| movable=["subordinate_clause"],
|
| fixed_spans=list(slots.entities),
|
| )
|
|
|
| passive_candidate = active_to_passive(raw)
|
| if passive_candidate:
|
| slots = extract_slots(raw)
|
| slots.confidence = 0.7
|
| return RewritePlan(
|
| safe=True,
|
| template_id="active_to_passive",
|
| candidates=[TemplateCandidate("active_to_passive", 0.7)],
|
| confidence=0.7,
|
| slots=slots,
|
| movable=["subject", "object"],
|
| fixed_spans=list(slots.entities),
|
| )
|
| return RewritePlan(
|
| safe=False,
|
| skip_reason="complex",
|
| confidence=0.0,
|
| slots=SentenceSlots(text=raw, sentence_type=kind),
|
| )
|
|
|
| if not is_rewriteable_type(kind):
|
| passive_candidate = active_to_passive(raw)
|
| if passive_candidate:
|
| slots = extract_slots(raw)
|
| slots.confidence = 0.7
|
| return RewritePlan(
|
| safe=True,
|
| template_id="active_to_passive",
|
| candidates=[TemplateCandidate("active_to_passive", 0.7)],
|
| confidence=0.7,
|
| slots=slots,
|
| movable=["subject", "object"],
|
| fixed_spans=list(slots.entities),
|
| )
|
| return RewritePlan(
|
| safe=False,
|
| skip_reason=kind,
|
| confidence=0.0,
|
| slots=SentenceSlots(text=raw, sentence_type=kind),
|
| )
|
|
|
| slots = extract_slots(raw)
|
| if "low_confidence" in slots.reasons or slots.confidence < min_confidence:
|
| return RewritePlan(
|
| safe=False,
|
| skip_reason="low_confidence",
|
| confidence=slots.confidence,
|
| slots=slots,
|
| )
|
| if any(r.startswith("skip:") for r in slots.reasons):
|
| return RewritePlan(
|
| safe=False,
|
| skip_reason=slots.reasons[0],
|
| confidence=slots.confidence,
|
| slots=slots,
|
| )
|
|
|
| candidates = rank_templates(slots)
|
| structural_ids = {candidate.template_id for candidate in candidates}
|
| if not structural_ids or structural_ids <= {"subj_verb_object"}:
|
| if active_to_passive(raw):
|
| candidates.append(TemplateCandidate("active_to_passive", 0.7))
|
| if not candidates:
|
| return RewritePlan(
|
| safe=False,
|
| skip_reason="no_template",
|
| confidence=slots.confidence,
|
| slots=slots,
|
| )
|
|
|
|
|
| candidates = [c for c in candidates if c.confidence >= min_confidence]
|
| if not candidates:
|
| return RewritePlan(
|
| safe=False,
|
| skip_reason="low_template_confidence",
|
| confidence=slots.confidence,
|
| slots=slots,
|
| )
|
|
|
| fixed = list(slots.entities)
|
| if slots.negation:
|
| fixed.append(slots.negation)
|
| movable = [x for x in ("time", "manner", "place") if getattr(slots, x, "")]
|
|
|
| return RewritePlan(
|
| safe=True,
|
| slots=slots,
|
| template_id=candidates[0].template_id,
|
| candidates=candidates,
|
| fixed_spans=fixed,
|
| movable=movable,
|
| confidence=candidates[0].confidence,
|
| )
|
|
|