Replacing getAddress.io? Free drop-in replacement →
← Back to Blog
Tutorials Feb 3, 2026 · 9 min read min read

Building a School Finder: Ofsted Ratings + Distance from Any UK Postcode

Build a school finder that returns nearby schools sorted by Ofsted rating and distance — in under 30 lines. Covers primary, secondary, and special schools. Full Python and JavaScript examples using the Homedata Schools API.

Why schools data matters in property

School proximity is one of the strongest predictors of house prices in the UK. Properties within the catchment area of an "Outstanding" Ofsted-rated primary school command a 6–8% premium over equivalent properties near "Requires Improvement" schools. For secondary schools, the premium can hit 10–15%.

If you're building a property portal, estate agent CRM, or neighbourhood analysis tool, school data isn't optional — it's a core feature. Buyers with children filter by schools before they filter by price.

The data: GIAS and Ofsted

School data in England comes from two sources:

  • GIAS (Get Information About Schools) — the DfE's official register. Contains ~27,000 open establishments with names, addresses, types, age ranges, pupil counts, and URNs (unique reference numbers).
  • Ofsted — inspection outcomes. Ratings: Outstanding, Good, Requires Improvement, Inadequate. Plus the date of last inspection.

The Homedata Schools API combines both datasets. One call to /api/schools/{postcode}/ returns nearby schools with GIAS metadata and Ofsted ratings, sorted by distance.

Step 1: Query schools near a postcode

import requests

API_KEY = "your_api_key_here"
POSTCODE = "SW1A 1AA"

response = requests.get(
    f"https://api.homedata.co.uk/api/schools/{POSTCODE}/",
    headers={"Authorization": f"Api-Key {API_KEY}"}
).json()

schools = response['schools']
print(f"Found {len(schools)} schools near {POSTCODE}\n")

for s in schools[:5]:
    print(f"  {s['name']}")
    print(f"    Type: {s['type']} | Phase: {s['phase']}")
    print(f"    Ofsted: {s['ofsted_rating']} ({s['ofsted_date']})")
    print(f"    Pupils: {s['pupil_count']} | Age: {s['age_low']}–{s['age_high']}")
    print(f"    Distance: {s['distance_m']}m")
    print()

Step 2: Filter by phase and rating

Most school finders let users filter by phase (primary/secondary) and minimum Ofsted rating. Here's a practical filter:

def filter_schools(schools, phase=None, min_rating=None, max_distance_m=None):
    """
    Filter schools by phase, Ofsted rating, and distance.

    Args:
        phase: 'Primary', 'Secondary', 'All through', etc.
        min_rating: minimum Ofsted rating (1=Outstanding, 4=Inadequate)
        max_distance_m: maximum distance in metres
    """
    RATING_MAP = {
        'Outstanding': 1,
        'Good': 2,
        'Requires improvement': 3,
        'Inadequate': 4,
    }

    filtered = schools
    if phase:
        filtered = [s for s in filtered if s['phase'] == phase]
    if min_rating:
        filtered = [s for s in filtered
                    if RATING_MAP.get(s.get('ofsted_rating'), 99) <= min_rating]
    if max_distance_m:
        filtered = [s for s in filtered if s['distance_m'] <= max_distance_m]

    return filtered

# Outstanding + Good primaries within 1km
good_primaries = filter_schools(
    schools,
    phase='Primary',
    min_rating=2,        # Outstanding or Good
    max_distance_m=1000  # Within 1km
)

print(f"Good+ primaries within 1km: {len(good_primaries)}")
for s in good_primaries:
    print(f"  {s['name']} — {s['ofsted_rating']} — {s['distance_m']}m")

Step 3: Build a school score for a property

For property portals, a single "school score" is more useful than a raw list. Here's a simple scoring algorithm that weights Ofsted rating and distance:

def school_score(schools, max_radius_m=2000):
    """
    Calculate a 0–100 school score for a location.

    Weights:
    - Outstanding within 500m = maximum points
    - Distance decay: linear to max_radius_m
    - Separate primary and secondary scores, averaged
    """
    def phase_score(phase_schools):
        if not phase_schools:
            return 50  # Neutral if no schools found

        RATING_SCORES = {
            'Outstanding': 100,
            'Good': 75,
            'Requires improvement': 35,
            'Inadequate': 10,
        }

        total_weight = 0
        weighted_score = 0

        for s in phase_schools[:5]:  # Top 5 nearest
            rating_score = RATING_SCORES.get(s.get('ofsted_rating'), 50)
            distance_factor = max(0, 1 - (s['distance_m'] / max_radius_m))
            weight = distance_factor ** 0.5  # Square root for gentler decay

            weighted_score += rating_score * weight
            total_weight += weight

        return round(weighted_score / total_weight) if total_weight > 0 else 50

    primaries = [s for s in schools if s['phase'] == 'Primary']
    secondaries = [s for s in schools if s['phase'] == 'Secondary']

    primary_score = phase_score(primaries)
    secondary_score = phase_score(secondaries)

    # Weighted average (primary slightly more important for family buyers)
    overall = round(primary_score * 0.55 + secondary_score * 0.45)

    return {
        'overall': overall,
        'primary': primary_score,
        'secondary': secondary_score,
        'label': 'Excellent' if overall >= 80 else 'Good' if overall >= 60 else 'Average' if overall >= 40 else 'Below average',
        'nearest_outstanding': next(
            (s['name'] for s in schools if s.get('ofsted_rating') == 'Outstanding'),
            None
        ),
    }

score = school_score(schools)
print(f"School score: {score['overall']}/100 ({score['label']})")
print(f"Primary: {score['primary']}, Secondary: {score['secondary']}")
if score['nearest_outstanding']:
    print(f"Nearest Outstanding: {score['nearest_outstanding']}")

JavaScript version

const API_KEY = 'your_api_key_here';
const BASE = 'https://api.homedata.co.uk/api';
const headers = { Authorization: `Api-Key ${API_KEY}` };

async function getSchoolScore(postcode) {
  const { schools } = await fetch(
    `${BASE}/schools/${encodeURIComponent(postcode)}/`,
    { headers }
  ).then(r => r.json());

  const ratingScores = {
    'Outstanding': 100, 'Good': 75,
    'Requires improvement': 35, 'Inadequate': 10
  };

  function phaseScore(list) {
    if (!list.length) return 50;
    let tw = 0, ws = 0;
    for (const s of list.slice(0, 5)) {
      const rs = ratingScores[s.ofsted_rating] ?? 50;
      const df = Math.max(0, 1 - s.distance_m / 2000);
      const w = Math.sqrt(df);
      ws += rs * w;
      tw += w;
    }
    return Math.round(ws / tw);
  }

  const primaries = schools.filter(s => s.phase === 'Primary');
  const secondaries = schools.filter(s => s.phase === 'Secondary');

  return {
    overall: Math.round(phaseScore(primaries) * 0.55 + phaseScore(secondaries) * 0.45),
    schools: schools.length,
    outstanding: schools.filter(s => s.ofsted_rating === 'Outstanding').length,
  };
}

getSchoolScore('SW1A 1AA').then(console.log);

Combining with property data

The real power comes from combining schools data with other Homedata endpoints. Here's how you'd enrich a property listing:

# Full property enrichment: schools + crime + broadband
uprn = "10023456789"
postcode = "SW1A 1AA"

property_data = requests.get(
    f"{BASE}/property/{uprn}/", headers=headers
).json()

schools_data = requests.get(
    f"{BASE}/schools/{postcode}/", headers=headers
).json()

crime_data = requests.get(
    f"{BASE}/crime/{postcode}/", headers=headers
).json()

broadband_data = requests.get(
    f"{BASE}/broadband/{postcode}/", headers=headers
).json()

# Now you have everything a buyer needs:
# - Property details (beds, type, EPC)
# - Nearby schools with Ofsted ratings
# - Local crime rates by category
# - Broadband speed availability

Or use the Postcode Profile API (/api/postcode-profile/{postcode}/) to get all of this in a single call — including a school count and average Ofsted score for the area.

Use cases

Use Case How Schools Data Helps
Property portals School score badges on listings — "3 Outstanding schools within 1km"
Estate agent CRMs Match family buyers to properties near good schools
Relocation tools Compare areas by school quality alongside transport and amenities
Investment analysis School quality correlates with price resilience and rental demand
Local authority dashboards Map school capacity vs. new housing development

Try it now: Get a free API key — the Schools endpoint is included from Starter (£49/month). Start free with 100 calls/month on the Core Property module, no credit card needed.

Schools data for every UK postcode

Ofsted ratings, pupil counts, age ranges — one API call.

Get free API key →