A Melbourne suburb recommender where a deterministic scoring engine always answers first, and the language model only ever improves the answer.
AwardWinner, Monash PG Industry Experience Expo, S1 2026
My roleAI layer, REST API and scoring engine, Vue frontend
TeamSix people, under an APA Group industry mentor
WhenMarch to June 2026, three shipped iterations
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.
Summaries hide quietly. The scores are already on screen.
Voice extraction fails
A friendly message, and the manual sliders keep working.
Re-ranking unavailable
The top 3 by maths alone, labelled as such.
API key missing
Every 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.