# Understanding the IR (Intermediate Representation) System in ReportEngine and Document Stitching

> Explore ReportEngine's IR system, a JSON contract for chapter structure validation. Learn how Document Stitching merges chapters into cohesive documents with DocumentComposer.

- Repository: [BaiFu/bettafish](https://github.com/666ghj/bettafish)
- Tags: deep-dive
- Published: 2026-02-23

---

**The IR (Intermediate Representation) system in ReportEngine is a canonical JSON contract that validates chapter structure before rendering, while document stitching merges multiple validated chapters into a single cohesive document using the `DocumentComposer` class.**

The **666ghj/bettafish** repository implements a robust reporting engine that bridges LLM-generated content with multiple output formats. The IR system ensures structural integrity through strict schema validation, while the stitching layer orchestrates multi-chapter documents with automatic anchor management and metadata injection.

## What Is the IR (Intermediate Representation) System in ReportEngine?

The IR system serves as the **canonical JSON contract** that every report generated by the engine must obey. It acts as the bridge between raw LLM output and the final rendered documents (HTML, PDF, Markdown), ensuring that downstream renderers never encounter structural crashes.

### Core Components of the IR Schema

The schema definition resides in [`ReportEngine/ir/schema.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/ir/schema.py) and establishes several critical constraints:

- **Versioning**: Every IR payload carries a `version` field defined by `IR_VERSION = "1.0"`, ensuring backward compatibility as the schema evolves.
- **Allowed Inline Marks**: The system restricts text formatting to a whitelist defined in `ALLOWED_INLINE_MARKS`, including `bold`, `italic`, `link`, and other semantic annotations.
- **Allowed Block Types**: Content structure is constrained by `ALLOWED_BLOCK_TYPES`, which enumerates valid building blocks such as `heading`, `paragraph`, `list`, `table`, and `widget`.
- **Chapter Schema**: The `CHAPTER_JSON_SCHEMA` constant defines the complete JSON-Schema for a chapter, specifying required fields like `chapterId`, `title`, `anchor`, `order`, and `blocks`, plus optional metadata extensions.

### IR Validation with IRValidator

The `IRValidator` class in [`ReportEngine/ir/validator.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/ir/validator.py) provides runtime enforcement of the schema. It walks a chapter (or whole document) and checks every field against `CHAPTER_JSON_SCHEMA`, returning a boolean validity flag and a list of human-readable error paths.

```python
from ReportEngine.ir import IRValidator

validator = IRValidator()
is_ok, errors = validator.validate_chapter(chapter_json)

if not is_ok:
    print("Chapter validation failed:", errors)

```

*Source:* [`ReportEngine/ir/validator.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/ir/validator.py)

## How Document Stitching Works in ReportEngine

Document stitching is the process of merging multiple validated chapter JSONs into a single cohesive Document IR. This logic lives in [`ReportEngine/core/stitcher.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/core/stitcher.py) and is encapsulated by the `DocumentComposer` class.

### The DocumentComposer.build_document Method

The `build_document` method orchestrates the assembly process through several distinct phases:

1. **Collect TOC Anchors**: Reads `metadata.toc.customEntries` to map any user-specified `chapterId → anchor` overrides via `_build_toc_anchor_map`.
2. **Sort Chapters**: Orders the supplied chapters list by the `order` field, defaulting to `0` for unspecified entries: `ordered = sorted(chapters, key=lambda c: c.get("order", 0))`.
3. **Inject Defaults**: Generates fallback values for missing required fields:
   - Creates sequential `chapterId` values (`S1`, `S2`, …) if missing.
   - Ensures every chapter has a unique `anchor` using `_ensure_unique_anchor`.
   - Guarantees an `order` value (defaulting to `idx * 10` based on position).
4. **Add Placeholder Headings**: If a chapter is marked as an error placeholder, `_ensure_heading_block` inserts a minimal heading block so the Table of Contents can still reference it.
5. **Compose Final IR**: Returns a dictionary containing `version`, `reportId`, enriched `metadata` (adding `generatedAt` timestamp), `themeTokens`, the ordered `chapters` array, and any global `assets`.

### Handling Anchor Uniqueness and Defaults

The `_ensure_unique_anchor` method maintains an internal `_seen_anchors` set to detect collisions. When a duplicate anchor is detected, the system automatically appends suffixes (`-2`, `-3`, etc.) to ensure globally unique identifiers throughout the stitched document.

```python
from ReportEngine.core.stitcher import DocumentComposer

composer = DocumentComposer()

# Example metadata (could include a custom TOC)

metadata = {
    "title": "年度报告",
    "toc": {
        "customEntries": [
            {"chapterId": "intro", "anchor": "overview"},
            {"chapterId": "conclusion", "anchor": "end"},
        ]
    },
    "themeTokens": {"primary": "#123456"},
    "assets": {"logo": "static/logo.png"},
}

# List of chapter payloads (already validated)

chapters = [chapter1, chapter2, chapter3]

document_ir = composer.build_document(
    report_id="report-2025",
    metadata=metadata,
    chapters=chapters,
)

print(document_ir["version"])          # → "1.0"

print(document_ir["chapters"][0]["anchor"])  # unique, e.g. "overview"

```

*Source:* [`ReportEngine/core/stitcher.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/core/stitcher.py)

## Complete Workflow: From Chapter Generation to Final Document

The typical pipeline for producing a report in the **666ghj/bettafish** repository follows these stages:

1. **Generate each chapter** using LLM or other engines → raw JSON payload.
2. **Validate each chapter** with `IRValidator` to ensure schema compliance.
3. **Collect all validated chapter JSONs** into a sequential list.
4. **Instantiate `DocumentComposer`** and invoke `build_document(report_id, metadata, chapters)`.
5. **Pass the resulting Document IR** to the appropriate renderer (`ReportEngine/renderers/*.py`) for HTML, PDF, or Markdown output.

```python
from ReportEngine.ir import IRValidator
from ReportEngine.core.stitcher import DocumentComposer

validator = IRValidator()
composer = DocumentComposer()

validated_chapters = []
for raw in raw_chapter_jsons:
    ok, errs = validator.validate_chapter(raw)
    if ok:
        validated_chapters.append(raw)
    else:
        raise ValueError(f"Invalid chapter: {errs}")

final_ir = composer.build_document(
    report_id="my-report",
    metadata=global_metadata,
    chapters=validated_chapters,
)

# `final_ir` can now be rendered to HTML, PDF, etc.

```

## Key Files in the IR and Stitching System

| File | Role |
|------|------|
| **[`ReportEngine/ir/schema.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/ir/schema.py)** | Defines the IR version, allowed inline marks, block types, and the JSON-Schema for chapters. |
| **[`ReportEngine/ir/validator.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/ir/validator.py)** | Implements `IRValidator` – the runtime checker that enforces the schema on each chapter. |
| **[`ReportEngine/core/stitcher.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/core/stitcher.py)** | Contains `DocumentComposer`, the component that merges chapter JSONs into a single Document IR, handling ordering, anchor uniqueness, and metadata injection. |
| **[`ReportEngine/utils/config.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/utils/config.py)** | Holds configuration defaults (output directories, template locations) often merged into the final IR’s `metadata`. |
| **[`ReportEngine/scripts/validate_ir.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/scripts/validate_ir.py)** | CLI utility that invokes `IRValidator` on whole IR files – useful for debugging and CI checks. |

These files together form the backbone of the **IR system** and the **document stitching** process, guaranteeing that every generated report conforms to a strict, versioned contract before it reaches the rendering stage.

## Summary

- The **IR (Intermediate Representation)** is a canonical JSON contract defined in [`ReportEngine/ir/schema.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/ir/schema.py) that ensures all report chapters follow a strict, versioned structure before rendering.
- **IR validation** is enforced by the `IRValidator` class in [`ReportEngine/ir/validator.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/ir/validator.py), which checks every chapter against the `CHAPTER_JSON_SCHEMA` and returns detailed error paths for debugging.
- **Document stitching** is handled by `DocumentComposer` in [`ReportEngine/core/stitcher.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/core/stitcher.py), which sorts chapters by `order`, injects default `chapterId` and `anchor` values, ensures global anchor uniqueness, and composes the final Document IR.
- The complete workflow involves generating raw chapters, validating them with `IRValidator`, collecting them into a list, and passing them to `DocumentComposer.build_document()` to produce a renderer-ready JSON payload.

## Frequently Asked Questions

### What is the current IR version in ReportEngine?

The current IR version is defined as `IR_VERSION = "1.0"` in [`ReportEngine/ir/schema.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/ir/schema.py). Every Document IR payload includes this version field to ensure backward compatibility and schema compliance across different versions of the rendering engine.

### How does ReportEngine handle duplicate chapter anchors?

The `DocumentComposer` class maintains an internal `_seen_anchors` set during the stitching process. When `_ensure_unique_anchor` detects a collision, it automatically appends incremental suffixes such as `-2`, `-3`, etc., to create globally unique identifiers throughout the final document.

### What happens if a chapter fails IR validation?

If `IRValidator.validate_chapter()` returns `False`, it provides a detailed list of human-readable error paths indicating which fields violate the `CHAPTER_JSON_SCHEMA`. The typical workflow raises a `ValueError` or logs the errors, preventing invalid chapters from reaching the stitching phase and ensuring renderers never receive malformed data.

### Can I customize the table of contents entries during stitching?

Yes, the `DocumentComposer.build_document` method accepts a `metadata` dictionary that can include `toc.customEntries`. This array maps specific `chapterId` values to custom `anchor` strings, allowing you to override default anchor generation and ensure TOC links point to specific sections as defined in [`ReportEngine/core/stitcher.py`](https://github.com/666ghj/bettafish/blob/main/ReportEngine/core/stitcher.py).