docx-redline v1.0.2
16

Structured model output

validate_edits(payload) -> list[str] schema only, no document access load_edits(payload, strict=True) -> list[RedlineEdit | ReviewNote] strict=True raises rather than silently dropping items: a dropped finding is a review that quietly did less than it claimed.

examples/16_model_output.py โ€” ran in 0.23s ยท exit 0

Console output

============================================================================
16 ยท Model output
============================================================================

--- kind defaults to 'edit'; every field is optional except para_id/target ---
  RedlineEdit  p19 'thirty (30) days' [payment-terms]
  RedlineEdit  p19 'of the invoice date' [payment-terms]
  ReviewNote   p15 'reverse engineer, decompile, or di' [IP]

--- straight into apply() ---
3 applied, 0 rejected
  APPLIED  [payment-terms] p19: 'thirty (30) days'
  APPLIED  [payment-terms] p19: 'of the invoice date'
  APPLIED  [IP] p15: 'reverse engineer, decompile, or disassemble'

--- validate_edits โ€” every problem it catches ---
   #0: 'edit' does not accept 'replacment'
   #0: 'edit' requires 'replacement' (use "" to delete)
   #1: 'edit' requires 'replacement' (use "" to delete)
   #2: 'note' requires 'body'
   #3: unknown kind 'annotation', expected 'edit' or 'note'
   #4: 'edit' requires 'target'
   #5: severity 'urgent' not in ('low', 'medium', 'high', 'critical')
   #6: occurrence must be >= 0
   #7: occurrence must be an integer

--- strict=True (the default) refuses the whole batch ---
  RedlineError: malformed edit payload:
   9 problems listed

--- strict=False keeps what parsed, drops what did not ---
  4 in -> 3 out: ['RedlineEdit', 'RedlineEdit', 'ReviewNote']

--- a clean payload validates to nothing ---
  validate_edits(GOOD) -> no problems

--- round-tripping the report back to JSON ---
{
  "applied": 3,
  "rejected": 1,
  "results": [
    {
      "para_id": 19,
      "target": "thirty (30) days",
      "agent": "payment-terms",
      "applied": true,
      "reason": null,
      "detail": "",
      "spans": 1
    },
    {
      "para_id": 19,
      "target": "of the invoice date",
      "agent": "payment-terms",
      "applied": true,
      "reason": null,
      "detail": "",
      "spans": 1
    },
    {
      "para_id": 15,
      "target": "reverse engineer, decompile, or disassemble",
      "ag ...

Source

"""16 ยท Structured model output.

    validate_edits(payload) -> list[str]        schema only, no document access
    load_edits(payload, strict=True) -> list[RedlineEdit | ReviewNote]

strict=True raises rather than silently dropping items: a dropped finding is a
review that quietly did less than it claimed.
"""

import json

from _shared import banner, fresh, section

from docx_redline import (
    ParagraphIndex,
    RedlineError,
    ReviewNote,
    load_edits,
    validate_edits,
)

banner("16 ยท Model output")

GOOD = json.loads("""[
  {"kind": "edit", "para_id": 19,
   "target": "thirty (30) days", "replacement": "forty-five (45) days",
   "agent": "payment-terms", "severity": "high",
   "rationale": "Net 45 matches our AP cycle."},

  {"kind": "edit", "para_id": 19,
   "target": "of the invoice date", "replacement": "of receipt of a valid invoice",
   "agent": "payment-terms", "severity": "low", "occurrence": 1,
   "insertion_first": false, "rationale": "Invoices are dated before they are sent."},

  {"kind": "note", "para_id": 15,
   "target": "reverse engineer, decompile, or disassemble",
   "body": "No carve-out for interoperability. Check local law.",
   "agent": "IP", "severity": "medium", "occurrence": 1}
]""")

section("kind defaults to 'edit'; every field is optional except para_id/target")
items = load_edits(GOOD)
for item in items:
    print(f"  {type(item).__name__:<12} p{item.para_id} {item.target[:34]!r} [{item.agent}]")

section("straight into apply()")
rl = fresh()
index = ParagraphIndex(rl)
print(index.apply(items).summary())

section("validate_edits โ€” every problem it catches")
BAD = [
    {"para_id": 0, "target": "x", "replacment": "y"},  # typo'd key
    {"para_id": 0, "target": "x"},  # no replacement
    {"kind": "note", "para_id": 0, "target": "x"},  # note with no body
    {"kind": "annotation", "para_id": 0, "target": "x"},  # unknown kind
    {"para_id": 0, "target": "", "replacement": "y"},  # empty target
    {"para_id": 0, "target": "x", "replacement": "y", "severity": "urgent"},
    {"para_id": 0, "target": "x", "replacement": "y", "occurrence": -1},
    {"para_id": 0, "target": "x", "replacement": "y", "occurrence": "two"},
]
for problem in validate_edits(BAD):
    print("  ", problem)

section("strict=True (the default) refuses the whole batch")
try:
    load_edits(BAD)
except RedlineError as exc:
    print("  RedlineError:", str(exc).splitlines()[0])
    print("  ", len(str(exc).splitlines()) - 1, "problems listed")

section("strict=False keeps what parsed, drops what did not")
mixed = [*GOOD, {"kind": "annotation", "para_id": 0, "target": "x"}]
kept = load_edits(mixed, strict=False)
print(f"  {len(mixed)} in -> {len(kept)} out:", [type(i).__name__ for i in kept])

section("a clean payload validates to nothing")
print("  validate_edits(GOOD) ->", validate_edits(GOOD) or "no problems")

section("round-tripping the report back to JSON")
rl = fresh()
index = ParagraphIndex(rl)
report = index.apply([*load_edits(GOOD), ReviewNote(19, "no such phrase", "orphan note")])
print(json.dumps(report.to_dict(), indent=2)[:520], "...")

What it wrote

This example prints its result rather than saving a document โ€” read the console output beside it.