Inspiration
Fix-and-flip crews and listing agents still finish houses to a national default: greige walls, white quartz, gray floors, an acoustic playlist, and "steps from shops and dining." The buyer who will actually write the offer already has a taste, and it shows up in where they eat, what they buy, what they watch, and what they play around that location. That graph is the part a language model cannot honestly invent. Qloo is the source. Without it, the advice collapses back to greige, and the product shows that collapse on purpose.
What it does
You enter a location (address, neighborhood, city, or zip), a price band, and an optional age band that maps to Qloo's three demographic buckets. An agent calls Qloo and the app returns:
- A buyer persona grounded in the entities that came back.
- A renovation and staging brief (palette, materials, what to leave out), including a note when two taste clusters are close and should not be averaged.
- Listing copy and a headline.
- An open-house playlist made of artists Qloo returned, and a hotspot list of places from the location query.
- Channel notes tied to those signals and to the age filter you actually sent.
Every card on the Qloo side cites the entity or tag that drove it. A split view shows the same house with no Qloo data. Price and beds stay visible as operator inputs. They are not pretended to be Qloo fields. Paint colors are labeled as our finish map, not as a Qloo output.
If the Qloo key is missing, the app runs in a labeled demo mode and GET /api/health reports "mock": true. Live errors are shown. They are not filled with demo entities.
How we built it
Python, FastAPI, one page of HTML/CSS/JS, Docker, Railway.
Qloo hackathon base URL: https://hackathon.api.qloo.com. Header: X-Api-Key.
Endpoints:
GET /searchto resolve the location to entity ids.GET /v2/insightsfor places withfilter.type=urn:entity:placeandfilter.location.query. Optional secondary signalsignal.demographics.age(35_and_younger,36_to_55,55_and_older).GET /v2/insightsforurn:entity:brand,urn:entity:artist,urn:entity:movie,urn:entity:tv_show, andurn:entity:destination, usingsignal.interests.entitiesfrom the places (andsignal.interests.tagsonly as a retry). Demographics alone are not sent. Public docs treat a primary interest or location signal as required, and several non-place types do not documentfilter.location.query.GET /v2/tagswithfilter.query.
/recs and /recommendations are not called.
The model uses an OpenAI-compatible tool loop (LLM_BASE_URL, LLM_API_KEY, LLM_MODEL; no provider is hardcoded) and decides which of those calls to make. Cap: 8 model tool calls, 12 Qloo calls, 2 concurrent LLM requests. If the model cannot call tools, a deterministic plan runs the same requests and the trace says so. The brief is compiled in code from the returned entities. The no-Qloo column is a separate call with no tools and no graph, or a fixed template when no model is configured.
Responses may be cached on the server in a gitignored directory. Live payloads are not in the repo. Tests use synthetic fixtures.
Challenges
Hackathon keys do not work on api.qloo.com, so the client defaults to the hackathon host and the UI warns if the base URL is pointed at the wrong one. Insights calls need a primary signal; an age bucket alone is not enough, so cross-domain taste is requested from the place ids at the property rather than from a guessed city stereotype. Tool calling on OpenAI-compatible endpoints is inconsistent, so the fallback plan exists and is tested. The finish map can be wrong about a material even when the citation is real; the UI says the palette is interpretive. The build was written against synthetic fixtures first, so parsers are defensive; the deployed demo now runs against a live hackathon key (a Phoenix, AZ scout makes 8 Qloo calls and returns real places, brands, artists and tags).
Accomplishments that we're proud of
The comparison is structural, not a caption. Citations resolve only to ids the tools returned. A Qloo outage does not silently become demo data. Demo mode is labeled on the page and in /api/health. The agent trace shows the path and query actually sent. Offline tests cover the client (including cache and key redaction), the tool loop with a fake model, the fallback plan, the call cap, and the HTTP API.
What's next
Tune the finish map against more live markets (for example, /search returned no entity for "Phoenix, AZ" while location insights still returned places). Let a contractor check off the spec. Save a brief per property. None of that is built. We also have no measured effect on a real listing.
Built with
Python, FastAPI, Qloo Taste API, OpenAI-compatible tool calling, Docker, Railway.