5 STATELESS SCENARIOS — Each is a complete, independent call
SCENARIO 1: DISCOVER (First Page Load — UTMs Only)
POST /api/v1/guidance/context
{ utm_campaign: "taxes", utm_source: "google" } ← No user data, no calc results
→ entry_point_resolution: { method: "UTM", id: "tax-efficiency" }
→ branching_questions: ["What is your annual income?"]
SCENARIO 2: HOOK (Age + Income Only)
POST /api/v1/guidance/context
{ entry_point_id: "tax-efficiency",
user_profile: { current_age: 45, income: 200000 } } ← No calc results
→ entry_point_resolution: { method: "EXPLICIT", id: "tax-efficiency" }
→ branching_questions: ["What state do you live in?"]
SCENARIO 3: ENGAGE (With State + Calc Results)
POST /api/v1/calculate/simulate → calc_results (charts update immediately)
POST /api/v1/guidance/context
{ entry_point_id: "tax-efficiency", user_profile: { ...+state_code },
calculation_results: calc_results } ← Optional: enables $ insights
→ headline: "You're on track — Evergreen adds $350K"
SCENARIO 4: QUALIFY (Full Account Breakdown)
Same as ENGAGE but with tier 3 calc_results (taxable accounts)
→ differentiators: [{ id: "tax_loss_harvesting", status: "REVEALED", value: 47000 }]
SCENARIO 5: INTERACTIVE SLIDER (Calculate → Guidance Debounce)
USER MOVES SLIDER (every movement)
│
▼
┌───────────────────────────────────────────────────────────────────────────────┐
│ STEP 1: CALCULATE API (called immediately, <20ms) │
│ POST /api/v1/calculate/simulate │
│ INPUT: user_profile, portfolio, config │
│ OUTPUT: PURE NUMBERS — success_probability, wealth, chart_data │
│ ➔ Charts update IMMEDIATELY │
└───────────────────────────────────────────────────────────────────────────────┘
│
▼ (debounce 300ms - wait for user to stop)
┌───────────────────────────────────────────────────────────────────────────────┐
│ STEP 2: GUIDANCE API (called on commit, ~50ms) │
│ POST /api/v1/guidance/context │
│ INPUT: entry_point_id, user_profile, calculation_results (OPTIONAL) │
│ OUTPUT: HAND-CRAFTED INTERPRETATION │
│ • entry_point_resolution: { method: "EXPLICIT", id: "tax-efficiency" } │
│ • headline: "You're on track — Evergreen adds $350K" │
│ • differentiators: [ { value: 47000, insight: "..." } ] │
│ • branching_questions: [...] │
│ ➔ Text/insights update after user stops │
└───────────────────────────────────────────────────────────────────────────────┘
📊 Combined Results (Calculate + Guidance)
HOOK
ENGAGE
QUALIFY
PLAN
READY
From Calculate API /api/v1/calculate/simulate
Wealth at Retirement (P50)
—
Evergreen Tax Alpha Benefit
—
Differentiators (Guidance tells status, Calculate provides numbers)
🔒 UNLOCKABLE
Add taxable account balance to reveal TLH insights
🧭 From Guidance API /api/v1/guidance
Data Fidelity Tier
TIER_1
Next Questions to Ask
What state do you live in?
Unlocks: state-specific tax strategies
Progression
Progress to Next Stage
Next Stage: ENGAGE
Data Needed: state_code
Progress: 20%
💻 Interactive Slider Code (The Debounce Pattern)
// INTERACTIVE SLIDER PATTERN: Calculate → Guidance
class InteractiveCalculator {
constructor() {
this.debounceTimer = null;
this.lastCalculateResults = null; // Store for passing to Guidance
}
// Called on EVERY slider movement (instant)
async onSliderMove(value) {
// STEP 1: Call Calculate API immediately (<20ms)
const calculateRequest = {
tier: this.tier,
user_profile: {
current_age: this.age,
income: this.income,
monthly_contribution: value // Slider value
},
portfolio: this.portfolio
};
const response = await fetch('/api/v1/calculate/simulate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(calculateRequest)
});
this.lastCalculateResults = await response.json();
// STEP 2: Update charts IMMEDIATELY
this.updateCharts(this.lastCalculateResults);
this.updateSuccessProbability(this.lastCalculateResults.results.success_probability);
// STEP 3: Debounce the Guidance API call (300ms)
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => this.onSliderCommit(), 300);
}
// Called when user STOPS moving (debounced)
async onSliderCommit() {
// STEP 4: Call Guidance API — entry_point_id required, calc_results OPTIONAL
const guidanceRequest = {
entry_point_id: this.entryPointId, // from URL param or UTM resolution
user_profile: this.userProfile,
portfolio: this.portfolio,
calculation_results: this.lastCalculateResults // OPTIONAL — enables $ insights
};
const response = await fetch('/api/v1/guidance/context', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(guidanceRequest)
});
const guidance = await response.json();
// STEP 5: Update text/insights
this.updateHeadline(guidance.headline); // "You're on track — Evergreen adds $350K"
this.updateDifferentiators(guidance.journey_context.differentiators);
}
}
// The result:
// • User drags slider → Charts animate smoothly (Calculate API)
// • User stops → Text updates with insights (Guidance API)
Entry point resolution priority: 1. Explicit
entry_point_id(deeplink) → method: EXPLICIT2.
utm_campaign→ mapped via campaigns_config → method: UTM3. Behavioral signals → method: BEHAVIORAL
4. Default entry point → method: DEFAULT
calculation_resultsis Optional — Guidance works at any stage:• First page load: UTMs only → DISCOVER stage, first branching question
• With user data: HOOK/ENGAGE stage, unlockable differentiators
• With calc results: dollar-value insights, REVEALED differentiators
Why this pattern?
• Sliders feel instant — Calculate API updates charts on every movement
• Text is meaningful — Guidance API interprets numbers using hand-crafted rules
• No AI hallucination — All text comes from entry_points.json config
• Stateless — each Guidance call is self-contained