Calculator-to-Purchase System Documentation¶
Status: Production Ready
Version: 2.6
Last Updated: February 2025
Owner: Protocol Raw
1. Overview¶
What This System Does¶
The calculator-to-purchase system converts personalised feeding recommendations into completed subscription orders. Customers use the feeding calculator, arrive at the product page with their verified plan, complete a 4-step verification modal, and proceed to checkout. If they don't complete checkout, the abandonment recovery system automatically follows up with personalised emails.
Core Flow:
Calculator → Product Page (with plan) → Modal (4 steps) → Checkout → Purchase → Webhook
↓
[No purchase?]
↓
Abandonment Recovery Emails
This architecture handles any volume â€â€ from first customer to 100,000+ â€â€ without modification.
Calculator Entry Points¶
The calculator is available in two locations:
| Location | URL | Use Case |
|---|---|---|
| Homepage | / (embedded section) |
Discovery â€â€ visitors browsing the site encounter the calculator naturally |
| Dedicated Page | /pages/calculator |
Intent â€â€ visitors specifically seeking to calculate their dog's needs |
Both entry points use identical calculation logic and lead to the same product page flow.
System Objectives¶
- Convert precision into purchase â€â€ customers who calculate are higher intent
- Capture clean retention data â€â€ verified plans enable accurate cohort analysis
- Prevent discount fraud â€â€ one discount per household maintains data integrity
- Recover abandoned sessions â€â€ personalised follow-up for non-converters
- Scale without reengineering â€â€ architecture supports growth from day one
2. Current Launch Configuration¶
These settings are launch-day choices, not architectural constraints. Each can be adjusted without system changes.
Geographic Scope¶
Current: London postcodes only (E, EC, N, NW, SE, SW, W, WC, BR, CR, DA, EN, HA, IG, KT, RM, SM, TW, UB)
To expand: Update postcode prefix list in edge function, or remove validation entirely for UK-wide.
Why London first: Ensures operational excellence and concentrated logistics before scaling. Framed to customers as "We're starting in London" (not "London only").
Calculator Discount¶
Current: £20 off first box for all calculator completions
To adjust: Modify discount amount, make conditional (referral-only, first-time only), or remove entirely.
Why £20: Incentivises calculator usage, captures email/address for remarketing, rewards precision over impulse buying.
Fraud Prevention¶
Current: Block discounts if address OR email already used
To evolve: Add phone verification, fuzzy address matching, or payment hold if fraud rates increase.
Why address-based: Simple, effective for launch. One discount per household prevents gaming while allowing legitimate flatmate purchases at full price.
3. Calculator Architecture¶
Two Calculator Variants¶
We maintain two versions of the calculator widget to avoid content redundancy:
Homepage Calculator (feed-calculator.liquid)¶
Full, self-contained version for embedding on the homepage.
Includes: - Header: "How Much Should I Feed My Dog?" - Subtitle explaining the methodology - 15kg example anchor (showing sample calculation) - Full calculator inputs (life stage, weight, activity, condition) - Results display with pricing - Treat adjustment selector (single dog only)
Use case: Homepage visitors need context before calculating. The widget must stand alone.
Dedicated Page Calculator (feed-calculator-minimal.liquid)¶
Stripped version for the dedicated calculator page.
Excludes: - Header (handled by hero section) - Subtitle (handled by hero section) - 15kg example anchor (handled by hero section)
Includes: - Calculator inputs only (life stage, weight, activity, condition) - Results display with pricing - Treat adjustment selector (single dog only)
Use case: The dedicated page has a hero section that provides context. Repeating it in the calculator widget would be redundant.
URL Parameters Generated¶
Both calculator variants generate identical URL parameters when redirecting to the product page:
| Parameter | Example | Purpose |
|---|---|---|
token |
27d1ce68-76d6-... |
Session identifier for discount lookup |
box |
12kg |
Recommended box size |
pet_count |
1 |
Number of dogs |
household_total |
33 |
Monthly cost estimate |
household_grams |
275 |
Total daily grams |
weeks |
4 |
Delivery frequency |
dog_name |
Kai |
Dog's name (single-dog only, for personalisation) |
treat_level |
some |
Treat adjustment level (none/some/lots) |
pets |
[{name,weight,...}] |
JSON array of pet details |
Dog Name Logic:
- Single dog with custom name → dog_name=Kai
- Single dog with default name ("Dog 1") → dog_name omitted
- Multiple dogs → dog_name omitted (uses generic "your pack" messaging)
Why Two Versions?¶
HOMEPAGE:
â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â
â  [Other homepage content above] â 
â  â 
â  â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â â 
â  â  How Much Should I Feed My Dog? â  â  â†Â Widget includes intro
â  â  [Explanation + 15kg example] â  â 
â  â  [Calculator inputs] â  â 
â  â  [Results] â  â 
â  ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘ â 
â  â 
â  [Other homepage content below] â 
ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘
DEDICATED PAGE:
â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â
â  â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â â 
â  â  HERO SECTION â  â  â†Â Hero handles intro
â  â  "Feed the individual..." â  â 
â  â  [Explanation + 15kg example] â  â 
â  ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘ â 
â  â 
â  â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â â 
â  â  CALCULATOR (MINIMAL) â  â  â†Â Widget is just inputs
â  â  [Calculator inputs] â  â 
â  â  [Results] â  â 
â  ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘ â 
â  â 
â  â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â â 
â  â  CONTENT SECTION â  â 
â  â  [Energy density education] â  â 
â  â  [Life stages] â  â 
â  â  [FAQs] â  â 
â  ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘ â 
ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘
4. Treat Adjustment Feature¶
Overview¶
After calculating their dog's plan, single-dog customers can adjust the feeding recommendation to account for treats. This reduces the daily food portion to leave caloric "room" for treats without overfeeding.
Availability: Single adult/senior dogs only (not puppies, not multipet)
Treat Levels¶
| Level | Label | Multiplier | Use Case |
|---|---|---|---|
none |
No treats most days | 1.0 (100%) | Rarely gives treats or scraps |
some |
Some treats | 0.9 (90%) | Most dogs â€â€ occasional training treats, small snacks |
lots |
Lots of treats | 0.8 (80%) | Daily chews or frequent snacks |
What Updates When Treat Level Changes¶
When a user selects a different treat level, the following recalculate in real-time:
- Daily grams â€â€ reduces proportionally to treat multiplier
- Daily calories â€â€ reduces proportionally to treat multiplier
- Human-readable pouch fraction â€â€ updates based on new grams
- Days supply â€â€ increases (same box lasts longer with less daily usage)
- Weekly price â€â€ decreases (box lasts more weeks)
- First delivery weekly price â€â€ decreases proportionally
- Alternative box recommendation â€â€ re-evaluated for validity
- CTA links â€â€ updated with new
treat_levelandhousehold_gramsparameters
Alternative Box Re-evaluation¶
When treats reduce the daily grams significantly, the originally recommended alternative box may no longer make sense. The system re-evaluates:
Scenario: 12kg box recommended, 16kg shown as "better value" alternative.
- With "No treats": 16kg lasts ~5 weeks → valid alternative
- With "Lots of treats": 16kg would last ~7.5 weeks → exceeds 6-week max
- Action: Alternative flips from 16kg "value" upsell to 8kg "convenience" option
Logic:
const altMaxDays = 6 * 7 + 3; // 45 days (6 weeks + buffer)
if (altAdjustedDaysSupply > altMaxDays && basePlanData.alternativeType === 'value') {
// Flip to smaller box as convenience option
showSmallerBoxAsConvenience();
} else {
// Keep original alternative, update pricing
updateAlternativePricing();
}
Technical Implementation¶
State Storage:
let basePlanData = null; // Stores unadjusted calculation
let currentTreatLevel = 'none';
const TREAT_MULTIPLIERS = { 'none': 1.0, 'some': 0.90, 'lots': 0.80 };
Recalculation Function: recalculateWithTreats(treatLevel)
- Retrieves base MER from basePlanData.petData[0].base_mer
- Applies multiplier to get adjusted MER
- Recalculates grams: Math.round((adjustedMER / FORMULA_ME) / 25) * 25
- Updates all display elements and CTA links
UI/UX Design¶
Component: .treat-adjustment-section
Layout:
- Title: "Do your dog's daily calories include treats?"
- Subtitle: "This leaves room in the calorie budget. You can change it anytime."
- 3 radio options in horizontal grid (stacks on small phones)
- Delta text shows adjustment when some or lots selected
Feedback: - Selection highlights immediately with checkmark - Values animate smoothly (400ms duration) - Delta text fades in: "Adjusted: 10% less food to leave room for treats"
5. Dedicated Calculator Page¶
Page Structure¶
URL: /pages/calculator
Template: page.calculator.json
The page consists of three sections loaded via JSON template:
{
"sections": {
"calculator-hero": {
"type": "calculator-page-hero"
},
"feed-calculator": {
"type": "feed-calculator-minimal"
},
"calculator-content": {
"type": "calculator-page-content"
}
},
"order": [
"calculator-hero",
"feed-calculator",
"calculator-content"
]
}
Section 1: Hero (calculator-page-hero.liquid)¶
Purpose: Position the calculator, set expectations, show example pricing.
Content: - Headline: "Feed the individual, not the average." - Paragraph 1: "A 15kg dog sleeping on a sofa needs very different fuel than a 15kg dog running in a field." - Callout: "Generic body-weight percentages ignore this." - Paragraph 2: MER standard explanation - 15kg example anchor (4 cards: daily feed, box size, weekly cost, first box savings) - CTA: "Calculate Your Dog's Plan ↓"
Section 2: Calculator (feed-calculator-minimal.liquid)¶
Purpose: Collect dog details and display personalised results.
Content: - Life stage selector (Puppy / Adult / Senior) - Multipet option (up to 3 dogs) - Weight slider with manual input - Neutered/intact toggle - Activity level selector - Body condition selector (with SVG illustrations) - Calculate button - Results display (daily grams, calories, pouch equivalent) - Treat adjustment selector (single dog only) - Pricing breakdown - CTA to product page with token
Section 3: Content (calculator-page-content.liquid)¶
Purpose: Educate on energy density, reinforce single-SKU positioning, answer FAQs.
Energy Density Section (Split Layout): - Left: "Why We Count Calories, Not Just Grams" + explanation - Right: Visual comparison (500g ≠ 500g â€â€ chicken vs beef calorie difference) - Tagline: "We don't just weigh the food. We engineer the fuel."
Life Stages Section: - Header: "Every Life Stage. One Formula." - 3 cards: Puppies (growth), Adults (maintenance), Seniors (precision)
FAQ Section: - Expandable accordion format - Why different from other calculators, weighing food, multiple dogs, recalculation frequency, all life stages suitability
6. User Flow¶
Product Page (Entry from Calculator)¶
When customers arrive from calculator with URL params (?token=xxx&box=12kg&daily_grams=375&weeks=4&dog_name=Kai):
Calculator Banner Appears: - Title: "Your Verified Plan" - Shows: Box size, daily feeding, delivery frequency - Footer: "Complete nutrition, verified safe, £20 off your first box"
Custom Pricing: - Discounted price shown (based on current discount config) - Regular price crossed out - Message: "Calculator discount applied"
Custom Button: - "Claim Your £20 Off" (replaces default add-to-cart)
Modal: Step 1 (Email)¶
- Title: "Lock in your plan"
- Subtitle: "We'll send your transition guide after your first order"
- Input: Email field (validated)
- Button: "Continue"
- Progress: â—Â ○ ○
Modal: Step 2 (Postcode)¶
- Title: "Delivery postcode"
- Subtitle: "We're starting in London to ensure quality at launch"
- Input: Postcode (auto-uppercase)
- Button: "Continue"
- Progress: â—Â â—Â ○
Validation: UK format + geographic scope check (currently London prefixes)
Modal: Step 3 (Address)¶
- Title: "Delivery address"
- Subtitle: "One verified plan per address"
- Input: Address field with autocomplete (getAddress.io API)
- Button: "Verify Plan"
- Progress: â—Â â—Â â—Â
Success Screen (New Customer)¶
- Checkmark animation
- Title: "Plan Secured"
- Discount code displayed
- Plan details (box size, daily feeding, delivery frequency, pricing)
- Button: "Continue to Secure Checkout"
- Note: "Your batch will be independently tested before shipping"
Success Screen (Existing Customer)¶
- Title: "Welcome Back!"
- No discount code (full price checkout)
- Plan details
- Button: "Continue to Secure Checkout"
7. Technical Architecture¶
System Components¶
â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â
â  1. CALCULATOR (Two Entry Points) â 
â  â 
â  HOMEPAGE DEDICATED PAGE â 
â  / /pages/calculator â 
â  feed-calculator.liquid calculator-page-hero.liquid â 
â  (full version) feed-calculator-minimal.liquid â 
â  calculator-page-content.liquid â 
â  â 
â  Both generate: token, box size, daily_grams, weeks, dog_name â 
â  Both redirect to: /products/product?token=xxx&... â 
ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘
â 
▼
â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â
â  2. PRODUCT PAGE â 
â  - Reads URL params (including dog_name) â 
â  - Shows calculator banner + custom pricing â 
â  - "Claim Your £20 Off" button triggers modal â 
ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘
â 
▼
â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â
â  3. MODAL (3-STEP FLOW) â 
â  Step 1: Email â 
â  Step 2: Postcode (geographic validation) â 
â  Step 3: Address (autocomplete + fraud check) â 
â  → Sends dog_name to Edge Function â 
ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘
â 
▼
â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â
â  4. EDGE FUNCTION: calculator-apply-discount â 
â  - Validates geographic scope â 
â  - Checks fraud (email + address) â 
â  - Generates Shopify discount code â 
â  - Saves to calculator_discounts (including dog_name) â 
ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘
â 
â‌â â â â â â â â â â â â â â â â‴â â â â â â â â â â â â â â â ââ€Â
▼ ▼
â‌â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â
â  5a. SUCCESS → CHECKOUT â  â  5b. NO PURCHASE (ABANDONMENT) â 
â  - Shows plan + code â  â  - Record sits with used=false â 
â  - Adds to cart â  â  - Cron job detects after 1hr â 
â  - Redirects to checkoutâ  â  - Sends personalised email â 
ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â‘ ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘
â  â 
▼ ▼
â‌â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â â‌â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â ââ€Â
â  6a. WEBHOOK: Order â  â  6b. RECOVERY EMAIL â 
â  - Marks used=true â  â  - "Kai's plan is waiting" â 
â  - Records used_at â  â  - Links back to checkout â 
â  - Triggers downstream â  â  - Second email at 48hrs â 
ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â‘ ââ€â€â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â â‘
Database Schema¶
Table: raw_ops.calculator_discounts
| Column | Type | Purpose |
|---|---|---|
id |
uuid | Primary key |
token |
text | Calculator session token |
email |
text | Normalised lowercase |
postcode |
text | Uppercase, no spaces |
address |
text | Normalised for matching |
box_size |
text | '8kg', '12kg', '16kg' |
daily_grams |
integer | Calculated daily portion |
delivery_weeks |
integer | Subscription frequency |
discount_code |
text | 'CALC20-xxxxx' format |
regular_price |
integer | Pre-discount price |
discounted_price |
integer | Post-discount price |
dog_name |
text | Dog's name for personalisation (NULL for multipet) |
pet_count |
integer | Number of dogs in household |
household_daily_grams |
integer | Total daily grams for all dogs |
household_total_gbp |
integer | Monthly cost for household |
used |
boolean | Flips true on purchase |
used_at |
timestamp | When order completed |
abandonment_sent_at |
timestamp | When recovery email sent (NULL = not sent) |
created_at |
timestamp | When discount generated |
expires_at |
timestamp | Discount expiry |
fraud_flag |
boolean | Flagged for fraud |
fraud_reason |
text | Why flagged |
Indexes:
- idx_calculator_discounts_email â€â€ fraud checking
- idx_calculator_discounts_address â€â€ fraud checking
- idx_calculator_discounts_postcode â€â€ geographic analytics
- idx_calculator_discounts_token â€â€ token lookup for recovery URLs
- idx_calculator_discounts_abandonment_queue â€â€ abandonment recovery queries
Fraud Prevention Logic¶
1. Email already purchased (used = true)?
→ Return "Welcome back" flow, no discount
2. Email has unused discount (used = false)?
→ Return existing discount code
3. Address already in system?
→ Block: "This delivery address has already been used"
4. All checks pass?
→ Generate new discount, save to database
Edge Functions¶
| Function | Purpose |
|---|---|
calculator-generate-token-multipet |
Generates secure token for calculator results |
calculator-apply-discount |
Validates and generates discount codes, saves dog_name |
send-abandonment-events |
Sends recovery events to Customer.io (hourly cron) |
shopify-webhook-handler |
Marks discounts as used on purchase |
Database Functions¶
| Function | Purpose |
|---|---|
fn_get_abandonment_candidates() |
Returns sessions ready for recovery email |
fn_mark_abandonment_sent(p_ids) |
Marks sessions as emailed to prevent duplicates |
8. Abandonment Recovery System¶
Overview¶
Customers who complete the modal but don't purchase receive automated recovery emails with personalised messaging using their dog's name.
Timing Windows¶
| Trigger | Content | |
|---|---|---|
| Email 1 | 1-2 hours after modal completion | "Kai's plan is ready" |
| Email 2 | 48-50 hours after modal completion | "Still thinking about Kai's plan?" |
Personalisation Logic¶
| Scenario | dog_name value |
Email subject |
|---|---|---|
| Single dog, named | "Kai" | "Kai's plan is ready" |
| Single dog, unnamed | NULL | "Your plan is ready" |
| Multi-dog household | NULL | "Your pack's plan is ready" |
Customer.io Event Payload¶
When send-abandonment-events runs, it sends:
{
"name": "modal_1h",
"data": {
"dog_name": "Kai",
"has_dog_name": true,
"box_size": "12kg",
"daily_grams": 275,
"delivery_weeks": 4,
"discount_code": "CALC20-ABCD1234",
"regular_price": 109,
"discounted_price": 89,
"savings": 20,
"hours_since_created": 1
}
}
Cron Configuration¶
Schedule: Every hour (0 * * * *)
Function: send-abandonment-events
Batch Size: 100 records per run
Duplicate Prevention¶
fn_get_abandonment_candidates()filters byabandonment_sent_at IS NULL- After successful send,
fn_mark_abandonment_sent()sets timestamp - Time-window approach (1h, 48h) ensures each email sends once
9. Design System¶
Typography¶
- Headings: Montserrat (bold, tight tracking)
- Body: Inter (readable, professional)
- Monospace: For discount codes, postcodes
Colour System¶
- Primary action: #B85C3A (Burnt Sienna)
- Success/Trust: #2D5144 (Forest Green)
- Text primary: #2B2523 (Espresso)
- Text secondary: #6B6360 (Taupe)
- Background: #FEFDF7 (Cream), #EBE8E3 (Warm Linen)
Animation Principles¶
- Fast enough to feel responsive
- Slow enough to feel premium
- Scroll-triggered where appropriate
- Never blocks user action
Mobile Optimisation¶
- Sticky calculate button
- Single-column card stacking
- 44px minimum touch targets
- Full-width inputs
- Safe area inset support
10. Monitoring & Analytics¶
Key Metrics¶
| Metric | Target | Red Flag |
|---|---|---|
| Calculator → Order conversion | 65%+ | <50% |
| Abandonment recovery rate | 15-25% | <10% |
| Box 2 retention | 70%+ | <60% |
| Box 3 retention (of Box 2) | 80%+ | <70% |
| Fraud attempts | <5% | >10% |
Dashboard Views¶
- Calculator Funnel: Discounts → Orders → Box 2 → Box 3
- Abandonment Recovery: Sent → Recovered → Revenue impact
- Box Size Retention: Which size has best retention?
- Weekly Cohorts: Is retention improving over time?
- Geographic Distribution: Where are customers?
- Entry Point Analysis: Homepage vs dedicated page conversion
- Treat Level Distribution: What % select each treat level?
Quick Queries¶
-- Retention check
SELECT total_orders_placed, box_2_retention_pct
FROM raw_ops.v_phase_a_kpis;
-- Box size comparison
SELECT * FROM raw_ops.v_box_size_retention;
-- Weekly cohorts
SELECT * FROM raw_ops.v_weekly_cohorts
ORDER BY cohort_week DESC LIMIT 8;
-- Abandonment recovery stats
SELECT * FROM raw_ops.v_abandonment_stats;
-- Abandonment queue health
SELECT * FROM raw_ops.check_abandonment_queue_health();
11. Operations & Troubleshooting¶
Common Issues¶
"I already used this discount" → They started but didn't purchase. System returns their existing unused code. This is correct.
Flatmates can't both get discount → Intentional. Second person pays full price, or support manually issues code if justified.
"Address already used" but claims first time → Someone else at address used it. Check database. Manual code for legitimate multi-unit buildings.
Outer area postcode rejected → Not in current geographic scope. Manually issue if legitimate delivery area, consider expanding prefix list.
Recovery emails not sending → Check cron job status, edge function logs, Customer.io event activity. See troubleshooting section below.
Abandonment Recovery Troubleshooting¶
Check 1: Is cron job running?
Check 2: Are there candidates to process?
Check 3: Queue health status
Check 4: Edge function logs
→ Supabase Dashboard → Edge Functions → send-abandonment-events → Logs
Check 5: Customer.io receiving events?
→ Customer.io → Activity Logs → Search for modal_1h or modal_48h
Manual Discount Codes¶
When to issue: - Legitimate edge case outside current scope - Customer support exception - System error recovery
How to issue:
1. Shopify → Discounts
2. Create: SUPPORT-[name]-[date]
3. Set: £20 off, subscription only, single use
4. Log reason
5. Send to customer
Never use CALC20- prefix manually (reserved for system).
12. File Locations¶
Shopify Templates¶
| File | Purpose |
|---|---|
templates/page.calculator.json |
Dedicated calculator page structure |
Shopify Sections¶
| File | Purpose |
|---|---|
sections/feed-calculator.liquid |
Full calculator (homepage) |
sections/feed-calculator-minimal.liquid |
Stripped calculator (dedicated page) |
sections/calculator-page-hero.liquid |
Dedicated page hero |
sections/calculator-page-content.liquid |
Education + FAQs |
sections/main-product.liquid |
Product page |
Shopify Snippets¶
| File | Purpose |
|---|---|
snippets/calculator-checkout-modal.liquid |
4-step verification modal |
Supabase Edge Functions¶
| Function | Purpose |
|---|---|
calculator-generate-token-multipet |
Token generation |
calculator-apply-discount |
Discount validation + creation |
send-abandonment-events |
Abandonment recovery (hourly cron) |
shopify-webhook-handler |
Order completion processing |
Supabase Database¶
| Resource | Purpose |
|---|---|
raw_ops.calculator_discounts |
Core data table |
raw_ops.fn_get_abandonment_candidates() |
Find sessions for recovery |
raw_ops.fn_mark_abandonment_sent() |
Mark as emailed |
raw_ops.v_abandonment_stats |
Recovery metrics view |
raw_ops.check_abandonment_queue_health() |
Monitoring function |
Cron Jobs¶
| Job | Schedule | Purpose |
|---|---|---|
send-abandonment-events-hourly |
0 * * * * |
Trigger recovery emails |
Customer.io¶
| Resource | Purpose |
|---|---|
Event: modal_1h |
1-hour abandonment trigger |
Event: modal_48h |
48-hour abandonment trigger |
| Campaign: "Calculator Recovery" | Email sequence |
Metabase¶
| Resource | Purpose |
|---|---|
| Dashboard: "Retention Analysis" | Core KPIs |
| Dashboard: "Abandonment Recovery" | Recovery metrics |
| Collection: Protocol Raw Analytics | All queries |
Version History: - v2.2 (Jan 2025): Restored missing sections (User Flow, Technical Architecture, full Abandonment Recovery) from v2.0; merged with v2.1 treat adjustment feature - v2.1 (Jan 2025): Added treat adjustment feature with dynamic alternative box re-evaluation - v2.0 (Nov 2024): Added dog name capture, abandonment recovery system - v1.0 (Nov 2024): Initial documentation
Owner: Protocol Raw Operations
Version 2.6 Changes — Previous Diet Capture¶
Overview¶
Version 2.6 adds a 4th step to the checkout modal to capture what the customer's dog currently eats. This enables tracking of "% new to raw" — a key metric for validating the thesis that Protocol Raw converts raw-curious premium buyers rather than existing raw believers.
Modal Changes¶
The modal is now 4 steps (was 3):
| Step | Title | Purpose |
|---|---|---|
| 1 | Capture email, check existing customer | |
| 2 | Postcode | Geographic validation |
| 3 | Address | Fraud prevention |
| 4 | Current Diet (NEW) | Capture previous feeding history |
Step 4 UI¶
- Title: "One last thing"
- Subtitle: "This helps us tailor your transition guidance"
- Question: "What does [dog_name] currently eat?"
- Options:
- Kibble or dry food →
kibble - Fresh / cooked food →
fresh_cooked - Raw food →
raw - Mixed / not sure →
mixed_unsure - Button: "Verify Plan"
Database Schema Changes¶
Table: calculator_discounts
ALTER TABLE raw_ops.calculator_discounts
ADD COLUMN previous_diet TEXT;
-- Values: 'kibble', 'fresh_cooked', 'raw', 'mixed_unsure'
Table: customers
ALTER TABLE raw_ops.customers
ADD COLUMN acquisition_diet TEXT;
-- Immutable after first order. Copied from calculator_discounts.previous_diet
Data Flow¶
Modal Step 4 (user selects diet)
↓
calculator-apply-discount Edge Function
↓
calculator_discounts.previous_diet (stored)
↓
[Customer completes order]
↓
shopify-webhook-handler Edge Function
↓
customers.acquisition_diet (copied, immutable)
+
Customer.io identify (profile attributes set)
Customer.io Integration¶
On first order, two profile attributes are set:
| Attribute | Type | Description |
|---|---|---|
acquisition_diet |
string | Raw value: kibble, fresh_cooked, raw, mixed_unsure |
is_new_to_raw |
boolean | true if kibble or fresh_cooked |
Segmentation Value¶
- % New to Raw = kibble + fresh_cooked customers
- % Converting from competitor = raw customers
- % DIY converting = (if we add home-prepared option later)
Analytics Queries¶
Acquisition composition:
SELECT
acquisition_diet,
COUNT(*) as customers,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) as pct
FROM raw_ops.customers
WHERE acquisition_diet IS NOT NULL
GROUP BY acquisition_diet
ORDER BY customers DESC;
% New to raw:
SELECT
ROUND(100.0 * COUNT(*) FILTER (WHERE acquisition_diet IN ('kibble', 'fresh_cooked')) / COUNT(*), 1) as pct_new_to_raw
FROM raw_ops.customers
WHERE acquisition_diet IS NOT NULL;
Files Changed¶
| File | Change |
|---|---|
snippets/calculator-checkout-modal.liquid |
Added Step 4 HTML, CSS, JS |
calculator-apply-discount/index.ts |
Accept and store previous_diet |
shopify-webhook-handler/index.ts |
Copy to acquisition_diet, sync to Customer.io |
| Database | Added columns to calculator_discounts and customers |
Version History: - v2.6 (Feb 2025): Added Step 4 (current diet) for % new-to-raw tracking - v2.2 (Jan 2025): Restored missing sections from v2.0; merged with v2.1 treat adjustment feature - v2.1 (Jan 2025): Added treat adjustment feature with dynamic alternative box re-evaluation - v2.0 (Nov 2024): Added dog name capture, abandonment recovery system - v1.0 (Nov 2024): Initial documentation