Australian grocery price comparison. See BRIEF.md for the full product spec.
Australian grocery price comparison app with basket optimisation, unit pricing, and automated fake-special detection across Coles, Woolworths, and Aldi.
Type: Application Skill Loadout: PAUL (required), AEGIS (post-build audit), ui-ux-pro-max (frontend phases), tdd-workflow, frontend-design Quality Gates: 80% test coverage, security scan (no HIGH/CRITICAL), WCAG AA accessibility, Core Web Vitals (LCP < 2s, CLS < 0.1)
Australian families spend $12,480/year on groceries (Canstar 2025). Coles and Woolworths hold 65%+ market share and have been found by the ACCC to run misleading "discount" promotions. The ACCC's 2025 inquiry now recommends mandatory pricing APIs from supermarkets.
Every previous comparison app has either died (SmartCart, PriceHipster), pivoted to selling data (Frugl), bloated into affiliate funnels (WiseList), or paywalled core comparison (Grocerize at $4.99/mo). BasketWise occupies the "honest, fast, basket-optimised, API-ready" position that nobody else holds.
Target user: Dual-shopper Australian household (2-4 people, $180-280/week spend) within driving distance of both a Coles/Woolies and an Aldi. Would split their shop across two stores if the app made it effortless.
Monetisation: $2.99/month premium for unlimited saved lists, price alerts, household sharing, CSV export. Core search, basket, price history, and specials are always free. No ads in shopping flow. No affiliate funnels.
Unique differentiators:
- Automated fake-special detection using 60-day trailing average (no competitor automates this)
- Unit price at equal visual weight to ticket price
- Aldi included (Grocerize is Coles/Woolies only)
- Mix-and-match basket optimisation with per-item store override
- PriceProvider interface ready for ACCC-mandated APIs
- Every price timestamped — no stale data without warning
Domains: basketwise.com.au, basketwise.app (available as of 2026-04-18)
| Layer | Choice | Rationale |
|---|---|---|
| Frontend | Next.js 14 + React + TypeScript | SSR for SEO on product pages, API routes for backend |
| Styling | Tailwind CSS + shadcn/ui | Fast path to polished MVP, command palette for search |
| Database | PostgreSQL (Neon) | Serverless, branching, connection pooling, full-text search |
| ORM | Drizzle ORM | Type-safe, lightweight, excellent PostgreSQL support |
| Web Hosting | Vercel | Zero-config Next.js deploys, edge caching |
| Scraper Hosting | Railway ($5/mo) | Reliable container hosting with cron |
| CI/CD | GitHub Actions | Lint, type-check, test, deploy (separate workflows for web + scraper) |
| Entity | Key Fields | Relationships |
|---|---|---|
| Product | id, name, category, brand, pack_size, unit_of_measure | has many StoreProduct, PriceRecord |
| Store | id, name, slug, logo_url, website_url | has many StoreProduct, PriceRecord |
| StoreProduct | id, product_id, store_id, store_sku, store_name, store_url, barcode | belongs to Product, Store |
| PriceRecord | id, product_id, store_id, price, unit_price, unit_measure, is_special, special_type, scraped_at, date | belongs to Product, Store |
| User | id, email, is_premium, premium_expires_at | has many Basket, PriceAlert, FavouriteGroup |
| Basket | id, user_id, name | has many BasketItem |
| BasketItem | id, basket_id, product_id, quantity, assigned_store_id | belongs to Basket, Product |
| PriceAlert | id, user_id, product_id, store_id, target_price, is_active | belongs to User, Product |
| FavouriteGroup | id, user_id, name, sort_order | has many FavouriteGroupItem |
| FavouriteGroupItem | id, group_id, product_id | belongs to FavouriteGroup, Product |
- PriceRecord: composite unique on (product_id, store_id, date) — upsert per scrape
special_typeenum: half_price, multi_buy, prices_dropped, down_down, rollbackunit_measureenum: per_kg, per_L, per_100g, per_100mL, per_unit- StoreProduct.barcode ready for GS1/GTIN when ACCC API mandate lands
- V1: ~2,000-3,000 hand-matched canonical products. Unmatched show "price not available"
- Product matching at scrape time (ingestion pipeline), not query time
Magic link email — no passwords. Anonymous users get full comparison via client-side cached data. Auth only for premium features.
| Route | Method | Auth | Purpose |
|---|---|---|---|
| /api/search | GET | none | Product search with prices, includes is_fake_special |
| /api/products/:id | GET | none | Product detail with store prices |
| /api/products/:id/history | GET | none | 90-day price history |
| /api/categories | GET | none | Category tree |
| /api/specials | GET | none | Current specials feed |
| /api/basket | POST, PATCH, DELETE | required | Basket CRUD |
| /api/basket/:id/optimise | GET | required | Single-store + mix-and-match totals |
| /api/auth/magic-link | POST | none | Send magic link |
| /api/auth/verify | GET | none | Verify token |
| /api/alerts | GET, POST, DELETE | required | Price alert management |
| /api/favourites | CRUD | required | Favourite group management |
| /api/health | GET | none | Scraper health (last_successful_scrape per store) |
Edge-cache search results at Vercel with 1-hour TTL. Rate limit by IP (anon) and user token (auth).
- Web app: Vercel — auto-deploys from main, preview deploys on PRs
- Database: Neon PostgreSQL — main branch = production, dev branches for staging
- Scraper: Railway container — cron every 12h (Coles/Woolies), weekly (Aldi)
- CI/CD: GitHub Actions — lint (ESLint), type-check (tsc), test (Vitest), deploy
Tailwind + shadcn/ui. Neutral base (slate/zinc). Store colours for identification only (Coles red, Woolies green, Aldi blue). Fake-special badge: rose-600 (distinct from Coles red). Best Price: emerald-500 with green row tint. BasketWise accent (mustard/teal TBD) for branding only.
| View | Purpose |
|---|---|
| Search / Browse | Landing. Search bar, category sidebar, product cards with 3-store columns, unit price, special badges, timestamps |
| Product Detail | Store prices (green highlight cheapest), 90-day chart (1mo/3mo/all toggles), min/avg/max stats, fake-special badge |
| Basket Summary | Summary card (Total/Savings/Stores), store-grouped items, store pill dropdown per item, Compare + List views |
| Shopping List | In-store companion. Category-grouped, checkbox, large touch targets, mobile-first critical path |
| Specials Feed | Half-price and 30%+ deals, filterable by store/category |
| Landing Page | Savings calculator slider ($50-500/week), projected savings, fun equivalents |
Mobile-first mandatory. PWA with offline shopping lists in Phase 6.
- Scraping: Respectful rate limiting, cache aggressively, no copyrighted images, PriceProvider abstraction for API swap
- Auth: Magic link, no passwords. Email only PII — encrypt at rest
- Input: Parameterised queries (Drizzle). Sanitise search for XSS
- Rate limiting: Edge cache + IP/token rate limits
- Privacy: Australian Privacy Act compliance from day one. Privacy policy at launch
- Monitoring: last_successful_scrape per store, health endpoint, alert if >24h stale
Neon schema, Railway scraper (Coles/Woolies, then Aldi), PriceProvider interface, daily upserts, health monitoring. Seed ~500 hand-matched products. Scraper-first: accumulate history from day one for Phase 4.
Next.js scaffold on Vercel, shadcn/ui design system, full-text search (pg_trgm/tsvector), product card grid with 3-store prices, unit pricing, special badges, product detail with green cheapest-row highlight.
localStorage basket (anonymous), summary card (Total/Savings/Stores), store-grouped items, store pill dropdown, Compare + List views, specials feed tab.
90-day Recharts chart (1mo/3mo/all toggles), min/avg/max stats, 60-day trailing average, is_fake_special computed field, "Not actually a saving" badge. Requires 60+ days of Phase 1 scraper history.
Magic link auth, server-side basket sync, favourite groups, price alerts (email), household sharing (premium, up to 6), savings tracker, Stripe ($2.99/mo).
PWA + offline lists, landing page with savings calculator, SEO + structured data, privacy policy/terms, OG social cards, Core Web Vitals audit, media outreach (ACCC angle).
Timeline: Phases 1-3 = core product. Phase 4 = differentiator. Phase 4 can't ship until 60 days after Phase 1 scraper starts. Phases 1-4 = launchable product.
- Canonical product table with per-store SKU mappings — reliability over fuzzy matching. "Price not available" > bad match.
- Scraper-first phase ordering — accumulate price history from day one for Phase 4's fake-special detection.
- One PriceRecord per product per store per day — sufficient for consumer charts. PriceAuditLog for intra-day if ACCC needs it.
- localStorage baskets for anonymous users — no server overhead, migrate on auth.
- Railway for scraper over NAS — public product needs infrastructure independence.
- Generic category icons — avoid copyright issues. Open Food Facts CC images for V2.
- Magic link auth — no password storage, simpler, better security.
- is_fake_special as computed field on search — avoids extra round trip.
- Neutral palette with store-only brand colours — fake-special badge (rose-600) distinct from Coles red.
- PriceProvider interface abstraction — architecture-level bet on ACCC regulatory direction.
- Full-text search: pg_trgm vs tsvector — need testing with real product names
- Aldi scraping feasibility: staples vs Special Buys rotation
- Product matching scale strategy: 500 → 5,000+ (semi-automated heuristics?)
- BasketWise accent colour: warm mustard or teal
- Household sharing sync: WebSockets vs SSE vs polling
- Primary domain: basketwise.com.au vs basketwise.app
| Competitor | Status | Our Advantage |
|---|---|---|
| Grocerize | Alive, $4.99/mo | We include Aldi, no paywall |
| WiseList | Alive, bloated | No affiliate funnels |
| Bargeroo | Alive, closest | Automated fake-special detection, unit pricing, freshness timestamps |
| TrolleyMate | Alive | Web access, price transparency |
| Szumark | Alive | Full basket comparison, not just specials |
| SmartCart | Dead | Sustainable architecture (not browser extension) |
| Frugl | Zombie | Consumer-first (not B2B data play) |
- ACCC 2025 Supermarket Inquiry: mandatory pricing API recommendation
- Canstar 2025: $12,480/year household grocery spend
- Grocerize, Bargeroo, TrolleyMate visual teardowns (April 2026)
- Domains: basketwise.com.au, basketwise.app
Graduated from SEED ideation: 2026-04-18