> ## Documentation Index
> Fetch the complete documentation index at: https://docs.artosai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# npm SDK Breadcrumbs

> Read section-rule breadcrumbs, metadata, and referenced resources with the Artos npm SDK.

# npm SDK Breadcrumbs

Start with `getSectionRules` when you need to understand how an MRT section
was produced. Each rule can include a breadcrumb trace, metadata, and links to
the files it used.

```javascript theme={null}
import { getSectionRules } from '@artosai/sdk'

const result = await getSectionRules({
    sectionId,
    accessToken,
    baseUrl
})
```

## Inspect a rule

```javascript theme={null}
for (const rule of result.rules) {
    console.log(rule.ruleId)
    console.log(rule.description)
    console.log(rule.breadcrumbs)
    console.log(rule.ruleMetadata)
}
```

`breadcrumbs` is the structured execution trace, when one was recorded.
`ruleMetadata` contains rule-specific information. Either field can be `null`.

## Open a file referenced by a rule

Depending on the rule, the file resource ID may be found in one of these fields:

```javascript theme={null}
rule.templateResourceId
rule.sourceChunk?.resourceId
rule.sourceChunks[].resourceId
rule.table?.resourceId
```

Pass the returned ID directly to `openFile`:

```javascript theme={null}
import { openFile } from '@artosai/sdk'

for (const rule of result.rules) {
    const resourceId =
        rule.sourceChunk?.resourceId ||
        rule.sourceChunks[0]?.resourceId ||
        rule.templateResourceId

    if (!resourceId) continue

    const opened = await openFile({
        resourceId,
        viewer: 'apryse',
        accessToken,
        baseUrl
    })

    if (opened.viewer === 'apryse') {
        console.log(opened.contentType, opened.blob.size)
    }
}
```

Do not use the deprecated `templateUrl` or `presignedUrl` compatibility fields
as browser URLs. Use the associated resource ID with `openFile` instead.

## Fetch and summarize all rules

This helper is useful when building a rules panel or logging the result of a
section run. It surfaces the rule type, confidence, resource IDs, breadcrumb
stages, and metadata without requiring a separate request for each rule.

```javascript theme={null}
import { getSectionRules } from '@artosai/sdk'

async function fetchRules(sectionId, accessToken, baseUrl) {
    const result = await getSectionRules({
        sectionId,
        accessToken,
        baseUrl
    })

    console.log(`Found ${result.rules.length} rules`)

    for (const [index, rule] of result.rules.entries()) {
        console.log(
            `Rule ${index + 1}: ${rule.ruleType} ` +
            `(confidence: ${rule.confidenceScore ?? 'n/a'})`
        )

        if (rule.templateResourceId) {
            console.log(`  template resource: ${rule.templateResourceId}`)
        }

        if (rule.breadcrumbs) {
            const stages = Object.keys(rule.breadcrumbs.stages || {})
            console.log(`  breadcrumb stages: ${stages.join(', ')}`)
        }

        if (rule.ruleMetadata) {
            console.log(`  metadata: ${Object.keys(rule.ruleMetadata).join(', ')}`)
        }
    }

    return result.rules
}
```

## Build a React rules inspector

The following is a starting point for a panel that lets a user expand a rule
and inspect its generated content, sources, table data, breadcrumbs, and
metadata. The styling is intentionally left to the application.

```jsx theme={null}
import { useEffect, useState } from 'react'
import { getSectionRules, openFile } from '@artosai/sdk'

export function RulesInspector({ sectionId, accessToken, baseUrl }) {
    const [rules, setRules] = useState([])
    const [loading, setLoading] = useState(true)
    const [error, setError] = useState(null)
    const [expandedRule, setExpandedRule] = useState(null)

    useEffect(() => {
        let cancelled = false

        async function loadRules() {
            try {
                setLoading(true)
                setError(null)
                const result = await getSectionRules({
                    sectionId,
                    accessToken,
                    baseUrl
                })
                if (!cancelled) setRules(result.rules)
            } catch (err) {
                if (!cancelled) setError(err)
            } finally {
                if (!cancelled) setLoading(false)
            }
        }

        if (sectionId) loadRules()
        return () => { cancelled = true }
    }, [sectionId, accessToken, baseUrl])

    if (loading) return <div>Loading rules...</div>
    if (error) return <div role="alert">Failed to load rules: {error.message}</div>
    if (!rules.length) return <div>No rules found for this section.</div>

    return (
        <div className="rules-inspector">
            {rules.map((rule, index) => (
                <article key={rule.ruleId || index} className="rule-card">
                    <button
                        type="button"
                        onClick={() => setExpandedRule(
                            expandedRule === index ? null : index
                        )}
                    >
                        #{(rule.orderIndex ?? index) + 1} {rule.ruleType}
                        {' — '}
                        {rule.confidenceScore == null
                            ? 'confidence unavailable'
                            : `${(rule.confidenceScore * 100).toFixed(0)}% confidence`}
                    </button>

                    <p>{rule.generatedContent?.slice(0, 300)}</p>

                    {expandedRule === index && (
                        <div className="rule-details">
                            <SourceChunks
                                rule={rule}
                                accessToken={accessToken}
                                baseUrl={baseUrl}
                            />
                            {rule.table && <TableDetails table={rule.table} />}
                            {rule.breadcrumbs && (
                                <BreadcrumbsTrail breadcrumbs={rule.breadcrumbs} />
                            )}
                            {rule.ruleMetadata && (
                                <RuleMetadataPanel metadata={rule.ruleMetadata} />
                            )}
                        </div>
                    )}
                </article>
            ))}
        </div>
    )
}

function SourceChunks({ rule, accessToken, baseUrl }) {
    const chunks = rule.sourceChunk
        ? [rule.sourceChunk]
        : (rule.sourceChunks || [])

    if (!chunks.length) return null

    return (
        <section>
            <h4>Sources ({chunks.length})</h4>
            {chunks.map(chunk => (
                <div key={chunk.chunkId} className="source-chunk-card">
                    <strong>{chunk.sectionName || 'Source document'}</strong>
                    {chunk.pageNumber != null && <span>Page {chunk.pageNumber}</span>}
                    <p>{chunk.content?.slice(0, 300)}</p>
                    {chunk.resourceId && (
                        <SourcePreviewButton
                            resourceId={chunk.resourceId}
                            accessToken={accessToken}
                            baseUrl={baseUrl}
                        />
                    )}
                </div>
            ))}
        </section>
    )
}

function TableDetails({ table }) {
    return (
        <section>
            <h4>{table.tableTitle || 'Table'}</h4>
            {table.tableMarkdown && <pre>{table.tableMarkdown}</pre>}
            {table.resourceId && <p>Table resource: {table.resourceId}</p>}
        </section>
    )
}

function SourcePreviewButton({ resourceId, accessToken, baseUrl }) {
    return (
        <button
            type="button"
            onClick={async () => {
                const result = await openFile({
                    resourceId,
                    viewer: 'apryse',
                    accessToken,
                    baseUrl
                })
                if (result.viewer !== 'apryse') return

                const url = URL.createObjectURL(result.blob)
                window.open(url, '_blank', 'noopener,noreferrer')
                // Revoke this URL when the preview is closed or replaced.
            }}
        >
            Open source
        </button>
    )
}
```

## Breadcrumb structure

The object has a stable top-level shape. Some stages are omitted when they do
not apply to a rule.

```text theme={null}
breadcrumbs
├── schema_version
├── created_at
├── updated_at
├── trace
│   ├── rule_trace_id
│   ├── rule_type
│   ├── section_title
│   ├── blueprint_rule_id
│   └── source_chunk_ids
├── stages
│   ├── generation
│   ├── execution_pre_postprocessing
│   ├── postprocessing
│   └── style_guide_text
└── trace_display
    ├── decision_notes
    │   ├── completed
    │   ├── incomplete
    │   └── missing
    └── discrepancies
```

The four stage keys describe different parts of the pipeline:

1. `generation` explains why the rule was selected, including search evidence,
   selection rationale, missing-content notes, and expansion reasoning.
2. `execution_pre_postprocessing` records status, duration, iterations,
   input/output sizes, execution steps, errors, and missing data.
3. `postprocessing` records whether cleanup changed the content and why.
4. `style_guide_text` records style-guide decisions, references, and changes.

## Render a breadcrumb audit trail

This component turns the trace into a compact summary with expandable stages.
The exact CSS is application-specific, but the data handling can look like
this:

```jsx theme={null}
function BreadcrumbsTrail({ breadcrumbs }) {
    if (!breadcrumbs) return null

    const stages = breadcrumbs.stages || {}
    const display = breadcrumbs.trace_display || {}
    const stageOrder = [
        ['generation', 'Generation'],
        ['execution_pre_postprocessing', 'Execution'],
        ['postprocessing', 'Post-processing'],
        ['style_guide_text', 'Style guide']
    ]

    return (
        <section className="breadcrumbs-trail">
            <h4>Processing audit trail</h4>

            <DecisionNotes notes={display.decision_notes} />

            {display.discrepancies?.length > 0 && (
                <div className="discrepancies-panel">
                    <h5>Flagged issues</h5>
                    {display.discrepancies.map((item, index) => (
                        <div key={index}>
                            <strong>{item.title}</strong>
                            {item.description?.map((text, i) => <p key={i}>{text}</p>)}
                        </div>
                    ))}
                </div>
            )}

            {stageOrder.map(([key, label]) => {
                const stage = stages[key]
                if (!stage) return null

                return (
                    <details key={key}>
                        <summary>{label}: <StageStatus stage={stage} keyName={key} /></summary>
                        <StageDetails stage={stage} keyName={key} />
                    </details>
                )
            })}
        </section>
    )
}

function DecisionNotes({ notes }) {
    if (!notes) return null
    return (
        <div className="decision-notes">
            {['completed', 'incomplete', 'missing'].map(key => (
                notes[key]?.length > 0 && (
                    <div key={key}>
                        <strong>{key}</strong>
                        <ul>{notes[key].map((note, i) => <li key={i}>{note}</li>)}</ul>
                    </div>
                )
            ))}
        </div>
    )
}

function StageStatus({ stage, keyName }) {
    if (keyName === 'generation') return null
    if (typeof stage.changed === 'boolean') return stage.changed ? 'changed' : 'unchanged'
    return stage.execution_trace?.status || 'status unavailable'
}

function StageDetails({ stage, keyName }) {
    if (keyName === 'generation') {
        const notes = stage.rule_decision_notes
        const search = stage.selection?.search_final_answer?.notes
        const generated = stage.why_generated?.rule_generation_final_answer
        return (
            <div>
                {notes?.rationale && <p><b>Selection rationale:</b> {notes.rationale}</p>}
                {notes?.why_why_not && <p><b>Why this rule:</b> {notes.why_why_not}</p>}
                {search && <p><b>Search evidence:</b> {search}</p>}
                {generated?.source_notes && <p><b>Source notes:</b> {generated.source_notes}</p>}
                {generated?.missing_content_notes && <p><b>Missing content:</b> {generated.missing_content_notes}</p>}
            </div>
        )
    }

    if (keyName === 'execution_pre_postprocessing') {
        const trace = stage.execution_trace
        if (!trace) return <p>No execution trace available.</p>
        return (
            <div>
                <p>Status: {trace.status}</p>
                {trace.duration_ms != null && <p>Duration: {trace.duration_ms}ms</p>}
                {trace.iteration_count != null && <p>Iterations: {trace.iteration_count}</p>}
                {trace.summary && <p>{trace.summary}</p>}
                {trace.error && <p role="alert">{trace.error}</p>}
                {trace.steps?.length > 0 && (
                    <ul>
                        {trace.steps.map((step, i) => (
                            <li key={i}>
                                <b>{step.step_label || step.step_key}</b>: {step.status}
                                {step.summary && ` — ${step.summary}`}
                            </li>
                        ))}
                    </ul>
                )}
            </div>
        )
    }

    return (
        <div>
            <p>Content {stage.changed ? 'was' : 'was not'} changed in this stage.</p>
            {stage.change_attribution?.summary && <p>{stage.change_attribution.summary}</p>}
            {stage.domain_rationales?.map((domain, i) => (
                <div key={i}>
                    <b>{domain.domain_id}</b> ({domain.status})
                    {domain.rationale && <p>{domain.rationale}</p>}
                    {domain.why_not && <p>{domain.why_not}</p>}
                    {domain.style_guide_refs?.length > 0 && (
                        <p>References: {domain.style_guide_refs.join(', ')}</p>
                    )}
                </div>
            ))}
        </div>
    )
}
```

## Rule metadata by type

Metadata varies by rule type. These are representative shapes; applications
should treat unknown fields as optional.

### `template_text`

```javascript theme={null}
{
    rule_type: 'template_text',
    section_id: 'section-uuid',
    section_title: 'Section name',
    user_instructions: 'Instructions for generation',
    data_instructions: 'Instructions for extracting data'
}
```

### `template_table`

```javascript theme={null}
{
    rule_type: 'template_table',
    section_id: 'section-uuid',
    section_title: 'Section name',
    user_instructions: 'Instructions for generation',
    data_instructions: 'Instructions for extracting data',
    table_context_instructions: 'Table-specific context',
    template_table: {
        template_table_id: 'table-uuid',
        blueprint_rule_id: 'rule-uuid',
        table_structure_html: '<table>...</table>',
        table_caption: 'Table 1',
        repeat_key: 'study_arm',
        expansion_notes: 'Notes about expansion',
        can_spawn_multiple: true,
        spawn_multiple_instructions: 'When to create multiple tables'
    }
}
```

### Extraction and intra-document summary rules

```javascript theme={null}
// summarize_content or copy_paste
{
    rule_type: 'summarize_content',
    section_id: 'section-uuid',
    section_title: 'Section name',
    section_index: 0,
    rule_index: 0,
    step_name: 'rule_execution'
}

// intra_document_summarize
{
    rule_type: 'intra_document_summarize',
    section_id: 'section-uuid',
    section_title: 'Section name',
    target_section_ids_for_summary: ['section-uuid-1', 'section-uuid-2']
}
```

When rendering `table_structure_html`, sanitize it before inserting it into
the DOM. Treat it as untrusted HTML even when it came from your own document.

```jsx theme={null}
function RuleMetadataPanel({ metadata, sanitizeHtml }) {
    if (!metadata) return null

    return (
        <section className="rule-metadata-panel">
            <h4>Rule metadata</h4>
            {metadata.user_instructions && <p>{metadata.user_instructions}</p>}
            {metadata.data_instructions && <p>{metadata.data_instructions}</p>}
            {metadata.table_context_instructions && (
                <p>{metadata.table_context_instructions}</p>
            )}
            {metadata.template_table && (
                <div>
                    <h5>Template table</h5>
                    {metadata.template_table.table_caption && (
                        <p>{metadata.template_table.table_caption}</p>
                    )}
                    {metadata.template_table.table_structure_html && (
                        <div
                            dangerouslySetInnerHTML={{
                                __html: sanitizeHtml(
                                    metadata.template_table.table_structure_html
                                )
                            }}
                        />
                    )}
                    {metadata.template_table.repeat_key && (
                        <code>{metadata.template_table.repeat_key}</code>
                    )}
                </div>
            )}
            {metadata.target_section_ids_for_summary?.length > 0 && (
                <ul>
                    {metadata.target_section_ids_for_summary.map(id => (
                        <li key={id}>{id}</li>
                    ))}
                </ul>
            )}
        </section>
    )
}
```

## Walk through every section

For a document-level rules view, first list the sections and then fetch the
rules for each section. This gives the UI enough context to render navigation,
rule details, and breadcrumb warnings together.

```javascript theme={null}
import { getDocumentSections, getSectionRules } from '@artosai/sdk'

async function buildDocumentRulesView(documentId, accessToken, baseUrl) {
    const { sections } = await getDocumentSections({
        documentId,
        accessToken,
        baseUrl
    })

    const sectionRules = await Promise.all(
        sections.map(async section => ({
            section,
            rules: (await getSectionRules({
                sectionId: section.sectionId,
                accessToken,
                baseUrl
            })).rules
        }))
    )

    for (const { section, rules } of sectionRules) {
        console.log(`Section: ${section.sectionTitle}`)
        console.log(`Rules: ${rules.length}`)

        for (const rule of rules) {
            const discrepancies = rule.breadcrumbs?.trace_display?.discrepancies || []
            if (discrepancies.length) {
                console.warn(
                    `Rule ${rule.orderIndex} has ${discrepancies.length} flagged issue(s)`
                )
            }
        }
    }

    return sectionRules
}
```

## Filter rules by type

Different rule types usually deserve different UI treatments:

```javascript theme={null}
function categorizeRules(rules) {
    return {
        templateText: rules.filter(rule => rule.ruleType === 'template_text'),
        templateTable: rules.filter(rule => rule.ruleType === 'template_table'),
        copyPaste: rules.filter(rule => rule.ruleType === 'copy_paste'),
        summarize: rules.filter(rule => rule.ruleType === 'summarize_content'),
        other: rules.filter(rule => ![
            'template_text',
            'template_table',
            'copy_paste',
            'summarize_content'
        ].includes(rule.ruleType))
    }
}

const { rules } = await getSectionRules({
    sectionId,
    accessToken,
    baseUrl
})
const groups = categorizeRules(rules)

groups.templateText.forEach(rule => {
    renderTemplateComparison(
        rule.ruleTemplateText,
        rule.generatedContent,
        rule.templateResourceId
    )
})

groups.copyPaste.forEach(rule => {
    renderExtractionCard(rule.generatedContent, rule.sourceChunk)
})
```
