A standalone comsocwebapp
application: five housemates give up a shared flat and have to divide the
furniture between them.
Each housemate spreads 100 points over the eight items to say what each is worth to them. A rule then hands everyone a bundle — and, more importantly, publishes the arithmetic that says the split was fair: what each housemate's own pile is worth to them, what somebody else's pile would have been worth, and which fairness guarantees survive on the ballots as cast.
This folder is self-contained. Copy it into a repository of your own, rename
things, and it keeps working — it depends only on comsocwebapp.
Requires Python 3.10+.
python -m venv --system-site-packages venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install --upgrade pip # avoid "project name unknown" with older pip
pip install -r requirements.txt --upgrade
python app.pyOpen http://127.0.0.1:5020/ and log in as admin@example.com / admin. The
console also prints five personal invitation links — open one in a private
window to see what a housemate sees.
There is no database server to set up: the app creates an SQLite file under
instance/ on first run, seeds the eight items, and simulates five housemates
so the rules have something to chew on before the real ones arrive.
- Log in as the admin and open Admin → the poll → Run a rule.
- Run
round_robin_auditedover the dummy scope. The execution log shows every pick as it happens, then the fairness audit. - Run
fairpyx_iterated_maximum_matchingon the same ballots and compare the two audits — the second usually raises the total value handed out, and the audit says whether it kept EF1 while doing so. - Open an invitation link in a private window, register, and spread your own
100 points. Re-run a rule over
all, and the results page will show you your own pile plus the log that justifies it.
Both definitions are standard, and both are computed from the ballots as cast rather than asserted:
- Proportional — every housemate values their own pile at no less than 1/n of what they value the whole flat.
- EF1 (envy-free up to one item) — for every pair of housemates, any envy the first feels towards the second disappears once a single item is removed from the second's pile.
EF1 is the guarantee to expect. With indivisible items, envy-freeness proper is frequently impossible — one road bike and two cyclists — while round-robin reaches EF1 on any valuations at all. When a run is envy-free, the audit says so.
app.py the entire application: config, rules, seeding
requirements.txt comsocwebapp[allocation] -- pulls in fairpyx
example.env template for .env -- committed, every line commented out
.env your secrets; git-ignored, created by you (see below)
.gitignore keeps .env and the database out of version control
templates/
└── participant/
├── index.html the landing page
└── results.html one pile per housemate, yours marked, audit underneath
static/
└── style.css this application's own styling
instance/ created on first run; holds the SQLite database
Every other page — the ballot, the admin dashboard, login, registration — comes
from the package. templates/ only has to hold the files it wants to replace.
app.py is organised in the four steps you will edit:
- Configuration —
PORT, the point limit, the database path, the template/static folders. Not the secrets: those live in.env. - The rules — a round-robin rule of our own, plus four
fairpyxalgorithms, all of them ending their log with the same fairness audit. - Seeding —
db.ensure_db()creates the schema only when it is missing, so restarting never destroys collected ballots. - Entry point — the application factory, also usable from
flaskorgunicorn.
| Rule | Needs fairpyx? |
What it does |
|---|---|---|
round_robin_audited |
no | Agents take turns claiming their favourite remaining item. About forty lines in app.py, and enough on its own — the app is fully usable on a bare pip install comsocwebapp. |
fairpyx_round_robin |
yes | The library's version of the same idea. |
fairpyx_bidirectional_round_robin |
yes | The picking order reverses every other round, which softens the last-picker penalty. |
fairpyx_iterated_maximum_matching |
yes | Each round hands out items so as to maximise that round's total value. |
fairpyx_almost_egalitarian_allocation |
yes | Pushes up the worst-off agent's value rather than the sum. |
The four fairpyx rules are registered by
register_audited_fairpyx_rule(...) in app.py, one line each. Without
fairpyx installed those calls register nothing, so the admin's dropdown never
offers a rule this installation cannot run.
The package also ships a one-line adapters.register_fairpyx_rule("round_robin")
that skips the wrapper entirely. This app writes its own because it wants the
fairness audit appended to every run log; the adapter's to_fairpyx_instance
still does all the conversion, and only the log gets longer.
Any function in fairpyx.algorithms works. One line:
register_audited_fairpyx_rule(
"utilitarian_matching",
"Rule: fairpyx utilitarian matching -- maximises the total value handed out.")Or write your own, which is a plain function returning a RuleResult:
@rules.register_rule("my_rule", formats=("points",), poll_types=("allocation",))
def my_rule(poll_id, scope=adapters.SCOPE_ALL, **_):
valuations = adapters.preference_matrix(poll_id, scope, by_name=True)
bundles = ... # {agent: [item, ...]}
labels = agent_labels(poll_id, scope)
log = ["Rule: mine."] + fairness_report(valuations, bundles, labels)
return rules.RuleResult(outcome=_encode(bundles), log_lines=log)fairness_report, agent_labels and _encode are the three helpers in
app.py that every rule here shares, so a new rule gets the audit, the
readable names and the results page for free.
Settings come from two places, and the split matters once this folder is on GitHub:
- Not secret — the port, the point limit, the database path, the template
and static folders — is the
CONFIGdict inapp.py, and is committed. - Secret — the session key, any OAuth client secrets, a database password —
is a
.envfile next toapp.py, which.gitignorekeeps out of the repository.app.pyreads it withload_dotenv()on start-up.
example.env is the committed template, with every line commented out. Copy it
and fill in what you need:
cp example.env .env # Windows: copy example.env .envNothing in it is required to run locally: a missing .env, or a line left
commented, simply changes nothing. The one line to set before this is reachable
from anywhere but your own machine is the session key:
COMSOCWEBAPP_SECRET_KEY=<output of: python -c "import secrets; print(secrets.token_hex(32))">Without it the app falls back to the development key "dev", and anyone who
knows that — it is in the package source — can forge a session cookie,
including an administrator's.
- Different items? Edit
ITEMSinseed(), and deleteinstance/so the seed runs again. Keep commas out of the item names: an outcome is stored as a comma-separated list of bundles. After the first run the admin GUI can add, rename and remove items without touching the code. - A different point limit?
POINT_LIMITat the top ofapp.py. It is stored on the poll asbudget_limit— the one numeric limit a poll carries, read according to its type — so the ballot page enforces "must total exactly N" without this application doing anything. - Rankings instead of points? Change
pref_formatto"ranking"in thecreate_poll(...)call.to_fairpyx_instanceinverts a ranking into utilities by itself; widen theformats=("points",)filter on the rules to("points", "ranking")so they are still offered. - Different look? Every template in
comsocwebapp/templates/can be overridden by putting a file with the same path undertemplates/here. This app overrides two; everything else falls back to the package. - Sign-in with Google / GitHub / ORCID?
pip install "comsocwebapp[oauth] @ git+https://github.com/ariel-research/comsocwebapp", then uncomment that provider's two lines in.env. The buttons appear by themselves, andapp.pyprints the redirect URI to register in the provider's console. Client secrets belong in.env, never inapp.py.
Delete the database and re-run:
rm -rf instance/ # Windows: rmdir /s /q instance
python app.pyPut a real session key in .env (see Secrets), then
run behind a WSGI server:
pip install gunicorn
gunicorn "app:app" --bind 0.0.0.0:8000 --workers 4app.py loads .env by absolute path, so this works from any directory. If
your platform injects configuration as real environment variables instead —
most PaaS do — set COMSOCWEBAPP_SECRET_KEY there and skip the file; the app
reads the same variable either way.
Serve it over HTTPS: invitation tokens and session cookies travel in the request.
GPL-3.0, the same as comsocwebapp. See LICENSE.