All files / web/src/db/schema vision-training-sessions.ts

88.81% Statements 127/143
100% Branches 1/1
0% Functions 0/1
88.81% Lines 127/143

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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 1442x 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 2x 2x 2x 2x 2x 2x 2x 2x 2x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 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                                  
/**
 * Vision Training Sessions Schema
 *
 * Stores training session metadata, configs, results, and model paths
 * for ML models trained through the vision training UI.
 */
 
import { createId } from '@paralleldrive/cuid2'
import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
import type {
  DatasetInfo,
  EpochData,
  ModelType,
  TrainingConfig,
  TrainingResult,
} from '@/app/vision-training/train/components/wizard/types'
 
/**
 * Vision training sessions table - stores training history and model references
 */
export const visionTrainingSessions = sqliteTable(
  'vision_training_sessions',
  {
    /** Primary key */
    id: text('id')
      .primaryKey()
      .$defaultFn(() => createId()),
 
    // ---- Model Identification ----
 
    /** Model type: 'column-classifier' | 'boundary-detector' */
    modelType: text('model_type').$type<ModelType>().notNull(),
 
    /** User-friendly display name for this training session */
    displayName: text('display_name').notNull(),
 
    // ---- Training Configuration ----
 
    /** Training config used for this session */
    config: text('config', { mode: 'json' }).notNull().$type<TrainingConfig>(),
 
    /** Dataset info at training time */
    datasetInfo: text('dataset_info', { mode: 'json' }).notNull().$type<DatasetInfo>(),
 
    // ---- Training Results ----
 
    /** Final training results */
    result: text('result', { mode: 'json' }).notNull().$type<TrainingResult>(),
 
    /** Epoch-by-epoch training history */
    epochHistory: text('epoch_history', { mode: 'json' }).notNull().$type<EpochData[]>(),
 
    // ---- Model Storage ----
 
    /**
     * Path to model files relative to data/vision-training/models/
     * e.g., "boundary-detector/sessions/2024-01-09_abc123"
     */
    modelPath: text('model_path').notNull(),
 
    /** Is this the active model for this model type? */
    isActive: integer('is_active', { mode: 'boolean' }).notNull().default(false),
 
    // ---- Organization ----
 
    /** Optional notes about this training session */
    notes: text('notes'),
 
    /** Tags for filtering/organization */
    tags: text('tags', { mode: 'json' }).$type<string[]>().default([]),
 
    // ---- Timestamps ----
 
    /** When this record was created */
    createdAt: integer('created_at', { mode: 'timestamp' })
      .notNull()
      .$defaultFn(() => new Date()),
 
    /** When training was completed */
    trainedAt: integer('trained_at', { mode: 'timestamp' }).notNull(),
  },
  (table) => ({
    /** Index for filtering by model type */
    modelTypeIdx: index('vision_training_sessions_model_type_idx').on(table.modelType),
 
    /** Index for finding active models */
    isActiveIdx: index('vision_training_sessions_is_active_idx').on(table.isActive),
 
    /** Index for sorting by creation date */
    createdAtIdx: index('vision_training_sessions_created_at_idx').on(table.createdAt),
 
    /** Compound index for finding active model by type */
    activeByTypeIdx: index('vision_training_sessions_active_by_type_idx').on(
      table.modelType,
      table.isActive
    ),
  })
)
 
export type VisionTrainingSession = typeof visionTrainingSessions.$inferSelect
export type NewVisionTrainingSession = typeof visionTrainingSessions.$inferInsert
 
// ============================================================================
// Helper Types
// ============================================================================
 
/**
 * Summary view of a training session for list display
 */
export interface VisionTrainingSessionSummary {
  id: string
  modelType: ModelType
  displayName: string
  isActive: boolean
  trainedAt: Date
  // Key metrics extracted from result
  finalAccuracy: number
  finalLoss: number
  epochsTrained: number
  // For boundary detector
  finalPixelError?: number
}
 
/**
 * Convert a full session to a summary
 */
export function toVisionSessionSummary(
  session: VisionTrainingSession
): VisionTrainingSessionSummary {
  const result = session.result as TrainingResult

  return {
    id: session.id,
    modelType: session.modelType,
    displayName: session.displayName,
    isActive: session.isActive,
    trainedAt: session.trainedAt,
    finalAccuracy: result.final_accuracy,
    finalLoss: result.final_loss,
    epochsTrained: result.epochs_trained,
    finalPixelError: result.type === 'boundary-detector' ? result.final_pixel_error : undefined,
  }
}