# SRS Algorithm v2 — Design Spec

## Boxes

| Box | Role |
|-----|------|
| 0 | **New**. Unseen problems land here. First-try correct → Box 3. Wrong → Box 1. Problems never demote back to Box 0. |
| 1–9 | **Regular buckets**. Correct → promote one box. Wrong → stay (no immediate demotion). |
| 10 | **Mastered**. Rarely shown. Correct → stays 10. Wrong → drop to Box 7. |

## Promotion

- Correct answer → `box + 1` (max 10).
- Box 0 special case: first-try correct → Box 3, wrong → Box 1.

## Demotion

Demotion is **time-based**, not triggered by wrong answers.

- Clock starts from **last time the problem was shown** (not last correct).
- After the demotion interval passes without being shown, drop **1 box**.
- Demotion is checked when building a session (lazy evaluation).

### Demotion intervals (time since last shown)

| Box | Interval |
|-----|----------|
| 1–3 | 7 days |
| 4–6 | 9 days |
| 7–9 | 11 days |
| 10 | 14 days |

### Demotion cap

Each problem tracks `peakBox` (highest box ever reached).

- Minimum box after demotion: `max(1, peakBox - 2)`
- Multiple intervals can pass during absence; each interval drops 1 box, but never below the cap.
- Example: a card that reached Box 6 (peakBox=6) can never fall below Box 4, no matter how long the break.

## Problem Picking

### Weighted box selection

Parameter `p` controls bias toward lower boxes (default: 0.5).

1. Roll a random number to select a box using geometric distribution:
   - Box 1: probability `p`
   - Box 2: probability `(1-p) * p`
   - Box 3: probability `(1-p)^2 * p`
   - ...
   - Box 9: probability `(1-p)^8 * p`
   - Box 10 (mastered): separate pool, ~5% chance per pick

2. If the selected box is empty or no suitable problem found:
   - Look in box+1, box+2, ... up to Box 10.
   - If Box 10 is empty too, wrap around to Box 1.
   - If all regular boxes exhausted, pick from Box 0.
   - If Box 0 is also empty, return null.

### Cooldown filter

Skip problems shown in the last 5 minutes (`COOLDOWN_MINUTES = 5`). When picking:

1. Try to find a candidate in the selected box that is NOT on cooldown.
2. If all candidates in that box are on cooldown, fall through to the next box (same as empty-box logic).
3. If ALL boxes are exhausted (every candidate across every box is on cooldown), ignore cooldown and pick the least-recently-shown problem.

This prevents rapid repetition within a session without blocking progress when the focus set is small.

## Focus Sets

A focus set is a filtered view of the boxes. Only problems in the current focus set are candidates for picking.

### Focus set size

Configurable. Default: 10.

### Building a new focus set

Pick problems in this priority order until the focus set is full:

1. **Leftover problems** from the previous focus set (not yet mastered).
2. **Box 0** problems (new, unseen).
3. **Box 1** problems, then Box 2, then Box 3, ... (lowest box first).

### Focus set graduation

When **80% of focus set problems are in Box 3 or higher**, the focus set is considered graduated. Trigger building a new focus set.

- Problems that graduated (Box 3+) stay in their boxes but leave the focus set.
- Problems below Box 3 carry over as "leftover" into the next focus set.

## Mastered Pool

Box 10 problems enter the mastered pool.

- ~5% of picks come from the mastered pool (spot-checks).
- Correct → stays Box 10.
- Wrong → drops to Box 7. Re-enters normal rotation and will be added to a future focus set as a leftover/low-box problem.

## Stored State Per Problem

| Field | Type | Description |
|-------|------|-------------|
| `box` | int | Current box (0–10) |
| `peakBox` | int | Highest box ever reached |
| `lastShownAt` | datetime | Last time problem was shown |
| `lastCorrectAt` | datetime | Last time answered correctly |
| `correctCount` | int | Total correct answers |
| `inFocusSet` | bool | Currently in the active focus set |

## Constants

```
FOCUS_SET_SIZE = 10
FOCUS_GRADUATION_RATIO = 0.8    # 80% in Box 3+ triggers new focus set
BOX_WEIGHT_P = 0.5              # Geometric distribution parameter
MASTERED_PICK_RATE = 0.05       # 5% chance of picking a mastered card
MASTERED_BOX = 10
MASTERED_DEMOTE_TO = 7
NEW_CORRECT_PROMOTE_TO = 3     # Box 0 first-try correct lands here
DEMOTION_CAP_OFFSET = 2        # max(1, peakBox - 2)
COOLDOWN_MINUTES = 5           # skip problems shown within last 5 min
```
