Skip to content

Commit

Permalink
Initial "layers of state" app
Browse files Browse the repository at this point in the history
  • Loading branch information
symroe committed Jul 1, 2024
1 parent 2fb1de6 commit d8b63bc
Show file tree
Hide file tree
Showing 49 changed files with 1,078 additions and 40 deletions.
118 changes: 118 additions & 0 deletions wcivf/apps/administrations/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from enum import Enum

WEIGHT_MAP = {
"parl": -100,
"mayor": -50,
"local-authority": 0,
"gla": 50,
"senedd": 50,
"pcc": 100,
}


class PostTypes(Enum):
GLA_A = "GLA_A" # GLA additional
GLA_C = "GLA_C" # GLA constituencies
LONDON_ALDERMAN = "LONDON_ALDERMAN" # Who knows
DIW = "DIW" # District ward
MTW = "MTW" # Metropolitan district ward
CED = "CED" # Council ward
UTW = "UTE" # Unitary Authority ward
UTE = "UTE" # Unitary Authority electoral division
LBW = "LBW" # London before Wicket
MAYOR = "MAYOR" # TODO: do we need CA Mayor too?
WMC = "WMC" # Westminster constituency
SPC = "SPC" # Scottish Parliament constituency
SPE = "SPE" # Scottish Parliament region
WAC = "WAC" # Welsh Assembly constituency
WAE = "WAE" # Welsh Assembly region
PCC = "PCC" # Police and Crime Commissioner
PFCC = "PFCC" # Police, Fire and Crime Commissioner
LGE = "LGE" # Northern Irish Council electoral area
NIE = "NIE" # Northern Ireland Assembly constituency

@classmethod
def from_administration_data(cls, data):
if data["organisation"]["official_identifier"] == "gla":
if data["subtype"]["election_subtype"] == "a":
return cls.GLA_A
if data["subtype"]["election_subtype"] == "c":
return cls.GLA_C

if division := data.get("division", None):
division_type = division["division_type"]
if value := getattr(cls, division_type.upper(), None):
return value

if data["organisation"]["organisation_type"] == "police-area":
if "fire" in data["elected_title"]:
return cls.PFCC
return cls.PCC

if data.get("election_type", {}).get("election_type") == "mayor":
return cls.MAYOR

return None


POST_TYPE_TO_NAME = {
PostTypes.LONDON_ALDERMAN: ("Alderman",),
PostTypes.GLA_C: ("Assembly Member", "Assembly Members"),
PostTypes.GLA_A: (
"Assembly Member (additional)",
"Assembly Members (additional)",
),
PostTypes.NIE: (
"member of the Legislative Assembly",
"members of the Legislative Assembly",
),
PostTypes.DIW: ("local Councillor", "local Councillors"),
PostTypes.LBW: ("local Councillor", "local Councillors"),
PostTypes.MTW: ("local Councillor", "local Councillors"),
PostTypes.UTW: ("local Councillor", "local Councillors"),
PostTypes.LGE: ("local Councillor", "local Councillors"),
PostTypes.UTE: ("local Councillor", "local Councillors"),
PostTypes.CED: ("County Councillor", "County Councillors"),
PostTypes.MAYOR: ("Mayor",),
PostTypes.WMC: ("member of Parliament",),
PostTypes.SPC: ("member of the Scottish Parliament",),
PostTypes.SPE: (
"member of the Scottish Parliament",
"members of the Scottish Parliament",
),
PostTypes.WAC: ("member of the Senedd",),
PostTypes.WAE: ("member of the Senedd",),
PostTypes.PCC: ("Police and Crime Commissioner",),
PostTypes.PFCC: ("Police, Fire and Crime Commissioner",),
}
ORG_ID_TO_MAYOR_NAME = {
"BDF": "Mayor of Bedford",
"BST": "Mayor of Bristol",
"CPCA": "Mayor of Cambridgeshire and Peterborough Combined Authority",
"CRY": "Mayor of Croydon",
"DNC": "Mayor of Doncaster",
"EMCA": "Mayor of East Midlands Combined County Authority",
"GMCA": "Mayor of Greater Manchester Combined Authority",
"HCK": "Mayor of Hackney",
"LCE": "Mayor of Leicester",
"LCR": "Mayor of Liverpool City Region Combined Authority",
"LEW": "Mayor of Lewisham",
"LIV": "Mayor of Liverpool",
"london": "Mayor of London",
"MAS": "Mayor of Mansfield",
"MDB": "Mayor of Middlesbrough",
"NEMC": "Mayor of North East Combined Authority",
"north-of-tyne": "Mayor of North of Tyne Combined Authority",
"NTY": "Mayor of North Tyneside",
"NWM": "Mayor of Newham",
"SCR": "Mayor of Sheffield City Region Combined Authority",
"SLF": "Mayor of Salford",
"TOB": "Mayor of Torbay",
"TVCA": "Mayor of Tees Valley Combined Authority",
"TWH": "Mayor of Tower Hamlets",
"WAT": "Mayor of Watford",
"WECA": "Mayor of West of England Combined Authority",
"west-yorkshire": "Mayor of West Yorkshire Combined Authority",
"WMCA": "Mayor of West Midlands Combined Authority",
"YNYC": "Mayor of York and North Yorkshire Combined Authority",
}
215 changes: 196 additions & 19 deletions wcivf/apps/administrations/helpers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import json
from pathlib import Path
from typing import Dict, List
from functools import cached_property
from typing import List

BASE_DATA_PATH = Path("/home/symroe/Data/StaticOrgLayer/")
import requests
from administrations.constants import (
ORG_ID_TO_MAYOR_NAME,
POST_TYPE_TO_NAME,
WEIGHT_MAP,
PostTypes,
)
from core.utils import LastWord
from django.db.models.functions import Coalesce
from django.template.loader import select_template
from elections.models import PostElection


def get_weight(id_str, weight_map):
# Split the ID by "::" and check each segment for a key in weight_map
for part in id_str.split("::"):
# If the part contains ":", split by ":" and check the first segment
key = part.split(":")[0] if ":" in part else part

if key in weight_map:
return weight_map[key]

# Return a default weight if no key is found
return 0


class Administration:
def __init__(self, admin_id, data: Dict):
def __init__(self, admin_id: str):
self.admin_id = admin_id
data = self.load_json(admin_id)
self.data = data
self.post_type = PostTypes.from_administration_data(data)

def load_json(self, administration_id):
req = requests.get(
f"https://s3.eu-west-2.amazonaws.com/ee.public.data/layers-of-state/administrations_json/{self.admin_id}.json"
)
return req.json()

@property
def administration_type(self):
Expand All @@ -21,10 +51,21 @@ def role_type(self):
return self.admin_id.split("::")[-1]

def friendly_name(self):
org_name = self.data["organisation"]["common_name"]
org_name = self.data["organisation"]["official_name"]

if self.post_type in [PostTypes.GLA_A, PostTypes.GLA_C]:
return "London Assembly"

if self.post_type in [PostTypes.WAC, PostTypes.WAE]:
return "Senedd Cymru"

if self.post_type in [PostTypes.SPC, PostTypes.SPE]:
return "Scottish Parliament"

if self.administration_type == "organisation":
if self.role_type == "mayor":
if self.data["organisation"]["official_identifier"] == "london":
return "London Assembly"
org_name = f"Mayor of {org_name}"

return org_name
Expand All @@ -34,25 +75,161 @@ def friendly_name(self):
div_name = f"{div_name} ward"

if self.role_type == "parl":
return f"MP for {div_name}"
return f"{div_name} parliamentary constituency"
return f"{org_name}: {div_name}"

def seats_contested(self): ...
def role_name(self):
name = None

index = 0
if self.seats_total > 1:
index = -1
if self.post_type in [PostTypes.GLA_A, PostTypes.SPE]:
index = -1
role_name = POST_TYPE_TO_NAME.get(self.post_type, [name])[index]

if self.post_type == PostTypes.MAYOR:
org_id = self.data["organisation"]["official_identifier"]
if org_id in ORG_ID_TO_MAYOR_NAME:
return ORG_ID_TO_MAYOR_NAME[org_id]

if self.post_type in [
PostTypes.GLA_C,
PostTypes.WAC,
PostTypes.WAE,
PostTypes.SPE,
PostTypes.SPC,
]:
role_name = f"{role_name} for {self.data['division']['name']}"

return role_name

@cached_property
def seats_total(self):
if "division" in self.data:
return self.data["division"]["seats_total"]

return 1

def friendly_description(self):
return "asd"
def responsibilities_template(self):
return select_template(
[
f"responsibilities/single_id/{self.admin_id}.html",
f"responsibilities/post_type/{self.post_type.name}.html",
f"responsibilities/org_id/{self.data['organisation']['official_identifier']}.html",
f"responsibilities/org_type/{self.data['organisation']['organisation_type']}.html",
]
)

def should_show_people(self):
if self.post_type == PostTypes.GLA_A:
return True

if self.seats_total == 1:
return True

if self.ballot_obj.winner_count == self.seats_total:
return True

return False

@cached_property
def ballot_obj(self):
ballot_id = self.data.get(
"last_division_election_id",
self.data.get(
"last_org_election_id",
),
)

if self.post_type == PostTypes.GLA_A:
date = ballot_id.split(".")[-1]
ballot_id = f"gla.a.{date}"

return PostElection.objects.get(ballot_paper_id=ballot_id)

@cached_property
def elected_people(self):
if not self.should_show_people:
return None

qs = self.ballot_obj.personpost_set.filter(elected=True).select_related(
"party", "person"
)
return (
qs.annotate(last_name=LastWord("person__name"))
.annotate(
name_for_ordering=Coalesce("person__sort_name", "last_name")
)
.order_by("name_for_ordering", "person__name")
)

@cached_property
def weight(self):
weight_modifier = 0

org_type = self.data["organisation"]["organisation_type"]
if self.admin_id == "O::london::mayor":
org_type = "gla"
if self.role_type == "mayor":
weight_modifier = -10

if self.post_type in [PostTypes.GLA_A, PostTypes.WAE, PostTypes.SPE]:
weight_modifier = 10
weight = WEIGHT_MAP.get(org_type, 1)
return weight + weight_modifier


class AdministrationsHelper:
def __init__(self, administration_ids: List[str]):
self.administration_ids = administration_ids
self.data_path = BASE_DATA_PATH
def __init__(self, postcode: str):
self.postcode = postcode
self.administration_ids = self.lookup_administration_ids(self.postcode)
self.administrations: List[Administration] = []
IGNORE_IDS = [
"O::LIV::mayor",
"O::TOB::mayor",
"O::BST::mayor",
]
for admin_id in self.administration_ids:
data = self.load_json(admin_id)
self.administrations.append(Administration(admin_id, data))
if admin_id in IGNORE_IDS:
continue
self.administrations.append(Administration(admin_id))

def load_json(self, administration_id):
path = self.data_path / f"{administration_id}.json"
with path.open() as f:
return json.load(f)
self.administrations = sorted(
self.administrations, key=lambda admin: admin.weight
)

def lookup_administration_ids(self, postcode):
if postcode == "GL5 1NA":
return [
"D::GLS::2021-05-06::unit_id:42821::local",
"D::STO::2016-04-13::gss:E05010988::local",
"D::parl-hoc::2024-07-04::gss:E14001529::parl",
"O::gloucestershire::pcc",
]
if postcode == "DG8 8NH":
return [
"D::DGY::2017-05-04::gss:S13002881::local",
"D::parl-hoc::2024-07-04::gss:S14000073::parl",
"D::sp::2016-04-13::gss:S16000114::sp::c",
"D::sp::2016-04-13::gss:S17000015::sp::r",
]
if postcode == "SE22 8DJ":
return [
"D::SWK::2018-05-03::gss:E05011097::local",
"D::gla::2004-12-02::gss:E32000010::gla::c",
"D::parl-hoc::2024-07-04::gss:E14001205::parl",
"O::gla::gla::a",
"O::london::mayor",
]

return []
# TODO: clean up
# req = requests.get(
# f"http://localhost:3000/rpc/get_identifiers_by_postcode?postcode={postcode}"
# )
# ids = []
#
# for obj in req.json():
# ids.append(obj["identifier"])
# return ids
Loading

0 comments on commit d8b63bc

Please sign in to comment.