# Concept 4: Cart Pattern Recognition — Concrete Detection Rules

**Status:** Canonical spec v1  
**Date:** 2026-05-20  
**Purpose:** Define implementable detection rules for all five signal types. Replaces poetic descriptions with algorithms.

---

## The Problem

The cart system describes five pattern types poetically ("Hearts — you both have this"), but detection rules are absent. Without concrete rules, the pattern recognition engine is unimplementable. Every poetic description below is translated into a concrete algorithm.

---

## Signal Types and Detection Rules

### ❤️ Hearts — Exact Cart Overlap

**Definition:** Two users share the exact same cart item.

**Algorithm:**

```
Two items generate a Hearts signal when:
1. Same `name` (normalized — lowercase, stripped of possessive suffixes)
2. Same `category` (food | activities | habitat)
3. Same `type` sub-category (restaurant | food-type | cooking-habit | etc.)
```

**Normalization rules:**
- `"Ramen Bar X"` matches `"Ramen Bar X"` — exact
- `"Ramen Bar X"` does NOT match `"Ramen Bar Y"` — different restaurant
- `"Ramen"` (food-type) does NOT match `"Ramen Bar X"` (restaurant) — different type even if same category
- Names stripped of `'s`, `'re`, `'ve`, trailing punctuation before comparison

**Anti-gaming:**
- Item must have been in sender's cart for 3+ days before matching
- Matching must occur between two different user accounts (no self-match)
- Item removed from cart and re-added within 90 days does not generate Hearts signal

**Edge cases:**
- `"Sushi"` (food-type) from User A matches `"Sushi"` (food-type) from User B = Heart
- `"Nobu"` (restaurant) vs `"Sushi at Nobu"` (restaurant) = no match (different specificity)

---

### ⭐ Stars — Category Congruence

**Definition:** Two users have items in the same type sub-category, even if the specific items differ.

**Example:** User A has rock climbing. User B has hiking. Both are `activity.outdoor.adventure` type. Stars signal fires.

**Algorithm:**

```
For each cart category (Food, Activities, Habitat):
  1. Extract all item type sub-categories from User A's cart
  2. Extract all item type sub-categories from User B's cart
  3. Find intersection of type sub-categories
  
If intersection non-empty → Stars signal fires
Stars weight = 1 per matched type sub-category
```

**Type taxonomy (partial — full taxonomy in implementation):**

```
FOOD:
  restaurant.{cuisine}
  food-type.{specific}
  cooking-habit.{type}
  dietary-pattern.{type}
  social-dining.{type}
  drink-habit.{type}

ACTIVITIES:
  sport.{specific}
  outdoor.{subtype}        ← outdoor.adventure, outdoor.nature, outdoor.water
  creative.{subtype}       ← creative.arts, creative.music, creative.craft
  entertainment.{subtype}  ← entertainment.live, entertainment.screening
  social.{type}
  intellectual.{type}
  relaxation.{type}

HABITAT:
  plants.{type}
  pets.{type}
  living-situation.{type}
  aesthetic.{type}
  cleanliness.{type}
  environmental.{type}
```

**Concrete example:**
```
User A cart items:
  - "Rock climbing" → type: sport.climbing
  - "Trail running" → type: sport.running, outdoor.nature
  - "Farmers market" → type: food-type.produce, social-dining.market

User B cart items:
  - "Hiking" → type: sport.hiking, outdoor.adventure
  - "Sushi" → type: food-type.japanese, restaurant.japanese
  - "Urban garden" → type: plants.garden

Type intersections:
  User A.outdoor ∩ User B.outdoor = {outdoor.adventure, outdoor.nature}
  → Stars signal fires (outdoor match)
  
  User A.food-type ∩ User B.food-type = {} (no overlap — produce ≠ japanese)
  → No food Stars
```

**Anti-gaming:**
- Match must be sustained 7+ days before Stars credit confirms (prevents item-add-and-match farming)
- Category with only 1 item in either user's cart does not generate Stars (insufficient pattern)

**Edge cases:**
- User A has 12 activity items across 4 types. User B has 1 activity item. Stars do NOT fire — single-item category is noise, not pattern.
- User A and User B both have `outdoor.adventure` items = Stars fires on outdoor type, even if they have no outdoor overlap in exact items

---

### 🌙 Moons — Complementary Rhythm

**Definition:** User A's cart fills a gap in User B's cart, AND User B's cart fills a gap in User A's cart. Both directions must be true for Moons to fire.

**What is a "gap"?**

A gap is a type sub-category present in User B's cart but absent from User A's cart, where User A has other types that suggest the gap is fillable.

```
Gap criteria:
1. Type sub-category T is in User B's cart
2. Type sub-category T is NOT in User A's cart
3. User A has at least 2 items in adjacent/category that suggests complementarity
   (e.g., User A has climbing gear + camping gear, User B has climbing partner but no gear → gear gap is fillable by A)
```

**Algorithm:**

```
1. Build each user's type set: UA_types, UB_types
2. Compute UA_missing = UB_types − UA_types  (gaps in A that B has)
3. Compute UB_missing = UA_types − UB_types  (gaps in B that A has)

4. For each gap in UA_missing:
   Check if A's cart has adjacent/complement types that would fill it
   If yes → one-way complement confirmed

5. Moons fires when:
   UA_missing has ≥1 fillable complement → Moon signal (B fills A's gap)
   UB_missing has ≥1 fillable complement → Moon signal (A fills B's gap)
   Both directions true → Mutual Moons (+5 bonus)
```

**Concrete example:**

```
User A — climbing-focused cart:
  - Climbing gym → sport.climbing
  - Camping gear → outdoor.camping
  - Trail running → sport.running, outdoor.nature

User B — social-outdoors cart:
  - Hiking group → sport.hiking, outdoor.adventure
  - Picnic setup → social-dining.picnic
  - Photography → creative.photography

Type sets:
  UA_types = {sport.climbing, outdoor.camping, sport.running, outdoor.nature}
  UB_types = {sport.hiking, outdoor.adventure, social-dining.picnic, creative.photography}

Gaps:
  UA_missing ∩ UB = {outdoor.adventure, social-dining.picnic, creative.photography}
  UA has outdoor.camping + sport.climbing → outdoor.adventure gap is fillable
  UA has no social or creative → other gaps not fillable

  UB_missing ∩ UA = {sport.climbing, outdoor.camping, sport.running, outdoor.nature}
  UB has hiking → sport.climbing gap is fillable
  UB has no camping gear → outdoor.camping gap not fillable

Result: One-way Moon (A fills B's hiking gap, B's cart does not fill A's gap)

Mutual Moons requires both directions to have fillable gaps → does not fire here
```

**Anti-gaming:**
- Gap analysis requires cart minimum 5 items per user (insufficient pattern = no gap detection)
- Mutual Moons requires 30-day sustained complement before +5 bonus confirms
- Complimentary items cannot be manufactured by adding items in a gap category — adjacent/category evidence must exist independently

**Edge cases:**
- Two users with completely non-overlapping carts and no adjacent types = no Moons (not a complement, just different)
- User A has 20 items in outdoor. User B has 1 item in cooking. No Moon — single items are noise

---

### ⏳ Hourglasses — Values Alignment

**Definition:** Cart patterns over time reveal shared worldview, not just shared taste. Values alignment is deeper than category match — it is the *orientation behind* the categories.

**Current spec says:** "pass a values-alignment quiz (20 questions derived from cart patterns)"

**Problem:** A quiz that users can prepare for is gameable. A quiz that is derived from their cart patterns is circular (it asks them about things they already revealed by adding items). The quiz approach is wrong in principle.

**New approach: Values derived from cart pattern analysis, not self-report.**

The cart reveals values through:
1. **What you prioritize** (which categories have the most items)
2. **How you describe things** (the notes attached to items)
3. **What you avoid** (the gap between your stated preferences and your actual cart)
4. **The tempo of your additions** (slow and deliberate vs. rapid exploration)

**Algorithm:**

```
Step 1 — Extract value dimensions from cart

For each user, compute:
  diversity_score[category] = items_in_category / total_items
  note_density = items_with_notes / total_items
  tempo_score = stddev(days_between_additions)  (low = consistent, high = exploratory)
  
  pattern_axes:
    - adventure_vs_routine: ratio of novel/unusual items to familiar items
    - social_vs_solo: social-dining + social-activities vs solo items
    - curated_vs_spontaneous: note_length / items_added_same_week
    - care_vs_consumption: plants/pets/environmental items / total_items
    - display_vs_substance: featured_items / total_items (high = performance orientation)
```

```
Step 2 — Map axes to value vectors

Each axis maps to a value vector with a position:
  adventure_vs_routine: 0 = "routines are comfort" | 1 = "routines are prisons"
  social_vs_solo: 0 = "solitude is sacred" | 1 = "alone is a failure state"
  curated_vs_spontaneous: 0 = "intentional, deliberate" | 1 = "trust the moment"
  care_vs_consumption: 0 = "stewardship, restraint" | 1 = "abundance, expansion"
  display_vs_substance: 0 = "substance over appearance" | 1 = "identity is performance"
```

```
Step 3 — Compute alignment score

For User A and User B:
  For each value axis, compute |A_position - B_position|
  Average across all axes → Hourglass alignment score (0-1)
  
  Score > 0.75 = strong values alignment → Hourglass signal fires
  Score > 0.60 = moderate alignment → partial signal (visible to user, not promoted to ring)
```

**Concrete example:**

```
User A:
  - 15/20 items are outdoor activities (high adventure score)
  - 12/15 items have detailed notes (high curation)
  - adventure_vs_routine = 0.82, social_vs_solo = 0.65
  - care_vs_consumption = 0.30 (few plants/pets — low nurturing orientation)

User B:
  - 10/20 items outdoor, 8/20 social dining
  - notes on 4/20 items (low curation)
  - adventure_vs_routine = 0.78, social_vs_solo = 0.70
  - care_vs_consumption = 0.28

Axis distances:
  adventure_vs_routine: |0.82 - 0.78| = 0.04
  social_vs_solo: |0.65 - 0.70| = 0.05
  care_vs_consumption: |0.30 - 0.28| = 0.02
  Average = 0.037 → alignment score = 1 - 0.037 = 0.963

Hourglass fires — strong values alignment
```

**Anti-gaming:**
- Values are derived from cart structure, not self-report — cannot be prepared for
- Cart history (removed items) factors into pattern analysis — adding/removing to manipulate scores creates false tempo signals
- Minimum 20 items in cart required before Hourglass can fire (insufficient pattern otherwise)

**What replaces the quiz:**
The quiz is replaced by a **Values Profile** — a one-page view of the user's pattern axes, generated automatically from their cart. The user sees it: "Your constellation reveals your values as: high adventure, high curation, moderate social." This is not a test — it is a reflection. Users can verify that it matches their self-understanding.

---

### 🎈 Balloons — Frequency + Growth

**Definition:** Consistent presence over time. Not a single event — sustained showing up. Balloons measure the user's cart tending behavior, not any single action.

**Current spec says:** "Add to your cart once per week = +1. Daily check-in = +2/week."

**Problem:** Both are gameable. "Add an item" can be automated. "Check in" is trivially faked by opening the app. The signal is too thin.

**New approach: Balloons measure actual resonance generation, not activity theater.**

```
Balloons algorithm:

Base score per week:
  cart_additions = count(items added in rolling 7-day window)
  seed_sent = count(fractal seeds sent in rolling 7-day window)
  resonance_received = count(Hearts + Stars received from non-R1 users in rolling 7-day window)

  Weekly_balloons = cart_additions×1 + seed_sent×2 + resonance_received×3
  
  Cap: maximum 10 balloons/week from any single category (prevents automation farming)
```

**Anti-gaming rules:**
- Adding the same item type repeatedly does not generate additional balloons — must be distinct items
- Seeds sent to users who never open them do not count — seed must be received and viewed
- Resonance received from accounts with no other activity (no cart additions, no seeds sent) does not count — prevents ring-farming accounts
- Balloon accumulation rate checked against expected rate for user's tenure — if rate exceeds 3x expected for tenure, flag for review

**What Balloons are NOT:**
- Balloons are not a gamification scoreboard
- Balloons are not visible on profiles (only Float Level label is visible)
- Balloons cannot be transferred, gifted, or sold

**Float Level and Balloons:**

```
Float Level = Balloon tier determined by total accumulated balloons (not rate)

Level 0 "Grounded": 0-9 balloons
Level 1 "Rising": 10-49 balloons
Level 2 "Airy": 50-149 balloons
Level 3 "Drifting": 150-499 balloons
Level 4 "Cloud Walking": 500+ balloons

Float Level affects ambient visibility (R6 circulation priority only)
Float Level does NOT affect R1-R5 ring assignment (those are resonance-driven)
```

---

## Cross-Signal Validation Rules

The five signals are not independent. They validate each other:

### Rule 1: Hearts-only score penalty
```
If Hearts ≥ 3 AND Stars = 0:
  Effective Hearts score × 0.6 (40% penalty applied)
Reason: Exact item matches without category alignment are likely coincidental
```

### Rule 2: Moons require adjacent-type evidence
```
Moons do not fire unless:
  Each "filled gap" has adjacent-type evidence in the filler's cart
  (A cannot fill B's gap unless A has at least 2 items in the adjacent/category)
```

### Rule 3: Hourglasses override Stars
```
If Hourglasses alignment > 0.80 AND Stars exist:
  Stars weight increases by 20% (values-aligned category match is more significant)
```

### Rule 4: Balloons cannot accelerate ring promotion
```
Balloons affect Float Level (R6 ambient visibility) only
Balloons do not affect: R4/Echo, R5/Wave, or R6/Current confirmation thresholds
```

---

## Summary: Detection Rules at a Glance

| Signal | Detection Rule | Minimum Bar | Anti-Gaming |
|--------|--------------|-------------|-------------|
| ❤️ Hearts | Exact name + category + type match | Item in cart 3+ days | No item flip within 90 days |
| ⭐ Stars | Type sub-category intersection, 7+ days sustained | Both users ≥1 item in matched type; 7-day sustain | No single-item category matches |
| 🌙 Moons | Bidirectional fillable gaps | Each cart ≥5 items; adjacent-type evidence | No single-item gaps; 30-day sustain for bonus |
| ⏳ Hourglasses | Values axis distance < 0.25 | Cart ≥20 items; 90-day pattern history | Derived from cart, not self-report |
| 🎈 Balloons | Weekly cart + seed + resonance activity | 10 balloon/week cap | No ring-farming account resonance |

---

## What This Does NOT Define

1. **UI for values profile** — How the user sees their values axes and those of their matches is a separate design problem.
2. **Type taxonomy completeness** — The type taxonomy above is partial. A full type taxonomy requires user research to identify all meaningful type distinctions in each category.
3. **Calibration against real data** — All thresholds (7-day sustain, 5-item minimum, 0.25 axis distance) are starting points, not validated numbers. They need tuning against real user behavior data before launch.

---

*All four concepts now documented. Cascade concepts folder: `Cascade/concepts/`*