n9e6y
← WorkProduct24 Oct 20265 min read

ببین (Bebin)

An in-person shopping assistant for Torob. It takes a shopper from "I want X" to a verified visit: what to buy, which store, when to go, where the store is inside the mall, and what the seller has actually confirmed.

Live app ↗ API docs ↗ GitHub ↗ Open it on a phone
If the demo is down The live app may go offline one day, maybe for good. Everything it needs is on GitHub ↗, so you can run your own copy.
Built forTorob's in-person shopping challenge, part of the Star Coach hackathon
Time~1.5 days (Oct 23–24, 2026)
RoleSolo developer and designer (with Claude Code)
PlatformPersian · RTL · mobile-first · installable PWA
StackNext.js 16 · React 19 · TypeScript · Tailwind · FastAPI · Python 3.12 · SQLite FTS5 · embeddings · SSE
LinksLive ↗API docs ↗GitHub ↗
Shops
6,476
Products
225k
225,903
Offers
331k
331,506
LLM budget
~$3
in total
Search results for phones, with the need-finder asking what the phone is mostly forSearch
The seller confirmed the price and stock: the mascot holds the stamp with the time it was givenVerified
Inside the mall: the shop's floor and unit, with the floors to pick fromIn the mall
«قبل از رفتن، ببین.»Look before you go.

The problem

Online price comparison stops at the screen. In Tehran, a lot of shopping still happens in person, in small shops, arcades (پاساژ) and malls, and that's where it goes wrong:

  • "I don't know which model is right."
  • The listed price is old, and nobody knows whether the item is in stock.
  • You cross the city and the shop is closed, or the owner never picks up.
  • You find the mall but not the shop inside it.
  • You can only buy on installments, and you don't know who offers them.

The challenge asked for a flow that covers four steps: choose → find stores → compare stores → contact stores, plus all five pain points.

Figure · The journey

Four steps, five places it goes wrong.

Each pain point sits under the step where it hurts · click a step to jump to it

What it does

Choose: search that understands plain Persian

Type «گوشی سامسونگ زیر ۶۰ میلیون اقساطی» ("Samsung phone under 60 million, on installments") and Bebin pulls out the budget, the brand and the payment condition as removable chips. If you're unsure, a need-finder asks short questions about budget and use, and you can compare products side by side.

Find: stores near you, honestly labelled

Sellers appear in a list and on a map, ranked from your location, with walking or taxi times. Shops that only match the category are kept in a separate group. The app never implies they carry the item.

Compare: rankings that explain themselves

Sort by cheapest, nearest, or a balance of both. Each shop shows whether it will be open when you arrive, not just now, and a «چرا این ترتیب؟» ("why this order?") sheet breaks down the score.

Figure · Ranking weights

What "balanced" means.

Share of the score per factor, by ranking mode · real weights from the ranker

Modepricedistanceopen on arrivalfreshverified
balanced0.350.300.150.100.10
cheapest0.600.150.100.100.05
nearest0.150.600.150.050.05
Unlabelled segments are .05.

Ask: «از فروشگاه بپرس»

Ask up to three shops about today's price, stock, exact location or installments. Replies arrive live. A confirmed answer gets a stamp (مهر) with the time it was given, and it replaces the old listed price.

If a shop doesn't answer, or says it's out of stock, Bebin offers alternatives in order: the same product at another shop, similar models at the same shop, then similar models nearby.

Figure · Inquiry statesSchematic

The life of a question.

Boxes = states, ink outline = the outcome you want, dashed = dead ends that hand off to alternatives

sentto ≤ 3 shops delivered seen answeredlive over SSE stamped · مهرreplaces listed price cancelledby the shopper no answerdeadline passed one nudgeextends deadline out of stockseller's answer alternatives same productanother shop similar modelssame shop similar modelsnearby
Alternatives are offered in this order.

Go: a plan that knows what will be open

The visit plan orders your stops, groups the ones in the same mall, and warns you when a shop will be closed on arrival. It can reschedule («برای شنبه ۱۰:۰۰ بچین», "plan it for Saturday 10:00") or swap the stop for an alternative. Mall pages show the floor, unit and neighbouring shops, so you can find the shop once you're inside.

Also

  • An assistant chat that uses the same tools: it searches, compares, finds sellers, asks stores (only after you confirm) and plans the visit. Its answers render with the same cards as the regular screens.
  • A seller panel (/seller): open it on a second phone and answer the inquiries as the shop.

Honesty, by design

The dataset has no stock column, every phone number is fictitious, and most optional fields are empty. Instead of hiding that, Bebin is built around it. These rules are enforced in the API, not only in the UI:

  • Stock is never stated unless a seller answered, and the answer shows when they answered.
  • Listed price, last price change and seller verification are always shown separately.
  • Unknown is a first-class value: «نامشخص», drawn dashed, with a way to find out.
  • Simulated seller replies are labelled «پاسخ شبیه‌سازی‌شده» ("simulated reply").
Figure · Trust scale

Saturation = certainty.

How Bebin draws a price, from most to least certain · drawn here in ink

مهر
Seller-verifiedstamp green · most saturated
listed
Listed and freshplain ink
stale
Stalewilted olive
؟
Unknowngrey, dashed
← more certainless certain →
Figure · Price freshness

How fresh are the prices?

Age of each listed price, measured against the snapshot date (2026-10-13)

AgeShare of prices (%)
Older than two weeks55
Newer45
Why freshness is printed next to every price.

How it works

Figure · ArchitectureSchematic

Two apps, two SQLite files.

Boxes = processes, cylinders flattened to boxes = data, dashed = optional

Phone · webNext.js 16PWA, RTL REST + SSE apiFastAPI, single instance in-process schedulerinquiry simulator + live SSE hub llm.pydaily token guard catalog.dbSQLite + FTS5, read-onlybuilt from the CSVs app.dbSQLite (WAL): inquiries,verifications, LLM cache vectors/226k × 256 embeddingsoptional LLM providerany OpenAI-compatible optional, every featurehas a fallback
Ink outline = the one API process.

Key decisions

  • Few moving parts. Two apps and two SQLite files: no Redis, no queues, no workers. The read-only catalog and the user data live in separate files, so rebuilding the catalog never touches inquiries.
  • Deterministic code ranks and filters; the LLM understands, asks, explains and writes. Rankings are reproducible and testable, and an LLM outage can't break a core flow.
  • Hybrid search. SQLite FTS5 keyword search fused with vector embeddings (reciprocal rank fusion, rescaled onto each query's keyword-score range). It handles typos and synonyms: «کفش ورزشی» ("sports shoes") went from 11 results to about 70.
  • Live updates over SSE, with replay after a reconnect and an in-process scheduler that survives restarts.
  • The API returns view-ready data: reasons, trust fields, distances, open status. The frontend and the assistant reuse the same payloads, and the TypeScript types are generated from the OpenAPI schema.
  • Built for Iran: self-hosted fonts, logical (RTL-safe) CSS only, Persian digits and Jalali dates, deployed on an Iranian host.
Stack Next.js 16, React 19, TypeScript, Tailwind, TanStack Query, Zustand, Leaflet · Python 3.12, FastAPI, pydantic v2, SQLite, numpy · gpt-5.4-mini and text-embedding-3-small through an OpenAI-compatible provider · Docker on Liara.

Design

The whole identity uses two hues: a radish's red and its leaves' green. Bright radish means "you can do this" and marks every call to action. Dark beet means "something's wrong". The «oo» in the wordmark is a pair of glasses, which doubles as the search icon.

The mascot is «تربچه», a 24×24 pixel radish that appears only at moments that matter, at most once per screen: searching, waiting for a reply, happy with the stamp, sleepy when a shop is closed, wilted when you're offline.

Figure · تربچه
defaulthello
searchinglooking
askingasking a shop
waitingawaiting reply
happystamped
sleepyshop closed
wiltedoffline
apronseller

All 25 designed screens were built, with 44 px touch targets, visible focus rings, reduced-motion support, and colour never used as the only signal.

Read more

«قبل از رفتن، ببین.»Look before you go.
Contact

Let's talk.