renderer(tier-1): row anchors + number-format expansion + merged-cell propagation#12
Open
arnav2 wants to merge 1 commit into
Open
Conversation
… propagation
Three changes to text_renderer.render_block(), each addressing a
distinct way the parser/chunker was losing signal between the workbook
and the chunk's render_text. Together they move the parser-quality
metric (rank IS NOT None — answer surfaced in some chunk's text) by
+0.6 pp on the full 912 SpreadsheetBench v0.1, and lift text@5 by
+1.0 pp / text@5_in_scope by +0.9 pp on the same run.
1. Row-number anchors
Every data row of the markdown grid now carries an `r<N>` prefix
where N is the sheet row (1-indexed):
[Sheet1!A1:D10] (table)
| A | B | C | D |
|------|-----|-----|------|
r1 | Name | Q1 | Q2 | Q3 |
r2 | Wgt | 100 | 150 | 200 |
A downstream LLM consuming the chunk can now compute cell
coordinates deterministically: the block header gives the A1
range; per-row anchors close the gap to (row, col). Citation-
grade output for the agent-side use cases on ks-backend.
2. Number-format-aware rendering
When a cell's number_format produces a meaningfully-different
displayed string (0.06 → "6%", 1272 → "1,272.00", 46022 → date),
we now emit both:
r2 | 0.06 [6%] | 1272 [1,272.00] |
Substring-search retrieval hits either form — the question may
quote the raw or the displayed, and answer.xlsx may use the
display form even though input.xlsx keeps the raw.
Trivial diffs (1272 → "1272.00", "1272.0") are NOT expanded — no
information added, only noise.
3. Merged-cell value propagation
Slave cells in a merged region currently render blank because
openpyxl returns None for them. That kills text-match retrieval
whenever a question references the cell by a slave coordinate.
Renderer now looks up the master and emits the master's value
at each slave with a `← ` propagation marker:
r1 | Total | ← Total | ← Total |
The merged region's visible value now appears at every position
it appears in Excel, not just the top-left.
Bench (full 912 / text-embedding-3-large):
parser-quality (rank IS NOT None): 0.843 → 0.849 (+4 instances)
recall_text@5: 0.750 → 0.760 (+0.010)
recall_text@5_in_scope: 0.838 → 0.847 (+0.009)
recall_geometric@5: 0.482 → 0.484 (no real change)
mean parse_ms: 156 → 184 (+27 ms)
per-instance: 6 miss→hit, 0 hit→miss
Tests: tests/test_renderer_tier1.py — 7 cases (row anchor presence
+ correct sheet-row indexing, percent/decimal format expansion,
trivial-diff suppression, merged-cell propagation + sanity).
1079 → 1086 total passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three surgical changes to
text_renderer.render_block()— each addresses a distinct way the parser/chunker was losing signal between the workbook and the chunk's render_text. Stacked on #11. Together they move the parser-quality metric by +0.6 pp on the full 912 and lift text@5 by +1.0 pp / text@5 in-scope by +0.9 pp — the first measurable parser+chunker improvement of this session.What changed
1. Row-number anchors (
r<N>prefix per data row)The renderer already emitted a block header with the A1 range, but data rows had no row number. Downstream consumers — especially the agent on ks-backend — couldn't compute cell coordinates from chunk text. Now:
Row prefix width is sized to the largest row number in the block so the grid stays aligned regardless of block depth.
2. Number-format-aware rendering
When a cell's
number_formatproduces a meaningfully different displayed string from the raw value, we now emit BOTH:0.060%0.06 [6%]1272#,##0.001272 [1,272.00]46022yyyy-mm-ddSubstring-match retrieval can hit either form — the question may quote either, and
answer.xlsxoften uses the display form even thoughinput.xlsxkeeps the raw.Trivial diffs (
1272→"1272.00","1272.0") are NOT expanded — they add no retrieval-relevant tokens, only noise.3. Merged-cell value propagation
Slave cells in a merged region used to render blank (openpyxl returns
Nonefor them). Questions that referenced the cell by a slave coordinate could never match. Now:The merged region's visible value appears at every position it appears in Excel.
Bench on full 912 with
text-embedding-3-largeThe +0.9 pp text@5_in_scope move is modest but clean — zero regressions, no parser internals reshaped, all gains come from making the chunk text more faithful to what's visually in the workbook.
Type of change
Test plan
make testpasses — 1079 → 1086 (+7 new)tests/test_renderer_tier1.pycovers all 3 changes + boundary cases (trivial diff suppression, no spurious markers on unmerged sheets)Notes for reviewers
←markers. Within the embedder's context window for any reasonable block; doesn't trigger any chunk-size cap.🤖 Generated with Claude Code