A city-scale list is not enough
The starting point is a messy list of restaurants and bars. The product idea is to turn that into a way to choose a night by intent instead of reading hundreds of lists.
Active build / Tokyo
A working experiment for finding Tokyo restaurants and bars by intent: start from a messy list, enrich it, test embedding/clustering, then build a planner that can recommend one place or a route.
Model path
This started as a simple idea: use a list of Tokyo restaurants and bars to recommend better nights out. The early clustering runs were useful, but they showed the wrong product shape. The current version keeps the useful part of the embedding work and moves the planner toward gates, profile dimensions, and feedback.
The starting point is a messy list of restaurants and bars. The product idea is to turn that into a way to choose a night by intent instead of reading hundreds of lists.
Clustering helped show where venues were similar, but it mostly rediscovered obvious categories. The overlap mattered more than the cluster names.
A closed restaurant, wrong cuisine, or wrong venue type should not be rescued by a vibe score. The current version gates first and ranks second.
The better long-term shape is retrieval, ranking, and feedback loops: find candidates, rerank them, collect labels, and improve the model only after enough repeated evidence.
Current model
Right now it is a retrieval and ranking system. Embeddings create venue profile features, hard rules remove impossible matches, and a transparent ranker orders the survivors.
It is not linear regression, logistic regression, K-means, or a live-learning model. There are no fitted coefficients yet. The current weights are product rules that we can audit.
Type, cuisine, and drink are now gates. Price is a soft fit. Data confidence is shown separately and is not added to the score. Open hours are not scored until real hours are loaded.
One example
Request: Shibuya, route planning, French or bistro first stop, cocktail-focused second stop, quiet vinyl / old wood / low light.
The model first asks whether a venue is eligible, then asks how well it fits. Type, cuisine, drink intent, time of day, location, price, quality, walk-in likelihood, and the requested atmosphere all matter, but the first few are rules while the rest are evidence.
Dependencies
Each venue record carries its location, place identity, price, cuisine and drink signals, walk-in estimate, and experience-dimension scores.
The planner applies the preferences a person selects in the browser, then explains why a result survived the filters.
Map links open the specific place when identity data is available and otherwise use the closest safe location.
Walkthrough
1. Gate: remove venues with the wrong role. For a route, the first stop cannot be bar-like and the second stop must be bar-like.
2. Gate: apply cuisine and drink intent. French applies to the first stop; cocktail focus applies to the second stop.
3. Rank: convert words like quiet, vinyl, old wood, and low light into atmosphere descriptors, then compare those descriptors against venue dimension scores.
4. Rank: add location, price fit, quality, and walk-in scores as soft evidence. Data confidence is shown separately so weak data does not secretly inflate the recommendation.
About the Data.
Prep
Kitchen pass
Normalize → gate → rank1621 source signals, with evidence kept separate from preference scoring.Service
Ranked venue or two-stop routeEach result explains fit, score parts, confidence, and its Maps link.Data visualization
The moving 3D view is a diagnostic projection, not the recommender. The important lesson is overlap: a good bar can be part listening room, part hidden entrance, part cocktail craft. That is why the planner uses multiple profile scores instead of one hard cluster.
Current planner
Planning tool / reset build
This is not linear regression. It is a retrieval ranker: filters remove invalid venues, then a transparent scoring function ranks the remaining candidates.
Map selection is paused until it is a real basemap, not a dot field.
Leaving this blank means no vibe bias. These are loose atmosphere handles, not formal categories the user needs to understand.
eligible results after gates
atmosphere descriptors
top ranked match
data confidence only
Single-place results
Shinjuku Golden Gai / bar / $
ranked without atmosphere biasExact Google place linkOpen in Google MapsShibuya / cafe / unknown
ranked without atmosphere biasExact Google place linkOpen in Google MapsUmegaoka / cafe_bar / $$
ranked without atmosphere biasExact Google place linkOpen in Google MapsTokyo / cafe / unknown
ranked without atmosphere biasExact Google place linkOpen in Google MapsTokyo / bar / unknown
ranked without atmosphere biasExact Google place linkOpen in Google MapsEbisu / cafe / $
ranked without atmosphere biasExact Google place linkOpen in Google MapsTokyo / bar / PRICE_LEVEL_MODERATE
ranked without atmosphere biasExact Google place linkOpen in Google MapsChuo / cafe / unknown
ranked without atmosphere biasExact Google place linkOpen in Google MapsTop result audit
Plain-English model
It is a recommendation ranker, not a trained regression model. The embedding work creates venue profile features. The UI filters act as gates. The ranker then scores only the venues that survive.
Descriptor bridge
No descriptors selected, so atmosphere is not part of the score.
Map feedback
Use embeddings to retrieve a candidate set, then use gates and feature scores to rank the shortlist. This is the direction that matches how recommendation systems are usually structured.
Embed the user's plain-English request and compare it against venue profile documents. This should eventually replace hand-mapped atmosphere words.
Google opening-hour periods should become a 0/1 eligibility check. If it is closed at the requested time, it should not be ranked.
Firebase should store accept/reject labels, selected reasons, venue ids, route ids, model version, and score parts so future training has clean examples.
Future iterations
Opening hours, reservations, and real-time availability are still uneven, so the system should continue withholding uncertain places instead of pretending they are ready.
A few reactions do not prove a preference. The next useful threshold is repeated accept or reject feedback across similar requests.
Words like quiet or lively can mean different things to different people. The model needs more examples tied to a specific plan, place, and outcome.
The planner should eventually hand a reviewed shortlist into the Tokyo map, where people can inspect location, context, and alternatives before acting.
Experiment journal
Iteration 01
The planner stopped letting soft scores override hard user intent. Wrong type, wrong cuisine, or wrong route role now removes a place before taste enters the score.
Iteration 02
I carried Google place ids into the planner output and rewired the export links so a selected recommendation opens the actual place when Google data is available. I also added a short model walkthrough that shows one request moving through gates, ranking, confidence, and Maps export.
Iteration 03
I reorganized the page so the first section shows the path from source list to clustering failure to gated ranking. I also grouped small neighborhoods into broader Tokyo areas, expanded cuisine filters from the dataset, added exploration mode, and added a plain-English prompt bridge for atmosphere words.
Iteration 04
I paused the fake map picker and rebuilt the planner logic so type, cuisine, and drink act as hard eligibility gates before any atmosphere scoring happens. The page now explains the model as a retrieval and ranking system, not regression or clustering.
System map
Venue identity, coordinates, types, ratings, price, reviews, and eventually opening-hour periods.
Clean the source lists, merge likely matches, derive cuisine signals, tokenize review language, and generate planner JSON.
Create venue profile vectors and later support plain-English query matching against those profiles.
Runs hard gates and transparent ranking locally in the page so the current result can be audited.
Persists QA feedback over time. It should not change weights until enough repeated labels exist.
Final action layer: open one place or export a route with multiple stops.