Merge pull request 'feat(search): phases in the order a child meets them' (#174) from feat/phase-order-child-path into main
Stage (build -> staging -> E2E gate) / prepare (push) Successful in 1s
Stage (build -> staging -> E2E gate) / Build Backend (FastAPI) (push) Successful in 20s
Stage (build -> staging -> E2E gate) / Build Frontend (Next.js) (push) Successful in 1m25s
Stage (build -> staging -> E2E gate) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 15s
Stage (build -> staging -> E2E gate) / Deploy to Staging (push) Successful in 31s
Stage (build -> staging -> E2E gate) / E2E Journeys against Staging (push) Successful in 3m10s
Stage (build -> staging -> E2E gate) / prepare (push) Successful in 1s
Stage (build -> staging -> E2E gate) / Build Backend (FastAPI) (push) Successful in 20s
Stage (build -> staging -> E2E gate) / Build Frontend (Next.js) (push) Successful in 1m25s
Stage (build -> staging -> E2E gate) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 15s
Stage (build -> staging -> E2E gate) / Deploy to Staging (push) Successful in 31s
Stage (build -> staging -> E2E gate) / E2E Journeys against Staging (push) Successful in 3m10s
Reviewed-on: #174
This commit was merged in pull request #174.
This commit is contained in:
commit
dd5b48e612
4 files changed
+58
-4
No files matched your search
+10
-4
@@ -40,7 +40,7 @@ from .data_loader import (
|
|||||||
from .data_loader import get_data_info as get_db_info
|
from .data_loader import get_data_info as get_db_info
|
||||||
from . import flags
|
from . import flags
|
||||||
from .places import build_place_index, build_place_registry, places_for_urn
|
from .places import build_place_index, build_place_registry, places_for_urn
|
||||||
from .schemas import METRIC_DEFINITIONS, PHASE_GROUPS, RANKING_COLUMNS, SCHOOL_COLUMNS
|
from .schemas import METRIC_DEFINITIONS, PHASE_GROUPS, PHASE_ORDER, RANKING_COLUMNS, SCHOOL_COLUMNS
|
||||||
from .school_groups import (
|
from .school_groups import (
|
||||||
FAITH_GROUPS,
|
FAITH_GROUPS,
|
||||||
FAITH_KEYS,
|
FAITH_KEYS,
|
||||||
@@ -381,6 +381,12 @@ def clean_filter_values(series: pd.Series) -> list[str]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def order_phases(phases: list[str]) -> list[str]:
|
||||||
|
"""Phases in the order a child meets them; any GIAS adds later follow, A-Z."""
|
||||||
|
rank = {p: i for i, p in enumerate(PHASE_ORDER)}
|
||||||
|
return sorted(phases, key=lambda p: (rank.get(p.lower(), len(rank)), p))
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# SECURITY MIDDLEWARE & HELPERS
|
# SECURITY MIDDLEWARE & HELPERS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -937,7 +943,7 @@ async def get_schools(
|
|||||||
result_filters = {
|
result_filters = {
|
||||||
"local_authorities": clean_filter_values(schools_df["local_authority"]) if "local_authority" in schools_df.columns else [],
|
"local_authorities": clean_filter_values(schools_df["local_authority"]) if "local_authority" in schools_df.columns else [],
|
||||||
"school_types": clean_filter_values(schools_df["school_type"]) if "school_type" in schools_df.columns else [],
|
"school_types": clean_filter_values(schools_df["school_type"]) if "school_type" in schools_df.columns else [],
|
||||||
"phases": clean_filter_values(schools_df["phase"]) if "phase" in schools_df.columns else [],
|
"phases": order_phases(clean_filter_values(schools_df["phase"])) if "phase" in schools_df.columns else [],
|
||||||
"genders": clean_filter_values(schools_df.loc[_sec_mask, "gender"]) if "gender" in schools_df.columns and _sec_mask.any() else [],
|
"genders": clean_filter_values(schools_df.loc[_sec_mask, "gender"]) if "gender" in schools_df.columns and _sec_mask.any() else [],
|
||||||
"admissions_policies": clean_filter_values(schools_df.loc[_sec_mask, "admissions_policy"]) if "admissions_policy" in schools_df.columns and _sec_mask.any() else [],
|
"admissions_policies": clean_filter_values(schools_df.loc[_sec_mask, "admissions_policy"]) if "admissions_policy" in schools_df.columns and _sec_mask.any() else [],
|
||||||
}
|
}
|
||||||
@@ -1207,8 +1213,8 @@ async def get_filter_options(request: Request):
|
|||||||
"faiths": [],
|
"faiths": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Phases: return values from data, ordered sensibly
|
# Phases: the values in the data, in the order a child meets them
|
||||||
phases = clean_filter_values(df["phase"]) if "phase" in df.columns else []
|
phases = order_phases(clean_filter_values(df["phase"])) if "phase" in df.columns else []
|
||||||
|
|
||||||
secondary_df = df[df["attainment_8_score"].notna()] if "attainment_8_score" in df.columns else df.iloc[0:0]
|
secondary_df = df[df["attainment_8_score"].notna()] if "attainment_8_score" in df.columns else df.iloc[0:0]
|
||||||
genders = clean_filter_values(secondary_df["gender"]) if "gender" in secondary_df.columns else []
|
genders = clean_filter_values(secondary_df["gender"]) if "gender" in secondary_df.columns else []
|
||||||
|
|||||||
@@ -544,6 +544,19 @@ PHASE_GROUPS: dict[str, set[str]] = {
|
|||||||
"all-through": {"all-through"},
|
"all-through": {"all-through"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# GIAS phases in the order a child meets them, for the phase filter's options.
|
||||||
|
# All-through spans the whole path, so it follows the stages. Lowercased, as
|
||||||
|
# PHASE_GROUPS is, so a change of case in the GIAS label keeps its place.
|
||||||
|
PHASE_ORDER: list[str] = [
|
||||||
|
"nursery",
|
||||||
|
"primary",
|
||||||
|
"middle deemed primary",
|
||||||
|
"middle deemed secondary",
|
||||||
|
"secondary",
|
||||||
|
"16 plus",
|
||||||
|
"all-through",
|
||||||
|
]
|
||||||
|
|
||||||
# School listing columns
|
# School listing columns
|
||||||
SCHOOL_COLUMNS = [
|
SCHOOL_COLUMNS = [
|
||||||
"urn",
|
"urn",
|
||||||
|
|||||||
@@ -82,3 +82,25 @@ def test_grouped_phases_still_take_in_their_related_phases(client):
|
|||||||
|
|
||||||
def test_an_unknown_phase_returns_nothing_rather_than_everything(client):
|
def test_an_unknown_phase_returns_nothing_rather_than_everything(client):
|
||||||
assert _urns(client, "kindergarten") == []
|
assert _urns(client, "kindergarten") == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_filters_lists_phases_in_the_order_a_child_meets_them(client):
|
||||||
|
# Alphabetical put "16 plus" and "All-through" first and Nursery fifth.
|
||||||
|
# All-through spans the whole path, so it comes after the stages.
|
||||||
|
assert client.get("/api/filters").json()["phases"] == [
|
||||||
|
"Nursery",
|
||||||
|
"Primary",
|
||||||
|
"Middle deemed primary",
|
||||||
|
"Middle deemed secondary",
|
||||||
|
"Secondary",
|
||||||
|
"16 plus",
|
||||||
|
"All-through",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_an_unknown_phase_follows_the_known_ones():
|
||||||
|
from backend.app import order_phases
|
||||||
|
|
||||||
|
assert order_phases(["Secondary", "Zeta", "Alpha", "Nursery"]) == [
|
||||||
|
"Nursery", "Secondary", "Alpha", "Zeta",
|
||||||
|
]
|
||||||
@@ -286,6 +286,19 @@ test('the phase filter switches straight from secondary to primary', async ({ pa
|
|||||||
await expect(phase).toHaveValue('primary');
|
await expect(phase).toHaveValue('primary');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the phase filter lists phases in the order a child meets them', async ({ page }) => {
|
||||||
|
// They were alphabetical, so "16 plus" and "All-through" came before Nursery.
|
||||||
|
const childPath = ['Nursery', 'Primary', 'Middle deemed primary',
|
||||||
|
'Middle deemed secondary', 'Secondary', '16 plus', 'All-through'];
|
||||||
|
await page.goto('/?search=school');
|
||||||
|
const phase = page.getByRole('combobox', { name: 'Phase' });
|
||||||
|
await expect(phase).toBeVisible({ timeout: 15_000 });
|
||||||
|
const offered = (await phase.locator('option').allTextContents())
|
||||||
|
.filter((o) => childPath.includes(o));
|
||||||
|
expect(offered, 'no phase on offer').toContain('Primary');
|
||||||
|
expect(offered).toEqual(childPath.filter((p) => offered.includes(p)));
|
||||||
|
});
|
||||||
|
|
||||||
test('school type and gender switch straight to another value', async ({ page }) => {
|
test('school type and gender switch straight to another value', async ({ page }) => {
|
||||||
// Their options came from the result set, which the filter had already
|
// Their options came from the result set, which the filter had already
|
||||||
// narrowed, so with one value chosen it was the only one on offer.
|
// narrowed, so with one value chosen it was the only one on offer.
|
||||||
|
|||||||
Reference in new issue
Block a user