Inspiration
I kept picturing a broker or a landlord about to sign a retail tenant. The wrong concept on the wrong corner is a lost lease. Foot traffic, census, income, and crime are the usual substitutes, and none of them say whether the block already likes this kind of place.
Qloo already holds that taste graph: places, brands, and the affinities between them. The hackathon asked for an agent that uses it. I wanted the output to be the memo that agent hands someone before they sign, with every claim tied to a hop.
What it does
I take one address and two concepts and return a go, a caution, or a no-go.
The desk shows each hop as it finishes. I resolve the block’s places, search each concept’s anchors, read the neighborhood’s brands and artists, then score both concepts in code. The score is 100 × (0.55 place affinity + 0.30 brand overlap + 0.15 explainability). Go is 70 or above. Caution starts at 45. Below that is no-go. The model only writes sentences that quote those hops. If the draft invents a number, I throw it out and use a template that still cites the scores.
Qloo is load-bearing. The same address and the same concept names, with the tools unplugged, still go to the model. I stamp that note UNGROUNDED and cite no affinities and no scores. Unplug the graph and the memo is a guess.
I put two presets on the desk. West Village, October 24, 2026: natural wine / small plates 90 GO against a sports bar 86 GO. Wine leads. Williamsburg runs the same loop: contemporary apparel 87 GO against off-price 58 CAUTION. I left Silver Lake off the desk because Seco does not resolve as an exact-name place.
How I built it
I built this solo. Next.js, TypeScript, Tailwind CSS, and shadcn/ui. Two screens: the deal desk, and the deal itself (live trace, memo, evidence).
The server is the only thing that calls Qloo. The browser never sees the key. I allow GET on https://hackathon.api.qloo.com for /search, /v2/tags, and /v2/insights. I resolve names through search first. Insights then takes those entity IDs, with explainability on for the score hops.
I keep fit scores in code, in weights I chose before any back-test: place 0.55, brand 0.30, explainability 0.15. The memo writer is the Vercel AI SDK with a zod schema. I use OpenAI when that key is set, then Anthropic, then Qwen. A second check rejects drafts that name a place, an affinity, or a verdict the hops do not support.
I store deals in a private Vercel Blob, because a serverless isolate does not remember the deal between the action that creates it and the page that runs it. The run route streams newline-delimited JSON so the trace can mark a hop DONE while the next one is still RUNNING. I deployed the app at https://sitefit-theta.vercel.app. Source is MIT at https://github.com/ctrlaltelite26-hue/sitefit.
Challenges I ran into
The hackathon host allows 10 Qloo calls a second. One grounded deal is about a dozen calls: the site, each anchor, neighborhood brands, neighborhood artists, then place and brand insights for both concepts. On my laptop those calls were slow enough, and cached, so the run finished. On Vercel they returned in a few hundred milliseconds and the tenth call came back 429. The monthly quota was fine, about 49,700 of 50,000 still left. I pace at 8 calls a second and wait on the reset header before one retry.
Serverless memory was the next break. The deal existed in the process that handled the form, then the page and the run route woke up in another process and the deal was gone. A private Blob store is the record now.
The model wants to sound sure. It will invent an affinity, a verdict, or a place that was never returned. I give grounded drafts one retry, then the template. The ungrounded path was the opposite bug: skipping the model made the “guess” a static sentence. I now call the model from the names alone, and the checker rejects any score so the page cannot show a fake one.
Coverage is exact-name. Riviera Cafe does not exact-match. Seco and Barr Seco are not an exact-name place, so I cannot score Silver Lake’s wine concept and I hide that card. The locality string has to be Silver Lake, Los Angeles. Bare Silver Lake resolves to a different town. Invalid Insights parameters also fail quietly: an empty 200 is not proof the block has no culture.
The last UI bug was the trace itself. One JSON response arrived only when the memo was done, so the desk looked frozen. I stream each stage now. Rows flip from WAITING to RUNNING to DONE before the verdict is written.
Accomplishments that I'm proud of
I did not edit the weights after the live numbers came in. On October 24, 2026 the score picked the survivor in 3 of the 3 opened-versus-closed cases it could score. Three cases dropped. There were no misses among the scored cases. Buvette 97 against The Windsor 78. Madewell 94 against Urban Outfitters 93. Bar Bandini 97 against Starbucks 93.
The demo contrast is the same address twice. With Qloo, a cited memo. Without it, a model note that I stamp UNGROUNDED and that has no score. West Village is the run I trust: 90 GO against 86 GO, wine still leading, on the same formula as the back-test.
The desk shows the work. Six hops can already read DONE while the memo is still waiting, and the draft panel stays empty until the last tool returns.
What I learned
A taste graph is a set of hops with affinities, and the sentence comes after. When I let the model own the score, the memo sounded finished and the number was un-auditable. When I kept the score in code, a wrong sentence was easy to throw out.
Quota and rate are different limits. Tens of thousands of monthly calls were left when a single deal failed, because more than 10 landed in the same second. My long-lived dev server hid that. A cold function did not.
Dropped rows belong in the result. If I retuned the weights so Seco scored, or loosened the name match until the wrong place got in, I would raise the hit rate and ruin it. Fairfax has drifted toward a neighborhood restaurant. Aland left its flagship and moved. I keep those caveats next to the hits.
Cultural fit is a narrow claim. I would rather cite the hops I have than borrow a census number.
What's next for SiteFit
A landlord usually holds more than one corner. I will take one concept and a short list of addresses, score each address with the same formula, and return a ranked memo: which corner is a go, which is caution, and the hops behind each row. West Village against Williamsburg for the same wine bar is the first version of that page.
The West Village demo already returns two goes, 90 against 86. I will add the flip: which cited affinity, moved by a stated amount, would change the winner or drop a concept under 70. That number stays arithmetic on the frozen weights. The model still only writes the sentence.
The memo has to leave the desk. I will give each finished run a link a broker can forward, with the verdict, the citations, and the evidence frozen as they were that day. An ungrounded note keeps its stamp on that link, so a forwarded guess cannot be read as a score.
Built With
- nextjs
- qlooapi
- shadcn/ui
- tailwind
- typescript
- vercelaisdk
- zod
Log in or sign up for Devpost to join the conversation.