mirror of
https://github.com/tgorordo/smithy.git
synced 2026-06-13 02:42:14 -07:00
move cli script to reduce core package dependencies
This commit is contained in:
parent
2138a0ea6b
commit
8b39991bcd
11 changed files with 251 additions and 141 deletions
148
README.md
148
README.md
|
|
@ -1,5 +1,147 @@
|
||||||
# Smithy
|
# Smithy
|
||||||
*A simple smith set solver for ranked-choice ballots.*
|
*A simple Smith set solver for ranked-choice ballots.*
|
||||||
|
|
||||||
|
The Smith set is the minimal set of election candidates which can beat all others pairwise
|
||||||
|
(by simple majority ranking preference) - if there is a single winner in the set they are
|
||||||
|
guaranteed the standard Condorcet i.e. Majority winner (they beat all others pairwise).
|
||||||
|
|
||||||
|
`smithy` currently identifies the set by brute-force search which is combinatoric complexity
|
||||||
|
in the worst case (TODO: better [algorithm](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm))
|
||||||
|
but appears approximately $O(n^2)$ on-average in the number of candidates for typical/random ballots,
|
||||||
|
and linear in the number of ballots.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
`smithy` can be used a few different ways: as a CLI command, via a web form upload,
|
||||||
|
or imported as a python package (TODO: a small [GUI](https://doc.qt.io/qtforpython-6/)).
|
||||||
|
|
||||||
|
|
||||||
|
### CLI Command
|
||||||
|
`smithy` provides a [`click`](https://click.palletsprojects.com/en/stable/)
|
||||||
|
CLI command defined in `src/cmd.py` which can be invoked a couple of ways:
|
||||||
|
|
||||||
|
- The [GitHub Releases Page](https://github.com/tgorordo/smithy/releases) provides standalone
|
||||||
|
CLI executables bundled using [PyInstaller](https://pyinstaller.org/en/stable/index.html)
|
||||||
|
for linux and windows on `x86_64` and linux on `aarch64`. Downloading the appropriate
|
||||||
|
executable should give you access to the `smithycmd` CLI command.
|
||||||
|
|
||||||
|
- Alternatively, if you are using `uv` (see [Development](#Development)), you can invoke
|
||||||
|
this in your shell as `uv run src/smithycmd.py [...]`.
|
||||||
|
|
||||||
|
In either case, the command expects the same argument structure:
|
||||||
|
TODO
|
||||||
|
|
||||||
|
While plain output is the default, so that the command can easily be used in a unix-pipe
|
||||||
|
or `stdio` workflows, it can also pretty-print its output for your reading pleasure
|
||||||
|
using [`rich`](https://rich.readthedocs.io/en/stable/introduction.html) if you pass it the
|
||||||
|
`--pretty` (or `-p`) flag.
|
||||||
|
|
||||||
|
|
||||||
|
### UOregon CGI Application
|
||||||
|
[**This** `pages.uoregon.edu` page](https://pages.uoregon.edu/tgorordo/files/smithy/src/cgi/form.html)
|
||||||
|
hosts a [CGI form](https://service.uoregon.edu/TDClient/2030/Portal/KB/ArticleDet?ID=43069)
|
||||||
|
which accepts a `.csv` or `.xls`(`x`) upload of a ballot-box and responds with the resulting
|
||||||
|
Smith set. The application can typically handle ~100s of candidates and ~10ks of votes
|
||||||
|
before running into hosting resource limitations and timing out (pathological cases, involving
|
||||||
|
many or large cycles, or especially a large cyclic tie, may perform worse and are not extensively tested).
|
||||||
|
|
||||||
|
|
||||||
|
### Python Package
|
||||||
|
The `smithy` python package primarily provides the function `smith_set(ballot_box_df)`
|
||||||
|
(defined in `src/smithy/rcv.py`), which returns the smith set given a
|
||||||
|
[polars](https://docs.pola.rs/api/python/stable/reference/index.html)
|
||||||
|
dataframe `ballot_box_df` whose columns are candidates and whose rows are RCV ballots.
|
||||||
|
|
||||||
|
e.g. `smithy` can be imported into a [marimo](https://docs.marimo.io/#highlights) notebook
|
||||||
|
as seen in `test/test_nb.py` - if you're using `uv` you can launch marimo via
|
||||||
|
`uv run marimo --edit` and navigate to that example notebook
|
||||||
|
(`just marimo` is an available shorthand if you are using the `just` taskrunner --
|
||||||
|
see [Development](#Development)). Cloning `smithy` then working in scratch notebooks within the repo
|
||||||
|
(e.g. makeing your own `nbs/` directory)
|
||||||
|
is probably the easiest way to use it if you need to do some of cleanup of the tabular
|
||||||
|
data before invocation - just do your cleanup in a notebook using
|
||||||
|
[polars](https://docs.pola.rs/py-polars/html/reference/) then pass a tidy dataframe to `smith_set(df)`.
|
||||||
|
|
||||||
|
Of course, you can also import into any python script or package for a more involved project
|
||||||
|
as well:
|
||||||
|
- If you're using `uv` adding `smithy` should be as simple as
|
||||||
|
```bash
|
||||||
|
uv add git+https://github.com/tgorordo/smithy.git
|
||||||
|
```
|
||||||
|
- Alternatively, if you're using the more default `pip` you can make it available by
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/tgorordo/smithy.git # or submodule add somewhere appropriate in your project
|
||||||
|
cd smithy
|
||||||
|
pip install .
|
||||||
|
```
|
||||||
|
(if you're new to pip-venvs,
|
||||||
|
[this brief guide might be helpful](https://pages.uoregon.edu/tgorordo/courses/uoph410-510a_Image-Analysis/setup.html)).
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
Current development tooling is based on the [`uv`](https://docs.astral.sh/uv/) Python package and
|
||||||
|
project manager. A [`justfile`](https://github.com/casey/just) lists some common useful
|
||||||
|
task commands. A simple [`shell.nix`](https://nix.dev/tutorials/first-steps/declarative-shell.html)
|
||||||
|
can be used to load these tools into a local environment, and you might find
|
||||||
|
[`direnv`](https://github.com/direnv/direnv) a convenient pairing to `nix` - but just having a
|
||||||
|
system-provided `uv` available is more than enough to use and develop `smithy`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Formatting, Checking, Testing and Locking
|
||||||
|
Source can be formatted using [`ruff`](https://docs.astral.sh/ruff/) by running
|
||||||
|
```bash
|
||||||
|
uv run ruff format src test
|
||||||
|
```
|
||||||
|
(or `just format`)
|
||||||
|
which should be done before any `git` commit.
|
||||||
|
|
||||||
|
This is not really taken full advantage of at the moment, but type hints can be checked
|
||||||
|
by running
|
||||||
|
```bash
|
||||||
|
uv run pyright src
|
||||||
|
```
|
||||||
|
(or `just check`).
|
||||||
|
|
||||||
|
A test suite can be run as
|
||||||
|
```bash
|
||||||
|
uv run pytest -vvv --tb=short --log-cli-level=INFO
|
||||||
|
```
|
||||||
|
(or `just test`).
|
||||||
|
|
||||||
|
Dependencies can be locked by running
|
||||||
|
```bash
|
||||||
|
uv lock
|
||||||
|
uv pip compile pyproject.toml -o requirements.txt --group dev
|
||||||
|
```
|
||||||
|
(or `just lock`).
|
||||||
|
|
||||||
|
|
||||||
|
### Bundling, Releases, Deployment and Cleanup
|
||||||
|
Bundling with [PyInstaller](https://pyinstaller.org/en/stable/index.html) can be done by
|
||||||
|
```bash
|
||||||
|
uv run pyinstaller --clean -F src/cmd.py --name smithycmd
|
||||||
|
```
|
||||||
|
which will output an executable `smithycmd` for the CLI in `dist/`, which can be uploaded
|
||||||
|
to the latest [GitHub Releases Page](https://github.com/tgorordo/smithy/releases).
|
||||||
|
|
||||||
|
Deploying the main CGI page at
|
||||||
|
[`pages.uoregon.edu/tgorordo/files/smithy/src/cgi`](https://pages.uoregon.edu/tgorordo/files/smithy/src/cgi)
|
||||||
|
requires shell or SFTP access to [my `pages.uoregon.edu`](https://pages.uoregon.edu/tgorordo),
|
||||||
|
which you won't get unless you're [me](https://github.com/tgorordo).
|
||||||
|
|
||||||
|
If you want to deploy it to your own `pages.uoregon.edu` page, then it should be enough to
|
||||||
|
ensure you have [CGI set up](https://service.uoregon.edu/TDClient/2030/Portal/KB/ArticleDet?ID=43069)
|
||||||
|
then SFTP into `sftp.uoregon.edu` and `put -r smithy` (or git clone when `ssh`ed into
|
||||||
|
`shell.uoregon.edu`) wherever you'd like in your `public_html`, ensure `smithy.cgi` has
|
||||||
|
`chmod 755` permissions,
|
||||||
|
[standalone install `uv` for your user](https://docs.astral.sh/uv/getting-started/installation/)
|
||||||
|
then run `uv sync` from somewhere inside the `smithy` directory. Navigating to the `smithy.cgi`
|
||||||
|
file in `pages.uoregon.edu/YOUR_USER/.../cgi/smithy.cgi` should give you a working form.
|
||||||
|
|
||||||
|
If you're using `just` you can cleanup working files by running `just clean` and can fully
|
||||||
|
wipe your `uv` generated `.venv` with `just wipe` (if you want manual versions of those,
|
||||||
|
check the `justfile` definition). It's probably best to do this before any commit as well,
|
||||||
|
though probably not required if you're careful or have `.gitignore` updated appropriately
|
||||||
|
for any changes you made.
|
||||||
|
|
||||||
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
|
||||||
in the set they are guaranteed the standard Condorcet i.e. Majority winner (they beat all others pairwise).
|
|
||||||
|
|
|
||||||
6
justfile
6
justfile
|
|
@ -1,8 +1,8 @@
|
||||||
list:
|
list:
|
||||||
just --list --unsorted
|
just --list --unsorted
|
||||||
|
|
||||||
run spreadsheet:
|
run *args:
|
||||||
uv run smithy {{spreadsheet}}
|
uv run src/smithycmd.py {{args}}
|
||||||
|
|
||||||
marimo:
|
marimo:
|
||||||
uv run marimo --edit
|
uv run marimo --edit
|
||||||
|
|
@ -25,7 +25,7 @@ compile:
|
||||||
clean:
|
clean:
|
||||||
uv run pyclean src test
|
uv run pyclean src test
|
||||||
uv run ruff clean
|
uv run ruff clean
|
||||||
rm -rf main.spec cli.spec build dist .pytest_cache .hypothesis .benchmarks __marimo__
|
rm -rf smithy.spec build dist .pytest_cache .hypothesis .benchmarks __marimo__
|
||||||
|
|
||||||
wipe:
|
wipe:
|
||||||
just clean
|
just clean
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,17 @@ authors = [
|
||||||
]
|
]
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click>=8.4.0",
|
|
||||||
"polars>=1.40.1",
|
"polars>=1.40.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
cli = [
|
||||||
|
"click>=8.4.0",
|
||||||
"rich>=15.0.0",
|
"rich>=15.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
#[project.scripts]
|
||||||
smithy = "smithy:cli"
|
#smithy = "smithycmd:cli"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["uv_build>=0.11.7,<0.12.0"]
|
requires = ["uv_build>=0.11.7,<0.12.0"]
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ charset-normalizer==3.4.7
|
||||||
# via requests
|
# via requests
|
||||||
click==8.4.0
|
click==8.4.0
|
||||||
# via
|
# via
|
||||||
# smithy (pyproject.toml)
|
|
||||||
# marimo
|
# marimo
|
||||||
# uvicorn
|
# uvicorn
|
||||||
distro==1.9.0
|
distro==1.9.0
|
||||||
|
|
@ -84,12 +83,8 @@ markdown==3.10.2
|
||||||
# via
|
# via
|
||||||
# marimo
|
# marimo
|
||||||
# pymdown-extensions
|
# pymdown-extensions
|
||||||
markdown-it-py==4.2.0
|
|
||||||
# via rich
|
|
||||||
markupsafe==3.0.3
|
markupsafe==3.0.3
|
||||||
# via jinja2
|
# via jinja2
|
||||||
mdurl==0.1.2
|
|
||||||
# via markdown-it-py
|
|
||||||
msgspec==0.21.1
|
msgspec==0.21.1
|
||||||
# via marimo
|
# via marimo
|
||||||
narwhals==2.21.2
|
narwhals==2.21.2
|
||||||
|
|
@ -151,7 +146,6 @@ pygments==2.20.0
|
||||||
# via
|
# via
|
||||||
# marimo
|
# marimo
|
||||||
# pytest
|
# pytest
|
||||||
# rich
|
|
||||||
pyinstaller==6.20.0
|
pyinstaller==6.20.0
|
||||||
# via smithy (pyproject.toml:dev)
|
# via smithy (pyproject.toml:dev)
|
||||||
pyinstaller-hooks-contrib==2026.5
|
pyinstaller-hooks-contrib==2026.5
|
||||||
|
|
@ -184,8 +178,6 @@ regex==2026.5.9
|
||||||
# via tiktoken
|
# via tiktoken
|
||||||
requests==2.34.2
|
requests==2.34.2
|
||||||
# via tiktoken
|
# via tiktoken
|
||||||
rich==15.0.0
|
|
||||||
# via smithy (pyproject.toml)
|
|
||||||
rpds-py==0.30.0
|
rpds-py==0.30.0
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
|
|
|
||||||
38
smithy.spec
38
smithy.spec
|
|
@ -1,38 +0,0 @@
|
||||||
# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['src/cmd.py'],
|
|
||||||
pathex=[],
|
|
||||||
binaries=[],
|
|
||||||
datas=[],
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
hooksconfig={},
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
noarchive=False,
|
|
||||||
optimize=0,
|
|
||||||
)
|
|
||||||
pyz = PYZ(a.pure)
|
|
||||||
|
|
||||||
exe = EXE(
|
|
||||||
pyz,
|
|
||||||
a.scripts,
|
|
||||||
a.binaries,
|
|
||||||
a.datas,
|
|
||||||
[],
|
|
||||||
name='smithy',
|
|
||||||
debug=False,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
runtime_tmpdir=None,
|
|
||||||
console=True,
|
|
||||||
disable_windowed_traceback=False,
|
|
||||||
argv_emulation=False,
|
|
||||||
target_arch=None,
|
|
||||||
codesign_identity=None,
|
|
||||||
entitlements_file=None,
|
|
||||||
)
|
|
||||||
|
|
@ -43,10 +43,10 @@
|
||||||
|
|
||||||
<h2>About</h2>
|
<h2>About</h2>
|
||||||
<p>This is an upload form for the <a href="https://github.com/tgorordo/smithy">smithy</a> RCV Ballot counter for identifying the Smith set (majority winners) in small elections,
|
<p>This is an upload form for the <a href="https://github.com/tgorordo/smithy">smithy</a> RCV Ballot counter for identifying the Smith set (majority winners) in small elections,
|
||||||
mainly to help with UO Physics Grad student elections of various flavors. This form uses the <a href="https://service.uoregon.edu/TDClient/2030/Portal/KB/ArticleDet?ID=43069">UO pages.uoregon.edu CGI Capability</a>,
|
mainly to help with <a href="https://blogs.uoregon.edu/physicsgsg/">UO Physics Grad student elections</a> of various flavors. This form uses the <a href="https://service.uoregon.edu/TDClient/2030/Portal/KB/ArticleDet?ID=43069">UO pages.uoregon.edu CGI Capability</a>,
|
||||||
so the implementation of smithy being invoked can be <a href="https://pages.uoregon.edu/tgorordo/files/smithy/"> inspected here</a>
|
so the implementation of smithy being invoked can be <a href="https://pages.uoregon.edu/tgorordo/files/smithy/"> inspected here</a>
|
||||||
and you may also inspect the source of this page to verify that <a href="https://pages.uoregon.edu/tgorordo/files/smithy/src/cgi/smithy.cgi?source">this script<a> is called to invoke it - though
|
and you may also inspect the source of this page to verify that <a href="https://pages.uoregon.edu/tgorordo/files/smithy/src/cgi/smithy.cgi?source">this script<a> is called to invoke it - though
|
||||||
you have to trust it to be responding with its own contents faithfully.
|
you have to trust it to <a href="https://en.wikipedia.org/wiki/Quine_(computing)">quine</a> itself faithfully.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p> The <a href="https://en.wikipedia.org/wiki/Smith_set">Smith set</a> is the minimal set of candidates which can beat all others pairwise (by majority ranking)
|
<p> The <a href="https://en.wikipedia.org/wiki/Smith_set">Smith set</a> is the minimal set of candidates which can beat all others pairwise (by majority ranking)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
from smithy import cli
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
cli()
|
|
||||||
|
|
@ -1,77 +1 @@
|
||||||
import click
|
|
||||||
import polars as pl
|
|
||||||
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.table import Table
|
|
||||||
from rich.panel import Panel
|
|
||||||
|
|
||||||
from .rcv import smith_set
|
from .rcv import smith_set
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.argument("spreadsheet", type=click.Path(exists=True, dir_okay=False))
|
|
||||||
def cli(spreadsheet: str) -> None:
|
|
||||||
"""
|
|
||||||
Compute the Smith set from a ranked-choice ballot spreadsheet.
|
|
||||||
|
|
||||||
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
|
||||||
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
|
||||||
"""
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Load spreadsheet
|
|
||||||
if spreadsheet.endswith(".csv"):
|
|
||||||
df = pl.read_csv(spreadsheet)
|
|
||||||
|
|
||||||
elif spreadsheet.endswith((".xlsx", ".xls")):
|
|
||||||
df = pl.read_excel(spreadsheet)
|
|
||||||
|
|
||||||
else:
|
|
||||||
console.print(
|
|
||||||
"[bold red]Unsupported file type.[/bold red]\nUse CSV or Excel."
|
|
||||||
)
|
|
||||||
raise SystemExit(1)
|
|
||||||
|
|
||||||
# Normalize numerical dataframe entries
|
|
||||||
df = df.with_columns(
|
|
||||||
[
|
|
||||||
pl.col(c)
|
|
||||||
.cast(pl.Utf8)
|
|
||||||
.str.strip_chars()
|
|
||||||
.cast(pl.Int64, strict=False)
|
|
||||||
.fill_null(0)
|
|
||||||
for c in df.columns
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Compute Smith set
|
|
||||||
smiths = smith_set(df)
|
|
||||||
|
|
||||||
# Preview table
|
|
||||||
preview = Table(title="Ballot Box")
|
|
||||||
|
|
||||||
for col in df.columns:
|
|
||||||
preview.add_column(col)
|
|
||||||
|
|
||||||
for row in df.head(5).iter_rows():
|
|
||||||
preview.add_row(*map(str, row))
|
|
||||||
|
|
||||||
console.print(preview)
|
|
||||||
|
|
||||||
# Results
|
|
||||||
console.print()
|
|
||||||
|
|
||||||
console.print(
|
|
||||||
Panel.fit(
|
|
||||||
"\n".join(f"• {c}" for c in smiths),
|
|
||||||
title="Resulting Smith Set",
|
|
||||||
border_style="green",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
||||||
|
|
||||||
raise SystemExit(1)
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import polars as pl
|
import polars as pl
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
|
|
||||||
|
|
||||||
def smith_set(df: pl.DataFrame) -> list:
|
def smith_set(df: pl.DataFrame) -> list:
|
||||||
"""
|
"""
|
||||||
Compute the Smith set from a Ranked-Choice ballot.
|
Compute the Smith set from a Ranked-Choice ballot.
|
||||||
|
|
|
||||||
86
src/smithycmd.py
Normal file
86
src/smithycmd.py
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.13"
|
||||||
|
# dependencies = [
|
||||||
|
# "click>=8.4.1",
|
||||||
|
# "rich>=15.0.0",
|
||||||
|
# "polars>=1.40.1"
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
import click
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.panel import Panel
|
||||||
|
|
||||||
|
from smithy import smith_set
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument("spreadsheet", type=click.Path(exists=True, dir_okay=False))
|
||||||
|
def cli(spreadsheet: str) -> None:
|
||||||
|
"""
|
||||||
|
Compute the Smith set from a ranked-choice ballot spreadsheet.
|
||||||
|
|
||||||
|
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
||||||
|
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Load spreadsheet
|
||||||
|
if spreadsheet.endswith(".csv"):
|
||||||
|
df = pl.read_csv(spreadsheet)
|
||||||
|
|
||||||
|
elif spreadsheet.endswith((".xlsx", ".xls")):
|
||||||
|
df = pl.read_excel(spreadsheet)
|
||||||
|
|
||||||
|
else:
|
||||||
|
console.print(
|
||||||
|
"[bold red]Unsupported file type.[/bold red]\nUse CSV or Excel."
|
||||||
|
)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
# Normalize numerical dataframe entries
|
||||||
|
df = df.with_columns(
|
||||||
|
[
|
||||||
|
pl.col(c)
|
||||||
|
.cast(pl.Utf8)
|
||||||
|
.str.strip_chars()
|
||||||
|
.cast(pl.Int64, strict=False)
|
||||||
|
.fill_null(0)
|
||||||
|
for c in df.columns
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compute Smith set
|
||||||
|
smiths = smith_set(df)
|
||||||
|
|
||||||
|
# Preview table
|
||||||
|
preview = Table(title="Ballot Box")
|
||||||
|
|
||||||
|
for col in df.columns:
|
||||||
|
preview.add_column(col)
|
||||||
|
|
||||||
|
for row in df.head(5).iter_rows():
|
||||||
|
preview.add_row(*map(str, row))
|
||||||
|
|
||||||
|
console.print(preview)
|
||||||
|
|
||||||
|
# Results
|
||||||
|
console.print()
|
||||||
|
|
||||||
|
console.print(
|
||||||
|
Panel.fit(
|
||||||
|
"\n".join(f"• {c}" for c in smiths),
|
||||||
|
title="Resulting Smith Set",
|
||||||
|
border_style="green",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||||
|
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli()
|
||||||
11
uv.lock
generated
11
uv.lock
generated
|
|
@ -1484,8 +1484,12 @@ name = "smithy"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
|
||||||
{ name = "polars" },
|
{ name = "polars" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
cli = [
|
||||||
|
{ name = "click" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1505,10 +1509,11 @@ dev = [
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "click", specifier = ">=8.4.0" },
|
{ name = "click", marker = "extra == 'cli'", specifier = ">=8.4.0" },
|
||||||
{ name = "polars", specifier = ">=1.40.1" },
|
{ name = "polars", specifier = ">=1.40.1" },
|
||||||
{ name = "rich", specifier = ">=15.0.0" },
|
{ name = "rich", marker = "extra == 'cli'", specifier = ">=15.0.0" },
|
||||||
]
|
]
|
||||||
|
provides-extras = ["cli"]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue