@vue-skuilder/db 0.1.32-b → 0.1.32-e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{contentSource-DF1nUbPQ.d.cts → contentSource-BMlMwSiG.d.cts} +124 -5
- package/dist/{contentSource-Bdwkvqa8.d.ts → contentSource-Ht3N2f-y.d.ts} +124 -5
- package/dist/core/index.d.cts +26 -83
- package/dist/core/index.d.ts +26 -83
- package/dist/core/index.js +767 -71
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +767 -71
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BQdfJuBN.d.cts → dataLayerProvider-BEqB8VBR.d.cts} +1 -1
- package/dist/{dataLayerProvider-BKmVoyJR.d.ts → dataLayerProvider-DObSXjnf.d.ts} +1 -1
- package/dist/impl/couch/index.d.cts +18 -5
- package/dist/impl/couch/index.d.ts +18 -5
- package/dist/impl/couch/index.js +817 -74
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +817 -74
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +4 -4
- package/dist/impl/static/index.d.ts +4 -4
- package/dist/impl/static/index.js +763 -67
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +763 -67
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +23 -8
- package/dist/index.d.ts +23 -8
- package/dist/index.js +872 -86
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +872 -86
- package/dist/index.mjs.map +1 -1
- package/docs/navigators-architecture.md +2 -2
- package/package.json +2 -2
- package/src/core/interfaces/contentSource.ts +3 -3
- package/src/core/navigators/Pipeline.ts +104 -32
- package/src/core/navigators/PipelineDebugger.ts +152 -1
- package/src/core/navigators/filters/hierarchyDefinition.ts +90 -6
- package/src/core/navigators/filters/interferenceMitigator.ts +2 -1
- package/src/core/navigators/filters/relativePriority.ts +2 -1
- package/src/core/navigators/filters/userTagPreference.ts +2 -1
- package/src/core/navigators/generators/CompositeGenerator.ts +58 -5
- package/src/core/navigators/generators/elo.ts +7 -7
- package/src/core/navigators/generators/prescribed.ts +710 -46
- package/src/core/navigators/generators/srs.ts +3 -4
- package/src/core/navigators/generators/types.ts +48 -2
- package/src/core/navigators/index.ts +4 -3
- package/src/impl/couch/CourseSyncService.ts +72 -4
- package/src/impl/couch/classroomDB.ts +4 -3
- package/src/impl/couch/courseDB.ts +5 -4
- package/src/impl/static/courseDB.ts +5 -4
- package/src/study/SessionController.ts +58 -10
- package/src/study/TagFilteredContentSource.ts +4 -3
- package/src/study/services/EloService.ts +22 -3
- package/src/study/services/ResponseProcessor.ts +7 -3
package/dist/core/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/interfaces/adminDB.ts","../../src/core/interfaces/classroomDB.ts","../../src/impl/common/SyncStrategy.ts","../../src/util/logger.ts","../../src/core/types/types-legacy.ts","../../src/core/util/index.ts","../../src/impl/couch/pouchdb-setup.ts","../../src/util/dataDirectory.ts","../../src/impl/common/userDBHelpers.ts","../../src/util/Loggable.ts","../../src/impl/couch/updateQueue.ts","../../src/impl/couch/user-course-relDB.ts","../../src/impl/couch/clientCache.ts","../../src/impl/couch/courseAPI.ts","../../src/impl/couch/courseLookupDB.ts","../../src/core/navigators/PipelineDebugger.ts","../../src/core/navigators/generators/CompositeGenerator.ts","../../src/core/navigators/generators/elo.ts","../../src/core/navigators/generators/index.ts","../../src/core/navigators/generators/prescribed.ts","../../src/core/navigators/generators/srs.ts","../../src/core/navigators/generators/types.ts","../../src/core/types/contentNavigationStrategy.ts","../../src/core/navigators/filters/WeightedFilter.ts","../../src/core/navigators/filters/eloDistance.ts","../../src/core/navigators/filters/hierarchyDefinition.ts","../../src/core/navigators/filters/userTagPreference.ts","../../src/core/navigators/filters/index.ts","../../src/core/navigators/filters/inferredPreferenceStub.ts","../../src/core/navigators/filters/interferenceMitigator.ts","../../src/core/navigators/filters/relativePriority.ts","../../src/core/navigators/filters/types.ts","../../src/core/navigators/filters/userGoalStub.ts","../../src/core/orchestration/gradient.ts","../../src/core/orchestration/learning.ts","../../src/core/orchestration/signal.ts","../../src/core/orchestration/recording.ts","../../src/core/orchestration/index.ts","../../src/core/navigators/Pipeline.ts","../../src/core/navigators/defaults.ts","../../src/core/navigators/PipelineAssembler.ts","../../src/core/navigators/index.ts","../../src/impl/couch/courseDB.ts","../../src/impl/couch/classroomDB.ts","../../src/impl/couch/adminDB.ts","../../src/impl/couch/CourseSyncService.ts","../../src/impl/couch/auth.ts","../../src/impl/couch/CouchDBSyncStrategy.ts","../../src/impl/couch/index.ts","../../src/impl/common/BaseUserDB.ts","../../src/impl/common/index.ts","../../src/factory.ts","../../src/study/TagFilteredContentSource.ts","../../src/core/interfaces/contentSource.ts","../../src/core/interfaces/courseDB.ts","../../src/core/interfaces/dataLayerProvider.ts","../../src/core/interfaces/userDB.ts","../../src/core/interfaces/index.ts","../../src/core/types/user.ts","../../src/core/types/strategyState.ts","../../src/core/types/userOutcome.ts","../../src/core/bulkImport/cardProcessor.ts","../../src/core/bulkImport/types.ts","../../src/core/bulkImport/index.ts","../../src/core/UserDBDebugger.ts","../../src/core/index.ts"],"sourcesContent":["import { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\n\n/**\n * Admin functionality\n */\nexport interface AdminDBInterface {\n /**\n * Get all users\n */\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n getUsers(): Promise<PouchDB.Core.Document<{}>[]>;\n\n /**\n * Get all courses\n */\n getCourses(): Promise<CourseConfig[]>;\n\n /**\n * Remove a course\n */\n removeCourse(id: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all classrooms\n */\n getClassrooms(): Promise<(ClassroomConfig & { _id: string })[]>;\n}\n","import { ClassroomConfig } from '@vue-skuilder/common';\n\n/**\n * Classroom management\n */\nexport interface ClassroomDBInterface {\n /**\n * Get classroom config\n */\n getConfig(): ClassroomConfig;\n\n /**\n * Get assigned content\n */\n getAssignedContent(): Promise<AssignedContent[]>;\n}\n\nexport interface TeacherClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For teacher interfaces: assign content\n */\n assignContent?(content: AssignedContent): Promise<boolean>;\n\n /**\n * For teacher interfaces: remove content\n */\n removeContent?(content: AssignedContent): Promise<void>;\n}\n\n/**\n * Student-facing classroom interface.\n * Content is accessed via StudyContentSource.getWeightedCards().\n */\nexport type StudentClassroomDBInterface = ClassroomDBInterface;\n\nexport type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;\n\nexport interface AssignedTag extends ContentBase {\n type: 'tag';\n tagID: string;\n}\nexport interface AssignedCourse extends ContentBase {\n type: 'course';\n}\nexport interface AssignedCard extends ContentBase {\n type: 'card';\n cardID: string;\n}\n\ninterface ContentBase {\n type: 'course' | 'tag' | 'card';\n /**\n * Username of the assigning teacher.\n */\n assignedBy: string;\n /**\n * Date the content was assigned.\n */\n assignedOn: moment.Moment;\n /**\n * A 'due' date for this assigned content, for scheduling content\n * in advance. Content will not be actively pushed to students until\n * this date.\n */\n activeOn: moment.Moment;\n courseID: string;\n}\n","// packages/db/src/impl/common/SyncStrategy.ts\n\nimport type { AccountCreationResult, AuthenticationResult } from './types';\n\n/**\n * Strategy interface for handling user data synchronization\n * Different implementations handle remote sync vs local-only storage\n */\nexport interface SyncStrategy {\n /**\n * Set up the remote database for a user\n * @param username The username to set up remote DB for\n * @returns PouchDB database instance (may be same as local for no-op)\n */\n setupRemoteDB(username: string): PouchDB.Database;\n\n /**\n * Get the database to use for write operations (local-first approach)\n * @param username The username to get write DB for\n * @returns PouchDB database instance for write operations\n */\n getWriteDB?(username: string): PouchDB.Database;\n\n /**\n * Start synchronization between local and remote databases\n * @param localDB The local PouchDB instance\n * @param remoteDB The remote PouchDB instance\n */\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n\n /**\n * Stop synchronization (optional - for cleanup)\n */\n stopSync?(): void;\n\n /**\n * Whether this strategy supports account creation\n */\n canCreateAccount(): boolean;\n\n /**\n * Whether this strategy supports authentication\n */\n canAuthenticate(): boolean;\n\n /**\n * Create a new user account (if supported)\n * @param username The username for the new account\n * @param password The password for the new account\n */\n createAccount?(username: string, password: string): Promise<AccountCreationResult>;\n\n /**\n * Authenticate a user (if supported)\n * @param username The username to authenticate\n * @param password The password to authenticate with\n */\n authenticate?(username: string, password: string): Promise<AuthenticationResult>;\n\n /**\n * Log out the current user (if supported)\n */\n logout?(): Promise<AuthenticationResult>;\n\n /**\n * Get the current logged-in username\n * Returns the username if logged in, or a default guest username\n */\n getCurrentUsername(): Promise<string>;\n}\n\n/**\n * Base class for sync strategies with common functionality\n */\nexport abstract class BaseSyncStrategy implements SyncStrategy {\n abstract setupRemoteDB(username: string): PouchDB.Database;\n abstract startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n abstract canCreateAccount(): boolean;\n abstract canAuthenticate(): boolean;\n abstract getCurrentUsername(): Promise<string>;\n\n stopSync?(): void {\n // Default no-op implementation\n }\n\n async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {\n throw new Error('Account creation not supported by this sync strategy');\n }\n\n async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {\n throw new Error('Authentication not supported by this sync strategy');\n }\n\n async logout(): Promise<AuthenticationResult> {\n throw new Error('Logout not supported by this sync strategy');\n }\n}\n","/**\n * Simple logging utility for @vue-skuilder/db package\n *\n * This utility provides environment-aware logging with ESLint suppressions\n * to resolve console statement violations while maintaining logging functionality.\n */\n\nconst isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';\n\nexport const logger = {\n /**\n * Debug-level logging - only shown in development\n */\n debug: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.debug(`[DB:DEBUG] ${message}`, ...args);\n }\n },\n\n /**\n * Info-level logging - general information\n */\n info: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.info(`[DB:INFO] ${message}`, ...args);\n },\n\n /**\n * Warning-level logging - potential issues\n */\n warn: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.warn(`[DB:WARN] ${message}`, ...args);\n },\n\n /**\n * Error-level logging - serious problems\n */\n error: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.error(`[DB:ERROR] ${message}`, ...args);\n },\n\n /**\n * Log function for backward compatibility with existing log() usage\n */\n log: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.log(`[DB:LOG] ${message}`, ...args);\n }\n },\n};\n","import { CourseElo, Answer, Evaluation } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { logger } from '../../util/logger';\n\nexport const GuestUsername: string = 'sk-guest-';\n\nexport const log = (message: string): void => {\n logger.log(message);\n};\n\nexport enum DocType {\n DISPLAYABLE_DATA = 'DISPLAYABLE_DATA',\n CARD = 'CARD',\n DATASHAPE = 'DATASHAPE',\n QUESTIONTYPE = 'QUESTION',\n VIEW = 'VIEW',\n PEDAGOGY = 'PEDAGOGY',\n CARDRECORD = 'CARDRECORD',\n SCHEDULED_CARD = 'SCHEDULED_CARD',\n TAG = 'TAG',\n NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',\n STRATEGY_STATE = 'STRATEGY_STATE',\n USER_OUTCOME = 'USER_OUTCOME',\n STRATEGY_LEARNING_STATE = 'STRATEGY_LEARNING_STATE',\n}\n\nexport interface QualifiedCardID {\n courseID: string;\n cardID: string;\n}\n\n/**\n * Interface for all data on course content and pedagogy stored\n * in the c/pouch database.\n */\nexport interface SkuilderCourseData {\n course: string;\n docType: DocType;\n}\n\nexport interface Tag extends SkuilderCourseData {\n docType: DocType.TAG;\n name: string;\n snippet: string; // 200 char description of the tag\n wiki: string; // 3000 char md-friendly description\n taggedCards: PouchDB.Core.DocumentId[];\n author: string;\n}\nexport interface TagStub {\n name: string;\n snippet: string;\n count: number; // the number of cards that have this tag applied\n}\n\nexport interface CardData extends SkuilderCourseData {\n docType: DocType.CARD;\n id_displayable_data: PouchDB.Core.DocumentId[];\n id_view: PouchDB.Core.DocumentId;\n elo: CourseElo;\n author: string;\n}\n\n/** A list of populated courses in the DB */\nexport interface CourseListData extends PouchDB.Core.Response {\n courses: string[];\n}\n\n/**\n * The data used to hydrate viewable components (questions, info, etc)\n */\nexport interface DisplayableData extends SkuilderCourseData {\n docType: DocType.DISPLAYABLE_DATA;\n author?: string;\n id_datashape: PouchDB.Core.DocumentId;\n data: Field[];\n _attachments?: { [index: string]: PouchDB.Core.FullAttachment };\n}\n\nexport interface Field {\n data: unknown;\n name: string;\n}\n\nexport interface DataShapeData extends SkuilderCourseData {\n docType: DocType.DATASHAPE;\n _id: PouchDB.Core.DocumentId;\n questionTypes: PouchDB.Core.DocumentId[];\n}\n\nexport interface QuestionData extends SkuilderCourseData {\n docType: DocType.QUESTIONTYPE;\n _id: PouchDB.Core.DocumentId;\n viewList: string[];\n dataShapeList: PouchDB.Core.DocumentId[];\n}\n\nexport const DocTypePrefixes = {\n [DocType.CARD]: 'c',\n [DocType.DISPLAYABLE_DATA]: 'dd',\n [DocType.TAG]: 'TAG',\n [DocType.CARDRECORD]: 'cardH',\n [DocType.SCHEDULED_CARD]: 'card_review_',\n // Add other doctypes here as they get prefixed IDs\n [DocType.DATASHAPE]: 'DATASHAPE',\n [DocType.QUESTIONTYPE]: 'QUESTION',\n [DocType.VIEW]: 'VIEW',\n [DocType.PEDAGOGY]: 'PEDAGOGY',\n [DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',\n [DocType.STRATEGY_STATE]: 'STRATEGY_STATE',\n [DocType.USER_OUTCOME]: 'USER_OUTCOME',\n [DocType.STRATEGY_LEARNING_STATE]: 'STRATEGY_LEARNING_STATE',\n} as const;\n\nexport interface CardHistory<T extends CardRecord> {\n _id: PouchDB.Core.DocumentId;\n /**\n * The CouchDB id of the card\n */\n cardID: PouchDB.Core.DocumentId;\n\n /**\n * The ID of the course\n */\n courseID: string;\n\n /**\n * The to-date largest interval between successful\n * card reviews. `0` indicates no successful reviews.\n */\n bestInterval: number;\n\n /**\n * The number of times that a card has been\n * failed in review\n */\n lapses: number;\n\n /**\n * The number of consecutive successful impressions\n * on this card\n */\n streak: number;\n\n records: T[];\n}\n\nexport interface CardRecord {\n /**\n * The CouchDB id of the card\n */\n cardID: string;\n /**\n * The ID of the course\n */\n courseID: string;\n /**\n * Number of milliseconds that the user spent before dismissing\n * the card (ie, \"I've read this\" or \"here is my answer\")\n *\n * //TODO: this (sometimes?) wants to be replaced with a rich\n * recording of user activity in working the question\n */\n timeSpent: number;\n /**\n * The date-time that the card was rendered. timeStamp + timeSpent will give the\n * time of user submission.\n */\n timeStamp: Moment;\n}\n\nexport interface QuestionRecord extends CardRecord, Evaluation {\n userAnswer: Answer;\n /**\n * The number of incorrect user submissions prededing this submisstion.\n *\n * eg, if a user is asked 7*6=__, submitting 46, 48, 42 will result in three\n * records being created having 0, 1, and 2 as their recorded 'priorAttempts' values\n */\n priorAttemps: number;\n}\n","import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';\n\nexport function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {\n return isQuestionRecord(h.records[0]);\n}\n\nexport function isQuestionRecord(c: CardRecord): c is QuestionRecord {\n return (c as QuestionRecord).userAnswer !== undefined;\n}\n\nexport function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {\n return `${DocTypePrefixes[DocType.CARDRECORD]}-${courseID}-${cardID}`;\n}\n\nexport function parseCardHistoryID(id: string): {\n courseID: string;\n cardID: string;\n} {\n const split = id.split('-');\n let error: string = '';\n error += split.length === 3 ? '' : `\\n\\tgiven ID has incorrect number of '-' characters`;\n error +=\n split[0] === DocTypePrefixes[DocType.CARDRECORD] ? '' : `\n\tgiven ID does not start with ${DocTypePrefixes[DocType.CARDRECORD]}`;\n\n if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {\n return {\n courseID: split[1],\n cardID: split[2],\n };\n } else {\n throw new Error('parseCardHistory Error:' + error);\n }\n}\n\ninterface PouchDBError extends Error {\n error?: string;\n reason?: string;\n}\n\nexport function docIsDeleted(e: PouchDBError): boolean {\n return Boolean(e?.error === 'not_found' && e?.reason === 'deleted');\n}\n","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Disable PouchDB debug logging to prevent interference with CLI prompts\n// Debug logging (like DerivedLogger.emit) will still go to the TUI log file\n// if initializeTuiLogging() has been called, but won't clutter terminal output\nif (typeof PouchDB.debug !== 'undefined') {\n PouchDB.debug.disable();\n}\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n","// Cross-platform data directory utilities for PouchDB\n// Provides OS-appropriate application data directories\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { logger } from './logger';\nimport { ENV } from '@db/factory';\n\n/**\n * Get the application data directory for the current platform\n * Uses ~/.tuilder as requested by user for simplicity\n */\nexport function getAppDataDirectory(): string {\n if (ENV.LOCAL_STORAGE_PREFIX) {\n return path.join(os.homedir(), `.tuilder`, ENV.LOCAL_STORAGE_PREFIX);\n } else {\n return path.join(os.homedir(), '.tuilder');\n }\n}\n\n/**\n * Ensure the application data directory exists\n * Creates directory recursively if it doesn't exist\n */\nexport async function ensureAppDataDirectory(): Promise<string> {\n const appDataDir = getAppDataDirectory();\n \n try {\n await fs.promises.mkdir(appDataDir, { recursive: true });\n } catch (err: any) {\n if (err.code !== 'EEXIST') {\n throw new Error(`Failed to create app data directory ${appDataDir}: ${err.message}`);\n }\n }\n \n return appDataDir;\n}\n\n/**\n * Get the full path for a PouchDB database file\n * @param dbName - The database name (e.g., 'userdb-Colin')\n */\nexport function getDbPath(dbName: string): string {\n return path.join(getAppDataDirectory(), dbName);\n}\n\n/**\n * Initialize data directory for PouchDB usage\n * Should be called once at application startup\n */\nexport async function initializeDataDirectory(): Promise<void> {\n await ensureAppDataDirectory();\n \n // Log initialization\n logger.info(`PouchDB data directory initialized: ${getAppDataDirectory()}`);\n}","// packages/db/src/impl/common/userDBHelpers.ts\n\nimport moment from 'moment';\nimport { DocType, DocTypePrefixes } from '@db/core';\nimport { logger } from '../../util/logger';\nimport { ScheduledCard } from '@db/core/types/user';\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nimport pouch from '../couch/pouchdb-setup';\nimport { getDbPath } from '../../util/dataDirectory';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n const expiryDocID: string = 'GuestAccountExpirationDate';\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\n/**\n * Get local user database with appropriate adapter for environment\n */\nexport function getLocalUserDB(username: string): PouchDB.Database {\n // // Choose adapter based on environment\n //\n // Not certain of this is required. Let's let pouch's auto detection\n // handle it until we specifically know we need to intervene.\n //\n // let adapter: string;\n // if (typeof window !== 'undefined') {\n // // Browser environment - use IndexedDB\n // adapter = 'idb';\n // } else {\n // // Node.js environment (tests) - use memory adapter\n // adapter = 'memory';\n // }\n\n const dbName = `userdb-${username}`;\n \n // Use proper data directory in Node.js, browser will use IndexedDB\n if (typeof window === 'undefined') {\n // Node.js environment - use filesystem with proper app data directory\n return new pouch(getDbPath(dbName), {});\n } else {\n // Browser environment - use default (IndexedDB)\n return new pouch(dbName, {});\n }\n}\n\n/**\n * Schedule a card review (strategy-agnostic version)\n */\nexport function scheduleCardReviewLocal(\n userDB: PouchDB.Database,\n review: {\n card_id: PouchDB.Core.DocumentId;\n time: moment.Moment;\n course_id: string;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }\n) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void userDB.put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\n/**\n * Remove a scheduled card review (strategy-agnostic version)\n */\nexport async function removeScheduledCardReviewLocal(\n userDB: PouchDB.Database,\n reviewDocID: string\n) {\n const reviewDoc = await userDB.get(reviewDocID);\n userDB\n .remove(reviewDoc)\n .then((res) => {\n if (res.ok) {\n log(`Removed Review Doc: ${reviewDocID}`);\n }\n })\n .catch((err) => {\n log(`Failed to remove Review Doc: ${reviewDocID},\\n${JSON.stringify(err)}`);\n });\n}\n","export abstract class Loggable {\n protected abstract readonly _className: string;\n protected log(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.log(`LOG-${this._className}@${new Date()}:`, ...args);\n }\n protected error(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.error(`ERROR-${this._className}@${new Date()}:`, ...args);\n }\n}\n","import { Loggable } from '../../util/Loggable';\nimport { logger } from '../../util/logger';\n\nexport type Update<T> = Partial<T> | ((x: T) => T);\n\nexport default class UpdateQueue extends Loggable {\n _className: string = 'UpdateQueue';\n private pendingUpdates: {\n [index: string]: Update<unknown>[];\n } = {};\n private inprogressUpdates: {\n [index: string]: boolean;\n } = {};\n\n private readDB: PouchDB.Database; // Database for read operations\n private writeDB: PouchDB.Database; // Database for write operations (local-first)\n\n /**\n * Queues an update for a document and applies it with conflict resolution.\n *\n * @param id - Document ID to update\n * @param update - Partial object or function that transforms the document\n * @returns Promise resolving to the updated document\n *\n * @throws {PouchError} with status 404 if document doesn't exist\n *\n * @remarks\n * **Error Handling Pattern:**\n * - This method does NOT create documents if they don't exist\n * - Callers are responsible for handling 404 errors and creating documents\n * - This design maintains separation of concerns (UpdateQueue handles conflicts, callers handle lifecycle)\n *\n * @example\n * ```typescript\n * try {\n * await updateQueue.update(docId, (doc) => ({ ...doc, field: newValue }));\n * } catch (e) {\n * if ((e as PouchError).status === 404) {\n * // Create the document with initial values\n * await db.put({ _id: docId, field: newValue, ...initialFields });\n * }\n * }\n * ```\n */\n public update<T extends PouchDB.Core.Document<object>>(\n id: PouchDB.Core.DocumentId,\n update: Update<T>\n ) {\n logger.debug(`Update requested on doc: ${id}`);\n if (this.pendingUpdates[id]) {\n this.pendingUpdates[id].push(update);\n } else {\n this.pendingUpdates[id] = [update];\n }\n return this.applyUpdates<T>(id);\n }\n\n constructor(readDB: PouchDB.Database, writeDB?: PouchDB.Database) {\n super();\n // PouchDB.debug.enable('*');\n this.readDB = readDB;\n this.writeDB = writeDB || readDB; // Default to readDB if writeDB not provided\n logger.debug(`UpdateQ initialized...`);\n void this.readDB.info().then((i) => {\n logger.debug(`db info: ${JSON.stringify(i)}`);\n });\n }\n\n private async applyUpdates<T extends PouchDB.Core.Document<object>>(\n id: string\n ): Promise<T & PouchDB.Core.GetMeta & PouchDB.Core.RevisionIdMeta> {\n logger.debug(`Applying updates on doc: ${id}`);\n if (this.inprogressUpdates[id]) {\n // Poll instead of recursing to avoid infinite recursion\n while (this.inprogressUpdates[id]) {\n await new Promise(resolve => setTimeout(resolve, Math.random() * 50));\n }\n return this.applyUpdates<T>(id);\n } else {\n if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {\n this.inprogressUpdates[id] = true;\n\n const MAX_RETRIES = 5;\n for (let i = 0; i < MAX_RETRIES; i++) {\n try {\n const doc = await this.readDB.get<T>(id);\n\n // Create a new doc object to apply updates to for this attempt\n let updatedDoc = { ...doc };\n\n // Note: This loop is not fully safe if updates are functions that depend on a specific doc state\n // that might change between retries. But for simple object merges, it's okay.\n const updatesToApply = [...this.pendingUpdates[id]];\n for (const update of updatesToApply) {\n if (typeof update === 'function') {\n updatedDoc = { ...updatedDoc, ...update(updatedDoc) };\n } else {\n updatedDoc = {\n ...updatedDoc,\n ...update,\n };\n }\n }\n\n await this.writeDB.put<T>(updatedDoc);\n\n // Success! Remove the updates we just applied.\n this.pendingUpdates[id].splice(0, updatesToApply.length);\n\n if (this.pendingUpdates[id].length === 0) {\n this.inprogressUpdates[id] = false;\n delete this.inprogressUpdates[id];\n } else {\n // More updates came in, run again.\n return this.applyUpdates<T>(id);\n }\n return updatedDoc as any; // success, exit loop and function\n } catch (e: any) {\n if (e.name === 'conflict' && i < MAX_RETRIES - 1) {\n logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);\n await new Promise((res) => setTimeout(res, 50 * Math.random()));\n // continue to next iteration of the loop\n } else if (e.name === 'not_found' && i === 0) {\n // Document not present - throw to caller for initialization\n logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);\n delete this.inprogressUpdates[id];\n throw e; // Let caller handle\n } else {\n // Max retries reached or a non-conflict error\n delete this.inprogressUpdates[id];\n if (this.pendingUpdates[id]) {\n delete this.pendingUpdates[id];\n }\n logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);\n throw e; // Let caller handle\n }\n }\n }\n // This should be unreachable, but it satisfies the compiler that a value is always returned or an error thrown.\n throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);\n } else {\n throw new Error(`Empty Updates Queue Triggered`);\n }\n }\n }\n}\n","import {\n ScheduledCard,\n UserCourseSetting,\n UserCourseSettings,\n UsrCrsDataInterface,\n} from '@db/core';\n\nimport moment, { Moment } from 'moment';\n\nimport { UserDBInterface } from '@db/core';\nimport { logger } from '../../util/logger';\n\nexport class UsrCrsData implements UsrCrsDataInterface {\n private user: UserDBInterface;\n private _courseId: string;\n\n constructor(user: UserDBInterface, courseId: string) {\n this.user = user;\n this._courseId = courseId;\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews() {\n const now = moment.utc();\n return this.getReviewstoDate(now);\n }\n\n public async getScheduledReviewCount(): Promise<number> {\n return (await this.getPendingReviews()).length;\n }\n\n public async getCourseSettings(): Promise<UserCourseSettings> {\n const regDoc = await this.user.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);\n\n if (crsDoc && crsDoc.settings) {\n return crsDoc.settings;\n } else {\n logger.warn(`no settings found during lookup on course ${this._courseId}`);\n return {};\n }\n }\n public updateCourseSettings(updates: UserCourseSetting[]): void {\n // TODO: Add updateCourseSettings method to UserDBInterface\n // For now, we'll need to cast to access the concrete implementation\n if ('updateCourseSettings' in this.user) {\n void (this.user as any).updateCourseSettings(this._courseId, updates);\n }\n }\n\n public async getStrategyState<T>(strategyKey: string): Promise<T | null> {\n return this.user.getStrategyState<T>(this._courseId, strategyKey);\n }\n\n public async putStrategyState<T>(strategyKey: string, data: T): Promise<void> {\n return this.user.putStrategyState<T>(this._courseId, strategyKey, data);\n }\n\n public async deleteStrategyState(strategyKey: string): Promise<void> {\n return this.user.deleteStrategyState(this._courseId, strategyKey);\n }\n\n private async getReviewstoDate(targetDate: Moment) {\n // Use the interface method instead of direct database access\n const allReviews = await this.user.getPendingReviews(this._courseId);\n\n logger.debug(\n `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`\n );\n\n return allReviews.filter((review: ScheduledCard) => {\n const reviewTime = moment.utc(review.reviewTime);\n return targetDate.isAfter(reviewTime);\n });\n }\n}\n","// todo: something good here instead\n\nconst CLIENT_CACHE: {\n [k: string]: unknown;\n} = {};\n\nexport async function GET_CACHED<K>(k: string, f?: (x: string) => Promise<K>): Promise<K> {\n if (CLIENT_CACHE[k]) {\n // console.log('returning a cached item');\n return CLIENT_CACHE[k] as K;\n }\n\n CLIENT_CACHE[k] = f ? await f(k) : await GET_ITEM(k);\n return GET_CACHED(k);\n}\n\nasync function GET_ITEM(k: string): Promise<unknown> {\n throw new Error(`No implementation found for GET_CACHED(${k})`);\n}\n","import pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from '.';\nimport { ENV } from '@db/factory';\n// import { DataShape } from '../..base-course/Interfaces/DataShape';\nimport { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';\nimport { CourseConfig, DataShape } from '@vue-skuilder/common';\nimport { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';\nimport { CourseDB, createTag } from './courseDB';\nimport { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';\nimport { prepareNote55 } from '@vue-skuilder/common';\nimport { BaseUser } from '../common';\nimport { logger } from '@db/util/logger';\nimport { v4 as uuidv4 } from 'uuid';\n\n/**\n *\n * @param courseID id of the course (quilt) being added to\n * @param codeCourse\n * @param shape\n * @param data the datashape data - data required for this shape\n * @param author\n * @param uploads optional additional media uploads: img0, img1, ..., aud0, aud1,...\n * @returns\n */\nexport async function addNote55(\n courseID: string,\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [x: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);\n const _id = `${DocTypePrefixes[DocType.DISPLAYABLE_DATA]}-${uuidv4()}`;\n const result = await db.put<DisplayableData>({ ...payload, _id });\n\n const dataShapeId = NameSpacer.getDataShapeString({\n course: codeCourse,\n dataShape: shape.name,\n });\n\n if (result.ok) {\n try {\n // create cards\n await createCards(courseID, dataShapeId, result.id, tags, elo, author);\n } catch (error) {\n // Handle CouchDB errors which often have a 'reason' property\n let errorMessage = 'Unknown error';\n if (error instanceof Error) {\n errorMessage = error.message;\n } else if (error && typeof error === 'object' && 'reason' in error) {\n errorMessage = error.reason as string;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = error.message as string;\n } else {\n errorMessage = String(error);\n }\n\n logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);\n // Add info to result to indicate card creation failed\n (result as any).cardCreationFailed = true;\n (result as any).cardCreationError = errorMessage;\n }\n } else {\n logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);\n }\n\n return result;\n}\n\nasync function createCards(\n courseID: string,\n datashapeID: PouchDB.Core.DocumentId,\n noteID: PouchDB.Core.DocumentId,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const cfg = await getCredentialledCourseConfig(courseID);\n const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);\n let questionViewTypes: string[] = [];\n\n for (const ds of cfg.dataShapes) {\n if (ds.name === datashapeID) {\n questionViewTypes = ds.questionTypes;\n }\n }\n\n if (questionViewTypes.length === 0) {\n const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;\n logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n for (const questionView of questionViewTypes) {\n await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);\n }\n}\n\nasync function createCard(\n questionViewName: string,\n courseID: string,\n dsDescriptor: ShapeDescriptor,\n noteID: string,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);\n const cfg = await getCredentialledCourseConfig(courseID);\n\n for (const rQ of cfg.questionTypes) {\n if (rQ.name === questionViewName) {\n for (const view of rQ.viewList) {\n await addCard(\n courseID,\n dsDescriptor.course,\n [noteID],\n NameSpacer.getViewString({\n course: qDescriptor.course,\n questionType: qDescriptor.questionType,\n view,\n }),\n elo,\n tags,\n author\n );\n }\n }\n }\n}\n\n/**\n *\n * Adds a card to the DB. This function is called\n * as a side effect of adding either a View or\n * DisplayableData item.\n * @param course The name of the course that the card belongs to\n * @param id_displayable_data C/PouchDB ID of the data used to hydrate the view\n * @param id_view C/PouchDB ID of the view used to display the card\n *\n * @package\n */\nasync function addCard(\n courseID: string,\n course: string,\n id_displayable_data: PouchDB.Core.DocumentId[],\n id_view: PouchDB.Core.DocumentId,\n elo: CourseElo,\n tags: string[],\n author: string\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;\n const card = await db.put<CardData>({\n _id,\n course,\n id_displayable_data,\n id_view,\n docType: DocType.CARD,\n elo: elo || toCourseElo(990 + Math.round(20 * Math.random())),\n author,\n });\n for (const tag of tags) {\n logger.info(`adding tag: ${tag} to card ${card.id}`);\n await addTagToCard(courseID, card.id, tag, author, false);\n }\n return card;\n}\n\nexport async function getCredentialledCourseConfig(courseID: string): Promise<CourseConfig> {\n try {\n const db = getCourseDB(courseID);\n const ret = await db.get<CourseConfig>('CourseConfig');\n ret.courseID = courseID;\n logger.info(`Returning course config: ${JSON.stringify(ret)}`);\n return ret;\n } catch (e) {\n logger.error(`Error fetching config for ${courseID}:`, e);\n throw e;\n }\n}\n\n/**\n Assciates a tag with a card.\n\n NB: DB stores tags as separate documents, with a list of card IDs.\n Consider renaming to `addCardToTag` to reflect this.\n\n NB: tags are created if they don't already exist\n\n @param updateELO whether to update the ELO of the card with the new tag. Default true.\n @package\n*/\nexport async function addTagToCard(\n courseID: string,\n cardID: string,\n tagID: string,\n author: string,\n updateELO: boolean = true\n): Promise<PouchDB.Core.Response> {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n const prefixedTagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const courseApi = new CourseDB(courseID, async () => {\n const dummySyncStrategy = {\n setupRemoteDB: () => null as any,\n startSync: () => {},\n canCreateAccount: () => false,\n canAuthenticate: () => false,\n getCurrentUsername: async () => 'DummyUser',\n };\n return BaseUser.Dummy(dummySyncStrategy);\n });\n try {\n logger.info(`Applying tag ${tagID} to card ${courseID + '-' + cardID}...`);\n const tag = await courseDB.get<Tag>(prefixedTagID);\n if (!tag.taggedCards.includes(cardID)) {\n tag.taggedCards.push(cardID);\n\n if (updateELO) {\n try {\n const eloData = await courseApi.getCardEloData([cardID]);\n const elo = eloData[0];\n elo.tags[tagID] = {\n count: 0,\n score: elo.global.score, // todo: or 1000?\n };\n await updateCardElo(courseID, cardID, elo);\n } catch (error) {\n logger.error('Failed to update ELO data for card:', cardID, error);\n }\n }\n\n return courseDB.put<Tag>(tag);\n } else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);\n } catch (e) {\n if (e instanceof AlreadyTaggedErr) {\n throw e;\n }\n\n await createTag(courseID, tagID, author);\n return addTagToCard(courseID, cardID, tagID, author, updateELO);\n }\n}\n\nasync function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nclass AlreadyTaggedErr extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AlreadyTaggedErr';\n }\n}\n\nexport function getTagID(tagName: string): string {\n const tagPrefix = DocType.TAG.valueOf() + '-';\n if (tagName.indexOf(tagPrefix) === 0) {\n return tagName;\n } else {\n return tagPrefix + tagName;\n }\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n const dbName = `coursedb-${courseID}`;\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport { logger } from '../../util/logger';\n\nconst courseLookupDBTitle = 'coursedb-lookup';\n\ninterface CourseLookupDoc {\n _id: string;\n _rev: string;\n name: string;\n disambiguator?: string;\n}\n\nlogger.debug(`COURSELOOKUP FILE RUNNING`);\n\n/**\n * A Lookup table of existant courses. Each docID in this DB correspondes to a\n * course database whose name is `coursedb-{docID}`\n */\nexport default class CourseLookup {\n // [ ] this db should be read only for public, admin-only for write\n // Cache for the PouchDB instance\n private static _dbInstance: PouchDB.Database | null = null;\n\n /**\n * Static getter for the PouchDB database instance.\n * Connects using ENV variables and caches the instance.\n * Throws an error if required ENV variables are not set.\n */\n private static get _db(): PouchDB.Database {\n // Return cached instance if available\n if (this._dbInstance) {\n return this._dbInstance;\n }\n\n // --- Check required environment variables ---\n if (ENV.COUCHDB_SERVER_URL === 'NOT_SET' || !ENV.COUCHDB_SERVER_URL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_URL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n if (ENV.COUCHDB_SERVER_PROTOCOL === 'NOT_SET' || !ENV.COUCHDB_SERVER_PROTOCOL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_PROTOCOL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n\n // --- Construct connection options ---\n const dbUrl = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}/${courseLookupDBTitle}`;\n const options: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n // fetch: (url, opts) => { // Optional: Add for debugging network requests\n // console.log('PouchDB fetch:', url, opts);\n // return pouch.fetch(url, opts);\n // }\n };\n\n // Add authentication if both username and password are provided\n if (ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD) {\n options.auth = {\n username: ENV.COUCHDB_USERNAME,\n password: ENV.COUCHDB_PASSWORD,\n };\n logger.info(`CourseLookup: Connecting to ${dbUrl} with authentication.`);\n } else {\n logger.info(`CourseLookup: Connecting to ${dbUrl} without authentication.`);\n }\n\n // --- Create and cache the PouchDB instance ---\n try {\n this._dbInstance = new pouch(dbUrl, options);\n logger.info(`CourseLookup: Database instance created for ${courseLookupDBTitle}.`);\n return this._dbInstance;\n } catch (error) {\n logger.error(`CourseLookup: Failed to create PouchDB instance for ${dbUrl}`, error);\n // Reset cache attempt on failure\n this._dbInstance = null;\n // Re-throw the error to indicate connection failure\n throw new Error(\n `CourseLookup: Failed to initialize database connection: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * Adds a new course to the lookup database, and returns the courseID\n * @param courseName\n * @returns\n */\n static async add(courseName: string): Promise<string> {\n const resp = await CourseLookup._db.post({\n name: courseName,\n });\n\n return resp.id;\n }\n\n /**\n * Adds a new course to the lookup database with a specific courseID\n * @param courseId The specific course ID to use\n * @param courseName The course name\n * @param disambiguator Optional disambiguator\n * @returns Promise<void>\n */\n static async addWithId(\n courseId: string,\n courseName: string,\n disambiguator?: string\n ): Promise<void> {\n const doc: Omit<CourseLookupDoc, '_rev'> = {\n _id: courseId,\n name: courseName,\n };\n\n if (disambiguator) {\n doc.disambiguator = disambiguator;\n }\n\n await CourseLookup._db.put(doc);\n }\n\n /**\n * Removes a course from the index\n * @param courseID\n */\n static async delete(courseID: string): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get(courseID);\n return await CourseLookup._db.remove(doc);\n }\n\n // [ ] rename to allCourses()\n static async allCourseWare(): Promise<CourseLookupDoc[]> {\n const resp = await CourseLookup._db.allDocs<CourseLookupDoc>({\n include_docs: true,\n });\n\n return resp.rows.map((row) => row.doc!);\n }\n\n static async updateDisambiguator(\n courseID: string,\n disambiguator?: string\n ): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get<CourseLookupDoc>(courseID);\n doc.disambiguator = disambiguator;\n return await CourseLookup._db.put(doc);\n }\n\n static async isCourse(courseID: string): Promise<boolean> {\n try {\n await CourseLookup._db.get(courseID);\n return true;\n } catch (error) {\n logger.info(`Courselookup failed:`, error);\n return false;\n }\n }\n}\n","import type { WeightedCard, StrategyContribution } from './index';\nimport {\n getRegisteredNavigatorNames,\n getRegisteredNavigatorRole,\n NavigatorRoles,\n type Navigators,\n isGenerator,\n isFilter,\n} from './index';\nimport { logger } from '../../util/logger';\nimport type { Pipeline, CardSpaceDiagnosis } from './Pipeline';\n\n/**\n * Captured reference to the most recently created Pipeline instance.\n * Used by the debug API to run diagnostics against the live pipeline.\n */\nlet _activePipeline: Pipeline | null = null;\n\n/**\n * Register a pipeline instance for diagnostic access.\n * Called by Pipeline constructor.\n */\nexport function registerPipelineForDebug(pipeline: Pipeline): void {\n _activePipeline = pipeline;\n}\n\n// ============================================================================\n// PIPELINE DEBUGGER\n// ============================================================================\n//\n// Console-accessible debug API for inspecting pipeline decisions.\n//\n// Exposed as `window.skuilder.pipeline` for interactive exploration.\n//\n// Usage:\n// window.skuilder.pipeline.showLastRun()\n// window.skuilder.pipeline.showCard('cardId123')\n// window.skuilder.pipeline.explainReviews()\n// window.skuilder.pipeline.export()\n//\n// ============================================================================\n\n/**\n * Summary of a single generator's contribution.\n */\nexport interface GeneratorSummary {\n name: string;\n cardCount: number;\n newCount: number;\n reviewCount: number;\n topScore: number;\n}\n\n/**\n * Summary of a filter's impact on scores.\n */\nexport interface FilterImpact {\n name: string;\n boosted: number;\n penalized: number;\n passed: number;\n removed: number;\n}\n\n/**\n * Complete record of a single pipeline execution.\n */\nexport interface PipelineRunReport {\n runId: string;\n timestamp: Date;\n courseId: string;\n courseName?: string;\n\n // Generator phase\n generatorName: string;\n generators?: GeneratorSummary[];\n generatedCount: number;\n\n // Filter phase\n filters: FilterImpact[];\n\n // Results\n finalCount: number;\n reviewsSelected: number;\n newSelected: number;\n\n // Full card data for inspection\n cards: Array<{\n cardId: string;\n courseId: string;\n origin: 'new' | 'review' | 'unknown';\n finalScore: number;\n provenance: StrategyContribution[];\n tags?: string[];\n selected: boolean;\n }>;\n}\n\n/**\n * Ring buffer for storing recent pipeline runs.\n */\nconst MAX_RUNS = 10;\nconst runHistory: PipelineRunReport[] = [];\n\n/**\n * Determine card origin from provenance trail.\n */\nfunction getOrigin(card: WeightedCard): 'new' | 'review' | 'unknown' {\n const firstEntry = card.provenance[0];\n if (!firstEntry) return 'unknown';\n const reason = firstEntry.reason?.toLowerCase() || '';\n if (reason.includes('new card')) return 'new';\n if (reason.includes('review')) return 'review';\n return 'unknown';\n}\n\n/**\n * Capture a pipeline run for later inspection.\n */\nexport function captureRun(report: Omit<PipelineRunReport, 'runId' | 'timestamp'>): void {\n const fullReport: PipelineRunReport = {\n ...report,\n runId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: new Date(),\n };\n\n runHistory.unshift(fullReport);\n if (runHistory.length > MAX_RUNS) {\n runHistory.pop();\n }\n}\n\n/**\n * Build a capture-ready report from pipeline execution data.\n */\nexport function buildRunReport(\n courseId: string,\n courseName: string | undefined,\n generatorName: string,\n generators: GeneratorSummary[] | undefined,\n generatedCount: number,\n filters: FilterImpact[],\n allCards: WeightedCard[],\n selectedCards: WeightedCard[]\n): Omit<PipelineRunReport, 'runId' | 'timestamp'> {\n const selectedIds = new Set(selectedCards.map((c) => c.cardId));\n\n const cards = allCards.map((card) => ({\n cardId: card.cardId,\n courseId: card.courseId,\n origin: getOrigin(card),\n finalScore: card.score,\n provenance: card.provenance,\n tags: card.tags,\n selected: selectedIds.has(card.cardId),\n }));\n\n const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === 'review').length;\n const newSelected = selectedCards.filter((c) => getOrigin(c) === 'new').length;\n\n return {\n courseId,\n courseName,\n generatorName,\n generators,\n generatedCount,\n filters,\n finalCount: selectedCards.length,\n reviewsSelected,\n newSelected,\n cards,\n };\n}\n\n// ============================================================================\n// CONSOLE API\n// ============================================================================\n\n/**\n * Format a provenance trail for console display.\n */\nfunction formatProvenance(provenance: StrategyContribution[]): string {\n return provenance\n .map((p) => {\n const actionSymbol =\n p.action === 'generated'\n ? '🎲'\n : p.action === 'boosted'\n ? '⬆️'\n : p.action === 'penalized'\n ? '⬇️'\n : '➡️';\n return ` ${actionSymbol} ${p.strategyName}: ${p.score.toFixed(3)} - ${p.reason}`;\n })\n .join('\\n');\n}\n\n/**\n * Print summary of a single pipeline run.\n */\nfunction printRunSummary(run: PipelineRunReport): void {\n // eslint-disable-next-line no-console\n console.group(`🔍 Pipeline Run: ${run.courseId} (${run.courseName || 'unnamed'})`);\n logger.info(`Run ID: ${run.runId}`);\n logger.info(`Time: ${run.timestamp.toISOString()}`);\n logger.info(`Generator: ${run.generatorName} → ${run.generatedCount} candidates`);\n\n if (run.generators && run.generators.length > 0) {\n // eslint-disable-next-line no-console\n console.group('Generator breakdown:');\n for (const g of run.generators) {\n logger.info(\n ` ${g.name}: ${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} reviews, top: ${g.topScore.toFixed(2)})`\n );\n }\n // eslint-disable-next-line no-console\n console.groupEnd();\n }\n\n if (run.filters.length > 0) {\n // eslint-disable-next-line no-console\n console.group('Filter impact:');\n for (const f of run.filters) {\n logger.info(` ${f.name}: ↑${f.boosted} ↓${f.penalized} =${f.passed} ✕${f.removed}`);\n }\n // eslint-disable-next-line no-console\n console.groupEnd();\n }\n\n logger.info(\n `Result: ${run.finalCount} cards selected (${run.newSelected} new, ${run.reviewsSelected} reviews)`\n );\n // eslint-disable-next-line no-console\n console.groupEnd();\n}\n\n/**\n * Console API object exposed on window.skuilder.pipeline\n */\nexport const pipelineDebugAPI = {\n /**\n * Get raw run history for programmatic access.\n */\n get runs(): PipelineRunReport[] {\n return [...runHistory];\n },\n\n /**\n * Show summary of a specific pipeline run.\n */\n showRun(idOrIndex: string | number = 0): void {\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No runs captured yet.');\n return;\n }\n\n let run: PipelineRunReport | undefined;\n\n if (typeof idOrIndex === 'number') {\n run = runHistory[idOrIndex];\n if (!run) {\n logger.info(\n `[Pipeline Debug] No run found at index ${idOrIndex}. History length: ${runHistory.length}`\n );\n return;\n }\n } else {\n run = runHistory.find((r) => r.runId.endsWith(idOrIndex));\n if (!run) {\n logger.info(`[Pipeline Debug] No run found matching ID '${idOrIndex}'.`);\n return;\n }\n }\n\n printRunSummary(run);\n },\n\n /**\n * Show summary of the last pipeline run.\n */\n showLastRun(): void {\n this.showRun(0);\n },\n\n /**\n * Show detailed provenance for a specific card.\n */\n showCard(cardId: string): void {\n for (const run of runHistory) {\n const card = run.cards.find((c) => c.cardId === cardId);\n if (card) {\n // eslint-disable-next-line no-console\n console.group(`🎴 Card: ${cardId}`);\n logger.info(`Course: ${card.courseId}`);\n logger.info(`Origin: ${card.origin}`);\n logger.info(`Final score: ${card.finalScore.toFixed(3)}`);\n logger.info(`Selected: ${card.selected ? 'Yes ✅' : 'No ❌'}`);\n logger.info('Provenance:');\n logger.info(formatProvenance(card.provenance));\n // eslint-disable-next-line no-console\n console.groupEnd();\n return;\n }\n }\n logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);\n },\n\n /**\n * Explain why reviews may or may not have been selected.\n */\n explainReviews(): void {\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No runs captured yet.');\n return;\n }\n\n // eslint-disable-next-line no-console\n console.group('📋 Review Selection Analysis');\n\n for (const run of runHistory) {\n // eslint-disable-next-line no-console\n console.group(`Run: ${run.courseId} @ ${run.timestamp.toLocaleTimeString()}`);\n\n const allReviews = run.cards.filter((c) => c.origin === 'review');\n const selectedReviews = allReviews.filter((c) => c.selected);\n\n if (allReviews.length === 0) {\n logger.info('❌ No reviews were generated. Check SRS logs for why.');\n } else if (selectedReviews.length === 0) {\n logger.info(`⚠️ ${allReviews.length} reviews generated but none selected.`);\n logger.info('Possible reasons:');\n\n // Check if new cards scored higher\n const topNewScore = Math.max(\n ...run.cards.filter((c) => c.origin === 'new' && c.selected).map((c) => c.finalScore),\n 0\n );\n const topReviewScore = Math.max(...allReviews.map((c) => c.finalScore), 0);\n\n if (topReviewScore < topNewScore) {\n logger.info(\n ` - New cards scored higher (top new: ${topNewScore.toFixed(2)}, top review: ${topReviewScore.toFixed(2)})`\n );\n }\n\n // Show top review that didn't make it\n const topReview = allReviews.sort((a, b) => b.finalScore - a.finalScore)[0];\n if (topReview) {\n logger.info(` - Top review score: ${topReview.finalScore.toFixed(3)}`);\n logger.info(' - Its provenance:');\n logger.info(formatProvenance(topReview.provenance));\n }\n } else {\n logger.info(`✅ ${selectedReviews.length}/${allReviews.length} reviews selected.`);\n logger.info('Top selected review:');\n const topSelected = selectedReviews.sort((a, b) => b.finalScore - a.finalScore)[0];\n logger.info(formatProvenance(topSelected.provenance));\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Show all runs in compact format.\n */\n listRuns(): void {\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No runs captured yet.');\n return;\n }\n\n // eslint-disable-next-line no-console\n console.table(\n runHistory.map((r) => ({\n id: r.runId.slice(-8),\n time: r.timestamp.toLocaleTimeString(),\n course: r.courseName || r.courseId.slice(0, 8),\n generated: r.generatedCount,\n selected: r.finalCount,\n new: r.newSelected,\n reviews: r.reviewsSelected,\n }))\n );\n },\n\n /**\n * Export run history as JSON for bug reports.\n */\n export(): string {\n const json = JSON.stringify(runHistory, null, 2);\n logger.info('[Pipeline Debug] Run history exported. Copy the returned string or use:');\n logger.info(' copy(window.skuilder.pipeline.export())');\n return json;\n },\n\n /**\n * Clear run history.\n */\n clear(): void {\n runHistory.length = 0;\n logger.info('[Pipeline Debug] Run history cleared.');\n },\n\n /**\n * Show the navigator registry: all registered classes and their roles.\n *\n * Useful for verifying that consumer-defined navigators were registered\n * before pipeline assembly.\n */\n showRegistry(): void {\n const names = getRegisteredNavigatorNames();\n if (names.length === 0) {\n logger.info('[Pipeline Debug] Navigator registry is empty.');\n return;\n }\n\n // eslint-disable-next-line no-console\n console.group('📦 Navigator Registry');\n // eslint-disable-next-line no-console\n console.table(\n names.map((name) => {\n const registryRole = getRegisteredNavigatorRole(name);\n const builtinRole = NavigatorRoles[name as Navigators];\n const effectiveRole = builtinRole || registryRole || '⚠️ NONE';\n const source = builtinRole ? 'built-in' : registryRole ? 'consumer' : 'unclassified';\n return {\n name,\n role: effectiveRole,\n source,\n isGenerator: isGenerator(name),\n isFilter: isFilter(name),\n };\n })\n );\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Show strategy documents from the last pipeline run and how they mapped\n * to the registry.\n *\n * If no runs are captured yet, falls back to showing just the registry.\n */\n showStrategies(): void {\n this.showRegistry();\n\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No pipeline runs captured yet — cannot show strategy doc mapping.');\n return;\n }\n\n const run = runHistory[0];\n // eslint-disable-next-line no-console\n console.group('🔌 Pipeline Strategy Mapping (last run)');\n logger.info(`Generator: ${run.generatorName}`);\n if (run.generators && run.generators.length > 0) {\n for (const g of run.generators) {\n logger.info(` 📥 ${g.name}: ${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} reviews)`);\n }\n }\n if (run.filters.length > 0) {\n logger.info('Filters:');\n for (const f of run.filters) {\n logger.info(` 🔸 ${f.name}: ↑${f.boosted} ↓${f.penalized} =${f.passed} ✕${f.removed}`);\n }\n } else {\n logger.info('Filters: (none)');\n }\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Scan the full card space through the filter chain for the current user.\n *\n * Reports how many cards are well-indicated and how many are new.\n * Use this to understand how the search space grows during onboarding.\n *\n * @param threshold - Score threshold for \"well indicated\" (default 0.10)\n */\n async diagnoseCardSpace(threshold?: number): Promise<CardSpaceDiagnosis | null> {\n if (!_activePipeline) {\n logger.info('[Pipeline Debug] No active pipeline. Run a session first.');\n return null;\n }\n return _activePipeline.diagnoseCardSpace({ threshold });\n },\n\n /**\n * Show help.\n */\n help(): void {\n logger.info(`\n🔧 Pipeline Debug API\n\nCommands:\n .showLastRun() Show summary of most recent pipeline run\n .showRun(id|index) Show summary of a specific run (by index or ID suffix)\n .showCard(cardId) Show provenance trail for a specific card\n .explainReviews() Analyze why reviews were/weren't selected\n .diagnoseCardSpace() Scan full card space through filters (async)\n .showRegistry() Show navigator registry (classes + roles)\n .showStrategies() Show registry + strategy mapping from last run\n .listRuns() List all captured runs in table format\n .export() Export run history as JSON for bug reports\n .clear() Clear run history\n .runs Access raw run history array\n .help() Show this help message\n\nExample:\n window.skuilder.pipeline.showLastRun()\n window.skuilder.pipeline.showRun(1)\n await window.skuilder.pipeline.diagnoseCardSpace()\n`);\n },\n};\n\n// ============================================================================\n// WINDOW MOUNT\n// ============================================================================\n\n/**\n * Mount the debug API on window.skuilder.pipeline\n */\nexport function mountPipelineDebugger(): void {\n if (typeof window === 'undefined') return;\n\n const win = window as any;\n win.skuilder = win.skuilder || {};\n win.skuilder.pipeline = pipelineDebugAPI;\n}\n\n// Auto-mount when module is loaded\nmountPipelineDebugger();","import { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport type { CardGenerator, GeneratorContext } from './types';\nimport { logger } from '../../../util/logger';\n\n// ============================================================================\n// COMPOSITE GENERATOR\n// ============================================================================\n//\n// Composes multiple generator strategies into a single generator.\n//\n// Use case: When a course has multiple generators (e.g., ELO + SRS), this\n// class merges their outputs into a unified candidate list.\n//\n// Aggregation strategy:\n// - Cards appearing in multiple generators get a frequency boost\n// - Score = average(scores) * (1 + 0.1 * (appearances - 1))\n// - This rewards cards that multiple generators agree on\n//\n// ============================================================================\n\n/**\n * Aggregation modes for combining scores from multiple generators.\n */\nexport enum AggregationMode {\n /** Use the maximum score from any generator */\n MAX = 'max',\n /** Average all scores */\n AVERAGE = 'average',\n /** Average with frequency boost: avg * (1 + 0.1 * (n-1)) */\n FREQUENCY_BOOST = 'frequencyBoost',\n}\n\nconst DEFAULT_AGGREGATION_MODE = AggregationMode.FREQUENCY_BOOST;\nconst FREQUENCY_BOOST_FACTOR = 0.1;\n\n/**\n * Composes multiple generators into a single generator.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n *\n * Fetches candidates from all generators, deduplicates by cardId,\n * and aggregates scores based on the configured mode.\n */\nexport default class CompositeGenerator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string = 'Composite Generator';\n\n private generators: CardGenerator[];\n private aggregationMode: AggregationMode;\n\n constructor(\n generators: CardGenerator[],\n aggregationMode: AggregationMode = DEFAULT_AGGREGATION_MODE\n ) {\n super();\n this.generators = generators;\n this.aggregationMode = aggregationMode;\n\n if (generators.length === 0) {\n throw new Error('CompositeGenerator requires at least one generator');\n }\n\n logger.debug(\n `[CompositeGenerator] Created with ${generators.length} generators, mode: ${aggregationMode}`\n );\n }\n\n /**\n * Creates a CompositeGenerator from strategy data.\n *\n * This is a convenience factory for use by PipelineAssembler.\n */\n static async fromStrategies(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategies: ContentNavigationStrategyData[],\n aggregationMode: AggregationMode = DEFAULT_AGGREGATION_MODE\n ): Promise<CompositeGenerator> {\n const generators = await Promise.all(\n strategies.map((s) => ContentNavigator.create(user, course, s))\n );\n // Cast is safe because we know these are generators\n return new CompositeGenerator(generators as unknown as CardGenerator[], aggregationMode);\n }\n\n /**\n * Get weighted cards from all generators, merge and deduplicate.\n *\n * Cards appearing in multiple generators receive a score boost.\n * Provenance tracks which generators produced each card and how scores were aggregated.\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param context - GeneratorContext passed to child generators (required when called via Pipeline)\n */\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {\n if (!context) {\n throw new Error(\n 'CompositeGenerator.getWeightedCards requires a GeneratorContext. ' +\n 'It should be called via Pipeline, not directly.'\n );\n }\n\n // Fetch from all generators in parallel\n const results = await Promise.all(\n this.generators.map((g) => g.getWeightedCards(limit, context))\n );\n\n // Log per-generator breakdown for transparency\n const generatorSummaries: string[] = [];\n results.forEach((cards, index) => {\n const gen = this.generators[index];\n const genName = gen.name || `Generator ${index}`;\n const newCards = cards.filter((c) => c.provenance[0]?.reason?.includes('new card'));\n const reviewCards = cards.filter((c) => c.provenance[0]?.reason?.includes('review'));\n \n if (cards.length > 0) {\n const topScore = Math.max(...cards.map((c) => c.score)).toFixed(2);\n const parts: string[] = [];\n if (newCards.length > 0) parts.push(`${newCards.length} new`);\n if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);\n const breakdown = parts.length > 0 ? parts.join(', ') : `${cards.length} cards`;\n generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);\n } else {\n generatorSummaries.push(`${genName}: 0 cards`);\n }\n });\n logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(' | ')}`);\n\n // Group by cardId, tracking the weight of the generator that produced each instance\n type WeightedResult = { card: WeightedCard; weight: number };\n const byCardId = new Map<string, WeightedResult[]>();\n\n results.forEach((cards, index) => {\n // Access learnable weight if available\n const gen = this.generators[index] as unknown as ContentNavigator;\n\n // Determine effective weight\n let weight = gen.learnable?.weight ?? 1.0;\n let deviation: number | undefined;\n\n if (gen.learnable && !gen.staticWeight && context.orchestration) {\n // Access strategyId (protected field) via type assertion\n const strategyId = (gen as any).strategyId;\n if (strategyId) {\n weight = context.orchestration.getEffectiveWeight(strategyId, gen.learnable);\n deviation = context.orchestration.getDeviation(strategyId);\n }\n }\n\n for (const card of cards) {\n // Record effective weight in provenance for transparency\n if (card.provenance.length > 0) {\n card.provenance[0].effectiveWeight = weight;\n card.provenance[0].deviation = deviation;\n }\n\n const existing = byCardId.get(card.cardId) || [];\n existing.push({ card, weight });\n byCardId.set(card.cardId, existing);\n }\n });\n\n // Aggregate scores\n const merged: WeightedCard[] = [];\n for (const [, items] of byCardId) {\n const cards = items.map((i) => i.card);\n const aggregatedScore = this.aggregateScores(items);\n const finalScore = Math.min(1.0, aggregatedScore); // Clamp to [0, 1]\n\n // Merge provenance from all generators that produced this card\n const mergedProvenance = cards.flatMap((c) => c.provenance);\n\n // Determine action based on whether score changed\n const initialScore = cards[0].score;\n const action =\n finalScore > initialScore ? 'boosted' : finalScore < initialScore ? 'penalized' : 'passed';\n\n // Build reason explaining the aggregation\n const reason = this.buildAggregationReason(items, finalScore);\n\n // Append composite provenance entry\n merged.push({\n ...cards[0],\n score: finalScore,\n provenance: [\n ...mergedProvenance,\n {\n strategy: 'composite',\n strategyName: 'Composite Generator',\n strategyId: 'COMPOSITE_GENERATOR',\n action,\n score: finalScore,\n reason,\n },\n ],\n });\n }\n\n // Sort by score descending and limit\n return merged.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n\n /**\n * Build human-readable reason for score aggregation.\n */\n private buildAggregationReason(\n items: { card: WeightedCard; weight: number }[],\n finalScore: number\n ): string {\n const cards = items.map((i) => i.card);\n const count = cards.length;\n const scores = cards.map((c) => c.score.toFixed(2)).join(', ');\n\n if (count === 1) {\n const weightMsg =\n Math.abs(items[0].weight - 1.0) > 0.001 ? ` (w=${items[0].weight.toFixed(2)})` : '';\n return `Single generator, score ${finalScore.toFixed(2)}${weightMsg}`;\n }\n\n const strategies = cards.map((c) => c.provenance[0]?.strategy || 'unknown').join(', ');\n\n switch (this.aggregationMode) {\n case AggregationMode.MAX:\n return `Max of ${count} generators (${strategies}): scores [${scores}] → ${finalScore.toFixed(2)}`;\n\n case AggregationMode.AVERAGE:\n return `Weighted Avg of ${count} generators (${strategies}): scores [${scores}] → ${finalScore.toFixed(2)}`;\n\n case AggregationMode.FREQUENCY_BOOST: {\n // Recalculate basic weighted avg for display\n const totalWeight = items.reduce((sum, i) => sum + i.weight, 0);\n const weightedSum = items.reduce((sum, i) => sum + i.card.score * i.weight, 0);\n const avg = totalWeight > 0 ? weightedSum / totalWeight : 0;\n\n const boost = 1 + FREQUENCY_BOOST_FACTOR * (count - 1);\n return `Frequency boost from ${count} generators (${strategies}): w-avg ${avg.toFixed(2)} × ${boost.toFixed(2)} → ${finalScore.toFixed(2)}`;\n }\n\n default:\n return `Aggregated from ${count} generators: ${finalScore.toFixed(2)}`;\n }\n }\n\n /**\n * Aggregate scores from multiple generators for the same card.\n */\n private aggregateScores(items: { card: WeightedCard; weight: number }[]): number {\n const scores = items.map((i) => i.card.score);\n\n switch (this.aggregationMode) {\n case AggregationMode.MAX:\n return Math.max(...scores);\n\n case AggregationMode.AVERAGE: {\n const totalWeight = items.reduce((sum, i) => sum + i.weight, 0);\n if (totalWeight === 0) return 0;\n const weightedSum = items.reduce((sum, i) => sum + i.card.score * i.weight, 0);\n return weightedSum / totalWeight;\n }\n\n case AggregationMode.FREQUENCY_BOOST: {\n const totalWeight = items.reduce((sum, i) => sum + i.weight, 0);\n const weightedSum = items.reduce((sum, i) => sum + i.card.score * i.weight, 0);\n const avg = totalWeight > 0 ? weightedSum / totalWeight : 0;\n\n const frequencyBoost = 1 + FREQUENCY_BOOST_FACTOR * (items.length - 1);\n return avg * frequencyBoost;\n }\n\n default:\n return scores[0];\n }\n }\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport { toCourseElo } from '@vue-skuilder/common';\nimport type { QualifiedCardID } from '../..';\nimport type { CardGenerator, GeneratorContext } from './types';\nimport { logger } from '@db/util/logger';\n\n// ============================================================================\n// ELO NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that selects new cards based on ELO proximity.\n//\n// Cards closer to the user's skill level (ELO) receive higher scores.\n// This ensures learners see content matched to their current ability.\n//\n// NOTE: This generator only handles NEW cards. Reviews are handled by\n// SRSNavigator. Use CompositeGenerator to combine both.\n//\n// ============================================================================\n\n/**\n * A navigation strategy that scores new cards by ELO proximity.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Higher scores indicate better ELO match:\n * - Cards at user's ELO level score highest\n * - Score decreases with ELO distance\n *\n * Only returns new cards - use SRSNavigator for reviews.\n */\nexport default class ELONavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData?: { name: string; _id: string }\n // The ELO strategy is non-parameterized.\n //\n // It instead relies on existing meta data from the course and user with respect to\n // ELO scores - it uses those to select cards matched to user skill level.\n ) {\n super(user, course, strategyData as any);\n this.name = strategyData?.name || 'ELO';\n }\n\n /**\n * Get new cards with suitability scores based on ELO distance.\n *\n * Cards closer to user's ELO get higher scores.\n * Score formula: max(0, 1 - distance / 500)\n *\n * NOTE: This generator only handles NEW cards. Reviews are handled by\n * SRSNavigator. Use CompositeGenerator to combine both.\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param context - Optional GeneratorContext (used when called via Pipeline)\n */\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<WeightedCard[]> {\n // Determine user ELO - from context if available, otherwise fetch\n let userGlobalElo: number;\n if (context?.userElo !== undefined) {\n userGlobalElo = context.userElo;\n } else {\n const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n userGlobalElo = userElo.global.score;\n }\n\n const activeCards = await this.user.getActiveCards();\n const newCards = (\n await this.course.getCardsCenteredAtELO(\n { limit, elo: 'user' },\n (c: QualifiedCardID) => !activeCards.some((ac) => c.cardID === ac.cardID)\n )\n ).map((c) => ({ ...c, status: 'new' as const }));\n\n // Get ELO data for all cards in one batch\n const cardIds = newCards.map((c) => c.cardID);\n const cardEloData = await this.course.getCardEloData(cardIds);\n\n // Score new cards by ELO distance, then apply weighted sampling without\n // replacement using the Efraimidis-Spirakis (A-Res) algorithm:\n //\n // key = U ^ (1 / rawScore) where U ~ Uniform(0, 1)\n //\n // Sorting by key descending produces a weighted random sample: high-score\n // cards are still preferred, but cards with equal scores are shuffled\n // uniformly rather than deterministically. This prevents the same failed\n // cards from looping back every session when many cards share similar ELO.\n //\n // Edge case: rawScore=0 → key=0, never selected (correct exclusion).\n const scored: WeightedCard[] = newCards.map((c, i) => {\n const cardElo = cardEloData[i]?.global?.score ?? 1000;\n const distance = Math.abs(cardElo - userGlobalElo);\n const rawScore = Math.max(0, 1 - distance / 500);\n const samplingKey = rawScore > 0 ? Math.random() ** (1 / rawScore) : 0;\n\n return {\n cardId: c.cardID,\n courseId: c.courseID,\n score: samplingKey,\n provenance: [\n {\n strategy: 'elo',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-ELO-default',\n action: 'generated',\n score: samplingKey,\n reason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userGlobalElo)}), raw ${rawScore.toFixed(3)}, key ${samplingKey.toFixed(3)}`,\n },\n ],\n };\n });\n\n // Sort by sampling key descending (weighted sample without replacement)\n scored.sort((a, b) => b.score - a.score);\n\n const result = scored.slice(0, limit);\n\n // Log summary for transparency\n if (result.length > 0) {\n const topScores = result.slice(0, 3).map((c) => c.score.toFixed(2)).join(', ');\n logger.info(\n `[ELO] Course ${this.course.getCourseID()}: ${result.length} new cards (top scores: ${topScores})`\n );\n } else {\n logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);\n }\n\n return result;\n }\n}\n","// Generator types and interfaces\nexport type { CardGenerator, GeneratorContext, CardGeneratorFactory } from './types';\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardGenerator, GeneratorContext } from './types';\nimport { logger } from '@db/util/logger';\n\n// ============================================================================\n// PRESCRIBED CARDS GENERATOR\n// ============================================================================\n//\n// A generator that always emits a configured list of card IDs at score 1.0.\n//\n// Use case: Cold-start curriculum bootstrapping. Ensures critical cards\n// (e.g., intro-s, early WS exercises) are always in the candidate set\n// regardless of ELO proximity sampling. Filters (hierarchy, priority)\n// still run — cards whose utility has expired get penalized normally\n// and drop out of the top-N selection.\n//\n// Config format:\n// { \"cardIds\": [\"c-intro-s-S\", \"c-ws-sit-abc123\", ...] }\n//\n// ============================================================================\n\ninterface PrescribedConfig {\n cardIds: string[];\n}\n\nexport default class PrescribedCardsGenerator extends ContentNavigator implements CardGenerator {\n name: string;\n private config: PrescribedConfig;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.name = strategyData.name || 'Prescribed Cards';\n\n try {\n const parsed = JSON.parse(strategyData.serializedData);\n this.config = { cardIds: parsed.cardIds || [] };\n } catch {\n this.config = { cardIds: [] };\n }\n\n logger.debug(\n `[Prescribed] Initialized with ${this.config.cardIds.length} prescribed cards`\n );\n }\n\n async getWeightedCards(limit: number, _context?: GeneratorContext): Promise<WeightedCard[]> {\n if (this.config.cardIds.length === 0) {\n return [];\n }\n\n const courseId = this.course.getCourseID();\n\n // Filter out cards the user has already interacted with\n const activeCards = await this.user.getActiveCards();\n const activeIds = new Set(activeCards.map((ac) => ac.cardID));\n const eligibleIds = this.config.cardIds.filter((id) => !activeIds.has(id));\n\n if (eligibleIds.length === 0) {\n logger.debug('[Prescribed] All prescribed cards already active, returning empty');\n return [];\n }\n\n // Emit at score 1.0 — CompositeGenerator deduplicates, and if ELO\n // also surfaces the same card, the composite picks the higher score.\n const cards: WeightedCard[] = eligibleIds.slice(0, limit).map((cardId) => ({\n cardId,\n courseId,\n score: 1.0,\n provenance: [\n {\n strategy: 'prescribed',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-prescribed',\n action: 'generated' as const,\n score: 1.0,\n reason: `Prescribed card (${eligibleIds.length} eligible of ${this.config.cardIds.length} configured)`,\n },\n ],\n }));\n\n logger.info(\n `[Prescribed] Emitting ${cards.length} cards (${eligibleIds.length} eligible, ${activeIds.size} already active)`\n );\n\n return cards;\n }\n}\n","import moment from 'moment';\nimport type { ScheduledCard } from '../../types/user';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardGenerator, GeneratorContext } from './types';\nimport { logger } from '@db/util/logger';\n\n// ============================================================================\n// SRS NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that scores review cards by urgency.\n//\n// Urgency is determined by three factors:\n// 1. Overdueness - how far past the scheduled review time\n// 2. Interval recency - shorter scheduled intervals indicate \"novel content in progress\"\n// 3. Backlog pressure - when too many reviews pile up, urgency increases globally\n//\n// A card with a 3-day interval that's 2 days overdue is more urgent than a card\n// with a 6-month interval that's 2 days overdue. The shorter interval represents\n// active learning at higher resolution.\n//\n// DESIGN PHILOSOPHY: SRS scheduling times are \"eligibility dates\" not hard \"due dates\".\n// When a card becomes eligible, it is \"okish\" to review now, but reviewing a little\n// later may be optimal. We don't aim to always beat review queues to zero (death spiral),\n// but rather maintain a healthy backlog of eligible reviews so the system can gracefully\n// handle usage upticks or breaks.\n//\n// This navigator only handles reviews - it does not generate new cards.\n// For new cards, use ELONavigator or another generator via CompositeGenerator.\n//\n// ============================================================================\n\n/**\n * Default healthy backlog size.\n * When due reviews exceed this, backlog pressure kicks in.\n * Can be overridden via strategy config.\n */\nconst DEFAULT_HEALTHY_BACKLOG = 20;\n\n/**\n * Maximum backlog pressure contribution to score.\n * At 3x healthy backlog, pressure maxes out.\n */\nconst MAX_BACKLOG_PRESSURE = 0.5;\n\n/**\n * Configuration for the SRS strategy.\n */\nexport interface SRSConfig {\n /**\n * Target \"healthy\" backlog size.\n * When due reviews exceed this, urgency increases globally.\n * Default: 20\n */\n healthyBacklog?: number;\n}\n\n/**\n * A navigation strategy that scores review cards by urgency.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Higher scores indicate more urgent reviews:\n * - Cards that are more overdue (relative to their interval) score higher\n * - Cards with shorter intervals (recent learning) score higher\n * - When backlog exceeds \"healthy\" threshold, all reviews get urgency boost\n *\n * Only returns cards that are actually due (reviewTime has passed).\n * Does not generate new cards - use with CompositeGenerator for mixed content.\n */\nexport default class SRSNavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n /** Healthy backlog threshold - when exceeded, backlog pressure kicks in */\n private healthyBacklog: number;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData?: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData as ContentNavigationStrategyData);\n this.name = strategyData?.name || 'SRS';\n\n // Parse config from serializedData if available\n const config = this.parseConfig(strategyData?.serializedData);\n this.healthyBacklog = config.healthyBacklog ?? DEFAULT_HEALTHY_BACKLOG;\n }\n\n /**\n * Parse configuration from serialized JSON.\n */\n private parseConfig(serializedData?: string): SRSConfig {\n if (!serializedData) return {};\n try {\n return JSON.parse(serializedData) as SRSConfig;\n } catch {\n logger.warn('[SRS] Failed to parse strategy config, using defaults');\n return {};\n }\n }\n\n /**\n * Get review cards scored by urgency.\n *\n * Score formula combines:\n * - Relative overdueness: hoursOverdue / intervalHours\n * - Interval recency: exponential decay favoring shorter intervals\n * - Backlog pressure: boost when due reviews exceed healthy threshold\n *\n * Cards not yet due are excluded (not scored as 0).\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param _context - Optional GeneratorContext (currently unused, but required for interface)\n */\n async getWeightedCards(limit: number, _context?: GeneratorContext): Promise<WeightedCard[]> {\n if (!this.user || !this.course) {\n throw new Error('SRSNavigator requires user and course to be set');\n }\n\n const courseId = this.course.getCourseID();\n const reviews = await this.user.getPendingReviews(courseId);\n const now = moment.utc();\n\n // Filter to only cards that are actually due\n const dueReviews = reviews.filter((r) => now.isAfter(moment.utc(r.reviewTime)));\n\n // Compute backlog pressure - applies globally to all reviews\n const backlogPressure = this.computeBacklogPressure(dueReviews.length);\n\n // Log review status for transparency\n if (dueReviews.length > 0) {\n const pressureNote =\n backlogPressure > 0\n ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]`\n : ` [healthy backlog]`;\n logger.info(\n `[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`\n );\n } else if (reviews.length > 0) {\n // Reviews exist but none are due yet - show when next one is due\n const sortedByDue = [...reviews].sort((a, b) =>\n moment.utc(a.reviewTime).diff(moment.utc(b.reviewTime))\n );\n const nextDue = sortedByDue[0];\n const nextDueTime = moment.utc(nextDue.reviewTime);\n const untilDue = moment.duration(nextDueTime.diff(now));\n const untilDueStr =\n untilDue.asHours() < 1\n ? `${Math.round(untilDue.asMinutes())}m`\n : untilDue.asHours() < 24\n ? `${Math.round(untilDue.asHours())}h`\n : `${Math.round(untilDue.asDays())}d`;\n logger.info(\n `[SRS] Course ${courseId}: 0 reviews due now (${reviews.length} scheduled, next in ${untilDueStr})`\n );\n } else {\n logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);\n }\n\n const scored = dueReviews.map((review) => {\n const { score, reason } = this.computeUrgencyScore(review, now, backlogPressure);\n\n return {\n cardId: review.cardId,\n courseId: review.courseId,\n score,\n reviewID: review._id,\n provenance: [\n {\n strategy: 'srs',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-SRS-default',\n action: 'generated' as const,\n score,\n reason,\n },\n ],\n };\n });\n\n // Sort by score descending and limit\n return scored.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n\n /**\n * Compute backlog pressure based on number of due reviews.\n *\n * Backlog pressure is 0 when at or below healthy threshold,\n * and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.\n *\n * Examples (with default healthyBacklog=20):\n * - 10 due reviews → 0.00 (healthy)\n * - 20 due reviews → 0.00 (at threshold)\n * - 40 due reviews → 0.25 (2x threshold)\n * - 60 due reviews → 0.50 (3x threshold, maxed)\n *\n * @param dueCount - Number of reviews currently due\n * @returns Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)\n */\n private computeBacklogPressure(dueCount: number): number {\n if (dueCount <= this.healthyBacklog) {\n return 0;\n }\n\n // Linear increase: at 2x healthy, pressure = 0.25; at 3x, pressure = 0.50\n const excess = dueCount - this.healthyBacklog;\n const pressure = (excess / this.healthyBacklog) * (MAX_BACKLOG_PRESSURE / 2);\n\n return Math.min(MAX_BACKLOG_PRESSURE, pressure);\n }\n\n /**\n * Compute urgency score for a review card.\n *\n * Three factors:\n * 1. Relative overdueness = hoursOverdue / intervalHours\n * - 2 days overdue on 3-day interval = 0.67 (urgent)\n * - 2 days overdue on 180-day interval = 0.01 (not urgent)\n *\n * 2. Interval recency factor = 0.3 + 0.7 * exp(-intervalHours / 720)\n * - 24h interval → ~1.0 (very recent learning)\n * - 30 days (720h) → ~0.56\n * - 180 days → ~0.30\n *\n * 3. Backlog pressure = global boost when review backlog exceeds healthy threshold\n * - At healthy backlog: 0\n * - At 2x healthy: +0.25\n * - At 3x+ healthy: +0.50 (max)\n *\n * Combined: base 0.5 + (urgency factors * 0.45) + backlog pressure\n * Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)\n *\n * @param review - The scheduled card to score\n * @param now - Current time\n * @param backlogPressure - Pre-computed backlog pressure (0 to 0.5)\n */\n private computeUrgencyScore(\n review: ScheduledCard,\n now: moment.Moment,\n backlogPressure: number\n ): { score: number; reason: string } {\n const scheduledAt = moment.utc(review.scheduledAt);\n const due = moment.utc(review.reviewTime);\n\n // Interval = time between scheduling and due date (minimum 1 hour to avoid division issues)\n const intervalHours = Math.max(1, due.diff(scheduledAt, 'hours'));\n const hoursOverdue = now.diff(due, 'hours');\n\n // Relative overdueness: how late relative to the interval\n const relativeOverdue = hoursOverdue / intervalHours;\n\n // Interval recency factor: shorter intervals = more urgent\n // Exponential decay with 720h (30 days) as the characteristic time\n const recencyFactor = 0.3 + 0.7 * Math.exp(-intervalHours / 720);\n\n // Combined urgency: weighted average of relative overdue and recency\n // Clamp relative overdue contribution to [0, 1] to avoid runaway scores\n const overdueContribution = Math.min(1.0, Math.max(0, relativeOverdue));\n const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;\n\n // Final score: base 0.5 + urgency contribution + backlog pressure\n // Uncapped at 1.0 (no 0.95 ceiling) - allows high-urgency reviews to compete with new cards\n const baseScore = 0.5 + urgency * 0.45;\n const score = Math.min(1.0, baseScore + backlogPressure);\n\n // Build reason string with all contributing factors\n const reasonParts = [\n `${Math.round(hoursOverdue)}h overdue`,\n `interval: ${Math.round(intervalHours)}h`,\n `relative: ${relativeOverdue.toFixed(2)}`,\n `recency: ${recencyFactor.toFixed(2)}`,\n ];\n\n if (backlogPressure > 0) {\n reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);\n }\n\n reasonParts.push('review');\n\n const reason = reasonParts.join(', ');\n\n return { score, reason };\n }\n}","import type { WeightedCard } from '../index';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport type { OrchestrationContext } from '../../orchestration';\n\n// ============================================================================\n// CARD GENERATOR INTERFACE\n// ============================================================================\n//\n// Generators produce candidate cards with initial scores.\n// They are the \"source\" stage of a navigation pipeline.\n//\n// Examples: ELO (skill proximity), SRS (review scheduling)\n//\n// Generators differ from filters:\n// - Generators: produce candidates from DB queries, assign initial scores\n// - Filters: transform existing candidates, adjust scores with multipliers\n//\n// The Pipeline class orchestrates: Generator → Filter₁ → Filter₂ → ... → Results\n//\n// ============================================================================\n\n/**\n * Context available to generators when producing candidates.\n *\n * Built once per getWeightedCards() call by the Pipeline.\n */\nexport interface GeneratorContext {\n /** User database interface */\n user: UserDBInterface;\n\n /** Course database interface */\n course: CourseDBInterface;\n\n /** User's global ELO score for this course */\n userElo: number;\n\n /** Orchestration context for evolutionary weighting */\n orchestration?: OrchestrationContext;\n\n // Future extensions:\n // - user's tag-level ELO data\n // - course config\n // - session state (cards already seen this session)\n}\n\n/**\n * A generator that produces candidate cards with initial scores.\n *\n * Generators are the \"source\" stage of a navigation pipeline.\n * They query the database for eligible cards and assign initial\n * suitability scores based on their strategy (ELO proximity,\n * review urgency, fixed order, etc.).\n *\n * ## Implementation Guidelines\n *\n * 1. **Create provenance**: Each card should have a provenance entry\n * with action='generated' documenting why it was selected.\n *\n * 2. **Score semantics**: Higher scores = more suitable for presentation.\n * Scores should be in [0, 1] range for composability.\n *\n * 3. **Limit handling**: Respect the limit parameter, but may over-fetch\n * internally if needed for scoring accuracy.\n *\n * 4. **Sort before returning**: Return cards sorted by score descending.\n *\n * ## Example Implementation\n *\n * ```typescript\n * const myGenerator: CardGenerator = {\n * name: 'My Generator',\n * async getWeightedCards(limit, context) {\n * const candidates = await fetchCandidates(context.course, limit);\n * return candidates.map(c => ({\n * cardId: c.id,\n * courseId: context.course.getCourseID(),\n * score: computeScore(c, context),\n * provenance: [{\n * strategy: 'myGenerator',\n * strategyName: 'My Generator',\n * strategyId: 'MY_GENERATOR',\n * action: 'generated',\n * score: computeScore(c, context),\n * reason: 'Explanation of selection'\n * }]\n * }));\n * }\n * };\n * ```\n */\nexport interface CardGenerator {\n /** Human-readable name for this generator */\n name: string;\n\n /**\n * Produce candidate cards with initial scores.\n *\n * @param limit - Maximum number of cards to return\n * @param context - Shared context (user, course, userElo, etc.)\n * @returns Cards sorted by score descending, with provenance\n */\n getWeightedCards(limit: number, context: GeneratorContext): Promise<WeightedCard[]>;\n}\n\n/**\n * Factory function type for creating generators from configuration.\n *\n * Used by PipelineAssembler to instantiate generators from strategy documents.\n */\nexport type CardGeneratorFactory<TConfig = unknown> = (config: TConfig) => CardGenerator;\n","import { DocType, SkuilderCourseData } from './types-legacy';\nimport type { DocTypePrefixes } from './types-legacy';\n\n/**\n * Configuration for an evolutionarily-weighted strategy.\n *\n * This structure tracks the \"learned\" weight of a strategy, representing the\n * system's confidence in its utility.\n *\n * - weight: The best-known multiplier (peak of the bell curve)\n * - confidence: How certain we are (inverse of variance / spread)\n * - sampleSize: How many data points informed this weight\n */\nexport interface LearnableWeight {\n /** The current best estimate of optimal weight (multiplier) */\n weight: number;\n /** Confidence in this weight (0-1). Higher = narrower exploration spread. */\n confidence: number;\n /** Number of outcome observations that contributed to this weight */\n sampleSize: number;\n}\n\nexport const DEFAULT_LEARNABLE_WEIGHT: LearnableWeight = {\n weight: 1.0,\n confidence: 0.1, // Low confidence initially = wide exploration\n sampleSize: 0,\n};\n\n/**\n *\n */\nexport interface ContentNavigationStrategyData extends SkuilderCourseData {\n _id: `${typeof DocTypePrefixes[DocType.NAVIGATION_STRATEGY]}-${string}`;\n docType: DocType.NAVIGATION_STRATEGY;\n name: string;\n description: string;\n /**\n The name of the class that implements the navigation strategy at runtime.\n */\n implementingClass: string;\n\n /**\n A representation of the strategy's parameterization - to be deserialized\n by the implementing class's constructor at runtime.\n */\n serializedData: string;\n\n /**\n * Evolutionary weighting configuration.\n * If present, the strategy's influence is scaled by this weight.\n * If omitted, weight defaults to 1.0.\n */\n learnable?: LearnableWeight;\n\n /**\n * If true, the weight is applied exactly as configured, without\n * per-user deviation. Used for manual tuning or A/B testing.\n */\n staticWeight?: boolean;\n}\n","import { CardFilter, FilterContext } from './types';\nimport { WeightedCard } from '../index';\nimport { LearnableWeight, DEFAULT_LEARNABLE_WEIGHT } from '../../types/contentNavigationStrategy';\n\n/**\n * Wraps a CardFilter to apply evolutionary weighting to its effects.\n *\n * If a filter applies a multiplier M (score * M), and the strategy has\n * an effective weight W, the final multiplier becomes M^W.\n *\n * - W=1.0: Original behavior\n * - W>1.0: Amplifies the filter's opinion (stronger boost/penalty)\n * - W<1.0: Dampens the filter's opinion (weaker boost/penalty)\n * - W=0.0: Nullifies the filter (identity)\n *\n * This wrapper handles the math of scaling the filter's impact and updating\n * the provenance trail with the effective weight used.\n */\nexport class WeightedFilter implements CardFilter {\n public name: string;\n private inner: CardFilter;\n private learnable: LearnableWeight;\n private staticWeight: boolean;\n private strategyId?: string;\n\n constructor(\n inner: CardFilter,\n learnable: LearnableWeight = DEFAULT_LEARNABLE_WEIGHT,\n staticWeight: boolean = false,\n strategyId?: string\n ) {\n this.inner = inner;\n this.name = inner.name;\n this.learnable = learnable;\n this.staticWeight = staticWeight;\n this.strategyId = strategyId;\n }\n\n /**\n * Apply the inner filter, then scale its effect by the configured weight.\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n // ========================================================================\n // 1. DETERMINE EFFECTIVE WEIGHT\n // ========================================================================\n \n // Determine effective weight using orchestration context if available\n let effectiveWeight = this.learnable.weight;\n let deviation: number | undefined;\n\n if (!this.staticWeight && context.orchestration) {\n // ContentNavigator instances have a strategyId property (protected/private)\n // We assume inner filter is a ContentNavigator or has a strategyId property.\n // Fallback to name if not present.\n const strategyId = this.strategyId || (this.inner as any).strategyId || this.name;\n effectiveWeight = context.orchestration.getEffectiveWeight(strategyId, this.learnable);\n deviation = context.orchestration.getDeviation(strategyId);\n }\n\n // Optimization: If weight is 1.0, the scaling is an identity operation.\n // Just run the inner filter directly.\n if (Math.abs(effectiveWeight - 1.0) < 0.001) {\n return this.inner.transform(cards, context);\n }\n\n // ========================================================================\n // 2. CAPTURE STATE BEFORE FILTER\n // ========================================================================\n \n // We need original scores to calculate what the filter did (M = new/old)\n const originalScores = new Map<string, number>();\n for (const card of cards) {\n originalScores.set(card.cardId, card.score);\n }\n\n // ========================================================================\n // 3. RUN INNER FILTER\n // ========================================================================\n \n const transformedCards = await this.inner.transform(cards, context);\n\n // ========================================================================\n // 4. APPLY WEIGHT SCALING\n // ========================================================================\n \n return transformedCards.map((card) => {\n const originalScore = originalScores.get(card.cardId);\n\n // Edge cases where we can't or shouldn't scale:\n // - Original score missing (shouldn't happen)\n // - Original score 0 (was already excluded)\n // - New score 0 (filter excluded it / vetoed) - we treat vetoes as absolute\n if (originalScore === undefined || originalScore === 0 || card.score === 0) {\n return card;\n }\n\n // Calculate raw effect multiplier: M = new / old\n const rawEffect = card.score / originalScore;\n\n // If filter didn't change this card, nothing to scale\n if (Math.abs(rawEffect - 1.0) < 0.0001) {\n return card;\n }\n\n // Apply weight: scaled = M ^ W\n // Example: 0.5 penalty ^ 2.0 weight = 0.25 (stronger penalty)\n // Example: 0.5 penalty ^ 0.5 weight = 0.707 (weaker penalty)\n const weightedEffect = Math.pow(rawEffect, effectiveWeight);\n const newScore = originalScore * weightedEffect;\n\n // Update provenance\n // The inner filter just added the last entry. We need to update it\n // to reflect the weighted score and record the effective weight.\n const lastProvIndex = card.provenance.length - 1;\n const lastProv = card.provenance[lastProvIndex];\n\n if (lastProv) {\n const updatedProvenance = [...card.provenance];\n updatedProvenance[lastProvIndex] = {\n ...lastProv,\n score: newScore,\n effectiveWeight: effectiveWeight,\n deviation: deviation,\n // We can optionally append to the reason, but the structured field is key\n };\n\n return {\n ...card,\n score: newScore,\n provenance: updatedProvenance,\n };\n }\n\n // Fallback if no provenance found (rare)\n return {\n ...card,\n score: newScore,\n };\n });\n }\n}","import type { WeightedCard } from '../index';\nimport type { CardFilter, FilterContext } from './types';\n\n// ============================================================================\n// ELO DISTANCE FILTER\n// ============================================================================\n//\n// Penalizes cards that are far from the user's current ELO using a smooth curve.\n//\n// This filter addresses cross-strategy coordination:\n// - SRS generates reviews based on scheduling\n// - But some scheduled cards may be \"below\" the user's current level\n// - Or \"above\" (shouldn't happen often, but possible)\n//\n// By applying ELO distance penalties, we can:\n// - Deprioritize reviews the user has \"moved beyond\"\n// - Deprioritize cards that are too hard for current skill level\n//\n// The penalty curve is smooth (no discontinuities) using a Gaussian-like decay.\n//\n// ============================================================================\n\n/**\n * Configuration for the ELO distance filter.\n */\nexport interface EloDistanceConfig {\n /**\n * The ELO distance at which the multiplier is ~0.6 (one standard deviation).\n * Default: 200 ELO points.\n *\n * - At distance 0: multiplier ≈ 1.0\n * - At distance = halfLife: multiplier ≈ 0.6\n * - At distance = 2 * halfLife: multiplier ≈ 0.37\n * - At distance = 3 * halfLife: multiplier ≈ 0.22\n */\n halfLife?: number;\n\n /**\n * Minimum multiplier (floor) to prevent scores from going too low.\n * Default: 0.3\n */\n minMultiplier?: number;\n\n /**\n * Maximum multiplier (ceiling). Usually 1.0 (no boost for close cards).\n * Default: 1.0\n */\n maxMultiplier?: number;\n}\n\nconst DEFAULT_HALF_LIFE = 200;\nconst DEFAULT_MIN_MULTIPLIER = 0.3;\nconst DEFAULT_MAX_MULTIPLIER = 1.0;\n\n/**\n * Compute the multiplier for a given ELO distance using Gaussian decay.\n *\n * Formula: minMultiplier + (maxMultiplier - minMultiplier) * exp(-(distance/halfLife)^2)\n *\n * This produces a smooth bell curve centered at distance=0:\n * - At distance 0: multiplier = maxMultiplier (1.0)\n * - As distance increases: multiplier smoothly decays toward minMultiplier\n * - No discontinuities or sudden jumps\n */\nfunction computeMultiplier(\n distance: number,\n halfLife: number,\n minMultiplier: number,\n maxMultiplier: number\n): number {\n // Gaussian decay: exp(-(d/h)^2)\n const normalizedDistance = distance / halfLife;\n const decay = Math.exp(-(normalizedDistance * normalizedDistance));\n\n // Scale between min and max\n return minMultiplier + (maxMultiplier - minMultiplier) * decay;\n}\n\n/**\n * Create an ELO distance filter.\n *\n * Penalizes cards that are far from the user's current ELO level\n * using a smooth Gaussian decay curve. No discontinuities.\n *\n * @param config - Optional configuration for the decay curve\n * @returns A CardFilter that applies ELO distance penalties\n */\nexport function createEloDistanceFilter(config?: EloDistanceConfig): CardFilter {\n const halfLife = config?.halfLife ?? DEFAULT_HALF_LIFE;\n const minMultiplier = config?.minMultiplier ?? DEFAULT_MIN_MULTIPLIER;\n const maxMultiplier = config?.maxMultiplier ?? DEFAULT_MAX_MULTIPLIER;\n\n return {\n name: 'ELO Distance Filter',\n\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n const { course, userElo } = context;\n\n // Batch fetch ELO data for all cards\n const cardIds = cards.map((c) => c.cardId);\n const cardElos = await course.getCardEloData(cardIds);\n\n return cards.map((card, i) => {\n const cardElo = cardElos[i]?.global?.score ?? 1000;\n const distance = Math.abs(cardElo - userElo);\n const multiplier = computeMultiplier(distance, halfLife, minMultiplier, maxMultiplier);\n const newScore = card.score * multiplier;\n\n const action = multiplier < maxMultiplier - 0.01 ? 'penalized' : 'passed';\n\n return {\n ...card,\n score: newScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'eloDistance',\n strategyName: 'ELO Distance Filter',\n strategyId: 'ELO_DISTANCE_FILTER',\n action,\n score: newScore,\n reason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userElo)}) → ${multiplier.toFixed(2)}x`,\n },\n ],\n };\n });\n },\n };\n}\n\n// Export defaults for testing\nexport { DEFAULT_HALF_LIFE, DEFAULT_MIN_MULTIPLIER, DEFAULT_MAX_MULTIPLIER };\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardFilter, FilterContext } from './types';\nimport { toCourseElo } from '@vue-skuilder/common';\nimport { logger } from '../../../util/logger';\n\n/**\n * A single prerequisite requirement for a tag.\n * Each prerequisite refers to one tag with its own mastery threshold.\n */\ninterface TagPrerequisite {\n /** The tag that must be mastered */\n tag: string;\n /** Thresholds for considering this prerequisite tag \"mastered\" */\n masteryThreshold?: {\n /** Minimum ELO score for mastery. If not set, uses avgElo comparison */\n minElo?: number;\n /** Minimum interaction count (default: 3) */\n minCount?: number;\n };\n /**\n * Score multiplier applied to cards that carry this prereq tag while\n * the gate is still closed. Steers the pipeline toward cards that help\n * unlock the gated content. Falls away once the prereq is met.\n *\n * Example: `preReqBoost: 1.3` gives a 30% score increase to cards\n * tagged `gpc:expose:t-T` while `gpc:intro:t-T` is still locked.\n */\n preReqBoost?: number;\n}\n\n/**\n * Configuration for the HierarchyDefinition strategy\n */\nexport interface HierarchyConfig {\n /** Map of tag ID to its list of prerequisites (each with individual thresholds) */\n prerequisites: {\n [tagId: string]: TagPrerequisite[];\n };\n}\n\nconst DEFAULT_MIN_COUNT = 3;\n\n/**\n * A filter strategy that gates cards based on prerequisite mastery.\n *\n * Cards are locked until the user masters all prerequisite tags.\n * Locked cards receive score * 0.05 (strong penalty, not hard filter).\n *\n * Mastery is determined by:\n * - User's ELO for the tag exceeds threshold (or avgElo if not specified)\n * - User has minimum interaction count with the tag\n *\n * Tags with no prerequisites are always unlocked.\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class HierarchyDefinitionNavigator extends ContentNavigator implements CardFilter {\n private config: HierarchyConfig;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.config = this.parseConfig(strategyData.serializedData);\n this.name = strategyData.name || 'Hierarchy Definition';\n }\n\n private parseConfig(serializedData: string): HierarchyConfig {\n try {\n const parsed = JSON.parse(serializedData);\n return {\n prerequisites: parsed.prerequisites || {},\n };\n } catch {\n // Return safe defaults if parsing fails\n return {\n prerequisites: {},\n };\n }\n }\n\n /**\n * Check if a specific prerequisite is satisfied\n */\n private isPrerequisiteMet(\n prereq: TagPrerequisite,\n userTagElo: { score: number; count: number } | undefined,\n userGlobalElo: number\n ): boolean {\n if (!userTagElo) return false;\n\n const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;\n if (userTagElo.count < minCount) return false;\n\n if (prereq.masteryThreshold?.minElo !== undefined) {\n return userTagElo.score >= prereq.masteryThreshold.minElo;\n } else if (prereq.masteryThreshold?.minCount !== undefined) {\n // Explicit minCount without minElo: count alone is sufficient.\n // The config author specified a concrete interaction threshold —\n // don't additionally require above-average ELO.\n return true;\n } else {\n // No thresholds specified at all: fall back to above-average ELO\n return userTagElo.score >= userGlobalElo;\n }\n }\n\n /**\n * Get the set of tags the user has mastered.\n * A tag is \"mastered\" if it appears as a prerequisite somewhere and meets its threshold.\n */\n private async getMasteredTags(context: FilterContext): Promise<Set<string>> {\n const mastered = new Set<string>();\n\n try {\n const courseReg = await context.user.getCourseRegDoc(context.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n\n // Collect all unique prerequisite tags and check mastery for each\n for (const prereqs of Object.values(this.config.prerequisites)) {\n for (const prereq of prereqs) {\n const tagElo = userElo.tags[prereq.tag];\n if (this.isPrerequisiteMet(prereq, tagElo, userElo.global.score)) {\n mastered.add(prereq.tag);\n }\n }\n }\n } catch {\n // If we can't get user data, return empty set (no tags mastered)\n }\n\n return mastered;\n }\n\n /**\n * Get the set of tags that are unlocked (prerequisites met)\n */\n private getUnlockedTags(masteredTags: Set<string>): Set<string> {\n const unlocked = new Set<string>();\n\n for (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {\n const allPrereqsMet = prereqs.every((prereq) => masteredTags.has(prereq.tag));\n if (allPrereqsMet) {\n unlocked.add(tagId);\n }\n }\n\n return unlocked;\n }\n\n /**\n * Check if a tag has prerequisites defined in config\n */\n private hasPrerequisites(tagId: string): boolean {\n return tagId in this.config.prerequisites;\n }\n\n /**\n * Check if a card is unlocked and generate reason.\n */\n private async checkCardUnlock(\n card: WeightedCard,\n _course: CourseDBInterface,\n unlockedTags: Set<string>,\n masteredTags: Set<string>\n ): Promise<{ isUnlocked: boolean; reason: string }> {\n try {\n // Pipeline hydrates tags before filters run\n const cardTags = card.tags ?? [];\n\n // Check each tag's prerequisite status\n const lockedTags = cardTags.filter(\n (tag) => this.hasPrerequisites(tag) && !unlockedTags.has(tag)\n );\n\n if (lockedTags.length === 0) {\n const tagList = cardTags.length > 0 ? cardTags.join(', ') : 'none';\n return {\n isUnlocked: true,\n reason: `Prerequisites met, tags: ${tagList}`,\n };\n }\n\n // Find missing prerequisites for locked tags\n const missingPrereqs = lockedTags.flatMap((tag) => {\n const prereqs = this.config.prerequisites[tag] || [];\n return prereqs.filter((p) => !masteredTags.has(p.tag)).map((p) => p.tag);\n });\n\n return {\n isUnlocked: false,\n reason: `Blocked: missing prerequisites ${missingPrereqs.join(', ')} for tags ${lockedTags.join(', ')}`,\n };\n } catch {\n // If we can't get tags, assume unlocked (fail open)\n return {\n isUnlocked: true,\n reason: 'Prerequisites check skipped (tag lookup failed)',\n };\n }\n }\n\n /**\n * Build a map of prereq tag → max configured boost for all *closed* gates.\n *\n * When a gate is closed (prereqs unmet), cards carrying that gate's prereq\n * tags get boosted — steering the pipeline toward content that helps unlock\n * the gated material. Once the gate opens, the boost disappears.\n */\n private getPreReqBoosts(\n unlockedTags: Set<string>,\n masteredTags: Set<string>\n ): Map<string, number> {\n const boosts = new Map<string, number>();\n\n for (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {\n // Only boost prereqs of closed gates\n if (unlockedTags.has(tagId)) continue;\n\n for (const prereq of prereqs) {\n if (!prereq.preReqBoost || prereq.preReqBoost <= 1.0) continue;\n // Only boost prereqs that aren't already met\n if (masteredTags.has(prereq.tag)) continue;\n\n const existing = boosts.get(prereq.tag) ?? 1.0;\n boosts.set(prereq.tag, Math.max(existing, prereq.preReqBoost));\n }\n }\n\n return boosts;\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Two effects:\n * 1. Cards with locked tags receive score * 0.05 (gating penalty)\n * 2. Cards carrying prereq tags of closed gates receive a configured\n * boost (preReqBoost), steering toward content that unlocks gates\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n // Get mastery state\n const masteredTags = await this.getMasteredTags(context);\n const unlockedTags = this.getUnlockedTags(masteredTags);\n const preReqBoosts = this.getPreReqBoosts(unlockedTags, masteredTags);\n\n // Apply prerequisite gating + prereq boosting\n const gated: WeightedCard[] = [];\n\n for (const card of cards) {\n const { isUnlocked, reason } = await this.checkCardUnlock(\n card,\n context.course,\n unlockedTags,\n masteredTags\n );\n const LOCKED_PENALTY = 0.02;\n let finalScore = isUnlocked ? card.score : card.score * LOCKED_PENALTY;\n let action: 'passed' | 'penalized' | 'boosted' = isUnlocked ? 'passed' : 'penalized';\n let finalReason = reason;\n\n // Apply prereq boost to cards that passed gating (don't boost locked cards)\n if (isUnlocked && preReqBoosts.size > 0) {\n const cardTags = card.tags ?? [];\n let maxBoost = 1.0;\n const boostedPrereqs: string[] = [];\n\n for (const tag of cardTags) {\n const boost = preReqBoosts.get(tag);\n if (boost && boost > maxBoost) {\n maxBoost = boost;\n boostedPrereqs.push(tag);\n }\n }\n\n if (maxBoost > 1.0) {\n finalScore *= maxBoost;\n action = 'boosted';\n finalReason = `${reason} | preReqBoost ×${maxBoost.toFixed(2)} for ${boostedPrereqs.join(', ')}`;\n logger.info(\n `[HierarchyDefinition] preReqBoost ×${maxBoost.toFixed(2)} applied to card ${card.cardId} via tags [${boostedPrereqs.join(', ')}] (score: ${card.score.toFixed(3)} → ${finalScore.toFixed(3)})`\n );\n }\n }\n\n gated.push({\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'hierarchyDefinition',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hierarchy',\n action,\n score: finalScore,\n reason: finalReason,\n },\n ],\n });\n }\n\n return gated;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(\n 'HierarchyDefinitionNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardFilter, FilterContext } from './types';\n\n// ============================================================================\n// USER TAG PREFERENCE FILTER\n// ============================================================================\n//\n// Allows users to personalize their learning experience by specifying:\n// - Tags to boost/penalize (score multiplied by boost factor)\n//\n// User preferences are stored in STRATEGY_STATE documents in the user's\n// database, enabling persistence across sessions and sync across devices.\n//\n// Use cases:\n// - Goal-based learning: \"I want to learn piano by ear, skip sight-reading\"\n// - Selective focus: \"I only want to practice chess endgames\"\n// - Accessibility: \"Skip text-heavy cards, prefer visual content\"\n// - Difficulty customization: \"Skip beginner content I already know\"\n//\n// ============================================================================\n\n/**\n * User's tag preference state, stored in STRATEGY_STATE document.\n *\n * This interface defines what gets persisted to the user's database.\n * UI components write to this structure, and the filter reads from it.\n *\n * ## Preferences vs Goals\n *\n * Preferences are **path constraints** — they affect HOW the user learns,\n * not WHAT they're trying to learn. Examples:\n * - \"Skip text-heavy cards\" (accessibility)\n * - \"Prefer visual content\"\n *\n * For **goal-based** filtering (defining WHAT to learn), see the separate\n * UserGoalNavigator (stub). Goals affect progress tracking and completion\n * criteria; preferences only affect card selection.\n *\n * ## Slider Semantics\n *\n * Each tag maps to a multiplier value in the `boost` record:\n * - `0` = banish/exclude (card score = 0)\n * - `0.5` = penalize by 50%\n * - `1.0` = neutral/no effect (default when tag added)\n * - `2.0` = 2x preference boost\n * - Higher values = stronger preference\n *\n * If multiple tags on a card have preferences, the maximum multiplier wins.\n */\nexport interface UserTagPreferenceState {\n /**\n * Tag-specific multipliers.\n * Maps tag name to score multiplier (0 = exclude, 1 = neutral, >1 = boost).\n */\n boost: Record<string, number>;\n\n /**\n * ISO timestamp of last update.\n * Use `moment.utc(updatedAt)` to parse into a Moment object.\n */\n updatedAt: string;\n}\n\n/**\n * A filter that applies user-configured tag preferences.\n *\n * Reads preferences from STRATEGY_STATE document in user's database.\n * If no preferences exist, passes through unchanged (no-op).\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for compatibility with dynamic loading.\n */\nexport default class UserTagPreferenceFilter extends ContentNavigator implements CardFilter {\n private _strategyData: ContentNavigationStrategyData;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this._strategyData = strategyData;\n this.name = strategyData.name || 'User Tag Preferences';\n }\n\n /**\n * Compute multiplier for a card based on its tags and user preferences.\n * Returns the maximum multiplier among all matching tags, or 1.0 if no matches.\n */\n private computeMultiplier(cardTags: string[], boostMap: Record<string, number>): number {\n const multipliers = cardTags.map((tag) => boostMap[tag]).filter((val) => val !== undefined);\n\n if (multipliers.length === 0) {\n return 1.0;\n }\n\n // Use max multiplier among matching tags\n return Math.max(...multipliers);\n }\n\n /**\n * Build human-readable reason for the filter's decision.\n */\n private buildReason(\n cardTags: string[],\n boostMap: Record<string, number>,\n multiplier: number\n ): string {\n // Find which tag(s) contributed to the multiplier\n const matchingTags = cardTags.filter((tag) => boostMap[tag] === multiplier);\n\n if (multiplier === 0) {\n return `Excluded by user preference: ${matchingTags.join(', ')} (${multiplier}x)`;\n }\n\n if (multiplier < 1.0) {\n return `Penalized by user preference: ${matchingTags.join(', ')} (${multiplier.toFixed(2)}x)`;\n }\n\n if (multiplier > 1.0) {\n return `Boosted by user preference: ${matchingTags.join(', ')} (${multiplier.toFixed(2)}x)`;\n }\n\n return 'No matching user preferences';\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply user tag preferences:\n * 1. Read preferences from strategy state\n * 2. If no preferences, pass through unchanged\n * 3. For each card:\n * - Look up tag in boost record\n * - If tag found: apply multiplier (0 = exclude, 1 = neutral, >1 = boost)\n * - If multiple tags match: use max multiplier\n * - Append provenance with clear reason\n */\n async transform(cards: WeightedCard[], _context: FilterContext): Promise<WeightedCard[]> {\n // Read user preferences from strategy state\n const prefs = await this.getStrategyState<UserTagPreferenceState>();\n\n // No preferences configured → pass through unchanged\n if (!prefs || Object.keys(prefs.boost).length === 0) {\n return cards.map((card) => ({\n ...card,\n provenance: [\n ...card.provenance,\n {\n strategy: 'userTagPreference',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || this._strategyData._id,\n action: 'passed' as const,\n score: card.score,\n reason: 'No user tag preferences configured',\n },\n ],\n }));\n }\n\n // Process each card\n const adjusted: WeightedCard[] = await Promise.all(\n cards.map(async (card) => {\n const cardTags = card.tags ?? [];\n\n // Compute multiplier based on card tags and user preferences\n const multiplier = this.computeMultiplier(cardTags, prefs.boost);\n const finalScore = Math.min(1, card.score * multiplier);\n\n // Determine action for provenance\n let action: 'passed' | 'boosted' | 'penalized';\n if (multiplier === 0 || multiplier < 1.0) {\n action = 'penalized';\n } else if (multiplier > 1.0) {\n action = 'boosted';\n } else {\n action = 'passed';\n }\n\n return {\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'userTagPreference',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || this._strategyData._id,\n action,\n score: finalScore,\n reason: this.buildReason(cardTags, prefs.boost, multiplier),\n },\n ],\n };\n })\n );\n\n return adjusted;\n }\n\n /**\n * Legacy getWeightedCards - throws as filters should not be used as generators.\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(\n 'UserTagPreferenceFilter is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n}\n","// Filter types and interfaces\nexport type { CardFilter, FilterContext, CardFilterFactory } from './types';\n\n// Filter implementations\nexport { createEloDistanceFilter } from './eloDistance';\nexport type { EloDistanceConfig } from './eloDistance';\n\nexport { default as UserTagPreferenceFilter } from './userTagPreference';\nexport type { UserTagPreferenceState } from './userTagPreference';\n","// ============================================================================\n// INFERRED PREFERENCE NAVIGATOR — STUB\n// ============================================================================\n//\n// STATUS: NOT IMPLEMENTED — This file documents architectural intent only.\n//\n// ============================================================================\n//\n// ## Purpose\n//\n// Inferred preferences are learned from user behavior, as opposed to explicit\n// preferences which are configured via UI. The system observes patterns in\n// user interactions and adjusts card selection accordingly.\n//\n// ## Inference Signals\n//\n// Potential signals to learn from:\n//\n// 1. **Card dismissal patterns**: User consistently skips certain card types\n// 2. **Time-on-card**: User spends less time on certain content (boredom?)\n// 3. **Error patterns**: User struggles with certain presentation styles\n// 4. **Session timing**: User performs better at certain times of day\n// 5. **Tag success rates**: User masters some tags faster than others\n//\n// ## Inferred State (Proposed)\n//\n// ```typescript\n// interface InferredPreferenceState {\n// // Learned tag affinities (positive = user does well, negative = struggles)\n// tagAffinities: Record<string, number>;\n//\n// // Presentation style preferences\n// preferredStyles: {\n// visualVsText: number; // -1 to 1 (negative = text, positive = visual)\n// shortVsLong: number; // -1 to 1 (negative = long, positive = short)\n// };\n//\n// // Temporal patterns\n// optimalSessionLength: number; // minutes\n// optimalTimeOfDay: number; // hour (0-23)\n//\n// // Confidence in inferences\n// sampleSize: number;\n// lastUpdated: string;\n// }\n// ```\n//\n// ## Relationship to Explicit Preferences\n//\n// - Explicit preferences (UserTagPreferenceFilter) always take precedence\n// - Inferred preferences act as soft suggestions when no explicit pref exists\n// - User can \"lock in\" an inference as an explicit preference via UI\n// - User can dismiss/override an inference (\"I actually like text cards\")\n//\n// ## Transparency Requirements\n//\n// Inferred preferences must be:\n//\n// 1. **Visible**: User can see what the system has inferred\n// 2. **Explainable**: \"We noticed you master visual cards faster\"\n// 3. **Overridable**: User can disable or invert any inference\n// 4. **Forgettable**: User can reset inferences and start fresh\n//\n// ## Implementation Considerations\n//\n// 1. **Cold start**: Need minimum sample size before inferring\n// 2. **Drift**: Preferences may change over time; use decay/recency weighting\n// 3. **Privacy**: Inference data is personal; handle with care\n// 4. **Bias**: Avoid reinforcing accidental patterns as permanent preferences\n//\n// ## Related Files\n//\n// - `filters/userTagPreference.ts` — Explicit preferences (takes precedence)\n// - `userGoal.ts` — Goals (destination, not path)\n// - `../types/strategyState.ts` — Storage mechanism\n//\n// ## Next Steps\n//\n// 1. Define minimum viable inference signals\n// 2. Design inference algorithms (simple heuristics vs ML)\n// 3. Build transparency UI (\"Here's what we learned about you\")\n// 4. Implement override/dismiss mechanism\n// 5. Add to card record collection for inference input\n//\n// ============================================================================\n\n// Placeholder export to make this a valid module\nexport const INFERRED_PREFERENCE_NAVIGATOR_STUB = true;\n\n/**\n * @stub InferredPreferenceNavigator\n *\n * A navigator that learns user preferences from behavior patterns.\n * See module-level documentation for architectural intent.\n *\n * NOT IMPLEMENTED — This is a design placeholder.\n */\nexport interface InferredPreferenceState {\n /** Learned affinity scores per tag (-1 to 1) */\n tagAffinities: Record<string, number>;\n\n /** Number of card interactions used to build inferences */\n sampleSize: number;\n\n /** ISO timestamp of last inference update */\n updatedAt: string;\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardFilter, FilterContext } from './types';\nimport { toCourseElo } from '@vue-skuilder/common';\n\n/**\n * Configuration for the InterferenceMitigator strategy.\n *\n * Course authors define explicit interference relationships between tags.\n * The mitigator discourages introducing new concepts that interfere with\n * currently immature learnings.\n */\n/**\n * A single interference group with its own decay coefficient.\n */\nexport interface InterferenceGroup {\n /** Tags that interfere with each other in this group */\n tags: string[];\n /** How strongly these tags interfere (0-1, default: 0.8). Higher = stronger avoidance. */\n decay?: number;\n}\n\nexport interface InterferenceConfig {\n /**\n * Groups of tags that interfere with each other.\n * Each group can have its own decay coefficient.\n *\n * Example: [\n * { tags: [\"b\", \"d\", \"p\"], decay: 0.9 }, // visual similarity - strong\n * { tags: [\"d\", \"t\"], decay: 0.7 }, // phonetic similarity - moderate\n * { tags: [\"m\", \"n\"], decay: 0.8 }\n * ]\n */\n interferenceSets: InterferenceGroup[];\n\n /**\n * Threshold below which a tag is considered \"immature\" (still being learned).\n * Immature tags trigger interference avoidance for their interference partners.\n */\n maturityThreshold?: {\n /** Minimum interaction count to be considered mature (default: 10) */\n minCount?: number;\n /** Minimum ELO score to be considered mature (optional) */\n minElo?: number;\n /**\n * Minimum elapsed time (in days) since first interaction to be considered mature.\n * Prevents recent cramming success from indicating maturity.\n * The skill should be \"lindy\" — maintained over time.\n */\n minElapsedDays?: number;\n };\n\n /**\n * Default decay for groups that don't specify their own (0-1, default: 0.8).\n */\n defaultDecay?: number;\n}\n\nconst DEFAULT_MIN_COUNT = 10;\nconst DEFAULT_MIN_ELAPSED_DAYS = 3;\nconst DEFAULT_INTERFERENCE_DECAY = 0.8;\n\n/**\n * A filter strategy that avoids introducing confusable concepts simultaneously.\n *\n * When a user is learning a concept (tag is \"immature\"), this strategy reduces\n * scores for cards tagged with interfering concepts. This encourages the system\n * to introduce new content that is maximally distant from current learning focus.\n *\n * Example: While learning 'd', prefer introducing 'x' over 'b' (visual interference)\n * or 't' (phonetic interference).\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class InterferenceMitigatorNavigator extends ContentNavigator implements CardFilter {\n private config: InterferenceConfig;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n /** Precomputed map: tag -> set of { partner, decay } it interferes with */\n private interferenceMap: Map<string, Array<{ partner: string; decay: number }>>;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.config = this.parseConfig(strategyData.serializedData);\n this.interferenceMap = this.buildInterferenceMap();\n this.name = strategyData.name || 'Interference Mitigator';\n }\n\n private parseConfig(serializedData: string): InterferenceConfig {\n try {\n const parsed = JSON.parse(serializedData);\n // Normalize legacy format (string[][]) to new format (InterferenceGroup[])\n let sets: InterferenceGroup[] = parsed.interferenceSets || [];\n if (sets.length > 0 && Array.isArray(sets[0])) {\n // Legacy format: convert string[][] to InterferenceGroup[]\n sets = (sets as unknown as string[][]).map((tags) => ({ tags }));\n }\n\n return {\n interferenceSets: sets,\n maturityThreshold: {\n minCount: parsed.maturityThreshold?.minCount ?? DEFAULT_MIN_COUNT,\n minElo: parsed.maturityThreshold?.minElo,\n minElapsedDays: parsed.maturityThreshold?.minElapsedDays ?? DEFAULT_MIN_ELAPSED_DAYS,\n },\n defaultDecay: parsed.defaultDecay ?? DEFAULT_INTERFERENCE_DECAY,\n };\n } catch {\n return {\n interferenceSets: [],\n maturityThreshold: {\n minCount: DEFAULT_MIN_COUNT,\n minElapsedDays: DEFAULT_MIN_ELAPSED_DAYS,\n },\n defaultDecay: DEFAULT_INTERFERENCE_DECAY,\n };\n }\n }\n\n /**\n * Build a map from each tag to its interference partners with decay coefficients.\n * If tags A, B, C are in an interference group with decay 0.8, then:\n * - A interferes with B (decay 0.8) and C (decay 0.8)\n * - B interferes with A (decay 0.8) and C (decay 0.8)\n * - etc.\n */\n private buildInterferenceMap(): Map<string, Array<{ partner: string; decay: number }>> {\n const map = new Map<string, Array<{ partner: string; decay: number }>>();\n\n for (const group of this.config.interferenceSets) {\n const decay = group.decay ?? this.config.defaultDecay ?? DEFAULT_INTERFERENCE_DECAY;\n\n for (const tag of group.tags) {\n if (!map.has(tag)) {\n map.set(tag, []);\n }\n const partners = map.get(tag)!;\n for (const other of group.tags) {\n if (other !== tag) {\n // Check if partner already exists (from overlapping groups)\n const existing = partners.find((p) => p.partner === other);\n if (existing) {\n // Use the stronger (higher) decay\n existing.decay = Math.max(existing.decay, decay);\n } else {\n partners.push({ partner: other, decay });\n }\n }\n }\n }\n }\n\n return map;\n }\n\n /**\n * Get the set of tags that are currently immature for this user.\n * A tag is immature if the user has interacted with it but hasn't\n * reached the maturity threshold.\n */\n private async getImmatureTags(context: FilterContext): Promise<Set<string>> {\n const immature = new Set<string>();\n\n try {\n const courseReg = await context.user.getCourseRegDoc(context.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n\n const minCount = this.config.maturityThreshold?.minCount ?? DEFAULT_MIN_COUNT;\n const minElo = this.config.maturityThreshold?.minElo;\n\n // TODO: To properly check elapsed time, we need access to first interaction timestamp.\n // For now, we use count as a proxy (more interactions = more time elapsed).\n // Future: query card history for earliest timestamp per tag.\n const minElapsedDays =\n this.config.maturityThreshold?.minElapsedDays ?? DEFAULT_MIN_ELAPSED_DAYS;\n const minCountForElapsed = minElapsedDays * 2; // Rough proxy: ~2 interactions per day\n\n for (const [tagId, tagElo] of Object.entries(userElo.tags)) {\n // Only consider tags that have been started (count > 0)\n if (tagElo.count === 0) continue;\n\n // Check if below maturity threshold\n const belowCount = tagElo.count < minCount;\n const belowElo = minElo !== undefined && tagElo.score < minElo;\n const belowElapsed = tagElo.count < minCountForElapsed; // Proxy for time\n\n if (belowCount || belowElo || belowElapsed) {\n immature.add(tagId);\n }\n }\n } catch {\n // If we can't get user data, assume no immature tags\n }\n\n return immature;\n }\n\n /**\n * Get all tags that interfere with any immature tag, along with their decay coefficients.\n * These are the tags we want to avoid introducing.\n */\n private getTagsToAvoid(immatureTags: Set<string>): Map<string, number> {\n const avoid = new Map<string, number>();\n\n for (const immatureTag of immatureTags) {\n const partners = this.interferenceMap.get(immatureTag);\n if (partners) {\n for (const { partner, decay } of partners) {\n // Avoid the partner, but not if it's also immature\n // (if both are immature, we're already learning both)\n if (!immatureTags.has(partner)) {\n // Use the strongest (highest) decay if partner appears multiple times\n const existing = avoid.get(partner) ?? 0;\n avoid.set(partner, Math.max(existing, decay));\n }\n }\n }\n }\n\n return avoid;\n }\n\n /**\n * Compute interference score reduction for a card.\n * Returns: { multiplier, interfering tags, reason }\n */\n private computeInterferenceEffect(\n cardTags: string[],\n tagsToAvoid: Map<string, number>,\n immatureTags: Set<string>\n ): { multiplier: number; interferingTags: string[]; reason: string } {\n if (tagsToAvoid.size === 0) {\n return {\n multiplier: 1.0,\n interferingTags: [],\n reason: 'No interference detected',\n };\n }\n\n let multiplier = 1.0;\n const interferingTags: string[] = [];\n\n for (const tag of cardTags) {\n const decay = tagsToAvoid.get(tag);\n if (decay !== undefined) {\n interferingTags.push(tag);\n multiplier *= 1.0 - decay;\n }\n }\n\n if (interferingTags.length === 0) {\n return {\n multiplier: 1.0,\n interferingTags: [],\n reason: 'No interference detected',\n };\n }\n\n // Find which immature tags these interfere with\n const causingTags = new Set<string>();\n for (const tag of interferingTags) {\n for (const immatureTag of immatureTags) {\n const partners = this.interferenceMap.get(immatureTag);\n if (partners?.some((p) => p.partner === tag)) {\n causingTags.add(immatureTag);\n }\n }\n }\n\n const reason = `Interferes with immature tags ${Array.from(causingTags).join(', ')} (tags: ${interferingTags.join(', ')}, multiplier: ${multiplier.toFixed(2)})`;\n\n return { multiplier, interferingTags, reason };\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply interference-aware scoring. Cards with tags that interfere with\n * immature learnings get reduced scores.\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n // Identify what to avoid\n const immatureTags = await this.getImmatureTags(context);\n const tagsToAvoid = this.getTagsToAvoid(immatureTags);\n\n // Adjust scores based on interference\n const adjusted: WeightedCard[] = [];\n\n for (const card of cards) {\n const cardTags = card.tags ?? [];\n const { multiplier, reason } = this.computeInterferenceEffect(\n cardTags,\n tagsToAvoid,\n immatureTags\n );\n const finalScore = card.score * multiplier;\n\n const action = multiplier < 1.0 ? 'penalized' : multiplier > 1.0 ? 'boosted' : 'passed';\n\n adjusted.push({\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'interferenceMitigator',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-interference',\n action,\n score: finalScore,\n reason,\n },\n ],\n });\n }\n\n return adjusted;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(\n 'InterferenceMitigatorNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardFilter, FilterContext } from './types';\n\n/**\n * Configuration for the RelativePriority strategy.\n *\n * Course authors define priority weights for tags, allowing the system\n * to prefer high-utility content (common, well-behaved patterns) over\n * lower-utility content (rare, irregular patterns).\n *\n * Example use case: In phonics, prefer teaching 's' (common, consistent)\n * before 'x' or 'z' (rare, sometimes irregular).\n */\nexport interface RelativePriorityConfig {\n /**\n * Map of tag ID to priority weight (0-1).\n *\n * 1.0 = highest priority (present first)\n * 0.5 = neutral\n * 0.0 = lowest priority (defer until later)\n *\n * Example:\n * {\n * \"letter-s\": 0.95,\n * \"letter-t\": 0.90,\n * \"letter-x\": 0.10,\n * \"letter-z\": 0.05\n * }\n */\n tagPriorities: { [tagId: string]: number };\n\n /**\n * Priority for tags not explicitly listed (default: 0.5).\n * 0.5 means unlisted tags have neutral effect on scoring.\n */\n defaultPriority?: number;\n\n /**\n * How to combine priorities when a card has multiple tags.\n *\n * - 'max': Use the highest priority among the card's tags (default)\n * - 'average': Average all tag priorities\n * - 'min': Use the lowest priority (conservative)\n */\n combineMode?: 'max' | 'average' | 'min';\n\n /**\n * How strongly priority influences the final score (0-1, default: 0.5).\n *\n * At 0.0: Priority has no effect (pure delegate scoring)\n * At 0.5: Priority can boost/reduce scores by up to 25%\n * At 1.0: Priority can boost/reduce scores by up to 50%\n *\n * The boost factor formula: 1 + (priority - 0.5) * priorityInfluence\n * - Priority 1.0 with influence 0.5 → boost of 1.25\n * - Priority 0.5 with influence 0.5 → boost of 1.00 (neutral)\n * - Priority 0.0 with influence 0.5 → boost of 0.75\n */\n priorityInfluence?: number;\n}\n\nconst DEFAULT_PRIORITY = 0.5;\nconst DEFAULT_PRIORITY_INFLUENCE = 0.5;\nconst DEFAULT_COMBINE_MODE: 'max' | 'average' | 'min' = 'max';\n\n/**\n * A filter strategy that boosts scores for high-utility content.\n *\n * Course authors assign priority weights to tags. Cards with high-priority\n * tags get boosted scores, making them more likely to be presented first.\n * This allows teaching the most useful, well-behaved concepts before\n * moving on to rarer or more irregular ones.\n *\n * Example: When teaching phonics, prioritize common letters (s, t, a) over\n * rare ones (x, z, q) by assigning higher priority weights to common letters.\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class RelativePriorityNavigator extends ContentNavigator implements CardFilter {\n private config: RelativePriorityConfig;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.config = this.parseConfig(strategyData.serializedData);\n this.name = strategyData.name || 'Relative Priority';\n }\n\n private parseConfig(serializedData: string): RelativePriorityConfig {\n try {\n const parsed = JSON.parse(serializedData);\n return {\n tagPriorities: parsed.tagPriorities || {},\n defaultPriority: parsed.defaultPriority ?? DEFAULT_PRIORITY,\n combineMode: parsed.combineMode ?? DEFAULT_COMBINE_MODE,\n priorityInfluence: parsed.priorityInfluence ?? DEFAULT_PRIORITY_INFLUENCE,\n };\n } catch {\n // Return safe defaults if parsing fails\n return {\n tagPriorities: {},\n defaultPriority: DEFAULT_PRIORITY,\n combineMode: DEFAULT_COMBINE_MODE,\n priorityInfluence: DEFAULT_PRIORITY_INFLUENCE,\n };\n }\n }\n\n /**\n * Look up the priority for a tag.\n */\n private getTagPriority(tagId: string): number {\n return this.config.tagPriorities[tagId] ?? this.config.defaultPriority ?? DEFAULT_PRIORITY;\n }\n\n /**\n * Compute combined priority for a card based on its tags.\n */\n private computeCardPriority(cardTags: string[]): number {\n if (cardTags.length === 0) {\n return this.config.defaultPriority ?? DEFAULT_PRIORITY;\n }\n\n const priorities = cardTags.map((tag) => this.getTagPriority(tag));\n\n switch (this.config.combineMode) {\n case 'max':\n return Math.max(...priorities);\n case 'min':\n return Math.min(...priorities);\n case 'average':\n return priorities.reduce((sum, p) => sum + p, 0) / priorities.length;\n default:\n return Math.max(...priorities);\n }\n }\n\n /**\n * Compute boost factor based on priority.\n *\n * The formula: 1 + (priority - 0.5) * priorityInfluence\n *\n * This creates a multiplier centered around 1.0:\n * - Priority 1.0 with influence 0.5 → 1.25 (25% boost)\n * - Priority 0.5 with any influence → 1.00 (neutral)\n * - Priority 0.0 with influence 0.5 → 0.75 (25% reduction)\n */\n private computeBoostFactor(priority: number): number {\n const influence = this.config.priorityInfluence ?? DEFAULT_PRIORITY_INFLUENCE;\n return 1 + (priority - 0.5) * influence;\n }\n\n /**\n * Build human-readable reason for priority adjustment.\n */\n private buildPriorityReason(\n cardTags: string[],\n priority: number,\n boostFactor: number,\n finalScore: number\n ): string {\n if (cardTags.length === 0) {\n return `No tags, neutral priority (${priority.toFixed(2)})`;\n }\n\n const tagList = cardTags.slice(0, 3).join(', ');\n const more = cardTags.length > 3 ? ` (+${cardTags.length - 3} more)` : '';\n\n if (boostFactor === 1.0) {\n return `Neutral priority (${priority.toFixed(2)}) for tags: ${tagList}${more}`;\n } else if (boostFactor > 1.0) {\n return `High-priority tags: ${tagList}${more} (priority ${priority.toFixed(2)} → boost ${boostFactor.toFixed(2)}x → ${finalScore.toFixed(2)})`;\n } else {\n return `Low-priority tags: ${tagList}${more} (priority ${priority.toFixed(2)} → reduce ${boostFactor.toFixed(2)}x → ${finalScore.toFixed(2)})`;\n }\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply priority-adjusted scoring. Cards with high-priority tags get boosted,\n * cards with low-priority tags get reduced scores.\n */\n async transform(cards: WeightedCard[], _context: FilterContext): Promise<WeightedCard[]> {\n const adjusted: WeightedCard[] = await Promise.all(\n cards.map(async (card) => {\n const cardTags = card.tags ?? [];\n const priority = this.computeCardPriority(cardTags);\n const boostFactor = this.computeBoostFactor(priority);\n // No upper clamp — scores may exceed 1.0 intentionally.\n // Scores are only used for relative ordering within a pipeline run,\n // so absolute magnitude doesn't matter. Clamping to 1.0 here collapsed\n // differentiation when GPC preReqBoosts and priority boosts compounded\n // (e.g. 0.96 × 2.5 × 1.24 → 2.98, previously crushed back to 1.0).\n // Floor of 0 is kept: negative scores have no meaning.\n const finalScore = Math.max(0, card.score * boostFactor);\n\n // Determine action based on boost factor\n const action = boostFactor > 1.0 ? 'boosted' : boostFactor < 1.0 ? 'penalized' : 'passed';\n\n // Build reason explaining priority adjustment\n const reason = this.buildPriorityReason(cardTags, priority, boostFactor, finalScore);\n\n return {\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'relativePriority',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-priority',\n action,\n score: finalScore,\n reason,\n },\n ],\n };\n })\n );\n\n return adjusted;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(\n 'RelativePriorityNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n}\n","import type { WeightedCard } from '../index';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport type { OrchestrationContext } from '../../orchestration';\n\n// ============================================================================\n// CARD FILTER INTERFACE\n// ============================================================================\n//\n// Filters are pure transforms on a list of WeightedCards.\n// They replace the delegate-wrapping pattern with a simpler model:\n//\n// cards = Generator.getWeightedCards()\n// cards = Filter1.transform(cards, context)\n// cards = Filter2.transform(cards, context)\n//\n// Benefits:\n// - No nested instantiation\n// - Filters don't need to know about delegates\n// - Easy to add/remove/reorder filters\n// - Natural place to hydrate shared data before filter pass\n//\n// All filters should be score multipliers (including score: 0 for exclusion).\n// This means filter order doesn't affect final scores.\n//\n// ============================================================================\n\n/**\n * Shared context available to all filters in a pipeline.\n *\n * Built once per getWeightedCards() call and passed to each filter.\n * This avoids repeated lookups for common data like user ELO.\n */\nexport interface FilterContext {\n /** User database interface */\n user: UserDBInterface;\n\n /** Course database interface */\n course: CourseDBInterface;\n\n /** User's global ELO score for this course */\n userElo: number;\n\n /** Orchestration context for evolutionary weighting */\n orchestration?: OrchestrationContext;\n\n // Future extensions:\n // - hydrated tags for all cards (batch lookup)\n // - user's tag-level ELO data\n // - course config\n}\n\n/**\n * A filter that transforms a list of weighted cards.\n *\n * Filters are pure transforms - they receive cards and context,\n * and return a modified list of cards. No delegate wrapping,\n * no side effects beyond provenance tracking.\n *\n * ## Implementation Guidelines\n *\n * 1. **Append provenance**: Every filter should add a StrategyContribution\n * entry documenting its decision for each card.\n *\n * 2. **Use multipliers**: Adjust scores by multiplying, not replacing.\n * This ensures filter order doesn't matter.\n *\n * 3. **Score 0 for exclusion**: To exclude a card, set score to 0.\n * Don't filter it out - let provenance show why it was excluded.\n *\n * 4. **Don't sort**: The Pipeline handles final sorting.\n * Filters just transform scores.\n *\n * ## Example Implementation\n *\n * ```typescript\n * const myFilter: CardFilter = {\n * name: 'My Filter',\n * async transform(cards, context) {\n * return cards.map(card => {\n * const multiplier = computeMultiplier(card, context);\n * const newScore = card.score * multiplier;\n * return {\n * ...card,\n * score: newScore,\n * provenance: [...card.provenance, {\n * strategy: 'myFilter',\n * strategyName: 'My Filter',\n * strategyId: 'MY_FILTER',\n * action: multiplier < 1 ? 'penalized' : 'passed',\n * score: newScore,\n * reason: 'Explanation of decision'\n * }]\n * };\n * });\n * }\n * };\n * ```\n */\nexport interface CardFilter {\n /** Human-readable name for this filter */\n name: string;\n\n /**\n * Transform a list of weighted cards.\n *\n * @param cards - Cards to transform (already scored by generator)\n * @param context - Shared context (user, course, userElo, etc.)\n * @returns Transformed cards with updated scores and provenance\n */\n transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]>;\n}\n\n/**\n * Factory function type for creating filters from configuration.\n *\n * Used by PipelineAssembler to instantiate filters from strategy documents.\n */\nexport type CardFilterFactory<TConfig = unknown> = (config: TConfig) => CardFilter;\n","// ============================================================================\n// USER GOAL NAVIGATOR — STUB\n// ============================================================================\n//\n// STATUS: NOT IMPLEMENTED — This file documents architectural intent only.\n//\n// ============================================================================\n//\n// ## Purpose\n//\n// Goals define WHAT the user wants to learn, as opposed to preferences which\n// define HOW they want to learn. Goals affect:\n//\n// 1. **Content scoping**: Which tags/content are relevant to this user\n// 2. **Progress tracking**: ELO is measured against goal-relevant content\n// 3. **Completion criteria**: User is \"done\" when goal mastery is achieved\n// 4. **Curriculum composition**: Goals enable cross-curriculum dependencies\n//\n// ## Goals vs Preferences\n//\n// | Aspect | Goal | Preference |\n// |---------------|-------------------------------|-------------------------------|\n// | Defines | Destination (what to learn) | Path (how to learn) |\n// | Example | \"Master ear-training\" | \"Skip text-heavy cards\" |\n// | Affects ELO | Yes — scopes what's tracked | No — just filters cards |\n// | Completion | Yes — defines \"done\" | No — persists indefinitely |\n// | Filter impl | UserGoalNavigator | UserTagPreferenceFilter |\n//\n// ## Curriculum Composition\n//\n// Goals enable software-style composition for curricula. A physics course\n// can teach classical mechanics without owning the calculus prerequisites.\n//\n// Instead, it declares a dependency:\n//\n// ```typescript\n// interface CurriculumDependency {\n// // NPM-style package resolution\n// curriculumId: string; // e.g., \"@skuilder/calculus\"\n// version: string; // e.g., \"^2.0.0\" (semver)\n//\n// // Goal within that curriculum\n// goal: string; // e.g., \"differential-calculus\"\n//\n// // How this maps to local prerequisites\n// satisfiesLocalTags: string[]; // e.g., [\"calculus-prereq\"]\n// }\n// ```\n//\n// When a physics card requires \"calculus-prereq\", the system:\n// 1. Checks if user has achieved the \"differential-calculus\" goal in @skuilder/calculus\n// 2. If not, defers to that curriculum to teach the prerequisite\n// 3. Returns to physics once the goal is satisfied\n//\n// This allows:\n// - Specialized curricula (calculus experts author calculus content)\n// - Reusable prerequisites across multiple courses\n// - User can bring their own \"calculus credential\" from prior learning\n//\n// ## User Goal State (Proposed)\n//\n// ```typescript\n// interface UserGoalState {\n// // Primary goals — what the user wants to achieve\n// targetTags: string[];\n//\n// // Excluded goals — content the user explicitly doesn't care about\n// excludedTags: string[];\n//\n// // Cross-curriculum goals (for composition)\n// externalGoals?: {\n// curriculumId: string;\n// goal: string;\n// status: 'not-started' | 'in-progress' | 'achieved';\n// }[];\n//\n// // When this goal configuration was set\n// updatedAt: string;\n// }\n// ```\n//\n// ## Implementation Considerations\n//\n// 1. **ELO Scoping**: When goals are set, user ELO tracking should focus on\n// goal-relevant tags. This may require changes to ELO update logic.\n//\n// 2. **Progress Reporting**: UI should show progress toward goals, not just\n// overall course completion.\n//\n// 3. **Goal Achievement**: Need to define when a goal is \"achieved\" —\n// probably ELO threshold + mastery percentage on goal-tagged content.\n//\n// 4. **Curriculum Registry**: For cross-curriculum composition, need a\n// registry/resolver for curriculum packages (similar to npm registry).\n//\n// 5. **Interaction with HierarchyDefinition**: Goals should work with\n// prerequisite chains — user can't skip prerequisites just because\n// they're not part of their goal.\n//\n// ## Related Files\n//\n// - `filters/userTagPreference.ts` — Preferences (path constraints)\n// - `hierarchyDefinition.ts` — Prerequisites (enforced regardless of goals)\n// - `../types/strategyState.ts` — Storage mechanism for user state\n//\n// ## Next Steps\n//\n// 1. Design goal state schema in detail\n// 2. Define goal achievement criteria\n// 3. Implement goal-scoped ELO tracking\n// 4. Build UI for goal configuration\n// 5. Design curriculum dependency resolution\n//\n// ============================================================================\n\n// Placeholder export to make this a valid module\nexport const USER_GOAL_NAVIGATOR_STUB = true;\n\n/**\n * @stub UserGoalNavigator\n *\n * A navigator that scopes learning to user-defined goals.\n * See module-level documentation for architectural intent.\n *\n * NOT IMPLEMENTED — This is a design placeholder.\n */\nexport interface UserGoalState {\n /** Tags the user wants to master (defines \"success\") */\n targetTags: string[];\n\n /** Tags the user explicitly doesn't care about */\n excludedTags: string[];\n\n /** ISO timestamp of last update */\n updatedAt: string;\n}\n","import { UserOutcomeRecord } from '../types/userOutcome';\nimport { GradientObservation, GradientResult } from '../types/learningState';\nimport { logger } from '../../util/logger';\n\n/**\n * Extract (deviation, outcome) observations for a specific strategy\n * from a collection of UserOutcomeRecords.\n *\n * @param outcomes - Collection of outcome records (from multiple users)\n * @param strategyId - The strategy to extract observations for\n * @returns Array of gradient observations\n */\nexport function aggregateOutcomesForGradient(\n outcomes: UserOutcomeRecord[],\n strategyId: string\n): GradientObservation[] {\n const observations: GradientObservation[] = [];\n\n for (const outcome of outcomes) {\n // Skip if this outcome doesn't have a deviation for this strategy\n const deviation = outcome.deviations[strategyId];\n if (deviation === undefined) {\n continue;\n }\n\n observations.push({\n deviation,\n outcomeValue: outcome.outcomeValue,\n weight: 1.0,\n });\n }\n\n logger.debug(\n `[Orchestration] Aggregated ${observations.length} observations for strategy ${strategyId}`\n );\n\n return observations;\n}\n\n/**\n * Compute linear regression on (deviation, outcome) pairs.\n *\n * Uses ordinary least squares to find the best fit line:\n * outcome = gradient * deviation + intercept\n *\n * The gradient tells us:\n * - Positive: users with higher deviation (higher weight) had better outcomes\n * → we should increase the peak weight\n * - Negative: users with higher deviation (higher weight) had worse outcomes\n * → we should decrease the peak weight\n * - Near zero: weight doesn't affect outcomes much\n * → we're near optimal, increase confidence\n *\n * @param observations - Array of (deviation, outcome) pairs\n * @returns Regression result, or null if insufficient data\n */\nexport function computeStrategyGradient(\n observations: GradientObservation[]\n): GradientResult | null {\n const n = observations.length;\n\n if (n < 3) {\n logger.debug(`[Orchestration] Insufficient observations for gradient (${n} < 3)`);\n return null;\n }\n\n // Compute means\n let sumX = 0;\n let sumY = 0;\n let sumW = 0;\n\n for (const obs of observations) {\n const w = obs.weight ?? 1.0;\n sumX += obs.deviation * w;\n sumY += obs.outcomeValue * w;\n sumW += w;\n }\n\n const meanX = sumX / sumW;\n const meanY = sumY / sumW;\n\n // Compute slope (gradient) and intercept using weighted least squares\n let numerator = 0;\n let denominator = 0;\n let ssTotal = 0;\n\n for (const obs of observations) {\n const w = obs.weight ?? 1.0;\n const dx = obs.deviation - meanX;\n const dy = obs.outcomeValue - meanY;\n\n numerator += w * dx * dy;\n denominator += w * dx * dx;\n ssTotal += w * dy * dy;\n }\n\n // Avoid division by zero if all deviations are the same\n if (denominator < 1e-10) {\n logger.debug(`[Orchestration] No variance in deviations, cannot compute gradient`);\n return {\n gradient: 0,\n intercept: meanY,\n rSquared: 0,\n sampleSize: n,\n };\n }\n\n const gradient = numerator / denominator;\n const intercept = meanY - gradient * meanX;\n\n // Compute R-squared\n let ssResidual = 0;\n for (const obs of observations) {\n const w = obs.weight ?? 1.0;\n const predicted = gradient * obs.deviation + intercept;\n const residual = obs.outcomeValue - predicted;\n ssResidual += w * residual * residual;\n }\n\n const rSquared = ssTotal > 1e-10 ? 1 - ssResidual / ssTotal : 0;\n\n logger.debug(\n `[Orchestration] Computed gradient: ${gradient.toFixed(4)}, ` +\n `intercept: ${intercept.toFixed(4)}, R²: ${rSquared.toFixed(4)}, n=${n}`\n );\n\n return {\n gradient,\n intercept,\n rSquared: Math.max(0, Math.min(1, rSquared)), // Clamp to [0,1]\n sampleSize: n,\n };\n}\n","import { LearnableWeight, DEFAULT_LEARNABLE_WEIGHT } from '../types/contentNavigationStrategy';\nimport { StrategyLearningState, GradientResult } from '../types/learningState';\nimport { DocType } from '../types/types-legacy';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\n/** Minimum observations required before adjusting weight */\nconst MIN_OBSERVATIONS_FOR_UPDATE = 10;\n\n/** How much to adjust weight per gradient unit */\nconst LEARNING_RATE = 0.1;\n\n/** Maximum weight adjustment per update cycle */\nconst MAX_WEIGHT_DELTA = 0.3;\n\n/** R-squared threshold below which we consider gradient unreliable */\nconst MIN_R_SQUARED_FOR_GRADIENT = 0.05;\n\n/** Gradient magnitude below which we consider it \"flat\" (near optimal) */\nconst FLAT_GRADIENT_THRESHOLD = 0.02;\n\n/** Maximum history entries to retain */\nconst MAX_HISTORY_LENGTH = 100;\n\n// ============================================================================\n// WEIGHT UPDATE\n// ============================================================================\n\n/**\n * Compute updated weight based on gradient result.\n *\n * The update logic:\n * - Positive gradient: users with higher weight did better → increase weight\n * - Negative gradient: users with higher weight did worse → decrease weight\n * - Flat gradient: weight doesn't affect outcome → increase confidence\n *\n * @param current - Current learnable weight\n * @param gradient - Computed gradient result\n * @returns Updated learnable weight\n */\nexport function updateStrategyWeight(\n current: LearnableWeight,\n gradient: GradientResult\n): LearnableWeight {\n // Not enough data to make reliable updates\n if (gradient.sampleSize < MIN_OBSERVATIONS_FOR_UPDATE) {\n logger.debug(\n `[Orchestration] Insufficient samples (${gradient.sampleSize} < ${MIN_OBSERVATIONS_FOR_UPDATE}), ` +\n `keeping current weight`\n );\n return {\n ...current,\n sampleSize: current.sampleSize + gradient.sampleSize,\n };\n }\n\n // Check if gradient is reliable (R² threshold)\n const isReliable = gradient.rSquared >= MIN_R_SQUARED_FOR_GRADIENT;\n const isFlat = Math.abs(gradient.gradient) < FLAT_GRADIENT_THRESHOLD;\n\n let newWeight = current.weight;\n let newConfidence = current.confidence;\n\n if (!isReliable || isFlat) {\n // Gradient is unreliable or flat - we're likely near optimal\n // Increase confidence (narrow the exploration spread)\n const confidenceGain = 0.05 * (1 - current.confidence);\n newConfidence = Math.min(1.0, current.confidence + confidenceGain);\n\n logger.debug(\n `[Orchestration] Flat/unreliable gradient (|g|=${Math.abs(gradient.gradient).toFixed(4)}, ` +\n `R²=${gradient.rSquared.toFixed(4)}). Increasing confidence: ${current.confidence.toFixed(3)} → ${newConfidence.toFixed(3)}`\n );\n } else {\n // Reliable gradient - adjust weight in gradient direction\n // Scale by learning rate and clamp to max delta\n let delta = gradient.gradient * LEARNING_RATE;\n delta = Math.max(-MAX_WEIGHT_DELTA, Math.min(MAX_WEIGHT_DELTA, delta));\n\n newWeight = current.weight + delta;\n\n // Clamp weight to reasonable bounds\n newWeight = Math.max(0.1, Math.min(3.0, newWeight));\n\n // Slight confidence increase for having made an observation\n const confidenceGain = 0.02 * (1 - current.confidence);\n newConfidence = Math.min(1.0, current.confidence + confidenceGain);\n\n logger.debug(\n `[Orchestration] Adjusting weight: ${current.weight.toFixed(3)} → ${newWeight.toFixed(3)} ` +\n `(gradient=${gradient.gradient.toFixed(4)}, delta=${delta.toFixed(4)})`\n );\n }\n\n return {\n weight: newWeight,\n confidence: newConfidence,\n sampleSize: current.sampleSize + gradient.sampleSize,\n };\n}\n\n// ============================================================================\n// LEARNING STATE MANAGEMENT\n// ============================================================================\n\n/**\n * Create or update a StrategyLearningState document.\n *\n * @param courseId - Course ID\n * @param strategyId - Strategy ID\n * @param currentWeight - Current learned weight\n * @param gradient - Gradient result from recent computation\n * @param existing - Existing learning state (if any)\n * @returns Updated learning state document\n */\nexport function updateLearningState(\n courseId: string,\n strategyId: string,\n currentWeight: LearnableWeight,\n gradient: GradientResult,\n existing?: StrategyLearningState\n): StrategyLearningState {\n const now = new Date().toISOString();\n const id = `STRATEGY_LEARNING_STATE::${courseId}::${strategyId}`;\n\n // Build history entry\n const historyEntry = {\n timestamp: now,\n weight: currentWeight.weight,\n confidence: currentWeight.confidence,\n gradient: gradient.gradient,\n };\n\n // Append to existing history or start fresh\n let history = existing?.history ?? [];\n history = [...history, historyEntry];\n\n // Trim history if too long\n if (history.length > MAX_HISTORY_LENGTH) {\n history = history.slice(history.length - MAX_HISTORY_LENGTH);\n }\n\n const state: StrategyLearningState = {\n _id: id,\n _rev: existing?._rev,\n docType: DocType.STRATEGY_LEARNING_STATE,\n courseId,\n strategyId,\n currentWeight,\n regression: {\n gradient: gradient.gradient,\n intercept: gradient.intercept,\n rSquared: gradient.rSquared,\n sampleSize: gradient.sampleSize,\n computedAt: now,\n },\n history,\n updatedAt: now,\n };\n\n return state;\n}\n\n// ============================================================================\n// PERIOD UPDATE ORCHESTRATOR\n// ============================================================================\n\n/**\n * Input data for running a period update on a single strategy.\n */\nexport interface PeriodUpdateInput {\n courseId: string;\n strategyId: string;\n currentWeight: LearnableWeight;\n gradient: GradientResult;\n existingState?: StrategyLearningState;\n}\n\n/**\n * Result of a period update for a single strategy.\n */\nexport interface PeriodUpdateResult {\n strategyId: string;\n previousWeight: LearnableWeight;\n newWeight: LearnableWeight;\n gradient: GradientResult;\n learningState: StrategyLearningState;\n updated: boolean;\n}\n\n/**\n * Run a period update for a single strategy.\n *\n * This function:\n * 1. Takes the computed gradient\n * 2. Computes the new weight\n * 3. Generates the updated learning state\n *\n * Note: Actual persistence (updating strategy doc, saving learning state)\n * must be done by the caller with appropriate DB access.\n *\n * @param input - Update input data\n * @returns Update result with new weight and learning state\n */\nexport function runPeriodUpdate(input: PeriodUpdateInput): PeriodUpdateResult {\n const { courseId, strategyId, currentWeight, gradient, existingState } = input;\n\n logger.info(\n `[Orchestration] Running period update for strategy ${strategyId} ` +\n `(${gradient.sampleSize} observations)`\n );\n\n // Compute new weight\n const newWeight = updateStrategyWeight(currentWeight, gradient);\n const updated = newWeight.weight !== currentWeight.weight;\n\n // Generate learning state\n const learningState = updateLearningState(\n courseId,\n strategyId,\n newWeight,\n gradient,\n existingState\n );\n\n logger.info(\n `[Orchestration] Period update complete for ${strategyId}: ` +\n `weight ${currentWeight.weight.toFixed(3)} → ${newWeight.weight.toFixed(3)}, ` +\n `confidence ${currentWeight.confidence.toFixed(3)} → ${newWeight.confidence.toFixed(3)}`\n );\n\n return {\n strategyId,\n previousWeight: currentWeight,\n newWeight,\n gradient,\n learningState,\n updated,\n };\n}\n\n/**\n * Create a default LearnableWeight for strategies that don't have one.\n */\nexport function getDefaultLearnableWeight(): LearnableWeight {\n return { ...DEFAULT_LEARNABLE_WEIGHT };\n}\n","import { QuestionRecord } from '../types/types-legacy';\n\nexport interface SignalConfig {\n /** Target accuracy for \"in the zone\" learning (default: 0.85) */\n targetAccuracy?: number;\n /** Width of the peak plateau (default: 0.05) */\n tolerance?: number;\n}\n\n/**\n * Computes a scalar signal (0-1) representing the quality of the learning outcome.\n *\n * Current implementation focuses on \"accuracy within Zone of Proximal Development\".\n * Future versions should include ELO gain rate.\n *\n * @param records - List of question attempts in the period\n * @param config - Configuration for the signal function\n * @returns Score 0.0-1.0, or null if insufficient data\n */\nexport function computeOutcomeSignal(\n records: QuestionRecord[],\n config: SignalConfig = {}\n): number | null {\n if (!records || records.length === 0) {\n return null;\n }\n\n const target = config.targetAccuracy ?? 0.85;\n const tolerance = config.tolerance ?? 0.05;\n\n let correct = 0;\n for (const r of records) {\n // Cast to any to avoid type error if Evaluation interface is not correctly propagated\n \n if ((r as any).isCorrect) correct++;\n }\n\n const accuracy = correct / records.length;\n\n return scoreAccuracyInZone(accuracy, target, tolerance);\n}\n\n/**\n * Scores an accuracy value based on how close it is to the target \"sweet spot\".\n *\n * The function defines a plateau of width (2 * tolerance) around the target\n * where score is 1.0. Outside this plateau, it falls off linearly.\n *\n * @param accuracy - Observed accuracy (0-1)\n * @param target - Target accuracy (e.g. 0.85)\n * @param tolerance - +/- range allowed for max score\n */\nexport function scoreAccuracyInZone(accuracy: number, target: number, tolerance: number): number {\n const dist = Math.abs(accuracy - target);\n\n // Inside the sweet spot\n if (dist <= tolerance) {\n return 1.0;\n }\n\n // Outside, fall off.\n // We apply a linear penalty for deviation from the tolerance edge.\n const excess = dist - tolerance;\n const slope = 2.5; // Falloff rate (0.4 deviation = 0 score)\n\n return Math.max(0, 1.0 - excess * slope);\n}","import { OrchestrationContext } from './index';\nimport { computeOutcomeSignal, SignalConfig } from './signal';\nimport { UserOutcomeRecord } from '../types/userOutcome';\nimport { DocType, QuestionRecord } from '../types/types-legacy';\nimport { logger } from '../../util/logger';\n\n/**\n * Records a learning outcome for a specific period of time.\n *\n * This function:\n * 1. Computes a scalar \"success\" signal from the provided question records\n * 2. Re-computes the deviations that were active for this user/course\n * 3. Persists a UserOutcomeRecord to the user's database\n *\n * This record is later used by the optimization job to correlate\n * deviations with outcomes (Evolutionary Orchestration).\n *\n * @param context - Orchestration context (user, course, etc.)\n * @param periodStart - ISO timestamp of period start\n * @param periodEnd - ISO timestamp of period end (now)\n * @param records - Question records generated during this period\n * @param activeStrategyIds - IDs of strategies active in this course\n * @param eloStart - User's ELO at start of period (optional)\n * @param eloEnd - User's ELO at end of period (optional)\n * @param config - Optional configuration for signal computation\n */\nexport async function recordUserOutcome(\n context: OrchestrationContext,\n periodStart: string,\n periodEnd: string,\n records: QuestionRecord[],\n activeStrategyIds: string[],\n eloStart: number = 0,\n eloEnd: number = 0,\n config?: SignalConfig\n): Promise<void> {\n const { user, course, userId } = context;\n const courseId = course.getCourseID();\n\n // 1. Compute Signal\n // If we have no records, we can't determine an outcome.\n const outcomeValue = computeOutcomeSignal(records, config);\n\n if (outcomeValue === null) {\n logger.debug(\n `[Orchestration] No outcome signal computed for ${userId} (insufficient data). Skipping record.`\n );\n return;\n }\n\n // 2. Capture Deviations\n // We re-compute the deterministic deviations for all active strategies.\n // This tells the learning algorithm \"what parameter adjustments were active\n // when this outcome was achieved\".\n const deviations: Record<string, number> = {};\n for (const strategyId of activeStrategyIds) {\n deviations[strategyId] = context.getDeviation(strategyId);\n }\n\n // 3. Construct Record\n // ID format: USER_OUTCOME::{courseId}::{userId}::{periodEnd}\n // This ensures uniqueness per user/course/time-period.\n const id = `USER_OUTCOME::${courseId}::${userId}::${periodEnd}`;\n\n const record: UserOutcomeRecord = {\n _id: id,\n docType: DocType.USER_OUTCOME,\n courseId,\n userId,\n periodStart,\n periodEnd,\n outcomeValue,\n deviations,\n metadata: {\n sessionsCount: 1, // Assumes recording is triggered per-session currently\n cardsSeen: records.length,\n eloStart,\n eloEnd,\n signalType: 'accuracy_in_zone',\n },\n };\n\n // 4. Persist\n try {\n await user.putUserOutcome(record);\n logger.debug(\n `[Orchestration] Recorded outcome ${outcomeValue.toFixed(3)} for ${userId} (doc: ${id})`\n );\n } catch (e) {\n logger.error(`[Orchestration] Failed to record outcome: ${e}`);\n }\n}","import type { UserDBInterface } from '../interfaces/userDB';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { LearnableWeight } from '../types/contentNavigationStrategy';\nimport type { CourseConfig } from '@vue-skuilder/common';\nimport { logger } from '../../util/logger';\n\n// Re-export gradient and learning functions\nexport { aggregateOutcomesForGradient, computeStrategyGradient } from './gradient';\nexport {\n updateStrategyWeight,\n updateLearningState,\n runPeriodUpdate,\n getDefaultLearnableWeight,\n} from './learning';\nexport type { PeriodUpdateInput, PeriodUpdateResult } from './learning';\n\n// Re-export signal functions\nexport { computeOutcomeSignal, scoreAccuracyInZone } from './signal';\nexport type { SignalConfig } from './signal';\n\n// Re-export recording functions\nexport { recordUserOutcome } from './recording';\n\n// Re-export types\nexport type { GradientObservation, GradientResult, StrategyLearningState } from '../types/learningState';\nexport type { UserOutcomeRecord } from '../types/userOutcome';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\n/**\n * Context for orchestration decisions during a session.\n * \n * Provides access to user/course data and helper methods for determining\n * effective strategy weights based on the user's cohort assignment.\n */\nexport interface OrchestrationContext {\n user: UserDBInterface;\n course: CourseDBInterface;\n userId: string;\n courseConfig: CourseConfig;\n \n /**\n * Calculate the effective weight for a strategy for this user.\n * \n * Applies deviation based on the user's cohort assignment (derived from\n * userId, strategyId, and course salt).\n * \n * @param strategyId - Unique ID of the strategy\n * @param learnable - The strategy's learning configuration\n * @returns Effective weight multiplier (typically 0.1 - 3.0)\n */\n getEffectiveWeight(strategyId: string, learnable: LearnableWeight): number;\n\n /**\n * Get the deviation factor for this user/strategy.\n * Range [-1.0, 1.0].\n */\n getDeviation(strategyId: string): number;\n}\n\n// ============================================================================\n// DEVIATION LOGIC\n// ============================================================================\n\nconst MIN_SPREAD = 0.1;\nconst MAX_SPREAD = 0.5;\nconst MIN_WEIGHT = 0.1;\nconst MAX_WEIGHT = 3.0;\n\n/**\n * FNV-1a hash implementation for deterministic distribution.\n */\nfunction fnv1a(str: string): number {\n let hash = 2166136261;\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = Math.imul(hash, 16777619);\n }\n return hash >>> 0;\n}\n\n/**\n * Compute a user's deviation for a specific strategy.\n * \n * Returns a value in [-1, 1] that is:\n * 1. Deterministic for the same (user, strategy, salt) tuple\n * 2. Uniformly distributed across users\n * 3. Uncorrelated between different strategies (due to strategyId in hash)\n * 4. Rotatable by changing the salt\n * \n * @param userId - ID of the user\n * @param strategyId - ID of the strategy\n * @param salt - Random seed from course config\n * @returns Deviation factor between -1.0 and 1.0\n */\nexport function computeDeviation(userId: string, strategyId: string, salt: string): number {\n const input = `${userId}:${strategyId}:${salt}`;\n const hash = fnv1a(input);\n \n // Normalize 32-bit unsigned integer to [0, 1]\n const normalized = hash / 4294967296;\n \n // Map [0, 1] to [-1, 1]\n return (normalized * 2) - 1;\n}\n\n/**\n * Compute the exploration spread based on confidence.\n * \n * - Low confidence (0.0) -> Max spread (Explore broadly)\n * - High confidence (1.0) -> Min spread (Exploit known good weight)\n * \n * @param confidence - Confidence level 0-1\n * @returns Spread magnitude (half-width of the distribution)\n */\nexport function computeSpread(confidence: number): number {\n // Linear interpolation: confidence 0 -> MAX_SPREAD, confidence 1 -> MIN_SPREAD\n const clampedConfidence = Math.max(0, Math.min(1, confidence));\n return MAX_SPREAD - (clampedConfidence * (MAX_SPREAD - MIN_SPREAD));\n}\n\n/**\n * Calculate the effective weight for a strategy instance.\n * \n * Combines the learnable weight (peak) with the user's deviation and the\n * allowed spread (based on confidence).\n * \n * @param learnable - Strategy learning config\n * @param userId - User ID\n * @param strategyId - Strategy ID\n * @param salt - Course salt\n * @returns Effective weight multiplier\n */\nexport function computeEffectiveWeight(\n learnable: LearnableWeight,\n userId: string,\n strategyId: string,\n salt: string\n): number {\n const deviation = computeDeviation(userId, strategyId, salt);\n const spread = computeSpread(learnable.confidence);\n \n // Apply deviation: effective = weight + (deviation * spread * weight)\n // We scale the spread relative to the weight itself so it's proportional.\n // e.g. weight 2.0, deviation -0.5, spread 0.2 -> 2.0 + (-0.5 * 0.2 * 2.0) = 1.8\n const adjustment = deviation * spread * learnable.weight;\n \n const effective = learnable.weight + adjustment;\n \n // Clamp to sane bounds to prevent runaway weights or negative multipliers\n return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, effective));\n}\n\n// ============================================================================\n// CONTEXT FACTORY\n// ============================================================================\n\n/**\n * Create an orchestration context for a study session.\n * \n * Fetches necessary configuration to enable deterministic weight calculation.\n * \n * @param user - User DB interface\n * @param course - Course DB interface\n * @returns Initialized orchestration context\n */\nexport async function createOrchestrationContext(\n user: UserDBInterface,\n course: CourseDBInterface\n): Promise<OrchestrationContext> {\n let courseConfig: CourseConfig;\n try {\n courseConfig = await course.getCourseConfig();\n } catch (e) {\n logger.error(`[Orchestration] Failed to load course config: ${e}`);\n // Fallback stub if config load fails\n courseConfig = {\n name: 'Unknown',\n description: '',\n public: false,\n deleted: false,\n creator: '',\n admins: [],\n moderators: [],\n dataShapes: [],\n questionTypes: [],\n orchestration: { salt: 'default' },\n };\n }\n\n const userId = user.getUsername(); // Or user ID if available on interface\n const salt = courseConfig.orchestration?.salt || 'default_salt';\n\n return {\n user,\n course,\n userId,\n courseConfig,\n \n getEffectiveWeight(strategyId: string, learnable: LearnableWeight): number {\n return computeEffectiveWeight(learnable, userId, strategyId, salt);\n },\n\n getDeviation(strategyId: string): number {\n return computeDeviation(userId, strategyId, salt);\n }\n };\n}","import { toCourseElo } from '@vue-skuilder/common';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { CardFilter, FilterContext } from './filters/types';\nimport type { CardGenerator, GeneratorContext } from './generators/types';\nimport { logger } from '../../util/logger';\nimport { createOrchestrationContext, OrchestrationContext } from '../orchestration';\nimport { captureRun, buildRunReport, registerPipelineForDebug, type GeneratorSummary, type FilterImpact } from './PipelineDebugger';\n\n// ============================================================================\n// REPLAN HINTS\n// ============================================================================\n//\n// Ephemeral, one-shot scoring hints passed at replan time.\n// Applied after the filter chain, consumed after one pipeline run.\n//\n// Tag patterns support glob-style matching:\n// 'gpc:exercise:t-T' — exact match\n// 'gpc:intro:*' — all intro tags\n// 'gpc:exercise:t-*' — all t-variant exercises\n//\n\n/**\n * Ephemeral pipeline hints for a single run.\n * All fields are optional. Tag/card patterns support `*` wildcards.\n */\nexport interface ReplanHints {\n /** Multiply scores for cards matching these tag patterns. */\n boostTags?: Record<string, number>;\n /** Multiply scores for these specific card IDs (glob patterns). */\n boostCards?: Record<string, number>;\n /** Cards matching these tag patterns MUST appear in results. */\n requireTags?: string[];\n /** These specific card IDs MUST appear in results. */\n requireCards?: string[];\n /** Remove cards matching these tag patterns from results. */\n excludeTags?: string[];\n /** Remove these specific card IDs from results. */\n excludeCards?: string[];\n /**\n * Debugging label threaded from the replan requester.\n * Attached to provenance entries so card scoring history\n * can be traced back to the originating event.\n * Prefixed with `_` to signal it's metadata, not a scoring hint.\n */\n _label?: string;\n}\n\n/**\n * Convert a glob pattern (with `*` wildcards) to a RegExp.\n * Only `*` is supported as a wildcard (matches any characters).\n */\nfunction globToRegex(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const withWildcards = escaped.replace(/\\*/g, '.*');\n return new RegExp(`^${withWildcards}$`);\n}\n\n/** Test whether a string matches a glob pattern. */\nfunction globMatch(value: string, pattern: string): boolean {\n if (!pattern.includes('*')) return value === pattern;\n return globToRegex(pattern).test(value);\n}\n\n/** Test whether any of a card's tags match a glob pattern. */\nfunction cardMatchesTagPattern(card: WeightedCard, pattern: string): boolean {\n return (card.tags ?? []).some((tag) => globMatch(tag, pattern));\n}\n\n// ============================================================================\n// PIPELINE LOGGING HELPERS\n// ============================================================================\n//\n// Focused logging functions that can be toggled by commenting single lines.\n// Use these to inspect pipeline behavior in development/production.\n//\n\n/**\n * Log pipeline configuration on construction.\n * Shows generator and filter chain structure.\n */\nfunction logPipelineConfig(generator: CardGenerator, filters: CardFilter[]): void {\n const filterList =\n filters.length > 0 ? '\\n - ' + filters.map((f) => f.name).join('\\n - ') : ' none';\n\n logger.info(\n `[Pipeline] Configuration:\\n` + ` Generator: ${generator.name}\\n` + ` Filters:${filterList}`\n );\n}\n\n/**\n * Log tag hydration results.\n * Shows effectiveness of batch query (how many cards/tags were hydrated).\n */\nfunction logTagHydration(cards: WeightedCard[], tagsByCard: Map<string, string[]>): void {\n const totalTags = Array.from(tagsByCard.values()).reduce((sum, tags) => sum + tags.length, 0);\n const cardsWithTags = Array.from(tagsByCard.values()).filter((tags) => tags.length > 0).length;\n\n logger.debug(\n `[Pipeline] Tag hydration: ${cards.length} cards, ` +\n `${cardsWithTags} have tags (${totalTags} total tags) - single batch query`\n );\n}\n\n/**\n * Log pipeline execution summary.\n * Shows complete flow from generator through filters to final results.\n */\nfunction logExecutionSummary(\n generatorName: string,\n generatedCount: number,\n filterCount: number,\n finalCount: number,\n topScores: number[],\n filterImpacts: Array<{ name: string; boosted: number; penalized: number; passed: number }>\n): void {\n const scoreDisplay =\n topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(', ') : 'none';\n\n let filterSummary = '';\n if (filterImpacts.length > 0) {\n const impacts = filterImpacts.map((f) => {\n const parts: string[] = [];\n if (f.boosted > 0) parts.push(`+${f.boosted}`);\n if (f.penalized > 0) parts.push(`-${f.penalized}`);\n if (f.passed > 0) parts.push(`=${f.passed}`);\n return `${f.name}: ${parts.join('/')}`;\n });\n filterSummary = `\\n Filter impact: ${impacts.join(', ')}`;\n }\n\n logger.info(\n `[Pipeline] Execution: ${generatorName} produced ${generatedCount} → ` +\n `${filterCount} filters → ${finalCount} results (top scores: ${scoreDisplay})` +\n filterSummary +\n `\\n 💡 Inspect: window.skuilder.pipeline`\n );\n}\n\n/**\n * Log all result cards with score, cardId, and key provenance.\n * Toggle: set VERBOSE_RESULTS = true to enable.\n */\nconst VERBOSE_RESULTS = true;\n\nfunction logResultCards(cards: WeightedCard[]): void {\n if (!VERBOSE_RESULTS || cards.length === 0) return;\n\n logger.info(`[Pipeline] Results (${cards.length} cards):`);\n for (let i = 0; i < cards.length; i++) {\n const c = cards[i];\n const tags = c.tags?.slice(0, 3).join(', ') || '';\n const filters = c.provenance\n .filter((p) => p.strategy === 'hierarchyDefinition' || p.strategy === 'priorityDefinition' || p.strategy === 'interferenceFilter' || p.strategy === 'letterGating' || p.strategy === 'ephemeralHint')\n .map((p) => {\n const arrow = p.action === 'boosted' ? '↑' : p.action === 'penalized' ? '↓' : '=';\n return `${p.strategyName}${arrow}${p.score.toFixed(2)}`;\n })\n .join(' | ');\n logger.info(\n `[Pipeline] ${String(i + 1).padStart(2)}. ${c.score.toFixed(4)} ${c.cardId} [${tags}]${filters ? ` {${filters}}` : ''}`\n );\n }\n}\n\n/**\n * Log provenance trails for cards.\n * Shows the complete scoring history for each card through the pipeline.\n * Useful for debugging why cards scored the way they did.\n */\nfunction logCardProvenance(cards: WeightedCard[], maxCards: number = 3): void {\n const cardsToLog = cards.slice(0, maxCards);\n\n logger.debug(`[Pipeline] Provenance for top ${cardsToLog.length} cards:`);\n\n for (const card of cardsToLog) {\n logger.debug(`[Pipeline] ${card.cardId} (final score: ${card.score.toFixed(3)}):`);\n\n for (const entry of card.provenance) {\n const scoreChange = entry.score.toFixed(3);\n const action = entry.action.padEnd(9); // Align columns\n logger.debug(\n `[Pipeline] ${action} ${scoreChange} - ${entry.strategyName}: ${entry.reason}`\n );\n }\n }\n}\n\n// ============================================================================\n// PIPELINE\n// ============================================================================\n//\n// Executes a navigation pipeline: generator → filters → sorted results.\n//\n// Architecture:\n// cards = generator.getWeightedCards(limit, context)\n// cards = filter1.transform(cards, context)\n// cards = filter2.transform(cards, context)\n// cards = filter3.transform(cards, context)\n// return sorted(cards).slice(0, limit)\n//\n// Benefits:\n// - Clear separation: generators produce, filters transform\n// - No nested instantiation complexity\n// - Filters don't need to know about each other\n// - Shared context built once, passed to all stages\n//\n// ============================================================================\n\n/**\n * A navigation pipeline that runs a generator and applies filters sequentially.\n *\n * Implements StudyContentSource for backward compatibility with SessionController.\n *\n * ## Usage\n *\n * ```typescript\n * const pipeline = new Pipeline(\n * compositeGenerator, // or single generator\n * [eloDistanceFilter, interferenceFilter],\n * user,\n * course\n * );\n *\n * const cards = await pipeline.getWeightedCards(20);\n * ```\n */\nexport class Pipeline extends ContentNavigator {\n private generator: CardGenerator;\n private filters: CardFilter[];\n\n /**\n * Cached orchestration context. Course config and salt don't change within\n * a page load, so we build the orchestration context once and reuse it on\n * subsequent getWeightedCards() calls (e.g. mid-session replans).\n *\n * This eliminates a remote getCourseConfig() round trip per pipeline run.\n */\n private _cachedOrchestration: OrchestrationContext | null = null;\n\n /**\n * Persistent tag cache. Maps cardId → tag names.\n *\n * Tags are static within a session (they're set at card generation time),\n * so we cache them across pipeline runs. On replans, many of the same cards\n * reappear — cache hits avoid redundant remote getAppliedTagsBatch() queries.\n */\n private _tagCache: Map<string, string[]> = new Map();\n\n /**\n * One-shot replan hints. Applied after the filter chain on the next\n * getWeightedCards() call, then cleared.\n */\n private _ephemeralHints: ReplanHints | null = null;\n\n /**\n * Create a new pipeline.\n *\n * @param generator - The generator (or CompositeGenerator) that produces candidates\n * @param filters - Filters to apply sequentially (order doesn't matter for multipliers)\n * @param user - User database interface\n * @param course - Course database interface\n */\n constructor(\n generator: CardGenerator,\n filters: CardFilter[],\n user: UserDBInterface,\n course: CourseDBInterface\n ) {\n super();\n this.generator = generator;\n this.filters = filters;\n this.user = user;\n this.course = course;\n\n course\n .getCourseConfig()\n .then((cfg) => {\n logger.debug(`[pipeline] Crated pipeline for ${cfg.name}`);\n })\n .catch((e) => {\n logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`);\n });\n // Toggle pipeline configuration logging:\n logPipelineConfig(generator, filters);\n\n // Register for debug API access\n registerPipelineForDebug(this);\n }\n\n /**\n * Set one-shot hints for the next pipeline run.\n * Consumed after one getWeightedCards() call, then cleared.\n *\n * Overrides ContentNavigator.setEphemeralHints() no-op.\n */\n override setEphemeralHints(hints: Record<string, unknown>): void {\n this._ephemeralHints = hints as ReplanHints;\n logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(hints)}`);\n }\n\n /**\n * Get weighted cards by running generator and applying filters.\n *\n * 1. Build shared context (user ELO, etc.)\n * 2. Get candidates from generator (passing context)\n * 3. Batch hydrate tags for all candidates\n * 4. Apply each filter sequentially\n * 5. Remove zero-score cards\n * 6. Sort by score descending\n * 7. Return top N\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n const t0 = performance.now();\n\n // Build shared context once\n const context = await this.buildContext();\n const tContext = performance.now();\n\n // Over-fetch from generator to give filters a wide candidate pool.\n // With local course DB the cost is negligible (~20ms for 500 cards).\n // Filters (hierarchy, letter gating, etc.) can be aggressive — a wide\n // pool ensures enough well-indicated candidates survive.\n const fetchLimit = 500;\n\n logger.debug(\n `[Pipeline] Fetching ${fetchLimit} candidates from generator '${this.generator.name}'`\n );\n\n // Get candidates from generator, passing context\n let cards = await this.generator.getWeightedCards(fetchLimit, context);\n const tGenerate = performance.now();\n const generatedCount = cards.length;\n \n // Capture generator breakdown for debugging (if CompositeGenerator)\n let generatorSummaries: GeneratorSummary[] | undefined;\n if ((this.generator as any).generators) {\n // This is a CompositeGenerator - extract per-generator info from provenance\n const genMap = new Map<string, { cards: WeightedCard[] }>();\n for (const card of cards) {\n const firstProv = card.provenance[0];\n if (firstProv) {\n const genName = firstProv.strategyName;\n if (!genMap.has(genName)) {\n genMap.set(genName, { cards: [] });\n }\n genMap.get(genName)!.cards.push(card);\n }\n }\n generatorSummaries = Array.from(genMap.entries()).map(([name, data]) => {\n const newCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes('new card'));\n const reviewCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes('review'));\n return {\n name,\n cardCount: data.cards.length,\n newCount: newCards.length,\n reviewCount: reviewCards.length,\n topScore: Math.max(...data.cards.map((c) => c.score), 0),\n };\n });\n }\n\n logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);\n\n // Batch hydrate tags before filters run\n cards = await this.hydrateTags(cards);\n const tHydrate = performance.now();\n \n // Keep a copy of all cards for debug capture (before filtering removes any)\n const allCardsBeforeFiltering = [...cards];\n\n // Apply filters sequentially, tracking impact\n const filterImpacts: FilterImpact[] = [];\n for (const filter of this.filters) {\n const beforeCount = cards.length;\n const beforeScores = new Map(cards.map((c) => [c.cardId, c.score]));\n cards = await filter.transform(cards, context);\n \n // Count boost/penalize/pass/removed for this filter\n let boosted = 0, penalized = 0, passed = 0;\n const removed = beforeCount - cards.length;\n \n for (const card of cards) {\n const before = beforeScores.get(card.cardId) ?? 0;\n if (card.score > before) boosted++;\n else if (card.score < before) penalized++;\n else passed++;\n }\n filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });\n \n logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} → ${cards.length} cards (↑${boosted} ↓${penalized} =${passed})`);\n }\n\n // Remove zero-score cards (hard filtered)\n cards = cards.filter((c) => c.score > 0);\n\n // Apply ephemeral hints (one-shot, post-filter)\n const hints = this._ephemeralHints;\n if (hints) {\n this._ephemeralHints = null; // consume\n cards = this.applyHints(cards, hints, allCardsBeforeFiltering);\n }\n\n // Sort by score descending\n cards.sort((a, b) => b.score - a.score);\n\n // Return top N\n const tFilter = performance.now();\n const result = cards.slice(0, limit);\n\n logger.info(\n `[Pipeline:timing] total=${(tFilter - t0).toFixed(0)}ms ` +\n `(context=${(tContext - t0).toFixed(0)} generate=${(tGenerate - tContext).toFixed(0)} ` +\n `hydrate=${(tHydrate - tGenerate).toFixed(0)} filter=${(tFilter - tHydrate).toFixed(0)})`\n );\n\n // Toggle execution summary logging:\n const topScores = result.slice(0, 3).map((c) => c.score);\n logExecutionSummary(\n this.generator.name,\n generatedCount,\n this.filters.length,\n result.length,\n topScores,\n filterImpacts\n );\n\n // Toggle verbose result listing:\n logResultCards(result);\n\n // Toggle provenance logging (shows scoring history for top cards):\n logCardProvenance(result, 3);\n\n // Capture run for debug API\n try {\n const courseName = await this.course?.getCourseConfig().then((c) => c.name).catch(() => undefined);\n const report = buildRunReport(\n this.course?.getCourseID() || 'unknown',\n courseName,\n this.generator.name,\n generatorSummaries,\n generatedCount,\n filterImpacts,\n allCardsBeforeFiltering,\n result\n );\n captureRun(report);\n } catch (e) {\n logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);\n }\n\n return result;\n }\n\n /**\n * Batch hydrate tags for all cards.\n *\n * Fetches tags for all cards in a single database query and attaches them\n * to the WeightedCard objects. Filters can then use card.tags instead of\n * making individual getAppliedTags() calls.\n *\n * Uses a persistent tag cache across pipeline runs — tags are static within\n * a session, so cards seen in a prior run (e.g. before a replan) don't\n * require a second DB query.\n *\n * @param cards - Cards to hydrate\n * @returns Cards with tags populated\n */\n private async hydrateTags(cards: WeightedCard[]): Promise<WeightedCard[]> {\n if (cards.length === 0) {\n return cards;\n }\n\n // Separate cards with cached tags from those needing a DB query\n const uncachedIds: string[] = [];\n for (const card of cards) {\n if (!this._tagCache.has(card.cardId)) {\n uncachedIds.push(card.cardId);\n }\n }\n\n // Only query the DB for cards not already in cache\n if (uncachedIds.length > 0) {\n const freshTags = await this.course!.getAppliedTagsBatch(uncachedIds);\n for (const [cardId, tags] of freshTags) {\n this._tagCache.set(cardId, tags);\n }\n }\n\n // Build the tagsByCard map from cache (for logging compatibility)\n const tagsByCard = new Map<string, string[]>();\n for (const card of cards) {\n tagsByCard.set(card.cardId, this._tagCache.get(card.cardId) ?? []);\n }\n\n // Toggle tag hydration logging:\n logTagHydration(cards, tagsByCard);\n\n return cards.map((card) => ({\n ...card,\n tags: this._tagCache.get(card.cardId) ?? [],\n }));\n }\n\n // ---------------------------------------------------------------------------\n // Ephemeral hints application\n // ---------------------------------------------------------------------------\n\n /**\n * Apply one-shot replan hints to the post-filter card set.\n *\n * Order of operations:\n * 1. Exclude (remove unwanted cards)\n * 2. Boost (multiply scores)\n * 3. Require (inject must-have cards from the full pre-filter pool)\n *\n * @param cards - Post-filter cards (score > 0)\n * @param hints - The ephemeral hints to apply\n * @param allCards - Full pre-filter card pool (for require injection)\n */\n private applyHints(\n cards: WeightedCard[],\n hints: ReplanHints,\n allCards: WeightedCard[]\n ): WeightedCard[] {\n const beforeCount = cards.length;\n\n // 1. Exclude\n if (hints.excludeCards?.length) {\n cards = cards.filter(\n (c) => !hints.excludeCards!.some((pat) => globMatch(c.cardId, pat))\n );\n }\n if (hints.excludeTags?.length) {\n cards = cards.filter(\n (c) => !hints.excludeTags!.some((pat) => cardMatchesTagPattern(c, pat))\n );\n }\n\n // 2. Boost\n if (hints.boostTags) {\n for (const [pattern, factor] of Object.entries(hints.boostTags)) {\n for (const card of cards) {\n if (cardMatchesTagPattern(card, pattern)) {\n card.score *= factor;\n card.provenance.push({\n strategy: 'ephemeralHint',\n strategyId: 'ephemeral-hint',\n strategyName: hints._label ? `Replan Hint (${hints._label})` : 'Replan Hint',\n action: 'boosted',\n score: card.score,\n reason: `boostTag ${pattern} ×${factor}`,\n });\n }\n }\n }\n }\n if (hints.boostCards) {\n for (const [pattern, factor] of Object.entries(hints.boostCards)) {\n for (const card of cards) {\n if (globMatch(card.cardId, pattern)) {\n card.score *= factor;\n card.provenance.push({\n strategy: 'ephemeralHint',\n strategyId: 'ephemeral-hint',\n strategyName: hints._label ? `Replan Hint (${hints._label})` : 'Replan Hint',\n action: 'boosted',\n score: card.score,\n reason: `boostCard ${pattern} ×${factor}`,\n });\n }\n }\n }\n }\n\n // 3. Require — inject from the full pool if not already present\n const cardIds = new Set(cards.map((c) => c.cardId));\n const hintLabel = hints._label ? `Replan Hint (${hints._label})` : 'Replan Hint';\n const inject = (card: WeightedCard, reason: string) => {\n if (!cardIds.has(card.cardId)) {\n // Give required cards a floor score so they sort above zero-score filler\n const floorScore = Math.max(card.score, 1.0);\n cards.push({\n ...card,\n score: floorScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'ephemeralHint',\n strategyId: 'ephemeral-hint',\n strategyName: hintLabel,\n action: 'boosted',\n score: floorScore,\n reason,\n },\n ],\n });\n cardIds.add(card.cardId);\n }\n };\n\n if (hints.requireCards?.length) {\n for (const pattern of hints.requireCards) {\n for (const card of allCards) {\n if (globMatch(card.cardId, pattern)) inject(card, `requireCard ${pattern}`);\n }\n }\n }\n if (hints.requireTags?.length) {\n for (const pattern of hints.requireTags) {\n for (const card of allCards) {\n if (cardMatchesTagPattern(card, pattern)) inject(card, `requireTag ${pattern}`);\n }\n }\n }\n\n logger.info(`[Pipeline] Hints applied: ${beforeCount} → ${cards.length} cards`);\n\n return cards;\n }\n\n /**\n * Build shared context for generator and filters.\n *\n * Called once per getWeightedCards() invocation.\n * Contains data that the generator and multiple filters might need.\n *\n * The context satisfies both GeneratorContext and FilterContext interfaces.\n */\n private async buildContext(): Promise<GeneratorContext & FilterContext> {\n let userElo = 1000; // Default ELO\n\n try {\n const courseReg = await this.user!.getCourseRegDoc(this.course!.getCourseID());\n const courseElo = toCourseElo(courseReg.elo);\n userElo = courseElo.global.score;\n } catch (e) {\n logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);\n }\n\n // Reuse cached orchestration context if available (course config is stable\n // within a page load). This avoids a remote getCourseConfig() call on\n // subsequent pipeline runs (e.g. mid-session replans).\n if (!this._cachedOrchestration) {\n this._cachedOrchestration = await createOrchestrationContext(this.user!, this.course!);\n }\n const orchestration = this._cachedOrchestration;\n\n return {\n user: this.user!,\n course: this.course!,\n userElo,\n orchestration,\n };\n }\n\n /**\n * Get the course ID for this pipeline.\n */\n getCourseID(): string {\n return this.course!.getCourseID();\n }\n\n /**\n * Get orchestration context for outcome recording.\n */\n async getOrchestrationContext(): Promise<OrchestrationContext> {\n return createOrchestrationContext(this.user!, this.course!);\n }\n\n /**\n * Get IDs of all strategies in this pipeline.\n * Used to record which strategies contributed to an outcome.\n */\n getStrategyIds(): string[] {\n const ids: string[] = [];\n\n const extractId = (obj: any): string | null => {\n // Check for strategyId property (ContentNavigator, WeightedFilter)\n if (obj.strategyId) return obj.strategyId;\n return null;\n };\n\n // Generator(s)\n const genId = extractId(this.generator);\n if (genId) ids.push(genId);\n\n // Inspect CompositeGenerator children (accessing private field via cast)\n if ((this.generator as any).generators && Array.isArray((this.generator as any).generators)) {\n (this.generator as any).generators.forEach((g: any) => {\n const subId = extractId(g);\n if (subId) ids.push(subId);\n });\n }\n\n // Filters\n for (const filter of this.filters) {\n const fId = extractId(filter);\n if (fId) ids.push(fId);\n }\n\n return [...new Set(ids)];\n }\n\n // ---------------------------------------------------------------------------\n // Card-space diagnostic\n // ---------------------------------------------------------------------------\n\n /**\n * Scan every card in the course through the filter chain and report\n * how many are \"well indicated\" (score >= threshold) for the current user.\n *\n * Also reports how many well-indicated cards the user has NOT yet encountered.\n *\n * Exposed via `window.skuilder.pipeline.diagnoseCardSpace()`.\n */\n async diagnoseCardSpace(opts?: { threshold?: number }): Promise<CardSpaceDiagnosis> {\n const THRESHOLD = opts?.threshold ?? 0.10;\n const t0 = performance.now();\n\n // 1. Get all card IDs\n const allCardIds = await this.course!.getAllCardIds();\n\n // 2. Build dummy WeightedCards (score=1.0, no provenance)\n let cards: WeightedCard[] = allCardIds.map((cardId) => ({\n cardId,\n courseId: this.course!.getCourseID(),\n score: 1.0,\n provenance: [],\n }));\n\n // 3. Hydrate tags\n cards = await this.hydrateTags(cards);\n\n // 4. Run through filters\n const context = await this.buildContext();\n const filterBreakdown: Array<{ name: string; wellIndicated: number }> = [];\n\n // Track cumulative filter effects\n for (const filter of this.filters) {\n cards = await filter.transform(cards, context);\n const wi = cards.filter((c) => c.score >= THRESHOLD).length;\n filterBreakdown.push({ name: filter.name, wellIndicated: wi });\n }\n\n // 5. Count well-indicated\n const wellIndicated = cards.filter((c) => c.score >= THRESHOLD);\n const wellIndicatedIds = new Set(wellIndicated.map((c) => c.cardId));\n\n // 6. Get encountered cards\n let encounteredIds: Set<string>;\n try {\n const courseId = this.course!.getCourseID();\n const seenCards = await this.user!.getSeenCards(courseId);\n encounteredIds = new Set(seenCards);\n } catch {\n encounteredIds = new Set();\n }\n\n const wellIndicatedNew = wellIndicated.filter((c) => !encounteredIds.has(c.cardId));\n\n // 7. Group by card type\n const byType = new Map<string, { total: number; wellIndicated: number; new: number }>();\n for (const card of cards) {\n const type = card.cardId.split('-')[1] || 'unknown'; // c-ws-... → ws, c-intro-... → intro, etc.\n if (!byType.has(type)) {\n byType.set(type, { total: 0, wellIndicated: 0, new: 0 });\n }\n const entry = byType.get(type)!;\n entry.total++;\n if (card.score >= THRESHOLD) {\n entry.wellIndicated++;\n if (!encounteredIds.has(card.cardId)) entry.new++;\n }\n }\n\n const elapsed = performance.now() - t0;\n\n const result: CardSpaceDiagnosis = {\n totalCards: allCardIds.length,\n threshold: THRESHOLD,\n wellIndicated: wellIndicatedIds.size,\n encountered: encounteredIds.size,\n wellIndicatedNew: wellIndicatedNew.length,\n byType: Object.fromEntries(byType),\n filterBreakdown,\n elapsedMs: Math.round(elapsed),\n };\n\n // Log to console\n logger.info(`[Pipeline:diagnose] Card space scan (${result.elapsedMs}ms):`);\n logger.info(`[Pipeline:diagnose] Total cards: ${result.totalCards}`);\n logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${THRESHOLD}): ${result.wellIndicated}`);\n logger.info(`[Pipeline:diagnose] Encountered: ${result.encountered}`);\n logger.info(`[Pipeline:diagnose] Well-indicated & new: ${result.wellIndicatedNew}`);\n logger.info(`[Pipeline:diagnose] By type:`);\n for (const [type, counts] of byType) {\n logger.info(\n `[Pipeline:diagnose] ${type}: ${counts.wellIndicated}/${counts.total} well-indicated, ${counts.new} new`\n );\n }\n logger.info(`[Pipeline:diagnose] After each filter:`);\n for (const fb of filterBreakdown) {\n logger.info(`[Pipeline:diagnose] ${fb.name}: ${fb.wellIndicated} well-indicated`);\n }\n\n return result;\n }\n\n}\n\n/**\n * Diagnosis of the full card space for the current user.\n */\nexport interface CardSpaceDiagnosis {\n totalCards: number;\n threshold: number;\n wellIndicated: number;\n encountered: number;\n wellIndicatedNew: number;\n byType: Record<string, { total: number; wellIndicated: number; new: number }>;\n filterBreakdown: Array<{ name: string; wellIndicated: number }>;\n elapsedMs: number;\n}\n","import { Navigators } from './index';\nimport { Pipeline } from './Pipeline';\nimport CompositeGenerator from './generators/CompositeGenerator';\nimport ELONavigator from './generators/elo';\nimport SRSNavigator from './generators/srs';\nimport { createEloDistanceFilter } from './filters/eloDistance';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { DocType } from '../types/types-legacy';\nimport type { CourseDBInterface, UserDBInterface } from '../interfaces';\n\n/**\n * Default navigation pipeline configuration.\n *\n * This module provides factory functions for creating the canonical default\n * navigation pipeline used by both CouchDB and static course implementations.\n */\n\n/**\n * Create default ELO navigation strategy data.\n * Used when no custom strategies are configured.\n *\n * @param courseId - The course ID to associate with this strategy\n * @returns Strategy data for default ELO navigation\n */\nexport function createDefaultEloStrategy(courseId: string): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-ELO-default',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO (default)',\n description: 'Default ELO-based navigation strategy for new cards',\n implementingClass: Navigators.ELO,\n course: courseId,\n serializedData: '',\n };\n}\n\n/**\n * Create default SRS navigation strategy data.\n * Used when no custom strategies are configured.\n *\n * @param courseId - The course ID to associate with this strategy\n * @returns Strategy data for default SRS navigation\n */\nexport function createDefaultSrsStrategy(courseId: string): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-SRS-default',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'SRS (default)',\n description: 'Default SRS-based navigation strategy for reviews',\n implementingClass: Navigators.SRS,\n course: courseId,\n serializedData: '',\n };\n}\n\n/**\n * Creates the default navigation pipeline for courses with no configured strategies.\n *\n * Default: Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\n * - ELO generator: scores new cards by skill proximity\n * - SRS generator: scores reviews by overdueness and interval recency\n * - ELO distance filter: penalizes cards far from user's current level\n *\n * This is the canonical default configuration used when:\n * - No navigation strategy documents exist in the course\n * - PipelineAssembler fails to build from strategy documents\n *\n * @param user - User database interface for accessing user state\n * @param course - Course database interface for accessing course data\n * @returns Configured Pipeline ready for use\n */\nexport function createDefaultPipeline(\n user: UserDBInterface,\n course: CourseDBInterface\n): Pipeline {\n const courseId = course.getCourseID();\n const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));\n const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));\n\n const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);\n const eloDistanceFilter = createEloDistanceFilter();\n\n return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);\n}\n","import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ContentNavigator, isGenerator, isFilter, Navigators } from './index';\nimport type { CardFilter } from './filters/types';\nimport { WeightedFilter } from './filters/WeightedFilter';\nimport type { CardGenerator } from './generators/types';\nimport { Pipeline } from './Pipeline';\nimport { logger } from '../../util/logger';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport CompositeGenerator from './generators/CompositeGenerator';\nimport { createDefaultEloStrategy, createDefaultSrsStrategy } from './defaults';\n\n// ============================================================================\n// PIPELINE ASSEMBLER\n// ============================================================================\n//\n// Assembles navigation strategies into a Pipeline instance.\n//\n// This class is DB-agnostic: it receives strategy documents and returns an\n// assembled, ready-to-use Pipeline. This separation enables:\n// 1. Use with different DB implementations (Couch, Static, etc.)\n// 2. Future dynamic/evolutionary strategy selection\n// 3. Easy unit testing without DB mocking\n//\n// Pipeline assembly:\n// 1. Separate strategies into generators and filters by role\n// 2. Instantiate generator(s) - wrap multiple in CompositeGenerator\n// 3. Instantiate filters\n// 4. Return Pipeline(generator, filters)\n//\n// ============================================================================\n\n/**\n * Input for pipeline assembly.\n */\nexport interface PipelineAssemblerInput {\n /** All strategy documents to assemble into a pipeline */\n strategies: ContentNavigationStrategyData[];\n /** User database interface (required for instantiation) */\n user: UserDBInterface;\n /** Course database interface (required for instantiation) */\n course: CourseDBInterface;\n}\n\n/**\n * Result of pipeline assembly.\n */\nexport interface PipelineAssemblyResult {\n /** The assembled pipeline, or null if assembly failed */\n pipeline: Pipeline | null;\n /** Generator strategies found (for informational purposes) */\n generatorStrategies: ContentNavigationStrategyData[];\n /** Filter strategies found (for informational purposes) */\n filterStrategies: ContentNavigationStrategyData[];\n /** Warnings encountered during assembly (logged but non-fatal) */\n warnings: string[];\n}\n\n/**\n * Assembles navigation strategies into a Pipeline.\n *\n * Instantiates generators and filters from strategy documents and\n * composes them into a ready-to-use Pipeline instance.\n */\nexport class PipelineAssembler {\n /**\n * Assembles a navigation pipeline from strategy documents.\n *\n * 1. Separates into generators and filters by role\n * 2. Validates at least one generator exists (or creates default ELO)\n * 3. Instantiates generators - wraps multiple in CompositeGenerator\n * 4. Instantiates filters\n * 5. Returns Pipeline(generator, filters)\n *\n * @param input - Strategy documents plus user/course interfaces\n * @returns Assembled pipeline and any warnings\n */\n async assemble(input: PipelineAssemblerInput): Promise<PipelineAssemblyResult> {\n const { strategies, user, course } = input;\n const warnings: string[] = [];\n\n if (strategies.length === 0) {\n return {\n pipeline: null,\n generatorStrategies: [],\n filterStrategies: [],\n warnings,\n };\n }\n\n // Separate generators from filters\n const generatorStrategies: ContentNavigationStrategyData[] = [];\n const filterStrategies: ContentNavigationStrategyData[] = [];\n\n for (const s of strategies) {\n if (isGenerator(s.implementingClass)) {\n generatorStrategies.push(s);\n } else if (isFilter(s.implementingClass)) {\n filterStrategies.push(s);\n } else {\n // Unknown strategy type — skip with warning\n warnings.push(`Unknown strategy type '${s.implementingClass}', skipping: ${s.name}`);\n }\n }\n\n // Always ensure ELO and SRS generators are present.\n // Custom generators (e.g., prescribed) supplement but don't replace them.\n const courseId = course.getCourseID();\n const hasElo = generatorStrategies.some((s) => s.implementingClass === Navigators.ELO);\n const hasSrs = generatorStrategies.some((s) => s.implementingClass === Navigators.SRS);\n\n if (!hasElo) {\n logger.debug('[PipelineAssembler] No ELO generator configured, adding default');\n generatorStrategies.push(createDefaultEloStrategy(courseId));\n }\n if (!hasSrs) {\n logger.debug('[PipelineAssembler] No SRS generator configured, adding default');\n generatorStrategies.push(createDefaultSrsStrategy(courseId));\n }\n\n if (generatorStrategies.length === 0) {\n warnings.push('No generator strategy found');\n return {\n pipeline: null,\n generatorStrategies: [],\n filterStrategies: [],\n warnings,\n };\n }\n\n // Instantiate generators\n let generator: CardGenerator;\n\n if (generatorStrategies.length === 1) {\n // Single generator\n const nav = await ContentNavigator.create(user, course, generatorStrategies[0]);\n generator = nav as unknown as CardGenerator;\n logger.debug(`[PipelineAssembler] Using single generator: ${generatorStrategies[0].name}`);\n } else {\n // Multiple generators - wrap in CompositeGenerator\n logger.debug(\n `[PipelineAssembler] Using CompositeGenerator for ${generatorStrategies.length} generators: ${generatorStrategies.map((g) => g.name).join(', ')}`\n );\n generator = await CompositeGenerator.fromStrategies(user, course, generatorStrategies);\n }\n\n // Instantiate filters\n const filters: CardFilter[] = [];\n\n // Sort filters alphabetically for deterministic ordering\n const sortedFilterStrategies = [...filterStrategies].sort((a, b) =>\n a.name.localeCompare(b.name)\n );\n\n for (const filterStrategy of sortedFilterStrategies) {\n try {\n const nav = await ContentNavigator.create(user, course, filterStrategy);\n // The navigator implements CardFilter\n if ('transform' in nav && typeof nav.transform === 'function') {\n let filter = nav as unknown as CardFilter;\n\n // Apply evolutionary weighting wrapper if configured\n if (filterStrategy.learnable) {\n filter = new WeightedFilter(\n filter,\n filterStrategy.learnable,\n filterStrategy.staticWeight,\n filterStrategy._id\n );\n }\n\n filters.push(filter);\n logger.debug(`[PipelineAssembler] Added filter: ${filterStrategy.name}`);\n } else {\n warnings.push(\n `Filter '${filterStrategy.name}' does not implement CardFilter.transform(), skipping`\n );\n }\n } catch (e) {\n warnings.push(`Failed to instantiate filter '${filterStrategy.name}': ${e}`);\n }\n }\n\n // Build pipeline\n const pipeline = new Pipeline(generator, filters, user, course);\n\n logger.debug(\n `[PipelineAssembler] Assembled pipeline with ${generatorStrategies.length} generator(s) and ${filters.length} filter(s)`\n );\n\n return {\n pipeline,\n generatorStrategies,\n filterStrategies: sortedFilterStrategies,\n warnings,\n };\n }\n}\n","import { StudyContentSource, UserDBInterface, CourseDBInterface } from '..';\n\n// Re-export filter types\nexport type { CardFilter, FilterContext, CardFilterFactory } from './filters/types';\n\n// Re-export generator types\nexport type { CardGenerator, GeneratorContext, CardGeneratorFactory } from './generators/types';\n\n// Re-export pipeline debugger API\nexport {\n pipelineDebugAPI,\n mountPipelineDebugger,\n type PipelineRunReport,\n type GeneratorSummary,\n type FilterImpact,\n} from './PipelineDebugger';\n\nimport { LearnableWeight } from '../types/contentNavigationStrategy';\nexport type { ContentNavigationStrategyData, LearnableWeight } from '../types/contentNavigationStrategy';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// NAVIGATOR REGISTRY\n// ============================================================================\n//\n// Static registry of navigator implementations. This allows ContentNavigator.create()\n// to instantiate navigators without relying on dynamic imports, which don't work\n// reliably in all environments (e.g., test runners, bundled code).\n//\n// Usage:\n// 1. Import your navigator class\n// 2. Call registerNavigator('implementingClass', YourNavigatorClass)\n// 3. ContentNavigator.create() will use the registry before falling back to\n// dynamic import\n//\n// ============================================================================\n\n/**\n * Type for navigator constructor functions.\n */\nexport type NavigatorConstructor = new (\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n) => ContentNavigator;\n\n/**\n * Entry in the navigator registry, storing the constructor and an optional\n * pipeline role. The role is used by PipelineAssembler to classify\n * consumer-registered navigators that aren't in the built-in Navigators enum.\n */\ninterface NavigatorRegistryEntry {\n constructor: NavigatorConstructor;\n role?: NavigatorRole;\n}\n\n/**\n * Registry mapping implementingClass names to navigator entries.\n * Populated by registerNavigator() and used by ContentNavigator.create().\n */\nconst navigatorRegistry = new Map<string, NavigatorRegistryEntry>();\n\n/**\n * Register a navigator implementation.\n *\n * Call this to make a navigator available for instantiation by\n * ContentNavigator.create() without relying on dynamic imports.\n *\n * Passing a `role` is optional for built-in navigators (whose roles are in\n * the hardcoded `NavigatorRoles` record), but **required** for consumer-\n * defined navigators that need to participate in pipeline assembly.\n *\n * @param implementingClass - The class name (e.g., 'elo', 'letterGatingFilter')\n * @param constructor - The navigator class constructor\n * @param role - Optional pipeline role (GENERATOR or FILTER)\n */\nexport function registerNavigator(\n implementingClass: string,\n constructor: NavigatorConstructor,\n role?: NavigatorRole\n): void {\n navigatorRegistry.set(implementingClass, { constructor, role });\n logger.debug(`[NavigatorRegistry] Registered: ${implementingClass}${role ? ` (${role})` : ''}`);\n}\n\n/**\n * Get a navigator constructor from the registry.\n *\n * @param implementingClass - The class name to look up\n * @returns The constructor, or undefined if not registered\n */\nexport function getRegisteredNavigator(implementingClass: string): NavigatorConstructor | undefined {\n return navigatorRegistry.get(implementingClass)?.constructor;\n}\n\n/**\n * Check if a navigator is registered.\n *\n * @param implementingClass - The class name to check\n * @returns true if registered, false otherwise\n */\nexport function hasRegisteredNavigator(implementingClass: string): boolean {\n return navigatorRegistry.has(implementingClass);\n}\n\n/**\n * Get the registered role for a navigator, if one was provided at registration.\n *\n * @param implementingClass - The class name to look up\n * @returns The role, or undefined if not registered or no role was specified\n */\nexport function getRegisteredNavigatorRole(implementingClass: string): NavigatorRole | undefined {\n return navigatorRegistry.get(implementingClass)?.role;\n}\n\n/**\n * Get all registered navigator names.\n * Useful for debugging and testing.\n */\nexport function getRegisteredNavigatorNames(): string[] {\n return Array.from(navigatorRegistry.keys());\n}\n\n/**\n * Initialize the navigator registry with all built-in navigators.\n *\n * This function dynamically imports all standard navigator implementations\n * and registers them. Call this once at application startup to ensure\n * all navigators are available.\n *\n * In test environments, this may need to be called explicitly before\n * using ContentNavigator.create().\n */\nexport async function initializeNavigatorRegistry(): Promise<void> {\n logger.debug('[NavigatorRegistry] Initializing built-in navigators...');\n\n // Import and register generators\n const [eloModule, srsModule] = await Promise.all([\n import('./generators/elo'),\n import('./generators/srs'),\n ]);\n const prescribedModule = await import('./generators/prescribed');\n registerNavigator('elo', eloModule.default);\n registerNavigator('srs', srsModule.default);\n registerNavigator('prescribed', prescribedModule.default);\n\n // Import and register filters\n const [\n hierarchyModule,\n interferenceModule,\n relativePriorityModule,\n userTagPreferenceModule,\n ] = await Promise.all([\n import('./filters/hierarchyDefinition'),\n import('./filters/interferenceMitigator'),\n import('./filters/relativePriority'),\n import('./filters/userTagPreference'),\n ]);\n registerNavigator('hierarchyDefinition', hierarchyModule.default);\n registerNavigator('interferenceMitigator', interferenceModule.default);\n registerNavigator('relativePriority', relativePriorityModule.default);\n registerNavigator('userTagPreference', userTagPreferenceModule.default);\n\n // Note: eloDistance uses a factory pattern (createEloDistanceFilter) rather than\n // a ContentNavigator class, so it's not registered here. It's used differently\n // via Pipeline composition.\n\n logger.debug(\n `[NavigatorRegistry] Initialized ${navigatorRegistry.size} navigators: ${getRegisteredNavigatorNames().join(', ')}`\n );\n}\n\n// ============================================================================\n// NAVIGATION STRATEGY API\n// ============================================================================\n//\n// This module defines the ContentNavigator base class and the WeightedCard type,\n// which form the foundation of the pluggable navigation strategy system.\n//\n// KEY CONCEPTS:\n//\n// 1. WeightedCard - A card with a suitability score (0-1) and provenance trail.\n// The provenance tracks how each strategy in the pipeline contributed to\n// the card's final score, ensuring transparency and debuggability.\n//\n// 2. ContentNavigator - Abstract base class for backward compatibility.\n// New code should use CardGenerator or CardFilter interfaces directly.\n//\n// 3. CardGenerator vs CardFilter:\n// - Generators (ELO, SRS) produce candidate cards with scores\n// - Filters (Hierarchy, Interference, Priority, EloDistance) transform scores\n//\n// 4. Pipeline architecture:\n// Pipeline(generator, [filter1, filter2, ...]) executes:\n// cards = generator.getWeightedCards()\n// cards = filter1.transform(cards, context)\n// cards = filter2.transform(cards, context)\n// return sorted(cards)\n//\n// 5. Provenance tracking - Each strategy adds an entry explaining its contribution.\n// This makes the system transparent and debuggable.\n//\n// ============================================================================\n\n/**\n * Tracks a single strategy's contribution to a card's final score.\n *\n * Each strategy in the pipeline adds a StrategyContribution entry to the\n * card's provenance array, creating an audit trail of scoring decisions.\n */\nexport interface StrategyContribution {\n /**\n * Strategy type (implementing class name).\n * Examples: 'elo', 'hierarchyDefinition', 'interferenceMitigator'\n */\n strategy: string;\n\n /**\n * Human-readable name identifying this specific strategy instance.\n * Extracted from ContentNavigationStrategyData.name.\n * Courses may have multiple instances of the same strategy type with\n * different configurations.\n *\n * Examples:\n * - \"ELO (default)\"\n * - \"Interference: b/d/p confusion\"\n * - \"Interference: phonetic confusables\"\n * - \"Priority: Common letters first\"\n */\n strategyName: string;\n\n /**\n * Unique database document ID for this strategy instance.\n * Extracted from ContentNavigationStrategyData._id.\n * Use this to fetch the full strategy configuration document.\n *\n * Examples:\n * - \"NAVIGATION_STRATEGY-ELO-default\"\n * - \"NAVIGATION_STRATEGY-interference-bdp\"\n * - \"NAVIGATION_STRATEGY-priority-common-letters\"\n */\n strategyId: string;\n\n /**\n * What the strategy did:\n * - 'generated': Strategy produced this card (generators only)\n * - 'passed': Strategy evaluated but didn't change score (transparent pass-through)\n * - 'boosted': Strategy increased the score\n * - 'penalized': Strategy decreased the score\n */\n action: 'generated' | 'passed' | 'boosted' | 'penalized';\n\n /** Score after this strategy's processing */\n score: number;\n\n /**\n * The effective weight applied for this strategy instance.\n * If using evolutionary orchestration, this includes deviation.\n * If omitted, implies weight 1.0 (legacy behavior).\n */\n effectiveWeight?: number;\n\n /**\n * The deviation factor applied to this user's cohort for this strategy.\n * Range [-1.0, 1.0].\n */\n deviation?: number;\n\n /**\n * Human-readable explanation of the strategy's decision.\n *\n * Examples:\n * - \"ELO distance 75, new card\"\n * - \"Prerequisites met: letter-sounds\"\n * - \"Interferes with immature tag 'd' (decay 0.8)\"\n * - \"High-priority tag 's' (0.95) → boost 1.15x\"\n *\n * Required for transparency - silent adjusters are anti-patterns.\n */\n reason: string;\n}\n\n/**\n * A card with a suitability score and provenance trail.\n *\n * Scores range from 0-1:\n * - 1.0 = fully suitable\n * - 0.0 = hard filter (e.g., prerequisite not met)\n * - 0.5 = neutral\n * - Intermediate values = soft preference\n *\n * Provenance tracks the scoring pipeline:\n * - First entry: Generator that produced the card\n * - Subsequent entries: Filters that transformed the score\n * - Each entry includes action and human-readable reason\n */\nexport interface WeightedCard {\n cardId: string;\n courseId: string;\n /** Suitability score from 0-1 */\n score: number;\n /**\n * Audit trail of strategy contributions.\n * First entry is from the generator, subsequent entries from filters.\n */\n provenance: StrategyContribution[];\n /**\n * Pre-fetched tags. Populated by Pipeline before filters run.\n * Filters should use this instead of querying getAppliedTags() individually.\n */\n tags?: string[];\n /**\n * Review document ID (_id from ScheduledCard).\n * Present when this card originated from SRS review scheduling.\n * Used by SessionController to track review outcomes and maintain review state.\n */\n reviewID?: string;\n}\n\n/**\n * Extract card origin from provenance trail.\n *\n * The first provenance entry (from the generator) indicates whether\n * this is a new card, review, or failed card. We parse the reason\n * string to extract this information.\n *\n * @param card - Card with provenance trail\n * @returns Card origin ('new', 'review', or 'failed')\n */\nexport function getCardOrigin(card: WeightedCard): 'new' | 'review' | 'failed' {\n if (card.provenance.length === 0) {\n throw new Error('Card has no provenance - cannot determine origin');\n }\n\n const firstEntry = card.provenance[0];\n const reason = firstEntry.reason.toLowerCase();\n\n if (reason.includes('failed')) {\n return 'failed';\n }\n if (reason.includes('review')) {\n return 'review';\n }\n return 'new';\n}\n\nexport enum Navigators {\n ELO = 'elo',\n SRS = 'srs',\n PRESCRIBED = 'prescribed',\n HIERARCHY = 'hierarchyDefinition',\n INTERFERENCE = 'interferenceMitigator',\n RELATIVE_PRIORITY = 'relativePriority',\n USER_TAG_PREFERENCE = 'userTagPreference',\n}\n\n// ============================================================================\n// NAVIGATOR ROLE CLASSIFICATION\n// ============================================================================\n//\n// Navigators are classified as either generators or filters:\n// - Generators: Produce candidate cards (ELO, SRS)\n// - Filters: Transform/score candidates (Hierarchy, Interference, RelativePriority)\n//\n// This classification is used by PipelineAssembler to build pipelines:\n// 1. Instantiate generators (possibly into a CompositeGenerator)\n// 2. Instantiate filters\n// 3. Create Pipeline(generator, filters)\n//\n// ============================================================================\n\n/**\n * Role classification for navigation strategies.\n *\n * - GENERATOR: Produces candidate cards with initial scores\n * - FILTER: Transforms cards with score multipliers\n */\nexport enum NavigatorRole {\n GENERATOR = 'generator',\n FILTER = 'filter',\n}\n\n/**\n * Registry mapping navigator implementations to their roles.\n */\nexport const NavigatorRoles: Record<Navigators, NavigatorRole> = {\n [Navigators.ELO]: NavigatorRole.GENERATOR,\n [Navigators.SRS]: NavigatorRole.GENERATOR,\n [Navigators.PRESCRIBED]: NavigatorRole.GENERATOR,\n [Navigators.HIERARCHY]: NavigatorRole.FILTER,\n [Navigators.INTERFERENCE]: NavigatorRole.FILTER,\n [Navigators.RELATIVE_PRIORITY]: NavigatorRole.FILTER,\n [Navigators.USER_TAG_PREFERENCE]: NavigatorRole.FILTER,\n};\n\n/**\n * Check if a navigator implementation is a generator.\n *\n * @param impl - Navigator implementation name (e.g., 'elo', 'hierarchyDefinition')\n * @returns true if the navigator is a generator, false otherwise\n */\nexport function isGenerator(impl: string): boolean {\n if (NavigatorRoles[impl as Navigators] === NavigatorRole.GENERATOR) return true;\n // Fallback: check the registry for consumer-registered navigators\n return getRegisteredNavigatorRole(impl) === NavigatorRole.GENERATOR;\n}\n\n/**\n * Check if a navigator implementation is a filter.\n *\n * Checks the built-in NavigatorRoles enum first, then falls back to the\n * navigator registry for consumer-registered navigators.\n *\n * @param impl - Navigator implementation name (e.g., 'elo', 'letterGatingFilter')\n * @returns true if the navigator is a filter, false otherwise\n */\nexport function isFilter(impl: string): boolean {\n if (NavigatorRoles[impl as Navigators] === NavigatorRole.FILTER) return true;\n // Fallback: check the registry for consumer-registered navigators\n return getRegisteredNavigatorRole(impl) === NavigatorRole.FILTER;\n}\n\n/**\n * Abstract base class for navigation strategies.\n *\n * This class exists primarily for backward compatibility with legacy code.\n * New code should use CardGenerator or CardFilter interfaces directly.\n *\n * The class implements StudyContentSource for compatibility with SessionController.\n * Once SessionController migrates to use getWeightedCards() exclusively,\n * the legacy methods can be removed.\n */\nexport abstract class ContentNavigator implements StudyContentSource {\n /** User interface for this navigation session */\n protected user: UserDBInterface;\n\n /** Course interface for this navigation session */\n protected course: CourseDBInterface;\n\n /** Human-readable name for this strategy instance (from ContentNavigationStrategyData.name) */\n protected strategyName?: string;\n\n /** Unique document ID for this strategy instance (from ContentNavigationStrategyData._id) */\n protected strategyId?: string;\n\n /** Evolutionary weighting configuration */\n public learnable?: LearnableWeight;\n\n /** Whether to bypass deviation (manual/static weighting) */\n public staticWeight?: boolean;\n\n /**\n * Constructor for standard navigators.\n * Call this from subclass constructors to initialize common fields.\n *\n * Note: CompositeGenerator and Pipeline call super() without args, then set\n * user/course fields directly if needed.\n */\n constructor(\n user?: UserDBInterface,\n course?: CourseDBInterface,\n strategyData?: ContentNavigationStrategyData\n ) {\n this.user = user!;\n this.course = course!;\n if (strategyData) {\n this.strategyName = strategyData.name;\n this.strategyId = strategyData._id;\n this.learnable = strategyData.learnable;\n this.staticWeight = strategyData.staticWeight;\n }\n }\n\n // ============================================================================\n // STRATEGY STATE HELPERS\n // ============================================================================\n //\n // These methods allow strategies to persist their own state (user preferences,\n // learned patterns, temporal tracking) in the user database.\n //\n // ============================================================================\n\n /**\n * Unique key identifying this strategy for state storage.\n *\n * Defaults to the constructor name (e.g., \"UserTagPreferenceFilter\").\n * Override in subclasses if multiple instances of the same strategy type\n * need separate state storage.\n */\n protected get strategyKey(): string {\n return this.constructor.name;\n }\n\n /**\n * Get this strategy's persisted state for the current course.\n *\n * @returns The strategy's data payload, or null if no state exists\n * @throws Error if user or course is not initialized\n */\n protected async getStrategyState<T>(): Promise<T | null> {\n if (!this.user || !this.course) {\n throw new Error(\n `Cannot get strategy state: navigator not properly initialized. ` +\n `Ensure user and course are provided to constructor.`\n );\n }\n return this.user.getStrategyState<T>(this.course.getCourseID(), this.strategyKey);\n }\n\n /**\n * Persist this strategy's state for the current course.\n *\n * @param data - The strategy's data payload to store\n * @throws Error if user or course is not initialized\n */\n protected async putStrategyState<T>(data: T): Promise<void> {\n if (!this.user || !this.course) {\n throw new Error(\n `Cannot put strategy state: navigator not properly initialized. ` +\n `Ensure user and course are provided to constructor.`\n );\n }\n return this.user.putStrategyState<T>(this.course.getCourseID(), this.strategyKey, data);\n }\n\n /**\n * Factory method to create navigator instances.\n *\n * First checks the navigator registry for a pre-registered constructor.\n * If not found, falls back to dynamic import (for custom navigators).\n *\n * For reliable operation in test environments, call initializeNavigatorRegistry()\n * before using this method.\n *\n * @param user - User interface\n * @param course - Course interface\n * @param strategyData - Strategy configuration document\n * @returns the runtime object used to steer a study session.\n */\n static async create(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ): Promise<ContentNavigator> {\n const implementingClass = strategyData.implementingClass;\n\n // First, check the registry for a pre-registered constructor\n const RegisteredImpl = getRegisteredNavigator(implementingClass);\n if (RegisteredImpl) {\n logger.debug(`[ContentNavigator.create] Using registered navigator: ${implementingClass}`);\n return new RegisteredImpl(user, course, strategyData);\n }\n\n // Fall back to dynamic import for custom/unknown navigators\n logger.debug(\n `[ContentNavigator.create] Navigator not in registry, attempting dynamic import: ${implementingClass}`\n );\n\n let NavigatorImpl;\n\n // Try different extension variations\n const variations = ['.ts', '.js', ''];\n\n for (const ext of variations) {\n // Try generators directory\n try {\n const module = await import(`./generators/${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n if (NavigatorImpl) break;\n } catch (e) {\n logger.debug(`Failed to load generator ${implementingClass}${ext}:`, e);\n }\n\n // Try filters directory\n try {\n const module = await import(`./filters/${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n if (NavigatorImpl) break;\n } catch (e) {\n logger.debug(`Failed to load filter ${implementingClass}${ext}:`, e);\n }\n\n // Try current directory (legacy)\n try {\n const module = await import(`./${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n if (NavigatorImpl) break;\n } catch (e) {\n logger.debug(`Failed to load legacy ${implementingClass}${ext}:`, e);\n }\n \n if (NavigatorImpl) break;\n }\n\n if (!NavigatorImpl) {\n throw new Error(`Could not load navigator implementation for: ${implementingClass}`);\n }\n\n return new NavigatorImpl(user, course, strategyData);\n }\n\n /**\n * Get cards with suitability scores and provenance trails.\n *\n * **This is the PRIMARY API for navigation strategies.**\n *\n * Returns cards ranked by suitability score (0-1). Higher scores indicate\n * better candidates for presentation. Each card includes a provenance trail\n * documenting how strategies contributed to the final score.\n *\n * ## Implementation Required\n * All navigation strategies MUST override this method. The base class does\n * not provide a default implementation.\n *\n * ## For Generators\n * Override this method to generate candidates and compute scores based on\n * your strategy's logic (e.g., ELO proximity, review urgency). Create the\n * initial provenance entry with action='generated'.\n *\n * ## For Filters\n * Filters should implement the CardFilter interface instead and be composed\n * via Pipeline. Filters do not directly implement getWeightedCards().\n *\n * @param limit - Maximum cards to return\n * @returns Cards sorted by score descending, with provenance trails\n */\n async getWeightedCards(_limit: number): Promise<WeightedCard[]> {\n throw new Error(`${this.constructor.name} must implement getWeightedCards(). `);\n }\n\n /**\n * Set ephemeral hints for the next pipeline run.\n * No-op for non-Pipeline navigators. Pipeline overrides this.\n */\n setEphemeralHints(_hints: Record<string, unknown>): void {\n // no-op — only Pipeline implements this\n }\n}\n","import { CourseDBInterface, CourseInfo, CoursesDBInterface, UserDBInterface } from '@db/core';\nimport {\n CourseConfig,\n CourseElo,\n DataShape,\n EloToNumber,\n Status,\n blankCourseElo,\n toCourseElo,\n} from '@vue-skuilder/common';\n\nimport { filterAllDocsByPrefix, getCourseDB } from '.';\nimport UpdateQueue from './updateQueue';\nimport { StudySessionItem } from '../../core/interfaces/contentSource';\nimport {\n CardData,\n DocType,\n QualifiedCardID,\n SkuilderCourseData,\n Tag,\n TagStub,\n DocTypePrefixes,\n} from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { GET_CACHED } from './clientCache';\nimport { addNote55, addTagToCard, getCredentialledCourseConfig, getTagID } from './courseAPI';\nimport { DataLayerResult } from '@db/core/types/db';\nimport { PouchError } from './types';\nimport CourseLookup from './courseLookupDB';\nimport { ContentNavigationStrategyData } from '@db/core/types/contentNavigationStrategy';\nimport { ContentNavigator, Navigators, WeightedCard } from '@db/core/navigators';\nimport { PipelineAssembler } from '@db/core/navigators/PipelineAssembler';\nimport { createDefaultPipeline } from '@db/core/navigators/defaults';\n\nexport class CoursesDB implements CoursesDBInterface {\n _courseIDs: string[] | undefined;\n\n constructor(courseIDs?: string[]) {\n if (courseIDs && courseIDs.length > 0) {\n this._courseIDs = courseIDs;\n } else {\n this._courseIDs = undefined;\n }\n }\n\n public async getCourseList(): Promise<CourseConfig[]> {\n let crsList = await CourseLookup.allCourseWare();\n logger.debug(`AllCourses: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n if (this._courseIDs) {\n crsList = crsList.filter((c) => this._courseIDs!.includes(c._id));\n }\n\n logger.debug(`AllCourses.filtered: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n\n const cfgs = await Promise.all(\n crsList.map(async (c) => {\n try {\n const cfg = await getCredentialledCourseConfig(c._id);\n logger.debug(`Found cfg: ${JSON.stringify(cfg)}`);\n return cfg;\n } catch (e) {\n logger.warn(`Error fetching cfg for course ${c.name}, ${c._id}: ${e}`);\n return undefined;\n }\n })\n );\n return cfgs.filter((c) => !!c);\n }\n\n async getCourseConfig(courseId: string): Promise<CourseConfig> {\n if (this._courseIDs && this._courseIDs.length && !this._courseIDs.includes(courseId)) {\n throw new Error(`Course ${courseId} not in course list`);\n }\n\n const cfg = await getCredentialledCourseConfig(courseId);\n if (cfg === undefined) {\n throw new Error(`Error fetching cfg for course ${courseId}`);\n } else {\n return cfg;\n }\n }\n\n public async disambiguateCourse(courseId: string, disambiguator: string): Promise<void> {\n await CourseLookup.updateDisambiguator(courseId, disambiguator);\n }\n}\n\nfunction randIntWeightedTowardZero(n: number) {\n return Math.floor(Math.random() * Math.random() * Math.random() * n);\n}\n\nexport class CourseDB implements CourseDBInterface {\n // private log(msg: string): void {\n // log(`CourseLog: ${this.id}\\n ${msg}`);\n // }\n\n /**\n * Primary database handle used for all **read** operations (queries, gets).\n *\n * When local sync is active, this points to the local PouchDB replica for\n * fast, network-free reads. Otherwise it points to the remote CouchDB.\n */\n private db: PouchDB.Database;\n\n /**\n * Remote database handle used for all **write** operations.\n *\n * Always points to the remote CouchDB so that writes (ELO updates, tag\n * mutations, admin operations) aggregate on the server. The local replica\n * is a read-only snapshot that refreshes on the next page load.\n *\n * When local sync is NOT active, this is the same instance as `this.db`.\n */\n private remoteDB: PouchDB.Database;\n\n private id: string;\n private _getCurrentUser: () => Promise<UserDBInterface>;\n private updateQueue: UpdateQueue;\n\n /**\n * @param id - Course ID\n * @param userLookup - Async function returning the current user DB\n * @param localDB - Optional local PouchDB replica for reads. When provided,\n * `this.db` uses the local replica and `this.remoteDB` stays remote.\n * The UpdateQueue reads from remote and writes to remote (local `_rev`\n * values may be stale, so read-modify-write cycles must go through\n * the remote DB to avoid conflicts).\n */\n constructor(id: string, userLookup: () => Promise<UserDBInterface>, localDB?: PouchDB.Database) {\n this.id = id;\n const remote = getCourseDB(this.id);\n this.remoteDB = remote;\n this.db = localDB ?? remote;\n this._getCurrentUser = userLookup;\n // UpdateQueue always operates against the remote DB for its\n // read-modify-write cycle. Local _rev values may be stale (the local\n // replica is a snapshot), so conflict retries must read from remote.\n this.updateQueue = new UpdateQueue(this.remoteDB, this.remoteDB);\n }\n\n public getCourseID(): string {\n return this.id;\n }\n\n public async getCourseInfo(): Promise<CourseInfo> {\n const cardCount = (\n await this.db.find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n })\n ).docs.length;\n\n return {\n cardCount,\n registeredUsers: 0,\n };\n }\n\n public async getInexperiencedCards(limit: number = 2) {\n return (\n await this.db.query('cardsByInexperience', {\n limit,\n })\n ).rows.map((r) => {\n const ret = {\n courseId: this.id,\n cardId: r.id,\n count: r.key,\n elo: r.value,\n };\n return ret;\n });\n }\n\n public async getCardsByEloLimits(\n options: {\n low: number;\n high: number;\n limit: number;\n page: number;\n } = {\n low: 0,\n high: Number.MIN_SAFE_INTEGER,\n limit: 25,\n page: 0,\n }\n ) {\n return (\n await this.db.query('elo', {\n startkey: options.low,\n endkey: options.high,\n limit: options.limit,\n skip: options.limit * options.page,\n })\n ).rows.map((r) => {\n return `${this.id}-${r.id}-${r.key}`;\n });\n }\n public async getCardEloData(id: string[]): Promise<CourseElo[]> {\n const docs = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: CourseElo[] = [];\n docs.rows.forEach((r) => {\n // [ ] remove these ts-ignore directives.\n if (isSuccessRow(r)) {\n if (r.doc && r.doc.elo) {\n ret.push(toCourseElo(r.doc.elo));\n } else {\n logger.warn('no elo data for card: ' + r.id);\n ret.push(blankCourseElo());\n }\n } else {\n logger.warn('no elo data for card: ' + JSON.stringify(r));\n ret.push(blankCourseElo());\n }\n });\n return ret;\n }\n\n /**\n * Returns the lowest and highest `global` ELO ratings in the course\n */\n public async getELOBounds() {\n const [low, high] = await Promise.all([\n (\n await this.db.query('elo', {\n startkey: 0,\n limit: 1,\n include_docs: false,\n })\n ).rows[0].key,\n (\n await this.db.query('elo', {\n limit: 1,\n descending: true,\n startkey: 100_000,\n })\n ).rows[0].key,\n ]);\n\n return {\n low: low,\n high: high,\n };\n }\n\n public async removeCard(id: string) {\n // Admin operation — read and write both go through remote DB to ensure\n // we have the current _rev for the delete.\n const doc = await this.remoteDB.get<CardData>(id);\n if (!doc.docType || !(doc.docType === DocType.CARD)) {\n throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);\n }\n\n // Remove card from all associated tags before deleting the card\n try {\n const appliedTags = await this.getAppliedTags(id);\n const results = await Promise.allSettled(\n appliedTags.rows.map(async (tagRow) => {\n const tagId = tagRow.id;\n await this.removeTagFromCard(id, tagId);\n })\n );\n\n // Log any individual tag cleanup failures\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const tagId = appliedTags.rows[index].id;\n logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);\n }\n });\n } catch (error) {\n logger.error(`Error removing card ${id} from tags: ${error}`);\n // Continue with card deletion even if tag cleanup fails\n }\n\n return this.remoteDB.remove(doc);\n }\n\n public async getCardDisplayableDataIDs(id: string[]) {\n logger.debug(id.join(', '));\n const cards = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: { [card: string]: string[] } = {};\n cards.rows.forEach((r) => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n });\n\n return ret;\n }\n\n async getCardsByELO(elo: number, cardLimit?: number) {\n elo = parseInt(elo as any);\n const limit = cardLimit ? cardLimit : 25;\n\n const below: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: Math.ceil(limit / 2),\n startkey: elo,\n descending: true,\n });\n\n const aboveLimit = limit - below.rows.length;\n\n const above: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: aboveLimit,\n startkey: elo + 1,\n });\n // logger.log(JSON.stringify(below));\n\n let cards = below.rows;\n cards = cards.concat(above.rows);\n\n const ret = cards\n .sort((a, b) => {\n const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);\n if (s === 0) {\n return Math.random() - 0.5;\n } else {\n return s;\n }\n })\n .map((c) => {\n return {\n courseID: this.id,\n cardID: c.id,\n elo: c.key,\n };\n });\n\n const str = `below:\\n${below.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}\n\nabove:\\n${above.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}`;\n\n logger.debug(`Getting ${limit} cards centered around elo: ${elo}:\\n\\n` + str);\n\n return ret;\n }\n\n async getCourseConfig(): Promise<CourseConfig> {\n const ret = await getCredentialledCourseConfig(this.id);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course config not found for course ID: ${this.id}`);\n }\n }\n\n async updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response> {\n logger.debug(`Updating: ${JSON.stringify(cfg)}`);\n // write both to the course DB:\n try {\n return await updateCredentialledCourseConfig(this.id, cfg);\n } catch (error) {\n logger.error(`Error updating course config in course DB: ${error}`);\n throw error;\n }\n }\n\n async updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n if (!elo) {\n throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);\n }\n\n try {\n const result = await this.updateQueue.update<\n CardData & PouchDB.Core.GetMeta & PouchDB.Core.IdMeta\n >(cardId, (card) => {\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return card;\n });\n return { ok: true, id: cardId, rev: result._rev };\n } catch (error) {\n logger.error(`Failed to update card elo for card ID: ${cardId}`, error);\n throw new Error(`Failed to update card elo for card ID: ${cardId}`);\n }\n }\n\n async getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>> {\n const ret = await getAppliedTags(this.id, cardId);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);\n }\n }\n\n async getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>> {\n if (cardIds.length === 0) {\n return new Map();\n }\n\n const result = await this.db.query<TagStub>('getTags', {\n keys: cardIds,\n include_docs: false,\n });\n\n const tagsByCard = new Map<string, string[]>();\n\n // Initialize all requested cards with empty arrays\n for (const cardId of cardIds) {\n tagsByCard.set(cardId, []);\n }\n\n // Populate from query results\n for (const row of result.rows) {\n const cardId = row.key as string;\n const tagName = row.value?.name;\n if (tagName && tagsByCard.has(cardId)) {\n tagsByCard.get(cardId)!.push(tagName);\n }\n }\n\n return tagsByCard;\n }\n\n async getAllCardIds(): Promise<string[]> {\n const result = await this.db.allDocs({\n startkey: 'CARD-',\n endkey: 'CARD-\\ufff0',\n include_docs: false,\n });\n return result.rows.map((row) => row.id);\n }\n\n async addTagToCard(\n cardId: string,\n tagId: string,\n updateELO?: boolean\n ): Promise<PouchDB.Core.Response> {\n return await addTagToCard(\n this.id,\n cardId,\n tagId,\n (await this._getCurrentUser()).getUsername(),\n updateELO\n );\n }\n\n async removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response> {\n return await removeTagFromCard(this.id, cardId, tagId);\n }\n\n async createTag(name: string, author: string): Promise<PouchDB.Core.Response> {\n return await createTag(this.id, name, author);\n }\n\n async getTag(tagId: string): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<Tag>> {\n return await getTag(this.id, tagId);\n }\n\n async updateTag(tag: Tag): Promise<PouchDB.Core.Response> {\n if (tag.course !== this.id) {\n throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);\n }\n\n return await updateTag(tag);\n }\n\n async getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n return getCourseTagStubs(this.id);\n }\n\n async addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n ): Promise<DataLayerResult> {\n try {\n const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);\n if (resp.ok) {\n // Check if card creation failed (property added by addNote55)\n if ((resp as any).cardCreationFailed) {\n logger.warn(\n `[courseDB.addNote] Note added but card creation failed: ${\n (resp as any).cardCreationError\n }`\n );\n return {\n status: Status.error,\n message: `Note was added but no cards were created: ${(resp as any).cardCreationError}`,\n id: resp.id,\n };\n }\n return {\n status: Status.ok,\n message: '',\n id: resp.id,\n };\n } else {\n return {\n status: Status.error,\n message: 'Unexpected error adding note',\n };\n }\n } catch (e) {\n const err = e as PouchDB.Core.Error;\n logger.error(\n `[addNote] error ${err.name}\\n\\treason: ${err.reason}\\n\\tmessage: ${err.message}`\n );\n return {\n status: Status.error,\n message: `Error adding note to course. ${(e as PouchError).reason || err.message}`,\n };\n }\n }\n\n async getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<T>> {\n // Use this.db (local when available) for read operations.\n // Falls back to the standalone helper (always remote) only if needed.\n return await this.db.get<T>(id, options) as PouchDB.Core.GetMeta & PouchDB.Core.Document<T>;\n }\n\n async getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>> {\n // Use this.db (local when available) for read operations.\n return await this.db.allDocs<T>({\n ...options,\n keys: ids,\n }) as PouchDB.Core.AllDocsWithKeysResponse<{} & T>;\n }\n\n ////////////////////////////////////\n // NavigationStrategyManager implementation\n ////////////////////////////////////\n\n getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData> {\n logger.debug(`[courseDB] Getting navigation strategy: ${id}`);\n\n if (id == '') {\n const strategy: ContentNavigationStrategyData = {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy for ordering content by difficulty',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '', // serde is a noop for ELO navigator.\n };\n return Promise.resolve(strategy);\n } else {\n return this.db.get(id);\n }\n }\n\n async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {\n const prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];\n const result = await this.db.allDocs<ContentNavigationStrategyData>({\n startkey: prefix,\n endkey: `${prefix}\\ufff0`,\n include_docs: true,\n });\n return result.rows.map((row) => row.doc!);\n }\n\n async addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);\n // Admin write operation — use remote DB.\n return this.remoteDB.put(data).then(() => {});\n }\n updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Updating navigation strategy: ${id}`);\n // For now, just log the data and return success\n logger.debug(JSON.stringify(data));\n return Promise.resolve();\n }\n\n /**\n * Creates an instantiated navigator for this course.\n *\n * Handles multiple generators by wrapping them in CompositeGenerator.\n * This is the preferred method for getting a ready-to-use navigator.\n *\n * @param user - User database interface\n * @returns Instantiated ContentNavigator ready for use\n */\n async createNavigator(user: UserDBInterface): Promise<ContentNavigator> {\n try {\n const allStrategies = await this.getAllNavigationStrategies();\n\n if (allStrategies.length === 0) {\n // No strategies configured: use default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\n logger.debug(\n '[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])'\n );\n return createDefaultPipeline(user, this);\n }\n\n // Use PipelineAssembler to build a Pipeline from strategy documents\n const assembler = new PipelineAssembler();\n const { pipeline, generatorStrategies, filterStrategies, warnings } =\n await assembler.assemble({\n strategies: allStrategies,\n user,\n course: this,\n });\n\n // Log any warnings from assembly\n for (const warning of warnings) {\n logger.warn(`[PipelineAssembler] ${warning}`);\n }\n\n if (!pipeline) {\n // Assembly failed - fall back to default\n logger.debug('[courseDB] Pipeline assembly failed, using default pipeline');\n return createDefaultPipeline(user, this);\n }\n\n logger.debug(\n `[courseDB] Using assembled pipeline with ${generatorStrategies.length} generator(s) and ${filterStrategies.length} filter(s)`\n );\n return pipeline;\n } catch (e) {\n logger.error(`[courseDB] Error creating navigator: ${e}`);\n throw e;\n }\n }\n\n ////////////////////////////////////\n // END NavigationStrategyManager implementation\n ////////////////////////////////////\n\n ////////////////////////////////////\n // StudyContentSource implementation\n ////////////////////////////////////\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * This is the PRIMARY API for content sources going forward. Delegates to the\n * course's configured NavigationStrategy to get scored candidates.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n private _pendingHints: Record<string, unknown> | null = null;\n\n public setEphemeralHints(hints: Record<string, unknown>): void {\n this._pendingHints = hints;\n }\n\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n const u = await this._getCurrentUser();\n\n try {\n const navigator = await this.createNavigator(u);\n if (this._pendingHints) {\n navigator.setEphemeralHints(this._pendingHints);\n this._pendingHints = null;\n }\n return navigator.getWeightedCards(limit);\n } catch (e) {\n logger.error(`[courseDB] Error getting weighted cards: ${e}`);\n throw e;\n }\n }\n\n public async getCardsCenteredAtELO(\n options: {\n limit: number;\n elo: 'user' | 'random' | number;\n } = {\n limit: 99,\n elo: 'user',\n },\n filter?: (a: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]> {\n let targetElo: number;\n\n if (options.elo === 'user') {\n const u = await this._getCurrentUser();\n\n targetElo = -1;\n try {\n const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {\n return c.courseID === this.id;\n })!;\n targetElo = EloToNumber(courseDoc.elo);\n } catch {\n targetElo = 1000;\n }\n } else if (options.elo === 'random') {\n const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());\n targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));\n // logger.log(`Picked ${targetElo} from [${bounds.low}, ${bounds.high}]`);\n } else {\n targetElo = options.elo;\n }\n\n let cards: (QualifiedCardID & { elo?: number })[] = [];\n let mult: number = 4;\n let previousCount: number = -1;\n let newCount: number = 0;\n\n while (cards.length < options.limit && newCount !== previousCount) {\n cards = await this.getCardsByELO(targetElo, mult * options.limit);\n previousCount = newCount;\n newCount = cards.length;\n\n logger.debug(`Found ${cards.length} elo neighbor cards...`);\n\n if (filter) {\n cards = cards.filter(filter);\n logger.debug(`Filtered to ${cards.length} cards...`);\n }\n\n mult *= 2;\n }\n\n const selectedCards: {\n courseID: string;\n cardID: string;\n elo?: number;\n }[] = [];\n\n while (selectedCards.length < options.limit && cards.length > 0) {\n const index = randIntWeightedTowardZero(cards.length);\n const card = cards.splice(index, 1)[0];\n selectedCards.push(card);\n }\n\n return selectedCards.map((c) => {\n return {\n courseID: this.id,\n cardID: c.cardID,\n contentSourceType: 'course',\n contentSourceID: this.id,\n elo: c.elo,\n status: 'new',\n };\n });\n }\n\n // Admin search methods\n public async searchCards(query: string): Promise<any[]> {\n logger.log(`[CourseDB ${this.id}] Searching for: \"${query}\"`);\n\n // Try multiple search approaches\n let displayableData;\n\n try {\n // Try regex search on the correct data structure: data[0].data\n displayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n 'data.0.data': { $regex: `.*${query}.*` },\n },\n });\n logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);\n } catch (regexError) {\n logger.log(\n `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,\n regexError\n );\n\n // Fallback: get all displayable data and filter manually\n const allDisplayable = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`\n );\n\n displayableData = {\n docs: allDisplayable.docs.filter((doc) => {\n // Search entire document as JSON string - inclusive approach for admin tool\n const docString = JSON.stringify(doc).toLowerCase();\n const match = docString.includes(query.toLowerCase());\n if (match) {\n logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);\n }\n return match;\n }),\n };\n }\n\n logger.log(\n `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`\n );\n\n if (displayableData.docs.length === 0) {\n // Debug: Let's see what displayable data exists\n const allDisplayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n limit: 5, // Just sample a few\n });\n\n logger.log(\n `[CourseDB ${this.id}] Sample displayable data:`,\n allDisplayableData.docs.map((d) => ({\n id: d._id,\n docType: (d as any).docType,\n dataStructure: (d as any).data ? Object.keys((d as any).data) : 'no data field',\n dataContent: (d as any).data,\n fullDoc: d,\n }))\n );\n }\n\n const allResults: any[] = [];\n\n for (const dd of displayableData.docs) {\n const cards = await this.db.find({\n selector: {\n docType: 'CARD',\n id_displayable_data: { $in: [dd._id] },\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`\n );\n allResults.push(...cards.docs);\n }\n\n logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);\n return allResults;\n }\n\n public async find(\n request: PouchDB.Find.FindRequest<any>\n ): Promise<PouchDB.Find.FindResponse<any>> {\n return this.db.find(request);\n }\n}\n\n/**\n * Returns a list of registered datashapes for the specified\n * course.\n * @param courseID The ID of the course\n */\nexport async function getCourseDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.dataShapes;\n}\n\nexport async function getCredentialledDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n\n return cfg.dataShapes;\n}\n\nexport async function getCourseQuestionTypes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.questionTypes;\n}\n\n// todo: this is actually returning full tag docs now.\n// - performance issue when tags have lots of\n// applied docs\n// - will require a computed couch DB view\nexport async function getCourseTagStubs(\n courseID: string\n): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n logger.debug(`Getting tag stubs for course: ${courseID}`);\n const stubs = await filterAllDocsByPrefix<Tag>(\n getCourseDB(courseID),\n DocType.TAG.valueOf() + '-'\n );\n\n stubs.rows.forEach((row) => {\n logger.debug(`\\tTag stub for doc: ${row.id}`);\n });\n\n return stubs;\n}\n\nexport async function deleteTag(courseID: string, tagName: string) {\n tagName = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const doc = await courseDB.get<Tag>(DocType.TAG.valueOf() + '-' + tagName);\n const resp = await courseDB.remove(doc);\n return resp;\n}\n\nexport async function createTag(courseID: string, tagName: string, author: string) {\n logger.debug(`Creating tag: ${tagName}...`);\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const resp = await courseDB.put<Tag>({\n course: courseID,\n docType: DocType.TAG,\n name: tagName,\n snippet: '',\n taggedCards: [],\n wiki: '',\n author,\n _id: tagID,\n });\n return resp;\n}\n\nexport async function updateTag(tag: Tag) {\n const prior = await getTag(tag.course, tag.name);\n return await getCourseDB(tag.course).put<Tag>({\n ...tag,\n _rev: prior._rev,\n });\n}\n\nexport async function getTag(courseID: string, tagName: string) {\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n return courseDB.get<Tag>(tagID);\n}\n\nexport async function removeTagFromCard(courseID: string, cardID: string, tagID: string) {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n tagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const tag = await courseDB.get<Tag>(tagID);\n tag.taggedCards = tag.taggedCards.filter((taggedID) => {\n return cardID !== taggedID;\n });\n return courseDB.put<Tag>(tag);\n}\n\n/**\n * Returns an array of ancestor tag IDs, where:\n * return[0] = parent,\n * return[1] = grandparent,\n * return[2] = great grandparent,\n * etc.\n *\n * If ret is empty, the tag itself is a root\n */\nexport function getAncestorTagIDs(courseID: string, tagID: string): string[] {\n tagID = getTagID(tagID);\n const split = tagID.split('>');\n if (split.length === 1) {\n return [];\n } else {\n split.pop();\n const parent = split.join('>');\n return [parent].concat(getAncestorTagIDs(courseID, parent));\n }\n}\n\nexport async function getChildTagStubs(courseID: string, tagID: string) {\n return await filterAllDocsByPrefix(getCourseDB(courseID), tagID + '>');\n}\n\nexport async function getAppliedTags(id_course: string, id_card: string) {\n const db = getCourseDB(id_course);\n\n const result = await db.query<TagStub>('getTags', {\n startkey: id_card,\n endkey: id_card,\n // include_docs: true\n });\n\n // log(`getAppliedTags looked up: ${id_card}`);\n // log(`getAppliedTags returning: ${JSON.stringify(result)}`);\n\n return result;\n}\n\nexport async function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nexport async function updateCredentialledCourseConfig(courseID: string, config: CourseConfig) {\n logger.debug(`Updating course config:\n\n${JSON.stringify(config)}\n`);\n\n const db = getCourseDB(courseID);\n const old = await getCredentialledCourseConfig(courseID);\n\n return await db.put<CourseConfig>({\n ...config,\n _rev: (old as any)._rev,\n });\n}\n\nfunction isSuccessRow<T>(\n row:\n | {\n key: PouchDB.Core.DocumentKey;\n error: 'not_found';\n }\n | {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n }\n): row is {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n} {\n return 'doc' in row && row.doc !== null && row.doc !== undefined;\n}\n","import { StudyContentSource } from '@db/core/interfaces/contentSource';\nimport { WeightedCard } from '@db/core/navigators';\nimport { ClassroomConfig } from '@vue-skuilder/common';\nimport { ENV } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport moment from 'moment';\nimport pouch from './pouchdb-setup';\nimport { getStartAndEndKeys, createPouchDBConfig, REVIEW_TIME_FORMAT } from '.';\nimport { CourseDB, getTag } from './courseDB';\n\nimport { UserDBInterface } from '@db/core';\nimport {\n AssignedContent,\n AssignedCourse,\n AssignedTag,\n StudentClassroomDBInterface,\n TeacherClassroomDBInterface,\n} from '@db/core/interfaces/classroomDB';\n\nconst classroomLookupDBTitle = 'classdb-lookup';\nexport const CLASSROOM_CONFIG = 'ClassroomConfig';\n\nexport type ClassroomMessage = object;\n\nabstract class ClassroomDBBase {\n public _id!: string;\n protected _db!: PouchDB.Database;\n protected _cfg!: ClassroomConfig;\n protected _initComplete: boolean = false;\n\n protected readonly _content_prefix: string = 'content';\n protected get _content_searchkeys() {\n return getStartAndEndKeys(this._content_prefix);\n }\n\n protected abstract init(): Promise<void>;\n\n public async getAssignedContent(): Promise<AssignedContent[]> {\n logger.info(`Getting assigned content...`);\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const docRows = await this._db.allDocs<AssignedContent>({\n startkey: this._content_prefix,\n endkey: this._content_prefix + `\\ufff0`,\n include_docs: true,\n });\n\n const ret = docRows.rows.map((row) => {\n return row.doc!;\n });\n // logger.info(`Assigned content: ${JSON.stringify(ret)}`);\n\n return ret;\n }\n\n protected getContentId(content: AssignedContent): string {\n if (content.type === 'tag') {\n return `${this._content_prefix}-${content.courseID}-${content.tagID}`;\n } else {\n return `${this._content_prefix}-${content.courseID}`;\n }\n }\n\n public get ready(): boolean {\n return this._initComplete;\n }\n public getConfig(): ClassroomConfig {\n return this._cfg;\n }\n}\n\nexport class StudentClassroomDB\n extends ClassroomDBBase\n implements StudyContentSource, StudentClassroomDBInterface\n{\n // private readonly _prefix: string = 'content';\n private userMessages!: PouchDB.Core.Changes<object>;\n private _user: UserDBInterface;\n\n private constructor(classID: string, user: UserDBInterface) {\n super();\n this._id = classID;\n this._user = user;\n // init() is called explicitly in factory method, not in constructor\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n try {\n const cfg = await this._db.get<ClassroomConfig>(CLASSROOM_CONFIG);\n this._cfg = cfg;\n this.userMessages = this._db.changes({\n since: 'now',\n live: true,\n include_docs: true,\n });\n this._initComplete = true;\n return;\n } catch (e) {\n throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string, user: UserDBInterface): Promise<StudentClassroomDB> {\n const ret = new StudentClassroomDB(classID, user);\n await ret.init();\n return ret;\n }\n\n public setChangeFcn(f: (value: unknown) => object): void {\n // todo: make this into a view request, w/ the user's name attached\n // todo: requires creating the view doc on classroom create in /express\n void this.userMessages.on('change', f);\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * Gathers new cards from assigned content (courses, tags, cards) and\n * pending reviews scheduled for this classroom. Assigns score=1.0 to all.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending (all scores = 1.0)\n */\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n const weighted: WeightedCard[] = [];\n\n // Get pending reviews for this classroom\n const allUserReviews = await this._user.getPendingReviews();\n const classroomReviews = allUserReviews.filter(\n (r) => r.scheduledFor === 'classroom' && r.schedulingAgentId === this._id\n );\n\n for (const r of classroomReviews) {\n weighted.push({\n cardId: r.cardId,\n courseId: r.courseId,\n score: 1.0,\n reviewID: r._id,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Classroom scheduled review',\n },\n ],\n });\n }\n\n // Get new cards from assigned content\n const activeCards = await this._user.getActiveCards();\n const activeCardIds = new Set(activeCards.map((ac) => ac.cardID));\n const now = moment.utc();\n const assigned = await this.getAssignedContent();\n const due = assigned.filter((c) => now.isAfter(moment.utc(c.activeOn, REVIEW_TIME_FORMAT)));\n\n logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(due)}`);\n\n for (const content of due) {\n if (content.type === 'course') {\n // Get weighted cards from the course directly\n const db = new CourseDB(content.courseID, async () => this._user);\n const courseCards = await db.getWeightedCards(limit);\n for (const card of courseCards) {\n if (!activeCardIds.has(card.cardId)) {\n weighted.push({\n ...card,\n provenance: [\n ...card.provenance,\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'passed' as const,\n score: card.score,\n reason: `Assigned via classroom from course ${content.courseID}`,\n },\n ],\n });\n }\n }\n } else if (content.type === 'tag') {\n const tagDoc = await getTag(content.courseID, content.tagID);\n\n for (const cardId of tagDoc.taggedCards) {\n if (!activeCardIds.has(cardId)) {\n weighted.push({\n cardId,\n courseId: content.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: `Classroom assigned tag: ${content.tagID}, new card`,\n },\n ],\n });\n }\n }\n } else if (content.type === 'card') {\n if (!activeCardIds.has(content.cardID)) {\n weighted.push({\n cardId: content.cardID,\n courseId: content.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Classroom assigned card, new card',\n },\n ],\n });\n }\n }\n }\n\n logger.info(\n `[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ` +\n `${weighted.length} total (reviews + new)`\n );\n\n // Sort by score descending and limit\n return weighted.sort((a, b) => b.score - a.score).slice(0, limit);\n }\n}\n\n/**\n * Interface for managing a classroom.\n */\nexport class TeacherClassroomDB extends ClassroomDBBase implements TeacherClassroomDBInterface {\n private _stuDb!: PouchDB.Database;\n\n private constructor(classID: string) {\n super();\n this._id = classID;\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-teacher-${this._id}`;\n const stuDbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n this._stuDb = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + stuDbName,\n createPouchDBConfig()\n );\n try {\n return this._db\n .get<ClassroomConfig>(CLASSROOM_CONFIG)\n .then((cfg) => {\n this._cfg = cfg;\n this._initComplete = true;\n })\n .then(() => {\n return;\n });\n } catch (e) {\n throw new Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string): Promise<TeacherClassroomDB> {\n const ret = new TeacherClassroomDB(classID);\n await ret.init();\n return ret;\n }\n\n public async removeContent(content: AssignedContent): Promise<void> {\n const contentID = this.getContentId(content);\n\n try {\n const doc = await this._db.get(contentID);\n await this._db.remove(doc);\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [contentID],\n });\n } catch (error) {\n logger.error('Failed to remove content:', contentID, error);\n }\n }\n\n public async assignContent(content: AssignedContent): Promise<boolean> {\n let put: PouchDB.Core.Response;\n const id: string = this.getContentId(content);\n\n if (content.type === 'tag') {\n put = await this._db.put<AssignedTag>({\n courseID: content.courseID,\n tagID: content.tagID,\n type: 'tag',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n } else {\n put = await this._db.put<AssignedCourse>({\n courseID: content.courseID,\n type: 'course',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n }\n\n if (put.ok) {\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [id],\n });\n return true;\n } else {\n return false;\n }\n }\n}\n\nexport const ClassroomLookupDB: () => PouchDB.Database = () =>\n new pouch(ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + classroomLookupDBTitle, {\n skip_setup: true,\n });\n\nexport function getClassroomDB(classID: string, version: 'student' | 'teacher'): PouchDB.Database {\n const dbName = `classdb-${version}-${classID}`;\n logger.info(`Retrieving classroom db: ${dbName}`);\n\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport async function getClassroomConfig(classID: string): Promise<ClassroomConfig> {\n return await getClassroomDB(classID, 'student').get<ClassroomConfig>(CLASSROOM_CONFIG);\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport {\n createPouchDBConfig,\n getStartAndEndKeys,\n getCredentialledCourseConfig,\n updateCredentialledCourseConfig,\n} from '.';\nimport { TeacherClassroomDB, ClassroomLookupDB } from './classroomDB';\nimport { PouchError } from './types';\n\nimport { AdminDBInterface } from '@db/core';\nimport CourseLookup from './courseLookupDB';\nimport { logger } from '@db/util/logger';\n\nexport class AdminDB implements AdminDBInterface {\n private usersDB!: PouchDB.Database;\n\n constructor() {\n // [ ] execute a check here against credentials, and throw an error\n // if the user is not an admin\n this.usersDB = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + '_users',\n createPouchDBConfig()\n );\n }\n\n public async getUsers() {\n return (\n await this.usersDB.allDocs({\n include_docs: true,\n ...getStartAndEndKeys('org.couchdb.user:'),\n })\n ).rows.map((r) => r.doc!);\n }\n\n public async getCourses() {\n const list = await CourseLookup.allCourseWare();\n return await Promise.all(\n list.map((c) => {\n return getCredentialledCourseConfig(c._id);\n })\n );\n }\n public async removeCourse(id: string) {\n // remove the indexer\n const delResp = await CourseLookup.delete(id);\n\n // set the 'CourseConfig' to 'deleted'\n const cfg = await getCredentialledCourseConfig(id);\n cfg.deleted = true;\n const isDeletedResp = await updateCredentialledCourseConfig(id, cfg);\n\n return {\n ok: delResp.ok && isDeletedResp.ok,\n id: delResp.id,\n rev: delResp.rev,\n };\n }\n\n public async getClassrooms() {\n // const joincodes =\n const uuids = (\n await ClassroomLookupDB().allDocs<{ uuid: string }>({\n include_docs: true,\n })\n ).rows.map((r) => r.doc!.uuid);\n logger.debug(uuids.join(', '));\n\n const promisedCRDbs: TeacherClassroomDB[] = [];\n for (let i = 0; i < uuids.length; i++) {\n try {\n const db = await TeacherClassroomDB.factory(uuids[i]);\n promisedCRDbs.push(db);\n } catch (e) {\n const err = e as PouchError;\n if (err.error && err.error === 'not_found') {\n logger.warn(`db ${uuids[i]} not found`);\n }\n }\n }\n\n return promisedCRDbs.map((db) => {\n return {\n ...db.getConfig(),\n _id: db._id,\n };\n });\n }\n}\n","import pouch from './pouchdb-setup';\nimport { getCourseDB } from '.';\nimport { logger } from '../../util/logger';\nimport type { CourseConfig } from '@vue-skuilder/common';\n\n// ============================================================================\n// COURSE SYNC SERVICE\n// ============================================================================\n//\n// Manages client-side PouchDB replicas of course databases.\n//\n// Courses opt in to local sync via CourseConfig.localSync.enabled. When\n// enabled, the service performs a one-shot replication from remote CouchDB\n// to a local PouchDB on first visit, then incremental sync on subsequent\n// visits. Pipeline scoring, tag hydration, and card lookup then run against\n// the local replica — eliminating network round trips from the study-session\n// hot path.\n//\n// Read/write split:\n// Local DB = read-only snapshot (pipeline, filters, card lookup)\n// Remote DB = all writes (ELO updates, tag mutations, admin ops)\n//\n// This avoids propagating per-interaction ELO write noise to every syncing\n// client. Each client's local snapshot refreshes on the next page load.\n//\n// Live replication is intentionally NOT supported. The remote course DB\n// receives high-frequency ELO updates from all concurrent users — live\n// sync would cause constant re-indexing of local PouchDB views.\n//\n// ============================================================================\n\n/**\n * Sync state for a single course database.\n */\nexport type CourseSyncState =\n | 'not-started'\n | 'checking-config'\n | 'syncing'\n | 'warming-views'\n | 'ready'\n | 'disabled'\n | 'error';\n\n/**\n * Detailed sync status for observability.\n */\nexport interface CourseSyncStatus {\n state: CourseSyncState;\n /** Number of documents replicated (set after sync completes) */\n docsReplicated?: number;\n /** Total replication time in ms */\n syncTimeMs?: number;\n /** View warming time in ms */\n viewWarmTimeMs?: number;\n /** Error message if state is 'error' */\n error?: string;\n}\n\n/**\n * Internal tracking entry per course.\n */\ninterface SyncEntry {\n localDB: PouchDB.Database | null;\n status: CourseSyncStatus;\n /** Promise that resolves when sync is complete (or rejects on failure) */\n readyPromise: Promise<void> | null;\n}\n\n/**\n * Service that manages local PouchDB replicas of course databases.\n *\n * Usage:\n * ```typescript\n * const syncService = CourseSyncService.getInstance();\n *\n * // Trigger sync (typically on app load / pre-session)\n * await syncService.ensureSynced(courseId);\n *\n * // Get local DB for reads (returns null if sync not ready/enabled)\n * const localDB = syncService.getLocalDB(courseId);\n * ```\n *\n * The service is a singleton — course sync state is shared across the app.\n */\nexport class CourseSyncService {\n private static instance: CourseSyncService | null = null;\n\n private entries: Map<string, SyncEntry> = new Map();\n\n private constructor() {}\n\n static getInstance(): CourseSyncService {\n if (!CourseSyncService.instance) {\n CourseSyncService.instance = new CourseSyncService();\n }\n return CourseSyncService.instance;\n }\n\n /**\n * Reset the singleton (for testing).\n */\n static resetInstance(): void {\n if (CourseSyncService.instance) {\n // Close all local DBs\n for (const [, entry] of CourseSyncService.instance.entries) {\n if (entry.localDB) {\n entry.localDB.close().catch(() => {});\n }\n }\n CourseSyncService.instance.entries.clear();\n }\n CourseSyncService.instance = null;\n }\n\n // --------------------------------------------------------------------------\n // Public API\n // --------------------------------------------------------------------------\n\n /**\n * Ensure a course's local replica is synced.\n *\n * On first call for a course:\n * 1. Fetches CourseConfig from remote to check localSync.enabled\n * 2. If enabled, performs one-shot replication remote → local\n * 3. Pre-warms PouchDB view indices (elo, getTags)\n *\n * On subsequent calls: returns immediately if already synced, or awaits\n * the in-flight sync if one is in progress.\n *\n * Safe to call multiple times — concurrent calls coalesce to one sync.\n *\n * @param courseId - The course to sync\n * @param forceEnabled - Skip the CourseConfig check and sync regardless.\n * Useful when the caller already knows local sync is desired (e.g.,\n * LettersPractice hardcodes this).\n */\n async ensureSynced(courseId: string, forceEnabled?: boolean): Promise<void> {\n const existing = this.entries.get(courseId);\n\n // Already synced\n if (existing?.status.state === 'ready') {\n return;\n }\n\n // Already disabled\n if (existing?.status.state === 'disabled') {\n return;\n }\n\n // Sync in flight — coalesce\n if (existing?.readyPromise) {\n return existing.readyPromise;\n }\n\n // Start a new sync\n const entry: SyncEntry = {\n localDB: null,\n status: { state: 'not-started' },\n readyPromise: null,\n };\n this.entries.set(courseId, entry);\n\n entry.readyPromise = this.performSync(courseId, entry, forceEnabled);\n return entry.readyPromise;\n }\n\n /**\n * Get the local PouchDB for a course, or null if not available.\n *\n * Returns null when:\n * - Local sync is not enabled for this course\n * - Sync has not been triggered yet\n * - Sync is still in progress\n * - Sync failed\n */\n getLocalDB(courseId: string): PouchDB.Database | null {\n const entry = this.entries.get(courseId);\n if (entry?.status.state === 'ready' && entry.localDB) {\n return entry.localDB;\n }\n return null;\n }\n\n /**\n * Check whether a course has a ready local replica.\n */\n isReady(courseId: string): boolean {\n return this.entries.get(courseId)?.status.state === 'ready';\n }\n\n /**\n * Get detailed sync status for a course.\n */\n getStatus(courseId: string): CourseSyncStatus {\n return (\n this.entries.get(courseId)?.status ?? { state: 'not-started' }\n );\n }\n\n // --------------------------------------------------------------------------\n // Internal\n // --------------------------------------------------------------------------\n\n private async performSync(\n courseId: string,\n entry: SyncEntry,\n forceEnabled?: boolean\n ): Promise<void> {\n try {\n // Step 1: Check if local sync is enabled for this course\n if (!forceEnabled) {\n entry.status = { state: 'checking-config' };\n const enabled = await this.checkLocalSyncEnabled(courseId);\n if (!enabled) {\n entry.status = { state: 'disabled' };\n entry.readyPromise = null;\n logger.debug(\n `[CourseSyncService] Local sync disabled for course ${courseId}`\n );\n return;\n }\n }\n\n // Step 2: Create local PouchDB and replicate\n entry.status = { state: 'syncing' };\n const localDBName = this.localDBName(courseId);\n const localDB = new pouch(localDBName);\n entry.localDB = localDB;\n\n const remoteDB = this.getRemoteDB(courseId);\n const syncStart = Date.now();\n\n logger.info(\n `[CourseSyncService] Starting one-shot replication for course ${courseId}`\n );\n\n const result = await this.replicate(remoteDB, localDB);\n const syncTimeMs = Date.now() - syncStart;\n\n logger.info(\n `[CourseSyncService] Replication complete for course ${courseId}: ` +\n `${result.docs_written} docs in ${syncTimeMs}ms`\n );\n\n // Step 3: Pre-warm view indices\n entry.status = { state: 'warming-views' };\n const warmStart = Date.now();\n await this.warmViewIndices(localDB);\n const viewWarmTimeMs = Date.now() - warmStart;\n\n logger.info(\n `[CourseSyncService] View indices warmed for course ${courseId} in ${viewWarmTimeMs}ms`\n );\n\n // Done\n entry.status = {\n state: 'ready',\n docsReplicated: result.docs_written,\n syncTimeMs,\n viewWarmTimeMs,\n };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n logger.error(\n `[CourseSyncService] Sync failed for course ${courseId}: ${errorMsg}`\n );\n entry.status = { state: 'error', error: errorMsg };\n entry.readyPromise = null;\n\n // Clean up the local DB on failure — don't leave a partial replica\n if (entry.localDB) {\n try {\n await entry.localDB.destroy();\n } catch {\n // Ignore cleanup errors\n }\n entry.localDB = null;\n }\n }\n }\n\n /**\n * Check CourseConfig.localSync.enabled on the remote DB.\n */\n private async checkLocalSyncEnabled(courseId: string): Promise<boolean> {\n try {\n const remoteDB = this.getRemoteDB(courseId);\n const config = await remoteDB.get<CourseConfig>('CourseConfig');\n return config.localSync?.enabled === true;\n } catch (e) {\n logger.warn(\n `[CourseSyncService] Could not read CourseConfig for ${courseId}, ` +\n `assuming local sync disabled: ${e}`\n );\n return false;\n }\n }\n\n /**\n * One-shot replication from remote to local.\n */\n private replicate(\n source: PouchDB.Database,\n target: PouchDB.Database\n ): Promise<PouchDB.Replication.ReplicationResultComplete<object>> {\n return new Promise((resolve, reject) => {\n void pouch.replicate(source, target, {\n // One-shot, not live. Local is a read-only snapshot.\n })\n .on('complete', (info) => {\n resolve(info);\n })\n .on('error', (err) => {\n reject(err);\n });\n });\n }\n\n /**\n * Pre-warm PouchDB view indices by running a minimal query against each\n * design doc. This forces PouchDB to build the MapReduce index now\n * (during a loading phase) rather than on first pipeline query.\n */\n private async warmViewIndices(localDB: PouchDB.Database): Promise<void> {\n const viewsToWarm = ['elo', 'getTags'];\n\n for (const viewName of viewsToWarm) {\n try {\n await localDB.query(viewName, { limit: 1 });\n logger.debug(\n `[CourseSyncService] Warmed view index: ${viewName}`\n );\n } catch (e) {\n // View might not exist in this course DB — that's OK.\n // Not all courses have all design docs.\n logger.debug(\n `[CourseSyncService] Could not warm view ${viewName}: ${e}`\n );\n }\n }\n }\n\n /**\n * Get a remote PouchDB handle for a course.\n */\n private getRemoteDB(courseId: string): PouchDB.Database {\n return getCourseDB(courseId);\n }\n\n /**\n * Local DB naming convention.\n */\n private localDBName(courseId: string): string {\n return `coursedb-local-${courseId}`;\n }\n}","import { ENV, NOT_SET } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport fetch from 'cross-fetch';\n\ninterface SessionResponse {\n info: unknown;\n ok: boolean;\n userCtx: {\n name: string;\n roles: string[];\n };\n}\n\nexport async function getCurrentSession(): Promise<SessionResponse> {\n // Legacy XMLHttpRequest implementation\n // return new Promise((resolve, reject) => {\n // const authXML = new XMLHttpRequest();\n // authXML.withCredentials = true;\n //\n // authXML.onerror = (e): void => {\n // reject(new Error('Session check failed:', e));\n // };\n //\n // authXML.addEventListener('load', () => {\n // try {\n // const resp: SessionResponse = JSON.parse(authXML.responseText);\n // resolve(resp);\n // } catch (e) {\n // reject(e);\n // }\n // });\n //\n // const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}_session`;\n // authXML.open('GET', url);\n // authXML.send();\n // });\n \n try {\n // Handle case where ENV variables might not be properly set\n if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {\n throw new Error(`CouchDB server configuration not properly initialized. Protocol: \"${ENV.COUCHDB_SERVER_PROTOCOL}\", URL: \"${ENV.COUCHDB_SERVER_URL}\"`);\n }\n \n // Ensure URL has proper slash before _session endpoint\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.debug(`Attempting session check at: ${url}`);\n \n const response = await fetch(url, {\n method: 'GET',\n credentials: 'include',\n });\n \n if (!response.ok) {\n throw new Error(`Session check failed: ${response.status}`);\n }\n \n const resp: SessionResponse = await response.json();\n return resp;\n } catch (error) {\n // Use same URL construction logic for error reporting\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.error(`Session check error attempting to connect to: ${url} - ${error}`);\n throw new Error(`Session check failed connecting to ${url}: ${error}`);\n }\n}\n\nexport async function getLoggedInUsername(): Promise<string> {\n const session = await getCurrentSession();\n if (session.userCtx.name && session.userCtx.name !== '') {\n return session.userCtx.name;\n }\n // Not logged in - throw so caller can handle guest account\n throw new Error('No logged in user');\n}\n","// packages/db/src/impl/couch/CouchDBSyncStrategy.ts\n\nimport { ENV } from '@db/factory';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { Status } from '@vue-skuilder/common';\nimport type { SyncStrategy } from '../common/SyncStrategy';\nimport type { AccountCreationResult, AuthenticationResult } from '../common/types';\nimport { getLocalUserDB, hexEncode, updateGuestAccountExpirationDate, accomodateGuest } from '../common';\nimport pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from './index';\nimport { getLoggedInUsername } from './auth';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n/**\n * Sync strategy that implements full CouchDB remote synchronization\n * Handles account creation, authentication, and live sync with remote CouchDB server\n */\nexport class CouchDBSyncStrategy implements SyncStrategy {\n private syncHandle?: any; // Handle to cancel sync if needed\n\n setupRemoteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // For guest users, remote is same as local (no remote sync)\n return getLocalUserDB(username);\n } else {\n // For real users, connect to remote CouchDB\n return this.getUserDB(username);\n }\n }\n\n getWriteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // Guest users write to local database\n return getLocalUserDB(username);\n } else {\n // Authenticated users write to remote (which will sync to local)\n return this.getUserDB(username);\n }\n }\n\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {\n // Only sync if local and remote are different instances\n if (localDB !== remoteDB) {\n this.syncHandle = pouch.sync(localDB, remoteDB, {\n live: true,\n retry: true,\n });\n }\n // If they're the same (guest mode), no sync needed\n }\n\n stopSync?(): void {\n if (this.syncHandle) {\n this.syncHandle.cancel();\n this.syncHandle = undefined;\n }\n }\n\n canCreateAccount(): boolean {\n return true;\n }\n\n canAuthenticate(): boolean {\n return true;\n }\n\n async createAccount(username: string, password: string): Promise<AccountCreationResult> {\n // IMPORTANT: Capture funnel username BEFORE any operations that might change session state\n const funnelUsername = await this.getCurrentUsername();\n const isFunnelAccount = funnelUsername.startsWith(GuestUsername);\n\n if (isFunnelAccount) {\n logger.info(`Creating account for funnel user ${funnelUsername} -> ${username}`);\n }\n\n try {\n const signupRequest = await this.getRemoteCouchRootDB().signUp(username, password);\n\n if (signupRequest.ok) {\n log(`CREATEACCOUNT: Successfully created account for ${username}`);\n\n // Log out any existing session\n try {\n const logoutResult = await this.getRemoteCouchRootDB().logOut();\n log(`CREATEACCOUNT: logged out: ${logoutResult.ok}`);\n } catch {\n // Ignore logout errors - might not be logged in\n }\n\n // Log in as the new user\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n log(`CREATEACCOUNT: logged in as new user: ${loginResult.ok}`);\n\n if (loginResult.ok) {\n // Migrate funnel account data if applicable\n if (isFunnelAccount) {\n logger.info(`Migrating data from funnel account ${funnelUsername} to ${username}`);\n const migrationResult = await this.migrateFunnelData(funnelUsername, username);\n if (!migrationResult.success) {\n logger.warn(`Migration failed: ${migrationResult.error}`);\n // Continue anyway - don't block account creation\n }\n }\n\n return {\n status: Status.ok,\n error: undefined,\n };\n } else {\n return {\n status: Status.error,\n error: 'Failed to log in after account creation',\n };\n }\n } else {\n logger.warn(`Signup not OK: ${JSON.stringify(signupRequest)}`);\n return {\n status: Status.error,\n error: 'Account creation failed',\n };\n }\n } catch (e: any) {\n if (e.reason === 'Document update conflict.') {\n return {\n status: Status.error,\n error: 'This username is taken!',\n };\n }\n logger.error(`Error on signup: ${JSON.stringify(e)}`);\n return {\n status: Status.error,\n error: e.message || 'Unknown error during account creation',\n };\n }\n }\n\n async authenticate(username: string, password: string): Promise<AuthenticationResult> {\n try {\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n\n if (loginResult.ok) {\n log(`Successfully logged in as ${username}`);\n return {\n ok: true,\n };\n } else {\n log(`Login failed for ${username}`);\n return {\n ok: false,\n error: 'Invalid username or password',\n };\n }\n } catch (error: any) {\n logger.error(`Authentication error for ${username}:`, error);\n return {\n ok: false,\n error: error.message || 'Authentication failed',\n };\n }\n }\n\n async logout(): Promise<AuthenticationResult> {\n try {\n const result = await this.getRemoteCouchRootDB().logOut();\n return {\n ok: result.ok,\n error: result.ok ? undefined : 'Logout failed',\n };\n } catch (error: any) {\n logger.error('Logout error:', error);\n return {\n ok: false,\n error: error.message || 'Logout failed',\n };\n }\n }\n\n async getCurrentUsername(): Promise<string> {\n logger.log('[funnel] CouchDBSyncStrategy.getCurrentUsername() called');\n try {\n const loggedInUsername = await getLoggedInUsername();\n logger.log('[funnel] getLoggedInUsername() returned:', loggedInUsername);\n return loggedInUsername;\n } catch (e) {\n // Not logged in - return unique guest account\n logger.log('[funnel] getLoggedInUsername() failed, calling accomodateGuest()');\n logger.log('[funnel] Error was:', e);\n const guestInfo = accomodateGuest();\n logger.log('[funnel] accomodateGuest() returned:', guestInfo);\n return guestInfo.username;\n }\n }\n\n /**\n * Migrate data from funnel account to real account\n */\n private async migrateFunnelData(\n oldUsername: string,\n newUsername: string\n ): Promise<{ success: boolean; error?: string }> {\n try {\n logger.info(`Starting data migration from ${oldUsername} to ${newUsername}`);\n\n const oldLocalDB = getLocalUserDB(oldUsername);\n const newLocalDB = getLocalUserDB(newUsername);\n\n // Get all docs from funnel account\n const allDocs = await oldLocalDB.allDocs({ include_docs: true });\n\n logger.info(`Found ${allDocs.rows.length} documents in funnel account`);\n\n // Filter out design docs and prepare for migration\n const docsToMigrate = allDocs.rows\n .filter(row => !row.id.startsWith('_design/'))\n .map(row => ({\n ...row.doc,\n _rev: undefined, // Remove rev to insert as new\n }));\n\n if (docsToMigrate.length > 0) {\n await newLocalDB.bulkDocs(docsToMigrate);\n logger.info(`Successfully migrated ${docsToMigrate.length} documents from ${oldUsername} to ${newUsername}`);\n } else {\n logger.info('No documents to migrate from funnel account');\n }\n\n return { success: true };\n } catch (error) {\n logger.error('Migration failed:', error);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n }\n\n /**\n * Get remote CouchDB root database for authentication operations\n */\n private getRemoteCouchRootDB(): PouchDB.Database {\n const remoteStr: string =\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'skuilder';\n\n try {\n return new pouch(remoteStr, {\n skip_setup: true,\n });\n } catch (error) {\n logger.error('Failed to initialize remote CouchDB connection:', error);\n throw new Error(`Failed to initialize CouchDB: ${JSON.stringify(error)}`);\n }\n }\n\n /**\n * Get remote user database for a specific user\n */\n private getUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // Odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n }\n}\n","import { ENV } from '@db/factory';\nimport {\n DocType,\n DocTypePrefixes,\n GuestUsername,\n log,\n SkuilderCourseData,\n} from '../../core/types/types-legacy';\nimport fetch from 'cross-fetch';\n// import { getCurrentUser } from '../../stores/useAuthStore';\nimport moment, { Moment } from 'moment';\nimport { logger } from '@db/util/logger';\n\nimport pouch from './pouchdb-setup';\n\nimport { ScheduledCard } from '@db/core/types/user';\nimport process from 'process';\n\nconst isBrowser = typeof window !== 'undefined';\n\nif (isBrowser) {\n (window as any).process = process; // required as a fix for pouchdb - see #18\n}\n\nconst expiryDocID: string = 'GuestAccountExpirationDate';\n\nconst GUEST_LOCAL_DB = `userdb-${GuestUsername}`;\nexport const localUserDB: PouchDB.Database = new pouch(GUEST_LOCAL_DB);\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\nconst pouchDBincludeCredentialsConfig: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n fetch(url: string | Request, opts: RequestInit): Promise<Response> {\n opts.credentials = 'include';\n\n return (pouch as any).fetch(url, opts);\n },\n} as PouchDB.Configuration.RemoteDatabaseConfiguration;\n\n/**\n * Creates PouchDB configuration with appropriate authentication method\n * - Uses HTTP Basic Auth when credentials are available (Node.js/MCP)\n * - Falls back to cookie auth for browser environments\n */\nexport function createPouchDBConfig(): PouchDB.Configuration.RemoteDatabaseConfiguration {\n // Check if running in Node.js with explicit credentials\n const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;\n const isNodeEnvironment = typeof window === 'undefined';\n \n if (hasExplicitCredentials && isNodeEnvironment) {\n // Use HTTP Basic Auth for Node.js environments (MCP server)\n return {\n fetch(url: string | Request, opts: RequestInit = {}): Promise<Response> {\n const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);\n const headers = new Headers(opts.headers || {});\n headers.set('Authorization', `Basic ${basicAuth}`);\n \n const newOpts = {\n ...opts,\n headers: headers\n };\n \n return (pouch as any).fetch(url, newOpts);\n }\n } as PouchDB.Configuration.RemoteDatabaseConfiguration;\n }\n \n // Use cookie-based auth for browser environments or when no explicit credentials\n return pouchDBincludeCredentialsConfig;\n}\n\nfunction getCouchDB(dbName: string): PouchDB.Database {\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n // todo: keep a cache of opened courseDBs? need to benchmark this somehow\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'coursedb-' + courseID,\n createPouchDBConfig()\n );\n}\n\nexport async function getLatestVersion() {\n try {\n const docs = await getCouchDB('version').allDocs({\n descending: true,\n limit: 1,\n });\n if (docs && docs.rows && docs.rows[0]) {\n return docs.rows[0].id;\n } else {\n return '0.0.0';\n }\n } catch {\n return '-1';\n }\n}\n\n/**\n * Checks the remote couchdb to see if a given username is available\n * @param username The username to be checked\n */\nexport async function usernameIsAvailable(username: string): Promise<boolean> {\n log(`Checking availability of ${username}`);\n \n // Legacy XMLHttpRequest implementation (browser sync)\n // const req = new XMLHttpRequest();\n // const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n // req.open('HEAD', url, false);\n // req.send();\n // return req.status === 404;\n \n try {\n const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n const response = await fetch(url, { method: 'HEAD' });\n return response.status === 404;\n } catch (error) {\n log(`Error checking username availability: ${error}`);\n return false;\n }\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\nexport function getCourseDocs<T extends SkuilderCourseData>(\n courseID: string,\n docIDs: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n) {\n return getCourseDB(courseID).allDocs<T>({\n ...options,\n keys: docIDs,\n });\n}\n\nexport function getCourseDoc<T extends SkuilderCourseData>(\n courseID: string,\n docID: PouchDB.Core.DocumentId,\n options: PouchDB.Core.GetOptions = {}\n): Promise<T> {\n return getCourseDB(courseID).get<T>(docID, options);\n}\n\n/**\n * Returns *all* cards from the parameter courses, in\n * 'qualified' card format (\"courseid-cardid\")\n *\n * @param courseIDs A list of all course_ids to get cards from\n */\nexport async function getRandomCards(courseIDs: string[]) {\n if (courseIDs.length === 0) {\n throw new Error(`getRandomCards:\\n\\tAttempted to get all cards from no courses!`);\n } else {\n const courseResults = await Promise.all(\n courseIDs.map((course) => {\n return getCourseDB(course).find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n });\n })\n );\n\n const ret: string[] = [];\n courseResults.forEach((courseCards, index) => {\n courseCards.docs.forEach((doc) => {\n ret.push(`${courseIDs[index]}-${doc._id}`);\n });\n });\n\n return ret;\n }\n}\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nexport function getCouchUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n // console.log(`Getting user db: ${username}`);\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n}\n\nexport function scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n}) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void getCouchUserDB(review.user).put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\n//////////////////////\n// Package exports\n//////////////////////\n\nexport * from '../../core/interfaces/contentSource';\nexport * from './adminDB';\nexport * from './classroomDB';\nexport * from './courseAPI';\nexport * from './courseDB';\nexport * from './CourseSyncService';\nexport * from './CouchDBSyncStrategy';\n","import { DocType, DocTypePrefixes, StrategyStateDoc, buildStrategyStateId } from '@db/core';\nimport { getCardHistoryID } from '@db/core/util';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport moment, { Moment } from 'moment';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\n\nimport {\n ClassroomRegistrationDoc,\n UserCourseSetting,\n UserDBInterface,\n UsrCrsDataInterface,\n} from '@db/core';\nimport {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n UserConfig,\n} from '@db/core/types/user';\nimport { DocumentUpdater } from '@db/study';\nimport { CardHistory, CardRecord } from '../../core/types/types-legacy';\nimport { UserOutcomeRecord } from '../../core/types/userOutcome';\nimport type { SyncStrategy } from './SyncStrategy';\nimport {\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n REVIEW_TIME_FORMAT,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\nimport { PouchError } from '../couch/types';\nimport UpdateQueue, { Update } from '../couch/updateQueue';\nimport { UsrCrsData } from '../couch/user-course-relDB';\nimport { getCredentialledCourseConfig } from '../couch/index';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n// logger.log(`Connecting to remote: ${remoteStr}`);\n\ninterface DesignDoc {\n _id: string;\n views: {\n [viewName: string]: {\n map: string; // String representation of the map function\n };\n };\n}\n\n/**\n * Base user database implementation that uses a pluggable sync strategy.\n * Handles local storage operations and delegates sync/remote operations to the strategy.\n */\nexport class BaseUser implements UserDBInterface, DocumentUpdater {\n private static _instance: BaseUser;\n private static _initialized: boolean = false;\n\n public static Dummy(syncStrategy: SyncStrategy): BaseUser {\n return new BaseUser('Me', syncStrategy);\n }\n\n static readonly DOC_IDS = {\n CONFIG: 'CONFIG',\n COURSE_REGISTRATIONS: 'CourseRegistrations',\n CLASSROOM_REGISTRATIONS: 'ClassroomRegistrations',\n };\n\n // private email: string;\n private _username: string;\n private syncStrategy: SyncStrategy;\n\n public getUsername(): string {\n return this._username;\n }\n\n public isLoggedIn(): boolean {\n return !this._username.startsWith(GuestUsername);\n }\n\n public remote(): PouchDB.Database {\n return this.remoteDB;\n }\n\n private localDB!: PouchDB.Database;\n private remoteDB!: PouchDB.Database;\n private writeDB!: PouchDB.Database; // Database to use for write operations (local-first approach)\n\n private updateQueue!: UpdateQueue;\n\n public async createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }> {\n if (!this.syncStrategy.canCreateAccount()) {\n throw new Error('Account creation not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername)) {\n throw new Error(\n `Cannot create a new account while logged in:\nCurrently logged-in as ${this._username}.`\n );\n }\n\n const result = await this.syncStrategy.createAccount!(username, password);\n\n // If account creation was successful, update the username and reinitialize\n if (result.status === Status.ok) {\n log(`Account created successfully, updating username to ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n\n return {\n status: result.status,\n error: result.error || '',\n };\n }\n public async login(username: string, password: string) {\n if (!this.syncStrategy.canAuthenticate()) {\n throw new Error('Authentication not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername) && this._username != username) {\n if (this._username != username) {\n throw new Error(`Cannot change accounts while logged in.\n Log out of account ${this.getUsername()} before logging in as ${username}.`);\n }\n logger.warn(`User ${this._username} is already logged in, but executing login again.`);\n }\n\n const loginResult = await this.syncStrategy.authenticate!(username, password);\n if (loginResult.ok) {\n log(`Logged in as ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n return loginResult;\n }\n\n public async resetUserData(): Promise<{ status: Status; error?: string }> {\n // Only allow reset for local-only sync strategies\n if (this.syncStrategy.canAuthenticate()) {\n return {\n status: Status.error,\n error:\n 'Reset user data is only available for local-only mode. Use logout instead for remote sync.',\n };\n }\n\n try {\n const localDB = getLocalUserDB(this._username);\n\n // Get all documents to identify user data to clear\n const allDocs = await localDB.allDocs({ include_docs: false });\n\n // Identify documents to delete (preserve authentication and user identity)\n const docsToDelete = allDocs.rows\n .filter((row) => {\n const id = row.id;\n // Delete user progress data but preserve authentication and user identity\n return (\n id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history\n id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews\n id.startsWith(DocTypePrefixes[DocType.STRATEGY_STATE]) || // Strategy state (user prefs, progression)\n id.startsWith(DocTypePrefixes[DocType.USER_OUTCOME]) || // Evolutionary orchestration outcomes\n id.startsWith(DocTypePrefixes[DocType.STRATEGY_LEARNING_STATE]) || // Strategy learning state\n id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations\n id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations\n id === BaseUser.DOC_IDS.CONFIG // User config\n );\n })\n .map((row) => ({ _id: row.id, _rev: row.value.rev, _deleted: true }));\n\n if (docsToDelete.length > 0) {\n await localDB.bulkDocs(docsToDelete);\n }\n\n // Reinitialize to create fresh default documents\n await this.init();\n\n return { status: Status.ok };\n } catch (error) {\n logger.error('Failed to reset user data:', error);\n return {\n status: Status.error,\n error: error instanceof Error ? error.message : 'Unknown error during reset',\n };\n }\n }\n\n public async logout() {\n if (!this.syncStrategy.canAuthenticate()) {\n // For strategies that don't support authentication, just switch to guest\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n return { ok: true };\n }\n\n const ret = await this.syncStrategy.logout!();\n // return to 'guest' mode\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n\n return ret;\n }\n\n public async get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta> {\n return this.localDB.get<T>(id);\n }\n\n public update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>) {\n return this.updateQueue.update(id, update);\n }\n\n public async getCourseRegistrationsDoc(): Promise<\n CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);\n\n let ret;\n\n try {\n const regDoc = await this.localDB.get<CourseRegistrationDoc>(\n BaseUser.DOC_IDS.COURSE_REGISTRATIONS\n );\n return regDoc;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n await this.localDB.put<CourseRegistrationDoc>({\n _id: BaseUser.DOC_IDS.COURSE_REGISTRATIONS,\n courses: [],\n studyWeight: {},\n });\n ret = await this.getCourseRegistrationsDoc();\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n }\n\n public async getActiveCourses() {\n const reg = await this.getCourseRegistrationsDoc();\n return reg.courses.filter((c) => {\n return c.status === undefined || c.status === 'active';\n });\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * a scheduled review for.\n *\n */\n public async getActiveCards() {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n return reviews.rows.map((r) => {\n return {\n courseID: r.doc!.courseId,\n cardID: r.doc!.cardId,\n };\n });\n }\n\n public async getActivityRecords(): Promise<ActivityRecord[]> {\n try {\n const hist = await this.getHistory();\n\n const allRecords: ActivityRecord[] = [];\n if (!Array.isArray(hist)) {\n logger.error('getHistory did not return an array:', hist);\n return allRecords;\n }\n\n // Sample the first few records to understand structure\n let sampleCount = 0;\n\n for (let i = 0; i < hist.length; i++) {\n try {\n if (hist[i] && Array.isArray(hist[i]!.records)) {\n hist[i]!.records.forEach((record: CardRecord) => {\n try {\n // Skip this record if timeStamp is missing\n if (!record.timeStamp) {\n return;\n }\n\n let timeStamp;\n\n // Handle different timestamp formats\n if (typeof record.timeStamp === 'object') {\n // It's likely a Moment object\n if (typeof record.timeStamp.toDate === 'function') {\n // It's definitely a Moment object\n timeStamp = record.timeStamp.toISOString();\n } else if (record.timeStamp instanceof Date) {\n // It's a Date object\n timeStamp = record.timeStamp.toISOString();\n } else {\n // Log a sample of unknown object types, but don't flood logger\n if (sampleCount < 3) {\n logger.warn('Unknown timestamp object type:', record.timeStamp);\n sampleCount++;\n }\n return;\n }\n } else if (typeof record.timeStamp === 'string') {\n // It's already a string, but make sure it's a valid date\n const date = new Date(record.timeStamp);\n if (isNaN(date.getTime())) {\n return; // Invalid date string\n }\n timeStamp = record.timeStamp;\n } else if (typeof record.timeStamp === 'number') {\n // Assume it's a Unix timestamp (milliseconds since epoch)\n timeStamp = new Date(record.timeStamp).toISOString();\n } else {\n // Unknown type, skip\n return;\n }\n\n allRecords.push({\n timeStamp,\n courseID: record.courseID || 'unknown',\n cardID: record.cardID || 'unknown',\n timeSpent: record.timeSpent || 0,\n type: 'card_view',\n });\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (err) {\n // Silently skip problematic records to avoid flooding logs\n }\n });\n }\n } catch (err) {\n logger.error('Error processing history item:', err);\n }\n }\n\n logger.debug(`Found ${allRecords.length} activity records`);\n return allRecords;\n } catch (err) {\n logger.error('Error in getActivityRecords:', err);\n return [];\n }\n }\n\n private async getReviewstoDate(targetDate: Moment, course_id?: string) {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n log(\n `Fetching ${this._username}'s scheduled reviews${\n course_id ? ` for course ${course_id}` : ''\n }.`\n );\n return reviews.rows\n .filter((r) => {\n if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {\n const date = moment.utc(\n r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),\n REVIEW_TIME_FORMAT\n );\n if (targetDate.isAfter(date)) {\n if (course_id === undefined || r.doc!.courseId === course_id) {\n return true;\n }\n }\n }\n })\n .map((r) => r.doc!);\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews(course_id?: string) {\n const now = moment.utc();\n return this.getReviewstoDate(now, course_id);\n }\n\n public async getScheduledReviewCount(course_id: string): Promise<number> {\n return (await this.getPendingReviews(course_id)).length;\n }\n\n public async getRegisteredCourses() {\n const regDoc = await this.getCourseRegistrationsDoc();\n return regDoc.courses.filter((c) => {\n return !c.status || c.status === 'active' || c.status === 'maintenance-mode';\n });\n }\n\n public async getCourseRegDoc(courseID: string) {\n const regDocs = await this.getCourseRegistrationsDoc();\n const ret = regDocs.courses.find((c) => c.courseID === courseID);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course registration not found for course ID: ${courseID}`);\n }\n }\n\n public async registerForCourse(course_id: string, previewMode: boolean = false) {\n return this.getCourseRegistrationsDoc()\n .then((doc: CourseRegistrationDoc) => {\n const status = previewMode ? 'preview' : 'active';\n logger.debug(`Registering for ${course_id} with status: ${status}`);\n\n const regItem: CourseRegistration = {\n status: status,\n courseID: course_id,\n user: true,\n admin: false,\n moderator: false,\n elo: {\n global: {\n score: 1000,\n count: 0,\n },\n tags: {},\n misc: {},\n },\n };\n\n if (\n doc.courses.filter((course) => {\n return course.courseID === regItem.courseID;\n }).length === 0\n ) {\n log(`It's a new course registration!`);\n doc.courses.push(regItem);\n doc.studyWeight[course_id] = 1;\n } else {\n doc.courses.forEach((c) => {\n log(`Found the previously registered course!`);\n if (c.courseID === course_id) {\n c.status = status;\n }\n });\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n })\n .catch((e) => {\n log(`Registration failed because of: ${JSON.stringify(e)}`);\n throw e;\n });\n }\n public async dropCourse(course_id: string, dropStatus: CourseRegistration['status'] = 'dropped') {\n return this.getCourseRegistrationsDoc().then((doc) => {\n let index: number = -1;\n for (let i = 0; i < doc.courses.length; i++) {\n if (doc.courses[i].courseID === course_id) {\n index = i;\n }\n }\n\n if (index !== -1) {\n // remove from the relative-weighting of course study\n delete doc.studyWeight[course_id];\n // set drop status\n doc.courses[index].status = dropStatus;\n } else {\n throw new Error(\n `User ${this.getUsername()} is not currently registered for course ${course_id}`\n );\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n });\n }\n\n public async getCourseInterface(courseId: string): Promise<UsrCrsDataInterface> {\n return new UsrCrsData(this, courseId);\n }\n\n public async getUserEditableCourses() {\n let courseIDs: string[] = [];\n\n const registeredCourses = await this.getCourseRegistrationsDoc();\n\n courseIDs = courseIDs.concat(\n registeredCourses.courses.map((course) => {\n return course.courseID;\n })\n );\n\n const cfgs = await Promise.all(\n courseIDs.map(async (id) => {\n return await getCredentialledCourseConfig(id);\n })\n );\n return cfgs;\n }\n\n public async getConfig(): Promise<UserConfig & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n const defaultConfig: PouchDB.Core.Document<UserConfig> = {\n _id: BaseUser.DOC_IDS.CONFIG,\n darkMode: false,\n likesConfetti: false,\n sessionTimeLimit: 5,\n };\n\n try {\n const cfg = await this.localDB.get<UserConfig>(BaseUser.DOC_IDS.CONFIG);\n logger.debug('Raw config from DB:', cfg);\n\n return cfg;\n } catch (e) {\n const err = e as PouchError;\n if (err.name && err.name === 'not_found') {\n await this.localDB.put<UserConfig>(defaultConfig);\n return this.getConfig();\n } else {\n logger.error(`Error setting user default config:`, e);\n throw new Error(`Error returning the user's configuration: ${JSON.stringify(e)}`);\n }\n }\n }\n\n public async setConfig(items: Partial<UserConfig>) {\n logger.debug(`Setting Config items ${JSON.stringify(items)}`);\n\n const c = await this.getConfig();\n const put = await this.localDB.put<UserConfig>({\n ...c,\n ...items,\n });\n\n if (put.ok) {\n logger.debug(`Config items set: ${JSON.stringify(items)}`);\n } else {\n logger.error(`Error setting config items: ${JSON.stringify(put)}`);\n }\n }\n\n /**\n *\n * This function should be called *only* by the pouchdb datalayer provider\n * auth store.\n *\n *\n * Anyone else seeking the current user should use the auth store's\n * exported `getCurrentUser` method.\n *\n */\n public static async instance(syncStrategy: SyncStrategy, username?: string): Promise<BaseUser> {\n if (username) {\n BaseUser._instance = new BaseUser(username, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n } else if (BaseUser._instance && BaseUser._initialized) {\n // log(`USER.instance() returning user ${BaseUser._instance._username}`);\n return BaseUser._instance;\n } else if (BaseUser._instance) {\n return new Promise((resolve) => {\n (function waitForUser() {\n if (BaseUser._initialized) {\n return resolve(BaseUser._instance);\n } else {\n setTimeout(waitForUser, 50);\n }\n })();\n });\n } else {\n const guestUsername = await syncStrategy.getCurrentUsername();\n BaseUser._instance = new BaseUser(guestUsername, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n }\n }\n\n private constructor(username: string, syncStrategy: SyncStrategy) {\n BaseUser._initialized = false;\n this._username = username;\n this.syncStrategy = syncStrategy;\n this.setDBandQ();\n }\n\n private setDBandQ() {\n this.localDB = getLocalUserDB(this._username);\n this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);\n // writeDB follows local-first pattern: static mode writes to local, CouchDB writes to remote/local as appropriate\n this.writeDB = this.syncStrategy.getWriteDB\n ? this.syncStrategy.getWriteDB(this._username)\n : this.localDB;\n this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);\n }\n\n private async init() {\n BaseUser._initialized = false;\n\n // Skip admin user\n if (this._username === 'admin') {\n BaseUser._initialized = true;\n return;\n }\n\n this.setDBandQ();\n\n this.syncStrategy.startSync(this.localDB, this.remoteDB);\n this.applyDesignDocs().catch((error) => {\n log(`Error in applyDesignDocs background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);\n }\n });\n this.deduplicateReviews().catch((error) => {\n log(`Error in deduplicateReviews background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in background task: ${JSON.stringify(error)}`);\n }\n });\n BaseUser._initialized = true;\n }\n\n private static designDocs: DesignDoc[] = [\n {\n _id: '_design/reviewCards',\n views: {\n reviewCards: {\n map: `function (doc) {\n if (doc._id && doc._id.indexOf('card_review') === 0 && doc.courseId && doc.cardId) {\n emit(doc._id, doc.courseId + '-' + doc.cardId);\n }\n }`,\n },\n },\n },\n ];\n\n private async applyDesignDocs() {\n log(`Starting applyDesignDocs for user: ${this._username}`);\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n\n if (this._username === 'admin') {\n // Skip admin user\n log('Skipping design docs for admin user');\n return;\n }\n\n log(`Applying ${BaseUser.designDocs.length} design docs`);\n for (const doc of BaseUser.designDocs) {\n log(`Applying design doc: ${doc._id}`);\n try {\n // Try to get existing doc\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n // Update existing doc\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'not_found') {\n // Create new doc\n await this.remoteDB.put(doc);\n } else {\n throw e; // Re-throw unexpected errors\n }\n }\n } catch (error: unknown) {\n if ((error as any).name && (error as any).name === 'conflict') {\n logger.warn(`Design doc ${doc._id} update conflict - will retry`);\n // Wait a bit and try again\n await new Promise((resolve) => setTimeout(resolve, 1000));\n await this.applyDesignDoc(doc); // Recursive retry\n } else {\n logger.error(`Failed to apply design doc ${doc._id}:`, error);\n throw error;\n }\n }\n }\n }\n\n // Helper method for single doc update with retry\n private async applyDesignDoc(doc: DesignDoc, retries = 3): Promise<void> {\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'conflict' && retries > 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n return this.applyDesignDoc(doc, retries - 1);\n }\n throw e;\n }\n }\n\n /**\n * Logs a record of the user's interaction with the card and returns the card's\n * up-to-date history.\n *\n * **Automatic Initialization:**\n * If this is the user's first interaction with the card (CardHistory doesn't exist),\n * this method automatically creates the CardHistory document with initial values\n * (lapses: 0, streak: 0, bestInterval: 0).\n *\n * **Error Handling:**\n * - Handles 404 errors by creating initial CardHistory document\n * - Re-throws all other errors from UpdateQueue\n *\n * // [ ] #db-refactor extract to a smaller scope - eg, UserStudySession\n *\n * @param record - The recent recorded interaction between user and card\n * @returns The updated state of the card's CardHistory data\n * @throws Error if document creation fails or non-404 database error occurs\n */\n\n public async putCardRecord<T extends CardRecord>(\n record: T\n ): Promise<CardHistory<CardRecord> & PouchDB.Core.RevisionIdMeta> {\n const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);\n // stringify the current record to make it writable to couchdb\n record.timeStamp = moment.utc(record.timeStamp).toString() as unknown as Moment;\n\n try {\n const cardHistory = await this.update<CardHistory<T>>(\n cardHistoryID,\n function (h: CardHistory<T>) {\n h.records.push(record);\n h.bestInterval = h.bestInterval || 0;\n h.lapses = h.lapses || 0;\n h.streak = h.streak || 0;\n return h;\n }\n );\n\n // Convert timestamps to moment objects\n cardHistory.records = cardHistory.records.map<T>((record) => {\n const ret: T = {\n ...(record as object),\n } as T;\n ret.timeStamp = moment.utc(record.timeStamp);\n return ret;\n });\n return cardHistory;\n } catch (e) {\n const reason = e as PouchError;\n if (reason.status === 404) {\n try {\n const initCardHistory: CardHistory<T> = {\n _id: cardHistoryID,\n cardID: record.cardID,\n courseID: record.courseID,\n records: [record],\n lapses: 0,\n streak: 0,\n bestInterval: 0,\n };\n const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);\n return { ...initCardHistory, _rev: putResult.rev };\n } catch (creationError) {\n throw new Error(\n `Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`\n );\n }\n } else {\n throw new Error(`putCardRecord failed because of:\n name:${reason.name}\n error: ${reason.error}\n message: ${reason.message}`);\n }\n }\n }\n\n private async deduplicateReviews() {\n try {\n log('Starting deduplication of scheduled reviews...');\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n log(`Write DB name: ${this.writeDB.name || 'unknown'}`);\n /**\n * Maps the qualified-id of a scheduled review card to\n * the docId of the same scheduled review.\n *\n * EG: {\n * courseId-cardId: 'card_review_2021-06--17:12:165'\n * }\n */\n const reviewsMap: { [index: string]: string } = {};\n const duplicateDocIds: string[] = [];\n\n log(\n `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || 'unknown'}`\n );\n const scheduledReviews = await this.remoteDB.query<{\n id: string;\n value: string;\n }>('reviewCards/reviewCards');\n\n log(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);\n\n // First pass: identify duplicates\n scheduledReviews.rows.forEach((r) => {\n const qualifiedCardId = r.value; // courseId-cardId\n const docId = r.key; // card_review_2021-06--17:12:165\n\n if (reviewsMap[qualifiedCardId]) {\n // this card is scheduled more than once! mark the earlier one for deletion\n log(`Found duplicate scheduled review for card: ${qualifiedCardId}`);\n log(\n `Marking earlier review ${reviewsMap[qualifiedCardId]} for deletion, keeping ${docId}`\n );\n duplicateDocIds.push(reviewsMap[qualifiedCardId]);\n // replace with the later-dated scheduled review\n reviewsMap[qualifiedCardId] = docId;\n } else {\n // note that this card is scheduled for review\n reviewsMap[qualifiedCardId] = docId;\n }\n });\n\n // Second pass: remove duplicates\n if (duplicateDocIds.length > 0) {\n log(`Removing ${duplicateDocIds.length} duplicate reviews...`);\n const deletePromises = duplicateDocIds.map(async (docId) => {\n try {\n const doc = await this.remoteDB.get(docId);\n await this.writeDB.remove(doc);\n log(`Successfully removed duplicate review: ${docId}`);\n } catch (error) {\n log(`Failed to remove duplicate review ${docId}: ${error}`);\n }\n });\n\n await Promise.all(deletePromises);\n log(`Deduplication complete. Processed ${duplicateDocIds.length} duplicates`);\n } else {\n log('No duplicate reviews found');\n }\n } catch (error) {\n log(`Error during review deduplication: ${error}`);\n if (error && typeof error === 'object' && 'status' in error && error.status === 404) {\n log(\n `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || 'unknown'}`\n );\n log(\n `This might indicate the user database doesn't exist or the reviewCards view isn't available`\n );\n }\n // Log full error details for debugging\n if (error && typeof error === 'object') {\n log(`Full error details: ${JSON.stringify(error)}`);\n }\n }\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * encountered in the past.\n *\n * @param course_id optional specification of individual course\n */\n async getSeenCards(course_id?: string) {\n let prefix = DocTypePrefixes[DocType.CARDRECORD];\n if (course_id) {\n prefix += course_id;\n }\n const docs = await filterAllDocsByPrefix(this.localDB, prefix, {\n include_docs: false,\n });\n // const docs = await this.localDB.allDocs({});\n const ret: PouchDB.Core.DocumentId[] = [];\n docs.rows.forEach((row) => {\n if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {\n ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));\n }\n });\n return ret;\n }\n\n /**\n *\n * @returns A promise of the cards that the user has seen in the past.\n */\n async getHistory() {\n const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(\n this.remoteDB,\n DocTypePrefixes[DocType.CARDRECORD],\n {\n include_docs: true,\n attachments: false,\n }\n );\n return cards.rows.map((r) => r.doc);\n }\n\n async updateCourseSettings(course_id: string, settings: UserCourseSetting[]) {\n void this.getCourseRegistrationsDoc().then((doc) => {\n const crs = doc.courses.find((c) => c.courseID === course_id);\n if (crs) {\n if (crs.settings === null || crs.settings === undefined) {\n crs.settings = {};\n }\n settings.forEach((setting) => {\n crs!.settings![setting.key] = setting.value;\n });\n }\n\n return this.localDB.put(doc);\n });\n }\n async getCourseSettings(course_id: string) {\n const regDoc = await this.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === course_id);\n\n if (crsDoc) {\n return crsDoc.settings;\n } else {\n throw new Error(`getCourseSettings Failed:\n User is not registered for course ${course_id}`);\n }\n }\n\n private async getOrCreateClassroomRegistrationsDoc(): Promise<\n ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n let ret;\n\n try {\n ret = await this.remoteDB.get<ClassroomRegistrationDoc>(\n BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS\n );\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await this.writeDB.put<ClassroomRegistrationDoc>({\n _id: BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,\n registrations: [],\n });\n ret = await this.getOrCreateClassroomRegistrationsDoc();\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (private method):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n logger.debug(`Returning classroom registrations doc: ${JSON.stringify(ret)}`);\n return ret;\n }\n\n /**\n * Retrieves the list of active classroom IDs where the user is registered as a student.\n *\n * @returns Promise<string[]> - Array of classroom IDs, or empty array if classroom\n * registration document is unavailable due to database errors\n *\n * @description This method gracefully handles database connectivity issues by returning\n * an empty array when the classroom registrations document cannot be accessed.\n * This ensures that users can still access other application features even\n * when classroom functionality is temporarily unavailable.\n */\n public async getActiveClasses(): Promise<string[]> {\n try {\n return (await this.getOrCreateClassroomRegistrationsDoc()).registrations\n .filter((c) => c.registeredAs === 'student')\n .map((c) => c.classID);\n } catch (error) {\n logger.warn(\n 'Failed to load classroom registrations, continuing without classroom data:',\n error\n );\n // Return empty array so user can still access other features\n return [];\n }\n }\n\n public async scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }) {\n return scheduleCardReviewLocal(this.writeDB, review);\n }\n public async removeScheduledCardReview(reviewId: string): Promise<void> {\n return removeScheduledCardReviewLocal(this.writeDB, reviewId);\n }\n\n public async registerForClassroom(\n _classId: string,\n _registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response> {\n return registerUserForClassroom(this._username, _classId, _registerAs);\n }\n\n public async dropFromClassroom(classId: string): Promise<PouchDB.Core.Response> {\n return dropUserFromClassroom(this._username, classId);\n }\n public async getUserClassrooms(): Promise<ClassroomRegistrationDoc> {\n return getUserClassrooms(this._username);\n }\n\n public async updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n return updateUserElo(this._username, courseId, elo);\n }\n\n public async getStrategyState<T>(courseId: string, strategyKey: string): Promise<T | null> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n try {\n const doc = await this.localDB.get<StrategyStateDoc<T>>(docId);\n return doc.data;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n return null;\n }\n throw e;\n }\n }\n\n public async putStrategyState<T>(courseId: string, strategyKey: string, data: T): Promise<void> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n let existingRev: string | undefined;\n\n try {\n const existing = await this.localDB.get<StrategyStateDoc<T>>(docId);\n existingRev = existing._rev;\n } catch (e) {\n const err = e as PouchError;\n if (err.status !== 404) {\n throw e;\n }\n }\n\n const doc: StrategyStateDoc<T> = {\n _id: docId,\n _rev: existingRev,\n docType: DocType.STRATEGY_STATE,\n courseId,\n strategyKey,\n data,\n updatedAt: new Date().toISOString(),\n };\n\n await this.localDB.put(doc);\n }\n\n public async putUserOutcome(record: UserOutcomeRecord): Promise<void> {\n try {\n await this.localDB.put(record);\n } catch (err: any) {\n if (err.status === 409) {\n // Overwrite if exists\n const existing = await this.localDB.get(record._id);\n (record as any)._rev = existing._rev;\n await this.localDB.put(record);\n } else {\n throw err;\n }\n }\n }\n\n public async deleteStrategyState(courseId: string, strategyKey: string): Promise<void> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n try {\n const doc = await this.localDB.get(docId);\n await this.localDB.remove(doc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n return;\n }\n throw e;\n }\n }\n}\n\nexport function accomodateGuest(): {\n username: string;\n firstVisit: boolean;\n} {\n logger.log('[funnel] accomodateGuest() called');\n\n // Check if localStorage is available (browser environment)\n if (typeof localStorage === 'undefined') {\n logger.log(\n '[funnel] localStorage not available (Node.js environment), returning default guest'\n );\n return {\n username: GuestUsername + 'nodejs-test',\n firstVisit: true,\n };\n }\n\n const dbUUID = 'sk-guest-uuid';\n let firstVisit: boolean;\n\n const existingUUID = localStorage.getItem(dbUUID);\n logger.log('[funnel] Checking localStorage for key:', dbUUID);\n logger.log('[funnel] Existing UUID value:', existingUUID);\n logger.log('[funnel] existingUUID !== null:', existingUUID !== null);\n\n if (existingUUID !== null) {\n firstVisit = false;\n logger.log(`[funnel] Returning guest ${existingUUID} \"logging in\".`);\n } else {\n firstVisit = true;\n logger.log('[funnel] No existing UUID, generating new one...');\n const uuid = generateUUID();\n logger.log('[funnel] Generated UUID:', uuid);\n logger.log('[funnel] UUID length:', uuid.length);\n\n try {\n localStorage.setItem(dbUUID, uuid);\n logger.log('[funnel] Successfully stored UUID in localStorage');\n const verification = localStorage.getItem(dbUUID);\n logger.log('[funnel] Verification read from localStorage:', verification);\n } catch (e) {\n logger.error('[funnel] ERROR storing UUID:', e);\n }\n\n logger.log(`[funnel] Accommodating a new guest with account: ${uuid}`);\n }\n\n const finalUUID = localStorage.getItem(dbUUID);\n const finalUsername = GuestUsername + finalUUID;\n logger.log('[funnel] Final UUID from localStorage:', finalUUID);\n logger.log('[funnel] GuestUsername constant:', GuestUsername);\n logger.log('[funnel] Final username to return:', finalUsername);\n\n return {\n username: finalUsername,\n firstVisit: firstVisit,\n };\n\n // Use cryptographically secure UUID generation\n function generateUUID() {\n logger.log('[funnel] Inside generateUUID()');\n\n // Use crypto.randomUUID() if available (Node 14.17+ / modern browsers)\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n const uuid = crypto.randomUUID();\n logger.log('[funnel] Generated UUID using crypto.randomUUID():', uuid);\n return uuid;\n }\n\n // Fallback for older environments: use crypto.getRandomValues()\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n // Set version (4) and variant bits according to RFC 4122\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n\n const uuid = [\n Array.from(bytes.slice(0, 4))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(4, 6))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(6, 8))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(8, 10))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(10, 16))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n ].join('-');\n\n logger.log('[funnel] Generated UUID using crypto.getRandomValues():', uuid);\n return uuid;\n }\n\n // Last resort fallback (should never happen in modern environments)\n logger.warn('[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)');\n let d = new Date().getTime();\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n d += performance.now();\n }\n const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n logger.log('[funnel] Generated UUID (fallback):', uuid);\n return uuid;\n }\n}\n\nconst userCoursesDoc = 'CourseRegistrations';\nconst userClassroomsDoc = 'ClassroomRegistrations';\n\nasync function getOrCreateClassroomRegistrationsDoc(\n user: string\n): Promise<ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<ClassroomRegistrationDoc>(userClassroomsDoc);\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<ClassroomRegistrationDoc>({\n _id: userClassroomsDoc,\n registrations: [],\n });\n ret = await getOrCreateClassroomRegistrationsDoc(user);\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (standalone function):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n return ret;\n}\n\nasync function getOrCreateCourseRegistrationsDoc(\n user: string\n): Promise<CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<CourseRegistrationDoc>(userCoursesDoc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<CourseRegistrationDoc>({\n _id: userCoursesDoc,\n courses: [],\n studyWeight: {},\n });\n ret = await getOrCreateCourseRegistrationsDoc(user);\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n}\n\nexport async function updateUserElo(user: string, course_id: string, elo: CourseElo) {\n const regDoc = await getOrCreateCourseRegistrationsDoc(user);\n const course = regDoc.courses.find((c) => c.courseID === course_id)!;\n course.elo = elo;\n return getLocalUserDB(user).put(regDoc);\n}\n\nexport async function registerUserForClassroom(\n user: string,\n classID: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n) {\n log(`Registering user: ${user} in course: ${classID}`);\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n const regItem = {\n classID: classID,\n registeredAs: registerAs,\n };\n\n if (\n doc.registrations.filter((reg) => {\n return reg.classID === regItem.classID && reg.registeredAs === regItem.registeredAs;\n }).length === 0\n ) {\n doc.registrations.push(regItem);\n } else {\n log(`User ${user} is already registered for class ${classID}`);\n }\n\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function dropUserFromClassroom(user: string, classID: string) {\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n let index: number = -1;\n\n for (let i = 0; i < doc.registrations.length; i++) {\n if (doc.registrations[i].classID === classID) {\n index = i;\n }\n }\n\n if (index !== -1) {\n doc.registrations.splice(index, 1);\n }\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function getUserClassrooms(user: string) {\n return getOrCreateClassroomRegistrationsDoc(user);\n}\n","// packages/db/src/impl/common/index.ts\n\nexport type { SyncStrategy } from './SyncStrategy';\nexport { BaseSyncStrategy } from './SyncStrategy';\nexport type {\n AccountCreationResult,\n AuthenticationResult,\n UserSession,\n SyncConfig,\n SyncStatus,\n} from './types';\nexport { BaseUser, accomodateGuest } from './BaseUserDB';\nexport {\n REVIEW_TIME_FORMAT,\n hexEncode,\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n updateGuestAccountExpirationDate,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\n","// db/src/factory.ts\n\nimport { DataLayerProvider } from './core/interfaces';\nimport { BaseUser } from './impl/common';\nimport { logger } from './util/logger';\nimport { StaticCourseManifest } from './util/packer/types';\nimport { initializeNavigatorRegistry } from './core/navigators';\n\nconst NOT_SET = 'NOT_SET' as const;\n\ninterface DBEnv {\n COUCHDB_SERVER_URL: string; // URL of CouchDB server\n COUCHDB_SERVER_PROTOCOL: string; // Protocol of CouchDB server (http or https)\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n LOCAL_STORAGE_PREFIX: string; // Prefix for IndexedDB storage names\n}\n\nexport const ENV: DBEnv = {\n COUCHDB_SERVER_PROTOCOL: NOT_SET,\n COUCHDB_SERVER_URL: NOT_SET,\n LOCAL_STORAGE_PREFIX: '',\n};\n\nexport { NOT_SET };\n\n// Configuration type for data layer initialization\nexport interface DataLayerConfig {\n type: 'couch' | 'static';\n options: {\n staticContentPath?: string; // Path to static content JSON files\n localStoragePrefix?: string; // Prefix for IndexedDB storage names\n manifests?: Record<string, StaticCourseManifest>; // Course manifests for static mode\n COUCHDB_SERVER_URL?: string;\n COUCHDB_SERVER_PROTOCOL?: string;\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n\n COURSE_IDS?: string[];\n };\n}\n\n// Singleton instance\nlet dataLayerInstance: DataLayerProvider | null = null;\n\n/**\n * Initialize the data layer with the specified configuration\n */\nexport async function initializeDataLayer(config: DataLayerConfig): Promise<DataLayerProvider> {\n if (dataLayerInstance) {\n logger.warn('Data layer already initialized. Returning existing instance.');\n return dataLayerInstance;\n }\n\n // Initialize the navigator registry before creating the data layer.\n // This ensures all built-in navigators are available for pipeline assembly.\n await initializeNavigatorRegistry();\n\n if (config.options.localStoragePrefix) {\n ENV.LOCAL_STORAGE_PREFIX = config.options.localStoragePrefix;\n }\n\n if (config.type === 'couch') {\n if (!config.options.COUCHDB_SERVER_URL || !config.options.COUCHDB_SERVER_PROTOCOL) {\n throw new Error('Missing CouchDB server URL or protocol');\n }\n ENV.COUCHDB_SERVER_PROTOCOL = config.options.COUCHDB_SERVER_PROTOCOL;\n ENV.COUCHDB_SERVER_URL = config.options.COUCHDB_SERVER_URL;\n ENV.COUCHDB_USERNAME = config.options.COUCHDB_USERNAME;\n ENV.COUCHDB_PASSWORD = config.options.COUCHDB_PASSWORD;\n\n if (\n config.options.COUCHDB_PASSWORD &&\n config.options.COUCHDB_USERNAME &&\n typeof window !== 'undefined'\n ) {\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDBSyncStrategy } = await import('./impl/couch/CouchDBSyncStrategy');\n\n // Create a sync strategy instance and authenticate\n const syncStrategy = new CouchDBSyncStrategy();\n\n const user = await BaseUser.instance(syncStrategy, config.options.COUCHDB_USERNAME);\n const authResult = await user.login(\n config.options.COUCHDB_USERNAME,\n config.options.COUCHDB_PASSWORD\n );\n\n if (authResult.ok) {\n logger.info(`Successfully authenticated as ${config.options.COUCHDB_USERNAME}`);\n } else {\n logger.warn(`Authentication failed: ${authResult.error}`);\n }\n }\n\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDataLayerProvider } = await import('./impl/couch/PouchDataLayerProvider');\n dataLayerInstance = new CouchDataLayerProvider(config.options.COURSE_IDS);\n } else if (config.type === 'static') {\n const { StaticDataLayerProvider } = await import('./impl/static/StaticDataLayerProvider');\n dataLayerInstance = new StaticDataLayerProvider(config.options);\n } else {\n throw new Error(`Unknown data layer type: ${config.type}`);\n }\n\n await dataLayerInstance.initialize();\n return dataLayerInstance;\n}\n\n/**\n * Get the initialized data layer instance\n * @throws Error if not initialized\n */\nexport function getDataLayer(): DataLayerProvider {\n if (!dataLayerInstance) {\n throw new Error('Data layer not initialized. Call initializeDataLayer first.');\n }\n return dataLayerInstance;\n}\n\n/**\n * Reset the data layer (primarily for testing)\n */\nexport async function _resetDataLayer(): Promise<void> {\n if (dataLayerInstance) {\n await dataLayerInstance.teardown();\n }\n dataLayerInstance = null;\n}\n","import { StudyContentSource } from '@db/core/interfaces/contentSource';\nimport { WeightedCard } from '@db/core/navigators';\nimport { UserDBInterface } from '@db/core';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { getTag } from '../impl/couch/courseDB';\nimport { logger } from '@db/util/logger';\n\n/**\n * A StudyContentSource that filters cards based on tag inclusion/exclusion.\n *\n * This enables ephemeral, tag-scoped study sessions where users can focus\n * on specific topics within a course without permanent configuration.\n *\n * Filter logic:\n * - If `include` is non-empty: card must have at least one of the included tags\n * - If `exclude` is non-empty: card must not have any of the excluded tags\n * - Both filters are applied (include first, then exclude)\n */\nexport class TagFilteredContentSource implements StudyContentSource {\n private courseId: string;\n private filter: TagFilter;\n private user: UserDBInterface;\n\n // Cache resolved card IDs to avoid repeated lookups within a session\n private resolvedCardIds: Set<string> | null = null;\n\n constructor(courseId: string, filter: TagFilter, user: UserDBInterface) {\n this.courseId = courseId;\n this.filter = filter;\n this.user = user;\n\n logger.info(\n `[TagFilteredContentSource] Created for course \"${courseId}\" with filter:`,\n JSON.stringify(filter)\n );\n }\n\n /**\n * Resolves the TagFilter to a set of eligible card IDs.\n *\n * - Cards in `include` tags are OR'd together (card needs at least one)\n * - Cards in `exclude` tags are removed from the result\n */\n private async resolveFilteredCardIds(): Promise<Set<string>> {\n // Return cached result if available\n if (this.resolvedCardIds !== null) {\n return this.resolvedCardIds;\n }\n\n const includedCardIds = new Set<string>();\n\n // Build inclusion set (OR of all include tags)\n if (this.filter.include.length > 0) {\n for (const tagName of this.filter.include) {\n try {\n const tagDoc = await getTag(this.courseId, tagName);\n tagDoc.taggedCards.forEach((cardId) => includedCardIds.add(cardId));\n } catch (error) {\n logger.warn(\n `[TagFilteredContentSource] Could not resolve tag \"${tagName}\" for inclusion:`,\n error\n );\n }\n }\n }\n\n // If no include tags specified or none resolved, return empty set\n // (requiring explicit inclusion prevents \"study everything\" on empty filter)\n if (includedCardIds.size === 0 && this.filter.include.length > 0) {\n logger.warn(\n `[TagFilteredContentSource] No cards found for include tags: ${this.filter.include.join(', ')}`\n );\n this.resolvedCardIds = new Set();\n return this.resolvedCardIds;\n }\n\n // Build exclusion set\n const excludedCardIds = new Set<string>();\n if (this.filter.exclude.length > 0) {\n for (const tagName of this.filter.exclude) {\n try {\n const tagDoc = await getTag(this.courseId, tagName);\n tagDoc.taggedCards.forEach((cardId) => excludedCardIds.add(cardId));\n } catch (error) {\n logger.warn(\n `[TagFilteredContentSource] Could not resolve tag \"${tagName}\" for exclusion:`,\n error\n );\n }\n }\n }\n\n // Apply exclusion filter\n const finalCardIds = new Set<string>();\n for (const cardId of includedCardIds) {\n if (!excludedCardIds.has(cardId)) {\n finalCardIds.add(cardId);\n }\n }\n\n logger.info(\n `[TagFilteredContentSource] Resolved ${finalCardIds.size} cards ` +\n `(included: ${includedCardIds.size}, excluded: ${excludedCardIds.size})`\n );\n\n this.resolvedCardIds = finalCardIds;\n return finalCardIds;\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * Filters cards by tag inclusion/exclusion and assigns score=1.0 to all.\n * TagFilteredContentSource does not currently support pluggable navigation\n * strategies - it returns flat-scored candidates.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending (all scores = 1.0)\n */\n public async getWeightedCards(limit: number): Promise<WeightedCard[]> {\n if (!hasActiveFilter(this.filter)) {\n logger.warn('[TagFilteredContentSource] getWeightedCards called with no active filter');\n return [];\n }\n\n const eligibleCardIds = await this.resolveFilteredCardIds();\n\n // Get new cards: eligible cards that are not already active\n const activeCards = await this.user.getActiveCards();\n const activeCardIds = new Set(activeCards.map((c) => c.cardID));\n\n const newCardWeighted: WeightedCard[] = [];\n for (const cardId of eligibleCardIds) {\n if (!activeCardIds.has(cardId)) {\n newCardWeighted.push({\n cardId,\n courseId: this.courseId,\n score: 1.0,\n provenance: [\n {\n strategy: 'tagFilter',\n strategyName: 'Tag Filter',\n strategyId: 'TAG_FILTER',\n action: 'generated' as const,\n score: 1.0,\n reason: `Tag-filtered new card (tags: ${this.filter.include.join(', ')})`,\n },\n ],\n });\n }\n\n if (newCardWeighted.length >= limit) {\n break;\n }\n }\n\n logger.info(\n `[TagFilteredContentSource] Found ${newCardWeighted.length} new cards matching filter`\n );\n\n // Get pending reviews: reviews for cards in the eligible set\n const allReviews = await this.user.getPendingReviews(this.courseId);\n const filteredReviews = allReviews.filter((review) => eligibleCardIds.has(review.cardId));\n\n logger.info(\n `[TagFilteredContentSource] Found ${filteredReviews.length} pending reviews matching filter ` +\n `(of ${allReviews.length} total)`\n );\n\n const reviewWeighted: WeightedCard[] = filteredReviews.map((r) => ({\n cardId: r.cardId,\n courseId: r.courseId,\n score: 1.0,\n reviewID: r._id,\n provenance: [\n {\n strategy: 'tagFilter',\n strategyName: 'Tag Filter',\n strategyId: 'TAG_FILTER',\n action: 'generated' as const,\n score: 1.0,\n reason: `Tag-filtered review (tags: ${this.filter.include.join(', ')})`,\n },\n ],\n }));\n\n // Reviews first, then new cards; respect limit\n return [...reviewWeighted, ...newCardWeighted].slice(0, limit);\n }\n\n /**\n * Clears the cached resolved card IDs.\n * Call this if the underlying tag data may have changed during a session.\n */\n public clearCache(): void {\n this.resolvedCardIds = null;\n }\n\n /**\n * Returns the course ID this source is filtering.\n */\n public getCourseId(): string {\n return this.courseId;\n }\n\n /**\n * Returns the active tag filter.\n */\n public getFilter(): TagFilter {\n return this.filter;\n }\n}\n","import { getDataLayer } from '@db/factory';\nimport { UserDBInterface } from '..';\nimport { StudentClassroomDB } from '../../impl/couch/classroomDB';\nimport { WeightedCard } from '../navigators';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { TagFilteredContentSource } from '../../study/TagFilteredContentSource';\nimport { OrchestrationContext } from '../orchestration';\n\nexport type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;\n\nexport interface StudySessionFailedNewItem extends StudySessionItem {\n status: 'failed-new';\n}\nexport interface StudySessionFailedReviewItem extends StudySessionReviewItem {\n status: 'failed-review';\n}\n\nexport interface StudySessionNewItem extends StudySessionItem {\n status: 'new';\n}\n\nexport interface StudySessionReviewItem extends StudySessionItem {\n reviewID: string;\n status: 'review' | 'failed-review';\n}\nexport function isReview(item: StudySessionItem): item is StudySessionReviewItem {\n const ret = item.status === 'review' || item.status === 'failed-review' || 'reviewID' in item;\n\n // console.log(`itemIsReview: ${ret}\n // \\t${JSON.stringify(item)}`);\n\n return ret;\n}\n\nexport interface StudySessionItem {\n status: 'new' | 'review' | 'failed-new' | 'failed-review';\n contentSourceType: 'course' | 'classroom';\n contentSourceID: string;\n // qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`;\n cardID: string;\n courseID: string;\n elo?: number;\n // reviewID?: string;\n}\n\nexport interface ContentSourceID {\n type: 'course' | 'classroom';\n id: string;\n /**\n * Optional tag filter for scoped study sessions.\n * When present, creates a TagFilteredContentSource instead of a regular course source.\n */\n tagFilter?: TagFilter;\n}\n\n// #region docs_StudyContentSource\n/**\n * Interface for sources that provide study content to SessionController.\n *\n * Content sources return scored candidates via getWeightedCards(), which\n * SessionController uses to populate study queues.\n *\n * See: packages/db/docs/navigators-architecture.md\n */\nexport interface StudyContentSource {\n /**\n * Get cards with suitability scores for presentation.\n *\n * Returns unified scored candidates that can be sorted and selected by SessionController.\n * The card origin ('new' | 'review' | 'failed') is determined by provenance metadata.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n getWeightedCards(limit: number): Promise<WeightedCard[]>;\n\n /**\n * Get the orchestration context for this source.\n * Used for recording learning outcomes.\n */\n getOrchestrationContext?(): Promise<OrchestrationContext>;\n\n /**\n * Set ephemeral hints for the next pipeline run.\n * No-op for sources that don't support hints.\n */\n setEphemeralHints?(hints: Record<string, unknown>): void;\n}\n// #endregion docs_StudyContentSource\n\nexport async function getStudySource(\n source: ContentSourceID,\n user: UserDBInterface\n): Promise<StudyContentSource> {\n if (source.type === 'classroom') {\n return await StudentClassroomDB.factory(source.id, user);\n } else {\n // Check if this is a tag-filtered course source\n if (hasActiveFilter(source.tagFilter)) {\n return new TagFilteredContentSource(source.id, source.tagFilter!, user);\n }\n\n // Regular course source\n return getDataLayer().getCourseDB(source.id) as unknown as StudyContentSource;\n }\n}\n","import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';\nimport { StudyContentSource, StudySessionItem } from './contentSource';\nimport { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';\nimport { DataLayerResult } from '../types/db';\nimport { NavigationStrategyManager } from './navigationStrategyManager';\n\n/**\n * Course content and management\n */\nexport interface CoursesDBInterface {\n /**\n * Get course config\n */\n getCourseConfig(courseId: string): Promise<CourseConfig>;\n\n /**\n * Get a list of all courses\n */\n getCourseList(): Promise<CourseConfig[]>;\n\n disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;\n}\n\nexport interface CourseInfo {\n cardCount: number;\n registeredUsers: number;\n}\n\nexport interface CourseDBInterface extends NavigationStrategyManager, StudyContentSource {\n /**\n * Get course config\n */\n getCourseConfig(): Promise<CourseConfig>;\n\n getCourseID(): string;\n\n /**\n * Set course config\n */\n updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response>;\n\n getCourseInfo(): Promise<CourseInfo>;\n\n getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<T>;\n getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options?: PouchDB.Core.AllDocsOptions\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>>;\n\n /**\n * Get cards sorted by ELO rating\n */\n getCardsByELO(\n elo: number,\n limit?: number\n ): Promise<\n {\n courseID: string;\n cardID: string;\n elo?: number;\n }[]\n >;\n\n /**\n * Get ELO data for specific cards\n */\n getCardEloData(cardIds: string[]): Promise<CourseElo[]>;\n\n /**\n * Update card ELO rating\n */\n updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Get cards centered at a particular ELO rating\n */\n getCardsCenteredAtELO(\n options: { limit: number; elo: 'user' | 'random' | number },\n filter?: (card: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]>;\n\n /**\n * Get tags for a card\n */\n getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;\n\n /**\n * Get tags for multiple cards in a single batch query.\n * More efficient than calling getAppliedTags() for each card.\n *\n * This method reduces redundant database operations when multiple filters\n * need tag data for the same cards. The Pipeline uses this to pre-hydrate\n * tags on WeightedCard objects before filters run.\n *\n * @param cardIds - Array of card IDs to fetch tags for\n * @returns Map from cardId to array of tag names\n */\n getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>>;\n\n /**\n * Get all card IDs in the course.\n * Used by diagnostics to scan the full card space.\n */\n getAllCardIds(): Promise<string[]>;\n\n /**\n * Add a tag to a card\n */\n addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Remove a tag from a card\n */\n removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Create a new tag\n */\n createTag(tagName: string, author: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get a tag by name\n */\n getTag(tagName: string): Promise<Tag>;\n\n /**\n * Update a tag\n */\n updateTag(tag: Tag): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all tag stubs for a course\n */\n getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>>;\n\n /**\n * Add a note to the course\n */\n addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo?: CourseElo\n ): Promise<DataLayerResult>;\n\n removeCard(cardId: string): Promise<PouchDB.Core.Response>;\n\n getInexperiencedCards(): Promise<\n {\n courseId: string;\n cardId: string;\n count: number;\n elo: CourseElo;\n }[]\n >;\n\n /**\n * Search for cards by text content\n * @param query Text to search for\n * @returns Array of matching card data\n */\n searchCards(query: string): Promise<any[]>;\n\n /**\n * Find documents using PouchDB query syntax\n * @param request PouchDB find request\n * @returns Query response\n */\n find(request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>>;\n}\n","// db/src/core/interfaces.ts\n\nimport { UserDBInterface, UserDBReader } from './userDB';\nimport { CourseDBInterface, CoursesDBInterface } from './courseDB';\nimport { ClassroomDBInterface } from './classroomDB';\nimport { AdminDBInterface } from './adminDB';\n\n/**\n * Main factory interface for data access\n */\nexport interface DataLayerProvider {\n /**\n * Get the user database interface\n */\n getUserDB(): UserDBInterface;\n\n /**\n * Create a UserDBReader for a specific user (admin access required)\n * Uses session authentication to verify requesting user is admin\n * @param targetUsername - The username to create a reader for\n * @throws Error if requesting user is not 'admin'\n */\n createUserReaderForUser(targetUsername: string): Promise<UserDBReader>;\n\n /**\n * Get a course database interface\n */\n getCourseDB(courseId: string): CourseDBInterface;\n\n /**\n * Get the courses-lookup interface\n */\n getCoursesDB(): CoursesDBInterface;\n\n /**\n * Get a classroom database interface\n */\n getClassroomDB(classId: string, type: 'student' | 'teacher'): Promise<ClassroomDBInterface>;\n\n /**\n * Get the admin database interface\n */\n getAdminDB(): AdminDBInterface;\n\n /**\n * Initialize the data layer\n */\n initialize(): Promise<void>;\n\n /**\n * Teardown the data layer\n */\n teardown(): Promise<void>;\n\n /**\n * Check if this data layer is read-only\n */\n isReadOnly(): boolean;\n\n /**\n * Trigger local replication of a course database.\n *\n * When a course opts in via `CourseConfig.localSync.enabled`, this method\n * replicates the remote course DB to a local PouchDB instance. Subsequent\n * `getCourseDB()` calls for that course will return a CourseDB that reads\n * from the local replica (fast, no network) and writes to the remote\n * (ELO updates, admin ops).\n *\n * Safe to call multiple times — concurrent calls coalesce. Returns when\n * sync is complete (or immediately if already synced / disabled).\n *\n * Implementations that don't support local sync may no-op.\n *\n * @param courseId - The course to sync locally\n * @param forceEnabled - Skip CourseConfig check and sync regardless.\n * Use when the caller already knows local sync is desired.\n */\n ensureCourseSynced?(courseId: string, forceEnabled?: boolean): Promise<void>;\n}\n","import {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n} from '@db/core/types/user';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { CardHistory, CardRecord, QualifiedCardID } from '../types/types-legacy';\nimport { UserOutcomeRecord } from '../types/userOutcome';\nimport { UserConfig } from '../types/user';\nimport { DocumentUpdater } from '@db/study';\n\n/**\n * Read-only user data operations\n */\nexport interface UserDBReader {\n get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta>;\n getUsername(): string;\n isLoggedIn(): boolean;\n\n /**\n * Get user configuration\n */\n getConfig(): Promise<UserConfig>;\n\n /**\n * Get cards that the user has seen\n */\n getSeenCards(courseId?: string): Promise<string[]>;\n\n /**\n * Get cards that are actively scheduled for review\n */\n getActiveCards(): Promise<QualifiedCardID[]>;\n\n /**\n * Get user's course registrations\n */\n getCourseRegistrationsDoc(): Promise<CourseRegistrationDoc>;\n\n /**\n * Get the registration doc for a specific course.\n * @param courseId\n */\n getCourseRegDoc(courseId: string): Promise<CourseRegistration>;\n\n /**\n * Get user's active courses\n */\n getActiveCourses(): Promise<CourseRegistration[]>;\n\n /**\n * Get user's pending reviews\n */\n getPendingReviews(courseId?: string): Promise<ScheduledCard[]>;\n\n getActivityRecords(): Promise<ActivityRecord[]>;\n\n /**\n * Get strategy-specific state for a course.\n *\n * Strategies use this to persist preferences, learned patterns, or temporal\n * tracking data across sessions. Each strategy owns its own namespace.\n *\n * @deprecated Use `getCourseInterface(courseId).getStrategyState(strategyKey)` instead.\n * Direct use bypasses course-scoping safety — the courseId parameter is unguarded,\n * allowing accidental cross-course data access. The course-scoped interface binds\n * courseId once at construction.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n * @returns The strategy's data payload, or null if no state exists\n */\n getStrategyState<T>(courseId: string, strategyKey: string): Promise<T | null>;\n\n /**\n * Get user's classroom registrations\n */\n getUserClassrooms(): Promise<ClassroomRegistrationDoc>;\n\n /**\n * Get user's active classes\n */\n getActiveClasses(): Promise<string[]>;\n\n getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;\n}\n\n/**\n * User data mutation operations\n */\nexport interface UserDBWriter extends DocumentUpdater {\n /**\n * Update user configuration\n */\n setConfig(config: Partial<UserConfig>): Promise<void>;\n\n /**\n * Record a user's interaction with a card\n */\n putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;\n\n /**\n * Register user for a course\n */\n registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop a course registration\n */\n dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Schedule a card for review\n */\n scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: string;\n time: Moment;\n scheduledFor: 'course' | 'classroom';\n schedulingAgentId: string;\n }): Promise<void>;\n\n /**\n * Remove a scheduled card review\n */\n removeScheduledCardReview(reviewId: string): Promise<void>;\n\n /**\n * Register user for a classroom\n */\n registerForClassroom(\n classId: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop user from classroom\n */\n dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Update user's ELO rating for a course\n */\n updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Reset all user data (progress, registrations, etc.) while preserving authentication\n */\n resetUserData(): Promise<{ status: Status; error?: string }>;\n\n /**\n * Store strategy-specific state for a course.\n *\n * Strategies use this to persist preferences, learned patterns, or temporal\n * tracking data across sessions. Each strategy owns its own namespace.\n *\n * @deprecated Use `getCourseInterface(courseId).putStrategyState(strategyKey, data)` instead.\n * Direct use bypasses course-scoping safety — the courseId parameter is unguarded,\n * allowing accidental cross-course data writes. The course-scoped interface binds\n * courseId once at construction.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n * @param data - The strategy's data payload to store\n */\n putStrategyState<T>(courseId: string, strategyKey: string, data: T): Promise<void>;\n\n /**\n * Delete strategy-specific state for a course.\n *\n * @deprecated Use `getCourseInterface(courseId).deleteStrategyState(strategyKey)` instead.\n * Direct use bypasses course-scoping safety.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n */\n deleteStrategyState(courseId: string, strategyKey: string): Promise<void>;\n\n /**\n * Record a user learning outcome for evolutionary orchestration.\n */\n putUserOutcome(record: UserOutcomeRecord): Promise<void>;\n}\n\n/**\n * Authentication and account management operations\n */\nexport interface UserDBAuthenticator {\n /**\n * Create a new user account\n */\n createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }>;\n\n /**\n * Log in as a user\n */\n login(\n username: string,\n password: string\n ): Promise<{\n ok: boolean;\n name?: string;\n roles?: string[];\n }>;\n\n /**\n * Log out the current user\n */\n logout(): Promise<{\n ok: boolean;\n }>;\n}\n\n/**\n * Complete user database interface - combines all user operations\n * This maintains backward compatibility with existing code\n */\nexport interface UserDBInterface extends UserDBReader, UserDBWriter, UserDBAuthenticator {}\n\nexport interface UserCourseSettings {\n [setting: string]: string | number | boolean;\n}\n\nexport interface UserCourseSetting {\n key: string;\n value: string | number | boolean;\n}\n\n// [ ] reconsider here. Should maybe be generic type based on <T extends StudyContentSource> ?\nexport interface UsrCrsDataInterface {\n getScheduledReviewCount(): Promise<number>;\n getCourseSettings(): Promise<UserCourseSettings>;\n updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?\n // getRegistrationDoc(): Promise<CourseRegistration>;\n\n /**\n * Get strategy-specific state for this course.\n *\n * Course-scoped alternative to `UserDBInterface.getStrategyState()`.\n * The courseId is bound at construction via `getCourseInterface(courseId)`,\n * so callers cannot accidentally access another course's state.\n *\n * @param strategyKey - Unique key identifying the state document\n * @returns The state payload, or null if no state exists\n */\n getStrategyState<T>(strategyKey: string): Promise<T | null>;\n\n /**\n * Store strategy-specific state for this course.\n *\n * Course-scoped alternative to `UserDBInterface.putStrategyState()`.\n *\n * @param strategyKey - Unique key identifying the state document\n * @param data - The state payload to store\n */\n putStrategyState<T>(strategyKey: string, data: T): Promise<void>;\n\n /**\n * Delete strategy-specific state for this course.\n *\n * Course-scoped alternative to `UserDBInterface.deleteStrategyState()`.\n *\n * @param strategyKey - Unique key identifying the state document\n */\n deleteStrategyState(strategyKey: string): Promise<void>;\n}\n\nexport type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';\n\nexport interface ClassroomRegistration {\n classID: string;\n registeredAs: ClassroomRegistrationDesignation;\n}\n\nexport interface ClassroomRegistrationDoc {\n registrations: ClassroomRegistration[];\n}\n","export * from './adminDB';\nexport * from './classroomDB';\nexport * from './contentSource';\nexport * from './courseDB';\nexport * from './dataLayerProvider';\n\nexport * from './userDB';\n","import { CourseElo } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\n\nexport interface SessionTrackingData {\n peekSessionCount: number;\n studySessionCount: number;\n sessionCount: number; // total\n firstSessionDate: string;\n lastSessionDate: string;\n signupPrompted: boolean;\n promptDismissalCount: number;\n studyModeAcknowledged: boolean;\n}\n\nexport interface UserConfig {\n darkMode: boolean;\n likesConfetti: boolean;\n sessionTimeLimit: number; // Session time limit in minutes\n email?: string; // Optional email for verification flows (added for enhanced auth)\n\n // Session tracking for trial enforcement (per-course)\n // Key is courseId (e.g., 'letterspractice-basic')\n sessionTracking?: Record<string, SessionTrackingData>;\n}\n\nexport interface ActivityRecord {\n timeStamp: number | string;\n [key: string]: any;\n}\n\nexport interface CourseRegistration {\n status?: 'active' | 'dropped' | 'maintenance-mode' | 'preview';\n courseID: string;\n admin: boolean;\n moderator: boolean;\n user: boolean;\n settings?: {\n [setting: string]: string | number | boolean;\n };\n elo: number | CourseElo;\n}\n\ninterface StudyWeights {\n [courseID: string]: number;\n}\n\nexport interface CourseRegistrationDoc {\n courses: CourseRegistration[];\n studyWeight: StudyWeights;\n}\n\nexport interface ScheduledCard {\n _id: PouchDB.Core.DocumentId;\n\n /**\n * The docID of the card to be reviewed\n */\n cardId: PouchDB.Core.DocumentId;\n /**\n * The ID of the course\n */\n courseId: string;\n /**\n * The time at which the card becomes eligible for review.\n *\n * (Should probably be UTC adjusted so that performance is\n * not wonky across time zones)\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(reviewTime)\n */\n reviewTime: string | Moment;\n\n /**\n * The time at which this scheduled event was created.\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(scheduledAt)\n */\n scheduledAt: string | Moment;\n\n /**\n * Classifying whether this card is scheduled on behalf of a\n * user-registered course or by as assigned content from a\n * user-registered classroom\n */\n scheduledFor: 'course' | 'classroom';\n\n /**\n * The ID of the course or classroom that requested this card\n */\n schedulingAgentId: string;\n}\n","import { DocType, DocTypePrefixes } from './types-legacy';\n\n/**\n * Template literal type for strategy state document IDs.\n *\n * Format: `STRATEGY_STATE-{courseId}-{strategyKey}`\n */\nexport type StrategyStateId =\n `${(typeof DocTypePrefixes)[DocType.STRATEGY_STATE]}::${string}::${string}`;\n\n/**\n * Document storing strategy-specific state in the user database.\n *\n * Each strategy can persist its own state (user preferences, learned patterns,\n * temporal tracking, etc.) using this document type. The state is scoped to\n * a (user, course, strategy) tuple.\n *\n * ## Use Cases\n *\n * 1. **Explicit user preferences**: User configures tag filters, difficulty\n * preferences, or learning goals. UI writes to strategy state.\n *\n * 2. **Learned/temporal state**: Strategy tracks patterns over time, e.g.,\n * \"when did I last introduce confusable concepts together?\"\n *\n * 3. **Adaptive personalization**: Strategy infers user preferences from\n * behavior and stores them for future sessions.\n *\n * ## Storage Location\n *\n * These documents live in the **user database**, not the course database.\n * They sync with the user's data across devices.\n *\n * ## Document ID Format\n *\n * `STRATEGY_STATE::{courseId}::{strategyKey}`\n *\n * Example: `STRATEGY_STATE::piano-basics::UserTagPreferenceFilter`\n *\n * @template T - The shape of the strategy-specific data payload\n */\nexport interface StrategyStateDoc<T = unknown> {\n _id: StrategyStateId;\n _rev?: string;\n docType: DocType.STRATEGY_STATE;\n\n /**\n * The course this state applies to.\n */\n courseId: string;\n\n /**\n * Unique key identifying the strategy instance.\n * Typically the strategy class name (e.g., \"UserTagPreferenceFilter\",\n * \"InterferenceMitigatorNavigator\").\n *\n * If a course has multiple instances of the same strategy type with\n * different configurations, use a more specific key.\n */\n strategyKey: string;\n\n /**\n * Strategy-specific data payload.\n * Each strategy defines its own schema for this field.\n */\n data: T;\n\n /**\n * ISO timestamp of last update.\n * Use `moment.utc(updatedAt)` to parse into a Moment object.\n */\n updatedAt: string;\n}\n\n/**\n * Build the document ID for a strategy state document.\n *\n * @param courseId - The course ID\n * @param strategyKey - The strategy key (typically class name)\n * @returns The document ID in format `STRATEGY_STATE::{courseId}::{strategyKey}`\n */\nexport function buildStrategyStateId(courseId: string, strategyKey: string): StrategyStateId {\n return `STRATEGY_STATE::${courseId}::${strategyKey}`;\n}\n","import { DocType } from './types-legacy';\n\n/**\n * Record of a user's learning outcome over a specific period.\n *\n * Used by the evolutionary orchestration system to correlate strategy\n * deviations with learning success.\n * \n * Stored in the UserDB.\n */\nexport interface UserOutcomeRecord {\n /** \n * Unique ID: \"USER_OUTCOME::{courseId}::{userId}::{timestamp}\" \n * Timestamp corresponds to periodEnd.\n */\n _id: string;\n \n docType: DocType.USER_OUTCOME;\n\n courseId: string;\n userId: string;\n\n /** Start of the measurement period (ISO timestamp) */\n periodStart: string;\n /** End of the measurement period (ISO timestamp) */\n periodEnd: string;\n\n /**\n * The computed signal value (e.g., 0.85 for 85% accuracy).\n * This is the 'Y' in the regression analysis.\n * \n * Higher values indicate better learning outcomes.\n */\n outcomeValue: number;\n\n /**\n * Snapshot of active deviations during this period.\n * Maps strategyId -> deviation value used [-1.0, 1.0].\n * This provides the 'X' values for regression analysis.\n */\n deviations: Record<string, number>;\n\n metadata: {\n sessionsCount: number;\n cardsSeen: number;\n eloStart: number;\n eloEnd: number;\n /** The algorithm used to compute outcomeValue (e.g. \"accuracy_in_zone\") */\n signalType: string;\n };\n}","import { CourseElo, Status, ParsedCard, BulkImportCardData } from '@vue-skuilder/common';\nimport { CourseDBInterface } from '../../core/interfaces/courseDB';\nimport { ImportResult, BulkCardProcessorConfig } from './types';\nimport { logger } from '../../util/logger';\n\n/**\n * Processes multiple cards from bulk text input\n *\n * @param parsedCards - Array of parsed cards to import\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Array of import results\n */\nexport async function importParsedCards(\n parsedCards: ParsedCard[],\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult[]> {\n const results: ImportResult[] = [];\n\n for (const parsedCard of parsedCards) {\n try {\n // processCard takes a ParsedCard and returns an ImportResult\n const result = await processCard(parsedCard, courseDB, config);\n results.push(result);\n } catch (error) {\n logger.error('Error processing card:', error);\n // Reconstruct originalText from parsedCard for this specific catch block\n // This is a fallback if processCard itself throws an unhandled error.\n // Normally, processCard should return an ImportResult with status 'error'.\n let errorOriginalText = parsedCard.markdown;\n if (parsedCard.tags && parsedCard.tags.length > 0) {\n errorOriginalText += `\\ntags: ${parsedCard.tags.join(', ')}`;\n }\n if (parsedCard.elo !== undefined) {\n errorOriginalText += `\\nelo: ${parsedCard.elo}`;\n }\n results.push({\n originalText: errorOriginalText,\n status: 'error',\n message: `Error processing card: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Processes a single parsed card\n *\n * @param parsedCard - Parsed card data\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Import result for the card\n */\nasync function processCard(\n parsedCard: ParsedCard,\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult> {\n const { markdown, tags, elo } = parsedCard;\n\n // Build the original text representation including metadata\n let originalText = markdown;\n if (tags.length > 0) {\n originalText += `\\ntags: ${tags.join(', ')}`;\n }\n if (elo !== undefined) {\n originalText += `\\nelo: ${elo}`;\n }\n\n // Create card data object\n const cardData: BulkImportCardData = {\n Input: markdown,\n Uploads: [], // No uploads for bulk import\n };\n\n const tagsElo: CourseElo['tags'] = {};\n for (const tag of tags) {\n tagsElo[tag] = {\n score: elo || 0,\n count: 1,\n };\n }\n\n try {\n const result = await courseDB.addNote(\n config.courseCode,\n config.dataShape,\n cardData,\n config.userName,\n tags,\n undefined, // attachments\n elo\n ? {\n global: {\n score: elo,\n count: 1,\n },\n tags: tagsElo,\n misc: {},\n }\n : undefined\n );\n\n if (result.status === Status.ok) {\n return {\n originalText,\n status: 'success',\n message: 'Card added successfully.',\n cardId: result.id ? result.id : '(unknown)',\n };\n } else {\n return {\n originalText,\n status: 'error',\n message: result.message || 'Failed to add card to database. Unknown error.',\n };\n }\n } catch (error) {\n logger.error('Error adding note:', error);\n return {\n originalText,\n status: 'error',\n message: `Error adding card: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n}\n\n/**\n * Validates the configuration for bulk card processing\n *\n * @param config - Configuration to validate\n * @returns Object with validation result and error message if any\n */\nexport function validateProcessorConfig(config: Partial<BulkCardProcessorConfig>): {\n isValid: boolean;\n errorMessage?: string;\n} {\n if (!config.dataShape) {\n return {\n isValid: false,\n errorMessage: 'No data shape provided for card processing',\n };\n }\n\n if (!config.courseCode) {\n return {\n isValid: false,\n errorMessage: 'No course code provided for card processing',\n };\n }\n\n if (!config.userName) {\n return {\n isValid: false,\n errorMessage: 'No user name provided for card processing',\n };\n }\n\n return { isValid: true };\n}\n","import { DataShape } from '@vue-skuilder/common';\n\n/**\n * Interface representing the result of a bulk import operation for a single card\n */\nexport interface ImportResult {\n /** The original text input for the card */\n originalText: string;\n /** Status of the import operation */\n status: 'success' | 'error';\n /** Message describing the result or error */\n message: string;\n /** ID of the newly created card (only for success) */\n cardId?: string;\n}\n\n/**\n * Configuration for the bulk card processor\n */\nexport interface BulkCardProcessorConfig {\n /** The data shape to use for the cards */\n dataShape: DataShape;\n /** The course code used for adding notes */\n courseCode: string;\n /** The username of the current user */\n userName: string;\n}\n","export * from './cardProcessor.js';\nexport * from './types.js';","import { logger } from '../util/logger';\nimport { getDataLayer } from '../factory';\nimport { DocType, DocTypePrefixes } from './types/types-legacy';\nimport { filterAllDocsByPrefix } from '../impl/common/userDBHelpers';\nimport type { UserDBInterface } from './interfaces/userDB';\nimport type { ScheduledCard, CourseRegistration } from './types/user';\n\n// ============================================================================\n// USER DATABASE DEBUGGER\n// ============================================================================\n//\n// Console-accessible debug API for inspecting user database (PouchDB/CouchDB).\n//\n// Exposed as `window.skuilder.userdb` for interactive exploration.\n//\n// Usage:\n// window.skuilder.userdb.showUser()\n// window.skuilder.userdb.showScheduledReviews()\n// window.skuilder.userdb.showCourseRegistrations()\n// window.skuilder.userdb.showCardHistory('cardId')\n// window.skuilder.userdb.queryByType('SCHEDULED_CARD')\n// window.skuilder.userdb.dbInfo()\n// window.skuilder.userdb.export()\n//\n// ============================================================================\n\n/**\n * Get the user database instance safely\n */\nfunction getUserDB(): UserDBInterface | null {\n try {\n const provider = getDataLayer();\n return provider.getUserDB();\n } catch {\n logger.info('[UserDB Debug] Data layer not initialized yet.');\n return null;\n }\n}\n\n/**\n * Get raw PouchDB instance for advanced queries\n * This accesses the internal localDB property\n */\nfunction getRawDB(): PouchDB.Database | null {\n const userDB = getUserDB();\n if (!userDB) return null;\n\n // Access the internal localDB property\n // This is a bit of a hack but necessary for raw queries\n const rawDB = (userDB as any).localDB;\n if (!rawDB) {\n logger.info('[UserDB Debug] Unable to access raw database instance.');\n return null;\n }\n\n return rawDB;\n}\n\n/**\n * Format a timestamp for display\n */\nfunction formatTimestamp(isoString: string): string {\n const date = new Date(isoString);\n return date.toLocaleString();\n}\n\n/**\n * Console API object exposed on window.skuilder.userdb\n */\nexport const userDBDebugAPI = {\n /**\n * Show current user information\n */\n showUser(): void {\n const userDB = getUserDB();\n if (!userDB) return;\n\n // eslint-disable-next-line no-console\n console.group('👤 User Information');\n logger.info(`Username: ${userDB.getUsername()}`);\n logger.info(`Logged in: ${userDB.isLoggedIn() ? 'Yes ✅' : 'No (Guest) ❌'}`);\n\n userDB.getConfig()\n .then((config) => {\n logger.info('Configuration:');\n logger.info(JSON.stringify(config, null, 2));\n })\n .catch((err) => {\n logger.info(`Error loading config: ${err.message}`);\n })\n .finally(() => {\n // eslint-disable-next-line no-console\n console.groupEnd();\n });\n },\n\n /**\n * Show scheduled reviews\n */\n async showScheduledReviews(courseId?: string): Promise<void> {\n const userDB = getUserDB();\n if (!userDB) return;\n\n try {\n const reviews = await userDB.getPendingReviews(courseId);\n\n // eslint-disable-next-line no-console\n console.group(`📅 Scheduled Reviews${courseId ? ` (${courseId})` : ''}`);\n logger.info(`Total: ${reviews.length}`);\n\n if (reviews.length > 0) {\n // Group by course\n const byCourse = new Map<string, ScheduledCard[]>();\n for (const review of reviews) {\n if (!byCourse.has(review.courseId)) {\n byCourse.set(review.courseId, []);\n }\n byCourse.get(review.courseId)!.push(review);\n }\n\n for (const [course, courseReviews] of byCourse) {\n // eslint-disable-next-line no-console\n console.group(`Course: ${course} (${courseReviews.length} reviews)`);\n\n // Sort by review time\n const sorted = courseReviews.sort((a, b) => {\n const timeA = typeof a.reviewTime === 'string' ? a.reviewTime : a.reviewTime.toISOString();\n const timeB = typeof b.reviewTime === 'string' ? b.reviewTime : b.reviewTime.toISOString();\n return new Date(timeA).getTime() - new Date(timeB).getTime();\n });\n\n // Show first 10\n for (const review of sorted.slice(0, 10)) {\n const reviewTimeStr = typeof review.reviewTime === 'string'\n ? review.reviewTime\n : review.reviewTime.toISOString();\n logger.info(\n ` ${review.cardId.slice(0, 12)}... @ ${formatTimestamp(reviewTimeStr)} ` +\n `[${review.scheduledFor}/${review.schedulingAgentId}]`\n );\n }\n\n if (sorted.length > 10) {\n logger.info(` ... and ${sorted.length - 10} more`);\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n }\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error loading scheduled reviews: ${err.message}`);\n }\n },\n\n /**\n * Show course registrations\n */\n async showCourseRegistrations(): Promise<void> {\n const userDB = getUserDB();\n if (!userDB) return;\n\n try {\n const registrations = await userDB.getActiveCourses();\n\n // eslint-disable-next-line no-console\n console.group('📚 Course Registrations');\n logger.info(`Total: ${registrations.length}`);\n\n if (registrations.length > 0) {\n // eslint-disable-next-line no-console\n console.table(\n registrations.map((reg: CourseRegistration) => ({\n courseId: reg.courseID,\n status: reg.status || 'active',\n elo: typeof reg.elo === 'number'\n ? reg.elo.toFixed(0)\n : reg.elo?.global?.score?.toFixed(0) || 'N/A',\n }))\n );\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error loading course registrations: ${err.message}`);\n }\n },\n\n /**\n * Show card history for a specific card\n */\n async showCardHistory(cardId: string): Promise<void> {\n const rawDB = getRawDB();\n if (!rawDB) return;\n\n try {\n // Card history docs use prefix 'cardH'\n const result = await filterAllDocsByPrefix(rawDB, DocTypePrefixes[DocType.CARDRECORD]);\n\n // Filter for this specific card\n const cardHistories = result.rows\n .filter((row: any) => row.doc && row.doc.cardID === cardId)\n .map((row: any) => row.doc);\n\n // eslint-disable-next-line no-console\n console.group(`🎴 Card History: ${cardId}`);\n logger.info(`Total interactions: ${cardHistories.length}`);\n\n if (cardHistories.length > 0) {\n // Sort by timestamp\n const sorted = cardHistories.sort((a: any, b: any) =>\n new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()\n );\n\n // Show recent history\n // eslint-disable-next-line no-console\n console.table(\n sorted.slice(0, 20).map((doc: any) => ({\n time: formatTimestamp(doc.timestamp),\n outcome: doc.outcome || 'N/A',\n duration: doc.duration ? `${(doc.duration / 1000).toFixed(1)}s` : 'N/A',\n courseId: doc.courseId,\n }))\n );\n\n if (sorted.length > 20) {\n logger.info(`... and ${sorted.length - 20} more interactions`);\n }\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error loading card history: ${err.message}`);\n }\n },\n\n /**\n * Query documents by type\n */\n async queryByType(docType: keyof typeof DocType, limit: number = 50): Promise<void> {\n const rawDB = getRawDB();\n if (!rawDB) return;\n\n try {\n const prefix = DocTypePrefixes[DocType[docType]];\n if (!prefix) {\n logger.info(`Unknown document type: ${docType}`);\n return;\n }\n\n const result = await filterAllDocsByPrefix(rawDB, prefix);\n\n // eslint-disable-next-line no-console\n console.group(`📄 Documents: ${docType}`);\n logger.info(`Total: ${result.rows.length}`);\n logger.info(`Prefix: ${prefix}`);\n\n if (result.rows.length > 0) {\n logger.info('Sample documents:');\n const samples = result.rows.slice(0, Math.min(limit, result.rows.length));\n\n for (const row of samples) {\n logger.info(`\\n${row.id}:`);\n logger.info(JSON.stringify(row.doc, null, 2));\n }\n\n if (result.rows.length > limit) {\n logger.info(`\\n... and ${result.rows.length - limit} more documents`);\n }\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error querying documents: ${err.message}`);\n }\n },\n\n /**\n * Show database info and statistics\n */\n async dbInfo(): Promise<void> {\n const rawDB = getRawDB();\n if (!rawDB) return;\n\n try {\n const info = await rawDB.info();\n\n // eslint-disable-next-line no-console\n console.group('ℹ️ Database Information');\n logger.info(`Database name: ${info.db_name}`);\n logger.info(`Total documents: ${info.doc_count}`);\n logger.info(`Update sequence: ${info.update_seq}`);\n // disk_size may not be available in all PouchDB implementations\n if ('disk_size' in info) {\n logger.info(`Disk size: ${((info as any).disk_size || 0) / 1024 / 1024} MB`);\n }\n\n // Count documents by type\n logger.info('\\nDocument counts by type:');\n const allDocs = await rawDB.allDocs({ include_docs: false });\n const typeCounts = new Map<string, number>();\n\n for (const row of allDocs.rows) {\n // Extract prefix from document ID\n let prefix = 'other';\n for (const [type, typePrefix] of Object.entries(DocTypePrefixes)) {\n if (row.id.startsWith(typePrefix)) {\n prefix = type;\n break;\n }\n }\n typeCounts.set(prefix, (typeCounts.get(prefix) || 0) + 1);\n }\n\n // eslint-disable-next-line no-console\n console.table(\n Array.from(typeCounts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([type, count]) => ({ type, count }))\n );\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error getting database info: ${err.message}`);\n }\n },\n\n /**\n * List all document types\n */\n listDocTypes(): void {\n // eslint-disable-next-line no-console\n console.group('📋 Available Document Types');\n logger.info('Use with queryByType(type):');\n\n for (const [type, prefix] of Object.entries(DocTypePrefixes)) {\n logger.info(` ${type.padEnd(30)} → prefix: \"${prefix}\"`);\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Export database contents (limited, for debugging)\n */\n async export(includeContent: boolean = false): Promise<string> {\n const rawDB = getRawDB();\n const userDB = getUserDB();\n if (!rawDB || !userDB) return '{}';\n\n try {\n const data: any = {\n username: userDB.getUsername(),\n loggedIn: userDB.isLoggedIn(),\n timestamp: new Date().toISOString(),\n };\n\n if (includeContent) {\n // Get all documents\n const allDocs = await rawDB.allDocs({ include_docs: true });\n data.documents = allDocs.rows.map((row: any) => ({\n id: row.id,\n doc: row.doc,\n }));\n data.totalDocs = allDocs.rows.length;\n } else {\n // Just get counts\n const allDocs = await rawDB.allDocs({ include_docs: false });\n data.totalDocs = allDocs.rows.length;\n\n const typeCounts = new Map<string, number>();\n for (const row of allDocs.rows) {\n let prefix = 'other';\n for (const [type, typePrefix] of Object.entries(DocTypePrefixes)) {\n if (row.id.startsWith(typePrefix)) {\n prefix = type;\n break;\n }\n }\n typeCounts.set(prefix, (typeCounts.get(prefix) || 0) + 1);\n }\n data.docCounts = Object.fromEntries(typeCounts);\n }\n\n const json = JSON.stringify(data, null, 2);\n logger.info('[UserDB Debug] Database info exported. Copy the returned string or use:');\n logger.info(' copy(window.skuilder.userdb.export())');\n if (!includeContent) {\n logger.info(' For full content export: window.skuilder.userdb.export(true)');\n }\n return json;\n } catch (err: any) {\n logger.info(`Error exporting database: ${err.message}`);\n return '{}';\n }\n },\n\n /**\n * Execute raw PouchDB query\n */\n async raw(queryFn: (db: PouchDB.Database) => Promise<any>): Promise<void> {\n const rawDB = getRawDB();\n if (!rawDB) return;\n\n try {\n const result = await queryFn(rawDB);\n logger.info('[UserDB Debug] Query result:');\n logger.info(result);\n } catch (err: any) {\n logger.info(`[UserDB Debug] Query error: ${err.message}`);\n }\n },\n\n /**\n * Show help\n */\n help(): void {\n logger.info(`\n🔧 UserDB Debug API\n\nCommands:\n .showUser() Show current user info and config\n .showScheduledReviews(courseId?) Show scheduled reviews (optionally filter by course)\n .showCourseRegistrations() Show all course registrations\n .showCardHistory(cardId) Show interaction history for a card\n .queryByType(docType, limit?) Query documents by type (e.g., 'SCHEDULED_CARD')\n .listDocTypes() List all available document types\n .dbInfo() Show database info and statistics\n .export(includeContent?) Export database info (true = include all docs)\n .raw(queryFn) Execute raw PouchDB query\n .help() Show this help message\n\nExamples:\n window.skuilder.userdb.showUser()\n window.skuilder.userdb.showScheduledReviews('course123')\n window.skuilder.userdb.queryByType('SCHEDULED_CARD', 10)\n window.skuilder.userdb.raw(db => db.allDocs({ limit: 5 }))\n`);\n },\n};\n\n// ============================================================================\n// WINDOW MOUNT\n// ============================================================================\n\n/**\n * Mount the debug API on window.skuilder.userdb\n */\nexport function mountUserDBDebugger(): void {\n if (typeof window === 'undefined') return;\n\n const win = window as any;\n win.skuilder = win.skuilder || {};\n win.skuilder.userdb = userDBDebugAPI;\n}\n\n// Auto-mount when module is loaded\nmountUserDBDebugger();\n","// Export all core interfaces and types\n\nexport * from './interfaces';\nexport * from './types/types-legacy';\nexport * from './types/user';\nexport * from './types/strategyState';\nexport * from './types/userOutcome';\nexport * from '../util/Loggable';\nexport * from './util';\nexport * from './navigators';\nexport * from './bulkImport';\nexport * from './orchestration';\n\n// Export debug APIs\nexport { userDBDebugAPI, mountUserDBDebugger } from './UserDBDebugger';\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAOM,eAEO;AATb;AAAA;AAAA;AAOA,IAAM,gBAAgB,OAAO,YAAY,eAAe,QAAQ,IAAI,aAAa;AAE1E,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA,MAIpB,OAAO,CAAC,YAAoB,SAAsB;AAChD,YAAI,eAAe;AAEjB,kBAAQ,MAAM,cAAc,OAAO,IAAI,GAAG,IAAI;AAAA,QAChD;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,CAAC,YAAoB,SAAsB;AAE/C,gBAAQ,KAAK,aAAa,OAAO,IAAI,GAAG,IAAI;AAAA,MAC9C;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,CAAC,YAAoB,SAAsB;AAE/C,gBAAQ,KAAK,aAAa,OAAO,IAAI,GAAG,IAAI;AAAA,MAC9C;AAAA;AAAA;AAAA;AAAA,MAKA,OAAO,CAAC,YAAoB,SAAsB;AAEhD,gBAAQ,MAAM,cAAc,OAAO,IAAI,GAAG,IAAI;AAAA,MAChD;AAAA;AAAA;AAAA;AAAA,MAKA,KAAK,CAAC,YAAoB,SAAsB;AAC9C,YAAI,eAAe;AAEjB,kBAAQ,IAAI,YAAY,OAAO,IAAI,GAAG,IAAI;AAAA,QAC5C;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;ACrDA,IAIa,eAEA,KAID,SAsFC;AAhGb;AAAA;AAAA;AAEA;AAEO,IAAM,gBAAwB;AAE9B,IAAM,MAAM,CAAC,YAA0B;AAC5C,aAAO,IAAI,OAAO;AAAA,IACpB;AAEO,IAAK,UAAL,kBAAKA,aAAL;AACL,MAAAA,SAAA,sBAAmB;AACnB,MAAAA,SAAA,UAAO;AACP,MAAAA,SAAA,eAAY;AACZ,MAAAA,SAAA,kBAAe;AACf,MAAAA,SAAA,UAAO;AACP,MAAAA,SAAA,cAAW;AACX,MAAAA,SAAA,gBAAa;AACb,MAAAA,SAAA,oBAAiB;AACjB,MAAAA,SAAA,SAAM;AACN,MAAAA,SAAA,yBAAsB;AACtB,MAAAA,SAAA,oBAAiB;AACjB,MAAAA,SAAA,kBAAe;AACf,MAAAA,SAAA,6BAA0B;AAbhB,aAAAA;AAAA,OAAA;AAsFL,IAAM,kBAAkB;AAAA,MAC7B,CAAC,iBAAY,GAAG;AAAA,MAChB,CAAC,yCAAwB,GAAG;AAAA,MAC5B,CAAC,eAAW,GAAG;AAAA,MACf,CAAC,6BAAkB,GAAG;AAAA,MACtB,CAAC,qCAAsB,GAAG;AAAA;AAAA,MAE1B,CAAC,2BAAiB,GAAG;AAAA,MACrB,CAAC,6BAAoB,GAAG;AAAA,MACxB,CAAC,iBAAY,GAAG;AAAA,MAChB,CAAC,yBAAgB,GAAG;AAAA,MACpB,CAAC,+CAA2B,GAAG;AAAA,MAC/B,CAAC,qCAAsB,GAAG;AAAA,MAC1B,CAAC,iCAAoB,GAAG;AAAA,MACxB,CAAC,uDAA+B,GAAG;AAAA,IACrC;AAAA;AAAA;;;AC7GO,SAAS,mBAAmB,GAA8D;AAC/F,SAAO,iBAAiB,EAAE,QAAQ,CAAC,CAAC;AACtC;AAEO,SAAS,iBAAiB,GAAoC;AACnE,SAAQ,EAAqB,eAAe;AAC9C;AAEO,SAAS,iBAAiB,UAAkB,QAAyC;AAC1F,SAAO,GAAG,6CAAkC,CAAC,IAAI,QAAQ,IAAI,MAAM;AACrE;AAEO,SAAS,mBAAmB,IAGjC;AACA,QAAM,QAAQ,GAAG,MAAM,GAAG;AAC1B,MAAI,QAAgB;AACpB,WAAS,MAAM,WAAW,IAAI,KAAK;AAAA;AACnC,WACE,MAAM,CAAC,MAAM,6CAAkC,IAAI,KAAK;AAAA,gCAC5B,6CAAkC,CAAC;AAEjE,MAAI,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM,6CAAkC,GAAG;AAC1E,WAAO;AAAA,MACL,UAAU,MAAM,CAAC;AAAA,MACjB,QAAQ,MAAM,CAAC;AAAA,IACjB;AAAA,EACF,OAAO;AACL,UAAM,IAAI,MAAM,4BAA4B,KAAK;AAAA,EACnD;AACF;AAOO,SAAS,aAAa,GAA0B;AACrD,SAAO,QAAQ,GAAG,UAAU,eAAe,GAAG,WAAW,SAAS;AACpE;AA1CA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,OAAO,aAAa;AACpB,OAAO,iBAAiB;AACxB,OAAO,iBAAiB;AAFxB,IAsBO;AAtBP;AAAA;AAAA;AAKA,YAAQ,OAAO,WAAW;AAC1B,YAAQ,OAAO,WAAW;AAK1B,QAAI,OAAO,QAAQ,UAAU,aAAa;AACxC,cAAQ,MAAM,QAAQ;AAAA,IACxB;AAGA,YAAQ,SAAS;AAAA;AAAA;AAAA;AAAA,IAIjB,CAAC;AAED,IAAO,wBAAQ;AAAA;AAAA;;;AClBf,YAAY,UAAU;AACtB,YAAY,QAAQ;AAQb,SAAS,sBAA8B;AAC5C,MAAI,IAAI,sBAAsB;AAC5B,WAAY,UAAQ,WAAQ,GAAG,YAAY,IAAI,oBAAoB;AAAA,EACrE,OAAO;AACL,WAAY,UAAQ,WAAQ,GAAG,UAAU;AAAA,EAC3C;AACF;AAwBO,SAAS,UAAU,QAAwB;AAChD,SAAY,UAAK,oBAAoB,GAAG,MAAM;AAChD;AA7CA;AAAA;AAAA;AAMA;AACA;AAAA;AAAA;;;ACLA,OAAO,YAAY;AA0BZ,SAAS,sBACd,IACA,QACA,MACA;AAGA,QAAM,UAAkD;AAAA,IACtD,UAAU;AAAA,IACV,QAAQ,SAAS;AAAA,IACjB,cAAc;AAAA,EAChB;AAEA,MAAI,MAAM;AACR,WAAO,OAAO,SAAS,IAAI;AAAA,EAC7B;AACA,SAAO,GAAG,QAAW,OAAO;AAC9B;AAEO,SAAS,mBAAmB,KAGjC;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,QAAQ,MAAM;AAAA,EAChB;AACF;AA2BO,SAAS,eAAe,UAAoC;AAejE,QAAM,SAAS,UAAU,QAAQ;AAGjC,MAAI,OAAO,WAAW,aAAa;AAEjC,WAAO,IAAI,sBAAM,UAAU,MAAM,GAAG,CAAC,CAAC;AAAA,EACxC,OAAO;AAEL,WAAO,IAAI,sBAAM,QAAQ,CAAC,CAAC;AAAA,EAC7B;AACF;AAKO,SAAS,wBACd,QACA,QAOA;AACA,QAAM,MAAM,OAAO,IAAI;AACvB,SAAO,KAAK,6BAA6B,OAAO,KAAK,KAAK,KAAK,GAAG,IAAI,EAAE,OAAO;AAC/E,OAAK,OAAO,IAAmB;AAAA,IAC7B,KAAK,qDAAsC,IAAI,OAAO,KAAK,OAAO,kBAAkB;AAAA,IACpF,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO,KAAK,YAAY;AAAA,IACpC,UAAU,OAAO;AAAA,IACjB,aAAa,IAAI,YAAY;AAAA,IAC7B,cAAc,OAAO;AAAA,IACrB,mBAAmB,OAAO;AAAA,EAC5B,CAAC;AACH;AAKA,eAAsB,+BACpB,QACA,aACA;AACA,QAAM,YAAY,MAAM,OAAO,IAAI,WAAW;AAC9C,SACG,OAAO,SAAS,EAChB,KAAK,CAAC,QAAQ;AACb,QAAI,IAAI,IAAI;AACV,MAAAC,KAAI,uBAAuB,WAAW,EAAE;AAAA,IAC1C;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,IAAAA,KAAI,gCAAgC,WAAW;AAAA,EAAM,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,EAC5E,CAAC;AACL;AAzJA,IAOa,oBAKPA;AAZN;AAAA;AAAA;AAGA;AACA;AAKA;AACA;AAHO,IAAM,qBAA6B;AAK1C,IAAMA,OAAM,CAAC,MAAW;AACtB,aAAO,KAAK,CAAC;AAAA,IACf;AAAA;AAAA;;;ACdA,IAAsB;AAAtB;AAAA;AAAA;AAAO,IAAe,WAAf,MAAwB;AAAA,MAEnB,OAAO,MAAuB;AAEtC,gBAAQ,IAAI,OAAO,KAAK,UAAU,IAAI,oBAAI,KAAK,CAAC,KAAK,GAAG,IAAI;AAAA,MAC9D;AAAA,MACU,SAAS,MAAuB;AAExC,gBAAQ,MAAM,SAAS,KAAK,UAAU,IAAI,oBAAI,KAAK,CAAC,KAAK,GAAG,IAAI;AAAA,MAClE;AAAA,IACF;AAAA;AAAA;;;ACVA,IAKqB;AALrB;AAAA;AAAA;AAAA;AACA;AAIA,IAAqB,cAArB,cAAyC,SAAS;AAAA,MAChD,aAAqB;AAAA,MACb,iBAEJ,CAAC;AAAA,MACG,oBAEJ,CAAC;AAAA,MAEG;AAAA;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MA6BD,OACL,IACA,QACA;AACA,eAAO,MAAM,4BAA4B,EAAE,EAAE;AAC7C,YAAI,KAAK,eAAe,EAAE,GAAG;AAC3B,eAAK,eAAe,EAAE,EAAE,KAAK,MAAM;AAAA,QACrC,OAAO;AACL,eAAK,eAAe,EAAE,IAAI,CAAC,MAAM;AAAA,QACnC;AACA,eAAO,KAAK,aAAgB,EAAE;AAAA,MAChC;AAAA,MAEA,YAAY,QAA0B,SAA4B;AAChE,cAAM;AAEN,aAAK,SAAS;AACd,aAAK,UAAU,WAAW;AAC1B,eAAO,MAAM,wBAAwB;AACrC,aAAK,KAAK,OAAO,KAAK,EAAE,KAAK,CAAC,MAAM;AAClC,iBAAO,MAAM,YAAY,KAAK,UAAU,CAAC,CAAC,EAAE;AAAA,QAC9C,CAAC;AAAA,MACH;AAAA,MAEA,MAAc,aACZ,IACiE;AACjE,eAAO,MAAM,4BAA4B,EAAE,EAAE;AAC7C,YAAI,KAAK,kBAAkB,EAAE,GAAG;AAE9B,iBAAO,KAAK,kBAAkB,EAAE,GAAG;AACjC,kBAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,KAAK,OAAO,IAAI,EAAE,CAAC;AAAA,UACtE;AACA,iBAAO,KAAK,aAAgB,EAAE;AAAA,QAChC,OAAO;AACL,cAAI,KAAK,eAAe,EAAE,KAAK,KAAK,eAAe,EAAE,EAAE,SAAS,GAAG;AACjE,iBAAK,kBAAkB,EAAE,IAAI;AAE7B,kBAAM,cAAc;AACpB,qBAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,kBAAI;AACF,sBAAM,MAAM,MAAM,KAAK,OAAO,IAAO,EAAE;AAGvC,oBAAI,aAAa,EAAE,GAAG,IAAI;AAI1B,sBAAM,iBAAiB,CAAC,GAAG,KAAK,eAAe,EAAE,CAAC;AAClD,2BAAW,UAAU,gBAAgB;AACnC,sBAAI,OAAO,WAAW,YAAY;AAChC,iCAAa,EAAE,GAAG,YAAY,GAAG,OAAO,UAAU,EAAE;AAAA,kBACtD,OAAO;AACL,iCAAa;AAAA,sBACX,GAAG;AAAA,sBACH,GAAG;AAAA,oBACL;AAAA,kBACF;AAAA,gBACF;AAEA,sBAAM,KAAK,QAAQ,IAAO,UAAU;AAGpC,qBAAK,eAAe,EAAE,EAAE,OAAO,GAAG,eAAe,MAAM;AAEvD,oBAAI,KAAK,eAAe,EAAE,EAAE,WAAW,GAAG;AACxC,uBAAK,kBAAkB,EAAE,IAAI;AAC7B,yBAAO,KAAK,kBAAkB,EAAE;AAAA,gBAClC,OAAO;AAEL,yBAAO,KAAK,aAAgB,EAAE;AAAA,gBAChC;AACA,uBAAO;AAAA,cACT,SAAS,GAAQ;AACf,oBAAI,EAAE,SAAS,cAAc,IAAI,cAAc,GAAG;AAChD,yBAAO,KAAK,8BAA8B,EAAE,YAAY,IAAI,CAAC,EAAE;AAC/D,wBAAM,IAAI,QAAQ,CAAC,QAAQ,WAAW,KAAK,KAAK,KAAK,OAAO,CAAC,CAAC;AAAA,gBAEhE,WAAW,EAAE,SAAS,eAAe,MAAM,GAAG;AAE5C,yBAAO,KAAK,qBAAqB,EAAE,wCAAwC;AAC3E,yBAAO,KAAK,kBAAkB,EAAE;AAChC,wBAAM;AAAA,gBACR,OAAO;AAEL,yBAAO,KAAK,kBAAkB,EAAE;AAChC,sBAAI,KAAK,eAAe,EAAE,GAAG;AAC3B,2BAAO,KAAK,eAAe,EAAE;AAAA,kBAC/B;AACA,yBAAO,MAAM,mCAAmC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE;AAC1E,wBAAM;AAAA,gBACR;AAAA,cACF;AAAA,YACF;AAEA,kBAAM,IAAI,MAAM,8BAA8B,EAAE,UAAU,WAAW,WAAW;AAAA,UAClF,OAAO;AACL,kBAAM,IAAI,MAAM,+BAA+B;AAAA,UACjD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;AC1IA,OAAOC,aAAwB;AAP/B,IAYa;AAZb;AAAA;AAAA;AAUA;AAEO,IAAM,aAAN,MAAgD;AAAA,MAC7C;AAAA,MACA;AAAA,MAER,YAAY,MAAuB,UAAkB;AACnD,aAAK,OAAO;AACZ,aAAK,YAAY;AAAA,MACnB;AAAA,MAEA,MAAa,kBAAkB,WAAmB;AAChD,cAAM,OAAOA,QAAO,IAAI,EAAE,IAAI,WAAW,MAAM;AAC/C,eAAO,KAAK,iBAAiB,IAAI;AAAA,MACnC;AAAA,MAEA,MAAa,oBAAoB;AAC/B,cAAM,MAAMA,QAAO,IAAI;AACvB,eAAO,KAAK,iBAAiB,GAAG;AAAA,MAClC;AAAA,MAEA,MAAa,0BAA2C;AACtD,gBAAQ,MAAM,KAAK,kBAAkB,GAAG;AAAA,MAC1C;AAAA,MAEA,MAAa,oBAAiD;AAC5D,cAAM,SAAS,MAAM,KAAK,KAAK,0BAA0B;AACzD,cAAM,SAAS,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,aAAa,KAAK,SAAS;AAEvE,YAAI,UAAU,OAAO,UAAU;AAC7B,iBAAO,OAAO;AAAA,QAChB,OAAO;AACL,iBAAO,KAAK,6CAA6C,KAAK,SAAS,EAAE;AACzE,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,MACO,qBAAqB,SAAoC;AAG9D,YAAI,0BAA0B,KAAK,MAAM;AACvC,eAAM,KAAK,KAAa,qBAAqB,KAAK,WAAW,OAAO;AAAA,QACtE;AAAA,MACF;AAAA,MAEA,MAAa,iBAAoB,aAAwC;AACvE,eAAO,KAAK,KAAK,iBAAoB,KAAK,WAAW,WAAW;AAAA,MAClE;AAAA,MAEA,MAAa,iBAAoB,aAAqB,MAAwB;AAC5E,eAAO,KAAK,KAAK,iBAAoB,KAAK,WAAW,aAAa,IAAI;AAAA,MACxE;AAAA,MAEA,MAAa,oBAAoB,aAAoC;AACnE,eAAO,KAAK,KAAK,oBAAoB,KAAK,WAAW,WAAW;AAAA,MAClE;AAAA,MAEA,MAAc,iBAAiB,YAAoB;AAEjD,cAAM,aAAa,MAAM,KAAK,KAAK,kBAAkB,KAAK,SAAS;AAEnE,eAAO;AAAA,UACL,YAAY,KAAK,KAAK,YAAY,CAAC,mCAAmC,KAAK,SAAS;AAAA,QACtF;AAEA,eAAO,WAAW,OAAO,CAAC,WAA0B;AAClD,gBAAM,aAAaA,QAAO,IAAI,OAAO,UAAU;AAC/C,iBAAO,WAAW,QAAQ,UAAU;AAAA,QACtC,CAAC;AAAA,MACH;AAAA,IACF;AAAA;AAAA;;;ACzEA,eAAsB,WAAc,GAAW,GAA2C;AACxF,MAAI,aAAa,CAAC,GAAG;AAEnB,WAAO,aAAa,CAAC;AAAA,EACvB;AAEA,eAAa,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC,IAAI,MAAM,SAAS,CAAC;AACnD,SAAO,WAAW,CAAC;AACrB;AAEA,eAAe,SAAS,GAA6B;AACnD,QAAM,IAAI,MAAM,0CAA0C,CAAC,GAAG;AAChE;AAlBA,IAEM;AAFN;AAAA;AAAA;AAEA,IAAM,eAEF,CAAC;AAAA;AAAA;;;ACAL,SAAS,kBAAmC;AAE5C,SAAoB,gBAAgB,mBAAmB;AAGvD,SAAS,qBAAqB;AAG9B,SAAS,MAAM,cAAc;AAY7B,eAAsB,UACpB,UACA,YACA,OACA,MACA,QACA,MACA,SACA,MAAiB,eAAe,GACA;AAChC,QAAM,KAAK,YAAY,QAAQ;AAC/B,QAAM,UAAU,cAAc,UAAU,YAAY,OAAO,MAAM,QAAQ,MAAM,OAAO;AACtF,QAAM,MAAM,GAAG,yDAAwC,CAAC,IAAI,OAAO,CAAC;AACpE,QAAM,SAAS,MAAM,GAAG,IAAqB,EAAE,GAAG,SAAS,IAAI,CAAC;AAEhE,QAAM,cAAc,WAAW,mBAAmB;AAAA,IAChD,QAAQ;AAAA,IACR,WAAW,MAAM;AAAA,EACnB,CAAC;AAED,MAAI,OAAO,IAAI;AACb,QAAI;AAEF,YAAM,YAAY,UAAU,aAAa,OAAO,IAAI,MAAM,KAAK,MAAM;AAAA,IACvE,SAAS,OAAO;AAEd,UAAI,eAAe;AACnB,UAAI,iBAAiB,OAAO;AAC1B,uBAAe,MAAM;AAAA,MACvB,WAAW,SAAS,OAAO,UAAU,YAAY,YAAY,OAAO;AAClE,uBAAe,MAAM;AAAA,MACvB,WAAW,SAAS,OAAO,UAAU,YAAY,aAAa,OAAO;AACnE,uBAAe,MAAM;AAAA,MACvB,OAAO;AACL,uBAAe,OAAO,KAAK;AAAA,MAC7B;AAEA,aAAO,MAAM,+CAA+C,OAAO,EAAE,KAAK,YAAY,EAAE;AAExF,MAAC,OAAe,qBAAqB;AACrC,MAAC,OAAe,oBAAoB;AAAA,IACtC;AAAA,EACF,OAAO;AACL,WAAO,MAAM,0CAA0C,KAAK,UAAU,MAAM,CAAC,EAAE;AAAA,EACjF;AAEA,SAAO;AACT;AAEA,eAAe,YACb,UACA,aACA,QACA,MACA,MAAiB,eAAe,GAChC,QACe;AACf,QAAM,MAAM,MAAM,6BAA6B,QAAQ;AACvD,QAAM,eAAe,WAAW,uBAAuB,WAAW;AAClE,MAAI,oBAA8B,CAAC;AAEnC,aAAW,MAAM,IAAI,YAAY;AAC/B,QAAI,GAAG,SAAS,aAAa;AAC3B,0BAAoB,GAAG;AAAA,IACzB;AAAA,EACF;AAEA,MAAI,kBAAkB,WAAW,GAAG;AAClC,UAAM,WAAW,+CAA+C,WAAW;AAC3E,WAAO,MAAM,QAAQ;AACrB,UAAM,IAAI,MAAM,QAAQ;AAAA,EAC1B;AAEA,aAAW,gBAAgB,mBAAmB;AAC5C,UAAM,WAAW,cAAc,UAAU,cAAc,QAAQ,MAAM,KAAK,MAAM;AAAA,EAClF;AACF;AAEA,eAAe,WACb,kBACA,UACA,cACA,QACA,MACA,MAAiB,eAAe,GAChC,QACe;AACf,QAAM,cAAc,WAAW,sBAAsB,gBAAgB;AACrE,QAAM,MAAM,MAAM,6BAA6B,QAAQ;AAEvD,aAAW,MAAM,IAAI,eAAe;AAClC,QAAI,GAAG,SAAS,kBAAkB;AAChC,iBAAW,QAAQ,GAAG,UAAU;AAC9B,cAAM;AAAA,UACJ;AAAA,UACA,aAAa;AAAA,UACb,CAAC,MAAM;AAAA,UACP,WAAW,cAAc;AAAA,YACvB,QAAQ,YAAY;AAAA,YACpB,cAAc,YAAY;AAAA,YAC1B;AAAA,UACF,CAAC;AAAA,UACD;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAaA,eAAe,QACb,UACA,QACA,qBACA,SACA,KACA,MACA,QACgC;AAChC,QAAM,KAAK,YAAY,QAAQ;AAC/B,QAAM,MAAM,GAAG,iCAA4B,CAAC,IAAI,OAAO,CAAC;AACxD,QAAM,OAAO,MAAM,GAAG,IAAc;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK,OAAO,YAAY,MAAM,KAAK,MAAM,KAAK,KAAK,OAAO,CAAC,CAAC;AAAA,IAC5D;AAAA,EACF,CAAC;AACD,aAAW,OAAO,MAAM;AACtB,WAAO,KAAK,eAAe,GAAG,YAAY,KAAK,EAAE,EAAE;AACnD,UAAM,aAAa,UAAU,KAAK,IAAI,KAAK,QAAQ,KAAK;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,eAAsB,6BAA6B,UAAyC;AAC1F,MAAI;AACF,UAAM,KAAK,YAAY,QAAQ;AAC/B,UAAM,MAAM,MAAM,GAAG,IAAkB,cAAc;AACrD,QAAI,WAAW;AACf,WAAO,KAAK,4BAA4B,KAAK,UAAU,GAAG,CAAC,EAAE;AAC7D,WAAO;AAAA,EACT,SAAS,GAAG;AACV,WAAO,MAAM,6BAA6B,QAAQ,KAAK,CAAC;AACxD,UAAM;AAAA,EACR;AACF;AAaA,eAAsB,aACpB,UACA,QACA,OACA,QACA,YAAqB,MACW;AAGhC,QAAM,gBAAgB,SAAS,KAAK;AACpC,QAAM,WAAW,YAAY,QAAQ;AACrC,QAAM,YAAY,IAAI,SAAS,UAAU,YAAY;AACnD,UAAM,oBAAoB;AAAA,MACxB,eAAe,MAAM;AAAA,MACrB,WAAW,MAAM;AAAA,MAAC;AAAA,MAClB,kBAAkB,MAAM;AAAA,MACxB,iBAAiB,MAAM;AAAA,MACvB,oBAAoB,YAAY;AAAA,IAClC;AACA,WAAO,SAAS,MAAM,iBAAiB;AAAA,EACzC,CAAC;AACD,MAAI;AACF,WAAO,KAAK,gBAAgB,KAAK,YAAY,WAAW,MAAM,MAAM,KAAK;AACzE,UAAM,MAAM,MAAM,SAAS,IAAS,aAAa;AACjD,QAAI,CAAC,IAAI,YAAY,SAAS,MAAM,GAAG;AACrC,UAAI,YAAY,KAAK,MAAM;AAE3B,UAAI,WAAW;AACb,YAAI;AACF,gBAAM,UAAU,MAAM,UAAU,eAAe,CAAC,MAAM,CAAC;AACvD,gBAAM,MAAM,QAAQ,CAAC;AACrB,cAAI,KAAK,KAAK,IAAI;AAAA,YAChB,OAAO;AAAA,YACP,OAAO,IAAI,OAAO;AAAA;AAAA,UACpB;AACA,gBAAM,cAAc,UAAU,QAAQ,GAAG;AAAA,QAC3C,SAAS,OAAO;AACd,iBAAO,MAAM,uCAAuC,QAAQ,KAAK;AAAA,QACnE;AAAA,MACF;AAEA,aAAO,SAAS,IAAS,GAAG;AAAA,IAC9B,MAAO,OAAM,IAAI,iBAAiB,QAAQ,MAAM,2BAA2B,KAAK,EAAE;AAAA,EACpF,SAAS,GAAG;AACV,QAAI,aAAa,kBAAkB;AACjC,YAAM;AAAA,IACR;AAEA,UAAM,UAAU,UAAU,OAAO,MAAM;AACvC,WAAO,aAAa,UAAU,QAAQ,OAAO,QAAQ,SAAS;AAAA,EAChE;AACF;AAEA,eAAe,cAAc,UAAkB,QAAgB,KAAgB;AAC7E,MAAI,KAAK;AAEP,UAAM,MAAM,YAAY,QAAQ;AAChC,UAAM,OAAO,MAAM,IAAI,IAAc,MAAM;AAC3C,WAAO,MAAM,aAAa,KAAK,UAAU,KAAK,GAAG,CAAC,SAAS,KAAK,UAAU,GAAG,CAAC,EAAE;AAChF,SAAK,MAAM;AACX,WAAO,IAAI,IAAI,IAAI;AAAA,EACrB;AACF;AASO,SAAS,SAAS,SAAyB;AAChD,QAAM,4BAAwB,QAAQ,IAAI;AAC1C,MAAI,QAAQ,QAAQ,SAAS,MAAM,GAAG;AACpC,WAAO;AAAA,EACT,OAAO;AACL,WAAO,YAAY;AAAA,EACrB;AACF;AAEO,SAAS,YAAY,UAAoC;AAC9D,QAAM,SAAS,YAAY,QAAQ;AACnC,SAAO,IAAI;AAAA,IACT,IAAI,0BAA0B,QAAQ,IAAI,qBAAqB;AAAA,IAC/D,oBAAoB;AAAA,EACtB;AACF;AA3RA,IAqQM;AArQN;AAAA;AAAA;AAAA;AACA;AACA;AAKA;AACA;AAEA;AACA;AA0PA,IAAM,mBAAN,cAA+B,MAAM;AAAA,MACnC,YAAY,SAAiB;AAC3B,cAAM,OAAO;AACb,aAAK,OAAO;AAAA,MACd;AAAA,IACF;AAAA;AAAA;;;AC1QA;AAAA;AAAA;AAAA;AACA;AACA;AAWA,WAAO,MAAM,2BAA2B;AAAA;AAAA;;;ACbxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBO,SAAS,yBAAyB,UAA0B;AACjE,oBAAkB;AACpB;AAmFA,SAAS,UAAU,MAAkD;AACnE,QAAM,aAAa,KAAK,WAAW,CAAC;AACpC,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,SAAS,WAAW,QAAQ,YAAY,KAAK;AACnD,MAAI,OAAO,SAAS,UAAU,EAAG,QAAO;AACxC,MAAI,OAAO,SAAS,QAAQ,EAAG,QAAO;AACtC,SAAO;AACT;AAKO,SAAS,WAAW,QAA8D;AACvF,QAAM,aAAgC;AAAA,IACpC,GAAG;AAAA,IACH,OAAO,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAClE,WAAW,oBAAI,KAAK;AAAA,EACtB;AAEA,aAAW,QAAQ,UAAU;AAC7B,MAAI,WAAW,SAAS,UAAU;AAChC,eAAW,IAAI;AAAA,EACjB;AACF;AAKO,SAAS,eACd,UACA,YACA,eACA,YACA,gBACA,SACA,UACA,eACgD;AAChD,QAAM,cAAc,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AAE9D,QAAM,QAAQ,SAAS,IAAI,CAAC,UAAU;AAAA,IACpC,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,IACf,QAAQ,UAAU,IAAI;AAAA,IACtB,YAAY,KAAK;AAAA,IACjB,YAAY,KAAK;AAAA,IACjB,MAAM,KAAK;AAAA,IACX,UAAU,YAAY,IAAI,KAAK,MAAM;AAAA,EACvC,EAAE;AAEF,QAAM,kBAAkB,cAAc,OAAO,CAAC,MAAM,UAAU,CAAC,MAAM,QAAQ,EAAE;AAC/E,QAAM,cAAc,cAAc,OAAO,CAAC,MAAM,UAAU,CAAC,MAAM,KAAK,EAAE;AAExE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,cAAc;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AASA,SAAS,iBAAiB,YAA4C;AACpE,SAAO,WACJ,IAAI,CAAC,MAAM;AACV,UAAM,eACJ,EAAE,WAAW,cACT,cACA,EAAE,WAAW,YACX,iBACA,EAAE,WAAW,cACX,iBACA;AACV,WAAO,KAAK,YAAY,IAAI,EAAE,YAAY,KAAK,EAAE,MAAM,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM;AAAA,EACjF,CAAC,EACA,KAAK,IAAI;AACd;AAKA,SAAS,gBAAgB,KAA8B;AAErD,UAAQ,MAAM,2BAAoB,IAAI,QAAQ,KAAK,IAAI,cAAc,SAAS,GAAG;AACjF,SAAO,KAAK,WAAW,IAAI,KAAK,EAAE;AAClC,SAAO,KAAK,SAAS,IAAI,UAAU,YAAY,CAAC,EAAE;AAClD,SAAO,KAAK,cAAc,IAAI,aAAa,WAAM,IAAI,cAAc,aAAa;AAEhF,MAAI,IAAI,cAAc,IAAI,WAAW,SAAS,GAAG;AAE/C,YAAQ,MAAM,sBAAsB;AACpC,eAAW,KAAK,IAAI,YAAY;AAC9B,aAAO;AAAA,QACL,KAAK,EAAE,IAAI,KAAK,EAAE,SAAS,WAAW,EAAE,QAAQ,SAAS,EAAE,WAAW,kBAAkB,EAAE,SAAS,QAAQ,CAAC,CAAC;AAAA,MAC/G;AAAA,IACF;AAEA,YAAQ,SAAS;AAAA,EACnB;AAEA,MAAI,IAAI,QAAQ,SAAS,GAAG;AAE1B,YAAQ,MAAM,gBAAgB;AAC9B,eAAW,KAAK,IAAI,SAAS;AAC3B,aAAO,KAAK,KAAK,EAAE,IAAI,WAAM,EAAE,OAAO,UAAK,EAAE,SAAS,KAAK,EAAE,MAAM,UAAK,EAAE,OAAO,EAAE;AAAA,IACrF;AAEA,YAAQ,SAAS;AAAA,EACnB;AAEA,SAAO;AAAA,IACL,WAAW,IAAI,UAAU,oBAAoB,IAAI,WAAW,SAAS,IAAI,eAAe;AAAA,EAC1F;AAEA,UAAQ,SAAS;AACnB;AAwSO,SAAS,wBAA8B;AAC5C,MAAI,OAAO,WAAW,YAAa;AAEnC,QAAM,MAAM;AACZ,MAAI,WAAW,IAAI,YAAY,CAAC;AAChC,MAAI,SAAS,WAAW;AAC1B;AAxhBA,IAgBI,iBAqFE,UACA,YAyIO;AA/Ob;AAAA;AAAA;AACA;AAQA;AAOA,IAAI,kBAAmC;AAqFvC,IAAM,WAAW;AACjB,IAAM,aAAkC,CAAC;AAyIlC,IAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA,MAI9B,IAAI,OAA4B;AAC9B,eAAO,CAAC,GAAG,UAAU;AAAA,MACvB;AAAA;AAAA;AAAA;AAAA,MAKA,QAAQ,YAA6B,GAAS;AAC5C,YAAI,WAAW,WAAW,GAAG;AAC3B,iBAAO,KAAK,wCAAwC;AACpD;AAAA,QACF;AAEA,YAAI;AAEJ,YAAI,OAAO,cAAc,UAAU;AACjC,gBAAM,WAAW,SAAS;AAC1B,cAAI,CAAC,KAAK;AACR,mBAAO;AAAA,cACL,0CAA0C,SAAS,qBAAqB,WAAW,MAAM;AAAA,YAC3F;AACA;AAAA,UACF;AAAA,QACF,OAAO;AACL,gBAAM,WAAW,KAAK,CAAC,MAAM,EAAE,MAAM,SAAS,SAAS,CAAC;AACxD,cAAI,CAAC,KAAK;AACR,mBAAO,KAAK,8CAA8C,SAAS,IAAI;AACvE;AAAA,UACF;AAAA,QACF;AAEA,wBAAgB,GAAG;AAAA,MACrB;AAAA;AAAA;AAAA;AAAA,MAKA,cAAoB;AAClB,aAAK,QAAQ,CAAC;AAAA,MAChB;AAAA;AAAA;AAAA;AAAA,MAKA,SAAS,QAAsB;AAC7B,mBAAW,OAAO,YAAY;AAC5B,gBAAM,OAAO,IAAI,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM;AACtD,cAAI,MAAM;AAER,oBAAQ,MAAM,mBAAY,MAAM,EAAE;AAClC,mBAAO,KAAK,WAAW,KAAK,QAAQ,EAAE;AACtC,mBAAO,KAAK,WAAW,KAAK,MAAM,EAAE;AACpC,mBAAO,KAAK,gBAAgB,KAAK,WAAW,QAAQ,CAAC,CAAC,EAAE;AACxD,mBAAO,KAAK,aAAa,KAAK,WAAW,eAAU,WAAM,EAAE;AAC3D,mBAAO,KAAK,aAAa;AACzB,mBAAO,KAAK,iBAAiB,KAAK,UAAU,CAAC;AAE7C,oBAAQ,SAAS;AACjB;AAAA,UACF;AAAA,QACF;AACA,eAAO,KAAK,0BAA0B,MAAM,6BAA6B;AAAA,MAC3E;AAAA;AAAA;AAAA;AAAA,MAKA,iBAAuB;AACrB,YAAI,WAAW,WAAW,GAAG;AAC3B,iBAAO,KAAK,wCAAwC;AACpD;AAAA,QACF;AAGA,gBAAQ,MAAM,qCAA8B;AAE5C,mBAAW,OAAO,YAAY;AAE5B,kBAAQ,MAAM,QAAQ,IAAI,QAAQ,MAAM,IAAI,UAAU,mBAAmB,CAAC,EAAE;AAE5E,gBAAM,aAAa,IAAI,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ;AAChE,gBAAM,kBAAkB,WAAW,OAAO,CAAC,MAAM,EAAE,QAAQ;AAE3D,cAAI,WAAW,WAAW,GAAG;AAC3B,mBAAO,KAAK,2DAAsD;AAAA,UACpE,WAAW,gBAAgB,WAAW,GAAG;AACvC,mBAAO,KAAK,gBAAM,WAAW,MAAM,uCAAuC;AAC1E,mBAAO,KAAK,mBAAmB;AAG/B,kBAAM,cAAc,KAAK;AAAA,cACvB,GAAG,IAAI,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;AAAA,cACpF;AAAA,YACF;AACA,kBAAM,iBAAiB,KAAK,IAAI,GAAG,WAAW,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,CAAC;AAEzE,gBAAI,iBAAiB,aAAa;AAChC,qBAAO;AAAA,gBACL,yCAAyC,YAAY,QAAQ,CAAC,CAAC,iBAAiB,eAAe,QAAQ,CAAC,CAAC;AAAA,cAC3G;AAAA,YACF;AAGA,kBAAM,YAAY,WAAW,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AAC1E,gBAAI,WAAW;AACb,qBAAO,KAAK,yBAAyB,UAAU,WAAW,QAAQ,CAAC,CAAC,EAAE;AACtE,qBAAO,KAAK,qBAAqB;AACjC,qBAAO,KAAK,iBAAiB,UAAU,UAAU,CAAC;AAAA,YACpD;AAAA,UACF,OAAO;AACL,mBAAO,KAAK,UAAK,gBAAgB,MAAM,IAAI,WAAW,MAAM,oBAAoB;AAChF,mBAAO,KAAK,sBAAsB;AAClC,kBAAM,cAAc,gBAAgB,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AACjF,mBAAO,KAAK,iBAAiB,YAAY,UAAU,CAAC;AAAA,UACtD;AAGA,kBAAQ,SAAS;AAAA,QACnB;AAGA,gBAAQ,SAAS;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA,MAKA,WAAiB;AACf,YAAI,WAAW,WAAW,GAAG;AAC3B,iBAAO,KAAK,wCAAwC;AACpD;AAAA,QACF;AAGA,gBAAQ;AAAA,UACN,WAAW,IAAI,CAAC,OAAO;AAAA,YACrB,IAAI,EAAE,MAAM,MAAM,EAAE;AAAA,YACpB,MAAM,EAAE,UAAU,mBAAmB;AAAA,YACrC,QAAQ,EAAE,cAAc,EAAE,SAAS,MAAM,GAAG,CAAC;AAAA,YAC7C,WAAW,EAAE;AAAA,YACb,UAAU,EAAE;AAAA,YACZ,KAAK,EAAE;AAAA,YACP,SAAS,EAAE;AAAA,UACb,EAAE;AAAA,QACJ;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,SAAiB;AACf,cAAM,OAAO,KAAK,UAAU,YAAY,MAAM,CAAC;AAC/C,eAAO,KAAK,yEAAyE;AACrF,eAAO,KAAK,2CAA2C;AACvD,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,QAAc;AACZ,mBAAW,SAAS;AACpB,eAAO,KAAK,uCAAuC;AAAA,MACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,eAAqB;AACnB,cAAM,QAAQ,4BAA4B;AAC1C,YAAI,MAAM,WAAW,GAAG;AACtB,iBAAO,KAAK,+CAA+C;AAC3D;AAAA,QACF;AAGA,gBAAQ,MAAM,8BAAuB;AAErC,gBAAQ;AAAA,UACN,MAAM,IAAI,CAAC,SAAS;AAClB,kBAAM,eAAe,2BAA2B,IAAI;AACpD,kBAAM,cAAc,eAAe,IAAkB;AACrD,kBAAM,gBAAgB,eAAe,gBAAgB;AACrD,kBAAM,SAAS,cAAc,aAAa,eAAe,aAAa;AACtE,mBAAO;AAAA,cACL;AAAA,cACA,MAAM;AAAA,cACN;AAAA,cACA,aAAa,YAAY,IAAI;AAAA,cAC7B,UAAU,SAAS,IAAI;AAAA,YACzB;AAAA,UACF,CAAC;AAAA,QACH;AAEA,gBAAQ,SAAS;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,iBAAuB;AACrB,aAAK,aAAa;AAElB,YAAI,WAAW,WAAW,GAAG;AAC3B,iBAAO,KAAK,yFAAoF;AAChG;AAAA,QACF;AAEA,cAAM,MAAM,WAAW,CAAC;AAExB,gBAAQ,MAAM,gDAAyC;AACvD,eAAO,KAAK,cAAc,IAAI,aAAa,EAAE;AAC7C,YAAI,IAAI,cAAc,IAAI,WAAW,SAAS,GAAG;AAC/C,qBAAW,KAAK,IAAI,YAAY;AAC9B,mBAAO,KAAK,eAAQ,EAAE,IAAI,KAAK,EAAE,SAAS,WAAW,EAAE,QAAQ,SAAS,EAAE,WAAW,WAAW;AAAA,UAClG;AAAA,QACF;AACA,YAAI,IAAI,QAAQ,SAAS,GAAG;AAC1B,iBAAO,KAAK,UAAU;AACtB,qBAAW,KAAK,IAAI,SAAS;AAC3B,mBAAO,KAAK,eAAQ,EAAE,IAAI,WAAM,EAAE,OAAO,UAAK,EAAE,SAAS,KAAK,EAAE,MAAM,UAAK,EAAE,OAAO,EAAE;AAAA,UACxF;AAAA,QACF,OAAO;AACL,iBAAO,KAAK,iBAAiB;AAAA,QAC/B;AAEA,gBAAQ,SAAS;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUA,MAAM,kBAAkB,WAAwD;AAC9E,YAAI,CAAC,iBAAiB;AACpB,iBAAO,KAAK,2DAA2D;AACvE,iBAAO;AAAA,QACT;AACA,eAAO,gBAAgB,kBAAkB,EAAE,UAAU,CAAC;AAAA,MACxD;AAAA;AAAA;AAAA;AAAA,MAKA,OAAa;AACX,eAAO,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAqBf;AAAA,MACC;AAAA,IACF;AAkBA,0BAAsB;AAAA;AAAA;;;AC3hBtB;AAAA;AAAA;AAAA;AAAA;AAAA,IA2BY,iBASN,0BACA,wBAWe;AAhDrB;AAAA;AAAA;AAAA;AAMA;AAqBO,IAAK,kBAAL,kBAAKC,qBAAL;AAEL,MAAAA,iBAAA,SAAM;AAEN,MAAAA,iBAAA,aAAU;AAEV,MAAAA,iBAAA,qBAAkB;AANR,aAAAA;AAAA,OAAA;AASZ,IAAM,2BAA2B;AACjC,IAAM,yBAAyB;AAW/B,IAAqB,qBAArB,MAAqB,4BAA2B,iBAA0C;AAAA;AAAA,MAExF,OAAe;AAAA,MAEP;AAAA,MACA;AAAA,MAER,YACE,YACA,kBAAmC,0BACnC;AACA,cAAM;AACN,aAAK,aAAa;AAClB,aAAK,kBAAkB;AAEvB,YAAI,WAAW,WAAW,GAAG;AAC3B,gBAAM,IAAI,MAAM,oDAAoD;AAAA,QACtE;AAEA,eAAO;AAAA,UACL,qCAAqC,WAAW,MAAM,sBAAsB,eAAe;AAAA,QAC7F;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,aAAa,eACX,MACA,QACA,YACA,kBAAmC,0BACN;AAC7B,cAAM,aAAa,MAAM,QAAQ;AAAA,UAC/B,WAAW,IAAI,CAAC,MAAM,iBAAiB,OAAO,MAAM,QAAQ,CAAC,CAAC;AAAA,QAChE;AAEA,eAAO,IAAI,oBAAmB,YAA0C,eAAe;AAAA,MACzF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAcA,MAAM,iBAAiB,OAAe,SAAqD;AACzF,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI;AAAA,YACR;AAAA,UAEF;AAAA,QACF;AAGA,cAAM,UAAU,MAAM,QAAQ;AAAA,UAC5B,KAAK,WAAW,IAAI,CAAC,MAAM,EAAE,iBAAiB,OAAO,OAAO,CAAC;AAAA,QAC/D;AAGA,cAAM,qBAA+B,CAAC;AACtC,gBAAQ,QAAQ,CAAC,OAAO,UAAU;AAChC,gBAAM,MAAM,KAAK,WAAW,KAAK;AACjC,gBAAM,UAAU,IAAI,QAAQ,aAAa,KAAK;AAC9C,gBAAM,WAAW,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,QAAQ,SAAS,UAAU,CAAC;AAClF,gBAAM,cAAc,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,QAAQ,SAAS,QAAQ,CAAC;AAEnF,cAAI,MAAM,SAAS,GAAG;AACpB,kBAAM,WAAW,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,CAAC;AACjE,kBAAM,QAAkB,CAAC;AACzB,gBAAI,SAAS,SAAS,EAAG,OAAM,KAAK,GAAG,SAAS,MAAM,MAAM;AAC5D,gBAAI,YAAY,SAAS,EAAG,OAAM,KAAK,GAAG,YAAY,MAAM,UAAU;AACtE,kBAAM,YAAY,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,GAAG,MAAM,MAAM;AACvE,+BAAmB,KAAK,GAAG,OAAO,KAAK,SAAS,UAAU,QAAQ,GAAG;AAAA,UACvE,OAAO;AACL,+BAAmB,KAAK,GAAG,OAAO,WAAW;AAAA,UAC/C;AAAA,QACF,CAAC;AACD,eAAO,KAAK,oCAAoC,mBAAmB,KAAK,KAAK,CAAC,EAAE;AAIhF,cAAM,WAAW,oBAAI,IAA8B;AAEnD,gBAAQ,QAAQ,CAAC,OAAO,UAAU;AAEhC,gBAAM,MAAM,KAAK,WAAW,KAAK;AAGjC,cAAI,SAAS,IAAI,WAAW,UAAU;AACtC,cAAI;AAEJ,cAAI,IAAI,aAAa,CAAC,IAAI,gBAAgB,QAAQ,eAAe;AAE/D,kBAAM,aAAc,IAAY;AAChC,gBAAI,YAAY;AACd,uBAAS,QAAQ,cAAc,mBAAmB,YAAY,IAAI,SAAS;AAC3E,0BAAY,QAAQ,cAAc,aAAa,UAAU;AAAA,YAC3D;AAAA,UACF;AAEA,qBAAW,QAAQ,OAAO;AAExB,gBAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,mBAAK,WAAW,CAAC,EAAE,kBAAkB;AACrC,mBAAK,WAAW,CAAC,EAAE,YAAY;AAAA,YACjC;AAEA,kBAAM,WAAW,SAAS,IAAI,KAAK,MAAM,KAAK,CAAC;AAC/C,qBAAS,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,qBAAS,IAAI,KAAK,QAAQ,QAAQ;AAAA,UACpC;AAAA,QACF,CAAC;AAGD,cAAM,SAAyB,CAAC;AAChC,mBAAW,CAAC,EAAE,KAAK,KAAK,UAAU;AAChC,gBAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AACrC,gBAAM,kBAAkB,KAAK,gBAAgB,KAAK;AAClD,gBAAM,aAAa,KAAK,IAAI,GAAK,eAAe;AAGhD,gBAAM,mBAAmB,MAAM,QAAQ,CAAC,MAAM,EAAE,UAAU;AAG1D,gBAAM,eAAe,MAAM,CAAC,EAAE;AAC9B,gBAAM,SACJ,aAAa,eAAe,YAAY,aAAa,eAAe,cAAc;AAGpF,gBAAM,SAAS,KAAK,uBAAuB,OAAO,UAAU;AAG5D,iBAAO,KAAK;AAAA,YACV,GAAG,MAAM,CAAC;AAAA,YACV,OAAO;AAAA,YACP,YAAY;AAAA,cACV,GAAG;AAAA,cACH;AAAA,gBACE,UAAU;AAAA,gBACV,cAAc;AAAA,gBACd,YAAY;AAAA,gBACZ;AAAA,gBACA,OAAO;AAAA,gBACP;AAAA,cACF;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAGA,eAAO,OAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK;AAAA,MAChE;AAAA;AAAA;AAAA;AAAA,MAKQ,uBACN,OACA,YACQ;AACR,cAAM,QAAQ,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AACrC,cAAM,QAAQ,MAAM;AACpB,cAAM,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC,EAAE,KAAK,IAAI;AAE7D,YAAI,UAAU,GAAG;AACf,gBAAM,YACJ,KAAK,IAAI,MAAM,CAAC,EAAE,SAAS,CAAG,IAAI,OAAQ,OAAO,MAAM,CAAC,EAAE,OAAO,QAAQ,CAAC,CAAC,MAAM;AACnF,iBAAO,2BAA2B,WAAW,QAAQ,CAAC,CAAC,GAAG,SAAS;AAAA,QACrE;AAEA,cAAM,aAAa,MAAM,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,YAAY,SAAS,EAAE,KAAK,IAAI;AAErF,gBAAQ,KAAK,iBAAiB;AAAA,UAC5B,KAAK;AACH,mBAAO,UAAU,KAAK,gBAAgB,UAAU,cAAc,MAAM,YAAO,WAAW,QAAQ,CAAC,CAAC;AAAA,UAElG,KAAK;AACH,mBAAO,mBAAmB,KAAK,gBAAgB,UAAU,cAAc,MAAM,YAAO,WAAW,QAAQ,CAAC,CAAC;AAAA,UAE3G,KAAK,wCAAiC;AAEpC,kBAAM,cAAc,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAC9D,kBAAM,cAAc,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,QAAQ,EAAE,QAAQ,CAAC;AAC7E,kBAAM,MAAM,cAAc,IAAI,cAAc,cAAc;AAE1D,kBAAM,QAAQ,IAAI,0BAA0B,QAAQ;AACpD,mBAAO,wBAAwB,KAAK,gBAAgB,UAAU,YAAY,IAAI,QAAQ,CAAC,CAAC,SAAM,MAAM,QAAQ,CAAC,CAAC,WAAM,WAAW,QAAQ,CAAC,CAAC;AAAA,UAC3I;AAAA,UAEA;AACE,mBAAO,mBAAmB,KAAK,gBAAgB,WAAW,QAAQ,CAAC,CAAC;AAAA,QACxE;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKQ,gBAAgB,OAAyD;AAC/E,cAAM,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,KAAK;AAE5C,gBAAQ,KAAK,iBAAiB;AAAA,UAC5B,KAAK;AACH,mBAAO,KAAK,IAAI,GAAG,MAAM;AAAA,UAE3B,KAAK,yBAAyB;AAC5B,kBAAM,cAAc,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAC9D,gBAAI,gBAAgB,EAAG,QAAO;AAC9B,kBAAM,cAAc,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,QAAQ,EAAE,QAAQ,CAAC;AAC7E,mBAAO,cAAc;AAAA,UACvB;AAAA,UAEA,KAAK,wCAAiC;AACpC,kBAAM,cAAc,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAC9D,kBAAM,cAAc,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,QAAQ,EAAE,QAAQ,CAAC;AAC7E,kBAAM,MAAM,cAAc,IAAI,cAAc,cAAc;AAE1D,kBAAM,iBAAiB,IAAI,0BAA0B,MAAM,SAAS;AACpE,mBAAO,MAAM;AAAA,UACf;AAAA,UAEA;AACE,mBAAO,OAAO,CAAC;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;ACzRA;AAAA;AAAA;AAAA;AAIA,SAAS,eAAAC,oBAAmB;AAJ5B,IAmCqB;AAnCrB;AAAA;AAAA;AAEA;AAKA;AA4BA,IAAqB,eAArB,cAA0C,iBAA0C;AAAA;AAAA,MAElF;AAAA,MAEA,YACE,MACA,QACA,cAKA;AACA,cAAM,MAAM,QAAQ,YAAmB;AACvC,aAAK,OAAO,cAAc,QAAQ;AAAA,MACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiBA,MAAM,iBAAiB,OAAe,SAAqD;AAEzF,YAAI;AACJ,YAAI,SAAS,YAAY,QAAW;AAClC,0BAAgB,QAAQ;AAAA,QAC1B,OAAO;AACL,gBAAM,YAAY,MAAM,KAAK,KAAK,gBAAgB,KAAK,OAAO,YAAY,CAAC;AAC3E,gBAAM,UAAUA,aAAY,UAAU,GAAG;AACzC,0BAAgB,QAAQ,OAAO;AAAA,QACjC;AAEA,cAAM,cAAc,MAAM,KAAK,KAAK,eAAe;AACnD,cAAM,YACJ,MAAM,KAAK,OAAO;AAAA,UAChB,EAAE,OAAO,KAAK,OAAO;AAAA,UACrB,CAAC,MAAuB,CAAC,YAAY,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM;AAAA,QAC1E,GACA,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,QAAQ,MAAe,EAAE;AAG/C,cAAM,UAAU,SAAS,IAAI,CAAC,MAAM,EAAE,MAAM;AAC5C,cAAM,cAAc,MAAM,KAAK,OAAO,eAAe,OAAO;AAa5D,cAAM,SAAyB,SAAS,IAAI,CAAC,GAAG,MAAM;AACpD,gBAAM,UAAU,YAAY,CAAC,GAAG,QAAQ,SAAS;AACjD,gBAAM,WAAW,KAAK,IAAI,UAAU,aAAa;AACjD,gBAAM,WAAW,KAAK,IAAI,GAAG,IAAI,WAAW,GAAG;AAC/C,gBAAM,cAAc,WAAW,IAAI,KAAK,OAAO,MAAM,IAAI,YAAY;AAErE,iBAAO;AAAA,YACL,QAAQ,EAAE;AAAA,YACV,UAAU,EAAE;AAAA,YACZ,OAAO;AAAA,YACP,YAAY;AAAA,cACV;AAAA,gBACE,UAAU;AAAA,gBACV,cAAc,KAAK,gBAAgB,KAAK;AAAA,gBACxC,YAAY,KAAK,cAAc;AAAA,gBAC/B,QAAQ;AAAA,gBACR,OAAO;AAAA,gBACP,QAAQ,gBAAgB,KAAK,MAAM,QAAQ,CAAC,WAAW,KAAK,MAAM,OAAO,CAAC,WAAW,KAAK,MAAM,aAAa,CAAC,UAAU,SAAS,QAAQ,CAAC,CAAC,SAAS,YAAY,QAAQ,CAAC,CAAC;AAAA,cAC5K;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AAGD,eAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAEvC,cAAM,SAAS,OAAO,MAAM,GAAG,KAAK;AAGpC,YAAI,OAAO,SAAS,GAAG;AACrB,gBAAM,YAAY,OAAO,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC,EAAE,KAAK,IAAI;AAC7E,iBAAO;AAAA,YACL,gBAAgB,KAAK,OAAO,YAAY,CAAC,KAAK,OAAO,MAAM,2BAA2B,SAAS;AAAA,UACjG;AAAA,QACF,OAAO;AACL,iBAAO,KAAK,gBAAgB,KAAK,OAAO,YAAY,CAAC,0BAA0B;AAAA,QACjF;AAEA,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;;;AC7IA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA,IA6BqB;AA7BrB;AAAA;AAAA;AAEA;AAIA;AAuBA,IAAqB,2BAArB,cAAsD,iBAA0C;AAAA,MAC9F;AAAA,MACQ;AAAA,MAER,YACE,MACA,QACA,cACA;AACA,cAAM,MAAM,QAAQ,YAAY;AAChC,aAAK,OAAO,aAAa,QAAQ;AAEjC,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,aAAa,cAAc;AACrD,eAAK,SAAS,EAAE,SAAS,OAAO,WAAW,CAAC,EAAE;AAAA,QAChD,QAAQ;AACN,eAAK,SAAS,EAAE,SAAS,CAAC,EAAE;AAAA,QAC9B;AAEA,eAAO;AAAA,UACL,iCAAiC,KAAK,OAAO,QAAQ,MAAM;AAAA,QAC7D;AAAA,MACF;AAAA,MAEA,MAAM,iBAAiB,OAAe,UAAsD;AAC1F,YAAI,KAAK,OAAO,QAAQ,WAAW,GAAG;AACpC,iBAAO,CAAC;AAAA,QACV;AAEA,cAAM,WAAW,KAAK,OAAO,YAAY;AAGzC,cAAM,cAAc,MAAM,KAAK,KAAK,eAAe;AACnD,cAAM,YAAY,IAAI,IAAI,YAAY,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;AAC5D,cAAM,cAAc,KAAK,OAAO,QAAQ,OAAO,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;AAEzE,YAAI,YAAY,WAAW,GAAG;AAC5B,iBAAO,MAAM,mEAAmE;AAChF,iBAAO,CAAC;AAAA,QACV;AAIA,cAAM,QAAwB,YAAY,MAAM,GAAG,KAAK,EAAE,IAAI,CAAC,YAAY;AAAA,UACzE;AAAA,UACA;AAAA,UACA,OAAO;AAAA,UACP,YAAY;AAAA,YACV;AAAA,cACE,UAAU;AAAA,cACV,cAAc,KAAK,gBAAgB,KAAK;AAAA,cACxC,YAAY,KAAK,cAAc;AAAA,cAC/B,QAAQ;AAAA,cACR,OAAO;AAAA,cACP,QAAQ,oBAAoB,YAAY,MAAM,gBAAgB,KAAK,OAAO,QAAQ,MAAM;AAAA,YAC1F;AAAA,UACF;AAAA,QACF,EAAE;AAEF,eAAO;AAAA,UACL,yBAAyB,MAAM,MAAM,WAAW,YAAY,MAAM,cAAc,UAAU,IAAI;AAAA,QAChG;AAEA,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA;;;AC9FA;AAAA;AAAA;AAAA;AAAA,OAAOC,aAAY;AAAnB,IAyCM,yBAMA,sBA4Be;AA3ErB;AAAA;AAAA;AAIA;AAIA;AAiCA,IAAM,0BAA0B;AAMhC,IAAM,uBAAuB;AA4B7B,IAAqB,eAArB,cAA0C,iBAA0C;AAAA;AAAA,MAElF;AAAA;AAAA,MAGQ;AAAA,MAER,YACE,MACA,QACA,cACA;AACA,cAAM,MAAM,QAAQ,YAA6C;AACjE,aAAK,OAAO,cAAc,QAAQ;AAGlC,cAAM,SAAS,KAAK,YAAY,cAAc,cAAc;AAC5D,aAAK,iBAAiB,OAAO,kBAAkB;AAAA,MACjD;AAAA;AAAA;AAAA;AAAA,MAKQ,YAAY,gBAAoC;AACtD,YAAI,CAAC,eAAgB,QAAO,CAAC;AAC7B,YAAI;AACF,iBAAO,KAAK,MAAM,cAAc;AAAA,QAClC,QAAQ;AACN,iBAAO,KAAK,uDAAuD;AACnE,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAkBA,MAAM,iBAAiB,OAAe,UAAsD;AAC1F,YAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,QAAQ;AAC9B,gBAAM,IAAI,MAAM,iDAAiD;AAAA,QACnE;AAEA,cAAM,WAAW,KAAK,OAAO,YAAY;AACzC,cAAM,UAAU,MAAM,KAAK,KAAK,kBAAkB,QAAQ;AAC1D,cAAM,MAAMA,QAAO,IAAI;AAGvB,cAAM,aAAa,QAAQ,OAAO,CAAC,MAAM,IAAI,QAAQA,QAAO,IAAI,EAAE,UAAU,CAAC,CAAC;AAG9E,cAAM,kBAAkB,KAAK,uBAAuB,WAAW,MAAM;AAGrE,YAAI,WAAW,SAAS,GAAG;AACzB,gBAAM,eACJ,kBAAkB,IACd,wBAAwB,gBAAgB,QAAQ,CAAC,CAAC,MAClD;AACN,iBAAO;AAAA,YACL,gBAAgB,QAAQ,KAAK,WAAW,MAAM,wBAAwB,QAAQ,MAAM,cAAc,YAAY;AAAA,UAChH;AAAA,QACF,WAAW,QAAQ,SAAS,GAAG;AAE7B,gBAAM,cAAc,CAAC,GAAG,OAAO,EAAE;AAAA,YAAK,CAAC,GAAG,MACxCA,QAAO,IAAI,EAAE,UAAU,EAAE,KAAKA,QAAO,IAAI,EAAE,UAAU,CAAC;AAAA,UACxD;AACA,gBAAM,UAAU,YAAY,CAAC;AAC7B,gBAAM,cAAcA,QAAO,IAAI,QAAQ,UAAU;AACjD,gBAAM,WAAWA,QAAO,SAAS,YAAY,KAAK,GAAG,CAAC;AACtD,gBAAM,cACJ,SAAS,QAAQ,IAAI,IACjB,GAAG,KAAK,MAAM,SAAS,UAAU,CAAC,CAAC,MACnC,SAAS,QAAQ,IAAI,KACnB,GAAG,KAAK,MAAM,SAAS,QAAQ,CAAC,CAAC,MACjC,GAAG,KAAK,MAAM,SAAS,OAAO,CAAC,CAAC;AACxC,iBAAO;AAAA,YACL,gBAAgB,QAAQ,wBAAwB,QAAQ,MAAM,uBAAuB,WAAW;AAAA,UAClG;AAAA,QACF,OAAO;AACL,iBAAO,KAAK,gBAAgB,QAAQ,wBAAwB;AAAA,QAC9D;AAEA,cAAM,SAAS,WAAW,IAAI,CAAC,WAAW;AACxC,gBAAM,EAAE,OAAO,OAAO,IAAI,KAAK,oBAAoB,QAAQ,KAAK,eAAe;AAE/E,iBAAO;AAAA,YACL,QAAQ,OAAO;AAAA,YACf,UAAU,OAAO;AAAA,YACjB;AAAA,YACA,UAAU,OAAO;AAAA,YACjB,YAAY;AAAA,cACV;AAAA,gBACE,UAAU;AAAA,gBACV,cAAc,KAAK,gBAAgB,KAAK;AAAA,gBACxC,YAAY,KAAK,cAAc;AAAA,gBAC/B,QAAQ;AAAA,gBACR;AAAA,gBACA;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AAGD,eAAO,OAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK;AAAA,MAChE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiBQ,uBAAuB,UAA0B;AACvD,YAAI,YAAY,KAAK,gBAAgB;AACnC,iBAAO;AAAA,QACT;AAGA,cAAM,SAAS,WAAW,KAAK;AAC/B,cAAM,WAAY,SAAS,KAAK,kBAAmB,uBAAuB;AAE1E,eAAO,KAAK,IAAI,sBAAsB,QAAQ;AAAA,MAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MA2BQ,oBACN,QACA,KACA,iBACmC;AACnC,cAAM,cAAcA,QAAO,IAAI,OAAO,WAAW;AACjD,cAAM,MAAMA,QAAO,IAAI,OAAO,UAAU;AAGxC,cAAM,gBAAgB,KAAK,IAAI,GAAG,IAAI,KAAK,aAAa,OAAO,CAAC;AAChE,cAAM,eAAe,IAAI,KAAK,KAAK,OAAO;AAG1C,cAAM,kBAAkB,eAAe;AAIvC,cAAM,gBAAgB,MAAM,MAAM,KAAK,IAAI,CAAC,gBAAgB,GAAG;AAI/D,cAAM,sBAAsB,KAAK,IAAI,GAAK,KAAK,IAAI,GAAG,eAAe,CAAC;AACtE,cAAM,UAAU,sBAAsB,MAAM,gBAAgB;AAI5D,cAAM,YAAY,MAAM,UAAU;AAClC,cAAM,QAAQ,KAAK,IAAI,GAAK,YAAY,eAAe;AAGvD,cAAM,cAAc;AAAA,UAClB,GAAG,KAAK,MAAM,YAAY,CAAC;AAAA,UAC3B,aAAa,KAAK,MAAM,aAAa,CAAC;AAAA,UACtC,aAAa,gBAAgB,QAAQ,CAAC,CAAC;AAAA,UACvC,YAAY,cAAc,QAAQ,CAAC,CAAC;AAAA,QACtC;AAEA,YAAI,kBAAkB,GAAG;AACvB,sBAAY,KAAK,aAAa,gBAAgB,QAAQ,CAAC,CAAC,EAAE;AAAA,QAC5D;AAEA,oBAAY,KAAK,QAAQ;AAEzB,cAAM,SAAS,YAAY,KAAK,IAAI;AAEpC,eAAO,EAAE,OAAO,OAAO;AAAA,MACzB;AAAA,IACF;AAAA;AAAA;;;ACrSA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;;;;;;;;;;;;;ACAA,IAsBa;AAtBb;AAAA;AAAA;AAsBO,IAAM,2BAA4C;AAAA,MACvD,QAAQ;AAAA,MACR,YAAY;AAAA;AAAA,MACZ,YAAY;AAAA,IACd;AAAA;AAAA;;;AC1BA;AAAA;AAAA;AAAA;AAAA,IAkBa;AAlBb;AAAA;AAAA;AAEA;AAgBO,IAAM,iBAAN,MAA2C;AAAA,MACzC;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAER,YACE,OACA,YAA6B,0BAC7B,eAAwB,OACxB,YACA;AACA,aAAK,QAAQ;AACb,aAAK,OAAO,MAAM;AAClB,aAAK,YAAY;AACjB,aAAK,eAAe;AACpB,aAAK,aAAa;AAAA,MACpB;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,UAAU,OAAuB,SAAiD;AAMtF,YAAI,kBAAkB,KAAK,UAAU;AACrC,YAAI;AAEJ,YAAI,CAAC,KAAK,gBAAgB,QAAQ,eAAe;AAI/C,gBAAM,aAAa,KAAK,cAAe,KAAK,MAAc,cAAc,KAAK;AAC7E,4BAAkB,QAAQ,cAAc,mBAAmB,YAAY,KAAK,SAAS;AACrF,sBAAY,QAAQ,cAAc,aAAa,UAAU;AAAA,QAC3D;AAIA,YAAI,KAAK,IAAI,kBAAkB,CAAG,IAAI,MAAO;AAC3C,iBAAO,KAAK,MAAM,UAAU,OAAO,OAAO;AAAA,QAC5C;AAOA,cAAM,iBAAiB,oBAAI,IAAoB;AAC/C,mBAAW,QAAQ,OAAO;AACxB,yBAAe,IAAI,KAAK,QAAQ,KAAK,KAAK;AAAA,QAC5C;AAMA,cAAM,mBAAmB,MAAM,KAAK,MAAM,UAAU,OAAO,OAAO;AAMlE,eAAO,iBAAiB,IAAI,CAAC,SAAS;AACpC,gBAAM,gBAAgB,eAAe,IAAI,KAAK,MAAM;AAMpD,cAAI,kBAAkB,UAAa,kBAAkB,KAAK,KAAK,UAAU,GAAG;AAC1E,mBAAO;AAAA,UACT;AAGA,gBAAM,YAAY,KAAK,QAAQ;AAG/B,cAAI,KAAK,IAAI,YAAY,CAAG,IAAI,MAAQ;AACtC,mBAAO;AAAA,UACT;AAKA,gBAAM,iBAAiB,KAAK,IAAI,WAAW,eAAe;AAC1D,gBAAM,WAAW,gBAAgB;AAKjC,gBAAM,gBAAgB,KAAK,WAAW,SAAS;AAC/C,gBAAM,WAAW,KAAK,WAAW,aAAa;AAE9C,cAAI,UAAU;AACZ,kBAAM,oBAAoB,CAAC,GAAG,KAAK,UAAU;AAC7C,8BAAkB,aAAa,IAAI;AAAA,cACjC,GAAG;AAAA,cACH,OAAO;AAAA,cACP;AAAA,cACA;AAAA;AAAA,YAEF;AAEA,mBAAO;AAAA,cACL,GAAG;AAAA,cACH,OAAO;AAAA,cACP,YAAY;AAAA,YACd;AAAA,UACF;AAGA,iBAAO;AAAA,YACL,GAAG;AAAA,YACH,OAAO;AAAA,UACT;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA;AAAA;;;AC5IA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgEA,SAAS,kBACP,UACA,UACA,eACA,eACQ;AAER,QAAM,qBAAqB,WAAW;AACtC,QAAM,QAAQ,KAAK,IAAI,EAAE,qBAAqB,mBAAmB;AAGjE,SAAO,iBAAiB,gBAAgB,iBAAiB;AAC3D;AAWO,SAAS,wBAAwB,QAAwC;AAC9E,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,gBAAgB,QAAQ,iBAAiB;AAE/C,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,MAAM,UAAU,OAAuB,SAAiD;AACtF,YAAM,EAAE,QAAQ,QAAQ,IAAI;AAG5B,YAAM,UAAU,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM;AACzC,YAAM,WAAW,MAAM,OAAO,eAAe,OAAO;AAEpD,aAAO,MAAM,IAAI,CAAC,MAAM,MAAM;AAC5B,cAAM,UAAU,SAAS,CAAC,GAAG,QAAQ,SAAS;AAC9C,cAAM,WAAW,KAAK,IAAI,UAAU,OAAO;AAC3C,cAAM,aAAa,kBAAkB,UAAU,UAAU,eAAe,aAAa;AACrF,cAAM,WAAW,KAAK,QAAQ;AAE9B,cAAM,SAAS,aAAa,gBAAgB,OAAO,cAAc;AAEjE,eAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO;AAAA,UACP,YAAY;AAAA,YACV,GAAG,KAAK;AAAA,YACR;AAAA,cACE,UAAU;AAAA,cACV,cAAc;AAAA,cACd,YAAY;AAAA,cACZ;AAAA,cACA,OAAO;AAAA,cACP,QAAQ,gBAAgB,KAAK,MAAM,QAAQ,CAAC,WAAW,KAAK,MAAM,OAAO,CAAC,WAAW,KAAK,MAAM,OAAO,CAAC,YAAO,WAAW,QAAQ,CAAC,CAAC;AAAA,YACtI;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAhIA,IAkDM,mBACA,wBACA;AApDN;AAAA;AAAA;AAkDA,IAAM,oBAAoB;AAC1B,IAAM,yBAAyB;AAC/B,IAAM,yBAAyB;AAAA;AAAA;;;ACpD/B;AAAA;AAAA;AAAA;AAMA,SAAS,eAAAC,oBAAmB;AAN5B,IA4CM,mBAiBe;AA7DrB;AAAA;AAAA;AAEA;AAKA;AAqCA,IAAM,oBAAoB;AAiB1B,IAAqB,+BAArB,cAA0D,iBAAuC;AAAA,MACvF;AAAA;AAAA,MAGR;AAAA,MAEA,YACE,MACA,QACA,cACA;AACA,cAAM,MAAM,QAAQ,YAAY;AAChC,aAAK,SAAS,KAAK,YAAY,aAAa,cAAc;AAC1D,aAAK,OAAO,aAAa,QAAQ;AAAA,MACnC;AAAA,MAEQ,YAAY,gBAAyC;AAC3D,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,cAAc;AACxC,iBAAO;AAAA,YACL,eAAe,OAAO,iBAAiB,CAAC;AAAA,UAC1C;AAAA,QACF,QAAQ;AAEN,iBAAO;AAAA,YACL,eAAe,CAAC;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKQ,kBACN,QACA,YACA,eACS;AACT,YAAI,CAAC,WAAY,QAAO;AAExB,cAAM,WAAW,OAAO,kBAAkB,YAAY;AACtD,YAAI,WAAW,QAAQ,SAAU,QAAO;AAExC,YAAI,OAAO,kBAAkB,WAAW,QAAW;AACjD,iBAAO,WAAW,SAAS,OAAO,iBAAiB;AAAA,QACrD,WAAW,OAAO,kBAAkB,aAAa,QAAW;AAI1D,iBAAO;AAAA,QACT,OAAO;AAEL,iBAAO,WAAW,SAAS;AAAA,QAC7B;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,MAAc,gBAAgB,SAA8C;AAC1E,cAAM,WAAW,oBAAI,IAAY;AAEjC,YAAI;AACF,gBAAM,YAAY,MAAM,QAAQ,KAAK,gBAAgB,QAAQ,OAAO,YAAY,CAAC;AACjF,gBAAM,UAAUA,aAAY,UAAU,GAAG;AAGzC,qBAAW,WAAW,OAAO,OAAO,KAAK,OAAO,aAAa,GAAG;AAC9D,uBAAW,UAAU,SAAS;AAC5B,oBAAM,SAAS,QAAQ,KAAK,OAAO,GAAG;AACtC,kBAAI,KAAK,kBAAkB,QAAQ,QAAQ,QAAQ,OAAO,KAAK,GAAG;AAChE,yBAAS,IAAI,OAAO,GAAG;AAAA,cACzB;AAAA,YACF;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKQ,gBAAgB,cAAwC;AAC9D,cAAM,WAAW,oBAAI,IAAY;AAEjC,mBAAW,CAAC,OAAO,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO,aAAa,GAAG;AACxE,gBAAM,gBAAgB,QAAQ,MAAM,CAAC,WAAW,aAAa,IAAI,OAAO,GAAG,CAAC;AAC5E,cAAI,eAAe;AACjB,qBAAS,IAAI,KAAK;AAAA,UACpB;AAAA,QACF;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKQ,iBAAiB,OAAwB;AAC/C,eAAO,SAAS,KAAK,OAAO;AAAA,MAC9B;AAAA;AAAA;AAAA;AAAA,MAKA,MAAc,gBACZ,MACA,SACA,cACA,cACkD;AAClD,YAAI;AAEF,gBAAM,WAAW,KAAK,QAAQ,CAAC;AAG/B,gBAAM,aAAa,SAAS;AAAA,YAC1B,CAAC,QAAQ,KAAK,iBAAiB,GAAG,KAAK,CAAC,aAAa,IAAI,GAAG;AAAA,UAC9D;AAEA,cAAI,WAAW,WAAW,GAAG;AAC3B,kBAAM,UAAU,SAAS,SAAS,IAAI,SAAS,KAAK,IAAI,IAAI;AAC5D,mBAAO;AAAA,cACL,YAAY;AAAA,cACZ,QAAQ,4BAA4B,OAAO;AAAA,YAC7C;AAAA,UACF;AAGA,gBAAM,iBAAiB,WAAW,QAAQ,CAAC,QAAQ;AACjD,kBAAM,UAAU,KAAK,OAAO,cAAc,GAAG,KAAK,CAAC;AACnD,mBAAO,QAAQ,OAAO,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAAA,UACzE,CAAC;AAED,iBAAO;AAAA,YACL,YAAY;AAAA,YACZ,QAAQ,kCAAkC,eAAe,KAAK,IAAI,CAAC,aAAa,WAAW,KAAK,IAAI,CAAC;AAAA,UACvG;AAAA,QACF,QAAQ;AAEN,iBAAO;AAAA,YACL,YAAY;AAAA,YACZ,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASQ,gBACN,cACA,cACqB;AACrB,cAAM,SAAS,oBAAI,IAAoB;AAEvC,mBAAW,CAAC,OAAO,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO,aAAa,GAAG;AAExE,cAAI,aAAa,IAAI,KAAK,EAAG;AAE7B,qBAAW,UAAU,SAAS;AAC5B,gBAAI,CAAC,OAAO,eAAe,OAAO,eAAe,EAAK;AAEtD,gBAAI,aAAa,IAAI,OAAO,GAAG,EAAG;AAElC,kBAAM,WAAW,OAAO,IAAI,OAAO,GAAG,KAAK;AAC3C,mBAAO,IAAI,OAAO,KAAK,KAAK,IAAI,UAAU,OAAO,WAAW,CAAC;AAAA,UAC/D;AAAA,QACF;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUA,MAAM,UAAU,OAAuB,SAAiD;AAEtF,cAAM,eAAe,MAAM,KAAK,gBAAgB,OAAO;AACvD,cAAM,eAAe,KAAK,gBAAgB,YAAY;AACtD,cAAM,eAAe,KAAK,gBAAgB,cAAc,YAAY;AAGpE,cAAM,QAAwB,CAAC;AAE/B,mBAAW,QAAQ,OAAO;AACxB,gBAAM,EAAE,YAAY,OAAO,IAAI,MAAM,KAAK;AAAA,YACxC;AAAA,YACA,QAAQ;AAAA,YACR;AAAA,YACA;AAAA,UACF;AACA,gBAAM,iBAAiB;AACvB,cAAI,aAAa,aAAa,KAAK,QAAQ,KAAK,QAAQ;AACxD,cAAI,SAA6C,aAAa,WAAW;AACzE,cAAI,cAAc;AAGlB,cAAI,cAAc,aAAa,OAAO,GAAG;AACvC,kBAAM,WAAW,KAAK,QAAQ,CAAC;AAC/B,gBAAI,WAAW;AACf,kBAAM,iBAA2B,CAAC;AAElC,uBAAW,OAAO,UAAU;AAC1B,oBAAM,QAAQ,aAAa,IAAI,GAAG;AAClC,kBAAI,SAAS,QAAQ,UAAU;AAC7B,2BAAW;AACX,+BAAe,KAAK,GAAG;AAAA,cACzB;AAAA,YACF;AAEA,gBAAI,WAAW,GAAK;AAClB,4BAAc;AACd,uBAAS;AACT,4BAAc,GAAG,MAAM,sBAAmB,SAAS,QAAQ,CAAC,CAAC,QAAQ,eAAe,KAAK,IAAI,CAAC;AAC9F,qBAAO;AAAA,gBACL,yCAAsC,SAAS,QAAQ,CAAC,CAAC,oBAAoB,KAAK,MAAM,cAAc,eAAe,KAAK,IAAI,CAAC,aAAa,KAAK,MAAM,QAAQ,CAAC,CAAC,WAAM,WAAW,QAAQ,CAAC,CAAC;AAAA,cAC9L;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,KAAK;AAAA,YACT,GAAG;AAAA,YACH,OAAO;AAAA,YACP,YAAY;AAAA,cACV,GAAG,KAAK;AAAA,cACR;AAAA,gBACE,UAAU;AAAA,gBACV,cAAc,KAAK,gBAAgB,KAAK;AAAA,gBACxC,YAAY,KAAK,cAAc;AAAA,gBAC/B;AAAA,gBACA,OAAO;AAAA,gBACP,QAAQ;AAAA,cACV;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAM,iBAAiB,QAAyC;AAC9D,cAAM,IAAI;AAAA,UACR;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;ACtUA;AAAA;AAAA;AAAA;AAAA,IA4EqB;AA5ErB;AAAA;AAAA;AAEA;AA0EA,IAAqB,0BAArB,cAAqD,iBAAuC;AAAA,MAClF;AAAA;AAAA,MAGR;AAAA,MAEA,YACE,MACA,QACA,cACA;AACA,cAAM,MAAM,QAAQ,YAAY;AAChC,aAAK,gBAAgB;AACrB,aAAK,OAAO,aAAa,QAAQ;AAAA,MACnC;AAAA;AAAA;AAAA;AAAA;AAAA,MAMQ,kBAAkB,UAAoB,UAA0C;AACtF,cAAM,cAAc,SAAS,IAAI,CAAC,QAAQ,SAAS,GAAG,CAAC,EAAE,OAAO,CAAC,QAAQ,QAAQ,MAAS;AAE1F,YAAI,YAAY,WAAW,GAAG;AAC5B,iBAAO;AAAA,QACT;AAGA,eAAO,KAAK,IAAI,GAAG,WAAW;AAAA,MAChC;AAAA;AAAA;AAAA;AAAA,MAKQ,YACN,UACA,UACA,YACQ;AAER,cAAM,eAAe,SAAS,OAAO,CAAC,QAAQ,SAAS,GAAG,MAAM,UAAU;AAE1E,YAAI,eAAe,GAAG;AACpB,iBAAO,gCAAgC,aAAa,KAAK,IAAI,CAAC,KAAK,UAAU;AAAA,QAC/E;AAEA,YAAI,aAAa,GAAK;AACpB,iBAAO,iCAAiC,aAAa,KAAK,IAAI,CAAC,KAAK,WAAW,QAAQ,CAAC,CAAC;AAAA,QAC3F;AAEA,YAAI,aAAa,GAAK;AACpB,iBAAO,+BAA+B,aAAa,KAAK,IAAI,CAAC,KAAK,WAAW,QAAQ,CAAC,CAAC;AAAA,QACzF;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAcA,MAAM,UAAU,OAAuB,UAAkD;AAEvF,cAAM,QAAQ,MAAM,KAAK,iBAAyC;AAGlE,YAAI,CAAC,SAAS,OAAO,KAAK,MAAM,KAAK,EAAE,WAAW,GAAG;AACnD,iBAAO,MAAM,IAAI,CAAC,UAAU;AAAA,YAC1B,GAAG;AAAA,YACH,YAAY;AAAA,cACV,GAAG,KAAK;AAAA,cACR;AAAA,gBACE,UAAU;AAAA,gBACV,cAAc,KAAK,gBAAgB,KAAK;AAAA,gBACxC,YAAY,KAAK,cAAc,KAAK,cAAc;AAAA,gBAClD,QAAQ;AAAA,gBACR,OAAO,KAAK;AAAA,gBACZ,QAAQ;AAAA,cACV;AAAA,YACF;AAAA,UACF,EAAE;AAAA,QACJ;AAGA,cAAM,WAA2B,MAAM,QAAQ;AAAA,UAC7C,MAAM,IAAI,OAAO,SAAS;AACxB,kBAAM,WAAW,KAAK,QAAQ,CAAC;AAG/B,kBAAM,aAAa,KAAK,kBAAkB,UAAU,MAAM,KAAK;AAC/D,kBAAM,aAAa,KAAK,IAAI,GAAG,KAAK,QAAQ,UAAU;AAGtD,gBAAI;AACJ,gBAAI,eAAe,KAAK,aAAa,GAAK;AACxC,uBAAS;AAAA,YACX,WAAW,aAAa,GAAK;AAC3B,uBAAS;AAAA,YACX,OAAO;AACL,uBAAS;AAAA,YACX;AAEA,mBAAO;AAAA,cACL,GAAG;AAAA,cACH,OAAO;AAAA,cACP,YAAY;AAAA,gBACV,GAAG,KAAK;AAAA,gBACR;AAAA,kBACE,UAAU;AAAA,kBACV,cAAc,KAAK,gBAAgB,KAAK;AAAA,kBACxC,YAAY,KAAK,cAAc,KAAK,cAAc;AAAA,kBAClD;AAAA,kBACA,OAAO;AAAA,kBACP,QAAQ,KAAK,YAAY,UAAU,MAAM,OAAO,UAAU;AAAA,gBAC5D;AAAA,cACF;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,iBAAiB,QAAyC;AAC9D,cAAM,IAAI;AAAA,UACR;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;ACxNA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA;AAGA;AAAA;AAAA;;;ACPA;AAAA;AAAA;AAAA;AAAA,IAuFa;AAvFb;AAAA;AAAA;AAuFO,IAAM,qCAAqC;AAAA;AAAA;;;ACvFlD;AAAA;AAAA;AAAA;AAMA,SAAS,eAAAC,oBAAmB;AAN5B,IA6DMC,oBACA,0BACA,4BAee;AA9ErB;AAAA;AAAA;AAEA;AA2DA,IAAMA,qBAAoB;AAC1B,IAAM,2BAA2B;AACjC,IAAM,6BAA6B;AAenC,IAAqB,iCAArB,cAA4D,iBAAuC;AAAA,MACzF;AAAA;AAAA,MAGR;AAAA;AAAA,MAGQ;AAAA,MAER,YACE,MACA,QACA,cACA;AACA,cAAM,MAAM,QAAQ,YAAY;AAChC,aAAK,SAAS,KAAK,YAAY,aAAa,cAAc;AAC1D,aAAK,kBAAkB,KAAK,qBAAqB;AACjD,aAAK,OAAO,aAAa,QAAQ;AAAA,MACnC;AAAA,MAEQ,YAAY,gBAA4C;AAC9D,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,cAAc;AAExC,cAAI,OAA4B,OAAO,oBAAoB,CAAC;AAC5D,cAAI,KAAK,SAAS,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC,GAAG;AAE7C,mBAAQ,KAA+B,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE;AAAA,UACjE;AAEA,iBAAO;AAAA,YACL,kBAAkB;AAAA,YAClB,mBAAmB;AAAA,cACjB,UAAU,OAAO,mBAAmB,YAAYA;AAAA,cAChD,QAAQ,OAAO,mBAAmB;AAAA,cAClC,gBAAgB,OAAO,mBAAmB,kBAAkB;AAAA,YAC9D;AAAA,YACA,cAAc,OAAO,gBAAgB;AAAA,UACvC;AAAA,QACF,QAAQ;AACN,iBAAO;AAAA,YACL,kBAAkB,CAAC;AAAA,YACnB,mBAAmB;AAAA,cACjB,UAAUA;AAAA,cACV,gBAAgB;AAAA,YAClB;AAAA,YACA,cAAc;AAAA,UAChB;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASQ,uBAA+E;AACrF,cAAM,MAAM,oBAAI,IAAuD;AAEvE,mBAAW,SAAS,KAAK,OAAO,kBAAkB;AAChD,gBAAM,QAAQ,MAAM,SAAS,KAAK,OAAO,gBAAgB;AAEzD,qBAAW,OAAO,MAAM,MAAM;AAC5B,gBAAI,CAAC,IAAI,IAAI,GAAG,GAAG;AACjB,kBAAI,IAAI,KAAK,CAAC,CAAC;AAAA,YACjB;AACA,kBAAM,WAAW,IAAI,IAAI,GAAG;AAC5B,uBAAW,SAAS,MAAM,MAAM;AAC9B,kBAAI,UAAU,KAAK;AAEjB,sBAAM,WAAW,SAAS,KAAK,CAAC,MAAM,EAAE,YAAY,KAAK;AACzD,oBAAI,UAAU;AAEZ,2BAAS,QAAQ,KAAK,IAAI,SAAS,OAAO,KAAK;AAAA,gBACjD,OAAO;AACL,2BAAS,KAAK,EAAE,SAAS,OAAO,MAAM,CAAC;AAAA,gBACzC;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAc,gBAAgB,SAA8C;AAC1E,cAAM,WAAW,oBAAI,IAAY;AAEjC,YAAI;AACF,gBAAM,YAAY,MAAM,QAAQ,KAAK,gBAAgB,QAAQ,OAAO,YAAY,CAAC;AACjF,gBAAM,UAAUD,aAAY,UAAU,GAAG;AAEzC,gBAAM,WAAW,KAAK,OAAO,mBAAmB,YAAYC;AAC5D,gBAAM,SAAS,KAAK,OAAO,mBAAmB;AAK9C,gBAAM,iBACJ,KAAK,OAAO,mBAAmB,kBAAkB;AACnD,gBAAM,qBAAqB,iBAAiB;AAE5C,qBAAW,CAAC,OAAO,MAAM,KAAK,OAAO,QAAQ,QAAQ,IAAI,GAAG;AAE1D,gBAAI,OAAO,UAAU,EAAG;AAGxB,kBAAM,aAAa,OAAO,QAAQ;AAClC,kBAAM,WAAW,WAAW,UAAa,OAAO,QAAQ;AACxD,kBAAM,eAAe,OAAO,QAAQ;AAEpC,gBAAI,cAAc,YAAY,cAAc;AAC1C,uBAAS,IAAI,KAAK;AAAA,YACpB;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA,MAMQ,eAAe,cAAgD;AACrE,cAAM,QAAQ,oBAAI,IAAoB;AAEtC,mBAAW,eAAe,cAAc;AACtC,gBAAM,WAAW,KAAK,gBAAgB,IAAI,WAAW;AACrD,cAAI,UAAU;AACZ,uBAAW,EAAE,SAAS,MAAM,KAAK,UAAU;AAGzC,kBAAI,CAAC,aAAa,IAAI,OAAO,GAAG;AAE9B,sBAAM,WAAW,MAAM,IAAI,OAAO,KAAK;AACvC,sBAAM,IAAI,SAAS,KAAK,IAAI,UAAU,KAAK,CAAC;AAAA,cAC9C;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA,MAMQ,0BACN,UACA,aACA,cACmE;AACnE,YAAI,YAAY,SAAS,GAAG;AAC1B,iBAAO;AAAA,YACL,YAAY;AAAA,YACZ,iBAAiB,CAAC;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAEA,YAAI,aAAa;AACjB,cAAM,kBAA4B,CAAC;AAEnC,mBAAW,OAAO,UAAU;AAC1B,gBAAM,QAAQ,YAAY,IAAI,GAAG;AACjC,cAAI,UAAU,QAAW;AACvB,4BAAgB,KAAK,GAAG;AACxB,0BAAc,IAAM;AAAA,UACtB;AAAA,QACF;AAEA,YAAI,gBAAgB,WAAW,GAAG;AAChC,iBAAO;AAAA,YACL,YAAY;AAAA,YACZ,iBAAiB,CAAC;AAAA,YAClB,QAAQ;AAAA,UACV;AAAA,QACF;AAGA,cAAM,cAAc,oBAAI,IAAY;AACpC,mBAAW,OAAO,iBAAiB;AACjC,qBAAW,eAAe,cAAc;AACtC,kBAAM,WAAW,KAAK,gBAAgB,IAAI,WAAW;AACrD,gBAAI,UAAU,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,GAAG;AAC5C,0BAAY,IAAI,WAAW;AAAA,YAC7B;AAAA,UACF;AAAA,QACF;AAEA,cAAM,SAAS,iCAAiC,MAAM,KAAK,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,gBAAgB,KAAK,IAAI,CAAC,iBAAiB,WAAW,QAAQ,CAAC,CAAC;AAE7J,eAAO,EAAE,YAAY,iBAAiB,OAAO;AAAA,MAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,MAAM,UAAU,OAAuB,SAAiD;AAEtF,cAAM,eAAe,MAAM,KAAK,gBAAgB,OAAO;AACvD,cAAM,cAAc,KAAK,eAAe,YAAY;AAGpD,cAAM,WAA2B,CAAC;AAElC,mBAAW,QAAQ,OAAO;AACxB,gBAAM,WAAW,KAAK,QAAQ,CAAC;AAC/B,gBAAM,EAAE,YAAY,OAAO,IAAI,KAAK;AAAA,YAClC;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,gBAAM,aAAa,KAAK,QAAQ;AAEhC,gBAAM,SAAS,aAAa,IAAM,cAAc,aAAa,IAAM,YAAY;AAE/E,mBAAS,KAAK;AAAA,YACZ,GAAG;AAAA,YACH,OAAO;AAAA,YACP,YAAY;AAAA,cACV,GAAG,KAAK;AAAA,cACR;AAAA,gBACE,UAAU;AAAA,gBACV,cAAc,KAAK,gBAAgB,KAAK;AAAA,gBACxC,YAAY,KAAK,cAAc;AAAA,gBAC/B;AAAA,gBACA,OAAO;AAAA,gBACP;AAAA,cACF;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAM,iBAAiB,QAAyC;AAC9D,cAAM,IAAI;AAAA,UACR;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;ACpVA;AAAA;AAAA;AAAA;AAAA,IAiEM,kBACA,4BACA,sBAgBe;AAnFrB;AAAA;AAAA;AAEA;AA+DA,IAAM,mBAAmB;AACzB,IAAM,6BAA6B;AACnC,IAAM,uBAAkD;AAgBxD,IAAqB,4BAArB,cAAuD,iBAAuC;AAAA,MACpF;AAAA;AAAA,MAGR;AAAA,MAEA,YACE,MACA,QACA,cACA;AACA,cAAM,MAAM,QAAQ,YAAY;AAChC,aAAK,SAAS,KAAK,YAAY,aAAa,cAAc;AAC1D,aAAK,OAAO,aAAa,QAAQ;AAAA,MACnC;AAAA,MAEQ,YAAY,gBAAgD;AAClE,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,cAAc;AACxC,iBAAO;AAAA,YACL,eAAe,OAAO,iBAAiB,CAAC;AAAA,YACxC,iBAAiB,OAAO,mBAAmB;AAAA,YAC3C,aAAa,OAAO,eAAe;AAAA,YACnC,mBAAmB,OAAO,qBAAqB;AAAA,UACjD;AAAA,QACF,QAAQ;AAEN,iBAAO;AAAA,YACL,eAAe,CAAC;AAAA,YAChB,iBAAiB;AAAA,YACjB,aAAa;AAAA,YACb,mBAAmB;AAAA,UACrB;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKQ,eAAe,OAAuB;AAC5C,eAAO,KAAK,OAAO,cAAc,KAAK,KAAK,KAAK,OAAO,mBAAmB;AAAA,MAC5E;AAAA;AAAA;AAAA;AAAA,MAKQ,oBAAoB,UAA4B;AACtD,YAAI,SAAS,WAAW,GAAG;AACzB,iBAAO,KAAK,OAAO,mBAAmB;AAAA,QACxC;AAEA,cAAM,aAAa,SAAS,IAAI,CAAC,QAAQ,KAAK,eAAe,GAAG,CAAC;AAEjE,gBAAQ,KAAK,OAAO,aAAa;AAAA,UAC/B,KAAK;AACH,mBAAO,KAAK,IAAI,GAAG,UAAU;AAAA,UAC/B,KAAK;AACH,mBAAO,KAAK,IAAI,GAAG,UAAU;AAAA,UAC/B,KAAK;AACH,mBAAO,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC,IAAI,WAAW;AAAA,UAChE;AACE,mBAAO,KAAK,IAAI,GAAG,UAAU;AAAA,QACjC;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYQ,mBAAmB,UAA0B;AACnD,cAAM,YAAY,KAAK,OAAO,qBAAqB;AACnD,eAAO,KAAK,WAAW,OAAO;AAAA,MAChC;AAAA;AAAA;AAAA;AAAA,MAKQ,oBACN,UACA,UACA,aACA,YACQ;AACR,YAAI,SAAS,WAAW,GAAG;AACzB,iBAAO,8BAA8B,SAAS,QAAQ,CAAC,CAAC;AAAA,QAC1D;AAEA,cAAM,UAAU,SAAS,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI;AAC9C,cAAM,OAAO,SAAS,SAAS,IAAI,MAAM,SAAS,SAAS,CAAC,WAAW;AAEvE,YAAI,gBAAgB,GAAK;AACvB,iBAAO,qBAAqB,SAAS,QAAQ,CAAC,CAAC,eAAe,OAAO,GAAG,IAAI;AAAA,QAC9E,WAAW,cAAc,GAAK;AAC5B,iBAAO,uBAAuB,OAAO,GAAG,IAAI,cAAc,SAAS,QAAQ,CAAC,CAAC,iBAAY,YAAY,QAAQ,CAAC,CAAC,YAAO,WAAW,QAAQ,CAAC,CAAC;AAAA,QAC7I,OAAO;AACL,iBAAO,sBAAsB,OAAO,GAAG,IAAI,cAAc,SAAS,QAAQ,CAAC,CAAC,kBAAa,YAAY,QAAQ,CAAC,CAAC,YAAO,WAAW,QAAQ,CAAC,CAAC;AAAA,QAC7I;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,MAAM,UAAU,OAAuB,UAAkD;AACvF,cAAM,WAA2B,MAAM,QAAQ;AAAA,UAC7C,MAAM,IAAI,OAAO,SAAS;AACxB,kBAAM,WAAW,KAAK,QAAQ,CAAC;AAC/B,kBAAM,WAAW,KAAK,oBAAoB,QAAQ;AAClD,kBAAM,cAAc,KAAK,mBAAmB,QAAQ;AAOpD,kBAAM,aAAa,KAAK,IAAI,GAAG,KAAK,QAAQ,WAAW;AAGvD,kBAAM,SAAS,cAAc,IAAM,YAAY,cAAc,IAAM,cAAc;AAGjF,kBAAM,SAAS,KAAK,oBAAoB,UAAU,UAAU,aAAa,UAAU;AAEnF,mBAAO;AAAA,cACL,GAAG;AAAA,cACH,OAAO;AAAA,cACP,YAAY;AAAA,gBACV,GAAG,KAAK;AAAA,gBACR;AAAA,kBACE,UAAU;AAAA,kBACV,cAAc,KAAK,gBAAgB,KAAK;AAAA,kBACxC,YAAY,KAAK,cAAc;AAAA,kBAC/B;AAAA,kBACA,OAAO;AAAA,kBACP;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAM,iBAAiB,QAAyC;AAC9D,cAAM,IAAI;AAAA,UACR;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;ACtPA,IAAAC,iBAAA;AAAA,IAAAC,cAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA,IAoHa;AApHb;AAAA;AAAA;AAoHO,IAAM,2BAA2B;AAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;ACxGjC,SAAS,6BACd,UACA,YACuB;AACvB,QAAM,eAAsC,CAAC;AAE7C,aAAW,WAAW,UAAU;AAE9B,UAAM,YAAY,QAAQ,WAAW,UAAU;AAC/C,QAAI,cAAc,QAAW;AAC3B;AAAA,IACF;AAEA,iBAAa,KAAK;AAAA,MAChB;AAAA,MACA,cAAc,QAAQ;AAAA,MACtB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,8BAA8B,aAAa,MAAM,8BAA8B,UAAU;AAAA,EAC3F;AAEA,SAAO;AACT;AAmBO,SAAS,wBACd,cACuB;AACvB,QAAM,IAAI,aAAa;AAEvB,MAAI,IAAI,GAAG;AACT,WAAO,MAAM,2DAA2D,CAAC,OAAO;AAChF,WAAO;AAAA,EACT;AAGA,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AAEX,aAAW,OAAO,cAAc;AAC9B,UAAM,IAAI,IAAI,UAAU;AACxB,YAAQ,IAAI,YAAY;AACxB,YAAQ,IAAI,eAAe;AAC3B,YAAQ;AAAA,EACV;AAEA,QAAM,QAAQ,OAAO;AACrB,QAAM,QAAQ,OAAO;AAGrB,MAAI,YAAY;AAChB,MAAI,cAAc;AAClB,MAAI,UAAU;AAEd,aAAW,OAAO,cAAc;AAC9B,UAAM,IAAI,IAAI,UAAU;AACxB,UAAM,KAAK,IAAI,YAAY;AAC3B,UAAM,KAAK,IAAI,eAAe;AAE9B,iBAAa,IAAI,KAAK;AACtB,mBAAe,IAAI,KAAK;AACxB,eAAW,IAAI,KAAK;AAAA,EACtB;AAGA,MAAI,cAAc,OAAO;AACvB,WAAO,MAAM,oEAAoE;AACjF,WAAO;AAAA,MACL,UAAU;AAAA,MACV,WAAW;AAAA,MACX,UAAU;AAAA,MACV,YAAY;AAAA,IACd;AAAA,EACF;AAEA,QAAM,WAAW,YAAY;AAC7B,QAAM,YAAY,QAAQ,WAAW;AAGrC,MAAI,aAAa;AACjB,aAAW,OAAO,cAAc;AAC9B,UAAM,IAAI,IAAI,UAAU;AACxB,UAAM,YAAY,WAAW,IAAI,YAAY;AAC7C,UAAM,WAAW,IAAI,eAAe;AACpC,kBAAc,IAAI,WAAW;AAAA,EAC/B;AAEA,QAAM,WAAW,UAAU,QAAQ,IAAI,aAAa,UAAU;AAE9D,SAAO;AAAA,IACL,sCAAsC,SAAS,QAAQ,CAAC,CAAC,gBACzC,UAAU,QAAQ,CAAC,CAAC,YAAS,SAAS,QAAQ,CAAC,CAAC,OAAO,CAAC;AAAA,EAC1E;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAAA;AAAA,IAC3C,YAAY;AAAA,EACd;AACF;AApIA;AAAA;AAAA;AAEA;AAAA;AAAA;;;ACyCO,SAAS,qBACd,SACA,UACiB;AAEjB,MAAI,SAAS,aAAa,6BAA6B;AACrD,WAAO;AAAA,MACL,yCAAyC,SAAS,UAAU,MAAM,2BAA2B;AAAA,IAE/F;AACA,WAAO;AAAA,MACL,GAAG;AAAA,MACH,YAAY,QAAQ,aAAa,SAAS;AAAA,IAC5C;AAAA,EACF;AAGA,QAAM,aAAa,SAAS,YAAY;AACxC,QAAM,SAAS,KAAK,IAAI,SAAS,QAAQ,IAAI;AAE7C,MAAI,YAAY,QAAQ;AACxB,MAAI,gBAAgB,QAAQ;AAE5B,MAAI,CAAC,cAAc,QAAQ;AAGzB,UAAM,iBAAiB,QAAQ,IAAI,QAAQ;AAC3C,oBAAgB,KAAK,IAAI,GAAK,QAAQ,aAAa,cAAc;AAEjE,WAAO;AAAA,MACL,iDAAiD,KAAK,IAAI,SAAS,QAAQ,EAAE,QAAQ,CAAC,CAAC,WAC/E,SAAS,SAAS,QAAQ,CAAC,CAAC,6BAA6B,QAAQ,WAAW,QAAQ,CAAC,CAAC,WAAM,cAAc,QAAQ,CAAC,CAAC;AAAA,IAC9H;AAAA,EACF,OAAO;AAGL,QAAI,QAAQ,SAAS,WAAW;AAChC,YAAQ,KAAK,IAAI,CAAC,kBAAkB,KAAK,IAAI,kBAAkB,KAAK,CAAC;AAErE,gBAAY,QAAQ,SAAS;AAG7B,gBAAY,KAAK,IAAI,KAAK,KAAK,IAAI,GAAK,SAAS,CAAC;AAGlD,UAAM,iBAAiB,QAAQ,IAAI,QAAQ;AAC3C,oBAAgB,KAAK,IAAI,GAAK,QAAQ,aAAa,cAAc;AAEjE,WAAO;AAAA,MACL,qCAAqC,QAAQ,OAAO,QAAQ,CAAC,CAAC,WAAM,UAAU,QAAQ,CAAC,CAAC,cACzE,SAAS,SAAS,QAAQ,CAAC,CAAC,WAAW,MAAM,QAAQ,CAAC,CAAC;AAAA,IACxE;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,QAAQ,aAAa,SAAS;AAAA,EAC5C;AACF;AAgBO,SAAS,oBACd,UACA,YACA,eACA,UACA,UACuB;AACvB,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,KAAK,4BAA4B,QAAQ,KAAK,UAAU;AAG9D,QAAM,eAAe;AAAA,IACnB,WAAW;AAAA,IACX,QAAQ,cAAc;AAAA,IACtB,YAAY,cAAc;AAAA,IAC1B,UAAU,SAAS;AAAA,EACrB;AAGA,MAAI,UAAU,UAAU,WAAW,CAAC;AACpC,YAAU,CAAC,GAAG,SAAS,YAAY;AAGnC,MAAI,QAAQ,SAAS,oBAAoB;AACvC,cAAU,QAAQ,MAAM,QAAQ,SAAS,kBAAkB;AAAA,EAC7D;AAEA,QAAM,QAA+B;AAAA,IACnC,KAAK;AAAA,IACL,MAAM,UAAU;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,MACV,UAAU,SAAS;AAAA,MACnB,WAAW,SAAS;AAAA,MACpB,UAAU,SAAS;AAAA,MACnB,YAAY,SAAS;AAAA,MACrB,YAAY;AAAA,IACd;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACb;AAEA,SAAO;AACT;AA2CO,SAAS,gBAAgB,OAA8C;AAC5E,QAAM,EAAE,UAAU,YAAY,eAAe,UAAU,cAAc,IAAI;AAEzE,SAAO;AAAA,IACL,sDAAsD,UAAU,KAC1D,SAAS,UAAU;AAAA,EAC3B;AAGA,QAAM,YAAY,qBAAqB,eAAe,QAAQ;AAC9D,QAAM,UAAU,UAAU,WAAW,cAAc;AAGnD,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,8CAA8C,UAAU,YAC5C,cAAc,OAAO,QAAQ,CAAC,CAAC,WAAM,UAAU,OAAO,QAAQ,CAAC,CAAC,gBAC5D,cAAc,WAAW,QAAQ,CAAC,CAAC,WAAM,UAAU,WAAW,QAAQ,CAAC,CAAC;AAAA,EAC1F;AAEA,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAKO,SAAS,4BAA6C;AAC3D,SAAO,EAAE,GAAG,yBAAyB;AACvC;AAzPA,IAUM,6BAGA,eAGA,kBAGA,4BAGA,yBAGA;AAzBN;AAAA;AAAA;AAAA;AAEA;AACA;AAOA,IAAM,8BAA8B;AAGpC,IAAM,gBAAgB;AAGtB,IAAM,mBAAmB;AAGzB,IAAM,6BAA6B;AAGnC,IAAM,0BAA0B;AAGhC,IAAM,qBAAqB;AAAA;AAAA;;;ACNpB,SAAS,qBACd,SACA,SAAuB,CAAC,GACT;AACf,MAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,OAAO,kBAAkB;AACxC,QAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,UAAU;AACd,aAAW,KAAK,SAAS;AAGvB,QAAK,EAAU,UAAW;AAAA,EAC5B;AAEA,QAAM,WAAW,UAAU,QAAQ;AAEnC,SAAO,oBAAoB,UAAU,QAAQ,SAAS;AACxD;AAYO,SAAS,oBAAoB,UAAkB,QAAgB,WAA2B;AAC/F,QAAM,OAAO,KAAK,IAAI,WAAW,MAAM;AAGvC,MAAI,QAAQ,WAAW;AACrB,WAAO;AAAA,EACT;AAIA,QAAM,SAAS,OAAO;AACtB,QAAM,QAAQ;AAEd,SAAO,KAAK,IAAI,GAAG,IAAM,SAAS,KAAK;AACzC;AAlEA;AAAA;AAAA;AAAA;AAAA;;;AC0BA,eAAsB,kBACpB,SACA,aACA,WACA,SACA,mBACA,WAAmB,GACnB,SAAiB,GACjB,QACe;AACf,QAAM,EAAE,MAAM,QAAQ,OAAO,IAAI;AACjC,QAAM,WAAW,OAAO,YAAY;AAIpC,QAAM,eAAe,qBAAqB,SAAS,MAAM;AAEzD,MAAI,iBAAiB,MAAM;AACzB,WAAO;AAAA,MACL,kDAAkD,MAAM;AAAA,IAC1D;AACA;AAAA,EACF;AAMA,QAAM,aAAqC,CAAC;AAC5C,aAAW,cAAc,mBAAmB;AAC1C,eAAW,UAAU,IAAI,QAAQ,aAAa,UAAU;AAAA,EAC1D;AAKA,QAAM,KAAK,iBAAiB,QAAQ,KAAK,MAAM,KAAK,SAAS;AAE7D,QAAM,SAA4B;AAAA,IAChC,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,MACR,eAAe;AAAA;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AAGA,MAAI;AACF,UAAM,KAAK,eAAe,MAAM;AAChC,WAAO;AAAA,MACL,oCAAoC,aAAa,QAAQ,CAAC,CAAC,QAAQ,MAAM,UAAU,EAAE;AAAA,IACvF;AAAA,EACF,SAAS,GAAG;AACV,WAAO,MAAM,6CAA6C,CAAC,EAAE;AAAA,EAC/D;AACF;AA3FA;AAAA;AAAA;AACA;AAEA;AACA;AAAA;AAAA;;;ACsEA,SAAS,MAAM,KAAqB;AAClC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,YAAQ,IAAI,WAAW,CAAC;AACxB,WAAO,KAAK,KAAK,MAAM,QAAQ;AAAA,EACjC;AACA,SAAO,SAAS;AAClB;AAgBO,SAAS,iBAAiB,QAAgB,YAAoB,MAAsB;AACzF,QAAM,QAAQ,GAAG,MAAM,IAAI,UAAU,IAAI,IAAI;AAC7C,QAAM,OAAO,MAAM,KAAK;AAGxB,QAAM,aAAa,OAAO;AAG1B,SAAQ,aAAa,IAAK;AAC5B;AAWO,SAAS,cAAc,YAA4B;AAExD,QAAM,oBAAoB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,CAAC;AAC7D,SAAO,aAAc,qBAAqB,aAAa;AACzD;AAcO,SAAS,uBACd,WACA,QACA,YACA,MACQ;AACR,QAAM,YAAY,iBAAiB,QAAQ,YAAY,IAAI;AAC3D,QAAM,SAAS,cAAc,UAAU,UAAU;AAKjD,QAAM,aAAa,YAAY,SAAS,UAAU;AAElD,QAAM,YAAY,UAAU,SAAS;AAGrC,SAAO,KAAK,IAAI,YAAY,KAAK,IAAI,YAAY,SAAS,CAAC;AAC7D;AAeA,eAAsB,2BACpB,MACA,QAC+B;AAC/B,MAAI;AACJ,MAAI;AACF,mBAAe,MAAM,OAAO,gBAAgB;AAAA,EAC9C,SAAS,GAAG;AACV,WAAO,MAAM,iDAAiD,CAAC,EAAE;AAEjE,mBAAe;AAAA,MACb,MAAM;AAAA,MACN,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ,CAAC;AAAA,MACT,YAAY,CAAC;AAAA,MACb,YAAY,CAAC;AAAA,MACb,eAAe,CAAC;AAAA,MAChB,eAAe,EAAE,MAAM,UAAU;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,SAAS,KAAK,YAAY;AAChC,QAAM,OAAO,aAAa,eAAe,QAAQ;AAEjD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IAEA,mBAAmB,YAAoB,WAAoC;AACzE,aAAO,uBAAuB,WAAW,QAAQ,YAAY,IAAI;AAAA,IACnE;AAAA,IAEA,aAAa,YAA4B;AACvC,aAAO,iBAAiB,QAAQ,YAAY,IAAI;AAAA,IAClD;AAAA,EACF;AACF;AAjNA,IAkEM,YACA,YACA,YACA;AArEN;AAAA;AAAA;AAIA;AAGA;AACA;AASA;AAIA;AA6CA,IAAM,aAAa;AACnB,IAAM,aAAa;AACnB,IAAM,aAAa;AACnB,IAAM,aAAa;AAAA;AAAA;;;ACrEnB;AAAA;AAAA;AAAA;AAAA,SAAS,eAAAC,oBAAmB;AAsD5B,SAAS,YAAY,SAAyB;AAC5C,QAAM,UAAU,QAAQ,QAAQ,sBAAsB,MAAM;AAC5D,QAAM,gBAAgB,QAAQ,QAAQ,OAAO,IAAI;AACjD,SAAO,IAAI,OAAO,IAAI,aAAa,GAAG;AACxC;AAGA,SAAS,UAAU,OAAe,SAA0B;AAC1D,MAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO,UAAU;AAC7C,SAAO,YAAY,OAAO,EAAE,KAAK,KAAK;AACxC;AAGA,SAAS,sBAAsB,MAAoB,SAA0B;AAC3E,UAAQ,KAAK,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,UAAU,KAAK,OAAO,CAAC;AAChE;AAcA,SAAS,kBAAkB,WAA0B,SAA6B;AAChF,QAAM,aACJ,QAAQ,SAAS,IAAI,aAAa,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,UAAU,IAAI;AAElF,SAAO;AAAA,IACL;AAAA,eAAgD,UAAU,IAAI;AAAA,YAAoB,UAAU;AAAA,EAC9F;AACF;AAMA,SAAS,gBAAgB,OAAuB,YAAyC;AACvF,QAAM,YAAY,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,QAAQ,CAAC;AAC5F,QAAM,gBAAgB,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE;AAExF,SAAO;AAAA,IACL,6BAA6B,MAAM,MAAM,WACpC,aAAa,eAAe,SAAS;AAAA,EAC5C;AACF;AAMA,SAAS,oBACP,eACA,gBACA,aACA,YACA,WACA,eACM;AACN,QAAM,eACJ,UAAU,SAAS,IAAI,UAAU,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;AAEzE,MAAI,gBAAgB;AACpB,MAAI,cAAc,SAAS,GAAG;AAC5B,UAAM,UAAU,cAAc,IAAI,CAAC,MAAM;AACvC,YAAM,QAAkB,CAAC;AACzB,UAAI,EAAE,UAAU,EAAG,OAAM,KAAK,IAAI,EAAE,OAAO,EAAE;AAC7C,UAAI,EAAE,YAAY,EAAG,OAAM,KAAK,IAAI,EAAE,SAAS,EAAE;AACjD,UAAI,EAAE,SAAS,EAAG,OAAM,KAAK,IAAI,EAAE,MAAM,EAAE;AAC3C,aAAO,GAAG,EAAE,IAAI,KAAK,MAAM,KAAK,GAAG,CAAC;AAAA,IACtC,CAAC;AACD,oBAAgB;AAAA,mBAAsB,QAAQ,KAAK,IAAI,CAAC;AAAA,EAC1D;AAEA,SAAO;AAAA,IACL,yBAAyB,aAAa,aAAa,cAAc,WAC5D,WAAW,mBAAc,UAAU,yBAAyB,YAAY,MAC3E,gBACA;AAAA;AAAA,EACJ;AACF;AAQA,SAAS,eAAe,OAA6B;AACnD,MAAI,CAAC,mBAAmB,MAAM,WAAW,EAAG;AAE5C,SAAO,KAAK,uBAAuB,MAAM,MAAM,UAAU;AACzD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,IAAI,MAAM,CAAC;AACjB,UAAM,OAAO,EAAE,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,KAAK;AAC/C,UAAM,UAAU,EAAE,WACf,OAAO,CAAC,MAAM,EAAE,aAAa,yBAAyB,EAAE,aAAa,wBAAwB,EAAE,aAAa,wBAAwB,EAAE,aAAa,kBAAkB,EAAE,aAAa,eAAe,EACnM,IAAI,CAAC,MAAM;AACV,YAAM,QAAQ,EAAE,WAAW,YAAY,WAAM,EAAE,WAAW,cAAc,WAAM;AAC9E,aAAO,GAAG,EAAE,YAAY,GAAG,KAAK,GAAG,EAAE,MAAM,QAAQ,CAAC,CAAC;AAAA,IACvD,CAAC,EACA,KAAK,KAAK;AACb,WAAO;AAAA,MACL,gBAAgB,OAAO,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,MAAM,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,MAAM,IAAI,IAAI,UAAU,MAAM,OAAO,MAAM,EAAE;AAAA,IAC5H;AAAA,EACF;AACF;AAOA,SAAS,kBAAkB,OAAuB,WAAmB,GAAS;AAC5E,QAAM,aAAa,MAAM,MAAM,GAAG,QAAQ;AAE1C,SAAO,MAAM,iCAAiC,WAAW,MAAM,SAAS;AAExE,aAAW,QAAQ,YAAY;AAC7B,WAAO,MAAM,gBAAgB,KAAK,MAAM,kBAAkB,KAAK,MAAM,QAAQ,CAAC,CAAC,IAAI;AAEnF,eAAW,SAAS,KAAK,YAAY;AACnC,YAAM,cAAc,MAAM,MAAM,QAAQ,CAAC;AACzC,YAAM,SAAS,MAAM,OAAO,OAAO,CAAC;AACpC,aAAO;AAAA,QACL,kBAAkB,MAAM,IAAI,WAAW,MAAM,MAAM,YAAY,KAAK,MAAM,MAAM;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AACF;AA5LA,IAiJM,iBAoFO;AArOb;AAAA;AAAA;AAGA;AAIA;AACA;AACA;AAwIA,IAAM,kBAAkB;AAoFjB,IAAM,WAAN,cAAuB,iBAAiB;AAAA,MACrC;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASA,uBAAoD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASpD,YAAmC,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,MAM3C,kBAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAU9C,YACE,WACA,SACA,MACA,QACA;AACA,cAAM;AACN,aAAK,YAAY;AACjB,aAAK,UAAU;AACf,aAAK,OAAO;AACZ,aAAK,SAAS;AAEd,eACG,gBAAgB,EAChB,KAAK,CAAC,QAAQ;AACb,iBAAO,MAAM,kCAAkC,IAAI,IAAI,EAAE;AAAA,QAC3D,CAAC,EACA,MAAM,CAAC,MAAM;AACZ,iBAAO,MAAM,0CAA0C,CAAC,EAAE;AAAA,QAC5D,CAAC;AAEH,0BAAkB,WAAW,OAAO;AAGpC,iCAAyB,IAAI;AAAA,MAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQS,kBAAkB,OAAsC;AAC/D,aAAK,kBAAkB;AACvB,eAAO,KAAK,mCAAmC,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,MACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAgBA,MAAM,iBAAiB,OAAwC;AAC7D,cAAM,KAAK,YAAY,IAAI;AAG3B,cAAM,UAAU,MAAM,KAAK,aAAa;AACxC,cAAM,WAAW,YAAY,IAAI;AAMjC,cAAM,aAAa;AAEnB,eAAO;AAAA,UACL,uBAAuB,UAAU,+BAA+B,KAAK,UAAU,IAAI;AAAA,QACrF;AAGA,YAAI,QAAQ,MAAM,KAAK,UAAU,iBAAiB,YAAY,OAAO;AACrE,cAAM,YAAY,YAAY,IAAI;AAClC,cAAM,iBAAiB,MAAM;AAG7B,YAAI;AACJ,YAAK,KAAK,UAAkB,YAAY;AAEtC,gBAAM,SAAS,oBAAI,IAAuC;AAC1D,qBAAW,QAAQ,OAAO;AACxB,kBAAM,YAAY,KAAK,WAAW,CAAC;AACnC,gBAAI,WAAW;AACb,oBAAM,UAAU,UAAU;AAC1B,kBAAI,CAAC,OAAO,IAAI,OAAO,GAAG;AACxB,uBAAO,IAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;AAAA,cACnC;AACA,qBAAO,IAAI,OAAO,EAAG,MAAM,KAAK,IAAI;AAAA,YACtC;AAAA,UACF;AACA,+BAAqB,MAAM,KAAK,OAAO,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM;AACtE,kBAAM,WAAW,KAAK,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,QAAQ,SAAS,UAAU,CAAC;AACvF,kBAAM,cAAc,KAAK,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,QAAQ,SAAS,QAAQ,CAAC;AACxF,mBAAO;AAAA,cACL;AAAA,cACA,WAAW,KAAK,MAAM;AAAA,cACtB,UAAU,SAAS;AAAA,cACnB,aAAa,YAAY;AAAA,cACzB,UAAU,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,CAAC;AAAA,YACzD;AAAA,UACF,CAAC;AAAA,QACH;AAEA,eAAO,MAAM,iCAAiC,cAAc,aAAa;AAGzE,gBAAQ,MAAM,KAAK,YAAY,KAAK;AACpC,cAAM,WAAW,YAAY,IAAI;AAGjC,cAAM,0BAA0B,CAAC,GAAG,KAAK;AAGzC,cAAM,gBAAgC,CAAC;AACvC,mBAAW,UAAU,KAAK,SAAS;AACjC,gBAAM,cAAc,MAAM;AAC1B,gBAAM,eAAe,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AAClE,kBAAQ,MAAM,OAAO,UAAU,OAAO,OAAO;AAG7C,cAAI,UAAU,GAAG,YAAY,GAAG,SAAS;AACzC,gBAAM,UAAU,cAAc,MAAM;AAEpC,qBAAW,QAAQ,OAAO;AACxB,kBAAM,SAAS,aAAa,IAAI,KAAK,MAAM,KAAK;AAChD,gBAAI,KAAK,QAAQ,OAAQ;AAAA,qBAChB,KAAK,QAAQ,OAAQ;AAAA,gBACzB;AAAA,UACP;AACA,wBAAc,KAAK,EAAE,MAAM,OAAO,MAAM,SAAS,WAAW,QAAQ,QAAQ,CAAC;AAE7E,iBAAO,MAAM,sBAAsB,OAAO,IAAI,MAAM,aAAa,IAAI,WAAM,MAAM,MAAM,iBAAY,OAAO,UAAK,SAAS,KAAK,MAAM,GAAG;AAAA,QACxI;AAGA,gBAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC;AAGvC,cAAM,QAAQ,KAAK;AACnB,YAAI,OAAO;AACT,eAAK,kBAAkB;AACvB,kBAAQ,KAAK,WAAW,OAAO,OAAO,uBAAuB;AAAA,QAC/D;AAGA,cAAM,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAGtC,cAAM,UAAU,YAAY,IAAI;AAChC,cAAM,SAAS,MAAM,MAAM,GAAG,KAAK;AAEnC,eAAO;AAAA,UACL,4BAA4B,UAAU,IAAI,QAAQ,CAAC,CAAC,gBACvC,WAAW,IAAI,QAAQ,CAAC,CAAC,cAAc,YAAY,UAAU,QAAQ,CAAC,CAAC,aACxE,WAAW,WAAW,QAAQ,CAAC,CAAC,YAAY,UAAU,UAAU,QAAQ,CAAC,CAAC;AAAA,QACxF;AAGA,cAAM,YAAY,OAAO,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK;AACvD;AAAA,UACE,KAAK,UAAU;AAAA,UACf;AAAA,UACA,KAAK,QAAQ;AAAA,UACb,OAAO;AAAA,UACP;AAAA,UACA;AAAA,QACF;AAGA,uBAAe,MAAM;AAGrB,0BAAkB,QAAQ,CAAC;AAG3B,YAAI;AACF,gBAAM,aAAa,MAAM,KAAK,QAAQ,gBAAgB,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,MAAM,MAAS;AACjG,gBAAM,SAAS;AAAA,YACb,KAAK,QAAQ,YAAY,KAAK;AAAA,YAC9B;AAAA,YACA,KAAK,UAAU;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,qBAAW,MAAM;AAAA,QACnB,SAAS,GAAG;AACV,iBAAO,MAAM,2CAA2C,CAAC,EAAE;AAAA,QAC7D;AAEA,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAgBA,MAAc,YAAY,OAAgD;AACxE,YAAI,MAAM,WAAW,GAAG;AACtB,iBAAO;AAAA,QACT;AAGA,cAAM,cAAwB,CAAC;AAC/B,mBAAW,QAAQ,OAAO;AACxB,cAAI,CAAC,KAAK,UAAU,IAAI,KAAK,MAAM,GAAG;AACpC,wBAAY,KAAK,KAAK,MAAM;AAAA,UAC9B;AAAA,QACF;AAGA,YAAI,YAAY,SAAS,GAAG;AAC1B,gBAAM,YAAY,MAAM,KAAK,OAAQ,oBAAoB,WAAW;AACpE,qBAAW,CAAC,QAAQ,IAAI,KAAK,WAAW;AACtC,iBAAK,UAAU,IAAI,QAAQ,IAAI;AAAA,UACjC;AAAA,QACF;AAGA,cAAM,aAAa,oBAAI,IAAsB;AAC7C,mBAAW,QAAQ,OAAO;AACxB,qBAAW,IAAI,KAAK,QAAQ,KAAK,UAAU,IAAI,KAAK,MAAM,KAAK,CAAC,CAAC;AAAA,QACnE;AAGA,wBAAgB,OAAO,UAAU;AAEjC,eAAO,MAAM,IAAI,CAAC,UAAU;AAAA,UAC1B,GAAG;AAAA,UACH,MAAM,KAAK,UAAU,IAAI,KAAK,MAAM,KAAK,CAAC;AAAA,QAC5C,EAAE;AAAA,MACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAkBQ,WACN,OACA,OACA,UACgB;AAChB,cAAM,cAAc,MAAM;AAG1B,YAAI,MAAM,cAAc,QAAQ;AAC9B,kBAAQ,MAAM;AAAA,YACZ,CAAC,MAAM,CAAC,MAAM,aAAc,KAAK,CAAC,QAAQ,UAAU,EAAE,QAAQ,GAAG,CAAC;AAAA,UACpE;AAAA,QACF;AACA,YAAI,MAAM,aAAa,QAAQ;AAC7B,kBAAQ,MAAM;AAAA,YACZ,CAAC,MAAM,CAAC,MAAM,YAAa,KAAK,CAAC,QAAQ,sBAAsB,GAAG,GAAG,CAAC;AAAA,UACxE;AAAA,QACF;AAGA,YAAI,MAAM,WAAW;AACnB,qBAAW,CAAC,SAAS,MAAM,KAAK,OAAO,QAAQ,MAAM,SAAS,GAAG;AAC/D,uBAAW,QAAQ,OAAO;AACxB,kBAAI,sBAAsB,MAAM,OAAO,GAAG;AACxC,qBAAK,SAAS;AACd,qBAAK,WAAW,KAAK;AAAA,kBACnB,UAAU;AAAA,kBACV,YAAY;AAAA,kBACZ,cAAc,MAAM,SAAS,gBAAgB,MAAM,MAAM,MAAM;AAAA,kBAC/D,QAAQ;AAAA,kBACR,OAAO,KAAK;AAAA,kBACZ,QAAQ,YAAY,OAAO,QAAK,MAAM;AAAA,gBACxC,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF;AAAA,QACF;AACA,YAAI,MAAM,YAAY;AACpB,qBAAW,CAAC,SAAS,MAAM,KAAK,OAAO,QAAQ,MAAM,UAAU,GAAG;AAChE,uBAAW,QAAQ,OAAO;AACxB,kBAAI,UAAU,KAAK,QAAQ,OAAO,GAAG;AACnC,qBAAK,SAAS;AACd,qBAAK,WAAW,KAAK;AAAA,kBACnB,UAAU;AAAA,kBACV,YAAY;AAAA,kBACZ,cAAc,MAAM,SAAS,gBAAgB,MAAM,MAAM,MAAM;AAAA,kBAC/D,QAAQ;AAAA,kBACR,OAAO,KAAK;AAAA,kBACZ,QAAQ,aAAa,OAAO,QAAK,MAAM;AAAA,gBACzC,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,cAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AAClD,cAAM,YAAY,MAAM,SAAS,gBAAgB,MAAM,MAAM,MAAM;AACnE,cAAM,SAAS,CAAC,MAAoB,WAAmB;AACrD,cAAI,CAAC,QAAQ,IAAI,KAAK,MAAM,GAAG;AAE7B,kBAAM,aAAa,KAAK,IAAI,KAAK,OAAO,CAAG;AAC3C,kBAAM,KAAK;AAAA,cACT,GAAG;AAAA,cACH,OAAO;AAAA,cACP,YAAY;AAAA,gBACV,GAAG,KAAK;AAAA,gBACR;AAAA,kBACE,UAAU;AAAA,kBACV,YAAY;AAAA,kBACZ,cAAc;AAAA,kBACd,QAAQ;AAAA,kBACR,OAAO;AAAA,kBACP;AAAA,gBACF;AAAA,cACF;AAAA,YACF,CAAC;AACD,oBAAQ,IAAI,KAAK,MAAM;AAAA,UACzB;AAAA,QACF;AAEA,YAAI,MAAM,cAAc,QAAQ;AAC9B,qBAAW,WAAW,MAAM,cAAc;AACxC,uBAAW,QAAQ,UAAU;AAC3B,kBAAI,UAAU,KAAK,QAAQ,OAAO,EAAG,QAAO,MAAM,eAAe,OAAO,EAAE;AAAA,YAC5E;AAAA,UACF;AAAA,QACF;AACA,YAAI,MAAM,aAAa,QAAQ;AAC7B,qBAAW,WAAW,MAAM,aAAa;AACvC,uBAAW,QAAQ,UAAU;AAC3B,kBAAI,sBAAsB,MAAM,OAAO,EAAG,QAAO,MAAM,cAAc,OAAO,EAAE;AAAA,YAChF;AAAA,UACF;AAAA,QACF;AAEA,eAAO,KAAK,6BAA6B,WAAW,WAAM,MAAM,MAAM,QAAQ;AAE9E,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUA,MAAc,eAA0D;AACtE,YAAI,UAAU;AAEd,YAAI;AACF,gBAAM,YAAY,MAAM,KAAK,KAAM,gBAAgB,KAAK,OAAQ,YAAY,CAAC;AAC7E,gBAAM,YAAYA,aAAY,UAAU,GAAG;AAC3C,oBAAU,UAAU,OAAO;AAAA,QAC7B,SAAS,GAAG;AACV,iBAAO,MAAM,qDAAqD,CAAC,EAAE;AAAA,QACvE;AAKA,YAAI,CAAC,KAAK,sBAAsB;AAC9B,eAAK,uBAAuB,MAAM,2BAA2B,KAAK,MAAO,KAAK,MAAO;AAAA,QACvF;AACA,cAAM,gBAAgB,KAAK;AAE3B,eAAO;AAAA,UACL,MAAM,KAAK;AAAA,UACX,QAAQ,KAAK;AAAA,UACb;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,cAAsB;AACpB,eAAO,KAAK,OAAQ,YAAY;AAAA,MAClC;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,0BAAyD;AAC7D,eAAO,2BAA2B,KAAK,MAAO,KAAK,MAAO;AAAA,MAC5D;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,iBAA2B;AACzB,cAAM,MAAgB,CAAC;AAEvB,cAAM,YAAY,CAAC,QAA4B;AAE7C,cAAI,IAAI,WAAY,QAAO,IAAI;AAC/B,iBAAO;AAAA,QACT;AAGA,cAAM,QAAQ,UAAU,KAAK,SAAS;AACtC,YAAI,MAAO,KAAI,KAAK,KAAK;AAGzB,YAAK,KAAK,UAAkB,cAAc,MAAM,QAAS,KAAK,UAAkB,UAAU,GAAG;AAC3F,UAAC,KAAK,UAAkB,WAAW,QAAQ,CAAC,MAAW;AACrD,kBAAM,QAAQ,UAAU,CAAC;AACzB,gBAAI,MAAO,KAAI,KAAK,KAAK;AAAA,UAC3B,CAAC;AAAA,QACH;AAGA,mBAAW,UAAU,KAAK,SAAS;AACjC,gBAAM,MAAM,UAAU,MAAM;AAC5B,cAAI,IAAK,KAAI,KAAK,GAAG;AAAA,QACvB;AAEA,eAAO,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC;AAAA,MACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAcA,MAAM,kBAAkB,MAA4D;AAClF,cAAM,YAAY,MAAM,aAAa;AACrC,cAAM,KAAK,YAAY,IAAI;AAG3B,cAAM,aAAa,MAAM,KAAK,OAAQ,cAAc;AAGpD,YAAI,QAAwB,WAAW,IAAI,CAAC,YAAY;AAAA,UACtD;AAAA,UACA,UAAU,KAAK,OAAQ,YAAY;AAAA,UACnC,OAAO;AAAA,UACP,YAAY,CAAC;AAAA,QACf,EAAE;AAGF,gBAAQ,MAAM,KAAK,YAAY,KAAK;AAGpC,cAAM,UAAU,MAAM,KAAK,aAAa;AACxC,cAAM,kBAAkE,CAAC;AAGzE,mBAAW,UAAU,KAAK,SAAS;AACjC,kBAAQ,MAAM,OAAO,UAAU,OAAO,OAAO;AAC7C,gBAAM,KAAK,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE;AACrD,0BAAgB,KAAK,EAAE,MAAM,OAAO,MAAM,eAAe,GAAG,CAAC;AAAA,QAC/D;AAGA,cAAM,gBAAgB,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,SAAS;AAC9D,cAAM,mBAAmB,IAAI,IAAI,cAAc,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AAGnE,YAAI;AACJ,YAAI;AACF,gBAAM,WAAW,KAAK,OAAQ,YAAY;AAC1C,gBAAM,YAAY,MAAM,KAAK,KAAM,aAAa,QAAQ;AACxD,2BAAiB,IAAI,IAAI,SAAS;AAAA,QACpC,QAAQ;AACN,2BAAiB,oBAAI,IAAI;AAAA,QAC3B;AAEA,cAAM,mBAAmB,cAAc,OAAO,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,MAAM,CAAC;AAGlF,cAAM,SAAS,oBAAI,IAAmE;AACtF,mBAAW,QAAQ,OAAO;AACxB,gBAAM,OAAO,KAAK,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK;AAC1C,cAAI,CAAC,OAAO,IAAI,IAAI,GAAG;AACrB,mBAAO,IAAI,MAAM,EAAE,OAAO,GAAG,eAAe,GAAG,KAAK,EAAE,CAAC;AAAA,UACzD;AACA,gBAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,gBAAM;AACN,cAAI,KAAK,SAAS,WAAW;AAC3B,kBAAM;AACN,gBAAI,CAAC,eAAe,IAAI,KAAK,MAAM,EAAG,OAAM;AAAA,UAC9C;AAAA,QACF;AAEA,cAAM,UAAU,YAAY,IAAI,IAAI;AAEpC,cAAM,SAA6B;AAAA,UACjC,YAAY,WAAW;AAAA,UACvB,WAAW;AAAA,UACX,eAAe,iBAAiB;AAAA,UAChC,aAAa,eAAe;AAAA,UAC5B,kBAAkB,iBAAiB;AAAA,UACnC,QAAQ,OAAO,YAAY,MAAM;AAAA,UACjC;AAAA,UACA,WAAW,KAAK,MAAM,OAAO;AAAA,QAC/B;AAGA,eAAO,KAAK,wCAAwC,OAAO,SAAS,MAAM;AAC1E,eAAO,KAAK,sCAAsC,OAAO,UAAU,EAAE;AACrE,eAAO,KAAK,kDAAkD,SAAS,MAAM,OAAO,aAAa,EAAE;AACnG,eAAO,KAAK,sCAAsC,OAAO,WAAW,EAAE;AACtE,eAAO,KAAK,+CAA+C,OAAO,gBAAgB,EAAE;AACpF,eAAO,KAAK,gCAAgC;AAC5C,mBAAW,CAAC,MAAM,MAAM,KAAK,QAAQ;AACnC,iBAAO;AAAA,YACL,2BAA2B,IAAI,KAAK,OAAO,aAAa,IAAI,OAAO,KAAK,oBAAoB,OAAO,GAAG;AAAA,UACxG;AAAA,QACF;AACA,eAAO,KAAK,0CAA0C;AACtD,mBAAW,MAAM,iBAAiB;AAChC,iBAAO,KAAK,2BAA2B,GAAG,IAAI,KAAK,GAAG,aAAa,iBAAiB;AAAA,QACtF;AAEA,eAAO;AAAA,MACT;AAAA,IAEF;AAAA;AAAA;;;AC9yBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBO,SAAS,yBAAyB,UAAiD;AACxF,SAAO;AAAA,IACL,KAAK;AAAA,IACL;AAAA,IACA,MAAM;AAAA,IACN,aAAa;AAAA,IACb;AAAA,IACA,QAAQ;AAAA,IACR,gBAAgB;AAAA,EAClB;AACF;AASO,SAAS,yBAAyB,UAAiD;AACxF,SAAO;AAAA,IACL,KAAK;AAAA,IACL;AAAA,IACA,MAAM;AAAA,IACN,aAAa;AAAA,IACb;AAAA,IACA,QAAQ;AAAA,IACR,gBAAgB;AAAA,EAClB;AACF;AAkBO,SAAS,sBACd,MACA,QACU;AACV,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,eAAe,IAAI,aAAa,MAAM,QAAQ,yBAAyB,QAAQ,CAAC;AACtF,QAAM,eAAe,IAAI,aAAa,MAAM,QAAQ,yBAAyB,QAAQ,CAAC;AAEtF,QAAM,qBAAqB,IAAI,mBAAmB,CAAC,cAAc,YAAY,CAAC;AAC9E,QAAM,oBAAoB,wBAAwB;AAElD,SAAO,IAAI,SAAS,oBAAoB,CAAC,iBAAiB,GAAG,MAAM,MAAM;AAC3E;AAnFA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;;;ACPA;AAAA;AAAA;AAAA;AAAA,IAgEa;AAhEb;AAAA;AAAA;AACA;AAEA;AAEA;AACA;AAGA;AACA;AAsDO,IAAM,oBAAN,MAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAa7B,MAAM,SAAS,OAAgE;AAC7E,cAAM,EAAE,YAAY,MAAM,OAAO,IAAI;AACrC,cAAM,WAAqB,CAAC;AAE5B,YAAI,WAAW,WAAW,GAAG;AAC3B,iBAAO;AAAA,YACL,UAAU;AAAA,YACV,qBAAqB,CAAC;AAAA,YACtB,kBAAkB,CAAC;AAAA,YACnB;AAAA,UACF;AAAA,QACF;AAGA,cAAM,sBAAuD,CAAC;AAC9D,cAAM,mBAAoD,CAAC;AAE3D,mBAAW,KAAK,YAAY;AAC1B,cAAI,YAAY,EAAE,iBAAiB,GAAG;AACpC,gCAAoB,KAAK,CAAC;AAAA,UAC5B,WAAW,SAAS,EAAE,iBAAiB,GAAG;AACxC,6BAAiB,KAAK,CAAC;AAAA,UACzB,OAAO;AAEL,qBAAS,KAAK,0BAA0B,EAAE,iBAAiB,gBAAgB,EAAE,IAAI,EAAE;AAAA,UACrF;AAAA,QACF;AAIA,cAAM,WAAW,OAAO,YAAY;AACpC,cAAM,SAAS,oBAAoB,KAAK,CAAC,MAAM,EAAE,qCAAoC;AACrF,cAAM,SAAS,oBAAoB,KAAK,CAAC,MAAM,EAAE,qCAAoC;AAErF,YAAI,CAAC,QAAQ;AACX,iBAAO,MAAM,iEAAiE;AAC9E,8BAAoB,KAAK,yBAAyB,QAAQ,CAAC;AAAA,QAC7D;AACA,YAAI,CAAC,QAAQ;AACX,iBAAO,MAAM,iEAAiE;AAC9E,8BAAoB,KAAK,yBAAyB,QAAQ,CAAC;AAAA,QAC7D;AAEA,YAAI,oBAAoB,WAAW,GAAG;AACpC,mBAAS,KAAK,6BAA6B;AAC3C,iBAAO;AAAA,YACL,UAAU;AAAA,YACV,qBAAqB,CAAC;AAAA,YACtB,kBAAkB,CAAC;AAAA,YACnB;AAAA,UACF;AAAA,QACF;AAGA,YAAI;AAEJ,YAAI,oBAAoB,WAAW,GAAG;AAEpC,gBAAM,MAAM,MAAM,iBAAiB,OAAO,MAAM,QAAQ,oBAAoB,CAAC,CAAC;AAC9E,sBAAY;AACZ,iBAAO,MAAM,+CAA+C,oBAAoB,CAAC,EAAE,IAAI,EAAE;AAAA,QAC3F,OAAO;AAEL,iBAAO;AAAA,YACL,oDAAoD,oBAAoB,MAAM,gBAAgB,oBAAoB,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,UACjJ;AACA,sBAAY,MAAM,mBAAmB,eAAe,MAAM,QAAQ,mBAAmB;AAAA,QACvF;AAGA,cAAM,UAAwB,CAAC;AAG/B,cAAM,yBAAyB,CAAC,GAAG,gBAAgB,EAAE;AAAA,UAAK,CAAC,GAAG,MAC5D,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,QAC7B;AAEA,mBAAW,kBAAkB,wBAAwB;AACnD,cAAI;AACF,kBAAM,MAAM,MAAM,iBAAiB,OAAO,MAAM,QAAQ,cAAc;AAEtE,gBAAI,eAAe,OAAO,OAAO,IAAI,cAAc,YAAY;AAC7D,kBAAI,SAAS;AAGb,kBAAI,eAAe,WAAW;AAC5B,yBAAS,IAAI;AAAA,kBACX;AAAA,kBACA,eAAe;AAAA,kBACf,eAAe;AAAA,kBACf,eAAe;AAAA,gBACjB;AAAA,cACF;AAEA,sBAAQ,KAAK,MAAM;AACnB,qBAAO,MAAM,qCAAqC,eAAe,IAAI,EAAE;AAAA,YACzE,OAAO;AACL,uBAAS;AAAA,gBACP,WAAW,eAAe,IAAI;AAAA,cAChC;AAAA,YACF;AAAA,UACF,SAAS,GAAG;AACV,qBAAS,KAAK,iCAAiC,eAAe,IAAI,MAAM,CAAC,EAAE;AAAA,UAC7E;AAAA,QACF;AAGA,cAAM,WAAW,IAAI,SAAS,WAAW,SAAS,MAAM,MAAM;AAE9D,eAAO;AAAA,UACL,+CAA+C,oBAAoB,MAAM,qBAAqB,QAAQ,MAAM;AAAA,QAC9G;AAEA,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,kBAAkB;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrMA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6EO,SAAS,kBACd,mBACA,aACA,MACM;AACN,oBAAkB,IAAI,mBAAmB,EAAE,aAAa,KAAK,CAAC;AAC9D,SAAO,MAAM,mCAAmC,iBAAiB,GAAG,OAAO,KAAK,IAAI,MAAM,EAAE,EAAE;AAChG;AAQO,SAAS,uBAAuB,mBAA6D;AAClG,SAAO,kBAAkB,IAAI,iBAAiB,GAAG;AACnD;AAQO,SAAS,uBAAuB,mBAAoC;AACzE,SAAO,kBAAkB,IAAI,iBAAiB;AAChD;AAQO,SAAS,2BAA2B,mBAAsD;AAC/F,SAAO,kBAAkB,IAAI,iBAAiB,GAAG;AACnD;AAMO,SAAS,8BAAwC;AACtD,SAAO,MAAM,KAAK,kBAAkB,KAAK,CAAC;AAC5C;AAYA,eAAsB,8BAA6C;AACjE,SAAO,MAAM,yDAAyD;AAGtE,QAAM,CAAC,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/C;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,mBAAmB,MAAM;AAC/B,oBAAkB,OAAO,UAAU,OAAO;AAC1C,oBAAkB,OAAO,UAAU,OAAO;AAC1C,oBAAkB,cAAc,iBAAiB,OAAO;AAGxD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,MAAM,QAAQ,IAAI;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,oBAAkB,uBAAuB,gBAAgB,OAAO;AAChE,oBAAkB,yBAAyB,mBAAmB,OAAO;AACrE,oBAAkB,oBAAoB,uBAAuB,OAAO;AACpE,oBAAkB,qBAAqB,wBAAwB,OAAO;AAMtE,SAAO;AAAA,IACL,mCAAmC,kBAAkB,IAAI,gBAAgB,4BAA4B,EAAE,KAAK,IAAI,CAAC;AAAA,EACnH;AACF;AA+JO,SAAS,cAAc,MAAiD;AAC7E,MAAI,KAAK,WAAW,WAAW,GAAG;AAChC,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,QAAM,aAAa,KAAK,WAAW,CAAC;AACpC,QAAM,SAAS,WAAW,OAAO,YAAY;AAE7C,MAAI,OAAO,SAAS,QAAQ,GAAG;AAC7B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,SAAS,QAAQ,GAAG;AAC7B,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAyDO,SAAS,YAAY,MAAuB;AACjD,MAAI,eAAe,IAAkB,MAAM,4BAAyB,QAAO;AAE3E,SAAO,2BAA2B,IAAI,MAAM;AAC9C;AAWO,SAAS,SAAS,MAAuB;AAC9C,MAAI,eAAe,IAAkB,MAAM,sBAAsB,QAAO;AAExE,SAAO,2BAA2B,IAAI,MAAM;AAC9C;AAraA,IA6DM,mBA8RM,YA+BA,eAQC,gBA+CS;AAjbtB;AAAA;AAAA;AASA;AAWA;AAmiBoC;AASA,IAAAC;AASA,IAAAA;AA5gBpC,IAAM,oBAAoB,oBAAI,IAAoC;AA8R3D,IAAK,aAAL,kBAAKC,gBAAL;AACL,MAAAA,YAAA,SAAM;AACN,MAAAA,YAAA,SAAM;AACN,MAAAA,YAAA,gBAAa;AACb,MAAAA,YAAA,eAAY;AACZ,MAAAA,YAAA,kBAAe;AACf,MAAAA,YAAA,uBAAoB;AACpB,MAAAA,YAAA,yBAAsB;AAPZ,aAAAA;AAAA,OAAA;AA+BL,IAAK,gBAAL,kBAAKC,mBAAL;AACL,MAAAA,eAAA,eAAY;AACZ,MAAAA,eAAA,YAAS;AAFC,aAAAA;AAAA,OAAA;AAQL,IAAM,iBAAoD;AAAA,MAC/D,CAAC,eAAc,GAAG;AAAA,MAClB,CAAC,eAAc,GAAG;AAAA,MAClB,CAAC,6BAAqB,GAAG;AAAA,MACzB,CAAC,qCAAoB,GAAG;AAAA,MACxB,CAAC,0CAAuB,GAAG;AAAA,MAC3B,CAAC,0CAA4B,GAAG;AAAA,MAChC,CAAC,6CAA8B,GAAG;AAAA,IACpC;AAuCO,IAAe,mBAAf,MAA8D;AAAA;AAAA,MAEzD;AAAA;AAAA,MAGA;AAAA;AAAA,MAGA;AAAA;AAAA,MAGA;AAAA;AAAA,MAGH;AAAA;AAAA,MAGA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASP,YACE,MACA,QACA,cACA;AACA,aAAK,OAAO;AACZ,aAAK,SAAS;AACd,YAAI,cAAc;AAChB,eAAK,eAAe,aAAa;AACjC,eAAK,aAAa,aAAa;AAC/B,eAAK,YAAY,aAAa;AAC9B,eAAK,eAAe,aAAa;AAAA,QACnC;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAkBA,IAAc,cAAsB;AAClC,eAAO,KAAK,YAAY;AAAA,MAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,MAAgB,mBAAyC;AACvD,YAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,QAAQ;AAC9B,gBAAM,IAAI;AAAA,YACR;AAAA,UAEF;AAAA,QACF;AACA,eAAO,KAAK,KAAK,iBAAoB,KAAK,OAAO,YAAY,GAAG,KAAK,WAAW;AAAA,MAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,MAAgB,iBAAoB,MAAwB;AAC1D,YAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,QAAQ;AAC9B,gBAAM,IAAI;AAAA,YACR;AAAA,UAEF;AAAA,QACF;AACA,eAAO,KAAK,KAAK,iBAAoB,KAAK,OAAO,YAAY,GAAG,KAAK,aAAa,IAAI;AAAA,MACxF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAgBA,aAAa,OACX,MACA,QACA,cAC2B;AAC3B,cAAM,oBAAoB,aAAa;AAGvC,cAAM,iBAAiB,uBAAuB,iBAAiB;AAC/D,YAAI,gBAAgB;AAClB,iBAAO,MAAM,yDAAyD,iBAAiB,EAAE;AACzF,iBAAO,IAAI,eAAe,MAAM,QAAQ,YAAY;AAAA,QACtD;AAGA,eAAO;AAAA,UACL,mFAAmF,iBAAiB;AAAA,QACtG;AAEA,YAAI;AAGJ,cAAM,aAAa,CAAC,OAAO,OAAO,EAAE;AAEpC,mBAAW,OAAO,YAAY;AAE5B,cAAI;AACF,kBAAM,SAAS,MAAa,sCAAgB,iBAAiB,GAAG,GAAG;AACnE,4BAAgB,OAAO;AACvB,gBAAI,cAAe;AAAA,UACrB,SAAS,GAAG;AACV,mBAAO,MAAM,4BAA4B,iBAAiB,GAAG,GAAG,KAAK,CAAC;AAAA,UACxE;AAGA,cAAI;AACF,kBAAM,SAAS,MAAa,gCAAa,iBAAiB,GAAG,GAAG;AAChE,4BAAgB,OAAO;AACvB,gBAAI,cAAe;AAAA,UACrB,SAAS,GAAG;AACV,mBAAO,MAAM,yBAAyB,iBAAiB,GAAG,GAAG,KAAK,CAAC;AAAA,UACrE;AAGA,cAAI;AACF,kBAAM,SAAS,MAAa,gBAAK,iBAAiB,GAAG,GAAG;AACxD,4BAAgB,OAAO;AACvB,gBAAI,cAAe;AAAA,UACrB,SAAS,GAAG;AACV,mBAAO,MAAM,yBAAyB,iBAAiB,GAAG,GAAG,KAAK,CAAC;AAAA,UACrE;AAEA,cAAI,cAAe;AAAA,QACrB;AAEA,YAAI,CAAC,eAAe;AAClB,gBAAM,IAAI,MAAM,gDAAgD,iBAAiB,EAAE;AAAA,QACrF;AAEA,eAAO,IAAI,cAAc,MAAM,QAAQ,YAAY;AAAA,MACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MA2BA,MAAM,iBAAiB,QAAyC;AAC9D,cAAM,IAAI,MAAM,GAAG,KAAK,YAAY,IAAI,sCAAsC;AAAA,MAChF;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,kBAAkB,QAAuC;AAAA,MAEzD;AAAA,IACF;AAAA;AAAA;;;AC7nBA;AAAA,EAIE;AAAA,EACA;AAAA,EACA,kBAAAC;AAAA,EACA,eAAAC;AAAA,OACK;AA8EP,SAAS,0BAA0B,GAAW;AAC5C,SAAO,KAAK,MAAM,KAAK,OAAO,IAAI,KAAK,OAAO,IAAI,KAAK,OAAO,IAAI,CAAC;AACrE;AAgxBA,eAAsB,kBACpB,UAC4C;AAC5C,SAAO,MAAM,iCAAiC,QAAQ,EAAE;AACxD,QAAM,QAAQ,MAAMC;AAAA,IAClBC,aAAY,QAAQ;AAAA,oBACR,QAAQ,IAAI;AAAA,EAC1B;AAEA,QAAM,KAAK,QAAQ,CAAC,QAAQ;AAC1B,WAAO,MAAM,sBAAuB,IAAI,EAAE,EAAE;AAAA,EAC9C,CAAC;AAED,SAAO;AACT;AAUA,eAAsB,UAAU,UAAkB,SAAiB,QAAgB;AACjF,SAAO,MAAM,iBAAiB,OAAO,KAAK;AAC1C,QAAM,QAAQ,SAAS,OAAO;AAC9B,QAAM,WAAWA,aAAY,QAAQ;AACrC,QAAM,OAAO,MAAM,SAAS,IAAS;AAAA,IACnC,QAAQ;AAAA,IACR;AAAA,IACA,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aAAa,CAAC;AAAA,IACd,MAAM;AAAA,IACN;AAAA,IACA,KAAK;AAAA,EACP,CAAC;AACD,SAAO;AACT;AAEA,eAAsB,UAAU,KAAU;AACxC,QAAM,QAAQ,MAAM,OAAO,IAAI,QAAQ,IAAI,IAAI;AAC/C,SAAO,MAAMA,aAAY,IAAI,MAAM,EAAE,IAAS;AAAA,IAC5C,GAAG;AAAA,IACH,MAAM,MAAM;AAAA,EACd,CAAC;AACH;AAEA,eAAsB,OAAO,UAAkB,SAAiB;AAC9D,QAAM,QAAQ,SAAS,OAAO;AAC9B,QAAM,WAAWA,aAAY,QAAQ;AACrC,SAAO,SAAS,IAAS,KAAK;AAChC;AAEA,eAAsB,kBAAkB,UAAkB,QAAgB,OAAe;AAGvF,UAAQ,SAAS,KAAK;AACtB,QAAM,WAAWA,aAAY,QAAQ;AACrC,QAAM,MAAM,MAAM,SAAS,IAAS,KAAK;AACzC,MAAI,cAAc,IAAI,YAAY,OAAO,CAAC,aAAa;AACrD,WAAO,WAAW;AAAA,EACpB,CAAC;AACD,SAAO,SAAS,IAAS,GAAG;AAC9B;AA2BA,eAAsB,eAAe,WAAmB,SAAiB;AACvE,QAAM,KAAKA,aAAY,SAAS;AAEhC,QAAM,SAAS,MAAM,GAAG,MAAe,WAAW;AAAA,IAChD,UAAU;AAAA,IACV,QAAQ;AAAA;AAAA,EAEV,CAAC;AAKD,SAAO;AACT;AAaA,eAAsB,gCAAgC,UAAkB,QAAsB;AAC5F,SAAO,MAAM;AAAA;AAAA,EAEb,KAAK,UAAU,MAAM,CAAC;AAAA,CACvB;AAEC,QAAM,KAAKA,aAAY,QAAQ;AAC/B,QAAM,MAAM,MAAM,6BAA6B,QAAQ;AAEvD,SAAO,MAAM,GAAG,IAAkB;AAAA,IAChC,GAAG;AAAA,IACH,MAAO,IAAY;AAAA,EACrB,CAAC;AACH;AAEA,SAAS,aACP,KAsBA;AACA,SAAO,SAAS,OAAO,IAAI,QAAQ,QAAQ,IAAI,QAAQ;AACzD;AAvgCA,IA2Fa;AA3Fb;AAAA;AAAA;AAWA;AACA;AAEA;AASA;AACA;AACA;AAGA;AAEA;AACA;AACA;AA2DO,IAAM,WAAN,MAA4C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWR,YAAY,IAAY,YAA4C,SAA4B;AAC9F,aAAK,KAAK;AACV,cAAM,SAASA,aAAY,KAAK,EAAE;AAClC,aAAK,WAAW;AAChB,aAAK,KAAK,WAAW;AACrB,aAAK,kBAAkB;AAIvB,aAAK,cAAc,IAAI,YAAY,KAAK,UAAU,KAAK,QAAQ;AAAA,MACjE;AAAA,MAEO,cAAsB;AAC3B,eAAO,KAAK;AAAA,MACd;AAAA,MAEA,MAAa,gBAAqC;AAChD,cAAM,aACJ,MAAM,KAAK,GAAG,KAAK;AAAA,UACjB,UAAU;AAAA,YACR;AAAA,UACF;AAAA,UACA,OAAO;AAAA,QACT,CAAC,GACD,KAAK;AAEP,eAAO;AAAA,UACL;AAAA,UACA,iBAAiB;AAAA,QACnB;AAAA,MACF;AAAA,MAEA,MAAa,sBAAsB,QAAgB,GAAG;AACpD,gBACE,MAAM,KAAK,GAAG,MAAM,uBAAuB;AAAA,UACzC;AAAA,QACF,CAAC,GACD,KAAK,IAAI,CAAC,MAAM;AAChB,gBAAM,MAAM;AAAA,YACV,UAAU,KAAK;AAAA,YACf,QAAQ,EAAE;AAAA,YACV,OAAO,EAAE;AAAA,YACT,KAAK,EAAE;AAAA,UACT;AACA,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,MAEA,MAAa,oBACX,UAKI;AAAA,QACF,KAAK;AAAA,QACL,MAAM,OAAO;AAAA,QACb,OAAO;AAAA,QACP,MAAM;AAAA,MACR,GACA;AACA,gBACE,MAAM,KAAK,GAAG,MAAM,OAAO;AAAA,UACzB,UAAU,QAAQ;AAAA,UAClB,QAAQ,QAAQ;AAAA,UAChB,OAAO,QAAQ;AAAA,UACf,MAAM,QAAQ,QAAQ,QAAQ;AAAA,QAChC,CAAC,GACD,KAAK,IAAI,CAAC,MAAM;AAChB,iBAAO,GAAG,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,MACA,MAAa,eAAe,IAAoC;AAC9D,cAAM,OAAO,MAAM,KAAK,GAAG,QAAkB;AAAA,UAC3C,MAAM;AAAA,UACN,cAAc;AAAA,QAChB,CAAC;AACD,cAAM,MAAmB,CAAC;AAC1B,aAAK,KAAK,QAAQ,CAAC,MAAM;AAEvB,cAAI,aAAa,CAAC,GAAG;AACnB,gBAAI,EAAE,OAAO,EAAE,IAAI,KAAK;AACtB,kBAAI,KAAKF,aAAY,EAAE,IAAI,GAAG,CAAC;AAAA,YACjC,OAAO;AACL,qBAAO,KAAK,2BAA2B,EAAE,EAAE;AAC3C,kBAAI,KAAKD,gBAAe,CAAC;AAAA,YAC3B;AAAA,UACF,OAAO;AACL,mBAAO,KAAK,2BAA2B,KAAK,UAAU,CAAC,CAAC;AACxD,gBAAI,KAAKA,gBAAe,CAAC;AAAA,UAC3B;AAAA,QACF,CAAC;AACD,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA,MAKA,MAAa,eAAe;AAC1B,cAAM,CAAC,KAAK,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,WAElC,MAAM,KAAK,GAAG,MAAM,OAAO;AAAA,YACzB,UAAU;AAAA,YACV,OAAO;AAAA,YACP,cAAc;AAAA,UAChB,CAAC,GACD,KAAK,CAAC,EAAE;AAAA,WAER,MAAM,KAAK,GAAG,MAAM,OAAO;AAAA,YACzB,OAAO;AAAA,YACP,YAAY;AAAA,YACZ,UAAU;AAAA,UACZ,CAAC,GACD,KAAK,CAAC,EAAE;AAAA,QACZ,CAAC;AAED,eAAO;AAAA,UACL;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAa,WAAW,IAAY;AAGlC,cAAM,MAAM,MAAM,KAAK,SAAS,IAAc,EAAE;AAChD,YAAI,CAAC,IAAI,WAAW,EAAE,IAAI,gCAA2B;AACnD,gBAAM,IAAI,MAAM,oBAAoB,EAAE,gBAAgB,KAAK,EAAE,+BAA+B;AAAA,QAC9F;AAGA,YAAI;AACF,gBAAM,cAAc,MAAM,KAAK,eAAe,EAAE;AAChD,gBAAM,UAAU,MAAM,QAAQ;AAAA,YAC5B,YAAY,KAAK,IAAI,OAAO,WAAW;AACrC,oBAAM,QAAQ,OAAO;AACrB,oBAAM,KAAK,kBAAkB,IAAI,KAAK;AAAA,YACxC,CAAC;AAAA,UACH;AAGA,kBAAQ,QAAQ,CAAC,QAAQ,UAAU;AACjC,gBAAI,OAAO,WAAW,YAAY;AAChC,oBAAM,QAAQ,YAAY,KAAK,KAAK,EAAE;AACtC,qBAAO,MAAM,yBAAyB,EAAE,aAAa,KAAK,KAAK,OAAO,MAAM,EAAE;AAAA,YAChF;AAAA,UACF,CAAC;AAAA,QACH,SAAS,OAAO;AACd,iBAAO,MAAM,uBAAuB,EAAE,eAAe,KAAK,EAAE;AAAA,QAE9D;AAEA,eAAO,KAAK,SAAS,OAAO,GAAG;AAAA,MACjC;AAAA,MAEA,MAAa,0BAA0B,IAAc;AACnD,eAAO,MAAM,GAAG,KAAK,IAAI,CAAC;AAC1B,cAAM,QAAQ,MAAM,KAAK,GAAG,QAAkB;AAAA,UAC5C,MAAM;AAAA,UACN,cAAc;AAAA,QAChB,CAAC;AACD,cAAM,MAAoC,CAAC;AAC3C,cAAM,KAAK,QAAQ,CAAC,MAAM;AACxB,cAAI,aAAa,CAAC,GAAG;AACnB,gBAAI,EAAE,EAAE,IAAI,EAAE,IAAK;AAAA,UACrB;AAAA,QACF,CAAC;AAED,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,cAAc,KAAa,WAAoB;AACnD,cAAM,SAAS,GAAU;AACzB,cAAM,QAAQ,YAAY,YAAY;AAEtC,cAAM,QAAwC,MAAM,KAAK,GAAG,MAAM,OAAO;AAAA,UACvE,OAAO,KAAK,KAAK,QAAQ,CAAC;AAAA,UAC1B,UAAU;AAAA,UACV,YAAY;AAAA,QACd,CAAC;AAED,cAAM,aAAa,QAAQ,MAAM,KAAK;AAEtC,cAAM,QAAwC,MAAM,KAAK,GAAG,MAAM,OAAO;AAAA,UACvE,OAAO;AAAA,UACP,UAAU,MAAM;AAAA,QAClB,CAAC;AAGD,YAAI,QAAQ,MAAM;AAClB,gBAAQ,MAAM,OAAO,MAAM,IAAI;AAE/B,cAAM,MAAM,MACT,KAAK,CAAC,GAAG,MAAM;AACd,gBAAM,IAAI,KAAK,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,EAAE,MAAM,GAAG;AACtD,cAAI,MAAM,GAAG;AACX,mBAAO,KAAK,OAAO,IAAI;AAAA,UACzB,OAAO;AACL,mBAAO;AAAA,UACT;AAAA,QACF,CAAC,EACA,IAAI,CAAC,MAAM;AACV,iBAAO;AAAA,YACL,UAAU,KAAK;AAAA,YACf,QAAQ,EAAE;AAAA,YACV,KAAK,EAAE;AAAA,UACT;AAAA,QACF,CAAC;AAEH,cAAM,MAAM;AAAA,EAAW,MAAM,KAAK,IAAI,CAAC,MAAM,IAAK,EAAE,EAAE,IAAI,EAAE,GAAG;AAAA,CAAI,CAAC;AAAA;AAAA;AAAA,EAE9D,MAAM,KAAK,IAAI,CAAC,MAAM,IAAK,EAAE,EAAE,IAAI,EAAE,GAAG;AAAA,CAAI,CAAC;AAEnD,eAAO,MAAM,WAAW,KAAK,+BAA+B,GAAG;AAAA;AAAA,IAAU,GAAG;AAE5E,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,kBAAyC;AAC7C,cAAM,MAAM,MAAM,6BAA6B,KAAK,EAAE;AACtD,YAAI,KAAK;AACP,iBAAO;AAAA,QACT,OAAO;AACL,gBAAM,IAAI,MAAM,0CAA0C,KAAK,EAAE,EAAE;AAAA,QACrE;AAAA,MACF;AAAA,MAEA,MAAM,mBAAmB,KAAmD;AAC1E,eAAO,MAAM,aAAa,KAAK,UAAU,GAAG,CAAC,EAAE;AAE/C,YAAI;AACF,iBAAO,MAAM,gCAAgC,KAAK,IAAI,GAAG;AAAA,QAC3D,SAAS,OAAO;AACd,iBAAO,MAAM,8CAA8C,KAAK,EAAE;AAClE,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,MAEA,MAAM,cAAc,QAAgB,KAAgD;AAClF,YAAI,CAAC,KAAK;AACR,gBAAM,IAAI,MAAM,oEAAoE,MAAM,EAAE;AAAA,QAC9F;AAEA,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,YAAY,OAEpC,QAAQ,CAAC,SAAS;AAClB,mBAAO,MAAM,aAAa,KAAK,UAAU,KAAK,GAAG,CAAC,SAAS,KAAK,UAAU,GAAG,CAAC,EAAE;AAChF,iBAAK,MAAM;AACX,mBAAO;AAAA,UACT,CAAC;AACD,iBAAO,EAAE,IAAI,MAAM,IAAI,QAAQ,KAAK,OAAO,KAAK;AAAA,QAClD,SAAS,OAAO;AACd,iBAAO,MAAM,0CAA0C,MAAM,IAAI,KAAK;AACtE,gBAAM,IAAI,MAAM,0CAA0C,MAAM,EAAE;AAAA,QACpE;AAAA,MACF;AAAA,MAEA,MAAM,eAAe,QAA0D;AAC7E,cAAM,MAAM,MAAM,eAAe,KAAK,IAAI,MAAM;AAChD,YAAI,KAAK;AACP,iBAAO;AAAA,QACT,OAAO;AACL,gBAAM,IAAI,MAAM,gCAAgC,KAAK,EAAE,IAAI,MAAM,EAAE;AAAA,QACrE;AAAA,MACF;AAAA,MAEA,MAAM,oBAAoB,SAAmD;AAC3E,YAAI,QAAQ,WAAW,GAAG;AACxB,iBAAO,oBAAI,IAAI;AAAA,QACjB;AAEA,cAAM,SAAS,MAAM,KAAK,GAAG,MAAe,WAAW;AAAA,UACrD,MAAM;AAAA,UACN,cAAc;AAAA,QAChB,CAAC;AAED,cAAM,aAAa,oBAAI,IAAsB;AAG7C,mBAAW,UAAU,SAAS;AAC5B,qBAAW,IAAI,QAAQ,CAAC,CAAC;AAAA,QAC3B;AAGA,mBAAW,OAAO,OAAO,MAAM;AAC7B,gBAAM,SAAS,IAAI;AACnB,gBAAM,UAAU,IAAI,OAAO;AAC3B,cAAI,WAAW,WAAW,IAAI,MAAM,GAAG;AACrC,uBAAW,IAAI,MAAM,EAAG,KAAK,OAAO;AAAA,UACtC;AAAA,QACF;AAEA,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,gBAAmC;AACvC,cAAM,SAAS,MAAM,KAAK,GAAG,QAAQ;AAAA,UACnC,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,cAAc;AAAA,QAChB,CAAC;AACD,eAAO,OAAO,KAAK,IAAI,CAAC,QAAQ,IAAI,EAAE;AAAA,MACxC;AAAA,MAEA,MAAM,aACJ,QACA,OACA,WACgC;AAChC,eAAO,MAAM;AAAA,UACX,KAAK;AAAA,UACL;AAAA,UACA;AAAA,WACC,MAAM,KAAK,gBAAgB,GAAG,YAAY;AAAA,UAC3C;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,kBAAkB,QAAgB,OAA+C;AACrF,eAAO,MAAM,kBAAkB,KAAK,IAAI,QAAQ,KAAK;AAAA,MACvD;AAAA,MAEA,MAAM,UAAU,MAAc,QAAgD;AAC5E,eAAO,MAAM,UAAU,KAAK,IAAI,MAAM,MAAM;AAAA,MAC9C;AAAA,MAEA,MAAM,OAAO,OAA2E;AACtF,eAAO,MAAM,OAAO,KAAK,IAAI,KAAK;AAAA,MACpC;AAAA,MAEA,MAAM,UAAU,KAA0C;AACxD,YAAI,IAAI,WAAW,KAAK,IAAI;AAC1B,gBAAM,IAAI,MAAM,OAAO,KAAK,UAAU,GAAG,CAAC,8BAA8B,KAAK,EAAE,EAAE;AAAA,QACnF;AAEA,eAAO,MAAM,UAAU,GAAG;AAAA,MAC5B;AAAA,MAEA,MAAM,oBAAgE;AACpE,eAAO,kBAAkB,KAAK,EAAE;AAAA,MAClC;AAAA,MAEA,MAAM,QACJ,YACA,OACA,MACA,QACA,MACA,SACA,MAAiBA,gBAAe,GACN;AAC1B,YAAI;AACF,gBAAM,OAAO,MAAM,UAAU,KAAK,IAAI,YAAY,OAAO,MAAM,QAAQ,MAAM,SAAS,GAAG;AACzF,cAAI,KAAK,IAAI;AAEX,gBAAK,KAAa,oBAAoB;AACpC,qBAAO;AAAA,gBACL,2DACG,KAAa,iBAChB;AAAA,cACF;AACA,qBAAO;AAAA,gBACL,QAAQ,OAAO;AAAA,gBACf,SAAS,6CAA8C,KAAa,iBAAiB;AAAA,gBACrF,IAAI,KAAK;AAAA,cACX;AAAA,YACF;AACA,mBAAO;AAAA,cACL,QAAQ,OAAO;AAAA,cACf,SAAS;AAAA,cACT,IAAI,KAAK;AAAA,YACX;AAAA,UACF,OAAO;AACL,mBAAO;AAAA,cACL,QAAQ,OAAO;AAAA,cACf,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF,SAAS,GAAG;AACV,gBAAM,MAAM;AACZ,iBAAO;AAAA,YACL,mBAAmB,IAAI,IAAI;AAAA,WAAe,IAAI,MAAM;AAAA,YAAgB,IAAI,OAAO;AAAA,UACjF;AACA,iBAAO;AAAA,YACL,QAAQ,OAAO;AAAA,YACf,SAAS,gCAAiC,EAAiB,UAAU,IAAI,OAAO;AAAA,UAClF;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,aACJ,IACA,SAC0D;AAG1D,eAAO,MAAM,KAAK,GAAG,IAAO,IAAI,OAAO;AAAA,MACzC;AAAA,MAEA,MAAM,cACJ,KACA,UAAuC,CAAC,GACe;AAEvD,eAAO,MAAM,KAAK,GAAG,QAAW;AAAA,UAC9B,GAAG;AAAA,UACH,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA;AAAA;AAAA;AAAA,MAMA,sBAAsB,IAAoD;AACxE,eAAO,MAAM,2CAA2C,EAAE,EAAE;AAE5D,YAAI,MAAM,IAAI;AACZ,gBAAM,WAA0C;AAAA,YAC9C,KAAK;AAAA,YACL;AAAA,YACA,MAAM;AAAA,YACN,aAAa;AAAA,YACb;AAAA,YACA,QAAQ,KAAK;AAAA,YACb,gBAAgB;AAAA;AAAA,UAClB;AACA,iBAAO,QAAQ,QAAQ,QAAQ;AAAA,QACjC,OAAO;AACL,iBAAO,KAAK,GAAG,IAAI,EAAE;AAAA,QACvB;AAAA,MACF;AAAA,MAEA,MAAM,6BAAuE;AAC3E,cAAM,SAAS,+DAA2C;AAC1D,cAAM,SAAS,MAAM,KAAK,GAAG,QAAuC;AAAA,UAClE,UAAU;AAAA,UACV,QAAQ,GAAG,MAAM;AAAA,UACjB,cAAc;AAAA,QAChB,CAAC;AACD,eAAO,OAAO,KAAK,IAAI,CAAC,QAAQ,IAAI,GAAI;AAAA,MAC1C;AAAA,MAEA,MAAM,sBAAsB,MAAoD;AAC9E,eAAO,MAAM,0CAA0C,KAAK,GAAG,EAAE;AAEjE,eAAO,KAAK,SAAS,IAAI,IAAI,EAAE,KAAK,MAAM;AAAA,QAAC,CAAC;AAAA,MAC9C;AAAA,MACA,yBAAyB,IAAY,MAAoD;AACvF,eAAO,MAAM,4CAA4C,EAAE,EAAE;AAE7D,eAAO,MAAM,KAAK,UAAU,IAAI,CAAC;AACjC,eAAO,QAAQ,QAAQ;AAAA,MACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWA,MAAM,gBAAgB,MAAkD;AACtE,YAAI;AACF,gBAAM,gBAAgB,MAAM,KAAK,2BAA2B;AAE5D,cAAI,cAAc,WAAW,GAAG;AAE9B,mBAAO;AAAA,cACL;AAAA,YACF;AACA,mBAAO,sBAAsB,MAAM,IAAI;AAAA,UACzC;AAGA,gBAAM,YAAY,IAAI,kBAAkB;AACxC,gBAAM,EAAE,UAAU,qBAAqB,kBAAkB,SAAS,IAChE,MAAM,UAAU,SAAS;AAAA,YACvB,YAAY;AAAA,YACZ;AAAA,YACA,QAAQ;AAAA,UACV,CAAC;AAGH,qBAAW,WAAW,UAAU;AAC9B,mBAAO,KAAK,uBAAuB,OAAO,EAAE;AAAA,UAC9C;AAEA,cAAI,CAAC,UAAU;AAEb,mBAAO,MAAM,6DAA6D;AAC1E,mBAAO,sBAAsB,MAAM,IAAI;AAAA,UACzC;AAEA,iBAAO;AAAA,YACL,4CAA4C,oBAAoB,MAAM,qBAAqB,iBAAiB,MAAM;AAAA,UACpH;AACA,iBAAO;AAAA,QACT,SAAS,GAAG;AACV,iBAAO,MAAM,wCAAwC,CAAC,EAAE;AACxD,gBAAM;AAAA,QACR;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAmBQ,gBAAgD;AAAA,MAEjD,kBAAkB,OAAsC;AAC7D,aAAK,gBAAgB;AAAA,MACvB;AAAA,MAEA,MAAa,iBAAiB,OAAwC;AACpE,cAAM,IAAI,MAAM,KAAK,gBAAgB;AAErC,YAAI;AACF,gBAAM,YAAY,MAAM,KAAK,gBAAgB,CAAC;AAC9C,cAAI,KAAK,eAAe;AACtB,sBAAU,kBAAkB,KAAK,aAAa;AAC9C,iBAAK,gBAAgB;AAAA,UACvB;AACA,iBAAO,UAAU,iBAAiB,KAAK;AAAA,QACzC,SAAS,GAAG;AACV,iBAAO,MAAM,4CAA4C,CAAC,EAAE;AAC5D,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,MAEA,MAAa,sBACX,UAGI;AAAA,QACF,OAAO;AAAA,QACP,KAAK;AAAA,MACP,GACA,QAC6B;AAC7B,YAAI;AAEJ,YAAI,QAAQ,QAAQ,QAAQ;AAC1B,gBAAM,IAAI,MAAM,KAAK,gBAAgB;AAErC,sBAAY;AACZ,cAAI;AACF,kBAAM,aAAa,MAAM,EAAE,0BAA0B,GAAG,QAAQ,KAAK,CAAC,MAAM;AAC1E,qBAAO,EAAE,aAAa,KAAK;AAAA,YAC7B,CAAC;AACD,wBAAY,YAAY,UAAU,GAAG;AAAA,UACvC,QAAQ;AACN,wBAAY;AAAA,UACd;AAAA,QACF,WAAW,QAAQ,QAAQ,UAAU;AACnC,gBAAM,SAAS,MAAM,WAAW,cAAc,KAAK,EAAE,IAAI,MAAM,KAAK,aAAa,CAAC;AAClF,sBAAY,KAAK,MAAM,OAAO,MAAM,KAAK,OAAO,KAAK,OAAO,OAAO,OAAO,IAAI;AAAA,QAEhF,OAAO;AACL,sBAAY,QAAQ;AAAA,QACtB;AAEA,YAAI,QAAgD,CAAC;AACrD,YAAI,OAAe;AACnB,YAAI,gBAAwB;AAC5B,YAAI,WAAmB;AAEvB,eAAO,MAAM,SAAS,QAAQ,SAAS,aAAa,eAAe;AACjE,kBAAQ,MAAM,KAAK,cAAc,WAAW,OAAO,QAAQ,KAAK;AAChE,0BAAgB;AAChB,qBAAW,MAAM;AAEjB,iBAAO,MAAM,SAAS,MAAM,MAAM,wBAAwB;AAE1D,cAAI,QAAQ;AACV,oBAAQ,MAAM,OAAO,MAAM;AAC3B,mBAAO,MAAM,eAAe,MAAM,MAAM,WAAW;AAAA,UACrD;AAEA,kBAAQ;AAAA,QACV;AAEA,cAAM,gBAIA,CAAC;AAEP,eAAO,cAAc,SAAS,QAAQ,SAAS,MAAM,SAAS,GAAG;AAC/D,gBAAM,QAAQ,0BAA0B,MAAM,MAAM;AACpD,gBAAM,OAAO,MAAM,OAAO,OAAO,CAAC,EAAE,CAAC;AACrC,wBAAc,KAAK,IAAI;AAAA,QACzB;AAEA,eAAO,cAAc,IAAI,CAAC,MAAM;AAC9B,iBAAO;AAAA,YACL,UAAU,KAAK;AAAA,YACf,QAAQ,EAAE;AAAA,YACV,mBAAmB;AAAA,YACnB,iBAAiB,KAAK;AAAA,YACtB,KAAK,EAAE;AAAA,YACP,QAAQ;AAAA,UACV;AAAA,QACF,CAAC;AAAA,MACH;AAAA;AAAA,MAGA,MAAa,YAAY,OAA+B;AACtD,eAAO,IAAI,aAAa,KAAK,EAAE,qBAAqB,KAAK,GAAG;AAG5D,YAAI;AAEJ,YAAI;AAEF,4BAAkB,MAAM,KAAK,GAAG,KAAK;AAAA,YACnC,UAAU;AAAA,cACR,SAAS;AAAA,cACT,eAAe,EAAE,QAAQ,KAAK,KAAK,KAAK;AAAA,YAC1C;AAAA,UACF,CAAC;AACD,iBAAO,IAAI,aAAa,KAAK,EAAE,2CAA2C;AAAA,QAC5E,SAAS,YAAY;AACnB,iBAAO;AAAA,YACL,aAAa,KAAK,EAAE;AAAA,YACpB;AAAA,UACF;AAGA,gBAAM,iBAAiB,MAAM,KAAK,GAAG,KAAK;AAAA,YACxC,UAAU;AAAA,cACR,SAAS;AAAA,YACX;AAAA,UACF,CAAC;AAED,iBAAO;AAAA,YACL,aAAa,KAAK,EAAE,eAAe,eAAe,KAAK,MAAM;AAAA,UAC/D;AAEA,4BAAkB;AAAA,YAChB,MAAM,eAAe,KAAK,OAAO,CAAC,QAAQ;AAExC,oBAAM,YAAY,KAAK,UAAU,GAAG,EAAE,YAAY;AAClD,oBAAM,QAAQ,UAAU,SAAS,MAAM,YAAY,CAAC;AACpD,kBAAI,OAAO;AACT,uBAAO,IAAI,aAAa,KAAK,EAAE,qCAAqC,IAAI,GAAG,EAAE;AAAA,cAC/E;AACA,qBAAO;AAAA,YACT,CAAC;AAAA,UACH;AAAA,QACF;AAEA,eAAO;AAAA,UACL,aAAa,KAAK,EAAE,WAAW,gBAAgB,KAAK,MAAM;AAAA,QAC5D;AAEA,YAAI,gBAAgB,KAAK,WAAW,GAAG;AAErC,gBAAM,qBAAqB,MAAM,KAAK,GAAG,KAAK;AAAA,YAC5C,UAAU;AAAA,cACR,SAAS;AAAA,YACX;AAAA,YACA,OAAO;AAAA;AAAA,UACT,CAAC;AAED,iBAAO;AAAA,YACL,aAAa,KAAK,EAAE;AAAA,YACpB,mBAAmB,KAAK,IAAI,CAAC,OAAO;AAAA,cAClC,IAAI,EAAE;AAAA,cACN,SAAU,EAAU;AAAA,cACpB,eAAgB,EAAU,OAAO,OAAO,KAAM,EAAU,IAAI,IAAI;AAAA,cAChE,aAAc,EAAU;AAAA,cACxB,SAAS;AAAA,YACX,EAAE;AAAA,UACJ;AAAA,QACF;AAEA,cAAM,aAAoB,CAAC;AAE3B,mBAAW,MAAM,gBAAgB,MAAM;AACrC,gBAAM,QAAQ,MAAM,KAAK,GAAG,KAAK;AAAA,YAC/B,UAAU;AAAA,cACR,SAAS;AAAA,cACT,qBAAqB,EAAE,KAAK,CAAC,GAAG,GAAG,EAAE;AAAA,YACvC;AAAA,UACF,CAAC;AAED,iBAAO;AAAA,YACL,aAAa,KAAK,EAAE,sBAAsB,GAAG,GAAG,cAAc,MAAM,KAAK,MAAM;AAAA,UACjF;AACA,qBAAW,KAAK,GAAG,MAAM,IAAI;AAAA,QAC/B;AAEA,eAAO,IAAI,aAAa,KAAK,EAAE,wBAAwB,WAAW,MAAM,EAAE;AAC1E,eAAO;AAAA,MACT;AAAA,MAEA,MAAa,KACX,SACyC;AACzC,eAAO,KAAK,GAAG,KAAK,OAAO;AAAA,MAC7B;AAAA,IACF;AAAA;AAAA;;;ACz0BA,OAAOI,aAAY;AALnB,IAoBa,kBAIE,iBA+CF;AAvEb,IAAAC,oBAAA;AAAA;AAAA;AAGA;AACA;AAEA;AACA;AACA;AAYO,IAAM,mBAAmB;AAIhC,IAAe,kBAAf,MAA+B;AAAA,MACtB;AAAA,MACG;AAAA,MACA;AAAA,MACA,gBAAyB;AAAA,MAEhB,kBAA0B;AAAA,MAC7C,IAAc,sBAAsB;AAClC,eAAOC,oBAAmB,KAAK,eAAe;AAAA,MAChD;AAAA,MAIA,MAAa,qBAAiD;AAC5D,eAAO,KAAK,6BAA6B;AAGzC,cAAM,UAAU,MAAM,KAAK,IAAI,QAAyB;AAAA,UACtD,UAAU,KAAK;AAAA,UACf,QAAQ,KAAK,kBAAkB;AAAA,UAC/B,cAAc;AAAA,QAChB,CAAC;AAED,cAAM,MAAM,QAAQ,KAAK,IAAI,CAAC,QAAQ;AACpC,iBAAO,IAAI;AAAA,QACb,CAAC;AAGD,eAAO;AAAA,MACT;AAAA,MAEU,aAAa,SAAkC;AACvD,YAAI,QAAQ,SAAS,OAAO;AAC1B,iBAAO,GAAG,KAAK,eAAe,IAAI,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAAA,QACrE,OAAO;AACL,iBAAO,GAAG,KAAK,eAAe,IAAI,QAAQ,QAAQ;AAAA,QACpD;AAAA,MACF;AAAA,MAEA,IAAW,QAAiB;AAC1B,eAAO,KAAK;AAAA,MACd;AAAA,MACO,YAA6B;AAClC,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAEO,IAAM,qBAAN,MAAM,4BACH,gBAEV;AAAA;AAAA,MAEU;AAAA,MACA;AAAA,MAEA,YAAY,SAAiB,MAAuB;AAC1D,cAAM;AACN,aAAK,MAAM;AACX,aAAK,QAAQ;AAAA,MAEf;AAAA,MAEA,MAAM,OAAsB;AAC1B,cAAM,SAAS,mBAAmB,KAAK,GAAG;AAC1C,aAAK,MAAM,IAAI;AAAA,UACb,IAAI,0BAA0B,QAAQ,IAAI,qBAAqB;AAAA,UAC/D,oBAAoB;AAAA,QACtB;AACA,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,IAAI,IAAqB,gBAAgB;AAChE,eAAK,OAAO;AACZ,eAAK,eAAe,KAAK,IAAI,QAAQ;AAAA,YACnC,OAAO;AAAA,YACP,MAAM;AAAA,YACN,cAAc;AAAA,UAChB,CAAC;AACD,eAAK,gBAAgB;AACrB;AAAA,QACF,SAAS,GAAG;AACV,gBAAM,IAAI,MAAM,4CAA4C,KAAK,UAAU,CAAC,CAAC,EAAE;AAAA,QACjF;AAAA,MACF;AAAA,MAEA,aAAoB,QAAQ,SAAiB,MAAoD;AAC/F,cAAM,MAAM,IAAI,oBAAmB,SAAS,IAAI;AAChD,cAAM,IAAI,KAAK;AACf,eAAO;AAAA,MACT;AAAA,MAEO,aAAa,GAAqC;AAGvD,aAAK,KAAK,aAAa,GAAG,UAAU,CAAC;AAAA,MACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWA,MAAa,iBAAiB,OAAwC;AACpE,cAAM,WAA2B,CAAC;AAGlC,cAAM,iBAAiB,MAAM,KAAK,MAAM,kBAAkB;AAC1D,cAAM,mBAAmB,eAAe;AAAA,UACtC,CAAC,MAAM,EAAE,iBAAiB,eAAe,EAAE,sBAAsB,KAAK;AAAA,QACxE;AAEA,mBAAW,KAAK,kBAAkB;AAChC,mBAAS,KAAK;AAAA,YACZ,QAAQ,EAAE;AAAA,YACV,UAAU,EAAE;AAAA,YACZ,OAAO;AAAA,YACP,UAAU,EAAE;AAAA,YACZ,YAAY;AAAA,cACV;AAAA,gBACE,UAAU;AAAA,gBACV,cAAc;AAAA,gBACd,YAAY;AAAA,gBACZ,QAAQ;AAAA,gBACR,OAAO;AAAA,gBACP,QAAQ;AAAA,cACV;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAGA,cAAM,cAAc,MAAM,KAAK,MAAM,eAAe;AACpD,cAAM,gBAAgB,IAAI,IAAI,YAAY,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;AAChE,cAAM,MAAMF,QAAO,IAAI;AACvB,cAAM,WAAW,MAAM,KAAK,mBAAmB;AAC/C,cAAM,MAAM,SAAS,OAAO,CAAC,MAAM,IAAI,QAAQA,QAAO,IAAI,EAAE,UAAUG,mBAAkB,CAAC,CAAC;AAE1F,eAAO,KAAK,qCAAqC,KAAK,UAAU,GAAG,CAAC,EAAE;AAEtE,mBAAW,WAAW,KAAK;AACzB,cAAI,QAAQ,SAAS,UAAU;AAE7B,kBAAM,KAAK,IAAI,SAAS,QAAQ,UAAU,YAAY,KAAK,KAAK;AAChE,kBAAM,cAAc,MAAM,GAAG,iBAAiB,KAAK;AACnD,uBAAW,QAAQ,aAAa;AAC9B,kBAAI,CAAC,cAAc,IAAI,KAAK,MAAM,GAAG;AACnC,yBAAS,KAAK;AAAA,kBACZ,GAAG;AAAA,kBACH,YAAY;AAAA,oBACV,GAAG,KAAK;AAAA,oBACR;AAAA,sBACE,UAAU;AAAA,sBACV,cAAc;AAAA,sBACd,YAAY;AAAA,sBACZ,QAAQ;AAAA,sBACR,OAAO,KAAK;AAAA,sBACZ,QAAQ,sCAAsC,QAAQ,QAAQ;AAAA,oBAChE;AAAA,kBACF;AAAA,gBACF,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF,WAAW,QAAQ,SAAS,OAAO;AACjC,kBAAM,SAAS,MAAM,OAAO,QAAQ,UAAU,QAAQ,KAAK;AAE3D,uBAAW,UAAU,OAAO,aAAa;AACvC,kBAAI,CAAC,cAAc,IAAI,MAAM,GAAG;AAC9B,yBAAS,KAAK;AAAA,kBACZ;AAAA,kBACA,UAAU,QAAQ;AAAA,kBAClB,OAAO;AAAA,kBACP,YAAY;AAAA,oBACV;AAAA,sBACE,UAAU;AAAA,sBACV,cAAc;AAAA,sBACd,YAAY;AAAA,sBACZ,QAAQ;AAAA,sBACR,OAAO;AAAA,sBACP,QAAQ,2BAA2B,QAAQ,KAAK;AAAA,oBAClD;AAAA,kBACF;AAAA,gBACF,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF,WAAW,QAAQ,SAAS,QAAQ;AAClC,gBAAI,CAAC,cAAc,IAAI,QAAQ,MAAM,GAAG;AACtC,uBAAS,KAAK;AAAA,gBACZ,QAAQ,QAAQ;AAAA,gBAChB,UAAU,QAAQ;AAAA,gBAClB,OAAO;AAAA,gBACP,YAAY;AAAA,kBACV;AAAA,oBACE,UAAU;AAAA,oBACV,cAAc;AAAA,oBACd,YAAY;AAAA,oBACZ,QAAQ;AAAA,oBACR,OAAO;AAAA,oBACP,QAAQ;AAAA,kBACV;AAAA,gBACF;AAAA,cACF,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAEA,eAAO;AAAA,UACL,iDAAiD,KAAK,KAAK,IAAI,KAC1D,SAAS,MAAM;AAAA,QACtB;AAGA,eAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK;AAAA,MAClE;AAAA,IACF;AAAA;AAAA;;;AC/OA,IAAAC,gBAAA;AAAA;AAAA;AAAA;AACA;AACA;AAMA,IAAAC;AAIA;AACA;AAAA;AAAA;;;ACbA;AAAA;AAAA;AAAA;AACA;AACA;AAAA;AAAA;;;ACAA,OAAO,WAAW;AAFlB;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;;;ACIA,SAAS,UAAAC,eAAc;AALvB;AAAA;AAAA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AAAA;AAAA;;;ACHA,OAAOC,YAAW;AAElB,OAAOC,aAAwB;AAM/B,OAAOC,cAAa;AAqCb,SAAS,sBAAyE;AAEvF,QAAM,yBAAyB,IAAI,oBAAoB,IAAI;AAC3D,QAAM,oBAAoB,OAAO,WAAW;AAE5C,MAAI,0BAA0B,mBAAmB;AAE/C,WAAO;AAAA,MACL,MAAM,KAAuB,OAAoB,CAAC,GAAsB;AACtE,cAAM,YAAY,KAAK,GAAG,IAAI,gBAAgB,IAAI,IAAI,gBAAgB,EAAE;AACxE,cAAM,UAAU,IAAI,QAAQ,KAAK,WAAW,CAAC,CAAC;AAC9C,gBAAQ,IAAI,iBAAiB,SAAS,SAAS,EAAE;AAEjD,cAAM,UAAU;AAAA,UACd,GAAG;AAAA,UACH;AAAA,QACF;AAEA,eAAQ,sBAAc,MAAM,KAAK,OAAO;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AACT;AASO,SAASC,aAAY,UAAoC;AAE9D,SAAO,IAAI;AAAA,IACT,IAAI,0BAA0B,QAAQ,IAAI,qBAAqB,cAAc;AAAA,IAC7E,oBAAoB;AAAA,EACtB;AACF;AA+JO,SAASC,uBACd,IACA,QACA,MACA;AAGA,QAAM,UAAkD;AAAA,IACtD,UAAU;AAAA,IACV,QAAQ,SAAS;AAAA,IACjB,cAAc;AAAA,EAChB;AAEA,MAAI,MAAM;AACR,WAAO,OAAO,SAAS,IAAI;AAAA,EAC7B;AACA,SAAO,GAAG,QAAW,OAAO;AAC9B;AAEO,SAASC,oBAAmB,KAGjC;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,QAAQ,MAAM;AAAA,EAChB;AACF;AAvRA,IAkBM,WAQA,gBACO,aAaP,iCAuKOC;AA/Mb;AAAA;AAAA;AAAA;AACA;AAUA;AAEA;AAgRA;AACA,IAAAC;AACA,IAAAC;AACA;AACA;AACA;AACA;AAjRA,IAAM,YAAY,OAAO,WAAW;AAEpC,QAAI,WAAW;AACb,MAAC,OAAe,UAAUN;AAAA,IAC5B;AAIA,IAAM,iBAAiB,UAAU,aAAa;AACvC,IAAM,cAAgC,IAAI,sBAAM,cAAc;AAarE,IAAM,kCAAqF;AAAA,MACzF,MAAM,KAAuB,MAAsC;AACjE,aAAK,cAAc;AAEnB,eAAQ,sBAAc,MAAM,KAAK,IAAI;AAAA,MACvC;AAAA,IACF;AAiKO,IAAMI,sBAA6B;AAAA;AAAA;;;AC7M1C,SAAoB,UAAAG,eAAc;AAClC,OAAOC,aAAwB;AAwtC/B,eAAe,qCACb,MACgF;AAChF,MAAI;AAEJ,MAAI;AACF,UAAM,MAAM,eAAe,IAAI,EAAE,IAA8B,iBAAiB;AAAA,EAClF,SAAS,GAAG;AACV,UAAM,MAAM;AAEZ,QAAI,IAAI,WAAW,KAAK;AAEtB,YAAM,eAAe,IAAI,EAAE,IAA8B;AAAA,QACvD,KAAK;AAAA,QACL,eAAe,CAAC;AAAA,MAClB,CAAC;AACD,YAAM,MAAM,qCAAqC,IAAI;AAAA,IACvD,OAAO;AAEL,YAAM,eAAe;AAAA,QACnB,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,QACZ,SAAS,IAAI;AAAA,QACb,QAAQ,IAAI;AAAA,QACZ,OAAO,IAAI;AAAA,MACb;AAEA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAEA,YAAM,IAAI;AAAA,QACR,qDAAqD,IAAI,WAAW,IAAI,QAAQ,eAAe,aAAa,IAAI,MAAM;AAAA,MACxH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,kCACb,MAC6E;AAC7E,MAAI;AAEJ,MAAI;AACF,UAAM,MAAM,eAAe,IAAI,EAAE,IAA2B,cAAc;AAAA,EAC5E,SAAS,GAAG;AACV,UAAM,MAAM;AACZ,QAAI,IAAI,WAAW,KAAK;AAEtB,YAAM,eAAe,IAAI,EAAE,IAA2B;AAAA,QACpD,KAAK;AAAA,QACL,SAAS,CAAC;AAAA,QACV,aAAa,CAAC;AAAA,MAChB,CAAC;AACD,YAAM,MAAM,kCAAkC,IAAI;AAAA,IACpD,OAAO;AACL,YAAM,IAAI;AAAA,QACR,oBAAoB,KAAK,UAAU,CAAC,CAAC;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,cAAc,MAAc,WAAmB,KAAgB;AACnF,QAAM,SAAS,MAAM,kCAAkC,IAAI;AAC3D,QAAM,SAAS,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,aAAa,SAAS;AAClE,SAAO,MAAM;AACb,SAAO,eAAe,IAAI,EAAE,IAAI,MAAM;AACxC;AAEA,eAAsB,yBACpB,MACA,SACA,YACA;AACA,EAAAC,KAAI,qBAAqB,IAAI,eAAe,OAAO,EAAE;AACrD,SAAO,qCAAqC,IAAI,EAAE,KAAK,CAAC,QAAQ;AAC9D,UAAM,UAAU;AAAA,MACd;AAAA,MACA,cAAc;AAAA,IAChB;AAEA,QACE,IAAI,cAAc,OAAO,CAAC,QAAQ;AAChC,aAAO,IAAI,YAAY,QAAQ,WAAW,IAAI,iBAAiB,QAAQ;AAAA,IACzE,CAAC,EAAE,WAAW,GACd;AACA,UAAI,cAAc,KAAK,OAAO;AAAA,IAChC,OAAO;AACL,MAAAA,KAAI,QAAQ,IAAI,oCAAoC,OAAO,EAAE;AAAA,IAC/D;AAEA,WAAO,eAAe,IAAI,EAAE,IAAI,GAAG;AAAA,EACrC,CAAC;AACH;AAEA,eAAsB,sBAAsB,MAAc,SAAiB;AACzE,SAAO,qCAAqC,IAAI,EAAE,KAAK,CAAC,QAAQ;AAC9D,QAAI,QAAgB;AAEpB,aAAS,IAAI,GAAG,IAAI,IAAI,cAAc,QAAQ,KAAK;AACjD,UAAI,IAAI,cAAc,CAAC,EAAE,YAAY,SAAS;AAC5C,gBAAQ;AAAA,MACV;AAAA,IACF;AAEA,QAAI,UAAU,IAAI;AAChB,UAAI,cAAc,OAAO,OAAO,CAAC;AAAA,IACnC;AACA,WAAO,eAAe,IAAI,EAAE,IAAI,GAAG;AAAA,EACrC,CAAC;AACH;AAEA,eAAsB,kBAAkB,MAAc;AACpD,SAAO,qCAAqC,IAAI;AAClD;AAn1CA,IAqCMA,MAmBO,UAgqCP,gBACA;AAztCN;AAAA;AAAA;AAAA;AACA;AAGA;AACA;AAmBA;AASA;AACA;AACA;AAEA,IAAMA,OAAM,CAAC,MAAW;AACtB,aAAO,KAAK,CAAC;AAAA,IACf;AAiBO,IAAM,WAAN,MAAM,UAAqD;AAAA,MAChE,OAAe;AAAA,MACf,OAAe,eAAwB;AAAA,MAEvC,OAAc,MAAM,cAAsC;AACxD,eAAO,IAAI,UAAS,MAAM,YAAY;AAAA,MACxC;AAAA,MAEA,OAAgB,UAAU;AAAA,QACxB,QAAQ;AAAA,QACR,sBAAsB;AAAA,QACtB,yBAAyB;AAAA,MAC3B;AAAA;AAAA,MAGQ;AAAA,MACA;AAAA,MAED,cAAsB;AAC3B,eAAO,KAAK;AAAA,MACd;AAAA,MAEO,aAAsB;AAC3B,eAAO,CAAC,KAAK,UAAU,WAAW,aAAa;AAAA,MACjD;AAAA,MAEO,SAA2B;AAChC,eAAO,KAAK;AAAA,MACd;AAAA,MAEQ;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MAER,MAAa,cACX,UACA,UAIC;AACD,YAAI,CAAC,KAAK,aAAa,iBAAiB,GAAG;AACzC,gBAAM,IAAI,MAAM,yDAAyD;AAAA,QAC3E;AAEA,YAAI,CAAC,KAAK,UAAU,WAAW,aAAa,GAAG;AAC7C,gBAAM,IAAI;AAAA,YACR;AAAA,yBACiB,KAAK,SAAS;AAAA,UACjC;AAAA,QACF;AAEA,cAAM,SAAS,MAAM,KAAK,aAAa,cAAe,UAAU,QAAQ;AAGxE,YAAI,OAAO,WAAWF,QAAO,IAAI;AAC/B,UAAAE,KAAI,sDAAsD,QAAQ,EAAE;AACpE,eAAK,YAAY;AACjB,cAAI;AACF,yBAAa,WAAW,eAAe;AAAA,UACzC,SAAS,GAAG;AACV,mBAAO,KAAK,qDAAqD,CAAC;AAAA,UACpE;AACA,gBAAM,KAAK,KAAK;AAAA,QAClB;AAEA,eAAO;AAAA,UACL,QAAQ,OAAO;AAAA,UACf,OAAO,OAAO,SAAS;AAAA,QACzB;AAAA,MACF;AAAA,MACA,MAAa,MAAM,UAAkB,UAAkB;AACrD,YAAI,CAAC,KAAK,aAAa,gBAAgB,GAAG;AACxC,gBAAM,IAAI,MAAM,uDAAuD;AAAA,QACzE;AAEA,YAAI,CAAC,KAAK,UAAU,WAAW,aAAa,KAAK,KAAK,aAAa,UAAU;AAC3E,cAAI,KAAK,aAAa,UAAU;AAC9B,kBAAM,IAAI,MAAM;AAAA,6BACK,KAAK,YAAY,CAAC,yBAAyB,QAAQ,GAAG;AAAA,UAC7E;AACA,iBAAO,KAAK,QAAQ,KAAK,SAAS,mDAAmD;AAAA,QACvF;AAEA,cAAM,cAAc,MAAM,KAAK,aAAa,aAAc,UAAU,QAAQ;AAC5E,YAAI,YAAY,IAAI;AAClB,UAAAA,KAAI,gBAAgB,QAAQ,EAAE;AAC9B,eAAK,YAAY;AACjB,cAAI;AACF,yBAAa,WAAW,eAAe;AAAA,UACzC,SAAS,GAAG;AACV,mBAAO,KAAK,qDAAqD,CAAC;AAAA,UACpE;AACA,gBAAM,KAAK,KAAK;AAAA,QAClB;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAa,gBAA6D;AAExE,YAAI,KAAK,aAAa,gBAAgB,GAAG;AACvC,iBAAO;AAAA,YACL,QAAQF,QAAO;AAAA,YACf,OACE;AAAA,UACJ;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,UAAU,eAAe,KAAK,SAAS;AAG7C,gBAAM,UAAU,MAAM,QAAQ,QAAQ,EAAE,cAAc,MAAM,CAAC;AAG7D,gBAAM,eAAe,QAAQ,KAC1B,OAAO,CAAC,QAAQ;AACf,kBAAM,KAAK,IAAI;AAEf,mBACE,GAAG,WAAW,6CAAkC,CAAC;AAAA,YACjD,GAAG,WAAW,qDAAsC,CAAC;AAAA,YACrD,GAAG,WAAW,qDAAsC,CAAC;AAAA,YACrD,GAAG,WAAW,iDAAoC,CAAC;AAAA,YACnD,GAAG,WAAW,uEAA+C,CAAC;AAAA,YAC9D,OAAO,UAAS,QAAQ;AAAA,YACxB,OAAO,UAAS,QAAQ;AAAA,YACxB,OAAO,UAAS,QAAQ;AAAA,UAE5B,CAAC,EACA,IAAI,CAAC,SAAS,EAAE,KAAK,IAAI,IAAI,MAAM,IAAI,MAAM,KAAK,UAAU,KAAK,EAAE;AAEtE,cAAI,aAAa,SAAS,GAAG;AAC3B,kBAAM,QAAQ,SAAS,YAAY;AAAA,UACrC;AAGA,gBAAM,KAAK,KAAK;AAEhB,iBAAO,EAAE,QAAQA,QAAO,GAAG;AAAA,QAC7B,SAAS,OAAO;AACd,iBAAO,MAAM,8BAA8B,KAAK;AAChD,iBAAO;AAAA,YACL,QAAQA,QAAO;AAAA,YACf,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAClD;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAa,SAAS;AACpB,YAAI,CAAC,KAAK,aAAa,gBAAgB,GAAG;AAExC,eAAK,YAAY,MAAM,KAAK,aAAa,mBAAmB;AAC5D,gBAAM,KAAK,KAAK;AAChB,iBAAO,EAAE,IAAI,KAAK;AAAA,QACpB;AAEA,cAAM,MAAM,MAAM,KAAK,aAAa,OAAQ;AAE5C,aAAK,YAAY,MAAM,KAAK,aAAa,mBAAmB;AAC5D,cAAM,KAAK,KAAK;AAEhB,eAAO;AAAA,MACT;AAAA,MAEA,MAAa,IAAO,IAAsD;AACxE,eAAO,KAAK,QAAQ,IAAO,EAAE;AAAA,MAC/B;AAAA,MAEO,OAAgD,IAAY,QAAmB;AACpF,eAAO,KAAK,YAAY,OAAO,IAAI,MAAM;AAAA,MAC3C;AAAA,MAEA,MAAa,4BAEX;AACA,eAAO,MAAM,oCAAoC,KAAK,YAAY,CAAC,EAAE;AAErE,YAAI;AAEJ,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,QAAQ;AAAA,YAChC,UAAS,QAAQ;AAAA,UACnB;AACA,iBAAO;AAAA,QACT,SAAS,GAAG;AACV,gBAAM,MAAM;AACZ,cAAI,IAAI,WAAW,KAAK;AACtB,kBAAM,KAAK,QAAQ,IAA2B;AAAA,cAC5C,KAAK,UAAS,QAAQ;AAAA,cACtB,SAAS,CAAC;AAAA,cACV,aAAa,CAAC;AAAA,YAChB,CAAC;AACD,kBAAM,MAAM,KAAK,0BAA0B;AAAA,UAC7C,OAAO;AACL,kBAAM,IAAI;AAAA,cACR,oBAAoB,KAAK,UAAU,CAAC,CAAC;AAAA,YACvC;AAAA,UACF;AAAA,QACF;AAEA,eAAO;AAAA,MACT;AAAA,MAEA,MAAa,mBAAmB;AAC9B,cAAM,MAAM,MAAM,KAAK,0BAA0B;AACjD,eAAO,IAAI,QAAQ,OAAO,CAAC,MAAM;AAC/B,iBAAO,EAAE,WAAW,UAAa,EAAE,WAAW;AAAA,QAChD,CAAC;AAAA,MACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAa,iBAAiB;AAC5B,cAAM,OAAO,mBAAmB,qDAAsC,CAAC;AAEvE,cAAM,UAAU,MAAM,KAAK,SAAS,QAAuB;AAAA,UACzD,UAAU,KAAK;AAAA,UACf,QAAQ,KAAK;AAAA,UACb,cAAc;AAAA,QAChB,CAAC;AAED,eAAO,QAAQ,KAAK,IAAI,CAAC,MAAM;AAC7B,iBAAO;AAAA,YACL,UAAU,EAAE,IAAK;AAAA,YACjB,QAAQ,EAAE,IAAK;AAAA,UACjB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MAEA,MAAa,qBAAgD;AAC3D,YAAI;AACF,gBAAM,OAAO,MAAM,KAAK,WAAW;AAEnC,gBAAM,aAA+B,CAAC;AACtC,cAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,mBAAO,MAAM,uCAAuC,IAAI;AACxD,mBAAO;AAAA,UACT;AAGA,cAAI,cAAc;AAElB,mBAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,gBAAI;AACF,kBAAI,KAAK,CAAC,KAAK,MAAM,QAAQ,KAAK,CAAC,EAAG,OAAO,GAAG;AAC9C,qBAAK,CAAC,EAAG,QAAQ,QAAQ,CAAC,WAAuB;AAC/C,sBAAI;AAEF,wBAAI,CAAC,OAAO,WAAW;AACrB;AAAA,oBACF;AAEA,wBAAI;AAGJ,wBAAI,OAAO,OAAO,cAAc,UAAU;AAExC,0BAAI,OAAO,OAAO,UAAU,WAAW,YAAY;AAEjD,oCAAY,OAAO,UAAU,YAAY;AAAA,sBAC3C,WAAW,OAAO,qBAAqB,MAAM;AAE3C,oCAAY,OAAO,UAAU,YAAY;AAAA,sBAC3C,OAAO;AAEL,4BAAI,cAAc,GAAG;AACnB,iCAAO,KAAK,kCAAkC,OAAO,SAAS;AAC9D;AAAA,wBACF;AACA;AAAA,sBACF;AAAA,oBACF,WAAW,OAAO,OAAO,cAAc,UAAU;AAE/C,4BAAM,OAAO,IAAI,KAAK,OAAO,SAAS;AACtC,0BAAI,MAAM,KAAK,QAAQ,CAAC,GAAG;AACzB;AAAA,sBACF;AACA,kCAAY,OAAO;AAAA,oBACrB,WAAW,OAAO,OAAO,cAAc,UAAU;AAE/C,kCAAY,IAAI,KAAK,OAAO,SAAS,EAAE,YAAY;AAAA,oBACrD,OAAO;AAEL;AAAA,oBACF;AAEA,+BAAW,KAAK;AAAA,sBACd;AAAA,sBACA,UAAU,OAAO,YAAY;AAAA,sBAC7B,QAAQ,OAAO,UAAU;AAAA,sBACzB,WAAW,OAAO,aAAa;AAAA,sBAC/B,MAAM;AAAA,oBACR,CAAC;AAAA,kBAEH,SAAS,KAAK;AAAA,kBAEd;AAAA,gBACF,CAAC;AAAA,cACH;AAAA,YACF,SAAS,KAAK;AACZ,qBAAO,MAAM,kCAAkC,GAAG;AAAA,YACpD;AAAA,UACF;AAEA,iBAAO,MAAM,SAAS,WAAW,MAAM,mBAAmB;AAC1D,iBAAO;AAAA,QACT,SAAS,KAAK;AACZ,iBAAO,MAAM,gCAAgC,GAAG;AAChD,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,MAEA,MAAc,iBAAiB,YAAoB,WAAoB;AACrE,cAAM,OAAO,mBAAmB,qDAAsC,CAAC;AAEvE,cAAM,UAAU,MAAM,KAAK,SAAS,QAAuB;AAAA,UACzD,UAAU,KAAK;AAAA,UACf,QAAQ,KAAK;AAAA,UACb,cAAc;AAAA,QAChB,CAAC;AAED,QAAAE;AAAA,UACE,YAAY,KAAK,SAAS,uBACxB,YAAY,eAAe,SAAS,KAAK,EAC3C;AAAA,QACF;AACA,eAAO,QAAQ,KACZ,OAAO,CAAC,MAAM;AACb,cAAI,EAAE,GAAG,WAAW,qDAAsC,CAAC,GAAG;AAC5D,kBAAM,OAAOD,QAAO;AAAA,cAClB,EAAE,GAAG,OAAO,qDAAsC,EAAE,MAAM;AAAA,cAC1D;AAAA,YACF;AACA,gBAAI,WAAW,QAAQ,IAAI,GAAG;AAC5B,kBAAI,cAAc,UAAa,EAAE,IAAK,aAAa,WAAW;AAC5D,uBAAO;AAAA,cACT;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC,EACA,IAAI,CAAC,MAAM,EAAE,GAAI;AAAA,MACtB;AAAA,MAEA,MAAa,kBAAkB,WAAmB;AAChD,cAAM,OAAOA,QAAO,IAAI,EAAE,IAAI,WAAW,MAAM;AAC/C,eAAO,KAAK,iBAAiB,IAAI;AAAA,MACnC;AAAA,MAEA,MAAa,kBAAkB,WAAoB;AACjD,cAAM,MAAMA,QAAO,IAAI;AACvB,eAAO,KAAK,iBAAiB,KAAK,SAAS;AAAA,MAC7C;AAAA,MAEA,MAAa,wBAAwB,WAAoC;AACvE,gBAAQ,MAAM,KAAK,kBAAkB,SAAS,GAAG;AAAA,MACnD;AAAA,MAEA,MAAa,uBAAuB;AAClC,cAAM,SAAS,MAAM,KAAK,0BAA0B;AACpD,eAAO,OAAO,QAAQ,OAAO,CAAC,MAAM;AAClC,iBAAO,CAAC,EAAE,UAAU,EAAE,WAAW,YAAY,EAAE,WAAW;AAAA,QAC5D,CAAC;AAAA,MACH;AAAA,MAEA,MAAa,gBAAgB,UAAkB;AAC7C,cAAM,UAAU,MAAM,KAAK,0BAA0B;AACrD,cAAM,MAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AAC/D,YAAI,KAAK;AACP,iBAAO;AAAA,QACT,OAAO;AACL,gBAAM,IAAI,MAAM,gDAAgD,QAAQ,EAAE;AAAA,QAC5E;AAAA,MACF;AAAA,MAEA,MAAa,kBAAkB,WAAmB,cAAuB,OAAO;AAC9E,eAAO,KAAK,0BAA0B,EACnC,KAAK,CAAC,QAA+B;AACpC,gBAAM,SAAS,cAAc,YAAY;AACzC,iBAAO,MAAM,mBAAmB,SAAS,iBAAiB,MAAM,EAAE;AAElE,gBAAM,UAA8B;AAAA,YAClC;AAAA,YACA,UAAU;AAAA,YACV,MAAM;AAAA,YACN,OAAO;AAAA,YACP,WAAW;AAAA,YACX,KAAK;AAAA,cACH,QAAQ;AAAA,gBACN,OAAO;AAAA,gBACP,OAAO;AAAA,cACT;AAAA,cACA,MAAM,CAAC;AAAA,cACP,MAAM,CAAC;AAAA,YACT;AAAA,UACF;AAEA,cACE,IAAI,QAAQ,OAAO,CAAC,WAAW;AAC7B,mBAAO,OAAO,aAAa,QAAQ;AAAA,UACrC,CAAC,EAAE,WAAW,GACd;AACA,YAAAC,KAAI,iCAAiC;AACrC,gBAAI,QAAQ,KAAK,OAAO;AACxB,gBAAI,YAAY,SAAS,IAAI;AAAA,UAC/B,OAAO;AACL,gBAAI,QAAQ,QAAQ,CAAC,MAAM;AACzB,cAAAA,KAAI,yCAAyC;AAC7C,kBAAI,EAAE,aAAa,WAAW;AAC5B,kBAAE,SAAS;AAAA,cACb;AAAA,YACF,CAAC;AAAA,UACH;AAEA,iBAAO,KAAK,QAAQ,IAA2B,GAAG;AAAA,QACpD,CAAC,EACA,MAAM,CAAC,MAAM;AACZ,UAAAA,KAAI,mCAAmC,KAAK,UAAU,CAAC,CAAC,EAAE;AAC1D,gBAAM;AAAA,QACR,CAAC;AAAA,MACL;AAAA,MACA,MAAa,WAAW,WAAmB,aAA2C,WAAW;AAC/F,eAAO,KAAK,0BAA0B,EAAE,KAAK,CAAC,QAAQ;AACpD,cAAI,QAAgB;AACpB,mBAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,QAAQ,KAAK;AAC3C,gBAAI,IAAI,QAAQ,CAAC,EAAE,aAAa,WAAW;AACzC,sBAAQ;AAAA,YACV;AAAA,UACF;AAEA,cAAI,UAAU,IAAI;AAEhB,mBAAO,IAAI,YAAY,SAAS;AAEhC,gBAAI,QAAQ,KAAK,EAAE,SAAS;AAAA,UAC9B,OAAO;AACL,kBAAM,IAAI;AAAA,cACR,QAAQ,KAAK,YAAY,CAAC,2CAA2C,SAAS;AAAA,YAChF;AAAA,UACF;AAEA,iBAAO,KAAK,QAAQ,IAA2B,GAAG;AAAA,QACpD,CAAC;AAAA,MACH;AAAA,MAEA,MAAa,mBAAmB,UAAgD;AAC9E,eAAO,IAAI,WAAW,MAAM,QAAQ;AAAA,MACtC;AAAA,MAEA,MAAa,yBAAyB;AACpC,YAAI,YAAsB,CAAC;AAE3B,cAAM,oBAAoB,MAAM,KAAK,0BAA0B;AAE/D,oBAAY,UAAU;AAAA,UACpB,kBAAkB,QAAQ,IAAI,CAAC,WAAW;AACxC,mBAAO,OAAO;AAAA,UAChB,CAAC;AAAA,QACH;AAEA,cAAM,OAAO,MAAM,QAAQ;AAAA,UACzB,UAAU,IAAI,OAAO,OAAO;AAC1B,mBAAO,MAAM,6BAA6B,EAAE;AAAA,UAC9C,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT;AAAA,MAEA,MAAa,YAA8E;AACzF,cAAM,gBAAmD;AAAA,UACvD,KAAK,UAAS,QAAQ;AAAA,UACtB,UAAU;AAAA,UACV,eAAe;AAAA,UACf,kBAAkB;AAAA,QACpB;AAEA,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,QAAQ,IAAgB,UAAS,QAAQ,MAAM;AACtE,iBAAO,MAAM,uBAAuB,GAAG;AAEvC,iBAAO;AAAA,QACT,SAAS,GAAG;AACV,gBAAM,MAAM;AACZ,cAAI,IAAI,QAAQ,IAAI,SAAS,aAAa;AACxC,kBAAM,KAAK,QAAQ,IAAgB,aAAa;AAChD,mBAAO,KAAK,UAAU;AAAA,UACxB,OAAO;AACL,mBAAO,MAAM,sCAAsC,CAAC;AACpD,kBAAM,IAAI,MAAM,6CAA6C,KAAK,UAAU,CAAC,CAAC,EAAE;AAAA,UAClF;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAa,UAAU,OAA4B;AACjD,eAAO,MAAM,wBAAwB,KAAK,UAAU,KAAK,CAAC,EAAE;AAE5D,cAAM,IAAI,MAAM,KAAK,UAAU;AAC/B,cAAM,MAAM,MAAM,KAAK,QAAQ,IAAgB;AAAA,UAC7C,GAAG;AAAA,UACH,GAAG;AAAA,QACL,CAAC;AAED,YAAI,IAAI,IAAI;AACV,iBAAO,MAAM,qBAAqB,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,QAC3D,OAAO;AACL,iBAAO,MAAM,+BAA+B,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,QACnE;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYA,aAAoB,SAAS,cAA4B,UAAsC;AAC7F,YAAI,UAAU;AACZ,oBAAS,YAAY,IAAI,UAAS,UAAU,YAAY;AACxD,gBAAM,UAAS,UAAU,KAAK;AAC9B,iBAAO,UAAS;AAAA,QAClB,WAAW,UAAS,aAAa,UAAS,cAAc;AAEtD,iBAAO,UAAS;AAAA,QAClB,WAAW,UAAS,WAAW;AAC7B,iBAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,aAAC,SAAS,cAAc;AACtB,kBAAI,UAAS,cAAc;AACzB,uBAAO,QAAQ,UAAS,SAAS;AAAA,cACnC,OAAO;AACL,2BAAW,aAAa,EAAE;AAAA,cAC5B;AAAA,YACF,GAAG;AAAA,UACL,CAAC;AAAA,QACH,OAAO;AACL,gBAAM,gBAAgB,MAAM,aAAa,mBAAmB;AAC5D,oBAAS,YAAY,IAAI,UAAS,eAAe,YAAY;AAC7D,gBAAM,UAAS,UAAU,KAAK;AAC9B,iBAAO,UAAS;AAAA,QAClB;AAAA,MACF;AAAA,MAEQ,YAAY,UAAkB,cAA4B;AAChE,kBAAS,eAAe;AACxB,aAAK,YAAY;AACjB,aAAK,eAAe;AACpB,aAAK,UAAU;AAAA,MACjB;AAAA,MAEQ,YAAY;AAClB,aAAK,UAAU,eAAe,KAAK,SAAS;AAC5C,aAAK,WAAW,KAAK,aAAa,cAAc,KAAK,SAAS;AAE9D,aAAK,UAAU,KAAK,aAAa,aAC7B,KAAK,aAAa,WAAW,KAAK,SAAS,IAC3C,KAAK;AACT,aAAK,cAAc,IAAI,YAAY,KAAK,SAAS,KAAK,OAAO;AAAA,MAC/D;AAAA,MAEA,MAAc,OAAO;AACnB,kBAAS,eAAe;AAGxB,YAAI,KAAK,cAAc,SAAS;AAC9B,oBAAS,eAAe;AACxB;AAAA,QACF;AAEA,aAAK,UAAU;AAEf,aAAK,aAAa,UAAU,KAAK,SAAS,KAAK,QAAQ;AACvD,aAAK,gBAAgB,EAAE,MAAM,CAAC,UAAU;AACtC,UAAAA,KAAI,6CAA6C,KAAK,EAAE;AACxD,cAAI,SAAS,OAAO,UAAU,UAAU;AACtC,YAAAA,KAAI,0CAA0C,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,UACvE;AAAA,QACF,CAAC;AACD,aAAK,mBAAmB,EAAE,MAAM,CAAC,UAAU;AACzC,UAAAA,KAAI,gDAAgD,KAAK,EAAE;AAC3D,cAAI,SAAS,OAAO,UAAU,UAAU;AACtC,YAAAA,KAAI,0CAA0C,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,UACvE;AAAA,QACF,CAAC;AACD,kBAAS,eAAe;AAAA,MAC1B;AAAA,MAEA,OAAe,aAA0B;AAAA,QACvC;AAAA,UACE,KAAK;AAAA,UACL,OAAO;AAAA,YACL,aAAa;AAAA,cACX,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,YAKP;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAc,kBAAkB;AAC9B,QAAAA,KAAI,sCAAsC,KAAK,SAAS,EAAE;AAC1D,QAAAA,KAAI,mBAAmB,KAAK,SAAS,QAAQ,SAAS,EAAE;AAExD,YAAI,KAAK,cAAc,SAAS;AAE9B,UAAAA,KAAI,qCAAqC;AACzC;AAAA,QACF;AAEA,QAAAA,KAAI,YAAY,UAAS,WAAW,MAAM,cAAc;AACxD,mBAAW,OAAO,UAAS,YAAY;AACrC,UAAAA,KAAI,wBAAwB,IAAI,GAAG,EAAE;AACrC,cAAI;AAEF,gBAAI;AACF,oBAAM,cAAc,MAAM,KAAK,SAAS,IAAI,IAAI,GAAG;AAEnD,oBAAM,KAAK,SAAS,IAAI;AAAA,gBACtB,GAAG;AAAA,gBACH,MAAM,YAAY;AAAA,cACpB,CAAC;AAAA,YACH,SAAS,GAAY;AACnB,kBAAI,aAAa,SAAS,EAAE,SAAS,aAAa;AAEhD,sBAAM,KAAK,SAAS,IAAI,GAAG;AAAA,cAC7B,OAAO;AACL,sBAAM;AAAA,cACR;AAAA,YACF;AAAA,UACF,SAAS,OAAgB;AACvB,gBAAK,MAAc,QAAS,MAAc,SAAS,YAAY;AAC7D,qBAAO,KAAK,cAAc,IAAI,GAAG,+BAA+B;AAEhE,oBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AACxD,oBAAM,KAAK,eAAe,GAAG;AAAA,YAC/B,OAAO;AACL,qBAAO,MAAM,8BAA8B,IAAI,GAAG,KAAK,KAAK;AAC5D,oBAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA;AAAA,MAGA,MAAc,eAAe,KAAgB,UAAU,GAAkB;AACvE,YAAI;AACF,gBAAM,cAAc,MAAM,KAAK,SAAS,IAAI,IAAI,GAAG;AACnD,gBAAM,KAAK,SAAS,IAAI;AAAA,YACtB,GAAG;AAAA,YACH,MAAM,YAAY;AAAA,UACpB,CAAC;AAAA,QACH,SAAS,GAAY;AACnB,cAAI,aAAa,SAAS,EAAE,SAAS,cAAc,UAAU,GAAG;AAC9D,kBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AACxD,mBAAO,KAAK,eAAe,KAAK,UAAU,CAAC;AAAA,UAC7C;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAsBA,MAAa,cACX,QACgE;AAChE,cAAM,gBAAgB,iBAAiB,OAAO,UAAU,OAAO,MAAM;AAErE,eAAO,YAAYD,QAAO,IAAI,OAAO,SAAS,EAAE,SAAS;AAEzD,YAAI;AACF,gBAAM,cAAc,MAAM,KAAK;AAAA,YAC7B;AAAA,YACA,SAAU,GAAmB;AAC3B,gBAAE,QAAQ,KAAK,MAAM;AACrB,gBAAE,eAAe,EAAE,gBAAgB;AACnC,gBAAE,SAAS,EAAE,UAAU;AACvB,gBAAE,SAAS,EAAE,UAAU;AACvB,qBAAO;AAAA,YACT;AAAA,UACF;AAGA,sBAAY,UAAU,YAAY,QAAQ,IAAO,CAACE,YAAW;AAC3D,kBAAM,MAAS;AAAA,cACb,GAAIA;AAAA,YACN;AACA,gBAAI,YAAYF,QAAO,IAAIE,QAAO,SAAS;AAC3C,mBAAO;AAAA,UACT,CAAC;AACD,iBAAO;AAAA,QACT,SAAS,GAAG;AACV,gBAAM,SAAS;AACf,cAAI,OAAO,WAAW,KAAK;AACzB,gBAAI;AACF,oBAAM,kBAAkC;AAAA,gBACtC,KAAK;AAAA,gBACL,QAAQ,OAAO;AAAA,gBACf,UAAU,OAAO;AAAA,gBACjB,SAAS,CAAC,MAAM;AAAA,gBAChB,QAAQ;AAAA,gBACR,QAAQ;AAAA,gBACR,cAAc;AAAA,cAChB;AACA,oBAAM,YAAY,MAAM,KAAK,QAAQ,IAAoB,eAAe;AACxE,qBAAO,EAAE,GAAG,iBAAiB,MAAM,UAAU,IAAI;AAAA,YACnD,SAAS,eAAe;AACtB,oBAAM,IAAI;AAAA,gBACR,oCAAoC,aAAa,aAAa,aAAa;AAAA,cAC7E;AAAA,YACF;AAAA,UACF,OAAO;AACL,kBAAM,IAAI,MAAM;AAAA,SACf,OAAO,IAAI;AAAA,WACT,OAAO,KAAK;AAAA,aACV,OAAO,OAAO,EAAE;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAc,qBAAqB;AACjC,YAAI;AACF,UAAAD,KAAI,gDAAgD;AACpD,UAAAA,KAAI,mBAAmB,KAAK,SAAS,QAAQ,SAAS,EAAE;AACxD,UAAAA,KAAI,kBAAkB,KAAK,QAAQ,QAAQ,SAAS,EAAE;AAStD,gBAAM,aAA0C,CAAC;AACjD,gBAAM,kBAA4B,CAAC;AAEnC,UAAAA;AAAA,YACE,uEAAuE,KAAK,SAAS,QAAQ,SAAS;AAAA,UACxG;AACA,gBAAM,mBAAmB,MAAM,KAAK,SAAS,MAG1C,yBAAyB;AAE5B,UAAAA,KAAI,SAAS,iBAAiB,KAAK,MAAM,+BAA+B;AAGxE,2BAAiB,KAAK,QAAQ,CAAC,MAAM;AACnC,kBAAM,kBAAkB,EAAE;AAC1B,kBAAM,QAAQ,EAAE;AAEhB,gBAAI,WAAW,eAAe,GAAG;AAE/B,cAAAA,KAAI,8CAA8C,eAAe,EAAE;AACnE,cAAAA;AAAA,gBACE,0BAA0B,WAAW,eAAe,CAAC,0BAA0B,KAAK;AAAA,cACtF;AACA,8BAAgB,KAAK,WAAW,eAAe,CAAC;AAEhD,yBAAW,eAAe,IAAI;AAAA,YAChC,OAAO;AAEL,yBAAW,eAAe,IAAI;AAAA,YAChC;AAAA,UACF,CAAC;AAGD,cAAI,gBAAgB,SAAS,GAAG;AAC9B,YAAAA,KAAI,YAAY,gBAAgB,MAAM,uBAAuB;AAC7D,kBAAM,iBAAiB,gBAAgB,IAAI,OAAO,UAAU;AAC1D,kBAAI;AACF,sBAAM,MAAM,MAAM,KAAK,SAAS,IAAI,KAAK;AACzC,sBAAM,KAAK,QAAQ,OAAO,GAAG;AAC7B,gBAAAA,KAAI,0CAA0C,KAAK,EAAE;AAAA,cACvD,SAAS,OAAO;AACd,gBAAAA,KAAI,qCAAqC,KAAK,KAAK,KAAK,EAAE;AAAA,cAC5D;AAAA,YACF,CAAC;AAED,kBAAM,QAAQ,IAAI,cAAc;AAChC,YAAAA,KAAI,qCAAqC,gBAAgB,MAAM,aAAa;AAAA,UAC9E,OAAO;AACL,YAAAA,KAAI,4BAA4B;AAAA,UAClC;AAAA,QACF,SAAS,OAAO;AACd,UAAAA,KAAI,sCAAsC,KAAK,EAAE;AACjD,cAAI,SAAS,OAAO,UAAU,YAAY,YAAY,SAAS,MAAM,WAAW,KAAK;AACnF,YAAAA;AAAA,cACE,mEAAmE,KAAK,SAAS,QAAQ,SAAS;AAAA,YACpG;AACA,YAAAA;AAAA,cACE;AAAA,YACF;AAAA,UACF;AAEA,cAAI,SAAS,OAAO,UAAU,UAAU;AACtC,YAAAA,KAAI,uBAAuB,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,MAAM,aAAa,WAAoB;AACrC,YAAI,SAAS,6CAAkC;AAC/C,YAAI,WAAW;AACb,oBAAU;AAAA,QACZ;AACA,cAAM,OAAO,MAAM,sBAAsB,KAAK,SAAS,QAAQ;AAAA,UAC7D,cAAc;AAAA,QAChB,CAAC;AAED,cAAM,MAAiC,CAAC;AACxC,aAAK,KAAK,QAAQ,CAAC,QAAQ;AACzB,cAAI,IAAI,GAAG,WAAW,6CAAkC,CAAC,GAAG;AAC1D,gBAAI,KAAK,IAAI,GAAG,OAAO,6CAAkC,EAAE,MAAM,CAAC;AAAA,UACpE;AAAA,QACF,CAAC;AACD,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,MAAM,aAAa;AACjB,cAAM,QAAQ,MAAM;AAAA,UAClB,KAAK;AAAA,UACL,6CAAkC;AAAA,UAClC;AAAA,YACE,cAAc;AAAA,YACd,aAAa;AAAA,UACf;AAAA,QACF;AACA,eAAO,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,GAAG;AAAA,MACpC;AAAA,MAEA,MAAM,qBAAqB,WAAmB,UAA+B;AAC3E,aAAK,KAAK,0BAA0B,EAAE,KAAK,CAAC,QAAQ;AAClD,gBAAM,MAAM,IAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,aAAa,SAAS;AAC5D,cAAI,KAAK;AACP,gBAAI,IAAI,aAAa,QAAQ,IAAI,aAAa,QAAW;AACvD,kBAAI,WAAW,CAAC;AAAA,YAClB;AACA,qBAAS,QAAQ,CAAC,YAAY;AAC5B,kBAAK,SAAU,QAAQ,GAAG,IAAI,QAAQ;AAAA,YACxC,CAAC;AAAA,UACH;AAEA,iBAAO,KAAK,QAAQ,IAAI,GAAG;AAAA,QAC7B,CAAC;AAAA,MACH;AAAA,MACA,MAAM,kBAAkB,WAAmB;AACzC,cAAM,SAAS,MAAM,KAAK,0BAA0B;AACpD,cAAM,SAAS,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,aAAa,SAAS;AAElE,YAAI,QAAQ;AACV,iBAAO,OAAO;AAAA,QAChB,OAAO;AACL,gBAAM,IAAI,MAAM;AAAA,0CACoB,SAAS,EAAE;AAAA,QACjD;AAAA,MACF;AAAA,MAEA,MAAc,uCAEZ;AACA,YAAI;AAEJ,YAAI;AACF,gBAAM,MAAM,KAAK,SAAS;AAAA,YACxB,UAAS,QAAQ;AAAA,UACnB;AAAA,QACF,SAAS,GAAG;AACV,gBAAM,MAAM;AAEZ,cAAI,IAAI,WAAW,KAAK;AAEtB,kBAAM,KAAK,QAAQ,IAA8B;AAAA,cAC/C,KAAK,UAAS,QAAQ;AAAA,cACtB,eAAe,CAAC;AAAA,YAClB,CAAC;AACD,kBAAM,MAAM,KAAK,qCAAqC;AAAA,UACxD,OAAO;AAEL,kBAAM,eAAe;AAAA,cACnB,MAAM,IAAI;AAAA,cACV,QAAQ,IAAI;AAAA,cACZ,SAAS,IAAI;AAAA,cACb,QAAQ,IAAI;AAAA,cACZ,OAAO,IAAI;AAAA,YACb;AAEA,mBAAO;AAAA,cACL;AAAA,cACA;AAAA,YACF;AAEA,kBAAM,IAAI;AAAA,cACR,qDAAqD,IAAI,WAAW,IAAI,QAAQ,eAAe,aAAa,IAAI,MAAM;AAAA,YACxH;AAAA,UACF;AAAA,QACF;AAEA,eAAO,MAAM,0CAA0C,KAAK,UAAU,GAAG,CAAC,EAAE;AAC5E,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAaA,MAAa,mBAAsC;AACjD,YAAI;AACF,kBAAQ,MAAM,KAAK,qCAAqC,GAAG,cACxD,OAAO,CAAC,MAAM,EAAE,iBAAiB,SAAS,EAC1C,IAAI,CAAC,MAAM,EAAE,OAAO;AAAA,QACzB,SAAS,OAAO;AACd,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,UACF;AAEA,iBAAO,CAAC;AAAA,QACV;AAAA,MACF;AAAA,MAEA,MAAa,mBAAmB,QAO7B;AACD,eAAO,wBAAwB,KAAK,SAAS,MAAM;AAAA,MACrD;AAAA,MACA,MAAa,0BAA0B,UAAiC;AACtE,eAAO,+BAA+B,KAAK,SAAS,QAAQ;AAAA,MAC9D;AAAA,MAEA,MAAa,qBACX,UACA,aACgC;AAChC,eAAO,yBAAyB,KAAK,WAAW,UAAU,WAAW;AAAA,MACvE;AAAA,MAEA,MAAa,kBAAkB,SAAiD;AAC9E,eAAO,sBAAsB,KAAK,WAAW,OAAO;AAAA,MACtD;AAAA,MACA,MAAa,oBAAuD;AAClE,eAAO,kBAAkB,KAAK,SAAS;AAAA,MACzC;AAAA,MAEA,MAAa,cAAc,UAAkB,KAAgD;AAC3F,eAAO,cAAc,KAAK,WAAW,UAAU,GAAG;AAAA,MACpD;AAAA,MAEA,MAAa,iBAAoB,UAAkB,aAAwC;AACzF,cAAM,QAAQ,qBAAqB,UAAU,WAAW;AACxD,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,QAAQ,IAAyB,KAAK;AAC7D,iBAAO,IAAI;AAAA,QACb,SAAS,GAAG;AACV,gBAAM,MAAM;AACZ,cAAI,IAAI,WAAW,KAAK;AACtB,mBAAO;AAAA,UACT;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,MAEA,MAAa,iBAAoB,UAAkB,aAAqB,MAAwB;AAC9F,cAAM,QAAQ,qBAAqB,UAAU,WAAW;AACxD,YAAI;AAEJ,YAAI;AACF,gBAAM,WAAW,MAAM,KAAK,QAAQ,IAAyB,KAAK;AAClE,wBAAc,SAAS;AAAA,QACzB,SAAS,GAAG;AACV,gBAAM,MAAM;AACZ,cAAI,IAAI,WAAW,KAAK;AACtB,kBAAM;AAAA,UACR;AAAA,QACF;AAEA,cAAM,MAA2B;AAAA,UAC/B,KAAK;AAAA,UACL,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC;AAEA,cAAM,KAAK,QAAQ,IAAI,GAAG;AAAA,MAC5B;AAAA,MAEA,MAAa,eAAe,QAA0C;AACpE,YAAI;AACF,gBAAM,KAAK,QAAQ,IAAI,MAAM;AAAA,QAC/B,SAAS,KAAU;AACjB,cAAI,IAAI,WAAW,KAAK;AAEtB,kBAAM,WAAW,MAAM,KAAK,QAAQ,IAAI,OAAO,GAAG;AAClD,YAAC,OAAe,OAAO,SAAS;AAChC,kBAAM,KAAK,QAAQ,IAAI,MAAM;AAAA,UAC/B,OAAO;AACL,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAa,oBAAoB,UAAkB,aAAoC;AACrF,cAAM,QAAQ,qBAAqB,UAAU,WAAW;AACxD,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,QAAQ,IAAI,KAAK;AACxC,gBAAM,KAAK,QAAQ,OAAO,GAAG;AAAA,QAC/B,SAAS,GAAG;AACV,gBAAM,MAAM;AACZ,cAAI,IAAI,WAAW,KAAK;AACtB;AAAA,UACF;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAsHA,IAAM,iBAAiB;AACvB,IAAM,oBAAoB;AAAA;AAAA;;;ACztC1B;AAAA;AAAA;AAGA;AAQA;AACA;AAAA;AAAA;;;ACqGO,SAAS,eAAkC;AAChD,MAAI,CAAC,mBAAmB;AACtB,UAAM,IAAI,MAAM,6DAA6D;AAAA,EAC/E;AACA,SAAO;AACT;AAtHA,IAQM,SAUO,KAyBT;AA3CJ;AAAA;AAAA;AAGA;AACA;AAEA;AAEA,IAAM,UAAU;AAUT,IAAM,MAAa;AAAA,MACxB,yBAAyB;AAAA,MACzB,oBAAoB;AAAA,MACpB,sBAAsB;AAAA,IACxB;AAqBA,IAAI,oBAA8C;AAAA;AAAA;;;ACxClD,SAAoB,uBAAuB;AAH3C,IAkBa;AAlBb;AAAA;AAAA;AAIA;AACA;AAaO,IAAM,2BAAN,MAA6D;AAAA,MAC1D;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAGA,kBAAsC;AAAA,MAE9C,YAAY,UAAkB,QAAmB,MAAuB;AACtE,aAAK,WAAW;AAChB,aAAK,SAAS;AACd,aAAK,OAAO;AAEZ,eAAO;AAAA,UACL,kDAAkD,QAAQ;AAAA,UAC1D,KAAK,UAAU,MAAM;AAAA,QACvB;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,MAAc,yBAA+C;AAE3D,YAAI,KAAK,oBAAoB,MAAM;AACjC,iBAAO,KAAK;AAAA,QACd;AAEA,cAAM,kBAAkB,oBAAI,IAAY;AAGxC,YAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AAClC,qBAAW,WAAW,KAAK,OAAO,SAAS;AACzC,gBAAI;AACF,oBAAM,SAAS,MAAM,OAAO,KAAK,UAAU,OAAO;AAClD,qBAAO,YAAY,QAAQ,CAAC,WAAW,gBAAgB,IAAI,MAAM,CAAC;AAAA,YACpE,SAAS,OAAO;AACd,qBAAO;AAAA,gBACL,qDAAqD,OAAO;AAAA,gBAC5D;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAIA,YAAI,gBAAgB,SAAS,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AAChE,iBAAO;AAAA,YACL,+DAA+D,KAAK,OAAO,QAAQ,KAAK,IAAI,CAAC;AAAA,UAC/F;AACA,eAAK,kBAAkB,oBAAI,IAAI;AAC/B,iBAAO,KAAK;AAAA,QACd;AAGA,cAAM,kBAAkB,oBAAI,IAAY;AACxC,YAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AAClC,qBAAW,WAAW,KAAK,OAAO,SAAS;AACzC,gBAAI;AACF,oBAAM,SAAS,MAAM,OAAO,KAAK,UAAU,OAAO;AAClD,qBAAO,YAAY,QAAQ,CAAC,WAAW,gBAAgB,IAAI,MAAM,CAAC;AAAA,YACpE,SAAS,OAAO;AACd,qBAAO;AAAA,gBACL,qDAAqD,OAAO;AAAA,gBAC5D;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAGA,cAAM,eAAe,oBAAI,IAAY;AACrC,mBAAW,UAAU,iBAAiB;AACpC,cAAI,CAAC,gBAAgB,IAAI,MAAM,GAAG;AAChC,yBAAa,IAAI,MAAM;AAAA,UACzB;AAAA,QACF;AAEA,eAAO;AAAA,UACL,uCAAuC,aAAa,IAAI,qBACxC,gBAAgB,IAAI,eAAe,gBAAgB,IAAI;AAAA,QACzE;AAEA,aAAK,kBAAkB;AACvB,eAAO;AAAA,MACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYA,MAAa,iBAAiB,OAAwC;AACpE,YAAI,CAAC,gBAAgB,KAAK,MAAM,GAAG;AACjC,iBAAO,KAAK,0EAA0E;AACtF,iBAAO,CAAC;AAAA,QACV;AAEA,cAAM,kBAAkB,MAAM,KAAK,uBAAuB;AAG1D,cAAM,cAAc,MAAM,KAAK,KAAK,eAAe;AACnD,cAAM,gBAAgB,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AAE9D,cAAM,kBAAkC,CAAC;AACzC,mBAAW,UAAU,iBAAiB;AACpC,cAAI,CAAC,cAAc,IAAI,MAAM,GAAG;AAC9B,4BAAgB,KAAK;AAAA,cACnB;AAAA,cACA,UAAU,KAAK;AAAA,cACf,OAAO;AAAA,cACP,YAAY;AAAA,gBACV;AAAA,kBACE,UAAU;AAAA,kBACV,cAAc;AAAA,kBACd,YAAY;AAAA,kBACZ,QAAQ;AAAA,kBACR,OAAO;AAAA,kBACP,QAAQ,gCAAgC,KAAK,OAAO,QAAQ,KAAK,IAAI,CAAC;AAAA,gBACxE;AAAA,cACF;AAAA,YACF,CAAC;AAAA,UACH;AAEA,cAAI,gBAAgB,UAAU,OAAO;AACnC;AAAA,UACF;AAAA,QACF;AAEA,eAAO;AAAA,UACL,oCAAoC,gBAAgB,MAAM;AAAA,QAC5D;AAGA,cAAM,aAAa,MAAM,KAAK,KAAK,kBAAkB,KAAK,QAAQ;AAClE,cAAM,kBAAkB,WAAW,OAAO,CAAC,WAAW,gBAAgB,IAAI,OAAO,MAAM,CAAC;AAExF,eAAO;AAAA,UACL,oCAAoC,gBAAgB,MAAM,wCACjD,WAAW,MAAM;AAAA,QAC5B;AAEA,cAAM,iBAAiC,gBAAgB,IAAI,CAAC,OAAO;AAAA,UACjE,QAAQ,EAAE;AAAA,UACV,UAAU,EAAE;AAAA,UACZ,OAAO;AAAA,UACP,UAAU,EAAE;AAAA,UACZ,YAAY;AAAA,YACV;AAAA,cACE,UAAU;AAAA,cACV,cAAc;AAAA,cACd,YAAY;AAAA,cACZ,QAAQ;AAAA,cACR,OAAO;AAAA,cACP,QAAQ,8BAA8B,KAAK,OAAO,QAAQ,KAAK,IAAI,CAAC;AAAA,YACtE;AAAA,UACF;AAAA,QACF,EAAE;AAGF,eAAO,CAAC,GAAG,gBAAgB,GAAG,eAAe,EAAE,MAAM,GAAG,KAAK;AAAA,MAC/D;AAAA;AAAA;AAAA;AAAA;AAAA,MAMO,aAAmB;AACxB,aAAK,kBAAkB;AAAA,MACzB;AAAA;AAAA;AAAA;AAAA,MAKO,cAAsB;AAC3B,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA,MAKO,YAAuB;AAC5B,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA;AAAA;;;AC/MA,SAAoB,mBAAAE,wBAAuB;AAqBpC,SAAS,SAAS,MAAwD;AAC/E,QAAM,MAAM,KAAK,WAAW,YAAY,KAAK,WAAW,mBAAmB,cAAc;AAKzF,SAAO;AACT;AA0DA,eAAsB,eACpB,QACA,MAC6B;AAC7B,MAAI,OAAO,SAAS,aAAa;AAC/B,WAAO,MAAM,mBAAmB,QAAQ,OAAO,IAAI,IAAI;AAAA,EACzD,OAAO;AAEL,QAAIA,iBAAgB,OAAO,SAAS,GAAG;AACrC,aAAO,IAAI,yBAAyB,OAAO,IAAI,OAAO,WAAY,IAAI;AAAA,IACxE;AAGA,WAAO,aAAa,EAAE,YAAY,OAAO,EAAE;AAAA,EAC7C;AACF;AAzGA;AAAA;AAAA;AAAA;AAEA,IAAAC;AAGA;AAAA;AAAA;;;ACLA,IAAAC,iBAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AACA;AACA;AACA,IAAAC;AACA;AAEA;AAAA;AAAA;;;ACNA;AAAA;AAAA;AAAA;AAAA;;;ACiFO,SAAS,qBAAqB,UAAkB,aAAsC;AAC3F,SAAO,mBAAmB,QAAQ,KAAK,WAAW;AACpD;AAnFA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,SAAoB,UAAAC,eAA8C;AAalE,eAAsB,kBACpB,aACA,UACA,QACyB;AACzB,QAAM,UAA0B,CAAC;AAEjC,aAAW,cAAc,aAAa;AACpC,QAAI;AAEF,YAAM,SAAS,MAAM,YAAY,YAAY,UAAU,MAAM;AAC7D,cAAQ,KAAK,MAAM;AAAA,IACrB,SAAS,OAAO;AACd,aAAO,MAAM,0BAA0B,KAAK;AAI5C,UAAI,oBAAoB,WAAW;AACnC,UAAI,WAAW,QAAQ,WAAW,KAAK,SAAS,GAAG;AACjD,6BAAqB;AAAA,QAAW,WAAW,KAAK,KAAK,IAAI,CAAC;AAAA,MAC5D;AACA,UAAI,WAAW,QAAQ,QAAW;AAChC,6BAAqB;AAAA,OAAU,WAAW,GAAG;AAAA,MAC/C;AACA,cAAQ,KAAK;AAAA,QACX,cAAc;AAAA,QACd,QAAQ;AAAA,QACR,SAAS,0BACP,iBAAiB,QAAQ,MAAM,UAAU,eAC3C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAUA,eAAe,YACb,YACA,UACA,QACuB;AACvB,QAAM,EAAE,UAAU,MAAM,IAAI,IAAI;AAGhC,MAAI,eAAe;AACnB,MAAI,KAAK,SAAS,GAAG;AACnB,oBAAgB;AAAA,QAAW,KAAK,KAAK,IAAI,CAAC;AAAA,EAC5C;AACA,MAAI,QAAQ,QAAW;AACrB,oBAAgB;AAAA,OAAU,GAAG;AAAA,EAC/B;AAGA,QAAM,WAA+B;AAAA,IACnC,OAAO;AAAA,IACP,SAAS,CAAC;AAAA;AAAA,EACZ;AAEA,QAAM,UAA6B,CAAC;AACpC,aAAW,OAAO,MAAM;AACtB,YAAQ,GAAG,IAAI;AAAA,MACb,OAAO,OAAO;AAAA,MACd,OAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI;AACF,UAAM,SAAS,MAAM,SAAS;AAAA,MAC5B,OAAO;AAAA,MACP,OAAO;AAAA,MACP;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA;AAAA,MACA,MACI;AAAA,QACE,QAAQ;AAAA,UACN,OAAO;AAAA,UACP,OAAO;AAAA,QACT;AAAA,QACA,MAAM;AAAA,QACN,MAAM,CAAC;AAAA,MACT,IACA;AAAA,IACN;AAEA,QAAI,OAAO,WAAWA,QAAO,IAAI;AAC/B,aAAO;AAAA,QACL;AAAA,QACA,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,QAAQ,OAAO,KAAK,OAAO,KAAK;AAAA,MAClC;AAAA,IACF,OAAO;AACL,aAAO;AAAA,QACL;AAAA,QACA,QAAQ;AAAA,QACR,SAAS,OAAO,WAAW;AAAA,MAC7B;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,WAAO,MAAM,sBAAsB,KAAK;AACxC,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,MACR,SAAS,sBAAsB,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,IACzF;AAAA,EACF;AACF;AAQO,SAAS,wBAAwB,QAGtC;AACA,MAAI,CAAC,OAAO,WAAW;AACrB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,YAAY;AACtB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,UAAU;AACpB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,cAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,KAAK;AACzB;AApKA;AAAA;AAAA;AAGA;AAAA;AAAA;;;ACHA,IAAAC,cAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AACA,IAAAC;AAAA;AAAA;;;AC4BA,SAAS,YAAoC;AAC3C,MAAI;AACF,UAAM,WAAW,aAAa;AAC9B,WAAO,SAAS,UAAU;AAAA,EAC5B,QAAQ;AACN,WAAO,KAAK,gDAAgD;AAC5D,WAAO;AAAA,EACT;AACF;AAMA,SAAS,WAAoC;AAC3C,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,OAAQ,QAAO;AAIpB,QAAM,QAAS,OAAe;AAC9B,MAAI,CAAC,OAAO;AACV,WAAO,KAAK,wDAAwD;AACpE,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAKA,SAAS,gBAAgB,WAA2B;AAClD,QAAM,OAAO,IAAI,KAAK,SAAS;AAC/B,SAAO,KAAK,eAAe;AAC7B;AAwYO,SAAS,sBAA4B;AAC1C,MAAI,OAAO,WAAW,YAAa;AAEnC,QAAM,MAAM;AACZ,MAAI,WAAW,IAAI,YAAY,CAAC;AAChC,MAAI,SAAS,SAAS;AACxB;AA9cA,IAqEa;AArEb;AAAA;AAAA;AAAA;AACA;AACA;AACA;AAkEO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA,MAI5B,WAAiB;AACf,cAAM,SAAS,UAAU;AACzB,YAAI,CAAC,OAAQ;AAGb,gBAAQ,MAAM,4BAAqB;AACnC,eAAO,KAAK,aAAa,OAAO,YAAY,CAAC,EAAE;AAC/C,eAAO,KAAK,cAAc,OAAO,WAAW,IAAI,eAAU,mBAAc,EAAE;AAE1E,eAAO,UAAU,EACd,KAAK,CAAC,WAAW;AAChB,iBAAO,KAAK,gBAAgB;AAC5B,iBAAO,KAAK,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,QAC7C,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,iBAAO,KAAK,yBAAyB,IAAI,OAAO,EAAE;AAAA,QACpD,CAAC,EACA,QAAQ,MAAM;AAEb,kBAAQ,SAAS;AAAA,QACnB,CAAC;AAAA,MACL;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,qBAAqB,UAAkC;AAC3D,cAAM,SAAS,UAAU;AACzB,YAAI,CAAC,OAAQ;AAEb,YAAI;AACF,gBAAM,UAAU,MAAM,OAAO,kBAAkB,QAAQ;AAGvD,kBAAQ,MAAM,8BAAuB,WAAW,KAAK,QAAQ,MAAM,EAAE,EAAE;AACvE,iBAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;AAEtC,cAAI,QAAQ,SAAS,GAAG;AAEtB,kBAAM,WAAW,oBAAI,IAA6B;AAClD,uBAAW,UAAU,SAAS;AAC5B,kBAAI,CAAC,SAAS,IAAI,OAAO,QAAQ,GAAG;AAClC,yBAAS,IAAI,OAAO,UAAU,CAAC,CAAC;AAAA,cAClC;AACA,uBAAS,IAAI,OAAO,QAAQ,EAAG,KAAK,MAAM;AAAA,YAC5C;AAEA,uBAAW,CAAC,QAAQ,aAAa,KAAK,UAAU;AAE9C,sBAAQ,MAAM,WAAW,MAAM,KAAK,cAAc,MAAM,WAAW;AAGnE,oBAAM,SAAS,cAAc,KAAK,CAAC,GAAG,MAAM;AAC1C,sBAAM,QAAQ,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa,EAAE,WAAW,YAAY;AACzF,sBAAM,QAAQ,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa,EAAE,WAAW,YAAY;AACzF,uBAAO,IAAI,KAAK,KAAK,EAAE,QAAQ,IAAI,IAAI,KAAK,KAAK,EAAE,QAAQ;AAAA,cAC7D,CAAC;AAGD,yBAAW,UAAU,OAAO,MAAM,GAAG,EAAE,GAAG;AACxC,sBAAM,gBAAgB,OAAO,OAAO,eAAe,WAC/C,OAAO,aACP,OAAO,WAAW,YAAY;AAClC,uBAAO;AAAA,kBACL,KAAK,OAAO,OAAO,MAAM,GAAG,EAAE,CAAC,SAAS,gBAAgB,aAAa,CAAC,KAClE,OAAO,YAAY,IAAI,OAAO,iBAAiB;AAAA,gBACrD;AAAA,cACF;AAEA,kBAAI,OAAO,SAAS,IAAI;AACtB,uBAAO,KAAK,aAAa,OAAO,SAAS,EAAE,OAAO;AAAA,cACpD;AAGA,sBAAQ,SAAS;AAAA,YACnB;AAAA,UACF;AAGA,kBAAQ,SAAS;AAAA,QACnB,SAAS,KAAU;AACjB,iBAAO,KAAK,oCAAoC,IAAI,OAAO,EAAE;AAAA,QAC/D;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,0BAAyC;AAC7C,cAAM,SAAS,UAAU;AACzB,YAAI,CAAC,OAAQ;AAEb,YAAI;AACF,gBAAM,gBAAgB,MAAM,OAAO,iBAAiB;AAGpD,kBAAQ,MAAM,gCAAyB;AACvC,iBAAO,KAAK,UAAU,cAAc,MAAM,EAAE;AAE5C,cAAI,cAAc,SAAS,GAAG;AAE5B,oBAAQ;AAAA,cACN,cAAc,IAAI,CAAC,SAA6B;AAAA,gBAC9C,UAAU,IAAI;AAAA,gBACd,QAAQ,IAAI,UAAU;AAAA,gBACtB,KAAK,OAAO,IAAI,QAAQ,WACpB,IAAI,IAAI,QAAQ,CAAC,IACjB,IAAI,KAAK,QAAQ,OAAO,QAAQ,CAAC,KAAK;AAAA,cAC5C,EAAE;AAAA,YACJ;AAAA,UACF;AAGA,kBAAQ,SAAS;AAAA,QACnB,SAAS,KAAU;AACjB,iBAAO,KAAK,uCAAuC,IAAI,OAAO,EAAE;AAAA,QAClE;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,gBAAgB,QAA+B;AACnD,cAAM,QAAQ,SAAS;AACvB,YAAI,CAAC,MAAO;AAEZ,YAAI;AAEF,gBAAM,SAAS,MAAM,sBAAsB,OAAO,6CAAkC,CAAC;AAGrF,gBAAM,gBAAgB,OAAO,KAC1B,OAAO,CAAC,QAAa,IAAI,OAAO,IAAI,IAAI,WAAW,MAAM,EACzD,IAAI,CAAC,QAAa,IAAI,GAAG;AAG5B,kBAAQ,MAAM,2BAAoB,MAAM,EAAE;AAC1C,iBAAO,KAAK,uBAAuB,cAAc,MAAM,EAAE;AAEzD,cAAI,cAAc,SAAS,GAAG;AAE5B,kBAAM,SAAS,cAAc;AAAA,cAAK,CAAC,GAAQ,MACzC,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ;AAAA,YAClE;AAIA,oBAAQ;AAAA,cACN,OAAO,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,SAAc;AAAA,gBACrC,MAAM,gBAAgB,IAAI,SAAS;AAAA,gBACnC,SAAS,IAAI,WAAW;AAAA,gBACxB,UAAU,IAAI,WAAW,IAAI,IAAI,WAAW,KAAM,QAAQ,CAAC,CAAC,MAAM;AAAA,gBAClE,UAAU,IAAI;AAAA,cAChB,EAAE;AAAA,YACJ;AAEA,gBAAI,OAAO,SAAS,IAAI;AACtB,qBAAO,KAAK,WAAW,OAAO,SAAS,EAAE,oBAAoB;AAAA,YAC/D;AAAA,UACF;AAGA,kBAAQ,SAAS;AAAA,QACnB,SAAS,KAAU;AACjB,iBAAO,KAAK,+BAA+B,IAAI,OAAO,EAAE;AAAA,QAC1D;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,YAAY,SAA+B,QAAgB,IAAmB;AAClF,cAAM,QAAQ,SAAS;AACvB,YAAI,CAAC,MAAO;AAEZ,YAAI;AACF,gBAAM,SAAS,gBAAgB,QAAQ,OAAO,CAAC;AAC/C,cAAI,CAAC,QAAQ;AACX,mBAAO,KAAK,0BAA0B,OAAO,EAAE;AAC/C;AAAA,UACF;AAEA,gBAAM,SAAS,MAAM,sBAAsB,OAAO,MAAM;AAGxD,kBAAQ,MAAM,wBAAiB,OAAO,EAAE;AACxC,iBAAO,KAAK,UAAU,OAAO,KAAK,MAAM,EAAE;AAC1C,iBAAO,KAAK,WAAW,MAAM,EAAE;AAE/B,cAAI,OAAO,KAAK,SAAS,GAAG;AAC1B,mBAAO,KAAK,mBAAmB;AAC/B,kBAAM,UAAU,OAAO,KAAK,MAAM,GAAG,KAAK,IAAI,OAAO,OAAO,KAAK,MAAM,CAAC;AAExE,uBAAW,OAAO,SAAS;AACzB,qBAAO,KAAK;AAAA,EAAK,IAAI,EAAE,GAAG;AAC1B,qBAAO,KAAK,KAAK,UAAU,IAAI,KAAK,MAAM,CAAC,CAAC;AAAA,YAC9C;AAEA,gBAAI,OAAO,KAAK,SAAS,OAAO;AAC9B,qBAAO,KAAK;AAAA,UAAa,OAAO,KAAK,SAAS,KAAK,iBAAiB;AAAA,YACtE;AAAA,UACF;AAGA,kBAAQ,SAAS;AAAA,QACnB,SAAS,KAAU;AACjB,iBAAO,KAAK,6BAA6B,IAAI,OAAO,EAAE;AAAA,QACxD;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,SAAwB;AAC5B,cAAM,QAAQ,SAAS;AACvB,YAAI,CAAC,MAAO;AAEZ,YAAI;AACF,gBAAM,OAAO,MAAM,MAAM,KAAK;AAG9B,kBAAQ,MAAM,mCAAyB;AACvC,iBAAO,KAAK,kBAAkB,KAAK,OAAO,EAAE;AAC5C,iBAAO,KAAK,oBAAoB,KAAK,SAAS,EAAE;AAChD,iBAAO,KAAK,oBAAoB,KAAK,UAAU,EAAE;AAEjD,cAAI,eAAe,MAAM;AACvB,mBAAO,KAAK,eAAgB,KAAa,aAAa,KAAK,OAAO,IAAI,KAAK;AAAA,UAC7E;AAGA,iBAAO,KAAK,4BAA4B;AACxC,gBAAM,UAAU,MAAM,MAAM,QAAQ,EAAE,cAAc,MAAM,CAAC;AAC3D,gBAAM,aAAa,oBAAI,IAAoB;AAE3C,qBAAW,OAAO,QAAQ,MAAM;AAE9B,gBAAI,SAAS;AACb,uBAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,eAAe,GAAG;AAChE,kBAAI,IAAI,GAAG,WAAW,UAAU,GAAG;AACjC,yBAAS;AACT;AAAA,cACF;AAAA,YACF;AACA,uBAAW,IAAI,SAAS,WAAW,IAAI,MAAM,KAAK,KAAK,CAAC;AAAA,UAC1D;AAGA,kBAAQ;AAAA,YACN,MAAM,KAAK,WAAW,QAAQ,CAAC,EAC5B,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAC1B,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,EAAE;AAAA,UAC7C;AAGA,kBAAQ,SAAS;AAAA,QACnB,SAAS,KAAU;AACjB,iBAAO,KAAK,gCAAgC,IAAI,OAAO,EAAE;AAAA,QAC3D;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,eAAqB;AAEnB,gBAAQ,MAAM,oCAA6B;AAC3C,eAAO,KAAK,6BAA6B;AAEzC,mBAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,eAAe,GAAG;AAC5D,iBAAO,KAAK,KAAK,KAAK,OAAO,EAAE,CAAC,oBAAe,MAAM,GAAG;AAAA,QAC1D;AAGA,gBAAQ,SAAS;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,OAAO,iBAA0B,OAAwB;AAC7D,cAAM,QAAQ,SAAS;AACvB,cAAM,SAAS,UAAU;AACzB,YAAI,CAAC,SAAS,CAAC,OAAQ,QAAO;AAE9B,YAAI;AACF,gBAAM,OAAY;AAAA,YAChB,UAAU,OAAO,YAAY;AAAA,YAC7B,UAAU,OAAO,WAAW;AAAA,YAC5B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC;AAEA,cAAI,gBAAgB;AAElB,kBAAM,UAAU,MAAM,MAAM,QAAQ,EAAE,cAAc,KAAK,CAAC;AAC1D,iBAAK,YAAY,QAAQ,KAAK,IAAI,CAAC,SAAc;AAAA,cAC/C,IAAI,IAAI;AAAA,cACR,KAAK,IAAI;AAAA,YACX,EAAE;AACF,iBAAK,YAAY,QAAQ,KAAK;AAAA,UAChC,OAAO;AAEL,kBAAM,UAAU,MAAM,MAAM,QAAQ,EAAE,cAAc,MAAM,CAAC;AAC3D,iBAAK,YAAY,QAAQ,KAAK;AAE9B,kBAAM,aAAa,oBAAI,IAAoB;AAC3C,uBAAW,OAAO,QAAQ,MAAM;AAC9B,kBAAI,SAAS;AACb,yBAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,eAAe,GAAG;AAChE,oBAAI,IAAI,GAAG,WAAW,UAAU,GAAG;AACjC,2BAAS;AACT;AAAA,gBACF;AAAA,cACF;AACA,yBAAW,IAAI,SAAS,WAAW,IAAI,MAAM,KAAK,KAAK,CAAC;AAAA,YAC1D;AACA,iBAAK,YAAY,OAAO,YAAY,UAAU;AAAA,UAChD;AAEA,gBAAM,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AACzC,iBAAO,KAAK,yEAAyE;AACrF,iBAAO,KAAK,yCAAyC;AACrD,cAAI,CAAC,gBAAgB;AACnB,mBAAO,KAAK,gEAAgE;AAAA,UAC9E;AACA,iBAAO;AAAA,QACT,SAAS,KAAU;AACjB,iBAAO,KAAK,6BAA6B,IAAI,OAAO,EAAE;AACtD,iBAAO;AAAA,QACT;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,MAAM,IAAI,SAAgE;AACxE,cAAM,QAAQ,SAAS;AACvB,YAAI,CAAC,MAAO;AAEZ,YAAI;AACF,gBAAM,SAAS,MAAM,QAAQ,KAAK;AAClC,iBAAO,KAAK,8BAA8B;AAC1C,iBAAO,KAAK,MAAM;AAAA,QACpB,SAAS,KAAU;AACjB,iBAAO,KAAK,+BAA+B,IAAI,OAAO,EAAE;AAAA,QAC1D;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAKA,OAAa;AACX,eAAO,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAoBf;AAAA,MACC;AAAA,IACF;AAkBA,wBAAoB;AAAA;AAAA;;;ACjdpB;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAAA;AAAA;","names":["DocType","log","moment","AggregationMode","toCourseElo","moment","toCourseElo","toCourseElo","DEFAULT_MIN_COUNT","types_exports","init_types","toCourseElo","init_","Navigators","NavigatorRole","blankCourseElo","toCourseElo","filterAllDocsByPrefix","getCourseDB","moment","init_classroomDB","getStartAndEndKeys","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","Status","fetch","moment","process","getCourseDB","filterAllDocsByPrefix","getStartAndEndKeys","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","Status","moment","log","record","hasActiveFilter","init_classroomDB","init_courseDB","init_courseDB","Status","init_types","init_types"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/interfaces/adminDB.ts","../../src/core/interfaces/classroomDB.ts","../../src/impl/common/SyncStrategy.ts","../../src/util/logger.ts","../../src/core/types/types-legacy.ts","../../src/core/util/index.ts","../../src/impl/couch/pouchdb-setup.ts","../../src/util/dataDirectory.ts","../../src/impl/common/userDBHelpers.ts","../../src/util/Loggable.ts","../../src/impl/couch/updateQueue.ts","../../src/impl/couch/user-course-relDB.ts","../../src/impl/couch/clientCache.ts","../../src/impl/couch/courseAPI.ts","../../src/impl/couch/courseLookupDB.ts","../../src/core/navigators/PipelineDebugger.ts","../../src/core/navigators/generators/CompositeGenerator.ts","../../src/core/navigators/generators/elo.ts","../../src/core/navigators/generators/index.ts","../../src/core/navigators/generators/prescribed.ts","../../src/core/navigators/generators/srs.ts","../../src/core/navigators/generators/types.ts","../../src/core/types/contentNavigationStrategy.ts","../../src/core/navigators/filters/WeightedFilter.ts","../../src/core/navigators/filters/eloDistance.ts","../../src/core/navigators/filters/hierarchyDefinition.ts","../../src/core/navigators/filters/userTagPreference.ts","../../src/core/navigators/filters/index.ts","../../src/core/navigators/filters/inferredPreferenceStub.ts","../../src/core/navigators/filters/interferenceMitigator.ts","../../src/core/navigators/filters/relativePriority.ts","../../src/core/navigators/filters/types.ts","../../src/core/navigators/filters/userGoalStub.ts","../../src/core/orchestration/gradient.ts","../../src/core/orchestration/learning.ts","../../src/core/orchestration/signal.ts","../../src/core/orchestration/recording.ts","../../src/core/orchestration/index.ts","../../src/core/navigators/Pipeline.ts","../../src/core/navigators/defaults.ts","../../src/core/navigators/PipelineAssembler.ts","../../src/core/navigators/index.ts","../../src/impl/couch/courseDB.ts","../../src/impl/couch/classroomDB.ts","../../src/impl/couch/adminDB.ts","../../src/impl/couch/CourseSyncService.ts","../../src/impl/couch/auth.ts","../../src/impl/couch/CouchDBSyncStrategy.ts","../../src/impl/couch/index.ts","../../src/impl/common/BaseUserDB.ts","../../src/impl/common/index.ts","../../src/factory.ts","../../src/study/TagFilteredContentSource.ts","../../src/core/interfaces/contentSource.ts","../../src/core/interfaces/courseDB.ts","../../src/core/interfaces/dataLayerProvider.ts","../../src/core/interfaces/userDB.ts","../../src/core/interfaces/index.ts","../../src/core/types/user.ts","../../src/core/types/strategyState.ts","../../src/core/types/userOutcome.ts","../../src/core/bulkImport/cardProcessor.ts","../../src/core/bulkImport/types.ts","../../src/core/bulkImport/index.ts","../../src/core/UserDBDebugger.ts","../../src/core/index.ts"],"sourcesContent":["import { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\n\n/**\n * Admin functionality\n */\nexport interface AdminDBInterface {\n /**\n * Get all users\n */\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n getUsers(): Promise<PouchDB.Core.Document<{}>[]>;\n\n /**\n * Get all courses\n */\n getCourses(): Promise<CourseConfig[]>;\n\n /**\n * Remove a course\n */\n removeCourse(id: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all classrooms\n */\n getClassrooms(): Promise<(ClassroomConfig & { _id: string })[]>;\n}\n","import { ClassroomConfig } from '@vue-skuilder/common';\n\n/**\n * Classroom management\n */\nexport interface ClassroomDBInterface {\n /**\n * Get classroom config\n */\n getConfig(): ClassroomConfig;\n\n /**\n * Get assigned content\n */\n getAssignedContent(): Promise<AssignedContent[]>;\n}\n\nexport interface TeacherClassroomDBInterface extends ClassroomDBInterface {\n /**\n * For teacher interfaces: assign content\n */\n assignContent?(content: AssignedContent): Promise<boolean>;\n\n /**\n * For teacher interfaces: remove content\n */\n removeContent?(content: AssignedContent): Promise<void>;\n}\n\n/**\n * Student-facing classroom interface.\n * Content is accessed via StudyContentSource.getWeightedCards().\n */\nexport type StudentClassroomDBInterface = ClassroomDBInterface;\n\nexport type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;\n\nexport interface AssignedTag extends ContentBase {\n type: 'tag';\n tagID: string;\n}\nexport interface AssignedCourse extends ContentBase {\n type: 'course';\n}\nexport interface AssignedCard extends ContentBase {\n type: 'card';\n cardID: string;\n}\n\ninterface ContentBase {\n type: 'course' | 'tag' | 'card';\n /**\n * Username of the assigning teacher.\n */\n assignedBy: string;\n /**\n * Date the content was assigned.\n */\n assignedOn: moment.Moment;\n /**\n * A 'due' date for this assigned content, for scheduling content\n * in advance. Content will not be actively pushed to students until\n * this date.\n */\n activeOn: moment.Moment;\n courseID: string;\n}\n","// packages/db/src/impl/common/SyncStrategy.ts\n\nimport type { AccountCreationResult, AuthenticationResult } from './types';\n\n/**\n * Strategy interface for handling user data synchronization\n * Different implementations handle remote sync vs local-only storage\n */\nexport interface SyncStrategy {\n /**\n * Set up the remote database for a user\n * @param username The username to set up remote DB for\n * @returns PouchDB database instance (may be same as local for no-op)\n */\n setupRemoteDB(username: string): PouchDB.Database;\n\n /**\n * Get the database to use for write operations (local-first approach)\n * @param username The username to get write DB for\n * @returns PouchDB database instance for write operations\n */\n getWriteDB?(username: string): PouchDB.Database;\n\n /**\n * Start synchronization between local and remote databases\n * @param localDB The local PouchDB instance\n * @param remoteDB The remote PouchDB instance\n */\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n\n /**\n * Stop synchronization (optional - for cleanup)\n */\n stopSync?(): void;\n\n /**\n * Whether this strategy supports account creation\n */\n canCreateAccount(): boolean;\n\n /**\n * Whether this strategy supports authentication\n */\n canAuthenticate(): boolean;\n\n /**\n * Create a new user account (if supported)\n * @param username The username for the new account\n * @param password The password for the new account\n */\n createAccount?(username: string, password: string): Promise<AccountCreationResult>;\n\n /**\n * Authenticate a user (if supported)\n * @param username The username to authenticate\n * @param password The password to authenticate with\n */\n authenticate?(username: string, password: string): Promise<AuthenticationResult>;\n\n /**\n * Log out the current user (if supported)\n */\n logout?(): Promise<AuthenticationResult>;\n\n /**\n * Get the current logged-in username\n * Returns the username if logged in, or a default guest username\n */\n getCurrentUsername(): Promise<string>;\n}\n\n/**\n * Base class for sync strategies with common functionality\n */\nexport abstract class BaseSyncStrategy implements SyncStrategy {\n abstract setupRemoteDB(username: string): PouchDB.Database;\n abstract startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void;\n abstract canCreateAccount(): boolean;\n abstract canAuthenticate(): boolean;\n abstract getCurrentUsername(): Promise<string>;\n\n stopSync?(): void {\n // Default no-op implementation\n }\n\n async createAccount(_username: string, _password: string): Promise<AccountCreationResult> {\n throw new Error('Account creation not supported by this sync strategy');\n }\n\n async authenticate(_username: string, _password: string): Promise<AuthenticationResult> {\n throw new Error('Authentication not supported by this sync strategy');\n }\n\n async logout(): Promise<AuthenticationResult> {\n throw new Error('Logout not supported by this sync strategy');\n }\n}\n","/**\n * Simple logging utility for @vue-skuilder/db package\n *\n * This utility provides environment-aware logging with ESLint suppressions\n * to resolve console statement violations while maintaining logging functionality.\n */\n\nconst isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';\n\nexport const logger = {\n /**\n * Debug-level logging - only shown in development\n */\n debug: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.debug(`[DB:DEBUG] ${message}`, ...args);\n }\n },\n\n /**\n * Info-level logging - general information\n */\n info: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.info(`[DB:INFO] ${message}`, ...args);\n },\n\n /**\n * Warning-level logging - potential issues\n */\n warn: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.warn(`[DB:WARN] ${message}`, ...args);\n },\n\n /**\n * Error-level logging - serious problems\n */\n error: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.error(`[DB:ERROR] ${message}`, ...args);\n },\n\n /**\n * Log function for backward compatibility with existing log() usage\n */\n log: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.log(`[DB:LOG] ${message}`, ...args);\n }\n },\n};\n","import { CourseElo, Answer, Evaluation } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { logger } from '../../util/logger';\n\nexport const GuestUsername: string = 'sk-guest-';\n\nexport const log = (message: string): void => {\n logger.log(message);\n};\n\nexport enum DocType {\n DISPLAYABLE_DATA = 'DISPLAYABLE_DATA',\n CARD = 'CARD',\n DATASHAPE = 'DATASHAPE',\n QUESTIONTYPE = 'QUESTION',\n VIEW = 'VIEW',\n PEDAGOGY = 'PEDAGOGY',\n CARDRECORD = 'CARDRECORD',\n SCHEDULED_CARD = 'SCHEDULED_CARD',\n TAG = 'TAG',\n NAVIGATION_STRATEGY = 'NAVIGATION_STRATEGY',\n STRATEGY_STATE = 'STRATEGY_STATE',\n USER_OUTCOME = 'USER_OUTCOME',\n STRATEGY_LEARNING_STATE = 'STRATEGY_LEARNING_STATE',\n}\n\nexport interface QualifiedCardID {\n courseID: string;\n cardID: string;\n}\n\n/**\n * Interface for all data on course content and pedagogy stored\n * in the c/pouch database.\n */\nexport interface SkuilderCourseData {\n course: string;\n docType: DocType;\n}\n\nexport interface Tag extends SkuilderCourseData {\n docType: DocType.TAG;\n name: string;\n snippet: string; // 200 char description of the tag\n wiki: string; // 3000 char md-friendly description\n taggedCards: PouchDB.Core.DocumentId[];\n author: string;\n}\nexport interface TagStub {\n name: string;\n snippet: string;\n count: number; // the number of cards that have this tag applied\n}\n\nexport interface CardData extends SkuilderCourseData {\n docType: DocType.CARD;\n id_displayable_data: PouchDB.Core.DocumentId[];\n id_view: PouchDB.Core.DocumentId;\n elo: CourseElo;\n author: string;\n}\n\n/** A list of populated courses in the DB */\nexport interface CourseListData extends PouchDB.Core.Response {\n courses: string[];\n}\n\n/**\n * The data used to hydrate viewable components (questions, info, etc)\n */\nexport interface DisplayableData extends SkuilderCourseData {\n docType: DocType.DISPLAYABLE_DATA;\n author?: string;\n id_datashape: PouchDB.Core.DocumentId;\n data: Field[];\n _attachments?: { [index: string]: PouchDB.Core.FullAttachment };\n}\n\nexport interface Field {\n data: unknown;\n name: string;\n}\n\nexport interface DataShapeData extends SkuilderCourseData {\n docType: DocType.DATASHAPE;\n _id: PouchDB.Core.DocumentId;\n questionTypes: PouchDB.Core.DocumentId[];\n}\n\nexport interface QuestionData extends SkuilderCourseData {\n docType: DocType.QUESTIONTYPE;\n _id: PouchDB.Core.DocumentId;\n viewList: string[];\n dataShapeList: PouchDB.Core.DocumentId[];\n}\n\nexport const DocTypePrefixes = {\n [DocType.CARD]: 'c',\n [DocType.DISPLAYABLE_DATA]: 'dd',\n [DocType.TAG]: 'TAG',\n [DocType.CARDRECORD]: 'cardH',\n [DocType.SCHEDULED_CARD]: 'card_review_',\n // Add other doctypes here as they get prefixed IDs\n [DocType.DATASHAPE]: 'DATASHAPE',\n [DocType.QUESTIONTYPE]: 'QUESTION',\n [DocType.VIEW]: 'VIEW',\n [DocType.PEDAGOGY]: 'PEDAGOGY',\n [DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',\n [DocType.STRATEGY_STATE]: 'STRATEGY_STATE',\n [DocType.USER_OUTCOME]: 'USER_OUTCOME',\n [DocType.STRATEGY_LEARNING_STATE]: 'STRATEGY_LEARNING_STATE',\n} as const;\n\nexport interface CardHistory<T extends CardRecord> {\n _id: PouchDB.Core.DocumentId;\n /**\n * The CouchDB id of the card\n */\n cardID: PouchDB.Core.DocumentId;\n\n /**\n * The ID of the course\n */\n courseID: string;\n\n /**\n * The to-date largest interval between successful\n * card reviews. `0` indicates no successful reviews.\n */\n bestInterval: number;\n\n /**\n * The number of times that a card has been\n * failed in review\n */\n lapses: number;\n\n /**\n * The number of consecutive successful impressions\n * on this card\n */\n streak: number;\n\n records: T[];\n}\n\nexport interface CardRecord {\n /**\n * The CouchDB id of the card\n */\n cardID: string;\n /**\n * The ID of the course\n */\n courseID: string;\n /**\n * Number of milliseconds that the user spent before dismissing\n * the card (ie, \"I've read this\" or \"here is my answer\")\n *\n * //TODO: this (sometimes?) wants to be replaced with a rich\n * recording of user activity in working the question\n */\n timeSpent: number;\n /**\n * The date-time that the card was rendered. timeStamp + timeSpent will give the\n * time of user submission.\n */\n timeStamp: Moment;\n}\n\nexport interface QuestionRecord extends CardRecord, Evaluation {\n userAnswer: Answer;\n /**\n * The number of incorrect user submissions prededing this submisstion.\n *\n * eg, if a user is asked 7*6=__, submitting 46, 48, 42 will result in three\n * records being created having 0, 1, and 2 as their recorded 'priorAttempts' values\n */\n priorAttemps: number;\n}\n","import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';\n\nexport function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {\n return isQuestionRecord(h.records[0]);\n}\n\nexport function isQuestionRecord(c: CardRecord): c is QuestionRecord {\n return (c as QuestionRecord).userAnswer !== undefined;\n}\n\nexport function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {\n return `${DocTypePrefixes[DocType.CARDRECORD]}-${courseID}-${cardID}`;\n}\n\nexport function parseCardHistoryID(id: string): {\n courseID: string;\n cardID: string;\n} {\n const split = id.split('-');\n let error: string = '';\n error += split.length === 3 ? '' : `\\n\\tgiven ID has incorrect number of '-' characters`;\n error +=\n split[0] === DocTypePrefixes[DocType.CARDRECORD] ? '' : `\n\tgiven ID does not start with ${DocTypePrefixes[DocType.CARDRECORD]}`;\n\n if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {\n return {\n courseID: split[1],\n cardID: split[2],\n };\n } else {\n throw new Error('parseCardHistory Error:' + error);\n }\n}\n\ninterface PouchDBError extends Error {\n error?: string;\n reason?: string;\n}\n\nexport function docIsDeleted(e: PouchDBError): boolean {\n return Boolean(e?.error === 'not_found' && e?.reason === 'deleted');\n}\n","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Disable PouchDB debug logging to prevent interference with CLI prompts\n// Debug logging (like DerivedLogger.emit) will still go to the TUI log file\n// if initializeTuiLogging() has been called, but won't clutter terminal output\nif (typeof PouchDB.debug !== 'undefined') {\n PouchDB.debug.disable();\n}\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n","// Cross-platform data directory utilities for PouchDB\n// Provides OS-appropriate application data directories\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { logger } from './logger';\nimport { ENV } from '@db/factory';\n\n/**\n * Get the application data directory for the current platform\n * Uses ~/.tuilder as requested by user for simplicity\n */\nexport function getAppDataDirectory(): string {\n if (ENV.LOCAL_STORAGE_PREFIX) {\n return path.join(os.homedir(), `.tuilder`, ENV.LOCAL_STORAGE_PREFIX);\n } else {\n return path.join(os.homedir(), '.tuilder');\n }\n}\n\n/**\n * Ensure the application data directory exists\n * Creates directory recursively if it doesn't exist\n */\nexport async function ensureAppDataDirectory(): Promise<string> {\n const appDataDir = getAppDataDirectory();\n \n try {\n await fs.promises.mkdir(appDataDir, { recursive: true });\n } catch (err: any) {\n if (err.code !== 'EEXIST') {\n throw new Error(`Failed to create app data directory ${appDataDir}: ${err.message}`);\n }\n }\n \n return appDataDir;\n}\n\n/**\n * Get the full path for a PouchDB database file\n * @param dbName - The database name (e.g., 'userdb-Colin')\n */\nexport function getDbPath(dbName: string): string {\n return path.join(getAppDataDirectory(), dbName);\n}\n\n/**\n * Initialize data directory for PouchDB usage\n * Should be called once at application startup\n */\nexport async function initializeDataDirectory(): Promise<void> {\n await ensureAppDataDirectory();\n \n // Log initialization\n logger.info(`PouchDB data directory initialized: ${getAppDataDirectory()}`);\n}","// packages/db/src/impl/common/userDBHelpers.ts\n\nimport moment from 'moment';\nimport { DocType, DocTypePrefixes } from '@db/core';\nimport { logger } from '../../util/logger';\nimport { ScheduledCard } from '@db/core/types/user';\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nimport pouch from '../couch/pouchdb-setup';\nimport { getDbPath } from '../../util/dataDirectory';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n const expiryDocID: string = 'GuestAccountExpirationDate';\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\n/**\n * Get local user database with appropriate adapter for environment\n */\nexport function getLocalUserDB(username: string): PouchDB.Database {\n // // Choose adapter based on environment\n //\n // Not certain of this is required. Let's let pouch's auto detection\n // handle it until we specifically know we need to intervene.\n //\n // let adapter: string;\n // if (typeof window !== 'undefined') {\n // // Browser environment - use IndexedDB\n // adapter = 'idb';\n // } else {\n // // Node.js environment (tests) - use memory adapter\n // adapter = 'memory';\n // }\n\n const dbName = `userdb-${username}`;\n \n // Use proper data directory in Node.js, browser will use IndexedDB\n if (typeof window === 'undefined') {\n // Node.js environment - use filesystem with proper app data directory\n return new pouch(getDbPath(dbName), {});\n } else {\n // Browser environment - use default (IndexedDB)\n return new pouch(dbName, {});\n }\n}\n\n/**\n * Schedule a card review (strategy-agnostic version)\n */\nexport function scheduleCardReviewLocal(\n userDB: PouchDB.Database,\n review: {\n card_id: PouchDB.Core.DocumentId;\n time: moment.Moment;\n course_id: string;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }\n) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void userDB.put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\n/**\n * Remove a scheduled card review (strategy-agnostic version)\n */\nexport async function removeScheduledCardReviewLocal(\n userDB: PouchDB.Database,\n reviewDocID: string\n) {\n const reviewDoc = await userDB.get(reviewDocID);\n userDB\n .remove(reviewDoc)\n .then((res) => {\n if (res.ok) {\n log(`Removed Review Doc: ${reviewDocID}`);\n }\n })\n .catch((err) => {\n log(`Failed to remove Review Doc: ${reviewDocID},\\n${JSON.stringify(err)}`);\n });\n}\n","export abstract class Loggable {\n protected abstract readonly _className: string;\n protected log(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.log(`LOG-${this._className}@${new Date()}:`, ...args);\n }\n protected error(...args: unknown[]): void {\n // eslint-disable-next-line no-console\n console.error(`ERROR-${this._className}@${new Date()}:`, ...args);\n }\n}\n","import { Loggable } from '../../util/Loggable';\nimport { logger } from '../../util/logger';\n\nexport type Update<T> = Partial<T> | ((x: T) => T);\n\nexport default class UpdateQueue extends Loggable {\n _className: string = 'UpdateQueue';\n private pendingUpdates: {\n [index: string]: Update<unknown>[];\n } = {};\n private inprogressUpdates: {\n [index: string]: boolean;\n } = {};\n\n private readDB: PouchDB.Database; // Database for read operations\n private writeDB: PouchDB.Database; // Database for write operations (local-first)\n\n /**\n * Queues an update for a document and applies it with conflict resolution.\n *\n * @param id - Document ID to update\n * @param update - Partial object or function that transforms the document\n * @returns Promise resolving to the updated document\n *\n * @throws {PouchError} with status 404 if document doesn't exist\n *\n * @remarks\n * **Error Handling Pattern:**\n * - This method does NOT create documents if they don't exist\n * - Callers are responsible for handling 404 errors and creating documents\n * - This design maintains separation of concerns (UpdateQueue handles conflicts, callers handle lifecycle)\n *\n * @example\n * ```typescript\n * try {\n * await updateQueue.update(docId, (doc) => ({ ...doc, field: newValue }));\n * } catch (e) {\n * if ((e as PouchError).status === 404) {\n * // Create the document with initial values\n * await db.put({ _id: docId, field: newValue, ...initialFields });\n * }\n * }\n * ```\n */\n public update<T extends PouchDB.Core.Document<object>>(\n id: PouchDB.Core.DocumentId,\n update: Update<T>\n ) {\n logger.debug(`Update requested on doc: ${id}`);\n if (this.pendingUpdates[id]) {\n this.pendingUpdates[id].push(update);\n } else {\n this.pendingUpdates[id] = [update];\n }\n return this.applyUpdates<T>(id);\n }\n\n constructor(readDB: PouchDB.Database, writeDB?: PouchDB.Database) {\n super();\n // PouchDB.debug.enable('*');\n this.readDB = readDB;\n this.writeDB = writeDB || readDB; // Default to readDB if writeDB not provided\n logger.debug(`UpdateQ initialized...`);\n void this.readDB.info().then((i) => {\n logger.debug(`db info: ${JSON.stringify(i)}`);\n });\n }\n\n private async applyUpdates<T extends PouchDB.Core.Document<object>>(\n id: string\n ): Promise<T & PouchDB.Core.GetMeta & PouchDB.Core.RevisionIdMeta> {\n logger.debug(`Applying updates on doc: ${id}`);\n if (this.inprogressUpdates[id]) {\n // Poll instead of recursing to avoid infinite recursion\n while (this.inprogressUpdates[id]) {\n await new Promise(resolve => setTimeout(resolve, Math.random() * 50));\n }\n return this.applyUpdates<T>(id);\n } else {\n if (this.pendingUpdates[id] && this.pendingUpdates[id].length > 0) {\n this.inprogressUpdates[id] = true;\n\n const MAX_RETRIES = 5;\n for (let i = 0; i < MAX_RETRIES; i++) {\n try {\n const doc = await this.readDB.get<T>(id);\n\n // Create a new doc object to apply updates to for this attempt\n let updatedDoc = { ...doc };\n\n // Note: This loop is not fully safe if updates are functions that depend on a specific doc state\n // that might change between retries. But for simple object merges, it's okay.\n const updatesToApply = [...this.pendingUpdates[id]];\n for (const update of updatesToApply) {\n if (typeof update === 'function') {\n updatedDoc = { ...updatedDoc, ...update(updatedDoc) };\n } else {\n updatedDoc = {\n ...updatedDoc,\n ...update,\n };\n }\n }\n\n await this.writeDB.put<T>(updatedDoc);\n\n // Success! Remove the updates we just applied.\n this.pendingUpdates[id].splice(0, updatesToApply.length);\n\n if (this.pendingUpdates[id].length === 0) {\n this.inprogressUpdates[id] = false;\n delete this.inprogressUpdates[id];\n } else {\n // More updates came in, run again.\n return this.applyUpdates<T>(id);\n }\n return updatedDoc as any; // success, exit loop and function\n } catch (e: any) {\n if (e.name === 'conflict' && i < MAX_RETRIES - 1) {\n logger.warn(`Conflict on update for doc ${id}, retry #${i + 1}`);\n await new Promise((res) => setTimeout(res, 50 * Math.random()));\n // continue to next iteration of the loop\n } else if (e.name === 'not_found' && i === 0) {\n // Document not present - throw to caller for initialization\n logger.warn(`Update failed for ${id} - does not exist. Throwing to caller.`);\n delete this.inprogressUpdates[id];\n throw e; // Let caller handle\n } else {\n // Max retries reached or a non-conflict error\n delete this.inprogressUpdates[id];\n if (this.pendingUpdates[id]) {\n delete this.pendingUpdates[id];\n }\n logger.error(`Error on attemped update (retry ${i}): ${JSON.stringify(e)}`);\n throw e; // Let caller handle\n }\n }\n }\n // This should be unreachable, but it satisfies the compiler that a value is always returned or an error thrown.\n throw new Error(`UpdateQueue failed for doc ${id} after ${MAX_RETRIES} retries.`);\n } else {\n throw new Error(`Empty Updates Queue Triggered`);\n }\n }\n }\n}\n","import {\n ScheduledCard,\n UserCourseSetting,\n UserCourseSettings,\n UsrCrsDataInterface,\n} from '@db/core';\n\nimport moment, { Moment } from 'moment';\n\nimport { UserDBInterface } from '@db/core';\nimport { logger } from '../../util/logger';\n\nexport class UsrCrsData implements UsrCrsDataInterface {\n private user: UserDBInterface;\n private _courseId: string;\n\n constructor(user: UserDBInterface, courseId: string) {\n this.user = user;\n this._courseId = courseId;\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews() {\n const now = moment.utc();\n return this.getReviewstoDate(now);\n }\n\n public async getScheduledReviewCount(): Promise<number> {\n return (await this.getPendingReviews()).length;\n }\n\n public async getCourseSettings(): Promise<UserCourseSettings> {\n const regDoc = await this.user.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === this._courseId);\n\n if (crsDoc && crsDoc.settings) {\n return crsDoc.settings;\n } else {\n logger.warn(`no settings found during lookup on course ${this._courseId}`);\n return {};\n }\n }\n public updateCourseSettings(updates: UserCourseSetting[]): void {\n // TODO: Add updateCourseSettings method to UserDBInterface\n // For now, we'll need to cast to access the concrete implementation\n if ('updateCourseSettings' in this.user) {\n void (this.user as any).updateCourseSettings(this._courseId, updates);\n }\n }\n\n public async getStrategyState<T>(strategyKey: string): Promise<T | null> {\n return this.user.getStrategyState<T>(this._courseId, strategyKey);\n }\n\n public async putStrategyState<T>(strategyKey: string, data: T): Promise<void> {\n return this.user.putStrategyState<T>(this._courseId, strategyKey, data);\n }\n\n public async deleteStrategyState(strategyKey: string): Promise<void> {\n return this.user.deleteStrategyState(this._courseId, strategyKey);\n }\n\n private async getReviewstoDate(targetDate: Moment) {\n // Use the interface method instead of direct database access\n const allReviews = await this.user.getPendingReviews(this._courseId);\n\n logger.debug(\n `Fetching ${this.user.getUsername()}'s scheduled reviews for course ${this._courseId}.`\n );\n\n return allReviews.filter((review: ScheduledCard) => {\n const reviewTime = moment.utc(review.reviewTime);\n return targetDate.isAfter(reviewTime);\n });\n }\n}\n","// todo: something good here instead\n\nconst CLIENT_CACHE: {\n [k: string]: unknown;\n} = {};\n\nexport async function GET_CACHED<K>(k: string, f?: (x: string) => Promise<K>): Promise<K> {\n if (CLIENT_CACHE[k]) {\n // console.log('returning a cached item');\n return CLIENT_CACHE[k] as K;\n }\n\n CLIENT_CACHE[k] = f ? await f(k) : await GET_ITEM(k);\n return GET_CACHED(k);\n}\n\nasync function GET_ITEM(k: string): Promise<unknown> {\n throw new Error(`No implementation found for GET_CACHED(${k})`);\n}\n","import pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from '.';\nimport { ENV } from '@db/factory';\n// import { DataShape } from '../..base-course/Interfaces/DataShape';\nimport { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';\nimport { CourseConfig, DataShape } from '@vue-skuilder/common';\nimport { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';\nimport { CourseDB, createTag } from './courseDB';\nimport { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';\nimport { prepareNote55 } from '@vue-skuilder/common';\nimport { BaseUser } from '../common';\nimport { logger } from '@db/util/logger';\nimport { v4 as uuidv4 } from 'uuid';\n\n/**\n *\n * @param courseID id of the course (quilt) being added to\n * @param codeCourse\n * @param shape\n * @param data the datashape data - data required for this shape\n * @param author\n * @param uploads optional additional media uploads: img0, img1, ..., aud0, aud1,...\n * @returns\n */\nexport async function addNote55(\n courseID: string,\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [x: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);\n const _id = `${DocTypePrefixes[DocType.DISPLAYABLE_DATA]}-${uuidv4()}`;\n const result = await db.put<DisplayableData>({ ...payload, _id });\n\n const dataShapeId = NameSpacer.getDataShapeString({\n course: codeCourse,\n dataShape: shape.name,\n });\n\n if (result.ok) {\n try {\n // create cards\n await createCards(courseID, dataShapeId, result.id, tags, elo, author);\n } catch (error) {\n // Handle CouchDB errors which often have a 'reason' property\n let errorMessage = 'Unknown error';\n if (error instanceof Error) {\n errorMessage = error.message;\n } else if (error && typeof error === 'object' && 'reason' in error) {\n errorMessage = error.reason as string;\n } else if (error && typeof error === 'object' && 'message' in error) {\n errorMessage = error.message as string;\n } else {\n errorMessage = String(error);\n }\n\n logger.error(`[addNote55] Failed to create cards for note ${result.id}: ${errorMessage}`);\n // Add info to result to indicate card creation failed\n (result as any).cardCreationFailed = true;\n (result as any).cardCreationError = errorMessage;\n }\n } else {\n logger.error(`[addNote55] Error adding note. Result: ${JSON.stringify(result)}`);\n }\n\n return result;\n}\n\nasync function createCards(\n courseID: string,\n datashapeID: PouchDB.Core.DocumentId,\n noteID: PouchDB.Core.DocumentId,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const cfg = await getCredentialledCourseConfig(courseID);\n const dsDescriptor = NameSpacer.getDataShapeDescriptor(datashapeID);\n let questionViewTypes: string[] = [];\n\n for (const ds of cfg.dataShapes) {\n if (ds.name === datashapeID) {\n questionViewTypes = ds.questionTypes;\n }\n }\n\n if (questionViewTypes.length === 0) {\n const errorMsg = `No questionViewTypes found for datashapeID: ${datashapeID} in course config. Cards cannot be created.`;\n logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n for (const questionView of questionViewTypes) {\n await createCard(questionView, courseID, dsDescriptor, noteID, tags, elo, author);\n }\n}\n\nasync function createCard(\n questionViewName: string,\n courseID: string,\n dsDescriptor: ShapeDescriptor,\n noteID: string,\n tags: string[],\n elo: CourseElo = blankCourseElo(),\n author: string\n): Promise<void> {\n const qDescriptor = NameSpacer.getQuestionDescriptor(questionViewName);\n const cfg = await getCredentialledCourseConfig(courseID);\n\n for (const rQ of cfg.questionTypes) {\n if (rQ.name === questionViewName) {\n for (const view of rQ.viewList) {\n await addCard(\n courseID,\n dsDescriptor.course,\n [noteID],\n NameSpacer.getViewString({\n course: qDescriptor.course,\n questionType: qDescriptor.questionType,\n view,\n }),\n elo,\n tags,\n author\n );\n }\n }\n }\n}\n\n/**\n *\n * Adds a card to the DB. This function is called\n * as a side effect of adding either a View or\n * DisplayableData item.\n * @param course The name of the course that the card belongs to\n * @param id_displayable_data C/PouchDB ID of the data used to hydrate the view\n * @param id_view C/PouchDB ID of the view used to display the card\n *\n * @package\n */\nasync function addCard(\n courseID: string,\n course: string,\n id_displayable_data: PouchDB.Core.DocumentId[],\n id_view: PouchDB.Core.DocumentId,\n elo: CourseElo,\n tags: string[],\n author: string\n): Promise<PouchDB.Core.Response> {\n const db = getCourseDB(courseID);\n const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;\n const card = await db.put<CardData>({\n _id,\n course,\n id_displayable_data,\n id_view,\n docType: DocType.CARD,\n elo: elo || toCourseElo(990 + Math.round(20 * Math.random())),\n author,\n });\n for (const tag of tags) {\n logger.info(`adding tag: ${tag} to card ${card.id}`);\n await addTagToCard(courseID, card.id, tag, author, false);\n }\n return card;\n}\n\nexport async function getCredentialledCourseConfig(courseID: string): Promise<CourseConfig> {\n try {\n const db = getCourseDB(courseID);\n const ret = await db.get<CourseConfig>('CourseConfig');\n ret.courseID = courseID;\n logger.info(`Returning course config: ${JSON.stringify(ret)}`);\n return ret;\n } catch (e) {\n logger.error(`Error fetching config for ${courseID}:`, e);\n throw e;\n }\n}\n\n/**\n Assciates a tag with a card.\n\n NB: DB stores tags as separate documents, with a list of card IDs.\n Consider renaming to `addCardToTag` to reflect this.\n\n NB: tags are created if they don't already exist\n\n @param updateELO whether to update the ELO of the card with the new tag. Default true.\n @package\n*/\nexport async function addTagToCard(\n courseID: string,\n cardID: string,\n tagID: string,\n author: string,\n updateELO: boolean = true\n): Promise<PouchDB.Core.Response> {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n const prefixedTagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const courseApi = new CourseDB(courseID, async () => {\n const dummySyncStrategy = {\n setupRemoteDB: () => null as any,\n startSync: () => {},\n canCreateAccount: () => false,\n canAuthenticate: () => false,\n getCurrentUsername: async () => 'DummyUser',\n };\n return BaseUser.Dummy(dummySyncStrategy);\n });\n try {\n logger.info(`Applying tag ${tagID} to card ${courseID + '-' + cardID}...`);\n const tag = await courseDB.get<Tag>(prefixedTagID);\n if (!tag.taggedCards.includes(cardID)) {\n tag.taggedCards.push(cardID);\n\n if (updateELO) {\n try {\n const eloData = await courseApi.getCardEloData([cardID]);\n const elo = eloData[0];\n elo.tags[tagID] = {\n count: 0,\n score: elo.global.score, // todo: or 1000?\n };\n await updateCardElo(courseID, cardID, elo);\n } catch (error) {\n logger.error('Failed to update ELO data for card:', cardID, error);\n }\n }\n\n return courseDB.put<Tag>(tag);\n } else throw new AlreadyTaggedErr(`Card ${cardID} is already tagged with ${tagID}`);\n } catch (e) {\n if (e instanceof AlreadyTaggedErr) {\n throw e;\n }\n\n await createTag(courseID, tagID, author);\n return addTagToCard(courseID, cardID, tagID, author, updateELO);\n }\n}\n\nasync function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nclass AlreadyTaggedErr extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AlreadyTaggedErr';\n }\n}\n\nexport function getTagID(tagName: string): string {\n const tagPrefix = DocType.TAG.valueOf() + '-';\n if (tagName.indexOf(tagPrefix) === 0) {\n return tagName;\n } else {\n return tagPrefix + tagName;\n }\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n const dbName = `coursedb-${courseID}`;\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport { logger } from '../../util/logger';\n\nconst courseLookupDBTitle = 'coursedb-lookup';\n\ninterface CourseLookupDoc {\n _id: string;\n _rev: string;\n name: string;\n disambiguator?: string;\n}\n\nlogger.debug(`COURSELOOKUP FILE RUNNING`);\n\n/**\n * A Lookup table of existant courses. Each docID in this DB correspondes to a\n * course database whose name is `coursedb-{docID}`\n */\nexport default class CourseLookup {\n // [ ] this db should be read only for public, admin-only for write\n // Cache for the PouchDB instance\n private static _dbInstance: PouchDB.Database | null = null;\n\n /**\n * Static getter for the PouchDB database instance.\n * Connects using ENV variables and caches the instance.\n * Throws an error if required ENV variables are not set.\n */\n private static get _db(): PouchDB.Database {\n // Return cached instance if available\n if (this._dbInstance) {\n return this._dbInstance;\n }\n\n // --- Check required environment variables ---\n if (ENV.COUCHDB_SERVER_URL === 'NOT_SET' || !ENV.COUCHDB_SERVER_URL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_URL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n if (ENV.COUCHDB_SERVER_PROTOCOL === 'NOT_SET' || !ENV.COUCHDB_SERVER_PROTOCOL) {\n throw new Error(\n 'CourseLookup.db: COUCHDB_SERVER_PROTOCOL is not set. Ensure initializeDataLayer has been called with valid configuration.'\n );\n }\n\n // --- Construct connection options ---\n const dbUrl = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}/${courseLookupDBTitle}`;\n const options: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n // fetch: (url, opts) => { // Optional: Add for debugging network requests\n // console.log('PouchDB fetch:', url, opts);\n // return pouch.fetch(url, opts);\n // }\n };\n\n // Add authentication if both username and password are provided\n if (ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD) {\n options.auth = {\n username: ENV.COUCHDB_USERNAME,\n password: ENV.COUCHDB_PASSWORD,\n };\n logger.info(`CourseLookup: Connecting to ${dbUrl} with authentication.`);\n } else {\n logger.info(`CourseLookup: Connecting to ${dbUrl} without authentication.`);\n }\n\n // --- Create and cache the PouchDB instance ---\n try {\n this._dbInstance = new pouch(dbUrl, options);\n logger.info(`CourseLookup: Database instance created for ${courseLookupDBTitle}.`);\n return this._dbInstance;\n } catch (error) {\n logger.error(`CourseLookup: Failed to create PouchDB instance for ${dbUrl}`, error);\n // Reset cache attempt on failure\n this._dbInstance = null;\n // Re-throw the error to indicate connection failure\n throw new Error(\n `CourseLookup: Failed to initialize database connection: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * Adds a new course to the lookup database, and returns the courseID\n * @param courseName\n * @returns\n */\n static async add(courseName: string): Promise<string> {\n const resp = await CourseLookup._db.post({\n name: courseName,\n });\n\n return resp.id;\n }\n\n /**\n * Adds a new course to the lookup database with a specific courseID\n * @param courseId The specific course ID to use\n * @param courseName The course name\n * @param disambiguator Optional disambiguator\n * @returns Promise<void>\n */\n static async addWithId(\n courseId: string,\n courseName: string,\n disambiguator?: string\n ): Promise<void> {\n const doc: Omit<CourseLookupDoc, '_rev'> = {\n _id: courseId,\n name: courseName,\n };\n\n if (disambiguator) {\n doc.disambiguator = disambiguator;\n }\n\n await CourseLookup._db.put(doc);\n }\n\n /**\n * Removes a course from the index\n * @param courseID\n */\n static async delete(courseID: string): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get(courseID);\n return await CourseLookup._db.remove(doc);\n }\n\n // [ ] rename to allCourses()\n static async allCourseWare(): Promise<CourseLookupDoc[]> {\n const resp = await CourseLookup._db.allDocs<CourseLookupDoc>({\n include_docs: true,\n });\n\n return resp.rows.map((row) => row.doc!);\n }\n\n static async updateDisambiguator(\n courseID: string,\n disambiguator?: string\n ): Promise<PouchDB.Core.Response> {\n const doc = await CourseLookup._db.get<CourseLookupDoc>(courseID);\n doc.disambiguator = disambiguator;\n return await CourseLookup._db.put(doc);\n }\n\n static async isCourse(courseID: string): Promise<boolean> {\n try {\n await CourseLookup._db.get(courseID);\n return true;\n } catch (error) {\n logger.info(`Courselookup failed:`, error);\n return false;\n }\n }\n}\n","import type { WeightedCard, StrategyContribution } from './index';\nimport {\n getRegisteredNavigatorNames,\n getRegisteredNavigatorRole,\n NavigatorRoles,\n type Navigators,\n isGenerator,\n isFilter,\n} from './index';\nimport { logger } from '../../util/logger';\nimport type { Pipeline, CardSpaceDiagnosis } from './Pipeline';\n\n/**\n * Captured reference to the most recently created Pipeline instance.\n * Used by the debug API to run diagnostics against the live pipeline.\n */\nlet _activePipeline: Pipeline | null = null;\n\n/**\n * Register a pipeline instance for diagnostic access.\n * Called by Pipeline constructor.\n */\nexport function registerPipelineForDebug(pipeline: Pipeline): void {\n _activePipeline = pipeline;\n}\n\n// ============================================================================\n// PIPELINE DEBUGGER\n// ============================================================================\n//\n// Console-accessible debug API for inspecting pipeline decisions.\n//\n// Exposed as `window.skuilder.pipeline` for interactive exploration.\n//\n// Usage:\n// window.skuilder.pipeline.showLastRun()\n// window.skuilder.pipeline.showCard('cardId123')\n// window.skuilder.pipeline.explainReviews()\n// window.skuilder.pipeline.showPrescribed()\n// window.skuilder.pipeline.export()\n//\n// ============================================================================\n\n/**\n * Summary of a single generator's contribution.\n */\nexport interface GeneratorSummary {\n name: string;\n cardCount: number;\n newCount: number;\n reviewCount: number;\n topScore: number;\n}\n\n/**\n * Summary of a filter's impact on scores.\n */\nexport interface FilterImpact {\n name: string;\n boosted: number;\n penalized: number;\n passed: number;\n removed: number;\n}\n\n/**\n * Complete record of a single pipeline execution.\n */\nexport interface PipelineRunReport {\n runId: string;\n timestamp: Date;\n courseId: string;\n courseName?: string;\n\n /** User's global ELO at the time of this pipeline run */\n userElo?: number;\n\n // Generator phase\n generatorName: string;\n generators?: GeneratorSummary[];\n generatedCount: number;\n\n // Filter phase\n filters: FilterImpact[];\n\n // Results\n finalCount: number;\n reviewsSelected: number;\n newSelected: number;\n\n // Full card data for inspection\n cards: Array<{\n cardId: string;\n courseId: string;\n origin: 'new' | 'review' | 'unknown';\n finalScore: number;\n /** Card's ELO (parsed from ELO generator provenance, if available) */\n cardElo?: number;\n provenance: StrategyContribution[];\n tags?: string[];\n selected: boolean;\n }>;\n}\n\n/**\n * Ring buffer for storing recent pipeline runs.\n */\nconst MAX_RUNS = 10;\nconst runHistory: PipelineRunReport[] = [];\n\n/**\n * Determine card origin from provenance trail.\n */\nfunction getOrigin(card: WeightedCard): 'new' | 'review' | 'unknown' {\n const firstEntry = card.provenance[0];\n if (!firstEntry) return 'unknown';\n const reason = firstEntry.reason?.toLowerCase() || '';\n if (reason.includes('new card')) return 'new';\n if (reason.includes('review')) return 'review';\n return 'unknown';\n}\n\n/**\n * Capture a pipeline run for later inspection.\n */\nexport function captureRun(report: Omit<PipelineRunReport, 'runId' | 'timestamp'>): void {\n const fullReport: PipelineRunReport = {\n ...report,\n runId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n timestamp: new Date(),\n };\n\n runHistory.unshift(fullReport);\n if (runHistory.length > MAX_RUNS) {\n runHistory.pop();\n }\n}\n\n/**\n * Build a capture-ready report from pipeline execution data.\n */\n/**\n * Parse card ELO from the ELO generator's provenance reason string.\n * Format: \"ELO distance XX (card: YYYY, user: ZZZZ), ...\"\n */\nfunction parseCardElo(provenance: StrategyContribution[]): number | undefined {\n const eloEntry = provenance.find((p) => p.strategy === 'elo');\n if (!eloEntry?.reason) return undefined;\n const match = eloEntry.reason.match(/card:\\s*(\\d+)/);\n return match ? parseInt(match[1], 10) : undefined;\n}\n\nexport function buildRunReport(\n courseId: string,\n courseName: string | undefined,\n generatorName: string,\n generators: GeneratorSummary[] | undefined,\n generatedCount: number,\n filters: FilterImpact[],\n allCards: WeightedCard[],\n selectedCards: WeightedCard[],\n userElo?: number\n): Omit<PipelineRunReport, 'runId' | 'timestamp'> {\n const selectedIds = new Set(selectedCards.map((c) => c.cardId));\n\n const cards = allCards.map((card) => ({\n cardId: card.cardId,\n courseId: card.courseId,\n origin: getOrigin(card),\n finalScore: card.score,\n cardElo: parseCardElo(card.provenance),\n provenance: card.provenance,\n tags: card.tags,\n selected: selectedIds.has(card.cardId),\n }));\n\n const reviewsSelected = selectedCards.filter((c) => getOrigin(c) === 'review').length;\n const newSelected = selectedCards.filter((c) => getOrigin(c) === 'new').length;\n\n return {\n courseId,\n courseName,\n userElo,\n generatorName,\n generators,\n generatedCount,\n filters,\n finalCount: selectedCards.length,\n reviewsSelected,\n newSelected,\n cards,\n };\n}\n\n// ============================================================================\n// CONSOLE API\n// ============================================================================\n\n/**\n * Format a provenance trail for console display.\n */\nfunction formatProvenance(provenance: StrategyContribution[]): string {\n return provenance\n .map((p) => {\n const actionSymbol =\n p.action === 'generated'\n ? '🎲'\n : p.action === 'boosted'\n ? '⬆️'\n : p.action === 'penalized'\n ? '⬇️'\n : '➡️';\n return ` ${actionSymbol} ${p.strategyName}: ${p.score.toFixed(3)} - ${p.reason}`;\n })\n .join('\\n');\n}\n\n/**\n * Print summary of a single pipeline run.\n */\nfunction printRunSummary(run: PipelineRunReport): void {\n // eslint-disable-next-line no-console\n console.group(`🔍 Pipeline Run: ${run.courseId} (${run.courseName || 'unnamed'})`);\n logger.info(`Run ID: ${run.runId}`);\n logger.info(`Time: ${run.timestamp.toISOString()}`);\n logger.info(`User ELO: ${run.userElo ?? 'unknown'}`);\n logger.info(`Generator: ${run.generatorName} → ${run.generatedCount} candidates`);\n\n if (run.generators && run.generators.length > 0) {\n // eslint-disable-next-line no-console\n console.group('Generator breakdown:');\n for (const g of run.generators) {\n logger.info(\n ` ${g.name}: ${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} reviews, top: ${g.topScore.toFixed(2)})`\n );\n }\n // eslint-disable-next-line no-console\n console.groupEnd();\n }\n\n if (run.filters.length > 0) {\n // eslint-disable-next-line no-console\n console.group('Filter impact:');\n for (const f of run.filters) {\n logger.info(` ${f.name}: ↑${f.boosted} ↓${f.penalized} =${f.passed} ✕${f.removed}`);\n }\n // eslint-disable-next-line no-console\n console.groupEnd();\n }\n\n logger.info(\n `Result: ${run.finalCount} cards selected (${run.newSelected} new, ${run.reviewsSelected} reviews)`\n );\n // eslint-disable-next-line no-console\n console.groupEnd();\n}\n\n/**\n * Console API object exposed on window.skuilder.pipeline\n */\nexport const pipelineDebugAPI = {\n /**\n * Get raw run history for programmatic access.\n */\n get runs(): PipelineRunReport[] {\n return [...runHistory];\n },\n\n /**\n * Show summary of a specific pipeline run.\n */\n showRun(idOrIndex: string | number = 0): void {\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No runs captured yet.');\n return;\n }\n\n let run: PipelineRunReport | undefined;\n\n if (typeof idOrIndex === 'number') {\n run = runHistory[idOrIndex];\n if (!run) {\n logger.info(\n `[Pipeline Debug] No run found at index ${idOrIndex}. History length: ${runHistory.length}`\n );\n return;\n }\n } else {\n run = runHistory.find((r) => r.runId.endsWith(idOrIndex));\n if (!run) {\n logger.info(`[Pipeline Debug] No run found matching ID '${idOrIndex}'.`);\n return;\n }\n }\n\n printRunSummary(run);\n },\n\n /**\n * Show summary of the last pipeline run.\n */\n showLastRun(): void {\n this.showRun(0);\n },\n\n /**\n * Show detailed provenance for a specific card.\n */\n showCard(cardId: string): void {\n for (const run of runHistory) {\n const card = run.cards.find((c) => c.cardId === cardId);\n if (card) {\n // eslint-disable-next-line no-console\n console.group(`🎴 Card: ${cardId}`);\n logger.info(`Course: ${card.courseId}`);\n logger.info(`Origin: ${card.origin}`);\n logger.info(`Card ELO: ${card.cardElo ?? 'unknown'} | User ELO: ${run.userElo ?? 'unknown'}`);\n logger.info(`Final score: ${card.finalScore.toFixed(3)}`);\n logger.info(`Selected: ${card.selected ? 'Yes ✅' : 'No ❌'}`);\n if (card.tags && card.tags.length > 0) {\n logger.info(`Tags (${card.tags.length}): ${card.tags.join(', ')}`);\n }\n logger.info('Provenance:');\n logger.info(formatProvenance(card.provenance));\n // eslint-disable-next-line no-console\n console.groupEnd();\n return;\n }\n }\n logger.info(`[Pipeline Debug] Card '${cardId}' not found in recent runs.`);\n },\n\n /**\n * Explain why reviews may or may not have been selected.\n */\n explainReviews(): void {\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No runs captured yet.');\n return;\n }\n\n // eslint-disable-next-line no-console\n console.group('📋 Review Selection Analysis');\n\n for (const run of runHistory) {\n // eslint-disable-next-line no-console\n console.group(`Run: ${run.courseId} @ ${run.timestamp.toLocaleTimeString()}`);\n\n const allReviews = run.cards.filter((c) => c.origin === 'review');\n const selectedReviews = allReviews.filter((c) => c.selected);\n\n if (allReviews.length === 0) {\n logger.info('❌ No reviews were generated. Check SRS logs for why.');\n } else if (selectedReviews.length === 0) {\n logger.info(`⚠️ ${allReviews.length} reviews generated but none selected.`);\n logger.info('Possible reasons:');\n\n // Check if new cards scored higher\n const topNewScore = Math.max(\n ...run.cards.filter((c) => c.origin === 'new' && c.selected).map((c) => c.finalScore),\n 0\n );\n const topReviewScore = Math.max(...allReviews.map((c) => c.finalScore), 0);\n\n if (topReviewScore < topNewScore) {\n logger.info(\n ` - New cards scored higher (top new: ${topNewScore.toFixed(2)}, top review: ${topReviewScore.toFixed(2)})`\n );\n }\n\n // Show top review that didn't make it\n const topReview = allReviews.sort((a, b) => b.finalScore - a.finalScore)[0];\n if (topReview) {\n logger.info(` - Top review score: ${topReview.finalScore.toFixed(3)}`);\n logger.info(' - Its provenance:');\n logger.info(formatProvenance(topReview.provenance));\n }\n } else {\n logger.info(`✅ ${selectedReviews.length}/${allReviews.length} reviews selected.`);\n logger.info('Top selected review:');\n const topSelected = selectedReviews.sort((a, b) => b.finalScore - a.finalScore)[0];\n logger.info(formatProvenance(topSelected.provenance));\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Show prescribed-related cards from the most recent run.\n *\n * Highlights:\n * - cards directly generated by the prescribed strategy\n * - blocked prescribed targets mentioned in provenance\n * - support tags resolved for blocked targets\n *\n * @param groupId - Optional prescribed group ID filter (e.g. 'intro-core')\n */\n showPrescribed(groupId?: string): void {\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No runs captured yet.');\n return;\n }\n\n const run = runHistory[0];\n const prescribedCards = run.cards.filter((c) =>\n c.provenance.some((p) => p.strategy === 'prescribed')\n );\n\n // eslint-disable-next-line no-console\n console.group(`🧭 Prescribed Debug (${run.courseId})`);\n\n if (prescribedCards.length === 0) {\n logger.info('No prescribed-generated cards were present in the most recent run.');\n // eslint-disable-next-line no-console\n console.groupEnd();\n return;\n }\n\n const rows = prescribedCards\n .map((card) => {\n const prescribedProv = card.provenance.find((p) => p.strategy === 'prescribed');\n const reason = prescribedProv?.reason ?? '';\n const parsedGroup = reason.match(/group=([^;]+)/)?.[1] ?? 'unknown';\n const mode = reason.match(/mode=([^;]+)/)?.[1] ?? 'unknown';\n const blocked = reason.match(/blocked=([^;]+)/)?.[1] ?? 'unknown';\n const blockedTargets = reason.match(/blockedTargets=([^;]+)/)?.[1] ?? 'none';\n const supportTags = reason.match(/supportTags=([^;]+)/)?.[1] ?? 'none';\n const multiplier = reason.match(/multiplier=([^;]+)/)?.[1] ?? 'unknown';\n\n return {\n group: parsedGroup,\n mode,\n cardId: card.cardId,\n selected: card.selected ? 'yes' : 'no',\n finalScore: card.finalScore.toFixed(3),\n blocked,\n blockedTargets,\n supportTags,\n multiplier,\n };\n })\n .filter((row) => !groupId || row.group === groupId)\n .sort((a, b) => Number(b.finalScore) - Number(a.finalScore));\n\n if (rows.length === 0) {\n logger.info(\n `[Pipeline Debug] No prescribed cards matched group '${groupId}' in the most recent run.`\n );\n // eslint-disable-next-line no-console\n console.groupEnd();\n return;\n }\n\n // eslint-disable-next-line no-console\n console.table(rows);\n\n const selectedRows = rows.filter((r) => r.selected === 'yes');\n const blockedTargetSet = new Set<string>();\n const supportTagSet = new Set<string>();\n\n for (const row of rows) {\n if (row.blockedTargets && row.blockedTargets !== 'none') {\n row.blockedTargets\n .split('|')\n .filter(Boolean)\n .forEach((t) => blockedTargetSet.add(t));\n }\n if (row.supportTags && row.supportTags !== 'none') {\n row.supportTags\n .split('|')\n .filter(Boolean)\n .forEach((t) => supportTagSet.add(t));\n }\n }\n\n logger.info(`Prescribed cards in run: ${rows.length}`);\n logger.info(`Selected prescribed cards: ${selectedRows.length}`);\n logger.info(\n `Blocked prescribed targets referenced: ${blockedTargetSet.size > 0 ? [...blockedTargetSet].join(', ') : 'none'}`\n );\n logger.info(\n `Resolved support tags referenced: ${supportTagSet.size > 0 ? [...supportTagSet].join(', ') : 'none'}`\n );\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Show all runs in compact format.\n */\n listRuns(): void {\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No runs captured yet.');\n return;\n }\n\n // eslint-disable-next-line no-console\n console.table(\n runHistory.map((r) => ({\n id: r.runId.slice(-8),\n time: r.timestamp.toLocaleTimeString(),\n course: r.courseName || r.courseId.slice(0, 8),\n generated: r.generatedCount,\n selected: r.finalCount,\n new: r.newSelected,\n reviews: r.reviewsSelected,\n }))\n );\n },\n\n /**\n * Export run history as JSON for bug reports.\n */\n export(): string {\n const json = JSON.stringify(runHistory, null, 2);\n logger.info('[Pipeline Debug] Run history exported. Copy the returned string or use:');\n logger.info(' copy(window.skuilder.pipeline.export())');\n return json;\n },\n\n /**\n * Clear run history.\n */\n clear(): void {\n runHistory.length = 0;\n logger.info('[Pipeline Debug] Run history cleared.');\n },\n\n /**\n * Show the navigator registry: all registered classes and their roles.\n *\n * Useful for verifying that consumer-defined navigators were registered\n * before pipeline assembly.\n */\n showRegistry(): void {\n const names = getRegisteredNavigatorNames();\n if (names.length === 0) {\n logger.info('[Pipeline Debug] Navigator registry is empty.');\n return;\n }\n\n // eslint-disable-next-line no-console\n console.group('📦 Navigator Registry');\n // eslint-disable-next-line no-console\n console.table(\n names.map((name) => {\n const registryRole = getRegisteredNavigatorRole(name);\n const builtinRole = NavigatorRoles[name as Navigators];\n const effectiveRole = builtinRole || registryRole || '⚠️ NONE';\n const source = builtinRole ? 'built-in' : registryRole ? 'consumer' : 'unclassified';\n return {\n name,\n role: effectiveRole,\n source,\n isGenerator: isGenerator(name),\n isFilter: isFilter(name),\n };\n })\n );\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Show strategy documents from the last pipeline run and how they mapped\n * to the registry.\n *\n * If no runs are captured yet, falls back to showing just the registry.\n */\n showStrategies(): void {\n this.showRegistry();\n\n if (runHistory.length === 0) {\n logger.info('[Pipeline Debug] No pipeline runs captured yet — cannot show strategy doc mapping.');\n return;\n }\n\n const run = runHistory[0];\n // eslint-disable-next-line no-console\n console.group('🔌 Pipeline Strategy Mapping (last run)');\n logger.info(`Generator: ${run.generatorName}`);\n if (run.generators && run.generators.length > 0) {\n for (const g of run.generators) {\n logger.info(` 📥 ${g.name}: ${g.cardCount} cards (${g.newCount} new, ${g.reviewCount} reviews)`);\n }\n }\n if (run.filters.length > 0) {\n logger.info('Filters:');\n for (const f of run.filters) {\n logger.info(` 🔸 ${f.name}: ↑${f.boosted} ↓${f.penalized} =${f.passed} ✕${f.removed}`);\n }\n } else {\n logger.info('Filters: (none)');\n }\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Scan the full card space through the filter chain for the current user.\n *\n * Reports how many cards are well-indicated and how many are new.\n * Use this to understand how the search space grows during onboarding.\n *\n * @param threshold - Score threshold for \"well indicated\" (default 0.10)\n */\n async diagnoseCardSpace(threshold?: number): Promise<CardSpaceDiagnosis | null> {\n if (!_activePipeline) {\n logger.info('[Pipeline Debug] No active pipeline. Run a session first.');\n return null;\n }\n return _activePipeline.diagnoseCardSpace({ threshold });\n },\n\n /**\n * Show user's per-tag ELO data. Useful for diagnosing hierarchy gate status.\n *\n * @param tagFilter - Optional glob pattern(s) to filter tags.\n * Examples: 'gpc:expose:*', 'gpc:intro:t-T', ['gpc:expose:t-*', 'gpc:intro:t-*']\n */\n async showTagElo(tagFilter?: string | string[]): Promise<void> {\n if (!_activePipeline) {\n logger.info('[Pipeline Debug] No active pipeline. Run a session first.');\n return;\n }\n const status = await _activePipeline.getTagEloStatus(tagFilter);\n const entries = Object.entries(status).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) {\n logger.info(`[Pipeline Debug] No tag ELO data found${tagFilter ? ` for pattern: ${tagFilter}` : ''}.`);\n return;\n }\n // eslint-disable-next-line no-console\n console.table(\n Object.fromEntries(entries.map(([tag, data]) => [tag, { score: Math.round(data.score), count: data.count }]))\n );\n },\n\n /**\n * Show help.\n */\n help(): void {\n logger.info(`\n🔧 Pipeline Debug API\n\nCommands:\n .showLastRun() Show summary of most recent pipeline run\n .showRun(id|index) Show summary of a specific run (by index or ID suffix)\n .showCard(cardId) Show provenance trail for a specific card\n .showTagElo(pattern) Show user's tag ELO data (async). E.g. 'gpc:expose:*'\n .explainReviews() Analyze why reviews were/weren't selected\n .diagnoseCardSpace() Scan full card space through filters (async)\n .showRegistry() Show navigator registry (classes + roles)\n .showStrategies() Show registry + strategy mapping from last run\n .showPrescribed(id?) Show prescribed-generated cards and blocked/support details from last run\n .listRuns() List all captured runs in table format\n .export() Export run history as JSON for bug reports\n .clear() Clear run history\n .runs Access raw run history array\n .help() Show this help message\n\nExample:\n window.skuilder.pipeline.showLastRun()\n window.skuilder.pipeline.showRun(1)\n await window.skuilder.pipeline.diagnoseCardSpace()\n`);\n },\n};\n\n// ============================================================================\n// WINDOW MOUNT\n// ============================================================================\n\n/**\n * Mount the debug API on window.skuilder.pipeline\n */\nexport function mountPipelineDebugger(): void {\n if (typeof window === 'undefined') return;\n\n const win = window as any;\n win.skuilder = win.skuilder || {};\n win.skuilder.pipeline = pipelineDebugAPI;\n}\n\n// Auto-mount when module is loaded\nmountPipelineDebugger();","import { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport type { CardGenerator, GeneratorContext, GeneratorResult, ReplanHints } from './types';\nimport { logger } from '../../../util/logger';\n\n// ============================================================================\n// COMPOSITE GENERATOR\n// ============================================================================\n//\n// Composes multiple generator strategies into a single generator.\n//\n// Use case: When a course has multiple generators (e.g., ELO + SRS), this\n// class merges their outputs into a unified candidate list.\n//\n// Aggregation strategy:\n// - Cards appearing in multiple generators get a frequency boost\n// - Score = average(scores) * (1 + 0.1 * (appearances - 1))\n// - This rewards cards that multiple generators agree on\n//\n// ============================================================================\n\n/**\n * Aggregation modes for combining scores from multiple generators.\n */\nexport enum AggregationMode {\n /** Use the maximum score from any generator */\n MAX = 'max',\n /** Average all scores */\n AVERAGE = 'average',\n /** Average with frequency boost: avg * (1 + 0.1 * (n-1)) */\n FREQUENCY_BOOST = 'frequencyBoost',\n}\n\nconst DEFAULT_AGGREGATION_MODE = AggregationMode.FREQUENCY_BOOST;\nconst FREQUENCY_BOOST_FACTOR = 0.1;\n\nfunction mergeHints(allHints: Array<ReplanHints | undefined>): ReplanHints | undefined {\n const defined = allHints.filter((h): h is ReplanHints => h !== undefined);\n if (defined.length === 0) return undefined;\n\n const merged: ReplanHints = {};\n\n const boostTags: Record<string, number> = {};\n for (const hints of defined) {\n for (const [pattern, factor] of Object.entries(hints.boostTags ?? {})) {\n boostTags[pattern] = (boostTags[pattern] ?? 1) * factor;\n }\n }\n if (Object.keys(boostTags).length > 0) {\n merged.boostTags = boostTags;\n }\n\n const boostCards: Record<string, number> = {};\n for (const hints of defined) {\n for (const [pattern, factor] of Object.entries(hints.boostCards ?? {})) {\n boostCards[pattern] = (boostCards[pattern] ?? 1) * factor;\n }\n }\n if (Object.keys(boostCards).length > 0) {\n merged.boostCards = boostCards;\n }\n\n const concatUnique = (\n field: 'requireTags' | 'requireCards' | 'excludeTags' | 'excludeCards'\n ): void => {\n const values = defined.flatMap((h) => h[field] ?? []);\n if (values.length > 0) {\n merged[field] = [...new Set(values)];\n }\n };\n\n concatUnique('requireTags');\n concatUnique('requireCards');\n concatUnique('excludeTags');\n concatUnique('excludeCards');\n\n const labels = defined.map((h) => h._label).filter(Boolean);\n if (labels.length > 0) {\n merged._label = labels.join('; ');\n }\n\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n/**\n * Composes multiple generators into a single generator.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n *\n * Fetches candidates from all generators, deduplicates by cardId,\n * and aggregates scores based on the configured mode.\n */\nexport default class CompositeGenerator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string = 'Composite Generator';\n\n private generators: CardGenerator[];\n private aggregationMode: AggregationMode;\n\n constructor(\n generators: CardGenerator[],\n aggregationMode: AggregationMode = DEFAULT_AGGREGATION_MODE\n ) {\n super();\n this.generators = generators;\n this.aggregationMode = aggregationMode;\n\n if (generators.length === 0) {\n throw new Error('CompositeGenerator requires at least one generator');\n }\n\n logger.debug(\n `[CompositeGenerator] Created with ${generators.length} generators, mode: ${aggregationMode}`\n );\n }\n\n /**\n * Creates a CompositeGenerator from strategy data.\n *\n * This is a convenience factory for use by PipelineAssembler.\n */\n static async fromStrategies(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategies: ContentNavigationStrategyData[],\n aggregationMode: AggregationMode = DEFAULT_AGGREGATION_MODE\n ): Promise<CompositeGenerator> {\n const generators = await Promise.all(\n strategies.map((s) => ContentNavigator.create(user, course, s))\n );\n // Cast is safe because we know these are generators\n return new CompositeGenerator(generators as unknown as CardGenerator[], aggregationMode);\n }\n\n /**\n * Get weighted cards from all generators, merge and deduplicate.\n *\n * Cards appearing in multiple generators receive a score boost.\n * Provenance tracks which generators produced each card and how scores were aggregated.\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param context - GeneratorContext passed to child generators (required when called via Pipeline)\n */\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<GeneratorResult> {\n if (!context) {\n throw new Error(\n 'CompositeGenerator.getWeightedCards requires a GeneratorContext. ' +\n 'It should be called via Pipeline, not directly.'\n );\n }\n\n // Fetch from all generators in parallel\n const results = await Promise.all(\n this.generators.map((g) => g.getWeightedCards(limit, context))\n );\n\n // Log per-generator breakdown for transparency\n const generatorSummaries: string[] = [];\n results.forEach((result, index) => {\n const cards = result.cards;\n const gen = this.generators[index];\n const genName = gen.name || `Generator ${index}`;\n const newCards = cards.filter((c) => c.provenance[0]?.reason?.includes('new card'));\n const reviewCards = cards.filter((c) => c.provenance[0]?.reason?.includes('review'));\n \n if (cards.length > 0) {\n const topScore = Math.max(...cards.map((c) => c.score)).toFixed(2);\n const parts: string[] = [];\n if (newCards.length > 0) parts.push(`${newCards.length} new`);\n if (reviewCards.length > 0) parts.push(`${reviewCards.length} reviews`);\n const breakdown = parts.length > 0 ? parts.join(', ') : `${cards.length} cards`;\n generatorSummaries.push(`${genName}: ${breakdown} (top: ${topScore})`);\n } else {\n generatorSummaries.push(`${genName}: 0 cards`);\n }\n });\n logger.info(`[Composite] Generator breakdown: ${generatorSummaries.join(' | ')}`);\n\n // Group by cardId, tracking the weight of the generator that produced each instance\n type WeightedResult = { card: WeightedCard; weight: number };\n const byCardId = new Map<string, WeightedResult[]>();\n\n results.forEach((result, index) => {\n const cards = result.cards;\n // Access learnable weight if available\n const gen = this.generators[index] as unknown as ContentNavigator;\n\n // Determine effective weight\n let weight = gen.learnable?.weight ?? 1.0;\n let deviation: number | undefined;\n\n if (gen.learnable && !gen.staticWeight && context.orchestration) {\n // Access strategyId (protected field) via type assertion\n const strategyId = (gen as any).strategyId;\n if (strategyId) {\n weight = context.orchestration.getEffectiveWeight(strategyId, gen.learnable);\n deviation = context.orchestration.getDeviation(strategyId);\n }\n }\n\n for (const card of cards) {\n // Record effective weight in provenance for transparency\n if (card.provenance.length > 0) {\n card.provenance[0].effectiveWeight = weight;\n card.provenance[0].deviation = deviation;\n }\n\n const existing = byCardId.get(card.cardId) || [];\n existing.push({ card, weight });\n byCardId.set(card.cardId, existing);\n }\n });\n\n // Aggregate scores\n const merged: WeightedCard[] = [];\n for (const [, items] of byCardId) {\n const cards = items.map((i) => i.card);\n const aggregatedScore = this.aggregateScores(items);\n const finalScore = Math.min(1.0, aggregatedScore); // Clamp to [0, 1]\n\n // Merge provenance from all generators that produced this card\n const mergedProvenance = cards.flatMap((c) => c.provenance);\n\n // Determine action based on whether score changed\n const initialScore = cards[0].score;\n const action =\n finalScore > initialScore ? 'boosted' : finalScore < initialScore ? 'penalized' : 'passed';\n\n // Build reason explaining the aggregation\n const reason = this.buildAggregationReason(items, finalScore);\n\n // Append composite provenance entry\n merged.push({\n ...cards[0],\n score: finalScore,\n provenance: [\n ...mergedProvenance,\n {\n strategy: 'composite',\n strategyName: 'Composite Generator',\n strategyId: 'COMPOSITE_GENERATOR',\n action,\n score: finalScore,\n reason,\n },\n ],\n });\n }\n\n // Sort by score descending and limit\n const cards = merged.sort((a, b) => b.score - a.score).slice(0, limit);\n const hints = mergeHints(results.map((result) => result.hints));\n\n return { cards, hints };\n }\n\n /**\n * Build human-readable reason for score aggregation.\n */\n private buildAggregationReason(\n items: { card: WeightedCard; weight: number }[],\n finalScore: number\n ): string {\n const cards = items.map((i) => i.card);\n const count = cards.length;\n const scores = cards.map((c) => c.score.toFixed(2)).join(', ');\n\n if (count === 1) {\n const weightMsg =\n Math.abs(items[0].weight - 1.0) > 0.001 ? ` (w=${items[0].weight.toFixed(2)})` : '';\n return `Single generator, score ${finalScore.toFixed(2)}${weightMsg}`;\n }\n\n const strategies = cards.map((c) => c.provenance[0]?.strategy || 'unknown').join(', ');\n\n switch (this.aggregationMode) {\n case AggregationMode.MAX:\n return `Max of ${count} generators (${strategies}): scores [${scores}] → ${finalScore.toFixed(2)}`;\n\n case AggregationMode.AVERAGE:\n return `Weighted Avg of ${count} generators (${strategies}): scores [${scores}] → ${finalScore.toFixed(2)}`;\n\n case AggregationMode.FREQUENCY_BOOST: {\n // Recalculate basic weighted avg for display\n const totalWeight = items.reduce((sum, i) => sum + i.weight, 0);\n const weightedSum = items.reduce((sum, i) => sum + i.card.score * i.weight, 0);\n const avg = totalWeight > 0 ? weightedSum / totalWeight : 0;\n\n const boost = 1 + FREQUENCY_BOOST_FACTOR * (count - 1);\n return `Frequency boost from ${count} generators (${strategies}): w-avg ${avg.toFixed(2)} × ${boost.toFixed(2)} → ${finalScore.toFixed(2)}`;\n }\n\n default:\n return `Aggregated from ${count} generators: ${finalScore.toFixed(2)}`;\n }\n }\n\n /**\n * Aggregate scores from multiple generators for the same card.\n */\n private aggregateScores(items: { card: WeightedCard; weight: number }[]): number {\n const scores = items.map((i) => i.card.score);\n\n switch (this.aggregationMode) {\n case AggregationMode.MAX:\n return Math.max(...scores);\n\n case AggregationMode.AVERAGE: {\n const totalWeight = items.reduce((sum, i) => sum + i.weight, 0);\n if (totalWeight === 0) return 0;\n const weightedSum = items.reduce((sum, i) => sum + i.card.score * i.weight, 0);\n return weightedSum / totalWeight;\n }\n\n case AggregationMode.FREQUENCY_BOOST: {\n const totalWeight = items.reduce((sum, i) => sum + i.weight, 0);\n const weightedSum = items.reduce((sum, i) => sum + i.card.score * i.weight, 0);\n const avg = totalWeight > 0 ? weightedSum / totalWeight : 0;\n\n const frequencyBoost = 1 + FREQUENCY_BOOST_FACTOR * (items.length - 1);\n return avg * frequencyBoost;\n }\n\n default:\n return scores[0];\n }\n }\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport { toCourseElo } from '@vue-skuilder/common';\nimport type { QualifiedCardID } from '../..';\nimport type { CardGenerator, GeneratorContext, GeneratorResult } from './types';\nimport { logger } from '@db/util/logger';\n\n// ============================================================================\n// ELO NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that selects new cards based on ELO proximity.\n//\n// Cards closer to the user's skill level (ELO) receive higher scores.\n// This ensures learners see content matched to their current ability.\n//\n// NOTE: This generator only handles NEW cards. Reviews are handled by\n// SRSNavigator. Use CompositeGenerator to combine both.\n//\n// ============================================================================\n\n/**\n * A navigation strategy that scores new cards by ELO proximity.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Higher scores indicate better ELO match:\n * - Cards at user's ELO level score highest\n * - Score decreases with ELO distance\n *\n * Only returns new cards - use SRSNavigator for reviews.\n */\nexport default class ELONavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData?: { name: string; _id: string }\n // The ELO strategy is non-parameterized.\n //\n // It instead relies on existing meta data from the course and user with respect to\n // ELO scores - it uses those to select cards matched to user skill level.\n ) {\n super(user, course, strategyData as any);\n this.name = strategyData?.name || 'ELO';\n }\n\n /**\n * Get new cards with suitability scores based on ELO distance.\n *\n * Cards closer to user's ELO get higher scores.\n * Score formula: max(0, 1 - distance / 500)\n *\n * NOTE: This generator only handles NEW cards. Reviews are handled by\n * SRSNavigator. Use CompositeGenerator to combine both.\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param context - Optional GeneratorContext (used when called via Pipeline)\n */\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<GeneratorResult> {\n // Determine user ELO - from context if available, otherwise fetch\n let userGlobalElo: number;\n if (context?.userElo !== undefined) {\n userGlobalElo = context.userElo;\n } else {\n const courseReg = await this.user.getCourseRegDoc(this.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n userGlobalElo = userElo.global.score;\n }\n\n const activeCards = await this.user.getActiveCards();\n const newCards = (\n await this.course.getCardsCenteredAtELO(\n { limit, elo: 'user' },\n (c: QualifiedCardID) => !activeCards.some((ac) => c.cardID === ac.cardID)\n )\n ).map((c) => ({ ...c, status: 'new' as const }));\n\n // Get ELO data for all cards in one batch\n const cardIds = newCards.map((c) => c.cardID);\n const cardEloData = await this.course.getCardEloData(cardIds);\n\n // Score new cards by ELO distance, then apply weighted sampling without\n // replacement using the Efraimidis-Spirakis (A-Res) algorithm:\n //\n // key = U ^ (1 / rawScore) where U ~ Uniform(0, 1)\n //\n // Sorting by key descending produces a weighted random sample: high-score\n // cards are still preferred, but cards with equal scores are shuffled\n // uniformly rather than deterministically. This prevents the same failed\n // cards from looping back every session when many cards share similar ELO.\n //\n // Edge case: rawScore=0 → key=0, never selected (correct exclusion).\n const scored: WeightedCard[] = newCards.map((c, i) => {\n const cardElo = cardEloData[i]?.global?.score ?? 1000;\n const distance = Math.abs(cardElo - userGlobalElo);\n const rawScore = Math.max(0, 1 - distance / 500);\n const samplingKey = rawScore > 0 ? Math.random() ** (1 / rawScore) : 0;\n\n return {\n cardId: c.cardID,\n courseId: c.courseID,\n score: samplingKey,\n provenance: [\n {\n strategy: 'elo',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-ELO-default',\n action: 'generated',\n score: samplingKey,\n reason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userGlobalElo)}), raw ${rawScore.toFixed(3)}, key ${samplingKey.toFixed(3)}`,\n },\n ],\n };\n });\n\n // Sort by sampling key descending (weighted sample without replacement)\n scored.sort((a, b) => b.score - a.score);\n\n const cards = scored.slice(0, limit);\n\n // Log summary for transparency\n if (cards.length > 0) {\n const topScores = cards.slice(0, 3).map((c) => c.score.toFixed(2)).join(', ');\n logger.info(\n `[ELO] Course ${this.course.getCourseID()}: ${cards.length} new cards (top scores: ${topScores})`\n );\n } else {\n logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);\n }\n\n return { cards };\n }\n}\n","// Generator types and interfaces\nexport type { CardGenerator, GeneratorContext, CardGeneratorFactory } from './types';\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardGenerator, GeneratorContext, GeneratorResult } from './types';\nimport { logger } from '@db/util/logger';\nimport type { ReplanHints } from '../../study/SessionController';\n\n// ============================================================================\n// PRESCRIBED CARDS GENERATOR\n// ============================================================================\n//\n// A stateful generator for authored, course-prescribed content.\n//\n// Unlike ELO/SRS, prescribed content is explicitly authored curriculum intent.\n// This generator therefore tracks whether prescribed targets have actually been\n// encountered by the user, applies progressive pressure to stale/pending target\n// groups, and can emit upstream support cards when direct targets remain\n// blocked behind prerequisite chains.\n//\n// The first intended use case is intro-card reliability:\n//\n// - direct targets: intro cards that must eventually surface\n// - support cards: low-complexity cards that help satisfy prereqs for blocked\n// intro targets\n//\n// Prescribed content still participates in the normal pipeline. Hierarchy,\n// lesson gating, letter gating, interference, and priority filters continue to\n// shape final ordering.\n//\n// ============================================================================\n\ninterface HierarchyWalkConfig {\n enabled?: boolean;\n maxDepth?: number;\n}\n\ninterface PrescribedGroupConfig {\n id: string;\n targetCardIds: string[];\n supportCardIds?: string[];\n supportTagPatterns?: string[];\n freshnessWindowSessions?: number;\n maxDirectTargetsPerRun?: number;\n maxSupportCardsPerRun?: number;\n hierarchyWalk?: HierarchyWalkConfig;\n retireOnEncounter?: boolean;\n}\n\ninterface PrescribedConfig {\n groups: PrescribedGroupConfig[];\n}\n\ninterface GroupCardState {\n encounteredCardIds: string[];\n pendingTargetIds: string[];\n lastSurfacedAt: string | null;\n sessionsSinceSurfaced: number;\n lastSupportAt: string | null;\n blockedTargetIds: string[];\n lastResolvedSupportTags: string[];\n}\n\ninterface PrescribedProgressState {\n updatedAt: string;\n groups: Record<string, GroupCardState>;\n}\n\ninterface TagPrerequisite {\n tag: string;\n masteryThreshold?: {\n minElo?: number;\n minCount?: number;\n };\n preReqBoost?: number;\n targetBoost?: number;\n}\n\ninterface HierarchyConfig {\n prerequisites: Record<string, TagPrerequisite[]>;\n}\n\ninterface GroupRuntimeState {\n group: PrescribedGroupConfig;\n encounteredTargets: Set<string>;\n pendingTargets: string[];\n blockedTargets: string[];\n surfaceableTargets: string[];\n targetTags: Map<string, string[]>;\n supportCandidates: string[];\n supportTags: string[];\n pressureMultiplier: number;\n supportMultiplier: number;\n debugVersion: string;\n}\n\ninterface HintEmissionSummary {\n boostTags: Record<string, number>;\n blockedTargetIds: string[];\n supportTags: string[];\n}\n\nconst DEFAULT_FRESHNESS_WINDOW = 3;\nconst DEFAULT_MAX_DIRECT_PER_RUN = 3;\nconst DEFAULT_MAX_SUPPORT_PER_RUN = 3;\nconst DEFAULT_HIERARCHY_DEPTH = 2;\nconst DEFAULT_MIN_COUNT = 3;\nconst BASE_TARGET_SCORE = 1.0;\nconst BASE_SUPPORT_SCORE = 0.8;\nconst MAX_TARGET_MULTIPLIER = 8.0;\nconst MAX_SUPPORT_MULTIPLIER = 4.0;\nconst LOCKED_TAG_PREFIXES = ['concept:'];\nconst LESSON_GATE_PENALTY_TAG_HINT = 'concept:';\nconst PRESCRIBED_DEBUG_VERSION = 'testversion-prescribed-v2';\n\nfunction dedupe<T>(arr: T[]): T[] {\n return [...new Set(arr)];\n}\n\nfunction isoNow(): string {\n return new Date().toISOString();\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\nfunction matchesTagPattern(tag: string, pattern: string): boolean {\n if (pattern === '*') return true;\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*');\n const re = new RegExp(`^${escaped}$`);\n return re.test(tag);\n}\n\nfunction pickTopByScore(cards: WeightedCard[], limit: number): WeightedCard[] {\n return [...cards]\n .sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId))\n .slice(0, limit);\n}\n\nexport default class PrescribedCardsGenerator extends ContentNavigator implements CardGenerator {\n name: string;\n private config: PrescribedConfig;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.name = strategyData.name || 'Prescribed Cards';\n this.config = this.parseConfig(strategyData.serializedData);\n\n logger.debug(\n `[Prescribed] Initialized with ${this.config.groups.length} groups and ` +\n `${this.config.groups.reduce((n, g) => n + g.targetCardIds.length, 0)} targets`\n );\n }\n\n protected override get strategyKey(): string {\n return 'PrescribedProgress';\n }\n\n async getWeightedCards(limit: number, context?: GeneratorContext): Promise<GeneratorResult> {\n if (this.config.groups.length === 0 || limit <= 0) {\n return { cards: [] };\n }\n\n const courseId = this.course.getCourseID();\n const activeCards = await this.user.getActiveCards();\n const activeIds = new Set(activeCards.map((ac) => ac.cardID));\n\n const seenCards = await this.user.getSeenCards(courseId).catch(() => []);\n const seenIds = new Set(seenCards);\n\n const progress = (await this.getStrategyState<PrescribedProgressState>()) ?? {\n updatedAt: isoNow(),\n groups: {},\n };\n\n const hierarchyConfigs = await this.loadHierarchyConfigs();\n const courseReg = await this.user.getCourseRegDoc(courseId).catch(() => null);\n const userGlobalElo =\n typeof courseReg?.elo === 'number'\n ? courseReg.elo\n : courseReg?.elo?.global?.score ?? context?.userElo ?? 1000;\n const userTagElo =\n typeof courseReg?.elo === 'number'\n ? {}\n : courseReg?.elo?.tags ?? {};\n\n const allTargetIds = dedupe(this.config.groups.flatMap((g) => g.targetCardIds));\n const allSupportIds = dedupe(this.config.groups.flatMap((g) => g.supportCardIds ?? []));\n const allRelevantIds = dedupe([...allTargetIds, ...allSupportIds]);\n\n const tagsByCard =\n allRelevantIds.length > 0\n ? await this.course.getAppliedTagsBatch(allRelevantIds)\n : new Map<string, string[]>();\n\n const nextState: PrescribedProgressState = {\n updatedAt: isoNow(),\n groups: {},\n };\n\n const emitted: WeightedCard[] = [];\n const emittedIds = new Set<string>();\n const groupRuntimes: GroupRuntimeState[] = [];\n\n for (const group of this.config.groups) {\n const runtime = this.buildGroupRuntimeState({\n group,\n priorState: progress.groups[group.id],\n activeIds,\n seenIds,\n tagsByCard,\n hierarchyConfigs,\n userTagElo,\n userGlobalElo,\n });\n\n groupRuntimes.push(runtime);\n nextState.groups[group.id] = this.buildNextGroupState(runtime, progress.groups[group.id]);\n\n const directCards = this.buildDirectTargetCards(\n runtime,\n courseId,\n emittedIds\n );\n const supportCards = this.buildSupportCards(\n runtime,\n courseId,\n emittedIds\n );\n\n emitted.push(...directCards, ...supportCards);\n }\n\n const hintSummary = this.buildSupportHintSummary(groupRuntimes);\n const hints: ReplanHints | undefined =\n Object.keys(hintSummary.boostTags).length > 0\n ? {\n boostTags: hintSummary.boostTags,\n _label:\n `prescribed-support (${hintSummary.supportTags.length} tags; ` +\n `blocked=${hintSummary.blockedTargetIds.length}; ` +\n `testversion=${PRESCRIBED_DEBUG_VERSION})`,\n }\n : undefined;\n\n if (emitted.length === 0) {\n logger.debug('[Prescribed] No prescribed targets/support emitted this run');\n await this.putStrategyState(nextState).catch((e) => {\n logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`);\n });\n return hints ? { cards: [], hints } : { cards: [] };\n }\n\n const finalCards = pickTopByScore(emitted, limit);\n\n const surfacedByGroup = new Map<string, { targetIds: string[]; supportIds: string[] }>();\n for (const card of finalCards) {\n const prov = card.provenance[0];\n const groupId = prov?.reason.match(/group=([^;]+)/)?.[1];\n const mode = prov?.reason.includes('mode=support') ? 'supportIds' : 'targetIds';\n if (!groupId) continue;\n if (!surfacedByGroup.has(groupId)) {\n surfacedByGroup.set(groupId, { targetIds: [], supportIds: [] });\n }\n surfacedByGroup.get(groupId)![mode].push(card.cardId);\n }\n\n for (const group of this.config.groups) {\n const groupState = nextState.groups[group.id];\n const surfaced = surfacedByGroup.get(group.id);\n if (surfaced && (surfaced.targetIds.length > 0 || surfaced.supportIds.length > 0)) {\n groupState.lastSurfacedAt = isoNow();\n groupState.sessionsSinceSurfaced = 0;\n if (surfaced.supportIds.length > 0) {\n groupState.lastSupportAt = isoNow();\n }\n }\n }\n\n await this.putStrategyState(nextState).catch((e) => {\n logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`);\n });\n\n logger.info(\n `[Prescribed] Emitting ${finalCards.length} cards ` +\n `(${finalCards.filter((c) => c.provenance[0]?.reason.includes('mode=target')).length} target, ` +\n `${finalCards.filter((c) => c.provenance[0]?.reason.includes('mode=support')).length} support)`\n );\n\n return hints ? { cards: finalCards, hints } : { cards: finalCards };\n }\n\n private buildSupportHintSummary(groupRuntimes: GroupRuntimeState[]): HintEmissionSummary {\n const boostTags: Record<string, number> = {};\n const blockedTargetIds = new Set<string>();\n const supportTags = new Set<string>();\n\n for (const runtime of groupRuntimes) {\n if (runtime.blockedTargets.length === 0 || runtime.supportTags.length === 0) {\n continue;\n }\n\n runtime.blockedTargets.forEach((cardId) => blockedTargetIds.add(cardId));\n\n for (const tag of runtime.supportTags) {\n supportTags.add(tag);\n boostTags[tag] = (boostTags[tag] ?? 1) * runtime.supportMultiplier;\n }\n }\n\n return {\n boostTags,\n blockedTargetIds: [...blockedTargetIds].sort(),\n supportTags: [...supportTags].sort(),\n };\n }\n\n private parseConfig(serializedData: string): PrescribedConfig {\n try {\n const parsed = JSON.parse(serializedData);\n const groupsRaw = Array.isArray(parsed.groups) ? parsed.groups : [];\n const groups: PrescribedGroupConfig[] = groupsRaw\n .map((raw: any, i: number) => ({\n id: typeof raw.id === 'string' && raw.id.trim().length > 0 ? raw.id : `group-${i + 1}`,\n targetCardIds: dedupe(Array.isArray(raw.targetCardIds) ? raw.targetCardIds.filter((v: unknown) => typeof v === 'string') : []),\n supportCardIds: dedupe(Array.isArray(raw.supportCardIds) ? raw.supportCardIds.filter((v: unknown) => typeof v === 'string') : []),\n supportTagPatterns: dedupe(Array.isArray(raw.supportTagPatterns) ? raw.supportTagPatterns.filter((v: unknown) => typeof v === 'string') : []),\n freshnessWindowSessions:\n typeof raw.freshnessWindowSessions === 'number' ? raw.freshnessWindowSessions : DEFAULT_FRESHNESS_WINDOW,\n maxDirectTargetsPerRun:\n typeof raw.maxDirectTargetsPerRun === 'number' ? raw.maxDirectTargetsPerRun : DEFAULT_MAX_DIRECT_PER_RUN,\n maxSupportCardsPerRun:\n typeof raw.maxSupportCardsPerRun === 'number' ? raw.maxSupportCardsPerRun : DEFAULT_MAX_SUPPORT_PER_RUN,\n hierarchyWalk: {\n enabled: raw.hierarchyWalk?.enabled !== false,\n maxDepth:\n typeof raw.hierarchyWalk?.maxDepth === 'number'\n ? raw.hierarchyWalk.maxDepth\n : DEFAULT_HIERARCHY_DEPTH,\n },\n retireOnEncounter: raw.retireOnEncounter !== false,\n }))\n .filter((g) => g.targetCardIds.length > 0);\n\n return { groups };\n } catch {\n return { groups: [] };\n }\n }\n\n private async loadHierarchyConfigs(): Promise<HierarchyConfig[]> {\n try {\n const strategies = await this.course.getNavigationStrategies();\n return strategies\n .filter((s) => s.implementingClass === 'hierarchyDefinition')\n .map((s) => {\n try {\n const parsed = JSON.parse(s.serializedData);\n return {\n prerequisites: parsed.prerequisites || {},\n } as HierarchyConfig;\n } catch {\n return { prerequisites: {} };\n }\n });\n } catch (e) {\n logger.debug(`[Prescribed] Failed to load hierarchy configs: ${e}`);\n return [];\n }\n }\n\n private buildGroupRuntimeState(args: {\n group: PrescribedGroupConfig;\n priorState?: GroupCardState;\n activeIds: Set<string>;\n seenIds: Set<string>;\n tagsByCard: Map<string, string[]>;\n hierarchyConfigs: HierarchyConfig[];\n userTagElo: Record<string, { score: number; count: number }>;\n userGlobalElo: number;\n }): GroupRuntimeState {\n const {\n group,\n priorState,\n activeIds,\n seenIds,\n tagsByCard,\n hierarchyConfigs,\n userTagElo,\n userGlobalElo,\n } = args;\n\n const encounteredTargets = new Set<string>();\n for (const cardId of group.targetCardIds) {\n if (activeIds.has(cardId) || seenIds.has(cardId)) {\n encounteredTargets.add(cardId);\n }\n }\n\n if (priorState?.encounteredCardIds?.length) {\n for (const cardId of priorState.encounteredCardIds) {\n encounteredTargets.add(cardId);\n }\n }\n\n const pendingTargets = group.targetCardIds.filter((id) => !encounteredTargets.has(id));\n const targetTags = new Map<string, string[]>();\n for (const cardId of pendingTargets) {\n targetTags.set(cardId, tagsByCard.get(cardId) ?? []);\n }\n\n const blockedTargets: string[] = [];\n const surfaceableTargets: string[] = [];\n const supportTags = new Set<string>();\n\n for (const cardId of pendingTargets) {\n const tags = targetTags.get(cardId) ?? [];\n const resolution = this.resolveBlockedSupportTags(\n tags,\n hierarchyConfigs,\n userTagElo,\n userGlobalElo,\n group.hierarchyWalk?.enabled !== false,\n group.hierarchyWalk?.maxDepth ?? DEFAULT_HIERARCHY_DEPTH\n );\n\n const introTags = tags.filter((tag) => tag.startsWith('gpc:intro:'));\n const exposeTags = new Set(tags.filter((tag) => tag.startsWith('gpc:expose:')));\n\n for (const introTag of introTags) {\n const suffix = introTag.slice('gpc:intro:'.length);\n if (suffix) {\n exposeTags.add(`gpc:expose:${suffix}`);\n }\n }\n\n const unmetExposeTags = [...exposeTags].filter((tag) => {\n const tagElo = userTagElo[tag];\n return !tagElo || tagElo.count < DEFAULT_MIN_COUNT;\n });\n\n if (unmetExposeTags.length > 0) {\n unmetExposeTags.forEach((tag) => supportTags.add(tag));\n }\n\n if (resolution.blocked || unmetExposeTags.length > 0) {\n blockedTargets.push(cardId);\n resolution.supportTags.forEach((t) => supportTags.add(t));\n } else {\n surfaceableTargets.push(cardId);\n }\n }\n\n const supportCandidates = dedupe([\n ...(group.supportCardIds ?? []),\n ...this.findSupportCardsByTags(\n group,\n tagsByCard,\n [...supportTags]\n ),\n ]).filter((id) => !activeIds.has(id) && !seenIds.has(id));\n\n const sessionsSinceSurfaced = priorState?.sessionsSinceSurfaced ?? 0;\n const freshnessWindow = group.freshnessWindowSessions ?? DEFAULT_FRESHNESS_WINDOW;\n const staleSessions = Math.max(0, sessionsSinceSurfaced - freshnessWindow);\n\n const pressureMultiplier = pendingTargets.length === 0\n ? 1.0\n : clamp(1 + staleSessions * 0.75 + Math.min(2, pendingTargets.length * 0.1), 1.0, MAX_TARGET_MULTIPLIER);\n\n const supportMultiplier = blockedTargets.length === 0\n ? 1.0\n : clamp(1 + staleSessions * 0.5 + Math.min(1.5, blockedTargets.length * 0.15), 1.0, MAX_SUPPORT_MULTIPLIER);\n\n return {\n group,\n encounteredTargets,\n pendingTargets,\n blockedTargets,\n surfaceableTargets,\n targetTags,\n supportCandidates,\n supportTags: [...supportTags],\n pressureMultiplier,\n supportMultiplier,\n debugVersion: PRESCRIBED_DEBUG_VERSION,\n };\n }\n\n private buildNextGroupState(runtime: GroupRuntimeState, prior?: GroupCardState): GroupCardState {\n const carriedSessions = prior?.sessionsSinceSurfaced ?? 0;\n const surfacedThisRun = false;\n\n return {\n encounteredCardIds: [...runtime.encounteredTargets].sort(),\n pendingTargetIds: [...runtime.pendingTargets].sort(),\n lastSurfacedAt: prior?.lastSurfacedAt ?? null,\n sessionsSinceSurfaced: surfacedThisRun ? 0 : carriedSessions + 1,\n lastSupportAt: prior?.lastSupportAt ?? null,\n blockedTargetIds: [...runtime.blockedTargets].sort(),\n lastResolvedSupportTags: [...runtime.supportTags].sort(),\n };\n }\n\n private buildDirectTargetCards(\n runtime: GroupRuntimeState,\n courseId: string,\n emittedIds: Set<string>\n ): WeightedCard[] {\n const maxDirect = runtime.group.maxDirectTargetsPerRun ?? DEFAULT_MAX_DIRECT_PER_RUN;\n\n const directIds = runtime.surfaceableTargets\n .filter((id) => !emittedIds.has(id))\n .slice(0, maxDirect);\n\n const cards: WeightedCard[] = [];\n for (const cardId of directIds) {\n emittedIds.add(cardId);\n cards.push({\n cardId,\n courseId,\n score: BASE_TARGET_SCORE * runtime.pressureMultiplier,\n provenance: [\n {\n strategy: 'prescribed',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-prescribed',\n action: 'generated' as const,\n score: BASE_TARGET_SCORE * runtime.pressureMultiplier,\n reason:\n `mode=target;group=${runtime.group.id};pending=${runtime.pendingTargets.length};` +\n `surfaceable=${runtime.surfaceableTargets.length};blocked=${runtime.blockedTargets.length};` +\n `blockedTargets=${runtime.blockedTargets.join('|') || 'none'};` +\n `supportTags=${runtime.supportTags.join('|') || 'none'};` +\n `multiplier=${runtime.pressureMultiplier.toFixed(2)};` +\n `testversion=${runtime.debugVersion}`,\n },\n ],\n });\n }\n\n return cards;\n }\n\n private buildSupportCards(\n runtime: GroupRuntimeState,\n courseId: string,\n emittedIds: Set<string>\n ): WeightedCard[] {\n if (runtime.blockedTargets.length === 0 || runtime.supportCandidates.length === 0) {\n return [];\n }\n\n const maxSupport = runtime.group.maxSupportCardsPerRun ?? DEFAULT_MAX_SUPPORT_PER_RUN;\n const supportIds = runtime.supportCandidates\n .filter((id) => !emittedIds.has(id))\n .slice(0, maxSupport);\n\n const cards: WeightedCard[] = [];\n for (const cardId of supportIds) {\n emittedIds.add(cardId);\n cards.push({\n cardId,\n courseId,\n score: BASE_SUPPORT_SCORE * runtime.supportMultiplier,\n provenance: [\n {\n strategy: 'prescribed',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-prescribed',\n action: 'generated' as const,\n score: BASE_SUPPORT_SCORE * runtime.supportMultiplier,\n reason:\n `mode=support;group=${runtime.group.id};pending=${runtime.pendingTargets.length};` +\n `blocked=${runtime.blockedTargets.length};` +\n `blockedTargets=${runtime.blockedTargets.join('|') || 'none'};` +\n `supportCard=${cardId};` +\n `supportTags=${runtime.supportTags.join('|') || 'none'};` +\n `multiplier=${runtime.supportMultiplier.toFixed(2)};` +\n `testversion=${runtime.debugVersion}`,\n },\n ],\n });\n }\n\n return cards;\n }\n\n private findSupportCardsByTags(\n group: PrescribedGroupConfig,\n tagsByCard: Map<string, string[]>,\n supportTags: string[]\n ): string[] {\n if (supportTags.length === 0) {\n return [];\n }\n\n const explicitSupportIds = group.supportCardIds ?? [];\n const explicitPatterns = group.supportTagPatterns ?? [];\n if (explicitSupportIds.length === 0 && explicitPatterns.length === 0) {\n return [];\n }\n\n const candidates = new Set<string>();\n\n for (const cardId of explicitSupportIds) {\n const cardTags = tagsByCard.get(cardId) ?? [];\n const matchesResolved = supportTags.some((supportTag) => cardTags.includes(supportTag));\n const matchesPattern = explicitPatterns.some((pattern) =>\n cardTags.some((tag) => matchesTagPattern(tag, pattern))\n );\n\n if (matchesResolved || matchesPattern) {\n candidates.add(cardId);\n }\n }\n\n return [...candidates];\n }\n\n private resolveBlockedSupportTags(\n targetTags: string[],\n hierarchyConfigs: HierarchyConfig[],\n userTagElo: Record<string, { score: number; count: number }>,\n userGlobalElo: number,\n hierarchyWalkEnabled: boolean,\n maxDepth: number\n ): { blocked: boolean; supportTags: string[] } {\n const supportTags = new Set<string>();\n let blocked = false;\n\n for (const targetTag of targetTags) {\n const prereqSets = hierarchyConfigs\n .map((hierarchy) => hierarchy.prerequisites[targetTag])\n .filter((prereqs): prereqs is TagPrerequisite[] => Array.isArray(prereqs) && prereqs.length > 0);\n\n if (prereqSets.length === 0) {\n continue;\n }\n\n const tagBlocked = prereqSets.some((prereqs) =>\n prereqs.some((pr) => !this.isPrerequisiteMet(pr, userTagElo[pr.tag], userGlobalElo))\n );\n\n if (!tagBlocked) {\n continue;\n }\n\n blocked = true;\n\n if (!hierarchyWalkEnabled) {\n for (const prereqs of prereqSets) {\n for (const prereq of prereqs) {\n if (!this.isPrerequisiteMet(prereq, userTagElo[prereq.tag], userGlobalElo)) {\n supportTags.add(prereq.tag);\n }\n }\n }\n continue;\n }\n\n for (const prereqs of prereqSets) {\n for (const prereq of prereqs) {\n if (!this.isPrerequisiteMet(prereq, userTagElo[prereq.tag], userGlobalElo)) {\n this.collectSupportTagsRecursive(\n prereq.tag,\n hierarchyConfigs,\n userTagElo,\n userGlobalElo,\n maxDepth,\n new Set<string>(),\n supportTags\n );\n }\n }\n }\n }\n\n return { blocked, supportTags: [...supportTags] };\n }\n\n private collectSupportTagsRecursive(\n tag: string,\n hierarchyConfigs: HierarchyConfig[],\n userTagElo: Record<string, { score: number; count: number }>,\n userGlobalElo: number,\n depth: number,\n visited: Set<string>,\n out: Set<string>\n ): void {\n if (depth < 0 || visited.has(tag)) return;\n if (this.isHardGatedTag(tag)) return;\n\n visited.add(tag);\n\n let walkedFurther = false;\n\n for (const hierarchy of hierarchyConfigs) {\n const prereqs = hierarchy.prerequisites[tag];\n if (!prereqs || prereqs.length === 0) continue;\n\n const unmet = prereqs.filter(\n (pr) => !this.isPrerequisiteMet(pr, userTagElo[pr.tag], userGlobalElo)\n );\n\n if (unmet.length > 0 && depth > 0) {\n walkedFurther = true;\n for (const prereq of unmet) {\n this.collectSupportTagsRecursive(\n prereq.tag,\n hierarchyConfigs,\n userTagElo,\n userGlobalElo,\n depth - 1,\n visited,\n out\n );\n }\n }\n }\n\n if (!walkedFurther) {\n out.add(tag);\n }\n }\n\n private isHardGatedTag(tag: string): boolean {\n return LOCKED_TAG_PREFIXES.some((prefix) => tag.startsWith(prefix)) &&\n tag.startsWith(LESSON_GATE_PENALTY_TAG_HINT);\n }\n\n private isPrerequisiteMet(\n prereq: TagPrerequisite,\n userTagElo: { score: number; count: number } | undefined,\n userGlobalElo: number\n ): boolean {\n if (!userTagElo) return false;\n\n const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;\n if (userTagElo.count < minCount) return false;\n\n if (prereq.masteryThreshold?.minElo !== undefined) {\n return userTagElo.score >= prereq.masteryThreshold.minElo;\n }\n if (prereq.masteryThreshold?.minCount !== undefined) {\n return true;\n }\n\n return userTagElo.score >= userGlobalElo;\n }\n}","import moment from 'moment';\nimport type { ScheduledCard } from '../../types/user';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardGenerator, GeneratorContext, GeneratorResult } from './types';\nimport { logger } from '@db/util/logger';\n\n// ============================================================================\n// SRS NAVIGATOR\n// ============================================================================\n//\n// A generator strategy that scores review cards by urgency.\n//\n// Urgency is determined by three factors:\n// 1. Overdueness - how far past the scheduled review time\n// 2. Interval recency - shorter scheduled intervals indicate \"novel content in progress\"\n// 3. Backlog pressure - when too many reviews pile up, urgency increases globally\n//\n// A card with a 3-day interval that's 2 days overdue is more urgent than a card\n// with a 6-month interval that's 2 days overdue. The shorter interval represents\n// active learning at higher resolution.\n//\n// DESIGN PHILOSOPHY: SRS scheduling times are \"eligibility dates\" not hard \"due dates\".\n// When a card becomes eligible, it is \"okish\" to review now, but reviewing a little\n// later may be optimal. We don't aim to always beat review queues to zero (death spiral),\n// but rather maintain a healthy backlog of eligible reviews so the system can gracefully\n// handle usage upticks or breaks.\n//\n// This navigator only handles reviews - it does not generate new cards.\n// For new cards, use ELONavigator or another generator via CompositeGenerator.\n//\n// ============================================================================\n\n/**\n * Default healthy backlog size.\n * When due reviews exceed this, backlog pressure kicks in.\n * Can be overridden via strategy config.\n */\nconst DEFAULT_HEALTHY_BACKLOG = 20;\n\n/**\n * Maximum backlog pressure contribution to score.\n * At 3x healthy backlog, pressure maxes out.\n */\nconst MAX_BACKLOG_PRESSURE = 0.5;\n\n/**\n * Configuration for the SRS strategy.\n */\nexport interface SRSConfig {\n /**\n * Target \"healthy\" backlog size.\n * When due reviews exceed this, urgency increases globally.\n * Default: 20\n */\n healthyBacklog?: number;\n}\n\n/**\n * A navigation strategy that scores review cards by urgency.\n *\n * Implements CardGenerator for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility with legacy code.\n *\n * Higher scores indicate more urgent reviews:\n * - Cards that are more overdue (relative to their interval) score higher\n * - Cards with shorter intervals (recent learning) score higher\n * - When backlog exceeds \"healthy\" threshold, all reviews get urgency boost\n *\n * Only returns cards that are actually due (reviewTime has passed).\n * Does not generate new cards - use with CompositeGenerator for mixed content.\n */\nexport default class SRSNavigator extends ContentNavigator implements CardGenerator {\n /** Human-readable name for CardGenerator interface */\n name: string;\n\n /** Healthy backlog threshold - when exceeded, backlog pressure kicks in */\n private healthyBacklog: number;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData?: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData as ContentNavigationStrategyData);\n this.name = strategyData?.name || 'SRS';\n\n // Parse config from serializedData if available\n const config = this.parseConfig(strategyData?.serializedData);\n this.healthyBacklog = config.healthyBacklog ?? DEFAULT_HEALTHY_BACKLOG;\n }\n\n /**\n * Parse configuration from serialized JSON.\n */\n private parseConfig(serializedData?: string): SRSConfig {\n if (!serializedData) return {};\n try {\n return JSON.parse(serializedData) as SRSConfig;\n } catch {\n logger.warn('[SRS] Failed to parse strategy config, using defaults');\n return {};\n }\n }\n\n /**\n * Get review cards scored by urgency.\n *\n * Score formula combines:\n * - Relative overdueness: hoursOverdue / intervalHours\n * - Interval recency: exponential decay favoring shorter intervals\n * - Backlog pressure: boost when due reviews exceed healthy threshold\n *\n * Cards not yet due are excluded (not scored as 0).\n *\n * This method supports both the legacy signature (limit only) and the\n * CardGenerator interface signature (limit, context).\n *\n * @param limit - Maximum number of cards to return\n * @param _context - Optional GeneratorContext (currently unused, but required for interface)\n */\n async getWeightedCards(limit: number, _context?: GeneratorContext): Promise<GeneratorResult> {\n if (!this.user || !this.course) {\n throw new Error('SRSNavigator requires user and course to be set');\n }\n\n const courseId = this.course.getCourseID();\n const reviews = await this.user.getPendingReviews(courseId);\n const now = moment.utc();\n\n // Filter to only cards that are actually due\n const dueReviews = reviews.filter((r) => now.isAfter(moment.utc(r.reviewTime)));\n\n // Compute backlog pressure - applies globally to all reviews\n const backlogPressure = this.computeBacklogPressure(dueReviews.length);\n\n // Log review status for transparency\n if (dueReviews.length > 0) {\n const pressureNote =\n backlogPressure > 0\n ? ` [backlog pressure: +${backlogPressure.toFixed(2)}]`\n : ` [healthy backlog]`;\n logger.info(\n `[SRS] Course ${courseId}: ${dueReviews.length} reviews due now (of ${reviews.length} scheduled)${pressureNote}`\n );\n } else if (reviews.length > 0) {\n // Reviews exist but none are due yet - show when next one is due\n const sortedByDue = [...reviews].sort((a, b) =>\n moment.utc(a.reviewTime).diff(moment.utc(b.reviewTime))\n );\n const nextDue = sortedByDue[0];\n const nextDueTime = moment.utc(nextDue.reviewTime);\n const untilDue = moment.duration(nextDueTime.diff(now));\n const untilDueStr =\n untilDue.asHours() < 1\n ? `${Math.round(untilDue.asMinutes())}m`\n : untilDue.asHours() < 24\n ? `${Math.round(untilDue.asHours())}h`\n : `${Math.round(untilDue.asDays())}d`;\n logger.info(\n `[SRS] Course ${courseId}: 0 reviews due now (${reviews.length} scheduled, next in ${untilDueStr})`\n );\n } else {\n logger.info(`[SRS] Course ${courseId}: No reviews scheduled`);\n }\n\n const scored = dueReviews.map((review) => {\n const { score, reason } = this.computeUrgencyScore(review, now, backlogPressure);\n\n return {\n cardId: review.cardId,\n courseId: review.courseId,\n score,\n reviewID: review._id,\n provenance: [\n {\n strategy: 'srs',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-SRS-default',\n action: 'generated' as const,\n score,\n reason,\n },\n ],\n };\n });\n\n // Sort by score descending and limit\n return { cards: scored.sort((a, b) => b.score - a.score).slice(0, limit) };\n }\n\n /**\n * Compute backlog pressure based on number of due reviews.\n *\n * Backlog pressure is 0 when at or below healthy threshold,\n * and increases linearly above it, maxing out at MAX_BACKLOG_PRESSURE.\n *\n * Examples (with default healthyBacklog=20):\n * - 10 due reviews → 0.00 (healthy)\n * - 20 due reviews → 0.00 (at threshold)\n * - 40 due reviews → 0.25 (2x threshold)\n * - 60 due reviews → 0.50 (3x threshold, maxed)\n *\n * @param dueCount - Number of reviews currently due\n * @returns Backlog pressure score to add to urgency (0 to MAX_BACKLOG_PRESSURE)\n */\n private computeBacklogPressure(dueCount: number): number {\n if (dueCount <= this.healthyBacklog) {\n return 0;\n }\n\n // Linear increase: at 2x healthy, pressure = 0.25; at 3x, pressure = 0.50\n const excess = dueCount - this.healthyBacklog;\n const pressure = (excess / this.healthyBacklog) * (MAX_BACKLOG_PRESSURE / 2);\n\n return Math.min(MAX_BACKLOG_PRESSURE, pressure);\n }\n\n /**\n * Compute urgency score for a review card.\n *\n * Three factors:\n * 1. Relative overdueness = hoursOverdue / intervalHours\n * - 2 days overdue on 3-day interval = 0.67 (urgent)\n * - 2 days overdue on 180-day interval = 0.01 (not urgent)\n *\n * 2. Interval recency factor = 0.3 + 0.7 * exp(-intervalHours / 720)\n * - 24h interval → ~1.0 (very recent learning)\n * - 30 days (720h) → ~0.56\n * - 180 days → ~0.30\n *\n * 3. Backlog pressure = global boost when review backlog exceeds healthy threshold\n * - At healthy backlog: 0\n * - At 2x healthy: +0.25\n * - At 3x+ healthy: +0.50 (max)\n *\n * Combined: base 0.5 + (urgency factors * 0.45) + backlog pressure\n * Result range: 0.5 to 1.0 (uncapped to allow high-urgency reviews to compete with new cards)\n *\n * @param review - The scheduled card to score\n * @param now - Current time\n * @param backlogPressure - Pre-computed backlog pressure (0 to 0.5)\n */\n private computeUrgencyScore(\n review: ScheduledCard,\n now: moment.Moment,\n backlogPressure: number\n ): { score: number; reason: string } {\n const scheduledAt = moment.utc(review.scheduledAt);\n const due = moment.utc(review.reviewTime);\n\n // Interval = time between scheduling and due date (minimum 1 hour to avoid division issues)\n const intervalHours = Math.max(1, due.diff(scheduledAt, 'hours'));\n const hoursOverdue = now.diff(due, 'hours');\n\n // Relative overdueness: how late relative to the interval\n const relativeOverdue = hoursOverdue / intervalHours;\n\n // Interval recency factor: shorter intervals = more urgent\n // Exponential decay with 720h (30 days) as the characteristic time\n const recencyFactor = 0.3 + 0.7 * Math.exp(-intervalHours / 720);\n\n // Combined urgency: weighted average of relative overdue and recency\n // Clamp relative overdue contribution to [0, 1] to avoid runaway scores\n const overdueContribution = Math.min(1.0, Math.max(0, relativeOverdue));\n const urgency = overdueContribution * 0.5 + recencyFactor * 0.5;\n\n // Final score: base 0.5 + urgency contribution + backlog pressure\n // Uncapped at 1.0 (no 0.95 ceiling) - allows high-urgency reviews to compete with new cards\n const baseScore = 0.5 + urgency * 0.45;\n const score = Math.min(1.0, baseScore + backlogPressure);\n\n // Build reason string with all contributing factors\n const reasonParts = [\n `${Math.round(hoursOverdue)}h overdue`,\n `interval: ${Math.round(intervalHours)}h`,\n `relative: ${relativeOverdue.toFixed(2)}`,\n `recency: ${recencyFactor.toFixed(2)}`,\n ];\n\n if (backlogPressure > 0) {\n reasonParts.push(`backlog: +${backlogPressure.toFixed(2)}`);\n }\n\n reasonParts.push('review');\n\n const reason = reasonParts.join(', ');\n\n return { score, reason };\n }\n}","import type { WeightedCard } from '../index';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport type { OrchestrationContext } from '../../orchestration';\n\n/**\n * Typed ephemeral pipeline hints for a single run.\n * All fields are optional. Tag/card patterns support `*` wildcards.\n */\nexport interface ReplanHints {\n /** Multiply scores for cards matching these tag patterns. */\n boostTags?: Record<string, number>;\n /** Multiply scores for these specific card IDs (glob patterns). */\n boostCards?: Record<string, number>;\n /** Cards matching these tag patterns MUST appear in results. */\n requireTags?: string[];\n /** These specific card IDs MUST appear in results. */\n requireCards?: string[];\n /** Remove cards matching these tag patterns from results. */\n excludeTags?: string[];\n /** Remove these specific card IDs from results. */\n excludeCards?: string[];\n /**\n * Debugging label threaded from the replan requester.\n * Prefixed with `_` to signal it's metadata, not a scoring hint.\n */\n _label?: string;\n}\n\n// ============================================================================\n// CARD GENERATOR INTERFACE\n// ============================================================================\n//\n// Generators produce candidate cards with initial scores.\n// They are the \"source\" stage of a navigation pipeline.\n//\n// Examples: ELO (skill proximity), SRS (review scheduling)\n//\n// Generators differ from filters:\n// - Generators: produce candidates from DB queries, assign initial scores\n// - Filters: transform existing candidates, adjust scores with multipliers\n//\n// The Pipeline class orchestrates: Generator → Filter₁ → Filter₂ → ... → Results\n//\n// ============================================================================\n\n/**\n * Context available to generators when producing candidates.\n *\n * Built once per getWeightedCards() call by the Pipeline.\n */\nexport interface GeneratorContext {\n /** User database interface */\n user: UserDBInterface;\n\n /** Course database interface */\n course: CourseDBInterface;\n\n /** User's global ELO score for this course */\n userElo: number;\n\n /** Orchestration context for evolutionary weighting */\n orchestration?: OrchestrationContext;\n\n // Future extensions:\n // - user's tag-level ELO data\n // - course config\n // - session state (cards already seen this session)\n}\n\n/**\n * Structured generator result.\n *\n * Generators may optionally emit one-shot replan hints alongside their\n * candidate cards. This allows a generator to shape the broader pipeline\n * without having to enumerate every affected support card directly.\n */\nexport interface GeneratorResult {\n /** Candidate cards produced by the generator */\n cards: WeightedCard[];\n\n /** Optional one-shot hints to apply after the filter chain */\n hints?: ReplanHints;\n}\n\n/**\n * A generator that produces candidate cards with initial scores.\n *\n * Generators are the \"source\" stage of a navigation pipeline.\n * They query the database for eligible cards and assign initial\n * suitability scores based on their strategy (ELO proximity,\n * review urgency, fixed order, etc.).\n *\n * ## Implementation Guidelines\n *\n * 1. **Create provenance**: Each card should have a provenance entry\n * with action='generated' documenting why it was selected.\n *\n * 2. **Score semantics**: Higher scores = more suitable for presentation.\n * Scores should be in [0, 1] range for composability.\n *\n * 3. **Limit handling**: Respect the limit parameter, but may over-fetch\n * internally if needed for scoring accuracy.\n *\n * 4. **Sort before returning**: Return cards sorted by score descending.\n *\n * 5. **Hints are optional**: Generators may return structured results with\n * `hints` when they need to apply pipeline-wide ephemeral pressure.\n *\n * ## Example Implementation\n *\n * ```typescript\n * const myGenerator: CardGenerator = {\n * name: 'My Generator',\n * async getWeightedCards(limit, context) {\n * const candidates = await fetchCandidates(context.course, limit);\n * return candidates.map(c => ({\n * cardId: c.id,\n * courseId: context.course.getCourseID(),\n * score: computeScore(c, context),\n * provenance: [{\n * strategy: 'myGenerator',\n * strategyName: 'My Generator',\n * strategyId: 'MY_GENERATOR',\n * action: 'generated',\n * score: computeScore(c, context),\n * reason: 'Explanation of selection'\n * }]\n * }));\n * }\n * };\n * ```\n */\nexport interface CardGenerator {\n /** Human-readable name for this generator */\n name: string;\n\n /**\n * Produce candidate cards with initial scores.\n *\n * @param limit - Maximum number of cards to return\n * @param context - Shared context (user, course, userElo, etc.)\n * @returns Cards sorted by score descending, with provenance, optionally\n * accompanied by one-shot replan hints\n */\n getWeightedCards(\n limit: number,\n context: GeneratorContext\n ): Promise<GeneratorResult>;\n}\n\n/**\n * Factory function type for creating generators from configuration.\n *\n * Used by PipelineAssembler to instantiate generators from strategy documents.\n */\nexport type CardGeneratorFactory<TConfig = unknown> = (config: TConfig) => CardGenerator;\n","import { DocType, SkuilderCourseData } from './types-legacy';\nimport type { DocTypePrefixes } from './types-legacy';\n\n/**\n * Configuration for an evolutionarily-weighted strategy.\n *\n * This structure tracks the \"learned\" weight of a strategy, representing the\n * system's confidence in its utility.\n *\n * - weight: The best-known multiplier (peak of the bell curve)\n * - confidence: How certain we are (inverse of variance / spread)\n * - sampleSize: How many data points informed this weight\n */\nexport interface LearnableWeight {\n /** The current best estimate of optimal weight (multiplier) */\n weight: number;\n /** Confidence in this weight (0-1). Higher = narrower exploration spread. */\n confidence: number;\n /** Number of outcome observations that contributed to this weight */\n sampleSize: number;\n}\n\nexport const DEFAULT_LEARNABLE_WEIGHT: LearnableWeight = {\n weight: 1.0,\n confidence: 0.1, // Low confidence initially = wide exploration\n sampleSize: 0,\n};\n\n/**\n *\n */\nexport interface ContentNavigationStrategyData extends SkuilderCourseData {\n _id: `${typeof DocTypePrefixes[DocType.NAVIGATION_STRATEGY]}-${string}`;\n docType: DocType.NAVIGATION_STRATEGY;\n name: string;\n description: string;\n /**\n The name of the class that implements the navigation strategy at runtime.\n */\n implementingClass: string;\n\n /**\n A representation of the strategy's parameterization - to be deserialized\n by the implementing class's constructor at runtime.\n */\n serializedData: string;\n\n /**\n * Evolutionary weighting configuration.\n * If present, the strategy's influence is scaled by this weight.\n * If omitted, weight defaults to 1.0.\n */\n learnable?: LearnableWeight;\n\n /**\n * If true, the weight is applied exactly as configured, without\n * per-user deviation. Used for manual tuning or A/B testing.\n */\n staticWeight?: boolean;\n}\n","import { CardFilter, FilterContext } from './types';\nimport { WeightedCard } from '../index';\nimport { LearnableWeight, DEFAULT_LEARNABLE_WEIGHT } from '../../types/contentNavigationStrategy';\n\n/**\n * Wraps a CardFilter to apply evolutionary weighting to its effects.\n *\n * If a filter applies a multiplier M (score * M), and the strategy has\n * an effective weight W, the final multiplier becomes M^W.\n *\n * - W=1.0: Original behavior\n * - W>1.0: Amplifies the filter's opinion (stronger boost/penalty)\n * - W<1.0: Dampens the filter's opinion (weaker boost/penalty)\n * - W=0.0: Nullifies the filter (identity)\n *\n * This wrapper handles the math of scaling the filter's impact and updating\n * the provenance trail with the effective weight used.\n */\nexport class WeightedFilter implements CardFilter {\n public name: string;\n private inner: CardFilter;\n private learnable: LearnableWeight;\n private staticWeight: boolean;\n private strategyId?: string;\n\n constructor(\n inner: CardFilter,\n learnable: LearnableWeight = DEFAULT_LEARNABLE_WEIGHT,\n staticWeight: boolean = false,\n strategyId?: string\n ) {\n this.inner = inner;\n this.name = inner.name;\n this.learnable = learnable;\n this.staticWeight = staticWeight;\n this.strategyId = strategyId;\n }\n\n /**\n * Apply the inner filter, then scale its effect by the configured weight.\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n // ========================================================================\n // 1. DETERMINE EFFECTIVE WEIGHT\n // ========================================================================\n \n // Determine effective weight using orchestration context if available\n let effectiveWeight = this.learnable.weight;\n let deviation: number | undefined;\n\n if (!this.staticWeight && context.orchestration) {\n // ContentNavigator instances have a strategyId property (protected/private)\n // We assume inner filter is a ContentNavigator or has a strategyId property.\n // Fallback to name if not present.\n const strategyId = this.strategyId || (this.inner as any).strategyId || this.name;\n effectiveWeight = context.orchestration.getEffectiveWeight(strategyId, this.learnable);\n deviation = context.orchestration.getDeviation(strategyId);\n }\n\n // Optimization: If weight is 1.0, the scaling is an identity operation.\n // Just run the inner filter directly.\n if (Math.abs(effectiveWeight - 1.0) < 0.001) {\n return this.inner.transform(cards, context);\n }\n\n // ========================================================================\n // 2. CAPTURE STATE BEFORE FILTER\n // ========================================================================\n \n // We need original scores to calculate what the filter did (M = new/old)\n const originalScores = new Map<string, number>();\n for (const card of cards) {\n originalScores.set(card.cardId, card.score);\n }\n\n // ========================================================================\n // 3. RUN INNER FILTER\n // ========================================================================\n \n const transformedCards = await this.inner.transform(cards, context);\n\n // ========================================================================\n // 4. APPLY WEIGHT SCALING\n // ========================================================================\n \n return transformedCards.map((card) => {\n const originalScore = originalScores.get(card.cardId);\n\n // Edge cases where we can't or shouldn't scale:\n // - Original score missing (shouldn't happen)\n // - Original score 0 (was already excluded)\n // - New score 0 (filter excluded it / vetoed) - we treat vetoes as absolute\n if (originalScore === undefined || originalScore === 0 || card.score === 0) {\n return card;\n }\n\n // Calculate raw effect multiplier: M = new / old\n const rawEffect = card.score / originalScore;\n\n // If filter didn't change this card, nothing to scale\n if (Math.abs(rawEffect - 1.0) < 0.0001) {\n return card;\n }\n\n // Apply weight: scaled = M ^ W\n // Example: 0.5 penalty ^ 2.0 weight = 0.25 (stronger penalty)\n // Example: 0.5 penalty ^ 0.5 weight = 0.707 (weaker penalty)\n const weightedEffect = Math.pow(rawEffect, effectiveWeight);\n const newScore = originalScore * weightedEffect;\n\n // Update provenance\n // The inner filter just added the last entry. We need to update it\n // to reflect the weighted score and record the effective weight.\n const lastProvIndex = card.provenance.length - 1;\n const lastProv = card.provenance[lastProvIndex];\n\n if (lastProv) {\n const updatedProvenance = [...card.provenance];\n updatedProvenance[lastProvIndex] = {\n ...lastProv,\n score: newScore,\n effectiveWeight: effectiveWeight,\n deviation: deviation,\n // We can optionally append to the reason, but the structured field is key\n };\n\n return {\n ...card,\n score: newScore,\n provenance: updatedProvenance,\n };\n }\n\n // Fallback if no provenance found (rare)\n return {\n ...card,\n score: newScore,\n };\n });\n }\n}","import type { WeightedCard } from '../index';\nimport type { CardFilter, FilterContext } from './types';\n\n// ============================================================================\n// ELO DISTANCE FILTER\n// ============================================================================\n//\n// Penalizes cards that are far from the user's current ELO using a smooth curve.\n//\n// This filter addresses cross-strategy coordination:\n// - SRS generates reviews based on scheduling\n// - But some scheduled cards may be \"below\" the user's current level\n// - Or \"above\" (shouldn't happen often, but possible)\n//\n// By applying ELO distance penalties, we can:\n// - Deprioritize reviews the user has \"moved beyond\"\n// - Deprioritize cards that are too hard for current skill level\n//\n// The penalty curve is smooth (no discontinuities) using a Gaussian-like decay.\n//\n// ============================================================================\n\n/**\n * Configuration for the ELO distance filter.\n */\nexport interface EloDistanceConfig {\n /**\n * The ELO distance at which the multiplier is ~0.6 (one standard deviation).\n * Default: 200 ELO points.\n *\n * - At distance 0: multiplier ≈ 1.0\n * - At distance = halfLife: multiplier ≈ 0.6\n * - At distance = 2 * halfLife: multiplier ≈ 0.37\n * - At distance = 3 * halfLife: multiplier ≈ 0.22\n */\n halfLife?: number;\n\n /**\n * Minimum multiplier (floor) to prevent scores from going too low.\n * Default: 0.3\n */\n minMultiplier?: number;\n\n /**\n * Maximum multiplier (ceiling). Usually 1.0 (no boost for close cards).\n * Default: 1.0\n */\n maxMultiplier?: number;\n}\n\nconst DEFAULT_HALF_LIFE = 200;\nconst DEFAULT_MIN_MULTIPLIER = 0.3;\nconst DEFAULT_MAX_MULTIPLIER = 1.0;\n\n/**\n * Compute the multiplier for a given ELO distance using Gaussian decay.\n *\n * Formula: minMultiplier + (maxMultiplier - minMultiplier) * exp(-(distance/halfLife)^2)\n *\n * This produces a smooth bell curve centered at distance=0:\n * - At distance 0: multiplier = maxMultiplier (1.0)\n * - As distance increases: multiplier smoothly decays toward minMultiplier\n * - No discontinuities or sudden jumps\n */\nfunction computeMultiplier(\n distance: number,\n halfLife: number,\n minMultiplier: number,\n maxMultiplier: number\n): number {\n // Gaussian decay: exp(-(d/h)^2)\n const normalizedDistance = distance / halfLife;\n const decay = Math.exp(-(normalizedDistance * normalizedDistance));\n\n // Scale between min and max\n return minMultiplier + (maxMultiplier - minMultiplier) * decay;\n}\n\n/**\n * Create an ELO distance filter.\n *\n * Penalizes cards that are far from the user's current ELO level\n * using a smooth Gaussian decay curve. No discontinuities.\n *\n * @param config - Optional configuration for the decay curve\n * @returns A CardFilter that applies ELO distance penalties\n */\nexport function createEloDistanceFilter(config?: EloDistanceConfig): CardFilter {\n const halfLife = config?.halfLife ?? DEFAULT_HALF_LIFE;\n const minMultiplier = config?.minMultiplier ?? DEFAULT_MIN_MULTIPLIER;\n const maxMultiplier = config?.maxMultiplier ?? DEFAULT_MAX_MULTIPLIER;\n\n return {\n name: 'ELO Distance Filter',\n\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n const { course, userElo } = context;\n\n // Batch fetch ELO data for all cards\n const cardIds = cards.map((c) => c.cardId);\n const cardElos = await course.getCardEloData(cardIds);\n\n return cards.map((card, i) => {\n const cardElo = cardElos[i]?.global?.score ?? 1000;\n const distance = Math.abs(cardElo - userElo);\n const multiplier = computeMultiplier(distance, halfLife, minMultiplier, maxMultiplier);\n const newScore = card.score * multiplier;\n\n const action = multiplier < maxMultiplier - 0.01 ? 'penalized' : 'passed';\n\n return {\n ...card,\n score: newScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'eloDistance',\n strategyName: 'ELO Distance Filter',\n strategyId: 'ELO_DISTANCE_FILTER',\n action,\n score: newScore,\n reason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userElo)}) → ${multiplier.toFixed(2)}x`,\n },\n ],\n };\n });\n },\n };\n}\n\n// Export defaults for testing\nexport { DEFAULT_HALF_LIFE, DEFAULT_MIN_MULTIPLIER, DEFAULT_MAX_MULTIPLIER };\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardFilter, FilterContext } from './types';\nimport type { GeneratorResult } from '../generators/types';\nimport { toCourseElo } from '@vue-skuilder/common';\nimport { logger } from '../../../util/logger';\n\n/**\n * A single prerequisite requirement for a tag.\n * Each prerequisite refers to one tag with its own mastery threshold.\n */\ninterface TagPrerequisite {\n /** The tag that must be mastered */\n tag: string;\n /** Thresholds for considering this prerequisite tag \"mastered\" */\n masteryThreshold?: {\n /** Minimum ELO score for mastery. If not set, uses avgElo comparison */\n minElo?: number;\n /** Minimum interaction count (default: 3) */\n minCount?: number;\n };\n /**\n * Score multiplier applied to cards that carry this prereq tag while\n * the gate is still closed. Steers the pipeline toward cards that help\n * unlock the gated content. Falls away once the prereq is met.\n *\n * Example: `preReqBoost: 1.3` gives a 30% score increase to cards\n * tagged `gpc:expose:t-T` while `gpc:intro:t-T` is still locked.\n */\n preReqBoost?: number;\n /**\n * Score multiplier applied to cards carrying the *gated* tag once the\n * gate opens (all prereqs met). Ensures newly-unlocked content surfaces\n * promptly before natural ELO/SRS scoring takes over.\n *\n * Falls away naturally as generators stop surfacing the card after\n * the user interacts with it.\n *\n * Example: `targetBoost: 4` on intro-t-T's prerequisite gives a 4×\n * score increase to intro-t-T cards once their expose gate is met.\n */\n targetBoost?: number;\n}\n\n/**\n * Configuration for the HierarchyDefinition strategy\n */\nexport interface HierarchyConfig {\n /** Map of tag ID to its list of prerequisites (each with individual thresholds) */\n prerequisites: {\n [tagId: string]: TagPrerequisite[];\n };\n}\n\nconst DEFAULT_MIN_COUNT = 3;\n\n/**\n * A filter strategy that gates cards based on prerequisite mastery.\n *\n * Cards are locked until the user masters all prerequisite tags.\n * Locked cards receive score * 0.05 (strong penalty, not hard filter).\n *\n * Mastery is determined by:\n * - User's ELO for the tag exceeds threshold (or avgElo if not specified)\n * - User has minimum interaction count with the tag\n *\n * Tags with no prerequisites are always unlocked.\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class HierarchyDefinitionNavigator extends ContentNavigator implements CardFilter {\n private config: HierarchyConfig;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.config = this.parseConfig(strategyData.serializedData);\n this.name = strategyData.name || 'Hierarchy Definition';\n }\n\n private parseConfig(serializedData: string): HierarchyConfig {\n try {\n const parsed = JSON.parse(serializedData);\n return {\n prerequisites: parsed.prerequisites || {},\n };\n } catch {\n // Return safe defaults if parsing fails\n return {\n prerequisites: {},\n };\n }\n }\n\n /**\n * Check if a specific prerequisite is satisfied\n */\n private isPrerequisiteMet(\n prereq: TagPrerequisite,\n userTagElo: { score: number; count: number } | undefined,\n userGlobalElo: number\n ): boolean {\n if (!userTagElo) return false;\n\n const minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;\n if (userTagElo.count < minCount) return false;\n\n if (prereq.masteryThreshold?.minElo !== undefined) {\n return userTagElo.score >= prereq.masteryThreshold.minElo;\n } else if (prereq.masteryThreshold?.minCount !== undefined) {\n // Explicit minCount without minElo: count alone is sufficient.\n // The config author specified a concrete interaction threshold —\n // don't additionally require above-average ELO.\n return true;\n } else {\n // No thresholds specified at all: fall back to above-average ELO\n return userTagElo.score >= userGlobalElo;\n }\n }\n\n /**\n * Get the set of tags the user has mastered.\n * A tag is \"mastered\" if it appears as a prerequisite somewhere and meets its threshold.\n */\n private async getMasteredTags(context: FilterContext): Promise<Set<string>> {\n const mastered = new Set<string>();\n\n try {\n const courseReg = await context.user.getCourseRegDoc(context.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n\n // Collect all unique prerequisite tags and check mastery for each\n for (const prereqs of Object.values(this.config.prerequisites)) {\n for (const prereq of prereqs) {\n const tagElo = userElo.tags[prereq.tag];\n if (this.isPrerequisiteMet(prereq, tagElo, userElo.global.score)) {\n mastered.add(prereq.tag);\n }\n }\n }\n } catch {\n // If we can't get user data, return empty set (no tags mastered)\n }\n\n return mastered;\n }\n\n /**\n * Get the set of tags that are unlocked (prerequisites met)\n */\n private getUnlockedTags(masteredTags: Set<string>): Set<string> {\n const unlocked = new Set<string>();\n\n for (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {\n const allPrereqsMet = prereqs.every((prereq) => masteredTags.has(prereq.tag));\n if (allPrereqsMet) {\n unlocked.add(tagId);\n }\n }\n\n return unlocked;\n }\n\n /**\n * Check if a tag has prerequisites defined in config\n */\n private hasPrerequisites(tagId: string): boolean {\n return tagId in this.config.prerequisites;\n }\n\n /**\n * Check if a card is unlocked and generate reason.\n */\n private async checkCardUnlock(\n card: WeightedCard,\n _course: CourseDBInterface,\n unlockedTags: Set<string>,\n masteredTags: Set<string>\n ): Promise<{ isUnlocked: boolean; reason: string }> {\n try {\n // Pipeline hydrates tags before filters run\n const cardTags = card.tags ?? [];\n\n // Check each tag's prerequisite status\n const lockedTags = cardTags.filter(\n (tag) => this.hasPrerequisites(tag) && !unlockedTags.has(tag)\n );\n\n if (lockedTags.length === 0) {\n const tagList = cardTags.length > 0 ? cardTags.join(', ') : 'none';\n return {\n isUnlocked: true,\n reason: `Prerequisites met, tags: ${tagList}`,\n };\n }\n\n // Find missing prerequisites for locked tags\n const missingPrereqs = lockedTags.flatMap((tag) => {\n const prereqs = this.config.prerequisites[tag] || [];\n return prereqs.filter((p) => !masteredTags.has(p.tag)).map((p) => p.tag);\n });\n\n return {\n isUnlocked: false,\n reason: `Blocked: missing prerequisites ${missingPrereqs.join(', ')} for tags ${lockedTags.join(', ')}`,\n };\n } catch {\n // If we can't get tags, assume unlocked (fail open)\n return {\n isUnlocked: true,\n reason: 'Prerequisites check skipped (tag lookup failed)',\n };\n }\n }\n\n /**\n * Build a map of prereq tag → max configured boost for all *closed* gates.\n *\n * When a gate is closed (prereqs unmet), cards carrying that gate's prereq\n * tags get boosted — steering the pipeline toward content that helps unlock\n * the gated material. Once the gate opens, the boost disappears.\n */\n private getPreReqBoosts(\n unlockedTags: Set<string>,\n masteredTags: Set<string>\n ): Map<string, number> {\n const boosts = new Map<string, number>();\n\n for (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {\n // Only boost prereqs of closed gates\n if (unlockedTags.has(tagId)) continue;\n\n for (const prereq of prereqs) {\n if (!prereq.preReqBoost || prereq.preReqBoost <= 1.0) continue;\n // Only boost prereqs that aren't already met\n if (masteredTags.has(prereq.tag)) continue;\n\n const existing = boosts.get(prereq.tag) ?? 1.0;\n boosts.set(prereq.tag, Math.max(existing, prereq.preReqBoost));\n }\n }\n\n return boosts;\n }\n\n /**\n * Build a map of gated tag → max configured targetBoost for all *open* gates.\n *\n * When a gate opens (prereqs met), cards carrying the gated tag get boosted —\n * ensuring newly-unlocked content surfaces promptly. The boost is a static\n * multiplier; natural ELO/SRS deprioritization after interaction handles decay.\n */\n private getTargetBoosts(unlockedTags: Set<string>): Map<string, number> {\n const boosts = new Map<string, number>();\n\n const configKeys = Object.keys(this.config.prerequisites);\n const unlockedArr = [...unlockedTags];\n logger.info(\n `[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${configKeys.length}, unlocked=${unlockedArr.length} (${unlockedArr.slice(0, 5).join(', ')}${unlockedArr.length > 5 ? '...' : ''})`\n );\n\n for (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {\n // Only boost targets of open gates\n if (!unlockedTags.has(tagId)) continue;\n\n // TRACE: dump prereq details for unlocked tags\n logger.info(\n `[HierarchyDefinition:targetBoost:trace] UNLOCKED ${tagId}: ${prereqs.length} prereqs, raw=${JSON.stringify(prereqs.map(p => ({ tag: p.tag, tb: p.targetBoost })))}`\n );\n\n for (const prereq of prereqs) {\n if (!prereq.targetBoost || prereq.targetBoost <= 1.0) continue;\n\n const existing = boosts.get(tagId) ?? 1.0;\n boosts.set(tagId, Math.max(existing, prereq.targetBoost));\n }\n }\n\n if (boosts.size > 0) {\n logger.info(\n `[HierarchyDefinition] targetBoosts active: ${[...boosts.entries()].map(([t, b]) => `${t}=×${b}`).join(', ')}`\n );\n } else {\n logger.info(\n `[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${unlockedArr.length} unlocked tags`\n );\n }\n\n return boosts;\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Three effects:\n * 1. Cards with locked tags receive score * 0.02 (gating penalty)\n * 2. Cards carrying prereq tags of closed gates receive preReqBoost\n * 3. Cards carrying gated tags of open gates receive targetBoost\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n // Get mastery state\n const masteredTags = await this.getMasteredTags(context);\n const unlockedTags = this.getUnlockedTags(masteredTags);\n const preReqBoosts = this.getPreReqBoosts(unlockedTags, masteredTags);\n const targetBoosts = this.getTargetBoosts(unlockedTags);\n\n // Apply prerequisite gating + prereq/target boosting\n const gated: WeightedCard[] = [];\n\n for (const card of cards) {\n const { isUnlocked, reason } = await this.checkCardUnlock(\n card,\n context.course,\n unlockedTags,\n masteredTags\n );\n const LOCKED_PENALTY = 0.02;\n let finalScore = isUnlocked ? card.score : card.score * LOCKED_PENALTY;\n let action: 'passed' | 'penalized' | 'boosted' = isUnlocked ? 'passed' : 'penalized';\n let finalReason = reason;\n\n // Apply prereq boost to cards that passed gating (don't boost locked cards)\n if (isUnlocked && preReqBoosts.size > 0) {\n const cardTags = card.tags ?? [];\n let maxBoost = 1.0;\n const boostedPrereqs: string[] = [];\n\n for (const tag of cardTags) {\n const boost = preReqBoosts.get(tag);\n if (boost && boost > maxBoost) {\n maxBoost = boost;\n boostedPrereqs.push(tag);\n }\n }\n\n if (maxBoost > 1.0) {\n finalScore *= maxBoost;\n action = 'boosted';\n finalReason = `${reason} | preReqBoost ×${maxBoost.toFixed(2)} for ${boostedPrereqs.join(', ')}`;\n logger.info(\n `[HierarchyDefinition] preReqBoost ×${maxBoost.toFixed(2)} applied to card ${card.cardId} via tags [${boostedPrereqs.join(', ')}] (score: ${card.score.toFixed(3)} → ${finalScore.toFixed(3)})`\n );\n }\n }\n\n // Apply target boost to unlocked cards whose gated tag just opened\n if (isUnlocked && targetBoosts.size > 0) {\n const cardTags = card.tags ?? [];\n let maxTargetBoost = 1.0;\n const boostedTargets: string[] = [];\n\n for (const tag of cardTags) {\n const boost = targetBoosts.get(tag);\n if (boost && boost > maxTargetBoost) {\n maxTargetBoost = boost;\n boostedTargets.push(tag);\n }\n }\n\n if (maxTargetBoost > 1.0) {\n finalScore *= maxTargetBoost;\n action = 'boosted';\n finalReason = `${finalReason} | targetBoost ×${maxTargetBoost.toFixed(2)} for ${boostedTargets.join(', ')}`;\n logger.info(\n `[HierarchyDefinition] targetBoost ×${maxTargetBoost.toFixed(2)} applied to card ${card.cardId} via tags [${boostedTargets.join(', ')}] (score: ${card.score.toFixed(3)} → ${finalScore.toFixed(3)})`\n );\n }\n }\n\n gated.push({\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'hierarchyDefinition',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hierarchy',\n action,\n score: finalScore,\n reason: finalReason,\n },\n ],\n });\n }\n\n return gated;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<GeneratorResult> {\n throw new Error(\n 'HierarchyDefinitionNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardFilter, FilterContext } from './types';\nimport type { GeneratorResult } from '../generators/types';\n\n// ============================================================================\n// USER TAG PREFERENCE FILTER\n// ============================================================================\n//\n// Allows users to personalize their learning experience by specifying:\n// - Tags to boost/penalize (score multiplied by boost factor)\n//\n// User preferences are stored in STRATEGY_STATE documents in the user's\n// database, enabling persistence across sessions and sync across devices.\n//\n// Use cases:\n// - Goal-based learning: \"I want to learn piano by ear, skip sight-reading\"\n// - Selective focus: \"I only want to practice chess endgames\"\n// - Accessibility: \"Skip text-heavy cards, prefer visual content\"\n// - Difficulty customization: \"Skip beginner content I already know\"\n//\n// ============================================================================\n\n/**\n * User's tag preference state, stored in STRATEGY_STATE document.\n *\n * This interface defines what gets persisted to the user's database.\n * UI components write to this structure, and the filter reads from it.\n *\n * ## Preferences vs Goals\n *\n * Preferences are **path constraints** — they affect HOW the user learns,\n * not WHAT they're trying to learn. Examples:\n * - \"Skip text-heavy cards\" (accessibility)\n * - \"Prefer visual content\"\n *\n * For **goal-based** filtering (defining WHAT to learn), see the separate\n * UserGoalNavigator (stub). Goals affect progress tracking and completion\n * criteria; preferences only affect card selection.\n *\n * ## Slider Semantics\n *\n * Each tag maps to a multiplier value in the `boost` record:\n * - `0` = banish/exclude (card score = 0)\n * - `0.5` = penalize by 50%\n * - `1.0` = neutral/no effect (default when tag added)\n * - `2.0` = 2x preference boost\n * - Higher values = stronger preference\n *\n * If multiple tags on a card have preferences, the maximum multiplier wins.\n */\nexport interface UserTagPreferenceState {\n /**\n * Tag-specific multipliers.\n * Maps tag name to score multiplier (0 = exclude, 1 = neutral, >1 = boost).\n */\n boost: Record<string, number>;\n\n /**\n * ISO timestamp of last update.\n * Use `moment.utc(updatedAt)` to parse into a Moment object.\n */\n updatedAt: string;\n}\n\n/**\n * A filter that applies user-configured tag preferences.\n *\n * Reads preferences from STRATEGY_STATE document in user's database.\n * If no preferences exist, passes through unchanged (no-op).\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for compatibility with dynamic loading.\n */\nexport default class UserTagPreferenceFilter extends ContentNavigator implements CardFilter {\n private _strategyData: ContentNavigationStrategyData;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this._strategyData = strategyData;\n this.name = strategyData.name || 'User Tag Preferences';\n }\n\n /**\n * Compute multiplier for a card based on its tags and user preferences.\n * Returns the maximum multiplier among all matching tags, or 1.0 if no matches.\n */\n private computeMultiplier(cardTags: string[], boostMap: Record<string, number>): number {\n const multipliers = cardTags.map((tag) => boostMap[tag]).filter((val) => val !== undefined);\n\n if (multipliers.length === 0) {\n return 1.0;\n }\n\n // Use max multiplier among matching tags\n return Math.max(...multipliers);\n }\n\n /**\n * Build human-readable reason for the filter's decision.\n */\n private buildReason(\n cardTags: string[],\n boostMap: Record<string, number>,\n multiplier: number\n ): string {\n // Find which tag(s) contributed to the multiplier\n const matchingTags = cardTags.filter((tag) => boostMap[tag] === multiplier);\n\n if (multiplier === 0) {\n return `Excluded by user preference: ${matchingTags.join(', ')} (${multiplier}x)`;\n }\n\n if (multiplier < 1.0) {\n return `Penalized by user preference: ${matchingTags.join(', ')} (${multiplier.toFixed(2)}x)`;\n }\n\n if (multiplier > 1.0) {\n return `Boosted by user preference: ${matchingTags.join(', ')} (${multiplier.toFixed(2)}x)`;\n }\n\n return 'No matching user preferences';\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply user tag preferences:\n * 1. Read preferences from strategy state\n * 2. If no preferences, pass through unchanged\n * 3. For each card:\n * - Look up tag in boost record\n * - If tag found: apply multiplier (0 = exclude, 1 = neutral, >1 = boost)\n * - If multiple tags match: use max multiplier\n * - Append provenance with clear reason\n */\n async transform(cards: WeightedCard[], _context: FilterContext): Promise<WeightedCard[]> {\n // Read user preferences from strategy state\n const prefs = await this.getStrategyState<UserTagPreferenceState>();\n\n // No preferences configured → pass through unchanged\n if (!prefs || Object.keys(prefs.boost).length === 0) {\n return cards.map((card) => ({\n ...card,\n provenance: [\n ...card.provenance,\n {\n strategy: 'userTagPreference',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || this._strategyData._id,\n action: 'passed' as const,\n score: card.score,\n reason: 'No user tag preferences configured',\n },\n ],\n }));\n }\n\n // Process each card\n const adjusted: WeightedCard[] = await Promise.all(\n cards.map(async (card) => {\n const cardTags = card.tags ?? [];\n\n // Compute multiplier based on card tags and user preferences\n const multiplier = this.computeMultiplier(cardTags, prefs.boost);\n const finalScore = Math.min(1, card.score * multiplier);\n\n // Determine action for provenance\n let action: 'passed' | 'boosted' | 'penalized';\n if (multiplier === 0 || multiplier < 1.0) {\n action = 'penalized';\n } else if (multiplier > 1.0) {\n action = 'boosted';\n } else {\n action = 'passed';\n }\n\n return {\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'userTagPreference',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || this._strategyData._id,\n action,\n score: finalScore,\n reason: this.buildReason(cardTags, prefs.boost, multiplier),\n },\n ],\n };\n })\n );\n\n return adjusted;\n }\n\n /**\n * Legacy getWeightedCards - throws as filters should not be used as generators.\n */\n async getWeightedCards(_limit: number): Promise<GeneratorResult> {\n throw new Error(\n 'UserTagPreferenceFilter is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n}\n","// Filter types and interfaces\nexport type { CardFilter, FilterContext, CardFilterFactory } from './types';\n\n// Filter implementations\nexport { createEloDistanceFilter } from './eloDistance';\nexport type { EloDistanceConfig } from './eloDistance';\n\nexport { default as UserTagPreferenceFilter } from './userTagPreference';\nexport type { UserTagPreferenceState } from './userTagPreference';\n","// ============================================================================\n// INFERRED PREFERENCE NAVIGATOR — STUB\n// ============================================================================\n//\n// STATUS: NOT IMPLEMENTED — This file documents architectural intent only.\n//\n// ============================================================================\n//\n// ## Purpose\n//\n// Inferred preferences are learned from user behavior, as opposed to explicit\n// preferences which are configured via UI. The system observes patterns in\n// user interactions and adjusts card selection accordingly.\n//\n// ## Inference Signals\n//\n// Potential signals to learn from:\n//\n// 1. **Card dismissal patterns**: User consistently skips certain card types\n// 2. **Time-on-card**: User spends less time on certain content (boredom?)\n// 3. **Error patterns**: User struggles with certain presentation styles\n// 4. **Session timing**: User performs better at certain times of day\n// 5. **Tag success rates**: User masters some tags faster than others\n//\n// ## Inferred State (Proposed)\n//\n// ```typescript\n// interface InferredPreferenceState {\n// // Learned tag affinities (positive = user does well, negative = struggles)\n// tagAffinities: Record<string, number>;\n//\n// // Presentation style preferences\n// preferredStyles: {\n// visualVsText: number; // -1 to 1 (negative = text, positive = visual)\n// shortVsLong: number; // -1 to 1 (negative = long, positive = short)\n// };\n//\n// // Temporal patterns\n// optimalSessionLength: number; // minutes\n// optimalTimeOfDay: number; // hour (0-23)\n//\n// // Confidence in inferences\n// sampleSize: number;\n// lastUpdated: string;\n// }\n// ```\n//\n// ## Relationship to Explicit Preferences\n//\n// - Explicit preferences (UserTagPreferenceFilter) always take precedence\n// - Inferred preferences act as soft suggestions when no explicit pref exists\n// - User can \"lock in\" an inference as an explicit preference via UI\n// - User can dismiss/override an inference (\"I actually like text cards\")\n//\n// ## Transparency Requirements\n//\n// Inferred preferences must be:\n//\n// 1. **Visible**: User can see what the system has inferred\n// 2. **Explainable**: \"We noticed you master visual cards faster\"\n// 3. **Overridable**: User can disable or invert any inference\n// 4. **Forgettable**: User can reset inferences and start fresh\n//\n// ## Implementation Considerations\n//\n// 1. **Cold start**: Need minimum sample size before inferring\n// 2. **Drift**: Preferences may change over time; use decay/recency weighting\n// 3. **Privacy**: Inference data is personal; handle with care\n// 4. **Bias**: Avoid reinforcing accidental patterns as permanent preferences\n//\n// ## Related Files\n//\n// - `filters/userTagPreference.ts` — Explicit preferences (takes precedence)\n// - `userGoal.ts` — Goals (destination, not path)\n// - `../types/strategyState.ts` — Storage mechanism\n//\n// ## Next Steps\n//\n// 1. Define minimum viable inference signals\n// 2. Design inference algorithms (simple heuristics vs ML)\n// 3. Build transparency UI (\"Here's what we learned about you\")\n// 4. Implement override/dismiss mechanism\n// 5. Add to card record collection for inference input\n//\n// ============================================================================\n\n// Placeholder export to make this a valid module\nexport const INFERRED_PREFERENCE_NAVIGATOR_STUB = true;\n\n/**\n * @stub InferredPreferenceNavigator\n *\n * A navigator that learns user preferences from behavior patterns.\n * See module-level documentation for architectural intent.\n *\n * NOT IMPLEMENTED — This is a design placeholder.\n */\nexport interface InferredPreferenceState {\n /** Learned affinity scores per tag (-1 to 1) */\n tagAffinities: Record<string, number>;\n\n /** Number of card interactions used to build inferences */\n sampleSize: number;\n\n /** ISO timestamp of last inference update */\n updatedAt: string;\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardFilter, FilterContext } from './types';\nimport type { GeneratorResult } from '../generators/types';\nimport { toCourseElo } from '@vue-skuilder/common';\n\n/**\n * Configuration for the InterferenceMitigator strategy.\n *\n * Course authors define explicit interference relationships between tags.\n * The mitigator discourages introducing new concepts that interfere with\n * currently immature learnings.\n */\n/**\n * A single interference group with its own decay coefficient.\n */\nexport interface InterferenceGroup {\n /** Tags that interfere with each other in this group */\n tags: string[];\n /** How strongly these tags interfere (0-1, default: 0.8). Higher = stronger avoidance. */\n decay?: number;\n}\n\nexport interface InterferenceConfig {\n /**\n * Groups of tags that interfere with each other.\n * Each group can have its own decay coefficient.\n *\n * Example: [\n * { tags: [\"b\", \"d\", \"p\"], decay: 0.9 }, // visual similarity - strong\n * { tags: [\"d\", \"t\"], decay: 0.7 }, // phonetic similarity - moderate\n * { tags: [\"m\", \"n\"], decay: 0.8 }\n * ]\n */\n interferenceSets: InterferenceGroup[];\n\n /**\n * Threshold below which a tag is considered \"immature\" (still being learned).\n * Immature tags trigger interference avoidance for their interference partners.\n */\n maturityThreshold?: {\n /** Minimum interaction count to be considered mature (default: 10) */\n minCount?: number;\n /** Minimum ELO score to be considered mature (optional) */\n minElo?: number;\n /**\n * Minimum elapsed time (in days) since first interaction to be considered mature.\n * Prevents recent cramming success from indicating maturity.\n * The skill should be \"lindy\" — maintained over time.\n */\n minElapsedDays?: number;\n };\n\n /**\n * Default decay for groups that don't specify their own (0-1, default: 0.8).\n */\n defaultDecay?: number;\n}\n\nconst DEFAULT_MIN_COUNT = 10;\nconst DEFAULT_MIN_ELAPSED_DAYS = 3;\nconst DEFAULT_INTERFERENCE_DECAY = 0.8;\n\n/**\n * A filter strategy that avoids introducing confusable concepts simultaneously.\n *\n * When a user is learning a concept (tag is \"immature\"), this strategy reduces\n * scores for cards tagged with interfering concepts. This encourages the system\n * to introduce new content that is maximally distant from current learning focus.\n *\n * Example: While learning 'd', prefer introducing 'x' over 'b' (visual interference)\n * or 't' (phonetic interference).\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class InterferenceMitigatorNavigator extends ContentNavigator implements CardFilter {\n private config: InterferenceConfig;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n /** Precomputed map: tag -> set of { partner, decay } it interferes with */\n private interferenceMap: Map<string, Array<{ partner: string; decay: number }>>;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.config = this.parseConfig(strategyData.serializedData);\n this.interferenceMap = this.buildInterferenceMap();\n this.name = strategyData.name || 'Interference Mitigator';\n }\n\n private parseConfig(serializedData: string): InterferenceConfig {\n try {\n const parsed = JSON.parse(serializedData);\n // Normalize legacy format (string[][]) to new format (InterferenceGroup[])\n let sets: InterferenceGroup[] = parsed.interferenceSets || [];\n if (sets.length > 0 && Array.isArray(sets[0])) {\n // Legacy format: convert string[][] to InterferenceGroup[]\n sets = (sets as unknown as string[][]).map((tags) => ({ tags }));\n }\n\n return {\n interferenceSets: sets,\n maturityThreshold: {\n minCount: parsed.maturityThreshold?.minCount ?? DEFAULT_MIN_COUNT,\n minElo: parsed.maturityThreshold?.minElo,\n minElapsedDays: parsed.maturityThreshold?.minElapsedDays ?? DEFAULT_MIN_ELAPSED_DAYS,\n },\n defaultDecay: parsed.defaultDecay ?? DEFAULT_INTERFERENCE_DECAY,\n };\n } catch {\n return {\n interferenceSets: [],\n maturityThreshold: {\n minCount: DEFAULT_MIN_COUNT,\n minElapsedDays: DEFAULT_MIN_ELAPSED_DAYS,\n },\n defaultDecay: DEFAULT_INTERFERENCE_DECAY,\n };\n }\n }\n\n /**\n * Build a map from each tag to its interference partners with decay coefficients.\n * If tags A, B, C are in an interference group with decay 0.8, then:\n * - A interferes with B (decay 0.8) and C (decay 0.8)\n * - B interferes with A (decay 0.8) and C (decay 0.8)\n * - etc.\n */\n private buildInterferenceMap(): Map<string, Array<{ partner: string; decay: number }>> {\n const map = new Map<string, Array<{ partner: string; decay: number }>>();\n\n for (const group of this.config.interferenceSets) {\n const decay = group.decay ?? this.config.defaultDecay ?? DEFAULT_INTERFERENCE_DECAY;\n\n for (const tag of group.tags) {\n if (!map.has(tag)) {\n map.set(tag, []);\n }\n const partners = map.get(tag)!;\n for (const other of group.tags) {\n if (other !== tag) {\n // Check if partner already exists (from overlapping groups)\n const existing = partners.find((p) => p.partner === other);\n if (existing) {\n // Use the stronger (higher) decay\n existing.decay = Math.max(existing.decay, decay);\n } else {\n partners.push({ partner: other, decay });\n }\n }\n }\n }\n }\n\n return map;\n }\n\n /**\n * Get the set of tags that are currently immature for this user.\n * A tag is immature if the user has interacted with it but hasn't\n * reached the maturity threshold.\n */\n private async getImmatureTags(context: FilterContext): Promise<Set<string>> {\n const immature = new Set<string>();\n\n try {\n const courseReg = await context.user.getCourseRegDoc(context.course.getCourseID());\n const userElo = toCourseElo(courseReg.elo);\n\n const minCount = this.config.maturityThreshold?.minCount ?? DEFAULT_MIN_COUNT;\n const minElo = this.config.maturityThreshold?.minElo;\n\n // TODO: To properly check elapsed time, we need access to first interaction timestamp.\n // For now, we use count as a proxy (more interactions = more time elapsed).\n // Future: query card history for earliest timestamp per tag.\n const minElapsedDays =\n this.config.maturityThreshold?.minElapsedDays ?? DEFAULT_MIN_ELAPSED_DAYS;\n const minCountForElapsed = minElapsedDays * 2; // Rough proxy: ~2 interactions per day\n\n for (const [tagId, tagElo] of Object.entries(userElo.tags)) {\n // Only consider tags that have been started (count > 0)\n if (tagElo.count === 0) continue;\n\n // Check if below maturity threshold\n const belowCount = tagElo.count < minCount;\n const belowElo = minElo !== undefined && tagElo.score < minElo;\n const belowElapsed = tagElo.count < minCountForElapsed; // Proxy for time\n\n if (belowCount || belowElo || belowElapsed) {\n immature.add(tagId);\n }\n }\n } catch {\n // If we can't get user data, assume no immature tags\n }\n\n return immature;\n }\n\n /**\n * Get all tags that interfere with any immature tag, along with their decay coefficients.\n * These are the tags we want to avoid introducing.\n */\n private getTagsToAvoid(immatureTags: Set<string>): Map<string, number> {\n const avoid = new Map<string, number>();\n\n for (const immatureTag of immatureTags) {\n const partners = this.interferenceMap.get(immatureTag);\n if (partners) {\n for (const { partner, decay } of partners) {\n // Avoid the partner, but not if it's also immature\n // (if both are immature, we're already learning both)\n if (!immatureTags.has(partner)) {\n // Use the strongest (highest) decay if partner appears multiple times\n const existing = avoid.get(partner) ?? 0;\n avoid.set(partner, Math.max(existing, decay));\n }\n }\n }\n }\n\n return avoid;\n }\n\n /**\n * Compute interference score reduction for a card.\n * Returns: { multiplier, interfering tags, reason }\n */\n private computeInterferenceEffect(\n cardTags: string[],\n tagsToAvoid: Map<string, number>,\n immatureTags: Set<string>\n ): { multiplier: number; interferingTags: string[]; reason: string } {\n if (tagsToAvoid.size === 0) {\n return {\n multiplier: 1.0,\n interferingTags: [],\n reason: 'No interference detected',\n };\n }\n\n let multiplier = 1.0;\n const interferingTags: string[] = [];\n\n for (const tag of cardTags) {\n const decay = tagsToAvoid.get(tag);\n if (decay !== undefined) {\n interferingTags.push(tag);\n multiplier *= 1.0 - decay;\n }\n }\n\n if (interferingTags.length === 0) {\n return {\n multiplier: 1.0,\n interferingTags: [],\n reason: 'No interference detected',\n };\n }\n\n // Find which immature tags these interfere with\n const causingTags = new Set<string>();\n for (const tag of interferingTags) {\n for (const immatureTag of immatureTags) {\n const partners = this.interferenceMap.get(immatureTag);\n if (partners?.some((p) => p.partner === tag)) {\n causingTags.add(immatureTag);\n }\n }\n }\n\n const reason = `Interferes with immature tags ${Array.from(causingTags).join(', ')} (tags: ${interferingTags.join(', ')}, multiplier: ${multiplier.toFixed(2)})`;\n\n return { multiplier, interferingTags, reason };\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply interference-aware scoring. Cards with tags that interfere with\n * immature learnings get reduced scores.\n */\n async transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]> {\n // Identify what to avoid\n const immatureTags = await this.getImmatureTags(context);\n const tagsToAvoid = this.getTagsToAvoid(immatureTags);\n\n // Adjust scores based on interference\n const adjusted: WeightedCard[] = [];\n\n for (const card of cards) {\n const cardTags = card.tags ?? [];\n const { multiplier, reason } = this.computeInterferenceEffect(\n cardTags,\n tagsToAvoid,\n immatureTags\n );\n const finalScore = card.score * multiplier;\n\n const action = multiplier < 1.0 ? 'penalized' : multiplier > 1.0 ? 'boosted' : 'passed';\n\n adjusted.push({\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'interferenceMitigator',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-interference',\n action,\n score: finalScore,\n reason,\n },\n ],\n });\n }\n\n return adjusted;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<GeneratorResult> {\n throw new Error(\n 'InterferenceMitigatorNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n}\n","import type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport { ContentNavigator } from '../index';\nimport type { WeightedCard } from '../index';\nimport type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';\nimport type { CardFilter, FilterContext } from './types';\nimport type { GeneratorResult } from '../generators/types';\n\n/**\n * Configuration for the RelativePriority strategy.\n *\n * Course authors define priority weights for tags, allowing the system\n * to prefer high-utility content (common, well-behaved patterns) over\n * lower-utility content (rare, irregular patterns).\n *\n * Example use case: In phonics, prefer teaching 's' (common, consistent)\n * before 'x' or 'z' (rare, sometimes irregular).\n */\nexport interface RelativePriorityConfig {\n /**\n * Map of tag ID to priority weight (0-1).\n *\n * 1.0 = highest priority (present first)\n * 0.5 = neutral\n * 0.0 = lowest priority (defer until later)\n *\n * Example:\n * {\n * \"letter-s\": 0.95,\n * \"letter-t\": 0.90,\n * \"letter-x\": 0.10,\n * \"letter-z\": 0.05\n * }\n */\n tagPriorities: { [tagId: string]: number };\n\n /**\n * Priority for tags not explicitly listed (default: 0.5).\n * 0.5 means unlisted tags have neutral effect on scoring.\n */\n defaultPriority?: number;\n\n /**\n * How to combine priorities when a card has multiple tags.\n *\n * - 'max': Use the highest priority among the card's tags (default)\n * - 'average': Average all tag priorities\n * - 'min': Use the lowest priority (conservative)\n */\n combineMode?: 'max' | 'average' | 'min';\n\n /**\n * How strongly priority influences the final score (0-1, default: 0.5).\n *\n * At 0.0: Priority has no effect (pure delegate scoring)\n * At 0.5: Priority can boost/reduce scores by up to 25%\n * At 1.0: Priority can boost/reduce scores by up to 50%\n *\n * The boost factor formula: 1 + (priority - 0.5) * priorityInfluence\n * - Priority 1.0 with influence 0.5 → boost of 1.25\n * - Priority 0.5 with influence 0.5 → boost of 1.00 (neutral)\n * - Priority 0.0 with influence 0.5 → boost of 0.75\n */\n priorityInfluence?: number;\n}\n\nconst DEFAULT_PRIORITY = 0.5;\nconst DEFAULT_PRIORITY_INFLUENCE = 0.5;\nconst DEFAULT_COMBINE_MODE: 'max' | 'average' | 'min' = 'max';\n\n/**\n * A filter strategy that boosts scores for high-utility content.\n *\n * Course authors assign priority weights to tags. Cards with high-priority\n * tags get boosted scores, making them more likely to be presented first.\n * This allows teaching the most useful, well-behaved concepts before\n * moving on to rarer or more irregular ones.\n *\n * Example: When teaching phonics, prioritize common letters (s, t, a) over\n * rare ones (x, z, q) by assigning higher priority weights to common letters.\n *\n * Implements CardFilter for use in Pipeline architecture.\n * Also extends ContentNavigator for backward compatibility.\n */\nexport default class RelativePriorityNavigator extends ContentNavigator implements CardFilter {\n private config: RelativePriorityConfig;\n\n /** Human-readable name for CardFilter interface */\n name: string;\n\n constructor(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ) {\n super(user, course, strategyData);\n this.config = this.parseConfig(strategyData.serializedData);\n this.name = strategyData.name || 'Relative Priority';\n }\n\n private parseConfig(serializedData: string): RelativePriorityConfig {\n try {\n const parsed = JSON.parse(serializedData);\n return {\n tagPriorities: parsed.tagPriorities || {},\n defaultPriority: parsed.defaultPriority ?? DEFAULT_PRIORITY,\n combineMode: parsed.combineMode ?? DEFAULT_COMBINE_MODE,\n priorityInfluence: parsed.priorityInfluence ?? DEFAULT_PRIORITY_INFLUENCE,\n };\n } catch {\n // Return safe defaults if parsing fails\n return {\n tagPriorities: {},\n defaultPriority: DEFAULT_PRIORITY,\n combineMode: DEFAULT_COMBINE_MODE,\n priorityInfluence: DEFAULT_PRIORITY_INFLUENCE,\n };\n }\n }\n\n /**\n * Look up the priority for a tag.\n */\n private getTagPriority(tagId: string): number {\n return this.config.tagPriorities[tagId] ?? this.config.defaultPriority ?? DEFAULT_PRIORITY;\n }\n\n /**\n * Compute combined priority for a card based on its tags.\n */\n private computeCardPriority(cardTags: string[]): number {\n if (cardTags.length === 0) {\n return this.config.defaultPriority ?? DEFAULT_PRIORITY;\n }\n\n const priorities = cardTags.map((tag) => this.getTagPriority(tag));\n\n switch (this.config.combineMode) {\n case 'max':\n return Math.max(...priorities);\n case 'min':\n return Math.min(...priorities);\n case 'average':\n return priorities.reduce((sum, p) => sum + p, 0) / priorities.length;\n default:\n return Math.max(...priorities);\n }\n }\n\n /**\n * Compute boost factor based on priority.\n *\n * The formula: 1 + (priority - 0.5) * priorityInfluence\n *\n * This creates a multiplier centered around 1.0:\n * - Priority 1.0 with influence 0.5 → 1.25 (25% boost)\n * - Priority 0.5 with any influence → 1.00 (neutral)\n * - Priority 0.0 with influence 0.5 → 0.75 (25% reduction)\n */\n private computeBoostFactor(priority: number): number {\n const influence = this.config.priorityInfluence ?? DEFAULT_PRIORITY_INFLUENCE;\n return 1 + (priority - 0.5) * influence;\n }\n\n /**\n * Build human-readable reason for priority adjustment.\n */\n private buildPriorityReason(\n cardTags: string[],\n priority: number,\n boostFactor: number,\n finalScore: number\n ): string {\n if (cardTags.length === 0) {\n return `No tags, neutral priority (${priority.toFixed(2)})`;\n }\n\n const tagList = cardTags.slice(0, 3).join(', ');\n const more = cardTags.length > 3 ? ` (+${cardTags.length - 3} more)` : '';\n\n if (boostFactor === 1.0) {\n return `Neutral priority (${priority.toFixed(2)}) for tags: ${tagList}${more}`;\n } else if (boostFactor > 1.0) {\n return `High-priority tags: ${tagList}${more} (priority ${priority.toFixed(2)} → boost ${boostFactor.toFixed(2)}x → ${finalScore.toFixed(2)})`;\n } else {\n return `Low-priority tags: ${tagList}${more} (priority ${priority.toFixed(2)} → reduce ${boostFactor.toFixed(2)}x → ${finalScore.toFixed(2)})`;\n }\n }\n\n /**\n * CardFilter.transform implementation.\n *\n * Apply priority-adjusted scoring. Cards with high-priority tags get boosted,\n * cards with low-priority tags get reduced scores.\n */\n async transform(cards: WeightedCard[], _context: FilterContext): Promise<WeightedCard[]> {\n const adjusted: WeightedCard[] = await Promise.all(\n cards.map(async (card) => {\n const cardTags = card.tags ?? [];\n const priority = this.computeCardPriority(cardTags);\n const boostFactor = this.computeBoostFactor(priority);\n // No upper clamp — scores may exceed 1.0 intentionally.\n // Scores are only used for relative ordering within a pipeline run,\n // so absolute magnitude doesn't matter. Clamping to 1.0 here collapsed\n // differentiation when GPC preReqBoosts and priority boosts compounded\n // (e.g. 0.96 × 2.5 × 1.24 → 2.98, previously crushed back to 1.0).\n // Floor of 0 is kept: negative scores have no meaning.\n const finalScore = Math.max(0, card.score * boostFactor);\n\n // Determine action based on boost factor\n const action = boostFactor > 1.0 ? 'boosted' : boostFactor < 1.0 ? 'penalized' : 'passed';\n\n // Build reason explaining priority adjustment\n const reason = this.buildPriorityReason(cardTags, priority, boostFactor, finalScore);\n\n return {\n ...card,\n score: finalScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'relativePriority',\n strategyName: this.strategyName || this.name,\n strategyId: this.strategyId || 'NAVIGATION_STRATEGY-priority',\n action,\n score: finalScore,\n reason,\n },\n ],\n };\n })\n );\n\n return adjusted;\n }\n\n /**\n * Legacy getWeightedCards - now throws as filters should not be used as generators.\n *\n * Use transform() via Pipeline instead.\n */\n async getWeightedCards(_limit: number): Promise<GeneratorResult> {\n throw new Error(\n 'RelativePriorityNavigator is a filter and should not be used as a generator. ' +\n 'Use Pipeline with a generator and this filter via transform().'\n );\n }\n}\n","import type { WeightedCard } from '../index';\nimport type { CourseDBInterface } from '../../interfaces/courseDB';\nimport type { UserDBInterface } from '../../interfaces/userDB';\nimport type { OrchestrationContext } from '../../orchestration';\n\n// ============================================================================\n// CARD FILTER INTERFACE\n// ============================================================================\n//\n// Filters are pure transforms on a list of WeightedCards.\n// They replace the delegate-wrapping pattern with a simpler model:\n//\n// cards = Generator.getWeightedCards()\n// cards = Filter1.transform(cards, context)\n// cards = Filter2.transform(cards, context)\n//\n// Benefits:\n// - No nested instantiation\n// - Filters don't need to know about delegates\n// - Easy to add/remove/reorder filters\n// - Natural place to hydrate shared data before filter pass\n//\n// All filters should be score multipliers (including score: 0 for exclusion).\n// This means filter order doesn't affect final scores.\n//\n// ============================================================================\n\n/**\n * Shared context available to all filters in a pipeline.\n *\n * Built once per getWeightedCards() call and passed to each filter.\n * This avoids repeated lookups for common data like user ELO.\n */\nexport interface FilterContext {\n /** User database interface */\n user: UserDBInterface;\n\n /** Course database interface */\n course: CourseDBInterface;\n\n /** User's global ELO score for this course */\n userElo: number;\n\n /** Orchestration context for evolutionary weighting */\n orchestration?: OrchestrationContext;\n\n // Future extensions:\n // - hydrated tags for all cards (batch lookup)\n // - user's tag-level ELO data\n // - course config\n}\n\n/**\n * A filter that transforms a list of weighted cards.\n *\n * Filters are pure transforms - they receive cards and context,\n * and return a modified list of cards. No delegate wrapping,\n * no side effects beyond provenance tracking.\n *\n * ## Implementation Guidelines\n *\n * 1. **Append provenance**: Every filter should add a StrategyContribution\n * entry documenting its decision for each card.\n *\n * 2. **Use multipliers**: Adjust scores by multiplying, not replacing.\n * This ensures filter order doesn't matter.\n *\n * 3. **Score 0 for exclusion**: To exclude a card, set score to 0.\n * Don't filter it out - let provenance show why it was excluded.\n *\n * 4. **Don't sort**: The Pipeline handles final sorting.\n * Filters just transform scores.\n *\n * ## Example Implementation\n *\n * ```typescript\n * const myFilter: CardFilter = {\n * name: 'My Filter',\n * async transform(cards, context) {\n * return cards.map(card => {\n * const multiplier = computeMultiplier(card, context);\n * const newScore = card.score * multiplier;\n * return {\n * ...card,\n * score: newScore,\n * provenance: [...card.provenance, {\n * strategy: 'myFilter',\n * strategyName: 'My Filter',\n * strategyId: 'MY_FILTER',\n * action: multiplier < 1 ? 'penalized' : 'passed',\n * score: newScore,\n * reason: 'Explanation of decision'\n * }]\n * };\n * });\n * }\n * };\n * ```\n */\nexport interface CardFilter {\n /** Human-readable name for this filter */\n name: string;\n\n /**\n * Transform a list of weighted cards.\n *\n * @param cards - Cards to transform (already scored by generator)\n * @param context - Shared context (user, course, userElo, etc.)\n * @returns Transformed cards with updated scores and provenance\n */\n transform(cards: WeightedCard[], context: FilterContext): Promise<WeightedCard[]>;\n}\n\n/**\n * Factory function type for creating filters from configuration.\n *\n * Used by PipelineAssembler to instantiate filters from strategy documents.\n */\nexport type CardFilterFactory<TConfig = unknown> = (config: TConfig) => CardFilter;\n","// ============================================================================\n// USER GOAL NAVIGATOR — STUB\n// ============================================================================\n//\n// STATUS: NOT IMPLEMENTED — This file documents architectural intent only.\n//\n// ============================================================================\n//\n// ## Purpose\n//\n// Goals define WHAT the user wants to learn, as opposed to preferences which\n// define HOW they want to learn. Goals affect:\n//\n// 1. **Content scoping**: Which tags/content are relevant to this user\n// 2. **Progress tracking**: ELO is measured against goal-relevant content\n// 3. **Completion criteria**: User is \"done\" when goal mastery is achieved\n// 4. **Curriculum composition**: Goals enable cross-curriculum dependencies\n//\n// ## Goals vs Preferences\n//\n// | Aspect | Goal | Preference |\n// |---------------|-------------------------------|-------------------------------|\n// | Defines | Destination (what to learn) | Path (how to learn) |\n// | Example | \"Master ear-training\" | \"Skip text-heavy cards\" |\n// | Affects ELO | Yes — scopes what's tracked | No — just filters cards |\n// | Completion | Yes — defines \"done\" | No — persists indefinitely |\n// | Filter impl | UserGoalNavigator | UserTagPreferenceFilter |\n//\n// ## Curriculum Composition\n//\n// Goals enable software-style composition for curricula. A physics course\n// can teach classical mechanics without owning the calculus prerequisites.\n//\n// Instead, it declares a dependency:\n//\n// ```typescript\n// interface CurriculumDependency {\n// // NPM-style package resolution\n// curriculumId: string; // e.g., \"@skuilder/calculus\"\n// version: string; // e.g., \"^2.0.0\" (semver)\n//\n// // Goal within that curriculum\n// goal: string; // e.g., \"differential-calculus\"\n//\n// // How this maps to local prerequisites\n// satisfiesLocalTags: string[]; // e.g., [\"calculus-prereq\"]\n// }\n// ```\n//\n// When a physics card requires \"calculus-prereq\", the system:\n// 1. Checks if user has achieved the \"differential-calculus\" goal in @skuilder/calculus\n// 2. If not, defers to that curriculum to teach the prerequisite\n// 3. Returns to physics once the goal is satisfied\n//\n// This allows:\n// - Specialized curricula (calculus experts author calculus content)\n// - Reusable prerequisites across multiple courses\n// - User can bring their own \"calculus credential\" from prior learning\n//\n// ## User Goal State (Proposed)\n//\n// ```typescript\n// interface UserGoalState {\n// // Primary goals — what the user wants to achieve\n// targetTags: string[];\n//\n// // Excluded goals — content the user explicitly doesn't care about\n// excludedTags: string[];\n//\n// // Cross-curriculum goals (for composition)\n// externalGoals?: {\n// curriculumId: string;\n// goal: string;\n// status: 'not-started' | 'in-progress' | 'achieved';\n// }[];\n//\n// // When this goal configuration was set\n// updatedAt: string;\n// }\n// ```\n//\n// ## Implementation Considerations\n//\n// 1. **ELO Scoping**: When goals are set, user ELO tracking should focus on\n// goal-relevant tags. This may require changes to ELO update logic.\n//\n// 2. **Progress Reporting**: UI should show progress toward goals, not just\n// overall course completion.\n//\n// 3. **Goal Achievement**: Need to define when a goal is \"achieved\" —\n// probably ELO threshold + mastery percentage on goal-tagged content.\n//\n// 4. **Curriculum Registry**: For cross-curriculum composition, need a\n// registry/resolver for curriculum packages (similar to npm registry).\n//\n// 5. **Interaction with HierarchyDefinition**: Goals should work with\n// prerequisite chains — user can't skip prerequisites just because\n// they're not part of their goal.\n//\n// ## Related Files\n//\n// - `filters/userTagPreference.ts` — Preferences (path constraints)\n// - `hierarchyDefinition.ts` — Prerequisites (enforced regardless of goals)\n// - `../types/strategyState.ts` — Storage mechanism for user state\n//\n// ## Next Steps\n//\n// 1. Design goal state schema in detail\n// 2. Define goal achievement criteria\n// 3. Implement goal-scoped ELO tracking\n// 4. Build UI for goal configuration\n// 5. Design curriculum dependency resolution\n//\n// ============================================================================\n\n// Placeholder export to make this a valid module\nexport const USER_GOAL_NAVIGATOR_STUB = true;\n\n/**\n * @stub UserGoalNavigator\n *\n * A navigator that scopes learning to user-defined goals.\n * See module-level documentation for architectural intent.\n *\n * NOT IMPLEMENTED — This is a design placeholder.\n */\nexport interface UserGoalState {\n /** Tags the user wants to master (defines \"success\") */\n targetTags: string[];\n\n /** Tags the user explicitly doesn't care about */\n excludedTags: string[];\n\n /** ISO timestamp of last update */\n updatedAt: string;\n}\n","import { UserOutcomeRecord } from '../types/userOutcome';\nimport { GradientObservation, GradientResult } from '../types/learningState';\nimport { logger } from '../../util/logger';\n\n/**\n * Extract (deviation, outcome) observations for a specific strategy\n * from a collection of UserOutcomeRecords.\n *\n * @param outcomes - Collection of outcome records (from multiple users)\n * @param strategyId - The strategy to extract observations for\n * @returns Array of gradient observations\n */\nexport function aggregateOutcomesForGradient(\n outcomes: UserOutcomeRecord[],\n strategyId: string\n): GradientObservation[] {\n const observations: GradientObservation[] = [];\n\n for (const outcome of outcomes) {\n // Skip if this outcome doesn't have a deviation for this strategy\n const deviation = outcome.deviations[strategyId];\n if (deviation === undefined) {\n continue;\n }\n\n observations.push({\n deviation,\n outcomeValue: outcome.outcomeValue,\n weight: 1.0,\n });\n }\n\n logger.debug(\n `[Orchestration] Aggregated ${observations.length} observations for strategy ${strategyId}`\n );\n\n return observations;\n}\n\n/**\n * Compute linear regression on (deviation, outcome) pairs.\n *\n * Uses ordinary least squares to find the best fit line:\n * outcome = gradient * deviation + intercept\n *\n * The gradient tells us:\n * - Positive: users with higher deviation (higher weight) had better outcomes\n * → we should increase the peak weight\n * - Negative: users with higher deviation (higher weight) had worse outcomes\n * → we should decrease the peak weight\n * - Near zero: weight doesn't affect outcomes much\n * → we're near optimal, increase confidence\n *\n * @param observations - Array of (deviation, outcome) pairs\n * @returns Regression result, or null if insufficient data\n */\nexport function computeStrategyGradient(\n observations: GradientObservation[]\n): GradientResult | null {\n const n = observations.length;\n\n if (n < 3) {\n logger.debug(`[Orchestration] Insufficient observations for gradient (${n} < 3)`);\n return null;\n }\n\n // Compute means\n let sumX = 0;\n let sumY = 0;\n let sumW = 0;\n\n for (const obs of observations) {\n const w = obs.weight ?? 1.0;\n sumX += obs.deviation * w;\n sumY += obs.outcomeValue * w;\n sumW += w;\n }\n\n const meanX = sumX / sumW;\n const meanY = sumY / sumW;\n\n // Compute slope (gradient) and intercept using weighted least squares\n let numerator = 0;\n let denominator = 0;\n let ssTotal = 0;\n\n for (const obs of observations) {\n const w = obs.weight ?? 1.0;\n const dx = obs.deviation - meanX;\n const dy = obs.outcomeValue - meanY;\n\n numerator += w * dx * dy;\n denominator += w * dx * dx;\n ssTotal += w * dy * dy;\n }\n\n // Avoid division by zero if all deviations are the same\n if (denominator < 1e-10) {\n logger.debug(`[Orchestration] No variance in deviations, cannot compute gradient`);\n return {\n gradient: 0,\n intercept: meanY,\n rSquared: 0,\n sampleSize: n,\n };\n }\n\n const gradient = numerator / denominator;\n const intercept = meanY - gradient * meanX;\n\n // Compute R-squared\n let ssResidual = 0;\n for (const obs of observations) {\n const w = obs.weight ?? 1.0;\n const predicted = gradient * obs.deviation + intercept;\n const residual = obs.outcomeValue - predicted;\n ssResidual += w * residual * residual;\n }\n\n const rSquared = ssTotal > 1e-10 ? 1 - ssResidual / ssTotal : 0;\n\n logger.debug(\n `[Orchestration] Computed gradient: ${gradient.toFixed(4)}, ` +\n `intercept: ${intercept.toFixed(4)}, R²: ${rSquared.toFixed(4)}, n=${n}`\n );\n\n return {\n gradient,\n intercept,\n rSquared: Math.max(0, Math.min(1, rSquared)), // Clamp to [0,1]\n sampleSize: n,\n };\n}\n","import { LearnableWeight, DEFAULT_LEARNABLE_WEIGHT } from '../types/contentNavigationStrategy';\nimport { StrategyLearningState, GradientResult } from '../types/learningState';\nimport { DocType } from '../types/types-legacy';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\n/** Minimum observations required before adjusting weight */\nconst MIN_OBSERVATIONS_FOR_UPDATE = 10;\n\n/** How much to adjust weight per gradient unit */\nconst LEARNING_RATE = 0.1;\n\n/** Maximum weight adjustment per update cycle */\nconst MAX_WEIGHT_DELTA = 0.3;\n\n/** R-squared threshold below which we consider gradient unreliable */\nconst MIN_R_SQUARED_FOR_GRADIENT = 0.05;\n\n/** Gradient magnitude below which we consider it \"flat\" (near optimal) */\nconst FLAT_GRADIENT_THRESHOLD = 0.02;\n\n/** Maximum history entries to retain */\nconst MAX_HISTORY_LENGTH = 100;\n\n// ============================================================================\n// WEIGHT UPDATE\n// ============================================================================\n\n/**\n * Compute updated weight based on gradient result.\n *\n * The update logic:\n * - Positive gradient: users with higher weight did better → increase weight\n * - Negative gradient: users with higher weight did worse → decrease weight\n * - Flat gradient: weight doesn't affect outcome → increase confidence\n *\n * @param current - Current learnable weight\n * @param gradient - Computed gradient result\n * @returns Updated learnable weight\n */\nexport function updateStrategyWeight(\n current: LearnableWeight,\n gradient: GradientResult\n): LearnableWeight {\n // Not enough data to make reliable updates\n if (gradient.sampleSize < MIN_OBSERVATIONS_FOR_UPDATE) {\n logger.debug(\n `[Orchestration] Insufficient samples (${gradient.sampleSize} < ${MIN_OBSERVATIONS_FOR_UPDATE}), ` +\n `keeping current weight`\n );\n return {\n ...current,\n sampleSize: current.sampleSize + gradient.sampleSize,\n };\n }\n\n // Check if gradient is reliable (R² threshold)\n const isReliable = gradient.rSquared >= MIN_R_SQUARED_FOR_GRADIENT;\n const isFlat = Math.abs(gradient.gradient) < FLAT_GRADIENT_THRESHOLD;\n\n let newWeight = current.weight;\n let newConfidence = current.confidence;\n\n if (!isReliable || isFlat) {\n // Gradient is unreliable or flat - we're likely near optimal\n // Increase confidence (narrow the exploration spread)\n const confidenceGain = 0.05 * (1 - current.confidence);\n newConfidence = Math.min(1.0, current.confidence + confidenceGain);\n\n logger.debug(\n `[Orchestration] Flat/unreliable gradient (|g|=${Math.abs(gradient.gradient).toFixed(4)}, ` +\n `R²=${gradient.rSquared.toFixed(4)}). Increasing confidence: ${current.confidence.toFixed(3)} → ${newConfidence.toFixed(3)}`\n );\n } else {\n // Reliable gradient - adjust weight in gradient direction\n // Scale by learning rate and clamp to max delta\n let delta = gradient.gradient * LEARNING_RATE;\n delta = Math.max(-MAX_WEIGHT_DELTA, Math.min(MAX_WEIGHT_DELTA, delta));\n\n newWeight = current.weight + delta;\n\n // Clamp weight to reasonable bounds\n newWeight = Math.max(0.1, Math.min(3.0, newWeight));\n\n // Slight confidence increase for having made an observation\n const confidenceGain = 0.02 * (1 - current.confidence);\n newConfidence = Math.min(1.0, current.confidence + confidenceGain);\n\n logger.debug(\n `[Orchestration] Adjusting weight: ${current.weight.toFixed(3)} → ${newWeight.toFixed(3)} ` +\n `(gradient=${gradient.gradient.toFixed(4)}, delta=${delta.toFixed(4)})`\n );\n }\n\n return {\n weight: newWeight,\n confidence: newConfidence,\n sampleSize: current.sampleSize + gradient.sampleSize,\n };\n}\n\n// ============================================================================\n// LEARNING STATE MANAGEMENT\n// ============================================================================\n\n/**\n * Create or update a StrategyLearningState document.\n *\n * @param courseId - Course ID\n * @param strategyId - Strategy ID\n * @param currentWeight - Current learned weight\n * @param gradient - Gradient result from recent computation\n * @param existing - Existing learning state (if any)\n * @returns Updated learning state document\n */\nexport function updateLearningState(\n courseId: string,\n strategyId: string,\n currentWeight: LearnableWeight,\n gradient: GradientResult,\n existing?: StrategyLearningState\n): StrategyLearningState {\n const now = new Date().toISOString();\n const id = `STRATEGY_LEARNING_STATE::${courseId}::${strategyId}`;\n\n // Build history entry\n const historyEntry = {\n timestamp: now,\n weight: currentWeight.weight,\n confidence: currentWeight.confidence,\n gradient: gradient.gradient,\n };\n\n // Append to existing history or start fresh\n let history = existing?.history ?? [];\n history = [...history, historyEntry];\n\n // Trim history if too long\n if (history.length > MAX_HISTORY_LENGTH) {\n history = history.slice(history.length - MAX_HISTORY_LENGTH);\n }\n\n const state: StrategyLearningState = {\n _id: id,\n _rev: existing?._rev,\n docType: DocType.STRATEGY_LEARNING_STATE,\n courseId,\n strategyId,\n currentWeight,\n regression: {\n gradient: gradient.gradient,\n intercept: gradient.intercept,\n rSquared: gradient.rSquared,\n sampleSize: gradient.sampleSize,\n computedAt: now,\n },\n history,\n updatedAt: now,\n };\n\n return state;\n}\n\n// ============================================================================\n// PERIOD UPDATE ORCHESTRATOR\n// ============================================================================\n\n/**\n * Input data for running a period update on a single strategy.\n */\nexport interface PeriodUpdateInput {\n courseId: string;\n strategyId: string;\n currentWeight: LearnableWeight;\n gradient: GradientResult;\n existingState?: StrategyLearningState;\n}\n\n/**\n * Result of a period update for a single strategy.\n */\nexport interface PeriodUpdateResult {\n strategyId: string;\n previousWeight: LearnableWeight;\n newWeight: LearnableWeight;\n gradient: GradientResult;\n learningState: StrategyLearningState;\n updated: boolean;\n}\n\n/**\n * Run a period update for a single strategy.\n *\n * This function:\n * 1. Takes the computed gradient\n * 2. Computes the new weight\n * 3. Generates the updated learning state\n *\n * Note: Actual persistence (updating strategy doc, saving learning state)\n * must be done by the caller with appropriate DB access.\n *\n * @param input - Update input data\n * @returns Update result with new weight and learning state\n */\nexport function runPeriodUpdate(input: PeriodUpdateInput): PeriodUpdateResult {\n const { courseId, strategyId, currentWeight, gradient, existingState } = input;\n\n logger.info(\n `[Orchestration] Running period update for strategy ${strategyId} ` +\n `(${gradient.sampleSize} observations)`\n );\n\n // Compute new weight\n const newWeight = updateStrategyWeight(currentWeight, gradient);\n const updated = newWeight.weight !== currentWeight.weight;\n\n // Generate learning state\n const learningState = updateLearningState(\n courseId,\n strategyId,\n newWeight,\n gradient,\n existingState\n );\n\n logger.info(\n `[Orchestration] Period update complete for ${strategyId}: ` +\n `weight ${currentWeight.weight.toFixed(3)} → ${newWeight.weight.toFixed(3)}, ` +\n `confidence ${currentWeight.confidence.toFixed(3)} → ${newWeight.confidence.toFixed(3)}`\n );\n\n return {\n strategyId,\n previousWeight: currentWeight,\n newWeight,\n gradient,\n learningState,\n updated,\n };\n}\n\n/**\n * Create a default LearnableWeight for strategies that don't have one.\n */\nexport function getDefaultLearnableWeight(): LearnableWeight {\n return { ...DEFAULT_LEARNABLE_WEIGHT };\n}\n","import { QuestionRecord } from '../types/types-legacy';\n\nexport interface SignalConfig {\n /** Target accuracy for \"in the zone\" learning (default: 0.85) */\n targetAccuracy?: number;\n /** Width of the peak plateau (default: 0.05) */\n tolerance?: number;\n}\n\n/**\n * Computes a scalar signal (0-1) representing the quality of the learning outcome.\n *\n * Current implementation focuses on \"accuracy within Zone of Proximal Development\".\n * Future versions should include ELO gain rate.\n *\n * @param records - List of question attempts in the period\n * @param config - Configuration for the signal function\n * @returns Score 0.0-1.0, or null if insufficient data\n */\nexport function computeOutcomeSignal(\n records: QuestionRecord[],\n config: SignalConfig = {}\n): number | null {\n if (!records || records.length === 0) {\n return null;\n }\n\n const target = config.targetAccuracy ?? 0.85;\n const tolerance = config.tolerance ?? 0.05;\n\n let correct = 0;\n for (const r of records) {\n // Cast to any to avoid type error if Evaluation interface is not correctly propagated\n \n if ((r as any).isCorrect) correct++;\n }\n\n const accuracy = correct / records.length;\n\n return scoreAccuracyInZone(accuracy, target, tolerance);\n}\n\n/**\n * Scores an accuracy value based on how close it is to the target \"sweet spot\".\n *\n * The function defines a plateau of width (2 * tolerance) around the target\n * where score is 1.0. Outside this plateau, it falls off linearly.\n *\n * @param accuracy - Observed accuracy (0-1)\n * @param target - Target accuracy (e.g. 0.85)\n * @param tolerance - +/- range allowed for max score\n */\nexport function scoreAccuracyInZone(accuracy: number, target: number, tolerance: number): number {\n const dist = Math.abs(accuracy - target);\n\n // Inside the sweet spot\n if (dist <= tolerance) {\n return 1.0;\n }\n\n // Outside, fall off.\n // We apply a linear penalty for deviation from the tolerance edge.\n const excess = dist - tolerance;\n const slope = 2.5; // Falloff rate (0.4 deviation = 0 score)\n\n return Math.max(0, 1.0 - excess * slope);\n}","import { OrchestrationContext } from './index';\nimport { computeOutcomeSignal, SignalConfig } from './signal';\nimport { UserOutcomeRecord } from '../types/userOutcome';\nimport { DocType, QuestionRecord } from '../types/types-legacy';\nimport { logger } from '../../util/logger';\n\n/**\n * Records a learning outcome for a specific period of time.\n *\n * This function:\n * 1. Computes a scalar \"success\" signal from the provided question records\n * 2. Re-computes the deviations that were active for this user/course\n * 3. Persists a UserOutcomeRecord to the user's database\n *\n * This record is later used by the optimization job to correlate\n * deviations with outcomes (Evolutionary Orchestration).\n *\n * @param context - Orchestration context (user, course, etc.)\n * @param periodStart - ISO timestamp of period start\n * @param periodEnd - ISO timestamp of period end (now)\n * @param records - Question records generated during this period\n * @param activeStrategyIds - IDs of strategies active in this course\n * @param eloStart - User's ELO at start of period (optional)\n * @param eloEnd - User's ELO at end of period (optional)\n * @param config - Optional configuration for signal computation\n */\nexport async function recordUserOutcome(\n context: OrchestrationContext,\n periodStart: string,\n periodEnd: string,\n records: QuestionRecord[],\n activeStrategyIds: string[],\n eloStart: number = 0,\n eloEnd: number = 0,\n config?: SignalConfig\n): Promise<void> {\n const { user, course, userId } = context;\n const courseId = course.getCourseID();\n\n // 1. Compute Signal\n // If we have no records, we can't determine an outcome.\n const outcomeValue = computeOutcomeSignal(records, config);\n\n if (outcomeValue === null) {\n logger.debug(\n `[Orchestration] No outcome signal computed for ${userId} (insufficient data). Skipping record.`\n );\n return;\n }\n\n // 2. Capture Deviations\n // We re-compute the deterministic deviations for all active strategies.\n // This tells the learning algorithm \"what parameter adjustments were active\n // when this outcome was achieved\".\n const deviations: Record<string, number> = {};\n for (const strategyId of activeStrategyIds) {\n deviations[strategyId] = context.getDeviation(strategyId);\n }\n\n // 3. Construct Record\n // ID format: USER_OUTCOME::{courseId}::{userId}::{periodEnd}\n // This ensures uniqueness per user/course/time-period.\n const id = `USER_OUTCOME::${courseId}::${userId}::${periodEnd}`;\n\n const record: UserOutcomeRecord = {\n _id: id,\n docType: DocType.USER_OUTCOME,\n courseId,\n userId,\n periodStart,\n periodEnd,\n outcomeValue,\n deviations,\n metadata: {\n sessionsCount: 1, // Assumes recording is triggered per-session currently\n cardsSeen: records.length,\n eloStart,\n eloEnd,\n signalType: 'accuracy_in_zone',\n },\n };\n\n // 4. Persist\n try {\n await user.putUserOutcome(record);\n logger.debug(\n `[Orchestration] Recorded outcome ${outcomeValue.toFixed(3)} for ${userId} (doc: ${id})`\n );\n } catch (e) {\n logger.error(`[Orchestration] Failed to record outcome: ${e}`);\n }\n}","import type { UserDBInterface } from '../interfaces/userDB';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { LearnableWeight } from '../types/contentNavigationStrategy';\nimport type { CourseConfig } from '@vue-skuilder/common';\nimport { logger } from '../../util/logger';\n\n// Re-export gradient and learning functions\nexport { aggregateOutcomesForGradient, computeStrategyGradient } from './gradient';\nexport {\n updateStrategyWeight,\n updateLearningState,\n runPeriodUpdate,\n getDefaultLearnableWeight,\n} from './learning';\nexport type { PeriodUpdateInput, PeriodUpdateResult } from './learning';\n\n// Re-export signal functions\nexport { computeOutcomeSignal, scoreAccuracyInZone } from './signal';\nexport type { SignalConfig } from './signal';\n\n// Re-export recording functions\nexport { recordUserOutcome } from './recording';\n\n// Re-export types\nexport type { GradientObservation, GradientResult, StrategyLearningState } from '../types/learningState';\nexport type { UserOutcomeRecord } from '../types/userOutcome';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\n/**\n * Context for orchestration decisions during a session.\n * \n * Provides access to user/course data and helper methods for determining\n * effective strategy weights based on the user's cohort assignment.\n */\nexport interface OrchestrationContext {\n user: UserDBInterface;\n course: CourseDBInterface;\n userId: string;\n courseConfig: CourseConfig;\n \n /**\n * Calculate the effective weight for a strategy for this user.\n * \n * Applies deviation based on the user's cohort assignment (derived from\n * userId, strategyId, and course salt).\n * \n * @param strategyId - Unique ID of the strategy\n * @param learnable - The strategy's learning configuration\n * @returns Effective weight multiplier (typically 0.1 - 3.0)\n */\n getEffectiveWeight(strategyId: string, learnable: LearnableWeight): number;\n\n /**\n * Get the deviation factor for this user/strategy.\n * Range [-1.0, 1.0].\n */\n getDeviation(strategyId: string): number;\n}\n\n// ============================================================================\n// DEVIATION LOGIC\n// ============================================================================\n\nconst MIN_SPREAD = 0.1;\nconst MAX_SPREAD = 0.5;\nconst MIN_WEIGHT = 0.1;\nconst MAX_WEIGHT = 3.0;\n\n/**\n * FNV-1a hash implementation for deterministic distribution.\n */\nfunction fnv1a(str: string): number {\n let hash = 2166136261;\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i);\n hash = Math.imul(hash, 16777619);\n }\n return hash >>> 0;\n}\n\n/**\n * Compute a user's deviation for a specific strategy.\n * \n * Returns a value in [-1, 1] that is:\n * 1. Deterministic for the same (user, strategy, salt) tuple\n * 2. Uniformly distributed across users\n * 3. Uncorrelated between different strategies (due to strategyId in hash)\n * 4. Rotatable by changing the salt\n * \n * @param userId - ID of the user\n * @param strategyId - ID of the strategy\n * @param salt - Random seed from course config\n * @returns Deviation factor between -1.0 and 1.0\n */\nexport function computeDeviation(userId: string, strategyId: string, salt: string): number {\n const input = `${userId}:${strategyId}:${salt}`;\n const hash = fnv1a(input);\n \n // Normalize 32-bit unsigned integer to [0, 1]\n const normalized = hash / 4294967296;\n \n // Map [0, 1] to [-1, 1]\n return (normalized * 2) - 1;\n}\n\n/**\n * Compute the exploration spread based on confidence.\n * \n * - Low confidence (0.0) -> Max spread (Explore broadly)\n * - High confidence (1.0) -> Min spread (Exploit known good weight)\n * \n * @param confidence - Confidence level 0-1\n * @returns Spread magnitude (half-width of the distribution)\n */\nexport function computeSpread(confidence: number): number {\n // Linear interpolation: confidence 0 -> MAX_SPREAD, confidence 1 -> MIN_SPREAD\n const clampedConfidence = Math.max(0, Math.min(1, confidence));\n return MAX_SPREAD - (clampedConfidence * (MAX_SPREAD - MIN_SPREAD));\n}\n\n/**\n * Calculate the effective weight for a strategy instance.\n * \n * Combines the learnable weight (peak) with the user's deviation and the\n * allowed spread (based on confidence).\n * \n * @param learnable - Strategy learning config\n * @param userId - User ID\n * @param strategyId - Strategy ID\n * @param salt - Course salt\n * @returns Effective weight multiplier\n */\nexport function computeEffectiveWeight(\n learnable: LearnableWeight,\n userId: string,\n strategyId: string,\n salt: string\n): number {\n const deviation = computeDeviation(userId, strategyId, salt);\n const spread = computeSpread(learnable.confidence);\n \n // Apply deviation: effective = weight + (deviation * spread * weight)\n // We scale the spread relative to the weight itself so it's proportional.\n // e.g. weight 2.0, deviation -0.5, spread 0.2 -> 2.0 + (-0.5 * 0.2 * 2.0) = 1.8\n const adjustment = deviation * spread * learnable.weight;\n \n const effective = learnable.weight + adjustment;\n \n // Clamp to sane bounds to prevent runaway weights or negative multipliers\n return Math.max(MIN_WEIGHT, Math.min(MAX_WEIGHT, effective));\n}\n\n// ============================================================================\n// CONTEXT FACTORY\n// ============================================================================\n\n/**\n * Create an orchestration context for a study session.\n * \n * Fetches necessary configuration to enable deterministic weight calculation.\n * \n * @param user - User DB interface\n * @param course - Course DB interface\n * @returns Initialized orchestration context\n */\nexport async function createOrchestrationContext(\n user: UserDBInterface,\n course: CourseDBInterface\n): Promise<OrchestrationContext> {\n let courseConfig: CourseConfig;\n try {\n courseConfig = await course.getCourseConfig();\n } catch (e) {\n logger.error(`[Orchestration] Failed to load course config: ${e}`);\n // Fallback stub if config load fails\n courseConfig = {\n name: 'Unknown',\n description: '',\n public: false,\n deleted: false,\n creator: '',\n admins: [],\n moderators: [],\n dataShapes: [],\n questionTypes: [],\n orchestration: { salt: 'default' },\n };\n }\n\n const userId = user.getUsername(); // Or user ID if available on interface\n const salt = courseConfig.orchestration?.salt || 'default_salt';\n\n return {\n user,\n course,\n userId,\n courseConfig,\n \n getEffectiveWeight(strategyId: string, learnable: LearnableWeight): number {\n return computeEffectiveWeight(learnable, userId, strategyId, salt);\n },\n\n getDeviation(strategyId: string): number {\n return computeDeviation(userId, strategyId, salt);\n }\n };\n}","import { toCourseElo } from '@vue-skuilder/common';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport { ContentNavigator } from './index';\nimport type { WeightedCard } from './index';\nimport type { CardFilter, FilterContext } from './filters/types';\nimport type { CardGenerator, GeneratorContext } from './generators/types';\nimport type { GeneratorResult } from './generators/types';\nimport { logger } from '../../util/logger';\nimport { createOrchestrationContext, OrchestrationContext } from '../orchestration';\nimport { captureRun, buildRunReport, registerPipelineForDebug, type GeneratorSummary, type FilterImpact } from './PipelineDebugger';\n\n// ============================================================================\n// REPLAN HINTS\n// ============================================================================\n//\n// Ephemeral, one-shot scoring hints passed at replan time.\n// Applied after the filter chain, consumed after one pipeline run.\n//\n// Tag patterns support glob-style matching:\n// 'gpc:exercise:t-T' — exact match\n// 'gpc:intro:*' — all intro tags\n// 'gpc:exercise:t-*' — all t-variant exercises\n//\n\n// ReplanHints is defined in generators/types — re-export for consumers that import from Pipeline\nimport type { ReplanHints } from './generators/types';\nexport type { ReplanHints };\n\n/**\n * Convert a glob pattern (with `*` wildcards) to a RegExp.\n * Only `*` is supported as a wildcard (matches any characters).\n */\nfunction globToRegex(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const withWildcards = escaped.replace(/\\*/g, '.*');\n return new RegExp(`^${withWildcards}$`);\n}\n\n/** Test whether a string matches a glob pattern. */\nfunction globMatch(value: string, pattern: string): boolean {\n if (!pattern.includes('*')) return value === pattern;\n return globToRegex(pattern).test(value);\n}\n\n/** Test whether any of a card's tags match a glob pattern. */\nfunction cardMatchesTagPattern(card: WeightedCard, pattern: string): boolean {\n return (card.tags ?? []).some((tag) => globMatch(tag, pattern));\n}\n\nfunction mergeHints(allHints: Array<ReplanHints | null | undefined>): ReplanHints | undefined {\n const defined = allHints.filter((h): h is ReplanHints => h !== null && h !== undefined);\n if (defined.length === 0) return undefined;\n\n const merged: ReplanHints = {};\n\n const boostTags: Record<string, number> = {};\n for (const hints of defined) {\n for (const [pattern, factor] of Object.entries(hints.boostTags ?? {})) {\n boostTags[pattern] = (boostTags[pattern] ?? 1) * factor;\n }\n }\n if (Object.keys(boostTags).length > 0) {\n merged.boostTags = boostTags;\n }\n\n const boostCards: Record<string, number> = {};\n for (const hints of defined) {\n for (const [pattern, factor] of Object.entries(hints.boostCards ?? {})) {\n boostCards[pattern] = (boostCards[pattern] ?? 1) * factor;\n }\n }\n if (Object.keys(boostCards).length > 0) {\n merged.boostCards = boostCards;\n }\n\n const concatUnique = (\n field: 'requireTags' | 'requireCards' | 'excludeTags' | 'excludeCards'\n ): void => {\n const values = defined.flatMap((h) => h[field] ?? []);\n if (values.length > 0) {\n merged[field] = [...new Set(values)];\n }\n };\n\n concatUnique('requireTags');\n concatUnique('requireCards');\n concatUnique('excludeTags');\n concatUnique('excludeCards');\n\n const labels = defined.map((h) => h._label).filter(Boolean);\n if (labels.length > 0) {\n merged._label = labels.join('; ');\n }\n\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n// ============================================================================\n// PIPELINE LOGGING HELPERS\n// ============================================================================\n//\n// Focused logging functions that can be toggled by commenting single lines.\n// Use these to inspect pipeline behavior in development/production.\n//\n\n/**\n * Log pipeline configuration on construction.\n * Shows generator and filter chain structure.\n */\nfunction logPipelineConfig(generator: CardGenerator, filters: CardFilter[]): void {\n const filterList =\n filters.length > 0 ? '\\n - ' + filters.map((f) => f.name).join('\\n - ') : ' none';\n\n logger.info(\n `[Pipeline] Configuration:\\n` + ` Generator: ${generator.name}\\n` + ` Filters:${filterList}`\n );\n}\n\n/**\n * Log tag hydration results.\n * Shows effectiveness of batch query (how many cards/tags were hydrated).\n */\nfunction logTagHydration(cards: WeightedCard[], tagsByCard: Map<string, string[]>): void {\n const totalTags = Array.from(tagsByCard.values()).reduce((sum, tags) => sum + tags.length, 0);\n const cardsWithTags = Array.from(tagsByCard.values()).filter((tags) => tags.length > 0).length;\n\n logger.debug(\n `[Pipeline] Tag hydration: ${cards.length} cards, ` +\n `${cardsWithTags} have tags (${totalTags} total tags) - single batch query`\n );\n}\n\n/**\n * Log pipeline execution summary.\n * Shows complete flow from generator through filters to final results.\n */\nfunction logExecutionSummary(\n generatorName: string,\n generatedCount: number,\n filterCount: number,\n finalCount: number,\n topScores: number[],\n filterImpacts: Array<{ name: string; boosted: number; penalized: number; passed: number }>\n): void {\n const scoreDisplay =\n topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(', ') : 'none';\n\n let filterSummary = '';\n if (filterImpacts.length > 0) {\n const impacts = filterImpacts.map((f) => {\n const parts: string[] = [];\n if (f.boosted > 0) parts.push(`+${f.boosted}`);\n if (f.penalized > 0) parts.push(`-${f.penalized}`);\n if (f.passed > 0) parts.push(`=${f.passed}`);\n return `${f.name}: ${parts.join('/')}`;\n });\n filterSummary = `\\n Filter impact: ${impacts.join(', ')}`;\n }\n\n logger.info(\n `[Pipeline] Execution: ${generatorName} produced ${generatedCount} → ` +\n `${filterCount} filters → ${finalCount} results (top scores: ${scoreDisplay})` +\n filterSummary +\n `\\n 💡 Inspect: window.skuilder.pipeline`\n );\n}\n\n/**\n * Log all result cards with score, cardId, and key provenance.\n * Toggle: set VERBOSE_RESULTS = true to enable.\n */\nconst VERBOSE_RESULTS = true;\n\nfunction logResultCards(cards: WeightedCard[]): void {\n if (!VERBOSE_RESULTS || cards.length === 0) return;\n\n logger.info(`[Pipeline] Results (${cards.length} cards):`);\n for (let i = 0; i < cards.length; i++) {\n const c = cards[i];\n const tags = c.tags?.slice(0, 3).join(', ') || '';\n const filters = c.provenance\n .filter((p) => p.strategy === 'hierarchyDefinition' || p.strategy === 'priorityDefinition' || p.strategy === 'interferenceFilter' || p.strategy === 'letterGating' || p.strategy === 'ephemeralHint')\n .map((p) => {\n const arrow = p.action === 'boosted' ? '↑' : p.action === 'penalized' ? '↓' : '=';\n return `${p.strategyName}${arrow}${p.score.toFixed(2)}`;\n })\n .join(' | ');\n logger.info(\n `[Pipeline] ${String(i + 1).padStart(2)}. ${c.score.toFixed(4)} ${c.cardId} [${tags}]${filters ? ` {${filters}}` : ''}`\n );\n }\n}\n\n/**\n * Log provenance trails for cards.\n * Shows the complete scoring history for each card through the pipeline.\n * Useful for debugging why cards scored the way they did.\n */\nfunction logCardProvenance(cards: WeightedCard[], maxCards: number = 3): void {\n const cardsToLog = cards.slice(0, maxCards);\n\n logger.debug(`[Pipeline] Provenance for top ${cardsToLog.length} cards:`);\n\n for (const card of cardsToLog) {\n logger.debug(`[Pipeline] ${card.cardId} (final score: ${card.score.toFixed(3)}):`);\n\n for (const entry of card.provenance) {\n const scoreChange = entry.score.toFixed(3);\n const action = entry.action.padEnd(9); // Align columns\n logger.debug(\n `[Pipeline] ${action} ${scoreChange} - ${entry.strategyName}: ${entry.reason}`\n );\n }\n }\n}\n\n// ============================================================================\n// PIPELINE\n// ============================================================================\n//\n// Executes a navigation pipeline: generator → filters → sorted results.\n//\n// Architecture:\n// cards = generator.getWeightedCards(limit, context)\n// cards = filter1.transform(cards, context)\n// cards = filter2.transform(cards, context)\n// cards = filter3.transform(cards, context)\n// return sorted(cards).slice(0, limit)\n//\n// Benefits:\n// - Clear separation: generators produce, filters transform\n// - No nested instantiation complexity\n// - Filters don't need to know about each other\n// - Shared context built once, passed to all stages\n//\n// ============================================================================\n\n/**\n * A navigation pipeline that runs a generator and applies filters sequentially.\n *\n * Implements StudyContentSource for backward compatibility with SessionController.\n *\n * ## Usage\n *\n * ```typescript\n * const pipeline = new Pipeline(\n * compositeGenerator, // or single generator\n * [eloDistanceFilter, interferenceFilter],\n * user,\n * course\n * );\n *\n * const cards = await pipeline.getWeightedCards(20);\n * ```\n */\nexport class Pipeline extends ContentNavigator {\n private generator: CardGenerator;\n private filters: CardFilter[];\n\n /**\n * Cached orchestration context. Course config and salt don't change within\n * a page load, so we build the orchestration context once and reuse it on\n * subsequent getWeightedCards() calls (e.g. mid-session replans).\n *\n * This eliminates a remote getCourseConfig() round trip per pipeline run.\n */\n private _cachedOrchestration: OrchestrationContext | null = null;\n\n /**\n * Persistent tag cache. Maps cardId → tag names.\n *\n * Tags are static within a session (they're set at card generation time),\n * so we cache them across pipeline runs. On replans, many of the same cards\n * reappear — cache hits avoid redundant remote getAppliedTagsBatch() queries.\n */\n private _tagCache: Map<string, string[]> = new Map();\n\n /**\n * One-shot replan hints. Applied after the filter chain on the next\n * getWeightedCards() call, then cleared.\n */\n private _ephemeralHints: ReplanHints | null = null;\n\n /**\n * Create a new pipeline.\n *\n * @param generator - The generator (or CompositeGenerator) that produces candidates\n * @param filters - Filters to apply sequentially (order doesn't matter for multipliers)\n * @param user - User database interface\n * @param course - Course database interface\n */\n constructor(\n generator: CardGenerator,\n filters: CardFilter[],\n user: UserDBInterface,\n course: CourseDBInterface\n ) {\n super();\n this.generator = generator;\n this.filters = filters;\n this.user = user;\n this.course = course;\n\n course\n .getCourseConfig()\n .then((cfg) => {\n logger.debug(`[pipeline] Crated pipeline for ${cfg.name}`);\n })\n .catch((e) => {\n logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`);\n });\n // Toggle pipeline configuration logging:\n logPipelineConfig(generator, filters);\n\n // Register for debug API access\n registerPipelineForDebug(this);\n }\n\n /**\n * Set one-shot hints for the next pipeline run.\n * Consumed after one getWeightedCards() call, then cleared.\n *\n * Overrides ContentNavigator.setEphemeralHints() no-op.\n */\n override setEphemeralHints(hints: ReplanHints): void {\n this._ephemeralHints = hints;\n logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(hints)}`);\n }\n\n /**\n * Get weighted cards by running generator and applying filters.\n *\n * 1. Build shared context (user ELO, etc.)\n * 2. Get candidates from generator (passing context)\n * 3. Batch hydrate tags for all candidates\n * 4. Apply each filter sequentially\n * 5. Remove zero-score cards\n * 6. Sort by score descending\n * 7. Return top N\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n async getWeightedCards(limit: number): Promise<GeneratorResult> {\n const t0 = performance.now();\n\n // Build shared context once\n const context = await this.buildContext();\n const tContext = performance.now();\n\n // Over-fetch from generator to give filters a wide candidate pool.\n // With local course DB the cost is negligible (~20ms for 500 cards).\n // Filters (hierarchy, letter gating, etc.) can be aggressive — a wide\n // pool ensures enough well-indicated candidates survive.\n const fetchLimit = 500;\n\n logger.debug(\n `[Pipeline] Fetching ${fetchLimit} candidates from generator '${this.generator.name}'`\n );\n\n // Get candidates from generator, passing context\n const generatorResult = await this.generator.getWeightedCards(fetchLimit, context);\n let cards = generatorResult.cards;\n const tGenerate = performance.now();\n const generatedCount = cards.length;\n\n // Merge generator-emitted hints with any externally supplied one-shot hints\n const mergedHints = mergeHints([this._ephemeralHints, generatorResult.hints]);\n this._ephemeralHints = mergedHints ?? null;\n \n // Capture generator breakdown for debugging (if CompositeGenerator)\n let generatorSummaries: GeneratorSummary[] | undefined;\n if ((this.generator as any).generators) {\n // This is a CompositeGenerator - extract per-generator info from provenance\n const genMap = new Map<string, { cards: WeightedCard[] }>();\n for (const card of cards) {\n const firstProv = card.provenance[0];\n if (firstProv) {\n const genName = firstProv.strategyName;\n if (!genMap.has(genName)) {\n genMap.set(genName, { cards: [] });\n }\n genMap.get(genName)!.cards.push(card);\n }\n }\n generatorSummaries = Array.from(genMap.entries()).map(([name, data]) => {\n const newCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes('new card'));\n const reviewCards = data.cards.filter((c) => c.provenance[0]?.reason?.includes('review'));\n return {\n name,\n cardCount: data.cards.length,\n newCount: newCards.length,\n reviewCount: reviewCards.length,\n topScore: Math.max(...data.cards.map((c) => c.score), 0),\n };\n });\n }\n\n logger.debug(`[Pipeline] Generator returned ${generatedCount} candidates`);\n\n // Batch hydrate tags before filters run\n cards = await this.hydrateTags(cards);\n const tHydrate = performance.now();\n \n // Keep a copy of all cards for debug capture (before filtering removes any)\n const allCardsBeforeFiltering = [...cards];\n\n // Apply filters sequentially, tracking impact\n const filterImpacts: FilterImpact[] = [];\n for (const filter of this.filters) {\n const beforeCount = cards.length;\n const beforeScores = new Map(cards.map((c) => [c.cardId, c.score]));\n cards = await filter.transform(cards, context);\n \n // Count boost/penalize/pass/removed for this filter\n let boosted = 0, penalized = 0, passed = 0;\n const removed = beforeCount - cards.length;\n \n for (const card of cards) {\n const before = beforeScores.get(card.cardId) ?? 0;\n if (card.score > before) boosted++;\n else if (card.score < before) penalized++;\n else passed++;\n }\n filterImpacts.push({ name: filter.name, boosted, penalized, passed, removed });\n \n logger.debug(`[Pipeline] Filter '${filter.name}': ${beforeScores.size} → ${cards.length} cards (↑${boosted} ↓${penalized} =${passed})`);\n }\n\n // Remove zero-score cards (hard filtered)\n cards = cards.filter((c) => c.score > 0);\n\n // Apply ephemeral hints (one-shot, post-filter)\n const hints = this._ephemeralHints;\n if (hints) {\n this._ephemeralHints = null; // consume\n cards = this.applyHints(cards, hints, allCardsBeforeFiltering);\n }\n\n // Sort by score descending\n cards.sort((a, b) => b.score - a.score);\n\n // Return top N\n const tFilter = performance.now();\n const result = cards.slice(0, limit);\n\n logger.info(\n `[Pipeline:timing] total=${(tFilter - t0).toFixed(0)}ms ` +\n `(context=${(tContext - t0).toFixed(0)} generate=${(tGenerate - tContext).toFixed(0)} ` +\n `hydrate=${(tHydrate - tGenerate).toFixed(0)} filter=${(tFilter - tHydrate).toFixed(0)})`\n );\n\n // Toggle execution summary logging:\n const topScores = result.slice(0, 3).map((c) => c.score);\n logExecutionSummary(\n this.generator.name,\n generatedCount,\n this.filters.length,\n result.length,\n topScores,\n filterImpacts\n );\n\n // Toggle verbose result listing:\n logResultCards(result);\n\n // Toggle provenance logging (shows scoring history for top cards):\n logCardProvenance(result, 3);\n\n // Capture run for debug API\n try {\n const courseName = await this.course?.getCourseConfig().then((c) => c.name).catch(() => undefined);\n // Use the full post-filter sorted array (not just top N) so that\n // showCard() can inspect provenance for cards that didn't make the cut.\n // `cards` is the post-filter, post-hints, sorted array.\n const report = buildRunReport(\n this.course?.getCourseID() || 'unknown',\n courseName,\n this.generator.name,\n generatorSummaries,\n generatedCount,\n filterImpacts,\n cards,\n result,\n context.userElo\n );\n captureRun(report);\n } catch (e) {\n logger.debug(`[Pipeline] Failed to capture debug run: ${e}`);\n }\n\n return { cards: result };\n }\n\n /**\n * Batch hydrate tags for all cards.\n *\n * Fetches tags for all cards in a single database query and attaches them\n * to the WeightedCard objects. Filters can then use card.tags instead of\n * making individual getAppliedTags() calls.\n *\n * Uses a persistent tag cache across pipeline runs — tags are static within\n * a session, so cards seen in a prior run (e.g. before a replan) don't\n * require a second DB query.\n *\n * @param cards - Cards to hydrate\n * @returns Cards with tags populated\n */\n private async hydrateTags(cards: WeightedCard[]): Promise<WeightedCard[]> {\n if (cards.length === 0) {\n return cards;\n }\n\n // Separate cards with cached tags from those needing a DB query\n const uncachedIds: string[] = [];\n for (const card of cards) {\n if (!this._tagCache.has(card.cardId)) {\n uncachedIds.push(card.cardId);\n }\n }\n\n // Only query the DB for cards not already in cache\n if (uncachedIds.length > 0) {\n const freshTags = await this.course!.getAppliedTagsBatch(uncachedIds);\n for (const [cardId, tags] of freshTags) {\n this._tagCache.set(cardId, tags);\n }\n }\n\n // Build the tagsByCard map from cache (for logging compatibility)\n const tagsByCard = new Map<string, string[]>();\n for (const card of cards) {\n tagsByCard.set(card.cardId, this._tagCache.get(card.cardId) ?? []);\n }\n\n // Toggle tag hydration logging:\n logTagHydration(cards, tagsByCard);\n\n return cards.map((card) => ({\n ...card,\n tags: this._tagCache.get(card.cardId) ?? [],\n }));\n }\n\n // ---------------------------------------------------------------------------\n // Ephemeral hints application\n // ---------------------------------------------------------------------------\n\n /**\n * Apply one-shot replan hints to the post-filter card set.\n *\n * Order of operations:\n * 1. Exclude (remove unwanted cards)\n * 2. Boost (multiply scores)\n * 3. Require (inject must-have cards from the full pre-filter pool)\n *\n * @param cards - Post-filter cards (score > 0)\n * @param hints - The ephemeral hints to apply\n * @param allCards - Full pre-filter card pool (for require injection)\n */\n private applyHints(\n cards: WeightedCard[],\n hints: ReplanHints,\n allCards: WeightedCard[]\n ): WeightedCard[] {\n const beforeCount = cards.length;\n\n // 1. Exclude\n if (hints.excludeCards?.length) {\n cards = cards.filter(\n (c) => !hints.excludeCards!.some((pat) => globMatch(c.cardId, pat))\n );\n }\n if (hints.excludeTags?.length) {\n cards = cards.filter(\n (c) => !hints.excludeTags!.some((pat) => cardMatchesTagPattern(c, pat))\n );\n }\n\n // 2. Boost\n if (hints.boostTags) {\n for (const [pattern, factor] of Object.entries(hints.boostTags)) {\n for (const card of cards) {\n if (cardMatchesTagPattern(card, pattern)) {\n card.score *= factor;\n card.provenance.push({\n strategy: 'ephemeralHint',\n strategyId: 'ephemeral-hint',\n strategyName: hints._label ? `Replan Hint (${hints._label})` : 'Replan Hint',\n action: 'boosted',\n score: card.score,\n reason: `boostTag ${pattern} ×${factor}`,\n });\n }\n }\n }\n }\n if (hints.boostCards) {\n for (const [pattern, factor] of Object.entries(hints.boostCards)) {\n for (const card of cards) {\n if (globMatch(card.cardId, pattern)) {\n card.score *= factor;\n card.provenance.push({\n strategy: 'ephemeralHint',\n strategyId: 'ephemeral-hint',\n strategyName: hints._label ? `Replan Hint (${hints._label})` : 'Replan Hint',\n action: 'boosted',\n score: card.score,\n reason: `boostCard ${pattern} ×${factor}`,\n });\n }\n }\n }\n }\n\n // 3. Require — inject from the full pool if not already present\n const cardIds = new Set(cards.map((c) => c.cardId));\n const hintLabel = hints._label ? `Replan Hint (${hints._label})` : 'Replan Hint';\n const inject = (card: WeightedCard, reason: string) => {\n if (!cardIds.has(card.cardId)) {\n // Give required cards a floor score so they sort above zero-score filler\n const floorScore = Math.max(card.score, 1.0);\n cards.push({\n ...card,\n score: floorScore,\n provenance: [\n ...card.provenance,\n {\n strategy: 'ephemeralHint',\n strategyId: 'ephemeral-hint',\n strategyName: hintLabel,\n action: 'boosted',\n score: floorScore,\n reason,\n },\n ],\n });\n cardIds.add(card.cardId);\n }\n };\n\n if (hints.requireCards?.length) {\n for (const pattern of hints.requireCards) {\n for (const card of allCards) {\n if (globMatch(card.cardId, pattern)) inject(card, `requireCard ${pattern}`);\n }\n }\n }\n if (hints.requireTags?.length) {\n for (const pattern of hints.requireTags) {\n for (const card of allCards) {\n if (cardMatchesTagPattern(card, pattern)) inject(card, `requireTag ${pattern}`);\n }\n }\n }\n\n logger.info(`[Pipeline] Hints applied: ${beforeCount} → ${cards.length} cards`);\n\n return cards;\n }\n\n /**\n * Build shared context for generator and filters.\n *\n * Called once per getWeightedCards() invocation.\n * Contains data that the generator and multiple filters might need.\n *\n * The context satisfies both GeneratorContext and FilterContext interfaces.\n */\n private async buildContext(): Promise<GeneratorContext & FilterContext> {\n let userElo = 1000; // Default ELO\n\n try {\n const courseReg = await this.user!.getCourseRegDoc(this.course!.getCourseID());\n const courseElo = toCourseElo(courseReg.elo);\n userElo = courseElo.global.score;\n } catch (e) {\n logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`);\n }\n\n // Reuse cached orchestration context if available (course config is stable\n // within a page load). This avoids a remote getCourseConfig() call on\n // subsequent pipeline runs (e.g. mid-session replans).\n if (!this._cachedOrchestration) {\n this._cachedOrchestration = await createOrchestrationContext(this.user!, this.course!);\n }\n const orchestration = this._cachedOrchestration;\n\n return {\n user: this.user!,\n course: this.course!,\n userElo,\n orchestration,\n };\n }\n\n /**\n * Get the course ID for this pipeline.\n */\n getCourseID(): string {\n return this.course!.getCourseID();\n }\n\n /**\n * Get orchestration context for outcome recording.\n */\n async getOrchestrationContext(): Promise<OrchestrationContext> {\n return createOrchestrationContext(this.user!, this.course!);\n }\n\n /**\n * Get IDs of all strategies in this pipeline.\n * Used to record which strategies contributed to an outcome.\n */\n getStrategyIds(): string[] {\n const ids: string[] = [];\n\n const extractId = (obj: any): string | null => {\n // Check for strategyId property (ContentNavigator, WeightedFilter)\n if (obj.strategyId) return obj.strategyId;\n return null;\n };\n\n // Generator(s)\n const genId = extractId(this.generator);\n if (genId) ids.push(genId);\n\n // Inspect CompositeGenerator children (accessing private field via cast)\n if ((this.generator as any).generators && Array.isArray((this.generator as any).generators)) {\n (this.generator as any).generators.forEach((g: any) => {\n const subId = extractId(g);\n if (subId) ids.push(subId);\n });\n }\n\n // Filters\n for (const filter of this.filters) {\n const fId = extractId(filter);\n if (fId) ids.push(fId);\n }\n\n return [...new Set(ids)];\n }\n\n // ---------------------------------------------------------------------------\n // Tag ELO diagnostic\n // ---------------------------------------------------------------------------\n\n /**\n * Get the user's per-tag ELO data for specified tags (or all tags).\n * Useful for diagnosing why hierarchy gates are open/closed.\n */\n async getTagEloStatus(\n tagFilter?: string | string[]\n ): Promise<Record<string, { score: number; count: number }>> {\n const courseReg = await this.user!.getCourseRegDoc(this.course!.getCourseID());\n const courseElo = toCourseElo(courseReg.elo);\n\n const result: Record<string, { score: number; count: number }> = {};\n\n if (!tagFilter) {\n // Return all tags\n for (const [tag, data] of Object.entries(courseElo.tags)) {\n result[tag] = { score: data.score, count: data.count };\n }\n } else {\n const patterns = Array.isArray(tagFilter) ? tagFilter : [tagFilter];\n for (const pattern of patterns) {\n const regex = globToRegex(pattern);\n for (const [tag, data] of Object.entries(courseElo.tags)) {\n if (regex.test(tag)) {\n result[tag] = { score: data.score, count: data.count };\n }\n }\n }\n }\n\n return result;\n }\n\n // ---------------------------------------------------------------------------\n // Card-space diagnostic\n // ---------------------------------------------------------------------------\n\n /**\n * Scan every card in the course through the filter chain and report\n * how many are \"well indicated\" (score >= threshold) for the current user.\n *\n * Also reports how many well-indicated cards the user has NOT yet encountered.\n *\n * Exposed via `window.skuilder.pipeline.diagnoseCardSpace()`.\n */\n async diagnoseCardSpace(opts?: { threshold?: number }): Promise<CardSpaceDiagnosis> {\n const THRESHOLD = opts?.threshold ?? 0.10;\n const t0 = performance.now();\n\n // 1. Get all card IDs\n const allCardIds = await this.course!.getAllCardIds();\n\n // 2. Build dummy WeightedCards (score=1.0, no provenance)\n let cards: WeightedCard[] = allCardIds.map((cardId) => ({\n cardId,\n courseId: this.course!.getCourseID(),\n score: 1.0,\n provenance: [],\n }));\n\n // 3. Hydrate tags\n cards = await this.hydrateTags(cards);\n\n // 4. Run through filters\n const context = await this.buildContext();\n const filterBreakdown: Array<{ name: string; wellIndicated: number }> = [];\n\n // Track cumulative filter effects\n for (const filter of this.filters) {\n cards = await filter.transform(cards, context);\n const wi = cards.filter((c) => c.score >= THRESHOLD).length;\n filterBreakdown.push({ name: filter.name, wellIndicated: wi });\n }\n\n // 5. Count well-indicated\n const wellIndicated = cards.filter((c) => c.score >= THRESHOLD);\n const wellIndicatedIds = new Set(wellIndicated.map((c) => c.cardId));\n\n // 6. Get encountered cards\n let encounteredIds: Set<string>;\n try {\n const courseId = this.course!.getCourseID();\n const seenCards = await this.user!.getSeenCards(courseId);\n encounteredIds = new Set(seenCards);\n } catch {\n encounteredIds = new Set();\n }\n\n const wellIndicatedNew = wellIndicated.filter((c) => !encounteredIds.has(c.cardId));\n\n // 7. Group by card type\n const byType = new Map<string, { total: number; wellIndicated: number; new: number }>();\n for (const card of cards) {\n const type = card.cardId.split('-')[1] || 'unknown'; // c-ws-... → ws, c-intro-... → intro, etc.\n if (!byType.has(type)) {\n byType.set(type, { total: 0, wellIndicated: 0, new: 0 });\n }\n const entry = byType.get(type)!;\n entry.total++;\n if (card.score >= THRESHOLD) {\n entry.wellIndicated++;\n if (!encounteredIds.has(card.cardId)) entry.new++;\n }\n }\n\n const elapsed = performance.now() - t0;\n\n const result: CardSpaceDiagnosis = {\n totalCards: allCardIds.length,\n threshold: THRESHOLD,\n wellIndicated: wellIndicatedIds.size,\n encountered: encounteredIds.size,\n wellIndicatedNew: wellIndicatedNew.length,\n byType: Object.fromEntries(byType),\n filterBreakdown,\n elapsedMs: Math.round(elapsed),\n };\n\n // Log to console\n logger.info(`[Pipeline:diagnose] Card space scan (${result.elapsedMs}ms):`);\n logger.info(`[Pipeline:diagnose] Total cards: ${result.totalCards}`);\n logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${THRESHOLD}): ${result.wellIndicated}`);\n logger.info(`[Pipeline:diagnose] Encountered: ${result.encountered}`);\n logger.info(`[Pipeline:diagnose] Well-indicated & new: ${result.wellIndicatedNew}`);\n logger.info(`[Pipeline:diagnose] By type:`);\n for (const [type, counts] of byType) {\n logger.info(\n `[Pipeline:diagnose] ${type}: ${counts.wellIndicated}/${counts.total} well-indicated, ${counts.new} new`\n );\n }\n logger.info(`[Pipeline:diagnose] After each filter:`);\n for (const fb of filterBreakdown) {\n logger.info(`[Pipeline:diagnose] ${fb.name}: ${fb.wellIndicated} well-indicated`);\n }\n\n return result;\n }\n\n}\n\n/**\n * Diagnosis of the full card space for the current user.\n */\nexport interface CardSpaceDiagnosis {\n totalCards: number;\n threshold: number;\n wellIndicated: number;\n encountered: number;\n wellIndicatedNew: number;\n byType: Record<string, { total: number; wellIndicated: number; new: number }>;\n filterBreakdown: Array<{ name: string; wellIndicated: number }>;\n elapsedMs: number;\n}\n","import { Navigators } from './index';\nimport { Pipeline } from './Pipeline';\nimport CompositeGenerator from './generators/CompositeGenerator';\nimport ELONavigator from './generators/elo';\nimport SRSNavigator from './generators/srs';\nimport { createEloDistanceFilter } from './filters/eloDistance';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { DocType } from '../types/types-legacy';\nimport type { CourseDBInterface, UserDBInterface } from '../interfaces';\n\n/**\n * Default navigation pipeline configuration.\n *\n * This module provides factory functions for creating the canonical default\n * navigation pipeline used by both CouchDB and static course implementations.\n */\n\n/**\n * Create default ELO navigation strategy data.\n * Used when no custom strategies are configured.\n *\n * @param courseId - The course ID to associate with this strategy\n * @returns Strategy data for default ELO navigation\n */\nexport function createDefaultEloStrategy(courseId: string): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-ELO-default',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO (default)',\n description: 'Default ELO-based navigation strategy for new cards',\n implementingClass: Navigators.ELO,\n course: courseId,\n serializedData: '',\n };\n}\n\n/**\n * Create default SRS navigation strategy data.\n * Used when no custom strategies are configured.\n *\n * @param courseId - The course ID to associate with this strategy\n * @returns Strategy data for default SRS navigation\n */\nexport function createDefaultSrsStrategy(courseId: string): ContentNavigationStrategyData {\n return {\n _id: 'NAVIGATION_STRATEGY-SRS-default',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'SRS (default)',\n description: 'Default SRS-based navigation strategy for reviews',\n implementingClass: Navigators.SRS,\n course: courseId,\n serializedData: '',\n };\n}\n\n/**\n * Creates the default navigation pipeline for courses with no configured strategies.\n *\n * Default: Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\n * - ELO generator: scores new cards by skill proximity\n * - SRS generator: scores reviews by overdueness and interval recency\n * - ELO distance filter: penalizes cards far from user's current level\n *\n * This is the canonical default configuration used when:\n * - No navigation strategy documents exist in the course\n * - PipelineAssembler fails to build from strategy documents\n *\n * @param user - User database interface for accessing user state\n * @param course - Course database interface for accessing course data\n * @returns Configured Pipeline ready for use\n */\nexport function createDefaultPipeline(\n user: UserDBInterface,\n course: CourseDBInterface\n): Pipeline {\n const courseId = course.getCourseID();\n const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));\n const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));\n\n const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);\n const eloDistanceFilter = createEloDistanceFilter();\n\n return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);\n}\n","import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { ContentNavigator, isGenerator, isFilter, Navigators } from './index';\nimport type { CardFilter } from './filters/types';\nimport { WeightedFilter } from './filters/WeightedFilter';\nimport type { CardGenerator } from './generators/types';\nimport { Pipeline } from './Pipeline';\nimport { logger } from '../../util/logger';\nimport type { CourseDBInterface } from '../interfaces/courseDB';\nimport type { UserDBInterface } from '../interfaces/userDB';\nimport CompositeGenerator from './generators/CompositeGenerator';\nimport { createDefaultEloStrategy, createDefaultSrsStrategy } from './defaults';\n\n// ============================================================================\n// PIPELINE ASSEMBLER\n// ============================================================================\n//\n// Assembles navigation strategies into a Pipeline instance.\n//\n// This class is DB-agnostic: it receives strategy documents and returns an\n// assembled, ready-to-use Pipeline. This separation enables:\n// 1. Use with different DB implementations (Couch, Static, etc.)\n// 2. Future dynamic/evolutionary strategy selection\n// 3. Easy unit testing without DB mocking\n//\n// Pipeline assembly:\n// 1. Separate strategies into generators and filters by role\n// 2. Instantiate generator(s) - wrap multiple in CompositeGenerator\n// 3. Instantiate filters\n// 4. Return Pipeline(generator, filters)\n//\n// ============================================================================\n\n/**\n * Input for pipeline assembly.\n */\nexport interface PipelineAssemblerInput {\n /** All strategy documents to assemble into a pipeline */\n strategies: ContentNavigationStrategyData[];\n /** User database interface (required for instantiation) */\n user: UserDBInterface;\n /** Course database interface (required for instantiation) */\n course: CourseDBInterface;\n}\n\n/**\n * Result of pipeline assembly.\n */\nexport interface PipelineAssemblyResult {\n /** The assembled pipeline, or null if assembly failed */\n pipeline: Pipeline | null;\n /** Generator strategies found (for informational purposes) */\n generatorStrategies: ContentNavigationStrategyData[];\n /** Filter strategies found (for informational purposes) */\n filterStrategies: ContentNavigationStrategyData[];\n /** Warnings encountered during assembly (logged but non-fatal) */\n warnings: string[];\n}\n\n/**\n * Assembles navigation strategies into a Pipeline.\n *\n * Instantiates generators and filters from strategy documents and\n * composes them into a ready-to-use Pipeline instance.\n */\nexport class PipelineAssembler {\n /**\n * Assembles a navigation pipeline from strategy documents.\n *\n * 1. Separates into generators and filters by role\n * 2. Validates at least one generator exists (or creates default ELO)\n * 3. Instantiates generators - wraps multiple in CompositeGenerator\n * 4. Instantiates filters\n * 5. Returns Pipeline(generator, filters)\n *\n * @param input - Strategy documents plus user/course interfaces\n * @returns Assembled pipeline and any warnings\n */\n async assemble(input: PipelineAssemblerInput): Promise<PipelineAssemblyResult> {\n const { strategies, user, course } = input;\n const warnings: string[] = [];\n\n if (strategies.length === 0) {\n return {\n pipeline: null,\n generatorStrategies: [],\n filterStrategies: [],\n warnings,\n };\n }\n\n // Separate generators from filters\n const generatorStrategies: ContentNavigationStrategyData[] = [];\n const filterStrategies: ContentNavigationStrategyData[] = [];\n\n for (const s of strategies) {\n if (isGenerator(s.implementingClass)) {\n generatorStrategies.push(s);\n } else if (isFilter(s.implementingClass)) {\n filterStrategies.push(s);\n } else {\n // Unknown strategy type — skip with warning\n warnings.push(`Unknown strategy type '${s.implementingClass}', skipping: ${s.name}`);\n }\n }\n\n // Always ensure ELO and SRS generators are present.\n // Custom generators (e.g., prescribed) supplement but don't replace them.\n const courseId = course.getCourseID();\n const hasElo = generatorStrategies.some((s) => s.implementingClass === Navigators.ELO);\n const hasSrs = generatorStrategies.some((s) => s.implementingClass === Navigators.SRS);\n\n if (!hasElo) {\n logger.debug('[PipelineAssembler] No ELO generator configured, adding default');\n generatorStrategies.push(createDefaultEloStrategy(courseId));\n }\n if (!hasSrs) {\n logger.debug('[PipelineAssembler] No SRS generator configured, adding default');\n generatorStrategies.push(createDefaultSrsStrategy(courseId));\n }\n\n if (generatorStrategies.length === 0) {\n warnings.push('No generator strategy found');\n return {\n pipeline: null,\n generatorStrategies: [],\n filterStrategies: [],\n warnings,\n };\n }\n\n // Instantiate generators\n let generator: CardGenerator;\n\n if (generatorStrategies.length === 1) {\n // Single generator\n const nav = await ContentNavigator.create(user, course, generatorStrategies[0]);\n generator = nav as unknown as CardGenerator;\n logger.debug(`[PipelineAssembler] Using single generator: ${generatorStrategies[0].name}`);\n } else {\n // Multiple generators - wrap in CompositeGenerator\n logger.debug(\n `[PipelineAssembler] Using CompositeGenerator for ${generatorStrategies.length} generators: ${generatorStrategies.map((g) => g.name).join(', ')}`\n );\n generator = await CompositeGenerator.fromStrategies(user, course, generatorStrategies);\n }\n\n // Instantiate filters\n const filters: CardFilter[] = [];\n\n // Sort filters alphabetically for deterministic ordering\n const sortedFilterStrategies = [...filterStrategies].sort((a, b) =>\n a.name.localeCompare(b.name)\n );\n\n for (const filterStrategy of sortedFilterStrategies) {\n try {\n const nav = await ContentNavigator.create(user, course, filterStrategy);\n // The navigator implements CardFilter\n if ('transform' in nav && typeof nav.transform === 'function') {\n let filter = nav as unknown as CardFilter;\n\n // Apply evolutionary weighting wrapper if configured\n if (filterStrategy.learnable) {\n filter = new WeightedFilter(\n filter,\n filterStrategy.learnable,\n filterStrategy.staticWeight,\n filterStrategy._id\n );\n }\n\n filters.push(filter);\n logger.debug(`[PipelineAssembler] Added filter: ${filterStrategy.name}`);\n } else {\n warnings.push(\n `Filter '${filterStrategy.name}' does not implement CardFilter.transform(), skipping`\n );\n }\n } catch (e) {\n warnings.push(`Failed to instantiate filter '${filterStrategy.name}': ${e}`);\n }\n }\n\n // Build pipeline\n const pipeline = new Pipeline(generator, filters, user, course);\n\n logger.debug(\n `[PipelineAssembler] Assembled pipeline with ${generatorStrategies.length} generator(s) and ${filters.length} filter(s)`\n );\n\n return {\n pipeline,\n generatorStrategies,\n filterStrategies: sortedFilterStrategies,\n warnings,\n };\n }\n}\n","import { StudyContentSource, UserDBInterface, CourseDBInterface } from '..';\n\n// Re-export filter types\nexport type { CardFilter, FilterContext, CardFilterFactory } from './filters/types';\n\n// Re-export generator types\nexport type { CardGenerator, GeneratorContext, CardGeneratorFactory, GeneratorResult, ReplanHints } from './generators/types';\nimport type { GeneratorResult, ReplanHints } from './generators/types';\n\n// Re-export pipeline debugger API\nexport {\n pipelineDebugAPI,\n mountPipelineDebugger,\n type PipelineRunReport,\n type GeneratorSummary,\n type FilterImpact,\n} from './PipelineDebugger';\n\nimport { LearnableWeight } from '../types/contentNavigationStrategy';\nexport type { ContentNavigationStrategyData, LearnableWeight } from '../types/contentNavigationStrategy';\nimport type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';\nimport { logger } from '../../util/logger';\n\n// ============================================================================\n// NAVIGATOR REGISTRY\n// ============================================================================\n//\n// Static registry of navigator implementations. This allows ContentNavigator.create()\n// to instantiate navigators without relying on dynamic imports, which don't work\n// reliably in all environments (e.g., test runners, bundled code).\n//\n// Usage:\n// 1. Import your navigator class\n// 2. Call registerNavigator('implementingClass', YourNavigatorClass)\n// 3. ContentNavigator.create() will use the registry before falling back to\n// dynamic import\n//\n// ============================================================================\n\n/**\n * Type for navigator constructor functions.\n */\nexport type NavigatorConstructor = new (\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n) => ContentNavigator;\n\n/**\n * Entry in the navigator registry, storing the constructor and an optional\n * pipeline role. The role is used by PipelineAssembler to classify\n * consumer-registered navigators that aren't in the built-in Navigators enum.\n */\ninterface NavigatorRegistryEntry {\n constructor: NavigatorConstructor;\n role?: NavigatorRole;\n}\n\n/**\n * Registry mapping implementingClass names to navigator entries.\n * Populated by registerNavigator() and used by ContentNavigator.create().\n */\nconst navigatorRegistry = new Map<string, NavigatorRegistryEntry>();\n\n/**\n * Register a navigator implementation.\n *\n * Call this to make a navigator available for instantiation by\n * ContentNavigator.create() without relying on dynamic imports.\n *\n * Passing a `role` is optional for built-in navigators (whose roles are in\n * the hardcoded `NavigatorRoles` record), but **required** for consumer-\n * defined navigators that need to participate in pipeline assembly.\n *\n * @param implementingClass - The class name (e.g., 'elo', 'letterGatingFilter')\n * @param constructor - The navigator class constructor\n * @param role - Optional pipeline role (GENERATOR or FILTER)\n */\nexport function registerNavigator(\n implementingClass: string,\n constructor: NavigatorConstructor,\n role?: NavigatorRole\n): void {\n navigatorRegistry.set(implementingClass, { constructor, role });\n logger.debug(`[NavigatorRegistry] Registered: ${implementingClass}${role ? ` (${role})` : ''}`);\n}\n\n/**\n * Get a navigator constructor from the registry.\n *\n * @param implementingClass - The class name to look up\n * @returns The constructor, or undefined if not registered\n */\nexport function getRegisteredNavigator(implementingClass: string): NavigatorConstructor | undefined {\n return navigatorRegistry.get(implementingClass)?.constructor;\n}\n\n/**\n * Check if a navigator is registered.\n *\n * @param implementingClass - The class name to check\n * @returns true if registered, false otherwise\n */\nexport function hasRegisteredNavigator(implementingClass: string): boolean {\n return navigatorRegistry.has(implementingClass);\n}\n\n/**\n * Get the registered role for a navigator, if one was provided at registration.\n *\n * @param implementingClass - The class name to look up\n * @returns The role, or undefined if not registered or no role was specified\n */\nexport function getRegisteredNavigatorRole(implementingClass: string): NavigatorRole | undefined {\n return navigatorRegistry.get(implementingClass)?.role;\n}\n\n/**\n * Get all registered navigator names.\n * Useful for debugging and testing.\n */\nexport function getRegisteredNavigatorNames(): string[] {\n return Array.from(navigatorRegistry.keys());\n}\n\n/**\n * Initialize the navigator registry with all built-in navigators.\n *\n * This function dynamically imports all standard navigator implementations\n * and registers them. Call this once at application startup to ensure\n * all navigators are available.\n *\n * In test environments, this may need to be called explicitly before\n * using ContentNavigator.create().\n */\nexport async function initializeNavigatorRegistry(): Promise<void> {\n logger.debug('[NavigatorRegistry] Initializing built-in navigators...');\n\n // Import and register generators\n const [eloModule, srsModule] = await Promise.all([\n import('./generators/elo'),\n import('./generators/srs'),\n ]);\n const prescribedModule = await import('./generators/prescribed');\n registerNavigator('elo', eloModule.default);\n registerNavigator('srs', srsModule.default);\n registerNavigator('prescribed', prescribedModule.default);\n\n // Import and register filters\n const [\n hierarchyModule,\n interferenceModule,\n relativePriorityModule,\n userTagPreferenceModule,\n ] = await Promise.all([\n import('./filters/hierarchyDefinition'),\n import('./filters/interferenceMitigator'),\n import('./filters/relativePriority'),\n import('./filters/userTagPreference'),\n ]);\n registerNavigator('hierarchyDefinition', hierarchyModule.default);\n registerNavigator('interferenceMitigator', interferenceModule.default);\n registerNavigator('relativePriority', relativePriorityModule.default);\n registerNavigator('userTagPreference', userTagPreferenceModule.default);\n\n // Note: eloDistance uses a factory pattern (createEloDistanceFilter) rather than\n // a ContentNavigator class, so it's not registered here. It's used differently\n // via Pipeline composition.\n\n logger.debug(\n `[NavigatorRegistry] Initialized ${navigatorRegistry.size} navigators: ${getRegisteredNavigatorNames().join(', ')}`\n );\n}\n\n// ============================================================================\n// NAVIGATION STRATEGY API\n// ============================================================================\n//\n// This module defines the ContentNavigator base class and the WeightedCard type,\n// which form the foundation of the pluggable navigation strategy system.\n//\n// KEY CONCEPTS:\n//\n// 1. WeightedCard - A card with a suitability score (0-1) and provenance trail.\n// The provenance tracks how each strategy in the pipeline contributed to\n// the card's final score, ensuring transparency and debuggability.\n//\n// 2. ContentNavigator - Abstract base class for backward compatibility.\n// New code should use CardGenerator or CardFilter interfaces directly.\n//\n// 3. CardGenerator vs CardFilter:\n// - Generators (ELO, SRS) produce candidate cards with scores\n// - Filters (Hierarchy, Interference, Priority, EloDistance) transform scores\n//\n// 4. Pipeline architecture:\n// Pipeline(generator, [filter1, filter2, ...]) executes:\n// cards = generator.getWeightedCards()\n// cards = filter1.transform(cards, context)\n// cards = filter2.transform(cards, context)\n// return sorted(cards)\n//\n// 5. Provenance tracking - Each strategy adds an entry explaining its contribution.\n// This makes the system transparent and debuggable.\n//\n// ============================================================================\n\n/**\n * Tracks a single strategy's contribution to a card's final score.\n *\n * Each strategy in the pipeline adds a StrategyContribution entry to the\n * card's provenance array, creating an audit trail of scoring decisions.\n */\nexport interface StrategyContribution {\n /**\n * Strategy type (implementing class name).\n * Examples: 'elo', 'hierarchyDefinition', 'interferenceMitigator'\n */\n strategy: string;\n\n /**\n * Human-readable name identifying this specific strategy instance.\n * Extracted from ContentNavigationStrategyData.name.\n * Courses may have multiple instances of the same strategy type with\n * different configurations.\n *\n * Examples:\n * - \"ELO (default)\"\n * - \"Interference: b/d/p confusion\"\n * - \"Interference: phonetic confusables\"\n * - \"Priority: Common letters first\"\n */\n strategyName: string;\n\n /**\n * Unique database document ID for this strategy instance.\n * Extracted from ContentNavigationStrategyData._id.\n * Use this to fetch the full strategy configuration document.\n *\n * Examples:\n * - \"NAVIGATION_STRATEGY-ELO-default\"\n * - \"NAVIGATION_STRATEGY-interference-bdp\"\n * - \"NAVIGATION_STRATEGY-priority-common-letters\"\n */\n strategyId: string;\n\n /**\n * What the strategy did:\n * - 'generated': Strategy produced this card (generators only)\n * - 'passed': Strategy evaluated but didn't change score (transparent pass-through)\n * - 'boosted': Strategy increased the score\n * - 'penalized': Strategy decreased the score\n */\n action: 'generated' | 'passed' | 'boosted' | 'penalized';\n\n /** Score after this strategy's processing */\n score: number;\n\n /**\n * The effective weight applied for this strategy instance.\n * If using evolutionary orchestration, this includes deviation.\n * If omitted, implies weight 1.0 (legacy behavior).\n */\n effectiveWeight?: number;\n\n /**\n * The deviation factor applied to this user's cohort for this strategy.\n * Range [-1.0, 1.0].\n */\n deviation?: number;\n\n /**\n * Human-readable explanation of the strategy's decision.\n *\n * Examples:\n * - \"ELO distance 75, new card\"\n * - \"Prerequisites met: letter-sounds\"\n * - \"Interferes with immature tag 'd' (decay 0.8)\"\n * - \"High-priority tag 's' (0.95) → boost 1.15x\"\n *\n * Required for transparency - silent adjusters are anti-patterns.\n */\n reason: string;\n}\n\n/**\n * A card with a suitability score and provenance trail.\n *\n * Scores range from 0-1:\n * - 1.0 = fully suitable\n * - 0.0 = hard filter (e.g., prerequisite not met)\n * - 0.5 = neutral\n * - Intermediate values = soft preference\n *\n * Provenance tracks the scoring pipeline:\n * - First entry: Generator that produced the card\n * - Subsequent entries: Filters that transformed the score\n * - Each entry includes action and human-readable reason\n */\nexport interface WeightedCard {\n cardId: string;\n courseId: string;\n /** Suitability score from 0-1 */\n score: number;\n /**\n * Audit trail of strategy contributions.\n * First entry is from the generator, subsequent entries from filters.\n */\n provenance: StrategyContribution[];\n /**\n * Pre-fetched tags. Populated by Pipeline before filters run.\n * Filters should use this instead of querying getAppliedTags() individually.\n */\n tags?: string[];\n /**\n * Review document ID (_id from ScheduledCard).\n * Present when this card originated from SRS review scheduling.\n * Used by SessionController to track review outcomes and maintain review state.\n */\n reviewID?: string;\n}\n\n/**\n * Extract card origin from provenance trail.\n *\n * The first provenance entry (from the generator) indicates whether\n * this is a new card, review, or failed card. We parse the reason\n * string to extract this information.\n *\n * @param card - Card with provenance trail\n * @returns Card origin ('new', 'review', or 'failed')\n */\nexport function getCardOrigin(card: WeightedCard): 'new' | 'review' | 'failed' {\n if (card.provenance.length === 0) {\n throw new Error('Card has no provenance - cannot determine origin');\n }\n\n const firstEntry = card.provenance[0];\n const reason = firstEntry.reason.toLowerCase();\n\n if (reason.includes('failed')) {\n return 'failed';\n }\n if (reason.includes('review')) {\n return 'review';\n }\n return 'new';\n}\n\nexport enum Navigators {\n ELO = 'elo',\n SRS = 'srs',\n PRESCRIBED = 'prescribed',\n HIERARCHY = 'hierarchyDefinition',\n INTERFERENCE = 'interferenceMitigator',\n RELATIVE_PRIORITY = 'relativePriority',\n USER_TAG_PREFERENCE = 'userTagPreference',\n}\n\n// ============================================================================\n// NAVIGATOR ROLE CLASSIFICATION\n// ============================================================================\n//\n// Navigators are classified as either generators or filters:\n// - Generators: Produce candidate cards (ELO, SRS)\n// - Filters: Transform/score candidates (Hierarchy, Interference, RelativePriority)\n//\n// This classification is used by PipelineAssembler to build pipelines:\n// 1. Instantiate generators (possibly into a CompositeGenerator)\n// 2. Instantiate filters\n// 3. Create Pipeline(generator, filters)\n//\n// ============================================================================\n\n/**\n * Role classification for navigation strategies.\n *\n * - GENERATOR: Produces candidate cards with initial scores\n * - FILTER: Transforms cards with score multipliers\n */\nexport enum NavigatorRole {\n GENERATOR = 'generator',\n FILTER = 'filter',\n}\n\n/**\n * Registry mapping navigator implementations to their roles.\n */\nexport const NavigatorRoles: Record<Navigators, NavigatorRole> = {\n [Navigators.ELO]: NavigatorRole.GENERATOR,\n [Navigators.SRS]: NavigatorRole.GENERATOR,\n [Navigators.PRESCRIBED]: NavigatorRole.GENERATOR,\n [Navigators.HIERARCHY]: NavigatorRole.FILTER,\n [Navigators.INTERFERENCE]: NavigatorRole.FILTER,\n [Navigators.RELATIVE_PRIORITY]: NavigatorRole.FILTER,\n [Navigators.USER_TAG_PREFERENCE]: NavigatorRole.FILTER,\n};\n\n/**\n * Check if a navigator implementation is a generator.\n *\n * @param impl - Navigator implementation name (e.g., 'elo', 'hierarchyDefinition')\n * @returns true if the navigator is a generator, false otherwise\n */\nexport function isGenerator(impl: string): boolean {\n if (NavigatorRoles[impl as Navigators] === NavigatorRole.GENERATOR) return true;\n // Fallback: check the registry for consumer-registered navigators\n return getRegisteredNavigatorRole(impl) === NavigatorRole.GENERATOR;\n}\n\n/**\n * Check if a navigator implementation is a filter.\n *\n * Checks the built-in NavigatorRoles enum first, then falls back to the\n * navigator registry for consumer-registered navigators.\n *\n * @param impl - Navigator implementation name (e.g., 'elo', 'letterGatingFilter')\n * @returns true if the navigator is a filter, false otherwise\n */\nexport function isFilter(impl: string): boolean {\n if (NavigatorRoles[impl as Navigators] === NavigatorRole.FILTER) return true;\n // Fallback: check the registry for consumer-registered navigators\n return getRegisteredNavigatorRole(impl) === NavigatorRole.FILTER;\n}\n\n/**\n * Abstract base class for navigation strategies.\n *\n * This class exists primarily for backward compatibility with legacy code.\n * New code should use CardGenerator or CardFilter interfaces directly.\n *\n * The class implements StudyContentSource for compatibility with SessionController.\n * Once SessionController migrates to use getWeightedCards() exclusively,\n * the legacy methods can be removed.\n */\nexport abstract class ContentNavigator implements StudyContentSource {\n /** User interface for this navigation session */\n protected user: UserDBInterface;\n\n /** Course interface for this navigation session */\n protected course: CourseDBInterface;\n\n /** Human-readable name for this strategy instance (from ContentNavigationStrategyData.name) */\n protected strategyName?: string;\n\n /** Unique document ID for this strategy instance (from ContentNavigationStrategyData._id) */\n protected strategyId?: string;\n\n /** Evolutionary weighting configuration */\n public learnable?: LearnableWeight;\n\n /** Whether to bypass deviation (manual/static weighting) */\n public staticWeight?: boolean;\n\n /**\n * Constructor for standard navigators.\n * Call this from subclass constructors to initialize common fields.\n *\n * Note: CompositeGenerator and Pipeline call super() without args, then set\n * user/course fields directly if needed.\n */\n constructor(\n user?: UserDBInterface,\n course?: CourseDBInterface,\n strategyData?: ContentNavigationStrategyData\n ) {\n this.user = user!;\n this.course = course!;\n if (strategyData) {\n this.strategyName = strategyData.name;\n this.strategyId = strategyData._id;\n this.learnable = strategyData.learnable;\n this.staticWeight = strategyData.staticWeight;\n }\n }\n\n // ============================================================================\n // STRATEGY STATE HELPERS\n // ============================================================================\n //\n // These methods allow strategies to persist their own state (user preferences,\n // learned patterns, temporal tracking) in the user database.\n //\n // ============================================================================\n\n /**\n * Unique key identifying this strategy for state storage.\n *\n * Defaults to the constructor name (e.g., \"UserTagPreferenceFilter\").\n * Override in subclasses if multiple instances of the same strategy type\n * need separate state storage.\n */\n protected get strategyKey(): string {\n return this.constructor.name;\n }\n\n /**\n * Get this strategy's persisted state for the current course.\n *\n * @returns The strategy's data payload, or null if no state exists\n * @throws Error if user or course is not initialized\n */\n protected async getStrategyState<T>(): Promise<T | null> {\n if (!this.user || !this.course) {\n throw new Error(\n `Cannot get strategy state: navigator not properly initialized. ` +\n `Ensure user and course are provided to constructor.`\n );\n }\n return this.user.getStrategyState<T>(this.course.getCourseID(), this.strategyKey);\n }\n\n /**\n * Persist this strategy's state for the current course.\n *\n * @param data - The strategy's data payload to store\n * @throws Error if user or course is not initialized\n */\n protected async putStrategyState<T>(data: T): Promise<void> {\n if (!this.user || !this.course) {\n throw new Error(\n `Cannot put strategy state: navigator not properly initialized. ` +\n `Ensure user and course are provided to constructor.`\n );\n }\n return this.user.putStrategyState<T>(this.course.getCourseID(), this.strategyKey, data);\n }\n\n /**\n * Factory method to create navigator instances.\n *\n * First checks the navigator registry for a pre-registered constructor.\n * If not found, falls back to dynamic import (for custom navigators).\n *\n * For reliable operation in test environments, call initializeNavigatorRegistry()\n * before using this method.\n *\n * @param user - User interface\n * @param course - Course interface\n * @param strategyData - Strategy configuration document\n * @returns the runtime object used to steer a study session.\n */\n static async create(\n user: UserDBInterface,\n course: CourseDBInterface,\n strategyData: ContentNavigationStrategyData\n ): Promise<ContentNavigator> {\n const implementingClass = strategyData.implementingClass;\n\n // First, check the registry for a pre-registered constructor\n const RegisteredImpl = getRegisteredNavigator(implementingClass);\n if (RegisteredImpl) {\n logger.debug(`[ContentNavigator.create] Using registered navigator: ${implementingClass}`);\n return new RegisteredImpl(user, course, strategyData);\n }\n\n // Fall back to dynamic import for custom/unknown navigators\n logger.debug(\n `[ContentNavigator.create] Navigator not in registry, attempting dynamic import: ${implementingClass}`\n );\n\n let NavigatorImpl;\n\n // Try different extension variations\n const variations = ['.ts', '.js', ''];\n\n for (const ext of variations) {\n // Try generators directory\n try {\n const module = await import(`./generators/${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n if (NavigatorImpl) break;\n } catch (e) {\n logger.debug(`Failed to load generator ${implementingClass}${ext}:`, e);\n }\n\n // Try filters directory\n try {\n const module = await import(`./filters/${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n if (NavigatorImpl) break;\n } catch (e) {\n logger.debug(`Failed to load filter ${implementingClass}${ext}:`, e);\n }\n\n // Try current directory (legacy)\n try {\n const module = await import(`./${implementingClass}${ext}`);\n NavigatorImpl = module.default;\n if (NavigatorImpl) break;\n } catch (e) {\n logger.debug(`Failed to load legacy ${implementingClass}${ext}:`, e);\n }\n \n if (NavigatorImpl) break;\n }\n\n if (!NavigatorImpl) {\n throw new Error(`Could not load navigator implementation for: ${implementingClass}`);\n }\n\n return new NavigatorImpl(user, course, strategyData);\n }\n\n /**\n * Get cards with suitability scores and provenance trails.\n *\n * **This is the PRIMARY API for navigation strategies.**\n *\n * Returns cards ranked by suitability score (0-1). Higher scores indicate\n * better candidates for presentation. Each card includes a provenance trail\n * documenting how strategies contributed to the final score.\n *\n * ## Implementation Required\n * All navigation strategies MUST override this method. The base class does\n * not provide a default implementation.\n *\n * ## For Generators\n * Override this method to generate candidates and compute scores based on\n * your strategy's logic (e.g., ELO proximity, review urgency). Create the\n * initial provenance entry with action='generated'.\n *\n * ## For Filters\n * Filters should implement the CardFilter interface instead and be composed\n * via Pipeline. Filters do not directly implement getWeightedCards().\n *\n * @param limit - Maximum cards to return\n * @returns Cards sorted by score descending, with provenance trails\n */\n async getWeightedCards(_limit: number): Promise<GeneratorResult> {\n throw new Error(`${this.constructor.name} must implement getWeightedCards(). `);\n }\n\n /**\n * Set ephemeral hints for the next pipeline run.\n * No-op for non-Pipeline navigators. Pipeline overrides this.\n */\n setEphemeralHints(_hints: ReplanHints): void {\n // no-op — only Pipeline implements this\n }\n}\n","import { CourseDBInterface, CourseInfo, CoursesDBInterface, UserDBInterface } from '@db/core';\nimport type { GeneratorResult, ReplanHints } from '@db/core/navigators/generators/types';\nimport {\n CourseConfig,\n CourseElo,\n DataShape,\n EloToNumber,\n Status,\n blankCourseElo,\n toCourseElo,\n} from '@vue-skuilder/common';\n\nimport { filterAllDocsByPrefix, getCourseDB } from '.';\nimport UpdateQueue from './updateQueue';\nimport { StudySessionItem } from '../../core/interfaces/contentSource';\nimport {\n CardData,\n DocType,\n QualifiedCardID,\n SkuilderCourseData,\n Tag,\n TagStub,\n DocTypePrefixes,\n} from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { GET_CACHED } from './clientCache';\nimport { addNote55, addTagToCard, getCredentialledCourseConfig, getTagID } from './courseAPI';\nimport { DataLayerResult } from '@db/core/types/db';\nimport { PouchError } from './types';\nimport CourseLookup from './courseLookupDB';\nimport { ContentNavigationStrategyData } from '@db/core/types/contentNavigationStrategy';\nimport { ContentNavigator, Navigators } from '@db/core/navigators';\nimport { PipelineAssembler } from '@db/core/navigators/PipelineAssembler';\nimport { createDefaultPipeline } from '@db/core/navigators/defaults';\n\nexport class CoursesDB implements CoursesDBInterface {\n _courseIDs: string[] | undefined;\n\n constructor(courseIDs?: string[]) {\n if (courseIDs && courseIDs.length > 0) {\n this._courseIDs = courseIDs;\n } else {\n this._courseIDs = undefined;\n }\n }\n\n public async getCourseList(): Promise<CourseConfig[]> {\n let crsList = await CourseLookup.allCourseWare();\n logger.debug(`AllCourses: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n if (this._courseIDs) {\n crsList = crsList.filter((c) => this._courseIDs!.includes(c._id));\n }\n\n logger.debug(`AllCourses.filtered: ${crsList.map((c) => c.name + ', ' + c._id + '\\n\\t')}`);\n\n const cfgs = await Promise.all(\n crsList.map(async (c) => {\n try {\n const cfg = await getCredentialledCourseConfig(c._id);\n logger.debug(`Found cfg: ${JSON.stringify(cfg)}`);\n return cfg;\n } catch (e) {\n logger.warn(`Error fetching cfg for course ${c.name}, ${c._id}: ${e}`);\n return undefined;\n }\n })\n );\n return cfgs.filter((c) => !!c);\n }\n\n async getCourseConfig(courseId: string): Promise<CourseConfig> {\n if (this._courseIDs && this._courseIDs.length && !this._courseIDs.includes(courseId)) {\n throw new Error(`Course ${courseId} not in course list`);\n }\n\n const cfg = await getCredentialledCourseConfig(courseId);\n if (cfg === undefined) {\n throw new Error(`Error fetching cfg for course ${courseId}`);\n } else {\n return cfg;\n }\n }\n\n public async disambiguateCourse(courseId: string, disambiguator: string): Promise<void> {\n await CourseLookup.updateDisambiguator(courseId, disambiguator);\n }\n}\n\nfunction randIntWeightedTowardZero(n: number) {\n return Math.floor(Math.random() * Math.random() * Math.random() * n);\n}\n\nexport class CourseDB implements CourseDBInterface {\n // private log(msg: string): void {\n // log(`CourseLog: ${this.id}\\n ${msg}`);\n // }\n\n /**\n * Primary database handle used for all **read** operations (queries, gets).\n *\n * When local sync is active, this points to the local PouchDB replica for\n * fast, network-free reads. Otherwise it points to the remote CouchDB.\n */\n private db: PouchDB.Database;\n\n /**\n * Remote database handle used for all **write** operations.\n *\n * Always points to the remote CouchDB so that writes (ELO updates, tag\n * mutations, admin operations) aggregate on the server. The local replica\n * is a read-only snapshot that refreshes on the next page load.\n *\n * When local sync is NOT active, this is the same instance as `this.db`.\n */\n private remoteDB: PouchDB.Database;\n\n private id: string;\n private _getCurrentUser: () => Promise<UserDBInterface>;\n private updateQueue: UpdateQueue;\n\n /**\n * @param id - Course ID\n * @param userLookup - Async function returning the current user DB\n * @param localDB - Optional local PouchDB replica for reads. When provided,\n * `this.db` uses the local replica and `this.remoteDB` stays remote.\n * The UpdateQueue reads from remote and writes to remote (local `_rev`\n * values may be stale, so read-modify-write cycles must go through\n * the remote DB to avoid conflicts).\n */\n constructor(id: string, userLookup: () => Promise<UserDBInterface>, localDB?: PouchDB.Database) {\n this.id = id;\n const remote = getCourseDB(this.id);\n this.remoteDB = remote;\n this.db = localDB ?? remote;\n this._getCurrentUser = userLookup;\n // UpdateQueue always operates against the remote DB for its\n // read-modify-write cycle. Local _rev values may be stale (the local\n // replica is a snapshot), so conflict retries must read from remote.\n this.updateQueue = new UpdateQueue(this.remoteDB, this.remoteDB);\n }\n\n public getCourseID(): string {\n return this.id;\n }\n\n public async getCourseInfo(): Promise<CourseInfo> {\n const cardCount = (\n await this.db.find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n })\n ).docs.length;\n\n return {\n cardCount,\n registeredUsers: 0,\n };\n }\n\n public async getInexperiencedCards(limit: number = 2) {\n return (\n await this.db.query('cardsByInexperience', {\n limit,\n })\n ).rows.map((r) => {\n const ret = {\n courseId: this.id,\n cardId: r.id,\n count: r.key,\n elo: r.value,\n };\n return ret;\n });\n }\n\n public async getCardsByEloLimits(\n options: {\n low: number;\n high: number;\n limit: number;\n page: number;\n } = {\n low: 0,\n high: Number.MIN_SAFE_INTEGER,\n limit: 25,\n page: 0,\n }\n ) {\n return (\n await this.db.query('elo', {\n startkey: options.low,\n endkey: options.high,\n limit: options.limit,\n skip: options.limit * options.page,\n })\n ).rows.map((r) => {\n return `${this.id}-${r.id}-${r.key}`;\n });\n }\n public async getCardEloData(id: string[]): Promise<CourseElo[]> {\n const docs = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: CourseElo[] = [];\n docs.rows.forEach((r) => {\n // [ ] remove these ts-ignore directives.\n if (isSuccessRow(r)) {\n if (r.doc && r.doc.elo) {\n ret.push(toCourseElo(r.doc.elo));\n } else {\n logger.warn('no elo data for card: ' + r.id);\n ret.push(blankCourseElo());\n }\n } else {\n logger.warn('no elo data for card: ' + JSON.stringify(r));\n ret.push(blankCourseElo());\n }\n });\n return ret;\n }\n\n /**\n * Returns the lowest and highest `global` ELO ratings in the course\n */\n public async getELOBounds() {\n const [low, high] = await Promise.all([\n (\n await this.db.query('elo', {\n startkey: 0,\n limit: 1,\n include_docs: false,\n })\n ).rows[0].key,\n (\n await this.db.query('elo', {\n limit: 1,\n descending: true,\n startkey: 100_000,\n })\n ).rows[0].key,\n ]);\n\n return {\n low: low,\n high: high,\n };\n }\n\n public async removeCard(id: string) {\n // Admin operation — read and write both go through remote DB to ensure\n // we have the current _rev for the delete.\n const doc = await this.remoteDB.get<CardData>(id);\n if (!doc.docType || !(doc.docType === DocType.CARD)) {\n throw new Error(`failed to remove ${id} from course ${this.id}. id does not point to a card`);\n }\n\n // Remove card from all associated tags before deleting the card\n try {\n const appliedTags = await this.getAppliedTags(id);\n const results = await Promise.allSettled(\n appliedTags.rows.map(async (tagRow) => {\n const tagId = tagRow.id;\n await this.removeTagFromCard(id, tagId);\n })\n );\n\n // Log any individual tag cleanup failures\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const tagId = appliedTags.rows[index].id;\n logger.error(`Failed to remove card ${id} from tag ${tagId}: ${result.reason}`);\n }\n });\n } catch (error) {\n logger.error(`Error removing card ${id} from tags: ${error}`);\n // Continue with card deletion even if tag cleanup fails\n }\n\n return this.remoteDB.remove(doc);\n }\n\n public async getCardDisplayableDataIDs(id: string[]) {\n logger.debug(id.join(', '));\n const cards = await this.db.allDocs<CardData>({\n keys: id,\n include_docs: true,\n });\n const ret: { [card: string]: string[] } = {};\n cards.rows.forEach((r) => {\n if (isSuccessRow(r)) {\n ret[r.id] = r.doc!.id_displayable_data;\n }\n });\n\n return ret;\n }\n\n async getCardsByELO(elo: number, cardLimit?: number) {\n elo = parseInt(elo as any);\n const limit = cardLimit ? cardLimit : 25;\n\n const below: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: Math.ceil(limit / 2),\n startkey: elo,\n descending: true,\n });\n\n const aboveLimit = limit - below.rows.length;\n\n const above: PouchDB.Query.Response<object> = await this.db.query('elo', {\n limit: aboveLimit,\n startkey: elo + 1,\n });\n // logger.log(JSON.stringify(below));\n\n let cards = below.rows;\n cards = cards.concat(above.rows);\n\n const ret = cards\n .sort((a, b) => {\n const s = Math.abs(a.key - elo) - Math.abs(b.key - elo);\n if (s === 0) {\n return Math.random() - 0.5;\n } else {\n return s;\n }\n })\n .map((c) => {\n return {\n courseID: this.id,\n cardID: c.id,\n elo: c.key,\n };\n });\n\n const str = `below:\\n${below.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}\n\nabove:\\n${above.rows.map((r) => `\\t${r.id}-${r.key}\\n`)}`;\n\n logger.debug(`Getting ${limit} cards centered around elo: ${elo}:\\n\\n` + str);\n\n return ret;\n }\n\n async getCourseConfig(): Promise<CourseConfig> {\n const ret = await getCredentialledCourseConfig(this.id);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course config not found for course ID: ${this.id}`);\n }\n }\n\n async updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response> {\n logger.debug(`Updating: ${JSON.stringify(cfg)}`);\n // write both to the course DB:\n try {\n return await updateCredentialledCourseConfig(this.id, cfg);\n } catch (error) {\n logger.error(`Error updating course config in course DB: ${error}`);\n throw error;\n }\n }\n\n async updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n if (!elo) {\n throw new Error(`Cannot update card elo with null or undefined value for card ID: ${cardId}`);\n }\n\n try {\n const result = await this.updateQueue.update<\n CardData & PouchDB.Core.GetMeta & PouchDB.Core.IdMeta\n >(cardId, (card) => {\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return card;\n });\n return { ok: true, id: cardId, rev: result._rev };\n } catch (error) {\n logger.error(`Failed to update card elo for card ID: ${cardId}`, error);\n throw new Error(`Failed to update card elo for card ID: ${cardId}`);\n }\n }\n\n async getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>> {\n const ret = await getAppliedTags(this.id, cardId);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Failed to find tags for card ${this.id}-${cardId}`);\n }\n }\n\n async getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>> {\n if (cardIds.length === 0) {\n return new Map();\n }\n\n const result = await this.db.query<TagStub>('getTags', {\n keys: cardIds,\n include_docs: false,\n });\n\n const tagsByCard = new Map<string, string[]>();\n\n // Initialize all requested cards with empty arrays\n for (const cardId of cardIds) {\n tagsByCard.set(cardId, []);\n }\n\n // Populate from query results\n for (const row of result.rows) {\n const cardId = row.key as string;\n const tagName = row.value?.name;\n if (tagName && tagsByCard.has(cardId)) {\n tagsByCard.get(cardId)!.push(tagName);\n }\n }\n\n return tagsByCard;\n }\n\n async getAllCardIds(): Promise<string[]> {\n const result = await this.db.allDocs({\n startkey: 'CARD-',\n endkey: 'CARD-\\ufff0',\n include_docs: false,\n });\n return result.rows.map((row) => row.id);\n }\n\n async addTagToCard(\n cardId: string,\n tagId: string,\n updateELO?: boolean\n ): Promise<PouchDB.Core.Response> {\n return await addTagToCard(\n this.id,\n cardId,\n tagId,\n (await this._getCurrentUser()).getUsername(),\n updateELO\n );\n }\n\n async removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response> {\n return await removeTagFromCard(this.id, cardId, tagId);\n }\n\n async createTag(name: string, author: string): Promise<PouchDB.Core.Response> {\n return await createTag(this.id, name, author);\n }\n\n async getTag(tagId: string): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<Tag>> {\n return await getTag(this.id, tagId);\n }\n\n async updateTag(tag: Tag): Promise<PouchDB.Core.Response> {\n if (tag.course !== this.id) {\n throw new Error(`Tag ${JSON.stringify(tag)} does not belong to course ${this.id}`);\n }\n\n return await updateTag(tag);\n }\n\n async getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n return getCourseTagStubs(this.id);\n }\n\n async addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo: CourseElo = blankCourseElo()\n ): Promise<DataLayerResult> {\n try {\n const resp = await addNote55(this.id, codeCourse, shape, data, author, tags, uploads, elo);\n if (resp.ok) {\n // Check if card creation failed (property added by addNote55)\n if ((resp as any).cardCreationFailed) {\n logger.warn(\n `[courseDB.addNote] Note added but card creation failed: ${\n (resp as any).cardCreationError\n }`\n );\n return {\n status: Status.error,\n message: `Note was added but no cards were created: ${(resp as any).cardCreationError}`,\n id: resp.id,\n };\n }\n return {\n status: Status.ok,\n message: '',\n id: resp.id,\n };\n } else {\n return {\n status: Status.error,\n message: 'Unexpected error adding note',\n };\n }\n } catch (e) {\n const err = e as PouchDB.Core.Error;\n logger.error(\n `[addNote] error ${err.name}\\n\\treason: ${err.reason}\\n\\tmessage: ${err.message}`\n );\n return {\n status: Status.error,\n message: `Error adding note to course. ${(e as PouchError).reason || err.message}`,\n };\n }\n }\n\n async getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<PouchDB.Core.GetMeta & PouchDB.Core.Document<T>> {\n // Use this.db (local when available) for read operations.\n // Falls back to the standalone helper (always remote) only if needed.\n return await this.db.get<T>(id, options) as PouchDB.Core.GetMeta & PouchDB.Core.Document<T>;\n }\n\n async getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>> {\n // Use this.db (local when available) for read operations.\n return await this.db.allDocs<T>({\n ...options,\n keys: ids,\n }) as PouchDB.Core.AllDocsWithKeysResponse<{} & T>;\n }\n\n ////////////////////////////////////\n // NavigationStrategyManager implementation\n ////////////////////////////////////\n\n getNavigationStrategy(id: string): Promise<ContentNavigationStrategyData> {\n logger.debug(`[courseDB] Getting navigation strategy: ${id}`);\n\n if (id == '') {\n const strategy: ContentNavigationStrategyData = {\n _id: 'NAVIGATION_STRATEGY-ELO',\n docType: DocType.NAVIGATION_STRATEGY,\n name: 'ELO',\n description: 'ELO-based navigation strategy for ordering content by difficulty',\n implementingClass: Navigators.ELO,\n course: this.id,\n serializedData: '', // serde is a noop for ELO navigator.\n };\n return Promise.resolve(strategy);\n } else {\n return this.db.get(id);\n }\n }\n\n async getAllNavigationStrategies(): Promise<ContentNavigationStrategyData[]> {\n const prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];\n const result = await this.db.allDocs<ContentNavigationStrategyData>({\n startkey: prefix,\n endkey: `${prefix}\\ufff0`,\n include_docs: true,\n });\n return result.rows.map((row) => row.doc!);\n }\n\n async addNavigationStrategy(data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Adding navigation strategy: ${data._id}`);\n // Admin write operation — use remote DB.\n return this.remoteDB.put(data).then(() => {});\n }\n updateNavigationStrategy(id: string, data: ContentNavigationStrategyData): Promise<void> {\n logger.debug(`[courseDB] Updating navigation strategy: ${id}`);\n // For now, just log the data and return success\n logger.debug(JSON.stringify(data));\n return Promise.resolve();\n }\n\n /**\n * Creates an instantiated navigator for this course.\n *\n * Handles multiple generators by wrapping them in CompositeGenerator.\n * This is the preferred method for getting a ready-to-use navigator.\n *\n * @param user - User database interface\n * @returns Instantiated ContentNavigator ready for use\n */\n async createNavigator(user: UserDBInterface): Promise<ContentNavigator> {\n try {\n const allStrategies = await this.getAllNavigationStrategies();\n\n if (allStrategies.length === 0) {\n // No strategies configured: use default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\n logger.debug(\n '[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])'\n );\n return createDefaultPipeline(user, this);\n }\n\n // Use PipelineAssembler to build a Pipeline from strategy documents\n const assembler = new PipelineAssembler();\n const { pipeline, generatorStrategies, filterStrategies, warnings } =\n await assembler.assemble({\n strategies: allStrategies,\n user,\n course: this,\n });\n\n // Log any warnings from assembly\n for (const warning of warnings) {\n logger.warn(`[PipelineAssembler] ${warning}`);\n }\n\n if (!pipeline) {\n // Assembly failed - fall back to default\n logger.debug('[courseDB] Pipeline assembly failed, using default pipeline');\n return createDefaultPipeline(user, this);\n }\n\n logger.debug(\n `[courseDB] Using assembled pipeline with ${generatorStrategies.length} generator(s) and ${filterStrategies.length} filter(s)`\n );\n return pipeline;\n } catch (e) {\n logger.error(`[courseDB] Error creating navigator: ${e}`);\n throw e;\n }\n }\n\n ////////////////////////////////////\n // END NavigationStrategyManager implementation\n ////////////////////////////////////\n\n ////////////////////////////////////\n // StudyContentSource implementation\n ////////////////////////////////////\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * This is the PRIMARY API for content sources going forward. Delegates to the\n * course's configured NavigationStrategy to get scored candidates.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n private _pendingHints: ReplanHints | null = null;\n\n public setEphemeralHints(hints: ReplanHints): void {\n this._pendingHints = hints;\n }\n\n public async getWeightedCards(limit: number): Promise<GeneratorResult> {\n const u = await this._getCurrentUser();\n\n try {\n const navigator = await this.createNavigator(u);\n if (this._pendingHints) {\n navigator.setEphemeralHints(this._pendingHints);\n this._pendingHints = null;\n }\n return navigator.getWeightedCards(limit);\n } catch (e) {\n logger.error(`[courseDB] Error getting weighted cards: ${e}`);\n throw e;\n }\n }\n\n public async getCardsCenteredAtELO(\n options: {\n limit: number;\n elo: 'user' | 'random' | number;\n } = {\n limit: 99,\n elo: 'user',\n },\n filter?: (a: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]> {\n let targetElo: number;\n\n if (options.elo === 'user') {\n const u = await this._getCurrentUser();\n\n targetElo = -1;\n try {\n const courseDoc = (await u.getCourseRegistrationsDoc()).courses.find((c) => {\n return c.courseID === this.id;\n })!;\n targetElo = EloToNumber(courseDoc.elo);\n } catch {\n targetElo = 1000;\n }\n } else if (options.elo === 'random') {\n const bounds = await GET_CACHED(`elo-bounds-${this.id}`, () => this.getELOBounds());\n targetElo = Math.round(bounds.low + Math.random() * (bounds.high - bounds.low));\n // logger.log(`Picked ${targetElo} from [${bounds.low}, ${bounds.high}]`);\n } else {\n targetElo = options.elo;\n }\n\n let cards: (QualifiedCardID & { elo?: number })[] = [];\n let mult: number = 4;\n let previousCount: number = -1;\n let newCount: number = 0;\n\n while (cards.length < options.limit && newCount !== previousCount) {\n cards = await this.getCardsByELO(targetElo, mult * options.limit);\n previousCount = newCount;\n newCount = cards.length;\n\n logger.debug(`Found ${cards.length} elo neighbor cards...`);\n\n if (filter) {\n cards = cards.filter(filter);\n logger.debug(`Filtered to ${cards.length} cards...`);\n }\n\n mult *= 2;\n }\n\n const selectedCards: {\n courseID: string;\n cardID: string;\n elo?: number;\n }[] = [];\n\n while (selectedCards.length < options.limit && cards.length > 0) {\n const index = randIntWeightedTowardZero(cards.length);\n const card = cards.splice(index, 1)[0];\n selectedCards.push(card);\n }\n\n return selectedCards.map((c) => {\n return {\n courseID: this.id,\n cardID: c.cardID,\n contentSourceType: 'course',\n contentSourceID: this.id,\n elo: c.elo,\n status: 'new',\n };\n });\n }\n\n // Admin search methods\n public async searchCards(query: string): Promise<any[]> {\n logger.log(`[CourseDB ${this.id}] Searching for: \"${query}\"`);\n\n // Try multiple search approaches\n let displayableData;\n\n try {\n // Try regex search on the correct data structure: data[0].data\n displayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n 'data.0.data': { $regex: `.*${query}.*` },\n },\n });\n logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`);\n } catch (regexError) {\n logger.log(\n `[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,\n regexError\n );\n\n // Fallback: get all displayable data and filter manually\n const allDisplayable = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Retrieved ${allDisplayable.docs.length} documents for manual filtering`\n );\n\n displayableData = {\n docs: allDisplayable.docs.filter((doc) => {\n // Search entire document as JSON string - inclusive approach for admin tool\n const docString = JSON.stringify(doc).toLowerCase();\n const match = docString.includes(query.toLowerCase());\n if (match) {\n logger.log(`[CourseDB ${this.id}] Manual match found in document: ${doc._id}`);\n }\n return match;\n }),\n };\n }\n\n logger.log(\n `[CourseDB ${this.id}] Found ${displayableData.docs.length} displayable data documents`\n );\n\n if (displayableData.docs.length === 0) {\n // Debug: Let's see what displayable data exists\n const allDisplayableData = await this.db.find({\n selector: {\n docType: 'DISPLAYABLE_DATA',\n },\n limit: 5, // Just sample a few\n });\n\n logger.log(\n `[CourseDB ${this.id}] Sample displayable data:`,\n allDisplayableData.docs.map((d) => ({\n id: d._id,\n docType: (d as any).docType,\n dataStructure: (d as any).data ? Object.keys((d as any).data) : 'no data field',\n dataContent: (d as any).data,\n fullDoc: d,\n }))\n );\n }\n\n const allResults: any[] = [];\n\n for (const dd of displayableData.docs) {\n const cards = await this.db.find({\n selector: {\n docType: 'CARD',\n id_displayable_data: { $in: [dd._id] },\n },\n });\n\n logger.log(\n `[CourseDB ${this.id}] Displayable data ${dd._id} linked to ${cards.docs.length} cards`\n );\n allResults.push(...cards.docs);\n }\n\n logger.log(`[CourseDB ${this.id}] Total cards found: ${allResults.length}`);\n return allResults;\n }\n\n public async find(\n request: PouchDB.Find.FindRequest<any>\n ): Promise<PouchDB.Find.FindResponse<any>> {\n return this.db.find(request);\n }\n}\n\n/**\n * Returns a list of registered datashapes for the specified\n * course.\n * @param courseID The ID of the course\n */\nexport async function getCourseDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.dataShapes;\n}\n\nexport async function getCredentialledDataShapes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n\n return cfg.dataShapes;\n}\n\nexport async function getCourseQuestionTypes(courseID: string) {\n const cfg = await getCredentialledCourseConfig(courseID);\n return cfg!.questionTypes;\n}\n\n// todo: this is actually returning full tag docs now.\n// - performance issue when tags have lots of\n// applied docs\n// - will require a computed couch DB view\nexport async function getCourseTagStubs(\n courseID: string\n): Promise<PouchDB.Core.AllDocsResponse<Tag>> {\n logger.debug(`Getting tag stubs for course: ${courseID}`);\n const stubs = await filterAllDocsByPrefix<Tag>(\n getCourseDB(courseID),\n DocType.TAG.valueOf() + '-'\n );\n\n stubs.rows.forEach((row) => {\n logger.debug(`\\tTag stub for doc: ${row.id}`);\n });\n\n return stubs;\n}\n\nexport async function deleteTag(courseID: string, tagName: string) {\n tagName = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const doc = await courseDB.get<Tag>(DocType.TAG.valueOf() + '-' + tagName);\n const resp = await courseDB.remove(doc);\n return resp;\n}\n\nexport async function createTag(courseID: string, tagName: string, author: string) {\n logger.debug(`Creating tag: ${tagName}...`);\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n const resp = await courseDB.put<Tag>({\n course: courseID,\n docType: DocType.TAG,\n name: tagName,\n snippet: '',\n taggedCards: [],\n wiki: '',\n author,\n _id: tagID,\n });\n return resp;\n}\n\nexport async function updateTag(tag: Tag) {\n const prior = await getTag(tag.course, tag.name);\n return await getCourseDB(tag.course).put<Tag>({\n ...tag,\n _rev: prior._rev,\n });\n}\n\nexport async function getTag(courseID: string, tagName: string) {\n const tagID = getTagID(tagName);\n const courseDB = getCourseDB(courseID);\n return courseDB.get<Tag>(tagID);\n}\n\nexport async function removeTagFromCard(courseID: string, cardID: string, tagID: string) {\n // todo: possible future perf. hit if tags have large #s of taggedCards.\n // In this case, should be converted to a server-request\n tagID = getTagID(tagID);\n const courseDB = getCourseDB(courseID);\n const tag = await courseDB.get<Tag>(tagID);\n tag.taggedCards = tag.taggedCards.filter((taggedID) => {\n return cardID !== taggedID;\n });\n return courseDB.put<Tag>(tag);\n}\n\n/**\n * Returns an array of ancestor tag IDs, where:\n * return[0] = parent,\n * return[1] = grandparent,\n * return[2] = great grandparent,\n * etc.\n *\n * If ret is empty, the tag itself is a root\n */\nexport function getAncestorTagIDs(courseID: string, tagID: string): string[] {\n tagID = getTagID(tagID);\n const split = tagID.split('>');\n if (split.length === 1) {\n return [];\n } else {\n split.pop();\n const parent = split.join('>');\n return [parent].concat(getAncestorTagIDs(courseID, parent));\n }\n}\n\nexport async function getChildTagStubs(courseID: string, tagID: string) {\n return await filterAllDocsByPrefix(getCourseDB(courseID), tagID + '>');\n}\n\nexport async function getAppliedTags(id_course: string, id_card: string) {\n const db = getCourseDB(id_course);\n\n const result = await db.query<TagStub>('getTags', {\n startkey: id_card,\n endkey: id_card,\n // include_docs: true\n });\n\n // log(`getAppliedTags looked up: ${id_card}`);\n // log(`getAppliedTags returning: ${JSON.stringify(result)}`);\n\n return result;\n}\n\nexport async function updateCardElo(courseID: string, cardID: string, elo: CourseElo) {\n if (elo) {\n // checking against null, undefined, NaN\n const cDB = getCourseDB(courseID);\n const card = await cDB.get<CardData>(cardID);\n logger.debug(`Replacing ${JSON.stringify(card.elo)} with ${JSON.stringify(elo)}`);\n card.elo = elo;\n return cDB.put(card); // race conditions - is it important? probably not (net-zero effect)\n }\n}\n\nexport async function updateCredentialledCourseConfig(courseID: string, config: CourseConfig) {\n logger.debug(`Updating course config:\n\n${JSON.stringify(config)}\n`);\n\n const db = getCourseDB(courseID);\n const old = await getCredentialledCourseConfig(courseID);\n\n return await db.put<CourseConfig>({\n ...config,\n _rev: (old as any)._rev,\n });\n}\n\nfunction isSuccessRow<T>(\n row:\n | {\n key: PouchDB.Core.DocumentKey;\n error: 'not_found';\n }\n | {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n }\n): row is {\n doc?: PouchDB.Core.ExistingDocument<PouchDB.Core.AllDocsMeta & T> | null | undefined;\n id: PouchDB.Core.DocumentId;\n key: PouchDB.Core.DocumentKey;\n value: {\n rev: PouchDB.Core.RevisionId;\n deleted?: boolean | undefined;\n };\n} {\n return 'doc' in row && row.doc !== null && row.doc !== undefined;\n}\n","import { StudyContentSource } from '@db/core/interfaces/contentSource';\nimport { WeightedCard } from '@db/core/navigators';\nimport type { GeneratorResult } from '@db/core/navigators/generators/types';\nimport { ClassroomConfig } from '@vue-skuilder/common';\nimport { ENV } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport moment from 'moment';\nimport pouch from './pouchdb-setup';\nimport { getStartAndEndKeys, createPouchDBConfig, REVIEW_TIME_FORMAT } from '.';\nimport { CourseDB, getTag } from './courseDB';\n\nimport { UserDBInterface } from '@db/core';\nimport {\n AssignedContent,\n AssignedCourse,\n AssignedTag,\n StudentClassroomDBInterface,\n TeacherClassroomDBInterface,\n} from '@db/core/interfaces/classroomDB';\n\nconst classroomLookupDBTitle = 'classdb-lookup';\nexport const CLASSROOM_CONFIG = 'ClassroomConfig';\n\nexport type ClassroomMessage = object;\n\nabstract class ClassroomDBBase {\n public _id!: string;\n protected _db!: PouchDB.Database;\n protected _cfg!: ClassroomConfig;\n protected _initComplete: boolean = false;\n\n protected readonly _content_prefix: string = 'content';\n protected get _content_searchkeys() {\n return getStartAndEndKeys(this._content_prefix);\n }\n\n protected abstract init(): Promise<void>;\n\n public async getAssignedContent(): Promise<AssignedContent[]> {\n logger.info(`Getting assigned content...`);\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const docRows = await this._db.allDocs<AssignedContent>({\n startkey: this._content_prefix,\n endkey: this._content_prefix + `\\ufff0`,\n include_docs: true,\n });\n\n const ret = docRows.rows.map((row) => {\n return row.doc!;\n });\n // logger.info(`Assigned content: ${JSON.stringify(ret)}`);\n\n return ret;\n }\n\n protected getContentId(content: AssignedContent): string {\n if (content.type === 'tag') {\n return `${this._content_prefix}-${content.courseID}-${content.tagID}`;\n } else {\n return `${this._content_prefix}-${content.courseID}`;\n }\n }\n\n public get ready(): boolean {\n return this._initComplete;\n }\n public getConfig(): ClassroomConfig {\n return this._cfg;\n }\n}\n\nexport class StudentClassroomDB\n extends ClassroomDBBase\n implements StudyContentSource, StudentClassroomDBInterface\n{\n // private readonly _prefix: string = 'content';\n private userMessages!: PouchDB.Core.Changes<object>;\n private _user: UserDBInterface;\n\n private constructor(classID: string, user: UserDBInterface) {\n super();\n this._id = classID;\n this._user = user;\n // init() is called explicitly in factory method, not in constructor\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n try {\n const cfg = await this._db.get<ClassroomConfig>(CLASSROOM_CONFIG);\n this._cfg = cfg;\n this.userMessages = this._db.changes({\n since: 'now',\n live: true,\n include_docs: true,\n });\n this._initComplete = true;\n return;\n } catch (e) {\n throw new Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string, user: UserDBInterface): Promise<StudentClassroomDB> {\n const ret = new StudentClassroomDB(classID, user);\n await ret.init();\n return ret;\n }\n\n public setChangeFcn(f: (value: unknown) => object): void {\n // todo: make this into a view request, w/ the user's name attached\n // todo: requires creating the view doc on classroom create in /express\n void this.userMessages.on('change', f);\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * Gathers new cards from assigned content (courses, tags, cards) and\n * pending reviews scheduled for this classroom. Assigns score=1.0 to all.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending (all scores = 1.0)\n */\n public async getWeightedCards(limit: number): Promise<GeneratorResult> {\n const weighted: WeightedCard[] = [];\n\n // Get pending reviews for this classroom\n const allUserReviews = await this._user.getPendingReviews();\n const classroomReviews = allUserReviews.filter(\n (r) => r.scheduledFor === 'classroom' && r.schedulingAgentId === this._id\n );\n\n for (const r of classroomReviews) {\n weighted.push({\n cardId: r.cardId,\n courseId: r.courseId,\n score: 1.0,\n reviewID: r._id,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Classroom scheduled review',\n },\n ],\n });\n }\n\n // Get new cards from assigned content\n const activeCards = await this._user.getActiveCards();\n const activeCardIds = new Set(activeCards.map((ac) => ac.cardID));\n const now = moment.utc();\n const assigned = await this.getAssignedContent();\n const due = assigned.filter((c) => now.isAfter(moment.utc(c.activeOn, REVIEW_TIME_FORMAT)));\n\n logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(due)}`);\n\n for (const content of due) {\n if (content.type === 'course') {\n // Get weighted cards from the course directly\n const db = new CourseDB(content.courseID, async () => this._user);\n const { cards: courseCards } = await db.getWeightedCards(limit);\n for (const card of courseCards) {\n if (!activeCardIds.has(card.cardId)) {\n weighted.push({\n ...card,\n provenance: [\n ...card.provenance,\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'passed' as const,\n score: card.score,\n reason: `Assigned via classroom from course ${content.courseID}`,\n },\n ],\n });\n }\n }\n } else if (content.type === 'tag') {\n const tagDoc = await getTag(content.courseID, content.tagID);\n\n for (const cardId of tagDoc.taggedCards) {\n if (!activeCardIds.has(cardId)) {\n weighted.push({\n cardId,\n courseId: content.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: `Classroom assigned tag: ${content.tagID}, new card`,\n },\n ],\n });\n }\n }\n } else if (content.type === 'card') {\n if (!activeCardIds.has(content.cardID)) {\n weighted.push({\n cardId: content.cardID,\n courseId: content.courseID,\n score: 1.0,\n provenance: [\n {\n strategy: 'classroom',\n strategyName: 'Classroom',\n strategyId: 'CLASSROOM',\n action: 'generated' as const,\n score: 1.0,\n reason: 'Classroom assigned card, new card',\n },\n ],\n });\n }\n }\n }\n\n logger.info(\n `[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ` +\n `${weighted.length} total (reviews + new)`\n );\n\n // Sort by score descending and limit\n return { cards: weighted.sort((a, b) => b.score - a.score).slice(0, limit) };\n }\n}\n\n/**\n * Interface for managing a classroom.\n */\nexport class TeacherClassroomDB extends ClassroomDBBase implements TeacherClassroomDBInterface {\n private _stuDb!: PouchDB.Database;\n\n private constructor(classID: string) {\n super();\n this._id = classID;\n }\n\n async init(): Promise<void> {\n const dbName = `classdb-teacher-${this._id}`;\n const stuDbName = `classdb-student-${this._id}`;\n this._db = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n this._stuDb = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + stuDbName,\n createPouchDBConfig()\n );\n try {\n return this._db\n .get<ClassroomConfig>(CLASSROOM_CONFIG)\n .then((cfg) => {\n this._cfg = cfg;\n this._initComplete = true;\n })\n .then(() => {\n return;\n });\n } catch (e) {\n throw new Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`);\n }\n }\n\n public static async factory(classID: string): Promise<TeacherClassroomDB> {\n const ret = new TeacherClassroomDB(classID);\n await ret.init();\n return ret;\n }\n\n public async removeContent(content: AssignedContent): Promise<void> {\n const contentID = this.getContentId(content);\n\n try {\n const doc = await this._db.get(contentID);\n await this._db.remove(doc);\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [contentID],\n });\n } catch (error) {\n logger.error('Failed to remove content:', contentID, error);\n }\n }\n\n public async assignContent(content: AssignedContent): Promise<boolean> {\n let put: PouchDB.Core.Response;\n const id: string = this.getContentId(content);\n\n if (content.type === 'tag') {\n put = await this._db.put<AssignedTag>({\n courseID: content.courseID,\n tagID: content.tagID,\n type: 'tag',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n } else {\n put = await this._db.put<AssignedCourse>({\n courseID: content.courseID,\n type: 'course',\n _id: id,\n assignedBy: content.assignedBy,\n assignedOn: moment.utc(),\n activeOn: content.activeOn || moment.utc(),\n });\n }\n\n if (put.ok) {\n void this._db.replicate.to(this._stuDb, {\n doc_ids: [id],\n });\n return true;\n } else {\n return false;\n }\n }\n}\n\nexport const ClassroomLookupDB: () => PouchDB.Database = () =>\n new pouch(ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + classroomLookupDBTitle, {\n skip_setup: true,\n });\n\nexport function getClassroomDB(classID: string, version: 'student' | 'teacher'): PouchDB.Database {\n const dbName = `classdb-${version}-${classID}`;\n logger.info(`Retrieving classroom db: ${dbName}`);\n\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport async function getClassroomConfig(classID: string): Promise<ClassroomConfig> {\n return await getClassroomDB(classID, 'student').get<ClassroomConfig>(CLASSROOM_CONFIG);\n}\n","import pouch from './pouchdb-setup';\nimport { ENV } from '@db/factory';\nimport {\n createPouchDBConfig,\n getStartAndEndKeys,\n getCredentialledCourseConfig,\n updateCredentialledCourseConfig,\n} from '.';\nimport { TeacherClassroomDB, ClassroomLookupDB } from './classroomDB';\nimport { PouchError } from './types';\n\nimport { AdminDBInterface } from '@db/core';\nimport CourseLookup from './courseLookupDB';\nimport { logger } from '@db/util/logger';\n\nexport class AdminDB implements AdminDBInterface {\n private usersDB!: PouchDB.Database;\n\n constructor() {\n // [ ] execute a check here against credentials, and throw an error\n // if the user is not an admin\n this.usersDB = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + '_users',\n createPouchDBConfig()\n );\n }\n\n public async getUsers() {\n return (\n await this.usersDB.allDocs({\n include_docs: true,\n ...getStartAndEndKeys('org.couchdb.user:'),\n })\n ).rows.map((r) => r.doc!);\n }\n\n public async getCourses() {\n const list = await CourseLookup.allCourseWare();\n return await Promise.all(\n list.map((c) => {\n return getCredentialledCourseConfig(c._id);\n })\n );\n }\n public async removeCourse(id: string) {\n // remove the indexer\n const delResp = await CourseLookup.delete(id);\n\n // set the 'CourseConfig' to 'deleted'\n const cfg = await getCredentialledCourseConfig(id);\n cfg.deleted = true;\n const isDeletedResp = await updateCredentialledCourseConfig(id, cfg);\n\n return {\n ok: delResp.ok && isDeletedResp.ok,\n id: delResp.id,\n rev: delResp.rev,\n };\n }\n\n public async getClassrooms() {\n // const joincodes =\n const uuids = (\n await ClassroomLookupDB().allDocs<{ uuid: string }>({\n include_docs: true,\n })\n ).rows.map((r) => r.doc!.uuid);\n logger.debug(uuids.join(', '));\n\n const promisedCRDbs: TeacherClassroomDB[] = [];\n for (let i = 0; i < uuids.length; i++) {\n try {\n const db = await TeacherClassroomDB.factory(uuids[i]);\n promisedCRDbs.push(db);\n } catch (e) {\n const err = e as PouchError;\n if (err.error && err.error === 'not_found') {\n logger.warn(`db ${uuids[i]} not found`);\n }\n }\n }\n\n return promisedCRDbs.map((db) => {\n return {\n ...db.getConfig(),\n _id: db._id,\n };\n });\n }\n}\n","import pouch from './pouchdb-setup';\nimport { getCourseDB } from '.';\nimport { logger } from '../../util/logger';\nimport type { CourseConfig } from '@vue-skuilder/common';\n\n// ============================================================================\n// COURSE SYNC SERVICE\n// ============================================================================\n//\n// Manages client-side PouchDB replicas of course databases.\n//\n// Courses opt in to local sync via CourseConfig.localSync.enabled. When\n// enabled, the service performs a one-shot replication from remote CouchDB\n// to a local PouchDB on first visit, then incremental sync on subsequent\n// visits. Pipeline scoring, tag hydration, and card lookup then run against\n// the local replica — eliminating network round trips from the study-session\n// hot path.\n//\n// Read/write split:\n// Local DB = read-only snapshot (pipeline, filters, card lookup)\n// Remote DB = all writes (ELO updates, tag mutations, admin ops)\n//\n// This avoids propagating per-interaction ELO write noise to every syncing\n// client. Each client's local snapshot refreshes on the next page load.\n//\n// Live replication is intentionally NOT supported. The remote course DB\n// receives high-frequency ELO updates from all concurrent users — live\n// sync would cause constant re-indexing of local PouchDB views.\n//\n// ============================================================================\n\n/**\n * Sync state for a single course database.\n */\nexport type CourseSyncState =\n | 'not-started'\n | 'checking-config'\n | 'syncing'\n | 'warming-views'\n | 'ready'\n | 'disabled'\n | 'error';\n\n/**\n * Detailed sync status for observability.\n */\nexport interface CourseSyncStatus {\n state: CourseSyncState;\n /** Number of documents replicated (set after sync completes) */\n docsReplicated?: number;\n /** Total replication time in ms */\n syncTimeMs?: number;\n /** View warming time in ms */\n viewWarmTimeMs?: number;\n /** Error message if state is 'error' */\n error?: string;\n}\n\n/**\n * Internal tracking entry per course.\n */\ninterface SyncEntry {\n localDB: PouchDB.Database | null;\n status: CourseSyncStatus;\n /** Promise that resolves when sync is complete (or rejects on failure) */\n readyPromise: Promise<void> | null;\n}\n\n/**\n * Service that manages local PouchDB replicas of course databases.\n *\n * Usage:\n * ```typescript\n * const syncService = CourseSyncService.getInstance();\n *\n * // Trigger sync (typically on app load / pre-session)\n * await syncService.ensureSynced(courseId);\n *\n * // Get local DB for reads (returns null if sync not ready/enabled)\n * const localDB = syncService.getLocalDB(courseId);\n * ```\n *\n * The service is a singleton — course sync state is shared across the app.\n */\nexport class CourseSyncService {\n private static instance: CourseSyncService | null = null;\n\n private entries: Map<string, SyncEntry> = new Map();\n\n private constructor() {}\n\n static getInstance(): CourseSyncService {\n if (!CourseSyncService.instance) {\n CourseSyncService.instance = new CourseSyncService();\n }\n return CourseSyncService.instance;\n }\n\n /**\n * Reset the singleton (for testing).\n */\n static resetInstance(): void {\n if (CourseSyncService.instance) {\n // Close all local DBs\n for (const [, entry] of CourseSyncService.instance.entries) {\n if (entry.localDB) {\n entry.localDB.close().catch(() => {});\n }\n }\n CourseSyncService.instance.entries.clear();\n }\n CourseSyncService.instance = null;\n }\n\n // --------------------------------------------------------------------------\n // Public API\n // --------------------------------------------------------------------------\n\n /**\n * Ensure a course's local replica is synced.\n *\n * On first call for a course:\n * 1. Fetches CourseConfig from remote to check localSync.enabled\n * 2. If enabled, performs one-shot replication remote → local\n * 3. Pre-warms PouchDB view indices (elo, getTags)\n *\n * On subsequent calls: returns immediately if already synced, or awaits\n * the in-flight sync if one is in progress.\n *\n * Safe to call multiple times — concurrent calls coalesce to one sync.\n *\n * @param courseId - The course to sync\n * @param forceEnabled - Skip the CourseConfig check and sync regardless.\n * Useful when the caller already knows local sync is desired (e.g.,\n * LettersPractice hardcodes this).\n */\n async ensureSynced(courseId: string, forceEnabled?: boolean): Promise<void> {\n const existing = this.entries.get(courseId);\n\n // Already synced — but check if the remote DB has been recreated\n // (e.g., after a dev reseed). The seed script writes a `db-epoch`\n // doc; if the remote epoch differs from our local copy, the local\n // replica is stale and must be destroyed before re-syncing.\n if (existing?.status.state === 'ready' && existing.localDB) {\n const stale = await this.isLocalEpochStale(courseId, existing.localDB);\n if (!stale) {\n return;\n }\n logger.info(\n `[CourseSyncService] Remote DB epoch changed for course ${courseId} — destroying stale local replica`\n );\n try {\n await existing.localDB.destroy();\n } catch {\n // Ignore cleanup errors\n }\n existing.localDB = null;\n existing.readyPromise = null;\n // Fall through to start a fresh sync\n }\n\n // Already disabled\n if (existing?.status.state === 'disabled') {\n return;\n }\n\n // Sync in flight — coalesce\n if (existing?.readyPromise) {\n return existing.readyPromise;\n }\n\n // Start a new sync\n const entry: SyncEntry = {\n localDB: null,\n status: { state: 'not-started' },\n readyPromise: null,\n };\n this.entries.set(courseId, entry);\n\n entry.readyPromise = this.performSync(courseId, entry, forceEnabled);\n return entry.readyPromise;\n }\n\n /**\n * Get the local PouchDB for a course, or null if not available.\n *\n * Returns null when:\n * - Local sync is not enabled for this course\n * - Sync has not been triggered yet\n * - Sync is still in progress\n * - Sync failed\n */\n getLocalDB(courseId: string): PouchDB.Database | null {\n const entry = this.entries.get(courseId);\n if (entry?.status.state === 'ready' && entry.localDB) {\n return entry.localDB;\n }\n return null;\n }\n\n /**\n * Check whether a course has a ready local replica.\n */\n isReady(courseId: string): boolean {\n return this.entries.get(courseId)?.status.state === 'ready';\n }\n\n /**\n * Get detailed sync status for a course.\n */\n getStatus(courseId: string): CourseSyncStatus {\n return (\n this.entries.get(courseId)?.status ?? { state: 'not-started' }\n );\n }\n\n // --------------------------------------------------------------------------\n // Internal\n // --------------------------------------------------------------------------\n\n private async performSync(\n courseId: string,\n entry: SyncEntry,\n forceEnabled?: boolean\n ): Promise<void> {\n try {\n // Step 1: Check if local sync is enabled for this course\n if (!forceEnabled) {\n entry.status = { state: 'checking-config' };\n const enabled = await this.checkLocalSyncEnabled(courseId);\n if (!enabled) {\n entry.status = { state: 'disabled' };\n entry.readyPromise = null;\n logger.debug(\n `[CourseSyncService] Local sync disabled for course ${courseId}`\n );\n return;\n }\n }\n\n // Step 2: Create local PouchDB and replicate\n entry.status = { state: 'syncing' };\n const localDBName = this.localDBName(courseId);\n let localDB = new pouch(localDBName);\n\n // Check for stale local replica before replicating. If the remote DB\n // was wiped and recreated (e.g., `yarn db:seed`), the local PouchDB\n // has documents at rev 1-oldHash while the remote has 1-newHash for\n // the same _ids. PouchDB replication treats this as a conflict rather\n // than an update, and the stale revision can win — causing partial or\n // incorrect data. Destroying the stale local DB avoids this entirely.\n const stale = await this.isLocalEpochStale(courseId, localDB);\n if (stale) {\n logger.info(\n `[CourseSyncService] Stale local DB detected for course ${courseId} — destroying before sync`\n );\n await localDB.destroy();\n localDB = new pouch(localDBName);\n }\n\n entry.localDB = localDB;\n\n const remoteDB = this.getRemoteDB(courseId);\n const syncStart = Date.now();\n\n logger.info(\n `[CourseSyncService] Starting one-shot replication for course ${courseId}`\n );\n\n const result = await this.replicate(remoteDB, localDB);\n const syncTimeMs = Date.now() - syncStart;\n\n logger.info(\n `[CourseSyncService] Replication complete for course ${courseId}: ` +\n `${result.docs_written} docs in ${syncTimeMs}ms`\n );\n\n // Step 3: Pre-warm view indices\n entry.status = { state: 'warming-views' };\n const warmStart = Date.now();\n await this.warmViewIndices(localDB);\n const viewWarmTimeMs = Date.now() - warmStart;\n\n logger.info(\n `[CourseSyncService] View indices warmed for course ${courseId} in ${viewWarmTimeMs}ms`\n );\n\n // Done\n entry.status = {\n state: 'ready',\n docsReplicated: result.docs_written,\n syncTimeMs,\n viewWarmTimeMs,\n };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n logger.error(\n `[CourseSyncService] Sync failed for course ${courseId}: ${errorMsg}`\n );\n entry.status = { state: 'error', error: errorMsg };\n entry.readyPromise = null;\n\n // Clean up the local DB on failure — don't leave a partial replica\n if (entry.localDB) {\n try {\n await entry.localDB.destroy();\n } catch {\n // Ignore cleanup errors\n }\n entry.localDB = null;\n }\n }\n }\n\n /**\n * Check CourseConfig.localSync.enabled on the remote DB.\n */\n private async checkLocalSyncEnabled(courseId: string): Promise<boolean> {\n try {\n const remoteDB = this.getRemoteDB(courseId);\n const config = await remoteDB.get<CourseConfig>('CourseConfig');\n return config.localSync?.enabled === true;\n } catch (e) {\n logger.warn(\n `[CourseSyncService] Could not read CourseConfig for ${courseId}, ` +\n `assuming local sync disabled: ${e}`\n );\n return false;\n }\n }\n\n /**\n * One-shot replication from remote to local.\n */\n private replicate(\n source: PouchDB.Database,\n target: PouchDB.Database\n ): Promise<PouchDB.Replication.ReplicationResultComplete<object>> {\n return new Promise((resolve, reject) => {\n void pouch.replicate(source, target, {\n // One-shot, not live. Local is a read-only snapshot.\n })\n .on('complete', (info) => {\n resolve(info);\n })\n .on('error', (err) => {\n reject(err);\n });\n });\n }\n\n /**\n * Pre-warm PouchDB view indices by running a minimal query against each\n * design doc. This forces PouchDB to build the MapReduce index now\n * (during a loading phase) rather than on first pipeline query.\n */\n private async warmViewIndices(localDB: PouchDB.Database): Promise<void> {\n const viewsToWarm = ['elo', 'getTags'];\n\n for (const viewName of viewsToWarm) {\n try {\n await localDB.query(viewName, { limit: 1 });\n logger.debug(\n `[CourseSyncService] Warmed view index: ${viewName}`\n );\n } catch (e) {\n // View might not exist in this course DB — that's OK.\n // Not all courses have all design docs.\n logger.debug(\n `[CourseSyncService] Could not warm view ${viewName}: ${e}`\n );\n }\n }\n }\n\n /**\n * Check whether the local replica's `db-epoch` doc matches the remote.\n *\n * The seed script (and optionally upload-cards) writes a `db-epoch`\n * document with a numeric timestamp. If the remote epoch differs from\n * the local copy, the remote DB was recreated (e.g., `yarn db:seed`)\n * and the local PouchDB is stale.\n *\n * Returns `true` if stale (epoch mismatch or remote has epoch but local\n * doesn't). Returns `false` (not stale) if epochs match, or if the\n * remote doesn't have an epoch doc at all (backwards compat).\n */\n private async isLocalEpochStale(\n courseId: string,\n localDB: PouchDB.Database\n ): Promise<boolean> {\n try {\n const remoteDB = this.getRemoteDB(courseId);\n const remoteEpoch = await remoteDB.get<{ epoch: number }>('db-epoch');\n\n let localEpoch: { epoch: number } | null = null;\n try {\n localEpoch = await localDB.get<{ epoch: number }>('db-epoch');\n } catch {\n // Local doesn't have the epoch doc — stale\n return true;\n }\n\n return remoteEpoch.epoch !== localEpoch.epoch;\n } catch {\n // Remote doesn't have db-epoch — no epoch tracking, not stale\n return false;\n }\n }\n\n /**\n * Get a remote PouchDB handle for a course.\n */\n private getRemoteDB(courseId: string): PouchDB.Database {\n return getCourseDB(courseId);\n }\n\n /**\n * Local DB naming convention.\n */\n private localDBName(courseId: string): string {\n return `coursedb-local-${courseId}`;\n }\n}","import { ENV, NOT_SET } from '@db/factory';\nimport { logger } from '@db/util/logger';\nimport fetch from 'cross-fetch';\n\ninterface SessionResponse {\n info: unknown;\n ok: boolean;\n userCtx: {\n name: string;\n roles: string[];\n };\n}\n\nexport async function getCurrentSession(): Promise<SessionResponse> {\n // Legacy XMLHttpRequest implementation\n // return new Promise((resolve, reject) => {\n // const authXML = new XMLHttpRequest();\n // authXML.withCredentials = true;\n //\n // authXML.onerror = (e): void => {\n // reject(new Error('Session check failed:', e));\n // };\n //\n // authXML.addEventListener('load', () => {\n // try {\n // const resp: SessionResponse = JSON.parse(authXML.responseText);\n // resolve(resp);\n // } catch (e) {\n // reject(e);\n // }\n // });\n //\n // const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}_session`;\n // authXML.open('GET', url);\n // authXML.send();\n // });\n \n try {\n // Handle case where ENV variables might not be properly set\n if (ENV.COUCHDB_SERVER_URL === NOT_SET || ENV.COUCHDB_SERVER_PROTOCOL === NOT_SET) {\n throw new Error(`CouchDB server configuration not properly initialized. Protocol: \"${ENV.COUCHDB_SERVER_PROTOCOL}\", URL: \"${ENV.COUCHDB_SERVER_URL}\"`);\n }\n \n // Ensure URL has proper slash before _session endpoint\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.debug(`Attempting session check at: ${url}`);\n \n const response = await fetch(url, {\n method: 'GET',\n credentials: 'include',\n });\n \n if (!response.ok) {\n throw new Error(`Session check failed: ${response.status}`);\n }\n \n const resp: SessionResponse = await response.json();\n return resp;\n } catch (error) {\n // Use same URL construction logic for error reporting\n const baseUrl = ENV.COUCHDB_SERVER_URL.endsWith('/') \n ? ENV.COUCHDB_SERVER_URL.slice(0, -1) \n : ENV.COUCHDB_SERVER_URL;\n const url = `${ENV.COUCHDB_SERVER_PROTOCOL}://${baseUrl}/_session`;\n logger.error(`Session check error attempting to connect to: ${url} - ${error}`);\n throw new Error(`Session check failed connecting to ${url}: ${error}`);\n }\n}\n\nexport async function getLoggedInUsername(): Promise<string> {\n const session = await getCurrentSession();\n if (session.userCtx.name && session.userCtx.name !== '') {\n return session.userCtx.name;\n }\n // Not logged in - throw so caller can handle guest account\n throw new Error('No logged in user');\n}\n","// packages/db/src/impl/couch/CouchDBSyncStrategy.ts\n\nimport { ENV } from '@db/factory';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\nimport { Status } from '@vue-skuilder/common';\nimport type { SyncStrategy } from '../common/SyncStrategy';\nimport type { AccountCreationResult, AuthenticationResult } from '../common/types';\nimport { getLocalUserDB, hexEncode, updateGuestAccountExpirationDate, accomodateGuest } from '../common';\nimport pouch from './pouchdb-setup';\nimport { createPouchDBConfig } from './index';\nimport { getLoggedInUsername } from './auth';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n/**\n * Sync strategy that implements full CouchDB remote synchronization\n * Handles account creation, authentication, and live sync with remote CouchDB server\n */\nexport class CouchDBSyncStrategy implements SyncStrategy {\n private syncHandle?: any; // Handle to cancel sync if needed\n\n setupRemoteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // For guest users, remote is same as local (no remote sync)\n return getLocalUserDB(username);\n } else {\n // For real users, connect to remote CouchDB\n return this.getUserDB(username);\n }\n }\n\n getWriteDB(username: string): PouchDB.Database {\n if (username === GuestUsername || username.startsWith(GuestUsername)) {\n // Guest users write to local database\n return getLocalUserDB(username);\n } else {\n // Authenticated users write to remote (which will sync to local)\n return this.getUserDB(username);\n }\n }\n\n startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {\n // Only sync if local and remote are different instances\n if (localDB !== remoteDB) {\n this.syncHandle = pouch.sync(localDB, remoteDB, {\n live: true,\n retry: true,\n });\n }\n // If they're the same (guest mode), no sync needed\n }\n\n stopSync?(): void {\n if (this.syncHandle) {\n this.syncHandle.cancel();\n this.syncHandle = undefined;\n }\n }\n\n canCreateAccount(): boolean {\n return true;\n }\n\n canAuthenticate(): boolean {\n return true;\n }\n\n async createAccount(username: string, password: string): Promise<AccountCreationResult> {\n // IMPORTANT: Capture funnel username BEFORE any operations that might change session state\n const funnelUsername = await this.getCurrentUsername();\n const isFunnelAccount = funnelUsername.startsWith(GuestUsername);\n\n if (isFunnelAccount) {\n logger.info(`Creating account for funnel user ${funnelUsername} -> ${username}`);\n }\n\n try {\n const signupRequest = await this.getRemoteCouchRootDB().signUp(username, password);\n\n if (signupRequest.ok) {\n log(`CREATEACCOUNT: Successfully created account for ${username}`);\n\n // Log out any existing session\n try {\n const logoutResult = await this.getRemoteCouchRootDB().logOut();\n log(`CREATEACCOUNT: logged out: ${logoutResult.ok}`);\n } catch {\n // Ignore logout errors - might not be logged in\n }\n\n // Log in as the new user\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n log(`CREATEACCOUNT: logged in as new user: ${loginResult.ok}`);\n\n if (loginResult.ok) {\n // Migrate funnel account data if applicable\n if (isFunnelAccount) {\n logger.info(`Migrating data from funnel account ${funnelUsername} to ${username}`);\n const migrationResult = await this.migrateFunnelData(funnelUsername, username);\n if (!migrationResult.success) {\n logger.warn(`Migration failed: ${migrationResult.error}`);\n // Continue anyway - don't block account creation\n }\n }\n\n return {\n status: Status.ok,\n error: undefined,\n };\n } else {\n return {\n status: Status.error,\n error: 'Failed to log in after account creation',\n };\n }\n } else {\n logger.warn(`Signup not OK: ${JSON.stringify(signupRequest)}`);\n return {\n status: Status.error,\n error: 'Account creation failed',\n };\n }\n } catch (e: any) {\n if (e.reason === 'Document update conflict.') {\n return {\n status: Status.error,\n error: 'This username is taken!',\n };\n }\n logger.error(`Error on signup: ${JSON.stringify(e)}`);\n return {\n status: Status.error,\n error: e.message || 'Unknown error during account creation',\n };\n }\n }\n\n async authenticate(username: string, password: string): Promise<AuthenticationResult> {\n try {\n const loginResult = await this.getRemoteCouchRootDB().logIn(username, password);\n\n if (loginResult.ok) {\n log(`Successfully logged in as ${username}`);\n return {\n ok: true,\n };\n } else {\n log(`Login failed for ${username}`);\n return {\n ok: false,\n error: 'Invalid username or password',\n };\n }\n } catch (error: any) {\n logger.error(`Authentication error for ${username}:`, error);\n return {\n ok: false,\n error: error.message || 'Authentication failed',\n };\n }\n }\n\n async logout(): Promise<AuthenticationResult> {\n try {\n const result = await this.getRemoteCouchRootDB().logOut();\n return {\n ok: result.ok,\n error: result.ok ? undefined : 'Logout failed',\n };\n } catch (error: any) {\n logger.error('Logout error:', error);\n return {\n ok: false,\n error: error.message || 'Logout failed',\n };\n }\n }\n\n async getCurrentUsername(): Promise<string> {\n logger.log('[funnel] CouchDBSyncStrategy.getCurrentUsername() called');\n try {\n const loggedInUsername = await getLoggedInUsername();\n logger.log('[funnel] getLoggedInUsername() returned:', loggedInUsername);\n return loggedInUsername;\n } catch (e) {\n // Not logged in - return unique guest account\n logger.log('[funnel] getLoggedInUsername() failed, calling accomodateGuest()');\n logger.log('[funnel] Error was:', e);\n const guestInfo = accomodateGuest();\n logger.log('[funnel] accomodateGuest() returned:', guestInfo);\n return guestInfo.username;\n }\n }\n\n /**\n * Migrate data from funnel account to real account\n */\n private async migrateFunnelData(\n oldUsername: string,\n newUsername: string\n ): Promise<{ success: boolean; error?: string }> {\n try {\n logger.info(`Starting data migration from ${oldUsername} to ${newUsername}`);\n\n const oldLocalDB = getLocalUserDB(oldUsername);\n const newLocalDB = getLocalUserDB(newUsername);\n\n // Get all docs from funnel account\n const allDocs = await oldLocalDB.allDocs({ include_docs: true });\n\n logger.info(`Found ${allDocs.rows.length} documents in funnel account`);\n\n // Filter out design docs and prepare for migration\n const docsToMigrate = allDocs.rows\n .filter(row => !row.id.startsWith('_design/'))\n .map(row => ({\n ...row.doc,\n _rev: undefined, // Remove rev to insert as new\n }));\n\n if (docsToMigrate.length > 0) {\n await newLocalDB.bulkDocs(docsToMigrate);\n logger.info(`Successfully migrated ${docsToMigrate.length} documents from ${oldUsername} to ${newUsername}`);\n } else {\n logger.info('No documents to migrate from funnel account');\n }\n\n return { success: true };\n } catch (error) {\n logger.error('Migration failed:', error);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error'\n };\n }\n }\n\n /**\n * Get remote CouchDB root database for authentication operations\n */\n private getRemoteCouchRootDB(): PouchDB.Database {\n const remoteStr: string =\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'skuilder';\n\n try {\n return new pouch(remoteStr, {\n skip_setup: true,\n });\n } catch (error) {\n logger.error('Failed to initialize remote CouchDB connection:', error);\n throw new Error(`Failed to initialize CouchDB: ${JSON.stringify(error)}`);\n }\n }\n\n /**\n * Get remote user database for a specific user\n */\n private getUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // Odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n }\n}\n","import { ENV } from '@db/factory';\nimport {\n DocType,\n DocTypePrefixes,\n GuestUsername,\n log,\n SkuilderCourseData,\n} from '../../core/types/types-legacy';\nimport fetch from 'cross-fetch';\n// import { getCurrentUser } from '../../stores/useAuthStore';\nimport moment, { Moment } from 'moment';\nimport { logger } from '@db/util/logger';\n\nimport pouch from './pouchdb-setup';\n\nimport { ScheduledCard } from '@db/core/types/user';\nimport process from 'process';\n\nconst isBrowser = typeof window !== 'undefined';\n\nif (isBrowser) {\n (window as any).process = process; // required as a fix for pouchdb - see #18\n}\n\nconst expiryDocID: string = 'GuestAccountExpirationDate';\n\nconst GUEST_LOCAL_DB = `userdb-${GuestUsername}`;\nexport const localUserDB: PouchDB.Database = new pouch(GUEST_LOCAL_DB);\n\nexport function hexEncode(str: string): string {\n let hex: string;\n let returnStr: string = '';\n\n for (let i = 0; i < str.length; i++) {\n hex = str.charCodeAt(i).toString(16);\n returnStr += ('000' + hex).slice(3);\n }\n\n return returnStr;\n}\nconst pouchDBincludeCredentialsConfig: PouchDB.Configuration.RemoteDatabaseConfiguration = {\n fetch(url: string | Request, opts: RequestInit): Promise<Response> {\n opts.credentials = 'include';\n\n return (pouch as any).fetch(url, opts);\n },\n} as PouchDB.Configuration.RemoteDatabaseConfiguration;\n\n/**\n * Creates PouchDB configuration with appropriate authentication method\n * - Uses HTTP Basic Auth when credentials are available (Node.js/MCP)\n * - Falls back to cookie auth for browser environments\n */\nexport function createPouchDBConfig(): PouchDB.Configuration.RemoteDatabaseConfiguration {\n // Check if running in Node.js with explicit credentials\n const hasExplicitCredentials = ENV.COUCHDB_USERNAME && ENV.COUCHDB_PASSWORD;\n const isNodeEnvironment = typeof window === 'undefined';\n \n if (hasExplicitCredentials && isNodeEnvironment) {\n // Use HTTP Basic Auth for Node.js environments (MCP server)\n return {\n fetch(url: string | Request, opts: RequestInit = {}): Promise<Response> {\n const basicAuth = btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`);\n const headers = new Headers(opts.headers || {});\n headers.set('Authorization', `Basic ${basicAuth}`);\n \n const newOpts = {\n ...opts,\n headers: headers\n };\n \n return (pouch as any).fetch(url, newOpts);\n }\n } as PouchDB.Configuration.RemoteDatabaseConfiguration;\n }\n \n // Use cookie-based auth for browser environments or when no explicit credentials\n return pouchDBincludeCredentialsConfig;\n}\n\nfunction getCouchDB(dbName: string): PouchDB.Database {\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n}\n\nexport function getCourseDB(courseID: string): PouchDB.Database {\n // todo: keep a cache of opened courseDBs? need to benchmark this somehow\n return new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + 'coursedb-' + courseID,\n createPouchDBConfig()\n );\n}\n\nexport async function getLatestVersion() {\n try {\n const docs = await getCouchDB('version').allDocs({\n descending: true,\n limit: 1,\n });\n if (docs && docs.rows && docs.rows[0]) {\n return docs.rows[0].id;\n } else {\n return '0.0.0';\n }\n } catch {\n return '-1';\n }\n}\n\n/**\n * Checks the remote couchdb to see if a given username is available\n * @param username The username to be checked\n */\nexport async function usernameIsAvailable(username: string): Promise<boolean> {\n log(`Checking availability of ${username}`);\n \n // Legacy XMLHttpRequest implementation (browser sync)\n // const req = new XMLHttpRequest();\n // const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n // req.open('HEAD', url, false);\n // req.send();\n // return req.status === 404;\n \n try {\n const url = ENV.COUCHDB_SERVER_URL + 'userdb-' + hexEncode(username);\n const response = await fetch(url, { method: 'HEAD' });\n return response.status === 404;\n } catch (error) {\n log(`Error checking username availability: ${error}`);\n return false;\n }\n}\n\nexport function updateGuestAccountExpirationDate(guestDB: PouchDB.Database<object>) {\n const currentTime = moment.utc();\n const expirationDate: string = currentTime.add(2, 'months').toISOString();\n\n void guestDB\n .get(expiryDocID)\n .then((doc) => {\n return guestDB.put({\n _id: expiryDocID,\n _rev: doc._rev,\n date: expirationDate,\n });\n })\n .catch(() => {\n return guestDB.put({\n _id: expiryDocID,\n date: expirationDate,\n });\n });\n}\n\nexport function getCourseDocs<T extends SkuilderCourseData>(\n courseID: string,\n docIDs: string[],\n options: PouchDB.Core.AllDocsOptions = {}\n) {\n return getCourseDB(courseID).allDocs<T>({\n ...options,\n keys: docIDs,\n });\n}\n\nexport function getCourseDoc<T extends SkuilderCourseData>(\n courseID: string,\n docID: PouchDB.Core.DocumentId,\n options: PouchDB.Core.GetOptions = {}\n): Promise<T> {\n return getCourseDB(courseID).get<T>(docID, options);\n}\n\n/**\n * Returns *all* cards from the parameter courses, in\n * 'qualified' card format (\"courseid-cardid\")\n *\n * @param courseIDs A list of all course_ids to get cards from\n */\nexport async function getRandomCards(courseIDs: string[]) {\n if (courseIDs.length === 0) {\n throw new Error(`getRandomCards:\\n\\tAttempted to get all cards from no courses!`);\n } else {\n const courseResults = await Promise.all(\n courseIDs.map((course) => {\n return getCourseDB(course).find({\n selector: {\n docType: DocType.CARD,\n },\n limit: 1000,\n });\n })\n );\n\n const ret: string[] = [];\n courseResults.forEach((courseCards, index) => {\n courseCards.docs.forEach((doc) => {\n ret.push(`${courseIDs[index]}-${doc._id}`);\n });\n });\n\n return ret;\n }\n}\n\nexport const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';\n\nexport function getCouchUserDB(username: string): PouchDB.Database {\n const guestAccount: boolean = false;\n // console.log(`Getting user db: ${username}`);\n\n const hexName = hexEncode(username);\n const dbName = `userdb-${hexName}`;\n log(`Fetching user database: ${dbName} (${username})`);\n\n // odd construction here the result of a bug in the\n // interaction between pouch, pouch-auth.\n // see: https://github.com/pouchdb-community/pouchdb-authentication/issues/239\n const ret = new pouch(\n ENV.COUCHDB_SERVER_PROTOCOL + '://' + ENV.COUCHDB_SERVER_URL + dbName,\n createPouchDBConfig()\n );\n if (guestAccount) {\n updateGuestAccountExpirationDate(ret);\n }\n\n return ret;\n}\n\nexport function scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n}) {\n const now = moment.utc();\n logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);\n void getCouchUserDB(review.user).put<ScheduledCard>({\n _id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),\n cardId: review.card_id,\n reviewTime: review.time.toISOString(),\n courseId: review.course_id,\n scheduledAt: now.toISOString(),\n scheduledFor: review.scheduledFor,\n schedulingAgentId: review.schedulingAgentId,\n });\n}\n\nexport function filterAllDocsByPrefix<T>(\n db: PouchDB.Database,\n prefix: string,\n opts?: PouchDB.Core.AllDocsOptions\n) {\n // see couchdb docs 6.2.2:\n // Guide to Views -> Views Collation -> String Ranges\n const options: PouchDB.Core.AllDocsWithinRangeOptions = {\n startkey: prefix,\n endkey: prefix + '\\ufff0',\n include_docs: true,\n };\n\n if (opts) {\n Object.assign(options, opts);\n }\n return db.allDocs<T>(options);\n}\n\nexport function getStartAndEndKeys(key: string): {\n startkey: string;\n endkey: string;\n} {\n return {\n startkey: key,\n endkey: key + '\\ufff0',\n };\n}\n\n//////////////////////\n// Package exports\n//////////////////////\n\nexport * from '../../core/interfaces/contentSource';\nexport * from './adminDB';\nexport * from './classroomDB';\nexport * from './courseAPI';\nexport * from './courseDB';\nexport * from './CourseSyncService';\nexport * from './CouchDBSyncStrategy';\n","import { DocType, DocTypePrefixes, StrategyStateDoc, buildStrategyStateId } from '@db/core';\nimport { getCardHistoryID } from '@db/core/util';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport moment, { Moment } from 'moment';\nimport { GuestUsername } from '../../core/types/types-legacy';\nimport { logger } from '../../util/logger';\n\nimport {\n ClassroomRegistrationDoc,\n UserCourseSetting,\n UserDBInterface,\n UsrCrsDataInterface,\n} from '@db/core';\nimport {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n UserConfig,\n} from '@db/core/types/user';\nimport { DocumentUpdater } from '@db/study';\nimport { CardHistory, CardRecord } from '../../core/types/types-legacy';\nimport { UserOutcomeRecord } from '../../core/types/userOutcome';\nimport type { SyncStrategy } from './SyncStrategy';\nimport {\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n REVIEW_TIME_FORMAT,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\nimport { PouchError } from '../couch/types';\nimport UpdateQueue, { Update } from '../couch/updateQueue';\nimport { UsrCrsData } from '../couch/user-course-relDB';\nimport { getCredentialledCourseConfig } from '../couch/index';\n\nconst log = (s: any) => {\n logger.info(s);\n};\n\n// logger.log(`Connecting to remote: ${remoteStr}`);\n\ninterface DesignDoc {\n _id: string;\n views: {\n [viewName: string]: {\n map: string; // String representation of the map function\n };\n };\n}\n\n/**\n * Base user database implementation that uses a pluggable sync strategy.\n * Handles local storage operations and delegates sync/remote operations to the strategy.\n */\nexport class BaseUser implements UserDBInterface, DocumentUpdater {\n private static _instance: BaseUser;\n private static _initialized: boolean = false;\n\n public static Dummy(syncStrategy: SyncStrategy): BaseUser {\n return new BaseUser('Me', syncStrategy);\n }\n\n static readonly DOC_IDS = {\n CONFIG: 'CONFIG',\n COURSE_REGISTRATIONS: 'CourseRegistrations',\n CLASSROOM_REGISTRATIONS: 'ClassroomRegistrations',\n };\n\n // private email: string;\n private _username: string;\n private syncStrategy: SyncStrategy;\n\n public getUsername(): string {\n return this._username;\n }\n\n public isLoggedIn(): boolean {\n return !this._username.startsWith(GuestUsername);\n }\n\n public remote(): PouchDB.Database {\n return this.remoteDB;\n }\n\n private localDB!: PouchDB.Database;\n private remoteDB!: PouchDB.Database;\n private writeDB!: PouchDB.Database; // Database to use for write operations (local-first approach)\n\n private updateQueue!: UpdateQueue;\n\n public async createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }> {\n if (!this.syncStrategy.canCreateAccount()) {\n throw new Error('Account creation not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername)) {\n throw new Error(\n `Cannot create a new account while logged in:\nCurrently logged-in as ${this._username}.`\n );\n }\n\n const result = await this.syncStrategy.createAccount!(username, password);\n\n // If account creation was successful, update the username and reinitialize\n if (result.status === Status.ok) {\n log(`Account created successfully, updating username to ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n\n return {\n status: result.status,\n error: result.error || '',\n };\n }\n public async login(username: string, password: string) {\n if (!this.syncStrategy.canAuthenticate()) {\n throw new Error('Authentication not supported by current sync strategy');\n }\n\n if (!this._username.startsWith(GuestUsername) && this._username != username) {\n if (this._username != username) {\n throw new Error(`Cannot change accounts while logged in.\n Log out of account ${this.getUsername()} before logging in as ${username}.`);\n }\n logger.warn(`User ${this._username} is already logged in, but executing login again.`);\n }\n\n const loginResult = await this.syncStrategy.authenticate!(username, password);\n if (loginResult.ok) {\n log(`Logged in as ${username}`);\n this._username = username;\n try {\n localStorage.removeItem('sk-guest-uuid');\n } catch (e) {\n logger.warn('localStorage not available (Node.js environment):', e);\n }\n await this.init();\n }\n return loginResult;\n }\n\n public async resetUserData(): Promise<{ status: Status; error?: string }> {\n // Only allow reset for local-only sync strategies\n if (this.syncStrategy.canAuthenticate()) {\n return {\n status: Status.error,\n error:\n 'Reset user data is only available for local-only mode. Use logout instead for remote sync.',\n };\n }\n\n try {\n const localDB = getLocalUserDB(this._username);\n\n // Get all documents to identify user data to clear\n const allDocs = await localDB.allDocs({ include_docs: false });\n\n // Identify documents to delete (preserve authentication and user identity)\n const docsToDelete = allDocs.rows\n .filter((row) => {\n const id = row.id;\n // Delete user progress data but preserve authentication and user identity\n return (\n id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history\n id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews\n id.startsWith(DocTypePrefixes[DocType.STRATEGY_STATE]) || // Strategy state (user prefs, progression)\n id.startsWith(DocTypePrefixes[DocType.USER_OUTCOME]) || // Evolutionary orchestration outcomes\n id.startsWith(DocTypePrefixes[DocType.STRATEGY_LEARNING_STATE]) || // Strategy learning state\n id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations\n id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations\n id === BaseUser.DOC_IDS.CONFIG // User config\n );\n })\n .map((row) => ({ _id: row.id, _rev: row.value.rev, _deleted: true }));\n\n if (docsToDelete.length > 0) {\n await localDB.bulkDocs(docsToDelete);\n }\n\n // Reinitialize to create fresh default documents\n await this.init();\n\n return { status: Status.ok };\n } catch (error) {\n logger.error('Failed to reset user data:', error);\n return {\n status: Status.error,\n error: error instanceof Error ? error.message : 'Unknown error during reset',\n };\n }\n }\n\n public async logout() {\n if (!this.syncStrategy.canAuthenticate()) {\n // For strategies that don't support authentication, just switch to guest\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n return { ok: true };\n }\n\n const ret = await this.syncStrategy.logout!();\n // return to 'guest' mode\n this._username = await this.syncStrategy.getCurrentUsername();\n await this.init();\n\n return ret;\n }\n\n public async get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta> {\n return this.localDB.get<T>(id);\n }\n\n public update<T extends PouchDB.Core.Document<object>>(id: string, update: Update<T>) {\n return this.updateQueue.update(id, update);\n }\n\n public async getCourseRegistrationsDoc(): Promise<\n CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);\n\n let ret;\n\n try {\n const regDoc = await this.localDB.get<CourseRegistrationDoc>(\n BaseUser.DOC_IDS.COURSE_REGISTRATIONS\n );\n return regDoc;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n await this.localDB.put<CourseRegistrationDoc>({\n _id: BaseUser.DOC_IDS.COURSE_REGISTRATIONS,\n courses: [],\n studyWeight: {},\n });\n ret = await this.getCourseRegistrationsDoc();\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n }\n\n public async getActiveCourses() {\n const reg = await this.getCourseRegistrationsDoc();\n return reg.courses.filter((c) => {\n return c.status === undefined || c.status === 'active';\n });\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * a scheduled review for.\n *\n */\n public async getActiveCards() {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n return reviews.rows.map((r) => {\n return {\n courseID: r.doc!.courseId,\n cardID: r.doc!.cardId,\n };\n });\n }\n\n public async getActivityRecords(): Promise<ActivityRecord[]> {\n try {\n const hist = await this.getHistory();\n\n const allRecords: ActivityRecord[] = [];\n if (!Array.isArray(hist)) {\n logger.error('getHistory did not return an array:', hist);\n return allRecords;\n }\n\n // Sample the first few records to understand structure\n let sampleCount = 0;\n\n for (let i = 0; i < hist.length; i++) {\n try {\n if (hist[i] && Array.isArray(hist[i]!.records)) {\n hist[i]!.records.forEach((record: CardRecord) => {\n try {\n // Skip this record if timeStamp is missing\n if (!record.timeStamp) {\n return;\n }\n\n let timeStamp;\n\n // Handle different timestamp formats\n if (typeof record.timeStamp === 'object') {\n // It's likely a Moment object\n if (typeof record.timeStamp.toDate === 'function') {\n // It's definitely a Moment object\n timeStamp = record.timeStamp.toISOString();\n } else if (record.timeStamp instanceof Date) {\n // It's a Date object\n timeStamp = record.timeStamp.toISOString();\n } else {\n // Log a sample of unknown object types, but don't flood logger\n if (sampleCount < 3) {\n logger.warn('Unknown timestamp object type:', record.timeStamp);\n sampleCount++;\n }\n return;\n }\n } else if (typeof record.timeStamp === 'string') {\n // It's already a string, but make sure it's a valid date\n const date = new Date(record.timeStamp);\n if (isNaN(date.getTime())) {\n return; // Invalid date string\n }\n timeStamp = record.timeStamp;\n } else if (typeof record.timeStamp === 'number') {\n // Assume it's a Unix timestamp (milliseconds since epoch)\n timeStamp = new Date(record.timeStamp).toISOString();\n } else {\n // Unknown type, skip\n return;\n }\n\n allRecords.push({\n timeStamp,\n courseID: record.courseID || 'unknown',\n cardID: record.cardID || 'unknown',\n timeSpent: record.timeSpent || 0,\n type: 'card_view',\n });\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (err) {\n // Silently skip problematic records to avoid flooding logs\n }\n });\n }\n } catch (err) {\n logger.error('Error processing history item:', err);\n }\n }\n\n logger.debug(`Found ${allRecords.length} activity records`);\n return allRecords;\n } catch (err) {\n logger.error('Error in getActivityRecords:', err);\n return [];\n }\n }\n\n private async getReviewstoDate(targetDate: Moment, course_id?: string) {\n const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);\n\n const reviews = await this.remoteDB.allDocs<ScheduledCard>({\n startkey: keys.startkey,\n endkey: keys.endkey,\n include_docs: true,\n });\n\n log(\n `Fetching ${this._username}'s scheduled reviews${\n course_id ? ` for course ${course_id}` : ''\n }.`\n );\n return reviews.rows\n .filter((r) => {\n if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {\n const date = moment.utc(\n r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),\n REVIEW_TIME_FORMAT\n );\n if (targetDate.isAfter(date)) {\n if (course_id === undefined || r.doc!.courseId === course_id) {\n return true;\n }\n }\n }\n })\n .map((r) => r.doc!);\n }\n\n public async getReviewsForcast(daysCount: number) {\n const time = moment.utc().add(daysCount, 'days');\n return this.getReviewstoDate(time);\n }\n\n public async getPendingReviews(course_id?: string) {\n const now = moment.utc();\n return this.getReviewstoDate(now, course_id);\n }\n\n public async getScheduledReviewCount(course_id: string): Promise<number> {\n return (await this.getPendingReviews(course_id)).length;\n }\n\n public async getRegisteredCourses() {\n const regDoc = await this.getCourseRegistrationsDoc();\n return regDoc.courses.filter((c) => {\n return !c.status || c.status === 'active' || c.status === 'maintenance-mode';\n });\n }\n\n public async getCourseRegDoc(courseID: string) {\n const regDocs = await this.getCourseRegistrationsDoc();\n const ret = regDocs.courses.find((c) => c.courseID === courseID);\n if (ret) {\n return ret;\n } else {\n throw new Error(`Course registration not found for course ID: ${courseID}`);\n }\n }\n\n public async registerForCourse(course_id: string, previewMode: boolean = false) {\n return this.getCourseRegistrationsDoc()\n .then((doc: CourseRegistrationDoc) => {\n const status = previewMode ? 'preview' : 'active';\n logger.debug(`Registering for ${course_id} with status: ${status}`);\n\n const regItem: CourseRegistration = {\n status: status,\n courseID: course_id,\n user: true,\n admin: false,\n moderator: false,\n elo: {\n global: {\n score: 1000,\n count: 0,\n },\n tags: {},\n misc: {},\n },\n };\n\n if (\n doc.courses.filter((course) => {\n return course.courseID === regItem.courseID;\n }).length === 0\n ) {\n log(`It's a new course registration!`);\n doc.courses.push(regItem);\n doc.studyWeight[course_id] = 1;\n } else {\n doc.courses.forEach((c) => {\n log(`Found the previously registered course!`);\n if (c.courseID === course_id) {\n c.status = status;\n }\n });\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n })\n .catch((e) => {\n log(`Registration failed because of: ${JSON.stringify(e)}`);\n throw e;\n });\n }\n public async dropCourse(course_id: string, dropStatus: CourseRegistration['status'] = 'dropped') {\n return this.getCourseRegistrationsDoc().then((doc) => {\n let index: number = -1;\n for (let i = 0; i < doc.courses.length; i++) {\n if (doc.courses[i].courseID === course_id) {\n index = i;\n }\n }\n\n if (index !== -1) {\n // remove from the relative-weighting of course study\n delete doc.studyWeight[course_id];\n // set drop status\n doc.courses[index].status = dropStatus;\n } else {\n throw new Error(\n `User ${this.getUsername()} is not currently registered for course ${course_id}`\n );\n }\n\n return this.localDB.put<CourseRegistrationDoc>(doc);\n });\n }\n\n public async getCourseInterface(courseId: string): Promise<UsrCrsDataInterface> {\n return new UsrCrsData(this, courseId);\n }\n\n public async getUserEditableCourses() {\n let courseIDs: string[] = [];\n\n const registeredCourses = await this.getCourseRegistrationsDoc();\n\n courseIDs = courseIDs.concat(\n registeredCourses.courses.map((course) => {\n return course.courseID;\n })\n );\n\n const cfgs = await Promise.all(\n courseIDs.map(async (id) => {\n return await getCredentialledCourseConfig(id);\n })\n );\n return cfgs;\n }\n\n public async getConfig(): Promise<UserConfig & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n const defaultConfig: PouchDB.Core.Document<UserConfig> = {\n _id: BaseUser.DOC_IDS.CONFIG,\n darkMode: false,\n likesConfetti: false,\n sessionTimeLimit: 5,\n };\n\n try {\n const cfg = await this.localDB.get<UserConfig>(BaseUser.DOC_IDS.CONFIG);\n logger.debug('Raw config from DB:', cfg);\n\n return cfg;\n } catch (e) {\n const err = e as PouchError;\n if (err.name && err.name === 'not_found') {\n await this.localDB.put<UserConfig>(defaultConfig);\n return this.getConfig();\n } else {\n logger.error(`Error setting user default config:`, e);\n throw new Error(`Error returning the user's configuration: ${JSON.stringify(e)}`);\n }\n }\n }\n\n public async setConfig(items: Partial<UserConfig>) {\n logger.debug(`Setting Config items ${JSON.stringify(items)}`);\n\n const c = await this.getConfig();\n const put = await this.localDB.put<UserConfig>({\n ...c,\n ...items,\n });\n\n if (put.ok) {\n logger.debug(`Config items set: ${JSON.stringify(items)}`);\n } else {\n logger.error(`Error setting config items: ${JSON.stringify(put)}`);\n }\n }\n\n /**\n *\n * This function should be called *only* by the pouchdb datalayer provider\n * auth store.\n *\n *\n * Anyone else seeking the current user should use the auth store's\n * exported `getCurrentUser` method.\n *\n */\n public static async instance(syncStrategy: SyncStrategy, username?: string): Promise<BaseUser> {\n if (username) {\n BaseUser._instance = new BaseUser(username, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n } else if (BaseUser._instance && BaseUser._initialized) {\n // log(`USER.instance() returning user ${BaseUser._instance._username}`);\n return BaseUser._instance;\n } else if (BaseUser._instance) {\n return new Promise((resolve) => {\n (function waitForUser() {\n if (BaseUser._initialized) {\n return resolve(BaseUser._instance);\n } else {\n setTimeout(waitForUser, 50);\n }\n })();\n });\n } else {\n const guestUsername = await syncStrategy.getCurrentUsername();\n BaseUser._instance = new BaseUser(guestUsername, syncStrategy);\n await BaseUser._instance.init();\n return BaseUser._instance;\n }\n }\n\n private constructor(username: string, syncStrategy: SyncStrategy) {\n BaseUser._initialized = false;\n this._username = username;\n this.syncStrategy = syncStrategy;\n this.setDBandQ();\n }\n\n private setDBandQ() {\n this.localDB = getLocalUserDB(this._username);\n this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);\n // writeDB follows local-first pattern: static mode writes to local, CouchDB writes to remote/local as appropriate\n this.writeDB = this.syncStrategy.getWriteDB\n ? this.syncStrategy.getWriteDB(this._username)\n : this.localDB;\n this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);\n }\n\n private async init() {\n BaseUser._initialized = false;\n\n // Skip admin user\n if (this._username === 'admin') {\n BaseUser._initialized = true;\n return;\n }\n\n this.setDBandQ();\n\n this.syncStrategy.startSync(this.localDB, this.remoteDB);\n this.applyDesignDocs().catch((error) => {\n log(`Error in applyDesignDocs background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in applyDesignDocs: ${JSON.stringify(error)}`);\n }\n });\n this.deduplicateReviews().catch((error) => {\n log(`Error in deduplicateReviews background task: ${error}`);\n if (error && typeof error === 'object') {\n log(`Full error details in background task: ${JSON.stringify(error)}`);\n }\n });\n BaseUser._initialized = true;\n }\n\n private static designDocs: DesignDoc[] = [\n {\n _id: '_design/reviewCards',\n views: {\n reviewCards: {\n map: `function (doc) {\n if (doc._id && doc._id.indexOf('card_review') === 0 && doc.courseId && doc.cardId) {\n emit(doc._id, doc.courseId + '-' + doc.cardId);\n }\n }`,\n },\n },\n },\n ];\n\n private async applyDesignDocs() {\n log(`Starting applyDesignDocs for user: ${this._username}`);\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n\n if (this._username === 'admin') {\n // Skip admin user\n log('Skipping design docs for admin user');\n return;\n }\n\n log(`Applying ${BaseUser.designDocs.length} design docs`);\n for (const doc of BaseUser.designDocs) {\n log(`Applying design doc: ${doc._id}`);\n try {\n // Try to get existing doc\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n // Update existing doc\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'not_found') {\n // Create new doc\n await this.remoteDB.put(doc);\n } else {\n throw e; // Re-throw unexpected errors\n }\n }\n } catch (error: unknown) {\n if ((error as any).name && (error as any).name === 'conflict') {\n logger.warn(`Design doc ${doc._id} update conflict - will retry`);\n // Wait a bit and try again\n await new Promise((resolve) => setTimeout(resolve, 1000));\n await this.applyDesignDoc(doc); // Recursive retry\n } else {\n logger.error(`Failed to apply design doc ${doc._id}:`, error);\n throw error;\n }\n }\n }\n }\n\n // Helper method for single doc update with retry\n private async applyDesignDoc(doc: DesignDoc, retries = 3): Promise<void> {\n try {\n const existingDoc = await this.remoteDB.get(doc._id);\n await this.remoteDB.put({\n ...doc,\n _rev: existingDoc._rev,\n });\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'conflict' && retries > 0) {\n await new Promise((resolve) => setTimeout(resolve, 1000));\n return this.applyDesignDoc(doc, retries - 1);\n }\n throw e;\n }\n }\n\n /**\n * Logs a record of the user's interaction with the card and returns the card's\n * up-to-date history.\n *\n * **Automatic Initialization:**\n * If this is the user's first interaction with the card (CardHistory doesn't exist),\n * this method automatically creates the CardHistory document with initial values\n * (lapses: 0, streak: 0, bestInterval: 0).\n *\n * **Error Handling:**\n * - Handles 404 errors by creating initial CardHistory document\n * - Re-throws all other errors from UpdateQueue\n *\n * // [ ] #db-refactor extract to a smaller scope - eg, UserStudySession\n *\n * @param record - The recent recorded interaction between user and card\n * @returns The updated state of the card's CardHistory data\n * @throws Error if document creation fails or non-404 database error occurs\n */\n\n public async putCardRecord<T extends CardRecord>(\n record: T\n ): Promise<CardHistory<CardRecord> & PouchDB.Core.RevisionIdMeta> {\n const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);\n // stringify the current record to make it writable to couchdb\n record.timeStamp = moment.utc(record.timeStamp).toString() as unknown as Moment;\n\n try {\n const cardHistory = await this.update<CardHistory<T>>(\n cardHistoryID,\n function (h: CardHistory<T>) {\n h.records.push(record);\n h.bestInterval = h.bestInterval || 0;\n h.lapses = h.lapses || 0;\n h.streak = h.streak || 0;\n return h;\n }\n );\n\n // Convert timestamps to moment objects\n cardHistory.records = cardHistory.records.map<T>((record) => {\n const ret: T = {\n ...(record as object),\n } as T;\n ret.timeStamp = moment.utc(record.timeStamp);\n return ret;\n });\n return cardHistory;\n } catch (e) {\n const reason = e as PouchError;\n if (reason.status === 404) {\n try {\n const initCardHistory: CardHistory<T> = {\n _id: cardHistoryID,\n cardID: record.cardID,\n courseID: record.courseID,\n records: [record],\n lapses: 0,\n streak: 0,\n bestInterval: 0,\n };\n const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);\n return { ...initCardHistory, _rev: putResult.rev };\n } catch (creationError) {\n throw new Error(\n `Failed to create CardHistory for ${cardHistoryID}. Reason: ${creationError}`\n );\n }\n } else {\n throw new Error(`putCardRecord failed because of:\n name:${reason.name}\n error: ${reason.error}\n message: ${reason.message}`);\n }\n }\n }\n\n private async deduplicateReviews() {\n try {\n log('Starting deduplication of scheduled reviews...');\n log(`Remote DB name: ${this.remoteDB.name || 'unknown'}`);\n log(`Write DB name: ${this.writeDB.name || 'unknown'}`);\n /**\n * Maps the qualified-id of a scheduled review card to\n * the docId of the same scheduled review.\n *\n * EG: {\n * courseId-cardId: 'card_review_2021-06--17:12:165'\n * }\n */\n const reviewsMap: { [index: string]: string } = {};\n const duplicateDocIds: string[] = [];\n\n log(\n `Attempting to query remoteDB for reviewCards/reviewCards. Database: ${this.remoteDB.name || 'unknown'}`\n );\n const scheduledReviews = await this.remoteDB.query<{\n id: string;\n value: string;\n }>('reviewCards/reviewCards');\n\n log(`Found ${scheduledReviews.rows.length} scheduled reviews to process`);\n\n // First pass: identify duplicates\n scheduledReviews.rows.forEach((r) => {\n const qualifiedCardId = r.value; // courseId-cardId\n const docId = r.key; // card_review_2021-06--17:12:165\n\n if (reviewsMap[qualifiedCardId]) {\n // this card is scheduled more than once! mark the earlier one for deletion\n log(`Found duplicate scheduled review for card: ${qualifiedCardId}`);\n log(\n `Marking earlier review ${reviewsMap[qualifiedCardId]} for deletion, keeping ${docId}`\n );\n duplicateDocIds.push(reviewsMap[qualifiedCardId]);\n // replace with the later-dated scheduled review\n reviewsMap[qualifiedCardId] = docId;\n } else {\n // note that this card is scheduled for review\n reviewsMap[qualifiedCardId] = docId;\n }\n });\n\n // Second pass: remove duplicates\n if (duplicateDocIds.length > 0) {\n log(`Removing ${duplicateDocIds.length} duplicate reviews...`);\n const deletePromises = duplicateDocIds.map(async (docId) => {\n try {\n const doc = await this.remoteDB.get(docId);\n await this.writeDB.remove(doc);\n log(`Successfully removed duplicate review: ${docId}`);\n } catch (error) {\n log(`Failed to remove duplicate review ${docId}: ${error}`);\n }\n });\n\n await Promise.all(deletePromises);\n log(`Deduplication complete. Processed ${duplicateDocIds.length} duplicates`);\n } else {\n log('No duplicate reviews found');\n }\n } catch (error) {\n log(`Error during review deduplication: ${error}`);\n if (error && typeof error === 'object' && 'status' in error && error.status === 404) {\n log(\n `Database not found (404) during review deduplication. Database: ${this.remoteDB.name || 'unknown'}`\n );\n log(\n `This might indicate the user database doesn't exist or the reviewCards view isn't available`\n );\n }\n // Log full error details for debugging\n if (error && typeof error === 'object') {\n log(`Full error details: ${JSON.stringify(error)}`);\n }\n }\n }\n\n /**\n * Returns a promise of the card IDs that the user has\n * encountered in the past.\n *\n * @param course_id optional specification of individual course\n */\n async getSeenCards(course_id?: string) {\n let prefix = DocTypePrefixes[DocType.CARDRECORD];\n if (course_id) {\n prefix += course_id;\n }\n const docs = await filterAllDocsByPrefix(this.localDB, prefix, {\n include_docs: false,\n });\n // const docs = await this.localDB.allDocs({});\n const ret: PouchDB.Core.DocumentId[] = [];\n docs.rows.forEach((row) => {\n if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {\n ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));\n }\n });\n return ret;\n }\n\n /**\n *\n * @returns A promise of the cards that the user has seen in the past.\n */\n async getHistory() {\n const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(\n this.remoteDB,\n DocTypePrefixes[DocType.CARDRECORD],\n {\n include_docs: true,\n attachments: false,\n }\n );\n return cards.rows.map((r) => r.doc);\n }\n\n async updateCourseSettings(course_id: string, settings: UserCourseSetting[]) {\n void this.getCourseRegistrationsDoc().then((doc) => {\n const crs = doc.courses.find((c) => c.courseID === course_id);\n if (crs) {\n if (crs.settings === null || crs.settings === undefined) {\n crs.settings = {};\n }\n settings.forEach((setting) => {\n crs!.settings![setting.key] = setting.value;\n });\n }\n\n return this.localDB.put(doc);\n });\n }\n async getCourseSettings(course_id: string) {\n const regDoc = await this.getCourseRegistrationsDoc();\n const crsDoc = regDoc.courses.find((c) => c.courseID === course_id);\n\n if (crsDoc) {\n return crsDoc.settings;\n } else {\n throw new Error(`getCourseSettings Failed:\n User is not registered for course ${course_id}`);\n }\n }\n\n private async getOrCreateClassroomRegistrationsDoc(): Promise<\n ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta\n > {\n let ret;\n\n try {\n ret = await this.remoteDB.get<ClassroomRegistrationDoc>(\n BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS\n );\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await this.writeDB.put<ClassroomRegistrationDoc>({\n _id: BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,\n registrations: [],\n });\n ret = await this.getOrCreateClassroomRegistrationsDoc();\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (private method):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n logger.debug(`Returning classroom registrations doc: ${JSON.stringify(ret)}`);\n return ret;\n }\n\n /**\n * Retrieves the list of active classroom IDs where the user is registered as a student.\n *\n * @returns Promise<string[]> - Array of classroom IDs, or empty array if classroom\n * registration document is unavailable due to database errors\n *\n * @description This method gracefully handles database connectivity issues by returning\n * an empty array when the classroom registrations document cannot be accessed.\n * This ensures that users can still access other application features even\n * when classroom functionality is temporarily unavailable.\n */\n public async getActiveClasses(): Promise<string[]> {\n try {\n return (await this.getOrCreateClassroomRegistrationsDoc()).registrations\n .filter((c) => c.registeredAs === 'student')\n .map((c) => c.classID);\n } catch (error) {\n logger.warn(\n 'Failed to load classroom registrations, continuing without classroom data:',\n error\n );\n // Return empty array so user can still access other features\n return [];\n }\n }\n\n public async scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: PouchDB.Core.DocumentId;\n time: Moment;\n scheduledFor: ScheduledCard['scheduledFor'];\n schedulingAgentId: ScheduledCard['schedulingAgentId'];\n }) {\n return scheduleCardReviewLocal(this.writeDB, review);\n }\n public async removeScheduledCardReview(reviewId: string): Promise<void> {\n return removeScheduledCardReviewLocal(this.writeDB, reviewId);\n }\n\n public async registerForClassroom(\n _classId: string,\n _registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response> {\n return registerUserForClassroom(this._username, _classId, _registerAs);\n }\n\n public async dropFromClassroom(classId: string): Promise<PouchDB.Core.Response> {\n return dropUserFromClassroom(this._username, classId);\n }\n public async getUserClassrooms(): Promise<ClassroomRegistrationDoc> {\n return getUserClassrooms(this._username);\n }\n\n public async updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response> {\n return updateUserElo(this._username, courseId, elo);\n }\n\n public async getStrategyState<T>(courseId: string, strategyKey: string): Promise<T | null> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n try {\n const doc = await this.localDB.get<StrategyStateDoc<T>>(docId);\n return doc.data;\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n return null;\n }\n throw e;\n }\n }\n\n public async putStrategyState<T>(courseId: string, strategyKey: string, data: T): Promise<void> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n let existingRev: string | undefined;\n\n try {\n const existing = await this.localDB.get<StrategyStateDoc<T>>(docId);\n existingRev = existing._rev;\n } catch (e) {\n const err = e as PouchError;\n if (err.status !== 404) {\n throw e;\n }\n }\n\n const doc: StrategyStateDoc<T> = {\n _id: docId,\n _rev: existingRev,\n docType: DocType.STRATEGY_STATE,\n courseId,\n strategyKey,\n data,\n updatedAt: new Date().toISOString(),\n };\n\n await this.localDB.put(doc);\n }\n\n public async putUserOutcome(record: UserOutcomeRecord): Promise<void> {\n try {\n await this.localDB.put(record);\n } catch (err: any) {\n if (err.status === 409) {\n // Overwrite if exists\n const existing = await this.localDB.get(record._id);\n (record as any)._rev = existing._rev;\n await this.localDB.put(record);\n } else {\n throw err;\n }\n }\n }\n\n public async deleteStrategyState(courseId: string, strategyKey: string): Promise<void> {\n const docId = buildStrategyStateId(courseId, strategyKey);\n try {\n const doc = await this.localDB.get(docId);\n await this.localDB.remove(doc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n return;\n }\n throw e;\n }\n }\n}\n\nexport function accomodateGuest(): {\n username: string;\n firstVisit: boolean;\n} {\n logger.log('[funnel] accomodateGuest() called');\n\n // Check if localStorage is available (browser environment)\n if (typeof localStorage === 'undefined') {\n logger.log(\n '[funnel] localStorage not available (Node.js environment), returning default guest'\n );\n return {\n username: GuestUsername + 'nodejs-test',\n firstVisit: true,\n };\n }\n\n const dbUUID = 'sk-guest-uuid';\n let firstVisit: boolean;\n\n const existingUUID = localStorage.getItem(dbUUID);\n logger.log('[funnel] Checking localStorage for key:', dbUUID);\n logger.log('[funnel] Existing UUID value:', existingUUID);\n logger.log('[funnel] existingUUID !== null:', existingUUID !== null);\n\n if (existingUUID !== null) {\n firstVisit = false;\n logger.log(`[funnel] Returning guest ${existingUUID} \"logging in\".`);\n } else {\n firstVisit = true;\n logger.log('[funnel] No existing UUID, generating new one...');\n const uuid = generateUUID();\n logger.log('[funnel] Generated UUID:', uuid);\n logger.log('[funnel] UUID length:', uuid.length);\n\n try {\n localStorage.setItem(dbUUID, uuid);\n logger.log('[funnel] Successfully stored UUID in localStorage');\n const verification = localStorage.getItem(dbUUID);\n logger.log('[funnel] Verification read from localStorage:', verification);\n } catch (e) {\n logger.error('[funnel] ERROR storing UUID:', e);\n }\n\n logger.log(`[funnel] Accommodating a new guest with account: ${uuid}`);\n }\n\n const finalUUID = localStorage.getItem(dbUUID);\n const finalUsername = GuestUsername + finalUUID;\n logger.log('[funnel] Final UUID from localStorage:', finalUUID);\n logger.log('[funnel] GuestUsername constant:', GuestUsername);\n logger.log('[funnel] Final username to return:', finalUsername);\n\n return {\n username: finalUsername,\n firstVisit: firstVisit,\n };\n\n // Use cryptographically secure UUID generation\n function generateUUID() {\n logger.log('[funnel] Inside generateUUID()');\n\n // Use crypto.randomUUID() if available (Node 14.17+ / modern browsers)\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n const uuid = crypto.randomUUID();\n logger.log('[funnel] Generated UUID using crypto.randomUUID():', uuid);\n return uuid;\n }\n\n // Fallback for older environments: use crypto.getRandomValues()\n if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n // Set version (4) and variant bits according to RFC 4122\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n\n const uuid = [\n Array.from(bytes.slice(0, 4))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(4, 6))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(6, 8))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(8, 10))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n Array.from(bytes.slice(10, 16))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join(''),\n ].join('-');\n\n logger.log('[funnel] Generated UUID using crypto.getRandomValues():', uuid);\n return uuid;\n }\n\n // Last resort fallback (should never happen in modern environments)\n logger.warn('[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)');\n let d = new Date().getTime();\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n d += performance.now();\n }\n const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n logger.log('[funnel] Generated UUID (fallback):', uuid);\n return uuid;\n }\n}\n\nconst userCoursesDoc = 'CourseRegistrations';\nconst userClassroomsDoc = 'ClassroomRegistrations';\n\nasync function getOrCreateClassroomRegistrationsDoc(\n user: string\n): Promise<ClassroomRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<ClassroomRegistrationDoc>(userClassroomsDoc);\n } catch (e) {\n const err = e as PouchError;\n\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<ClassroomRegistrationDoc>({\n _id: userClassroomsDoc,\n registrations: [],\n });\n ret = await getOrCreateClassroomRegistrationsDoc(user);\n } else {\n // Properly serialize error information\n const errorDetails = {\n name: err.name,\n status: err.status,\n message: err.message,\n reason: err.reason,\n error: err.error,\n };\n\n logger.error(\n 'Database error in getOrCreateClassroomRegistrationsDoc (standalone function):',\n errorDetails\n );\n\n throw new Error(\n `Database error accessing classroom registrations: ${err.message || err.name || 'Unknown error'} (status: ${err.status})`\n );\n }\n }\n\n return ret;\n}\n\nasync function getOrCreateCourseRegistrationsDoc(\n user: string\n): Promise<CourseRegistrationDoc & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta> {\n let ret;\n\n try {\n ret = await getLocalUserDB(user).get<CourseRegistrationDoc>(userCoursesDoc);\n } catch (e) {\n const err = e as PouchError;\n if (err.status === 404) {\n // doc does not exist. Create it and then run this fcn again.\n await getLocalUserDB(user).put<CourseRegistrationDoc>({\n _id: userCoursesDoc,\n courses: [],\n studyWeight: {},\n });\n ret = await getOrCreateCourseRegistrationsDoc(user);\n } else {\n throw new Error(\n `Unexpected error ${JSON.stringify(e)} in getOrCreateCourseRegistrationDoc...`\n );\n }\n }\n\n return ret;\n}\n\nexport async function updateUserElo(user: string, course_id: string, elo: CourseElo) {\n const regDoc = await getOrCreateCourseRegistrationsDoc(user);\n const course = regDoc.courses.find((c) => c.courseID === course_id)!;\n course.elo = elo;\n return getLocalUserDB(user).put(regDoc);\n}\n\nexport async function registerUserForClassroom(\n user: string,\n classID: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n) {\n log(`Registering user: ${user} in course: ${classID}`);\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n const regItem = {\n classID: classID,\n registeredAs: registerAs,\n };\n\n if (\n doc.registrations.filter((reg) => {\n return reg.classID === regItem.classID && reg.registeredAs === regItem.registeredAs;\n }).length === 0\n ) {\n doc.registrations.push(regItem);\n } else {\n log(`User ${user} is already registered for class ${classID}`);\n }\n\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function dropUserFromClassroom(user: string, classID: string) {\n return getOrCreateClassroomRegistrationsDoc(user).then((doc) => {\n let index: number = -1;\n\n for (let i = 0; i < doc.registrations.length; i++) {\n if (doc.registrations[i].classID === classID) {\n index = i;\n }\n }\n\n if (index !== -1) {\n doc.registrations.splice(index, 1);\n }\n return getLocalUserDB(user).put(doc);\n });\n}\n\nexport async function getUserClassrooms(user: string) {\n return getOrCreateClassroomRegistrationsDoc(user);\n}\n","// packages/db/src/impl/common/index.ts\n\nexport type { SyncStrategy } from './SyncStrategy';\nexport { BaseSyncStrategy } from './SyncStrategy';\nexport type {\n AccountCreationResult,\n AuthenticationResult,\n UserSession,\n SyncConfig,\n SyncStatus,\n} from './types';\nexport { BaseUser, accomodateGuest } from './BaseUserDB';\nexport {\n REVIEW_TIME_FORMAT,\n hexEncode,\n filterAllDocsByPrefix,\n getStartAndEndKeys,\n updateGuestAccountExpirationDate,\n getLocalUserDB,\n scheduleCardReviewLocal,\n removeScheduledCardReviewLocal,\n} from './userDBHelpers';\n","// db/src/factory.ts\n\nimport { DataLayerProvider } from './core/interfaces';\nimport { BaseUser } from './impl/common';\nimport { logger } from './util/logger';\nimport { StaticCourseManifest } from './util/packer/types';\nimport { initializeNavigatorRegistry } from './core/navigators';\n\nconst NOT_SET = 'NOT_SET' as const;\n\ninterface DBEnv {\n COUCHDB_SERVER_URL: string; // URL of CouchDB server\n COUCHDB_SERVER_PROTOCOL: string; // Protocol of CouchDB server (http or https)\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n LOCAL_STORAGE_PREFIX: string; // Prefix for IndexedDB storage names\n}\n\nexport const ENV: DBEnv = {\n COUCHDB_SERVER_PROTOCOL: NOT_SET,\n COUCHDB_SERVER_URL: NOT_SET,\n LOCAL_STORAGE_PREFIX: '',\n};\n\nexport { NOT_SET };\n\n// Configuration type for data layer initialization\nexport interface DataLayerConfig {\n type: 'couch' | 'static';\n options: {\n staticContentPath?: string; // Path to static content JSON files\n localStoragePrefix?: string; // Prefix for IndexedDB storage names\n manifests?: Record<string, StaticCourseManifest>; // Course manifests for static mode\n COUCHDB_SERVER_URL?: string;\n COUCHDB_SERVER_PROTOCOL?: string;\n COUCHDB_USERNAME?: string;\n COUCHDB_PASSWORD?: string;\n\n COURSE_IDS?: string[];\n };\n}\n\n// Singleton instance\nlet dataLayerInstance: DataLayerProvider | null = null;\n\n/**\n * Initialize the data layer with the specified configuration\n */\nexport async function initializeDataLayer(config: DataLayerConfig): Promise<DataLayerProvider> {\n if (dataLayerInstance) {\n logger.warn('Data layer already initialized. Returning existing instance.');\n return dataLayerInstance;\n }\n\n // Initialize the navigator registry before creating the data layer.\n // This ensures all built-in navigators are available for pipeline assembly.\n await initializeNavigatorRegistry();\n\n if (config.options.localStoragePrefix) {\n ENV.LOCAL_STORAGE_PREFIX = config.options.localStoragePrefix;\n }\n\n if (config.type === 'couch') {\n if (!config.options.COUCHDB_SERVER_URL || !config.options.COUCHDB_SERVER_PROTOCOL) {\n throw new Error('Missing CouchDB server URL or protocol');\n }\n ENV.COUCHDB_SERVER_PROTOCOL = config.options.COUCHDB_SERVER_PROTOCOL;\n ENV.COUCHDB_SERVER_URL = config.options.COUCHDB_SERVER_URL;\n ENV.COUCHDB_USERNAME = config.options.COUCHDB_USERNAME;\n ENV.COUCHDB_PASSWORD = config.options.COUCHDB_PASSWORD;\n\n if (\n config.options.COUCHDB_PASSWORD &&\n config.options.COUCHDB_USERNAME &&\n typeof window !== 'undefined'\n ) {\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDBSyncStrategy } = await import('./impl/couch/CouchDBSyncStrategy');\n\n // Create a sync strategy instance and authenticate\n const syncStrategy = new CouchDBSyncStrategy();\n\n const user = await BaseUser.instance(syncStrategy, config.options.COUCHDB_USERNAME);\n const authResult = await user.login(\n config.options.COUCHDB_USERNAME,\n config.options.COUCHDB_PASSWORD\n );\n\n if (authResult.ok) {\n logger.info(`Successfully authenticated as ${config.options.COUCHDB_USERNAME}`);\n } else {\n logger.warn(`Authentication failed: ${authResult.error}`);\n }\n }\n\n // Dynamic import to avoid loading both implementations when only one is needed\n const { CouchDataLayerProvider } = await import('./impl/couch/PouchDataLayerProvider');\n dataLayerInstance = new CouchDataLayerProvider(config.options.COURSE_IDS);\n } else if (config.type === 'static') {\n const { StaticDataLayerProvider } = await import('./impl/static/StaticDataLayerProvider');\n dataLayerInstance = new StaticDataLayerProvider(config.options);\n } else {\n throw new Error(`Unknown data layer type: ${config.type}`);\n }\n\n await dataLayerInstance.initialize();\n return dataLayerInstance;\n}\n\n/**\n * Get the initialized data layer instance\n * @throws Error if not initialized\n */\nexport function getDataLayer(): DataLayerProvider {\n if (!dataLayerInstance) {\n throw new Error('Data layer not initialized. Call initializeDataLayer first.');\n }\n return dataLayerInstance;\n}\n\n/**\n * Reset the data layer (primarily for testing)\n */\nexport async function _resetDataLayer(): Promise<void> {\n if (dataLayerInstance) {\n await dataLayerInstance.teardown();\n }\n dataLayerInstance = null;\n}\n","import { StudyContentSource } from '@db/core/interfaces/contentSource';\nimport { WeightedCard } from '@db/core/navigators';\nimport type { GeneratorResult } from '@db/core/navigators/generators/types';\nimport { UserDBInterface } from '@db/core';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { getTag } from '../impl/couch/courseDB';\nimport { logger } from '@db/util/logger';\n\n/**\n * A StudyContentSource that filters cards based on tag inclusion/exclusion.\n *\n * This enables ephemeral, tag-scoped study sessions where users can focus\n * on specific topics within a course without permanent configuration.\n *\n * Filter logic:\n * - If `include` is non-empty: card must have at least one of the included tags\n * - If `exclude` is non-empty: card must not have any of the excluded tags\n * - Both filters are applied (include first, then exclude)\n */\nexport class TagFilteredContentSource implements StudyContentSource {\n private courseId: string;\n private filter: TagFilter;\n private user: UserDBInterface;\n\n // Cache resolved card IDs to avoid repeated lookups within a session\n private resolvedCardIds: Set<string> | null = null;\n\n constructor(courseId: string, filter: TagFilter, user: UserDBInterface) {\n this.courseId = courseId;\n this.filter = filter;\n this.user = user;\n\n logger.info(\n `[TagFilteredContentSource] Created for course \"${courseId}\" with filter:`,\n JSON.stringify(filter)\n );\n }\n\n /**\n * Resolves the TagFilter to a set of eligible card IDs.\n *\n * - Cards in `include` tags are OR'd together (card needs at least one)\n * - Cards in `exclude` tags are removed from the result\n */\n private async resolveFilteredCardIds(): Promise<Set<string>> {\n // Return cached result if available\n if (this.resolvedCardIds !== null) {\n return this.resolvedCardIds;\n }\n\n const includedCardIds = new Set<string>();\n\n // Build inclusion set (OR of all include tags)\n if (this.filter.include.length > 0) {\n for (const tagName of this.filter.include) {\n try {\n const tagDoc = await getTag(this.courseId, tagName);\n tagDoc.taggedCards.forEach((cardId) => includedCardIds.add(cardId));\n } catch (error) {\n logger.warn(\n `[TagFilteredContentSource] Could not resolve tag \"${tagName}\" for inclusion:`,\n error\n );\n }\n }\n }\n\n // If no include tags specified or none resolved, return empty set\n // (requiring explicit inclusion prevents \"study everything\" on empty filter)\n if (includedCardIds.size === 0 && this.filter.include.length > 0) {\n logger.warn(\n `[TagFilteredContentSource] No cards found for include tags: ${this.filter.include.join(', ')}`\n );\n this.resolvedCardIds = new Set();\n return this.resolvedCardIds;\n }\n\n // Build exclusion set\n const excludedCardIds = new Set<string>();\n if (this.filter.exclude.length > 0) {\n for (const tagName of this.filter.exclude) {\n try {\n const tagDoc = await getTag(this.courseId, tagName);\n tagDoc.taggedCards.forEach((cardId) => excludedCardIds.add(cardId));\n } catch (error) {\n logger.warn(\n `[TagFilteredContentSource] Could not resolve tag \"${tagName}\" for exclusion:`,\n error\n );\n }\n }\n }\n\n // Apply exclusion filter\n const finalCardIds = new Set<string>();\n for (const cardId of includedCardIds) {\n if (!excludedCardIds.has(cardId)) {\n finalCardIds.add(cardId);\n }\n }\n\n logger.info(\n `[TagFilteredContentSource] Resolved ${finalCardIds.size} cards ` +\n `(included: ${includedCardIds.size}, excluded: ${excludedCardIds.size})`\n );\n\n this.resolvedCardIds = finalCardIds;\n return finalCardIds;\n }\n\n /**\n * Get cards with suitability scores for presentation.\n *\n * Filters cards by tag inclusion/exclusion and assigns score=1.0 to all.\n * TagFilteredContentSource does not currently support pluggable navigation\n * strategies - it returns flat-scored candidates.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending (all scores = 1.0)\n */\n public async getWeightedCards(limit: number): Promise<GeneratorResult> {\n if (!hasActiveFilter(this.filter)) {\n logger.warn('[TagFilteredContentSource] getWeightedCards called with no active filter');\n return { cards: [] };\n }\n\n const eligibleCardIds = await this.resolveFilteredCardIds();\n\n // Get new cards: eligible cards that are not already active\n const activeCards = await this.user.getActiveCards();\n const activeCardIds = new Set(activeCards.map((c) => c.cardID));\n\n const newCardWeighted: WeightedCard[] = [];\n for (const cardId of eligibleCardIds) {\n if (!activeCardIds.has(cardId)) {\n newCardWeighted.push({\n cardId,\n courseId: this.courseId,\n score: 1.0,\n provenance: [\n {\n strategy: 'tagFilter',\n strategyName: 'Tag Filter',\n strategyId: 'TAG_FILTER',\n action: 'generated' as const,\n score: 1.0,\n reason: `Tag-filtered new card (tags: ${this.filter.include.join(', ')})`,\n },\n ],\n });\n }\n\n if (newCardWeighted.length >= limit) {\n break;\n }\n }\n\n logger.info(\n `[TagFilteredContentSource] Found ${newCardWeighted.length} new cards matching filter`\n );\n\n // Get pending reviews: reviews for cards in the eligible set\n const allReviews = await this.user.getPendingReviews(this.courseId);\n const filteredReviews = allReviews.filter((review) => eligibleCardIds.has(review.cardId));\n\n logger.info(\n `[TagFilteredContentSource] Found ${filteredReviews.length} pending reviews matching filter ` +\n `(of ${allReviews.length} total)`\n );\n\n const reviewWeighted: WeightedCard[] = filteredReviews.map((r) => ({\n cardId: r.cardId,\n courseId: r.courseId,\n score: 1.0,\n reviewID: r._id,\n provenance: [\n {\n strategy: 'tagFilter',\n strategyName: 'Tag Filter',\n strategyId: 'TAG_FILTER',\n action: 'generated' as const,\n score: 1.0,\n reason: `Tag-filtered review (tags: ${this.filter.include.join(', ')})`,\n },\n ],\n }));\n\n // Reviews first, then new cards; respect limit\n return { cards: [...reviewWeighted, ...newCardWeighted].slice(0, limit) };\n }\n\n /**\n * Clears the cached resolved card IDs.\n * Call this if the underlying tag data may have changed during a session.\n */\n public clearCache(): void {\n this.resolvedCardIds = null;\n }\n\n /**\n * Returns the course ID this source is filtering.\n */\n public getCourseId(): string {\n return this.courseId;\n }\n\n /**\n * Returns the active tag filter.\n */\n public getFilter(): TagFilter {\n return this.filter;\n }\n}\n","import { getDataLayer } from '@db/factory';\nimport { UserDBInterface } from '..';\nimport { StudentClassroomDB } from '../../impl/couch/classroomDB';\nimport type { GeneratorResult, ReplanHints } from '../navigators/generators/types';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { TagFilteredContentSource } from '../../study/TagFilteredContentSource';\nimport { OrchestrationContext } from '../orchestration';\n\nexport type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;\n\nexport interface StudySessionFailedNewItem extends StudySessionItem {\n status: 'failed-new';\n}\nexport interface StudySessionFailedReviewItem extends StudySessionReviewItem {\n status: 'failed-review';\n}\n\nexport interface StudySessionNewItem extends StudySessionItem {\n status: 'new';\n}\n\nexport interface StudySessionReviewItem extends StudySessionItem {\n reviewID: string;\n status: 'review' | 'failed-review';\n}\nexport function isReview(item: StudySessionItem): item is StudySessionReviewItem {\n const ret = item.status === 'review' || item.status === 'failed-review' || 'reviewID' in item;\n\n // console.log(`itemIsReview: ${ret}\n // \\t${JSON.stringify(item)}`);\n\n return ret;\n}\n\nexport interface StudySessionItem {\n status: 'new' | 'review' | 'failed-new' | 'failed-review';\n contentSourceType: 'course' | 'classroom';\n contentSourceID: string;\n // qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`;\n cardID: string;\n courseID: string;\n elo?: number;\n // reviewID?: string;\n}\n\nexport interface ContentSourceID {\n type: 'course' | 'classroom';\n id: string;\n /**\n * Optional tag filter for scoped study sessions.\n * When present, creates a TagFilteredContentSource instead of a regular course source.\n */\n tagFilter?: TagFilter;\n}\n\n// #region docs_StudyContentSource\n/**\n * Interface for sources that provide study content to SessionController.\n *\n * Content sources return scored candidates via getWeightedCards(), which\n * SessionController uses to populate study queues.\n *\n * See: packages/db/docs/navigators-architecture.md\n */\nexport interface StudyContentSource {\n /**\n * Get cards with suitability scores for presentation.\n *\n * Returns unified scored candidates that can be sorted and selected by SessionController.\n * The card origin ('new' | 'review' | 'failed') is determined by provenance metadata.\n *\n * @param limit - Maximum number of cards to return\n * @returns Cards sorted by score descending\n */\n getWeightedCards(limit: number): Promise<GeneratorResult>;\n\n /**\n * Get the orchestration context for this source.\n * Used for recording learning outcomes.\n */\n getOrchestrationContext?(): Promise<OrchestrationContext>;\n\n /**\n * Set ephemeral hints for the next pipeline run.\n * No-op for sources that don't support hints.\n */\n setEphemeralHints?(hints: ReplanHints): void;\n}\n// #endregion docs_StudyContentSource\n\nexport async function getStudySource(\n source: ContentSourceID,\n user: UserDBInterface\n): Promise<StudyContentSource> {\n if (source.type === 'classroom') {\n return await StudentClassroomDB.factory(source.id, user);\n } else {\n // Check if this is a tag-filtered course source\n if (hasActiveFilter(source.tagFilter)) {\n return new TagFilteredContentSource(source.id, source.tagFilter!, user);\n }\n\n // Regular course source\n return getDataLayer().getCourseDB(source.id) as unknown as StudyContentSource;\n }\n}\n","import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';\nimport { StudyContentSource, StudySessionItem } from './contentSource';\nimport { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';\nimport { DataLayerResult } from '../types/db';\nimport { NavigationStrategyManager } from './navigationStrategyManager';\n\n/**\n * Course content and management\n */\nexport interface CoursesDBInterface {\n /**\n * Get course config\n */\n getCourseConfig(courseId: string): Promise<CourseConfig>;\n\n /**\n * Get a list of all courses\n */\n getCourseList(): Promise<CourseConfig[]>;\n\n disambiguateCourse(courseId: string, disambiguator: string): Promise<void>;\n}\n\nexport interface CourseInfo {\n cardCount: number;\n registeredUsers: number;\n}\n\nexport interface CourseDBInterface extends NavigationStrategyManager, StudyContentSource {\n /**\n * Get course config\n */\n getCourseConfig(): Promise<CourseConfig>;\n\n getCourseID(): string;\n\n /**\n * Set course config\n */\n updateCourseConfig(cfg: CourseConfig): Promise<PouchDB.Core.Response>;\n\n getCourseInfo(): Promise<CourseInfo>;\n\n getCourseDoc<T extends SkuilderCourseData>(\n id: string,\n options?: PouchDB.Core.GetOptions\n ): Promise<T>;\n getCourseDocs<T extends SkuilderCourseData>(\n ids: string[],\n options?: PouchDB.Core.AllDocsOptions\n ): Promise<PouchDB.Core.AllDocsWithKeysResponse<{} & T>>;\n\n /**\n * Get cards sorted by ELO rating\n */\n getCardsByELO(\n elo: number,\n limit?: number\n ): Promise<\n {\n courseID: string;\n cardID: string;\n elo?: number;\n }[]\n >;\n\n /**\n * Get ELO data for specific cards\n */\n getCardEloData(cardIds: string[]): Promise<CourseElo[]>;\n\n /**\n * Update card ELO rating\n */\n updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Get cards centered at a particular ELO rating\n */\n getCardsCenteredAtELO(\n options: { limit: number; elo: 'user' | 'random' | number },\n filter?: (card: QualifiedCardID) => boolean\n ): Promise<StudySessionItem[]>;\n\n /**\n * Get tags for a card\n */\n getAppliedTags(cardId: string): Promise<PouchDB.Query.Response<TagStub>>;\n\n /**\n * Get tags for multiple cards in a single batch query.\n * More efficient than calling getAppliedTags() for each card.\n *\n * This method reduces redundant database operations when multiple filters\n * need tag data for the same cards. The Pipeline uses this to pre-hydrate\n * tags on WeightedCard objects before filters run.\n *\n * @param cardIds - Array of card IDs to fetch tags for\n * @returns Map from cardId to array of tag names\n */\n getAppliedTagsBatch(cardIds: string[]): Promise<Map<string, string[]>>;\n\n /**\n * Get all card IDs in the course.\n * Used by diagnostics to scan the full card space.\n */\n getAllCardIds(): Promise<string[]>;\n\n /**\n * Add a tag to a card\n */\n addTagToCard(cardId: string, tagId: string, updateELO?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Remove a tag from a card\n */\n removeTagFromCard(cardId: string, tagId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Create a new tag\n */\n createTag(tagName: string, author: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Get a tag by name\n */\n getTag(tagName: string): Promise<Tag>;\n\n /**\n * Update a tag\n */\n updateTag(tag: Tag): Promise<PouchDB.Core.Response>;\n\n /**\n * Get all tag stubs for a course\n */\n getCourseTagStubs(): Promise<PouchDB.Core.AllDocsResponse<Tag>>;\n\n /**\n * Add a note to the course\n */\n addNote(\n codeCourse: string,\n shape: DataShape,\n data: unknown,\n author: string,\n tags: string[],\n uploads?: { [key: string]: PouchDB.Core.FullAttachment },\n elo?: CourseElo\n ): Promise<DataLayerResult>;\n\n removeCard(cardId: string): Promise<PouchDB.Core.Response>;\n\n getInexperiencedCards(): Promise<\n {\n courseId: string;\n cardId: string;\n count: number;\n elo: CourseElo;\n }[]\n >;\n\n /**\n * Search for cards by text content\n * @param query Text to search for\n * @returns Array of matching card data\n */\n searchCards(query: string): Promise<any[]>;\n\n /**\n * Find documents using PouchDB query syntax\n * @param request PouchDB find request\n * @returns Query response\n */\n find(request: PouchDB.Find.FindRequest<any>): Promise<PouchDB.Find.FindResponse<any>>;\n}\n","// db/src/core/interfaces.ts\n\nimport { UserDBInterface, UserDBReader } from './userDB';\nimport { CourseDBInterface, CoursesDBInterface } from './courseDB';\nimport { ClassroomDBInterface } from './classroomDB';\nimport { AdminDBInterface } from './adminDB';\n\n/**\n * Main factory interface for data access\n */\nexport interface DataLayerProvider {\n /**\n * Get the user database interface\n */\n getUserDB(): UserDBInterface;\n\n /**\n * Create a UserDBReader for a specific user (admin access required)\n * Uses session authentication to verify requesting user is admin\n * @param targetUsername - The username to create a reader for\n * @throws Error if requesting user is not 'admin'\n */\n createUserReaderForUser(targetUsername: string): Promise<UserDBReader>;\n\n /**\n * Get a course database interface\n */\n getCourseDB(courseId: string): CourseDBInterface;\n\n /**\n * Get the courses-lookup interface\n */\n getCoursesDB(): CoursesDBInterface;\n\n /**\n * Get a classroom database interface\n */\n getClassroomDB(classId: string, type: 'student' | 'teacher'): Promise<ClassroomDBInterface>;\n\n /**\n * Get the admin database interface\n */\n getAdminDB(): AdminDBInterface;\n\n /**\n * Initialize the data layer\n */\n initialize(): Promise<void>;\n\n /**\n * Teardown the data layer\n */\n teardown(): Promise<void>;\n\n /**\n * Check if this data layer is read-only\n */\n isReadOnly(): boolean;\n\n /**\n * Trigger local replication of a course database.\n *\n * When a course opts in via `CourseConfig.localSync.enabled`, this method\n * replicates the remote course DB to a local PouchDB instance. Subsequent\n * `getCourseDB()` calls for that course will return a CourseDB that reads\n * from the local replica (fast, no network) and writes to the remote\n * (ELO updates, admin ops).\n *\n * Safe to call multiple times — concurrent calls coalesce. Returns when\n * sync is complete (or immediately if already synced / disabled).\n *\n * Implementations that don't support local sync may no-op.\n *\n * @param courseId - The course to sync locally\n * @param forceEnabled - Skip CourseConfig check and sync regardless.\n * Use when the caller already knows local sync is desired.\n */\n ensureCourseSynced?(courseId: string, forceEnabled?: boolean): Promise<void>;\n}\n","import {\n ActivityRecord,\n CourseRegistration,\n CourseRegistrationDoc,\n ScheduledCard,\n} from '@db/core/types/user';\nimport { CourseElo, Status } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\nimport { CardHistory, CardRecord, QualifiedCardID } from '../types/types-legacy';\nimport { UserOutcomeRecord } from '../types/userOutcome';\nimport { UserConfig } from '../types/user';\nimport { DocumentUpdater } from '@db/study';\n\n/**\n * Read-only user data operations\n */\nexport interface UserDBReader {\n get<T>(id: string): Promise<T & PouchDB.Core.RevisionIdMeta>;\n getUsername(): string;\n isLoggedIn(): boolean;\n\n /**\n * Get user configuration\n */\n getConfig(): Promise<UserConfig>;\n\n /**\n * Get cards that the user has seen\n */\n getSeenCards(courseId?: string): Promise<string[]>;\n\n /**\n * Get cards that are actively scheduled for review\n */\n getActiveCards(): Promise<QualifiedCardID[]>;\n\n /**\n * Get user's course registrations\n */\n getCourseRegistrationsDoc(): Promise<CourseRegistrationDoc>;\n\n /**\n * Get the registration doc for a specific course.\n * @param courseId\n */\n getCourseRegDoc(courseId: string): Promise<CourseRegistration>;\n\n /**\n * Get user's active courses\n */\n getActiveCourses(): Promise<CourseRegistration[]>;\n\n /**\n * Get user's pending reviews\n */\n getPendingReviews(courseId?: string): Promise<ScheduledCard[]>;\n\n getActivityRecords(): Promise<ActivityRecord[]>;\n\n /**\n * Get strategy-specific state for a course.\n *\n * Strategies use this to persist preferences, learned patterns, or temporal\n * tracking data across sessions. Each strategy owns its own namespace.\n *\n * @deprecated Use `getCourseInterface(courseId).getStrategyState(strategyKey)` instead.\n * Direct use bypasses course-scoping safety — the courseId parameter is unguarded,\n * allowing accidental cross-course data access. The course-scoped interface binds\n * courseId once at construction.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n * @returns The strategy's data payload, or null if no state exists\n */\n getStrategyState<T>(courseId: string, strategyKey: string): Promise<T | null>;\n\n /**\n * Get user's classroom registrations\n */\n getUserClassrooms(): Promise<ClassroomRegistrationDoc>;\n\n /**\n * Get user's active classes\n */\n getActiveClasses(): Promise<string[]>;\n\n getCourseInterface(courseId: string): Promise<UsrCrsDataInterface>;\n}\n\n/**\n * User data mutation operations\n */\nexport interface UserDBWriter extends DocumentUpdater {\n /**\n * Update user configuration\n */\n setConfig(config: Partial<UserConfig>): Promise<void>;\n\n /**\n * Record a user's interaction with a card\n */\n putCardRecord<T extends CardRecord>(record: T): Promise<CardHistory<CardRecord>>;\n\n /**\n * Register user for a course\n */\n registerForCourse(courseId: string, previewMode?: boolean): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop a course registration\n */\n dropCourse(courseId: string, dropStatus?: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Schedule a card for review\n */\n scheduleCardReview(review: {\n user: string;\n course_id: string;\n card_id: string;\n time: Moment;\n scheduledFor: 'course' | 'classroom';\n schedulingAgentId: string;\n }): Promise<void>;\n\n /**\n * Remove a scheduled card review\n */\n removeScheduledCardReview(reviewId: string): Promise<void>;\n\n /**\n * Register user for a classroom\n */\n registerForClassroom(\n classId: string,\n registerAs: 'student' | 'teacher' | 'aide' | 'admin'\n ): Promise<PouchDB.Core.Response>;\n\n /**\n * Drop user from classroom\n */\n dropFromClassroom(classId: string): Promise<PouchDB.Core.Response>;\n\n /**\n * Update user's ELO rating for a course\n */\n updateUserElo(courseId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;\n\n /**\n * Reset all user data (progress, registrations, etc.) while preserving authentication\n */\n resetUserData(): Promise<{ status: Status; error?: string }>;\n\n /**\n * Store strategy-specific state for a course.\n *\n * Strategies use this to persist preferences, learned patterns, or temporal\n * tracking data across sessions. Each strategy owns its own namespace.\n *\n * @deprecated Use `getCourseInterface(courseId).putStrategyState(strategyKey, data)` instead.\n * Direct use bypasses course-scoping safety — the courseId parameter is unguarded,\n * allowing accidental cross-course data writes. The course-scoped interface binds\n * courseId once at construction.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n * @param data - The strategy's data payload to store\n */\n putStrategyState<T>(courseId: string, strategyKey: string, data: T): Promise<void>;\n\n /**\n * Delete strategy-specific state for a course.\n *\n * @deprecated Use `getCourseInterface(courseId).deleteStrategyState(strategyKey)` instead.\n * Direct use bypasses course-scoping safety.\n *\n * @param courseId - The course this state applies to\n * @param strategyKey - Unique key identifying the strategy (typically class name)\n */\n deleteStrategyState(courseId: string, strategyKey: string): Promise<void>;\n\n /**\n * Record a user learning outcome for evolutionary orchestration.\n */\n putUserOutcome(record: UserOutcomeRecord): Promise<void>;\n}\n\n/**\n * Authentication and account management operations\n */\nexport interface UserDBAuthenticator {\n /**\n * Create a new user account\n */\n createAccount(\n username: string,\n password: string\n ): Promise<{\n status: Status;\n error: string;\n }>;\n\n /**\n * Log in as a user\n */\n login(\n username: string,\n password: string\n ): Promise<{\n ok: boolean;\n name?: string;\n roles?: string[];\n }>;\n\n /**\n * Log out the current user\n */\n logout(): Promise<{\n ok: boolean;\n }>;\n}\n\n/**\n * Complete user database interface - combines all user operations\n * This maintains backward compatibility with existing code\n */\nexport interface UserDBInterface extends UserDBReader, UserDBWriter, UserDBAuthenticator {}\n\nexport interface UserCourseSettings {\n [setting: string]: string | number | boolean;\n}\n\nexport interface UserCourseSetting {\n key: string;\n value: string | number | boolean;\n}\n\n// [ ] reconsider here. Should maybe be generic type based on <T extends StudyContentSource> ?\nexport interface UsrCrsDataInterface {\n getScheduledReviewCount(): Promise<number>;\n getCourseSettings(): Promise<UserCourseSettings>;\n updateCourseSettings(updates: UserCourseSetting[]): void; // [ ] return a result of some sort?\n // getRegistrationDoc(): Promise<CourseRegistration>;\n\n /**\n * Get strategy-specific state for this course.\n *\n * Course-scoped alternative to `UserDBInterface.getStrategyState()`.\n * The courseId is bound at construction via `getCourseInterface(courseId)`,\n * so callers cannot accidentally access another course's state.\n *\n * @param strategyKey - Unique key identifying the state document\n * @returns The state payload, or null if no state exists\n */\n getStrategyState<T>(strategyKey: string): Promise<T | null>;\n\n /**\n * Store strategy-specific state for this course.\n *\n * Course-scoped alternative to `UserDBInterface.putStrategyState()`.\n *\n * @param strategyKey - Unique key identifying the state document\n * @param data - The state payload to store\n */\n putStrategyState<T>(strategyKey: string, data: T): Promise<void>;\n\n /**\n * Delete strategy-specific state for this course.\n *\n * Course-scoped alternative to `UserDBInterface.deleteStrategyState()`.\n *\n * @param strategyKey - Unique key identifying the state document\n */\n deleteStrategyState(strategyKey: string): Promise<void>;\n}\n\nexport type ClassroomRegistrationDesignation = 'student' | 'teacher' | 'aide' | 'admin';\n\nexport interface ClassroomRegistration {\n classID: string;\n registeredAs: ClassroomRegistrationDesignation;\n}\n\nexport interface ClassroomRegistrationDoc {\n registrations: ClassroomRegistration[];\n}\n","export * from './adminDB';\nexport * from './classroomDB';\nexport * from './contentSource';\nexport * from './courseDB';\nexport * from './dataLayerProvider';\n\nexport * from './userDB';\n","import { CourseElo } from '@vue-skuilder/common';\nimport { Moment } from 'moment';\n\nexport interface SessionTrackingData {\n peekSessionCount: number;\n studySessionCount: number;\n sessionCount: number; // total\n firstSessionDate: string;\n lastSessionDate: string;\n signupPrompted: boolean;\n promptDismissalCount: number;\n studyModeAcknowledged: boolean;\n}\n\nexport interface UserConfig {\n darkMode: boolean;\n likesConfetti: boolean;\n sessionTimeLimit: number; // Session time limit in minutes\n email?: string; // Optional email for verification flows (added for enhanced auth)\n\n // Session tracking for trial enforcement (per-course)\n // Key is courseId (e.g., 'letterspractice-basic')\n sessionTracking?: Record<string, SessionTrackingData>;\n}\n\nexport interface ActivityRecord {\n timeStamp: number | string;\n [key: string]: any;\n}\n\nexport interface CourseRegistration {\n status?: 'active' | 'dropped' | 'maintenance-mode' | 'preview';\n courseID: string;\n admin: boolean;\n moderator: boolean;\n user: boolean;\n settings?: {\n [setting: string]: string | number | boolean;\n };\n elo: number | CourseElo;\n}\n\ninterface StudyWeights {\n [courseID: string]: number;\n}\n\nexport interface CourseRegistrationDoc {\n courses: CourseRegistration[];\n studyWeight: StudyWeights;\n}\n\nexport interface ScheduledCard {\n _id: PouchDB.Core.DocumentId;\n\n /**\n * The docID of the card to be reviewed\n */\n cardId: PouchDB.Core.DocumentId;\n /**\n * The ID of the course\n */\n courseId: string;\n /**\n * The time at which the card becomes eligible for review.\n *\n * (Should probably be UTC adjusted so that performance is\n * not wonky across time zones)\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(reviewTime)\n */\n reviewTime: string | Moment;\n\n /**\n * The time at which this scheduled event was created.\n * \n * Note: Stored as ISO string for PouchDB serialization compatibility,\n * but can be consumed as Moment objects via moment.utc(scheduledAt)\n */\n scheduledAt: string | Moment;\n\n /**\n * Classifying whether this card is scheduled on behalf of a\n * user-registered course or by as assigned content from a\n * user-registered classroom\n */\n scheduledFor: 'course' | 'classroom';\n\n /**\n * The ID of the course or classroom that requested this card\n */\n schedulingAgentId: string;\n}\n","import { DocType, DocTypePrefixes } from './types-legacy';\n\n/**\n * Template literal type for strategy state document IDs.\n *\n * Format: `STRATEGY_STATE-{courseId}-{strategyKey}`\n */\nexport type StrategyStateId =\n `${(typeof DocTypePrefixes)[DocType.STRATEGY_STATE]}::${string}::${string}`;\n\n/**\n * Document storing strategy-specific state in the user database.\n *\n * Each strategy can persist its own state (user preferences, learned patterns,\n * temporal tracking, etc.) using this document type. The state is scoped to\n * a (user, course, strategy) tuple.\n *\n * ## Use Cases\n *\n * 1. **Explicit user preferences**: User configures tag filters, difficulty\n * preferences, or learning goals. UI writes to strategy state.\n *\n * 2. **Learned/temporal state**: Strategy tracks patterns over time, e.g.,\n * \"when did I last introduce confusable concepts together?\"\n *\n * 3. **Adaptive personalization**: Strategy infers user preferences from\n * behavior and stores them for future sessions.\n *\n * ## Storage Location\n *\n * These documents live in the **user database**, not the course database.\n * They sync with the user's data across devices.\n *\n * ## Document ID Format\n *\n * `STRATEGY_STATE::{courseId}::{strategyKey}`\n *\n * Example: `STRATEGY_STATE::piano-basics::UserTagPreferenceFilter`\n *\n * @template T - The shape of the strategy-specific data payload\n */\nexport interface StrategyStateDoc<T = unknown> {\n _id: StrategyStateId;\n _rev?: string;\n docType: DocType.STRATEGY_STATE;\n\n /**\n * The course this state applies to.\n */\n courseId: string;\n\n /**\n * Unique key identifying the strategy instance.\n * Typically the strategy class name (e.g., \"UserTagPreferenceFilter\",\n * \"InterferenceMitigatorNavigator\").\n *\n * If a course has multiple instances of the same strategy type with\n * different configurations, use a more specific key.\n */\n strategyKey: string;\n\n /**\n * Strategy-specific data payload.\n * Each strategy defines its own schema for this field.\n */\n data: T;\n\n /**\n * ISO timestamp of last update.\n * Use `moment.utc(updatedAt)` to parse into a Moment object.\n */\n updatedAt: string;\n}\n\n/**\n * Build the document ID for a strategy state document.\n *\n * @param courseId - The course ID\n * @param strategyKey - The strategy key (typically class name)\n * @returns The document ID in format `STRATEGY_STATE::{courseId}::{strategyKey}`\n */\nexport function buildStrategyStateId(courseId: string, strategyKey: string): StrategyStateId {\n return `STRATEGY_STATE::${courseId}::${strategyKey}`;\n}\n","import { DocType } from './types-legacy';\n\n/**\n * Record of a user's learning outcome over a specific period.\n *\n * Used by the evolutionary orchestration system to correlate strategy\n * deviations with learning success.\n * \n * Stored in the UserDB.\n */\nexport interface UserOutcomeRecord {\n /** \n * Unique ID: \"USER_OUTCOME::{courseId}::{userId}::{timestamp}\" \n * Timestamp corresponds to periodEnd.\n */\n _id: string;\n \n docType: DocType.USER_OUTCOME;\n\n courseId: string;\n userId: string;\n\n /** Start of the measurement period (ISO timestamp) */\n periodStart: string;\n /** End of the measurement period (ISO timestamp) */\n periodEnd: string;\n\n /**\n * The computed signal value (e.g., 0.85 for 85% accuracy).\n * This is the 'Y' in the regression analysis.\n * \n * Higher values indicate better learning outcomes.\n */\n outcomeValue: number;\n\n /**\n * Snapshot of active deviations during this period.\n * Maps strategyId -> deviation value used [-1.0, 1.0].\n * This provides the 'X' values for regression analysis.\n */\n deviations: Record<string, number>;\n\n metadata: {\n sessionsCount: number;\n cardsSeen: number;\n eloStart: number;\n eloEnd: number;\n /** The algorithm used to compute outcomeValue (e.g. \"accuracy_in_zone\") */\n signalType: string;\n };\n}","import { CourseElo, Status, ParsedCard, BulkImportCardData } from '@vue-skuilder/common';\nimport { CourseDBInterface } from '../../core/interfaces/courseDB';\nimport { ImportResult, BulkCardProcessorConfig } from './types';\nimport { logger } from '../../util/logger';\n\n/**\n * Processes multiple cards from bulk text input\n *\n * @param parsedCards - Array of parsed cards to import\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Array of import results\n */\nexport async function importParsedCards(\n parsedCards: ParsedCard[],\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult[]> {\n const results: ImportResult[] = [];\n\n for (const parsedCard of parsedCards) {\n try {\n // processCard takes a ParsedCard and returns an ImportResult\n const result = await processCard(parsedCard, courseDB, config);\n results.push(result);\n } catch (error) {\n logger.error('Error processing card:', error);\n // Reconstruct originalText from parsedCard for this specific catch block\n // This is a fallback if processCard itself throws an unhandled error.\n // Normally, processCard should return an ImportResult with status 'error'.\n let errorOriginalText = parsedCard.markdown;\n if (parsedCard.tags && parsedCard.tags.length > 0) {\n errorOriginalText += `\\ntags: ${parsedCard.tags.join(', ')}`;\n }\n if (parsedCard.elo !== undefined) {\n errorOriginalText += `\\nelo: ${parsedCard.elo}`;\n }\n results.push({\n originalText: errorOriginalText,\n status: 'error',\n message: `Error processing card: ${\n error instanceof Error ? error.message : 'Unknown error'\n }`,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Processes a single parsed card\n *\n * @param parsedCard - Parsed card data\n * @param courseDB - Course database interface\n * @param config - Configuration for the card processor\n * @returns Import result for the card\n */\nasync function processCard(\n parsedCard: ParsedCard,\n courseDB: CourseDBInterface,\n config: BulkCardProcessorConfig\n): Promise<ImportResult> {\n const { markdown, tags, elo } = parsedCard;\n\n // Build the original text representation including metadata\n let originalText = markdown;\n if (tags.length > 0) {\n originalText += `\\ntags: ${tags.join(', ')}`;\n }\n if (elo !== undefined) {\n originalText += `\\nelo: ${elo}`;\n }\n\n // Create card data object\n const cardData: BulkImportCardData = {\n Input: markdown,\n Uploads: [], // No uploads for bulk import\n };\n\n const tagsElo: CourseElo['tags'] = {};\n for (const tag of tags) {\n tagsElo[tag] = {\n score: elo || 0,\n count: 1,\n };\n }\n\n try {\n const result = await courseDB.addNote(\n config.courseCode,\n config.dataShape,\n cardData,\n config.userName,\n tags,\n undefined, // attachments\n elo\n ? {\n global: {\n score: elo,\n count: 1,\n },\n tags: tagsElo,\n misc: {},\n }\n : undefined\n );\n\n if (result.status === Status.ok) {\n return {\n originalText,\n status: 'success',\n message: 'Card added successfully.',\n cardId: result.id ? result.id : '(unknown)',\n };\n } else {\n return {\n originalText,\n status: 'error',\n message: result.message || 'Failed to add card to database. Unknown error.',\n };\n }\n } catch (error) {\n logger.error('Error adding note:', error);\n return {\n originalText,\n status: 'error',\n message: `Error adding card: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n}\n\n/**\n * Validates the configuration for bulk card processing\n *\n * @param config - Configuration to validate\n * @returns Object with validation result and error message if any\n */\nexport function validateProcessorConfig(config: Partial<BulkCardProcessorConfig>): {\n isValid: boolean;\n errorMessage?: string;\n} {\n if (!config.dataShape) {\n return {\n isValid: false,\n errorMessage: 'No data shape provided for card processing',\n };\n }\n\n if (!config.courseCode) {\n return {\n isValid: false,\n errorMessage: 'No course code provided for card processing',\n };\n }\n\n if (!config.userName) {\n return {\n isValid: false,\n errorMessage: 'No user name provided for card processing',\n };\n }\n\n return { isValid: true };\n}\n","import { DataShape } from '@vue-skuilder/common';\n\n/**\n * Interface representing the result of a bulk import operation for a single card\n */\nexport interface ImportResult {\n /** The original text input for the card */\n originalText: string;\n /** Status of the import operation */\n status: 'success' | 'error';\n /** Message describing the result or error */\n message: string;\n /** ID of the newly created card (only for success) */\n cardId?: string;\n}\n\n/**\n * Configuration for the bulk card processor\n */\nexport interface BulkCardProcessorConfig {\n /** The data shape to use for the cards */\n dataShape: DataShape;\n /** The course code used for adding notes */\n courseCode: string;\n /** The username of the current user */\n userName: string;\n}\n","export * from './cardProcessor.js';\nexport * from './types.js';","import { logger } from '../util/logger';\nimport { getDataLayer } from '../factory';\nimport { DocType, DocTypePrefixes } from './types/types-legacy';\nimport { filterAllDocsByPrefix } from '../impl/common/userDBHelpers';\nimport type { UserDBInterface } from './interfaces/userDB';\nimport type { ScheduledCard, CourseRegistration } from './types/user';\n\n// ============================================================================\n// USER DATABASE DEBUGGER\n// ============================================================================\n//\n// Console-accessible debug API for inspecting user database (PouchDB/CouchDB).\n//\n// Exposed as `window.skuilder.userdb` for interactive exploration.\n//\n// Usage:\n// window.skuilder.userdb.showUser()\n// window.skuilder.userdb.showScheduledReviews()\n// window.skuilder.userdb.showCourseRegistrations()\n// window.skuilder.userdb.showCardHistory('cardId')\n// window.skuilder.userdb.queryByType('SCHEDULED_CARD')\n// window.skuilder.userdb.dbInfo()\n// window.skuilder.userdb.export()\n//\n// ============================================================================\n\n/**\n * Get the user database instance safely\n */\nfunction getUserDB(): UserDBInterface | null {\n try {\n const provider = getDataLayer();\n return provider.getUserDB();\n } catch {\n logger.info('[UserDB Debug] Data layer not initialized yet.');\n return null;\n }\n}\n\n/**\n * Get raw PouchDB instance for advanced queries\n * This accesses the internal localDB property\n */\nfunction getRawDB(): PouchDB.Database | null {\n const userDB = getUserDB();\n if (!userDB) return null;\n\n // Access the internal localDB property\n // This is a bit of a hack but necessary for raw queries\n const rawDB = (userDB as any).localDB;\n if (!rawDB) {\n logger.info('[UserDB Debug] Unable to access raw database instance.');\n return null;\n }\n\n return rawDB;\n}\n\n/**\n * Format a timestamp for display\n */\nfunction formatTimestamp(isoString: string): string {\n const date = new Date(isoString);\n return date.toLocaleString();\n}\n\n/**\n * Console API object exposed on window.skuilder.userdb\n */\nexport const userDBDebugAPI = {\n /**\n * Show current user information\n */\n showUser(): void {\n const userDB = getUserDB();\n if (!userDB) return;\n\n // eslint-disable-next-line no-console\n console.group('👤 User Information');\n logger.info(`Username: ${userDB.getUsername()}`);\n logger.info(`Logged in: ${userDB.isLoggedIn() ? 'Yes ✅' : 'No (Guest) ❌'}`);\n\n userDB.getConfig()\n .then((config) => {\n logger.info('Configuration:');\n logger.info(JSON.stringify(config, null, 2));\n })\n .catch((err) => {\n logger.info(`Error loading config: ${err.message}`);\n })\n .finally(() => {\n // eslint-disable-next-line no-console\n console.groupEnd();\n });\n },\n\n /**\n * Show scheduled reviews\n */\n async showScheduledReviews(courseId?: string): Promise<void> {\n const userDB = getUserDB();\n if (!userDB) return;\n\n try {\n const reviews = await userDB.getPendingReviews(courseId);\n\n // eslint-disable-next-line no-console\n console.group(`📅 Scheduled Reviews${courseId ? ` (${courseId})` : ''}`);\n logger.info(`Total: ${reviews.length}`);\n\n if (reviews.length > 0) {\n // Group by course\n const byCourse = new Map<string, ScheduledCard[]>();\n for (const review of reviews) {\n if (!byCourse.has(review.courseId)) {\n byCourse.set(review.courseId, []);\n }\n byCourse.get(review.courseId)!.push(review);\n }\n\n for (const [course, courseReviews] of byCourse) {\n // eslint-disable-next-line no-console\n console.group(`Course: ${course} (${courseReviews.length} reviews)`);\n\n // Sort by review time\n const sorted = courseReviews.sort((a, b) => {\n const timeA = typeof a.reviewTime === 'string' ? a.reviewTime : a.reviewTime.toISOString();\n const timeB = typeof b.reviewTime === 'string' ? b.reviewTime : b.reviewTime.toISOString();\n return new Date(timeA).getTime() - new Date(timeB).getTime();\n });\n\n // Show first 10\n for (const review of sorted.slice(0, 10)) {\n const reviewTimeStr = typeof review.reviewTime === 'string'\n ? review.reviewTime\n : review.reviewTime.toISOString();\n logger.info(\n ` ${review.cardId.slice(0, 12)}... @ ${formatTimestamp(reviewTimeStr)} ` +\n `[${review.scheduledFor}/${review.schedulingAgentId}]`\n );\n }\n\n if (sorted.length > 10) {\n logger.info(` ... and ${sorted.length - 10} more`);\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n }\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error loading scheduled reviews: ${err.message}`);\n }\n },\n\n /**\n * Show course registrations\n */\n async showCourseRegistrations(): Promise<void> {\n const userDB = getUserDB();\n if (!userDB) return;\n\n try {\n const registrations = await userDB.getActiveCourses();\n\n // eslint-disable-next-line no-console\n console.group('📚 Course Registrations');\n logger.info(`Total: ${registrations.length}`);\n\n if (registrations.length > 0) {\n // eslint-disable-next-line no-console\n console.table(\n registrations.map((reg: CourseRegistration) => ({\n courseId: reg.courseID,\n status: reg.status || 'active',\n elo: typeof reg.elo === 'number'\n ? reg.elo.toFixed(0)\n : reg.elo?.global?.score?.toFixed(0) || 'N/A',\n }))\n );\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error loading course registrations: ${err.message}`);\n }\n },\n\n /**\n * Show card history for a specific card\n */\n async showCardHistory(cardId: string): Promise<void> {\n const rawDB = getRawDB();\n if (!rawDB) return;\n\n try {\n // Card history docs use prefix 'cardH'\n const result = await filterAllDocsByPrefix(rawDB, DocTypePrefixes[DocType.CARDRECORD]);\n\n // Filter for this specific card\n const cardHistories = result.rows\n .filter((row: any) => row.doc && row.doc.cardID === cardId)\n .map((row: any) => row.doc);\n\n // eslint-disable-next-line no-console\n console.group(`🎴 Card History: ${cardId}`);\n logger.info(`Total interactions: ${cardHistories.length}`);\n\n if (cardHistories.length > 0) {\n // Sort by timestamp\n const sorted = cardHistories.sort((a: any, b: any) =>\n new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()\n );\n\n // Show recent history\n // eslint-disable-next-line no-console\n console.table(\n sorted.slice(0, 20).map((doc: any) => ({\n time: formatTimestamp(doc.timestamp),\n outcome: doc.outcome || 'N/A',\n duration: doc.duration ? `${(doc.duration / 1000).toFixed(1)}s` : 'N/A',\n courseId: doc.courseId,\n }))\n );\n\n if (sorted.length > 20) {\n logger.info(`... and ${sorted.length - 20} more interactions`);\n }\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error loading card history: ${err.message}`);\n }\n },\n\n /**\n * Query documents by type\n */\n async queryByType(docType: keyof typeof DocType, limit: number = 50): Promise<void> {\n const rawDB = getRawDB();\n if (!rawDB) return;\n\n try {\n const prefix = DocTypePrefixes[DocType[docType]];\n if (!prefix) {\n logger.info(`Unknown document type: ${docType}`);\n return;\n }\n\n const result = await filterAllDocsByPrefix(rawDB, prefix);\n\n // eslint-disable-next-line no-console\n console.group(`📄 Documents: ${docType}`);\n logger.info(`Total: ${result.rows.length}`);\n logger.info(`Prefix: ${prefix}`);\n\n if (result.rows.length > 0) {\n logger.info('Sample documents:');\n const samples = result.rows.slice(0, Math.min(limit, result.rows.length));\n\n for (const row of samples) {\n logger.info(`\\n${row.id}:`);\n logger.info(JSON.stringify(row.doc, null, 2));\n }\n\n if (result.rows.length > limit) {\n logger.info(`\\n... and ${result.rows.length - limit} more documents`);\n }\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error querying documents: ${err.message}`);\n }\n },\n\n /**\n * Show database info and statistics\n */\n async dbInfo(): Promise<void> {\n const rawDB = getRawDB();\n if (!rawDB) return;\n\n try {\n const info = await rawDB.info();\n\n // eslint-disable-next-line no-console\n console.group('ℹ️ Database Information');\n logger.info(`Database name: ${info.db_name}`);\n logger.info(`Total documents: ${info.doc_count}`);\n logger.info(`Update sequence: ${info.update_seq}`);\n // disk_size may not be available in all PouchDB implementations\n if ('disk_size' in info) {\n logger.info(`Disk size: ${((info as any).disk_size || 0) / 1024 / 1024} MB`);\n }\n\n // Count documents by type\n logger.info('\\nDocument counts by type:');\n const allDocs = await rawDB.allDocs({ include_docs: false });\n const typeCounts = new Map<string, number>();\n\n for (const row of allDocs.rows) {\n // Extract prefix from document ID\n let prefix = 'other';\n for (const [type, typePrefix] of Object.entries(DocTypePrefixes)) {\n if (row.id.startsWith(typePrefix)) {\n prefix = type;\n break;\n }\n }\n typeCounts.set(prefix, (typeCounts.get(prefix) || 0) + 1);\n }\n\n // eslint-disable-next-line no-console\n console.table(\n Array.from(typeCounts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([type, count]) => ({ type, count }))\n );\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n } catch (err: any) {\n logger.info(`Error getting database info: ${err.message}`);\n }\n },\n\n /**\n * List all document types\n */\n listDocTypes(): void {\n // eslint-disable-next-line no-console\n console.group('📋 Available Document Types');\n logger.info('Use with queryByType(type):');\n\n for (const [type, prefix] of Object.entries(DocTypePrefixes)) {\n logger.info(` ${type.padEnd(30)} → prefix: \"${prefix}\"`);\n }\n\n // eslint-disable-next-line no-console\n console.groupEnd();\n },\n\n /**\n * Export database contents (limited, for debugging)\n */\n async export(includeContent: boolean = false): Promise<string> {\n const rawDB = getRawDB();\n const userDB = getUserDB();\n if (!rawDB || !userDB) return '{}';\n\n try {\n const data: any = {\n username: userDB.getUsername(),\n loggedIn: userDB.isLoggedIn(),\n timestamp: new Date().toISOString(),\n };\n\n if (includeContent) {\n // Get all documents\n const allDocs = await rawDB.allDocs({ include_docs: true });\n data.documents = allDocs.rows.map((row: any) => ({\n id: row.id,\n doc: row.doc,\n }));\n data.totalDocs = allDocs.rows.length;\n } else {\n // Just get counts\n const allDocs = await rawDB.allDocs({ include_docs: false });\n data.totalDocs = allDocs.rows.length;\n\n const typeCounts = new Map<string, number>();\n for (const row of allDocs.rows) {\n let prefix = 'other';\n for (const [type, typePrefix] of Object.entries(DocTypePrefixes)) {\n if (row.id.startsWith(typePrefix)) {\n prefix = type;\n break;\n }\n }\n typeCounts.set(prefix, (typeCounts.get(prefix) || 0) + 1);\n }\n data.docCounts = Object.fromEntries(typeCounts);\n }\n\n const json = JSON.stringify(data, null, 2);\n logger.info('[UserDB Debug] Database info exported. Copy the returned string or use:');\n logger.info(' copy(window.skuilder.userdb.export())');\n if (!includeContent) {\n logger.info(' For full content export: window.skuilder.userdb.export(true)');\n }\n return json;\n } catch (err: any) {\n logger.info(`Error exporting database: ${err.message}`);\n return '{}';\n }\n },\n\n /**\n * Execute raw PouchDB query\n */\n async raw(queryFn: (db: PouchDB.Database) => Promise<any>): Promise<void> {\n const rawDB = getRawDB();\n if (!rawDB) return;\n\n try {\n const result = await queryFn(rawDB);\n logger.info('[UserDB Debug] Query result:');\n logger.info(result);\n } catch (err: any) {\n logger.info(`[UserDB Debug] Query error: ${err.message}`);\n }\n },\n\n /**\n * Show help\n */\n help(): void {\n logger.info(`\n🔧 UserDB Debug API\n\nCommands:\n .showUser() Show current user info and config\n .showScheduledReviews(courseId?) Show scheduled reviews (optionally filter by course)\n .showCourseRegistrations() Show all course registrations\n .showCardHistory(cardId) Show interaction history for a card\n .queryByType(docType, limit?) Query documents by type (e.g., 'SCHEDULED_CARD')\n .listDocTypes() List all available document types\n .dbInfo() Show database info and statistics\n .export(includeContent?) Export database info (true = include all docs)\n .raw(queryFn) Execute raw PouchDB query\n .help() Show this help message\n\nExamples:\n window.skuilder.userdb.showUser()\n window.skuilder.userdb.showScheduledReviews('course123')\n window.skuilder.userdb.queryByType('SCHEDULED_CARD', 10)\n window.skuilder.userdb.raw(db => db.allDocs({ limit: 5 }))\n`);\n },\n};\n\n// ============================================================================\n// WINDOW MOUNT\n// ============================================================================\n\n/**\n * Mount the debug API on window.skuilder.userdb\n */\nexport function mountUserDBDebugger(): void {\n if (typeof window === 'undefined') return;\n\n const win = window as any;\n win.skuilder = win.skuilder || {};\n win.skuilder.userdb = userDBDebugAPI;\n}\n\n// Auto-mount when module is loaded\nmountUserDBDebugger();\n","// Export all core interfaces and types\n\nexport * from './interfaces';\nexport * from './types/types-legacy';\nexport * from './types/user';\nexport * from './types/strategyState';\nexport * from './types/userOutcome';\nexport * from '../util/Loggable';\nexport * from './util';\nexport * from './navigators';\nexport * from './bulkImport';\nexport * from './orchestration';\n\n// Export debug APIs\nexport { userDBDebugAPI, mountUserDBDebugger } from './UserDBDebugger';\n"],"mappings":"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","names":["DocType","log","moment","AggregationMode","cards","toCourseElo","moment","toCourseElo","DEFAULT_MIN_COUNT","toCourseElo","DEFAULT_MIN_COUNT","types_exports","init_types","toCourseElo","mergeHints","init_","Navigators","NavigatorRole","blankCourseElo","toCourseElo","filterAllDocsByPrefix","getCourseDB","moment","init_classroomDB","getStartAndEndKeys","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","Status","fetch","moment","process","getCourseDB","filterAllDocsByPrefix","getStartAndEndKeys","REVIEW_TIME_FORMAT","init_adminDB","init_classroomDB","Status","moment","log","record","hasActiveFilter","init_classroomDB","init_courseDB","init_courseDB","Status","init_types","init_types"]}
|