Files
Sauci/docs/question-selection.md
T
2025-12-29 23:10:12 +00:00

6.3 KiB

Question Selection Algorithm

This document explains how questions are selected and ordered for users in the swipe interface.

Overview

Question selection happens in two stages:

  1. Database Layer - get_recommended_questions() PostgreSQL function filters available questions
  2. Client Layer - swipe.tsx applies additional filtering and sorting with bias

Stage 1: Database Filtering

The get_recommended_questions(target_pack_id) function (apps/supabase/migrations/20251227172500_role_specific_questions.sql) returns questions the user hasn't answered yet.

Pack Selection Logic

IF target_pack_id provided:
    → Use only that specific pack
ELSE IF couple has any couple_packs entries:
    → Use only packs where enabled = true
ELSE (new couple, no preferences set):
    → Use all public packs (is_public = true)

Question Filtering

For each question in active packs:

  • Excluded: Questions the current user has already answered
  • Included: All unanswered questions from active packs

Returned Fields

Field Description
id Question UUID
text Display text (see two-part logic below)
partner_text Original partner text (for reference)
is_two_part true if question has different text for second responder
pack_id Parent pack UUID
intensity 1-5 rating
partner_answered true if partner has already responded

Two-Part Questions

Some questions have role-specific text (partner_text column). The function handles this:

IF partner_text EXISTS AND partner has answered:
    → Return partner_text as display text
ELSE:
    → Return original text

This enables questions like:

  • First person sees: "Would you like your partner to wake you up with oral?"
  • Second person sees: "Would you wake your partner up with oral?"

Stage 2: Client Sorting & Filtering

Located in apps/mobile/app/(app)/swipe.tsx, lines 57-95.

Skip Filtering

Before sorting, questions are filtered against locally-skipped questions:

const filtered = data.filter(q => !skippedIds.has(q.id));

Skip behavior (src/lib/skippedQuestions.ts):

  • Swipe down = skip question
  • Skipped questions hidden for 24 hours
  • Stored in SecureStore (native) or localStorage (web)
  • No server-side record - purely client-side convenience

Sorting Algorithm with Bias

Questions are sorted by a computed score:

const sorted = filtered.sort((a, b) => {
    let scoreA = Math.random();  // Base: 0.0 - 1.0
    let scoreB = Math.random();

    // Apply bias bonuses
    if (a.partner_answered && a.is_two_part) scoreA += 1.5;
    else if (a.partner_answered) scoreA += 0.7;
    else if (a.is_two_part) scoreA += 0.4;

    // Same for scoreB...

    return scoreB - scoreA;  // Descending order
});

Priority Tiers

Priority Condition Score Range Rationale
1st partner_answered + is_two_part 1.5 - 2.5 Completes two-part question, immediate match potential
2nd partner_answered only 0.7 - 1.7 High match potential, partner waiting
3rd is_two_part only 0.4 - 1.4 Initiates two-part sequence
4th Neither 0.0 - 1.0 Standard questions, fully randomized

Why This Bias?

  1. Engagement: Questions partner already answered can immediately create matches, providing instant gratification

  2. Two-part completion: When partner answered a two-part question first, completing it quickly maintains context and creates better matches

  3. Two-part initiation: Slightly prioritizes two-part questions to create more engaging interactions

  4. Randomization within tiers: The random base prevents predictable ordering while maintaining priority bands

Visual Flow

┌─────────────────────────────────────────────────────────────┐
│                    get_recommended_questions()               │
│  ┌─────────────┐    ┌──────────────┐    ┌───────────────┐  │
│  │ Pack Filter │ → │ User Answered │ → │ Two-Part Text │   │
│  │             │    │   Exclusion   │    │  Resolution   │   │
│  └─────────────┘    └──────────────┘    └───────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                     Client (swipe.tsx)                       │
│  ┌─────────────┐    ┌──────────────┐    ┌───────────────┐  │
│  │ Skip Filter │ → │ Bias Scoring │ → │     Sort      │   │
│  │  (24 hour)  │    │              │    │  (Descending) │   │
│  └─────────────┘    └──────────────┘    └───────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              ↓
                    Questions shown to user

Edge Cases

Scenario Behavior
No packs enabled Shows "No packs enabled" prompt
All questions answered Shows "All caught up!" with refresh button
No partner paired Shows pairing prompt
Pack disabled mid-session Client filters out questions from disabled packs

Configuration

Setting Value Location
Skip duration 24 hours src/lib/skippedQuestions.ts:6
Two-part + answered bias +1.5 swipe.tsx:78
Answered-only bias +0.7 swipe.tsx:79
Two-part-only bias +0.4 swipe.tsx:80