Plattform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Modelle Tools & Services
Lösungen
Organisationen Entwickler Internet Service Provider Managed Service Provider AI-in-a-Box
Ressourcen
Support Documentation Blog Downloads
Unternehmen
Über uns Forschung Karriere Investieren Kontakt
Anmelden

Ingest + edit

You'll: ingest an existing .docx you didn't author, inspect the fidelity envelope, surgically edit one paragraph, and re-render. Then you'll do the same with a ScaiScribe-authored document and see the difference the §4.7 breadcrumb makes.

Setup#

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import os, httpx
from scaiscribe import Client, ScaiKeyAuth

client = Client(
    "https://scaiscribe.scailabs.ai",
    auth=ScaiKeyAuth(
        client_id=os.environ["SCAIKEY_CLIENT_ID"],
        client_secret=os.environ["SCAIKEY_CLIENT_SECRET"],
    ),
    default_tenant_id=os.environ["SCAISCRIBE_TENANT_ID"],
)

Part 1 — Externally-authored DOCX#

Step 1 — Ingest#

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
with open("contract.docx", "rb") as f:
    result = client.ingest_document(
        file=f.read(),
        format="docx",
        theme="office",
    )

print("Doc ID:", result.doc_id)
print("Fidelity score:", result.spec.fidelity.score)
for w in result.spec.fidelity.warnings:
    print(f"  {w.code}: {w.message}")

Sample output for a typical contract:

scdoc
1
2
3
4
5
Doc ID: doc_01HXYZ...
Fidelity score: 0.85
  UNMAPPED_STYLE: Custom paragraph style 'ContractHeader' not mapped to the theme.
  INGEST_PANDOC_FOOTNOTE_DETECTED: Pandoc detected 3 footnote(s)…
  INGEST_IMAGE_DROPPED: Image dropped during ingest…

Step 2 — Decide#

Score What to do
≥ 0.9, no critical warnings Edit and re-render with confidence.
0.6–0.9 Inspect warnings; structural edits may not survive cleanly.
< 0.6 Treat as a content extract; don't expect round-trip fidelity.

For this contract at 0.85, edits to text bodies will work; footnotes will degrade to inline text on re-render.

Step 3 — Edit one paragraph#

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
doc = client.get_document(result.doc_id)
spec = doc.spec

# Find the paragraph mentioning "twelve months" and update to "twenty-four months"
target_id = None
for el in spec["body"]:
    if el.get("type") == "paragraph" and "twelve months" in el.get("markdown", ""):
        target_id = el["elem_id"]
        break

assert target_id, "couldn't find the target paragraph"

client.patch_document(doc.doc_id, [{
    "op": "edit_element",
    "elem_id": target_id,
    "element": {
        "type": "paragraph",
        "markdown": "The agreement term is twenty-four months from the Effective Date.",
    },
}])

Step 4 — Re-render#

python
1
2
3
result = client.finalize(doc.doc_id, formats=["docx", "pdf"])
for r in result.outputs:
    print(f"{r.format}: {r.download_url}")

The re-rendered document carries your edit. The original footnotes degrade to inline text (per the ingest warning); everything else is preserved.

Part 2 — ScaiScribe-authored DOCX (the breadcrumb advantage)#

Setup — render a doc first#

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
doc = client.create_document(format="docx", theme="office", title="Quarterly")
client.add_element(doc.doc_id, {"type": "heading", "level": 1, "text": "Q4"})
client.add_element(doc.doc_id, {
    "type": "code_block",
    "language": "python",
    "content": "def revenue():\n    return 1_200_000",
})
client.add_element(doc.doc_id, {
    "type": "callout",
    "style": "info",
    "markdown": "Strong Q4 — exceeds plan.",
})
client.add_element(doc.doc_id, {"type": "page_break"})
client.add_element(doc.doc_id, {"type": "paragraph", "markdown": "Page 2 content."})

result = client.finalize(doc.doc_id, formats=["docx"])
docx_url = result.outputs[0].download_url

# Stream it down
with httpx.stream("GET", docx_url) as resp:
    with open("/tmp/q4.docx", "wb") as f:
        for chunk in resp.iter_bytes():
            f.write(chunk)

Ingest the round-trip#

python
1
2
3
4
5
6
7
8
9
with open("/tmp/q4.docx", "rb") as f:
    result = client.ingest_document(
        file=f.read(),
        format="docx",
        theme="office",
    )

print("Fidelity score:", result.spec.fidelity.score)
print("Element types:", [el["type"] for el in result.spec.body])

Output:

carbon
1
2
Fidelity score: 1.0
Element types: ['heading', 'code_block', 'callout', 'page_break', 'paragraph']

Every element survives — including code_block, callout, and page_break. The §4.7 JSON breadcrumb at scaiscribe/breadcrumbs.json inside the .docx provided full equivalence; the ingester saw it and used it verbatim instead of reconstructing from mammoth.

Why this matters#

External documents lose information on ingest, because the OOXML doesn't always carry the structural distinctions we'd need (a "Code Block" paragraph and a regular "monospace text" paragraph look the same in raw .docx).

ScaiScribe-authored documents carry a small JSON sidecar declaring what each piece is. That makes the round-trip lossless within ScaiScribe even though Word/LibreOffice happily ignore the sidecar.

Part 3 — Slow-path recovery without the breadcrumb#

What if someone strips the breadcrumb (zip-edits the file, or it goes through a third-party tool that doesn't preserve unknown parts)?

For code_block, callout, and page_break, ScaiScribe's ingester has a backup path. It looks at the OOXML paragraph style names (Code Block, Callout Info, Page Break) and reconstructs the element type from those alone. This means:

  • Strip the breadcrumb from a ScaiScribe DOCX → ingest still recovers code_block, callout, page_break correctly.
  • Other lossy elements (chart, TOC) need the breadcrumb. Without it they degrade to image / inline-text approximations.

External documents that happen to use the same style names (Word's built-in "Code" style, etc.) won't trigger the recovery — the names have to be exact matches.

Common patterns#

Inspect before edit#

Always read the fidelity envelope before mutating. A UNMAPPED_STYLE warning on the section you're about to edit means your text edits will still work but the original visual styling won't survive.

Batch your edits#

PATCH accepts a list of operations applied atomically. Don't do 10 single-element PATCH calls — do one PATCH with 10 ops. Faster and safer.

Don't re-ingest your own output#

If you ingested a document, edited the spec, and re-rendered, the result is ScaiScribe-authored — has the breadcrumb. If you then re-ingest THAT, fidelity will be 1.0 and the spec round-trips perfectly. Don't be surprised when this looks better than the first ingest.

Avoid double-render loops#

A common antipattern: ingest → edit → render → store → later, ingest the stored render → edit → render → store again. Each ingest is fine, but you'll accumulate slight visual drift each time you go through formats that have lossy compilation (e.g. the breadcrumb covers element types but not custom formatting). Keep the spec as your source of truth and only render when you need a binary.

Where to next#

Updated 2026-07-03 13:00:10 View source (.md) rev 10