diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..cc68ec3
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+*.mp4 filter=lfs diff=lfs merge=lfs -text
+*.mov filter=lfs diff=lfs merge=lfs -text
+*.webm filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
diff --git a/Makefile.data b/Makefile.data
index af3a1f3..c9c9a85 100644
--- a/Makefile.data
+++ b/Makefile.data
@@ -32,7 +32,7 @@ PRICES_STAMP := $(DATA_DIR)/.prices_done
EPC := $(MANUAL_DATA)/domestic-csv.zip
ACTUAL_LISTINGS_RAW := $(FINDER_DATA)/online_listings_buy.parquet
ACTUAL_LISTINGS_ENRICHED := $(FINDER_DATA)/online_listings_buy_enriched.parquet
-ETHNICITY := $(DATA_DIR)/ethnicity_by_la.parquet
+ETHNICITY := $(DATA_DIR)/ethnicity_by_lsoa.parquet
CRIME_DIR := $(DATA_DIR)/crime
CRIME := $(DATA_DIR)/crime_by_postcode.parquet
CRIME_BY_YEAR := $(DATA_DIR)/crime_by_postcode_by_year.parquet
@@ -48,7 +48,8 @@ NAPTAN := $(DATA_DIR)/naptan.parquet
BROADBAND := $(DATA_DIR)/broadband.parquet
CONSERVATION_AREAS := $(DATA_DIR)/conservation_areas.geojson
LISTED_BUILDINGS := $(DATA_DIR)/listed_buildings.gpkg
-SCHOOL_PROX := $(DATA_DIR)/school_proximity.parquet
+SCHOOL_CATCH := $(DATA_DIR)/school_catchments.parquet
+LSOA_CHILDREN := $(DATA_DIR)/lsoa_children.parquet
RENTAL := $(DATA_DIR)/rental_prices.parquet
INSPIRE_DIR := $(DATA_DIR)/inspire
OA_BOUNDARIES := $(DATA_DIR)/oa_boundaries.gpkg
@@ -100,19 +101,19 @@ PC_BOUNDARIES_DEPS := pipeline/transform/postcode_boundaries/__main__.py \
pipeline/transform/postcode_boundaries/voronoi.py
CRIME_DOWNLOAD_DEPS := pipeline/download/crime.py
INSPIRE_DOWNLOAD_DEPS := pipeline/download/inspire.py
-TRANSIT_DOWNLOAD_DEPS := pipeline/download/transit_network.py pipeline/download/transxchange2gtfs_shim.js
+TRANSIT_DOWNLOAD_DEPS := pipeline/download/transit_network.py
MAP_ASSETS_DEPS := pipeline/download/map_assets.py pipeline/transform/transform_poi.py
# ── Phony aliases ─────────────────────────────────────────────────────────────
.PHONY: prepare merge tiles satellite-tiles satellite-highres-tiles overlay-tiles noise-overlay-tiles crime-hotspot-tiles tree-overlay-tiles property-border-tiles \
download-arcgis download-price-paid download-deprivation download-ethnicity \
- download-naptan download-pois download-grocery-retail-points download-ofsted download-gias download-broadband download-conservation-areas download-listed-buildings download-rental-prices \
+ download-naptan download-pois download-grocery-retail-points download-ofsted download-gias download-lsoa-children download-broadband download-conservation-areas download-listed-buildings download-rental-prices \
download-postcodes download-noise download-inspire download-crime \
download-oa-boundaries download-uprn-lookup download-transit-network download-greenspace download-os-greenspace download-pbf download-fr-tow download-nfi download-ofs-register download-places download-median-age download-england-boundary download-rightmove-outcodes \
download-map-assets \
transform-pois transform-epc-pp transform-crime transform-poi-proximity \
- transform-school-proximity transform-tree-density \
+ transform-school-catchments transform-tree-density \
generate-postcode-boundaries generate-travel-times enrich-actual-listings
prepare: $(PRICES_STAMP) download-places tiles satellite-tiles overlay-tiles property-border-tiles tree-overlay-tiles crime-hotspot-tiles property-border-tiles generate-postcode-boundaries download-map-assets generate-travel-times | $(POSTCODES_PQ) $(PROPERTIES_PQ) $(PRICE_INDEX)
@@ -139,6 +140,7 @@ download-pois: $(POIS_RAW)
download-grocery-retail-points: $(GROCERY_RETAIL_POINTS)
download-ofsted: $(OFSTED)
download-gias: $(GIAS)
+download-lsoa-children: $(LSOA_CHILDREN)
download-broadband: $(BROADBAND)
download-conservation-areas: $(CONSERVATION_AREAS)
download-listed-buildings: $(LISTED_BUILDINGS)
@@ -150,7 +152,7 @@ download-inspire: $(INSPIRE_STAMP)
download-oa-boundaries: $(OA_BOUNDARIES)
download-uprn-lookup: $(UPRN_LOOKUP)
download-transit-network: $(TRANSIT_STAMP)
- $(VALIDATE_OUTPUTS) --file $(TRANSIT_DIR)/raw/england.osm.pbf --zip $(TRANSIT_DIR)/bods_gtfs.zip --zip $(TRANSIT_DIR)/tfl_gtfs.zip
+ $(VALIDATE_OUTPUTS) --file $(TRANSIT_DIR)/raw/england.osm.pbf --zip $(TRANSIT_DIR)/bods_gtfs.zip
download-greenspace: $(GREENSPACE)
download-os-greenspace: $(OS_GREENSPACE)
download-pbf: $(PBF)
@@ -168,11 +170,11 @@ transform-pois: $(POIS_FILTERED)
transform-epc-pp: $(EPC_PP)
transform-crime: $(CRIME)
transform-poi-proximity: $(POI_PROXIMITY)
-transform-school-proximity: $(SCHOOL_PROX)
+transform-school-catchments: $(SCHOOL_CATCH)
transform-tree-density: $(TREE_DENSITY_PC)
generate-postcode-boundaries: $(PC_BOUNDARIES_STAMP)
-$(PC_BOUNDARIES_STAMP): $(OA_BOUNDARIES) $(INSPIRE_STAMP) $(UPRN_LOOKUP) $(ARCGIS) $(PC_BOUNDARIES_DEPS)
+$(PC_BOUNDARIES_STAMP): $(OA_BOUNDARIES) $(INSPIRE_STAMP) $(UPRN_LOOKUP) $(ARCGIS) $(GREENSPACE) $(PC_BOUNDARIES_DEPS)
@rm -f $@
$(VALIDATE_OUTPUTS) --dir $(INSPIRE_DIR) --zip-glob "$(INSPIRE_DIR)::*.zip"
uv run python -m pipeline.transform.postcode_boundaries \
@@ -180,6 +182,7 @@ $(PC_BOUNDARIES_STAMP): $(OA_BOUNDARIES) $(INSPIRE_STAMP) $(UPRN_LOOKUP) $(ARCGI
--arcgis $(ARCGIS) \
--oa-boundaries $(OA_BOUNDARIES) \
--inspire $(INSPIRE_DIR) \
+ --greenspace $(GREENSPACE) \
--output $(PC_BOUNDARIES)
$(VALIDATE_OUTPUTS) --active-postcode-boundary-match "$(ARCGIS)::$(PC_BOUNDARIES)"
@touch $@
@@ -273,6 +276,9 @@ $(OFSTED):
$(GIAS): pipeline/download/gias.py
uv run python -m pipeline.download.gias --output $@
+$(LSOA_CHILDREN): pipeline/download/lsoa_children.py
+ uv run python -m pipeline.download.lsoa_children --output $@
+
$(BROADBAND):
uv run python -m pipeline.download.broadband --output $@
@@ -315,7 +321,7 @@ $(UPRN_LOOKUP):
$(TRANSIT_STAMP): $(TRANSIT_DOWNLOAD_DEPS)
@rm -f $@
uv run python -m pipeline.download.transit_network --output $(TRANSIT_DIR)
- $(VALIDATE_OUTPUTS) --file $(TRANSIT_DIR)/raw/england.osm.pbf --zip $(TRANSIT_DIR)/bods_gtfs.zip --zip $(TRANSIT_DIR)/tfl_gtfs.zip
+ $(VALIDATE_OUTPUTS) --file $(TRANSIT_DIR)/raw/england.osm.pbf --zip $(TRANSIT_DIR)/bods_gtfs.zip
@touch $@
$(RENTAL): pipeline/download/rental_prices.py
@@ -364,8 +370,8 @@ $(CRIME) $(CRIME_BY_YEAR) &: $(CRIME_STAMP) $(PC_BOUNDARIES_STAMP) pipeline/tran
$(POI_PROXIMITY): $(ARCGIS) $(POIS_FILTERED) $(OS_GREENSPACE) $(POI_PROXIMITY_DEPS)
uv run python -m pipeline.transform.poi_proximity --arcgis $(ARCGIS) --pois $(POIS_FILTERED) --greenspace $(OS_GREENSPACE) --output $@
-$(SCHOOL_PROX): $(OFSTED) $(ARCGIS) pipeline/transform/school_proximity.py pipeline/utils/poi_counts.py
- uv run python -m pipeline.transform.school_proximity --ofsted $(OFSTED) --arcgis $(ARCGIS) --output $@
+$(SCHOOL_CATCH): $(OFSTED) $(ARCGIS) $(GIAS) $(LSOA_CHILDREN) pipeline/transform/school_catchments.py pipeline/utils/poi_counts.py
+ uv run python -m pipeline.transform.school_catchments --ofsted $(OFSTED) --arcgis $(ARCGIS) --gias $(GIAS) --lsoa-children $(LSOA_CHILDREN) --output $@
$(TREE_DENSITY_PC): $(FR_TOW) $(NFI) $(ARCGIS) $(TREE_DENSITY_DEPS)
uv run python -m pipeline.transform.tree_density \
@@ -386,6 +392,7 @@ $(PC_BOUNDARIES):
@echo " --arcgis $(ARCGIS) \\"
@echo " --oa-boundaries $(OA_BOUNDARIES) \\"
@echo " --inspire $(INSPIRE_DIR) \\"
+ @echo " --greenspace $(GREENSPACE) \\"
@echo " --output $@"
@echo ""
@exit 1
@@ -393,7 +400,7 @@ $(PC_BOUNDARIES):
# ── Final merge → postcode.parquet + properties.parquet ──────────────────────
$(MERGE_STAMP): $(EPC_PP) $(ARCGIS) $(IOD) $(POI_PROXIMITY) \
- $(ETHNICITY) $(CRIME) $(NOISE) $(SCHOOL_PROX) $(BROADBAND) $(CONSERVATION_AREAS) $(LISTED_BUILDINGS) $(RENTAL) $(MEDIAN_AGE) $(ELECTION) $(TREE_DENSITY_PC) $(MERGE_DEPS)
+ $(ETHNICITY) $(CRIME) $(NOISE) $(SCHOOL_CATCH) $(BROADBAND) $(CONSERVATION_AREAS) $(LISTED_BUILDINGS) $(RENTAL) $(MEDIAN_AGE) $(ELECTION) $(TREE_DENSITY_PC) $(MERGE_DEPS)
@rm -f $@
uv run python -m pipeline.transform.merge \
--epc-pp $(EPC_PP) \
@@ -403,7 +410,7 @@ $(MERGE_STAMP): $(EPC_PP) $(ARCGIS) $(IOD) $(POI_PROXIMITY) \
--ethnicity $(ETHNICITY) \
--crime $(CRIME) \
--noise $(NOISE) \
- --school-proximity $(SCHOOL_PROX) \
+ --school-catchments $(SCHOOL_CATCH) \
--broadband $(BROADBAND) \
--conservation-areas $(CONSERVATION_AREAS) \
--listed-buildings $(LISTED_BUILDINGS) \
@@ -433,7 +440,7 @@ $(PRICES_STAMP): $(MERGE_STAMP) $(PRICE_INDEX) $(PRICE_ESTIMATE_DEPS) | $(PROPER
$(ACTUAL_LISTINGS_ENRICHED): $(ACTUAL_LISTINGS_RAW) $(EPC) \
$(EPC_PP) $(ARCGIS) $(IOD) $(POI_PROXIMITY) \
- $(ETHNICITY) $(CRIME) $(NOISE) $(SCHOOL_PROX) $(BROADBAND) \
+ $(ETHNICITY) $(CRIME) $(NOISE) $(SCHOOL_CATCH) $(BROADBAND) \
$(CONSERVATION_AREAS) $(LISTED_BUILDINGS) $(RENTAL) \
$(MEDIAN_AGE) $(ELECTION) $(TREE_DENSITY_PC) \
$(MERGE_DEPS) pipeline/utils/fuzzy_join.py
@@ -445,7 +452,7 @@ $(ACTUAL_LISTINGS_ENRICHED): $(ACTUAL_LISTINGS_RAW) $(EPC) \
--ethnicity $(ETHNICITY) \
--crime $(CRIME) \
--noise $(NOISE) \
- --school-proximity $(SCHOOL_PROX) \
+ --school-catchments $(SCHOOL_CATCH) \
--broadband $(BROADBAND) \
--conservation-areas $(CONSERVATION_AREAS) \
--listed-buildings $(LISTED_BUILDINGS) \
diff --git a/analyses/school_catchment_model.ipynb b/analyses/school_catchment_model.ipynb
new file mode 100644
index 0000000..c259bd7
--- /dev/null
+++ b/analyses/school_catchment_model.ipynb
@@ -0,0 +1,854 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "46a28f40",
+ "metadata": {},
+ "source": [
+ "# School catchment model — the working\n",
+ "\n",
+ "The postcode features **\"Good+/Outstanding primary/secondary school catchments\"** count the\n",
+ "rated state schools whose modelled *admission cutoff radius* covers a postcode. This notebook\n",
+ "walks through how those radii are produced and how well they match reality.\n",
+ "\n",
+ "**Why model it at all?** No national dataset of school catchments exists for England. Catchments\n",
+ "are set per admission authority, only a handful of councils publish polygons, and the\n",
+ "pupil-residence data behind commercial \"heatmap\" catchments lives in the restricted National\n",
+ "Pupil Database. What *is* public: where every school is and how many pupils it has (GIAS), how\n",
+ "many children live where (Census 2021), and the fact that most English admissions are run as\n",
+ "**deferred acceptance with distance tie-breaks**. That is enough to *solve for* each school's\n",
+ "cutoff distance — the \"last distance offered\" that councils publish each offer day — and those\n",
+ "published figures give us ground truth to calibrate against.\n",
+ "\n",
+ "The production code is `pipeline/transform/school_catchments.py`; the calibration harness is\n",
+ "`pipeline/check_school_cutoffs.py`. Everything below uses the same functions.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "7f9f82b8",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:53:21.904559Z",
+ "iopub.status.busy": "2026-06-10T18:53:21.904382Z",
+ "iopub.status.idle": "2026-06-10T18:53:23.353150Z",
+ "shell.execute_reply": "2026-06-10T18:53:23.351424Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Calibrated constants (see the calibration section for how these were chosen):\n",
+ " DEMAND_SCALE = 0.8\n",
+ " CHOICE_TEMPERATURE_KM = 0.3\n",
+ " PREF_BONUS_OUTSTANDING_KM = 0.6\n",
+ " PREF_BONUS_GOOD_KM = 0.3\n",
+ " CUTOFF_CALIBRATION_FACTOR = {'primary': 1.44, 'secondary': 1.28}\n",
+ " radius clip = [0.3, 25.0] km\n"
+ ]
+ }
+ ],
+ "source": [
+ "import sys\n",
+ "from pathlib import Path\n",
+ "\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "import polars as pl\n",
+ "\n",
+ "ROOT = Path.cwd()\n",
+ "if not (ROOT / \"pipeline\").exists():\n",
+ " ROOT = ROOT.parent\n",
+ "sys.path.insert(0, str(ROOT))\n",
+ "DATA = ROOT / \"property-data\"\n",
+ "\n",
+ "from pipeline.transform import school_catchments as sc\n",
+ "\n",
+ "print(\"Calibrated constants (see the calibration section for how these were chosen):\")\n",
+ "print(f\" DEMAND_SCALE = {sc.DEMAND_SCALE}\")\n",
+ "print(f\" CHOICE_TEMPERATURE_KM = {sc.CHOICE_TEMPERATURE_KM}\")\n",
+ "print(f\" PREF_BONUS_OUTSTANDING_KM = {sc.PREF_BONUS_OUTSTANDING_KM}\")\n",
+ "print(f\" PREF_BONUS_GOOD_KM = {sc.PREF_BONUS_GOOD_KM}\")\n",
+ "print(f\" CUTOFF_CALIBRATION_FACTOR = {sc.CUTOFF_CALIBRATION_FACTOR}\")\n",
+ "print(f\" radius clip = [{sc.MIN_RADIUS_KM}, {sc.MAX_RADIUS_KM}] km\")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e13f2bc4",
+ "metadata": {},
+ "source": [
+ "## 1. Supply — schools and their phase fill targets\n",
+ "\n",
+ "Every open, **non-selective** state school (academies, LA-maintained, free schools) takes part.\n",
+ "Grammar schools are excluded outright: their intakes are test-based and region-wide, so any\n",
+ "distance-based catchment would be fabricated. Independent, special and Welsh schools don't\n",
+ "admit by distance either.\n",
+ "\n",
+ "A school's *fill target* is `max(capacity, headcount)` — an over-full school keeps its\n",
+ "demonstrated size, an under-full one can admit up to capacity (the feature asks \"would you get\n",
+ "a place?\", not \"does a pupil already live there?\"). The target is prorated over the cohort ages\n",
+ "the school teaches, parsed from its age range: nursery years weigh 0.5 and sixth-form years 0.6,\n",
+ "because their cohort sizes differ from the Reception–Y11 core and neither is allocated by the\n",
+ "distance-based admissions round. Primary then competes for the cohort weight in ages 4–10,\n",
+ "secondary for ages 11–15.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "af3e5e33",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:53:23.356272Z",
+ "iopub.status.busy": "2026-06-10T18:53:23.355817Z",
+ "iopub.status.idle": "2026-06-10T18:53:23.541458Z",
+ "shell.execute_reply": "2026-06-10T18:53:23.541094Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "21,246 schools in the admissions model\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "
shape: (3, 6) name age_range pupils capacity primary_intake secondary_intake str str i32 i32 f64 f64 "The Aldgate School" "3–11" 249 245 232.0 0.0 "Haverstock School" "11–18" 909 1035 0.0 835.0 "Saint Mary Magdalene Church of… "3–18" 1667 1860 950.0 679.0
"
+ ],
+ "text/plain": [
+ "shape: (3, 6)\n",
+ "┌──────────────────────────────┬───────────┬────────┬──────────┬────────────────┬──────────────────┐\n",
+ "│ name ┆ age_range ┆ pupils ┆ capacity ┆ primary_intake ┆ secondary_intake │\n",
+ "│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │\n",
+ "│ str ┆ str ┆ i32 ┆ i32 ┆ f64 ┆ f64 │\n",
+ "╞══════════════════════════════╪═══════════╪════════╪══════════╪════════════════╪══════════════════╡\n",
+ "│ The Aldgate School ┆ 3–11 ┆ 249 ┆ 245 ┆ 232.0 ┆ 0.0 │\n",
+ "│ Haverstock School ┆ 11–18 ┆ 909 ┆ 1035 ┆ 0.0 ┆ 835.0 │\n",
+ "│ Saint Mary Magdalene Church ┆ 3–18 ┆ 1667 ┆ 1860 ┆ 950.0 ┆ 679.0 │\n",
+ "│ of… ┆ ┆ ┆ ┆ ┆ │\n",
+ "└──────────────────────────────┴───────────┴────────┴──────────┴────────────────┴──────────────────┘"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "gias = pl.read_parquet(DATA / \"gias.parquet\")\n",
+ "ofsted = pl.read_parquet(DATA / \"ofsted.parquet\")\n",
+ "\n",
+ "supply = (\n",
+ " sc.phase_intakes(gias)\n",
+ " .join(sc.school_preference_bonuses(ofsted), on=\"urn\", how=\"left\")\n",
+ " .with_columns(pl.col(\"bonus_km\").fill_null(0.0))\n",
+ ")\n",
+ "print(f\"{len(supply):,} schools in the admissions model\")\n",
+ "\n",
+ "detail = supply.join(\n",
+ " gias.select(\"urn\", \"name\", \"age_range\", \"pupils\", \"capacity\"), on=\"urn\"\n",
+ ")\n",
+ "# One of each shape: a primary with a nursery year, an 11-18 secondary with a\n",
+ "# sixth form, and an all-through school feeding both phases.\n",
+ "examples = pl.concat(\n",
+ " [\n",
+ " detail.filter(pl.col(\"age_range\") == \"3–11\").head(1),\n",
+ " detail.filter(pl.col(\"age_range\") == \"11–18\").head(1),\n",
+ " detail.filter(pl.col(\"age_range\").is_in([\"4–18\", \"3–18\"])).head(1),\n",
+ " ]\n",
+ ").select(\n",
+ " \"name\", \"age_range\", \"pupils\", \"capacity\",\n",
+ " pl.col(\"primary_intake\").round(0), pl.col(\"secondary_intake\").round(0),\n",
+ ")\n",
+ "examples\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2905514f",
+ "metadata": {},
+ "source": [
+ "## 2. Demand — children per postcode\n",
+ "\n",
+ "Census 2021 (TS007A) gives children by five-year band per LSOA. Bands don't align with school\n",
+ "phases, so phases take fractional shares — primary (ages 4–10) = ⅕·(0–4) + (5–9) + ⅕·(10–14);\n",
+ "secondary (11–15) = ⅘·(10–14) + ⅕·(15–19) — and each LSOA's total is split evenly across its\n",
+ "live postcodes (LSOAs hold ~40 postcodes, small enough at catchment scale).\n",
+ "\n",
+ "Not all of those children compete for state places: births fell ~10% between 2016 and 2021\n",
+ "(exactly the gap between the census stock and the cohorts reaching Reception by mid-decade) and\n",
+ "~7% attend independent schools or are home-educated. `DEMAND_SCALE = 0.8` absorbs both — without\n",
+ "it, modelled cutoffs run systematically tight and half the genuinely undersubscribed schools\n",
+ "look full (this was the single biggest correction the ground truth forced; see §7).\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "f4d291d8",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:53:23.545962Z",
+ "iopub.status.busy": "2026-06-10T18:53:23.545728Z",
+ "iopub.status.idle": "2026-06-10T18:53:24.060323Z",
+ "shell.execute_reply": "2026-06-10T18:53:24.059899Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "primary : 4,646,719 resident children x 0.8 = 3,717,375 applicants vs 5,043,815 places (ratio 0.74)\n",
+ "secondary: 3,374,462 resident children x 0.8 = 2,699,569 applicants vs 3,222,545 places (ratio 0.84)\n"
+ ]
+ }
+ ],
+ "source": [
+ "arcgis = pl.read_parquet(DATA / \"arcgis_data.parquet\").select(\n",
+ " pl.col(\"pcds\").alias(\"postcode\"), \"lat\", pl.col(\"long\").alias(\"lng\"),\n",
+ " \"lsoa21cd\", \"doterm\",\n",
+ ")\n",
+ "live = arcgis.filter(pl.col(\"doterm\").is_null() & pl.col(\"lsoa21cd\").str.starts_with(\"E\"))\n",
+ "demand = sc.children_per_postcode(live, pl.read_parquet(DATA / \"lsoa_children.parquet\"))\n",
+ "\n",
+ "for phase in (\"primary\", \"secondary\"):\n",
+ " kids = demand[f\"{phase}_children\"].sum()\n",
+ " places = supply[f\"{phase}_intake\"].sum()\n",
+ " print(\n",
+ " f\"{phase:9s}: {kids:>12,.0f} resident children \"\n",
+ " f\"x {sc.DEMAND_SCALE} = {kids * sc.DEMAND_SCALE:>12,.0f} applicants \"\n",
+ " f\"vs {places:>12,.0f} places (ratio {kids * sc.DEMAND_SCALE / places:.2f})\"\n",
+ " )\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "20d44b21",
+ "metadata": {},
+ "source": [
+ "## 3. Preferences — grade bonuses and logit choice\n",
+ "\n",
+ "Families don't just pick the nearest school. Two ingredients:\n",
+ "\n",
+ "- **Grade bonus** — a school's *effective distance* is its real distance minus an Ofsted-grade\n",
+ " bonus (+0.6 km Outstanding, +0.3 km Good, −0.3/−0.6 km for grade 3/4). A family accepts that\n",
+ " much extra travel for a better school.\n",
+ "- **Logit smearing** — even so, not everyone at a postcode ranks the same school first. Each\n",
+ " postcode's children split across the nearby feasible schools with weights\n",
+ " `softmax(−effective_distance / τ)`, τ = 0.3 km. This matters more than it looks: with\n",
+ " deterministic choice a popular school fills entirely from its nearest band, putting its\n",
+ " marginal admitted child — and therefore its cutoff — unrealistically close (about 2× too\n",
+ " tight against published cutoffs).\n",
+ "\n",
+ "Below: the share of applications a Good school captures against an unrated neighbour 1 km away.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "51363989",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:53:24.061920Z",
+ "iopub.status.busy": "2026-06-10T18:53:24.061806Z",
+ "iopub.status.idle": "2026-06-10T18:53:24.197184Z",
+ "shell.execute_reply": "2026-06-10T18:53:24.196444Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "xs = np.linspace(-1.5, 2.5, 300) # family position along the line, schools at 0 and 1 km\n",
+ "d_good, d_other = np.abs(xs), np.abs(xs - 1.0)\n",
+ "\n",
+ "fig, ax = plt.subplots(figsize=(7, 3.2))\n",
+ "for tau, ls in [(0.05, \":\"), (0.3, \"-\"), (1.0, \"--\")]:\n",
+ " eff_good = d_good - sc.PREF_BONUS_GOOD_KM\n",
+ " z = np.stack([-eff_good / tau, -d_other / tau])\n",
+ " share_good = np.exp(z[0] - z.max(0)) / np.exp(z - z.max(0)).sum(0)\n",
+ " ax.plot(xs, share_good, ls, label=f\"tau = {tau} km\")\n",
+ "ax.axvline(0, color=\"tab:green\", lw=1); ax.text(0, 1.04, \"Good school\", ha=\"center\", color=\"tab:green\")\n",
+ "ax.axvline(1, color=\"tab:gray\", lw=1); ax.text(1, 1.04, \"unrated school\", ha=\"center\", color=\"tab:gray\")\n",
+ "ax.set(xlabel=\"family position (km)\", ylabel=\"share applying to the Good school\", ylim=(0, 1.12))\n",
+ "ax.legend(loc=\"lower left\"); fig.tight_layout()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "04bcfbcf",
+ "metadata": {},
+ "source": [
+ "## 4. The equilibrium — cutoff dynamics\n",
+ "\n",
+ "English admissions run deferred acceptance with distance priority; in a continuum economy that\n",
+ "is equivalent to finding **market-clearing cutoff distances** (Azevedo & Leshno 2016). The solver:\n",
+ "\n",
+ "1. start every school's cutoff at ∞;\n",
+ "2. every child unit applies to its preferred school(s) among those whose cutoff still covers it;\n",
+ "3. every oversubscribed school tightens its cutoff to the distance of its **marginal admitted\n",
+ " child** — exactly the published \"last distance offered\";\n",
+ "4. repeat. Cutoffs only ever tighten, so the iteration converges to the deferred-acceptance\n",
+ " outcome. Schools that never fill keep no binding cutoff; their radius falls back to the\n",
+ " distance within which the local child population would cover their fill target.\n",
+ "\n",
+ "Toy city: three identical-capacity schools on a 14 km line with uniform children. The popular\n",
+ "end oversubscribes school A; its rejections cascade outward and the cutoffs settle in a few\n",
+ "rounds.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "50496410",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:53:24.198279Z",
+ "iopub.status.busy": "2026-06-10T18:53:24.198137Z",
+ "iopub.status.idle": "2026-06-10T18:53:24.743900Z",
+ "shell.execute_reply": "2026-06-10T18:53:24.743352Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "deterministic cutoffs: [1.5 2.2 2.2] logit cutoffs: [1.5 2.2 2.2]\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "school_xy = np.array([[0.0, 0.0], [4.0, 0.0], [9.0, 0.0]])\n",
+ "targets = np.array([60.0, 90.0, 90.0])\n",
+ "pc_xy = np.column_stack([np.arange(-2, 12, 0.1), np.zeros(140)])\n",
+ "children = np.full(len(pc_xy), 2.0) # 20 children / km\n",
+ "\n",
+ "iters = range(1, 8)\n",
+ "trajectories = np.array(\n",
+ " [\n",
+ " sc.equilibrium_cutoffs(school_xy, targets, np.zeros(3), pc_xy, children,\n",
+ " tau_km=0.0, max_iter=n)\n",
+ " for n in iters\n",
+ " ]\n",
+ ")\n",
+ "final_smeared = sc.equilibrium_cutoffs(school_xy, targets, np.zeros(3), pc_xy, children,\n",
+ " tau_km=0.3)\n",
+ "\n",
+ "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3.4))\n",
+ "for s, label in enumerate([\"A (60 places)\", \"B (90 places)\", \"C (90 places)\"]):\n",
+ " ax1.plot(list(iters), np.where(np.isinf(trajectories[:, s]), 12, trajectories[:, s]),\n",
+ " marker=\"o\", label=label)\n",
+ "ax1.set(xlabel=\"iteration\", ylabel=\"cutoff (km, 12 = unbounded)\", title=\"cutoffs tighten monotonically\")\n",
+ "ax1.legend()\n",
+ "\n",
+ "x = np.arange(3)\n",
+ "det = np.where(np.isinf(trajectories[-1]), 12, trajectories[-1])\n",
+ "sme = np.where(np.isinf(final_smeared), 12, final_smeared)\n",
+ "ax2.bar(x - 0.18, det, 0.36, label=\"deterministic (tau=0)\")\n",
+ "ax2.bar(x + 0.18, sme, 0.36, label=\"logit (tau=0.3)\")\n",
+ "ax2.set(xticks=x, xticklabels=[\"A\", \"B\", \"C\"], ylabel=\"final cutoff (km)\",\n",
+ " title=\"smearing widens the popular school's cutoff\")\n",
+ "ax2.legend(); fig.tight_layout()\n",
+ "print(\"deterministic cutoffs:\", np.round(det, 2), \" logit cutoffs:\", np.round(sme, 2))\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aec83d81",
+ "metadata": {},
+ "source": [
+ "## 5. Solving England\n",
+ "\n",
+ "The real solve: ~17.8k primary-phase and ~4k secondary-phase schools against 1.49 M live\n",
+ "postcodes, 16 nearest candidate schools each. This reruns the exact pipeline path and then\n",
+ "checks the result against the shipped `school_catchment_radii.parquet`. (Takes a couple of\n",
+ "minutes per phase.)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "2cc217fe",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:53:24.745404Z",
+ "iopub.status.busy": "2026-06-10T18:53:24.745249Z",
+ "iopub.status.idle": "2026-06-10T18:57:01.958207Z",
+ "shell.execute_reply": "2026-06-10T18:57:01.957691Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "primary: 6,353/17,815 schools have binding cutoffs, median calibrated radius 1.30 km\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "secondary: 2,465/4,035 schools have binding cutoffs, median calibrated radius 1.75 km\n",
+ "vs shipped pipeline artifact: 14,361 rated rows, max |radius diff| = 0.000000 km\n"
+ ]
+ }
+ ],
+ "source": [
+ "pc_lats = arcgis[\"lat\"].to_numpy()\n",
+ "pc_lngs = arcgis[\"lng\"].to_numpy()\n",
+ "pc_valid = sc.valid_uk_coords_mask(pc_lats, pc_lngs)\n",
+ "origin_lat = float(np.mean(pc_lats[pc_valid]))\n",
+ "\n",
+ "demand_lats = demand[\"lat\"].to_numpy()\n",
+ "demand_lngs = demand[\"lng\"].to_numpy()\n",
+ "demand_valid = sc.valid_uk_coords_mask(demand_lats, demand_lngs)\n",
+ "demand_xy = sc._project_lat_lng_km(demand_lats, demand_lngs, origin_lat)\n",
+ "school_xy_all = sc._project_lat_lng_km(\n",
+ " supply[\"lat\"].to_numpy(), supply[\"lng\"].to_numpy(), origin_lat\n",
+ ")\n",
+ "\n",
+ "radii_frames = []\n",
+ "for phase in (\"primary\", \"secondary\"):\n",
+ " in_phase = supply[f\"{phase}_intake\"].to_numpy() > 0\n",
+ " targets = supply[f\"{phase}_intake\"].to_numpy()[in_phase]\n",
+ " xy = school_xy_all[in_phase]\n",
+ " kids = np.where(\n",
+ " demand_valid, demand[f\"{phase}_children\"].to_numpy() * sc.DEMAND_SCALE, 0.0\n",
+ " )\n",
+ " cutoffs = sc.equilibrium_cutoffs(\n",
+ " xy, targets, supply[\"bonus_km\"].to_numpy()[in_phase], demand_xy, kids\n",
+ " )\n",
+ " filled = np.isfinite(cutoffs)\n",
+ " raw = cutoffs.copy()\n",
+ " raw[~filled] = sc.capacity_fill_radii(xy[~filled], targets[~filled], demand_xy, kids)\n",
+ " radii_frames.append(\n",
+ " pl.DataFrame(\n",
+ " {\n",
+ " \"urn\": supply[\"urn\"].to_numpy()[in_phase],\n",
+ " \"phase\": phase,\n",
+ " \"cutoff_km\": raw,\n",
+ " \"filled\": filled,\n",
+ " \"radius_km\": np.clip(\n",
+ " raw * sc.CUTOFF_CALIBRATION_FACTOR[phase],\n",
+ " sc.MIN_RADIUS_KM, sc.MAX_RADIUS_KM,\n",
+ " ),\n",
+ " }\n",
+ " )\n",
+ " )\n",
+ " print(\n",
+ " f\"{phase}: {filled.sum():,}/{len(targets):,} schools have binding cutoffs, \"\n",
+ " f\"median calibrated radius \"\n",
+ " f\"{radii_frames[-1].filter(pl.col('filled'))['radius_km'].median():.2f} km\"\n",
+ " )\n",
+ "model_radii = pl.concat(radii_frames)\n",
+ "\n",
+ "shipped = pl.read_parquet(DATA / \"school_catchment_radii.parquet\")\n",
+ "check = model_radii.join(shipped.select(\"urn\", \"phase\", \"radius_km\"), on=[\"urn\", \"phase\"], how=\"inner\")\n",
+ "max_diff = (check[\"radius_km\"] - check[\"radius_km_right\"]).abs().max()\n",
+ "print(f\"vs shipped pipeline artifact: {len(check):,} rated rows, max |radius diff| = {max_diff:.6f} km\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "5daa36b7",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:57:01.959228Z",
+ "iopub.status.busy": "2026-06-10T18:57:01.959095Z",
+ "iopub.status.idle": "2026-06-10T18:57:02.256601Z",
+ "shell.execute_reply": "2026-06-10T18:57:02.256145Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAFKCAYAAAAAMjxUAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAU8tJREFUeJzt3XlYFWX/x/HPARFEWRQRRFxwSREXDLRwT8l9TVPTFJfSUnJf8nnSLDXNLE2zSDOXcimtfHosLdfcyK0scy3DfRcBN5Blfn/48zwdQUDkeFjer+s61+WZueee7xxGvnzPfc+MyTAMQwAAAAAAINvZ2ToAAAAAAADyKopuAAAAAACshKIbAAAAAAAroegGAAAAAMBKKLoBAAAAALASim4AAAAAAKyEohsAAAAAACuh6AYAAAAAwEoougEAAAAAsBKKbiAPMJlMmjBhgq3DyHE2b94sk8mklStXPvJ9L1y4UCaTScePH3/k+wYA5GyNGzdW48aNbR1GtiPvAmmj6AYAAAAAwEoK2DoAAA/v1q1bKlCA/84AAABATsNIN5BLpaSkKD4+XpLk5OSUY4rupKQk3b5929ZhAACQL5B3gZyPohuwoQkTJshkMunw4cPq0qWLXF1d5eHhoSFDhpgL6rtMJpPCw8O1ZMkSBQQEyNHRUWvXrjWv++c13Xf7PXr0qJ5//nm5ubnJ09NT48aNk2EYOnXqlNq3by9XV1d5e3vr3XfftdjX7du3NX78eAUFBcnNzU2FCxdWgwYNtGnTJot2x48fl8lk0vTp0zVz5kxVqFBBjo6O2rVrlwoXLqwhQ4akOubTp0/L3t5eU6ZMSfezWb58uYKCguTi4iJXV1dVr15d77//vkWbmJgYDRs2TOXKlZOjo6N8fX3Vq1cvXb582aJdSkqKJk+eLF9fXzk5Oalp06b666+/Uu1zxYoVCgoKUqFChVS8eHE9//zzOnPmTKp2GzduVIMGDVS4cGG5u7urffv2OnToULrHI0l79uxR8+bNVbx4cRUqVEh+fn7q27dvhtsBQH507do1DR061Pw7vkSJEnr66af1yy+/WLTbuXOnWrRoITc3Nzk7O6tRo0bavn17qv7OnDmjfv36ycfHR46OjvLz89PLL79sUbD+/fffevbZZ1WsWDE5OzvrySef1HfffWfRz93rlr/88stM5Za5c+eqQoUKKlSokOrUqaOtW7emakPeJe8ib8sZQ2NAPtelSxeVK1dOU6ZM0c8//6xZs2bp6tWrWrx4sUW7jRs36ssvv1R4eLiKFy+ucuXKpdtv165d5e/vr6lTp+q7777TpEmTVKxYMX388cdq0qSJ3n77bS1ZskQjR45U7dq11bBhQ0lSXFycPvnkEz333HN68cUXde3aNc2fP1/NmzfXrl27FBgYaLGfBQsWKD4+Xv3795ejo6PKlCmjjh076osvvtB7770ne3t7c9tly5bJMAz16NHjvnGvW7dOzz33nJo2baq3335bknTo0CFt377d/AfF9evX1aBBAx06dEh9+/bV448/rsuXL+vbb7/V6dOnVbx4cXN/U6dOlZ2dnUaOHKnY2FhNmzZNPXr00M6dO81tFi5cqD59+qh27dqaMmWKLly4oPfff1/bt2/Xr7/+Knd3d0nS+vXr1bJlS5UvX14TJkzQrVu3NHv2bNWrV0+//PLLfX8mFy9eVLNmzeTp6alXX31V7u7uOn78uL7++ut0f4YAkF+99NJLWrlypcLDw1W1alVduXJF27Zt06FDh/T4449LupMXW7ZsqaCgIL3++uuys7PTggUL1KRJE23dulV16tSRJJ09e1Z16tRRTEyM+vfvrypVqujMmTNauXKlbt68qYIFC+rChQuqW7eubt68qcGDB8vDw0OLFi1Su3bttHLlSnXs2NEivszklvnz52vAgAGqW7euhg4dqr///lvt2rVTsWLFVLp0aXM78i55F3mcAcBmXn/9dUOS0a5dO4vlAwcONCQZv/32m3mZJMPOzs44cOBAqn4kGa+//nqqfvv3729elpSUZPj6+homk8mYOnWqefnVq1eNQoUKGWFhYRZtExISLPZx9epVw8vLy+jbt695WVRUlCHJcHV1NS5evGjR/ocffjAkGWvWrLFYXqNGDaNRo0b3/1AMwxgyZIjh6upqJCUl3bfN+PHjDUnG119/nWpdSkqKYRiGsWnTJkOS4e/vb3E877//viHJ2L9/v2EYhnH79m2jRIkSRrVq1Yxbt26Z261evdqQZIwfP968LDAw0ChRooRx5coV87LffvvNsLOzM3r16mVetmDBAkOSERUVZRiGYXzzzTeGJGP37t3pHjsA4A43Nzdj0KBB912fkpJiVKpUyWjevLn5975hGMbNmzcNPz8/4+mnnzYv69Wrl2Fn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433++efVrFkzrVy5UpJ06dKldI8vMDBQL730kmJiYsxF+R9//KFRo0Zp7969KlWqlCZPnqwXXnhBa9as0ffff6/AwEBz8dqtWzeL9/favHmzhg0bZn4/ceJEJSQk6Pfff9etW7dUv359ValSRV27dlVCQoL27Nmj4OBg/f333zpy5Ihat26tv//+WxMmTNAPP/wgV1dX/fXXX2rQoIGOHz8uSTp06JA+/PBDzZ8/X5Lk5uamw4cPq2TJkkpMTFRCQkK6n8FdBw8e1I4dO5SYmKiGDRtq2bJlatmypaZPn65z586pUKFCunnzpuzs7BQdHa0OHTpo5cqVatCggVJSUiwuRbhy5Yp27twpk8lkUdhLUmxsrPz9/TV9+nT9/PPPateunY4dO6ZLly7d9zjXr1+vFStWaO/evXJxcVHPnj0t+vT29lahQoV04MABVatWLVPHCwAArI+RbgDIwAsvvCBJKl++vBo2bKgtW7ak2e5uIV28eHGVL19eUVFRiouL0759+9S7d29JUvXq1VW/fv377qtFixby8PCQJIWEhOjYsWOS7ozAPv/883JwcJCDg4PCwsLS3P769evatm2bRowYYV7m6en5YAcsadOmTWrRooVKlSolSRo4cKA2btyo5OTkB+7r9OnT8vLyMr9fv369XnzxRdnZ2alw4cLq1auX1q1bJ0nq06ePFixYIElatGiRevTooQIFCmjt2rX666+/1LBhQwUGBqpz586ys7PTyZMnJd352TRq1Mi8j6ZNm6pnz556//33FRUVlelnVPfq1UsODg5ydnbW888/r/Xr18vV1VWVKlXS888/r48//ljR0dFycnJSZGSkKleurAYNGkiS7OzsVKxYMXNfvXv3vu9NzAoUKGA+J5588kn5+Pjo119/Tfc4N2zYoC5dusjV1VUmk0kDBgxI1a+3t7dOnz6dqWMFAACPBiPdwKOS0RTz3qsfTRx4aPcrpJycnMz/tre3V1JS0gNtn1193E+BAgUsiub4+PhMF6MPcwdsZ2dnxcfHZ6rvsLAw1axZU9OnT9fixYu1evWd/xeGYejpp5/W0qVLU21/5syZVMfx1Vdfae/evdq8ebNatWqlSZMmqVu3bml+BukxmUyyt7fXzz//rB07dmjz5s168skntWzZsgyPO7Of7T/3ld5xptX+XvHx8TxyDwCAHIaRbgDIwN2R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c3/bt2/Xvn375O/vr2nTpql///768ccfMxVrRESEPv74Y23dutVciIeGhurzzz9X//79deHCBa1fv15z585Nc/vIyEj9+uuv8vLykiSNGTNG06dPlyQtX75cQ4YM0dq1a3Xx4kX16dNHW7duVdWqVTV37lxduXLlQT5WSdKIESN0+PBhlSxZUomJiUpISEjVZvfu3fLz85Ozs7Mkae3atYqJidHBgwclWX6OBw4c0Pvvv6/Fixfryy+/VLdu3XTo0CG5u7vrv//9r4oUKaLk5GS1b9/evF6Sdu7cqV9//VWVK1dWTEyMnnrqqTR/vqVKlUoV386dO/Xzzz/L2dlZHTp00IwZM/Svf/3Los3jjz+ubdu2yd7eXtHR0apVq5aaN28uX19fDR06VIUKFdLvv/8uOzs7Xbp0SZL0zjvvqHDhwtq1a5ekO6P9r732mubMmZPu5xYSEqINGzbI39//gX8eAADAeii6ASCTduzYob///lstW7a0WH7kyBGVLFlSM2bM0HfffaekpCTFxsaaR8cbNmyoUaNG6fr162rUqJFCQ0PT7L9u3brmgql///567bXXlJycnOV4hwwZohdffFH9+/fXvHnz9Nxzz6lIkSJptm3VqpW54JakdevWafbs2bp27ZpSUlLMBe7PP/+sGjVqqGrVqpKkfv366ZVXXnng2Jo2baqePXuqbdu2atmypR577LFUbU6fPm0RU82aNXXo0CENHDhQjRo1UqtWrczrypUrp6ZNm0qSunTpov79++vUqVMqXry4xowZo23btskwDF28eFHVqlUzF91169Y1X3+e3s83raK7S5cucnFxMX8Os2bNSlV0X7lyRf369dPRo0dVoEABXblyRX/88Yd8fX21evVq7dy50zx13tPTU5K0atUqxcbG6quvvpIk3b59W+XKlcvwc/P29tbp06cz+ugBAMAjRtENAJlkGIYCAgK0Y8eOVOs+//xzbdy4UT/99JNcXV01a9Ysbdy4UZLUqVMn1a1bV+vWrdMHH3ygmTNnZvu1z2mpU6eOnJ2dtWnTJs2dO1fr16+/b9t/FuMnT55UeHi4du/erQoVKuj3339Xw4YN09zOZDLdt88CBQpYfGkQHx9v/vdXX32lvXv3avPmzWrVqpUmTZpkLoTvcnZ2ttimfPnyOnjwoDZu3Kj169dr9OjR5mnkacVlMpn03nvv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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axes = plt.subplots(1, 2, figsize=(10, 3.4), sharey=True)\n",
+ "bins = np.geomspace(0.2, 30, 50)\n",
+ "for ax, phase in zip(axes, (\"primary\", \"secondary\")):\n",
+ " sub = model_radii.filter(pl.col(\"phase\") == phase)\n",
+ " ax.hist(sub.filter(pl.col(\"filled\"))[\"radius_km\"], bins=bins, alpha=0.75,\n",
+ " label=\"binding cutoff (oversubscribed)\")\n",
+ " ax.hist(sub.filter(~pl.col(\"filled\"))[\"radius_km\"], bins=bins, alpha=0.75,\n",
+ " label=\"feasibility radius (spare places)\")\n",
+ " ax.set(xscale=\"log\", xlabel=\"catchment radius (km)\", title=f\"{phase} schools\")\n",
+ "axes[0].set_ylabel(\"schools\")\n",
+ "axes[0].legend(fontsize=8)\n",
+ "fig.tight_layout()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "39039685",
+ "metadata": {},
+ "source": [
+ "The bimodal logic is visible: oversubscribed urban schools cluster well under 1 km while schools\n",
+ "with spare places reach further. A concrete slice — Cambridge and its villages. Circles are the\n",
+ "calibrated catchment radii of Good+ primary schools: tight in town, wide in the villages.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "664426c2",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:57:02.257888Z",
+ "iopub.status.busy": "2026-06-10T18:57:02.257746Z",
+ "iopub.status.idle": "2026-06-10T18:57:02.490665Z",
+ "shell.execute_reply": "2026-06-10T18:57:02.490325Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0AAAANHCAYAAAAfZj36AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXd0VNX6sJ+pmcmkdxJS6ZBAIPTesSCKAgIqFhSw67U3Lip2/axXEb2KStGLFQtSRIo06QQCoaQS0nufdr4/8ptxkkySSTKZSTnPWqwwZ/bZ+z119rvfJhEEQUBERERERERERERERKQLIHW2ACIiIiIiIiIiIiIiIo5CVIBERERERERERERERLoMogIkIiIiIiIiIiIiItJlEBUgEREREREREREREZEug6gAiYiIiIiIiIiIiIh0GUQFSERERERERERERESkyyAqQCIiIiIiIiIiIiIiXQZRARIRERERERERERER6TKICpCIiIiIiIiIiIiISJdBVIBERERajUQiYcWKFc3eLyUlBYlEwpo1a8zbVqxYgUQisZ9wwMSJE5k4caJd+2yItpBf5B8mTpxIdHS0s8UQaWNa+k5pD6xZswaJRMLhw4db1U9ERAS33XabzeOlpKS0ajwRka6EqACJiHQSTD+CEomEv/76q973giAQGhqKRCJh5syZTpBQRAQSEhJYsWJFl5ysXb58mRUrVnD8+HFni9KpKCkp4fnnn2fQoEG4ubmhVquJjo7miSee4PLly84WT0REpB0iKkAiIp0MlUrF+vXr623ftWsXly5dwsXFxQlSdR2effZZKisrnS1GuyUhIYHnn3++yypAzz//vKgA2ZGkpCRiY2N58cUX6d+/P6+99hrvvfcekyZN4r///a/DLL9tQWJiIp988omzxRAR6ZTInS2AiIiIfbnqqqvYuHEj7733HnL5P4/4+vXriYuLIy8vz4nSdV7Ky8vRaDTI5fJa593ZmOQSEalLR7839Ho9119/PdnZ2ezcuZOxY8fW+v6ll17itddec5J0LUMQBKqqqlCr1eJilYhIGyJagEREOhkLFiwgPz+fbdu2mbdptVq+/fZbFi5caHWf8vJyHnnkEUJDQ3FxcaFPnz68+eabCIJQq111dTUPP/ww/v7+uLu7M2vWLC5dumS1z4yMDO644w4CAwNxcXFhwIABfPbZZy0+rrVr1xIXF4darcbHx4f58+eTnp5er93q1avp0aMHarWa4cOHs2fPHpvHkEgk3Hfffaxbt44+ffqgUqmIi4tj9+7dtdqZ4nwSEhJYuHAh3t7e5smXtRggU78bN26kf//+qNVqRo0aRXx8PAAff/wxPXv2RKVSMXHixHrWkT179jB37lzCwsJwcXEhNDSUhx9+uJ6l6bbbbsPNzY2LFy9y1VVX4e7uzk033cS///1vFAoFubm59Y55yZIleHl5UVVV1ei5OXv2LPPmzcPf3x+1Wk2fPn145plnzN+npqZyzz330KdPH9RqNb6+vsydO7fWsaxZs4a5c+cCMGnSJLPL5s6dO81tNm/ezIQJE3B3d8fDw4Nhw4ZZtWgmJCQwadIkXF1dCQkJ4fXXX6/1/c6dO5FIJPzvf//j+eefJyQkBHd3d+bMmUNxcTHV1dU89NBDBAQE4Obmxu233051dXW9cWy570xxSY3JtHPnToYNGwbA7bffbj52U/zb+fPnueGGGwgKCkKlUtG9e3fmz59PcXFxo9eltfcG2Pb8W4vXM1E3Xsf0DFy4cIHbbrsNLy8vPD09uf3226moqKi1b3PeKXX57rvvOHHiBM8880w95QfAw8ODl156qcXnKi0tjZkzZ+Lm5kZISAj/+c9/AIiPj2fy5MloNBrCw8Ot3p8AFRUVLF26FF9fXzw8PFi0aBGFhYW12kRERDBz5ky2bNnC0KFDUavVfPzxx+bv6sYAnT59msmTJ6NWq+nevTsrV67EaDTWG9toNLJixQqCg4NxdXVl0qRJJCQkWO2zqKiIhx56yHz9e/bsyWuvvWa1XxGRzkL7WaYUERGxCxEREYwaNYoNGzZw5ZVXAjWTyuLiYubPn897771Xq70gCMyaNYs///yTxYsXExsby5YtW3jsscfIyMjg7bffNre98847Wbt2LQsXLmT06NHs2LGDq6++up4M2dnZjBw50jzx9/f3Z/PmzSxevJiSkhIeeuihZh3TSy+9xHPPPce8efO48847yc3N5f3332f8+PEcO3YMLy8vAP773/+ydOlSRo8ezUMPPURSUhKzZs3Cx8eH0NBQm8batWsX33zzDQ888AAuLi58+OGHXHHFFfz999/1gu/nzp1Lr169ePnll+spi3XZs2cPmzZt4t577wXglVdeYebMmTz++ON8+OGH3HPPPRQWFvL6669zxx13sGPHDvO+GzdupKKigrvvvhtfX1/+/vtv3n//fS5dusTGjRtrjaPX65kxYwZjx47lzTffxNXVlVGjRvHCCy/wzTffcN9995nbmhTjG264AZVK1aDsJ0+eZNy4cSgUCpYsWUJERAQXL17k559/Nk8wDx06xL59+5g/fz7du3cnJSWFjz76iIkTJ5KQkICrqyvjx4/ngQce4L333uPpp5+mX79+AOa/a9as4Y477mDAgAE89dRTeHl5cezYMX7//fdaynthYSFXXHEF119/PfPmzePbb7/liSeeICYmxnzPm3jllVdQq9U8+eSTXLhwgffffx+FQoFUKqWwsJAVK1Zw4MAB1qxZQ2RkJMuXLzfva+t9Z4tM/fr144UXXmD58uUsWbKEcePGATB69Gi0Wi0zZsygurqa+++/n6CgIDIyMvjll18oKirC09OzwWvT2nujOc9/c5k3bx6RkZG88sorHD16lE8//ZSAgIBaVhlb3ynW2LRpEwC33HKLTe2bc64MBgNXXnkl48eP5/XXX2fdunXcd999aDQannnmGW666Sauv/56Vq1axaJFixg1ahSRkZG1+rjvvvvw8vJixYoVJCYm8tFHH5GammpWzk0kJiayYMECli5dyl133UWfPn2syp+VlcWkSZPQ6/U8+eSTaDQaVq9ejVqtrtf2qaee4vXXX+eaa65hxowZnDhxghkzZtRb6KioqGDChAlkZGSwdOlSwsLC2LdvH0899RSZmZm88847Np1bEZEOhyAiItIp+PzzzwVAOHTokPDBBx8I7u7uQkVFhSAIgjB37lxh0qRJgiAIQnh4uHD11Veb9/vxxx8FQFi5cmWt/ubMmSNIJBLhwoULgiAIwvHjxwVAuOeee2q1W7hwoQAI//73v83bFi9eLHTr1k3Iy8ur1Xb+/PmCp6enWa7k5GQBED7//HNzm3//+9+C5aspJSVFkMlkwksvvVSrr/j4eEEul5u3a7VaISAgQIiNjRWqq6vN7VavXi0AwoQJE5o8h4AACIcPHzZvS01NFVQqlTB79ux6Mi5YsKBeH3XlN/Xr4uIiJCcnm7d9/PHHAiAEBQUJJSUl5u1PPfWUANRqazpflrzyyiuCRCIRUlNTzdtuvfVWARCefPLJeu1HjRoljBgxota277//XgCEP//8s/7JsGD8+PGCu7t7rbEEQRCMRmOjMu7f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1FONtrXnc2Qr58+fZ9KkSebPZWVlZGZmctVVV7Wov/DwcKvvp7rny3TNLly4UMu6lJ+fX89S1aNHD8rKyuzyrhYR6UiILnAiIp0UNzc3PvroI1asWME111zTYLurrroKg8HABx98UGv722+/jUQiMWfVMv2tm0WubpYgmUzGDTfcwHfffWd10mwtFXNjXH/99chkMp5//vl6q6yCIJCfnw/A0KFD8ff3Z9WqVWi1WnObNWvW1Ju0Ncb+/ftrxaykp6fz008/MX369AZXf9sS05iWxy4IAu+++26z+7ryyivx8/PjtddeY9euXTZZf/z9/Rk/fjyfffYZaWlptb6zlEkmk9W7Pu+//349C4Gp7kzdazJ9+nTc3d155ZVX6mWqspfFoDnYet81h4aOvaSkxBx7ZCImJgapVGo1NbcJe9wbtj7/Hh4e+Pn51YsJ+fDDD20eqy62vlMaYs6cOcTExPDSSy+xf//+et+XlpaaU7Xb8zmyldWrV6PT6cyfP/roI/R6fb1MhbZy1VVXceDAAf7++2/zttzcXLNbq4kpU6Ygl8v56KOPam2ve42hJl50//79bNmypd53RUVF9e5LEZHOgmgBEhHpxDTkgmbJNddcw6RJk3jmmWdISUlh0KBBbN26lZ9++omHHnrIvDIfGxvLggUL+PDDDykuLmb06NH88ccfXLhwoV6fr776Kn/++ScjRozgrrvuon///hQUFHD06FG2b99OQUGBzcfQo0cPVq5cyVNPPUVKSgrXXXcd7u7uJCcn88MPP7BkyRIeffRRFAoFK1euZOnSpUyePJkbb7yR5ORkPv/882bFAEVHRzNjxoxaabABnn/+eZv7sCd9+/alR48ePProo2RkZODh4cF3331nU2xJXRQKBfPnz+eDDz5AJpOxYMECm/Z77733GDt2LEOGDGHJkiVERkaSkpLCr7/+yvHjxwGYOXMmX331FZ6envTv35/9+/ezfft2fH19a/UVGxuLTCbjtddeo7i4GBcXFyZPnkxAQABvv/02d955J8OGDTPXVzpx4gQVFRV88cUXzT7e1mDrfdfcPr28vFi1ahXu7u5oNBpGjBjBiRMnuO+++5g7dy69e/dGr9fz1VdfmRcTGsIe94atzz/UJAV49dVXufPOOxk6dCi7d+/m3LlzzToHljTnnWINhULB999/z9SpUxk/fjzz5s1jzJgxKBQKTp8+zfr16/H29uall16y63NkK1qtlilTpjBv3jwSExP58MMPGTt2LLNmzWpRf48//jhfffUVV1xxBQ8++KA5DXZ4eDgnT540twsMDOTBBx/krbfeYtasWVxxxRWcOHGCzZs34+fnV8sS+dhjj7Fp0yZmzpzJbbfdRlxcHOXl5cTHx/Ptt9+SkpKCn59fq8+FiEi7wwGZ5kRERByAZRrsxqibBlsQBKG0tFR4+OGHheDgYEGhUAi9evUS3njjjVrphwVBECorK4UHHnhA8PX1FTQajXDNNdcI6enpVlPWZmdnC/fee68QGhoqKBQKISgoSJgyZYqwevVqcxtb0mCb+O6774SxY8cKGo1G0Gg0Qt++fYV7771XSExMrNXuww8/FCIjIwUXFxdh6NChwu7du4UJEybYnAb73nvvFdauXSv06tVLcHFxEQYPHlwvZbNJxtzc3Hp9NJQG+9577621zXTsb7zxRq3tpvTNlimRExIShKlTpwpubm6Cn5+fcNdddwknTpyod+5uvfVWQaPRNHqMf//9twAI06dPb7RdXU6dOiXMnj1b8PLyElQqldCnTx/hueeeM39fWFgo3H777YKfn5/g5uYmzJgxQzh79my9VL6CIAiffPKJEBUVJchksnopsTdt2iSMHj1aUKvVgoeHhzB8+HBhw4YN5u8nTJggDBgwoJ58t956a6001tbOoyA0/Jw0dE1tue9slUkQBOGnn34S+vfvL8jlcvP1S0pKEu644w6hR48egkqlEnx8fIRJkyYJ27dvr9dnXexxb9j6/FdUVAiLFy8WPD09BXd3d2HevHlCTk5Og2mw655L07m3TPHenHdKQxQWFgrLly8XYmJiBFdXV0GlUgnR0dHCU089JWRmZtrtXDV0neu+U03HuWvXLmHJkiWCt7e34ObmJtx0001Cfn5+o/vW/a7us3Py5ElhwoQJgkqlEkJCQoQXX3xR+O9//1vvvOr1euG5554TgoKCBLVaLUyePFk4c+aM4OvrKyxbtqxWn6WlpcJTTz0l9OzZU1AqlYKfn58wevRo4c0336yVxltEpDMhEQQn+BaIiIiItEMkEgn33nuvVVeRzsKJEyeIjY3lyy+/tLmApIiISMenqKgIb29vVq5caXYNFBHpqogxQCIiIiJdiE8++QQ3Nzeuv/56Z4siIiLSRlRWVtbbZoqtmjhxomOFERFph4gxQCIiIiJdgJ9//pmEhARWr17NfffdZw7IFxER6Xx88803rFmzhquuugo3Nzf++usvNmzYwPTp0xkzZoyzxRMRcTqiAiQiIiLSBbj//vvJzs7mqquuclpCBxEREccwcOBA5HI5r7/+OiUlJebECA2lVBcR6WqIMUAiIiIiIiIiIiIiIl0GMQZIRERERERERERERKTLILrA1cFoNHL58mXc3d2brNotIiIiIiIiIiIiItI+EASB0tJSgoODkUobtvOIClAdLl++TGhoqLPFEBERERERERERERFpAenp6XTv3r3B70UFqA7u7u5AzYnz8PBwsjQdg7MFZ/ng2AeU6cq4odcNzIya2e6tZ4Ig8PGJjzmQdYCFfRYyPXJ6re9XHlhJXmUe70x6x+5j5+XlkZ2dTWBgIH5+fvU+27KPszh9+jRV1VUk65JJkiaRkJ8AQLAmmLEhYxkcMJhubt3abGytVgtgXqSwPCerjq/iQNYBlsQsYXTIaKDmvKWnp5v7CA0NNZ+/5pzTptoaBSPJRckcyTnCvox9FGmLkEvkDA0cypSwKfT07tnun4nOwMZzG/k16VdeHfsqQW5BzhbHZr48/SU70ndwXc/ruK7ndc4WRwRYc2oNOy/t5OZ+NzM1fKqzxREREbGBvLw8kpKSmDZtmnk+3xCiAlQH0yTFw8NDVIBsZLjHcN4MeJM3D73JpoxNVCuquW3AbUgl7TfE7M+0PzlScoSRkSO5YeAN9SancrUctUzdJvdAamoqSqWSkpISysvLMRgMKJVKysvLiYqKMrfLzc0lKyuLoKAgysvLrbZxJIVVhSS6JLI9azuVVOKidGFCzwlMC59GP59+bT7B79mzJ2lpaQCUl5cDmM9JnjrPfD2H+A4hNTUVjUZDYWEhbm5uAISFheHv72/uz3QdbDmntrQd7DmYweGDuS3uNg5nH2ZrylaO5B/hyKkjhLuHMz1iOmNCxqCWq+1xOkSs4O7ujsJVgau7Kx7uHef9vXT4UtJ0aWy+vJmh4UPp79vf2SJ1ee4afhfnKs/x06WfGNtjLAGuAc4WSUREpAlSU1NRKBQATc5J2u8MVaRDEeAawPNjnifaN5otKVtYfXI1RsHobLGskl6azmenPsNX5cs9g+6x+pAYBEObKXBBQUEolUoAtFotBoMBiUSCwWAgNzfX3C4rKwutVmtWgpRKJUFBjl/VTixI5J0j73DvH/ey+fJm3N3duSn6Jj6c8iEPDnmQ/r79HWLd8Pf3JywsDKVSiUajwWAwIJPJ8PL34vPTn6MUlIyRj+Hy5ctotVoKCwvN+5qUn7zKPJZtW8bYr8fydvrbVEoqa53T3Nxc4uPja12H3Nxc81i2nH+5VM7IbiNZPmo5b054kysiriCnModP4j9h2bZlfHbqM7LKs+x7ckQAkElkAA5/91i7b5qDQqbg4SEPo5Qpee/YexRXF9tZQpHmoparWTpwKVWGKj4/9bmzxREREamDtfduc+ZIogIkYjfUcjVPDH+CWP9Y/kz/k49OfNTulKBqQzXvHHkHvVHPg0MexE3pZrWdQTCYJ1MN0dxJj6k91H9IBUHAYDCQlfXPxNhS6fH39ycmJqaWBaO1k66mSC9J5/VDr7N833L2Z+6nv29/Hh36KB9M/oDre12Pl8qrTca1hZKSErNSsrtwNwVVBUzwnIDKqAJqLEPe3t4olcpalp9n/3qWA5kHKK4u5mjeUdbmr611Ti2VTsttBoMBg8HQbDlD3UO5Pfp2Ppr6EXfF3EWgayBbUrbwr53/4tP4TymqKmrdiRCphWnRQi/oHTqutfumuXRz68bimMUUVhXy4fEP2927sysS4x/DmOAxHM05yuGsw84WR0RExAJr711/f3+b4/hFF7hmYDAY0Ol0zhaj3XNf9H2sOb2GU5mn+EryFfN6z2s38Q/fnP2GisoKFvZcSLhrOFVVVVbbuUncQEaD3wNkZmai0+nIzMxs0te0bnuouZ+kUikymQy1Wk1ZWRl6vZ7Lly/j4+ODu7u7uV9rcjR3fFspqCxgc8pmjmQdQUBgcrfJzIiYQZCmRmnTaXXoaJvnQCaTIZfLG7xfTC88mUyGUqlE8BD49dSvRHpGck2fa8jJzjErjNY4lX8Kg1CjyBgEA6fzT9f6PigoyGxxs9xmcr3LyspqsO/GUMvVTA2fypSwKSQUJPD12a/ZlrqN3Zd2c1XkVczqMQtXhWuz+xWpjUxas2jh6PJ21u6bljC++3hO551m56Wd/Jr0K9f0uMZOEoq0lJv738yR7CN8cfoLBvoPRClTOlskERERGn7v2honLRZCrUNJSQmenp4UFxfXiv8oKyvj0qVLDv9h7agIgkCJtgStUYtarsZNYd3S4kiq9dWU6EpQSpV4uHggoWGlrLCqEAEBH5VPg21MVgGZTIZM1ri1qG5702fLfU0B/iaa6re54zeFUTBSoa+gSl+FgIBSqkSj0CCXOnadxNXVlW7dupndBC2xjIvy8/PjpYMvEZ8Xz7/6/QvXCtdGlR+AZduWcSDzgNnCN7LbSFZNW9WkTLm5uWRkZAAQEhLSIiXIEkEQOJJ9hA1nN3Cp7BJuCjeu63kdV0RcgUKmaFXfXZkfzv/A14lf8+q4V4n0jHS2OC2iSl/F0389TVZ5FitGr6C3d29ni9Tl+fniz6w9s5Y5vecwt/dcZ4sjIiLSCA3N4+siKkB1sHbiDAYD58+fx9XVFX9//3ZjzWjvGAUjeRV5VBmq8FZ54660n5WiueiMOrLLs5EgIVAT2OSkPqs8C6NgJNgt2EESgk6nQ6+vcd0RBAGJRIJa3fYB80bBSIm2hDJtGUbBiFKmxFPpiVrh2GB9QRDQarXmmJtevXo1msN//+X9vHP0HSaGTmSsdCxarRalUklMTEyD++RV5vH4n49zpuAM/Xz68fqk1xHKBLNSVdfF0HJ7fHy8TWM0B6NgZPel3fwv8X/kV+Xjo/JhXu95jO8+3mzNELGd7859x//O/Y83xr9BmEeYs8VpMaklqTzz1zN4u3jz+oTXxcQZTkZv1PPE7ifIrsjmrQlvEagJdLZIIiIiDWCrAiS6wNmATqdDEAT8/f0dMiHtTIS4hJBRlkGpsRQ3uZtTfsgFQaCwvBCJQkI3TTebXI3kOjkGwYBKpXKAhDWYxtLpdOh0OnMmE9P/TZ/thSAIlOvKyavMwyAYULoo8VH5oFFonKbkq9VqFAoFqampaLXaWuffUiFx83bjy4Qv0Sg0LOy7EG2J1qopvK4S46f24+HQh9EG1igyfmo/4i/Em/2IG4oH8vf3t5ubkyVSiZSJoRMZEzyGralb+eH8D6w6uYpfkn5h6aCl4up/MzG5N7bnDJS2EO4RzsK+C/ki4Qs2nN3AHdF3OFukLo1cKueO6Dt44cALfJHwBY8Pe9zZIomIiLSSjv0r4WBEy0/zkUllBGmCkEgkZJdnozM4PoaqVFtKhb4Cd6W7zXEWzrrWlsqPQqEwK9/2jj3TG/VkV2STXZENgL/an1D3UNyUbk6/zxuy+lgqJD+c/4GCqgJu7HMjni6eDfZlLUiybka9hjLs1d1eNxGFPZNQKGQKro66mvcmv8f1Pa8nqyKLf+/9N+vOrHPKM9NRMSlATSUw6QhcEXkF/Xz6sSVlS71YNRHHM8BvAKODR3Mk+whHs486WxwREZFWIipAIm2OUqYkwDUAg2AgqyLLodmNdEYd+VX5yKVyfFW+Nu8nQeKUeK+6Co9CoUAikSCTyaioqGi1IiQIAmXaMi6VXqJcV46bwo3u7t1rYqLauYJvUkiU3kp+S/6NcI9wpoVPAxrOwmWpxFhm4bNUZCwVG0ulxlrmPUvskfmrLq4KV27se6M5hmXTxU08secJzheet9sYnRnTu6UzuA9KJVKWDVqGUqpk1fFVVOornS1Sl+fmfjfjInNh7Zm1GIzNzwopIiLSfhAVIBEAdu7cye+//27XPk+dOkVERAQAGoUGH5UPWoOWnIochygXgiCQW5GLUTDir/Zv1qRIggQBxytAJoXH5O6mUChwdXXFYDC02hJU1+oT6BpoUzxUe8GkkOzI34HOqOOmfjeZXZ0asuJYKjEmhSUtLa1Bq01zlJq2rM0U6h7Ki2NeZEHfBWRXZLN873LRGmQD1YZqABTSzpFIIkgTxMJ+C8mpzGHD2Q3OFqfL46v25erIq8koy2D3pd3OFkdERKQViAqQCNA2ClBdvFy8cFO4Ua4rp1Rb2qZjQY3rW6W+slmubyZkUhlGwejwWhwmhaduvE9dxag5NGT1aagGUnvmROoJfj/3Oz1cezDQb2Cz9rVUVDIyMqy6rzVHqWnKQtRaZFIZ1/W8TrQGNYOiqiIkSPBQNhz42tGYETGjQ7rCtXWdMmdxTY9rcFe4s/HcRnFBQkSkAyMqQB0YiUTCs88+y+DBg+nduzfr1q0zf7dlyxaGDBnCwIEDmTBhAgkJCQCcP3+eMWPGMGjQIGJiYnj22Wc5fvw4q1atYt26dcTGxvLCCy8A8OuvvzJs2DAGDRpEbGwsBw8ebLRvgBUrVtCrVy/i4uL4+uuva8m7detWrp9xPddNvI5xo8ex7Y9tDcrUWlrq+mbCFEOgNzq2oKIJnU5Xy+WtIcWoKQxGQ4e2+tRlQ8IGjEYj49zG1XLZs8Vy4+/vT1hYmDm9trX2ba3UtISGrEGiC059CqsL8VB6dAoXOBMd1RWuLVxE2wOuCleu63Ud+VX5bEnd4mxxREREWkjHnAV1MOpmorInEomEY8eOkZSUxNChQxkzZgyurq4sXLiQnTt3EhMTw7p165gzZw6nT5/mgw8+YObMmTz11FMAFBQU4OPjw7JlyygqKuKdd94B4Ny5c9x+++3s3r2bvn37mifkOTk5Dfb922+/sXHjRo4cOYK7uzu33HKLWc6kpCRWrFjBli1bkKvl7I/fz8KrFpKakmpVptYgCAJ5FXkYBSOBroEtmgyZFARTULWjsYwFamn2N61BS1Z5FjqjDjeFG75q33at+JgSQBgM1s/5ucJznK86T7RHNEMjh9b6zlqGNmvPnelvRkYGMpnMZve1tnyGbcFkDYoLjOOj4x+x6eImkouTeWjIQx3SktdWFFUV4a3ydrYYdsfkCrfm9JoOkxWuLbImthdmhM/gt6Tf+OH8D0wOnSwWMRYR6YCIFiAH0JYrYXfeeScAUVFRjB8/nt27d3Pw4EFiYmLMtUpuuukmLl++TEZGBuPHj+eTTz7hmWeeYevWrXh5eVntd9u2bVxxxRX07dsXqLFAeHp6Ntr3H3/8wbx58/DwqAmoX7p0qbm/33//nQsXLjB+/HhGDxvNg7c9iEQq4czFMzbLZCvluvJmZ32ri8kC5KxVdmsub3WtQo1RoasgoywDvVGPn9qPANcAhyk/zZGz7n6CIJhrIVkiCALrz6xHqVBy37j76ikh1iw3ls+dpTtOVlaWuYCsrcpMe1nNDnUP5YUxLzAtfBrxefE8/dfTpJemO1Wm9oIgCBRUF3RKBQhqXOH6ePdha8pWkoqTnC1Ok7RHa6q9UMgUzOszjzJdGT9f/NnZ4oiIiLQAUQFyAG0ZLF2XpjJ53XDDDezdu5c+ffqYLS+OkEUQBKZNm8bx48c5fvw4J46f4ODZg3h192LW7Fl2k8koGMmvykcmkeGj8mlxPyarkV5wjgucNZc3W1JiC4JAUVURmeWZSKipe+Tp4unQDG8tTd1tUvrk8vqK2oncE5wpOMPE0ImEuIXY1J/lc2epwLTkeXTkM9wUcqmcO2PuZHH0YvIq83j2r2c5nHXY2WI5nXJdOXqjHm+XzqkASSVSFscsRoKEz+I/c3h8okhtxncfT3e37vya/CtFVUXOFkdERKSZiAqQA2jLlbDPP/8cgJSUFPbs2cO4ceMYOXIk8fHxnDp1CoCvv/6akJAQQkJCOH/+PIGBgSxatIjXX3+dAwcOAODh4UFxcbG53xkzZrBlyxbOnj0L1Exqi4uLG+176tSpbNy4kdLSUgRBYPXq1bX62759OydPngRqFIzUU6kYBSOH4g9ZlaklFFYV1kyCVN6tsnjIJf/nAteO4iyaSoRgFIzkVOaQX5WPUqYkxC0EtcLxhWdbmrChoTgnQRDYeG4jCqmCOb3n2Nyf5XNnqcC05Hm0dR9HBn5Pj5jOsyOfRSFV8ObhN/nxwo9OSd3eXiisKgRodxYge94T4R7hzIiYwfmi8+xK32UH6URailQiZX7f+VQbqvk5qXVWoM6aMEJEvLbtmfYbECBiEwaDgcGDB1NeXs57771nTju9bt06Fi1ahF6vx9vbm40bNyKRSPj2229Zu3YtSqUSo9HIqlWrAJg9ezZfffUVsbGxXH/99SxfvpzPP/+cm2++GZ1Oh0wmY9WqVQwfPrzBvq+66ir+/vtvhgwZgoeHB1deeaVZzp49e7J+/XqWLl1KRUUFWq2WwYMH8/Ynb/PRdx9x58I7cVG61JKpuWgNWoqri3GRubQ6C5SzLUCWWBZHdXW17tKnN+rJKs+i2lCNRqEhwDXAnCLa0ZiKuNqLhPwELhRdYFr4NHzVzU9oATUKjCNccSwtTY4Yr79vf14e9zJvHHqDDWc3kFaSVhMwL1O2+djtjcLq9qkA2fuemNtnLvsu72P92fUMCxomxoA5kaGBQwlzD2N76nZm95zd4mvh6PeGiOMQr237RSJ05SVDK5SUlODp6UlxcTEeHjWT6KqqKpKTk4mMjESlUjlZwn+QSCQUFha2OmbGmWgNWi6VXsJF7kKwJrjFrlqCIJBZnkmlvpIQtxBUctuvk6WCYTlxTy5OxkXmQrBbcItkshcVFRUIgoBEIrGqAFXpq8iuyDa7/3irvNt9UdPGqPu8rTywktN5p3ln0jsEagKdLV6jOCtZQqW+kg+Pf8jfWX8T5RnFo0MfbbGy2FHZlb6LD098yKNDH2VY0DBni2OmLe4J07HOiJjRIRIidGb+yviL94+9z9zec61aqG25/s5OsiLSdlheW0C8zg7A2jzeGqILnIhTUcqUuCvdqdJXUaGvaHE/5bpyc82f5ig/0HDMilwqd1oWOEsacymr1FeSWZ6JQTAQ6BqIj9qnQys/dUkqSiI+L55RwaNapPw42v3AWYHfarmah+MeZk7vOSQVJ7Fi3wpyKnIcKoOzMVuA2lkMUFvcE+O6j6OPdx+2pWwjpTjFbv2KNJ9R3UYR4BrA78m/U6Wvqve9rSn6O2vCiK6OtWLczk6mI1KDqAB1YARB6NDWHxMmi0VBVUGLYhhMiQ+kEmmLEh80pGDIJDKn1QGypKHYmApdBZnlmQAEa4I7pSvMjxd+BOC6nte1aP+u9IMjlUiZ23suywYuI7cylxX7VpBZlulssRyGKRC9vbnAtQVSidRs+fns1GddOvbL2cikMmb1mEWprpQ/0v6o9317SqAi4lzEe6F9ISpAIk5HLpXjqfREa9BSpitr9v5F1UXojXp8VD4tSnzQkIIhk8owCsZ2mW2pXFdOVkUWUqR003RrttWrI5Bdns3fWX8zJGAIYR5hLerD2g9Oc6xCHTGAdVLYJO6NvZfCqkKe3/98l0mTXVBdgAQJni6ezhbFIUR4RjAtYhqJhYn8lfGXs8Vp17T1czyx+0S8XLz4JekXdMbangSidUfEhHgvtC9EBUikXeDl4oVMIqOouqhZq5l6o57i6mKUMmWrEx/UrV+jkNYoRDpD89I5tzXlunKyy7M7tfIDsCNtBwJCi60/0HR9oKboqBakcd3H8cCQByjRlvDC/he4VHrJ2SK1OZfLLrf7Yr/2Zl7vebgp3Phf4v/a3XuqPdHWz7FCpuDqqKspqCpgz6U9bTKGiIiIfREVIJF2gUwqw13pjtagtepH3RBF1UUYBSM+qtbHvtSNBXKRuQBQbahuVb/2pEJXQXZFNlKJlGC3YFzkLs4WqU0wCkaOZB+hn08/+vj0sWvfzXFD6MguC6OCR/Fw3MOU68pZeWBlp3aHqzZUk1GaQZRnlLNFcShuSjeu7XktOZU5bE/b7mxx2i2OeI6nhk1Fo9DwS9IvokuiiEgHQFSARNoNJgtOsba4iZY16Aw6SqpLUMlUuMqtp4duDnVjgdqbAlSpq/zH7c2tW6dOdVylr8IgGLg66uom2zbXvaU5bggd3WVhWNAw7h98P8XVxbx44MVOmxghpTgFI8YupwABXBFxBd4qb74//z2V+kpni9MuccRz7KpwZVLoJDLKMkjIT2izcUREROyDqACJtBsUMgWuclcqdBU2JR8oqCpAQLBb5rO6sUAyiQyZRNYuFKAqfRVZFVlIkNBN082snHVGBEGg0lCJp4snQwKGNNm+o7qpOYpRwaO4O/ZuCqoKeHH/i+RX5jtbJLuTXJwMQKRnpJMlcTxKmZK5veZSoi3hl6RfnC1Ol2Za+DQAtqRucbIkIiIiTSEqQB0YnU7H888/T9++fRkwYACDBw/muuuu4/jx43YdZ+jQoezcubPV/Sxfvpx169Y12sbDxQMBgRJtSaPtqvXVlOnKcJW7oparWy2bNSQSCS5yF7RGrTkRwooVK6iqatpFb8WKFTz00EN2kUNn0JFVXjO576bphlSQ1opVssbOnTv5/fff7TK+o6nUVWIUjIwOHm0uSNsYHdlNzVGM7z6eJQOXkFOZw5uH30Rr0DpbJLuSVJwEQJRX57EANceyOTF0IsGaYH65+AvF1bZZ0EXsT5AmiEH+gziUeYiCqgJniyMiItIIogLUAgRBoEJX0ab/bPEhvv322zl27Bj79+/n9OnTHDt2jPvuu4/ExEQHnIXmodfreeGFF7jpppsabecqd0UulVOqLW30HJh+XCzTXuv19k9Z7SJzqYkL+r8A4+eff94mBcheGAUj2RXZGAQDAa4BqOSqBusWWdKRFaAyXRkSJIzsNtKm9o5wb+mI2eDqMjlsMjf0uoGk4iRWnVjVqeIUkoqT8FP7tToRSnuiOZZNmVTG/L7zqTJU8cP5HxwgnUhDTA+fjhGj1ZTYIiIi7Yeuky7HjlTqKxmxfkSbjnFw4UFcFQ3HtZw/f54ffviB9PR0vL3/qXsxdepU8/8NBgNPPvkkmzdvBmDSpEm89dZbKJVKcnJyWLZsGefPn0cQBO6//36WLl0KwL59+7jnnnvQ6/UMGzasQcVi586d3HfffQwZMoSjR4/i4uLCf//7X2JjY9m5cyf33nsvI0eO5MiRIzzzzDP8+uuvxMbG8tBDD7FixQoSEhKorKwkMTGR3r178+qrr/LII49wMeki/Qb2Y8P6Dbi5uLF+/XreffddtFotRqOR555/jsETB+OmdGPG1BkMHDiQQ4cOoVar6d+/P8HBwTz99NMAJCYmMnXqVJKTk5HLa9/u+/fv57HHHqO0tEbZevHFF7n22mt59NFH2bVrFzqdDo27hhX/bwX+A/158L4HARg3bhwymYytW7fi4uLCI488woEDB5DJZMTFxfHZZ58BkJmZyTXXXMPFixcJCgri22+/xcfHhzVr1rB27Vr8/f05ceIEXl5efPrppzzzzDOcPXuW0NBQvv/+ezQaDRnFGaxcsZJDfx1Cr9XTu3dv3n33Xby8vFiyZAkajYYLFy6Qnp5OdHQ0X3/9NQkJCaxatQqDwcDOnTu5/vrrWb58eXNvQaegNWipMlTVZPVzaXwy68jq6ZaT0Y4aDwQwp/cc0krS2Ht5L+Ee4Vzb81pni9RqTAkQhgYNdbYodsF0X2s0GgCbLZvDg4bTw7MH21K3cVXUVQS4BrSlmCINMCRwCH5qP/5I+4PZPWd3qayEIiIdCdEC1EE5duwYPXv2xMen4cKfq1ev5tChQxw5coTjx49z8eJF3n77bQDuv/9++vTpQ3x8PDt27GDlypUcOHAArVbLjTfeyJtvvsmpU6dYsGABJ06caHCM06dPc+utt3Lq1CmeeOIJ5s+fb15ZPnPmDIsWLeL48ePMnTu33r6HDx/myy+/JDExkdLSUu68806+/fZbTp46ycVzF9n06yYAZsyYwYEDBzh27Bg//vgj9yy9B221Fh+XmmM/d+4cu3fvZseOHdx///2sXr0ag8EAwIcffsiSJUvqKT8FBQVcd911vPLKK5w4cYLjx48zbtw4AJ544gkOHTrE8ePHuefue3jxqRepNlSzatUqAPbs2cPx48cJCAjgoYceQqlUcvLkSU6cOMFrr71mHuPgwYOsWbOGhIQEAgIC+Pjjj83fHTp0iNdee42EhAR69OjBNddcw6pVqzhz5gxKpZIvvviCwupC3nnrHbzcvdizaw/79++nX79+rFy5EolEglQq5fjx4/z888+cOXOG7OxsvvvuO2JjY1m2bBk33XQTx48fNys/ddN8t0dMro9qWdNujY6M/eksbnZSiZR7B99LqHsoG85u4Ej2EWeL1Go6WwIE031dXl7eLMumRCJhYb+F6AU93537ro2lFGkIqUTK1LCpFFYVdornS0SksyIuTbQAtVzNwYUH23yM5nDx4kVuuOEGKisrGT16NJ9//jnbt2/ntttuw8WlJmD+rrvu4j//+Q9PPPEE27dv58iRmpdzQEAA119/Pdu3b8fV1RW5XG62JE2fPp2oqIYnFhEREUyZMgWAefPmsWTJEtLTawovRkVFMWHChAb3nT59utl6NWTIEFxcXHB3dwcgelA0Z8+dRRAEkpOTuemmm7h06RJSmZSiwiIKLxeiCKxJVnDzzTebExf06dOH/v3789NPPzFjxgw2bNhAfHx8vbH3799Pnz59zEqPVCo1K5Pbtm3j/fffp7S0FKPRSG5+boOJEH755RcOHjyIVFqzlmA5Wbniiivw9fUFYNSoUbXkGDVqFGFhNcU9hw4dik6nIzAwEIBhw4aRcDaBwqpC/vjtDyrLKvl90+9mt7fIyEhcXV2RSqXMnj0bV9caS+Hw4cO5ePFig+fb0nWubtHX9oBRMFKqLUUhU2CQGZpsHxQUZLYAtTX+/v4d2vJjiVqu5vFhj/P0nqd57+h7vDT2Jbq7d3e2WC2msyVACAoKIiMjA4PBQG5ubrPuu2i/aPr59GNPxh7m9pmLn9qvDSUVaYjJYZP59ty3bE3ZyohubestIiIi0jJEC1ALkEgkuCpc2/RfU1nNBg8ezIULFygsLASgR48eHD9+nKeeesq8zZrcjR1TS76z1tbU3s3NrdG2KtU/BTxlMlmtz0q5Ep1Oh9agZf78+dx5552cOnWKLXu34KpxRWH8ZwJfd5wHH3yQDz74gLVr1zJt2jSzYmELaWlp3Hfffaxdu5ZTp07x9ddfo63W1kqEYCt1j8/SlbCxYxckAqXVpcgkMuRSOe+//z4nTpzg5MmTnDlzht9++82mMepSN813e6NCV4FRMOKmaPy+MdHRU1Q7kwDXAB4e+jA6o443Dr1BmbbM2SK1mM6WAMHf3x+ZTIbBYGiRdfO6ntdhEAz8clHMCOcsPF08GRo0lFP5p8irzHO2OCIiIlYQFaAOSq9evbj22mtZvHgxRUVF5u3l5eXm/0+dOpUvv/wSrVaLXq/n008/Zfr06ebvPvnkE6DG5/z7779n2rRp9O3bF71ez59//gnA9u3bG7UqpKSkmNt+++23BAYG0r1761eTTX7T5fpyCgsLiYyMpFpfzfp16ykuKkYha3gSP336dLKysli5ciX33Xef1TajR4/m/Pnz7NlTU7XbaDRSUFBAcXExCoWCbt26IQgCH3zwAYA5EYK7uzvFxf9kWZo1axZvvvkmRmONctTaIHm9UU+ZrgyEmoxCs6+bzdtvv01FRQUAFRUVnD59usl+PDw8askJ9dN8tzdKdaVIkLRZVj+R2gzwHcCtA24lqyKLd46+g8HYtNWtPdIZEyC0xuVykP8gIjwi+CPtDzEjnBMZF1LjXfBXxl9OlkRERMQaogLUgVmzZg0xMTGMGDGCAQMGMHbsWLZv384TTzwBwJIlSxgyZAhDhgwhNjaWiIgIc2rm9957jzNnzhATE8OkSZN45plnGDFiBEqlkm+++YaHH36YmJgY1q9fz6BBgxqUYcCAAWY5XnnlFTZs2GCXmjwyiQyJREKFroJ3332XOXPmMDRuKAnxCYSGhTa6r0QiYfHixfj7+zNo0CCrMS/e3t788MMPPPnkkwwcOJAhQ4awd+9eYmJimD9/PgMGDGDYsGGEhYUhoeZ4qgxVPPLII0ybNo3Y2FhycnJ4++23qa6uJiYmhtjYWHPyhZYgCAI5FTkYBSMquQqVXMUTTzzBsGHDGDFiBAMHDmTkyJE2pTmfPXs2x48fJzY2lhdeeKHFMjkKvVFPpa4StVzdLoOGO0MWOGtMD5/O1LCpxOfFs/HcRmeL02zKdeVklGbQw7OHs0WxK62xbkokEq7reR1ao5bfkztmJsjOwKCAQbgp3NhzaU+nyrgoItJZkAjik1mLkpISPD09KS4uxsOjZkWxqqqK5ORkIiMja7kcdXV27tzJQw89ZPe6QyZyKnIo1ZYS7hGOUTCSXpqOq9yVbm7dmtx35syZXHfddSxYsMCskCkUihZZP4yCkZSSFNQytU1jt5SS6hJyK3NxV7p3uQxOxdXF5FXmEeAagMKoaHfPW3x8PFqtFqVSSUxMjLPFsSs6o47le5eTUpzCyrEr6eHVcZSJvRl7ee/YeyyJWcKU8CnOFqfNsTXzoVEw8q+d/6JEW8IHkz9oNKOoSNvxafynbEvdxmvjXiPCM8LZ4oiIdAmszeOtIVqARNotKlnN5LfaUE1RdREAXiqvRvc5fPgwPXv2RCqVcvPNN5uVn6bq5jSGVCJFLVNTqa9sMzchnUFHflU+cqkcX5Vvm4zRninTliGVSNEoNM4WxSqdJQucNRRSBfcMugepRMqHxz8017zqCBzOPgzUpB7uCtia+VAqkTKrxyzKdeVsT9vuIOlE6iK6wYmItF9EBcjJdITUxA0xceLENrP+AMiQYTQaKa8up0xbVuMWJmvcIjBo0CBOnjzJd999h0qlMse8tDb431XhioBApb6yxX00hCAI5FbmYhSM+Kv9kUlldh+jPaMz6KgyVKFRaJBK2ucrqbMnXAj1CGVO7zlcKrvEt+e/dbY4NqEz6jiec5yeXj3xVnk3vUMnoDmK+LiQcXirvPk16dcOpdR2Jnp79yZAHcDey3ubnURHRESkbWmfs40uhGVqYpHaSAwSJEgo1ZYiIODl4tVkfFHd86nT6Zo8t7YooSYXkgp9RTOPomlKtaVU6ivxUHp0SVeVUl0pgM3Z30Tahlk9ZhHlGcWmC5u4WNRw4pP2wtn8s1ToK4gLjHO2KA6jOYq4QqZgZtRMiqqL2H1ptwOkE6mLRCJhbMhYCqoKSMhPcLY4IiIiFogKkANobILd3lMTOxOFQoFcKkcraFFIFbjKm1YO6p5Pk0LUmJJpixKqkCpwkblQoauwa0Crpeubj6rhoradmXJdOTKJTMz+5mRkUlmHcoU7mnMUoEspQM1lStgUVDIVW1K2iIH4TmJs97FATbyaiIhI+0FUgBxAYxPs9p6a2JkoFArkMjkCAm5KtxZllzMpRI0pmbYqoa4KVwyCgSpDVbPlsEZXd30D0Bq0aA1aNAqNXbIHirQOS1e49pwVThAEDmcdxl/tT5h7mLPFabeo5WomhE4gtTSVc4XnnC1OlyTELYTubt05kn1EdIMTEWlHiAqQAxCtPC1HZ9QhQYJCatu5q6ts2pL5zVYlVCOvCdCv0NnHDa6ru77BPy6FzT1+a2mpO2uqakdjcoX7+eLP7dYV7lLpJXIqc4gLjBMV5yaYFj4NgK2pW50sSdclLjCOYm0xSUVJzhZFRETk/xAVIAfQVlYerVbLE088Qc+ePenXrx8xMTF88cUXNu1bVFTEq6++alPbFStWUFVlH6uHJY8++igrVqwAYNWqVbzxxhu1vq/SV2EwGpBKpBgE27KvWVM2LZWixmJ9YmNjKS0tbbBvpUyJXCqnXFfeYBtb0Rv1dnd9O378OF9//bVNbSMiIto0gUVTvPPOO2RlZVGhq0AiaX7xU2vZsGzNkCXSOJaucKtOrGqXq9am7G9dxf2tNcp9qHso/Xz6cSDzgFgY1UmY7lPTfSsiIuJ8RAWoA3Pbbbdx4cIFTpw4wZkzZ/j555959dVX+e9//9vkvs1RgJ5//vk2UYAsWbZsGY899litbSXaEpDUTMhsTT9dV9k0KTuNpcPW6/VAjQLh7u7eYN8SiQRXuSs6ow6tQWvbgVkZB6CougijYMRP5Wc317fmKEDO5p133iHjcgZV+ipc5a7Nzv5mLRtWZ05V7WhCPUKZ2WMmaaVp7TKF7+Hsw6hkKvr59nO2KA6htcr99Ijp6I16/kz/086SidhCL+9eeCg9OJJ9xNmiiIiI/B+iAtQCBEFAV21o039NBayeP3+eH3/8kdWrV6PR1LhmRURE8NZbb/H8888DNYVKY2NjzfucOnWKiIgIoEbhKC0tJTY2lqFDhwKwcuVK+vXrR2xsLLGxsaSmprJs2TIAxo0bR2xsLDk5Oaxfv54RI0YwePBgBg0axM8//2weY+LEiTz66KOMGzeOHj16mPcHyMzMZMaMGfTv35+pU6dy6dIl83crVqzgoYceAmDNmjVMmTqFxYsWM3PMTGZPms3FpH9ccf7973/Ts2dPhg0bxrPPPms+JpOFp7q6moqKChYtWsTixYuZPHkygwYNYunSpVRVVaFQKLjtttu44447GD9+PNHR0UCNglNUVGQ+l88++yyjR48mNDSUVatW8fnnnzNz8kwmDJzAV+u+Mstz0003MXToUAYOHMjVV19tnqSkpKTg5eXFE088wZAhQ3jzzTcJCgoiKSWJkuoSVDIVK/+9kieeeMLqNX7ttdeIiYlh0KBBjBw5koqKCrKyspg0aRJxcXEMGDCA++67D6PRSE5ODsuXL+fPP/8kNjbWfN7379/P2LFjGTRoEAMHDuSnn34y9//9998zatQoIiMjWblyZa1r+MgjjzB+/HjCwsJ47rnn+O233xg7diwRERH8v//3/2rdh1dffTXDhg1j4MCBfPDBB+bvJBIJL7/8MsOHDycyMpLPP/8cgBdeeIHLly8zf/58Zo6byYXTF6wef2NYy4bV2VNVO5pZPWbhpnDjf4n/43L25XbjXlhUVcSFogsMDhhss2tsR6e1yv2woGF4uXixPXV7u7TodXakEilDAoeQVppGTkWOs8UREREB5M4WoCOi1xpZ/eCuNh1jybsTULg0bBk4duwYvXr1wte3dtHMUaNGkZ6e3uREZdWqVcTGxprdoAoLC3nzzTfJzMxErVZTUVGBVCpl1apVfPzxx+zZswcvLy8AZsyYwYIFC5BIJKSkpDBy5EhSU1NxcXEB4OLFi/z555/odDr69+/P/v37GTVqFA888ADDhw9ny5YtZGRkEBsbS9++fa3Kd+jQITbt3kRcvzgee+Ix3nvrPdZ+tpZff/2Vb7/9lr179+Lt7c3SpUvN+5hc3UyWFqPRyNGjR/nzzz/x9PRk7ty5fPzxxzz99NMAHDlyhL/++qtBq095eTn79u3jwoULxMTE8Mwzz7B//3427dzE4rmLWbxoMVBjzTBNul999VVWrFjBqlWrACguLmbAgAG89tprAJSWlvLuf97lgWceQCPVsGbNGg4cOFBv7C+++ILvvvuOv/76C09PTwoLC3FxccHLy4uff/4ZNzc3DAYD1157Lf/73/+YP38+L7zwAj/++CM//vgjAAUFBVx33XV8++23jBs3DqPRaFbwoMYKuH//fvLy8ujRowe33347ISEhAKSmpvLnn39SUlJCREQEhYWF7Nmzh8uXL9OnTx/uuOMO3N3dWbBgAWvXrqVv375UVFQwcuRIRowYwbBhwwBwcXHh77//5uzZswwbNoxbbrmF5cuX89lnn7Hqi1VE9Isg3CO8gbtUxJloFBqu7Xkt686sY9PZTQxzG0ZWVpbTFcy/s/4GOlfx09zcXLKysggKCrJ6fv39/Vt13hVSBVPCpvDd+e84lnOsy7gOtieGBg5lZ/pOjmQf4crIK50tjohIl0e0AIkA4OHhQa9evbj55pv5+OOPKSgoQKWyXnQ0OTmZK6+8kujoaK677joKCgpITk42f3/jjTcil8tRq9XExsZy8WKN9eaPP/7gzjvvBCAkJIRZs2ZZ7V8QBIYMG0JERAQahYZhI4aRkpxi7mP27Nm4ubmh1+tZvHixeT9T/I9cLjdnfrv++uvx8fFBpVJx6623smXLFrML3Ny5cxt1ebvxxhsB6NmzJyqVijlz5iCVSBk1fBRFhUVk52UDsH79eoYOHUp0dDSffvpprdgahULBzTffbP68eMlivln3DTKDjF9++IXhw4cTHl5fAfjll19YtmwZnp6eAHh7eyOT1RSGfeKJJxg0aBCDBw/m8OHDDcby7N+/nz59+jBuXE01cqlUio/PP/FGCxcuBMDPz4+oqKha13DOnDnIZDK8vb2Jiopi5syZSCQSQkJC8Pf3JyUlhcTERE6fPs38+fOJjY1l9OjRlJaWkpDwT72Lm266CYC+ffsil8trufBU6atQyVXIpeI6THvliogr8FH5sL90P0aZ0enuhYIgsD11OyqZimFBw5wqiz1xRPzalLApSJGyNUVMhuAMYvxiUEgVohuciEg7QZx5tAC5UsqSdye0+RjWMBX2jI6O5vz58+Tn59eyAu3fv5/Q0FD8/f2Ry+UYDP/EzjQWxyOTyThw4AD79u1j586djBw5kg0bNpgnz5bMnz+fV199lTlz5gDg4+NTq29LxUkmk9WKfbGkoexNOqMOhYsCd6U7UokUuUyOXvdPH1Kp1JzowLKPuhnfLOv/KBQKDAYDEonErAC5uTVeeLPucZg+mwqyFlYUcv6v87z33nvs37+fgIAANm3axPLly837ubq6IpX+cy3VvmqGjRrGH5v+4NPVn/LCCy80KkNd/t//+3/k5ORw8OBBVCoV//rXv1ocn9XYdWro2C3bKhQKfHx8Gk2m0NAYAgJGjDbVduqsNLXq3x5QypTM6T2H1SdXk6xOZpi/c5WOc4XnSC1NZUbEjE5VNyooKMh8L7QVvmpfBgcO5lj2MQqqCrps3TFnoZKrGOA7gFN5p6g2VOMic3G2SCIiXRrRAtQCJBIJCheZ3f8hNaIzVIPU2LBy8H9uXhEREVxzzTUsWbKEioqaVMIpKSk88sgjPPfccwBERUWRmppqdof76qt/4lY8PDyorKxEq60J5i8tLSU7O5tx48bx3HPPMXbsWI4dOwaAu7s7xcX/ZA8qLCwkMjISgLVr11JYWGjTeZs6dSqfffYZUBMPtGnTJqvtqvXVALgpaxQUqURaM2EWjEyePJkff/wRo9GIXC7n008/NSs5dbO8SSQSfvzxRyorKzEYDKxdu5ZJkya1Ohufi7zmh6tcX05efh7u7u74+vqi1Wr5+OOPG9yvUl9Jhb6Cu++7mxXLV1BUVMTUqVOttp01axarVq0yn/eioiIMBgOFhYUEBQWhUqnIyspi48Z/arV4eHjUuk6jR4/m/Pnz7NmzB6hxCSwoKGjVsVvSp08fPDw8zLE9ABcuXLBpDDd3N0pLSjvVJLa5dJSsdRO7TyTELYRfk351ehaxLSlbgH9SO3cWHBW/Ni5kHEaM7Lu8r03HEbFOtF80ekHPuQKxJpOIiLMRFaB2RGMFU02Te5lMZrZ+fPnll0RERBAdHU3fvn2ZOXMmjz32GHfddRcAwcHBPP744wwfPpyRI0fWcn/y8fFh0aJFDBw4kKFDh1JcXMz1119PTEwMAwcORKfTceuttwLwyCOPMG3aNHMShHfffZc5c+YwePBgjh07RliYbYUI3333XQ4cOED//v1ZtGgRkydPrtfGKBipNlQjlUjNK2SWGdxmzpzJtddeS2xsLMOGDcPd3R1PT0+zAmR5/gRBIC4ujpkzZ9KvXz98fHx4/PHH7ZaO3CgYGTtlLH369DG7mlkmnbBEEAQKKguQIGHa+Gl4enpyzz33NKjo3nLLLdxwww2MHj2aQYMGcdVVV1FdXc2DDz7IwYMHGTBgALfcckstBWrKlClUV1czcOBAli1bhre3Nz/88ANPPvkkAwcOZMiQIezda79q5HK5nF9++YXvv/+egQMHMmDAABYvXkxlZWWT+96+9HaeefAZRg4d6dR03M6ko2Stk0ll3NjnRqoMVXx//nunyVFcXczBzIP09+1PqHuo0+ToyMQFxqGWq9tlZr+uQH/f/gCczj/tZElEREQkQlPpxroYJSUleHp6UlxcjIeHB1DjOpacnExkZGSDcTH2wDSJVygUIIPLZZep1Feilqvxkfkgpcb1y9X1H7ehiooKBEGot72jUq4rJ6s8C1+VL14qLwByK3Ip0ZYQ4RGBTCqjtLQUd3d3BEHg4Ycfpry8nA8//BDAfP4UCgWLFi0iOjqahx9+2O41mIyCkbSSNGQSGd3duzdZjNF0XJ4unlQXVDN06FDOnTvXaAxSZ8UoGEkpSUEtU9PNrVu97x31vInYjiAIPLv3WVKKU3h70tsEuAY4XIYfzv/A14lf89CQhxgVPMrh43cWPjrxETvTd/LmhDdFRdLBGIwGFm9dTJh7GC+MaZ77s4iIiG1Ym8dbQ7QAtSMsa9hcLrtMmbYMg9FAmbaMfH1+rQKf1ixCnYFSbU0hUo1SU+87gRpdfdGiRQwePJj+/ftz6dIlXnnlFbPSY1ICTW6BSqWyTc6NVCLFXemO1qilUt+0xaOougiJRMI7L7/DiBEjePXVV7uk8gM1Lo6CIHRp97eOhkQiYX6f+egFPZuTNzt8fKNgZFvqNrxcvDpV8gNnMC6kJq5zb4b9rMEitiGTyujn04+LRRep0rdtbT0REZHGEZMgtEN0Oh0Vuopa26r0VaCo3UYQBAwGQ6ew/EDN6liFvgK1XN1ofY8ffvih0X5M52bVqlVtem48lB4UVRdRoi3BVdHwONX6aqr0Vbgr3Vn54kpWvriywbZdgUpDjcKokovWnY5EtF803d26s+vSLub3ne/QIO6j2UfJr8rnhl43iFkDW0l/3/74qHzYm7GXeX3mNbsIsUjr6O/bn6M5RzlXeI6B/gOdLY6ISJdFfPO1Q3Q6HS7S2pMLpVRZK77FMgOatZihjki5rhxBEHBTWM/OJqFxNzMTpnPT1lYxhUyBq9yVCl0FeqP1THcAJdoSoEZhEqlR5i1jvEQ6BhKJhGkR0yjXlbMvw7FB9FtTtyJFypSwKQ4dtzMilUgZGzKWnMoczhWKwfiOZoDvAAAS8hOaaCkiItKWiApQM3BUuJRCocBf6Y9GoUEmleGmdCNYE1xrUm/621DShI5Ima4MiUSCRlHb/c3k+tZUnI0JS1fCtsbDxQMBwazk1MVgNFCmK8NF5iJO+KlxZaoyVKGSqRq8nkajWKm+vTI+ZDwqmYotqVsc9j7MKs/iRO4JhgUNw1ft2/QOIk0yJngMAHsu7XGyJF2PCM8IXOWuYiIEEREnI/oS2IDJopCbm4u/v7/NE/FWjSlTECSzyA5lrKl/YzAYMBgM6HQ69Hq9OQGCwWDo0HFAeqOesooy1HI1Oq0OHf8oddVV1Rj0Bqqrqs11fPR6PXK53Ooxm76XyWQYDAbk8prbvLF9WopUkIIeCssKUQvqevdGqbYUXbUOd5U71dXVdhu3o1Ktr0ZfrUfqIq1Xv0gQBLRaLbm5uUilUpRKpZOkFGkIV4Ur47qPY1vqNi4UXaCXd682H3Nb6jYApkdMb/OxugrhHuF0d+vOgcwD3BF9BzKpzNkidRmkEil9fPoQnxdfU/OuEXdvERGRtkNUgGxAJpPRvXt3Ll26REpKitPkMCk/pol9XTryhLFKX0WprhR3hTtl8rJa3xVVF2EwGjCoa47ZVLsIrB+z5ffWsPd5qtBVUK4vp0RRUi+upaCqAEEQ0Kv05Epy7TpuR6RSX0mZroxKZSUFMuv1glxdXQkLC6tVQNZedITio+2daeHT2Ja6ja2pW9tcASrVlvJH2h+EuIWYXYdEWo9EImFEtxF8d/47zhWeo59vP2eL1KWI8oziWM4xLpVeItIz0tniiIh0SUQFyEbc3Nzo1auXU93Nzp07h16vRyKR4O/vT2Zmpvm7bt261arz09H4NP5TzuSd4YUxL9TLALdy/0pc5C481v8xAAoKCsjLy0OtVlNZWYmfn1+tY7f2PUBeXl69tvagUlfJSwdfQi6V8/SIp1HKahSs84XnWXt8LeO7j2do1FC7jtlRWXdmHYezD7NyzEqrmf5kMhlyubzNrKyWxUdFBahlhHuE09enL/sv7+eW/re0aWzbD+d/oFJfyZxecxxiee9KxAXG8d357ziSfURUgByMSelJKkoSFSARESchKkDNQCaTIZM5z1WgW7dutVavFQpFp1jNrjZUczD3ID29e+LrUdvHXxAEUipTGOA7wFwTJjg4GIVCQVpaGgD5+fkEBweb9wkODq712XJ7W6BSqZgUNYkvE75kZ9ZOZvWYBcC2y9soMBQwMXKiWM/m/0goTkCmlNW7zo4iKCjI/MyItJzp4dN5r+A9dqb/c7/bm7zKPLakbiHCI4KRwSPbZAxH0R4tj5GekXirvDmcfZib+9/sbHG6FFGeUQAkFScxBTGxh4iIMxCTIHQg/P39iYmJMf+A1v3cUYnPjUdr1DI0sL6VpFxXjs6ow1vlXWt7VlaW+f/WJrO5ubnEx8eTm+sYt7Pp4dPxV/vz04WfKNeVU1hVyKHMQ8T4xVgt9tkVqdJXcbnssvnH3xl0lmfG2QwPGo6n0pNtKdswCm2TtGLjuY3ojXoW9lvY4VM1W1oe2wtSiZS4gDgyyzO5XHbZ2eJ0KXxUPngqPUkuTna2KCIiXZYO9auye/durrnmGoKDazKi/fjjj7W+FwSB5cuX061bN9RqNVOnTuX8+fPOEbaNaGxi7+hJv704mnMUqHHJqEthdSEA3i7e5uNLSkoyx0KFhYVZncw6esKhkCmY12ceZboyNl3cxN9Zf2PEyOTQyQ4ZvyOQWpKKEWOLXD466r3dWVHIFIzrPo6cypw2mcSll6azO3030b7RDPTr+LVSgoKCUCqV7c7yODSoZtHpSPYRJ0vStZBIJER5RZFamorO2DmyuIqIdDQ6lAJUXl7OoEGD+M9//mP1+9dff5333nuPVatWcfDgQTQaDTNmzKiXbcreOHJyZjmxt1QI4uPjycjIaHerjE1hFIwcyT5CiFsIQZr6k4P8ynwAvFXe5mMvLCw0K0ANreQ7Y8IxNmQsoe6h/Jb0G/su70MukRMbEOuw8ds7polylFfzLUDtcQW9qzMsaBjQNpPnb85+gxEjC/ou6BSxP+3V8hjtG42LzEVUgJxAlGcUeqOeS6WXnC2KiEiXpEMpQFdeeSUrV65k9uzZ9b4TBIF33nmHZ599lmuvvZaBAwfy5Zdfcvny5XqWIkuqq6spKSmp9a+5OHJyZjmxt1QItFqtuX6KRlM/uLy9klSURFF1kVXrD9RYDQC6u3c3H7u3t3ejyo2z/O2lEinz+8ynylDFvox99PPth6vC1WHjt3eSipMAWuQC115X0Lsyvb17465w53D2Ybv2m1iQyKHsQ4wIGkFP75527dsRdCRrpUKmYKDfQBILEhusZdZZcfZ1MidC+L/3YlfB2eddRMREh1KAGiM5OZmsrCymTp1q3ubp6cmIESPYv39/g/u98soreHp6mv+FhoY2e2xHTs4sVxLrKgSmtMHl5eUd5iVjmjxZi/+Bmh8HCRIiPSPNxx4VFdXoampDCqkjzklcYBx+Kj+Kqovo6dXxJm9tyaXSS3irvHFXujd73/a6gt6VkUqkDA4cTGpJKnmVeXbpUxAENpzdgBQp8/vOt0ufbU3d90pHs1bGBcZhxMjxnOPOFsWhOPs6hXuEA3Q5C5Czz7uIiIlOowCZHqbAwMBa2wMDAxt90J566imKi4vN/9LT05s9trMmZ3UVgpCQkHrWofb+konPi8dN4dZgPZGk4iSC3YJRy9U299mQQuqIcyKRSAhyqxk3qahrrew1hiAIXC6/TLCmbTLxiTgH08KFvaxAJ3JPcKbgDBNDJxLs1v7uFWuLKHXfKx3NWjk4YDAAJ3NPOlkSx+Ls6+Sn9kMhVZBZntl0406Es8+7iIiJLp8G28XFBRcXF2eLYRf8/f1rKWHtPd1vha6CpKIk4gLjrGZ5KtOWkVORw9iQsc3qt+55MOGIFMhGwUh6STpBmiBO5J0gqSipRTEvnY0SbQmV+kq6abpRpa8itSSVlJIUCqoKKKoqorC6kKKqIir1lRgEAwICcokchUyBl4sXXi5eeKu88VZ5092tO1FeUW1af0bENgb6D0QulXMk6whXRFzRqr6MgpH1Z9ejkCqY03uOnSS0Dya3WlMxass6UnXfKw29f5o7lqNceL1UXoS4hXA6/zSCIHSKmCtbaO11ai1SiZRA10Ayy7qWAuTs8y4iYqLTKECmH5/s7Gy6dfsn7XB2djaxsbFOksp58Sgd4SVzrvAcRoz09+1v9Xtz0Hwr0yZbXoOYmBjzKq5Go6G8vNyu1+Zc4TlKdaVcHXU1O9N38kXCF/x71L87fBrf1lCiLeHniz+TU5HDn+l/siNtB0Zqp052kbng5eKFRqlBJqmptWUUjFQbqkkpSaFcV16vXz+1H1GeUfT16UtcYJzVJBoibYtaribaN5pTeaeo0FW0Kubtz7Q/SS1JZVaPWfiqnVMnqiEyMjIwGAzmz5ZxlvZ+1zqjWO8A3wFsTd1KdkW2+Bw5kGC3YA5nHUZn1KGQKpwtjohIl6LTKECRkZEEBQXxxx9/mBWekpISDh48yN133+00ucTK8w1zOv80AAP8Blj93hQc2tpK2XWvgemzVqs1f2+va2PKpjQtfBpKmZItKVvYkrKFKyOvtNpeEARKdaUUVhWa/xVV/2MRKawuRGvQYhSMGAQDBqMBo2BEL+gxCkYkEgkyiQypRIpMIqv5J5Uhl8hxU7rh7VJjNfFSeeHt4v2PJcXFG4Ws7X5wcypyOJB5gCPZRzhXcI5ibTFl2jJkbjIGBw4m0jOyJq7L1R9vF2/UcnWjK89ag5bCqkLyq/JJK0kjqTiJpOIkDmcd5u+sv/ky4UtC3EKIC4xjZLeRRHlGdZmVbGcTFxjH8dzjnMg9wajgUS3qI68yjy8TvsRH5cPsnvWT3LQ3ioqKOHLkCN7e3kRF2dfC64xivf19+7M1dSun80+LCpADCdIEYcRITkUOIW4hzhZHRKRL0aEUoLKyMi5cuGD+nJyczPHjx/Hx8SEsLIyHHnqIlStX0qtXLyIjI3nuuecIDg7muuuuc5rMYuX5hjmddxo3hRuh7tYTT8TnxSOXylutANW9BqbPlhYge3E46zDeKm8iPSMJdgvmaNZRPj/1OXqjHp1R94+iU13zt7i6GL2gt9qXFCnuSndc5C7IJXKkEikuchezwmOyKhmMBgyCwawkVeurKRfKuVR2iWpDdYOyuincarmWebt446P2Icw9jAiPiGav5hsFIydzT7IlZQvHco4hIKCSqRgWNIxSbSnxefG8Pv71FhWGVcqUBGoCCdQE1rIYVumrOJV3iiPZRziac5RNFzex6eImIj0jmR4+nTEhY3CRdQ4X17aitVbquMA4/nvqvxzJPtIiBUgQBFafXE2VoYqH4x5uN5kTLc9LSEgIGRkZ5kybgiAAUFhYaPdxnWG9Nz1TCfkJTAmb4tCxuzKmmMjMskxRARIRcTAdSgE6fPgwkyZNMn/+17/+BcCtt97KmjVrePzxxykvL2fJkiUUFRUxduxYfv/9d1QqlbNE7hCuaM6gQldBcnEyQ4OGWnUPq9BVcCb/DNF+0c1KgGALbXVNCioLSC1Jpbd3b75K+Irk4mSyK7NJKU7h9UOvm4O6pUjxVHni7eJNuEc4PiqfWhYa02dPpScyqazF8giCQKW+0mxNKqgqoKj6n78ma9P5wvNUGerXyuqm6UaUZxSRnpH08OrRoFKkM+r4I+0Pfk36lZyKHKRIiQuMY3LYZAb6DUQhU/D6oddRypT4u9r3vKvkKoYGDWVo0FCMgpGLRRfZdWkXey7t4eOTH7P2zFqmhE3hmh7XiDFDDdBaK7Wv2pdQ91CzRbe57EjfwYncE0wMndiu6mZZnhdTkpvc3FzS0tLMbby9vZ0oof3wdPGku1t3EvITulQckLMx/SZcLr/sZElERLoeHUoBmjhxonnlzRoSiYQXXniBF154oc1kcFZMT2fjbMFZjBgZ4Gvd/e147nH0gt5cqbw1tIUbolEwklmeycWiiyQXJ5NcnEx8bjzppelU6itJLklGJVMR7RdNoGsgycXJXBV5Fdf2uBYPFw+HxARJJBJcFa64KlybXF2s1FdSVFVEbmUuKSUpJBcnc7HoInsv72Xv5b3mdpZKUZRnFJnlmfx04SdyKnPwUHowu+dspoRNqafoZJVnEeAagFzadq8cqURKL+9e9PLuxcK+C9mdsZutKVvZdHET21K3MavHLK6MvNLuCnV7p6l3lj2s1D28erAzfSfF1cV4unjavF9eZR5fnq5xfbu1/60tHt9eWJ4ra+clIyPD/H9vb2/Ky8tJSkqyGkvY0X4rBvgNYEvKFrLKs1pkpRVpPiZ3w8tlogIkIuJoOpQC1B4QY3rsQ0J+AkCDCRBMsTRDAoa0eizLiUxLJyWCIJBaksqR7CPE58WTXJxcy2qikqlwU7rh6eLJHdF3MDZkLIGaQKQSKZX6Sh7f9TjbU7czI2IGXhKvVh+TvVHL1ajd1HRz68ZA/4Hm7eW6cpKLk0kqTqqlFP2R9gd5lXloDVpUchVDAoYwt89c4gLj6ikYgiCQV5lHH+8+DjseV4UrV0RcwfTw6RzIPMA3Z7/hm8Rv+D35d27seyOTQyd3mVXupt5Z9rCIRnpGsjN9J8nFyTZbcdqj65s1q481ZDIZ5eXljcYSdrTfigG+NQpQQn6CqAA5CA+lByqZivzKfGeLImIDHW1RQ6RxRAWomYgxPfYhIT8Bd4U73d271/vOYDRwPOc4kZ6RdskGZTnBi4+Pt3lSojPqSMhP4Ej2EY5kHzEXe1TJVER5RRHl+c+/QE0gbx5+E51Rx3U9r6uVYEAtV7Msdhkv7H+BVSdWsXzU8g6TFU6j0BDtF020XzRQE3Oz5vQafkv+zew2o5AqyKrI4v1j7yOXyon2jSYuMI64wDh81b5U6iupNlTjo/JxuPxSiZTRwaMZHjScP9P/5Ntz37L65Gr2X97P0oFL7e6S1x5xxDvLlKkxqTjJZgWoPbq+NXWuPDw8KCwsxMPDA3d391rucHX36Wi/Ff18+wGQUJDAlPD2EQdkOeEEOt3kUyKR4OXiRWG1/WPJROxPR1vUEGkcUQFqJmJMT+vRGXWklqYy0G+gVUUgsTCRMl0ZVwS2rq6INZqalJRpyziac5Qj2Uc4nnPcbOUJUAdwVeRVxAXG0denr1VXruTiZLq7d7eaXW2A7wBmRMxoMitceyYhP4GPTnxETkUOQwKGsGzQMrMLR05FDkezj3I4+zCn8k5xPPc4/z3135r4Ic8eVBuq8XLxcprscqmcaeHTGBM8hq8SvmJH+g4e3fUot/S/hSlhU7qMNaitiPCIQIrU5uK/7c31zURT7/fy8nLzX1P2t4Ym5R3tt8JD6UE3TTcuFl10tihm6haZtTb57Oir8t4qby6VXnK2GCI20NEWNUQaR1SARBxOemk6eqO+wexuey7tAWBY4LAWj9HQj6K1SUlmWSaHsw9zJPsIiQWJGDEiQUJPr57EBcYxNGgo3d26NzpJNiUZGOQ/qME2C/ou4Fj2MdafWc/ggMEdJt2sIAj8dPEnvj77NQqpgtsG3MaMiBm1lNcA1wCuiLyCKyKvoEJXwfHc42YlMiE/gcyyTL6/8D3l+nKGBg5lgO+ANk3D3RCuCleWDlrKiG4j+Pjkx3wS/wnxefHcPehuVHLnJUtpS0yTyIyMjDabKCplSrq7d+dicdOTZ0EQ+PjEx+3K9a0xLN8lGo0GrVZrrgPU0ZQcS6y9IyM9I9l3eV+razrZi7oTTmuTz46+Ku+t8uZMwRl0Bp1T3okittORn3eR+ogKkIjDMa0SWytwWqYt46+Mv+jp1ZMIz4gWj9HUj2JhVSE/JfzEnvQ9FBmLkCvkKKVKhgQOYWjgUIYEDmlWMLepZlFjRVs7oiuc1qBl1YlV7L28lzD3MP4V968m4wNcFa6MDh7N6ODRGIwGvj77NZ+f/hy1XM221G1sS92GSqZicMBgpoRNIdov2uEWmNiAWN6a8BarTqziQOYBssqzeGzYY/ip/RwqR1thLZjfYDDYtILe0hX1KK8omxIh7Ejfwcm8k+3K9a0xTO+StLQ0ZLKarIwmS1BHxto7Msozin2X95FcktxgghpHUnfCaWsSj45kFTJZxouqi7qES66ISHtBVIBEHE5ycTJgvcDprku70Bq1zIiY0aoxrP0oCoLAmYIzbE3Zyt9Zf1NWUYZGqiHWI5arB15NtF80SpmyRePZogBBbVe4bxK/YUHfBS0azxHkV+bz5uE3SSpOYnjQcO6JvafZGdRkUhkeLh74qn15evjTuLu4cyT7CIeyDrE/cz/7M/cTrAlmWvg0JoROQKPQtNHR1MdV4cpDcQ/x/fnv2XhuI0/veZpHhj5CHx/HJWtoKywnt6ZnwMPDw2rdq4YKBTd3Rd2WRAhJxUmsObWm3bm+NUZQUFCtWB+lUtkpXGCsvSPNsVxFSe1CAbIFa6vyHckq5K2qSaVeWF0oKkAiIg5EVIBEHL5allSchKfSs15QvFEwsi11G24KN0Z1a1lFeROWP4oVugp+PfMrvyf9Tq42F4VCweBugxnRYwQB2gCCuwW3+riTipOQSWSEe4Q32faWfrdwsegiP174kTD3MMaEjGnV2G1BTkUOL+5/kZzKHG7odQNzes9psbWqoKoAqKkX4+/qT4hbCLN6zCKrPIvtqdv5M/1Pvkj4gq8Tv2ZM8BimR0xvdfFbW5FKpMzpPYdQ91A+PP4hKw+s5NFhjzbqytgRsJzcmiaDADExMY22tfbZVppKhFBcXcwbh95AQOBfcf9qFy5WJhoKtjf935TyuiNYFGzFmuJgeu5Mi1Ttheb+RnWkWA1vl/9TgKrERAgiIo5EVIBEHLpapjPqSCtNY6DfwHpuT6fzTpNZnsmsHrPs4gudVpLG1tSt7Lm0h8KyQlwkLozwHMFov9FMipvUdAfNILUktcEECHVRyBQ8MvQRnv7raT468VFNbR2vxi1HjiS7PJvn9z9PQVUBSwcuZXLY5Fb1V1RdBFAvCUKQJoib+9/MvD7z2H95P1tTt7IjfQc70nfQy6sX0yOmM6rbKIf4xY/oNgI/tR8vH3yZ1w+9zmNDH+sQ7lkNUXdy29hk0JqbUUveA6ZECCnFKfW+0xl0vHn4TQqqCrg39l56efdqdv9tiWWclMFgMG8DzKmuw8LCOlXwvTVcFa4EuQaZLdrtheb+RnWkWA1LC5CIiIjjaN8BCCIOISgoyGFuHY0lQNiauhUJEqaEtTwFq86oY2/GXv697988tvsxtqVuI0gTxB397uCxqMe4OvBqosOiW3MI9TAKRgqrCvFX2/6D66Py4bGhjwHwxuE3KKoqsqtMLSWvMo+VB1ZSUFXAPbH3tFr5ASjVlqKWqxtUZJQyJRNCJ/DS2Jd4eezLTAydSEpJCv85/h/u/uNu1p1ZR05FTrPHzc3NJT4+ntzcXJva9/DqwXMjn0MlU/Hm4TeJz41v9pjtEX9//0Zr2tgLpUyJp4sn+VW1a5oIgsCnpz7lXOE5ZkbNZHz38W0qR0swvQPrbrN8J6alpdW6l+pmKOssRHnVFDiu0FU4WxQzjvyNcjQeSg+gJv5VRETEcYgWIBGHrpY1lAAhrSSNQ1mHWpwdrbi6mM3Jm9mRtoNibTEKqYLx3cczPXw6Pb16tmmQfam2FINgMK/k2UoPrx4sHbiUD45/wFtH3mL5yOVOzQJUqa/klYOvkFOZw92D7rbbRLVKX2Vz7FAPrx7c7XU3t/S7hV2XdrE1ZSubLm7i54s/ExsQy9WRVxPjX9+NyxotyX4W4RnBMyOf4cX9L/Lm4TdZOXYloe6hNo0nUrOabbL4mfg95Xd2pu8k1j+Wm/rd5BS5msJ0X6SnpwPg7e1d614xxQBZWiA6kptVczBlgmsviRCgY1l0mosp+2SVvqqJliIiIvZEVIBEHEpKSQpQPwHChrMbEBCY23tus/qr1FfyS9Iv/HLxF6oMVQSoA7ip301MDJ1oXllra0y+2yZf7uYwrvs40krT2HRxE5/Gf8qyQcucUpPGKBh5/9j7XCq7xMK+C5kYOtFufVfqK1HJmpdi2k3pxtVRV3Nl5JWcyjvF1pStHMk+wrGcY8T4xbCg7wJ6ePVotI+Gsp815boU5RnFY8MeY+WBlbx+6HVeHvsy7kr3ZsnfVfFy8SKtJA1BEJBIJMTnxvPl6S/ppunGA0MeaHXWw7Z0O8vKykIQBKB2ljfTOHWVnbqTcntl03M2psWplOKUdqMAdWZMClClvtLJkoiIdC1EBUjEoWSWZaKSqWolQNh/cT97U/cyMmikzbEwOoOO7Wnb+f7895RoS+im6caNfW5kRLcRDk8tbY5xUXm1aP8FfReQXprOzks7CfMI4+qoq+0nnI38L/F/HMk+wtiQsczqMcuufVcbqlukjObm5pKRkQHArRG3smjAIr479x27L+3m6b+eZkS3EczvM59gt2Cr+5smqElJSRQWFiKXy4mPj8dgMGAwGBqNJ+jv2587ou/gk/hPePvI2zw94mmrxW9FauOj8kEv6CnVlVKhq+Cdo++gkqt4fNjjLc7wZ6lI1HU7M90fISEhLVYyTP1rNBpz/I9GoyE+Pr5eUoTGxrBXNj1nE+IWAkBmeaaTJWmajqpkWqKW1VjHTUW3RUREHIP4iy7iUC6XXybYLdhs5RAEgfVn1iMRJIxybTrzm1Ew8lfGX2xM3EhOZQ7eKm+WxCxhYuhEZFJZW4tvFVOWs5ZYgKAmE9kDgx/g2b3PsjZhLd3duzs0C9mhrEP8cOEHenjWuOTZ2wJVqa8kwDXA5vamSY1JUYGayWVMTAx3x97NzB4z2XB2AwczD3Io8xCTwiYx0Wci1YXVVidCptX8ioqamAaZTGZTPMHU8KmklqSyNXUr68+sZ9GARc057C6JaREgsyyTj09+TIWugieGP9GgkmoLlq6MUHP9LK17pjbNnQDXvc8MBkOtvi0VLVvqJ9krm56z8XLxQiVTcbnssrNFaZKOqmRaopApkEvkogVIRMTBiAqQiMOo1FdSUFVAP59+5m1Hso9wWXeZYd7DGk1OIAgCx3KOseHsBtJK09AoNCzsu5ArIq/ARebiCPEbxGQBam4MkCWuClceG/oYz+x9hnePvstLY15qsuCoPSiuLmb1ydW4K9x5ZOgjLa6D1BhV+iqzm4ctmCY1MpnMXHjSchIZ6h7K48MeJ7Egkc+Of8Yvib+wmc2M8BzBJMOkehMh00RUo9E0O5XxrQNuJaUkhd+Sf2NY0DD6+fZreqcujGkR4JOTn5BRlsHN/W5udTY9S2XHYDCgVCrN18+kFLVEybC8z5RKpdlVsq6iZWobFBRkkzXKREeNW5FIJAS7BXcIC1BHVTLropKrxBggEREHIypAIg4juzwbwDyx1xv1bDi7AVcXV+4dd2+DCkRiQSIbzm7gTMEZFFIFs3rM4toe1+KmdHOY7I3RWguQiW5u3XhwyIO8evBVXj74Mv8e/W/81H72ELFB1pxeQ4m2hAcHP4iv2tfu/euMOvSCvlkxQEFBQWRkZGA0GpFKpXh4eJgnmZYTyj4+fZjvPZ9EeSLbC7azr2gfR0uOMkuYxdyBc3GRuTTpImPpZmfNjUoulXPPoHt4bPdjfHTiI14f/3qzlLmuhqeLJ7kVuZRqS7kq8ipmRs1sdZ8mRaJurZ7WKhiWk2fL/q0pWqa/8fHxtQrMWv5tjTWqvdFN042k4iQq9ZXNLn7sSDqqklkXUQESEXE8YhpsEYdxubzGpSJYU+MO89OFn7hUdolrelxjVflJL03n9UOvs3zfchILEpkaNpV3J73LTf1usovy09w0yQ1RVFWEFCkeLq1PujDIfxDLBi0jp7KmEGlbFsc7mHmQfZf3MTxoOKOCW1d4tiFMP+rNmUT5+/sjk8kQBAGDwUBhYaF50ln3mkkkEnpperEsdBnXBVyHi9SFb859wx0/3cH38d9zOfNyo6mKLSe8GRkZVu+Hbm7dWNB3AdkV2XyT+E0Lz4T9sNd9a28EQWBn+k5KtaUEa4Lt7k5pz3Te1hRjU/8hISEolUpzHJDlebZMx2wpT1BQkNli2dGtEfDPIpVp0UqkbVHJVGIMkIiIgxEVIBGHkVVeMwntpulGakkq35//nlD3UGb3nF2rXZW+ijWn1vDYrsc4kn2Ekd1G8tbEt7hr4F3NtlI0Nlm0Vx2PwupCPFWedku+MCF0AnfG3ElWRRYvHniR4upiu/RrSZW+is9PfY6bwo07Y+5ss8xzWoMWoNmudZYTSm9vb/Oks+41CwkJQSaTIUHCIPdB3Bd2H1f6X4nBaGDN2TV8kvkJOYacBielpnEkEkmtbHEmTPfPULeh9PHuw+bkzVYLfTqS9lh/RhAEvkr4igOZB1DL1YzoNsKpKd2borFzaFJsysvL6yneQC0lzHJ7bGwssbGx9b5rb4qqLZgWqUyLViJti4vMhWpDtUPH7Mj3p4iIPRAVIBGHYQqq9Vf78+HxDzEKRu4ZdE+tiVJCfgKP736czSmb6eXdi5fHvszDcQ+3OIi6sYmOvYrrlWnLcFfYN03ytPBpLOq/iIyyDFYeWEmpttSu/W9O3kxhdSHz+szD08XTrn1bIlCTVri5yqG/v795QhkVFVVrpd1ydR4wxwkB+Pv4M9pnNA9HPczsqNkUGgr5MudLtuZt5XL25Vo/+CYrQEhICArFP/egwWAwtzHdPznZOdwRfQcCAhvObmjVOWkt7bEo5DeJ3/Br8q/08+lHkCao3Qd023IOLds09B5p7P3SHhVVW+mmqbEAZZY5Ng6oq07KJRKJOQW7o+jI96eIiD0QY4BEHEZmeSbeLt5sTd1KSkkK1/e83pz2ukpfxddnv2ZzymaUUiWL+i/iysgrW21VaSxI1l7+40bB2CYpkq+OuhqdUceGsxtYeWAlz4x8xi61jUq1pfx08ScCXQOZEjbFDpL+Q13XIqNgRK/Tk5WZRa5/bovro9SN1bGWGaxuDM8IRjAkdQirTq5iY8JGtku3MzugxtpYN01x3UB7U/FUjaYmdXNQUBD+nv6MCR7D3st7OZ1/mgG+AxAEAb2gxygYMRgNGAUjRozIJDKkEilyiRyZVGbX1OztKe5BEAQ2ntvIDxd+oKdXT54c/iSLty7GKBidLZpV8irzePavZzmVf4po32hW9lxptZ21e9TyPVI3dbZJaba8Lh05QN9UjNrRiRA6Q1a3liCVSDHi2GemI9+fIiL2QFSARBxGVnkWXi5eZte363tdD8CZ/DN8dOIjsiuy6ePdh7sH3W23DGiOmCzqBX2b1R4a4zmGNE0au/J38cL+F3hu5HOtttj8dOEnKvWV3BVzl90Vt7oTGKNgRKfX1aq705JJTt0g88Yyg1kiLZFyR7c72Fu0l50FO/k041OuVl5NX0NfNBoNWq0WjUZTL9Ber9eTX5HPpcpL+If5c6LiBAWJBeiNenLKc3hk5yOEe4RTUl1i08RFJVPhrfLGy8ULH5UPXi5eeKu8a/651Pz1V/u3a7exugiCwLoz6/g56Wd6ePbgqeFP4aZ0Q4oUg2Cw2zj2rPXy7F/PciDzAAbBwP7M/Ty4+UEei3qsnvLc0D1aWlpaL0W7TCaz2tbynjLVFOook3pXhSueSk+z27Kj6KqTcgmOtwC1p4UUERFnICpAIg5BZ9BRoi0hpyIHN4Ub9wy6B4NgYN2pdfye8jsKqcJuVh9HYzAakEnapgZRVlYWYzzGIJfI2VW6ixX7VvDsyGdbnLGtXFfO1tSthHuE2zXxganYqKurK1A7LbFCrkAukzdYH8WWTG0GgwGJRIJUKjW3s1RYlN5Klm1b9s/K/tiV+Kn9zBnlxvuMp797f34v/Z0d2TtI3JPINNdpBMgCKCsrI7cil6TiJJKLk0mqSCIxN5HCykKkUinGVGPNMShqXpcKmYLi6mLkEjlxgXEoZUqzlUcuqWljFIw1ViHBgEEwUKotpbCqkPTSdM4UnLF6DqVICXUPJdIrkijPKKI8owj3CG+T1OStxSgYWXN6DVtSttDbuzdPDX8KV0XNtZdK7acA5ebmkpaWBtgnu9qp/FNm2YyCkaSKJKtFceveoxkZGeYYMahfS6qxSbultbIjFe30VftSWN12SVis4YhJeXssnuoMFzgRka6OqACJOIS8yjxyKnJQSBXcO/heqg3VPL778Tax+jgao2BssyKsponY3AFziSqP4vNTn7Ni/wqWj1yOv2vzf7x3pe+i2lDN1ZFX203RzM3NpbCwZqJUUVFBXFyceXtiWiIA3YK71cq2ZRko3tQE1zT5lMlkxMbG1vrO1NeybcvYn7kfo2Bkf+Z+nv3rWVZNW2W2OBkMBnzlvtwdeTc/p/7MjrwdnDCcwFvmjVKupCyxzKzkKKQKwr3CGRc+jqr8KtSCGje5G31C+9AjuAdl2jIe2vkQPbx68K+h/2r2+dIZdBRWF1JYVUhRdRGF1YUUVBaQUZZBUnESO9N3sjN9J1BbKYr0jKS/T39C3UPbLGmFLRgFI5/Gf8ofaX/Qw7UH17pdS3lROa7+NQqQTCLDYLSPAmQZn2CK+2rNxDXaN9psAZJKpES5RjUrc5tEIkGhUNSToTF5LK2VHcm9y8vFi7SSNARBcOr9Zk9MCzWAVYXUWcqRUTB2uIU/EZGOjqgAiTiEdWfWUaGrYHTwaDLLMllzag1yqbzDWn0sMQX6twWWysIV/legkCj4JP4Tlu9bzuPDHifSM9LmvgRBYGvqVtwUbna1/lhOUr29vc2TCJ1OZ/7X0Oqm5b6tcXs5lX/KHHdiFIyczj8N/GM9KtIVca7iHGcvnyWlMgW9UU+lsZJSfSkarYY4jziiPaOZOHAi3d27m10DLRU0isA9wh13pTuD/AdxKOsQ+ZX5zbbGKWQKAlwDCHANsPp9cXUxScVJJBUl1fytoxQFqAOIC4wjLjCOvr59UUgd5zZXbajmo+MfsT9zP4P8B3GF6goEvVBrUi9BYrdnom6tnboKREMT1oa2rxy7kmf/etYcw2WyFNal7limuLOWTIwti7Z2pDTZPiof9IKeUl2pXWIP2wMm5cdE3fvJWTFInUnJFBHpKIgKkEibszN9Jzsv7UQhU5BbmcvvKb93eKuPJTKJzGFB31PCp+Aid+GjEx+xfO9y7om9x2Zl5lTeKTLLM5nVY5Zd3apMsTTe3t5ERUWZi0VCzWQYaPD81C1GaQ3LyWdDE9to32izBUgqkTLAdwCHkg6x88JOEisSydHmgKQmHXeUOoremt6Eu4ZzvvI8ewr3cL7yPL0CexHiFlIrLsrf399sgbJkevh0TuSeYEf6Dub2ntuq81cXTxdPBgcMZnDAYPO24upiLhZd5GTuSQ5nH2ZzymY2p2xGLVczOGAwcYFxxPrHtmlx4LzKPN449AYpJSmM6DaC+2Pvp6igqJ77l0Gwn0toXZeoumM1NGFtaLuf2o9V01ZZHcvy3qrrAldXjuZaCkwWIKWy5rnrCDFBXiovoKbOWWdRgLy9vSksLMTb2xt3d3dzIovjx48D4OFRc5yOVlIFBPO7UkRExDGICpBIm5JYkMjqk6vRGrToDDqq9FUsjlnc4a0+lsik9nP5sYWxIWMJcA3grcNv8c7Rd0gvTWdO7zlNns9dl3YB2D3zW3l5ea2/psmjRqOhvKBmW3FZcYPFJ5uaBFq2MSlXdSe2ppX9+Lx4vFXeaA1aXjzyIoJRQC1TM8J/BFP7TiVYEkx+Vr55v0BFIAPcB7Cjcgc7sndw6cAlHol7xDz5g5pJUWFhIUaj0Zzpa0jgEHxUPuxK38WcXnPafPXW08WTIYFDGBI4hFsH3Ep6aTpHso9wOPsw+y7vY9/lfUiR0s+3HxO6T2BU8Ci7KrmJBYm8dfgtirXFzO09l+t7XY9UIrV6/QxGg92Sa9S9Z+qO1VDQfHOC6U1jmBIbZGVlNVlwta6CVVdOy8+A2YXT0pJlsiy2VyXI26WmOHVBVQFhHmFOlsY+REVF1frs7+9PfHy8eYGjvLycmJgYh8tlz0UDERER2xAVIJE2I70kndf+fo28yjx0Rh0KmYJnRz7LkMAhzhbNrsgkMrtmvbKF3t69eXnsy7x1+C2+O/8daSVp3Dv4XtRytdX2BqOBYznHiPCIMKe4tReNrZbnl+QjCAIFxQV2cS+xlkDhUuYlMuWZBGmCyC3LRafXoULF1RFXE1AdQHdVdxRyBbI8GfmGfPNKvKmvQUGDmOQ7ia8Tv2bTxU089ddTPDb0MXOKdpNiJwj/uHpJJVKGBw3n95TfSS9Nd+gEUSKREOYRRphHGLN7zaawqpCjOUc5kn2Ek7knOZ1/mi8TvmRS6CSmhk9t9fXekbaD/8b/F7lUziNxjzC82/AG25rSgNtrcaNurRJbFWhbLTaWLo51Exs0ZuWpex/Wvbfryl03U6E9Ezu0FV4uXgAUVRc5VY62xpQoxfR/Z1Clr0IlVzllbBGRroqoAIm0CRllGSzft5yLRRfxdPHET+2Hzqijn28/Z4tmd6QSqUMtQCZ81b6sGL2CVSdWsffyXp7b+xyPD3vcamxJYmEiZboyroi8wu5yNGbFCQsOQ3Jeglwtt7oq31xXIsux8irz+Cr+K/4u+JsKYwVyqZwBbgMY6j6USPdIunXrRnp6OoIgYDQazavwpkluXblv6ncToe6hrD65muX7lnPPoHsYHTK6wQnS0MCh/J7yO0eyjzh1hdxb5c2UsClMCZtCua6cXem72Jq6lZ+TfubnpJ+J9Y9lWvg0hgQOaZZiYjAa+CrhKzanbCZAHcCjwx4l3CO80X1Mro72Ws1uKgbIVhpzlTNhaypsqH/P1723G8oiZ1krqL2ne/ZW1ViACqscmwnO0VheD2dRpa/C3dW+xbRFREQaR1SAROzO5bLLPLXnKc4XnsdX7cuNfW4kpSSF84XnG7RQdGRcFa6UakudMrZSpuT+wfcT7hHOhrMbeHrP0zwc9zAD/AbUanck+wgAcYFxbSaLNWUmMCAQLzcvpMp/3KUs66I0d1IrCAIn806yNWUrR7OPotVpcZO7MdZjLAPdBuKh9KjlamRKviCVSs3bGxtnfPfxBGuCefPwm7x77F3SStOY12ee1X36+vZFLVdzOPsws3vNbuFZsy8ahYaroq7iysgrOZV3im2p2ziUdYjjucfxU/sxNWwqk8MmN1lLqkxbxjtH3yE+L55+Pv3419B/2RQHUq6rsZaZUmK3loZigJqrONviKmeLe11d17aG3POsyW15n3eEGiwmC5CjU2E7A2cXYK3UV6KSiRYgERFHIipAInYlvSSdR3Y9QmpJKkGaIB4Z+gjju4/n8d2Pt7qAZ3vF28Wb9JJ0p2XykUgkXNvzWrq7d+e9o++x8sBKbou+jRkRM8xtjuccx1vlTaSH7VnjbMFyQtjQJEIlU1FlqDJ/tmzXnFiN03mnWX92PReKLiBBwuCAwcyImMFA/4Hk5+XXi8GwrB1Ud3W/MXp69+TlcTXuhT9c+IG00jTui72v1qTedNy9Nb05UXSCUm0p7sr2s4IrkUiI8Y8hxj+G/Mp8/kj7gz/S/uDrxK/58cKPXB11NTOjZlpVVNJL03nj0BtkV2QzLXwatw24zeaYHpO1wBQ/Yk9siQWri7XEBiasbW9MiTHtY+naZuukuSMW+DQpQMXVxc4VxAE48/oYBSPVhupOuTgoItKeERUgEbtxruAcj+x6hOyKbHp49uCFMS/Qy7sXAJW6SjRKjZMlbBu8Vd7tIl1sXGAcL419iTcOvcFnpz4jrSSNRQMWYRSMZJRlMLzbcLsraJZFHoFaaX5Nk0+pUUqlvtK8j2WSBFtW8ZOLk9lwdgMnck8gl8gZ6zeWQYpBdPfuTnl2OfmS/HrWJVNAu1KpbFFQs4/KhxWjVvDxyY/Zk7GH5/Y+x+KoxRiKDbWUPV+jr1nGgf4Dmz2OI/BV+zKvzzyu73U9f2f+zbfnv+W789+xNWUrs3vNZnr4dBSymlTaBzMP8uHxD9EZdSyOXsz0iOnNGssUL2KZRKItaGjCWtcy1JDC0tD/G7oPTf1qNBrz+GC7G1tHsPjURSaVoZKpaj27nRVnXp9qQzUCgqgAiYhQ8zyYauQVVBVQVFVkrptXqi3FIBjQG/W1Co1LkNQUIpfKkUqk6Cp0No0lKkAidmF76nZePvgyZboyhgQM4aWxL9Wqj1JpqMRX1rx6KR0Fk698e0gX2929Oy+NfYl3jr7D9rTtnM4/zYyIGQgIRHlGNd1BM7Es8lg30Ns0+RS0ApXKfyZRpslGU6v4WeVZfJP4Dfsu70OChHEh45jbey45STlotVpzTQ9rdTxMip5pwtoSFDIF98beS7hHOOvPrOf5Q88zJ2BOreMe2H0gO87sIKk4qd0qQCbkUjmjQ0YzotsIdl/azf/O/Y8vE77kt+TfmBk1k3MF59iXuQ93hTuPD3+cAb4Dmu60DgVVBUDbWIAsaWjCWtcKaS1ZQVP/h9oKT3l5ufn+Bmop1B1NqWkuLjIXqvRVTTcUaTGm8ysmQRDpKlToKkgpSSGpOInUklQKKgvMSk6FvqLB/ZRSJQqZAqlEikwiM/8D0At6DEYDRsFIRVnDfVgiKkAircIoGFl9cjVfJXyFUTByTdQ1PDH8iXopeKv0VZ12hau9pYt1U7rx9Iin+TXpV75J/Ib3jr6Hzqgj1D3U7mNZWl7qZmczJR3wcvOiQF9Qb3W+oVX8oqoivjv/HX+k/YFBMDA4YDDz+8wnwjMCAGmQtNbk1DImRKPR1JqsmjK4tRSJRMI1Pa4h1D2UNw6+wdqstdzkcRPRftH4+/tTqa9EekZKUnFSq8ZxJDKpjElhkxgbMpbfU37ny4QveX7/88gkMkZ2G8kzI57BR+3Tor5NFiDTooAjsObmptFozHFmdRUWaymr62JSpEz1rIAOVcTUXqjlalEBamNEBUikM2Op7CQVJZFcnExmeWatYtmuctcaF33PSLxV3ni7eJv/eqm8zH9tnUOWlJSwgQ1NthMVIJEWU6GrYMW+Fey6tKsmGD/2fm7se2M9NyuD0YDOqOu0L/j2mC5WKpFyTY9riAuM44EdD5Baksrnpz5Ho9DQ16ev3cezFvRtsgj5uPmQkplCamoqEomkwUBwo2Bkc/Jmvkn8hmpDNb29e7Og7wL6+/ZvdCygVvFVmUxmVoDsNWGNDYjl9Umv88ahN/gu/TsKKWRxzGLUcjXBbsEkFyfbZRxHUmWoMsvtp/ZDJpFxqewS7x17j2WDlrUofbajLECWWFp9TPV7GrMuWrpt1q3/Y2prUqTkcjkVFTWriTKZrNNbfOqikquo0Nm2mirSMkwr3p11gVCka5FXmcfR7KOcLThrVdkJdA1kZLeRRHlFEeUZRYRHRJsW8G4MUQGyI83NTNSRuVR6ieV7l3Mq/xTeLt6sHLOSYd2GWW1r8iHvrFlu2nO62GC3YKL9otEZdRRVF7Fi3wqujLyS+X3n4yJzabNxLVfiqzKrqKyupMpYhVqmtupqJPGU8L/0/5FYmEigayC39L+FoYFDbY5ZasjVyZ7PYYhbCC+NfYl3j77LjvQdlOnKeGDwAwRpgjiWcwyjYL/6N23NoaxDfHLyE4q1xQwLGsZdMXcB8O25b9metp3Hdj3Gwn4LmRExo1nHVFRVhBQpHi6OcwW1ZvWxVi+qrpXIYDCg1Wrr1f+xpLq6utY4XQ21XG1WakXaBtPCmaeycyYJEuncCIJAcnEyh7MPcyT7CCklKebvglyDGBU8ikjPSKI8o4j0jESjaD+x4KICZEecnUrTERgFI78k/cJn8Z9xufwywZpg/t/E/2cuGmkNUwawzmoBMitA7TRdbFF1ET29evLI0Ef46MRH/Jb8G8dyjrFs0LI2sQZZUlJSgis1mcaqpFX0CetT69m4nHmZ3bm7+TPxT5QqJVdFXtUi5cxaGuK2oKKogqvVVyP1kvJ31t+8deQtvF28MQgGSrWl7T7TYam2lM9Pfc7ey3vRKDTcG3sv40LGmRXNuwbexZiQMXx04iPWnF7DwcyDzbIGFVYX4qnydIoiWFJSYrbkmCxBJqxZiRpbsDK1r1s3qquhkneNJAjOxJw50YFuo7bQlRZ0RZqHzqDjdP5ps9JjWiRxU7gxLmQcQ4OGEu0b7TTLjq2ICpAd6YipTpvDpdJLfHTiI47nHKeouoheXr14ddyrhHo0Hlti+gHtrCZ+T6UnEiQUVRU5WxSrFFYV0su7F8FuwTw/+nlzbFBbWoMsJ5BeKi8UCgVKT2WtdMOZZZmszV1LYkEiAeoArvW/lpEBI9vUMtVasrKy0Ov0XOlxJZ5unuy8tBNXuStGwUhhVWG7VoAsrT5DAoZw18C78FHVj/Xp79ufN8a/wfqz69mSsqVZ1qDCqsI2dX+zFr+TlpYG1LiomdwfLQuOQu13s2UfDWUI1Gg0aLVaPDw8cHd3t5omuyuglqvRGXUYjAZkUvsUtxWpjWnhrL0pQF1hQVfEdqoN1RzMPMihrEOczD1pXtjupunGNVE17va9vXt3qPeEqADZkY6Y6tQWTFaf/yX+j8KqQnRGHdF+0SwftZwQt5Am9+/sQZ4yqQwvFy/yKvOcLUo9KvWVVBmqzD+ulrFBJmvQ31l/M6/3PMZ1H2e3lXvLCac2UMv2ku2kZKegUWu4nHmZg6UH+frs1+iNem7ofwPR+mjQ0+5/bC2Pa6nfUhQyBd+f/55SbSnZFdnmRA3tidSSVDac3cCxnGNWrT7WUMlV3BF9ByO7jbTZGmRys2zLc1B3UmZZ1yckJKTBSVtzawiVlJSY/5aXl3fZiaDJbbnKUIVG2n5cVzoTpoUzUyxpe6GzL+iK2EZmWSZbU7ey69IuynXlSJHSx6cPQwKHMDRwKMFuwc4WscWIClADfJP4DQsGLWj3Jry25lTeKdadWUdScRJyiRy5VE6EZwTPjXzOZreYakONH317XtlvLaHuoZwpONPuVkrLdTVZ0NwUte9jkzXo9+Tf+e78d3x44kM2XdzEgr4LiAuMa3W9IMsJZ1FhEQAyjYxqqlmXuY4LJRcIdg9mUe9FuFe6o3Gvyeim9FaybNsyTuWfIto3mpVjV+Kn9muVLPbEMt02wOLoxWSWZbLp4iY+jf+UGL8Yq8VFnUFORQ7/S/wff2X8hYDA6ODR3NL/FqtWn4awZg26a+BdjO8+vl7bS6WXMAgGwtzbLhNi3UmZ5WfLa9PYpK25Ezt7TwSbci1qT65HpkWrKn1Vu/Ld70wUVhcil8jbVSFl6LwLuiJNYzAaOJpzlC0pW4jPiwdqYmDn9Z7H6JDRTi/3YS9EBagBNidvZn/Bfq7tcS1XRF7RqSfv1kgqSmL92fXE58Ujl8rp79Of0/mnCfMIY/nI5fi72v5iNApGAHO+9s5IlFcUJ/NOcqnsEuEe4Tbv19aTHYOxJhuaXFr/UZdKpFwVdRUTQiew6eImfkv6jTcOv0Ef7z4s7LfQbvFBJutTtiGbv4r+IqMkg2Huw7iq21XIymRoDVoMBgOxsbEs27aMA5kHMAgGDmQe4Nm/nmXVtFV2kcMaLTn/da0MV0Zeye5Lu8koy+CVv1/h6RFPO9Xds7i6mO/Pf8/21O3oBT0D/QayoN+CFteBsrQGfXDsA/5z/D+klqRyU7+balkMTanA26LelAlrsV6NfbalD2v3gMmaZNpmz2ezKdeiulnqnKkIma7v/2fvvOPqqu///7yTCxe4XOZlrwRCCAkJkGGmmc64NYl11a/V2lbb+lPr7FRbta3aOmqtqdYmbhONMSF7L0gghJWw94bLvHDH+f1xe6+QEMK4cC/kPn34IAnnnvO563w+r8/7/X69Lfdwe+NI4tBWWFJnx4uBipOJi7Zby+6y3ewo3UGjrtHaGmFlxEqmek+1eSN1e+MUQBfhgYQH2Fq1lQ15G/iu5Dtum3wbS0KXONTu/mhQ3V7NJ/mfcKT6CCJELApZRKRnJP/J+Q/+bv78et6vh7wjb5k8J/IN3rLoK2opGpIAGu08a8trL+LiNy6lTMnaKWu5KuIqc/+d0l38+vCvmeU/i7VT1o64t5HKRUV7TzvfFn9LoDKQB6Y+QIQpAo1GQ2VlpfW4+vp6MmszMQpm0WYUjGQ3Zo/o2pdiOK//+REBiViCWqEmzjuOvOY8Xj7xMr+a/asx3zTp1HeypWgL3xZ9i86oY5LXJNZOWcs032k2Of9Un6m8tPAl/pz2Z7YUbaGirYJHZj1ijQxYLLUHMkRxNHrXEPX+DIzm7velIkrnu9TZM/XOcs/ubWNrTyZiXUpzdzM+ionZJNzJ+CC/KZ9tJds4Xn0cg2BArVBzW8xtLA1bOqSMgfGGUwBdhPnB81kes5xdZbv44twXvJv1Lt8UfcNVEVexKGSRw6S52AJBEChoKWB7yXYOVR7ChImkgCTWxK6hpqOGv6b/FS+FF8/OfXZE6UgDLcLHOxYBVNxazJVcOejHjXaetUWwD2YBo1ao+b+E/+PayGv59OynHK46zKm6UywIXsDtsbfj7+Y/5OubBBNfnP2CRl0jSpmSX8/7NZPVk/scY3n+NTU1RLpGkt2ejQkTEpGEeJ/4IV9zKAzn9e+9OG7oauCVE69wtvksIkQsCFnAwcqDvHriVZ5IeQKZRDZaQ7eiN+pJLU3lq4KvaOtpI0gZxJopa5itmW3zHTuVi4rn5j7Hv878iz3le3jm4DM8kfIEQe5BFLUU4Sn3HFeLud41RGNV63ApcXWx5sL2xFEiQBOtLsVoMqLVaZmkmmTvoTi5DCnWFvNx3sdk1GcAkOCbwIrwFSQFJPWbNTLRmPjPcATIJDKuiryKxaGL2VK0hW3F21ifvZ6NeRtZELyAFeErHLLoebB0G7s5VHmI1NJU6+5tnHcca6asYYr3FE7VneL1k6/jIfcYUs3P+VwOESBfV188ZB4UtRQN6XGjnWdtSTvUG/WDfkygeyCPznqU66OuZ2PeRg5UHuBI1RGWhS/j6oirCXQPHNR5ugxd/O3U30ivTUftokbjprlA/Jz//B/WP8z7Ne9zrvUc8T7x/GHBHy44ry3TYEb6+j978FmyG7MxCSbymvJQK9QsDV3K7vLd/PXkX/lF0i+QiUdHBHUZujhUeYhNBZuo76rHW+HNj6b/iCUhoxuplklkPDj9QSI8I/gg+wOeOfgMP038KWVtZcT7xI+rNIn+aojAMVKtHKEGwxoBEhwjAuQIr4ktqe+qx4SJAGWAvYfi5DKipqOGT/M/5VDVIfPGXfACbpp0EyEeIfYe2pjiFECDwFXqym0xt7E6ejWHKw+TWprKzrKd7CzbSYw6xqqYx0ORqCAIlLaWsq9in9XVQyFRsCJ8BSvCV1jTt840nOHPaX/GVerKc3OfG5HTh6OkT4wmIpGISFWkwxkhWIoVtT3aIT82yiuKZ+Y+w5mGM2zINRfBby/ZToJvAqsiVjHLf9ZFn2dtRy0vn3iZivYKFgQvQBAEDlUdolPfSUdLx0Wblvr5+bGABdYFqNAuwHnlNKORBjOcBa8lZc8i8E2YyG7M5q3lb6E36TlQeYC/nfobP5/1c5uK/4q2CnaU7mBfxT66DF24y9y5M+5Oroq4CrlEbrPrDIRIJOKqyKsIdg/mtZOv8cKxF+g0dBLhGTEm17cVF1tQT8RUq+EwkaP2jkBVexUAQcrx66TlZPzQomsxp7mX7cIoGEn0S2TtlLXjeiN/JDgF0BBwkbhwZdiVXBl2JYUthaSWpHKo6hBvZryJRCRhqs9UZvnPIlmTPKx0odHC0rQqvTad9Np0GnWNAIS4h3B77O0sCu6b0lfdXs1f0v+CTCzjmbnPXLLPz6WwZyHtWO7kDtcIYTSRSWS4y9ytzfaGwzTfabyw4AVymnJILUnlRM0Jshqy8FZ4syJ8BUtDl+Kl8LIeX6Qt4sWjL9Kub+fOuDu5Pup6NhVsAqC6o5rOmk7r4hLod6E50ALUUjvUX7+X4XL+9QbzubGk7J1pP4OAgFgkJt4nHrFIzI9n/Bi9Sc/R6qNmR8kpa0c0PoPJQFptGqklqda6qDCPMFZFrGJ+8Hy7mS4k+CXwwoIXeHzf45S3lZPXnIdJMI37aO9ES7UaLtYawnEU1RtPVHdUAww6qu7EyXDo1HeajY6Kt9Jt7Gay12TWxa1jqs9Uew/NrjgF0DCJ9ormx4k/5gdTf8Cx6mOk16aT1ZBFVkMWH+R8QKhHqLUxVJQqakybnOlNesrbyilqKeJ0w2ky6zKtTasC3AK4JvIaZmtmM8V7ygUTW6e+k1fSXqFL38WTs58kUhU54vFYdhHtEQkay53c4RohjDZeLl60dLeM6BwikYh4n3jifeJp0jWxu2w3O8t28kn+J3xx9gtmB85mZfhKJCIJLx1/Cb1Jz/9L/n8ka5IBrOmTVe1VTNFM6TcC1BvLAlSpVJKVldVHiFj6v9jyfT1/wWs5v6VAvr9raDQaHtY/zB8K/0BNVw0pmhRryp5ELOGniT+loauBTQWbCPMIY37w/CGPq7Grkd3lu9lVustqlzs/aD4rI1YSq451iIWpRqnhiqArKG0t5UzDGf6R+Q8enPHguBFB/YndiZZqNVws9+zx8l6ON6wCSOkUQE5sj96oZ3vpdjad20Sbvo1g92DWTllLckCyQ8wd9sYpgEaIh9yD5eHLWR6+HJ1BR1ZDFmm1aZysPWnd9QZQu6iJVEUS5RVFlCqKYPdgvFy8RtQc1CSYaO1upVHXSIm2hCJtEUXaIsrayjCYDIBZfExWTyY5IJlZAbMIcQ+56AffJJh449QbVLZXcvfUu0n0Txz22HpjuZ498sjHcifXKoC0RUMyQhht1Ao1Z5vPIgiCTW563gpvbo25lRsn3Uh6bTo7SndwuOowu8p20aJrQeWi4tfzfm0VP4A1hbK6o5qFIQsvsC4+H8sC9GJNK239vp6/4FUqlfT09AAXb85qecxi/WJyGnN4d8W7fV5fmUTGY8mP8fTBp3k78200Sg3RXtGXHItJMJHT+H20zYQJX1df1sSuYWnYUlQuKhs8Y9tS3lZOjDqGSV6T2FuxF71Jz8OJDztsIW1v0eNMd7s4lnu2MxVudKhur0YhUThcE1Qn45+C5gLeynyLyvZKfBQ+PBT3EItDFzs3M3rhmLPTOEUhVZCiSSFFk4JJMFGsLaawpdAqTDLrMzlZd7LvYyQKvBXeeCm8ULuoUbmokIllSEQSxGIxgiBgEkwYBSOd+k6au5tp0bXQ1N2EVqfFRN+0MpVcxTSfaUSpoojyiiJGHTPoBZOlW/ySkCVcE3mNzV4XMeYvnMXeeCwZy51cX1dfvBXenK4/bTOxYQsClYFkNWTR0NUwpP5Nl0IqljIncA5zAuewt3wvfzz+R/QmPWKRmL+e/CvxPvEkBSSRFJBkjQBZdjx7c77bVe8/G41GJBLJBULH1u/r+WNobv4+ZfBSIqu8rRyNUtPv++2t8Obx5Mf59eFf82raq7y44MV+o8Hdxm6y6rOsaaqWmq1Ev0RWhK9gVsAsh5242nraKNYWk6xJ5pFZj/DGyTc4VHUIg8nAI7MecUgR1Fv0ONPdLo7lnu0o97KJRlVHFUHuQc7X14nN0Bv1fHr2U7YUbkEsEnNH7B1cH3X9mDiSjjccb2aaIIhFYqK9ovvs+OqNesrayijSFlHXWUezrpmW7haadc2UtpaSo8+55HklIgleLl74KHyIVkWjVqjxVngT6hFKlCoKb4X3sG6m+yv283Xh18SoY/i/hP+z6Q3ZUpjdbey22TkdEZFIRFJAEjtKd1DVUUWwe7C9hwT0jUzZUgBZyG/KZ/2Z9WiUGl5d/Cq1nbUcqz5GdkM2mfWZvH/mfcI9w+kx9nC26cJIVO/FKHxfE2Q0Gq0CCLggFc6W9E55s1wPQK1WD3g9bbeWRl0jCb4JFz0m2iuaB6c/yN8z/s6f0/7Mr+f9GplERouuhfQ6s+A5XX8avcns1BfiHmKuNQy9ctjOi2PJqbpTVut8mVjGo7Me5c2MNzlcdZg3M97kZzN/5nDi7Xz3N2fkp396jOYoqEIy/EwFJ/2jM+ho0jUR5x1n76E4mSD0jvpEqaJ4eMbDI67hnsg4BdAYIpPILhBFvekx9tDa04rBZMAoGDGajIhEIqQiKRKxBBeJCx5yD5svJgpbCnn39Lt4K7x5LOkxm+8UuEnNBguWOqSJTHJAMjtKd5Bem+4wAshSx1WsLWZO4BybnruwpZCXjr8EwFOznyLWO5ZpTGNZ2DK6DF2crj9tTQlt7Gqksr2Sh3c9bI0MTfOZ1mcx2tbWRk9PD0qlktbWVut1LAKlsrJyVEwtNBqNtd6nN83NzQMaLVjs4yO9Bq6VWxiykLLWMj4/9zmP7XsMd5k7hdpCwBwhjfOJuyBaNl5Iq01DhIiZ/jMBc2TwJ4k/wWAycLjqMHKx3OFqgpyiZ3B0GboAcJGObVPfy4GqDrMDnNMAwclIOT/qsyZ2DaujVzuMG62j4hRADoRcIh9Ro9Hh0KRr4pW0VwB4PPnxPm5etsIyeeoME18AxfvEo5AoSKtJY3X0ansPB4AQjxBkYhlF2qH1KLoUpa2lvHjsRYwmI0/NMYuf3rhKXa0pckaTkb+m/5VtJdswCSZ2lO5gR+kOFBIFU32mEuUVRY+pB51WhwIFHR0dBAcHX5AaZzQaR80CW61W09HRYb2eRRANdC3LaxrpeaEA6jZ2U6ItoVhbTJG2iJyGHJp0TVS0VRCoDGRZ+DKSApJI9EvEXe5uk+cy1uiNejLrMpmsntwn1VYqlvLIrEf4S9pf2FuxF5lExv3T7nem+owzugxdSMXSUetldTlj2TwZb9bxThyLguYC3s58m4r2CmfUZ4g4BdBljN6o589pf6ZZ18w9UffQUd5Bvd42tsK9sVj0Xg4CSCaRMcNvBsdrjqPt1jpEwbpULCVKFUV+Uz56o94mET5tt5Y/Hf8TOqOOJ1KeuKSdpkQsYV7QPE7UnuAHcT8g2iua9Np00mrTOF1/mpN1JzHoDej1etyl7sT4xBDvEk+kXyQSDwneCu8L7KlthSW6BJCQkHDB7wa6Vk5jDjKxjED3QPKb8q1ip0hbRGVbZZ8aPX83f+6ccifHao7Roe9gcchimxmN2Ivsxmx0Rh3JAckX/E4mlvHLpF/ypxN/YkfpDgLcArg++no7jNLJcNEZdbhK7GOxPtGxRo9t4LTq5PJDb9Tz2dnP+KbwG2fUZ5g4BdBliiAI/OP0PyhoKeDGSTcSqg8dNScki9OdJZ1iopMUkMSxmmNmQ4nQJfYeDgCzAmaR35xPTlMOM/xmjOhceqOeV9NepVHXyMMzHh70+XrXIi0MWUiQexDXR19vrY3be3ovZR1l1OhrONd+jtyzudbHWlwUI1WR+Kn8MApGOrQdqBXqEaeFXqwI/vw0qS5DFy26FqsRSVV7FXvK96CQKHgw9cE+YsfX1ZdkTTJRqijruC1i+Pro63nm0DO8fvJ1Xlzw4rhOgUmvTQfMn/n+kEnMIujZQ8+yIXcDoR6h4170XU7oDLoROZU6uThFLUV4yj3xUfjYeyhOxhlNuib+nPZnCloKnFGfEeAUQJcpW4q2cKDyAEkBSdwReweNDY2j5oQkE8uQiqWXRQ0QQKJ/ImLEpNemO4wASgpIYmPeRtJr00ckgARB4F9n/sXZ5rNcF3Udi0MXD/qxAcoAXKWu1p1PC5baOM84T+tn0Mvbi4zSDE5XnEYr01Krr+3XRRG+NwZRK9TWn2oXNV4KLzzlnkjFUiQiidlZUSS2/m9xVzRixBhgpMxURn5lPs26ZqtBSZOuyWpUcv7nt72nndbuVny9Li52+iPQPZCfz/o5Lx17iZdPvMwLC17o04h4vCAIAmm1aWjcNAPWu7nJ3Hgi5QmeOWgWfS8seMFqi+7EsekydDkF0ChgMBkoaysj3ifemRbqZEgUNBfwatqrNHc3c0P0DdwRe4cz6jNMnALoMiSrPosNuRsIcQ/hp4k/RSwSj3pRsEKiuGwiQCoXFTHeMWTWZ9os5WykhLiH4O/mT3ptOvfF3zfsSXd7yXb2lO8h0S+RO+PuHNJjxSIxkapIilqKMAmmC6I2538GFR0KEpWJyOVyEhIS0Bv1VLRX0KRrMouU/0ViLD8bdY3mc59nDT8S3GXueLl4MVk9+QJxtb14O9mN2by17C18XIe2izvdbzp3Tb2LD3I+4G+n/sYTKU+Mu4VQSWsJTbomro289pJj1yg1/HzWz3nx2ItW0aeUKcdopE6Gi86gGxXnyMudirYK9Ca9NSruxMlg2F+xn3dPvwvAozMf5YrgK+w8ovGNUwBdZnTqO3k7823kEjmPpzw+ZjvPrlJXug0T2wa7N0kBSeQ15XGm8YzVHcueiEQiUgJS+Lb4W/Kb85niPWXI58iqz+KD7A8IVAbyyKxHhpV2FqWKIqcxh+qO6ku65J2fmiaTyKwRlothaQ7c3G2O4LT1tGE0Gc2Rnv85KxoFIwKCOSok/j4yJBVJcZG6oHZRW6NJFgv38+kydPGPzH8Qo44ZsvixcHXk1RRpizhQeYC95Xu5MsxxmucOhrTaNIA+DW8HIsEvgXvi72F99npeP/k6v5r9K4dyhnNyIV2GLlwkTgc4W2NxgIzycgogJ5fGJJjYmLeRrwu/tvaWc352Ro5TAF1mfJT7EY26Rh5IeGBM7XYVUgWdhs4xu569ma2ZzX9z/8ve8r0OIYAArgy7km+LvyW1JHXIAqimo4bXTr6GQqrgiZQnhr17b60Daim6pAAaTlRSLBLjpfAaFTfD3hyoOIDOqGNp2NJhn0MkEnHftPs403iGD3I+IMEvYcxdIIeLSTCxv3w/KrmKWHXspR/wP1ZFrKKktYQ95Xv4b+5/uWvqXaM4SicjwSSY6DZ2W9sYjAW9TU4msk250wDByWDp1Hfy+snXyajPIEYdw2NJj436/Ha54Nx+u4zIrM9kV9kuEnwTWBa2bEyvrZKr0HZrx/Sa9kSj1JDgm8CJmhM06ZrsPRwAQj1CmeozlWPVx4b0XnTqO3n5xMt06jt5dNajI6rfsAigwpbCYZ9jqNTX15OVlUV9fb1NzicIAqmlqbhKXZkfPH9E51LKlDw4/UG6DF28e/pdBEGwyRhHm9P1p6nrqmNp2NIh5Z+LRCLun3Y/sepYthRtYV/5vlEcpZOR0NbThoCAp4vnmF3z/KbIE5WClgJUcpXTAMHJgFS3V/PMwWfIqM9gSegSnp/7vFP82BCnALpM6NR38o/Mf5gdq6Y/OOb1Bl4KLzoNnXQbL580uJXhKzEKRnaX7bb3UKysiliFQTAMaUwf5HxAZXsl6+LWjdjBS6PUoJKryGnKGdF5hiJqbL2oym/Op7ytnCUhS6wW7yNhpv9MloQsIbM+k73le0d8vrFge8l2xIiHtZEik8h4LPkxfBQ+vJf1HjUdo7/YtbUIvhxo1jUD4OXiNWbX1Gg0yOXyUTHjGUsG+rx16Dso1ZYyxXvKuKv7czJ2ZNZn8vTBp6npqOGeqffw0PSHHKKeeCLhFECXCZbUt7um3mWXolZvhTfw/aR6OZAUkIS3wptdZbswmoz2Hg4AyQHJqBVqviv+blCmFBl1Gewt38sMvxlcF3XdiK8vEomI942nrLWM9p72YZ+nsrKSnp4eKisrL3msrRdVX537CoAVEStscj6Au+PvRq1Q80HOBzR0NdjsvKNBXWcdp+pOMTNg5rDvJSoXFT+Z+RN6TD28k/kOJsF2xhW9sSxELZ+XiR5ZsCUt3S0AqBXqMbumn58fCQkJ4z79baBNl7ymPEyYmOo7cO80J5cvR6qO8Kfjf0IkEvGrOb/imqhrnGJ5FHAKoMsAe6a+WbDsIlom1eEw3nZxJWIJy8OW06RrsvZLsTdSsZSbJ92MtkfLtuJtAx7boe/gH6f/gavUlR9N/5HNbsDxPvEICOQ0jiwKNFhsuajKacwhoz6D+UHzL1nDNBTGUyrcztKdCAisDF85ovPE+8SzKmIVuU25bC/ZbqPR9aV3k9uJEFkYSyypu2qXsRNAE4X+Nl0s89ex0mOA+fPvxMn57K/Yzxsn38BT7snvrvjdiPv2Obk4ThOECY69U98sWCbRkdTD9N5VGy87hEvDlvLFuS9ILU1lduBsew8HMI/p26Jv2Vy4meXhy/GQe/R73Ic5H9Kka+LB6Q/atDh/qo955zOnMWfYr0lwcPAFfatGu4BaEAT+m/tfJCIJt8febvPzW1Lh9lbsdVhXOL1Rz+7y3QS4BTDdb/qIz7duyjpO1Z1iQ+4G4tynoTJ6o2vX09Wu/9/PHox6E4IAJpOACBCJRYglIuQKKa4eMhTuMlzd5f/7KUMq/74mqbeT4Hi5ZzgK9ogATRT6M3CxzF+Z1ZmIjCJculyg/1uvk8uUXWW7+Ofpf+Kt8Ob5ec+PqVHV5YhTAE1weru+2bOfg6Vwr0XXMuxznG+LPB5QK9TM1szmSPURqtqrHKIBpFQs5Y7YO3j91Ot8ee5L7om/54JjTtWdsqa+XRlq24V4oDIQLxevYUWAeouchISEPr8bbYF8ouYEBS0FrIpYNWoT093xd5PZkOmwrnBHq4/S1tPGDdE3DNvCWjAJtDbqaK7poLm6k+Tqq9igf4fnP/sj17bcjWiEiQlKLxfUGjfUGiXqQDdiJ8Uhd3VOdUPFkq7sjADZBo1GQ3FlMdXd1cQp46itrcXf39/ew3LiIGwv2c77Z97H39Wf5+Y9h7+b87Mx2jhnhQmMI6S+WbDWAHUPvwbIsqtmSSUYL7u6KyNWcqT6CDtKd/QrNuzB3KC5bCnawrbibSwMXtinp8Bopb5ZEIlExPvEc6jqEK09rXjKB+8yNZDIGU2B3KnvZH32ehQSBTdPutnm57dgSYX74/E/8u7pd3lq9lMOlfudWpqKTCxjSeiSIT2uQ9tN1bkWqgu0NFS0Yej5vuZHpQgkxW8Bma5HaY0qZJlmBa4eclzdzdEdqUyMSCxCJBaBICCYwGg00dNlpKu9xxop0rXr6WztoaW2i8r8Ziryvr/XKL1cCIxWETTZC79wDyQSZ/b3pWjWNSNGjKeL52VjTz2a+Pn5UWosRVYsY5LHpHG1kedkdNlZupP3z7xPoDKQ5+Y+N+zeck6GhlMATVAcJfXNgqUGyBYmCOMtFS7OO44Q9xD2lu/l9tjbbeIcNlLEIjEPzniQpw48xVuZb/HSgpesDjMf5nxIs67Z5qlvvZnqM5VDVYfIbcxlTuCcQT9uIJEznL5Bg8WSDvhAwgOjbkPqqKlwRdoizjafZUnIkoumTVoQBIHmmk6qzrVQda6FllpzDzCxRIRPiDvegUq8/xehUXq5cJVxKo/vr2C/7juun7wUjVJ1kTOLQAISmRi5Qoq7uv8mnYYeIy11nTRVm6NMDRXtFKTXUZBeh1QuJjBaReAkL4ImeTmjQxehubsZlUKFWCQed/dcRyW7MRupTMpVM6/Cz8P5Ojox1/y8l/Ue/m7+TvEzxjjv/BMUR0l9s6CQKnCVutpEAPVeBI+HnUmRSMQ1Udfw7ul32VK0hdtibrP3kAAI9wzn5sk389nZz/ji3BesmbLGasU8GqlvvZnmOw0w95MZigAaSOSM1mchoy6DPeV7xjSS6oipcJ/mfwrA1ZFXX/QYfY+RsuxGCk/WW0WP3FVKeIIPwZO9CIhSIZNf2DdIIVXw0IyH+N2R3/FO5jv8et6vR7RpI5VL8A3xwDfELNQEQaC1QWcWZAUtVOQ1U57bjEQqIizeh+iZ/ngHDa+570SlWddsTX8bj+nHjsjp+tOo5CpC3EPGxdzlZHQ5XHmYtzPexsfVh+fnPu8UP2OMUwBNQAqaC9hVtotpPtPsnvrWG383f2o7a0d8nt6L4KysrHGxM7kkZAnfFH7DlsItrAxficrlYjvcY8uNk27kRM0JNhdsZrrfdD7K+QiZWDYqqW+90Sg1BCoDSa9L5/+E/7PJtUZjl1rbreUfp82R1IdmPDRmkdTeqXD/zf0vj856dEyuezFyGnM4VXeKK4KuIEIVccHvWxu6KDhZR2lWI/puI1K5mElJ/oTGeeMT4o5YfOnXLd4nnuVhy9lZtpP02nSSNck2G79IJELl54rKz5W4KwLRdeipOtdC8ekGijPN/3sHKZk0yzxmiezyTpHrMfbQpGsiRh0DjG50dSLTW+SY3ExUtFewJHQJIpHIer+qrKy8QAg5xdHEJ60mjb+d+hsqhYrn5j7nEBvVlxuX911+AiIIAhvyNiBGzL3T7rV76ltvgpRBNHQ10GPssdk5x0vjPIlYwpopa9AZdWwq2GTv4ViRiqX8dOZPkUvkPH/oeYq0RVwdefWYRBySA5Jp1jVTrC22yfl6fxbq6+vJyMggIyNj2LbpeqOeV9NepUnXxP0J9495FGam/0xm+c/icNVhilqKxvTavREEgY15G5GIJNwRe0ef37XUdnLgk7Nse/cMBWl1uKnkJF0dzvWPJDJrVTh+YR6DEj8Wbou5DReJCxvzNtq0N9D5FvoKpYyoRD+W3R3Hih9OJSrRj9b6Lo5vKeabv2eSd7Qao350ehONB2o6ahAQCFQG2nso45remzKWVgjJAWZhb7lfARf0DLJ182YnjkWJtoQ3Tr2Bu9yd5+Y+53R7sxNOATTBON1wmuzGbBaFLiLUI9Tew+lDkHsQAoLNOr+Pt12yOZo5RKuiSS1Npa6zzt7DsRLqEcqD0x+ksr2Shq4GVkWsGpPrJgUkAZBWm2bzc9fU1GA0GjEajcNaRAiCwHtn3uNs81mui7qORSGLbD7GwbBmyhpEiNiYt9Eu1wfz+3O2+SxLw5ZaJ+r25m6Obi4k9V/ZVBdqCY1Tc+VdU1h5fzzRM/37TXMbDF4KL66JvIaK9goOVByw2XMYaEGp1ihJviaC6342g5krwpDJxZzeXcHWd7IoyqjHZHLcnkyjRXVHNQCB7k4BNBJ6b8qk16YjE8tI8DW7V1r6k3l6mk1glEpzCmZ9fT16vb7PvzmZOGi7tbya9ipGk5H/l/z/bNpPzsnQcAqgCYRJMLEhdwMyscxh6kx6Y1k8WSbX4dB7J3e87ZKJRCLWTlmLwWSw1lM4CtoeLR5yDxQSBR9mf2jT3feLEaOOwUPmMWIBZPlMVFZWWj8PGo0GiUSCRCIZVnRwa/FW9pbvJdEvkTvj7hzR+EZCuGc4C4IXcLrhNGcazoz59U2CiY/zPsZF4sKtk29F16Hn5PZSvvtHFmXZTWiiVay8P555N03CL9TDJhHn1dGrcZe58+nZT9Eb9TZ4FoOLFMsVUianBHDVgwkkrgjFZDSRtrWE1H+eoSKvyaGb09qaqvYqwBy1n6gMtbF27+Mtfy4qKhrwHBaR46pyJacxhwTfBBRSRZ9jOjo6+vysrKy0ftY6OjouGOd4awju5Hv0Jj1/Tf8r9V31/F/C/xHrHWvvIV3WOAXQBOJo1VFKWktYGb7SIYqmz8cymVom1+HQW/SMl/S33iT4JZDgm8DByoOUtZbZezgAdBm6+PLcl0SpolgRvoJjNcd49/S7oy6CJGIJMwNmUtpaSkNXw7DPY/lMANbPg5+fH4mJiSQmJg45OrinbA8f5nxIkDKIR2Y9Mux+N7bi9tjbkYqkbMjdMOaL8P0V+6lor+DqiKtpPmvgu3eyKEivwzvQjSt/MIVFd8TgFeBm02u6ydy4cdKNNHQ1kFqaapNzWhaig/ksSKRiYlI0XPPwdOIXBtHZ1sPhLws58Mk5Olttl77ryFg2qSZyas5QN9B6H2/5c3Nz8wXn6E+gZNZnYsJkTX/rfazRaLzoRo3FfKL3Ncbbxp+T7/n3mX+T25TLNZHXOIy753jFFhsBThOECYLBZOCT/E9wlbpy0+Sb7D2cfrFMpiNJgTu/s/t4SH07n3Vx63jqwFNszNvIk7OftPdw2FK0hdaeVh5IeIDFIYt5Ne1V9pTvQSKScH/C/aMqAJIDktlfsZ/02vRhp96d/5kYCfvK9/GP0//A39WfZ+Y+g1Jm/xQUfzd/locvZ1vJNo7XHB+Sa95I0Bv1fJr/Ka4iN3wzp3KiqBhXdxkp10YQHKse1frCqyKuYmvxVr469xVXhl6Jm8y2ImswyOQS4hcGEz3Ln6y9FRRnNrDt3SwSl4cROcPXoeorbYUgCHQZuihsKUQiknCq7hQt3S006Zpo6W6hWddMS3cLOqMOk8mEUTBiMBkwCSbEIjESsQSJyPy/TCzD08UTtYsaL4UX3gpvvFy8UCvUqF3UqBVqPOQedttgGKqb6PlOeDU1NSiVSjo6OvqIl/4EywGtOZ1zVsCsPue0pOr2Jjg4+IKx9L6u05FvfJJaksrOsp0k+Cbwg7gf2Hs44xbLd7V3ivtw532nAJog7C7bTU1nDXfE3nHJHh32wl3ujqfck6qO4UeAxqvo6U2UKoorgq7gcNVhchtzifOJs9tYtN1athRuIVAZyJWhVyIRS3gs+TFePvEyO8t2ojfp+dH0HyEVj86tYrrfdKRiKWk1acMWQLb6TKSWpPKvM//Cz9WP5+Y951BR1Jsn38ze8r18nPcxyQHJSMTDq7EZCttLtlPdVMuMuiU0t/YQPs2HmSvCxqRvjkwi4/aY23nn9DtsKdrC7bG3j/o1L4ZCKSPl2khCpniTtrWEtK0lVOQ2kXRNBEpV/32IxgMGk4GKtgqKtEUUaYso1hZT0VaBzqijRFuCXCLn7xl/7/MYd5k7Xi5e+Ln6IRFJzKLnfz8FBEyCCYPJgFEwojfpadI1UdRShIn+o8kSkQR/N3+iVFFEqiKJ8ooi0jNyVATv+SJnqG6i599nLnacUqmkp6cHqVRKWVkZRsHIydqTTPKfhKHNQNa575t4azQaysrMmQCWa/cWPRe77nifAy83shuy+Xf2vwlwC+Dns34+JvfviYplg0EikYw4A8gpgCYAOoOOL859gUqu4prIa+w9nAEJcg+isq3S3sOwO7fH3M6x6mOsP7OeFxa+gEwss8s4NhVsQmfUcUfsHdabslwi58mUJ/lL+l/YV7GPhq4GfpH0i1ER1q5SVxJ8E8isy0TbrbWLPbjRZOQ/Of/hu5LvCHAL4Pl5zzuU+AFQuai4Lvo6Pj/7OXsr9o66vX2ttp73D/8HOl2YbprL3NsmETRZParXPJ9FIYv4pugbvi36llURq+xuHR8YrWLVA/Fk7iqnOLOB7f88w+zrowiJHdvXZTgYTUbK28r7iJ3S1lL0pu9rrDzkHkxWT8Zd7k6TronpvtO5PfZ2vFy+j95YmiUPBZNgoq2nrU8UqVnXTHO3+WdleyWHqg5xqOqQ9TGBykCzIFJF2UwUDWSTP1BUZahmO5Zans5Ocx+sgs4CdEYdU5RT+o0OqdXqAaNI519zvJn/XO7Uddbx1/S/IhPLeDzlcdzl7vYe0rimv4yP4X4nnAJoAvBd8Xe0dLfww2k/vKDA0tHQKDXkNeXZbbHrKAS6B3LjpBv54twXfHXuK7vscHcZuthdtpswjzDmBs7t8zuZRMZjyY/xYc6HbC/ZztMHnuaJlCcI9bS9s+DC4IWcqjvFkeojXBVxVb/HjNak397TzmsnXyOrIYs47zh+kfQLh/1cXht5LdtLtvP52c9ZGLwQuUQ+Ktdpa+ri11/9iXZTBz/weZDrrp05JlGf85GIJdw6+VZeP/U6u8t2O0Rqr1whtUaDjn9TxOEvCpi2KJi4+YEOlxLXqe8koy6DtNo0Muoz6NB3WH/nIfdgqs9Us8D4n8jwUfggEonIb8rnSNURFoUsskm6pVgkRuWiGvB71aHvoERbYhVoRS1FHK46zOGqw4A5UjTFewpJAUkkByQToAwY8jgGEjkDRVWG0l+sd02Pp6cnHR0dnKk7gyAIRBDRZwy9axcTEhKsNQ0ajeaCsfa+/41GvzMno0OXoYuXT7xMu76dx1Medzhn3vHAQJFbC8P9TjgF0DinraeNzYWbCXALcKimpxcjwjMCgJLWEmb4zbDvYOzMTZNvIq02jU0Fm0jRpBCpihzT6x+oOIDOqGNVxKp+F29SsZQfTvshYR5hvH/mfZ499CwPTH+A+UHzbbrYSwpIQiFRcKDiwEUF0GhM+oUthbxx8g1qOmtYHrace6fda7dI3GBwk7lx86Sb+SDnA7aVbGN19GqbX6O2uJX3v/2UIpc85gcu5K5rr7Prwn524Gy8crzYWbaTGybdYHdDCguB0SqW3zeVQ58XcGZ/Jdr6TlKujUQ6TPtvW1HXWcfJ2pOk1aaR05iDUTDXl0SpopjuN51oVXQfsdMflr5c4Z7hYzZupUxJvG888b7x1n/rLYpyGnPIasgiuzGbD3M+JMQ9hGRNMkkBSUzymjSoz8VwU8eGUnNjqU2Qy+VERUXRqe+ksKiQSGUkcaFxF4yhsrISo9F4gatpQkKC9XyWn73Nf5w1QOOD98+8T3lbOeumrLO2fXAyNAYz9w/3O+EUQOOczQWb6TJ08UDCA6NWp2FLolRRABS1FI2aABovKQIysYyHZzzM0wef5u2Mt8c0FU4QBFJLU3GVujI/eP6Axy4PX06QexCvpb/G3079jaNVR/m/hP/DS+Flk7EopApSNCkcqDxATUdNv85Ttpz09UY9n539jG8Kv0EsEvPDaT8cs95HI2VF+Aq2Fm9lU8EmloYutVk6hSAIFKTXcWjXGQ75bCPYT8NjK39i96iGVCxlWdgyvjj3BSdrT5KsSb70g8YIpcqFpXdN4fiWYspzm2lr6mb+rZPGvC6orLWMI1VHSK9Np7StFDC/bgm+CSRrkpnlPwsfV59Bn69Ia266O9YbMufTWxRdH309OoOOrIYs0mrTOFV7ik0Fm9hUsAmVXMWsgFmkaFJI9EsccX1F7/kDGDDd5vzfn3+fOlFzAiSwOmF1v4+XSCTWxV3v2qGsrKw+Rd4TwfznciOtJo39FftJ9Esclc2qy4XBzP3D/U44/orZyUVp6GpgW8k2IjwjmBc0z97DGRThnuGIEVt3GUeD8ZQiEKGKsEsqXF5THuVt5VwVcRWuUtdLHj/VZyqvLnmV9WfWm80bmnK5N/5eFgQvsMkieWHIQg5UHuBA5YF+e1jZatI/13yOdzLfoaK9gkhVJA/PeJgwz7ARn3eskElk3B57O29mvMnXhV+zLm7diM8pCAKnd1eQd6yaowHfofAR8bO5DztMrvqysGV8de4rdpTucCgBBCCVS5h3UzS5h6o5s7+SXR/ksnhtLCq/S3+nRoLeqOdYzTFSS1LJb84HzGltS0KWkBSQRIJfwqC+1/1RpC3C19XX4VJBLRslKZoUTIKJgpYC0mvTSatJY0/5HvaU78Fb4c2K8BUsDV06pA2a+vp6Kiu/r03t3UC591xSX1/fx7Tg/N+ff586UHkAqVjaJ8X4YtEcy/kstUO9i7ydomd80d7TzntZ7+EmdeNH039k942k8cxofvadAmgc83Xh1+hNetZOWeswqSGXQiFVEOQeNKoCaLylCNgjFc7SX2VF+IpLHtt7x/LRWY8yN3Au/8r6F3/P+DvbSraxbsq6Pqkrw2GazzRUchUHKw5y6+RbbT5hVLdX83H+xxytPopUJGVN7Bquj75+XERNz2dB8AK+Lvya74q/MzcNHYFQEUwCp3aUUZBeR01wPk3qMpaGXXmBXa898XH1IUWTwrGaYxeNENoTkUjE1AVBeHgrOPp1EXv/m8fitbE2748EUN9Zz86ynewu201rTysysYzFIYu5MvRKYr1jRzwPdBu7qWyrdDiheT5ikZgYdQwx6hjWTllLTUcNBysPsrNsJ5/kf8LnZz9nTuAcVoSvIM477pL3E0sqGnCBu9T50R4L/f2+N026Js40nCE5ILnPd3SgaM751tpO0TM++Xf2v2nububHM348pOirk7Fl/M3+TgBzcd2+8n2Ee4aPu1qaKK8o9lfsp62nbVScxcbbbtlYp8K16Fo4Xn2ceJ94QjxCLnn8+RG1OYFziPOJ44uzX7CzdCe/O/o7ZvjN4I7YO4j2ih7WmCRiCfOD57O1eCuFLYVMUk8a1nnOp7GrkS/Pfcnust2YMJEUkMTaKWvHdTGqWCRmdfRq3sx4k30V+7g26tphnUcQvhc/ijATJ7134S335u6pd9t4xCPH0qB3Z+lOfjDVMXtohE71RiITcfjLQvZuyGPJuik2EUEmwcTp+tOklqZyqvYUJkz4u/mzOno1S0KX2PQeWqItwYTJmqo8XtAoNdwacys3TrqR9Np0dpTusJoohLiHsDJiJQuDFw7KSS44OPiidtf9OVBdbK7Znredzq5OEtwT+vz7xeYny7/X19dbneScjD/SatI4UHmAmf4zWRyy2N7DcTIATgE0TjlYedBcwB7efwG7IxOlMgugIu3o1QGNN8YyFW5P+R4MgoGVESsHdXx/ETVPuSf3TbuPa6Ou5dP8TzlYeZDM+kxi1DGsCF/BvMB5Q7bMXRi8kK3FW9lbsXdEAkgQBM40nGFH6Q5O1JzAhIlYdSzr4tYxxXvKsM/rSMwLnMeH2R+yo3QHV0dePeSdf0EQyNxVTkF6HX7hHhyO+hJdYxePJjziMKlvvZnmO40gZRB7yvdwe+zto+aAN1KCJquZf+tkDn1+jn0b81ly55Rhp8MZTUb2lO9hc+Fm6jrrECFiVsAsVoavZLrf9FGJ+he3miPz9q7/GS5SsZQ5gXOYEziHyvZKdpbuZG/5Xt4/8z4bcjewNGwpN0668YL0vv6aj/ZHb/EyUK2pIAjsLtuNHDn+Pf5Deg7jKYXbSV96p749kPCATdZm46WmeTziFEDjEEEQSC0ZXAG7IzIWRgjjEUsq3FfnviJGHUOif6LNr2E0GdlRugO1i5rkgMGluQwUUfN38+enM3/K9dHXs7V4K4cqD/Fmxpt8mPMhi4IXkaJJGXRqTqQqknDPcA5UHODOuDuHVMMgCAIV7RXWwlNLs91Ev0SuiryKRL/EcbdRMBAyiYwrw67k68KvOdNwhul+04f0+NzD1Zw9XotvqDtNSdmcLshkcchih0p9641IJGJF+Ao+yPmAI1VHWBzquDurgdEqrrg5mkNfFLJ/Yz7L7p2Km+fgBZsgCBytPson+Z9Q3VGNh8yDGyfdyLKwZfi7DW0xPVSKW8a3AOpNsHsw98Tfwx2xd3Co6hDbS7aztXgru8t2c23UtVwXdd2Iegv1J1Qsi9UOtw7q9fUkeSSh8rh4LVXv2iNL5Gm8pXA7+Z7RSH1zCuLRwymAxiH5zfmUtZVxdcTVDt/3pz/GwghhPCITy/jFrF/wzKFneP3k67y44EUC3QNteo2cxhwadY3cMvkWm9a/hHuG8+MZP+auuLvYW7GXHSU7+Lb4W74t/hYPmQczA2Yy038mk7wm4efq168YEYlErApfxbtZ73Kg4sAlI1StPa0UtRSRWZ9Jem06tZ21gLlb/XVR17EifIXD1YvYkuVhy/mm8BtSS1KHJIAq8po4s68SrwA33Ja08W7mZ4R5hHHftPtGcbQjZ3HoYjbkbWBvxV6HFkBgjgTNWR3J0U1FHPr8HFfeNQWp7NLuZFn1WWzM20ihthCFRMFtMbdxbdS1wzY0GCqOaoAwEhRSBcvClrE0dCnHa47zcd7HfHHuC1JLU7l50s0kKhOprqgGGHSvn5qaGqRSKT09PQiCQHp6urWhaU9PD99WfYvJZCJZlTxgOpvFNhvMdUgW4WOxwXYyfhit1DenIB49nAJoHJJa8r8C9ohLF7A7IgqpgmCPYM61nEMQhGHtzBcVFdHc3IxarSYqanzlqw9EoHsgj856lD8e+yMvn3iZPyz4A0qZ0mbnT6tNA7ig8amtcJebxcc1kddQrC02uzTVmqMy+yv2m4+RuROpiiRSFYmfqx9qhRq1ixovhRezAmbhkuPCtpJtLAhegN6kp1nXTEt3C026Jhp1jZS1llGkLaKhq8F6XR+FDyvDV5IUkES8T/ywOtaPNwKUAST6J5Jem05DVwO+rr6XfExLbSfHtxSjUEqJuFrBC5kv4y5z5/GUx8dskT1clDIl0/2mc6r2FO097Q6ZqtebsKk+tDboyDlYxYlvS5h7Q9RF73VFLUVsyNtAVkMWUrGUayKv6TdVazTpMnRR0VZBiibFZud0pPQdkUjEnMA5JAcks7diL5+f/ZwPcj7gY/3HLFYvZrr79EH3+unp6bE2MdXr9QA0NzcTFhZGQUUB+bp8c42le8iA59RoNH3c50Zrp9+R3oeJSHtPO//M+qdNU98sjLea5vGEUwCNM7TdWo5VH2OazzSC3YPtPZxhE+cdR2ppKrWdtcPapW9ubu7z05EZ6uQzw28GP5j6Az7M+ZA3Tr7Bk7OftEm+vyAIpNem4+/qP+omAGKRmGivaKK9ork99nbqO+vJbsymSFtEsbaY/KZ8shqy+n1sY1cjuY25rP12bb8RTjFigj2CWRyymChVFFO8pxDuGT6hUtwGy8rwlZyqO8Wu0l3cMeWOAY/Vdeg59Pk5TEaBGbcG8ue8F9Gb9Dwx+4lRT62yFUkBSaTXppNRn8GC4AX2Hs4liV8QhLa+i/KcJrz8XYm7IqjP72s6avg472OOVB9BhIhFIYu4LeY2u7wf+U35mDDZtE7OEdN3JGIJy8KWsTB4IdtKtvFZ7mdsrt9MRk8G94Xehx8Dj9OyI29xa5NKpXR2dqJWq/Hz8+Ngy0FEEhGrp6wmIWjgSM7FaopsjSO+DxOJj/M/pqW7xen6Ns5wCqBxxu6y3UMqYHdUpvpMJbU0lZzGnGEJILVabY0AOTrDmXyuibyGsrYy9pbvZWPeRu6Mu3PE4yhvK6e+q56rI64ec7Hg5+bHErclLAldAphrkWo6a2jqaqK5u5lmnfn/LmMX2m4t20u24+XixcKQhdbokLfCGy8XL4Lcg3CRjG2zSUcl0T8Rf1d/dpXt4uaYmy/qHmgymjjyVSEd2h5mXRPKf2reo7azlvun3U+8z8gszG3FYDYKkvzN3dTTa9PHhQASiUXMvj6S3c06zuyrROXnStBkNSbBxLdF3/JJ/ifoTXqSApJYE7vGrj2pshuzAfO92VY4cvqOXCJndfRqloUtY3PBZr4r/o4/nfgTcwLncP+0+y8afbOIlqKiInp6elAqlcTFxQFmx76dZTvxcvEiRZNy0c90f/8+mjv9jvw+jHeq26vZVbqLGHWM0/XNQWhoaLj0QTgF0LjCJJjMBewKNUkBSfYezoiwTLI5jTksDVs65MePp7S34Uw+IpGI/5v2f1S1V/F14deEeoSyKGTRiMaRXpsO4BCfHYlYQrB78EWjmHqjnrymPG6dfOuQGhpebohFYpaHL2dD3gZOVJ/giuAr+j0u51A19WVtTE4O4KB0G1kNWawIX+FQGymD2SjwUngxyWsSp+pOoTfpR9Uu3lbI5BIW3DqZnf/O4fg3xSSs07K+4D3OtZwjUBnIj6b/yKaiY7hkN2TjLnO3qQgbD+k7SpmSdXHruCryKv595t8cqz5GTmMO90+7f8AG4/1lIZysPUlDV4O1xtLSX6iystJqcW2p+7H8+1ikpo2H92G88unZTzFhYt2UdZdlFoIjUltbO6jjxkf3TCeA+ebaqGtkWdiycdnAsTcqFxUh7iFkN2YjCIK9hzOq+Pn5kZCQMOQJSCaR8VjSY3grvHn39LsUNBeMaBxptWm4Sd2I84kb0XnGgpURKzEIBnaV7bL3UByO+vp6srKyqK+vB+DKsCuRiWXW5rbn01zTQe6hKrwC3GiKOcd3Jd8R5x3HPfH3jOWwL4lGo+nTgPJiJAck02XoIq8xb4xGNnKUXi7MujqMNNEBfrnj/1HQUsA1kdfwp0V/cgjx06nvpFhbzFSfqeOmqbat8VZ484ukX/DozEdBgNdOvsZf0v+Ctlvb7/GW7IPeWQippamIEbMsbFm/j7GIfAC53OwMaBH9TsYfRdoiDlcdZpb/rHExr14uBAQEDOq4y/NON0651M11vBHvG0+Trsnq3uXkQrwUXjye/DgAr6S9Qk3H8CbKZl0zBS0FJPonjgvxnBSQhI/Ch+0l2+kx9th7OA5F70gJmHsyzQ2cS25TLuWt5X2ONRpNHN9SDCIR7gs6eT/7ffxc/fhF0i8cLnoy2I0CSwTTEtEcD1S2V/KP+r+S4bcHWYeSH/n9nHvi73GYVM68pjxMmBxCjNkTkUjEFcFX8Oclf2aOZg7Hqo/x2L7HOFJ15IJjo6KiSEpKsmYjlGhLyKzPZHbgbGsdSHBwMHK5nOBgc6TbIvKDg4OtQl8ikThT08YpG3M3IkLEmilr7D0UJ73w9b20IRA4BdC4oaajhsz6TFICU/BWeNt7ODbBMtlacs+d9E+UVxQ/nflTWrtb+f3R3/dxPxssJ+tOAo6R/jYYpGIp10dfj7ZHy+7y3fYejkPRX6TEksp2fhQo91A12rouXJPbebvwDVykLjye8vi4tjkO9QjF39Wf9Np0h48emwQT3xR+w5P7n6SgpYCbpt3A2u6HaT0kp6vNcYR9TmMOYNv6n/GMykV1yWjQ+ZHYjVkb6erqIsklyfrv54v63n/vbYFdU1NjPY+T8cGZhjOcbjjNguAFhHuG23s4ToaB428FOwFgR+kOAFaFr7LzSGxHnLc5ZJzdkD0uo1p6kx6dQUeXoYtuQzedhk50Rh16ox4BAUEQEBAwCSZEIhFixIhEIkSIkIqluEpdUUgV3/+UuCIVSy/II+4x9hCjjmFd3Dr+nf1vnj7wNI/MegSViwqpSIpELEEsEiMTy3CXuSMRX9hvJL02HTFiEv0Sx+jVGTlLw5byxbkv2FK4heVhy8dF5Gos6C+ff7LXZCI8I9hfsZ91cetwlbpaU986A+r5Vv8JYpGYp2c/Pe4na5FIRFJAEt+VfEdZW5nDPh9tt5bXTr5GTmMOgcpAHprxEFO8p1Dp1syhzwtI/66U+bdNcoi6gezGbDxkHiN2hzy/uN+R7JeHOhZLNCjeN55/Zf2LY9XHyGvM4xdJvyDOJ84aia2srCS7LJujVUeJco1C2a2kB/O/n9/ktDeW2lCj0eh0aBtnCILAhtwNSEVSbo+93d7DcTJMnCuKcYBJMHGg4gCBysAJtUOnclER6hFKTmPOsPsB2RKDyUBLdwstupY+zmTN3d/3oenUd9Jl6EJn0GEQDCO6niAIGAQDBpMBo8mIUTCaxRIiTJhAML/3AgISsQSpSEq3sZuS1hIeSH2AIGUQUknfr7AIEZ5yT7wUXlb3NLVczeHKwwS6B1LdUY0GDR5yjxGNfSxwkbhwTeQ1fJL/CYerDo/YBGIiIxKJWBG+gn9m/ZMTNSdYGLyQU6ll1EsrOez3JWLg6TlPM0k9yd5DtQkz/GfwXcl35DblOqQAKm0t5dUTr1LXVceK8BXcNfUua7pbcIyasHhvyrKbqClqJTDavtG4Dn0HJdoSUjQpI67/Od/IwpHsl4c7Fks06HDVYd49/S5/OPoHfjjth0zXTLcKmL11ezFhYqF6IW5ubhgMBqvRgeXa/QnC8//NyfjgeM1xCrWFXB1x9bhpIeDkQpwCaBxQ2FKItkfLktAldhcJtibBN4GtxVspbysfEwtYvUlPfWc9Ve1VVHdUU91RTVV7FTUdNTR3X7ynkEwsQ+2ixlPuiYfIA6PJiLeHNz6ePiikCvP/EnM0RyaWmSM+IjHi/2WZCoJAc08z1e3ma1a3V1PVUUWXoQtBEDBhwiSY/7eIQYlIYo0emQQTekGPWCTGVepKp76TsrYyayNRd7k77jJ3xCIxJsFER08HVe1V6E169EY9le2VtOnbePbQswD4u/pbm5FGeUURpYpySFG0Mnwlmws2s7lgMwuCF1y2BdqDIUWTwntZ75Fem86krgSya/LYF/IpCqmEJ1J+ZdP+LvYmSmWuuyjWFtt5JBdyrPoYb2W8hcFk4EfTf9RvdHv6laFU5jeTtbcCTaQnIrH97utZDVmYMBHvO3I79PMdLx3JfnkkYxGJRMwPnk+oRyivnHiFd7PeZVXEKu6Ov5viqmIyCzIJVYQSrgjHYDBYG5yKRCLEYrH1mv2JMKdD2/jCaDLycd7HKCQKbpp8k72H42QEOAXQOMCR7IttzSz/WWwt3kp6bbpNBZAgCNR21lLUUkRxazEVbRVUtVdR11lnjq70wk3qRpB7ELHesXgrvFEr1Hi59IqguKhRypRW8ZmVlUVPTw9yuZyEhP4b3XXqOznTcIaClgJr8892fbv19zKxjATfBKK8oghwC0CtUKN2UZt/KtTW3WJBEOgydNHS3WKNRjXrmjlcdZij1UfRC3okYok5ctXdYj2/CBG+Cl/83Pxo17fT2tPK8rDlxHrHUtleSbG2mBM1JzhWc8z6mN6iKNY7llh1bL/pdGOJu9ydlREr+brwa9Jr023apd4RGUnKkMpFxWT1ZDLqMnA/tY9t6o9Qe7ryq9m/YprvtFEasX1QuajwVnhT3OI4AsgkmPjy3Jd8dvYzVHIVT8156qKi081TzqTkAPKP1lCW00T4NPs1T0yrSQNGPr9YPrtKpdJq0OFIi/vhjqX3d9IVV+70vpPNzZvZXrKdgvoC3AxuiCVibo69GZcelz6pbefPEb1FmCOlB44FE+X57qvYR1VHFbfG3DquaymdOAXQuCC9Nh2V3Ly4mWjE+cThJnUjrTZt2LspvcVOkbbIKjg6DZ3WY6RiKRo3DcmaZDRKDUHKIALdAwlUBuIp9xxSZO1iO4kNXQ2k1aaRXpNOTmOONUVOJpYR4RlBlFcUkZ6RRHtFE+wePChxIRKJcJO54SYzizQL10dfz87SnbyX9R4ecg9+k/QbDIKBmo4aqtqrqOqooqa9hoKWAqo7qtF2azlWc4zcplwiVBFM95vO1ZFXIxfLadO3UdJaQlFLUR9R5C5zJ9E/kaSAJBL9EnGTuQ36NbIl10Rew3fF37GpYBPJAckTLgram5GmDCUFJJFWns7Hwj/w9vDi2SuenVCRn95EqaI4WXuSHmMPconcrmPRGXS8lfEWx2qOEe4ZzhMpT+DrOrATUdy8QIoz6jmzv5KQODUSydhHN40mI6fqThHuGX7J8V4Ky2fXYvPsCGlvtuB810WZScYdvneQLkrnq9yvaOhpYJLbJJbGLO0Toe5vjugtwjIyMvr0CJroOFI65HARBIFvi77FTerGdVHX2Xs4TkaIUwA5OHWddZS1lbEkdIlDpv+MdFdHKpaS6J/I4arDNOuaUSvUl3yM0WSkSFtETmMOOY05nG0+20fsuEhczKldqihrRCPYPdhmr59lEjMJJgpbCkmvTSetNo3S1lLrc5rmO41ZAbOI844btNgZKsvDlyOXyHk7423+dOJPPJb82AV1MibBxDMHnyG3MZebJ91McWsxxdpi8pq+76GikCiI9opmXtA8JnlNQiaWkduUS3ptOgcrD3Kw8iBSkZQ4nziSA5KZFTBrTPOe1Qo1S0KXsKN0Bxn1Gcz0nzlm1x5rRpoyZDAYqdPVoZR48tslv56w4gcg2iva+r2z5+ZQfWc9r6S9QmlrKXMD5/LjGT9GIVVc8nFyVymxcwPJ2ltB0al6JicPrneFLclvzqdd386qiJGb6yiVSnp6eqw1MI6Q9mYLLM9LKpXS3d2NRCIhODCYRL9EztadZVfVLrSClpO1J0nWJAOOFflyFBwpHXK45DblUtFewTWR1+AqdbX3cJyMEKcAcnAs6W/JAcl2Hkn/2GJXJzkgmcNVhzlZe5Jl4RfmyxtNRoq1xeQ05ZDdkE1eUx46ow4wi41oVTTRXtGjInb6o0Rbws6ynaTVptGsM9cNecg8WByymKSAJKb7TR+zm+OikEW4SFz4+6m/89sjv+VHCT9icehi6+9FiKjtrGW633Tuir8LMIuius46a8SsUFvIueZzVjtymVhGrDqW5IBkbpx0I83dzWTUZXCm4QxZDVmsz15PuEc4c4PmcmXolYMSrSPlpkk3sbd8LxtzNzLDb4ZDbgbYguEunEyCiY15G/ks60ukJjmB7oHEBcSOwggdB0sdUJG2yG4CqERbwgvHXqC1p5U1sWu4cdKNQ4pQTk7xpyCtlrzD1UTP9EM8xlEgW84vHR0dABgMhoumBo9HLM+rs9O8ySaRSKipqaGlu4Wq7ipmBczCJJh4Ne1V7o2/lyT3pEtuClosryUSibVH0KUY7ylkE0EUppaY2wysCF9h55E4sQVOAeTgpNemW+tFHBFb7OrM8JuBRCQhrTbNKoAs6WQZdRnkNuZ+L3hEUiarJzPVZypTfaYSo44Zk/QXvVHPkeoj7CjdwdnmswAEKgNZHb2apIAkYtQxdluUzwmcg7+bP6+ceIW3Mt+irK2MO+PuRCwSU9tZS4e+w7pYBBCLxGiUGjRKDVcEX2F+fiY9RS3mqFp2Yzb5TfmcaTwDgFwsJ9Y7lpsm3YSb1I2S1hLS69L5JP8TPj/7ObM1s1kZsZI477hRS0/zcfXh6sir+brwaw5VHmJhyMJRuc54pFPfyRun3uBU3Sm8OwIJ64qnxi+f0tZSIlQR9h7eqBGpigTMAsgeFLYU8uKxF9EZdfwy6ZfMCZwz5HNIZRKik/w5s6+SyrMthMaNbY+39Np01Aq19bUcCZbCf6PRaO2BMxGwzHFKpZKOjg70ej1Go5Gvsr+ix9TDo9MfJcwzjJeOvcT67PUUuhcy12MuZWVltLW10dHRcYFo6V0jNNjXaSKkkI1nmnXNHK85ToJvQp90dCfjF6cAcmA69B3kNOYww2/GoFIq7IEtdnXc5e5M8Z7CiZoTbMjdQEZ9hjWdTCKSMMlrEvE+8WbB4x0zpt3T6zrr2FG6gz3le2jraUMmlrEkdAkrw1cS7RU9ZuO4FJGqSF5a+BJ/TvszW4q2UNFWwSOzHrEuDqO8ogZ8vEwsMxsfeMdy0+Sb0Bv1FGoLyW7IJqcxh7ymPLIasgAIdg9mcchiXKWu5DbmcqT6CEeqjxDiHsKKiBUsCl40KvVCN0TfwK6yXXya/ylzg+YiE8tsfo3xRnV7Na+kvUJleyWzPa8g6PRs5NM7+JR80mvTJ7QAshoh2MEJLr8pn5eOv4TRZOSJlCeY4Tdj2OeKSvQj52AVBSfrxlQAWZwwV4SvsMnGhaPZXtsKy/OwbPRVVlZS11VHujadGcEzSApIQiQS8ZsrfsPvj/6e3U276dZ3s9h7Mc3N5gyBysrKPtGb4WwcToQUsvHM7rLdGAUjK8NX2nsoTmyEUwA5MJl1mRgF44R0fwNzVOVM4xnSatLIrM+krLWMDXkbCHALYGHwQpI1yUz3nT7mxfcmwURmfSapJamcqjuFgIDGTcONk25kScgS3OXuYzqewaJyUfHc3Of415l/sad8D88cfIYYdQzAkHd4ZRIZU7ynMMV7CrdwCz3GHrIaskivTSe9Np0tRVsA8JB7MMt/Fj3GHgpaClh/Zj0bczeyMGQhK8JX2LRHi7vcndXRq9mYt5FdZbu4KuIqm517PJJZn8nrJ1+nS9/F3VPvxidrCmU0sSRlNt9mbCC9Np1bYm6x9zBHFXsYIeQ05vCn438C4Kk5T424N5tCKSNkipqy7Ca09V2o/MYmfTat1jbub70ZrUW6vdO/LJGtyspKgoOD+Sz9M6QyKevi1lnFo8pFxfPznueFoy9wqPEQSOCG8Bvo7OwcsNnpYJ/bREghG68YTUZ2lu3EW+E9YddjlyNOAeTAWCaoWQGz7DwS22ESTGQ3ZHOw8iBHq49aU9v8XP1o7W4lRZPCs3OetYv9siAIpNemszFvIxXtFYgRkxSQxMqIlST4JoyLuhOZRMaD0x8kwjOCD7I/ILshG1epK0HKkYXs5RI5SQFJJAUkYRJMFGuLSatJI702nZN1JwFztC5IGUS7vp3UklR2lO4gKSCJNbFrbGZxfnXk1Wwr3saXZ7+0RqEcldFatAmCwNbirXyU8xGuMld+NedXxLpNZctnmfiFeeAT4EmiXyKHqg7RpGvCWzG2aVVjSYhHCGm1aTR0NYxJWkp+Uz5/Ov4nRCIRT81+ilhv29RZTUrypyy7icKTdcxaNTaNXU/UnMBF4sI0H9tZpI/WIt2RIkttsjYKDAXMDp59gcmIp9yT5+c9z4vHXuRYyzHCjGHcmnDrBc1Oz3eW6/3n8VznM1FJr02nSdfEHbF32L01hBPb4RRADorBZCCjPoNoVfS4X8AIgkBJawkHKw9yqPKQteFolCqKeUHzSA5IJsg9iKcOPEWxthijYETC2N5kchtzeT/jffKb8lG6KFk9eTWrIlaN2BrWHohEIq6KvIpg92Ae2f0I9V31fHb2M26ZfAsyycjTxsQiMdFeZuOJO6bcQV1nHSdrT3K0+ii5TbkIgoBEJMFF6sKhykOcrD3JwpCF3B5zO35uI5vUXSQu3BpzK//M+iffFn3LrTG3jvj5jBb9LdpGKopae1pZf2Y9h6sOE6QM4omUJwh0DyTvaDUmo0D0LPM5kwOSOVRlfu2Xhy+36fNyJCwGHM265lEXQIUthbx0/CUAm4ofAJ9gd7z8XSnJamD6lSFI5aN7/6vtqOVs81nmB823yT1hKAznO2Dv9K/g4GDr9f+R9w9EiFg7ZW2/xyplSp6e8zS/P/p7Pjv7GTKxjBsm3QBgFTnnPx/Lnx1J6Dn5ntTSVCQiCUtDl9p7KE5siFMAOSj5Tfl06DvGdbi1vrPeaqNc0V4BmJtt3jzpZhaELCDYva/7zYLgBXyY8yHptenMC5o3JmMsbS3l47yPOVl3ku6ubpI8klgesJz5cfPH5PqjSYJfAtFe0ZS3lfNVwVek1abx8IyHL1kPNFT83fy5KvIqroq86oL3XISIZl0zmws2s7d8L9dFXceNk24cUQO5JaFL2FK0hW8Kv+HK0CvxcbVfE8mB6G/RNpIFzvHq47yX9R7aHi2zNbP58YwfW9NDK3KbkblICI41C4JE/0QkIgnptekTWwC5/E8A/W9TZbQo0Zbw4rEXMZgMPDXHtuIHzJsWEdN9ydhZTm1JK8Exo+useKjqEGC+544l9fX1lJWVAUPrE2Tv9C/L9dNr08lqyGJh8MIBo9pKmZJn5jzD7478jg15G+jq6GKywexUWFNTQ0JCQp/nc75BgrPOx3Goaq8iqyGLK4KuwEvhZe/hOLEhTgHkoIxGfvZYYDQZOVl3ktSSVE43nAbMDTVXhK9gYfBCYtQxFy24vSLoCj7K+YiDlQdHXQDVddbxaf6nHKw8iIDAFUFXsNx3OSatacJMPoIg0GXsYnHoYmLVsXx57kueOfgMN0y6wWbRoPPxc/Pjpsk3ceOkGyltLeVA5QEOVR4yF1y3V/Ne1ntsKtjEHbF3cMOkG4aVwiYVS7kn/h7+ePyPfJT7EY/OetTmz2O4nL+7ff6izdJTRKlUDvqcvaM+7jJ3fjbzZ8wPmm/9HnW19dBU3UHoVG9rM003mRtTfaaS1ZCFzqBzWBOVkeLl4gVgtaMfDRq6Gqxub0+kPEG8T/yoXCdospqMneVUnWsZVQEkCAIHKw/iIfdgut/0UbtOf1giIMC4u8/qjXr+nf1vFBLFRaM/vfGQe/Ds3Gf57ZHfsuHsBm7xu4VpHtMGfN72Fnr9Ye/6K3uzo3QH4LS+nog4BZCDklmfibfC26ZF5KOJtlvL7rLd7CjdQaOuETFi5mjmsChkETP8ZwzKsUutUDPNdxoZdRm09bThIfew+Th1Bh2f5H9CakkqBsHADL8ZrJ2y1iY2sI5Gu74dg8mAj8KHW2JuIVmTzFsZb41qNMiCSCQiQhVBhCqCO+PuJLshm73le9lZtpPazlpeO/kaG3I3cN+0+4bcOwVgpv9MkgKSOFx1mOVhy4n3HZ1F6VC5VITH0lOko6NjUAuL3lGf5IBkHkh44IJdyOoCLQDBk/v++0z/mWQ1ZJHXlEeif+KIn5sj0jsFbjToNnbzatqraHu0/HzWz0fk9nYp3NUuePq6Ul3QgmASEIlHx1K+WFtMZXslqyJWIRWP7RLAsgGgVqvH3WJ6c+Fm6jrruDPuzkFHnVUuKp6Z8wyP736crxu+Zlr4tGGbINiLyzktTxAEjlYfJcAtgDjvOHsPx4mNcQogB6TL0EVVexVzAueMWl8VWyAIAnlNeaSWpnKo/BC6Hh2+br7cGnMrS0OXDis1aWHIQk43nOZo9VGb77jkNObwdubb1HXWEa2K5s64Ox1m4TwatOhagO8XieGe4byw4AU2FWwak2iQBbFITIJfAgl+Cdwz7R52le3ik7xPKGk1pxX9N/e//HTmT1kUsmhIRhP3xt/L6frT/OvMv3h50ctjvpjrj0vVKvT+/UALi0tFfXpTda4FkViEJqpvWqHFAbCwpXDiCqD/pcC1dLfY/NyCIPBO5jsUa4u5ZfItY5KWGxzjRe7hapqqO/AJHh23yQOVB4CB099Ga1HeewNgPFHXWcemgk0EuwdzdeTVQ3qsj6sPT13xFL898lveK3yPSUGT+qQAO7rAsHf9lT0pbi2mSdfEdVHX2WUtpjPoaOluoVPfiVEwYhJMGAUjRpPR/PN//yYRSZCIJUhEEsQisfnvIglSsRQPuQcqF5VDzI+OhvMVcUBKtCUICA7VZ6Y3eqOe/ZX72Va8jbI2cz53kDSIJK8kEtQJJMYkDvvcKZoU5GI5BysP2kwA6Qw6NuZtZFvJNuRiOXdPvZurI68eF65uI6Gpuwn4XgCBOX3s1phbSQ5I5q3M76NB9027b9RSe3rjKffkpkk3cUP0DRyrPsbfT/2ds81neXL/k0Sporgz7k6Why8fVMqWv5s/N0y6gc/Pfs53xd9xffT1oz7+/rhU2ltvzv/9+QsLk2DiYOVBPsr5aMCojwWD3khtiRa/MA/krn1v5+Ge4UhEErs1Ch0LZBIZ7jJ3mnRNNj/3poJNHK46zGzN7DEz2wiabBZAVedaRkUAGU1GDlcdJsAtgMleky963GgtysfrYvqD7A/Qm/TcF3/fsPqPTVZP5kfTf8SbGW/yatqrPD/3eVqaWqwNVsFxUwIdMS1vrEivTQdsX4rQZeiiRddCc3czLboWmrqbrH9v1jXT0t1Ck66JLkOXTa4nQoSn3BMvhRdqFzVqhRq1ixovhRfeCm+8XLxQK9R4uXhdVkLp8nmm4wjLgiXS07HSsroMXewq28WWoi0065pxlbpydcTVLA9fjovOxSYTm6vUlRRNCoeqDlHXWYe/m/+Iztc76hOrjuXHM35MoHvgiM45XrBEgCx1Er2JUEXw4oIX2VSwia/OfcXvjvyORL9E1k5ZOybNM8UiMfOC5jEvaB67y3bzxsk3KG4t5uW0l/k0/1Oun3Q9V0dcfcmeSzdE38D+iv18fvZz5gfPt4tj4lAXi70FU0JCAmCONpyqO8XHeR9T2laKu8ydnyb+lAXBCwbceawva8doEAiadKGphFwiJ8QjZEILIDB/vm0dAUqrSeOT/E8I8wjj4cSHx2yzxDtQiUIppbpQS8KSEJuf/0zjGVq6W7g15tYBP1ejIVQcPdXrYmTUZZBWm8a8wHkk+CUM+zyLQhZR1lrGN0Xf8N6Z95gvmo9erwew3gecOBbptem4y9yJVQ/d9MQkmKjtqKVYW0yRtoiS1hIauhpo1jVb23/0h1KmxMvFi0lek6zCxE3qhlQsNUd3xBKkIqk10iMSicyRIZOxb5RIMGIwGWjtbv1eYOmaqWqvQm/SX/T6nnJP1C5qApQBRKmiiFRFEqmKHJFxkaPiFEAOiFUAOUhdSltPG9tKtvH12a/RdmnxdfPlB3E/YHn48u+L2D2w2aS2MGQhh6oOcbDyIDdPvnlY59AZdHyc9zHflXx3WUV9emPZFb+YKLBEgxYGL+ST/E84VHWIjPoM5gfN5/bY29Eox2ZHcmnYUuYEzuHfZ/7NlqItFGmLWH9mPd8UfMPy8OVcG3XtRdMp5RI598bfy8snXuY/Of+xiyGCZbGoVCrJysq65ALvfMGU35TPxryN5DblIhPLWB29mhuibxhUw93manMqkU9I/8dGqiLZW74Xbbd2XE9gAy2e1Qo155rP2exa5W3l/O3U33CXu/N4yuNj2mtKJBbhHeROdaEWo8GERGrb+9WBiv+lvwUN7P42Grv+jp7q1R96o571Z9ajkCi4a+pdIz7furh1lLeVs7d8L96h3kyVT3XYyM/lTmNXI8XaYhYGL7xk75/zxU6RtohibTGdhk7rMQqJAl9XXyarJ1ujLZboS+8ozGg3dBYEgQ59hzX61Nxtjjg168zRp+buZpp0TaTVpHG85rj1cb6uvkSpoiaUKHIKIAekWFuMv5v/oBZAo0ljVyNbirawu2w3OqMOV4Mr1/pcy2yf2cyMnjlq103wTUAlV7GnbA83TrpxyKIlvymfNzPepLaz9rKL+vTGcvNVygZ2HAtQBvDIrEdYHb2ajXkbOVR1iKPVR1kevpybJ908JtafSpmSn8z8CVcEX8E/Mv9BeVs5jbpGNhduZnvpdhaHLOb6qOv7fR8tDVoPVx1mYfBCmzcOvtTOtWWxmJWVNahu7xbBZPI08fKJl0mvTUeMmOVhy7l58s1Dqp1rqu5AJBbh5e/W7++jVFHsLd9LkbaImf6j950dbQZaPHvIPdAZdRhNxhE3KWzraePlEy+jN+l5cvaTI45ADwe1xo2qcy201HXiE2S7OaC9p52j1UeZ7DXZLvfD8Zj+9vm5z6nprGHdlHU2sdsXi8Q8MusRnj34LJvKNxE3J27ciMHLjYulv50vdgq1hRRri/ukqykkCqtIiFRFEu0VTaAy0CE2YEUiEe5yd9zl7oR6hF7we71RT3N3M41djZS1lVHRVkF5WzmV7ZUcqDjAvop9iP73n6+rL7HesUzymjQuRZFTADkYvQ0Q7EVjVyNfnvuSveV7MQgGwj3CuXHSjUTJoqivrR/1CUwqlrIsbBlfFnxJRl3GkBa0u8p28X7W+4hF4ssy6tMbo8kIMGBOb++FeYRfBE/NeYqcxhw25G5ge8l29pXv45rIa7g++nprz5nRZKb/TP685M98mPMhe8v3IhVJ8XX1ZVfZLvaU7WFe0Lx+o1P3T7ufnMYc3s16l1fVr9p08+D8xffFBNFAC7ze59BEazhgPMD+rP0ICMwJnMOa2DXDauTZXNOJys/1opGCKJXZ5a+oZXwLoIFeW4nILHpMgmlEDZRNgonXT75OXWcdDyQ8wFSfqcM+10hQB5o3LJqrbSuA9lXsQ2/SszJipc3OOZEpbCnk64KviVRFcm3UtTY7r1Km5ImUJ3j64NO8dvI1/rjwj3YR2k4GJr02HalIygy/GbT3tJNZn0labRqZ9Zl06L838egtdqJUUUR5RTmM2OkPbbeWYm0xVe1VF0R/WrpbaNe3X/SxcomcHmMP3cZuuo3d1HfVk1mfaTVgkIgkeLl4EeUVxTSfaSRrkpnkNcnum/kXwymAHAyLAYJl4TKWtPe0s7lwM98Vf4fepCdGHcPNk28m0S/Rmi+u8R+b3btl4cvYVLCJ1NLUQQkgo8nIhzkfsq1kG/6u/jye8viAjeouB0yCCWDAG3F/O+tTfaby+/m/J602jY15G/my4Eu2Fm9lcehiVoSv6HfXyJYoZUp+POPHJPgm8E7mOzR2NXJD9A2UtJZYo1NLw5Zyy+RbrAYPPq4+3DP1Ht45/Q4f5nzIw4kPj3gcFqEjlUr79O6xvGaWho6W122gtKGAgACOFB3hTOcZsvdkYxSMTPOZxrq4dcM2O9F16Olq60ET5XnRY8I9wxEjHvd1QAO9thYBZBAMyBi+m2FqaSpZDVksDV1q1+ax3pr/CaAa2zmlmQQTO0p34CHzYF7g2DSZPp/xlAKnN+p5K+MtxCIxD8942OaF4YHugTw661FeOv4S/8j8B8/MfcZhF8yXI12GLk7VncLLxYtX014ltzEXE+b5NFoVTax37LgSO0XaIopazKl5jbrGC45TSBSoFWpCPUKt6Xmeck+kYun3tUciSR8HOoPJQGtPKw1dDVS0VVDdUU1jVyP1XfVUd1RzqPIQ/8z6J24yN4KUQczwm2F93SJVkQ4hipwCyMGwLFTGUgB1G7vZVryNzYWb6dB3EOYRxtopa5npPxORSGSX4lVfV19mBcwivTad2o5aApQBFz22raeN10++TlZDFnHecfwy+Zd4yi++KLxcMArmCJBlgdgfF9tZF4lEpGhSSApI4kDFAb4t/pbtJdvZXrKdOO84VkasJEWTMixHpMGyIHgBgcpAXkl7hc2Fm7kh+gZuiL6Bj/M/ZkfpDvZX7OeayGtYHb0aN5kbS0KXcLT6KPsq9jE3cO6wU+Esn3ej0YjRaKSnpwf43rpXo9H0282+oauBJ/Y8QW5TLnHecbx85cu4Sd3YX7Gf1NJUKtsrAZjuO53ro68fcRNKS/2PWnPxFEe5RE6oZ+i4F0ADYVl8WAT/cKjpqGFD7gb8Xf25O/5uWw1tWCjcZbh6yGmu6bz0wYPkTMMZqjuqWR29elQt7wdiPKXAfX7ucyraK1gTu2bUNtIS/RNZHracnWU72Vm60xmZszMmwcS55nOk16aTWpJKsbYYH1cfOg2dzAyYSZJ/ErMCZvVxVXU0OvWdZNRnkF6bTm5jbh+xI0ZMiEcICb4JRHpFEuoRireLN14KL5vVOQqCQKe+kzMNZzhafZT0unTK28opbi3mXMs5FBIFbjI3lDIlQcogpvpOJTkgmQTfBLs063YKIAejWFsMjI0BgsFkYG/5Xj4/9znNumb8Xf25N/5eFgQv6LOjYa+du5URK0mrTWNn2U7ujLuz32PK28p55cQr1HbWsiJ8BffG33tZ2TgOhGVBOJAAulSxs1gkZnHoYhaFLOJs81lSS1M5Wn2U3JO5eLl4sSxsGcvCltkkP74/or2ieWnBS7ya9iqbCzdT3lbOr1J+RW5TLhvzNvJVwVfsKN3BjZNuZFXEKn40/Uc8tu+xEaXCWT7vEokEuVyOUqmko6Ojj8lBWFjYBYu5Zw8+S3p9OiZMpNenc+939+Lj6oPOqEMpU3Jt5LWsCF9hs/qL1gZzzrlXwMCT10QxQrgYlrofg8kwrMebBBPvZL5Dt7Gbh2Y8NKamBxfDy9+V2pJWmzVETS1JRYSI5WH2i2yNFzvl3qlvo22tf2fcnWTUZ/Df3P+S6J/oTIUbY3QGHVkNWaTVpnGq9hTaHnNT6dbuVjzkHvy/5P/HwpCFuEhc7DzSi1PXWcfJ2pOk1aaR25iLQTDfB0PcQ1gSsoRIr0giPSOJUEWM+vMQiUQo5UrmBM1hTpC5jKOxq5FTdac4Vn2M9Np0Og2dNOma0HZryWvOY3vxdtzl7iT4JpAUYBaZY+Xm6lwpOhhF2qJRN0AQBIG02jT+m/tfqjuq8ZR7cm/8vSwPW97v7qC9du4SfBPQuGnYU7aH22Nuv2BsaTVp/O3U39Cb9Nw/7X7nDtpFEBBGfA6RSESsdyyx3rHcPfVu9pTvYWfpTr449wVfnfuKpIAkFocuHpWdHLVCzW/m/YZ3s95lf8V+njv8HE+kPMEri19hf8V+Ps3/lI9yP2Jr8VbuiL2Du+Lu4t2sd4edCtf78957wdbb5KC/70JWQ5Y1TcKEiYr2Cqb5TWNV+CquCL7C5pNPV7vZytTVY2DXIIsRQrG2eEI2RBUE8+d7IKE/EKmlqeQ25bIyfKXDNEZ29ZRjMgr06Ay4uI0sYtPQ1UB6bTqJ/okDRtKdjH7q2/m4ydx4aPpD/OHYH5ypcGOESTBxuv40O0t3klGfYbWEDvUIZWnYUpICkvhL+l9wk7rZNRV2ICrbKzlYcZD02nRK20oBc63vNF9z3c0s/1mjtik5VHxcfVgevpzl4cv7CM6TtSdp7Gqk09BJe0c7tZ21HKs+hkwiI1oVTbImmQXBC0Z1U8ApgByIsTBAqG6vZn32ejLrM1FIFNwacyvXRV130V1Pe/ZuEIvErIhYwX9y/sOR6iMsClkEmBc8Xxd+zca8jbjL3Hly9pN2K1h2ZCw74yNJDeoPlYuKGyfdyOro1ZyqO0VqSSpptWmcqD2BTCwblZ0cmUTGwzMeJtwznP/m/JdnDj7DL5N+yZLQJcwPmk9qaSpfnfuKtzPfJsYrhkjPSPZV7GO2ZjbJmuQhXcvyOa+pqenzd41GQ2VlJUajkcrKSgwGA1mlWbS3tpNWk9bndRYhYrrvdF5a8NKodRC3CCCFcuAFsiWdtrClcEIKIMuO53AWjr1T39bFrbP10IaNq7v5Pe1q049YAO0q3YUJEyvDnRtEl+LTs5+Oeurb+ST4JThT4caA9p529pTvYUfpDmo7axEhIt4n3uoiatkc0HZradI1MT10ZCnKtsZgMljT8840ngHMDphLQpYwK2AW0/2mO0T0eiAUUgUpmhRSNCl9Ug5P1JygvL2cLn0XUrGUs81nKWwp5NP8T5npP5OVESuZ4TfD5psDTgE0AGO9+B9NAwSdQcdXBV+xpWgLBpOBhcEL+UHcDy5pcXyxgu+xYknIEj7O+5jUklQWhSxCEAQ25m1kc+FmwjzCeDzlcWfawEWwFocPMzXoUohFYuvkUddZx4maE5ysPUlGXQYn605CFtadnKSAJMI8wkYkBkQiEddFXUeIewivn3ydl46/xGPJjzHTfybXRl3LktAlfJb/GdtLtmMQDLT3tPP3U3/nz0v+POTdsP7SPv38/KiqrqKgtYBzXec423EWraBFWiNFIVFwXdR1nK4/TWV7JdN8p/GHBX8YNfEDoGvXI3eVXrJXzEQxQrgY1lTPIVpgO2LqmwXF/wSQruPiDQsHg96kZ1fZLvxd/Sek+LUlmfWZfFP4DdGq6FFPfTufi6XCjdfmsY5EYUshqSWpHKo6hN6kx0PuwQ3RN7A8fHm/awd71GEPRJOuiV1lu9hVtotmXTNSkZQrgq5gefhy4rzjxm3EUCwSW7NK1k5ZS25TLqklqRyvOY7BZEAqlqKUKc3RorqT+Lv6syJiBUtCl9isxtspgAZgrGtfRuOLJwgCx2uO80H2BzTqGgn3COeHCT9kiveUAR9nufEqlUprEbg93Hvc5e7MD57P3vK95Dflc7T6KFuLtzLZazJPzXnqkj1uLmcsAshihjCa+Lv5c23UtVwbdW0fy9CMugw+yf+ET/I/wc/VjwTfBKK9oolURRLmETasguxE/0Sen/c8Lxx9gVdPvMqjsx5lduBslDIl9067lyWhS1ifvZ702nTymvN46sBTvLnszSFdy5IGJ1FI+PrY13S4dFBnqiOtKg1tlxaZVIa/hz9XB1xNtEs0Xl1ehASF4Dd37L4fbW2d9Li3c7b5LAaTwerOA+b33mJNqpQpCXQPpLClcMzGNhC2XtRZ7N6HuhBwxNQ3C9YIUPvIBNCRqiNoe7Ssm7LOYRZKjriob9G18OapN1FIFTw669ExryPtnQr3TuY7PDv3WcQi8bhyznMkeow9HK46TGpJKoVa830vRh3DyvCVzA2cO+BcUNTiGAIopzGHbSXbOFF9AhMmfBQ+rIldw5WhV45Jb76xRCQSMdVnKlN9ptKsa2Z32W52lu20NnNXyVU06Zr4b+5/+TT/U+YGzuXqyKuH7aBqwSmABmCsa19qO2sBCPYItsn5qtqreP/M+2Q1ZOEmdeO++PtYEb5iUDullhsv0G/B91hyXdR17C3fywvHXqDb2E2cdxxPzn5y1HZshztBO9rEbpnEu43dY3pdi2idHzwfg8lAXlMe6bXppNWksbt8N7vLd5vHJ5IS4hFClCpqyKIoUhXJr+f9mt8f/T1/Tf8rP5v5M64IvgKACFUEv5n3Gw5UHuDl4y9zqu4U9267l9/P/z1RXhef1Dr1nZS0llgtQ4u1xRQ1FGEymRCJRbi6uhKpimR1zGqSApKI9IxEJBKZa4MMtl+kmAQTle2VFGuLKW8rt/ZpsHTrrpTVI5GL8Th06Zqrus46OvQd/GzXz/B29UbtokatUBOgDCBKFUWEZ8SodyC3YOtFnd6kR4x4SDVADV0NDpn6ZkGhNL8XXW09wz6HSTCxqWATConCoWoZHG1RbxJM/O3U39D2aHl01qN2q5PqnQq3p2wPy8KXjSvnPEegpqOGHaU72Fu+l3Z9Oy4SF5aHLWdF+AoiVBGDOkeRtggxYsI9w0d1rBejsKWQjXkbyWrIAsyuoSsjVjLLf9aIGz2PB9QKNbfE3MKNk240p/z9rz2BIAi4Sl2RiqXsq9jHgcoDJAcks2bKmmG35nAKoAEYa9eaFl0LYsSo5CNzatKb9Gwq2MSmc5swCAaWhCxhXdy6ITlAWSI/SqXS7u49Ie4huEhcyG3MZVnYMp6a89SICsovJVSGO0E72sTu5eIFmB1t8LDPGCyFmdN8p3FP/D0065rNAkP7P4HRUnSBKAp2D8bH1cfaj8Bb4Y2Xi5f1714uXkjEEkI9Q/nNFb/h90d/z99O/Q1EcEWQWQSJRCIWhSxihu8MHtjxAGebzvKz3T9jScgS5gbNpUPfYW4Cp2uhSddEfVc9NR01fQwjNG4a5gbOxdPgyYzgGcyMmNlvxNEWi5TeYsfSt6FEW4LOqOtznKvUFbWLmjD3MFy6Agjy8GdGXDRysRyJWGLd5RcEAYNgjgq19bSxt3wvuU25iEQiqtqryGvK63Nei0VqlCrK6hoUpYoaFcvkwb5eg91QaOluwdPFc0gRjs/yP6Pb2M39Cfc7VOqbBRc389Ss1w0/eptem05leyWro1c7VKTcXov6i32eNhVs4kzjGVaEr7DeP+zFnXF3cqLmBJ+e/ZT5wfPtPveOBwRBsC6UM+szAQh2D+a2mNtYFLJoyA28i7RFhHqEjtmGkIXq9mo+zv+Yo9VHESFicchibpp0k81cQ8cbErGE2YGzmR04m6r2qj7CVowYV6krh6sOc7L2JItCFnFb7G34uvoO6RpOAeRANHc34+niOSKVX6It4a3MtyhtLSXUI5QHEh4g1juW+vp6smqy+l1M9DcxWHqeWH7aC0EQ+DDnQ7TdWlylrgS6B47YTetSQmW4E7Sj7dZZ+hVYwsiOgFqhJklhrhuycL4oKm8t53TD6YvWLokQ4Sn3RClTIhFLkIll1HbW8uT+J4n3iUetUPdp2OYicQGROSL6ydlP+KrgK/zc/KxudS4SF3wUPswLmkekKpJoVTQRqogLFo319fUU1RRZbbEt35fhLlL0Rj1nGs+QVpNGel06zbpm6+/cpG5M8ppElFcUkZ6RhKvC8VH4WMds0Bv5ct9JwiN8mBN96VQNpUxJc3czP0n8CbHesehNerTdWiraKqyCq7ClkL0Ve9lbsRcwN8eb7jedpIAkZvrPtJmF9kCvV+97Ue/vqdJFxa4PcqgtaSUgwpNl90zFzdO8QGnWNQ+pN0d5Wzn7K/YzzWcaM/xm2OQ52RqL9bXF4W6oCILApoJNyMQyro281pZDGzH2WtT3d9/Pbczls/zPCPcI5+6p9u3/BOZUuJtjbmb9mfV8V/wdN02+yd5Dcmiy6rPYmLeRQm0hYsTM0cxhZcRK4n3ih1V/aTVAGGGPtqHQpGvii7NfsLtsNyZMJAUksTZ2LaGeo9twfDwR5B7EPfH3cEfsHRyuOsz2ku2UtJYgCAImTOws28nBqoOsDF85pO+MUwA5EEOdyHtjifp8de4rBEHg5kk3c3PMzdZGlQMt+vv7nSMs5gVBYEPeBrYWb2Wm/0wAjlYdZe2UtSMyPrjUcxvuBG3riX2kKXWWCFBLd4vNxjQa9CeKBEGgXd9ujtB0N1kjNb1TwDr1ndb6j0hVJPnN+ZyuP020VzRqhdraxTraKxqNUsPJupN4yDysVtXzA+dzV/xdeLl4DWqytHxPRlITp+3WcrL2JOm16ZxuOG1NTwxSBjEvch6TvSYT5RVFgFvAgGOyGM6JB9kjxnJfsXwWZGIZvq6++Lr69imO7905/HT9adJq0jhecxwRIiarJ5McYDa0CHYPHvICo/fnGej3z5bXuLLS3DRWIpGg0WjY9UEO5TlNCAKU5zSx64Mcrv9ZIoIg0NzdPKQUiI/zPsaEiXVx60bVpGIkiCXmcZmMwxNA2Y3ZFLQUsCJ8xYSrFxgu59/3W3taeePUG8gkMh5NenTMd/wvxrKwZXxb9C2bCzezPHw5HnI7he8dmKKWIjbkbSCrIQupSMo1kddwXdR1I7Z+HksDhC5DF1+d+4rvir+jx9TDFO8prJuyjljv2FG/9nhFIVWwNGwpV4Zeydnms3x29jNO15/GKBjRG/V8U/QNu8t2s0KzYlDncwogB2E4E7mF3lGfMI8wfpz44wu+wAMt+vv7nSOE3jcXbubrwq+Z7DWZJ2c/SUFzAX849ge+KfyG+xPuH/Z5L/XcHKWWZ6QpdY4YARosIpEID7kHHnIPQhncd6K8tZzfHvktOqOOH8/4MXE+cX1+/4/Mf7C7fDfLQpdR0V7BoepDFGgL+j22PzQajdUN0fL3wWASTGQ1ZJFaksrJ2pOYMCHG7IBjcdELcg8a1LksDDUyoHYxfxaau5sHPE7loiLRP5FE/0RunnwzrT2tZNRlkFaTRmZ9Jmebz7IhbwPhnuGsCl/F/OD5g+771PvzDPT7595W4wByuRw/Pz9qS3KxPGVBgLqSNgDa9e0YTIZBbxzlN+WTVpvGnMA5Iy6gHQuGGwHaVLAJMWKujxpbNzNHpvd932gy8sbJN2jSNfHwjIcJdrdN3a0tkIllrIldwxun3mBzwWZ+MPUH9h6Sw1DdXs0n+Z9wpPoIIsxpzrfF3GYzJ9ixMkDIbszmnYx3qOuqI9QjlLVT1jLLf5bDbsg4Gpa+hM/OfZas+iw25G2gsKUQo9FIp9DJ52c/H9R5nALIQbBM5JZd+8FwqahPb3rf/M9f4DuC2DmfEzUn2Ji3kXCPcJ6a8xSuUlem+U4jWhXNnvI93Bpz66h0ta+vr7cucu1dyzPSKJxl0evoESBbEeoZytNzn+a3h3/LX9L/wosLXsTP7fv3775p91HaWsqu8l08MvMR5gTOYWPeRn575LdcFXEVa6asGXAx37s/0GDEcXtPO3sr9rKjZAc1nTWIEJHon8gVQVeQ6J84IitPS+RHMA1ugWyJAvROsxsMnnJPFoUsYlHIIvRGPdmN2RyvOc6hykO8m/UuH+V+xOKQxayMWHlREdfbUdJoNGI0GvH0ND93y2e7t+jpjdFopL6+noAIT2sESCQC/wjzrniLrsX8/AZx3zogltoAAQAASURBVLRY6IsRsyZ2zZBeh7HG8r6KxUN3bitsKSSrIYv5QfOdjU8vwke5H5HVkMXysOUsDl1s7+FcwLygeWwu3My2km1cHXm1wzS1tBf9pYmNRq+m0TZA6DJ0sTFvI9tLtiMXy7k3/l5WRaxyGIfG8UiCXwIv+L7AsepjfJL/CVXtVfQYB2ce4xRADoJlIh/sTmZ1ezWvnXyNktaSi0Z9LoajFeufT3lrOX8/9Xc85Z48nvK4tRZDJBJxw6Qb+Ev6X/i26NtRcW+y7EjD4Hf4R4uLNeQcbIRKJpHhLnMf8qJ3PBOliuKnM3/KH4/8kWd3Pstzs58jRBMCgFwi57Hkx3j64NO8k/kOv5v/O15Z9ApvZ77NdyXfcaruFI/OenRAp7jBbBaUaEv4ruQ7DlV+33didfRqVoSvsNlO5fc1IoM73iqG/3efGQ4yicwaHfpB3A84UHmA1JJUviv5ju9KviPBN4GV4StJ1iT3mdB7O0pKJBJ6enro6OggISHhgmN6f/8Aa9PZoNkienrcaarsQukrJvE68+vY1G2Obg6m4W5GfQa5TWYjlaFG3MYa0/8E0HDWRZsLNgNw46QbbTiiicOesj1sLd5KnHcc9067197D6RexSMy6Ket46fhLfHb2Mx6a8ZC9h2QXOvQdbC7YbE0Ti1XHsi5u3SXbeAyXYm3xqBkg9I76xHnH8dCMh9AoHaNeeLwjFomZFzSPFE0Ke8v3suHUhkE9zimAHATLRD4YAXSw8iD/PP1Puo3d3DTpJm6JuaXfqM/FcIT6novR1tPGyydeRm/S86vZv+qzgw+Qokkh2D2Y7SXbuTbq2hFHgc4XE71fG0cQh/2J1aEIWC8Xr8smAmQhRZPCYq/F7KjbwVsZb/HCqhesqQU+rj48lvwYvz3yW1458QovLXyJ31zxG7YWb2Vj3kaeO/QcP5j6A66KuAqRSNQnetHb+KA/eqdngLnvxIrwFcwLnGdzNzWxWIRILELfPTiXMFepKy4SF5ulQ7rJ3FgVsYqV4SvJbcplR+kOjlUfI6shi3DPcNZNWccMvxmIRKIL7jf93Xt6p755enrS0dFhfc2NRiNIDcSsdAVc6enpoaW9kVCCBh0BMgkmNuZuRCaWccvkW2zyGowmhh5zkZdUPjRDnNLWUo7XHGeW/yyb745PBPKb8nkv6z38XP34RdIvhjRvjjUz/GYw1Wcq+8r3cX309Q6Vpjfa9Bh72Fayjc0Fm2nXtxPqEcqa2DUkBSSNWpqY3qSnUdfIJPUkm57XGfUZO6RiKcvDlzPDcwbrWX/p48dgTE4GgTUC5HJxAaQz6FifvZ695XtRu6h5IuWJYTXwc8SUNwCDycBf0/9KXVcdP0r4Ub91GWKRmNtibuO1k6+xqWAT98TfM6Jrni8mHOG16S3K+hOrQxGwaoWac83nRnO4Dsma+DXUdNeQ35XPl+e+5JaY7xe9MeoYfpTwI97KfIs/p/2Z5+Y9x3VR1zHVZyqvp7/Ov7P/TXZDNg/NeOgC44PKysoLBHJ/6Rm3xtw6qnnkIrEIhVKGbpCNMkUiEWoXtc3FcO8Gdi26FrYUbWFbyTZeOv4SU32msnbKWmL8Yvp8p/r7fvn5+VkFUGtrK4mJidTX19PR0WEVRBqNhra2Nqs9P3xf03SpjaNDlYcobStldfTqYacTmQQTbT1tffoxtXT3NefoNHRiMBkwCSarEyGYU9kkIon1f4VUYbV2VyvUqF3Ufazeu9vMiyOFcmgL9I15GxEQuD329mE9x9HE3rWVDV0N/Dntz0jFUh5PeXxUUqhtQe/Xae2UtTx36Dk+yfuEXyb/0t5DG3UEQeBA5QE25m2kSdeEn6sf98Tfw4LgBaMuGLTdWgC8XS4dTR4spa2lvJr2KnWdzqjPWDJYp2CnAHIQLDuzF5vIy1vLee3ka1S0VzDDbwY/SfyJw97Ah8uHOR+S3ZjNqohVLAtfdtHj5gTOIUoVRWppKtdEXnNBlGgoOGI0rLcoS0hI6LNYGOoiwkfhQ5Yxi/aedtzl7qM5bIfC39+f55c/z/OHn+fTs58S6hHK7MDZ1t8vDl1MaWsp3xZ/y7+y/sWD0x8kShXFSwtf4r2s9zhUdYjiA8XcFXEXSpR9ohGW98ZV5WrO0y/eRo+phwjXCBa6L2ROxBz8VKO/wHN1l9E1SAEE5ntLRVvFqI3HS+Fljp5FXsXnZz9nX/k+njv0HCkBKayZsoYQj5Ahna936pwlXc6SImex52/oagAGFkCCIPBN4TcoJApuiL7hktcVBIGGrgarLXthSyGV7ZW0dLdgFPqPuIkRo3JRoZQpUUgVSEVSJGKJtTlrb1t2k2CitaeVstYyDEL/Vu/etWFEtc7hbOkBQhU+RKrMfZmC3IMuuhDMaczhVN0p5gfNJ1IVecnnOdaMdep173ulp7cnr5x4BW2Pll8m/dJuTS4Hw/n3/6SAJI7VHKO6vXpC94Rp7Grkn1n/5FTdKTzkHtwz9R5WhK8YlV5k/THUMoRLcbz6OG9mvInBZODuqXdzdeTVzqiPg+EUQA6CZWf2/FQOQRDYXbab9dnrMZqM3Bl3J9dFXTfhvkg7S3eyvWQ78T7xl+zHYMmP/sOxP/DZ2c94OPHhYV/XESI+5zOQKBvqIsKSBlOsLSbBL+ESR08sFFIFT6Q8wVMHnuLNjDfRKDV90oLujLuT8rZy9pTvIdg9mOujr8dN5sbPZv6Mab7TWH9mPa/nvs4dU+5gdeRqxCIx9fX1VFdXUywq5rU9r1nTM9ZOWYusVoZerx+zBZ7CQ0ZzbSeCSbDWBA2El4sXuU256E36UU398XX15aEZD3Fd1HV8mv8px2qOkV6bzlWRZqOJ1qbWPiLeslD19PSktbUVgKKiIoxGo9UG28L534282jxEPSIMrQa4iH/F2eazlLaVsipi1QWbAP2JnWJtMW36NusxMrGMYPdgIlQR5kiNwgtvF2+8FF7W6I3KRTXke7IgCLTp2/q1eG9tFSGIpRQaSskqPmZ9jEKiIEIVYW5Y20sUiRCxMW8jEpHEIaM/MPabTZZ7ZVV1Ff8p/Q8lrSXcGnMrcwLnjMn1h8v5r9O1kdeSXpvOjtId3B1v/15FtkYQBPaW7+XDnA/pNHSyKGQR90y9Z8w37AYbTb4UgiDw5bkv+fTsp3jKPXlqzlOjVrM0UbBXdNgpgByE5u5mRIj6RHW6DF388/Q/OVR1CF9XXx6Z+ciE9IjPa8rj/TPv4+/qzy+SfoFUfOmPZYJfAgm+Ceyv2M/1UddPqKZhA4myoS4iLDvBxa2XnwAC82L8seTH+P2R3/PKiVd4ceGL1r4aErGER2c9yq8P/5qPcj9C5aJiUcgiRCIRS8OWMlk9mdfSX2Nj3kayG7L52ayfIfGQ8F3xd5ysO4mXixc/SfyJNT2jXlw/pgs8V3cZgkmgu8swqFQpy8Su7dYOuWP2cAjxCOGu8LuYYprC7tbdbC3eysnakyxXLidIGmQViv2ZJDQ3mxcjFhtsC5bvRn19PZmnMylsLiRcEU5VVRW1tbX9TqDbS7YDsCLc3Buiy9DF6frTpNemk1GXgbZHaz1WJpYR7hnOPNU8q8gI8QgZ1D1pqIhE5oa+nnLPC+p1TlWVcc69lgdW/Yk2SbO1N5NFqOU15VmPVUgU+Ln6kdOUw7VR1zpsis1YbzZpNBqqq6vZ07GHY43HuCLoinFR/3X+6zTVZyrB7sHsq9jHHVPuGHEjcEeid9RH7aLmJ4k/IVmTbJexWMyCBipDuBQ6g463Mt/iWPUxwj3DeSLliTG514537GXM5RRADkKzrhlPuad1oq3rrOPVE69S2lbKbM1sHpz+4IRMYeoydPH3U39HKpbyRMoTQ2r6tnbKWp4++DQb8zfyRMoTw7q+vfPShzqWwSwiep8nUh2JCJG1v8HlyBTvKdw37T7+mfVP1p9ZzyOzHrH+zl3uzlNznuK5Q8/xTuY7eMo9rY1BQz1CeXHhi/z7zL/ZVbaLh1IfApE5nWlh8ELujb+3z3dyrBd4ru5mpyJdu35IAqhJ1zRmk3JNTQ0BkgDu8r+LMmUZn+R/wgetH5Dsnsxd0+4C+op6S42Pm5sbBoPhAjFp+WwbjUYqOysxCkbClGbx0N8Equ3Wcqz6GNGqaHIbc/ko5yPONJ7BYDKnnoV7hFtTaqO8ogh2Dx4VsTNUdB1mQejmLsddEkigeyBXBF8BmGuR6jrrKGopori1mPymfHaV7UJv0rOnbA81HTXW/lIh7iGXbW8RPz8/9jXv43DZYRJ8E3h4xsPjMnNCJBKxMnwl67PXc7jyMFeGXWnvIY0YQRDYV7GPD7I/sGvUpzfWLJxhNg6u76znlbRXKG0tZW7gXH4848eD7pF2uWOvUgT73+mdAOb8U8sXL6cxh7+k/4W2njbWxK7hxkk3TthJ7L+5/6W+q577pt035ChOtFc0cwPncrT6KPlN+cOKjjmSJbitxmI5T1lZGWGEEagMtHa4vlxZFraMk3UnOVR1iLmBc4mURvYRm0/PeZrfHP4Nf0n/C8/Pfd7qBOQiceG22NvIasjieM1xpGIpD8942CGaEyq9zDvBrY1deAW4XfJ4S3rtSKywh0rviW2G3wySApJ4O/NtTjWfoia3hjs67sC1zdV6vKW2x2AwkJCQQH19PVlZWdb3yfLZlkgk1BvrkclkzI+bT7A02GqiUF9fj6+vL8Wtxfwr61+UtpbS0t1CobYQqUjKVJ+pJGmSSPJPGlH94GjS2qDDTSVHLLlwwS4WidEoNWiUGq4IvoK95XvJbcplht8MlDIlGXUZnG0+y8a8jQS4BZAUkERyQDKx3rEOIe7Gip2lO/ns7GdEqiL5ZdIvx6yWZDRYFLKIDXkb2FG6Y9wLIEeK+vTGWoc9jAjQueZzvHziZVp7Wrkj9g5umnTThF2zjQb2KkW4fO6GDk63sRtvV292lu7k/TPvIxPLeDz5cYe4MYwWZxrOsKN0B1N9prIyfOWwzrEmdg3Hq4+zIW8Dv5n3myHfdBzJBMFWY9FoNH2auUZ5RXGw8uBlZ4TQG5FIxAMJD1htcO/3vx+pUWoVm6EeoTyR8gR/OPoH/njij/zuit8R5B5ERl0Gfzv1N9r17VwTeQ2lraV8U/QNbjI3u09yao1Z9DRXdxI29dLOZq5Ss9DQGXWjOq7enD+xybpk3OpxK9nKbLZWbeWPJ//IIq9FLFIvsn72z7fM7r0p0Pv36TXpSLVSolRR+LmZXeQ6ejr44swXnBWdpaKtgvK2csQiMasiVpGiSWGG3wzcZJcWi/bEoDfS2tBFUMylF2J6o55P8z9FJVfxy6Rf4iZzQ2/Uk9OUQ3ptOum16Wwt3srW4q14uXixLGwZy8KWTfjGmserj/OvrH8R4BbAU7Ofcvj3/FK4ydxYGLyQnWU7KWwpJNor2t5DGjKOGPXpTUt3CzKxzNp3cLDkNubyx+N/REDgsaTH+pjtOHFsnALIQTCYDJxtPkteUx7+bv48kfIEoR4Tp67lfLoMXbyT+Q4KiYKHpj807NSEQPdAloYtZWfZTo7VHGNu4NwhPd5RTBCGmoo30PG9+wVpNBqi2swC6HI0QuiNWqHmnvh7eDPjTfa07+Fa1bV9xGasdyw/T/o5r554lReOvsCikEVsKtiEq8yV/5f8/0jRpFjTHD7J/4TS1lK7pjl4eCuQysU01XQM6njLd8wkmEZzWANSU1ODQW8gQZ7AkkVLePnIy+xr3kddTx03B95MW1tbn+MH2hQo0hbhIfPA19WXEm0JX9d+TUZrBgbBgMpNRbA4mA55B3dPu5s7ptwxVk9xxLTUdiEI3wvcgdhStIVGXSN3Tb3LusiXSWTM8JvBDL8Z3Bd/H6WtpZyoPcG+8n18ce4Lvjr3FcmaZFaGr2Sa77QJt1Od05jDG6fewEPuwdNznp4wbqkrwlews8xsFjQS4x974KhRn9406ZpQu6iH9H3Iqs/ilbRXECHiqdlP9du6w4nj4hRADkBrTyvFrcUYTUaWhy/nF0m/GFItzHikd+pbgDJgROe6PfZ2jlQf4YPsD5jhN8O60z2eGGr6W+/jLX/vLYZ6C7tIcSQGvYH92fvRxNu/1smeLAxeyNHqo6TXprM0eukFr0VSQBI/nPZDXjz+Ihn1GSQFJPHU7Kes9rN+bn787orf8VbGWxytPkp1R7XdCl1FYhHqADdaagbnBNfbktleWBqe6vV66grqeDzhcT6v+pxdhbv4Z9k/WdOzBrVMfdG+XL2dvUq0JagVap4//Dxnm89i0BsIUASwKnIVocZQ/lP2H6SCuTHeeKL5f4LWO3Dgnej6znq+PPclQcogVkWs6vcYkUhEhCqCCFUEt0y+hVN1p0gtSeV4zXGO1xwnUBnIivAVLAldMuSdb0ekqKWIV068gkQk4ak5TzmsIcRwiFBFEKOO4XDVYe6aete4WSMcqjzEe1nvOWTUpzctupYhrUUy6jJ4Ne1VZGIZT895msnqyaM4OiejwfirCJxgVLVX8cyBZ+jQdxDuGc7Tc54eNze24WKL1LfeqFxU3B57O026Jr4695UNRjj2WBo7Wn5eCo1Gg1wut+6Q9xZD5xOpisRgMFDeUX7RYy4XLKlw7jJ33st6j7aevhGHZl0z+yr24SJxQSqWIhVLL/g+KqQKfp70c26LuY3S1lKeOvAUBc0FY/k0rKgDlei7jbS3dF/yWIsAGkwEyFJ7U19fP+Ix9j6n5fMnCAJGo5Gm+iYemfkIt0bfSp2+jvW166k0VKJUKvu9vkajoUvUxfbW7RRqC8lvzudsw1mmyKbw5Iwnefv6t7lx2o24ertSpCsiRZMy7tK9mqvNAuhSEaD/5PyHHlMP9027b1C25mKR2Czo5zzF61e+zvVR19PW08aHOR/y0I6HeCfzHcpay2zyHOxBibaEF469gN6k54mUJxyyF9JIWRm+Er1Jz97yvfYeyiUxCSY+yvmIN069gVwi5/Hkx/lJ4k8cUvxYenMNtv4nuyGbV9NexUXiwnPznnMY8TMa9+2JjFMA2ZGC5gKeP/Q8DV0N+Lv6k+CXMOGLVG2V+nY+K8JWEO4ZzrdF31LVXmWTc44lluJvy89L4efnZ22S2lsM9Yer1JUQzxAqeioICBhZtG0wOPpNWK1Qc2/8vWh7tKw/s97670UtRTx98GnOtZzjrql38cjMR6hsr+QPx/5Ae097n3OIRWJujbmVXyb9Ep1Bx++P/p6Muowxfiag1pgFc1PVpT83EvH/IkCmS0eAeotqW72f59tdSyQSlEolZ86c4erQq3l+4fNI5VI+bviYPRV7LhD17T3tpDak8k7NO+yp3YMIEddGXssvw3/Jat/VuHZ8H/kt6CnA1dWVq2OuHtGY7UFTdSduKjkubhcXNZn1mRyrOcYczRym+00f8jU0Sg0/mPoD3l7+Ng/PeJgwzzD2lO/hif1P8LdTf6O2o3YkT2HMKW0t5Q9H/0C3sZvHUx4n3jfe3kMaFeYGzkUhUXCs+tilD7YjHfoO/nT8T3xT9A0x6hheXvSyw6W89cZoMiIgIJfIL3lsflM+fzrxJ6RiKU/PeZooVdQYjHBwXGoz1ElfnALITmTUZfC7o7+j29jN/0v5f7jL3ZGKJrb4AdiYt5H6rnrWxq0dcepbbyRiCfdPux+DYGD9mfUIgmCzc48FFxMxF1t8NnQ18NCOh1jw8QKePPEkWr2WgZgTMge9TE+Hy+AE1kgYDzfhBcELSA5I5lDVIdJq0shtzOW3R35LW08bP5v5M+6Mu5NbYm7htpjbKNYW88KxFy4QQQBzAufw7NxnkYqlvHziZfZX7B/T5+EXZo5O1RQN/P7D0FLgBhthHIo4spwzODiYxMREEhMT6ejosJ47XBLOXX534SH2YGvjVo60HUGj0dBt7Oarc1/xs90/4+vCrwlSBhHpHkmALICbg24mKjjqgu/OydqT/H/2zjs8qjLtw/fUTOrMpPceQgsBEghVEBawl7WBZXV1XcW1CxZE7LBiWwvirq51V7FhQXEFpHeSkJCEEEjvPZnUydTvj/lmnFSSMDNJMPd1cQHJmfO+Z057n/Z7nEROTPIeuHEwlLSqOmiqbcc3tPcsAK1ey4eZH+IkcjrnxphSkZR5IfN4cc6LvDj7RSb5TGJ/2X4e3v0wH2R+gKrj7NfVUFPUVMTzh5+nXd/OI4mPEO8TP9RTshsSkYTJvpPJbcwdtuemvKWcJ/c/SVpNGvND5rNmxpphX4elM5pk8c1Oot4oVBWy9shaS83PcBOjOJszdJTOnP8r7mHI3tK9vJv+Ls5iZx6d9igxyhiMGM+7YtSuVLRUsL1wO2OUY2yS+taVWM9YLgi+gL2lezlaeXTYd/y2FjLojd5qg1bvX82hikMYjAZSalJQt6t5QvKEpUlkZWUlrq6utLa24u/vT4J/Aj8V/ERKVYrdU0OGk7JebwgEAv4S9xcyazN5J+0dtAYtQoGQJ5Oe7FTIeu2YazEYDXxz5hueP/w8q2es7pYSF+sZyzOznmHtkbVsSNtAk6aJyyIvc8hxuHhIUfq7UJHXiMFgRNhHHdBADCDr2htzb56e0jMHUrtmvU/raxR+u2aUAiW3+d/Gt03fsrdpL/W59ZQ1l9GoacTX2ZfbJ97OVL+p3PL9LYTJwlDVqgiNC+00dpOmiZz6HBL9E0ec9HH5mUYAAmMUvW7zU8FPVLRWsDR2qU1rz6KV0axKWkVWbRafnfqMXwp/YU/JHi6NvJTLIi8blkpqBaoCXjj8Amq9mocTHmaK75ShnpLdSfBL4HDFYVKrUoedJHZadRpvpL6BWqfmtgm3cVH4RSNiXWOOipufkT2h6lDxcvLL6Iw6ViWtGpZN6YeLqNNIYTQC5GC25G1hQ9oG5E5ynpn1TKebyIhjohZDlaK0KWcTBgzcPO5muzWku3nczTiLnfnk5Cd06M9eFzGUWC8ee/Oy91YblFmXaanlMGCgoL2gm3RwQ0ODZZ+xyljcJG6kVKXY/bis0/OGM0qZksm+k8msy6RF28LqGat7VPG5PvZ6rhtzHYVNhTx/6PkePa8h7iE8P/t5gt2C+fTkp/w3+78OU1sLjFGgaddTV9o9QmWNAdN8BAxsQdJXemZ/PY5dnznma7S1tbVbKmdkUCRLIpbQoG5gS94WKloruG3Cbbx24WvMDZ7LgbwD6Aw6xruP73HctOo0DBhI8EsY0HEOB8rPNCIUCfCL7NljXttey+YzmwlwDbCbkT3BewIvzH6BRxIewcvZi2/OfMP9u+7np/yf0Oq1dhlzMOQ35lvS3lYkrhiR53swTPWdihAhyVXJQz0VC0ajkS15W3jp6EsIBUJWJa3i4oiLR4TxA785hXpbl2gNWl5LeY3a9lr+MvEvTPA6P1MsRxrWmTB3b7+b2vbaAX1+NAJ0FrR6LQ0dDTSoG2jXtWMwGtAb9eiNegwGAzqjDoPRgFAgRCwQIxQKEQlEiAQihAIhrhJXlDIl7lJ3vsr5ii35Wwh2C2ZV0ipLca5QIESIEIPBMQsm60aZgEMWqnmNeRyuOEyCX4JdPSdyJzk3xN7AR1kf8e2Zb1k6dqndxjpXeup54u/v3yky1Nvic6LXREsESCgQEu8XbzmP5v26urrS1NSEXq+noa6Byb6T2V+2n7r2uhFXGG4PTtadJLkyGYlQgofUgzCPsF63vXbMtYgEIjblbOLZQ8+yKmlVN++7t7M3z856lpeOvcQPeT/Q2NHIXZPusntdX2CMgqx95ZSdbrCkxPWE2SAbqPPB+jrtKr/eX49jX/184LeIUIdbB2/kvEGeKg8/Fz/8Xf1R69W0adsshf4Hiw9iNBoZ5zGux7GTK5MxGo0EuAaQVZdFo7qRenU9jR2NNKgbaNY0m57f//8sNxqNiISm57VIIDI9s52UKGX//8dJiUKmQOmkxFXiardFnbZDT01xM77hHkikPXuiP8kyOXZum3CbXaNbAoGA6QHTSfBLYG/pXr48/SWfnPyErQVbuW3CbUzzn2a3sftDVl0WLx97GZ1Bx8ppK8/rtLeuuEndiPWM5UTNCTR6Tb/qVuyJVq/lXxn/Ym/pXoLcgnh02qMOU98baPuI3jAbQD2VIRiNRj7I+IBT9ae4JOKSYRd1+z2zev9qDlccRm/Uc7jiMKv3r+bdRe/2+/OjBlAvrNq3inZxOy3avr2q/cIINe01tOnaUDop8fD04MucL1HKfnuxagwaWrQtaPVau6dtdG2U6QgD6PNTnyNAwLLYZXYfa3HYYnaV7OL73O+ZHjB9WBUpWtPT4rGyshK9Xo9er++xMaSZF+a8wOr9q8mqy2KC1wRemPMC0P2FkJGRYVl4JvglsL9sP6nVqSwKW+Sw4xyO5NTn8NLRl5CIJNwZdyc/F/7M/wr/xxVRV/T6matjrsZJ5MTHJz/mqQNPsWr6KkI8OvfqcpO6sXrGav6R8g/2lu6lWdPMwwkP23WRovBzwdldSvmZRuIXhvS6QDe/5M+W594V6+vUfD2VlZUNaOHR9Trumg6XX5jPnoY9HFAdwM3FjXk+85ginUKgfyAbczfy5ekvkYgkXBZ5GQWaAgKcAxgXMg6j0Uh1WzX5qnzyVfnkNebxc8HPiIVi1hxc0+NcZCIZYqHY5KiyEoYwGE0OrXZde6/HIRPJCPUIJVIeafkT6BY44O+0JyrzVRj0RgKjFT3+/kjFEYvwwWTfyec8Xn8QCUVcGHohs4Nm80vhL3yb+y2vJL/C7MDZ/Hnin4dEsfRIxRHePP4mYoGYJ6Y/cd4KHvRFgl8C2fXZZNRmDGnkq0HdwCvJr5DbmMtU36ncN+U+h6ZKDrR9RG/0VTP8S9Ev7CzZSZx3HDePu3nQY4xiezLrMi3vNb1RT1Zd1oA+P2oA9YLeqCfEPcTiAVTKlLhIXExRnv/3FJq9hkKBEIPRYPIoGvSWqJDeoKdZ08yP+T9S1VZFgGsAQW5B5DbmcrL+ZKfxSppLqGmvIbs+G38XfyIVkUTII4iSRxEhj7DpQ6Vro0x7k1GTQUZtBvOC53VbMNoDkVDEPfH3sGr/Kt5Ne5cX577YL5nYocDaYDE/zEUikSWtqDcPu7ezd4+ejr487VGKKEQCEcmVyeeNATQYD2BlayXrj60H4InpTxApjySlKoXvcr9jQciCPmVaL4m8BA8nDzambeTpQ0/z6LRHGes5ttM2TiInViSu4F8Z/2J3yW7WH1vPymkrcRI5Dfo4+0IgEBA0RkFuSjUNFW14BvYspW7Ocz8XsRXz9aTX6/vsQzUQUgpS+KrsK6o11UR5RvFQ0kM0FTWh0Whoqm3iyaQnee7Qc/w3+7+0dLTQoGtgjGIM/8z7JwWpBZ2cVDqDDrFQzBTfKcwImIGnzNPiZDI/y8/mYNIZdKg6VDSoG2joaLBEjhrUDdS011CgKuB0w2nL9lKhlHB5OJHySCZ6TyTOO25QzXGLT9YjEPRc/6PqUPF+xvu4Sdy4feLtA973uSIVSbk86nLmBM3hvYz3OFB+gMzaTO6cdKdDo0HbCrfxQeYHeEg9eCLpifNS6ro/JPol8p/s/5BalTpkBlBeYx4vJ79Mg7qBK6OuZOnYpXZLbe8NW9WbmqP05jRhM5m1mXyc+TH+Lv48OPVBmzg6RrEdE70mWiJAQoTEeAxMjnzUAOqFly54CQ8Pj3Pah86g463jb9GsbeaKqCu4b+p9SIQSjEYj7bp2S2pdg7qBl4+9jIeTBwl+CRQ1FXGw/CAHyw9a9mVro8hRxXJGo5HPTn2GWCjmujHX2X08M+HycK6OuZqvT3/Nt2e+5frY6x029kCwNlisH+aDPTe9edpramrIO5VHhEsEmXWZtOvaR2TD2K4M1APYpm3j5WMv06pt5bHpj1nSMa+LvY4NaRv4Ie8Hbhx3Y5/7mBM0B3epO68lv8YLh1/gwakPdpN4FQlF3DXpLiRCCduLtvPS0Zd4dNqjg1oY94eIeG9yU6rJO16NZ2DPi8LBpsBZY309dTXczeegJ6O0p/Ok1Wv55sw3fF3+NVqtlgs9L2SOcg5hHmHU+P8mkpB9Mpvpiulk1WbxasqrpmNAiIeTKW0xSh5leTb+UvAL24u38+i0Rwl2Dx7UMYqFYrycvXpNEzUajdS015DXmEe+Kp8CVYHFKPpf4f+QCCXEeceR4JfAVL+peMo8zzpmW5OG8tMN+EXKcfHoHi38KOsjmjRN3D/lfhQyxaCOyxYoZUpWJq5kf9l+Psr6yGHRIKPRyFenv+KbM9/g7+LPqqRVNlURHWkEuJmcqSlVKdxhvMPhhsf+sv28m25ywN0/5X5mB8126PhmbLWOsQjEWLUIqGqt4vWU13ESO7Fy2sph2b/o9445Eya9Kp0I5whu9x+Yc2jUALITOoOON1Pf5EjlEWYEzOC+KfdZvAwCgQAXiQsuEheC3IIA+PjkxwS7BfNQwkOAaaFW0FRAfqPpBZuvyu/VKIpWRJPglzAsu14frjhMviqfSyIuwcfFsUXxV0VfxbHKY3x75lsS/ROHZSpcV6PnXB/mve3DvAANE4aRa8jlRM2JYa+S1x8G4gE0GA28dfwtSltKuXnczZ0Uo+YEzWFL3hZ+LviZiyIuOuuiNd4nnjUz17Du6DpeTX6VOyfdyYLQBZ22EQqE3DHRtDj5pfAX/n707zw2/TG7GJ5Kf1e8glwpzqojfkEIUufuj3adwST1aouapK7XWddatq7GTtfzlN+Yzzvp71DSXEKUZxQXiC/A3+m3cyhxl1DaUsq2k9vIb83HKDAilUgxYkQsFPPMzGeY5Dup08LPaDSSWp2Kv4u/5blqDwQCAb4uvvi6+DIzcKZl7Kq2Ko5XHyelKoX0mnRSq1MhA6LkUST4JTArcBYBbgE9Goj5aTUYjRA91bfbeEcqjnCw/CDT/KYxK3CW3Y6rvwgEAuYGz2Wi90SHRIMMRgP/zvg3O4p3ECmP5PHpjw97WWVHkOCXwA95P1CgKnCoHPMvhb/wQeYHeMo8WZG4YthJQQ8G83PELIdtMBp4O+1ti6NssM6UUeyLOROmP4q6PTFqANkBa+NnVuAs7p1871lDpyKBqJM8rYvEhQleEzqpjfRlFH1y8hOC3IJI8Esg0S+RGGWMw71CXdEb9HyR8wUykYyroq9y+PgSocSSCrcxbSNr564dNqlw1jdsXFycTfZjvSDt+nPzAnRe1Dy2JW/jm7RviBRHDnultrMxEKPxi5wvSK1OZW7Q3G4KWkKBkKVjl7L+2Hq+Of0Nd06686z7i1JE8fys51l7ZC3/PPFP6tX1XBNzTacaHIFAwJ8n/BmRQMTWgq28dPQlHp/+uF0iQVFTfTm6pYDCjFrGTO/+IjA/XwaqAnc2up4DaxGOjIyMTsa91qBl06lNfJ/7PUKBkOvHXM+V0VeSfjwdo9FIkbqI/an7OVJxxDRfPYxxG8OMkBmE+YTx9MGnUevV/CfjP9zkcxPBgcGWsevUddSp61gUtsguQgU93WvWP/P38efiiIu5OOJiWrWtpFenk1yVTFpNGl+e/pIvT39JnHcc0YZooqRRFgNRrzeQn1aDq1xKQFTnhb116ttf4v4yrFS1HBENate1s+H4Bo5VHSPOO45HEh85LyLXtmCS9yR+yPuBU/WnHGaE/JT/E5+c/IQA1wBWz1htUxn2ocS8PjNHyX/K/4nTDae5JOKS34W0+khnsM7jUQPIxugNet4+/vaAjB8AV4lrj40WrenNKDpZd5KUqhRSqlL4Ie8Hfsj7AQ+pB1P9ppLolzjonPRzJbkqmYrWCq6JuWbIPHbDNRXOVsWbve2n68+tHxCRGZGcaj5Fel468cSPeCOoPxwoO8B3ud8RrYjmrkl39biQnOo7lTHKMewu3c31sdf365oNcAvgudnPse7oOr46/RUlzSUsj1/e6X4TCAT8abypYeXWgq2sP7aex6Y/ZvOaoJBxnqT/WkJeag0xiX4IuvQEMj9f7F24br7WrAU4fHx8yCvL4/Xjr1OhrSDaK5p7Jt9DmEcYxRXFpLamkt6eTkVbBVqdljjvOC6LvYwpvlMs3+XLB15GpBexJHAJ+8v3843mG64XXP9bFKUxHzBFXGyJ2cixFifpK7UPTM/zWUGzmBU0C51BR3ZdNjtLdnKk4gjHNcdxxpk/hP2BYHUwLflG1C1a4uYHdztnwyX1rTd6igadbjjNymkr+1RV7A/VbdW8cuwVipqLmB04m+WTlw8bB9ZwwFz/VKAqcMh43575lk05mwh2C2b1jNUoZUqHjOsIzClwOoOO8pZyvsj5ggDXgGGtIjvKuXNe9QF65plnEAgEnf6MHTv27B+0EQajgQ1pGzhUcYikgKR+Gz8AnjJP6tX1Ax7TReJCon8id8XfxbuL3uWF2S9wdfTVyJ3k7C7ZzSvJr/CXbX/hpaMvsaNox6DGGCzbi7YjEojs0vR0IFwVfRVhHmF8e+Zb8lX5QzoXM4Pp2NxT/6be+gT1tf+Loy7GaDSS0pTSre/Q+UihqpCN6RtRypQ8kvhIr0XwAoGAi8MvRmfQsbtkd7/3r5QpeXbWsyT5J3G44jBPH3y6Wz8CsxG0JHyJRcJXo9ecw1F1RyQWEhHvTXO9msr87r2K6jtM977CSWHTcXvD+hrMV+Xz9NGnKWotYprbNJYpltGmauP9jPd55OAj/FDxA9Ut1Uxzn8Y9wfew1HspMwNnWoyfJk0ThyoOEeIUwhL3JUz1ncqJ1hOcNPwmJmO+t21dGG82coBu91R/7mOxUEycTxwPTH2ADQs3cOOEG3F3d+fn8p+559d7ePXYa9RJy4mI7+xNH26pb31hjgb9ddJfaVA3sObAGo5UHBn0/rLrslm1fxVFzUUsjV3KfVPuGzV+uuAmdcPXxdfu7zSj0ciXOV+yKWcTYR5hrJm55rwyfsB0j7qIXVCpVWxM34jOoGN5/HK7CdeMMjw47yJAEyZMYMeOHZb/i8WOOUSj0cj7Ge9zoPwA0/2nc/+U+wekGKJwUqDWq1Hr1IOO1ggFQmKUMcQoY1g6dinVbdWWyJB1Tnq0IpoFoQuYHTjbbpGhspYyMmozmBU4a8g9l9apcG8ff5u1c9YOSUQMuqe+mY2arr2A+qrjsfY2W/cJ6isP1vp382Lm8Xne56S1pHGL7y12PNqhR6vX8nba2xiNRlYkrDhrbc+0gGnIs+TsKNrB5VGX9zuN1FnszIMJD5qK+k9/zap9q3g48eFOCnECgYDbJtyG3qBnR/EO/pH6Dx5JeMSmykLRCX6cOVZFxp4y/CPlnSIKjepGAIcvXo7VHOPzws/RCrVcF3QdkbJItpRvITUnFamzlED3QOKd41kcsxgnkVOP1/Dukt0IRAJmes0kMCCQR8c9ypP7n2RL+RZmR80mxD2ErMostGotMrUMbBhwdnV1RaPR4OHhQWRk5zrCgaZeKGVKrhlzDVdFX0VKVQrfnthCiuY4eQFZtJ/KY2nsUgLdAqlrr+O9E+8Ny9S33hAIBCwMXUiwWzCvJr/Kaymvce2Ya7km5poBpWPvKNrBB5kfIBFKWJG4Ysh7DtkSW/WtMRMpj+RIxRG7idqYRYx+yPuBKHkUq5JWnbdiAEqZkuz6bAwYuCTiErv2KxxleHBeRYDAZPD4+/tb/nh7952j2tHRQVNTU6c/g+Gr01/xa/GvxHnHcf/U+wdcaGxelDR2NA5q/J7wdfHl4oiLWT1jNe8tfo8HpjzA7MDZlDaX8q8T/2L5juV8nPUx5S3lNhvTzI4ikxE6XOSWw+XhLB27lLKWMj7M+nDI5mFtxHT9f9ffdcXf3x+RSIRer7dEgaw90H3ty/r/QoGQy8ZchkFqIF83PCJi9uKbM99Q0lzCH2P+SLQy+qzbS4QSFoYupLq9mrTqtAGNJRQIuW7MdTyc8DBqvZrnDz3PzuKd3ba5I+4O5gTNIaUqhfcy3uuzB8VAcfGQEp3gR2NVGyWnOkd7GzoaEAlEDuvdUl5Rzs8VP/Nu1ru4SlxZPWs1Mh8Z75S/w7HmY0QoI/hb7N+4w/8OLh97OcH+pnqeuLg4i5pcRkYGVdVVbC/cjrvUnQlupvRfF4kL90y+B4PBwMa0jej0Os7Un8FX4ktNdc1ZZjYwemtGfC6IhCKm+U3jwpIbuK7pHmaGJXGk4giP7H6Ed9Pf5ZXkV2jWNnPXpLuG3IE0UGI9Y1k7dy3hHuF8ffpr/pHyjz57K5nRGXR8kPkB72W8h5fMi+dnP39eGT/Q/fl/rkTKIzFipFBVaJP9WWNt/IxRjmH1jNXnrfEDJjn7gqaC0dS3YUxPWTDnwnlnAJ05c4bAwEAiIyO56aabLA0/e2PdunXI5XLLn5CQgfep+aXwF7458w1R8ihTik0vofq+Tp45LSWvIs+mJ9iMOSf9/qn3s/EPG7l94u14yjzZWrCVh3Y/xAuHXzAVHlvJQA4WtU7N7pLdBLsFM85z3LlP3kZcFnkZ8T7x7C7Zzb7SfQ4d23zuXV1dO6XMWBswZ0un8fHxsRhA5heo9YLR2kDqaxyA+SHzkQglbCvc5oCjHxryG/P5Pvd7wj3CuTL6yn5/bmHYQoQI2VY0uO8mKSCJ52Y9h1Km5J8n/smHmR+iNWgtvxcKhNwdfzfxPvHsKtnFlzlfDmqc3hg7KwCJk4jMPWXo9b/1tWhUNyJ3kjtEHKVN28Z3Dd+xX7WfSEUkF4ZeyBvH3+CbM9/g5+7Hk3Oe5NWFr6JUK9FqtZbr2Xyf5OfnU1xcjEajYX/efqrbq5nkPAmDzmDZNkYZw2VRl5GnymNTzia0Ai0hLiE27202mHTV/lB8sp7G6namT4lj1ZzHeW7Wc8R6xrL5zGZ+Lf4VPxc/xnuNt+mYjsLb2ZvnZj/HrMBZHKk8wtMHnqa6rbrX7VUdKtYeWcsvhb8wwWsCL859kVCPUAfO2DHY+loyK5vaIw3u6zNf80PeD8QoYliVtMqhDU4djcFo4EzjGfQGPXfE3TGa+jZMsbUD4bwygJKSkvjoo4/43//+x8aNGykoKGDu3Lk0Nzf3+pknnngClUpl+VNSUjKgMQ9XHObDzA8JcA04q8RtXyfPHAEqrCq06QnuCReJC0vCl/DKvFd4eubTzAqcxcm6k7yW8hr37ryXr09/TYO6YdD7P1B+gDZdG4vDFw+r1A2hQMjfJv8NpZOS9zPep6KlwmFjm899a2urxWCBzgaM+d+AxQjuajT39QK1NpCsI5ld0y5qamooyikiXh5PTkOOXbyHQ41Wr+Wd9HcQCoTcM/meAUVkvZ29SfBLIK06jarWqkGNHy4P58U5LzLOcxz/K/wfTx94utO+JEIJDyU8RJQ8is25m/lf4f8GNU5PODmLiZ3hT0tDBwVpv9UiNXQ09KsnzblS2VrJUwee4mTzSaYETkEn1LH5zGYkQgl/nfRXXpn3CkkBSQgEgm7Xs/k+aWj47fmT0pKCul3NvMB5nbatqalhvGY83hJvvjr9FRqDhkBZYI9zOhfPofU9aiv0egOZe8uQykSMmxkAmCIn18Rcg0wkQ+GkoLK1kvt33c/ukt02jRI6CieRE/dPuZ+lsUspai5i1f5VnKw72W27zNpMHt37KFl1WSwOW8yqpFV4SM+tB99wxdbXkr2EEL7L/Y6vT39NpDySJ5KeOO+V9w6WH6SpowkPJw98nM9/UaCRiq0dCOeVAXTxxRdz3XXXMWnSJJYsWcLWrVtpbGzkyy9797A6OTnh4eHR6U9/yarN4q3jbyF3krMqadVZVaP6OnlKJ5MBJHIV2cXb2BMCgYDxXuN5YOoDvLPwHZbGmsK+X53+ir/9+jdeT3mdrLqsAb18jUYjvxT+gkwkY27QXHtNfdDIneTcN+U+OvQdvJ76Olq99uwfsgH9uXHNi7SysrJeU9nO9gI1jwOcNR1uomQiYBKrON+wTn0bjBrV4vDFGDGyo3jH2TfuBbmTnNUzVnNV9FXkq/J5bN9jnfp4OYudeWz6YwS4BvBR5kedfneujJnmh8xVzMn9ZWjUOgxGA43qRrsLIOQ15vHk/icpaS4hxD2EUw2naNI0cePYG3njwjdYGLqwU81T1+vZfP0qlUqkUikuvi5kNWYR4xyDVCvttG1lZSVqjZrZbrNp0jRR2VZJRWsFm09uZkveFn7M/5GfC35mW+E2dpzeQa4ql+zi7LPe87ZOs+jxe0qpprWxg7EzAyw9m1QdKt5OexulTMmHSz7k3in3IhFK2Ji+kfXH1lPXXme3+dgLgUDA1TFXszJxJVq9lhcOv8CBsgOASTH1y5wveeHwC6h1au6bch93xN1hkz5VvxfcpG74OttWCOGn/J/4/NTnhLmHsSppFa4S17N/aASjNWj5IucLnCXOKJ2U5+T8HcW+2NqBcF4/aRQKBWPGjCE3N9fm+y5UFbL+2HokQglPTH8CX5fuDey60lfBrDkCpHfSEzdu8H1hBotCpuDqmKu5IuoKUqtT+aXwFw5XHOZwxWHCPMJYGruUKb5TzhrROdN4hqKmIhaFLRq2IfMJ3hO4Zsw1fH36az7N/pTbJw6se/BA6W/hq9kwEYlE3bzi/TWIzddYV0EE63+b64US/BLY27GXvaV7uXbMteeNsk+BqmBQqW/WTPSeSIBrALuKd3H9mOt7VY47G2KhmGVjlzHeazwbjm/gjdQ3yKrN4tYJtyIVSS3OkzUH1rAhbQMeUg8mek8c1FidxpWKmDgvmOSthZzYWULMQk8MGOx6jnPqc1h3dB3NHc24SFwoaS5hnOc47o6/u99Nmrs+Iz/M/BCxWMx0z+m0u7azrXAb+SpTH7RSVSnN6mYkYgmt2lY6DB1sr9+Op7Mn4ubOrzadVodWp0UilrChYgOuEldLI+kIeQSR8kiC3YORCCU2k6jvjZaGDjL3lOEqlxKdaHpvmBVEGzsauW/KfQS5BxHkHsRU36l8lPUR+8r2sWLPCm6bcBsXBF8wrCLr1vT2rEv0T+SF2S+w9uha3jr+FvXqelKqUsiuzybcI5wHpz5IgFvAEM585BKhiOBoxVGbCCFsK9xm6Sn45IwnHVYvOJT8Wvwr1W3VzA2aS3JVMg0dowbQ74Xz2gBqaWkhLy+PW26xrdJVdVs1646uQ2/Us2r6KsLl4ee8T7Nndqi9DyKhiGn+05jmP42Klgq2FW1jR9EOXjr2EuM8x7Fs7LI+1VF+LfoVYMilr8/GNTHXkFWbZck5TwpIsttYXRdUvS0SzIZJ158PZhHWdSHZ9d/m///R8EdeSX6FzWc2c4X/FTZVKBoq/pv9XwCWxy8ftDdZKBCyKGwRn5z8hCOVR5gTNOec5hTvE8/6C9bzdtrb7CjewemG0zyY8CBBbkH4uvjyRNITPHPwGV4+9jLPzHqmTynn/hrUEfHelJysJz+tFkJNPYDMkWZbc7LuJOuOrKO6rRqZWGZpALs4fHGfNUe9HYvRaCS5Kpkvcr5AgIBv67/FUP9bPZOb0A0foQ/xgfEEeQax+fRmatpr8Hbx5rHpj1kWbgajAZ1BR5OmieKaYkrqSjA4GegQdVDWXEZObY7FMJI5yYiURxLjHEOgMZBJfpNs/j0ZDUaO/VSATmtg9qURiCWmaNiWvC2k16RzYciFna41N6kb9065lxkBM3gv4z3eSX+HwxWHuXPSnQ5JZxwofRmPIR4hPDPzGVbsWcGLh19EKVNyfez13Dzu5kE7GEaBcI9wjlQcobyl/Jwaoh6pOMK/M/+Nv4s/T814ash69zmSdl07m09vRumkZFHYIpKrki1qmb9HbK1SONw5rwygFStWcPnllxMWFkZ5eTlPP/00IpGIZcuW2WyMdl07Lx19CVWHiocSHrJZkapMLMNZ7DzkBpA1AW4B3DrhVi6PupxvTn/DzuKdrDm4hgS/BJaNXUaIe2fBCL1BT3JVMmEeYcO+gFUoEHLflPt4bN9jvJv+LqHuoXbzQFobNtC3QWSuAXIUiX6JjFGO4dfiX4nSROGOu908347gRM0JMmozmB88/5wdE3OC5vDpyU85Wnn0nA0gMEVZVyWt4vvc7/ky50ue2PcEfxr/JxaGLiTMI4yV01ay9sha1h9bz7o563pV/+pvhEIgEJB4aTi/vJfJ4b2nMAYb7aIodqLmBM8fep6K1gq8nL2Y4julX1Gfmpoai0hNZWUlXt5enKg5QXJVMilVKZyqP0WLpoVIRSRT/KYQKY+0/Ck5U4JGo0EqlTJh7AS+y/mOaOdoVHoVWXWmCFtXPOo9GKMYg1RqSqUzGo0cOH6AopYiKjWVVGgqyKvL47TwNAC+Kl8S/ROZ5j+NcZ7jbBJ1yU2tpqa4maipPviFm9KtT9adZNMpU4PJP0/8c4+fS/RPZKzn2E7RoOXxy3tVSRuqhUzXZ501HfoO/lf4P9p0bUhEEkRCEf6u/qPGzzliNoTPZe1QqCpkQ9oG5FI5T8186rzJBjgbW/O3otKo+EvcX/Bz8QP4XUeA7B39Hm6cVzVApaWlLFu2jNjYWK6//nq8vLw4fPiwzU6kwWjg7eNvU9pSyrKxy2weNfCUeQ7Lm89T5smdk+7ktfmvMSNgBilVKazcs5J30t6hpu23PPmchhxatC0jRrrUy9mL+6bch1qn5uXkl2nTttllnN7qHHoyiByNQCBg2dhl6I16DrUfclj9mT0wGA18fupzxEIx18Ved877kzvJiVHGkF6dbrNaMaFAyNUxV7Nm5hrcJG68l/EeLx55kdr2WsZ7jeevk/5KvbqeV1Ne7XXMgRSCusqdiF8YQoO6gfZmrc2jBll1WTx14ClKW0rxd/Xn7vi7WTNzTb9S3szXe6u+lXRdOg/sfIB1R9exvWg7eoMesUDMvJB5fHbJZzw67VGuHXMtIcIQSs6UWPq7ubq60qxppkPbQYgkBF+xL9uKtnVrRAvdvzeBQEBsSCyTlJNY6L2QZX7LWBm+kieTnuSi8IswYmRrwVaePfQsj+x5hJ8LfqZVO3gp7JaGDjJ2leIqlzJpgcl5VN1WzWspryEVSXkw4cE+1afM0aCViSsRCUSWyG1PNZpD9UzpLUc/pz6Hx/Y+xtaCrYz3Gs8HSz5gjHIMH2V9xC+Fvzh0jucb5uyRwbbQUHWoeCX5FfQGPY8kPoK3c9+tQ84XmjRN/JD3AwGuAVwYcqHFOTScnNCOxl6Kl8OV8yoCtGnTJrvu/6ucr0iuSmZ24GyuiLrC5vtXOCkobCq0+X5tRYBbAA8lPEReYx6fn/qcPaV7OFB+gMVhi7k65mpSq1IBSPBLGOKZ9p94n3huGn8Tn578lLeOv8XKaSvtLhPcNT2tL6+pIxjvNZ7JPpNJq0ljadxSfOQj0/NzuOIw+ap8Lom4xGYv8US/RE43nOZk/UnifeJtsk+AcV7jeGXeK3x68lN2luzkkd2P8Kfxf2JB6AKKm4rZkr+F9zPf5+5Jd3eLPAy0+WbkZB/EWTo62rR0VAjh7OWK/SK7LpuVu1dSp64j1jOWp2Y8RYwypt+fb3dt58fyH8lR54AI5FI5f4z+I3OD5/LpyU9RaVT8bfLfLKIJ1hEjjUYDmHrzqCpUYDQZrAtDF7IhZwNf5XzF8snLu0VCun5vPdXM+fj4MMlnErdNuI2ipiL2l+1nV8kuPsr6iM9Pfc6coDlcFH7RgKLcer2Bo1vyLalvEqmIdl07rxx7hRZNCysSV3SLqPdGon8i4fJw1h9bzxc5X1DUVMTy+OWdmjsP9TPFTIe+g3+n/Jtfin5BJpVx88SbuTTyUoQCIWtmrOGZQ8/wQeYHSIVSLgy9cEjnOlKxRIAG4TzVGrS8nvI6Ne013D3p7t9V889vz3yLWq/m+tjrEQvFiIViXCWuPTpPfi8M9N0y0jmvDCB7crDsIJtzNxMlj+Lu+O6LEluglCnJqstCo9cgFUltvn9bEaWIYvWM1WTUZPDZqc/YWrCVncU7adO1oXRSEuHRe/3CcOTSiEspbipmT+kePj/1OTeNu2mop2QX+kqLWTZ2Gek16Xx+6nNWJa0aohkOHp1BxxenvsBZ7MzVMVfbbL9T/aby2anPSKlKsakBBCY5+rvi7yIpIIl/nvgn/8r4F4cqDvHXSX+lpLmE3SW7CXMP45LIS85pHIFAgCJWhOCEgNwdjYwLbEfuc27F0idrT3Lfzvto0baQ6J/Ic7Oew8vZq1+fLW4qZlPOJlKqUgAY5zOOxeGLmeY/DYlQQnZdNqnVqcwKnNWpFso6mqFUKmltbcXf35+9uXsxGo3IJXIuiLmAvXUmYY/Loi6jsbKxx5SOngyjrveEQCAgXB5OuDyc62Ov52D5QbYVbuPX4l/5tfhXZgfO5vrY6/sV7UrbXkxtaQsx0/zwC/fAYDQ1cC1qLmJp7FIS/RP79d2Z8Xb25rlZz1lqgipaK3h02qMWw3+oFzI1NTUcyT/CTw0/UdZcRrA0mGuDrmVB1ALLNgqZgqdmPMUzB5/hnyf+iVgoZm7w8FMOHe4MNnJhNBr5KPMjsuuzuSTikt+VAdqiaWF70XbCPcKZETDD8vNQ91AKVAUYjAaH9EsbZWgZPcP9IF+Vz8b0jSicFDyS+IjdjBOzJ2ekyJ3G+cTx4pwXeXDqgziLnSlUFVLWUsaxymNDPbUBIRAIuDPuTmIUMfyQ9wP7y/Y7dHxHpav0NU64PJzZQbNJr0knqzbLrvOwB7tLdlPZVskVUVfYtIdIsFswfi5+JFcm260Xy2Tfybw671UWhCwgozaDlXtWMsl7EgGuAXx68lPSa9LPeYxmVHgoXZBqndn/1Rk62nWD3teJmhMs/3U5zdpmLoq4iNfnv94v46e6rZoNaRt4dO+jpFSlMDNgJi9fYBJ9mBU4C4lQYuk+LxKIuCH2hk6ft5bIbm1txdXV1XRNizUIhALC/Exy58vGLsOAgU2nNvWa0jHQe04qkjI/ZD5r565l7Zy1TPGdwoHyAzy8+2H+nfHvPgunc1OqyUutwS/cg/iFpijPN2e+4UjlEWYFzuKq6Kv6NYeuyMQyHpr6ENePuZ6ipiKe2PcEp+pPDWpfg8VaMtwi419ZxgfpH/Cvon9R1VLF0pil3BVxF3Fhcd0kxr2cvSw1J++kvWMxikfpP+4Sd8QC8YANoG1F29hRvIM47zhuHneznWY3PNlduhutQWuJRpqJlEei1qupbHV8Ovr5hCNaCdiCUQPoLKg6VLx87GUMGHgk8ZF+ezkHQ7BbMMCwToPrilAgZGbgTJZELMHb2RtXiSuvprzKm6lv0qzpvQHtcEMikvBI4iN4yjx5N/1d8hrzHDa2o/JuzzbO9WOuRywQ89mpz0ZU40WD0cD3ud/jIfXg4oiLbbpvgUBAgl8Cdeo6ipqKbLpva8zRoCemP4GLxIVPsj9BJDClfr2R+sY5N+0tVBUS7hVKwuJwWhs7OPRtHga94ewf7EJmTSb3/nov7dp2/jT+Tzwz85mzOoS0ei2fZX/GQ7sfYm/pXqJdo7nN9zZuCr2pUxpZTU0NXx36ipPVJ1kQuqBbZMVcX9La2mpplqrRaKhtqcXZ2ZlIv0jAFKFO8k8iuSoZtUzdY03KudxzUYooHp/+OE/PfJpIeSTbirZx/677+fbMt+gN+k7bVhc1cXx7MW5KJ2ZcHYVQKOBIxRG+Pv01EfKIc84mEAgEXDPmGh5JeASNXsPzh57ncMXhQe+vv/TUs6yiooL0+nSeOPQER5uPEuoSyrPTn+XGyTcSPykeHx+fHg1PXxdf1sxYg6vElTdT36SkeWDNyH/vCAQCFDLFgFLgsmqz+DjrY/xd/Hlw6oOdenOd7xiMBrYXbsdd6s7MgJmdfhepMD1DbNlX6ffIUNY1D4RRA6gPtAYtrya/Sr26nr/G/ZUxyjF2HS9SPnJvvrTqNHxdfNmwcAOJfokcKD/AI7sf4WjF0aGeWr9RypSsSFwBwMvJLzusGNIeneYHM46fqx8LwxaS25jLntI9dp2LLUmrTqO6vZo/hP7BLh3LzelJyVXJNt93V8zRoIvDL6a8pZw2bRvFTcWsO7pu0CIdqg4Vdeo6IuWRRE31JWqqD9WFTaTtGNhC81TdKR7Y9QBqvZrbJtzGvVPuPXtfsIYzPLbvMb7P+55gt2BWJ63mBu8b8BX5dns5FpcX81PlT6CDcbpxnbyH1h5FV9cujRn//5RbK9xdGnkp0HuTX1vcc+O9xvP87OdZkbgCL5kXm3I28dSBpywL+NbGDg5uzkMkFjD72hicnMWd1LZWJK7oU/RgIEwPmM5zs5/DTerGGylvsK90n0322xPmWixzHZZUKkUgF/BZ5Wd8UfkFGjT8ZfJfePPSN/F39u/kCfb390ckEqHX6zud3wC3AB5KfAitQcvLx16mRdNit/mfjyicFP2Wb65qreL1lNeRCCWsnLYSN6mbfSc3zMiozaCyrZILQy7spkBoWYM1jrw12HBipIgpjBpAffBZ9mfkNORwacSlzAuZZ/fxAt0CcRI5UaAqsPtYtqRJ08Tp+tPE+8Tj4+LDisQV3Dv5XvRG/YiLBkUpolgev5wGdQMvH3uZdl37UE/JoVw/5noUTgo+zvp4xKRibivahhAhfwj7g132H6uMxU3i5rD0HBeJC7dNvI11c9cx2W8yIqGIo5VHWb1/dbcIQ38wP08iFKZ6mimLQvEJcyc3pZqTB8r7tY/sumwe2v0QzdpmLou8jOWTl/e5vVav5b/Z/2XNgTVUtVWxNHYpa+esJc4nrseXY01NDduqt9Gga2CB9wJkBlknA8nao9ja+psSm0gkolHTiBAhculvfUvGKMcQ5hHGnpI9dr2HBQIB0/ynsf6C9VwRdQUFqgIe3/c4X6d/x+7PTqFV65hxZSRyH2fq2ut4Ofll9Eb7qG2FeYTxzMxnUMgUbEjbwK7iXTbdvxnr86IxaDisPsy6zHWcVJ0k3j2eB8IfINEtkazMrE4RIjAZnmYDqKsBPMFrArdNuI2qtir+kfqPQV3rv1c8ZZ6oOlQYjH1Hddt17byc/DIt2hbun3o/we7BDprh8GFb4TYECFgUtqhbqpa/qz8ykWxEOqGHE45y6p4rowZQLxyvPs7Wgq2MUY6xaVF8X7mRIqGIMI8wClQFIyoFKb06HQMGpvpNBUyLgrnBc3l13qsjMho0O2g218RcQ54qj9dTXkdnGHy9xEjDTerGnXF30qZr472M94b9dVjVWkVadRqJ/ol2S08VC8XE+8STr8p3aJO8cHk4z8x8hlXTV+Eh9WBf2T7u3HbngL2TZgPI7N0UioTM/mM0Cj8XMveUkXOk7zSFnPocHt/3OPXqemYGzuSJpCf6jPyUt5Tz+L7H+SHvByLkEfx97t+5OuZqS5pN15djTU0N+3L2cajhEKEuoVw74VqkUimurq6WZ6W10WSOAAkEAvR6PVVNVbhL3amvq7dsLxAIWBK2BLVe7ZCaPolIwk3jbuK52c8RIAom89taTpcWELPAk8AYJa3aVtYdXUdtey13TbrLbmpbAW4BPDvrWXycffjniX+yp8T2kVx/f38kEgknW0/yRsEbbCnYQpBbEKumruKGkBuIDo62GKxAN2O3L+/wFNcpxDnFcbziOJ+e/NTmcz9fUTgpMGBA1aHqdRuD0cCG4xsoaS5h2dhlI0qt1VbUtteSWpXKFN8p+Lr4dkvVEgqERMgjLEIIo5zfjBpAvfD+ifdxk7jxwNQHbJofe7bcyEh5JC3aFmrah3fxmDW5jbkAjPMc1+nnCplixEaDrhtzHQtCFpBek8676e/+rh6Gif6JzA2ay/Hq4+wt3evQsQdaPLmjeAdGjCwOW2zXeZkbHpuvdUchEAiYFzKP/1zyHwLdAsmsy2TFnhW8dfwtqtuq+7WPfFU+QoSEe4RbfiZ1FnPB0jF4eDuT/mtJr0ZQXmMeaw6sobK1krGeY3lu1nNIhL03rkyrTuPJ/U9S3lLO0tilPD/7+bPKOxeXF/N99feIEHFz5M1UV1Xj7+9vqfUxK7iZjSZzBEgoFCKVShFJRcjEsm7P1tlBs3EWO7OtcJvDDPlgSRgLC2/C3xBMXthR3lG9RGZNJi8fe5mS5hJuGncTFwRfYNc5+Lr48vSsp/F29ubd9Hc5WHbQpvuvF9XzdfPXfFX5FR2GDi71vZR1c9YxI2qG5RyZjVQPDw9Lc2fzfe3j42OR6e56n1dWVrJIuYggSRA/F/7MzuKdNp37+YpZAl2j1/S6zdenv+ZY1TG7tfEYCewo2oEBA4vDTe+LnozxUSGE3w+jBlAvtOpaWR6/3OZpCmfLjTR7aR1ZhH+u5KvycZO44evSvcHISI0GCQQC/hL3FxL9EtlXto//Zv93qKfkUG6bcBsKJwUfZX3k0FS4gRRPavVadhXvIsA1gAneE+w6L7Mcc0HT0KSn+rr4sv6C9YS4h6DWq9lbupeHdj3Eh5kfdvP6djUi8xrzCHYP7iZWIHOVMP/GWIsR1DUdrqSphGcPPUtJcwnBbsE8M+sZ5E5yesJoNLIlbwsvHX0JoUDIqqRVnaI+vWGd+nbdmOtw1btazr/1s7KnGiAnJ1P9jN6oRywUd3u2ysQy5gfPp7i5mJyGnH5+04OnrUnD7v/k0FzXwYWXTubmK66gXdfOfbvu42jlUS6NuJTLIy+3+zzAJJO9esZq5DI5bx1/i+TKc6tfq6mpYfux7Ty751nWHFzDmYYzLAhZwCPRj/DHCX/sdp7NRqr57673dW/3ub+/Py5OLjw49UF8nH34d8a/yam3/7kb6ZgFU3pz1CVXJvPNmW/s2sZjuKM36NlZvBNfZ19LS4OeUrVGhRB+P4waQL2wOGzxgHsz9Iez5UaONCEEg9FAUVMREfKIPh+qPUWDNqZt7LXb/XBAJBTxwNQHGOs5lh/zf2RL3pahnpLDGKpUuIEUTx6qOESztplFYYvs3rMh1D0UsUA8pMWxsZ6x3DrhVtyl7sR5xxEuD+d/hf/j/p3382XOlxaRBOvFpbUAQk/I3CRceHOsJR0u9ZciDHoDTZom/n707xSoCvBy9mLFtBWdevJYo9VreSf9Hf6T/R8C3AJ4cc6LxPnE9euYDpw5wKGGQ4TIQlgav7TT+Tc/KwFL0X1ZWZllUd3W1oZGo6FN3YZQIOzx2boofBFgyvu3Jw2Vrfz6cTbN9WqmLgkjNsmfJP8kJnpPRK1T065rx2A0YMRxKaX+rv6smbEGN6kbbx5/c9AqhtVt1byd+jZvFbxFSnUK8fJ4/hrwV26KvomZU2b2+C7reh93NWb1ej0ikajbfW4+h5GBkdwRdQcd7R2sO7Tud92csj+Yn386Y/d07WZNM/868S/cJG52beMx3MlpyEGlUXFByAV9vi9GhRD6x0iRuu6LUQOoF64bc92QjBvoFohUKHWIEIItLuCyljI69B29Lo6ssY4GTfaZzO7S3Txz6Bnq1fWDHt/eSEVSViauJNgtmP9k/8fhKWFDyVCkwg2kePJIxRGECO2eUgSmGo9g9+AhFyi5LPIy4n3iyarLYkn4Eh5OeBhPmSffnPmG+3fdz0/5P+Hp42lZbHYVQOgJJxdTJMg33IPclGp2fp7NK4df4VTDKdyl7twy/hZmBs7s8bMavYZXkl9hb+lepvpO5YXZL/SrMShAh76D76q+Q4SIK3yuoK62rpPRY3429RQlMPcDkkqlCEVCmlXNPT7HgtyCGKMcQ0pVit2cLcUn69j5ySk07VqSrogkOsEUCf/q9FekVqeyOHwxc4Pm8nPhz7yT9o5Di/sD3QJZkbgCvUHPy8de7rNGpCuN6kY+zvqYh3Y/RGZbJtGu0axJXMPlisvxwIOysjIyMjLIKc3h7u13M2fTHG7fejv7U/fT3PxbmnPXprOVlZUWA6hrc1rrnkKGOgNX+lxJfVs9b6S+8btKQx4oYqGpp31P39GHmR+i0qi4dcKtdm3jMdwxR0ET/fp2bI8KIfSPkSJ13RejBlAvdJVHdBQioYhwebhDhBBscQGbvSS9eZh7QiFT8Nj0x7gq+ipyG3NZtX/VsE75c5O6sSpplSWnPq06bain5DCsU+GGkxdWo9dwouYEsZ6xuEvdHTJmlCKKho6GITHYzYvDuto6/jb5byidlPw749+EuofyyrxXuGvSXUiEEj45+QnPnHiGXFkuLgqXbgIIvSF1FnPBDTFEJ/qypf4rDp1JQaKRMd1/OteOubbHz3ToO1h/bD1pNWnMD5nPymkrcZG49PuY/pX8L+q19SzwWoC31LtX1TezdLJIJCIoKOi3KEFkJHFxcWi0GjDS63Ms0S8RtV5NVp1tG/waDUYydpdy+Lt8pDIR828eS9hE0wLzl8JfLClHj09/nKdmPGVJp33z+JsOFVaJ9YzlL3F/oaa9htdTXkdr6NsQrGqt4v2M97l3571sLdhKmHsYz8x5htcufY3EiESLAQqg0Wh4/pip95CqQ0VKTQrvFLxj6dFUWVnZ7T3Tn+a05m3Hu41nnt88Tjec5ueCn+3w7ZwfCDBlX+iNnY3rY5XHOFB+gAS/BOYGzR2KqQ0LjEYjyVXJeMo8O9VC9oRQICTWM5YzjWd+dyqwA8F8H1uL1Yw0Rg2gYYijhBBsodVu9pIMxAAC00Nm2dhl3D/lflo0LTx98OlhHV3xcvZiVdIqnMXOvJr8Klm1tl1MDQZHhKDdpG7cNeku2nRtvHzsZTr0HXYbayBk1GagMWjskqbaG0OZGmG9OJQ7yblvyn106Dt46/hbACwIXcAbF77Bnyf8GalIyqacTdyz4x5+zP8Ro9F41pc+mNThasfkUOh3AnmzL3OzbuSPLrf0mC6i1Wt55dgrZNRmsChsEXdNumtAaYh7S/eyvXg7IbIQZipM0SXr/j5dU+EmT57M5MmTe4wMOsucQUCvzzGz2pUtZcw1ah0Hvskl+2AFngGu/OHP4/EKNPVT2VOyhw8zPyTANYDHpj+Gs9gZiUjCQwkPMTNgJocrDrMhbYNDIxoXhl7IxeEXk12fzcdZH/e4TXFTMW+mvsmDux7k59yfcde7c0f0Hd1SGs0GqIeHBwD5bfmWhbcBAwXtv0VJ/f39u71neovyWm9nfS7ny+fj5+LH56c+P+eGwOcr5tRKodWSrlnTzHsn3sNNYkpn/j3W/Zgpaymjqq2KRL/Efn0PU/2mojPoOFFzwgGzG1mY1x1gumetnR0jjVEDaBjiKCEEW2i19yWA0B9mB83m2VnP4i51Z0PaBv6b/d9hm+oQ5BbEqqRViIViXjr2EifrTg7pfBwVgp7qN5Vrx1xLYVMhG9M2DgtpbPNiNsHXcVKuQymE0HUROcF7ApdHXU6eKo8t+abaNKlIykURF/HGhW9wT/w9eDl7cbLuJBWtFXxy8pOzqsZl1WXxUdZH4KTHxVtCgLsfp7bWc+SHfDTtv0Us9AY9r6e+zonaE/wh9A/cMfGOARk/ZxrO8K8T/8LH1YebQm5CLDKl71j39xkIzjJnPOQevT7HgtyC8HPxI7kq2SbXbkWeil/ey6L8TCNhE7248OaxOLubIiJ7S/eyMX0j3s7ePJn0ZCfRCLFQzH1T7iPJP4mD5Qf5Z/o/HXov3TL+FuK849hetL1TTVROfQ4vHX2JlXtXcqD8ABO8J3BzwM3cEXAHvmpfMjMzyc/P7+ZsMZ+vSJdISxG+SCBinOc4pFIpoaGhZ323WDtxrN9HPj4+hIaGmvYTGMry+OXoDDreSX9n2L4fhhKzAWotRmGd+qaUKYdqasMCy/uin9Lf5jQ5RzS/Hmn0FKmF3h1Qw5lRA2gYMlKEEPorgHA2ohRRrJ2zlhhFDD/k/cD6Y+sH3fXe3kQpoliVtAoBAl46+tKQKRT1VUhsD66JuYYk/yQOVRziu9zv7D5eXxiMBlKqUghyCyLALcBh4w6lEEJX6eCamhrGa8bjLfHmq9NfUdJcYtlWLBQzL2Qea2aswcvZC18XX7YXbeeBnQ/wWsprZNVmdVt417XX8XqyKT1KKBASGRnI9ffMISBKTlFmHb+8l0n5mQYA/pP9H1KqUpgbNJc74u4Y0L1fr67n1eRXAXhy1pPMmjKLoKCgbpFo65fs2SKdIoGoW+qPNQKBgAS/BOrV9RQ2FfZ7rl3RqHUc+6mAfV+cRqfRk3hJONMvj0AkMb1GD5YdZGPaRjxlnqyZuQYfl+6Lf5FQxH1T72Oq71R2l+7m+7zvBz2fgSISinhw6oP4ufjxYeaHfH7qc5468BRrDq7hePVxkvyTWDtnLatnrGZO1ByLyp5Go+nRy2s2yp+a9hQzAmagcFIwI2AG6y9c38mxZj6XxcXFPcpe9+bEsTaIxnmN4+KIi0dT4XrBXFdmNkRHU986k1yVjEwkY4JX/9RCvZ29CfMI43jV8dGGvF3oGqntr7NjODJqAA1DHCmEcC4MRADhbChlSp6e+TTzg+dzvPo4qw+sHrbpDjHKGFYlrQJg7ZG1nKo/5fA59FZIbC+EAiHLJy8nzD2MTTmbOFZ5zO5j9kZ+Yz6NHY0Ob+Q3VEIIZgOgrKysk+fNoDNwkcdFtLS28PqR17u9qAubCnGVuHJn3J08NeMp4nziOFJxhOcOP8cjex7hfwX/o03bhtFo5F8n/oVKo8JJ6IS71J274+/GTS5jzvUxTLssAp3WwP6vcvnyPzv55fR2xnqO5a74gaW9afQaXjn2Cg0dDdwTf49FbranSLT1S/ZskU6RUHTWRYo5VXKwHl1z1KcgvRb/SDlL7pxI5GQfi/F3sOwgbx1/C4VMwZqZa/qMiEuEEu6fej+h7qFsOrXJpql5Z6Nd185Yz7EUNhXyVupb5DXmMT9kPq/Me4WHEx8mShEF/GZwg8mAFAgEiESiHvP9PZ08eXfRu2xeuJm/+f8NY0tn49rasC0rK+v2u/6mYd8Qe8NoKlwvWCJAAtFo6lsXVB0qzjScId4nvl+13ebn7VjXsTRrmzndcNoBsxw5dI3UnmsW0VAyagANQ0RCEVGKKM40nBnWMtE1baaXYKBroE32JxFJuDv+bv40/k9UtFTw5IEnSa9Jt8m+bU2sZyxPJD0BwLoj6xyeDmeL+q2e6Mvb7ix2ZsW0FbhL3Xn7+NuUNJX0sAf7k1qdCvQ/ncGWBLoF0tDR4ND70mwAAN08byHOIcyUzyS3IdeSCmfGXPQfo4xhovdEViWt4h/z/8FlkZeh6lDxYdaH3L3jblbtX8XRyqMonBRojVpuHHujRclNIBAQMcmbJXdORBZkoCCjhsmpl3GV4VaEhv43iDYbWXmqPK6OvppZQbP63N76xXq2a10ilNBh6Ls2baxyLG4SN45XHe/3nAEaq9rY9+XpTlGfuTfE4OLxm5TwvtJ9vHX8LeQyOWtmrOmXCp6z2JmV01biJnHjzdQ3O0XwbI3BaCCtOo31x9Zz/8772VO6hxD3EDycPJgXPI/l8csJdg/u9jmzk8VoNGI0GhGJRJ2a0/a3t4+Pjw8iUc/XykAWUDKxzJIKtzF942gqnBXmBqhioXg09a0L6TXpGDH2+r7o+s4zX8eBetO6xpEOilEcy6gBNEyZ6jfVLspFtqShw5QSY8uHrEAg4NLIS3ls+mMYjUb+fuTvdu/hMVjGeo61RIL+fvTvdhdG6C1f3paczdvu6+LLwwkPozVoWX9sPc2a5h63sydnGs4gEUqIVkQ7fGyFkwKAxo5Gh41pNgCCgoK6ed6CgoJY4reEEI+QbqlwKVUpKJwUnSK0AW4B3DL+Fjb+YSPL45fj4+zDzuKdlLWUcbLuJO5Sd5ICkrrNoV3SzI/+/6Z0XBrhXmGc2VvH1o0ZFKTXYDCcvY5lS/4W9pXtI8Evgetjr+91u54M8LNd63InOU0dTX0uiEVCEbGesRQ2FfbLeG1t7ODID/ls/yCLilwVIeM9WfyXCZ2iPmASPNiQtgGlTMkzM58ZUEqmr4svDyU+hNZgEpRo0bT0+7P9oaKlgq9Of8UDux5g3dF1pFalMsVvCk9Mf4LPLv2M6f7T2VWyi+y6bMtnrL9/a7lxkUiEXq/H1dW1mxHetddPT1Eic5pjUFBQj+e4v4Iu5lS4nIacYZMKNxz6oZifR6cbTo+mvnUhtzEXMF07PdE1RdMsxhLjFYNSphytAzqPGTWAhikjoQivUd0I/LYotCWTfSezds5a/Fz9+Hfmv4dtE9JYz1hWz1iNUCDk70f/bleJ7IHURQyW/kSWxnuN5/aJt1PdXm2S1XVgNMRoNFKgKiDMI8zS+8KRmI19s/HvCPoyAHx8fJgSP4UHpz+IwWBgY9pG9AY9FS0VlLWUkeCX0GOamlQkZV7wPLydvfF39UchVaA36qlvr+dvv/6Nl46+xMGyg3ToOzAajWxM34hKq+KmJVdyzb1JTP5DCHqdgWM/FbLt/UxKsut7NYRSqlL4LPszgt2CuXfyvX2mzfVmgOfn55OSkkJ+fvf6K6WTEr1Rf1ZjPEIegd6op7i5uNdtSgrK2frxEX54O5WizDr8IuQsun08M6+KwlXu1GnbHUU7LIIHz8x6pt/9j6yZ4DWBWyfcSmVbpUmA4hxRdaj4ueBnntz/JA/ufpCvT3+NRq/hquireHPBmzw67VEm+05GLBSzPH45YqGYjekbUevUQOfv31puHECv19PU1NTJCDenKObn51sky62jRDU1NaSlpVFWVtapD1DXc2xO7+yaItcTS8cutaTCOUKa/2zP2uHQD6VB3YCL2IUPMz8cTX3rQl5jHm4SN3yce3agdK09tG60nOiXSEVrBeUt5Q6Za08MBwP7fGXUABqmBLoFEuAaYDPlIntgjwiQNQFuATwz8xlLE9LNZzbbZZxzJUYZw1MznkIqkrL+2Hq7yXkPpC5ioFhLW/YnsvSHsD+wJHwJWXVZ/CP1Hw7ra1LbXkuztnnAsuu2wmzsN6gdZwD1RNeXYowyhsuiLiNPlceP+T+SUn121aPdJbtJq0kjShGF0lnJwwkP83Diw8R5x5FWncYbx9/gr9v+ysq9KzlacZT5wfOZETADkVjImOn+XLI8jvGzA2hTaTj0bR4/bTjByf3ltLdoLGNk1mbyesrruEnc+uwTZD4e6wiD9c8bGkzft/lva8RaMe3t7eSV962a2Zu4jNFgpLJAxYGvz7Drw1yqz6gRuxqYf1MsFywdg9LftfP2RiNfn/6a9zLew9fF96w1P2djcdhipvpOZV/ZPkuzxoHQrmtnf9l+1h1Zx93b7+ajrI8obS5lXvA8VietZuMfNrJs7LJucwxwC2DZ2GVUtVWx6dQmYOCptebnkFkkwWzoWD+n9Ho9er3+rH2A+ouTyIk74u5Aa9Dy1emvBrWPgXC2Z6290pEHQkNHAw3qhtHUty7oDfqzCjVZKw52jWzaQ0J/oAwHA/t8xfEu1FH6TaJfIlvyt1CgKrAUDA8nGtQNiAQiuzaiVMgUPD3raV44/AJf5HyBRq/hhtgbhp13K0oRxbOznmXtkbVsSNuAqkPF5VGXn/N+u3ZRtzZMzD+3BV09v/3htgm30aJp4UD5Ad4+/jb3TbmvkwyrPRhs3ylb4SnzBBwbAeqJns7X9WOuJ7kyma9Pf02QexBSoZQ477geP1/XXsfHJz9GLpXTqG5E6aTkyugrcRI5MSdoDqoOFYfKD7G9aDv7SvchFAg5UnEEnVFHgl8Ck30m4yJzYeK8YKIT/cg/XkPe8Roy95aRtb+c4FglxpgG3i1+E4lQwqqkVX1GSKzrnOLi4rr9XCAQYDQaUSq7L+yMbUaMBiMFlQVMjZja6xid+jiFQUe7jqKMWvJSa2iuVyMQgH+kHJdAPWMmB+Pr69FtHwajgQ8yP2B70XYi5BE8Mf2JTlLXg0EgEHDnpDtZsWcF72W8x1jPsbhJ3fr8TF17HanVqSRXJpNVl4XWoEUkEDHZdzJzg+aS4J+Ak8ipz30AXBxxMUcqjvBz4c9MD5jOeJ/xPd7/QUFBPT5vzEaOVqu1OOq6PqdKSkowGo2W1CJrdTjz/3vbf29M8p7ERK+J7C3Zy2WRlxHiHtKvzw2UmpoatFpThNu6T5U1XY/X0RiNRkqaS2joaODSoEtHU9+sKG0pRWvQWsQ9eqPrOTT/W6FX4CRyIqUqxSbv88FgvsdGosz0cGfUABrGJPglsCV/C8lVycPSAGrsaETuJB+QEtRg8JB6sGbGGtYeWcu3ud+iNWi5edzNw84ICnYP5vnZz7P2yFr+k/0fGjsauWncTYP6fsyGj7X3tOsD2pYv3cE8ZIUCIX+b/Dd0Bh2HKg4hFoq5Z/I9dr0eLAbQEN0Plhqg/0//HCrM58tcb2E2kP888c88d+g5jlQc4ZKIS5CKpD1+/sucL2nXtRPnH8fRyqPcOO7GTgtmuZOcJeFLSK5KJsgtiLnBc6luq2Z/2X72l+1HLBAzzmscif6JJPgmMH5OIGNnBVB+ppG8lGpSzmTwc82nCEUCbve5F1mNJ3pXg0Uyurfj6W2B7erqSmtrK+7u7t2cAmG+YQgqBYjc+za+lTIlHkI5aflZ7Mo4RW1JM0YjSJ3FxM7wJ2qKL27K3o0GrV7LW8ff4kjlEeK843gk8RGcxc59jtlfPGWe3DbhNjakbeDDrA+5b8p9nX5vNBopbCokpSqFlKoUy31gPg/T/KcxI2DGgI0xoUDI8vjlrNi7gk9OfsLaOWt7vH/NzxtzRM58nqzPmfnfXc+P2YhtamqyfLarAT/Q55lAIGDZ2GU8eeBJvjj1BSumrRjQcfeXyspKi2E32D5V9qZF20JNWw0uEhfumDgwWfrzHXPLgsE6zBrrGwkiiMzqTKrbqs8p0jtYhtrAPp8ZNYCGMWOUY3CXuJNSldJn4fBQUa+uR+nkmFC7m9SN1TNW8/ejf+fH/B8BhqUR5OXsxbOznmX9sfX8mP8jqg4Vd8ffPeB6FfMCQSQSOSS9YrAPWXNfE22yln1l+xAIBCyPX243Iyi/MR+JUEKwW3fVKkcwFDVAPWE+XxkZGZ0WkpN8JhHqHkpRUxHezt49frakuYS9pXsZoxxDenU6Aa4BzA+Z3227fWX7yKjNYHH4Yu6OvxvoHHnIrMskozaDD/mQUPdQJnpPZLzXeOQLJRw5uhmnFhGXtNyMNtOV/ZlnEImF+EV44B8pxzPAFbmvMyKxsNPx9Oc4gU7HHOEfgfMZZ4xO3VOF25s1NFS2UlPSQkVuI0atO/nSM1Q3qAiK9iR4rJKQcZ6WefRGm7aN9cfWk12fzazAWdwTf0+/JHUHwtyguRwqP8T+sv3MDZpLsHswJ+tOklWXRUZNBnXqOgDcJG7MCZrzWySul7TC/hLgFsDisMVsLdjK4fLDzAqaZTFizIZnb/U75n9bp812vSatU+HMP+9q8HY1mvpDtDKaJP8kjlQeIac+h1jP2HP6HnrC39/fUpc0XD3w2wq3odFrmBkwEy9nr6GezrDC3LR6oK06rB2Qk90mk9WSxfai7dw07iZ7THOUIWLUABrGiISmlIZ9Zfuoba/tdUEzFBiMBlQdKpv0AOovLhIXnkh6gnVH1vFj/o9IhVJuGHuDw8bvL2Zj7R+p/2Bf2T6aNE08lPDQgLzF1gsEW3l/BrPI6A8SoYSHEx7mtZTX2Fu6F51Bx98m/80uIgWFTYWEeYT1K9XOHsfrInZBKpQOWQSo6zH1FDnxcfFBKBCSUpnCzeNvRiLsvFDfdGoTBgwonBScNpzmhtgbup0rrV7Llzlf4iZx4+ZxN1t+7uXsxaKwRSwKW0S7rp0TNSdIrUrlePVxthZs5Zsz31DVWoWTyIlLoy5lcmggIYJIGgs1lJ9ppCK3kfIzjQAIRQI8vJ3xDHBB6e+Ku5eMNk0zjc11BAb74+v7m7e163GWlZWh1+upqalB7qrAoDdSVlNJmaGBxuo2GiraaKhspb3lN4EOmZuEsUHR1BuLmHy1kljvmH595w3qBtYdXUdRUxEXh1/Mnyb8yS4Gfp26jgleE9hetJ1H9jyCl8zL4uAJcA3g0ohLSfRPJFYZa/NU06tjrmZn8U4+yfgElzoXMGAxWIBuxkxPESAzXbexjh71lc470BRcMAkiHKs8xuenPufpmU/b3CE23L3vWr2W73O/RygQMid4zlBPZ9hxNgEE6Pk9YZ2SG+kcSaB7INvytjFeM57gwOBhfU2M0n9GDaBhTqJ/IvvK9pFSlcKS8CVDPR0LzZpm9Ea9wyJAZpzFzjw+/XFePPIim3M3IxVJuTrmaofOoT9IRVIeSXiE9zLeY1fJLp4/9Dwrp63sd3Fq1xevLRbzg11k9AeJSMLDiQ/zVupbHCw/iFav5YGpD9jUS96h76BJ00S8T3y/trfH8QoEAhQyxZBFgM6WOqQ1aDlVf4pYZSw16hq25m/lyugrLb/Pqc8huSqZOO84UqtTiZRH9ih7vb1oOzXtNdw07qZe61Gcxc4kBSSRFJCE0WhkZ/FO/pH6D9yl7vi6+JJanUpqdSoCBIR6hBIZF0nYtEi8WwNxavagqbqDhopW8tNqAZOaV1t7GxjhhLgRb79KZG4SxFIRQqEAgdAZVU4jRoORmuo2NO16sjSnkAilNHmrOVGSj2+TSfJWKBIg93EmIFqBMsAFzwBXFL4uHKho4djx3TRq+3f+SppLWH90PdXt1Swbu4wro660ySK7VdtKgaqAfFU+BaoCchtyqW6vtvy+XdtOtH80l0RewjjPcXb37HtIPbgi6go+Tv+Yw7WHmeE5wyJpbY4AQe+1EtacLZrXE4Otcwh0C2R+yHx2luwkvSadyb6TB/T5kc62om3UqetQyBQEuPZfgv33gMFooKipiLGeY/u8Z3t6T/j7+1NcbFKLFAqEXDbmMv6V8i+ONxxHLBSPGkB9YC9Hqz0YNYCGOfE+8YiF4mFnALVqTfnQ9hRA6A0XiQtPTH+C5w4/x6acTYiF4iErUOwLkVDEXZPuQumkZHPuZp7Y/wQrE1eetSCzJ2yxmLd3MaVEKOGBqQ+wIW0DB8oPsO7oOh5KeMhm14hZea2/RqS9jtdD6jFkKnBnO6bsumzUejXXjbmO/WX72XxmM7ODZuPt7I3RaOTzU58jRIhEKEFn0HHj2Bu7RTPatG1szt2Mp8yTi8Iv6te89pXt48OsD/Fx8WHV9FVEKaIobSklqy6Lk7UnOd14ml0lu4BdgKljfbBfMBFjIvBS+yKqdCHAJRhVlR5Nmx5dB4gkQppq1Rh0BgwGk9ABAgFCIej0QowCAwofV5Re7nhplEi8dEy7IAK5t3On9DprzA6b/hiwyZXJvHX8LbQGLXdPupsLQy/s13dhjcFooEHdQHlLOQVNBRSoCshrzKOqrarTdgGuAVwYciHjvcYT5h7G04eeplnbTFJAUrcInr24JPIStpzZwt7GvVw2/jKC/QeWZjpUC59rx1zLvrJ9fHbqMyb5TLJ7TepwoU3bxre53yITyxAKhHZpRzGSadY0ozVozypP32MU3ceH2tpa2traAJgom4iz1JnUllQunzD81hrDCXs6Wm3NqAE0zHEWOzPBawJZtVm0advOOd/bVugNegC7q371hpvUjSeTnuS5Q8/xn+z/4CRyYnH44iGZS18IBAJuGHsDQe5BvJv+Lk8ffJq74+9mTlDP6Qq9LSJssZh3RDqHSCji3in34iZ145fCX1i1fxWPJj5KiMe5qzSZm/3190Vvr+MVCUTojXqb77c/C0jrY+ppe3PfsKSAJKIV0aw7uo5PT37KQwkPkVaTRnZ9Nol+iaRUpRDnHUecT3eVuB/zf6RZ08xdk+7qVUTBjMFo4LPsz9iSvwVPmSePTXuMcHk4ACHuIYS4h1iMqEZ1I/mqfPJUeZbox+6S3bS3t2M0GBFoBMgVcjzkHoR7hhPtG02AWwABrgH4uvj2mUK67WCEqT9UnGefC2Cz8dxXCqPRaOS73O/4IucL3CRuPDb9McZ7je9z+1ZtK5WtlZS3llPRUmHqHdJaTmVrJR36jk7bB7gGMDtwNhHyCKIUUYR7hHd7rl8ZdSWbcjaxs3inwxxfzmJnlk5YyoeZH3Ks+VivBlDX6+5sgi29Yb2fc1k0eTl7cXHExfyQ9wMHyg4wN/j3oYJmvk8jPCIoaSkh0C1wqKc0rKhX1wOcNUult/fEuHHjLPVszXXNTPeZzp6yPRS3mCJDIyXKMVgG69AYSap1owbQCCDBL4H0mnRO1J5gRsCMoZ4OgGUBKBYM3SUkd5Lz1IyneObQM3yY+SF+rn79To+yFf19SMwJmkOAawAvJ7/MW8ffoqipiGVjl3VbrPW0EBhJIWUwpQzcPvF2Qt1D+SDzA1YfWM19U+4j0T/xnPbb3xeavREKhHYxgAa6COy6vdFo5FDxIZx1zji1OzHZdzLT/KZxuOIwmbWZbDq1yRJNMGJk2dhl3fap1qn5ueBnAl0DmRc8r8/xW7WtvJn6Jmk1aYxRjmFF4oo+VcgUMgVTZVOZ6vebVHWjupHUolSyK7JRS9U00URFSwXJDckcrj6MVqdFIpYgloiRiWQoZUqUMiVOeicEagEh3iGE+oTiLHZG1aEitSqVcHk4MpEMmVjWrbbpbH2c1Do176S9w8GKgwS4BvDnCX9GJBCRVp1GY0cjDeoGGjoaaFQ3dvpba+jcDFiAAG9nb8Yox1h6uoV5hPVo7PTExREX83PBz3yf+z2LwhY5LKqxMHQhP+X/xPd535PgloCqVtWt3qfrdWddLyESiQbcQ6gnUYSBcmXUlWwv2s53ud8xJ2jOsBPHsTWqDhU/5f9EkFsQeqMeX2ffIcnGGM5YHGYyxaD3YX1djm8fz27Dbrae2cpVAVeNmCjHYOl6n/d3HdKTzP1wXcOMGkAjgAS/BD7I/IDkyuRhZwANdbqBQqbgsWmPsWr/Kt5IfYMXZ79IgJvjcqEHsmiNUkSxbs46Xkl+hR/yfqC0uZT7ptzXaUHU00Kg6xi17bWs3r+azLpMJnpN5IU5LwwrgQwzfwj7A4FugbyW8hqvJL/CDbE3cFX0VYNenJi99ufyQrMFYqEYg9Fg8/1an/v+vDC6XitFTUVUtVaR5JFEVVUVvr6+3DL+Fo5XH+fd9Hepaa9hXvA8DpQfYJznuB5TMQ+UHaBN18ayscv6jO5WtFSw/th6ylvLmR8yn79M/Mug6r0UMgULYhewIHYB8Jux7+HtwcmSk1S2VdJoaETqKTUZH+oGipuKqWmqMUWNGgQ4lzrTommhuq2aNQfXdFoISoQSZGIZTiInhP/f97uspYwf8n7gVP0pDBjAaHqetWhaKGgqQK1T4ypxRYCAl4691OO8BQjwkHqglCkJcg9C4aTAz8WPQLdAAl0D8XP1O2v0rC9kYhmLwhfx9emvSa1KPWfnQX+RCCVcFXUV/8r4F7+c+YWprlO7Kb51ve6s6yVEIlG/FzhdhV7OZWHkJnXjwpAL2Vqwlez67D4jdgNhuC7cNp/ZjFqv5qroq3gn7R2mB0wf6ikNOywp04N0mHU994kkElwVzEn1Sf7k/SeaaptGRJRjsPQkeNLftU5vTpLhZjCOGkAjAG9nbyLkESRXJaPWqZGJZUM9JXQGHTD0BhCYZFwfmPoALx19iZeTX+aF2S84LFVwoJ5LpUzJMzOf4b2M99hTuofVB1azMnGlxWjraSHQdYzV+1dzqOIQBqOBQxWHWL1/Ne8uete2B2YjxnuNZ+2ctbxy7BU25WyiuLmYu+Pv7leTxq6Y6zbMzUiHCpFAZEkBtSXW576rlHBPC7Gu18qB8gNIxBImKiZarhU/Vz8Whi3k05Of4ipxxVnsjM6gY3FY93RRo9HIL0W/IBPJ+kwjSqtO443UN1Dr1Nw24TYuCr/IZh53S8+Y2iYSIxJ7XXyWV5WTW5qLVCEFFyhuKubfmf9mrOdY4n3i6dB30K5rR61T065rR6PXYMSIwWhAJpKhM+hwEjshQIBQIKRZ00x5azlCgZBp/tOY4jsFZ7EzMrEpkuQsckbuJEcpU6JwUqBwUtg9/XdByAI2n97MtqJtDjOAAGYHzeY/2f8hvT2dJEVStwhQb0II1sp8/Vnk2DpFdVHYIrYWbGVb4bZzNoAGm9bnCKpaq9hRtINoRTQ+zj4YMQ5ZY+jhzEBrRrvSk+DMDZNvYGP6Rk6qT3Jx3MW2nO6wo+v92dNap78p+8M1LW7UABohLAxdyPsZ73Og7AALwxYO9XSGXYrBZN/J3Dz+Zj45+QlvHn+TR6c96hDjbDAvcYlIwvL45YR6hPLfk//lyQNPsjRsKQHagG4Pkp4eMJl1mZYIhMFoIKsuy3YHZAd8XXx5dvazbEzbyMHyg5S3lLNy2soBR63ML7ShLvY10r3fjK0ZqPdNq9eyq2QXIYoQLp9+eadrf1HoIv6Z/k9TR/PKFORSOdMCpnXbx+mG0xQ1FbE4bHGP9TZGo5Ef83/ks+zPcJG4sCppVY81RIOlpqYGvV5vSaPqqfmm+dgD/QIJ9Put5mG6/3R+yv8JD6kHt064tc9xnjn4DKUtpay/YL3J6Cv8hU+zPyXQLZB7Jt8zbKLsXs5eTAuYxpGKI1S2Vp61mNtWyMQy5ofMZ2vBVppdmhFUCvD39ycurvdzPRy8vIFugcR5mxr7NqgbBr3whc592EQi0YAMO3vz5ekv0RlNAiYFKlOfm1EDqDtmh9lgI0A9LdpnBc7i05Ofsq1wG0vClwwLB7AjGGjdX09OkuFw73Tl93H2HID5JV1TU2OX/c8JmoNMJOOXol8snamHEpHA5P20RyrQYLkk4hLmB8/nePVxPj/1+VBPp08EAgGXRV7GY9MfQygQ8nbG23xX9h2l5aWdtuvaeBBgotdEy4NXKBAywWuC5Xfm6zA/P9+u1+NAcRY782DCg1w75loKmwpZuWcle0r2DOhaVuvUCBEOeQTUYDTYzftvPn/w2wu4pqYGf3//TguxrhyqOESzprnHepHkqmTkTnJ0Bh35TfksDF3Yo7LY9qLtAD1Gh2rba/n70b/zn+z/EOAWwItzXrSp8QNYXqhd06h6uge6IhQIiZCbhBDO9kxyFjuj1qlp0bTwavKrfJj1IV4yL56b9dywMX7MLAkzCSBsK9zm0HEXhS0CYGvu1rN+92b8/f0d0rS5LxaHLUZv1P+/4uDgMR9LUFCQ5b7rz3dgbwpVhRwoO0C8TzwTvCeQr8oHBt7o8/dAg7oBIUI8nDwG9XkfH59ODX7B1N5iQegCylvLOVxx2FZTHfZ0fQZb1/0N9T1/LowaQDaiPy/pc8FZ7My8kHkUNRVxuuG0XcYYCGYDyB7F4INFIBDwl7i/EKOI4Ye8H9hXum+op3RWJvtO5qW5LzHeazzHmo/xUdVHVLRUWH7f06LihTkvMDNgJgonBTMDZvLCnBcsvzNfhw0NDXa9HgeDUCDkujHX8di0x3ASOfFO+jusP7aeuva6fn1eb9QPmeqgNTqDznL92xrr54j532VlZZbzqNfrKS4uthhBZoNpS84WBAYB3k3enX6XfiKdrWe2EuYRBkbToqCn9LYOfQdHKo4wRjmmk2Kfub/Pij0rSKtJY37IfF6Y/YJdohG9LaCtf96XoylKEYVar6ayte9rXiwU06pp5bF9j3Gs6hizAmfx97l/t6jXDSfGe40nwDWAA+UHHOpsMkdTTqtP0yHsGDELnAS/BDxlnmwv2j7oNNWemg0Pl0XeppxNnQRMClQFowIIvdDY0YhcJrd5lGaWfBaGDgOfnviUyqrKYeVotBdd7wFrB0FXI3EkMWoA2QhHPCTNXjmzp3YoMS9Eh5MBBKb0skcSH8FT5sk/T/yTvMa8oZ7SWfFy9uLF+S9y86SbqdJU8fi+xy3GW09eKG9nb95d9C77lu7j3UXvdkolM1+HSqVy2Ly0uzLVbyqvzHuFuUFzSa1OZcWeFf2KBumNersZHgPBnvOwfo6Y/w1YvG1mysrKyMjIoKysjKLmInLqc4iQRPBG3htc/svl3L71dnJKcshqzKKqtYqZgTMRi8SmNLiqlG7jZtRkoDFoOjVFNUd9/nnin8jEMh6f/jjL45fbrb7O+lq3NnSsf96Xo8mcBmT2iveEwWggrzGP0pZSmjqa+Oukv3L/lPuHTXuBrggEAqb5T6Oxo5H8xt6Pyx4sDluMUCyk0m3gqoRDhUgo4g+hf6BeXU9qdeqg9tH1OHp6Bg8F+ap8jlcfZ1bgLCLkEbTr2ilvKSdCMRr96Qm1To2L2Pb3dUtdCzM9ZlLWXMZPOT8N+TXvCIbLPWBrRg0gG+GICyTEPYRxnuM4VHEIVYfKbuP0B7P8tVavPcuWjkcpU7IicQUALye/PGRNKweCSCji+tjreWrGUziLnXk77W02pm9ErVMPaD/m6zAyMnJYP7DcpG7cO+VeViauRCqS9isaZDAahkXOtd5gv0iU9XPE/G8PD1MKh4eHB6GhoZ2MIr1eT3JzMhKxhCJtEVktWbTqW0mpSeGDyg9Ia01DKpGiM+jwkHoQ4h7Cd7nf0aZt6zSu2ShK8E3oHvUJns+r815liu8UuxyzGWujp7fFdF+OJrMB1JvTQ9Wh4u9H/052fTZSkZTnZj/HwtCFw66esSuJfiYBBHOPJ0dhjqbsKNrRq3PC+py5uroCWP4eKhaELkAkEPFL4S+D+vxwivhYY06DNDf9LmoqwoBhtP6nF+zlqPL39+cC3wvwdPHkQPMBEDPsrhV7MxycHbZg6FcTowyIxeGL0Rl07C7ZPaTzMIfcmzRNQzqP3ohSRLE8fjkN6gZeSX5lWBpqPTHBewIvXfAS8T7x7C7Zzar9q/r0aI90Ev0TeXXeq52iQbtLdve44BIgGBY1ZyqNyqEpJ62trZa/zUZRUFAQAO36djKaMwh3D6egqcAk6wwYMHC66TTlxnIm+U3iaOVRAt0CuT3udlq0LfyY/6Nl/wajgZSqFILcgpCIJN2jPpOX4yqx/6K2a1+YnhahfTma/Fz9cJe49ygKkladxqN7HyW9Jp1Q91AC3QJNaYEO4FzrQ2OUMXhIPXqM3NkTkVDErMBZ1KnrKGgq6HEb63NmfZ3auya2L5QyJdP9p5NRm9Epnbi/DEdvd4umhQNlB4hWRFsMnqxa03Ueo4wZyqkNW/QGvV0cZj4+PiTGJ7J0wlJaja2UuJYMq2vF1uSU5nDztzcz+7PZ3L39bmrba4etk2CgjBpAI4xp/tNQOCnYUbRjSBeDrhJXJEKJpdnYcGR20GyujLqS3MZc3s94f6in02/kTnIen/44N427icrWSp7c9yRf5nzZrdni+ULXaNDG9I2sPrDa8oI3IxaKhyTl0noxZzQaaVQ3OrQZa08vGx8fH0JDQ8lqzwIRTJJNIsI5wtLrRiQQEeIWQntHO7omHQ2tDSwMXci84Hn4u/jzv8L/0a5rByC/MZ96dT1Go5GHdj3k0KhPb8c5mEWoUCBkit8UipqKqG2vBaBN28Y/0//JuqPrUOvU3DflPib5TEIsEDssmniu3lKhQMhUv6kUNxdT3Vbd63bZ2dmkpKSQnZ092Kl2I8EvAaBX46unlM2uzVKHAnO6+J7SPUMyvq3ZU7oHjUHDkvAllp+lVKXgKnElVhk7hDMbvuiN+m6NkG3JwtCF+Dr78nX21xxJO3Le1gE9f+x5MpoyaNI2cbjiMKv3r+41XXmkMWoAjTAkQgkLQxdS3V5NWnXakM1DIBCgdFIO+/SypWOXMtlnMrtLd3O04uhQT6ffCAVCroi6grVz1hLiEcI3Z75h1b7fRzRoSfgSClWFPHf4OdYeWWuRehUKhENi9Fsv5pq1zeiMunOS2LUV3t7epLWnITaImeE/g78E/4UJbhNwE7kxI2AGiX6JaHVaWjWtaHVaZgbOtFxXrdpWdhTtoEPfwScnP6GkuYR8VT5+rn48Mf0Jh0V9rLGF5906XSytOo1H9jzCzpKdxHnH8er8V5kTNAe9UY9Q6LhXny28pWZDNKM2o9dt2traOv1tC2KVsbhJ3Eiu7J5+11UswPr8DbWHeJzXOORSucOjZvbAYDSwvWg7bhI3ZgbMBKCuvY48VR6TfSbbdZE/kumpXYEtF+sSoYTrY6+nSd3ErupdIz4drDcK2n/LLNAb9d0i7EPt7DgXRg2gEcjC0IUIEbKtyLHSqF1RyBTD3gASCoQsj1+Om8SN9zPeH7Ype70RLg/nxTkvcu2YaylrKftdRINun3g7r81/jTlBczhRc4LH9z3OG6lvoDVo0Rv1Dk9ntF7MNaobgcE31xsoNTU1FBcX9/iCyazNpKy5jMluk9G0a4iLjOOJMU/ww5IfeOcP75DdkE24PJwybRkR8giLWMYFwRegcFLwSdYn3L/zfvaW7kUkEHH/1PtZf8F6JvtOdsix2YNJPpMQCoR8kvUJ646uo03bxl/j/sqTSU9ajl+tUyMVSh02J1sYdkq9kvb2djLLMnvdxsXFpdPftkAkFDHFdwqFTYWWqJqZvhY+jk4j67qwNUcDzxY1Gwlk1WZR0VrBgtAFSEQm+frj1ceB3wz+UbojFnTPGLD1Yn120GzC5eEcbTqKk3Lgzb1HAnE+cZZaKpFA1KntBgzfmrn+MGoAjUC8nL1I9E8krTqNqtaqfn/O1qFKpZOSJk3ToOVGHYVCpuC2Cbeh0qj4KPOjoZ7OgJEIJVw35rpO0aAn9z1piYycj/i5+nHflPv4+9y/M8V3CgfLD7K7ZDe17bUUNRU5dC7Wi7lzba43UKxf1F1fMN+f+h6MkKRM6pY2dqbhDM2aZqK8ozBKjcwOmw2YZK1TqlNo7Gi0LGp9XHyYETjD5FgZBiITXRnIc+t0w2nq2+vJbcxlnOc4Xp3/KgvDOgsdNHY0DosI3kDoqO/AReDCqdpT3X5n/n68vb1JSEhg3LhxNh070d+0yE6t6qyqNpwWPj0tbM3GwUiPAm0r2oYAAQtDf2uAnlyVjEggIt43fghnNrwRCUUYDJ0zBmx9zQoFQv48+c9IZBJ21Z1b76nhygtzXmBGwAwUTgpmBMzo1HYDhmfNXH8Zfm+7UfrF4rDFGDGyo3hHvz9ja++HQqbAiBGVZmgV6frDnKA5JPolcqD8wIhKhbPGOhpU2lLKk/ufZNOpTXToO4Z6anYjXB7O49Mf5+mZTxPgGkBTRxNP7H+C9068R6Gq0OHzMUc8FU4Kh4xnfmGHhoZ2esHUtddxrPIYUc5R+Dh377JtVgwzi0lM9JrIjqIdPLb3MV5PeR2pUIq/qz9eMi+ECIlWRDvkeAaCeWFfVlZ21udWk6aJd9PfZe2RtTiJnfCSebEkfEkniXgzDeoGh9Zw2YKAgACCXYKpN9SjM+g6/c7eKSiTvE01U10NieG08OlpYRvnHYdEKBnRBlBdex3JlcnE+8Rbem+pdWoyazMZ7zXe4WmqIwmhQIjO2PlescU129UhM9lnMuM8x7G7ZPd56ZQ0t93YvHAzf/P/G8aW/jcvH+6MGkAjlAneEwhwDWBX8S40es3ZP4DtvR/mRcRwT4MDU83SnXF3jthUODPW0aBg92C+zf2Wh3c/zJGKI2ftozOSGe81njvi7sDP1Q+5k5wdxTt4bN9jrDmwhv1l+x2WFme+1h0VQejthf1t1rcYjUZL9KcrqVWpKJwUZNdl06ptZX3yet7LeI+qtiouibiEtxe+zS3jb6GitYIWbcuwlNLtT7dxc33EQ7seYlfJLuK843j5gpfxcOpZNa1D30Gbrg2FTGHv6dsUHx8fpkVOAxGUNpd2+p29IzEuEhfGe40nsy5zwLL8jsJcd1RZWWlZmMrEMuK84zhZd5JWbesQz3Bw/Fr8KwYMLA5fbPlZRm0GWoPWIlAxSs84iZzscr12dTgIBAJunXArABvTN5636ekjudanN0YNoBGKUCDkooiLaNY287/C//XrM7b22JkXgediADlSQcQ6Fe7DzA/tPp49CZeHs27OOv484c+0alt5LeU11h5ZOyjZ15GCl8wLV4kr18Zcy4uzX2Re8DzyVfm8dfwt7vn1Hj4/9Tk1bfa9jswpcI6KAPVEs6aZX4p+QSFWMM5jXLf7uamjiZyGHKpaq0itTqVd246XzIs7Jt7Bu4ve5dYJtyJ3knNxxMUYjAYaOxoJ9wgfmoPpg7N1Gz/dcJpV+1fxfsb7SEQSHpjyAE8mPUmsZyxhHmEcrz7eLT3X/KzylHkOak7m51V+fr7l77S0NFJTU0lLSzvn51hfz0MlpjqgEyUnOv18MM91g9GAWqemSdNEvbqeZk0zHfqOXkVGJnpPRGfQUdhUOKDjcSQ9LdAS/BLQG/WkV6cP4cwGh9ag5dfiX/F19u2kxmiO7o4aQH2jcFKg6lDZ3DHYk8MhQh7B1TFXU9RUxHe539l0vOHCcEp5tRWj8iEjmIUhC/kp7ye+y/2OBSELcJO6OXR88yLwXKSwrV9ajkilmBM0h8MVhzlYfpAZATM6db4faYiEIi6KuIiZgTP5b/Z/2VO6hxV7VnBZ1GVcHX01MrFsqKdoU8xe+8aORqKV0UQro7ll/C3sLtnNtqJtfJf7Hd/nfs9Uv6nMC57HJJ9JOIudbTqHRnUjAgRDZgDV1NTw4YkP0aLlKr+rCAwIBEypbiXNJRyuOMz3ud9T1VqFi8QFV6krt0bfymTxZAJcAzp9H+5SdyLkERypOEJVWxVjvcYOyTH1hllZrCuqDhWfnfqM3SW7EQlEXBF1BX+M+WOnY0v0S+SbM9+Q05DDeK/xlp+bn1WDPX/m55U5MmX+G0Cv15/zc8y8/7Kysk4KawCGJgNGg5GiGlMNXFcVtp4wGA1UtlaSr8onvzGffFU+hU2FFgn0nnCXuhPhEUGkIpJIuelPhDwCMDWZHes5vK4TM+YIkPUCbarfVMgwGQ2zgmYN4ewGzrHKYzR2NHLj2BsttXkGo4HUqlRC3EPwdfEd4hkOb5QyJTqjjmZtMx5SD5vtt7fn0tUxV3Os8hjfnvmWaX7TCJeH22zM4UBvxz2SGTWARjASkYTrYq9jQ9oGfsj7gRvH3ejQ8c05yWUtZYPfRw8vLXtiToU7VX+Kf2f8m3Fe42z6cBwK5E5y7pl8DwtDF/LvzH/zXe537Cvdx58m/Ikk/6Rh3+m+v1hSLjt+izi6S925POpyLo28lBM1J9hWtI3UqlRSqlIQC8VM9JpIon8iU32n4uXsdc5zKG0pxcfZB5HQ9h3Ge6LrIvdUySkO1R3CT+bHdTOuI6c+h58yfyKlKoXqdpPalc6gw1PmSaQiklZtK3GiOLRabY+L8wDXAAQIOFRxiHkh8xxyTIPFYDSwrWgbX+Z8aTou7zhum3Abwe7B3bad7j+db858w57SPRYDqKamhtTTqXR0dNBY0UiNe02PL/S+DAvz80osFtPW1oaLiwvt7e0YjUYEAkG/nmP92b9er+/mGIoOikZQJADn39QBgW7ntbS5lD2lezjTcIYCVQFq/W9pQDKRjAh5BEqZEolQgkQoQSwUozPo0Bg0aPQaattrya7P5kTtb5EmZ5EzFS0VbM3fSlJAUo+1VUNNTws0T5knUfIo0mrS0Bl0I0oyelvhNsRCMfND5lt+lladRpOmydLnaJTesTho1Y0OecdLhBKWT17Ok/ue5J30d3hxzotIhBK7jzvK4Bk5T4NRemRO0By25G3h54KfuSjiokGndgwGPxc/XCWu59SbZii8CuZUuLfT3ubDzA95YOoDDh3fXsR6xrJuzjq2F2/ni1Nf8HrK64z1HMuNY28k1nPkN8tzFjsjE8l6lLUVCoRM9p3MZN/J1LbXklyZTHJVMhm1GaTVpAEQKY9kqt9UEv0SCfcIH7Bh2K5rp7ylnOkB021xOP2ia1rPz5U/06xvJsIlgru230WbztTzxcfZh4vCLyLBL4Ffi3/lYPlB6trrmO4/neDA4F6dDB36DhQyBWnVaVS3VQ9Lr7LRaCSlKoXPT31OaUspnjJP7oy7kxkBM3o9h+HycKIV0RwoO8At427BTepGZWUlte216HV6XIwuvUZreopKm40WV1dT0XlHh0l4RKfTIZFI0Gg0CIVCy3nq65nW1/79/f2Ji4vr9H8zkYGRuDq7ohVru6kDVlZVsit3FxkdGeS15QEgMojwFnqT5JvEpKBJRMoj8Xf175fSX2V1JSeKTtDm3EY99eQ35nOm8QzHa45z36/3keCXwJLwJUzwnjAslQOtSfBL4MvTX5LTkNNNwne4UtJUQnZ9NnOD5iJ3klt+vq1oG0KELAhdMISzGxmY10INHQ2EEuqQMSPlkVwdczXfnPmG73K/47ox1zlk3FEGx6gBNMIRCoQsHbuU9cfW883pb7hz0p0OG1sgEBDhEUFuYy4Go2HYvwitOZ9S4awRCUVcFH4RMwNm8kXOF+wq3sWag2tI8Etg2dhlhLiHDPUUB41AICBCHkGhqrDP683b2ZuLIi7iooiLaNO2kVaTRkpVCmnVaXx9+mu+Pv01njJPYpWxRCoiifCIIEIecdYU0qKmIowYHSYYYDAaMHgYyCrJoknXxPGDx0lXpSMVSSlTlxGtiCbRL5EEvwRC3EOora2lsrKS0zWnccKJJnUTIdKQPp0MDeoGwj3CUWlU/Fr8K8vGLnPIsfWX7LpsPjv1GacbTiMVSrk6+mqujL6yX6mNS8KXsCFtA3tK93Bp5KUmQ6G0EoFAgJ+Tn8WYqampoazMFMUOCgqyRGFcXV3JyMiw/N869U0kEnXKh+8atTH/rK8oj7Vx09Uo6umcCQVC5E5y6tX1+IeZ9iFTytjdsJvvsr6jSdOEWCjmwqgLWRS2CE2ZBp1Wh1QqJS4obkDfe01VDV5CL3x1vkSJorg89nI8ZZ7sLtnNBO8JJFclc6zqGAGuASwKW8S84HkOT8HuLxO8J8BpONNwZsQYQDtLdgJ0ivRUt5manyf4JQzLCNxwwxwBcrRI09UxV5NcmXzepsKdT4waQOcBU32nMtZzLDuLd3JZ5GUEuAU4bOxIRSSZdZmUtZSNqMV111Q4e9SLDCVyJzl/nfRXLou8jE05mzhScYTUqlQuCL6A68Zch4/LyMzljZBHkF2fTVVrVb+ucxeJC7MCZzErcBZ6g56chhySK5NJq0njcMVhDlUcsmzr6+JLlDyqV6Mov9EU6bSHAaQ36ClvLadAVWCp1ShqKuqUvlSjrsFJ5MQtMbdww6QbuinRVVZW0tTeRFlzGXKxHKPBiKu6b5ncho4GxnuNp7K1kp3FO7k25lpLs8WhpKipiE2nNpFanYpIIGJR2CKuiblmQOp7MwNm8nHWx2wv2s7FERfj4+NDeUc5nhJPnIRONDQ0WCIter1JLKGyspK4OJOxYE4xMxtHIpEIDw8PWltbuxk2Pj4+naI2XSN31sZQT8ZNX6nA1vt1xpnimmJaIlvYp9/HvrR9GDDg7uTODMUMLhlzCVFBUabPGbpHkXqja1peT6l4UYoojlQe4dox13Jn3J3sKN7BruJdfHLyEzad2sSS8CVcH3s9UpH9m8w2aZqob6+nsaORBnUDjR2NaPQa9EY9QoEQoUCIm8QNhUyBi9gFnV5HbmOu3edlC4xGI8cqj+Hr7MsY5RjLz7cXbceIsZMi3Ci9YwuRpsFgSYXbP5oKN9wZNYDOAwQCATeOvZE1B9ewKWcTDyU85LCxIzxMxbEFqoIRZQCBKRVu2dhlvJfxHj/m/3hehqsD3QJ5OOFh8hrz+Cz7M/aU7uFA+QEWhy3m6pirR1z9k7kYu0BVMGBDXyQUMd5rPOO9xvMn/kS7rp0CVYHJ6Pj/IvGuRpFMJEMpU6JwUpDXmEddex2n6k+h6lBZfi4VSREJRIiFYoQCIQIE6I169EY9BqMBnUFHq7aVevVvCzbzos38s6aOJgz8psDlKnElWhFtMsbkEQgQ8PLhl4n1iOWaiN8MAeuFq7+/P/l5+UjEEpycnBDqhEwOm9zr92EwGtAatLiIXVgUtoj/ZP+HwxWHmRs8d0Df60Dpqwamuq2aL3O+ZH/ZfowYmRU4ixtib7DUGw4EiUjCgtAF/JD3A1m1WUQqImk2NjNW9lsRf1lZGUFBQZSUlGA0GnF1de1UX2NGr9cjlUqJjPzN+O16HOZjsU6Vc3V17bVWxxrz580qcNbfjbUxpe/QU6+pZ9WhVbQaWpnoNZE/jv8jU3yndKtL68nQ6u277y0C1VMqXl5jHpdGXspN427i+jHXc6jiED/l/8SW/C0kVyWzPH65TVNu23XtZNdlk6fKszgIrOsA+0NFWwWbz2ymrr3OIuoQ6xlLsFvwsKuRLG4upqa9hksiLrHMTavXsqtkF/4u/sR5Dyya93vFEgEa4LViCyLkEVwVfdVoKtwwZ9QAOk+I9YwlwS+BwxWHyWvMI0oR1a/PGY1GWrQtpkVZRwON6sZOC7UmTZPFs2YwGtAbTZ5SkUCEUCBEa9BS3lLOx1kfU6AqsCwKlU5KFDLT364S12H3kjFzYciF/Jj/Iz/m/cjisMWd8q3PJ6IUUTw18ylO1Jzgs1OfsbVgKzuLd3JRxEVcEnHJiDluc/QlX5V/zqpOzmJni0FkxtooKm4qpk5dR4O6gZLmEvJUeRiNRr458805jWtGLBSjdFLi4+xDrDIWP1c/IuQRRMoj8XPxs9wzRqORZw89i16vZ55iXqeFtPXCNS4uDpc2F8S1YjRoiPaJJti/u0CAGfO9LBQImR8yny9yvmBb0Ta7G0A91cBUtlayJW8Lu0t2ozPqiPeJZ+nYpYOKttW217J6/2oy6zKJUcSgN+jZVrSNJYIliCViZsbORNQkQq/XYzAYOtXTNDU10dr6W8+Y0NBQy5ytU+J8fHx6Pg6r3kVxcXFkZGRY9mU2igb63ZijMW5ebpTmllKlqcJF7MJVPleR6JnIJP9J3fbT1dCxjnT1pFbXWwTK2oiSdpgiO+Ut5ZbfS0QSLgi+gDlBc/gx/0e+zPmSpw8+zSURl7B07NJBR4Pq2utIqUohpSqFzLpMS/NXkUBEiHsI8b7x+Dr7Wt4xCicFTmInhP/f2UNn1FFcVUxhVSEiVxG7anZxqv4UNW01nWpWfZ19LSIp47zGDQuRBHP/KmuZ60MVh2jWNHNV9FUjKtV8KFE4KRAJRHZvjdAb1qlwE70mMs5r3JDMY5TeGfq7fRSbsSx2GalVqXx+6nNWz1jd7fdt2jYKmwot3u4CVQHV7dXdOoubESDAXeqOVCRFKBAiEogQCUxeRq1Ba/JyG0ye7tMNp2nRtvS4H4lQgr+rfydJ1XB5OE4iJ9sd/CARCUXcEHsD/0j9B9/lfmdpaHa+MslnEhO9J3K44jBf5XzFd7nf8VP+TywIXcBlkZcNyyJ4awLdApGJZOSp8uyy/56MIjAZRrf+fCuTfCZx3ZjrTA6C/3cY6Aw6S8RHb9BjxGi5V4QCISKhCBexC54yTxQyBZ5Opr/dJG79cgyk16STXZ/NvKB5BLkFdVqkdl24NqgbMBgNNGubSVL0Xddm7o8hFAhxl7ozO3A2u0t3U6gqtGveuvWci5qK+D73ew6VH8KAgRhFDMvGLjPVbQyS1ftXc6jikEUyWClRsjd/L+5Gd3RaHYYaAx4+HjQ1NVlSvMwYDKYonEgkIigoCPgtBc68vdlg0mpNDQ+ta4n0ej0ikchyPvz9/S0RIHPKXV8CCT0ZIj4+PhTqC3nrxFvUdtTiIfNg7Yy1tNe395re1tWQMv+/a+2S9RhnE6Nxl7ojEoh69KgLBUKuiLqCBL8ENqZt5KeCn0itTh1QNMh8vrYVbSO9xtS3RywQM85rHAl+CYxRjiHUPbTfKZqNhY2MlY1FKpLiEeVBY0cjD0x9gCD3IPIb88mozSClKoWtBVvZWrAVuVTOwtCFLAxbOKQ1NilVKbiIXTrJjW8v2o5EKGF+8Pwhm9dIQyQUEeweTEFTwZCMLxFKuG/Kfaw+sNrUp2/O2kGnnvdH8n6UgTNqAJ1HhHiEcEHwBewp3cPx6uOWhaLZ2KlorcDIb03B/Fz8iPOOMy3MnBSWv81RHIWTol9yv88fep7TDad5df6rqDpUv6X2qBup7zD9XdpikmbdU7oHACFCgtyDLEZRpCKSaEX0kHi3ZgTMIEoexbaibVwccfGwNwLOFaFAyKzAWcwImMGxymN8n/s9vxT+wvbC7cwOms2V0VcO23RGoUBIuDycAlWBQ4U3ipqKEAgETPSe6FBFPYPRwGenPkMilHDrlFu7SXlbRx4A6tX1dOg6EBqFiFVi8vPze6xZASzGl7n55eLwxewu3c32ou12FVPx8fGhTlTHB7kfkFqdCsAk70lcFXMV4z3Hn3O0OLMu03JMBgy06dtwF7mzp2QPWr0Wb6E3ra2tiEQiS+2PQCBAKDRdS+Z0N6BTKpx5Xq6urlRWVloMyMbGRjIyMizRFalU2inqolQqaWgwGQ09pcH1lkoHJknz9zLeY3fJbtwkbkz0nohapyY6KBqCev8O/P39KSsrQ6/XU1NTg6urKxqNBicnJ3S6nh1eZ0MoEKJwUtCobux1myC3IJ6b/VynaNAfY/7IdWOu6/W8tuva2Va4jW1F26htr0WIkCT/JGYGziTeJx4Xicug5mttTJqNpjxVHnE+cXj6e5Lon8htE26juLmY5MpkdpfsZnPuZr7L/Y4EvwQuj7rc4eqZDeoGchtzmRU4yxKNKlAVcLrhNPND5g9boYnhSoQ8gt0lu2lUN1r6yDmSEI8Q7p1yL68kv8LLyS/z3KznBtWfz9H9En8vjBpA5xEtmhbCPMKobavlwV0Pdkqj8XPxY0bAjAGpXvUXsxBCh76DGGVMr9upOlSday5U+Z2MIneJO1P8ppDol+hQUQKBQMCysct44cgLfHX6K/42+W8OGXeoEQqEJAUkMd1/Opm1mab+QWX72Fe2jwS/BK6KvqpTEe5wIUoexan6U5Q0lxDmEeaQMe0pgNAXB8sPUtRUxOWRl/fax8j65djY0YjOqEOkE+Er8u1z4W2O5pqNhShFFGEeYRwoP8BtE26zuRiC0WgkrSaN73K/41T9KQQISApI4qqoq4hUnNv3ai1THe4UTkZHBgYMCBES7RqN0Cgkry2PiYqJuMvcLREQs5FgNBotkRvzz0pKSnocq6GhodNi3mg0douudK0jCg0N7RTZ6Y9ggoenB6+nvM7x6uNM9pnM3fF380bqG5S2lJ71++iaomemra3NMsZgFlJKmZJ6dX2f21hHg94+/jbfnPmGJk0Tt0+8vZPDQmvQ8mvxr2w+vRmVRoXSScm1Y65lQcgCm/TssjYm3XRuCBB0a9kgEAgI8wgjzCOMq2OuJq06jW1F2ywqd45Wz0ytMjkEEv0SLT/7X+H/AFgStsQhczifiJJHsbtkN/mqfKbKpg7JHKb5T+P6Mdfz5ekveSf9HR6a+tCAnTyO7pdozfkcfRo1gEY4la2Vllzp7LpsDBgQCAToDXoS/BK4JOISmxo7PRGjMBk9WXVZfb4o5E5yS68WM2ajKKc+h5TqFPaW7mVv6V5LE8sEvwQS/BJs8kLsizifOOK849hXuo/LIy8n1MMxfQOGAwKBwHT8PnHkNuTyXe53HKs6RkpVCjGKGBaHL2ZmwMxhoQ4GMMV3iinFpirVYQZQVl0WQoT9rq2zBVq9li9yvjDVe0Rf1et21i/H+mzT4tRJ6kSoeyhKd6UlAtQVoUCIRCjppDR3QfAFfHryU1KqU5gRMMMmx9GmbWNv2V62FW6jrKUMscDU3PHKqCsJdAu0yRjWMtW3B97OB3xAUUcRE7wm8MKcF3h///tkt2YjMogsKm/wW3obYPmOzFGhvjBHf6zx8PCwiCRY1/70FNmxNk7M0RmxWGwxmgrLCvnpzE9k12czP3g+f530V0RCERq9Bqmwf3U11tdFc3MzGo0GiUSCVqvtVz1STyidlOQ35vcr+hrkFsSamWt4NflVthdtp03bxj2T70EsFHO04iifZn9KdVs1Sicld8bdybyQeXZTy3IWOxPoFkhBY+/pUEKBkKl+U5nqN5XylnK+yPmCwxWHOV51nAuCL+DGcTfavVYyuSoZkUBEvE88ABUtFewt2cs4z3Hn7CT4PWJdMzrVb2gMIIA/xvyR4uZiDlccZvOZzVwz5poBfX4o+iWaOZ+jT6MG0AhE1aFiZ/FO9pftt3gDJUIJU/ymkOCXQIwihqcPPk2BqsDuxg+YjAexUExKZQoXhV80oM9aG0U3jL2BmrYaUqpTSKlMIbM2k7SaNP6d+W8i5BFcEHQB80Lm4SoZ3Mv7bCwbu4xV+1exKWcTj0571C5jDHeildGsmLaCkuYSfsz/kQNlB9iQtoFPTn7ChSGm/iJDnSI41msszmJnkquSuTrmaruPp9FrOFFzgrFeY+127fXEN2e+obqtmhvH3tjnPWz9cmw83ohWryXMM4yE+IReP2NG4aSgsaPR8v9ZgbP478n/sr90/zkbQIWqQrYXbWd/2X7UejWuElcujbiUSyMvtblDw7rWRi6W8/qc1y1paBW5Fbg5u5kKog01FFcUo6pVdTIKRSIRPj4+nQyXrgiFQjw8PCxRta60trZ2a5jam9fU2jgxR2jM0RmNQcO3jd9S1F7EJRGXcMv4WyzGRkNHQ6/NrntKpeuaImmuW2pqauqmNtcfFDIFBgw0a5r7ZQw4i515bNpjvHn8TQ6UH0CtVyMRSjhccRgXsQtLY5dySeQlDqkHjZRHsq9sHy2alrO+EwPdAnko4SHyGvP4/NTn7C7dTUp1CndMvIOZgTPtMr8OfQcZtRmM8xxnmd+mnE0YMAy7/lwjhTCPMEQC0Tk1a7cFAoGA5fHLqWit4MvTXxLiHuLQhtrnwlBGn+zNqAE0QjAajZxuOM0vhb9wpOIIOqMOuVTOgpAFJPglEOcT1+klcuuEW9mYvpGPT35s95QuZ7EzE70mklmbSZu2bdA52wA+LqaO9heFm5pYptekk1KVQmp1Kh+f/JjPT33OnKA5LA5fbJFEthVRiihmBMzgcMVhTtWf6lSE+nsjxD2E5fHLuWXcLewp3cO2wm38kPcDW/K2MMV3CovDFxPvEz8kNVsSoYQpvlM4WH7QIbndGbUZaAyaTqpM9ia/MZ/vc78n3COcSyMv7ddn9AY9jR2NaA3afosYKGVKqtuqLf/3lHkywXsCx6uP92uh2BWtXsuRyiNsK9xGTkMOYMrDXxK2hFlBs+y20LVe6PckIZ1Zn0mAawAajYYvs77kQuWFVFZWEhQU1EnhzdXV1VLLY8bFxYW2tjZLDyAwLWi6RoGs09mATpGmnubbNSIkFotpam1ic+1mclpyWOC/gCn6KRQWFNLa2oqvny+N6kai5FGdDC1zhK+rp7arRLr19tb9fQZiAJmNrwZ1Q7+jIRKRhAenPshjex9j85nNuIpduTjiYu6cdKdD1Scj5BHsK9tHcXNxN5GT3ohSRPFk0pMcKj/EB5kf8I/Uf3C44jC3T7zd5nPPqMlAa9BanjN5jXkcrjhMgl+Cw2uRzhckIgnB7sFDbgAByMQyHp32KE/se4INaRvwd/UfEZkmQxl9sjejBtAwp13XzoGyA2wr3EZRcxEAE7wmsDh8MYl+ib3Kds4LnsfhisPsLd1Lkn8Sif6JPW5nKxL8EkirSSO9Jt1mHjIXiQszA2f+H3vnHR5Vmbbx39RMMpNM2iSTXoCQAIFAAOmINAvY165rWftadhV1Vey6ruWz7Cq6lrUg2AsqLh0poYUQCCEhhPQyqZNJm0z//hjPMJlMGiQUN/d1cZFkzjnznnPe95yn3M/9MDVyKmabmR1VO1hbupaN5RvZWL5xUOhZV428it3Vu1mRt4Knpz192sp3nyyo5CouSLyA8xLOI6c+h7Ula8mqySKrNosw3zDOiT2H6VHTT3pWKD08nYyqDPbW7mVu7NxB/S6XLG3YyXGALDYLb+9/G5FI5KIM9QUGswGr3YpELOmzilWQTxCF+sJOlKaZUTPJqc9hZ/VO5sXN69NxypvL2VK5hc3lm2k2NyMTy5gdPZsFcQsYFjjspKyj7hqMHi4/TJ2tjrHKsVS2V7JDv4OpIVOJ1ca69snJyXFR6CSSY8Iv7gIG7o6GkEkRIJzf8URLhTEcOHCAb2u+Jb81nwkBE5jhOwOLxeL6/qLKIuzYCVQEdqL8AZ2yScJ3e0qku18bz/4+feX5C71V3LOGvcFmt7E8bzllLWUoZUoUEgURyoiTLr0vZB372xhTJBIxLWoao0NH80HOB+ys3skR/RGWTFoyoEG4zJpM4Jj89cr8lYgQcfXIoezPieBUCyG4I9Q3lAcmPsCzO57l5T0v8/zM58+4Xny/JwwJyp+mqG2v5cODH3LHujt4L+c9atprWBi/kFdmv8ITU59gSsSUHg0jkUjEram34if14/2c92k1e5eoHigID23BWBxoyCVyZsfM5vkZz/PCjBc4O+ZsSppLeCv7Le5cfyef5X02IB2fI1QRnBN7Dof1h10KVUNw8uPHacaxZNIS/jn3n1wy/BJMNhOfH/6cezbew5MZT7K+dP2gzzMBaZo0xIgHbb4JsDvs7K3ZS5Qqqt+NV48X3xz5hvKWci4dcWm/apwsNqc0vQhRp0apOTk51NV574UhUJoMJoPrb5O0k5CL5Wyr3Nbj9zUYG1h1dBUP/foQD255kFVHV6GQKrgu5TqWzVvGXWl3MTxo+Elxfurq6sjOziY7O7vTuWo0GtqD25HKpEyPnc654eeCFPKkeV364LhDIpEgkUhobm72uo2Q/RGJRMjlchwOhyub4u5s9Hb93bfJ6cghry2PUapRLA5bjFgsRiKREBQUhFwuxyfQmT0LVgSj1WqRy+WuzwTHxf27hW28OWPu2wqCDZ6CCd4gZPDMNnOP2wloNbfy4u4XWV28mpSQFD47/zPSwtJYVbSKnLru6YaDgSAf55roTcShO6h91Pwl/S/cOe5ODGYDT2x/goyqjAEZmyABHq2KJlwZTk5dDjn1OcyKnkVMwOmpyHmmYHjgcABXRvpUIzk4mZtTb6bWWMv/Zf4fFpul952GMCgYygCdZjCYDHx75FvWl67H6rAS5x/HgvgFTI+a3m9VtBDfkJNGhQvxDSFBncC+2n3Y7LY+yWcfL4YFDuPOwK70rDUlazg/4XwuHHbhCdHwLku6jC0VW/g8/3MmhE34n88CeSLUN5Srkq/i8qTLOVB3gK2VW8nUZZLfmM9/Dv6HtLA0ZkbPJD0sfdCEE1RyFSkhKRyoO4DJZho0alVRUxFNpiZmRc8alON7+z6B+taT8IE32B12rHYrIpHIZex1pzAmGMnCdnXGOpfT5CfzY6J2IhlVGdS213bK7rVb2tlVvYutlVs51HAIBw6UMiXzYucxI2oGI4NHnhAt0u6wo+/QY7aZMdlM2Bw2pGIpcrEcuUROsCLY63oUmnwKP7tTwDbqNmI2mhkVOIqY5BhyMnLYWLaR6ZHTeSPrDQ42HGRMyBj+kvIXzHpzJzqZu7Kbe6bIHZ69fzzHZTabXfVJ3iSwy8rK0Fv0fF/5PWqZmovCLkKEyKVMJwgrZOqcGQLhngH4+/u7Pnc/pnCfPWl43jI97k5Pb5kr4d4KTXR7gq5Nx4u7X6S6rZpzYs7h5tSbkYll3DfhPpb8uoR3DrzDK7NfOWlqn8L87k/2yhMikYizY84mUhXJq5mv8kbWG1S2VnL5iMtP6D1R2FSIwWxgTuwcl/S9VCzlD0l/OO5jDsEJQVAiqyaLsyJ67o12sjA3di4VLRWsLl7Nq3tf5YGJDwyaAIgnfs+qbv3FkAN0mqDd0s5PRT/xc9HPdNg6GB44nKtGXsWY0DEn9GA9mVS49PB0vi74msP6w33mWJ8I3OlZe2v28nn+53xX+B3rStdx8fCLOTf+3OMywIMVwcyLm8fq4tUcajh0Qk0Zf8+QiqUu1SSj1cge3R62VGwhqyaLzJpMfKW+nBVxFpPCJ3WpURsITNZOJrchl13VuwbNQRFoKe6ytIOF46W+CbA6rK4MkEBV8iy296z7EDJMpc2lnSTPZ0TNIKMqg+2V25kfN5/sumx263aTVZOFxW5BKpYyWTuZmdEzSdOkHdc6szvsVLdVU9TklMQXJPLdVek8oZQpSQhIcMn5JwYmEu4X7pKvFs4ZnIZ9fXs9efo8En0TMdQb8JX6Mlk6mUPmQ/x181+pbqvG5rCxs3onr/Ea78x/x/Vd3owEQbFNUFMDOvX+AbrU3gjOj2e9jeD8OBwOfqz7EbvYzp9G/wl/kz9SqZT29vZOam1CQ8fYgFh05d0rMwn3ubKystuaKPf93OdIbwaRp3R6d6hoqeC5nc9hMBm4acxNLIxb6HqPhfmFcU3KNXx48EOWH1o+qD2n3CE4jgPBFEgKSuKFGS/wauarfF3wNe2Wdm4YdcNxv6tdNNvwdHZV76LIUMT5Cecfd+PMIRxDmF8Ycf5x7K3de1J7x/WG60ddT7ulnc0Vm3kz603um3Bfv5/5x4Pfs6pbfzHkAJ1iWGwW1pau5bvC72gxtxCpjOSq5KuYrJ08IJkHgQr34K8P8n7O+yQHJ7sKmwc6EjAxfCJfF3zN3pq9J8UBEiAWiZmknUR6eDpbK7byZcGXLM9bzuri1VyRdAWzomf1OyO1IG4Bq4tXs6Z0zZAD1Af4Sn2ZFT2LWdGz0HfoyajKYGvlVjaXb2Zz+WZkYhljNWNJD0tnQvgEVzT2RDAjagaf5X3G2pK1g+YAZdVkESAP6LG/1UBBoL79IekPxyXv7XA4sNqtiEVir9fXW31KgDUAo9FITlUO8+PmH9vWT4vJauK9A+/x5eEvseM0eEeFjGJm1EzOijjruBTxHA4HR5uOsrZ0Lbuqd3VydhQSBQnqBGIDYvGV+iIXy5GKpVjsFsw2M+3WdsqayzjSdISDDQdd+6lkKqZGTmVB4oJORcVarZa12WuxO+ykB6RjsVgoKysjUhpJok8i6+rXuTIZNoeN/TX7qaur66KgBseelUKWSXB+BJqa+zV1NzC0Wq2rpsgzuyJkXvY276XIWMRE9URipDG0mdowmUyAs+eQMKZiQzFSkZRY/1jKleWYzWavctbCffYUOqirq/OarRLOVaDi9fQ+EN5JPWWAqlqreGbHM7RaWrl3wr1ea0Lnx81nV/Uu1petZ0rEFFI13QtG9IT+vMNkEhkqmQq96cQdIHCyHpZOXco/dv+D1cWrcTgc/HH0H4/rvZ1Tl4O/3J/4gHjezn4bhUTR7wzwELpHeng63xZ+yxH9kdNGUEIsEnP7uNsx281kVGXwVvZb3DP+nkF30H7Pqm79xZADdIrgcDjYWrmVLw9/SZ2xjmBFMLePvZ3Z0bMHnD7WHRVuoCMB8QHxBCuCydRlcl3KdSedOiYWiZkdM5tpkdNcTuU7B97hx6IfuSb5mn5lvyJUEYwNHcue6j00djR2Kz07hK4IUgRxQaJT7ti9T9W+mn3OSGeOk5ct9HiK9Y89rrmikquYFjXN1ehuoJuU1rbXUtpSytnRZw/6S+lEqG8CRCIRNofN6QB5ocB5FsIDGBuNqMQq8mvznb24frtXFa0VNJubabW0MjliMjOjZp6Q42qymciozGBt6VqXItPwwOEkByeTqE4kUZ1IuDLc63XuZOSmarA77FS2VroyRrkNuawrXce60nUkBydzVuBZRFgjiAiP4JDlEGq5mlEBo3DYj6m2XZVyFVsyttBudUpPixET6RNJXlkedoed2ppar5kTYZ4KGSD33j8C3BXXhOyPkCHyzA7lleWxrnEdAZIA5gfPdwkeuAsxCM/nw3WHUdlUNDU2udTohP/d4e7QeIoiCNkqoIuz05f3gZD5ETJBXe5Vex3P7nyWFnMLf0n/S7dSv2KRmDvG3cGSX5fw7oF3eXn2y8dFhevvOyzQJ5CmjqZ+f0938JX68sjkR3hx94v8UvILCqmCq5Kv6tcxLHYLpS2lpIamsuroKqrbqrki6YqTLhLxe4bgAGXWZJ42DhA418HdaXdjs9vIqMpAIpJwV9pdg/q++T2ruvUXQw7QKUC9sZ5397/LgfoDqGQqrku5joXxC5FL+tbg7njgjQo30JEAkUjElIgprC5eTX5jPikhKQNy3P5CJpFxQeIFzImZ46IVvpz5MlMjpnLTmJv6/GJZEL+AA/UH2FC2YYiLfZzQKrUuZ6jV3Ep2XbbTGardR2FTIV8c/oIgRRCjgkcxOmQ0o0NHE+4X3meHaGHcQjaXb2ZdyTpuH3f7gI59c/lmgEHnjXtS3/QN+uPKzEpFUqx2K0qZ0kVJ87bG6+rqqKquwh5gp8BeQKu9laMtR1m6fSkikQi1XM2cmDkopUpWFa1iSsQU5sYdn9Jeu6Wd7wq/Y2PZRlotrSgkCubFzmN+3Pw+S3V7GrlikZgY/xhi/GOYFT0Lh8NBYVMha0vXsqNqB/sq9+En8iO2MpYWWwtXjbkKhY+CPSV7qDJV0SRpQlerQylTYrQaAWdxf4ejgzfK3kBULCJcHk5MTQzTkqcxInCEi2LnmQHydEDc5and+wV5U2YbM2YMW45uwSFxcJHmInzEPohEIsRiMVFRUQCu7yysLKSurY7xqvFeFd+8wdPQ6Y0O2ZdjCpkfbwZah7WDl/e8TGNHI/eNv6/XPifuVLjP8j7jT6l/6nF7b+jvOyxIEcQR/ZF+f09PUEgVPDz5YZ7b+RzfFX5HpCqyXxnp8pZyrHZnW4tvj3xLjH8MFw67cEDH+L+OxMBEAn0C2Vuzl2tTrj3Vw+kEqVjKPRPuwZJpYWvlVmwOG3en3X1S6HD/6xi6wicRDoeDjWUb+eTQJ3TYOjg75myuT7l+0BuVgncq3GBEAoTamXWl606ZAyTAT+bHFSOvYEHcAj48+CE7qneQ25DLLam39KnJ44SwCYQoQthQtoFLhl8y9EDqJzzpKSq5ihlRM5gRNQOL3UJ+Qz57a/ZysOEg26u2s71qO/BbL5qQ0YwKcTpFYX5h3TpEiYGJDA8czrbKbVw36roBa1RqtVvZULYBja+GtLC0ATlmd/CkvgmF9t5qOHqCWCTG5rChkChcfxPWuN1hp7y5nNzGXH7N+5WjbUfpcHTg6+uLSWRCJpExPWo65yWcx/DA4YhFYoxWI2tK15BZk8m5Cf1rcAxwoO4A7x54l3pjPVGqKP4w8g/MiprVq0CJ57zpzcgViUSMCBrBiKARnB92Pj/n/8xuw24y9BlYHBY2lG7gi44vXNv7Sn1JDExkdvRs1pSuocXcwh+S/oC/3J82SxuHaw5TqC+k0lBJxo4MZFIZo8NGk+RIYoTPCKRiKUFBQTQ3N2Oz2SgqKurSi8ddKMHPz89Fd3M/l4yjGewu383sqNmk+KdgNptdwgdwjCJns9nIKs1CJpURq4zt1Oi0P/DWe8gbFa4ndJcBsjvsvJ39NqUtpVw18iqmRU3r05jmx81nR9UONpRu4OLhF/dZvr0/Y3ZHoE8gHbYOjFbjgIov+Ep9eWjSQzyy9RH+feDfRCgj+kybLW4qxuFwsLdmLw6Hg7vG3TVo4jH/qxCLxKSHp7OhbAPVrdUnTdGzr5CJZfx14l/5Z9Y/yajKwGq3cu/4e4fmwSBjyKIbRLi/yEUqkSvrE6wI5i/pfxl0w8oTJ0MVLkoVxZiQMeyq3oXBZDgt0viBikD+kv4XVzO71/a+xpSIKb02s5OIJcyPm8/nhz9nb83e00ZB5kxBT/QUmVhGqibVxf03mAzkNeZxqOEQufW5bK3cytbKrQCEKEIYHjTcRZVKVCd2ChosiFvA2/vf5tfyXzk/8fwBGfse3R6aTE1cnXz1oNIRvFHfuqvh6A0yiQyb3YaPxIfGjkaKmooobi6mqKmIwqZCms3NWC1WrBYrEYoI0rRpTI6bjAgRL+55EZVdhanCRIO1AY1G42xwHDqGnLqcfjU4bre081neZ6wvW49CouCWMbcwL25er9exrq6uU5ZFOG93I7e3mo/WhlbGKMdQb6nnUNshrDYru6p2kaJO4U8T/kRSUFInqt2s6Fks3b6UAn0B9yXdR11tHeennk9IaAib9myior2CI8YjHKw+yAHRAXxFvkwLm8Z40XhUYhU2m82V6amsrCQqKspFe3Ndj/Z21/m4OxyrC1fjsDsYLx/fRcTBXYVOIpGQ35IPwFnxZ7kcI2+Kcn11mPtT9+MOk82EyWSiqLCIUGOoi/737ZFv2aXbxdSIqf2icIpFYi4afhEv7n6RDaUbuDL5yj7vezxwKcF1NOGrGlj1ObWPmiWTlvDk9id5NfNVnp/xvKv3UE8oMjiVJm0OG39I+gOJgQNL5R2CE4IDlFmTyWLV4lM9nC4QFBLfyn6L7VXbnepw6Q8MOUGDiCEHaBCh0+kwmUz8mP8jv7b+6sr6/HHUH09IpvlE4E6FS9OkMT1q+oB/x4L4BRxsOMjGso1cMuKSAT/+8cC9md37Oe+zs3qnMxs05pYeG7eeE3sOXxd8zdqStUMOUD/RH3qK2kfNlIgprsycwWRwOkMNueQ15LGneg+7qne5tg/zDSNB7VQBi/GPQSFRsKZkDQvjFw5IDd3a0rVIxVLmxMw54WN1h3ZLO//K/lcX1bfuaji8weFwoDfpKWoqokBfgMlmoshQxJ3r73RtIxVJiQ2IZVb0LBQGBRGSCNS+apdEss1uw1/mz+6q3aRFpXVyuNLD09lXu4/sumymRfYe1XfP+owJGcPt427vc5Ncd5EBcCquZWdnAxAVFYVGo+nRqXY4HBwVHWVFxQrqLHVIRVJmBc/CYDNQ21HL94Xfc2fanUSIjkV/hwcNZ/Gwxfxw9Ae+OPgFMwJnUFlZiUajIUQeQqAkkDH+Y1hoW8i+5n3kWnJZV72OzWxmYehCJvh3lsnXaDSdnDiJREJAQIArWyR8dqTiCHnteST6JRIfHO81kyHc++rqavJb81FL1TgMji7CBsJ2wrGF8ff1mvfHyW7qaMJmteEv8Uev15OTk0ObXxtfF3xNXEAcd4y7o9/1fOM04wjzDWND2QYuTbp0UOWAhaaTzeZmIhj4LECiOpE7x93JG/ve4J397/DoWY/2ej1y6nNoMbeQGprKZSMuG/AxDcGJ1NBUFBIFWyu3sihx0WnZ3kIilvDn8X9GJpGxuXwzz+96nr9O/OtQs9RBwpADNIjwC/bjP/v/Q6mplDD/sFOS9fGESCTi9rG387dtf2PZ/mVEKCMGPOKUHp5OkCKIdaXruGj4RaeN7CQ4De2/pv+VHVU7+ODgB7ye9To7q3dy+9jbvTqlah81Z0Wcxfaq7VS0VBDtH30KRn1mojt6Sl8i1WofNVMjp7qc0w5rB6XNpRxtOuqSTN6j28MundMp0nfoOaw/zO3rbic9PJ0IVQQRyggilZFoldp+RdHKW8o51HCIGVEzBi2DaXfY+ee+f1LZWskNo27wqvrmfv2MViPVbdXoWnVUtVVR3VZNdWs11W3VrkJ+h8OBAwdikZi5sXNJVCc6VdX8Y5FJZM4Mi7mzVDQ4X7ojVSPZathKi72F0dpjqocTwiYATpne3hygn4t+5pNDn/Qr6+MO9yxIVFRUJ4eorKyMlpaWLipmwlzyCfJheeFysuuyUSvUBEgCSJYkc03ENThwsMOwg+3N23ly+5PcmXZnpxqNs4PPZmPBRjY1bCLJN4kIP6dhHBAQgF6vRyaT4Ycf04OmM1s2m1p5LR/lfsSPtT+S15bHReEXESgLdNXtCPsBrj4+Aq1R6Ct00HIQq83KpJBJrjoiz3UhODjl7eW02lo5K+ysTucvyGhD514+Aryts+7ohUqlstdMUF1dHfml+YjFYlQSFSKRiLaONv599N9I5VLum3AfCqnC6749QSwSMz9+Pp/lfcae6j19ps8dD4QgQ28y3ieCaVHT2F+/n83lm9lYvpG5sd3XzxmtRvbV7sNH4sPdaXcPRfsHEXKJnFnRs1hbupYCfcFpJYbgDrFI7LRHpH6sLl7NY1sf48FJDx6XMugQesaQAzRIKDIU8WreqzTYG5iXOI8bRt0wYPUJJ4ogRRBLJi7hyYwneTnzZf4+4+8EKgIH7PhSsZS5sXP5uuBrsmqyBrX30PHAWzaovKWcJROXeOUGL4hfwPaq7awrXcdNY246BSP+feF41AcVUgUjg0d2emkJTlGRoYgj+iN8X/g9h/WHaTY3d4ruiRCh8dUQrgwnSBFEoE8gQT5BBCl++/fbz4IIybrSdYCTWjdYWJm/kqzaLM6OPps5MXOobq2m0dRIU0cTepMefYfzX2NHI7p2ndfeJUGKIOLV8UQqI0lQJ6BVarlt7W0EK4K5bextru3cJZw9e9YIiCYah8PBEeMRztac7fp7iG8IiepE9tXuw2q3eq2DczgcfH3ka74u+JooVRSPTH6kz1kfTwjGvTA+92yK4FS4j1+n05HZkMkvBb/QYe9grHIsNrGNIlERF8dcjMgqQiwSMz1wOqPUo/i26Vveyn6Ldms758Y765oaahu4MPRCPqj8gFUNq3hy2JPAMYEDQfAAwF/lj6PZwV2xd7G2YS0HjQf5T/1/uCX1FtI0aZ32g2OOpns2NDgkmLc3vk2wTzAjlSNRKpVdnBn388tryQMRJMoTO92/nJycLt/jnjH0lhHyXHvCP8FB62lN6nQ6l4T0zMkzaahv4D8H/oPBYeCPI/9IlCqqP7e6E86OOZsvD3/J2tK1g+oA9bWP0YnihlE3cKDuAJ/kfsI4zbhua5s+zv2YDmsHc2LmDFHfTgIWxC1gbela1pWuO20dIHA6QX8c/Uei/aP5MOdDntj+BHen3d2rsMgQ+ochB2gQkFGVwbLsZdixc3fa3Seti3x/MCxwGLePvZ1/Zf+LVzJf4cmpTw5o9Glu7Fy+LfiWtaVrTzsHSICQDfq5+Gc+O/QZj21/jPsn3M9YzdhO240MGkmsfyy/VvzKVclXnbTO5b9XDJT6oLtTdF7CeQwLHMYnhz7h8hGXkxaW1ilLUtVWxRH9kR6bbPpJ/VDJVRyoO4BSpuSHwh9QSBX4Sn1RSBXHfpYonGpdiJ3//5blsDvsOBwO7NixO+x0WJ3F1karEZPNhNFqpMPaQbGhmIP1B/GV+pJRlcHmis3djkklUxHuF87okNGujFaEKgKtUttpHtbV1XG0/CgSkYQOWwcOh8PlBLrXk8jlcq/XfcawGXxZ/SXFtuIunyX5JZGry2VX0S6mD+9Kmf388Od8X/g9iepEHpn8yHFnzYRxCo6AJxVQqVS6xAYE5Nhy+K7uO0L9Qrlj+B1Ym60sK1tGgm8C4Y5wbNhcvXrGaceRNjqNpb8u5e09b9PU1MRVaVe5jneB/AI21Gxgm2Ebl4df3qmJKdBJ+EAhVnB51OWc7XM2H+R+wBt73qC0rpRUUSp2ux2JRIJvqC9Ls5eSU5dDgm8CSyctRaPRsDZ/LZX6Ss4JPgexSExbW5tXp6murg6r1Up+ez6BvoFMSphEXU2dV6dKcFp6Cyh0t/b6sia1Wi3tJe2EKkMRi8S0yFrIMmaRoklhUeIir/v0tS4pQB7AlIgpbK3cSllzWad+TgMJwQGyOqyDcnwBSpmS28fezt93/50Pcj7g4ckPd9mm2FDMj0d/RC6Rc+mISwd1PENwIiYghpTgFHZU7+D6UdefFjXKPWFu7FyiVFG8mvkqr+59lSuSruDSEZeelvS9MxFDDtAAwu6w8+XhL/mu8DsCfQJ5YOIDnbqrn26YGT2TspYyVh1dxfs57x8Xf7s7BCuCmRQxiV3Vu9C16dAqT8+mWyKRiEWJi4hWRfNG1hv8fdffuW7UdZyfcL7rWohEIhbEL+D9nPfZXrmdeXHzTvGoz2wMVh+CBXEL+KX4F/5b8l/OTzzfqwqT0Wp0ZVk6ZVt++79AX4DZZiZYEUx2XXaPDR+PBza7jcrWSuQSOaNCRqHx03TKQrn/HOgT2GdpfJ1Oh8lsAofzOdRmaXMJRXgayt4K3xUSBcnKZA43He6yXrU2LQ67g11lXR2gHwp/4PvC7xkeOJzHznrshGob3R0O90yEuyPk7ih8XfA1P1T8wAjNCB6b8hjBimD+sfsf2Ow2zgk8B8Dl8An7VxdWc23otXxo/JAVBStoN7QzQT2BqKgoFtkXsad6D58f+pxhgcMYHzYewDUmT8lrrVYLOrg18lY+qf6Er458hS5Qxzkh5yCRSPjHgX+wt24vduzkWHJ4ZvczPKJ/hF9Kf0HkEDHefzwSiQSbzUZAgJPjr1Qq0el0tLS0oNfrKe8op9Zcy4XJF6IN06IN867a5ulo1NXVAccal3o2ePWcA31ZkxqNBoevg3CfcABW5K8A4Laxt3VLdexPtndB/AK2Vm5lfdl6bh5zc4/bHi/cgxWDjbSwNKZFTiOjKoNDDYc6NQc3mAy8kvkKZpsZja/mpDRaHoITC+IWkNeYx+byzVw0/KJTPZxekRyczN9n/p2X9rzElwVfUtpSyl3j7jouuukQOmPIARogGK1G/rXvX2TWZDJMPYwHJj7QJwWYU42rk6+mvKWczRWbiQ2I5YLECwbs2AvjFrKrehc/F/3MLam3DNhxBwNpYWk8P+N5Xt7zMp8c+oSyljL+NOZPrqzYjKgZLD+0nI1lG4ccoNMUMomMK0ZewVvZb/Hj0R+9NiT0lfriq/L1SnW0O+w8+OuDSMVSls1bhq/UF4vNgtHmzNwIGR2TzeQyoOwOZ7ZHyASJELmyQz5SH1fGyFfqi9FqZGnGUhRSBU9Pe5phgcMG7Ny1Wi3N5c3IJXJEiNCb9C4HyJv8sadRWllZyQTVBHJbnE1Frx91vWv7ESEj8Cn1oczcWd3s1/JfWZG/glj/WP42+W8nLOziTtPylolwH/fult2szF2JRqLhz0l/JlgRTF5DHlm1WaSHpBPhE9GpSak7xUwlVfHH6D/yfvn7fF/zPQrRMUPiirAreK/yPd7IeoPnpj9HtCba9d1KpZLm5maXsIHwN7PZzE2RN7Fct5ytTVvxlfpyZeqV5O3Pw85v8wQ7Re1FdJg6KDWWEu8bj1quRiKRYDabaWtrIzU11UVFE2S09xj2IJPKWBi30Os186Q3CvfUvX5KoMG5O0je5kBv2Rq7w47BZCBBnUBOXQ459TnMjp7dY7amP9neEYEjCPUNJbc+t9dtjxeCA2SzD2xgoztcOfJKdlXvYmX+Sp6Z9gwikQiL3cJre1+j3lhPjH8MUrF0qNH2ScSkiEmoc9WsL13P4mGLT6sa5e4Q6hvKM9OeYdn+Zeys3omuTceSiUvQ+A01ND0RnP53/gxATVsNj297nMyaTKZHTuepaU+dEc4POF8I946/lyhVFMsPLWd/3f4BO/aokFGMCBzBxrKN1LTVDNhxBwuRqkiem/EcaZo0Npdv5pmdz7i6hvtKfZmkncRRw1EajA2ndJxD6B4zomYQ4x/Dz0U/9/s+/Vr+K5WtlZwXf56LXiaTyAiQBxDmF0ZsQCwjg0cyVjOWtLA00sLSmBA+gYnaiaSHpzM+bDxpYWmM04wjVZNKUlASMf4xaPw0+Eh8eD3rdfQdeu4cd+eAOj/gdB7GjB6DXCrHgaPHc9dqtV6pcAm+CYTIQ9hcvhmz7VgfG2O7kTjfOEpaSuiwOimEte21fHjwQ4IVwTx61qMD1stMo9GQmprq1QBXKp01lEWWIj459AmB4kCu0V5Da0MrdoedFfkrkIgkzFbPBjpnbNxFAqKioghWBHND5A0oxApW1a6izebcNlgWzBXhV6Bv0fP89udpNbe6xtTW1uZyNJqbmzGbzej1eoKCglD7qnlk4iOMCBvB1vatHLUeJSU4BfFvr1iJSEJKcAoGDNjFdmL8YoiKiupyL4Tfg4KCMIvNFFoKSQ1PJSYgxuv1EhwZoNvjyOXOLKLg8Lh/7j4H3CmIQgbJHS3mFmwOG4E+gazIX4FULO21QXRP99MTIpGIYephVLVWuebZQEPI6J6snm5apZa5sXMp0Be4+vx8dPAj8hrzWBi3EJPdRGJg4hCl6SRCJpYxN3YutcZasmuzT/Vw+gyFVMH9E+7nypFXUtpcyqPbHiW/Mf9UD+uMxpADdIIoMZTw2PbHqGyt5NqUa7ln/D19pq2cLvCT+bFk4hJ8Zb68kfUG1a3VA3JckUjENSnXYHVY+eLwF73vcBpAKVPy8OSHuXDYhRToC3h8++Po2pxGw4RwpyJWVm3WqRziEHqAWCTm2pRrMdvNvJ/zPg6Ho0/7WWwWvir4Cn+ZP4uHDWyPCIfDwbsH3qWwqZCLhl00KNLz4DSyZRIZOJxKdt3Bm1EaFRWFj48P5yacS6ullYyqDNdnWq2WRGUidoedXUW7cDgc/PvAv+mwdXDHuDtcvVUGE3V1dej1etpsbXxR8gUqmYq7R96NUqJEqVSyvnQ9BfoCpoZMJVAS2EkpTjgHuVxObGwsGo0GpVJJsCyYi6Mupt3RzhbjFhcNLdEvkXNDzqWypZLXs153ZQu0Wi0mu4lSYykHWw6S3ZJNpiGTzZWb0Qfp0Yl0XJ18NUqZkvdz3mfp9KVMjZxKoE8gUyKm8NKcl5CFy/Dx8WF6yvQe+/j4+/uTqc/EaDIyOXCyi7JWVFRETk6Oy0ERzisqKqrTPRUU3oSaKXeKnfC55xzwdIY8UdpcCjizJ0WGIhbELehzBFoYvzfHyh0J6gTs2F3fNdDorpHrYOKyEZchF8v59oizJnZ92XpSQ1OZHT0bq91KonpI/OBkY27cXMSIWV28+lQPpV8QiURcOuJSHpz4IGabmWd2PMNXBV9hsVt633kIXTBEgTsBHG06yvO7nsdkM/HAxAeYpJ10qod03IhQRXDfhPt4cdeLvLTnJZ6f8fyA9CoaFTKKNE0a26u2c+GwC4lXx5/4YAcZghEdqYzk3wf+zdM7nmbplKWkadKQiqRk6jKZHzf/VA9zCN1gfNh4ZkTNYFvlNrZWbu2TCMma0jU0dDRw/ajrB7xH109FP7G1cisTwiZ4peUNFMQiMT4SH1pppchQ1Ov23mSXEy2J/Fz1M6uLVzMrehZikRiNRsMw1TActQ52luyk3lRPTn0Oc2PnMk4zbtDOx32MAp3rl7pfaLW2cm3MtcgsMsyYqdBX8FntZ2h8NUz1nYrN2lXpzrNeRlCUG6Mcw8Twieyo2kG0LZpRqlGIxWKmh06nTdHGVt1WHtv2GEGSIArqC2i0NrrGIpaIsdvtyKQypIZjr9KWjhYaOxp59NdHuXHsjTww8QES1Yk0NjSy/fB2rJZjRq87FQ2OZWlMFhO7mnbhJ/Yjwhrh2k7I9niquHmr63JXgZNInAa/e1bMs8+UTqcjKCioi9CEAGFOlbaUIkLE+Ql9bzrc11qgBHUC4BQIGAyVLkH8QCw+ebHfQEUgM6Nn8lPRT+Q15BEbEMv9E+539TUTznkIJw+hvqFMi5rGtspt5NbnMjp0dO87nSC8rbe+NiD2fFZP0k7iuenP8c99/+Trgq/Zo9vDXePuOiPsq9MJQxmg48ThxsM8s+MZLDYLD096+Ix2fgSM04zjulHXUdVWxRtZbwxYoejVyVcDTunfMwlzYudwz/h7aOpo4qmMp2joaCAlJIWDDQcxWo2nenhD6AE3jb6JQJ9APsr9iMaOxh63bbe0892R7whRhHRba3G8yK7NZkXeCqJUUdwz/p5B5ZvrG/SYOkwoxAqKDV3V3Dyj8J7GNzgzoPPj5nO04SgrM1a6th0fNx6FREFBWwGfF3xOiCKE61KuG7RzEeBO8TpiOkJuWy5JfknEWGPQarVIZVL+a/gvHbYObh97O3GRcd0q3bkfU0BERARz/eeiECv4ue5n2mxtWHwsrK1bS1ZtFrWttawvXc/a0rVYbBZSlCksDFvIZWGXcU3ENTx79rMsnb6UByc+yL3j7+WKpCsY4zcGP7EfBYYC3sx6k8e3P849G+/h84OfU9xSjNQudcmEu1PR3H/eY9iDwWJgWuA0oiOiu1DaPM/P2710R0+UN51O5/pZqEXyZpQVGYqc2Z+mIiaET+hX/UF3tEtPCFLQRw1H+3zs/kDI5klFJzf2mx6WTk1bDW3WNpZMWoJKrqK0xZnlGnKATg2uSLoCqUjKivwVfWYKnAi8rbfu1mtP+wqICYjh+ZnPc9mIy6hoqeDRbY8OZYP6iSEH6DhwuPEwL+x6AYC/nfW3LrLJZzLOTzifs6PPJrsue8Aclnh1PNMjp5Ndl82hhkMDcsyThWlR0/hL+l9os7Tx9I6nSVAnYLVbOVB34FQP7YxBX+kvAwmVXMWtqbfSZmnjvQPv9fiCW3V0Fa2WVq4YecWASsFXtToDCX4yPx6a9NCAZ5Y8UV9bDw7wcfg4G6Ra2jt97vkS7c4ovXj4xYjtYtbUrKGy2tmYNDwsnNGa0eS352MVWbl9XOfGwb3d4+OZA3V1da7GnyHaEDa2bkTtp+Yi7UXY7c7gTE1ADUfbjzIvdh5ajjX11Ol03X6XuzOh0+mIUEdwvuZ8Gi2NvFn1Js8dfI5f639FZpdxoeZCYhWxKKVKLg2/lL+k/4UrRl/BhJAJzBkxh3GacUSJolDUKUiSJ3FZ0mUsmbSE51OeJ1mdjEqu4oKEC7BjZ3PTZrJbs7GILeQ35neZkwItTRWkYlvzNgKkASyMX+jK8qSmppKYmOjVQRGobcL/4KQ1CvQ4b5Q393364qAUNxUjEjlFPvrbI6uvtUAB8gBCfUMpauo9g3k8EGrbZOKT13DUaDWyIn8FcokcpUxJoE8g4GzeLEY8JIBwihCuDGdu3FwKmwrZrds96N/XXbCjv/u6QyZ2Cv88P+N5olRRfF3wNY9te4wSQ8kgnMHvD0MOUD9xtOmoy/l59KxHO0lb/h4gEon4U+qfSApKYtXRVfx49McBOe4VI69AIpKwIu/kRFsGEpMjJvPAxAcwWoysK1mHxWZhb83eUz2sMwbuhvfJdIYmaicyI2oGWbVZbK3c6nWbpo4mVhevJloVPaD9uura63h+1/N0WDu4f8L9J0UGPjIiEpVUhZ+P0zHxzAK5v0R7Uvzyl/uzOGExTbYmCil0/T02OBazw8zosNFdqG+9RTT7G/EU9hEcoLyOPBraGpgdMBt/qT82m428sjw+y3NS364bdZ3rO/R6fY/f5S5qYDabyanNIduejUlsoqy1jJSQFG6Ku4l74u5hQegCbom6BalIyld1X2H0MXYx5j3PTaPRMHX8VG4e55RyDvML41/n/Isbx96Ir8yXFmsLT+14ike3Pcq+kn1dhAd+KvqJFnMLs4NnYzaavZ6DJwRqW3Nzs6tWqCeKjTsNsK2trVcHpdXcSq2xlnZLOxpfTZ+Cfse71gUhhApdhWv/gXpuCI1cB7Lxd08w28y8vOdlKlorWJS4CLlEzh7dHudYOvSofdR9zgqfikDS7x2XjbgMhUTBF4e/GHRlQPc1JvwM9ClwBPS4fYI6gRdmvsBlIy6jvLl8KBvURww5QP1AiaGEF3a9gN1h5+HJD5/WnYRPBDKJjAcnPki0Kprlecv5b8l/T/iYghrOkaYjZNZkDsAoTy4mhE/gvgn3YbKZaDI1sbN650npJfF7gLvhfTyG8InAnQpXb6zv8vk3R77BZDNxVfJVA0ZPazA28MzOZ2gwNnBn2p2kalIH5Li9QaPREBMag0zmjG571gG5v4C93Qd3A+uKsVcQ4hfCt4XfUqGrAKC6zSmO4s2Z6y2i2d+Ip/s+4eHh/FjwIyKriNG+Tq6+SCzi66qvae1o5faxt+Mr9e2VJuZ+jkVFRbRb2vml4Rc+qf6EmpYapgVMI8I3gtSIVC6dcilxsU46XZRfFFdor6Dd0s5zO5+jsrXSdazs7GwsFksX0QWAGdEzUMlUrC1di0QkIcY/hghVBLem3srC+IWUGEp4r/I9NjduxuqwUllZyY59O/gu/zui/aM5K+SsfkeIgU5OYGVlpVeDqbKystO+3uA+H4oMRZhtZmwOG5O0k/q0Vo53rYcrw7Fjp7Cy8LgpQ92hqaMJMWLU8sFvgGmxWXgl8xVyG3JZGL+Qu8bdhRix6/3XZGrql4DIyX52nijOBIdN7aPmgsQLqGyt5NeKXwf9+/pCQ3aH5+c9be/KBs0cygb1FUMOUB+ha9Px3K7nMNlMLJm05HeX+fGE2kfN0ilLiVBG8J+D/2FD2YYTPuZlIy7DR+LDirwVWGxnXmRicsRk7hl/D1KxlCP6I2TVDKnB9QXuhrdgqCmVypPyclTJVdw29jbaLG28vOdlTDaT67OKlgo2lG0gKSiJieETB+T7mjqaeHbns9S213Jr6q0DmlXqC4IUQZjsznPsSQjB0yER+uQIL1eFVMEM/xm0WFpYfnA5LeYWDjUcwk/mR5OpqcvxvGUQBAchKyuLysrKPhf8eh6zQdJAqaGUtIA05GInpWttw1qK24sZrxwPumONPt2Vz7x9l2BAZFVl8a+Sf7HbsJt4v3huDruZSzWXEiwJZmPZRip1x/rmREVFMTpwNHel3kVjeyMPr3uYQ2WHXBkqh8OBRCLp8n0+Eh9mR8+msrWSQw2HXNFYrUrLzWNu5vmZzxMfFE9GawYfVn1ItamaH6t/pLWjlVkBs4iMiAQgOzub7OzsHjMhwrUSaG+e8teCIySoyAkUQm/j9rxWOp2OIoPTYfSR+JAent6n+3c8Ti8co6cFhQYdN2WoO+hNegJ8ApCIB1cFzmq38lrWa+yv2885Medw4+gb8ffxZ2TwSA7UHcBkNaE36V10uL5goK7BycKZ4rAtSlyEv8yfrwq+6iT/PxjoiYbsvraFn5VKpVd5+57mQKI6sVM26G9b/8a7+98dat/hBUMOUB/Qbmnn5T0v02pu5f4J9/+uan56QqAikKVTlhLmF8Z7B95jS8WWEz7eJcMvoaqtiq+PfD1Aozy5mBY1jRtH3+h8we19bSjF3E940o9OxssxPTydy5Mup6S5hGXZy3A4HNgddpbtd/58/ajrB6QPR7O5mWd3Pkt1WzU3jbmJuXFzB2D0/UOgTyB2hx2tn5bc+lxqamt6NJjdaVwChNqQRcmLiFPGkdmaycq8lVgdVkYGjaTYUNwnGqu7gyA06ewPBCPg+7zvkUllTAuZRmxsLLkduexp2UO8Mp5zNed6jZB6Zj6EYyn8FKzVr2V5zXLabe2cF3IeV4VcRbAsGLFIzCT1JFotrawrWOc6jnCt5ifP5xLNJTRbmnlx74tIAiVIJBKv2R8BglrkmtI1rqCP0CZBMFSuHHUlDfYGXit+jR1NO0hSJjFcPtyV+XBvctqbUeleKySMSVCAc88MicVijCIjb1a9yYzPZ3DHujuoN9Z3MsLcja2D9U7hl0CfQJKDk/t0/9znWH+yAcL1UalVXShD/XGgvaGpo6lfTsfxwGq38ua+N9lbs5eZUTO5deytrozZRO1EzHYzu3W7sdqt/coADdQ1OFk4Uxw2P5kfl4y4hMaOxkGXxfa8Jt1l5bsTJenrHHDPBo0MHsnG8o3ct+k+Psv7jFZz66Ce45mEIQeoF9gddv6V/S8qWiu4JuUaJmoHJlJ8piDEN4QnpjxBsCKYZdnLOvUHOR4sHraYBHUCqwpXcbRpcJR+BhtXJV+FVqmluLmYD3M+PONqmgYSx0tzONkvx8tGXMZZ2rPYUb2D7wu/56einyhsKuSCxAtICko64eO3mlt5budzVLRWcF3KdZwbf+4AjLr/EAqqRwaPxGA2sLdkb7cGs6exK0CoJwkPC+fRsx9FIVew8vBK/GX+TNZOptXSSm17rdfjCFmf7OxslEolEokEkUjUo5PQHXQ6HW0dbeyq3kWULIrUuFQaJY28l/Me2gAtz81/jrjouE4RVKFmCOhUW6PT6WjvaOeDgg/I7shmXMQ47o67m8nqyZ3oXLOiZiEXy9nVtMvrmBYkLeBS7aUYLAZezXmV8OHhpKWldWuQRKgiSA1NZU/1HtoszusqKJDV1dWRn5vPnOA5XK25mjZrG622VsLUYcjkMlfmQ7iGNpvNdU1tNluva869hsozMxQVFcXyhuVk1WdhMBnYWb2Tx7c93kWuOjU1FWWgktz6XEQiEeM045CKpf1e9/3JBggZoIEOLjkcDhpNjYMqOmCxWfi/vf/HrupdTI2Yyp3j7uw0v4TaOaEB58nooXWqcCY5bAviFqD10/J1wddUtVYN2vf0dE1ORCShOySqE3ly6pM8MvkRIpWRrDq6ins33cv3hd93YkP8r2LIAeoFXx7+0hXJWZw4sA0SzxRo/DQ8MfUJ1D5q/pn1T7ZXbj/uY0nFUicXWiTm7ey3z0gqnFgkZlHiIqQiKWtK1rCmdM2pHtIpw/HSHE72y1EsEnNX2l3EBcTx6aFP+TDnQyKVkVwx8ooTPraQ+SltLuXKkVcOeCPV/kCIbsf5xwFQJa7q9kXqaezGxsZ2Ma4jVZFMj5xOq7kVf7k/ScFOZ1GQKfakzrlnLNra2khLS2PChAk9OgndQavV0uhoxO6wE+8Tz+Hyw7ya+SoSkYQlE5dgbjZ3KvR3/26h8SdAWVkZMl8ZX9R+QX57PsMlw7nQ90ICpYGubQSnydpuZYR0BNWWauqsx7JHAjQaDelB6Vwadin1bfX87de/sT5zfY+OwOiQ0dixu2rQHDi6XP99zfsIlAWSpExib9Nettq2YnfY0el0REVFIZPJXNcUcPX36e36Cffem4rcwYaD2BzOwm+bw0ZuQ65Xwyu7Lpt2azt+Uj+XTHV/131/DDphTAPdrLTN0obVbh20DJDZZuaVzFdc9sI94+/pQrWLUkWhkCgobHKKiwT5/H4doDMJMomMO9PuxGq3smz/spNe3ysEaQQFS8BVM+tOifNshNwXiEQixoeN58VZL/LntD+jlCpZmb+S+zbdx/rS9Vjt1sE6rdMeQw5QD8iozOC7wu8YHjic28fePiA0mTMVWqWWJ6c+SaAikH/u+yebyzcf97FiA2K5LOkyKlorzjgqnPAgGqEYQbgyHLlEzscHPyanLudUD+2U4EyhOQAopAr+OuGv6E16qtuquXj4xS66zfHCYDLwzI5nKGku4YqkK7h0xKUDNNrjgxBRVsqVqOVq8tvy+xRxBKdxLzhA7oZtqG8oCqmCkuYSV9RQUJhz3849Q3E8GR93CAZBm6INsURMmDyMFVUr0Bl0XBt/LfHq+C7qgkKDUgC9Xk9QkPNaWB1W3i94n0pHJekB6VysuRjszmaYcrmc2NhYV4YEYLhiODarjXJjucvRcM9w2Ww2xgWO4+7UuzG0G3iv5D32Fu/tNHZ3I0VwGgQHqK6xM7+/XFJOXkceowNGszR1KXGSODaVbOLd7HcxmUwuJ68/66wnlT/h83ifeFd2QiKSMDpktNfAxN6avZhsJvxkfgxTDwP6v+77E/DwpAqeCNzvhaAANxhZl3ZLO//Y/Q+y67I5J+Yc7kq7y2udkVgkJkGdQFlLGcCg0/GG0HckBydzXsJ5FOgL+Lno55P63d4ULL1R4npTuOwJYpGYmdEz+b85/8dNo2/CZrfxXs57PPjrg+yo2vE/yWQZcoC6QXFTMcv2LyPIJ4gHJj4woP1BzlREqCJ4aupTaHw1LNu/jPWl64/7WBcOu/CMpMIJD6KAjgCn0RA6Gh+pD69nvY6u7fQu9hwMnEk0B4DdNbvxl/uj9lHzxeEvOlG5+osGYwNPZTxFeUs516Vcx2VJlw3gSI8PQkTZYDIwPnw85S3l3Z6j571zp5C5G7bFzcWE+4WjlqtZmb8SESKXAyQYwoLjJGR9jifj4w5hnR3SHcJms7HbsJvytnJmBc4i3BTepUDYne4loK2tjeiYaFbVr6LQWMgo+Sgui7oMsUjsCmYJDoK7iECkj1N8oNJU2UlIwD3DJZFIWJC8gHvH3osFCx/pPmJH1Y5OYxeMlER1IlaLlfzKfKwWK7UNtS5+vzJGyYqSFWj8NTw651E62jq4LPQyYmWx7G/bzybDpk4ZHI1G06m/j3DfPOud3LNy3uhqOp2OmyNvJtU/lUCfQKZETOG5Gc91uQ82u43s2mz8pH5IxVJX087BXPdmu7MQXS4+cQfI/V7oOwbHAdK16Xh8++McbDjIwviFnWp+vCFBnUC7pR2L3fK7psCdibgq+Sq0flq+OPzFoFLhPOFNwdIbJc7Pz9niwL3fV38hE8s4N+Fc3jznTf6Q9Af0HXpez3qdB359gF+Kf+nSP+73jCEHqBu8ue9NHDh4cOKDQ43K3BCuDOfJaU+i9dPyXs57rC46vqLB05EK1xdeu/DgCfYPJlIVSW17LfdNuM+lMva/9PA401DRUsGXh79kmHoYj0x+hMaORp7d+exxqePUtdfx1I6nqGqr4qYxN51S2ps7hP4m+g69S9lu05FN3c5r9znvbty7G7bFhmISAxO5P/1+jBYj+g49hfpCHA5HF/Wx41H382bAC+NocDRgtBspNhYz1n8s54SeA9ClQFgwEKKiojpR+TZVbaLQWshI35GcH3w+RqMRwCXM4N5/R7gG4QHhqKQq6mx1nc5Nq9V2aTg6Z+QcXpz7ImpfNa9nvc4X+V8QFh7WKTvSYejAz+FHg6kBi9WCTCVDLpfjG+zL8xnP097ezi2Jt7junVQs5erIqxkVNoqdzTvZeGRjlzG6Z3Y8M2FlZWWua+suPe+eydJqtWj8NLw24zW+nfstd2vvxtHq6HIvDusP02ppxVfqS7AiGJVc1ef7erxoMbcA4Cv1PeFjuRuRgnrhQGZdcupyeGzbY1S3VnP9qOu5afRNvUqEx/rHYnVYsdiGHCA4veSyfSQ+p4QK542iKjzXhEBKamoqVquTriZQYU8EvlJfLk+6nDfPeZMLh12IwWTgo9yPuGP9Hfz7wL//J+SzhxygbqA36blt7G0MDxp+qody2iHUN5Qnpz1JlCqKjw99zHdHvjuu9GlsQCyXJ11+2lDhBEOhrKyMoiLvEsLCg6etrY0EdQK17bWMCBzBNSnXUNFawfs575/MIQ+hj7DZbSzbvwyb3cYd4+7gnNhzuHXsrdS21/Lszmdd0eG+oLq1mqd2PEVdex23pd52ygQPvCHIJwiZWEZ1WzWpoanIxDJ2VOzoljbhbjx7ozUZTAYaOxpJUCcQJYpihnIGRrORQn0hm/du7mK0NDc3H1fDU3cDvbKyEpvNhkgsIrc5F6PNyGjVaC4OuxiZVNbJIRHg2WRQIpFQa6zlq8KvCPcL55KwS47RvX4TFRDgLpRgNptpb28n0ieSyrZKLDZLp2O7r38BSUFJvDDjBYaph/Ft4bd8UvoJgZpAl0Oi0+kIk4fRZmtDLBZzqOYQIWEhfFzyMXVtdSwKXYSiTQHgOreEmAT+mv5XpHYp31V9R1FlUZf75U0q1/26x8bGdnIOgS4CB+7qU5WVla7MkXBNMnXOnjUSsYRgRfBJMVaLDcWo5WrUPifeq8f9PIWIfphf2Akf1+Fw8EvxL536Ai5KXNQnmnyQIgib3YbNYTsp/YhOd5xuctmnigrXXaa2O9nsgYLaR821KdeybN4y7hx3J9GqaDaUbeDhrQ+zdPtStlRsOS0C1IOBIQeoG8yOnn3Se3icSQhWBPPk1CeJ84/j88Of887+d45rkbirwp3qOhr3h4rQJd3bNsIDKFHt5PYXG4pZnLiY9PB0tldtd3X6HsLpgy8LvnSpvgkNjOfGzuWmMTdR3VbN0zue7lMmKKcuh8e3P06jsZG70u46JVLXPUEilhAXEEeRoQgfiQ9jNWOpsFRgFBm9vjS9Fcq7Z3+EXkIakYaysjLO8j+LJN8kjFYj31Z9S3W1szmq8KIGjrvhKeA6hkQm4WfDzzSbmolQRvCHyD8gk8o6jRPwWhhcV1eHxWphVf0qkMAd4+7oRKdy791jsBp4teRVLlxzIa+Vv4ZRZEQkEhHpE4ndYXfVagjwzAAJsLfauUR5CcMlw9lZsZOndz1NTVuNy7H0kfggEokIFAdS0V7Bv/b+i0O1h7gg/gImBE5wCU+434MgRRDXJV9Hu6OdTa2butwvb1K5wueC8wNdewR53htBac69hgqgsrqSjOoMQn1DMVvNBPoE9mqsnqiDZLFbKGspIzEwccBrbosMRcjEMqJUUSd0HIvNwrsH3uWj3I8IV4bz/IznSQtL6/P+QYogbA4bMrFs0PsRnQnobk2dSrhT4QS672DAMwPvuba81WkKa/94BBF6glwi5+yYs3lh5gu8MOMFzo4+m2JDMW9lv8WdG+7ks7zPTogyfjpiyAHqBleOvPJUD+G0h9pHzVPTnmJ82Hg2V2zmmZ3P0NTR1K9jSMVS7h1/Lwqp4pTX0Wg0GlfhtPC/t20EY8PlADUXIxKJ+FPqn1DKlLx34D0XjWMIpx4ZlRl8X/g9w9TDuqzrc+PP5ZYxt1DdVs1TGU91+4D3FvE9XQMkCeoEGtsa2b5vO6OkozBbzBy2H/Zar9FbLYdAg/DrcHLPRSIRi7WL8ZH4cLDtILtMu3A4HC7jJSAgoN+1IcIYBOU2X5Uv603ryW7KRilTMjN2Junj00lLSwPoYjC4FwYLFLCMxgzKOspYNGIRo0JGIRZ3fdUJUtC5rbm02lrJqs9iecNyYmJiiFHGIJPJKDYUdzJShMxPc3Nzl47uYoeYSzSXcE7QOTQ5mniv8j0MCgMajQa5RO6kDMo1FBmL2N+yn2GKYdw68VavwhMCFo1axIz4Gexr2sce3Z5OBpBnk0TP++npjHg6ju6Gk7vzI9QhlFhLqNJXMdZ/LA6RgyBFUK8R6BON5pe3lGO1W13PVui/U+Vte4fDQZGhiLiAOKRi6XGNDX4TPdn5DJvKNzFOM47nZzxPpCqyX8cIUgRhtVtdct//6/CWVT3V8JH48Ofxf8aBg5f3vIzBZBiU73FnnXhbzz3tcyKCCL1hWOAw7ky7k2XzlnFdynX4Sf2cEtob7+WpjKf48eiPJ7VGarBw/E+C3zn8ZH6neghnBPxkfjw06SFW5q9k1dFV/G3b31gycYlL+agviFRFct+E+/jH7n/w0p6XeG76c6fs+icm9n3c8ep4RIgoanJGyYMVwdw4+kbeyn6L/xz8D/dOuHewhtln9KYGdaox2OMrairqVcxkQfwCZGIZ7x54lycynmDJxCUMCxzm+txis/D+wffZXL6ZSGUkD016iAhVxICPdSBQV1eHpEmCxWKhpKWEkcqRqCVq1peu54YJN/Tb6Go2NwOgDdDiaHMQFBREckwyy+uXY3fYWV+znoDDAaSYU5zb/+YYKJVK2tra+nVf29raaLe18+nhT6l2VDMtchoZVRmIOJYJcDewlUolZrMZPz8/rFaryzFoMDewoXEDkf6RXJ18NeB0zPR6fadtNRoNBc0F2HHy/AUpaI1GQ7osnW8av6HV0tqFJijUSgmGCzgjtYIs9RWjr2CKfQpvZr3JW4fforK9EqvNyd1vsjfRbmtnpHIkt4y4hdyDuS7n0ZvhIxKJuDX1Vg43Hua9A++RHJzcKdsmODQC3NeTp8y5t2soUOAESCQS/P39aWtrY1PVJkQOEVq7c1wB8gAXxbA7CN97vBSd4iZntF0QW/A23u5Qb6zn8W2Ps79mPwm+Cdxlucu1fUNHAy3mFqZGTPW6r9Vupd5Yj75D7/xn0tNkakLfoafd2o7NbqOxo5H9dfsx2UwkqhNRyVR8X/g9gT6BBPkEEagIJFgRTIhvSI/rTCVVYXPYeq0V+l/Bic6ZwcKIoBHclnobb+9/m1czX2Xp1KUD7rRqtVrXM0TI5LrD29wXrpdUKqW9vX1QM2f+cn8WD1vMBYkXcKDuABvLNpJdl01eYx7L85YToYxgYvhE0sPTSQpKOuMymkMO0BBOGGKRmGtTriXGP4Z/H/g3T2Q8wV3j7mJa1LQ+HyMtLI1rUq5hed5y/rnvnyyZtOS0f0H4Sn2JVEW6eqIAzIyayc7qnWyv2s7UyKlM0k46hSPsu/FwquAeAQMGdIxNHU28nPkyDhw8MPEBQnxDut12TuwcfCQ+vL3/bZ7MeJI7xt3BjKgZGEwGXsl8hQJ9AWmaNO6dcC9K2elD1fCETqcjTOKscai11TJKPIqJARPZ0LiBPdV7+rUm4VgzSpPRhBy5U7lMpiTIJ4ggRRASkYRvC79lTtgcpimmYbfbMZvNLgO9srKyz/e0RdzCexXv0exo5pyEc8htyKVAX0CzuZlrUq4h1De0U/ZDoKlardZOhsPXWV8jloq5d5Izs1xXV9dpW/damTEhY9hZvRObw+aSgoZjCmQWu6WTgSY4AO5iAzqdrkvWS4OGB0Y9wGv7XuOzw59hs9tosjZhajPhI/ZhjGoMxlajq2+Ru3qdJ4IUQdww6gbe3v82/y35L2drz+7WYPTmrHmjvAl/FwQnRCIRYrGYqKgodDodla2VFLcXkxqQSpAqCGO5kaaGJnJycro4tZ5BjBNZwwLlMlGd2Kk3ijDunvD4tsdd9zK3NZcPdR8ygxkALqXRRHUiVruVipYKigxFzn9NRZS1lHXbeFWMmCZTE40djYhEIsL9wjHbzGyv8t4PTyqWEucfR2JgIgnqBBLViUT7Rx8znn/z5we6z9GZhIGcM4OJ2TGzKWkuYXXxaj7M+ZDbxt42oNRMdyETYX67Xxv355372tNoNOTkOEsG9Hq9iz7ruf9AXVexSExaWBppYWmYbWZyG3LJ1GWyt2YvPxb9yI9FP+Iv82d8+HjSw9MZpxk3ICImg40hB+gk4nSPxp8oZkXPIlIZySt7X+GNfW9Q2uJsDNlXR2ZR4iLKWsrYUrGFLw5/4Yrens5IUCewrXIbreZWVHKVK2Kb35jvitj6y/1P2fhOZXStL/PdPQI2kE6axWbh1b2v0tjRyN1pdzMiaESv+0yLmka4MpyXM1/mn/v+yb7afeTW56I36VmcuJhrUq457Z1yrVaL2WpGIpLQJG4iJiaGyaLJZLRlsOrwKvwb/fv1/BGMwihtFIZ6g+tFHCQJoqq5inui7uEr81dsqt2ELdrGJRGX0FDbgMVi6VUYxX1+7G/cz7JDy7A6rFyovZA9DXvYVb0Lm8NGVWsVj297nHfmv9Pl5S+cswCFWsFR21FGh41mVMgooHOvIs/MyHMznuPxbY+T25DL6JDRLiloIVNotpm9GmjC74Jgg7sBIsCut/OnyD+xun41v9T/gtVhJc43DplDRo25BrvdmXmy2+0uClx392Vm9Ey+LPiSDaUbuGT4JV228+YseBu3+zUHXGtPLpd3ciJ/3P8jMpmM6yZdR8nREhx2B+3t7Zh9es8onQgEAYRgRTAHCw9iNpt7dRAFuDd2tWPnSPMR12c59Tk0m5pZW7qWDw5+0MnZCZAHMDpkNDH+MQQrgl2ZnECfQPQdet7PeR87dsZqxrqaKQOYbCb0HXqXc9TU0YTepKesuYxiQ3GnwJhMLCMlOIV0bTrjQsc5//g/1lbQfe4J68Y9QHK62kfXpVxHRUsFG8s3EqeOGzDRG/fzTU1NdVE3haCIe2AlJyfHaybI27tzsIOecomc8WHjGR82nlsct1BsKGZvzV4yazLZUrGFLRVbkIqljAgcQaI6kcTARBLViWiV2tPu/TnkAJ1EnO7R+IHA8KDhvDDjBV7NfJXvC7+nvKWcP6f9uU+UNpFIxG2pt1HVWsX3hd8T6x/L9KjpJ2HUx49EdSLbKrdRbCgmVeM0IIIUQacNFe5URtf6Mt+9RcBOFA6Hg/cPvk+BvoDFiYv7VaszLHAYf5/xdx7e8jCfHvoUpUzJo2c9yvy4+QMytsGGQGkKl4dzpPEIoaGhaDQatrZvZWvFVioUFdhstj7PCYF+FhoaSmxErOtF7Gfzw2A00GHp4MrQK9lg2sCWii1Ut1bzwMQHsLZYe72nOp0Ok8nE57mfs75uPQqRgmsirmHWyFn8Z8N/XMasAwe5Dbmd9vXMygjYXL4Zo8lIkiPJVQMiGNBC3xwh42Gz2WiubuZu7d1o0zofR3DevL2w3Q0XiUTSKYMpnJdw3ka7kWqTUyjCR+xDh7gDi8TCUeNRHCIHuPmIPVFZxCIx82Ln8fnhz8msyWSYdFin8xcMSpvN5qqV6u6ae6sbcL9PqiAVBdYCEoITSA5OxthsRFQqws/Pz1Wv5O7wDVSQxWAyUNpcyljNWEQiURe6YW/vTc9sXqI6ka8KviJTl8nO6p0YrUZKm0sZFTLKaZz9lqEJUYR0iepbbBa+OfINPxT+gFgk5oqkK7ho+EWd6od8JD5olVq0yq7n7XA4aOxodGWYjjQd4VDjIQ7UH8DusGO2mdF36Ck2FHei+/2e0Vt92OlqH0nEEu6bcB+Pb3+cjw9+TJQyyvWu7y+8UVTdnRiz2YxEIulSC+TuNAprr7t358kMeopFYoYFDmNY4DCuGHkFde117K3dy17dXgr0BeQ15rm2VUgUrozo6eIUDTlAJxGnK9d1oBGsCOapqU/x7oF32Vq5laXbl7Jk0hKvLwpPyCQyHpj4AI9ue5Rl+5ehVWo71WOcbnAXQnB/KLpT4WZFz+qXStDvBX2d7wPtpP1S/AubyzeTpnHSKvsDu8POmpI1NJmaCFWEIpfI+aX4F0aFjDph9aiTBa1WS4wuhrrWOho6Ggj1DWW0dDRb2cru5t0s8lnkNWPhDXLJMSqYe3+e4ZrhHGg7QIujhZTIFG503IikRcK2mm3c88s93DfuPialHqN/1tXVuWpkoqKi0Gg0qEPVvLv/XfKN+cSp47g85HJSYlPQaDSdjFmAcGk42dnZLifGm/Njd9hZW7oWuUPOCJ8RLkPLZrMhl8tdkVSh4N9ms7mocZ5Gl9CM05Pz70l984zCCk5IZWUlLcoW3jv6Hu2OdtQyNQu1CzFbzGxp2kJpRymb2jexwH+BKwPUWxH4ObHn8HXB16wtWcslqktc0tU6nc6VTXIfp5ARcq/F8lyTwjbCtdJoNGyv3I7RamRK0BT2799PvakemVSGOliNxCLpQlkdqPW7uXwzVoeVmVEzOx3bM2vVHZ6b8RyPbXuM/XX78ZP60Wpp5euCr1HKlPhKfRkZNJLX57zeazDuaNNR3s5+m4rWCuID4jtlffoKkUhEiG8IIb4hLhq00WrkQN0Btldup6CxgMaORh7Z+gjD1MNYEL+AaZHTXOvt9wj3udfS0oJer3cJn3h+frpBJVexZNISHtv2GK9nvc5z0587rjpQT4qq57OkuwyYu1y9+7PK3QkSfj+VQU+Nn4Zz48/l3PhzsTvs6Np0FBmKONp0lGJDMcWGYq9OUahvKEGKIBe12v3nwVwTQw7QScTpzHUdaMgkMu5Ou5u4gDhW5K3gsW2Pcf+E+/sUOQlWBLNk4hKezHiSlzNf5plpzwxI74bBgKcQggCRSMQtY24hpy6Hz/I+Y6xm7GmX/h1sDOR87ys9IlOXyaeHPiVSGcm9E+7t1zU3Wo38c98/2Vuzl+GBw/lr+l/ZqdvJZ4c+4+EtD3PlyCu5IPGC0/4+ajQapo6cyoEDByhqKiLUN5RJCZP4seZHspuzmRk0s881V4IDYLKZqNHVuJyJJE0S0kopykily7GYEziHEEkIq2pX8fesv3O35G7OiT0HkUjkcg7A+bLWoeOdQ+9Qb61nWsw07kq7qxNnXKCmZddlY7fZmR00u5NSmrdI8YG6A9S21zIjbAZSsRSlUom/v38no0qIpNrtdsRiMQEBAS4HAY7NM6PS2TDV8+XrSafzjMJWVlZid9jZ0biD7RXbkcqlzPCdwe6W3UTKIhkdMJoEvwTeLnubVeWr6Ijv4LKoy7A2Wb0afp7Gf4IkgayqLKZETyHMJ8x1LSQSCRKJxEVRFBwxoRbL02Fxnyvu9JrA4EB+KPwBpUxJvCMem82Gr8gXi9WCwWxAGzE4lFW7w8760vWo5WomRXSum+zLc0TXpmND2QZsdhtxAXHIxXKmR01nVvQsghXB3LfpPs6KOKtH56cvWZ8Tga/Ul7MiziJSGcnK/JUkBSUxK3oW2yq3sWz/Mj499Clnx5zN/Lj5fQoWnmnwzFpAZ+W3090+ilJFcd+E+3hp90s8s/MZnp72dL/tEm9OTl9robpzEE/XzJlYJCZSFUmkKpIZUc5aPHenqKjJWYNX2lzaySnyhFKmdAmNBPgEIBPLEIvESEQSJGKJq5bOardid9ixOqy0tfRNUXDIARrCoEEkErF42GJi/GN4Pet1Xtj1AlenXM2ixEW9GpHDAodx17i7eHPfmzy741menPYkob6hJ2nkfYc3IQQBIb4hnJtwLquOrmJb5bbTVjb5TEBfHvLZtdm8lvUaKrmKhyY91C+xgrLmMt7IeoOK1gpmRM3g9rG3I5fIWZS4iBGBI1i2fxnL85azq3oXd6bdedpngwJtgRiNRrIrskmQJlBTU8NVyVfxz9x/srlxMxeFXdSnF2awIhiAmraaTi/gDkUHgEsKVfhsduxstH5avq7/mn/n/JvNxZtZGLCQyMBIbDYbHbYO1jesZ3v+dlQ+Km4Zewvz4uZ1eR6E+obyzvx3qG6t5s/r/kyrvRWJROKKHpvNZiwWS6dM1sbyjYgQMc5vHDicxlViYmIXg19wxiQSSSfVR/fsToGhAHA2lnWHNwOmk2Fn1PH+gfeptlQTExjDYv/F7G7YjcVqIS0mDVGziHNjz6XGp4bVxavJqskipyaHCX4TmGk+lvkQ4EkbmqCawH7DfjKbMlkcudglBmG3210CBu40GnexCM/77a1u6NuD31LaWMoVw68gLjKOyspKVGIVvnJf9B36Tkab4GyFh4fjUDqoM9a5FNT0HXqMViN2hx0HDsQiMT4SH6damk8gQYogQhQhRKgikIqlZNdmU2us5dLhl3pV2uouAKJr0/Hl4S/JqMrAgYMIZQSXjriUWdGzUMlVAOys3gnQSVrbHXaHnS0VW/iq4CvqjfXHnfXpKxo7GgFnQ9bbxt7GtSnXsqViC2tL1vJT0U/8VPQTUyOmcuXIK09btckTgXsmeTCyPYNZSzQ+bDx3j7+bf+3713HZJd4CED3V6fW0raB6mFOXQ4JvAn9N+etxK3CeLHhzisAZfBRq6IT/3Z8lTaYmjhqOYrQa+/Q9lva+9aQccoD6ie6oBUPoHmlhaTw/43le2fMKn+V9xh7dHu4cd2ev/ROmRU3DZDPxzoF3eG7nczw59UmCFN7785xKxAXEkVGVgdlm7hIxvmjYRWwo28BXh79iasRUrzLMQ+gdvdEjcutzeSXzFRQSBY9PebzPhoPVbuXHoz/ydcHX2B12rk25lsWJizvVBIwMHslLs17i8/zPWV28+ozIBklaJChECvZU7mGiZCIOhwO1Vc1Y7VhyanKY7ZjNWO3YXo8j1CcUNxeTOuyY0pnFZkGECF17Z+oFOKXkZ1lm8cmhT/il4Bfy6vK4QHsB6cnpfHrgU8obyon3jecS7SXMiZ/T4/eHK8MJkAeQ35bPzeNudjkw4KyzEH7W6XQc1B0kLiCO6MBo9Hp9tzU1PUVSBTTLnfLfnnL+3UVp7Q47Pxf9zBeHv8CKlQuTLuSq5Ktoamji04pPCfINItw3nNpmZ5+pCxIvYEfFDhJkCTRbmtnWuI09TXuY3TybW2fe6lKvc69XCggIYLhjOMpaJeUd5SiVSpqbmztdC28OmkA/tNlsFBUVud5bnnVDRquR77d+j0qsIkWc0snZCZAFoDc5HSlloJJ2azv/PfRfKtor0BXowKfH29gtZGIZcQFxlDWX0WpuJT083et2ngGQpo4mvjnyjTPr47AxUjWS8YrxTE+cTlhY56j8vtp9AIwOHd3p7w6Hg8yaTD7P/5yK1gpUMhXXpVzHeQnnDVjWxxsEB0gp/a0BqEzJeQnncW78ueQ25PJz0c/sqN7Bbt1uzok9h8tGXHZavveOF0IAQqClDsbxBzMjMiNqBhabhXcOvMOzO57lialP9Kgw2hs8HZ6+jt9d9fCg9SD/l/d//CX2L66s7+mWEeoJvlJffFW+vb63LXYLdocdm92GzWFzZnzsVkQikTMj9FtWqK2lje/4rtfvHXKA+ghhknpSC86kSXYqEaWK4sVZL/J5/uf8UvwLD215qE9G5JzYOZjtZj48+CHP7XyOJ6Y+gdpHfRJH3jtCFM6Hn75DT7gyvNNnKrmKi4ZdxIr8FWwo28C5CQOjIPO/hp7oAYcbD/PSnpeQiqU8dtZjfY7cljWX8fb+tyk2FBOtiuautLu6rTeTS+TcMPoGzoo4q1M26E+pfyJeHX+8pzVoiIyIJKkiif0t+9Gb9QTKAnE4HFyTfA2PNzxONtnM1czt9TgJ6gSvFE+ZREaAPAB9h97rfkqZkjvH3UmyIpl3c9/l48qP+U/lf4hQRbAobBHjVeORSXuP9ItFYkb7jObX1l/ZXLCZK8Ou7NRzx9X7p72B+vZ6xgSPcWU8BHpNXV0d1dXVSAIliJQiLFgwa8wcsRwhOz+bZn0zWo2WgIAApAYpkRGR/HjkRxQSBRHK3h3pw42H+fTQpxxpOkKQNIgLQi5gtO9oCg4V0ODTQIujham+U6muqnbVB4nFYoJEQZS0lfBg4oMUtBWwoWEDm5s2k7Umi+n+0xnvP57fWhQ5BRuam51Rc7mWUmMpLa2dmy0LtTzeIseCUeWeDfK87p8d/Ayjw8gFIRcgcUg6NZvFAgWNBTy/83kONRzC6rBitVjBBsOChjEqfBRalfYYh98nCKVM6eoLYrM7s39Cb52mjibqjHWumoD8xnyUMiWPbX+MRHUi6eHpTAyfSFxAXCdBBHWo2hWIMNlMJAUlcXXy1diqnO/kmpqaTg6Q3WEnqyaLWP/YTnSlQw2HWJm/kgJ9AT4SHy4ZfgmLhy0+KRL3wprxpOOJRCLGhI5hTOgY8hvzWZG3gnWl6/i1/FfOTzyfi4ZddEb3J+yvrPnx4mTUEs2JnYPVYeX9nPd5asdTPDHlCTR+x2cHeooheBu/t6yQu+qhzWGj2Fjsyvq603p/T3Blh3sRhbRIhjJAAwJPx8dTjrO7SXa6SjqeSvhIfPjj6D86jchspxG5W7e712zQwviFWOwWPj30KU/veJrHpzzuouacDghUBALQZGrq4gABnJtwLr+U/MI3R75hdszsM0If/0xBbkMuL+1+CYC/Tf5bnxrwemZ9Lh5+MZePuLxP2bku2aCtDzM9cjpXjrzS670/VdBoNMxMmMn+A/spaC9gsnoyDocDiV5CgiSBjPIMFg9bTFJQUo/HESieQn8WdwQqAmnqaHL97vnMM1qNNIgakClkiK1i7HY7FruFRmkjbaI2RkeN7nJMb9HPSepJbGncwm7Dbq7kSq/OcH5hvrNQ36LGIXPQaGnE6mtl/6H9ZBZnUtFegcluQi6XI5Ude+0ZjUYcdgeiChG+vr4oJAriWuLYrdtNXEAcVa1VRKoivQZpypvL+XDfh+yt3YtULGWyejLnBJ+DxCFxORqrK1ZjtVpJU6YBTrlpm82GxWJhcuBkfqr7iUp5JTek38DV9qvZWLaRj7M+5ofWH9jasJVzgs5hlGqU6/vNZjNRiiiK2ovQGXWM0IxwGTuehpS7gIBgdHoaR8L9KqwoZGv9VoJlwUwOnozVYqW8opzC9kJ2N+2m0FhIq7UVuUTOOM04JoRPcAmD9CkLKnEa/MGKYPCIX32S+wltljYuHX4pjaZG9tXu46uCr/iq4CsS1AksiFvA9KjpVDoq+fuBv9PY0Ui0Kpqrk68mPTwdkUhEnd27UMIR/RGazc3MjXU6+6XNpazMX8m+2n1IRBLmx80/6RmWitYKwCm/3R2Sg5N5etrT7K3Zy+eHP+f7wu/ZUrGF28bexviw8SdrqAMKlzNN1wa+A4mTVUs0P24+YsS8l/MeT+14iqVTlvZau+XNLvQUQ/DsKSb83fO56Kl6mKpJ7VFIYQhdMeQAdYPc3FyGDx/uogmIRKJO0oTe1HPc4e1lNAQnkoOTeWn2S/3KBi1KXIQIEZ8c+oSnM57r9crfAAEAAElEQVRm6dSlp01NkFAnIFAbPOEj8eHyEZfzXs57/FT0E39I+sPJHN7vFtm12byS+QpSsZS/Tf4bI4NH9rpPf7I+3UHIBs2KnsXK/JVsr9rOzuqdzIubx2UjLjttMpSzRsziw4IPXQ4QOJvmnR14NnkVeazIW8GTU5/stbGfZ68rAUE+QVS1VuFwOFxCB2azmYrqCva27uWbI9/QbG4mWBrMrVG3khSRxNq6teyr28cBwwHm+czjItVFLvqIJ+VLkLFWy9Qkq5IpMhdR01bTxdHUaDTQCNYaKwfbD/JLzS9YxVZ8Gpy8LIldQrhPOJE+kYQqQhkePxyZRIZULKWiqoKmliYUfgrE/mKKDEUcbjxMg7EBq93KA78+gEKiYFTIKBbEL2CcZhx17XV8c+QbtlRsod3YTopfCnOD5xIiD3EW5f4mRlDRVEGRqYjxYeMJV4a7jBLBCDov+Tx2m3bzS/UvzEuah9pHzcL4hYxSjOK7vO/Y1bqLb2q/YVvTNqYGTWXhyIXU6eqI9HEGi8rby4lsi3QZk0JtFBzL8LjLc3szOoX30pdFX2JxWJgbMheFn4JNlZvIaMxAb9YjFosZrRlNcXMx9064l2mR/Wum2xPKm8tZU7KGBHUCV6dcjVgkxmq3clh/mIzKDJdAwD92/wObw0a4Xzi3pt7KObHndHpXeBq9wjXe0bEDh8NBoE8gr+19jV3Vu3DgYFrkNK4ceeUpERsoay5DIpagkCp63E4kEjFRO5EJ4RPYVLaJT/M+5cXdL3J29NncMPqG07ohc3eG/umq8na8mBs3F5lExrLsZTyV8RQPT364R1nz3pTcurs23q6dtx5m1YXVp6UgwumKIQeoG3jq1YvF4k6TsLcGju6fC1Kl/6teubeH4fFkgy5IvACZWMYHBz/g6QxnJuh0iLoL0cMmU1O328yJmeMscD36EwviFpw2RvKZikxdJq9lvYZCouDRsx7t1Yk5kaxPd4hXx/O3s/7GoYZDrMhbwZqSNU66SsL5LB62+JTRVdzXW5o2jYP1B7FgQYYMkUhElCqKWVGz2N24m8yaTJdMb3cQel0VGYoYqzlWNxSkCMJit9BmaUMlVxEWHsaagjVsq92GwWYgSBHEbam3EWoIxWqxIm+T87ez/sb2wu2syFvBz0d+Zl3JOiZpJ7EwfiG2alsniWp3GeupwVMprS9l1dFV3Dr2VtcY2i3tbKncwke5H1FnrMNuthPjE0OsMpZpydNIVCcSqYqkob7B6zM4pyEHs8jsbAQ6yukg/Fr+K29kvcEMzQxsRhuNokb21exjW+U2VwNNlVxFmiaNhWELUbQpvNaEbj64GYlUwsUpF5MadqzRodD4EOBm2828uvtVXtzwIldGXulSpbt6zNXcqL6R5dnL2VC2gZ8bf2ZT5iZSZCkk+SXhcDioNlV3qnNyV9QS3lXuv3vODfjNUQqAjIYMohRRtFvaeS7nOcwyM74yX+ao53D+iPOR+ct4dNujVLZW9mkO9gU2u41l+5dhd9i5Y+wdLodGKpYyOmQ0o0NGkxqayiuZr9BqaUUikuDAQYOxgQ5rR4/rS6fT0dLRwi8lv6C36/lP7n8ASNOkcWXyld0KIgw2rHYrla2V+Eh8sDvsve+As3B8btxcxoWN470D77G5YjMH6g+c1tmg7gz936P9Myt6FjKxjH9l/4sntj/BXWl3MTVyqtdtvfXz6QtbyNu1E4Ri3OHQOn53TuZgYsgB6gae2R7PDuLu8Cy2FSZ0UFAQbW1trpqh/9VsUE9Fff3NBi2IX4BMLOPdA++ydPtSHpj4QJ8i/4MJIQPUXT0EOJupXTnySl7Pep01JWu4YuQVJ2t4Jw0ng/bpcDj4b8l/+ST3E1RyFY9PebzXmp+ByPr0hFEho3h2+rNk1mSyMn8l3xZ+y+ri1cyMnsmCuAXEBsQO2Hf1Be7rbYTvCHa076CKKkbIR7gM9QtjLyS3JZf3c94nJTilU2bHE6mhTmN9e+X2zg7Qb/O+rKWMAn0B60vXU91cjdQuZXHiYv4w9g/4SHw6FeHX1dURYAzgpoibKLYUc8B0gM3Fm9lSuoVgSTCT1ZNJD01HbBZ3krGeFjON/fb9rC9bz7TIaajkKtaWrGVb5TY6bB00m5uJC4jjwWEPIjVKCQoKIjH6mJHrbkC4z1NvkdUd1TvwkfgwWTEZi8TCIeMhGhWNNBgaaDG34CPxwU/qR6BPIOogNUmJXWmEGYUZ/JD3AyODR7qumTdWwNTIqSQpkjjYfJAUQwqjbaNd22rRMtN3JhdMv4AdtTv4b9F/2dG2gx1NO2iyNnG47bCrDshdWSsgIMAl9S0SiRCLjz1HhftQWVmJRCKhw9TBRwc+otXWikwq48eGH1H7qrl+1PUsjFvoChBYbBakIimF+sK+TsNeseroKo4ajnLhsAsZHjS802c2u43lectZXbwapUzJ01Ofxt/Hny8Of8G3hd+yrnQdl4+8nAVxC7q8J8qby9nUtomN5Rup6KggxDeE8+LPY17cPKL9owds/MeDsuYyrA4rcokcq8Par31DfUN5ZPIjbC7fzMeHPubF3S9ybvy5XD/q+kEVbTge/B6zPT1hauRUQnxDeDXzVV7Pep3ylnIuT7q8y9z0JnLQX9GGnt6zv1cnc7Agcggtr4cAQHNzM2q1GoPBQEBAgNeImaC+I0Qr5XJ5J3qB0FdB+Lu7vKrntv8L6KthnN+Yz7LsZejadSQFJfWYDdpdvZt/Zf8Lm93Gn1L/xJzYntWkBhNGq5Eb/3sjs6JncXfa3d1uZ3fYuWfjPVjtVt6a+9Zp99I6UXjO+4GGxWbhg4MfsKl8ExHKCB6a9FCPtWOeWZ8Lh194wlmf3mB32NlWuY3VxaspNhQDTid/QdwCJkdM9irxO9BwX2+Hyw/zjyP/YHzgeJ5Z+Eyne6QP0vNW9lvMjJrJn8f/ucdjPrH9CYoMRSybtwx/uT8Oh4OPcz/m40MfE+gTiFwiJ9AnkBRJChNVE1H7qrt9JrqLGADUGmvJbM4kuyWbDkcHIkREyaMYrR7NRekXEaWKQiQScajsEI/ufJR2WztysRwfuQ8jQkewMG4hn+Z9SrgsnMvVlwPHAlhesz49zFNdq447N9xJmG8YfiI/jjYdRSaVEaIMYW7sXGbHzKaoqYi1pWtdvStmRs3kxtE3YjQYnYEvTRCP73wcg9nAvcPuZU76HNd9caekCQEyu9zOCwdfQISIv435G2KzuFPATS53KkuaTCaKjcXsad7Dr42/IhKJnOpnqvFMCJiAUqJ0bStQ4QRIJBLS0tLIzs520QzFYjGb6jexqn4VEomESFUkFyRc0G328pkdz1CgL+CDhR/gIzlO2bffUN5cziNbHyFcGc4/Zv6j05psNbfyetbr5NTnkByczD3j73HRnW12Gz8e+pGvj3yNESOp4ancMe4OQhQh7NHtYW3pWnJqcrBYLfjJ/TBYDSydspRpUQNH2zsRfF3wNV8c/gKzzcz8uPm9rrvuUG+s51/7/kVeYx5jQsZwf/r9+Mv9B3i0Q+gvGowNvJL5CkWGIiaFT+Lu8Xd3qfn1tIf6Gzjs63u2p+P+3mvUPe347jDkAHnA88J5m2zC34Tmc56TyJtUNnjvXj6EzjDZTK5skFQs5dz4c7l4+MVeI9QlhhJeyXyFOmMd58Wfx/WjrnepDvWEwVj8f/zlj4wIGsHjUx7vcbvvjnzH54c/5/4J93ebJj9TMZgPVYPJwKuZr3JYf5hxmnHcO/7ebrMWDoeDvTV7WZm/korWikHJ+vQGh8NBYVMha0vXsqNqBxa7BbVczZzYOUyNmOpStxps1NXVsXTHUpqsTTwQ+wAqpQqr1dl0MzQ0lJf2vERWbRZLJi5honZit8fZVrmNf+77JxcPv5hgRTAbyjZwqOEQNW01pIen88fRf2SSdhL6Bn23MswAUVFRnZpvikQiHA4Hfn5+GM1GKqWV7G/cz/7a/djFdqQyKeF+4aSEpNBY28iv9b9Sb64n2jeaW2NuZdGURYhEIv74yx/RODRcHX41ALGxsa5sh2D8u18TYYz+Qf6UNpc6G/MZilhXso7K1koilBGE+oWSpkljcsRk0sPTuziv+2v388CmB6jrqEMpVXJT5E1MCJjAOv069rTsYX7wfC4bdZlXOWp3yOVyWoJbeGPfG0yJmMJf0v/SaZxCwM1utyO8rt8oe4MOaQcSs4RWaysikYgYRQxTYqYwTD4MuVGOWCx2bS8SiZgwYUInUZ89jXv4oOoDxBIxC2IXcNf4u3ps7Phz0c98cuiTXudKb7DZbSzdvpRiQzHPTn+2U/anoqWCl/e8jK5dx7zYedw45sYu1z0nJ4eWjhbWNK5hT9seTDYTflI/fKW+yCQyhkmGMUE1gY36jejFet5f+P4JO2wDhUe3Pkp1WzVtljZmRM3g3gn3HvexrHYrH+d+zNrStYT5hfHQpIeI8Y8ZwNF2j9+7AX0iMNvMvLP/HbZXbSfWP5Ylk5YMaCP3vl77nhylwQ5WnmoMOUDHiZ4yQJ4Uit56Af3eJ9lgIr8xn2WZyyg2FBOgCOCy5Ms4L+G8LoWjBpOB1/a+Rl5jHqmhqdw/4f4e6TwwOPfl/k33IxFJePXsV3vczmAycNeGuxgROIKnpj01IN/9e0eJoYSX9rxEQ0cDixIXcU3yNd06uoJ87GH9YeRiOYuHLeaS4Zec0v5LzeZmNpdvZl3JOmqNzj4wob6hpIenkx6ezqiQUQOeGXJ3OvbZ9vFt4bfcGHUj8b7xpKcf67XS2NHIg78+iEws49XZr3ZZOw6Hg9LmUnZX72bZgWV0WDuIDYhFIVEwJmQMGdUZ3Dj6Ri4aflG3Y/G23jzVNeFYRkSr1RIYHEhuQy57a/ayR7eHI01HMHQYABAhQoyYcf7jSFQlEhMaw+fFn5OoTGRR0CJUShUmowmLxYLN4ewX4a/xB19cMsz6Dj3lLeVUtVZh/01r2uFwoGvTEeobyjPTnyE5OLmLQ+f+PliavZQdVTtc+yvECtID0qm2VDMyaCTXaq4lMiLStZ+Q/RECZ+59fCIjI1letpzt5du5RHMJC0cuBOjkMLlntZaVL8NoNvJA0gNk1WWR25pLUUcRYh8xRqORQEkgowJGkeSXRLQ8GolIQmxsLBqNBofDwaf7PmXZoWXYsHFX2l3cMPqGXtXcdG067tt0H+fEnMPt427vbQp2i68Lvuargq+4cNiFXJtyrevvWTVZvJH1BmabmRvH3MiCuAWdggQOh4Oqtio2H9nMzsqdVFurMWOmvr0eiVjCWM1YnpzyJKJ2EUWVRbxc8jKToybz0KSHjnusA4kGYwN3bbiLaRHT2Fm9k/TwdB6c9OAJH3dd6Tr+c/A/yMQy7hl/zwk5p33FkG3TMxwOB6uOrmJl/kpUMhV/Hv9n0sLSTuoYhjJAvTtAvy8OziBA4FQKBazuTo9AURBEDjwdov81HuxAIsQWwnXB15Erz2Vz02Y+P/w5/y35L5cnXc6cmDku+pjaR81jUx7j49yPWVe6jke3PcqSSUt6jIQNxn0J9AmkvKW81+3UPmqmRExhW+U2ylvKT1rE7kzFjqodvJ39Nnbs3DXuLmbHzPa6XVlzGSvzV5JVm3XK5G27Q4A8gAuHXciixEXkNeSRWZNJpi6TNSVrWFOyBoVEQVpYGhPCJjA8aDgRyogTbrAqOBcAUfYoJFIJ+1v2MypklKsQX6PREKwI5sbRN/JW9lt8lPsRfx7/Zxo7GilqKuJA/QEydZk0dDQA4Cd10qIWJSzi8pGX02ZpI7M2s0fxD+i83txfvJ70YEE6Wvg9LSwNuUROdm22k14XnEKSMokjdUfINGSS1ZJFibEEhV5BrbmWZnMzOrMOY7vRmfkQAb+F90SVTplrd2h8NUzSTiJUHIrKrMLqa+Wb8m+4YdQNjAkd02ls3vj6BxsOupwfAKvDSqYhE6lYymLZYqwWa6f9BERFRbmOCU6Rh5qaGm5JvYXMsky+rvoalVTFMOWwTtki4efU1FRMRSZEDhFWs5Ux/mMYrRqNXCHnSMsRikxF7G/YT0ZjBjsNO5E5ZIzwG8FI40hmyGbwU9FPrC5ejVgi5rrk67hxzI29TyhAq9QSrYomsyaTm+0399tpr6ur4+f8n/mx/kcSghOYEzzHNRcPdRzinf3v4Cfz46FJD7malgpzMa8xj701e6luqwZAIVEwTTuN9PB0koOTWZG/gp3VO3ll7yssmbgEfaAeiVTCZO3kfo1xMLFHtweASdpJHGo8hMFsGJDjzo+bT5Qqiv/L/D9eyXyF28fePuiU8CHbpmeIRCIuGn4R0f7RvJn1Jn/f/Xfmxs7lupTrTpo4Tk/1QEO1Qk4MZYA80J3nKEQ8BLhH49yjmMJnQ1GRE4P79Y6MjuSA8QDfFHyDwWxA66flypFXMiVySidDcW3JWj7K/ajPkbCBioLU1dXx+t7XyTfm8/mFn/eabTjceJgnMp5gYfxCbh5z83F/7+8Zdoedrwu+5psj3xDoE8gDEx/w2rOmtr2WLw9/ybbKbadc3rY/cDgcVLRWkKnLZG/NXgqbCnH8Zq0rJAoS1Amuf4KSWV+cIvfsdFNTk4te9tbRt6iz1XF/9P3IkbtoYY0djRxtOsq7+9/lSNORLtctzDeMidqJTAyfSIgihPs3309SUBJPTXuKDmsHN625ibNjzubOcXd6HYdn5txbLaRQl+IOuVxOY2Aj7+x/B6lYyjUp17AwfiG5B3Mxm8002Bv4qPoj2jraWBC6gPWG9YQoQ1gYv5DSslKsdisSsQQ/hR8dxg7CAsJIjkvG0e7A3GRmWNQwIsKdTU5zcnJo72jnvar3MMlMLJu3DJVc1ekZJGRPPDNAQh8OsUiMn8SPQGkgKh8VdoeduUFz+cPoP6DRaCgqKkKv1zvFGRITu6VRZxVn8fe9f8eOndsSbiMUZ+2LQBUUxvLwzoeR2qTcEXsHnq9wuVyOw+GgvLWco6ajlNhKKNQXIpaIqTfVY7KZAGdz6nsm3ENSUFKf+/n8UPgDK/JXcN/4+/pUV+N+vTYc2cCKihUEygJ57dzXqCqswmw2k92WzbrmdShQME01jVZbKwapAZ1ZR31rPRarBZlURph/mKtB6uiQ0Z2esw6Hg+8Kv+OLw1+4+uvYHDaWzVt2WtDfHA4HD/76II0djbwz/x2ezHiSVnMr/5r7rwH7jpq2Gp7b+Ry1xlpuGXMLC+IXDNixBxK/9+yDJ+ra63j3wLvk1OcQogjh9nG3M04z7lQP63eNoQzQAMJTYcc9y+PZbK6trQ2lUtkpyjoE7+jpQehZwBxBBLOiZ/FL8S/8UPgDb+x7g1VFq7g6+WrGho5FJBKxIH4BUf5RvJb5Gi9nvsy82Hlcm3JttxGX/qqvdAedToefyA+L1YLepCfML6zHc0sKSiIuII5fy3/l6uSrhxqjeqC6tZpl+5dxWH+YRHUiD0580NUrRoDBZOC7I9+xrnQdVoeVsaFjuTrl6lMmb9tfiEQiYvxjiPGP4ZIRl2AwGThYf9BVi1JsKHYV2YOzA3aQIohgRTCBPoEEK4IJUgQ5lch81MjEMiQiCUXFRVgsFuzNdkRiEWarmfbWdvxEfujadLxb9i5qqZpWWyuOGgdtFqd0ss1uw2wzU91azaLERYwPH09ycDLRquhONKSF8Qv5b8l/+W/xf1kY76RpmaymLufnbW25Z0Hcn5FRUVGdxAEAMhoz+Dn/Z8KV4Tw16ymiVM6sifBcGK8dT+LwRB7b+Bhr69ciEolQoiTZlEx6YnrnLL3vb06MJBZdmw6JQ0J9bb3LAdJqtXya8yl6u55rhl/jogF6PoM81/RfUv7Cs63PcqT9CCJERMgjeGjCQ4yIHMELu15gg34DHTkdXDfmOpdEtfC/t2Pn5OQQqAzkGu01fFL9CR8Uf8B1EdcRpYjqdG0qKipoM7ehkWkQi8WdaoOEYwOIdCImj5iMRqPhaNNRnt7xNC1Wp2qc1WHFgYN/H/g34HS64wLiiFfHE6IIcc2tIEUQQT5BKGVKRCIRZ8eczZcFX7K2dG0nB8hbnZf7PNh0ZBPf1H6Dj9iHK5OuZHfxbkrqSzjYcpADbQcQiUX4i/z5se1HwCmFPVI7kmhHNGHSMBL8E5g/cX63dXMikYhLR1xKtCqal/a8RHlLOZeNuOy0cH4A8hrzqGit4PyE8/GR+BDkE0R5S7mrh9ZAIFwZzpPTnuS5nc/xwcEPcOBwrdGBxIk6MAP13j1ToPHT8NhZj7GhbAOfHvqUF3a9cNKzQUPwjiEHqBd4Ri0TE7saWJ7pRCG697+ywI8XPT0IvaVofaW+XDriUubFzeP7I9+zpnQNL+x6gdEho7km+RqGBw1ndMho/j7z77yz/x3Wl60nuy6bO8beQaqma0ZOMEKOx2H1lNJV16nBAUXVRYQNC+vx3EQiEQviFvBezntsq9zG/Lj5/bxyv0/YHXZ+Kf6FlfkrsdqtnJ9wPlcnX41cIndtY7QaXf2UOmwdDFMP45qUaxgTOuYUjvzEofZRMz1qOtOjpgPOa1HTVuNyiCpaKtB36KlsrSS/Mb/b45hMJmzW37Ipv1HAxGIxdoedDnsH+e35RPtEE6YKIzwgnAhlBInqRBLUCbRb23lx14uUt5RzR9odXjvVX518NVm1WazMX8n4sPHIxXKMVmOX7bxRZNz/5r4+hEyQ8Jzdpt/G+ob1hMhDuE5zHXKjnJzinE5BJwANGh5Jf4SXs16m3FROY30jjepGVzapsrKSgIAAVxZHMNCFjIuAZlkze9r3MCJ0RKdaJs9nkOeabqpvIt4nHoPZgEqq4rqI61C2KwnxDeGpaU/x8JqH2VS3CfMBM39I+ANms9nVMqG7Y5vNZmJ9Y7laezVf1n7Jx1Ufc03ENQxTHaPDNZoaMWFCq3Ceg1gsdn0mZKqE7wCnPP+bWW/SYe0gITCBFnMLt6beypjQMS5nu9hQ7GwCqz/sdV7JxDKCfIIIVARitVjZULIBLBAd7KwvqtHVYLVZsTvsiGpFhIaHYrFZ0Bl0HG44THlHOWKxmAhlBJ8Uf4LRaKTN2kaTtQm5RM4oxShGqEcQQgiRikjGJ4wnMjyy03O2L47C5IjJJAcnU95Szt7avRxqOMSokFHdbn+yshFrS9YCsCDOmZUJ9AnEare6emgNFEJ9Q3ly6pM8veNpPjz4IXKxfMDpcCfqwPwv0udEIhHz4uYxTjOOdw+8y4ayDWTXZg9INuh/LaPWF2TqMvu03RAFzgPdqcBB55eLOzylsr1FwobQFSe6cOuN9XxV8BVbyrdgx85Z2rNYPGwxwwOH48DB+tL1fJb3GR22jh6zQQIFx1Mtqid4FoH+Z+t/+LbqW/4Y+0cun3p5r0IZRquRO9bdQWxALM9Of7bf5/57g3vWJ9wvnDvH3UlKSIrr81ZzK5vKN/HD0R9oMbcQqYzkquSrmKydfFLU1E4nWOwWDCYD+g49TaYmDCYDVrsVq8NKaVkpFqsFiUjirJNzgJ/Yj+HRw9lRu4PNFZtZkraE2Unea6k2lG3g3wf+zaiQUTx21mNepdoPNRzi6R1PkxycTFVrFZGqSJ6e9nSnbXpb291R5L7J+Yaf634m3CecGyJvIEAegEQi6SKhbbfbEYvFREVFIVaJeWjLQ+yv3U+sIpbLwy9npNLZG6w7qrLwLLfYLDyy9RF0bTpemPlCjz2l3Mdcbi/nrb1vUdtSy3C/4VysdarjuSt+ShVS3s5/m1JjKbODZ3N20Nnd0qO9PS+2H9nO8srl2LBxufZykv2Snde/9RBf6r7kovCLGO8/HpFIhEwm83qtW8wtPJnxJJWtlSQEJFDcXNwt9dbhcNDY0egUiTDpOwlG6E1613wrayyj1lSLr9iXcGU4UpkUq8WKxeJsEiuTyZDKnPPGaDVS11aHj8iHWZGzGBk+EolJQlFtEVsatxCmCuMm7U0EiAJ6pI73VTr4SMURluxYQmxALAa7s8bmialPMCxwmCtL5T533KXGPQU6BsqgrG6t5q+b/8ro0NEuldAvD3/JN0e+4ZVZrxATMPB1oA3GBp7KeIo6Yx1/Tf8rkyMGrhZqyOA+MTgcDlc2qMPWwVkRZ3HVyKt6bOnQE4YEKY6hxFDCyvyV7Cndw3dXfjdEgTtRdNdLwh2eDVJtNhtyuXzo4dALTrQQL9Q3lDvH3cmixEV8nv85u3S72KXbRYI6gQVxC5gdM5u0sDTe3f9ur9mgvsLdUIFjdJPQ4FBEOhE+Cp9O3d6Fh5NnE1xfqS/jwsaxu3o3BpMBtY/6uMd0JsNb1ueq5Ktc1JWipiLWlK4hozIDs91MsCKY28bextnRZ/dJ8vz3CJlYRqhvqKs3ijvq/DtLJwsGv6RVQpo4jS1sYfXR1d06QHNj51LaXMqakjV8nPsxt6Te0mWbUSGjWBi/kDUlawAIsnYVmvDW9NMdnmu/rq6O1XmrWV2/mhB5CDdG34jaR+2qZQInbc5d3MFms1Fe7swszPObR4WignZ7OyurVzJKNYoZwTM4K+asTlRlT1GDb458Q0VrBVckXUFcQFyPgYvQ0FAMUgMrilewpXQLYruYaxKvYbTPaCIiIrp8B8AfY//IeyXv8Wvjr/jJ/Lgk+RIqdZVU11QTqY1EIpJQW1Prel64I7Y8lj9G/ZHlVcv5vOpz0gLSWBiykCpTFQARcieFTywWezV8rHYrr+19jcrWSsaHjWdf7T5Gh4zmhlE3eL33IpGIEN+QLnTTLve2VsfzO5+n2FjMDRE3MCttlqsOSiKSIBFJaDI18VHuR+yr3sdIv5FcH3E9YcowUlNSWZ+5nq8MXxEoD+SRsY9gbnQGGHvKCHhmHQR1PM/5tfrIamx2G9P9pzMueRxPbXuKJzY9wZOTnsSkN3WaO0KgUiQSuZr0eh67srLyhAOZXxz+Ajt2/pD0B9ffBHEWvUlPDAPvAIX4hrB06lIe2/YYb2W/hVapHbCGzEMF9CcG92zQR7kfsat6F3uq9zAndg6Xjbis1/Xnif/FjJondG06vjz8JdurtgMwRTuF7/iu1/2GHKBe4Mlf97bwPSegt8l4pkZNzoRxx/jHsGTSEkqbS1lXuo6tFVt598C7LM9bzuzo2dw85mZyG3L5LO8zntv1XJdsUEBAAHq9vsdIgQDhRQx0MjqCAoPw9fWlzdiG2efYi1qr1XYxugRMDJ/IrupdZNVkHRdN4Uy4Nz3BM+tzx7g7GBUyCrPNzK/lv7K2dC2FTc7O80lBScyPm8/UiKmnVNL6dIdgnOTk5LiymuA0+IJkQQz3G85R01HqjfVeHSiAG0bdQGVLJWtL1xLoCCRJlNRljl2TfA37aveRXZuNUqrscoye5r037CvZx7e6b/GX+HN9xPVEBkfS1tZGc3Ozq75FEBFwF6NxOBzYbDa0ci1yh5xpUdPQt+gp7CikpKGEbZZtLOhYwIyoGa4xCAbupsOb+KHwBxLUCS7qmzsVTfg9IDiAjMoM1paupchQhNViJU4WxyLNItRiNSqVisrKSpdB3WZro9pUja5JR7OoGalYSou9hY8qPuKXxl/ACg67A1GBCBEifEW++B/1JyIwgpjgGOIC4khUJ2LHTqRPJLfH3M5PdT9xoPUAR9uP4itz9rtJCk+io72jWwrvJ4c+IbchlzRNGgfqDhDmG8Zf0v9ywg2YtWFabky9kacznybLlMVFimO0QYfDwabyTXxy6BOMViOpylTmBc7DV+KLVqulxdzCV3VfYcPGX8f+FZvB5goYAq7zEK698LMwl4VztduPqe+5lPZqdOzW7yZAFsDs4bPRBmtZHLKYzys+57V9r/H4pMep09W5MkDCcYX/vc3T7v7eVxQ1FbGjegfp4emMDB7p+nuQz28OUIf+uI7bF4T5hfHAxAd4dsezvLznZZ6f+bxXWusQTg00fhqWTFrC4cbDrMxfyYayDWyp2MJ5Cedx0bCL+kyNPF0d0pNhnzR1NPFt4besL12PzWEjTZPG1clXEywK5mEe7nX/IQeoF7hH9PoazfR2s8/Uwr8zadxxAXH8KfVPXJN8DVsqt7CuZB2ri1ezung1qaGpXJtyLTuqdrC+bD17dHu4LOky5sbOdRUm6/V6VxSwO3jWDQmR4lZpq5MGYrN0qjFwd6A9ZYDHh41HjJis2uNzgM6ke+MOg8nA94Xfs7Z0LVa7lfPiz+Oq5KtoMjWx/NByNpVvotXSio/Eh7mxc5kfN58EdcKpHvYZBc96G8HQWzxiMe8ceYf1peu5Kvkqr/tKxVLuT7+fR7c9yqeHP+UqrXM79zmmkCq4f8L93LLmFg7rD1NiKCFeHe/63HPe9wSL3cLP+p9BBNdGXUtCWIJLEltQSRPG39bW5mpyCseCF/4Sf5RiJc2OZl467yXKmstYW7qWrRVb+feBf7P80HJSQlKcIhlG6DB38M2Rb5CL5cz1mYu+Qe8KWBwqOkS1qZoqUxU6i47qkmrMIjMKiYJ5sfOINjoL8wXUNtRSZCzicNthijuKabI2gcPpDIjEIkIDQknxTSG3Ppf2jnama6aDDZQqJWa7marGKlpsLRysP8jh1mM1OHarnSCCSFImMT9sPmM6xvCz7meOtB8hShlFZGwk7U3tXh3NDWUbWFOyhriAOAqbCpGJZTw06SH85f49T5w+Ij0hnbn6uWRUZbjqbOqN9bx34D2y67IJUgRx7/h7iRHHuOZAcEgwf9/9d5psTdw9+W6mx03v9Dz0ZFJ4sioAV1ZTcOzhWOZo89HNNFuamR82H22Y828LkhZQY6phu2E731Z/y5/T/oxIJOok2iBAeKb7+PjQ3t4OdK0X80RPRp7D4WBF/gpEiLh65NWdPnPPAA0mkoOTuTn1Zv594N+8tve1bmmtQzh1GBk8kienPkl2XTYr81ay6ugqNpRt4MJhF3Ju/Lldeh+eKRhM+6TN0sZPR3/i5+KfMdlMDA8czrUp17rq/YT+ar1haCX0AnflIuH3/6XCvzNx3H4yP86NP5eFcQvJa8xjbcladut2k1OfQ7AimLGhYylsKuTDgx/yc9HPzFLPIs4Rh1gk7nJ/Peu7PF/WQqS40dCIxWrBarMikUi68NQ96xB0Oh2pmlRGBo9kf91+LDZLvzMbZ9q9MVqN/Fz0Mz8e/ZEOWwcJ6gSuT7keo9XIa3tfI7suG4AIZQSXjbiM2TGzUcq6ZheG0Du8FdlrtVpCQkP4tuJbNpZt5LIRl3U75zoMHVwUcBEfNn/IlzVfcr/2/i7bDAscxsTwiWyt3MoLu15g6ZSlneoZ+hqZ/L7we3RmHdeOvZbzR55PTk6O6zPBGJXJZFgsFpRKZSfnyt/fadDr9XpGBI6g0FBIvbGe2IBYLtJexDjbOErEJewz7GNfzT721uylraMNnVGHBAkJvgl8WfUlX1U7sxJ2qZ1mczMWq8XVQyhSEcmFcRcSb48nPiKe8vJybHYbhe2FZDVnUWIpwWT5TVo6IIqztGcRKgpF1aEiMTgRa4cVrVbLtwe/5afanxBbxTx57pOucxSeE+Hh4cgCZC5BgiJDETk1OWxp2sK25m0oRUqiFFFUmitpNDZy77p7maueywifEYhF4mM1qK2V/OfgfwiQBdBobMRoNfLgxAcHvNbkypFXklGewT+2/IPJgZPJaM3AKrJydvTZXD/q+k4RbJ1OxxdFX5BTn8P8uPku4Rf3e+lJK3aXc3eHUNflObcOmA4gFUs5P+l81980Gg33z7kfa6aVbZXbGKcZx6zoWa5nsbsMueczXfisJ/Rk5G0u30xOfQ5nx5zd5doH+gQCg5sBEjA3di5lzWX8t+S/3dJah3BqIRKJGB82nnGacWyr3MZXh79iZf5Kfij8gdnRs1kQv+C4a4ROFQbDPikxlLC2dC3bKrdhspmIUkVxdfLVTAyfeFy1wEMOUC9QKpWYzWb8/PywWq2dbma9sZ6HNj1EXmMeKcEpvDTnpW5pJadrmrI3HO+4jyf9OdApU5FIxKiQUYwKGYW+Q8/Gso2sL1vPgfoDiBARogih3FDOR7UfEaGIYF7IPObEdM7EdBeVFBa3xWLB4XAgRoxMKkMilXR6gXvuK5FIXC9wgPTwdPIa8zjYcJDxYeP7dX5nypyy2C1sKNvAtwXfYjAbiFBGcG78ubRb21m2fxl1xjrEiDlLexbz4+YzJnTM/5ywwWDCc54siF/A8rzlbCjbwLkJ53rdR6fTESQK4oaoG/ii4QveL3wfTbCmkzAFQJR/FBo/Da3/z955h8dRXf/73d7U66o3N9mWLVtyARtjio0hYHowJECAEAKEkIRA6PklgRRISAVMCdVgA6HbFLkXbGxLbpJsy7Z6W2nVpdWutsz8/tjvjHfVLNmSG/s+jx5Juzt37pSde88953yOs4v/t+3/8fDMhxkbPnbIfator+Djwx+TEpzC1WOvBpCFDgB5JV5KsG9tbWXPnj0Acp5GcnIy6enpKBoUPL31ad7e9jY/zPohFosFpUfJWMYyIWwCEWMj2NO9h6V7lxJhiCDFmILgFHCKTkRRRI0avaBnZvpM0kLTCPeEo+nSkBSfJIfNHSg/wO6O3eR35NPmakOn0zE5djK55lxS1amI7aJfor6vd2Z68HSKOovIb89n0+FNzBs7r9/rE64PZ3rsdO8xCh5KWktYuWcl+zv2s7tzNy7BRbwhnrqOOt7rfo9gVTAXp1xMenA6gijw4p4XsbvtqBQqnB4n9+fcL7d3PPjmRUkrqwkJCWCEcEU4BV0FlHeXk2xM5jfn/qbPviwWC0WtReQ15DE9YTo/mvSjPu/3F1YshXL66jRJ+TiSbLh0rg+2HKTUVsoFGRcwJmGMX/tKhZJ7pt7DAxsf4I3iN5gcNVke13U6HW63G/D37ttsNjwezzFXsAea5DXbm3lz/5uE68O5OfPmPtuF6cJQoBh1D5DELRNvoaazhrzKPKZET2GGecZJ2W+A4aFUKJmXOI9z4s9hQ/UGvir/ii8rvuTLii/JispiQcoCcmNzz4j815Gan7g8Lr6t/5a8yjwOtR4CID00nUvTLmVO/JwTOhcBA+gYSOFRbre7j0rM36v/ToG1AAGBAmsBj295nKULlp7K7p42HI/7czRdpuH6cK4ddy1XjbmKgoYC8irzKGwqxOVyYRftlNhKqHZVU6Qs4hbNLfIkbqD8LunLLRU4DDYFo25Tk5ScJPd9sG0lcmNzWXZgGfmW/GEbQKc7gijwTe03vF/yPg3dDehVeiZFTqLL2cXrxa8D3onAtWO9oYjDTf48Fmd6jtRosTB1IavKV/Hh4Q85P+n8futQSfdujjmHjDEZPPXtU/zhmz+wJHYJ56SfI59PlUKFSWPigdwH+Meuf/DUt09xd/bdzI6bfcx+uAQXL+x9AVEUuTv7bjRKrzcqOjpafhb4FgCV6B0CJYcy1YFRNLKjdQfn1ZxHSmKKvEjhdrv5quQr1tnWEW2M5jczfuOXk9Gfp9dkMmFTep//Do+Dzc2b2da+DZfgwqQyMT9yPpdPuBxnqxNzsLnP88s3esBsNtPZ2cmV7it5sfpFXi16ldzUXGxttkFVQ1VKFRMjJxI9JZqSyhL+eOSPhKhD8IgeFAoFgiDg1rv5ovYL8urzCNeFU9FRgVqppsfTw73Z9w7pWgyGr2dEEAUOdx/mrZq3ONJ9BEEQMKlMKFFyx8Q7/Iwf6ZwqdApWNa8iRB/CfdPu6xOCNdhKsW8uma9n3dewjIqK4t0D76JE6Sc04EuQNog7s+7k2fxneWXfK1yqvxQ4amBbLF459sG8//3R3yRPFL31lexuOz+f9vN+czlUShUh2hDaHG0Dtj2SqJQqfjbtZzyw8QFe2fcKEyImjFg4ZICRR6PUsCBlARcnX8yBlgOsrlzN9vrtchTLRckXcWHyhUToI051V0eNxu5G1lSuYV31OjqdnWiUGuYnzmdh6kIywjJGZB8BA+gY9FcrRhoQ9rfsR8CbjCkgUNxcfIp7e/pwPO7PkxHSpVKqmBk3k5lxM6nrquPrkq/ZXrudenc9ne5Ovq74mo3VG5kcNZkl45dwXuJ5g8quygUO7d7fA1VTH2g1JC4ojnhTPAWNBfxY/PFZ4flwepxsrdvKytKVlLSW4BJcGDVGHB4Hxc3FBGmCOC/hPGaYZ5ATmzNqMelnao7UaKNT6bhu7HW8UvgKq8pWcd246+T3fO9zacEnmmgenfUoj69/nNerXscturkm+hoA+X6dEj2Fx2c/zjM7n+HvBX/n3PhzuW3ybX5J172/Q58c+YTKjkquHXttnxyv3s+C3oVSQ0JC/FTuLBYLoiCSG5LLmuY1FHcUk0IKZrOZorIiPmv8jFJ7KQkRCfxkzE9w1jqxeqzyd9s3H8R3wu8W3bxf9D6bWjbR7ekmWhvNgtgFpGvSUSvUdDV0Af41hqSxQq1Wy9ED0rFMDZ7KIucivmr+incOvMNsxWy/YxjoPo2Ojub94vdRKVRcF3sdqYZU8jvyKbAX4BE9hOpCUSvUbLdsxy24MWlMLEhZgFKhpL6rnlhTLEqFctiLAh7BgyfYw+7y3dT31FPSXUK7ux0FCiaYJjAjZAbBqmBeqn2J1/a/RnJQMhkJGX7n8ePqj7Fj52dTf+a3yNHfvdbfcUttmUwmuWSBhMlkYlfjLkpaS7g4+WLiguL6bcdqtaKz6sgJz6GgsYCMuAzSSOs3ssN338fz3FhZtpI91j3MS5w3qOctTB92UkLgJML14fxo0o94fs/zvFH8BvdNu++k7TvA8eEbxdLmaGNd9TpWV67mg0Mf8L9D/2Nc+DhyzblMj5lOQlDCGT1/EEWRyo5KChoKKGgooLS9FIBYYyxXZlzJ/KT5I260BwygISINttIDu6qqinRDOsVdxQgIqBQqJkVOOtXdPG04nsHjZId0xQfFc1vObdyWcxvN9mZ2Ne5iQ/UGttVtY6dlJzvqd2BSmZgZO5M5pjkkqBP8JilWq1VejQ6LCIMWfwPIYrFg7bbyly1/oaKngsmRk3lq7lN9wiRzYnP4vOxzqjqrBq1DcrpjsVn4vPRzviz/khZHCw6Pg2BNMGH6MBKCEsiNzWV67HTGh48/KS780y1H6nTySF2QdAEry1byeennLEhZIMuwD2Q0jo8Yz73j7uWFkhd4t+5dEusTmRk3EwXeAVdEZHzEeP52/t94tfBVttZtpaipiB9n/ZhZcbP6rNrrQnV8euRTkoKT5NC33uen9znyNYJsNhsJCQmy4SEZG9NCprG+ZT0723cyrX4au9p3sdKykh6hh9kxs7kw5EKEZgEn/p4aXyNEer7X9dTxSeMnNDobCVGFcGXMlcyImIEoiH6TcAnpWSAJOEhhXb29DHdE3UHZ+jI+L/mcrElZ6FXeJOfB7lNBFNhr30uQOojM4Ey0Ki23ZN/CnaF38vHhj8mrzKOyrRK34CZYE0ysKZYDLQc40HLAe35ENZGKSOJ0cZg1ZiJsEUxQTUCn0qFSqnALbpweJy7BRZO9yZuD1FZGZUclTsHpFXhxuwjXhTM/eD7TQ6YTog5BoVCgVCq5IuYKPrZ8zD92/4Pn4p5Do/TWJVpzYA17O/cyPng885Pm+x2TFFZYW1s76PdBeq+3EQzQ1t7G0sKlKBQKrh137YBtSPs6T38eJZoSPq/+nHsS7vGL7BgJdjfu5p0D75AYlMhtk24b9LPhunDquuq8YhknaeJ6XsJ5fFv/LVtqtzA7bnYgFO4MIkwfxjVjr+HKjCvZ1biLzTWb2WvdS0lrCe8ceAez0UxObA655tyTNsaeKC6Pi/0t+yloKCDfkk+zoxkAk8bEnPg5zEucx5ToKQMuLJ8oAQNoAIqLixkzZow8Ieidu6FSqbgr+S7etL7J4Y7DTIqcxFNznzrFvQ5wvEQaIuXkXLvbzobqDSzbvYwjtiOsq1vHBsUGQtWhzI2bS4+lh7HhY+WJk1arJTjEuzLh+9Axm838ZctfKOwoREBgW/22fsMkx4WPA7ySqWeaAdTj6SGvPI/Pyj7jYMtBHG4HGpWGMF0Y5yacy+y42eTG5g47gXMkjIXTLUfqVHqkep9PlVLFIvMiXih6gXf2vMP16df3m4juSySR3Bp/K8ssy/hbwd9YMn6JbAA1WhtptbZiNpt5IPcBttZt5bWi13iu4DnOjT+XHHIwYJDb3lC9AXuPnWmh02hrbvMLe+vv/ERHR9PZ2UlraysKhcJPHhmOGhkmlYnJwZPZ1bGL/1r+S42tBoPawD0T7yHaGe0noe2r5Cjl8plMJsIiw/i08lM+r/0crUbLBZEXMCdkDmqlt+An4Beap1Kp5KKavoaRVKDUt6YQQHNTM7naXIqEIjbUbeDhix8e9FoBbK3bSrunnWsmXcPMCf5FLW+ddCsKFDxnfQ4RkUh1JD8d91OUXUq6Dd0000xBeQG19loquitAARq1BnWTWjZsNOqjxUslgjRBjI8YT3poOulh6aSHpmM5YpHzsXwLgydYE2h0NbKzcyevF73OnVl3YgwzsqZrDQa1gZ9M+YmsvtZ7UUIQhH5lvH3xDSdUqVSyOMbe9r1YHBbOjzp/0HAgSTZbp9Bx5ZgreXPfm+zt3svVqVcPuM1wqe2q5Z+7/olJY+LBGQ/2W3Tbl3B9OC7Bhc1lG7Lk8YmiUCi4M+tODrYc5JV9r5AZkXnS9h1gZFApVcwwz2CGeQYuj4vi5mKvAdGQz6ryVawqX0WQJojJUZPJCMsgLSSNtNC00+I6dzg7KGsro7y9nCNtRyhqKsLhcQBe2fbL0i4jNzaX8RHjT4paYcAAGoDeye79JbZG6CN47bLXTnFPz0xOp9Xw3nS1dpHYlcgfsv9AS0cLBc4CNjRuoLKzklXVq1hTv4ZQbSixuliildFkxmaibFXiETyoFEcNoOjoaCp6Ko6GSYr9h0mmh6UDUNpeygUMXw77ZOESXFR3VlPWVsb+5v3ssOygrL0Ml8c7IQrVhTI3YS6LUhcxLXbaCdWcOBvD106lR6q/8xnviidOG8fq6tVM1kzGJHqNn4FWxKV+P5n0JK+Xvc6KkhXoVXoEUcDSYEHhVsjtz0mYw6TISbI3aLN7M2N1Y7lszGVEREawet9qdKKOMbox8jaDnR+r1Sp7VjQab65Qb4+AIAoc6T5Cs7OZRqfXILsy5UouCLqA9Ajvd8w338Y3+V6j0eB0Oqlrq+PvX/+dio4KEowJXGO+hjGRY7DZbKjVatnQ6i8xv3c4XUJCgnzufZ9zFouFFH0KUdooiuxFONwO9Gq9/EyUlCJra2uxWCwYI4y8UfQGwdrgPqIVgijwesHrvFbyGlqVlkuiLqHcVc6/Cv/FzJCZXBZzGSnqFM6beB6dXZ0oQ5V0qDvodHXi8rgoryqnx92DXq0nLSUNjVJDqC6U9NB0og3RNDU1efuvNhNtjEYRp+j3uR0dHc2DFz7Ibzf9lpUlK9G79agMKrqELn4646eMSxzX5z70NRqdTqd8vP2NCb3H4cLCQtyCm3Ut6zCoDCxK6l/MQ0KpVOLxeBBFkRxTDl8Ef8HOnp3cHN5XoOB4aLY38+cdf6bH3cOjsx7FbDr2d1xaFKrqrJLle08GvqFwHx7+kFsn3XrS9h1gZNGoNGTHZJMdk83tk2+noqOC/IZ8ChoK2F6/nW/rv5U/G2uMlRcz0kLSSA5JJlgbPCreFUEUaOtpo6qjyk/VssneJH9GidKrJGrOJSc2h8SgxJMewhcwgAZAq9XKlcd7D14Sp0tozZlGfxXZTyd8J0YpCSnoLDqumXUNHZoOlu1dxpb6LdicNg47D1OmKKOw2+vhabY3889d/2RazDTSQ9PJCMtgQvgEdjbsRBAFlAplv2GSkfpIgrXBlLeXn+xDHRBfY0d6gB1pPUKHswOb20aPu0dO5J2eOJ1LUy/l3PhzR6xI6ekWvjYSnEqPlKSs5lvxPi4ujkXdi1hWv4zt9u0sClk05ITv38X9jhf3vMjKspX0eHrQhemgw/+ZGKYP44HcB9hj3cNX5V+xx7qH/5T8h9dLX6fOVsfixMUYdUa/mlm9z4+vUSChVqv9jJ9uoZtd7btkZTaFQsE44ziaXc0INgG1QS2rxWVnZ8vt+hbXbGtro9pRzYdNH9LmaOO88POYHzEflUIle518vUe+dHZ29nuvSh4r8H/OSQpkF8RfwCrrKr46+BWp7lT5GKVoA4/HQ09PD+/ue5dOTye/mP4LOVQRvLLyz+9+njVla/AIHuZHzucPi/5As72ZZ7c9y47mHTS5mrguxpvjNXXK1D59t4b0vxBltVopKi3yk+2Xrk9/KmzgXZm+IuwKGtoaeO/Ie3gUHlJ0KUzSH33m+ebU+irLSaIWg3kAfV8zm838r+h/tLnaWBC5ALHHXyijNwkJCfI9Y7VYmambyaqmVayuXM3ijMWDbnss2hxtPPXtUzR2N/KTKT8hK3poIXUZod5cqbL2spNqAAHMTZjLl+VfkleZx/fSvzegem2AMweFQkFaqNfbc/2467G77VS0V8jGR3l7Od/Wf8u2+m3yNiqFilBdKOG6cML0YUToIwjThRGuCydEF4JaqUapUKJSqFAqlAiigEf0IIgCLo+LDmcHrT2ttDnaaHG00NbTRqujlfaednnhF7zGTkJwAucnni8bYKmhqehUulNxqmQCBtAATJo0icrKyj4P5N4rUb05nT0bpwunuxHZu4ikdA9kZWVxTcQ1XGS4iP32/TRoG2QXbntPOyIiNpeNbfXbyG/IB8AtuAnRhmB320kISmB+0ny21m0lXBdOuN77o1PpSA9NZ3/zftyC+6S4fgVRoKPH+/BqdbTKD7EmexMVHRVUdVbh8rhwuB10u7txeryKXDqVjlhjLDPMMzg/8XxmmGeMSn9Pt/C1U8lIhQP29gJFR0dzdfTVFH9bTEFTAUuylhAdMrT2DWoDv8j5BUfajrDTspN/lfyLX+f+mugI/+2l+hbTYqZhsVlYXbmaN4vfpL2nnbV1aylUFzK+azyTuyeTHppOUkiSrAYHRxcjJKMgIjqC/NJ86nrqqO+px+K00NDTgIiISWViXsQ8ckNyCdWG8mr1q3zb/i0TgyaSpE/ye477hq/abDb2dOzhs8bPUClV3DbmNlJIwWg09uvx8S3MCshGTlVVld970uuAn4iOJCU9Tj2Otaq1fFH6BT9J+Im8+ukr9fzVwa8o7ixmVtwszok/R26vsbuRZ3Y+Q3VnNaHaUNSimsUp3ol8pCGSP8z/A28UvcFXpV/xau2r/DDhh5jKjobiHes+Gij0G/wXsHrn76QnpHO7eDt/LPsjlm4L00zTaGxoJDYmVlbMDA8Px2az+dXckfYx1EWPoPAgNrZuJEQdwszQmcfcpvd1n6SfxBpxDasrVnN5+uXHvQrebG/mD9/+gXpbPT+a9CMuSr5oyNtKwh9lbWXHte8TQalQcuOEG3l6+9N8UPIBd2fffdL7EGB0MagNZEZm+pUu8DWK6m31tDpaaevxGi8VHRV4xL65jUNFrVATpg8jyhDF2PCxhOnCSAhOOG2Mnf447pmL0+mksbFRjq2VSE5OPuFOnQ40NTXJK4Rms3lIijUw9MTO7zLHMiJPNQMVkYT/M9gscHXa1URHe+uf7LXuZUXJCnbU70BERKVQYVQbvcpLKEkISsDabUVA4OMjH/fZn0FtwOa00dDdwJ+3/5nU0FTvaowugjB9mDdRWaHyrsIoj67GqBQqREQ8gndFxi26/f7u6Onwrsj8n3Hja+y0O/xXaMA7yXMJLlQKFWqlmm5XNyi8oW1mo5lccy65sblMiJzgN0kNMLqMVDhgf14ggJsm3MQjWx7h7QNv88jMR4YchqBUKJkQMYGqziocbge/3/Z7lkxYwvfSv9fvhNJsMnNJ6iWsLF3JxMiJaB1aarprWFezjm3N3lVJtUJNXFAcBrUBrVKLq8dFh60DtU6NqBapq6yj296NIAgolUqiTFFM0E5gZsxM0jXpJMYlyufs5jE380zxM3zS+An3ptzrN0mWvDAmk4mCrgI+afyEUHUov5z2S6an+Us519bWyvuTwtp8XwsJCfEzdiRvhqRUB/4iOhIaNEwLm8a66nXU9tSSqE+U82qsVitHao6wsnElBqWBC4MulLfbXr+dV/a9QperiwXmBawsXUmKMYUgV5CfV+aquKsQW0RWNa7i1apXucV9C7G6WLkPvuF2ve+twZ7RvsfQm+joaJwGJ/pqPRHaCIo6i/i69WsyhUz5HLW2tpKcnHxUatzHKBvK/W21Wnmr8C0cooPFMYvJSM0Y0na+3iuLxcKFyRey2rKafdZ9ZMdkH3P73pS1l/HXnX+l2dHMbZNvY1Hq4GF4vTFqjJiN5lPm+Z8SPYWsqCw21Wzi8ozLSQoeeqFcQRTwCB55zJG8AtL4p1KoUClVfn8HOPX0ZxRJCKJAp7NTNoo6nB1+8wlRFP28QSrlUe9RuD6cIE3QGadCN2wD6PDhw9x+++1s3brV73VJyaQ/dZwzkerqaoKCgtBqtXLMcX9xygGPz/A5k1b3e/e19/9B2iDmJMyhsLaQSk0lPx7zY1oVreRb8v0GNoPaQJwxjihjFKG6UIxqoxwn29bTxpG2I/R09rC1fiuFzYWjcizSCk2kPpL00HSUKOnx9NDl6qLV0UqTvUl+uImITI2ZSm6sNz43KTjpjHu4nS0MZLgMl4GEBtLD0jk/8Xw21mxkY83GPmpdgyEiYtKY+N2c3/HPgn+y7MAydlh2cM/Ue/pIElutVpYVLsPtdnNH1h3EE099fT1urZsjLUfo1HZiFazUdtXS5ezyqo8JbjRqDRo06AQd6fp0InWRxOviSTQmMjd3LoD8fJYm506nE4PKwAXhF7C6eTWfNH7CH6b9Qe6LJF+/pnoNazvXkh6dzhOzn+hTi2qgZ5VkPKhUKtLT0/utIxQcHCx7OqTvjslkIjg4WPagjBXHslZcS0FHAVlxR+vQVNdV8071O9g9dr4f930MCgPldeV8ZvmMrXVbCdIE8evcX7Pj4A48eJgRMkM+bl91u5zgHIKUQbxveZ9ljcu4LeE2ss3Zg3p4pOOWjsP3f/Dej9XV1YiiSEhI3zy/9w6+h1Kp5KfJP2W9dT2bGjZh327nsqDLwOnNtzqRMbOgvICNTRuJ08dx65xbhz25lq5pnD2OtZa1fF3x9bANoK21W3lx74uIiNybfS/zEucNa3uJ9LB0ttZtpdvVfUzRhJGg93zlxgk38uiWR1lxcAUPzngQl8fVJypA/t/ntU5X57D2G6QJ8oZW6cPl31LYVe9oiACnBqVCSagu1C/M9mxn2AbQj370I9RqNStXriQuLu6snxRJkw5pRax3dWhpIJEGNGk1sL+B4Wznu2wM1rbWIooiCZ4EFk9dzC0Tb6HOVkdpWyn7avdRYi2hoq2Cso6j4Q4mjYm0kDTMJjMJQQk02BrIjc3livQrEPF6YzqcHbgEl7zaJghHY3A9ogcFCnmlTalQyjG70oKEEiUovPU87G47bT1t1HTWsNe6F7fglvsSog0hJzZHTpIcGzaWMH3YKTiTAXpzLIW04TBQmNEtE29hn3Ufbxa/SVZU1pCL0vZ4elAr1aSHpvPM+c+w4uAKviz/kgc3PciSCUu4LO0y2RtksVg40nkEtaAmKyoLpUIpLy5l6DLQarX9etel54rJZOrjafF9Tzo+aV8ej4dzws6hpqeGYlsxT+U9xRUxV5CYmIjZbGblwZV83fI1sYZYvh/2fYQuAfrWhB3wPPY2SqXr4muM+Y4bHo8Hm81Genq6vK1ZZyZSG0mFveJo0W3BzWdtn1HVU8XClIVMNU5lT8sevij7AkErkBuby51ZdxKmD+PNfW9i0phYmLkQjVLjd22lPk4Mmcj9sffz6pFX+aD1AyZnTh6SF76/e07yiEkhgVKfJWo6a9hu2U5ubC7zUueRYcpgY/dGtjZtpbShlOtjrsfM0Ws03HvZ5XGxqm0VSoWSu7LuGpbx03t8ijJEMS12GrsadtHe0z6kiZ9bcPPBoQ/45MgnhOvCeSD3Ablo9vGQFprG1rqtVHRUnJQ8IOma1tfX4zF6aLA1EKQJ4vPSz9nbuBeX6BpwW71KT5gujMTgREJ0IWiVWr+xRxIA8h2fPIIHt+Cm3dlOm6ONw62HZdWv/jBpTCQFJ5Eemk5aaBrpoenEB8WPmgxygO82wzaA9uzZQ0FBARMmTBiN/pw2aLVaOZ5byv/o7fEB/0rVtbW1cvhD74Hhu4D0cB1MzedsxaP1YFAbSI73hoAqFAoSghJICEogvDWcc+LOQa1RE5EW4RUVaPMmJh5uO0xRcxEArY5W1lWvo6S1BPAmDoboQmT3shRKoFaoZS+Nb/iBS3DR3tNOW08bXa6uAfsaog1hcuTko4owoWlE6iPP+sWMM5mREoUYyKMRpA3izil38szOZ3il8BV+M+M3Q7of7G67XMdGp9Jx66RbmWmeydK9S3l7/9tsr98ue4NiYmNoKGlgfOR4v6Kcg0lvg39hUl88Ho+fGIKv8eT7rL5j/B28cOAFdrbtRImSK1RXoEnQsLpjNfEh8dwQfgM6QTesCXl/Rqnv8UgGj9QvyXDweDyUlXkXQSQp7XhdPIWdhQRHBuPyuPj37n9zoPMAi8Yu4qbMm3hx54tsa9xGqCGUu6bdxZz4OSgUCg5UH6CyvZLzE84nPvaozLyv18bPWx0axHP5z/HMzmd4eu7TZEVn0e3qpri5mPquer8k5i5XFza7DbvdjslgIrTLuzLsaHVgwECYOow4XRxTkqb4nZeP93+M3W5nmm6aV+ktLoH7o+9nYuVEXt79Mq/UvsIl8ZdwTvA5x3Uvf3T4Ixqdjfxw6g/9QhWHgu/1Au94HWOPweVysbtx9zE9n5Udlbyw5wUqOirICM3ggdwHhrxQMBDpoV51wtEUQhBFkYbuBsraythn38dB60GahCZcVV5jx+F20OPpwSW6mBM/56iHRvr5Pw+NQT3E1YFjYHfbZc9S79DsZnszFR0VHGw5KH9er9KTGpoaMIoCjDjDNoAmTpxIU1PTsT94hhMbGyur0/g+qPsLiZIGNhh4ovJd8I4M5CX7LuBQOEiMTOz3eP1WW4OjSQpOkkMmpLjbtp42/rrzr9R11bFk/JI+g4PFZvFbVRPxrsAqUcqrcGqlmmBtMEnBSUfVXHoNYmG6MAxqQ8DYOcM4GWGjObE5zEucx6aaTUMOhetx9/SZGGVGZvbxBl079lqyorNQ69VMMntVwXwNG9+aMr3xVQ6Tcml64ysy0HuhymKxsMS8hHfq32F723Y8eg9VdVVolBoemvkQeof+uIxL3++1rzCAFFrmK2UtGUXgL44AEKeLo6SnhCMdR/jXnn9RZi9jonEiZpWZhzY+RLuznfNSz5O9PhKbyjYhCiKJJMqvDeYpnGGewdVjr+b14te5/evbiTXFYrH1zedRK9UEaYK83mSjEptoo7m9mW53t7du0P/VAVKqlOhqdYyJGENacBoRzgg21GwgWhtNmDMMp+JoPxakLCAjLIMX97zIhtYNlHvKuSflHqIZ+j1d1lbGBwc/IFIZydzQuUPeTkLyiLlcLvlapevTcbvc7GrYNeD97hJcfHbkMz46/BGCKHD1mKu5duy1I6J4OVpCCA22BgoaCtjVuIvStlK63d3ye3qVnozwDNmgSAtJ4z97/oPFZuHHWT8e9VA8g9qAIcjQJ0RWQhAFGmwNfvLJ5e3lfYyitNA0psdOJyc2h3hTfGBMCzBshmQASYmcAH/5y1946KGH+OMf/0hWVpZck0HibAn9qqurw2AwyDlAEr0NGavVChwthjdYzPjZbhT0TjI9HRXeRotWRyvpof3nAgwmnOEbd5sRlkGjvZHLMy4/psiAIAry9gECjBS3TryVgtoCXtjxAvGKeLl+iy++97jdbe83bl/yBs2Km8XSvUtZUbKC5QeX0+nsJEQIkQuQSl6dwZ6LvZ+pVqtVzkEB/FTFeocmS/2sqqrih3E/5L2G98iry0OlUPHkOU96E7+DOa5ncu+wt95IUta9vVdSf6W6QhOiJ7Cuax3PFz6Py+Mi2ZBMvaOeNw6+QUxIDPdNu0/2+vhS2lOKWqFmSvRRL0x/C3BN9iY212wmvyHfm2vo7qG0uxSn4PTK0oamkxScRKQ+kjB92IDJzE6PkyN1RyirK0MwCRRZiqix1XCw+SCFTYV0ubro9HQSrgmnzF5GmiHNrx/poen88bw/8tGhj/jkyCc8svkR5iXN4/px1xNliBp0kdDlcfHC3hdwu9wsTliMtdGKOXZo40tvz5wvJpWJJEMSe617cXlcfkaNIApsq9vGipIVNHY3khiUyD3Z95ARljGk/Q6F7rZujG4jBxsPHvvDgyCIAkfajrCrYRf5DflUd1YDoFFqGBM2RpZHHsh7sjB1IS/ve5nNtZu5JPWSE+rLiaJUKIkLiiMuKI5zE84FvMfX2N3oLcvQ4Y2gONJ2hAMtB3jnwDuYjWZyYnPINecyPnx8QHQhwJAYkgEUFhbm90AURZGLLvKXezzbRBAAv+TQ3kXqfAdaSU51sEH0bKxr0h/9DWK+A9BQZVjPJHo8PXS7uwnTh/UJsxiO0RuuDwegvaf9mHUZAobPyeW74MEFbyjcpRGX8lbVW7y872WeTXi2z2TY9x63e+xE6Qe+VydETODZ859lXdU6/r373zTZm1hespxLIi4hW8yW1cB6yywPdq59DRxAllaGo2F0/eW4WCwWUiNT2du9F41Sw9v73wbgouSLTnj12NdLJT3j4GhYdEhICDabze/92tpaBFFgX+M+artq0Sg0xGpjqXPXoRJVLE5bzE1Tb/JbkZfOTWRMJIfbD5NqSMXtOJrLJxllgiiw17qXTw98SkFDAWq1mmBDMNlh2cSFxrHbvhuL08LFyRczKapvbbL+0Kq0eNo8xKnjULlVJEclQxSY482Ud5Tzx11/xC7YsXqsLLMsIyE4gcsjLuf80PPlKvQapYYbJtzAzLiZLNu/jA3VG/im9hsWpixkrHMsWkHb7/Pyw8MfUt1ZzZXpV5KsTe4zLg8ll8nXCJWuSUdHB+NM49jYvpFDbYeYFDlJPnfLDy6nsqMSo9rIkvFLuDz98hGrc+bbN7PGzP7O/cMWQujx9FBoLSS/Id+bx+RsByBUG8r8pPnkxuaSFZWFXq0/Zltz4uewbP8y8iryWJiy8LTzpigVSswmM2aTWTaKXB4Xxc3FFDQUkN+Qz6ryVawqX0WQJohpMdPIic1havTUkyIuEeDMZEgG0Pr160e7H6cd8fHxpKeny/8PpJozVMPmTFI+Gy69w016T/qlEMGhrPaeibQ6vCEtEfoIzFH+94Pv38carCUDqMXREihMd5pxqj24J9MAmz9mPkUdRezt2subW97kexO+169Esslkorm9mSjV4PeqRqnhktRL2FS9iX3sQ6FS8GHjh3zT/g036m9k/uT5fhOugZLvfcPgepdf6OjokIucQv+hyofaD7H24FpmRM3ghkk38ErhK7xS+Arf1n/LXVPuItp4/Oe1v+d7YWGhvDgmKcL5ymHX2+v5uOFjquxVCIKAS+FCb9BzccrFXDPmmn5FSKRzs7dyL0q1kpSgFL+xRxAFvq3/lg9KPqDOVofD7iDDkME5kedw1ayrOFh8EKfTSaoxldcaX2Pp3qU8c/4zQ87v6C3qoNVqvcViW1rRarTcOfFOLk29lHXV6/i69GteKniJt4ve5tKMS5kXPo/2Ru8kPSEhgcdnP05hUyHvHnyXVeWrED0i43Tj+N7Y78kLquAND/v0yKekhKRw87Sb/eqODeV72Z9x6uu5i9fG43K7ONB0gIr2ClZXrqbeVo9GqWFxxmIWZywmWBs8pPMzXMxmM4nNiRxyHhqSEIJLcLGzfidb6rawz7oPl+ANR0wMSuSC5AvIic1hTNiYYS+Q6dV65ifN54vyLyhpLWFCxOmf461RaciOySY7JpvbJ99ORUcF+Q35FDQUsLl2M5trN6NWqMmMzGRO/BzmJMxBq9Ke6m4HOI0YkgF0/vnny39XVVWRlNRXElcURaqrq0e2d6eQqCj/QX0g1Zyz2bAZKr3DTXpP+iWvoEKhQKPRnHVesLaeNgDCdGH9TrwkjjVYh+nCvO052kazuwGOg1PtwT0ZBpivkfXgBQ9y16q7WGlZSboxvY8hYjabqa+vp8fTg9vhPqaBJooiNV01zDDP4Jc5v+TFzS+yrXkbf9/7d1bWrWRh6kLmJc7DoDb0e64HWsWXni2SuMBAHmaH28FLhS+hQsXCoIVMj53O3yL+xlv732J99Xp+vfHXXJFxBRclXyQvRJwo/RVUBmgX2tnTuocva7/E5rGhUWrQK72r9E/PfVrOC+nvnEpttdhbcLldTI6fLL+3z7qP5QeXU9Zehl6lZ3HGYqYHTcfd5sZsNqNRauTtJ5kncVPETbxe/DrLDy7n9sm3D+mYeoc5m0wmqqqq2Ne2D5fbxbyEecQFxfGDzB8w0TmR3a272da2jc8OfcZKYSXnhJzD7LDZ8n08JXoKk6Mmk2/J5+uKrylqLuIfB/5Bck0y58afy6TISby07yUUCgX3TL1HNn6GKqDh2+f+CIsOw9Zmo93TztJ9S4k0RBKsCWZxxmIWpS46YZGDYxEdHc35k89n07ebBhVCaLI3sbZyLWur1tLubEeJkszITHJic8iJzcFsOvHn0rzEeXxR/gU76necEQaQLwqFQg7zu37c9TTbmyloKKCgoYCi5iIKmwpZdmAZ5yeez8LUhSNyvgKc+QxbBCEtLY36+npiYmL8Xm9paSEtLe2sCoHzJWDoDExv47D3pF8iKSnprDyHLY4WAMJ1g0+cjjWJlrZv6WkZ2Q4GOGFO9fd/uAbYQAbJYIaKr5GVFZ3Fjyf9mL/v/Tv/a/4fM9wzMKgNfp+JiImAEogMiTymgeYW3Dg8DsL14QRrg7ltym2cU30Oe117KWgt4LWi13j3wLvMS5zHwpSFfXLmpON3uVxy7g9482l8C2xKxyH9lo7zo8Mf0eZq45LISwhThcnG0rXx1zI7bjavFr7KB4c+4KPDHzHTPJOFqQvJjMg8oVAg33tGEAXWHlzLN63fsN+2H5vHhgIFkZpIzok5B4fTwUH7QUwaU7/XQ2pHavOTvE8QBRGDzUCro5VXCl/xhrop1VyWdhlXj72aEO3/5eMm9d+nhVEL+bb+W76u+JrzE88fVm6L1I6U+1RiKyHKGEV62NGoicT4RAS3QFZQFod7DrO5czMbWjeQ35HP4tjFmK3ea9Pc1IyhycBPx/yU+vh6Vh1eRXFHMSs6V9Bga8DhdjApaBLFtcV0tncitAmIguinsjdUul3dcg5JYVMhxc3FuAU3LsGFTq3j3ux7OSfunBEPdRuMgYQQBFGgqKmIvIo8ChoKEBCIMcRw04SbmJ80f8TrtaSGpBKhjyC/IZ+bJ9582oXBDYdIQyQLUxeyMHUhNpeNzTWbyavMk8PkpkRNYWHqQqbHTA/kC32HGbYB5Oua9qWrqwu9/tixpgFOX443zKa/yeFgiadnG5LH5lh1c441iZZWngMeoFPP6Za3NlwDbCCDpHeOmu/3vbeRdcG4C2hTtLGiZAUv7nmRX+T8wu8zmmANBoOBiJCIYxpo9Y312O12XHaX3/Gcx3k43A6+qfuGryu+Jq8yj7zKPDIjMrk45WKmx0zHqDH28Tz41tWRjCCj0Yjb7Za9EtLxKYOUfFH+BalhqcwMnQlAhbWCV2tepXxvOVNjp/L7c39PRUcFeZV5bK7azLqydSSHJHPJmEsYEzaG1NDUYcsAdzm7KG8vZ3/zflaWraSyvZJudzdGtZGM0AzODT6Xy8ZdRkZCBisOruDggYMU7S8iK8VbckESiTCZTH2ezc1CM0aVkVqhltc2vkaXq4tz489lUcwinK1Oetp7sDL481ypUHJH1h08tPEh3j3wLo/PfvyYk97e/TCbzRRVFdFBB4uSFtHc1Nxv7tWClAV8P/L7rK9ezyv5r/BO7TsUdxVzuflydOjksEAFChaGL2RRzCK2uLfwaemnBCmDaHA08FLxSwBo0BCvjydcG05CRAJVZVWE68LRqrR0tnfS1NKERqehxdaC0qTErXHT6miltquWelu9fCxqpZpJkZPIic2hoKGAvda9J934AW/tG7PRTFm71wDqcnaxsWajHIqnQEF2TDYLUxaSHZM9avmfCoWC3Nhc8irzqO2qJTE48dgbnQGYNCYWpS3iktRL2N+8n68rv2Zn/U72Ne0jUh/JgpQFXJh84XeqAGgAL0M2gH71q18B3i/JE088gdF4NLHM4/Gwfft2vxjsAGce0uToWHV8hpN4Ct5QlbNZAa+152gO0IkgGUBSewFOHb1Drs60e3cgg8T3dSk3r7a2VjYweh/jVWOuoqqziq11W3lz15vkanPl731lRyUAIbqQQRdBzGYzNZYaREHEYetbBFGv1nNR8kVcmHQhh1oPsbpyNdvqt3Gg5QBqhZqJkRPJMeeQG5vbr9KkZMy53W6ysrL8FNnMZjMfHv4Qe4+duaFz5cnjqzWvUtxVjIA3Z+bJrU+ydMFSZsbNZF3+OrY1b2Nv116WHVgGgAIF8UHxsnRwjDEGjVKDVqX1Fi32uHB6nFi6LZS2lXKo9RCVHZV0u7qxu+0oFApCtCHMT57PlWOuZFrMNL+JrE6lw+V20e3slp+/0sKRb+6QdK2s3Vba3G0sK19GhDGCayKu4cKkC4ctwpIUnMS8pHlsqN7AvqZ9TI2eOuh91Z9hfbDTq2CWE5szqCdQrVSzIGUBKaoUXt73Mns79nLEdoQb4m4gIzjDL2+yQdPAlqotTI+dzn1j7+NI3RHsBjuHmg9R2lpKk6eJRlcjJQ0l0HB0H3a7HVE46iFUKBUYDAaUKIk1xTInfo5XDS0snfTQdNmobbA1sNe6l9aeVmKM/tEtJ4MJkRPIq8jjr/l/ZW/jXpyCUw7Fuzj5YmJNsSelHzmxOeRVej1OZ4sBJKFQKJgUNYlJUZNotjezrnoda6vWsqJkBf879D9mxs3ke2nfY0z4mFPd1QAniSEbQLt37wa8HqDCwkK02qPJZFqtlqlTp/LrX/965HsY4KQhrTgKguCndNebY4W7SHk/kiqgJI1+tuX+SEgiCFIOz/FiVBvRKDW097SPQK8CnAiySpcgoFQqz7h7dyCPke/rtbW1g7YhGRnXxV9HXVcdH5d9jDpGTTbZREdHy/e9b+jnQIIoUdFRKI4oCA0J7fM5OOqJGh89nvER47l54s18W/8tuxp2UdRcxL6mfbxe9DopISnkxuaSqklFK3rHoN7Gnu//boObvLI8zCoz6bqj4Vll9jIEvEIKHtFDofWo0ZSVkkW0Lppbo2+lVdVKWXsZpe2llLeVy8nVvRFFEZfgotvdjd1lxyN60Kq0GNQGsqKzmJ80nznxc/xyD3y9jLXVtYiiiIAge7d8BXeka+XxeFi7fS1l9jKUopLpIdO5IvYKdOj6zcEcStjk9eOu55vab3j3wLtMiZoyoBdIeq771sazWCwUdxSDG7CAKdg/J6e/sWJc4jieTXiWz4o/4+2Db/N67evcMuEWsqK9BWMbehp4rfw1gvXBPJj7INHGaJLivHF8SYVJnGc8D61Wy7iJ42h1tMrFW12Ci5a2Ftrb2jHqjShdStLMaWTEZxCsDR7UcyIvPjlOvgFk7bZS3VlNTWcNayrXkBuby8LUhafEGzUpchJ6lZ6ChgKuHHPlSd33ySTSEMn1467n6jFXU9BQQF5FHlvrtrK1biszzTNZMmEJCUEJp7qbAUaZIRtAkhLcbbfdxr/+9S+Cg0dHFSXAqcFqtcox9Eqlsk8BWF8GC3fxLQgIyGEqw4nTPtOo6qgiUh+JXq0/IbUuhULhXQn2uEappwF8Gexa+Urcq1SqIV/LM0kuOyEhYdAJsjR5bbG28OCMB/n1ul/zifUTJiV7ZZP7M/wHEkTRhmgxGAzojLo+n5Mm/JJ3AyBUF8olqZdwSeol2N129ln3kd+Qz+6G3V6Pjt2OSlRhrjSTk5pDWmgawdpgwoVwPy/R69tex+l0cmH8hSgUCnlyn2HMoKirCEEUUKIkzZAmH4OvkZhIIlnRR59dXc4uSttKKWsvo7azllpbLVa7FWu3FbfgRq/SE22IJjs6m5zYHKbHTh8wtMbXy9jj6QERVBzNR5DqyklUVVVR1l3GCssKnIKT7JBsro25FrVKLT+vffsu1aiTOHDgAN3d3RiNRjIzM+XXowxRXJR8EV9VfMX+lv1MiuxfFlu6VlLJB6vVit1lp9pRzTjTOES32OdZP9BYoVAouHLylZgcJt6sepM3D75JG21kujN5s+pNbG4b9068t48yn297OpVOlkaWOU6nhRx+/H+CNieD9p52PjnyCXmVeTg9TkwaE1lRWTw196mT1ofeaFQapkZPZYdlx7Bluc9E1Eo1s+JmMStuFtUd1bx/6H12WHaQb8nn/KTzuX7c9aMuhBHg1DGsHCCXy8Xbb7/NAw88wOTJk0erTwFOAb5iBVLNCuh/QjdYPoJvO1LBvzNt9Xw4OD1OqjuryYnNAU5crUupUOIRz+6cqdMBX0N9oGt1PMpvJ0Ot7USxWq2yR6H3JFt6v7fCVpQhikfOeYTfb/s9r5W9RkZ8hjxZ9FVNG0gQxe6243a5qamvwWq29gnFGwyD2iBPUgRR4FDrIbaUbeFg40GahCZWV66WP6tRakgOTsZsMtPZ3MmGpg2EacJQq9Voo7V0N3WjFtX8JOknvGl9kwMtB0g3pvPEjCfkNpweJ82OZo60HuFI2xEabA00djfS0tNCm6ON9p523KK39o4CBRqVhhhDDPFB8UyImECaNg2T3cQYwxhCdaHy+Za8iQkJ3pVlyUsuiqLcnlap9SuqLREdHc1XB77if5b/IYoisYZY5iTOQafVDWhs974Xu7u7AeTfvixKXcRXFV+RV5E3oAHkKyctSXzXdtciKkSy4rLQarR9vivHyl2bmjKVn6l/xqctn/J1xdd85vwMj8vDFTFXEGLvW1R9tMRIJCP+ZIQf2912VpWt4vPSz3F4HGSEZnDjhBv5ovwL9lr3nnLDY0z4GLZbtg9JlvtsIikkiQdyH+BQ6yHePfAu66vXs6V2C4tSF3HVmKvkWlYBzh6GZQBpNBqSk5PP+qT27yL9Sbb2jiNXBCl4fMvjFDUXMTlyMk/NfapPvRrfCU1wcLBfLaWzkcqOSgQEWf3IN7fCarUOe7BWKVQBA+gk4GuoD2TgHM9kazTkskfaq+SbX1JVVUVtba3fhNs3f893NX98xHh+nPVjlu5byt8L/k5CsHci72sADXTOtEotLrcLh8vhVZnLyuojzjCUc6ZUKJkQMUGW6RVFkWZHM+Xt5ZS2lVLeXu79u72U1u5WWtwtKNQK3m16F3e9G6fTiVqhRqVUoVKoSNGnICLy56I/07Grgy5nF93ubpweJ4Io9Nm/SqnCqDYSqgnFoDagU+twe9wICNR01VDTVSPnoYRVhDE2cizBzmDGGMcQp41DFEWqqqr8JLwBXKLX66tSHPUAFRYWyiIc1apq/mf5H0aVkcuiL2Nlx0oiQiPIGjOwZ733vWg0GmUPUG/iguLIispip2UnLY6WfvMZfZXfpJp4jZ5GlEolQc4gzEn+92eTvemY44XUZo6Qw09X/5S97XvJCM5gZuTMk7pwplF6Q83cgvsYnzx+XIKLtVVr+ejQR7Q724kzxXHD+BuYHTcbhUJBQ3cDuxp3sadxj1zs81SQHuodywaT5T6bGRc+jt+e81v2Wvfy7sF3+bzsc9ZWrWVxxmIuTbt0SIVlA5wZDFsF7rHHHuPRRx/l7bffJiLixJK+A5w+9Cdf3TuO/PEtj/Nt/bd4RA/f1n/L41seZ+mCpX0maGfCKvhIUdpeChwdNE70+JUKJR4hYACdCP0ZDL1V3Xy9G8O5RscyRkZjhXqkv0++RjrQJ9+vv1pe0v8XJF9AZUclX1Z8yeG2wyhQEKo9tnqSSqki2hRNq7v1mF4CURRp7G6koqOCZnszrT2tcq5Hh7MDj+Dxhq4plKgUKnRqHeG6cML0YUyImMDsuNlEG6JZum8pjd2N3DvtXrpd3RSXFdPW00a30I0gCjhFJ62uVto97bQ3tyMqRBC8YTGpQamkhaeRGppKYnAiiUGJpISk9FsjSBAFOp2dlNaVcqTuCM3GZup66mgSmrzyxYLAuuZ1hKhDGGcax3jTeDIMGWjUGgRB8BpxzmZUChUmlfe+9A2PO9B1gA8aPyBMH8YPon+AJkiDu9lNbU0t1lCr/PmBatRZrVYKCwv9nuf9Lc5cknoJhU2FbKzeyNVjrx70/pFzrHAjNApEK6Nlj6rUbn/jxR+y/9BvX9/b9x4N7Q2kmFIQlAKrnav5VeSvjnlfjRSS4dmf0XuiCKLAN7Xf8H7J+zTaGwnXhXNn1p3MT5rvV9R1Wsw0APIb8k+pATSQLPd3CYXCq7w3JXoKW+u28l7Je6woWcFXFV9x3bjruDDpwoB89lnAsA2g//znPxw5coT4+HhSUlLkiYTErl27RqxzAU4NvSck0t+F1kLZO+ERPRQ3FwN9pXV9BRCOxwtyJlHeXg4cHTTg1BfN/K7Tn8HQW9VNyu0ZibZHm5G+n3wnxpK31rft/hZDfI/55ok3Y7Vb+az0M4K1wUOuFzI+ejw7a3dSU1cj78dqtVJdV02rvhWLYJE9OF2urj7b61V6QnQhaJQaOaHdKThps7VxuPUwIkfVv1yCi+qOahKDE9nftJ+MsAy+N+57OJocVHVXsbN9J8VdxbhFN2qFmuzgbMabxjPGOIYoTRRKhZLk5OQhXWOlQkmoLhRNl4YMfQYZem89Ha1WS0R0BPkV+ZS5ytjXvI9dnbvIb88nRB3CrIhZzIufh7vTTb2znlhdLGqFWg6Rq62tpbq7mg8bP8SoNHKz+WbCVeE02htxuV143B4/L31vA6S/6yd9tr/7d3rMdII0Qeyw7BjUAPK9P8qKyzAHmTGqjPK+pPeKmov6jBe9+2KxWDgoHuS9w+8RrYnm9qTbyWvOY3vNdt7Uv8kdWXcc8/yPBJIghoKRrX3T2N3IS3tfoqi5CJPGxE0TbmJR2iJ0Kl2fz0YaIskIzWCPdQ9uwe1nHJ1MJFluaWz7LqNUKJmbMJdZcbNk792rha+yvmo9d2ffTVJw0rEbCXDaMuxv2FVXXTUK3QhwJpBmSKPQVYiAgEqhkmPFe4fPDbSyfDZS3lZOpD7SL9H5RLwAgigEVpZOkP4MBt/8BZvNhsfjOS5D5lQYt8dzPw0lbG6o7fY+ZpVSxS+m/4I1lWto72nn9aLXuX3y7cc0hNJC09hSsYXqrmocNQ4K7YV8VfwVh7sO48HjFUlQ6UgJSfGTm5a8O4PV4fEIHtqd7bQ6WmlxtPBF+Re02FswqA1sqNnA+ur1dDg6cLgciIjolXrGGMYwPXQ6E4MnEqQNwmQy0dbWJhdaHcigGOw8Sbk+4H3+aVVarpztVdMSRZE1O9ewr30fuzp2sbFtI+ua1pFmSKPd1c6kkEl+Rtf+8v0sr18OItxovpFwVbj3HIteoRpURw3XwfLZ1Gq1N/RPrSYqKmrA+1elVJEdk82W2i0025uPmfztcDuo66pjhnkGycnJcrvSvTcuZBy7mnbhET3yeOH7PayqqqKgo4CVTStJDE7k5tibMSgMXB55OR3uDvIq80gOSWZByoIhnf8TQfL8jFSNHUEUWFO5hncOvIPD4+Di5Iu5ccKNx8wjyYnN4f1D71PSUsKkqP5zsU4G6WHpbKvbdsrzkU4XNEoNi1IXcX7i+Xx0+CNWlq7k4c0Pc93Y61icsTgwZp+hDNsA+u1vfzsa/QhwGiMNaL/K/BV/2/83yrrLyIzIlNVqfFfZJY+gbwHJs5XeAggjgTRZGG3OJLWy4dD7uHz/981n6S3DPFRGKwl7pBlJT1V/x6xSqog0RGLSmMirzEOj1ByzenxaSBpuhZv/Nf6PbkU3qloVHreHZGMysxNmMyd9DglBCcc1mVApVUToI4jQR5BBBl9XfE1icCKvLHiFgsYCXt79MvXOepyCE61Si0JUYMOGAwfmODNp8V4PrpTfIuFbE62zs5PW1lbCw8P7zW30PU979uzpo2ynUCiI1kYzP2I+88LnUWIrYWfHToo6i2hxtVDrrEU0iVitViqrK1lev5xOdyfXxl5Lgt7rFRJFEa2oRRAEQmNCh5RH5St+cKz7Nyc2hy21W9jduJuLUy4e9JxbbBacLifKTq/RIH2/pHN4u/l2tBotxc3FTIqc5JcDVFVVxd7Ovay0riQuOI4/nP8HIg2R8vfy/mn389zB53i96HUSghJGPRel09kJQLD2xNVtfb0+UYYofj3l135KgoMhGUD5Dfmn1ABKDk5mK1upt9WTEZZxyvpxumFQG/hB5g+YaZ7Ji3tfZEXJCnZadga8QWcox+1jLSgo4MCBAwBMmjSJadOmjVinApxeSJMpLVoeHvuw92+t1i+hVcop8Hg835mCuL0FEEYCQRROigF0tuVpSRMn6R6Ujmug4zxTDJnjZbQ9VS2OFjyih0Wpi6iz1bGqfBWCKHDrpFv7GEFdzi7WV69nZdlKrA4rPdoeFqYu5Nz4c5kWM23E1ZV6PD3sb95Pakgqz+Q/w/7m/bgcLi6Luoxzw86l1d3KQdtB9nXtY1PXJrbt2cbshtksSl2E2WzuI+MvGTKSZ7u1tVW+39RqNd3d3YSHhxMcHNzvOfc1thMSEqiqqkKpUJIZlElmUCYfWT5ifet6KjoruGvVXeSG5OIW3dQ6apkbPpesYO/kWaVSodPpcHe5USqVsgrfsRYzwsPDZcPtWGRHZ6NSqNjVuOuYBlBbTxsutwsjxn5zyJLNySydvrTPdhaLhV0du/jc+jmxQbE8df5TCF0Ce0r2AEeVCR8KfojHtzzOPwr+wd/m/21EjJOB6K+m1XDpz+vzw4k/HNRz2ZuUkBQi9ZEUNBRwy8RbhhxeOtL41kUK0Jex4WP5y3l/4f1D7we8QWcwwzaAGhsbWbJkCRs2bCAsLAyAtrY2LrjgAlasWHFWTyq+q0gFUk0m04CD/HcF38lGqc1fAOFEEUWRHk/PSSl+d7blKUmGjm/xSBj4OM9WD5jEaBt49V31ACSHJHPzpJt5ZuczfFnxJS7BxR1Zd6BUKOnx9PBl+Zd8VvoZNpeNEG0I8UHxpIak8puZvxm1vpW2ldJkb6LV0UqYLoz5SfO5MPJCnK1O+T7IsGRwjeEadjbspKCrgLzDeeQdzmN67HRmamYSq4v1a1MKa5OQDCLJW9Ta2orNZpON7ZCQEFpbWwkJCZHvzaqqKpKTk2U1NvB6hbqFbmK0MSyJW8L6lvVsbNlIm7uNCaYJXBjhrV+UlJQkK7CplWq0opb6Nu81ONZixnCUOI0aI0nBSUNKgG9xtKBRa4jQR/TJIZP65fu/xEHxIJ83fU5ccBy/nPxLLKVHFy6k7aKjo0kKTuKOrDt4fs/zvFH8BvdNu2/IxzFcJGMyTB92XNufiNfHF4VCQa45l68rvqamq+aUeRUkWfCTWRfpTEOj0gS8QWc4ww54ve++++js7KS4uJiWlhZaWlooKiqio6ODn//856PRxwCnGKkmkFQotbeELXhX7bRarZzAe7biO9noTwDhROh2d+MSXAMWThxJoqOj+72OZypms1m+/3yPa6Dj7J2MHWB41Nu8k2+zyYxBbeDhmQ8zJWoKa6rW8Pye58mryOP+9fez/OByTGoTd0+9mxcueoE5CXOw2q10u/rWohkJqjur+cuOv9DqaCUuKI7fz/k9d0+9mwhdhOzJ6ez0hjt1d3UzQT+BH0T9gJ8m/pSJponsatzFK/Wv8HHDxzjUDlQqFSqVyptz0wutVitLSisUCkwmE1qtFpPJJD8re4cBV1VVERUVRU5ODjk5OUzNnkqntpOJcROZmTCTW+JuQa/SgwKaXE186/6WzCmZft4VgGB1MA0dDfJrvkb/iZIemi4r7w1GW08bao2anIk5Q/5+fV76Of+r+h8JwQncFHUTtkabbERK59r3OM5LOI/pMdPZUruFnZadI3J8/dHiaAGG7wESRIG8ijwe3PggRc1FXJx8MX89/6/HZfxISCHVBQ0Fx93GiSLJoJ+MukhnOpI3aHHGYsrby3l488N8fPjjgJrrGcCwDaCvvvqKF154wa+S9MSJE3n++ef58ssvR7RzAU4NkmyqVEm89wDeu8I4nH0T6t7nQPrfZDLJNTwONBwgQh8xYgbLSIRhfFcZ7v030pPG7xqSARQfFA+ATqXjoRkPMSZsDCsOruDp7U/jdDu5Nulabo+6nUn6SWhUGnJic/CIHjbXbh7xPm2t3cqjmx+lprOGMF0Yz857lrHhYwH8wiNbW1txOp1+dXhidbFcb76eJ3KeICchh8PCYV6ofYGKoAqypmSRkJCASqVCoVDIhUqzsrLIzMxEq9UiiiI2m42srCw6OjrkdiUPY3Jysvyar1GQ35BPp6uT6bHTsdlsbGrdBAr48fgfM8Y0hg31G3jimydosHmNHamtaF00nXTi9DiP69nb+/nmixTSW9Y+uBdIMhr6qxnU+/sliALL9i9j2YFlJAYlclPUTQQpj4Y+JiQkkJ2dTXZ2tt9xKBQK7pxyJyaNiVf2vTJqhnNtVy1BmiBMGtOxP/x/2N12/lHwD/5b9F+CtEE8Putx7pxy57BC3vpjYsRE9Cr9KTWAJE9YIARuaEjeoN/P+T2xxlhWlHifgVJuWYDTk2EbQIIgoNH0DdHRaDR9wgQCnJn4rt5JoUK+8eNVVVWUlZXJA+hgg+mZSn9yrU6nE5vNhkqlwuFyUNleSUboyCWISoPN8YZhBBg6Z5vBfrKp66pDo9TIeYAuwcVHhz+itLUUvUqPTqUjRBdCjDsGwS3I36M58XMwqo3kVeTJamsniiAKrDi4gn/u/icmjYmxEWMZEz7GT8VsKIZueHg4M9Jn8Pjsx3l81uPEGmNZfnA5T259EofeQXZ2NtOnT/ebpFut1j6y/xIqlcrv/urPw7G6cjUqhYqLki+iR9PD9vbtmLVmpiunc7P5Zs4PPZ+azhoe3fIoxU3esgPR0dFkxmbidDnZXbF7yOfJ9zk9mAc0LcTr0a7qqOrzni9tjjaUKPvNzfH9ftlcNv6y4y98XvY548LH8eQ5T2JSHjU0jiU3HqGP4AcTfkC7s51V5auGerhDxiN4qGivIC00bcg5N43djfz2m9+y3bKdc+PP5dl5z56Q18cXjUpDdkw2h1sP02RvGpE2h0uwJhi1Qh0wgIaJ5A1amLKQ4uZiHt38KNUd1ae6WwEGYNgG0IUXXsj9999PXV2d/FptbS2//OUvueiii0a0cwFODWazGZVKhcvloqqqCqfTSVtbm1/dFGkV1WKxnHXhRNKkRpqs+P5vMpm8oTSuWtRq9Ygq5EjhBhG6QIHhAKc39bZ6Yo2xKBVKKtoreHTzo3x05COSQ5N5deGr3DftPhpsDfy3/r9UuarkSb9erWd+0nxqumo40HLghPvhcDv4a/5f+fjIx2SEZvD03Kdxe9yEakOx2CzUdNZQ1VFFp7aTZpqpcdRQ56jD0mOh0dlIs7OZDncHgijI4gYAWdFZ/Pm8P7M4YzFlbWU8vPlhPiv9rE+hTMmzJIoiHo+H6upq2SDyDQf29UBJ1HbVsrt+NynKFMr3l/NJ2Sd4RA8LIhagQIFSoWRO6ByujbyWru4uHl3/KB/u+xCAoJ4gREFkV+WuPgtP/S1IWa1W+Vku5b4N5AGVjNpmR/Og597mtmHUGAeVjq7rquOxLY+xx7qH+UnzeXL2k4TqQuWQwt5G4kDMT5pPvCmelaUrae9pP+bnh0NdVx1OwTnkUOb9zft5dMujVHVWsWT8En4+7ecjLhU9L3EeIiLrqtaNaLtDRaFQYNKY6HaPjsftbEaj0nBH1h3cmXUnzY5mHv/m8VEN3wxw/BxXIdTFixeTmppKUpI30au6uprJkyezbNmyEe/g8fD888/z7LPPYrFYmDp1Kv/+97+ZOXPmqe7WGYG0Ogj4rdBKA7y0imkymejo6MDj8RASEgIMbZX1TECarKhUKr+Ji1arlWvIHO4+jFqjZnrs9BHb74km4gYIcDJwCS6s3VZyzblsrdvKi3texOF0MCd0DkvGL8EcZiYtLI2k4CT+sesfvN/0PvpYPYuiFqFQKFiQsoAvyr9gdeXqYcsbOz1O6m31tDpasdgsLD+4nJquGsxGMyIij3/zOPua9mFUG7k3715cbhcatQa1Rk23bYDJnMJbANOkMhFSG0K6OZ0wXRjh+nBijDF8f8L3+aLsC94qfovDrYe5J/seDGqDn/dHelb6/vYVAZCEZOCotPbq1tU4nU5yo3NpcDSwr2sf6cZ00o3pfm2ma9O5zXwby+uX82bJm6iMKrKTs6EK6nrq+ogfDFQIWEIKyxvI8AjRhaBESZujbdBrcSzFyj2Ne/jnrn/icDv40aQfsSh1kexhSUhIGJYIi0qpYsmEJTxX8BwfHf6I2ybfNqTthoIU6jeYmE2TvYnHtzzO7sbdKBVK0kLT+HXur8k1545YP3yZFjONaEM0a6vWcvXYq9EoR18YpzdKpbKPwf9dZrjCORenXEx8UDzPFTzHX/P/ypLxS7hqzFWnTNkvQF+GbQAlJSWxa9cu1qxZw8GDBwHIzMzk4osHl8w8Wbz33nv86le/YunSpcyaNYt//OMfXHLJJZSUlBATE3Oqu3faIw2egPxFVSqV6HQ6uru7CQkJkVWFpHoPUuz72YJvQdf+lMXq6+spc5URbYgmOTj5GK0NHSncoL+Y+gABThcauxsREKjvquefu/5JmC6MJVFLMKvMWButmGO935PsmGyenvs0T215ihfzX+RA/QHum3Uf8UHxZEVlsb1+O22ONtng7z3BcHqcVHZUUtZeRll7GeVt5VR3ViMgIIgC9bZ6etw9hOpC8QgeWh2tGDVGDGoDmRGZZCgzEAURJUqUCiUqvXeyLm0v/XbixKly0mJvwalysq9pH27B7XfMoijS7Gjmg5IPyDuSx6Xxl5KgSCBOF4dB1X/Oh9PppLa21k9CW6LL0cXWhq1Ea6NJ0afwcePHiKLIRRH9R1GMix3HnZo7ebP2Td7Y+wYXRlxIrCmWCkcFsbH+inW9lQ99PdiSxPRgKBVKQvWhx0yA9wiefiV/RVFkZdlK3j3wLkaNkUdmPcKU6Cl+nzkelcKZ5plkhGawpmoN14y9ZsRyLwubCgEYFz5uwM88tuUxttVtQ8RrlGpV2lEzfsB7DS5OuZjlB5ez07KTc+PPHbV9DYRaoQ4k8vtwPKUjJkZO5E9z/8SzO59lRckKKjsquTv7bnQq3Sj3NsBQOK46QAqFggULFrBgwehXaB4uzz33HHfeeSe33eZdIVq6dCmrVq3itdde4+GHHz7FvTv98a2DodFo5MG0p6cHOKoIJ332bJJSlpAGZ6vVSm1tLYDfqo9D56C5tJkcTQ5NTU0jlkciGUAnQwUuQIDjpaK9ggZbA53OTrKjs3kg9wGELqHfZ0FCUAI/Mv+I5a7lbKjdQNu3bTyQ+wALUxZS2FTImqo1XDfuOgRRIL88n4quCiy1Fuqd9bQILajU3gm22+XGgIHMyEzGRI9hffV6HG4HC8Yt4ObMmwnVh6JUKCltK+XRLY9yTuQ5TGZyv3lG0sKO73sKhYKkKUlYLBaMRiNNnU3ownQoTd56O832Zmq7all9ZDU19hreqXyHcE04OoWOKH0UMeoY4nXxxOviSTWlEmQIoru7u4/hI4khfFz0MT1CDzNCZ9AtdFPcVUyKIUUueCr1SRRFjEYjNpuNIHUQt8bdypt1b7K6cTXxhnicGifdOn/PVm/jQlrM0Wq1gHfh6lir2GG6MDp6OgZ8H5CNAV9cHhcvF77MpppNJAQl8GDug8QFxcnvn4j8vEKh4PL0y/nn7n+ysWYjizMWD2v7/vAIHnY37iY1JNUvZ8yXDmcH+ZZ8v+MtbSs94X0fiwuSLuCDQx+QV5F3Sgwg6P8af1c53vlOtDGa3835HS/ueZFt9duot9Xz4IwH/eooBjg1HJcBtHbtWtauXUtjY2Mf4YPXXnttRDp2PDidTgoKCnjkkUfk15RKJRdffDHbtm3rd5uenh55cg/4Kfh8F/ENmZC+8P15QaTPns1J5AMV0sxvyMfldjFWN3ZEC4m29bQRrAk+JeEOAQIMhW5XN0v3LsXmsnFh8oU8PPNhtCotGPrWe5FIT0jnNuVtbHVsZUPjBh7e/DD3TL2HSH0kbxe9TX5pPhbBQqezE5fbhSiKhKhCSDekM3vsbNJD07HX2tEKWjQaDWu719Lh7OC6cdf1KRYpeW66O7sRg/qfvPVnFImi6FfbR40aVaeKrFR/z/YNiTfw9YGvWV6/nB5PD4mmRByCgwO2A+zv2o9CoUCj1JBhzGCcYRxjjWMJUnvVzpKTk1EEKXhk8yPstOxEr9CTZkhjd8duPKKHGSEzAPzC3wC5ZhCAQWXg5vibebP2TWqcNShUCgoaCkgNTe3XuOidz9ifuEt/xohGqcEluvo9fxJqpdovRKq2q5bndz9PaXsp02Omc9+0+/rkxpxoAeYZcTMILQ5ldcVqLk+/fND8o6FQ0lpCl6uLRWmL+n2/sqOSv+78KxqVBpfgQkREpVAxKXLSCe13KITqQjkn7hw2126murP6pNeW8Yiek1KU+0zhROY7BrWBX+T8gg8Pf8j/Dv2PRzc/ygO5DzA+YvwI9zLAcBi2AfS73/2O3//+9+Tm5hIXF3daxTM2NTXh8Xj6hATExsbK4Xq9+dOf/sTvfve7k9G9M4b+VhDP1oKRx6K/VZ9vKr9Bq9CSFpQ2Yt4vURSp6awh1hR77A8HCHAK6HZ18/T2p6nqrCJcH87DM/7P+BkA3wn51ClTmcpUUspSWLpvKT9b9zMUKOjs6cTabSUzKJNrJ1zLxMiJdFV1YVAYUKlUZI/N9ral8La1y7WLb+u/ZVbcLG6eeHOf8UeakIeHhaMSvZM3Kd9ECu0dCMlQUCqVuFwu1Oq+w2N0dDSTaydzt/pu3qp7i7qeOm4w30CqIZUGZwO1PbUc7DpISVcJB7u8Y06iLpEJwRPoaezhP5v+Q1FXESIi3WI3b9W/hQoVJpWJCUETCA8Pl3MrJXwNIoXCm6t0b+a9vFj5IkXNRWyo3sC1467t17joHX7n+zwbyBixWq20t7bjUDgGPV9KhRKP4EEQBVaVreK9kvdwC26uGnMVN4y/oV/j5ESjBjRKDRcmX8jHRz5mr3Uv02KmHVc7EvmWfOBo7R1fDrYc5E/b/4RbcPPwjIf5quIripuLmRQ5iafmPnVC+x0qC1IWsLl2M6srV3P75NtPyj4lBgpxDHB8KBVKrh93PcnByTy/53l+/+3v+eX0X45qKGWAwRm2AbR06VLeeOMNbr755tHoz0nnkUce4Ve/+pX8f0dHhyzuEODs9/Ici97H3+Zo43DrYTKNmejUuhE7N032JjpdncwOnT0i7QUIMJLY3Xb+tONPHGk7QpgujMyITLTqgY0fOLrav79qP81tzeRb8iltL0WtVCOKIiIiwdpg1KKayzIu49rMa4Gjxk5vb3OFp4Iv8r8gJSSFe6be0+8EW3otKDiI7PRsb3tWKy7X4N4MX6TPdnd3Y7Va+/2Ox+niuC3hNt6se5P3Le+zJG4JY41jmRo3lbkdc+n2dHPIdogDnQc40n2EmqYaVltXU+WoksOKRETK7eXEG+OZETIDtUJNR0eHX1SFr9fdV5BF49Lw6xm/5t6195LfkE9RU9ExjYva2to+dXakz/saqxaLBbfH3ScPqjcGtYF2ZztPbHmCI+1HiDPF8dOpP2VCxIR+P38i4W++SAbQtrptJ2QAuTwuttRuIcYQI0t/SxRaC3k2/1kUKHh09qNMipzEVWOv8vvMSB3PYIwLH0dKSAobqzdy44QbT7jG0FARRZFud7e3KG+AEWVW3CxijbE8vf1pnit4jp9P/zmz4wLj/qlg2P5jp9PJueeemnjUYxEVFYVKpaKhocHv9YaGhgEHBZ1OR0hIiN9PgAADsatxF2q1mkmhk0Y090lSIhrJukIBAowEHsHD3/L/xqHWQ1ycfDF6tV4uljkQgihg1VpZ3rCcf1f/m/dK3qPeVs+58efyQM4DvHHeG/ww7ofEGmKxiTZeOfTKoHVnGrsb+c/u/xCiDeHB3AfRq/ufmKmV3jU9p+eot8disQy55lDvkO7+pP2loqhxhjh+k/Mbgo3BvFf/HrWOWtl7o0NHlimLG+Ju4KHUh/hB3A/IDslGwVGPlQIFOqWOHmcPE0xeo8HXW6NSqWRxGcmTExISIhtF4yPGc/vk23ELbn6/5ffU19f3CX/rja9Etm+tHl9vkMlkwi26BzVwBVGgwdZAZUclh1oPcVnaZfxl3l8GNH6kczkS5RJijDGkhKSwq2HXCamU7bDsoN3ZzoLUBX6exD2Ne/jLzr+gUqh4bPZjcrhbb3nxk1H+QaFQcEnKJTg8DjbXjHzx4IGwuWy4BFdAkXSUSA1N5bfn/BY9ev605U+sLF55zG3OxnqLp5phG0A//vGPeffdd0ejLyeMVqslJyeHtWvXyq8JgsDatWs555xzTmHPApwt5Dfko9VouXrm1SO66lfeXg4w5FoUAQKcLJYdWEZhUyEXJV9Edkw2MLBkcHtPO58e+ZT7193PS4dfolaoZXbCbB6Z+QivLHyF+6ffz5yEOdhabOQE5XB34t3kxubSbG/m9q9u55Ntn1BbW+s3sWxobOCP6/9Il6OL+6bdR7Rx4O9diNa7gNXhPJrLKdU16w+j0T9HRalUolKpUCgU/RY3Ba83SqVS4fF4UHeq+fnEn4MC3mt4jw63fw6pKIqolWrGGMcQFBJERngGGaEZ6FV6grXBdLu6sTqtFHYWYnVa5T5otVq5jpCv50dS3JSePTdOuJEEYwJlHWVsatzkNxn3lfOX2pMm7VVVVX7H5VsXyGazYfPY0NG/UlVdVx2/3fpbCpsKUSvV3Jt9L7dOuvWYylaD1R4aLjkxOXS6OjnUeui428irzEOj1DA/ab782k7LTp7NfxadSscT5zzhpwzX2+AZyeMZjDkJczCoDeRVjlzx4GMRqEl3YgzFWEkMTuRm880EKYN4Zf8rx6z5dLbVWzwdGHYInMPh4OWXX2bNmjVMmTIFjcY/Yfu5554bsc4dD7/61a+49dZbyc3NZebMmfzjH//AZrPJqnABAhwvTo+TQmshEyInEKQNGtG2y9rL0Cg1JAYnjmi7AQKcCOur1vNF+RdkRmRy2+Tb+OTIJ4C/oS6KIodaD7G6cjXb6rfhFryFSK8Zcw0XpVzUr9qRFGo1yTyJedPn8ZtNv2F91Xr+Wf5PLoq6iEtiLpEnlp8f/JzSrlJmRszsI6fcmzBdGIBfBXtfVcfq6mp5EqlSqXC7/cO8QkJC5Fpf4PXI9Jcj01tY4Kroq/iw8UOW1y3njsQ7iAiLwGazyXlHRV1F7GjZwcyEmTw04yGamprYU7mHvxz5CypUFNgK2GPfQ5I2iUvTL+Wi8RfJ3iyz2Sz3W61W+6m4NTU1sTB0IW/Y3uBT66dcPP5iOTTLZDIBYDKZZPVOX5VP3+PyDfUVRAHbQRvjQvxlod2Cmy/Lv5RzfWaaZ3Ko9dCQvQQjGU6da87loyMfUdBQMKjXaSDK28s52HKQeYnzZKM535LP3wv+jklj4onZT5Ac4l/ioHeI4UDHM9KhcXq1nvmJ8/my4ksOthwkMzLzhNs8FlINqIAH6PgYqtjH5OTJ3MVdLGtcxkv7XkKBgguSL5Df972XzlbV3VPJsA2gffv2kZ2dDUBRUZHfe6eDIMINN9yA1WrlySefxGKxkJ2dzVdffdVHGCFAgOFS2FSIU3D2mzB7IoiiSFl7GSkhKfKkJ0CAU01JSwmvFr5KtCGaX+b8Eo1SQ1lbGWqFmuTgZARRYFvdNj4t/ZTKjkoAMiMyWZiykBlxMwZVM/SdPFqtVr4f+n0sIRZKO0vZ0r6FcrGcq4KvIkefw7q2dYRrw7l18q3H7LNGpSFYEywXFe5vn3v27JENHN+aX5KHRXpNMhx6Tzh8ZaWlY5jOdBrdjWxq3sSnjZ9yvfp6goKCcDqdlHWX8XHDxxjVRq6Jv4aioiI8Hg+1XbUYVUZ+PfvXRBujyavMY3v9dl4rfY2Paz9mUeoiFqUu8gtRkxThJHn+qqoqpoVM4+vmr6l31rOiegXXBF0j5zFlZWXJ9dosFoscUjfYREofqkdn0BEfHg8gX+f3St6jobuBOFMcd0+9my5XF8/sfMbP2DxZpIWmYVAbONx6+Li2X3FwBQDfS/se4A17+/uuv2PUGHnynCf7VVwbqsFzokp3/bEgdQFfVnxJXmXeSTGAZA9QoCbdceH7DBlMdl66pybaJ/K7rb/jpX0voVFpmJswF/A3pHw9vwFGhmHPttavXz8a/RhRfvazn/Gzn/1sVPdxMhIgA5xeFDQUAN7wi5Gk2dFMp7MzkAgZ4LSh29XNv3b/C5VSxa9zfy3XpiprLyMxOJHi5mKWH1xORUcFepWeBSkLWJiysM+q+bGwWq1UVVWhRMmdiXfybsu71HbV0uPpYdmBZbyw5wUUCgV/nPNHkmOH1naEPoIme5Pfa2VlZbS2thIeHi6HgvkaAJJBJHlszGazbGT0ZiBPQExsDNYCK3s79pKmT2Oqayo1jhpWWFagUWr49bRf42h1yGUFGj2NaNQaMsIzSAhKIDMykzZHG+ur15NXmcfbhW/z/t73mR85nwuSvKvCLpdL9mBJoTBapZZLoy7lnfp32FK9hfEJ48kMypT7N1TPhYR07sL14exp3CNfZ4PawJLxS7gs/TJ0Kh1lbd68xf6MzdFGqVCSFppGWVsZgigMSw57f/N+9lj3MCd+DqmhqRQ3F/PX/L+iV+l5fNbjA8pNDzTm9zZ4RmOlPiEogcmRk9lRv8OvePBoIRm1kkc1wPCQvmPS4kNtbe2g88UoQxRPnPMEv936W57f/TwapYZZcbMCXp9R5sRE9L/DBOIxv1u4PC52WHaQEJTgV9hvJJAmEgEBhACnC+8ceIcmexM3T7yZ1NBUAJrtzVhsFio7KvnTjj9R21XL5emX85+L/sOPs348bOMH/EUGMpMzeWz2Y8QaYxFFkTHGMTR1N9HZ08lbB94i72Ae+/btO2YScJQ6iqqWKirqKuTXWltb/X73Tv7v3Sdfr1BVVRVlZWX9igf4EhsTy5MXPUmEKYIvm77kQNcBltUvQ6vT8sC0B9B0aTCZTHI+jt1oJ0gfRJzp6PMkTB/G1WOv5l8X/otFkYsQRIHPGz7n6X1Ps6dtj5z0L9UrkvKVpodMJ8WQQoezgy8bv0QQBb/wtmOtHvvmLFS0V9Dj7mFD9Qb5Ol+RfgX/vvDfXD32ajnXx2zyTsqqO6sHvR6jRXpoOg6Pg3pb/ZC3EUWRdw+8i0qh4vvjv09JSwnP7HgGtVLNo7Mele/1/hhozO+dCzSU8308LExdiFt0s7pq9Yi22x/SNZWucYBj01/ej3RvAMecL8YYY3hy9pOE6EL4165/UdBQMGr3UgAvAQPoODlZCZABTg+21W+j09nJhckXjnjbUiLvsZS1AgQ4GRRaC1lTtYbJkZO5KPkiwFvk8qlvn6Kuq44uVxfzk+bzjwv+wc0TbyZYG3zc+5Keo8nJyURHR5MUnMSjsx/FoDawoX4DIeoQLoq4iGZ7M/8p/A8vlL/A1rKtgyYZh7hDEAWRgooC+TO+4dmSQeO7/9596i2c0NraKq/kDkaQNoh7c+6lU+jkpZqXUKgUPHbuY5i6TTidTlpbWzGbzYiiSHFdMWaduV/vhUap4arMq/hl+i+5KOoiujxdvF/3Pi9Xv8yR7iN+yfBKpRKtWssPJvwAo8pIub2cXW27/Iw23/PV37mTJvf7Kvfx6t5Xqe6opqKpgqmmqfzU/FMuib6kz3U2aozEmeJkBcuTjSTEIQnIDIXNtZs53HZYvq+f2fkMAI/MfISMsMEXoHqP+dJ5lN6zWCwDnt+RICc2B7PRzMrSlX4iH6NBeXs5MYaYE/puf9foz0CWDJiEhIQhzRfjguJ4fPbjGDQG/l7wd3lxdLT4rivLBRIOjpPven2c7xqrK1d7FYMS54942wUNBYTrw0kOHv4KeoAAI4ndbWfpvqXoVXrumnoXrY5WPjj0ARurN9LQ3YBJY+KPc/94TDECiWOFCkuvSZOG6Oho0kPTuSnzJh7Z9AhOwUliSCL3T7ufZXuWsa56HW/VvcXGpo3MCJ7h5+mQmJIwhS8av6DWXkuqLpWqqiqMRqOcPwNHPUGDPcejo6Pl0LmhIooiHc4OXAoXTpxcnXk1k6MmYxWtfuIDdo8du9uO0W0csC2pb7OYxZV1V7KieAXbW7ezrH4ZaYY0FkQuIMmYJOcjLcpcxIb6DayrW8dK60qygrNQK9TyuZUMOCncTwrZamxspNJWyfa27RxwHKC2q5ZgdTD3Jt1LjC4GRH/BBF/SQtPYWreVblc3Rs3AxzIaSN4Ja/fQJm8tjhbeKH6DUG0oV6RfwZ93/pluVze/mfkbxkeMP+b2/RUI953w9v77WGFPw0WtVHPD+Bv45+5/8snhT7hl0i0n3GZ/2N126rrqmBE3Y1TaP1sZLFxtOPPFpOAkHp7xMP9v2//j2fxn+dPcP41ayONo5KudSQQ8QAECHIPy9nIOtR5iTsKcEVd/q+uqo85WR05MzmkhIhLgu82nRz6lyd7EkglL2F6/nfvX38/66vWMjxhPakgqE0InoLAohrxiOJRQ4f4+U9BQQJQ2iiR9Ep9Vf8YTa57ggvgLWHrZUs5POZ8qVxXLG5bzfO3zfHrkU9p72uVtp6VOQ6vRUmU/WlfI1/iBvvLXAyGpp4FXNU6Spu6PZnszf97xZ17e9zIZoRmMCx/H+qr12N12oqOjSU5OlleBPVqvEWJUGI95Lq1WK+2N7VwafSm/SPsF04KnUemoZFnLMtZ1r0NQCZjNZhQKBbeNu40wTRh1PXWUCqWyXLcUeudLRHQE66vW89vtv+WlypfY27mXidETiTHGkBOaQ6w+FoVCIavd+fZHWjWWvTAd/XthRnOFWcpPGUoOkiiKvLLvFWwuG3dk3cHrxa9T21XLTZk3ydLuw8XXIyT9bTKZZIVAOGoIjdQ5mB0/m7TQNL6u/LpPnttIUdlRiYAwoNR9gP4ZyXC1MeFj+MmUn9DiaOFvBX/D5Rl6Iefh8F2PZAoYQAECHIPVld6Y64UpC0e8bUlYIdecO+JtBwgwHFodrXxR/gWR+ki+qf2GZQeWEWWI4pGZj3Bv9r3Y3DbMCvOQQsGgr1z0QPQehJvsTexq2EVubC4PjHmA2WGzqequ4skdT7KzYSc/m/Yzll6ylB9k/QBU8O7Bd7ln7T38Z/d/KGkpQa/SE6+Op9ReikvwThzCw8PRarXyxLS3/LUvZWVlFBQUUFZW1q9QQm9EUWR91Xoe2PgAe6x7OD/xfJ6b/xy3TLqFdmc77+97v0+oVF1bHQBBqqB+J8i+hoNvPlKwMphr4q7h9zN/T1ZUFttbtvNa02vUit7r4WpzcUXUFQgIfNLwCUql0q9+UEJCAh10sMm2iV998yv+U/Af2sV2ZoTN4O7ku5kTMQe9Vs+CSQvQaDSIoohKpRrQ8yFNkgcK1RnNXNkwXRgKFENSodtQvYFdjbuYEz+H0rZSdjXu4ryE87g8/fLj3r/vhFf6W5JQl4zloeZ/DBWlQsmNE27ELbj5oOSDE26vP6RrGTCATi3zEuexOGMxh1oP8Wrhq6NSA+q7nmN0XCFwdXV1bNmyhcbGxj6Vs3/+85+PSMcCBDgdsLlsbKndQkZoxjFjxI+HgoYCdCodkyMnj3jbAQIMhw8PfUhDdwMd6g5aHC1clnYZSyYsQafSsalmEwBphqEX6u0tFz1QOFzv8JA1lWuw99jJDMkkPDicO+PvJKssi1Wtq3h7/9vsqN/B3VPv5oYJN3DNuGvYWb+TvMo8NtduZnPtZlJCUogIjqDMVkaZvYyJIRMJDg6mo6MDQRD69Wj49stXMCE93TsJlMLXpN9Sf5vtzby872X2WPcQrg/n59N+TpIyifKScrJisrw5G+UrSUtKAwuyIdPh7EChUBCq9arrScVJOzs75Um0VINIUqSTDDCVSkVWShZTkqewrmodb+1/iz/t+BMXJF3AZTGXMV+cz6auTZS1lfGl/ksuib6E6Nhodlp28nnp5+xu3E2XowsVKkI0IUSERHCo4xAHuw5idVnpdHeSdziPcaHjCHYFMzV6qp/Smm+ojynUW2voSNuRfu+B0VSxUilVhGhDZMnmgTjcepj/Fv2XCH0EmZGZvFr4KmPCxnDXlLtG3Ovue7zSPVJbW3vMRYDhMCVqCpMjJ7OpZhOXZ1w+oGrd8SJdy0BR7lPPjRNupKqjig01G0gOSeZ76d871V06qxi2AfTGG29w1113odVqiYyM9HuAKBSK76QBFJDEPnvZVLOJHk8PC1NH3vvT6eykpKWEXHMuGtXANVMCBBht9jbu5a39byEikhmRyU+n/tSvwGRxczFul5tUQ+oxQ8EkfCeDktw14JcbAfg9O92Cm3VV6whWBJNhzJCNkCuir2C6ZTpvF71NvjWfBzc9KEtvn5twLucmnEt1RzV5lXlsqtlEp7MTi9PC/xr+x+XC5ThEBxrB+x2TPBq9vStS/8LDw2XJbDhq7Pjm8CiCFKypXMPXFV/T7e7m/MTzuWXiLQRpg2TpW2ujlRvG38DfdvyNbR3buD7qetm4cuPGYDQwefxk4oiT25beV6lUslfMt5Cr73lTKBRclHIRU2OmsnTvUtZXr2evdS83JN/ATTE38efOP/Npw6cUdxVTd6iOjp4ORIWISWPCpDGhFtXEBMVgVBvRB+vptndT5ahCq9BS1lbGVutWHG4HunId8bp4JkROYF7aPOYlziMrOku+zhmhGeyz7sMtuPvUMRvtXFm9Wk+Pp2fA95vtzfwt/28oULBk/BJeKXyFcH04D+Q+gEalocvZRWVHJV2uLpweJy7BhUtwoVaq0Sq1aFVajGojqaGpQxIE6C9PqHfNqBNFoVBw44Qbeeybx1hxcAUPznhwRNoF8Age9lr3khoytOMNMLooFUrun34/j215jGX7l5EYnMjU6KmnultnDcM2gJ544gmefPJJHnnkEZTKQAQdBBLJzlZEUSSvIo8gTRDnxp874u3vbtyNgMD0mOkj3naAAENBEAVWla3iX7v/hd1t5/rx13P/9PtlqWOJ/c37CVGEEKQM6hMSNRC+E3dpgg9HvSDSyrjvs7OktYR2ZzvzY+ejVChlIwSgxdrCgvAFTAqexEbHRr4o/4Ivyr8gKyqLmWEziXXGsjhuMTdl3sSmmk38edufqXZU83HDx3zR9gWxyljGG8czM34mgOxV8V3E8y0W2vtYBFFg0+FNbK/fzuEDh1Fr1MSHxnPftPuYHuv9DvcO+5sUNYkxkWMo7Cpkfsd81P835IZFhEEXtLe1o3AoZJEGjUaDy+VCp9PR09Mjhxr6nu/Ozk4/ozHKEMVjsx5jXdU6Xit+jT8U/AGDwoBbcGPHzp6OPeiVemK1sUwOmcz0hOkY7UayU7JJMB81ZNeXrKdmbw3zI+dT5CiivLkcQRSwe+w0O5s53HKYQ+2HeLXgVeYmzOXqiVeTGppKrjmX90re42DLQSZHTZbPw8lYFFQpVHiE/kMTezw9/C3/b7T2tHL75Nt558A7dLu8xuqbxW9S1l5GY3fjkPcVbfAKdKSFppEelk56aPoxjYTR8oCNCR/DLPMstlu2U9JSMiQRh6FQ0lpCl6uLRWmLRqS90x3f+mCSt/d0w6gx8tCMh3h0y6P8c9c/eXrO0yNeiuO7yrANoO7ubpYsWRIwfnwIFKs6OyluLqbOVscV6VegVWmPvcEwyW/IR4FCnjwFCHAyaexu5N+7/83BloPYXXZmxc3ioRkP9flck72Jxu5GZsfOPq6E2cFyH3o/O/Mt+QBcOvlSUkNT5VwYKdHcYrEwO3k2l0ddzoGWA+RV5LHDsoMd1TsIVgYzq3kWN82+iYUpC2lrb+Otg28xNXQqepOePQ17qLJXsaZ1DekN6cS6YhlnHEeiLhGdRif3pzdtjjY2125mdeVqKpoqABhrHMus8Flcd+51cmiYr6Hnu+J/RcYV/Hv3vymllCxtFiaTifaadnp6erBYLBiNRrkWkcvlzVnyFW2QPGaS4Sh91mKxEBkVSVVHFbsad5Fvyaezp5N2dztWwYoCBSpUmFQmEvWJ3Jl4J+kx6dhsNpxqJy3WFj8D6IvSLwCYET6Djw59JNccEhFxiS7+OfefrD68mm0t21hfs55vm79lXPg4ZppnIooiBQ0FsgF0shYFRcR+ZcRdHhfP7nyWktYS0kLS+OvOv9LW00a0MZo1VWtQoCDOFMfchLmkhaYRrgtHo9SgVWnRKDWyJ8jpcdLW00Z5eznl7eXstOxku2W7vJ8YQwyz42ezIGUBMcaYUTvO/lgyYQk7LTtZfnA5vz3ntyMSzicX+44d2WLfpyu964OdrsQFxXH/9Pv5y46/8Gz+szw156mTrrp4NjJsA+iOO+7ggw8+4OGHHx6N/pyRBCSxz07yKvIAuDjl4hFv2+62s7dxL2PDxxKqCx3x9gMEGIz9zft5ruA5upxdcgHe68dd3+9n91r3AjA+dDz0v9g+KGazmerqakRRRKFQEBYWRkfH0TomksdFmkRH6iNJCUkB/CfSUrJuWVkZVVVVhIeH84ucX3Ck9ggrD65kZ/tONrZt5Ju13xCqDSUpOAmH4OCI7Qg/i/kZt829jW/Lv6VGUcPBroNsa9vG1tatKBVKYrQxJBgSiGqNIiIsArVBTWVnJWXtZbQ4WgAI1gZzXsR5TAuaRrgmHJVKhVKh9Aul8z1miVnmWbypfZNvW75lyQVL2LN7D4JHwOP2IOA1MsLDw7HZbJhMJjlXCZAXGp1OJyIiVqeVZrGZqu4qWhWtNHzVQI+nB1EUsfXY6HR2EqWPIkwZhrXHSqurlSR9EjaPjTfr3uROzZ1kJmf2WbBr7G6k3FnOhKAJBCuDGRcyjl1Nu/CIHlQKFVNjp6JVackOziY7OBtXiIv8zny+qf2GQ62H6HR2sq1uG7dMvAWFQnHSFgU9gqePt9LhdvD4lsfJb8hHpVBRYC/A4XEwPXY6izMWkxaaRlpoGga1Ydj7c7gdVHZ474vStlJKWkr4rPQzPi/9nGkx01iYupCp0VNlo2w0DcH4oHjmJ81nXfU69lj3MC1m2gm3WdBQQIQ+grSQ70b+T+9w19OZ7Jhsfjjxh7y1/y3+vfvfPDTjoYBy7AkybAPoT3/6E5dffjlfffUVWVlZaDT+uQvPPffciHUuQIBTRbO9mZ2WnWRHZ49KNexvar/B4XEwL3HeiLcdIMBgrK5czetFr6NRangg9wHeOfAOIdoQzok/p9/PFzQUoERJpDMSp2f4k7no6Gh5IqjRaEhPT5fzZHzbqu2qpaG7gUtSL5EH9v4m0r1Xbe0tds4LO48Loi+gJ7qHvda9lLWXcbD1IB6Fh8Pdh/lz+Z+Jb4onxhiDVqUlOTiZIEUQ1W3VdIldlHSXsLdzLx7RAzXeBHutUkuMMYb00HRyzbne76oNGuu9YVNSHpR0bFLejslk8qtrpFFpuDD5Qj458gn7rPsQRRGD0jv5tnvschFY8HqRmlua6fJ04VA4MMYZ2Ve3jxJrCXX2OlnVTqvVEmYMY1z4OLQeLYWNhXR7ukk3pHON+RrOGXMOu8p28bvDv6PcUc6UoClYeiy8Wv0qPxJ/RFaKv/JTXkUeKrWK2RGz8Xg83G6+Ha1GS3FzMWNDxvLDyB/KIYNarZbs9GxmMIObJtzEf4v+y6qyVexu3M1npZ+xOGPxSVsU7HJ1EWuK9f7t7CKvMo+X971Ms72ZYG0wWVFZlLaXclHcRTw669F+vUXDQa/WMz5ivBxyJogC+6z7yKvMY3fDbnY17iLGEMOC1AXMT5o/oCE4UiGC1427js21m1l+YLmf4XU81HXVUW+rZ0HKgu/MxPp0DXsbiMvSLqO8vZzNtZtZV7WOi1IuOtVdOqM5LgPo66+/Zvx47wOgtwhCgABnA2ur1iIgsCBlwYi3LeUW6VV65ibMHfH2AwToD7fg5o3iN1hduZpYYywPzniQDmcH9bb6AcM8ezw97LPuIzMyk/SEdL/JXO+kfClfJSEhoc+kTlIyc7lc7Nmzh5CQEPl1iZKWEgCyoo7m4PhOpKX9SXkyRqPRL+cmPi6e6OhoZsZ5c3xcgoud9Tt56tunCNGHEGWIosnRhMvjDW1yC27UBjWxqlgSlYkoPUqUbiXBxmDQeAtntvW0Ud1VTfWRaj4+8jEAwZpgwvRhhDvCCa8NR+PSIHaLRAdFI3aL0O5NXq6x15CpzQRgTNgYetw9vF/yPvND5tPkasLmsbGjfQc9dT20lbfR4mihuqmaLncXoiii1WpRN6pxu9yoBTVJ+iTidHHE6+JJNCYSqgpla9tW1jZ5n1Vzw+cyP3w+eo0egHhjPIuSFrHWspb6nnqCVEG09LTwQvkL3Oi6kWujrwW88udfV3xNYlAic9Ln0NjQSLI5maXTlwLIxirQJwQyVBfKr3J+hdlo5l+7/8V/dv+H0rZS7si6Y9Q92z2eHrrd3YTqQvmq/CveLH6Tys5KPIKH2fGz+fm0n/PX/L8SZYji7ql3n7Dx0x9KhZLsmGyyY7KxdltZW7WWtVVreefAO7xf8j7Xj7ueWbGz/AxiGDnPUKQhkkvTLuWz0s/4pvYbzks877jb2lyzGYDc2EBJhtMVhULB7ZNvp7i5mLf2v8XUmKlEGaJOdbfOWIZtAP3tb3/jtdde40c/+tEodCdAgFNPl7OLryq+IsYYMyr5OYdaD1HZWcklqZccVxhGgADDpcPZwXP5z3Gg5QBZUVn8YvovCNIG8UbRGwADTpwKrYW4BBc5sTn9Klz51jeRQsD6m9RJXiApj0WqS+NLWfvg9Uek/UlIOTQD1efRKDWcm3Aul6Vfxubazdw44UY/9bL+VuF7v9bmaKOsvYyarhpaHa209bTJvw+1HsLhccjt2RvtiMLRWh2SASPR4mhhdeVqCrWFuAU37e52irqKqDlUg0FrICYohoTQBNRONfHh8SRGJmI2mXHWOzGJJr8FRlEp8kHdBxR3FROpjeSqmKtINiSj0Whkr4PT6SRKjMJsNJOkTKKkqwSNQoMHD8sblqMt1TIreBav7H2F7p5ubpx+I7ExscTGxPqdx/6knXuzZMISNtdsprS9lG/rv6W8o5wnZj8xqnkxrY5WXIKL7fXbyavIo62njQhdBHdOuZPFGYt5fs/ztPW0cffUu4k0RI5aPySijdEsmbCEa8deyw7LDj449AHvHnyXPDGPK6KuAMtRA2gkQwSvzLiStVVreb/kfWbHz0ajHL6iqEtwsbZqLTGGGKZETznhPgUYPYwaI3dNuYs/7fgTS/cu5bFZjwWcD8fJsA0gnU7HnDlzRqMvAQKcFnxa+ik2l43bJt02KquGeZXe3KLR8C4FCNCbyo5K/rrzrzTaG1mUuoibJ96MWqmWc25iDDEkByf3u23vpGhfA6H3JE7K8zGZTP22JXmBAEwmkyxuIE0Ky9rLCNWGEqGPGHB7i8WCy+WSiwKazeYBa/RIfH/899lWv43lB5czOWqyPFmQjARJZMBkMslhdb5tTddPH3AhpNpSzeGaw+jCdLQ726m0VHrFA5SQmJSIIAoIooBKqaKoqYh15evIDs4mSZ/E/yz/Y4JpAt83fx+TxsS0aX1zOKxWK7Uq7zkLCQnBZrMRER3BSwdfYr9tP5OCJ3HbuNtw2p19DBSLxcKUhClsLNlIij6FCcYJrLSuxCN4ULlVvLHvDdZp13G46zBpprQBE9+HEs7W3NTMFM0UGlWNzE2Yy9a6rTy59Ukem/XYiNepAW/o2crSlVR3VKNX69GpdGSEZfCL6b8gOyabfEs+m2s3Mz1mOucnnj/i+x8MjUrDnIQ5zDDP4L2S9/j00Ke8XPsy12ZcyyRxEkqFckRDBIO0QSzOWMzyg8v57MhnXDvu2mG3sbN+J+3Odm6acNOojHkBRpbsmGzmJ81nQ/WGQCjcCTBsA+j+++/n3//+N//6179Goz+nDcXFxYwZMyYgbvAdo9nezJflX5ISnMKchJE39Nt72vm2/lsyIzJHZWIQIIAvO+p38Pye53ELbn4y5SdclHx0oKzprKHR3silqZf2u4IoiAIFDQUkBiXKeXD9iRJISDkivgIHvvhO+vbs2SNLYUdHR+PyuKjqqCIrOqvfvvQOt/P1SHR2dsqGS3/epxhjDAtTFvJF+Resq14nnwPJoJKU1Xy9S+A16Hzb6q8PHo+HUEUoWruWOVlzsEYPnNsxO242+bX5KFGSG5rLptZN2Dw2DCoDgiBQVlYmCyHYbDa//mm1WtLT03F5XDyz8xlKukq4dPyl3DXlLj8hhs7OTvn8JyQkeIs3l0CLooXLQy4nShvFe5b36HB1EKIIYUf7DpweJ5cm9L0HpDZ9+zPQeGixWMgyZrG6aTVWu5WfT/s5/9nzH/6w7Q/87tzfjahsr8VmYenepWyp3YJbcGNUG5kaPZVf5vySuKA4upxdvFL4Cka1kR9n/fiUrY5rVVpunngzM80zeXHvi3xW+xn7bfu5J/seEoKOXUdrOHwv7XtsrtnMR4c/ItecK4uIDJW8yjzUSjXzk+aPaL8CjB63TryVfdZ9gVC4E2DYpv6OHTt48803vcXprriCa665xu/nbME3tCPAd4cPD3+IS3BxY+aNo7IStq5qHW7BzSWpl4x42wEC+LKuah3PFTyHVqXliXOe8DN+wCvDDgNL3pa2ldLubPd732w2H5cU9rE4WHuQzu5OwhX9qzH1562xWCxYrVZsNptf//rj+nHXE6GP4K3it2iyNwFeg8y3qKhCoUClUsnbiKIoy3BLxoDT6aSqqora2lrZYFKpVHg8HqxWK9HR0X0MQ7lvJjNR6ihKbCWggAR9AlaXFafgbae1tRWn0yn/lgwp6Xx7BA//3v1v9jXt46Lki7g27lqKi4r9+tba2upXZ+lQ8SG0Ti21nbUkJyczZ9wcfpL4ExL0CTT0NODxeAhSBfF13dc8V/CcX10c6Ril/tTW1srnos+xmc2EGkKZEz+HQ62HiA+K55c5v6TT2clT25+Sz/mJ8m39tzyw8QG+qfuGLlcXaqWamzJv4unzjtZGeaP4Ddp62vjRpB+dlNC3YzE+YjzPzHuGy9Mvp7StlN9s+g2bajaN6D40Kg33ZN+DIAq8sOcF3IJ7yNtWd1RzoOUA58SdE1AkPYOQQuEcHgdL9y6VveIBhs6wZ3hhYWFcc801nH/++URFRREaGur3c7YwGoN8gNObuq461letJzMik+zo7BFvXxAF1lStIUwXxgzzjBFvP0AAia8qvuKlfS8RbYjm6blPMyFiQp/PFDUVoVPpmBDZ9z3ovybIYBP8hIQEtFqtrI7WG19jQvpsSEgIhYWFHK45jCiIKLr7X62XDAFgQCPBV02tN76ThZf2viRPFiQPiyiKiKKISqWSPQYKhcLP8Oqda+RrLHk8HqqqqmTjwPdYfcmNy6Vb7EYZrmT22NkYjAbseru3j0YjWq2W8PBw2agC5PP9waEP2G7Zzrnx5/LjrB9TU1MjG2SSEIS0rdQ3j8dDsCqYDleHHNY3OX0y34/7Pi7RhYDA1KCpTDBNYEPZBn62+me8UfQG7T3tfc6hIAgDLgxK98VVE68CYFX5KmaYZ3B39t002Zt4ZuczsoLd8bK6YjW/2/o7KtsrMaqNhGhDmBg5kVsn3SrnvfiGvp1OCpuSN+h35/6OUF0oz+95ni/LvxzRfWSEZbB4zGIqOir4rPSzIW+3qnwVAAtTF45ofwKMPlIoXGFTIeuq1p3q7pxxDDsE7vXXXx+Nfpx2TJo0SVYqCvDdYEXJCgQEbpxw46iETWyv306TvYlrx16LWjnsr16AAENiZdlK3t7/NnGmOJ6Y/US/q+CiKFLeUU5qSOqASdM7LTsJ1YYyNnzsgPvqLRowWMhwf+FzksJYl7sLhVJBSkzKoG0PFJY1lFBl37j5L8u/5LL0y+QwM9/24Gg+k1rt/Z5KXhVfo0d6vfcxwtEcIinET+5DUjbrG9fTrmonzeittXKo5RAzgmbQ3d0tG3G9ZcJL20r59MinxOvimaeeR3NTs9+Kr9S34OBgv+Oora0lWB1MTU8NgihgsVjInJTJyh0rCVWFEhcUR1lPGZnGTH5g/gEbWjfwZcWXrK9ez7lR55KpyiREGYLH40GpVKJSqfrN35LICMtgcuRkttRsYXHGYuYlzqOuq46Pj3zMJ0c+GbDW1GB0u7r5565/ypP6zMhMbhh3A68XvS4XXgVOm9C3wRgfMZ7fn/t7nt7+NG8Uv4HNZePasdeOWF+vG3sd+ZZ8Pjz0ITmxOccMhavtqmVj9UYyIzIZGzbw9zzA6cutE28lvzafpflLSVQlMj5x/Knu0hnDsD1Ay5cvH/C9Bx988IQ6EyDAqaK0rZTt9dvJic2RazyMJB7Bw3sl76FX6QPhbwFGjU+PfMrb+98mMSiR/3fO/xswBKihuwGby+bNEemHyo5KarpqmGGeMWgoaG8luMHoL3xOek0dokaj1uDucMtek/7aljwN6enpA3qiBuPWibcSZ4rj7f1vU2gtHLA9ybjo7u7GbDb7ebck74xUrFRCOjbf/kqhcZJHKNTtjZIoay9jfMR41Eo1NWKN/HnJi2Q2m+X91DXU8cKeF1AqlCwKWYTgEqiqqupTg8/3nEmhggkJCYxPGY9SpaTL04XJZOLt/W9T7azm3Mhz+cPsP3Bh+oUcsB1gZ/tObk+8nV/l/IoIfQTrGtbxYt2LfNr+KRXOCuLi48jKysJmsw16zW/KvAkBgRUHVwBw7bhrSQ5O5uPDH1PRXjHka1XZUckr+17hhpU38NHhj9AoNdw37T6WXryUCEMEokJkUuQk+fNv7n+Ttp42bs68mfaedtZVrePz0s/5+PDHfHjoQz4r/YyvKr6iuLmYbld3v/scyHM3kkQaIvntOb8lLTSNDw59IMurjwTDDYV77+B7o7roF2D0MWqMXBZ+GQ63gxd3v0hjY+OxNwoAHIcH6O677yYsLIxLL73U7/Vf/vKXrFixgmeffXbEOhcgwMli+cHlKFBw44QbR6X99dXrqbfVc9246wJx1gFGhVVlq3j34LukBKfw2OzHBr3PJMnptND+K75vqd0CcMw6VScq5yt5b/Yd2IfL7UIramWvh8lkwul0DqgqdzwYNUYemvEQj255lH/s+gdPz31aFniQvEu9vTpVVVUkJyeTlZVFWVlZH7EEQK4kX1VVhdFo9FOqkwwFp9OJ2CqiRUtBRQGXx15OujGdImsRNyXchL3DLn8+KytLDrt7p/Adahw1LBm/hFhbrNw/l+toSJlCoZBDD6XtPB4PFouFMH0YgiDQ5eliQ+0Gvm7/minmKTw06yFam1uZp5xHU3AT+zr3kWfL4+HYh5lhniEX+NzVsIv97Ofrzq+ZHjudMfoxhAvhA17zjLAMZplnsd2ynZKWEsZHjOfu7Lt5bPNjvLD3BZ6e+3S/XkdBFDjcepj8hnwKGgqo7aql09lJR08HGcEZ3Jt4LxOiJqBVaf3y11yCi8+PfM6Hhz7EpDHx36L/4haPnQMTZ4ojPTSdcRHjmBs/lyBtUB8D8kQLlQ5EqC6UJ2c/ye+//T3vlbyHUWNkUeqiEWk7IyyDK8dcycdHPuaz0s+4Zmz/udmlbaVst2wnNzZ3VBb9Apw85o2Zx562Pezp2MPG0o1cHzN8T+t3kWEbQO+88w433ngjK1euZO5c7+B433338dFHH7F+/foR72CAAKPNPus+CpsKOT/x/FFRZuvx9PC/w/8jRBvC5emXj3j7AQLkVeTx1v63SAhKOKbxA1DeXg70bwAJosCW2i1EG6KPOTEaSvjZ/2fvvMObOq8//tGWLC/Zli3vAdhgMBgwYQcCgaw2Cc1OR3aaNm1GM9qmSUdKk2b/krTZTdI0k7QhScliz4Rhg8GAGcZ7yJZtecmSte7vD/VeJFteYINJ/H0eHmHp3ve+7x3vPec93/M9/o6FaJR338ftdaNSqlAr1FLURBQ38Bc5GAokhCZw17S7+OvOv/L4rsf545w/Eq4Ol4zf7jQ3QDKInU4nre5WXqt+jTJ7GWP0Y3hoxkOEacIk2ltnZydqtVpylLor18VUxVDeWU5tXS2JQiKF3kIK6go4f8L50jZigdcaRw1bm7cyIX4C3x/zfSrLK7FarchkMiIjIyX1OzEa1D0qYzKZ2FezDwSo6qpiY9tG4kLjWBa9jL179iKTyRAEgUuMl+CVednXuo8nv3mS88POJzE+kftn3E9DZwMbKjew07yTDVUb2MAGVHIVY21jSW9IJyMig4zIDOJC4iRq79Xjr2aXeRfvHXqPP8z+AxkRGVw69lI+KvmIj0s+5rJxl2F1WClrLaO0tZTS1lJKrCW0u9oBMGgMjI8az76GfeQac7k66mp0gg6z2Ux0TDS763cTFxLHquJVrKtcR2VXJV2eLpLCksiOziYjIoP0iHTC1eFoFBrAV+um091JZVslpa2llLWWsa12G9tqt/H2wbfJjcxlgnwCiVqfIzkUhUr7QogqhN+e9Vv++PUfeWP/G4QoQ4Ysb+mycZexy7yrVyqcIAi8W/wucuRcPf7qITnmKE4fjEYjP8n5CQe3H2Rz+2YuEy4blTMfAAbtAF100UW88MILXHzxxaxZs4Z//OMffPLJJ2zYsIHMzMzh6OMoRjFs8Ape3j30Lkq58oT46QPBl2VfYnVYuWHiDaOFT78DCFZgczixoXID/9j/D0whJh6a9dCAIoyi2le8vqc8cXFzMc2OZpaNXTYkL1HRcRApNmJExz+fp6a2Bo/XA14kJ2koi0V2R25sLj/J/gn/PPhPlm9fzu9n/b7H8USHRoQ4hteqX+NAxwG8eClqL+KxfY9xe9zt0nYGg4GwsLAe94CYwxStiuaYcAx9tJ5Meyaf8RkH2g8wt30uTqeT9vZ2yelb07QGtVrNz6f8HKVcKUlcy+VyMjIyAu41fwdOoVDg9XqpqamhpbkFh9fBqoZVhCpCuWnMTbiafNEjUfhBgYI7pt7Bvyr/xZayLdjCbFzquRSAenM9i0yLuGr8VZhtZgrqC9jTsIeSlhKKm4ulccuQEa4OJ1IbiUFjQKvUsql6Ew9ue5C4kDjsbjstjhaeKXiGDw9/GEC5UsqVpIalssS4hDxTHuHqcO7bfB/GECMPzn4QRadCimx9fvBzjlqPolaoKW8qR4kSFSqWjV/Gr6b/ql8q1xTjFOn/na5OCuoLWFOxhs01m9nk3URSSBJXZ11NVFfUsAshRWgi+N2s3/H7bb/n1X2vkmnIlCKSJwORCvfg1gd5odAXdfPPO93TsIf9TftZmLxwtBzDtwTjksZx2cTL+OTYJ2yt2TqiREBGKk4oE/vaa6+lpaWFuXPnYjQa2bRpE2PHjh3qvo1iFMOO7bXbKWst48L0CzGGDL2x2uHs4JNjnxCri+0hQzyKbyf8c1eG2wEqbCjklX2vEKuL5aHZD2HQBpeR7o6WrhbC1eFBxTi2VG8B6LcO1kAdPf96NmKdoKKiIokm5nQ68Xq9eNwevHilfcToUnFxsUQtmzBhwoDGNxBcmHEhXZ4u3j/8Pg9vf5jfzfwdOUYfzU2MrIiQy+V4PB6USiVl9jKpn17B63MC4nzbiUIEYsHX7ueppqYGvVyPgEBJbQmp4alkhGRwtOsoVZYqQpWhWK1WUlJS2Fuxl1pPLQtSF5ASfrxQrVfwYnFYeGvrW7Sp2uhSdNF6oJWW9hY8gofw0HBcDhcyQUakMpJyezlNzibiNfH8KP5H0HJ8XCqVCplMJp3vu2Luoq6hjj1te4hQRbBUuVRSmgMwGU1clHERF2VchFfwUm+rl6IpDfYGWhwtWB1WajtqcbgddLo6WVe5juSwZJQyJVHaKGwuGwatgbNMZ5EWnkZ6ZDrJYckSLU4QBP6686/Y3XbumHEHik4FZrMZr+Blh3UH79W9R6e3k/PTz2dhzEI+PPwhKrWK6yZeN+g8lhBVCPOT5jM/aT67y3bzWclnHLQf5IUjL7AoeRE/jvzxoNo7EcToYrh96u08/M3DvLT3JX429mc01Dec9AJKb1Q4m8vGq0WvolVouTLzyqEaxihGAC4ZewnrKtex4vAKZsfPRqUILnAzCh8G5AD96le/Cvq90Whk2rRpvPDCC9J3Tz/99ND0bBSjGGZ0ujr5V/G/0Cq0XDr20mE5xifHPsHmsnH9xOtHJ6PvCIYzcuGPuo46nt39LDqVjgdmPjCoQnjNjmYMmp7OksvjYkfdDtLC0/pdGe7N0etNGa6mpkail/nn0chkMtRKNQqlAoVS0UPOurOzU/rsqyDqiWDZuGWATwHyD1//gftn3N/D+YHj+TYul4t0XboUAZIjZ2zoWGlciYmJAXlElZWVkpiB2WzG6/USpggDAZrtzSRqE7lw7IX8ff/f2d+1n1nKWRgMBoxGI5X1lShVSs5OOpvN1ZvZW7OXQ/WHMHeZcQk+51EmlxGiC8HlcKFAgUqhosvVRVtXGw6XA5vHRrOrGY/gwel1sqZ5DQmdCcSr4snSZ8H/0ogqKyulyNOtWbfybPGzbGndQpwmjgkhE6Tr56/EZzKZiDfGEx8a38NZFgQBj+BhY9VGXt73MvOT5vOL3F/gETz8Yt0vUMlV3DDphh4RRovFwmeHPiO/MZ9F6YuYFjeNoqIi6m31fGL5hGO2Y3R5u5gSPoXl85bzdc3XtChauCj9IoQOgaKSnup0fTnq/r9NS5/GtPRpWDotvLzvZdZXrWevZS8/nfLTgKjRcGBi9ETOSzuPr8q/4uPij8kLzZOeq5OJKAejwr118C2aHc3cOvnWEVEnaRRDB71KzyVjL+Gd4ndYW7mWC9Iv6H+n7zAG5ADt2bMn6Pdjx46lra1N+n1URWQUZxLEF8EtObcMizBBk72JL8q+ICUspd9k8pPBqaZcjaJvDFSW+WRgc9l4fNfjONwOfjvzt1IRyIFAEARaHC3ER/fcZ3fDbjrdncxPmt9vO705esEcI5G+pFAoSExMDKCXJScnMy5sHAX2ApLGJGGM9O0j3tdyuRyv14tcLpf28xcWONlI27Jxy9CpdPxz/z/53dbfcbnpckweEyEhIXR1dQWIIoSEhPDz9J/zuvl1ipuLSdelc0P8DSQmJgb0oXvdIFEhrqrKF+UBcMgcmEwmXNUuQuWhfN34NbfNuw2FXIHdbWd1+WrcXjdP5z9Nl7cLu92ORqYhJSSF8bHjCXOGkZucy4TkCTQ2NkpRp8TERGrravms7jO2tmwlRheDW3AzzTQNc6eZ/I58nC4nGpmGGdEzmKKbglFtDHD8lp+7nF9+8Uv+Y/4PNyXeRLzm+L0ykAinTCZDKVOyOGUxBfUFbKvZxpz4OeSZ8licspj/HP0Pexr29CjCe7jqMKvMq9ApdVyXfR0AjZpGXjr8Em6ZmzhNHHK5nKuyrsLtdfPB4Q/QKXVcOu5SKg5XBPRrILlnwcZiDDHyu5m/Y23lWt4++DaP7HiEH4z9AVdmXTmsNs41469hT/0eNrRuICs0i4mmib32caDoToW7MvNKNlZtZIpxCouSFw3DKEZxujFdP50VrhV8cOADFiYvHKXd94EBOUCj4gaj+LahsKGQDVUbyInJCUpNO1mnQhAE/rH/H7i8Ln444YfDmpB4KilXozj98Apentv9HLW2Wq7Lvo7JxsmD2t/hceD0OonURPb4bUv1FmTImJMwp992/B09/+elvwiYuI//8xVp9/XF2nXcCBfvaxH+stN6vT4gz+ZkcX7a+STqE/m/3f/H29Vvc/X4q/n+mO8jl8kpLCzE4/Egk8lwu90kRCZwt+punHHH+9a93o9CoUAul+NyudBoNBJ1r6amhlBFKDKZjIg436KL4BXIC89jg3UDG6o2EKIM4Y0Db3Cs5RgxuhjGRo5lpmEmkY5IDEoDSUlJQZ9z0eE6Vn2ML9q+YE/bHibGTCQ2PJb8+nwenvswSrmSels9q3avYnvTdrY1bWOncifJ6mQWxi8kXZmOXq+ntqSWm8fdzPOHn2dFwwpuir+JZIMvIjiYCKdMJuPmnJu5d9O9vFb0GuOjxrM4ZTErj65kdfnqAAdIEARWt63GiZOfZ/+cUHUoX9d+zatHXyUiLIKlYUv5qPYjDCoDk6Mm88+t/6S6tZofTvoh4erwHv3yz4nqrbB5b2ORyWQsSV1CkjyJ5wqfY0XxCmxuXyR/uOZynVLHbbm38fA3D7OxayNnx5zdZx8HCpEK95+j/+GP3/yRKG0Ut06+dXTB+luKZkszZ0eezarGVawqXTVsuc3fBozKRIwiAKeiDsLpRqerk5f3vYxWoeW2KbcFfREMpr5JMGyp2UJBfQFzE+aSG5t7kj3uG8Hqq4zi24v3Dr1HoaWQhckLT4ji4PL6eE9qhTrg+xZHC3sa9jApZhJR2qhBtdndCe9eo8e/jg7QYxsxd8nq8DlAogqaTCZDJpOhUCgkqelWdyu/3/t7lq1bxt/Nf0cWOjSGXI4xh7/M+wsJoQm8e+hd/vTNnzDbzFLf5XI5TqcTq9UaIOrQ27kQaXMihU+cU8VcF2ubVZpfZkbMxC24+dM3f+KZ3c9wuPkwBq2BJ85+gr/O/ysZngwi5BHSMbvP0WI7BzsO8lLNSxxqP8QFmRfwyMJH6HR3ohbUHD54mKbGJuJD47l0wqXcOeZOfjP1N5ydejYNNPBe7Xu81PASW2u30tXVhREjd828ixZXCyvqV9Da0dqDgjiQd0W0LprrJ16PtcvKK/teIUobRZ4pj72WvTTaG6XtNlZt5HDHYc4bdx6Lshaxo24Hz+9+nnB1OH+c80c61B3YBTuXjbuMmroa1lrWohE0XJB+QdAFK3FeTExM7LVmlHgfimMpLS0N+Oxq6uLHph+TrE7mq/KvePPAmwEFaIcaIhWuuLmYL8u+DOjjySxuXTbuMgDMNjO5sbmDosuO4syCyWRiZvRMksKSWHVsFa1drdJv3wX7bjAYdYB6QWNj43fyRjlZw/9MgEh9+3H2j3t9EZyMU2F1WHnzwJtEqCO4YdINJ9vdfjEUL8hRnBnYXL2ZT499SqYhk5snnVi1e4/XFynovpK9vmo9bsHNktQlg26zv+elr3vUYrHQUNGA2+WWIkAidUkQBEmpLCMjg5SUFF6vfZ2i9iJau1rZXredB7c+OOj+9joOvYm/zPsLF6RdwKHmQ9y36T52tu/EGOfrt+iIiQ6RCNGx8z8X3SGOSSb4pKebrc14PB5a3a2sqF9Bl6cLp8dJdlQ2Jr2J89LOY5JxEo2NjQF0umBztFft5d/mf/Ofxv+gUPkU3W6bfBstzS1UNFSg9qilfURnIT4+njlj53DntDt5YfELXJ11NR6vh08sn7DCsgJdlI4FyQs4P/V8qrqq2GDbQF1dnSSKUFNTM+B3xdlJZzM7fjY7zDv46OhH5MXlISBw1HoU8OWevXvoXQwaA9dlX8f+xv08t+c5wtRh/GH2HwhXh7PZspmxxrF8P/v7HPAcwOa1sWzsMnRKXY9z4q8wKI65L+NP3F90bsVPgDBtGPdNv4/s6Gy+Kv+KD4982O94TwbXjL+GWF0sHxz+ALvbPiRtripdhSAIGEOM7DLv4lDzoSFpdxSnH93va6PRyJTJU7huynV0ODp4YcsLfRaX/i5j1AHqBfX19d/JG+XbHk3oj/om4kSdCkEQeLXoVWwuG7dMvoUwddjJdnkUowCgxFoiraDfm3fvCYtqyOjpNHm8HtZUrMGgNZAXlzfoNk/GCTebzYQSitPppKShBKDH/CP+bTQaKe8qxyv46HAewcOBpgODPmZf0Cq1XD/pev4w+w9EaiN56+BbPPDNAxS1Fkny0zk5OVJkqLtog39UofsYFAqFFNUC2NG8gxerX6TMUcbcyLmM1Y3liPUIXsFLRkSGdH5EeL1eXC4XCoUCvV5P/t583ih4g+X7l7O/Yz+Zukxuir2JTHUmMpmM6tpqmrqaMKgM0rwezAiK1EaybNwynl74NAvTFlLhqeCR/Y+wuXozt06/lZnJM9nRtIPDHA7oCzCgQrUymYzbptxGangqK46soN3pq/cjFuT9pu4b2pxtXJRxEQDP73kelVzFAzMfID40ng8Of4DD4+DqrKvp8nSxoWEDqdGpXDrpUunc+r+3ujs0ZrM5oMipf5THYrFI+4vOrfgpRo8STb6aSBkRGfzn6H840Di095w/dEodl4y9BIfHISkyngzyzfl8cPgD0iPS+b9z/g+VXMVT+U8FRN9GceYimPNfVFREuiKdWGUs25u3c7jK99x+2+27wWLUAeoFcXFx37kb5dueTD8Q6tvJwp/6NsM0Y8jbH8V3E1aHlSfzn0SGjPtm3HdSoh1i5EJ0IsAnftDsaObclHNRyHsWAh1OmEwmNHIN0epojjb6IgJGo5GUlBTJGPVfxU/TpCH/36tLLpMzMXrisPQrOzqbJ85+gsszL8chc/Bhw4e82fAmexr24BW8/Tp9ImVP/DQajT5FOMGDBw8FHQWssqxCr9Lzqym/4srkK/nxhB9j7bLSaG+UitSKjhMgRcRcuFhdtZonjj7BytKVGDQGrjRdyVVxV6HxaiQjv03RhiATyDZlS30V2xMLzvqvIIeqQ/nl1F9yb969KGVK/l74d57e/TTXTbyOeH08H1V+hD3cjkKhkKhgYm2i/qBVarl/xv2Eq8NZcXgFgiBIBXnXVKxBJVexMHkhbx54k5auFm6YeANpEWkUWYpYV7mOidETmWGaweaazXS6O7l07KUSnbD7teju0Ii5aWJkrrtzJO4fFhYmSbX7R4/A55j8KPVHuB1unt317JBFZ4JhbuJcdEodqytWnxTlrqq9iuf3PE+oOpT7ZtxHpiGTX0z9BW3ONklEZRRnNnpz/uvr67ki8woEmcAhry/iN8oWCcSoA9QLQiJCvnM3yrc9PCpS336S/ZNh4UA3O5pPKfVtFN8NCILAK/tewdpl5bYpt0mRgROFaDR2ebqk774q/wqFTMGilNOjDKVQKIjXxGNxWthesB2LxSIZ66KhWlNTQ2VlJTcm3MjE0ImEKkLJCcth+bzlQ9aP7nQSrVLLFZlX8OJ5L/KDCT+gvquev+78K3duuJP/HvuvFMkIhoyMDKZPn05GxvHrZTKZqHZW09DVQIO9gWnh07gt8Taiu6IxmUycP+F8jDojNpeNxs7jK/QiBa6+q57PGj/jmcpnWNO8BpVCxQ3jb+D2lNvJDs2WEv7BZ+SXNJeg0+mYnDhZGpt4vkVltGDz/gzTDJ5e+DTzEudRUF/AH7/5I1dmXYlaoeaNo29gc9pO6PzG6GK4J+8ePIIHq8PKUetRSltKOWI9wtyEuRxuPsyWmi1Mi53G2UlnY3fbeWnfS9KiFcDq8tXoVfo+lTX9HRr/70QjUaQy+juCcJym6PF4pPuusrJSchQdFgeLDIswd5h579B7J3QOBgKdUseCpAVUtVedMF1NdHJcXhe/mv4rYkNiAd+1vTLzSiraKnhx74vDmtM0iuGD//MczPk3mUwszFxIalQqO5t3SrmfoziOUQeoF3xw+IPT3YVTjm9zeNSf+jYcRp4gCLxW9Noo9W0UQ44tNVvY3bCbeYnz+i1OOhBoFVo0Cg0tjhbAV0+oqLGIGaYZgxY/GAqIRmeCJgEEqLHXSMZ4sMWYCGUEy6cu55Upr/DMvGeGdDGjt0WgCE0E10+6nmcXPculYy/F4XbwdvHb/Gztz3h81+Osq1wnnc++UCPU8JH5IwQEzok+h2Xxy1Chwul0UlVVRVFREXq5Hq1Sy/uH36fZ3kx+aT4bmzfyctXL/KP+HxxwHSA5Mpnbp9/O6xe/zrKcZYSF+uab8PDwAHpeu8rnoKVHpAeMzX+u723eD1WHcnXS1SyLWkZLRwvPfPMMM6JmYLFZWN20GvBR2/zznwaC8VHjuSnnJrx4KWkpkXJq5iXO49WiV9Gr9Nwy+RZkMhlvHXiLRnsj1064ltiQWA42H6Smo4YFSQvQKDRSm8HyeywWC5WVlQHXU7zXxJwyf0cQjkfb/EU3xP3Ebc6KOIsJ0RP4qvyrIadf+kPMxVtdsXrQ+9pcNv6y/S80dDZww6QbyI7ODvj9B+N+wKz4WWyv287KkpVD0t9RnFp0p3T65wCJDpFcJmdJ2hJana3sqtt1mns88jAgGezvIjZVb2Jq9VTOTjr7dHfllOFU1C85HTgV1Lf/lv53lPo2iiGHGFWM1ERyw8ShiSrKZDIMGgMtXS3AcQNradrSIWl/sNDr9TidTsZGjGVN0xoquio423RcAlisbxMeHo7NZhtWim5/ksMxuhiuGX8Nl4+7nB3mHWyo3MCe+j0U1BcAMCZiDGMNY8mIyCAjIoPE0ESJUrijbgdP7ngSQRCIVkUzPTawBo4gCLTZ2yhvLScpNIljrce4YtUVhHhDANApdCweu5i88Dy0HVridfGSkp/NZgv4FOfy1ze8jkFrIEobhdfkDVqgVtw+GMxmM+M047gm9hrerXuXteVrMelN7G7bTXZoNllhWSd0LRalLGJ7rc/4/uTYJ8xLmEdJSwktXS3cNvk2orRRrK1Yy/qq9eTE5Bx3BspX43a5SXGkSFFCsZ/dSwH4O7Hi9ex+fbv/LZ4XkQ5uMBike05s02QycXfI3dyz8R7+feTfTJw9PBTMpLAkJkZPZGfdTlocLURqIwe0X6erk0d2PEJ5WzlXZF4RVNREJpPxsyk/o85WxweHPyApNImz4s8a4hGMYjghzo191bkCyNHl4HK4+PTwp8xJ7L+8wXcJow5QLzBoDLyy7xUS9AmMNYw93d0ZxQlCEATeOPCGr/J1zq3DQn3b07CHd4vfJSk0iZtzbh7y9kfx3YQgCLy6zyeocV/efYSqQ4esbYPWQHVHNV2eLjZVbyIpNInsqOz+dxwGiEZ7gjqBiJAIjtiOSL8ZjUbJuLXZbEHFBU4UwXIeB7oIpFKomJc4j3mJ8+hwdlBoKaSgvoB9ln0caz0mbaeUKaV8rQONB1B6lcRp4qh2VLPZvBmNTEO7q512Tzsd7g5sHhuNzka6ZF0oZUpsbhspYSlcEnMJE40TcXQ68LR5cHlcAQZPMMettqOWWlst56aci0wm63VsjfZGHtz6IPub9jMpehLL5y2X5kmx3amGqajlav5V+y/qO+sREPi04VNu195+wud/YcpCvij/ApvLhsVu4cvyLzFoDMxPms/BpoO8vv91YnWx3DHtDuQyOc2OZnaZd5GqTiWc8ICCp2JUx3/8/uek+/UVI0Ymk0m6p/zvB/GeczqdksiFf3TJpDcxM34m22q3UdVeRXJY8gmfh76wNG0pB5oOsKFqA8vGLet3e5vLxiM7HqGkpYRLx14qyV8Hg1ap5b68+3hg6wP8vfDvmPQmUsJThrL7oxhGiHOjSI/tbdGmo6mDCSETKGoqorKtcvQa+2GUAtcL7ph6BwBP5j9Js6P5NPdmFCeKL8u/ZHP1ZqbGTh0W6ltNRw3P7n4WvUrPfTPuI0QVMuTHGEqM1gE4c7ClZgs7a3aSqcokVZE6pG0btAbane1sqNyAzWVjSdqS01YYUaRgJcQnkK5Kp8ZeI6kW+f8+1NTcocp5DFWHMi9xHndOu5PXlr7G84ue5+7pd3PJmEuYEjsFGTL21O+h09WJSq6i2lGNzWPjQMcB9rTtodxRjtPrJEoVRXZYNtG6aBYmLeQ/F/+HJQlLaLI3YdPYcHQ6JGnm7ucjWHKzGJXyLzYqwn8eeHDrg3xT9w2tXa18U/dNgKy42G5GRgazxs3ihpQb0Gv1uAQXTa4mVjf3Ts9ye910ujpp7WrF0mmhyd5Eu7OdLk8XgiCglvuiVxHqCCydFvY37mdO4hyaHc08XfA0KrlKEk0AWF+5Ho/g4YIxF6BWq1EqlRQUFFBVVSU5QL2p8QWrm9T92nenCPp/7/+7SDk6K8IXMVlTsabXc3CyyIvLI0wdxva67f1u2+5s58/b/0xJSwmXjLmEq7Ou7veZNoYYuSfvHtxeN0/seoI258AELUYxMiDOjd2VKLtvMydmDiql6oTolN9mjEaAekF6ZDo/n/Jznt3zLE/lP8UfZ//xhGVnR3F6sM+yj7cOvEWCPoFfTv3lkBt4Hc4Ontj1BF3uLh6Y+QAm/cjMnQq2stlbuHwUIwMi9U0jaDjPcN6QX6/YkFgEQeDfR/5NqCqUBUkLhqztwcI/MjEnZQ6FbYWYleagvw8VeosanCxkMhmxIbHEhsQyK34Wbc427t14L4maRK41XYtRbeTdunep7arlruS70Cg0qGVqSRrbYDLwm92/ISE0gXBNON+L+B5VzVW8f+R9SIfJuskDpgAW1BegUWiYFDNJGnOweWB/035JEdAreHvNazEajVxgvICwo2E8uedJWtwt7O3cS2FDIVlRWZS1llHWWkZpayllrWXUdtQiEDzBPkwVRqg6FJvLxrjIcWgUGsw2MxsrN7K5ajMdrg7uybuH5HBfZMXtdbO2ci1R2igWZS5CIVdQUOBz8ARB6NNBDjbnBYuY+VOKAFJSUqRaQkVFRZLct8fjwel0EiKEkBqWyqaqTVwz/hp0Sl2/12SwUMqVjIscxz7LPlweV682SFV7FU/seoL6znquzLySH4z7wYDfd+OjxnPjpBt5pegVnil4ht/N/B1K+ahpeCZgIHOj0WjkwpgL2ezYzJbqLVw7/toRv1B7qjAaAeoDcxLnsGzsMkpaSnh538ujailnEMw2M8/uftYX5p9xH3pV/7UqBgOP18Nze56jzlbHdROvI8c4dNScoYK4yutfsFBcMRJf6qORoJEHf+rbjRNvJEIXMeTRj/SIdGwuG7W2Ws5PP39YjLcTwdljzyZcH86+9n3Dehz/ZPi+DIjekuvF7/r7/Y39b9DqbOX6CdczLmIcoapQLE4LSdokwlXhaOQayVAVBIG6mjo6bZ24vW4A0hLT+HHCjzGoDKyoWEF9eP2AnB9Lp4VDzYcYpx/H4YOHJecnmAjCpOhJUlFcf1nx3qLFobZQzo85H41cg8Vh4a4Nd3HdF9fxp2/+xFsH32JrzVZcXhczTDNYmLyQ89LO46L0i7gg7QLOTTmX+YnzidPHcdR6FLfXTU1HDcXNxWiVWvZa9lJoKWRR8qKAXMqC+gKsDitLUpdIOVX+MuN9FdkN5ugGiw6J+3s8HqqqqqTzZLPZAiiYosBEfHw8S9OW4vA42Fm3s99rcqLIiMzALbipbK8MGJfY74L6Ah7c+iCN9kZumnQTl2VeNujFvsWpizkv7TwONh3khcIXAiTyR3FmoC92h0wmk+7VLTUnX1tqJMNisXDgwMDESUbd/H5wZdaVVLZXsqVmC6nhqXx/zPdPd5dG0Q86XZ08vutxOl2d/PqsX5MQmjDkx3in+B32WvayKHkR56WdN+TtDwVEg0eUxvU3ANra2vpMnBzF6YOo+jY/cT7nZp07LMdID0+npauFUHUo56edPyzHOBGEqEKYnTCbzdWbqWirIDV86Kh//hGQ/sQORPSWXO9Pn+rt93VH17G5fjNjtWM5y3gWsbGxHKg8gLPESWpo8HGpZL4V/mpztWSUp7ancp1wHe9a3uXVolexu+18L+N7fRq5ayvXIiCQrcoOcHqCiSAsn7ec+zfcT3FzMROiJkiy4sHG7hW8HLEd4WjnUdrd7TgEB0q5Ep1Sxw/G/YCMiAzSI9IHlK/2YuGLvFb0GtnR2RyxHsEjeKQxvXngTWo6argn7x7CNeGsqVjjk2lPPk5j9pcXD5bPJarAgY8y6P99fxFxQRCCnjcIXHXPtecCcLTlKAuShyeKmh7uqwdV2lrKmMgxgO/adHV1seLACrZ0bCFUFcr9M+5nYsyJCzL8JPsnNNmb2Fa7DZVCxU8n/1RyjEcxvBiKGoz9sTvmJszl7YNvs7p8NUtTl542yvNwQzwPA8Ho3d0P5DI5v8j9BUmhSbxT/A755vzT3aVR9AGv4OX5Pc9T01HDtROuJTc2d8iPsb5yPZ+VfeajDuTcOGInEnGVV6xm7p9QDj3zCEZx+mF32/nXwX8RoY7g+onXD9txqturcXvdRGmiRpxk+9JUnxrdUOdWdDcQBlLnLVj+UTAJaf+IqslkwiV38aX1S5ReJRdGXUh9fT0AhyyHELwCRvnx48pkMlJSfInJcpkctVxNq7NVcrBsNhuRqkhuSLyBxNBE3i5+m5f2voTLE7yuh8vjYn3lemJDYpk3Zp7UP5HO5V/cE3zKdncn383fJvyNu5PvDhBAEMfZ5eniv8f+y10b7uKDhg8osZdwdsLZpIWnEaGOoNPdyaSYSeQYcwYs1lHRVoFcJkej0GB32zHqjDw06yFunXwraoWaz8s+Z9kny/jL9r9QUF/AzPiZvSqhiVFuUTEQgqvAid/7Ozf+tYDE6I5/AVWxflD38wYQrY0mXB0uFXQdDmRE+hy90pZS6buImAg+bvyY9db1JIUl8Zd5fzkp5wd8dLu7pt1FrjGXjVUbeX3/66Osl1OEweQj9hbp6S9XUqvUsjB5IdUd1SdcW+pMgH/B4/4w6gANACGqEO6b4VNh+r/d/8c+y/DSM0Zx4nj/0PvsbtjN2Uln872M7w15+1/XfM2r+17FqDPyq+m/kopKjkQEM/KCOUWjGDn4rPQz2pxtXJV11ZCqvnXHJ8c+QavUIiD0akifLoyNHEt6RDpbqrfQ6eo84Xa6GwonIqbQl6PU3t7egyYlRhx2CDuwC3auSL2CUGUoSqWSoqIiDjX6DI94TbyUvDxt2jSMRiPTp09n+vTpjI0eS72rnti42IB+ZyVnsXzucqbGTmVj9Ub+9M2fsDqsPfq1w7yDNmcbS1OXEhcbR05OjtQ//6Ky/udGzG8RP/3H3ihv5L5N9/F28dvY3XYun3A5r170Kn9d9Fd+lvszQtWhNDuaB10YtKSlBK/gZa9lL3KZnD/M+QOXjL2EWyffyqeXfsr8xPk4vU5Wlqyksq0Sj9cz4GKO/tS37gni/k6raHB6PB7p2onX0381vjcDVSaTkRGRQUVbhURbHGpEaaOI1ERS2upzgPZa9vLXA3/lqPsos5Jm8ee5fyZOHzckx1IpVNyTdw+ToiexpmINbx18a9QJOgUYzNzU2704kEWdhckLAZ8k/7cVRqORiRMHthgw6gANECa9iQdnPohaoeaJXU9wsOng6e7SKLpha81WPjn2CWMjx3Jrzq1DHpnZWbeT5/c8T6Q2kodmPSTJ2450+BuCA135HsWpR2tXK/899l/i9fHSi2o4UNxUzGHrYabGTkVAGNZijicCmUzGeann4fA4+Lzs8xNup7uh0D3vo7S0NMAJEJ8T/++DrbaK7YrORHcq3YajG/i69mvy4vLIVGcC0NnZidPp5GjnUcKUYRjVxl7pLtlx2cg1crwh3h7UmBBVCPfPuJ+Lx1xMsaWYn3/+c74q/krqZ31DPSuPrkQtVwfcQ6KBJUY2gIBz072OEEBVXRV/WfMXfrf5dzQ5mvhx9o95YfELnBt9LrVHaiksLGSKbgrTYqchCAI763YOeHHQ0mmhrLUML15kMhmz42czxThF+j1cE85TC33iQ1qFFo/gYeXhlfz0059SUFbQoz0xcpOYmChR30QxAzFyI54jIMAp9IdY8LS7gelvoHa/J9Ij0nF5XVS3Vw9o7CeCjIgMKtsqebHwRR7Z8Qg2l42bc27m3rx7hzx/T61Qc9+M+xgfNZ7Pyz7nvUPvjTpBw4z+3sv+99zJqGKmhKVg1BkpqC8YvaaMOkCDQlpEGr+b+TvkMjmP7XyMw82H+99pFKcEx1qO8dLelzBoDdybd++QK/YV1Bfw7O5nCVOH8ftZvx+yFbdTgd5WjEYlsUcWPi75GIfHwdXjr5YSvYfrODJkXDfxOuC4XPJIwvyk+cTr4/nvsf/S2tV6Qm30Zih0d2DEaIhIo/J3bPoyhkNCfEpKLpcvKpGSkoJarWZ753Y0Cg235NxCfHy8tG2js5EWbwvZ4dlSP0T4P4sZERm4XW7W710v9amyslJ6TuUyOT+c8EMui70Mj9fD3/b9jafzn8Zqt/LlkS+p7qjmooyLAqiN/nLW/on8JpOJ1q5WKj2VbGrexIr6Fdy+7nZ+8MkPuHzd5XxU9xF1tjqs7Vbe3Pcmt6y5hT9u/yP/Nf+XfGs+eyv2cvPkm4nWRdPmbOPdQ+/2mUAvCALrK9dzx/o7cHldGHVGYkNiGRc1rse2MpmMPFMecfo4kjRJKFCwv30/v9v5O1YcXhEQDfJ3bsVIjgjxGldWVgZcS/E6hoeHo1AoJKGEYPeNv4Ha/Z5ICfPRF2s7ansd98lCJVdR3lbO6orVTIqexBMLnmBJ6vBJ12uVWn4949eMjRzLJ8c+YcXhFWe8wXwmv+9OhL4bDDKZjOlx02mwNwyrw36mYFQEYZAYEzmGB2Y+wCM7HuGRHY/w67N+TXb06SkgOAofrA4rT+Y/CcB9efdh0BqGtP2ddTt5dvez6FQ6Hpr9EPGh8UPa/nCjt4TvUUnskQGLxUJxVTGfV33OmKgxzDTNHLZjlbaUUmgpZFb8LHKNuZhCTOTX53PjpJGVy6aUK7kq6yr+b/f/8XHJx5KzNhj0JhFrMpmCGsnBtmtvb8fpdPaghhmNRimaIAgCZrOZnJwcytvLKbGWMMc4h6qjVVKhzaKiIg7bDuNxe5gaMxW1MtDA9n8WM9IzcLldVHRUMFE/MWAb//EsyVxCojKRVY2rONB+gNLOUpwyJ5EhkVw85mJpu+5RJLfXTYmrhK+7vubY3mPUt9dL45fJZISEhNDkaEKn1JGmSSNJk4RH8CDIBUIiQjjadJSqtioAVC0qQiwheAQPTq+Tg40H2V67PaDivHj8zpBOvqj/guLmYgRBQCVXMdU4lfL2ckKUwWV5d9fvRilXct3E62hoauDT+k9pdDXytz1/Y1P1Jh6e8zDRuuiAcyQiJSWF5q5m/rzrzxyzHSNdl87NSTcTpYiSouHidfR4PFJkrL9k9O7zqU7li8B0ebqCbn8yKGst471D77G5ejNewcvFYy7m+onXn5JnNUQVwgMzH+Av2//CRyUf4RbcXDv+2hE1TwwGZ/L7bqCiLQNBXlweX5Z/SX59viQz/13FqAN0AsiKyuKBmQ/w6M5HeXTHo9w7496A8P0oTh3sbrtUrPYXub+QVHKGCl/XfM3ze54nTB3GQ7MfGraK38OJvgzBoZpUR3HiMJvNrDavxu60c834a4bVwHj30LvIkHHZuMuk1cDPyj6jrK2MjIiM/hs4hZgZP5OMiAxWV6zmvLTzhqTOlmiMh4SE0NnZKUkpW62BuTRi3ohoULe1tVFUVCQZxmKOiUwmQy6XSyIIq2pWIXgFctQ5UuRBFB843HkYlVxFgjxBilaI8K9Bo+xUEqoJpc5VR2JiIkCvz2mEJoJrE66lTlPHP0r+QXVbNQqlgl3mXcxPmo9cJpcMv8NVh9nQvIH1leuxdlmRIycpLIlJukmYIkzEqeOoVFTyTdM3zEmYwx1T76CxrFFyjsRzIggCFrtFqvlzrOUY+xv343Q7qe6oZvmO5fx5zp+ZGjcVuUzO3oq9fGX+isP2w6hVaqbop6DVaSlrLUOPHrvdjqPTEfR65dfnI0fOosxFhKpDudR+Ke8Uv8NnpZ+xs24nP/niJ/wm5zdEOiLR6/VS3k9iYiJGo5GH1jzE/vb9eAQPBzoO8I+af/Cr1F9J18VkMqHX63E6nb48rdIiXqt+jbK9ZUyJm8LyecsROoSA89/dQRILunZ5h84BMtvMfHD4A76u/RoZMiZGT6SsrYxpcdNOqQOiV+l5cNaDPLrzUT499ilOj5PrJl53RqrDncnvu6GshTY+ejw6pY78+nyWjVs2JG2eqRh1gE4QWVFZPDTrIR7Z8QiP73qcu6bdFVC3YBTDD4fbwWM7H6OkpYQfjP0B85PmD2n7Gyo38Mq+V4jURvL7Wb8/4yI/vcF/Rbi7ITaKUw9PmIciWxE5MTnDWk+qyFJEUWMRZyedTUq4j7YzM34mn5V9xpbqLSPOAZLL5Pw4+8f86Zs/8fK+l3lo1kMnbXj5qyBOnz4dQIrkAAHGs38iPQTKXfvXEVIoFLS1tdHh7GBf2z5SdClMME3AZrNJRTObnE2YPWay9FnIBJlEw/E3psW+NdQ3MD1hOrvMu/CEeCTHz2w2097eLiXoi31Qq9VMS53GP4/+kzh1HFqZlhf2vsC/j/ybxamL0Sq1bKjawKGOQyhVSkzhJq7OuppFKYuI0ERQWFiIy+3i08ZPOeI+wqToSdw7w5dbojQpexj8/sVeZ8b7opUer4efrvkpB5sPYum08JtNvyFCEUGoLpRWZyset4ezTGcxRzeHcMJ5quIpZMjQuXQIXoHWtp40R6fHyT7LPrKisiRBkBhdDHdOu5NlY5fx+K7H2VG3g3u238MCwwLOjT6XGHVMgOT1/iaf8wPgxUu5oxy1Wi1dF/+IUWdnJ69Vv8aBjgN48bK9bjsPbn2Q20239yl7LravlJ28OVXXUcdnZZ+xvnI9HsHD1NipXJ11NZXtlfy98O+nRbBEjAQ9vutxviz/EpfXxc05N59xTtBwFFQ+E6GSq8g15rK9bjstjpZelRW/Cziz7uARhjGRY3ho1kPolDqeyn+KT0o+OeN5smcKnB4nT+Y/SXFzMRemX8iVWVcOWdtewcvbB9/mpX0vEa2L5g+z/3DanJ8T5S13388/wbs7F34UpxerLavR6rTcOPXGYTuGIAi8d+g9lHIlV2ReIX2facgkOSyZjVUbcbiDr8KfTmRHZ7MkdQkHmw4OiSx2X7LWKSkp5ObmBih/+UcU/PcTKXGiIQ2wp30PbsHNWZFnkZGRIUUkZDIZhZ0+J2NG5Ayp/lb3XBL/vi1NXYqAwLrKdVJfggkvqNVqYuNieWnvS9iddm5IuIHb4m7jvPjz6HB18EzBM/x+9+/52vo1BqWBS6Mv5flFz7Ns3DIiNBFYLBYEQeDzps85aD/I9Ljp/Oas30iJ9f4S0H2JQyjkCq7IvAKjzkiIIgRrl5UKewVFzUW0u9u5Nudafjfvd+Sk5lDpqKTJ2YRWpkUr1yKTydDpeybyFzUW0dnVSZwzTjq2+Knr0vGHyX/gmvhrUMqUbLJu4vnq53nb/DZ1qjo8Xp9TMil6EgqZz4FVyBRkRWYBoNFoAFAqldI1NhgMlNl9wgzgc2wONB0IKnvuf/84Pf8rK6AYmPRud3i8HnaZd7F8+3Lu2ngXayrWkBGRwR9m/4HfnPUb0iLSJOfKLQyP0lx/0Cl1/Oas3zA5ZjLrKtfxQuEL0jkeDpzJ+TpnAqbHTUdAYHfD7tPdldOK0QjQSSItIo2/zP0LT+Q/wbuH3qWirYLbptx2wpPhKPqHy+vi6YKnKWosYknqEn6S/ZMhowXYXDae2/0chZZCMg2Z3DP9ntO6QnKivOXu+4n0Gv9chzORCvBtw+HmwxTUFzArftaQ0zf9scO8g2Otx7gw/UJiQ2Kl72UyGUtTl/KP/f9gW802FqcuHrY+nCh+OOGHFDYU8m7xu0yNnRrQ/8Ei2CrwQCii3bfxV0sD36JJfls+IYoQJob68nZEWp3T4+Tr+q+JUkaRrktHqVT2oFN1R6wQi96t58tjX3JF5hVSX/R6fYBEs9Fo5MuyLyluLmZJ8hLSVGm+RbhOn2EdqgolLiSOjs4OmlxNfFT/EbW7asmNzWVs5Fjaatv4yvIVBW0FzEyeyd3T7u4hICPOJeLcITpsXV1dHCg/gPmoGYvcwoH2A5htZjxen9M3NWwqC9MWssmyiZUlK6loq+CWybdwhCPI5DL0Mj0yQYZMJsOl7BnZKKgvwOV2MUYz5vi57NaHBZELiFJE8X79+6CEGm8N/yj5ByurV7I4ZTF3Tb+L/yv4Pw40HWBi9ER+FP2jgLF0dh6XWQ8LC2NK3BS2123HI3hQyBRMjA6U0w12r7R0tQAQqhqcbL2l08KWmi2sqVhDs6MZhUzBrPhZLE1dSnZ0dsA7TRR8OJ1lFzQKDffPuJ9ndj/DlpotONwObp96+5Cr0MGZna9zJmBq7FTkyMmvz2dRyqL+d/iWYtQBGgLE6eP489w/87c9f2Nb7TbMNjP35N0TkJw5iqGB2+vmud3PsadhDwuTFg5p8nZtRy2P73qcOlsdC5MXcvOkm4dcTW6wOFHecl/7+dN8RnF68UXZFwABUZmhhsfr4f1D76NVaLl07KU9fp+fNJ93it/hq4qvWJSyaMQlOeuUOn46+acs37GcZ3c/yx9n//GUPJd9UWbEnB0RNZ01WF1W5kTNITU5FQCDwYDVauWA7QB2j50FhgXIZDLpmaypqcHr9UrtdI8ITQubxurm1Wyv287ZSWf36IvFYqGgrIA36t4gNjSWm/NuptJcyUuFL3Gs8xjGUCPL5/nqBh2sOsiW0i1UeCvYa9nLzpqduNwunIKTFmcL0apoouXRrKtah0FjIEwdJkVO2nXt1HXU0Sl00tjZiEfloVVo5WjzUTrdnQiCgEwuIzU6lamxUyltLCVCGYHNa+PKyVdysfti3jjwBltrtnLXhrtotDWSpE2iyd0EMohRxVBsKQ4Ym1fwsrt+N8nhycSHxkuOn78DKJ7D3MhcwqLDWFGxgjERY8gMyWRt5VreLnobr9dLWkQat+feTl5cHmGuMBrqG9Dr9bS1teH1eiXWRmVlJXdPuJtneEZymJbPW05dSV2fxrhYnyctIq3P+8kreClrLSPfnE9BfQHHmo/hcrsw6o1ckXkFi1IWEaWNCrqv6ACJ+UanCyqFirun382LhS+yrXYbf9j2B+6dce9JLUoEw5mcr3MmIFQdyvjo8RRZinB6nN/ZBftRB2iIoFPquCfvHj48/CEflXzEA1sf4J68e8g0ZJ7urp0R6K5UFAwuj4vn9jzHTvNO5ibM5adTfjpkPOTChkKe3f0sDreD67Kv44L0C0aEIXiivOXu+yUmJkrKVwqFYtT5GQGwOqzsNO8kJyaHpLCkYTvOxqqN1NnquDzz8qC1q3RKHWcnnc3qitUUNRYx2Th52Ppyosgx5nDp2Ev5uORjXt73Mrfn3n5an0//56u0tJR8az5yhZxzJp4jfZ+RkYFX8PLmmjdRK9RMi5wm0d8AqU6Nx+ORnCCZTIbH4yE8PJzpkdNZ37yejw99zPzE+chksoB58mj1Uf5V9S9cgos7p93JzrqdvL7/dax2K7lhuXwv/ntMjZ0KQHZyNtnJPrVSm8vGZ7s+o6S9hNVNq5Eh89Hgyj+HClApVShVPU0Du92O4PU5O+H6cMbEjiFOGUe4K5xpKdPISclhY9VG/tbxNzK0GZQ4SiioL2Bm/Ex+OfWXzIqfxfLty6m31xNOOC6vC6/MS7w6nn0d+2h3tkvS3WWtZVi7rFw85mJMMYHGsBh9878GueRixcqaijWkC+ncmXwnRzuPcsh2iCNtR/iw+ENWlqwkUhNJbmwumYpMXC4XBpkBjVIjXQun1clLS14KGLdgEvo0xktbS4nURPZwXsTaQKWtpRy1HqWwoRBrly+SFaYOY1LIJDJ1mUyMnEhuZm6f95uoMHe6F+TAF4X65dRfkhyWzPuH3+eBrQ/wq+m/Oik1XPG+9ndwR/NThxd5cXkcbDpIUWMR0+Omn+7unBaMOkBDCLlMzlXjryI5PJkXC1/kT9/8iVtzbmVB8oLT3bURD3H1s7KyMiDRV3zBuTwunip4ij0Ne5iTMIfbc28fEudHEARWla7i3eJ30al0/Hbmb0ekAXiy8M9rGF1VGxkQE53PSztv2I7h9Dj58OiHhKvD+V7G93rd7uIxF7O+aj3vHXqPSTGTRmSC81VZV1HVXsWWmi2khqfy/THfH/JjDGQhpvu2LpeLuq46PG4P7RXtFLcUM2HCBMBXnLnB2cDlky5nbPRYychra2vr0Z6/Q9TW1kaoOpQpYVMosBawuXoz2dpsaRGjuraalc0rafe2c8uEWyhqLOL9w+8TpY3i+snXE+OMkZ5zi8UiRZvkcjmJiYksHLuQPQV7iNZFc3PWzcQ4Y2hxttDqbMXmtdEldBEeEY4+TI9SrvRFHhzganMxNmEsGQkZQe+RjMgMlColmUmZVFVWsaZijSSUkBaeRrg6HKPWSJunDavbik1pI0WfQnFXMaWtpZKaqlhQdWrsVMy15qAiBBAoIiFSJde3rCdTn8mspFlkWbPwCl4aPA20hLdQUF/AxqqNbKzaiNvlxuvxRYhSQ1PRu/SkhKSgsWowaAxEaiJRKVR9LkJ1OjspbSklIyKDnXU7ae5qpqqtirLWMiraK3B7j+fsJIYmMj9pPnlxeYwzjKOpsWnA83GzoxkgoLbT6YRMJmPZuGUkhSXxtz1/Y/n25dw46UbOTT33hNoLRrMcXaQbXkyLncZbB99in2XfqAM0iqHDnIQ5xOvjeWLXE7yw9wUONh3kJxN/gl6l73/n7yj8a3OIfG9RqtRgNPBm+ZsUNRaxMGnhkEV+WhwtvFb0Grvqd5EYmsh9efedkNjBYIym04lRFZyRA4/Xw9rKtURpo5gWO23YjrOqdBVWh5XrJ17fJ1ffGGJkaepSPi/7nB11O5idMHvY+nSikMvk/CL3Fzy07SHeLX6XuJA4zoo/a0iPMZjcAzGvDqC2qxadQkekMlLKK3F5XPyr6F/QBXMi5vSoO+NffLN7e+CbE8/3nE+Fp4I3D7zJTbE3oUOHV/DyedPnHG4+zIWpF9Iqb+W/h/+LSW3i6uirGRs1NqDvopgDHI82HbQd5IjjCHNT5nJB9gXA8XnM4/H4tvdASnjKoOYMjUODy+GirKmMmfEz+br2a2o6akjQJ/Dyvpdxep38dcFfqemo4c/b/8xa81r+POfPrNuzjhJrieQAHWs5hhw5YyLH0OZtk86NRqORJKvF94V4rXRKHVelXMUThU/wResXPDLtEcLCwjCbzUzQ+1T5lk5cijxULkl4l7aWUtpSyhbzFlxuF6omFcpjx82iUFUoepUej8tDl90XhdHoNChUCjpdnTTaG6npqKGlq4WytjJpvwh1BJOiJzEmcgzpEelkRGT0oMQPZj4ubS1Fq9ASrx9ZSqQzTDNYPnc5j+c/zqtFr1LRVsF1E69DKR+caRksx20UwwuT3kSoKlSicH4XMeoADRPSI9J5dP6j/G3P39hYvZF9jfv46eSfkhube7q7NuIgvngNBkMAz9vj8dDuaOfV/Fepp55zU87lppybTtr5EQSBr2u/5vX9r9Ph6mB2/GxunXwrIargxfj6w5mUsHmmOGvfduTX59PsaOaqrKtQyBXDcgxLp4WVR1eSoE8Y0MrssnHLWF+5ng8Of8AM04xBGzGnAiGqEO6bcR+/3/Z7nt39LPfk3cO0uKFzIHvLPejruREQMHeZSQ1JlQqJAqypWEN9Rz3nGs6lrbEN4oMfQ/y/QqGQHJXw8HDfeBUhXDv2Wl48+CIr61Zybfy1bHFuYVfTLjJ1mTQ1N7GmZg1jwsdwcdjFqL3qHvOQmK8kRoA63B18UvcJKrmKmybdJEWIAKnuUHfnojf4nxeAmqoa4lRxHG06yjWTrmFzxWb+tf1fjIsbR1FjEeckn0NubC65sbl8UfYFO807eePAG2gUGrbUbGHZuGXIZXJKW0tJDEtEo9AESISLzqW/eIFYg8lkMhHpiGR62HQKmgpYXbGa6XrfynZbW5tEPzSZTCjNSjLJ5Jz0c4iJiWHbnm3U2epwyBxExEdgdVhp6WrB6rDS6e6kub35uAKbA6J0UURpo/DipcPVwWXjLmNC9AQMGgOaLg2OZgfx8fFDMseKuUNpEWkjMjKbHJ7MI/Me4ZmCZ1hdsZrqjmrunn434erwAbcxujh36iGTyUiPSOdw82E8Xs+wvYdGMkbeG+5bhAhNBA/MfID1Vet568BbPLrzURYmL+Qn2aPRIH/41+bIycnBYrFgs9mQ6WS8U/UOZreZRQmLmMlMmhqbTmqibO1q5bWi19hp3km4Opy7p9/NrPhZJ9X/U5Ww2d3Y6M+RCWa0nUnO2rcZayrWoJApWJQ8fAo8bx18C6fXyY2TbhyQelS4OpyLx1zMiiMr+LL8yz4pc6cTJr2Jh2Y9xJ+++RNPFTzF3dPuJs+UNyRt92aIBXtuEhMTMZvN1HTW4BbcJOmSpNpCrV2tfHT0I4x6I/OM8zCZTEHrbxUVFQXIWouOh81mw2az4XQ6MTgNTNBOoLCtkJerXqZV0UquMZcwZxibrZtJ06dxVcxV8D/73N8h8B+PeIw3j7yJXbDzs/E/I0ITQZG5SHK8zGaz1Ddx+76cv+7CDQAJmgQavA3E6GIwyA1sb97OTutODHoDP8n+ibTd/MT5lLWW0WhvRIaMTlcnBxoPkBKeQpOjiZyYnIDcEHFs3cUQ/OnTBoOBi+Ivolqo5vU9r6NL0BGqDJXyxfR6fUBETLyeWclZRJgjpHZnm2YHjLW7k2g0GrG77dy25jamxU3j1sm3SscoKirC5XIN2Rxbb6vH7raPuDpd/ghTh/HAzAd46+BbfFX+Fb/b8jvum3GfVG9sFCMTGREZFDUWUdtRS3L4mVfk/WQx8pYTvmWQyWQsTlnMUwufYnLMZDZWbeTeTfdS2FB4urs2YtC9toLZbKa8vZwnDz5Jo6yRyydcznzdfOml4o+B1gsQBIFtNdv41cZfsdO8k9nxs3lywZMn7fyAz2jKyckZdofC39gIZnj0tb0I/5Xmk62xMFqr4cRQ01FDUWMRM+NnDpvEemFDITvNO5kZP3NQxVW/N+Z7mEJMvH/ofeo66oalb0OB5PBkHpr9ECHKEJ4ueJpNVZtOqr3e6maJfwer/yI+9136LmRyGTkJvvMsCAL/KPoH7a52bs69mWlTpgVEMbo/j2K7RqORlJSUgJozIhYZFtHp6WR3225ihBgmGiay3badBF0C902/j5SEFKmWkeg4icexWCxS7a+S6hIKWwqZljhNor6Jc4I/Hc9/TgvWb/H86PX6HjVycpJyUKqUFFYWMi18GvXOelrdrVxouDAgyp4RmUGoOpSzE88GoM5Wx+dln1PW6qOSpUemS8e22Wzk5OSQkZFBTk4OYWHHc2H8z1NbWxupCamcHXo2dqedPe17pGsCSM4TEKDI57+/0+mkpqYmoO4QQG5ubkCdqC3VW3B4HCxNXRogyBHsXjkZSOcjIv2k2+ptzh6KuVwpV3LjpBu5NedWmh3NPLTtIXaZd51sl0cxjMiI9DnV31Ua3KgDdIoQo4vhgZkPcOvkW+l0dfLozkd5ce+LdDg7TnfXTju6OxDVimrerH0Tp9zJz6f8nGvGX0NoqK/GgvjyEjEQR6DF0cIzBc/w3J7nkMvk3D39bu6afldQRayhgH/R0aF0EPoryNe9D2KeQXejTXSATrYQ6kDO/Sh6Ym3FWgCWpC4ZlvZdHhdvHngTjUITsOI+EGgUGm6bchtur5sX976IV/AOSx+HAqnhqfxh9h+I1ETywt4XeOvAW4Mqzuhv9HW/l7v/3dcih1wnR6fTkW7yGajf1H7DDvMOZsXPknKpgj2PwSIr/scRHaIObwcf1H+AUqZEK9di6bLw7pF3SQhP4PHzHken1AW0E2xBScRR4Shuwc0swyxpjjKbzSQmJkrGvf956W0e8S/M6h9pMplMCB0CbpebqoYqSm2luAQXOoWO+WPmB5w3MaIRqg7lR9k/QilXsrZirSSAkB6eHnSe83foxDxRfwfEbDaTpcsiVBlKflt+wD1sMpkkFTmVStUjMg6gVvskgbsXnvWHIAisrliNTqljbuLcgN/8i8cOxdwvGqdDEQHqbc4eyFw+UCdpcepiHpz1IGqFmifzn+Sjox+NFogfoRDvqWOtx05zT04PRilwpxBiNGiKcQov732ZjVUbyTfnc8nYSzg/7fzvrBa7CK/g9cmIl35ETEQM9+Tdg8FtkJKGwbeC509HELnywRyBTlcnq0pX8VnpZzg8DmbHz+aGSTcMm+MjYrgUbbrTc/pqU6R5qNXqHtsNFWVvtFbD4NHl6WJj1UaSQpOYEDVhWI6xqnQVdbY6rhl/DTG6mEHvPyF6AhekX8DnZZ/zRdkXXJRx0TD0cmiQFJbEo/Mf5an8p/is7DOq2qu4c9qdhKr7L0rpb/R1v5cHc2/712g5VnOM53Y8h06t48ZJNwYcq/vz2Bsd1X9+c4Q5eLnqZdpd7VwYdyGpxlQe3/s4AgJXaa/C2ebb39Jp4bGtj1HeVc6k6Eksn7dcuvZSDpDgZUvdFiI1kZhcJpyu4HNUd2NYnHvb29ulc+JP1xOdEPGcCW4Bp8vJHscequxVZEVm4cJFi6qFWI7Xi4nSRhGhjqC0tZSbcm5iXcU6dpp3svLoSrRKLWkRaVIOkHheRNU9fzidzqCCEjMiZ7CtbRtmhZkEbwIhISE96HQiVbB70dtgssz+2Fi1kbLmMmaEzaDD2oHOGCgwMpRU42Otx9AqtCSEJgxo+2COdTAqoT9NciD3e/cx9UWNnBA9gUfmPcIT+U/wweEPKG8t5+bJNw8qL2gUww+jzkioKlSKMn7XMBoBOg0Qo0G35/qqKL9T/A53briTdZXrBrWC+W2C3W3nqfyn+KjkIzIiMnhk3iNkGjJ7rMyJE7WoVCRSI/wnYJfHxWeln3HH+jv4z9H/EK2L5r68+4Y16uMPcdXSYDAMmAox1HQy/xdddwwVZc+/nVE6XCB6Ox/7LPvodHcOW8HRRnsjK0tWEq+PPynH5erxVxMXEsd7h96jxFoyhD0cekRoInho1kOck3wO+xr38estv5aiCN3hf12608/8V+0H84w4Pb75SSFX8LfCv2Fz2TjPcF7AXON/rGD0Mf++1dTU4HQ7WWdZx193/xW3zM2Vpiu5OO1iNHoNEboINHINX9Z/SXFlMSaTiddrX6eovYjWrla+qf2G+zfcL7UHvtpfh9oPUW+rZ3HKYhLjE3udo/znDv/v/aMh/nQ9IMCRVKvUtHnaKGkvIdeYy7Vx1+Jxe/im9puA8yaTyRgTOYaKtgrane38bubvCFOHUdZWhlqhRqPQBGwvvgvEaIL4/IiFnf0jZ7m5uVw35zrUKjX7nfuZPn06brc7IGrVnSoIPidPPGf+lDv/+6DR3sg/D/4THTrmhc8LGjUZKhpcp6uTYy3HSI9IH7AAQk1NjRQdE9GdSth97AO534NFFrtTLf3nPGOIkYfnPMzs+NnsMO/g3o33sqNuxwmdh1EMD0QhhPLW8u+k7TnqAJ0myGQyzk46m2cWPsN12dfh8rp4Zd8r3LvJN0l8l0LG9bZ6Htr2EPn1+cxNmMuf5vxJkgwVJ13xBQe+FUmZTCYVDRQnXK/gZWPVRu7aeBdvHXwLtULNbZNv44mznxiyJOnBICwsbMBUiKGmk4k0D/FzuOHf/1FnqPfrmV+fD/jkY4cDbx18iy5PFzdMumFAwge9QaPQcNf0u5Ah48n8J6U6JCMVKoWKn07+Kbfk3EKHs4O/7PgLr+57lU5XZ8B23Vex/Y2+/p7B3u5rj+AzHD4u+ZhKZyVnRZ3FuZmBqnvisQCJvtV98UY8fl1XHa9Wv8rG5o2YQkzckHAD2aHZlDWW8d6h9xhnGMfPJ/6cVk8r/6z/J82KZsq7yiWqlxcvxc3FAePR6/Xsat2FUq5kccpiqT8ZGRk95ij/ucPf0enuLIlOIyBFX9qcbayqX4XNbSM9Ip1lhmXEyeMIIYSC+oKA95rFYiHJlUSXs4sNVRtICEvguuzrEASBYy3HaHO2BdCJRSqe2A+5XB4gZtD9ukTroplhmkGhpRCzLTC6ITlrfotqfVHeRAiCwCv7XsHutnNj9o1E6CKCOjlDtci0uWYzXZ4u5ifN73/jPtDdeQnmkPc3X3dfJOjPIQLQKrXcOe1Ofjn1l3gED08XPM2zu5+lzdmzDtYoTg8yIjJwep3UdtSe7q6ccow6QKcZKoWKCzMu5PlFz3N55uU0O5p5uuBpfrf1dxRZir71jtCBxgM8sPUBqturuXb8tVyddDWHDx4+vooUxEjxeDyoVCpUKhUej4faulp2mXdx36b7eHHvizjcDn404Uc8e86znJNyzimXdxysWAEMfeLsULc3mOON5gYFP/9ewcvu+t0khyUTGxLbx94nhl3mXeyo28FM00ypnsrJICMig59N+RnWLitP7npSinSMVMhkMs5NPZcnFjzBxOiJrK1cy32b72NbzTbJOejruejPKOztvlYr1LQ721lfuZ6cuBx+s/g3PShtYlv+q/Ld+xAWHcaGlg28Xvc6Da4G5hvm89OknxKviccrePnI/BGdXZ3cNuU2rppyFb+e82u66OKPX/+RKE0UCplvnpMjZ0LUhIBIjrnVTElnCZkhmXg7vAFj6z6u7ufI31nyV6/zrxskl8vZZdnFb7/5LWWdZegUOi7MuJCkhCQ0Gg0z4mdQ31lPTUdgVGKsZiwaQcPairV4BS85xhx0Kh2d7k4e3/k4dXV1kmMiOkBiPxITE3tEoLpDzLPbXL25h8iE/7slWNQ+2D2wvmo9ey17WZC0gEVZi4ZV/KahoYF/7/03KkHF3IS5/e/wPyQmJqJQKPB6vezevZvCwkKAgL72J3LRG/paQOjt2ZLJZMxLnMdTC54iLy6Pr2u/Ho0GjSB8l4UQRnOARgh0Sh1XZF7B0tSlrDy6kjUVa1i+YzkpYSksTVvKvMR5fRYyPBPxVflXvLn/TdQKNffNuI/pcdMDZGGDvVj8uco2l40vjn7BXvterJVWNAoNy8Yu4+IxF59wTZ+hQHc+9UByCXqT3w3GRR/IC/dU11Xofrzvem5QsPN/1HqUNmcbi1MWD/nx2p3tvLrvVUJVodww6YYha3dO4hwq2ytZWbKSl/a+xC+m/mJE1iLxR2xILA/OepC1FWt599C7PLfnOT499inXjL+GKcYpvT4X/tcs2DzUW56EpdNCo72RidETuXv63T0ib8EMTIVCIbXb5eniy7Iv+eTYJ9hcNtIN6VyRfAVam1Y61ucHP6fSXslMw0y8tV4sXgtzEueQEp7Ci3tf5FDzISI1kTg9TiYbJ7N83nLqSnwqflarlSZ5EzK5jBlJM6T+iDk83cfV39zRPXeqpLqEL1p9NX10Mh2Loxazw7aDLlsX5k7fNmcLZ7OtcRsF9QUkhSUFHHdxymJWm1ezp2EPFW0VxIfE0+RoYmPVRuKS45ijm0NISAhutzvonBIeHt5r8cwJ0RNQyVUU1RWRac+UaiH1de3huNCCOF6j0cg+yz5e3/86UdqoQYuL+LfbW95Md2wv2069o5650XNpt7Zz1Hy0xzslWBuiU+Nf/LavXKTB5Lv1tW1/902kNpJ78+5lW+023tj/Bk8XPM2chDncMOmG0dyg0wh/IYQFyQtOc29OLUYdoBGGCE0Err1CXAABAABJREFU10+6ngszLuSz0s/YVL2J14pe4+2Db7MgeQFLUpeQHHZm67Xb3XbeOvAW66vWExcSx30z7pPG1N9kbDQaaVO18e+Kf7OtZhsur4swVRgXj7mYC9MvxKA1nMqh9NrHYGIFfb38eqvxMxSCCqe6+OloUbvgKKgvAGB63PQhb/uN/W/Q6mzl9tzbh/wZuDLrSqraq9hWuw29Ss+Nk24clvyloYRcJmdp2lJmJ8zm45KP+ar8Kx7d+SgTonwCD9PjpqOUK3t9NoLNQ8Hu6xJrCV+WfYkMGddOuDZojmFvCyKtXa1srNrIF+VfYHVYMeqMLEtaRoonhYSwBIwZx49V7ClGqVByTtQ5AY5ZUlgSD895mM/KPmPF4RV4vB5mJ8xGLpMHCBUcsx5Dp9MxNXkqYa6wAAEDkb7W1zzl/504nihjFPvs+3iv4T3aXe3MNM1kUegiGmlka+tW2q3tOEN9fZ2YPRGtQkt+vU/0x/98JtgTWGtey+ry1YSpw1Ar1VyRdQUfHP6Az6o/wxPjYaFiIbm5uQHnVZwbxchQMCjlSlLDUznacBR3qFuiTfeXyO/vrJpMJoqbinky/0k0Cg2/Oes3AxLZCIbBiCPsd+5HJpdxUeZFAfuJ+a81NTV9OjX+xW/7cm4GM1+f7NwuRoMmRU/i1aJX+br2a/bU7WFJ+BImhk2UaiyN4tTBqDOiVWi/kxS4UQdohCI2JJYbJt3A1eOvZlvNNlaXr+ar8q/4qvwrsqOzWZq6dMRWa+8LB5oO8FLhSzTYG8iJyeGuaXcFvEx6m2CdHidf137N6vLVkmRjpiGTJalLmB0/G5XixPMdThW6rwT7v3i7/9ZdnUqv10vVzMUk7RM57ujL5fShoL6ACHUEYyLHDGm7u8y72Fa7jelx05mfeHK5AsEgl8m5c9qdPLbzMVZXrEYhU3DdxOtGvBMEvgKNP87+MRekX8CHRz5kS/UWipuLMWgNnJtyLqYOEzpB1+PZGIihd6zlGI/sfASlQkmcPg6XxxV0O/+2BEGgWdHMBxUf8E3dN7i9biLUEVw/8XrOTTmXQwcP4XQFPqtV7VWUdZaxeMxiMpMyqampCZgHFHIFF4+5mGmx03hx74t8XvY5X5Z9yYz4GUxUTiTaFU2DpwGlTElyWLI0V4rGdPe5Idh8EfBdhpG9ir1s2LuBDlcHYeowbhhzA4nuREwmE7v270LwChjVRtRqNXq9nkMHD5EZmkmR1SfU4O8oRuuiyTPlscu8i/SIdHRKHZeMuYRNVZvo6OxgXdM63LiZIkzpUW+ntzGIsFgshNhDcAgOOoQOwuXhyOVy9Ho9hYWFATlE4qfo5Il0xf3N+/nHsX+gkCn49Yxfkxqe2ud90Rf8neG+FqasDiv72/ZzVvJZ5KTkBGzrT6PsDcO5CBWsKKz/bwNZbPOPBj2//Xne73ifSaGTuES4ZPQddYohk8kwaA1YHdbT3ZVTjjPLev4OQqfUcW7quSxOWcwR6xG+Kv+KHXU7ONh0kHB1ONPjppMXl0eOMaeHas5Igt1t571D7/FV+Veo5Wqun3g956Wd1yedptPVSaGlkIL6AvY07MHmsqGWq1mUvIglaUtGdGVs6Pky8H/5dTcyelsl7o+WMxAMhuIwiuGB2WamuqOaRcmLhpRC5k99uyXnlmFzSkSa6l93/pUvyr/A5XVxU85NI54OJyJGF8PPpvyMa7KuYX3VetZUrOHDIx/idrmJU8YxK3EWEW0RJIcl09jYGNSI83+emxXNPLrzUTxeD7+e8WueLni6Vw69y+PiQNMB8uvzKagvkAQlJkRN8C1kxc+QaHPBntU1FWtwu9ykO311hhQKRdB5ICksiTsy72BTySaKnEXsqNvBRvtGopXR1DvrGWscK+VDivOKaMy6XC4KCwtJTEwM2gddlI4DFQc42H6QAyUHcLldJIYlcl32dSxIXkDpodLjIigeCzKZjExjJlljs6R5K1mWzD72sbt+N+eknBNwPpemLmWneSdHrUdJDksmJTyFSTGT2Fu3l2hFNFtbt9K5pZNzw87FFGGSKG+iQ9C9TpEIs9lMnDIOr9dLSFII0+KnBdDbRHSfk3Nycqiuq2Z9/Xq2tGwhJjyG38787aAXL4JRmP3zqHqbz9dVrsMjeFiaujTgevmP63TN5/70uu6OY/f3Wl8OkRgNMs0y8creVzjQcYC62jpkJhkz42ee8nF9lxGpiaSqvep0d+OUY9QBGgRONZXIHzKZjKyoLLKismjtamV95Xq21mxlQ9UGNlRtQCVXkROTQ15cHtPipo0IKpgI/6jPhKgJ3DblNkz64JN3cVUxm0o2cch2iFpPLTKFz6BLC0/j8nGXsyB5AXpVT2nnkYjuL4O+XmLBaHNiEm4wB2owGKWknX4MF/2tN+rbcMxVWqWW3878LU/seoK1lWtp6WrhF1N/cUblJkZqI/nBuB9wyZhL2N2w20fBadjDZ7Wf8VntZ77aNK4IYpWxpHaksihsERGaCOQyufQ8rz6ymi9av0AhU/Cbmb9hYvREjDojZa1luLwuLJ0WSltLKW0ppbS1lLLWMhweBwCmEBMXpV/EOcnnkBzek8rc/Vm1u+1sqtpEjCKGOEVcQBQgWDS4ob6BMZoxTAibQExeDB/s+YDN5s00OBtwNDq44csbSI9Il/4lhSXR7G5GcAvIZXJaKlvIGJeBM9bJRutGysrLKG0tlVaH5cjJ1GYyLXQaWRFZTM6Y7BuX39xUdrQMo9qIvcMeUEh1fsZ8vtj9BQX1BZyTco40hsrKSpKSk0gMTeTr2q8lJ2Np6lJ21+wmOywbi8tCvjmf/Q37uTDmQiaFTpKMb7F98e/29nasVisGgwGTyUSKIwVVi4rS1lLSlekBzo8ooe0f6TKZTJS3lvNa9WtUtleSHJbMA3MekHKX/NHfc9YXhbm3+bzR3siq0lXE6GKCzhcnMp/3R2scaHvi9ZTJZBK1rntOWPeFvP4W7cYmjuWxhMfYWrOVNw+8ydMFTzPTNJNrxl9DfGj8oMY5ihODQWuguLkYl8d1RrBphgqjDtAgMFKoRBGaCJaNW8ayccsw28wU1BdQUF9AYUMhuxt2QxGMiRjD9LjpZBoySY9IP2HO8slgIFGf1q5WylrLONR8iN31uznUcAjBK6CQKRgbOpal2UuZFjfthAo6nm4MNGG0Lx56Xw7UKE4ep2pRo6C+wLdIYcwZsjb7or4N11ylUWj49Yxf89K+l9has5UHtz7I/TPuJ04fN2THOBVQyBXMMM1ghmkGLq+LQ02HKKgv4EDTAUo6SjjYfhCs8K75XTRqDZGaSLRoqW2tpc5ZR4g6hNkJs1lbsZZPSj6hsq2SBnsDP/zshwFROK1CS0ZkBlNjpzI9bjoJ+oRBRem21mzF4XFwSfolaNAEpcz2lrtkDDWyOHIxUZ4o/mX+F7MSZqFS+ByB4uZiaR+3yy0VF1WpVFAJLrcLlVKFRq0hOSyZqcapxMhiiHPHkRCZ0EN0QJybWrtaaXO3kRuWCxyPFigUCjqaOkgJSWFf4z5cHhder1fav6G+geuyr2Nz1Wb2m/dT31DPjPgZRIVEsadjD3ek3MEk+yRWWVbxcdPHHLIf4kcTf0ScLq4HDU50NqxWKxkZGSyKXsRblrcoay2jxnGcPpaSktLj2ej0dPJpxaesq19HZ2cnC6IWsDhucVDnRxzfQER7ghVUDTaf+8ts3zntziFTMO2X1tjLHNF9fhTPtVqtliJZYlvdGQvgUyB0Op3o9fp+o0Hzk+aTE5PD6/tfZ4d5B7vMu1iUsojLMy8fUQu630YYNL7za+2yDotC6UjFqAM0CIxEKpFJb+KijIu4KOMiOpwdAZSxY0eOSdvFhsSSEZEh/RtupyhY1Een1LHPsi9gdbTJ0STtE6YKY37CfBK8CYzVjyUjOeOMNvgH6rAMxJgZRd84UUfmVCxqOD1ODjUfYrJx8pDRVFu7Wvukvg3nvaNSqPhF7i9IDU/l3eJ3eWDrA9w9/W4mxUwa8mOdCoiOqeicOj1O1hSsobKjkiZPE+pINY2djext3EuzvRmNUoNepedA0wHA5xRqlVpUchXjo8ZLi04ZERnE6eNOiia4qWoTWoWWs4xnYbUc5+j7G5b+6D7nmEwm3M1udHIdC40LWZy1mPqGeg5WHcSus+NQOXB6nDg9TgQEVHIVDeYGtIKWBE0CeWPzSIhLCKCNifWLusNisbC5ZDNyhZxkfTKJiYlAYK5RijyFEkcJq3atIlWdGtBPb4gXvUJPu6ud/xz4D/Nj5jMvbh5fVH1BnaeOpeOX8v2Z3+eN/W/wde3X/Hnfn8mJyeG8tPNIlidjqffVpvGPAIFPCCFCE0G7sx3+9/j5q/ABlLaU8q89/6KwtRAPHsbHjueq7KtQd/RdSmAgoj2DmVc2VG1gr2UvC5MWMjV26oD36w/B+ukfSSwtLcVms0m5puDL7+mPqt3fGP1rSvkXX+1t+0htJL/K+xWHmg/xTvE7rK1cy+bqzVyYcSEXj7n4jGF/nGkQHUyrY9QBGkUvGOkr8KHqUOYlzmNe4jxcXhcl1hKfs/E/GsaOuh1sr9subR8bEktiaCIGjQGD1vcvUhMp/R2hiRiwyILL46KlqwWzzcxHRz+SjiPKkD78zcMBzo4cOYlhieTE5JAemc6YiDGMiRwzKGPhdFIShxK9vURH+v02knCijsypcDLL28rxCB4yDZlD0p5X8PL8nudpdbZy59Q7g66ODve9I5PJuHjMxSSHJfPs7mdZvn05F6ZfyNXjr0atUA/bcU8F1Ao1Z6WfRYo5xXd/YOblfS8TqYnke+nf45acW5DL5ciQoZArkCFjX+M+HtnxCLMTZnNB+gUDPlZfc5jb66a8rZyMkAzqa+qB4xQq0bC0Wq19iqIYjUa8Mi+CINBm9Rm2DfUNhBNOjCeGnOwgjkzMcWenqaGJhLiEPusXiTCbzVR0VOD1elmQswCjwdcnWaiM+zfcT3FzMalhqThcDqo6qkiPSQ9QoDvUfIhoXTQul4s1jWsYqx1LrDwWpUpJu6bdd54wcee0O1mSuoSvyr9il3kXRY1FRGmjmBI+hfAj4eQm5zJt2rSARQGVXIXT6yQ8PByr1YpcJ6ewoZCy1jLy6/MpaSnB7XKTqEvkvPTzyIvJo7Ghsd93y1A+Z432Rt46+JZPZnviiclsd4f//dXdafWPJFqtPudajJ5BYF5Pb1Tt/o4rOujdc1v7w/io8Tw852EK6gt479B7fFzyMWsr1nLJ2Es4P+38M36OGWkQ3yEtXS2ntyNDAEunhbf2vTWgbUcdoG8pVHIVE6InMCF6gvSd3W2nrLWMstYyySkqaizC7XUHbUOGjFB1KGq5GoVMgUKuQCFTICDgFbx4vB48ggenx0mbsw27206jvRG3141WqcWoM1LTUUNDZwMmvUlydtLD00mLSDvp1fCRQkk8WYw6OieGYNK8A3Vk+jIMhhpicvxQiXZ8UvIJRY1FLE5ZzJzEOUPS5oliauxUHp33KH8v/DuflX3G7obd/GzKz8iKyjqt/ToRdHdGQg2hvFP8Dmsq1qBVaLlp0k0sSV0SlMI2JmIMSpmSneadg3KA+prDqturcXldRHojpe/E+9tf4tp/32AOVWh4KLJ6GfHGeGnf/qIWYrvdt+keOfE/ZkhICEfsRwjVhJIWnib9/uDWB9nduBuP4OFQ6yE0Cg317nop90bMdaxR1qBSqbh8/OX859B/+G/Tf/lp5k+xV9o5UHeA8bHjpbFmR2eTHZ1Ns6OZ9ZXrWVu5ls8rPkfwCrxf+z4xxTGkh6cTrglHrVBT2VaJ0+vkpc6XqOmsod3bjk7ny10LJqxzooIzJwp/6ttd0+4askjHYCl6/hGgYJS2wR4X6DG/irla/bUrk8nIM/lymrfWbGXF4RW8U/wOX5R9wRWZV7AgacEpL3L+bYVEgTuDleBau1r5uORjVlesxt5hH9A+ow5QL8g357MofNHp7saQQqfUSS8OEYIgYHPZsDqsWLustHS10OxopsXRQnNXM21dbbi8LjyCB6/Xi1twI0OGWq5GrpSjkCnodHXS0tWC3W0nShvF4pTFLE5ZTLQuGoPWQIgyZFjUqUYpYt9tdFdtGmnUNxFlLWUApEekn3Rbh5sPs+LwCpLDkrlu4nUn3d5QID40nofnPsyq0lWsOLyCP3z9B85PO5/LMi8jTB12urvXJ/wdBvGeqK6uZt3RdaxrXUebp42J0RO5bcptyGwy9u/fHzQqEKoOZWb8TLbVbqOqvWrAtdr6msNEuf+chBwUjkBDrzcnJdh9HRoeikqpoqmxCUu4ZUBGbfdtRDpUMPlm8ZhV9iqscitLUpcErNDvb9qPR/hfUU7Bg8vrok3d1iMXpaK5AoApsVNwepx8Uf4FW+u2EqWMotZRi1rdk44WpY3i8szL+cG4H7D94HYOWQ5hlVtpVbRS0lIiiU/Ud9YjCAKlslIMKgNTY6YyKWESGREZpIWn9YgonOp3y/qq9T7qW/JCcmNzh6zdE6Hoide3OwbDuOjtuCcy78plcs5OOpvZ8bNZXbGalSUreXnfy6wqXcVVWVdxlumsM0KSfyQjUhsJ+HKAzjTY3XY+K/2M/x77Lw6Pg4yIDC4efzErWdnvvqMOUC/4R9E/mJQ06VvPh5TJfFGeUHUoyQyuwGpVexXvH3qfkpYSVHIVV2ZdyWXjLiNaFz3ofpwInW00cvLdxskYKafSwCltLSVaGx20SOZg0O5s59ndz6KUK7lz2p2nRPZ+oM+lXCaXatG8tPclvij/go3VG7lkzCVckH4BWqV22Pt6IuiuYPVN6Td8Wf8lVfYqNAoNt+TdwpLUJchlcoqO9R0VOC/tPLbVbmNNxRpunHTjSfetrNXnOE9NnUpdSV2PY/tLWYtqkcHua7VcjcvtotPZOWiHP1ik1D86AsdV2Ha37cZutzNePj5AvXJS9CS2123HI3hQyBSY9CYaOhvocHYQqg6V8pm65F0ARGmiuHr81exu2M36lvXEKGOod9czZvwYQlQhvfYvzBvGlLApABgMBmzhNoxxRiKiIrh/8/2o5CqeWvDUgIzlvhyDoaZcFzcV8+b+N33Ut+yhob6J6O8dGWxM4jMhRhgHI5rQ33FPZt5VKVRclOFTUFxVuorPSj/j6YKnGRs5lmvHX8vEmImDbnMUPnjaPdjtdqqbqk93VwYMl9fFusp1fHTkI1qdrcTr47kq6ypmxs+ko71jQG2cGUUcTgPsHjvP7X6uV3rYdxmN9kZeLHyR+zfdT359PjPjZ/LUwqe4dfKtPZwf8eVssVj6bNN/ch3FKAYCo9E46MjPUOw7GDg9Tqrbq0+a/iYIAi/ufZEmRxM3TrpxwBGGk8Vgn8uksCQenvswd069k3B1OO8ffp87N9zJl+VfYncPjJZwKmEymVCpVNhCbLxW+hrvWN6hUWhkdtRsHpv9WIBqpclkChqFEJFpyCQ1LJVNVZsGPNa+zm9pSynh6nCitdG9HlsUJ/A3TLvf150dnSCAV+YdtOEZrH/+fREFDhyCg91Nu0nWJKNz6CQD2mKxsHzecmbFzyJSE8ms+FmSkS86eGI+U1OnL0c0UhuJVqnlvrz7CNOGUemuxC1zS9v31j//sVmtVpxOJ5Z6CzqlDkEQ0Cq0JxUpGI53VGlrKY/tegy5TM69efee8iT/3q6v/+9wXP5aoVCg1+v7fKcHe+eL3wEnPe+GqEK4MutKnlv0HOennU95azkPb3+YB7c+yObqzb0WJB5F72ixtKBESV1r3enuSr9odjTz7yP/5o71d/DG/jeQy+TcknMLTy54ktkJsweVRz4aAeoFF6RfwNr6tXxw+AN+OOGHp7s7IwJtzjZWHl3J6orVuL1uJkVP4toJ1/ZZHG6gq0ajdLZRDCdOl2BGeVs5XrxkRJ6cA/RFma9+yrzEeSxMXjg0nRsATuS5lMvkzEmcw4z4GWyo3MCHhz/k1X2v8nrR60yMmci4yHGoFWrcXrcUFZDL5OhVegwaA5HaSKK0URi0hmGNclWbq1l9dDV77XsxO32G3tyEuVyVdVVQWe/+VtNlMhlL05byatGrbKvZxrmp5/bbh97Or9vrprK9konRE5HJZL0euzfD1R/ONqeP6izYBix33Fv/eosYHHIfQqaQMTt6NgaDQUqqF+mpLy15SWrzYNNBVpaspKytjBxjjnQMuVaO0q2U6kolhyfzm7N+wwNbHqCyvZIddTt6rPIHyH779cdfdtoreGlztvVae26gGOp3VHV7NY/ueBS3131CRVaHAv5j8r+2KSkpAWMVHV21Wt1Dza03SqR4b4r7ejyeIaUcR2giuGHSDVyUcREfHf2IrTVb+Xvh33nrwFuck3IOS1KXfOsZPEOF+Ph49Mf0MEK1JQRB4GDzQVaXr2aXeRcewUOUNoofTvgh56Wdd8LviVEHqBdcNu4yyrvK+fTYp0yImsC0uGmnu0unDXa3nS/KvuDTY59id9tJj0jn2vHXkhOT0++K2kBfGqN0tlEMJ06XYMZQCCCUWEt4p/gd4vXx3Jxz8ynluw/2ufQKXiraKgKKgNrcNjpdnbQ526RVfK1SS6gqlBBVSJ9Kk/H6eEm2f0ykTynyZJwil8fFgaYD5Nfn89WRr3C4HWiVWs7PPJ+laUtPOrI2N3Eubxe/zVflX7E4ZXG/16q389vh7MDldfVZX8l/Vd6/mGd3TEmZwts1b9Ouag/Ytzej1b+d7v3rvp3RaCQ6JppX1r9CmDqMiaETCQsLIywsrNd5Pz0iHRkySltKA9TC2qva8Xq8AduOM4zjrul3cffGu/nwyIfMTZwbILDRvX/BzmdVexVdnq6TzsEbyndUaUspj+x8hE5XJ7+a/ismRp8e+pb/mPypjWKURozc9KXm1v2e8H/ni78pFIo+o6cng9iQWG6bchs/mvAjNlVvYnX5aj499in/PfZfcmNzWZq6lNzY3JOSo/+2w2g0EhkRiUo5soqgdro62VyzmdXlq6np8ClRitL302KnnbQIxqgD1AtEnv1vt/yW5/Y8x1/m/YXE0MTT3a1Tig5nBxuqNvDfY/+l1dmKKcTErTm3Mith1oAnk1HHZhQjAQNZyR4sBtKGaPCfqPFldVh5suBJZDIZd067U1odH6r++hug4or5YM+Hw+2gqLGI/Pp89tTvodXZKv0WoY5gsnEy8fp4IjWRdLo6Odh8kOKmYjyCB0EQiAuJY2LMRFLCUghRhWBz2rB2WbF0WihrLWNb7Ta21W4DfOqWk42TmR43nemx06Xk3d4gCAL1nfUcsR4hvz6fvQ17pcT4+LB4pminsDRzKcmmoaEU6pQ6zkk+h8/LPmdz9WYWJC+QfhvMuXZ6fQpaorPXW+TF4/H02Lf7tpmJmcRGxNLgbpC26ctoFdFob5TkqydETeDxcx4Put3q8tU02BuYoZ2B1+2lpqaG3NzcXsemU+pICE2gtLVU6ofT6cTlceF29aSci/ePw+1g+fbl3JN3z6CEAkpbji9CjITSCQebDvLYzsfwCB7uzbt3xCyu+tcFEqXVe1Nz68s5CuY0n4rzHaoO5aKMi7gg/QL2N+5ndflqqSZirC6Wc1PPZWHywpPOxfy2QiFT4BW8/W94ClDeWs6aijVSIWi9Ss+F6ReyNHUp8aHxQ3acUQeoD8SGxHLXtLt4ZMcjPLHrCZbPXT6sxUNHCkpbSvmq4iu+rvkap9dJpCaSm3Nu5pzkcwZcF2gUpwYj4YV+JmIoIkIDaaOspeyEBRBcHhdP5j+J1WHljql3nPQKdrD++hug4t8DOR+CIFDcXMzq8tXk1+fj8vp498lhySxIXkCmIZOMiAyitFFBoyCdrk72WvZKBsr6yvXA8fpgGREZjI8az7zEeWgVWtpcbTTYGtjXuI899XsoqC8AfPVCFqcsZkrMFDrcHVgdVpodzVS1V1HaUkpZWxk2ly/HRIaMcYZx5MXlMT1uOmq7mvr6erSKoRVouGzcZXxd+zVvHniTSTGTpLzIwZxrp8e3jUquCtjXf5/uUtjiZ3e6kUwmIyMig4NNB3F73SjlyqDGbnen+JmqZyiwFODFS4GlgAe3PshLS14K6PPByoO8WvAq0fpo5kfNB2Fg5yg9Ip2tNVsJiw6jvakdpVKJV/AGre+iVqjRKrXkxeVR3lbOE7ue4IqsK/h+xvcHtALsH4U1l56+0glewcuXZV/y7qF3UcgUPDDzgQBF1tOB/iKBvRXchcD7OSUl5ZSJRvQHuUzOZONkJhsn02hvZF3FOtZVruPdQ++y4sgKZsfPZknqEjINmaPqcX5QyBSSWuPpgMvjYod5B6vLV3PYehjwPbNLU5cyJ3HOsNChR63ZfpBjzOG6Sdfxxv43eG7Pc/x6xq+/ldrzTo+Tb2q/YXXFakpaSgAYFzmOpWlLmR0/G5ViZIVGR+HD6aJ2jTR4BS8yZAEvtGASx/4r3t0NwMGiP3qnIAjU2eoCanENFIIg8PK+lylpKeHSsZcyN3HuoNvojmD97V4HpD+KikhJWFO+huoOn2LQhKgJnGU6i+lx0/ukbPkjRBXC7ITZzE6Yjcfr4bD1MIebD1PaWsqxlmNsqt7EpupNAfvIkaNX69Gr9dhcNlocLawpX8MXZV8gl8kJU4URpgmTnAaNQkNqeCpjIsYQLYsmxhnD2KSxxyk/5QOr9TJYQy5UHcotObfwRP4TvFb0GvfPuB+ZTDaoc+0RPLhdbsy1ZixRlqDXrrsUdl90o4yIDPZa9lLdXk1aRFpQY1ccp+hAFTcX48W3IuzFy4GmAwF9rG+o54XCF3B6nFxkuIiM5IwB58gkhSb5xhniISchh6KiIrx4Ebw9PSiFzPe+DdeE88c5f+SZgmd479B77Kzbyc9yf9YvbbG0tZRQVSixIbHITfLTkmtqtpl5ce+LHGo+RGxILHdPu/uk8wKHpF/9RAJFgQqbzdbjOfB3wA+WH+SVLa9Q2lkqRQtHwrspRhfDVeOv4geZP2BX3S5WV6xmS80WttRsITU8lYVJC8kz5Y3mCgEKuYIud9cpPaZX8HLUepT8+nw2Vm2kzdmGSq5iYdJClqYtHfa8uFEHaAA4L/U8KlorWF+1nneK3xmyKs0jAWabmbUVa9lQtYEOV0fQonCjGLn4tolHuDwurF3W43WpHP+rS9V1/LO1qxWX1+Urxit48Hg9CP9bepYjRy6Xo5QpsXfakQtywkvDCVWGEqYIIzkmmdryWqK0UVicFjReDUKdcMJKctB7YT+7247T65SqbA8Gq0pXsaVmC9Nip3FV1lWD3j8YulNTuhs0/vU/uo/F6XHyVflXfFzyMR2ujiGlJCjkih71yVq7Wqloq6DJ3kRLV4t0L7Q526RrLouQ4RW8NNmbMNvM2D12HG4Hk+ImcWXWlWRHZyOXySWlNAiMugz02TkRQy7PlMf8xPmSsXV20tmDogOr5CpcbhcOlyMgJ6M7eqMbif8XtxHn8rLWMtIi0oBA57eoqEhyfMRFhMzwTPa27JWEKrrnqXx66FMq7BXMiJjBnDFzBjU+RZcCu91OWX0ZYyLH+PpcDjptT4qnGF1Uy9UkhyXz2PzHWHFkBauOreI3W37DFZm9R4PEnDRxtf9UU7LFqM97h97D6XVyftr5XDP+mhEjC9/9GRDPTU1NDTU1NYSHhwds1z3/C3z32StHX2F/+34pWnjXl3fx8JSHpX1PN1RyFXMS5zAncQ5VbVWsrljN5urN/PPgP/nnwX+SHJYsRYbHRI75TuYLnSoKXG+U6diQWC4eczELkxeesvpxow7QACCTybgx50ZqbbV8VvYZqeGpAdzuMwWigRMbF0uVt4o1FWsotBQCvmTjy8ZdxoLkBadcinMUJ44zNcfK5XH5aEqtx5PlGzob6HD1rt+vUWgwaAzE6+NRK9QoZAoUcoWkIgY+g8PtdeMVvHRoO2i1tdLmasNis+CReTjkPgRmcLvcuFw+w0qlUhFaGUoYYWTFZDE5cTIZERkkhCb0G+3tyzhudjQDx6tsDxSFDYW8W/wuiaGJ/HLqL0/4Zdxf5KK7WlMwJ8Hj9bCpehMfHvmQZkczRp2Ra8dfy7ykecOq0Bah8eUODRRewcs+yz5WlqzkUPMhHtv5GOenn88lYy7pVSktGO0r2Lk60UWG6ydeT1FjkUSFi9JGDXhflVzlS0hW9DQge+ur/3gKCwvxeDzU1NRgNBqlldTtlduJaY2R9jUajVLyuxg5Ep+LG+Nv5D3dexxoOsDE6Iksn7dcOpbZZmZ9y3qiNFHckHvDgM6jP9wdbgSvQEV9BWT5+h4VFYWls6e0skQH/B8LQaVQ8cMJP2SGaQYvFr7YZzToiPUIXZ6u06Kw1j3q87MpPzvtlLfuCPb+8M8ts9lsAbk//g6z/z1UXlQeEC08ZjtGW1sbCsXIY8skhydzU85N/HDCDyVDfHf9blaWrGRlyUoi1BG+HMO46eQYc05JvbWRAK/gHTZKYLOjmd31uymoL6CosSiAMr0oZdFpczxHHaABQiVX8avpv+K3W37LK0WvEB8aT6Yh83R3a8DwCl52lu6kqLWIgyUH6VJ1IUfOTNNMlqQuYVLMpFE+7BkEf0MDTl2i6Ymgu7NT2lpKVVsVbuF4wnOkJpLksGSitFFEaiIxaA2+fxrfZ6QmEp1Sd0L3qMVioa6ujvCYcBRhClocLew5vAerw4odOxqDhtK6Uuod9VRWVbK1cSvgW3FOi0jzKZBFjAnqFPVlHLd0tfjG1k+ivj9qO2p5dvezhKhCuH/G/T0KPw4G/UUu/CMAovMDSLTADnUHLxa+SEV7BWHqMH6S/ROWpi4dkXRYuUxObmwuU4xT2NOwh/cOvcenxz5lfeV6fpD8A5JVycTH+yJV/sabiL7O1YkuMvhT4V4ofIFfn/VriZ7XH9QKNUqVEoPRENRA7UuGOBiiddFkR2ezs3onc1RzwHx8tb+7lLToPIXKQ/lz7p97RA2raqt4r/E9vHIv98+5n+SY407HQKNlGaYMZFUy5PrjBo+ry0Vre2sPSmqXx0fLUcsD84MyDZk8dvZjfHjkQykaJFJnUsNTAZ9AA8C8xHm9n+whRnV7NWsq1rC+cj1Or5Pz0s7j2vHXjpiojz+CRYHFKKBcLg+6YOCvFidepxxjjlTsVi6TM0bvczhPNwWuL2iVWmaYZjDDNEOiYhXUF1BQX8D6qvWsr1qPWq4mx5hDXlwe02KnDWouP9PgFbwS3fRkIQgCle2VFNQXkG/O51jrMcAXZcqOzmZa7LRBUaaHC6MO0CAQoYng/hn38/uvf89T+U+xfO5yjCEj78EWYXfbKbIU+R7qhgKsNisutwuj3sj3xnyPxSmLexQuHcWZge6r9yPpReMVvJS1lpFvzmevZS8VbRU9nJ3JxsmkR6STEZFBRmTGoFbHB4vuBmxyWDImTD1e/HV1dSgNSloVrVJUqqy1jCPWI9K+armajMgMco255JnySIpJ6vWcixGgKM3AxtbubOfxXY/jcDt4YOYDw1q3pHt+lAiFwscD/2fhP9lh24FKpWKJaQlTlFNIC0sbkc6PP2QyGdPippEbm8vWmq28Xvg6Lx94mby4PO7IvIOqo1VBn5XhopLmmfI4N+Vc1lau5YXCFwYc0QtXh6NX6alqr+rxW/e+BnM6EhMTe4xnaepS9tTuYU/7Hi42Xix93/35SExMDBoNBKipq+Gdqnc4aj/KlROvlOry+CvciX3sC2MSxqDT6XCpjhetdNgduL3uHsesbPP1JSE0oUc7aoVaiga9sf8N1lauZW3lWrIMWcxNnMs3dd+QHZ097IWD3V43+fX5rC5fLeVKpYSlcP2k60+bxPVA0P3eEaM/arU6IPLjj2DPyvJ5y3lw64McaDrAuPBx3Gi6kfDwcGw2W4+I0UiEXCYnKyqLrKgsrp1wLWab2We8/4+mJYqujI0cS15cHllRWaSFp53UAtVIg+i8nggEQaDZ0Uxpayn7G/dTUF+Axe6L5oYoQ5ibMJe8uDymxE4ZUQyjUQdokEiLSOPnuT/n/wr+j0d2PMKf5v6JcHX46e6WhCPVR9hSuoVKoZJjtmNSqDE1LJUlKUtIV6ej6dCQEJUwbM6PIAi4vW463Z10ebpwepwICAiCIHFMZTIZcuQgA6VMiUapQafUoVFovpP828EimBF0OrnWTo+Tokafs727fjfWLl8hxDBV2Cl1dgaK7kaf+LfFYsFj9rDUtBTjBCOCIGCxW3yRq//VtjlqPcqh5kO8f/h9YkNiyYvLk16K/iqJg4kAOdwOHtv5GHW2Om6YdAM5xuDGx8mM0R/+ho//vVRjq+HVwlep76onQZfAb+f9lvbK9hHlYA8Ecpmcs5PORm1R80ntJ+TX53PPpnu4IuUKEkkMMMpgeCOoN066kXZnO5srNtPW1MZPp/yU2Ni+k65F5bYj1iN4BW/AnNj9ug5E0KO0tBRFswIdOvLb8jmn45w+qXTQc07xCl4+bfyUI7YjzIydybUTrpV+600uuTeEqkJRypVYHVbpuzB9GDh6Ok9lbf1LyWcaMnlk3iOUtJSwumI139R+w466HbQ6W8mJycHSaRmWxcomexPrq9azrmId1i4rSpmSuQlzWZq2lCxD1ohnVXQvhCrWlerrXRJsXonRxUjFbsUIkUiBa2trG/IiqMMNk97ERRkXcVHGRXQ4Oyi0FEqKlaJIFPSsU3YmO0UerweNsn+6nyAINDmaKGstC6j35l/+IFYXy4XpFzI9bjrjo8aPWPXgkdmrEY5Z8bO4buJ1vHngTR7b+RgPzXrotIW3BUGgoq2C/Pp8CuoLOGA+gOAVUMgV5CX7DLNpcdMklZOioiKcrsEZM/6J6S1dLVKCutXh+9fp7sThdmB32+nydGF3209KTlGr0KJVaiWHSKfUEa4JJ0oTJVWJl2hSGgN6lX7Ev2iGGsEM+FON1q5WdtfvJr8+n32WfVLtksTQROYnzScvLo9xhnFnlEPbfUVUJpMRGxJLbEgss+JnAb6k7OKmYumZ+7zscz4v+xy9Su+LDP1vpUs07vpz+NxeN8/ufpajLUe5eMzFnJ92/rCPszvtyWg0ssu8i78V/41OOlkcu5irsq8iPiIei8ly2h3sE8WYpDFcq7yWY65jrChfwauHXuXyCZeT3ZHtU1nrFkGFoXeGFHIFv5z6S6rrq9nZvBNvoZcHz32wR35Zd4ckPSKdosYiajpq+o1giDkb/iv5/vex1WpFIVMwPXw6m1s2Y9FYkJvlvY67+/zi8rr4+56/k9+Uz3j9eL4f/f2A57ovueRgkMlkGDQGaaEEIDQkFE2npsd5L20pRSlTkhKW0m+b4wzjGGcYxw8n/JCbv7oZu9tOYUMhv1j/C2JDYn2LMP/7lx6RPqiyFp2uTp/RJ1J5W0oxd/rOXbQ2mquzrmZRyqIzqs6M/3UWhTDUavVJ3fvi3OLxeKTcMoVCcVKKm6cToepQ5iXOY17iPFxeF0etRznWcky6B/zrlEGgU5QRmUF6ePoZ4RQFo8AFc3ZKW0tpc7ZJ2yjlSlLDUjkr/izSI9LJNGSSFJp0Rthkow7QCeKC9Ato6Wrh45KPeabgGe6bcd+we7liUT9/ek5Za5mUOB6qCmVuwlySSWZexjxS4nu+MHqjejjcDsw2M7W2Wt9nRy11tjrqbfW0u9p7tCNCo9CgV+nRKXVEaiLRKo87L1qF71MlVyGXy5Ehk16agiD4ZE//Fy3q8nRJTpTD879Pt4NOVyeN9kY6mntPjlfJVcToYojXx5MQmkC8Pp740Hji9fEYNIYz4kE8U2B329lWs41N1Zs4aj2KgIAcH30gz5TH9NjpQ1qo7FRjIFQosRjnZONkbph4AxVtFRJ3XHwZKmQK3F43Npetz5efIAi8su8VdjfsZkHSAq4df22v2w4XBEHgn3v+yUfHPiJKF8Wfz/4z46PGS7/3px43EuHfx5ycHCiCWxJv4cOGD/m45GMOhx3m/NDzUXgUhIeHSwpoYiSlpqamh2T1yYxVpVBxz/R7eGb3M+yz7ePZ3c/yy6m/DKAUdndaxIhHWWtZnw5QMJGH7vexwWDAarWyKHkRBV0F7GzZyS0Zt/QqE++PLk8XT+c/TaGlkLNMZ3FR+EWEhYYF0Jr85ZIHikhNJI32xoBz5PK68Hg9Ac5hWWsZyeHJg6JfllhLUMgV3D71drIMWWyt2Uppayk76nawvW67tJ3oFEVqIlEr1D7xCbkKt+DG6XHi9Dhpc7YFODsi4vXxzE2Yy5yEOUyNnXrGl8cYKhqofzR9IPfXmQSVXNVDsdLmslHeWh4g6NObUxQbEkukNhKDxpfXKi7mni5qsSAI2N12WrpaaLQ34vQ4+e+x/2J1WKnpqOnV2ZkZP1NidSSFJQ04t3GkYdQBOglcnXU1LV0tbKzayEt7X+LnuT8fstXu/pwd8DkfaeFpjDOMY3rcdLIMWf1OwtEx0Th1TnZU7GD/3v20KdqweqxSvoI/IjWRJIUlDUti+mDh9rpp7Wo9Lo8sRqL+93dDZwP7LPvY3bA7YD+tQotJbyIpLElalUmPSEen7Cm3OoreUdXuUw3cVLUJh8eBVqFlZvxMpsdNZ2rs1FMmWzlc6GEwDxAymYy0iDTSItK4LPOyALWbz8s+p8vdxV3r72JRyiLOTT2XGF1MwP7vHnqXTdWbmBY7jVsn33rKnHXRIKmureadyndYX7qeOFUcP0n4SYDz09e+I9mYCVbfBDP8Ou/XvFX+Fl/Xfs0u8y4mh05G06DxKSB5ZagVasJV4cSp44hTxg26QGxfSDQl8uj5j/L3PX/nm7pvaPy6MUC5rLsBKkpXl7aUcnbS2b222z2aFwxhYWE+Ry7axFm2s9het53q9GqSTElBc3fE58EV6uL9yvepaK9gYfJCbs25FYVc0SMR/kSMZ4PWwLGWYxLFL1ITCfiooyI9u9nRjLXLyrS4aQNu1+Vx8e6hd1HKlZybci7RumhyY3MB3wJOd2N1R90OSUa/N4jOzpm2qj8YDLWiaF8S7d8m6FV6JsZMlPLhoGe0UHSKekOYKkxyjETbKkobJX2nV+kl1VO5zFfmQS6Xo5ApkCE7XhJC8Pj+7/VIiqitzlaphEQAi+d//3d4HAiCQFlrGXqVnpbiFsDn7KWEpUjOzpiIMSSFJY1YOtuJ4NszktMAmUzGrTm30tbVxpaaLRi0Bn444YcD3t/uttPiOF7jwtplpcneRGV7Za/OjphHkR6RTmJoYp8Ol1fwUmer41jLsQAnyuFxYLfbEbwCWqWWsbFjmRA1QYqaJOgTMOlNI2qCV8qVROui+8xb8ng9WOwWzDYzNR011NnqpP9vrdnK1hqfupcMGfH6eIm3O+oUBYfb62aX2Vc87mDTQcCX2Hte2nnMTZz7rTpfQ2XUR2mjODf1XM5NPZeKtgqsXVY0Cg0rS1byScknTI2bytLUpUw2TuaLsi/49NinjIscx13T7zqlLxaTyURVbRUrrSs53HGYs0xncUH4BaQk9E0zEvcd6caM2MeImAjWV6730VZaj1FdWY1H8NWNqnfVs655HfH6eJQyJS63C7lcjtfrlT6jNFHEqeLIjssmsi2S5PATS6b3d7DvmHYHxkPGHnVsuhuMcSFxqAU1+eX5XBR3Ua/3ZW9Sxt2T28W/f5z9Y/Za9vLyvpe5MeZGFB7fopm/419dV826+nVsbdlKSEgIV2RewWXjLqOxsTGowzRQSXF/GLQGvHhp7WqVFtYg0AEqbSkFGFRNuhVHVlDTUcM146/p8b7QKXVMiJ4QUJzY4XbQ6e7E5XHh9Dpxe90o5UrUcjUqhQqdUvetmutOBbrfA2dquYYTRYgqpIdTZHfbfY5IkNp2ojNyxHoEh8cx7P0LU4dh0BjINGQSpY1CJVfx76P/Zk78HK4afxUGrYEobdS3ytkJhm/36E4Cj+18DFO06Xj0Q2MgRBUSUHtEIVMgl8tZNm4ZdbY6Vhxegc1pY17SPDxenzfe4eoI8Lb9c2d6u9FPxNkBn8NT017DgaYDHGw6yMGmgwH0Na1CS0akj/8cTTQ6u47s5GziYk+vFOFQQSFXYNKbMOlN0oqfiA5nh88BbCuT+Lv+YWo5cjIiM8iOzmZi9ESyorK+sy+9JnsT6yrXsb5yfUBi73lp50kFBb9tGA6j3iN4GBc5jj/O+SN7LXtZXb5aig4pZUoaHY1kGbL49Vm/PuW1JiKjInn52Msc7jjMOcnncOvkWwccvT4TjBm7xs76jvVsObwFQSH4JKU1BnJjcyXqxqHaQ7x39D0MGgMPz3+YKG0Uu/ftprmrmXpXPV6Dl9KWUirbKymvLefz2s+ZEDWBpalLmRE/Y1C0j+4OSfc6NrvMu7htym0BVDeZTEa8Mp5D7YfYX7qfSUwa8Hnvfj/7/x2ji+En2T/h5X0vs6VzC0vDlgbc9+Wt5bxpflMqmnr3zLslOl4wsYPukvy9Kch1hzi/Oty+96BYMNhfGGGneScAWVFZAxr3EesRVh1bxZiIMXw/4/sD2kekbY9i6NDbgtJg6LNnAtV2MNApdSSGJpIYmtjnduLCeHPXcWdJzKsWozoBkR68kj0q1sSTbFS5gjB1WED+dIQmosfcVWItYU3lGibHTh7ws/ZtwKgD1AvKWssocZT0v+H/4BW8WOwWXi16lY+PfdwrJShUFUqkJpJxhnEBifz+9DJjiHFAxoggCFR3VAc6PE6fwyNHTnpEOvOj5jM2ciwZERnE6eOkyuhmsxlTyrdjYhkIQtWh5BhzAtS1/J2iw82HKW4upqSlhE+PfYpCpiAjIoOJMRPJjs4my5D1rX9JVrZV8u8j/2aXeRdevMToYs7IxN4TwXAY9aKsqFwmZ2rsVKbGTqWhs4HX97/OxyUfI0NGQ2cD7xS/ww/G/UASKhluuLwuni54mqLGIs5NOZebcm46o4QqeoPH62F73XZWV6zmUPMh7HY7cao4ZkfPZtmMZZKBLULdoMYV62KlZSWP73qcX2b9Er1Mj16rJ0WXwrQpPtqV0+PkaMtRNlRu4Ju6byhuLibiQASLUxazJG3JgFQNgznYwerYXD7ucs5PP19yDs7LOI/iwmIK2gqIMccMyJgciOF4TvI57KjbwU7LThaNXYTRaKTT1cmq0lV8WPwhLpeLSzMu5cdTfxxgLHVXDROT3UWVr+5j7gtiwrUomONPgQOfLPzXtV8zPmr8gGSsXR4XL+19Cblczs9yf3bG5+ScyehtQWkwkfYzgWo7HNApdehCdac0l1YUIxls0e4zHaMOUC94aclLqEPUAXxJu8d+nGvpx7EUEJDL5HS6Ovm45GPane3MSZjD/7N33uFRlGsfvnc3u+m990YLJLSE0IuUYAP1qNj12FHsiIqiYi8gKhZs56jYsLcjaui9pRKS0NIT0ssm2WSzm535/si366aR3mDu6+KKZndm3p3MzL6/93me3xPuFo6t0rZZXqdKoer44GdBb9CTWp5qKrou15YDTWldQY5BzPabzWjX0YxyGdWu3/r5+mBpSTNRFNokYnOqc0grTyO1PJX08nROVZ3il9O/oJQrGeM6hiivpoZo51L/pJK6Er478R17C/YiIjLOfRwxgTFM9Jx4TkyMu0JpaSkFBQVAUz+Untwfgii0SiE4U3uGlLIURruO5tKQS0koTmBH3g72FOwhJjCGy4dd3qdiUxRFPjv2mcl0oTviZzCuzObV5LEhaQMZ6gyUciWz/GYxyWESSrUSmUxGY00jtFi/8PLyIpJIXNxc+Cr7K9bGr+VWn1uxlFsiiiKZmZkmA4Qx7mMY4zqGm0bfxM68nWzJ2cJPp39ic9Zmbhp9E/MC5p01MtqewDbvY/PWobf4NPFTvkz6kgmOE1g0chEXjLiATZmbSKhJ4MaxNzbbtmUvsJYF50Yjh7aK0GUyGXeOvZNHdz3KW/FvEe0VzeGiw2gNWpzlziz2XUygKrDVSnFL1zCjy5dKpeqypbjxujO2RjBFgP5/MrYzbyd6QU9MYMxZ92PEPPWtr/v+SJwdcyMEc7OMrkTah0Kq7bmCcdGh5SLRuY4kgM6ClYUVXhZeXWpIOD9wPs8deI4DZw4Q6Rlpss7tCdW6apJKkkyNJY2pc542nlwcfDFjXMcQ5hrWYYOprjarO9+Qy+SmeqBLQi5BEAWyq7NJLUsluTSZlLIUkkqTgKaeFJGekUR5RhHkEDQk08LUDWp+PvUzW3K20Cg2MtZtLNeNuo4Qp87n259rGFe0jf/dkwm+QTA0sxU9WnqUtXFrsVJY8fSUpwlyDOJfw/9FSmkKXx//ms1Zm9meu51LQy/l0pBL+yQFMzYnlq25W4lwi+DusXd3WvwMVkcng2Dgt4zf2JS2Ca1Oy0WBF3H9+OtNvdmMk/QTeSd4OulpjpUfI9w1nBdnvGiapEUQgcHSwKeJn/JLyS9c7Xk1cpmcysqmibj553S0dOSyYZexKHQRcUVxfJb6GR+nfMzBwoPcPfbubveaGeE8gju87+CI6ghHqo9wqPIQhw4eIjwjnFHuozhUeIikiiR8S3xNznTmz/G2+joZbYhb9nqCpoW04xXHsZBZkFyWTJY6i6k+U7kw6EJ8db7UqGs6tLRuz3yhs9eE8d4wNkk2T4ETRIHYnFgcVY5Ee0V3uK/9Bfv5PeP3LqW+SfQ9PXlWDIVU23MFY9qpFAGS6BEeNh48PeVpnjvwHO8lvoccOdN8p3V5P+oGNQcKD3DwzEFOVJxAQEBGU5+DKM8oIj0j8bXz7dLE2zx/27wDvPSQaRu5TG7qGbEodBF1+rpmDdF+OPkDP5z8ARcrF6K9opnpO5NQp9BBL4bqG+v5X+b/+F/G/9AatIQ6hnJ92PWEu4UP9ND6lbYiGcamksb/7gnmnbVTSlN4/cjrqBQqVk1ZRZBjkOl9Ee4RvOT2EocKD/HtiW/54eQPxGbHcuXwK5kXOK/XLEZTy1L5PPVzPG08eWjiQ11KEWqvcepAYh71scOO632uJ8Q6pFljauNY38t7j/iyeARR4EDhAVbtXWVq3Ahw+bDLOV58nAOFB4hriGOGw4xmFtgtkcvkRHtHE+4WzhdpX7A9bzuP7nq0U9Gg9vD38UcuyolyjCK3Ppcj1UdIL09HUaugoLaAd1LeIcYlBh9LH7wtvdHpdAQEBLRqXNqWDbGVoxUCAnur9pKVk0VaRVO6tIXMgrHuYymvL8fTxpNZfrNIS20yPNFoND1Ks+sI4/VnEJoWHOyV9ljImpqjHi09SkldCVcMu6JDi+DEkkTeTXoXJysnHors2nUt0be01bC7ZXRSmn8MPCYBdJ5FgGSiKJ7d//E8o7q6GkdHR9RqNQ4ODh1v0A5FmiJWH1iNWqvm3vH3MtNvZofbNBgaiC+KZ0/BHpJLkzGIBlRyFePcxxHpGclEz4k9So9paxVXpVJ1yfZXoolGoZETlSeIK2pqhllcVwz8v12q73Rm+s7sUuSwP9Ab9GzN3cpPp36iWleNt6031426jmiv6EEv2voCY3Sgr+6BGzffSIRbBBcGX8iaI2tQKVQ8NfkpQp1C292mUWhkZ95Ofjj5A5UNlXhYe7Bk5BKm+07vUTpihbaCx3Y9hl7Q8+KMF7ucIjTY0t4OFR7incR30Oq0TLafzOLAxejqde2mYc3YNAN1wz+dyp0sndhz7Z5m+2wwNPDMvmfIrs7msUmPEekZ2enxJJUk8eHRD6nQVjDFewr3TbivU8LVPCpv3m/I+HtBJbC7cDd/lf1FsbYYd6U7lhaWGBoNOCud8bPxY2zQ2GY9bPSCHr2gp8HQQGFtIZnqTErrS03HlCPH396fmX4zCbcOp7qsmj/Uf5CsTmam70wWOiykuqoaZ2dnNBpNs2aWxvElJSVhMBhQKBSMHz++0+fJnN8yfuOr9K94ecbLpnti2bZlJoeqpJIk1s9df9ao2tHSo7x+5HWsFFY8O+1ZKfVtkNOybgxoJuIlBoZXD7/KsbJjfHHRF+fEXKCz83gpAtRHeNl6sXrq6qZIUNJ7CKLAbP/Zrd4niAKpZansKdjDocJDaA1a5MgZ6z6WGb4ziPKK6lEqTFt2lEYGwyruUMVCbsEY16a6gJtH30ymOpO9BXvZV7DPFBka7jScGX4zmOo9dcBNBI4UHWFj6kZK6ktwtnLmroi7mOM/55xfLT3bxL2vIxkKmYIiTRFrjqzBUmHJU5Of6jC90EJuwfzA+cz0m8lfWX/xa8avvJv0Lr9n/M7tEbd3y6FHFEU+PvoxNfoalkcu79YkcTClo+zM28mHyR9ir7LnOrfr8FJ4oavXmUSsUdiarzKHu4ZzoPCAqefMGNcxrfZbXVHNpXaX8pH6Iz46+hFvzH4DO5Vdp8Y03mM8b8x+gw+SP+Bg4UHqG+t5JPKRDo1TjAtRLfsNubu7U1NTQ2VlJdPspxFkGcRHeR/h7+DPTM+ZpBWnkV+fz7HqY6SlpmFp2baLoBw5PnY+zPKbZYpmBzoEmsaVkpJCo76RixwvwsLKgj0Fe8gvyucKtytMgqxlOl1vXQfGyI+5sHeydCKrKossspjsNfms4udQ4SHWJ6zH0sKSlZNXSuJnCGAeneysW6BE31OprTwvm8ZLAqgPMYqg5w8+z4bkDTSKjcwLmAc0rcgarYaNTUhDHUOZ6TeTaT7Tem3C3F4O7mCa0Ax1ZDIZoU6hhDqFcmPYjaSUpbC3YC9Hio7w6bFP2Zi6kSjPKGKCYhjjOqZfHzI1uho+PfYp+87sw8bChhvCbuDCoAt7bMYxVDhbDnpf3wN1jXVkl2Qz3Gl4q7S3jrBUWHLZsMuYFzCPXzN+5c+sP3l2/7NcHHwx1466tkt/vz0Fe0goSWCm70yivTuupxjMbMvdxkdHP8JB4cD1rtcT4BzQLE2ttLTUFJkATH/7F2e8yKq9q0gtT2WM6xhenPFis/0aJ2Q22DDfeT6/lf7G6zte5/6J93f6GrFR2vBQ5EN8dPQjduTt4LUjr7EyeuVZ/1ZGgdFWup2xBgnAR+VDlEMU8TXx1NrUcrn75QA0CA3UNNYwcvTIVj1sVAoVLlYuZxVh5osAj7g+wvtJ77MrZxdfl3zNsnHL2kynA3BwcKCysrJHWRLG2h8L2T/TEDsLO7KqsxjpMpKbx9zc5naCKPBn1p98mfYlDpYOPDX5KQIcOu5fJTF4aJm2KTGwVDVU9ZsL6WBCEkB9jKetZ5MIOvA8HyZ/yPHy4zQYGkxWw65Wrlw5/Epm+s7sE9vDwZKvf76gkCsY7zGe8R7j0TZqiSuOY1feLg4VHeJQ0SF8bH1YELiA2f6zOzSt6ClHio7w8dGPUevURHpGcmfEneddju9AXf87cneQV5OHrYUtz0x9ptsTNDuVHTeE3cAc/zlsSNrAH1l/kFCSwD3j7ulUNKhCW8FnqZ/hZOnEv8f8u1tjGCzsP7Ofj49+jIeNB9c4XYO1aI1Go2mWvmiMVhgFkDFty83ajQ8WfGCayIu1IpgF1s0tnC8acRHHqo+RWJXIjlM7WOK+pNNjlMvk3D32blQKFX9n/82b8W+yPGp5uw0FuyLCF7guIF/MZ0v5FoJVwU0TFj14eXp1SVyf7fj3TbgPO5Udf2f/zSvHXuHf4r+Z5Ter1fs0Gk2zn91BEJrc3+TyfyJA2dXZNAqNLA5djJu1W6ttijRFfJD8AekV6XjaePLk5CcHXaqxROeQFmEHB/WN9agb1Ix2HT3QQ+l3JAHUD9gobZjlP4sPkz/ks9TPcLR05AL/C1gYtJAJHhP6NA1JesgMHFYWVszwncEM3xkUaYrYkrOFnXk7+Tztczad2MR0n+nEBMWYmgz2FuZRH1ulLcvGL2Om78zzLrwN/X/9i6LIL6d/YdOJTVjKLZngMaFXVqd97Xx5fvrz/JH5B9+e+LbT0aCNqRvR6DWsiFrR6XSuwciJihO8l/geTpZOPD3laWQaWbN+NAUFBaYJtVH8GAwGVKqmc2O04m0vItjS0eyusXex6tAq/q76m8sMl3WpUa1MJuPfY/5NfWM9u/N38+mxT7lz7J2d2rZlpMWIQqFgeMBwHgx+kCd3PMmvJb9yR9AdREa2rlNqmfbZFSMDuUzObeG3MdZtLB+nfMz7ye9zsPAgd0Tc0cz6vzcWFowRIKMbXGJJInm1eVgrrfGyab5fQRT4O/tvvk7/Gp2gY2HQQq4bdd1526x6qDPYagrPZ7LV2YiIvT4PGQpIAqgPKakr4feM39mVv4sGQwN+9n542nhS31iPncqOce7jzvkaDIkmvGy9uGn0TVwz8hr2n9lPbHYs2/O2sz1vOyOcR7A4dDGRnpE97rsjRX0GDkEU2Ji6kT+z/yTQPhBPG09Tk8feQC6Tsyh0ERM9J3YqGpRRlcGBwiY7/iivqF4bR3/TYGhgQ/IGZDIZT05+sinyYdMkbjMzM5ulisE/UR/jBKugoACDwUBBQQG+vr5tTtxbCuXhfsO5VX8rn6Z+yl9Zf3HZsMu6NGa5TM7SsUup1FayNXcrk7wmMd5jfIfbmQs0Z2dnKisrcXZ2JiSkqXbMHXfm+c1jW942DlYcxKfUp9UEsqXIa0/0nS09NMorilEuo/gs9TP2FOzh0V2PcsuYW5jlNwu5TN4rCwsafVP0yFZpi0av4cOjH+KocsTawpqs6iyTe2phbSEfHv2Q9Ip0PKw9WDpuKWPcWtdwSQwdBpOV/vlOpjoTgGAHSQBJ9AJ51Xn8mvEr+wr2ISAQ7BjMwsCFTPOdhhw57yS+w/4z+6nR1bA8arm0inUeoVKomOM/hzn+c8ioyiA2O5a9Z/ayNm4tfnZ+XDbsMqb5TGs3ZaY9anW1fJr6KXsL9p73UR/o/xVGvUHP+8nvs//MfsJcwnhs0mOsi1/HqcpTvX6szkaDvjn+DTJkXDfqul4fQ3/y7fFvKdQUcmPYja2iaS3Fj0wma2X4YrQ1h7NHBFs2wZ0XMI8/Mv/g14xfmRcwr8sRNIVcwdJxS1m+czkfHv2QN2a/gY3S5qzbtNdbByA9PZ26ujqmWU/jiMUR/ir9C79MPy5xv6TZ+2xtbdHpdK36BLUUfeb1R8YIGTR30Ltvwn1M8Z7CxykfsyF5Az+e+pGYwBjm+M/BXmXfpfPRkkptJRZyC6wUVryZ8CaV2krujLiTz1M/J7Mqk6SSJGJzYkkoTkBElKI+5xBSav7gIUudBXBeRoAkG+wW9MQG+0TFCX7N+JX44ngAwl3DuXzY5YS7hTebiAqiwH+P/ZctOVsIdgxmZfTKAXcJkxg4KrWVbM7aTGx2LFqDFndrdy4NvZS5/nM7VeyeWp7K+oT1VDVUSVGf/6evLa7NqdPXsS5+HSllKUz2msz9E+5HqVDybuK77CnYw2cXftZnk7aC2gI2JG3gVNUpvG29m1zeHPxJKU3hxUMvMttvNveOv7dPjt0fnKg4wVO7n8JT4ckzk5/B08PT9FppaSl5eXmIoohMJkMUxTb/3u1ZTbfEeM0Apv3szt/Ne0nvsTh0MTeE3dCtz2A0bpjjP4d7xt3TrX0AxMfHm/47X5vP52c+x9bKlhdnv0igQ2Crz9HZa9/8/UCb29bqavk983e2526nWleNUq5kms80YgJjut377Ik9T1CjqyHMJYw9BXuY6TuTm0ffzP3b7ydTnYmPnQ8yZEz0nMiikEWEuYZ1+RgSEhJn55Gdj6AX9Lwz952BHkqv0dl5vCSAWtBVASSKIillKfx06ifSK9IBmOQ5icuGXcZw5+Fn3e7HUz/y/cnvpWJOCaBpkhGbE8vmrM3U6GpwUDlwcfDFLAxa2O7KcWx2LJ+lfoZSruT2iNvP66iPOf0VAarSVvHqkVfJUmexIHABt4XfZkpj/Cr9K37L+I235rzVJwYnRgRR4I/MP/jm+Dco5Urum3Afv2f8ToY6g7fmvHVWK+HBjM6g47Hdj5Fbnstdvnfha+fbbOXYvJdZWw0XW9bAGHuPGCf3bdXKmEeA3N3dEUSBx3c/TqGmkA3zN3Qr6iGKIi8fepmjZUdZGb3SlArX0TXa8nVjBEipVCKTyai0quTDUx9ia2HLs9OeRVWv6pTQO9txWp67lugNeg4VHSI2O5YTlScACLAPYKTLSIIdgwlxDMHf3r9TEew7/76TioYKGgwNeNl4EeIUQlp5GoWaQjR6DbeNuY3Lh19+XrpTSUj0B/WN9dz6161M9p7Mw5EPD/Rweg1JAHWTrgigk5Un+Tr9a9Ir0pEjZ4bfDBaHLu5SP4KtOVv5T8p/mixUJz5EhLvUlPR8p8HQwPbc7fye8Tvl2nLslfZcMfwKYgJjTF3R9YKez459xtbcrXjZePHopEelPhj9TGZVJmvi1lChreDqEVdz5fArm4nPzZmb+Tztc56d+my/OOyklaexLn4dZfVl1OvruWzYZUM6+rMxYSM/ZPzAZX6XEW0X3Uz0GI0O4B+xYqRlBMTYtFMmk6FUKk2Te+P7jE0+W1pRG4VAekM67yW9x41hN7IodFG3PktZfRnLdy7HQeXA23PfRi6Tdxip6ej10tJStp3axo8lP+Jo48hVzlfhLnfvt+bW2epstuRs4XDRYap11abfK+VKAuwDCHIMwlZpa2rQahAN6Aw69IKegpoCfjj1A4Io4KBywMvWC6VCyXCn4XjaeLIjbwcPRz7MVJ+pff45JCTOV9LL01l9YDU3hN3A4tDFAz2cXkNqhNqH5Nfks+n4Jo4UH0GOnDn+c7hy+JXdWqmaHzgfV2tX3k54m5cPvcwtY25hYdBCaRX/PMZSYclFwRexIHABu/N38/3J79mYtpHNWZu5esTVjHMfx9sJb5NekU6EWwQPTXxoSDt8DUX2F+zn/eT3AVg2fhmz/Ga1eo8xDbFKW9UvYxrtOppXZrzC0i1LqdBWUKwppsHQ0CUHs76iqxE5g2AgNicWe7k9UbZRrayu9Xo9oiiiUChaOZ21V18gl8ub7adlk0/zZqTwTw+hqaOnsjFtI1tytnBJyCXdMipxs3bjkpBL+PHUjySWJBLpGdlhHUTLWp6WFBUVMdxyOEs8l/Br5a98UfQFV7peSYgihNLS0i5FPjvz92n5niDHIO4ceyd3RNxBubacLHUWmVWZZKozyVJnkaHOaHM/gihQWleKQTTgZePFw5EPM8JlBP72/ijlSvKq89iZv5NMdaYkgCQk+pDz2QABJAHUJcrqy/j+5PfsztuNgEC0VzTXjLwGP3u/Hu13gscEXprxEmuOrOHT1E/Jqc7htojbUMqVvTRyiaGIhdyCuQFzmeE7g7+z/+aX07/wdsLbqBvU2ChtuGr4Vdw0+qYh6SQ4VG1QBVHguxPf8fPpn3G2dObRqEcZ5jyszfcaBVBlQ2Wbr/cFtkpbrJXW+Nr5crzyOM/se4YVk1a02VOlP+mq61N8cTz11DPbZTY+3j6m3xtNDIxRnbb2HxER0ewYHTm/Ga9FCwsL6urqTIJDp9Oh1+upqqjiAv8L+C3jN46WHu2Um1tbzAuYx8+nfiY2O5ZIz8gOndQ66rVjFFBzAuYwWj6a1w+/zucFnzPZYTILZQs7LWTgn/NXUFDQ7n1pfE9ubi7wTzNLmUyGm7UbbtZuTPKaBDSl/VXrqmkwNKAz6NAJOixkFmRVZ/F12tfoGnXIZDLuHHsnFwZf2Ow4PnY+qOQqU3G2hIRE33A+GyAA9Mxz9zxBo9fwRdoXPLTjIXbm7WSU6yhenP4iy6OW91j8GPG18+XF6S8y1m0s2/O28+LBF1E3qHtl3xJDG5VCxaLQRdw8+ma0jVo0eg2CKJChzuB01emBHl63MJ+wDhXq9HWsjVvLz6d/JtQxlJdnvtyu+AFwsnQCmrps9xd7C/aiF/Q8MPEBrhpxFdnV2Ty550lOVJzotzG0hZeXl6lWpzP8evxXDI0GLh5xsWmiXVpaSkpKCqWlpaa0BgcHB0pLS9Hr9QBtRkvc3d1biaKWr3t5eVFXVwc0OctVVVUBTRP5oqIiFgQuQIaMbbnbuvS5zXG1diXKK4rk0mSKNP9c9+afy5z2zllpaSlJSUkUFBSYhMoY1zG8PPNlhjsP52D1QT4p/KTdv3lb957xWEC796X5ODq6b2UyGY6WjnjYeOBn74e3rTdbc7fyftL71DXWERMUg4eNB65Wrq22VcgVBDkGkaXOQsrQl5DoOzLVmXjYeJy3GSSSADoLgiiwI3cHD+14iP9l/g8fOx9WRq/kmSnPnNXgoLvYqex4IvoJLgm+hOMVx1m5ZyXZ6uxeP47E0EIQBb4/+T3vJ7+Pv70/7859l38N/xcZVRk8s/8Z3kt6b8iJ5a5OiAeaIk0RT+97mvjieGb6zmT1tNW4WLmcdRtjBKhCW9EfQwTgcNFhLOQWTPWZytUjruaRyEfQGrQ8f+B5tudu77dxtKQjEWLOmdozpJSlMMpmFPUV9abfm0cgqqubak40Gg1FRUWmiXJ70RIj7YmNlhN684m3l5cXHjYejHIZRVJJEjqDrsPP0B4xgTHo9Xq+OPiFaQwFBQWm6Is57Z0zc0OHoqIi02dS1at4fd7r3DnxTmqEGp7d/ywbUzfSYGhotn1b957xWEZh2Z6QDAgI6PJ9m1qeymO7HuPv7L8Jcwljzew1pr5V7blVBjsGU6uvpbiuuNPHkZCQ6Dz1jfWcqT1DiGPIQA9lwJBS4NohW53N9ynfc7LyJA4qB+4Zd4+pCVxfopAruHnMzQQ4BPBxysc8ve9p7hp7FzP9ZvbpcSUGJwbBwPvJ77O3YC9BDkGmdKZJ3pO4JPgSPk39lN35uzlSdIQlI5awMGjhkEiJ641Giv1FUkkS7yS+g0av4cawG7k05NJO1ehZKiyxVdpSXl/eD6NsilCll6cT7hZust2e7D0ZTxtP1sat5cOjH1JSV8I1I68Z1DWG23K3obRQMs1tGl5eXs1srI11OoIgAE0TdXt7e5N46Ghibi6iampqTELKwcEBg8GAIAjI5XIcHBxaOalFekaSXpFOSlkKkZ6R3fpsY9zG4Chz5EjlEeYXzu/WPWBs7mr875YRnaC6IB4f8zjf5n3LH1l/EF8cz6LQRUz3nU5tZe1ZU087Srvryn2bUZXBn1l/sqdgDyq5in+P+TcLgxYil8nZnb8baF8AjXIexd/Zf3Os7Jjkjioh0QPaSzc/VnYMEZGRzq2baJ8vSAKoHZ4/8DxKGyUXBl3IkpFLsFW2XYjaV8zxn4O3rTdvxr/Ju0nvcqz8GLeOuRUrC6t+HYfEwKEX9LyT8A6Hig4R6RnJgxMfbFbQ7m3nzcrolRwpOsLnaZ/zedrn7Mjbwa3ht/aL69i5TqPQyLcnvuW3jN+wsbDh8ejHmeAxoUv78LXzJa/mn141fUlyaTKNYmOryXmQYxAvzXiJ14+8zs+nf0Zn0HHT6JsGlQgy/5JOK0/D1daVRdGLkMlkzfrzBAQENIuAaDQa7O3tTS5uRhHQ3iTdy8vLVMNi3kRVo9EgCAKiKCIIAiEhrVdFozyj+DL9SxKKE7otgOQyOdP8pvFb1m8I9k0irr0apfZoS4S0tAanDO4fcT+Haw7zw8kf+DjlY75M/5IRFiOYaDsRito+Rz1tUKkz6NhXsI8tOVtMJgjhruHcOfbOZkImuzobhUyBj61Pm/sZ5zEOhUxBfHE88wPnd2ssEhIS7ddfGvtVdvdZdi4gCaB2GOY8jKXRSwlyDBqwMYx0Gclrs17j3cR32Zm3k9OVp3lo4kP4O0h2x+c6eoOeNxPeJL44nqneU7lvwn1t9taQyWREe0czzmMcP5/6md8zf+e5A88xw3cGN4bdeN43RO0uJXUlrE9Yz6mqUwxzGsaDEx/slstjiGMIJytPUlJXgqetZ8cb9ICzfaE5Wjry1OSneO3Ia/yR9Qd6Qc+t4bf2eUT7bJhHdoxiJP9MPrnVuUS4R5gEmvmkvKVxgfmk39zF7Wx9dZydndFoNNja2poiQObCyDz9zfw43u7e+Nj6EF8czx3iHd0WkOP8xrGlaAtViiqge9HQlqu65tsbP0dJcQlTvKbg7eNNjiyHg5UHia+I52DlQcJdw6kpqGGM2xh01bp299UZ9IKevOo89p3Zx868ndTqa7FUWDI/YD4LAhe0+R2apc7Cz97PZOvfElulLaNdR5NSloK2USst/ElIdJO2FjUEUSChOAE/O78+/14azEgCqB1WRq/E0dFxoIeBo6UjKyev5PeM39l0fBMr967k1jG3Mjdg7qBawZXoPRoMDaw9spajZUeZ6TuTe8bd02Fam6XCkmtHXctsv9l8lvoZewv2kliSyL/H/FtqjtpFDhUe4sOjH6LRa1gcuphrRl7TqcaObRHqFAo0FZv29RfNiYoT+Nn54WrdurAcwEZpw8rolaw5sobYnFj0gp67xt41ICKotLTUNFE3CheAM/Vn0Gg1WNVamayc25qUmwshg8GAQqFolrbWFqboCLTZJ6empobKykqcnZ1bbWMUVWPdx/JX9l8U1xV3OzXLmHPfE5ezgoICDAaDKRWuLVc348RH1ihjhGoE/5r1L05UniA2O5ZDhYd4O/FtAJR6JV5KLwLKApg5ZiYhjiE4Wrb93acX9OTV5JFVlWWyu86pyaFRaASaIp5Xj7iaWX6z2m3erG5QU6GtYJz7uLN+xokeE0kpSyGlLMXkLichIdE12np+nq46jVqn5oKACwZoVIMDSQC1w2CaMMplci4bdhmjXEaxPnE9H6V8xLHyY9wZcWe7XzKDiaFqeTwQ6A161hxZQ0pZCnP953Ln2Du7NEH1tvPmiegnOFJ0hP8c+w/vJb3HgTMHuHPsnR0W7Z/v6A16vkj/gr+z/8ZeZc/K6JXdtjw2YrQX7eueJjW6GkrqS9rsR2SOlYUVj0c/zhtxb7AjbwfAgIggc9MBmUxmirrk1+VjaDTgYeHRKctsYzqcSqVqM23NnI7Su9ravuU2JkFbldklAWT+DHRzc8NR5UhmVWantz8bLUVaywmP8bgymYxRLqMY5TIKdYOaQ4WHyFBncLz4OKfUp8jSZ7Hv8D4AU/NSlUKFXCZHb9CjF/Q0GBoQ+SdC5qhyJMItghDHEMa4jWG0y+gOvzuNvUc6Kr6O9Izk87TPiSuOkwSQhEQvIqW/NSEJoCHESJeRvDbzNTYkb2D/mf1kVGXw4MQHTV/Kg5Wu9gA5X9ELetbFryOlLIV5AfO4I+KObk1MjWlxo11H81nqZ+wp2MOjux6VokFn4UztGd5KeIuc6hzCXMK4f8L97UZSuoKvnS+WCss+72nS2UklNNmqPxr1KGvj1rIjbwdKuZLbwm/r1+vCvJDfGLmxtbWlpKIEuVyOt6V3uw1AW+7HOMFvb6HF/PdtRX5acrb0MqOgzarOYprvtE5/3pbPwGDHYI6VH0Mv6LvV761l3VB7wq69lDZHS0digmJM/68z6MipziFT3dTItLqhGr2gR2fQYRANKOVKLBWWWCos8bP3I9gxmBDHEFysXLp83RiFX0fXqqetJ/72/iQUJyCIwoCma0pInEvEFcXhqHJkmFP7bRzOByQBNMSwU9nxaNSj/J39N1+kf8Gqvau4fNjl/Gv4v9rNpx5oelpYez5gEAy8k/AOCSUJzPab3W3xY46dyo77JtzHFO8pfJzysRQNagNBFNictZlNxzfRKDRy5fAruXL4lb3mpCeXyQl0CDT1NOkrkdHZSaURpULJ8qjlvHb4NWJzYlHKlf1qjODu7m4SBRqNxiRMdIU6rGussVfYd2hpbdyPcYJvNEswigyjkDG3jO7MAkxLVzVzMWQUtF2N3rR8BgY7BpNUmkSRpgh/+67XdLYUNj1dWFIpVAx3Ht4n7R1akqHOQCFTEOgQ2OF7ozyj+Pn0z5yqPGWyzpaQkOg+xZpi8mvzmeM/57xfVDi/P/0QRSaTcWHwhbw0/SX8Hfz56fRPrNy70rQKPNjoSg+Q8xFBFHgv6T0OFR1ims80lo5b2qsPpiivKN6Y/QYzfWeSUJLAo7seZV/Bvl7b/1ClsLaQZ/c/yxdpX+Bi5cKzU59lycglvW4jHuIYQq2+lpK6kl7drzl5NXkABDgEdHoblULFikkrGOUyij+y/uDbE9/21fDaxBjhMY/0aBu1ONs4Y2lpaYrqpKSkkJmZSVJSEklJSa16+LS3P/Oan/Z615TVl7F0y1JmbJrB0i1LKasva7aflmJILpMTYB9ATk1Olz5ry2eg0VVU26jt0n7OBToyQDDHmKJjTNmRkJDoGcZ7KcozaoBHMvBIAmgIY7S3vWrEVZypPcNTe57i2+Pfohf0Az00iS7w7Ylv2XdmH5M8J7Fs/LI+WZUxRoNWRK3AQmbB+sT1fJD8QasmiecDgijwR+YfrNi9gpOVJ7ko6CJen/U6Ya5hfXI8Y1SmLxcoKrQV2CntTP1/OouVhRWPT3qcYU7D+Pn0z2zL3dZHI2yN0YHN+BOaUrHsre1NEaHc3Fx0Oh2VlZWmSI4xda5lU9OWPWyMDT99fX3bXYBZtXcVBwsPom5Qc7DwIKv2rmq2n7aahrpZu1Gjq+nSc9Z8rKWlpRQVFNGob0QndL+p6lDEaIBgTCXsiFCnUBxVjpIAkpDoJeKL41HKlUS4dZwOfK4jCaAhjlKu5OoRV/PyjJdN0aAn9zw5aKNBEs3ZV7CPX07/YrJa7q7bWGeJ8opizew1jHMfx468HTy550lT9OB8oLC2kNX7V7MxbaMp6vPv8H/3qc1ufwigqoaqblue2yhteGzSY3hYe/DflP9yvOJ4L4/u7AiCYIryVKoradQ3OYqZGyWYO7MZaRmdaSlWOhN5PlZ+DINoAMAgGkgtT222n7b2YTzP6gZ1pz+j+ViLiooQGgX0jXoahcZWQu5cpiu1atAUcYv0jCS/Np8iTVHHG0hISLSLRq8hvSKdCLcIyVoeSQCdM5hHgwpqC/6JBhmkaNBgJaMqgw3JG3C2cmZ51PJ+q+FytHTkiegnuCHsBs7UnuHJPU+yLXdbs/4n5xoGwWCK+pyoPGGK+vRHw1gfOx+sFFZ9KiwqtZU4WTp1e3tHS0cenfQoFnIL3oh7g7L6st4b3P/TcqLv6+uLSqVCLpebojxy5NTU1QD/CJqAgABCQkIICAgwRXTMXz+bAUJHhLuGo5A1pTwqZArGuI7B3d3dVLPTligxCqBKbWWr19rDfKxeXl7IFDKUFk1Oay2F3LlMenk6QJeKr41pcAfOHOiTMfUVQ1nYDuWxS7TP4cLDGESDlP72/0gC6ByirWjQ8l3LSSxJHOihSbSgSlvF2ri1AKyIWtHvpgRymZzFoYtZPW01DpYOfHT0I9YnrqdOX9ev4+gPTlScYOXelf0a9TFHIVcwzmMcpypPdSlq0FnqG+vRGrQ9bnob6BDIsvHLqNZV8/qR13u9PqWtib7BYEAQBBQKBc7OzqgUKpA3GRpAU7+empoa4uPjqampaRaNMUZn4J9UuY5EhHlNUUpKCg+HPcwU7yk4WTox0W0iN7reaBJT7e3PKDS7IoDMI0nu7u54+XlhobRAJVe1mWZ3rhJfHI+jyrHTKXAA4z3G42TpxLbcbQii0Iej612GsrAdymOXaJ8tOVuwVFj2aUuGoYQkgM5BjNGgG8JuQN2g5tXDr/L6kdf7tAhbovPoDXrWxq2lQlvB3WPvHlAbc6O1+iTPSew/s58n9jxBTnXXCrwHK+oGNe8lvccz+5+hoKaAy4dd3m9Rn5ZEeUYhIpJQnNDr+zYKFRuLnvcEi/aO5uoRV5NTncOG5A09jgqaryS3NCk4kXeC1zNe5960e1mbtRYHbwd83HyobqimoaHBNPmqrKxs9rPl6rT5JK0jEWGc2FVWVqLT6dBV6vhgwQfsuXYPD/s/jLVobYokqVQqbG1tW62EG8+z1tB9gVija4py2ShtzhuTmCJNEfm1+UR6RnapztFCbsG8gHmU1pf2yf3TVwxlYTuUxy7RNhlVGWSoM5jpO3NI9I/sDyQBdI6ilCtZHLqYdXPWMdV7KvHF8Tyy8xG+P/k9OsP5VXg7mBBFkU9SPuFU1SkWhy5mpt/MgR4Sdio7lkct59Yxt1JeX84z+57hcOHhgR5WtzEIBjZnbubBHQ+yO38349zHsXb2Wq4bdd2A5T1P8JiAHDkJJb0/gTPWsBhTuXrKv4b/i8nekzlYeJCfT//co32ZryQbzQWqq6tJSUnh4/yPSa1NRWPQkFKdwqq9qwhwCKBR3kidrM6U2ma05jbWAZnvs7S0FIPBgEKhICAgoE0RYS6YjBM7G5umCYC5A11btT8ajQadTkdubq5JBBnr9AyCodvnJUudhaXCskvNVIc6PWm+OC9gHnLkbMnZ0tvD6jOGsrAdymOXaJvY7FgAFgQuGOCRDB4kAXSO42rtykORD7Fq8io8bDz44eQPLN+5nLiiuIEe2nnJ5qzN7MzfyQSPCVw36rqBHo4Jo7X601OfRqVQ8Ub8G/x48schVxeUVp7GE3ue4PO0z7FV2rI8cjkro1fibec9oOOyV9kz0mUkyaXJvV6XZ/wb9VYPH7lMzr3j7iXQIZBvT3zLkaIj3d5Xy9oXlUoFgE6n42T1SQSaUpoEBFLLUwlxDGlKDfNRmXoFiaKISqUiJCSk1T6NfX4UCkW7k7WWTUgjIiJobGwyWjDvNWRe+2NMkTMXSEb3OSPGsXcVURTJUmcR5BB0XvXhMLlPuXfdfcrV2pVJXpNMvZMkJCQ6T42uhn1n9jHCeQRBjkEDPZxBw/nz9D3PiXCP4LVZr3Fj2I1U66pZE7eGVw+/Sm517kAP7bwhuTSZL9O+xNfOlwcmPDAoJz+jXEbxysxXCHQI5LuT3/FWwltDoldJsaaY9Qnree7Ac5zRnOGKYVewbs46or2j+625Z0dEekbSYGjgWPmxXt2v8TrqzfoIKwsrVkStwEHlwLuJ7/bKc8IoPozmB2EuYaaxy2VyxriO+ccx7/8bjbaVimO+Ot2ZVJ2WZgkpKSlYWDRFcYwCx/j7goKCZilylZWVpuvH6FZXXlkOgIWse46NJXUl1OprO+2Edi5Qq6slvTydse5jsVRYdmsfxpXrrTlbe3NoEhLnPDvzdqIX9MQExgz0UAYVg28GJtFnKOVKFoUuYt2cdUz3mU5iSSKP7X6MdxPfleqD+phKbSXrE9ZjrbRmRdSKQZ2D62btxvPTnjelQT2z/5k+cQXrKaWlpRxIPMA7B9/hkZ2PsO/MPiZ4TGDtrLVcO+rabk+0+gqj805vR1+NKVm93f/L3cad5VHLaRQaWRe/rls9o4zRl4KCApP5gLHG5vULXmeq91ScLJ2Y6j2VF2e8iK+dLyq5ymSXfLZUnM46v5nvwzieuromsw9jBMj4e0FoLSLlcnkzt7rS8uapcF0lQ50BQIjT+SOAkkqTEBC6lf5mJNwtHB9bH3bk7ZDSuCUkOokgCmzJ2YK9yp4p3lMGejiDCkkAnYe4WrvywMQHeHnGy4S7hbOnYA8P73yYz1M/7xOXqvMdURT5OOVjavW1LB27dMDTsTqDlYUVD098mCUjlpBTncPKPSs5UXFioIdlok5fx5cpX7L29Fq25G0hyDGIZ6Y8wxPRTwza8+tt542PrQ/xJfG9mlpor7JHjrxP7t1RLqO4dtS1FGoK2XR8U5e39/LyQqFQYDAYmkVWioqKcLN2MxkQfLDgA9ys3VDIFQQ5BpGlzmoV0WrL/MC8Fsj8tZaNR1saMdjY2DSLHJmnuhlxdnZu1kjVGLlS2DXVWjmoHLp8PuCf6FZXnNCGOqb6H4/uCyCZTMaCwAXU6muHnCW2hMRAcbT0KMV1xcz1n9tvrTaGCpIAOo8JdQpl1ZRVrJq8igD7ADZnbeaB7Q/w/cnvqW+sH+jhnTPsKdhDfHE8032mE+0dPdDD6RSlpaUcO3aMWc6zWB65HJ1BxwsHX+hRPUhvoDfo+SPzDx7Y/gB71XtxUjlxX8R9vDj9Rca4jRnQsXWGKK8oKrWVZKmzem2fcpkcR0tHKrQVvbZPcy4JuYQRziP4M+vPLvcyahmZMYqKs6WsRbhFUKuvJakkqdnvz9b4tOVrLRuPtjRiaGhoHs0y/t4Y7TH2HjJGjsyjTQZVk/mBk5VTl84FNBkn7DuzDxcrF3ztfLu8/WChoz4x5q/rBT1JJUkMcxrWrXNmzmz/2ajkKmJzYnu0HwmJ84XYnFhkyJgfOH+ghzLokASQBBHuEbw842UemvgQzlbO/HDyBx7Y/gB/ZP4xJOo/BjOV2ko+T/0cR5Ujt4bfOtDD6TTmk8Zo72ienfosNhY2rItbx7acbf0+Hr2gZ3vudh7a+RAb0zaiVChZFrmMjxZ9RMyomEFT59MREz0mAnCw8GCv7tfZypmqhqpe3acRoymChdyCDUkbupwKZ0wrk8lkhISE4OXlRUFBAUlJSW1OoI2OXy0nuS3rfcxT21pabJvbWOv1etNrLY0YjKl5tra2pmhPW+Mzvx8qG5rsuLvTuyuhJIEKbQXzA+YPyhrAztJen5iWtVRFRUWklqVS11jXK80XbZW2TPedzumq02RUZfR4fxIS5zKldaUkFicywWMCHjYeAz2cQcfQfQJL9CoymYypPlNZO3std0bciUKmYGPaRu7bdh8/nvyRWl3tQA9xyGGe+nbn2DuxV9kP9JA6TcvJZohTCM9Pfx43Gzc+Svmo3xzi6hvrTRGfD49+iLZRy41hN/L2BW8zN2AuCnnvWD/3FyNdRuJh7WEqSu0tnCydqNJW9VmjSG87b64ddS1FdUVdToWTy5u+ZkRRNEVSDAYDBoOhzUaLrtauRHlFkVSSRLGm2PT7s9UDGSM45q5u0GS5bbxONRpNKyMGaBJCGo2mWZ1Qy/GZ3w9V2iqUcmW3+i79nf03CpmCuQFzu7ztYKI98wmjMAJMr2/J2YIMGdN9p/fKsWOCmgq5/5f5v17Zn4TEucrmrM0ICCwMWjjQQxmUSAJIohkWcgvmB85n/dz13BZ+G9YW1nx38juWbVvGl2lf9lmazbmIeerbJK9JAz2cLtHWZNPL1osXpr1gcoj7NPXTPptw1+pq+f7k99y37T42pm3EIBi4ftT1vDP3HRaFLkKlUPXJcfsauUzOgqAFqHVqjhQ2pRN2lE7UGbxsvWgUG/vUzOTi4ItNqXDp5emd3s7X959UL2MamUKhQKFQtJsKFxMYg4jIL2m/dOrctJyQm0/EjdFB8zqflkLIfBxtjc/8fiioLcDTxrPLUcfC2kJSylKI9orG2cq5S9sONswtw83/Nsa/g7FuSmYnI6E4oVdXoEMcQxjvPp79Z/aTrc7ulX1KSJxrlNWXEZsTS7BjMGPdxw70cAYlkgCSaBOVQsXCoIW8ecGbLBu/DDdrN37P/J37t9/Px0c/lnoxdECFtoLPUj8bcqlvHeFk5cTqqasJdw3n7+y/eSvhrV7ta1NeX87G1I0s27aMH07+gI2FDXdE3MF7897jsmGXDWr3vM4yx38OSrnSlOLVXjpRVzAW1BsL7PsC81S4D5I/6HQqnLu7u6mJqYWFBUVFRfj6+jJ+/Ph23dvGuI3BxcKFbTnb0Gg1plS19oRQS8FuPhFXKpsKf1tGh1puZxSiNTU1KBQKfH19m42vtLSUQ0mHKKwu7JaDm/HvbYxgDHXaum5b/h225mxFQOj1z2zsofbN8W96db8S5x+9sQA1GPn+xPc0Co1cN+q6IZ1u25dIZ0XirFjILZjlN4s1s9ewImoFwQ7BbM3dysM7Huat+LdIL08fcs0y+xpRFPn46Mdo9Johl/rWGWyUNjwR/QRTvKdwqPAQrx5+tce1YpnqTDYkb+CBHQ/wR9YfeNh4cP+E+3nrgrdYELjgnHKvcVA5MMV7CukV6eRW53aql01HGAVQVnXvmSu0RXdT4Yzio66urlNiTy6TM9FmIvWGenZW7gRotl1Hk5aWvYKMTnRnm+QYJ/TmTnUtX8+uyUbfqO9yD5+86jxic2IJtA8kzCWsS9sOVjq6bo11ex7WHoxzH9erxw5yDGK6z3SSSpNILU/t1X1LnF/0xgLUYCOvJo/d+bsJdw1nrJsU/WmP7jUykDjvkMvkRHlFEekZSXpFOr+c/oUDhQc4UHgAf3t/YgJjmOk3E2sL64Ee6oCzp2APCSUJQzL1rbMoFUoenPgg9sfs2ZKzhVcPv8rj0Y936e+vN+g5UHiAv7P/5nTVaQBGOo/ksmGXMdFj4pAxNugOMUEx7CnYQ2xOLHdE3HHWPjadwdfOFyuFVZ9GgIxcHHwxhwoP8WfWn0R7RRPm2vGE3pguZWtri0ajMTUlbfk78/Mwx3cOcZVxHK45zNxhc7HWWLdKcSsoKDjrPoBm/X+KiopaRXWM2xsMBhQKBQ4ODlRXV5sEk3lUaWfZTpQWyi4JIINgYEPyBgRB4K6xd50z17W7u/tZr9sjhUdQ69TcEHZDn6xALxm5hIOFB/k6/WtenP7iOXNeJfoX47OpJwtQg41NxzchIHB92PXSfXEWJAEk0SVkMhmjXUcz2nU0BbUFbMnews78nfzn2H/4Kv0rZvnNIiYwBn8H/145XmebHQ4W6vR1bEzb2Gepb4PpfMhlcm4Pvx2lXMnmrM28cugVVk5e2aEIKtYUszV3Kztyd1Cjr0ElVzHXfy4LghZ0eWV9qDLcaTjBjsHsyd/D9aOu73Fqn1wmJ9gxmNNVp9ELepTyvouYGVPhVuxewScpn7Bm9poOJ7htTZZTUlLQ6XSmWh2jmDFORKqrqrnc43I+KviIz099zh1ed1BTU9NMsBj/GffRUuAYMZ/kmN9DRmFkXrgfEhJiGpv5/tzd3dHYalBpVAQ6BHb6fP2W8RsZ6gwWhy5mmPOwTm831Pn1xK/otXoirCP6ZP9etl7MD5zP39l/E1ccd84uNkn0LR0J+aHGiYoTxBXHMdlrMqFOoQM9nEGNlAIn0W187Xz5d/i/+WD+B9wVcReetp7E5sTy6O5HWb1/NfsL9ve4PmSohaf/l/k/anQ1XDvq2j5JfRts50Mmk3Hz6Ju5NORSTlSe4OVDL7fZQ6pRaCSuKI5XDr3Cgzse5LeM37BT2XHL6FvYMH8Dd4+7+7wRP9B03mICY9AatOwu2N0r+xznPg6tQdslg4Lu4m3nzSUhl5Bfm8/egr1d2taYvmZMnVUqlc0c2YqKiigoKADAQ+XBFSFXkFedx9/Ff5vS06qrq1Eo/nEA7Ki/kHlKnPk9ZEzjarl9W+ld2kYtx8qOMdp1NFYWVp36rHnVefxw6gd87XxZMmJJl87TUCa3Opf08nTCbMKoLe87B9F/DfsXlgpLvjn+DQbB0GfHkZAYCoiiyDfHv0GOnGtHXTvQwxn0SBEgiR5jZWHFvMB5zA2Yy8nKk2zJ2cKBwgOkV6RjY2HDZO/JzPSdSZhrWJdTIYZSeFrdoOaPzD/wtfNltt/sPjnGYDwfMpmMG8NuRC6T81vGb7x6+FWeiH4CK4UVp6tOs6dgDwfOHKBaV40cOZO8JhETGEO4W/h5HZ6f7judL9K+YEv2FhYGLuzxuYjyimLTiU3EFcf1i+vP4tDFbM3ZyncnvmOq99RO12mZO7QB6PV6IiMjm0VmjAJIoVAQ4xPD/vz97K/azzCbYQRbB2MwGBAEoU2zgo4wv4faW/1tM2JVloJe0BPpGdmp42gbtbyX/B6CIHDvuHvPqTq2jojNiUVpoWSa27Q+fVY5WTlxSfAl/HT6J3bn7+aCgAv67FgSEoOdpNIk0ivSmes/Fx87n4EezqBHEkASvYZMJmOky0hGuozkptE3sadgD3sL9rIjbwc78nbgYuXCDN8ZzPCd0ekUkqEUnv7x1I9oDVquGXlNn/WnGaznQyaTcf2o6zEIBn45/QtLtyzFXmlPqbap6NzXzpeLgi9itt9sXK1dB3i0gwNLhSVz/OewOWszR8uO9rhQ3M/ODw9rDxKKE7h1zK19Li5tlbZcNuwyvkr/ii05W7g45OJObWcUIBYWFtTV1Zkc4syv7ZqaGiorK3FwcKDoTBGXuV/GJ/mf8E3hN/zb59/4WPkgiiIKhaLL90NH91B7aaZxxXEAnRJAeoOeN+LeIEudxVUjrjqvUt+qddXsyd/DMNdhTAqaZIpW99Vza1HoIrbkbOH7k98zw3fGeSU0Jc5vzJ9Vrm6ufJP+DUq5kqtGXDXQQxsSSAJIok9wtHTk0pBLuTTkUvJq8thXsI+9BXv5LeM3fsv4jQD7AGb4zmCqz9RzokNxsaaYbTnbGOY0jGiv6IEeTr9Tqa3kUOEhjlccR61Tk1eTh6OlI9ePup45AXMIdgg+r6M97XFR8EXE5sTy7fFvGes2tkfnSCaTEeUVxeaszWSps7pl1dxVLgy6kD+z/uSn0z8xx39Op2qZOiPiWzY29VB5cIPPDXxZ+CWbyjZxs9fNeFp69lp0oa26IPP6H72gJ6E4AT87PzxtPc+6L72gZ33ieo6WHWV+wHyuGn5+TUZ+OfULWoOWy0Ivo7i4uE3zid7ERmnD5cMv54u0L/gr+y8WhS7qk+NISAw2zJ9V6Q3p5NTksDh0sbTI2EmkGiCJPsff3p9rR13L+rnreW7acywIXECFtoKvj3/N/dvvZ8WuFWw6vonTlad7vbFmf3n8f3fyOxrFRq4fdX64roiiSE51Dj+d+okn9zzJ0q1L+TT1UwpqC7hi2BVND2ErV2r0NQQ5BJ0X56Q7eNh4EBMYQ4Y6g0NFh3q8vxm+MwDYmru1x/vqDCqFiqtGXEWNroY/sv7otf2a1+AYm5XOHDGTVTNXgQV8V/UdtoG2PZpUmz8b2qoLMhdXRwqPUK2rNp3f9mgwNLD2yFoOFx1mms80bo+4/by69svqy/g752+CHIKY4jOlVyzeO8PCwIW4W7vzw8kf+rQZsITEYMJ4f9m52vFl2pfYKe24LPSygR7WkEEmSk1cmlFdXY2joyNqtRoHB4eBHs45i17Qk1ySzJHiIyQUJ1CtqwbAydKJiR4TifSMJMI9AkuFZY+OY3RzUqlURET0jRtRtjqbJ/Y8wVj3sTw5+UlgcLm19RZ6QU96eTpxxXEkFCdQWt8kKm0sbJjgMYEozygmek7EysIKQRR4K/4tDhUd4qKgi7hlzC3n1USwK6gb1Dyw/QGcrZx5Y/YbPU6ffHLPk+TX5rNh/gZslba9NMr2MQgGVuxeQXl9OevnrsfR0rFPj5dUksS6+HXIkHHv+HuZ7D25W/sxfza0rAtqyer9qzlVdYr3573f7ucrqy9jfcJ6TlSeYI7/HO4ee/d514BwQ/IGdubt5MnJT/Z675+OOFp6lJcOvUS4azirpqySnjcS5w3rE9az78w+lo1fxiy/WQM6lsEw9+nsPF5KgZMYEJRyJVFeUUR5RSGIAqcqTxFfHE98cTzb87azPW87KrmKCPcIwl3DGeM2Bn97/0FporDpxCZERFN3cqDNNJqeMBAPFVEUOaM5Q1p5GsfKjpFcmmxyePOw9uDi4IuJ9IxklMsoLOTNHyVymZz7J9xP9aFq/sz+EycrJy4fdnm/jHuo4WjpyKWhl/LDyR/YmbeTeYHzerS/mKAYNiRvYHf+bi4Kvqh3BnkWFHIF14y8hnXx6/jp1E99Yv9uzniP8Tw1+SnWHFnDuvh1TPOZxq3ht+Kg6tqClXlvorPdW3nVeaRXpDPDd0ab4kcURbbnbmdj2ka0Bi2LQhZxQ9gN590EPK8mj915uxnjOmZAmi+OdR/L/ID5bM3dytbcrSwIXNDvY5CQ6G8OFx5m35l9RHpGMtN35kAPp9fnPn2JJIAkBhy5TG4yT7g+7HqKNEXEF8cTVxxHYnEi8cXxANgp7RjtOpoxrmPwknkhq5bh4+1z1pusr00D0srTSCxJZJrPNIIdg02/723h1R8PFVEUKdIUkVqeSmp5KmnlaVQ1VAEgQ8Ywp2FEekYS5RWFn51fhxM8pULJikkreG7/c3xz/BscVY6SS1M7XBpyKbHZsXx/6ntm+s1EpVB1e1/TfKbxRdoX/J39NwuDFvZLFCLaK5phTsPYmrOVS0Iu6fO6vpEuI1k7ey2fpHzC/jP7OVZ2jDsi7uhSNMj4bGir5485f2X/BUBMYEyr18rqy/gg+QNSylJwsXLh4ciHGe8xvtufayjz7fFvm5ovDmAa8A1hN5BUmsSXaV8yzn3cOVFfKiHRHjW6Gj5J+QQ7pR13Rtw5KBZdBqNTbXtIKXAtkFLgBhd1+jpOVp5smpSXpZKlzkJAoL6+HmuZNSG2IcwcOZNQp1ACHQI73Z+jt1i9fzUnK0+ybs46vGz77obviwiQ3qAnpzqHTHUmxyuOk1aRRqW20vR6oH0gY9zGMNp1NGEuYdip7Lp1nAptBc/ue5ay+jIenfRop22Ezzf+zPqTz1I/44awG1gcurhH+/r2+Lf8dPonlo5d2m+iM7UslecPPs8cvzncM/6efjmmKIrsP7Of/x77L7X6WqZ4T+Gq4Vd1qRHz2e6tIk0Rj+x8hBDHEF6Y/oJpglGnr2NH3g6+O/EdWoOWOf5zuGX0LT1uaNvT8Q4UJypO8Mz+Z5jsNZlHoh4Z0LGYp8I9NeWp8y4NUeL8YTClvg0mpBQ4iXMCG6UN4z3Gm1ZV6/R1HK84zuGcwyQXJ3Oq4RSZaZkAyJHjY+dDiFMIIY5N//pSFGWrs0mvSGe23+w+FT/Q80iWudgx/suvyccg/tM8MMA+gMlekxnjOoYw17Bea+TqYuXCk5Of5On9T/NW/Fs8M/UZhjsP75V9n0vMD5jP5szN/HL6F+b6z+224ISBsQYe4zaGUS6j2HdmHzeMvqHLKWndQSaTMd13OmNcx/BJyiccLDzIwcKDhLmEERMUwySvSSjlZ//sLe8tc4HxXd53GEQD14c1RTVyq3OJzYllT/4etAbtgER9BluKyWBrvmieCrctd5uUCidxTjLYUt+GIlIEqAVSBGhoodFryKjKaJrUV2WSpc6ipP4fFyCjKAp0CMTbzhtv23/+9XS19uOjH7M1dysvz3iZUKdQ0+8HcoVW26ilSFPEGc2Zpp+1Z8ityW0ldlysXAh2DDYJxWHOw/p8wnq68jTPHXgOW6UtL814SbLqbIPd+bt5L+k9Lgu9jOvDru/Rvv6X+T++SPuCm0bfxKUhl/bSCM/O/oL9vJ34dq9EsbqKKIqcrDxJbE4sBwsP0ig04mTpxFz/uUzwnECQQ1CHqYWlpaXk5uYCUC6U80nRJ4xwHkFMUAzbc7eTXpEOQJBDEAuDFjLNZ1q/R50HWwQoqSSJVw6/wlz/udw97u6BHg7QtFC2YvcKanW1rJm9RkqFkzinqNHVsHzncgyigbWz1+Js5TzQQxpUdHYeLwmgFkgCaOhTo6shS51FRlUGWeqsVqLIiJOl0z+CyM4bd2t3qAddlY5hvsPw9fJt9xh1+jqWbl2Kn50fL898udlrfek8pzfoqWyopFJbSVVDFWX1ZRRqCimsLeSM5gwV2opW27QUOyGOIThZOfXquDqLcYIc6hjK6mmre1Trci4iiAJP7H6CAk0Br858FX/7zqdytURv0PPgjgdpMDSwbs66PndngyanwGVbl2GpsOTtuW8PWPqRukHNjrwdbM3ZanIrlCPHz96PEMcQgp2C8bf3x0phhUquQiaToRf0pB1Po6y+jIKGAvbX7qdUW4qPnQ8qhQqlXMlUn6nEBMYwzGnYoMi3H2gaDA08tusxKrQVvHXBW4NqUUNKhZM4VzGmvt03/j5m+knRn5ZIAqibSALo3KStyEihppBCTSEavcb0vvr6ekRBRCaX4ebghpOlE86WzjhbNf2zU9phZWFFWnka23K3cdXwq5jmOw1rC2usLayxUlihrlBTUlyCt5c3Hh4eyJAhIiKIAqIoIiKiF/TUN9ZT31hPQ2MD9Y31aA3app+NWrQGLbW62mZip1JbSa2+ts3PZ6WwwsfOxyTmvG298bH1wcvWq1frEloKsAZDA4IoYBANpp+ITcYWCrkChUyBXCZHpVDhZOmEk6UTW3O28nvm78z0ncmy8cukiWQLTlWe4pl9zxDsGMwL019oZYttfp4VcgUyZO2ew/1n9vN2wttM9Z7KQ5EP9cPoYdPxTfx8+mdWRq8ccEMAQRRILUvldNVpU6S4XFve7vsb9Y3oG/U00EClrpJgh2AuDrmYYMdgxnuM75e0vqHE56mfszlrMzePvplLQi4Z6OG0whilvz38dmKCWptYSEgMNQ4XHuaN+DeI9IxkRdQK6fuzDSQB1E0kAXTucbaUEVEUqdZVU6gppLy+nNzSXPLK8xAsBXQKHZXaSiobKk32z8Zt8mvzMQgGAhwC+nxl0UphhbOVc5MYM/vpbOmMq7Ur3rbeOFk69fhBKIgCZ2rPkF+bT6W2kgpthUl4GQVPewKsK4iiSIW2grrGOsJcwhjvMf4fkWnpjI+dD372fq2stc9l6hvrOVPbFMGr1FYSmxNLQnECoU6hOFs5U6WtQtOoQRAEBFo3C7aQWWBpYYmTpRMuVi6ma8RR5ciOvB2cqjrF/RPu75d6iLL6Mu7fdj/jPcbzePTjpt8LokCj0Eij0IggCiaBbCG36NfVeXWDmix1FoWaQvSCngZDA6IoopQrUSlUiIh8mfYlTpZOrJuzrl+MDYYi6eXpPHfgOUY4j2D1tNWDMsJiTIVTN6h5btpzzVKVJSSGGoW1hTy17ylkyKTUt7MgCaBuIgmgwU138t97IyVN26ilqqEKjV7DsbJjfJzyMVGeUUz3nW6K2hh/6gU9IqIp2iOIAnKZ3LRSL0OGQq7AxsIGK4UV1sqmyJGVhZUpimRlYYWt0hZnK2esLay7NeazYRQ7meqmuqlMdSbZ6my0Bm2r97YUYEax4mTphJWFFQqZolm0x7h/U1RIMNBgaKCqoYqqhioqtBWU1ZWx78w+anQ1eNh4tJpkKuVKAh0CTSl7wY7B54woqm+sN6VmGtM0CzWFiPzzKDYX2RFuEXjbeWOvssdC1iQWjOdZRMQgGDCIBuob602Cta6xzrQvg2AgvyYfZDDGdQwjnEcQ4hRCsEMwIU4huFi59Npnq9RWkqnO5MPkD8lQZzDDZwY6oWkhQd2gblO8KWQKHC0dcbZ0xsmqKeLqZu1GsGMwwY7B/ZK6Z0QURV469BIpZSmDIoI1WDGmvpVry1kzaw3edt4DPaR2yajK4Nn9z2KnsuOVGa9Ik0aJIUmdvo5V+1ZRWFvIE5Of6PdGw0MJyQVO4pykOw5IveFLb2VhhZdF0/a/ZvyKrdKWu8fejbed96ArSm6Lsvoy0srT2hU71hbWJve8QIdA3KzdTBPSvhBg0GQx/NTep2gUGnko8iGsFFZUaivJqckxGVqcrjpten9LUTTKZdSgnngZ0eg1JJUkkViSSEZVRiux42njyRTvKQQ6BOJq7Wo67xX1Fbxy6BU8bDxYPXV1q1S4s9FgaDBF7iobKjlceJjfMn6juK6Y+sZ6EkoSTO91snQixDGEcLdwojyj8LT17NQxRFEkS53V9LnUTellRhv1On0d1Q3VHCw8yDDnYbhZuzHceThWFlZNAg45AgKCKFCnrzMJ4+zq7GZmHQCuVq6EOoUS6hTKRI+J+Nv791nax28Zv5FSlsIF/hdI4ucsbDq+iaK6Im4effOgvwdDnUJZOm4p7yS+w9q4tayeurpfXBElJHoLQRRYn7iegtoCbh59syR+eolzKgIUFBRETk5Os9+98sorPPHEE53ehxQBGtwMtNio0FawbOsywt2aCmuhb00PuosgCmSps0wNZXOq/7kvrC2sTaYIoY6hBDsG42nrOSApLMfKjrF672qcZE48N/m5ZsYToihSVl/2j3X3/4uiGn2N6T0+tj5EekYS6RnJCOcRXRIJfUmxppi44jgSihNIr0g3Teo9bTybBJzTP5EtW6Vtu/v5Kv0rfsv4jetGXcflwy7v9nhEUeTDox+yI28HFwVdxCy/Wc0s0fOq82gUGwHws/MjyiuKSM9IhjkNa3Zd6A16UstTiSuOI7443mS6oZApTAYDRhG9PnE9DY0NbJi/odMTTkEUqNHVUKQpMon1THUmBTUFpuiRh7WH6W8e5hrWa1HBhOIEXj/yOr52vrw448VOCf+Bfh4NBEMh9a0tjPfSbL/Z3DPuHql2QmLIYLx25/jNYem4pdK12wHnbQTo+eef58477zT9v7197/QykRgc9LQfTncwn+TsrNyJgNCsoHawdD7WG/QcKz9GXFEc8SXxptV4e6U9s/xmMc59HKGOoQMmdtoi3C2cC5wu4K/iv/jk6Cc86/Ws6TWZTIa7jTvuNu5M9p4MNBdFR0uPEl8cz++Zv/N75u/YK+2Z4DmBKM8oxrqP7bPIVXtkq7M5UHiAuKI4siuz0TfqsVZZM9F7IlGeUUz0nNjl9JslI5YQXxzP9ye/J9IzstuucDKZjNvDb+dM7Rn+zP6TEKcQ5gfON73eYGggpTTFJNp+Of0Lv5z+BUeVIxM8JxDsEExBbQF7CvaY6uG8bb25NORSIj0jGe40vJXIuSj4Ir5I+4KDhQc77VQkl8lxtHTE0dKRkS4jm43vZMVJk6D/M/tP/sz+EzulHRf4X8D8wPk96sWVX5PP2wlvY6u05bFJj3X62hlsPXn6mgZDAx8kf4CF3IJ7xt0zaJ4jneG6UdeRX5PPrvxdBDgE9Js1vIRET9iTv4ffMn5jhPMI7oi4QxI/vcg5FwF66KGHeOihh7q9DykCNHgZqNVWY4RHqVTyYemHCKLAu3PfHRTRhlpdrWk1Prk0mQZDAzB4IyNtUVxSzOtxr5PdkM3D0Q8zw3dGp7dtL9JlIbcg3DWciZ4Tmew1uc9sv/UGPQcLDxKbE8vJypMAOKoc8cWX4VbDGek4kshxkT06RkeucF1B3aBm5Z6VqHVqnp7yNKNcRrV6jyAKnK46zeGiw2zN2UpGVQbaRi0WcguCHYJZPGwxs/xm4WPnc9Zj1epqWbp1KcOdh/Ps1GfP+t6uIIoieTV5xBXHsSd/D2c0ZwAY7z6eBYELmOg5sUsTc3WDmmf2PUNJXQnLRi7DUetoWszo6HlzvkWABrvrW0eY11E8Hv24lOYoMagx1q/Zq+x5ZcYrA9a+YqhxXpogBAUFodVq0ev1BAQEcP311/Pwww9jYdF+oKuhoYGGhgbT/1dXV+Pv7y8JoEHIQKWaGSc5OjsdLye9TLR9NLeNu21AJzyZVZn8nfM3+wv2oxN0yJEz0mWkSfR0NDkdbKgb1Dy2+zG0jVpenflqt+sKSutKiS+JJ74onrTyNBrFRhQyBdFe0cQExRDmEtYrK2gldSVszdnKjrwdVOuqUcqVTPeZztyAuQx3Hk55WXmvToyNKRCLQhZx4+gbe7SvTHUmz+1/DoCnpzzNMOdhzV4XRIHd+bv5/uT3lNWXIYoibtZu1Ohq0Ak6LGQWTPaeTExQDCOdR571fK45soaE4gQ+jvkYO5Vdj8bdFqIocqzsGFtytnCk6AgCAt623iwZuYQp3lM6FEI1uhqeP/A8uTW53BFxB17VXqZnDIBOp0OhUKBQKM4bkdMeW09s5Z2j7zDSZSSvzn11SEV/zDHWHgqiwEszXhpyz0qJ84MKbQVP7n2SWl2t5GDYRc5LAbRu3TomTpyIi4sL+/fvZ+XKldx6662sW7eu3W1Wr17Nc8891+r3kgAafAz0ausPJ39gY/JGbvG+hRGOI/q93kdn0HHgzAFic2JN5gDDnYYzL2AeUV5R2KuGdrrnsbJjvHjwRQIdAnlx+os9LlSu09eRVJLE9rztpJSlAE31LQuCFjDLd1aX7Y1FUSSlLIU/s/4ksSQRERFPG08WBC5gjv+cPj3/eoOe5w8+z8nKkywbv4xZfrN6tL+08jReOfQKSoWSldErGe48HFEUiSuOY9PxTeTX5mOntOPSkEuZGzAXR0tHGoVGjhQdITYnlrTyNAAC7ANYGLSQ2X6z2/x7bc/dzodHP+yXhn3l9eVszdnK5qzNaA1aQhxDuH7U9US4t32fVuuqeengS2RXZ5tqrMyfMdAUATIYDBgMhkFV49ff5FXn8ciWR5CLcpYFL2NWZM+uv4EmpTSFlw+9jKetJy/NeOmsdXgSEv2N3qBn9YHVnK46zQMTHmC67/SBHtKQ4pwRQE888QSvvfbaWd+Tnp7OqFGtUzn++9//cvfdd1NbW4ulpWWb20oRIIm2aEtsrdyzknx1Po8GPIqPt0+/ibAiTZEp2lCrr8VSYckM3xksCFxAsGNwv4yhv/juxHf8eOpHLgy6kFvDb+21/RbWFhKbE8uu/F1o9BqsFFbM9JtJTGAMAQ4BHW5/svIkX6d/TXpFOnLkTPCcQExgDGPdx/bbSniVtoqVe1dSo6th9dTVrSI3XSWlNIXXj7yOXCbnxtE3sjt/NycrT2KpsOTi4ItZHLq4XZGYV5PHlpwt7Mrbhdagxd3anSUjlzDDd0az86FuUHP3lruZ4j2l3xqxqhvU/HL6F2JzYmkUGolwi+DW8FvxtfvHYKNCW8HLh14mryaPJSOWcOWIK9vdX2ZmJpWVlTg7OxMSEgI0iafMqkwKagtMTnvGXln1jfUYBAMioqnPkYPKwWTx7WTlhLu1O0EOQf1i7S6IAkWaIpOZREVDBVXaKiobKqnSVlHXWIdBMCAgIEeOXC7HxsLGZHdvo7RhV94u6nX1XOt1LReOvBBPj845BQ5mNmdu5vO0z4lwi+DxSY9LznASgwJBFHgn8R32n9nPZaGXcX3Y9QM9pCHHOSOASktLKS9vv3M3QEhIiCllwZzU1FTCw8M5fvw4I0eObGPL1kg1QBLQOt2uvL6ce7fdywzfGdw/4f4+P74oiiSUJBCbHUtSaRLQVNcTExTDLL9Z5+yKpUEw8MLBF0ivSGdF1AqivKJ6df8Nhgb2Fezj7+y/ya7OBmCUyyhiAmOY4j2lVX1Nfk0+m45v4kjxEeTImeU/iyuHX4mHjUevjquzZKozeXbfs9gqbXl55ss97uFztPQoT+x5grL6Mtyt3fnX8H9x5fArO51rXt9Yz5acLfx6+ldq9bX42/tz7chrifSMNKXGrdq7iryaPD5Z+AlKef9NMkvrSvnh5A/syt+FhdyCJSOXcGnIpWSps1gTt4ZKbSU3hN3A4tDFZ93P0aNHKdAUkNmQSYNdA5nqTErrS1u9z1Zpi5OlEzYWNijkCmTIMIgGGoVG1A1q1A1qk9OeEXNr93C38F4x79ALeo6XHyepNMnUZ6plfy+lXGnq6WWrtMVCboFMJkMURRqFRuoa60zNkHOrc6lvrMfN2g0HSwesFFamHk3jPcYz2mX0kBQPoijySconbM3dSqRnJA9HPtyv16eEREsEUeDDox+yM28n0V7RPBz58JBNNR1IzhkB1BO++uorbr75ZsrKynB27pz7kiSAJKD1qu+WnC18kvIJD058kGk+0/r02CmlKXx9/Gsy1ZnIkTPJaxIxQTGMcR1zXjjAlNeX89jux1DIFLwx540+SS0TRZHTVaeJzYnlwJkD6AU9PrY+XDvqWqK9oinXlvP9ye/ZnbcbAYFor2iuGXkNfvZ+vT6WrrK/YD9vJ75NqGMoq6etRqVovfjTGTLVmWxI2sDpqtNU66pxUDmwKHQRN4++uctRiVpdLb9l/MafWX+iE3SMdB7J9WHXM8plFD+f+plNJzbx1OSnGOs+tltj7QknKk6wIXkDhZpC7JX2VOuqUSlU3DPunnZTSwRRIL0inbiiOA7kHeBMzRmUFkpUShVuKjdcRBfGeI0h3C8cVytXnK2cO/w7CKJArb6WSm1lc5vvqkyTtbvRvCPSM5Ior6hOC1yNXkNiSSLxxfEkliSanPqMYsVovR5oH4iLtQs2Fjadepb8J+U//JX9F1O9pzLFe4qp31OWOqvZMcZ7jCfSM5KJHhP7pNarrxBEgfeT3mdPwR4me0/mwQkPDmrDGIlzF1EU+c+x/7AlZwvj3cfzaNSjQ3JhYTBw3gmgAwcOcOjQIS644ALs7e05cOAADz/8MBdddBGff/55p/cjCaDzG2PqW8u8/1cOvcKxsmN8HPNxl2tHOktGVQbfHP+GlLIULOQWxATGcGnIpbhau/bJ8QYzu/N3817Se0z3mc4DEx/o02NV66r5O/tv/pfxP+ob61HIFDQYGlAqlIx2Hc31o65nuPPwPh1DV9l0fBM/n/6Zmb4zWTZ+WZeEsSAK/Hr6V7478R0ymYwrhl/B/ID5vJ3wNukV6YxxHcOy8cu6dd1VaCv48eSPbM/djoBApGckC4MW8vKhl3s9rbEraHQaVu5dyYEzB1AqlNw99m5uHn1zq/OmblCzM28nW3O2UlJfAjQ1YjWai4xyGcWp9FO9asYiiiIldSUkliY2M++QIyfSM5KYoBjC3cLbXAnOqMogNieWfQX70At6AIY5DSPKM4oJHhMIcAjo9grytpxtfJTyEaNdR/PU5KeaiWJBFCioKWgyHCmO51TlKURElHIlU32mEhMYwzCnYUNiwcYgGFifuJ6DhQeZ4TuDZeOXSavuEv2KKIpsTNvI5qzNRLhF8Nikx7q9sCVxHgqghIQE7r33Xo4fP05DQwPBwcHcdNNNPPLII+3W/7RFWyduoIvvJfoPY+qbufOTnbMdd8TeQZhLGKumrOr1YxbWFrLpxCYOFh5EhoxZfrO4esTVuNucv9eaKIqsiVtDfHE8j0Y9yiSvSX1+zMTiRF44+AL5tfko5UqivaK5b8J9g9J9RxAF1satJb44nhvDbmRR6KJObadt1PJ+8vscKjxEgH0Ay8YvI8gxCIBGoZHPUz8nNicWGwsbbh59M3P853RrEltYW8i3J77lQOEBrBRWNBgacLVy5d157/b7pPh05WneT36fgtoCPG080Rl0VDZUcoH/BdwefjtKhZIsdRZ/ZP7BgcIDNAqNOKocmRswlyneUwh0CGw25r7+PqjT15FUmsTu/N0klSQhIuJt682CwAXMD5yPXCZnX8E+YrNjyVBnADDCeQRz/OYQ6RnZK1a56eXpvHjwRVysXHhp5ks4qM6+GKhuUBNfHM+u/F0crzgOQLBjMDGBMcz0nTnoV7IbhUbejH+TuOI45vjN4e5xd0siSKJfEEWRr49/zW8ZvxHmEsYT0U9gZWE10MMa0px3Aqi3aOvEDZT9skT/09bk5lDhIdbFr+PW8Fu5MOjCXjtWW6vl1428Dn+H7jW7PNeo1Fby6K5H+zQVDpocd747+R3/y/gfcrmchUELqdJWsf/MfkREpvlM45qR1/So0WZfUKev4+l9T1NQW8CKSSuI9Dx7v6HSulLWxK0hpzqHKd5TuGfcPW1+0cYVxfFJyidUNlQywWMCd0bc2e0oZFJJEh8e/ZBTlafQGXSsn7u+33qvtPy7XjX8KhaHLqa+sZ71ietJLk3G384fDxsP4kviAQhzCSMmMIZJ3pM6XQ/Sl4KoWFPM1tyt7MhtsluXIUMuk2MQDVhZWPWJGUpJXQlP7X0KnUHHC9Nf6JRJiDk51TlsydnCnvw9aA1aPKw9uHrk1a0MMgYTpaWl5J/J55eqXzhec5zpPtNZNn6ZlA4n0aeYR35GOI/gyclP9nsD73MRSQB1EykCJNGS95PeZ1f+Lt6d+26vRGXqG+v5+dTPpnqJUS6juH7U9Yx06ZxRx/lEX6fCnao8xYbkDRTUFhDqGMo94+/B375JgOZU57Dp+CYSShJQyBTMDZjL1SOuxtHSsdfH0V2KNEWs2rsKrUHLE9FPEO4W3ub7jlcc5424N6jWVXPNyGu4YtgVZ43E1Opq+Tztc3bn78ZSYcklwZewKHRRt9I/6/R1rIlbw/8y/oenrScPT3yYuQFz+ywSJIgCe/L38N3J7yirL2v6u467p9nCQnl9OU/ve5ojxUewkFkw138ud4690xQNa8nZvgP6eoFMFEXii+N5O/HtplQzUSTQIZCl45Zygf8FvXoey+rLeG7/c5TWl7I8anmPIq91+romg4yMX9HoNQTYB3DtqGuZ6DFx0KXGGf+Gcgs523TbOFJ8hMlek7l/4v2SMYJEnyCIAv9J+Q9bc7cS5hLG49GPS+Knl5AEUDeRaoAkzBFFkbu23IWzpTOvz369x/tLKU3hw6MfUlpfSoB9ANeNuo4JHhMG3YRgsNBXqXAtowNXj7iaRSGL2lzxTS9P5+vjX3Oy8iT2KntuD7+dqT5Te2UcvUFmVSYvHHwBg2jgyclPMsqleUuA5NJk1hxZg0Km4L4J93XpHCaWJPJF2hcU1BZgr7Tn8uGXszBwYZdTmhqFRq7733VUNFTgZu1GhFsES8ctxc3arUv7ORtGofDN8W9MfYwuH3Y5FwdfbPq7iqLInoI9fJb6GRq9BgeVA+X15ThaOvLk5CfbrfU6m8gxGqbY2NjQ2NjYqwtlFdoKPj76MQklCSjlSuYHzEcpVxKbE4vWoGW8+3juGntXr9QJVmoree7AcxRqClk6dikXBFzQC5+gtUHGOPdx3DX2rl792/cUc4Hr7OrMe0nvsf/MfiZ6TOSRyEcGfQqfxNBCEAU+TP6Qnfk7iXCLYMWkFVgqOl+qIXF2JAHUTSQBJGFOSV0J92+/n4VBC7kt/LZu76e+sZ6v0r9iS84WrBRWXB92PQsCFwzalJDBhHkq3Lo563rsMlWlreKN+Dc4WXmyVdSnPURR5MCZA/z32H+p0dcw2Xsyt4ffPmiiQScrT/LSwZcAeHrK06YeQYkliayNW4uVwoonJz/ZrXomQRTYlbeL709+T7m23FQfMz9wfpcmsa8ceoWjpUeZ4TeD3fm7cVA58EjkI4S5hnV5TObUN9abamJyanJQyVVcEtIUsTK3izcXE06WTtwWfhvRXtEcKzvGmrg1yJCxcvLKVgISOhcBMtIbkaCWQm26z3RuCLvBJHSqddV8lfYVO/N3Ym1hzS2jb+l2vRY01fA8f+B58mvzuT38dmKCYno0/rao0Faw6fgmduXvwtrCmptH39zrEazeQhAFNiRvYHf+bsJcwngk6pEO66AkJDpDfWM97yW+x5HiI0zwmMDyyOWSwO5lJAHUTfpLAElpdUODg4UHeTP+Te4Zdw9z/Od06+9mHvUZ4zqGpeOWDlgfmaHKzrydbEje0GMhmlmVyZq4NVRoK1gUsojrRl3XpTx/dYOa/6T8h0NFhwZdNCi9PJ1XDr+CQqbgyclPUt9Yz+tHXsdKYcWqKavaTe/qLHqDnticWP7K+ouS+hLkyJnoOZF5AfOIcIvo8Evc6Fz32szXKKkr4b2k92gUGrkt/DbmBc7r0lhEUSRLncXO/J2mZqzWFtbM8ZvDZcMuw9mqeduDg4UH+ejoR2j0Gmb6zuTfY/7dTEinl6fz6uFXAVg1ZVWXXP+MzwRbW1uqq6sB8PX17fZzvUpbxYdHPzQJtTsj7my3H1ZiSSIfHv2QSm0l493Hc9+E+7pcK6duUPPiwRfJrcnl5tE3c0nIJd0ad2dpOebeimD1NoIosDF1I39m/4mHtQcrJq3ocj2UhIQ5JXUlrDmyhtyaXKb7TOee8fdIKZZ9gCSAukl/CSDJWGFo8HX61/ya8StrZq0hwCGgS3+3llGfG8JuMLk4SXQNQRR4dv+znK48zSszX+nWZH5/wX7eT34fgLvG3sUsv1ndGstgjgYdKzvG60deR9uoxSAacLJ04ukpT/dY/JgjiALJpcnEZseSWJKIiIiVwopx7uOI8opivMf4NlfLjxQdYW3cWu4eezdzA+aSW53LmiNrKKkvYWHQwg77D+kNelLLU4krjiO+OJ4KbQUAgfaBxATFMN13eqscekEU+OHkD/x46kccVY7cNfaudsWEuYB8btpz3Zrs9vS5nlGVYWrS2pZQawuNXsPnqZ+zK38XHtYePDbpsU4bqVRoK3jx4IsU1BZ0qilsb6HRa9iYupGd+TtxVDmyPGr5oK2B3Jazjf8e+y8Wcosup5BKSBhJLU/lzbg3qdXXcu2oa7ks9LJBGf08F5AEUDeRIkAS5rx48EVOVJzgsws/QyFXdPrvJkV9ep9sdTYr96xkuPNwnpv2XKe/PARR4LsT3/Hz6Z9xtnTm0ahHTSliPWGwRoMOnDnA8l3LEUSB1VNXc2Fw7zkXtqSkroSDhQeJK4rjZOVJRJq+TrxtvQlxDGlqwukUgretN3qDngd2PMCCwAXcEXEH0JTKtS5uHekV6YS7hvNQ5EPYKe2o0ddQVldmarqZqc4ktyaXRqHRtP8ozygmeU1ihPOINq8F81STEMcQHo16tMNIQ0ppCq8efrXT9s8tMY8GaTSaLj3f9+Tv4cOjHwKwdNxSZvjO6NKxt+Zs5b/H/otSruzURL2svowXDrxAUV0Rt4y+hYtDLu7S8XqDA2cOsCF5AwbRwJ0RdzLHf06/j6EzpJensy5+HdW6apaMWMK/hv9LmrxKdJrY7Fg+S/0MpVzJAxMf6NCxU6JnSAKom0g1QBJGRFHkjtg78LHz4YXpL3RqG71Bz8a0jcTmxA5Yrc+5LK7/e+y//J39N8vGL+tUBKe+sZ53Et8hvjieUMdQHp30KC5WLr02npbRoCneU7h77N191iy3I7SNWp7Z/wwnKk6glCuxsrDi4ciH++ULV92gJrEkkaOlR8lUZ1KoKWz2ugwZ+TVN5gQz/WZiIbdAIVPQKDaSWpZKVnUWFjIL3GzcUMiapyU6qhwJdQolzDWMKM8ofOx8zjqWCm0Frxx6xZRqsnTc0k43FtyWu42Pjn5k6vt1tqhUe7QXCWrr3hREgW+Of8NvGb/hYuXCbSG3YaWx6tb9m1aexrr4ddToas4a0SmsLeSlQy9RWl/KnRF3Mj9wfpc/Y2+Rrc5mTdwayurLuDj4Ym4Mu3FQ2k931kZeQsKIXtDzeernbMnZgqeNJysmreiw3lSi50gCqJtIAkjCSFcNEE4XnObNhDcpaixirNdY7hl3z4BEfc7l9MpaXS0P73wYGTLeuuCtswqNWl0trxx+hdNVp5npO5O7xt7VZ921zaNBvna+rIhagbedd58cqz1EUeSthLc4WHiQJSOWMMFzAi8ffJlafS3Xh13PopBF/bpqXaevI6s6i8yqTMrqy6jUVrIrf1eTNbVTKAbRgCAIyGVy5DI59Y31FNcVY6mwZH7AfIY5DyPIIYhgx2BcrFw6PfbSulJeOPgCxXXFXDvyWi4fdnmXP/enxz7lr+y/mkWrukJ7kSBzw4SAgABcXF14P/l99hbsZbjTcJZHLSf/VH6P7t+SuhJeP/I6eTV5XDXiKq4aflWzz59cmszbCW9Tr6/n7nF3D4qoi7pBzbr4dRyvOM5U76ncN+G+bgnPvkbbqGVD8gYOFh4k0CGQFVErzuuG1RLto25Q82b8m6RXpBPhFsFDEx/qsYGPROeQBFA3kQSQhJGWBghnI6Mqg2d3PItap2a222wemfvIgNX6nIsRIPPPdKz+GB8c/YCLgy/mljG3tPn+al01Lx58kZzqHK4YdgXXjLymzyf/oiiyOWszX6Z9ibXSmgcnPsg493F9ekxzfj71M5tObGKy92QenvgwMpmMIk0Ra46sIb82n5m+M7l77N0D6jj07fFv+en0T7w287U265KSS5NZe2QtCrmCldEru1wXYt7H5o6IO7od2TAIBl45/AopZSncFXFXl00ajCQlJWEwGFAoFIwfP95kmQ2gUCrYod/BwcKDRHlG8dDEh1AqlL1y/9bqannp0EtkqjP517B/cc2oawb8+uwIvaDnncR3OFR4qOl8RD5kKhAfTM80URT5+fTPfHviWxxUDiyPWt6mc6DE+UtOdQ5rjqyhtL50UEc1z1U6O4+XqrElJNohsyoTgBDHkLO+b2/BXp7d/yx6uZ4lvku4ZewtA2p04O7uTkRExIBPFHqToqIidDodRUVFzPafzTCnYfyV9Rd51Xmt3qtuUPPc/ufIqc7hmpHXcO2oa/sl8iGTybgk5BIej34cgFcPvcofmX/QH2tM2epsvj/5Pf72/tw77l7T5/Wy9eKF6S8Q6RnJnoI9rD6w2mQeMBCEODXdS5nqzDZfH+c+jpWTVyKKIi8fepm08rRO77tSW8kLB16gpL6Eu8fe3aO0LoVcwUMTH8Ld2p3P0z6nWFPc6W1LS0tJSUmhtLS01WsajQZoSnv7s/pPDhYeZIr3FB6OfNgkTHt6/5aWlpJ1Iot7ht1DqGMoP53+ie9PfM8HyR+wMW0jXrZevDT9pUElfgCUciUPTniQGb4ziCuO452EdzAIBqD5/T/QyGQy/jX8XyyPXI7OoOP5A8/z48kfTfVpEucvgijwV/ZfPL3vaSq1lSwdu5RbxtwiiZ9BiiSAJCTaIVOdiUquwtfOt83XBVHg6/SveSfxHexUdrw8+2VumnHTOSU8BgteXl6oVCq8vLyQy+TcFn4bAgLfnPim2fuqddW8cPAF8mvzuTHsRv41/F/9PtbxHuN5afpLeNl6sTFtIx8kf4DeoO+z4+kFPe8nv48oiiwbv6xVXYKN0oZHox7limFXcLrqNE/ueZLTlaf7bDxnI9gxGGhfAAGMdh3Nk5OfRCaT8drh1zhRcaLD/eoMOtYcWUNRXRF3RNzRK0087VR2LB23lAZDAxuSNyCIQqe2M5+s+/r6olKpcHBwICUlBVtbW5RKJXv0e0iqSmKy12QemPBAr6Z7GY+vLlPz5OQn8bH1YX3iev6X+T/Gu4/npRkv9Xt6ZmdRyBUsG7+MaT7TOFR0iA3JGxBFsdn9P1iI9o7mhekv4G3rzXcnv2PVvlXkVucO9LAkBogiTRHPH3ieT499iqPKkWemPtNrzYQl+gZJAElItIGxz0igQ2Cbqzd1+jrWHFnDrxm/MsxpGK/MeKVbTSYlOqat9JdQp1CmeE8hvjie4xXHgSZr3ZcOvkReTR43hN3AotBFAzZmbztvXprxEuPdx7MzfyfPH3yeKm1Vnxzrl9O/NKX6Db/CJDBaIpfJuXbUtTw44UE0eg2rD6xmd/7uPhnP2XC1csVB5WCKrrbHSJeRrIxeCcDLh14+q2ATRZGPjn5EhjqDK4ZdwYLABb023nC3cBYELiC9Ip3YnNhObWM+WTdGczQaDTqdDo1GQ55dHofKDzHOfRz3T7y/W6vDZ4symR+/pK6EGl0NukYd9Q31zHObN2AGHZ1FLpOzbPwyU9Tyt4zfBm1UO8AhgFdnvsoVw64gR53Dyj0r+enUT1I06DxCEAX+yvqLx3Y/RnpFOjGBMbw++/VBa+su8Q+SAJKQaINafS21+lr87P1avVakKWLVvlUklCQwy28Wq6eubtV4sS3ONmmRaJ/20l+uHXktcuR8nf412kYtrxx6hezqbK4ecXW/9TM5GzZKGx6PfpzFoYs5WXmSlXtXdjjx7yrZ6mx+PvUzgfaBXDH8ig7fP813Gs9New57lT3vJb3Hl2lfmtKM+gOZTIa/vT9FdR2nMo10Gcnj0Y8jiAIvH3653dX13zN/Z0/BHiI9I1kycklvD5kbwm7Aw9qDr9O/pkjT8bjbmqx7eXmhUCg4UX2CT49+io+tDw9NfIiq8iqSkpJISkrq1HPB+AwpKChoNyXMePyTupM8s/8ZtAYt1/hcg6PCkbeT32Zf4r5B/wyykFtw/4T78bPz45vj35BQnDDQQ2oXpULJtaOu5cUZL+Jl68W3J76VokHnCaaoT+r/R32mPMPtEbe36kcmMTiRBJCERBtUapsKlVtaJp+qPMVTe5+isLaQm0bfxL3j7u10UflgymMfSpivaJuLSG87b+YGzOV4xXGeO/Acp6pOsTh0MVcOv3Kgh2xCLpNzQ9gNLBu/jBpdDc/sf6bXJnOiKPJp6qeIotiljuIhTiG8MuMVRjiP4PfM33l639Pk1bSupeornK2c0eg16Ay6Dt872nU0KyatoKGxgTVH1lCtq272ekppCl+nf42fnR/3jb+vT2rvrC2suXvc3TQYGvgq/atu7cPd3Z1KQyXfFn6L3CDnsUmPYaO0oaioCIPBgMFgaPe5YH7NG58hQLspYTW6GtYnrGd94nrslHasnrqay4MuZ7HHYqobq/kq9yvOFJ7p1ufoT6wtrHls0mPYKm15O+Ft8mvyB3pIZyXUKVSKBp0ntBf1GeM2ZqCHJtEFJAEkIdEGlQ1NAsg8spNens6LB19EL+h5PPpxLg25tEvF9YMxj30o4O7ujq2tLbm5ueTl5TUTkVeOuJJaXS2783cT7RXNdaOuG5QNCmf5zeLZqc9ipbDijbg3OFx4uMf7TCxJ5HjFceYGzG039a09nKyceGbKMywOXUyWOosn9jzBxoSNJB9N7vPogLNl0z1lXGToiLHuY7kt4jZK6ktYF7fONKGs09fxfvL7WCosWTFpRZ+mdoW7hTPZazKHiw5zsvJkl7fXG/T8VP4TOlHHvRH3mmpwjJEhhULR7nPBfOHE+Azx9fU1RZnMBdLW41u5+393sytnF5Gekbwy8xWGOQ9Do9Ew1n4s05ynka/L56jhaI/OR3/haevJI5GPoDPoeCPujU6J5oFEigad+7SM+jw95Wkp6jNEkQSQhEQbGOs1jJO1lNIUXjn8CgAro1cy3mN8l/c5WPPYhwJG62BRFJuJyIyqDPRCk8HAOPdxA+q+1xHDnYfz7LRnsVXa8mb8m+wr2NftfRmbZyrlym4bPSgVSm4Iu4Hnpz+Pp40nP2T8wPtZ75Ock9ztcXUG46JCVUNVp7eZFzCPC4MuJL0inc9SPwPgi7QvqNBWcNPom/Cy7ftFhSUjl5hSLjvj7GcuTH489SMluhJuGncTc0bMMb1uNEoYP358u8+FtmqKzN9bVFREVX1VU9QnZT16Qc8i10VcaHkhjTWNzfZxy9hbGOY+jL8K/+rXqF9PGOM2huvCruOM5gzfnvh2oIfTKdqKBn2e+nmrCKbE0KFOX8d3J75rFfUJdwsf6KFJdJPBO1uQkBhAjFbBzlbOJJUk8dqR11DIFKyasoow17ABHl3H9LTeaLDVKzk7O5t+GieAedV5vJv4Ln72foQ4hvDL6V/61G2tN/C39+e5ac/hZOXEO4nvsDNvZ7f2s7dgL7k1uVwUfBGu1q49GtNw5+G8NvM1Lgm6hCJdER8XfMzPp37us9og46JCV+24bxp9E+Gu4WzJ2cJHyR+xPW87EW4RzAvoXo+eruJn78ds/9mkV6STXNqxSDRGbg6cPMAPx38g2DGYy4dd3ur1goKCHt1rxcpiNuRvIK0ujUiPSB4KfYjxDuPR6/WmSKlROPl4+rB03FIEQWBD0oZ+rf/qCZeGXMowp2H8kflHp1wB+4OOnpHm0aAAhwA2Z23m/m3388PJH6hvrO/n0Up0F71Bzx+Zf/DA9gf48dSPuFm7SVGfcwRJAElItIFxdTq/Jp81cWuwUljx9NSnGe48fGAH1gYtv4hLS0vJzc3tUb3RYKtXCgkJITIykpCQpj4yNboaXj/yOnUNdVzhfAULvRdSWl/K7oJ/nM06mqAMlMjztvNm9dTVuFm78UHyB+zK29Wl7Q2Cge9PfI+t0pbLQi/rlTEpFUrunnQ3T0U+ha1oy6eJn7Ji+4o+iRI4WTkBXYsAQVNh/EORTb15Pkv9DEEQuHvs3f2a8nj1iKtRypV8c/ybDqNAXl5eNAqN/FLyCwa9gXvH3dvM7toYlQHOeq+1J5SMtT4fn/4YK1srHpz0IM/MeobpE6ab7LfbSqsb4TyCS0MvJUOdwZ/Zf3b3VPQrcpmce8c3nb8NyRsGRSpcZ5+RoU6hvDTjJR6c+CDOVs58f/J7Htz+IH9l/2WKXksMPgRRYGfeTh7a+RAb0zaiVChZOnYpa2atkaI+5wiSAJKQaIPKhkq0jVo+OvqRSfx01BB1oGj5RWz+hdzdeqOBqlfqjCgRRIE349+kpL6EC10uxNfCl1HyUdgqbfk943dTvxbz89LWfgdS5HnaevLs1GdxtXblg+QP2F+wv9PbxhfHU1JfwkXBF2GnsuvVcVlrrLnb926mOU0jozKDJ/Y8wVfpX1Grq+21Y3Q3AgRgr7InyisKg2hAL+gR6fsms+a4WrsyN2Au2dXZnKg8eyTC3d2ddEU6ZY1lXB56OQEOAa1ej4iIOKtYgX/qhAwGQ5MQKixga85Wlu9czr4z+4j0jOSN2W8wy2+WSQx2lG67ZMQS3Kzd+PnUz2j0mm6cif7H186XJSOXUKgp5KdTPw30cLr0jJTL5Ezzmcba2Wu5M+JO5DI5nx77lEd2PMKe/D2d7jEl0feIosiRoiOs2LWCDckb0DZquTHsRt6+4G0uCLhAamp6DiEJIAmJNshWZ1NaV4pSoeSpyU8R6BA40EMC2hYILb+Ijf8fEBDQ7gSoI6ExUPVKnREl/8v8H6nlqSwIXMDisMWoVCoCfQJZGLiQQk0hBwsPAs3PS1v7HWhTCncbd56e8jSOVo68k/hOp40RYnNiUcgUzA+Y3+tj8vLywlJpyYUeF/JU5FP42vryW8ZvPLDjAX49/SsNhoYeH6O7ESBo6vW0J38Pw52Ho1KoutSgtLdYGLQQgNjss/cFUjeo2VG8gxC3EG4Yf0Or1433IGC6Rs3vR/PXFQoFgihwrPYYH575kI9TPgbgxuAbm9X6dBalQsmSEUuo1dfye8bvXdp2ILk05FL87Pz4I/OPTpto9BXdeUZayC2YHzift+e+zXWjrqNWX8u7Se/yxO4nSCxJ7FRtmUTfkV6ezjP7n2Ft3FpK60u5YtgVrJ+7nkWhi1ApVAM9PIleRhJAEhItyKjKIKE4AQu5BSujVxLiNHgiP21N5Ft+EXfmi3mwpbgZ6UiUHM05ymeJn+Fi4cLNo29u9lkvDL4QlVzFL6d/QRTFZq/Z2toCmH5C03lqa+LZn3jZevHMlGewV9nzdsLbJJYknvX9Z2rPkFKWQrRXdKd6T3UHhUKBr68v0SHRvDrrVZaNX4athS1fH/+aB3c8yNacrT2qHbG2sMZKYdWtCeyvp3+lVl/LXWPvYkHgAtLK09iSs6XbY+kOvna+hLuGc6jwEOoGdbvv++X0L2gNWq4ddW2z1Dcj5vdgW/ej+e8qrSr5b+F/+a3iN+rEOpaMWMLbc98mSAhqVuvTFWb6zRw0YqKzyGVyrht1HTpBx4+nfhzo4XQbS4Ullw+7nHfmvsPi0MWc0Zzh1cOv8uz+Z9l/Zr+UGtePCKJAfHE8Lx18idUHVpNRlcGCwAW8fcHbXDvqWmyVth3v5DxksNUJdwdJAElImFFWX8arh15FL+iZ7D150HVz7q2oxUBHP9qjPfFWWlpK8tFkNiRvoFFo5FLnS1utyOmqdYxWjSajPMNUpG58SFdXN7kvaTTN030GgxD0sfPh6SlPY21hzVvxb53VMtc42Y8JiumTsbRMG0w9lkqYZRjr5qzj1jG3YhAMfJzyMct3Ledg4cFur1g7Wzl3edKtblDzZ9af+Nv7M8N3BjeOvhE3aze+Tv+akrqSbo2ju8QExdAoNrI9d3ubr5fUlRCbE0uoYyiTvSa3+R7ze9D437a2tqZJhZeXFyWGEjaVbeK9E+9Rrahm8YjFPDvuWUY0jKC2srZH97G5mPj59M9d3n6giPSMZKTzSLblbKOwtnCgh9Mj7FR23BB2A29f8DbzAuaRoc7g7YS3uW/bfXx34jvK68sHeojnLOoGNb+c/oUHtj/A60de51jZMab5TGPdnHXcEXFHny0wnSsMhu/OniIJIAmJ/6fB0MAbcW9QpavC3cZ9UEV+jPRWatpQs+QuKipiV8kuCrQFzHCdQXRIdJvvibaLxtBo4JfTv5h+Z2wcaayjOFv64EDh7+DP8qjlNAqNbTb8BNALenbl78LPzo8wl75xIjQ/HwUFBabie6VCyYXBF7J+7nquGnEVldpK3ox/k6f2PkVyaXKXhZCTpVOXU+C2525HJ+j41/B/IZfJsbawZunYpWgN2n5PhYv0jMTZypltudva/Ow/nvyRRqHxrH2pjPcgYOrxo9Fo0Ol0HM05ype5X/JZyWdkaDKY7Tebt+a8xS1jbkFTrjH9XYqKirC1te12FDPSM5JAh0B25e2iTl/X5e0HAplMxvVh1yMg8P3J7wd6OL2Cq7Urd429i/fnvc91o65DKVfy46kfuW/bfbwR9wYppSlSnVAvIIoiJypO8E7iO9y77V6+Of4NOoOOK4dfyTvz3uHBiQ/2i6X+ucBg+e7sCa3j8hIS5yGiKPJB8gdkqjO5JPgS/sz+EyuF1UAPS+L/ER1Edp7Yia+DLw/NeajNfGwvLy8oguk+04mriONExQlsbW3R6XQ4ODiYJpdFRUXN0gXhH+OIgRSEYa5h3BZ+Gx+lfNQkLiY/1Sx16nj5cTR6DYtCFvWq85mxH42xz4zxHBQUFLR6r7WFNVePuJqYwBh+Of0LsdmxvHzoZfzs/FgQtIBZvrM61ZDUUmHZpTQfQRTYmrsVZ0tnor3+Eb8R7hHMD5jP1tytbMnZYqrP6Wss5BZM9Z7K5qzN5FTnEOQYZHpN3aBmb8FewlzCiHCP6HBfRpF+pvAMxapi/sz9k1xdLhZKCyI9I7lu5HVYNVhRcLKAAgpwcHAAMJkiGAW++XXdWWQyGQuDFvLR0Y/YXbCbC4Mu7NL2A8Uol1FEuEVw4MwBbhx9Iy5WLgM9pF7B0dKRy4ddzuLQxSSWJBKbHcuRoiMcLjqMt603CwIXMNtvdq+bn5zr1DfWs69gH7E5seRU5wAQ5hJGTFAMk7wmoZQrB3iEQw/z74qhiiSAJCRoytfff2Y/0V7RXBLSJIAGc1PN84XS0lLOFJ7hPwX/wSAauGX4Le0WoxofyM41zhzadohPD33Ktd7XAk2pb8Z6n5YrVuah/IF+oM8LnEdOTQ5/Z//N56mfc3vE7abX4ovjgaZV+96kvc/v6+vb5vmCponaLWNu4aLgi/gz60925u3k02Of8k36N8z0m0lMYEwr1zNzFHIFBrHzdUQJxQmU1Zdx1YirWtXT3Dj6RpJLk/k6/WsmeEzAw8aj0/vtCVGeUWzO2kx8cXwzAbQ9dzuNYmOn0xStXazZcXIHyfXJ1Aq1KGQKZgbM5KLgi0wpuCkpKRgMTedLo9EQERFhEq62tram67s7TPeZzpdpX7IlewsLAxf2q614T1gYtJCUshS25W7j6hFXD/RwehW5TE6kZySRnpEUaYrYlruN7bnb2Zi2kU3HNzHNZxqTvCYR4R6BpcJyoIc7IDQKjVQ1VFGpraSqoQp1g5pGsRGDYEAQBRoaG8ivzSerOqupYbZBj0qhIswljHEe4/Cx9cHawpqCmgKcrZyxV9lL3/nnGZIAkjjviSuK49sT3xJgH8C94++lRlcD0GbhskT/YJzcGQwG9pXvI6s6i6lOU7Gt67gg1c/ejyBVEGk1aajd1DgqHU2TR2PKkTntCaOB4ubRN1NQU0BsTiyBDoHMD5yPKIrEF8fjYe2Bv71/rx6v5ec3n1h3hIeNB7eMuYVrRl7D/jP7+Tv7b7bkbGFLzhZGuYwiJjCGaO/oViusFjKLLhkpxObEIkfOXP+5rV6ztrDm7rF38+KhF/kk5ROenPxkp/fbE0a6jMRWaUt8cTxXjrgSaIpUbcvdhpOlE5O8JrW7rSiKpFek80vaLxwuOozCQoGHvQdLApYwN2Buq/oDY0qi8b+hefTSGL3rDlYWVszxn8PmrM2kV6Qz2nV0t/bT30z0mIirlSvbcrdxxbArztnntZetFzeE3cDVI67mYOFB/s7+m535O9mZvxOVXEWEewRRnlFEekbiaOk40MPtFfSCnryaPErrSqlsqKRKW/XPT20llQ2VbaYJGwQDdY111OnrqG+sN6UNqhQqHFQOWFlYkVOTQ05NTqtt5chxsnLC2dK52U8XSxecrJzwtvXGy9brnBBJLaP+5yvn5hNDQqKT5NXk8U7iO9gp7VgxaQXWFtZUaasAzokH3VDFGJXQo2ePeg+u1q4s9FrYpkgpqy9j1d5VHCs/RrhrOC/OeJGLQi/i5NGTnJKdYrJi8lkjPIMtlG9s+Pnk3if577H/4mPng53Srqn3T9BFvb5C3/LzG8+9eWqV8Wd7X5hWFlbMDZjLBf4XcLrqNLE5sRw4c4D1FetxTHVkjv8cJntPJtgxGLlMjlwmR0REEIUO77MaXQ0ppSlM8JyAq7Vrm++JcI9gpu9M9hTsIaU0pVOpZz3FQm7BePfx7Duzj/L6clytXUksSaS0vpQrh1/ZZlpNsaaYuOI4tuduJ782n/r6eoKtgpnqOpWrp1zdZo8R42TF19cXaJ6u2VvRy7kBc9mctZn9Z/Z3WwD196RKIVewIHABm05sIr44nsnebZtNnCuoFCpm+c1ilt8sCmsLiS+JJ64ojsTiROKL45EhY5jTMCI9I4nyisLPzm9IRPP0gp686jwy1ZlkqbPIVGeSW51Lo9ja2l0pV+Js5Yy3rTejXUfjbOmMTCajtL6UnOoc8qrzUMqVOFk5Mc5+HBHuEYx1HYu7jTsCAgaxKTpkEAxoDdpmEaRKbaVJXGVXZ7cZobZSWBHsGEyIYwghTiGEOobiaes5ZOYK5guLBoNhUGQ9DCSSAJI4b6nR1fD6kdfRC3oei37MlDpjfPAqZFLDs4HCWLsTr41HVIncPPZmIgP+Sf0yn2w9nfQ0BwoPIIgCBwoPsGrvKt6f/z4/5//MkcojXDL2EspKykwOW0Nh1cteZc9jkx5j1d5VrItfxxy/OUDvp7+1hTHaIAgCcrm8VR+ls507mUzGcOfhDHcezk2jb2Jn3k62ZG/h14xf+TXjV5wtnYn0jKSkrgRRFDGIhg4nD4kliQgITPJsP6ICsGTkEg6cOcA3x78h3C28XyZ/UZ5R7Duzj6OlR7kg4AJ25e9Choy5AU2RKkEUOF11mvjipslqfm0+ADYWNlwcfDET7SeC+v+bnZqJH/Pru6XbUksDBOh+w2MjfnZ+eNl4EV8cz+3ht3f63LU3zv66v+YGzOW7E9+xM2/nOS+AzPG28+ZSu0u5NORSanW1JJY0iaDEkkROVZ1i04lNeFh7EOUVRZhLGCFOIbhauQ64IGpT7NTk0ij8I3YcVY5EuEcQ4hiCl60XzpbOOFs1/bOxsKGqoYpMdSbHK44TXxxPQW1TZFQlVzHddzqRnpFM9Jho6jfWHQRRoEZXYxJHFdoK8mryyFJnkaXOIr0i3fReawtrghyCCHEMIdQplBDHkEErioz3qEKhGPIGBr2BJIAkzksMgoG3E96mpK6E28NvZ4zrmIEekoQZGo2G2sZadhTuIMAtgNl+s5u9bj7ZOlZ+zJTqIIgCqeWpyGVy5gfO55vj35BlyGJaxDRSUlIGTa1PZ/C39+f+CfezNm4t3538DiuFFcOch/X5cY2RBYPBgEKhaJVq1VkcVA5MdZhKoHsgGhsNWfos4ovj2Zq7lZK6EjR6Devi1hHtFc1Ez4ntpu8YV7cnek486/E8bDxYELiAP7P/5FDRIaZ4T+n8h+4mI1xGAJChzmCGYQbJJcmEOIWQrc7mh5M/kFCcgFrX1CvIUdUUCYvyjGKs+9iz1m6YX9/mKYo1NTXodDoEQThrWmdXkclkRHpG8kfWH2RXZxPsGNyp7QoKCjAYDOTm5uLs3JS215+TKkdLR8Jcw0gpS6HB0HBe1sPYqeyY6TeTmX4z0Qt60svTiS+OJ744ns1Zm9mctRloWlQJcQz5518/iaKy+jLiiuNIKE4gtTy1tdhxizCNKdgxGBcrF9OYKrWVZKozOVB4gMyqJtFU2fCPfb6TpRPzAuYR6RlJhFtErzUrlcvkOFo6tvlMEkSBIk0RGVUZJiHXUhTZKm0Z7z6eKM8oxnmMGzS9hMyfJUPhO7CvkQSQxHnJrxm/klKWwvyA+a2KlS1kTbeFZDs6cHh5efFr4q/oBB0LPRe2Sg0yf5CPyBtBfGk8AgJy5ITYhJCSkkKESwTfGL5hU8ImhiuHD7pan84Q5RXFNSOv4fUjr+Nl64W1hXW/HLfluepummBRURGN+kYs1ZbEBMRwQ9gNFNYW8tKhl0guTSaxOJGEkgRkNEWORrmMMqWYeNp40ig0klSSxHDn4Z2qb7hi+BXsyNvBpuObmOQ5qc2Ust7E1coVGwsb4ori0Og1ZFdnU1pfSkZVBtAUWbkg4AIiPSMZ5jSs1apwe2lj7V2rxj5WcrkchULRq9dylFcUf2T9QVxxXKcFkDmVlZUoFP0fNY/0jCS1PJWU0hSivKL6/fiDCaVcyVj3sYx1H8u/x/yb/Jp8TledJkOdQZY6i7TyNFOPNGguioIdg3GzdsPJ0gknS6du3zuCKJClblrsiCuOM7muWcgsCHMNY4TziGZiR0RE3aCmsqFJ7GzP296m2LGQWeBn78dEz4kmARfkENTvkRa5TI6PnQ8+dj7M9Jtp+sxFmiIy1ZlkVGVwouIE+87sY9+ZfShkCka7jmaix0SivKL6zaSlLQZbuvdAIwkgifOOnOocfjz5I352fvx7zL9bvW58oHbFpUqidxFsBOLV8fhY+uCt9271uvmD/Dav29DWa8mqzyLYOphbPG9pql+pgBFWI0iqSiI5J5n5UfOH5MN/XsA83ox/k6qGqn6rb+nJF6X5pN7Ly4vc3KbGrsbIm7edNyH/x955h7dVnQ38J8mS5W1575nYcRJn2ZmEkJAFlD0TaIEOCqFlJkApo2z6NYFCoUALhUIpCZumJZAdsoftxHESxxnee8tT1vz+cK8qO7ItWZIlJ/f3PHmSSPee817de88573lXUAqN3Y28u+Rds/vOkfojnGo5ZW7HX+5PkHcQ1R3VTAybSG1nLZG+kYPuWAd5B3Fl6pV8ceoLdlTsYGHiwmFdw0Coe9SUqEvMu7/Cn259N6daTtGt72Z29Gzmxc0jKzKLSL/IQduzxW1sIGuQs5/ldFVvUof8+nybsqpZqztkMBioqqoa0fcsKzKLj058RE5dzgWvAFkikUiID4wnPjCeBSwAejOnVbZX9j7DAyhFABIkBCoCzckAVEpVH1e0QEUgcpkcL4kXUonU7OqZ35DP0Yaj5hpfvl6+TAidQHJgMqE+oXTqO2nRtLC1fKs5qYG6R42RvpuN/ZWd5KBkEgISkMs8M121pVI0N3YuAM2aZvLq8sity6WgsYCCxgI+PPEh8QHx5qQVqcGpHukqd6EgKkAiFxR6o95cNPHeKfdaHVCFnS97slSJOJfPTn2G1EvK5VGXEx19rgJkSXp8Oo9KHzXHrHh7e9PV1YWfnx8/GvMj8vPyOaE/wSIWnXPuaMiGU9pWSphvGDqDjneOvsPqeattqrXjLiwX7P0LfQroTXpkEhkBigDmxc0jwzuDS+WXQhCoZWqKW3sVi7y6PFp7WtlVtYucuhz85H4kBCQQogwhRBnSJ0uTkLXpypQr2VS6ic9Pf87cuLk2u0XpDDqzv39rT2uf7FMtmhYq2ito1jSbj5ciJT4gnomhEznbepYgZRATfSfy8sUv2+xWZEtq9v5Kj/CcOvvZlUllpAalUthciMFoGNICILi/uZsovyji/OPIq8uzKanGhYyX1IukoCSSgpLOUYrK28tp1jSbEwG0aHqf/+qOaus1u0zQqe+kQ9tBl77LXBBYLpPj5+WHr9wXvVHP8abjHG86fo4cKm8VYT5hjFWNNStYwd7BJAQmeLSyYyshyhAWJS5iUeIiNHoNBY0FZlfAr898zddnviZIEURWZBYLEhYwNnis22O0LjREBUjkgmL92fWUqEu4dsy1pAanWj1GSH4gWoDcQ1VHFXuq9pAdk801M68xfz7Qgq9/zIpe3+tj3tbWhq/Ul6SgJI6oj1BdV01TfVOf8z2pBtBAlLaVIpfKuSr5Kr4v+55/Fv6Tuybd5W6xBsQW97n+C1XBVU7RpmBeZm+mK4DXcl/jh8ofWDF5BdUd1eagaUt/+/4oZUp6DL01QFZsXkFSUK+bjJfUCwkSDCYDBqOh92+TgW59Ny2aFjp0HQO26S3zJsoviknhk8wuQ4mBiShkCg7WHOT/Dv0f7dp2FicuPmcRY/ncCtdqTWnpbzmzpvRY4opnNyU4haONR6nsqCQxMNHqMYKcRuP/du2Fa5ZKpeZsdSPJpPBJbCjZQF1nHdH+g2+YuBNP3HCxVIqsYTKZ6NR1mjcEKtor2F+zn2ONx9AatHjLvIkPiCcuII44/zhCfEKQSWRIJVJkEhkyqYwgRVAfa5K/3P+CWuwrvZRMj5rO9KjpGE1GTrecNsdpbavYxraKbSQFJrEkaQlzY23ftBFxDFEBErlgsHR9u3HsjQMeJ6SvtadSvYjz2Fy6GRMmrhlzTZ/PB1vw9V90CwqRTqcjXZHOzo6d7Dy7k7HeY/ucb3meJy5OAJq6mwC4MvVKKjsq2VK+hVnRs0bEFW442OI+pzPo8JJ69ak5JKRmbWhoMJ9foi4hMTDxnDg9rUH7v/S1/92ttty5bupuorKj0hyLI6S/NWFCJpHhJfUyL9CUXkriA+JRKXt3oEOUIf/7+7+LNh8vnwEXbBG+EfQYetAb9aQGnbupYi2Lm/AMDvRdZmbmkL/hUDFtw3meU4JSAChuLR5QAbLMJCVYgITdf8ukGSOJELNUoi7xaAVoNGy49EcikeAn96O8vZyt5Vs5WHsQg8lApF8kP078MQviF5xTt0pkYKQSKekh6aSHpHNrxq2Ut5WzqWwTuyp38dejf+XjEx8zP34+ixMXE+Mf425xz2tEBUjkgsAW1zcBX7kvMonM7McsMnJo9Bp2VO4gPiCejJCMPt8NtuDrv+gODw83LwAXj1nMriO7OGM4wzjZuD6LbMvzPDVLXEtPCxIkhChDuHvy3TzywyOjwhVuMFp7WgnyDjIvCKF38Wz5+3fpuqjtquWimIvOOV8hUxDpFzlojM0/C//J+rPreTDrQSaFT3LZtaiUKnoMPRhNRqvJA6wp58K/B/sOoLi4mJaWFlQqFSkpKecoNYM9p7Yuti3bNCtA6mImNky0KUGDEOMlfOcOLOWeEzvHLTLYwmhLxNKl62Jn1U42l242p3DPDMtkadJSpkVMc3mSkQuBhMAEfpH5C24ddyu7qnaxqXSTOXtfZlgmSxKXkBWZJf7WLkBUgEQuCATXt+vGXDeg65uAkAJTKIgqMnLsqdpDt76bJYlLztlxtzcw3/L4CRUTKGwpZKHvQhQorAZqe+ripFXTSqAiEJlURoRvBLdl3Mbfjv3N413hBqO1p5WEwARzvSc/Pz8CAgL6KgNdvVaRWP/huVQtTFjIv8/+m02lm1yqAAUoAtAatMgkMuIC4s753ppybg1rz3dLS0ufv+2xINj6PFu2OXHiRLxl3tR21g7YlzU53W05jfGPQSlTUqwudkv/tjJasnBZWiU0Bg1+cj+uSL6CJYlLPNrCNprxlfuyNGkpSxKXUNhcyOayzRyoOUBBY0FvPFHCIhYmLHSovpFIX0QF6DzCU1143I2l69sNY2+w6ZwQZUifgGcR12MymdhYthGlTGlOL+os5sbO5VjTMY53HGeq/1Srx3jq4qRF09Jn0luUuIgDNQc83hVuIKrrqmloa2Cs31hzWufOzk5SUlL6/P7CBkSIMmRY/UT5RTE5fDK5dbk0djcS5hPmsOzWELJg+Xr54iU9d0odbFweSqFRqVRmCxDY57Jp6/Ns2aZEIsFX4ktpfSl+E20rsuoJ741UIiU5KJnStlK3yjHaqe6oZl3ROg7UHAAgKTCJpUlLuSj2IjEuZYSQSCSMDx3P+NDxtGpa2Vaxjc1lm/ns1Gd8feZrLk++nGtSr8Ff4e9uUUc9YroUD6OhoYGCggKrKUaHor8/uUhvsLWtrm+WBHsH06ppFWsBjSCnWk5R1lbGJfGXOL3ezczomcilcs4YzqBQKNwSqD1cWntaUXn/z8deKpFy9+S7UcqUvHP0Hbr13W6UzjYsx7UzlWcwGU1Ie6RERUUNWJG8RdNr9XAkvmBp0lKMGNlatnXYbQyFzqAzx8BYo/+4bPlbDHb9ACkpKWRlZZGS0uviFR4ebo4PsjbeD2f+sGwTQKFXoNaq6ezstCkWyVOI8I2gU9c5Kt4HT6Opu4m/Hv0rK3es5EDNAbIjs3nhohf4/cW/59KES0Xlx00EK4O5fuz1vHnpmzyc9TAx/jGsP7ue+7bdx9env6bH0ONuEUc1ogLkYTiixAw1mV6I7KzcSYm6hCtTrxzS9c0SlVKFESPt2nYXSidiyeayzQAsTlzs9LZ95b5kRWZR0lVC9JjoUbOoM5qMaI1alF7KPp9H+EawPGM5jd2NbCje4CbpbEcY14oqinir8i3KNeXsUe9B4i8ZcJEtFEEM9g4edr9TIqYQ4RPB1vKtLktq0tLTglQiHTD9cv9xub/VZ7hKhrXx3tFNsIaGBvxl/miMGsIihraYObJh52yE50RQnEWGpkPbwT8L/8kD2x9ga/lW0kPSeW7Oczwy/RHGqsS0zJ6CTCpjZvRMfn/x77l/6v34K/xZV7SOB7Y9wOayzeiNeneLOCoRFSAPwxElxpHJ9HxEZ9DxWdFnBMgDuG7MdXadK+y4i5PpyNCubWdfzT4yQjKID4h3SR9Cgbo9VXtc0r4rEFKxC6nZLVmUsIhov2jWn12Pukc90qLZhTCuvV/7PoXqQowYKWkv4dHtjw54jnBNjliApBIpixIXodaqyanNGXY7gyGMEQMpQP3HZeG38PPzs0t56K9sWBvvrc0f9igptbW1+Ep9QQLywKGt5Z7kdSC4SgqKs8jA9Bh6+ObMN9y//X7Wn11PjF8Mv5nxG343+3ekh6S7WzyRAZBKpFwUexGvzn+Vn038GUaTkfcK3mPljpXsrdoreqzYiagAeRiiEuM8NpZtpEnTxLVjr7U7W5YQcyFOpiNDXl0eeqOe+fHzXdbHlPApKGVKDtUeclkfzkZwrbK2uPaSenFz+s1oDBq+OfPNCEtmH8K4dqrtlHmSNmEatJ6P1tibHU4hUzjUt1BT6GDtQYfaGYjWnlYkEolZSW3sbuSezfcwd91c7tl8D43djcD/FJH29l6rcltbm13Kw3CVjcFc8PoTFRWFj9wHuZfcpl1lT/I6MI/Z4qbVgOiNeraUbeGB7Q+w9uRa/Lz8+PWUX/P7eb9nasRU0eIzSpBL5SxNWsrrl77OLem30NrTyuuHX+e3u3/L0Yaj7hZv1CAqQCLnJV26Lr45/Q2hylCWJi61+3zRAjSy5NblIkHC1AjrCQqcgVwmZ0rEFM60nvF4i4mAoPgMtLM3K3oWKUEpbCrbREPX8NyQRtKNaWLoRCT8t2gm0nNSnVtiMPZavwayrNiKSqliTPAYjtQfsdsNzpbfRt2jxmQymeMLn9z9JPtq9qHuUbOvZh9P7n4S+J8i0tLSYk79bU15GKjP/sqGteOsKUmDueBZQ/rfZYEtCpAnbdgFKYIARs27PdIcbTjKqh9W8W7BuxiNRn468ae8uuBVLo672OF3TMQ9+Hj5cP3Y63lj4Rv8KPlHVLRX8OKBF3lh/wvUdNS4WzyPR3zqRc5L/n3237Tr2rkp7SabEx9YIrjdiAqQ69EZdOQ35JOmSiPIO8ilfWVFZmHCRF5dnkv7cRaCVUFvsr4YlUqkLB+3HL1RzxenvhhWHyPpxvTC3BeIUEQgRcqEgAn8YcEfBjzWmbvR2ZHZdOm7ONl00q7zbPltBKXKS9KbAe5Y0zGzwmo0GTnedJyGhgYMBgMymQyVSmVOxGFNeRioz/DwcHPGNiED3FDKjjWsHSNYra7eeDXf13+PRqcZddYAwVIoxkP0pUvXxV+P/pUXD7xIi6aFm9Ju4vVLX+eypMvMRb9FRjeBikBun3A7ry94nflx8znWeIxHdj7Ct8Xfim5xgyAqQCLnHa2aVr4t+ZY4/zguib9kWG2YLUCiC5zLOd50HI1BQ3ZUtsv7mhoxFSlScupcEw/ibCQSCUqZki5d14DHTAqfRGZYJjsrd1LRXmF3HyPpxhTmE8as6FmM9RvL6xe/Pmhq6qGsX/aQFZkF9Foa7cGW38ZgMmAwGcyJKiaGTjTLLpVImRA6gdraWrMClJKSMqjVZLA+BaWnvLwcPz8/ZDIZBoOB4uJiCgoKAM5p21JRGih19pO7n2R/zX46DB3U9NTQqG20GnfmyQi/uRA3JwL5Dfms+mEVW8u3khmWyepLVnNj2o1Oz7Ip4hmE+YSxYsoKfjf7d4QoQ/joxEc8s/cZ0Ro0AKICJHLe8dWZr+gx9LBs3LJhm/YDvQNRypTiwDECCMrItIhpLu8rQBFAekg6RxuOojVoXd6fM1ApVbT2tA56zPJxyzFiZO3JtTa3K7hQwbmLZmdi6arV0NBAeUs50YFDZ+JTSHt39Hv0jqd6jQ+IJ8Ingty63EFTVvfHFhcvCRIMJgOBikCg18o1O3o2wd7BzI6ezQtzX7BLyRysT8vz29razAqQ4FZnzVJl2fdA1qVjTcfMioMJEz3GHqs1jUYDgovlhYxg9XnpwEt06jq5K/Munpj5BBG+Ee4WbVh4UrbB0UBGaAarL1nNFclXcKrllGgNGoDROcKJjEpGolBrbWctW8q2MDZ4LNmRw7coSCVSEgMTKVYXYzKZRp07yGjBZDKRW5dLlG8Usf4jU5snKzKLwuZCjjUeY1qk65UuRwn2DqayvXLQY1KDU5kZPZMDNQcoai6yKZPTUEU4nYVlP536Tlq0LST7Jg95npDWuLWn1eHq5xKJhKzILL4r/Y6K9goSAhMcaq8/BqOBAEUA0LsL+87id/oe4INTfuPw8HCqqqowGHqVlaioKKqqqjAajUilUqsKVv9CpcIYbMnE0Insq9mH0WREggSll9Ll7qjORljcyaSjy3Ll7HkxvyGfv+T/hSZNE5lhmfxy0i9HreIjMFJj1fmEt8ybOybcwYyoGbyd/zYfnfiIAzUHWDF5BdH+0e4WzyMQLUAiI8ZIxBp8VvQZBpOBWzNudVhpSQlKoUvfRV1XnZOkE+lPSVsJzZpmsiKzRkzJFBRje92h3IVKqaJd147OMHgA/7L0ZUiRsvbk2j5WDluD6oc6frhY9qPx0yCRSpgQNWHI85ydiXG4bnBDIcQA2Ztp0hq2/PaxsbHmGKLw8HBkMhkmkwmZTGZeHFprx3KhDfT5/qGMh5jgNwF/mT8quYoU/xROHj85qnbchaKQo811z1nz4vlm9bHEk7INjjZEa9DAiAqQB3ChmHddPYjVd9Wzt3ovk8MnMz50vMPtpQT3Vl8vbi12uC0R6xQ09LpgjaQlJto/mlj/WHLr7XOHcheWlpDBiPGP4ZL4SyhsLuR062nz57YusIRxqKqqyqkbFZYuXS2SFnx8fJgcP3nI88x1XZyUiCQjNAOlTEl+Q75T2hOQIMFkMtHS1OLwGD5UymprSoyfn59NBVEtPxP+XVVVRUFBAd2N3axMWsmbGW+SEZKBP/4eU9/HVoT3Q4jfHC04Y14UMrxZxvosSlx03ngueFK2wdGIYA0SY4P6IipAHoAnFZNzJa4exLaWb8WEiSuSrxj0OFsVzuSgXjedkrYSp8ko0pdidTESJKQGp45ov5PCJtGiaaGms+8E4ImbEeE+ve9LbefQ48OPkn8EwKbSTebPBlpg9R93hP+D9fTMzqBEXYIUKUlBSUMeKyh+zZpmu/uxdh+9pF4kByVToi6xe/dzsOdCb9RjwgRGHB7Dh0pZbfl/QVFta2vrM64KGeckEgkGg8EceyUkYYiKijL3A/S558HBwTR0NBCiDBlWsVZ30qppBXDYXXKkcWReNJqMfHHqC1488OJ5Z/URcT79rUG/2fUblxWIHg2ICpAHIJp3HUdn0LGtfBuRvpFMCp806LG2Kpyx/rEoZUrRAuRCiluLifGPGfGsRIKF8ETTiT6fe+JmhD2KeHxgPBkhGeY6NHDuAktYzPe3HAjj0EDpmZ1BsbqY2IBYvGXeQx4b6RsJQFVHld39DHQfU4JS0Bg0NimTtrQH0NTdhAQJ3l7eDo/h/e9V/7lhsLnC0oJnMBgwmUwYDAazxUdQgISYoMzMzD7udJmZmVS1VqE1aAmUBJKZmUlnZ6fHvQ8DIbhKjjYL0HDR6DW8lvsan5/6nMTAxPPO6iPiGgRr0FOznkIuk7MmZw1fn/56VHhDOBtRAfIARPOu4xyoPUCbto3FiYuHzPxmq8IpJEIoaSu5IAcHV9Oh7aC+u968wB9JMkIzkCDheNPxPp+7ejNiOBampKAkJEg423rWpuOXJC5Bb9Szo2KH1e+FxXxnZ2efcad/nRln06Zto6G7gZSgFJuO95X7Eu0XbfcGRH9rhyVmt1Z1cZ/jh7ong6am7qpFJpURFh7m9DG8/9xg+X9L5QXOteAJ9YYsLT795e/ffrdvd2+MVvSEIa/b0xBc4EabBWg4NHQ18PTepzlQe4BZ0bN4bs5zotVHxC4mhE3gpbkvkRCQwLqidfzp8J/McXQXCmIWOJHzgk2lm5BL5cyPnz/ksf2zIg1GSlAKRS1F1HXVEeXn+YuA0USJuteiYeuC2JkEKAJICEzgeOPxPln+7Hk2hsNwshn5ePkQ4x/TZ9FuDSE+JDkimSBFEFvKtnBV6lVIJVKKi4tpaWlBpVKZlRzLRa1wrsFgMFsNnP07CIqMPfc7JSiFPdV76NJ12ZxkQLgOhUJxzjUIfRe3FjM3dq75+KHuyUDPhclkoqytDH+F/4gnS+kvk+V9tSbrYPdTuP9lmjJ8fHyYmjDVah+eTG1nLf5y//O+xk1hUyGv5L5Cu7adZenLuHbMtaLVR2RYRPhG8OxFz/L2kbfZW72Xms4aVmWvGrQ+2/mEaAESGfWUqkspainiopiLzKlonYW1HWMR5yD8pu5QgAAmhE5ArVUPy8VquAx3Rz05KJn6rno6tB0DHiMs5BvrG1mYsJD67nqO1B8BoKWlxfy34CLV3t5utnyMVPyPcC22Ynb/U9seh+fn59fnb0ui/KJ63Vot3mdHrBx1XXV06buI84+jvL3cnBHOHTjiSSDc/5P1J1FIFcT6x3pkPNxAGE1GytrK3GJNHkm2lG3h+f3PozPoWJW9iuvGXicqPyIO4ePlw4NZD3Jj2o2UqEv47a7fUtRc5G6xRgRRARIZ9Wwu2wzAkqQlTm97OAswEdsQEiDYEhDvCgaKA3IVjtT7GBs8FoDC5sIBj7Fc+C9MXIgUKZvKepMhqFS9cRFCYHz/4plDxf84YzFsTwIEASE5RlGL7RNyZ2dnn78tkUqk5yRCcERxKGzqvR/jQsahN+qpaK+wuw13YhkPJpPLqDfWkxSUhEwq88h4uIGo6qiix9Bz3ipAeqOe94+9z7sF7xKqDOX5i55netR0d4slcp4glUi5Ke0mHs56GI1Bw3P7nmN7+XZ3i+VyRAVIZFTTpetid9VuUoNSXZJJTEiEcKHsiIwkxWr3JEAQyAixHgfkKhxZUE6JmAIMXsPGcuEf5hNGVmQWR+qPcKLiBJ2dnSQkJBAfH49MJkMmk/WJERlKCXB0MWw0GTnVcoq4gDibEiAIjFWNxcfLx67aPUMlCvDp8qFD00Fdp+Muazl1OUiRclHsRQCUtI6ujRLLeDB5jBydRMeU8CnA6Ir/GY575WihTdvGSwdeYmPpRsaHjufFi190eiFfERGAmdEzeW7OcwQrg3nn6Dt8ePxDDEaDu8VyGaICJDKq2Vm1E41Bw+LExS5pXyqRMil8EqdbTtOubXdJHxcieqOehq4G4vzj3CaDv8KfhMAECpsKRyTJhSMLyii/KOL848iry+uTwtnSMtO//aVJS9HpdKwvXN8nxmXKlClMmTKFlJQUMjMzARxKAmALJeoSWnpazIqcrcilcqZGTOVM6xlzmuOhGEyZq62tJVQWik6vOycFur1oDVqONhwlPSSdCaG9SQNGm6us5X3NqetNhysUjB1NyXnOqnsThJxvClBtZy1P7H6C403HWZK4hCdmPkGgItDdYomcxyQFJfHS3JfICMlgQ8kG/u/Q/6HRa9wtlksQFSCRUc3m0s34y/2ZEzvHZX1kRWZhxMjh+sMu6+NCQ92jxoQJldK9KWvTVGmotWqaNE0u78vRBWV2VDZqrZozrWfMn/UP4Ldsf0LYBIIkQeS15aE36QdUXoQ2ysvLB1SCHJVdsOBMi7C/4K2wIM+rzxtW35ZERUWhUqqQe8ltKrA6mOtfQWMBWqOW7KhsQpQhRPhEcLTh6KjKGCnc17CwMHJqcwjzCSMxMNHdYtnN0YajqJSq8yoTWlVHFc/se4bGrkZ+kfkLfp75c7ykYt4qEdcT5B3EE7OeYEH8AvIb8vn9wd/Tre92t1hOR1SARPowmgJfqzuqqeyoZFb0LLvcauxlasRUJEjMO6QijmOu2u5mBUiIGRjpWk/Dec8ERcCycN1glhmpRMr8hPloTBp6gnoGVF4sz3VVvEdOXQ4B8gDSVGl2nzslfApSpByqPeSwHOHh4WRPyMZL7mWuGzMYg7n+CUpdVkQWEomErMgs6rvrR10cEEB5ezn13fVkRWaNuqD66o5qajpryI7MHnWyD0R5WznP7n0WtUbNfVPvc5mHg4jIQMilcu6edDdXJF9BYXMhL+7vLbZ7PiFuJ7gQR4Ke3cVw0vS6C0EhcXUwaJB3EGNVY8mvz0dn0CGXyV3a34WAsPvuqqKFJpOJTl0nLZoWWnpaaNG0oNaqMRgN6E16jEYjBpOBxu5GWjQtfHH6C6o6qpBJZQQqAlEpVai8VaiUKvzl/k5fWA3nPRsTPAaVUsXuqt0sG7cMqUQ6ZJriBWMW8G31txT3FDOPeVaPEc7vnxrbWTR2N1LWVsa8uHnIpDK7z/dX+DM5YjJH6o/Q2N3ocIrWYO9ggCEtQIPVE+ox9LC/Zj/xAfFE+0cDvQrqd6XfkVuXO+piNLaWbwVgTozrLOmuwqyI/neDYLRTrC7mxf0votFreCjrIWZEz3C3SCIXKBKJhNvH345CpuCbM9/w/P7neWLmE07PtusuRAXIhYwmZULAWo0QTyW3LhelTGn2v3cl2ZHZnGo5xfGm43bHMYici7D77kjRQoPRQGVHJcWtxZS3l9PU3URLTwutmlZaelpsSklsMplo7WnlQM0BytrKrB7jJfVC5a0i2DuYEGUIKqWKxMBEUoJSiAuIG5ZbynDeM6lEysKEhXxx6gvy6vLIjsoe8py4gDgifCLIrcvljgl3DKjIubLei7BRkR05tLwDsSRxCYfrD7O1bCu3jLvFIXkCFAHIJDKrFiDLTavB6gl9V/gdjW2NXB59ufmzjNAMfL18yanL4bqx1zkk40jSre/mh4ofSAhIIF2V7m5x7Ca3LhdvmTcTQye6WxSHOdt6lhcPvIjWoGXV9FVMjZjqbpFELnAkEgnL0pchl8r5/NTnPLfvOZ6c9SRB3kHuFs1hRAXIhYwmZULA0wvfCQsU/1B/TjWfYnrU9BGxyGRFZvHJyU/IrcsVFSAnIOy+h3iH2HS83qinsr2SEnUJxepiitXFlLWV9VFypEgJ9A4k2DuYuIA4gr2Dey05/7XmBHoHopAqkEllyCQypJJeD+DfH/w9TZomnpv9HHqTHnWPmtaeVrPlSLAiNXY3crb1LEb+l4RALpWTEJBASnAKyUHJpAal2qQUDfc9uzT+Ur469RUbSzfapAAJrlnflX5HRXuFWywTeXV5eEm9mBQ+adhtTImYQoRPBFvLt3J92vXIpcN/56USKcHewVaTKlhuWg00fptMJr4v/h45cpKMSebPvaReTImYwt7qvdR31Y+aeJQ9VXvQGDQsSVoy6lzImrqbKGouIjsqe9Rb5kvVpbx04CV0Bh2PTX+MzPBMd4skIgL0ziM3pt2IXCrnk5Of8OKBF3l61tP4K/zdLZpDiAqQC/F0ZWI0IixQdhXvwoiRaZH2B1UPh1j/WKJ8ezMl/Wziz0bdQsHTEBSggSxAJpOJqo4qcutyyavP42zr2T7KToA8gIyQDFKCU0gJ6lU+QpWhw3Kxmhg2kc1lmwnwDhjSvcpoMtLU3URpWylnW8+aFTIhCxX0KkXJQclMi5hGdmQ2cQFxTnteQn1CmR41nQO1B6jpqDG7Xw2GO12zunRdHG88zoSwCQ6lO5dKpCxKXMQnJz/hYM1Bc9rp4aJSqmjWNJ/zuaXSI4zfQryW8NmZ1jM0GBqYGjyVpNikPufPj5/P3uq9bCnbwq0Ztzok40hgMpnYWLoRpUzJ3Ni57hbHbrZVbMOIkUviLnG3KA5R0V7BCwdeoMfQw6PTHxWVHxGP5Jox12A0GVlXtI6XDrzEk7OexFfu626xho2oAImMKoQFSnlXORIkI+YiIOykf1vyLaVtpedtwb2RorWnFS+pF/7y/+0gGYwGTracJLc2l9y6XGq7egPPlTJlH2UnJSiFMJ8wpykVlsVuB1OALN2jpkdNN8eemUwmmjRNFLf2WqZK1CWcbD7JqZZTfHzsY/wl/syInsElqZcwLmScw5mcliQtYU/FHj7c/yE/n/zzITdZ3OmadbTxKHqT3inxGQsSFvDV6a/48vSXzI6ZbbbgDQeVt4ri1mKMJmOfdqxtWvV3Zf6+9Hu85F78ZOZPCA/se2xmWCaRvpFsq9jGTWk3ebxV4mDtQcrby7ki+Qq31eMaLnqjnq1lWwnzCRuxjTBXUNNRw/P7nqdL18XK7JUOWUpFRFzNdWOvQ2vQ8tWZr/j9wd/z+MzHR93YISAqQCKjivDwcIJDglmzaQ1pqrQR9UPNjsrm25JvyanLERUgB+nWd+Pr5YvWqCW3rlfhOVJ/hA5dBwAhyhAWJy4mOzKbCaETXLqQFGqHFLcWD5pQY6CYPolEQphPGGE+YeaAZZ1RR2FTIf858h9OtJ9gY/lGdjbsxNfLlykRU8iOzGZa5LRhTRwTQicQJgvjQPMB5lbMHVIBsnTNatW0OhR3ZS9CxjpnKECBikCuSL6Cr858xQ8VP7AgYcGw2/KV+2LEiM6oGzKDpKVVqKK9gr1Ve8kMy7SaLloqkbI4cTEfF37Mvpp9zIuznnjCEzAYDaw7uQ6lTMm1Y651tzh2c6j2EC09LSxLX+aQMuxO6rvqeW7/c7Rr23kw68HzJpGDyPnNzek3ozVq+U/xf1h9aDW/mfEbFDKFu8WyG1EBEvFIBsugd7zpOBqDxqYYCGeSrkrHX+7PgZoD3Dj2RtENzgE6tB3UdtayYssKc2rN5KBkLou8jOzIbJICk0bs943zj0OChKrOqkGPsyemTy6VMyl8EtFToqmpqcEQaKBEV0JObQ57q/eyt3ovPl4+XBJ3CYsTFxMXYFtBWOG9uDrhav5y+i8c1BxkLkO7LmVFZrG3ei+59bksTFhoU1+OojPoOFx/mMTARIcztwlclXoVm8s28/mpz5kbO3fYirGwYDYYDTCE16SlVWj1odUYMbIsfdmAx8+Pn8+nRZ+ysXQjF8de7LHjxA+VP1DdWc0NY28YlQHNG0s34iXx4tKES90tyrDo0nXxfwf/jxZNC7+e+mtmRs90t0giIjYhkUj4ccaP0Rl1bCzdyF+P/pVfTfmVx451AyEqQCIeyWAZ9PpXLB8pZFIZc2Pn8n3p95xqOUV6yOjLmORODEYDefV5bCzdyP7a/RiMBtJUadyUdhMzomYQ6hPqFrnkMjnhPuHUdgxeA2c4MX2W50xlKtePvZ5mTTOHag+xpWwL35d+z/el3zMhdAJLkpaQHZk9qItcbW0tDV0NfFL1CVWaKj4v/ZwlaUuYGDZ4Biyhlk5u3cgpQPtq9tGh6+CGsTc4rU1fuS/Xjr2Wf5z4B9+Xfs9VqVcNqx2ZpFfrMZgMNp9T1FxETl0OM6NmMkY1ZsDjAhQBXBJ3CVvKt3C4/rBHumf1GHr4/NTnBCgCuDLlSneLYzcFDQUUNhcyP27+qFTejCYjbx55k8qOSm7LuG1Uxl+JXNhIJBLunHAnjd2N7KraRUJgAlenXu1usexidNqNRc57BivwmFeXR7RfNDF+MSMul1CQbmPpxhHve7TSqmnlq9Nf8ettv2ZNzhpONJ0gTBlGekg6ay5Zw+XJl7tN+RGI8ouiprMGo8k49MEOEqIMYZrfNH6i+gm/TPglY73Gcqz+GH/M/SO/3vprPj/1OU3dTdbljIri/er3OdZxDK1RS4eug4e2PTRkUVV/hT8ZoRkcbTiKzjB0enBnsLF0IwqpgkvinRugvjRxKRE+EXxx6gvqOuuG1YaQLMNgtE0BMplMrD25FilSm9JwXz+2N1Pd2pNrR+SZspd1J9fRrGnmhrE3jLogZqPJyCcnP8FL6sWNaTe6W5xh8VnRZ+TW5XJx7MVclTI8JV5ExN1IJVJ+PeXXxPnH8UnhJxyuP+xukexCVIBEPJLw8HAyMzPP2XFv1jTTpGlifOh4t5hb4wLiGB86ngM1B1D3qEe8/9FEQ1cDbx95m3u33sunRZ8CcHPazfx54Z8ZFzqOAHmAx5jMo/2j0Rq1VjODOZuGhgbKy8vR6XQE64K5Lvw6Hkp4yBzL8MWpL/j11l/zxuE3qO081yp1tvNsn0V1fXc9J1pPUFs7uAVrfOh4dEbdgPWOnElxazFnWs8wN3YufnI/cya1wZQ0W5HL5Nw9+W40Bg3vHH1nWAqGyWQCsPn521e9r9fiED+fWP/YIY8P9Qnl8uTLKW8vZ0/VHrvlcyUnm0/yXcl3pKnSWJq01N3i2M2BmgMUq4tZkriEcN/Rl2V1b9Vevj7zNalBqfxy0i89ZgwUERkOvnJfHpn+CH5yP17Pe52qjsFdyT0JUQESGVUUtxYD/wtcdwdLEpegN+nZVr7NbTJ4Mm3aNj48/iEP7niQHZU7SFOl8XDWw7x56ZvckHYDKqUKmURml/uRq4nx77Um1nTUuLwvS0VFpVKhUCgYEzeG68Zex5sL3+SR7EeYEDaB3VW7eXjHw7x/7H2zsl1bW0uyTzLS/w7dUokUXy9f/tP4H4LCBncFSg1OBXorzbuajWW9FtLFSb0WU0uXVmcwMWwiixMXc6LpBJvKNtl9vqA0Ca5wg6HuUfP+sfcJVASybNzAsT/9uSb1GvzkfnxW9NmIWd2GosfQw9tH3sZL6sWKySv6JA9wppLqKvRGPZ8WfTpqEzcUq4t5O/9tVN4qVmavHJWB4yIi/Ynyi+LBaQ/So+9h9aHVdGg73C2STYgKkMiooqStBMCtWdimR00n2DuYzWWbPdK9xV1067v54tQX3Lf1PjaUbCAhIIEnZz7JM3OeYWb0zD41ejxNAYr2662nU9PpegVIcO9MSEggJSWlj6VTKpGSHZXNk7Oe5OnZT5MclMzG0o3cv+1+Piv6jKCwIO5Nvpes8CyCvYOZHT2bR2Y8glau5fuG7wft15ztzsUKUIe2gz1VexgbPNbc52AurcPltozbCPcJZ23hWqo7qu06V2/SAwyZPcxkMvFewXu069r5eebP7Yo38Vf4c+2Ya6nvrufL01/aJZ+r+GfhP6ntquWW9FvMSr+As5VUV/D1ma+p6azhmjHXjLrYH3WPmtWHVmPCxMrslW53+xURcSaZ4ZncMfEOajpreC3vNZvdi92JqACJjCqKW4vxkniREDDyFe0FvKReLExYSJOmiby6PLfJYS+u2uHVGXV8X/o9D2x7gM9PfY5KqeLBaQ/y4twXByzoJ5VIPWqAHEkFaCD3zv5MCJ3ACxe9wMqs3sXSl6e/5Lmjz6EOVvOXpX9h17JdvLP4HeYGzSVGEsPGsxvJb8gfsL0g7yBClCEuV4B+qPwBnVHHkqQl5s9svWZ78PHyYcWUFWgNWlYfWk2Xrsvmc4Vnb6jCufuq93Gw9iCzo2czK3qW3TJekXwFyUHJ/OvMvzjbenboE1zIjoodbCzdSEZIBj9K+dE537tCSXUmpepSvj79NQkBCcNOfuEudAYda3LW0Kxp5peTfslY1Vh3iyQi4nSWJi5lYcJCChoL+LjwY3eLMySiAiQyqihWFxMfGO/2AoMLExYiRTos9xt34Yod3pzaHB7e/jAfHPsAqUTKXZl3seaSNUMWqvST+9Gp6/QYC1qYTxhyqdxuS8Jw6a+MDqScSiQSZkTPYPW81dwz6R7kMjkfnfiIB3c8yP6a/QDU1dXxo9AfITVKeSf/nUFj05KDkqlsr3SZS5bRZGRz2WYC5AHMjp7tkj4smRA6gZ+M/wnVndW8nve6zc9Tp64TL6kXCunALkj1XfX87djfCFQE8tOJPx2WfF5SL+6dfC9SiZS3jrzlNle4ouYi3i14lzCfMB7Kesjqu+kKJdVZ6I163sp/C5PJxIopK5BLPbvArCUmk4n3jr3HqZZTXJVylUO1oUaDm6LIhYtEIuGnE3/KuJBxbCjZwI6KHe4WaVBEBUhk1NCsaaa1p9Wt8T8CoT6hTI+aTn5DvtVAdU/EmTu8HdoO3jj8BqtzVtOua2dZ+jJev/R1JvtOpvB44ZATtEqpwoiRdm27w7I4A6lESphPGE0a69nXhou1BYuQBMFSGR1KOZVJZSxIWMDrC17nxxk/RqPX8MfcP/LH3D/iF+pHpF8kt4+7nWZNM6/kvILOaH2hnRKUgsFkcFkihGONx6jprGFBwoIR26S4PPly5sfP50jDEdaeXGvTOS09Lai8VQMGoHfru1l9aDWduk7umXyPQ+5WCYEJ3JB2A5UdlXx26rNhtzNcmrqbeCXnFWQSGY9kPzLqXMeg1/WtrK2M68Ze5xHjvz18X/o9Oyp2MCV8Crdm3OpQW6PBTVHkwkYulfNw1sOEKkN5t+BdipqL3C3SgIgKkMiowRMSIFgipMTeXLbZzZLYhrN2eA/VHuLhHQ+zu2o30yKm8er8V7lu7HV4y7zNE3RVVdWgO5UqbxUALZoWh2RxJsHewU6Xx9qCxfLfgjJqq3KqkCm4KvUqXp3/KjOiZrC/Zj8vHXuJjtAOrppwFVemXElRSxF/K/ibOdOZJa5OhLCpdBMSJCxKWOSS9q0hkUj4xcRfkKZKY/3Z9Wwo3jDkOa2aVoK9g61+ZzQZeevIW5S3l7Ns3DKn1Bu7OvVqUoNSWX92PQdrDjrcnq20adt4+eDLqLVq7p1yL0lBSSPWt7PIq8vjy1NfkhiQyHVjr3O3OHZRqi7lHyf+QbRfNPdPu3/ImLOh8HQ3RRER6HW3fnT6o0iR8sbhN+jWd7tbJKuICpCIxzCUed8TEiBYMjFsIrH+sWwp20Kbts3d4rgcweqzJmcNepOeX035FY9Of5QQZYj5GGGCBgbdqQxWBgO9O/GegkqpokPX4VQ3JWsLFsskCIIyGh4eTlRUVG+h037Pv7X3Isg7iIezHuaBqQ+g1Wp5ec/LvLjrRa5MuZIp4VPYXrHdaq0qVyZCqGivILculykRU4j0i3R6+4Mhl8lZlb2KWP9YPjzx4aCbEkaTEXWPus9za8kXp77gYO1BLoq5iGtSr3GKfF5SLx7KeohARSB/PvLnEUlF3qHt4IX9L1DRXsGPM348rBgmd1PZXsnrea/jJ/djZfbKUeX6Zum2d/+0+/GT+zncpie7KYqIWJIUlMRt42+jobuBfxb+093iWEVUgEQ8hqHM+56QAMESiUTCjWNvRGPQ8PXpr90tjkvpb/VZc8ka5sXNO8eFSJigY2NjB92p9EQLkFkmJypl1hYsAy1iBnr+B/pcIpEwJ3YOK2JXkO6bzoHaAzyy8xFmRc8i2i+aD49/SE5tTp9zXJkIYe3JtRgxclPaTU5v2xaCvIN4ctaTRPlG8V7Be3xfaj0rnrpHjRGjWQm3ZEfFDr48/SUpQSncM/kep9ZoCfcNZ2X2SvRGPWsOrXHppom6R81z+5+jrK2MW9JvGXVJA6BXgVt9aDVag5aHsh4acaXaUUaz256IiDNYkriE8aHj2Vy2mYKGAneLcw6iAuQGxEBG6wxl3i9RlxAXEOf2BAiWzIqZRVJgEpvKNtHY3ehucZxOj6GHPx/586BWH2sMtVOpUnqgAuRmmQZ6/od6L8bEjeEn8T/hjpQ76Ojo4M3cNwn3CcdP7scf8/7IkfojfY53RSKEouYicutymRk90+xm5w5ClCE8PftpYvxi+ODYB3xW9Nk5iRGE+ysovAJ7q/byl/y/EO4TzqrsVS6p0TIuZBw/y/wZ9d31vJrzKj2GHqf3UdtZy7P7nqWsrYxl6cu4fuz1Tu/D1egMOv6Y+0dqu2q5Y+IdTAyb6G6R7MIyY91oc9sTEXEWUomUeybdg1Km5C9H/+JxrnCiAuQGxEBG6wy2aO4x9NDS02JTFfaRRCqRcmvGreiNej4v+tzd4jiVxu5Gnt7zNDsrdzI1YuqAVp/hKPSusLY4ihAT0trT6pb+B3r++38u/N7FxcUUFPTuqk2aNIk0RRorYlcwVjmWo41HkcvkSJGyJmdNHyUozj8Og8ngNIXdZDKx9uRapEhZlm57oVBXEeoTyjNzniE5KJkvT3/Ja7mv9Zl4hWfOMgZoT9Ue3jj8BsHKYJ6a9ZRLa7QsTFjIFclXUNhcyJpDa5yqiBY0FPDE7icoby3n0oBLmRs812ltjxQ6o45Xc1/lWOMxLo69mKyILOo662joaqBV0+pR6fOtMZoz1omIOJtIv0iWZyz3SFc4L3cLcCEi+Pp7SiBjQ0ODWR5P8y0WZJMF9dbrGGhhMtxrcMa1TwqbxMTQieys3MmVqVcSHxA/rHY8iaLmIl7JeQW1Vs2NaTdyw9gbBgzgtVTobf0NA70DkSKlWdPsTLEdQrBqeZJSZg3h99Zqteb/CzFE1MLDyQ+zt20v606uMxecXX1oNQ9mPcj0qOl9rjPaP9pheQ7XH6awuZCFCQvPKa7pLoK8g3h2zrO8feRt9tXso3ZPLaumryLCN+J/FqD/Wvx2VOzgnfx3UClVPD3r6WG5Wtk7jvxk/E/Q6DVsq9jGK7mvsDJrpUOWbZPJxPel3/PR8Y9QeilZHrmcJEWSXe+ku2jWNFPcWkxJWwlnWs+wt2ovDd0N+Hr5srNyJ7uqdvU5XoKEQEUgwcpgwnzCSApMIiUoheSgZEKUIU51WxwOguvbDWNvEF3fRETodYU7UHOAzWWbmRk1c8D6gCONqAC5gfDwcI+alIazgB0pBNmqa3rrswyUuWm41+CMa5dIJCwft5wn9jzBupPreGT6I8Nqx1PYXr6d9wreQyaV8XDWw8yMnjno8ZYKva0LQalESox/DBVtFc4Wf9iYEzN4kFueNYTf28/Pj87OTvNGiuW4cm3EtcQHxPOnvD/Rre1GIpHwas6r3Db+NkKVvZsIzrhOo8nI2pNrkUvl3DD2BofbcybeMm8emPYAiWcSWVe0jt/u+i0/y/yZOQFBjH8M606u4+szXxPhE8GTs54cdpyJveOIVCLlrkl3YcTIjood/N+h/2Nl9kp8vHzs7rtN28YHxz5gb/Veov2ieXT6o8i75R61yWaJzqijsKmQnLoc8uryaOjutRwbTUYauhroMfQQ6x/LnOg5BCuDUcgUSCVSjCYjOqOO1p5WWjQttGpaOdJ+hNy6XHPbYT5hTIuYRlZkFhNCJ4y4u7To+iYici6CK9yjOx/lL0f/wupLVg9rrHM2ogIk4nEWKUsE2bx8vKDhXL/9/sfZew3OuvYxqjHMjJrJgdoDFDUXkR6S7lB77sBgNPCPE//gu9LvzHEQtqTNtVx4FxQU2LwQTA5KZlfVLtq17QQoApxxCQ4RqAgE8JjaRANh6wZKVmQWL8x9gdWHVlPZXonGoOGj4x8xIWwCJpPJKZau3VW7KW8v5+rUq13qNjZcJBIJ1429jviAeN45+g6v572O1qAlQBHA34/9ndz6XFKDUlmZvdIh+YczjkglUu6edDdyqZzNZZv53Z7fma1UtnKw5iDvFbyHWqtmeuR0VkxZ0ZttzB+P28wqbi1mY9lG9lfvR2PQABDhG8HChIWE+4SztXwrEiRcmnApv5z0S5tSRusMOiraKyhWF1OiLqGgsYBNZZvYVLYJpUzJrOhZLElaMiJxaTqjTnR9ExEZAMEV7oNjH/DPwn/yi8xfuFskUQES8TyLlCWCbOXF5eh1ehorG2lQNFiNkxjqGqxZJ5x57cvGLeNQ7SHWnlzL72b/zu2uGPbQoe3gtbzXKGgsICMkg4eyHrK7YGJDQwMGgwGZTGbTQjAlKIVdVbsoVhczOXzycEV3GkovJYDHBWo6QnxAPC/NfYnX8l4jvyEfnVHH4brDNGoaqWqvcqhtnVHH50Wf4yf3c1q6aFeRHZXNK6pXePfou3xa9CkmTDR1N7E0aSn3TL7H4YQHwx1HpBIpP5/4c0KVob1Wqt2/5eGshxkfOn7Q81o1rXx04iP2VO/BX+7PfVPv46KYizxuzDEYDeyp3sPG0o2caT0DQGpQKjOiZ5AVmUWcfxynWk6Z3W1vSr9pUHfb/shlclKCU0gJ7nU1M5lMVHVUkVuXy8Hag+yo3MGOyh2kBqWyJGkJc2Pn4iV1zbLnmzPfiK5vIiKD4GmucKICJDIqaO1pRafXoTQqqaqqGlbcjqtd/WL8Y5gfP59tFds40nCEqRFTnd6HK2jqbuL5/c9T01nDooRF3DnxzmHtXtbW1mIwGFAoFDb9vsKipbjVMxQguVSOl9QLjV7jblGcir/Cn8dnPM7HhR/zbfG3aA1aNHoNX5/5mmmR05geNX1Y7W4t20p9dz23jrsVf4W/k6V2PoGKQBKDEjFhwmQyoTfpqemsoai5yK0TsaWV6o3Db/DC/hf46cSfsihh0TkKTZeuq7fYa8kGegw9ZEdmc1fmXVZTersTk8nEgdoDrDu5jprOGhRSBZfGX8ripMVm5cBkMrGjYodd7rZDIZFIiAuIIy4gjmvGXEOpupTNZZvZVbWLt/Pf5uvTX3NL+i3MipnlcFFSS8rbykXXNxGRIejvCrfmkjXmjUd3ICpAIjbj6mQJlu0Dffpq0bQg95IT6hsKJoalyIyEq9+NaTeyq2oX/zjxDyaGTvSolN3WqO+q5/l9z1PfXc+dE+7k8uTLh92Wvb9vYmAiEiSUqEuG3aez8ZH5nFcWIAGZVMYdE+4g1j+W9wrew0viRbe+mzU5a5gbO5efTvipXUpMm7aNL05/gUqp4rLky1wouXNo0bTwbsG77KjYgZfUi7sy78JL6sWmsk28cOAFMsMyuSX9FsaqxrpNxuyobF6Y+wJ/OPQH3it4j8P1h7kr8y5UShUavYbNZZv55sw3dOg6iPOPY/m45WRFZnmc1aeouYgPj3/IWfVZlDIlN6bdyOVJl/d5vtQ9av5W8DcO1B6wy93WXpKCkrhr0l3cmnEr35d8z/qz63n98OusL17PnRPuZFzIOKf083HhxxhNRlZMFl3fREQGI9IvklvSb+HDEx/yXcl3bt0wEBUgEZtxtQWlf3pwy3+frT6L0ktJ1uSscxQlWxkJV79Qn1CuH3s9nxZ9ypenv2TZOPenBR6Imo4ant//PM2aZu6ZdA8LEhY41J69v6+Plw+x/rGepQB5+ZjjE0YjQ21SLEpchLfMm9/s+g0mk4k0VRq7q3ZT0FDAXZPustka9MGxD2jXtnPf1Pvwlnk7+zKchslkYlfVLj48/iEdug6i/KJ6XfbGXEOYTxhXJF/B56c+Z2flTgoaCxgTPIYliUuYHTPbJXWAhiI+IJ6X577MB8c/YHfVbvIb8kkNSqW8rZxuQzdhPmHcPv52Lo672KkWDGegNWhZd3IdG0o2IJPIuDzpcq4be10fV1qTycS+6n28f+x92nXtzI6ezc8yf2aOv3MVfnI/bki7gUWJi/jmzDdsKtvEM3uf4fLky1k2bplDz/DxxuPkN+QzN3au2aotIiIyMIuTFvNd6Xf86+y/WJS4yJH8J84AAJeOSURBVG0xwJ41gop4NEMVZHRm+5b/rq2tRa1V423snaSGKrLpbq5OvZrkoGT+deZfnG09625xrFLTUcOz+56lRdPCr6b8ymHlZ7gkByVT313vMYkHvL28R7ULnC01xi6Ou5hpEdPQGrTUdNRwc9rN6E161uSs4YX9L1DcWjxoHwdqDrC3ei/TI6dzUcxFzr4Ep1HYVMjTe5/mz0f+jEwiY1X2KqL9ognyDjJnwgv3DefeKfey+pLVXBp/KeVt5byV/xYrtqzgHyf+wamWU+cUUnU1JkxMDp9MhE8EZ1vP8l3JdzRqGrluzHW8Nv81Lom/xOOUn9Mtp3l056N8W/ItqcGp/N+8/+POiXf2UX5qO2t5JecVXj/8OhKJhIeyHuLBrAddrvxYEuQdxB0T7mD1vNWMVY1lQ8kGHv3hUYqai4bVnslk4pOTn+Al8eLmtJudLK17EAu1i7gauVTOsvRldOu7+ebMN26TQ7QAidiMqy0o/du3/HfnqU5Sg9xXYd4evKRe3Dv5Xh7f9ThvHXmL31/8e49yhavvque5/c+h7lFz/7T7mR0z222yjAkew66qXRQ1F5Edle02OQR8vHxo6Bq9E7+tbojpIemUtpXSqetkY+lGHsl+hG0V29hVuYvHdz/O7OjZ3JJ+yzl1gtq0bbxX8B7+cn9+kfkLj3O/gt54jLUn15JXn4cUKYsTF3NL+i3IpXJez3udCaETzpE7PiCeuyffzW0Zt7Gzciebyjbxn+L/8J/i/xCkCGJq5FSyI7MZHzq+N8uaEzGajFR1VHGkvjelc1FzEUaMeEm8uGHsDXRoOzjZcpJ/nfkXbdo2bhh7g0dl3BPS5kskEm7LuI0rU67so6C1aFr48vSXbCvfhsFkYFb0LH428Wd2J1lxJjH+MTw751m+Lf6WT4s+5dl9z/KziT9jUeIiu9o5WHuQM61nWJq0dNgp1N3FQNZiTy6LIXL+MDtmNv86+y82lm7k8uTLCfMJG3EZRAVIZMQYaMAdym1HFaoCb4gMHj0TTEJgAjek3eBxrnCN3Y08v+95WjQt3Df1PrcqP4A5+cHh+sMeoQApZcpRHQNk6yZFsHcwvnJf7pxwJ38//nf+dPhPPDP7Ga5KuYq1J9eyr2YfB2oOMC9uHkuTlppdez449gFt2jbun3q/RwXem0wmTrWc4vvS79lXvQ8TJubEzOHmtJvNStyh2kPojDqmREwZsB1/hT9XpFzB5cmXc6rlFHn1eeTU5rCjYgc7KnYAEOUbRUpwb+HNxMBEQpQhhChD8PXyHVQh1Bv1qHvUtGhaqO2qpbi12Jy+WXC79JZ5kxWZRVZkFtMip5mVhKLmItaeXMvW8q3srNzJ5cmXc0XyFeZiru5gqLT56h4135V8Z07YkKZK49Zxt5IRmuE2mS2RSqRclXoVU8Kn8IdDf+Ddgncpby/n9vG325QpzmA08GnRpyhlSo+pgWVPnO5Aio4nl8UQOX+QSqTcOu5WXj74Mp+f+pwVk1eMuAyiAiQyYgw04A6141RXX0d3dzfabu1IiuswV6dezcHag/zrzL+YHjV9RGpRDIa6R21OeLBi8gouinW/+1K0fzSx/rHk1uXyc9PP3e7ao/RS0mPocasMI4GwwJsUPolfT/21OfvYM3Oe4bEZj3Gy+STrTq4zpxEeEzyGxIBE9lTtYUbUDObEzHHzFfTSre9md9VuNpdupqy9t8Dp5PDJLEtfdk48Rk5dDtBbH2koJBIJ6SHppIeks3zccmo7a8mry+NM6xmK1cXsrd7L3uq9fc6RS+WovFX4yH3wknghkUgwmAzoDDratG20a9sxYepzjlKmJDkomeSgZDLDMpkYNtFq7FF6SDq/m/07jjQcYW3hWtafXc+3xd8yI2oGS5KWkBGSMaLWuB5DD6/mvMqRhiN90uabTCaKWorYVLqJAzUH0Jv0Hp2wASA+MJ6XLn6JP+b+kY2lG6nuqGZV9qohs1P9UPkDVR1V3DD2Brdasyyxx3ozkKLjyWUxRM4vJodPZnzoeHZW7OTKlCuJD4gf0f5FBUhkxBhowB1qx6m6thqT0URXZ9dIiOk0PMkVTmfQsSZnDbVdtfx84s+ZHz/fLXJYIysyi/Vn11OiLnG7kiiVSM1pkj1xseYsZFIZAHqTnotiL0Jr0PLO0Xd4JecVnpnzDONCxvHMnGcoVhezuXQzOyp2sLV8K1KJlItkF5FXn0dmWKZbEgV067s52nCU3LpcDtQcQGPQ4OPlw2VJl7EocZHVSdRoMpJXl0d8QLxdhUYFovyiuCLlCvP/u3RdlLaVUtFeQaumlZaeFlo0LbT0tKDRazCajBhMBrwkXsikMmL8Ywj2DkblrUKlVBHmE0ZyUDJRflE2K/0SiYSpEVOZHD6ZnNocNpVtYl/NPvbV7CPOP4758fPJjsw+x23R2Wj0Gv5w6A8cbzrO/Pj53JV5F43djeyu2s2Oih2Ut5cDMDF0IouTFjMjaobbNzaGIkARwG9n/pYPjn3AlvIt/P7g73lsxmMDVqvXGrR8fupzAhQBXJly5QhLOzD2WG9ERUfE3UgkEpaPW85Te57is6LPWJm9ckT7FxUgkRFjoAF3qIE4LCIMyWkJQQGesctmD57gCmcymXjv2HucajnFlSlXsiRpyYjLMBiCApRTm+MRChD0BqJLOI8VIEmvAmQwGgBYkLCA+q56vjrzFe/kv8N9U+9DIpGQEpTC3ZPvpl3bTktPC5G+kWbrh0KqIDM8k+zIbMaFjLNrMW8PQoxMYVMhuXW5HGs6ht6oB3qTaCxKWMRFsRcNuFiF3iD9Nm0bCxMWOkUmX7kv40PHD1mw1BVIJVJmRM9gRvQMqjuq2VzWq6B+XPgxHxd+TKx/bK8LXcQ0xgSPceqmi9agZU3OGo41HiMrMosgRRCP7XyMyo5KALMiujhxMXEBcU7r11YcKdXgJfXiF5m/QCFTsKFkA3849Ad+M+M3VjPEbSzdSLOmmdvH346v3NetclsyHKXG1eUtREQGI02VxoyoGRysPcipllOkqdJGrG9RARIZMRq7G3ly95McazrGxNCJvDD3hXMC36wNxiGhIfj4+BAYMHLZgpyJs1zhhjtRfV/6PTsqdjAlfAq3Zdw2rL5twZb7a400VRoB8gBy63O5ZdwtLpPPFgSlx2gyevyutSMI12aZ4eym9Jsoby9nT/UeEgMTuWbMNUBv1rdDdYdYlLCIldkrqe2sJbc+l5zaHA7XHSa3Lhfo686VEpRChG8EKqUKlbfKpkW41qClRdNCa08rdV11FLcWc1Z9lrK2MrNbopfEi/Gh48mKyiIrIotwX9veA0FGW9zfRhMx/jHcMeEOlo9bzrHGY+TW5ZJbl8v6s+tZf3Y9MomMuIA4UoJSSAlKMccsBXsH23RPdEYd6h41Td1NlLeV84/Cf3C29Sy+Xr7k1eeRV59HkCKIS+MvJSsyi8zwTLemRXc0gF8ikXD7+Nsxmox8X/o9b+S9wcPZD/cZCzp1nXxz5hvCfMJYkuiczSR3Jh4Qkx6IuJtb0m8hpzaHTwo/4Xezfzdi3heiAnQBMpI7PpZ9PXXkKfbX7MdgMrC/Zj9P7n6Sdxa/0+cYa4OxsNsrHaVZ2y1d4V7Pe52X5r5kV9FJgeFMVAUNBXx0/COi/aK5f9r9Ll3UP7n7Sav3dyikEinTIqfxQ+UPNHQ12LyodQXCwGsymYY4cnQjWID0Jr35M6lEyq+n/pqn9jzF2pNriQ+IJ8Y/hr8e/WufrG/R/tFc6X8lV6ZcSYe2g6MNRzmrPsvZ1rOUqEsobC48pz9/uT/B3sH4ePkgk8qQIMGECYPRQJe+i9aeVjp1neec5yf3I02VRnJQMmOCx5AZljmsHffculyCFEFutzC6CoVMwbTIaUyLnMbPTT+nRF3C4frDFKt7Ey1sr9jO9ortfc4JkAcQrAzGX+6PTCJDIpFgMpkwmAx06jpp6Wnpk56+RdPr5hfqE8qC+AWkBKcwJXwKqcGpHrNZMJgLmK3znkQi4Y4Jd9CubWdP9R6+OPUFN6f/L8X1v8/+mw5dB7ePv91p1rWRSDzQ//qF//v5+ZllGE3WoNEkq8jgxAXEMS9+HjsqdpDfkD9oohpnIipAFyAjueNj2VdBQwEGU6/LjcFk4HjT8XOOsTYRWLoljVYSAhO4fcLtvH/sfV7Le43HZzxujsOwFXsnydrOWl7Lew2ll5JHpz/q9PS9/TnWdMzq/RUYbMLKiszih8ofyKnL4fLky10q52CYFZ/z1/sN+N+7JChCAj5ePjw6/VF+u+u3vJr7KoGKQDp1nTw24zGrWd/8Ff7MiZ3DnNjepAhGk5G6zjpK1CU0aZrMcTGCZadJ04TBaDBb2GRSGUqZksTARFTeql7rhDK4N0YmMJkI3wiHdwNrOmqo7Kjk0vhLPWah7kqkEimpwal9lL1WTSvF6mIqOyrN90L4u6qjyhyzJJPIkEqk+Hj5EOcfh0qpItg7mBZNC1vKt3BR7EX8/uLfD5kgwF0M5gJmz7wnlUi5Z/I91HTW8OXpL4kLiGNOzBy69d18V/Idsf6xXBx38YjI7Sz61wgrLy83f5eZmQlAQUHBqLEGiZar84ub0m5id+VuvjnzjagAibiOkUxzadlX8plkCnQFGDEik8iYEDrhnGOsTQTCokVYXI9WliQuobytnC3lW/i48GPumHCHXefbM0l26br4w6E/0KXr4rEZjxHjHzMcke1iYuhE9lXvw4gRKVLz/RUYbMKaHD4ZHy8ftpdv57Kky9yWgEBQDEartdFWhNif1pZWCioK+iilEb4RPJD1APdtvQ+tQcuDWQ8yNWKqTW1qjVoCFAFMCJtgXlRLkPQurKVSvCReSCVSvGXeI3aPt1VsA2BWzKwR6c8TCVYGM03ZayGyl/queh754RGi/aJ5ctaTHqv8wOCbLPbOewqZglXZq3h81+O8k/8OKUEpHGs8hsag4YrkK0adMm15/ZaFkgULUP9jPJ3RJKvI0IT5hDEzeiZ7qvdQ0V4xIhnhRAXoAmOkzcaWi/anpj/F84eep6S7hMzwTF6Y+8I5x1iTt7iqGL1OP+IV2Z2NRCLhzol3UtVRxYaSDSQEJLAgYYHT+zGZTLx55E2qOqr4ccaPR2w35YW5L/Do9kcpbC4kIyTDfH8F+k9Y/Z/FS+Iu4fvS7znVcor0kPQRkbk/wjM22hY39iJsJjQ3NhMiDTlHKT3eeBwfLx8kEgmFTYXUd9Wba9i09LTQqmmluafZnAGtVdNKm7bNZiutl8SLIO8gc4yQSqkyWxuEGBWVUkWgItAhRUln0LG9YjtRvlFkhmUOu50LFaPJyF/y/4LGoGFl9kq3FCu0BWEsMRgMGAwGq5ssw7GyhPqE8sC0B3hu/3O8k/8OndpOlDKlR5QQsJf+1y9YgDo7Owc8xpMZTbKK2MbSpKXsqd7D5rLN/Gziz1zen6gAXWC402wc4h3CY2Me6+ODXHCmYFBlrLa2FoPOgE6vG/UWIOitFfJQ1kM8sfsJ3it4jxj/GKcv9reVbyO3Lpe5sXNHNEVrmE8Y71/xPtC7IKk5U4MpymS+t/0nrP7P4uLExXxf+j0bSze6TQESXODO5xTY8D9FLyoiCtowK6V6o55/n/k3H534CH+FP74mX74t+ZZ9Nfus1jqRS+WolCqi/KIYFzquN8bnv25UQi0c6FW4jCYjBqMBg8lAh66DFk0LTZomiluLMWJ9c8Nf7m9OqpAS3BvIH+4TbvP92Vezj3ZtO9eOufa8V2pdwZayLRxrOsaihEVMCp/k9Pbt3ZAb6HhhLJHJZCgUCqdaBSaETWBp0lL+deZf9Bh6uDn95kEzDnoi/X834bcTLSginkSaKo3EgER+qPiB5eOWu/w9ExWgCwx3mo37L3htUcaioqLQV+uRe8nNbjujnSDvIFZlr+LpvU/zSs4rvHTxS07bWW3sbuSjEx8Rogzh5xN/7raFvK331vJZ9NZ4Ey2JZnfFbu6YcIdbigv2GHpQSEe+ts1IozfqMZlM9Hj30BbUxoHaA5QUlVDUXER5ezlSiRRfL1+CvIPo1HViMpn4UfKPSApKQuWtIljZa6nx9fJ1+Bkzmoy09bT9z6JkETdU1lbGyeaTFDQWmI/vrxSlBqUOmDhjU+km5FI58+PmOyTjhUhTdxP/LPwnYT5hLsseae+G3EDH93ejHgp7Fa/l45bz5akvadY0kx2ZPeTxnoa13020oIh4GhKJhCVJS3i34F12V+1mceJil/YnKkAXGO4c9PoveG1RxsLDwwkLC0NZqaRLP7oKoQ5GUlASv5ryK17NfZXVh1bz3EXPOZw+1mQy8U7+O2gMGh7Kesgp9SmGi7V7a20Xsv8ubpZ/FsX1xWwr38Z1Y68bcbk1Bo1Hxzg4ilBEdGv5Vsrayvj9wd+bk3H4ePnQpm0jRBnCLyf9knlx8whVhnKi+QTP7XuOYnUxPx7/Y6dbUqQSKcHK4N4kC1Z0XoPRQFVHlTmjWYm6V1GzVIpi/GLIjsomKzKLNFUaUomUYnUxp1tPMz9+/rCyLl7ofHn6SzQGDfdPvN9lY4ktc4DluDHQ8fbOa/YqXlqDFrlUjlKmZE/1HpdYw1yJGC8jMlq4KPYiPi78mE2lm1iUsMilm7iiAiQyYvSfpGydtCQSCcHK3kxE5xMzo2dyY9qNfHHqC97Jf4f7p97v0Mu+rXwbBY0FLIhf4LK4H1t3Tq3d26EWHVFRUWSaMtnesZ3NZZu5Zsw1I+621K3vPu8UoMbuRnN9mONNx9Eb9dR11iGXyrlu7HWMDR5LfEA8fz7yZ7r13fxqyq+YFzfPfP6E0F4XoI2lG9lYunHEs/TJpDISAhNICExgfvx8oK9SdKrlFHn1eebaNwHyAKZGTqW2sxajyei0Wi0XEtUd1Wwv305GSAbTIuxPnGArtswBluNGZmamUyw89ioE28q3oZApmBQ+iZ0VO7ky5coRCdJ2FqK1R2S04OPlY44HLmopYlzIOJf1JSpAIqOCYO9gmjRN7hbD6dww9gYq2irYW72XCN8Ilo9bPqx2LF3fbh9/u5Ol/B+OxJANtegQJunLlZfzxakvyK3LZXrUdGeIbTMavWbU+fdbo1RdysHag+TW5VLaVgr0Jh6YEDaBaZHT2Fy6mS59F7dl3IbRZOTNw29yuvU0V6Vc1Uf5Ebh13K0crj/MJ4WfMDViKlF+7t1J7q8UGU1GStQl5NblklOXw/by7ZS3l+Mt8+bTok/JisxiRtQMVEqVW+UeLawrWocRI8vHLXd7PFxUVBRVVVUYDAYaGhrM485gSs5Q45Q1haChoYGqqioAAgMD6ezsJCoqCqPJyNfHvsZf4c99U+/j6b1P8+nJT1k1fZWLrlhE5MJGiAfeVLpJVIBEREKUIb3B0v+tH3K+IJVIWTFlBU37m/jmzDd4y7y5fuz1drUxkq5vtu6cWluc2LoLuTBhId+c+YavTn9FVmTWiN5vjV4zahfJWoOWvdV72VS6ibPqs0BvvMzFsReTHZXNpLBJ5mfjX2f+hcpb1Zvl6+hf2FO9h+mR07k141arbSu9lNwz+R6e29ebDevp2U971HtoWfvm5vSbea/gPb489SWpwakcbzxOfkM+Hx7/kOyobJYmLmV86Hi3L+w9ldrOWg7UHGBaxDS3JSOxZKB4UUEpEpQWy/HGz88PrVbbJ8WzNfoX4TYYeuNMW1pazG0WthfSom1hYfBC0kPSmRE1g4O1B6nuqB6R8gIiIhcacQFxTAidwIGaA6h71C6LBxYVIJFRQbB3MEZ6g6WtFWQczfh4+fD4jMd5fv/zfFr0KXKpnKtSr7L5/JFwfROwVYlxxFIUogxhSeISNpRsYH/NfubEzBmuuHbTre9GKbPuAueplcdrO2vZXLaZHRU76NB14C3zZmHCQubGziVdlX5OwV2jyYi6R01SYBLvH3ufHRU7mBoxlQemPTCoUuNuVzhbadG0sLNyJ+NDx7P6ktVo9BqONBxhe/l2DtQc4EDNAWL9Y1mSuIR5cfPcGivniWwp2wLAj1J+NKL9Wr5f0FehGWrjpf94I6R2tkzxbK2f/kW4rVmAPmv6DKlUyhVjrwDg8uTLOVh7kM1lm+2u5TaYLJ40poiIuJslSUs43nTcpfHAogIkMioQduWbe5rPOwUIwF/hzxOznuC5fc/xceHHyGVyLku6bMjz2rXtfFz4sctd3wRsnbAdDbq9bux1bCvfxqcnP2VG1Ay8pK4fqowmI1qjdsAYIE+qPG40Gcmry2NT2SbyG/KB3kQAN6bdyLy4efjJB975bte2ozfqKW0rJa8+j8ywTFZmrUQukw/Z7/Jxyzlcf5i1J9cyO3q2R76LX53+ih5DD8vGLevNZif3ZU7MHObEzKGmo6ZXWazcwQfHP2DtybXMjZ3L4sTFJAUluVt0t6M1aNlesZ0Yv5hzChm7Gsv3C+jzrlnbeImNje0zxli6yfW3AA2m9Fi2IZPJ+oxttZ21nOo4xSXJl5AWlwZARkgGcf5x7KjYwS3ptzgUM+hJY4qIiCeRHZmNylvl0nhgz/FhEBEZBJV3rwLUqml1ryAuJFARyFOzniLGL4YPjn3A9yXfD3nON2e+oUvfxU8yfjIiO9n9FykDER4efk7AckNDAwUFBTQ0NFj9vyWBikCuTr2a2q5atldsd+5FDIBGrwEYcEETFRXl9Boj9tKl6+KbM99w37b7WJ2zmoKGAmZGzeSpWU/x6vxXuTz58kGVH4Dm7maaNc2cbT1LRkgGq7JX2aT8QK+18vbxt9Nj6OHL018645KcSm1nLVvLt5KmSrOarjjaP5rbJ9zO24ve5p5J9xDjH8OW8i08tusxntrzFHur9476gsuOsL9mPx26DpYkLRlxF0HL92uwd00YNwDzGBMeHo5MJjMXQe1vAeqv9AhtW45TwjFVVVXmcelg7UGAPgWrhVS9Xfou9lXvc9o1i4iI/A8vqRfz4ubRpGniTOsZl/QhKkAiowLBAnS+ZYLrT5B3EE/N/q8SdPwD/n323wMe29jdyMbSjSQFJjErZtaIyOfIhN1feRpKmboi5QqCFEF8ceoLegw9DsltCx26DgD8vKwrENaUupFCZ9CxoXgD92+/n7Un12IwGrgx7UbeXPgmD2c/zMSwiTYtWI0mIx8Xfoy6R01iYCKPzXjM7h3s7Mhs0lRpbC3fSm3n4IrwSPNZ0WcYTIYhg/e9Zd4sSFjAyxe/zEtzX2J+3HxK1CW8nvc6j+96nCP1R8xFcV3JYJsA7uBQ7SGkSLko9qIR79vy/er/rln+TgONG5Zjk2D5Ef4eSOmxdj78z/qUU5uDUqZkYujEPsdeFHMRUqQcqjvktGsWERHpS3ZU7yZWbl2uS9oXFSA78bQJ60Ih2DsYgJae81sBgt4YmGfmPEN8QDwfF37MV6e/snrc56c+R2fUsXzccqQSqd3P5nCeZcsJWzi/uLjYpnb6K09DKVM+Xj5cn3Y9rT2tbCjeYLOMw0VQrj0pCYLRZGRn5U4e2vEQH574ELlUzi8n/ZI3F77JTWk3EeoTaldbfzn6F/bV7MPHy4efT/z5sDLeSSQSlo9bjsFk4LOiz+w+31WUqEvYU72HqRFTGR863ubzUoNTWTFlBW8teosrU66kqqOKlw++zHP7n+N0y2kXSmy7RXUk0Bl05Dfkkx6STqAi0K2y9B+bBrLgWGI5NllagOxJ3Z+ZmUlgYO+1t2paOdFwgsnhk8+xkPor/MkIzeBow9ER2ZwREbkQGRM8hiBFEDm1OS5pX1SA7MSTJqwLiQvFAiQQ5B3E07OfJikwiU+LPmXtybV9dqQr2ivYWbGTCaETmBw+Gfjfs1leXm6TUuPosyyc39LSMiy3OFt2PxcmLCTCJ4L1Z9ej7lEPS05bEZTrEGWIS/uxBZPJRG5dLo/ufNRcn+e2jNt4fcHrLExYaHdMlN6o589H/syOih3E+MUQ6RdJhF/EsOUbHzqeKeFT2FO9hxJ1ybDbcRYmk4l/Fv4TCRKWpS8bVhuBikB+Mv4nvLbgNebHz+dk00me3PMkr+a8SlVHlZMl7sWTXKCONx2nx9BDVmSWu0U5Z2wSfic/Pz+bYxAFl7iqqiq7xjlBeSrqKEKr0w74e2RFZqEz6ihoKLD6vUgv4qaxyHCRSqRMi5xGZUelS7wNRAXITjxpwrqQCFAEIJfKaei+cAZRISZoTPAYvjnzDX86/CfzbuNnRZ+dU6fD8pm0ZbJ35FluaGjAYDAgk8lQqVQueyfkUjnLM5bTpe/i78f/7vT2LRGUa3cH9hc1F/G7vb/jD4f+QF1nHdekXsOfLv0TV6dejUKmsLu9Nm0bL+x/gd1Vu8mOzDanFg9V2m49ssbyccuRIGHdyXUOteMMdlXtoqCxgHlx8xxOZhDmE8aKySv4wyV/YHrkdA7UHmDVjlW8k/8OTd3OrUXmSS5QQjKNaZGuK3xqK/3HJuF36uzstHmzRVCAAKvj00ALc0F5Ot19GoVcMWBmTUExOtJwZBhXeOEgbhqLOIIQy+kKNzgxC5ydiBWV3YNUIiUhIIFSdSkmk+mCqeHhr/DnyVlP8taRt9hbvZeazhquH3M9B2sPMjNqJmNVY83HmjMX2Zh9bTjPsuBOYjAYMBgMKBQKUlJS7LsoO5kdPZs9kXvYW72XWdGzmBk90yX9CApQiLd7LEAtmhbeP/Y+B2sPIkXKooRF3JB2g0MWqbK2MtYcWkN9dz2XJV3GT8b/hKf2PEWwd7DDtRWSgpK4KPYidlft5njTcSKMEW5J6dusaebvx/9OkCLIqZkQ4wPiWTV9FadaTvFJ4Sdsr9jO7qrdXDPmGq4dcy1yqW2JI9yFvSmWz7SewV/uT4yf62rb2OOOZu17e7JLCpngAgMDrY5R1mISBbl0Rh1nz5wlVZU64HsS6RtJgCKA4tbiIWW5kHE0I6jIhU1meCZyqZy8ujynp+YXLUAio4bkoGTUWjXNmmZ3izKi+Hj58FDWQ9yYdiMl6hIe3/04Wr2WW8bdcs6x9u4o2+ueICwawPquqiuQSCTclXkX/nJ//lbwN9q0bS7pp7WnFRh5C5DJZGJn5U5W/rDSrNi+Ov9V7pp0l0PKz8Gagzy952maNc38ctIv+enEn2IymShvKyclyDlK681pN+Ml8eKTwk+oqakZ8Z1ek8nEu0ffpVPXyV2T7sJf4e/0PtJUafxu9u94fMbjRPlF8cWpL3hi9xOUqkud3pczsWfn3WgyUtZWRnJQsks3l4ZrDbCW+W0oBFe2tra2AS09whjWX659xfvoMfSQIEsYsH2JREJqUCrl7eXojXq7rudCQpiTANEVTsRuvGXeZIZlcqLpBB3aDqe2LSpAIqMGYdFWrL7wdtykEik3pd3EjWNvpF3bTruunVPNpxxu194FibBoiI2NHVHXnWBlMHdOuBO1Vs0Hxz5wSR/NmmaUMuWwEgMMlxZNC6tzVvPnI3/GS+LFquxVPJz9MNH+0cNu02Qy8eWpL3kl9xUUMgVPzX6KhQkLgd7YMb1JT3JQssOyNzQ0UF9cz6zQWZxpPUOrT6s5TmOkFjq7qnaRV5/H3Ni5TI+a7rJ+JBIJUyKm8PLFL3PD2BuobK/kt7t/a05E4onY4+Ja1VFFj6HH4ediqA2V4bjdNjQ0UF5ebrfiZC2rmyWWm0WWcjU0NHC87TgSiYR5KfMG7SM5KBmdUUdle6XNcl2oiK5wIsMlOzIbI0azm66zEBUgkVFDcnDv5OwJQdfuorarlhj/GBICE3jn6Dt8ePxDDEZDn2MsFyHOXpC4M2ZhbuxcsiOz2Vu9lwM1B5zefmtP64hlgLO0+uTW5XJRzEW8Mv8VhxfxGr2GP+b9kc9OfUZiYCIvX/wy40LGmb8XNg+cYQESFjSZ8t7d3Rx1jl1xGo5i6fp254Q7XdqXgFwq5+b0m3lx7ovE+cd5tDXInndVWMAnBiY61OdQi1xbZLKWAU5goLpAR44c4ciRI33Guf5Z3YSU2EPJVVlZycmOk4QqQslMyBz0eoXfq7JDVICGQoyfFhkuQlxiTp1zs8GJCpDIqCE+IB4vqRdnW8+6WxS3oO5Rs79mP1PCp/DH+X8kIySDDSUbePngy31Mw5aLkMEWJPbGCLgbV7rCmUwmmrqbRkQBsmb1uX/a/QQoAhxqt6Grgaf3Ps2BmgPMip7Fc3OeI8wnrM8xwuaBMyxAwoJmQsIEpoRP4VDtIZq6m8wLTS8vL5dZgvq7vjn629lLclAyL1784qixBg2FOf5tmC6XgtLi5+dnTj5gS8FjawyUAS4hIcHqOGUZk2hZxFSgra13nGhtbbVJjuqeatr17aT7pQ8pq/B7XSjZSR3BkxJ+iIwuVEoVY4LHcKT+iFPHWFEBEhk1yKVyEgMSKVGXjEiRQk9je8V29EY9S5KWEOQdxBOznmBRwiIKGgt4YvcT5mBcWyuqj0aXBEtXuLePvI3RZHRKu23aNrr0XS4NAAdcYvWB3gxev939W8rayrgl/RYenPag1QKnxepigr2DnZLq23JBszhxMUaMbC3fao696OrqQqvVWl2UOsrG0o0j4vo2GKPJGjQUQgp4lbdtGwAD1enp7Ow0K0C2FjzuT393NMsA+sGytslkMoABnzmTyWSTHHXyOiRSCRcnXzykrBdaeQYREXeRHZlNl76Lk00nndamqACJjCou1EQIRpORLWVbCPYONi/45FI5v8j8BTcm3EhZUxmP7niUT09+SnBI8IAV1S0ZrS4Jc2PnckncJeTV5zktBXNNZw0AUX6u+S10Rh3vFbzndKtPl66Lv+T/hZcOvITWoGVV9iquH3u91UB2nUFnToDg7ED3aZHTCPMJY2v5VsIjwlEoFOb06GA9BmO4FDQU8OHxD4n2i+anE37qlDYdob816Mk9T7K7are7xbILe1PAD2Slsbbp0v//Q1mELMesgazZlm2Eh4czZcoUpkyZQmxs7DnPnPCZSqU6xzpljQptBSp/FbNSZg35OwgKo5BARURExDVMCp8EQGFzodPaFNNgi4wqLBMhhPo4VsdkNHG4/jAN3Q3cMPaGPul3JRIJ4yTjuCv2LtY3ruerM19xqO4Q906+l5TgweM8RmtKd8EVrrqjmn+d/RcJgQnMjZ3rUJs1Hb0KULTf8JMPDIS6R80fc/9IYXMhmWGZ3D/tfgIVgQ63m9+Qz1/y/0KTponMsEzunnQ34b4D309nJkDoj1TSm7Z7XdE6SgwlzM6cbf6u/y6+I9R21vJa3mt4e3nzyPRHXJL1bTgI1qCsyCzW5KzhjcNvUNZWxvJxy5FKPH+fsVPXiZfUy+YEIPakNu4/zgyUftry38LxUVFRFFUU8eeKP3Oy5SRJyiTuirsLQ5XBbGWybFvoS3jm/Pz8qKrqLWIbEBBgjk+rqqqy6v5rNBkpUZeQHJSMTCob8trkMjneMm86dM7NTiUiItKXhIAEvKReTo0B9/yRWUTEggs1EcKm0k1IkZqzeVkSFRVFQkACqyasYo7fHMpaynhi9xN8evJTdIbRGZMwFHKZnJXZKwlRhvBO/jsOx4VVd1YDEOPvXBe4srbee1HYXMjlSZfz+IzHHVZ+LK0+nbpO7sq8iydmPjGo8gPOTYBgjUsTLgUDrDu8zmowOjiWBrdL18XqQ6vp1HXy4LQHifWPdYrcziQ1OJWX5r7E2OCxrD+7ntWHVtOl63K3WENiNBnxkti+H9rfsmxL3KFlnJC19NNVVVVm5UQ4vra2lvdr3yevMY92fTvHO47zbuW7wOBp+C0LpwrxQYLCM5hVsq6zjm59t12bBDKJzGmuuCIiItaRy+QkBCRQrC52WgiEqACJjCqERAinW067W5QRo7azlvyGfKZHTbdq9RIme02XhrmBc7kr9i7iA+P56sxXPL778fO2UJ9KqWJV9ioAVuesdsgPv6ajBinSIZUIezhQc4Cn9zxNi6aFuyfdzZ0T77RpV3kw8hvyWfXDKrZVbCMzLJM1l6xhUeIim1zaTrf2vjOuSnUc5B1EmjKN4o5i8svOTVfqSMyZ0WTkzSNvUtlRya0ZtzIlYspwxXc5KqWK383+ndlN88k9T5otjJ6KCccWFLbEHVrGCVlLP90f4fjC5kIMpt5Ml0aMlHSX2JyGX4gPkkgkGAy9bWRmZprd4vr3K2wS2POOSCSSCzImVURkpEkJSqG1p9VpIRCiAjTC2JsRR6QvcqmcjJAMCpsL6dZ3u1ucEWFHxQ5MmFicuHjQ44TFxNSkqbw09yVuTLuR6o7q89oalBqcyj2T76FF08KanDXDvsaazhoifCP6uBcOF6PJyOenPufV3Ffxlnnz9Oyne60jDjBcq4+lTPn1+cT6xzrsOjqYIvOjMT9CIpVQ0F1wzjjnSMzZZ0WfkVuXy8WxF3NVylUOyT8SyGVyVkxewe3jb6emo4Yn9jzB0Yaj7hZrQGQSmVnJsJf+2SQtrUOW8521+295rKCUxMbG0tDQgMFgQCaTkRGSgUwiM8s5OXLyOYrPYPOqTCZDKpX2ScwwEMOxkhqMBoc3NkRERIZG2JhwlgeQqACNMKMx85ankR2Vjc6oo6ChwN2ijAg5dTkEKYKYEDZh0OMsFxNeUi9uSruJl+a+ZLYG/WbXbzhSf+S8262cGzuXhZELKagp4A97/3BOXaShMJqM1HbVOlR8VKBb381rua/xxakvSApM4qWLXyI9ZOh0uoPJtrtq97CtPgIl6hJaelrIiswatiwCgykyM1NmEqeKI78pn56enj7j3HDT4G4t28rXZ75mTPAY7p50t9MTOLgKiUTCj1J+xGMzHsNkMvHygZf5tvhbl75/w91gU3op0Rl1w9pAGGxOs/xuqPvfP/mBoAD9YcEfmBU9i2DvYKaFTePHoT8+5/oGkkH4HPq6zA10fLG6GKVMabMrrN6op8fQg1J2bsZFERER52IZA+4MRAVohBmtmbc8iayI3kVcbl2umyVxPXWddVS0VzA1cuqwgqmTgpLM1qC6rjpePvgyz+1/7rxzIZzpPZPxvuM5UHuAt/Lfsssnv76rHr1R73AChDZtG8/ue5YDtQeYHT2b5y46tw6PrZhMJo7UH+E3u37DG4ffoFvfbbfVx5Kc2t4CctmR2cOSx5LBFrISiYTsqGzaTe200urwOPdDxQ+8W/AuEb4RrMxeiVzmuIVupJkSMYWX5r5ElF8UH534iPePve+ymJHhbrAJ2cyEdNj2MNicJrigDZV5rX/xZkH58fPzo+ZMDc9PeZ5dy3bxUPxD+Jh8zrm+gfoRZOvvMmdNZssECLaOteoeNSZMI1ZAWUTkQkYIgXCWBUjMAjfCjNbMW55EuG84iQGJ5NbnYjQZR0WWpeEiKHmOLFwFa9CC+AV8fupzdlbs5Mk9TzIjagbLxi3zyGBye4mJjmEZy1jfup7dVbuRS+X8ctIvbXo2hBgpR2Jj1D1qnt//PBXtFdww9gZuSrtp2JaKUy2n+KTwEwqbC5FL5VydejXXpF7jUMaz3LpcAhQBjFWNHdb59hTNnRYxjY2lG+kI7nBorNtbtZd38t8h1CeUp2c97ZTaRe4i2j+aF+e+yCs5r7CpbBNao5a7J93t9LHLnuxslljWs4nwjbDr3IHmNOGZAaxmbLM8xjJRgXC8QqEwZ20Tzh3o+vqnzBb6GUg2a58PJwGCkP5aVIDcy2gr6i0yPCwTITgDUQESGZVkRWbx1ZmvON1y2iEXI08nty4XuVROZlimw22F+YSxYvIKrky5ks+KPuNg7UFyanO4JP4Sbkq7aVSnFRcWNBMME1iTs4btFduRIOGuSXcNucgsaevdTRquAtTU3cQL+1+gurOa2zJu4+rUq4fVTmV7JetOruNQ3SGkSFkQv8Ap96Whq4Gy9jLmx80f9oLb2uJyICaETkApU5Jbl8u1Y64dVn97qvbw5uE3CVIG8dSsp5yanMJd+Mp9eWzGY7yS8wo7KnagN+q5d/K9To0fGe4GW7B3MODcejbCMyOTyVAoFPj5+VFQUNBnkdr/mP7psAGqqqrMlh3L84A+1xoVFWX1WFsRdpXtGQeEYGxbC8iKuAZ7xieR0U1KUArF6mKaNc0Ob4qdv1vnIuc12VG9FpGcuhw3S+I6OnWd5toxSi/n+ZjHB8SzMnslz1/0POkh6Wyv2M4D2x/g4xMf06Ed3fUshPTYk8Mns61iG28deWvImKDi1l6//+FYwhq7G3lm3zNUd1bz0wk/HZby09jdyNv5b/PID49wqO4QM6Nmsmb+Gu6ZfI9TlNLc+l4roiPxP/a47splciZHTOZ0y2nUPWq7+9pRsYM3Dr9BsDKY3836ncuK07oDhUzBquxVZEdms7tqN68ffh29Ue9uscwxLxXtFU5rs7/7maU1Z6Bj+idRCA8PN7u2VVVVUVBQYE6X3d8NzvLY4cTYnlX3ptK3JwFCZXtl73WcR8+oPYxEUidb+hBDCy4chA0KZ2S3FRUgkVFJclAyKm/VeR0HlF+fj8FkcErgujXSVGn8bvbveHzG48T4x/Dv4n9z37b7+GfhP6nvqndJnyOBQqbgkexHyIrMYlfVLl4//PqAwd0mk4lidTGJgYl2W0eaupt4bt9zNHQ1cFfmXVyWfJld55e3lfNewXs8uP1BdlTsICM0gxcvepGHsx92qltiTm0OXlIvMsOHb0W0N4FBdmQ2Jkzk1eXZ1c/G0o28nf824T7hPDP7GackpvA05DI5D2U9xOzo2RyoOcCbh9+0O3GHs0kISEAmkTnNtQTOrf8k1P8RLEGClWao56p/7R4YuAaQtYWwrYt0exMgQK/VSIqUpKAkm885nxiJpE629DHQcyRm3T3/cGYiBNEFTmRUIpVIyYrMYkv5Fmo6as7LhZJg3ZoWOc1lfUgkEqZETGFS+CT2Ve/j81Ofs/7sev599t9MjZjKkqQlTA6fPOrirOQyOQ9nPcwbh99gf81+WjQtrMxaSbAyuM9xdV11dOm77C4Oqu5R88L+F6jrquOuzLtYlLjIpvN0Rh0Haw6yqWwTJ5tPApAalMrN6TczOXyy0zOcdem6KGwqZGLYRHy8fJza9mBMjZiKFCm5dbksSFgw5PEGo4F/nPgH35V+R5RvFE/NfmrYCSRGA15SL5bFL6Ouvo5d5bvwknpx75R73faeyWVy4gLiXFJg2jITW2ZmJgUFBUO6K1lLrW1LnIelC6C1+KLBFK3ytnISAhPsugfF6mJi/GPwlnnbfM75xHBjzkaqj4Fc48SYodFLfGA8MomMsrYyh9sSFSA7EF8az0JQgHLqcrjK3/Nrg9iD3qjncP1hUoNSRyT4WyqRclHsRcyOmc2xxmNsKt1Ebl0uefV5RPhEsDhpMfPj5xOoCHS5LM7CS+rFA9MeIOJkBOvPrufx3Y/zSPYjpAT/T9kRzOiWnw1Fm7aN5/c/T3VnNXdOuNMm5aexu5EtZVvYVr4NtVaNXCpnXtw8liQuYUzwGJeldj7ScAS9SW92GR0pAhQBpIekc7TxKFqDFoVMMeCxHdoOXst7jYLGAjJCMngo6yGCvINGUFr30FDXwLXh1/Jlw5fsqtqFQqbgrsy73JbmOyUohe0V22nqbnJaPKBlRjdhASssaK3FBAnYk9DAWp+Wik//+CJraA1aOnQdhPvYPq+re9Q0dDcwL26ezefYw2hYb3h6UqeB4sLEmKHRi1wqJ9g72KHC5wKja1vXzYg1fDyLiWET8ZZ5s79mv7tFcTrl7eV06buYFD5pRPuVSqRMCp/EqumreGPhG1w/5np6DD38s/Cf3LvlXt48/CanWk55bC2h/i4PUomU2zJu41dTfkW7tp2n9z7N3qq95uPtTYCg0Wt4+cDLVLRXcFvGbVyefPmAxxpNRo7UH+EPh/7AfVvv4+szX+Mt8+a2jNt4a9Fb/GrKrxirGuvSBe+BmgNAb2Y2W3Cmy8jk8Mn0GHoGtSpUtFfw292/paCxgEUJi3hi1hMXhPIDvYszX29fHpz2IJPCJrG1fCv/LPyn2+QREq0cqT/itDYt6/lYKjKZmZm0tbWh1Wqpqqo65zxb02cP1Kelq1z/FNjWEBZT9mRzE9w7nZGgxhrieqMXR36HgeLCxJih0Y1KqRpWyv7+iBYgG7G2kyXiXhQyBbNjZrOjYoe5fsP5Qklr76IxNTjVbTKE+YRxy7hbuD7teg7VHGJT2SZ2Ve1iV9UuEgMTmR09m6zILOID4j2mOOVAO3vz4uYR4xfDmpw1vH74dcrby7k5/WbOtJ7BW+ZtU8yNyWTi7fy3KVYXc/2Y660mPDCajJxpPUNeXR77qvdR21WLBAlTI6ayNGkpk8InjZibU4umhUO1h8gMy7R5R9+ZO6OCVa1YXWw1U2NuXS5/yvsTOqOOn0/8OUuSljjU32jDcvd8VdgqXtz/Iv8u/jdxAXHMj58/4vJMDp+MTCIjpy6HhYkLndLmcN2XBkprbW+ftp4nLKbsUYBy63KRImVKxBSbz7GHkXAvGw3Y+jsMZDGzdr6nW66Gw2iwGDoLlbeK4tZih8ugiAqQjQg7WQqFYtCaBxfCw+dJLElcwo6KHWwu28wvJ/3S3eI4DSHAz97YFFcgl8qZEzuHObFzqGirMCtC64rWsa5oHRE+EWRFZpEVmcW40HHIpe4rVjnQZNnQ0EB3bTcrM1byQckHfH3ma0rVpZxsOsmEsAk2DaJfn/ma/TX7mRk9k5vSbzJ/3mPooaChgJy6HPLq8lBrezOfBSmCuCb1GhYlLrK7tooz2Fa+DYPJwJJE2xULZy66BgpWNZqM/OvMv/i06FP85f48Ov1RJoRNcLi/0Yy3zJuV2St5fNfjvFvwLtF+0SOe3t9f4U9GSAYFjQX0GHqcEtcy2EIzNjZ20GfNFlc5e/scCLMFyMZ01jqDjqONR0kPSXeZW/D5uEgfDrb+DgMpzBfK73ghufUFK4MxYqRd2+6Qx4CoANnIUAuDC+nh8yRSg1NJDUpld9Vubsu4DT+5n7tFcgol6hKCFEEeV/wxPjCen2f+nNsn3M7JppPk1uWSW5fLd6Xf8V3pd/h4+TA1YipZkVlMCZ/iUPHO4TDQZGd2i2mBZ+Y8w1+P/pXNZZup66pjRvSMIds9VHuIT4s+JTEwkXsn34u6R01eXR45dTkUNBagM/ZmmYvzj2NBwgKyIrMYEzzGbUHtBqOBLeVbCFGG2JVF0JmLhQBFAOE+4X3SldZ01PB2/tsUtRQRHxDPo9MfdYty6IkEeQfx6PRHeXrv07ya+yovzX1pxGtzZUdlc6zpGIdqDzE3dq5L+xrqWRO+Ky8vB3Dp3GpvQdNDdYfoMfS4LEOniP1c6BazC+n6hY2KFk2LqACNBEMN1hfSw+dpLElawtv5b7OzcuegMRmjBZ1RR1l7GRNDJ3qMa1l/5FI5meGZZIZncseEO6horyC3Lpecuhz2Vu9lb/VepEhJD0knPSSdlKAUUoJSCPMJc8s1Wb6fCpmCX035Fa09rfz77L/ZUbEDhUzBHePvsKqwlapLeTX3VYwmI8mByTy37zlzzRApUsaHjmda5DSyIrM8ph5Ibl0uzZpmbk672amFNu0lJSiFQ7WH6NJ1sb1iO2tPrkVn1HF50uUsG7fMqfWtzgeSgpJYMXkFr+W9xpqcNTw759lBE0g4m7mxc/ln4T/ZVLrJ5QrQYDQ0NJiD1wVcObfaW9B0U+kmvKReLkuAIGI/F4qlZyAupOsXNipaelpIImnY7YgKkJO4kB4+T2NOzBz+ceIfbCrdxGVJl3ms0mArle2V6I16t8b/2INEIiEhMIGEwASuG3sdrZpWcut7LUMFDQUUNheajw2QB5AclExqcCrJQckjphT1fz8lEgkGk4GU4BQywzLZWbmTgoYCfpH5C2L9YylWF1OsLqawqZCdlTvpMfYQ7RfNjsod+Hr5MidmDtmR2UwOnzziVi5b2FS2CZlExqUJl7pVjuSgZHZX7ebxXY9T21VLpG8k90y+h/Gh490ql4Anui7PjplNRXsFX57+knfy3+G+qfeN2JgWoAhgTswcfqj8gVJ1qdvq2wgu5wIqlcql90ewAPVPk2+NivYKCpsLmRs794JJ2DHasHyvAY94xz1xrBmtmBUgBzPBiQqQyKhHIVMwP34+/yn+D8ebjjMxbKK7RXIIwWVotCZ1CFYGszBhIQsTFqI36qnqqKK4tfh/SkVzIUcbj5qPD1AEkBKUQoxfDCqlqvePt4pgZTAqbxV+cj+nLQCNpl6/4YbuBo7UHyHaL5pxqnF0ajvZW7OXX239Fb5yX0KVoUglUhq6GpBIJCxNXMplyZeREpRClF+UR9dFqu6opqCxgNnRs+0K6nY2RpOR2s5aKjsq6dJ3sSx9mcdZfTzVdfnGtBspbytnT/UeEgITuHbMtSPW95LEJfxQ+QMbSzdy9+S7R6xfSyzTFwN0dna6tL8WTQtKmdKmWlkbSzcC2BVbdz4xGhby/TPHecI77qljzWjE0gXOEUQFSOS8YHHiYv5T/B82lW4a/QqQgwkQPGmC8pJ6kRiYSGJgIgvoLYhpTSk60XSC/IZ8q23IpfI+ClGwMhi5VI5UIkUmkSGTyvCSeGHChMFkwGA0YDAZMJqM9Bh6aNW00tLTQoumhdaeVgwmA936bmo6aujSddGkaQIg2i+aLn0XHdoOACJ9IwG4LPkyfjbxZyPwazmHzWWbAdyWVc1kMnGk4QhrC9dS0laCl8SLWdGzuHPinW6RZzA81XVZKpHyq6m/4uk9T7Pu5DriA+JHLN4kNTiVNFUaP1T+wDVjrnGLW6dl8VNrdVycTYumxabNgvqueraXb2dM8BjSVGkukcXTGQ0L+f7vtSe845461oxGLF3gHEFUgETOC6L8opgcPplDtYecWsjPHTiaAMHTJyhrSpHBaKC1p5UWTUsfZcXy/3WddZxuOY0J+2oQSZGalaekoKTeFJrqYvQGPb+c/EsmhU9C5a0iyDsICRJ2Ve3i7SNvs79mPyqlisSARIfTbY4UGr2GHRU7iPOPIyMkY8T7L2ouYu3JtRQ2FyKXyrl2zLXsrtpNh65jxGWxBU92Xfbx8uGR6Y/w212/5Y3Db7DmkjWE+YS5vF+JRMLycct5dt+zfFr0KQ9Me8DlfQr037xxJB22PbT2tBIfED/kcZ8VfYbepGf5uOWj3tV6uAy2kPeUzbf+77UnvOOePNaMNgIUAUiR0qppdagdUQESOW9YkriE/IZ8tpZv5eb0m90tzrBp6G4gxj9m2BOs5QTlKRPSUMikMkJ9QodUXA1GA+3adnRGndnKYzAa0Jv0SJAgk8p6rUL/tQzJpfLewbKf8vL0nqcJ9w3nypQr8ZL2HQYnh09G6aUk1j+WIO8g/lrwVzaUbGD5uOVkRWZ59MJnT9WeXnezpGUjKmdFewXrTq4jpy4HKVIWJizkhrE3EOoTSom6hNMtp0dMlvOJCN8Ifj3117x88GX+evSvPD7j8RG5r+NDxzM1Yip7q/dyVepVI5aO35qy4+qdc51BR4euY8gNp7K2MnZX7WZS2KRR72XgCIMt5Pvfv9Ey/4iMLqSS3k1N0QIkIvJfpkVOI8Ingu9Lv+eK5CvsDk73hMFab9TTpm1jvPfwg8QtJ6iCggKPtgbZi0wqsylQGXrvZ3lt+Tn3U92j5nTLaaZHTT9H+TGZTLxX8B5d+i4en/k4mWGZrD+7ng0lG1ids5o0VRo3pd3ExLCJHmcR0hl1fHP2G3y9fLk49uIR6bO6o5p/nfkXOyt3YsTIzKiZ3DLulj6FZVVKFRqDhm59t00xFiJ9mRIxhfnx89lRsYPtFdtHLLHF8nHLya/P528Ff+O5Oc+NSDZBdxSt7NT1xhcNNl8YTUbeK3gPgOUZy10my2jHmuvZ+TT/iHgOAfIAs7v6cPGsGVxExAGkEik3pd9Ep66T9WfX231+/8BJdyBkI3JW/Z+oqCgUCsUF6Xc80P08Un8EI0arMRV7q/dysPYgc2LmMDN6Jr5yX5aNW8brC15nceJizrae5cUDL/Lwjof5tvhbhwdgZ7K1fCv1XfVcnXo1vnJfl/VjMBo4UHOAF/a/wEM7HmJH5Q7Gh47npbkv8XD2w32UH/hfwKqj7goXMrePv50QZQgfnfiIxu7GEekzMTCRH6X8iDOtZ/hP8X9GpM/w8HAyMzOHXCw3NDRQUFBAQ0ODw30aTL2JFmSSgRW8b4u/5VTLKS5PvtwjilN7Arbcgwt5/hFxLTKpDKPJ6FAbogI0DJw5+Io4l7mxc0kISOC7ku/MtR1sxRMGa2GRGOwd7JT2bF1QnG80NDRgMBiQyWTn3M/culwkSJgSMaXP5+oeNe8fe59ARSB3Trizz3cqpYpfZP6C1xa8xtWpV9Oh7eCjEx+xYssK3s5/u0+xT3fQre/mq1NfEewd7LJaWM2aZr449QW/3vZrXs19lRNNJ5gTM4ffzf4dT856csC07c4KWL2Q8ZP7cfeku+nWd/PXo3/FZLIvDm643Jx+MzF+MXx26jMq2itGpE9bcOZmlaAA9bcGC1R3VPNp0adE+UaxbNwyh/s7X7B2D/p/dqHOPyKuRyqRojfpHWvDSbJcUHiCpUDEOlKJlGXjlqE1avny1Jd2nesJg7WwSHRn+uLzAaGOiEwm63M/dQYd+Q35pKnS+tTwEFzfOnQd/CLzFwPW94jwjeC2jNt4e9Hb/GrKr0gITGBHxQ4e3/04T+x+gq3lW1H3qF19eeewoXgDaq2aG9NudGqaaa1BS15dHn/M/SO/3vprPj/1OQC3pN/CWwvf4oFpDzA+dPygcSmCMi9agBxDcIXLb8hne8X2EelTIVOwYsoKjEYjr+e9Tre+e0T6FRhos7H/ZpUjm5IGY68CZM2lVaPX8Frea+iNelZMWYG3zHsYV3F+Ym3D0BmbiOIGs4gtyCSOW4DEGKBhIKYz9GymRUxjXMg4tpVv48qUK4n2j3a3SDYj5LW3tSK5iHUGekePNx1HY9CQHZXd5/P+rm9DIZfJmRc3j3lx8yhuLWZT2Sb2VO3hr0f/yru8y1jVWLIis8iKzCLOP86lgett2jbWn11PlG8UC+IXONyeukdNXl0eOXU5FDQW0GPoASAzLJOlSUuZFjHNrngQwZ2zucc+i6zIudw+/naONhzloxMfMSl80ohkhUtTpXHLuFtYe3Itfz78Zx7Oftgl8W/9YzAbGhooLy8H6BND0r/IZUFBAQaDAYPBMKxYk4Fc4EwmE2/lv0VZWxk3p93MuJBxjl7ieYW12Cx74rUGirl1VtxQY3cjT+5+kmNNx5gYOpEX5r4wIu+LI3hCHPJoQSaVmd/d4SIqQMNATGfo2UgkEm4ddytP732adUXreCjrIXeLZDP2VCQXsR1hYvmh8wegV0kWsHR9++nEn9rddkpwCvcE38OPM35MXn2v4pBfn8+pllOsPbmWSN9IszI0VjXW6bvIX5/+Go1Bwy3ptwzoxjMYRpORyvZK8urzyK3LNacalyIlPSSdrMgspkdNH3Y9GNECZDtDLYAEV7iRzgp3Teo15sKsX5z6wiVZNvsvfC09LCw3MqwVuZTJZMO2PAi7yP2Vuq9Of8WBmgPMip7F9WOvH84leRyesMAWZBhIaXXWBvOTu59kf81+DCYD+2v28+j2R3ko/iGPVi7EpBG2I1qAREQGQFi47a/Zz9nWswPGJ3gaQtySaAFyDGuLqZ6eHg7VHCIqKKpPoP4Hxz6gQ9fBw1kPE6gIPKctWxcN/gp/s1VIZ9BxovkEuXW55NTmsKFkAxtKNiBFSlxAHClBKSQHJ5McmExSUNKwlaLG7kY2lW0iOSiZWTGzhjzeaDJS1VFFibqEEnUJZ1vPUtZWhsagAUApUzIzeiZZkVlMjZhKgCJgWHJZIrhz2huTdyFiywLIMivczsqdXBJ/icvlkkgk3DP5Hmo6a/jy9JeolCoWJy52ah/9F75+fn5otVpUKtU5C2ShOGpgYKD5s+EuGK3VFdtavpXPTn1GYmAiKyav8OjU9/Zg6wJ7OIqSrecIMgyktDprg/lY0zGzhcBgMlDYXIg20rOVC9G7yD5EBUhEZACWpy8nry6PtSfX8uSsJx1qa6R2ztQ9auRSOX5yP5f1cSEgTCR+fn4UFBTg5+dHRXcF3XT3qeVzsPgg24q3kR2ZPaDr23B25eQyOZPDJzM5fDI/nfBTytrKOFx/mGJ1McXqYnZU7mBH5Q4As1KUGJiISqkiRBlCsHew+W+VUoVCprDaz+dFn6M39hZmlEqk5oKyQhHZ1p5WcyHZyvbKPsoOgK+XL2OCx5ASnMLEsImMDxmPXCa345ceGoVMgZ/cz2zdFBkYWxdAt4+/ndzaXNYVrWNOzByn3zNrKGQKHp3+KM/sfYb3Ct5DKumt9+Qs+i98Ozs7+/xteZzwTnZ2dpKZmWmOGxnO+CxYfoTF1I6KHbx79F0ifCN4bPpjTo2pczdDPV9DWWcGY6Bxsv/caSmDK+fSiaETzRYgmURGRkiG25McDYXoXWQ7BpMBL4ljKsyoUYBefPFFvv32W44cOYJCoaC1tfWcY8rLy1mxYgXbt2/H39+fO+64g5dffhkvr1FzmSJOJD4wnnlx8/ih8gcKGgrIDM8cdlsjZZruMfTgLfN2+Y6jJ7hCuBJhIhHqIAHU+tbiJfdibuxcoNfH/5PCT8AEs7xnDbiAcnRXTiKRkBSURFJQkvkzdY+6jxWmWF3MrqpdA7bhJ/fDX+6Pl9QLqUSKTCKjW99NXl0eAYoAPjj2AV26Ltq0bVZ3tKGvspMcmExKcAqRvpEjsrvtLfNGa9C6vJ/RSnFxMS0tLahUKjIzhx6n/OR+XDv2Wv5x4h98X/o9V6VeNQJS9lrznpr9FM/ufZa/Hv0rOoOOy5Ivc0lfg713zqw3IyyiDCYDm8s2817Be4T7hPP0rKeHLMw82hhqgT2UdWYwBrpf/e/NSC3yX5j7Ak/ufpKChgKSfZJ5bNJjpMelu7xfkZHBaDI6XJts1GgGWq2Wm266idmzZ/O3v/3tnO8NBgM/+tGPiIqKYu/evdTU1HD77bcjl8t56aWX3CCxiCdwU9pN7K3ey/vH3uf/5v3fgDvpQzFSpmmjyThoPQpncaH4Ggv3LTwinH35+4jxiyE5KBmAo41HqdBWkBWcRYR3xDm/h6WSaMui1B6CvIOYEjGlTyrubn03rZr/WWxaelpo1bTSrGmmtaeVDl0HRpMRg9GA1qTlZPNJjBhJCExAIVMQoAhgXMg4gpXBqLxVqJQqswVJpVQRIA9wmyuPl8TLYXeF84n+GxAtLb3JT4S/bWFp4lI2FG/gmzPfsDBhoUtrP1kS5hPGM3Oe4fn9z/PB8Q+o7qzm9vG3Dyv+TMDahow9C2VHxmeZVIbJZGJf9T7quuqI9I3k6dlPe3zAvCtwxDoz0P2ydFlsaGgYsfkmzCeMdxa/Y94E07ZoIe5/33vCJqAnyDBaMRgNDidjGTUK0LPPPgvA3//+d6vfb9q0iRMnTrBlyxYiIyOZMmUKzz//PI899hjPPPMMCsXwFr72ID7Mnke4bzg3pd3EJyc/4bOiz/jx+B8Pr50R2rXSG/Vuq7h+PiLct8P1h2nTtnF58uVIJBIauhp4aPtDNGuaCfYNxifMB22L1mqwtZCJytX338fLBx9/H5uyFm4o3kBdVx3Lxi3jZxN/5lK5nIEzajacT/TfgFCpVGYLkK3IZXJuTr+Zt/PfZv3Z9SNaoybUJ5TnLnqO13JfY2PpRirbK3ko66Fhx4zZuyFj7d0c7vvZre+mtrOWZk0zc2Pn8lDWQwOmwXclnrB+cMU8Z+my6I4NNyGWzM+vr1u5J2wCeoIMoxVnuMCdN3WA9u3bR2ZmJpGRkebPli5dSltbG8ePHx/wvJ6eHtra2vr8GS5ifSDP5MqUKxkTPIb/FP+HouYid4szKCNlAfKEmkcjya7KXveyi2IuAuCejffQ0N2AwWQgty6XPxb+8Zzfw3LC9KR3urazlrUn1xLhE8HyccvdLY5NOKNq9/lE/3opKSkpZGVlkZKSYlc78+LmEecfx4aSDbRoWmyqoeKsOiuBikB+O/O3LE1ayvGm4zy28zGONhwdVlvW6scMJmf/jYrhcqzxGC8deIlufTdjgsfwxKwn3KL8wPm9fnBnkfGBYsk8ofC5J8gwWjGYHLcAnTcKUG1tbR/lBzD/f7AB5eWXXyYoKMj8Jz4+ftgyiA+zZyKTylgxeQVeUi/eyX/Ho2MRjBiRcH5kHPIUuvXdHKo9RLoqnUi/SPRGPWfbz5q/N5gMHG86d5PEcsL0lHfaaDL2PsNGLfdMuQcfLx93i2QTEiSiAmSBszYghMLPPYYevjr91aCLaEGhqKqqGnKhbauS5CX14mcTf8Y9k+6hU9fJiwde5N2j79Kl67LrOiyD44U+h1IIZDIZMplsWO9mt76b9wre4/n9z9Op6yTMJ4yJYRORS12fTGIgzuf1gzs33Ab6XT1hE7C/DGIRWNvp1HXiI3ds/nOrAvSb3/wGiUQy6J+TJ0+6VIbHH38ctVpt/lNRUTHstjzhhRKxTlxAHDel3UR1ZzWfFX3mbnEGRCZxvLiXPVwIA+6h2kNojVoujrsYgG3l2/CWepsVTSlSJoROOOc8YeJMSEg455121++2sXQjhc2FLE1aalVmV+LINQuZmEScT3ZkNmmqNLaWb0UaJB1wES0oFMCQC217rRELEhaw+pLVTAydyJbyLaz8YSU/VPxgl9Lbv8/BFAIhU5lMJrNrvjWajOys3MmqH1axuWwzGSEZvHLJK0T6Rbo9S6G4fnA+nuBWaA/nsxXQmZhMJlo1rQ6XC3FrDNDKlSu58847Bz3GVpeAqKgoDh482Oezuro683cD4e3tjbe3cwsTingmV6ZcycHag/yn+D9Mj5pOeojnZYSRSqQjulN+Ifgg76rchUwiY1b0LDR6DV+e/pKM0AwMWgNn2s+QFpjGC3NfOOe8wfzh3fG71XbW8knhJ25zfXPkmg0mA0rp+ZNO2JMQCj8/s+8ZtjZu5f5p91s9zpYAd2HB6OfnZ06FbGvgeoRvBE/MeoKt5VtZd3Idb+W/xfqz61k+bnmf1PMD0T91/WDJR+yNYTSZTByuP8zak2spby/HT+7HTyf+lCWJS5BKpIQoQ8Q6Vechnjq/DaSYXSixuY7SrmtHb9ITogxxqB23KkDODLibPXs2L774IvX19URERACwefNmAgMDGT9+vFP6EBndCK5wv9n1G97Jf8ehrHCuYqQtQP0H3NG2YzYU9V31FDQWkB2ZTYAigG+Lv6W1p5WfTfwZMe0xaLVaFArFgBmfPGWi8gTXN0eueaRi20Yrjr53GaEZTI2Yyp7qPdyQdkOfQr8C/edba31aWolkMpndi0epRMrixMXMiZnD+rPr2VC8gdU5q4kPiGdJ4hIujrt4wGe3f+p6a/3am5mxW9/N7qrdbC7dTFl7GXKpnKtTr+aa1GvwV/ibjwv2Dqa0rdSma+wvx/kwTp4vDFZzyJMYSDET6wDZRqumFYBgZbBD7YyaGKDy8nKOHDlCeXk5BoOBI0eOcOTIETo6OgBYsmQJ48eP5yc/+Qn5+fls3LiRJ598kl/96leihUfEjKe7wnlJvdAbRy5bVn+3i/PJBN/Q0MBH+z5Cp9OxOGkxRpORTaWbCFAEcGn8pTb53HvK7+FO1zcBR1x0nJGy9HzGGc/ZNanXALC5dPOgxw0WC2T5TjgSk+In92P5uOX86dI/cUXyFTR2N/K3Y3/jns338O7RdzlSfwSdQWf13KFc34b6nXQGHfkN+bx79F3u2XwP7xW8R11XHZclXcbrC17ntozb6FZ393HnDPYOplPXaXN8qKeMCyJ96X9fhhqz3OXKfD7He40ELT295QJGtQucPTz99NN8+OGH5v9PnToVgO3btzN//nxkMhn/+c9/WLFiBbNnz8bPz4877riD5557zl0ii3golq5w0yKnMT7UcyyEfnI/uvRd6I16h+pqDBdbdsxGy+5nZXUlh1oOEegVSGZYJgWNBdR21XJ16tXIZXKbdttsLe7nSqo6qnpd33wjuHXcrS7tyxWYTCbade0ky5PdLYrH4oyd6nEh44gPiGdH5Q6WjVuG0su6y+FgxS77vxOOPtsqpYo7JtzBLem3sKdqDxvLNrKlfAtbyreglCmZHD6ZiWETSQ1OJSEgwfxeCnIKMli65kFft3adQUd5eznF6mKONR7jSP0RNAYNAAkBCSxNWspFsRf1sTz1f38FV5oWTQuRfn2TKcHosSyMBlw5f9h7X0ZiHHe0xpXIubRoehWgYO9gh9oZNQrQ3//+9wFrAAkkJiayYcOGkRFIZNQiuMI9sfsJXst9jZcufsljit4JOxrqHvWIVyG3dWLyVL/q/lR6VdJt7GZe0DyaGpvYVLoJCRIWJy4e8tz+rjbCTuFIL4A6tB2sPrQavVHPiskrBlzUejIdug70Rj0qpWO7deczzlgQSSQSliQu4W/H/saeqj0sTFzY53vhmfby8kKr1RIYGGhzjK2ji1all5KFiQu5NOFSKjsqyavLI6cuh4O1BzlQewDodf+NC4gjyjcKTYsGH4kPwa3BhNeH09TQhBEjtEJwVDCH6g7RWtZKXVcdle2V5hpTEiSMVY0lKzKLrMgs4vzjrMYe9X9/BVealh7rClD/MU9cwA4fV84fwn0Rxms/Pz86OzsHfG5HYhwfLfPlaEJQgBydU0aNAiQi4kziAuK4d8q9vJr7Kn849P/t3Xd809X+P/BXkjZJm7ZJ96K70BYopbRQhgjKqAtQURHxur2IXBEBUfwxFEG8ICgoF+71KngVEdf1gos9ZHdQKKMt3aV075l5fn/0m5i26UqzmryfjwcPTfIZJ59+cj7nfeZGrB2/1iIKl+ofdE1rjckDoN5m1AOl9vNi7UXYce0QI4pBRlEGUstSEesVCy9Hrx737XgtzPEQU6qU2HZpG0qaSvDs8GctqqWyLwz1sCI9mzhoIvbc2IODBQdxd+Dd7Qr/6ntYPcan47oo3THU/c/hcBDgHIAA5wDMCp+Felk9smuykVuXi9y6XOTV5aGovggyuQxyhRz2dvaQl8rBVAxA2+x1dnVtxRYuuJAIJYjxikGIOASh4lCES8J7tY5PxwDGTfBnC5AuAyXPGwh0XUtDtwp1vNe7um9NEcjSvWN4mi5wFAARop8E3wQ8OuRRfJf1HXZc3oHFoxb3OFORsWkCIKnuB7Ex9fbBZGm1nx3TWFFRgdT8VFwvv45493g48BxwrvkcVFBhevD0Xh2z47Xo+NoUAdGeG3twueIypgROQWJQolHOYQqG6q9NeuZg54A7B92JX7N/xa8Xf0WUZ5SmBlwkEkEmk8HR0REKhaJPBVBjFeJc+C4Y5T0Ko7xHad5TMRXqpHWokdagTlqHyupKVFZVwtHBEXKpHME+wZDwJWiqaoKfr1+3v7/eFqw1LUBdBECWlucNZLqupaHzUx8fHxQXF0OlUoHL5Zo1+KB7x/BqW2vBAQdifv8WLaYAiNi0hwc/jML6QpwvOY+fsn/CQ4MfMmt61H1aTbEmhXa/enUhqePMSgOh+V5Xa83ZyrOQK+SIFcVCoVLgbPlZeIm9EOMZ06tj6hoLYcpZ4E4UncAveb8g0i0Szw5/1uyBeX9oZuzpZ39t0jvTg6bjfxn/w9mqs/Cx+zNgV1MoFH3+nZuyEMflcOEqdP2zdvf/GmzT09MhYzLw6nioU9Zp0m2I7rrq4NwcFU/E8Pmp+jmgz1pRxPLVSGsgFojB4/ZvZlGalofYNC6Hi5dHvowglyB8k/kNkkqTzJoe7S5wxtJxFqiampouZzQaCLPV+Pj4gMfjadYscXJ3wtWmqwh3DUd8SDxuym5CypFiWvA0g81EZsxFCzOrM/Fp+qdwF7pjSdwSs65Obwjq9VWoC5zxVVRUoLagFoNdBuN683XYO9n3ala3gfI75/P5nd7rzT49baerBUifGcJsYWFpYzBGfjoQ7mmin9rWWoNUqFELELF5QjshXo9/HW+dfgufXPoE6yasQ4BLgFnSoqmJNGIA1HEWKO0WoI501fzqajnqz4Orv/2/O47RyRBkwE5oh9lRs+Hp6YmbN2/CQeCAyQGT9U6jMdKtS1VLFbakbAGPw8Py0ct7NZ7B0qlbM6kFyHjU96J68dJRTqNwS34Lebw8zIqepdmuq/t0IHTT0R7g3tvfXW+/l4OdA4Q8Iapaqzpdy760fg+EFnNr19e1okyZHron+k+pUqJGWoNBzoP6fSxqASIEgKejJ5bELYFcJcfGpI1okDWYJR0iexHsufZG6YqhPTMOj9fWdOzj44PQ0FBNF4Te1FyqH/LaLUf9qfk0xJoa6to+iacEv+f9Di9HL4z1HYtbDbeQUZ2Bsb5j4cJ30fv4uhh6LRCZUoYPkj9ArbQWC2IWIFgcbJDjmltNaw044FAAZETaC5jy+XzcHX43xHwxjhQeAWPMzKnrP1O0rAS6BKKwvhAlJSXtrqVIJOr1uanVwfD6+re3tDWaLC09A93txtuQq+QUABFiSFHuUXhu+HMobynHlpQtXS7UZ0wcDgeuAldUt1Qb/NjqjLipqUnTZUydKevKpLt68Kgf8q6urpqHfX8y+e4KDb19+Km7UFxuvoxGeSNmhc0Cj8vTdGmcOGhin9PVn3T3lYqpsOPyDuTW5WL24NkY5zfOACm0DNWt1XDhu/S7vzbRraKiQjPWwd/fH9HR0fD19sV4v/Eoby5HUUNRv45tCV26tPMXddfd4uJig54jVByKRnkj7FztwOfzNdeyqamp13mbMbvGWruu7rXuni269rGUIFS7wtES0mMtcutyAQBhkrB+H4sCIEK0TA2ainuC78H1quv4MPVDyFWmD4L8nf1R3FRs8HN3t8q7rodGVw8e9UM+NDRU87Dvz0Onu0JDXwIruVKOn3N/hkQgwaRBkwAAKWUpEPKEGOpm+CmkDVXYUTEVPr3yKc7ePosEnwQ8MuQRA6XQ/FRMhaKGIoPU1hHduhrsHecdB6DtNwDoF8xYSu21KQq1oeK2NZHqeHXtfteWUqC2dl3da91df137WEoQql3haAnpsRbqAEj9e+0PCoAI6eDpYU9jov9EpJSl4OPUj6FUKU16/lBxKBQqRb9qboHOBR7tB0PHh4T2a31qroz10OlL4eNU8SnUtNbg/tD7Yc+zR520Dtm12RjpNRL2PMucSIAxht3XduNY0TGM8BiBV2JfMdhEDZbgduNttCpbDfKwIrrp+o1UVFRAWaKEPbNHclkyAP2CGUsp/GvnL/7+/poWGkNS36PqApb2ufvSRZjop6t7rbtnS8d9LKXFErCc3461ya3LhZO9Ezwd+l/WoEkQCOmAy+FiQcwCKJkSZ2+fxfa07fhb7N9MVjANEYcAAPJq8/pVcNR3QK72eAJzDyLt7SBmFVPhQM4BiOxFmBo4FQCQWpYKBqapCbc0jLG2RSvzD2Ko+1AsG73MYgM1fRmyto50pmuAdUVFBQoLCwEAgXaBSC9JR3Zxtl5TDVvi5AjGSpOfkx/4XD7y6vI6fUaTGxifPn/XjvtY0t/JEn87+lIxFYobi3G78TZqpDWoba1FjbQG9dJ6yFVyKJkSPA4PXA4XDnYOcBO6QSKQwFXoCncHdwS7BENkL+p3OpQqJfLr8hHhFmGQpSEoACJEBx6Xh4UjF0KulOPM7TOw49rhpZiXTBIEaddETsEUvY+j79oKfd3PEma5OV9yHiVNJXgo/CE42jsCAJLLksEFF6O8RvWwt+kxxvBN5jc4kHsAQ1yHYPno5RDwBOZOlsGpC5OhEgqAjKFjq466O5xapCgS6fXpOJV7Cs9NfG7AF8iMmdfwuDwEi4ORV5cHxpimgKU9xopq8y2begFU9ZIIA/1+N5dGWSOuVFxBVk0WcupyUFBfAKlS2mk7O64d+Fy+plykUCnQqmzVeUxvR2+EikMRKgnFCI8RCHIJ6nMQc7vxNmQqmaaSuL8oACKkC3ZcO7w66lVsSdmCk7dOQqFS4OWRL8OOa9yfjZvQDWK+uFNXjL7Stwaqr/uZu9ZNqVLiu8zvIOQJcW/IvQDaZlS7UnEFEW4RcOI7mTxN3WGM4T/X/4Nf835FuCQcK8asgIOdg7mTZRS5dblwtHOEt6O3uZNilbQrKzpOb+/j4wN3uTt+KPsBhapCcyfVIIyd14SIQ5BVk4Xy5nJ4i7w151QqleDz+VSgtnAdl0Sgv1fvlTeXI6k0CSllKbhRdQMqqAC0zUw7xHUIQsQhCHAOgKvAVbNQsaOdY6cgRq6So05ah5rWGtRKa1HaVIq8ujzk1eXhfMl5nCs5hz3YAw8HD8R5xyHOOw7D3If1qlxl6B4FFAAZgSXUiBPDsOfZY0n8EmxL3YYzt89ArpJj0ahFRl2cksPhIFQSivTKdMhVcotcCFP7Hjf0Kt59deLWCdxuuo3Zg2dr1s1Jr0yHTCVDvE+8WdLUFRVT4fOrn+NwwWFEukXijdFvaFqsrI2KqZBfl48wSZhBuivYOl3PFV1dgNp9Dk/EDYpDVk0WpErpgG9l1M5rjPGcDRO3zSyVW5erCYDMnb+RvqG/V+8pVUqklqfiYP5BpFemAwDsufaI9Y5FnHcchrsPh5ejV5/yb3uuPTwcPODh4NHpsxZFC7JrspFanorksmQczD+Ig/kH4SpwxZSgKbg74G64O7h3eWxDzgAHUABkFFQDYV3sufZ4ddSr2J62HWdvn8Xm5M1YGrfUqOM1QsWhuFR+CUUNRRY5fkL7HjfnDDcypQzfZ30PZ74zHgh9QPO+euarOC/LGf+jYir888o/caLoBKI9orEsfhmEdkJzJ8toaAIEw+rpudJVy22cdxyuVV1DekW6xVUI9JX2d0xPTzf4c1a7+7F6KnprGsthTqaqGKa/V8+a5c04mH8QhwsOo6q1CjwODwm+CbjT/05Ee0YbraLEwc4B0Z7RiPaMxlNDn8Ktxlu4WHIRx4qO4fus7/Fj1o8Y7TMaM8NmItw1vNP+hpwAAaAAyCioBsL62HHt8ErsK+Bz+Thx6wTWnl+LpXFLIRFKjHI+Q02EYCyWco//nv87qlur8dTQpzQtKSqmQmpZKvyd/OHr5GvW9Kk1y5vx8aWPkVqeilivWCyJWwI+j2/uZBkVTYDQf+pCo0gk0nscSpx3HP5z/T9IKksa8AGQNmPkQd1NhED6hyqGzU+ulONwwWH8mP0jGmQNcBW64tEhj+LuwLvhJnQzaVo4HA4CnAMQ4ByAhwY/hNSyVBwqOIQLpRdwofQCEnwSMCdyDvyd2mZ7NPQECAAFQEZBNRDWicvhYn7MfLgIXLA/Zz9WnF6BZfHLDNYcq01daMysycSUIP0nQuir3tbSWcI93iRvwv+y/wcPBw9MD5queb+0qRQ10hqLWUy0pLEEm5I3obixGJMGTcKL0S9a3WxvumTVZAGgCRD6Q11oVM/KqM84FB+RD3xFvrhRdUPvdFhSt27ttBh6lkr1RAi5dblQMZVVTUlvbpZSaWZu5vgtMcZwuvg09mXuQ0VLBdyEbvjriL9i0qBJRh/T3BtcDhfxPvGI94lHUUMRvs38FhdKLyCpNAmTAiZhbuRc1EvrDToBAkABECF9wuVwMS9qHgKcA/CvK//CmrNr8HLMyxjvP96g53F3cIc33xt/5P6B2X6z4e1lmkHkA6mWbn/OfjTKG/HU0KfaBRSG7ifcH1cqruCj1I/QIm/BX4b+BfeH3G8T42EYY0gpS4GPow9NgNAP6kKjSCRCU1OT3oXHUHEoztw+gyZ5k17T0VpSvmDstES4RiCrJgt5dXkWkYdYC0uoNOsrYwQrpv4tVbVU4dP0T3Gp/BKc7J0wL2oe7gm+x2J7IAQ4B2Bp/FLcrLmJrzO+xvGi40gqTcJwj+FgjCHCNULnftp/K4Ggd134qHqDED3cOehOvD3ubTjxnbD10lbszdgLFVN1ub0+C7QF2wWjUd6I5LxkQyS5VwbK4m01rTX4NfdXBDgHYOKgie0+y639vwBIbL7CC2MMv+b+ig0XNoAxhjfGvIEHQh+wieAHAPLr81HdWo1R3qNs5jsbg3oRyNDQ0H6NtVO3wunbtUs7XzD3YpPGzqPU64apxxES26XP4sE9MdUzljGGE0UnsOzkMiQVJyHSPhKroldhZtjMboMfc/++1Qa7DsbqsavxWtxr4HK4+O/N/6KypRKBLoE6t9fnb0UBECF6CncNx3t3vIdwSTh+yv4JHyR/gGZ5s85t9flx3hF8BzhcDm5xbxkqyT3SXnXbUjJCXb7O+BoylQyPRzzeqZtKbl0uhDyhZhYnU5Mr5dh5ZSe+uP4FfEQ+WH/Heoz0GmmWtJhLcllb0G5NY04GMu3B/frQzheMUSjUNy3GMMR1CJztnTX3MLFdxghWjH3/AkCrohUfpX6EHZd3QMATYI73HMz0mImGqoYe9zX371sbh8PBWN+xWDt+LXhcHpRMibdOv4VrVdc6bavP34oCIEL6wU3ohrfHvY2J/hORUpaCVWdWobSpc8ahz48zPiQePmIfZDVlGTLJvWZJGaG2zOpMnLp1CiM8Rmhqa9VUTIW8ujyEiEPM0n+/trUWa8+vxYmiExjpORLr7lgHPyc/k6fD3FLLUuFk79RldwViWsEuwQD0bwHSNlBaifXF4/IQ6x2LgvoCVLZUmjs5xIxMEawYWkVzBVafXY3zJeeR4JuADyZ9gLsG39Xr36wl/r5z6nLg5eiFxyMeh1wpx/rz63Eo/1C7bfT5W1EAREg/2fPssXDkQjwZ9SRuN97G/zv9/3Cp/FK7bfT6cXK4iPOOw63GWzqDKmOzxIxQqVLis6ufwY5jh+eGP9epe1VpU6nZpl7OrM7EitMrkFWThRmhM/DGmDf0Gm8x0FW1VCG3LhcjvUZaxABbAjjaO8JP5Iec2px+H2sgFgr7Sl2xQq1AZCC5UXUDb51+CwX1BXg84nG8Nuo1OPGd+vSbtcTft7o76tzIuVh3xzp4OHjgs6uf4d/p/4ZCpdD7uBQAEWIAHA4HM8LaCr0qpsL7F9/Hjss70CRv6tdxzdkf3RIzwsOFh1FQX4AHwh7QOcW1ZuplE848JlPK8NX1r/D22bfRIGtoC4aHPmmzM0illqcCQKfWOWJeIeIQlDWX9TtPsgUxnjGw49ohpZTGAZGBIbk0GesurINMKcOy+GV4aPBDVjH+Uq6S41L5JYRLwiERShDgHID37ngP0R7ROFxwGJuTN0OulOt1bNt8QhNiJCO9RmLTpE0Y4TFCMwAxrTytx/26Gm8T7RkNPpdPNZEA6qR12JexD+5CdzwU/pDObdQTIJiqBSirJgtvnHoDB3IPIEQcgg0TN+DOQXea5NyWKrk0GXYcO4z0HGnupBAt/Z0IwZY42DlgmPswXK+63uW4TkIsxYWSC9iSsgWOdo54Z/w7GO0z2txJMpiMqgy0KFoQ7/3neFInvhNWjFmBOwfdidTyVGxM2qhXEEQBECEG5uHggbcS3sJfR/wVzfJmbLi4QWdrkHbQ09V4m/rqevhyfJFelo4GWc8DGK3Z1xlfo1nRjKeGPQWhnVDnNuoJEHxExu22p271WXNmDcpbyvFE5BN4d8K7CHAOMOp5LV2LogXXqq4hyj1KszAtsQz9nQjB1sR5x0HBFLhSecXcSSGkS8mlydiauhXOfGesHrcaweJgcyfJoDQT6ni3n1CHx+VhQcwCTA2ciiuVV7A5ZTPkqr4FQRQAEWIEHA4HUwKnYPPkzV22BmkHPV2NtyktLUWkQyRkchlO3Tpl2i9hQTKrM3Gi6ASiPaKR4JOgcxvGGPLr840+AULHVp/3J76PWeGzwOPyjHbOgeJ08WnIVXKM9R1r7qSQDoJdgsEBh1qAekkzDqiUWt+JZcqszsRHqR/B0d4RK8eutLoKOPV6cl4OXhjkPKjT51wOF89HP4/JAZNxqfwSdqTtAGOs18enAIgQI+quNUg76OlqvI2Pjw9iXWMhcZDgcMHhbtcaslZypRz/uvIv2HHs8OzwZ7vs19wob0SLosVos65Rq0/3GGM4lH8IDnYOmOA/wdzJIR042jtCIpSgvLnc3EkZEDwcPBDsEoxL5ZegVCnNnRxC2qlqqcLm5M3gcrh4K+Etq3wOFTYUoqKlAvE+8V0+97kcLuaPmI9473icuX0G+3P29/r4FAARYmRdtQbdkN7AsOHDup1kwNPTE7ExsUgMS0RJUwmuVl41XcItxA83f8Ctxlt4ePDD8Hfy73K72tZaAIBEIDHo+RljSC5NxvJTy6nVpxuZNZkobCjEpEGT4GDnYO7kEB1cBa6oldaaOxkDRpx3HBrljcisyTR3UgjRkCll+CD5A9TJ6rAgZoFZZj01BfXkT6O8RnW7HZfDxd9i/4YA5wDszdjbaRbeLvfrdwoJIb2i3RokVUqxPW07lp9ajpSylB6bbacGTgUHnE5z31u7nNoc/C/7fwgRh2BW+Kxut62WVgNoW5vJUK5XXcfqs6uxKXkTqlurMTdyLrX6dEF9b04LmmbmlJCuuApdUdta26duIrYswbetu+3JWyfNnBJC/vSvK/9Cbl0uZg+ejXF+48ydHKNQMRVOFp2EM98Zke6RPW7vYOeA1+Nfh8hehJ2Xd/bqHBQAEWJClZWV8Krzwuro1ZgVNgtlTWXYmLQRa86uQWZ117WM3iJvjPQaiZSyFJtZnE+ulOMfaf8Al8PFgpgFPa4pY8gWoIL6Arx/8X28c+4d5NTmYFrQNGy9ayseDH+QWn10qJPW4ULJBQxzH6azrzaxDK4CVyiYAg1yw0+o0tVMlsbazxSCXIIwxHUIzhSfQaOs0dzJIQTnbp/DH8V/IM47Do8MecTcyTGa9Mp0lDaX4q6Au2DPte/VPt4ib7wW9xpkSlmvtqcAiBiNJT/YzEU98UFDVQOeiHoCW+/eiqmBU3Gz5iZWn12NjUkbUVhfqHPfxOBEqKDC0YKjJk61eWh3fQtyCepx++rWthYgV6Gr3vdeeXM5Prn0Cd449QYulV/CeL/x2DJ5C16IfgGuQle9voctOFZ4DAqmQGJwormTQrohEUoAADWtNQY/dlczWRprP1OZFjQNcpUcJ26dMHdSiI2rk9bhs6ufwdneGfNHzLfqteYO5R8CB5w+9ygY7jEc04Om92pb6716xOws/cFmDh1ne3MTuuHFES9iy+QtGOc7DillKVh+ajm2p21HRXP7wnuMZwy8HLxwtPBon6d7HGj60vVNTT22QSKQ9Pneq5PWYdfVXXjt+Gv4o/gPjPAcgQ0TN+DVUa8afUrtgU7FVDhccBiuQlda/NTCuQragnhjBEBdzWSp1lWlRE/79ZWhK97G+Y6DM98Zh/NtcxIaYjk+u/oZGmQNeG74cxALxAY9tiVVWFe2VCK1LBWxXrHwcvTq8/6zh8zu1Xbd9ykhpB98fHw0UzyTNp6enu0mPcjNzUVNTQ1cXV2xOG4xZtTOwN6MvTh16xTO3j6LCX4TMD1oOsIkYeByuJgWPA17buzBxZKLVjvTVl+7vqnVSGvAAQcSgQQqH1Wv7r2ihiIcLjiMk0Un0apsRbgkHE9EPoFhHsMM8VVsQkpZCqpaq/DIkEd6/bciPVOvD6aeJdIQ1N1DjTERQse8rSPtSgnt7Xrar6+6Oo++7Hn2uCvgLuzP2Y/0ynTEeMYYIJWE9E1yaTIulFxAgk+CUcb9qH83xcXFBs93+upIwRGooML04N615HTE5/F7tR09rYjRGPrBZo1qamra/TdMEoaVY1civSId32V9h5O3TuLkrZMIFYdietB0jPMbh++zvsePN3/EOL9xVtkE/uWNL3Gr8RbmRMzpVdc3tZrWGrjwXcDj8rq99+QqOZJKk3Ao/xBuVN8AAAQ5B2H2kNkY4zOmy+k2SWcqpsIPN3+AHdcOUwKntPvMGAV4W2Logjzw5wQhxmgB6ompKsSMcZ5pQdNwIOcADuUfogCImJyKqbA3Yy8EPEG3S0Ho0lM+rP5cJBIBAJRKpcHznb6Qq+Q4VngMXg5eRv+tUQBESD/1p6Dn6uqqaQHSFu0ZjWjPaOTX5eNwwWGcLj6NnVd2QmQvgrvQHXl1efjj1h+YFDDJkF/F7C6UXMDB/IOIcovCg+EP9mnf2tZazRgHXapaqnC08CiOFh5FrbQWdlw73OF/B6YHTccQ1yEU+OjhfMl55NXl4b6Q+zrNvmeMArwtMUZBXjMGSKp/AKRvfmeqCjFjnMfL0QuxXrFILUtFZUslPBw8DHp8a2TMChBbq1w5WXQStxpv4aHwh/o8FrWnfFj9OdCW5xQVFQGAJiAytaSSJNTJ6jAvap7RK3gpACKkn/pT0AsN7X7+/mBxMF4c8SLmRc3DqeJTOJR/CEUNRShuLMb6C+vB5/IxxneMRc1Mpu/Dqby5HP+88k84853xSuwrfc78aqQ1nRZBZYzhauVVHCo4hOTSZKiggqeDJ+ZGzsVdAXcZvB+1LVGoFNiXsQ9CnhAPDX6o0+fUBbZ/jFGQF/PF4ICjmTFRH/rkd9p5gvoYA63wmhiciNTyVBwpOILHIx83d3IsnjErQGypckWulOO7rO/gZO+EmWEz+7x/T/mw9uelpaWaKfKbmpr6lW59HSo4BHuuPSYHTDb6uSgAIqSfTFHQc7R3xD3B9yAxKBHXq6/jH2n/wMXSi3jn/DsIdglGnHcc4rzjMNxjeK/7vxqLPg8nhUqBbanb0CRvwooxK+Du4N6nQErFVJCr5BDaCaFQKZBRnYGUshQklyajvKUcHHAQ4xmD6cHTEesVa5VdB03teNFxlDaX4rEhj8GF79Lpc+oC23+GrunmcXng8/iQKqV6H6Mv+Z06/UqlEkqlUjMpyUAsvI7wHAEvBy8cKzyG2YNnw57Xu6l5bZUxn4u2VLlyvOg4qlqr8GTUk3C0d+zz/j3lw+rP1N3glEolAJjl2hbWF+JG9Q1M9J+o85liaBQAEdJPpizocTgcDHMfhg8nf4iXj7yM8pZy8Hl8HCk8giOFRyDgCTDCYwTivOMwynuUWVo49Hk47cvch5u1NzEzbCZGeo0E0LdAqkHWgEZZI1LLUvHioRfRrGgG0Lb47MywmZgSOIVmczMgqVKK77O+h5gvxn2h95k7OVbLGDXdXA4XSqbUe3/t/K6nAE2dfh6P126mt4FYeG03CU2p9U5CYyjGfC7aSuUKYwyHCg7Bwc7BqAtMq3+nMpkMgYGBmvcAmPQ6HypoW0xb38kP+ooCIDJg2Fq/3+7weXw8EfUE/nnln7jD/w5M9J+I5LJkpJSlIKUsBUllSeCAg8GugzWtQ4OcBuk9zqUv176vD6e08jTsz9mPcEk45kTM0bzfUyBV1lSm+b7Xqq6hvLkczYpmjPMdh3ifeMR5xyHQOZDG9hjBr7m/olZai2eHPwsHOwdzJ8dqGaOm245j168ASFtPAZp2+jvO/DYQTQ6YjG8zv8Wveb9ivN94yluIUWVUZ6CooQj3Bt8LoZ3QaOfx8fFBYWHb+oOGbKXtS7mhUdaIP279gWCXYAyWDNb7nH1BARAZMGyp329vTBo0CT/n/oxfcn9BYnAiZoTNwIywGWiQNeBS+SWklKUgrTwNWTVZ2JuxF+5Cd4RJwhAqDkWIOAQh4pBetxAZ69pXtlTik7RP4GjniFdHvdpuGmXtQKpJ3oS8ujzk1uUiry4PObU5KGsuAwDYc+0R7RGNemk9pgZPxZK4JQZLH+msUdaI/Tn74eXo1WnmN22VLZVYeXolrlZdxXD34Vh3xzoaPN5Hxqjp5nK4BlvPpqcAzdpq6l34LpgcMBmHCw4jpSwF8T7x5k4SsWLqFpFpwcZr/QH+rJAoLi6GUqmEQCCATCbr90QIfSk3/JT9E1qVrXgg9AGTVSxQAER0ssTWFkvt92uua8Xj8vB4xOPYnLIZ32V9hxeiXwAAOPOdceegO3HnoDshV8pxvfo6UspScKPqBpJLk3Gx9KLmGB4OHggVh/YYFBnj2rcqWrExaSMaZA1YFr9Ms+BZx2AntzYXpc1/LmjKAQe+Il9MDpiMeO94RHtEQ6FS4ErlFTjZOxksfUS3H2/+iGZFM56Pfh723K7HQaw8vRLnS85DyZQ4X3IeK0+vxM5pO02YUqILj8uDQqUwyLGsLcDpjYcHP4yTRSfxTeY3GOU9isYTEqNolDXiYslFDHMfBn8nf6Ofz9PTUxOwNDe3dSHXZyIE7fJQb8oNFRUVyCzKxIGiAwhyDTJp11IKgIhOltjaYqkPW3Neq9E+oxHpFokjBUcwwW8Cotyj2n1uz7NHjGeMZj59qVKK/Lp85NXnIa+2LcjoGBS5Cl3hLnSHq8AVEqEEbkI3SAQSuHq7oonfBL6UD2e+c58f/IwxNMgbUNtai6qWKvzn+n9wveo6hnoMxR+3/sD+nP2obq1GZUulZh91sDPBbwJCJW2BWrBLcKfBoI2yRs05iPFk12Tjt7zfECYOw3i/8Tq3UT8A0yvSNV2tlEyJa1XXTJlU0gXGmMEK7ZZYUWZsbkI33Bd6H37K/gmni0/jzkF3mjtJxApdrrgMBVNggp/pAgJ1wCISidDU1KRXhad2eSg6OrrHfKG0tBSHyg6hRdaCJyKf0ORNpshbKACyAfrcSJba2mKJzHmtOBwOXhrxEl4/9Tp2Xt6JjZM2QsATdLm9gCdAhFsEItwiNO91DIqKGopQ3VqN3NpcqKC7qwyPw4NYIIaAJwCPwwOPywOPwwOXwwUHHCiZsu2fSgkVU0GqlKJOWgcFa6t5rmmtQU1rDUT2IhQ3FKOksQRigRgSgQQRrhGaYCdEHNKrMSbqacAN1bWHdCZXyvGPy/8Al8PFgpgFXRai1Q/AYGEwrsqvQgUVeBwehrkPM3GKiS5KpgSPY5hp8y2xoswUZobNxJGCI/g281uM8x1HM8IRg0suSwYAjPIeZbJz9lTJ3JuyZFfloY77ql838hpxufEyhroPbbfwqSnyFgqAbIA+N5L21Ijar0ln5m6Z8nXyxeORj+PL61/im4xv8PSwp/u0v66gCGgLJhpkDahurUattLbtv621qJHWaAIYmUrWFuAopFAxFRRMAQYGO47dn4ERlwexnRhBLkFwFbqiVlqL44XHEeMZgyXxS+Aj8oGYL+7XWkZ2nLaszFCDu8mf1A+qc63nUNxYjMcjHkeAS0CX26sfgH8N+Cv+WfhP5LXkIcY7BuvuWGfCVA9sxqz9VDGVwQIgW60oE9mLMCt8Fvbc2IPDBYdpJkRiUHKVHGnlaQiXhPd54dP+6Crf0TWdva7PtffrWHbsWA5Vv95fvh9CByGei32u3dgf7dao9PR0o+SFFADZAH0fUrZauzcQ3RdyHy6WXMRveb9hjM+YTl3h9MHlcCEWiA06lXZhfSFWnVmFQc6D8N4d78HT0XBT+wIUABlDaWkp8urz8MvtX9pa5lpCUVFR0WWeoK4QqKiowArhCpvqHmUo6rxXPTOTIa+fQqUwWBc4c1f+mNM9wffgt7zf8GP2j5gcMFmvNVoI0SWrJgvNimbEeceZ9Lxdlfm6ms6+q/10HadjOdTHxwdJeUnIas3CuEHjMMR1iCaQ0u6CZ8xyKI3eswGenp696ovZkY+Pj86b3ZgqKiqQnp6OiooKk53TGqi7Jdlx7bDz8s5+LXRoLHXSOmxK2gSFSoGl8UsNFvwAWgGQigIgQ3P3csf+yv0Q8oW43/V+KOQKTe1ed/TNd0j7RQh7c637QsmU/WptJW34PD4eGfIIGmQN+CXvF3Mnh1iRmzU3AQBD3Yea9LzqMp+61UVdDlO/7+/vrzNP71hW1FV27Pg88PDwQJIyCXx7PuZEti1/oQ52ampqNEGPMcuhFACRLvWlAGOowEU72h8ILClg83XyxdzIuShtLsU3Gd+YOznttChasDFpI8pbyvHc8OcQ6RZp0ONzOBw42TuhQd5g0OMS4ET1CdSoanCHyx0IdQs1yMPIkn43lsjT0xOBgYEGf/BLlVLIVXKaLdFAJg+aDD+RH37O+Rl10jpzJ4dYidy6XHDAQZBLkEnPqy7zNTU1tSuHqd8HoDPf7lhW7K7sqM77T948ietV1xHvFo/a/FpUVFR0yuvUvQe6O3d/UABkY4xV8DBU4GKOVqf+sLSA7d6QexHhGoHf8n7DjaobRj9fb+4nhUqBD1M+RHZtNh4OfxhTgrpeO6Y/JAIJaltrjXJsW6L9N82pzcGBnAPwsvPCWOexaGpqMsjDyNJ+N5bIGC1oNa01AGDScQXWjMfl4fHIx9GqbMWPN380d3KIlcirzYO/k7/ZFpnWVQ6rqKhAYWFhn/PtjmWE0tJStEpbsTdjL+y59kgQJrTr4qau+AkMDNTZDc+QzwwKgGyMdt9yQwZBPj4+4PF4UCqV/TqupXSb6fij7aqgb2kBm7ornD3XHtsubTN6QNBTpqRiKuy8vBOXKy7j7oC78VjEY0ZLi6vQFdWt1UY7vjZrbsFQ/01zbuXgo5SPwOVwMT96PoQCoeY+7+/DyNJ+N7ZCHQBJBBLzJsSKjPEZgzBxGI4UHEFpEwX0pH8aZY0obylHiDjEbGnQVQ7TzuvV+bb6OZibm9vl87Djs0IkEuFq41WUycswwWMCnLhO4HA4mrKjrnNXVFRAqVSCx+MZ9JlBAZCNMVbfck9PT00AZA21uh1/tNqvtQu/lhKwafN18sWLI15EdWs1NqdshlwpN9q5eirI7rmxB38U/4E47zi8EP2CUVd4lggkaFW2okXRYrRzqBUXF0Mmk6G4uNjo5zI1Hx8f8Ox5+L78exRUF+CRwEcwMnhku/u8vwGMJf5ubEGN9P9agATUAmQoHA4HTw59EgqmwM7LO2kqftIvZc1lAGCSxU/7Qp3na7fM6Bqz09V+6mdFWV0ZDlYehAACDMdwKJVKMMZ0lh3VZa2ioiIolW3jew35zKAAyMZ4enrC1bXt4ScSiQx6bGuq1e1uUN9A6L5z56A7MTNsJrJqsvDv9H8bbYHQ7gqyB3IO4OfcnxHpFonFoxYbfeC1ulsPdYPrH09PT6RwUpBVn4XRzqMRjnCd21AAM/BQFzjjGOo+FNOCpuFG9Q0cKjjUq32suRWZ6E/9G3V3cO92u/7eP931ctF1bF15vrpc5Orq2mXZr+N+xxuPo5k14wGvB+DIa5s5Udf+2l3ujFV+oQDIBjU1NbX7r6FYU6Gou+8yUAK9uZFzEesVixO3TuDXvF9Neu5Tt07hqxtfYZDTILwe/zr4PL7Rz6mu1VbXchuTv7+/ZlYca3O44DAOFRxCtEc0ZvrNtPj7nPRerbQWAAVAxjAvah68HLzw9Y2vUdZU1uP2A6EijZie+vnVUzfV/t4/3fVy6c2xtdf+CQ0N1dltrWMQdb7kPC7XXcakkElIjEgEj8cDj8eDs7Nzp7Gl2udWB0iGft5SAGSDBkoB3hAqKiqQlpaGtLS0ftW0dZyLfiAEelwOF4tiF8HfyR9fXf8Klysum+S8526fw460HfBw8MBbCW/BiW+aGadM2QI0UO6BvrpedR27ru6Cl6MX3pzwJmJGxGjW9KHa6oGPxgAZj4OdA+bHzIdUKcWOyzt67ApnS89h0nuaVtoeuqn29/7prpdLx8905f89jSfvGETVSevwWfpncLZ3xgvRL8DLy6vdsImOkyxod7nTFWAZAgVANshaC2+6dFy9WF8D9WHlaO+I1+Nfh4O9A7ambkVJY4lRz3eh5AK2pW6DWCDGyoSVPTbjG5I6ADJmC5A1BwLlzeXYkrwF9lx7LB+9HM58Z81n3dUIWvM16YuBcB1qpbVwsHOA0E5o7qRYpeEew3vdFc6WnsOk9+pl9QAAF4FLt9v19/7pburqjp/pyv97Gk/escy06+ou1Mvq8dzw5zSLq2tPnqU9nra7cpYh81kKgGzQQHhQG4r6B9bf2UM8PT01438G2nXzdfLFq6NeRYu8bS2eJrlhuz6qJZcmY2vqVjjznbFq3Cr4Ovka5TxdUddqq7v5GIO1dltpUbRgU9ImNMob8UrsKwhwDmj3eXcVANZ6TfpqIFyHmtaafk+AYEvPD330tSscIdoUKgUAwJ5rb9Lzdve77mph08DAQLRwWvBh0Ye445s78NLhl1DZUtmue5ynpyfOl5zHuZJzGOMzBuP8xrU7hjoAAtBukoWO+ak6feoJiAyRz1IAZIMGwoPaUDw9PTFy5Ej4+/v3O3jRdd0M1cXO2GI8Y/Dk0Cdxu+k23r/4vsFnSksqTcKHKR9CZC/CqnGrzDKDjboFqKqlymjnMGZLoLkKllKlFBuTNqKwoRBzIuYg3ie+0zbWMCbO2Cz9OjDGUN1aDYlQ0ud9tae71WctEFviYOeAl2Je6nVXOEK0qe8XY86Yqq2rwEJdtklNTUVxcbEmmNHm6emJr6q+QmplKuqkdThfch4rT69sV1bq2PWt4/dS55v+/v6aSubc3NxO016rjwnAYPmsXb+PYGXUs03U19ebOSXGIxKJ0NjYCFdXV6v+ntqys7Mhk8nQ2NgIgUCg1zF0XTf1cQGgpaVF72Obwh3ud+C29238lvcb1h5fiyXxSyDg9T+9yaXJ2HF5B0R2IrwW8xrETGy2+0qikiCrJMto5xcIBAgKalud29DnMMQ92ldypRwfpX6Ea1XXMDVwKu72urvP38uY12QgsfTrUNlcibr6OriL3fucPvW9WV395zpbAQEBFvk9LUEAPwAT3SfiWNEx/PfqfzEtaJq5k0QGiNamVsib5airq4Pc3nhLWKipf9vqnjLq8o122QbounyTVpSm2U4JJS4XXYYo7M+y0o7zO1BVW4UFIxaAI+WgXto+z9DON69du9Yun+Hz+RAIBKivr9eUv7y9veHh4QGg63xW/X5Ps8dxmLHmlxugbt26hYCAgJ43JIQQQgghhFicoqIiDBo0qMvPKQDqQKVS4fbt23B2djZZE6QlqK+vR0BAAIqKiuDi0v3gO0IMhe47Ymp0zxFTo3uOmJot33OMMTQ0NMDPzw9cbtcjfagLXAdcLrfbiNHaubi42NyPhZgf3XfE1OieI6ZG9xwxNVu958RicY/b0CQIhBBCCCGEEJtBARAhhBBCCCHEZlAARAC0zcSxZs0ai57FjFgfuu+IqdE9R0yN7jlianTP9YwmQSCEEEIIIYTYDGoBIoQQQgghhNgMCoAIIYQQQgghNoMCIEIIIYQQQojNoACIEEIIIYQQYjMoACJYv349xo8fD0dHR0gkEp3bFBYW4v7774ejoyO8vLzw+uuvQ6FQmDahxKoFBweDw+G0+/f++++bO1nEimzfvh3BwcEQCoVISEjAxYsXzZ0kYsXefvvtTnlaZGSkuZNFrMipU6cwY8YM+Pn5gcPh4Keffmr3OWMMq1evhq+vLxwcHDB16lTcvHnTPIm1MBQAEchkMjz66KNYsGCBzs+VSiXuv/9+yGQynD17Fl988QV2796N1atXmzilxNqtXbsWJSUlmn+vvPKKuZNErMS+ffuwZMkSrFmzBqmpqYiJiUFiYiLKy8vNnTRixYYNG9YuTzt9+rS5k0SsSFNTE2JiYrB9+3adn2/cuBHbtm3Dzp07ceHCBYhEIiQmJqK1tdXEKbU8NA020di9ezcWL16M2tradu//9ttveOCBB3D79m14e3sDAHbu3Ik33ngDFRUV4PP5ZkgtsTbBwcFYvHgxFi9ebO6kECuUkJCA0aNH45NPPgEAqFQqBAQE4JVXXsGbb75p5tQRa/T222/jp59+QlpamrmTQmwAh8PBf//7Xzz44IMA2lp//Pz8sHTpUixbtgwAUFdXB29vb+zevRuPP/64GVNrftQCRHp07tw5REdHa4IfAEhMTER9fT2uXbtmxpQRa/P+++/D3d0dsbGx2LRpE3WzJAYhk8mQkpKCqVOnat7jcrmYOnUqzp07Z8aUEWt38+ZN+Pn5ITQ0FPPmzUNhYaG5k0RsRF5eHkpLS9vle2KxGAkJCZTvAbAzdwKI5SstLW0X/ADQvC4tLTVHkogVWrRoEUaNGgU3NzecPXsWK1asQElJCbZs2WLupJEBrrKyEkqlUmc+lpGRYaZUEWuXkJCA3bt3IyIiAiUlJXjnnXcwceJEXL16Fc7OzuZOHrFy6vKZrnyPym7UAmS13nzzzU6DLzv+owc/Mba+3IdLlizB5MmTMWLECLz00kvYvHkzPv74Y0ilUjN/C0II6bt7770Xjz76KEaMGIHExET8+uuvqK2txbfffmvupBFi86gFyEotXboUzzzzTLfbhIaG9upYPj4+nWZLKisr03xGSFf6cx8mJCRAoVAgPz8fERERRkgdsRUeHh7g8XiafEutrKyM8jBiMhKJBEOGDEF2dra5k0JsgDpvKysrg6+vr+b9srIyjBw50kypshwUAFkpT09PeHp6GuRY48aNw/r161FeXg4vLy8AwOHDh+Hi4oKhQ4ca5BzEOvXnPkxLSwOXy9Xcc4Toi8/nIy4uDkePHtUMEFapVDh69Cj+9re/mTdxxGY0NjYiJycHf/nLX8ydFGIDQkJC4OPjg6NHj2oCnvr6ely4cKHLWX9tCQVABIWFhaiurkZhYSGUSqVmxprw8HA4OTlh+vTpGDp0KP7yl79g48aNKC0txcqVK7Fw4UIIBALzJp5YhXPnzuHChQu466674OzsjHPnzuG1117Dk08+CVdXV3Mnj1iBJUuW4Omnn0Z8fDzGjBmDjz76CE1NTXj22WfNnTRipZYtW4YZM2YgKCgIt2/fxpo1a8Dj8TB37lxzJ41YicbGxnYtinl5eUhLS4ObmxsCAwOxePFirFu3DoMHD0ZISAhWrVoFPz8/TUWQTWPE5j399NMMQKd/x48f12yTn5/P7r33Xubg4MA8PDzY0qVLmVwuN1+iiVVJSUlhCQkJTCwWM6FQyKKioth7773HWltbzZ00YkU+/vhjFhgYyPh8PhszZgw7f/68uZNErNicOXOYr68v4/P5zN/fn82ZM4dlZ2ebO1nEihw/flxn+e3pp59mjDGmUqnYqlWrmLe3NxMIBGzKlCksMzPTvIm2ELQOECGEEEIIIcRm0CxwhBBCCCGEEJtBARAhhBBCCCHEZlAARAghhBBCCLEZFAARQgghhBBCbAYFQIQQQgghhBCbQQEQIYQQQgghxGZQAEQIIYQQQgixGRQAEUIIIYQQQmwGBUCEEGJEkydPxuLFi82dDIuSkZGBsWPHQigUYuTIkeZOjk7BwcH46KOP+rzfiRMnwOFwUFtb2+U2u3fvhkQi0TtthBBC+ocCIEIIIT16++23DRasrFmzBiKRCJmZmTh69GiX25WWluKVV15BaGgoBAIBAgICMGPGjG73Mbfx48ejpKQEYrHY3EkxCX0DRUIIMSc7cyeAEEKIbcnJycH999+PoKCgLrfJz8/HhAkTIJFIsGnTJkRHR0Mul+PgwYNYuHAhMjIyTJji3pHL5eDz+fDx8TF3UiyKUqkEh8MBl0t1roQQy0C5ESGEmNAvv/wCsViMPXv2AACeeeYZPPjgg3jvvffg7e0NiUSCtWvXQqFQ4PXXX4ebmxsGDRqEXbt2dXtclUqFDRs2ICQkBA4ODoiJicH333+v+VypVOL555/XfB4REYGtW7e2O8aJEycwZswYiEQiSCQSTJgwAQUFBdi9ezfeeecdXL58GRwOBxwOB7t37+4yHWvXrsWgQYMgEAgwcuRI/P7775rPORwOUlJSsHbtWnA4HLz99ts6j/Pyyy+Dw+Hg4sWLmD17NoYMGYJhw4ZhyZIlOH/+vGa7LVu2IDo6GiKRCAEBAXj55ZfR2Nio+Vzd3eznn39GREQEHB0d8cgjj6C5uRlffPEFgoOD4erqikWLFkGpVLZLQ0NDA+bOnQuRSAR/f39s37693eccDgc7duzAzJkzIRKJsH79ep1d4Hbv3o3AwEA4OjrioYceQlVVVafvu27dOnh5ecHZ2RkvvPAC3nzzzU4tbv/+978RFRUFoVCIyMhI/OMf/9B57bT/Fhs3bkR4eDgEAgECAwOxfv16zedFRUV47LHHIJFI4ObmhlmzZiE/P1/zufre/OCDD+Dr6wt3d3csXLgQcrkcQFv3zoKCArz22mua+0L7mu/fvx9Dhw6FQCBAYWEhpFIpli1bBn9/f4hEIiQkJODEiRPdfgdCCDEKRgghxGgmTZrEXn31VcYYY3v27GHOzs7swIEDms+ffvpp5uzszBYuXMgyMjLYZ599xgCwxMREtn79epaVlcXeffddZm9vz4qKiro8z7p161hkZCT7/fffWU5ODtu1axcTCATsxIkTjDHGZDIZW716NUtKSmK5ubnsq6++Yo6Ojmzfvn2MMcbkcjkTi8Vs2bJlLDs7m12/fp3t3r2bFRQUsObmZrZ06VI2bNgwVlJSwkpKSlhzc7POdGzZsoW5uLiwvXv3soyMDLZ8+XJmb2/PsrKyGGOMlZSUsGHDhrGlS5eykpIS1tDQ0OkYVVVVjMPhsPfee6/H6/vhhx+yY8eOsby8PHb06FEWERHBFixYoPl8165dzN7enk2bNo2lpqaykydPMnd3dzZ9+nT22GOPsWvXrrEDBw4wPp/PvvnmG81+QUFBzNnZmW3YsIFlZmaybdu2MR6Pxw4dOqTZBgDz8vJin3/+OcvJyWEFBQXs+PHjDACrqalhjDF2/vx5xuVy2d///neWmZnJtm7dyiQSCROLxZrjfPXVV0woFLLPP/+cZWZmsnfeeYe5uLiwmJiYdtv4+vqyH374geXm5rIffviBubm5sd27d3d5bZYvX85cXV3Z7t27WXZ2Nvvjjz/Yp59+yhhrux+ioqLYc889x65cucKuX7/OnnjiCRYREcGkUiljrO3edHFxYS+99BK7ceMGO3DgAHN0dGT/+te/NH+nQYMGsbVr12ruC+1rPn78eHbmzBmWkZHBmpqa2AsvvMDGjx/PTp06xbKzs9mmTZuYQCDQ3BuEEGIqFAARQogRqQOgTz75hInFYk1Aovb000+zoKAgplQqNe9FRESwiRMnal4rFAomEonY3r17dZ6jtbWVOTo6srNnz7Z7//nnn2dz587tMm0LFy5ks2fPZoy1FWYBdEqf2po1a9oVyLvi5+fH1q9f3+690aNHs5dfflnzOiYmhq1Zs6bLY1y4cIEBYD/++GOP5+vou+++Y+7u7prXu3btYgBYdna25r358+czR0fHdsFXYmIimz9/vuZ1UFAQu+eee9ode86cOezee+/VvAbAFi9e3G6bjgHQ3Llz2X333dfpONoBUEJCAlu4cGG7bSZMmNDueoeFhbGvv/663TbvvvsuGzdunK7LwOrr65lAINAEPB19+eWXLCIigqlUKs17UqmUOTg4sIMHDzLG/rw3FQqFZptHH32UzZkzR/M6KCiIffjhh+2Orb7maWlpmvcKCgoYj8djxcXF7badMmUKW7Fihc40EkKIsdAYIEIIMbLvv/8e5eXlOHPmDEaPHt3p82HDhrUbH+Ht7Y3hw4drXvN4PLi7u6O8vFzn8bOzs9Hc3Ixp06a1e18mkyE2Nlbzevv27fj8889RWFiIlpYWyGQyTTcrNzc3PPPMM0hMTMS0adMwdepUPPbYY/D19e3196yvr8ft27cxYcKEdu9PmDABly9f7vVxGGO93vbIkSPYsGEDMjIyUF9fD4VCgdbWVjQ3N8PR0REA4OjoiLCwMM0+3t7eCA4OhpOTU7v3Ol7fcePGdXrdccB/fHx8t+m7ceMGHnrooU7H0e4WmJmZiZdffrndNmPGjMGxY8cAAE1NTcjJycHzzz+PF198UbONQqHocrKFGzduQCqVYsqUKTo/v3z5MrKzs+Hs7Nzu/dbWVuTk5GheDxs2DDweT/Pa19cX6enp3X1lAACfz8eIESM0r9PT06FUKjFkyJB220mlUri7u/d4PEIIMSQKgAghxMhiY2ORmpqKzz//HPHx8ZqxEmr29vbtXnM4HJ3vqVQqncdXj3n55Zdf4O/v3+4zgUAAAPjmm2+wbNkybN68GePGjYOzszM2bdqECxcuaLbdtWsXFi1ahN9//x379u3DypUrcfjwYYwdO1a/L66nwYMHg8Ph9DjRQX5+Ph544AEsWLAA69evh5ubG06fPo3nn38eMplMEwD19/p2RyQS9XmfvlL/fT/99FMkJCS0+0w7ONHm4ODQ4zHj4uI0Y9G0eXp6av5f3+vk4ODQ7j5vbGwEj8dDSkpKpzRrB6KEEGIKFAARQoiRhYWFYfPmzZg8eTJ4PB4++eQTgx5fe6D5pEmTdG5z5swZjB8/vl1Lg3ZNv1psbCxiY2OxYsUKjBs3Dl9//TXGjh0LPp/faZKAjlxcXODn54czZ860S8eZM2cwZsyYXn8fNzc3JCYmYvv27Vi0aFGnIKO2thYSiQQpKSlQqVTYvHmzpgXt22+/7fV5eqI92YL6dVRUVJ+OERUV1S7I1HXciIgIJCUl4amnntK8l5SUpPl/b29v+Pn5ITc3F/PmzevVeQcPHgwHBwccPXoUL7zwQqfPR40ahX379sHLywsuLi59+Urt9Oa+ANruK6VSifLyckycOFHv8xFCiCHQLHCEEGICQ4YMwfHjx/HDDz8YfGFUZ2dnLFu2DK+99hq++OIL5OTkIDU1FR9//DG++OILAG0F4uTkZBw8eBBZWVlYtWpVu0J2Xl4eVqxYgXPnzqGgoACHDh3CzZs3NQX+4OBg5OXlIS0tDZWVlZBKpTrT8vrrr+Pvf/879u3bh8zMTLz55ptIS0vDq6++2qfvtH37diiVSowZMwY//PADbt68iRs3bmDbtm2armnh4eGQy+X4+OOPkZubiy+//BI7d+7U5xLqdObMGWzcuBFZWVnYvn07vvvuuz5/D3WL2gcffICbN2/ik08+adf9DQBeeeUVfPbZZ/jiiy9w8+ZNrFu3DleuXGnXgvLOO+9gw4YN2LZtG7KyspCeno5du3Zhy5YtOs8rFArxxhtvYPny5fjPf/6DnJwcnD9/Hp999hkAYN68efDw8MCsWbPwxx9/IC8vDydOnMCiRYtw69atXn+/4OBgnDp1CsXFxaisrOxyuyFDhmDevHl46qmn8OOPPyIvLw8XL17Ehg0b8Msvv/T6fIQQYggUABFCiIlERETg2LFj2Lt3L5YuXWrQY7/77rtYtWoVNmzYgKioKNxzzz345ZdfEBISAgCYP38+Hn74YcyZMwcJCQmoqqpq1xrk6OiIjIwMzZTTf/3rX7Fw4ULMnz8fADB79mzcc889uOuuu+Dp6Ym9e/fqTMeiRYuwZMkSLF26FNHR0fj999+xf/9+DB48uE/fJzQ0FKmpqbjrrruwdOlSDB8+HNOmTcPRo0exY8cOAEBMTAy2bNmCv//97xg+fDj27NmDDRs26HP5dFq6dCmSk5MRGxuLdevWYcuWLUhMTOzTMcaOHYtPP/0UW7duRUxMDA4dOoSVK1e222bevHlYsWIFli1bhlGjRiEvLw/PPPMMhEKhZpsXXngB//73v7Fr1y5ER0dj0qRJ2L17t+bvq8uqVauwdOlSrF69GlFRUZgzZ45mnJOjoyNOnTqFwMBAPPzww4iKisLzzz+P1tbWPrUIrV27Fvn5+QgLC2vXdU6XXbt24amnnsLSpUsRERGBBx98EElJSQgMDOz1+QghxBA4rC+jTQkhhBBidNOmTYOPjw++/PJLcyeFEEKsDo0BIoQQQsyoubkZO3fuRGJiIng8Hvbu3YsjR47g8OHD5k4aIYRYJWoBIoQQQsyopaUFM2bMwKVLl9Da2oqIiAisXLkSDz/8sLmTRgghVokCIEIIIYQQQojNoEkQCCGEEEIIITaDAiBCCCGEEEKIzaAAiBBCCCGEEGIzKAAihBBCCCGE2AwKgAghhBBCCCE2gwIgQgghhBBCiM2gAIgQQgghhBBiMygAIoQQQgghhNiM/w9SwiMgS3Ai2gAAAABJRU5ErkJggg==",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "open_urns = set(gias[\"urn\"].drop_nulls().cast(pl.Int64).to_list())\n",
+ "rated = sc.classify_good_plus_schools(ofsted, open_urns=open_urns).with_columns(\n",
+ " pl.col(\"category\").str.split(\"_\").list.get(1).alias(\"phase\")\n",
+ ")\n",
+ "rated_radii = rated.join(model_radii, on=[\"urn\", \"phase\"], how=\"inner\").join(\n",
+ " supply.select(\"urn\", \"lat\", \"lng\"), on=\"urn\", how=\"inner\"\n",
+ ")\n",
+ "\n",
+ "centre = sc._project_lat_lng_km(np.array([52.205]), np.array([0.119]), origin_lat)[0]\n",
+ "half = 12.0 # km\n",
+ "\n",
+ "fig, ax = plt.subplots(figsize=(8.5, 8.5))\n",
+ "in_box = (np.abs(demand_xy[:, 0] - centre[0]) < half) & (np.abs(demand_xy[:, 1] - centre[1]) < half)\n",
+ "ax.scatter(demand_xy[in_box, 0] - centre[0], demand_xy[in_box, 1] - centre[1],\n",
+ " s=1, c=\"0.8\", rasterized=True, label=\"postcodes\")\n",
+ "\n",
+ "prim = rated_radii.filter(pl.col(\"phase\") == \"primary\")\n",
+ "sxy = sc._project_lat_lng_km(prim[\"lat\"].to_numpy(), prim[\"lng\"].to_numpy(), origin_lat)\n",
+ "for x, y, r, cat in zip(sxy[:, 0] - centre[0], sxy[:, 1] - centre[1],\n",
+ " prim[\"radius_km\"], prim[\"category\"]):\n",
+ " if abs(x) < half and abs(y) < half:\n",
+ " colour = \"tab:purple\" if cat == \"outstanding_primary\" else \"tab:green\"\n",
+ " ax.add_patch(plt.Circle((x, y), r, fill=False, color=colour, lw=1.2, alpha=0.8))\n",
+ " ax.plot(x, y, \".\", color=colour, ms=5)\n",
+ "ax.plot([], [], color=\"tab:green\", label=\"Good primary catchment\")\n",
+ "ax.plot([], [], color=\"tab:purple\", label=\"Outstanding primary catchment\")\n",
+ "ax.set(xlim=(-half, half), ylim=(-half, half), xlabel=\"km east of Cambridge centre\",\n",
+ " ylabel=\"km north\", title=\"Modelled primary catchments around Cambridge\")\n",
+ "ax.set_aspect(\"equal\"); ax.legend(loc=\"upper left\", fontsize=8)\n",
+ "fig.tight_layout()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25770af8",
+ "metadata": {},
+ "source": [
+ "## 6. Calibration — modelled vs published cutoffs\n",
+ "\n",
+ "Councils publish each school's **last distance offered** in their allocation reports. We scraped\n",
+ "783 rows from nine authorities (Hertfordshire, Surrey, Stockport, Manchester, Bristol, Barnet,\n",
+ "Redbridge, Ealing, Lambeth — `property-data/ground_truth/`), matched them to GIAS URNs, and\n",
+ "compare against the modelled radii. Faith schools are reported separately: their published\n",
+ "cutoff applies *within* faith priority, which a postcode model cannot see. \"All applicants\n",
+ "offered\" schools test whether the model agrees there was no binding cutoff at all.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "c0174356",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:57:02.492282Z",
+ "iopub.status.busy": "2026-06-10T18:57:02.492183Z",
+ "iopub.status.idle": "2026-06-10T18:57:04.229239Z",
+ "shell.execute_reply": "2026-06-10T18:57:04.228777Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Ground truth rows: 783 from /volumes/projects/perfect-postcode/property-data/ground_truth\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Matched 648/767 ground-truth rows to GIAS URNs (postcode 0, exact name 566, stripped 65, fuzzy 17)\n",
+ "primary : n=225 median bias x0.95 median error x1.57 within x2: 70% Spearman 0.24\n",
+ "secondary: n= 75 median bias x0.99 median error x1.45 within x2: 71% Spearman 0.32\n",
+ "all-offered: n=295 model agrees no binding cutoff for 57%\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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CJFMJNj5lmjBDkDlHow5HgnKZDKRnZ1NW1sbWVlZ3rdbrVaqqqooLS0lNTXVO+cumnJzc2WQcQLr+9+MEELEEwl2vsVbsGtvb2fcuHH9cklf0i2rAI1GQ25urtxQIYQQIuFIsPMt3oJdKCTcKUTClxBCiEQjwc63RA52IGfuhBBCiCFJgp1viR7sQMKdEEIIMeRIsPMtGYIdSLgTQgghhhQJdr4lS7ADCXdCCCHEkCHBzrdkCnYg4W7Iev311/nRj34U62X08sQTT3jvx1XCVVddxaRJk5g1axaLFi1iy5Yt3vf9/Oc/Z/LkycyaNYt58+bx9ttve9935513UlhYyOzZs5k9ezaXX355rzXed999iq1RCCEGiwQ735It2IHMuRtQsHPuEo3D4Qj6OrJweW6Z8NwTG4jFYmHatGl88cUXpKenK7KG119/nbPOOgutVsubb77JDTfcwMGDBwF46623WLx4MQaDgc8//5yTTz6Zuro60tPTufPOO2ltbeXRRx/t95xdXV1MmTKFbdu2kZ2dHfRaEv2/GSFEYpNg51uiBbtg59xJ5W6QNVmaeG3/a/y+/Pe8tv81mixNij23SqXitttuY86cOUyaNIm//vWvvd53xx13MH/+fH72s5/x/PPPc9555wHw/vvvM336dP7v//6PmTNnMmPGDHbs2MF3v/tdZsyYwXHHHcfhw4cB+OKLLzjxxBM59thjmTp1Kvfcc4/3Ne68805WrFjBGWecwfTp0/nzn//M6aef7n2/0+lkzJgx7N69u9/a//nPf7Jo0SJvsHv++edZtmwZl156KTNmzGDevHlUVlaG9PU455xzvCH2+OOP5/Dhw967cs8880wMBgMAM2bMwO12e687G0hKSgqnn346L774YkhrEUKIWJFg51uiBbtQSLgbRE2WJh7b9hgvV7zMZ0c+4+WKl3ls22OKB7zt27ezbt06vv/973srVdA9i2/Lli08+OCD/T5uz549XHvttezYsYPzzjuPpUuXcsstt/DFF18wb948bxVr7NixrF+/nm3btrF161ZeeeUVPvnkE+/zbN68mT/96U/s3r2bb3/721RUVLB3716gu5I2YcIEpk6d2u/133///V5XkQFs2bKF++67jy+++IJly5bx61//Guj+PxfPlmnff8444wyfX5fHHnvMW8Xr67nnnmPcuHGMGTPG+7aX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lfFQk9LSUqWmplZ5TwoLC7Vt2zb95je/qetT9Yk/68TFIRnMdalVq1Y6fvx4telxcXHKzs7WokWLtH//fsv7Xvw+eDwey/eloqKiyrRvvvlGktS6det6jRuoD+IOcMi//vWvKn+vWrVKV111lRo0aCDpvx8OF+8J2rFjh+XegIs//C5cuKC1a9cqIyPD53FcffXV+vWvf62lS5dq2LBhmjNnjk/3S0lJUVpamnbv3q0rr7yyyr/LL79ckpSRkaENGzbo22+/tRyvL9auXauKigpNnz5d1157ra6++modOHCg2nzp6ekaPXq03n33Xd1///168cUXJUmxsbGSZLtHNCMjw/I9sdOzZ09NmjRJX3zxhbp166bXX3/d+7iXPqavz6U2CQkJ6tixY42XZPnud7+rQ4cOqWHDhtXel1atWlneJyMjQ0VFRSorK/NO+/zzzxUTE6POnTv7PDZf1om63q+u61LPnj21efPmatNjYmL06quvqlevXho4cKDf70NNNm3apHbt2tX4WgOhQNwBDtm3b58mTpyobdu26Y033tCsWbM0YcIE7+2DBg3S7NmztX79en355ZcaPXp0tT0HkpSfn6+FCxdq69atGjNmjI4fP66f/exnto9/5swZjR07VgUFBdq7d68+//xzrVmzxq8wnDp1qqZNm6ann35a27dv18aNGzVnzhzNmDFDkjR8+HB5PB6NGjVKmzdv1ocffqgnnnjC5+VL0pVXXqnz589r1qxZ2r17t1599VU999xzVeb51a9+pSVLlqi4uFjr1q3TihUrvM+jQ4cO8ng8WrRokb7++usqZ6ZebPz48froo4/0xBNPaMeOHZo9e7Y++uijGsdVXFysSZMmaeXKldq7d6+WLl2qHTt2eB+3Y8eOKi4uVmFhoY4ePaqzZ8/69Fx8MWXKFE2fPl1PP/20duzYoXXr1nn3VGZnZ6tfv34aMmSIli5dqj179uiLL77QQw89VO1M6Ep33nmn4uPjlZeXp02bNmnFihUaN26cRowYoZSUFL/GZrdO1PV+dV2XcnJytHLlSsu4b9CggebNm6fMzEwNGjRIhw4d8uu5Wvnss89044031ns5QL04fMwfEJX69+9v7rvvPjN69GiTmJhoWrRoYX7/+99XOah7//795sYbbzRNmzY1V111lfnwww8tT6h4/fXXTVZWlomNjTVdunQxH3/8sXcZtZ1QcfbsWXPHHXeY9PR0Exsba9LS0szYsWO9B6xfevKFMdYH0M+bN8/06NHDxMbGmhYtWpjvf//75t133/XevnLlSpOZmWliY2NNjx49vGev+nNCxYwZM0xqaqpp3LixycnJMa+88kqV5zV27FjTqVMnExcXZ1q3bm1GjBjhPWPYGGMeeeQR06ZNG+PxeLxngFr529/+Ztq1a2caN25scnNzzRNPPFHjCRWHDh0yQ4YMMampqSY2NtZ06NDBPPzww6a8vNwYY8y3335rbrnlFtO8eXMjyfu+2T0XX1/35557znTu3Nk0atTIpKammnHjxnlvO3XqlBk3bpxJS0szjRo1Munp6ebOO+80+/btq/G5b9iwwQwcONDEx8ebli1bmlGjRpmSkpJax2A1zZja14nK9dbq/Q/GunT+/HmTlpZmPvroI++0S1/j8+fPm2HDhpmMjAxz+PBhy5NwrN6XS0+aOXPmjElKSjIrV66scTxAKHiMueSgHgBBN2DAAPXo0cMVP7EFRLr8/Hz94x//0JIlS4L6OM8++6wWLlyopUuXBvVxADsNnR4AAADBdO+99+rEiRMqKSkJ6k+QNWrUKCQn8gB22HMHOIA9dwCAYCHuAAAAIghnywIAAEQQ4g4AACCCEHcAAAARhLgDAACIIMQdAABABCHuAAAAIghxBwAAEEGIOwAAgAhC3AEAAESQ/wMiKeTC19cGmAAAAABJRU5ErkJggg==",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from pipeline.check_school_cutoffs import load_ground_truth, match_schools\n",
+ "\n",
+ "truth = (\n",
+ " load_ground_truth(DATA / \"ground_truth\")\n",
+ " .sort(\"entry_year\", descending=True)\n",
+ " .unique(subset=[\"school_name\", \"la\", \"phase\"], keep=\"first\")\n",
+ ")\n",
+ "gias_match = gias.select(\"urn\", \"name\", \"postcode\", \"local_authority\")\n",
+ "matched = match_schools(truth, gias_match).join(\n",
+ " gias.select(\"urn\", \"religious_character\"), on=\"urn\", how=\"left\"\n",
+ ").with_columns(\n",
+ " pl.when(pl.col(\"religious_character\").is_not_null())\n",
+ " .then(~pl.col(\"religious_character\").is_in([\"None\", \"Does not apply\"]))\n",
+ " .otherwise(pl.col(\"faith_school\"))\n",
+ " .alias(\"faith_school\")\n",
+ ")\n",
+ "# Select only the model columns we need: both frames carry a `cutoff_km`\n",
+ "# column (published vs raw model), and a bare join would shadow the truth.\n",
+ "joined = matched.join(\n",
+ " model_radii.select(\"urn\", \"phase\", \"filled\", \"radius_km\"),\n",
+ " on=[\"urn\", \"phase\"],\n",
+ " how=\"inner\",\n",
+ ")\n",
+ "binding = joined.filter(~pl.col(\"all_offered\") & pl.col(\"cutoff_km\").is_between(0.05, 20.0))\n",
+ "\n",
+ "fig, ax = plt.subplots(figsize=(6.4, 6.4))\n",
+ "lim = (0.1, 22)\n",
+ "ax.plot(lim, lim, \"k-\", lw=1, label=\"y = x\")\n",
+ "ax.fill_between(lim, [v / 2 for v in lim], [v * 2 for v in lim], color=\"0.92\",\n",
+ " label=\"within factor 2\")\n",
+ "for phase, colour in [(\"primary\", \"tab:green\"), (\"secondary\", \"tab:blue\")]:\n",
+ " sub = binding.filter((pl.col(\"phase\") == phase) & ~pl.col(\"faith_school\"))\n",
+ " ax.scatter(sub[\"cutoff_km\"].to_numpy(), sub[\"radius_km\"].to_numpy(),\n",
+ " s=14, alpha=0.6, color=colour, label=f\"{phase} (n={len(sub)})\")\n",
+ "sub = binding.filter(pl.col(\"faith_school\"))\n",
+ "ax.scatter(sub[\"cutoff_km\"].to_numpy(), sub[\"radius_km\"].to_numpy(),\n",
+ " s=14, alpha=0.5, color=\"tab:orange\", marker=\"^\", label=f\"faith (n={len(sub)})\")\n",
+ "ax.set(xscale=\"log\", yscale=\"log\", xlim=lim, ylim=lim,\n",
+ " xlabel=\"published last distance offered (km)\", ylabel=\"modelled cutoff radius (km)\")\n",
+ "ax.set_aspect(\"equal\"); ax.legend(fontsize=8); fig.tight_layout()\n",
+ "\n",
+ "for phase in (\"primary\", \"secondary\"):\n",
+ " sub = binding.filter((pl.col(\"phase\") == phase) & ~pl.col(\"faith_school\"))\n",
+ " lr = np.log2(sub[\"radius_km\"].to_numpy() / sub[\"cutoff_km\"].to_numpy())\n",
+ " rho = pl.DataFrame({\"t\": sub[\"cutoff_km\"], \"m\": sub[\"radius_km\"]}).select(\n",
+ " pl.corr(\"t\", \"m\", method=\"spearman\")).item()\n",
+ " print(\n",
+ " f\"{phase:9s}: n={len(sub):3d} median bias x{2 ** np.median(lr):.2f} \"\n",
+ " f\"median error x{2 ** np.median(np.abs(lr)):.2f} \"\n",
+ " f\"within x2: {np.mean(np.abs(lr) <= 1):.0%} Spearman {rho:.2f}\"\n",
+ " )\n",
+ "offered = joined.filter(pl.col(\"all_offered\"))\n",
+ "print(\n",
+ " f\"all-offered: n={len(offered)} model agrees no binding cutoff for \"\n",
+ " f\"{float((~offered['filled']).mean()):.0%}\"\n",
+ ")\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e8cdf18b",
+ "metadata": {},
+ "source": [
+ "## 7. How the parameters were chosen\n",
+ "\n",
+ "Each lever was swept against the ground truth (`--demand-scale`, `--choice-temperature-km`,\n",
+ "`--bonus-*-km` + `--schools-output`, evaluated with `check_school_cutoffs.py`):\n",
+ "\n",
+ "| configuration | primary bias | primary within ×2 | primary Spearman | undersubscribed agreement |\n",
+ "|---|---|---|---|---|\n",
+ "| deterministic, raw census demand | ×0.54 | 54% | 0.31 | 12% |\n",
+ "| + demand scale 0.8 | ×0.55 | 54% | 0.24 | ~30% |\n",
+ "| + logit τ=0.3 | ×0.69 | 64% | 0.24 | 49% |\n",
+ "| logit τ=0.6 | ×0.77 | 60% | **0.07** | 57% |\n",
+ "| logit τ=1.0 | ×0.76 | 58% | **0.01** | 60% |\n",
+ "| τ=0.3 + residual factors 1.44/1.28 | **×1.00** | **70%** | 0.24 | 49% |\n",
+ "\n",
+ "Three findings drove the final settings:\n",
+ "\n",
+ "1. **Deterministic choice is ~2× too tight.** If everyone applies to their nearest school, the\n",
+ " marginal admitted child lives implausibly close. Logit smearing fixes the level…\n",
+ "2. **…but too much smearing erases school identity.** Above τ≈0.6 every school's cutoff drifts\n",
+ " toward the local average and rank correlation collapses. τ=0.3 keeps both.\n",
+ "3. **The residual is a clean multiplicative bias** (offer-day frictions, waiting-list churn,\n",
+ " furthest-applicant noise), so it is removed with explicit per-phase factors\n",
+ " (2^0.53≈1.44 primary, 2^0.36≈1.28 secondary) that shift the median without touching ranking.\n",
+ "\n",
+ "Published cutoffs themselves swing ±50% year on year, so ~×1.4–1.6 median error against a single\n",
+ "year's snapshot is close to the noise floor.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "93781235",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-06-10T18:57:04.230987Z",
+ "iopub.status.busy": "2026-06-10T18:57:04.230879Z",
+ "iopub.status.idle": "2026-06-10T18:57:04.652122Z",
+ "shell.execute_reply": "2026-06-10T18:57:04.648825Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "
shape: (5, 5) postcode good_primary_catchments good_secondary_catchments outstanding_primary_catchments outstanding_secondary_catchments str i32 i32 i32 i32 "CB2 1TN" 6 3 0 1 "M14 5RB" 4 4 0 0 "N1 1QN" 7 1 1 0 "SW1A 1AA" 2 3 0 1 "YO62 4LF" 2 3 0 2
"
+ ],
+ "text/plain": [
+ "shape: (5, 5)\n",
+ "┌──────────┬─────────────────────┬─────────────────────┬─────────────────────┬─────────────────────┐\n",
+ "│ postcode ┆ good_primary_catchm ┆ good_secondary_catc ┆ outstanding_primary ┆ outstanding_seconda │\n",
+ "│ --- ┆ ents ┆ hments ┆ _catchments ┆ ry_catchmen… │\n",
+ "│ str ┆ --- ┆ --- ┆ --- ┆ --- │\n",
+ "│ ┆ i32 ┆ i32 ┆ i32 ┆ i32 │\n",
+ "╞══════════╪═════════════════════╪═════════════════════╪═════════════════════╪═════════════════════╡\n",
+ "│ CB2 1TN ┆ 6 ┆ 3 ┆ 0 ┆ 1 │\n",
+ "│ M14 5RB ┆ 4 ┆ 4 ┆ 0 ┆ 0 │\n",
+ "│ N1 1QN ┆ 7 ┆ 1 ┆ 1 ┆ 0 │\n",
+ "│ SW1A 1AA ┆ 2 ┆ 3 ┆ 0 ┆ 1 │\n",
+ "│ YO62 4LF ┆ 2 ┆ 3 ┆ 0 ┆ 2 │\n",
+ "└──────────┴─────────────────────┴─────────────────────┴─────────────────────┴─────────────────────┘"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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FBERERFRfVQuZE1MTHDw4MFq7QcPHoSxsbFaQhERERER1UflqxaMGzcOo0ePRmZmJoKDgwEAhw4dwtdff41p06apPSARERERUU1ULmQ/+OADeHp6YtGiRfjuu+8AAG3btkVCQgIGDBig9oBERERERDVp0HVkBwwYwKKViIiIiLSqwTdEyMjIwJkzZwAAvr6+CAwMVFsoIiIiIqL6qFzI5ufnY9CgQdi7dy+sra0BAIWFhXjppZewfv162NnZqTsjEREREVE1Kl+14L333sODBw9w6tQp3L17F3fv3sXJkydRVFSE2NhYTWQkIiIiIqpG5RHZpKQk7NmzB23btpXa2rVrh6VLlyIyMlKt4YiIiIiIaqPyiGxlZSUMDQ2rtRsaGqKyslItoYiIiIiI6qNyIfvyyy9j7NixuH79utR27do1jB8/HuHh4WoNR0RERERUG5UL2SVLlqCoqAju7u7w8vKCl5cXPDw8UFRUhMWLF2siIxERERFRNSrPkXV1dUVmZib27NmDs2fPAvjzhggRERFqD0dEREREVJsGXUdWJpOhe/fu6N69u7rzEBEREREpReWpBbGxsfjiiy+qtS9ZsgTjxo1TRyYiIiIionqpXMhu3LgRYWFh1dpDQ0Px448/qiUUEREREVF9VC5kCwoKYGVlVa3d0tISd+7cUUsoIiIiIqL6qDxH1tvbG0lJSRgzZoxC+86dO+Hp6am2YER/V35r/Bq1vxPRJxq1PyIiInVRuZCNi4vDmDFjcPv2bbz88ssAgJSUFHz22WdYuHChuvMREREREdVI5UJ2+PDhKC0txZw5czB79mwAgLu7O5YtW4Y333xT7QGJiIiIiGrSoMtvjR49GqNHj8bt27dhYmICc3NzdeciIiIiIqpTg25RW1hYCACws7OTitiioiJpqgERERERkaapXMju3bsXZWVl1dofPXqE3377TS2hiIiIiIjqo/TUguPHj0v/Pn36NG7evCktV1RUICkpCS1atFBvOiIiIiKiWihdyLZv3x4ymQwymazGKQQmJiZYvHixWsMREREREdVG6UI2NzcXQgh4enoiPT0ddnZ20jojIyPY29tDX19fIyGJiIiIiJ6mdCHr5uYGAKisrNRYGCIiIiIiZal8steaNWuwfft2aXnSpEmwtrZGaGgoLl++rNZwRERERES1UbmQnTt3LkxMTAAAqampWLJkCebPn4/mzZtj/Pjxag9IRERERFQTlW+IkJeXB29vbwDAli1b8Prrr+Ptt99GWFgYunXrpu58REREREQ1UnlE1tzcHAUFBQCA3bt3o3v37gAAY2Nj/PHHH+pNR0RERERUC5VHZLt3746RI0ciMDAQ586dwyuvvAIAOHXqFNzd3dWdj4iIiIioRiqPyC5duhQhISG4ffs2Nm7cCFtbWwBARkYGBg8erPaAREREREQ1UXlE1traGkuWLKnWPnPmTLUEIiIiIiJShsqFLAAUFhZi9erVOHPmDADA19cXw4cPh5WVlVrDERERERHVRuWpBUeOHIGXlxcWLFiAu3fv4u7du/j888/h5eWFzMxMTWQkIiIiIqpG5RHZ8ePHo0+fPli5ciUMDP58enl5OUaOHIlx48Zh//79ag9JRERERPQ0lQvZI0eOKBSxAGBgYIBJkyYhKChIreGIiIiIiGqj8tQCS0tLXLlypVp7Xl4eLCws1BKKiIiIiKg+KheyAwcOxIgRI7Bhwwbk5eUhLy8P69evx8iRI3n5LSIiIiJqNCoXsv/973/Rv39/vPnmm3B3d4e7uzuGDRuG119/HfPmzVNpX/v370fv3r3h7OwMmUyGLVu2KKwfNmwYZDKZwqNnz56qRiYiIiKiZ5DKc2SNjIywaNEixMfHIycnBwDg5eUFU1NTlTsvKSlBQEAAhg8fjv79+9e4Tc+ePZGQkCAty+VylfshIiIiomdPg64jCwCmpqawtraW/t0QUVFRiIqKqnMbuVwOR0fHBu2fiIiIiJ5dKk8tKC8vx7Rp02BlZSVNLbCyssLUqVPx+PFjtQfcu3cv7O3t4ePjg9GjR6OgoKDO7UtLS1FUVKTwICIiIqJnj8ojsu+99x42bdqE+fPnIyQkBACQmpqKGTNmoKCgAMuWLVNbuJ49e6J///7w8PBATk4OPvzwQ0RFRSE1NRX6+vo1Pic+Pp63yyUiIiL6G1C5kF27di3Wr1+vMCXA398frq6uGDx4sFoL2UGDBkn/9vPzg7+/P7y8vLB3716Eh4fX+JzJkycjLi5OWi4qKoKrq6vaMhERERFR06Dy1AK5XA53d/dq7R4eHjAyMlJHplp5enqiefPmuHDhQq3byOVyWFpaKjyIiIiI6NmjciE7ZswYzJ49G6WlpVJbaWkp5syZgzFjxqg13NOuXr2KgoICODk5abQfIiIiImr6VJ5acPToUaSkpMDFxQUBAQEAgGPHjqGsrAzh4eEKl9HatGlTnfsqLi5WGF3Nzc1FVlYWbGxsYGNjg5kzZ+K1116Do6MjcnJyMGnSJHh7e6NHjx6qxiYiIiKiZ4zKhay1tTVee+01hbaGzkE9cuQIXnrpJWm5am5rdHQ0li1bhuPHj2PNmjUoLCyEs7MzIiMjMXv2bF5LloiIiIhUL2SfvDnBX9WtWzcIIWpdv2vXLrX1RURERETPFpXnyBIRERERNQUsZImIiIhIJ7GQJSIiIiKdxEKWiIiIiHQSC1kiIiIi0klKXbXgiy++UHqHsbGxDQ5DRERERKQspQrZBQsWKCzfvn0bDx8+hLW1NQCgsLAQpqamsLe3ZyFLRERERI1CqakFubm50mPOnDlo3749zpw5g7t37+Lu3bs4c+YMnn/+ecyePVvTeYmIiIiIADRgjuy0adOwePFi+Pj4SG0+Pj5YsGABpk6dqtZwRERERES1UbmQvXHjBsrLy6u1V1RU4NatW2oJRURERERUH5UL2fDwcPz73/9GZmam1JaRkYHRo0cjIiJCreGIiIiIiGqjciH79ddfw9HREUFBQZDL5ZDL5QgODoaDgwNWrVqliYxERERERNUoddWCJ9nZ2WHHjh04d+4czp49CwBo06YNWrdurfZwRERERES1UbmQrdK6dWsWr0RERESkNSoXshUVFUhMTERKSgry8/NRWVmpsP6XX35RWzgiIiIiotqoXMiOHTsWiYmJ6NWrF5577jnIZDJN5CIiIiIiqpPKhez69evxww8/4JVXXtFEHiIiIiIipah81QIjIyN4e3trIgsRERERkdJULmT/85//YNGiRRBCaCIPEREREZFSVJ5acODAAfz666/YuXMnfH19YWhoqLB+06ZNagtH6uG3xq/R+zwRfaLR+yQiIqK/F5ULWWtra7z66quayEJEREREpDSVC9mEhARN5CAiIiIiUonKc2SJiIiIiJqCBt3Z68cff8QPP/yAK1euoKysTGFdZmamWoIREREREdVF5RHZL774Am+99RYcHBxw9OhRBAcHw9bWFhcvXkRUVJQmMhIRERERVaNyIfvll19ixYoVWLx4MYyMjDBp0iQkJycjNjYW9+/f10RGIiIiIqJqVC5kr1y5gtDQUACAiYkJHjx4AAAYOnQo1q1bp950RERERES1ULmQdXR0xN27dwEALVu2RFpaGgAgNzeXN0kgIiIiokajciH78ssvY+vWrQCAt956C+PHj0f37t0xcOBAXl+WiIiIiBqNylctWLFiBSorKwEAMTExsLW1xe+//44+ffrg3//+t9oDEhERERHVROVCVk9PD3p6/zeQO2jQIAwaNEitoYiIiIiI6qNUIXv8+HGld+jv79/gMEREREREylKqkG3fvj1kMlm9J3PJZDJUVFSoJRgRERERUV2UKmRzc3M1nYOIiIiISCVKFbJubm6azkFETZDfGr9G7/NE9IlG75OIiHSTyid7VV1662kymQzGxsbw9vaGh4fHXw5GRERERFQXlQvZfv361ThftqpNJpOhc+fO2LJlC5o1a6a2oERERERET1L5hgjJycno2LEjkpOTcf/+fdy/fx/Jycno1KkTtm3bhv3796OgoAATJkzQRF4iIiIiIgANGJEdO3YsVqxYgdDQUKktPDwcxsbGePvtt3Hq1CksXLgQw4cPV2tQIiIiIqInqTwim5OTA0tLy2rtlpaWuHjxIgCgVatWuHPnzl9PR0RERERUC5UL2Q4dOmDixIm4ffu21Hb79m1MmjQJHTt2BACcP38erq6u6ktJRERERPQUlQvZ1atXIzc3Fy4uLvD29oa3tzdcXFxw6dIlrFq1CgBQXFyMqVOn1ruv/fv3o3fv3nB2doZMJsOWLVsU1gsh8NFHH8HJyQkmJiaIiIjA+fPnVY1MRERERM8glefI+vj44PTp09i9ezfOnTsntXXv3h16en/Wxf369VNqXyUlJQgICMDw4cPRv3//auvnz5+PL774AmvWrIGHhwemTZuGHj164PTp0zA2NlY1OhERERE9Q1QuZAFAT08PPXv2RM+ePf9S51FRUYiKiqpxnRACCxcuxNSpU9G3b18AwDfffAMHBwds2bIFgwYN+kt9ExEREZFua1Ahm5KSgpSUFOTn56OyslJh3ddff62WYLm5ubh58yYiIiKkNisrK3Tq1Ampqam1FrKlpaUoLS2VlouKitSSh4iIiIiaFpXnyM6cORORkZFISUnBnTt3cO/ePYWHuty8eRMA4ODgoNDu4OAgratJfHw8rKyspAdPOiMiIiJ6Nqk8Irt8+XIkJiZi6NChmsjzl02ePBlxcXHSclFREYtZIiIiomeQyiOyZWVlCjdD0BRHR0cAwK1btxTab926Ja2riVwuh6WlpcKDiIiIiJ49KheyI0eOxNq1azWRRYGHhwccHR2RkpIitRUVFeHQoUMICQnReP9ERERE1LSpPLXg0aNHWLFiBfbs2QN/f38YGhoqrP/888+V3ldxcTEuXLggLefm5iIrKws2NjZo2bIlxo0bh48//hitWrWSLr/l7Oys9OW9iIiIiOjZpXIhe/z4cbRv3x4AcPLkSYV1MplMpX0dOXIEL730krRcNbc1OjoaiYmJmDRpEkpKSvD222+jsLAQnTt3RlJSEq8hS0RERESqF7K//vqr2jrv1q0bhBC1rpfJZJg1axZmzZqltj6JiIiI6Nmg8hzZuuTn56tzd0REREREtVK6kDU1NcXt27el5V69euHGjRvS8q1bt+Dk5KTedEREREREtVC6kH306JHCNID9+/fjjz/+UNimrmkCRERERETqpNapBaqe7EVERERE1FBqLWSJiIiIiBqL0oWsTCZTGHF9epmIiIiIqDEpffktIQRat24tFa/FxcUIDAyEnp6etJ6IiIiIqLEoXcgmJCRoMgcRERERkUqULmSjo6M1mYOIiIiISCU82YuIiIiIdBILWSIiIiLSSSxkiYiIiEgnsZAlIiIiIp3U4EK2rKwM2dnZKC8vV2ceIiIiIiKlqFzIPnz4ECNGjICpqSl8fX1x5coVAMB7772HTz75RO0BiYiIiIhqonIhO3nyZBw7dgx79+6FsbGx1B4REYENGzaoNRwRERERUW2Uvo5slS1btmDDhg144YUXFG5R6+vri5ycHLWGIyIiIiKqjcojsrdv34a9vX219pKSEoXCloiIiIhIk1QuZIOCgrB9+3Zpuap4XbVqFUJCQtSXjIiIiIioDipPLZg7dy6ioqJw+vRplJeXY9GiRTh9+jR+//137Nu3TxMZiYiIiIiqUXlEtnPnzsjKykJ5eTn8/Pywe/du2NvbIzU1FR06dNBERiIiIiKialQekQUALy8vrFy5Ut1ZiIiIiIiUpvKIbEREBBITE1FUVKSJPERERERESlG5kPX19cXkyZPh6OiIN954Az/99BMeP36siWxERERERLVSuZBdtGgRrl27hi1btsDMzAxvvvkmHBwc8Pbbb/NkLyIiIiJqNCoXsgCgp6eHyMhIJCYm4tatW/jqq6+Qnp6Ol19+Wd35iIiIiIhq1KCTvarcvHkT69evx3fffYfjx48jODhYXbmIiIiIiOqk8ohsUVEREhIS0L17d7i6umLZsmXo06cPzp8/j7S0NE1kJCIiIiKqRuURWQcHBzRr1gwDBw5EfHw8goKCNJGLiIiIiKhOKheyW7duRXh4OPT0GjS9loiIiIhILVQuZLt3766JHEREREREKlGqkH3++eeRkpKCZs2aITAwEDKZrNZtMzMz1RaOiIiIiKg2ShWyffv2hVwuBwD069dPk3mIiIiIiJSiVCE7ffr0Gv9NRERERKQtf+k6skREjclvjV+j93ki+kSj90lERMpRqpBt1qxZnfNin3T37t2/FIiIiIiISBlKFbILFy7UcAwiIiIiItUoVchGR0drOgcRERERkUp4VwMiIiIi0kksZImIiIhIJ7GQJSIiIiKdpFQhe/z4cVRWVmo6SzUzZsyATCZTeLRp06bRcxARERFR06NUIRsYGIg7d+4AADw9PVFQUKDRUE/y9fXFjRs3pMeBAwcarW8iIiIiarqUumqBtbU1cnNzYW9vj0uXLjXq6KyBgQEcHR0brT8iIiIi0g1KFbKvvfYaunbtCicnJ8hkMgQFBUFfX7/GbS9evKjWgOfPn4ezszOMjY0REhKC+Ph4tGzZstbtS0tLUVpaKi0XFRWpNQ8RERERNQ1KFbIrVqxA//79ceHCBcTGxmLUqFGwsLDQdDZ06tQJiYmJ8PHxwY0bNzBz5kx06dIFJ0+erLX/+Ph4zJw5U+PZiIiIiEi7lCpkAaBnz54AgIyMDIwdO7ZRCtmoqCjp3/7+/ujUqRPc3Nzwww8/YMSIETU+Z/LkyYiLi5OWi4qK4OrqqvGsRERERNS4lC5kqyQkJEj/vnr1KgDAxcVFfYnqYG1tjdatW+PChQu1biOXyyGXyxslDxERERFpj8rXka2srMSsWbNgZWUFNzc3uLm5wdraGrNnz9b4SWDFxcXIycmBk5OTRvshIiIioqZP5RHZKVOmYPXq1fjkk08QFhYGADhw4ABmzJiBR48eYc6cOWoLN2HCBPTu3Rtubm64fv06pk+fDn19fQwePFhtfRARERGRblK5kF2zZg1WrVqFPn36SG3+/v5o0aIF3n33XbUWslevXsXgwYNRUFAAOzs7dO7cGWlpabCzs1NbH0RERESkm1QuZO/evVvj3bXatGmDu3fvqiVUlfXr16t1f0RERET07FB5jmxAQACWLFlSrX3JkiUICAhQSygiIiIiovqoPCI7f/589OrVC3v27EFISAgAIDU1FXl5edixY4faAxIRERER1UTlEdmuXbvi3LlzePXVV1FYWIjCwkL0798f2dnZ6NKliyYyEhERERFVo/KILAA4Ozur9aQuIiIiIiJVqTwiS0RERETUFLCQJSIiIiKdxEKWiIiIiHQSC1kiIiIi0kkNOtmryp07d3Do0CFUVFSgY8eOcHJyUlcuIiIiIqI6NbiQ3bhxI0aMGIHWrVvj8ePHyM7OxtKlS/HWW2+pMx8RERERUY2UnlpQXFyssDxz5kykp6cjPT0dR48exf/+9z9MmTJF7QGJiIiIiGqidCHboUMH/PTTT9KygYEB8vPzpeVbt27ByMhIvemIiIiIiGqh9NSCXbt2ISYmBomJiVi6dCkWLVqEgQMHoqKiAuXl5dDT00NiYqIGo+oOvzV+jd7niegTjd4n0d8df9aJiLRL6ULW3d0d27dvx7p169C1a1fExsbiwoULuHDhAioqKtCmTRsYGxtrMisRERERkUTly28NHjwYhw8fxrFjx9CtWzdUVlaiffv2LGKJiIiIqFGpdNWCHTt24MyZMwgICMCqVauwb98+DBkyBFFRUZg1axZMTEw0lZOIiIiISIHSI7L/+c9/8NZbb+Hw4cP497//jdmzZ6Nr167IzMyEsbExAgMDsXPnTk1mJSIiIiKSKF3IJiYmYseOHVi/fj0OHz6Mb7/9FgBgZGSE2bNnY9OmTZg7d67GghIRERERPUnpQtbMzAy5ubkAgLy8vGpzYtu1a4fffvtNvemIiIiIiGqhdCEbHx+PN998E87OzujatStmz56tyVxERERERHVS+mSvIUOGoGfPnrh48SJatWoFa2trDcYiIiIiIqqbSlctsLW1ha2traayEBEREREpTeXryBIRERERNQUsZImIiIhIJ7GQJSIiIiKdxEKWiIiIiHQSC1kiIiIi0kksZImIiIhIJ7GQJSIiIiKdxEKWiIiIiHSSSjdEICKipstvjV+j93ki+kSj90lEVIUjskRERESkk1jIEhEREZFOYiFLRERERDqJhSwRERER6SQWskRERESkk1jIEhEREZFOYiFLRERERDqJhSwRERER6STeEIGIiDSCN2ggIk3jiCwRERER6SSdKGSXLl0Kd3d3GBsbo1OnTkhPT9d2JCIi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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "counts = pl.read_parquet(DATA / \"school_catchments.parquet\")\n",
+ "live_counts = live.select(\"postcode\").join(counts, on=\"postcode\", how=\"inner\")\n",
+ "\n",
+ "fig, ax = plt.subplots(figsize=(7, 3.2))\n",
+ "vals = live_counts[\"good_primary_catchments\"].value_counts().sort(\"good_primary_catchments\")\n",
+ "ax.bar(vals[\"good_primary_catchments\"], vals[\"count\"] / len(live_counts) * 100, color=\"tab:green\")\n",
+ "ax.set(xlabel=\"Good+ primary catchments covering the postcode\",\n",
+ " ylabel=\"% of live England postcodes\", xticks=range(0, 16))\n",
+ "fig.tight_layout()\n",
+ "\n",
+ "examples = [\"N1 1QN\", \"CB2 1TN\", \"YO62 4LF\", \"M14 5RB\", \"SW1A 1AA\"]\n",
+ "counts.filter(pl.col(\"postcode\").is_in(examples))\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b4a6c49e",
+ "metadata": {},
+ "source": [
+ "## 8. Limitations\n",
+ "\n",
+ "- **Faith admissions are not modelled** — whether a faith school's catchment is open to a given\n",
+ " family depends on the family. Their fit is accordingly worse (the orange triangles above).\n",
+ "- **Cutoffs are single-year snapshots**; real ones move with each cohort. The model is a\n",
+ " steady-state estimate, not this September's number.\n",
+ "- **Straight-line distance** is used throughout — it is the modal LA tie-break, but some\n",
+ " authorities measure walking routes, and none of sibling priority, feeder schools or\n",
+ " designated catchment polygons are visible to the model.\n",
+ "- Census 2021 child counts age; `DEMAND_SCALE` should drift upward as the birth-rate dip works\n",
+ " through, and the constants can be re-fit any time by re-running the sweep against\n",
+ " `pipeline/check_school_cutoffs.py` (more LA reports in `property-data/ground_truth/` = tighter\n",
+ " calibration).\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index f10af75..3c4cf31 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,7 @@ services:
command: >
bash -c "
cargo install cargo-watch &&
- cargo watch --poll -i logs/ -x 'run -- --properties /app/property-data4/properties.parquet --postcode-features /app/property-data4/postcode.parquet --pois /app/property-data4/filtered_uk_pois.parquet --places /app/property-data4/places.parquet --tiles /app/property-data4/uk.pmtiles --postcodes /app/property-data4/postcode_boundaries --travel-times /app/property-data4/travel-times --satellite-tiles /app/property-data4/satellite.pmtiles --satellite-highres-tiles /app/property-data4/satellite_highres.pmtiles --noise-overlay-tiles /app/property-data4/noise_lden_10m.pmtiles --crime-hotspot-tiles /app/property-data4/crime_hotspots.pmtiles --tree-overlay-tiles /app/property-data4/trees_outside_woodlands.pmtiles --property-border-tiles /app/property-data4/property_borders.pmtiles'
+ cargo watch --poll -i logs/ -x 'run -- --properties /app/property-data/properties.parquet --postcode-features /app/property-data/postcode.parquet --pois /app/property-data/filtered_uk_pois.parquet --places /app/property-data/places.parquet --tiles /app/property-data/uk.pmtiles --postcodes /app/property-data/postcode_boundaries --travel-times /app/property-data/travel-times --satellite-tiles /app/property-data/satellite.pmtiles --satellite-highres-tiles /app/property-data/satellite_highres.pmtiles --noise-overlay-tiles /app/property-data/noise_lden_10m.pmtiles --crime-hotspot-tiles /app/property-data/crime_hotspots.pmtiles --tree-overlay-tiles /app/property-data/trees_outside_woodlands.pmtiles --property-border-tiles /app/property-data/property_borders.pmtiles --actual-listings-path /app/finder/data/online_listings_buy_enriched.parquet --crime-by-year-path /app/property-data/crime_by_postcode_by_year.parquet'
"
ports:
- "8001:8001"
diff --git a/frontend/public/assets/twemoji/1f68a.png b/frontend/public/assets/twemoji/1f68a.png
new file mode 100644
index 0000000..9d51921
Binary files /dev/null and b/frontend/public/assets/twemoji/1f68a.png differ
diff --git a/frontend/public/assets/twemoji/1fa7a.png b/frontend/public/assets/twemoji/1fa7a.png
new file mode 100644
index 0000000..edb9307
Binary files /dev/null and b/frontend/public/assets/twemoji/1fa7a.png differ
diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml
index a74e07d..6cbe8bd 100644
--- a/frontend/public/sitemap.xml
+++ b/frontend/public/sitemap.xml
@@ -70,4 +70,14 @@
monthly
0.6
+
+ https://perfect-postcode.co.uk/terms
+ yearly
+ 0.3
+
+
+ https://perfect-postcode.co.uk/privacy
+ yearly
+ 0.3
+
diff --git a/frontend/public/video/ad-01-say-it.jpg b/frontend/public/video/ad-01-say-it.jpg
new file mode 100644
index 0000000..156ef4b
--- /dev/null
+++ b/frontend/public/video/ad-01-say-it.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:feda4d907ccc0935216dbf2f70c1c72ad736648fbd8439ae1450f3178c24b6e0
+size 252974
diff --git a/frontend/public/video/ad-01-say-it.mp4 b/frontend/public/video/ad-01-say-it.mp4
new file mode 100644
index 0000000..5cbfb40
--- /dev/null
+++ b/frontend/public/video/ad-01-say-it.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ca42a9f06efe741b444a88f3439a87766d85a813038a11366d4dbe1d1563e7d7
+size 14163191
diff --git a/frontend/public/video/ad-02-twenty-minute-map.jpg b/frontend/public/video/ad-02-twenty-minute-map.jpg
new file mode 100644
index 0000000..015ac38
--- /dev/null
+++ b/frontend/public/video/ad-02-twenty-minute-map.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b914fe8cf24470bc35cf0c133ca2b36b6eaa7071456327f02e363883a0291484
+size 332708
diff --git a/frontend/public/video/ad-02-twenty-minute-map.mp4 b/frontend/public/video/ad-02-twenty-minute-map.mp4
new file mode 100644
index 0000000..ae57ff6
--- /dev/null
+++ b/frontend/public/video/ad-02-twenty-minute-map.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2b5c2283b01920830ac53905be9ade0dea1c54266cdb94435e62ae0869da08b3
+size 20973560
diff --git a/frontend/public/video/ad-03-postcode-files.jpg b/frontend/public/video/ad-03-postcode-files.jpg
new file mode 100644
index 0000000..8b8b718
--- /dev/null
+++ b/frontend/public/video/ad-03-postcode-files.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:081a2362b7a5064554744ee850a78d15b7e54155543ab3dff4c82d17001d5ac4
+size 194206
diff --git a/frontend/public/video/ad-03-postcode-files.mp4 b/frontend/public/video/ad-03-postcode-files.mp4
new file mode 100644
index 0000000..63ea25c
--- /dev/null
+++ b/frontend/public/video/ad-03-postcode-files.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2766cd3abdf5de15961f502938a85d20390e690ef81dfeb3564eba0140e46fc3
+size 14428541
diff --git a/frontend/public/video/ad-04-quiet-streets.jpg b/frontend/public/video/ad-04-quiet-streets.jpg
new file mode 100644
index 0000000..061e1b0
--- /dev/null
+++ b/frontend/public/video/ad-04-quiet-streets.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7f873aa0b7fe358c6b18ddeda02c838ed72f4b926410cff71bd8fde3b9f05e8e
+size 236388
diff --git a/frontend/public/video/ad-04-quiet-streets.mp4 b/frontend/public/video/ad-04-quiet-streets.mp4
new file mode 100644
index 0000000..8045e53
--- /dev/null
+++ b/frontend/public/video/ad-04-quiet-streets.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3443dd382034f0cad340455d84c9402032027a99bf20bf90d99378cdc307f76c
+size 13308433
diff --git a/frontend/public/video/ad-05-school-run.jpg b/frontend/public/video/ad-05-school-run.jpg
new file mode 100644
index 0000000..8456133
--- /dev/null
+++ b/frontend/public/video/ad-05-school-run.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8a176491a7d9b146c5cd2e8785cd00cd3f2eadf41c68326e19e5d2ef4c92ea90
+size 259165
diff --git a/frontend/public/video/ad-05-school-run.mp4 b/frontend/public/video/ad-05-school-run.mp4
new file mode 100644
index 0000000..83c5638
--- /dev/null
+++ b/frontend/public/video/ad-05-school-run.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f9d123fd1d5f012367bf6818394d1b5601065692962118378f721f8347299d01
+size 16605037
diff --git a/frontend/public/video/ad-06-waitrose-test.jpg b/frontend/public/video/ad-06-waitrose-test.jpg
new file mode 100644
index 0000000..a6865e0
--- /dev/null
+++ b/frontend/public/video/ad-06-waitrose-test.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:062af2bdd4b85e3e213c3211e1981d2582a439d8afc878e6af198563fe8737ef
+size 266022
diff --git a/frontend/public/video/ad-06-waitrose-test.mp4 b/frontend/public/video/ad-06-waitrose-test.mp4
new file mode 100644
index 0000000..3752854
--- /dev/null
+++ b/frontend/public/video/ad-06-waitrose-test.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:814f21ee607ef664911dc45ab895d3a6abcbc6ca99beaa1df82b2bb3a08eab58
+size 16350932
diff --git a/frontend/public/video/ad-07-renters-map.jpg b/frontend/public/video/ad-07-renters-map.jpg
new file mode 100644
index 0000000..824a48b
--- /dev/null
+++ b/frontend/public/video/ad-07-renters-map.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b20a96a63320a873f1fbb6f65924192636d1027cc7e2568f218d6ffc9fcbf7d5
+size 244320
diff --git a/frontend/public/video/ad-07-renters-map.mp4 b/frontend/public/video/ad-07-renters-map.mp4
new file mode 100644
index 0000000..398ac0b
--- /dev/null
+++ b/frontend/public/video/ad-07-renters-map.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0b61d1243cfb115d2421b20ea6d2f282530f34bb975e9b70fcf77dbd9df5b6e8
+size 13896671
diff --git a/frontend/public/video/ad-08-cheap-insurance.jpg b/frontend/public/video/ad-08-cheap-insurance.jpg
new file mode 100644
index 0000000..63622a0
--- /dev/null
+++ b/frontend/public/video/ad-08-cheap-insurance.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8fd356f8055c9d1c6b0f039072b7122227cd6de2f80987c78a3e12b7bdb6516c
+size 266039
diff --git a/frontend/public/video/ad-08-cheap-insurance.mp4 b/frontend/public/video/ad-08-cheap-insurance.mp4
new file mode 100644
index 0000000..c6fe8f9
--- /dev/null
+++ b/frontend/public/video/ad-08-cheap-insurance.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f0a696e433bbfc980c19ed1a52961981467c9b47893bfa1cfabffeb8d422da8b
+size 15165825
diff --git a/frontend/public/video/poster.jpg b/frontend/public/video/poster.jpg
index 3131ea8..fdc037e 100644
Binary files a/frontend/public/video/poster.jpg and b/frontend/public/video/poster.jpg differ
diff --git a/frontend/public/video/recording-de-mobile.jpg b/frontend/public/video/recording-de-mobile.jpg
index 5f734ea..9cbfe1b 100644
Binary files a/frontend/public/video/recording-de-mobile.jpg and b/frontend/public/video/recording-de-mobile.jpg differ
diff --git a/frontend/public/video/recording-de-mobile.mp4 b/frontend/public/video/recording-de-mobile.mp4
index cc88ce0..5dfade7 100644
Binary files a/frontend/public/video/recording-de-mobile.mp4 and b/frontend/public/video/recording-de-mobile.mp4 differ
diff --git a/frontend/public/video/recording-de.jpg b/frontend/public/video/recording-de.jpg
index 5714cf5..249cee4 100644
Binary files a/frontend/public/video/recording-de.jpg and b/frontend/public/video/recording-de.jpg differ
diff --git a/frontend/public/video/recording-de.mp4 b/frontend/public/video/recording-de.mp4
index 2b9f98f..8f422b7 100644
Binary files a/frontend/public/video/recording-de.mp4 and b/frontend/public/video/recording-de.mp4 differ
diff --git a/frontend/public/video/recording-hi-mobile.jpg b/frontend/public/video/recording-hi-mobile.jpg
index 8ae9277..794f4b4 100644
Binary files a/frontend/public/video/recording-hi-mobile.jpg and b/frontend/public/video/recording-hi-mobile.jpg differ
diff --git a/frontend/public/video/recording-hi-mobile.mp4 b/frontend/public/video/recording-hi-mobile.mp4
index e2d55d8..a65f0d1 100644
Binary files a/frontend/public/video/recording-hi-mobile.mp4 and b/frontend/public/video/recording-hi-mobile.mp4 differ
diff --git a/frontend/public/video/recording-hi.jpg b/frontend/public/video/recording-hi.jpg
index c5454b3..0454717 100644
Binary files a/frontend/public/video/recording-hi.jpg and b/frontend/public/video/recording-hi.jpg differ
diff --git a/frontend/public/video/recording-hi.mp4 b/frontend/public/video/recording-hi.mp4
index b832576..0d8b3b8 100644
Binary files a/frontend/public/video/recording-hi.mp4 and b/frontend/public/video/recording-hi.mp4 differ
diff --git a/frontend/public/video/recording-mobile.jpg b/frontend/public/video/recording-mobile.jpg
index 19f1cdb..40ae2d3 100644
Binary files a/frontend/public/video/recording-mobile.jpg and b/frontend/public/video/recording-mobile.jpg differ
diff --git a/frontend/public/video/recording-mobile.mp4 b/frontend/public/video/recording-mobile.mp4
index 95b868e..97cd7e8 100644
Binary files a/frontend/public/video/recording-mobile.mp4 and b/frontend/public/video/recording-mobile.mp4 differ
diff --git a/frontend/public/video/recording-zh-mobile.jpg b/frontend/public/video/recording-zh-mobile.jpg
index e38d3f0..fc22073 100644
Binary files a/frontend/public/video/recording-zh-mobile.jpg and b/frontend/public/video/recording-zh-mobile.jpg differ
diff --git a/frontend/public/video/recording-zh-mobile.mp4 b/frontend/public/video/recording-zh-mobile.mp4
index 38a6c07..0ffdd60 100644
Binary files a/frontend/public/video/recording-zh-mobile.mp4 and b/frontend/public/video/recording-zh-mobile.mp4 differ
diff --git a/frontend/public/video/recording-zh.jpg b/frontend/public/video/recording-zh.jpg
index cab338e..42a2e4a 100644
Binary files a/frontend/public/video/recording-zh.jpg and b/frontend/public/video/recording-zh.jpg differ
diff --git a/frontend/public/video/recording-zh.mp4 b/frontend/public/video/recording-zh.mp4
index d4bb2a6..c838dcf 100644
Binary files a/frontend/public/video/recording-zh.mp4 and b/frontend/public/video/recording-zh.mp4 differ
diff --git a/frontend/public/video/recording.jpg b/frontend/public/video/recording.jpg
index de8c3ff..633284e 100644
Binary files a/frontend/public/video/recording.jpg and b/frontend/public/video/recording.jpg differ
diff --git a/frontend/public/video/recording.mp4 b/frontend/public/video/recording.mp4
index d4cc29b..de1a3fb 100644
Binary files a/frontend/public/video/recording.mp4 and b/frontend/public/video/recording.mp4 differ
diff --git a/frontend/scripts/prerender.mjs b/frontend/scripts/prerender.mjs
index 5006dba..66d005c 100644
--- a/frontend/scripts/prerender.mjs
+++ b/frontend/scripts/prerender.mjs
@@ -107,6 +107,20 @@ const ROUTES = [
description:
'Learn how Perfect Postcode treats saved searches, account data and property research workflows with privacy and security in mind.',
},
+ {
+ path: '/terms',
+ output: 'terms/index.html',
+ title: 'Terms of Service | Perfect Postcode',
+ description:
+ 'The terms that govern your use of Perfect Postcode, including lifetime access, acceptable use, data accuracy, payments and refunds.',
+ },
+ {
+ path: '/privacy',
+ output: 'privacy/index.html',
+ title: 'Privacy Policy | Perfect Postcode',
+ description:
+ 'How Perfect Postcode collects, uses and protects your data: account details, payments, saved searches, AI queries, analytics and your UK GDPR rights.',
+ },
];
const FAQ_SCHEMA_ITEMS = [
@@ -325,11 +339,16 @@ async function prerender() {
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
- try {
- const baseIndexHtml = cleanBaseIndexHtml(readFileSync(INDEX_PATH, 'utf-8'));
+ // Every real page renders tens of kB; a few hundred chars means the SPA
+ // raced hydration and we captured a loading shell.
+ const MIN_HTML_CHARS = 1000;
+ const MAX_ATTEMPTS = 3;
- for (const route of ROUTES) {
- const page = await browser.newPage();
+ async function renderRoute(route) {
+ // A fresh context per attempt: pages otherwise share cache/storage, and a
+ // poisoned chunk-fetch in the shared cache makes a route fail every retry.
+ const context = await browser.createBrowserContext();
+ const page = await context.newPage();
// Intercept API requests to prevent real fetches and retry loops.
await page.setRequestInterception(true);
@@ -374,15 +393,16 @@ async function prerender() {
}
});
- await page.goto(`http://127.0.0.1:${port}${route.path}`, {
- waitUntil: 'networkidle0',
- timeout: 30000,
- });
+ try {
+ await page.goto(`http://127.0.0.1:${port}${route.path}`, {
+ waitUntil: 'networkidle0',
+ timeout: 30000,
+ });
- await page.waitForSelector('h1', { timeout: 10000 });
+ await page.waitForSelector('h1', { timeout: 10000 });
- // Extract and clean the rendered HTML.
- const html = await page.evaluate(() => {
+ // Extract and clean the rendered HTML.
+ const html = await page.evaluate(() => {
const root = document.getElementById('root');
if (!root) return '';
@@ -400,10 +420,33 @@ async function prerender() {
});
return root.innerHTML;
- });
+ });
- if (!html || html.length < 100) {
- throw new Error(`Prerender produced too little HTML for ${route.path}`);
+ if (!html || html.length < MIN_HTML_CHARS) {
+ throw new Error(
+ `Prerender produced too little HTML for ${route.path} (${html?.length ?? 0} chars)`
+ );
+ }
+
+ return html;
+ } finally {
+ await context.close().catch(() => {});
+ }
+ }
+
+ try {
+ const baseIndexHtml = cleanBaseIndexHtml(readFileSync(INDEX_PATH, 'utf-8'));
+
+ for (const route of ROUTES) {
+ let html = null;
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
+ try {
+ html = await renderRoute(route);
+ break;
+ } catch (err) {
+ if (attempt === MAX_ATTEMPTS) throw err;
+ console.warn(`Retrying ${route.path} (attempt ${attempt} failed: ${err.message})`);
+ }
}
const updated = updateHead(baseIndexHtml, route).replace(
@@ -418,7 +461,6 @@ async function prerender() {
const outputPath = join(DIST_DIR, route.output);
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, updated);
- await page.close();
console.log(`Prerendered ${route.path} (${html.length} chars) into ${route.output}`);
}
} finally {
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b3a23be..7f03f83 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -15,6 +15,7 @@ import type { FeatureMeta, FeatureGroup, POICategoriesResponse, POICategoryGroup
import { fetchWithRetry, apiUrl, logNonAbortError } from './lib/api';
import { trackEvent } from './lib/analytics';
import { parseUrlState } from './lib/url-state';
+import pb from './lib/pocketbase';
import { INITIAL_VIEW_STATE } from './lib/consts';
import { useTheme } from './hooks/useTheme';
import { useIsMobile } from './hooks/useIsMobile';
@@ -40,6 +41,7 @@ const SavedPage = lazy(() =>
import('./components/account/AccountPage').then((module) => ({ default: module.SavedPage }))
);
const InvitePage = lazy(() => import('./components/invite/InvitePage'));
+const LegalPage = lazy(() => import('./components/legal/LegalPage'));
const MapPage = lazy(() => import('./components/map/MapPage'));
const AuthModal = lazy(() => import('./components/ui/AuthModal'));
const SaveSearchModal = lazy(() => import('./components/ui/SaveSearchModal'));
@@ -77,19 +79,42 @@ function currentRelativePath(): string {
return `${window.location.pathname}${window.location.search}`;
}
+const LAST_DASHBOARD_PARAMS_KEY = 'pp_last_dashboard_params';
+
+function persistLastDashboardParams(params: string) {
+ try {
+ if (params) window.localStorage.setItem(LAST_DASHBOARD_PARAMS_KEY, params);
+ } catch {
+ // Storage unavailable (private mode/quota) — session restore is best-effort.
+ }
+}
+
+function readLastDashboardSearch(): string {
+ try {
+ const saved = window.localStorage.getItem(LAST_DASHBOARD_PARAMS_KEY);
+ return saved ? `?${saved.replace(/^\?/, '')}` : '';
+ } catch {
+ return '';
+ }
+}
+
+/**
+ * Filters and map view live only in the URL. When the dashboard is opened bare
+ * (no query), restore the last session's params so users pick up where they
+ * left off. Explicit params and shared links always win.
+ */
+function restoreLastDashboardSession() {
+ const pathname = window.location.pathname.replace(/\/+$/, '');
+ if (pathname !== '/dashboard' || window.location.search) return;
+ const saved = readLastDashboardSearch();
+ if (!saved) return;
+ window.history.replaceState(window.history.state, '', `/dashboard${saved}`);
+}
+
function isProtectedPage(page: Page): boolean {
return page === 'account' || page === 'saved';
}
-function isSharedDashboardUrl(): boolean {
- const share = new URLSearchParams(window.location.search).get('share');
- return !!share && /^[a-z0-9]{1,20}$/i.test(share);
-}
-
-function isAuthRequiredRoute(page: Page): boolean {
- return isProtectedPage(page) || (page === 'dashboard' && !isSharedDashboardUrl());
-}
-
function buildPageUrl(page: Page, inviteCode?: string, search = '', hash = ''): string {
const normalizedHash = normalizeHash(hash);
return `${pageToPath(page, inviteCode)}${search}${normalizedHash ? `#${normalizedHash}` : ''}`;
@@ -126,6 +151,10 @@ function pageToPath(page: Page, inviteCode?: string): string {
case 'methodology':
case 'privacy-security':
return SEO_CONTENT_PATHS[page];
+ case 'terms':
+ return '/terms';
+ case 'privacy':
+ return '/privacy';
case 'saved':
return '/saved';
case 'account':
@@ -140,7 +169,10 @@ function pageToPath(page: Page, inviteCode?: string): string {
}
}
-function pathToPage(pathname: string): RouteMatch | null {
+function pathToPage(rawPathname: string): RouteMatch | null {
+ // Proxies 307-redirect /learn -> /learn/; treat trailing slashes as equivalent.
+ const pathname =
+ rawPathname.length > 1 ? rawPathname.replace(/\/+$/, '') || '/' : rawPathname;
if (pathname === '/dashboard') return { page: 'dashboard' };
if (pathname === '/saved') return { page: 'saved' };
if (pathname === '/invites') return { page: 'account', hash: 'invites' };
@@ -152,6 +184,8 @@ function pathToPage(pathname: string): RouteMatch | null {
if (seoContentPage) return { page: seoContentPage };
if (pathname === '/account') return { page: 'account' };
if (pathname === '/support') return { page: 'learn' };
+ if (pathname === '/terms') return { page: 'terms' };
+ if (pathname === '/privacy') return { page: 'privacy' };
if (pathname.startsWith('/invite/')) {
const code = pathname.slice('/invite/'.length);
return { page: 'invite', inviteCode: code };
@@ -169,7 +203,11 @@ function isSeoContentPage(page: Page): page is SeoContentKey {
}
export default function App() {
- const urlState = useMemo(() => parseUrlState(), []);
+ const urlState = useMemo(() => {
+ // Must run before any reads of window.location.search below.
+ restoreLastDashboardSession();
+ return parseUrlState();
+ }, []);
const initialRoute = useMemo(() => pathToPage(window.location.pathname), []);
const [mapUrlState, setMapUrlState] = useState(urlState);
const [dashboardRouteKey, setDashboardRouteKey] = useState(() =>
@@ -276,7 +314,9 @@ export default function App() {
if (!completed) {
setPostAuthIntent(null);
postAuthCheckoutReturnPathRef.current = null;
- if (isAuthRequiredRoute(activePageRef.current)) {
+ // Only protected pages bounce home; the dashboard stays open in demo
+ // mode (server-enforced free zone) when the modal is dismissed.
+ if (isProtectedPage(activePageRef.current)) {
window.history.replaceState({ page: 'home', hash: '' }, '', '/');
setRouteHash('');
setActivePage('home');
@@ -300,8 +340,11 @@ export default function App() {
async function refreshOnStartup() {
if (!returnedFromCheckout) {
- // Always refresh auth on startup to pick up server-side subscription changes.
- refreshAuthRef.current().catch(() => {});
+ // Refresh auth on startup to pick up server-side subscription changes,
+ // but only when a token exists — logged-out visitors would just 401.
+ if (pb.authStore.token) {
+ refreshAuthRef.current().catch(() => {});
+ }
return;
}
@@ -384,8 +427,10 @@ export default function App() {
if (infoFeature) {
window.history.replaceState({ ...window.history.state, infoFeature }, '');
}
- // Restore dashboard search params when navigating back
- const search = page === 'dashboard' ? dashboardSearchRef.current : '';
+ // Restore dashboard search params when navigating back, falling back to
+ // the last persisted session for first visits in this tab.
+ const search =
+ page === 'dashboard' ? dashboardSearchRef.current || readLastDashboardSearch() : '';
const url = buildPageUrl(page, inviteCode ?? undefined, search, targetHash);
window.history.pushState({ page, hash: targetHash }, '', url);
if (page === 'dashboard') {
@@ -527,19 +572,20 @@ export default function App() {
}
}, [activePage, fetchSearches]);
- const isAuthRequiredPage =
- activePage === 'account' ||
- activePage === 'saved' ||
- (activePage === 'dashboard' && !mapUrlState.share);
+ const isProtectedPageActive = isProtectedPage(activePage);
+ // Only protected pages (account/saved) prompt for login on entry. The
+ // dashboard opens straight into demo mode (server-enforced free zone) so
+ // visitors can try it without logging in; the upgrade modal still appears
+ // when they pan outside the free zone.
useEffect(() => {
if (authLoading) return;
- if (isAuthRequiredPage && !user) {
+ if (isProtectedPageActive && !user) {
openAuthModal('login');
}
if (activePage === 'pricing' && hasFullAccess(user)) {
navigateTo('dashboard');
}
- }, [activePage, authLoading, isAuthRequiredPage, navigateTo, openAuthModal, user]);
+ }, [activePage, authLoading, isProtectedPageActive, navigateTo, openAuthModal, user]);
const [exportState, setExportState] = useState(null);
@@ -641,6 +687,8 @@ export default function App() {
/>
) : activePage === 'learn' ? (
+ ) : activePage === 'terms' || activePage === 'privacy' ? (
+
) : isSeoLandingPage(activePage) ? (
navigateTo('dashboard')} />
) : isSeoContentPage(activePage) ? (
@@ -662,7 +710,7 @@ export default function App() {
}}
scrollTarget={routeHash}
/>
- ) : isAuthRequiredPage && !user ? (
+ ) : isProtectedPageActive && !user ? (
) : activePage === 'invite' && inviteCode ? (
setPendingInfoFeature(null)}
onNavigateTo={navigateTo}
onExportStateChange={setExportState}
- onDashboardParamsChange={setDashboardParams}
+ onDashboardParamsChange={(params) => {
+ setDashboardParams(params);
+ if (!mapUrlState.share) persistLastDashboardParams(params);
+ }}
onDashboardReadyChange={setDashboardReady}
isMobile={isMobile}
initialTravelTime={mapUrlState.travelTime}
diff --git a/frontend/src/components/home/HomePage.tsx b/frontend/src/components/home/HomePage.tsx
index ed703a1..8cdaf4a 100644
--- a/frontend/src/components/home/HomePage.tsx
+++ b/frontend/src/components/home/HomePage.tsx
@@ -8,6 +8,7 @@ import BottomIllustration from './BottomIllustration';
import { TickerValue } from '../ui/TickerValue';
import { ChevronIcon, LogoIcon, PlayIcon } from '../ui/icons';
import { trackEvent } from '../../lib/analytics';
+import { apiUrl } from '../../lib/api';
const BRAND_NAME = 'Perfect Postcode';
const BRAND_TEXT_CLASS = 'text-teal-600 dark:text-teal-400';
@@ -163,11 +164,78 @@ function ProductDemoVideo() {
);
}
+interface PriceStripTier {
+ up_to: number | null;
+ price_pence: number;
+}
+
+/**
+ * Compact pricing teaser under the hero CTAs: surfaces the current lifetime
+ * price and tier scarcity that otherwise hide behind the Pricing nav link.
+ */
+function PriceStrip({
+ onOpenPricing,
+ hidePricing,
+}: {
+ onOpenPricing: () => void;
+ hidePricing?: boolean;
+}) {
+ const { t } = useTranslation();
+ const [pricing, setPricing] = useState<{
+ licensed_count: number;
+ current_price_pence: number;
+ tiers: PriceStripTier[];
+ } | null>(null);
+
+ useEffect(() => {
+ if (hidePricing) return;
+ const controller = new AbortController();
+ fetch(apiUrl('pricing'), { signal: controller.signal })
+ .then((res) => (res.ok ? res.json() : null))
+ .then(setPricing)
+ .catch(() => {});
+ return () => controller.abort();
+ }, [hidePricing]);
+
+ if (hidePricing || !pricing) return null;
+
+ const price = `£${pricing.current_price_pence / 100}`;
+ const currentTier = pricing.tiers.find(
+ (tier) => tier.up_to === null || pricing.licensed_count < tier.up_to
+ );
+ const spotsRemaining =
+ currentTier?.up_to != null ? currentTier.up_to - pricing.licensed_count : 0;
+
+ return (
+
+ {pricing.current_price_pence === 0
+ ? t('upgrade.freeForEarly')
+ : t('home.priceStrip', { price })}{' '}
+ {pricing.current_price_pence > 0 && spotsRemaining > 0 && (
+
+ {spotsRemaining === 1
+ ? t('home.priceStripSpots', { count: spotsRemaining })
+ : t('home.priceStripSpotsPlural', { count: spotsRemaining })}
+
+ )}{' '}
+ {
+ trackEvent('CTA Click', { location: 'hero', label: 'price_strip' });
+ onOpenPricing();
+ }}
+ className="underline decoration-dotted underline-offset-2 text-teal-300 hover:text-teal-200"
+ >
+ {t('home.priceStripCta')}
+
+
+ );
+}
+
export default function HomePage({
onOpenDashboard,
- onOpenPricing: _onOpenPricing,
+ onOpenPricing,
theme = 'light',
- hidePricing: _hidePricing,
+ hidePricing,
}: {
onOpenDashboard: () => void;
onOpenPricing: () => void;
@@ -327,7 +395,7 @@ export default function HomePage({
{highlightBrandText(t('home.heroDescription'), 'font-semibold text-teal-300')}
-
+
{
trackEvent('CTA Click', { location: 'hero', label: 'explore_map' });
@@ -347,6 +415,8 @@ export default function HomePage({
{t('home.seeTheDifference')}
+
+
{t('home.coverageNote')}
@@ -356,7 +426,7 @@ export default function HomePage({
-
+
{t('home.statFilters')}
diff --git a/frontend/src/components/home/ProductShowcase.tsx b/frontend/src/components/home/ProductShowcase.tsx
index 3c9cefe..b4bc291 100644
--- a/frontend/src/components/home/ProductShowcase.tsx
+++ b/frontend/src/components/home/ProductShowcase.tsx
@@ -82,7 +82,7 @@ const DEMO_FEATURES: FeatureMeta[] = [
step: 1,
},
{
- name: 'Good+ primary schools within 2km',
+ name: 'Good+ primary school catchments',
type: 'numeric',
group: 'Schools',
min: 0,
@@ -300,7 +300,7 @@ function interpolateViewState(progress: number): ViewState {
function demoSliderStep(feature: FeatureMeta): number {
if (feature.name === 'Estimated price') return 1000;
if (feature.name === 'Noise (dB)') return 0.05;
- if (feature.name === 'Good+ primary schools within 2km') return 0.01;
+ if (feature.name === 'Good+ primary school catchments') return 0.01;
if (feature.name === 'Travel time to nearest train or tube station (min)') return 0.1;
return feature.step ?? 1;
}
@@ -350,7 +350,7 @@ function FilterPreviewRow({
const shortLabelKeys = {
'Estimated price': 'home.showcaseFeaturePriceShort',
'Noise (dB)': 'home.showcaseFeatureNoiseShort',
- 'Good+ primary schools within 2km': 'home.showcaseFeatureSchoolsShort',
+ 'Good+ primary school catchments': 'home.showcaseFeatureSchoolsShort',
'Travel time to nearest train or tube station (min)': 'home.showcaseFeatureTravelShort',
} as const;
const shortLabelKey = shortLabelKeys[feature.name as keyof typeof shortLabelKeys];
@@ -406,7 +406,7 @@ function formatDemoRange(feature: FeatureMeta, value: [number, number], t: TFunc
if (feature.name === 'Noise (dB)') {
return `${Math.round(value[0])} - ${Math.round(value[1])} dB`;
}
- if (feature.name === 'Good+ primary schools within 2km') {
+ if (feature.name === 'Good+ primary school catchments') {
return t('home.showcaseGoodPrimariesNearby', { count: Math.round(value[0]) });
}
if (feature.name === 'Travel time to nearest train or tube station (min)') {
diff --git a/frontend/src/components/landing/SeoContentPage.tsx b/frontend/src/components/landing/SeoContentPage.tsx
index a326c45..27a269e 100644
--- a/frontend/src/components/landing/SeoContentPage.tsx
+++ b/frontend/src/components/landing/SeoContentPage.tsx
@@ -2,6 +2,7 @@ import { lazy, Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import { CheckIcon } from '../ui/icons/CheckIcon';
import HomeFinalCta from '../home/HomeFinalCta';
+import Footer from '../ui/Footer';
import { usePageMeta } from '../../hooks/usePageMeta';
import {
getLocalizedSeoContentPage,
@@ -202,6 +203,7 @@ export default function SeoContentPage({
trackingLocation={`seo_${pageKey}_bottom`}
/>
+
);
}
diff --git a/frontend/src/components/landing/SeoLandingPage.tsx b/frontend/src/components/landing/SeoLandingPage.tsx
index daa92ad..1615d36 100644
--- a/frontend/src/components/landing/SeoLandingPage.tsx
+++ b/frontend/src/components/landing/SeoLandingPage.tsx
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import { CheckIcon } from '../ui/icons/CheckIcon';
import { LogoIcon } from '../ui/icons/LogoIcon';
import HomeFinalCta from '../home/HomeFinalCta';
+import Footer from '../ui/Footer';
import { usePageMeta } from '../../hooks/usePageMeta';
import {
getLocalizedSeoLandingPage,
@@ -252,6 +253,7 @@ export default function SeoLandingPage({
trackingLocation={`seo_${pageKey}_bottom`}
/>
+
);
}
diff --git a/frontend/src/components/learn/LearnPage.tsx b/frontend/src/components/learn/LearnPage.tsx
index ac2b05d..9b7a20f 100644
--- a/frontend/src/components/learn/LearnPage.tsx
+++ b/frontend/src/components/learn/LearnPage.tsx
@@ -3,9 +3,52 @@ import { useTranslation } from 'react-i18next';
import { tDynamic } from '../../i18n';
import { getLocalizedSeoPages } from '../../lib/seoLandingPages';
import { ChevronIcon } from '../ui/icons/ChevronIcon';
+import { PlayIcon } from '../ui/icons';
import { SubNav } from '../ui/SubNav';
+import Footer from '../ui/Footer';
-type LearnTab = 'data-sources' | 'faq' | 'articles' | 'support';
+type LearnTab = 'data-sources' | 'faq' | 'articles' | 'videos' | 'support';
+
+// Social-media ad cuts rendered by the recorder pipeline (video/src/storyboard.ts).
+// Each `
.mp4` is a 9:16 clip with a matching `.jpg` poster in /public/video.
+const SOCIAL_VIDEOS: { slug: string; titleKey: string; descKey: string }[] = [
+ { slug: 'ad-01-say-it', titleKey: 'learnPage.video01Title', descKey: 'learnPage.video01Desc' },
+ {
+ slug: 'ad-02-twenty-minute-map',
+ titleKey: 'learnPage.video02Title',
+ descKey: 'learnPage.video02Desc',
+ },
+ {
+ slug: 'ad-03-postcode-files',
+ titleKey: 'learnPage.video03Title',
+ descKey: 'learnPage.video03Desc',
+ },
+ {
+ slug: 'ad-04-quiet-streets',
+ titleKey: 'learnPage.video04Title',
+ descKey: 'learnPage.video04Desc',
+ },
+ {
+ slug: 'ad-05-school-run',
+ titleKey: 'learnPage.video05Title',
+ descKey: 'learnPage.video05Desc',
+ },
+ {
+ slug: 'ad-06-waitrose-test',
+ titleKey: 'learnPage.video06Title',
+ descKey: 'learnPage.video06Desc',
+ },
+ {
+ slug: 'ad-07-renters-map',
+ titleKey: 'learnPage.video07Title',
+ descKey: 'learnPage.video07Desc',
+ },
+ {
+ slug: 'ad-08-cheap-insurance',
+ titleKey: 'learnPage.video08Title',
+ descKey: 'learnPage.video08Desc',
+ },
+];
interface DataSourceDef {
id: string;
@@ -176,6 +219,68 @@ function FAQItemCard({ question, answer }: { question: string; answer: string })
);
}
+function SocialVideoCard({
+ slug,
+ title,
+ description,
+}: {
+ slug: string;
+ title: string;
+ description: string;
+}) {
+ const { t } = useTranslation();
+ const videoRef = useRef(null);
+ const [isLoaded, setIsLoaded] = useState(false);
+ const [isPlaying, setIsPlaying] = useState(false);
+ const videoSrc = `/video/${slug}.mp4`;
+ const posterSrc = `/video/${slug}.jpg`;
+
+ const playVideo = () => {
+ const video = videoRef.current;
+ setIsLoaded(true);
+ if (!video) return;
+ if (video.getAttribute('src') !== videoSrc) {
+ video.src = videoSrc;
+ video.load();
+ }
+ void video.play().catch(() => setIsPlaying(false));
+ };
+
+ return (
+
+
+
setIsPlaying(true)}
+ onPause={() => setIsPlaying(false)}
+ onEnded={() => setIsPlaying(false)}
+ />
+ {!isPlaying && (
+
+ )}
+
+
{title}
+
{description}
+
+ );
+}
+
export default function LearnPage() {
const { t, i18n } = useTranslation();
const [tab, setTab] = useState('faq');
@@ -197,6 +302,7 @@ export default function LearnPage() {
{ key: 'faq', label: t('learnPage.faq') },
{ key: 'data-sources', label: t('learnPage.dataSources') },
{ key: 'articles', label: t('learnPage.articles') },
+ { key: 'videos', label: t('learnPage.videos') },
{ key: 'support', label: t('learnPage.support') },
];
@@ -268,6 +374,9 @@ export default function LearnPage() {
} else if (hash === 'articles') {
setTab('articles');
setHighlightedId(null);
+ } else if (hash === 'videos') {
+ setTab('videos');
+ setHighlightedId(null);
} else if (hash === 'support') {
setTab('support');
setHighlightedId(null);
@@ -300,141 +409,163 @@ export default function LearnPage() {
- {tab === 'data-sources' ? (
- <>
-
-
-
- {t('learnPage.dataSources')}
-
-
- {t('learnPage.dataSourcesIntro', { count: DATA_SOURCE_DEFS.length })}
-
-
- {DATA_SOURCE_DEFS.map((source) => {
- const keys = DS_KEYS[source.id];
- const [nameKey, originKey, useKey] = keys;
- return (
-
{
- cardRefs.current[source.id] = el;
- }}
- className={`bg-white dark:bg-warm-800 rounded-lg border p-5 ${
- highlightedId === source.id
- ? 'border-teal-400 ring-2 ring-teal-400'
- : 'border-warm-200 dark:border-warm-700'
- }`}
- >
-
-
- {tDynamic(nameKey)}
-
-
- {source.license}
-
-
-
- {t('learnPage.source')} {tDynamic(originKey)}
-
-
- {tDynamic(useKey)}
-
-
- );
- })}
-
-
-
-
-
-
-
- {t('learnPage.attribution')}
-
-
- {t('learnPage.attrLandRegistry')}
-
- {t('learnPage.attrOgl')} {t('learnPage.attrOglLink')}.
-
- {t('learnPage.attrOs')}
- {t('learnPage.attrTfl')}
-
- {t('learnPage.attrOsm')} {t('learnPage.attrOsmContrib')},{' '}
- {t('learnPage.attrOsmLicense')} {t('learnPage.attrOsmLicenseLink')}.
-
-
-
-
- >
- ) : tab === 'faq' ? (
-
-
- {t('learnPage.faq')}
-
-
{t('learnPage.faqIntro')}
-
- {FAQ_SECTIONS.map((section) => (
-
-
- {section.title}
-
-
- {section.items.map((item, index) => (
-
- ))}
-
-
- ))}
-
-
- ) : tab === 'articles' ? (
-
);
diff --git a/frontend/src/components/legal/LegalPage.tsx b/frontend/src/components/legal/LegalPage.tsx
new file mode 100644
index 0000000..9a5a3ea
--- /dev/null
+++ b/frontend/src/components/legal/LegalPage.tsx
@@ -0,0 +1,68 @@
+import { useTranslation } from 'react-i18next';
+import { usePageMeta } from '../../hooks/usePageMeta';
+import Footer from '../ui/Footer';
+import { PRIVACY, TERMS, type LegalDoc } from './legal-content';
+
+export type LegalKind = 'terms' | 'privacy';
+
+const DOCS: Record
= { terms: TERMS, privacy: PRIVACY };
+
+export default function LegalPage({ kind }: { kind: LegalKind }) {
+ const { t, i18n } = useTranslation();
+ const doc = DOCS[kind];
+ usePageMeta(`${doc.title} | Perfect Postcode`, doc.metaDescription);
+
+ const showEnglishNotice = !i18n.language?.toLowerCase().startsWith('en');
+
+ return (
+
+
+
{doc.title}
+
+ {t('legal.lastUpdated', { date: doc.lastUpdated })}
+
+ {showEnglishNotice && (
+
+ {t('legal.englishOnly')}
+
+ )}
+
+
+ {doc.intro.map((paragraph) => (
+
+ {paragraph}
+
+ ))}
+
+
+
+ {doc.sections.map((section) => (
+
+
+ {section.heading}
+
+ {section.paragraphs.map((paragraph) => (
+
+ {paragraph}
+
+ ))}
+ {section.bullets && (
+
+ {section.bullets.map((bullet) => (
+
+ {bullet}
+
+ ))}
+
+ )}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/legal/legal-content.ts b/frontend/src/components/legal/legal-content.ts
new file mode 100644
index 0000000..108f1cc
--- /dev/null
+++ b/frontend/src/components/legal/legal-content.ts
@@ -0,0 +1,183 @@
+/**
+ * Legal documents are maintained in English only; the English text is the
+ * authoritative version (a localized notice says so on the page). Keeping
+ * legal copy out of the i18n catalogues avoids meaning drift in translation.
+ *
+ * TODO before launch: confirm the operator/legal-entity details below.
+ */
+
+export interface LegalSection {
+ heading: string;
+ paragraphs: string[];
+ bullets?: string[];
+}
+
+export interface LegalDoc {
+ title: string;
+ metaDescription: string;
+ lastUpdated: string;
+ intro: string[];
+ sections: LegalSection[];
+}
+
+export const SUPPORT_EMAIL = 'support@perfect-postcode.co.uk';
+
+export const TERMS: LegalDoc = {
+ title: 'Terms of Service',
+ metaDescription:
+ 'The terms that govern your use of Perfect Postcode, including lifetime access, acceptable use, data accuracy, payments and refunds.',
+ lastUpdated: '10 June 2026',
+ intro: [
+ `These terms govern your use of perfect-postcode.co.uk ("Perfect Postcode", "the service", "we", "us"). By creating an account or purchasing access you agree to them. If you have any questions, contact ${SUPPORT_EMAIL}.`,
+ ],
+ sections: [
+ {
+ heading: '1. The service',
+ paragraphs: [
+ 'Perfect Postcode is a research tool that combines public datasets about England — property transactions, energy certificates, schools, crime, noise, broadband, transport and more — on an interactive map, so you can shortlist areas that fit your needs before booking viewings.',
+ 'We are not an estate agent, mortgage broker, surveyor or financial adviser, and the service does not provide financial, legal or investment advice.',
+ ],
+ },
+ {
+ heading: '2. Accounts',
+ paragraphs: [
+ 'You need an account to use the service beyond the free demo area. Provide a valid email address, keep your credentials secure, and do not share your account. Accounts are for one person each.',
+ 'We may suspend or close accounts that breach these terms, abuse the service, or attempt to circumvent access restrictions. If we close your account without cause, we will refund the price you paid.',
+ ],
+ },
+ {
+ heading: '3. Free demo and lifetime access',
+ paragraphs: [
+ 'Free accounts can explore all features within the demo area (inner London). Lifetime access is a one-time payment that gives your account ongoing access to the paid map — every postcode, every filter — for as long as the service runs. It is not a subscription, and routine data updates are included.',
+ 'Lifetime access is personal and non-transferable, and is for personal, non-commercial property research. If you would like to use Perfect Postcode commercially (for example in lettings, relocation or research services), contact us first.',
+ ],
+ },
+ {
+ heading: '4. Acceptable use',
+ paragraphs: ['You agree not to:'],
+ bullets: [
+ 'scrape, crawl or bulk-download data outside the export tools we provide;',
+ 'resell, republish or redistribute the data or substantial extracts of it;',
+ 'probe, disrupt or place unreasonable load on the service;',
+ 'use the AI search or other features to process content you have no right to submit.',
+ ],
+ },
+ {
+ heading: '5. Data accuracy',
+ paragraphs: [
+ 'The maps and figures are built from public datasets (HM Land Registry, EPC register, ONS, Ofsted, DfT, police.uk and others) combined with modelling and estimation. Sources can be incomplete, out of date or wrong at the level of an individual property, and our estimates — including estimated current prices — are statistical indications, not valuations.',
+ 'Always verify anything that matters in person and through professional advice (surveys, solicitors, mortgage advisers) before making offers or financial decisions. We provide the service "as is" and do not warrant that any figure is accurate, complete or current.',
+ ],
+ },
+ {
+ heading: '6. Payments and refunds',
+ paragraphs: [
+ 'Payments are processed by Stripe; we never see or store your card details. Prices are shown in pounds sterling at checkout. Early-access pricing tiers can change as tiers fill; the price shown at the moment you pay is the price you get.',
+ `If Perfect Postcode is not for you, email ${SUPPORT_EMAIL} within 14 days of purchase and we will refund you in full.`,
+ ],
+ },
+ {
+ heading: '7. Third-party content',
+ paragraphs: [
+ 'Street View imagery, listing-portal links and similar embedded content are provided by third parties and governed by their own terms. We are not responsible for their availability or accuracy.',
+ ],
+ },
+ {
+ heading: '8. Liability',
+ paragraphs: [
+ 'To the extent permitted by law, we are not liable for decisions made in reliance on the data, for indirect or consequential losses, or for interruptions to the service; our total liability to you is limited to the amount you paid us. Nothing in these terms excludes liability that cannot legally be excluded, and nothing affects your statutory rights as a consumer.',
+ ],
+ },
+ {
+ heading: '9. Changes to the service or these terms',
+ paragraphs: [
+ 'We are a small product that improves continuously; features and data sources may change. We may update these terms, and will note the date of the latest revision above. If a change is material we will flag it on the site or by email. Continued use after a change means you accept the updated terms.',
+ ],
+ },
+ {
+ heading: '10. Governing law and contact',
+ paragraphs: [
+ `These terms are governed by the law of England and Wales, and disputes are subject to the jurisdiction of the courts of England and Wales (consumers keep any mandatory protections of their country of residence). Questions and complaints: ${SUPPORT_EMAIL} — we typically respond within 24 hours.`,
+ ],
+ },
+ ],
+};
+
+export const PRIVACY: LegalDoc = {
+ title: 'Privacy Policy',
+ metaDescription:
+ 'How Perfect Postcode collects, uses and protects your data: account details, payments, saved searches, AI queries, analytics and your UK GDPR rights.',
+ lastUpdated: '10 June 2026',
+ intro: [
+ `This policy explains what personal data Perfect Postcode ("we", "us") collects, why, and your rights over it. We handle personal data under UK data-protection law (UK GDPR and the Data Protection Act 2018). Contact: ${SUPPORT_EMAIL}.`,
+ ],
+ sections: [
+ {
+ heading: '1. What we collect',
+ paragraphs: [],
+ bullets: [
+ 'Account data: your email address, a hashed password (or your Google account identifier if you sign in with Google), newsletter preference and access status.',
+ 'Purchase records: what you bought and when. Payments are processed by Stripe; we never receive your card details.',
+ 'Things you create: saved searches, shared links and their settings.',
+ 'AI search queries: the text you type into the AI search is processed to generate filters and logged with your account so we can debug and improve the feature.',
+ 'Usage data: which pages and features are used, collected as events for product analytics, and standard server logs (IP address, user agent) kept for security.',
+ ],
+ },
+ {
+ heading: '2. How we use it',
+ paragraphs: [],
+ bullets: [
+ 'To provide and secure the service, including signing you in and remembering your saved work (performance of contract).',
+ 'To process payments and keep the records tax law requires (legal obligation).',
+ 'To answer support requests (performance of contract).',
+ 'To send the newsletter, only if you opted in — every email includes an unsubscribe link (consent).',
+ 'To understand how features are used and improve them, using aggregated analytics and logged AI queries (legitimate interests).',
+ ],
+ },
+ {
+ heading: '3. Who we share it with',
+ paragraphs: [
+ 'We do not sell personal data. We use a small number of processors to run the service:',
+ ],
+ bullets: [
+ 'Stripe — payment processing.',
+ 'Google — sign-in (if you choose Google OAuth), embedded Maps/Street View imagery, and the Gemini API which processes the text of AI searches.',
+ 'Hosting and infrastructure providers that run our servers and store backups.',
+ ],
+ },
+ {
+ heading: '4. International transfers',
+ paragraphs: [
+ 'Some processors (such as Stripe and Google) process data outside the UK. Where that happens, transfers rely on UK adequacy decisions or standard contractual clauses.',
+ ],
+ },
+ {
+ heading: '5. Cookies and local storage',
+ paragraphs: [
+ 'We do not use advertising cookies or third-party trackers. Your browser’s local storage holds your sign-in token and preferences (theme, language, tutorial progress, last map view). Embedded Google content (Street View, sign-in) may set its own cookies under Google’s policies.',
+ ],
+ },
+ {
+ heading: '6. Retention',
+ paragraphs: [
+ 'Account data is kept while your account exists and deleted when you ask us to close it. Server logs are kept for a short period for security. Purchase records are kept for as long as tax law requires (typically six years).',
+ ],
+ },
+ {
+ heading: '7. Your rights',
+ paragraphs: [
+ `You can ask for a copy of your data, have it corrected or deleted, restrict or object to processing, and receive your data in a portable format. Email ${SUPPORT_EMAIL} and we will respond promptly. If you are unhappy with how we handle your data you can complain to the Information Commissioner’s Office (ico.org.uk).`,
+ ],
+ },
+ {
+ heading: '8. Children',
+ paragraphs: ['The service is aimed at home buyers and renters and is not directed at children under 16.'],
+ },
+ {
+ heading: '9. Changes to this policy',
+ paragraphs: [
+ 'We will post any changes here and update the date at the top. Material changes will be flagged on the site or by email.',
+ ],
+ },
+ ],
+};
diff --git a/frontend/src/components/map/Map.tsx b/frontend/src/components/map/Map.tsx
index 0b06134..4d0f4a7 100644
--- a/frontend/src/components/map/Map.tsx
+++ b/frontend/src/components/map/Map.tsx
@@ -1172,10 +1172,7 @@ export default memo(function Map({
? [postcodeCountRange.min, postcodeCountRange.max]
: [countRange.min, countRange.max]
}
- totalCount={
- totalCountProp ??
- (usePostcodeView ? postcodeCountRange.total : countRange.total)
- }
+ totalCount={totalCountProp}
showCancel={false}
onCancel={onCancelPin}
mode="density"
diff --git a/frontend/src/components/map/MapPage.tsx b/frontend/src/components/map/MapPage.tsx
index 3d7df16..9a1c9ae 100644
--- a/frontend/src/components/map/MapPage.tsx
+++ b/frontend/src/components/map/MapPage.tsx
@@ -26,6 +26,7 @@ import { apiUrl, authHeaders, buildFilterString } from '../../lib/api';
import { useFilterCounts } from '../../hooks/useFilterCounts';
import { trackEvent } from '../../lib/analytics';
import { INITIAL_VIEW_STATE, POSTCODE_ZOOM_THRESHOLD } from '../../lib/consts';
+import { boundsToCenterZoom } from '../../lib/fit-bounds';
import type { OverlayId } from '../../lib/overlays';
import { CRIME_TYPE_VALUES } from '../../lib/crime-types';
import type { BasemapId } from '../../lib/basemaps';
@@ -257,6 +258,14 @@ export default function MapPage({
}))
);
+ // Move the camera to where the matches actually are — flying to the
+ // travel-time anchor often lands on a viewport with zero matches.
+ if (result.matchBounds) {
+ const target = boundsToCenterZoom(result.matchBounds);
+ mapFlyToRef.current?.(target.lat, target.lng, target.zoom, getMobileMapFlyToOptions());
+ return;
+ }
+
const firstTravelTime = representable[0]?.tt;
if (!firstTravelTime?.slug) return;
@@ -482,7 +491,15 @@ export default function MapPage({
}, []);
const shareReturnViewRef = useRef(shareCode ? initialViewState : null);
+ // Hide the upgrade modal as soon as the user dismisses it. We can't rely on
+ // the camera fly alone to close it: flying back to the free/shared zone only
+ // clears `licenseRequired` once the resulting refetch returns non-403, and
+ // when the fly target equals the current view (e.g. "back to shared area"
+ // while already at the shared view) `jumpTo` is a no-op, so no refetch fires
+ // and the modal would otherwise stay stuck open.
+ const [upgradeModalDismissed, setUpgradeModalDismissed] = useState(false);
const handleZoomToFreeZone = useCallback(() => {
+ setUpgradeModalDismissed(true);
const target = shareReturnViewRef.current ?? INITIAL_VIEW_STATE;
mapFlyToRef.current?.(target.latitude, target.longitude, target.zoom);
}, []);
@@ -576,7 +593,6 @@ export default function MapPage({
const tutorial = useTutorial(initialLoading, isMobile, deferTutorial || mapData.licenseRequired);
const tutorialTheme = useMemo(() => getTutorialStyles(theme), [theme]);
const densityLabel = t('mapLegend.historicalMatches');
- const hasActiveFilters = Object.keys(filters).length > 0 || activeEntries.length > 0;
const mobileLegendMeta = useMobileLegendMeta(viewFeature, features);
const mapViewFeature = useMapViewFeature(viewFeature);
const mobileDensityRange = useMobileDensityRange(mapData);
@@ -661,7 +677,13 @@ export default function MapPage({
}, [onDashboardReadyChange]);
useEffect(() => {
- if (mapData.licenseRequired) trackEvent('Upgrade Modal Shown');
+ if (mapData.licenseRequired) {
+ trackEvent('Upgrade Modal Shown');
+ } else {
+ // Back in a viewable area — re-arm so the modal shows again the next time
+ // the user pans into a gated area.
+ setUpgradeModalDismissed(false);
+ }
}, [mapData.licenseRequired]);
if (screenshotMode) {
@@ -856,22 +878,25 @@ export default function MapPage({
) : null;
- const upgradeModal = mapData.licenseRequired ? (
-
-
- onCheckoutLoginClick ? onCheckoutLoginClick(checkoutReturnPath) : onLoginClick()
- }
- onRegisterClick={() =>
- onCheckoutRegisterClick ? onCheckoutRegisterClick(checkoutReturnPath) : onRegisterClick()
- }
- onStartCheckout={() => license.startCheckout(checkoutReturnPath)}
- onZoomToFreeZone={handleZoomToFreeZone}
- isShareReturn={!!shareReturnViewRef.current}
- />
-
- ) : null;
+ const upgradeModal =
+ mapData.licenseRequired && !upgradeModalDismissed ? (
+
+
+ onCheckoutLoginClick ? onCheckoutLoginClick(checkoutReturnPath) : onLoginClick()
+ }
+ onRegisterClick={() =>
+ onCheckoutRegisterClick
+ ? onCheckoutRegisterClick(checkoutReturnPath)
+ : onRegisterClick()
+ }
+ onStartCheckout={() => license.startCheckout(checkoutReturnPath)}
+ onZoomToFreeZone={handleZoomToFreeZone}
+ isShareReturn={!!shareReturnViewRef.current}
+ />
+
+ ) : null;
if (isMobile) {
return (
@@ -980,7 +1005,7 @@ export default function MapPage({
onToggleActualListings={canSeeListings ? handleToggleActualListings : undefined}
travelTimeEntries={entries}
densityLabel={densityLabel}
- totalCount={hasActiveFilters ? filterCounts.total : undefined}
+ totalCount={filterCounts.total ?? undefined}
poiPaneOpen={poiPaneOpen}
onTogglePoiPane={handleTogglePoiPane}
poiPane={renderPOIPane()}
diff --git a/frontend/src/components/map/PriceHistoryChart.tsx b/frontend/src/components/map/PriceHistoryChart.tsx
index b87b327..846de2a 100644
--- a/frontend/src/components/map/PriceHistoryChart.tsx
+++ b/frontend/src/components/map/PriceHistoryChart.tsx
@@ -6,11 +6,16 @@ interface PriceHistoryChartProps {
points: PricePoint[];
}
-const PADDING = { top: 8, right: 24, bottom: 20, left: 42 };
+const PADDING = { top: 8, right: 24, bottom: 20, left: 48 };
const HEIGHT = 120;
const PRICE_SCALE_TOP_PERCENTILE = 95;
const priceFmt = { prefix: '£' };
+/** Ticks are nice round values; "£800.0k" both clips and reads worse than "£800k". */
+function formatTick(tick: number): string {
+ return formatValue(tick, priceFmt).replace(/\.0(?=[kM])/, '');
+}
+
interface PriceScale {
min: number;
max: number;
@@ -148,7 +153,7 @@ export default function PriceHistoryChart({ points }: PriceHistoryChartProps) {
className="fill-warm-500 dark:fill-warm-400"
fontSize={10}
>
- {formatValue(tick, priceFmt)}
+ {formatTick(tick)}
))}
diff --git a/frontend/src/components/map/TravelTimeCard.tsx b/frontend/src/components/map/TravelTimeCard.tsx
index 646a2ce..e4ce40c 100644
--- a/frontend/src/components/map/TravelTimeCard.tsx
+++ b/frontend/src/components/map/TravelTimeCard.tsx
@@ -9,7 +9,9 @@ import { TravelTimeInfoPopup } from '../ui/TravelTimeInfoPopup';
import { CloseIcon } from '../ui/icons/CloseIcon';
import { EyeIcon } from '../ui/icons/EyeIcon';
import { InfoIcon } from '../ui/icons/InfoIcon';
-import { formatFilterValue, formatNumber } from '../../lib/format';
+import { SliderLabels } from './filters/SliderLabels';
+import { formatNumber } from '../../lib/format';
+import type { FeatureMeta } from '../../types';
import { useTravelDestinations } from '../../hooks/useTravelDestinations';
import {
MAX_TRAVEL_MINUTES,
@@ -56,7 +58,7 @@ export function TravelTimeCard({
dragValue,
onTogglePin,
onSetDestination,
- onTimeRangeChange: _onTimeRangeChange,
+ onTimeRangeChange,
onDragStart,
onDragChange,
onDragEnd,
@@ -86,6 +88,17 @@ export function TravelTimeCard({
const sliderMax = MAX_TRAVEL_MINUTES;
const displayRange = isActive && dragValue ? dragValue : (timeRange ?? [sliderMin, sliderMax]);
+ // Synthetic feature so the time labels reuse the shared SliderLabels (matching
+ // every other filter card) — editable, thumb-following, with the minute unit.
+ const travelFeature: FeatureMeta = {
+ name: 'travelTime',
+ type: 'numeric',
+ min: sliderMin,
+ max: sliderMax,
+ suffix: ` ${t('common.minute')}`,
+ raw: true,
+ };
+
const ModeIcon = MODE_ICONS[mode];
return (
@@ -211,14 +224,17 @@ export function TravelTimeCard({
onPointerDown={() => onDragStart(displayRange)}
onPointerUp={() => onDragEnd()}
/>
-
-
- {formatFilterValue(displayRange[0])} {t('common.minute')}
-
-
- {formatFilterValue(displayRange[1])} {t('common.minute')}
-
-
+ = sliderMax}
+ raw
+ showUnit
+ feature={travelFeature}
+ onValueChange={onTimeRangeChange}
+ />
{filterImpact != null && filterImpact > 0 && (
{t('filters.filtersOut', { value: formatNumber(filterImpact) })}
diff --git a/frontend/src/components/map/filters/SchoolFilterCard.tsx b/frontend/src/components/map/filters/SchoolFilterCard.tsx
index e8de853..672916a 100644
--- a/frontend/src/components/map/filters/SchoolFilterCard.tsx
+++ b/frontend/src/components/map/filters/SchoolFilterCard.tsx
@@ -11,7 +11,6 @@ import {
getSchoolFilterConfig,
getSchoolFilterMeta,
replaceSchoolFilterKeySelection,
- type SchoolDistance,
type SchoolPhase,
type SchoolRating,
} from '../../../lib/school-filter';
@@ -74,7 +73,6 @@ export function SchoolFilterCard({
next: Partial<{
phase: SchoolPhase;
rating: SchoolRating;
- distance: SchoolDistance;
}>
) => {
const nextName = replaceSchoolFilterKeySelection(schoolFeature.name, next);
@@ -180,35 +178,6 @@ export function SchoolFilterCard({
-
-
- {t('filters.distance')}
-
-
- replaceSchoolFeature({ distance: 2 })}
- className={optionClass(config.distance === 2)}
- >
- 2 km
-
- replaceSchoolFeature({ distance: 5 })}
- className={optionClass(config.distance === 5)}
- >
- 5 km
-
-
-
void;
}) {
const { t } = useTranslation();
+ const isDark = useIsDarkTheme();
const license = useLicense();
const [pricing, setPricing] = useState(null);
const [loading, setLoading] = useState(true);
@@ -137,17 +139,23 @@ export default function PricingPage({
) : null;
return (
-
+
-
{t('pricingPage.title')}
-
{t('pricingPage.subtitle')}
-
{t('pricingPage.lessThanSurvey')}
+
+ {t('pricingPage.title')}
+
+
+ {t('pricingPage.subtitle')}
+
+
+ {t('pricingPage.lessThanSurvey')}
+
@@ -203,7 +211,7 @@ export default function PricingPage({
className={`relative flex flex-col rounded-2xl border overflow-hidden w-80 shrink-0 ${
isCurrent
? 'border-teal-400 ring-2 ring-teal-400 shadow-lg'
- : 'border-warm-700 shadow-md'
+ : 'border-warm-300 dark:border-warm-700 shadow-md'
} ${isFilled ? 'opacity-60' : ''}`}
>
{isCurrent && (
@@ -266,11 +274,6 @@ export default function PricingPage({
})}
)}
- {isFilled && (
-
- {t('pricingPage.filled')}
-
- )}
{/* Progress bar for current tier */}
@@ -308,10 +311,14 @@ export default function PricingPage({
{license.error}
)}
- {isFree && (
+ {isFree ? (
{t('pricingPage.noCreditCard')}
+ ) : (
+
+ {t('pricingPage.moneyBack')}
+
)}
>
) : isFilled ? (
diff --git a/frontend/src/components/ui/AuthModal.tsx b/frontend/src/components/ui/AuthModal.tsx
index b4ce606..408f9db 100644
--- a/frontend/src/components/ui/AuthModal.tsx
+++ b/frontend/src/components/ui/AuthModal.tsx
@@ -1,5 +1,5 @@
import { useState, useCallback, useEffect, useId } from 'react';
-import { useTranslation } from 'react-i18next';
+import { Trans, useTranslation } from 'react-i18next';
import { CloseIcon } from './icons/CloseIcon';
import { GoogleIcon } from './icons/GoogleIcon';
import { trackEvent } from '../../lib/analytics';
@@ -106,7 +106,7 @@ export default function AuthModal({
return (
{
if (e.target === e.currentTarget) onClose();
}}
@@ -283,6 +283,32 @@ export default function AuthModal({
{t('auth.backToLogin')}
)}
+
+ {view === 'register' && (
+
+
+ ),
+ privacy: (
+
+ ),
+ }}
+ />
+
+ )}
diff --git a/frontend/src/components/ui/Footer.tsx b/frontend/src/components/ui/Footer.tsx
new file mode 100644
index 0000000..fd8b510
--- /dev/null
+++ b/frontend/src/components/ui/Footer.tsx
@@ -0,0 +1,79 @@
+import { useTranslation } from 'react-i18next';
+import { LogoIcon } from './icons/LogoIcon';
+
+const SUPPORT_EMAIL = 'support@perfect-postcode.co.uk';
+
+function FooterLink({ href, label }: { href: string; label: string }) {
+ return (
+
+
+ {label}
+
+
+ );
+}
+
+export default function Footer() {
+ const { t } = useTranslation();
+ const year = new Date().getFullYear();
+
+ return (
+
+
+
+
+
+
+
+ {t('footer.product')}
+
+
+
+
+
+
+ {t('footer.resources')}
+
+
+
+
+
+
+ {t('footer.legal')}
+
+
+
+
+
+
+
{t('footer.copyright', { year })}
+
{t('footer.coverage')}
+
+
+
+ );
+}
diff --git a/frontend/src/components/ui/Header.tsx b/frontend/src/components/ui/Header.tsx
index 5534dde..51f00d2 100644
--- a/frontend/src/components/ui/Header.tsx
+++ b/frontend/src/components/ui/Header.tsx
@@ -33,6 +33,8 @@ export type Page =
| 'data-sources'
| 'methodology'
| 'privacy-security'
+ | 'terms'
+ | 'privacy'
| 'account'
| 'saved'
| 'invite';
@@ -58,6 +60,8 @@ export const PAGE_PATHS: Record
= {
'data-sources': '/data-sources',
methodology: '/methodology',
'privacy-security': '/privacy-security',
+ terms: '/terms',
+ privacy: '/privacy',
saved: '/saved',
account: '/account',
invite: '/invite',
diff --git a/frontend/src/components/ui/PlaceSearchInput.tsx b/frontend/src/components/ui/PlaceSearchInput.tsx
index eed23ed..9fa5081 100644
--- a/frontend/src/components/ui/PlaceSearchInput.tsx
+++ b/frontend/src/components/ui/PlaceSearchInput.tsx
@@ -18,6 +18,12 @@ interface SearchHook {
handleInputChange: (value: string) => void;
handleKeyDown: (e: React.KeyboardEvent, onSelect: (result: SearchResult) => void) => void;
showEmptySearches: () => void;
+ close?: () => void;
+}
+
+/** Addresses arrive in raw ALL-CAPS Land Registry casing; title-case for display. */
+function titleCaseAddress(address: string): string {
+ return address.toLowerCase().replace(/(^|[\s\-/(])([a-z])/g, (_, sep, c) => sep + c.toUpperCase());
}
interface PlaceSearchInputProps {
@@ -65,6 +71,9 @@ export function PlaceSearchInput({
const dropdown = showDropdown && (
e.preventDefault()}
style={
portal && dropdownPos
? {
@@ -116,8 +125,11 @@ export function PlaceSearchInput({
) : result.type === 'address' ? (
<>
-
- {result.address}
+
+ {titleCaseAddress(result.address)}
{result.postcode}
@@ -126,7 +138,10 @@ export function PlaceSearchInput({
) : (
<>
-
+
{result.name}
{result.city && (
({result.city})
@@ -154,6 +169,9 @@ export function PlaceSearchInput({
onFocus={() => {
search.showEmptySearches();
}}
+ // Without this, instances whose parents lack outside-click handling
+ // leave a detached dropdown floating over the map.
+ onBlur={() => search.close?.()}
onKeyDown={(e) => search.handleKeyDown(e, onSelect)}
aria-label={ariaLabel ?? placeholder}
placeholder={placeholder}
diff --git a/frontend/src/hooks/useAiFilters.ts b/frontend/src/hooks/useAiFilters.ts
index 9f27d70..ae48c56 100644
--- a/frontend/src/hooks/useAiFilters.ts
+++ b/frontend/src/hooks/useAiFilters.ts
@@ -15,6 +15,13 @@ export interface AiTravelTimeFilter {
max?: number;
}
+export interface AiMatchBounds {
+ south: number;
+ west: number;
+ north: number;
+ east: number;
+}
+
export interface AiFiltersResult {
filters: FeatureFilters;
travelTimeFilters: AiTravelTimeFilter[];
@@ -23,6 +30,8 @@ export interface AiFiltersResult {
summary: string;
/** Number of properties matching the proposed filters (excludes travel time) */
matchCount: number;
+ /** Bounding box of the matching properties, if any matched */
+ matchBounds: AiMatchBounds | null;
}
export type AiFilterErrorType = 'auth' | 'limit' | 'error';
@@ -152,6 +161,7 @@ export function useAiFilters(): UseAiFiltersResult {
notes: json.notes || '',
summary: summaryText,
matchCount,
+ matchBounds: (json.match_bounds as AiMatchBounds | undefined) ?? null,
};
setNotes(result.notes || null);
setSummary(summaryText);
diff --git a/frontend/src/hooks/useDeckLayers.ts b/frontend/src/hooks/useDeckLayers.ts
index 3e9cf32..d5c4c09 100644
--- a/frontend/src/hooks/useDeckLayers.ts
+++ b/frontend/src/hooks/useDeckLayers.ts
@@ -176,10 +176,9 @@ export function useDeckLayers({
enumPaletteRef.current = enumPalette;
const countRange = useMemo(() => {
- if (data.length === 0) return { min: 0, max: 1, total: 0 };
+ if (data.length === 0) return { min: 0, max: 1 };
let min = Infinity;
let max = -Infinity;
- let total = 0;
for (const d of data) {
if (viewportBounds) {
if (
@@ -191,24 +190,22 @@ export function useDeckLayers({
continue;
}
const c = d.count as number;
- total += c;
if (c <= 0) continue;
if (c < min) min = c;
if (c > max) max = c;
}
- if (min === Infinity) return { min: 0, max: 1, total: 0 };
- if (min === max) return { min, max: min + 1, total };
- return { min, max, total };
+ if (min === Infinity) return { min: 0, max: 1 };
+ if (min === max) return { min, max: min + 1 };
+ return { min, max };
}, [data, viewportBounds]);
const countRangeRef = useRef(countRange);
countRangeRef.current = countRange;
const postcodeCountRange = useMemo(() => {
- if (postcodeData.length === 0) return { min: 0, max: 1, total: 0 };
+ if (postcodeData.length === 0) return { min: 0, max: 1 };
let min = Infinity;
let max = -Infinity;
- let total = 0;
for (const d of postcodeData) {
if (viewportBounds) {
const [lng, lat] = d.properties.centroid as [number, number];
@@ -221,14 +218,13 @@ export function useDeckLayers({
continue;
}
const c = d.properties.count;
- total += c;
if (c <= 0) continue;
if (c < min) min = c;
if (c > max) max = c;
}
- if (min === Infinity) return { min: 0, max: 1, total: 0 };
- if (min === max) return { min, max: min + 1, total };
- return { min, max, total };
+ if (min === Infinity) return { min: 0, max: 1 };
+ if (min === max) return { min, max: min + 1 };
+ return { min, max };
}, [postcodeData, viewportBounds]);
const postcodeCountRangeRef = useRef(postcodeCountRange);
diff --git a/frontend/src/hooks/useFilterCounts.ts b/frontend/src/hooks/useFilterCounts.ts
index acc9e69..9e55259 100644
--- a/frontend/src/hooks/useFilterCounts.ts
+++ b/frontend/src/hooks/useFilterCounts.ts
@@ -12,8 +12,11 @@ interface FilterCountsResponse {
}
/**
- * Fetches per-filter rejection counts: for each active filter,
- * how many in-bounds properties that filter removes.
+ * Single source of truth for the in-view property count. Returns:
+ * - `total`: exact count of in-bounds properties passing the active filters
+ * (or the full in-bounds total when there are none); null until first load.
+ * - `impacts`: per-filter rejection counts — how many in-bounds properties
+ * each active filter removes.
*/
export function useFilterCounts(
filters: FeatureFilters,
@@ -23,7 +26,10 @@ export function useFilterCounts(
shareCode?: string
) {
const [impacts, setImpacts] = useState>({});
- const [total, setTotal] = useState(0);
+ // null = not loaded yet for the current bounds (hide the counter until the
+ // first response). The number is the exact count of in-bounds properties
+ // passing the active filters — and, with no filters, the total in bounds.
+ const [total, setTotal] = useState(null);
const debounceRef = useRef | null>(null);
const abortRef = useRef(null);
@@ -39,16 +45,7 @@ export function useFilterCounts(
if (!bounds) {
abortRef.current?.abort();
setImpacts({});
- setTotal(0);
- return;
- }
-
- const filterCount = Object.keys(filters).length;
- const hasTravelFilters = travelTimeEntries.some((e) => e.slug && e.timeRange);
- if (filterCount === 0 && !hasTravelFilters) {
- abortRef.current?.abort();
- setImpacts({});
- setTotal(0);
+ setTotal(null);
return;
}
diff --git a/frontend/src/hooks/useFilters.ts b/frontend/src/hooks/useFilters.ts
index b455905..a2fa31c 100644
--- a/frontend/src/hooks/useFilters.ts
+++ b/frontend/src/hooks/useFilters.ts
@@ -180,12 +180,7 @@ export function useFilters({ initialFilters, features }: UseFiltersOptions) {
undoStackRef.current.push(prev);
if (undoStackRef.current.length > 50) undoStackRef.current.shift();
if (name === SCHOOL_FILTER_NAME) {
- const schoolKey = createSchoolFilterKey(
- 'primary',
- 'good',
- 2,
- schoolFilterIdRef.current++
- );
+ const schoolKey = createSchoolFilterKey('primary', 'good', schoolFilterIdRef.current++);
const defaultSchoolFeatureName = getDefaultSchoolFeatureName(features);
const defaultSchoolFeature = defaultSchoolFeatureName
? features.find((feature) => feature.name === defaultSchoolFeatureName)
diff --git a/frontend/src/hooks/useIsDarkTheme.ts b/frontend/src/hooks/useIsDarkTheme.ts
new file mode 100644
index 0000000..8cd3e4d
--- /dev/null
+++ b/frontend/src/hooks/useIsDarkTheme.ts
@@ -0,0 +1,18 @@
+import { useEffect, useState } from 'react';
+
+/**
+ * Tracks whether dark mode is active by observing the html.dark class.
+ * Useful in components that don't receive the theme as a prop (showcase,
+ * pricing backdrop) but must keep canvas/map content in sync with it.
+ */
+export function useIsDarkTheme(): boolean {
+ const [isDark, setIsDark] = useState(() => document.documentElement.classList.contains('dark'));
+ useEffect(() => {
+ const observer = new MutationObserver(() =>
+ setIsDark(document.documentElement.classList.contains('dark'))
+ );
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
+ return () => observer.disconnect();
+ }, []);
+ return isDark;
+}
diff --git a/frontend/src/hooks/useTutorial.ts b/frontend/src/hooks/useTutorial.ts
index f0209ff..423094f 100644
--- a/frontend/src/hooks/useTutorial.ts
+++ b/frontend/src/hooks/useTutorial.ts
@@ -31,7 +31,9 @@ export function useTutorial(initialLoading: boolean, isMobile: boolean, blocked
target: '[data-tutorial="map"]',
title: t('tutorial.step3Title'),
content: t('tutorial.step3Content'),
- placement: 'top' as const,
+ // The map fills the viewport; directional placements push the tooltip
+ // off-screen. Center it over the map instead.
+ placement: 'center' as const,
skipBeacon: true,
},
{
diff --git a/frontend/src/i18n/descriptions.ts b/frontend/src/i18n/descriptions.ts
index 31a279b..2599132 100644
--- a/frontend/src/i18n/descriptions.ts
+++ b/frontend/src/i18n/descriptions.ts
@@ -31,28 +31,20 @@ const descriptions: Record> = {
'Potential energy rating':
'Classe énergétique EPC possible si toutes les améliorations recommandées étaient réalisées',
'Interior height (m)': 'Hauteur intérieure moyenne relevée lors du diagnostic EPC',
- 'Street tree density percentile':
+ 'Tree canopy density percentile':
'Percentile estimé de couverture arborée autour du code postal',
'Within conservation area':
'Indique si le point représentatif du code postal se situe dans une zone de conservation désignée',
'Listed building':
'Indique si le bien semble correspondre à un bâtiment classé répertorié par Historic England',
- 'Good+ primary schools within 2km':
- 'Écoles primaires notées Bien ou Excellent par Ofsted dans un rayon de 2 km',
- 'Good+ secondary schools within 2km':
- 'Collèges/lycées notés Bien ou Excellent par Ofsted dans un rayon de 2 km',
- 'Good+ primary schools within 5km':
- 'Écoles primaires notées Bien ou Excellent par Ofsted dans un rayon de 5 km',
- 'Good+ secondary schools within 5km':
- 'Collèges/lycées notés Bien ou Excellent par Ofsted dans un rayon de 5 km',
- 'Outstanding primary schools within 2km':
- 'Écoles primaires notées Excellent par Ofsted dans un rayon de 2 km',
- 'Outstanding secondary schools within 2km':
- 'Collèges/lycées notés Excellent par Ofsted dans un rayon de 2 km',
- 'Outstanding primary schools within 5km':
- 'Écoles primaires notées Excellent par Ofsted dans un rayon de 5 km',
- 'Outstanding secondary schools within 5km':
- 'Collèges/lycées notés Excellent par Ofsted dans un rayon de 5 km',
+ 'Good+ primary school catchments':
+ 'Écoles primaires notées Bien ou Excellent par Ofsted dont la zone de recrutement modélisée couvre ce code postal',
+ 'Good+ secondary school catchments':
+ 'Collèges/lycées notés Bien ou Excellent par Ofsted dont la zone de recrutement modélisée couvre ce code postal',
+ 'Outstanding primary school catchments':
+ 'Écoles primaires notées Excellent par Ofsted dont la zone de recrutement modélisée couvre ce code postal',
+ 'Outstanding secondary school catchments':
+ 'Collèges/lycées notés Excellent par Ofsted dont la zone de recrutement modélisée couvre ce code postal',
'Education, Skills and Training Score':
'Percentile de défaveur en éducation, compétences et formation (plus élevé = moins défavorisé)',
'Income Score': 'Percentile de défaveur liée au revenu (plus élevé = moins défavorisé)',
@@ -88,7 +80,8 @@ const descriptions: Record> = {
'% White': 'Part de la population s’identifiant comme blanche',
'% South Asian': 'Part de la population s’identifiant comme sud-asiatique',
'% Black': 'Part de la population s’identifiant comme noire',
- '% East/SE Asian': 'Part de la population s’identifiant comme est/sud-est asiatique',
+ '% East Asian': 'Part de la population s’identifiant comme est-asiatique',
+ '% SE Asian': 'Part de la population s’identifiant comme sud-est asiatique',
'% Mixed':
'Part de la population s’identifiant comme métisse ou de plusieurs groupes ethniques',
'% Other': 'Part de la population s’identifiant comme appartenant à un autre groupe ethnique',
@@ -131,28 +124,20 @@ const descriptions: Record> = {
'Potential energy rating':
'Mögliche EPC-Energieeffizienzklasse, wenn alle empfohlenen Maßnahmen umgesetzt würden',
'Interior height (m)': 'Durchschnittliche Raumhöhe laut EPC-Gutachten',
- 'Street tree density percentile':
- 'Geschätztes Perzentil der Straßenbaumdichte rund um den Postcode',
+ 'Tree canopy density percentile':
+ 'Geschätztes Perzentil der Baumkronendichte rund um den Postcode',
'Within conservation area':
'Ob der repräsentative Punkt des Postcodes in einem ausgewiesenen Denkmalschutzgebiet liegt',
'Listed building':
'Ob die Immobilie wahrscheinlich einem Eintrag von Historic England für ein denkmalgeschütztes Gebäude entspricht',
- 'Good+ primary schools within 2km':
- 'Von Ofsted mit Gut oder Hervorragend bewertete Grundschulen im Umkreis von 2 km',
- 'Good+ secondary schools within 2km':
- 'Von Ofsted mit Gut oder Hervorragend bewertete weiterführende Schulen im Umkreis von 2 km',
- 'Good+ primary schools within 5km':
- 'Von Ofsted mit Gut oder Hervorragend bewertete Grundschulen im Umkreis von 5 km',
- 'Good+ secondary schools within 5km':
- 'Von Ofsted mit Gut oder Hervorragend bewertete weiterführende Schulen im Umkreis von 5 km',
- 'Outstanding primary schools within 2km':
- 'Von Ofsted mit Hervorragend bewertete Grundschulen im Umkreis von 2 km',
- 'Outstanding secondary schools within 2km':
- 'Von Ofsted mit Hervorragend bewertete weiterführende Schulen im Umkreis von 2 km',
- 'Outstanding primary schools within 5km':
- 'Von Ofsted mit Hervorragend bewertete Grundschulen im Umkreis von 5 km',
- 'Outstanding secondary schools within 5km':
- 'Von Ofsted mit Hervorragend bewertete weiterführende Schulen im Umkreis von 5 km',
+ 'Good+ primary school catchments':
+ 'Von Ofsted mit Gut oder Hervorragend bewertete Grundschulen, deren modelliertes Einzugsgebiet diese Postleitzahl abdeckt',
+ 'Good+ secondary school catchments':
+ 'Von Ofsted mit Gut oder Hervorragend bewertete weiterführende Schulen, deren modelliertes Einzugsgebiet diese Postleitzahl abdeckt',
+ 'Outstanding primary school catchments':
+ 'Von Ofsted mit Hervorragend bewertete Grundschulen, deren modelliertes Einzugsgebiet diese Postleitzahl abdeckt',
+ 'Outstanding secondary school catchments':
+ 'Von Ofsted mit Hervorragend bewertete weiterführende Schulen, deren modelliertes Einzugsgebiet diese Postleitzahl abdeckt',
'Education, Skills and Training Score':
'Benachteiligungsperzentil für Bildung, Kompetenzen und Ausbildung (höher = weniger benachteiligt)',
'Income Score': 'Einkommensbenachteiligungsperzentil (höher = weniger benachteiligt)',
@@ -188,7 +173,8 @@ const descriptions: Record> = {
'% White': 'Anteil der Personen, die sich als weiß identifizieren',
'% South Asian': 'Anteil der Personen, die sich als südasiatisch identifizieren',
'% Black': 'Anteil der Personen, die sich als schwarz identifizieren',
- '% East/SE Asian': 'Anteil der Personen, die sich als ost-/südostasiatisch identifizieren',
+ '% East Asian': 'Anteil der Personen, die sich als ostasiatisch identifizieren',
+ '% SE Asian': 'Anteil der Personen, die sich als südostasiatisch identifizieren',
'% Mixed':
'Anteil der Personen, die sich als gemischt oder mehreren ethnischen Gruppen zugehörig identifizieren',
'% Other': 'Anteil der Personen, die sich einer anderen ethnischen Gruppe zuordnen',
@@ -229,17 +215,13 @@ const descriptions: Record> = {
'Current energy rating': '当前 EPC 能源评级(A = 最佳,G = 最差)',
'Potential energy rating': '完成所有建议改进后的潜在 EPC 评级',
'Interior height (m)': 'EPC 评估记录的平均室内层高',
- 'Street tree density percentile': '邮编周边估计街道树木覆盖率百分位',
+ 'Tree canopy density percentile': '邮编周边估计树冠覆盖密度百分位',
'Within conservation area': '邮编代表点是否位于指定保护区内',
'Listed building': '该房产是否疑似对应 Historic England 的登录建筑记录',
- 'Good+ primary schools within 2km': '2 公里内 Ofsted 评为良好或优秀的小学',
- 'Good+ secondary schools within 2km': '2 公里内 Ofsted 评为良好或优秀的中学',
- 'Good+ primary schools within 5km': '5 公里内 Ofsted 评为良好或优秀的小学',
- 'Good+ secondary schools within 5km': '5 公里内 Ofsted 评为良好或优秀的中学',
- 'Outstanding primary schools within 2km': '2 公里内 Ofsted 评为优秀的小学',
- 'Outstanding secondary schools within 2km': '2 公里内 Ofsted 评为优秀的中学',
- 'Outstanding primary schools within 5km': '5 公里内 Ofsted 评为优秀的小学',
- 'Outstanding secondary schools within 5km': '5 公里内 Ofsted 评为优秀的中学',
+ 'Good+ primary school catchments': '建模学区覆盖此邮编、Ofsted 评为良好或优秀的小学',
+ 'Good+ secondary school catchments': '建模学区覆盖此邮编、Ofsted 评为良好或优秀的中学',
+ 'Outstanding primary school catchments': '建模学区覆盖此邮编、Ofsted 评为优秀的小学',
+ 'Outstanding secondary school catchments': '建模学区覆盖此邮编、Ofsted 评为优秀的中学',
'Education, Skills and Training Score': '教育、技能与培训剥夺百分位(越高 = 剥夺越少)',
'Income Score': '收入剥夺百分位(越高 = 剥夺越少)',
'Employment Score': '就业剥夺百分位(越高 = 剥夺越少)',
@@ -266,7 +248,8 @@ const descriptions: Record> = {
'% White': '白人人口比例',
'% South Asian': '南亚裔人口比例',
'% Black': '黑人人口比例',
- '% East/SE Asian': '东亚/东南亚裔人口比例',
+ '% East Asian': '东亚裔人口比例',
+ '% SE Asian': '东南亚裔人口比例',
'% Mixed': '混血或多族裔人口比例',
'% Other': '其他族裔人口比例',
'Voter turnout (%)': '2024 年大选中登记选民的投票率',
@@ -304,26 +287,18 @@ const descriptions: Record> = {
'Current energy rating': 'मौजूदा EPC ऊर्जा रेटिंग (A = सबसे अच्छी, G = सबसे खराब)',
'Potential energy rating': 'सभी सुझाए गए सुधार होने पर संभावित EPC रेटिंग',
'Interior height (m)': 'EPC सर्वेक्षण के अनुसार औसत अंदरूनी ऊंचाई',
- 'Street tree density percentile': 'संपत्ति वाली सड़क का अनुमानित वृक्ष आच्छादन प्रतिशतक',
+ 'Tree canopy density percentile': 'पोस्टकोड के आसपास का अनुमानित वृक्ष आच्छादन घनत्व प्रतिशतक',
'Within conservation area': 'पोस्टकोड प्रतिनिधि बिंदु नामित संरक्षण क्षेत्र में है या नहीं',
'Listed building':
'यह संपत्ति Historic England के सूचीबद्ध भवन रिकॉर्ड से मिलती-जुलती है या नहीं',
- 'Good+ primary schools within 2km':
- '2 किमी के भीतर Ofsted से अच्छी या उत्कृष्ट रेटिंग वाले प्राइमरी स्कूल',
- 'Good+ secondary schools within 2km':
- '2 किमी के भीतर Ofsted से अच्छी या उत्कृष्ट रेटिंग वाले सेकेंडरी स्कूल',
- 'Good+ primary schools within 5km':
- '5 किमी के भीतर Ofsted से अच्छी या उत्कृष्ट रेटिंग वाले प्राइमरी स्कूल',
- 'Good+ secondary schools within 5km':
- '5 किमी के भीतर Ofsted से अच्छी या उत्कृष्ट रेटिंग वाले सेकेंडरी स्कूल',
- 'Outstanding primary schools within 2km':
- '2 किमी के भीतर Ofsted से उत्कृष्ट रेटिंग वाले प्राइमरी स्कूल',
- 'Outstanding secondary schools within 2km':
- '2 किमी के भीतर Ofsted से उत्कृष्ट रेटिंग वाले सेकेंडरी स्कूल',
- 'Outstanding primary schools within 5km':
- '5 किमी के भीतर Ofsted से उत्कृष्ट रेटिंग वाले प्राइमरी स्कूल',
- 'Outstanding secondary schools within 5km':
- '5 किमी के भीतर Ofsted से उत्कृष्ट रेटिंग वाले सेकेंडरी स्कूल',
+ 'Good+ primary school catchments':
+ 'Ofsted से अच्छी या उत्कृष्ट रेटिंग वाले प्राइमरी स्कूल जिनका मॉडल किया गया कैचमेंट क्षेत्र इस पोस्टकोड को कवर करता है',
+ 'Good+ secondary school catchments':
+ 'Ofsted से अच्छी या उत्कृष्ट रेटिंग वाले सेकेंडरी स्कूल जिनका मॉडल किया गया कैचमेंट क्षेत्र इस पोस्टकोड को कवर करता है',
+ 'Outstanding primary school catchments':
+ 'Ofsted से उत्कृष्ट रेटिंग वाले प्राइमरी स्कूल जिनका मॉडल किया गया कैचमेंट क्षेत्र इस पोस्टकोड को कवर करता है',
+ 'Outstanding secondary school catchments':
+ 'Ofsted से उत्कृष्ट रेटिंग वाले सेकेंडरी स्कूल जिनका मॉडल किया गया कैचमेंट क्षेत्र इस पोस्टकोड को कवर करता है',
'Education, Skills and Training Score': 'शिक्षा और कौशल वंचना प्रतिशतक (अधिक = कम वंचना)',
'Income Score': 'आय वंचना प्रतिशतक (अधिक = कम वंचना)',
'Employment Score': 'रोजगार वंचना प्रतिशतक (अधिक = कम वंचना)',
@@ -352,7 +327,8 @@ const descriptions: Record> = {
'% White': 'श्वेत के रूप में पहचान करने वाली आबादी का प्रतिशत',
'% South Asian': 'दक्षिण एशियाई के रूप में पहचान करने वाली आबादी का प्रतिशत',
'% Black': 'अश्वेत के रूप में पहचान करने वाली आबादी का प्रतिशत',
- '% East/SE Asian': 'पूर्वी/दक्षिण-पूर्वी एशियाई के रूप में पहचान करने वाली आबादी का प्रतिशत',
+ '% East Asian': 'पूर्वी एशियाई के रूप में पहचान करने वाली आबादी का प्रतिशत',
+ '% SE Asian': 'दक्षिण-पूर्वी एशियाई के रूप में पहचान करने वाली आबादी का प्रतिशत',
'% Mixed': 'मिश्रित या कई जातीय समूहों से पहचान करने वाली आबादी का प्रतिशत',
'% Other': 'अन्य जातीय समूह के रूप में पहचान करने वाली आबादी का प्रतिशत',
'Voter turnout (%)': '2024 आम चुनाव में मतदान करने वाले पंजीकृत मतदाताओं का प्रतिशत',
@@ -391,28 +367,20 @@ const descriptions: Record> = {
'Potential energy rating':
'Potenciális EPC besorolás az összes javasolt fejlesztés elvégzése után',
'Interior height (m)': 'Átlagos belmagasság az EPC felmérés alapján',
- 'Street tree density percentile':
- 'Az ingatlan utcájának becsült lombkorona-fedettségi percentilise',
+ 'Tree canopy density percentile':
+ 'A postai irányítószám környékének becsült lombkorona-fedettségi percentilise',
'Within conservation area':
'Az irányítószám reprezentatív pontja kijelölt műemléki területre esik-e',
'Listed building':
'Az ingatlan látszólag megfelel-e egy Historic England műemléki épület bejegyzésének',
- 'Good+ primary schools within 2km':
- 'Ofsted által Jó vagy Kiváló minősítésű általános iskolák 2 km-en belül',
- 'Good+ secondary schools within 2km':
- 'Ofsted által Jó vagy Kiváló minősítésű középiskolák 2 km-en belül',
- 'Good+ primary schools within 5km':
- 'Ofsted által Jó vagy Kiváló minősítésű általános iskolák 5 km-en belül',
- 'Good+ secondary schools within 5km':
- 'Ofsted által Jó vagy Kiváló minősítésű középiskolák 5 km-en belül',
- 'Outstanding primary schools within 2km':
- 'Ofsted által Kiváló minősítésű általános iskolák 2 km-en belül',
- 'Outstanding secondary schools within 2km':
- 'Ofsted által Kiváló minősítésű középiskolák 2 km-en belül',
- 'Outstanding primary schools within 5km':
- 'Ofsted által Kiváló minősítésű általános iskolák 5 km-en belül',
- 'Outstanding secondary schools within 5km':
- 'Ofsted által Kiváló minősítésű középiskolák 5 km-en belül',
+ 'Good+ primary school catchments':
+ 'Ofsted által Jó vagy Kiváló minősítésű általános iskolák, amelyek modellezett körzete lefedi ezt az irányítószámot',
+ 'Good+ secondary school catchments':
+ 'Ofsted által Jó vagy Kiváló minősítésű középiskolák, amelyek modellezett körzete lefedi ezt az irányítószámot',
+ 'Outstanding primary school catchments':
+ 'Ofsted által Kiváló minősítésű általános iskolák, amelyek modellezett körzete lefedi ezt az irányítószámot',
+ 'Outstanding secondary school catchments':
+ 'Ofsted által Kiváló minősítésű középiskolák, amelyek modellezett körzete lefedi ezt az irányítószámot',
'Education, Skills and Training Score':
'Oktatási és készségbeli deprivációs percentilis (magasabb = kevésbé hátrányos)',
'Income Score': 'Jövedelmi deprivációs percentilis (magasabb = kevésbé hátrányos)',
@@ -444,7 +412,8 @@ const descriptions: Record> = {
'% White': 'A fehérként azonosított lakosság aránya',
'% South Asian': 'A dél-ázsiaiként azonosított lakosság aránya',
'% Black': 'A feketeként azonosított lakosság aránya',
- '% East/SE Asian': 'A kelet-/délkelet-ázsiaiként azonosított lakosság aránya',
+ '% East Asian': 'A kelet-ázsiaiként azonosított lakosság aránya',
+ '% SE Asian': 'A délkelet-ázsiaiként azonosított lakosság aránya',
'% Mixed': 'A vegyes vagy több etnikai csoporthoz tartozóként azonosított lakosság aránya',
'% Other': 'Az egyéb etnikai csoportba tartozóként azonosított lakosság aránya',
'Voter turnout (%)':
diff --git a/frontend/src/i18n/details.ts b/frontend/src/i18n/details.ts
index 578433b..7fd5619 100644
--- a/frontend/src/i18n/details.ts
+++ b/frontend/src/i18n/details.ts
@@ -35,28 +35,20 @@ export const details: Record> = {
"La note d'efficacité énergétique potentielle issue du certificat de performance énergétique (EPC), si toutes les améliorations rentables recommandées dans le rapport EPC étaient réalisées. Va de A (plus efficace) à G (moins efficace).",
'Interior height (m)':
"Hauteur intérieure moyenne (sol au plafond) en mètres telle qu'enregistrée lors de l'évaluation du certificat de performance énergétique (EPC). Calculée en divisant le volume intérieur total par la surface habitable totale.",
- 'Street tree density percentile':
- "Couverture arborée approximative autour du centroïde du code postal, dérivée de la carte Trees Outside Woodland 2025 de Forest Research. Les polygones de couvert arboré des arbres isolés et groupes d'arbres sont comptés dans un rayon de 50 m de chaque centroïde de code postal, puis convertis en percentile parmi les codes postaux anglais. Il s'agit d'une approximation fondée sur le centroïde du code postal, pas d'une mesure exacte du bien ou du segment de rue.",
+ 'Tree canopy density percentile':
+ "Couverture arborée approximative autour du centroïde du code postal, dérivée de la carte Trees Outside Woodland 2025 de Forest Research et de l'Inventaire forestier national (NFI). Les polygones de couvert arboré des arbres isolés, des groupes d'arbres ET des boisements de l'Inventaire forestier national sont comptés dans un rayon de 50 m de chaque centroïde de code postal, puis convertis en percentile parmi les codes postaux anglais. Il s'agit d'une approximation fondée sur le centroïde du code postal, pas d'une mesure exacte du bien ou du segment de rue.",
'Within conservation area':
"Limites des conservation areas dans Planning Data, rattachées au point représentatif du code postal. Le jeu de données national est encore en cours de constitution et peut contenir des doublons ou une couverture locale incomplète ; toute décision dépendant précisément d'une limite doit être vérifiée auprès de la local planning authority.",
'Listed building':
"Points de listed buildings dans la National Heritage List for England de Historic England, appariés prudemment aux adresses à partir du nom de l'entrée classée et de codes postaux candidats à proximité. À utiliser comme signal de présélection, pas comme conclusion juridique : vérifiez tout bien précis dans la NHLE et auprès de la local planning authority.",
- 'Good+ primary schools within 2km':
- "Écoles primaires financées par l'État dans un rayon de 2 km ayant une note Ofsted actuelle Good ou Outstanding. Les écoles non encore inspectées sont exclues.",
- 'Good+ secondary schools within 2km':
- "Écoles secondaires financées par l'État dans un rayon de 2 km ayant une note Ofsted actuelle Good ou Outstanding. Les établissements non encore inspectés sont exclus.",
- 'Good+ primary schools within 5km':
- "Écoles primaires financées par l'État dans un rayon de 5 km ayant une note Ofsted actuelle Good ou Outstanding. Les écoles non encore inspectées sont exclues.",
- 'Good+ secondary schools within 5km':
- "Écoles secondaires financées par l'État dans un rayon de 5 km ayant une note Ofsted actuelle Good ou Outstanding. Les établissements non encore inspectés sont exclus.",
- 'Outstanding primary schools within 2km':
- "Écoles primaires financées par l'État dans un rayon de 2 km ayant une note Ofsted actuelle Outstanding. Les écoles non encore inspectées sont exclues.",
- 'Outstanding secondary schools within 2km':
- "Écoles secondaires financées par l'État dans un rayon de 2 km ayant une note Ofsted actuelle Outstanding. Les établissements non encore inspectés sont exclus.",
- 'Outstanding primary schools within 5km':
- "Écoles primaires financées par l'État dans un rayon de 5 km ayant une note Ofsted actuelle Outstanding. Les écoles non encore inspectées sont exclues.",
- 'Outstanding secondary schools within 5km':
- "Écoles secondaires financées par l'État dans un rayon de 5 km ayant une note Ofsted actuelle Outstanding. Les établissements non encore inspectés sont exclus.",
+ 'Good+ primary school catchments':
+ "Nombre d'écoles primaires financées par l'État ayant une note Ofsted actuelle Good ou Outstanding dont les élèves proviennent d'une zone couvrant ce code postal. Les rayons de recrutement sont modélisés à partir des effectifs de chaque école et de la population locale d'enfants (recensement 2021), en approchant des admissions fondées sur la distance ; ce sont des estimations historiques, pas des secteurs d'admission officiels. Les écoles non encore inspectées sont exclues.",
+ 'Good+ secondary school catchments':
+ "Nombre d'écoles secondaires financées par l'État ayant une note Ofsted actuelle Good ou Outstanding dont les élèves proviennent d'une zone couvrant ce code postal. Les rayons de recrutement sont modélisés à partir des effectifs de chaque école et de la population locale d'enfants (recensement 2021), en approchant des admissions fondées sur la distance ; ce sont des estimations historiques, pas des secteurs d'admission officiels. Les établissements non encore inspectés sont exclus.",
+ 'Outstanding primary school catchments':
+ "Nombre d'écoles primaires financées par l'État ayant une note Ofsted actuelle Outstanding dont les élèves proviennent d'une zone couvrant ce code postal. Les rayons de recrutement sont modélisés à partir des effectifs de chaque école et de la population locale d'enfants (recensement 2021), en approchant des admissions fondées sur la distance ; ce sont des estimations historiques, pas des secteurs d'admission officiels. Les écoles non encore inspectées sont exclues.",
+ 'Outstanding secondary school catchments':
+ "Nombre d'écoles secondaires financées par l'État ayant une note Ofsted actuelle Outstanding dont les élèves proviennent d'une zone couvrant ce code postal. Les rayons de recrutement sont modélisés à partir des effectifs de chaque école et de la population locale d'enfants (recensement 2021), en approchant des admissions fondées sur la distance ; ce sont des estimations historiques, pas des secteurs d'admission officiels. Les établissements non encore inspectés sont exclus.",
'Education, Skills and Training Score':
"Issu des English Indices of Deprivation, converti en percentile national : 0 % correspond aux zones les plus défavorisées et 100 % aux moins défavorisées. Couvre les résultats scolaires, l'accès à l'enseignement supérieur, les qualifications des adultes et la maîtrise de l'anglais.",
'Income Score':
@@ -109,8 +101,10 @@ export const details: Record> = {
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Indien, Pakistanais, Bangladais ou toute autre origine asiatique.",
'% Black':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Noir, Noir britannique, Caribéen ou Africain.",
- '% East/SE Asian':
- "Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Chinois ou d'une autre origine est/sud-est asiatique.",
+ '% East Asian':
+ "Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Chinois.",
+ '% SE Asian':
+ "Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme appartenant à une autre origine est/sud-est asiatique (par ex. Philippin, Vietnamien ou Thaïlandais).",
'% Mixed':
"Provient du Census 2021. Pourcentage de la population de l'autorité locale s'identifiant comme Mixte ou appartenant à plusieurs groupes ethniques (Blanc et Noir caribéen, Blanc et Noir africain, Blanc et Asiatique, ou tout autre fond mixte ou multiple).",
'% Other':
@@ -181,28 +175,20 @@ export const details: Record> = {
'Die potenzielle Energieeffizienzklasse aus dem Energieausweis-Zertifikat, wenn alle im EPC-Bericht empfohlenen kosteneffizienten Verbesserungen durchgeführt würden. Reicht von A (am effizientesten) bis G (am wenigsten effizient).',
'Interior height (m)':
'Durchschnittliche lichte Raumhöhe in Metern, wie während der Energieausweis-Begutachtung erfasst. Berechnet durch Division des gesamten Innenvolumens durch die Gesamtwohnfläche.',
- 'Street tree density percentile':
- 'Ungefähre Baumkronenbedeckung rund um den Postleitzahlen-Zentroiden aus der Forest-Research-Karte Trees Outside Woodland 2025. Baumkronen-Polygone für Einzelbäume und Baumgruppen werden im Umkreis von 50 m um jeden Postleitzahlen-Zentroiden gezählt und dann in ein Perzentil über englische Postleitzahlen umgerechnet. Dies ist ein Näherungswert auf Basis des Postleitzahlen-Zentroids, keine exakte Messung für Immobilie oder Straßenabschnitt.',
+ 'Tree canopy density percentile':
+ 'Ungefähre Baumkronenbedeckung rund um den Postleitzahlen-Zentroiden aus der Forest-Research-Karte Trees Outside Woodland 2025 und dem National Forest Inventory (NFI). Baumkronen-Polygone für Einzelbäume, Baumgruppen UND Waldflächen des National Forest Inventory werden im Umkreis von 50 m um jeden Postleitzahlen-Zentroiden gezählt und dann in ein Perzentil über englische Postleitzahlen umgerechnet. Dies ist ein Näherungswert auf Basis des Postleitzahlen-Zentroids, keine exakte Messung für Immobilie oder Straßenabschnitt.',
'Within conservation area':
'Planning-Data-Grenzen für Erhaltungsgebiete, dem repräsentativen Punkt der Postleitzahl zugeordnet. Der nationale Datensatz wird laufend verbessert und kann Duplikate oder unvollständige lokale Abdeckung enthalten; grenznahe Entscheidungen sollten bei der lokalen Planungsbehörde geprüft werden.',
'Listed building':
'Punktdaten zu denkmalgeschützten Gebäuden aus der National Heritage List for England von Historic England, vorsichtig mit Immobilienadressen abgeglichen anhand des Namens des Denkmaleintrags und nahegelegener Postleitzahlkandidaten. Behandle dies als Vorauswahl-Hinweis, nicht als rechtliche Feststellung: Prüfe jede konkrete Immobilie in der NHLE und bei der lokalen Planungsbehörde.',
- 'Good+ primary schools within 2km':
- 'Staatlich geförderte Grundschulen innerhalb von 2 km mit einer aktuellen Ofsted-Bewertung von Gut oder Hervorragend. Noch nicht inspizierte Schulen sind ausgeschlossen.',
- 'Good+ secondary schools within 2km':
- 'Staatlich geförderte weiterführende Schulen innerhalb von 2 km mit einer aktuellen Ofsted-Bewertung von Gut oder Hervorragend. Noch nicht inspizierte Schulen sind ausgeschlossen.',
- 'Good+ primary schools within 5km':
- 'Staatlich geförderte Grundschulen innerhalb von 5 km mit einer aktuellen Ofsted-Bewertung von Gut oder Hervorragend. Noch nicht inspizierte Schulen sind ausgeschlossen.',
- 'Good+ secondary schools within 5km':
- 'Staatlich geförderte weiterführende Schulen innerhalb von 5 km mit einer aktuellen Ofsted-Bewertung von Gut oder Hervorragend. Noch nicht inspizierte Schulen sind ausgeschlossen.',
- 'Outstanding primary schools within 2km':
- 'Staatlich geförderte Grundschulen innerhalb von 2 km mit einer aktuellen Ofsted-Bewertung von Hervorragend. Noch nicht inspizierte Schulen sind ausgeschlossen.',
- 'Outstanding secondary schools within 2km':
- 'Staatlich geförderte weiterführende Schulen innerhalb von 2 km mit einer aktuellen Ofsted-Bewertung von Hervorragend. Noch nicht inspizierte Schulen sind ausgeschlossen.',
- 'Outstanding primary schools within 5km':
- 'Staatlich geförderte Grundschulen innerhalb von 5 km mit einer aktuellen Ofsted-Bewertung von Hervorragend. Noch nicht inspizierte Schulen sind ausgeschlossen.',
- 'Outstanding secondary schools within 5km':
- 'Staatlich geförderte weiterführende Schulen innerhalb von 5 km mit einer aktuellen Ofsted-Bewertung von Hervorragend. Noch nicht inspizierte Schulen sind ausgeschlossen.',
+ 'Good+ primary school catchments':
+ 'Wie viele staatlich geförderte Grundschulen mit einer aktuellen Ofsted-Bewertung von Gut oder Hervorragend ihre Schüler aus einem Gebiet beziehen, das diese Postleitzahl abdeckt. Die Einzugsradien werden aus den Schülerzahlen jeder Schule und der lokalen Kinderbevölkerung (Zensus 2021) modelliert und nähern entfernungsbasierte Aufnahmen an; es sind historische Schätzungen, keine offiziellen Aufnahmegebiete. Noch nicht inspizierte Schulen sind ausgeschlossen.',
+ 'Good+ secondary school catchments':
+ 'Wie viele staatlich geförderte weiterführende Schulen mit einer aktuellen Ofsted-Bewertung von Gut oder Hervorragend ihre Schüler aus einem Gebiet beziehen, das diese Postleitzahl abdeckt. Die Einzugsradien werden aus den Schülerzahlen jeder Schule und der lokalen Kinderbevölkerung (Zensus 2021) modelliert und nähern entfernungsbasierte Aufnahmen an; es sind historische Schätzungen, keine offiziellen Aufnahmegebiete. Noch nicht inspizierte Schulen sind ausgeschlossen.',
+ 'Outstanding primary school catchments':
+ 'Wie viele staatlich geförderte Grundschulen mit einer aktuellen Ofsted-Bewertung von Hervorragend ihre Schüler aus einem Gebiet beziehen, das diese Postleitzahl abdeckt. Die Einzugsradien werden aus den Schülerzahlen jeder Schule und der lokalen Kinderbevölkerung (Zensus 2021) modelliert und nähern entfernungsbasierte Aufnahmen an; es sind historische Schätzungen, keine offiziellen Aufnahmegebiete. Noch nicht inspizierte Schulen sind ausgeschlossen.',
+ 'Outstanding secondary school catchments':
+ 'Wie viele staatlich geförderte weiterführende Schulen mit einer aktuellen Ofsted-Bewertung von Hervorragend ihre Schüler aus einem Gebiet beziehen, das diese Postleitzahl abdeckt. Die Einzugsradien werden aus den Schülerzahlen jeder Schule und der lokalen Kinderbevölkerung (Zensus 2021) modelliert und nähern entfernungsbasierte Aufnahmen an; es sind historische Schätzungen, keine offiziellen Aufnahmegebiete. Noch nicht inspizierte Schulen sind ausgeschlossen.',
'Education, Skills and Training Score':
'Aus den englischen Deprivationsindizes, in ein nationales Perzentil umgerechnet: 0 % steht für die am stärksten benachteiligten Gebiete, 100 % für die am wenigsten benachteiligten. Umfasst Schulleistungen, Hochschulzugang, Qualifikationen Erwachsener und Englischsprachkenntnisse.',
'Income Score':
@@ -255,8 +241,10 @@ export const details: Record> = {
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als Indisch, Pakistanisch, Bangladeschisch oder mit sonstigem asiatischen Hintergrund identifiziert.',
'% Black':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als Schwarz, Schwarz-Britisch, Karibisch oder Afrikanisch identifiziert.',
- '% East/SE Asian':
- 'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als Chinesisch oder einer anderen ost-/südostasiatischen Herkunft identifiziert.',
+ '% East Asian':
+ 'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als Chinesisch identifiziert.',
+ '% SE Asian':
+ 'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich mit einer anderen (nicht chinesischen) ost-/südostasiatischen Herkunft identifiziert, z. B. philippinisch, vietnamesisch oder thailändisch.',
'% Mixed':
'Aus dem Census 2021. Prozentsatz der Bevölkerung der Gemeinde, die sich als gemischt oder mit mehreren ethnischen Zugehörigkeiten identifiziert (Weiß und Schwarzkaribisch, Weiß und Schwarzafrikanisch, Weiß und Asiatisch oder sonstiger gemischter Hintergrund).',
'% Other':
@@ -327,28 +315,20 @@ export const details: Record> = {
'若实施EPC报告中建议的所有具有成本效益的改进措施后,该房产的潜在能源效率等级。从A(最高效)到G(最低效)。',
'Interior height (m)':
'EPC评估期间记录的平均室内净高(米)。通过将室内总容积除以总建筑面积计算得出。',
- 'Street tree density percentile':
- '基于 Forest Research 2025 年 Trees Outside Woodland 地图估算的邮编质心周边树冠覆盖率。系统会统计每个邮编质心 50 米范围内的孤立树木和树群树冠多边形,然后转换为英格兰邮编范围内的百分位。这是邮编质心近似指标,不是精确的房产或道路路段测量。',
+ 'Tree canopy density percentile':
+ '基于 Forest Research 2025 年 Trees Outside Woodland 地图与国家森林清查(NFI)估算的邮编质心周边树冠覆盖率。系统会统计每个邮编质心 50 米范围内的孤立树木、树群以及国家森林清查林地的树冠多边形,然后转换为英格兰邮编范围内的百分位。这是邮编质心近似指标,不是精确的房产或道路路段测量。',
'Within conservation area':
'Planning Data 保护区边界,与邮编代表点匹配。全国数据集仍在完善中,可能包含重复记录或地方覆盖不完整;涉及边界的决策应向地方规划部门核实。',
'Listed building':
'Historic England 英格兰国家遗产名录(NHLE)中的受保护建筑点位记录,会根据名录条目名称和附近候选邮编,谨慎匹配到房产地址。请把它当作初筛信号,而不是法律认定:具体房产应在 NHLE 和地方规划部门核实。',
- 'Good+ primary schools within 2km':
- '2km范围内Ofsted评级为“良好”或“优秀”的公立小学数量。尚未接受评估的学校不计入。',
- 'Good+ secondary schools within 2km':
- '2km范围内Ofsted评级为“良好”或“优秀”的公立中学数量。尚未接受评估的学校不计入。',
- 'Good+ primary schools within 5km':
- '5km范围内Ofsted评级为“良好”或“优秀”的公立小学数量。尚未接受评估的学校不计入。',
- 'Good+ secondary schools within 5km':
- '5km范围内Ofsted评级为“良好”或“优秀”的公立中学数量。尚未接受评估的学校不计入。',
- 'Outstanding primary schools within 2km':
- '2km范围内Ofsted评级为“优秀”的公立小学数量。尚未接受评估的学校不计入。',
- 'Outstanding secondary schools within 2km':
- '2km范围内Ofsted评级为“优秀”的公立中学数量。尚未接受评估的学校不计入。',
- 'Outstanding primary schools within 5km':
- '5km范围内Ofsted评级为“优秀”的公立小学数量。尚未接受评估的学校不计入。',
- 'Outstanding secondary schools within 5km':
- '5km范围内Ofsted评级为“优秀”的公立中学数量。尚未接受评估的学校不计入。',
+ 'Good+ primary school catchments':
+ '有多少所Ofsted当前评级为“良好”或“优秀”的公立小学,其生源来自覆盖此邮编的区域。学区半径根据各校学生人数和当地儿童人口(2021年人口普查)建模,近似按距离录取;这些是历史估计,并非官方招生范围。尚未接受评估的学校不计入。',
+ 'Good+ secondary school catchments':
+ '有多少所Ofsted当前评级为“良好”或“优秀”的公立中学,其生源来自覆盖此邮编的区域。学区半径根据各校学生人数和当地儿童人口(2021年人口普查)建模,近似按距离录取;这些是历史估计,并非官方招生范围。尚未接受评估的学校不计入。',
+ 'Outstanding primary school catchments':
+ '有多少所Ofsted当前评级为“优秀”的公立小学,其生源来自覆盖此邮编的区域。学区半径根据各校学生人数和当地儿童人口(2021年人口普查)建模,近似按距离录取;这些是历史估计,并非官方招生范围。尚未接受评估的学校不计入。',
+ 'Outstanding secondary school catchments':
+ '有多少所Ofsted当前评级为“优秀”的公立中学,其生源来自覆盖此邮编的区域。学区半径根据各校学生人数和当地儿童人口(2021年人口普查)建模,近似按距离录取;这些是历史估计,并非官方招生范围。尚未接受评估的学校不计入。',
'Education, Skills and Training Score':
'来自英格兰剥夺指数,转换为全国百分位:0%表示最贫困,100%表示最不贫困。涵盖学校成绩、高等教育入学率、成人学历和英语水平。',
'Income Score':
@@ -398,7 +378,9 @@ export const details: Record> = {
'% South Asian':
'来自2021年Census。地方政府人口中认同为印度人、巴基斯坦人、孟加拉国人或其他亚洲背景的百分比。',
'% Black': '来自2021年Census。地方政府人口中认同为黑人、英国黑人、加勒比人或非洲人的百分比。',
- '% East/SE Asian': '来自2021年Census。地方政府人口中认同为华人或其他东亚/东南亚裔的百分比。',
+ '% East Asian': '来自2021年Census。地方政府人口中认同为华人的百分比。',
+ '% SE Asian':
+ '来自2021年Census。地方政府人口中认同为其他(非华人)东亚/东南亚裔(如菲律宾裔、越南裔或泰裔)的百分比。',
'% Mixed':
'来自2021年 Census。地方政府人口中认同为混血或多种族群体(白人与黑人加勒比裔、白人与黑人非洲裔、白人与亚洲裔,或其他混血或多种族背景)的百分比。',
'% Other':
@@ -465,28 +447,20 @@ export const details: Record> = {
'EPC से संभावित ऊर्जा दक्षता रेटिंग, यदि EPC रिपोर्ट में सुझाए गए सभी किफायती सुधार कर दिए जाएं. यह A (सबसे दक्ष) से G (सबसे कम दक्ष) तक होती है.',
'Interior height (m)':
'EPC आकलन के दौरान दर्ज औसत अंदरूनी फर्श-से-छत ऊंचाई, मीटर में. कुल आंतरिक आयतन को कुल फर्श क्षेत्र से भाग देकर निकाली जाती है.',
- 'Street tree density percentile':
- 'Forest Research के 2025 Trees Outside Woodland नक्शे से निकाला गया पोस्टकोड केंद्र के आसपास का अनुमानित वृक्ष आच्छादन. अकेले पेड़ों और पेड़ों के समूहों के वृक्ष-शिखर बहुभुजों को हर पोस्टकोड केंद्र से 50m के भीतर गिना जाता है, फिर इंग्लैंड के पोस्टकोडों के मुकाबले प्रतिशतक में बदला जाता है. यह पोस्टकोड-केंद्र पर आधारित अनुमानक है, किसी संपत्ति या सड़क-खंड की सटीक माप नहीं.',
+ 'Tree canopy density percentile':
+ 'Forest Research के 2025 Trees Outside Woodland नक्शे और राष्ट्रीय वन सूची (NFI) से निकाला गया पोस्टकोड केंद्र के आसपास का अनुमानित वृक्ष आच्छादन. अकेले पेड़ों, पेड़ों के समूहों और राष्ट्रीय वन सूची की वनभूमि के वृक्ष-शिखर बहुभुजों को हर पोस्टकोड केंद्र से 50m के भीतर गिना जाता है, फिर इंग्लैंड के पोस्टकोडों के मुकाबले प्रतिशतक में बदला जाता है. यह पोस्टकोड-केंद्र पर आधारित अनुमानक है, किसी संपत्ति या सड़क-खंड की सटीक माप नहीं.',
'Within conservation area':
'Planning Data संरक्षण क्षेत्र सीमाएं पोस्टकोड प्रतिनिधि बिंदु से मिलाई जाती हैं. राष्ट्रीय डेटासेट अभी बेहतर किया जा रहा है और इसमें डुप्लीकेट या अधूरी स्थानीय कवरेज हो सकती है; सीमा-संवेदनशील निर्णय स्थानीय योजना प्राधिकरण से जांचे जाने चाहिए.',
'Listed building':
'Historic England की इंग्लैंड की राष्ट्रीय धरोहर सूची (NHLE) में सूचीबद्ध भवनों के बिंदु रिकॉर्ड, जिन्हें सूचीबद्ध प्रविष्टि के नाम और पास के संभावित पोस्टकोड के आधार पर संपत्ति पते से सावधानी से मिलाया गया है. इसे केवल प्रारंभिक जांच संकेत मानें, कानूनी निर्णय नहीं: किसी भी विशिष्ट संपत्ति को NHLE और स्थानीय योजना प्राधिकरण से सत्यापित करें.',
- 'Good+ primary schools within 2km':
- '2 km के भीतर सरकारी वित्तपोषित प्राइमरी स्कूल जिनकी मौजूदा Ofsted रेटिंग अच्छी या उत्कृष्ट है. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
- 'Good+ secondary schools within 2km':
- '2 km के भीतर सरकारी वित्तपोषित सेकेंडरी स्कूल जिनकी मौजूदा Ofsted रेटिंग अच्छी या उत्कृष्ट है. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
- 'Good+ primary schools within 5km':
- '5 km के भीतर सरकारी वित्तपोषित प्राइमरी स्कूल जिनकी मौजूदा Ofsted रेटिंग अच्छी या उत्कृष्ट है. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
- 'Good+ secondary schools within 5km':
- '5 km के भीतर सरकारी वित्तपोषित सेकेंडरी स्कूल जिनकी मौजूदा Ofsted रेटिंग अच्छी या उत्कृष्ट है. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
- 'Outstanding primary schools within 2km':
- '2 km के भीतर सरकारी वित्तपोषित प्राइमरी स्कूल जिनकी मौजूदा Ofsted रेटिंग उत्कृष्ट है. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
- 'Outstanding secondary schools within 2km':
- '2 km के भीतर सरकारी वित्तपोषित सेकेंडरी स्कूल जिनकी मौजूदा Ofsted रेटिंग उत्कृष्ट है. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
- 'Outstanding primary schools within 5km':
- '5 km के भीतर सरकारी वित्तपोषित प्राइमरी स्कूल जिनकी मौजूदा Ofsted रेटिंग उत्कृष्ट है. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
- 'Outstanding secondary schools within 5km':
- '5 km के भीतर सरकारी वित्तपोषित सेकेंडरी स्कूल जिनकी मौजूदा Ofsted रेटिंग उत्कृष्ट है. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
+ 'Good+ primary school catchments':
+ 'कितने सरकारी वित्तपोषित प्राइमरी स्कूल, जिनकी मौजूदा Ofsted रेटिंग अच्छी या उत्कृष्ट है, अपने छात्र इस पोस्टकोड को कवर करने वाले क्षेत्र से लेते हैं. कैचमेंट दायरे हर स्कूल की छात्र संख्या और स्थानीय बाल जनसंख्या (जनगणना 2021) से मॉडल किए जाते हैं, जो दूरी-आधारित दाखिलों का अनुमान है; ये ऐतिहासिक अनुमान हैं, आधिकारिक दाखिला क्षेत्र नहीं. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
+ 'Good+ secondary school catchments':
+ 'कितने सरकारी वित्तपोषित सेकेंडरी स्कूल, जिनकी मौजूदा Ofsted रेटिंग अच्छी या उत्कृष्ट है, अपने छात्र इस पोस्टकोड को कवर करने वाले क्षेत्र से लेते हैं. कैचमेंट दायरे हर स्कूल की छात्र संख्या और स्थानीय बाल जनसंख्या (जनगणना 2021) से मॉडल किए जाते हैं, जो दूरी-आधारित दाखिलों का अनुमान है; ये ऐतिहासिक अनुमान हैं, आधिकारिक दाखिला क्षेत्र नहीं. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
+ 'Outstanding primary school catchments':
+ 'कितने सरकारी वित्तपोषित प्राइमरी स्कूल, जिनकी मौजूदा Ofsted रेटिंग उत्कृष्ट है, अपने छात्र इस पोस्टकोड को कवर करने वाले क्षेत्र से लेते हैं. कैचमेंट दायरे हर स्कूल की छात्र संख्या और स्थानीय बाल जनसंख्या (जनगणना 2021) से मॉडल किए जाते हैं, जो दूरी-आधारित दाखिलों का अनुमान है; ये ऐतिहासिक अनुमान हैं, आधिकारिक दाखिला क्षेत्र नहीं. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
+ 'Outstanding secondary school catchments':
+ 'कितने सरकारी वित्तपोषित सेकेंडरी स्कूल, जिनकी मौजूदा Ofsted रेटिंग उत्कृष्ट है, अपने छात्र इस पोस्टकोड को कवर करने वाले क्षेत्र से लेते हैं. कैचमेंट दायरे हर स्कूल की छात्र संख्या और स्थानीय बाल जनसंख्या (जनगणना 2021) से मॉडल किए जाते हैं, जो दूरी-आधारित दाखिलों का अनुमान है; ये ऐतिहासिक अनुमान हैं, आधिकारिक दाखिला क्षेत्र नहीं. जिन स्कूलों का अभी निरीक्षण नहीं हुआ है, उन्हें शामिल नहीं किया गया है.',
'Education, Skills and Training Score':
'English Indices of Deprivation से लिया गया और राष्ट्रीय प्रतिशतक में बदला गया: 0% सबसे अधिक वंचित क्षेत्रों और 100% सबसे कम वंचित क्षेत्रों को दर्शाता है. इसमें स्कूल उपलब्धि, उच्च शिक्षा में प्रवेश, वयस्क योग्यता और अंग्रेजी भाषा दक्षता शामिल है.',
'Income Score':
@@ -539,8 +513,10 @@ export const details: Record> = {
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को भारतीय, पाकिस्तानी, बांग्लादेशी या किसी अन्य एशियाई पृष्ठभूमि के रूप में पहचानता है.',
'% Black':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को अश्वेत, अश्वेत ब्रिटिश, कैरिबियाई या अफ्रीकी के रूप में पहचानता है.',
- '% East/SE Asian':
- 'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को चीनी या अन्य पूर्वी/दक्षिण-पूर्वी एशियाई के रूप में पहचानता है.',
+ '% East Asian':
+ 'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को चीनी के रूप में पहचानता है.',
+ '% SE Asian':
+ 'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को अन्य (गैर-चीनी) पूर्वी/दक्षिण-पूर्वी एशियाई (जैसे फिलिपिनो, वियतनामी या थाई) के रूप में पहचानता है.',
'% Mixed':
'Census 2021 से. स्थानीय प्राधिकरण की आबादी का प्रतिशत जो खुद को मिश्रित या कई जातीय समूहों (श्वेत और अश्वेत कैरिबियाई, श्वेत और अश्वेत अफ्रीकी, श्वेत और एशियाई या अन्य मिश्रित/बहुजातीय पृष्ठभूमि) के रूप में पहचानता है.',
'% Other':
@@ -611,28 +587,20 @@ export const details: Record> = {
'Az EPC-tanúsítvány potenciális energiahatékonysági besorolása, amennyiben az EPC-jelentésben ajánlott összes költséghatékony fejlesztést elvégeznék. A-tól (leghatékonyabb) G-ig (legkevésbé hatékony) terjed.',
'Interior height (m)':
'Az EPC-tanúsítvány felmérése során rögzített átlagos belső padló-mennyezet magasság méterben. A teljes belső térfogatot osztják a teljes alapterülettel.',
- 'Street tree density percentile':
- 'A Forest Research 2025-os Trees Outside Woodland térképéből származó hozzávetőleges lombkorona-fedettség az irányítószám-középpont körül. A magányos fák és facsoportok lombkorona-poligonjait minden irányítószám-középpont 50 méteres körzetében számoljuk, majd az angliai irányítószámok közötti percentilissé alakítjuk. Ez az irányítószám-középponton alapuló közelítő mutató, nem pontos ingatlan- vagy utcaszakasz-mérés.',
+ 'Tree canopy density percentile':
+ 'A Forest Research 2025-os Trees Outside Woodland térképéből és a National Forest Inventory (NFI) adataiból származó hozzávetőleges lombkorona-fedettség az irányítószám-középpont körül. A magányos fák, facsoportok ÉS a National Forest Inventory erdőterületeinek lombkorona-poligonjait minden irányítószám-középpont 50 méteres körzetében számoljuk, majd az angliai irányítószámok közötti percentilissé alakítjuk. Ez az irányítószám-középponton alapuló közelítő mutató, nem pontos ingatlan- vagy utcaszakasz-mérés.',
'Within conservation area':
'A Planning Data műemléki területeinek határai az irányítószám reprezentatív pontjához rendelve. Az országos adatállomány fejlesztés alatt áll, és tartalmazhat duplikátumokat vagy hiányos helyi lefedettséget; határérzékeny döntéseknél a helyi tervezési hatóság adatait kell ellenőrizni.',
'Listed building':
'A Historic England National Heritage List for England műemlékiépület-pontrekordjai, amelyeket óvatosan egyeztetünk ingatlancímekhez a műemléki bejegyzés neve és a közeli irányítószám-jelöltek alapján. Előszűrési jelzésként kezelendő, nem jogi megállapításként: minden konkrét ingatlant ellenőrizni kell az NHLE-ben és a helyi tervezési hatóságnál.',
- 'Good+ primary schools within 2km':
- '2 km-en belüli állami fenntartású általános iskolák, amelyek aktuális Ofsted besorolása Jó vagy Kiemelkedő. A még nem vizsgált iskolák ki vannak zárva.',
- 'Good+ secondary schools within 2km':
- '2 km-en belüli állami fenntartású középiskolák, amelyek aktuális Ofsted besorolása Jó vagy Kiemelkedő. A még nem vizsgált iskolák ki vannak zárva.',
- 'Good+ primary schools within 5km':
- '5 km-en belüli állami fenntartású általános iskolák, amelyek aktuális Ofsted besorolása Jó vagy Kiemelkedő. A még nem vizsgált iskolák ki vannak zárva.',
- 'Good+ secondary schools within 5km':
- '5 km-en belüli állami fenntartású középiskolák, amelyek aktuális Ofsted besorolása Jó vagy Kiemelkedő. A még nem vizsgált iskolák ki vannak zárva.',
- 'Outstanding primary schools within 2km':
- '2 km-en belüli állami fenntartású általános iskolák, amelyek aktuális Ofsted besorolása Kiemelkedő. A még nem vizsgált iskolák ki vannak zárva.',
- 'Outstanding secondary schools within 2km':
- '2 km-en belüli állami fenntartású középiskolák, amelyek aktuális Ofsted besorolása Kiemelkedő. A még nem vizsgált iskolák ki vannak zárva.',
- 'Outstanding primary schools within 5km':
- '5 km-en belüli állami fenntartású általános iskolák, amelyek aktuális Ofsted besorolása Kiemelkedő. A még nem vizsgált iskolák ki vannak zárva.',
- 'Outstanding secondary schools within 5km':
- '5 km-en belüli állami fenntartású középiskolák, amelyek aktuális Ofsted besorolása Kiemelkedő. A még nem vizsgált iskolák ki vannak zárva.',
+ 'Good+ primary school catchments':
+ 'Hány olyan állami fenntartású általános iskola van, amelynek aktuális Ofsted besorolása Jó vagy Kiemelkedő, és amely a tanulóit az ezt az irányítószámot lefedő területről veszi fel. A körzetsugarakat az egyes iskolák tanulólétszámából és a helyi gyermeknépességből (2021-es népszámlálás) modellezzük, távolságalapú felvételt közelítve; történeti becslések, nem hivatalos felvételi körzetek. A még nem vizsgált iskolák ki vannak zárva.',
+ 'Good+ secondary school catchments':
+ 'Hány olyan állami fenntartású középiskola van, amelynek aktuális Ofsted besorolása Jó vagy Kiemelkedő, és amely a tanulóit az ezt az irányítószámot lefedő területről veszi fel. A körzetsugarakat az egyes iskolák tanulólétszámából és a helyi gyermeknépességből (2021-es népszámlálás) modellezzük, távolságalapú felvételt közelítve; történeti becslések, nem hivatalos felvételi körzetek. A még nem vizsgált iskolák ki vannak zárva.',
+ 'Outstanding primary school catchments':
+ 'Hány olyan állami fenntartású általános iskola van, amelynek aktuális Ofsted besorolása Kiemelkedő, és amely a tanulóit az ezt az irányítószámot lefedő területről veszi fel. A körzetsugarakat az egyes iskolák tanulólétszámából és a helyi gyermeknépességből (2021-es népszámlálás) modellezzük, távolságalapú felvételt közelítve; történeti becslések, nem hivatalos felvételi körzetek. A még nem vizsgált iskolák ki vannak zárva.',
+ 'Outstanding secondary school catchments':
+ 'Hány olyan állami fenntartású középiskola van, amelynek aktuális Ofsted besorolása Kiemelkedő, és amely a tanulóit az ezt az irányítószámot lefedő területről veszi fel. A körzetsugarakat az egyes iskolák tanulólétszámából és a helyi gyermeknépességből (2021-es népszámlálás) modellezzük, távolságalapú felvételt közelítve; történeti becslések, nem hivatalos felvételi körzetek. A még nem vizsgált iskolák ki vannak zárva.',
'Education, Skills and Training Score':
'Az Angol Nélkülözési Indexekből származik, országos percentilissé alakítva: 0% a leginkább hátrányos, 100% a legkevésbé hátrányos területeket jelzi. Az iskolai teljesítményt, a felsőoktatásba való bejutást, a felnőttkori képesítéseket és az angol nyelvi jártasságot foglalja magában.',
'Income Score':
@@ -685,8 +653,10 @@ export const details: Record> = {
'A 2021-es Census alapján. A helyi hatóság területén indiai, pakisztáni, bangladesi vagy bármely más ázsiai háttérként azonosított népesség százaléka.',
'% Black':
'A 2021-es Census alapján. A helyi hatóság területén fekete, brit fekete, karibi vagy afrikai háttérként azonosított népesség százaléka.',
- '% East/SE Asian':
- 'A 2021-es Census alapján. A helyi hatóság területén kínai vagy más kelet-/délkelet-ázsiai származásúként azonosított népesség százaléka.',
+ '% East Asian':
+ 'A 2021-es Census alapján. A helyi hatóság területén kínaiként azonosított népesség százaléka.',
+ '% SE Asian':
+ 'A 2021-es Census alapján. A helyi hatóság területén más (nem kínai) kelet-/délkelet-ázsiai származásúként (pl. filippínó, vietnámi vagy thai) azonosított népesség százaléka.',
'% Mixed':
'A 2021-es Census alapján. A helyi hatóság területén vegyes vagy többes etnikai csoportként (fehér és fekete karibi, fehér és fekete afrikai, fehér és ázsiai, vagy bármely más vegyes vagy többes háttér) azonosított népesség százaléka.',
'% Other':
diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts
index b4fdb4e..936ca4a 100644
--- a/frontend/src/i18n/locales/de.ts
+++ b/frontend/src/i18n/locales/de.ts
@@ -609,6 +609,8 @@ const de: Translations = {
pleaseWait: 'Bitte warten...',
sendResetLink: 'Link zum Zurücksetzen senden',
backToLogin: 'Zurück zur Anmeldung',
+ registerConsent:
+ 'Mit der Kontoerstellung akzeptierst du unsere Nutzungsbedingungen und unsere Datenschutzerklärung .',
},
// ── Upgrade Modal ──────────────────────────────────
@@ -666,7 +668,7 @@ const de: Translations = {
addFiltersHint:
'Füge unten Filter hinzu, um die Karte auf Gebiete einzugrenzen, die zu deinen Kriterien passen',
upgradePrompt:
- 'Finde passende Postcodes mit Kriminalität, Schulen, Lärm, Breitband, Preisen und über 50 weiteren Filtern in ganz England.',
+ 'Finde passende Postcodes mit Kriminalität, Schulen, Lärm, Breitband, Preisen und über 40 kombinierbaren Filtern in ganz England.',
oneTimeLifetime: 'Einmalzahlung, lebenslanger Zugang.',
upgradeToFullMap: 'Auf die vollständige Karte upgraden',
chooseFilters:
@@ -698,7 +700,6 @@ const de: Translations = {
filtersOut: 'schließt {{value}} aus',
schoolType: 'Schultyp',
schoolRating: 'Schulbewertung',
- schoolDistance: 'Schulentfernung',
primary: 'Grundschule',
secondary: 'Weiterführende Schule',
rating: 'Bewertung',
@@ -943,7 +944,7 @@ const de: Translations = {
showcaseFeatureNoiseShort: 'Lärm',
showcaseFeatureSchoolsShort: 'Schulen',
showcaseFeatureTravelShort: 'Fahrzeit',
- showcaseGoodPrimariesNearby: '{{count}}+ Good Primaries in der Nähe',
+ showcaseGoodPrimariesNearby: 'In {{count}}+ Einzugsgebieten guter Grundschulen',
showcaseWithinRail: 'Innerhalb von {{count}} Min. zur Bahn',
showcaseMatchingHomesLabel: 'Passende Immobilien',
showcaseMatchingHomes: '{{value}} passende Immobilien',
@@ -1004,6 +1005,13 @@ const de: Translations = {
statFilters: 'kombinierbare Filter',
statEvery: 'Jeder',
statPostcodeInEngland: 'Postcode in England',
+ coverageNote:
+ 'Deckt jeden Postcode in England ab — je 200+ Datenfelder. Schottland und Wales sind in Planung.',
+ priceStrip:
+ 'Lebenslanger Zugang, aktuell {{price}} — der Preis steigt, sobald sich die Stufen füllen.',
+ priceStripSpots: 'Noch {{count}} Platz zu diesem Preis.',
+ priceStripSpotsPlural: 'Noch {{count}} Plätze zu diesem Preis.',
+ priceStripCta: 'Preise ansehen',
ourPhilosophy: 'Beginne mit dem, was zählt, und finde den passenden Postcode',
philosophyP1:
'Die meisten Immobilienseiten fragen, wo du wohnen möchtest. In London ist das besonders schwierig, aber dasselbe Problem gibt es in ganz England: Käufer starten mit wenigen bekannten Orten und prüfen dann Pendelzeit, Schulen, Kriminalität, Street View, Breitband und Verkaufspreise in getrennten Tabs.',
@@ -1027,7 +1035,7 @@ const de: Translations = {
compAreaDataSub: '(Kriminalität, Schulen, Lärm, Breitband, Infrastruktur)',
compPropertyData: 'Historie auf Immobilienebene',
compPropertyDataSub: '(Verkaufspreise, EPC, Wohnfläche, Schätzwert)',
- compFilters: '56 Filter, die zusammenarbeiten',
+ compFilters: '40+ Filter, die zusammenarbeiten',
compFiltersSub: '(nicht ein Postcode oder ein Inserat nach dem anderen)',
ctaTitle: 'Hör auf zu raten, wo du kaufen sollst.',
ctaDescription:
@@ -1035,13 +1043,36 @@ const de: Translations = {
},
// ── Pricing Page ───────────────────────────────────
+ // ── Footer ─────────────────────────────────────────
+ footer: {
+ tagline:
+ 'Finde die Postcodes, die zu deinem Leben passen. Datenbasierte Gegendrecherche für England.',
+ product: 'Produkt',
+ resources: 'Ressourcen',
+ legal: 'Rechtliches',
+ dataSources: 'Datenquellen',
+ methodology: 'Methodik',
+ contact: 'Support kontaktieren',
+ terms: 'Nutzungsbedingungen',
+ privacy: 'Datenschutzerklärung',
+ copyright: '© {{year}} Perfect Postcode. Alle Rechte vorbehalten.',
+ coverage: 'Deckt jeden Postcode in England ab.',
+ },
+
+ // ── Legal pages ────────────────────────────────────
+ legal: {
+ lastUpdated: 'Zuletzt aktualisiert: {{date}}',
+ englishOnly: 'Dieses Dokument liegt auf Englisch vor; die englische Fassung ist maßgeblich.',
+ },
+
pricingPage: {
title: 'Mit einem besseren Suchgebiet kaufen',
subtitle:
'Lebenslanger Zugang zu der Karte, die zeigt, wo du suchen solltest, bevor du Besichtigungen buchst.',
costContext:
'Käufer verbringen oft Abende damit, Inserate, Pendelzeiten, Schulberichte, Kriminalitätskarten, Street View und Verkaufspreise zusammenzuführen. In London ist das besonders mühsam, aber dasselbe Rechercheproblem gibt es in ganz England. Perfect Postcode bringt die Gebietsrecherche auf eine Karte, bevor du Wochenenden, Gebühren und Aufmerksamkeit investierst.',
- lessThanSurvey: 'Weniger als ein Survey. Deutlich wirksamer, um deine Auswahl zu steuern.',
+ lessThanSurvey:
+ 'Kostet weniger als ein Zehntel eines Gutachtens — und prägt eine weit größere Entscheidung.',
currentTier: 'Aktuelle Stufe',
firstNUsers: 'Erste {{count}} Nutzer',
everyoneAfter: 'Alle danach',
@@ -1054,11 +1085,12 @@ const de: Translations = {
getStarted: 'Jetzt starten',
getStartedPrice: 'Jetzt starten — {{price}}',
noCreditCard: 'Keine Kreditkarte erforderlich',
+ moneyBack: '14 Tage Geld-zurück-Garantie.',
soldOut: 'Ausverkauft',
upcoming: 'Demnächst',
failedToLoad: 'Preise konnten nicht geladen werden. Bitte später erneut versuchen.',
- feat1: '56 Filter in ganz England',
+ feat1: '40+ Filter und 200+ Datenfelder',
feat2: 'Jeder Postcode nach deinen Bedürfnissen durchsuchbar',
feat3: 'Unbegrenztes Erkunden der Karte, gespeicherte Suchen und Exporte',
feat4: '13 Mio. historische Transaktionen und Preiskontext',
@@ -1079,6 +1111,34 @@ const de: Translations = {
articlesIntro:
'Durchsuche die öffentlichen Leitfäden zu Immobiliensuche, Pendeln, Schulen, Postleitzahlprüfungen, regionalen Vergleichen, Datenabdeckung, Methodik und Datenschutz.',
supportIntro: 'Hast du eine Frage? Schau in unsere FAQ oder kontaktiere uns direkt.',
+ videos: 'Videos',
+ videosTitle: 'Social-Media-Videos',
+ videosIntro:
+ 'Kurze Clips aus unseren Social-Media-Kanälen – jeder zeigt eine einzelne Suche in Aktion, von ruhigen Straßen über Schuleinzugsgebiete bis zu Pendelzeiten.',
+ video01Title: 'Ein Satz, jede Postleitzahl',
+ video01Desc:
+ 'Gib deinen kompletten Wohnungswunsch in normaler Sprache ein und sieh zu, wie jede passende Postleitzahl in England aufleuchtet.',
+ video02Title: 'Die 20-Minuten-Karte',
+ video02Desc:
+ 'Färbe die Karte nach Pendelzeit und sieh genau, was dir 20 Minuten ins Zentrum von London wirklich übrig lassen.',
+ video03Title: 'Jede Postleitzahl hat eine Akte',
+ video03Desc:
+ 'Tippe auf eine Postleitzahl und lies ihre Akte – verkaufte Preise, Schulen, Kriminalität und Street View an einem Ort.',
+ video04Title: 'Ein Foto kann man nicht hören',
+ video04Desc:
+ 'Anzeigenfotos sind stumm. Filtere nach Lärmpegel und finde die wirklich ruhigen Straßen unter 55 Dezibel.',
+ video05Title: 'Die Schulweg-Karte',
+ video05Desc:
+ 'Gute Grundschul-Einzugsgebiete, wenig Kriminalität und ein Budget – der Familienwunsch, über eine ganze Stadt kartiert.',
+ video06Title: 'Der Waitrose-Test',
+ video06Desc:
+ 'Zu Fuß zu einem Supermarkt, einer U-Bahn-Station und einem Park – filtere nach dem Leben, nicht nur nach dem Grundriss.',
+ video07Title: 'Auch Mieter bekommen eine Karte',
+ video07Desc:
+ 'Miete im Budget, kurzer Arbeitsweg und eine ruhige Straße – Mietportale zeigen Wohnungen, das hier zeigt dir Gegenden.',
+ video08Title: '9,99 £ gegen einen verlorenen Samstag',
+ video08Desc:
+ 'Eine schlechte Besichtigung kostet eine Zugfahrt und ein halbes Wochenende. Sieh vorher, wo du nicht hinmusst.',
source: 'Quelle:',
optOut: 'Widerspruch gegen öffentliche Offenlegung',
attribution: 'Quellenangaben',
@@ -1111,7 +1171,7 @@ const de: Translations = {
dsEthnicityName: 'Bevölkerung nach Ethnie (Zensus 2021)',
dsEthnicityOrigin: 'ONS',
dsEthnicityUse:
- 'Bevölkerungsanteile nach ethnischer Gruppe (südasiatisch, ostasiatisch, schwarz, gemischt, weiß, andere) pro Bezirk.',
+ 'Bevölkerungsanteile nach ethnischer Gruppe (südasiatisch, ostasiatisch, südostasiatisch, schwarz, gemischt, weiß, andere) pro Bezirk.',
dsCrimeName: 'Kriminalitätsdaten auf Straßenebene',
dsCrimeOrigin: 'data.police.uk',
dsCrimeUse:
@@ -1298,9 +1358,10 @@ const de: Translations = {
faqBehindData3Q: 'Warum zeigen benachbarte Postleitzahlen identische Kriminalitätszahlen?',
faqBehindData3A:
'Polizeilich erfasste Kriminalität auf Straßenebene wird auf LSOA-Ebene veröffentlicht — kleine Nachbarschaftsgebiete mit etwa 1.500 Einwohnern. Jede Postleitzahl innerhalb derselben LSOA übernimmt dieselben Jahreszahlen, sodass eine ruhige Wohnstraße und eine Hauptstraße einen Block entfernt identische Werte zeigen können, wenn sie auf derselben Seite der Grenze liegen. Die Pro-Kopf-Rate kann in Postleitzahlen mit Krankenhäusern, Universitätsgeländen oder Industriegebieten ungewöhnlich hoch wirken, weil dort viele Vorfälle gezählt werden, aber wenige Einwohner gemeldet sind.',
- faqBehindData4Q: 'Bedeutet „Gute Schulen in 2 km Umkreis“, dass mein Kind dorthin gehen kann?',
+ faqBehindData4Q:
+ 'Was bedeutet die Zahl der „Schul-Einzugsgebiete“ — kann mein Kind diese Schulen besuchen?',
faqBehindData4A:
- 'Nein. Die Zählung sucht staatliche Schulen, deren eigene Postleitzahl in einem Kreis um deinen Postleitzahl-Mittelpunkt liegt. Einzugsgebiete, konfessionelle oder selektive Aufnahmekriterien, Geschwisterregelungen und Anmeldebedingungen werden nicht modelliert — eine nahegelegene Gute oder Hervorragende Schule kann von deiner Adresse aus unerreichbar sein. Nutze die Zahl, um Gebiete zu vergleichen, und prüfe tatsächliche Aufnahmebedingungen bei der Schule oder Gemeinde, bevor du dich darauf verlässt.',
+ 'Jede Zahl gibt an, wie viele staatliche Schulen mit der Bewertung Gut oder Hervorragend ein modelliertes historisches Einzugsgebiet haben, das die Postleitzahl abdeckt. Wir simulieren, wie Englands entfernungsbasierte Aufnahmen Plätze vergeben: Kinder (Zensus 2021) bewerben sich an nahegelegenen Schulen und wägen dabei Entfernung gegen Ofsted-Bewertung ab; eine überbuchte Schule nimmt die nächstgelegenen Bewerber auf, bis sie voll ist — ihr Einzugsradius ist die Entfernung des zuletzt aufgenommenen Kindes, genau die „zuletzt vergebene Entfernung“, die Gemeinden veröffentlichen. Das Modell ist an Hunderten dieser veröffentlichten Werte kalibriert. Es schätzt, wo man plausibel einen Platz bekommt; es ist kein offizielles Aufnahmegebiet. Konfessionelle und selektive Aufnahmen, Geschwisterpriorität und jährliche Grenzänderungen werden nicht modelliert — prüfe Einzugsgebiete und Aufnahmeregeln daher immer bei der Schule oder Gemeinde, bevor du eine Entscheidung darauf stützt.',
faqBehindData5Q:
'Warum zeigt eine Postleitzahl „Gigabit“, wenn nicht jedes Haus Glasfaser hat?',
faqBehindData5A:
@@ -1459,7 +1520,7 @@ const de: Translations = {
'Current energy rating': 'Aktuelle Energieeffizienzklasse',
'Potential energy rating': 'Mögliche Energieeffizienzklasse',
'Interior height (m)': 'Raumhöhe (m)',
- 'Street tree density percentile': 'Perzentil der Straßenbaumdichte',
+ 'Tree canopy density percentile': 'Perzentil der Baumkronendichte',
'Within conservation area': 'In Denkmalschutzgebiet',
'Listed building': 'Denkmalgeschütztes Gebäude',
@@ -1468,16 +1529,11 @@ const de: Translations = {
'Fahrzeit zum nächsten Bahn- oder U-Bahnhof (Min.)',
// ─ Feature names (Education) ─
- 'Good+ primary schools within 2km': 'Gute+ Grundschulen im Umkreis von 2 km',
- 'Good+ secondary schools within 2km': 'Gute+ weiterführende Schulen im Umkreis von 2 km',
- 'Good+ primary schools within 5km': 'Gute+ Grundschulen im Umkreis von 5 km',
- 'Good+ secondary schools within 5km': 'Gute+ weiterführende Schulen im Umkreis von 5 km',
- 'Outstanding primary schools within 2km': 'Hervorragende Grundschulen im Umkreis von 2 km',
- 'Outstanding secondary schools within 2km':
- 'Hervorragende weiterführende Schulen im Umkreis von 2 km',
- 'Outstanding primary schools within 5km': 'Hervorragende Grundschulen im Umkreis von 5 km',
- 'Outstanding secondary schools within 5km':
- 'Hervorragende weiterführende Schulen im Umkreis von 5 km',
+ 'Good+ primary school catchments': 'Einzugsgebiete guter+ Grundschulen',
+ 'Good+ secondary school catchments': 'Einzugsgebiete guter+ weiterführender Schulen',
+ 'Outstanding primary school catchments': 'Einzugsgebiete hervorragender Grundschulen',
+ 'Outstanding secondary school catchments':
+ 'Einzugsgebiete hervorragender weiterführender Schulen',
'Education, Skills and Training Score': 'Wert für Bildung, Kompetenzen und Ausbildung',
// ─ Feature names (Area development) ─
@@ -1511,7 +1567,8 @@ const de: Translations = {
'% White': '% weiß',
'% South Asian': '% südasiatisch',
'% Black': '% schwarz',
- '% East/SE Asian': '% ost-/südostasiatisch',
+ '% East Asian': '% ostasiatisch',
+ '% SE Asian': '% südostasiatisch',
'% Mixed': '% gemischt',
'% Other': '% Sonstige',
@@ -1579,6 +1636,7 @@ const de: Translations = {
'Bus station': 'Busbahnhof',
'Taxi rank': 'Taxistand',
'Tube station': 'U-Bahn-Station',
+ 'Tram & Metro stop': 'Tram- & Metro-Haltestelle',
Café: 'Café',
Restaurant: 'Restaurant',
Pub: 'Pub',
@@ -1625,7 +1683,8 @@ const de: Translations = {
'GP Surgery': 'Hausarztpraxis',
Dentist: 'Zahnarzt',
Pharmacy: 'Apotheke',
- 'Hospital & Clinic': 'Krankenhaus & Klinik',
+ Hospital: 'Krankenhaus',
+ Clinic: 'Klinik',
Optician: 'Optiker',
Physiotherapy: 'Physiotherapie',
'Counselling & Therapy': 'Beratung & Therapie',
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index a8b2eee..75ba1a6 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -597,6 +597,8 @@ const en = {
pleaseWait: 'Please wait...',
sendResetLink: 'Send reset link',
backToLogin: 'Back to login',
+ registerConsent:
+ 'By creating an account you agree to our Terms of Service and Privacy Policy .',
},
// ── Upgrade Modal ──────────────────────────────────
@@ -653,7 +655,7 @@ const en = {
findingPerfectPostcode: 'Finding the Perfect Postcode',
addFiltersHint: 'Add filters below to narrow the map to areas that match your criteria',
upgradePrompt:
- 'Find matching postcodes using crime, schools, noise, broadband, prices, and 50+ more filters across England.',
+ 'Find matching postcodes using crime, schools, noise, broadband, prices, and 40+ combinable filters across England.',
oneTimeLifetime: 'One-time payment, lifetime access.',
upgradeToFullMap: 'Upgrade to full map',
chooseFilters: 'Click Add to filter. The small buttons show data details or colour the map.',
@@ -684,7 +686,6 @@ const en = {
filtersOut: 'filters out {{value}}',
schoolType: 'School type',
schoolRating: 'School rating',
- schoolDistance: 'School distance',
primary: 'Primary',
secondary: 'Secondary',
rating: 'Rating',
@@ -927,7 +928,7 @@ const en = {
showcaseFeatureNoiseShort: 'Noise',
showcaseFeatureSchoolsShort: 'Schools',
showcaseFeatureTravelShort: 'Travel',
- showcaseGoodPrimariesNearby: '{{count}}+ good primaries nearby',
+ showcaseGoodPrimariesNearby: 'In {{count}}+ good primary catchments',
showcaseWithinRail: 'Within {{count}} min of rail',
showcaseMatchingHomesLabel: 'Matching homes',
showcaseMatchingHomes: '{{value}} matching homes',
@@ -987,6 +988,12 @@ const en = {
statFilters: 'combinable filters',
statEvery: 'Every',
statPostcodeInEngland: 'postcode in England',
+ coverageNote:
+ 'Covers every postcode in England — 200+ data fields each. Scotland & Wales are on the roadmap.',
+ priceStrip: 'Lifetime access, currently {{price}} — the price rises as tiers fill.',
+ priceStripSpots: '{{count}} spot left at this price.',
+ priceStripSpotsPlural: '{{count}} spots left at this price.',
+ priceStripCta: 'See pricing',
ourPhilosophy: 'Start with what matters, then find the right postcode',
philosophyP1:
'Most property sites ask where you want to live. In London that’s painfully hard, but the same problem shows up across England: buyers choose from the few places they know, then cross-check commute tools, Ofsted, police data, Street View, broadband checkers, and sold prices in separate tabs.',
@@ -1010,7 +1017,7 @@ const en = {
compAreaDataSub: '(crime, schools, noise, broadband, amenities)',
compPropertyData: 'Property-level history',
compPropertyDataSub: '(sold prices, EPC, floor area, estimated value)',
- compFilters: '56 filters working together',
+ compFilters: '40+ filters working together',
compFiltersSub: '(not one postcode or one listing at a time)',
ctaTitle: 'Stop guessing where to buy.',
ctaDescription:
@@ -1018,13 +1025,34 @@ const en = {
},
// ── Pricing Page ───────────────────────────────────
+ // ── Footer ─────────────────────────────────────────
+ footer: {
+ tagline: 'Find the postcodes that fit your life. Evidence-first area research for England.',
+ product: 'Product',
+ resources: 'Resources',
+ legal: 'Legal',
+ dataSources: 'Data sources',
+ methodology: 'Methodology',
+ contact: 'Contact support',
+ terms: 'Terms of Service',
+ privacy: 'Privacy Policy',
+ copyright: '© {{year}} Perfect Postcode. All rights reserved.',
+ coverage: 'Covers every postcode in England.',
+ },
+
+ // ── Legal pages ────────────────────────────────────
+ legal: {
+ lastUpdated: 'Last updated: {{date}}',
+ englishOnly: 'This document is provided in English; the English version is authoritative.',
+ },
+
pricingPage: {
title: 'Buy with a better search area',
subtitle:
'Lifetime access to the map that helps you find where to look before you book viewings.',
costContext:
'Buyers often spend evenings stitching together listings, commute checks, school reports, crime maps, Street View, and sold prices. In London this is relentless, but the same research problem appears across England. Perfect Postcode puts the area research on one map before you commit your weekends, fees, and attention.',
- lessThanSurvey: 'Less than a survey. Vastly more impactful in guiding your choices.',
+ lessThanSurvey: 'Costs less than a tenth of a survey — and shapes a far bigger decision.',
currentTier: 'Current tier',
firstNUsers: 'First {{count}} users',
everyoneAfter: 'Everyone after',
@@ -1037,11 +1065,12 @@ const en = {
getStarted: 'Get started',
getStartedPrice: 'Get started - {{price}}',
noCreditCard: 'No credit card required',
+ moneyBack: '14-day money-back guarantee.',
soldOut: 'Sold out',
upcoming: 'Upcoming',
failedToLoad: 'Failed to load pricing. Please try again later.',
- feat1: '56 filters across England',
+ feat1: '40+ filters and 200+ data fields',
feat2: 'Every postcode searchable from your needs',
feat3: 'Unlimited map exploration, saved searches and exports',
feat4: '13M historical transactions and price context',
@@ -1062,6 +1091,34 @@ const en = {
articlesIntro:
'Browse the public guides for property search, commute, schools, postcode checks, regional comparisons, data coverage, methodology, and privacy.',
supportIntro: 'Have a question? Check our FAQ or reach out to us directly.',
+ videos: 'Videos',
+ videosTitle: 'Social media videos',
+ videosIntro:
+ 'Short clips from our social channels — each one shows a single search in action, from quiet streets to school catchments and commute times.',
+ video01Title: 'One sentence, every postcode',
+ video01Desc:
+ 'Type your whole house brief in plain English and watch every matching postcode in England light up.',
+ video02Title: 'The 20-minute map',
+ video02Desc:
+ 'Colour the map by commute time and see exactly what a 20-minute journey to central London actually leaves you.',
+ video03Title: 'Every postcode has a file',
+ video03Desc:
+ 'Tap any postcode to read its file — sold prices, schools, crime and Street View, all in one place.',
+ video04Title: 'You can’t hear a photo',
+ video04Desc:
+ 'Listing photos are silent. Filter by noise level to find the genuinely quiet streets under 55 decibels.',
+ video05Title: 'The school-run map',
+ video05Desc:
+ 'Good primary catchments, low crime and a budget — the family brief, mapped across a whole city.',
+ video06Title: 'The Waitrose test',
+ video06Desc:
+ 'Walking distance to a Waitrose, a tube station and a park — filter for the life, not just the floor plan.',
+ video07Title: 'Renters get a map too',
+ video07Desc:
+ 'Rent under budget, a short commute and a quiet street — letting sites show flats, this shows you areas.',
+ video08Title: '£9.99 vs a wasted Saturday',
+ video08Desc:
+ 'A bad viewing costs a train ticket and half a weekend. See where not to go before you book one.',
source: 'Source:',
optOut: 'Opt out of public disclosure',
attribution: 'Attribution',
@@ -1093,7 +1150,7 @@ const en = {
dsEthnicityName: 'Population by Ethnicity (2021 Census)',
dsEthnicityOrigin: 'ONS',
dsEthnicityUse:
- 'Population percentages by ethnic group (South Asian, East Asian, Black, Mixed, White, Other) per local authority.',
+ 'Population percentages by ethnic group (South Asian, East Asian, SE Asian, Black, Mixed, White, Other) per local authority.',
dsCrimeName: 'Street-level Crime Data',
dsCrimeOrigin: 'data.police.uk',
dsCrimeUse:
@@ -1277,9 +1334,10 @@ const en = {
faqBehindData3Q: 'Why do nearby postcodes share the same crime numbers?',
faqBehindData3A:
'Police-recorded street-level crime is published at LSOA level — small neighbourhood areas of about 1,500 residents. Every postcode inside the same LSOA inherits the same yearly totals, so a quiet residential street and a high street one block over can show identical figures if they fall on the same side of the boundary. Per-capita rates can also look unusually high in postcodes covering hospitals, university campuses or industrial estates, because those areas record incidents normally but have very few residents on paper to divide the count across.',
- faqBehindData4Q: 'Does "Good schools within 2km" mean my child can attend them?',
+ faqBehindData4Q:
+ 'What does a "school catchments" count mean — can my child attend those schools?',
faqBehindData4A:
- 'No. The count looks for state schools whose own postcode falls inside a circle around your postcode’s centroid. Catchment areas, faith and selection criteria, sibling priority and admission rules are not modelled — a Good or Outstanding school nearby may still be unreachable from your address. Use the count to compare areas, then confirm actual admissions with the school or local authority before relying on it for a decision.',
+ 'Each count is the number of Good or Outstanding state schools whose modelled historical catchment area covers the postcode. We simulate how England’s distance-based admissions allocate places: children (Census 2021) apply to nearby schools, trading distance against Ofsted rating, and an oversubscribed school admits its closest applicants until full — its catchment radius is the distance of the last child admitted, the same "last distance offered" councils publish, and the model is calibrated against hundreds of those published figures. It estimates where pupils plausibly get a place; it is not an official admission area. Faith and selective admissions, sibling priority and yearly boundary changes are not modelled, so always confirm catchments and admission rules with the school or local authority before relying on them for a decision.',
faqBehindData5Q: 'Why does a postcode show "Gigabit" when not every home has fibre?',
faqBehindData5A:
'Broadband coverage from Ofcom Connected Nations is reported per postcode as the percentage of premises that can get each speed tier. We display the highest tier with any availability, so a postcode where even one home can reach Gigabit reads "Gigabit available". It is the right answer for "is full-fibre on this street at all?", but does not guarantee every flat in a block can be ordered today. Always verify with the providers for your specific address before signing.',
@@ -1436,7 +1494,7 @@ const en = {
'Current energy rating': 'Current energy rating',
'Potential energy rating': 'Potential energy rating',
'Interior height (m)': 'Interior height (m)',
- 'Street tree density percentile': 'Street tree density percentile',
+ 'Tree canopy density percentile': 'Tree canopy density percentile',
'Within conservation area': 'Within conservation area',
'Listed building': 'Listed building',
@@ -1445,14 +1503,10 @@ const en = {
'Travel time to nearest train or tube station (min)',
// ─ Feature names (Education) ─
- 'Good+ primary schools within 2km': 'Good+ primary schools within 2km',
- 'Good+ secondary schools within 2km': 'Good+ secondary schools within 2km',
- 'Good+ primary schools within 5km': 'Good+ primary schools within 5km',
- 'Good+ secondary schools within 5km': 'Good+ secondary schools within 5km',
- 'Outstanding primary schools within 2km': 'Outstanding primary schools within 2km',
- 'Outstanding secondary schools within 2km': 'Outstanding secondary schools within 2km',
- 'Outstanding primary schools within 5km': 'Outstanding primary schools within 5km',
- 'Outstanding secondary schools within 5km': 'Outstanding secondary schools within 5km',
+ 'Good+ primary school catchments': 'Good+ primary school catchments',
+ 'Good+ secondary school catchments': 'Good+ secondary school catchments',
+ 'Outstanding primary school catchments': 'Outstanding primary school catchments',
+ 'Outstanding secondary school catchments': 'Outstanding secondary school catchments',
'Education, Skills and Training Score': 'Education, Skills and Training Score',
// ─ Feature names (Area development) ─
@@ -1485,7 +1539,8 @@ const en = {
'% White': '% White',
'% South Asian': '% South Asian',
'% Black': '% Black',
- '% East/SE Asian': '% East/SE Asian',
+ '% East Asian': '% East Asian',
+ '% SE Asian': '% SE Asian',
'% Mixed': '% Mixed',
'% Other': '% Other',
@@ -1553,6 +1608,7 @@ const en = {
'Bus station': 'Bus station',
'Taxi rank': 'Taxi rank',
'Tube station': 'Tube station',
+ 'Tram & Metro stop': 'Tram & Metro stop',
Café: 'Café',
Restaurant: 'Restaurant',
Pub: 'Pub',
@@ -1599,7 +1655,8 @@ const en = {
'GP Surgery': 'GP Surgery',
Dentist: 'Dentist',
Pharmacy: 'Pharmacy',
- 'Hospital & Clinic': 'Hospital & Clinic',
+ Hospital: 'Hospital',
+ Clinic: 'Clinic',
Optician: 'Optician',
Physiotherapy: 'Physiotherapy',
'Counselling & Therapy': 'Counselling & Therapy',
diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts
index 918d22d..2087fd9 100644
--- a/frontend/src/i18n/locales/fr.ts
+++ b/frontend/src/i18n/locales/fr.ts
@@ -622,6 +622,8 @@ const fr: Translations = {
pleaseWait: 'Veuillez patienter...',
sendResetLink: 'Envoyer le lien de réinitialisation',
backToLogin: 'Retour à la connexion',
+ registerConsent:
+ 'En créant un compte, vous acceptez nos Conditions d’utilisation et notre Politique de confidentialité .',
},
// ── Upgrade Modal ──────────────────────────────────
@@ -680,7 +682,7 @@ const fr: Translations = {
addFiltersHint:
'Ajoutez des filtres ci-dessous pour limiter la carte aux secteurs adaptés à vos critères',
upgradePrompt:
- 'Trouvez les codes postaux compatibles grâce aux filtres de criminalité, d’écoles, de bruit, de haut débit, de prix et plus de 50 autres filtres dans toute l’Angleterre.',
+ 'Trouvez les codes postaux compatibles grâce aux filtres de criminalité, d’écoles, de bruit, de haut débit, de prix et plus de 40 filtres combinables dans toute l’Angleterre.',
oneTimeLifetime: 'Paiement unique, accès à vie.',
upgradeToFullMap: 'Passer à la carte complète',
chooseFilters:
@@ -712,7 +714,6 @@ const fr: Translations = {
filtersOut: 'exclut {{value}}',
schoolType: 'Type d’école',
schoolRating: 'Évaluation Ofsted',
- schoolDistance: 'Distance de l’école',
primary: 'Primaire',
secondary: 'Secondaire',
rating: 'Note',
@@ -956,7 +957,7 @@ const fr: Translations = {
showcaseFeatureNoiseShort: 'Bruit',
showcaseFeatureSchoolsShort: 'Écoles',
showcaseFeatureTravelShort: 'Trajet',
- showcaseGoodPrimariesNearby: '{{count}}+ écoles primaires Good+ à proximité',
+ showcaseGoodPrimariesNearby: 'Dans {{count}}+ zones de recrutement de primaires Good+',
showcaseWithinRail: 'À moins de {{count}} min du train',
showcaseMatchingHomesLabel: 'Biens compatibles',
showcaseMatchingHomes: '{{value}} biens compatibles',
@@ -1018,6 +1019,13 @@ const fr: Translations = {
statFilters: 'filtres combinables',
statEvery: 'Chaque',
statPostcodeInEngland: 'code postal d’Angleterre',
+ coverageNote:
+ 'Couvre tous les codes postaux d’Angleterre — plus de 200 champs de données chacun. L’Écosse et le pays de Galles sont sur la feuille de route.',
+ priceStrip:
+ 'Accès à vie, actuellement {{price}} — le prix augmente à mesure que les paliers se remplissent.',
+ priceStripSpots: '{{count}} place restante à ce prix.',
+ priceStripSpotsPlural: '{{count}} places restantes à ce prix.',
+ priceStripCta: 'Voir les tarifs',
ourPhilosophy: 'Commencez par ce qui compte, puis trouvez le bon code postal',
philosophyP1:
'La plupart des sites immobiliers demandent où vous voulez vivre. À Londres, c’est particulièrement difficile, mais le même problème existe partout en Angleterre : les acheteurs partent des quelques lieux qu’ils connaissent, puis vérifient séparément trajets, écoles, criminalité, Street View, haut débit et prix vendus.',
@@ -1041,7 +1049,7 @@ const fr: Translations = {
compAreaDataSub: '(criminalité, écoles, bruit, haut débit, services)',
compPropertyData: 'Historique par bien',
compPropertyDataSub: '(prix vendus, EPC, surface, valeur estimée)',
- compFilters: '56 filtres qui fonctionnent ensemble',
+ compFilters: '40+ filtres qui fonctionnent ensemble',
compFiltersSub: '(pas un code postal ou une annonce à la fois)',
ctaTitle: 'Arrêtez de deviner où acheter ou louer.',
ctaDescription:
@@ -1049,13 +1057,36 @@ const fr: Translations = {
},
// ── Pricing Page ───────────────────────────────────
+ // ── Footer ─────────────────────────────────────────
+ footer: {
+ tagline:
+ 'Trouvez les codes postaux qui correspondent à votre vie. Une recherche de quartier fondée sur les données, pour l’Angleterre.',
+ product: 'Produit',
+ resources: 'Ressources',
+ legal: 'Légal',
+ dataSources: 'Sources de données',
+ methodology: 'Méthodologie',
+ contact: 'Contacter le support',
+ terms: 'Conditions d’utilisation',
+ privacy: 'Politique de confidentialité',
+ copyright: '© {{year}} Perfect Postcode. Tous droits réservés.',
+ coverage: 'Couvre tous les codes postaux d’Angleterre.',
+ },
+
+ // ── Legal pages ────────────────────────────────────
+ legal: {
+ lastUpdated: 'Dernière mise à jour : {{date}}',
+ englishOnly: 'Ce document est fourni en anglais ; la version anglaise fait foi.',
+ },
+
pricingPage: {
title: 'Acheter avec un meilleur secteur de recherche',
subtitle:
'Accès à vie à la carte qui vous aide à savoir où chercher avant de réserver des visites.',
costContext:
'Les acheteurs passent souvent leurs soirées à recouper annonces, trajets, rapports scolaires, cartes de criminalité, Street View et prix vendus. À Londres, c’est incessant, mais le même problème existe dans toute l’Angleterre. Perfect Postcode rassemble la recherche de secteur sur une seule carte avant que vous n’engagiez vos week-ends, vos frais et votre attention.',
- lessThanSurvey: 'Moins cher qu’un survey immobilier. Bien plus utile pour guider vos choix.',
+ lessThanSurvey:
+ 'Moins d’un dixième du prix d’une expertise — pour une décision bien plus importante.',
currentTier: 'Offre actuelle',
firstNUsers: '{{count}} premiers utilisateurs',
everyoneAfter: 'Utilisateurs suivants',
@@ -1068,11 +1099,12 @@ const fr: Translations = {
getStarted: 'Commencer',
getStartedPrice: 'Commencer - {{price}}',
noCreditCard: 'Aucune carte bancaire requise',
+ moneyBack: 'Garantie satisfait ou remboursé de 14 jours.',
soldOut: 'Épuisé',
upcoming: 'À venir',
failedToLoad: 'Échec du chargement des tarifs. Veuillez réessayer plus tard.',
- feat1: '56 filtres dans toute l’Angleterre',
+ feat1: '40+ filtres et 200+ champs de données',
feat2: 'Chaque code postal recherchable selon vos besoins',
feat3: 'Exploration illimitée de la carte, recherches enregistrées et exportations',
feat4: '13 M de ventes historiques et contexte des prix',
@@ -1093,6 +1125,34 @@ const fr: Translations = {
articlesIntro:
'Parcourez les guides publics sur la recherche immobilière, les trajets, les écoles, les codes postaux, les comparaisons régionales, la couverture des données, la méthodologie et la confidentialité.',
supportIntro: 'Vous avez une question ? Consultez notre FAQ ou contactez-nous directement.',
+ videos: 'Vidéos',
+ videosTitle: 'Vidéos pour les réseaux sociaux',
+ videosIntro:
+ 'De courtes vidéos de nos réseaux sociaux — chacune montre une recherche en action, des rues calmes aux secteurs scolaires en passant par les temps de trajet.',
+ video01Title: 'Une phrase, chaque code postal',
+ video01Desc:
+ 'Décrivez tout votre projet immobilier en langage courant et voyez s’allumer chaque code postal correspondant en Angleterre.',
+ video02Title: 'La carte des 20 minutes',
+ video02Desc:
+ 'Colorez la carte selon le temps de trajet et voyez exactement ce que 20 minutes du centre de Londres vous laissent vraiment.',
+ video03Title: 'Chaque code postal a sa fiche',
+ video03Desc:
+ 'Touchez un code postal pour lire sa fiche — prix de vente, écoles, criminalité et Street View, au même endroit.',
+ video04Title: 'Une photo, ça ne s’entend pas',
+ video04Desc:
+ 'Les photos d’annonces sont muettes. Filtrez par niveau de bruit pour trouver les rues vraiment calmes, sous 55 décibels.',
+ video05Title: 'La carte du trajet d’école',
+ video05Desc:
+ 'Bons secteurs d’école primaire, faible criminalité et un budget — le projet des familles, cartographié sur toute une ville.',
+ video06Title: 'Le test Waitrose',
+ video06Desc:
+ 'À pied d’un supermarché, d’une station de métro et d’un parc — filtrez selon votre vie, pas seulement selon le plan.',
+ video07Title: 'Les locataires aussi ont une carte',
+ video07Desc:
+ 'Loyer dans le budget, trajet court et rue calme — les sites de location montrent des logements, ceci vous montre des quartiers.',
+ video08Title: '9,99 £ contre un samedi gâché',
+ video08Desc:
+ 'Une mauvaise visite coûte un billet de train et la moitié d’un week-end. Voyez où ne pas aller avant d’en réserver une.',
source: 'Source :',
optOut: 'Refus de la publication publique',
attribution: 'Attribution',
@@ -1125,7 +1185,7 @@ const fr: Translations = {
dsEthnicityName: 'Population par ethnie (recensement 2021)',
dsEthnicityOrigin: 'ONS',
dsEthnicityUse:
- 'Pourcentages de population par groupe ethnique (sud-asiatique, est-asiatique, noir, mixte, blanc, autre) par autorité locale.',
+ 'Pourcentages de population par groupe ethnique (sud-asiatique, est-asiatique, sud-est asiatique, noir, mixte, blanc, autre) par autorité locale.',
dsCrimeName: 'Données de criminalité au niveau rue',
dsCrimeOrigin: 'data.police.uk',
dsCrimeUse:
@@ -1316,9 +1376,9 @@ const fr: Translations = {
faqBehindData3A:
'La criminalité enregistrée par la police au niveau rue est publiée à l’échelle LSOA — de petits quartiers d’environ 1 500 habitants. Chaque code postal situé dans la même LSOA hérite des mêmes totaux annuels, donc une rue résidentielle calme et une rue commerçante un pâté de maisons plus loin peuvent afficher des chiffres identiques si elles sont du même côté de la limite. Les taux par habitant peuvent sembler anormalement élevés dans des codes postaux couvrant des hôpitaux, des campus ou des zones industrielles, car ils enregistrent des incidents normalement mais comptent peu de résidents officiels.',
faqBehindData4Q:
- '« Bonnes écoles dans un rayon de 2 km » signifie-t-il que mon enfant peut y aller ?',
+ 'Que signifie le nombre de « zones de recrutement » — mon enfant peut-il fréquenter ces écoles ?',
faqBehindData4A:
- 'Non. Le décompte cherche les écoles publiques dont le propre code postal tombe dans un cercle autour du centroïde de votre code postal. Les secteurs scolaires, critères religieux ou sélectifs, priorité fratrie et règles d’admission ne sont pas modélisés — une école Bonne ou Excellente proche peut rester inaccessible depuis votre adresse. Utilisez le décompte pour comparer des zones, puis confirmez les conditions d’admission auprès de l’école ou de la mairie avant de vous y fier.',
+ 'Chaque nombre correspond aux écoles publiques notées Bien ou Excellent dont la zone de recrutement historique modélisée couvre le code postal. Nous simulons la façon dont les admissions anglaises fondées sur la distance attribuent les places : les enfants (recensement 2021) candidatent aux écoles proches en arbitrant entre distance et note Ofsted, et une école sursouscrite admet les candidats les plus proches jusqu’à être pleine — son rayon de recrutement est la distance du dernier enfant admis, la « dernière distance d’admission » que publient les collectivités. Le modèle est calibré sur des centaines de ces distances publiées. Il estime où une place est plausible ; ce n’est pas un secteur d’admission officiel. Les critères religieux ou sélectifs, la priorité fratrie et les changements annuels de limites ne sont pas modélisés : confirmez toujours les zones et les règles d’admission auprès de l’école ou de la mairie avant de vous y fier pour une décision.',
faqBehindData5Q:
'Pourquoi un code postal affiche-t-il « Gigabit » quand toutes les maisons n’en ont pas ?',
faqBehindData5A:
@@ -1479,7 +1539,7 @@ const fr: Translations = {
'Current energy rating': 'Classe énergétique actuelle',
'Potential energy rating': 'Classe énergétique potentielle',
'Interior height (m)': 'Hauteur intérieure (m)',
- 'Street tree density percentile': 'Percentile de densité arborée de la rue',
+ 'Tree canopy density percentile': 'Percentile de densité de couvert arboré',
'Within conservation area': 'Dans une zone de conservation',
'Listed building': 'Bâtiment classé',
@@ -1488,14 +1548,10 @@ const fr: Translations = {
'Temps de trajet jusqu’à la gare ou station de métro la plus proche (min)',
// ─ Feature names (Education) ─
- 'Good+ primary schools within 2km': 'Écoles primaires Bien+ dans un rayon de 2 km',
- 'Good+ secondary schools within 2km': 'Collèges/lycées Bien+ dans un rayon de 2 km',
- 'Good+ primary schools within 5km': 'Écoles primaires Bien+ dans un rayon de 5 km',
- 'Good+ secondary schools within 5km': 'Collèges/lycées Bien+ dans un rayon de 5 km',
- 'Outstanding primary schools within 2km': 'Écoles primaires Excellent dans un rayon de 2 km',
- 'Outstanding secondary schools within 2km': 'Collèges/lycées Excellent dans un rayon de 2 km',
- 'Outstanding primary schools within 5km': 'Écoles primaires Excellent dans un rayon de 5 km',
- 'Outstanding secondary schools within 5km': 'Collèges/lycées Excellent dans un rayon de 5 km',
+ 'Good+ primary school catchments': 'Zones de recrutement d’écoles primaires Bien+',
+ 'Good+ secondary school catchments': 'Zones de recrutement de collèges/lycées Bien+',
+ 'Outstanding primary school catchments': 'Zones de recrutement d’écoles primaires Excellent',
+ 'Outstanding secondary school catchments': 'Zones de recrutement de collèges/lycées Excellent',
'Education, Skills and Training Score': 'Score éducation, compétences et formation',
// ─ Feature names (Area development) ─
@@ -1528,7 +1584,8 @@ const fr: Translations = {
'% White': '% Blancs',
'% South Asian': '% Sud-Asiatiques',
'% Black': '% Noirs',
- '% East/SE Asian': '% est/sud-est asiatique',
+ '% East Asian': '% est-asiatique',
+ '% SE Asian': '% sud-est asiatique',
'% Mixed': '% Métis',
'% Other': '% Autres',
@@ -1595,7 +1652,8 @@ const fr: Translations = {
'Bus stop': 'Arrêt de bus',
'Bus station': 'Gare routière',
'Taxi rank': 'Station de taxi',
- 'Tube station': 'Station de métro',
+ 'Tube station': 'Station de métro londonien',
+ 'Tram & Metro stop': 'Arrêt de tramway et métro',
Café: 'Café',
Restaurant: 'Restaurant',
Pub: 'Pub',
@@ -1642,7 +1700,8 @@ const fr: Translations = {
'GP Surgery': 'Cabinet médical',
Dentist: 'Dentiste',
Pharmacy: 'Pharmacie',
- 'Hospital & Clinic': 'Hôpital et clinique',
+ Hospital: 'Hôpital',
+ Clinic: 'Clinique',
Optician: 'Opticien',
Physiotherapy: 'Kinésithérapie',
'Counselling & Therapy': 'Soutien psychologique et thérapie',
diff --git a/frontend/src/i18n/locales/hi.ts b/frontend/src/i18n/locales/hi.ts
index 175eb45..acd9539 100644
--- a/frontend/src/i18n/locales/hi.ts
+++ b/frontend/src/i18n/locales/hi.ts
@@ -595,6 +595,8 @@ const hi: Translations = {
pleaseWait: 'कृपया प्रतीक्षा करें...',
sendResetLink: 'रीसेट लिंक भेजें',
backToLogin: 'लॉग इन पर वापस जाएं',
+ registerConsent:
+ 'खाता बनाकर आप हमारी सेवा की शर्तों और गोपनीयता नीति से सहमत होते हैं.',
},
upgrade: {
@@ -647,7 +649,7 @@ const hi: Translations = {
findingPerfectPostcode: 'Perfect Postcode खोजा जा रहा है',
addFiltersHint: 'अपनी शर्तों से मेल खाने वाले क्षेत्र पाने के लिए नीचे फ़िल्टर जोड़ें',
upgradePrompt:
- 'इंग्लैंड भर में अपराध, स्कूल, शोर, ब्रॉडबैंड, कीमतें और 50+ अन्य फ़िल्टर से मेल खाने वाले पोस्टकोड खोजें.',
+ 'इंग्लैंड भर में अपराध, स्कूल, शोर, ब्रॉडबैंड, कीमतें और 40+ संयोजित फ़िल्टर से मेल खाने वाले पोस्टकोड खोजें.',
oneTimeLifetime: 'एक बार भुगतान, आजीवन पहुँच.',
upgradeToFullMap: 'पूरे मानचित्र पर अपग्रेड करें',
chooseFilters:
@@ -679,7 +681,6 @@ const hi: Translations = {
filtersOut: '{{value}} को बाहर करता है',
schoolType: 'स्कूल प्रकार',
schoolRating: 'स्कूल रेटिंग',
- schoolDistance: 'स्कूल दूरी',
primary: 'प्राइमरी',
secondary: 'सेकेंडरी',
rating: 'रेटिंग',
@@ -909,7 +910,7 @@ const hi: Translations = {
showcaseFeatureNoiseShort: 'शोर',
showcaseFeatureSchoolsShort: 'स्कूल',
showcaseFeatureTravelShort: 'यात्रा',
- showcaseGoodPrimariesNearby: '{{count}}+ अच्छे प्राइमरी स्कूल पास में',
+ showcaseGoodPrimariesNearby: '{{count}}+ अच्छे प्राइमरी स्कूलों के कैचमेंट क्षेत्र में',
showcaseWithinRail: 'रेल से {{count}} मिनट के भीतर',
showcaseMatchingHomesLabel: 'मेल खाते घर',
showcaseMatchingHomes: '{{value}} मेल खाते घर',
@@ -969,6 +970,12 @@ const hi: Translations = {
statFilters: 'जोड़े जा सकने वाले फ़िल्टर',
statEvery: 'इंग्लैंड का हर',
statPostcodeInEngland: 'पोस्टकोड',
+ coverageNote:
+ 'इंग्लैंड का हर पोस्टकोड कवर — हर एक के लिए 200+ डेटा फ़ील्ड. स्कॉटलैंड और वेल्स रोडमैप पर हैं.',
+ priceStrip: 'लाइफ़टाइम एक्सेस, अभी {{price}} — टियर भरने पर क़ीमत बढ़ती है.',
+ priceStripSpots: 'इस क़ीमत पर सिर्फ़ {{count}} जगह बची है.',
+ priceStripSpotsPlural: 'इस क़ीमत पर सिर्फ़ {{count}} जगहें बची हैं.',
+ priceStripCta: 'क़ीमतें देखें',
ourPhilosophy: 'जो मायने रखता है उससे शुरू करें, फिर सही पोस्टकोड खोजें',
philosophyP1:
'अधिकांश संपत्ति साइटें पूछती हैं कि आप कहां रहना चाहते हैं. लंदन में यह बहुत मुश्किल है, लेकिन यही समस्या पूरे इंग्लैंड में आती है: खरीदार उन कुछ जगहों में से चुनते हैं जिन्हें वे जानते हैं, फिर आवागमन साधन, Ofsted, पुलिस डेटा, Street View, ब्रॉडबैंड जांच और बेचे गए दामों को अलग-अलग टैब में मिलाते हैं.',
@@ -992,20 +999,43 @@ const hi: Translations = {
compAreaDataSub: '(अपराध, स्कूल, शोर, ब्रॉडबैंड, सुविधाएं)',
compPropertyData: 'संपत्ति-स्तर इतिहास',
compPropertyDataSub: '(बेची कीमतें, EPC, फर्श क्षेत्रफल, अनुमानित मूल्य)',
- compFilters: '56 फ़िल्टर साथ काम करते हुए',
+ compFilters: '40+ फ़िल्टर साथ काम करते हुए',
compFiltersSub: '(एक समय में केवल एक पोस्टकोड या एक लिस्टिंग नहीं)',
ctaTitle: 'कहां खरीदना है, इसका अनुमान लगाना बंद करें.',
ctaDescription:
'उन पोस्टकोड की शॉर्टलिस्ट बनाएं जो आपकी वास्तविक जिंदगी से मेल खाते हैं, फिर उन्हें खुद जांचें.',
},
+ // ── Footer ─────────────────────────────────────────
+ footer: {
+ tagline:
+ 'अपनी ज़िंदगी से मेल खाते पोस्टकोड खोजें. इंग्लैंड के लिए डेटा-आधारित इलाक़े की रिसर्च.',
+ product: 'प्रोडक्ट',
+ resources: 'संसाधन',
+ legal: 'क़ानूनी',
+ dataSources: 'डेटा स्रोत',
+ methodology: 'कार्यप्रणाली',
+ contact: 'सपोर्ट से संपर्क करें',
+ terms: 'सेवा की शर्तें',
+ privacy: 'गोपनीयता नीति',
+ copyright: '© {{year}} Perfect Postcode. सर्वाधिकार सुरक्षित.',
+ coverage: 'इंग्लैंड का हर पोस्टकोड कवर करता है.',
+ },
+
+ // ── Legal pages ────────────────────────────────────
+ legal: {
+ lastUpdated: 'आख़िरी अपडेट: {{date}}',
+ englishOnly: 'यह दस्तावेज़ अंग्रेज़ी में उपलब्ध है; अंग्रेज़ी संस्करण ही मान्य है.',
+ },
+
pricingPage: {
title: 'बेहतर खोज क्षेत्र के साथ खरीदें',
subtitle:
'उस मानचित्र की आजीवन पहुँच जो मकान देखने की बुकिंग से पहले यह तय करने में मदद करता है कि कहां देखना है.',
costContext:
'खरीदार अक्सर शामें लिस्टिंग, आवागमन जांच, स्कूल रिपोर्ट, अपराध मानचित्र, Street View और बेचे गए दामों को जोड़ने में बिताते हैं. लंदन में यह लगातार होता है, लेकिन यही शोध समस्या पूरे इंग्लैंड में दिखती है. Perfect Postcode आपके सप्ताहांत, फीस और ध्यान लगाने से पहले क्षेत्र शोध को एक मानचित्र पर रखता है.',
- lessThanSurvey: 'एक मकान सर्वेक्षण से कम खर्च. आपके चुनावों को दिशा देने में कहीं अधिक असरदार.',
+ lessThanSurvey:
+ 'एक मकान सर्वेक्षण की क़ीमत के दसवें हिस्से से भी कम — और कहीं बड़े फ़ैसले में मददगार.',
currentTier: 'मौजूदा स्तर',
firstNUsers: 'पहले {{count}} उपयोगकर्ता',
everyoneAfter: 'उसके बाद सभी',
@@ -1018,10 +1048,11 @@ const hi: Translations = {
getStarted: 'शुरू करें',
getStartedPrice: 'शुरू करें - {{price}}',
noCreditCard: 'क्रेडिट कार्ड की जरूरत नहीं',
+ moneyBack: '14 दिन की मनी-बैक गारंटी.',
soldOut: 'बिक गया',
upcoming: 'आने वाला',
failedToLoad: 'कीमतें लोड नहीं हो सकीं. कृपया बाद में फिर कोशिश करें.',
- feat1: 'इंग्लैंड भर में 56 फ़िल्टर',
+ feat1: '40+ फ़िल्टर और 200+ डेटा फ़ील्ड',
feat2: 'आपकी जरूरतों से हर पोस्टकोड खोजने योग्य',
feat3: 'असीमित मानचित्र खोज, सहेजी गई खोजें और निर्यात',
feat4: '1.3 करोड़ ऐतिहासिक लेनदेन और कीमत संदर्भ',
@@ -1041,6 +1072,34 @@ const hi: Translations = {
articlesIntro:
'संपत्ति खोज, आवागमन, स्कूल, पोस्टकोड जांच, क्षेत्रीय तुलना, डेटा कवरेज, कार्यप्रणाली और गोपनीयता पर सार्वजनिक गाइड देखें.',
supportIntro: 'कोई सवाल है? हमारे प्रश्नोत्तर देखें या सीधे संपर्क करें.',
+ videos: 'वीडियो',
+ videosTitle: 'सोशल मीडिया वीडियो',
+ videosIntro:
+ 'हमारे सोशल चैनलों की छोटी क्लिप्स — हर एक एक खोज को क्रिया में दिखाती है, शांत गलियों से लेकर स्कूल कैचमेंट और सफर के समय तक.',
+ video01Title: 'एक वाक्य, हर पोस्टकोड',
+ video01Desc:
+ 'अपनी पूरी घर की ज़रूरत सामान्य भाषा में लिखें और इंग्लैंड का हर मेल खाता पोस्टकोड जगमगाते देखें.',
+ video02Title: '20 मिनट का नक्शा',
+ video02Desc:
+ 'नक्शे को सफर के समय के अनुसार रंगें और देखें कि सेंट्रल लंदन से 20 मिनट वास्तव में आपको क्या देते हैं.',
+ video03Title: 'हर पोस्टकोड की एक फ़ाइल है',
+ video03Desc:
+ 'किसी भी पोस्टकोड पर टैप करके उसकी फ़ाइल पढ़ें — बिक्री मूल्य, स्कूल, अपराध और स्ट्रीट व्यू, सब एक जगह.',
+ video04Title: 'फ़ोटो सुनी नहीं जा सकती',
+ video04Desc:
+ 'विज्ञापन की तस्वीरें खामोश होती हैं. शोर के स्तर से छानकर 55 डेसिबल से नीचे की सचमुच शांत गलियाँ खोजें.',
+ video05Title: 'स्कूल-रन नक्शा',
+ video05Desc:
+ 'अच्छे प्राइमरी कैचमेंट, कम अपराध और एक बजट — परिवार की ज़रूरत, पूरे शहर पर मैप की गई.',
+ video06Title: 'वेट्रोज़ टेस्ट',
+ video06Desc:
+ 'सुपरमार्केट, ट्यूब स्टेशन और पार्क से पैदल दूरी — सिर्फ़ फ़्लोर प्लान नहीं, अपनी ज़िंदगी के हिसाब से छानें.',
+ video07Title: 'किराएदारों के लिए भी नक्शा',
+ video07Desc:
+ 'बजट में किराया, छोटा सफर और शांत गली — किराये की साइटें फ़्लैट दिखाती हैं, यह आपको इलाके दिखाता है.',
+ video08Title: '£9.99 बनाम एक बर्बाद शनिवार',
+ video08Desc:
+ 'एक ख़राब विज़िट एक ट्रेन टिकट और आधा सप्ताहांत ले जाती है. बुकिंग से पहले देखें कि कहाँ नहीं जाना है.',
source: 'स्रोत:',
optOut: 'सार्वजनिक प्रकटीकरण से बाहर निकलें',
attribution: 'श्रेय',
@@ -1071,7 +1130,7 @@ const hi: Translations = {
dsEthnicityName: 'जातीयता के अनुसार जनसंख्या (2021 जनगणना)',
dsEthnicityOrigin: 'ONS',
dsEthnicityUse:
- 'स्थानीय प्राधिकरण के अनुसार जातीय समूहों (दक्षिण एशियाई, पूर्वी एशियाई, अश्वेत, मिश्रित, श्वेत, अन्य) की जनसंख्या प्रतिशत.',
+ 'स्थानीय प्राधिकरण के अनुसार जातीय समूहों (दक्षिण एशियाई, पूर्वी एशियाई, दक्षिण-पूर्वी एशियाई, अश्वेत, मिश्रित, श्वेत, अन्य) की जनसंख्या प्रतिशत.',
dsCrimeName: 'सड़क-स्तर अपराध डेटा',
dsCrimeOrigin: 'data.police.uk',
dsCrimeUse:
@@ -1242,9 +1301,10 @@ const hi: Translations = {
faqBehindData3Q: 'पास के पोस्टकोड में अपराध संख्या समान क्यों होती है?',
faqBehindData3A:
'पुलिस द्वारा दर्ज सड़क-स्तरीय अपराध डेटा LSOA स्तर पर प्रकाशित होता है — लगभग 1,500 निवासियों वाले छोटे पड़ोस क्षेत्र. एक ही LSOA के सभी पोस्टकोड को समान वार्षिक संख्याएँ मिलती हैं, इसलिए एक शांत आवासीय सड़क और एक ब्लॉक दूर मुख्य सड़क समान आँकड़े दिखा सकती हैं अगर वे सीमा के एक ही ओर हों. अस्पतालों, विश्वविद्यालय परिसरों या औद्योगिक क्षेत्रों को कवर करने वाले पोस्टकोड में प्रति-व्यक्ति दर असामान्य रूप से ऊँची लग सकती है, क्योंकि वहाँ घटनाएँ सामान्य रूप से दर्ज होती हैं पर कागज़ पर निवासी कम होते हैं.',
- faqBehindData4Q: '"2 किमी के भीतर अच्छे स्कूल" का मतलब क्या मेरा बच्चा वहाँ जा सकता है?',
+ faqBehindData4Q:
+ '"स्कूल कैचमेंट क्षेत्र" की संख्या का क्या मतलब है — क्या मेरा बच्चा उन स्कूलों में जा सकता है?',
faqBehindData4A:
- 'नहीं. यह गणना उन सरकारी स्कूलों को खोजती है जिनका अपना पोस्टकोड आपके पोस्टकोड के केंद्र के चारों ओर एक वृत्त के भीतर आता है. प्रवेश क्षेत्र, धार्मिक या चयन मानदंड, भाई-बहन प्राथमिकता और दाखिला नियम मॉडल नहीं किए जाते — पास का अच्छा या उत्कृष्ट स्कूल आपके पते से पहुंच से बाहर भी हो सकता है. क्षेत्रों की तुलना के लिए इस संख्या का उपयोग करें, फिर निर्णय से पहले स्कूल या स्थानीय प्राधिकरण से वास्तविक दाखिले की पुष्टि करें.',
+ 'हर संख्या उन अच्छी या उत्कृष्ट रेटिंग वाले सरकारी स्कूलों की गिनती है जिनका मॉडल किया गया ऐतिहासिक कैचमेंट क्षेत्र इस पोस्टकोड को कवर करता है. हम अनुकरण करते हैं कि इंग्लैंड के दूरी-आधारित दाखिले सीटें कैसे बाँटते हैं: बच्चे (जनगणना 2021) दूरी और Ofsted रेटिंग को तौलते हुए पास के स्कूलों में आवेदन करते हैं, और ज़्यादा माँग वाला स्कूल भरने तक सबसे नज़दीकी आवेदकों को लेता है — उसका कैचमेंट दायरा आखिरी दाखिल बच्चे की दूरी है, वही "आखिरी दाखिला दूरी" जो परिषदें प्रकाशित करती हैं. मॉडल ऐसी सैकड़ों प्रकाशित दूरियों पर कैलिब्रेट किया गया है. यह अनुमान है कि कहाँ सीट मिलना संभव है; यह आधिकारिक दाखिला क्षेत्र नहीं है. धार्मिक या चयनात्मक दाखिले, भाई-बहन प्राथमिकता और सीमाओं में वार्षिक बदलाव मॉडल नहीं किए जाते, इसलिए निर्णय से पहले कैचमेंट और दाखिला नियमों की पुष्टि हमेशा स्कूल या स्थानीय प्राधिकरण से करें.',
faqBehindData5Q: 'जब हर घर में फ़ाइबर नहीं है, तो पोस्टकोड "Gigabit" क्यों दिखाता है?',
faqBehindData5A:
'Ofcom Connected Nations का ब्रॉडबैंड कवरेज प्रति पोस्टकोड इस प्रतिशत के रूप में दिया जाता है कि कितने परिसर हर गति स्तर पा सकते हैं. हम किसी भी उपलब्धता वाला सर्वोच्च स्तर दिखाते हैं, इसलिए जिस पोस्टकोड में सिर्फ एक घर Gigabit पा सकता है वह "Gigabit उपलब्ध" दिखाता है. "क्या इस सड़क पर कहीं भी फुल-फ़ाइबर है?" के लिए यह सही उत्तर है, पर इससे यह गारंटी नहीं मिलती कि ब्लॉक के हर फ्लैट में आज सेवा लगवाई जा सकती है. अनुबंध करने से पहले अपने सटीक पते के लिए हमेशा प्रदाताओं से जाँच करें.',
@@ -1390,21 +1450,17 @@ const hi: Translations = {
'Current energy rating': 'मौजूदा ऊर्जा रेटिंग',
'Potential energy rating': 'संभावित ऊर्जा रेटिंग',
'Interior height (m)': 'भीतरी ऊँचाई (मी)',
- 'Street tree density percentile': 'सड़क वृक्ष घनत्व प्रतिशतक',
+ 'Tree canopy density percentile': 'वृक्ष आच्छादन घनत्व प्रतिशतक',
'Within conservation area': 'संरक्षण क्षेत्र में',
'Listed building': 'सूचीबद्ध भवन',
'Travel time to nearest train or tube station (min)':
'निकटतम ट्रेन या ट्यूब स्टेशन तक यात्रा समय (मिनट)',
- 'Good+ primary schools within 2km': '2 किमी के अंदर अच्छी या बेहतर रेटिंग वाले प्राथमिक स्कूल',
- 'Good+ secondary schools within 2km':
- '2 किमी के अंदर अच्छी या बेहतर रेटिंग वाले सेकेंडरी स्कूल',
- 'Good+ primary schools within 5km': '5 किमी के अंदर अच्छी या बेहतर रेटिंग वाले प्राथमिक स्कूल',
- 'Good+ secondary schools within 5km':
- '5 किमी के अंदर अच्छी या बेहतर रेटिंग वाले सेकेंडरी स्कूल',
- 'Outstanding primary schools within 2km': '2 किमी के अंदर उत्कृष्ट प्राथमिक स्कूल',
- 'Outstanding secondary schools within 2km': '2 किमी के अंदर उत्कृष्ट सेकेंडरी स्कूल',
- 'Outstanding primary schools within 5km': '5 किमी के अंदर उत्कृष्ट प्राथमिक स्कूल',
- 'Outstanding secondary schools within 5km': '5 किमी के अंदर उत्कृष्ट सेकेंडरी स्कूल',
+ 'Good+ primary school catchments':
+ 'अच्छी या बेहतर रेटिंग वाले प्राथमिक स्कूलों के कैचमेंट क्षेत्र',
+ 'Good+ secondary school catchments':
+ 'अच्छी या बेहतर रेटिंग वाले सेकेंडरी स्कूलों के कैचमेंट क्षेत्र',
+ 'Outstanding primary school catchments': 'उत्कृष्ट प्राथमिक स्कूलों के कैचमेंट क्षेत्र',
+ 'Outstanding secondary school catchments': 'उत्कृष्ट सेकेंडरी स्कूलों के कैचमेंट क्षेत्र',
'Education, Skills and Training Score': 'शिक्षा, कौशल और प्रशिक्षण स्कोर',
'Income Score': 'आय स्कोर',
'Employment Score': 'रोजगार स्कोर',
@@ -1431,7 +1487,8 @@ const hi: Translations = {
'% White': '% श्वेत',
'% South Asian': '% दक्षिण एशियाई',
'% Black': '% अश्वेत',
- '% East/SE Asian': '% पूर्वी/दक्षिण-पूर्वी एशियाई',
+ '% East Asian': '% पूर्वी एशियाई',
+ '% SE Asian': '% दक्षिण-पूर्वी एशियाई',
'% Mixed': '% मिश्रित',
'% Other': '% अन्य',
'Voter turnout (%)': 'मतदाता भागीदारी (%)',
@@ -1486,6 +1543,7 @@ const hi: Translations = {
'Bus station': 'बस स्टेशन',
'Taxi rank': 'टैक्सी स्टैंड',
'Tube station': 'ट्यूब स्टेशन',
+ 'Tram & Metro stop': 'ट्राम और मेट्रो स्टॉप',
Café: 'कैफे',
Restaurant: 'रेस्तरां',
Pub: 'पब',
@@ -1532,7 +1590,8 @@ const hi: Translations = {
'GP Surgery': 'GP क्लिनिक',
Dentist: 'दंत चिकित्सक',
Pharmacy: 'दवा दुकान',
- 'Hospital & Clinic': 'अस्पताल और क्लिनिक',
+ Hospital: 'अस्पताल',
+ Clinic: 'क्लिनिक',
Optician: 'ऑप्टिशियन',
Physiotherapy: 'फिजियोथेरेपी',
'Counselling & Therapy': 'काउंसलिंग और थेरेपी',
diff --git a/frontend/src/i18n/locales/hu.ts b/frontend/src/i18n/locales/hu.ts
index 35f0282..b0ef4ab 100644
--- a/frontend/src/i18n/locales/hu.ts
+++ b/frontend/src/i18n/locales/hu.ts
@@ -613,6 +613,8 @@ const hu: Translations = {
pleaseWait: 'Egy pillanat...',
sendResetLink: 'Visszaállító hivatkozás küldése',
backToLogin: 'Vissza a bejelentkezéshez',
+ registerConsent:
+ 'A fiók létrehozásával elfogadod a Felhasználási feltételeket és az Adatvédelmi tájékoztatót .',
},
// ── Upgrade Modal ──────────────────────────────────
@@ -669,7 +671,7 @@ const hu: Translations = {
findingPerfectPostcode: 'Tökéletes irányítószám keresése',
addFiltersHint: 'Adj hozzá szűrőket a térkép szűkítéséhez a feltételeidnek megfelelően',
upgradePrompt:
- 'Találj megfelelő irányítószámokat bűnözés, iskolák, zaj, szélessáv, árak és több mint 50 további szűrő alapján egész Angliában.',
+ 'Találj megfelelő irányítószámokat bűnözés, iskolák, zaj, szélessáv, árak és több mint 40 kombinálható szűrő alapján egész Angliában.',
oneTimeLifetime: 'Egyszeri fizetés, élethosszig tartó hozzáférés.',
upgradeToFullMap: 'Teljes térkép megnyitása',
chooseFilters:
@@ -701,7 +703,6 @@ const hu: Translations = {
filtersOut: '{{value}} helyet kiszűr',
schoolType: 'Iskolatípus',
schoolRating: 'Iskolai értékelés',
- schoolDistance: 'Iskolatávolság',
primary: 'Általános iskola',
secondary: 'Középiskola',
rating: 'Értékelés',
@@ -944,7 +945,7 @@ const hu: Translations = {
showcaseFeatureNoiseShort: 'Zaj',
showcaseFeatureSchoolsShort: 'Iskolák',
showcaseFeatureTravelShort: 'Utazás',
- showcaseGoodPrimariesNearby: '{{count}}+ jó általános iskola a közelben',
+ showcaseGoodPrimariesNearby: '{{count}}+ jó általános iskola körzetében',
showcaseWithinRail: 'Vasút {{count}} percen belül',
showcaseMatchingHomesLabel: 'Megfelelő otthonok',
showcaseMatchingHomes: '{{value}} megfelelő otthon',
@@ -1005,6 +1006,12 @@ const hu: Translations = {
statFilters: 'kombinálható szűrő',
statEvery: 'Minden',
statPostcodeInEngland: 'irányítószám Angliában',
+ coverageNote:
+ 'Anglia összes irányítószámát lefedi — egyenként 200+ adatmezővel. Skócia és Wales a terveink között szerepel.',
+ priceStrip: 'Örökös hozzáférés, jelenleg {{price}} — az ár a szintek betelésével emelkedik.',
+ priceStripSpots: 'Már csak {{count}} hely ezen az áron.',
+ priceStripSpotsPlural: 'Már csak {{count}} hely ezen az áron.',
+ priceStripCta: 'Árak megtekintése',
ourPhilosophy: 'Indulj ki abból, ami számít, majd találd meg a megfelelő irányítószámot',
philosophyP1:
'A legtöbb ingatlanoldal először azt kérdezi, hol szeretnél élni. Londonban ez különösen nehéz, de ugyanez a probléma egész Angliában megjelenik: a vevők néhány ismert helyből indulnak ki, majd külön füleken ellenőrzik az ingázást, iskolákat, bűnözést, Street View-t, internetet és eladási árakat.',
@@ -1028,7 +1035,7 @@ const hu: Translations = {
compAreaDataSub: '(bűnözés, iskolák, zaj, internet, szolgáltatások)',
compPropertyData: 'Ingatlanszintű előzmények',
compPropertyDataSub: '(eladási árak, EPC, alapterület, becsült érték)',
- compFilters: '56 együtt működő szűrő',
+ compFilters: '40+ együtt működő szűrő',
compFiltersSub: '(nem egyenkénti irányítószám- vagy hirdetésellenőrzés)',
ctaTitle: 'Ne találgasd, hol vegyél.',
ctaDescription:
@@ -1036,14 +1043,34 @@ const hu: Translations = {
},
// ── Pricing Page ───────────────────────────────────
+ // ── Footer ─────────────────────────────────────────
+ footer: {
+ tagline: 'Találd meg az életedhez illő irányítószámokat. Adatalapú környékkutatás Angliához.',
+ product: 'Termék',
+ resources: 'Források',
+ legal: 'Jogi információk',
+ dataSources: 'Adatforrások',
+ methodology: 'Módszertan',
+ contact: 'Kapcsolatfelvétel',
+ terms: 'Felhasználási feltételek',
+ privacy: 'Adatvédelmi tájékoztató',
+ copyright: '© {{year}} Perfect Postcode. Minden jog fenntartva.',
+ coverage: 'Anglia összes irányítószámát lefedi.',
+ },
+
+ // ── Legal pages ────────────────────────────────────
+ legal: {
+ lastUpdated: 'Utolsó frissítés: {{date}}',
+ englishOnly: 'Ez a dokumentum angol nyelven készült; az angol változat az irányadó.',
+ },
+
pricingPage: {
title: 'Vásárolj jobb keresési területből kiindulva',
subtitle:
'Élethosszig tartó hozzáférés a térképhez, amely segít eldönteni, hol érdemes keresni, mielőtt megtekintéseket foglalnál.',
costContext:
'A vevők gyakran estéket töltenek hirdetések, ingázási ellenőrzések, iskolai jelentések, bűnözési térképek, Street View és eladási árak összeillesztésével. Londonban ez kimerítő, de ugyanez a kutatási probléma egész Angliában megjelenik. A Perfect Postcode egy térképre teszi a területkutatást, mielőtt a hétvégéidet, díjaidat és figyelmedet rászánnád.',
- lessThanSurvey:
- 'Kevesebb, mint egy műszaki felmérés. Sokkal többet segít a döntések irányításában.',
+ lessThanSurvey: 'Egy műszaki felmérés árának töredékéért — egy sokkal nagyobb döntéshez.',
currentTier: 'Jelenlegi szint',
firstNUsers: 'Első {{count}} felhasználó',
everyoneAfter: 'Mindenki más utána',
@@ -1056,11 +1083,12 @@ const hu: Translations = {
getStarted: 'Kezdjük el',
getStartedPrice: 'Kezdjük el – {{price}}',
noCreditCard: 'Nem szükséges bankkártya',
+ moneyBack: '14 napos pénzvisszafizetési garancia.',
soldOut: 'Elfogyott',
upcoming: 'Következő',
failedToLoad: 'Nem sikerült betölteni az árakat. Kérjük, próbáld újra később.',
- feat1: '56 szűrő egész Angliában',
+ feat1: '40+ szűrő és 200+ adatmező',
feat2: 'Minden irányítószám kereshető a saját igényeid alapján',
feat3: 'Korlátlan térképfelfedezés, mentett keresések és exportálás',
feat4: '13 millió korábbi tranzakció és árkörnyezet',
@@ -1081,6 +1109,34 @@ const hu: Translations = {
articlesIntro:
'Böngészd a nyilvános útmutatókat ingatlankeresésről, ingázásról, iskolákról, irányítószám-ellenőrzésről, regionális összehasonlításokról, adatlefedettségről, módszertanról és adatvédelemről.',
supportIntro: 'Kérdésed van? Nézd meg a GYIK-et, vagy írj nekünk közvetlenül.',
+ videos: 'Videók',
+ videosTitle: 'Közösségimédia-videók',
+ videosIntro:
+ 'Rövid klipek a közösségi csatornáinkról – mindegyik egy-egy keresést mutat be működés közben, a csendes utcáktól az iskolai körzeteken át az utazási időkig.',
+ video01Title: 'Egy mondat, minden irányítószám',
+ video01Desc:
+ 'Írd le a teljes lakáskeresési igényedet hétköznapi nyelven, és nézd, ahogy Anglia minden illeszkedő irányítószáma felgyúl.',
+ video02Title: 'A 20 perces térkép',
+ video02Desc:
+ 'Színezd a térképet utazási idő szerint, és lásd pontosan, mit hagy neked valójában 20 perc London központjától.',
+ video03Title: 'Minden irányítószámnak van aktája',
+ video03Desc:
+ 'Koppints bármelyik irányítószámra, és olvasd el az aktáját – eladási árak, iskolák, bűnözés és Street View egy helyen.',
+ video04Title: 'Egy fotót nem lehet meghallani',
+ video04Desc:
+ 'A hirdetésfotók némák. Szűrj zajszint szerint, és találd meg a valóban csendes, 55 decibel alatti utcákat.',
+ video05Title: 'Az iskolába vezető út térképe',
+ video05Desc:
+ 'Jó általános iskolai körzetek, alacsony bűnözés és egy költségvetés – a családi igény, egy egész városra térképezve.',
+ video06Title: 'A Waitrose-teszt',
+ video06Desc:
+ 'Sétatávolságra egy szupermarkettől, egy metrómegállótól és egy parktól – az életedre szűrj, ne csak az alaprajzra.',
+ video07Title: 'A bérlőknek is jár térkép',
+ video07Desc:
+ 'Költségvetésbe férő bérleti díj, rövid ingázás és csendes utca – a bérleti oldalak lakásokat mutatnak, ez környékeket.',
+ video08Title: '9,99 £ egy elpazarolt szombat ellen',
+ video08Desc:
+ 'Egy rossz megtekintés egy vonatjegybe és egy fél hétvégébe kerül. Még foglalás előtt lásd, hova ne menj.',
source: 'Forrás:',
optOut: 'Nyilvános közzététel visszautasítása',
attribution: 'Forrásmegnevezés',
@@ -1113,7 +1169,7 @@ const hu: Translations = {
dsEthnicityName: 'Népesség etnikai megoszlás szerint (2021-es népszámlálás)',
dsEthnicityOrigin: 'ONS',
dsEthnicityUse:
- 'Népesség százalékos megoszlása etnikai csoportonként (dél-ázsiai, kelet-ázsiai, fekete, vegyes, fehér, egyéb) helyi önkormányzatonként.',
+ 'Népesség százalékos megoszlása etnikai csoportonként (dél-ázsiai, kelet-ázsiai, délkelet-ázsiai, fekete, vegyes, fehér, egyéb) helyi önkormányzatonként.',
dsCrimeName: 'Utcaszintű bűnözési adatok',
dsCrimeOrigin: 'data.police.uk',
dsCrimeUse:
@@ -1300,9 +1356,9 @@ const hu: Translations = {
faqBehindData3A:
'A rendőrségi utcaszintű bűnözési adatok LSOA-szinten kerülnek közzétételre — ezek kb. 1500 lakosú kis környékek. Az ugyanazon LSOA-ban lévő minden irányítószám ugyanazokat az éves számokat kapja, így egy csendes lakóutca és egy egy háztömbnyire lévő főutca azonos értékeket mutathat, ha ugyanazon az oldalon vannak a határnak. Az egy főre jutó ráta szokatlanul magasnak tűnhet kórházakat, egyetemi kampuszokat vagy ipari területeket lefedő irányítószámoknál, mert ott normál mennyiségű incidens történik, de papíron kevés a lakos.',
faqBehindData4Q:
- 'A „2 km-en belüli Jó iskolák” azt jelenti, hogy oda be is iratkozhat a gyerekem?',
+ 'Mit jelent az „iskolai körzetek” száma — beiratkozhat a gyerekem ezekbe az iskolákba?',
faqBehindData4A:
- 'Nem. A számláló azokat az állami iskolákat keresi, amelyek saját irányítószáma az irányítószámod középpontja körüli körben van. A körzethatárokat, vallási vagy felvételi kritériumokat, testvérprioritást és felvételi szabályokat nem modellezzük — egy közeli Jó vagy Kiváló iskola lehet, hogy a te címedről mégsem elérhető. Használd a számot területek összehasonlítására, majd a tényleges felvételi feltételeket egyeztesd az iskolával vagy az önkormányzattal, mielőtt erre alapoznál.',
+ 'Mindegyik szám azt mutatja, hány Jó vagy Kiváló minősítésű állami iskola modellezett történeti körzete fedi le az irányítószámot. Azt szimuláljuk, ahogyan az angol távolságalapú felvételi elosztja a helyeket: a gyerekek (2021-es népszámlálás) a közeli iskolákba jelentkeznek, mérlegelve a távolságot és az Ofsted-minősítést, a túljelentkezéses iskola pedig a legközelebbi jelentkezőket veszi fel, amíg meg nem telik — körzetének sugara az utolsóként felvett gyerek távolsága, pontosan az önkormányzatok által közzétett „utolsó felvett távolság”. A modellt több száz ilyen közzétett értékhez kalibráltuk. Azt becsüli, hol kapható életszerűen hely; nem hivatalos felvételi körzet. A vallási és szelektív felvételit, a testvérprioritást és az évenkénti határváltozásokat nem modellezzük, ezért a körzeteket és a felvételi szabályokat mindig egyeztesd az iskolával vagy az önkormányzattal, mielőtt döntést alapoznál rájuk.',
faqBehindData5Q: 'Miért mutat „Gigabit”-et egy irányítószám, ha nem minden otthon kapja?',
faqBehindData5A:
'Az Ofcom Connected Nations szélessáv-lefedettsége irányítószámonként az egyes sebességszinteket elérni képes ingatlanok százalékát adja meg. Mi a legmagasabb elérhető szintet jelenítjük meg, így ha akár egyetlen otthon eléri a gigabites sebességet, az irányítószám „Gigabit elérhető”-ként jelenik meg. Ez jól válaszol arra, hogy „van-e egyáltalán optikai internet ezen az utcán?”, de nem garantálja, hogy a tömbben minden lakásba megrendelhető. Mindig ellenőrizd a szolgáltatóknál a saját címedre vonatkozóan, mielőtt szerződnél.',
@@ -1462,7 +1518,7 @@ const hu: Translations = {
'Current energy rating': 'Jelenlegi energetikai besorolás',
'Potential energy rating': 'Lehetséges energetikai besorolás',
'Interior height (m)': 'Belmagasság (m)',
- 'Street tree density percentile': 'Utcai fasűrűségi percentilis',
+ 'Tree canopy density percentile': 'Lombkorona-sűrűségi percentilis',
'Within conservation area': 'Műemlékvédelmi területen',
'Listed building': 'Műemlék épület',
@@ -1471,14 +1527,10 @@ const hu: Translations = {
'Utazási idő a legközelebbi vonat- vagy metróállomásig (perc)',
// ─ Feature names (Education) ─
- 'Good+ primary schools within 2km': 'Jó+ általános iskolák 2 km-en belül',
- 'Good+ secondary schools within 2km': 'Jó+ középiskolák 2 km-en belül',
- 'Good+ primary schools within 5km': 'Jó+ általános iskolák 5 km-en belül',
- 'Good+ secondary schools within 5km': 'Jó+ középiskolák 5 km-en belül',
- 'Outstanding primary schools within 2km': 'Kiváló általános iskolák 2 km-en belül',
- 'Outstanding secondary schools within 2km': 'Kiváló középiskolák 2 km-en belül',
- 'Outstanding primary schools within 5km': 'Kiváló általános iskolák 5 km-en belül',
- 'Outstanding secondary schools within 5km': 'Kiváló középiskolák 5 km-en belül',
+ 'Good+ primary school catchments': 'Jó+ általános iskolai körzetek',
+ 'Good+ secondary school catchments': 'Jó+ középiskolai körzetek',
+ 'Outstanding primary school catchments': 'Kiváló általános iskolai körzetek',
+ 'Outstanding secondary school catchments': 'Kiváló középiskolai körzetek',
'Education, Skills and Training Score': 'Oktatás, készségek és képzés pontszám',
// ─ Feature names (Area development) ─
@@ -1511,7 +1563,8 @@ const hu: Translations = {
'% White': '% fehér',
'% South Asian': '% dél-ázsiai',
'% Black': '% fekete',
- '% East/SE Asian': '% kelet-/dél-kelet-ázsiai',
+ '% East Asian': '% kelet-ázsiai',
+ '% SE Asian': '% délkelet-ázsiai',
'% Mixed': '% vegyes',
'% Other': '% egyéb',
@@ -1578,7 +1631,8 @@ const hu: Translations = {
'Bus stop': 'Buszmegálló',
'Bus station': 'Buszpályaudvar',
'Taxi rank': 'Taxiállomás',
- 'Tube station': 'Metróállomás',
+ 'Tube station': 'Londoni metróállomás',
+ 'Tram & Metro stop': 'Villamos- és metrómegálló',
Café: 'Kávézó',
Restaurant: 'Étterem',
Pub: 'Kocsma',
@@ -1625,7 +1679,8 @@ const hu: Translations = {
'GP Surgery': 'Háziorvosi rendelő',
Dentist: 'Fogorvos',
Pharmacy: 'Gyógyszertár',
- 'Hospital & Clinic': 'Kórház és klinika',
+ Hospital: 'Kórház',
+ Clinic: 'Klinika',
Optician: 'Optikus',
Physiotherapy: 'Fizioterápia',
'Counselling & Therapy': 'Tanácsadás és terápia',
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index 0e8c41d..827ef80 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -561,6 +561,8 @@ const zh: Translations = {
pleaseWait: '请稍候...',
sendResetLink: '发送重置链接',
backToLogin: '返回登录',
+ registerConsent:
+ '创建账户即表示您同意我们的服务条款 和隐私政策 。',
},
// ── Upgrade Modal ──────────────────────────────────
@@ -617,7 +619,7 @@ const zh: Translations = {
findingPerfectPostcode: '正在寻找理想邮编',
addFiltersHint: '添加以下筛选条件,将地图缩小到符合您要求的区域',
upgradePrompt:
- '用治安、学校、噪音、宽带、价格和 50 多项其他筛选条件,在整个英格兰找到匹配的邮编。',
+ '用治安、学校、噪音、宽带、价格等 40 多项联动筛选条件,在整个英格兰找到匹配的邮编。',
oneTimeLifetime: '一次性付款,终身访问。',
upgradeToFullMap: '升级到完整地图',
chooseFilters: '点击“添加”来筛选。小按钮可查看数据说明或给地图着色。',
@@ -647,7 +649,6 @@ const zh: Translations = {
filtersOut: '会筛掉 {{value}}',
schoolType: '学校类型',
schoolRating: '学校评级',
- schoolDistance: '学校距离',
primary: '小学',
secondary: '中学',
rating: '评级',
@@ -886,7 +887,7 @@ const zh: Translations = {
showcaseFeatureNoiseShort: '噪音',
showcaseFeatureSchoolsShort: '学校',
showcaseFeatureTravelShort: '出行',
- showcaseGoodPrimariesNearby: '附近 {{count}}+ 所良好及以上小学',
+ showcaseGoodPrimariesNearby: '位于 {{count}}+ 个良好及以上小学学区内',
showcaseWithinRail: '到火车/地铁站 {{count}} 分钟内',
showcaseMatchingHomesLabel: '匹配房源',
showcaseMatchingHomes: '{{value}} 套匹配房源',
@@ -945,6 +946,11 @@ const zh: Translations = {
statFilters: '可组合筛选条件',
statEvery: '覆盖',
statPostcodeInEngland: '英格兰每个邮编',
+ coverageNote: '覆盖英格兰所有邮编——每个邮编 200 多个数据字段。苏格兰和威尔士已在计划中。',
+ priceStrip: '终身使用权,当前价格 {{price}}——名额售出后价格将上调。',
+ priceStripSpots: '此价格仅剩 {{count}} 个名额。',
+ priceStripSpotsPlural: '此价格仅剩 {{count}} 个名额。',
+ priceStripCta: '查看价格',
ourPhilosophy: '先明确重要条件,再找到合适的邮编',
philosophyP1:
'大多数房源网站一上来就问:您想住哪儿?在伦敦尤其难答,英格兰其他地方也是一样。买家通常只能从几个熟悉的地方入手,再分别去查通勤、学校、治安、街景、宽带和成交价。',
@@ -967,19 +973,40 @@ const zh: Translations = {
compAreaDataSub: '(治安、学校、噪音、宽带、周边设施)',
compPropertyData: '房产级的历史记录',
compPropertyDataSub: '(成交价、EPC、室内面积、估值)',
- compFilters: '56 项联动筛选',
+ compFilters: '40+ 项联动筛选',
compFiltersSub: '(不必一次只查一个邮编或一套房源)',
ctaTitle: '别再猜哪里值得买。',
ctaDescription: '先按真实生活需求建好邮编候选名单,再去实地感受。',
},
// ── Pricing Page ───────────────────────────────────
+ // ── Footer ─────────────────────────────────────────
+ footer: {
+ tagline: '找到适合您生活的邮编。基于数据的英格兰社区调研。',
+ product: '产品',
+ resources: '资源',
+ legal: '法律',
+ dataSources: '数据来源',
+ methodology: '方法论',
+ contact: '联系客服',
+ terms: '服务条款',
+ privacy: '隐私政策',
+ copyright: '© {{year}} Perfect Postcode。保留所有权利。',
+ coverage: '覆盖英格兰所有邮编。',
+ },
+
+ // ── Legal pages ────────────────────────────────────
+ legal: {
+ lastUpdated: '最后更新:{{date}}',
+ englishOnly: '本文件以英文提供,以英文版本为准。',
+ },
+
pricingPage: {
title: '用更靠谱的搜索范围去买房',
subtitle: '终身访问这张地图,预约看房前先弄清楚该去哪儿看。',
costContext:
'买家常常把晚上耗在拼凑房源、通勤查询、学校报告、治安地图、Street View 和成交价上。在伦敦这尤其费力,但同样的研究困境存在于整个英格兰。Perfect Postcode 先把区域研究汇到一张地图上,再让您决定把周末、费用和精力投向哪里。',
- lessThanSurvey: '费用低于一次验房,却能更早影响您的选择。',
+ lessThanSurvey: '不到一次验房费用的十分之一,却能影响更重大的决定。',
currentTier: '当前档位',
firstNUsers: '前 {{count}} 名用户',
everyoneAfter: '之后的所有人',
@@ -992,11 +1019,12 @@ const zh: Translations = {
getStarted: '立即开始',
getStartedPrice: '立即开始:{{price}}',
noCreditCard: '无需信用卡',
+ moneyBack: '14 天退款保证。',
soldOut: '已售罄',
upcoming: '即将开放',
failedToLoad: '加载价格信息失败,请稍后重试。',
- feat1: '覆盖英格兰的 56 项筛选条件',
+ feat1: '40+ 项筛选条件和 200+ 个数据字段',
feat2: '从您的需求出发搜索每个邮编',
feat3: '无限地图探索、保存搜索和导出',
feat4: '1300 万笔历史交易和价格背景',
@@ -1017,6 +1045,26 @@ const zh: Translations = {
articlesIntro:
'浏览关于找房、通勤、学校、邮编速查、区域对比、数据覆盖、方法论和隐私的公开指南。',
supportIntro: '还有疑问?欢迎查看常见问题,或直接与我们联系。',
+ videos: '视频',
+ videosTitle: '社交媒体视频',
+ videosIntro:
+ '来自我们社交平台的短片——每一个都展示一次实际搜索,从安静街道到学校学区,再到通勤时间。',
+ video01Title: '一句话,每个邮编',
+ video01Desc: '用日常语言写下你的全部购房需求,看着英格兰每个符合条件的邮编亮起来。',
+ video02Title: '20 分钟地图',
+ video02Desc: '按通勤时间为地图着色,看清从伦敦市中心 20 分钟究竟能到达哪里。',
+ video03Title: '每个邮编都有一份档案',
+ video03Desc: '点按任意邮编即可查看其档案——成交价、学校、犯罪和街景,尽在一处。',
+ video04Title: '照片听不见声音',
+ video04Desc: '房源照片是无声的。按噪音水平筛选,找到真正安静、低于 55 分贝的街道。',
+ video05Title: '上学路线地图',
+ video05Desc: '优质小学学区、低犯罪率和预算——把家庭需求映射到整座城市。',
+ video06Title: 'Waitrose 测试',
+ video06Desc: '步行可达超市、地铁站和公园——按你的生活方式筛选,而不只是户型图。',
+ video07Title: '租房者也有地图',
+ video07Desc: '预算内的租金、短通勤和安静街道——租房网站展示房源,这里为你展示区域。',
+ video08Title: '£9.99 对比浪费的周六',
+ video08Desc: '一次糟糕的看房要花一张车票和半个周末。在预约之前就看清哪里不该去。',
source: '来源:',
optOut: '选择不公开',
attribution: '数据引用声明',
@@ -1046,7 +1094,7 @@ const zh: Translations = {
dsEthnicityName: '按族裔划分的人口(2021 年人口普查)',
dsEthnicityOrigin: 'ONS',
dsEthnicityUse:
- '按族裔群体(南亚裔、东亚裔、黑人、混血、白人、其他)划分的各地方政府辖区人口百分比。',
+ '按族裔群体(南亚裔、东亚裔、东南亚裔、黑人、混血、白人、其他)划分的各地方政府辖区人口百分比。',
dsCrimeName: '街道级犯罪数据',
dsCrimeOrigin: 'data.police.uk',
dsCrimeUse:
@@ -1226,9 +1274,9 @@ const zh: Translations = {
faqBehindData3Q: '为什么相邻邮编的犯罪数字相同?',
faqBehindData3A:
'警方街道级犯罪数据按 LSOA 发布,即大约 1,500 名居民的小型社区单元。同一 LSOA 内每个邮编都继承同一年度总数,因此一条安静的住宅街和一个街区外的繁华街道,如果在同一边界内,可能显示完全相同的数据。覆盖医院、大学校园或工业园区的邮编,人均率可能异常偏高,因为那里事件数正常但登记居民很少。',
- faqBehindData4Q: '“2 公里内的好学校”是否意味着我孩子可以入读?',
+ faqBehindData4Q: '“学区数量”是什么意思——我的孩子能入读这些学校吗?',
faqBehindData4A:
- '不一定。统计查找的是自身邮编落在您邮编中心点周围圆形范围内的公立学校。招生范围、宗教或选拔标准、兄弟姐妹优先以及录取规则都没有建模。附近的“良好”或“优秀”学校,您家未必实际有资格申请。请用此数字对比区域,决策前向学校或地方政府确认实际录取条件。',
+ '每个数字表示有多少所评级为“良好”或“优秀”的公立学校,其建模的历史学区覆盖该邮编。我们模拟英格兰按距离录取的实际分配过程:儿童(2021 年人口普查)在距离与 Ofsted 评级之间权衡后向附近学校申请,报名超额的学校按距离由近及远录取直至满额——其学区半径就是最后一名被录取儿童的距离,即各地方政府公布的“最远录取距离”。模型已用数百个此类公布距离进行校准。它估计的是在哪里大概率能获得学位,并非官方招生范围。宗教或选拔性录取、兄弟姐妹优先以及每年的边界变化均未建模,因此在做决定前,请务必向学校或地方政府确认学区和录取规则。',
faqBehindData5Q: '为什么并非每户都有光纤的邮编也显示“Gigabit”?',
faqBehindData5A:
'Ofcom Connected Nations 的宽带覆盖按邮编给出可达到每个速度档的房产百分比。我们显示有任何可用性的最高档,因此只要邮编内有一户能达到 Gigabit,就会显示“Gigabit 可用”。这回答的是“这条街上到底有没有光纤?”,但并不保证楼里每一套今天都能下单。签约前,请始终就您的具体地址向运营商核实。',
@@ -1382,7 +1430,7 @@ const zh: Translations = {
'Current energy rating': '当前能源评级',
'Potential energy rating': '潜在能源评级',
'Interior height (m)': '室内层高(米)',
- 'Street tree density percentile': '街道树木覆盖率百分位',
+ 'Tree canopy density percentile': '树冠覆盖密度百分位',
'Within conservation area': '位于保护区内',
'Listed building': '登录建筑',
@@ -1390,14 +1438,10 @@ const zh: Translations = {
'Travel time to nearest train or tube station (min)': '到最近火车或地铁站的出行时间(分钟)',
// ─ Feature names (Education) ─
- 'Good+ primary schools within 2km': '2 公里内良好及以上小学数量',
- 'Good+ secondary schools within 2km': '2 公里内良好及以上中学数量',
- 'Good+ primary schools within 5km': '5 公里内良好及以上小学数量',
- 'Good+ secondary schools within 5km': '5 公里内良好及以上中学数量',
- 'Outstanding primary schools within 2km': '2 公里内优秀小学数量',
- 'Outstanding secondary schools within 2km': '2 公里内优秀中学数量',
- 'Outstanding primary schools within 5km': '5 公里内优秀小学数量',
- 'Outstanding secondary schools within 5km': '5 公里内优秀中学数量',
+ 'Good+ primary school catchments': '良好及以上小学学区数量',
+ 'Good+ secondary school catchments': '良好及以上中学学区数量',
+ 'Outstanding primary school catchments': '优秀小学学区数量',
+ 'Outstanding secondary school catchments': '优秀中学学区数量',
'Education, Skills and Training Score': '教育、技能与培训得分',
// ─ Feature names (Area development) ─
@@ -1430,7 +1474,8 @@ const zh: Translations = {
'% White': '% 白人',
'% South Asian': '% 南亚裔',
'% Black': '% 黑人',
- '% East/SE Asian': '% 东亚/东南亚裔',
+ '% East Asian': '% 东亚裔',
+ '% SE Asian': '% 东南亚裔',
'% Mixed': '% 混血',
'% Other': '% 其他',
@@ -1497,7 +1542,8 @@ const zh: Translations = {
'Bus stop': '公交站',
'Bus station': '公交枢纽',
'Taxi rank': '出租车站',
- 'Tube station': '地铁站',
+ 'Tube station': '伦敦地铁站',
+ 'Tram & Metro stop': '有轨电车与城市轨道站',
Café: '咖啡馆',
Restaurant: '餐厅',
Pub: '酒吧',
@@ -1544,7 +1590,8 @@ const zh: Translations = {
'GP Surgery': '全科诊所',
Dentist: '牙科',
Pharmacy: '药房',
- 'Hospital & Clinic': '医院与诊所',
+ Hospital: '医院',
+ Clinic: '诊所',
Optician: '眼镜店',
Physiotherapy: '理疗',
'Counselling & Therapy': '心理咨询与治疗',
diff --git a/frontend/src/lib/api.test.ts b/frontend/src/lib/api.test.ts
index 9894a89..75019f2 100644
--- a/frontend/src/lib/api.test.ts
+++ b/frontend/src/lib/api.test.ts
@@ -82,18 +82,18 @@ describe('api utilities', () => {
it('deduplicates repeated synthetic school filters before backend routes', () => {
const features: FeatureMeta[] = [
- { name: 'Good+ primary schools within 2km', type: 'numeric', min: 0, max: 10 },
+ { name: 'Good+ primary school catchments', type: 'numeric', min: 0, max: 10 },
];
expect(
buildFilterString(
{
- [createSchoolFilterKey('primary', 'good', 2, 1)]: [1, 10],
- [createSchoolFilterKey('primary', 'good', 2, 2)]: [2, 8],
+ [createSchoolFilterKey('primary', 'good', 1)]: [1, 10],
+ [createSchoolFilterKey('primary', 'good', 2)]: [2, 8],
},
features
)
- ).toBe('Good+ primary schools within 2km:2:8');
+ ).toBe('Good+ primary school catchments:2:8');
});
it('serializes specific crime filters using their selected backend crime feature', () => {
diff --git a/frontend/src/lib/consts.ts b/frontend/src/lib/consts.ts
index d996ce2..77457ba 100644
--- a/frontend/src/lib/consts.ts
+++ b/frontend/src/lib/consts.ts
@@ -267,7 +267,15 @@ export const STACKED_GROUPS: Record<
{
label: 'Ethnic composition',
unit: '%',
- components: ['% White', '% South Asian', '% East/SE Asian', '% Black', '% Mixed', '% Other'],
+ components: [
+ '% White',
+ '% South Asian',
+ '% East Asian',
+ '% SE Asian',
+ '% Black',
+ '% Mixed',
+ '% Other',
+ ],
},
{
label: 'Political vote share',
@@ -444,7 +452,8 @@ export const STACKED_SEGMENT_COLORS: Record = {
'Other crime (avg/yr)': '#6b7280',
'% White': '#3b82f6',
'% South Asian': '#f97316',
- '% East/SE Asian': '#eab308',
+ '% East Asian': '#eab308',
+ '% SE Asian': '#ec4899',
'% Black': '#8b5cf6',
'% Mixed': '#14b8a6',
'% Other': '#6b7280',
diff --git a/frontend/src/lib/ethnicity-filter.ts b/frontend/src/lib/ethnicity-filter.ts
index 0c1bb93..ae34de0 100644
--- a/frontend/src/lib/ethnicity-filter.ts
+++ b/frontend/src/lib/ethnicity-filter.ts
@@ -6,7 +6,8 @@ export const ETHNICITIES_FILTER_KEY_PREFIX = `${ETHNICITIES_FILTER_NAME}:`;
export const ETHNICITY_FEATURE_NAMES = [
'% White',
'% South Asian',
- '% East/SE Asian',
+ '% East Asian',
+ '% SE Asian',
'% Black',
'% Mixed',
'% Other',
diff --git a/frontend/src/lib/feature-icons.tsx b/frontend/src/lib/feature-icons.tsx
index 19ed953..215296e 100644
--- a/frontend/src/lib/feature-icons.tsx
+++ b/frontend/src/lib/feature-icons.tsx
@@ -102,7 +102,7 @@ const FEATURE_ICON_PATHS: Record = {
>
),
- 'Street tree density percentile': (
+ 'Tree canopy density percentile': (
<>
@@ -146,53 +146,27 @@ const FEATURE_ICON_PATHS: Record = {
>
),
- 'Good+ primary schools within 5km': (
+ 'Good+ primary school catchments': (
<>
>
),
- 'Good+ secondary schools within 5km': (
+ 'Good+ secondary school catchments': (
<>
>
),
- 'Outstanding primary schools within 5km': (
+ 'Outstanding primary school catchments': (
<>
>
),
- 'Outstanding secondary schools within 5km': (
- <>
-
-
- >
- ),
- 'Good+ primary schools within 2km': (
- <>
-
-
-
- >
- ),
- 'Good+ secondary schools within 2km': (
- <>
-
-
- >
- ),
- 'Outstanding primary schools within 2km': (
- <>
-
-
-
- >
- ),
- 'Outstanding secondary schools within 2km': (
+ 'Outstanding secondary school catchments': (
<>
@@ -367,7 +341,15 @@ const FEATURE_ICON_PATHS: Record = {
>
),
- '% East/SE Asian': (
+ '% East Asian': (
+ <>
+
+
+
+
+ >
+ ),
+ '% SE Asian': (
<>
diff --git a/frontend/src/lib/fit-bounds.test.ts b/frontend/src/lib/fit-bounds.test.ts
new file mode 100644
index 0000000..bdc36b1
--- /dev/null
+++ b/frontend/src/lib/fit-bounds.test.ts
@@ -0,0 +1,35 @@
+import { describe, expect, it } from 'vitest';
+
+import { MAP_MIN_ZOOM } from './consts';
+import { boundsToCenterZoom } from './fit-bounds';
+
+describe('boundsToCenterZoom', () => {
+ it('centers on the middle of the box', () => {
+ const target = boundsToCenterZoom({ south: 51.4, north: 51.6, west: -0.3, east: 0.1 });
+ expect(target.lat).toBeCloseTo(51.5, 5);
+ expect(target.lng).toBeCloseTo(-0.1, 5);
+ });
+
+ it('zooms close for a small box and far out for a country-sized box', () => {
+ const street = boundsToCenterZoom({ south: 51.5, north: 51.51, west: -0.11, east: -0.1 });
+ const england = boundsToCenterZoom({ south: 50.0, north: 55.5, west: -5.7, east: 1.8 });
+ expect(street.zoom).toBeGreaterThan(england.zoom);
+ expect(england.zoom).toBeGreaterThanOrEqual(MAP_MIN_ZOOM);
+ // Greater London-ish box should land in a sensible city-scale zoom range
+ const london = boundsToCenterZoom({ south: 51.44, north: 51.59, west: -0.31, east: 0.05 });
+ expect(london.zoom).toBeGreaterThan(8);
+ expect(london.zoom).toBeLessThan(12);
+ });
+
+ it('caps zoom-in for degenerate (single point) boxes', () => {
+ const point = boundsToCenterZoom({ south: 51.5, north: 51.5, west: -0.1, east: -0.1 });
+ expect(point.zoom).toBeLessThanOrEqual(13);
+ });
+
+ it('tolerates swapped corners', () => {
+ const target = boundsToCenterZoom({ south: 51.6, north: 51.4, west: 0.1, east: -0.3 });
+ expect(target.lat).toBeCloseTo(51.5, 5);
+ expect(target.lng).toBeCloseTo(-0.1, 5);
+ expect(Number.isFinite(target.zoom)).toBe(true);
+ });
+});
diff --git a/frontend/src/lib/fit-bounds.ts b/frontend/src/lib/fit-bounds.ts
new file mode 100644
index 0000000..2265dfb
--- /dev/null
+++ b/frontend/src/lib/fit-bounds.ts
@@ -0,0 +1,45 @@
+import { MAP_MIN_ZOOM } from './consts';
+
+export interface GeoBounds {
+ south: number;
+ west: number;
+ north: number;
+ east: number;
+}
+
+/**
+ * Nominal viewport used to derive a zoom from a bounding box. The map only
+ * exposes flyTo(lat, lng, zoom), so we approximate fitBounds; being half a
+ * zoom level off for unusual window sizes is fine for "show me the matches".
+ */
+const NOMINAL_VIEWPORT = { width: 1000, height: 700 };
+const TILE_SIZE = 512;
+/** Keep matches comfortably inside the viewport edges. */
+const ZOOM_PADDING = 0.4;
+const MAX_FIT_ZOOM = 13;
+
+function mercatorY(lat: number): number {
+ const rad = (lat * Math.PI) / 180;
+ return Math.log(Math.tan(Math.PI / 4 + rad / 2));
+}
+
+/** Convert a bounding box into a flyTo target that roughly fits it on screen. */
+export function boundsToCenterZoom(bounds: GeoBounds): { lat: number; lng: number; zoom: number } {
+ const south = Math.min(bounds.south, bounds.north);
+ const north = Math.max(bounds.south, bounds.north);
+ const west = Math.min(bounds.west, bounds.east);
+ const east = Math.max(bounds.west, bounds.east);
+
+ const lonSpan = Math.max(east - west, 1e-6);
+ const mercSpan = Math.max(mercatorY(north) - mercatorY(south), 1e-6);
+
+ const zoomX = Math.log2((NOMINAL_VIEWPORT.width * 360) / (TILE_SIZE * lonSpan));
+ const zoomY = Math.log2((NOMINAL_VIEWPORT.height * 2 * Math.PI) / (TILE_SIZE * mercSpan));
+ const zoom = Math.max(MAP_MIN_ZOOM, Math.min(MAX_FIT_ZOOM, Math.min(zoomX, zoomY) - ZOOM_PADDING));
+
+ return {
+ lat: (south + north) / 2,
+ lng: (west + east) / 2,
+ zoom,
+ };
+}
diff --git a/frontend/src/lib/poi-distance-filter.ts b/frontend/src/lib/poi-distance-filter.ts
index edadee1..187b11a 100644
--- a/frontend/src/lib/poi-distance-filter.ts
+++ b/frontend/src/lib/poi-distance-filter.ts
@@ -24,6 +24,7 @@ const TRANSPORT_POI_CATEGORIES = new Set([
'Ferry',
'Rail station',
'Taxi rank',
+ 'Tram & Metro stop',
'Tube station',
]);
diff --git a/frontend/src/lib/school-filter.ts b/frontend/src/lib/school-filter.ts
index aa3a23b..e1f73b5 100644
--- a/frontend/src/lib/school-filter.ts
+++ b/frontend/src/lib/school-filter.ts
@@ -5,12 +5,10 @@ export const SCHOOL_FILTER_KEY_PREFIX = `${SCHOOL_FILTER_NAME}:`;
export type SchoolPhase = 'primary' | 'secondary';
export type SchoolRating = 'good' | 'outstanding';
-export type SchoolDistance = 2 | 5;
export interface SchoolFilterConfig {
phase: SchoolPhase;
rating: SchoolRating;
- distance: SchoolDistance;
featureName: string;
}
@@ -18,50 +16,22 @@ export const SCHOOL_FILTERS: SchoolFilterConfig[] = [
{
phase: 'primary',
rating: 'good',
- distance: 2,
- featureName: 'Good+ primary schools within 2km',
+ featureName: 'Good+ primary school catchments',
},
{
phase: 'secondary',
rating: 'good',
- distance: 2,
- featureName: 'Good+ secondary schools within 2km',
+ featureName: 'Good+ secondary school catchments',
},
{
phase: 'primary',
rating: 'outstanding',
- distance: 2,
- featureName: 'Outstanding primary schools within 2km',
+ featureName: 'Outstanding primary school catchments',
},
{
phase: 'secondary',
rating: 'outstanding',
- distance: 2,
- featureName: 'Outstanding secondary schools within 2km',
- },
- {
- phase: 'primary',
- rating: 'good',
- distance: 5,
- featureName: 'Good+ primary schools within 5km',
- },
- {
- phase: 'secondary',
- rating: 'good',
- distance: 5,
- featureName: 'Good+ secondary schools within 5km',
- },
- {
- phase: 'primary',
- rating: 'outstanding',
- distance: 5,
- featureName: 'Outstanding primary schools within 5km',
- },
- {
- phase: 'secondary',
- rating: 'outstanding',
- distance: 5,
- featureName: 'Outstanding secondary schools within 5km',
+ featureName: 'Outstanding secondary school catchments',
},
];
@@ -81,42 +51,34 @@ export function getSchoolFilterConfig(name: string): SchoolFilterConfig | null {
return SCHOOL_FILTERS.find((filter) => filter.featureName === name) ?? null;
}
-export function getSchoolFeatureName(
- phase: SchoolPhase,
- rating: SchoolRating,
- distance: SchoolDistance
-): string {
+export function getSchoolFeatureName(phase: SchoolPhase, rating: SchoolRating): string {
return (
- SCHOOL_FILTERS.find(
- (filter) => filter.phase === phase && filter.rating === rating && filter.distance === distance
- )?.featureName ?? SCHOOL_FILTERS[0].featureName
+ SCHOOL_FILTERS.find((filter) => filter.phase === phase && filter.rating === rating)
+ ?.featureName ?? SCHOOL_FILTERS[0].featureName
);
}
export function createSchoolFilterKey(
phase: SchoolPhase,
rating: SchoolRating,
- distance: SchoolDistance,
id: number | string
): string {
- return `${SCHOOL_FILTER_KEY_PREFIX}${phase}:${rating}:${distance}:${id}`;
+ return `${SCHOOL_FILTER_KEY_PREFIX}${phase}:${rating}:${id}`;
}
export function getSchoolFilterKeyId(name: string): string | null {
if (!name.startsWith(SCHOOL_FILTER_KEY_PREFIX)) return null;
- return name.split(':')[4] ?? null;
+ return name.split(':')[3] ?? null;
}
export function parseSchoolFilterKey(name: string): SchoolFilterConfig | null {
if (!name.startsWith(SCHOOL_FILTER_KEY_PREFIX)) return null;
- const [, phaseRaw, ratingRaw, distanceRaw] = name.split(':');
+ const [, phaseRaw, ratingRaw] = name.split(':');
const phase = phaseRaw as SchoolPhase;
const rating = ratingRaw as SchoolRating;
- const distance = Number(distanceRaw) as SchoolDistance;
if (
(phase !== 'primary' && phase !== 'secondary') ||
- (rating !== 'good' && rating !== 'outstanding') ||
- (distance !== 2 && distance !== 5)
+ (rating !== 'good' && rating !== 'outstanding')
) {
return null;
}
@@ -124,8 +86,7 @@ export function parseSchoolFilterKey(name: string): SchoolFilterConfig | null {
return {
phase,
rating,
- distance,
- featureName: getSchoolFeatureName(phase, rating, distance),
+ featureName: getSchoolFeatureName(phase, rating),
};
}
@@ -139,18 +100,12 @@ export function replaceSchoolFilterKeySelection(
next: {
phase?: SchoolPhase;
rating?: SchoolRating;
- distance?: SchoolDistance;
}
): string {
const config = getSchoolFilterConfig(key) ?? SCHOOL_FILTERS[0];
const parts = key.startsWith(SCHOOL_FILTER_KEY_PREFIX) ? key.split(':') : [];
- const id = parts[4] ?? '0';
- return createSchoolFilterKey(
- next.phase ?? config.phase,
- next.rating ?? config.rating,
- next.distance ?? config.distance,
- id
- );
+ const id = parts[3] ?? '0';
+ return createSchoolFilterKey(next.phase ?? config.phase, next.rating ?? config.rating, id);
}
export function getDefaultSchoolFeatureName(features: FeatureMeta[]): string | null {
@@ -171,14 +126,7 @@ export function normalizeSchoolFilters(filters: FeatureFilters): FeatureFilters
if (isBackendSchoolFeatureName(name)) {
const config = getSchoolFilterConfig(name);
if (!config) continue;
- next[
- createSchoolFilterKey(
- config.phase,
- config.rating,
- config.distance,
- Object.keys(next).length
- )
- ] = value;
+ next[createSchoolFilterKey(config.phase, config.rating, Object.keys(next).length)] = value;
changed = true;
continue;
}
@@ -201,9 +149,9 @@ export function getSchoolFilterMeta(features: FeatureMeta[]): FeatureMeta {
min: sourceFeature?.min ?? 0,
max: sourceFeature?.max ?? 10,
step: 1,
- description: 'Rated primary and secondary schools nearby',
+ description: 'Rated schools whose catchment area likely covers the postcode',
detail:
- 'Filter by primary or secondary schools, Ofsted rating, and whether schools are within 2km or 5km.',
+ 'Filter by how many Good+ or Outstanding primary or secondary schools have a historical catchment area covering the postcode. Catchments are modelled from each school’s pupil numbers and local child population, approximating distance-based admissions.',
source: 'ofsted',
raw: true,
};
diff --git a/frontend/src/lib/url-state.test.ts b/frontend/src/lib/url-state.test.ts
index 7d193ad..bfd3c2f 100644
--- a/frontend/src/lib/url-state.test.ts
+++ b/frontend/src/lib/url-state.test.ts
@@ -352,8 +352,8 @@ describe('url-state', () => {
});
it('round-trips repeated school filters with dedicated URL params', () => {
- const schoolOne = createSchoolFilterKey('primary', 'good', 2, 1);
- const schoolTwo = createSchoolFilterKey('secondary', 'outstanding', 5, 2);
+ const schoolOne = createSchoolFilterKey('primary', 'good', 1);
+ const schoolTwo = createSchoolFilterKey('secondary', 'outstanding', 2);
const params = stateToParams(
null,
@@ -366,18 +366,22 @@ describe('url-state', () => {
'area'
);
- expect(params.getAll('school')).toEqual([
- 'primary:good:2:1:10',
- 'secondary:outstanding:5:2:15',
- ]);
+ expect(params.getAll('school')).toEqual(['primary:good:1:10', 'secondary:outstanding:2:15']);
expect(params.getAll('filter')).toEqual([]);
window.history.replaceState({}, '', `/?${params.toString()}`);
const state = parseUrlState();
expect(state.filters).toEqual({
- [createSchoolFilterKey('primary', 'good', 2, 0)]: [1, 10],
- [createSchoolFilterKey('secondary', 'outstanding', 5, 1)]: [2, 15],
+ [createSchoolFilterKey('primary', 'good', 0)]: [1, 10],
+ [createSchoolFilterKey('secondary', 'outstanding', 1)]: [2, 15],
+ });
+ });
+
+ it('parses legacy school URL params that still carry a distance segment', () => {
+ window.history.replaceState({}, '', '/?school=primary%3Agood%3A2%3A1%3A10');
+ expect(parseUrlState().filters).toEqual({
+ [createSchoolFilterKey('primary', 'good', 0)]: [1, 10],
});
});
diff --git a/frontend/src/lib/url-state.ts b/frontend/src/lib/url-state.ts
index 89f233e..d61cf22 100644
--- a/frontend/src/lib/url-state.ts
+++ b/frontend/src/lib/url-state.ts
@@ -12,7 +12,6 @@ import {
createSchoolFilterKey,
getSchoolFilterConfig,
isSchoolFilterName,
- type SchoolDistance,
type SchoolPhase,
type SchoolRating,
} from './school-filter';
@@ -122,22 +121,22 @@ function parseFilters(params: URLSearchParams): FeatureFilters {
schoolParams.forEach((entry, index) => {
const parts = entry.split(':');
- if (parts.length !== 5) return;
+ // 4 parts is the current phase:rating:min:max form; 5 parts is the legacy
+ // phase:rating:distance:min:max form, whose distance segment is ignored.
+ if (parts.length !== 4 && parts.length !== 5) return;
const phase = parts[0] as SchoolPhase;
const rating = parts[1] as SchoolRating;
- const distance = Number(parts[2]) as SchoolDistance;
- const min = Number(parts[3]);
- const max = Number(parts[4]);
+ const min = Number(parts[parts.length - 2]);
+ const max = Number(parts[parts.length - 1]);
if (
(phase !== 'primary' && phase !== 'secondary') ||
(rating !== 'good' && rating !== 'outstanding') ||
- (distance !== 2 && distance !== 5) ||
isNaN(min) ||
isNaN(max)
) {
return;
}
- filters[createSchoolFilterKey(phase, rating, distance, index)] = [min, max];
+ filters[createSchoolFilterKey(phase, rating, index)] = [min, max];
});
crimeParams.forEach((entry, index) => {
@@ -379,10 +378,7 @@ export function stateToParams(
const schoolConfig = getSchoolFilterConfig(name);
if (schoolConfig && isSchoolFilterName(name)) {
const [min, max] = value as [number, number];
- params.append(
- 'school',
- `${schoolConfig.phase}:${schoolConfig.rating}:${schoolConfig.distance}:${min}:${max}`
- );
+ params.append('school', `${schoolConfig.phase}:${schoolConfig.rating}:${min}:${max}`);
continue;
}
diff --git a/pipeline/check_school_cutoffs.py b/pipeline/check_school_cutoffs.py
new file mode 100644
index 0000000..0215a27
--- /dev/null
+++ b/pipeline/check_school_cutoffs.py
@@ -0,0 +1,297 @@
+"""Evaluate modelled school catchment radii against published cutoffs.
+
+Local authorities publish each school's "last distance offered" in their
+yearly allocation reports; ``property-data/ground_truth/cutoffs_*.json``
+holds a scraped sample of those figures (see the collection notes in each
+file's ``source_url`` fields). This script matches them to the per-school
+radii emitted by ``pipeline.transform.school_catchments --schools-output``
+and reports how well the model reproduces reality, so the preference-bonus
+constants can be calibrated.
+
+Headline metrics use non-faith schools whose published cutoff was a binding
+distance. Faith schools are reported separately (their distance criterion
+applies within faith priority, so published figures aren't comparable), as
+are "all applicants offered" schools, where the model should ideally show no
+binding cutoff.
+"""
+
+import argparse
+import difflib
+import json
+import re
+from pathlib import Path
+
+import numpy as np
+import polars as pl
+
+_NOISE_WORDS = re.compile(
+ r"\b(the|of|and|c\s*of\s*e|cofe|ce|rc|voluntary|aided|controlled|va|vc)\b"
+)
+_NON_ALNUM = re.compile(r"[^a-z0-9 ]")
+_SCHOOL_WORDS = re.compile(
+ r"\b(school|academy|primary|secondary|junior|infant|community|college|high)\b"
+)
+
+
+def normalize_name(name: str, strip_school_words: bool = False) -> str:
+ s = name.lower().replace("&", " and ").replace("st.", "st ").replace("'", "")
+ s = _NON_ALNUM.sub(" ", s)
+ s = _NOISE_WORDS.sub(" ", s)
+ if strip_school_words:
+ s = _SCHOOL_WORDS.sub(" ", s)
+ return " ".join(s.split())
+
+
+def normalize_la(la: str) -> str:
+ s = _NON_ALNUM.sub(" ", la.lower().replace("&", " and "))
+ return " ".join(s.replace("city of", "").split())
+
+
+def load_ground_truth(directory: Path) -> pl.DataFrame:
+ rows = []
+ for path in sorted(directory.glob("cutoffs_*.json")):
+ for row in json.loads(path.read_text()):
+ rows.append(
+ {
+ "school_name": row["school_name"],
+ "la": row["la"],
+ "phase": row["phase"],
+ "entry_year": int(row.get("entry_year") or 0),
+ "cutoff_km": (
+ float(row["cutoff_km"]) if row.get("cutoff_km") is not None else None
+ ),
+ "all_offered": bool(row.get("all_offered", False)),
+ "faith_school": bool(row.get("faith_school", False)),
+ "school_postcode": row.get("school_postcode"),
+ "source_url": row.get("source_url", ""),
+ }
+ )
+ if not rows:
+ raise SystemExit(f"No cutoffs_*.json files with rows under {directory}")
+ df = pl.DataFrame(rows, schema_overrides={"school_postcode": pl.Utf8})
+ print(f"Ground truth rows: {len(df)} from {directory}")
+ return df
+
+
+def match_schools(truth: pl.DataFrame, gias: pl.DataFrame) -> pl.DataFrame:
+ """Attach GIAS URNs to ground-truth rows by postcode, then name."""
+ def stripped(name: str) -> str:
+ return normalize_name(name, strip_school_words=True)
+
+ gias = gias.with_columns(
+ pl.col("name")
+ .map_elements(normalize_name, return_dtype=pl.Utf8)
+ .alias("_name_norm"),
+ pl.col("name")
+ .map_elements(stripped, return_dtype=pl.Utf8)
+ .alias("_name_stripped"),
+ pl.col("local_authority")
+ .map_elements(normalize_la, return_dtype=pl.Utf8)
+ .alias("_la_norm"),
+ pl.col("postcode").str.replace_all(" ", "").str.to_uppercase().alias("_pc"),
+ )
+ truth = truth.with_columns(
+ pl.col("school_name")
+ .map_elements(normalize_name, return_dtype=pl.Utf8)
+ .alias("_name_norm"),
+ pl.col("school_name")
+ .map_elements(stripped, return_dtype=pl.Utf8)
+ .alias("_name_stripped"),
+ pl.col("la")
+ .map_elements(normalize_la, return_dtype=pl.Utf8)
+ .alias("_la_norm"),
+ pl.col("school_postcode")
+ .str.replace_all(" ", "")
+ .str.to_uppercase()
+ .alias("_pc"),
+ ).with_row_index("_row_id")
+
+ # 1. Exact postcode match (unique postcodes only — site-sharing schools
+ # would mismatch phases otherwise; those fall through to name matching).
+ pc_unique = gias.filter(pl.col("_pc").is_not_null()).unique(
+ subset="_pc", keep="none"
+ )
+ by_pc = truth.filter(pl.col("_pc").is_not_null()).join(
+ pc_unique.select("_pc", "urn"), on="_pc", how="inner"
+ )
+ matched_ids = set(by_pc["_row_id"].to_list())
+
+ # 2. Exact normalized (name, LA) match, unique on both sides.
+ gias_named = gias.unique(subset=["_name_norm", "_la_norm"], keep="none")
+ remaining = truth.filter(~pl.col("_row_id").is_in(list(matched_ids)))
+ by_name = remaining.join(
+ gias_named.select("_name_norm", "_la_norm", "urn"),
+ on=["_name_norm", "_la_norm"],
+ how="inner",
+ )
+ matched_ids |= set(by_name["_row_id"].to_list())
+
+ # 3. Reports often print informal names ("Ashmole Primary" for "Ashmole
+ # Primary School"): match on names with school-type words stripped,
+ # unique on both sides so site-sharing infant/junior pairs fall through.
+ gias_stripped = gias.filter(pl.col("_name_stripped") != "").unique(
+ subset=["_name_stripped", "_la_norm"], keep="none"
+ )
+ remaining = truth.filter(
+ (~pl.col("_row_id").is_in(list(matched_ids))) & (pl.col("_name_stripped") != "")
+ ).unique(subset=["_name_stripped", "_la_norm", "phase"], keep="none")
+ by_stripped = remaining.join(
+ gias_stripped.select("_name_stripped", "_la_norm", "urn"),
+ on=["_name_stripped", "_la_norm"],
+ how="inner",
+ )
+ matched_ids |= set(by_stripped["_row_id"].to_list())
+
+ # 4. Fuzzy name match within the LA: unique best candidate >= 0.87.
+ remaining = truth.filter(~pl.col("_row_id").is_in(list(matched_ids)))
+ fuzzy_rows = []
+ gias_by_la: dict[str, pl.DataFrame] = {}
+ for row in remaining.iter_rows(named=True):
+ la = row["_la_norm"]
+ if la not in gias_by_la:
+ gias_by_la[la] = gias.filter(pl.col("_la_norm") == la)
+ candidates = gias_by_la[la]
+ if candidates.is_empty():
+ continue
+ scores = [
+ difflib.SequenceMatcher(None, row["_name_norm"], cand).ratio()
+ for cand in candidates["_name_norm"].to_list()
+ ]
+ order = np.argsort(scores)[::-1]
+ if scores[order[0]] >= 0.87 and (
+ len(order) == 1 or scores[order[1]] < scores[order[0]] - 0.04
+ ):
+ fuzzy_rows.append({**row, "urn": candidates["urn"][int(order[0])]})
+ by_fuzzy = (
+ pl.DataFrame(fuzzy_rows).with_columns(pl.col("_row_id").cast(pl.UInt32))
+ if fuzzy_rows
+ else None
+ )
+
+ parts = [by_pc, by_name, by_stripped] + ([by_fuzzy] if by_fuzzy is not None else [])
+ matched = pl.concat(
+ [p.select(truth.columns + ["urn"]) for p in parts if not p.is_empty()]
+ ).unique(subset="_row_id", keep="first")
+ print(
+ f"Matched {len(matched)}/{len(truth)} ground-truth rows to GIAS URNs "
+ f"(postcode {len(by_pc)}, exact name {len(by_name)}, "
+ f"stripped {len(by_stripped)}, fuzzy {0 if by_fuzzy is None else len(by_fuzzy)})"
+ )
+ return matched
+
+
+def evaluate(matched: pl.DataFrame, radii: pl.DataFrame) -> pl.DataFrame:
+ joined = matched.join(radii, on=["urn", "phase"], how="inner")
+ print(f"Joined to modelled radii: {len(joined)} rows")
+
+ # Published figures occasionally include non-typical admits (a child who
+ # moved mid-process can print as hundreds of km); cap at distances a
+ # distance criterion can plausibly produce.
+ binding = joined.filter(
+ ~pl.col("all_offered")
+ & pl.col("cutoff_km").is_between(0.05, 20.0)
+ )
+
+ def report(df: pl.DataFrame, label: str) -> None:
+ if df.is_empty():
+ print(f"\n{label}: no rows")
+ return
+ truth_km = df["cutoff_km"].to_numpy()
+ model_km = df["radius_km"].to_numpy()
+ log_ratio = np.log2(model_km / truth_km)
+ within2 = float(np.mean(np.abs(log_ratio) <= 1))
+ rank = (
+ pl.DataFrame({"t": truth_km, "m": model_km})
+ .select(pl.corr("t", "m", method="spearman"))
+ .item()
+ )
+ print(
+ f"\n{label} (n={len(df)}):\n"
+ f" median bias (log2 model/truth): {np.median(log_ratio):+.2f} "
+ f"(x{2 ** np.median(log_ratio):.2f})\n"
+ f" median |log2 error|: {np.median(np.abs(log_ratio)):.2f} "
+ f"(x{2 ** np.median(np.abs(log_ratio)):.2f})\n"
+ f" within factor 2: {within2:.0%}\n"
+ f" Spearman rank corr: {rank:.2f}"
+ )
+
+ for phase in ("primary", "secondary"):
+ report(
+ binding.filter((pl.col("phase") == phase) & ~pl.col("faith_school")),
+ f"BINDING, non-faith, {phase}",
+ )
+ report(binding.filter(pl.col("faith_school")), "BINDING, faith (informational)")
+
+ offered = joined.filter(pl.col("all_offered"))
+ if not offered.is_empty():
+ unbound_share = float((~offered["filled"]).mean())
+ print(
+ f"\nALL-OFFERED schools (n={len(offered)}): model agrees no binding "
+ f"cutoff for {unbound_share:.0%}; median modelled radius "
+ f"{offered['radius_km'].median():.2f} km"
+ )
+ return binding
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description="Compare modelled catchment radii with published cutoffs"
+ )
+ parser.add_argument(
+ "--ground-truth-dir",
+ type=Path,
+ default=Path("property-data/ground_truth"),
+ )
+ parser.add_argument(
+ "--radii",
+ type=Path,
+ default=Path("property-data/school_catchment_radii.parquet"),
+ help="Per-school radii parquet from school_catchments --schools-output",
+ )
+ parser.add_argument("--gias", type=Path, default=Path("property-data/gias.parquet"))
+ parser.add_argument(
+ "--matched-out",
+ type=Path,
+ default=None,
+ help="Optional CSV of matched rows for inspection",
+ )
+ args = parser.parse_args()
+
+ truth = load_ground_truth(args.ground_truth_dir)
+ # One row per school+phase: keep the most recent entry year.
+ truth = (
+ truth.sort("entry_year", descending=True)
+ .unique(subset=["school_name", "la", "phase"], keep="first")
+ )
+ gias = pl.read_parquet(args.gias).select(
+ "urn", "name", "postcode", "local_authority", "religious_character"
+ )
+ radii = pl.read_parquet(args.radii).unique(subset=["urn", "phase"], keep="first")
+
+ matched = match_schools(truth, gias.drop("religious_character"))
+ # GIAS religious character is authoritative; the scraped name-based flag
+ # only covers rows that failed to match.
+ matched = matched.join(
+ gias.select("urn", "religious_character"), on="urn", how="left"
+ ).with_columns(
+ pl.when(pl.col("religious_character").is_not_null())
+ .then(~pl.col("religious_character").is_in(["None", "Does not apply"]))
+ .otherwise(pl.col("faith_school"))
+ .alias("faith_school")
+ )
+ binding = evaluate(matched, radii)
+
+ if args.matched_out is not None:
+ out = matched.join(radii, on=["urn", "phase"], how="inner").drop(
+ "_row_id", "_name_norm", "_la_norm", "_pc"
+ )
+ args.matched_out.parent.mkdir(parents=True, exist_ok=True)
+ out.write_csv(args.matched_out)
+ print(f"\nWrote matched rows to {args.matched_out}")
+
+ if binding.is_empty():
+ raise SystemExit("No binding, matchable cutoffs — nothing to calibrate on")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pipeline/download/ethnicity.py b/pipeline/download/ethnicity.py
index 6e31ee3..ade2f54 100644
--- a/pipeline/download/ethnicity.py
+++ b/pipeline/download/ethnicity.py
@@ -1,4 +1,22 @@
+"""Download Census 2021 ethnic group (TS021) by LSOA.
+
+Downloads the 20-category ethnic-group breakdown (TS021, classification
+C2021_ETH_20) from the NOMIS API at LSOA 2021 granularity, folds the 19 detailed
+leaf categories into our 7 output buckets, and emits one row per LSOA with the
+percentage in each bucket.
+
+Sourcing at LSOA (~33,755 England areas) rather than Local Authority (~319) is a
+~100x granularity gain with no change to the 7-bucket output schema: two very
+different neighbourhoods in one borough no longer share an identical ethnicity
+profile. The join key downstream (merge.py) is `lsoa21`, the same key already
+used for median age and IoD.
+
+Source: NOMIS (ONS Census 2021 — TS021 dataset, NM_2041_1)
+License: Open Government Licence v3.0
+"""
+
import argparse
+from io import BytesIO
from pathlib import Path
import httpx
@@ -6,143 +24,169 @@ import polars as pl
pl.Config.set_tbl_cols(-1)
+# NOMIS API: Census 2021 TS021 (ethnic group, 20 categories) by LSOA 2021
+# (TYPE151). c2021_eth_20=1..19 selects the 19 detailed leaf categories
+# (excluding the 5 broad aggregates 1001-1005 and the 0 = Total, which we
+# re-derive ourselves). measures=20100 selects the absolute count.
+BASE_URL = (
+ "https://www.nomisweb.co.uk/api/v01/dataset/NM_2041_1.data.csv"
+ "?geography=TYPE151"
+ "&c2021_eth_20=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19"
+ "&measures=20100"
+ "&select=GEOGRAPHY_CODE,C2021_ETH_20_NAME,OBS_VALUE"
+)
+PAGE_SIZE = 25000
-URL = "https://www.ethnicity-facts-figures.service.gov.uk/uk-population-by-ethnicity/national-and-regional-populations/regional-ethnic-diversity/latest/downloads/population-by-ethnicity-and-local-authority-2021.csv"
-
-GEOGRAPHY_CODE_REPLACEMENTS = {
- # 2023 Cumberland unitary authority
- "E07000026": "E06000063", # Allerdale
- "E07000028": "E06000063", # Carlisle
- "E07000029": "E06000063", # Copeland
- # 2023 Westmorland and Furness unitary authority
- "E07000027": "E06000064", # Barrow-in-Furness
- "E07000030": "E06000064", # Eden
- "E07000031": "E06000064", # South Lakeland
- # 2023 North Yorkshire unitary authority
- "E07000163": "E06000065", # Craven
- "E07000164": "E06000065", # Hambleton
- "E07000165": "E06000065", # Harrogate
- "E07000166": "E06000065", # Richmondshire
- "E07000167": "E06000065", # Ryedale
- "E07000168": "E06000065", # Scarborough
- "E07000169": "E06000065", # Selby
- # 2023 Somerset unitary authority
- "E07000187": "E06000066", # Mendip
- "E07000188": "E06000066", # Sedgemoor
- "E07000189": "E06000066", # South Somerset
- "E07000246": "E06000066", # Somerset West and Taunton
+# Map the 19 detailed NOMIS C2021_ETH_20 leaf categories to our 7 output groups.
+# The Asian split:
+# * "Chinese" routes to East Asian.
+# * "Other Asian" routes to SE Asian (not South Asian). The ONS "Other Asian"
+# bucket is predominantly East/Southeast Asian (Filipino, Vietnamese, Thai,
+# Japanese, Korean, ...) rather than South Asian, so routing it here avoids
+# inflating "% South Asian". The split is approximate (the bucket also holds
+# some South Asian groups such as Sri Lankan/Nepalese).
+GROUP_MAP = {
+ # White
+ "White: English, Welsh, Scottish, Northern Irish or British": "White",
+ "White: Irish": "White",
+ "White: Gypsy or Irish Traveller": "White",
+ "White: Roma": "White",
+ "White: Other White": "White",
+ # South Asian
+ "Asian, Asian British or Asian Welsh: Indian": "South Asian",
+ "Asian, Asian British or Asian Welsh: Pakistani": "South Asian",
+ "Asian, Asian British or Asian Welsh: Bangladeshi": "South Asian",
+ # East / Southeast Asian
+ "Asian, Asian British or Asian Welsh: Chinese": "East Asian",
+ "Asian, Asian British or Asian Welsh: Other Asian": "SE Asian",
+ # Black
+ "Black, Black British, Black Welsh, Caribbean or African: African": "Black",
+ "Black, Black British, Black Welsh, Caribbean or African: Caribbean": "Black",
+ "Black, Black British, Black Welsh, Caribbean or African: Other Black": "Black",
+ # Mixed
+ "Mixed or Multiple ethnic groups: White and Asian": "Mixed",
+ "Mixed or Multiple ethnic groups: White and Black African": "Mixed",
+ "Mixed or Multiple ethnic groups: White and Black Caribbean": "Mixed",
+ "Mixed or Multiple ethnic groups: Other Mixed or Multiple ethnic groups": "Mixed",
+ # Other
+ "Other ethnic group: Arab": "Other",
+ "Other ethnic group: Any other ethnic group": "Other",
}
+# The 7 output groups, in a fixed order so the largest-remainder rounding below
+# is deterministic regardless of pivot column ordering.
+OUTPUT_GROUPS = ["White", "South Asian", "East Asian", "SE Asian", "Black", "Mixed", "Other"]
+assert set(GROUP_MAP.values()) == set(OUTPUT_GROUPS), (
+ "GROUP_MAP values must be exactly the OUTPUT_GROUPS"
+)
+
def _ethnicity_percentages(df: pl.DataFrame) -> pl.DataFrame:
- # Use the detailed 19+1 breakdown to get sub-categories for Asian ethnicity,
- # then aggregate back to the broad groups plus a South Asian / East/SE Asian
- # split (Indian/Pakistani/Bangladeshi vs Chinese + other East/SE Asian).
- detailed = df.filter(
- (pl.col("Ethnicity_type") == "ONS 2021 19+1") & (pl.col("Ethnicity") != "All")
+ """Fold the 19 NOMIS leaf categories into 7-bucket percentages per LSOA.
+
+ `df` is the long-format NOMIS download with columns GEOGRAPHY_CODE,
+ C2021_ETH_20_NAME (the detailed leaf label) and OBS_VALUE (a count). A
+ missing/extra/relabelled leaf category would silently drop people from the
+ denominator, so we validate the category set against GROUP_MAP first and
+ fail loudly otherwise.
+ """
+ found = set(df["C2021_ETH_20_NAME"].unique().to_list())
+ expected = set(GROUP_MAP)
+ if found != expected:
+ missing = sorted(expected - found)
+ unexpected = sorted(found - expected)
+ raise ValueError(
+ "Census ethnic-group categories do not match the expected NOMIS "
+ "TS021 C2021_ETH_20 leaf set.\n"
+ f" expected {len(expected)} categories, found {len(found)}\n"
+ f" missing: {missing}\n"
+ f" unexpected: {unexpected}\n"
+ "Refusing to compute percentages against an unrecognised breakdown."
+ )
+
+ # Map each leaf to its output group and sum counts per (LSOA, group). Summing
+ # counts (not rounded percentages) keeps the denominator exact.
+ grouped = (
+ df.with_columns(
+ pl.col("C2021_ETH_20_NAME").replace_strict(GROUP_MAP).alias("group"),
+ pl.col("OBS_VALUE").cast(pl.Float64, strict=False).alias("_count"),
+ )
+ .group_by("GEOGRAPHY_CODE", "group")
+ .agg(pl.col("_count").sum())
+ )
+ wide = grouped.pivot(on="group", index="GEOGRAPHY_CODE", values="_count").rename(
+ {"GEOGRAPHY_CODE": "lsoa21"}
)
- # Map detailed categories to our output groups
- group_map = {
- # White
- "White British": "White",
- "White Irish": "White",
- "Gypsy Or Irish Traveller": "White",
- "Roma": "White",
- "Any Other White Background": "White",
- # South Asian
- "Indian": "South Asian",
- "Pakistani": "South Asian",
- "Bangladeshi": "South Asian",
- # East / Southeast Asian. The ONS "Any Other Asian Background" bucket is
- # predominantly East/Southeast Asian (Filipino, Vietnamese, Thai,
- # Japanese, Korean, ...) rather than South Asian, so route it here rather
- # than inflating "% South Asian". The split is approximate (the ONS
- # bucket also holds some South Asian groups such as Sri Lankan/Nepalese).
- "Chinese": "East/SE Asian",
- "Any Other Asian Background": "East/SE Asian",
- # Black
- "Black African": "Black",
- "Black Caribbean": "Black",
- "Any Other Black Background": "Black",
- # Mixed
- "Mixed White And Asian": "Mixed",
- "Mixed White And Black African": "Mixed",
- "Mixed White And Black Caribbean": "Mixed",
- "Any Other Mixed/Multiple Ethnic Background": "Mixed",
- # Other
- "Arab": "Other",
- "Any Other Ethnic Background": "Other",
- }
+ # A group with no people in an LSOA is absent from the long rows, so the pivot
+ # leaves a null; treat it as 0 before normalising.
+ wide = wide.with_columns(pl.col(OUTPUT_GROUPS).fill_null(0.0))
- detailed = detailed.with_columns(
- pl.col("Ethnicity").replace_strict(group_map).alias("group"),
- pl.col("Geography_code")
- .replace(GEOGRAPHY_CODE_REPLACEMENTS)
- .alias("output_geography_code"),
- pl.col("Ethnic Population").cast(pl.Float64, strict=False).alias("_population"),
- )
-
- # Sum counts, not rounded percentages, so old districts can be safely
- # recombined into their current unitary authorities.
- grouped = detailed.group_by("output_geography_code", "group").agg(
- pl.col("_population").sum()
- )
- wide = grouped.pivot(
- on="group", index="output_geography_code", values="_population"
- ).rename({"output_geography_code": "Geography_code"})
-
- # Normalize so each row sums to exactly 100%, then round using largest-remainder
- # method to preserve the sum. Independent rounding of 6 values can drift ±0.3.
- group_cols = [c for c in wide.columns if c != "Geography_code"]
- row_total = sum(pl.col(c) for c in group_cols)
- # Scale each group so they sum to exactly 100
+ # Normalize so each row sums to exactly 100%, then round with the
+ # largest-remainder method to preserve the sum. Independent rounding of 6
+ # values can drift +/-0.3.
+ row_total = sum(pl.col(c) for c in OUTPUT_GROUPS)
wide = wide.with_columns(
- [(pl.col(c) / row_total * 100.0).alias(c) for c in group_cols]
+ [(pl.col(c) / row_total * 100.0).alias(c) for c in OUTPUT_GROUPS]
)
- # Round to 1 decimal, then adjust the largest group to absorb residual
- rounded_cols = [pl.col(c).round(1).alias(c) for c in group_cols]
- wide = wide.with_columns(rounded_cols)
- rounded_sum = sum(pl.col(c) for c in group_cols)
+ # Round to 1 decimal, then adjust the largest group to absorb the residual.
+ wide = wide.with_columns([pl.col(c).round(1).alias(c) for c in OUTPUT_GROUPS])
+ rounded_sum = sum(pl.col(c) for c in OUTPUT_GROUPS)
residual = (100.0 - rounded_sum).round(1)
- # Find which group is largest per row and add the residual there
- largest_col = pl.concat_list(group_cols).list.arg_max()
+ largest_col = pl.concat_list(OUTPUT_GROUPS).list.arg_max()
wide = wide.with_columns(
[
pl.when(largest_col == i)
.then(pl.col(c) + residual)
.otherwise(pl.col(c))
.alias(c)
- for i, c in enumerate(group_cols)
+ for i, c in enumerate(OUTPUT_GROUPS)
]
)
- # Rename columns to be descriptive
- rename_map = {col: f"% {col}" for col in wide.columns if col != "Geography_code"}
- wide = wide.rename(rename_map)
- return wide
+ rename_map = {col: f"% {col}" for col in OUTPUT_GROUPS}
+ return wide.rename(rename_map)
def download_and_convert(output_path: Path) -> None:
- print("Downloading ethnicity data...")
- response = httpx.get(URL, follow_redirects=True, timeout=60)
- response.raise_for_status()
+ print("Downloading Census 2021 ethnic group (TS021) by LSOA from NOMIS...")
+ frames = []
+ offset = 0
+ while True:
+ url = f"{BASE_URL}&recordoffset={offset}"
+ response = httpx.get(url, follow_redirects=True, timeout=120)
+ response.raise_for_status()
+ if len(response.content) == 0:
+ break
+ chunk = pl.read_csv(BytesIO(response.content))
+ if chunk.height == 0:
+ break
+ frames.append(chunk)
+ print(f" Fetched {chunk.height} rows (offset={offset})")
+ if chunk.height < PAGE_SIZE:
+ break
+ offset += PAGE_SIZE
- df = pl.read_csv(response.content)
- print(f"Raw shape: {df.head(100)}")
+ df = pl.concat(frames)
+ print(f"Total rows: {df.height}")
+
+ # Filter to England only (E-prefixed LSOA codes); the merge joins on the
+ # English postcode universe and the IoD coverage check is England-wide.
+ df = df.filter(pl.col("GEOGRAPHY_CODE").str.starts_with("E"))
wide = _ethnicity_percentages(df)
- print(f"Output shape: {wide.shape}")
+ print(f"England LSOAs: {wide.height}")
print(f"Columns: {wide.columns}")
+ output_path.parent.mkdir(parents=True, exist_ok=True)
wide.write_parquet(output_path, compression="zstd")
print(f"Saved to {output_path}")
def main() -> None:
parser = argparse.ArgumentParser(
- description="Download and convert ethnicity by local authority data"
+ description="Download Census 2021 ethnic group (TS021) by LSOA"
)
parser.add_argument(
"--output", type=Path, required=True, help="Output parquet file path"
diff --git a/pipeline/download/gias.py b/pipeline/download/gias.py
index 11e4387..c6676a9 100644
--- a/pipeline/download/gias.py
+++ b/pipeline/download/gias.py
@@ -192,6 +192,10 @@ def _read_csv_from_zip(zip_bytes: bytes) -> pl.DataFrame:
infer_schema_length=20000,
null_values=_NULL_VALUES,
truncate_ragged_lines=True,
+ # Force the phone number to stay a string: schema inference reads it as
+ # an integer and strips the leading 0 (e.g. 020 8427 7222 -> 2084277222),
+ # making nearly every school phone number un-diallable.
+ schema_overrides={"TelephoneNum": pl.String},
)
diff --git a/pipeline/download/lsoa_children.py b/pipeline/download/lsoa_children.py
new file mode 100644
index 0000000..8e8c032
--- /dev/null
+++ b/pipeline/download/lsoa_children.py
@@ -0,0 +1,93 @@
+"""Download Census 2021 children by five-year age band per LSOA.
+
+Source: NOMIS (ONS Census 2021 — TS007A dataset, age by five-year bands)
+License: Open Government Licence v3.0
+
+Used to estimate how many primary-age (4-10) and secondary-age (11-15)
+children live in each LSOA, which drives the school catchment model. Census
+bands don't align with school phases, so phase totals take fractional shares
+of the 0-4, 10-14 and 15-19 bands (one fifth per single year of age).
+"""
+
+import argparse
+from io import BytesIO
+from pathlib import Path
+
+import httpx
+import polars as pl
+
+# NOMIS API: Census 2021 TS007A (age, five-year bands) by LSOA 2021 (TYPE151).
+# c2021_age_19 codes: 1 = 0-4, 2 = 5-9, 3 = 10-14, 4 = 15-19.
+# NOMIS paginates at 25,000 rows by default, so we paginate with recordoffset.
+BASE_URL = (
+ "https://www.nomisweb.co.uk/api/v01/dataset/NM_2020_1.data.csv"
+ "?date=latest&geography=TYPE151&measures=20100&c2021_age_19=1,2,3,4"
+ "&select=GEOGRAPHY_CODE,C2021_AGE_19,OBS_VALUE"
+)
+PAGE_SIZE = 25000
+
+AGE_BAND_COLUMNS = {
+ 1: "aged_0_4",
+ 2: "aged_5_9",
+ 3: "aged_10_14",
+ 4: "aged_15_19",
+}
+
+
+def download_and_convert(output_path: Path) -> None:
+ print("Downloading Census 2021 LSOA age bands from NOMIS...")
+ frames = []
+ offset = 0
+ while True:
+ url = f"{BASE_URL}&recordoffset={offset}"
+ response = httpx.get(url, follow_redirects=True, timeout=120)
+ response.raise_for_status()
+ if len(response.content) == 0:
+ break
+ chunk = pl.read_csv(BytesIO(response.content))
+ if chunk.height == 0:
+ break
+ frames.append(chunk)
+ print(f" Fetched {chunk.height} rows (offset={offset})")
+ if chunk.height < PAGE_SIZE:
+ break
+ offset += PAGE_SIZE
+
+ df = pl.concat(frames)
+ print(f"Total rows: {df.height}")
+
+ result = (
+ df.rename({"GEOGRAPHY_CODE": "lsoa21"})
+ .pivot(on="C2021_AGE_19", index="lsoa21", values="OBS_VALUE")
+ .rename({str(code): name for code, name in AGE_BAND_COLUMNS.items()})
+ .with_columns(pl.col(name).cast(pl.UInt32) for name in AGE_BAND_COLUMNS.values())
+ .filter(pl.col("lsoa21").str.starts_with("E"))
+ .sort("lsoa21")
+ )
+
+ missing = [c for c in AGE_BAND_COLUMNS.values() if c not in result.columns]
+ if missing:
+ raise ValueError(f"NOMIS response missing age bands: {missing}")
+
+ print(f"England LSOAs: {result.height}")
+ for name in AGE_BAND_COLUMNS.values():
+ print(f" {name}: total {result[name].sum():,}")
+
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ result.write_parquet(output_path, compression="zstd")
+ print(f"Saved to {output_path}")
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description="Download Census 2021 age bands (children) by LSOA"
+ )
+ parser.add_argument(
+ "--output", type=Path, required=True, help="Output parquet file path"
+ )
+ args = parser.parse_args()
+ download_and_convert(args.output)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pipeline/download/naptan.py b/pipeline/download/naptan.py
index 3dac4c7..186903c 100644
--- a/pipeline/download/naptan.py
+++ b/pipeline/download/naptan.py
@@ -12,8 +12,18 @@ import polars as pl
NAPTAN_CSV_URL = "https://naptan.api.dft.gov.uk/v1/access-nodes?dataFormat=csv"
TUBE_STATION_CATEGORY = "Tube station"
+TRAM_METRO_CATEGORY = "Tram & Metro stop"
TUBE_STATION_MERGE_RADIUS_DEGREES = 0.01
+# London Underground ATCO codes are "ZZLU": a 3-digit
+# AdministrativeAreaCode (940 national, 490 London, plus 150/210/040/... for
+# LU stations outside Greater London such as Epping or Amersham), then "0"
+# (platform/entrance node) or "G" (station group node), then the system code.
+# "ZZLU" is unique to London Underground, which cleanly separates genuine Tube
+# stations from every other TMU/MET network (Metrolink, Supertram, T&W Metro,
+# WM Metro, Blackpool Tramway, heritage railways, ...).
+LONDON_UNDERGROUND_ATCO_PATTERN = r"(?i)^\d{3}[0G]ZZLU"
+
STOP_TYPES = {
"AIR": "Airport",
@@ -25,25 +35,110 @@ STOP_TYPES = {
"RLY": "Rail station",
"RSE": "Rail station",
"BCT": "Bus stop",
+ # Bus/coach stations: BST is the station access-area node, BCS/BCQ are
+ # bays/stands within the station and BCE is a station entrance. NaPTAN maps
+ # very few BCE nodes (~80 GB-wide), so without BST/BCS/BCQ the category was
+ # so sparse that 20% of England showed the nearest bus station >100km away.
+ # Bays and entrances collapse to one POI per station via
+ # STATION_MERGE_CATEGORIES below.
+ "BST": "Bus station",
+ "BCS": "Bus station",
+ "BCQ": "Bus station",
"BCE": "Bus station",
"TXR": "Taxi rank",
- "TMU": "Tube station",
- "MET": "Tube station",
+ # Tram/Metro/Underground: TMU is an entrance node, MET the station access
+ # area. Both start as "Tram & Metro stop"; merged stations whose ATCO codes
+ # mark them as London Underground (ZZLU) are reclassified to "Tube station"
+ # after dedup (see _deduplicate_station_areas). Heritage railways (RHDR,
+ # Severn Valley, ...) are TMU/MET in NaPTAN with no machine-readable
+ # "heritage" flag, so they remain in "Tram & Metro stop".
+ "TMU": TRAM_METRO_CATEGORY,
+ "MET": TRAM_METRO_CATEGORY,
}
# Stop types that are access/entrance nodes rather than the primary station or
-# terminal node. During dedup the primary node (e.g. RLY/FER) wins so a station
-# with both a station node and entrances yields one POI at the station node.
-ENTRANCE_STOP_TYPES = {"RSE", "FTD"}
+# terminal node. During dedup the primary node (e.g. RLY/FER/MET) wins so a
+# station with both a station node and entrances yields one POI at the station
+# node.
+ENTRANCE_STOP_TYPES = {"RSE", "FTD", "TMU", "BCE"}
# Categories whose entrances/variants are merged into a single station-level POI
# by normalized name + area (like Tube stations), so an RLY node and its RSE
# entrances collapse to one POI at the station node.
-STATION_MERGE_CATEGORIES = {TUBE_STATION_CATEGORY, "Rail station", "Ferry"}
+STATION_MERGE_CATEGORIES = {
+ TRAM_METRO_CATEGORY,
+ TUBE_STATION_CATEGORY,
+ "Rail station",
+ "Ferry",
+ "Bus station",
+}
OUTPUT_COLUMNS = ["id", "name", "category", "lat", "lng"]
+# Trailing entrance designators ("North East Ent", "Main Entrance No 2",
+# "West Station Entrance", ...) are stripped from canonical names so a
+# station's individually-named entrance nodes collapse into the station.
+# A trailing run of filler words is only stripped when it contains at least
+# one entrance word, so "Maze Hill North" or "Platform 1" are untouched.
+_ENTRANCE_NAME_WORDS = {"ent", "entrance", "entrances", "access"}
+_ENTRANCE_FILLER_WORDS = {
+ "north",
+ "south",
+ "east",
+ "west",
+ "ne",
+ "nw",
+ "se",
+ "sw",
+ "n",
+ "s",
+ "e",
+ "w",
+ "wt",
+ "main",
+ "side",
+ "no",
+ "station",
+ "stop",
+ "platform",
+}
+
+_ENTRANCE_WORDS_RE = "(?:ent|entrance|entrances|access)"
+_ENTRANCE_FILLER_RE = (
+ r"(?:north|south|east|west|ne|nw|se|sw|n|s|e|w|wt|main|side|no|station|stop"
+ r"|platform|\d+)"
+)
+_ENTRANCE_SUFFIX_RE = (
+ rf"(?:\s+(?:{_ENTRANCE_FILLER_RE}|{_ENTRANCE_WORDS_RE}))*"
+ rf"\s+{_ENTRANCE_WORDS_RE}"
+ rf"(?:\s+(?:{_ENTRANCE_FILLER_RE}|{_ENTRANCE_WORDS_RE}))*$"
+)
+
+# Bus-station bay/stand designators ("Stand A3", "Bay 2", "Stance 5") are
+# stripped so every bay of one station shares a canonical name. The designator
+# word must be followed by a short alphanumeric token, so place names ending in
+# a bare "Bay" (Colwyn Bay, Herne Bay) are untouched.
+_BAY_WORDS = {"stand", "stance", "bay", "gate"}
+_BAY_SUFFIX_RE = r"\s+(?:stand|stance|bay|gate)\s+[a-z0-9]{1,3}$"
+
+
+def _strip_entrance_suffix(words: list[str]) -> list[str]:
+ """Drop a trailing entrance designator (direction/number filler around an
+ entrance word) from a tokenized stop name; no-op when no entrance word."""
+ idx = len(words)
+ saw_entrance = False
+ while idx > 0:
+ word = words[idx - 1]
+ if word in _ENTRANCE_NAME_WORDS:
+ saw_entrance = True
+ elif word.isdigit() or word in _ENTRANCE_FILLER_WORDS:
+ pass
+ else:
+ break
+ idx -= 1
+ return words[:idx] if saw_entrance else words
+
def canonical_station_name(name: str | None) -> str:
"""Normalize station names so entrances/transport-mode variants collapse."""
@@ -55,18 +150,24 @@ def canonical_station_name(name: str | None) -> str:
normalized = re.sub(r"['’`]", "", normalized)
normalized = normalized.replace("&", " and ")
normalized = re.sub(r"[^a-z0-9]+", " ", normalized)
- words = normalized.split()
+ words = _strip_entrance_suffix(normalized.split())
+
+ if len(words) >= 3 and words[-2] in _BAY_WORDS and len(words[-1]) <= 3:
+ del words[-2:]
suffixes = (
("underground", "station"),
("tube", "station"),
("dlr", "station"),
("metro", "station"),
+ ("metrolink", "station"),
+ ("metrolink", "stop"),
("tram", "stop"),
("rail", "station"),
("railway", "station"),
("station",),
("stop",),
+ ("metrolink",),
)
while True:
suffix = next(
@@ -88,11 +189,14 @@ def canonical_station_name_expr(name_col: str = "name") -> pl.Expr:
expr = expr.str.replace_all(r"&", " and ")
expr = expr.str.replace_all(r"[^a-z0-9]+", " ")
expr = expr.str.replace_all(r"\s+", " ").str.strip_chars()
+ expr = expr.str.replace_all(_ENTRANCE_SUFFIX_RE, "")
+ expr = expr.str.replace_all(_BAY_SUFFIX_RE, "")
expr = expr.str.replace_all(
- r"\s+(underground|tube|dlr|metro|rail|railway)\s+station$", ""
+ r"\s+(underground|tube|dlr|metro|metrolink|rail|railway)\s+station$", ""
)
- expr = expr.str.replace_all(r"\s+tram\s+stop$", "")
+ expr = expr.str.replace_all(r"\s+(metrolink|tram)\s+stop$", "")
expr = expr.str.replace_all(r"\s+(station|stop)$", "")
+ expr = expr.str.replace_all(r"\s+metrolink$", "")
return expr.str.strip_chars()
@@ -140,6 +244,7 @@ class StationAccumulator:
lat_sum: float
lng_sum: float
entrance: bool = False
+ is_lu: bool = False
count: int = 1
@property
@@ -159,6 +264,7 @@ class StationAccumulator:
self.lat_sum += float(row["lat"])
self.lng_sum += float(row["lng"])
self.count += 1
+ self.is_lu = self.is_lu or bool(row.get("is_lu"))
name = str(row["name"] or "")
entrance = bool(row.get("entrance"))
@@ -169,6 +275,16 @@ class StationAccumulator:
self.name = name
self.entrance = entrance
+ @property
+ def output_category(self) -> str:
+ # A merged tram/metro station is a genuine Tube station when ANY of its
+ # constituent nodes carries a London Underground ATCO code. Checking
+ # the whole group (not just the winning node) matters because LU
+ # entrance nodes often carry non-ZZLU codes (e.g. 4900VICT...).
+ if self.category == TRAM_METRO_CATEGORY and self.is_lu:
+ return TUBE_STATION_CATEGORY
+ return self.category
+
def _station_from_row(row: dict[str, object]) -> StationAccumulator:
return StationAccumulator(
@@ -178,6 +294,7 @@ def _station_from_row(row: dict[str, object]) -> StationAccumulator:
lat_sum=float(row["lat"]),
lng_sum=float(row["lng"]),
entrance=bool(row.get("entrance")),
+ is_lu=bool(row.get("is_lu")),
)
@@ -217,7 +334,7 @@ def _deduplicate_station_areas(df: pl.DataFrame) -> pl.DataFrame:
{
"id": [station.id for station in selected],
"name": [station.name for station in selected],
- "category": [station.category for station in selected],
+ "category": [station.output_category for station in selected],
"lat": [station.lat for station in selected],
"lng": [station.lng for station in selected],
}
@@ -258,10 +375,12 @@ def _deduplicate_local_stops(df: pl.DataFrame) -> pl.DataFrame:
def deduplicate_naptan(df: pl.DataFrame) -> pl.DataFrame:
"""Deduplicate NaPTAN stops, merging station/terminal entrances by area.
- Tube, rail and ferry POIs are merged to one record per station by
- normalized name + area, with the primary station/terminal node (e.g. RLY,
- FER) winning over an entrance node (RSE, FTD). Other stops are deduplicated
- by exact name+category+locality.
+ Tram/metro, rail, ferry and bus-station POIs are merged to one record per
+ station by normalized name + area, with the primary station/terminal node
+ (e.g. RLY, FER, MET, BST) winning over an entrance node (RSE, FTD, TMU,
+ BCE). Merged tram/metro stations with a London Underground ATCO code in
+ the group become "Tube station". Other stops are deduplicated by exact
+ name+category+locality.
"""
station = df.filter(pl.col("category").is_in(list(STATION_MERGE_CATEGORIES)))
other = df.filter(~pl.col("category").is_in(list(STATION_MERGE_CATEGORIES)))
@@ -274,6 +393,29 @@ def deduplicate_naptan(df: pl.DataFrame) -> pl.DataFrame:
).select(OUTPUT_COLUMNS)
+def filter_active_stops(df: pl.DataFrame) -> pl.DataFrame:
+ """Keep only active NaPTAN stops.
+
+ The NaPTAN export's Status column marks stops as active/inactive/pending;
+ without this filter closed stations ("(closed)", "not in use") ship as
+ live POIs. Rows with a null Status are kept (benefit of the doubt); a
+ missing column is tolerated so older extracts still load.
+ """
+ if "Status" not in df.columns:
+ print("WARNING: NaPTAN data has no Status column; keeping all stops")
+ return df
+
+ before = len(df)
+ df = df.filter(
+ pl.col("Status").is_null()
+ | pl.col("Status").str.strip_chars().str.to_lowercase().is_in(["active", "act"])
+ )
+ dropped = before - len(df)
+ if dropped:
+ print(f"Dropped {dropped:,} non-active stops (Status != active)")
+ return df
+
+
def download_naptan(output: Path) -> None:
output.parent.mkdir(parents=True, exist_ok=True)
@@ -291,15 +433,19 @@ def download_naptan(output: Path) -> None:
)
.drop_nulls(subset=["Latitude", "Longitude"])
.filter(pl.col("StopType").is_in(list(STOP_TYPES.keys())))
- .select(
- pl.col("ATCOCode").alias("id"),
- pl.col("CommonName").alias("name"),
- pl.col("StopType").replace(STOP_TYPES).alias("category"),
- pl.col("Latitude").alias("lat"),
- pl.col("Longitude").alias("lng"),
- pl.col("NptgLocalityCode").alias("locality"),
- pl.col("StopType").is_in(list(ENTRANCE_STOP_TYPES)).alias("entrance"),
- )
+ )
+ df = filter_active_stops(df).select(
+ pl.col("ATCOCode").alias("id"),
+ pl.col("CommonName").alias("name"),
+ pl.col("StopType").replace(STOP_TYPES).alias("category"),
+ pl.col("Latitude").alias("lat"),
+ pl.col("Longitude").alias("lng"),
+ pl.col("NptgLocalityCode").alias("locality"),
+ pl.col("StopType").is_in(list(ENTRANCE_STOP_TYPES)).alias("entrance"),
+ pl.col("ATCOCode")
+ .str.contains(LONDON_UNDERGROUND_ATCO_PATTERN)
+ .fill_null(False)
+ .alias("is_lu"),
)
before = len(df)
diff --git a/pipeline/download/os_greenspace.py b/pipeline/download/os_greenspace.py
index 734edf4..59e2768 100644
--- a/pipeline/download/os_greenspace.py
+++ b/pipeline/download/os_greenspace.py
@@ -2,12 +2,15 @@
Downloads the OS Open Greenspace dataset as ESRI Shapefile and extracts
access point locations (park entrances). Each access point is tagged with
-its parent site's function type (e.g. Public Park Or Garden). Sites without
-access points fall back to polygon centroids.
+its parent site's function type (e.g. Public Park Or Garden), the parent
+site id and the site's polygon centroid. Sites without access points fall
+back to polygon centroids.
Using access points rather than polygon centroids gives much more accurate
distance calculations — a property next to Hyde Park won't show 400m just
-because the centroid is in the middle of the park.
+because the centroid is in the middle of the park. The site id / centroid
+columns let downstream consumers (poi_proximity) collapse the frame back to
+one row per SITE for counting, so a park with 30 gates counts as one park.
Source: https://osdatahub.os.uk/downloads/open/OpenGreenspace
License: Open Government Licence v3.0
@@ -65,8 +68,8 @@ def _read_site_functions(shp_path: Path) -> dict[str, str]:
def _read_access_points(
shp_path: Path, site_funcs: dict[str, str]
-) -> tuple[list[float], list[float], list[str]]:
- """Read access points, tagging each with its parent site's function."""
+) -> tuple[list[float], list[float], list[str], list[str]]:
+ """Read access points, tagging each with its parent site's function and id."""
reader = shp.Reader(str(shp_path), encoding="latin-1")
field_names = [f[0] for f in reader.fields[1:]]
@@ -80,6 +83,7 @@ def _read_access_points(
lats: list[float] = []
lngs: list[float] = []
categories: list[str] = []
+ site_ids: list[str] = []
skipped = 0
error_skipped = 0
@@ -107,6 +111,7 @@ def _read_access_points(
lats.append(lat)
lngs.append(lng)
categories.append(func)
+ site_ids.append(str(site_id))
if skipped:
print(f" Skipped {skipped:,} access points with unknown site ID")
@@ -116,31 +121,26 @@ def _read_access_points(
error_skipped,
)
- return lats, lngs, categories
+ return lats, lngs, categories, site_ids
-def _read_site_centroids(
- shp_path: Path, site_funcs: dict[str, str], covered_ids: set[str]
-) -> tuple[list[float], list[float], list[str]]:
- """Read polygon centroids for sites that have no access points (fallback)."""
+def _read_site_centroids(shp_path: Path) -> dict[str, tuple[float, float]]:
+ """Compute the WGS84 polygon centroid of every greenspace site.
+
+ Used both as the representative point for site-level counting and as the
+ location fallback for sites that have no access points.
+ """
reader = shp.Reader(str(shp_path), encoding="latin-1")
field_names = [f[0] for f in reader.fields[1:]]
id_idx = _find_field(field_names, "id")
- func_idx = _find_field(field_names, "funct")
- if id_idx is None or func_idx is None:
- return [], [], []
+ if id_idx is None:
+ return {}
- lats: list[float] = []
- lngs: list[float] = []
- categories: list[str] = []
+ centroids: dict[str, tuple[float, float]] = {}
error_skipped = 0
for sr in reader.shapeRecords():
site_id = sr.record[id_idx]
- if site_id in covered_ids:
- continue
-
- func = sr.record[func_idx]
try:
geom = to_shapely(sr.shape.__geo_interface__)
if geom.is_empty or not geom.is_valid:
@@ -156,9 +156,7 @@ def _read_site_centroids(
)
continue
- lats.append(lat)
- lngs.append(lng)
- categories.append(func)
+ centroids[str(site_id)] = (lat, lng)
if error_skipped:
logger.warning(
@@ -166,7 +164,7 @@ def _read_site_centroids(
error_skipped,
)
- return lats, lngs, categories
+ return centroids
def download_greenspace(output: Path) -> None:
@@ -194,33 +192,53 @@ def download_greenspace(output: Path) -> None:
# Step 2: Read access points (primary — park entrances)
print(f"Reading {access_shps[0].name}...")
- ap_lats, ap_lngs, ap_cats = _read_access_points(access_shps[0], site_funcs)
+ ap_lats, ap_lngs, ap_cats, ap_site_ids = _read_access_points(
+ access_shps[0], site_funcs
+ )
print(f" {len(ap_lats):,} access points loaded")
- # Step 3: Fall back to centroids for sites without any access points
- covered_ids = set()
- reader = shp.Reader(str(access_shps[0]), encoding="latin-1")
- field_names = [f[0] for f in reader.fields[1:]]
- ref_idx = _find_field(field_names, "refto", "ref_to", "greensp")
- if ref_idx is not None:
- for rec in reader.iterRecords():
- covered_ids.add(rec[ref_idx])
+ # Step 3: Compute every site's centroid: the representative point for
+ # site-level counting, and the location fallback for sites without any
+ # access points.
+ print("Computing site centroids...")
+ centroids = _read_site_centroids(site_shps[0])
+ print(f" {len(centroids):,} site centroids computed")
- print("Adding centroids for sites without access points...")
- fb_lats, fb_lngs, fb_cats = _read_site_centroids(
- site_shps[0], site_funcs, covered_ids
- )
+ covered_ids = set(ap_site_ids)
+ fb_lats: list[float] = []
+ fb_lngs: list[float] = []
+ fb_cats: list[str] = []
+ fb_site_ids: list[str] = []
+ for site_id, (lat, lng) in centroids.items():
+ if site_id in covered_ids:
+ continue
+ func = site_funcs.get(site_id)
+ if func is None:
+ continue
+ fb_lats.append(lat)
+ fb_lngs.append(lng)
+ fb_cats.append(func)
+ fb_site_ids.append(site_id)
print(f" {len(fb_lats):,} centroid fallbacks added")
lats = ap_lats + fb_lats
lngs = ap_lngs + fb_lngs
categories = ap_cats + fb_cats
+ site_ids = ap_site_ids + fb_site_ids
+ site_lats = [centroids.get(site_id, (None, None))[0] for site_id in site_ids]
+ site_lngs = [centroids.get(site_id, (None, None))[1] for site_id in site_ids]
df = pl.DataFrame(
{
"lat": np.array(lats, dtype=np.float64),
"lng": np.array(lngs, dtype=np.float64),
"category": categories,
+ "site_id": site_ids,
+ # Site polygon centroid (null when the centroid could not be
+ # computed): the representative point when collapsing to one row
+ # per site for counting.
+ "site_lat": pl.Series(site_lats, dtype=pl.Float64),
+ "site_lng": pl.Series(site_lngs, dtype=pl.Float64),
}
)
diff --git a/pipeline/download/places.py b/pipeline/download/places.py
index 6b5d875..07ec1e2 100644
--- a/pipeline/download/places.py
+++ b/pipeline/download/places.py
@@ -641,7 +641,7 @@ def _naptan_dlr_stations(naptan_path: Path) -> list[dict]:
match = _DLR_CODE_RE.search(atco_id)
if not match:
continue
- if row["category"] not in {"Tube station", "Rail station"}:
+ if row["category"] not in {"Tube station", "Tram & Metro stop", "Rail station"}:
continue
code = match.group(1)
diff --git a/pipeline/download/test_ethnicity.py b/pipeline/download/test_ethnicity.py
index d83b8b4..63f913f 100644
--- a/pipeline/download/test_ethnicity.py
+++ b/pipeline/download/test_ethnicity.py
@@ -1,65 +1,119 @@
import polars as pl
+import pytest
-from pipeline.download.ethnicity import _ethnicity_percentages
+from pipeline.download.ethnicity import GROUP_MAP, OUTPUT_GROUPS, _ethnicity_percentages
-def test_ethnicity_percentages_recombines_predecessor_lads_by_population():
- rows = []
- for code, white, indian in [
- ("E07000026", 80, 20),
- ("E07000028", 10, 90),
- ]:
- total = white + indian
- rows.extend(
- [
- {
- "Geography_code": code,
- "Ethnicity_type": "ONS 2021 19+1",
- "Ethnicity": "White British",
- "Ethnic Population": white,
- "Value1": white / total * 100,
- },
- {
- "Geography_code": code,
- "Ethnicity_type": "ONS 2021 19+1",
- "Ethnicity": "Indian",
- "Ethnic Population": indian,
- "Value1": indian / total * 100,
- },
- ]
- )
+def _long_rows(geo: str, counts: dict[str, int]) -> list[dict]:
+ """Build NOMIS-shaped long rows for one LSOA from {leaf_label: count}.
- result = _ethnicity_percentages(pl.DataFrame(rows))
-
- cumberland = result.filter(pl.col("Geography_code") == "E06000063")
- assert cumberland.select("% White", "% South Asian").to_dicts() == [
- {"% White": 45.0, "% South Asian": 55.0}
- ]
-
-
-def test_ethnicity_routes_any_other_asian_to_east_se_asian():
- """'Any Other Asian Background' and 'Chinese' both fold into '% East/SE Asian'
- (not '% South Asian'), fixing the East/SE Asian undercount."""
- rows = [
+ Every one of the 19 leaf categories must be present in the download (NOMIS
+ emits a 0-count row when an LSOA has none), so categories not given default
+ to 0 to mirror that.
+ """
+ return [
{
- "Geography_code": "E06000001",
- "Ethnicity_type": "ONS 2021 19+1",
- "Ethnicity": ethnicity,
- "Ethnic Population": pop,
- "Value1": 0.0,
+ "GEOGRAPHY_CODE": geo,
+ "C2021_ETH_20_NAME": label,
+ "OBS_VALUE": counts.get(label, 0),
}
- for ethnicity, pop in [
- ("Chinese", 30),
- ("Any Other Asian Background", 20),
- ("Indian", 50),
+ for label in GROUP_MAP
+ ]
+
+
+def test_ethnicity_percentages_keyed_by_lsoa_with_seven_buckets():
+ df = pl.DataFrame(
+ _long_rows(
+ "E01000001",
+ {
+ "White: English, Welsh, Scottish, Northern Irish or British": 60,
+ "White: Other White": 10,
+ "Asian, Asian British or Asian Welsh: Indian": 20,
+ "Black, Black British, Black Welsh, Caribbean or African: African": 10,
+ },
+ )
+ )
+
+ result = _ethnicity_percentages(df)
+
+ assert result.columns[0] == "lsoa21"
+ assert set(result.columns) == {"lsoa21", *(f"% {g}" for g in OUTPUT_GROUPS)}
+ row = result.filter(pl.col("lsoa21") == "E01000001").to_dicts()[0]
+ assert row["% White"] == 70.0
+ assert row["% South Asian"] == 20.0
+ assert row["% Black"] == 10.0
+ # Percentages always sum to exactly 100 (largest-remainder rounding).
+ assert round(sum(row[f"% {g}"] for g in OUTPUT_GROUPS), 1) == 100.0
+
+
+def test_ethnicity_routes_chinese_to_east_and_other_asian_to_se():
+ """'Chinese' folds into '% East Asian' and 'Other Asian' into '% SE Asian'
+ (neither into '% South Asian'), keeping the two Asian buckets distinct."""
+ df = pl.DataFrame(
+ _long_rows(
+ "E01000002",
+ {
+ "Asian, Asian British or Asian Welsh: Chinese": 30,
+ "Asian, Asian British or Asian Welsh: Other Asian": 20,
+ "Asian, Asian British or Asian Welsh: Indian": 50,
+ },
+ )
+ )
+
+ result = _ethnicity_percentages(df)
+ area = result.filter(pl.col("lsoa21") == "E01000002")
+
+ assert "% East Asian" in result.columns
+ assert "% SE Asian" in result.columns
+ assert "% East/SE Asian" not in result.columns
+ assert area.select("% East Asian", "% SE Asian", "% South Asian").to_dicts() == [
+ {"% East Asian": 30.0, "% SE Asian": 20.0, "% South Asian": 50.0}
+ ]
+
+
+def test_ethnicity_percentages_independent_per_lsoa():
+ """Two LSOAs get independent profiles — the LSOA granularity is the point."""
+ df = pl.concat(
+ [
+ pl.DataFrame(
+ _long_rows(
+ "E01000010",
+ {"White: Other White": 100},
+ )
+ ),
+ pl.DataFrame(
+ _long_rows(
+ "E01000011",
+ {"Asian, Asian British or Asian Welsh: Pakistani": 100},
+ )
+ ),
]
- ]
+ )
- result = _ethnicity_percentages(pl.DataFrame(rows))
- area = result.filter(pl.col("Geography_code") == "E06000001")
+ result = _ethnicity_percentages(df).sort("lsoa21")
- assert "% East/SE Asian" in result.columns
- assert "% East Asian" not in result.columns
- assert area.select("% East/SE Asian", "% South Asian").to_dicts() == [
- {"% East/SE Asian": 50.0, "% South Asian": 50.0}
- ]
+ assert result["% White"].to_list() == [100.0, 0.0]
+ assert result["% South Asian"].to_list() == [0.0, 100.0]
+
+
+def test_ethnicity_percentages_rejects_unexpected_category():
+ rows = _long_rows("E01000003", {"White: Other White": 10})
+ rows.append(
+ {
+ "GEOGRAPHY_CODE": "E01000003",
+ "C2021_ETH_20_NAME": "White: A Brand New Census Category",
+ "OBS_VALUE": 5,
+ }
+ )
+
+ with pytest.raises(ValueError, match="do not match the expected"):
+ _ethnicity_percentages(pl.DataFrame(rows))
+
+
+def test_ethnicity_percentages_rejects_missing_category():
+ # Drop one leaf entirely: its people would vanish from the denominator.
+ rows = [r for r in _long_rows("E01000004", {"White: Other White": 10}) if
+ r["C2021_ETH_20_NAME"] != "Other ethnic group: Arab"]
+
+ with pytest.raises(ValueError, match="missing"):
+ _ethnicity_percentages(pl.DataFrame(rows))
diff --git a/pipeline/download/test_naptan.py b/pipeline/download/test_naptan.py
index 172a561..2671404 100644
--- a/pipeline/download/test_naptan.py
+++ b/pipeline/download/test_naptan.py
@@ -2,9 +2,12 @@ import polars as pl
import pytest
from pipeline.download.naptan import (
+ TRAM_METRO_CATEGORY,
+ TUBE_STATION_CATEGORY,
canonical_station_name,
canonical_station_name_expr,
deduplicate_naptan,
+ filter_active_stops,
)
@@ -34,6 +37,127 @@ def test_canonical_station_name_expr_normalizes_transport_suffixes():
assert [canonical_station_name(name) for name in names] == result
+def test_canonical_station_name_strips_entrance_suffixes():
+ # Real shipped NaPTAN entrance names that previously failed to merge with
+ # their station node (79 stray entrance POIs).
+ cases = {
+ "Weaste Metrolink Station North East Entrance": "weaste",
+ "Weaste Metrolink Station North Entrance No 2": "weaste",
+ "Whitefield Metrolink Station Main Entrance": "whitefield",
+ "Radcliffe Metrolink Station Entrance": "radcliffe",
+ "Stretford Metrolink Station Wt Platform Entrance": "stretford",
+ "Salford Quays Metrolink Station SW entrance": "salford quays",
+ "Bank Station Ent 2": "bank",
+ "Hainault": "hainault",
+ # The Metrolink MET node names collapse to the same key.
+ "Weaste (Manchester Metrolink)": "weaste",
+ # No entrance word: direction/filler words must NOT be stripped.
+ "Maze Hill North": "maze hill north",
+ "Bus Station Entrance": "bus",
+ # Bus-station bay/stand designators collapse to the station name…
+ "Tonypandy Bus Station Stand A3": "tonypandy bus",
+ "Caerphilly Interchange Stand 5": "caerphilly interchange",
+ "Stanley Bus Station Stand G": "stanley bus",
+ # …but a bare trailing "Bay" (place names) is untouched.
+ "Colwyn Bay": "colwyn bay",
+ }
+ for name, expected in cases.items():
+ assert canonical_station_name(name) == expected, name
+
+ df = pl.DataFrame({"name": list(cases.keys())})
+ expr_result = df.select(canonical_station_name_expr().alias("key"))["key"].to_list()
+ assert expr_result == list(cases.values())
+
+
+def test_filter_active_stops_drops_non_active():
+ df = pl.DataFrame(
+ {
+ "ATCOCode": ["a", "b", "c", "d"],
+ "Status": ["active", "inactive", None, "Pending"],
+ }
+ )
+
+ result = filter_active_stops(df)
+
+ # Active and unknown (null) statuses survive; inactive/pending are dropped.
+ assert result["ATCOCode"].to_list() == ["a", "c"]
+
+
+def test_filter_active_stops_tolerates_missing_status_column():
+ df = pl.DataFrame({"ATCOCode": ["a"]})
+
+ assert filter_active_stops(df)["ATCOCode"].to_list() == ["a"]
+
+
+def test_deduplicate_naptan_splits_london_underground_from_tram_metro():
+ # MET station nodes plus TMU entrances, pre-categorised as the tram/metro
+ # family. The Hainault group contains a 940GZZLU station node, so the
+ # merged POI is a genuine "Tube station" even though its entrance carries a
+ # non-ZZLU ATCO code; the Metrolink group stays "Tram & Metro stop".
+ df = pl.DataFrame(
+ {
+ "id": [
+ "940GZZLUHLT",
+ "490000095003",
+ "9400ZZMAWST",
+ "1800NFR2691",
+ ],
+ "name": [
+ "Hainault Underground Station",
+ "Hainault",
+ "Weaste (Manchester Metrolink)",
+ "Weaste Metrolink Station North West Entrance",
+ ],
+ "category": [TRAM_METRO_CATEGORY] * 4,
+ "lat": [51.6034, 51.6037, 53.4826, 53.4826],
+ "lng": [0.0933, 0.0931, -2.3087, -2.3086],
+ "locality": [None, None, None, None],
+ "entrance": [False, True, False, True],
+ "is_lu": [True, False, False, False],
+ }
+ )
+
+ result = deduplicate_naptan(df).sort("category")
+
+ assert len(result) == 2
+ assert result["category"].to_list() == [
+ TRAM_METRO_CATEGORY,
+ TUBE_STATION_CATEGORY,
+ ]
+ tube = result.filter(pl.col("category") == TUBE_STATION_CATEGORY)
+ # The station node (not the entrance) represents the merged POI.
+ assert tube["id"][0] == "940GZZLUHLT"
+ tram = result.filter(pl.col("category") == TRAM_METRO_CATEGORY)
+ assert tram["id"][0] == "9400ZZMAWST"
+
+
+def test_deduplicate_naptan_merges_bus_station_bays_and_entrances():
+ # BCS bays and a BCE entrance of one bus station collapse to a single POI
+ # represented by a non-entrance node; a different bus station in another
+ # area survives separately.
+ df = pl.DataFrame(
+ {
+ "id": ["bay-1", "bay-2", "ent-1", "other"],
+ "name": [
+ "Bury Interchange",
+ "Bury Interchange",
+ "Bury Interchange East Entrance",
+ "Rochdale Interchange",
+ ],
+ "category": ["Bus station"] * 4,
+ "lat": [53.5907, 53.5908, 53.5909, 53.6160],
+ "lng": [-2.2958, -2.2957, -2.2956, -2.1561],
+ "locality": ["BURY", "BURY", "BURY", "ROCHDALE"],
+ "entrance": [False, False, True, False],
+ }
+ )
+
+ result = deduplicate_naptan(df).sort("name")
+
+ assert result["name"].to_list() == ["Bury Interchange", "Rochdale Interchange"]
+ assert result.filter(pl.col("name") == "Bury Interchange")["id"][0] == "bay-1"
+
+
def test_deduplicate_naptan_merges_tube_station_variants_by_area():
df = pl.DataFrame(
{
diff --git a/pipeline/download/test_places.py b/pipeline/download/test_places.py
index 48c9bae..b3b2dc4 100644
--- a/pipeline/download/test_places.py
+++ b/pipeline/download/test_places.py
@@ -86,7 +86,7 @@ def test_naptan_dlr_stations_are_deduplicated_by_atco_code(tmp_path):
"Bank",
],
"category": [
- "Tube station",
+ "Tram & Metro stop",
"Tube station",
"Rail station",
"Bus stop",
diff --git a/pipeline/download/test_transit_network.py b/pipeline/download/test_transit_network.py
index c50e654..47b7be9 100644
--- a/pipeline/download/test_transit_network.py
+++ b/pipeline/download/test_transit_network.py
@@ -1,11 +1,15 @@
"""Tests for transit_network GTFS processing."""
+import datetime as dt
import zipfile
from pathlib import Path
import pytest
-from pipeline.download.transit_network import convert_high_freq_to_frequency_based
+from pipeline.download.transit_network import (
+ convert_high_freq_to_frequency_based,
+ validate_gtfs_feed,
+)
def _write_gtfs(path: Path, *, stop_times: str) -> None:
@@ -77,3 +81,162 @@ def test_raises_when_no_first_stops_found(tmp_path: Path) -> None:
with pytest.raises(RuntimeError, match="no first stops"):
convert_high_freq_to_frequency_based(src, dst)
+
+
+# ── validate_gtfs_feed ────────────────────────────────────────────────────────
+
+TODAY = dt.date(2026, 6, 10)
+
+
+def _make_gtfs(
+ path: Path,
+ *,
+ calendar: str | None = (
+ "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
+ "start_date,end_date\n"
+ "S1,1,1,1,1,1,0,0,20260101,20271231\n"
+ ),
+ calendar_dates: str | None = None,
+ stops: str = (
+ "stop_id,stop_name,stop_lat,stop_lon\n"
+ "STOP_A,Bank,51.5133,-0.0886\n"
+ "STOP_B,Liverpool Street,51.5178,-0.0823\n"
+ ),
+ routes: str = "route_id,agency_id,route_short_name,route_type\nR1,OP1,Central,1\n",
+ trips: str = "trip_id,route_id,service_id\nT1,R1,S1\n",
+ stop_times: str = (
+ "trip_id,stop_sequence,departure_time,stop_id\n"
+ "T1,0,06:00:00,STOP_A\n"
+ "T1,1,06:02:00,STOP_B\n"
+ ),
+) -> Path:
+ """Write a tiny synthetic GTFS zip; defaults form a valid current feed."""
+ with zipfile.ZipFile(path, "w") as z:
+ if calendar is not None:
+ z.writestr("calendar.txt", calendar)
+ if calendar_dates is not None:
+ z.writestr("calendar_dates.txt", calendar_dates)
+ z.writestr("stops.txt", stops)
+ z.writestr("routes.txt", routes)
+ z.writestr("trips.txt", trips)
+ z.writestr("stop_times.txt", stop_times)
+ return path
+
+
+def test_validate_gtfs_feed_happy_path(tmp_path: Path) -> None:
+ feed = _make_gtfs(tmp_path / "feed.zip")
+ validate_gtfs_feed(feed, "test feed", today=TODAY) # must not raise
+
+
+def test_validate_gtfs_feed_expired_calendar(tmp_path: Path) -> None:
+ """The 2010 TfL snapshot failure mode: all calendars ended years ago."""
+ feed = _make_gtfs(
+ tmp_path / "feed.zip",
+ calendar=(
+ "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
+ "start_date,end_date\n"
+ "S1,1,1,1,1,1,0,0,20091201,20101224\n"
+ ),
+ )
+ with pytest.raises(RuntimeError, match=r"'stale tfl'.*no service active"):
+ validate_gtfs_feed(feed, "stale tfl", today=TODAY)
+
+
+def test_validate_gtfs_feed_calendar_starting_after_window_fails(
+ tmp_path: Path,
+) -> None:
+ feed = _make_gtfs(
+ tmp_path / "feed.zip",
+ calendar=(
+ "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
+ "start_date,end_date\n"
+ "S1,1,1,1,1,1,0,0,20270101,20271231\n"
+ ),
+ )
+ with pytest.raises(RuntimeError, match="no service active"):
+ validate_gtfs_feed(feed, "future feed", today=TODAY)
+
+
+def test_validate_gtfs_feed_calendar_dates_rescues_expired_calendar(
+ tmp_path: Path,
+) -> None:
+ """An expired calendar.txt passes if calendar_dates.txt adds service now."""
+ feed = _make_gtfs(
+ tmp_path / "feed.zip",
+ calendar=(
+ "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
+ "start_date,end_date\n"
+ "S1,1,1,1,1,1,0,0,20091201,20101224\n"
+ ),
+ calendar_dates="service_id,date,exception_type\nS1,20260615,1\n",
+ )
+ validate_gtfs_feed(feed, "rescued feed", today=TODAY) # must not raise
+
+
+def test_validate_gtfs_feed_removed_service_exception_does_not_count(
+ tmp_path: Path,
+) -> None:
+ feed = _make_gtfs(
+ tmp_path / "feed.zip",
+ calendar=None,
+ calendar_dates="service_id,date,exception_type\nS1,20260615,2\n",
+ )
+ with pytest.raises(RuntimeError, match="no service active"):
+ validate_gtfs_feed(feed, "removed-only feed", today=TODAY)
+
+
+def test_validate_gtfs_feed_zero_and_empty_coords(tmp_path: Path) -> None:
+ """The 2010 TfL snapshot's other failure mode: empty or 0,0 stop coords."""
+ feed = _make_gtfs(
+ tmp_path / "feed.zip",
+ stops=(
+ "stop_id,stop_name,stop_lat,stop_lon\n"
+ "STOP_A,Nowhere,0,0\n"
+ "STOP_B,Blank,,\n"
+ ),
+ )
+ with pytest.raises(RuntimeError, match=r"plausible UK coordinates"):
+ validate_gtfs_feed(feed, "coordless feed", today=TODAY)
+
+
+def test_validate_gtfs_feed_non_uk_coords_fail(tmp_path: Path) -> None:
+ feed = _make_gtfs(
+ tmp_path / "feed.zip",
+ stops=(
+ "stop_id,stop_name,stop_lat,stop_lon\n"
+ "STOP_A,New York,40.71,-74.0\n"
+ "STOP_B,Sydney,-33.87,151.21\n"
+ ),
+ )
+ with pytest.raises(RuntimeError, match="plausible UK coordinates"):
+ validate_gtfs_feed(feed, "abroad feed", today=TODAY)
+
+
+def test_validate_gtfs_feed_minority_bad_coords_pass(tmp_path: Path) -> None:
+ """One bad stop out of 30 (3.3%) stays under the 5% tolerance."""
+ rows = [f"STOP_{i},Stop {i},51.5,{-0.1 + i * 0.001}\n" for i in range(29)]
+ rows.append("STOP_BAD,Broken,0,0\n")
+ feed = _make_gtfs(
+ tmp_path / "feed.zip",
+ stops="stop_id,stop_name,stop_lat,stop_lon\n" + "".join(rows),
+ )
+ validate_gtfs_feed(feed, "mostly good feed", today=TODAY) # must not raise
+
+
+def test_validate_gtfs_feed_empty_trips(tmp_path: Path) -> None:
+ feed = _make_gtfs(tmp_path / "feed.zip", trips="trip_id,route_id,service_id\n")
+ with pytest.raises(RuntimeError, match="trips.txt has no data rows"):
+ validate_gtfs_feed(feed, "tripless feed", today=TODAY)
+
+
+def test_validate_gtfs_feed_missing_calendar_files(tmp_path: Path) -> None:
+ feed = _make_gtfs(tmp_path / "feed.zip", calendar=None)
+ with pytest.raises(RuntimeError, match="neither calendar.txt nor calendar_dates"):
+ validate_gtfs_feed(feed, "calendarless feed", today=TODAY)
+
+
+def test_validate_gtfs_feed_not_a_zip(tmp_path: Path) -> None:
+ bogus = tmp_path / "feed.zip"
+ bogus.write_text("not a zip")
+ with pytest.raises(RuntimeError, match="not a valid zip"):
+ validate_gtfs_feed(bogus, "bogus feed", today=TODAY)
diff --git a/pipeline/download/transit_network.py b/pipeline/download/transit_network.py
index bbb576f..8d56788 100644
--- a/pipeline/download/transit_network.py
+++ b/pipeline/download/transit_network.py
@@ -2,24 +2,32 @@
Downloads:
- England OSM PBF from Geofabrik (~1.5GB)
- - BODS GTFS from Bus Open Data Service (~1.5GB, all England bus/tram/ferry)
- - TfL TransXChange timetables → converted to GTFS
- - National Rail CIF timetable → converted to GTFS (requires credentials)
+ - BODS GTFS from Bus Open Data Service (~1.5GB; all England bus/tram/ferry,
+ plus London Underground, DLR, London Tramlink and the IFS Cloud Cable Car)
+ - National Rail CIF timetable → converted to GTFS (requires credentials;
+ includes the Elizabeth line, TOC "XR")
Then processes for R5 compatibility:
- Cleans BODS GTFS (fixes stop_times >72h, feed_info year >2100)
- Converts high-frequency metro/tram services to frequency-based GTFS
- - Converts TfL TransXChange to GTFS via transxchange2gtfs
- Converts National Rail CIF to GTFS via dtd2mysql (requires MariaDB Docker)
+ - Validates every produced GTFS zip (active calendar window, plausible UK
+ stop coordinates, non-empty routes/trips/stop_times)
-Requires: osmium-tool, Node.js (npx), Docker (for national rail)
+Note: the legacy TfL TransXChange feed (tfl.gov.uk journey-planner-timetables)
+was removed: that URL serves a 2010-10-28 snapshot whose calendars all expired
+in 2010 and whose stops have empty/0,0 coordinates, so it contributed zero
+service. BODS covers all TfL modes that feed nominally provided.
+
+Requires: osmium-tool, Docker (for national rail)
Output directory: property-data/transit/
- raw/england.osm.pbf + bods_gtfs.zip + tfl_gtfs.zip + national_rail_gtfs.zip
+ raw/england.osm.pbf + bods_gtfs.zip + national_rail_gtfs.zip
"""
import argparse
import csv
+import datetime as dt
import io
import json
import os
@@ -45,20 +53,18 @@ ENGLAND_PBF_URL = (
# Bus Open Data Service — pre-converted GTFS covering all England bus/tram/ferry
BODS_GTFS_URL = "https://data.bus-data.dft.gov.uk/timetable/download/gtfs-file/all/"
-# TfL TransXChange timetables (tube, DLR, tram, buses, river bus, cable car)
-TFL_TRANSXCHANGE_URL = (
- "https://tfl.gov.uk/cdn/static/cms/documents/journey-planner-timetables.zip"
-)
-
-# NaPTAN stops data — needed by transxchange2gtfs (its built-in URL is broken)
-NAPTAN_URL = "https://naptan.api.dft.gov.uk/v1/access-nodes?dataFormat=csv"
-
# National Rail Open Data API
NR_AUTH_URL = "https://opendata.nationalrail.co.uk/authenticate"
NR_TIMETABLE_URL = "https://opendata.nationalrail.co.uk/api/staticfeeds/3.0/timetable"
USER_AGENT = "property-map-pipeline/1.0 (https://github.com)"
-TRANSXCHANGE2GTFS_PACKAGE = "transxchange2gtfs@1.12.0"
+
+# GTFS validation: a feed must have service within this many days of the build
+# date, and at least this fraction of stops must have plausible UK coordinates.
+GTFS_CALENDAR_LOOKAHEAD_DAYS = 60
+GTFS_MIN_VALID_STOP_FRACTION = 0.95
+UK_LAT_RANGE = (49.0, 61.0)
+UK_LON_RANGE = (-9.0, 2.5)
def _download_http(
@@ -468,89 +474,175 @@ def convert_high_freq_to_frequency_based(
print(f" Saved to {dst}")
-def download_tfl_transxchange(raw_dir: Path) -> Path:
- """Download TfL TransXChange timetable bundle."""
- dest = raw_dir / "tfl_transxchange.zip"
- if dest.exists():
- print(f"TfL TransXChange already exists: {dest}")
- return dest
-
- print("Downloading TfL TransXChange timetables...")
- _download_http(TFL_TRANSXCHANGE_URL, dest, desc="tfl_transxchange.zip")
- return dest
+def _gtfs_has_data_row(z: zipfile.ZipFile, filename: str) -> bool:
+ """True if a GTFS file has at least one non-empty data row after the header."""
+ with z.open(filename) as f:
+ f.readline() # header
+ for line in f:
+ if _parse_csv_line(line):
+ return True
+ return False
-def download_naptan() -> None:
- """Download NaPTAN stops to the local temp dir for transxchange2gtfs."""
- dest = local_tmp_dir() / "Stops.csv"
- if dest.exists():
- print(f"NaPTAN Stops.csv already exists: {dest}")
- return
+def _calendar_active_in_window(
+ z: zipfile.ZipFile, names: set[str], window_start: int, window_end: int
+) -> bool:
+ """True if calendar.txt/calendar_dates.txt have service in [start, end].
- print("Downloading NaPTAN stops data...")
- _download_http(NAPTAN_URL, dest, desc="Stops.csv")
-
-
-def convert_tfl_to_gtfs(raw_dir: Path, output_dir: Path) -> Path:
- """Convert TfL TransXChange to GTFS using transxchange2gtfs."""
- dest = output_dir / "tfl_gtfs.zip"
- if dest.exists():
- print(f"TfL GTFS already exists: {dest}")
- return dest
-
- txc_path = raw_dir / "tfl_transxchange.zip"
-
- # Ensure NaPTAN is available (transxchange2gtfs has a broken download URL)
- download_naptan()
-
- print("Converting TfL TransXChange → GTFS...")
- # The shim patches known packaging/runtime issues in the pinned npm package
- # before loading its CLI from npx's temporary install.
- shim_path = Path(__file__).with_name("transxchange2gtfs_shim.js")
- subprocess.run(
- [
- "npx",
- "--yes",
- "--package",
- TRANSXCHANGE2GTFS_PACKAGE,
- "sh",
- "-c",
- "\n".join(
- [
- 'bin="$(command -v transxchange2gtfs)"',
- 'script="$(readlink -f "$bin")"',
- 'pkg_dir="$(dirname "$(dirname "$script")")"',
- 'shim="$1"',
- "shift",
- 'exec node "$shim" "$pkg_dir" "$@"',
- ]
- ),
- "transxchange2gtfs",
- str(shim_path.resolve()),
- str(txc_path.resolve()),
- str(dest.resolve()),
- ],
- check=True,
+ Dates are compared as YYYYMMDD integers. A calendar.txt row counts when its
+ date range overlaps the window AND at least one weekday flag is set; a
+ calendar_dates.txt row counts when it adds service (exception_type=1) on a
+ date inside the window.
+ """
+ weekdays = (
+ "monday",
+ "tuesday",
+ "wednesday",
+ "thursday",
+ "friday",
+ "saturday",
+ "sunday",
+ )
+ if "calendar.txt" in names:
+ with z.open("calendar.txt") as f:
+ cols = _parse_csv_line(f.readline())
+ try:
+ start_idx = cols.index("start_date")
+ end_idx = cols.index("end_date")
+ except ValueError:
+ return False
+ day_idxs = [cols.index(d) for d in weekdays if d in cols]
+ for line in f:
+ parts = _parse_csv_line(line)
+ if not parts:
+ continue
+ try:
+ start = int(parts[start_idx].strip('"'))
+ end = int(parts[end_idx].strip('"'))
+ except (ValueError, IndexError):
+ continue
+ if start > window_end or end < window_start:
+ continue
+ if day_idxs and not any(
+ parts[i].strip('"') == "1" for i in day_idxs if i < len(parts)
+ ):
+ continue
+ return True
+
+ if "calendar_dates.txt" in names:
+ with z.open("calendar_dates.txt") as f:
+ cols = _parse_csv_line(f.readline())
+ try:
+ date_idx = cols.index("date")
+ exc_idx = cols.index("exception_type")
+ except ValueError:
+ return False
+ for line in f:
+ parts = _parse_csv_line(line)
+ if not parts:
+ continue
+ try:
+ date = int(parts[date_idx].strip('"'))
+ except (ValueError, IndexError):
+ continue
+ if exc_idx < len(parts) and parts[exc_idx].strip('"') != "1":
+ continue
+ if window_start <= date <= window_end:
+ return True
+
+ return False
+
+
+def validate_gtfs_feed(path: Path, feed_name: str, *, today: dt.date | None = None) -> None:
+ """Sanity-check a produced/downloaded GTFS zip; raise RuntimeError if dead.
+
+ Guards against silently shipping a feed that contributes zero service (as
+ the old TfL dump did: 2010 calendars, empty/0,0 stop coordinates). Checks:
+ (a) calendar.txt/calendar_dates.txt have at least one service active
+ within [today, today + GTFS_CALENDAR_LOOKAHEAD_DAYS];
+ (b) stops.txt is non-empty and >= GTFS_MIN_VALID_STOP_FRACTION of stops
+ have plausible UK coordinates (lat 49-61, lon -9..2.5, not 0,0);
+ (c) routes.txt, trips.txt and stop_times.txt each have data rows.
+ """
+ if today is None:
+ today = dt.date.today()
+ window_start = int(today.strftime("%Y%m%d"))
+ window_end = int(
+ (today + dt.timedelta(days=GTFS_CALENDAR_LOOKAHEAD_DAYS)).strftime("%Y%m%d")
+ )
+
+ def fail(reason: str) -> None:
+ raise RuntimeError(
+ f"GTFS validation failed for feed '{feed_name}' ({path}): {reason}"
+ )
+
+ print(f"Validating GTFS feed '{feed_name}'...")
+ if not path.exists() or not zipfile.is_zipfile(path):
+ fail("not a valid zip file")
+
+ with zipfile.ZipFile(path) as z:
+ names = set(z.namelist())
+
+ # (c) core files present and non-empty
+ for required in ("routes.txt", "trips.txt", "stop_times.txt", "stops.txt"):
+ if required not in names:
+ fail(f"missing {required}")
+ if not _gtfs_has_data_row(z, required):
+ fail(f"{required} has no data rows")
+
+ # (a) at least one service active in the routing window
+ if "calendar.txt" not in names and "calendar_dates.txt" not in names:
+ fail("has neither calendar.txt nor calendar_dates.txt")
+ if not _calendar_active_in_window(z, names, window_start, window_end):
+ fail(
+ f"no service active between {window_start} and {window_end} — "
+ "the feed's calendars are stale/expired and it would contribute "
+ "zero service to routing"
+ )
+
+ # (b) stops have plausible UK coordinates
+ total_stops = 0
+ valid_stops = 0
+ with z.open("stops.txt") as f:
+ cols = _parse_csv_line(f.readline())
+ try:
+ lat_idx = cols.index("stop_lat")
+ lon_idx = cols.index("stop_lon")
+ except ValueError:
+ fail("stops.txt is missing stop_lat/stop_lon columns")
+ for line in f:
+ parts = _parse_csv_line(line)
+ if not parts:
+ continue
+ total_stops += 1
+ try:
+ lat = float(parts[lat_idx].strip('"'))
+ lon = float(parts[lon_idx].strip('"'))
+ except (ValueError, IndexError):
+ continue # empty/garbage coordinate → invalid
+ if lat == 0.0 and lon == 0.0:
+ continue
+ if (
+ UK_LAT_RANGE[0] <= lat <= UK_LAT_RANGE[1]
+ and UK_LON_RANGE[0] <= lon <= UK_LON_RANGE[1]
+ ):
+ valid_stops += 1
+ if total_stops == 0:
+ fail("stops.txt has no stops")
+ fraction = valid_stops / total_stops
+ if fraction < GTFS_MIN_VALID_STOP_FRACTION:
+ fail(
+ f"only {valid_stops}/{total_stops} stops "
+ f"({fraction:.1%}) have plausible UK coordinates "
+ f"(lat {UK_LAT_RANGE[0]}-{UK_LAT_RANGE[1]}, "
+ f"lon {UK_LON_RANGE[0]}..{UK_LON_RANGE[1]}, non-null, not 0,0); "
+ f"need >= {GTFS_MIN_VALID_STOP_FRACTION:.0%}"
+ )
+
+ print(
+ f" OK: service active in window, {valid_stops}/{total_stops} stops "
+ f"({fraction:.1%}) with plausible UK coordinates"
)
- required_files = {
- "agency.txt",
- "calendar.txt",
- "calendar_dates.txt",
- "routes.txt",
- "stop_times.txt",
- "stops.txt",
- "trips.txt",
- }
- if not dest.exists() or not zipfile.is_zipfile(dest):
- raise RuntimeError(f"transxchange2gtfs did not create a valid GTFS zip: {dest}")
- with zipfile.ZipFile(dest) as z:
- missing = required_files - set(z.namelist())
- if missing:
- missing_str = ", ".join(sorted(missing))
- raise RuntimeError(f"TfL GTFS zip is missing required files: {missing_str}")
- size_mb = dest.stat().st_size / (1024 * 1024)
- print(f" Saved to {dest} ({size_mb:.1f} MB)")
- return dest
def download_national_rail_cif(raw_dir: Path) -> Path | None:
@@ -560,8 +652,9 @@ def download_national_rail_cif(raw_dir: Path) -> Path | None:
print(f"National Rail CIF already exists: {dest}")
return dest
- email = os.environ.get("NATIONAL_RAIL_EMAIL")
- password = os.environ.get("NATIONAL_RAIL_PASSWORD")
+ # Free National Rail Open Data account; env vars override the baked-in default.
+ email = os.environ.get("NATIONAL_RAIL_EMAIL", "schmelczerandras@gmail.com")
+ password = os.environ.get("NATIONAL_RAIL_PASSWORD", "z8^b!4GhCS8kj1Vp")
if not email or not password:
print(
"Warning: NATIONAL_RAIL_EMAIL/NATIONAL_RAIL_PASSWORD not set, skipping national rail"
@@ -1006,23 +1099,15 @@ def main() -> None:
required=True,
help="Output directory for transit data",
)
- parser.add_argument(
- "--skip-tfl",
- action="store_true",
- help="Skip TfL TransXChange download and conversion",
- )
- parser.add_argument(
- "--skip-national-rail",
- action="store_true",
- help="Skip National Rail CIF download and conversion",
- )
args = parser.parse_args()
output_dir: Path = args.output
raw_dir = output_dir / "raw"
raw_dir.mkdir(parents=True, exist_ok=True)
- # 1. Download, clean, and frequency-convert BODS GTFS
+ # 1. Download, clean, and frequency-convert BODS GTFS. BODS covers all
+ # England bus/tram/ferry plus London Underground, DLR, London Tramlink and
+ # the IFS Cloud Cable Car, so no separate TfL feed is needed.
download_osm_pbf(raw_dir)
bods_raw = download_bods_gtfs(raw_dir)
@@ -1031,21 +1116,23 @@ def main() -> None:
bods_final = output_dir / "bods_gtfs.zip"
convert_high_freq_to_frequency_based(bods_cleaned, bods_final)
+ validate_gtfs_feed(bods_final, "BODS GTFS")
- # 2. TfL TransXChange → GTFS
- if args.skip_tfl:
- print("Skipping TfL (--skip-tfl)")
- else:
- download_tfl_transxchange(raw_dir)
- convert_tfl_to_gtfs(raw_dir, output_dir)
-
- # 3. National Rail CIF → GTFS
- if args.skip_national_rail:
- print("Skipping National Rail (--skip-national-rail)")
- else:
- cif = download_national_rail_cif(raw_dir)
- if cif is not None:
- convert_national_rail_to_gtfs(raw_dir, output_dir)
+ # 2. National Rail CIF → GTFS. Heavy rail is mandatory: trains are how people
+ # reach the ~2,725 railway-station destinations, so a bus/metro-only network
+ # silently overstates every train commute. Missing credentials are a HARD
+ # error, so a rail-less network can never ship.
+ cif = download_national_rail_cif(raw_dir)
+ if cif is None:
+ raise RuntimeError(
+ "National Rail timetable was not downloaded — set "
+ "NATIONAL_RAIL_EMAIL / NATIONAL_RAIL_PASSWORD (register free at "
+ "https://opendata.nationalrail.co.uk/). National Rail heavy rail is "
+ "required; without it the transit network models every train journey "
+ "as bus-only and overstates commute times."
+ )
+ nr_final = convert_national_rail_to_gtfs(raw_dir, output_dir)
+ validate_gtfs_feed(nr_final, "National Rail GTFS")
# Summary
print()
diff --git a/pipeline/download/transxchange2gtfs_shim.js b/pipeline/download/transxchange2gtfs_shim.js
deleted file mode 100644
index 1c0a1c2..0000000
--- a/pipeline/download/transxchange2gtfs_shim.js
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/usr/bin/env node
-"use strict";
-
-const fs = require("fs");
-const path = require("path");
-const { createRequire } = require("module");
-
-const [pkgDirArg, ...converterArgs] = process.argv.slice(2);
-
-if (!pkgDirArg || converterArgs.length < 2) {
- console.error(
- "Usage: transxchange2gtfs_shim.js ",
- );
- process.exit(2);
-}
-
-const pkgDir = path.resolve(pkgDirArg);
-const defaultTmpDir = path.resolve(__dirname, "..", "..", ".tmp");
-const localTmpDir =
- process.env.TMPDIR || process.env.TEMP || process.env.TMP || defaultTmpDir;
-const stopsCsv = path.join(localTmpDir, "Stops.csv");
-const converterTmpPrefix = path.join(localTmpDir, "transxchange2gtfs_");
-const converterTmpPatch =
- `static TMP = ${JSON.stringify(converterTmpPrefix)}` +
- ` + process.pid + ${JSON.stringify(path.sep)};`;
-
-fs.mkdirSync(localTmpDir, { recursive: true });
-
-function replaceOnce(relativePath, before, after) {
- const file = path.join(pkgDir, relativePath);
- const original = fs.readFileSync(file, "utf8");
- if (original.includes(before)) {
- fs.writeFileSync(file, original.replace(before, after));
- } else if (original.includes(after)) {
- return;
- } else {
- throw new Error(`Could not patch ${relativePath}: expected text not found`);
- }
-}
-
-// The published 1.12.0 package has a few compatibility issues with current
-// TfL TransXChange exports:
-// - the bin script points at dist/src/cli.js, but the package ships dist/cli.js
-// - the compiled date-holidays import expects a synthetic default export
-// - some TfL journeys reference timing links without matching route-link geometry
-//
-// GTFS shapes are optional for R5 routing. Clear shape references and omit
-// shapes.txt so missing route geometry does not drop otherwise usable trips.
-function patchPackage() {
- replaceOnce(
- "dist/Container.js",
- "static TMP = `/tmp/transxchange2gtfs_${process.pid}/`;",
- converterTmpPatch,
- );
- replaceOnce(
- "dist/Container.js",
- 'fs.existsSync("/tmp/Stops.csv")',
- `fs.existsSync(${JSON.stringify(stopsCsv)})`,
- );
- replaceOnce(
- "dist/Container.js",
- 'fs.createReadStream("/tmp/Stops.csv", "utf8")',
- `fs.createReadStream(${JSON.stringify(stopsCsv)}, "utf8")`,
- );
- replaceOnce(
- "dist/converter/GetStopData.js",
- 'fs.createWriteStream("/tmp/Stops.csv")',
- `fs.createWriteStream(${JSON.stringify(stopsCsv)})`,
- );
- replaceOnce(
- "dist/transxchange/TransXChangeJourneyStream.js",
- "distanceSoFarM += routeLink.Distance;",
- "distanceSoFarM += routeLink ? routeLink.Distance : 0;",
- );
- replaceOnce(
- "dist/gtfs/TripsStream.js",
- "(0, crypto_1.createHash)('md5').update(JSON.stringify({ routeId: journey.route, routeLinkSeq: journey.routeLinkIds })).digest(\"hex\"));",
- "\"\");",
- );
- replaceOnce(
- "dist/gtfs/StopTimesStream.js",
- "stop.shapeDistTraveled, stop.exactTime ? \"1\" : \"0\");",
- "\"\", stop.exactTime ? \"1\" : \"0\");",
- );
- replaceOnce(
- "dist/Container.js",
- "\"stops.txt\": transxchange.pipe(new StopsStream_1.StopsStream(naptanIndex)),\n \"shapes.txt\": journeyStream.pipe(new ShapesStream_1.ShapesStream())",
- "\"stops.txt\": transxchange.pipe(new StopsStream_1.StopsStream(naptanIndex))",
- );
- replaceOnce(
- "dist/Container.js",
- "\"routes.txt\": transxchange.pipe(new RoutesStream_1.RoutesStream()),\n \"transfers.txt\": transxchange.pipe(new TransfersStream_1.TransfersStream(naptanIndex, locationIndex)),\n \"stops.txt\": transxchange.pipe(new StopsStream_1.StopsStream(naptanIndex))",
- "\"routes.txt\": transxchange.pipe(new RoutesStream_1.RoutesStream()),\n \"stops.txt\": transxchange.pipe(new StopsStream_1.StopsStream(naptanIndex))",
- );
-}
-
-patchPackage();
-
-const pkgRequire = createRequire(path.join(pkgDir, "package.json"));
-const Holidays = pkgRequire("date-holidays");
-if (!Holidays.default) {
- Holidays.default = Holidays;
-}
-
-process.argv = [process.argv[0], "transxchange2gtfs", ...converterArgs];
-require(path.join(pkgDir, "dist", "cli.js"));
diff --git a/pipeline/transform/crime_spatial.py b/pipeline/transform/crime_spatial.py
index 4b54623..55b1baa 100644
--- a/pipeline/transform/crime_spatial.py
+++ b/pipeline/transform/crime_spatial.py
@@ -273,27 +273,24 @@ def _write_avg_yr(
for type_idx, name in enumerate(ALL_CRIME_TYPES):
data[f"{name} (avg/yr)"] = avg[:, type_idx]
- # Serious/Minor rollup headlines, computed the SAME way as the by-year rollup
- # bars (_write_by_year/_rollup_long): sum the rollup's types per year, then
- # average over the years in which ANY of those types occurred. This keeps the
- # headline equal to the mean of the "Serious/Minor crime (by year)" bars.
- # Summing the per-type avg/yr values instead (as the merge previously did)
- # divides each type by its OWN years-present and overstates the rollup when a
- # postcode's serious/minor types occur in disjoint years.
+ # Serious/Minor rollup headlines = the exact SUM of their component (avg/yr)
+ # columns, so each rollup always equals the sum of the parts shown beside it
+ # and can never fall below one of its own components. (Previously the rollup
+ # re-derived a union-years-present mean: it divided the summed counts by the
+ # number of years in which ANY component type occurred, whereas each
+ # component divides by its OWN years-present. When a postcode's serious/minor
+ # types occurred in disjoint years the union denominator was larger, so the
+ # rollup came out smaller than the sum of its parts.) The by-year rollup
+ # series in _write_by_year is likewise the per-year sum of the component
+ # bars, so headline and chart both present the rollup as the sum of its parts.
for rollup_name, rollup_types in (
("Serious crime", SERIOUS_CRIME_TYPES),
("Minor crime", MINOR_CRIME_TYPES),
):
rollup_idx = [ALL_CRIME_TYPES.index(name) for name in rollup_types]
- rollup_counts = counts[:, rollup_idx, :].sum(axis=1) # (n_postcodes, n_years)
- rollup_per_year = per_year[:, rollup_idx, :].sum(axis=1)
- rollup_years_present = np.clip(
- (rollup_counts > 0).sum(axis=1), 1, None
- ).astype(np.float64)
- rollup_avg = rollup_per_year.sum(axis=1) / rollup_years_present
- data[f"{rollup_name} (avg/yr)"] = np.round(rollup_avg * norm, 1).astype(
- np.float32
- )
+ data[f"{rollup_name} (avg/yr)"] = np.round(
+ avg[:, rollup_idx].sum(axis=1), 1
+ ).astype(np.float32)
output_path.parent.mkdir(parents=True, exist_ok=True)
pl.DataFrame(data).write_parquet(output_path, compression="zstd")
diff --git a/pipeline/transform/join_epc_pp.py b/pipeline/transform/join_epc_pp.py
index d75b92e..13b855f 100644
--- a/pipeline/transform/join_epc_pp.py
+++ b/pipeline/transform/join_epc_pp.py
@@ -36,6 +36,16 @@ MIN_PRICE = 10_000
MIN_BUILD_YEAR = 1700
MAX_BUILD_YEAR = 2030
+# Plausibility bounds for raw EPC dimensions. EPC lodgements contain data-entry
+# errors (0 m storey heights, 116 m "interior height", 9,210 m² floor areas, 99
+# habitable rooms) that otherwise propagate verbatim into the published per-
+# property columns. Values outside these bands are nulled (treated as unknown)
+# rather than shown. Bounds are deliberately wide so only clear errors are cut.
+MIN_FLOOR_HEIGHT_M = 1.5 # below this a storey is not habitable
+MAX_FLOOR_HEIGHT_M = 6.0 # above this is a data error, not a normal storey
+MAX_TOTAL_FLOOR_AREA_M2 = 2000.0 # ~21,500 sqft; larger is a bulk/garbage record
+MAX_HABITABLE_ROOMS = 20 # dwellings above this are data errors
+
def epc_band_to_year(band: pl.Expr) -> pl.Expr:
"""Map an EPC construction age band to a single representative build year.
@@ -99,6 +109,27 @@ def _clean_number(column: str, dtype: pl.DataType) -> pl.Expr:
return _clean_string(column).cast(dtype, strict=False)
+def _join_address_parts(*columns: str) -> pl.Expr:
+ """Join address components into one display address, single-spaced.
+
+ Price-paid SAON/PAON/STREET are EMPTY STRINGS (not null) when absent —
+ saon is "" on ~88% of rows — and ``concat_str(..., ignore_nulls=True)``
+ skips only nulls, so empty components still contributed their separator
+ (``' 10 PALACE GREEN'``, doubled spaces when a middle part was empty).
+ Convert ``''``→null per component so ignore_nulls works as intended, then
+ defensively collapse residual whitespace runs and strip the result. A
+ fully-empty address becomes null (dropped by the downstream
+ ``pp_address.is_not_null()`` filter) instead of whitespace junk.
+ """
+ joined = pl.concat_str(
+ [_clean_string(column) for column in columns],
+ separator=" ",
+ ignore_nulls=True,
+ )
+ cleaned = joined.str.replace_all(r"\s+", " ").str.strip_chars()
+ return pl.when(cleaned == "").then(None).otherwise(cleaned)
+
+
def _select_epc_columns(raw: pl.LazyFrame) -> pl.LazyFrame:
return (
raw.select(
@@ -132,10 +163,28 @@ def _select_epc_columns(raw: pl.LazyFrame) -> pl.LazyFrame:
)
.filter(pl.col("epc_address").is_not_null())
.with_columns(
- pl.when(pl.col("number_habitable_rooms") == 0)
- .then(None)
- .otherwise(pl.col("number_habitable_rooms"))
+ # Null implausible EPC dimensions so data-entry errors don't reach
+ # the published per-property columns (Interior height, Total floor
+ # area, Number of bedrooms & living rooms). Treated as unknown.
+ pl.when(
+ (pl.col("number_habitable_rooms") >= 1)
+ & (pl.col("number_habitable_rooms") <= MAX_HABITABLE_ROOMS)
+ )
+ .then(pl.col("number_habitable_rooms"))
+ .otherwise(None)
.alias("number_habitable_rooms"),
+ pl.when(
+ pl.col("floor_height").is_between(
+ MIN_FLOOR_HEIGHT_M, MAX_FLOOR_HEIGHT_M
+ )
+ )
+ .then(pl.col("floor_height"))
+ .otherwise(None)
+ .alias("floor_height"),
+ pl.when(pl.col("total_floor_area") <= MAX_TOTAL_FLOOR_AREA_M2)
+ .then(pl.col("total_floor_area"))
+ .otherwise(None)
+ .alias("total_floor_area"),
)
)
@@ -408,11 +457,7 @@ def _run(epc_path: Path, price_paid_path: Path, output_path: Path, temp_dir: Pat
)
.filter(pl.col("pp_property_type") != "Other")
.with_columns(
- pl.concat_str(
- [pl.col("saon"), pl.col("paon"), pl.col("street")],
- separator=" ",
- ignore_nulls=True,
- ).alias("pp_address"),
+ _join_address_parts("saon", "paon", "street").alias("pp_address"),
)
.with_columns(
normalize_address_key(pl.col("pp_address")).alias("_pp_match_address"),
diff --git a/pipeline/transform/merge.py b/pipeline/transform/merge.py
index 0aa8dad..8cad45e 100644
--- a/pipeline/transform/merge.py
+++ b/pipeline/transform/merge.py
@@ -2,6 +2,7 @@ import argparse
import re
import tempfile
from dataclasses import dataclass
+from datetime import date
from typing import Literal
import numpy as np
@@ -30,7 +31,10 @@ from pipeline.utils.postcode_mapping import build_postcode_mapping
MIN_FLOOR_AREA_M2 = 10
CONSERVATION_AREA_FEATURE = "Within conservation area"
-TREE_DENSITY_FEATURE = "Street tree density percentile"
+# Named "Tree canopy" (not "Street tree") because the underlying density unions
+# Forest Research TOW lone-tree/group crowns AND NFI woodland canopy, so a
+# woodland-edge postcode's score reflects forest canopy, not only street trees.
+TREE_DENSITY_FEATURE = "Tree canopy density percentile"
LISTED_BUILDING_FEATURE = "Listed building"
LISTED_BUILDING_MATCH_RADIUS_M = 250.0
LISTED_BUILDING_NEAREST_POSTCODES = 3
@@ -64,7 +68,8 @@ _AREA_COLUMNS = [
"Air Quality and Road Safety Score",
# Ethnicity
"% South Asian",
- "% East/SE Asian",
+ "% East Asian",
+ "% SE Asian",
"% Black",
"% Mixed",
"% White",
@@ -97,15 +102,11 @@ _AREA_COLUMNS = [
# is postcode-grain: it belongs in the area output (one value per postcode,
# covering property-less postcodes too) rather than duplicated per property.
TREE_DENSITY_FEATURE,
- # Schools
- "Good+ primary schools within 5km",
- "Good+ secondary schools within 5km",
- "Good+ primary schools within 2km",
- "Good+ secondary schools within 2km",
- "Outstanding primary schools within 5km",
- "Outstanding secondary schools within 5km",
- "Outstanding primary schools within 2km",
- "Outstanding secondary schools within 2km",
+ # Schools (modelled historical catchment areas covering the postcode)
+ "Good+ primary school catchments",
+ "Good+ secondary school catchments",
+ "Outstanding primary school catchments",
+ "Outstanding secondary school catchments",
# Demographics
"Median age",
# Politics
@@ -167,14 +168,10 @@ _FINAL_RENAME_COLUMNS = {
"latest_price": "Last known price",
"number_habitable_rooms": "Number of bedrooms & living rooms",
"noise_lden_db": "Noise (dB)",
- "good_primary_5km": "Good+ primary schools within 5km",
- "good_secondary_5km": "Good+ secondary schools within 5km",
- "good_primary_2km": "Good+ primary schools within 2km",
- "good_secondary_2km": "Good+ secondary schools within 2km",
- "outstanding_primary_5km": "Outstanding primary schools within 5km",
- "outstanding_secondary_5km": "Outstanding secondary schools within 5km",
- "outstanding_primary_2km": "Outstanding primary schools within 2km",
- "outstanding_secondary_2km": "Outstanding secondary schools within 2km",
+ "good_primary_catchments": "Good+ primary school catchments",
+ "good_secondary_catchments": "Good+ secondary school catchments",
+ "outstanding_primary_catchments": "Outstanding primary school catchments",
+ "outstanding_secondary_catchments": "Outstanding secondary school catchments",
"max_download_speed": "Max available download speed (Mbps)",
"serious_crime_avg_yr": "Serious crime (avg/yr)",
"minor_crime_avg_yr": "Minor crime (avg/yr)",
@@ -528,10 +525,22 @@ def _is_planning_conservation_area_record(dataset: object) -> bool:
def _is_current_planning_record(end_date: object) -> bool:
+ """A planning record is current when it has no end-date OR its end-date is
+ still in the future. The planning.data.gov.uk `end-date` field marks when a
+ designation is RETIRED, so a future date (e.g. 2029-12-31) is a still-current
+ area and must NOT be dropped — the previous "any non-empty date = ended"
+ logic wrongly excluded those (e.g. 22 current Gateshead conservation areas)."""
if end_date is None:
return True
if isinstance(end_date, str):
- return end_date.strip() == ""
+ text = end_date.strip()
+ if text == "":
+ return True
+ try:
+ return date.fromisoformat(text[:10]) > date.today()
+ except ValueError:
+ # Unparseable end-date: keep the record rather than silently drop it.
+ return True
return False
@@ -706,8 +715,32 @@ def _tree_density_by_postcode(tree_density_postcodes_path: Path) -> pl.LazyFrame
)
+def _validate_lsoa_source_coverage(iod_path: Path, ethnicity_path: Path) -> None:
+ """Fail if ethnicity (now LSOA-keyed) misses any IoD LSOA.
+
+ Ethnicity is sourced from Census 2021 TS021 at LSOA, then joined on `lsoa21`
+ like median age and IoD. The IoD table defines the LSOA universe every
+ postcode resolves into, so a missing LSOA would silently null the ethnicity
+ columns for those postcodes; require full coverage instead.
+ """
+ iod_lsoas = pl.read_parquet(
+ iod_path, columns=["LSOA code (2021)"]
+ ).rename({"LSOA code (2021)": "lsoa21"})
+
+ ethnicity_lsoas = pl.read_parquet(ethnicity_path, columns=["lsoa21"])
+ missing_ethnicity = iod_lsoas.join(
+ ethnicity_lsoas, on="lsoa21", how="anti"
+ ).sort("lsoa21")
+ if missing_ethnicity.height > 0:
+ raise ValueError(
+ "Ethnicity data is missing LSOA coverage: "
+ f"{missing_ethnicity.height} LSOAs, e.g. "
+ f"{missing_ethnicity.head(10).to_dicts()}"
+ )
+
+
def _validate_lad_source_coverage(
- iod_path: Path, ethnicity_path: Path, rental_prices_path: Path
+ iod_path: Path, rental_prices_path: Path
) -> None:
iod_lads = (
pl.read_parquet(
@@ -726,16 +759,6 @@ def _validate_lad_source_coverage(
.unique(["lad"])
)
- ethnicity_lads = pl.read_parquet(ethnicity_path, columns=["Geography_code"]).rename(
- {"Geography_code": "lad"}
- )
- missing_ethnicity = iod_lads.join(ethnicity_lads, on="lad", how="anti").sort("lad")
- if missing_ethnicity.height > 0:
- raise ValueError(
- "Ethnicity data is missing 2024 LAD coverage: "
- f"{missing_ethnicity.to_dicts()}"
- )
-
rental_lads = pl.read_parquet(rental_prices_path, columns=["area_code"]).rename(
{"area_code": "lad"}
)
@@ -843,18 +866,16 @@ def _join_area_side_tables(
election: pl.LazyFrame,
poi_counts: pl.LazyFrame,
noise: pl.LazyFrame,
- school_proximity: pl.LazyFrame,
+ school_catchments: pl.LazyFrame,
conservation_areas: pl.LazyFrame,
tree_density: pl.LazyFrame | None,
broadband: pl.LazyFrame,
) -> pl.LazyFrame:
base = base.join(iod, left_on="lsoa21", right_on="LSOA code (2021)", how="left")
- base = base.join(
- ethnicity,
- left_on="Local Authority District code (2024)",
- right_on="Geography_code",
- how="left",
- )
+ # Ethnicity is Census 2021 TS021 at LSOA (~33,755 areas), joined on the same
+ # `lsoa21` key as median age and IoD — a ~100x granularity gain over the old
+ # Local-Authority broadcast, with no change to the 6-bucket output schema.
+ base = base.join(ethnicity, on="lsoa21", how="left")
# Crime is counted spatially per postcode (incidents within 50m of the
# postcode boundary), so it joins on postcode rather than LSOA. crime_spatial
@@ -876,7 +897,7 @@ def _join_area_side_tables(
base = base.join(election, on="pcon", how="left")
base = base.join(poi_counts, on="postcode", how="left")
base = base.join(noise, on="postcode", how="left")
- base = base.join(school_proximity, on="postcode", how="left")
+ base = base.join(school_catchments, on="postcode", how="left")
base = base.join(conservation_areas, on="postcode", how="left").with_columns(
pl.col(CONSERVATION_AREA_FEATURE).fill_null("No")
)
@@ -1941,7 +1962,7 @@ def _build(
ethnicity_path: Path,
crime_path: Path,
noise_path: Path,
- school_proximity_path: Path,
+ school_catchments_path: Path,
broadband_path: Path,
conservation_areas_path: Path,
rental_prices_path: Path,
@@ -1966,7 +1987,8 @@ def _build(
"""
if mode == "listings" and actual_listings_path is None:
raise ValueError("listings mode requires actual_listings_path")
- _validate_lad_source_coverage(iod_path, ethnicity_path, rental_prices_path)
+ _validate_lsoa_source_coverage(iod_path, ethnicity_path)
+ _validate_lad_source_coverage(iod_path, rental_prices_path)
wide = pl.scan_parquet(epc_pp_path).filter(
pl.col("total_floor_area").is_null()
@@ -2050,7 +2072,7 @@ def _build(
)
.select("postcode", "noise_lden_db")
)
- school_proximity = pl.scan_parquet(school_proximity_path)
+ school_catchments = pl.scan_parquet(school_catchments_path)
conservation_areas = _conservation_area_by_postcode(
arcgis.select("postcode", "lat", "lon"), conservation_areas_path
)
@@ -2090,7 +2112,7 @@ def _build(
"election": election,
"poi_counts": poi_counts,
"noise": noise,
- "school_proximity": school_proximity,
+ "school_catchments": school_catchments,
"conservation_areas": conservation_areas,
"tree_density": tree_density,
"broadband": broadband,
@@ -2225,7 +2247,7 @@ def main():
"--ethnicity",
type=Path,
required=True,
- help="Ethnicity by local authority parquet file (optional)",
+ help="Census 2021 ethnic group (TS021) by LSOA parquet file",
)
parser.add_argument(
"--crime",
@@ -2237,10 +2259,10 @@ def main():
"--noise", type=Path, required=True, help="Road noise by postcode parquet file"
)
parser.add_argument(
- "--school-proximity",
+ "--school-catchments",
type=Path,
required=True,
- help="School proximity counts parquet file",
+ help="School catchment counts parquet file",
)
parser.add_argument(
"--broadband",
@@ -2346,7 +2368,7 @@ def main():
ethnicity_path=args.ethnicity,
crime_path=args.crime,
noise_path=args.noise,
- school_proximity_path=args.school_proximity,
+ school_catchments_path=args.school_catchments,
broadband_path=args.broadband,
conservation_areas_path=args.conservation_areas,
rental_prices_path=args.rental_prices,
diff --git a/pipeline/transform/poi_proximity.py b/pipeline/transform/poi_proximity.py
index d062397..5ccc68c 100644
--- a/pipeline/transform/poi_proximity.py
+++ b/pipeline/transform/poi_proximity.py
@@ -25,11 +25,30 @@ POI_GROUPS_2KM = {
# Greengrocer, ...) and the GEOLYTIX brand categories (Tesco, Aldi, ...).
GROCERIES_GROUP = "Groceries"
+# Groceries categories EXCLUDED from the static "Number of grocery shops and
+# supermarkets within 2km" metric. Bakeries, butchers, delis and off-licences
+# are speciality food retail, not somewhere you do a grocery shop; together
+# they were ~a third of the group and inflated the headline count. The metric
+# keeps Supermarket, Convenience Store, Greengrocer and every GEOLYTIX brand.
+GROCERY_STATIC_EXCLUDED_CATEGORIES = {
+ "Bakery",
+ "Butcher & Fishmonger",
+ "Deli & Specialty",
+ "Off-Licence",
+}
+
# OS Open Greenspace function types used for park counts and distance calculation.
# Uses the authoritative OS dataset instead of OSM point POIs for better coverage
# of green spaces that are only mapped as polygons in OSM.
+# Scope: "Public Park Or Garden" is the core park function. "Playing Field"
+# (open public recreation grounds) is borderline but kept: outside big cities
+# the local rec ground is the de facto park. "Play Space" (playgrounds) is
+# excluded — a playground is not a park, and "Playground" is already its own
+# OSM-derived category. The remaining functions (Religious Grounds, Golf
+# Course, Cemetery, Allotments, Bowling Green, Tennis Court, Other Sports
+# Facility) are clearly not parks.
GREENSPACE_PARK_FUNCTIONS = {
- "parks": ["Public Park Or Garden", "Playing Field", "Play Space"],
+ "parks": ["Public Park Or Garden", "Playing Field"],
}
GROCERY_DYNAMIC_FILTER_MIN_POIS = 100
@@ -50,17 +69,22 @@ def _poi_category_slug(category: str) -> str:
def _groceries_categories(pois: pl.DataFrame) -> list[str]:
- """Return the distinct `category` values for the Groceries group.
+ """Return the distinct `category` values for the static groceries metric.
`count_pois_per_postcode` matches POIs on `category`, but the authoritative
GEOLYTIX grocery dataset stores the brand name there (e.g. "Tesco", "Aldi")
with group "Groceries"; it never emits the literal "Supermarket". Collecting
every Groceries category captures both the OSM strings and the brand names.
+ Speciality food retail (bakeries, butchers, delis, off-licences) is
+ excluded — see GROCERY_STATIC_EXCLUDED_CATEGORIES.
"""
if "group" not in pois.columns:
raise ValueError("POI dataframe must include a 'group' column")
return (
- pois.filter(pl.col("group") == GROCERIES_GROUP)
+ pois.filter(
+ (pl.col("group") == GROCERIES_GROUP)
+ & ~pl.col("category").is_in(list(GROCERY_STATIC_EXCLUDED_CATEGORIES))
+ )
.select("category")
.unique()
.sort("category")
@@ -109,6 +133,40 @@ def _build_poi_category_groups(
return groups, display_names
+def _greenspace_count_frame(greenspace: pl.DataFrame) -> pl.DataFrame:
+ """Collapse the greenspace frame to ONE representative row per site.
+
+ os_greenspace.parquet is one row per ACCESS POINT (park gate), which is the
+ right grain for nearest-distance (the nearest gate is what matters) but
+ wildly over-counts "Number of amenities (Park) within Xkm" — a large park
+ with 30 gates counted as 30 parks. Counting uses one row per site at the
+ site centroid (falling back to the first access point when no centroid is
+ available). Degrades gracefully: a legacy parquet without `site_id` is
+ returned unchanged (gate-grain counts) rather than crashing.
+ """
+ if "site_id" not in greenspace.columns:
+ print(
+ "WARNING: greenspace parquet has no site_id column; park counts "
+ "will count access points, not sites (regenerate os_greenspace)"
+ )
+ return greenspace
+
+ keyed = greenspace.filter(pl.col("site_id").is_not_null())
+ unkeyed = greenspace.filter(pl.col("site_id").is_null())
+
+ representatives = keyed.unique(subset=["site_id"], keep="first")
+ if {"site_lat", "site_lng"}.issubset(greenspace.columns):
+ representatives = representatives.with_columns(
+ pl.coalesce([pl.col("site_lat"), pl.col("lat")]).alias("lat"),
+ pl.coalesce([pl.col("site_lng"), pl.col("lng")]).alias("lng"),
+ )
+
+ frames = [representatives.select(greenspace.columns)]
+ if len(unkeyed) > 0:
+ frames.append(unkeyed)
+ return pl.concat(frames)
+
+
def _dynamic_poi_metric_renames(display_names: dict[str, str]) -> dict[str, str]:
renames: dict[str, str] = {}
for group_key, category in display_names.items():
@@ -185,13 +243,16 @@ def main():
# Park counts and distances from OS Open Greenspace. They use the dynamic
# amenity metric names so filters read through the same side-table path as
- # OSM-derived amenity metrics.
+ # OSM-derived amenity metrics. Distances use the access-point grain (the
+ # nearest park GATE is the right semantics); counts use one row per SITE so
+ # a park with many gates counts once.
greenspace = pl.read_parquet(args.greenspace)
+ greenspace_sites = _greenspace_count_frame(greenspace)
park_counts_2km = count_pois_per_postcode(
- postcodes, greenspace, groups=GREENSPACE_PARK_FUNCTIONS, radius_km=2
+ postcodes, greenspace_sites, groups=GREENSPACE_PARK_FUNCTIONS, radius_km=2
)
park_counts_5km = count_pois_per_postcode(
- postcodes, greenspace, groups=GREENSPACE_PARK_FUNCTIONS, radius_km=5
+ postcodes, greenspace_sites, groups=GREENSPACE_PARK_FUNCTIONS, radius_km=5
)
park_distances = min_distance_per_postcode(
postcodes, greenspace, groups=GREENSPACE_PARK_FUNCTIONS
diff --git a/pipeline/transform/postcode_boundaries/__main__.py b/pipeline/transform/postcode_boundaries/__main__.py
index c48b664..cac0cfb 100644
--- a/pipeline/transform/postcode_boundaries/__main__.py
+++ b/pipeline/transform/postcode_boundaries/__main__.py
@@ -260,6 +260,12 @@ def main() -> None:
)
args = parser.parse_args()
+ if args.greenspace and not args.greenspace.exists():
+ # Fail loudly and EARLY (before the ~10h Phases 1-3): silently skipping
+ # the subtraction is exactly how parks/lakes shipped inside postcode
+ # boundaries unnoticed.
+ raise SystemExit(f"--greenspace file not found: {args.greenspace}")
+
fragments_cache = args.output / "fragments_cache.parquet"
# Phase 3 depends only on these inputs; greenspace is applied later (Phase 4),
# so a greenspace change must not invalidate the fragment cache.
@@ -294,7 +300,7 @@ def main() -> None:
greenspace_tree = None
greenspace_geoms = None
- if args.greenspace and args.greenspace.exists():
+ if args.greenspace:
from .greenspace import load_greenspace
print(f" Loading greenspace/water from {args.greenspace}...")
diff --git a/pipeline/transform/postcode_boundaries/greenspace.py b/pipeline/transform/postcode_boundaries/greenspace.py
index 2d81c8f..93a0d7b 100644
--- a/pipeline/transform/postcode_boundaries/greenspace.py
+++ b/pipeline/transform/postcode_boundaries/greenspace.py
@@ -3,7 +3,7 @@
from pathlib import Path
import polars as pl
-from shapely import wkb
+from shapely import make_valid, wkb
from shapely.geometry import MultiPolygon, Polygon
from shapely.strtree import STRtree
@@ -13,12 +13,23 @@ from .geometry import safe_difference, safe_union
def load_greenspace(path: Path) -> tuple[STRtree, list]:
"""Load greenspace parquet and build an STRtree spatial index.
+ Geometries are repaired with ``make_valid`` on load: an invalid park/lake
+ polygon would make the per-postcode ``intersects`` predicate (and the exact
+ difference path) liable to raise mid-merge, hours into a build. Empty
+ geometries are dropped.
+
Returns:
(tree, geoms) where tree is a Shapely STRtree and geoms is
the list of geometries indexed by the tree.
"""
df = pl.read_parquet(path)
- geoms = [wkb.loads(g) for g in df["geometry"].to_list()]
+ geoms = []
+ for raw in df["geometry"].to_list():
+ geom = wkb.loads(raw)
+ if not geom.is_valid:
+ geom = make_valid(geom)
+ if not geom.is_empty:
+ geoms.append(geom)
tree = STRtree(geoms)
return tree, geoms
diff --git a/pipeline/transform/postcode_boundaries/output.py b/pipeline/transform/postcode_boundaries/output.py
index fa881fc..6f3707f 100644
--- a/pipeline/transform/postcode_boundaries/output.py
+++ b/pipeline/transform/postcode_boundaries/output.py
@@ -53,6 +53,18 @@ _OUTPUT_PRECISION_DEG = 0.000001
# tolerance), we fatten it just enough to survive snapping rather than drop it.
_MIN_FOOTPRINT_BUFFER_M = 0.5
+# Building-scale buffer for POINTLIKE inputs that carry no real extent. Multi-
+# dwelling (tower-block) postcodes have every UPRN geocoded to a single shared
+# coordinate, so the boundary collapses to a point; a 0.5 m buffer then yields an
+# invisible ~0.8 m² dot covering hundreds of homes. Such inputs get a ~200 m²
+# building-scale footprint instead. (Genuine thin slivers, which still carry
+# length, keep the minimal buffer.) _resolve_overlaps runs afterwards, so any
+# overlap this introduces is trimmed; a postcode shaved back to sub-grid still
+# falls through to the tiny _grid_footprint, so this can only improve the result.
+_POINT_RESCUE_BUFFER_M = 8.0
+_POINTLIKE_AREA_M2 = 1.0
+_POINTLIKE_PERIMETER_M = 4.0
+
def _snap_to_wgs84_geojson(geom_bng: Polygon | MultiPolygon) -> dict | None:
"""Transform a BNG polygon to WGS84, snap to output precision, validate.
@@ -89,9 +101,33 @@ def _snap_to_wgs84_geojson(geom_bng: Polygon | MultiPolygon) -> dict | None:
return geojson_dict
+def _is_pointlike(geom_bng) -> bool:
+ """True if a BNG geometry carries no real extent (tower-block signature).
+
+ Near-zero area AND short perimeter together distinguish a collapsed point
+ from a genuine thin sliver, which still carries length.
+ """
+ try:
+ return (
+ geom_bng.area < _POINTLIKE_AREA_M2
+ and geom_bng.length < _POINTLIKE_PERIMETER_M
+ )
+ except GEOSException:
+ return False
+
+
def _rescue_footprint(geom_bng) -> dict | None:
- """Fatten a degenerate BNG geometry into a representable footprint and snap."""
- footprint = _largest_polygonal(geom_bng.buffer(_MIN_FOOTPRINT_BUFFER_M))
+ """Fatten a degenerate BNG geometry into a representable footprint and snap.
+
+ A POINTLIKE input (a point, or a near-zero-area/short-perimeter polygon — the
+ signature of a tower-block postcode whose UPRNs all share one coordinate)
+ gets a building-scale buffer so it is not reduced to an invisible sub-metre
+ dot; thin slivers that still carry length keep the minimal buffer.
+ """
+ buffer_m = (
+ _POINT_RESCUE_BUFFER_M if _is_pointlike(geom_bng) else _MIN_FOOTPRINT_BUFFER_M
+ )
+ footprint = _largest_polygonal(geom_bng.buffer(buffer_m))
if footprint is None:
return None
return _snap_to_wgs84_geojson(footprint)
@@ -120,10 +156,16 @@ def to_wgs84_geojson(
)
if simplified is None:
simplified = cleaned
- # Normal path; if snapping erases a thin sliver, fatten its real shape.
- result = _snap_to_wgs84_geojson(simplified)
- if result is None:
+ if _is_pointlike(simplified):
+ # A POINTLIKE footprint is rescued to building scale even when it
+ # would survive snapping: a 0.1-1 m² polygon serializes fine but
+ # ships as an invisible dot covering a whole tower block.
result = _rescue_footprint(simplified)
+ else:
+ # Normal path; if snapping erases a thin sliver, fatten its real shape.
+ result = _snap_to_wgs84_geojson(simplified)
+ if result is None:
+ result = _rescue_footprint(simplified)
if result is not None:
return result
@@ -202,6 +244,10 @@ def merge_fragments(
greenspace_tree: Optional STRtree of park/water polygons.
greenspace_geoms: Optional list of park/water geometries (indexed by tree).
"""
+ subtract = greenspace_tree is not None and greenspace_geoms is not None
+ if subtract:
+ from .greenspace import subtract_greenspace
+
by_postcode: dict[str, list] = defaultdict(list)
for pc, geom in all_fragments:
by_postcode[pc].append(geom)
@@ -229,9 +275,7 @@ def merge_fragments(
# Remove artifact interior holes from INSPIRE+Voronoi+make_valid chain
combined = _fill_holes(combined)
# Subtract parks/water if provided
- if greenspace_tree is not None and greenspace_geoms is not None:
- from .greenspace import subtract_greenspace
-
+ if subtract:
pre_green = combined
combined = subtract_greenspace(combined, greenspace_tree, greenspace_geoms)
combined = _keep_polygon_parts(combined)
diff --git a/pipeline/transform/postcode_boundaries/test_postcode_boundaries.py b/pipeline/transform/postcode_boundaries/test_postcode_boundaries.py
index 760ac72..c316abb 100644
--- a/pipeline/transform/postcode_boundaries/test_postcode_boundaries.py
+++ b/pipeline/transform/postcode_boundaries/test_postcode_boundaries.py
@@ -906,6 +906,80 @@ class TestToWgs84Geojson:
assert result is not None
assert result["type"] == "Polygon"
+ def test_pointlike_input_gets_building_scale_footprint(self):
+ """A tower-block postcode (all UPRNs at one point) must not collapse to a
+ sub-metre dot; it gets a building-scale footprint instead."""
+ import pyproj
+ from shapely.geometry import Point, shape
+ from shapely.ops import transform as transform_geometry
+
+ to_bng = pyproj.Transformer.from_crs(
+ "EPSG:4326", "EPSG:27700", always_xy=True
+ )
+ result = to_wgs84_geojson(Point(360000, 170000))
+ assert result is not None
+ area_m2 = transform_geometry(to_bng.transform, shape(result)).area
+ assert area_m2 > 100, f"point footprint only {area_m2:.1f} m^2"
+
+ def test_snappable_pointlike_polygon_still_gets_building_scale_footprint(self):
+ """A collapsed-but-snappable footprint (e.g. EC2A 2FJ: 181 properties on
+ 0.86 m²) must NOT ship as-is just because it survives precision snapping;
+ pointlike inputs are rescued to a ~201 m² disc unconditionally."""
+ import pyproj
+ from shapely.geometry import shape
+ from shapely.ops import transform as transform_geometry
+
+ to_bng = pyproj.Transformer.from_crs(
+ "EPSG:4326", "EPSG:27700", always_xy=True
+ )
+ # 0.9m x 0.9m square: area 0.81 m², perimeter 3.6 m — pointlike, yet
+ # large enough (~8 output-grid cells) to survive the 1e-6 deg snap.
+ tiny = box(530000, 180000, 530000.9, 180000.9)
+ from .output import _snap_to_wgs84_geojson
+
+ assert _snap_to_wgs84_geojson(tiny) is not None, (
+ "precondition: this polygon must be snappable, otherwise the test "
+ "exercises the old snap-fails path instead of the new one"
+ )
+ result = to_wgs84_geojson(tiny)
+ assert result is not None
+ area_m2 = transform_geometry(to_bng.transform, shape(result)).area
+ assert 150 < area_m2 < 300, (
+ f"pointlike snappable footprint shipped at {area_m2:.2f} m^2 "
+ "instead of a building-scale (~201 m^2) disc"
+ )
+
+ def test_normal_polygon_area_unchanged(self):
+ """A normal polygon must pass through without rescue inflation."""
+ import pyproj
+ from shapely.geometry import shape
+ from shapely.ops import transform as transform_geometry
+
+ to_bng = pyproj.Transformer.from_crs(
+ "EPSG:4326", "EPSG:27700", always_xy=True
+ )
+ poly = box(530000, 180000, 530100, 180100) # 10,000 m²
+ result = to_wgs84_geojson(poly)
+ assert result is not None
+ area_m2 = transform_geometry(to_bng.transform, shape(result)).area
+ assert area_m2 == pytest.approx(10_000, rel=0.01)
+
+ def test_thin_sliver_keeps_minimal_buffer(self):
+ """A genuine elongated sliver still carries length, so it is NOT inflated
+ to building scale — only truly pointlike inputs are."""
+ import pyproj
+ from shapely.geometry import LineString, shape
+ from shapely.ops import transform as transform_geometry
+
+ to_bng = pyproj.Transformer.from_crs(
+ "EPSG:4326", "EPSG:27700", always_xy=True
+ )
+ sliver = LineString([(360000, 170000), (360040, 170000)]).buffer(0.05)
+ result = to_wgs84_geojson(sliver)
+ assert result is not None
+ area_m2 = transform_geometry(to_bng.transform, shape(result)).area
+ assert area_m2 < 100, f"sliver inflated to {area_m2:.1f} m^2"
+
def test_coordinates_have_limited_precision(self):
"""GeoJSON coordinates should be rounded to 6 decimal places."""
import json
@@ -1101,6 +1175,26 @@ class TestSubtractGreenspace:
# 80% < 90% cap, so subtraction should happen
assert result.area == pytest.approx(2000, rel=0.01)
+ def test_load_greenspace_repairs_invalid_and_drops_empty(self, tmp_path):
+ """An invalid (bow-tie) park polygon in the parquet must be repaired on
+ load: it would otherwise make the per-postcode intersects/difference
+ liable to raise hours into a merge."""
+ from .greenspace import load_greenspace
+
+ bowtie = Polygon([(0, 0), (10, 10), (10, 0), (0, 10)]) # self-intersects
+ assert not bowtie.is_valid
+ valid = box(20, 20, 30, 30)
+ path = tmp_path / "greenspace.parquet"
+ pl.DataFrame({"geometry": [bowtie.wkb, valid.wkb]}).write_parquet(path)
+
+ tree, geoms = load_greenspace(path)
+ assert len(geoms) == 2
+ assert all(g.is_valid and not g.is_empty for g in geoms)
+ # The repaired bow-tie must still subtract cleanly.
+ result = subtract_greenspace(box(0, 0, 100, 100), tree, geoms)
+ assert result.is_valid
+ assert result.area < 10_000
+
class TestToWgs84GeojsonValidity:
"""to_wgs84_geojson must emit GeoJSON that round-trips to a valid geometry."""
diff --git a/pipeline/transform/price_estimation/estimate.py b/pipeline/transform/price_estimation/estimate.py
index fb0af8e..c5d3e93 100644
--- a/pipeline/transform/price_estimation/estimate.py
+++ b/pipeline/transform/price_estimation/estimate.py
@@ -230,11 +230,28 @@ def main():
).height
print(f" kNN blended: {n_blended:,} of {n_estimated:,} estimates")
+ # Null the absolute "Estimated current price" itself when its implied
+ # per-sqm is implausible (outside [MIN_COMPARABLE_PSM, MAX_COMPARABLE_PSM])
+ # AND the floor area is known: these come from bulk/block transfers or
+ # garbage source prices (e.g. a £207.5M "sale" on a 93 m² terrace -> a £197M
+ # estimate) and are not meaningful single-dwelling values. Previously only
+ # the derived per-sqm was nulled, leaving the absurd headline price visible.
+ _raw_est_psm = pl.col("Estimated current price") / pl.col("Total floor area (sqm)")
+ df = df.with_columns(
+ pl.when(
+ pl.col("Estimated current price").is_not_null()
+ & pl.col("Total floor area (sqm)").is_not_null()
+ & (pl.col("Total floor area (sqm)") > 0)
+ & ((_raw_est_psm < MIN_COMPARABLE_PSM) | (_raw_est_psm > MAX_COMPARABLE_PSM))
+ )
+ .then(None)
+ .otherwise(pl.col("Estimated current price"))
+ .alias("Estimated current price"),
+ )
+
# Derive estimated price per sqm where both estimated price and floor area
- # exist. Null out values outside the plausibility band [MIN_COMPARABLE_PSM,
- # MAX_COMPARABLE_PSM] (the same band the kNN pool uses): extreme values come
- # from bulk/block transactions or floor-area errors and are not meaningful
- # per-unit prices.
+ # exist. Now that the implausible-psm estimates are nulled above, the band
+ # filter here mainly guards the floor-area>0 case.
_est_psm = pl.col("Estimated current price") / pl.col("Total floor area (sqm)")
df = df.with_columns(
pl.when(
diff --git a/pipeline/transform/price_estimation/index.py b/pipeline/transform/price_estimation/index.py
index e667ae3..829baa3 100644
--- a/pipeline/transform/price_estimation/index.py
+++ b/pipeline/transform/price_estimation/index.py
@@ -25,6 +25,8 @@ from pipeline.transform.price_estimation.shrinkage import (
)
from pipeline.transform.price_estimation.utils import (
CURRENT_YEAR,
+ LATEST_COMPLETE_YEAR,
+ SMOOTHNESS_SUPPORT_PAIRS,
TEMPORAL_SMOOTHNESS_LAMBDA,
TYPE_GROUPS,
build_hedonic_features,
@@ -36,6 +38,19 @@ from pipeline.transform.price_estimation.utils import (
MIN_PAIRS = 5
OUTLIER_THRESHOLD = 3.0 # hard pre-filter; Huber handles the rest
+# Gap-aware companion to OUTLIER_THRESHOLD: |log_ratio| must also stay within
+# this many log-units PER YEAR of holding period (short gaps are allowed a
+# full year's band). A flat +/-3.0 cap admits e.g. a 10k -> 196k "sale" six
+# months apart (log +2.95, and weight 1/sqrt(gap) gives it the leverage of
+# ~10 normal pairs); Huber does NOT recover, because once the thin year's
+# beta satisfies the garbage pair it is the many good long-gap pairs that
+# carry the residual and get down-weighted. Such pairs are data errors or
+# non-market transfers (right-to-buy, probate, flips), not house-price
+# signal -- standard repeat-sales practice (Case-Shiller) excludes extreme
+# annualised returns for the same reason. 0.7 log/yr (~2x in a year) keeps
+# any plausible genuine market move; long-gap pairs are still governed by
+# the +/-3.0 cap.
+ANNUALISED_OUTLIER_THRESHOLD = 0.7
HUBER_K = 1.345
IRLS_ITERATIONS = 5
@@ -110,7 +125,16 @@ def extract_pairs(input_path: Path, max_year2: int | None = None) -> pl.DataFram
/ (pl.col("frac_year2") - pl.col("frac_year1")).cast(pl.Float64).sqrt()
).alias("weight"),
)
- .filter(pl.col("log_ratio").abs() <= OUTLIER_THRESHOLD)
+ .filter(
+ pl.col("log_ratio").abs()
+ <= pl.min_horizontal(
+ pl.lit(OUTLIER_THRESHOLD),
+ ANNUALISED_OUTLIER_THRESHOLD
+ * pl.max_horizontal(
+ pl.col("frac_year2") - pl.col("frac_year1"), pl.lit(1.0)
+ ),
+ )
+ )
.collect()
)
@@ -180,11 +204,27 @@ def solve_robust_index(
# beta=0) has no column, so the penalty spans the non-baseline years only.
# For cells with <3 betas there is no curvature to penalise and the solve is
# unchanged.
+ #
+ # The penalty is SUPPORT-SCALED per row: a flat lambda is too weak for
+ # years identified by only 1-2 repeat-sale pairs (a cell can have hundreds
+ # of pairs overall yet single thin years, yielding 2-7x one-year spikes
+ # that cell-level shrinkage cannot catch). Each curvature row's lambda is
+ # lambda0 * (1 + SMOOTHNESS_SUPPORT_PAIRS / s), with s the minimum
+ # cross-year pair count among the row's three years, so thin years are
+ # pulled strongly toward the local trend while well-supported years keep
+ # the baseline penalty. Taking the min over the triple (not just the
+ # middle year) also covers thin FIRST/LAST years of the range, which only
+ # ever appear at a triple's edge -- the last solved year feeds the
+ # CURRENT_YEAR trend extrapolation, so spikes there are the costliest.
n_pen = 0
pen_rows_arr = pen_cols_arr = np.empty(0, dtype=np.int64)
pen_vals_arr = pen_b = np.empty(0, dtype=np.float64)
if TEMPORAL_SMOOTHNESS_LAMBDA > 0 and n_cols >= 3:
- sqrt_lambda = float(np.sqrt(TEMPORAL_SMOOTHNESS_LAMBDA))
+ cross = years1 != years2
+ touched, counts = np.unique(
+ np.concatenate([years1[cross], years2[cross]]), return_counts=True
+ )
+ support = {int(y): int(c) for y, c in zip(touched, counts)}
years_sorted = sorted(year_to_col)
cols_by_year = [year_to_col[y] for y in years_sorted]
n_pen = n_cols - 2
@@ -201,6 +241,11 @@ def solve_robust_index(
w0 = 2.0 / ((y1 - y0) * (y2 - y0))
w1 = -2.0 / ((y1 - y0) * (y2 - y1))
w2 = 2.0 / ((y2 - y1) * (y2 - y0))
+ s_k = min(support.get(y, 0) for y in (y0, y1, y2))
+ lam_k = TEMPORAL_SMOOTHNESS_LAMBDA * (
+ 1.0 + SMOOTHNESS_SUPPORT_PAIRS / max(s_k, 1)
+ )
+ sqrt_lambda = float(np.sqrt(lam_k))
pen_vals[3 * k : 3 * k + 3] = (
sqrt_lambda * w0,
sqrt_lambda * w1,
@@ -346,10 +391,22 @@ def compute_hedonic_index(
EXTRAPOLATION_YEARS = 3
+# Bound on the per-year slope used to trend-extrapolate beyond the last solved
+# year (the solve stops at LATEST_COMPLETE_YEAR; CURRENT_YEAR is filled here).
+# +/-0.10 log/yr (~+/-10.5%/yr) comfortably covers genuine UK sector-level
+# annual moves while preventing a residual spike in the recent betas from
+# compounding into an absurd extrapolated step (e.g. +49% in one year).
+MAX_EXTRAPOLATION_SLOPE = 0.10
def forward_fill(index: dict, min_year: int, max_year: int) -> dict:
- """Forward-fill missing years, with linear extrapolation beyond last known year."""
+ """Forward-fill missing years, with trend extrapolation beyond last known year.
+
+ The extrapolation slope is the MEDIAN of the per-year slopes between
+ consecutive known points in the recent window (a single noisy year corrupts
+ at most one of those slopes, unlike a least-squares fit through all the
+ points), clamped to +/-MAX_EXTRAPOLATION_SLOPE.
+ """
if not index:
return {y: 0.0 for y in range(min_year, max_year + 1)}
@@ -364,7 +421,7 @@ def forward_fill(index: dict, min_year: int, max_year: int) -> dict:
last = index[y]
filled[y] = last
- # Linear extrapolation beyond last known year
+ # Robust trend extrapolation beyond last known year
if last_known_year < max_year:
recent = [
(y, index[y])
@@ -372,9 +429,17 @@ def forward_fill(index: dict, min_year: int, max_year: int) -> dict:
if y >= last_known_year - EXTRAPOLATION_YEARS
]
if len(recent) >= 2:
- years_arr = np.array([r[0] for r in recent], dtype=np.float64)
- vals_arr = np.array([r[1] for r in recent], dtype=np.float64)
- slope = np.polyfit(years_arr, vals_arr, 1)[0]
+ slopes = [
+ (v_b - v_a) / (y_b - y_a)
+ for (y_a, v_a), (y_b, v_b) in zip(recent[:-1], recent[1:])
+ ]
+ slope = float(
+ np.clip(
+ np.median(slopes),
+ -MAX_EXTRAPOLATION_SLOPE,
+ MAX_EXTRAPOLATION_SLOPE,
+ )
+ )
for y in range(last_known_year + 1, max_year + 1):
filled[y] = index[last_known_year] + slope * (y - last_known_year)
else:
@@ -388,21 +453,36 @@ def build_index(
input_path: Path,
max_pair_year: int | None = None,
postcodes_path: Path | None = None,
+ sectors: list[str] | None = None,
) -> pl.DataFrame:
"""Build the full price index from raw data.
If max_pair_year is set, only pairs before that year are used (backtesting holdout).
The index is still forward-filled to CURRENT_YEAR.
postcodes_path: if provided, lat/lon are read from this file instead of input_path.
+ sectors: if provided, restrict the build to these postcode sectors (for
+ debugging/verification runs; hierarchy levels are then computed only from
+ the scoped pairs, so scoped output is NOT identical to a full build).
"""
- pairs = extract_pairs(input_path, max_year2=max_pair_year)
+ # Solve the index only on COMPLETE calendar years: exclude the partial
+ # current year, whose thin repeat-sale set yields wild betas. The index is
+ # still forward-filled/trend-extrapolated to CURRENT_YEAR below, so 2026
+ # follows the established trend rather than a partial-year spike. Backtest
+ # passes a stricter max_pair_year, which is honoured.
+ estimation_cap = (
+ max_pair_year if max_pair_year is not None else LATEST_COMPLETE_YEAR + 1
+ )
+ pairs = extract_pairs(input_path, max_year2=estimation_cap)
+ if sectors is not None:
+ pairs = pairs.filter(pl.col("sector").is_in(sectors))
+ print(f" Scoped to {len(sectors)} sectors: {len(pairs):,} pairs")
centroids = extract_centroids(postcodes_path or input_path)
min_year = int(pairs["year1"].min())
max_year = CURRENT_YEAR
hedonic_idx = compute_hedonic_index(
- input_path, min_year, max_year, max_sale_year=max_pair_year
+ input_path, min_year, max_year, max_sale_year=estimation_cap
)
# Precompute hierarchy
@@ -525,9 +605,21 @@ def main():
help="Path to postcode.parquet (for lat/lon centroids)",
)
parser.add_argument("--output", type=Path, required=True)
+ parser.add_argument(
+ "--sectors",
+ type=str,
+ default=None,
+ help="Comma-separated postcode sectors to scope the build to "
+ "(debug/verification only; hierarchy is computed from scoped pairs)",
+ )
args = parser.parse_args()
- result = build_index(args.input, postcodes_path=args.postcodes)
+ sectors = (
+ [s.strip() for s in args.sectors.split(",") if s.strip()]
+ if args.sectors
+ else None
+ )
+ result = build_index(args.input, postcodes_path=args.postcodes, sectors=sectors)
result.write_parquet(args.output)
size_mb = args.output.stat().st_size / (1024 * 1024)
diff --git a/pipeline/transform/price_estimation/test_index.py b/pipeline/transform/price_estimation/test_index.py
index 4cc9e8e..51e9590 100644
--- a/pipeline/transform/price_estimation/test_index.py
+++ b/pipeline/transform/price_estimation/test_index.py
@@ -3,7 +3,10 @@ import polars as pl
from pipeline.transform.price_estimation import index as index_mod
from pipeline.transform.price_estimation.index import (
+ MAX_EXTRAPOLATION_SLOPE,
compute_indices_for_level,
+ extract_pairs,
+ forward_fill,
solve_robust_index,
)
@@ -105,6 +108,139 @@ def test_gap_spanning_level_jump_is_not_smoothed_into_a_ramp():
assert abs(idx[2015] - true[2015]) < 0.05
+def _ramp_pairs_with_thin_tail(tail_ratio: float, tail_n: int, ramp_reps: int):
+ """Smooth 0.04/yr ramp 2010-2020 with `ramp_reps` copies of each adjacent
+ pair, plus `tail_n` pair(s) 2020->2021 asserting a `tail_ratio` jump."""
+ years = range(2010, 2021)
+ true = {y: 0.04 * (y - 2010) for y in years}
+ y1, y2, lr, w = [], [], [], []
+ for a in range(2010, 2020):
+ for _ in range(ramp_reps):
+ y1.append(a)
+ y2.append(a + 1)
+ lr.append(true[a + 1] - true[a])
+ w.append(1.0)
+ for _ in range(tail_n):
+ y1.append(2020)
+ y2.append(2021)
+ lr.append(tail_ratio)
+ w.append(1.0)
+ return (
+ np.array(y1, dtype=np.int32),
+ np.array(y2, dtype=np.int32),
+ np.array(lr, dtype=np.float64),
+ np.array(w, dtype=np.float64),
+ )
+
+
+def test_support_scaled_penalty_suppresses_thin_year_spike(monkeypatch):
+ """A final year identified by a SINGLE pair claiming a +1.5 log jump is
+ pulled strongly toward the local trend; with the flat baseline penalty
+ (support scaling off) the jump survives almost entirely. The thin year is
+ the LAST year of the range (only ever at a penalty triple's edge), proving
+ the min-over-triple support rule covers range edges -- the last solved year
+ feeds the CURRENT_YEAR trend extrapolation."""
+ y1, y2, lr, w = _ramp_pairs_with_thin_tail(tail_ratio=1.5, tail_n=1, ramp_reps=10)
+
+ monkeypatch.setattr(index_mod, "SMOOTHNESS_SUPPORT_PAIRS", 0)
+ flat = solve_robust_index(y1, y2, lr, w)
+ monkeypatch.undo()
+ scaled = solve_robust_index(y1, y2, lr, w)
+
+ flat_step = flat[2021] - flat[2020]
+ scaled_step = scaled[2021] - scaled[2020]
+ assert flat_step > 1.2 # flat lambda barely resists the spike
+ assert scaled_step < 0.65 # support-scaled lambda suppresses it
+ # The well-supported ramp stays close to truth: the strong penalty row
+ # spanning the thin year drags its immediate neighbour slightly (<0.1)
+ # toward collinearity -- the price of suppressing a x4.5 one-year spike.
+ for y in range(2010, 2021):
+ assert abs(scaled[y] - 0.04 * (y - 2010)) < 0.1
+
+
+def test_support_scaling_leaves_well_supported_years_unchanged(monkeypatch):
+ """With ample pairs everywhere (support 50-100 per year), lambda_eff ~
+ lambda0 and the solution matches the flat-penalty solve to <1e-3."""
+ y1, y2, lr, w = _ramp_pairs_with_thin_tail(tail_ratio=0.04, tail_n=50, ramp_reps=50)
+
+ monkeypatch.setattr(index_mod, "SMOOTHNESS_SUPPORT_PAIRS", 0)
+ flat = solve_robust_index(y1, y2, lr, w)
+ monkeypatch.undo()
+ scaled = solve_robust_index(y1, y2, lr, w)
+
+ assert set(flat) == set(scaled)
+ assert max(abs(flat[y] - scaled[y]) for y in flat) < 1e-3
+
+
+def test_forward_fill_extrapolation_uses_robust_median_slope():
+ """A residual spike in ONE recent year must not corrupt the extrapolated
+ step: the median of consecutive per-year slopes ignores it (a least-squares
+ fit through the same points would extrapolate a large positive slope)."""
+ index = {2022: 1.00, 2023: 1.05, 2024: 1.60, 2025: 1.10}
+ filled = forward_fill(index, 2022, 2026)
+ # slopes: [+0.05, +0.55, -0.50] -> median +0.05
+ assert abs(filled[2026] - (1.10 + 0.05)) < 1e-9
+
+
+def test_forward_fill_extrapolated_slope_is_clamped():
+ """A consistent (but absurd) recent trend is clamped to MAX_EXTRAPOLATION_SLOPE."""
+ index = {2022: 0.0, 2023: 0.4, 2024: 0.8, 2025: 1.2}
+ filled = forward_fill(index, 2022, 2026)
+ assert abs(filled[2026] - (1.2 + MAX_EXTRAPOLATION_SLOPE)) < 1e-9
+
+ index_down = {2022: 1.2, 2023: 0.8, 2024: 0.4, 2025: 0.0}
+ filled_down = forward_fill(index_down, 2022, 2026)
+ assert abs(filled_down[2026] - (0.0 - MAX_EXTRAPOLATION_SLOPE)) < 1e-9
+
+
+def test_forward_fill_preserves_sane_trend_and_flat_fallback():
+ """Genuine moderate trends still extrapolate (it stays a forward-FILL-with-
+ trend); with <2 recent points the fill is flat."""
+ index = {2022: 1.00, 2023: 1.05, 2024: 1.10, 2025: 1.15}
+ filled = forward_fill(index, 2022, 2026)
+ assert abs(filled[2026] - 1.20) < 1e-9
+
+ assert forward_fill({2025: 0.7}, 2024, 2026)[2026] == 0.7
+
+
+def test_extract_pairs_drops_extreme_annualised_returns(tmp_path):
+ """A +-3.0 log cap alone admits e.g. a 10x 'gain' in six months -- a data
+ error or non-market transfer with huge leverage (weight = 1/sqrt(gap)).
+ Such pairs are dropped via the annualised cap; large ratios over long
+ holding periods (genuine appreciation) are kept."""
+ df = pl.DataFrame(
+ {
+ "Postcode": ["AB1 2CD", "AB1 2CE", "AB1 2CF"],
+ "Property type": ["Detached", "Detached", "Detached"],
+ "historical_prices": [
+ # +2.30 log in 6 months -> dropped (cap 0.7 for gap <= 1yr)
+ [
+ {"year": 2020, "month": 1, "price": 100_000},
+ {"year": 2020, "month": 7, "price": 1_000_000},
+ ],
+ # +2.20 log over 24 years -> kept (flat 3.0 cap governs)
+ [
+ {"year": 2000, "month": 1, "price": 100_000},
+ {"year": 2024, "month": 1, "price": 900_000},
+ ],
+ # +0.41 log in 1 year -> kept (within the 0.7/yr band)
+ [
+ {"year": 2020, "month": 1, "price": 100_000},
+ {"year": 2021, "month": 1, "price": 150_000},
+ ],
+ ],
+ }
+ )
+ path = tmp_path / "props.parquet"
+ df.write_parquet(path)
+
+ pairs = extract_pairs(path)
+
+ assert len(pairs) == 2
+ ratios = sorted(round(r, 2) for r in pairs["log_ratio"].to_list())
+ assert ratios == [0.41, 2.2]
+
+
def test_n_pairs_counts_only_cross_year_pairs():
"""FIX #12: same-year pairs carry zero index information and must not inflate
the shrinkage weight; n_pairs counts only cross-year (year2 != year1) pairs."""
diff --git a/pipeline/transform/price_estimation/utils.py b/pipeline/transform/price_estimation/utils.py
index bdb80d3..2b53346 100644
--- a/pipeline/transform/price_estimation/utils.py
+++ b/pipeline/transform/price_estimation/utils.py
@@ -6,6 +6,13 @@ import numpy as np
import polars as pl
CURRENT_YEAR = 2026
+# Latest COMPLETE calendar year. The current year's transactions are only
+# partially reported (Land Registry lags ~2-3 months), so a sector's thin
+# partial-year repeat-sale set produces wild index betas (e.g. +334% in a
+# single sector). The index is SOLVED only on complete years (<= this) and
+# forward-filled/trend-extrapolated to CURRENT_YEAR, so current-value
+# projections follow the established trend instead of a partial-year spike.
+LATEST_COMPLETE_YEAR = CURRENT_YEAR - 1
_today = date.today()
CURRENT_FRAC_YEAR = _today.year + (_today.month - 1) / 12
@@ -29,6 +36,20 @@ SHRINKAGE_K = 50
# noisy year) without flattening genuine multi-year trends.
TEMPORAL_SMOOTHNESS_LAMBDA = 0.05
+# Per-year support scaling for the temporal smoothness penalty. A flat lambda
+# is too weak for years with very few repeat-sale pairs: a sector can have
+# hundreds of pairs overall (so cell-level n/(n+k) shrinkage barely moves it)
+# yet have individual years estimated from 1-2 pairs, producing 2-7x
+# single-year index spikes. Each curvature row is therefore scaled by the
+# local pair support of its year triple:
+# lambda_eff = lambda0 * (1 + SMOOTHNESS_SUPPORT_PAIRS / s)
+# where s is the minimum cross-year pair count among the triple's years.
+# Well-supported years (s >> SMOOTHNESS_SUPPORT_PAIRS) keep lambda_eff ~
+# lambda0 (current behaviour); a year identified by a single pair gets
+# ~41x lambda0, pulling its beta strongly toward the local trend through its
+# neighbours. Same-year pairs cancel in the design and are not counted.
+SMOOTHNESS_SUPPORT_PAIRS = 40
+
def type_group_expr():
"""Polars expression: Property type -> type_group."""
diff --git a/pipeline/transform/school_catchments.py b/pipeline/transform/school_catchments.py
new file mode 100644
index 0000000..e71b607
--- /dev/null
+++ b/pipeline/transform/school_catchments.py
@@ -0,0 +1,748 @@
+"""Model historical school catchment areas and count them per postcode.
+
+No national dataset of school catchment areas exists for England: catchments
+are set per admission authority, only a handful of councils publish polygons,
+and the pupil-residence data behind commercial "heatmap" catchments lives in
+the restricted National Pupil Database. This module therefore COMPILES one
+from open data, estimating each school's admission cutoff distance ("last
+distance offered") — the radius within which an applicant would plausibly be
+offered a place.
+
+Model: English state admissions are run as deferred acceptance with distance
+tie-breaks, which in a continuum economy is equivalent to finding
+market-clearing cutoff distances (Azevedo & Leshno 2016). Per phase
+(primary/secondary):
+
+1. Demand — Census 2021 children per LSOA (TS007A age bands, prorated to the
+ phase's cohort ages) split evenly across the LSOA's live postcodes.
+2. Supply — every open, non-selective state-funded school (GIAS), with a fill
+ target of max(capacity, headcount) prorated to the phase's cohorts
+ (sixth-form and nursery years carry reduced weight, since their class
+ sizes differ and they are not allocated by the same admissions round).
+3. Preferences — children prefer nearby schools, trading distance against
+ Ofsted grade: a school's effective distance is its real distance minus a
+ grade bonus (Outstanding > Good > ungraded > below-Good). Because real
+ first preferences are heterogeneous, each postcode's children split
+ across nearby feasible schools with logit weights over effective
+ distance rather than all picking the same one.
+4. Equilibrium — cutoffs start unbounded and tighten monotonically: each
+ round, children apply to their preferred feasible school(s), and
+ oversubscribed schools tighten their cutoff to the distance of their
+ marginal admitted child. Converges to the deferred-acceptance outcome.
+5. Schools that never fill have no binding cutoff — anyone who applies gets
+ in — so their feasibility radius is the distance within which the local
+ child population would cover their fill target, capped.
+
+The free parameters (preference bonuses, demand scale, choice temperature,
+residual calibration factors) are CALIBRATED against published "last
+distance offered" figures scraped from nine local authorities' allocation
+reports — see check_school_cutoffs.py and the constants below.
+
+A postcode is "inside the catchment" of every school whose cutoff radius
+covers it. The output counts those schools per postcode for the four
+good+/outstanding x primary/secondary categories (Ofsted-classified, same
+rules as the previous proximity metric). Selective (grammar) schools are
+excluded throughout: their intakes are test-based and region-wide, so a
+distance model would fabricate a catchment that does not exist.
+
+Known limitations: faith oversubscription criteria are not modelled (whether
+a faith school's catchment is open to a given family depends on the family),
+and Census 2021 child counts lag current rolls slightly. Cutoffs are
+straight-line distances, the modal LA tie-break criterion.
+"""
+
+import argparse
+from pathlib import Path
+
+import numpy as np
+import polars as pl
+from scipy.spatial import cKDTree
+
+from pipeline.utils.poi_counts import _project_lat_lng_km, valid_uk_coords_mask
+
+SCHOOL_GROUPS = {
+ "good_primary": ["good_primary", "outstanding_primary"],
+ "good_secondary": ["good_secondary", "outstanding_secondary"],
+ "outstanding_primary": ["outstanding_primary"],
+ "outstanding_secondary": ["outstanding_secondary"],
+}
+
+# Age thresholds for deciding which phase(s) a school serves. A school serves
+# PRIMARY-age children if its statutory lowest age is <= 10, and SECONDARY-age
+# children if its statutory highest age is >= 12. All-through (e.g. 3-18) and
+# middle-deemed-secondary (e.g. 9-13) schools satisfy BOTH and so are counted in
+# both the primary and the secondary metrics — Ofsted's coarse "Ofsted phase"
+# labels such schools as just "Secondary", which previously hid them from every
+# postcode's primary-school count.
+PRIMARY_MAX_AGE = 10
+SECONDARY_MIN_AGE = 12
+
+# Cohort ages (inclusive) each phase competes for: Reception-Y6 and Y7-Y11.
+PRIMARY_AGES = (4, 10)
+SECONDARY_AGES = (11, 15)
+
+# Cohort weights for prorating a school's headcount/capacity across the ages
+# it teaches. Nursery classes are typically part-time and small; sixth forms
+# run at roughly 60% of a school's Y7-Y11 cohort size. A flat proration
+# undersupplied secondary places by ~8%.
+NURSERY_COHORT_WEIGHT = 0.5 # ages < 4
+SIXTH_FORM_COHORT_WEIGHT = 0.6 # ages >= 16
+
+# Only schools that admit (mostly) by geography take part in the assignment.
+# Independent, special and Welsh schools and post-16 colleges either don't
+# admit by distance or fall outside the England postcode universe; selective
+# (grammar) schools admit by test from a wide region.
+STATE_SCHOOL_TYPE_GROUPS = [
+ "Academies",
+ "Local authority maintained schools",
+ "Free Schools",
+]
+
+# Preference bonuses (km of extra travel a family accepts for a better
+# school), applied as a discount on effective distance when children choose.
+# Grade 3/4 schools repel by the same magnitudes.
+PREF_BONUS_OUTSTANDING_KM = 0.6
+PREF_BONUS_GOOD_KM = 0.3
+
+# Share of resident children who actually compete for state places. Census
+# 2021 counts overstate current entry cohorts (birth rates fell ~10% between
+# 2016 and 2021, which is exactly the gap between the census stock and the
+# children reaching Reception by mid-decade) and independent/home-educated
+# children (~7%) never enter the allocation at all. Without this, modelled
+# cutoffs run systematically tight and undersubscribed schools look full.
+DEMAND_SCALE = 0.8
+
+# Logit choice temperature (km). With deterministic choice every child at a
+# postcode ranks the same school first, so popular schools fill entirely from
+# their nearest band and the marginal admitted child sits unrealistically
+# close. Real first preferences are heterogeneous; a school draws only a
+# distance-decaying share of nearby families. Children therefore split across
+# nearby feasible schools with weights softmax(-effective_distance / tau):
+# higher tau = more smearing = wider cutoffs. tau -> 0 recovers the
+# deterministic model (used by the unit tests). Calibrated 2026-06 against
+# 240 published binding cutoffs from 9 LAs (check_school_cutoffs.py): 0.3 km
+# maximises rank correlation and within-2x share; beyond ~0.6 the smearing
+# erases school-to-school differentiation (Spearman 0.24 -> 0.01).
+CHOICE_TEMPERATURE_KM = 0.3
+
+# Residual calibration from the same ground truth: after the equilibrium
+# solve, modelled cutoffs still ran systematically tight (median log2 bias
+# -0.53 primary / -0.36 secondary at the settings above — published "last
+# distance offered" reflects offer-day frictions, waiting-list churn and
+# furthest-applicant noise that no clean equilibrium reproduces). Radii are
+# multiplied by 2^-bias so the modelled median matches the published median;
+# rank ordering is unaffected.
+CUTOFF_CALIBRATION_FACTOR = {"primary": 1.44, "secondary": 1.28}
+
+# Each demand postcode considers this many nearest schools; beyond ~16
+# candidates assignment shares are negligible.
+NEAREST_SCHOOL_CANDIDATES = 16
+
+# Radius guard rails: the floor absorbs postcode-centroid noise around tiny
+# urban catchments; the cap bounds feasibility radii for schools the model
+# never fills (mostly rural).
+MIN_RADIUS_KM = 0.3
+MAX_RADIUS_KM = 25.0
+
+EQUILIBRIUM_MAX_ITER = 100
+
+
+def classify_good_plus_schools(
+ ofsted: pl.DataFrame, open_urns: set[int] | None = None
+) -> pl.DataFrame:
+ """Label good+/outstanding primary & secondary schools for catchment counts.
+
+ Derives a grade ("1" = outstanding, "2" = good) and one or two
+ ``category`` rows per school, returning a ``(urn, category)`` frame.
+
+ Schools with a recent GRADED inspection carry a 1-4 grade in "Latest OEIF
+ overall effectiveness" (OEIF = the previous Ofsted Education Inspection
+ Framework). A large and growing share of schools were last inspected under an
+ UNGRADED (Section 8) inspection or the post-2024 report-card framework, so
+ that column is null/"Not judged" for them even when they are demonstrably
+ good — their status lives in "Ungraded inspection overall outcome" ("School
+ remains Good"/"School remains Outstanding"). Filtering on the graded column
+ alone dropped ~7,000 genuinely good/outstanding schools. We fall back to the
+ ungraded outcome, but ONLY when there is no usable graded result
+ (null/"Not judged"), so a genuine grade 3/4 is never overridden.
+
+ Outcomes flagged "(Concerns)" are NOT treated as good+: a "remains Good
+ (Concerns)" outcome signals inspectors found issues warranting an earlier
+ graded re-inspection, so marketing it as a good+ school is misleading.
+
+ Phase assignment uses the statutory age range when available (so all-through
+ and middle schools count toward BOTH primary and secondary), falling back to
+ the coarse "Ofsted phase" label when age columns are absent. When
+ ``open_urns`` is given, schools whose URN is not in the current GIAS open
+ register are dropped so closed/merged schools are not counted.
+ """
+ graded = _with_derived_grade(ofsted).filter(
+ pl.col("Ofsted phase").is_in(["Primary", "Secondary"])
+ & pl.col("_ofsted_grade").is_in(["1", "2"])
+ )
+
+ # Drop schools no longer open (closed/merged) when the GIAS open register is
+ # provided, so stale Ofsted "latest inspection" rows are not counted.
+ if open_urns is not None and "URN" in graded.columns:
+ graded = graded.filter(pl.col("URN").is_in(list(open_urns)))
+
+ # Decide which phase(s) each school serves.
+ if {"Statutory lowest age", "Statutory highest age"} <= set(graded.columns):
+ low = pl.col("Statutory lowest age").cast(pl.Int64, strict=False)
+ high = pl.col("Statutory highest age").cast(pl.Int64, strict=False)
+ serves_primary = (
+ pl.when(low.is_not_null())
+ .then(low <= PRIMARY_MAX_AGE)
+ .otherwise(pl.col("Ofsted phase") == "Primary")
+ )
+ serves_secondary = (
+ pl.when(high.is_not_null())
+ .then(high >= SECONDARY_MIN_AGE)
+ .otherwise(pl.col("Ofsted phase") == "Secondary")
+ )
+ else:
+ serves_primary = pl.col("Ofsted phase") == "Primary"
+ serves_secondary = pl.col("Ofsted phase") == "Secondary"
+
+ graded = graded.with_columns(
+ serves_primary.alias("_serves_primary"),
+ serves_secondary.alias("_serves_secondary"),
+ )
+
+ # Good+ groups include both grade variants; outstanding groups count grade 1.
+ # A school can yield up to two rows (primary and secondary).
+ primary = graded.filter(pl.col("_serves_primary")).with_columns(
+ pl.when(pl.col("_ofsted_grade") == "1")
+ .then(pl.lit("outstanding_primary"))
+ .otherwise(pl.lit("good_primary"))
+ .alias("category")
+ )
+ secondary = graded.filter(pl.col("_serves_secondary")).with_columns(
+ pl.when(pl.col("_ofsted_grade") == "1")
+ .then(pl.lit("outstanding_secondary"))
+ .otherwise(pl.lit("good_secondary"))
+ .alias("category")
+ )
+ return pl.concat([primary, secondary]).select(
+ pl.col("URN").cast(pl.Int64).alias("urn"),
+ "category",
+ )
+
+
+def _with_derived_grade(ofsted: pl.DataFrame) -> pl.DataFrame:
+ """Attach ``_ofsted_grade`` ("1"-"4" or null): graded OEIF result first,
+ falling back to ungraded "School remains Good/Outstanding" outcomes (minus
+ "(Concerns)") only when there is no usable graded result."""
+ # Cast to Utf8 so the string predicates below are well-defined even if a
+ # column happens to be entirely null (read back as a Null dtype).
+ oeif = pl.col("Latest OEIF overall effectiveness").cast(pl.Utf8, strict=False)
+ ungraded = pl.col("Ungraded inspection overall outcome").cast(pl.Utf8, strict=False)
+ no_usable_grade = oeif.is_null() | (oeif == "Not judged")
+ has_concern = ungraded.str.contains(r"\(Concerns\)")
+ remains_outstanding = (
+ ungraded.str.starts_with("School remains Outstanding") & ~has_concern
+ )
+ remains_good = ungraded.str.starts_with("School remains Good") & ~has_concern
+ return ofsted.with_columns(
+ pl.when(oeif.is_in(["1", "2", "3", "4"]))
+ .then(oeif)
+ .when(no_usable_grade & remains_outstanding)
+ .then(pl.lit("1"))
+ .when(no_usable_grade & remains_good)
+ .then(pl.lit("2"))
+ .otherwise(None)
+ .alias("_ofsted_grade")
+ )
+
+
+def school_preference_bonuses(
+ ofsted: pl.DataFrame,
+ bonus_outstanding_km: float = PREF_BONUS_OUTSTANDING_KM,
+ bonus_good_km: float = PREF_BONUS_GOOD_KM,
+) -> pl.DataFrame:
+ """Per-school preference bonus in km, from the derived Ofsted grade.
+
+ Outstanding/Good schools attract demand from further away; grade 3/4
+ schools repel it symmetrically. Ungraded (typically new) schools are
+ neutral. Returns ``(urn, bonus_km)`` with one row per URN.
+ """
+ bonus = {
+ "1": bonus_outstanding_km,
+ "2": bonus_good_km,
+ "3": -bonus_good_km,
+ "4": -bonus_outstanding_km,
+ }
+ return (
+ _with_derived_grade(ofsted)
+ .filter(pl.col("URN").is_not_null())
+ .select(
+ pl.col("URN").cast(pl.Int64).alias("urn"),
+ pl.col("_ofsted_grade")
+ .replace_strict(bonus, default=0.0, return_dtype=pl.Float64)
+ .alias("bonus_km"),
+ )
+ .unique(subset="urn", keep="first")
+ )
+
+
+def phase_intakes(gias: pl.DataFrame) -> pl.DataFrame:
+ """Per-school phase-prorated fill targets for the admissions model.
+
+ Returns one row per open, non-selective state-funded school with valid
+ coordinates: ``(urn, lat, lng, primary_intake, secondary_intake)``. The
+ fill target — max(capacity, headcount), so over-full schools keep their
+ demonstrated size and under-full schools can admit up to capacity — is
+ spread over the cohort ages the school teaches (parsed from ``age_range``,
+ e.g. "3–11" = ages 3..10) with nursery and sixth-form ages down-weighted,
+ and each phase receives the share of cohort weight in its age band.
+ """
+ ages = pl.col("age_range").str.extract_all(r"\d+")
+ low = ages.list.get(0, null_on_oob=True).cast(pl.Int64, strict=False)
+ # The leaving age is exclusive as a cohort: a "3-11" school teaches
+ # children aged 3 through 10.
+ high = ages.list.get(1, null_on_oob=True).cast(pl.Int64, strict=False) - 1
+
+ schools = (
+ gias.filter(
+ pl.col("type_group").is_in(STATE_SCHOOL_TYPE_GROUPS)
+ & (
+ pl.col("admissions_policy").is_null()
+ | (pl.col("admissions_policy") != "Selective")
+ )
+ & pl.col("lat").is_not_null()
+ & pl.col("lng").is_not_null()
+ )
+ .with_columns(low.alias("_low"), high.alias("_high"))
+ .filter(pl.col("_low").is_not_null() & (pl.col("_high") >= pl.col("_low")))
+ .with_columns(
+ pl.max_horizontal(
+ pl.col("pupils").fill_null(0), pl.col("capacity").fill_null(0)
+ )
+ .cast(pl.Float64)
+ .alias("_fill_target"),
+ )
+ .filter(pl.col("_fill_target") > 0)
+ )
+
+ def weighted_overlap(lo: int, hi: int, weight: float = 1.0) -> pl.Expr:
+ """Cohort weight contributed by ages [lo, hi] within [_low, _high]."""
+ return (
+ weight
+ * (
+ pl.min_horizontal(pl.col("_high"), hi)
+ - pl.max_horizontal(pl.col("_low"), lo)
+ + 1
+ ).clip(lower_bound=0)
+ ).cast(pl.Float64)
+
+ total_weight = (
+ weighted_overlap(0, 3, NURSERY_COHORT_WEIGHT)
+ + weighted_overlap(4, 15)
+ + weighted_overlap(16, 30, SIXTH_FORM_COHORT_WEIGHT)
+ )
+ return schools.select(
+ pl.col("urn").cast(pl.Int64),
+ "lat",
+ "lng",
+ (pl.col("_fill_target") * weighted_overlap(*PRIMARY_AGES) / total_weight).alias(
+ "primary_intake"
+ ),
+ (
+ pl.col("_fill_target") * weighted_overlap(*SECONDARY_AGES) / total_weight
+ ).alias("secondary_intake"),
+ )
+
+
+def children_per_postcode(
+ postcodes: pl.DataFrame, lsoa_children: pl.DataFrame
+) -> pl.DataFrame:
+ """Estimate phase-age children living at each live postcode.
+
+ Census age bands don't align with school phases, so phase totals take
+ fractional shares of bands (one fifth per single year of age): primary
+ (4-10) = age 4 + ages 5-9 + age 10, secondary (11-15) = ages 11-14 +
+ age 15. LSOA totals are then split evenly across the LSOA's postcodes.
+ """
+ lsoa = lsoa_children.select(
+ "lsoa21",
+ (
+ 0.2 * pl.col("aged_0_4") + pl.col("aged_5_9") + 0.2 * pl.col("aged_10_14")
+ ).alias("_lsoa_primary"),
+ (0.8 * pl.col("aged_10_14") + 0.2 * pl.col("aged_15_19")).alias(
+ "_lsoa_secondary"
+ ),
+ )
+ return (
+ postcodes.join(lsoa, left_on="lsoa21cd", right_on="lsoa21", how="inner")
+ .with_columns(pl.len().over("lsoa21cd").alias("_lsoa_postcodes"))
+ .select(
+ "postcode",
+ "lat",
+ "lng",
+ (pl.col("_lsoa_primary") / pl.col("_lsoa_postcodes")).alias(
+ "primary_children"
+ ),
+ (pl.col("_lsoa_secondary") / pl.col("_lsoa_postcodes")).alias(
+ "secondary_children"
+ ),
+ )
+ )
+
+
+def equilibrium_cutoffs(
+ school_xy: np.ndarray,
+ fill_target: np.ndarray,
+ bonus_km: np.ndarray,
+ pc_xy: np.ndarray,
+ pc_children: np.ndarray,
+ k: int = NEAREST_SCHOOL_CANDIDATES,
+ max_iter: int = EQUILIBRIUM_MAX_ITER,
+ tau_km: float = CHOICE_TEMPERATURE_KM,
+) -> np.ndarray:
+ """Market-clearing admission cutoff distance (km) per school.
+
+ Deferred acceptance with distance priority, solved as cutoff dynamics
+ (Azevedo & Leshno): cutoffs start unbounded; each round every child unit
+ applies to its preferred feasible school(s) — a logit split over
+ effective distance (distance - school bonus) among schools whose cutoff
+ covers it, collapsing to the single best school when ``tau_km`` is 0 —
+ and each oversubscribed school tightens its cutoff to its marginal
+ admitted child's distance. Cutoffs only ever tighten, so the iteration
+ converges.
+
+ Returns np.inf for schools that never fill (no binding cutoff).
+ """
+ n_schools = len(school_xy)
+ k = min(k, n_schools)
+ demand = np.flatnonzero(pc_children > 0)
+ weights = pc_children[demand]
+ tree = cKDTree(school_xy)
+ dist, cand = tree.query(pc_xy[demand], k=k, workers=-1)
+ if k == 1:
+ dist = dist[:, None]
+ cand = cand[:, None]
+ eff = dist - bonus_km[cand]
+
+ rows = np.arange(len(demand))
+ cutoff = np.full(n_schools, np.inf)
+ for _ in range(max_iter):
+ eff_feasible = np.where(dist <= cutoff[cand], eff, np.inf)
+ if tau_km <= 0:
+ choice = np.argmin(eff_feasible, axis=1)
+ valid = np.isfinite(eff_feasible[rows, choice])
+ chosen_school = cand[rows[valid], choice[valid]]
+ chosen_dist = dist[rows[valid], choice[valid]]
+ chosen_mass = weights[valid]
+ else:
+ z = -eff_feasible / tau_km
+ z_max = z.max(axis=1, keepdims=True)
+ share = np.exp(z - np.where(np.isfinite(z_max), z_max, 0.0))
+ share[~np.isfinite(eff_feasible)] = 0.0
+ total = share.sum(axis=1, keepdims=True)
+ mass = weights[:, None] * share / np.where(total > 0, total, 1.0)
+ # Sub-thousandth-of-a-child applications only slow the sort down.
+ keep = mass > 1e-3
+ chosen_school = cand[keep]
+ chosen_dist = dist[keep]
+ chosen_mass = mass[keep]
+
+ order = np.lexsort((chosen_dist, chosen_school))
+ s_sorted = chosen_school[order]
+ d_sorted = chosen_dist[order]
+ m_cum = np.cumsum(chosen_mass[order])
+ boundaries = np.flatnonzero(np.diff(s_sorted)) + 1
+ starts = np.concatenate(([0], boundaries))
+ ends = np.concatenate((boundaries, [len(s_sorted)]))
+
+ changed = False
+ for start, end in zip(starts, ends):
+ school = s_sorted[start]
+ seg_cum = m_cum[start:end] - (m_cum[start - 1] if start else 0.0)
+ if seg_cum[-1] <= fill_target[school]:
+ continue
+ marginal = d_sorted[start + np.searchsorted(seg_cum, fill_target[school])]
+ if marginal < cutoff[school]:
+ cutoff[school] = marginal
+ changed = True
+ if not changed:
+ break
+
+ return cutoff
+
+
+def capacity_fill_radii(
+ school_xy: np.ndarray,
+ fill_target: np.ndarray,
+ pc_xy: np.ndarray,
+ pc_children: np.ndarray,
+ max_radius_km: float = MAX_RADIUS_KM,
+) -> np.ndarray:
+ """Feasibility radius for schools without a binding cutoff.
+
+ An undersubscribed school admits anyone who applies, so its catchment is
+ bounded by plausibility rather than competition: the distance within
+ which the local child population would cover its fill target. Capped at
+ ``max_radius_km``.
+ """
+ demand = np.flatnonzero(pc_children > 0)
+ tree = cKDTree(pc_xy[demand])
+ radii = np.full(len(school_xy), max_radius_km)
+ k = min(4096, len(demand))
+ for i in range(len(school_xy)):
+ dists, idx = tree.query(
+ school_xy[i], k=k, distance_upper_bound=max_radius_km
+ )
+ found = np.isfinite(dists)
+ cum = np.cumsum(pc_children[demand[idx[found]]])
+ if len(cum) and cum[-1] >= fill_target[i]:
+ radii[i] = dists[found][np.searchsorted(cum, fill_target[i])]
+ return radii
+
+
+def count_covering_catchments(
+ pc_xy: np.ndarray,
+ pc_valid: np.ndarray,
+ school_xy: np.ndarray,
+ school_radii: np.ndarray,
+ n_postcodes: int,
+) -> np.ndarray:
+ """Count, per postcode, how many schools' catchment radii cover it."""
+ counts = np.zeros(n_postcodes, dtype=np.int32)
+ if len(school_xy) == 0:
+ return counts
+ valid_indices = np.flatnonzero(pc_valid)
+ tree = cKDTree(pc_xy[valid_indices])
+ covered = np.zeros(len(valid_indices), dtype=np.int32)
+ for indices in tree.query_ball_point(school_xy, school_radii, workers=-1):
+ covered[indices] += 1
+ counts[valid_indices] = covered
+ return counts
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=(
+ "Model school admission cutoff radii and count good+/outstanding "
+ "primary/secondary catchments covering each postcode"
+ )
+ )
+ parser.add_argument(
+ "--ofsted", type=Path, required=True, help="Ofsted inspection parquet"
+ )
+ parser.add_argument(
+ "--gias", type=Path, required=True, help="GIAS open-school parquet"
+ )
+ parser.add_argument(
+ "--arcgis", type=Path, required=True, help="ArcGIS postcode parquet"
+ )
+ parser.add_argument(
+ "--lsoa-children",
+ type=Path,
+ required=True,
+ help="Census 2021 children by LSOA parquet",
+ )
+ parser.add_argument(
+ "--output",
+ type=Path,
+ default=None,
+ help="Per-postcode counts parquet; omit for calibration runs that only "
+ "need --schools-output",
+ )
+ parser.add_argument(
+ "--schools-output",
+ type=Path,
+ default=None,
+ help="Optional per-school catchment radii parquet (for calibration/debugging)",
+ )
+ parser.add_argument(
+ "--bonus-outstanding-km",
+ type=float,
+ default=PREF_BONUS_OUTSTANDING_KM,
+ help="Preference bonus for Outstanding schools (calibration sweeps)",
+ )
+ parser.add_argument(
+ "--bonus-good-km",
+ type=float,
+ default=PREF_BONUS_GOOD_KM,
+ help="Preference bonus for Good schools (calibration sweeps)",
+ )
+ parser.add_argument(
+ "--demand-scale",
+ type=float,
+ default=DEMAND_SCALE,
+ help="Share of resident children competing for state places",
+ )
+ parser.add_argument(
+ "--choice-temperature-km",
+ type=float,
+ default=CHOICE_TEMPERATURE_KM,
+ help="Logit choice temperature over effective distance",
+ )
+ args = parser.parse_args()
+
+ gias = pl.read_parquet(args.gias)
+ open_urns = set(
+ gias.select(pl.col("urn").cast(pl.Int64, strict=False))
+ .to_series()
+ .drop_nulls()
+ .to_list()
+ )
+ print(f"GIAS open register: {len(open_urns):,} open school URNs")
+
+ ofsted = pl.read_parquet(args.ofsted)
+ rated = classify_good_plus_schools(ofsted, open_urns=open_urns)
+ if rated.is_empty():
+ raise ValueError("No good+ primary/secondary Ofsted schools found")
+ print(f"Good+ school/phase rows: {len(rated):,}")
+
+ supply = phase_intakes(gias).join(
+ school_preference_bonuses(
+ ofsted,
+ bonus_outstanding_km=args.bonus_outstanding_km,
+ bonus_good_km=args.bonus_good_km,
+ ),
+ on="urn",
+ how="left",
+ ).with_columns(pl.col("bonus_km").fill_null(0.0))
+ print(f"State schools in admissions model: {len(supply):,}")
+
+ arcgis = pl.read_parquet(args.arcgis).select(
+ pl.col("pcds").alias("postcode"),
+ "lat",
+ pl.col("long").alias("lng"),
+ "lsoa21cd",
+ "doterm",
+ )
+ live = arcgis.filter(
+ pl.col("doterm").is_null() & pl.col("lsoa21cd").str.starts_with("E")
+ )
+ demand = children_per_postcode(live, pl.read_parquet(args.lsoa_children))
+ print(
+ f"Demand postcodes: {len(demand):,} "
+ f"({demand['primary_children'].sum():,.0f} primary-age, "
+ f"{demand['secondary_children'].sum():,.0f} secondary-age children)"
+ )
+
+ # Shared local-km projection so assignment and coverage use one metric.
+ pc_lats = arcgis["lat"].to_numpy()
+ pc_lngs = arcgis["lng"].to_numpy()
+ pc_valid = valid_uk_coords_mask(pc_lats, pc_lngs)
+ origin_lat = float(np.mean(pc_lats[pc_valid]))
+ pc_xy = _project_lat_lng_km(pc_lats, pc_lngs, origin_lat)
+
+ demand_lats = demand["lat"].to_numpy()
+ demand_lngs = demand["lng"].to_numpy()
+ demand_valid = valid_uk_coords_mask(demand_lats, demand_lngs)
+ demand_xy = _project_lat_lng_km(demand_lats, demand_lngs, origin_lat)
+
+ school_xy = _project_lat_lng_km(
+ supply["lat"].to_numpy(), supply["lng"].to_numpy(), origin_lat
+ )
+
+ radii = {}
+ for phase in ("primary", "secondary"):
+ in_phase = supply[f"{phase}_intake"].to_numpy() > 0
+ targets = supply[f"{phase}_intake"].to_numpy()[in_phase]
+ xy = school_xy[in_phase]
+ children = np.where(
+ demand_valid,
+ demand[f"{phase}_children"].to_numpy() * args.demand_scale,
+ 0.0,
+ )
+ print(f"Solving {phase} admissions for {in_phase.sum():,} schools...")
+ cutoffs = equilibrium_cutoffs(
+ xy,
+ targets,
+ supply["bonus_km"].to_numpy()[in_phase],
+ demand_xy,
+ children,
+ tau_km=args.choice_temperature_km,
+ )
+ filled = np.isfinite(cutoffs)
+ print(
+ f" {filled.sum():,} schools have binding cutoffs "
+ f"(median {np.median(cutoffs[filled]):.2f} km); "
+ f"{(~filled).sum():,} undersubscribed"
+ )
+ fallback = capacity_fill_radii(
+ xy[~filled], targets[~filled], demand_xy, children
+ )
+ raw = cutoffs.copy()
+ raw[~filled] = fallback
+ radii[phase] = pl.DataFrame(
+ {
+ "urn": supply["urn"].to_numpy()[in_phase],
+ "phase": phase,
+ "cutoff_km": raw,
+ "filled": filled,
+ "radius_km": np.clip(
+ raw * CUTOFF_CALIBRATION_FACTOR[phase],
+ MIN_RADIUS_KM,
+ MAX_RADIUS_KM,
+ ),
+ }
+ )
+ print(
+ f" radius km: median {radii[phase]['radius_km'].median():.2f}, "
+ f"p90 {radii[phase]['radius_km'].quantile(0.9):.2f}"
+ )
+
+ # Attach each rated school's phase radius; rated schools outside the
+ # admissions model (special schools, selective schools, missing
+ # headcounts) cannot be given a defensible radius and are dropped.
+ rated = rated.with_columns(
+ pl.col("category").str.split("_").list.get(1).alias("phase")
+ )
+ rated_with_radius = rated.join(
+ pl.concat(list(radii.values())), on=["urn", "phase"], how="inner"
+ ).join(supply.select("urn", "lat", "lng"), on="urn", how="inner")
+ dropped = len(rated) - len(rated_with_radius)
+ print(
+ f"Rated school/phase rows with radii: {len(rated_with_radius):,} "
+ f"(dropped {dropped:,}, incl. selective schools)"
+ )
+
+ if args.output is None and args.schools_output is None:
+ raise SystemExit("Provide --output and/or --schools-output")
+
+ if args.output is not None:
+ category_counts = {}
+ for category in set(c for cats in SCHOOL_GROUPS.values() for c in cats):
+ cat = rated_with_radius.filter(pl.col("category") == category)
+ cat_xy = _project_lat_lng_km(
+ cat["lat"].to_numpy(), cat["lng"].to_numpy(), origin_lat
+ )
+ category_counts[category] = count_covering_catchments(
+ pc_xy, pc_valid, cat_xy, cat["radius_km"].to_numpy(), len(arcgis)
+ )
+ print(f" {category}: {len(cat):,} schools")
+
+ result = pl.DataFrame(
+ {
+ "postcode": arcgis["postcode"],
+ **{
+ f"{group}_catchments": sum(category_counts[c] for c in categories)
+ for group, categories in SCHOOL_GROUPS.items()
+ },
+ }
+ )
+ for group in SCHOOL_GROUPS:
+ col = result[f"{group}_catchments"]
+ print(f" {group}_catchments: mean {col.mean():.2f}, max {col.max()}")
+
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ result.write_parquet(args.output)
+ size_mb = args.output.stat().st_size / (1024 * 1024)
+ print(f"Wrote {args.output} ({size_mb:.1f} MB)")
+
+ if args.schools_output is not None:
+ schools_out = rated_with_radius.select(
+ "urn", "category", "phase", "cutoff_km", "filled", "radius_km", "lat", "lng"
+ )
+ args.schools_output.parent.mkdir(parents=True, exist_ok=True)
+ schools_out.write_parquet(args.schools_output)
+ print(f"Wrote {args.schools_output}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pipeline/transform/school_proximity.py b/pipeline/transform/school_proximity.py
deleted file mode 100644
index 49e6edf..0000000
--- a/pipeline/transform/school_proximity.py
+++ /dev/null
@@ -1,134 +0,0 @@
-"""Compute Ofsted-rated school proximity counts per postcode."""
-
-import argparse
-from pathlib import Path
-
-import polars as pl
-
-from pipeline.utils.poi_counts import count_pois_per_postcode
-
-SCHOOL_GROUPS = {
- "good_primary": ["good_primary", "outstanding_primary"],
- "good_secondary": ["good_secondary", "outstanding_secondary"],
- "outstanding_primary": ["outstanding_primary"],
- "outstanding_secondary": ["outstanding_secondary"],
-}
-
-
-def classify_good_plus_schools(ofsted: pl.DataFrame) -> pl.DataFrame:
- """Label good+/outstanding primary & secondary schools for proximity counts.
-
- Derives a grade ("1" = outstanding, "2" = good) and a proximity ``category``,
- returning a ``(postcode, category)`` frame.
-
- Schools with a recent GRADED inspection carry a 1-4 grade in "Latest OEIF
- overall effectiveness" (OEIF = the previous Ofsted Education Inspection
- Framework). A large and growing share of schools were last inspected under an
- UNGRADED (Section 8) inspection or the post-2024 report-card framework, so
- that column is null/"Not judged" for them even when they are demonstrably
- good — their status lives in "Ungraded inspection overall outcome" ("School
- remains Good"/"School remains Outstanding", incl. "(Concerns)"/"(Improving)"
- variants). Filtering on the graded column alone dropped ~7,000 genuinely
- good/outstanding schools. We fall back to the ungraded outcome, but ONLY when
- there is no usable graded result (null/"Not judged"), so a genuine grade 3/4
- is never overridden.
- """
- # Cast to Utf8 so the string predicates below are well-defined even if a
- # column happens to be entirely null (read back as a Null dtype).
- oeif = pl.col("Latest OEIF overall effectiveness").cast(pl.Utf8, strict=False)
- ungraded = pl.col("Ungraded inspection overall outcome").cast(pl.Utf8, strict=False)
- no_usable_grade = oeif.is_null() | (oeif == "Not judged")
- graded = (
- ofsted.filter(pl.col("Ofsted phase").is_in(["Primary", "Secondary"]))
- .with_columns(
- pl.when(oeif.is_in(["1", "2"]))
- .then(oeif)
- .when(
- no_usable_grade
- & ungraded.str.starts_with("School remains Outstanding")
- )
- .then(pl.lit("1"))
- .when(no_usable_grade & ungraded.str.starts_with("School remains Good"))
- .then(pl.lit("2"))
- .otherwise(None)
- .alias("_ofsted_grade")
- )
- .filter(pl.col("_ofsted_grade").is_not_null())
- )
- # Good+ groups include both grade variants; outstanding groups count grade 1.
- return graded.with_columns(
- pl.when(pl.col("Ofsted phase") == "Primary")
- .then(
- pl.when(pl.col("_ofsted_grade") == "1")
- .then(pl.lit("outstanding_primary"))
- .otherwise(pl.lit("good_primary"))
- )
- .otherwise(
- pl.when(pl.col("_ofsted_grade") == "1")
- .then(pl.lit("outstanding_secondary"))
- .otherwise(pl.lit("good_secondary"))
- )
- .alias("category")
- ).select(
- pl.col("Postcode").alias("postcode"),
- "category",
- )
-
-
-def main():
- parser = argparse.ArgumentParser(
- description="Count good+ and outstanding primary/secondary schools near each postcode"
- )
- parser.add_argument(
- "--ofsted", type=Path, required=True, help="Ofsted inspection parquet"
- )
- parser.add_argument(
- "--arcgis", type=Path, required=True, help="ArcGIS postcode parquet"
- )
- parser.add_argument(
- "--output", type=Path, required=True, help="Output parquet path"
- )
- args = parser.parse_args()
-
- ofsted = classify_good_plus_schools(pl.read_parquet(args.ofsted))
- if ofsted.is_empty():
- raise ValueError("No good+ primary/secondary Ofsted schools found")
-
- print(f"Good+ schools: {len(ofsted):,}")
- print(
- "Outstanding schools: "
- f"{ofsted.filter(pl.col('category').str.starts_with('outstanding')).height:,}"
- )
-
- # Join with arcgis to get lat/lng for each school's postcode
- arcgis = pl.read_parquet(args.arcgis).select(
- pl.col("pcds").alias("postcode"),
- "lat",
- pl.col("long").alias("lng"),
- )
-
- schools = ofsted.join(arcgis, on="postcode", how="inner")
- if schools.is_empty():
- raise ValueError("No Ofsted schools matched ArcGIS postcode coordinates")
- print(f"Schools with coordinates: {len(schools):,}")
-
- # Load all postcodes for proximity counting
- postcodes = arcgis.rename({"lng": "lon"})
-
- counts_5km = count_pois_per_postcode(
- postcodes, schools, radius_km=5, groups=SCHOOL_GROUPS
- )
- counts_2km = count_pois_per_postcode(
- postcodes, schools, radius_km=2, groups=SCHOOL_GROUPS
- )
-
- result = counts_5km.join(counts_2km, on="postcode")
-
- args.output.parent.mkdir(parents=True, exist_ok=True)
- result.write_parquet(args.output)
- size_mb = args.output.stat().st_size / (1024 * 1024)
- print(f"Wrote {args.output} ({size_mb:.1f} MB)")
-
-
-if __name__ == "__main__":
- main()
diff --git a/pipeline/transform/test_crime_spatial.py b/pipeline/transform/test_crime_spatial.py
index d368e77..0021242 100644
--- a/pipeline/transform/test_crime_spatial.py
+++ b/pipeline/transform/test_crime_spatial.py
@@ -252,14 +252,15 @@ def test_avg_yr_is_simple_mean_of_year_bars(tmp_path):
assert bars == {2023: pytest.approx(24.0, abs=0.05), 2024: pytest.approx(12.0, abs=0.05)}
-def test_serious_rollup_avg_yr_equals_mean_of_rollup_bars(tmp_path):
+def test_serious_rollup_avg_yr_equals_sum_of_components(tmp_path):
# Two SERIOUS types occur in DISJOINT years for one postcode: Burglary only in
# 2014, Robbery only in 2024 (each a single full month -> 12/yr). The headline
- # "Serious crime (avg/yr)" must equal the mean of the "Serious crime (by year)"
- # bars (which span the UNION of years any serious type occurred), NOT the sum
- # of the per-type means. Summing per-type means divides each type by its OWN
- # years-present (1 each) -> 12 + 12 = 24; the consistent rollup divides the
- # per-year serious total by the years any serious type occurred (2) -> 12.
+ # "Serious crime (avg/yr)" must equal the SUM of its component (avg/yr) columns
+ # (Burglary 12 + Robbery 12 = 24), so the rollup is always the sum of the parts
+ # shown beside it and can never fall below a single component. (The previous
+ # union-years-present mean would have divided the per-year serious total by the
+ # 2 years any serious type occurred, giving a misleading 12 that sits below
+ # both the burglary and robbery rollup contributions.)
units = tmp_path / "units"
_write_boundaries(
units, {"AB1": [_square_feature("AB1 1AA", 1000, 1000, 1010, 1010)]}
@@ -274,13 +275,16 @@ def test_serious_rollup_avg_yr_equals_mean_of_rollup_bars(tmp_path):
transform_crime_spatial(crime, units, output, by_year, buffer_m=50.0)
avg = pl.read_parquet(output).row(0, named=True)
- # The precomputed rollup headline exists and equals the mean of the bars (12),
- # not the sum of the per-type avg/yr values (Burglary 12 + Robbery 12 = 24).
assert "Serious crime (avg/yr)" in avg
assert avg["Burglary (avg/yr)"] == pytest.approx(12.0, abs=0.05)
assert avg["Robbery (avg/yr)"] == pytest.approx(12.0, abs=0.05)
- assert avg["Serious crime (avg/yr)"] == pytest.approx(12.0, abs=0.05)
+ # Rollup == sum of its component (avg/yr) columns.
+ assert avg["Serious crime (avg/yr)"] == pytest.approx(24.0, abs=0.05)
+ assert avg["Serious crime (avg/yr)"] == pytest.approx(
+ avg["Burglary (avg/yr)"] + avg["Robbery (avg/yr)"], abs=0.05
+ )
+ # The by-year rollup series remains the per-year sum of the component bars.
serious_bars = {
p["year"]: p["count"]
for p in pl.read_parquet(by_year).row(0, named=True)["Serious crime (by year)"]
@@ -289,8 +293,6 @@ def test_serious_rollup_avg_yr_equals_mean_of_rollup_bars(tmp_path):
2014: pytest.approx(12.0, abs=0.05),
2024: pytest.approx(12.0, abs=0.05),
}
- mean_of_bars = sum(serious_bars.values()) / len(serious_bars)
- assert avg["Serious crime (avg/yr)"] == pytest.approx(mean_of_bars, abs=0.05)
def test_avg_yr_denominator_is_per_postcode_not_global(tmp_path):
diff --git a/pipeline/transform/test_join_epc_pp.py b/pipeline/transform/test_join_epc_pp.py
index baa6cf2..b8d4dcd 100644
--- a/pipeline/transform/test_join_epc_pp.py
+++ b/pipeline/transform/test_join_epc_pp.py
@@ -8,6 +8,7 @@ import polars as pl
from pipeline.transform.join_epc_pp import (
EPC_SOURCE_COLUMNS,
+ _join_address_parts,
_run,
_scan_epc_certificates,
)
@@ -111,6 +112,89 @@ def test_scan_epc_certificates_supports_domestic_zip(tmp_path: Path):
assert df.schema["number_habitable_rooms"] == pl.Int16
+def test_join_address_parts_empty_string_components():
+ # Price-paid SAON/PAON/STREET are empty strings (not null) when absent;
+ # concat_str(ignore_nulls=True) alone leaked the separator into the
+ # display address (' 10 PALACE GREEN') and doubled it for empty middle
+ # components. Empty/whitespace-only parts must contribute nothing.
+ df = pl.DataFrame(
+ {
+ "saon": ["", "FLAT 1", "FLAT 1", "FLAT 21", "", None, " ", " FLAT 2"],
+ "paon": ["10", "10", "", "82", "", None, "10", "11 "],
+ "street": [
+ "PALACE GREEN",
+ "HIGH STREET",
+ "HIGH STREET",
+ "",
+ "",
+ None,
+ "PALACE GREEN",
+ "STATION ROAD",
+ ],
+ }
+ )
+ out = df.select(
+ _join_address_parts("saon", "paon", "street").alias("address")
+ ).get_column("address")
+
+ assert out.to_list() == [
+ "10 PALACE GREEN", # empty saon -> no leading space
+ "FLAT 1 10 HIGH STREET", # normal three-part address is unchanged
+ "FLAT 1 HIGH STREET", # empty middle component -> no double space
+ "FLAT 21 82", # empty street -> no trailing space
+ None, # all-empty -> null, not whitespace junk
+ None, # all-null -> null
+ "10 PALACE GREEN", # whitespace-only component treated as empty
+ "FLAT 2 11 STATION ROAD", # per-component padding is stripped
+ ]
+ # Invariant: every produced address is trimmed and single-spaced.
+ produced = out.drop_nulls()
+ assert produced.str.starts_with(" ").sum() == 0
+ assert produced.str.ends_with(" ").sum() == 0
+ assert produced.str.contains(" ", literal=True).sum() == 0
+
+
+def test_run_builds_clean_pp_address_from_empty_string_saon(tmp_path: Path):
+ # Real price-paid rows carry saon == "" (not null) on ~88% of rows; the
+ # published pp_address must not inherit a leading separator from it.
+ zip_path = tmp_path / "domestic-csv.zip"
+ with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
+ csv_buffer = io.StringIO()
+ writer = csv.DictWriter(csv_buffer, fieldnames=EPC_SOURCE_COLUMNS)
+ writer.writeheader()
+ writer.writerow(_row())
+ archive.writestr("certificates-2024.csv", csv_buffer.getvalue())
+
+ price_paid_path = tmp_path / "price-paid.parquet"
+ pl.DataFrame(
+ {
+ "price": [250_000],
+ "date_of_transfer": [date(2024, 2, 3)],
+ "property_type": ["T"],
+ "postcode": ["AA1 1AA"],
+ "paon": ["1"],
+ "saon": [""],
+ "street": ["Example Street"],
+ "locality": [""],
+ "town_city": ["Exampletown"],
+ "duration": ["F"],
+ "old_new": ["N"],
+ "ppd_category": ["A"],
+ }
+ ).write_parquet(price_paid_path)
+
+ output_path = tmp_path / "epc-pp.parquet"
+ _run(zip_path, price_paid_path, output_path, tmp_path)
+
+ df = pl.read_parquet(output_path)
+
+ assert df.height == 1
+ # No leading space, and the clean address still matches its EPC record.
+ assert df.select("pp_address", "epc_address").to_dicts() == [
+ {"pp_address": "1 Example Street", "epc_address": "1 Example Street"}
+ ]
+
+
def test_run_joins_domestic_zip_with_price_paid(tmp_path: Path):
zip_path = tmp_path / "domestic-csv.zip"
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as archive:
diff --git a/pipeline/transform/test_merge.py b/pipeline/transform/test_merge.py
index 3fe6369..d2dfd3d 100644
--- a/pipeline/transform/test_merge.py
+++ b/pipeline/transform/test_merge.py
@@ -34,6 +34,7 @@ from pipeline.transform.merge import (
_split_normal_outputs,
_tree_density_by_postcode,
_validate_lad_source_coverage,
+ _validate_lsoa_source_coverage,
_validate_postcode_feature_output,
_validate_property_postcodes,
)
@@ -297,13 +298,13 @@ def test_join_area_side_tables_does_not_fan_out_on_unique_keys() -> None:
joined = _join_area_side_tables(
base,
iod=pl.LazyFrame({"LSOA code (2021)": ["E01000001", "E01000002"]}),
- ethnicity=pl.LazyFrame({"Geography_code": ["E09000001", "E09000002"]}),
+ ethnicity=pl.LazyFrame({"lsoa21": ["E01000001", "E01000002"]}),
crime=crime,
median_age=pl.LazyFrame({"lsoa21": ["E01000001", "E01000002"]}),
election=pl.LazyFrame({"pcon": ["E14000001", "E14000002"]}),
poi_counts=_by_postcode({}),
noise=_by_postcode({}),
- school_proximity=_by_postcode({}),
+ school_catchments=_by_postcode({}),
conservation_areas=_by_postcode({CONSERVATION_AREA_FEATURE: ["Yes", "No"]}),
tree_density=None,
broadband=pl.LazyFrame(
@@ -355,13 +356,13 @@ def test_join_area_side_tables_normalizes_broadband_postcode_key() -> None:
joined = _join_area_side_tables(
base,
iod=pl.LazyFrame({"LSOA code (2021)": ["E01000001", "E01000002"]}),
- ethnicity=pl.LazyFrame({"Geography_code": ["E09000001", "E09000002"]}),
+ ethnicity=pl.LazyFrame({"lsoa21": ["E01000001", "E01000002"]}),
crime=crime,
median_age=pl.LazyFrame({"lsoa21": ["E01000001", "E01000002"]}),
election=pl.LazyFrame({"pcon": ["E14000001", "E14000002"]}),
poi_counts=_by_postcode({}),
noise=_by_postcode({}),
- school_proximity=_by_postcode({}),
+ school_catchments=_by_postcode({}),
conservation_areas=_by_postcode({CONSERVATION_AREA_FEATURE: ["Yes", "No"]}),
tree_density=None,
broadband=broadband,
@@ -531,7 +532,6 @@ def test_validate_lad_source_coverage_allows_only_known_rent_no_data_lads(
tmp_path,
) -> None:
iod_path = tmp_path / "iod.parquet"
- ethnicity_path = tmp_path / "ethnicity.parquet"
rental_path = tmp_path / "rental.parquet"
pl.DataFrame(
{
@@ -547,19 +547,15 @@ def test_validate_lad_source_coverage_allows_only_known_rent_no_data_lads(
],
}
).write_parquet(iod_path)
- pl.DataFrame(
- {"Geography_code": ["E08000016", "E06000053", "E09000001"]}
- ).write_parquet(ethnicity_path)
pl.DataFrame({"area_code": ["E08000016"], "bedrooms": [1]}).write_parquet(
rental_path
)
- _validate_lad_source_coverage(iod_path, ethnicity_path, rental_path)
+ _validate_lad_source_coverage(iod_path, rental_path)
def test_validate_lad_source_coverage_rejects_unexpected_rent_holes(tmp_path) -> None:
iod_path = tmp_path / "iod.parquet"
- ethnicity_path = tmp_path / "ethnicity.parquet"
rental_path = tmp_path / "rental.parquet"
pl.DataFrame(
{
@@ -567,13 +563,41 @@ def test_validate_lad_source_coverage_rejects_unexpected_rent_holes(tmp_path) ->
"Local Authority District name (2024)": ["Barnsley"],
}
).write_parquet(iod_path)
- pl.DataFrame({"Geography_code": ["E08000016"]}).write_parquet(ethnicity_path)
pl.DataFrame({"area_code": ["E08000019"], "bedrooms": [1]}).write_parquet(
rental_path
)
with pytest.raises(ValueError, match="Rental data is missing"):
- _validate_lad_source_coverage(iod_path, ethnicity_path, rental_path)
+ _validate_lad_source_coverage(iod_path, rental_path)
+
+
+def test_validate_lsoa_source_coverage_allows_full_ethnicity_coverage(
+ tmp_path,
+) -> None:
+ iod_path = tmp_path / "iod.parquet"
+ ethnicity_path = tmp_path / "ethnicity.parquet"
+ pl.DataFrame({"LSOA code (2021)": ["E01000001", "E01000002"]}).write_parquet(
+ iod_path
+ )
+ # Ethnicity may carry extra LSOAs (e.g. property-less ones); only the IoD
+ # LSOAs are required to all be present.
+ pl.DataFrame(
+ {"lsoa21": ["E01000001", "E01000002", "E01000003"]}
+ ).write_parquet(ethnicity_path)
+
+ _validate_lsoa_source_coverage(iod_path, ethnicity_path)
+
+
+def test_validate_lsoa_source_coverage_rejects_missing_lsoa(tmp_path) -> None:
+ iod_path = tmp_path / "iod.parquet"
+ ethnicity_path = tmp_path / "ethnicity.parquet"
+ pl.DataFrame({"LSOA code (2021)": ["E01000001", "E01000002"]}).write_parquet(
+ iod_path
+ )
+ pl.DataFrame({"lsoa21": ["E01000001"]}).write_parquet(ethnicity_path)
+
+ with pytest.raises(ValueError, match="Ethnicity data is missing LSOA coverage"):
+ _validate_lsoa_source_coverage(iod_path, ethnicity_path)
def test_tree_density_by_postcode_aliases_radius_percentile(tmp_path) -> None:
@@ -1027,13 +1051,13 @@ def test_join_area_side_tables_preserves_missing_crime_as_null() -> None:
joined = _join_area_side_tables(
base,
iod=pl.LazyFrame({"LSOA code (2021)": ["E01000001", "E01000002"]}),
- ethnicity=pl.LazyFrame({"Geography_code": ["E09000001", "E09000002"]}),
+ ethnicity=pl.LazyFrame({"lsoa21": ["E01000001", "E01000002"]}),
crime=crime,
median_age=pl.LazyFrame({"lsoa21": ["E01000001", "E01000002"]}),
election=pl.LazyFrame({"pcon": ["E14000001", "E14000002"]}),
poi_counts=_by_postcode({}),
noise=_by_postcode({}),
- school_proximity=_by_postcode({}),
+ school_catchments=_by_postcode({}),
conservation_areas=_by_postcode({CONSERVATION_AREA_FEATURE: ["Yes", "No"]}),
tree_density=None,
broadband=pl.LazyFrame(
@@ -1427,7 +1451,7 @@ def test_finalize_listings_promotes_overlay_columns_and_filters_to_listing_rows(
"Property type": ["Terraced", None],
"Leasehold/Freehold": ["Leasehold", None],
"Last known price": [500_000, None],
- "Street tree density percentile": [42.0, 42.0],
+ "Tree canopy density percentile": [42.0, 42.0],
# Overlay columns: row 0 is a matched listing, row 1 is unmatched, row none.
"_actual_listing_url": ["url0", "url1"],
"_actual_asking_price": [600_000, 700_000],
@@ -1458,7 +1482,7 @@ def test_finalize_listings_promotes_overlay_columns_and_filters_to_listing_rows(
"Property type": pl.Utf8,
"Leasehold/Freehold": pl.Utf8,
"Last known price": pl.Int64,
- "Street tree density percentile": pl.Float32,
+ "Tree canopy density percentile": pl.Float32,
"_actual_listing_url": pl.Utf8,
"_actual_asking_price": pl.Int64,
"_actual_asking_price_per_sqm": pl.Int32,
@@ -1496,7 +1520,7 @@ def test_finalize_listings_promotes_overlay_columns_and_filters_to_listing_rows(
assert finalized["Property type"].to_list() == ["Terraced", "Flats/Maisonettes"]
assert finalized["Leasehold/Freehold"].to_list() == ["Freehold", "Leasehold"]
# Postcode-level feature carried through to both matched and unmatched rows.
- assert finalized["Street tree density percentile"].to_list() == [42.0, 42.0]
+ assert finalized["Tree canopy density percentile"].to_list() == [42.0, 42.0]
# Match status reflects historical context availability.
assert finalized["Historical property match status"].to_list() == [
"matched",
@@ -1524,7 +1548,7 @@ def test_finalize_listings_dedupes_fanned_out_listing_rows() -> None:
"Property type": ["Terraced", "Terraced"],
"Leasehold/Freehold": ["Leasehold", "Leasehold"],
"Last known price": [500_000, 480_000],
- "Street tree density percentile": [42.0, 42.0],
+ "Tree canopy density percentile": [42.0, 42.0],
# Same listing URL on both collapsed rows — the fan-out to fix.
"_actual_listing_url": ["url0", "url0"],
"_actual_asking_price": [600_000, 600_000],
@@ -1555,7 +1579,7 @@ def test_finalize_listings_dedupes_fanned_out_listing_rows() -> None:
"Property type": pl.Utf8,
"Leasehold/Freehold": pl.Utf8,
"Last known price": pl.Int64,
- "Street tree density percentile": pl.Float32,
+ "Tree canopy density percentile": pl.Float32,
"_actual_listing_url": pl.Utf8,
"_actual_asking_price": pl.Int64,
"_actual_asking_price_per_sqm": pl.Int32,
diff --git a/pipeline/transform/test_poi_proximity.py b/pipeline/transform/test_poi_proximity.py
index 9ba233f..09d0025 100644
--- a/pipeline/transform/test_poi_proximity.py
+++ b/pipeline/transform/test_poi_proximity.py
@@ -1,9 +1,11 @@
import polars as pl
from pipeline.transform.poi_proximity import (
+ GREENSPACE_PARK_FUNCTIONS,
POI_GROUPS_2KM,
_build_poi_category_groups,
_dynamic_poi_metric_renames,
+ _greenspace_count_frame,
_groceries_categories,
)
from pipeline.utils.poi_counts import count_pois_per_postcode
@@ -88,3 +90,84 @@ def test_dynamic_poi_metric_renames_support_park_count_options() -> None:
"parks_2km": "Number of amenities (Park) within 2km",
"parks_5km": "Number of amenities (Park) within 5km",
}
+
+
+def test_groceries_categories_exclude_speciality_food_retail() -> None:
+ """The static groceries metric must not count bakeries/butchers/delis/
+ off-licences (speciality retail, ~a third of the group), while keeping
+ Supermarket, Convenience Store, Greengrocer and GEOLYTIX brands."""
+ pois = pl.DataFrame(
+ {
+ "category": [
+ "Tesco",
+ "Supermarket",
+ "Convenience Store",
+ "Greengrocer",
+ "Bakery",
+ "Butcher & Fishmonger",
+ "Deli & Specialty",
+ "Off-Licence",
+ "Café",
+ ],
+ "group": ["Groceries"] * 8 + ["Leisure"],
+ "lat": [51.5] * 9,
+ "lng": [-0.1] * 9,
+ }
+ )
+
+ assert _groceries_categories(pois) == [
+ "Convenience Store",
+ "Greengrocer",
+ "Supermarket",
+ "Tesco",
+ ]
+
+
+def test_park_group_excludes_playgrounds_and_play_space() -> None:
+ # "Play Space" (playgrounds) must not count as a Park; Public Park Or
+ # Garden and Playing Field (open recreation grounds) are in scope.
+ assert GREENSPACE_PARK_FUNCTIONS == {
+ "parks": ["Public Park Or Garden", "Playing Field"]
+ }
+
+
+def test_greenspace_count_frame_collapses_to_one_row_per_site() -> None:
+ # Three gates of one park (with a site centroid), one gate of another park
+ # without a centroid, and one centroid-fallback row with a null site_id.
+ greenspace = pl.DataFrame(
+ {
+ "lat": [51.50, 51.51, 51.52, 53.0, 54.0],
+ "lng": [-0.10, -0.11, -0.12, -2.0, -3.0],
+ "category": ["Public Park Or Garden"] * 3
+ + ["Playing Field", "Public Park Or Garden"],
+ "site_id": ["site-a", "site-a", "site-a", "site-b", None],
+ "site_lat": [51.505, 51.505, 51.505, None, None],
+ "site_lng": [-0.105, -0.105, -0.105, None, None],
+ }
+ )
+
+ result = _greenspace_count_frame(greenspace).sort("lat")
+
+ # One row per site (site-a collapses 3 → 1), null-site rows preserved.
+ assert result.height == 3
+ site_a = result.filter(pl.col("site_id") == "site-a")
+ # The representative point is the site centroid…
+ assert site_a["lat"].to_list() == [51.505]
+ assert site_a["lng"].to_list() == [-0.105]
+ # …or the first access point when no centroid is available.
+ site_b = result.filter(pl.col("site_id") == "site-b")
+ assert site_b["lat"].to_list() == [53.0]
+
+
+def test_greenspace_count_frame_passes_legacy_parquet_through() -> None:
+ # The shipped parquet predates the site_id column; counting must not crash
+ # (it keeps the old access-point grain until regenerated).
+ legacy = pl.DataFrame(
+ {
+ "lat": [51.50, 51.51],
+ "lng": [-0.10, -0.11],
+ "category": ["Public Park Or Garden", "Play Space"],
+ }
+ )
+
+ assert _greenspace_count_frame(legacy).equals(legacy)
diff --git a/pipeline/transform/test_school_catchments.py b/pipeline/transform/test_school_catchments.py
new file mode 100644
index 0000000..b10fc34
--- /dev/null
+++ b/pipeline/transform/test_school_catchments.py
@@ -0,0 +1,354 @@
+import numpy as np
+import polars as pl
+
+from pipeline.transform.school_catchments import (
+ capacity_fill_radii,
+ children_per_postcode,
+ classify_good_plus_schools,
+ count_covering_catchments,
+ equilibrium_cutoffs,
+ phase_intakes,
+ school_preference_bonuses,
+)
+
+
+def _school(phase, oeif, ungraded, urn=100000):
+ return {
+ "URN": urn,
+ "Postcode": "AA1 1AA",
+ "Ofsted phase": phase,
+ "Latest OEIF overall effectiveness": oeif,
+ "Ungraded inspection overall outcome": ungraded,
+ }
+
+
+def _classify(rows):
+ result = classify_good_plus_schools(pl.DataFrame(rows))
+ return {(r["urn"], r["category"]) for r in result.to_dicts()}
+
+
+def test_legacy_oeif_grades_1_and_2_are_kept():
+ rows = [
+ _school("Primary", "1", None, 1),
+ _school("Primary", "2", None, 2),
+ _school("Secondary", "1", None, 3),
+ _school("Secondary", "2", None, 4),
+ ]
+ assert _classify(rows) == {
+ (1, "outstanding_primary"),
+ (2, "good_primary"),
+ (3, "outstanding_secondary"),
+ (4, "good_secondary"),
+ }
+
+
+def test_grades_3_and_4_are_excluded():
+ rows = [_school("Primary", "3", None), _school("Primary", "4", None)]
+ assert _classify(rows) == set()
+
+
+def test_ungraded_remains_good_is_recovered_when_no_graded_result():
+ # Null and "Not judged" OEIF fall back to the ungraded outcome.
+ rows = [
+ _school("Primary", None, "School remains Good", 1),
+ _school("Secondary", "Not judged", "School remains Outstanding", 2),
+ # "(Improving)" is still good+ ...
+ _school("Primary", None, "School remains Good (Improving) - S5 Next", 3),
+ ]
+ assert _classify(rows) == {
+ (1, "good_primary"),
+ (2, "outstanding_secondary"),
+ (3, "good_primary"),
+ }
+
+
+def test_ungraded_concerns_are_not_good_plus():
+ # "(Concerns)" outcomes signal issues warranting earlier re-inspection and
+ # must NOT be counted as good+ schools.
+ rows = [
+ _school("Primary", None, "School remains Good (Concerns) - S5 Next", 1),
+ _school(
+ "Secondary",
+ None,
+ "School remains Outstanding (Concerns) - S5 Next",
+ 2,
+ ),
+ ]
+ assert _classify(rows) == set()
+
+
+def test_ungraded_non_good_outcomes_are_excluded():
+ rows = [
+ _school("Primary", None, "Some aspects not as strong"),
+ _school("Primary", None, "Standards maintained"),
+ _school("Primary", None, None),
+ ]
+ assert _classify(rows) == set()
+
+
+def test_genuine_grade_3_is_not_overridden_by_stale_remains_good():
+ # A real grade 3 must not be promoted by an ungraded "remains Good".
+ rows = [_school("Primary", "3", "School remains Good")]
+ assert _classify(rows) == set()
+
+
+def test_non_primary_secondary_phases_excluded():
+ rows = [
+ _school("Nursery", "1", None),
+ _school("Not applicable", "2", None),
+ ]
+ assert _classify(rows) == set()
+
+
+def _aged_school(phase, oeif, low, high, urn=100000):
+ return {
+ "URN": urn,
+ "Postcode": "AA1 1AA",
+ "Ofsted phase": phase,
+ "Latest OEIF overall effectiveness": oeif,
+ "Ungraded inspection overall outcome": None,
+ "Statutory lowest age": low,
+ "Statutory highest age": high,
+ }
+
+
+def test_all_through_school_counts_toward_both_primary_and_secondary():
+ # An all-through school (age 3-18) is labelled "Secondary" by Ofsted phase but
+ # serves primary-age children too, so it must count in BOTH metrics.
+ rows = [_aged_school("Secondary", "2", 3, 18, 1)]
+ assert _classify(rows) == {
+ (1, "good_primary"),
+ (1, "good_secondary"),
+ }
+
+
+def test_age_ranges_assign_single_phase_for_standard_schools():
+ rows = [
+ _aged_school("Primary", "1", 4, 11, 1), # primary only
+ _aged_school("Secondary", "2", 11, 16, 2), # secondary only
+ _aged_school("Secondary", "1", 9, 13, 3), # middle -> both
+ ]
+ assert _classify(rows) == {
+ (1, "outstanding_primary"),
+ (2, "good_secondary"),
+ (3, "outstanding_primary"),
+ (3, "outstanding_secondary"),
+ }
+
+
+def test_closed_schools_excluded_when_open_register_given():
+ rows = [
+ _aged_school("Primary", "1", 4, 11, 111),
+ _aged_school("Secondary", "2", 11, 16, 222),
+ ]
+ result = classify_good_plus_schools(pl.DataFrame(rows), open_urns={111})
+ pairs = {(r["urn"], r["category"]) for r in result.to_dicts()}
+ # URN 222 is not in the open register, so it is dropped.
+ assert pairs == {(111, "outstanding_primary")}
+
+
+def _gias_row(
+ urn,
+ type_group="Academies",
+ age_range="4–11",
+ pupils=210,
+ capacity=None,
+ admissions_policy=None,
+):
+ return {
+ "urn": urn,
+ "name": f"School {urn}",
+ "lat": 51.5,
+ "lng": -0.1,
+ "type_group": type_group,
+ "age_range": age_range,
+ "pupils": pupils,
+ "capacity": capacity,
+ "admissions_policy": admissions_policy,
+ }
+
+
+def test_phase_intakes_prorates_fill_target_over_weighted_cohorts():
+ intakes = phase_intakes(
+ pl.DataFrame(
+ [
+ # 4-11 = cohorts 4..10, all 7 primary: full fill target.
+ _gias_row(1, age_range="4–11", pupils=210),
+ # 11-16 = cohorts 11..15, all 5 secondary.
+ _gias_row(2, age_range="11–16", pupils=500),
+ # 3-11 = cohorts 3..10; nursery year weighs 0.5, so primary
+ # gets 7 of 7.5 cohort weights.
+ _gias_row(3, age_range="3–11", pupils=240),
+ # All-through 4-16 = cohorts 4..15: 7/12 primary, 5/12 secondary.
+ _gias_row(4, age_range="4–16", pupils=1200),
+ # 11-18 = cohorts 11..17; sixth-form years weigh 0.6 each, so
+ # secondary gets 5 of 6.2 cohort weights.
+ _gias_row(5, age_range="11–18", pupils=1240),
+ ]
+ )
+ ).sort("urn")
+ assert intakes["primary_intake"].to_list() == [210.0, 0.0, 224.0, 700.0, 0.0]
+ assert intakes["secondary_intake"].to_list() == [0.0, 500.0, 0.0, 500.0, 1000.0]
+
+
+def test_phase_intakes_excludes_non_state_and_selective_schools():
+ intakes = phase_intakes(
+ pl.DataFrame(
+ [
+ _gias_row(1, type_group="Independent schools"),
+ _gias_row(2, type_group="Special schools"),
+ _gias_row(3, type_group="Welsh schools"),
+ # Grammar school intakes are test-based and region-wide; a
+ # distance catchment would be fabricated.
+ _gias_row(4, admissions_policy="Selective"),
+ _gias_row(5, pupils=None, capacity=300),
+ _gias_row(6, pupils=None, capacity=None), # no usable headcount
+ _gias_row(7, age_range=None), # no parsable cohorts
+ # Over-full school keeps its demonstrated size.
+ _gias_row(8, pupils=350, capacity=300),
+ _gias_row(9, admissions_policy="Non-selective"),
+ ]
+ )
+ ).sort("urn")
+ assert intakes["urn"].to_list() == [5, 8, 9]
+ assert intakes["primary_intake"].to_list() == [300.0, 350.0, 210.0]
+
+
+def test_school_preference_bonuses_follow_derived_grade():
+ rows = [
+ {**_school("Primary", "1", None, 1)},
+ {**_school("Primary", "2", None, 2)},
+ {**_school("Primary", "3", None, 3)},
+ {**_school("Primary", "4", None, 4)},
+ {**_school("Primary", None, "Some aspects not as strong", 5)}, # unrated
+ {**_school("Primary", "Not judged", "School remains Good", 6)},
+ ]
+ bonuses = dict(
+ school_preference_bonuses(
+ pl.DataFrame(rows), bonus_outstanding_km=1.0, bonus_good_km=0.5
+ ).iter_rows()
+ )
+ assert bonuses == {1: 1.0, 2: 0.5, 3: -0.5, 4: -1.0, 5: 0.0, 6: 0.5}
+
+
+def test_children_per_postcode_prorates_bands_and_splits_lsoa_evenly():
+ postcodes = pl.DataFrame(
+ {
+ "postcode": ["AA1 1AA", "AA1 1AB", "BB2 2BB"],
+ "lat": [51.5, 51.5, 52.0],
+ "lng": [-0.1, -0.1, -0.2],
+ "lsoa21cd": ["E01000001", "E01000001", "E01000002"],
+ }
+ )
+ lsoa_children = pl.DataFrame(
+ {
+ "lsoa21": ["E01000001", "E01000002"],
+ "aged_0_4": [100, 30],
+ "aged_5_9": [100, 10],
+ "aged_10_14": [100, 20],
+ "aged_15_19": [100, 40],
+ }
+ )
+ result = children_per_postcode(postcodes, lsoa_children).sort("postcode")
+ # Primary 4-10 = 0.2*aged_0_4 + aged_5_9 + 0.2*aged_10_14: 140 split across
+ # the LSOA's 2 postcodes; 20 for the single-postcode LSOA.
+ assert result["primary_children"].to_list() == [70.0, 70.0, 20.0]
+ # Secondary 11-15 = 0.8*aged_10_14 + 0.2*aged_15_19: 100 split across 2; 24.
+ assert result["secondary_children"].to_list() == [50.0, 50.0, 24.0]
+
+
+def test_equilibrium_cutoff_tightens_to_marginal_admitted_distance():
+ # One school with 10 places; postcodes at 1km, 2km and 3km with 5 children
+ # each. The two nearest postcodes exactly fill it, so the cutoff is the
+ # marginal admitted child's distance and the 3km postcode is shut out.
+ cutoffs = equilibrium_cutoffs(
+ np.array([[0.0, 0.0]]),
+ np.array([10.0]),
+ np.array([0.0]),
+ np.array([[1.0, 0.0], [2.0, 0.0], [3.0, 0.0]]),
+ np.array([5.0, 5.0, 5.0]),
+ tau_km=0.0,
+ )
+ assert cutoffs.tolist() == [2.0]
+
+
+def test_equilibrium_rejected_demand_cascades_to_next_school():
+ # School A (5 places) at the origin, school B (5 places) at 10km.
+ # P1 (1km, 5 children) and P2 (1.5km, 5 children) both prefer A; A fills
+ # with P1 and tightens its cutoff to 1km, pushing P2 out to B. B never
+ # exceeds its target, so it keeps no binding cutoff.
+ cutoffs = equilibrium_cutoffs(
+ np.array([[0.0, 0.0], [10.0, 0.0]]),
+ np.array([5.0, 5.0]),
+ np.array([0.0, 0.0]),
+ np.array([[1.0, 0.0], [1.5, 0.0]]),
+ np.array([5.0, 5.0]),
+ tau_km=0.0,
+ )
+ assert cutoffs[0] == 1.0
+ assert np.isinf(cutoffs[1])
+
+
+def test_equilibrium_preference_bonus_steers_demand_to_better_school():
+ # Two schools equidistant from the only postcode; school A is rated
+ # better (0.5km bonus) so all children choose it; B attracts nobody.
+ cutoffs = equilibrium_cutoffs(
+ np.array([[0.0, 0.0], [2.0, 0.0]]),
+ np.array([5.0, 5.0]),
+ np.array([0.5, 0.0]),
+ np.array([[1.0, 0.0]]),
+ np.array([10.0]),
+ tau_km=0.0,
+ )
+ assert cutoffs[0] == 1.0
+ assert np.isinf(cutoffs[1])
+
+
+def test_equilibrium_logit_choice_smears_demand_across_schools():
+ # With a positive temperature some families prefer the further school, so
+ # both schools receive applications: the near school still fills and keeps
+ # a binding cutoff, and the far school now attracts mass it would never
+ # see under deterministic choice.
+ cutoffs = equilibrium_cutoffs(
+ np.array([[0.0, 0.0], [2.0, 0.0]]),
+ np.array([4.0, 4.0]),
+ np.array([0.0, 0.0]),
+ np.array([[1.0, 0.0]]),
+ np.array([10.0]),
+ tau_km=1.0,
+ )
+ # Each school gets half the 10 children (equidistant, equal utility),
+ # exceeding both fill targets: both cutoffs bind at the postcode.
+ assert cutoffs.tolist() == [1.0, 1.0]
+
+
+def test_capacity_fill_radii_covers_fill_target_population():
+ # Unfilled school needs 6 children: postcodes at 1km (5) and 2km (5)
+ # cumulate past the target at 2km. A school needing more children than
+ # exist within the cap keeps the cap.
+ radii = capacity_fill_radii(
+ np.array([[0.0, 0.0], [0.0, 0.0]]),
+ np.array([6.0, 1000.0]),
+ np.array([[1.0, 0.0], [2.0, 0.0], [3.0, 0.0]]),
+ np.array([5.0, 5.0, 5.0]),
+ max_radius_km=25.0,
+ )
+ assert radii.tolist() == [2.0, 25.0]
+
+
+def test_count_covering_catchments_respects_radius_and_validity():
+ pc_xy = np.array([[0.0, 0.0], [3.0, 0.0], [10.0, 0.0], [0.5, 0.0]])
+ pc_valid = np.array([True, True, True, False])
+ school_xy = np.array([[0.0, 0.0], [2.0, 0.0]])
+ radii = np.array([4.0, 1.5])
+ counts = count_covering_catchments(pc_xy, pc_valid, school_xy, radii, 4)
+ # pc0 is inside school 0 only (school 1 is 2km away > 1.5km radius);
+ # pc1 inside both; pc2 inside neither; pc3 invalid -> 0 despite proximity.
+ assert counts.tolist() == [1, 2, 0, 0]
+
+
+def test_count_covering_catchments_empty_schools():
+ counts = count_covering_catchments(
+ np.zeros((2, 2)), np.array([True, True]), np.empty((0, 2)), np.empty(0), 2
+ )
+ assert counts.tolist() == [0, 0]
diff --git a/pipeline/transform/test_school_proximity.py b/pipeline/transform/test_school_proximity.py
deleted file mode 100644
index 9d94b8d..0000000
--- a/pipeline/transform/test_school_proximity.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import polars as pl
-
-from pipeline.transform.school_proximity import classify_good_plus_schools
-
-
-def _school(phase, oeif, ungraded, postcode="AA1 1AA"):
- return {
- "Postcode": postcode,
- "Ofsted phase": phase,
- "Latest OEIF overall effectiveness": oeif,
- "Ungraded inspection overall outcome": ungraded,
- }
-
-
-def _classify(rows):
- result = classify_good_plus_schools(pl.DataFrame(rows))
- return {(r["postcode"], r["category"]) for r in result.to_dicts()}
-
-
-def test_legacy_oeif_grades_1_and_2_are_kept():
- rows = [
- _school("Primary", "1", None, "AA1 1AA"),
- _school("Primary", "2", None, "AA1 1AB"),
- _school("Secondary", "1", None, "AA1 1AC"),
- _school("Secondary", "2", None, "AA1 1AD"),
- ]
- assert _classify(rows) == {
- ("AA1 1AA", "outstanding_primary"),
- ("AA1 1AB", "good_primary"),
- ("AA1 1AC", "outstanding_secondary"),
- ("AA1 1AD", "good_secondary"),
- }
-
-
-def test_grades_3_and_4_are_excluded():
- rows = [_school("Primary", "3", None), _school("Primary", "4", None)]
- assert _classify(rows) == set()
-
-
-def test_ungraded_remains_good_is_recovered_when_no_graded_result():
- # Null and "Not judged" OEIF fall back to the ungraded outcome.
- rows = [
- _school("Primary", None, "School remains Good", "AA1 1AA"),
- _school("Secondary", "Not judged", "School remains Outstanding", "AA1 1AB"),
- # "(Concerns)"/"(Improving)" variants are still good+.
- _school("Primary", None, "School remains Good (Concerns) - S5 Next", "AA1 1AC"),
- _school(
- "Secondary",
- None,
- "School remains Outstanding (Concerns) - S5 Next",
- "AA1 1AD",
- ),
- ]
- assert _classify(rows) == {
- ("AA1 1AA", "good_primary"),
- ("AA1 1AB", "outstanding_secondary"),
- ("AA1 1AC", "good_primary"),
- ("AA1 1AD", "outstanding_secondary"),
- }
-
-
-def test_ungraded_non_good_outcomes_are_excluded():
- rows = [
- _school("Primary", None, "Some aspects not as strong"),
- _school("Primary", None, "Standards maintained"),
- _school("Primary", None, None),
- ]
- assert _classify(rows) == set()
-
-
-def test_genuine_grade_3_is_not_overridden_by_stale_remains_good():
- # A real grade 3 must not be promoted by an ungraded "remains Good".
- rows = [_school("Primary", "3", "School remains Good")]
- assert _classify(rows) == set()
-
-
-def test_non_primary_secondary_phases_excluded():
- rows = [
- _school("Nursery", "1", None),
- _school("Not applicable", "2", None),
- ]
- assert _classify(rows) == set()
diff --git a/pipeline/transform/test_transform_poi.py b/pipeline/transform/test_transform_poi.py
index 1648150..4599c8d 100644
--- a/pipeline/transform/test_transform_poi.py
+++ b/pipeline/transform/test_transform_poi.py
@@ -544,6 +544,142 @@ def test_transform_grocery_dedup_drops_only_grocery_aspect(tmp_path):
assert n2_grocery.height == 1
+def test_transform_drops_miscategorised_tags(tmp_path):
+ # Audit 2026-06-10: these tags polluted Entertainment (cycle-hire docks,
+ # slipways, marinas), Gallery (public artwork), Pharmacy (herbalists,
+ # alternative medicine), Hospital & Clinic (untyped healthcare/yes),
+ # Tourist Attraction (fountains, courthouses) and Gym & Fitness (outdoor
+ # apparatus). They must be dropped entirely.
+ dropped = [
+ "amenity/bicycle_rental",
+ "amenity/boat_rental",
+ "leisure/marina",
+ "leisure/slipway",
+ "tourism/artwork",
+ "healthcare/yes",
+ "healthcare/alternative",
+ "shop/herbalist",
+ "shop/health",
+ "amenity/fountain",
+ "amenity/courthouse",
+ "leisure/fitness_station",
+ ]
+ raw = pl.DataFrame(
+ {
+ "id": [f"n{i}" for i in range(len(dropped))],
+ "name": [f"POI {i}" for i in range(len(dropped))],
+ "category": dropped,
+ "lat": [51.50] * len(dropped),
+ "lng": [-0.10] * len(dropped),
+ }
+ )
+ inputs = _write_transform_inputs(tmp_path, raw)
+
+ out = transform(**inputs).collect()
+
+ assert out.filter(pl.col("id").is_in(raw["id"].to_list())).height == 0
+
+
+def test_transform_splits_hospital_and_clinic(tmp_path):
+ raw = pl.DataFrame(
+ {
+ "id": ["n1", "n2", "n3"],
+ "name": ["St Thomas'", "Vale Surgery Annexe", "Drop-in Centre"],
+ "category": [
+ "amenity/hospital",
+ "amenity/clinic",
+ "healthcare/clinic",
+ ],
+ "lat": [51.50, 51.51, 51.52],
+ "lng": [-0.10, -0.11, -0.12],
+ }
+ )
+ inputs = _write_transform_inputs(tmp_path, raw)
+
+ out = transform(**inputs).collect()
+
+ assert out.filter(pl.col("id") == "n1")["category"].to_list() == ["Hospital"]
+ assert out.filter(pl.col("id") == "n2")["category"].to_list() == ["Clinic"]
+ assert out.filter(pl.col("id") == "n3")["category"].to_list() == ["Clinic"]
+ assert "Hospital & Clinic" not in out["category"].to_list()
+
+
+def test_transform_maps_chalet_to_hotel(tmp_path):
+ # Holiday-let chalets are accommodation, not Tourist Attractions.
+ raw = pl.DataFrame(
+ {
+ "id": ["n1"],
+ "name": ["Seaview Chalet"],
+ "category": ["tourism/chalet"],
+ "lat": [51.50],
+ "lng": [-0.10],
+ }
+ )
+ inputs = _write_transform_inputs(tmp_path, raw)
+
+ out = transform(**inputs).collect()
+
+ assert out.filter(pl.col("id") == "n1")["category"].to_list() == ["Hotel"]
+
+
+def test_transform_name_gates_track_horse_riding_fishing(tmp_path):
+ # leisure/track, leisure/horse_riding and leisure/fishing are 83-84%
+ # unnamed (anonymous tracks/gallops/fishing spots); only named public
+ # facilities survive as a Sports Centre.
+ raw = pl.DataFrame(
+ {
+ "id": ["n1", "n2", "n3", "n4"],
+ "name": [None, "", "Herne Hill Velodrome", "Royal Mews Riding School"],
+ "category": [
+ "leisure/track",
+ "leisure/fishing",
+ "leisure/track",
+ "leisure/horse_riding",
+ ],
+ "lat": [51.50, 51.51, 51.52, 51.53],
+ "lng": [-0.10, -0.11, -0.12, -0.13],
+ }
+ )
+ inputs = _write_transform_inputs(tmp_path, raw)
+
+ out = transform(**inputs).collect()
+
+ assert out.filter(pl.col("id").is_in(["n1", "n2"])).height == 0
+ named = out.filter(pl.col("id").is_in(["n3", "n4"]))
+ assert named["category"].to_list() == ["Sports Centre", "Sports Centre"]
+
+
+def test_transform_passes_through_tram_metro_naptan_category(tmp_path):
+ # NaPTAN now emits "Tram & Metro stop" (non-LU TMU/MET networks); it must
+ # flow through with the Public Transport group and its own emoji.
+ raw = pl.DataFrame(
+ {
+ "id": ["n1"],
+ "name": ["A Cafe"],
+ "category": ["amenity/cafe"],
+ "lat": [51.50],
+ "lng": [-0.10],
+ }
+ )
+ inputs = _write_transform_inputs(tmp_path, raw)
+ pl.DataFrame(
+ {
+ "id": ["naptan-1", "naptan-2"],
+ "name": ["Test Rail Station", "Weaste"],
+ "category": ["Rail station", "Tram & Metro stop"],
+ "lat": [51.51, 51.52],
+ "lng": [-0.13, -0.14],
+ }
+ ).write_parquet(inputs["naptan_path"])
+
+ out = transform(**inputs).collect()
+
+ tram = out.filter(pl.col("category") == "Tram & Metro stop")
+ assert tram.height == 1
+ assert tram["group"].to_list() == ["Public Transport"]
+ assert tram["emoji"].to_list() == ["🚊"]
+
+
def test_transform_output_unique_per_id_category(tmp_path):
# Soundness: the full transform() output has at most one row per
# (id, category) overall, across every source.
diff --git a/pipeline/transform/transform_poi.py b/pipeline/transform/transform_poi.py
index 7abc6ea..157c548 100644
--- a/pipeline/transform/transform_poi.py
+++ b/pipeline/transform/transform_poi.py
@@ -33,6 +33,14 @@ DROP_CATEGORIES = {
"emergency/water_tank",
"leisure/bleachers",
"leisure/schoolyard",
+ # Park "furniture" / incidental features — not parks; they massively
+ # inflated the Park count (picnic_table ~15k, outdoor_seating ~5.8k).
+ "leisure/bandstand",
+ "leisure/bird_hide",
+ "leisure/firepit",
+ "leisure/outdoor_seating",
+ "leisure/picnic_table",
+ "leisure/wildlife_hide",
"public_transport/pay_scale_area",
"shop/taxi",
"amenity/feeding_place",
@@ -78,6 +86,28 @@ DROP_CATEGORIES = {
"amenity/water_point",
"amenity/watering_place",
"amenity/weighbridge",
+ # Boating/cycle-hire infrastructure formerly miscategorised as
+ # "Entertainment" (46% of the bucket): cycle-hire dock stations, boat
+ # ramps and moorings are not entertainment venues.
+ "amenity/bicycle_rental",
+ "amenity/boat_rental",
+ "leisure/marina",
+ "leisure/slipway",
+ # Public art (statues, murals, village signs) formerly 93% of "Gallery".
+ "tourism/artwork",
+ # Outdoor exercise apparatus (pull-up bars, trim trails) formerly inflating
+ # "Gym & Fitness".
+ "leisure/fitness_station",
+ # Untyped healthcare rows and non-pharmacy health shops formerly bucketed
+ # under "Hospital & Clinic" / "Pharmacy".
+ "healthcare/yes",
+ "healthcare/alternative",
+ "shop/herbalist",
+ "shop/health",
+ # Street fountains and courthouses formerly bucketed as
+ # "Tourist Attraction".
+ "amenity/fountain",
+ "amenity/courthouse",
# Niche amenities not useful for home buyers
"amenity/animal_boarding",
"amenity/animal_breeding",
@@ -182,9 +212,13 @@ DROP_CATEGORIES = {
"tourism/village_sign",
"tourism/wilderness_hut",
"tourism/yes",
- # Public transport (from NaPTAN instead)
+ # Public transport (from NaPTAN instead). public_transport/platform is the
+ # EXCEPTION: it is mapped to "Bus stop" (see _CATEGORIES) to fill NaPTAN's
+ # authority-level bus-stop gaps (e.g. West Cumbria, North Norfolk, where
+ # NaPTAN has zero stops), then deduped against NaPTAN so covered areas keep
+ # a single stop. stop_position is left dropped to avoid double-counting the
+ # same stop (platform + stop_position).
"public_transport/entrance",
- "public_transport/platform",
"public_transport/station",
"public_transport/stop_position",
# Education amenities — schools come from GIAS instead. OSM coverage for
@@ -301,16 +335,13 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
"🌳",
[
"leisure/park",
+ # leisure/garden is dominated by private residential gardens (98%+
+ # unnamed); it is name-gated in transform() via REQUIRE_NAME_CATEGORIES
+ # so only named (public/notable) gardens count as a Park.
"leisure/garden",
"leisure/common",
"leisure/nature_reserve",
"leisure/dog_park",
- "leisure/bandstand",
- "leisure/bird_hide",
- "leisure/firepit",
- "leisure/outdoor_seating",
- "leisure/picnic_table",
- "leisure/wildlife_hide",
],
),
(
@@ -329,6 +360,9 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
[
"leisure/sports_centre",
"leisure/sports_hall",
+ # leisure/pitch (73% of the old bucket) and leisure/swimming_pool
+ # (98% unnamed = private/garden pools) are name-gated in transform()
+ # via REQUIRE_NAME_CATEGORIES so only named public facilities count.
"leisure/pitch",
"leisure/track",
"leisure/golf_course",
@@ -361,10 +395,9 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
"leisure/tanning_salon",
"shop/amusements",
"tourism/theme_park",
- "amenity/bicycle_rental",
- "amenity/boat_rental",
- "leisure/marina",
- "leisure/slipway",
+ # bicycle_rental/boat_rental/marina/slipway used to live here and
+ # made up ~46% of the bucket (cycle-hire docks, boat ramps); they
+ # are infrastructure, not entertainment venues — see DROP_CATEGORIES.
"leisure/hackerspace",
"leisure/yes",
],
@@ -687,7 +720,8 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
"🏋️",
[
"leisure/fitness_centre",
- "leisure/fitness_station",
+ # leisure/fitness_station (outdoor pull-up bars / trim-trail
+ # apparatus, ~2.5k) is not a gym — see DROP_CATEGORIES.
"amenity/dojo",
"amenity/dancing_school",
],
@@ -813,28 +847,37 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
"amenity/pharmacy",
"healthcare/pharmacy",
"shop/chemist",
- "shop/herbalist",
- "shop/health",
- "healthcare/alternative",
+ # healthcare/alternative, shop/herbalist and shop/health (homeopaths,
+ # herbalists, generic "health" shops) are not dispensing pharmacies
+ # — see DROP_CATEGORIES.
+ ],
+ ),
+ # "Hospital & Clinic" used to be one bucket; an actual hospital and a small
+ # clinic are very different amenities for a homebuyer, so they are split.
+ (
+ "Health",
+ "Hospital",
+ "🏥",
+ [
+ "amenity/hospital",
+ "healthcare/hospital",
],
),
(
"Health",
- "Hospital & Clinic",
- "🏥",
+ "Clinic",
+ "🩺",
[
- "amenity/hospital",
"amenity/clinic",
"amenity/health_centre",
"healthcare/blood_donation",
- "healthcare/hospital",
"healthcare/centre",
"healthcare/clinic",
"office/healthcare",
"healthcare/laboratory",
"healthcare/rehabilitation",
"healthcare/vaccination_centre",
- "healthcare/yes",
+ # healthcare/yes (untyped junk rows) is dropped — see DROP_CATEGORIES.
],
),
(
@@ -905,7 +948,8 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
"🖼️",
[
"tourism/gallery",
- "tourism/artwork",
+ # tourism/artwork (statues, murals, village signs) was 93% of this
+ # bucket and is not a visitable gallery — see DROP_CATEGORIES.
],
),
(
@@ -949,9 +993,8 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
[
"tourism/attraction",
"tourism/aquarium",
- "amenity/fountain",
- "amenity/courthouse",
- "tourism/chalet",
+ # amenity/fountain (street furniture) and amenity/courthouse are
+ # dropped; tourism/chalet (holiday lets) moved to "Hotel".
],
),
# Note: schools come from the GIAS register (see transform_gias_schools).
@@ -970,6 +1013,9 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
"leisure/resort",
"tourism/holiday_park",
"tourism/self_catering",
+ # Holiday-let chalets are accommodation, not tourist attractions
+ # (where they previously sat).
+ "tourism/chalet",
],
),
(
@@ -1123,8 +1169,41 @@ _CATEGORIES: list[tuple[str, str, str, list[str]]] = [
"amenity/townhall",
],
),
+ # ── Public transport (OSM supplement to NaPTAN) ──────────
+ # OSM bus platforms fill NaPTAN's authority-level coverage gaps. Same group
+ # / friendly name / emoji as the NaPTAN "Bus stop" rows so they merge into
+ # one metric; OSM platforms that duplicate a NaPTAN stop are deduped in
+ # transform() (osm_stops_near_naptan).
+ (
+ "Public Transport",
+ "Bus stop",
+ "🚏",
+ [
+ "public_transport/platform",
+ ],
+ ),
]
+# Raw OSM tags whose UNNAMED instances are dropped before category mapping.
+# These tags are overwhelmingly private/incidental when unnamed: a nameless
+# `leisure/garden` is a private residential garden (not a public park), and a
+# nameless `leisure/pitch`/`swimming_pool` is a school cage or back-garden pool.
+# Keeping only named instances stops them inflating Park / Sports Centre counts
+# while preserving genuinely public, notable facilities (which carry a name).
+REQUIRE_NAME_CATEGORIES = {
+ "leisure/garden",
+ "leisure/pitch",
+ "leisure/practice_pitch",
+ "leisure/swimming_pool",
+ "leisure/paddling_pool",
+ # 83-84% unnamed: anonymous running tracks, private gallops/paddocks and
+ # fishing spots; only named public facilities count as a Sports Centre.
+ "leisure/track",
+ "leisure/horse_riding",
+ "leisure/fishing",
+}
+
+
# Build flat lookup: OSM category → (group, friendly_name, emoji)
CATEGORY_MAP: dict[str, tuple[str, str, str]] = {
osm_key: (group, name, emoji)
@@ -1141,6 +1220,7 @@ NAPTAN_EMOJIS: dict[str, str] = {
"Bus station": "🚌",
"Taxi rank": "🚕",
"Tube station": "🚇",
+ "Tram & Metro stop": "🚊",
}
@@ -1398,9 +1478,9 @@ def _load_ofsted_ratings(ofsted_path: Path) -> pl.LazyFrame:
(null/"Not judged", e.g. schools last seen under the post-2024 ungraded
report-card framework) we fall back to "Ungraded inspection overall outcome"
so genuinely good/outstanding schools aren't dropped — mirroring
- school_proximity.classify_good_plus_schools. Remaining nulls drop out."""
+ school_catchments.classify_good_plus_schools. Remaining nulls drop out."""
grade_col = pl.col("Latest OEIF overall effectiveness")
- # See school_proximity: the ungraded outcome carries "School remains Good"/
+ # See school_catchments: the ungraded outcome carries "School remains Good"/
# "School remains Outstanding" (with optional "(Concerns)"/"(Improving)"
# suffixes) when the graded column is null/"Not judged".
ungraded = pl.col("Ungraded inspection overall outcome").cast(pl.Utf8, strict=False)
@@ -1431,18 +1511,25 @@ def _load_ofsted_ratings(ofsted_path: Path) -> pl.LazyFrame:
)
-def transform_gias_schools(gias_path: Path, ofsted_path: Path) -> pl.LazyFrame:
+def transform_gias_schools(
+ gias_path: Path, ofsted_path: Path, boundary_path: Path
+) -> pl.LazyFrame:
"""Convert the GIAS register parquet into POI rows with school metadata.
Ofsted ratings are joined by URN so each school carries its latest OEIF
overall effectiveness grade (Outstanding/Good/Requires improvement/
- Inadequate/Not judged), surfaced in the map popup."""
+ Inadequate/Not judged), surfaced in the map popup.
+
+ Clipped to England (like NaPTAN/GEOLYTIX) because the GIAS register is
+ GB-wide, so ~1,400 Welsh/Scottish/IoM schools would otherwise leak into the
+ England-only Education layer (and depress apparent Ofsted coverage, since
+ Wales is inspected by Estyn, not Ofsted)."""
icon_category_expr = _school_icon_category_expr()
emoji_expr = icon_category_expr.replace_strict(SCHOOL_ICON_CATEGORIES)
ofsted = _load_ofsted_ratings(ofsted_path)
# category mirrors icon_category so the dashboard renders one toggle per
# school type (Nursery / Primary / Secondary / Sixth form / University /…)
# instead of bundling every GIAS row under a single "School" pill.
- return (
+ schools = (
pl.scan_parquet(gias_path)
.join(ofsted, on="urn", how="left")
.select(
@@ -1477,7 +1564,14 @@ def transform_gias_schools(gias_path: Path, ofsted_path: Path) -> pl.LazyFrame:
pl.col("head_name").alias("school_head_name"),
pl.col("ofsted_rating").alias("school_ofsted_rating"),
)
+ .collect()
)
+ mask = in_england_mask(
+ boundary_path,
+ schools["lat"].to_numpy(),
+ schools["lng"].to_numpy(),
+ )
+ return schools.filter(pl.Series(mask)).lazy()
# OSM convenience-format stores that GEOLYTIX also covers (Tesco Express,
@@ -1511,6 +1605,45 @@ def _significant_tokens(name: str | None) -> set[str]:
return tokens
+# OSM bus platforms are added to "Bus stop" to fill NaPTAN's authority-level
+# gaps. Where NaPTAN already has a stop within this radius the area is covered,
+# so the colocated OSM platform is dropped to avoid double-counting; OSM
+# platforms with no nearby NaPTAN stop (the gaps) are kept.
+BUS_STOP_DEDUP_RADIUS_M = 50.0
+
+
+def osm_stops_near_naptan(
+ osm_stops: pl.DataFrame,
+ naptan_stops: pl.DataFrame,
+ radius_m: float = BUS_STOP_DEDUP_RADIUS_M,
+) -> list[str]:
+ """Return OSM bus-stop ids within ``radius_m`` of any NaPTAN bus stop.
+
+ Purely spatial (no name match): in NaPTAN-covered areas the OSM platform is
+ a duplicate and is dropped; only OSM platforms that fill a NaPTAN gap (no
+ NaPTAN stop within the radius) survive. Both frames need ``id``/``lat``/``lng``.
+ """
+ if osm_stops.is_empty() or naptan_stops.is_empty():
+ return []
+
+ from scipy.spatial import cKDTree
+
+ n_lat = naptan_stops["lat"].to_numpy().astype(float)
+ n_lng = naptan_stops["lng"].to_numpy().astype(float)
+ o_lat = osm_stops["lat"].to_numpy().astype(float)
+ o_lng = osm_stops["lng"].to_numpy().astype(float)
+ o_ids = osm_stops["id"].to_list()
+
+ mean_lat = float(np.mean(np.concatenate([n_lat, o_lat])))
+ cos_lat = float(np.cos(np.radians(mean_lat)))
+ n_xy = np.column_stack([n_lng * cos_lat * 111_320.0, n_lat * 110_540.0])
+ o_xy = np.column_stack([o_lng * cos_lat * 111_320.0, o_lat * 110_540.0])
+
+ tree = cKDTree(n_xy)
+ dist, _ = tree.query(o_xy, k=1)
+ return [o_ids[i] for i in range(len(o_ids)) if dist[i] <= radius_m]
+
+
def osm_groceries_colocated_with_geolytix(
osm_groceries: pl.DataFrame,
geolytix: pl.DataFrame,
@@ -1601,6 +1734,19 @@ def transform(
# Drop unwanted categories
lf = lf.filter(~pl.col("category").is_in(list(DROP_CATEGORIES)))
+ # Drop UNNAMED instances of private-dominated tags (gardens, pitches,
+ # pools) so they don't inflate Park / Sports Centre proximity counts. Done
+ # while `category` still holds the raw OSM key, before the friendly mapping.
+ lf = lf.filter(
+ ~(
+ pl.col("category").is_in(list(REQUIRE_NAME_CATEGORIES))
+ & (
+ pl.col("name").is_null()
+ | (pl.col("name").cast(pl.String).str.strip_chars() == "")
+ )
+ )
+ )
+
# Build lookup expressions from the 3-tuple mapping
group_mapping = {k: v[0] for k, v in CATEGORY_MAP.items()}
name_mapping = {k: v[1] for k, v in CATEGORY_MAP.items()}
@@ -1665,11 +1811,37 @@ def transform(
~((pl.col("group") == "Groceries") & pl.col("id").is_in(duplicate_ids))
)
+ # Drop OSM bus platforms that duplicate a NaPTAN bus stop, so the OSM
+ # supplement only adds stops in NaPTAN's coverage gaps (no double-count in
+ # covered areas). OSM bus stops carry id-prefix "n"/"a" so they never clash
+ # with NaPTAN ATCO ids.
+ osm_bus_stops = (
+ lf.filter((pl.col("group") == "Public Transport") & (pl.col("category") == "Bus stop"))
+ .select("id", "lat", "lng")
+ .collect(engine="streaming")
+ )
+ naptan_bus_stops = naptan_df.filter(pl.col("category") == "Bus stop")
+ covered_bus_ids = osm_stops_near_naptan(osm_bus_stops, naptan_bus_stops)
+ kept_osm = osm_bus_stops.height - len(covered_bus_ids)
+ print(
+ f"OSM bus platforms: {osm_bus_stops.height:,} total, dropping "
+ f"{len(covered_bus_ids):,} that duplicate a NaPTAN stop, keeping "
+ f"{kept_osm:,} to fill NaPTAN gaps"
+ )
+ if covered_bus_ids:
+ lf = lf.filter(
+ ~(
+ (pl.col("group") == "Public Transport")
+ & (pl.col("category") == "Bus stop")
+ & pl.col("id").is_in(covered_bus_ids)
+ )
+ )
+
frames = [
lf,
naptan,
grocery_pois.lazy(),
- transform_gias_schools(gias_path, ofsted_path),
+ transform_gias_schools(gias_path, ofsted_path, boundary_path),
]
return pl.concat(frames, how="diagonal_relaxed")
diff --git a/pipeline/utils/poi_counts.py b/pipeline/utils/poi_counts.py
index 1edc0b9..9095d6e 100644
--- a/pipeline/utils/poi_counts.py
+++ b/pipeline/utils/poi_counts.py
@@ -10,6 +10,26 @@ EARTH_RADIUS_KM = 6371.0088
KM_PER_DEGREE_LAT = 111.32
DEFAULT_GRID_SIZE_DEGREES = 0.02
+# Generous GB/UK bounding box. The ArcGIS postcode source stores grid-less
+# postcodes with a placeholder coordinate (lat=99.999999, lon=0.0); these are
+# finite, so an isfinite() check alone lets them through and produces absurd
+# ~5,000 km "nearest amenity" distances. Reject anything outside this box so
+# such postcodes get NaN distance / zero counts instead of a fabricated value.
+UK_LAT_MIN, UK_LAT_MAX = 49.0, 61.5
+UK_LON_MIN, UK_LON_MAX = -9.0, 2.5
+
+
+def valid_uk_coords_mask(lats: np.ndarray, lons: np.ndarray) -> np.ndarray:
+ """Boolean mask of coordinates that are finite AND within the UK bbox."""
+ return (
+ np.isfinite(lats)
+ & np.isfinite(lons)
+ & (lats >= UK_LAT_MIN)
+ & (lats <= UK_LAT_MAX)
+ & (lons >= UK_LON_MIN)
+ & (lons <= UK_LON_MAX)
+ )
+
def _build_poi_grid(
pois: pl.DataFrame, grid_size: float = 0.05
@@ -43,7 +63,12 @@ def _get_nearby_indices(
grid_size: float = DEFAULT_GRID_SIZE_DEGREES,
) -> np.ndarray | None:
"""Get POI indices from all grid cells intersecting the radius bounding box."""
- if not np.isfinite(pc_lat) or not np.isfinite(pc_lon):
+ if (
+ not np.isfinite(pc_lat)
+ or not np.isfinite(pc_lon)
+ or not (UK_LAT_MIN <= pc_lat <= UK_LAT_MAX)
+ or not (UK_LON_MIN <= pc_lon <= UK_LON_MAX)
+ ):
return None
lat_delta = radius_km / KM_PER_DEGREE_LAT
@@ -182,7 +207,7 @@ def min_distance_per_postcode(
pc_lats = postcodes_df["lat"].to_numpy()
pc_lons = postcodes_df["lon"].to_numpy()
pc_codes = postcodes_df["postcode"].to_list()
- valid_pc_mask = np.isfinite(pc_lats) & np.isfinite(pc_lons)
+ valid_pc_mask = valid_uk_coords_mask(pc_lats, pc_lons)
valid_pc_indices = np.flatnonzero(valid_pc_mask)
result_min_dist = {
diff --git a/r5-java/src/main/java/propertymap/App.java b/r5-java/src/main/java/propertymap/App.java
index a9935d1..feb03ac 100644
--- a/r5-java/src/main/java/propertymap/App.java
+++ b/r5-java/src/main/java/propertymap/App.java
@@ -10,7 +10,9 @@ import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.DayOfWeek;
import java.time.LocalDate;
+import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
@@ -124,9 +126,15 @@ public class App {
Path outDir = Paths.get(outputDirStr);
Files.createDirectories(outDir);
- LocalDate today = LocalDate.now();
+ // Always route on a representative WEEKDAY (Monday), not the day the job
+ // happens to start: a commute search must reflect a typical weekday
+ // service pattern, and the 07:45-08:15 peak window paired with a weekend
+ // calendar (e.g. a Saturday run) understated frequencies and overstated
+ // transit times. --date=YYYY-MM-DD overrides for testing/holidays.
+ LocalDate routingDate = resolveRoutingDate(args);
+ System.err.printf("Routing date: %s (%s)%n", routingDate, routingDate.getDayOfWeek());
TransportNetwork network = Router.loadNetwork(requiredEnv("DATA_DIR"), requiredEnv("NETWORK_CACHE_DIR"));
- Router.validateTransitServices(network, today);
+ Router.validateTransitServices(network, routingDate);
System.err.println("Loading postcodes (England only)...");
Parquet.Postcodes postcodes = Parquet.loadEnglandPostcodes(
@@ -224,7 +232,7 @@ public class App {
int modeThreads = threadsForMode(mode, threads);
processMode(network, postcodeIndex, transitTiles,
postcodes.codes(), postcodes.lats(), postcodes.lons(),
- originNames, originLats, originLons, outDir, mode, today,
+ originNames, originLats, originLons, outDir, mode, routingDate,
modeThreads, writeQueue, enablePaths, originIndices, skipCompleted);
}
} finally {
@@ -544,6 +552,28 @@ public class App {
return defaultValue;
}
+ /**
+ * Resolve the date to route on. Defaults to the next-or-same Monday relative
+ * to today (a representative weekday close enough to "now" to stay within the
+ * GTFS calendar window), so transit times reflect a typical weekday commute
+ * rather than whatever day the batch job started. An explicit
+ * {@code --date=YYYY-MM-DD} (or {@code --date YYYY-MM-DD}) overrides this.
+ */
+ static LocalDate resolveRoutingDate(String[] args) {
+ for (int i = 0; i < args.length; i++) {
+ String value = null;
+ if (args[i].startsWith("--date=")) {
+ value = args[i].substring("--date=".length());
+ } else if (args[i].equals("--date") && i + 1 < args.length) {
+ value = args[i + 1];
+ }
+ if (value != null && !value.isBlank()) {
+ return LocalDate.parse(value.trim());
+ }
+ }
+ return LocalDate.now().with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
+ }
+
/**
* Filter place indices to those near at least one England postcode.
* Uses a 0.1° grid (~11km cells) built from postcode locations — a place is kept
diff --git a/server-rs/src/data/poi.rs b/server-rs/src/data/poi.rs
index 7f7d024..b6c223b 100644
--- a/server-rs/src/data/poi.rs
+++ b/server-rs/src/data/poi.rs
@@ -55,6 +55,7 @@ const DASHBOARD_POI_GROUPS: &[(&str, &[&str])] = &[
&[
"Rail station",
"Tube station",
+ "Tram & Metro stop",
"Bus station",
"Bus stop",
"Airport",
@@ -79,7 +80,7 @@ const DASHBOARD_POI_GROUPS: &[(&str, &[&str])] = &[
),
(
"Health",
- &["GP Surgery", "Pharmacy", "Dentist", "Hospital & Clinic"],
+ &["GP Surgery", "Pharmacy", "Dentist", "Hospital", "Clinic"],
),
(
"Leisure",
diff --git a/server-rs/src/features.rs b/server-rs/src/features.rs
index 74b74f9..cb16b1c 100644
--- a/server-rs/src/features.rs
+++ b/server-rs/src/features.rs
@@ -166,7 +166,7 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
name: "Defining characteristics",
features: &[
Feature::Numeric(FeatureConfig {
- name: "Street tree density percentile",
+ name: "Tree canopy density percentile",
bounds: Bounds::Fixed {
min: 0.0,
max: 100.0,
@@ -180,20 +180,6 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
raw: false,
absolute: true,
}),
- Feature::Enum(EnumFeatureConfig {
- name: "Within conservation area",
- order: Some(&["Yes", "No"]),
- description: "Whether the postcode point falls inside a designated conservation area",
- detail: "Planning Data conservation area boundaries, matched to the postcode representative point. The national dataset is a work in progress and may include duplicates or incomplete local coverage, so boundary-sensitive decisions should be checked with the local planning authority.",
- source: "conservation-areas",
- }),
- Feature::Enum(EnumFeatureConfig {
- name: "Listed building",
- order: Some(&["Yes", "No"]),
- description: "Whether this property appears to match a Historic England listed building entry",
- detail: "Historic England National Heritage List for England listed-building points, matched conservatively to property addresses using the listed-entry name and nearby postcode candidates. Treat this as a screening signal, not a legal determination: verify any specific property on the NHLE and with the local planning authority.",
- source: "listed-buildings",
- }),
Feature::Numeric(FeatureConfig {
name: "Noise (dB)",
bounds: Bounds::Fixed {
@@ -209,6 +195,20 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
raw: false,
absolute: false,
}),
+ Feature::Enum(EnumFeatureConfig {
+ name: "Within conservation area",
+ order: Some(&["Yes", "No"]),
+ description: "Whether the postcode point falls inside a designated conservation area",
+ detail: "Planning Data conservation area boundaries, matched to the postcode representative point. The national dataset is a work in progress and may include duplicates or incomplete local coverage, so boundary-sensitive decisions should be checked with the local planning authority.",
+ source: "conservation-areas",
+ }),
+ Feature::Enum(EnumFeatureConfig {
+ name: "Listed building",
+ order: Some(&["Yes", "No"]),
+ description: "Whether this property appears to match a Historic England listed building entry",
+ detail: "Historic England National Heritage List for England listed-building points, matched conservatively to property addresses using the listed-entry name and nearby postcode candidates. Treat this as a screening signal, not a legal determination: verify any specific property on the NHLE and with the local planning authority.",
+ source: "listed-buildings",
+ }),
],
},
FeatureGroup {
@@ -307,89 +307,14 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
name: "Schools",
features: &[
Feature::Numeric(FeatureConfig {
- name: "Good+ primary schools within 2km",
- bounds: Bounds::Fixed {
- min: 0.0,
- max: 10.0,
- },
- step: 1.0,
- description: "Primary schools rated Good or Outstanding by Ofsted within 2km",
- detail: "State-funded primary schools within 2km with a current Ofsted rating of Good or Outstanding. Schools not yet inspected are excluded.",
- source: "ofsted",
- prefix: "",
- suffix: "",
- raw: false,
- absolute: false,
- }),
- Feature::Numeric(FeatureConfig {
- name: "Good+ secondary schools within 2km",
- bounds: Bounds::Fixed {
- min: 0.0,
- max: 5.0,
- },
- step: 1.0,
- description: "Secondary schools rated Good or Outstanding by Ofsted within 2km",
- detail: "State-funded secondary schools within 2km with a current Ofsted rating of Good or Outstanding. Schools not yet inspected are excluded.",
- source: "ofsted",
- prefix: "",
- suffix: "",
- raw: false,
- absolute: false,
- }),
- Feature::Numeric(FeatureConfig {
- name: "Outstanding primary schools within 2km",
- bounds: Bounds::Fixed {
- min: 0.0,
- max: 10.0,
- },
- step: 1.0,
- description: "Primary schools rated Outstanding by Ofsted within 2km",
- detail: "State-funded primary schools within 2km with a current Ofsted rating of Outstanding. Schools not yet inspected are excluded.",
- source: "ofsted",
- prefix: "",
- suffix: "",
- raw: false,
- absolute: false,
- }),
- Feature::Numeric(FeatureConfig {
- name: "Outstanding secondary schools within 2km",
- bounds: Bounds::Fixed {
- min: 0.0,
- max: 5.0,
- },
- step: 1.0,
- description: "Secondary schools rated Outstanding by Ofsted within 2km",
- detail: "State-funded secondary schools within 2km with a current Ofsted rating of Outstanding. Schools not yet inspected are excluded.",
- source: "ofsted",
- prefix: "",
- suffix: "",
- raw: false,
- absolute: false,
- }),
- Feature::Numeric(FeatureConfig {
- name: "Good+ primary schools within 5km",
- bounds: Bounds::Fixed {
- min: 0.0,
- max: 30.0,
- },
- step: 1.0,
- description: "Primary schools rated Good or Outstanding by Ofsted within 5km",
- detail: "State-funded primary schools within 5km with a current Ofsted rating of Good or Outstanding. Schools not yet inspected are excluded.",
- source: "ofsted",
- prefix: "",
- suffix: "",
- raw: false,
- absolute: false,
- }),
- Feature::Numeric(FeatureConfig {
- name: "Good+ secondary schools within 5km",
+ name: "Good+ primary school catchments",
bounds: Bounds::Fixed {
min: 0.0,
max: 15.0,
},
step: 1.0,
- description: "Secondary schools rated Good or Outstanding by Ofsted within 5km",
- detail: "State-funded secondary schools within 5km with a current Ofsted rating of Good or Outstanding. Schools not yet inspected are excluded.",
+ description: "Primary schools rated Good or Outstanding whose modelled catchment area covers this postcode",
+ detail: "How many state-funded primary schools with a current Ofsted rating of Good or Outstanding draw their pupils from an area covering this postcode. Catchment radii are modelled by simulating England's distance-based admissions (each school's places against the local child population, Census 2021) and calibrated against published 'last distance offered' figures; they are estimates, not official admission areas. Schools not yet inspected are excluded.",
source: "ofsted",
prefix: "",
suffix: "",
@@ -397,14 +322,14 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
absolute: false,
}),
Feature::Numeric(FeatureConfig {
- name: "Outstanding primary schools within 5km",
+ name: "Good+ secondary school catchments",
bounds: Bounds::Fixed {
min: 0.0,
- max: 30.0,
+ max: 11.0,
},
step: 1.0,
- description: "Primary schools rated Outstanding by Ofsted within 5km",
- detail: "State-funded primary schools within 5km with a current Ofsted rating of Outstanding. Schools not yet inspected are excluded.",
+ description: "Secondary schools rated Good or Outstanding whose modelled catchment area covers this postcode",
+ detail: "How many state-funded secondary schools with a current Ofsted rating of Good or Outstanding draw their pupils from an area covering this postcode. Catchment radii are modelled by simulating England's distance-based admissions (each school's places against the local child population, Census 2021) and calibrated against published 'last distance offered' figures; they are estimates, not official admission areas. Schools not yet inspected are excluded.",
source: "ofsted",
prefix: "",
suffix: "",
@@ -412,14 +337,29 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
absolute: false,
}),
Feature::Numeric(FeatureConfig {
- name: "Outstanding secondary schools within 5km",
+ name: "Outstanding primary school catchments",
bounds: Bounds::Fixed {
min: 0.0,
- max: 15.0,
+ max: 8.0,
},
step: 1.0,
- description: "Secondary schools rated Outstanding by Ofsted within 5km",
- detail: "State-funded secondary schools within 5km with a current Ofsted rating of Outstanding. Schools not yet inspected are excluded.",
+ description: "Primary schools rated Outstanding whose modelled catchment area covers this postcode",
+ detail: "How many state-funded primary schools with a current Ofsted rating of Outstanding draw their pupils from an area covering this postcode. Catchment radii are modelled by simulating England's distance-based admissions (each school's places against the local child population, Census 2021) and calibrated against published 'last distance offered' figures; they are estimates, not official admission areas. Schools not yet inspected are excluded.",
+ source: "ofsted",
+ prefix: "",
+ suffix: "",
+ raw: false,
+ absolute: false,
+ }),
+ Feature::Numeric(FeatureConfig {
+ name: "Outstanding secondary school catchments",
+ bounds: Bounds::Fixed {
+ min: 0.0,
+ max: 4.0,
+ },
+ step: 1.0,
+ description: "Secondary schools rated Outstanding whose modelled catchment area covers this postcode",
+ detail: "How many state-funded secondary schools with a current Ofsted rating of Outstanding draw their pupils from an area covering this postcode. Catchment radii are modelled by simulating England's distance-based admissions (each school's places against the local child population, Census 2021) and calibrated against published 'last distance offered' figures; they are estimates, not official admission areas. Schools not yet inspected are excluded.",
source: "ofsted",
prefix: "",
suffix: "",
@@ -832,14 +772,29 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
absolute: false,
}),
Feature::Numeric(FeatureConfig {
- name: "% East/SE Asian",
+ name: "% East Asian",
bounds: Bounds::Fixed {
min: 0.0,
max: 100.0,
},
step: 0.1,
- description: "Percentage of population identifying as East or Southeast Asian",
- detail: "From the 2021 Census. Percentage of the local authority population identifying as Chinese, Vietnamese, Filipino, Thai, or any other East or Southeast Asian background.",
+ description: "Percentage of population identifying as East Asian",
+ detail: "From the 2021 Census. Percentage of the local authority population identifying as Chinese.",
+ source: "ethnicity",
+ prefix: "",
+ suffix: "%",
+ raw: false,
+ absolute: false,
+ }),
+ Feature::Numeric(FeatureConfig {
+ name: "% SE Asian",
+ bounds: Bounds::Fixed {
+ min: 0.0,
+ max: 100.0,
+ },
+ step: 0.1,
+ description: "Percentage of population identifying as Southeast Asian",
+ detail: "From the 2021 Census. Percentage of the local authority population identifying as another (non-Chinese) East or Southeast Asian background, e.g. Vietnamese, Filipino, or Thai.",
source: "ethnicity",
prefix: "",
suffix: "%",
diff --git a/server-rs/src/routes/ai_filters.rs b/server-rs/src/routes/ai_filters.rs
index c8c3cc5..9b7778e 100644
--- a/server-rs/src/routes/ai_filters.rs
+++ b/server-rs/src/routes/ai_filters.rs
@@ -62,6 +62,42 @@ pub struct AiFiltersResponse {
notes: String,
/// Number of properties matching the proposed property and travel time filters.
match_count: usize,
+ /// Bounding box of the matching properties so the client can move the
+ /// camera to where matches actually are. Absent when nothing matches.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ match_bounds: Option,
+}
+
+#[derive(Serialize)]
+pub struct MatchBounds {
+ south: f32,
+ west: f32,
+ north: f32,
+ east: f32,
+}
+
+/// Bounding box over matched coordinates, trimmed to the 5th–95th percentile
+/// per axis (when there are enough points) so a handful of remote outliers
+/// doesn't zoom the camera out to all of England.
+fn percentile_trimmed_bounds(mut lats: Vec, mut lons: Vec) -> Option {
+ if lats.is_empty() || lats.len() != lons.len() {
+ return None;
+ }
+ lats.sort_unstable_by(f32::total_cmp);
+ lons.sort_unstable_by(f32::total_cmp);
+ let last = lats.len() - 1;
+ let (lo, hi) = if lats.len() >= 20 {
+ let trim = lats.len() / 20;
+ (trim, last - trim)
+ } else {
+ (0, last)
+ };
+ Some(MatchBounds {
+ south: lats[lo],
+ north: lats[hi],
+ west: lons[lo],
+ east: lons[hi],
+ })
}
/// Strip markdown code fences (```json ... ``` or ``` ... ```) from LLM output.
@@ -90,17 +126,12 @@ fn school_feature_name_from_key(name: &str) -> Option<&'static str> {
let mut parts = rest.split(':');
let phase = parts.next()?;
let rating = parts.next()?;
- let distance = parts.next()?;
- match (phase, rating, distance) {
- ("primary", "good", "2") => Some("Good+ primary schools within 2km"),
- ("secondary", "good", "2") => Some("Good+ secondary schools within 2km"),
- ("primary", "outstanding", "2") => Some("Outstanding primary schools within 2km"),
- ("secondary", "outstanding", "2") => Some("Outstanding secondary schools within 2km"),
- ("primary", "good", "5") => Some("Good+ primary schools within 5km"),
- ("secondary", "good", "5") => Some("Good+ secondary schools within 5km"),
- ("primary", "outstanding", "5") => Some("Outstanding primary schools within 5km"),
- ("secondary", "outstanding", "5") => Some("Outstanding secondary schools within 5km"),
+ match (phase, rating) {
+ ("primary", "good") => Some("Good+ primary school catchments"),
+ ("secondary", "good") => Some("Good+ secondary school catchments"),
+ ("primary", "outstanding") => Some("Outstanding primary school catchments"),
+ ("secondary", "outstanding") => Some("Outstanding secondary school catchments"),
_ => None,
}
}
@@ -508,8 +539,8 @@ pub fn build_system_prompt(
{\"name\": \"Serious crime (avg/yr)\", \"bound\": \"max\", \"value\": 5}, \
{\"name\": \"Minor crime (avg/yr)\", \"bound\": \"max\", \"value\": 20}, \
{\"name\": \"Noise (dB)\", \"bound\": \"max\", \"value\": 55}, \
- {\"name\": \"Good+ primary schools within 2km\", \"bound\": \"min\", \"value\": 2}, \
- {\"name\": \"Good+ secondary schools within 2km\", \"bound\": \"min\", \"value\": 1}, \
+ {\"name\": \"Good+ primary school catchments\", \"bound\": \"min\", \"value\": 2}, \
+ {\"name\": \"Good+ secondary school catchments\", \"bound\": \"min\", \"value\": 1}, \
{\"name\": \"Number of amenities (Park) within 2km\", \"bound\": \"min\", \"value\": 3}], \
\"enum_filters\": [], \"travel_time_filters\": [], \"notes\": \"\"}"
.to_string(),
@@ -519,8 +550,8 @@ pub fn build_system_prompt(
"\nUser: \"quiet area with outstanding schools\"\n\
Output: {\"numeric_filters\": [\
{\"name\": \"Noise (dB)\", \"bound\": \"max\", \"value\": 55}, \
- {\"name\": \"Outstanding primary schools within 2km\", \"bound\": \"min\", \"value\": 1}, \
- {\"name\": \"Outstanding secondary schools within 2km\", \"bound\": \"min\", \"value\": 1}], \
+ {\"name\": \"Outstanding primary school catchments\", \"bound\": \"min\", \"value\": 1}, \
+ {\"name\": \"Outstanding secondary school catchments\", \"bound\": \"min\", \"value\": 1}], \
\"enum_filters\": [], \"travel_time_filters\": [], \"notes\": \"\"}"
.to_string(),
);
@@ -557,8 +588,8 @@ pub fn build_system_prompt(
Output: {\"numeric_filters\": [\
{\"name\": \"Total floor area (sqm)\", \"bound\": \"min\", \"value\": 100}, \
{\"name\": \"Number of bedrooms & living rooms\", \"bound\": \"min\", \"value\": 5}, \
- {\"name\": \"Good+ primary schools within 2km\", \"bound\": \"min\", \"value\": 2}, \
- {\"name\": \"Good+ secondary schools within 2km\", \"bound\": \"min\", \"value\": 1}], \
+ {\"name\": \"Good+ primary school catchments\", \"bound\": \"min\", \"value\": 2}, \
+ {\"name\": \"Good+ secondary school catchments\", \"bound\": \"min\", \"value\": 1}], \
\"enum_filters\": [{\"name\": \"Property type\", \
\"values\": [\"Detached\", \"Semi-Detached\"]}], \
\"travel_time_filters\": [{\"mode\": \"car\", \"slug\": \"manchester\", \
@@ -592,7 +623,7 @@ pub fn build_system_prompt(
"\nUser: \"3 bed house under 500k with good schools\"\n\
Output: {\
\"numeric_filters\": [{\"name\": \"Estimated current price\", \"bound\": \"max\", \"value\": 500000}, \
- {\"name\": \"Good+ primary schools within 2km\", \"bound\": \"min\", \"value\": 2}], \
+ {\"name\": \"Good+ primary school catchments\", \"bound\": \"min\", \"value\": 2}], \
\"enum_filters\": [{\"name\": \"Property type\", \
\"values\": [\"Detached\", \"Semi-Detached\", \"Terraced\"]}], \
\"travel_time_filters\": [], \
@@ -759,7 +790,7 @@ fn count_matching_rows(
state: &AppState,
filters: &Value,
travel_time_filters: &[TravelTimeFilter],
-) -> usize {
+) -> (usize, Option) {
let filter_str = filters_to_filter_string(filters);
let quant = state.data.quant_ref();
@@ -778,7 +809,7 @@ fn count_matching_rows(
Ok(f) => f,
Err(err) => {
warn!("Failed to parse filters for match count: {err}");
- return 0;
+ return (0, None);
}
}
};
@@ -800,6 +831,8 @@ fn count_matching_rows(
let has_poi_filters = !parsed_poi_filters.is_empty();
let mut count = 0usize;
+ let mut matched_lats: Vec = Vec::new();
+ let mut matched_lons: Vec = Vec::new();
for (row, pc_key) in pc_keys.iter().enumerate().take(num_rows) {
if !row_passes_filters(
row,
@@ -836,9 +869,11 @@ fn count_matching_rows(
}
count += 1;
+ matched_lats.push(state.data.lat[row]);
+ matched_lons.push(state.data.lon[row]);
}
- count
+ (count, percentile_trimmed_bounds(matched_lats, matched_lons))
}
/// Budget limits for the Gemini conversation loop. Separate counters prevent
@@ -1132,7 +1167,7 @@ pub async fn post_ai_filters(
.to_string();
// Count matching properties and refine if too restrictive
- let match_count = count_matching_rows(&state, &filters, &travel_time_filters);
+ let (match_count, match_bounds) = count_matching_rows(&state, &filters, &travel_time_filters);
info!(
match_count = match_count,
round = round,
@@ -1173,6 +1208,7 @@ pub async fn post_ai_filters(
travel_time_filters,
notes,
match_count: 0,
+ match_bounds: None,
}));
}
@@ -1236,6 +1272,7 @@ pub async fn post_ai_filters(
travel_time_filters,
notes,
match_count,
+ match_bounds,
}));
}
@@ -1488,9 +1525,14 @@ mod tests {
#[test]
fn synthetic_filter_keys_are_normalized_to_backend_names() {
+ assert_eq!(
+ canonical_filter_name("Schools:primary:good:0"),
+ "Good+ primary school catchments"
+ );
+ // Legacy keys still carry a distance segment; it is ignored.
assert_eq!(
canonical_filter_name("Schools:primary:good:2:0"),
- "Good+ primary schools within 2km"
+ "Good+ primary school catchments"
);
assert_eq!(
canonical_filter_name("Specific crimes:Burglary%20%28avg%2Fyr%29:1"),
diff --git a/server-rs/src/routes/filter_counts.rs b/server-rs/src/routes/filter_counts.rs
index 991ba53..cf2e3d8 100644
--- a/server-rs/src/routes/filter_counts.rs
+++ b/server-rs/src/routes/filter_counts.rs
@@ -68,8 +68,11 @@ pub async fn get_filter_counts(
let num_total_filters = num_regular + travel_filter_indices.len();
if num_total_filters == 0 {
+ // With no active filters the total is simply every property in bounds.
+ // count_in_bounds is O(grid cells), far cheaper than walking every row.
+ let total = state.grid.count_in_bounds(south, west, north, east) as u32;
return Ok(Json(FilterCountsResponse {
- total: 0,
+ total,
impacts: FxHashMap::default(),
}));
}
diff --git a/server-rs/src/routes/hexagon_stats.rs b/server-rs/src/routes/hexagon_stats.rs
index 3d53c4d..736b233 100644
--- a/server-rs/src/routes/hexagon_stats.rs
+++ b/server-rs/src/routes/hexagon_stats.rs
@@ -672,7 +672,7 @@ mod tests {
assert!(fields_specified);
assert!(field_set.contains("Property type"));
- assert!(field_set.contains("Street tree density percentile"));
+ assert!(field_set.contains("Tree canopy density percentile"));
assert!(field_set.contains("Noise (dB)"));
assert!(!field_set.contains("Max available download speed (Mbps)"));
assert!(!field_set.contains("Distance to nearest amenity (Cafe) (km)"));
diff --git a/video/render.sh b/video/render.sh
index 8d49663..53a7d86 100755
--- a/video/render.sh
+++ b/video/render.sh
@@ -337,19 +337,20 @@ if [ "$DO_AUDIO" = "1" ]; then
# it across the rest of the storyboards by copying _reference.wav +
# _reference.meta.json into their audio dirs before their synth runs.
# synth.py's _resolve_reference() reuses a matching cached reference
- # as long as the meta block (instruct/language/seed/etc.) matches —
- # which it always does, because every ad shares AD_VOICE.
+ # as long as the meta block (instruct/language/seed/etc.) matches.
+ #
+ # We copy ONLY the reference, never the cue wavs or index.json. Copying
+ # the whole audio dir (as an earlier version did) overwrote each later
+ # storyboard's cached index.json with the FIRST storyboard's, which
+ # forced a full re-synth on every run — and in multi-voice sets (the
+ # localized homepage demos: en/de/zh/hi) it clobbered correct localized
+ # audio. With a reference-only copy: same-voice sets reuse the reference
+ # (meta matches); different-voice sets re-mint their own (meta mismatch),
+ # and in both cases an up-to-date cached index.json lets synth skip.
shared_ref_wav=""
shared_ref_meta=""
- shared_audio_dir=""
for sb in "${STORYBOARDS[@]}"; do
- if [ -n "$shared_audio_dir" ] && [ -d "$shared_audio_dir" ]; then
- mkdir -p "output/$sb/audio"
- for cached_audio_file in "$shared_audio_dir"/*.wav "$shared_audio_dir"/*.json; do
- [ -f "$cached_audio_file" ] || continue
- cp -f "$cached_audio_file" "output/$sb/audio/$(basename "$cached_audio_file")"
- done
- elif [ -n "$shared_ref_wav" ] && [ -f "$shared_ref_wav" ] && [ -f "$shared_ref_meta" ]; then
+ if [ -n "$shared_ref_wav" ] && [ -f "$shared_ref_wav" ] && [ -f "$shared_ref_meta" ]; then
mkdir -p "output/$sb/audio"
cp -f "$shared_ref_wav" "output/$sb/audio/_reference.wav"
cp -f "$shared_ref_meta" "output/$sb/audio/_reference.meta.json"
@@ -365,9 +366,6 @@ if [ "$DO_AUDIO" = "1" ]; then
shared_ref_meta="output/$sb/audio/_reference.meta.json"
say "Locked voice reference to $shared_ref_wav — reusing for the rest of the set"
fi
- if [ -z "$shared_audio_dir" ] && [ -s "output/$sb/audio/index.json" ]; then
- shared_audio_dir="output/$sb/audio"
- fi
done
fi
diff --git a/video/src/browser.ts b/video/src/browser.ts
index 59660ac..fa838e5 100644
--- a/video/src/browser.ts
+++ b/video/src/browser.ts
@@ -34,8 +34,11 @@ export async function launchRecordingBrowser(
'--enable-gpu-rasterization',
'--enable-zero-copy',
'--disable-software-rasterizer',
- '--disable-frame-rate-limit',
- '--disable-gpu-vsync',
+ // NOTE: --disable-frame-rate-limit / --disable-gpu-vsync used to be
+ // here for screencast smoothness, but with the host GPU nearly full
+ // the uncapped render loop starved the renderer (ERR_INSUFFICIENT_
+ // RESOURCES + ~31s page stalls on the 1080p take). Vsync-limited
+ // 60fps is plenty for the 50fps output.
'--disable-features=CalculateNativeWinOcclusion,IntensiveWakeUpThrottling',
'--disable-renderer-backgrounding',
'--disable-background-timer-throttling',
diff --git a/video/src/dashboard.ts b/video/src/dashboard.ts
index 249f481..1c75f4d 100644
--- a/video/src/dashboard.ts
+++ b/video/src/dashboard.ts
@@ -66,10 +66,19 @@ export class DashboardRecorder {
private selectionStatsVersion = 0;
private lastHexagons: HexagonSnapshot | null = null;
private lastPostcodes: PostcodeSnapshot | null = null;
+ private lastRequestedMapBounds: string | null = null;
constructor(private readonly page: Page) {
page.on('request', (request) => {
- if (classifyApiRequest(request.url())) this.pending.add(request);
+ const kind = classifyApiRequest(request.url());
+ if (kind) this.pending.add(request);
+ if (kind === 'hexagons' || kind === 'postcodes') {
+ try {
+ this.lastRequestedMapBounds = new URL(request.url()).searchParams.get('bounds');
+ } catch {
+ /* ignore */
+ }
+ }
});
page.on('requestfinished', (request) => this.pending.delete(request));
page.on('requestfailed', (request) => this.pending.delete(request));
@@ -90,6 +99,28 @@ export class DashboardRecorder {
await this.waitForStable({ afterMapVersion, timeoutMs });
}
+ /**
+ * Best-effort wait for tracked API traffic to go quiet (250ms of no
+ * pending requests and no loading indicator). Unlike waitForStable this
+ * never throws — it simply returns at the deadline. Used before computing
+ * hexagon click targets so the projection runs against the response for
+ * the CURRENT viewport rather than one captured mid-animation.
+ */
+ async waitForApiIdle(timeoutMs = 3000): Promise {
+ const deadline = Date.now() + timeoutMs;
+ let stableSince: number | null = null;
+ while (Date.now() < deadline) {
+ const idle = this.pending.size === 0 && (await this.loadingIndicatorsHidden());
+ if (idle) {
+ stableSince ??= Date.now();
+ if (Date.now() - stableSince >= 250) return;
+ } else {
+ stableSince = null;
+ }
+ await this.page.waitForTimeout(100);
+ }
+ }
+
async waitForSelectionReady(afterSelectionVersion: number, timeoutMs = 12000): Promise {
await this.page
.locator(SELECTION_PANE_SELECTOR)
@@ -99,6 +130,22 @@ export class DashboardRecorder {
}
async visibleHexagonTargets(limit = 8): Promise {
+ // Empty responses don't replace the snapshot (parse* skips them), so a
+ // snapshot whose bounds differ from the latest REQUEST means the current
+ // view has zero matching features and we'd be projecting stale data.
+ // Surface that loudly — it almost always means the storyboard's filters
+ // emptied the area it zoomed into.
+ const snapshotBounds = this.lastPostcodes?.bounds ?? this.lastHexagons?.bounds;
+ if (snapshotBounds && this.lastRequestedMapBounds) {
+ const snapKey = `${snapshotBounds.south},${snapshotBounds.west},${snapshotBounds.north},${snapshotBounds.east}`;
+ if (snapKey !== this.lastRequestedMapBounds) {
+ console.log(
+ `[dashboard] WARNING: map snapshot is stale (snapshot bounds ${snapKey} ` +
+ `vs latest request ${this.lastRequestedMapBounds}) — the current view ` +
+ `likely has no matching features; clicks may land on empty map.`
+ );
+ }
+ }
const postcodeTargets = await this.visiblePostcodeTargets(limit);
if (postcodeTargets.length > 0) return postcodeTargets;
@@ -188,10 +235,14 @@ export class DashboardRecorder {
height: number;
}): Promise<{ top: number; bottom: number; left: number; right: number }> {
let bottomClear = mapBox.y + mapBox.height - 115;
+ // Short timeout: on desktop the MobileBottomSheet doesn't exist, and a
+ // bare boundingBox() would block for Playwright's default 30s before
+ // the catch fires (this masqueraded as a "renderer freeze" in every
+ // desktop take that used hex() targets).
const sheet = await this.page
.locator('section[class*="rounded-t-2xl"]')
.first()
- .boundingBox()
+ .boundingBox({ timeout: 250 })
.catch(() => null);
if (sheet && sheet.y > mapBox.y && sheet.y < bottomClear) {
bottomClear = sheet.y - 16;
diff --git a/video/src/dom.ts b/video/src/dom.ts
index 650a0e8..5a9eae8 100644
--- a/video/src/dom.ts
+++ b/video/src/dom.ts
@@ -9,7 +9,10 @@ import type { AdScene, AdScenePanel } from './script.js';
* the Node side. That keeps a single source of truth — Playwright's real mouse
* — and the visual is pure CSS, animated by the browser's compositor.
*/
-export async function installCursor(page: Page): Promise {
+export async function installCursor(
+ page: Page,
+ style: 'arrow' | 'touch' = 'arrow'
+): Promise {
await page.addStyleTag({
content: `
*, *::before, *::after { cursor: none !important; }
@@ -30,6 +33,12 @@ export async function installCursor(page: Page): Promise {
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.35));
}
#__demo-cursor.click { scale: 0.85; }
+ /* Touch mode: a soft fingertip dot centred on the contact point. */
+ #__demo-cursor.touch {
+ width: 34px; height: 34px;
+ transform-origin: 17px 17px;
+ }
+ #__demo-cursor.touch.click { scale: 0.7; }
.__demo-ripple {
position: fixed;
@@ -72,49 +81,54 @@ export async function installCursor(page: Page): Promise {
#__demo-vignette.gone { opacity: 0; }
/*
- * Caption positioning rules of thumb:
+ * Caption = short HOOK CHIP, not a transcript. Cues carry an optional
+ * ≤6-word caption that complements the narration (the spoken line is
+ * never rendered). Because the text is short, the chip can be loud
+ * (big type, accent bar) without covering the product.
+ *
+ * Positioning rules of thumb:
* Vertical (9:16) cuts MUST keep the caption inside the top ~62% of
* the viewport. TikTok, Reels, and Shorts overlay their own chrome
* across the bottom ~30%, so anything below y=68% gets eaten by
* the platform UI. Mobile dashboard captures also have a sheet
* covering the bottom half, so a low caption sits over filter
* controls rather than over the map.
- * Horizontal (16:9) cuts can use the classic lower-third instead.
+ * Horizontal (16:9) cuts use a compact lower-third chip instead.
* The body class is set once at recorder setup (setAspectClass) so
* every cue inherits the right positioning.
*/
#__demo-caption {
position: fixed;
left: 50%;
- transform: translate(-50%, 28px);
+ transform: translate(-50%, 24px);
width: max-content;
- max-width: min(1160px, 86vw);
- padding: 22px 30px;
- border-radius: 22px;
- background: rgba(2, 6, 23, 0.92);
- backdrop-filter: blur(20px) saturate(1.1);
- -webkit-backdrop-filter: blur(20px) saturate(1.1);
+ max-width: min(900px, 88vw);
+ padding: 14px 22px;
+ border-radius: 14px;
+ background: rgba(2, 6, 23, 0.9);
+ backdrop-filter: blur(16px) saturate(1.1);
+ -webkit-backdrop-filter: blur(16px) saturate(1.1);
color: #ffffff;
font:
- 800 36px/1.22 "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
+ 850 34px/1.15 "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
sans-serif;
- letter-spacing: -0.005em;
+ letter-spacing: -0.01em;
text-align: center;
text-shadow: 0 2px 6px rgba(0, 0, 0, 0.55);
box-shadow:
- 0 22px 60px rgba(0, 0, 0, 0.55),
- inset 0 0 0 1.5px rgba(255, 255, 255, 0.16);
+ 0 16px 44px rgba(0, 0, 0, 0.5),
+ inset 0 0 0 1.5px rgba(45, 212, 191, 0.45);
z-index: 2147483641;
opacity: 0;
pointer-events: none;
transition:
- opacity 280ms ease-out,
- transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
+ opacity 240ms ease-out,
+ transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
white-space: normal;
}
- /* Horizontal default: classic lower-third. */
+ /* Horizontal: compact lower-third chip. */
body.__demo-aspect-horizontal #__demo-caption {
- bottom: 7%;
+ bottom: 6%;
}
body.__demo-aspect-horizontal #__demo-caption.placement-side {
left: auto;
@@ -122,21 +136,22 @@ export async function installCursor(page: Page): Promise {
bottom: 10%;
transform: translate(28px, 0);
max-width: min(560px, 30vw);
- padding: 18px 22px;
- border-radius: 18px;
+ padding: 14px 20px;
+ border-radius: 14px;
font-size: 26px;
line-height: 1.18;
text-align: left;
}
- /* Vertical default: upper-third. Kept compact so the map remains the
- primary visual in the social ad cuts. */
+ /* Vertical: upper area, clear of platform chrome and the bottom sheet.
+ At the 540-wide CSS viewport this renders ~30px type → 60px in the
+ published 1080-wide mp4: a proper social-video hook size. */
body.__demo-aspect-vertical #__demo-caption {
- top: 7%;
- max-width: min(820px, 82vw);
- font-size: 27px;
- font-weight: 750;
+ top: 8%;
+ max-width: 88vw;
+ font-size: 30px;
+ font-weight: 850;
padding: 12px 18px;
- border-radius: 14px;
+ border-radius: 12px;
}
#__demo-caption.visible {
opacity: 1;
@@ -496,14 +511,26 @@ export async function installCursor(page: Page): Promise {
`,
});
- await page.evaluate(() => {
+ await page.evaluate((style) => {
const cursor = document.createElement('div');
cursor.id = '__demo-cursor';
- cursor.innerHTML = `
-
-
- `;
+ // Hotspot: arrow tip sits 2px in from the SVG corner; the touch dot is
+ // centred on the contact point.
+ const hot = style === 'touch' ? 17 : 2;
+ if (style === 'touch') {
+ cursor.classList.add('touch');
+ cursor.innerHTML = `
+
+
+ `;
+ } else {
+ cursor.innerHTML = `
+
+
+ `;
+ }
document.body.appendChild(cursor);
const vignette = document.createElement('div');
@@ -517,7 +544,7 @@ export async function installCursor(page: Page): Promise {
window.addEventListener(
'mousemove',
(e) => {
- cursor.style.transform = `translate(${e.clientX - 2}px, ${e.clientY - 2}px)`;
+ cursor.style.transform = `translate(${e.clientX - hot}px, ${e.clientY - hot}px)`;
},
{ passive: true, capture: true }
);
@@ -525,7 +552,7 @@ export async function installCursor(page: Page): Promise {
__demoMoveCursor?: (x: number, y: number, durationMs: number) => void;
}).__demoMoveCursor = (x, y, durationMs) => {
cursor.style.transition = `transform ${durationMs}ms cubic-bezier(0.22, 1, 0.36, 1), scale 120ms ease-out`;
- cursor.style.transform = `translate(${x - 2}px, ${y - 2}px)`;
+ cursor.style.transform = `translate(${x - hot}px, ${y - hot}px)`;
window.setTimeout(() => {
cursor.style.transition = 'transform 60ms linear, scale 120ms ease-out';
}, durationMs + 40);
@@ -549,7 +576,7 @@ export async function installCursor(page: Page): Promise {
() => cursor.classList.remove('click'),
{ passive: true, capture: true }
);
- });
+ }, style);
}
export async function clearVignette(page: Page): Promise {
@@ -897,8 +924,15 @@ export async function zoomTo(
const { scale, focusX, focusY, durationMs = 1100 } = opts;
const transitionMs = Math.round(durationMs);
const viewport = page.viewportSize() ?? { width: 1920, height: 1080 };
- const dx = viewport.width / 2 - scale * focusX;
- const dy = viewport.height / 2 - scale * focusY;
+ // Clamp the pan so the scaled app always covers the whole viewport.
+ // Without this, focusing an element near a screen edge drags the app
+ // off-frame and exposes the dark backdrop (the old cold-open showed a
+ // third of the frame as void). For scale ≥ 1 the translation must stay
+ // within [size·(1−scale), 0] on each axis.
+ const clampPan = (value: number, span: number): number =>
+ scale >= 1 ? Math.min(0, Math.max(span * (1 - scale), value)) : value;
+ const dx = clampPan(viewport.width / 2 - scale * focusX, viewport.width);
+ const dy = clampPan(viewport.height / 2 - scale * focusY, viewport.height);
await page.evaluate(
({ dx, dy, scale, transitionMs }) => {
const wrap = document.getElementById('__demo-zoom-wrap');
diff --git a/video/src/preflight.ts b/video/src/preflight.ts
index 4b6e0e9..7c9e1d2 100644
--- a/video/src/preflight.ts
+++ b/video/src/preflight.ts
@@ -59,9 +59,79 @@ function emitScript(storyboard: Storyboard): string {
return path;
}
-function main(): void {
+/**
+ * Validate every stubbed/initial filter name and travel-destination slug
+ * against the LIVE API. Wrong names don't error in the app — they silently
+ * no-op, the map never changes, and you only find out after a full render.
+ * Fails hard on a mismatch; soft-warns if the API is unreachable (render.sh
+ * has already health-checked it by the time preflight runs).
+ */
+async function validateAgainstLiveApi(): Promise {
+ const apiBase = process.env.API_URL ?? process.env.APP_URL;
+ if (!apiBase) {
+ console.warn('[preflight] no API_URL/APP_URL set — skipping live filter validation');
+ return;
+ }
+
+ let featureNames: Set;
+ try {
+ const res = await fetch(`${apiBase}/api/features`);
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const body = (await res.json()) as {
+ groups: { features: { name: string }[] }[];
+ };
+ featureNames = new Set(body.groups.flatMap((g) => g.features.map((f) => f.name)));
+ } catch (err) {
+ console.warn(`[preflight] could not fetch ${apiBase}/api/features (${err}) — skipping validation`);
+ return;
+ }
+
+ const problems: string[] = [];
+ for (const sb of storyboards) {
+ const filterNames = [
+ ...Object.keys(sb.content.stubbedFilters),
+ ...Object.keys(sb.content.initialFilters ?? {}),
+ ];
+ for (const name of filterNames) {
+ if (!featureNames.has(name)) {
+ problems.push(`[${sb.name}] filter "${name}" is not a live /api/features name`);
+ }
+ }
+ }
+
+ const modes = new Set(
+ storyboards.flatMap((sb) => sb.content.stubbedTravelTimeFilters.map((tt) => tt.mode))
+ );
+ for (const mode of modes) {
+ try {
+ const res = await fetch(`${apiBase}/api/travel-destinations?mode=${mode}`);
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const body = (await res.json()) as { destinations: { slug: string }[] };
+ const slugs = new Set(body.destinations.map((d) => d.slug));
+ for (const sb of storyboards) {
+ for (const tt of sb.content.stubbedTravelTimeFilters) {
+ if (tt.mode === mode && !slugs.has(tt.slug)) {
+ problems.push(`[${sb.name}] travel destination "${tt.slug}" (${mode}) not on live API`);
+ }
+ }
+ }
+ } catch (err) {
+ console.warn(`[preflight] could not validate travel destinations for ${mode}: ${err}`);
+ }
+ }
+
+ if (problems.length > 0) {
+ for (const p of problems) console.error(`[preflight] FAIL: ${p}`);
+ process.exit(1);
+ }
+ console.log('[preflight] all stubbed filter names and travel slugs match the live API');
+}
+
+async function main(): Promise {
if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true });
+ await validateAgainstLiveApi();
+
for (const sb of storyboards) emitScript(sb);
// Index for shell loops — each entry has every field render.sh needs to
@@ -84,4 +154,7 @@ function main(): void {
console.log(`[preflight] wrote storyboard index → ${indexPath}`);
}
-main();
+main().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
diff --git a/video/src/record.ts b/video/src/record.ts
index 49ea163..bc1e2d7 100644
--- a/video/src/record.ts
+++ b/video/src/record.ts
@@ -51,6 +51,31 @@ async function recordOne(storyboard: Storyboard): Promise {
const u = r.url();
if (u.includes('ai-filters')) console.log(`[req] ${r.method()} ${u}`);
});
+ // Debug instrumentation: per-second request-rate histogram by path bucket.
+ // The desktop take intermittently freezes for ~30s after heavy filter
+ // churn; this shows whether a request flood is what's choking the page.
+ if (process.env.VIDEO_DEBUG_REQ_RATE === '1') {
+ const counts = new Map();
+ page.on('request', (r) => {
+ let bucket: string;
+ try {
+ const u = new URL(r.url());
+ bucket = u.pathname.split('/').slice(0, 3).join('/');
+ } catch {
+ bucket = 'other';
+ }
+ counts.set(bucket, (counts.get(bucket) ?? 0) + 1);
+ });
+ const timer = setInterval(() => {
+ if (counts.size === 0) return;
+ const top = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
+ console.log(
+ `[req-rate] ${top.map(([k, v]) => `${k}=${v}`).join(' ')}`
+ );
+ counts.clear();
+ }, 1000);
+ timer.unref();
+ }
await installDemoRoutes(page, storyboard);
const ctx = await prepareTimeline(page, storyboard);
diff --git a/video/src/routes.ts b/video/src/routes.ts
index 49b77e6..167496d 100644
--- a/video/src/routes.ts
+++ b/video/src/routes.ts
@@ -13,6 +13,15 @@ export function dashboardUrl(storyboard: Storyboard): string {
lon: String(view.lon),
zoom: String(view.zoom),
});
+ // Cold-open filters: applied through the URL so the first frame already
+ // shows a filtered map. Numeric features use name:min:max, enums name:a|b.
+ for (const [name, value] of Object.entries(storyboard.content.initialFilters ?? {})) {
+ const entry =
+ typeof value[0] === 'number'
+ ? `${name}:${value[0]}:${value[1]}`
+ : `${name}:${(value as string[]).join('|')}`;
+ params.append('filter', entry);
+ }
for (const tt of storyboard.content.stubbedTravelTimeFilters) {
params.append('tt', `${tt.mode}:${tt.slug}:${tt.label}:${tt.min ?? 0}:${tt.max ?? 120}`);
}
diff --git a/video/src/runner.ts b/video/src/runner.ts
index 8d27e23..9707586 100644
--- a/video/src/runner.ts
+++ b/video/src/runner.ts
@@ -101,7 +101,10 @@ async function runCue(
videoTimeMs: cursor.ms + leadInMs,
durationMs: measuredAudioMs,
});
- await showCaption(ctx.page, cue.text, cue.captionPlacement);
+ // The spoken line is never rendered; only an explicit short caption is.
+ if (cue.caption) {
+ await showCaption(ctx.page, cue.caption, cue.captionPlacement);
+ }
const during = cue.during ?? [];
const declaredSum = during.reduce((s, a) => s + a.durationMs, 0);
@@ -126,7 +129,9 @@ async function runCue(
cursor.ms += duringElapsed;
}
- await hideCaption(ctx.page);
+ if (cue.caption) {
+ await hideCaption(ctx.page);
+ }
for (const step of cue.tail ?? []) {
cursor.ms += await runStep(ctx, step);
@@ -185,6 +190,12 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise {
}
case 'click': {
const selectionVersion = ctx.dashboard.getSelectionStatsVersion();
+ // Hexagon targets are projected from the latest map response; make
+ // sure that response corresponds to the settled viewport (a zoom in
+ // the previous cue may still have postcode fetches in flight).
+ if (step.target.kind === 'hexagon') {
+ await ctx.dashboard.waitForApiIdle(3000);
+ }
const candidates =
step.target.kind === 'hexagon' && step.waitForSelectionReady
? await ctx.dashboard.visibleHexagonTargets(4)
@@ -228,7 +239,7 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise {
const mapVersion = ctx.dashboard.getMapDataVersion();
const delta = step.direction === 'out' ? -MAP_ZOOM_WHEEL_DELTA : MAP_ZOOM_WHEEL_DELTA;
const handled = await ctx.page.evaluate(
- async ({ x, y, steps, durationMs, direction }) => {
+ async ({ x, y, steps, durationMs, direction, center }) => {
const root = document.querySelector('.maplibregl-map') as HTMLElement | null;
const fiberKey = root
? Object.getOwnPropertyNames(root).find((key) => key.startsWith('__reactFiber$'))
@@ -264,6 +275,12 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise {
zoom: number,
options: { around?: unknown; duration?: number; essential?: boolean }
) => void;
+ easeTo?: (options: {
+ center?: unknown;
+ zoom?: number;
+ duration?: number;
+ essential?: boolean;
+ }) => void;
};
if (
typeof mapApi.getCanvas !== 'function' ||
@@ -281,7 +298,16 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise {
const minZoom = mapApi.getMinZoom?.() ?? 0;
const maxZoom = mapApi.getMaxZoom?.() ?? 22;
const targetZoom = Math.max(minZoom, Math.min(maxZoom, mapApi.getZoom() + zoomDelta));
- mapApi.zoomTo(targetZoom, { around, duration: durationMs, essential: true });
+ if (center && typeof mapApi.easeTo === 'function') {
+ mapApi.easeTo({
+ center: around,
+ zoom: targetZoom,
+ duration: durationMs,
+ essential: true,
+ });
+ } else {
+ mapApi.zoomTo(targetZoom, { around, duration: durationMs, essential: true });
+ }
await new Promise((resolve) => window.setTimeout(resolve, durationMs));
return true;
},
@@ -291,6 +317,7 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise {
steps: step.steps,
durationMs: step.durationMs,
direction: step.direction,
+ center: step.center ?? false,
}
);
@@ -363,6 +390,41 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise {
case 'scrollPane':
await scrollPaneTo(ctx.page, step.selector, step.top);
return;
+ case 'dragSheet': {
+ const sheet = ctx.page.locator('section[class*="rounded-t-2xl"]').first();
+ const sheetBox = await sheet.boundingBox().catch(() => null);
+ if (!sheetBox) return; // desktop layout — nothing to drag
+ const handle = ctx.page
+ .locator('section[class*="rounded-t-2xl"] [class*="touch-none"]')
+ .first();
+ const handleBox = (await handle.boundingBox().catch(() => null)) ?? {
+ x: sheetBox.x,
+ y: sheetBox.y + 4,
+ width: sheetBox.width,
+ height: 20,
+ };
+ const viewport = ctx.page.viewportSize() ?? { width: 540, height: 960 };
+ const startX = handleBox.x + handleBox.width / 2;
+ const startY = handleBox.y + handleBox.height / 2;
+ // The sheet resizes so its top tracks the pointer (the component
+ // clamps to its own min/max). Aim the pointer where the handle would
+ // sit when the sheet covers `toHeightFrac` of the viewport.
+ const handleOffset = startY - sheetBox.y;
+ const endY = viewport.height * (1 - step.toHeightFrac) + handleOffset;
+ const approachMs = Math.min(260, Math.max(140, Math.round(step.durationMs * 0.25)));
+ const dragMs = Math.max(180, step.durationMs - approachMs - 80);
+ await smoothMove(ctx.page, ctx.cursor, { x: startX, y: startY }, { durationMs: approachMs });
+ await ctx.page.mouse.down();
+ await smoothMove(
+ ctx.page,
+ { x: startX, y: startY },
+ { x: startX, y: endY },
+ { durationMs: dragMs, realMouse: true }
+ );
+ await ctx.page.mouse.up();
+ ctx.cursor = { x: startX, y: endY };
+ return;
+ }
case 'openFilterGroup':
// Click is idempotent: if the group is already expanded, the click
// would collapse it — which we don't want. Detect via aria-expanded
@@ -375,6 +437,9 @@ async function runActivity(ctx: ScriptCtx, step: Activity): Promise {
trigger.click();
}, step.selector);
return;
+ case 'pressKey':
+ await ctx.page.keyboard.press(step.key);
+ return;
}
}
@@ -395,7 +460,9 @@ async function resolveTarget(
y: Math.round(Math.min(1, Math.max(0, target.yFrac)) * viewport.height),
};
}
- const box = await ctx.page.locator(target.selector).boundingBox();
+ // Bounded wait: a storyboard pointing at a missing element should fail in
+ // seconds with a clear error, not stall for Playwright's default 30s.
+ const box = await ctx.page.locator(target.selector).boundingBox({ timeout: 5000 });
if (!box) throw new Error(`No bounding box for selector: ${target.selector}`);
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
}
diff --git a/video/src/script.ts b/video/src/script.ts
index 87160ed..3a35cfd 100644
--- a/video/src/script.ts
+++ b/video/src/script.ts
@@ -143,6 +143,13 @@ export type Activity =
steps: number;
durationMs: number;
direction?: 'in' | 'out';
+ /**
+ * When true, the camera eases so `target` ends up at the canvas
+ * centre (instead of staying pinned at its current screen position).
+ * Use for "zoom into the best match" shots so the payoff lands
+ * centre-frame rather than wherever the match happened to be.
+ */
+ center?: boolean;
waitForMapSettled?: boolean;
timeoutMs?: number;
}
@@ -178,28 +185,45 @@ export type Activity =
* scroll through the property-stats drawer after a postcode click.
*/
| { kind: 'scrollPane'; selector: string; top: number; durationMs: number }
+ /**
+ * Drag the MobileBottomSheet's grab handle so the sheet ends up covering
+ * `toHeightFrac` of the viewport height. The component clamps to its own
+ * min/max, so 0 collapses the sheet to its grab-handle sliver and the map
+ * becomes the hero of the frame. No-op when the sheet isn't in the DOM
+ * (desktop layouts).
+ */
+ | { kind: 'dragSheet'; toHeightFrac: number; durationMs: number }
/**
* Click the header of a collapsible filter group (e.g. "Transport",
* "Schools") so the cards beneath it become visible. Idempotent —
* if the group is already open this is a no-op click.
*/
- | { kind: 'openFilterGroup'; selector: string; durationMs: number };
+ | { kind: 'openFilterGroup'; selector: string; durationMs: number }
+ /** Press a keyboard key (e.g. 'Escape' to dismiss a modal). */
+ | { kind: 'pressKey'; key: string; durationMs: number };
/**
* A narration cue + the activities that play alongside it.
*
- * gapBeforeMs : silent wall-time before the caption appears (= silence in
+ * text : the SPOKEN narration line (TTS input). Never rendered on
+ * screen — what's said must not also be shown.
+ * caption : optional SHORT on-screen chip (≤6 words) complementing the
+ * narration. Distinct from `text` by design: visual hooks
+ * ("You can't hear a photo") and stats live here, the story
+ * lives in the voice. Omit for no on-screen text.
+ * gapBeforeMs : silent wall-time before the cue starts (= silence in
* audio between the previous cue ending and this one).
- * during : activities that play WHILE the caption is on screen. The
+ * during : activities that play WHILE the cue's audio runs. The
* sum of declared durations must be ≤ the measured audio
- * duration; the runner pads short blocks so the caption stays
- * on for the full cue. Sum > measured is a hard error.
- * tail : activities that run AFTER the caption hides, before the
- * next cue's gapBefore starts. Use it for dwells/transitions
- * that aren't tied to spoken words.
+ * duration; the runner pads short blocks so the cue lasts
+ * the full audio. Sum > measured is a hard error.
+ * tail : activities that run AFTER the cue's audio (and caption)
+ * end, before the next cue's gapBefore starts. Use it for
+ * dwells/transitions that aren't tied to spoken words.
*/
export interface Cue {
text: string;
+ caption?: string;
/** Optional cue-specific caption layout for shots where the default lower-third hides the product. */
captionPlacement?: 'side';
gapBeforeMs: number;
@@ -223,6 +247,14 @@ export interface VideoConfig {
* If unset, the viewport comes from `viewportFor(aspect)`.
*/
viewport?: { width: number; height: number };
+ /**
+ * Visual style of the injected cursor. 'arrow' (default) renders the
+ * classic pointer — right for desktop demos. 'touch' renders a soft
+ * fingertip dot, which reads as a phone gesture on 9:16 mobile cuts
+ * (an arrow cursor on a phone-shaped video instantly breaks the
+ * illusion that you're watching the mobile product).
+ */
+ cursorStyle?: 'arrow' | 'touch';
/** WebM bitrate passed to libvpx, e.g. "8M" or "18M". */
webmBitrate: string;
/** Final fps after the trim/resample pass. */
@@ -273,6 +305,12 @@ export interface ContentConfig {
/** Cold-open zoom multiplier on the AI card. */
aiZoomScale: number;
initialMapView: { lat: number; lon: number; zoom: number };
+ /**
+ * Filters applied via the dashboard URL before the recording starts, so a
+ * storyboard can cold-open on an already-filtered map without burning
+ * screen time on a type+submit. Keys must be real /api/features names.
+ */
+ initialFilters?: Record;
stubbedFilters: Record;
stubbedTravelTimeFilters: TravelTimeFilter[];
travelTimeCardSelector: string;
diff --git a/video/src/storyboard.ts b/video/src/storyboard.ts
index 1f45a4a..384eff8 100644
--- a/video/src/storyboard.ts
+++ b/video/src/storyboard.ts
@@ -13,12 +13,24 @@ type FormFactor = 'desktop' | 'mobile';
/**
* The list of demo videos to render, in order.
*
- * Each entry is a fully self-contained Storyboard: video knobs (aspect,
- * bitrate, fps), voice persona (Qwen3-TTS instruct + language + sampling),
- * stubbed AI response, brand strings, AND the cue list. There is no shared
- * global state. The exported array can contain generated variants, so a
- * shared visual storyboard can render once per language without repeating
- * its activity sequence.
+ * Authoring rules (these are what earlier cuts kept getting wrong):
+ *
+ * 1. SAID ≠ SHOWN. The spoken `text` is never rendered on screen, and it
+ * must never describe what the viewer can already see ("now we zoom
+ * in…"). The voice carries intent and benefit; the product carries
+ * proof. The optional `caption` is a ≤6-word hook chip that says
+ * something DIFFERENT from the narration.
+ * 2. TIGHT TIMELINES. Cues are 1–2 short sentences (~0.45s/word with the
+ * current voices). Homepage demo ≈45s, ads ≈15–20s. Tails are short
+ * breaths, not dwells.
+ * 3. THE PRODUCT IS THE HERO. Every cue has the dashboard doing real work
+ * (typing, submitting, dragging, zooming, tapping postcodes, scrolling
+ * real data). On 9:16 cuts the bottom sheet gets dragged out of the way
+ * whenever the map is the story.
+ * 4. Filter names MUST match live /api/features exactly (e.g.
+ * "Serious crime (avg/yr)", "Distance to nearest amenity (Waitrose)
+ * (km)") — wrong names silently no-op and the map never changes.
+ * preflight.ts validates every stubbed name against the live API.
*
* `name` doubles as the on-disk slug. The pipeline writes per-storyboard
* artefacts to `output//` and publishes `.mp4` / `.jpg`
@@ -28,42 +40,56 @@ type FormFactor = 'desktop' | 'mobile';
* Audio is generated first (one batched Qwen call per storyboard, using
* its own voice config), so each cue's actual duration is known before
* recording. The runner sizes each cue's wall-time to the measured audio
- * length, padding short `during` blocks with a trailing wait. Inter-cue
- * spacing is controlled here via `gapBeforeMs` (silence in audio) plus
- * optional `tail` activities (visual movement after the caption hides,
- * before the next cue's gap).
+ * length, padding short `during` blocks with a trailing wait.
*/
-// Cold-open wrapper-zoom into the AI card. Desktop only — the card is
-// small relative to the 1920x1080 viewport, so leaning in sells the
-// "natural-language search" beat. On mobile the AI card is already the
-// most prominent thing on screen (it sits at the top of the bottom
-// sheet which covers ~44% of the viewport), so we skip the wrapper zoom
-// entirely — see buildPre().
-const AI_ZOOM_SCALE_DESKTOP = 2.05;
+// School features as served by live /api/features. The data pipeline moved
+// to modelled catchment counts ("Good+ primary school catchments"), which the
+// local stack already serves; prod still serves the older "…within 2km" names
+// until that deploy lands. Preflight fails loudly if these drift from
+// whichever API render.sh is pointed at — flip them back if you render against
+// prod before the catchment model deploys there.
+const SCHOOL_GOOD_PRIMARY = 'Good+ primary school catchments';
+const SCHOOL_OUTSTANDING_PRIMARY = 'Outstanding primary school catchments';
+
+// Cold-open lean-in on the AI card. Desktop only; kept moderate so the
+// map remains visible on the right (zoomTo clamps the pan so the app
+// always covers the full frame — no backdrop voids).
+const AI_ZOOM_SCALE_DESKTOP = 1.45;
const TT_CARD_SELECTOR = '[data-filter-name="tt_0"]';
const TT_SLIDER_MAX = 120;
const TT_DRAG_FROM_MIN = 35;
-const TT_DRAG_TO_MIN = 20;
+// 25 (not 20): tight enough that the drag visibly prunes the map, loose
+// enough that street-level central London keeps plenty of matching
+// postcodes — at 20 the brief emptied the centre and the postcode tap had
+// nothing fresh to land on (the drawer then opened in its "filtered stats
+// are empty" fallback).
+const TT_DRAG_TO_MIN = 25;
-// Where on the map the cue-4 zoom-in lands. Desktop targets a fixed pixel
+// Where on the map the demo zoom-in lands. Desktop targets a fixed pixel
// in the 1920x1080 viewport; mobile targets the upper-third of the visible
-// map area (the bottom ~44% of the 540x960 viewport is occupied by the
-// MobileBottomSheet, so a centre-screen point would click through to the
-// sheet, not the map).
+// map area (the bottom of the 540x960 viewport is occupied by the
+// MobileBottomSheet until it gets dragged away).
const MAP_FOCUS_DESKTOP = vfrac(1140 / 1920, 605 / 1080);
-const MAP_FOCUS_MOBILE = vfrac(0.5, 0.3);
+const MAP_FOCUS_MOBILE = vfrac(0.5, 0.34);
const HOMEPAGE_RIGHT_PANE_SELECTOR =
'[data-tutorial="right-pane"], .fixed.inset-0.z-50:has(button[aria-label="Close drawer"])';
-// Mobile mapZoom intensity. Keep mobile below the old 18-step drill that
-// overshot into featureless street-level tiles, but make the homepage pass
-// visibly break from city blobs into postcode/street scale.
const MOBILE_MAP_ZOOM_STEPS = 9;
const MOBILE_MAP_ZOOM_MS = 2200;
-const DESKTOP_MAP_ZOOM_STEPS = 18;
-const DESKTOP_MAP_ZOOM_MS = 4300;
+// 11 wheel-steps ≈ +3.1 zoom: deep enough that hexagons become postcode
+// polygons, shallow enough that several matching blocks stay in frame and
+// the deck.gl layer count doesn't thrash the main thread (14+ steps dove
+// into sparse street tiles and stalled every page.evaluate for seconds).
+const DESKTOP_MAP_ZOOM_STEPS = 11;
+const DESKTOP_MAP_ZOOM_MS = 2800;
+
+// Bottom-sheet height fractions for dragSheet. The MobileBottomSheet clamps
+// to its own minimum (~132px at 960h ≈ 0.14), so aiming slightly below that
+// pins it to the grab-handle sliver and hands the frame to the map.
+const SHEET_DOWN = 0.12;
+const SHEET_UP = 0.5;
type RecordingLocale = 'en' | 'de' | 'zh' | 'hi';
@@ -76,6 +102,10 @@ interface RecordingLocalization {
promptText: string;
travelTimeLabel: string;
exportButtonTitle: string;
+ /** Text of the ExportMenu modal's full-width confirm button (header.exportLabel). */
+ exportConfirmLabel: string;
+ /** Localized aria-label of the mobile drawer's close button (mobileDrawer.closeDrawer). */
+ closeDrawerLabel: string;
colourMapTitle: string;
brand: {
name: string;
@@ -83,14 +113,14 @@ interface RecordingLocalization {
url: string;
};
cues: {
- describe: string;
- prompt: string;
- dashboard: string;
- filters: string;
- zoom: string;
+ hook: string;
+ brief: string;
+ search: string;
+ commute: string;
+ streets: string;
open: string;
- details: string;
shortlist: string;
+ outro: string;
};
}
@@ -98,7 +128,6 @@ interface RecordingLocalization {
// as a bare domain. Keep this constant in URL form here since /api/* calls
// and other internal links still expect a scheme.
const BRAND_URL = 'https://perfect-postcode.co.uk';
-const BRAND_DOMAIN = 'perfect-postcode.co.uk';
const RECORDING_LOCALIZATIONS: Record = {
en: {
@@ -111,9 +140,11 @@ const RECORDING_LOCALIZATIONS: Record =
voiceReferenceText:
"Welcome to the demonstration. This is the narrator voice you'll hear throughout the video.",
promptText:
- 'First home under £315k, 35 min to Manchester, good schools, check crime, road noise, tree cover, fast broadband',
- travelTimeLabel: 'Manchester city centre',
+ 'First home under £600k, 35 min to central London, good schools, low crime, quiet street, fast broadband',
+ travelTimeLabel: 'Central London',
exportButtonTitle: 'Export to Excel',
+ exportConfirmLabel: 'Export',
+ closeDrawerLabel: 'Close drawer',
colourMapTitle: 'Colour map',
brand: {
name: 'Perfect Postcode',
@@ -121,18 +152,17 @@ const RECORDING_LOCALIZATIONS: Record =
url: BRAND_URL,
},
cues: {
- describe: 'A Manchester first-time buyer wants to stop wasting Saturdays on the wrong streets.',
- prompt:
- 'They type the whole brief: under £315k, thirty-five minutes to town, good schools, low crime, quieter roads, trees, and fast broadband.',
- dashboard:
- 'The map keeps only the postcodes that match. The rest of the country drops away.',
- filters:
- 'Now tweak it: cut the commute to twenty minutes and colour the map by travel time.',
- zoom: 'Zoom in until the blobs become streets, parks, and postcode blocks.',
- open: 'Open one block that still passes the filters.',
- details:
- 'On the right, you can see why it passed: journey time, listing links, Street View, sold prices, schools, crime, the noise number, and the tree score.',
- shortlist: 'Export those postcodes and only search there.',
+ hook: "You can't view your way to the right area.",
+ brief:
+ 'So start with the brief. Price, commute, schools, even how quiet the street is.',
+ search: 'One search, and England shrinks to the postcodes that fit.',
+ commute: 'Push the commute down, and the map answers in seconds.',
+ streets: 'Down at street level, the strongest streets start to stand out.',
+ open:
+ 'Open one, and it shows its work. Sold prices, schools, crime, noise, broadband.',
+ shortlist:
+ 'Shortlist the winners, export them, and only spend Saturdays where it counts.',
+ outro: 'Stop guessing postcodes. Start choosing them.',
},
},
de: {
@@ -145,27 +175,29 @@ const RECORDING_LOCALIZATIONS: Record =
voiceReferenceText:
'Willkommen zur Demonstration. Diese Sprecherstimme hörst du im gesamten Video.',
promptText:
- 'Wohnung unter £300k, 35 Min. nach Manchester, gute Schulen, niedrige Kriminalität, ruhige Straßen',
- travelTimeLabel: 'Stadtzentrum Manchester',
- exportButtonTitle: 'Als Excel exportieren',
+ 'Wohnung unter £600k, 35 Min. ins Zentrum von London, gute Schulen, niedrige Kriminalität, ruhige Straßen',
+ travelTimeLabel: 'Zentrum von London',
+ exportButtonTitle: 'Nach Excel exportieren',
+ exportConfirmLabel: 'Exportieren',
+ closeDrawerLabel: 'Drawer schließen',
colourMapTitle: 'Karte einfärben',
brand: {
name: 'Perfect Postcode',
- tagline: 'Wissen, wo du suchen solltest, bevor Inserate deine Suche bestimmen.',
+ tagline: 'Erst die Gegend, dann das Haus.',
url: BRAND_URL,
},
cues: {
- describe: 'Wähle kein Zuhause durch endloses Scrollen.',
- prompt:
- 'Beschreibe, was dir wichtig ist. Budget, Pendelzeit, Schulen, alles.',
- dashboard: 'Die Karte zeigt jede passende Postleitzahl in ganz England.',
- filters: 'Ein Regler bewegt sich. Die Karte antwortet sofort.',
- zoom: 'Jetzt von der Stadtansicht bis zu echten Straßen zoomen.',
- open: 'Öffne einen Treffer und sieh, warum er übrig bleibt.',
- details:
- 'Öffne eine Postleitzahl. Preise. Schulen. Kriminalität. Lärm. Alles auf einer Karte.',
+ hook: 'Das richtige Viertel findest du nicht durch Besichtigungen.',
+ brief:
+ 'Fang mit dem Briefing an. Preis, Pendelzeit, Schulen, sogar wie ruhig die Straße ist.',
+ search: 'Eine Suche, und England schrumpft auf die Postleitzahlen, die passen.',
+ commute: 'Verkürze den Arbeitsweg, und die Karte antwortet in Sekunden.',
+ streets: 'Auf Straßenebene stechen die besten Ecken von selbst hervor.',
+ open:
+ 'Öffne eine, und sie zeigt ihre Daten. Verkaufspreise, Schulen, Kriminalität, Lärm, Internet.',
shortlist:
- 'Mit dieser Auswahl zu den Inseraten. Du weißt jetzt, wo du suchen sollst.',
+ 'Speichere die Favoriten, exportiere sie, und verbringe deine Samstage nur dort, wo es zählt.',
+ outro: 'Hör auf, Postleitzahlen zu raten. Fang an, sie zu wählen.',
},
},
zh: {
@@ -176,24 +208,26 @@ const RECORDING_LOCALIZATIONS: Record =
'Calm and cheerful Mandarin Chinese male narrator with clear standard Mandarin ' +
'pronunciation and a friendly, practical delivery.',
voiceReferenceText: '欢迎观看演示。整段视频都会使用这位旁白的声音。',
- promptText: '30万英镑以内的公寓,35分钟到曼彻斯特,学校好,犯罪率低,街道安静',
- travelTimeLabel: '曼彻斯特市中心',
+ promptText: '60万英镑以内的公寓,35分钟到伦敦市中心,学校好,犯罪率低,街道安静',
+ travelTimeLabel: '伦敦市中心',
exportButtonTitle: '导出为 Excel',
- colourMapTitle: '为地图着色',
+ exportConfirmLabel: '导出',
+ closeDrawerLabel: '关闭侧栏',
+ colourMapTitle: '地图着色',
brand: {
name: 'Perfect Postcode',
- tagline: '先知道该看哪里,再让房源牵着你走。',
+ tagline: '先选对街区,再选房子。',
url: BRAND_URL,
},
cues: {
- describe: '别再靠刷房源挑家了。',
- prompt: '用日常话告诉地图你想要的家。预算、通勤、学校,什么都行。',
- dashboard: '地图点亮每一个符合条件的英格兰邮编。',
- filters: '动一个滑块,地图立刻给答案。',
- zoom: '现在从城市范围放大到真实街道。',
- open: '打开一个匹配项,看看它为什么留下来。',
- details: '打开任意邮编。成交价、学校、犯罪率、噪音,一目了然。',
- shortlist: '带着这份清单去房源网站。现在你知道该在哪儿找了。',
+ hook: '光靠看房,看不出哪个街区适合你。',
+ brief: '先列条件。预算、通勤、学校,甚至街道安不安静。',
+ search: '搜索一次,全英格兰只剩下符合条件的邮编。',
+ commute: '把通勤时间调短,地图几秒内就给出答案。',
+ streets: '放大到街道层面,好街区自己浮现出来。',
+ open: '点开一个,它会摆出证据。成交价、学校、犯罪率、噪音、宽带。',
+ shortlist: '把心仪的区域导出,周末只去值得去的地方。',
+ outro: '别再猜邮编,开始挑邮编。',
},
},
hi: {
@@ -205,29 +239,40 @@ const RECORDING_LOCALIZATIONS: Record =
'and a friendly, practical delivery.',
voiceReferenceText:
"Welcome to the demonstration. This is the narrator voice you'll hear throughout the video.",
- promptText: 'Flat under £300k, 35 min to Manchester, good schools, low crime, quiet streets',
- travelTimeLabel: 'Manchester city centre',
- exportButtonTitle: 'Excel में निर्यात करें',
- colourMapTitle: 'नक्शे को रंगें',
+ promptText: 'Flat under £600k, 35 min to central London, good schools, low crime, quiet streets',
+ travelTimeLabel: 'Central London',
+ exportButtonTitle: 'Excel में export करें',
+ exportConfirmLabel: 'Export',
+ closeDrawerLabel: 'ड्रॉअर बंद करें',
+ colourMapTitle: 'मानचित्र रंगें',
brand: {
name: 'Perfect Postcode',
- tagline: 'Know where to look before listings take over.',
+ tagline: 'Find the area before the house.',
url: BRAND_URL,
},
cues: {
- describe: "Don't pick a home by scrolling listings.",
- prompt:
- 'Describe what you want. Budget, commute, schools, whatever matters.',
- dashboard: 'The map lights up with every postcode in England that fits.',
- filters: 'Move one slider. The map answers instantly.',
- zoom: 'Now zoom in from the city pattern to actual streets.',
- open: 'Open one match and see why it made the cut.',
- details: 'Open any postcode. Sold prices. Schools. Crime. Noise. All on one screen.',
- shortlist: 'Take your shortlist to the listings. Now you know where to search.',
+ hook: "You can't view your way to the right area.",
+ brief:
+ 'So start with the brief. Price, commute, schools, even how quiet the street is.',
+ search: 'One search, and England shrinks to the postcodes that fit.',
+ commute: 'Push the commute down, and the map answers in seconds.',
+ streets: 'Down at street level, the strongest streets start to stand out.',
+ open:
+ 'Open one, and it shows its work. Sold prices, schools, crime, noise, broadband.',
+ shortlist:
+ 'Shortlist the winners, export them, and only spend Saturdays where it counts.',
+ outro: 'Stop guessing postcodes. Start choosing them.',
},
},
};
+/**
+ * Homepage demo cues. ~45s total: hook → type brief → search → commute
+ * slider + colour layer → street-level zoom → open a postcode (real data
+ * pane) → export → outro. No on-screen captions: the homepage player runs
+ * with sound, the narration carries the story, and the product stays
+ * unobstructed.
+ */
function createCues(locale: RecordingLocale, formFactor: FormFactor): Storyboard['cues'] {
const copy = RECORDING_LOCALIZATIONS[locale];
const isMobile = formFactor === 'mobile';
@@ -235,49 +280,18 @@ function createCues(locale: RecordingLocale, formFactor: FormFactor): Storyboard
const mapZoomSteps = isMobile ? MOBILE_MAP_ZOOM_STEPS : DESKTOP_MAP_ZOOM_STEPS;
const mapZoomMs = isMobile ? MOBILE_MAP_ZOOM_MS : DESKTOP_MAP_ZOOM_MS;
const colourTravelTime = el(`${TT_CARD_SELECTOR} button[title="${copy.colourMapTitle}"]`);
- const postcodeDemoTarget = isMobile
- ? vfrac(320 / 540, 255 / 960)
- : vfrac(1087 / 1920, 520 / 1080);
- const openPostcodeTarget = postcodeDemoTarget;
- const zoomPostcodeTarget = postcodeDemoTarget;
- const cursorParkTarget = isMobile ? vfrac(0.12, 0.61) : vfrac(0.12, 0.18);
const definingCharacteristicsSelector =
'[data-tutorial="right-pane"] button:has-text("Defining characteristics"), ' +
'.fixed.inset-0.z-50:has(button[aria-label="Close drawer"]) button:has-text("Defining characteristics")';
- const shortlistActivities: Storyboard['cues'][number]['during'] =
- formFactor === 'desktop'
- ? [
- { kind: 'zoomReset', durationMs: 800 },
- {
- kind: 'click',
- target: el(`button[title="${copy.exportButtonTitle}"]`),
- durationMs: 800,
- },
- ]
- : [
- {
- kind: 'click',
- target: el('button[aria-label="Close drawer"]'),
- durationMs: 650,
- },
- {
- kind: 'mapZoom',
- target: mapFocus,
- steps: MOBILE_MAP_ZOOM_STEPS,
- durationMs: MOBILE_MAP_ZOOM_MS,
- direction: 'out',
- waitForMapSettled: true,
- timeoutMs: 12000,
- },
- ];
-
return [
{
- text: copy.cues.describe,
+ // Hook over the untouched dashboard; desktop leans into the filter
+ // panel so the brief lands big in the next beat.
+ text: copy.cues.hook,
gapBeforeMs: 0,
during: isMobile
- ? [{ kind: 'wait', durationMs: 700 }]
+ ? [{ kind: 'wait', durationMs: 500 }]
: [
{
kind: 'zoomTo',
@@ -289,148 +303,172 @@ function createCues(locale: RecordingLocale, formFactor: FormFactor): Storyboard
tail: [{ kind: 'wait', durationMs: 150 }],
},
{
- text: copy.cues.prompt,
- gapBeforeMs: 0,
+ text: copy.cues.brief,
+ gapBeforeMs: 200,
during: [
{
kind: 'type',
selector: '[data-tutorial="ai-filters"] textarea',
text: copy.promptText,
- durationMs: 4300,
+ durationMs: 4200,
},
],
tail: [{ kind: 'wait', durationMs: 120 }],
},
{
- text: copy.cues.dashboard,
- gapBeforeMs: 300,
+ text: copy.cues.search,
+ gapBeforeMs: 250,
during: [
{
kind: 'submitForm',
formSelector: '[data-tutorial="ai-filters"] form',
- durationMs: 2200,
+ durationMs: 2000,
waitForMapSettled: true,
timeoutMs: 15000,
},
- { kind: 'zoomReset', durationMs: 900 },
+ ...(isMobile ? [] : [{ kind: 'zoomReset', durationMs: 800 } as Activity]),
],
- tail: [{ kind: 'wait', durationMs: 300 }],
+ tail: [{ kind: 'wait', durationMs: 250 }],
},
-
{
- text: copy.cues.filters,
- gapBeforeMs: 500,
+ text: copy.cues.commute,
+ gapBeforeMs: 300,
during: [
{
kind: 'dragSlider',
thumbSelector: `${TT_CARD_SELECTOR} [role="slider"] >> nth=1`,
trackSelector: `${TT_CARD_SELECTOR} [data-orientation="horizontal"] >> nth=0`,
toFraction: TT_DRAG_TO_MIN / TT_SLIDER_MAX,
- durationMs: 1800,
+ durationMs: 1600,
},
- { kind: 'click', target: colourTravelTime, durationMs: 750 },
- ],
- tail: [{ kind: 'wait', durationMs: 350 }],
- },
-
- {
- text: copy.cues.zoom,
- gapBeforeMs: 500,
- during: [
- { kind: 'cursorScale', scale: 1.4, durationMs: 200 },
- {
- kind: 'mapZoom',
- target: zoomPostcodeTarget,
- steps: mapZoomSteps,
- durationMs: mapZoomMs,
- },
- ],
- tail: [
- { kind: 'moveCursor', target: cursorParkTarget, durationMs: 250 },
- { kind: 'wait', durationMs: 120 },
- ],
- },
-
- {
- text: copy.cues.open,
- gapBeforeMs: 200,
- during: [
- {
- kind: 'click',
- target: openPostcodeTarget,
- durationMs: 1200,
- waitForSelectionReady: true,
- timeoutMs: 6000,
- },
- { kind: 'cursorScale', scale: 1, durationMs: 250 },
+ { kind: 'click', target: colourTravelTime, durationMs: 700 },
],
tail: [{ kind: 'wait', durationMs: 300 }],
},
-
{
- text: copy.cues.details,
- captionPlacement: isMobile ? undefined : 'side',
+ text: copy.cues.streets,
+ gapBeforeMs: 300,
+ during: [
+ ...(isMobile
+ ? [{ kind: 'dragSheet', toHeightFrac: SHEET_DOWN, durationMs: 800 } as Activity]
+ : []),
+ { kind: 'cursorScale', scale: isMobile ? 1 : 1.4, durationMs: 200 },
+ {
+ // Zoom AROUND the strongest visible match (hex()) so the
+ // destination area is guaranteed to contain matching postcodes —
+ // a fixed pixel target can dive into a part of town the filters
+ // just emptied. Settled so the next cue's hex() click projects
+ // against the postcode response for the FINAL viewport.
+ kind: 'mapZoom',
+ target: hex(),
+ steps: mapZoomSteps,
+ durationMs: mapZoomMs,
+ center: true,
+ waitForMapSettled: true,
+ timeoutMs: 10000,
+ },
+ ],
+ tail: [{ kind: 'wait', durationMs: 200 }],
+ },
+ {
+ text: copy.cues.open,
gapBeforeMs: 250,
+ during: [
+ {
+ kind: 'click',
+ target: hex(),
+ durationMs: 1100,
+ waitForSelectionReady: true,
+ timeoutMs: 8000,
+ },
+ ...(isMobile
+ ? []
+ : [
+ {
+ kind: 'zoomTo',
+ target: el(HOMEPAGE_RIGHT_PANE_SELECTOR),
+ scale: 1.3,
+ durationMs: 850,
+ } as Activity,
+ ]),
+ {
+ kind: 'scrollPane',
+ selector: HOMEPAGE_RIGHT_PANE_SELECTOR,
+ top: isMobile ? 430 : 380,
+ durationMs: 850,
+ },
+ ],
+ tail: [
+ {
+ kind: 'clickIfVisible',
+ target: el(definingCharacteristicsSelector),
+ durationMs: 600,
+ timeoutMs: 700,
+ },
+ {
+ kind: 'scrollPane',
+ selector: HOMEPAGE_RIGHT_PANE_SELECTOR,
+ top: isMobile ? 760 : 900,
+ durationMs: 850,
+ },
+ { kind: 'wait', durationMs: 350 },
+ ],
+ },
+ {
+ text: copy.cues.shortlist,
+ gapBeforeMs: 300,
during: isMobile
? [
{
- kind: 'scrollPane',
- selector: HOMEPAGE_RIGHT_PANE_SELECTOR,
- top: 430,
- durationMs: 900,
- },
- {
+ // The drawer's close-button aria-label is localized
+ // (mobileDrawer.closeDrawer), so use the per-locale string.
+ // clickIfVisible keeps a label mismatch from crashing the
+ // take — worst case the drawer lingers behind the zoom-out.
kind: 'clickIfVisible',
- target: el(definingCharacteristicsSelector),
+ target: el(`button[aria-label="${copy.closeDrawerLabel}"]`),
durationMs: 650,
- timeoutMs: 700,
+ timeoutMs: 1500,
},
{
- kind: 'scrollPane',
- selector: HOMEPAGE_RIGHT_PANE_SELECTOR,
- top: 700,
- durationMs: 850,
+ kind: 'mapZoom',
+ target: mapFocus,
+ steps: 6,
+ durationMs: 1800,
+ direction: 'out',
},
]
: [
+ { kind: 'zoomReset', durationMs: 800 },
{
- kind: 'zoomTo',
- target: el(HOMEPAGE_RIGHT_PANE_SELECTOR),
- scale: 1.35,
- durationMs: 950,
- },
- {
- kind: 'scrollPane',
- selector: HOMEPAGE_RIGHT_PANE_SELECTOR,
- top: 360,
- durationMs: 850,
- },
- {
+ // IfVisible: the export button only renders on ≥1024px-wide
+ // dashboards with a licensed user — a missing button must not
+ // crash the take, just skip the ripple.
kind: 'clickIfVisible',
- target: el(definingCharacteristicsSelector),
- durationMs: 650,
- timeoutMs: 700,
+ target: el(`button[title="${copy.exportButtonTitle}"]`),
+ durationMs: 800,
+ timeoutMs: 1500,
},
+ { kind: 'wait', durationMs: 400 },
{
- kind: 'scrollPane',
- selector: HOMEPAGE_RIGHT_PANE_SELECTOR,
- top: 920,
- durationMs: 850,
+ // Complete the flow: the header button opens the ExportMenu
+ // modal; confirm it so the demo shows a finished export
+ // rather than an abandoned dialog.
+ kind: 'clickIfVisible',
+ target: el(`button[class*="w-full"]:has-text("${copy.exportConfirmLabel}")`),
+ durationMs: 700,
+ timeoutMs: 1500,
},
],
- tail: [{ kind: 'wait', durationMs: 700 }],
+ tail: [
+ { kind: 'wait', durationMs: 500 },
+ // Insurance: if the modal lingers (slow export resolve), dismiss it
+ // before the outro card comes up.
+ { kind: 'pressKey', key: 'Escape', durationMs: 150 },
+ ],
},
-
{
- text: copy.cues.shortlist,
- gapBeforeMs: 500,
- during: shortlistActivities,
- tail: [{ kind: 'wait', durationMs: 650 }],
- },
-
- {
- text: `${copy.brand.name}. ${copy.brand.tagline}`,
- gapBeforeMs: 600,
+ text: copy.cues.outro,
+ gapBeforeMs: 450,
during: [
{
kind: 'showOutro',
@@ -440,18 +478,12 @@ function createCues(locale: RecordingLocale, formFactor: FormFactor): Storyboard
durationMs: 0,
},
],
- tail: [{ kind: 'wait', durationMs: 1500 }],
+ tail: [{ kind: 'wait', durationMs: 1300 }],
},
];
}
-function buildPre(formFactor: FormFactor): Storyboard['pre'] {
- if (formFactor === 'mobile') {
- return [
- { kind: 'clearVignette', durationMs: 0 },
- { kind: 'wait', durationMs: 120 },
- ];
- }
+function buildPre(): Storyboard['pre'] {
return [
{ kind: 'clearVignette', durationMs: 0 },
{ kind: 'wait', durationMs: 120 },
@@ -470,29 +502,29 @@ function buildVideoConfig(formFactor: FormFactor): VideoConfig {
// genuinely looks mobile-styled, not desktop in portrait.
viewport: { width: 540, height: 960 },
captureScale: 2,
- // 540x960 needs less bitrate than 1080p — 4M is comfortable.
+ cursorStyle: 'touch',
webmBitrate: '4M',
outputFps: 50,
minDurationS: 10,
maxDurationS: 75,
- // Click + drawer-open moment — landing on the right-pane reveal.
- posterTimeS: 12,
+ // Street-level zoom with the sheet collapsed — the map is the frame.
+ posterTimeS: 25,
};
}
return {
aspect: '16x9',
+ // Native 1920x1080. NOTE: don't be tempted by a narrower CSS viewport —
+ // the dashboard header switches to tablet sidebar nav between 768 and
+ // 1023px and the Export button (cue 7) disappears.
captureScale: 1,
- // 8M is enough for 1920x1080 at captureScale=1; bump to 18M when
- // captureScale > 1 (supersampled) — see render.sh history if reviving
- // higher-quality cuts.
+ cursorStyle: 'arrow',
webmBitrate: '8M',
outputFps: 50,
minDurationS: 10,
maxDurationS: 75,
- // Right-pane inspection (~16s into the trimmed timeline) is the
- // clearest paused-state preview: Manchester map, filters applied,
- // right pane populated, larger narration caption visible.
- posterTimeS: 16,
+ // Right-pane inspection: London map, filters applied, data pane
+ // open — the clearest paused-state preview.
+ posterTimeS: 31,
};
}
@@ -506,20 +538,22 @@ function createRecordingStoryboard(
): Storyboard {
const copy = RECORDING_LOCALIZATIONS[locale];
// Mobile bumps the initial zoom from 11.5 → 12 so the narrower 540-wide
- // viewport still shows a Manchester-metro slice densely populated with
- // hexagons (otherwise the visible map gets dominated by Pennine moors
- // on the east edge with no matches).
+ // viewport still shows an inner-London slice densely populated with
+ // hexagons (otherwise the visible map gets dominated by the low-density
+ // outer edges with no matches).
const initialZoom = formFactor === 'mobile' ? 12 : 11.5;
// On mobile the MobileBottomSheet covers the bottom ~44% of the
// viewport, so the map's geographic centre sits roughly at the seam
// between the visible map and the sheet. Shift the centre lat ~0.04°
- // south so Manchester city centre (53.4795) lands in the upper half of
- // the visible map area instead of getting hidden under the sheet. The
+ // south so central London (51.515) lands in the upper half of the
+ // visible map area instead of getting hidden under the sheet. The
// desktop layout already has the map dominate the viewport, so it
- // keeps the original centre.
- const mapLat = formFactor === 'mobile' ? 53.4395 : 53.4795;
- const mapLon = -2.2451;
+ // keeps the original centre. -0.13 lon centres on Holborn/Bloomsbury,
+ // between the West End and the City, so the Bank-station travel filter
+ // and the deep zoom-in both land on richly matched inner-London blocks.
+ const mapLat = formFactor === 'mobile' ? 51.475 : 51.515;
+ const mapLon = -0.13;
return {
name: storyboardName(copy, formFactor),
@@ -536,27 +570,36 @@ function createRecordingStoryboard(
content: {
promptText: copy.promptText,
appLanguage: copy.appLanguage,
- // aiZoomScale is irrelevant on mobile (pre skips the zoomTo) but
- // we keep the field populated so it stays a required Config.
aiZoomScale: AI_ZOOM_SCALE_DESKTOP,
initialMapView: { lat: mapLat, lon: mapLon, zoom: initialZoom },
// Filters returned by the AI stub. Keys MUST match real feature names
- // from /api/features (verified against the running server's schema).
+ // from /api/features (preflight validates against the live server).
stubbedFilters: {
'Property type': ['Flats/Maisonettes', 'Semi-Detached'],
- 'Estimated current price': [0, 315000],
- 'Serious crime per 1k residents (avg/yr)': [0, 70],
- 'Good+ primary schools within 2km': [1, 10],
- 'Noise (dB)': [50, 70],
- 'Street tree density percentile': [25, 100],
- 'Max available download speed (Mbps)': [100, 1000],
+ // £600k (not £315k like the old Manchester cut): central London is
+ // pricier, and at £315k the price+crime combo emptied the inner
+ // boroughs. At £600k ~51k postcodes pass in-frame, dropping to ~10k
+ // once the commute is dragged to 25 min — a visible prune that still
+ // leaves the zoom something to land on (verified via /api/filter-counts).
+ 'Estimated current price': [0, 600000],
+ // Loose enough to keep the central-London map richly populated — a
+ // cap of 20 emptied the city centre and left the zoom with nothing
+ // to land on.
+ 'Serious crime (avg/yr)': [0, 40],
+ [SCHOOL_GOOD_PRIMARY]: [1, 10],
+ 'Noise (dB)': [0, 65],
+ // ≥30 Mbps, not ≥100: FTTP coverage gaps make a 100 floor empty
+ // whole suburbs, and the narration only claims "fast broadband".
+ 'Max available download speed (Mbps)': [30, 1000],
},
// Travel-time filters returned by the AI stub. Slug matches the real
- // /api/travel-destinations?mode=transit response.
+ // /api/travel-destinations?mode=transit response. Bank tube station is
+ // the central-London transit anchor served by the local stack (the
+ // older city-level "manchester" slug only exists on prod's fuller data).
stubbedTravelTimeFilters: [
{
mode: 'transit',
- slug: 'manchester',
+ slug: 'bank-tube-station',
label: copy.travelTimeLabel,
max: TT_DRAG_FROM_MIN,
},
@@ -567,7 +610,7 @@ function createRecordingStoryboard(
travelTimeDragToMin: TT_DRAG_TO_MIN,
brand: copy.brand,
},
- pre: buildPre(formFactor),
+ pre: buildPre(),
cues: createCues(locale, formFactor),
};
}
@@ -583,28 +626,25 @@ const DEMO_STORYBOARDS: Storyboard[] = RECORDING_LOCALES.flatMap((locale) =>
RECORDING_FORM_FACTORS.map((formFactor) => createRecordingStoryboard(locale, formFactor))
);
-type CityKey =
- | 'manchester'
- | 'birmingham'
- | 'bristol'
- | 'london'
- | 'leeds'
- | 'liverpool'
- | 'sheffield';
+// ---------------------------------------------------------------------------
+// Social ads.
+//
+// Every ad is one hook + one real product interaction + one closer, in
+// 15–20 seconds of 9:16. Narration starts on the first frame and never
+// reads what's on screen; the only on-screen text is a short hook chip on
+// the opening cue. The bottom sheet is dragged away whenever the map is
+// the story, and a fingertip-style cursor sells the gestures as touch.
+// ---------------------------------------------------------------------------
+
+type CityKey = 'manchester' | 'birmingham' | 'bristol' | 'london' | 'leeds';
-/**
- * Per-cue spec for a demo ad. Each cue carries its caption and the live-
- * product activities that must happen while it is on screen. Unlike the
- * old overlay-only ads, here the dashboard does the visual work — type,
- * drag, zoom, click. Scene overlays (when present at all) are short
- * hooks layered transparently over the product, not full-frame scrims.
- */
interface DemoAdCueConfig {
text: string;
+ caption?: string;
gapBeforeMs?: number;
- /** Activities that fit inside the caption's audio window. */
+ /** Activities that fit inside the cue's audio window. */
during?: Activity[];
- /** Activities that run after the caption fades, before the next gap. */
+ /** Activities that run after the cue's audio ends, before the next cue. */
tail?: Activity[];
}
@@ -612,55 +652,36 @@ interface DemoAdStoryboardConfig {
name: string;
city: CityKey;
/**
- * The text typed into the AI box. Used by the runner when a `type`
- * activity is in this ad's cues; the AI stub returns `filters` /
- * `travelTimeFilters` regardless of the typed text, so the prompt
- * value here is purely for visual veracity (matches what the
- * filters imply).
+ * The text typed into the AI box. The AI stub returns `filters` /
+ * `travelTimeFilters` regardless of the typed text, so the prompt value
+ * here is purely for visual veracity (it must match what the filters
+ * imply, or sharp-eyed viewers cry fake).
*/
promptText?: string;
+ /** Filters returned by the AI stub after a type+submit. */
filters?: Record;
+ /** Filters already applied at load (via URL) for cold-open-on-results ads. */
+ initialFilters?: Record;
travelTimeFilters?: TravelTimeFilter[];
posterTimeS?: number;
initialZoom?: number;
- /**
- * Activities to run before the cue loop starts. Default is now an
- * empty no-op (clearVignette + hide-cursor only) so narration starts
- * IMMEDIATELY at t=0. Older ads that needed a silent type+submit
- * pre-prime can pass `prePrime: adPrime(promptText)` explicitly.
- */
+ /** Activities to run before the cue loop starts (silent — keep short). */
prePrime?: Activity[];
- /**
- * Optional spoken line during the outro card. Defaults to
- * "Find your area first." — kept short and DIFFERENT from the
- * visual brand+URL so we don't double-up "Perfect Postcode
- * perfect-postcode dot co dot uk".
- */
- outroLine?: string;
+ /** Spoken line during the outro card. Must NOT repeat the card's text. */
+ outroLine: string;
cues: DemoAdCueConfig[];
}
-/**
- * Ads are always vertical 9:16 with a true mobile look. CSS viewport
- * 540x960 falls under Tailwind's `md:` 768px breakpoint, so the
- * frontend renders the mobile layout (compact header, bottom-sheet
- * filters, mobile typography). captureScale 2 gives Chromium a
- * 1080x1920 device-pixel surface internally, and `recordedSizeFor` (in
- * script.ts) now multiplies viewport × captureScale so Playwright
- * records the final mp4 at sharp 1080x1920 — proper "mobile look,
- * higher DPR" rather than a soft 540x960 upscale.
- */
const AD_VIDEO: VideoConfig = {
aspect: '9x16',
viewport: { width: 540, height: 960 },
captureScale: 2,
+ cursorStyle: 'touch',
webmBitrate: '4M',
outputFps: 50,
minDurationS: 8,
- // Generous upper bound. Ads that include a mapZoom drift longer than
- // declared (deep-zoom tile load on maplibre eats a second or two);
- // 35s comfortably covers the worst case while staying within
- // TikTok / Reels / Shorts upload limits.
+ // Generous upper bound — ads with a deep mapZoom drift a second or two
+ // past their declared budgets while tiles load.
maxDurationS: 35,
posterTimeS: 5,
};
@@ -668,22 +689,19 @@ const AD_VIDEO: VideoConfig = {
const AD_BRAND = {
name: 'Perfect Postcode',
tagline: 'Search the area before you search the listings.',
- // dom.ts strips the protocol for display in the outro CTA, so this
- // renders as "perfect-postcode.co.uk" without us having to special-case
- // the field — and other consumers (analytics, links) still get a full URL.
url: BRAND_URL,
};
/**
- * Ad voice persona. The SAME config is used across every ad so the
- * voice timbre stays consistent across the set. (Synth still mints a
- * separate `_reference.wav` per storyboard, but with identical instruct
- * / language / referenceText / seed the cloned voice converges.)
+ * Ad voice persona. The SAME config is used across every ad so the voice
+ * timbre stays consistent across the set (render.sh additionally reuses the
+ * first minted reference WAV for all of them).
*/
const AD_VOICE = {
instruct:
- 'British male creator-style narrator. Warm, calm, conversational pace. ' +
- 'No salesy enthusiasm, no exaggeration. Short sentences. Plain delivery.',
+ 'British male creator-style narrator. Warm, confident, conversational, with a ' +
+ 'slightly quick pace, like telling a friend about a great find. No salesy hype, ' +
+ 'no exaggeration. Short sentences, natural delivery.',
language: 'English',
referenceText:
'This is a short social video for people choosing where to live in England.',
@@ -693,71 +711,91 @@ const AD_VOICE = {
};
/**
- * Initial map centres for each ad.
- *
- * On the mobile 540x960 viewport the MapLibre canvas fills the full
- * viewport, but the MobileBottomSheet overlays its bottom ~50% and the
- * post-click drawer overlays more. So the VISIBLE map band is roughly
- * y = 50–470 (≈ 44 % of the viewport), with its visual centre at y ≈ 260.
- * The map's geographic centre, however, renders at the canvas centre
- * (y = 480) — hidden by the sheet.
- *
- * To make the advertised city centre land at the VISIBLE centre (y ≈ 260),
- * we shift each map's centre lat roughly 0.13° SOUTH of the true city
- * centre. The true centre then re-projects upward by ~220 px and ends up
- * squarely inside the visible map band. The shift was derived from
- * Mercator pixels-per-degree at lat 51 and zoom 10–11; for cities at
- * higher latitudes the same px offset corresponds to a slightly smaller
- * deg shift, but ±0.02° tolerance is fine.
+ * Initial map centres for each ad. Centres are shifted south of the true
+ * city centre so the interesting area sits in the upper (visible) half of
+ * the 9:16 frame while the bottom sheet is still up; once the sheet is
+ * dragged away the framing reads as comfortably centred.
*/
const CITY_VIEWS: Record = {
manchester: { lat: 53.3495, lon: -2.2451, zoom: 11.2 },
birmingham: { lat: 52.3562, lon: -1.8904, zoom: 11.0 },
bristol: { lat: 51.3245, lon: -2.5879, zoom: 11.3 },
- london: { lat: 51.3772, lon: -0.1276, zoom: 10.5 },
- leeds: { lat: 53.6708, lon: -1.5491, zoom: 11.1 },
- liverpool: { lat: 53.2784, lon: -2.9916, zoom: 11.1 },
- sheffield: { lat: 53.2511, lon: -1.4701, zoom: 11.2 },
+ london: { lat: 51.4272, lon: -0.1276, zoom: 10.4 },
+ leeds: { lat: 53.7308, lon: -1.5491, zoom: 11.0 },
};
-const AD_DEFAULT_FILTERS: Record = {
- 'Estimated current price': [0, 350000],
- 'Serious crime per 1k residents (avg/yr)': [0, 70],
- 'Outstanding primary schools within 2km': [0, 10],
-};
+// -- small helpers used by the per-ad cue lists -------------------------------
-const linger = (durationMs = 360): Activity[] => [{ kind: 'wait', durationMs }];
+const AI_TEXTAREA = '[data-tutorial="ai-filters"] textarea';
+const AI_FORM = '[data-tutorial="ai-filters"] form';
+const typeAct = (text: string, durationMs = 2600): Activity => ({
+ kind: 'type',
+ selector: AI_TEXTAREA,
+ text,
+ durationMs,
+});
+const submitSettled = (durationMs = 1400, timeoutMs = 10000): Activity => ({
+ kind: 'submitForm',
+ formSelector: AI_FORM,
+ durationMs,
+ waitForMapSettled: true,
+ timeoutMs,
+});
+const ttDragAct = (toMin: number, durationMs = 1700): Activity => ({
+ kind: 'dragSlider',
+ thumbSelector: `${TT_CARD_SELECTOR} [role="slider"] >> nth=1`,
+ trackSelector: `${TT_CARD_SELECTOR} [data-orientation="horizontal"] >> nth=0`,
+ toFraction: toMin / TT_SLIDER_MAX,
+ durationMs,
+});
+const wait = (durationMs: number): Activity => ({ kind: 'wait', durationMs });
+const sheetDown = (durationMs = 800): Activity => ({
+ kind: 'dragSheet',
+ toHeightFrac: SHEET_DOWN,
+ durationMs,
+});
+const sheetUp = (durationMs = 700): Activity => ({
+ kind: 'dragSheet',
+ toHeightFrac: SHEET_UP,
+ durationMs,
+});
+const touchShow = (): Activity => ({ kind: 'cursorScale', scale: 1, durationMs: 150 });
+const touchHide = (): Activity => ({ kind: 'cursorScale', scale: 0, durationMs: 150 });
+const mapZoomIn = (durationMs = 1500, steps = 5): Activity => ({
+ kind: 'mapZoom',
+ target: vfrac(0.5, 0.34),
+ steps,
+ durationMs,
+});
/**
- * A short, near-invisible "warm-up" run before the cue loop:
- * 1. Drop the vignette and the cursor (no mouse hover in ads).
- * 2. Type the prompt FAST (600ms — feels like a quick paste).
- * 3. Submit, so by the time cue 0 starts, the AI filter response
- * has come back and the map is already filtered.
- *
- * Result: every ad starts on a FILTERED map (hexagons / postcodes
- * already lit up). The first caption is therefore reading against
- * a visual that's already telling part of the story.
+ * Tap the centre of the highest-priority visible postcode polygon from the
+ * latest map response (robust to zoom drift — a fixed pixel target at deep
+ * zoom can land on a road or river and the drawer never opens).
*/
-function adPrime(promptText: string): Activity[] {
- return [
- { kind: 'clearVignette', durationMs: 0 },
- { kind: 'cursorScale', scale: 0, durationMs: 0 },
- { kind: 'wait', durationMs: 200 },
- {
- kind: 'type',
- selector: '[data-tutorial="ai-filters"] textarea',
- text: promptText,
- durationMs: 600,
- },
- { kind: 'submitForm', formSelector: '[data-tutorial="ai-filters"] form', durationMs: 1100 },
- // Give the map a moment to re-fetch hexagons + redraw before the
- // first narrated cue starts. /api/hexagons typically returns in
- // 500-900ms on prod; 1100ms is a comfortable cushion for the
- // post-filter redraw + first paint.
- { kind: 'wait', durationMs: 1100 },
- ];
-}
+const tapHex = (durationMs = 1000): Activity => ({
+ kind: 'click',
+ target: hex(),
+ durationMs,
+ waitForSelectionReady: true,
+ timeoutMs: 8000,
+});
+
+// Right-pane / drawer used after a postcode tap.
+const RIGHT_PANE_SELECTOR = '[data-tutorial="right-pane"]';
+const scrollDrawer = (top: number, durationMs = 800): Activity => ({
+ kind: 'scrollPane',
+ selector: RIGHT_PANE_SELECTOR,
+ top,
+ durationMs,
+});
+
+/** Default silent pre: vignette + hidden cursor; narration starts at t≈0. */
+const AD_PRE: Activity[] = [
+ { kind: 'clearVignette', durationMs: 0 },
+ { kind: 'cursorScale', scale: 0, durationMs: 0 },
+ { kind: 'wait', durationMs: 80 },
+];
function createDemoAdStoryboard(ad: DemoAdStoryboardConfig): Storyboard {
const promptText =
@@ -769,21 +807,6 @@ function createDemoAdStoryboard(ad: DemoAdStoryboardConfig): Storyboard {
zoom: ad.initialZoom ?? CITY_VIEWS[ad.city].zoom,
};
- // Default pre: just clear the vignette and hide the cursor — no
- // silent type+submit. Narration starts at the very first frame.
- // Ads that need filters applied invisibly can pass an explicit
- // `prePrime` (e.g. adPrime(promptText)) to do a fast type+submit
- // before cue 0 — but the audio cost (~3s of silence) is the
- // trade-off.
- const pre =
- ad.prePrime !== undefined
- ? ad.prePrime
- : [
- { kind: 'clearVignette' as const, durationMs: 0 },
- { kind: 'cursorScale' as const, scale: 0, durationMs: 0 },
- { kind: 'wait' as const, durationMs: 80 },
- ];
-
return {
name: ad.name,
locale: 'en',
@@ -794,7 +817,8 @@ function createDemoAdStoryboard(ad: DemoAdStoryboardConfig): Storyboard {
appLanguage: 'en',
aiZoomScale: 1,
initialMapView,
- stubbedFilters: ad.filters ?? AD_DEFAULT_FILTERS,
+ initialFilters: ad.initialFilters,
+ stubbedFilters: ad.filters ?? {},
stubbedTravelTimeFilters: ad.travelTimeFilters ?? [],
travelTimeCardSelector: TT_CARD_SELECTOR,
travelTimeSliderMax: TT_SLIDER_MAX,
@@ -802,20 +826,18 @@ function createDemoAdStoryboard(ad: DemoAdStoryboardConfig): Storyboard {
travelTimeDragToMin: TT_DRAG_TO_MIN,
brand: AD_BRAND,
},
- pre,
+ pre: ad.prePrime ?? AD_PRE,
cues: [
...ad.cues.map((cue, index) => ({
text: cue.text,
- gapBeforeMs: cue.gapBeforeMs ?? (index === 0 ? 0 : 220),
+ caption: cue.caption,
+ gapBeforeMs: cue.gapBeforeMs ?? (index === 0 ? 0 : 250),
during: cue.during,
- tail: cue.tail ?? linger(),
+ tail: cue.tail ?? [wait(300)],
})),
{
- // The outro card already shows the brand name + URL visually, so
- // the spoken line is a short closer that complements rather than
- // echoing the brand. Each storyboard can override via outroLine.
- text: ad.outroLine ?? 'Find your area first.',
- gapBeforeMs: 200,
+ text: ad.outroLine,
+ gapBeforeMs: 250,
during: [
{
kind: 'showOutro',
@@ -825,543 +847,339 @@ function createDemoAdStoryboard(ad: DemoAdStoryboardConfig): Storyboard {
durationMs: 0,
},
],
- tail: [{ kind: 'wait', durationMs: 900 }],
+ tail: [wait(900)],
},
],
};
}
-// -- helpers used by the per-ad cue lists ------------------------------------
-
-const AI_TEXTAREA = '[data-tutorial="ai-filters"] textarea';
-const AI_FORM = '[data-tutorial="ai-filters"] form';
-
-const typeAct = (text: string, durationMs = 2800): Activity => ({
- kind: 'type',
- selector: AI_TEXTAREA,
- text,
- durationMs,
-});
-const submitAct = (durationMs = 1300): Activity => ({
- kind: 'submitForm',
- formSelector: AI_FORM,
- durationMs,
-});
-const ttDragAct = (toMin: number, durationMs = 1400): Activity => ({
- kind: 'dragSlider',
- thumbSelector: `${TT_CARD_SELECTOR} [role="slider"] >> nth=1`,
- trackSelector: `${TT_CARD_SELECTOR} [data-orientation="horizontal"] >> nth=0`,
- toFraction: toMin / TT_SLIDER_MAX,
- durationMs,
-});
-const wait = (durationMs: number): Activity => ({ kind: 'wait', durationMs });
-const mapZoomIn = (durationMs = 1400, steps = 5): Activity => ({
- kind: 'mapZoom',
- target: vfrac(0.5, 0.28),
- steps,
- durationMs,
-});
-/**
- * Click the centre of the highest-priority visible postcode polygon from
- * the latest map response. Robust to zoom drift in a way that a fixed
- * vfrac target is not: at zoom 14+ a viewport-fraction click can land on
- * a road or river, and the drawer never opens. `hex()` (resolved by
- * DashboardRecorder.visibleHexagonTargets) scans the page's recorded
- * postcode response, picks one that's visible AND clear of the bottom
- * sheet / side rails, and returns its centroid in pixels.
- */
-const clickHex = (durationMs = 900): Activity => ({
- kind: 'click',
- target: hex(),
- durationMs,
-});
-
// ---------------------------------------------------------------------------
-// Helpers for the per-ad cue lists.
-// ---------------------------------------------------------------------------
-
-// Scrollable container in the mobile bottom sheet (the section that holds
-// the active filter cards). scrollPane finds the nearest scrollable
-// ancestor of this selector and scrolls it.
-const FILTER_PANE_SELECTOR = 'section[class*="rounded-t-2xl"]';
-// Right-pane / drawer used after a postcode click for stats.
-const RIGHT_PANE_SELECTOR = '[data-tutorial="right-pane"]';
-
-const scrollFilters = (top: number, durationMs = 700): Activity => ({
- kind: 'scrollPane',
- selector: FILTER_PANE_SELECTOR,
- top,
- durationMs,
-});
-const scrollDrawer = (top: number, durationMs = 700): Activity => ({
- kind: 'scrollPane',
- selector: RIGHT_PANE_SELECTOR,
- top,
- durationMs,
-});
-
-// Common London centre — shifted south so the Thames + central boroughs
-// sit in the visible upper-half of the mobile viewport (above the sheet).
-const LONDON_VIEW = { lat: 51.4672, lon: -0.1276, zoom: 10.5 };
-
-// ---------------------------------------------------------------------------
-// The ad set.
-//
-// Every entry is a self-contained 9:16 mobile vertical video. Narration
-// starts AT THE FIRST FRAME (no silent pre-prime). The outro card shows
-// the brand name + URL visually; the spoken outro line is therefore a
-// SHORT closer like "Find your area first." — never "Perfect Postcode
-// perfect-postcode dot co dot uk".
-//
-// Each ad uses filters RELEVANT to its topic (not just price+schools),
-// and includes at least one visible map interaction (type, drag,
-// mapZoom, click, scroll-filters, scroll-drawer). Most ads are London-
-// focused; we keep one Leeds variant for city variety and two themed
-// ads (Waitrose distance, % Reform vote share) covering edge-case
-// filters available on prod.
+// The ad set. 8 concepts, each: hook chip + one product interaction.
+// All filter names verified against live /api/features.
// ---------------------------------------------------------------------------
const AD_CONFIGS: DemoAdStoryboardConfig[] = [
// -------------------------------------------------------------------
- // 01 — Search by sentence. Type the prompt on camera, narration runs
- // simultaneously. Filters relevant: price + commute + crime + noise.
+ // 01 — The hero feature: the whole brief in one sentence.
+ // Cold open ON the typing; map reveal as the payoff.
// -------------------------------------------------------------------
{
- name: 'ad-01-london-prompt',
+ name: 'ad-01-say-it',
city: 'london',
- promptText:
- 'London flat under £600k, 35 min to centre, lower crime, lower noise',
+ promptText: 'Flat under £600k, 35 min to central London, low crime, quiet street',
filters: {
'Property type': ['Flats/Maisonettes'],
'Estimated current price': [0, 600000],
- 'Serious crime per 1k residents (avg/yr)': [0, 50],
- 'Noise (dB)': [0, 58],
+ 'Serious crime (avg/yr)': [0, 35],
+ 'Noise (dB)': [0, 60],
},
travelTimeFilters: [
- { mode: 'transit', slug: 'london', label: 'London city centre', max: 35 },
+ { mode: 'transit', slug: 'london', label: 'Central London', max: 35 },
],
initialZoom: 10.2,
posterTimeS: 8,
+ outroLine: 'Your area is on this map. Go find it.',
cues: [
{
- text: 'Stop searching listing by listing. Search by the area brief.',
- during: [typeAct(
- 'London flat under £600k, 35 min to centre, lower crime, lower noise',
- 2800
- )],
- tail: [wait(200)],
+ text: 'This is my whole house brief. One sentence.',
+ caption: 'One sentence. Every postcode.',
+ during: [
+ typeAct('Flat under £600k, 35 min to central London, low crime, quiet street', 2600),
+ ],
+ tail: [wait(150)],
},
{
- text: 'Price, commute, crime and noise land on the map together.',
- during: [submitAct(1100)],
- tail: [wait(700)],
+ text: "And that's every postcode in England that fits it.",
+ during: [submitSettled(1400)],
+ tail: [sheetDown(800), wait(250)],
},
{
- text: 'Every lit postcode is somewhere worth checking first.',
- tail: [wait(600)],
+ text: 'Now I scroll a map, not nine hundred listings.',
+ during: [mapZoomIn(1400, 4)],
+ tail: [wait(400)],
},
],
},
// -------------------------------------------------------------------
- // 02 — One slider. TT filter applied via URL only (no AI submit) so
- // the cold-open shows a filtered map and cue 1 immediately drags it.
+ // 02 — The commute slider. Cold open on a travel-time-coloured map
+ // (filter + colour applied in pre), then the 60→20 drag is the story.
// -------------------------------------------------------------------
{
- name: 'ad-02-london-slider',
+ name: 'ad-02-twenty-minute-map',
city: 'london',
- promptText: 'London within 40 minutes of centre',
+ promptText: 'Within an hour of central London',
filters: {},
travelTimeFilters: [
- { mode: 'transit', slug: 'london', label: 'London city centre', max: 40 },
+ { mode: 'transit', slug: 'london', label: 'Central London', max: 60 },
+ ],
+ initialZoom: 10.2,
+ posterTimeS: 9,
+ outroLine: 'Check the commute before you fall in love.',
+ prePrime: [
+ { kind: 'clearVignette', durationMs: 0 },
+ { kind: 'cursorScale', scale: 0, durationMs: 0 },
+ // Paint the map by journey time, then hand the frame to the map.
+ {
+ kind: 'click',
+ target: el(`${TT_CARD_SELECTOR} button[title="Colour map"]`),
+ durationMs: 600,
+ },
+ { kind: 'wait', durationMs: 500 },
+ sheetDown(700),
+ { kind: 'wait', durationMs: 200 },
],
- initialZoom: 10.4,
- posterTimeS: 5.5,
cues: [
{
- text: 'Your commute limit should change the map, not your patience.',
- tail: [wait(200)],
+ text: 'Every colour on this map is a commute to central London.',
+ caption: 'The 20-minute map',
+ during: [wait(400)],
+ tail: [wait(150)],
},
{
- text: 'Drag forty minutes down to fifteen minutes.',
- during: [ttDragAct(15, 1900)],
- tail: [wait(700)],
+ text: "Here's what twenty minutes actually leaves you.",
+ during: [sheetUp(700), touchShow(), ttDragAct(20, 1700)],
+ tail: [touchHide(), sheetDown(800), wait(300)],
},
{
- text: 'The reachable postcodes disappear in front of you.',
- tail: [wait(600)],
+ text: "If it's not lit, you'd live on the train. Now you know before you view.",
+ during: [mapZoomIn(1300, 3)],
+ tail: [wait(400)],
},
],
},
// -------------------------------------------------------------------
- // 03 — Click a real postcode. Deeper zoom (10 wheel steps) past the
- // hex aggregation into actual postcode polygons, then click, then
- // scroll the property drawer to surface the data.
+ // 03 — Tap a postcode, read its file. The drawer (sold prices, Street
+ // View, schools, crime) is the wow — most viewers don't know this exists.
// -------------------------------------------------------------------
{
- name: 'ad-03-london-zoom-postcode',
+ name: 'ad-03-postcode-files',
city: 'london',
- promptText: 'Family home in London, decent schools nearby',
- filters: {
+ initialFilters: {
'Estimated current price': [0, 700000],
- 'Outstanding primary schools within 2km': [1, 10],
- },
- initialZoom: 10.5,
- posterTimeS: 10,
- cues: [
- {
- text: 'Type a family brief and watch matching areas appear.',
- during: [typeAct('Family home in London, decent schools nearby', 2400), submitAct(900)],
- tail: [wait(500)],
- },
- {
- text: 'Zoom from area patterns into actual postcodes.',
- during: [mapZoomIn(3000, 10)],
- tail: [wait(400)],
- },
- {
- text: 'Tap one for sold prices and street-level context.',
- during: [
- { kind: 'cursorScale', scale: 1.3, durationMs: 200 },
- clickHex(900),
- ],
- tail: [
- wait(900),
- scrollDrawer(450, 700),
- wait(1100),
- { kind: 'cursorScale', scale: 0, durationMs: 200 },
- ],
- },
- ],
- },
-
- // -------------------------------------------------------------------
- // 04 — London 400k. Type+submit, scroll the filter pane to show all
- // the applied filter cards (variety vs ad-01 which doesn't scroll).
- // -------------------------------------------------------------------
- {
- name: 'ad-04-london-400k',
- city: 'london',
- promptText:
- 'Flat in London under £400k, 30 min to centre, lower crime',
- filters: {
- 'Property type': ['Flats/Maisonettes'],
- 'Estimated current price': [0, 400000],
- 'Serious crime per 1k residents (avg/yr)': [0, 55],
- },
- travelTimeFilters: [
- { mode: 'transit', slug: 'london', label: 'London city centre', max: 30 },
- ],
- initialZoom: 10.3,
- posterTimeS: 6,
- cues: [
- {
- text: 'London under four hundred thousand, with a thirty minute commute.',
- during: [typeAct(
- 'Flat in London under £400k, 30 min to centre, lower crime',
- 2800
- ), submitAct(900)],
- tail: [wait(400)],
- },
- {
- text: 'The active filters stack up as the map tightens.',
- during: [scrollFilters(280, 900)],
- tail: [wait(600)],
- },
- {
- text: 'Now the cheap-looking areas have to pass the brief.',
- tail: [wait(500)],
- },
- ],
- },
-
- // -------------------------------------------------------------------
- // 05 — Two streets apart. Product-led now: noise + crime filters are
- // typed and submitted on screen instead of masking the product with
- // generic street photos.
- // -------------------------------------------------------------------
- {
- name: 'ad-05-two-streets-apart',
- city: 'london',
- promptText: 'Quiet London streets, lower noise, lower serious crime',
- filters: {
- 'Noise (dB)': [0, 55],
- 'Serious crime per 1k residents (avg/yr)': [0, 45],
+ [SCHOOL_OUTSTANDING_PRIMARY]: [1, 10],
},
initialZoom: 10.6,
- posterTimeS: 4,
+ posterTimeS: 11,
+ outroLine: "Read the area's file before you book the viewing.",
+ prePrime: [
+ { kind: 'clearVignette', durationMs: 0 },
+ { kind: 'cursorScale', scale: 0, durationMs: 0 },
+ sheetDown(700),
+ { kind: 'wait', durationMs: 200 },
+ ],
cues: [
{
- text: 'Two streets can look identical in a listing photo.',
- during: [typeAct(
- 'Quiet London streets, lower noise, lower serious crime',
- 2500
- ), submitAct(900)],
- tail: [wait(400)],
+ text: 'Estate agents say, up and coming. The data is more specific.',
+ caption: 'Every postcode has a file',
+ during: [
+ {
+ // Zoom around the strongest visible match so the destination
+ // area contains matching postcodes; settled so the tapHex in
+ // the next cue projects against the final viewport's response.
+ kind: 'mapZoom',
+ target: hex(),
+ steps: 8,
+ durationMs: 2600,
+ center: true,
+ waitForMapSettled: true,
+ timeoutMs: 9000,
+ },
+ ],
+ tail: [wait(150)],
},
{
- text: 'Filter noise and serious crime before you book a viewing.',
- during: [scrollFilters(220, 800)],
- tail: [wait(500)],
+ text: 'Sold prices, schools, crime, even Street View, for this exact postcode.',
+ during: [touchShow(), tapHex(1000), scrollDrawer(420, 850)],
+ tail: [wait(200)],
},
{
- text: 'Now the quieter pockets are the ones left on screen.',
- during: [mapZoomIn(1300, 4)],
- tail: [wait(600)],
+ text: 'Thirty seconds here saves you a Saturday on the wrong street.',
+ during: [scrollDrawer(820, 900), touchHide()],
+ tail: [wait(350)],
},
],
},
// -------------------------------------------------------------------
- // 06 — Commute tax. Starts on the live commute layer and immediately
- // proves the point with the travel-time slider.
+ // 04 — Noise. The strongest single line in the set; the product proves it.
// -------------------------------------------------------------------
{
- name: 'ad-06-london-commute-tax',
+ name: 'ad-04-quiet-streets',
city: 'london',
- promptText: 'London within sixty minutes',
- filters: {},
- travelTimeFilters: [
- { mode: 'transit', slug: 'london', label: 'London city centre', max: 60 },
- ],
- initialZoom: 10.0,
- posterTimeS: 4,
- cues: [
- {
- text: 'A cheap home gets expensive when the commute is wrong.',
- tail: [wait(300)],
- },
- {
- text: 'Drag sixty minutes down to twenty and watch the map shrink.',
- during: [ttDragAct(20, 1900)],
- tail: [wait(700)],
- },
- {
- text: 'That weekly time bill is visible before the viewing.',
- tail: [wait(600)],
- },
- ],
- },
-
- // -------------------------------------------------------------------
- // 07 — Quiet near London. Uses the real prod Noise (dB) feature.
- // -------------------------------------------------------------------
- {
- name: 'ad-07-quiet-near-london',
- city: 'london',
- promptText: 'Quieter London, lower road noise, good transit',
+ promptText: 'Quiet London street under 55 decibels, low crime',
filters: {
'Noise (dB)': [0, 55],
- 'Estimated current price': [0, 700000],
+ 'Serious crime (avg/yr)': [0, 35],
},
- travelTimeFilters: [
- { mode: 'transit', slug: 'london', label: 'London city centre', max: 45 },
- ],
- initialZoom: 10.4,
- posterTimeS: 4,
- cues: [
- {
- text: 'Quiet near London is searchable, not just hopeful.',
- during: [typeAct('Quieter London, lower road noise, good transit', 2500), submitAct(900)],
- tail: [wait(400)],
- },
- {
- text: 'Filter for noise alongside price and travel time.',
- during: [scrollFilters(220, 800)],
- tail: [wait(500)],
- },
- {
- text: 'The calmer pockets show up before you go anywhere.',
- tail: [wait(500)],
- },
- ],
- },
-
- // -------------------------------------------------------------------
- // 08 — The postcode comes with the keys. Keeps the memorable premise,
- // but shows the product doing the work instead of a keys stock photo.
- // -------------------------------------------------------------------
- {
- name: 'ad-08-postcode-with-the-keys',
- city: 'london',
- promptText: 'Family London, lower crime, good schools, lower noise',
- filters: {
- 'Estimated current price': [0, 750000],
- 'Outstanding primary schools within 2km': [1, 10],
- 'Serious crime per 1k residents (avg/yr)': [0, 50],
- 'Noise (dB)': [0, 58],
- },
- travelTimeFilters: [
- { mode: 'transit', slug: 'london', label: 'London city centre', max: 45 },
- ],
- initialZoom: 10.5,
- posterTimeS: 3,
- cues: [
- {
- text: 'You can change the kitchen. You inherit the postcode.',
- during: [typeAct(
- 'Family London, lower crime, good schools, lower noise',
- 2500
- ), submitAct(900)],
- tail: [wait(400)],
- },
- {
- text: 'So check commute, crime, schools and noise first.',
- during: [scrollFilters(320, 900)],
- tail: [wait(500)],
- },
- {
- text: 'Pick the area first. The keys come second.',
- during: [mapZoomIn(1200, 4)],
- tail: [wait(600)],
- },
- ],
- },
-
- // -------------------------------------------------------------------
- // 09 — Amenities. Waitrose is the memorable example, but the copy
- // frames it as practical amenity filtering rather than a throwaway gag.
- // -------------------------------------------------------------------
- {
- name: 'ad-09-london-waitrose',
- city: 'london',
- promptText:
- 'London postcodes near Waitrose, tube and parks under £800k',
- filters: {
- 'Distance to nearest Waitrose (km)': [0, 1],
- 'Distance to nearest tube station (km)': [0, 1.2],
- 'Distance to nearest park (km)': [0, 0.8],
- 'Estimated current price': [0, 800000],
- },
- initialZoom: 10.4,
+ initialZoom: 10.6,
posterTimeS: 7,
+ outroLine: 'Quiet is searchable now.',
cues: [
{
- text: 'Amenities should be filters, not guesses from the photos.',
- during: [typeAct(
- 'London postcodes near Waitrose, tube and parks under £800k',
- 2800
- ), submitAct(900)],
+ text: 'Listing photos are silent. Main roads are not.',
+ caption: "You can't hear a photo",
+ during: [typeAct('Quiet London street under 55 decibels, low crime', 2400)],
+ tail: [wait(150)],
+ },
+ {
+ text: 'This is London under fifty-five decibels.',
+ during: [submitSettled(1400)],
+ tail: [sheetDown(800), wait(250)],
+ },
+ {
+ text: "The quiet streets were always there. Now they're searchable.",
+ during: [mapZoomIn(1600, 5)],
tail: [wait(400)],
},
- {
- text: 'Waitrose, tube, parks and price can all count together.',
- during: [scrollFilters(300, 900)],
- tail: [wait(600)],
- },
- {
- text: 'Now you know which postcodes actually match that lifestyle.',
- tail: [wait(500)],
- },
],
},
// -------------------------------------------------------------------
- // 10 — Local politics. Matter-of-fact and product-led; lower threshold
- // keeps the map populated while still surfacing the Reform UK feature.
+ // 05 — Families / the school run. Leeds for non-London variety.
// -------------------------------------------------------------------
{
- name: 'ad-10-reform-councils',
+ name: 'ad-05-school-run',
city: 'leeds',
- promptText:
- 'Areas with higher Reform UK vote share and lower prices',
+ promptText: 'Family home in Leeds under £350k, good primary schools, low crime',
filters: {
- '% Reform UK': [15, 100],
'Estimated current price': [0, 350000],
- },
- initialZoom: 10.5,
- posterTimeS: 7,
- cues: [
- {
- text: 'Local politics is part of the neighbourhood data too.',
- during: [typeAct(
- 'Areas with higher Reform UK vote share and lower prices',
- 2600
- )],
- tail: [wait(300)],
- },
- {
- text: 'Run the filter and see which areas stay in view.',
- during: [submitAct(900), scrollFilters(180, 700)],
- tail: [wait(500)],
- },
- {
- text: 'No spin. Just another local signal before you buy.',
- tail: [wait(500)],
- },
- ],
- },
-
- // -------------------------------------------------------------------
- // 11 — Leeds for families. Non-London variety. Schools focus.
- // -------------------------------------------------------------------
- {
- name: 'ad-11-leeds-families',
- city: 'leeds',
- promptText:
- 'Leeds family areas, good primary schools nearby, lower crime',
- filters: {
- 'Estimated current price': [0, 380000],
- 'Good+ primary schools within 2km': [2, 10],
- 'Serious crime per 1k residents (avg/yr)': [0, 45],
+ [SCHOOL_GOOD_PRIMARY]: [2, 10],
+ 'Serious crime (avg/yr)': [0, 30],
},
initialZoom: 11.0,
- posterTimeS: 6,
+ posterTimeS: 7,
+ outroLine: 'Pick the area first. Then the house.',
cues: [
{
- text: 'Find Leeds areas that work for the school run.',
- during: [typeAct(
- 'Leeds family areas, good primary schools nearby, lower crime',
- 2500
- ), submitAct(900)],
- tail: [wait(300)],
+ text: 'Good primary school, low crime, under three hundred and fifty. The actual family brief.',
+ caption: 'The school-run map',
+ during: [
+ typeAct('Family home in Leeds under £350k, good primary schools, low crime', 2800),
+ ],
+ tail: [wait(150)],
},
{
- text: 'School quality and serious crime sit beside price.',
- during: [scrollFilters(220, 800)],
- tail: [wait(500)],
+ text: 'Everything still on the map passes all three.',
+ during: [submitSettled(1400)],
+ tail: [sheetDown(800), wait(250)],
},
{
- text: 'Every lit postcode is a better place to start.',
- tail: [wait(500)],
+ text: 'No more falling for a house, then googling the catchment at midnight.',
+ during: [mapZoomIn(1600, 5)],
+ tail: [wait(400)],
},
],
},
// -------------------------------------------------------------------
- // 12 — Pricing/value. Keeps the current £9.99 founder-price hook, but
- // proves value through the product instead of a static scarcity card.
+ // 06 — Lifestyle amenities. The Waitrose line is the hook; tube + park
+ // make it practical. Names match the live amenity-distance features.
// -------------------------------------------------------------------
{
- name: 'ad-12-pricing-scarcity',
+ name: 'ad-06-waitrose-test',
city: 'london',
- promptText: 'London under £700k, good schools, lower crime and lower noise',
+ promptText: 'Walking distance to a Waitrose, a tube station and a park',
filters: {
- 'Estimated current price': [0, 700000],
- 'Outstanding primary schools within 2km': [1, 10],
- 'Serious crime per 1k residents (avg/yr)': [0, 50],
- 'Noise (dB)': [0, 58],
+ 'Distance to nearest amenity (Waitrose) (km)': [0, 1],
+ 'Distance to nearest amenity (Tube station) (km)': [0, 0.8],
+ 'Distance to nearest amenity (Park) (km)': [0, 0.5],
},
initialZoom: 10.4,
- posterTimeS: 3,
+ posterTimeS: 7,
+ outroLine: 'Filter for the life, not just the floor plan.',
cues: [
{
- text: 'Nine ninety nine beats one wasted viewing.',
- during: [typeAct(
- 'London under £700k, good schools, lower crime and lower noise',
- 2700
- ), submitAct(900)],
+ text: "Near a Waitrose, a tube stop and a park. Yes, that's a real search.",
+ caption: 'The Waitrose test',
+ during: [typeAct('Walking distance to a Waitrose, a tube station and a park', 2800)],
+ tail: [wait(150)],
+ },
+ {
+ text: 'London, cut down to the postcodes that live the way you do.',
+ during: [submitSettled(1400)],
+ tail: [sheetDown(800), wait(250)],
+ },
+ {
+ text: 'Snobby? Maybe. Efficient? Extremely.',
+ during: [mapZoomIn(1400, 4)],
tail: [wait(400)],
},
+ ],
+ },
+
+ // -------------------------------------------------------------------
+ // 07 — Renters. Every tool is for buyers; rent is a live feature here.
+ // -------------------------------------------------------------------
+ {
+ name: 'ad-07-renters-map',
+ city: 'london',
+ promptText: 'Rent under £1,600, 30 min to central London, quiet street',
+ filters: {
+ 'Estimated monthly rent': [0, 1600],
+ 'Noise (dB)': [0, 58],
+ },
+ travelTimeFilters: [
+ { mode: 'transit', slug: 'london', label: 'Central London', max: 30 },
+ ],
+ initialZoom: 10.3,
+ posterTimeS: 7,
+ outroLine: 'Know the postcode before the viewing queue.',
+ cues: [
{
- text: 'Use the map before spending a Saturday in the wrong area.',
- during: [scrollFilters(300, 900)],
- tail: [wait(500)],
+ text: 'Rent under sixteen hundred, half an hour to work, on a quiet street.',
+ caption: 'Renters get a map too',
+ during: [typeAct('Rent under £1,600, 30 min to central London, quiet street', 2800)],
+ tail: [wait(150)],
},
{
- text: 'The cheapest mistake is the one you skip.',
- tail: [wait(600)],
+ text: 'Every letting site shows you flats. This shows you areas.',
+ during: [submitSettled(1400)],
+ tail: [sheetDown(800), wait(250)],
+ },
+ {
+ text: 'Turn up to the right viewings, and skip the rest.',
+ during: [mapZoomIn(1600, 5)],
+ tail: [wait(400)],
+ },
+ ],
+ },
+
+ // -------------------------------------------------------------------
+ // 08 — Value. £9.99 against the real cost of a wasted viewing.
+ // -------------------------------------------------------------------
+ {
+ name: 'ad-08-cheap-insurance',
+ city: 'london',
+ promptText: 'Under £500k, 35 min to central London, low crime, good schools',
+ filters: {
+ 'Estimated current price': [0, 500000],
+ 'Serious crime (avg/yr)': [0, 35],
+ [SCHOOL_GOOD_PRIMARY]: [1, 10],
+ },
+ travelTimeFilters: [
+ { mode: 'transit', slug: 'london', label: 'Central London', max: 35 },
+ ],
+ initialZoom: 10.4,
+ posterTimeS: 7,
+ outroLine: 'Skip the wrong viewings.',
+ cues: [
+ {
+ text: 'A bad viewing costs a train ticket and half a weekend.',
+ caption: '£9.99 vs a wasted Saturday',
+ during: [typeAct('Under £500k, 35 min to central London, low crime, good schools', 2600)],
+ tail: [wait(150)],
+ },
+ {
+ text: 'This costs nine ninety-nine, and tells you where not to go.',
+ during: [submitSettled(1400)],
+ tail: [sheetDown(800), wait(250)],
+ },
+ {
+ text: 'Cheap insurance, for the biggest purchase of your life.',
+ during: [mapZoomIn(1600, 5)],
+ tail: [wait(400)],
},
],
},
diff --git a/video/src/timeline.ts b/video/src/timeline.ts
index 55311f6..6d45c4b 100644
--- a/video/src/timeline.ts
+++ b/video/src/timeline.ts
@@ -29,16 +29,30 @@ export async function prepareTimeline(
await sleep(400);
await installZoomWrapper(page);
- await installCursor(page);
+ await installCursor(page, storyboard.video.cursorStyle ?? 'arrow');
await setAspectClass(page, storyboard.video.aspect);
const ctx: ScriptCtx = { page, dashboard, cursor: { x: 200, y: 240 } };
await page.mouse.move(ctx.cursor.x, ctx.cursor.y);
- await prepareAiBox(ctx);
+ // Only pre-open the AI prompt when the storyboard actually types into it.
+ // Opening it unconditionally grows the mobile bottom sheet (keyboard
+ // avoidance) and steals the frame from the map on ads that never type.
+ if (storyboardTypes(storyboard)) {
+ await prepareAiBox(ctx);
+ }
await sleep(80);
return ctx;
}
+function storyboardTypes(storyboard: Storyboard): boolean {
+ const all = [
+ ...(storyboard.pre ?? []),
+ ...storyboard.cues.flatMap((cue) => [...(cue.during ?? []), ...(cue.tail ?? [])]),
+ ...(storyboard.post ?? []),
+ ];
+ return all.some((activity) => activity.kind === 'type');
+}
+
export async function runTimeline(
ctx: ScriptCtx,
storyboard: Storyboard