105 lines
4.7 KiB
Python
105 lines
4.7 KiB
Python
"""Parent-facing groups over GIAS establishment types and religious characters.
|
|
|
|
The 34 GIAS establishment types describe governance and funding, which for a
|
|
mainstream state school barely changes what a parent experiences. The search
|
|
filter offers six groups a parent recognises instead, and a faith filter in
|
|
place of the faith signal that "Voluntary aided" and "Voluntary controlled"
|
|
only half carry. See
|
|
docs/superpowers/specs/2026-10-02-school-type-groups-and-faith-filter-design.md.
|
|
|
|
Groups are defined over GIAS codes and looked up by the translated name,
|
|
because the DataFrame the API filters carries names only: the codes are
|
|
replaced at load (data_loader.translate_gias_code_columns), the legacy-name
|
|
mart fallback never had them, and test fixtures are written in names.
|
|
"""
|
|
|
|
from typing import Optional
|
|
|
|
import pandas as pd
|
|
|
|
from .gias_codes import RELIGIOUS_CHARACTER, SCHOOL_TYPE
|
|
|
|
# (key, label, GIAS TypeOfEstablishment codes), in the order shown.
|
|
TYPE_GROUPS: tuple[tuple[str, str, frozenset[int]], ...] = (
|
|
# Academy sponsor led, Academy converter, Free schools, University technical
|
|
# college, Studio schools, City technology college. UTCs and studio schools
|
|
# are legally academies, and a family considering one searches by name.
|
|
("academy", "State school: academy or free school", frozenset({28, 34, 35, 40, 41, 6})),
|
|
# Community, Voluntary aided, Voluntary controlled, Foundation, LA nursery.
|
|
("council", "State school: council-run", frozenset({1, 2, 3, 5, 15})),
|
|
("independent", "Independent (fee-paying)", frozenset({11})),
|
|
# Every special type, independent ones included (usually funded by the
|
|
# council through an EHCP, so SEND provision to a parent, not private
|
|
# school), and Special post 16 institutions.
|
|
("special", "Special school (SEND)", frozenset({7, 8, 10, 12, 32, 33, 36, 44})),
|
|
# Further education, Sixth form centres, the 16-19 academies and free schools.
|
|
("post16", "Sixth form or college", frozenset({18, 31, 39, 45, 46})),
|
|
# Pupil referral units and AP academies and free schools. Last: parents do
|
|
# not apply to these; the local authority places children there.
|
|
("alternative", "Alternative provision", frozenset({14, 38, 42, 43})),
|
|
)
|
|
|
|
# In no group, so reachable only under "Any school type": Secure units,
|
|
# Miscellaneous, Higher education institutions, Online provider, Institution
|
|
# funded by other government department, Academy secure 16 to 19.
|
|
UNOFFERED_TYPE_CODES: frozenset[int] = frozenset({24, 27, 29, 49, 56, 57})
|
|
|
|
# (key, label, GIAS ReligiousCharacter codes), in the order shown. A joint
|
|
# school is in every faith its label names; a generic "Christian" beside a
|
|
# named church adds nothing.
|
|
FAITH_GROUPS: tuple[tuple[str, str, frozenset[int]], ...] = (
|
|
# Does not apply, None, and 99 (a blank label).
|
|
("none", "No religious character", frozenset({0, 6, 99})),
|
|
("church_of_england", "Church of England",
|
|
frozenset({2, 9, 10, 11, 12, 13, 19, 20, 30, 31, 32, 33, 34, 41, 48})),
|
|
("roman_catholic", "Roman Catholic", frozenset({3, 11, 13, 35, 48})),
|
|
# 28 "Inter- / non- denominational" is how GIAS files Christian schools
|
|
# tied to no one church.
|
|
("other_christian", "Other Christian",
|
|
frozenset({4, 8, 9, 10, 12, 14, 15, 16, 17, 18, 19, 22, 26, 28, 30, 33,
|
|
37, 38, 39, 40, 41, 44, 45, 46, 47})),
|
|
("jewish", "Jewish", frozenset({5, 36, 43})),
|
|
("muslim", "Muslim", frozenset({7, 42, 49})),
|
|
("other_faith", "Other faith", frozenset({21, 24, 25, 29})),
|
|
)
|
|
|
|
TYPE_GROUP_KEYS: frozenset[str] = frozenset(k for k, _, _ in TYPE_GROUPS)
|
|
FAITH_KEYS: frozenset[str] = frozenset(k for k, _, _ in FAITH_GROUPS)
|
|
|
|
|
|
def _key(name: str) -> str:
|
|
return name.strip().lower()
|
|
|
|
|
|
_TYPE_GROUP_BY_NAME: dict[str, str] = {
|
|
_key(SCHOOL_TYPE[code]): key
|
|
for key, _, codes in TYPE_GROUPS
|
|
for code in codes
|
|
if code in SCHOOL_TYPE
|
|
}
|
|
|
|
_FAITHS_BY_NAME: dict[str, tuple[str, ...]] = {}
|
|
for _faith, _, _codes in FAITH_GROUPS:
|
|
for _code in sorted(_codes):
|
|
if _code in RELIGIOUS_CHARACTER:
|
|
_name = _key(RELIGIOUS_CHARACTER[_code])
|
|
_FAITHS_BY_NAME[_name] = _FAITHS_BY_NAME.get(_name, ()) + (_faith,)
|
|
|
|
|
|
def type_group_for(name: object) -> Optional[str]:
|
|
"""The type group of a GIAS establishment type name, or None."""
|
|
if not isinstance(name, str):
|
|
return None
|
|
return _TYPE_GROUP_BY_NAME.get(_key(name))
|
|
|
|
|
|
def faith_groups_for(name: object) -> tuple[str, ...]:
|
|
"""The faith groups of a GIAS religious character name.
|
|
|
|
A missing or blank name is "No religious character". A name the
|
|
dictionary does not know has no faith, so it matches no faith option.
|
|
"""
|
|
if not isinstance(name, str):
|
|
return ("none",) if name is None or pd.isna(name) else ()
|
|
return _FAITHS_BY_NAME.get(_key(name), ())
|