move cli script to reduce core package dependencies

This commit is contained in:
Thomas (Tom) C. Gorordo 2026-05-25 00:15:49 -07:00
parent 2138a0ea6b
commit 8b39991bcd
Signed by: tgorordo
GPG key ID: 0CBED22BB0D94490
11 changed files with 251 additions and 141 deletions

View file

@ -43,10 +43,10 @@
<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,
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>
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> 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)

View file

@ -1,4 +0,0 @@
from smithy import cli
if __name__ == "__main__":
cli()

View file

@ -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
@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)

View file

@ -1,7 +1,6 @@
import polars as pl
from itertools import combinations
def smith_set(df: pl.DataFrame) -> list:
"""
Compute the Smith set from a Ranked-Choice ballot.

86
src/smithycmd.py Normal file
View 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()