All files / web/src/db/schema player-curriculum.ts

100% Statements 73/73
100% Branches 1/1
100% Functions 1/1
100% Lines 73/73

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 742x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x  
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { players } from './players'
import {
  DEFAULT_PROBLEM_GENERATION_MODE,
  type ProblemGenerationMode,
} from '@/lib/curriculum/config'
 
/**
 * Player curriculum table - tracks a player's position in the soroban curriculum
 *
 * One-to-one with players table. Stores the student's current level,
 * phase, and learning preferences. Deleted when player is deleted (cascade).
 */
export const playerCurriculum = sqliteTable('player_curriculum', {
  /** Primary key and foreign key to players table */
  playerId: text('player_id')
    .primaryKey()
    .references(() => players.id, { onDelete: 'cascade' }),
 
  /**
   * Current curriculum level (1, 2, or 3)
   * - Level 1: No regrouping (single column operations)
   * - Level 2: Addition with regrouping (friends of 10)
   * - Level 3: Subtraction with regrouping (friends of 10)
   */
  currentLevel: integer('current_level').notNull().default(1),
 
  /**
   * Current phase ID within the level
   * Format: "L{level}.{operation}.{number}.{technique}"
   * Examples:
   * - "L1.add.+3.direct" = Level 1, adding 3, without friends of 5
   * - "L1.add.+3.five" = Level 1, adding 3, with friends of 5
   * - "L2.add.+7.ten" = Level 2, adding 7, with friends of 10
   * - "L3.sub.-6.ten" = Level 3, subtracting 6, with friends of 10
   */
  currentPhaseId: text('current_phase_id').notNull().default('L1.add.+1.direct'),
 
  /**
   * Worksheet preset ID for generating worksheets at this student's level
   * Maps to a saved worksheet configuration
   */
  worksheetPreset: text('worksheet_preset'),
 
  /**
   * Whether visualization mode is enabled (practice without visible abacus)
   * This is the "mental math" mode for developing anzan skills
   */
  visualizationMode: integer('visualization_mode', { mode: 'boolean' }).notNull().default(false),
 
  /**
   * Problem generation mode preference:
   * - 'adaptive': BKT-based continuous scaling (recommended, default)
   * - 'classic': Fluency-based discrete states
   */
  problemGenerationMode: text('problem_generation_mode')
    .$type<ProblemGenerationMode>()
    .notNull()
    .default(DEFAULT_PROBLEM_GENERATION_MODE),
 
  /** When this record was created */
  createdAt: integer('created_at', { mode: 'timestamp' })
    .notNull()
    .$defaultFn(() => new Date()),
 
  /** When this record was last updated */
  updatedAt: integer('updated_at', { mode: 'timestamp' })
    .notNull()
    .$defaultFn(() => new Date()),
})
 
export type PlayerCurriculum = typeof playerCurriculum.$inferSelect
export type NewPlayerCurriculum = typeof playerCurriculum.$inferInsert