Technical Mentor @ Monash University
← All projects

Case study

NeighbourFit

A Melbourne suburb recommender where a deterministic scoring engine always answers first, and the language model only ever improves the answer.

The problem

Choosing where to live in Melbourne meant cross-referencing transport, amenities and demographics across separate sites, with nothing weighing any of it against your own priorities.

NeighbourFit gives every suburb a personalised Fit Score from 0 to 100 across transport, food, healthcare, education and parks, weighted by what you actually care about - plus a separate census profile that answers a different question: will I find people like me here.

Architecture

The local Flask server mirrored the Lambda logic, so scoring could be developed without deploying. Scores are precomputed into PostgreSQL once; only the user's weights are applied per request.

Three AI features

01

Voice to preferences

Describe your life out loud instead of moving sliders: "a quiet place with good parks for my dog and decent coffee nearby". Llama 3.3 acts as a structured extraction engine and returns a persona, five category weights and its reasoning. The sliders move, and the reasoning is shown so the user knows why.

02

Persona-aware summaries

Every search fires an async request that never blocks the scores. The model gets the persona, the five scores and the census profile, and is held to exactly three sentences: overall impression, one data-backed strength or weakness, a practical verdict.

03

Hybrid re-ranking

The maths ranks every suburb and keeps the top 15 - deterministic, auditable, no model involved. Only then does the model re-rank those 15 down to 3 on soft factors the numbers cannot see, with a one-sentence justification for each.

Graceful degradation

Every model call enforces a JSON schema, so the frontend never scrapes prose. And every call has a fallback: the deterministic core works whether the model is up or not. The AI is a layer on top of a system that stands without it.

Try breaking it yourself
When this happensThe user sees
Model times out or errorsSummaries hide quietly. The scores are already on screen.
Voice extraction failsA friendly message, and the manual sliders keep working.
Re-ranking unavailableThe top 3 by maths alone, labelled as such.
API key missingEvery AI feature switches off. The product still works end to end.

How the scoring works

Fit Score = Σ (category score × your weight) / Σ your weights

Demographics = (young adult ratio + diversity + student ratio) / 3 × 100 - kept separate on purpose

Open data only: PTV GTFS, OpenStreetMap, ACARA, AIHW and ABS Census 2021. No feature shipped without a dataset behind it.

Results

290+Melbourne suburbs scored
3 / 3iterations shipped on schedule
83%persona accuracy on voice input, 12-utterance test set
100%weight accuracy on the same set

Plus the one I am proudest of debugging: a PostgreSQL RealDictRow and Decimal serialisation failure that only existed inside Lambda. It worked locally and failed deployed - the lesson that became "local is not production".

What I would do differently

Density is not access

Counting amenities inside a boundary rewards big suburbs and ignores whether anything is walkable. Isochrones or distance decay would measure what users think they are being told.

Evaluate the model properly

Voice input was checked on a small test set, but the summaries and re-ranking were tuned by reading outputs. A labelled set and a rubric would show whether the AI layer adds value or just latency.

Cache the summaries

Every summary was a live call for a result that only changes when the scores do. A cache keyed on suburb, persona and scores would have removed nearly all of them.

Secrets from day one

Lambda read credentials from the environment; one local helper hardcoded them. That should have been enforced from the first commit.

The live demo's backend was switched off after the semester, and the code lives in the team's repository, so neither is linked here. Happy to walk through it.