All files / web/src/lib/tasks session-plan.ts

0% Statements 0/122
0% Branches 0/1
0% Functions 0/1
0% Lines 0/122

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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123                                                                                                                                                                                                                                                     
import type { SessionPlan, GameBreakSettings } from '@/db/schema/session-plans'
import {
  generateSessionPlan,
  type EnabledParts,
  type GenerateSessionPlanOptions,
} from '@/lib/curriculum'
import type { ProblemGenerationMode } from '@/lib/curriculum/config'
import type { SessionMode } from '@/lib/curriculum/session-mode'
import { createTask } from '../task-manager'
import type { SessionPlanEvent, PlanTimingBreakdown } from './events'

export interface SessionPlanInput {
  playerId: string
  durationMinutes: number
  enabledParts?: EnabledParts
  partTimeWeights?: { abacus: number; visualization: number; linear: number }
  purposeTimeWeights?: { focus: number; reinforce: number; review: number; challenge: number }
  shufflePurposes?: boolean
  problemGenerationMode?: ProblemGenerationMode
  confidenceThreshold?: number
  sessionMode?: SessionMode
  gameBreakSettings?: GameBreakSettings
  comfortAdjustment?: number
  config?: Record<string, unknown>
}

export interface SessionPlanOutput {
  plan: SessionPlan
}

/** Map session plan events to a 0-100 progress percentage */
function calculateProgressPercent(event: SessionPlanEvent): number {
  switch (event.type) {
    case 'plan_loading_data':
      return 5
    case 'plan_analyzing_skills':
      return 15
    case 'plan_structure_ready':
      return 20
    case 'plan_generating_problem': {
      // Scale from 20% to 85% during problem generation
      const ratio = event.total > 0 ? event.current / event.total : 0
      return Math.round(20 + ratio * 65)
    }
    case 'plan_part_complete':
      return 85
    case 'plan_saving':
      return 90
    case 'plan_complete':
      return 100
    default:
      return 0
  }
}

/** Get a human-readable progress message from a session plan event */
function getProgressMessage(event: SessionPlanEvent): string {
  switch (event.type) {
    case 'plan_loading_data':
      return event.message
    case 'plan_analyzing_skills':
      return event.message
    case 'plan_structure_ready':
      return event.message
    case 'plan_generating_problem':
      return `${event.partLabel} problems... ${event.current}/${event.total}`
    case 'plan_part_complete':
      return `${event.partType} complete`
    case 'plan_saving':
      return event.message
    case 'plan_complete':
      return 'Plan ready!'
    default:
      return 'Generating...'
  }
}

/**
 * Start a session plan generation background task.
 *
 * Creates a background task that generates an adaptive session plan
 * with real-time progress updates via Socket.IO.
 *
 * @returns The task ID (subscribe via useBackgroundTask to get progress)
 */
export async function startSessionPlanGeneration(
  input: SessionPlanInput,
  userId?: string
): Promise<string> {
  return createTask<SessionPlanInput, SessionPlanOutput, SessionPlanEvent>(
    'session-plan',
    input,
    async (handle) => {
      handle.setProgress(0, 'Loading student data...')

      const options: GenerateSessionPlanOptions = {
        playerId: input.playerId,
        durationMinutes: input.durationMinutes,
        enabledParts: input.enabledParts,
        shufflePurposes: input.shufflePurposes,
        problemGenerationMode: input.problemGenerationMode,
        confidenceThreshold: input.confidenceThreshold,
        sessionMode: input.sessionMode,
        gameBreakSettings: input.gameBreakSettings,
        comfortAdjustment: input.comfortAdjustment,
        config: input.config as GenerateSessionPlanOptions['config'],
        onProgress: (event) => {
          // Emit domain event for replay
          handle.emit(event as SessionPlanEvent)
          // Update progress bar
          const percent = calculateProgressPercent(event as SessionPlanEvent)
          const message = getProgressMessage(event as SessionPlanEvent)
          handle.setProgress(percent, message)
        },
      }

      const plan = await generateSessionPlan(options)
      handle.complete({ plan })
    },
    userId
  )
}