npm SDK Breadcrumbs
Start withgetSectionRules 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.
import { getSectionRules } from '@artosai/sdk'
const result = await getSectionRules({
sectionId,
accessToken,
baseUrl
})
Inspect a rule
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:rule.templateResourceId
rule.sourceChunk?.resourceId
rule.sourceChunks[].resourceId
rule.table?.resourceId
openFile:
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)
}
}
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.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.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.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
generationexplains why the rule was selected, including search evidence, selection rationale, missing-content notes, and expansion reasoning.execution_pre_postprocessingrecords status, duration, iterations, input/output sizes, execution steps, errors, and missing data.postprocessingrecords whether cleanup changed the content and why.style_guide_textrecords 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: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
{
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
{
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
// 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']
}
table_structure_html, sanitize it before inserting it into
the DOM. Treat it as untrusted HTML even when it came from your own document.
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.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: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)
})