尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Extract and filter listings on the BBX wine auction platform and set alerts
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Tools for finding below-market fine wine on the BBX (Berry Bros. & Rudd)
trading exchange and managing private cellar evidence:檢視完整 README ↗Tools for finding below-market fine wine on the BBX (Berry Bros. & Rudd) trading exchange and managing private cellar evidence:
The Streamlit application and hourly scanner are thin wrappers over the shared
core/pipeline.py scan pipeline. The Next.js application is a separate reader
and private-data workflow over Supabase.
Three phases, ordered cheapest-first:
prod_product index (stock_origin:'BBX', optional new_to_bbx window, colour, price band, format).getBiddableCprStock, which returns one entry per format (06-00750, 01-01500, …) for each parent SKU. Every metric is resolved to the discovered listing's own format: the ask is the effective-lowest per-format price from Algolia, while market_price and last_bbx_transaction come from the REST entry whose format matches (never entries[0], which is an arbitrary format). Records failing the market/last-transaction discount thresholds are dropped here.A single wine can appear once per format; the pipeline keeps ask, market, "next" and the displayed case_format all pinned to the same format so a magnum's price can never be attributed to a case (or vice versa), and notification dedup is keyed by parent_sku + format_code.
Threshold semantics: pct_market is always enforced; pct_last and pct_next are enforced only when computable (a wine with no last transaction or no competing seller passes on pct_market alone).
The prod_product index truncates any single query at 1,000 hits (10 pages × 100; larger hitsPerPage is clamped). The full BBX book is ~15,000 listings, so a broad query (e.g. "All BBX") cannot be paged in full by one filter set.
fetch_listings handles this by sharding: when a filter set exceeds the cap it splits by facet (region → colour → price band → vintage), recursing only into shards still over the cap, with a NOT query per level to catch records missing the facet, and de-duplication by objectID. Sharding is lazy — narrow queries (the hourly bot's "new in last 1 day") never shard and cost the same ~3 requests they always did.
Politeness is built in: a jittered sleep after every request, exponential backoff on 429/5xx, short-page early exit, and reuse of parent facet counts so under-cap shards skip their own count query. A full-book sweep is a few hundred requests over ~3 minutes.
bbx/
apps/
streamlit_app/streamlit_app.py # UI wrapper
arbitrage_bot/run_arbitrage.py # CLI/CI wrapper + Slack + dedup
daily_sweep/run_sweep.py # full biddable-universe snapshot
web/ # Next.js catalogue and private cellar app
core/
pipeline.py # shared 3-phase scan funnel (ScanConfig, run_scan)
fetch_listings.py # Algolia fetch helper
fetch_bbx_variants.py # GraphQL order-book helper
slack.py # Slack webhook helper
notification_state.py # dedup rules + S3/local state persistence
data/
payload.json # GraphQL payload template
tests/ # pytest unit tests (dedup, discount maths, sharding)
docs/
ROADMAP-2026-07.md # dated product roadmap and rationale
CELLARTRACKER-IMPLEMENTATION.md # fourth dataset implementation
CODEBASE-REVIEW-2026-07-31.md # dated defects and maintenance review
supabase/
migrations/ # deployed database schema history
tests/ # database access and behaviour tests
.github/workflows/arbitrage.yml
.github/workflows/daily_sweep.yml
.github/workflows/database-migrations.yml
ALGOLIA_APP_IDALGOLIA_API_KEYStreamlit reads these from .streamlit/secrets.toml; the bot reads environment variables.
The Next.js application also reads them from its server environment for the
owner-only release-price and CellarTracker matching pages. Do not prefix either
variable with NEXT_PUBLIC_.
SLACK_WEBHOOKS3_BUCKET, S3_STATE_KEY (plus AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION)When the S3 variables are unset, state falls back to a local file at data/arbitrage_state.json (gitignored).
streamlit run apps/streamlit_app/streamlit_app.py
Requires .streamlit/secrets.toml.
See apps/web/README.md for environment variables,
owner bootstrap, local development and release checks.
export ALGOLIA_APP_ID="..."
export ALGOLIA_API_KEY="..."
export SLACK_WEBHOOK="..."
python apps/arbitrage_bot/run_arbitrage.py
Runs hourly 08:00–23:00 UTC via .github/workflows/arbitrage.yml, with a concurrency guard, unit tests before each run, and a Slack alert if the job itself fails.
pip install -r requirements-dev.txt
python -m pytest tests/ -q
The Next.js checks run separately:
cd apps/web
npm run lint
npm test
npm run build
.github/workflows/ci.yml runs the Python and web checks on every pull
request and on push to main. The database migration workflow
(database-migrations.yml) runs separately, only on changes touching
supabase/**. The scheduled scanner and sweep also run the Python tests
before their production jobs.
Implemented in core/notification_state.py:
REMINDER_INTERVAL_DAYS → notify (reminder).The dated implementation roadmap is
docs/ROADMAP-2026-07.md. For the full set of
specs, phase implementation history and dated reviews, see the index at
docs/README.md.
Agents (and anyone else) making changes here should read
AGENTS.md first — it records operational rules learned from
real incidents, including how database migrations actually reach
production.