Skip to content
Snippets Groups Projects
Commit 9ae13cf9 authored by Tomáš Valenta's avatar Tomáš Valenta
Browse files

rough RV voting implementation

parent d137862c
No related branches found
No related tags found
1 merge request!1Test release
Pipeline #11248 failed
......@@ -2,6 +2,7 @@
<ul class="flex flex-col gap-2">
{% for option_key, option in options.items %}
{% if option.has_support or show_has_support %}
<h3 class="flex gap-2 items-end text-xl font-semibold">
{{ option_key }}
<span class="text-sm text-gray-600">
......@@ -13,11 +14,11 @@
</h3>
{% if option.votes|length != 0 %}
<ul class="flex gap-3 mt-1">
<ul class="flex gap-3 mt-1 flex-wrap">
{% for vote in option.votes %}
<li class="flex">
<div class="px-4 py-2 bg-gray-300">
{{ vote.member }}
{{ rv_members|index:vote.member|index:"displayName" }}
</div>
<ul class="px-4 py-2 flex gap-1 bg-gray-200">
{% for option in options_by_member|index:vote.member %}
......@@ -28,5 +29,6 @@
{% endfor %}
</ul>
{% endif %}
{% endif %}
{% endfor %}
</ul>
<li class="bg-green-200 drop-shadow-md p-4 font-semibold text-center text-xl">
Možnost {{ option_key }} vyhrává s {{ option.vote_count }} hlasy.
</li>
......@@ -7,5 +7,5 @@
</small>
</div>
{% include "rv_voting_calc/includes/option_list.html" with options=options options_by_member=options_by_member show_has_support=True %}
{% include "rv_voting_calc/includes/option_list.html" with options=options options_by_member=options_by_member show_proposers=True show_has_support=True %}
</li>
<li class="bg-red-200 drop-shadow-md p-4 font-semibold text-center text-xl">
Ani jedna možnost nevyhrává.
</li>
<li class="bg-gray-100 drop-shadow-md p-4">
<div class="pb-2 mb-2 border-b border-gray-300">
<h2 class="text-2xl font-semibold font-bebas">{{ iteration|add:1 }}. vyřazovací kolo</h2>
</div>
<p>Odstraněna {% if randomly %}(losováním){% else %}nepopulární{% endif %} možnost {{ option_key }}</p>
</li>
......@@ -3,5 +3,9 @@
<h2 class="text-2xl font-semibold font-bebas">Po vyřazení možností bez nadpoloviční podpory</h2>
</div>
{% if options|length != 0 %}
{% include "rv_voting_calc/includes/option_list.html" with options=options options_by_member=options_by_member %}
{% else %}
<span class="text-gray-800 mt-1">Žádné možnosti nemají nadpoloviční podporu.</span>
{% endif %}
</li>
import json
import math
import random
import gql
import requests
......@@ -76,6 +77,257 @@ def index(request):
)
def do_step_b_through_d(
request,
iteration: int,
steps: list,
options: dict,
options_by_member: dict,
options_without_support: list,
rv_members: dict,
winner_treshold: int,
) -> tuple:
## BEGIN Step B
#print("start", options)
for option in options.values():
votes_to_remove = []
for vote_list_position, vote in enumerate(option["votes"]):
if vote.get("was_moved", False):
continue
for other_acceptable_option_position, other_acceptable_option in (
enumerate(options_by_member[vote["member"]])
):
if not options[other_acceptable_option]["has_support"]:
continue
if (
other_acceptable_option_position >= vote["position"]
and option["has_support"]
):
break
already_voted_for_acceptable_option = False
for other_acceptable_option_vote in options[other_acceptable_option]["votes"]:
if other_acceptable_option_vote["member"] == vote["member"]:
already_voted_for_acceptable_option = True
if already_voted_for_acceptable_option:
votes_to_remove.append(vote_list_position)
break
vote["was_moved"] = True
options[other_acceptable_option]["votes"].append(vote)
break
index_offset = 0
for vote_list_position in votes_to_remove:
option["votes"].pop(vote_list_position - index_offset)
index_offset += 1
for option_key in options_without_support:
options.pop(option_key)
if len(options) == 0:
steps.append(
render_to_string(
"rv_voting_calc/steps/no_winner.html",
{},
request=request,
)
)
return options, True
## END Step B
## BEGIN Step C
total_vote_count = 0
lowest_vote_count = math.inf
highest_vote_count = 0
for option in options.values():
if option["vote_count"] < lowest_vote_count:
lowest_vote_count = option["vote_count"]
if option["vote_count"] > highest_vote_count:
highest_vote_count = option["vote_count"]
total_vote_count += option["vote_count"]
# Remove was_moved for next iteration
option.pop("was_moved", None)
options = {
key: value
for key, value
in sorted(
options.items(),
reverse=True,
key=lambda option: option[1]["vote_count"],
)
}
found_winner = False
winners = []
# Try to find the most popular option
for option_key, option in options.items():
option["is_popularity_winner"] = (option["vote_count"] >= winner_treshold)
if option["is_popularity_winner"]:
found_winner = True
winners.append(option_key)
# If more than 1 winners, delete one randomly
if found_winner:
if len(winners) == 1:
steps.append(
render_to_string(
"rv_voting_calc/steps/found_popularity_winner.html",
{
"option_key": winners[0],
"option": options[winners[0]],
},
request=request,
)
)
return options, True
else:
winners_to_eliminate = random.choices(winners, k=len(winners) - 1)
for eliminated_winner in winners_to_eliminate:
steps.append(
render_to_string(
"rv_voting_calc/steps/removed_option.html",
{
"iteration": iteration,
"option_key": eliminated_winner,
"option": options[eliminated_winner],
"randomly": True
},
request=request,
)
)
del options[eliminated_winner]
steps.append(
render_to_string(
"rv_voting_calc/steps/found_popularity_winner.html",
{
"option_key": winners[0],
"option": options[winners[0]]
},
request=request,
)
)
return options, True
## END Step C
## BEGIN Step D
# If no most popular option was found, remove the least popular one
most_unpopular_options = []
for option_key, option in options.items():
if (
option["vote_count"] < highest_vote_count
and option["vote_count"] == lowest_vote_count
):
most_unpopular_options.append(option_key)
if len(most_unpopular_options) == 1:
steps.append(
render_to_string(
"rv_voting_calc/steps/removed_option.html",
{
"iteration": iteration,
"option_key": option_key,
"option": option
},
request=request,
)
)
del options[most_unpopular_options[0]]
if len(options) > 1:
# Continue through to next loop
return options, False
steps.append(
render_to_string(
"rv_voting_calc/steps/found_popularity_winner.html",
{
"option_key": option_key,
"option": option
},
request=request,
)
)
return options, True
# Remove options with least tickets
min_unpopular_option_ticket_count = math.inf
for unpopular_option in most_unpopular_options:
if options[unpopular_option]["ticket_count"] < min_unpopular_option_ticket_count:
min_unpopular_option_ticket_count = options[unpopular_option]["ticket_count"]
unpopular_options_to_remove = []
for unpopular_option in most_unpopular_options:
if options[unpopular_option]["ticket_count"] == min_unpopular_option_ticket_count:
unpopular_options_to_remove.append(unpopular_option)
for option in unpopular_options_to_remove:
steps.append(
render_to_string(
"rv_voting_calc/steps/removed_option.html",
{
"iteration": iteration,
"option_key": option,
"option": options[option]
},
request=request,
)
)
del options[option]
## END Step D
if len(options) == 0:
steps.append(
render_to_string(
"rv_voting_calc/steps/no_winner.html",
{},
request=request,
)
)
return options, True
#print(options)
#import time
#time.sleep(1)
return options, False
def get_calculated_votes(request):
options_by_member = request.GET.get("votes")
......@@ -118,7 +370,8 @@ def get_calculated_votes(request):
# END Input validation
# BEGIN Sorting
## BEGIN Step A
# Sorting
steps = []
......@@ -144,6 +397,7 @@ def get_calculated_votes(request):
"ticket_count": 0,
"vote_count": 0,
"points": 0,
"is_popularity_winner": False,
"votes": [],
"has_support": False
}
......@@ -160,19 +414,17 @@ def get_calculated_votes(request):
option_is_ticket = False
# END Sorting
# BEGIN Filtering out options without enough support and figuring out
# Filtering out options without enough support and figuring out
# the most popular options
options_to_remove = []
options_without_support = []
for option_key, option in options.items():
if option["vote_count"] >= has_support_treshold:
option["has_support"] = True
if not option["has_support"]:
options_to_remove.append(option_key)
options_without_support.append(option_key)
continue
option["points"] += option["ticket_count"] * (max_vote_position + 1)
......@@ -194,52 +446,26 @@ def get_calculated_votes(request):
)
}
for option in options.values():
first_step_options = options.copy()
for option in first_step_options.values():
votes_to_remove = []
for vote_list_position, vote in enumerate(option["votes"]):
if vote.get("was_moved", False):
continue
for other_acceptable_option_position, other_acceptable_option in (
enumerate(options_by_member[vote["member"]])
):
if not options[other_acceptable_option]["has_support"]:
continue
if (
other_acceptable_option_position >= vote["position"]
and option["has_support"]
):
break
already_voted_for_acceptable_option = False
for other_acceptable_option_vote in options[other_acceptable_option]["votes"]:
if other_acceptable_option_vote["member"] == vote["member"]:
already_voted_for_acceptable_option = True
if already_voted_for_acceptable_option:
if vote["position"] != 0:
votes_to_remove.append(vote_list_position)
break
vote["was_moved"] = True
options[other_acceptable_option]["votes"].append(vote)
break
index_offset = 0
for vote_list_position in votes_to_remove:
option["votes"].pop(vote_list_position - index_offset)
index_offset += 1
steps.append(
render_to_string(
"rv_voting_calc/steps/initial_sort.html",
{
"options": options,
"options": first_step_options,
"options_by_member": options_by_member,
"rv_members": rv_members,
"total_ticket_count": total_ticket_count,
......@@ -249,8 +475,7 @@ def get_calculated_votes(request):
)
)
for option_key in options_to_remove:
options.pop(option_key)
del first_step_options
steps.append(
render_to_string(
......@@ -264,50 +489,24 @@ def get_calculated_votes(request):
)
)
# END Filtering out options without enough support and figuring out
# the most popular options
return HttpResponse("\n".join(steps))
#acceptable_options = []
#voting_member_count = len(options_by_member)
#for option, members in members_by_option.items():
#if len(members) >= math.floor(voting_member_count * 0.5):
#acceptable_options.append(option)
#steps = []
#if len(acceptable_options) == 0:
#result = {
#"type": "final:no_acceptable_options",
#"voting_member_count": voting_member_count,
#"options": [],
#}
## END Step A
#for option, members in members_by_option.items():
#result["options"].append({
#"name": option,
#"vote_count": len(members),
#})
iteration = 0
#steps.append(result)
#return JsonResponse(steps, safe=False)
#acceptable_options_step = {
#"type": "progress:acceptable_options",
#"voting_member_count": voting_member_count,
#"options": []
#}
#for option, members in members_by_option.items():
#if option in acceptable_options:
#acceptable_options_step["options"].append({
#"name": option,
#"vote_count": len(members),
#})
while True:
options, ended = do_step_b_through_d(
request,
iteration,
steps,
options,
options_by_member,
options_without_support,
rv_members,
has_support_treshold,
)
#steps.append(acceptable_options_step)
if ended:
return HttpResponse("\n".join(steps))
# END Acceptable options
# Step E
iteration += 1
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment