mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Update
This commit is contained in:
1 parent
358c2be0ee
commit
b10eae1bf3
26 files changed
+2509
-53
No files matched your search
@@ -0,0 +1,154 @@
|
||||
# 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:
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```typescript
|
||||
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` |
|
||||
Reference in new issue
Block a user