diff --git a/packages/db/src/core/interfaces/contentSource.ts b/packages/db/src/core/interfaces/contentSource.ts index 11907e7b5..35506fb34 100644 --- a/packages/db/src/core/interfaces/contentSource.ts +++ b/packages/db/src/core/interfaces/contentSource.ts @@ -31,11 +31,12 @@ export function isReview(item: StudySessionItem): item is StudySessionReviewItem export interface StudySessionItem { status: 'new' | 'review' | 'failed-new' | 'failed-review'; - qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`; - cardID: string; contentSourceType: 'course' | 'classroom'; contentSourceID: string; + // qualifiedID: `${string}-${string}` | `${string}-${string}-${number}`; + cardID: string; courseID: string; + elo?: number; // reviewID?: string; } diff --git a/packages/db/src/core/interfaces/courseDB.ts b/packages/db/src/core/interfaces/courseDB.ts index 4440a677b..e21dcb97b 100644 --- a/packages/db/src/core/interfaces/courseDB.ts +++ b/packages/db/src/core/interfaces/courseDB.ts @@ -53,7 +53,16 @@ export interface CourseDBInterface extends NavigationStrategyManager { /** * Get cards sorted by ELO rating */ - getCardsByELO(elo: number, limit?: number): Promise; + getCardsByELO( + elo: number, + limit?: number + ): Promise< + { + courseID: string; + cardID: string; + elo?: number; + }[] + >; /** * Get ELO data for specific cards @@ -145,7 +154,7 @@ export interface CourseDBInterface extends NavigationStrategyManager { searchCards(query: string): Promise; /** - * Find documents using PouchDB query syntax + * Find documents using PouchDB query syntax * @param request PouchDB find request * @returns Query response */ diff --git a/packages/db/src/impl/couch/classroomDB.ts b/packages/db/src/impl/couch/classroomDB.ts index 65097bb64..7e53ccb7d 100644 --- a/packages/db/src/impl/couch/classroomDB.ts +++ b/packages/db/src/impl/couch/classroomDB.ts @@ -8,12 +8,7 @@ import { ENV } from '@db/factory'; import { logger } from '@db/util/logger'; import moment from 'moment'; import pouch from './pouchdb-setup'; -import { - getCourseDB, - getStartAndEndKeys, - createPouchDBConfig, - REVIEW_TIME_FORMAT, -} from '.'; +import { getCourseDB, getStartAndEndKeys, createPouchDBConfig, REVIEW_TIME_FORMAT } from '.'; import { CourseDB, getTag } from './courseDB'; import { UserDBInterface } from '@db/core'; @@ -181,10 +176,13 @@ export class StudentClassroomDB } } - logger.info(`New Cards from classroom ${this._cfg.name}: ${ret.map((c) => c.qualifiedID)}`); + logger.info( + `New Cards from classroom ${this._cfg.name}: ${ret.map((c) => `${c.courseID}-${c.cardID}`)}` + ); return ret.filter((c) => { - if (activeCards.some((ac) => c.qualifiedID.includes(ac))) { + if (activeCards.some((ac) => c.cardID.includes(ac))) { + // [ ] almost certainly broken after removing qualifiedID from StudySessionItem return false; } else { return true; diff --git a/packages/db/src/impl/couch/courseDB.ts b/packages/db/src/impl/couch/courseDB.ts index 1890a13ea..c90777643 100644 --- a/packages/db/src/impl/couch/courseDB.ts +++ b/packages/db/src/impl/couch/courseDB.ts @@ -277,7 +277,13 @@ export class CourseDB implements StudyContentSource, CourseDBInterface { return s; } }) - .map((c) => `${this.id}-${c.id}-${c.key}`); + .map((c) => { + return { + courseID: this.id, + cardID: c.id, + elo: c.key, + }; + }); const str = `below:\n${below.rows.map((r) => `\t${r.id}-${r.key}\n`)} @@ -565,7 +571,7 @@ above:\n${above.rows.map((r) => `\t${r.id}-${r.key}\n`)}`; targetElo = options.elo; } - let cards: string[] = []; + let cards: { courseID: string; cardID: string; elo?: number }[] = []; let mult: number = 4; let previousCount: number = -1; let newCount: number = 0; @@ -578,14 +584,30 @@ above:\n${above.rows.map((r) => `\t${r.id}-${r.key}\n`)}`; logger.debug(`Found ${cards.length} elo neighbor cards...`); if (filter) { - cards = cards.filter(filter); + const filtered = cards + .map((card) => { + if (card.elo) { + return `${card.courseID}-${card.cardID}-${card.elo}`; + } else { + return `${card.courseID}-${card.cardID}`; + } + }) + .filter(filter); logger.debug(`Filtered to ${cards.length} cards...`); + cards = filtered.map((card) => { + const [courseID, cardID, elo] = card.split('-'); + return { courseID, cardID, elo: elo ? parseInt(elo) : undefined }; + }); } mult *= 2; } - const selectedCards: string[] = []; + const selectedCards: { + courseID: string; + cardID: string; + elo?: number; + }[] = []; while (selectedCards.length < options.limit && cards.length > 0) { const index = randIntWeightedTowardZero(cards.length); @@ -594,13 +616,12 @@ above:\n${above.rows.map((r) => `\t${r.id}-${r.key}\n`)}`; } return selectedCards.map((c) => { - const split = c.split('-'); return { courseID: this.id, - cardID: split[1], - qualifiedID: `${split[0]}-${split[1]}`, + cardID: c.cardID, contentSourceType: 'course', contentSourceID: this.id, + elo: c.elo, status: 'new', }; }); diff --git a/packages/db/src/impl/static/courseDB.ts b/packages/db/src/impl/static/courseDB.ts index 624e9b4c6..d9701ba3a 100644 --- a/packages/db/src/impl/static/courseDB.ts +++ b/packages/db/src/impl/static/courseDB.ts @@ -91,8 +91,20 @@ export class StaticCourseDB implements CourseDBInterface { }; } - async getCardsByELO(elo: number, limit?: number): Promise { - return this.unpacker.queryByElo(elo, limit || 25); + async getCardsByELO( + elo: number, + limit?: number + ): Promise< + { + courseID: string; + cardID: string; + elo?: number; + }[] + > { + return (await this.unpacker.queryByElo(elo, limit || 25)).map((card) => { + const [courseID, cardID, elo] = card.split('-'); + return { courseID, cardID, elo: elo ? parseInt(elo) : undefined }; + }); } async getCardEloData(cardIds: string[]): Promise { @@ -403,7 +415,7 @@ export class StaticCourseDB implements CourseDBInterface { // Could be implemented with local search if needed return { docs: [], - warning: 'Find operations not supported in static mode' + warning: 'Find operations not supported in static mode', } as any; } } diff --git a/packages/db/src/study/SessionController.ts b/packages/db/src/study/SessionController.ts index a90701d30..502748502 100644 --- a/packages/db/src/study/SessionController.ts +++ b/packages/db/src/study/SessionController.ts @@ -62,7 +62,8 @@ class ItemQueue { public get toString(): string { return ( - `${typeof this.q[0]}:\n` + this.q.map((i) => `\t${i.qualifiedID}: ${i.status}`).join('\n') + `${typeof this.q[0]}:\n` + + this.q.map((i) => `\t${i.courseID}+${i.cardID}: ${i.status}`).join('\n') ); } } @@ -224,7 +225,7 @@ export class SessionController extends Loggable { for (let i = 0; i < dueCards.length; i++) { const card = dueCards[i]; this.reviewQ.add(card); - report += `\t${card.qualifiedID}}\n`; + report += `\t${card.courseID}-${card.cardID}\n`; } this.log(report); } @@ -244,7 +245,7 @@ export class SessionController extends Loggable { for (let i = 0; i < newContent.length; i++) { if (newContent[i].length > 0) { const item = newContent[i].splice(0, 1)[0]; - this.log(`Adding new card: ${item.qualifiedID}`); + this.log(`Adding new card: ${item.courseID}-${item.cardID}`); // revealed bug here w/ new prefixes. osbserved log "Adding new card: 5e627b7f630998243834152aa00920f5-c" this.newQ.add(item); n--; } @@ -264,6 +265,7 @@ export class SessionController extends Loggable { } public nextCard( + // [ ] this is often slow. Why? action: | 'dismiss-success' | 'dismiss-failed' @@ -373,7 +375,6 @@ export class SessionController extends Loggable { failedItem = { cardID: this._currentCard.cardID, courseID: this._currentCard.courseID, - qualifiedID: this._currentCard.qualifiedID, contentSourceID: this._currentCard.contentSourceID, contentSourceType: this._currentCard.contentSourceType, status: 'failed-review', @@ -383,7 +384,6 @@ export class SessionController extends Loggable { failedItem = { cardID: this._currentCard.cardID, courseID: this._currentCard.courseID, - qualifiedID: this._currentCard.qualifiedID, contentSourceID: this._currentCard.contentSourceID, contentSourceType: this._currentCard.contentSourceType, status: 'failed-new', diff --git a/packages/mcp/src/resources/cards.ts b/packages/mcp/src/resources/cards.ts index 77a57664e..676868ac0 100644 --- a/packages/mcp/src/resources/cards.ts +++ b/packages/mcp/src/resources/cards.ts @@ -42,12 +42,12 @@ export async function handleCardsAllResource( const courseInfo = await courseDB.getCourseInfo(); // Get cards using ELO-based query (this gives us all cards sorted by ELO) - const cardIds = await courseDB.getCardsByELO(1500, limit + offset); + const eloCenteredCards = await courseDB.getCardsByELO(1500, limit + offset); // Skip offset cards and take limit - const targetCardIds = cardIds.slice(offset, offset + limit); + const targetCards = eloCenteredCards.slice(offset, offset + limit); - if (targetCardIds.length === 0) { + if (targetCards.length === 0) { return { cards: [], total: courseInfo.cardCount, @@ -58,11 +58,11 @@ export async function handleCardsAllResource( } // Get card documents - const cardDocs = await courseDB.getCourseDocs(targetCardIds); + const cardDocs = await courseDB.getCourseDocs(targetCards.map(card => card.cardID)); // Get ELO data for these cards - const eloData = await courseDB.getCardEloData(targetCardIds); - const eloMap = new Map(eloData.map((elo, index) => [targetCardIds[index], elo.global?.score || 1500])); + const eloData = await courseDB.getCardEloData(targetCards.map(card => card.cardID)); + const eloMap = new Map(eloData.map((elo, index) => [targetCards[index], elo.global?.score || 1500])); // Transform to CardResourceData format const cards: CardResourceData[] = []; @@ -162,7 +162,7 @@ export async function handleCardsShapeResource( } // Get card documents to check their shapes - const cardDocs = await courseDB.getCourseDocs(allCardIds); + const cardDocs = await courseDB.getCourseDocs(allCardIds.map(c => c.cardID)); // Filter by shape and collect card IDs const filteredCardIds: string[] = []; @@ -240,9 +240,9 @@ export async function handleCardsEloResource( // Get cards around the middle of the ELO range const targetElo = Math.floor((parsedRange.min + parsedRange.max) / 2); - const cardIds = await courseDB.getCardsByELO(targetElo, 1000); // Get more to filter from + const cards = await courseDB.getCardsByELO(targetElo, 1000); // Get more to filter from - if (cardIds.length === 0) { + if (cards.length === 0) { return { cards: [], total: 0, @@ -253,11 +253,11 @@ export async function handleCardsEloResource( } // Get ELO data for all cards - const eloData = await courseDB.getCardEloData(cardIds); + const eloData = await courseDB.getCardEloData(cards.map(c => c.cardID)); // Filter by ELO range const filteredEloData = eloData - .map((elo, index) => ({ elo, cardId: cardIds[index] })) + .map((elo, index) => ({ elo, cardId: cards[index] })) .filter(({ elo }) => { const score = elo.global?.score || 1500; return score >= parsedRange.min && score <= parsedRange.max; @@ -265,9 +265,9 @@ export async function handleCardsEloResource( // Apply pagination const paginatedEloData = filteredEloData.slice(offset, offset + limit); - const paginatedCardIds = paginatedEloData.map(({ cardId }) => cardId); + const paginatedCards = paginatedEloData.map(({ cardId }) => cardId); - if (paginatedCardIds.length === 0) { + if (paginatedCards.length === 0) { return { cards: [], total: filteredEloData.length, @@ -278,15 +278,15 @@ export async function handleCardsEloResource( } // Get card documents - const cardDocs = await courseDB.getCourseDocs(paginatedCardIds); + const cardDocs = await courseDB.getCourseDocs(paginatedCards.map(c => c.cardID)); const eloMap = new Map(paginatedEloData.map(({ elo, cardId }) => [cardId, elo.global?.score || 1500])); // Transform to CardResourceData format - const cards: CardResourceData[] = []; + const cardsData: CardResourceData[] = []; for (const row of cardDocs.rows) { if (isSuccessRow(row)) { const doc = row.doc; - cards.push({ + cardsData.push({ cardId: doc._id, datashape: (doc as any).shape?.name || 'unknown', data: (doc as any).data || {}, @@ -299,7 +299,7 @@ export async function handleCardsEloResource( } return { - cards, + cards: cardsData, total: filteredEloData.length, page: Math.floor(offset / limit) + 1, limit, diff --git a/packages/mcp/src/resources/shapes.ts b/packages/mcp/src/resources/shapes.ts index 62b5b3be3..3362484d9 100644 --- a/packages/mcp/src/resources/shapes.ts +++ b/packages/mcp/src/resources/shapes.ts @@ -82,9 +82,9 @@ export async function handleShapeSpecificResource( let examples: any[] = []; try { // Get a few cards that use this shape to provide examples - const cardIds = await courseDB.getCardsByELO(1500, 10); // Get some sample cards - if (cardIds.length > 0) { - const cardDocs = await courseDB.getCourseDocs(cardIds.slice(0, 5)); // Limit to 5 examples + const cards = await courseDB.getCardsByELO(1500, 10); // Get some sample cards + if (cards.length > 0) { + const cardDocs = await courseDB.getCourseDocs(cards.map(c=>c.cardID).slice(0, 5)); // Limit to 5 examples examples = []; for (const row of cardDocs.rows) { if (isSuccessRow(row) && (row.doc as any).shape?.name === shapeName) {