Dear Tim,
The final pieces of Kalmany are coming together. We’re now at the precipice, close to jumping over the edge and letting the elections run. So close, so very close. Today I finished up the work to do my policy implementation logic. So let’s talk about that shall we; how will Kalmany operate its daily election cycle.
At 08:00 GMT, the script prepareElection.py will run. AS per its name, we’ll prepare for an election by doing some busywork – first we creation an election ID from the year, month, and day. (I’ve always liked the 202201017 format, because sorting files is so much easier.) As there’s only going to be one election per day, this logic will work perfectly, however if I ever need to run multiple elections a day this won’t break other logic present in the system. It doesn’t rely on the fact one election occurs each day but it DOES rely on the promise that one and only one will run at any one time.
The election object (as we shall call it, even though it’s a database object) has three parameters to help with determining further steps; a start date, an end date, and an active boolean for the current status. When the prepareElection.py script runs, it will create the record with a start date, a null end date and leave the active boolean false. Then it will begin the task of arranging potential candidates.
I’ve mentioned before we have citizens in Kalmany – each with an array of distinct values. These values range to ethnicity from age. They are imperative to building the logic of a citizen’s affinity but we’ll leave that for now. What’s important is that, when I prepare an election, I’m ensuring I have my candidates ready for voting. (At this point in the future, we may take into account any campaigning values). First, we go through each candidate and determine how popular they are; we’ll look at how many votes they get on average. If they dip below 5% (around 50 votes), then they’re going to decide to stand down. Their candidate.running status will switch to False and they will become an inactive record, in case they ever want to run again. I could create all candidate records off the bat, but I decided that I would create them when needed – might need adjusting in the future.
However, if they do run again, they will regenerate their ten policies they want to run with – very important. Their entire platform may change depending on how the constituency is looking. They’ll have their own personal preference, but the environment will have an impact! Then we cycle through each citizen and determine if they like anyone who is still in the running. If not, that citizen will stand for election and either have their candidate re-activated, or a new one created. This can mean a candidate could step down and then immediately step up. Luckily, the likelihood is low as I cycle through the citizens in a random order. Currently, this process takes seven minutes on my home machine, but the live version will expect this to take anywhere up to an hour.
After this, I catalogue each candidate into a separate table to say they are standing in this new election, with a vote_tally of 0 (to track their votes). And we wait.
At 09:00 GMT, we begin the election with runElection.py. We get the most recent election object that hasn’t ended and checks it isn’t active – if so, we make it active. We cycle through all citizens yet again, randomly, and they rank the candidates in order of how much they like them based off personal preference and all the policies they run with. The highest preference gets their vote. Easy right? Well it means that it’s running logic against ten policies and then the candidate’s citizen attributes. That’s eleven comparison functions happening. Twenty-four thousand times. This process takes around sixteen minutes, but I’m going to elongate this process so it continues over six hours, hopefully finishing just after 15:00 GMT. Now it doesn’t update the voting tally after every single vote, this takes far too long. Instead, we collect the votes into an object and after a thousand we do a bulk update onto the DB. At each hundred votes, we can also run some logic to analyse the running i.e. see who’s winning, who’s losing. This information gets piped to the newsfeed which then may appear on the ticker tape on my site, giving a heads up display on how things are going. After some testing, because the citizens are picked randomly, very rarely does the ranking switch halfway through a race. This can make it a little unexciting, but the head-to-heads are the real nail biters, just like in real-life. After it’s all done, we tidy up by giving an end date to the election object and setting it inactive again.
This way our election object has three states – preparation (end date is null, is inactive), running (end date is null, is active), and finished (end date is populated, is inactive). If the end date is populated but is still active, the code has errored.
Then we get onto the assembly. At 17:00 GMT, we form an assembly using formAssembly.py. We take the highest vote count on each constituency and select that candidate to be part of our assembly. Our assembly object is pretty much exactly the same as our election object – a start date, end date, and an active boolean. The candidate id is written with the assembly id and then we also pick the candidate’s favourite policy to be the policy it takes to the assembly – this is decided before they enter the assembly! This is so I can be certain they don’t pick the same policy as someone else – you can’t twice on the same policy! If their highest preference is already picked, they work through the list. Just like the election object, we set the assembly object’s start date and leave the end date null and it inactive.
Then we move onto runAssembly.py at 18:00 GMT. The assembly I’ve already talked about; we cycle through each member, they bring their policy to discuss, everyone argues, then they cast a vote. The individual votes are recorded, and then the end result is also recorded. This allows me to provide a history on each member’s voting in the assemblies, and also calculate a value for the candidates trustworthiness; how often have they voted in the direction of their personal preference. Because they can enter arguments and get convinced otherwise, we get an interesting commentary on how no politician is trustworthy even when they’re in a system like this. Afterwards, we set the end date, the active status returns to inactive and we cleanup.
Now, we get to policy implementation. The assembly runs for about an hour at the moment (at three seconds between each member’s action enforced – without it takes less than a minute), we set off at 21:00 GMT the script implementPolicies.py which takes every result and does the hard process of mapping each policy to its unique function: this includes moving citizens around constituencies, changing earnings, changing spending, changing things that influence personal preference, affecting constituency attributes, and so on. After that’s all done, we ask each constituency to update their ratings; scores attributed to each sector of government that go in to influence was policies people care about. These rely on a multitude of factors but boil down to who is in what industry, what resources the constituency has. It also tallies how much each constituency is spending but that value I don’t care about. It tallies into the country’s debt. And I fully expect it to just keep rising.
And then we finish. The site has had access to this information all the way through – transparent on how the elections are panning out and how the assembly runs. In the end, we get an update to the ranking scores and all goes quiet, ready for the process to reignite the next day.
That’s how it will work. My idea was to use lambda functions, but after realising I’m going to artificially prolong these processes, I didn’t want to spend extra money when the function is effectively just waiting. Instead, I’m whipping up an EC2 instance and setting up a cronjob to run each section at its appointed time. In the end, it will work out better for me, especially if I have the instance shutdown during the night and during Sunday, my appointed rest day.
Once it’s running, we can tidy up the frontend – have a page for each candidate to display their personal profiles, their voting history, their candidate attributes, and the policies in their platform. And then we can set-up the process to log in and be able to register as a citizen and cast your own votes! Be a member of the political system.
And THEN, we can talk about monuments. The prizes. Little contests each week to gain certain entitlements and bring a little flavour to each constituency. I’ll leave that for now. I’m sure you’re eager to know the real logic behinds everything; how do I determine how much a citizen likes a candidate? How do the ratings get calculated? What do the policies really, specifically, do?
I’ll talk about that another time. I’m still sitting here scared that my system is about to run automatically on schedule. How’s that going to go? Only time will tell…
Anyway, I hear that the Swiss have brought in the army. You know that General Keller will be there right? Steer clear of him. He never forgave you for the black eye, nor the white eye. You’ll need to be in top shape so keep doing that yoga – whatever they taught you in Boston seems to working. I know you can do it.
Yours,
Stan
