Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/db/src/core/interfaces/contentSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
13 changes: 11 additions & 2 deletions packages/db/src/core/interfaces/courseDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,16 @@ export interface CourseDBInterface extends NavigationStrategyManager {
/**
* Get cards sorted by ELO rating
*/
getCardsByELO(elo: number, limit?: number): Promise<string[]>;
getCardsByELO(
elo: number,
limit?: number
): Promise<
{
courseID: string;
cardID: string;
elo?: number;
}[]
>;

/**
* Get ELO data for specific cards
Expand Down Expand Up @@ -145,7 +154,7 @@ export interface CourseDBInterface extends NavigationStrategyManager {
searchCards(query: string): Promise<any[]>;

/**
* Find documents using PouchDB query syntax
* Find documents using PouchDB query syntax
* @param request PouchDB find request
* @returns Query response
*/
Expand Down
14 changes: 6 additions & 8 deletions packages/db/src/impl/couch/classroomDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
35 changes: 28 additions & 7 deletions packages/db/src/impl/couch/courseDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`)}

Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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',
};
});
Expand Down
18 changes: 15 additions & 3 deletions packages/db/src/impl/static/courseDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,20 @@ export class StaticCourseDB implements CourseDBInterface {
};
}

async getCardsByELO(elo: number, limit?: number): Promise<string[]> {
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<CourseElo[]> {
Expand Down Expand Up @@ -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;
}
}
10 changes: 5 additions & 5 deletions packages/db/src/study/SessionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class ItemQueue<T extends StudySessionItem> {

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')
);
}
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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--;
}
Expand All @@ -264,6 +265,7 @@ export class SessionController extends Loggable {
}

public nextCard(
// [ ] this is often slow. Why?
action:
| 'dismiss-success'
| 'dismiss-failed'
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand Down
34 changes: 17 additions & 17 deletions packages/mcp/src/resources/cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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[] = [];
Expand Down Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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,
Expand All @@ -253,21 +253,21 @@ 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;
});

// 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,
Expand All @@ -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 || {},
Expand All @@ -299,7 +299,7 @@ export async function handleCardsEloResource(
}

return {
cards,
cards: cardsData,
total: filteredEloData.length,
page: Math.floor(offset / limit) + 1,
limit,
Expand Down
6 changes: 3 additions & 3 deletions packages/mcp/src/resources/shapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down