{"author":{"id":"MDQ6VXNlcjQ0MjU2NDA1","is_bot":false,"login":"xmandeng","name":"Xavier Mandeng"},"baseRefName":"main","body":"## Summary\n\nA page that prints two tables side by side came out of the grid locator as one region on most runs, and when the locator did report two, their tightened boxes overlapped by a stub column that Camelot then read into both tables (the column bleed QUE-370 was filed for). Two changes in `src/quber/agents/grid_locator.py`: one rule in the locator prompt naming the side-by-side case, and a vertical seam pass (`declash_side_by_side`) that mirrors the existing stacked-pair seam, searching the two boxes' overlap for the blank column between the tables and tiling both boxes at it. QUE-371 (the locator merge) is folded into QUE-370.\n\n## Related Jira Issue\n\nhttps://mandeng.atlassian.net/browse/QUE-370\n\n## Acceptance Criteria\n\n### AC1: Side-by-side pages come out as two tables with disjoint columns\n\n`uv run quber extract data/playground/998c8ed06c0be22d.pdf --review` on the branch (KREF Q1 2026 10-Q, 80 pages):\n\n| Page | Before (main) | After (branch) |\n|---|---|---|\n| 23 | one region x 0.071-0.929, 23x6, titled \"KKR Real Estate Finance Trust Inc.\" | Geography 23x3 at x 0.071-0.491, \"Concentration of Credit Risk\"; Collateral Property Type 9x3 at x 0.503-0.928, \"Collateral Property Type\" |\n| 33 | one region x 0.071-0.929, 18x6, titled \"KKR Real Estate Finance Trust Inc.\" | Geography 18x3 at x 0.071-0.495; Collateral Property Type 10x3 at x 0.495-0.928; both \"Concentration of Credit Risk\" |\n\nFirst rows after: page 23 `['Geography(A)', 'March 31, 2026', 'December 31, 2025']` / `['California', '17.6 %', '16.6 %']` and `['Collateral Property Type(A)', 'March 31, 2026', 'December 31, 2025']` / `['Multifamily', '40.6 %', '40.3 %']`; page 33 `['New York', '39.8 %', '46.2 %']` and `['Retail', '27.7 %', '24.1 %']`. No grid carries a neighbour's column. Every box starts below the running header.\n\n### AC2: The store no longer carries the stray column\n\nNot run: re-ingest of the document is a playground operation outside this PR. The grids above are what the ingest chunks; the page 23 property-type grid is 9x3 with the label in column 0, so no `t15-line-*` chunk can lead with a geography figure.\n\n### AC3: No other page changes\n\nFull-document comparison against a baseline extract of the same file on main, same day:\n\n| | Baseline | Branch |\n|---|---|---|\n| Tables | 67 | 69 |\n| Pages whose table count changed | | 23 and 33 only (1 to 2 each) |\n| Grid shape changes on any other page | | page 76: 6x3 to 6x5, same region |\n\nPage 76's 6x5 also appeared in a control run that changed only an unrelated prompt sentence and no geometry, so it is the structure-correction agent's run-to-run variance, not this change.\n\nLocator variance before the fix, measured in a scratch harness on pages 23 and 33 (4 runs each): current prompt merged page 23 in 4 of 4 and page 33 in 2 of 4; with the prompt rule, both pages split 4 of 4.\n\n### AC4: Unit tests pin the seam\n\nFour new tests in `tests/test_grid_locator.py`: the prompt rule sits between the stacked rule and the \"prefer ONE region\" line; the seam search finds the blank column despite a prose row crossing it; it finds none inside one table; the seam pass snaps an overlapping side-by-side pair to that column and leaves stacked and already-separated pairs untouched.\n\n## Test Evidence\n\nCommand: `uv run pytest tests/test_grid_locator.py -q`\nResult: PASS\nOutput: `24 passed in 3.44s`\n\nCommand: `uv run pytest -q`\nResult: PASS\nOutput: `712 passed, 13 warnings in 19.46s`\n\nCommand: `uv run pre-commit run --files src/quber/agents/grid_locator.py tests/test_grid_locator.py`\nResult: PASS\nOutput: trim trailing whitespace, fix end of files, check for merge conflicts, check for added large files, debug statements, check docstring is first, ruff, ruff-format, pyright type checking: all Passed\n\nCommand: `uv run quber extract data/playground/998c8ed06c0be22d.pdf --review -o \u003cscratch\u003e`\nResult: PASS\nOutput: `69 tables`; pages 23 and 33 as in AC1; comparison as in AC3\n\n## Changes Made\n\n- `src/quber/agents/grid_locator.py`: side-by-side bullet under \"Counting tables\" in `build_prompt`; `SEAM_MAX_CROSSING_ROWS`, `SEAM_OVERLAP_PAD_PTS`, `widest_blank_column_seam`, `declash_side_by_side`; the pass runs after `declash_stacked` in `locate`; module docstring describes the two seam passes.\n- `tests/test_grid_locator.py`: four tests covering the prompt rule, the seam search, and the seam pass.\n\nKnown limit: measured on one document. The stacked-pair and section-subheading cases the existing prompt wording protects have not been re-run on the review corpus.\n\nhttps://claude.ai/code/session_012MrxUTZDhTxkbPXnUu5ZXN\n","createdAt":"2026-09-12T04:01:03Z","headRefName":"bugfix/QUE-370-side-by-side-tables","mergeable":"MERGEABLE","number":211,"reviews":[],"state":"OPEN","statusCheckRollup":[{"__typename":"CheckRun","completedAt":"0001-01-01T00:00:00Z","conclusion":"","detailsUrl":"https://github.com/xmandeng/quber/actions/runs/34671887799/job/103494665346","name":"update-jira","startedAt":"2026-09-12T04:01:09Z","status":"IN_PROGRESS","workflowName":"Update Jira on PR and Branch Events"}],"title":"QUE-370: Side-by-side tables are located as two and tile at the blank column between them","updatedAt":"2026-09-12T04:01:03Z"}
