FloCafe
FloCafe는 구독 없이 무료로 주문·테이블·주방 디스플레이·로컬 데이터를 관리하는 로컬 퍼스트 레스토랑 POS입니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Vibe-coded Montreal-Area Coffee Roaster Index
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Crawls specialty coffee roasters' online shops and builds a catalog of their whole-bean coffees: name, origin, process, roast level, description, and price per kg for every bag size.
roasters.toml ──crawl──▶ products (raw, per roaster) ──normalize──▶ beans + sizes ──export──▶ JSON / CSV
Crawl (deterministic, no LLM). One adapter per storefront platform pulls
the full catalog and stores each product untouched (products.raw_json):
shopify: the public /products.json endpoint, optionally limited to one
collection with path (49 of the 55 roasters)woocommerce: the public Store API, with variations fetched in a second
listingsquarespace: ?format=json on the shop pagewix: the store sitemap, plus the product JSON Wix embeds in each pagedispatch: Dispatch's headless Shopify, read from the Next.js __NEXT_DATA__cafeunion: Café Union's custom shop, scraped from its product pagesEach crawl upserts every product. Products missing from a successful crawl
get retired_at set, and they come back automatically if they reappear.
A failed crawl changes nothing. A crawl that would retire more than half of
a catalog is refused (--allow-mass-retire overrides), because that usually
means the site changed and the adapter broke.
Normalize (LLM). Products that are new, or whose content changed, go through:
claude CLI, with a strict JSON schema.
It decides whether the product is a single whole-bean bag (samplers,
subscriptions, capsules and so on are excluded), and extracts the name,
origins, process, roast level, varieties, tasting notes and an English
description. When the roaster doesn't state a roast level, the model
infers one from the tasting notes and description, and
roast_level_source records whether it was stated or inferred.Price and stock changes don't trigger a new LLM call. Only title,
description, tags and variant labels count (RawProduct::content_hash).
Sizes are computed in code on every crawl. Grind variants collapse into
one size, the cheapest in-stock price wins, and price per kg is derived from
the weight. The weight is parsed from the variant label ("300g",
"2 lb", "2,27kg - 5lb"). When the label has no weight, the LLM reads it from
the description. Failing that, the platform's variant weight is used if it's
a standard bag size, and then the roaster's default_weight_g. Each size
records its weight_source. Variants matching exclude_variants (e.g. coffee
sold inside a canister) are ignored.
cargo run --release -- run # crawl + normalize everything
cargo run --release -- crawl --only nordik,zabcafe
cargo run --release -- normalize --limit 20 --concurrency 8
cargo run --release -- normalize --force --beans-only # re-run coffee bags after a prompt change
cargo run --release -- status
cargo run --release -- export --format csv --out exports/beans.csv
cargo run --release -- export --format json --include-retired
cargo run --release -- export --format site --out site/index.html
export --format site bakes the beans into site/template.html and writes a
single self-contained site/index.html. It needs no server or build step:
open it directly, or deploy it to any static host. The page has:
The template is compiled into the binary, so rebuild after editing it.
Normalization shells out to claude -p --model haiku, so you need to be logged
into Claude Code. Use --model to pick another model and --claude-bin to
point at a different binary.
cargo run -- detect https://new-roaster.com # prints a [[roaster]] entry
Paste the entry into roasters.toml. Detection identifies the platform and
checks whether /en/ serves translated content. Sites on an unsupported
platform need a new adapter in src/adapters/.
SQLite at roaster-rank.db:
products: raw catalog rows with first_seen, last_seen and retired_atbeans: one row per screened product. is_coffee_bag = 0 rows record why
the product was excluded.sizes: current bag sizes, prices and price per kgcrawls: log of every crawl attempt