Skip to content

Introduce explicit ranked method base - #60

Merged
fsargent merged 2 commits into
mainfrom
refactor/ranked-method-base
Jul 17, 2026
Merged

Introduce explicit ranked method base#60
fsargent merged 2 commits into
mainfrom
refactor/ranked-method-base

Conversation

@fsargent

@fsargent fsargent commented Jul 17, 2026

Copy link
Copy Markdown
Member

Summary

  • replace the RankedMethod = Borda alias with an explicit ranked-ballot base class
  • move shared honest and strategic ballot construction into the base
  • keep Borda and plurality aggregation explicit
  • preserve RankedMethod and RatedMethod package imports
  • add regression coverage for inheritance, ballots, strategy, and plurality results

Stack

Depends on #59, which depends on #58. Retarget this PR to main after its parent merges.

Validation

  • uv run python -m pytest (44 passed)
  • trunk check

Summary by Sourcery

Introduce a dedicated ranked-ballot base class and update ranked methods to use it while preserving public imports and behaviour.

New Features:

  • Add a RankedMethod base class providing shared ranked-ballot construction and default strategic ballot behavior for ranked methods.

Enhancements:

  • Refactor Borda, Plurality, IRNR, and Schulze to inherit from the new RankedMethod base class instead of using Borda as the implicit ranked-method implementation.
  • Keep RatedMethod as an alias of RankedMethod and expose RankedMethod and RatedMethod from the methods package to preserve existing imports.

Tests:

  • Add regression tests to verify RankedMethod inheritance relationships, honest and strategic ballot behavior, and plurality aggregation results.

Summary by CodeRabbit

  • New Features

    • Added shared ranked-ballot functionality for voting methods.
    • Improved support for constructing honest and strategic ranked ballots.
    • Added candidate scoring support to the Plurality method.
  • Bug Fixes

    • Corrected ranked-method inheritance and exports across voting methods.
    • Improved handling of zero-slot ranked ballots.
  • Tests

    • Added regression coverage for ranked-method relationships and ballot behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sourcery-ai

sourcery-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduce an explicit RankedMethod base class that centralizes ranked-ballot construction and strategy logic, updates Borda, Plurality, IRNR, and Schulze to inherit from it, preserves public imports for RankedMethod/RatedMethod, and adds regression tests to validate inheritance, ballot behavior, strategy, and plurality results.

File-Level Changes

Change Details Files
Extract ranked-ballot helpers and strategy behavior from Borda into a dedicated RankedMethod base class and update Borda to inherit from it.
  • Replace Borda’s inheritance from Method with inheritance from RankedMethod.
  • Remove ballot construction and strategic helper methods from Borda and rely on RankedMethod implementations.
  • Keep candScore on Borda while delegating honBallot and strategy behavior to the base class.
  • Maintain the RankedMethod and RatedMethod names but move their definitions to a new module.
src/vse_sim/methods/borda.py
src/vse_sim/methods/ranked.py
src/vse_sim/methods/__init__.py
Adopt the new RankedMethod base in other ranked-style methods and ensure plurality uses the shared strategy and scoring behavior.
  • Switch Plurality, IRNR, and Schulze to import and subclass RankedMethod from the new ranked module.
  • Define candScore on Plurality so ranked-style aggregation is consistent.
  • Ensure existing strategies for these methods use the shared RankedMethod behavior.
src/vse_sim/methods/plurality.py
src/vse_sim/methods/irnr.py
src/vse_sim/methods/schulze.py
src/vse_sim/methods/ranked.py
Add regression tests to lock in the new inheritance structure, ballot construction semantics, strategy behavior, and plurality results while preserving public imports.
  • Extend methods package exports to expose RankedMethod and RatedMethod from the new ranked module instead of via Borda.
  • Add tests confirming RankedMethod is distinct from Borda, RatedMethod aliases RankedMethod, and Borda/Schulze share honBallot behavior.
  • Add tests exercising Borda’s strategic ballot behavior and Plurality’s result aggregation.
  • Update regression test imports to include Borda, Plurality, RankedMethod, and RatedMethod.
src/vse_sim/methods/__init__.py
tests/test_regressions.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In RankedMethod.fillPrefOrder, the if whichCands: truthiness check means an empty iterable will be treated the same as None and ignore the filter; consider explicitly checking whichCands is not None to distinguish "no filter" from "empty filter".
  • In RankedMethod.fillCands, i is only defined inside the for loop; if nSlots is 0 but remainderScore is not None, the i += 1 path will raise an error—initialize a separate index (e.g., idx = nSlots) for the remainder loop instead of reusing i.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `RankedMethod.fillPrefOrder`, the `if whichCands:` truthiness check means an empty iterable will be treated the same as `None` and ignore the filter; consider explicitly checking `whichCands is not None` to distinguish "no filter" from "empty filter".
- In `RankedMethod.fillCands`, `i` is only defined inside the `for` loop; if `nSlots` is 0 but `remainderScore` is not `None`, the `i += 1` path will raise an error—initialize a separate index (e.g., `idx = nSlots`) for the remainder loop instead of reusing `i`.

## Individual Comments

### Comment 1
<location path="src/vse_sim/methods/ranked.py" line_range="39-47" />
<code_context>
-            nSlots=None, #again, None means "all"
-            remainderScore=None #what to give candidates that don't fit in nSlots
-            ):
-        if nSlots is None:
-            nSlots = len(whichCands)
-        cur = lowSlot + nSlots - 1
-        for i in range(nSlots):
-            ballot[whichCands[i][0]] = cur
-            cur -= 1
-        if remainderScore is not None:
-            i += 1
-            while i < len(whichCands):
-                ballot[whichCands[i][0]] = remainderScore
-                i += 1
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid using `i` after the loop when `nSlots` can be zero to prevent undefined-variable behavior.

When `nSlots` ends up as 0 (e.g., `whichCands` empty and `nSlots` left as `None`) and `remainderScore` is set, the loop never runs, so `i` is never initialized and `i += 1` will raise an error. This mirrors legacy behavior but is still a latent bug. Consider guarding this block with `if remainderScore is not None and nSlots > 0:` or rewriting to avoid using `i` after the loop, such as iterating `whichCands[nSlots:]` directly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/vse_sim/methods/ranked.py Outdated
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c1c5568-bc25-41aa-a6b5-d7f1eef74d4e

📥 Commits

Reviewing files that changed from the base of the PR and between 1b39df7 and 961e7cc.

📒 Files selected for processing (7)
  • src/vse_sim/methods/__init__.py
  • src/vse_sim/methods/borda.py
  • src/vse_sim/methods/irnr.py
  • src/vse_sim/methods/plurality.py
  • src/vse_sim/methods/ranked.py
  • src/vse_sim/methods/schulze.py
  • tests/test_regressions.py

📝 Walkthrough

Walkthrough

The change extracts shared ranked-ballot behavior into a new RankedMethod, updates ranked voting methods to inherit from it, adjusts public re-exports, adds Plurality.candScore, and expands regression coverage.

Changes

Ranked method hierarchy

Layer / File(s) Summary
RankedMethod implementation
src/vse_sim/methods/ranked.py
Adds ranked ballot construction, honest and strategic ballot helpers, and the RatedMethod alias.
Ranked method migration
src/vse_sim/methods/borda.py, src/vse_sim/methods/plurality.py, src/vse_sim/methods/irnr.py, src/vse_sim/methods/schulze.py
Updates ranked-method inheritance imports, changes Borda to inherit from RankedMethod, and adds Plurality.candScore.
Public exports and regression coverage
src/vse_sim/methods/__init__.py, tests/test_regressions.py
Re-exports ranked classes from ranked.py and tests inheritance, ballot behavior, and zero-slot filling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/ranked-method-base

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.

@fsargent
fsargent changed the base branch from docs/method-docstrings to main July 17, 2026 22:33
@fsargent
fsargent merged commit b42ea41 into main Jul 17, 2026
2 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