Operations dashboard
| Order | Stores | Status | Picker |
|---|---|---|---|
| BK-2841 | PnP · Sixty60 | On the way | Sipho |
| BK-2840 | Woolies | Picking | Nomvula |
| BK-2839 | Spar · PnP | Reconcile | Sipho |
This specification translates the Baskit MVP business plan and Phase 1 build plan into an executable engineering brief. It states what gets built, how the pieces fit, how the data is shaped, which screens are required, and what the effort is. It is written to be handed directly to a build team or an autonomous coding agent and worked up into a costed plan.
The starting position is strong. A complete visual design already exists for both the customer app and the picker app. The remaining work concentrates in three places: the services beneath the screens, one workstream that has never been designed (the operations console), and the operational wrapper around a manually run beta.
Phase 1 runs with no retailer API access. Picking and payment are performed manually in-store. Every technical and operational choice below follows from that constraint.
Flutter. Browse, compare prices across retailers, build one basket, Smart Split, check out, pay, and track. Fully designed.
Flutter. Accept jobs, pick aisle-by-aisle, handle substitutions, bag, and hand off against a code. Fully designed.
Web. Cohort approval, catalog, orders, picker assignment, reconciliation, and reporting. Net-new — designed here.
CodeIgniter API, the Smart Split engine, and a scheduled Python price pipeline feeding a shared PostgreSQL database.
| Layer | Choice | Why |
|---|---|---|
| Client | Flutter, single monorepo | Two apps from one codebase and shared packages. The design system, money formatting, models, and API client are written once and reused across the customer and picker apps. |
| API | CodeIgniter 4 (PHP) with Shield auth | The team’s language. Handles auth, catalog, cart, orders, picker jobs, and outbound integrations comfortably. Shield provides the customer, picker, and admin roles without hand-rolled auth. |
| Database | Azure Database for PostgreSQL — Flexible Server | One managed relational store for catalog, price history, orders, and the money ledger. Managed, never self-hosted on a VM. |
| Price pipeline | Python, scheduled job | Scraping and cross-retailer matching are their own discipline. The pipeline runs out-of-band and shares only the database with the API — never the request path. |
| Platform | Microsoft Azure | Container Apps for the API and the scraper job, Blob Storage for images and receipts, Key Vault for secrets, Application Insights for observability. |
The REST surface is defined in OpenAPI and the Dart client is generated from it. Both Flutter apps build against a mocked backend from day one, so the app team never waits on the engine or the pipeline.
Push notifications and polling cover order tracking and the picker job feed for 150 users. No websockets in Phase 1; Redis is deferred until a concrete need appears.
For a closed beta the load is minimal. The architecture is scale-ready; effort goes to reconciliation correctness and price freshness, which are the real Phase 1 risks.
The catalog is the spine: products carry a substitution group and tags, and retailer_prices holds a price per store with a capture timestamp and a confidence value, so the engine can down-weight stale data. A basket becomes an order, which splits into one order_store per retailer — each order_store is a picker job.
Money is deliberate. Every amount is stored as integer cents. The money trail is an append-only ledger, and a reconciliation row ties the customer charge to the prepaid-card spend and the receipt. Totals are never mutated in place; corrections are new ledger entries.
Thabo opens Baskit to a personalized welcome and his running savings. The home screen leads with his location, a Smart Baskit nudge (“your usual basket would cost R47.20 less this week”), and the aisles. He adds coffee, milk, eggs, and bread; on each product he sees the cheapest retailer and the alternatives.
In his Baskit he toggles Smart Split. The app splits eleven items across Checkers Sixty60 and Pick n Pay, shows he saves R13.50 against buying everything at one store, and keeps one delivery. He checks out to an ASAP window, pays R645.89 through Peach, and watches two stores pack in parallel. The saving is confirmed on arrival — the emotional peak of the product.
Sipho goes on duty in Constantia. He sees two same-store Pick n Pay jobs batched together with a bonus, and accepts. The active pick walks him aisle-by-aisle: bananas picked, avocados swapped to brown bread per the customer preference, white bread flagged as hard to find.
When the mature cheddar is not on the shelf, the substitution flow offers the customer-preferred alternative, or the option to ask the customer or refund and skip. He bags by group — cold, fresh, bakery — and releases the order to the driver against handoff code 4729. His earnings update in real time.
Nomsa runs the beta from the console. She approves the morning’s applicants, checks that staples met the freshness SLA overnight, assigns the unassigned Sixty60 leg of an order, and clears the reconciliation queue — auto-refunds pass through, and one order that ran R36.40 over the estimate lands in manual review for a decision. This console is the control room the whole manual beta depends on, and it is designed in the next sections.
The customer and picker apps are fully designed and only need to be built and wired to the backend. The operations console has no prior design, so it is specified here as Baskit-styled mockups — these are the net-new screens the build must produce.
| Customer app | Picker app |
|---|---|
| Splash, onboarding, sign-up, sign-in | Job feed (available / active / today), earnings summary |
| Home, aisles, search, category, product detail | Job detail, accept / decline |
| The Baskit (Smart Split), per-store carts | Active pick, aisle checklist, can’t-find |
| Checkout, payment, tracking, orders | Substitution flow, handoff, earnings |
| Order | Stores | Status | Picker |
|---|---|---|---|
| BK-2841 | PnP · Sixty60 | On the way | Sipho |
| BK-2840 | Woolies | Picking | Nomvula |
| BK-2839 | Spar · PnP | Reconcile | Sipho |
| Product | Equivalence | PnP | Sixty60 | Spar | Woolies | Freshness |
|---|---|---|---|---|---|---|
| Full Cream Milk 2L | mapped ×4 | R32.99 | R33.99 | R34.49 | R38.99 | 2h |
| White Bread 700g | mapped ×4 | R18.99 | R19.49 | R17.99 | R22.50 | 3h |
| Mature Cheddar 300g | 1 unmatched | R64.99 | R59.99 | — | R74.99 | 26h |
| Krönung Coffee 200g | mapped ×3 | R99.99 | R87.99 | R94.99 | R109.99 | stale · 3d |
| Order | Charged | Card spend | Receipt | Delta | Resolution |
|---|---|---|---|---|---|
| BK-2841 | R645.89 | R643.10 | R643.10 | −R2.79 | Auto-refunded |
| BK-2836 | R512.00 | R548.40 | R548.40 | +R36.40 | Review — over cap |
| BK-2833 | R289.50 | R281.00 | R281.00 | −R8.50 | Auto-refunded |
| BK-2830 | R430.00 | — | receipt pending | — | Awaiting receipt |
Application review, approval, and radius validation for the 150 founding households.
SKUs, cross-retailer equivalence, substitution groups, and price plus timestamp management.
Live orders, picker assignment, and status across the order state machine.
The messy core: the estimate against the card spend and the receipt, with a capped auto-adjust and a manual review queue.
Adjustments, and a combined issue, support, and feedback log.
The daily KPIs: GMV, AOV, out-of-stock rate, substitution acceptance, multi-store share, and contribution per order.
Every savings claim in the UI depends on this. It needs a clean cross-retailer equivalence map, a current price per store with a confidence value, and delivery-fee modeling — a split only saves money if the extra delivery cost does not erase the shelf-price gap.
Objective: minimize total cost including fees, subject to a maximum number of stores. It produces both outputs the UI shows — the optimized split and the best single-store basket, and the delta between them.
Recommendation: build a deterministic v1. It does not need to be “AI” to be correct; it needs to be right and explainable, because the app shows customers the exact rands saved. Keep it as an isolated, unit-tested service class so it stays swappable.
With no APIs, price freshness is an operational pipeline, not a feature. It underpins the engine and the trust the app promises with every “updated 12 min ago.”
Risk to name early: scraping is the activity retailers may resist. Keep the footprint modest and treat the proof pack as the route to sanctioned APIs in Phase 2.
The customer pays Baskit an estimate up front through Peach. The picker pays the real shelf price with a prepaid card. Those two numbers rarely match, so the order moves through an explicit state machine and every movement of money is a ledger entry.
Estimate, then post-shop reconciliation against the receipt. Differences within a cap auto-adjust (refund the customer or absorb a small overage); anything over the cap enters the manual review queue in the console.
Money is integer cents. The ledger is append-only. A total is never edited; a correction is a new entry. This makes every order’s money trail auditable end to end — charge, card spend, receipt, adjustment.
| Task | Effort (days) |
|---|---|
| Provision Azure environment (Container Apps, PostgreSQL Flexible Server, Blob Storage, Container Registry, Key Vault, Front Door) | 6 |
| CI/CD pipelines to Azure for the API, both apps, and the scraper | 5 |
| Observability: Application Insights, structured logging, alerting | 3 |
| Base PostgreSQL schema and migration tooling | 4 |
| CodeIgniter 4 API skeleton: structure, configuration, error handling | 4 |
| OpenAPI contract v1 and generated Dart client pipeline | 4 |
| Task | Effort (days) |
|---|---|
| Shield authentication with customer, picker, and admin roles (JWT) | 6 |
| Sign-up (two-step), sign-in, Apple and Google sign-in | 5 |
| Address management and 7 km radius validation (geocoding) | 4 |
| Beta cohort application and approval endpoints | 3 |
| Task | Effort (days) |
|---|---|
| PIM data model: products, store availability, tags, substitution groups | 5 |
| Cross-retailer equivalence mapping tooling | 6 |
| Price history model with freshness and confidence values | 4 |
| Python scraper framework: per-retailer adapters and scheduler | 12 |
| Retailer adapter: Pick n Pay | 4 |
| Retailer adapter: Checkers Sixty60 | 4 |
| Retailer adapter: Spar | 4 |
| Retailer adapter: Woolworths | 4 |
| Manual price-validation tooling (surfaced in the console) | 5 |
| Task | Effort (days) |
|---|---|
| Engine v1: cost optimization over the SKU map, including delivery-fee modeling | 10 |
| Best single-store basket and savings-delta computation | 4 |
| Confidence and staleness down-weighting of prices | 3 |
| Unit-test suite and savings-accuracy validation harness | 5 |
| Engine API endpoints (optimize basket, compare) | 3 |
| Task | Effort (days) |
|---|---|
| Shared design-system package: Baskit tokens and components | 6 |
| Onboarding and authentication screens | 5 |
| Home: shop-by-aisle, saved-this-month, Smart Baskit nudge, deals | 6 |
| Search: type-ahead, ‘did you mean’, filters | 6 |
| Category and product detail with cross-store comparison | 6 |
| The Baskit view: Smart Split versus single store, grouped carts | 8 |
| Per-store cart with ‘cheaper elsewhere’ prompts | 5 |
| Checkout: address, delivery window, synced multi-store summary | 6 |
| Payment: Peach, 3DS, saved methods | 5 |
| Tracking: multi-store, multi-driver ETAs, savings confirmation | 6 |
| Orders: history and repeat basket | 4 |
| Push-notification integration | 3 |
| Task | Effort (days) |
|---|---|
| Reuse of the shared design system | 2 |
| Job feed: available / active / today, same-store batching, earnings | 6 |
| Job detail: accept / decline, item list by aisle | 4 |
| Active pick: aisle-by-aisle checklist, progress, can’t-find flagging | 6 |
| Substitution flow: customer-preferred, ask-customer, refund / skip | 5 |
| Handoff: bagging groups, handoff code, driver detail | 5 |
| Earnings view | 3 |
| Task | Effort (days) |
|---|---|
| Split a basket into per-store picker jobs | 5 |
| Order state machine | 5 |
| Job-assignment logic (employed core, same-store batching) | 4 |
| Handoff-code lifecycle | 3 |
| Notification service: push / SMS / WhatsApp events | 5 |
| Task | Effort (days) |
|---|---|
| Peach integration: charge, 3DS, refunds and adjustments | 6 |
| Prepaid-card issuer integration: provisioning and transaction feed | 6 |
| Append-only ledger (money stored as integer cents) | 5 |
| Reconciliation engine: estimate ↔ card spend ↔ receipt | 6 |
| Capped auto-adjustment and manual review queue | 5 |
| Task | Effort (days) |
|---|---|
| Courier integration: dispatch, live tracking, handoff-code release | 6 |
| Tracking feed to the customer app | 3 |
| Task | Effort (days) |
|---|---|
| Console design in the Baskit visual language | 6 |
| Application shell and authentication | 4 |
| Cohort: application review, approval, radius validation | 4 |
| Catalog / PIM management interface | 8 |
| Price validation and freshness interface | 5 |
| Order management and picker assignment | 6 |
| Prepaid-card management and reconciliation queue | 8 |
| Delivery handoff tracking | 3 |
| Refunds and adjustments | 4 |
| Issue, support, and feedback log | 4 |
| Operations dashboard and KPI reporting | 8 |
| Task | Effort (days) |
|---|---|
| Picker recruitment and training | 6 |
| Catalog build: 600–1,200 validated SKUs | 15 |
| Prepaid-card procurement and reconciliation SOP | 4 |
| Beta recruitment: 150 households within the radius | 8 |
| Support workflow across WhatsApp / email / in-app | 3 |
| Legal and POPIA groundwork for a live consumer service | 5 |
| Task | Effort (days) |
|---|---|
| Test automation: API, engine, reconciliation | 8 |
| Internal alpha with two pickers | 6 |
| Security review and POPIA data-handling check | 4 |
| Performance, UAT, and defect-fix buffer | 8 |
| Beta launch and support standby | 4 |
Data and engine first, then the apps against a real backend, then integrations, then people. Durations are indicative; the point is the dependency order.
Once the beta proves demand and produces the retailer proof pack, the build order is deliberate. Each phase unlocks the next.
| Order | Phase | What it adds | Unlocks |
|---|---|---|---|
| 1 | Foundation | Harden the MVP; resolve the employed-vs-gig picker model; decide Group Buy in or out and build its data model if in. | A stable base and a settled operating model. |
| 2 | Fulfillment | Retailer API integrations (catalog, availability, order submission) and retailer-picker infrastructure in priority areas. | Scale beyond manual picking and scraping. |
| 3 | Intelligence | Live cross-retailer optimization, AI basket builder, predictive reorder, and budget or quality modes. | The household grocery assistant, not just a store. |
| 4 | Network effects | National footprint, loyalty linking, and social mechanics such as Group Buy at scale. | A defensible moat and category leadership. |
| Risk | Impact | Mitigation |
|---|---|---|
| Stale prices | Trust | Freshness SLA, in-app confidence flag, picker shelf price as source of truth |
| Reconciliation errors | Margin | Capped auto-adjust plus a manual review queue; a tight prepaid-card SOP |
| Picker supply | Fulfillment | A small trained core for the beta; batch same-store jobs; keep a backup picker |
| Out-of-stock | Experience | Substitution flow with customer preferences; refund-skip fallback |
| Retailer pushback on scraping | Legal / access | Modest footprint; position the proof pack toward sanctioned Phase 2 APIs |
| Delivery reliability | Experience | A single vetted courier for the beta; handoff-code accountability |