ó
    À‘´j0|  ã                  ót  • S r SSKJr  SSKrSSKJr  SSKJrJrJ	r	J
r
  SSKJrJr  SSKJrJrJrJrJrJrJrJrJr  SSKJr  SS	KJr  SS
KJr  SSKJr  SSK J!r!  SSK"J#r#  Sr$Sr%\
\&\&\&\&4   r'\
\&\&\&\&4   r(          S)S jr)      S*S jr*\RV                  \RX                  \RZ                  \R\                  1r/S+S jr0Sr1Sr2\Rf                  " S5      r4          S,S jr5S-S jr6S.S jr7S/S jr8            S0S jr9          S1S jr:            S2S jr;          S3S jr<\RV                  \RX                  \RZ                  \Rz                  1r>S4S jr?S5S jr@S6S  jrAS7S! jrB        S8S" jrC        S9S# jrDS:S$ jrES;S% jrFS<S& jrG S=         S>S' jjrHS?S( jrIg)@u#  Build the unified document: move Camelot bodies into the docling spine.

The matcher classified every region; this pass acts on those records with
docling-core's native mutation API, so no custom serializer is needed:

- `replace` â€” rebuild each matched docling table's body from the Camelot cells,
  in place, and attach the SoM title/subtitle/units/footnotes. The spine element
  and its reading-order position are untouched.
- `docling_miss` â€” insert the Camelot table into the spine right after the
  nearest element above it on the page (before the page's first element when the
  table sits at the top), so reading order is preserved.
- `docling_undercount` â€” delete the docling table(s) in the region and insert the
  finer-grained Camelot tables in their place.
- `image_table` â€” no graft. docling already holds the OCR'd table. The region
  still takes part in the region-text dedup below, so docling text items inside
  it that the SoM table's text accounts for are deleted.
- `chart` â€” no graft. docling already holds the picture. The matched picture's
  meta records that Set-of-Mark read it, under `READ_BY_FIELD`.
- `som_miss` â€” no change, surfaced as an error. A table docling found but SoM did
  not is never synthesized from docling.

The source document is never mutated: `build_unified_document` deep-copies it
and mutates the copy.

Deleting and inserting tables renumbers every later table's `self_ref`, so after
all mutation the pass rewrites each match's `docling_table_refs` to the grafted
tables' final positions. The matches ship in the fusion report beside the
unified document, and a consumer resolving a ref against that document must land
on the table the match is about â€” a stale ref silently hands it a neighboring
table's identity (its section heading, its position in the flow).
é    )ÚannotationsN)ÚCounter)ÚDictÚListÚOptionalÚTuple)ÚBoundingBoxÚCoordOrigin)	ÚDocItemÚDoclingDocumentÚFloatingMetaÚNodeItemÚPictureMetaÚProvenanceItemÚ	TableCellÚ	TableDataÚ	TableItem)ÚDocItemLabel)Úlogger)ÚExtractedTable)Úmarkdown_rows)ÚRegionMatch)Úprinted_keyÚquber__read_byzset-of-markc                ó  • U R                  SS9nUR                   Vs0 s H  oUR                  U_M     nn/ n/ nU GH  n	U	R                   V
s/ s H  oªUR	                  U
5      4PM     nn
U	R
                  S:X  a!  [        XFX‘U5        UR                  X›45        Ma  U	R
                  S:X  aF  [        XIX5      u  pÍUR                  U5        UR                  Xœ Vs/ s H  nSU4PM	     sn45        M·  U	R
                  S:X  aH  [        XFX‘U5      u  pÍUR                  U5        UR                  Xœ Vs/ s H  nSU4PM	     sn45        GM  U	R
                  S:X  a`  UR                  SU	R                   S	[        U	R                  5       S
[        U	R                  5       S35        UR                  X›45        GM  U	R
                  S:X  aA  UR                  SU	R                   SU	R                   S35        UR                  X›45        GMÐ  U	R
                  S:X  a!  [        XIU5        UR                  X›45        GM  UR                  X›45        GM     [        XBX5        [!        XH5        XG4$ s  snf s  sn
f s  snf s  snf )a¥  Graft the Camelot bodies into a clone of `document` per the matches.

Returns the unified document and a list of error strings: one per `som_miss`
region (a table docling found that SoM did not match), one per `som_merged`
region the split pass left unresolved, and one per `docling_miss` or
`docling_undercount` SoM table with no Camelot body, which stays out of the
unified document. The input document is not mutated.
T)ÚdeepÚreplaceÚdocling_missÚ Údocling_undercountÚ
som_mergedúpage z$: som_merged not resolved by split (z SoM vs z( docling); docling tables left untouchedÚsom_missz: docling table z< has no SoM match (SoM/Camelot missed a table docling found)Úchart)Ú
model_copyÚtablesÚself_refÚdocling_table_refsÚgetÚkindÚ_apply_replaceÚappendÚ_apply_insertÚextendÚ_apply_undercountÚpageÚlenÚsom_indicesÚ_mark_vettedÚ_dedup_table_region_textÚ_rewrite_report_refs)ÚdocumentÚ
som_tablesÚmatchesÚ	page_dimsÚunifiedÚtÚtables_by_refÚerrorsÚrealizedÚmatchÚrÚ	survivingÚinsertedÚerrss                 Ú5/home/mande/repo/quber/src/quber/core/fusion/graft.pyÚbuild_unified_documentrE   F   sX  € ð ×!Ñ! tÐ!Ð,€GØ,3¯NªNÓ;ªN q—Z‘Z ’]©N€MÐ;Ø€Fð QS€HäˆØ8=×8PÒ8PÓQÒ8P°1˜×*Ñ*¨1Ó-Ó.Ñ8Pˆ	ÐQØ�:‰:˜Ó"Ü˜7°5ÀiÔPØ�O‰O˜UÐ.Ö/Ø�Z‰Z˜>Ó)Ü*¨7¸:ÓQ‰NˆHØ�M‰M˜$ÔØ�O‰O˜U°hÓ$?²h° b¨!£W±hÑ$?Ð@ÖAØ�Z‰ZÐ/Ó/Ü.¨wÀuÐZcÓd‰NˆHØ�M‰M˜$ÔØ�O‰O˜U°hÓ$?²h° b¨!£W±hÑ$?Ð@×AØ�Z‰Z˜<Ó'ð
 �M‰MØ˜Ÿ
™
�|ð $Ü˜×)Ñ)Ó*Ð+¨8´C¸×8PÑ8PÓ4QÐ3Rð S0ð0ôð
 �O‰O˜UÐ.×/Ø�Z‰Z˜:Ó%Ø�M‰MØ˜Ÿ
™
�|Ð#3°E×4LÑ4LÐ3Mð N=ð =ôð �O‰O˜UÐ.×/Ø�Z‰Z˜7Ó"Ü˜¨Ô4Ø�O‰O˜UÐ.×/ð �O‰O˜UÐ.×/ñI ôL ˜W¨zÔEÜ˜Ô+Øˆ?Ðùòi <ùò Rùò %@ùò %@s   žI.ÁI3ÃI8Ä2I=c                ó:  • [        U R                  5       VVs0 s H  u  p#[        U5      SU 3_M     nnnU HY  u  pV/ nU HF  u  p‰U	b  UR                  [        U	5      5      OSn
U
c	  U(       d  M0  UR	                  U
b  U
OU5        MH     Xul        M[     gs  snnf )u‘  Point every match's `docling_table_refs` at the unified document.

Final refs are read off each tracked object's position in the tables
array â€” the same positions serialization writes â€” so the rewrite cannot
disagree with the document on disk. A ref that never resolved to a table
is kept verbatim rather than dropped: it still names what the matcher saw,
and dropping it would hide the mismatch.
z	#/tables/N)Ú	enumerater&   Úidr)   r,   r(   )r:   r>   Úir;   Ú	final_refr?   ÚitemsÚrefsÚoriginalÚitemÚrefs              rD   r5   r5   Œ   s”   € ô 5>¸g¿n¹nÔ4MÔNÒ4M©D¨A”�A“˜) A 3˜Ò'Ñ4M€IÑNÛ ‰ˆØˆÛ#‰NˆHØ-1Ñ-=�)—-‘-¤ 4£Ô)À4ˆCØ‰{¦8ÙØ�K‰K˜s™™°HÖ=ñ	 $ð
 $(Ö ò !ùó Os   ™Bc           	     ól  • UR                    Vs/ s H&  o2U   R                  (       d  M  X#   R                  PM(     nnU R                   Vs0 s H  oUR                  U_M     nnUR                   H·  nUR                  U5      nUc  M  UR                  =(       d
    [        5       n	[        [        U	[        S5      =(       d    / 5      n
U
R                  [        US.5        U	R                  [        U
0S9Ul        [        R                  " SU[        SR!                  U5      5        M¹     gs  snf s  snf )a”  Record on a picture that the table engine has already read its region.

A `chart` match is a table the engine extracted from the text layer whose
region overlaps a picture the parse detected. That is routinely a real table
printed beside a chart, both inside the one region the engine bounded: the
engine read the table correctly and drew its box around the pair.

Nothing about the document changes. What is recorded is which reader has read
the region and what it produced, so a later reader leaves it alone instead of
stating the same table twice. The alternative is for that later step to
recompute the overlap against an artifact it is not handed, which is the same
judgement made again from less.

The tag names the reader rather than the outcome. A region already read is
the general condition; which tool did the reading is what a caller needs to
weigh it, and another tool added later says so the same way.
N)ÚreaderÚproduced©Úupdatez"Fusion: picture {} read by {} ({})z, )r2   Útable_idÚpicturesr'   Údocling_picture_refsr)   Úmetar   ÚlistÚgetattrÚREAD_BY_FIELDr,   ÚSET_OF_MARKr%   r   ÚinfoÚjoin)r6   r?   r7   rI   rR   ÚpÚby_refrO   ÚpictureÚbaseÚread_bys              rD   r3   r3   ®   só   € ð$ 16×0AÒ0AÓ\Ò0A¨1ÐPQÁ]×E[ÕE[Ó&�
‘×&Ô&Ñ0A€HÐ\Ø%-×%6Ò%6Ó7Ò%6 �j‰j˜!ŠmÑ%6€FÐ7Ø×)Ô)ˆØ—*‘*˜S“/ˆØ‰?ÙØ�|‰|×,œ{›}ˆÜ”w˜t¤]°DÓ9×?¸RÓ@ˆØ�‰¤+¸8ÑDÔEØ—‘¬}¸gÐ.F�ÐGˆŒÜ�ŠÐ8¸#¼{ÈDÏIÉIÐV^ÓL_Ö`ò *ùò ]ùÚ7s   �D,ªD,ÁD1g\�Âõ(\ï?é   z	[a-z0-9]+c                óž  • 0 nU HÊ  nUR                   S;  a  M  UR                   H¥  nX&   nUR                  c  M  UR                  UR                  S5      u  p‰UR                  u  p«pÍ[        X¬5      U-  [        X½5      U	-  [        X¬5      U-  [        X½5      U	-  4nUR                  UR                  / 5      R                  Xç45        M§     MÌ     U(       d  g0 n/ nU R                   GHA  nUR                  [        ;  d  UR                  (       d  M+  UR                  S   R                  nUR                  U5      nU(       d  M^  UR                  US5      u  nn	UR                  S   R                  nUR                  [         R"                  :X  a/  UR$                  UR&                  UR(                  UR*                  4nO4UR$                  U	UR&                  -
  UR(                  U	UR*                  -
  4nU VVs/ s H  u  pç[-        UU5      (       d  M  UPM     nnnU(       d  GM@  SnU HX  nUR                  [/        U5      5      nUc  [1        U5      =nU[/        U5      '   [3        UR4                  U5      (       d  MV  Sn  O   U(       a  UR                  U5        GM»  [6        R8                  " SUUR                  R:                  UR<                  US   R>                  =(       d    US   R@                  SRC                  UR4                  RE                  5       SS	 5      5        GMD     U(       a  U RG                  US
9  ggs  snnf )uâ  Drop docling body text the grafted tables now represent.

docling emits a table's cell text â€” and its title/units/footnote lines â€” both
inside the TableItem and as loose TextItems in the reading-order flow, so the
unified output would show each fused table once as a clean table and again as
scattered fragments. For every `replace`, `docling_miss`, `docling_undercount`
and `image_table` region, consider the docling TextItems
(text/paragraph/caption/footnote) whose center falls inside the SoM region,
and delete each one whose words the SoM table's own text accounts for (its
cells, title, subtitle, units, and footnotes); the grafted body plus the
structured caption/footnotes is then the single representation.

An `image_table` region grafts no Camelot body and attaches no SoM caption or
footnotes, so a docling text item there that the SoM text accounts for is
deleted with nothing written in its place. `chart` and `som_merged` regions
are not deduplicated.

Position alone is never grounds for deletion. The locator's region is the
vision model's box, and it reaches past the table body: it is drawn to
enclose the footnote lines below the grid, and it can take in what is printed
under those too. Text inside the box that the table does not carry is not
table text; it is the box being too big. A paragraph stating what share of a
loan book floats, a sentence introducing the next table, a footnote whose
text continues on the next page and so exceeds what the table carries: each
stays in the document as the text it is. The separation is logged.

Section headers and page headers/footers are never removed, so the spine
survives. The attribution captions/footnotes added during the graft carry no
provenance box, so they are never matched here.
)r   r   r    Úimage_tableN©g      ƒ@g     Àˆ@r   FTzWFusion: page {}: {} {} lies in the region of table {} but is not table text; kept: {!r}Ú é   ©Ú
node_items)$r*   r2   Ú
som_regionr)   r0   ÚminÚmaxÚ
setdefaultr,   ÚtextsÚlabelÚ_REDUNDANT_LABELSÚprovÚpage_noÚbboxÚcoord_originr
   ÚTOPLEFTÚlr;   r@   ÚbÚ
_center_inrH   Ú_table_text_bagÚ_covered_byÚtextr   r]   Úvaluer'   rU   Útitler^   ÚsplitÚdelete_items)r:   r8   r7   r9   Úregions_by_pager?   rI   ÚsomÚwidthÚheightÚx1Úy1Úx2Úy2ÚregionÚbagsÚvictimsrN   rt   ÚregionsÚ_ru   ÚboxÚ	enclosingÚcoveredÚbags                             rD   r4   r4   Ø   s°  € ðH DF€OÛˆØ�:‰:Ð]Ó]ÙØ×"Ô"ˆAØ‘-ˆCØ�~‰~Ñ%ÙØ%ŸM™M¨#¯(©(°NÓC‰MˆEØ Ÿ^™^‰NˆB�BÜ˜"“k EÑ)¬3¨r«;¸Ñ+?ÄÀRÃÈuÑATÔVYÐZ\ÓVaÐdjÑVjÐkˆFØ×&Ñ& s§x¡x°Ó4×;Ñ;¸V¸MÖJó #ñ ö Øà$&€DØ €GØ—•ˆØ�:‰:Ô.Ó.°d·i·iÙØ—)‘)˜A‘,×&Ñ&ˆØ!×%Ñ% gÓ.ˆÞÙØ—M‘M '¨>Ó:‰	ˆˆ6Ø�y‰y˜‰|× Ñ ˆØ×Ñ¤× 3Ñ 3Ó3Ø—6‘6˜4Ÿ6™6 4§6¡6¨4¯6©6Ð2‰Cà—6‘6˜6 D§F¡F™?¨D¯F©F°F¸T¿V¹V±OÐDˆCÙ,3ÔOªG™[˜V´zÀ#Àv×7N—S©Gˆ	ÑOÞÚØˆÛˆCØ—(‘(œ2˜c›7Ó#ˆCØ‰{Ü&5°cÓ&:Ð:��dœ2˜c›7‘mÜ˜4Ÿ9™9 c×*Ó*Ø�Ùñ ö Ø�N‰N˜4× ä�KŠKØiØØ—
‘
× Ñ Ø—‘Ø˜!‘×%Ñ%×;¨°1©×);Ñ);Ø—‘˜Ÿ™Ÿ™Ó*¨3¨BÐ/Ó0÷ñ7 öH Ø×Ñ¨ÐÒ0ð ùó/ Ps   Ç=M	ÈM	c                óš  • U R                   U R                  U R                  /nU R                   H*  nUR	                  UR
                  UR                  /5        M,     [        U R                  5       H  nUR	                  U5        M     [        [        R                  SR                  S U 5       5      R                  5       5      5      $ )z|Every word the grafted table carries, with multiplicity: its cells, title,
subtitle, units, and footnote markers and bodies.rh   c              3  ó6   #   • U  H  o(       d  M  Uv •  M     g 7f©N© )Ú.0r_   s     rD   Ú	<genexpr>Ú"_table_text_bag.<locals>.<genexpr>>  s   é € Ð-Dº°AÃ!¯a©aºùs   ‚
�	)r   ÚsubtitleÚunitsÚ	footnotesr.   Úmarkerr}   r   Úmarkdownr   Ú	_TOKEN_REÚfindallr^   Úlower)ÚtableÚpartsÚfnÚrows       rD   r{   r{   6  s�   € ð �[‰[˜%Ÿ.™.¨%¯+©+Ð6€EØ�oŒoˆØ�‰�b—i‘i §¡Ð)Ö*ñ ä˜UŸ^™^Ö,ˆØ�‰�SÖñ -ä”9×$Ñ$ S§X¡XÑ-D¹Ó-DÓ%D×%JÑ%JÓ%LÓMÓNÐNó    c                ó*  • [         R                  U R                  5       5      nU(       d  gUR                  5       nSnU H  nX5   S:”  d  M  X5==   S-  ss'   US-  nM      [	        U5      U-
  nU[
        :*  =(       d    U[	        U5      -  [        :¬  $ )z‘Whether the table's words account for `text`: at most one token of it is
missing from the bag, or all but a rendering-noise fraction are present.Tr   rd   )rŸ   r    r¡   Úcopyr1   Ú_UNMATCHED_TOLERANCEÚ_COVERAGE_MIN)r}   r’   ÚtokensÚ	remainingÚmatchedÚtokÚ	unmatcheds          rD   r|   r|   A  sˆ   € ô ×Ñ˜tŸz™z›|Ó,€FÞØØ—‘“
€IØ€GÛˆØ‰>˜AÕØ‹N˜aÑ‹NØ�q‰LŠGñ ô �F“˜gÑ%€IØÔ,Ñ,×V°¼#¸f»+Ñ0EÌÑ0VÐVr¦   c                ó"  • U S   U S   -   S-  U S   U S   -   S-  p2[        US   US   5      [        US   US   5      pT[        US   US   5      [        US   US   5      pvXBs=:*  =(       a    U:*  Os  =(       a    XSs=:*  =(       a    U:*  $ s  $ )Nr   é   rd   é   )rm   rn   )r�   rŠ   ÚcxÚcyÚx0Úy0r†   r‡   s           rD   rz   rz   Q  s•   € Ø�!‰f�s˜1‘v‰o Ñ" S¨¡V¨c°!©f¡_¸Ñ$9ˆÜ�˜‘˜F 1™IÓ&¬¨F°1©I°v¸a±yÓ(AˆÜ�˜‘˜F 1™IÓ&¬¨F°1©I°v¸a±yÓ(AˆØ�>‹>�rŒ>×,˜bŸn›n¨"™nÐ,™nÐ,r¦   c                óò   • [        X#U5       Hg  u  pVUR                  U5      nUc  M  X5   nUR                  =(       d    SR                  5       n	U	(       d  MK  [	        U	5      Ul        [        XX„5        Mi     g)zHRebuild each matched docling table's body from its paired Camelot table.Nr   )Ú_pair_by_vertical_orderr)   rž   ÚstripÚmarkdown_to_table_dataÚdataÚattach_attribution)
r:   r<   r?   r7   r9   Úsom_idxrO   Ú
table_itemrƒ   rž   s
             rD   r+   r+   X  sn   € ô 0°À=ÖQ‰ˆØ"×&Ñ& sÓ+ˆ
ØÑÙØÑ!ˆØ—L‘L×& B×-Ñ-Ó/ˆÞñ Ü0°Ó:ˆ
ŒÜ˜7°Ö?ò Rr¦   c                ó¸  • / n/ nUR                    HÄ  nX&   nUR                  =(       d    SR                  5       nU(       d/  UR                  SUR                   SUR
                  < S35        M`  [        U5      n	[        Xs5      n
[        XU5      u  p¼Uc  U R                  XšS9nOU R                  X¹X¬S9n[        XXs5        UR                  U5        MÆ     XE4$ )a3  Insert a Camelot table docling missed, at its reading-order position.

Returns the inserted tables and an error for any table that has no Camelot
body: a table SoM located but neither docling nor Camelot could read (a
scanned table on a page docling did not detect). It is surfaced, never
silently dropped.
r   r"   zE: SoM located a table docling missed but Camelot read no body (title=z$); table not in the unified document©r»   rs   ©Úsiblingr»   rs   Úafter)r2   rž   r¹   r,   r0   r   rº   Ú	_som_provÚreading_order_anchorÚ	add_tableÚinsert_tabler¼   )r:   r?   r7   r9   r&   r=   r½   rƒ   rž   r»   rs   rÂ   rÃ   rB   s                 rD   r-   r-   n  sâ   € ð !€FØ€FØ×$Ô$ˆØÑ!ˆØ—L‘L×& B×-Ñ-Ó/ˆÞØ�M‰MØ˜Ÿ™�zð "Ø"Ÿy™y™mÐ+OðQôñ Ü% hÓ/ˆÜ˜Ó(ˆÜ-¨g¸IÓF‰ˆØ‰?Ø×(Ñ(¨dÐ(Ð>‰Hà×+Ñ+°GÈTÐ+Ð_ˆHÜ˜7¨cÔ=Ø�‰�hÖñ# %ð$ ˆ>Ðr¦   c                óÔ  ^• / n/ nUR                    Vs/ s H  owU;   d  M
  X   PM     nn[        U4S jUR                   5       S SS9n	Sn
SnU	b  [        X	XHS9u  p«U(       a  U R	                  US9  Sn[        UR                  U4S jS	9 HÐ  nTU   nUR                  =(       d    S
R                  5       nU(       d/  UR                  SUR                   SUR                  < S35        Ma  [        U5      n[        Xä5      nUb  U R                  UUUSS9nO&U
b  U R                  U
UUUS9nOU R                  UUS9n[        XXä5        UR                  U5        MÒ     XV4$ s  snf )aŒ  Delete the docling table(s) in the region and insert the finer Camelot tables.

docling under-counted (dropped or merged a complex table); Camelot's finer
count is trusted. The Camelot tables are inserted after the spine element
above the region, in vertical order. Returns the inserted tables and an
error for any Camelot table with no body, so a dropped sub-table is
surfaced rather than lost.
c              3  ó.   >#   • U  H
  nTU   v •  M     g 7fr•   r–   )r—   rI   r7   s     €rD   r˜   Ú$_apply_undercount.<locals>.<genexpr>¥  s   øé € Ð2Ò 1˜1ˆ�AŽÒ 1ùs   ƒc                ó   • [        U 5      $ r•   ©Ú_vertical_key)r;   s    rD   Ú<lambda>Ú#_apply_undercount.<locals>.<lambda>¦  s	   € ”m AÔ&r¦   N)ÚkeyÚdefaultT)Úexcluderj   c                ó    >• [        TU    5      $ r•   rÌ   ©rI   r7   s    €rD   rÎ   rÏ   ²  s   ø€ ¼=ÈÐTUÉÔ;Wr¦   ©rÐ   r   r"   zD: a sub-table of a docling-merged region had no Camelot body (title=z); not in the unified documentrÁ   rÀ   )r(   rm   r2   rÅ   r�   Úsortedrž   r¹   r,   r0   r   rº   rÄ   rÇ   rÆ   r¼   )r:   r<   r?   r7   r9   r&   r=   r@   ÚdoomedÚ
anchor_somrÂ   rÃ   Úlastr½   rƒ   rž   r»   rs   s      `              rD   r/   r/   ’  sŽ  ø€ ð !€FØ€FØ8=×8PÒ8PÓgÒ8P°1ÐYfÑTfÓ.˜mÔ.Ñ8P€FÐgÜÜ2 × 1Ò 1Ó2Ù&Øñ€Jð
 #'€GØ€EØÑÜ-¨gÀ9Ñ]‰ˆæØ×Ñ¨ÐÑ/à $€DÜ˜%×+Ñ+Ô1WÔXˆØ˜Ñ!ˆØ—L‘L×& B×-Ñ-Ó/ˆÞØ�M‰MØ˜Ÿ™�zð "ØŸ)™)™Ð&DðFôñ Ü% hÓ/ˆÜ˜Ó(ˆØÑØ×'Ñ'°¸4ÀdÐRVÐ'ÐW‰DØÑ Ø×'Ñ'°¸dÈÐUZÐ'Ð[‰Dà×$Ñ$¨$°TÐ$Ð:ˆDÜ˜7¨#Ô9Ø�‰�dÖñ% Yð& ˆ>ÐùòE hs
   ”	E%¡E%c                ó¬  • UR                   (       aH  UR                  =(       d
    [        5       nUR                  UR                   UR                  S.S9Ul        [        XU5        / n[        X5       H@  nU R                  [        R                  US9nUR                  UR                  5       5        MB     U(       a  XQl        UnUR                   H  n	U	R                  (       a)  U	R                   SU	R                   3R!                  5       OU	R                  nUR!                  5       (       d  M`  U R#                  [        R$                  XhSS9nM�     g)uÚ  Carry the SoM identity, cell geometry, title, subtitle, units (captions)
and footnotes onto a table.

The SoM table's `table_id` and `content_fingerprint` bind onto the docling
item through `meta` (docling's sanctioned extension point â€” the meta
model allows extra fields, so they serialize with the document), and every
grafted cell carries its measured box where the provenance record can pin
one. SoM is the vetted, vision-grounded side, so its attribution takes
precedence over whatever docling attached:

- the sentence that introduces the table, its printed name and its units
  become CAPTION nodes referenced from the table (top-to-bottom order; see
  `caption_lines`). The table serializer emits captions inline, so they
  render above the table in markdown/HTML.
- each footnote is inserted as a FOOTNOTE node in the body right after the
  table, in order. docling's markdown serializer does NOT emit a table's
  `footnotes` refs, so a referenced footnote would round-trip in JSON but
  vanish from the rendered markdown; a body node after the table renders as a
  paragraph below it and keeps the reading order.

Both kinds carry no provenance box, so the region-text dedup that strips
docling's loose copies never matches them.
)Úquber__table_idÚquber__content_fingerprintrS   )rq   r}   rh   T)rq   r}   rÂ   rÃ   N)rU   rX   r   r%   Úcontent_fingerprintÚ_graft_cell_geometryÚcaption_linesÚadd_textr   ÚCAPTIONr,   Úget_refÚcaptionsrœ   r�   r}   r¹   Úinsert_textÚFOOTNOTE)
r:   r¾   rƒ   r9   rb   Úcaption_refsr}   ÚcaptionÚanchorÚnotes
             rD   r¼   r¼   È  s  € ð: ‡|‡|Ø�‰×0¤,£.ˆØŸ/™/à#&§<¡<Ø.1×.EÑ.Eñð *ð 
ˆ
Œô ˜¨)Ô4à€LÜ˜gÖ+ˆØ×"Ñ"¬×)=Ñ)=ÀDÐ"ÐIˆØ×Ñ˜GŸO™OÓ-Ö.ñ ,ö Ø*Ôà!€FØ—”ˆð 8<·{·{�$—+‘+�˜a §	¡	˜{Ð+×1Ñ1Ô3ÈÏ	É	ˆØ�z‰z�|‰|ÙØ×$Ñ$¬<×+@Ñ+@ÀtÐcgÐ$ÐhŠò r¦   c                óP  • [        XR                  5      n/ nUR                  =(       d    SR                  5       nU(       a=  [	        U5      U;  a.  UR                  [        XR                  U5      =(       d    U5        UR                  =(       d    SR                  5       nU(       a  [	        U5      U;   a  SnU(       a3  U(       a  [	        U5      [	        US   5      ;   d  UR                  U5        UR                  (       aH  UR                  R                  5       (       a)  UR                  UR                  R                  5       5        UR                  (       aH  UR                  R                  5       (       a)  UR                  UR                  R                  5       5        U$ )a´  The header lines a table carries, top to bottom: the sentence that
introduces it, its printed name, its units.

The introducing sentence is the docling paragraph on the table's page that
contains the caption the vetting agent copied. Two tables printed side by
side under one sentence then carry the same whole sentence, whichever words
each copy kept, and a bold lead-in the copy left out comes back with the
paragraph. When no such paragraph exists, the copied caption stands. The
fusion graft calls this before its region dedup runs, so docling's copy of
the sentence is still in the document at that point. A name that repeats the
sentence is not written twice.

Page furniture is never a header line. A running header or footer the
parse labeled as such ("Table of Contents", the statement banner) is
printed on the page, so the printed-text check upstream lets it through
when the agent copies it; the parse's own label is what rules it out.
r   r   )
Ú_furniture_keysr0   rç   r¹   r   r,   Ú_paragraph_containingr   rš   r›   )r:   rƒ   Ú	furnitureÚlinesrç   r   s         rD   rß   rß   	  s  € ô$   ¯©Ó2€IØ€EØ�{‰{× ˜b×'Ñ'Ó)€GÞ”;˜wÓ'¨yÓ8Ø�‰Ô*¨7·H±H¸gÓF×QÈ'ÔRØ�Y‰Y�_˜"×#Ñ#Ó%€EÞ”˜UÓ# yÓ0ØˆÞ–e¤¨EÓ 2´kÀ%ÈÁ(Ó6KÓ KØ�‰�UÔà
‡|‡|˜Ÿ™×*Ñ*×,Ñ,Ø�‰�S—\‘\×'Ñ'Ó)Ô*Ø
‡y‡y�S—Y‘Y—_‘_×&Ñ&Ø�‰�S—Y‘Y—_‘_Ó&Ô'Ø€Lr¦   c                ób  • [        5       nU R                   H”  nUR                  [        R                  [        R
                  4;   d  M3  UR                  (       d  MF  UR                  S   R                  U:X  d  Me  [        UR                  5      nU(       d  Mƒ  UR                  U5        M–     U$ )zVThe printed keys of the page's running headers and footers, as the parse labeled them.r   )Úsetrp   rq   r   ÚPAGE_HEADERÚPAGE_FOOTERrs   rt   r   r}   Úadd)r:   r0   ÚkeysrN   rÐ   s        rD   rë   rë   -  sx   € ä“U€DØ—”ˆà�J‰Jœ<×3Ñ3´\×5MÑ5MÐNÕNØ—	—	‘	Ø—	‘	˜!‘×$Ñ$¨Õ,ä˜dŸi™iÓ(ˆCßˆsØ—‘˜–ñ ð €Kr¦   c                óP  • [        U5      nU R                   H‹  nUR                  [        ;  d.  UR                  (       a  UR                  S   R
                  U:w  a  MG  U[        UR                  5      ;   d  Mb  SR                  UR                  R                  5       5      s  $    g)zØThe text of the first docling item on `page` whose printed words contain
`caption`, whitespace collapsed; None when no item does. Items without a
page box are skipped, which excludes the caption nodes the graft adds.r   rh   N)	r   rp   rq   Ú_CAPTION_SOURCE_LABELSrs   rt   r}   r^   r€   )r:   r0   rç   rÐ   rN   s        rD   rì   rì   <  sy   € ô �gÓ
€CØ—”ˆØ�:‰:Ô3Ó3¸4¿9¿9ÈÏ	É	ÐRSÉ×H\ÑH\Ð`dÓHdÙØ”+˜dŸi™iÓ(Õ(Ø—8‘8˜DŸI™IŸO™OÓ-Ó.Ò.ñ	 ð
 r¦   c                óX  • [        U 5      nU(       d  [        / SSS9$ [        S U 5       5      n[        U5      n/ n[	        U5       HV  u  pVUS/U[        U5      -
  -  -   n[	        U5       H-  u  p‰UR                  [        U	UUS-   UUS-   SSUS:H  S95        M/     MX     [        XCUS9$ )a   Parse a pipe-delimited markdown table into a docling `TableData` grid.

The first content row is treated as the column header. The markdown separator
row (`| --- | --- |`) is dropped. Ragged rows are padded to the widest row so
the grid stays rectangular.
r   )Útable_cellsÚnum_rowsÚnum_colsc              3  ó8   #   • U  H  n[        U5      v •  M     g 7fr•   )r1   )r—   r@   s     rD   r˜   Ú)markdown_to_table_data.<locals>.<genexpr>T  s   é € Ð(¢4˜a”3�q—6�6¢4ùs   ‚r   rd   )r}   Ústart_row_offset_idxÚend_row_offset_idxÚstart_col_offset_idxÚend_col_offset_idxÚrow_spanÚcol_spanÚcolumn_header)r   r   rn   r1   rG   r,   r   )
rž   Úrowsrú   rù   rø   r@   r¥   ÚpaddedÚcr}   s
             rD   rº   rº   I  sÂ   € ô ˜Ó"€DÞÜ R°!¸aÑ@Ð@äÑ(¡4Ó(Ó(€HÜ�4‹y€HØ#%€KÜ˜D–/‰ˆØ˜�t˜x¬#¨c«(Ñ2Ñ3Ñ3ˆÜ  Ö(‰GˆAØ×ÑÜØØ)*Ø'(¨1¡uØ)*Ø'(¨1¡uØØØ#$¨¡6ñ	öó )ñ "ô  È(ÑSÐSr¦   c                óö  • UR                   nU(       d  gUR                  UR                  S5      u  pEU R                  R                   H­  nUR
                  UR                  p‡U[        U5      :  a"  U[        X7   5      :  a  X7   U   R                  OSn	U	c  MR  U	u  p«pÍ[        [        X¬5      U-  [        X¬5      U-  S[        X½5      -
  U-  S[        X½5      -
  U-  [        R                  S9Ul        M¯     g)u  Carry the already-resolved cell geometry onto the grafted TableCells.

`corrected_grid` is the complete cell-level view the grounding stage
produced â€” same markdown, same row/col addressing â€” so this is pure
carriage: read each cell's box and convert it from the normalized top-left
frame to docling's bottom-left points, the same convention `_som_prov`
uses for the table-level box. No matching or reconciliation happens here;
a cell the grounding stage could not pin arrives with box=None and stays
without one.
Nrg   ç      ð?©rx   r@   r;   ry   rv   )Úcorrected_gridr)   r0   r»   rø   rý   rÿ   r1   r�   r	   rm   rn   r
   Ú
BOTTOMLEFTru   )r¾   rƒ   r9   Úgridr„   r…   Úcellr@   r  r�   r†   r‡   rˆ   r‰   s                 rD   rÞ   rÞ   i  sÚ   € ð ×Ñ€DÞØØ—M‘M #§(¡(¨NÓ;�M€EØ—‘×+Ô+ˆØ×(Ñ(¨$×*CÑ*Cˆ1Ø !¤C¨£I£°!´c¸$¹'³lÓ2Bˆd‰g�a‰j�nŠnÈˆØ‰;ÙØ‰ˆ�ÜÜ�"‹k˜EÑ!Ü�"‹k˜EÑ!Ø”S˜“[Ñ  FÑ*Ø”S˜“[Ñ  FÑ*Ü$×/Ñ/ñ
ˆŽ	ò ,r¦   c                ó    ^^• [        U R                  U4S jS9n[        U4S jU R                   5       U4S jS9n[        [	        X4SS95      $ )zEPair SoM tables to docling tables in a region by top-to-bottom order.c                ó    >• [        TU    5      $ r•   rÌ   rÔ   s    €rD   rÎ   Ú)_pair_by_vertical_order.<locals>.<lambda>�  s   ø€ ´=ÀÈAÁÔ3Or¦   rÕ   c              3  ó6   >#   • U  H  oT;   d  M
  Uv •  M     g 7fr•   r–   )r—   r@   r<   s     €rD   r˜   Ú*_pair_by_vertical_order.<locals>.<genexpr>‘  s   øé € ÐCÒ,ˆq°]Ñ0B�‰Ò,ùs   ƒ	�	c                ó    >• [        TU    5      $ r•   )Ú
_table_top)r@   r<   s    €rD   rÎ   r  ’  s   ø€ ”j ¨qÑ!1Ô2r¦   F)Ústrict)rÖ   r2   r(   rY   Úzip)r?   r7   r<   ÚsomsrL   s    ``  rD   r¸   r¸   ‰  sG   ù€ ô �%×#Ñ#Ô)OÑP€DÜÜC�E×,Ò,ÓCÜ2ñ€Dô ”�D uÑ-Ó.Ð.r¦   c                ó®   • U R                   nUb  [        US   US   5      $ U R                  b'  [        U R                  S   U R                  S   5      * $ g)Nrd   r²   ç        )rl   rm   ru   rn   )r¢   rŠ   s     rD   rÍ   rÍ   —  sU   € Ø×Ñ€FØÑÜ�6˜!‘9˜f Q™iÓ(Ð(Ø‡z�zÑÜ�E—J‘J˜q‘M 5§:¡:¨a¡=Ó1Ð1Ð1Ør¦   c                óî   • U R                   (       d  gU R                   S   R                  nUR                  [        R                  :X  a  UR
                  $ [        UR
                  UR                  5      * $ )Nr  r   )rs   ru   rv   r
   rw   r;   rn   ry   )r¢   ru   s     rD   r  r     sT   € Ø�:�:ØØ�:‰:�a‰=×Ñ€DØ×ÑœK×/Ñ/Ó/Ø�v‰vˆÜ�—‘˜Ÿ™ÓÐÐr¦   c           
     ó4  • UR                  U R                  S5      u  p#U R                  nUc  gUu  pVpx[        [	        XW5      U-  [        XW5      U-  S[	        Xh5      -
  U-  S[        Xh5      -
  U-  [        R                  S9n	[        U R                  U	SS9$ )zJA ProvenanceItem for an inserted Camelot table, box in bottom-left points.rg   Nr  r	  )r   r   )rt   ru   Úcharspan)	r)   r0   rl   r	   rm   rn   r
   r  r   )
rƒ   r9   r„   r…   rŠ   r†   r‡   rˆ   r‰   ru   s
             rD   rÄ   rÄ   ©  s”   € à—M‘M #§(¡(¨NÓ;�M€EØ�^‰^€FØ�~ØØ�N€BˆBÜÜ
ˆb‹+˜Ñ
Ü
ˆb‹+˜Ñ
Ø”�R“Ñ Ñ
&Ø”�R“Ñ Ñ
&Ü ×+Ñ+ñ€Dô  #§(¡(°ÀÑGÐGr¦   c                ó¾  • UR                  UR                  S5      u  pEUR                  nUc  g[        US   US   5      U-  nU=(       d    /  Vs1 s H  n[	        U5      iM     n	n/ n
U R                  5        H‰  u  p¼[        U[        5      (       a  [	        U5      U	;   a  M+  UR                  (       a  UR                  S   OSnUb  UR                  UR                  :w  a  Ml  [        XÕ5      nU
R                  Xë45        M‹     U
(       d  gU
R                  S S9  U
 VVs/ s H  u  pëXç:  d  M  UPM     nnnU(       a  US	   S
4$ U
S   S   S4$ s  snf s  snnf )an  Find the spine element to anchor an inserted table to.

Returns (sibling, after). The sibling is the last element above the table on
its page, with after=True. When the table sits above every element on the
page, returns that page's first element with after=False. Returns (None, True)
when the page has no other anchorable element (caller falls back to add_table).
rg   N)NTrd   r²   r   c                ó   • U S   $ )Nr   r–   )Úxs    rD   rÎ   Ú&reading_order_anchor.<locals>.<lambda>Û  s   € ˜q štr¦   rÕ   éÿÿÿÿTF)r)   r0   rl   rm   rH   Úiterate_itemsÚ
isinstancer   rs   rt   Ú_item_top_left_topr,   Úsort)r6   rƒ   r9   rÒ   Ú_widthr…   rŠ   Ú	table_topÚeÚexcludedÚon_pagerN   Ú_levelrs   ÚtopÚaboves                   rD   rÅ   rÅ   º  s9  € ð —]‘] 3§8¡8¨^Ó<�N€FØ�^‰^€FØ�~ØÜ�F˜1‘I˜v a™yÓ)¨FÑ2€Ià '§¨2 Ó/¢˜!”�1–¡€HÐ/Ø,.€GØ ×.Ñ.Ö0‰ˆÜ˜$¤×(Ñ(¬B¨t«H¸Ó,@ÙØ#ŸyŸyˆt�y‰y˜Š|¨dˆØ‰<˜4Ÿ<™<¨3¯8©8Ó3ÙÜ  Ó.ˆØ�‰˜�{Ö#ñ 1ö Øà‡L�L‘^€LÑ$Ù#*Ô>¢7‘i�c¨c©o�T¡7€EÑ>ÞØ�R‰y˜$ˆÐØ�1‰:�a‰=˜%ÐÐùò% 0ùó ?s   ÁEÄ$EÄ3Ec                ó´   • U R                   nUR                  [        R                  :X  a  UR                  $ U[        UR                  UR                  5      -
  $ r•   )ru   rv   r
   rw   r;   rn   ry   )rs   Úpage_heightru   s      rD   r$  r$  â  sB   € Ø�9‰9€DØ×ÑœK×/Ñ/Ó/Ø�v‰vˆØœ˜TŸV™V T§V¡VÓ,Ñ,Ð,r¦   )
r6   r   r7   úList[ExtractedTable]r8   úList[RegionMatch]r9   úDict[int, Tuple[float, float]]Úreturnz!Tuple[DoclingDocument, List[str]])r:   r   r>   z?List[Tuple[RegionMatch, List[Tuple[str, Optional[TableItem]]]]]r3  ÚNone)r6   r   r?   r   r7   r0  r3  r4  )
r:   r   r8   r1  r7   r0  r9   r2  r3  r4  )r¢   r   r3  úCounter[str])r}   Ústrr’   r5  r3  Úbool)r�   ÚBoxrŠ   r8  r3  r7  )r:   r   r<   úDict[str, TableItem]r?   r   r7   r0  r9   r2  r3  r4  )
r:   r   r?   r   r7   r0  r9   r2  r3  ú!Tuple[List[TableItem], List[str]])r:   r   r<   r9  r?   r   r7   r0  r9   r2  r3  r:  )
r:   r   r¾   r   rƒ   r   r9   r2  r3  r4  )r:   r   rƒ   r   r3  z	List[str])r:   r   r0   Úintr3  zset[str])r:   r   r0   r;  rç   r6  r3  zOptional[str])rž   r6  r3  r   )r¾   r   rƒ   r   r9   r2  r3  r4  )r?   r   r7   r0  r<   r9  r3  zList[Tuple[int, str]])r¢   r   r3  Úfloat)r¢   r   r3  r<  )rƒ   r   r9   r2  r3  zOptional[ProvenanceItem]r•   )
r6   r   rƒ   r   r9   r2  rÒ   zOptional[List[NodeItem]]r3  zTuple[Optional[NodeItem], bool])rs   r   r/  r<  r3  r<  )JÚ__doc__Ú
__future__r   ÚreÚcollectionsr   Útypingr   r   r   r   Údocling_core.types.doc.baser	   r
   Údocling_core.types.doc.documentr   r   r   r   r   r   r   r   r   Údocling_core.types.doc.labelsr   Úlogurur   Úquber.core.extractors.baser   Ú1quber.core.extractors.set_of_mark.merge_groundingr   Úquber.core.fusion.modelsr   Úquber.core.printed_textr   r[   r\   r<  ÚNormBoxr8  rE   r5   ÚTEXTÚ	PARAGRAPHrá   rå   rr   r3   rª   r©   ÚcompilerŸ   r4   r{   r|   rz   r+   r-   r/   r¼   ÚSECTION_HEADERrö   rß   rë   rì   rº   rÞ   r¸   rÍ   r  rÄ   rÅ   r$  r–   r¦   rD   Ú<module>rO     s<  ðñõ@ #ã 	Ý ß .Ó .ç @÷
÷ 
õ 
õ 7Ý å 5Ý KÝ 0Ý /ð !€ð €à
��u˜e UÐ*Ñ
+€ØˆE�5˜% Ð&Ñ'€ðCØðCà$ðCð ðCð .ð	Cð
 'ôCðL(Øð(àMð(ð 
ô(ð6 ×ÑØ×ÑØ×ÑØ×Ñð	Ð ôaðH €ØÐ à�JŠJ�|Ó$€	ð[1Øð[1àð[1ð %ð[1ð .ð	[1ð
 
ô[1ô|OôWô -ð@Øð@à'ð@ð ð@ð %ð	@ð
 .ð@ð 
ô@ð,!Øð!àð!ð %ð!ð .ð	!ð
 'ô!ðH3Øð3à'ð3ð ð3ð %ð	3ð
 .ð3ð 'ô3ðl5iØð5iàð5ið 
ð5ið .ð	5ið
 
ô5iðt ×ÑØ×ÑØ×ÑØ×Ñð	Ð ô!ôHô
ôTð@
Øð
Ø .ð
Ø;Yð
à	ô
ð@/Øð/à$ð/ð (ð/ð ô	/ôô ôHð* )-ð	% Øð% à	ð% ð .ð% ð &ð	% ð
 %õ% õP-r¦   