Display interim results
This commit is contained in:
parent
41d545d191
commit
6edca66f7e
106
app.py
106
app.py
|
@ -150,7 +150,20 @@ def vote_date():
|
||||||
""",
|
""",
|
||||||
(session["user"]["id"], meet_id,))
|
(session["user"]["id"], meet_id,))
|
||||||
dates = csr.fetchall()
|
dates = csr.fetchall()
|
||||||
return render_template("date_vote_fragment.html", dates=dates)
|
|
||||||
|
result = instantrunoff_forward(meet_id, "date")
|
||||||
|
log.debug("result = %s", result)
|
||||||
|
|
||||||
|
return render_template("date_vote_fragment.html", dates=dates, result=result)
|
||||||
|
|
||||||
|
@app.get("/result/<int:meet_id>/date")
|
||||||
|
def result_date(meet_id):
|
||||||
|
result = instantrunoff_forward(meet_id, "date")
|
||||||
|
log.debug("result = %s", result)
|
||||||
|
|
||||||
|
return render_template("date_result_fragment.html", result=result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/vote/time")
|
@app.post("/vote/time")
|
||||||
def vote_time():
|
def vote_time():
|
||||||
|
@ -186,7 +199,18 @@ def vote_time():
|
||||||
""",
|
""",
|
||||||
(session["user"]["id"], meet_id,))
|
(session["user"]["id"], meet_id,))
|
||||||
times = csr.fetchall()
|
times = csr.fetchall()
|
||||||
return render_template("time_vote_fragment.html", times=times)
|
|
||||||
|
result = instantrunoff_forward(meet_id, "time")
|
||||||
|
log.debug("result = %s", result)
|
||||||
|
|
||||||
|
return render_template("time_vote_fragment.html", times=times, result=result)
|
||||||
|
|
||||||
|
@app.get("/result/<int:meet_id>/time")
|
||||||
|
def result_time(meet_id):
|
||||||
|
result = instantrunoff_forward(meet_id, "time")
|
||||||
|
log.debug("result = %s", result)
|
||||||
|
|
||||||
|
return render_template("time_result_fragment.html", result=result)
|
||||||
|
|
||||||
@app.post("/vote/place")
|
@app.post("/vote/place")
|
||||||
def vote_place():
|
def vote_place():
|
||||||
|
@ -222,7 +246,19 @@ def vote_place():
|
||||||
""",
|
""",
|
||||||
(session["user"]["id"], meet_id,))
|
(session["user"]["id"], meet_id,))
|
||||||
places = csr.fetchall()
|
places = csr.fetchall()
|
||||||
return render_template("place_vote_fragment.html", places=places)
|
|
||||||
|
result = instantrunoff_forward(meet_id, "place")
|
||||||
|
log.debug("result = %s", result)
|
||||||
|
|
||||||
|
return render_template("place_vote_fragment.html", places=places, result=result)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/result/<int:meet_id>/place")
|
||||||
|
def result_place(meet_id):
|
||||||
|
result = instantrunoff_forward(meet_id, "place")
|
||||||
|
log.debug("result = %s", result)
|
||||||
|
|
||||||
|
return render_template("place_result_fragment.html", result=result)
|
||||||
|
|
||||||
|
|
||||||
def send_mail(email_address, confirmation_url):
|
def send_mail(email_address, confirmation_url):
|
||||||
|
@ -235,6 +271,70 @@ def send_mail(email_address, confirmation_url):
|
||||||
mta = smtplib.SMTP(host="localhost")
|
mta = smtplib.SMTP(host="localhost")
|
||||||
mta.send_message(msg)
|
mta.send_message(msg)
|
||||||
|
|
||||||
|
def get_ballots(meet_id, kind):
|
||||||
|
csr = get_cursor()
|
||||||
|
|
||||||
|
q = f"""
|
||||||
|
select {kind}.*, bod, position
|
||||||
|
from {kind} join {kind}_vote on {kind}.id = {kind}_vote.{kind}
|
||||||
|
where meet = %s
|
||||||
|
order by bod, position
|
||||||
|
"""
|
||||||
|
csr.execute(q, (meet_id,))
|
||||||
|
|
||||||
|
last_bod = None
|
||||||
|
ballots = []
|
||||||
|
for r in csr:
|
||||||
|
if r.bod != last_bod:
|
||||||
|
ballot = []
|
||||||
|
ballots.append(ballot)
|
||||||
|
last_bod = r.bod
|
||||||
|
ballot.append(r)
|
||||||
|
return ballots
|
||||||
|
|
||||||
|
def dump_ballots(ballots):
|
||||||
|
for ballot in ballots:
|
||||||
|
log.debug ("---")
|
||||||
|
for r in ballot:
|
||||||
|
log.debug(r)
|
||||||
|
|
||||||
|
def runoff(ballots):
|
||||||
|
count = {}
|
||||||
|
candidates = {}
|
||||||
|
for ballot in ballots:
|
||||||
|
for r in ballot:
|
||||||
|
count[r.id] = 0
|
||||||
|
candidates[r.id] = r
|
||||||
|
for ballot in ballots:
|
||||||
|
# The votes are sorted by position, so the first entry is the favourite
|
||||||
|
count[ballot[0].id] += 1
|
||||||
|
result = sorted(count.keys(), key=lambda i: count[i])
|
||||||
|
log.debug("result of this round:")
|
||||||
|
for r in result:
|
||||||
|
log.debug(r, count[r])
|
||||||
|
log.debug("striking %d", result[0])
|
||||||
|
loser = candidates[result[0]]
|
||||||
|
new_ballots = [
|
||||||
|
[
|
||||||
|
r for r in ballot if r.id != loser.id
|
||||||
|
] for ballot in ballots
|
||||||
|
]
|
||||||
|
return loser, new_ballots
|
||||||
|
|
||||||
|
def instantrunoff_forward(meet_id, kind):
|
||||||
|
ballots = get_ballots(meet_id, kind)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
while max(len(b) for b in ballots):
|
||||||
|
dump_ballots(ballots)
|
||||||
|
loser, ballots = runoff(ballots)
|
||||||
|
result.append(loser)
|
||||||
|
result = list(reversed(result))
|
||||||
|
log.debug("final result")
|
||||||
|
for r in result:
|
||||||
|
log.debug(r)
|
||||||
|
return result
|
||||||
|
|
||||||
def get_cursor():
|
def get_cursor():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
csr = db.cursor(row_factory=psycopg.rows.namedtuple_row)
|
csr = db.cursor(row_factory=psycopg.rows.namedtuple_row)
|
||||||
|
|
|
@ -10,19 +10,31 @@ body {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border: #CCC 1px solid;
|
border: #CCC 1px solid;
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blue-background-class {
|
.blue-background-class {
|
||||||
background: #CDF;
|
background: #CDF;
|
||||||
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-column-gap: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#hello {
|
#hello {
|
||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3;
|
||||||
}
|
}
|
||||||
#h-day, #h-time, #h-place {
|
h1, h2 {
|
||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.result-item {
|
||||||
|
padding: 1em;
|
||||||
|
border: #CCC 1px solid;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div id="r-day" hx-swap-oob="true">
|
||||||
|
{% for r in result %}
|
||||||
|
<div class="result-item">
|
||||||
|
{{ r.display or r.date }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
|
@ -4,3 +4,10 @@
|
||||||
<input type="hidden" name="date" value="{{d.id}}">
|
<input type="hidden" name="date" value="{{d.id}}">
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<div id="r-day" hx-swap-oob="true">
|
||||||
|
{% for r in result %}
|
||||||
|
<div class="result-item">
|
||||||
|
{{ r.display or r.date }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div id="r-place" hx-swap-oob="true">
|
||||||
|
{% for r in result %}
|
||||||
|
<div class="result-item">
|
||||||
|
{{ r.name }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
|
@ -4,3 +4,10 @@
|
||||||
<input type="hidden" name="place" value="{{d.id}}">
|
<input type="hidden" name="place" value="{{d.id}}">
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<div id="r-place" hx-swap-oob="true">
|
||||||
|
{% for r in result %}
|
||||||
|
<div class="result-item">
|
||||||
|
{{ r.name }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
<body>
|
<body>
|
||||||
<p>Registriere dich:</p>
|
<p>Registriere dich:</p>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="email" name="email">
|
<input type="email" name="email" placeholder="Mail-Adresse">
|
||||||
<input name="target" value="{{request.args.target}}">
|
<input type="hidden" name="target" value="{{request.args.target}}">
|
||||||
<input type="submit">
|
<input type="submit">
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div id="r-time" hx-swap-oob="true">
|
||||||
|
{% for r in result %}
|
||||||
|
<div class="result-item">
|
||||||
|
{{ r.display or r.time }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
|
@ -4,3 +4,10 @@
|
||||||
<input type="hidden" name="time" value="{{d.id}}">
|
<input type="hidden" name="time" value="{{d.id}}">
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<div id="r-time" hx-swap-oob="true">
|
||||||
|
{% for r in result %}
|
||||||
|
<div class="result-item">
|
||||||
|
{{ r.display or r.time }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</form>
|
</form>
|
||||||
<div id="r-day">
|
<div id="r-day" hx-get="/result/{{meet.id}}/date" hx-trigger="load">
|
||||||
</div>
|
</div>
|
||||||
<h2 id="h-time">
|
<h2 id="h-time">
|
||||||
Zu welcher Zeit?
|
Zu welcher Zeit?
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</form>
|
</form>
|
||||||
<div id="r-time">
|
<div id="r-time" hx-get="/result/{{meet.id}}/time" hx-trigger="load">
|
||||||
</div>
|
</div>
|
||||||
<h2 id="h-place">
|
<h2 id="h-place">
|
||||||
An welchem Ort?
|
An welchem Ort?
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</form>
|
</form>
|
||||||
<div id="r-place">
|
<div id="r-place" hx-get="/result/{{meet.id}}/place" hx-trigger="load">
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
|
|
Loading…
Reference in New Issue