Building S1 School Finder: Helping Singapore Parents Navigate Secondary School Selection

November 27, 2025

5 min read

Every year, thousands of Singapore parents face the stressful task of selecting secondary schools for their children after the PSLE results are released. The official MOE data exists, but it's scattered across different sources and hard to compare. I built S1 School Finder to solve this problem.

The Problem

Choosing a secondary school in Singapore involves juggling multiple factors:

  • Cut-off points (COP): Does your child's AL score qualify for the school?
  • Location: How far is the school from home?
  • School type: Co-ed, boys-only, or girls-only?
  • Mother tongue offerings: Does the school offer Higher Chinese, Malay, or Tamil?
  • Affiliation benefits: Does your child's primary school have affiliated secondary schools with lower cut-offs?
  • Historical trends: Are cut-off points rising or falling?

MOE does have their own school finder tool, but when I tested it, the location-based filtering wasn't working properly. More importantly, MOE doesn't provide cut-off point data through an official API - the information exists, but it's scattered across various pages and not available in a structured format for developers to build on.

This meant scraping was the only viable option. Not ideal, but you gotta do what you gotta do.

The Solution: A Two-Part System

Part 1: The Scraper

The first challenge was getting the data. I built a Python scraper using BeautifulSoup that:

  • Collects data from approximately 140 secondary schools
  • Extracts historical cut-off points spanning 2023-2025
  • Separates regular vs. affiliated admission tracks
  • Distinguishes HCL (Higher Chinese Language) grades from numeric scores
  • Identifies school gender composition
  • Implements rate limiting (2-second delays) to be respectful to the source

The scraper uses a modular architecture with separate components for:

  • main_page_parser.py - Extracts school listings
  • detail_page_parser.py - Processes individual school pages
  • http_client.py - Manages requests with exponential backoff
  • csv_writer.py - Handles data export

I also built a preview mode that processes 15 schools first, letting me verify the data structure before committing to the full scrape.

Part 2: The React Frontend

The real magic happens in the finder application. Built with React, it provides:

Smart Filtering

  • Filter by your AL score to see only schools you qualify for
  • Set a maximum cut-off to filter out schools that might be "too easy"
  • Filter by school type (co-ed, boys, girls)
  • Select mother tongue requirements

Historical Data Analysis

  • View cut-off points for the past 3 years (2023-2025)
  • Toggle "historical maximum" mode to see if you would have qualified in previous years
  • Understand trends - are schools getting more or less competitive?

Affiliation Support

  • Select your affiliated primary school
  • See the separate (usually lower) affiliated cut-off points
  • Instantly identify which schools give you an advantage

Location Features

  • Filter by town
  • Enter your postal code to calculate distances
  • Sort schools by proximity

Technical Decisions

Why Python for scraping? BeautifulSoup and requests are battle-tested for this kind of work. The uv package manager made dependency management painless.

Why React for the frontend? The filtering logic benefits from React's state management. As users adjust sliders and checkboxes, the school list updates instantly without server round-trips.

Why static hosting on GitHub Pages? The data changes once per year (after the MOE releases new cut-off points). Static hosting is free, fast, and reliable. No backend needed.

Interesting Implementation Details

Handling Posting Groups

Singapore's PSLE scoring system places students into different "Posting Groups" based on their AL scores:

  • PG3 (AL 1-22): Former Express stream
  • PG2 (AL 23-24): Former Normal Academic
  • PG1 (AL 25-30): Former Normal Technical
  • IP: Integrated Programme (direct path to JC)

The app shows which posting groups you qualify for and filters schools accordingly.

The Cut-Off Point Paradox

Lower is better! An AL score of 6 is excellent, while 22 is the threshold for PG3. This inverse relationship can confuse users, so the UI clearly shows "Eligible: PG3, IP" based on the entered score.

Affiliated Schools Data

Some schools have two cut-off points - one for regular admission and one (usually more lenient) for students from affiliated primary schools. The scraper handles this by detecting affiliated entries and storing them separately.

What I Learned

  1. Data quality matters: Inconsistent formatting in source data required extensive normalization logic.

  2. Preview modes save time: Being able to test with 15 schools before running the full scrape prevented many wasted hours.

  3. Historical data adds context: Parents don't just want this year's numbers - they want to understand trends.

  4. Simple beats complex: A static site with client-side filtering handles this use case perfectly. No need for databases or authentication.

Try It Out

The S1 School Finder is live at ekchinhui.github.io/s1-helper. The source code is available on GitHub.

If you're a Singapore parent navigating the secondary school selection process, I hope this tool makes your decision a little easier. And if you're a developer interested in data scraping or building useful civic tools, feel free to explore the code and reach out with questions.


Built with Python, BeautifulSoup, React, and a desire to make public data more accessible.