Skip to content

refactor(relational): one data-validation module for this lane - #355

Merged
FBumann merged 1 commit into
mainfrom
refactor/data-validation-module
Jul 30, 2026
Merged

refactor(relational): one data-validation module for this lane#355
FBumann merged 1 commit into
mainfrom
refactor/data-validation-module

Conversation

@FBumann

@FBumann FBumann commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Answers #351 for the relational lane, at the scope you asked for: one data-validation module here, some duplication for the eager lane accepted.

Spec validation was already separate

validation.py is 298 lines with zero DataError, and it collects into an error list rather than raising — so a model with several problems reports them together. Law 2, §9 and the LanguageError/DataError exception tree already name the boundary. Nothing needed moving on that side.

The data half moves out of the executor

Five checks become functions over frames and declarations, holding no executor state (grep -c 'self\.' → 0):

check_one_row_per_coordinate a parameter is a function of its dims
_unknown_label_probes / _raise_unknown_label labels the dimension actually has (#350)
check_coordinates_single_valued one label, one coordinate value
check_coordinate_containment every coordinate value is a label of its target

The executor keeps orchestration and the model frames, and drops 123 lines (1192 → 1069).

_check_no_undefined_divisor stays. It reads the assembled matrix, so it is coupled to the assembly rather than to the binding — it is one of the two positions where law 8 grants no default, and the module docstring names that rather than pretending the split is total.

Why this lane only

These take tidy polars frames. The eager lane reads pandas/xarray natively because that is what linopy wants, and adapting it to tidy frames first would cost a copy of every parameter on the lane whose whole point is that the arrays are already in memory.

What the lanes share instead:

That table is what keeps the duplication honest, and it is why this is a move rather than a rewrite: the behaviour is pinned from outside, so the refactor cannot quietly change a verdict.

Measured, not assumed

This is the build hot path. dispatch at size l, alternating runs, minimum of three:

build
extracted 0.613 s
origin/main 0.603 s

1.7%, inside the noise a single run showed at 0.95 s. Function-call indirection at per-parameter granularity costs nothing measurable.

Verified

699 passed, 4 skipped, 1 xfailed. ruff check, ruff format --check, pyrefly check (0 errors), mkdocs build --strict. docs/ARCHITECTURE.md gains the module-map row — tests/test_architecture.py fails on a module that neither the map nor a directory README documents, which is how I found it wanted one.

🤖 Generated with Claude Code

Answers #351 for the relational lane. Spec validation was already separate —
`validation.py` is 298 lines with zero `DataError`, collecting into an error
list rather than raising. The data half was mixed into the executor, which owns
the model frames and the label assignment as well.

Five checks move to `relational/data_validation.py`, as functions over frames
and declarations holding no executor state: one row per coordinate, labels the
dimension actually has, coordinates single-valued per label, coordinate
containment. The executor keeps orchestration and drops 123 lines.

`_check_no_undefined_divisor` stays where it is. It reads the assembled matrix,
so it is genuinely coupled to the assembly rather than to the binding — one of
the two positions where law 8 grants no default, and the reason the module
docstring names them rather than pretending the split is total.

**Scoped to this lane deliberately**, per @FBumann: some duplication for the
eager lane is fine. These take tidy polars frames; the eager lane reads
pandas/xarray natively because that is what linopy wants, and adapting it first
would cost a copy of every parameter on the lane whose whole point is that the
arrays are already in memory. What the lanes share instead is the wording
(`errors.py`, three helpers now) and the contract —
`tests/test_data_parity.py` asserts they reach the same verdict on the same bad
data. That table is what keeps the duplication honest, and it is why this is a
move rather than a rewrite.

Hot path, so measured rather than assumed: `dispatch` at size l, alternating
runs, minimum of three — 0.613 s against main's 0.603 s, inside the noise a
single run showed at 0.95 s.
@coderabbitai

coderabbitai Bot commented Jul 30, 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: 7 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: fc94f4cc-d09e-4e39-b934-7b7c2a7442c0

📥 Commits

Reviewing files that changed from the base of the PR and between 36f6d69 and 23416c9.

📒 Files selected for processing (3)
  • docs/ARCHITECTURE.md
  • src/lpspec/relational/data_validation.py
  • src/lpspec/relational/executor.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 #33835810 | 📁 Comparing 23416c9 against latest (36f6d69)

  🔍 Preview build  

1 file changed
± ARCHITECTURE/index.html

@FBumann
FBumann merged commit 4b19fe4 into main Jul 30, 2026
4 checks passed
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