ó
    À‘´j7�  ã                  ó†  • S r SSKJr  SSKrSSKrSSKrSSKJr  SSK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Jr  SSKJr  SS	KJrJr  SS
KJrJr  SrSrSr Sr!Sr"S6S jr#\4S7S jjr$ " S S\5      r% " S S\5      r& " S S\5      r'S8S jr(              S9S jr)\!\"4               S:S jjr*\\4         S;S jjr+              S<S jr,          S=S jr-Sr. S>             S?S jjr/            S@S  jr0            SAS! jr1S"r2SBS# jr3S$\4             SCS% jjr4S"r5Sr6 SD             SES& jjr7S'r8S(r9          SFS) jr: SG           SHS* jjr;\<" 1 S+k5      r=S,r>S-r?SIS. jr@\ " S/ S0\5      5       rA " S1 S25      rB " S3 S45      rCSJSKS5 jjrDg)Lu
  
GridLocator â€” Set-of-Mark table flagging.

The grid locator finds every table and its region for the default
Set-of-Mark engine and for the dual engine. The camelot correspondence
engine also calls it lazily, when a detector box has drifted off its table.

The raw detector bbox drifts: a vision model asked for continuous
coordinates emits a discretized, often evenly-spaced grid that walks off
the actual tables on repetitive/stacked layouts (measured on stacked4 and
real TMUS/Visa pages). This module sidesteps that failure mode.

Instead of asking the model for coordinates, it overlays a *labeled* grid
on the rasterized page (numbered rows, lettered columns) and asks which
grid cells each table occupies. The model reports discrete row/column IDs
â€” reading printed labels, which it does reliably â€” and the grid, being
ours, maps each ID back to an exact coordinate. The coarse cell region is
then tightened to the page's text layer for a precise box.

This follows the Set-of-Mark / coordinate-scaffold line of visual prompting:

  - Yang, Zhang, Li, Zou, Li, Gao, "Set-of-Mark Prompting Unleashes
    Extraordinary Visual Grounding in GPT-4V," arXiv:2310.11441 (2023):
    overlay labelled marks on the image so the model answers by reading the
    marks rather than predicting continuous coordinates.
  - Lei, Yang, et al., "Scaffolding Coordinates to Promote Vision-Language
    Coordination in Large Multi-Modal Models," arXiv:2402.12058 (2024):
    overlay a labelled coordinate grid as positional anchors the model reads.

Overlaying a labelled row/column grid and reading back grid IDs in place of
continuous coordinates is the direct application of that work here.

The model is asked to bound the WHOLE table, not just its data grid: the
title/caption, units line, multi-level column headers, the row-label stub
column, the body and total rows, and any footnotes attached to the table. After
the text-layer tighten, each box is grown by a small fixed margin so a
downstream crop does not clip an edge, and two declash passes settle the seam
between overlapping neighbours: a stacked pair gets a horizontal seam through
the widest blank band near the model's row boundary, a side-by-side pair a
vertical seam through a blank column in the overlap. Which pass a pair gets is
the model's call, read off the grid column spans it reported. A side-by-side
pair whose overlap holds no blank column keeps the overlap, so a shared column
can land in both boxes. When tightening finds no text (an image-only region),
the coarse grid region is kept; a region is never dropped.
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚListÚOptionalÚProtocolÚSequenceÚTupleÚruntime_checkable)Úlogger)Ú	BaseModelÚField)Úsupports_sampling_temperature)ÚLangSmithTracerÚusage_metadata_from)ÚDEFAULT_LLM_MODELÚget_settingsé$   é   g      @g      $@c                ó(   • [         R                  SU  $ )z<Column letters A, B, C, ... for `cols` columns (cols <= 26).N)ÚstringÚascii_uppercase)Úcolss    Ú7/home/mande/repo/quber/src/quber/agents/grid_locator.pyÚcolumn_labelsr   U   s   € ä×!Ñ! % 4Ð(Ð(ó    c                ó¶   • [        U5      nU R                  5       (       d  gU R                  5       R                  5       S   nX2;   a  UR                  U5      $ S$ )z¤Map a column letter back to its 0-based index.

A blank letter or one past the last column maps to 0, the left edge. It
is not clamped to the nearest valid column.
r   )r   ÚstripÚupperÚindex)Úletterr   ÚlabelsÚchs       r   Úcolumn_indexr%   Z   sM   € ô ˜4Ó €FØ�<‰<�>‰>ØØ	�‰‹×	Ñ	Ó	 Ñ	"€BØ!›|ˆ6�<‰<˜ÓÐ2°Ð2r   c                  ó¢   • \ rS rSr% Sr\" SSS9rS\S'   \" SS	S
9rS\S'   \" SS9r	S\S'   \" SS9r
S\S'   \" SS9rS\S'   \" SS9rS\S'   Srg)ÚLocatedTableég   u�  A table flagged by the grid locator, with a tightened region.

`region` is normalized 0..1 with the page's top-left as origin
(x1, y1, x2, y2) â€” the same frame as `DetectedTable.bbox`, so it drops
straight into the correspondence/recovery geometry. `tightened` records
whether the region was snapped to the text layer (False means the
coarse grid cell was kept because no text was found there).
é   z*1-based reading-order position on the page©ÚgeÚdescriptionÚintÚordinalÚ z*Visible caption or one-line column summary©Údefaultr,   ÚstrÚtitlezBTightened region, normalized 0..1 top-left origin (x1, y1, x2, y2)©r,   ú!Tuple[float, float, float, float]Úregionz6Grid row span the model reported (start, end), 1-basedúTuple[int, int]Ú	grid_rowsz0Grid column span as 0-based indices (start, end)Ú	grid_colszFTrue if snapped to the text layer; False if coarse cell kept (no text)ÚboolÚ	tightened© N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r   r.   Ú__annotations__r3   r6   r8   r9   r;   Ú__static_attributes__r<   r   r   r'   r'   g   sx   ‡ ññ ˜AÐ+WÑX€GˆSÓXÙ˜rÐ/[Ñ\€Eˆ3Ó\Ù05ØXñ1€FÐ-ó ñ "'Ð3kÑ!l€IˆÓlÙ!&Ð3eÑ!f€IˆÓfÙØ\ñ€Iˆtö r   r'   c                  óž   • \ rS rSr% \" SSS9rS\S'   \" SSS	9rS
\S'   \" SS9rS\S'   \" SS9r	S\S'   \" SS9r
S
\S'   \" SS9rS
\S'   Srg)ÚGridFlagé€   r)   z91-based reading order (top-to-bottom, then left-to-right)r*   r-   r.   r/   z/Visible caption, else a one-line column summaryr0   r2   r3   z/Printed grid row number at the table's TOP edger4   Ú	row_startz2Printed grid row number at the table's BOTTOM edgeÚrow_endz3Printed grid column LETTER at the table's LEFT edgeÚ	col_startz4Printed grid column LETTER at the table's RIGHT edgeÚcol_endr<   N)r=   r>   r?   r@   r   r.   rB   r3   rG   rH   rI   rJ   rC   r<   r   r   rE   rE   €   sf   ‡ Ù˜AÐ+fÑg€GˆSÓgÙ˜rÐ/`Ña€Eˆ3ÓaÙÐ'XÑY€IˆsÓYÙÐ%YÑZ€GˆSÓZÙÐ'\Ñ]€IˆsÓ]ÙÐ%[Ñ\€GˆSÖ\r   rE   c                  ó,   • \ rS rSr% \" \S9rS\S'   Srg)ÚGridFlagResulté‰   )Údefault_factoryzList[GridFlag]Útablesr<   N)	r=   r>   r?   r@   r   ÚlistrO   rB   rC   r<   r   r   rL   rL   ‰   s   ‡ Ù"°4Ñ8€FˆNÖ8r   rL   c                óŒ   • [        U5      S   nSU  SU  SU SU S[        R                  " [        R	                  5       SS9 S	3$ )
Néÿÿÿÿz$The page image has a grid overlaid: z+ numbered horizontal rows (1 at the
top to z2 at the bottom, labeled on BOTH side margins) and z$ lettered
columns (A at the left to u¿   at the right, labeled on the top and bottom
margins), with thin gridlines.

WHAT COUNTS AS A TABLE: a block where values are arranged in a GRID â€” the same
two or more columns line up vertically across multiple rows. These are tables.
The following are NOT tables, even when they contain dollar amounts or numbers,
and must never be flagged: a bulleted or numbered list; a single column of
figures; a heading followed by prose sentences; a paragraph. If the numbers do
not line up into repeating vertical columns, it is not a table.

Find every TABLE on the page and report the grid region that bounds each one
COMPLETELY. Bound the WHOLE table, including all of its parts:

- TITLE / CAPTION: the table's heading line(s) and any units line directly
  above the columns (e.g. "Condensed Consolidated Statements of Income",
  "(In millions)", "(UNAUDITED)"). Include them in the region.
- COLUMN HEADERS: every header row, including multi-level / spanning headers
  (a label like "Three Months Ended" that sits above several sub-columns, with
  the period and year rows beneath it). Include all header levels.
- ROW LABELS (the stub column): the left-hand column of row names. The region's
  LEFT edge must include it â€” do not start at the first number.
- BODY: every data row, including subtotal and total rows. Never cut a table
  off partway; blank lines inside a table do NOT end it.
- FOOTNOTES: short notes tied to the table by a marker (e.g. "(1)", "(2)")
  sitting just below it. Include them inside that table's region. Footnotes are
  NEVER a table of their own â€” never report them as a separate table.

Counting tables â€” judge by what you see, not by repeated text:
- Two tables stacked vertically are separate ONLY when each has its own
  title/caption and its own column-header block. Report them separately.
- Two tables printed SIDE BY SIDE, one to the left of the other in the same
  rows, are separate tables. Report each with its own column span.
- One table may have section sub-headings inside it (e.g. "Cash flows from
  operating activities:", "Current assets:") and blank lines between sections.
  These do NOT start a new table â€” keep the whole thing as ONE region.
- Repeated "$" signs or a repeated subtotal style inside one table do NOT make
  a new table. When unsure, prefer ONE region over splitting.
Do NOT merge two genuinely separate tables, and do NOT split one table.

Exclude page furniture (running header timestamp, page number, document URL)
and free-standing prose paragraphs / lists that are not a table.

For each table, in reading order (top-to-bottom, then left-to-right), report:
- ordinal (1 = first)
- title (its visible caption, else a one-line summary of its columns)
- row_start: printed row number at the TOP of the title line (one row ABOVE the
  column headers when a title/caption is present) â€” include the title row
- row_end: printed row number at the BOTTOM-most part (last total, or footnote)
- col_start: printed column LETTER at the left edge, INCLUDING the row labels
- col_end: printed column LETTER at the right edge (last data column)

Read the printed labels off the grid â€” do not estimate coordinates.

Return ONLY a JSON object conforming to this schema (no prose, no code fences):

é   )ÚindentÚ
)r   ÚjsonÚdumpsrL   Úmodel_json_schema)Úrowsr   Úlasts      r   Úbuild_promptr[   �   si   € Ü˜Ó˜rÑ"€Dð%Ø%) Fð +Ø€vÐ?À¸vð FØ˜&ð 7!ôn ‡‚ŒN×,Ñ,Ó.°qÑ9Ð :ð ;ðu;ð ;r   c                óŠ   • [        SU S-
  U-  5      n[        SX-  5      n[        SX%-  5      n[        SUS-   U-  5      n	X†X—4$ )zÄCoarse grid cell span -> normalized 0..1 top-left box.

Rows are 1-based and inclusive (a table on rows 4..6 spans the top of
row 4 to the bottom of row 6). Columns are 0-based inclusive indices.
ç        r)   ç      ð?)ÚmaxÚmin)
rG   rH   Úcol_start_idxÚcol_end_idxrY   r   Úy0Úy1Úx0Úx1s
             r   Úgrid_region_normrg   Ð   sV   € ô 
ˆS�9˜q‘= DÑ(Ó	)€BÜ	ˆS�'‘.Ó	!€BÜ	ˆS�-Ñ&Ó	'€BÜ	ˆS�; ‘? dÑ*Ó	+€BØ�BÐÐr   c                óf  • [        U S   U S   5      U-  n[        U S   U S   5      U-  n[        U S   U S   5      U-  n	[        U S   U S   5      U-  n
U Vs/ s H(  o¹U-
  US   US   -   S-  s=::  a
  X¥-   ::  d  M"  O  M&  UPM*     nnU(       a6  U Vs/ s H(  o·U-
  US   US   -   S-  s=::  a
  X†-   ::  d  M"  O  M&  UPM*     nnOUnU(       d  g[        S U 5       5      n[        S U 5       5      n[        S	 U 5       5      n[        S
 U 5       5      nXâ-  Xó-  UU-  UU-  4$ s  snf s  snf )uÏ  Snap a coarse grid region to the page's text layer.

The grid region's vertical span is reliable (rows separate stacked
tables); within it, take the words and tight-box them. When the table is
vertically alone (`shares_rows` False) the full horizontal extent of
those words is used â€” recovering a label column the model clipped. When
it shares its rows with a side-by-side neighbour, selection is confined
to the flagged column range so neighbours do not merge.

`words` are PyMuPDF word tuples (x0, y0, x1, y1, text, ...) in PDF points,
top-left origin. Returns the tightened normalized box, or None when no
word falls in the region (an image-only region â€” the caller keeps the
coarse region rather than drop it).
r   rS   r)   é   ç       @Nc              3  ó*   #   • U  H	  oS    v •  M     g7f)r   Nr<   ©Ú.0Úws     r   Ú	<genexpr>Ú!tighten_region.<locals>.<genexpr>  ó   é € Ð šC�q�ŽdšCùó   ‚c              3  ó*   #   • U  H	  oS    v •  M     g7f)r)   Nr<   rl   s     r   ro   rp     rq   rr   c              3  ó*   #   • U  H	  oS    v •  M     g7f)rS   Nr<   rl   s     r   ro   rp     rq   rr   c              3  ó*   #   • U  H	  oS    v •  M     g7f©ri   Nr<   rl   s     r   ro   rp     rq   rr   ©r`   r_   )Úregion_normÚwordsÚpage_wÚpage_hÚshares_rowsÚy_padÚx_padre   rf   rc   rd   rn   Úin_yÚselÚtx0Úty0Útx1Úty1s                     r   Útighten_regionr…   ß   sP  € ô. 
ˆ[˜‰^˜[¨™^Ó	,¨vÑ	5€BÜ	ˆ[˜‰^˜[¨™^Ó	,¨vÑ	5€BÜ	ˆ[˜‰^˜[¨™^Ó	,¨vÑ	5€BÜ	ˆ[˜‰^˜[¨™^Ó	,¨vÑ	5€BáÓP’u�! U¡
¨q°©t°a¸±d©{¸cÑ.AÕ OÀRÁZÑ O‹AÑ O�A‘u€DÐPÞÙÓRš$�Q u¡*°°1±¸¸!¹±ÀÑ0CÕ"QÀrÁzÑ"Q‹qÑ"Q�q™$ˆÐRˆàˆÞØÜ
Ñ ™CÓ Ó
 €CÜ
Ñ ™CÓ Ó
 €CÜ
Ñ ™CÓ Ó
 €CÜ
Ñ ™CÓ Ó
 €CØ‰L˜#™,¨¨f©°c¸F±lÐCÐCùò QùâRs$   Á!D)Á>D)ÂD)Â!D.Â:D.Â>D.c                ó~  • SSK JnJnJn  UR	                  U 5      R                  S5      nUR                  U5      n UR                  SS5      n	[        U5      n
UR                  U-  nUR                  U-  n[        US-   5       H‚  nXÛ-  nUR                  SU4UR                  U4/SSS9  XÒ:  d  M/  UR                  S	US	-   4[!        US-   5      S
U	S9  UR                  UR                  S-
  US	-   4[!        US-   5      S
U	S9  M„     [        US-   5       Hn  nXü-  nUR                  US4UUR                  4/SSS9  Xó:  d  M/  UR                  US-   S	4X¯   SU	S9  UR                  US-   UR                  S-
  4X¯   SU	S9  Mp     UR#                  U5        g! [         a    UR                  5       n	 GNlf = f)z8Draw a labeled row/column grid on a rendered page image.r   )ÚImageÚ	ImageDrawÚ	ImageFontÚRGBz4/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttfé   r)   )éP   éx   éÿ   )ÚfillÚwidthrS   )é(   rŒ   éæ   )r�   Úfonté   )rŽ   éŒ   r   ri   )éÒ   én   r   é   N)ÚPILr‡   rˆ   r‰   ÚopenÚconvertÚDrawÚtruetypeÚ	ExceptionÚload_defaultr   Úheightr�   ÚrangeÚlineÚtextr2   Úsave)Ú
image_pathÚout_pathrY   r   r‡   rˆ   r‰   ÚimgÚdrawr“   r#   ÚrstepÚcstepÚrÚyÚciÚxs                    r   Úoverlay_gridr¯   	  s»  € ÷ 0Ñ/à
�*‰*�ZÓ
 ×
(Ñ
(¨Ó
/€CØ�>‰>˜#Ó€Dð(Ø×!Ñ!Ð"XÐZ\Ó]ˆô ˜4Ó €FØ�J‰J˜Ñ€EØ�I‰I˜Ñ€EÜ�4˜!‘8Ž_ˆØ‰IˆØ�	‰	�A�q�6˜CŸI™I q˜>Ð*°Àqˆ	ÑIØ�8Ø�I‰I�q˜!˜a™%�j¤# a¨!¡e£*°=ÀtˆIÑLØ�I‰I�s—y‘y 2‘~ q¨1¡uÐ-¬s°1°q±5«zÀÐTXˆIÓYñ ô �D˜1‘HŽoˆØ‰JˆØ�	‰	�A�q�6˜A˜sŸz™z˜?Ð+°-Àqˆ	ÑIØ�9Ø�I‰I�q˜1‘u˜a�j &¡*°=ÀtˆIÑLØ�I‰I�q˜1‘u˜cŸj™j¨2™oÐ.°±
ÀÐUYˆIÓZñ ð ‡H�HˆXÕøô# ó (Ø×%Ñ%Ó'‹ð(ús   ½F ÆF<Æ;F<c                óª  ^• / nU  HÉ  m[        U4S jU  5       5      n[        TR                  U5      n[        TR                  U5      n	X˜:  a  X˜p˜[	        TR
                  TR                  X‰XE5      n
[        X¡X#U5      nUR                  [        TR                  TR                  Ub  UOU
TR
                  TR                  4X‰4USLS95        MË     U$ )a"  Turn the model's grid-ID flags into tightened LocatedTables.

`shares_rows` for each flag is computed against the other flags: a flag
whose grid-row span overlaps another's is treated as side-by-side and
tightened within its columns; otherwise it snaps to the full word extent
in its rows.
c              3  ó´   >#   • U  HM  oTL=(       a>    TR                   UR                  :  =(       d    TR                  UR                   :„  (       + v •  MO     g 7f©N)rH   rG   )rm   ÚoÚfs     €r   ro   Ú#flags_to_located.<locals>.<genexpr>8  sC   øé € ÐlÒfkÐab˜a�Z×\¨¯©°Q·[±[Ñ)@×)[ÀAÇKÁKÐRS×R[ÑR[ÑD[Ô$\Ô\Òfkùs   ƒAAN)r.   r3   r6   r8   r9   r;   )Úanyr%   rI   rJ   rg   rG   rH   r…   Úappendr'   r.   r3   )Úflagsry   rz   r{   rY   r   ÚlocatedÚsharesÚc0Úc1ÚcoarseÚtightr´   s               @r   Úflags_to_locatedr¿   '  sÅ   ø€ ð #%€GÛˆÜÔlÑfkÓlÓlˆÜ˜!Ÿ+™+ tÓ,ˆÜ˜!Ÿ)™) TÓ*ˆØ‹7Ø�Ü! !§+¡+¨q¯y©y¸"À$ÓMˆÜ˜v¨f¸fÓEˆØ�‰ÜØŸ	™	Ø—g‘gØ %Ñ 1‘u°vØŸ;™;¨¯	©	Ð2Ø˜(Ø tÐ+ñö		
ñ ð$ €Nr   c                óD  • X1-  X2-  pT/ nU  H�  nUR                   u  p‰p«[        S[        XŠ5      U-
  5      [        S[        X›5      U-
  5      [        S[        XŠ5      U-   5      [        S[        X›5      U-   5      4nUR                  UR	                  SU0S95        M’     U$ )aS  Grow every region by a fixed margin on all sides, clamped to the page.

A guidance box should over-include slightly rather than clip, so a plain
symmetric pad covers residual sub-row edge misses without any
content-dependent heuristic. Any overlap this creates between stacked
tables is resolved by `declash_stacked`, which runs after it.
r]   r^   r6   ©Úupdate)r6   r_   r`   r·   Ú
model_copy)r¹   rz   r{   Úpad_ptsÚpxÚpyÚoutÚtre   rc   rf   rd   r6   s                r   Ú	pad_boxesrÉ   L  s¥   € ð Ñ˜wÑ/ˆØ €CÛˆØŸ™‰ˆ�ä�”S˜“[ 2Ñ%Ó&Ü�”S˜“[ 2Ñ%Ó&Ü�”S˜“[ 2Ñ%Ó&Ü�”S˜“[ 2Ñ%Ó&ð	
ˆð 	�
‰
�1—<‘<¨°&Ð'9�<Ð:Ö;ñ ð €Jr   g      ø?ç      @c           	     óR  • U  Vs/ s HA  ocUS   US   -   S-  s=::  a  U::  d  M  O  M!  XS   US   -   S-  s=::  a  U::  d  M;  O  M?  UPMC     nnUR                  S S9  / nU H‹  nUS   US   -   S-  n	U(       aW  X˜S   S	   -
  U::  aI  US   n
[        U
S
   US   5      U
S
'   [        U
S   US   5      U
S'   XšS	'   U
S   R                  U5        Mo  UR                  X–S   US   U/S.5        M�     U H*  n
SR	                  S [        U
S   S S9 5       5      U
S'   M,     U$ s  snf )a  Group page words into printed text rows within a window.

Words whose centers fall outside the window [x0, x1] x [y_lo, y_hi] are
discarded. The rest, taken top to bottom by vertical center, join the
current row when their center is within `tol` points of the last word added
to it. Otherwise they start a new row. Each returned row carries its top
edge, bottom edge and left-to-right joined text. Rows come back ordered top
to bottom. `words` are PyMuPDF word tuples (x0, y0, x1, y1, text, ...) in
PDF points, top-left origin.
r)   ri   rj   r   rS   c                ó   • U S   U S   -   S-  $ )Nr)   ri   rj   r<   ©rn   s    r   Ú<lambda>Ú#cluster_text_rows.<locals>.<lambda>~  s   € ˜A˜a™D 1 Q¡4™K¨3Ò.r   ©ÚkeyrR   ÚcenterÚtopÚbottomry   )rÒ   rÓ   rÔ   ry   Ú c              3  ó*   #   • U  H	  oS    v •  M     g7f)é   Nr<   rl   s     r   ro   Ú$cluster_text_rows.<locals>.<genexpr>‹  s   é € ÐVÒ-U¨ žtÒ-Uùrr   c                ó   • U S   $ ©Nr   r<   rÍ   s    r   rÎ   rÏ   ‹  s   € ÐPQÐRSÒPTr   r£   )Úsortr`   r_   r·   ÚjoinÚsorted)ry   re   rf   Úy_loÚy_hiÚtolrn   r€   rY   rÒ   Úrows              r   Úcluster_text_rowsrâ   k  sU  € ñ$ Ó
g’e�¨¨!©¨q°©t©°sÑ':ÕB¸dÑB‹1ÑB‰1ÀrÐPQÉdÐUVÐWXÑUYÉkÐ]`ÑM`ÕGfÐdfÑGf‹1ÑGf�1‘e€CÐ
gØ‡H�HÑ.€HÑ/Ø!#€DÛˆØ�A‘$˜˜1™‘+ Ñ$ˆÞ�F "™X hÑ/Ñ/°3Ó6Ø�r‘(ˆCÜ˜S ™Z¨¨1©Ó.ˆC�‰JÜ  H¡¨q°©tÓ4ˆC�‰MØ"�‰MØ�‰L×Ñ Ö"à�K‰K 6°A±$À!ÀAÁ$ÐRSÐQTÑUÖVñ ó ˆØ—h‘hÑV¬V°C¸±LÁnÒ-UÓVÓVˆˆF‹ñ à€Kùò hs   …D$¥D$©D$ÁD$ÁD$c                ó´   • [        XX#U5      n[        U5      S:  a  gSu  pg[        XUSS SS9 H%  u  p‰U	S   US   -
  n
X¦:”  d  M  X¨S   U	S   -   S	-  pvM'     U$ )
u¡  Midpoint of the widest blank band between consecutive text rows in the
window [y_lo, y_hi] (PDF points), or None when fewer than two rows fall in it.

Two stacked tables are separated by a blank band wider than the spacing
between rows inside either table, so the widest gap in a window centred on
their boundary is the seam â€” independent of whether the rows on each side are
totals, titles, headers or plain data.
rS   N)g      ð¿Nr)   F©ÚstrictrÓ   rÔ   rj   )râ   ÚlenÚzip)ry   re   rf   rÞ   rß   rY   Úbest_gapÚseamr    ÚlowerÚgaps              r   Úwidest_gap_seamrì   �  su   € ô ˜U¨°$Ó7€DÜ
ˆ4ƒy�1ƒ}ØØ�N€HÜ˜D q r (°5Ô9‰ˆØ�E‰l˜U 8™_Ñ,ˆØ�>Ø ¨¡?°U¸5±\Ñ#AÀSÑ"H’dñ :ð €Kr   c                óp  • [        U S   U S   5      U-  n[        U S   U S   5      U-  n[        U S   U S   5      U-  nU Vs/ s HK  nXXS   US   -   S-  s=:  a  US-
  :  d  M   O  M$  US-
  US   US   -   S-  s=::  a  US-   ::  d  ME  O  MI  UPMM     n	n[        [        S U	 5       US9U5      $ s  snf )	as  Pull the upper box's bottom up to its own last line of text above the
lower box's top. `declash_stacked` uses this when `widest_gap_seam` finds
no band near the model's row boundary, or finds one outside the two boxes'
combined vertical span.

Returns the new upper-box bottom in PDF points (top-origin); never reaches
below the lower box's top, so the boxes still tile.
r)   ri   r   rS   rj   ç      à?c              3  ó*   #   • U  H	  oS    v •  M     g7frv   r<   rl   s     r   ro   Ú fallback_seam.<locals>.<genexpr>À  s   é € Ð)¢&˜Q�a–D¢&ùrr   )r1   rw   )
Úupper_regionÚ	lower_topry   rz   r{   Úu_topÚux0Úux1rn   Úinsides
             r   Úfallback_seamr÷   ©  sô   € ô �˜Q‘ ¨a¡Ó1°FÑ:€EÜ
ˆl˜1‰o˜|¨A™Ó
/°&Ñ
8€CÜ
ˆl˜1‰o˜|¨A™Ó
/°&Ñ
8€Cñ óâˆAØ�a‘D˜1˜Q™4‘K 3Ñ&Õ8¨°S©Ñ8ó 	
á8ñ 	
à=@À3¹YÈ1ÈQÉ4ÐRSÐTUÑRVÉ;ÐZ]ÑJ]Õ=jÐadÐgjÑajÑ=jó 	
á=jõ 	
Ùð ð ô
 ŒsÑ)¡&Ó)°9Ñ=¸yÓIÐIùòs   ÁB3Á'B3Á+B3ÂB3ÂB3rî   c                óÈ   • [        U 5      [        U 5      p2[        U5      [        U5      pT[        X55      [        X$5      -
  S-   n[        X2-
  XT-
  5      S-   nUS:”  a  Xg-  $ S$ )z‹Fraction of the narrower grid-column span shared by the two spans.

Spans are the locator's 0-based inclusive (start, end) column indices.
r)   r   r]   rw   )ÚaÚbÚa0Úa1Úb0Úb1ÚsharedÚnarrowers           r   Úcol_span_overlap_fractionr  Î  sb   € ô
 �‹V”S˜“VˆÜ�‹V”S˜“VˆÜ�‹[œ3˜r›;Ñ&¨Ñ*€FÜ�2‘7˜B™GÓ$ qÑ(€HØ &¨£
ˆ6ÑÐ3°Ð3r   rj   c           
     ón  ^ • [        [        [        T 5      5      U 4S jS9n[        [        T 5      5       Vs0 s H  owT U   R                  _M     nnX5-  n	[	        XfSS SS9 GH‹  u  p«XŠ   X‹   pÜT U
   R
                  T U   R
                  pþ[        Xï5      [        :  a  M?  [        US   US   5      n[        US   US   5      U-  n[        US   US   5      U-  n[        US   US   5      nUUU-
  ::  a  M˜  T U
   R                  S   U-  T U   R                  S   S-
  U-  -   S	-  U-  n[        US   US
   US   US
   5      U-  S	-
  n[        US   US
   US   US
   5      U-  S	-   n[        UUUU[        U	-  -
  U[        U	-  -   5      nUb:  UU-  Us=:  a	  UU-  :  a'  O  O$UU-  nUS   UUS
   U4XŠ'   US   UUS
   U4X‹'   GMl  [        UUXU5      nUS   UUS
   UU-  4XŠ'   GMŽ     [        [        T 5      5       Vs/ s H  nT U   R                  SX‡   0S9PM     sn$ s  snf s  snf )uÜ  Set the seam between two stacked boxes to the blank band between the two
tables, so neither box trades a row with its neighbour.

When an upper box's bottom runs into the next box's top â€” grid
quantization, padding, or an upward tighten can pull one box over a row
belonging to the other â€” take the model's row-span boundary between the two
tables as a coarse anchor, find the widest word-free band within about a grid
row of it, and snap both the upper box's bottom and the lower box's top to
its midpoint. Each table keeps every one of its own rows, whatever they are.
When no gap is found in the window, fall back to pulling the upper box's
bottom up to its own last line of text. Only cleans the seam; it never merges
two boxes or splits one.

Only pairs the locator placed in the same column band get a seam: whether
two tables are stacked is the locator's call, read off the grid column
spans it reported. A pair whose spans share less than
`STACKED_MIN_COL_OVERLAP` of the narrower span is side-by-side, and a seam
through it would cut real rows from both tables. Such pairs pass through
untouched.

`words` are PyMuPDF word tuples (x0, y0, x1, y1, text, ...) in PDF points,
top-left origin â€” the same frame `tighten_region` uses.
c                ó\   >• [        TU    R                  S   TU    R                  S   5      $ )Nr)   ri   ©r`   r6   ©Úir¹   s    €r   rÎ   Ú!declash_stacked.<locals>.<lambda>ù  ó.   ø€ ´c¸'À!¹*×:KÑ:KÈAÑ:NÐPWÐXYÑPZ×PaÑPaÐbcÑPdÔ6er   rÐ   r)   NFrä   ri   r   rj   rS   r6   rÁ   )rÝ   r¡   ræ   r6   rç   r9   r  ÚSTACKED_MIN_COL_OVERLAPr`   r_   r8   rì   ÚSEAM_WINDOW_ROWSr÷   rÃ   )r¹   ry   rz   r{   Úmin_gap_ptsrY   Úorderr  ÚregionsÚrow_hr    rê   ÚruÚrlÚcols_uÚcols_lÚ
u_top_normÚu_bottomÚl_topÚl_bottom_normÚboundaryÚwx0Úwx1ré   Ú	seam_normÚ
new_bottoms   `                         r   Údeclash_stackedr  Ú  s¤  ø€ ô> ”5œ˜W›Ó&Ô,eÑf€EÜ-2´3°w³<Ô-@ÓAÒ-@¨�'˜!‘*×#Ñ#Ò#Ñ-@€GÐAØ‰M€EÜ˜E¨¨ 9°UÕ;‰ˆØ‘ ¡ˆBØ  ™×1Ñ1°7¸5±>×3KÑ3K�Ü$ VÓ4Ô7NÓNÙÜ˜˜A™  1¡Ó&ˆ
Ü�r˜!‘u˜b ™eÓ$ vÑ-ˆÜ�B�q‘E˜2˜a™5Ó! FÑ*ˆÜ˜B˜q™E 2 a¡5Ó)ˆØ�u˜{Ñ*Ó*Ùð �U‰^×%Ñ% aÑ(¨4Ñ/°7¸5±>×3KÑ3KÈAÑ3NÐQRÑ3RÐVZÑ2ZÑZÐ^aÑaÐdjÑjð 	ô �"�Q‘%˜˜A™  1¡ r¨!¡uÓ-°Ñ6¸Ñ<ˆÜ�"�Q‘%˜˜A™  1¡ r¨!¡uÓ-°Ñ6¸Ñ<ˆÜØ�3˜˜XÔ(8¸5Ñ(@Ñ@À(ÔM]Ð`eÑMeÑBeó
ˆð Ñ 
¨VÑ 3°dÕ S¸]ÈVÑ=SÖ SØ˜v™ˆIØ  ™e Z°°A±¸	ÐBˆG‰NØ  ™e Y°°1±°}ÐEˆGŒNä& r¨5°%ÀÓHˆJØ  ™e Z°°A±¸
ÀVÑ8KÐLˆGŒNñ5 <ô6 KPÔPSÐT[ÓP\ÔJ]Ó^ÒJ]ÀQˆG�A‰J×!Ñ!¨(°G±JÐ)?Ð!Ó@ÑJ]Ñ^Ð^ùò; Bùò: _s   ¸H-È
 H2c           	     ó”  • [        XX#U5      n[        U5      S:  a  / $ Sn[        X!-
  U-  5      S-   nS/U-  n	U Ho  n
U
S    Hc  n[        S[        US   U-
  U-  5      5      n[	        US-
  [        US   U-
  U-  5      5      n[        XÍS-   5       H  nXž==   S-  ss'   M     Me     Mq     [        [        U5      -  n/ nSnXè:  a~  Xž   U:”  a  US-  nM  UnUS-   U:  a(  U	US-      U::  a  US-  nUS-   U:  a  U	US-      U::  a  M  UU-
  S-   U-  U:¼  a!  UR                  XU-  -   UUS-   U-  -   45        US-   nXè:  a  M~  U$ )a£  Every ink-free vertical band at least `min_width_pts` wide in the window
[x0, x1] x [y_lo, y_hi] (PDF points), as (left edge, right edge) pairs in
left-to-right order; empty when the window holds fewer than two text rows.

A band is measured per printed text row: each row's words are projected
onto x, and a band counts as blank when at most `SEAM_MAX_CROSSING_ROWS` of
the rows have ink in it. An introductory sentence printed across the whole
page crosses the seam between two tables, and every row of both tables
respects it; a gap between two columns inside one table is blank in the
same way, which is why the caller, not this function, decides which band
is the seam.
rS   rî   r)   r   ry   )râ   ræ   r-   r_   r`   r¡   ÚSEAM_MAX_CROSSING_ROWSr·   )ry   re   rf   rÞ   rß   Úmin_width_ptsrY   ÚstepÚnÚcoveragerá   rn   rù   rú   r  ÚallowedÚbandsÚjs                     r   Úblank_column_bandsr&  )  s‡  € ô( ˜U¨°$Ó7€DÜ
ˆ4ƒy�1ƒ}Øˆ	Ø€DÜˆR‰W˜ÑÓ Ñ!€AØˆs�Q‰w€HÛˆØ�W”ˆAÜ�A”s˜A˜a™D 2™I¨Ñ-Ó.Ó/ˆAÜ�A˜‘Eœ3  !¡ r¡	¨TÑ1Ó2Ó3ˆAÜ˜1 !™e–_�Ø“˜qÑ •ó %ó ñ ô %¤s¨4£yÑ0€GØ')€EØ	€AØ
‹%Ø‰;˜Ó Ø�‰FˆAÙØˆØ�!‰e�a‹i˜H Q¨¡U™O¨wÓ6Ø�‰FˆAð �!‰e�a‹i˜H Q¨¡U™O¨wÕ6à�‰E�A‰I˜Ñ Ó.Ø�L‰L˜" 4™x™-¨¨q°1©u¸©nÑ)<Ð=Ô>Ø�‰Eˆð �%ð €Lr   g     €V@g333333ã?c                ó   • [        XU[        -   X#5      nU Vs/ s H"  oUS   (       d  M  [        US   S S9S   S   PM$     nn[        U5      S:  a  g[	        S U 5       5      nU[        U5      -  [
        :¬  $ s  snf )	a£  Whether the column of text just right of `band_right` reads as a
row-label column: on most rows its first word starts with a letter.

A financial table's first column names its rows; its other columns hold
figures. So of the blank bands inside two side-by-side boxes' overlap, the
one followed by a column of words is the seam between the tables, and one
followed by a column of figures is a gap inside the left table.
ry   c                ó   • U S   $ rÚ   r<   rÍ   s    r   rÎ   Ú%stub_column_follows.<locals>.<lambda>l  s   € ¨q°ªtr   rÐ   r   r×   rS   Fc              3  óX   #   • U  H   oS S R                  5       (       d  M  Sv •  M"     g 7f)Nr)   )Úisalpha)rm   rÈ   s     r   ro   Ú&stub_column_follows.<locals>.<genexpr>o  s   é € Ð5š6�a r¨ U§]¡]§_—‘š6ùs   ‚*¡	*)râ   ÚSTUB_LOOKAHEAD_PTSrÝ   ræ   ÚsumÚSTUB_MIN_ALPHA_FRACTION)ry   Ú
band_rightrÞ   rß   rY   r«   ÚfirstsÚalphas           r   Ústub_column_followsr3  `  s‚   € ô ˜U°
Ô=OÑ0OÐQUÓ\€DÙDHÓWÂD¸qÈgÍJÓ:Œf�Q�w‘Z¡^Ñ4°QÑ7¸Ô:ÁD€FÐWÜ
ˆ6ƒ{�QƒØÜÑ5™6Ó5Ó5€EØ”3�v“;ÑÔ"9Ñ9Ð9ùò	 Xs
   ™A;ªA;c           
     óD  ^ ^• [        [        T 5      5       Vs0 s H  oUT U   R                  _M     nn[        [        [        T 5      5      U 4S jS9n[	        XwSS SS9 GHn  u  p‰Xh   Xi   pº[        T U   R                  T U	   R                  5      [        :¼  a  M=  [        U
S   U
S   5      U-  n[        US   US   5      U-  nXÍU-
  ::  a  Mq  [        [        U
S   U
S	   5      [        US   US	   5      5      U-  n[        [        U
S   U
S	   5      [        US   US	   5      5      U-  nXþ::  a  MÖ  [        X[        -
  U[        -   Xï5      n[        U
S   U
S   5      U-  n[        US   US   5      U-  nU VVs/ s H#  u  nnUUU-   S
-  s=:  a  U:  d  M  O  M  UU4PM%     nnnU(       d  [        R                  " SUU5        GMo  T U   R                  S   S-   [        -  T U	   R                  S   [        -  -   S
-  U-  mU Vs/ s H  n[        UUS   Xï5      (       d  M  UPM     nn[        U=(       d    UU4S jS9nUS   US   -   S
-  nUU-  n[        U
S   U
S   5      [        U
S   U
S	   5      U[        U
S   U
S	   5      4Xh'   U[        US   US	   5      [        US   US   5      [        US   US	   5      4Xi'   GMq     [        [        T 5      5       Vs/ s H  nT U   R!                  SXe   0S9PM     sn$ s  snf s  snnf s  snf s  snf )a™  Set the seam between two side-by-side boxes to the blank column between
the two tables, so neither box trades a column with its neighbour.

The counterpart of `declash_stacked` for pairs the locator placed in
different column bands of the same rows. The grid columns are coarse, so a
left table's span and a right table's span usually share an edge column;
the tighten then admits words from the neighbour's stub column into both
boxes, and Camelot reads that column into both tables. Search the two
boxes' overlap for blank columns and snap the left box's right edge and
the right box's left edge to the midpoint of one. A blank column that a
row-label column follows is preferred. When none qualifies, the blank
column nearest the model's coarse column boundary is used, and it can be
a gap inside the left table. When the overlap holds no blank column at
all, the pair passes through untouched: a wrong seam would cut a real
column, and the overlap is at worst a duplicated one.

Only pairs whose regions overlap vertically and whose column spans share
less than `STACKED_MIN_COL_OVERLAP` of the narrower span are considered;
stacked pairs are `declash_stacked`'s business.
c                ó\   >• [        TU    R                  S   TU    R                  S   5      $ )Nr   rS   r  r  s    €r   rÎ   Ú&declash_side_by_side.<locals>.<lambda>�  r  r   rÐ   r)   NFrä   r   rS   ri   rj   zWdeclash_side_by_side: no blank column in the overlap x={:.0f}-{:.0f}pt; pair left as isc                ó8   >• [        U S   U S   -   S-  T-
  5      $ )Nr   r)   rj   )Úabs)Úbdr  s    €r   rÎ   r6  µ  s    ø€ ´°b¸±e¸bÀ¹e±mÀsÑ5JÈXÑ5UÔ1Vr   r6   rÁ   )r¡   ræ   r6   rÝ   rç   r  r9   r	  r_   r`   r&  ÚSEAM_OVERLAP_PAD_PTSr   ÚdebugÚDEFAULT_COLSr3  rÃ   )r¹   ry   rz   r{   r  r  r  r  rù   rú   ÚleftÚrightÚl_x1Úr_x0rÞ   rß   r$  Úl_x0Úr_x1rý   rþ   ÚbandÚstubré   r  r  s   `                        @r   Údeclash_side_by_siderE  s  sd  ù€ ô6 .3´3°w³<Ô-@ÓAÒ-@¨�'˜!‘*×#Ñ#Ò#Ñ-@€GÐAÜ”5œ˜W›Ó&Ô,eÑf€EÜ�E  ˜9¨UÕ3‰ˆØ‘j '¡*ˆeÜ$ W¨Q¡Z×%9Ñ%9¸7À1¹:×;OÑ;OÓPÔTkÓkÙÜ�4˜‘7˜D ™GÓ$ vÑ-ˆÜ�5˜‘8˜U 1™XÓ&¨Ñ/ˆØ˜+Ñ%Ó%ÙÜ”3�t˜A‘w  Q¡Ó(¬#¨e°A©h¸¸a¹Ó*AÓBÀVÑKˆÜ”3�t˜A‘w  Q¡Ó(¬#¨e°A©h¸¸a¹Ó*AÓBÀVÑKˆØ‹<Ùô #ØÔ.Ñ.°Ô7KÑ0KÈTó
ˆô �4˜‘7˜D ™GÓ$ vÑ-ˆÜ�5˜‘8˜U 1™XÓ&¨Ñ/ˆÙ(-ÔOª™f˜b "°¸¸b¹ÀC¹Õ1NÈ$Ñ1N“Ñ1N‘�"�b“©ˆÑOÞÜ�LŠLØiØØôò
 à�a‰j×"Ñ" 1Ñ%¨Ñ)¬\Ñ9¸GÀA¹J×<PÑ<PÐQRÑ<SÔVbÑ<bÑbØñàñð 	ñ
 "'ÓZ¢˜Ô*=¸eÀTÈ!ÁWÈd×*Y—¡ˆÐZÜ�4—=˜5Ô&VÑWˆØ�Q‘˜$˜q™'Ñ! SÑ(ˆØ˜6‘Mˆ	Ü˜$˜q™' 4¨¡7Ó+¬S°°a±¸$¸q¹'Ó-BÀIÌsÐSWÐXYÑSZÐ\`ÐabÑ\cÓOdÐeˆ‰
Ø¤ U¨1¡X¨u°Q©xÓ!8¼#¸eÀA¹hÈÈaÉÓ:QÔSVÐW\Ð]^ÑW_ÐafÐghÑaiÓSjÐkˆŒ
ñS 4ôT KPÔPSÐT[ÓP\ÔJ]Ó^ÒJ]ÀQˆG�A‰J×!Ñ!¨(°G±JÐ)?Ð!Ó@ÑJ]Ñ^Ð^ùòY Bùó2 Pùò [ùò _s)   ™LÆLÆLÆ#LÈLÈ-LË* L>   é  é˜  é™  é­  éô  éö  é÷  éø  r×   r^   c                óÎ   ^• [        U SS5      n[        U[        5      (       a  U[        ;   a  g[	        U 5      R
                  R                  5       m[        U4S jS 5       5      $ )u´   True for failures that a short backoff is likely to clear (rate limit,
overload, timeout) â€” as opposed to a malformed request or auth error, which
will fail identically on retry.Ústatus_codeNTc              3  ó.   >#   • U  H
  nUT;   v •  M     g 7fr²   r<   )rm   ÚtokenÚnames     €r   ro   Ú)is_transient_llm_error.<locals>.<genexpr>Î  s    øé € ð 
ò
ˆEð 	�Žò
ùs   ƒ)Ú	ratelimitÚoverloadÚtimeoutÚapiconnectionÚinternalserverÚserviceunavailable)ÚgetattrÚ
isinstancer-   ÚTRANSIENT_LLM_STATUSÚtyper=   rê   r¶   )ÚexcÚstatusrR  s     @r   Úis_transient_llm_errorr`  Æ  s_   ø€ ô �S˜-¨Ó.€FÜ�&œ#×Ñ 6Ô-AÓ#AØÜ�‹9×Ñ×#Ñ#Ó%€DÜô 
ñ
ó
ó 
ð 
r   c                  ó   • \ rS rSrSS jrSrg)ÚGridLocatoriÛ  c              ƒ  ó   #   • g 7fr²   r<   )ÚselfÚ
page_imageÚsourceÚpages       r   ÚlocateÚGridLocator.locateÝ  s   é € Ð]`ùs   ‚r<   N©re  r   rf  r   rg  r-   ÚreturnúList[LocatedTable])r=   r>   r?   r@   rh  rC   r<   r   r   rb  rb  Û  s   † ç`r   rb  c                  ót   • \ rS rSrSrSrSSS\\\S4               SS jjrSS jr	SS jr
SS	 jrS
rg)ÚPydanticAIGridLocatorià  zûGridLocator backed by pydantic-ai with an Anthropic vision model.

Mirrors `PydanticAITableDetector`: same auth resolution
(`ANTHROPIC_AUTH_TOKEN` preferred over `ANTHROPIC_API_KEY`), greedy
decoding for run-to-run stability of the discrete grid IDs.
r]   NéÈ   c                ój  • SSK Jn  SSKJn	  SSKJn
  SSKJn  [        5       R                  nU=(       d,    UR                  =(       d    UR                  =(       d    [        nUb  [        U5      (       d  S nU=(       d    UR                  nU=(       d    UR                  nU(       a  SSKJn  U" X5      nO U(       a  U
" US9nU	" UUS9nO[%        S	5      eXl        XPl        X`l        Xpl        Uc  S OU" US
9n[-        XV5      U l        U" U[0        U R.                  US9U l        [5        SS9U l        g )Nr   )ÚAgent)ÚAnthropicModel)ÚAnthropicProvider)ÚModelSettings)Úmake_oauth_anthropic_model)Úapi_key)ÚproviderzuPydanticAIGridLocator: neither ANTHROPIC_AUTH_TOKEN nor ANTHROPIC_API_KEY is set. Provide one via env or constructor.)Útemperature)Úoutput_typeÚsystem_promptÚmodel_settingszquber-grid-locator)Úrun_name)Úpydantic_airq  Úpydantic_ai.models.anthropicrr  Úpydantic_ai.providers.anthropicrs  Úpydantic_ai.settingsrt  r   ÚllmÚgrid_locator_modelÚmodelr   r   Úanthropic_auth_tokenÚanthropic_api_keyÚquber.agents._oauth_gateru  ÚRuntimeErrorrY   r   Údpir[   rz  rL   Úagentr   Útracer)rd  rƒ  Ú
auth_tokenrv  rx  rY   r   rˆ  rq  rr  rs  rt  Úllm_settingsÚresolved_authÚresolved_keyru  Ú
anth_modelrw  r{  s                      r   Ú__init__ÚPydanticAIGridLocator.__init__ê  s  € õ 	&Ý?ÝEÝ6ä#“~×)Ñ)ˆØ×c˜×8Ñ8×c¸L×<NÑ<N×cÔRcˆØÑ"Ô+HÈ×+OÑ+OØˆKØ"×G l×&GÑ&GˆØ×@ ,×"@Ñ"@ˆæÝKá3°EÓI‰JÞÙ(°Ñ>ˆHÙ'¨¸ÑA‰JäðPóð ð
 Œ
ØŒ	ØŒ	ØŒØ!,Ñ!4™¹-ÐT_Ñ:`ˆÜ)¨$Ó5ˆÔÙØÜ&Ø×,Ñ,Ø)ñ	
ˆŒ
ô &Ð/CÑDˆ�r   c                ó>   • SSU R                   S.SSSS.SS	S./S./0$ )
NÚmessagesÚsystem©ÚroleÚcontentÚuserr£   úFlag the tables using the grid.)r]  r£   Úimagez[gridded page image attached])rz  )rd  s    r   Útrace_inputsÚ"PydanticAIGridLocator.trace_inputs  sA   € àØ!¨d×.@Ñ.@ÑAà"à!'Ð1RÑSØ!(Ð2QÑRð ñð	ð
ð 	
r   c              ƒ  ó6  #   • SS K nSSKJn  SSKJn  [
        R                  " SS9 n[        U5      S-  nUR                  [        [        U5      X€R                  U R                  5      I S h  v•N   U" UR                  5       SS9n	U R                  5       n
U R                  R                  S	X R                   S
9 IS h  v•N nU R#                  X“5      I S h  v•N nSUR$                  R'                  5       S./[)        UR$                  R*                  5      [-        UR.                  5      S.Ul        S S S 5      IS h  v•N   UR                  U[        U5      U5      I S h  v•N u  pÞnS S S 5        [3        WR$                  R*                  WWWU R                  U R                  5      n[5        UXÞ[6        5      n[9        UXýXàR                  S9n[;        UXýU5      nU$  GNs GN$ GN N©! , IS h  v•N  (       d  f       N¾= f N¡! , (       d  f       N¢= f7f)Nr   )ÚBinaryContent)Ú
page_wordszquber-grid-)Úprefixzgridded.pngz	image/png)ÚdataÚ
media_typeÚgrid_locate)rƒ  Ú	assistantr•  )r“  Útable_countÚusage_metadata)rY   )Úasyncior}  rž  Úquber.agents.completenessrŸ  ÚtempfileÚTemporaryDirectoryr   Ú	to_threadr¯   rY   r   Ú
read_bytesr›  rŠ  Úllm_runrƒ  Úrun_locator_with_retryÚoutputÚmodel_dump_jsonræ   rO   r   ÚusageÚoutputsr¿   rÉ   ÚBOX_PAD_PTSr  rE  )rd  re  rf  rg  r§  rž  rŸ  ÚtmpÚgriddedrš  ÚinputsÚrunÚresultrz   r{   ry   r¹   s                    r   rh  ÚPydanticAIGridLocator.locate+  s­  é € Ûå-å8ä×(Ò(°Ò>À#Ü˜3“i -Ñ/ˆGð ×#Ñ#¤L´$°zÓ2BÀGÏYÉYÐX\×XaÑXaÓb×bÐbÙ! w×'9Ñ'9Ó';ÈÑTˆEØ×&Ñ&Ó(ˆFØ—{‘{×*Ñ*¨=¸&Ï
É
Ð*×SÑSÐWZØ#×:Ñ:¸5ÓG×G�à*5À&Ç-Á-×B_ÑB_ÓBaÑ!bÐ cÜ#& v§}¡}×';Ñ';Ó#<Ü&9¸&¿,¹,Ó&Gñ�”÷ T×Sð +2×*;Ñ*;¸JÌÈVËÐVZÓ*[×$[Ñ!ˆF˜E÷ ?ô # 6§=¡=×#7Ñ#7¸ÀÈÐPT×PYÑPYÐ[_×[dÑ[dÓeˆÜ˜G V´[ÓAˆÜ! '¨5¸&ÇyÁyÑQˆÜ& w°¸vÓFˆØˆò cò TÚG÷ T×S×SÐSúñ %\÷ ?Õ>üs˜   ‚%H§AHÁ.G!Á/AHÃ G$ÃHÃG,ÃG'ÃAG,Ä5HÅ G*Å#HÅ$HÅ%HÅ,A5HÇ!HÇ$HÇ'G,Ç*HÇ,H	Ç2G5Ç3H	Ç?HÈ
HÈHc           
   ƒ  óÊ  #   • SSK n[        S[        S-   5       H)  n U R                  R	                  SU/5      I Sh  v•N s  $    g N	! [
         aŒ  nU[        :X  d  [        U5      (       d  [        R                  " SUU[        U5        e [        SUS-
  -  -  n[        R                  " SUU[        UU5        UR                  U5      I Sh  v•N     SnAMÁ  SnAff = f7f)aý  Run the grid-locate call, retrying transient LLM failures with
exponential backoff. A page that still fails after the retry budget is
raised, never swallowed: returning no flags would silently drop every
table on the page with no record in the output -- on a financial
document an undetected page is a liability, not an acceptable omission.

TODO: offer a graceful-degradation mode that records a failed-page
marker and continues with a non-zero exit, instead of aborting the
whole run. Failing hard for now.
r   Nr)   r™  zegrid_locate: LLM call failed page={} attempt={}/{}; aborting (refusing to silently drop the page): {}rS   zKgrid_locate: transient LLM error page={} attempt={}/{}; retrying in {}s: {})r§  r¡   ÚGRID_LOCATE_MAX_ATTEMPTSr‰  r·  rž   r`  r   ÚerrorÚGRID_LOCATE_BACKOFF_BASE_SÚwarningÚsleep)rd  rš  rg  r§  Úattemptr^  Údelays          r   r®  Ú,PydanticAIGridLocator.run_locator_with_retryG  sÒ   é € ó 	ä˜QÔ 8¸1Ñ <Ö=ˆGð+Ø!ŸZ™ZŸ^™^Ð-NÐPUÐ,VÓW×WÒWò >áWøÜó +ØÔ6Ó6Ô>TÐUX×>YÑ>YÜ—L’LðCàØÜ0Øôð Ü2°Q¸7ÀQ¹;Ñ5GÑG�Ü—’ØaØØÜ,ØØôð —m‘m EÓ*×*×*ûð)+üsK   ‚C#ž A
¾A¿A
ÁC#ÁA
Á

C ÁA;CÃCÃCÃC#ÃC Ã C#)r‰  r   rˆ  rƒ  rY   rz  rŠ  )rƒ  úOptional[str]r‹  rÃ  rv  rÃ  rx  úOptional[float]rY   r-   r   r-   rˆ  r-   rk  ÚNone)rk  zDict[str, Any]rj  )rš  r   rg  r-   rk  r   )r=   r>   r?   r@   rA   ÚDEFAULT_TEMPERATUREÚDEFAULT_ROWSr<  r�  r›  rh  r®  rC   r<   r   r   rn  rn  à  s’   † ñð Ðð  $Ø$(Ø!%Ø':Ø Ø Øð1Eàð1Eð "ð1Eð ð	1Eð
 %ð1Eð ð1Eð ð1Eð ð1Eð 
õ1Eôf
ô÷8$+r   rn  c                  ó0   • \ rS rSrSrSSS jjrS	S jrSrg)
ÚMockGridLocatorin  z(Returns canned LocatedTables. For tests.Nc                ó$   • U=(       d    / U l         g r²   ©r¹   )rd  r¹   s     r   r�  ÚMockGridLocator.__init__q  s   € Ø—} "ˆ�r   c              ƒ  ó<   #   • XU4n[        U R                  5      $ 7fr²   )rP   r¹   )rd  re  rf  rg  Ú_s        r   rh  ÚMockGridLocator.locatet  s   é € Ø Ð&ˆÜ�D—L‘LÓ!Ð!ùs   ‚rË  r²   )r¹   zOptional[List[LocatedTable]]rk  rÅ  rj  )r=   r>   r?   r@   rA   r�  rh  rC   r<   r   r   rÉ  rÉ  n  s   † Ù2ö%÷"r   rÉ  c                ó°   • U =(       d    [        5       R                  R                  nUS:X  a
  [        5       $ US:X  a
  [	        5       $ [        SU< S35      e)NÚapiÚmockz$Unknown QUBER_GRID_LOCATOR_BACKEND: z. Expected api|mock.)r   r�  Úgrid_locator_backendrn  rÉ  Ú
ValueError)ÚbackendÚselecteds     r   Úget_grid_locatorr×  y  sR   € Ø×Aœ,›.×,Ñ,×AÑA€HØ�5ÓÜ$Ó&Ð&Ø�6ÓÜÓ Ð Ü
Ð;¸H¹<ÐG[Ð\Ó
]Ð]r   )r   r-   rk  r2   )r"   r2   r   r-   rk  r-   )rY   r-   r   r-   rk  r2   )rG   r-   rH   r-   ra   r-   rb   r-   rY   r-   r   r-   rk  r5   )rx   r5   ry   úSequence[Tuple[Any, ...]]rz   Úfloatr{   rÙ  r|   r:   r}   rÙ  r~   rÙ  rk  z+Optional[Tuple[float, float, float, float]])
r¥   r   r¦   r   rY   r-   r   r-   rk  rÅ  )r¸   zSequence[GridFlag]ry   rØ  rz   rÙ  r{   rÙ  rY   r-   r   r-   rk  rl  )
r¹   úSequence[LocatedTable]rz   rÙ  r{   rÙ  rÄ   rÙ  rk  rl  )rÊ   )ry   rØ  re   rÙ  rf   rÙ  rÞ   rÙ  rß   rÙ  rà   rÙ  rk  zList[Dict[str, Any]])ry   rØ  re   rÙ  rf   rÙ  rÞ   rÙ  rß   rÙ  rk  rÄ  )rñ   r5   rò   rÙ  ry   rØ  rz   rÙ  r{   rÙ  rk  rÙ  )rù   r7   rú   r7   rk  rÙ  )r¹   rÚ  ry   rØ  rz   rÙ  r{   rÙ  r  rÙ  rY   r-   rk  rl  )g      @)ry   rØ  re   rÙ  rf   rÙ  rÞ   rÙ  rß   rÙ  r  rÙ  rk  zList[Tuple[float, float]])
ry   rØ  r0  rÙ  rÞ   rÙ  rß   rÙ  rk  r:   )rj   )r¹   rÚ  ry   rØ  rz   rÙ  r{   rÙ  r  rÙ  rk  rl  )r^  ÚBaseExceptionrk  r:   r²   )rÕ  rÃ  rk  rb  )ErA   Ú
__future__r   rV   r   r©  Úpathlibr   Útypingr   r   r   r   r	   r
   r   r   Úlogurur   Úpydanticr   r   Úquber.agents.factoryr   Úquber.agents.langsmith_tracerr   r   Úquber.settingsr   r   rÇ  r<  r³  ÚTIGHTEN_Y_PAD_PTSÚTIGHTEN_X_PAD_PTSr   r%   r'   rE   rL   r[   rg   r…   r¯   r¿   rÉ   r
  râ   rì   r÷   r	  r  r  r  r:  r&  r-  r/  r3  rE  Ú	frozensetr\  r»  r½  r`  rb  rn  rÉ  r×  r<   r   r   Ú<module>rç     sX  ðñ,õ\ #ã Û Û Ý ß Z× ZÓ Zå ß %å >ß Nß :ð €Ø€ð €ð Ð ØÐ ô)ð
 +7ö 
3ô�9ô ô2]ˆyô ]ô9�Yô 9ô=ðFØðØ ðØ14ðØCFðØNQðØY\ðà&ôð* %Ø$ð'DØ2ð'Dà$ð'Dð ð'Dð ð	'Dð
 ð'Dð ð'Dð ð'Dð 1õ'DðV 3?ÈLðØðØ $ðØ,/ðØFIðà	õð<"Øð"à$ð"ð ð"ð ð	"ð
 ð"ð ð"ð ô"ðJØ#ðØ-2ðØ<AðØLQðàôð8 Ð ð ð!Ø$ð!àð!ð 	ð!ð ð	!ð
 ð!ð 
ð!ð õ!ðHØ$ðàðð 	ðð ð	ð
 ðð ôð4JØ3ðJàðJð %ðJð ð	Jð
 ðJð ôJðD Ð ô	4ð" Øð=_Ø#ð=_à$ð=_ð ð=_ð ð	=_ð
 ð=_ð ð=_ð õ=_ðN Ð ð
 Ð ð ð-Ø$ð-àð-ð 	ð-ð ð	-ð
 ð-ð ð-ð õ-ðf Ð ØÐ ð:Ø$ð:Ø27ð:Ø?Dð:ØLQð:à	ô:ð0 ðG_Ø#ðG_à$ðG_ð ðG_ð ð	G_ð
 ðG_ð õG_ñ\ !Ò!IÓJÐ ØÐ Ø Ð ôð* ôa�(ó aó ða÷K+ñ K+÷\"ñ "÷^r   