ó
    À‘´j›!  ã                  óv  • 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  SSKJr  SSKJr  SS	K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SK J!r!  SSK"J#r#J$r$  SSK%J&r&J'r'  \
\(\(\(\(4   r)          SS jr* " S S5      r+              SS jr,SS jr-        SS jr.g)aÌ  Fuse the document and table extractions into the two corrected outputs.

The fusion step: given a `ParseResult` (the document extraction) and the
Set-of-Mark/Camelot tables (the table extraction), match the two by overlap,
split any region SoM merged, annotate charts and image tables, and graft the
Camelot bodies into the docling spine. An LLM heading review then demotes page
decoration the parser labeled as section headings in the unified document, and
reports each demotion in `heading_flags`. The result carries both corrected
outputs: the SoM/Camelot tables (split, annotated) and the unified
`DoclingDocument`.

`fuse_artifacts` is the standalone core: it takes already-produced data and the
source PDF and knows nothing about how the data was produced, so a cloud job can
run it against artifacts loaded from S3. `DocumentFusion` is the local control
flow: it runs the document and table extractions in-process, one after the
other, then calls `fuse_artifacts`.
é    )ÚannotationsN)ÚPath)ÚDictÚListÚOptionalÚTuple)ÚCoordOrigin)Ú	TableItem)Úlogger)Ú	LLMClientÚget_llm_client)ÚExtractedTable)Úcamelot_bbox_to_norm)ÚSetOfMarkExtractor)Úsplit_table)Úbuild_unified_document)Úreview_headings)Úmatch_tables)ÚFusionResultÚRegionMatch)ÚParserÚParseResultc           	   ƒ  ó@  #   • U R                    Vs0 s H%  oDR                  UR                  UR                  4_M'     nnU R                  R
                   Vs0 s H  ofR                  U_M     nn[        X5      n[        X�X'XS5      I Sh  v•N u  pU	(       a  [        X5      n[        X�5        [        U R                  XU5      u  p«U H  n[        R                  " SU5        M     [        X£[        U5      S9I Sh  v•N n[        U
UUUUU S9$ s  snf s  snf  N� N7f)ap  Fuse already-produced document and table extractions into both outputs.

Takes the document extraction (`parse`), the table extraction (`som_tables`),
and the source PDF (needed to re-extract sub-regions when a merged region is
split). Knows nothing about how the inputs were produced, so the same call
serves the local flow and a cloud job loading artifacts from S3.
Nzfuse: {})Úsource)ÚdocumentÚtablesÚmatchesÚerrorsÚheading_flagsÚparse)ÚpagesÚpage_noÚwidthÚheightr   r   Úself_refr   Ú_split_merged_regionsÚ	_annotater   r   Úerrorr   Ústrr   )r    Ú
som_tablesr   ÚllmÚpÚ	page_dimsÚtÚtables_by_refr   Ú	split_anyÚunifiedr   Úerrr   s                 Ú6/home/mande/repo/quber/src/quber/core/fusion/fusion.pyÚfuse_artifactsr4   +   s  é € ð :?¿ºÓEº°A—‘˜QŸW™W a§h¡hÐ/Ò/¹€IÐEØ,1¯N©N×,AÒ,AÓBÒ,A q—Z‘Z ’]Ñ,A€MÐBä˜5Ó-€Gô
 #8Ø˜V°Ió#÷ Ñ€Jö Ü˜uÓ1ˆô ˆgÔ"ô -¨U¯^©^¸ZÐR[Ó\�O€GÛˆÜ�Š�Z Ö%ñ ô
 *¨'¼sÀ6»{ÑK×K€MäØØØØØ#Øñð ùò5 FùÚBññ  Lùs:   ‚D‘,D½DÁDÁ-DÂ
DÂA2DÃ=DÃ>DÄDc                  óT   • \ rS rSrSr    S         S	S jjrS
S jrS
S jrSrg)ÚDocumentFusioné\   uá  Local control flow: run both extractions in-process, then fuse them.

Holds the parser, the table extractor, and the LLM client. The client
serves the split pass, the heading review, and the default
`SetOfMarkExtractor` built when no extractor is passed. `fuse` runs the
whole flow; `fuse_sync` wraps it for the CLI. The cloud flow does not use
this class â€” it runs the document and table extractions as separate jobs
and calls `fuse_artifacts` directly on the loaded artifacts.
Nc                ó¦   • Uc  SSK Jn  U" U5      nXl        U=(       d    [        S 5      U l        U=(       d    [        U R                  S9U l        g )Nr   )Úparser_for_preset)r+   )Úquber.core.parsersr9   Úparserr   r+   r   Ú	extractor)Úselfr;   r<   r+   Úpresetr9   s         r3   Ú__init__ÚDocumentFusion.__init__g   sD   € ð ‰>õ
 =á& vÓ.ˆFØŒØ×.œ.¨Ó.ˆŒØ"×FÔ&8¸T¿X¹XÑ&Fˆ�ó    c              ƒ  ó  #   • [         R                  " U R                  R                  U5      nU R                  R                  U5      I S h  v•N nUI S h  v•N n[        XCXR                  5      I S h  v•N $  N, N$ N7f©N)ÚasyncioÚ	to_threadr;   r    r<   Úextract_tablesr4   r+   )r=   r   Ú
parse_taskr*   r    s        r3   ÚfuseÚDocumentFusion.fusez   sb   é € ô ×&Ò& t§{¡{×'8Ñ'8¸&ÓAˆ
ØŸ>™>×8Ñ8¸Ó@×@ˆ
Ø#-×-ˆÜ# E°v¿x¹xÓH×HÐHñ AÙ-ÙHùs6   ‚A
A?ÁA9Á	A?ÁA;ÁA?Á4A=Á5A?Á;A?Á=A?c                óL   • [         R                  " U R                  U5      5      $ rC   )rD   ÚrunrH   )r=   r   s     r3   Ú	fuse_syncÚDocumentFusion.fuse_syncƒ   s   € Ü�{Š{˜4Ÿ9™9 VÓ,Ó-Ð-rA   )r<   r+   r;   )NNNztuned-financial)
r;   zOptional[Parser]r<   zOptional[SetOfMarkExtractor]r+   zOptional[LLMClient]r>   r)   ÚreturnÚNone)r   r   rN   r   )	Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r?   rH   rL   Ú__static_attributes__© rA   r3   r6   r6   \   s[   † ñð $(Ø26Ø#'Ø'ðGà ðGð 0ðGð !ð	Gð
 ðGð 
õGô&I÷.rA   r6   c           	   ƒ  óè  #   • [        U5      nSnU  GHL  nUR                  S:w  d  [        UR                  5      S:w  a  M/  UR                  S   n	[        UR                  5      n
UR                   Vs/ s H"  nX³;   d  M
  [        X;   XHR                  5      PM$     nnU Vs/ s H	  oÝc  M  UPM     nn[        U5      U
:w  a  M³  [        U5      Xi   l        [        Xi   X¬U5      I Sh  v•N n[        U5      S:”  d  Mí  US   Xi'   [        USS SS9 H  u  nnUR                  XŸ-   U5        M     Sn[        R                  " SUR                  [        U5      5        GMO     Xg4$ s  snf s  snf  N†7f)	a  Replace each SoM-merged table with the tables `split_table` recovers.

Only the single-SoM-table case is handled (one fused region docling sees as
several); a region with more than one SoM table is too ambiguous to split and
is left for the graft pass to report.
FÚ
som_mergedé   r   N)ÚstartTz7fuse: split SoM-merged region on page {} into {} tables)ÚlistÚkindÚlenÚsom_indicesÚdocling_table_refsÚ_docling_norm_boxÚpager)   r   r   Ú	enumerateÚinsertr   Úinfo)r   r*   r   r/   r-   r+   Úoutr0   ÚmatchÚidxÚcountÚrÚ
boundariesÚbÚsplitsÚoffsetÚextras                    r3   r&   r&   ‡   sf  é € ô ˆzÓ
€CØ€IÜˆØ�:‰:˜Ó%¬¨U×->Ñ->Ó)?À1Ó)DÙØ×Ñ Ñ"ˆÜ�E×,Ñ,Ó-ˆð ×-Ò-ó
â-�ØÑ!ó GÔ˜mÑ.°	¿:¹:ÖFÙ-ð 	ð 
ñ
 ",Ó=¢˜A—a¡ˆ
Ð=Üˆz‹?˜eÓ#Ùô ˜f›+ˆ‰ŒÜ" 3¡8¨UÀÓD×DˆÜˆv‹;˜�?Ø˜a‘yˆC‰Hä!*¨6°!°"¨:¸QÔ!?‘�˜Ø—
‘
˜3™<¨Ö/ñ "@àˆIÜ�KŠKØIØ—
‘
Ü�F“÷ñ/ ð8 ˆ>Ðùò/
ùò
 >ñ Eùs=   ‚A1E2Á3	E&Â E&ÂE2Â"E+Â,E+Â27E2Ã)E0Ã*E2Ã?A2E2c                óÎ   • U  H_  nUR                   S:X  a  UR                   H  nSX   l         M     M1  UR                   S:X  d  MC  UR                   H  nSX   l         M     Ma     g)zGSet `ExtractedTable.kind` for image tables and charts from the matches.Úimage_tableÚchartN)r\   r^   )r   r*   rf   Úis       r3   r'   r'   ¶   sX   € ãˆØ�:‰:˜Ó&Ø×&Ô&�Ø%2�
‘Ö"ó 'à�Z‰Z˜7Õ"Ø×&Ô&�Ø%,�
‘Ö"ó 'ò rA   c                ó   • U R                   (       d  gUR                  US5      u  p4U R                   S   R                  nUR                  [        R
                  :X  a:  UR                  U-  UR                  U-  UR                  U-  UR                  U-  4$ [        UR                  UR                  UR                  UR                  4X45      $ )zKA docling table's box in the normalized top-left frame split_table expects.N)g      ƒ@g     Àˆ@r   )ÚprovÚgetÚbboxÚcoord_originr	   ÚTOPLEFTÚlr.   ri   rk   r   )Útabler-   ra   r#   r$   rv   s         r3   r`   r`   Á   sœ   € ð �:�:ØØ—M‘M $¨Ó7�M€EØ�:‰:�a‰=×Ñ€DØ×ÑœK×/Ñ/Ó/Ø—‘˜‘ §¡¨¡°·±¸%±ÀÇÁÈ&ÁÐQÐQÜ §¡¨¯©°·±¸¿¹Ð @À%ÓPÐPrA   )
r    r   r*   úList[ExtractedTable]r   r   r+   r   rN   r   )r   úList[RegionMatch]r*   r{   r   r   r/   zDict[str, TableItem]r-   úDict[int, Tuple[float, float]]r+   r   rN   z!Tuple[List[ExtractedTable], bool])r   r|   r*   r{   rN   rO   )rz   r
   r-   r}   ra   ÚintrN   zOptional[NormBox])/rT   Ú
__future__r   rD   Úpathlibr   Útypingr   r   r   r   Údocling_core.types.doc.baser	   Údocling_core.types.doc.documentr
   Úlogurur   Úquber.agents.llm_clientr   r   Úquber.core.extractors.baser   Ú5quber.core.extractors.camelot.correspondence.geometryr   Ú!quber.core.extractors.set_of_markr   Ú'quber.core.extractors.set_of_mark.splitr   Úquber.core.fusion.graftr   Ú quber.core.fusion.heading_reviewr   Úquber.core.fusion.matchingr   Úquber.core.fusion.modelsr   r   r:   r   r   ÚfloatÚNormBoxr4   r6   r&   r'   r`   rV   rA   r3   Ú<module>r�      s  ðñõ$ #ã Ý ß .Ó .å 3Ý 5Ý ç =Ý 5Ý VÝ @Ý ?Ý :Ý <Ý 3ß >ß 2à
��u˜e UÐ*Ñ
+€ð.Øð.à$ð.ð ð.ð 
ð	.ð
 ô.÷b(.ñ (.ðV,Øð,à$ð,ð ð,ð (ð	,ð
 .ð,ð 
ð,ð 'ô,ô^-ð
QØð
QØ!?ð
QØGJð
Qàõ
QrA   