Skip to content

refactor(relational): labels and the result are modules, not regions of the executor - #368

Merged
FBumann merged 1 commit into
mainfrom
refactor/executor-structure
Jul 31, 2026
Merged

refactor(relational): labels and the result are modules, not regions of the executor#368
FBumann merged 1 commit into
mainfrom
refactor/executor-structure

Conversation

@FBumann

@FBumann FBumann commented Jul 31, 2026

Copy link
Copy Markdown
Owner

executor.py was 1070 lines holding four separable jobs plus one piece of
public API. Two come out. The file is now 709 lines, and both splits fall
along seams docs/ARCHITECTURE.md already described in prose but the code had
no unit for.

Pure code motion — no logic changed. 700 passing, ruff clean, pyrefly 0 errors.

before after
executor.py 1070 709
labels.py 242
result.py 190

relational/labels.py

ARCHITECTURE gives labelling its own paragraph — "labels are the one place
order is load-bearing"
, three routes to one number that "must agree integer
for integer, because a label is a solver index"
. But the code was
_label_frame / _factored / _row_major sitting among twenty other methods,
reaching through self for the three things they needed alongside the ten they
didn't.

A Labeller states those inputs: the query, the dimension cardinalities, the
program. Nothing else about a build can now move a solver index — which is the
claim that paragraph was already making, now checkable by reading a
constructor.

relational/result.py

Result is what lps.solve hands back: the one object in this subpackage a
reader meets without going looking for it. It lived above PolarsExecutor in a
module named for an internal build phase. It also does the opposite job — the
executor builds a model, Result reads one back through label joins — so the
two being one file was working against both names. api.py and
relational/__init__.py now import it from where it is.

Considered and not done

Extracting source binding (_create_param_frame, _explicit_dim_frame,
the two dim-frame passes — about 180 lines). It is a coherent unit and it is
the largest remaining region, but the three passes are ordered and
interdependent: sourced dims first, so a parameter's labels can be checked in
the pass that binds it rather than a second one over the same rows; then the
parameters; then the dims derived from those. That argument lives in
build(), next to the sequence it explains. Splitting it would either strand
the argument away from the code it justifies or hand four pieces of state back
through a carrier object built only to be unpacked immediately.

Note

tests/test_architecture.py::test_every_module_is_documented_somewhere failed
on the first run — a new module with no row in the module map. That is the
check doing its job; docs/ARCHITECTURE.md is updated in this PR, including
the four prose references to _label_frame that would otherwise have gone
stale.

🤖 Generated with Claude Code

…of the executor

`executor.py` was 1070 lines holding four separable jobs and one piece of
public API. Two of them come out; the file is 709 lines and the split is
along seams the architecture doc already described in prose but the code did
not have a unit for.

**`relational/labels.py`.** ARCHITECTURE gives labelling its own paragraph —
"labels are the one place order is load-bearing", three routes to one number
that "must agree integer for integer, because a label *is* a solver index" —
but the code was `_label_frame` / `_factored` / `_row_major` sitting among
twenty other methods and reaching through `self` for what they needed. A
`Labeller` states its inputs instead: the query, the dimension cardinalities,
and the program. Nothing else about a build can now move a solver index,
which is the claim the paragraph was already making.

**`relational/result.py`.** `Result` is what `lps.solve` returns — the one
object in this subpackage a reader meets without going looking — and it lived
above `PolarsExecutor` in a module named for an internal build phase. It also
does the opposite job: the executor *builds* a model, `Result` *reads* one
back through label joins. `api.py` and `relational/__init__.py` now import it
from where it is.

Pure code motion: no logic changed, and `tests/test_architecture.py` caught
the one thing that did need writing — a new module with no home in the
module map — which is the check working as intended.

Considered and not done: extracting source binding (`_create_param_frame`,
`_explicit_dim_frame`, the two dim-frame passes, ~180 lines). It is a
coherent unit, but the three passes are ordered and interdependent — sourced
dims first so a parameter's labels can be checked as it binds, then
parameters, then the dims derived from them — and the ordering argument lives
in `build()`. Splitting it would either move that argument away from the
sequence it explains or hand four pieces of state back through a carrier
built only to be unpacked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@FBumann, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 14ff2d12-84e7-4811-9798-dd740268f463

📥 Commits

Reviewing files that changed from the base of the PR and between 04dd46c and 420d761.

📒 Files selected for processing (6)
  • docs/ARCHITECTURE.md
  • src/lpspec/api.py
  • src/lpspec/relational/__init__.py
  • src/lpspec/relational/executor.py
  • src/lpspec/relational/labels.py
  • src/lpspec/relational/result.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 lpspec | 🛠️ Build #33847062 | 📁 Comparing 420d761 against latest (04dd46c)

  🔍 Preview build  

1 file changed
± ARCHITECTURE/index.html

@FBumann
FBumann merged commit 7c8604a into main Jul 31, 2026
4 checks passed
@FBumann
FBumann deleted the refactor/executor-structure branch July 31, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant