@@ -201,6 +201,7 @@ pub(crate) struct PreparedParseDocument {
201201 pub structured_payloads : Vec < serde_json:: Value > ,
202202 pub tables : Vec < serde_json:: Value > ,
203203 pub pages : Vec < serde_json:: Value > ,
204+ pub elements : Vec < serde_json:: Value > ,
204205 pub structural_summary : String ,
205206 pub document_runtime : Option < serde_json:: Value > ,
206207 pub document_quality : Option < crate :: document_pipeline:: DocumentQualityReport > ,
@@ -414,6 +415,7 @@ pub(crate) fn prepare_parse_document_from_path(
414415 let structured_payloads = summarize_structured_payloads ( & raw_document, block_kind_label) ;
415416 let tables = extract_tables_for_output ( & raw_document) ;
416417 let pages = summarize_pages_for_parse_report ( & raw_document) ;
418+ let elements = extract_elements_for_output ( & raw_document) ;
417419 let structural_summary = build_structural_summary (
418420 & raw_document,
419421 crate :: document_service_types:: structural_summary_style_for_strategy_label ( & strategy_label) ,
@@ -455,6 +457,7 @@ pub(crate) fn prepare_parse_document_from_path(
455457 structured_payloads,
456458 tables,
457459 pages,
460+ elements,
458461 structural_summary,
459462 document_runtime,
460463 document_quality,
@@ -1242,6 +1245,7 @@ pub(crate) struct ParseResultInput<'a> {
12421245 pub structured_payloads : & ' a [ serde_json:: Value ] ,
12431246 pub tables : & ' a [ serde_json:: Value ] ,
12441247 pub pages : & ' a [ serde_json:: Value ] ,
1248+ pub elements : & ' a [ serde_json:: Value ] ,
12451249 pub max_chars : usize ,
12461250 pub structural_summary : & ' a str ,
12471251 pub llm_answer : Option < & ' a str > ,
@@ -1315,6 +1319,7 @@ pub(crate) fn build_parse_result(input: &ParseResultInput<'_>) -> BuiltParseResu
13151319 "structured_payloads" : input. structured_payloads,
13161320 "tables" : input. tables,
13171321 "pages" : input. pages,
1322+ "elements" : input. elements,
13181323 "max_chars" : input. max_chars,
13191324 "query_aware_selection" : input. query_used,
13201325 } ) ;
@@ -1356,6 +1361,7 @@ pub(crate) fn build_parse_tool_output_from_prepared(
13561361 structured_payloads : & prepared. structured_payloads ,
13571362 tables : & prepared. tables ,
13581363 pages : & prepared. pages ,
1364+ elements : & prepared. elements ,
13591365 max_chars,
13601366 structural_summary : & prepared. structural_summary ,
13611367 llm_answer,
@@ -1508,6 +1514,109 @@ fn extract_tables_for_output(doc: &ParsedDocument) -> Vec<serde_json::Value> {
15081514 . collect ( )
15091515}
15101516
1517+ /// Extract unified elements for stable machine-readable `elements[]` output.
1518+ /// Combines blocks, tables, and pages into a single indexed array.
1519+ fn extract_elements_for_output ( doc : & ParsedDocument ) -> Vec < serde_json:: Value > {
1520+ use crate :: doc:: StructuredElementKind ;
1521+
1522+ let mut elements = Vec :: new ( ) ;
1523+ let mut index = 0 ;
1524+
1525+ // Add blocks as elements
1526+ for block in & doc. blocks {
1527+ let location_display = block
1528+ . location
1529+ . as_ref ( )
1530+ . map ( |loc| crate :: document_render:: format_block_location ( loc) )
1531+ . unwrap_or_default ( ) ;
1532+
1533+ elements. push ( json ! ( {
1534+ "index" : index,
1535+ "kind" : "block" ,
1536+ "kind_detail" : match block. kind {
1537+ DocumentBlockKind :: Paragraph => "paragraph" ,
1538+ DocumentBlockKind :: Heading => "heading" ,
1539+ DocumentBlockKind :: Table => "table" ,
1540+ DocumentBlockKind :: Section => "section" ,
1541+ DocumentBlockKind :: Metadata => "metadata" ,
1542+ DocumentBlockKind :: Slide => "slide" ,
1543+ DocumentBlockKind :: EmailHeader => "email_header" ,
1544+ DocumentBlockKind :: Code => "code" ,
1545+ DocumentBlockKind :: Raw => "raw" ,
1546+ } ,
1547+ "label" : block. label,
1548+ "content" : block. content,
1549+ "page" : block. location. as_ref( ) . and_then( |l| l. page) ,
1550+ "source" : block. location. as_ref( ) . and_then( |l| l. source. clone( ) ) ,
1551+ "location" : block. location. as_ref( ) . map( |location: & DocumentBlockLocation | json!( {
1552+ "source" : location. source,
1553+ "page" : location. page,
1554+ "ordinal" : location. ordinal,
1555+ "continued_from_previous_page" : location. continued_from_previous_page,
1556+ "continued_to_next_page" : location. continued_to_next_page,
1557+ "display" : location_display,
1558+ } ) ) ,
1559+ "attributes" : block. attributes,
1560+ "structured_payload" : block. structured_payload,
1561+ } ) ) ;
1562+ index += 1 ;
1563+ }
1564+
1565+ // Add tables as elements
1566+ for table in & doc. tables {
1567+ elements. push ( json ! ( {
1568+ "index" : index,
1569+ "kind" : "table" ,
1570+ "kind_detail" : "table" ,
1571+ "label" : table. label,
1572+ "content" : if table. rows. is_empty( ) {
1573+ String :: new( )
1574+ } else {
1575+ table. rows. iter( ) . map( |row| row. join( "\t " ) ) . collect:: <Vec <_>>( ) . join( "\n " )
1576+ } ,
1577+ "page" : table. page,
1578+ "source" : table. source,
1579+ "location" : table. location. as_ref( ) . map( |location: & DocumentBlockLocation | json!( {
1580+ "source" : location. source,
1581+ "page" : location. page,
1582+ "ordinal" : location. ordinal,
1583+ "continued_from_previous_page" : location. continued_from_previous_page,
1584+ "continued_to_next_page" : location. continued_to_next_page,
1585+ "display" : crate :: document_render:: format_block_location( location) ,
1586+ } ) ) ,
1587+ "attributes" : {
1588+ "row_count" : table. row_count,
1589+ "column_count" : table. column_count,
1590+ } ,
1591+ "structured_payload" : null,
1592+ } ) ) ;
1593+ index += 1 ;
1594+ }
1595+
1596+ // Add pages as elements
1597+ for page in & doc. pages {
1598+ elements. push ( json ! ( {
1599+ "index" : index,
1600+ "kind" : "page" ,
1601+ "kind_detail" : "page" ,
1602+ "label" : null,
1603+ "content" : page. preview. clone( ) . unwrap_or_default( ) ,
1604+ "page" : page. page,
1605+ "source" : page. source,
1606+ "location" : null,
1607+ "attributes" : {
1608+ "block_count" : page. block_count,
1609+ "continued_from_previous_page" : page. continued_from_previous_page,
1610+ "continued_to_next_page" : page. continued_to_next_page,
1611+ } ,
1612+ "structured_payload" : null,
1613+ } ) ) ;
1614+ index += 1 ;
1615+ }
1616+
1617+ elements
1618+ }
1619+
15111620fn summarize_pages_for_parse_report ( doc : & ParsedDocument ) -> Vec < serde_json:: Value > {
15121621 use std:: collections:: BTreeMap ;
15131622
0 commit comments