@ankimcp/anki-mcp-server
Version:
Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards
126 lines • 4.34 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.computeDistribution = computeDistribution;
exports.computeRetention = computeRetention;
exports.calculateStreak = calculateStreak;
const date_utils_1 = require("./date.utils.js");
function formatNumber(n) {
if (Number.isInteger(n)) {
return n.toString();
}
return n.toFixed(1);
}
function createEmptyBuckets(config) {
const buckets = {};
const { boundaries, formatLabel, unitSuffix } = config;
if (boundaries.length === 0) {
return buckets;
}
const labels = [
formatLabel
? formatLabel(null, boundaries[0])
: `<${formatNumber(boundaries[0])}${unitSuffix || ""}`,
];
for (let i = 0; i < boundaries.length - 1; i++) {
labels.push(formatLabel
? formatLabel(boundaries[i], boundaries[i + 1])
: `${formatNumber(boundaries[i])}-${formatNumber(boundaries[i + 1])}${unitSuffix || ""}`);
}
labels.push(formatLabel
? formatLabel(boundaries[boundaries.length - 1], null)
: `>${formatNumber(boundaries[boundaries.length - 1])}${unitSuffix || ""}`);
labels.forEach((label) => {
buckets[label] = 0;
});
return buckets;
}
function computeBuckets(sortedValues, config) {
const buckets = createEmptyBuckets(config);
const { boundaries, formatLabel, unitSuffix } = config;
if (boundaries.length === 0) {
return buckets;
}
for (const value of sortedValues) {
let placed = false;
if (value < boundaries[0]) {
const label = formatLabel
? formatLabel(null, boundaries[0])
: `<${formatNumber(boundaries[0])}${unitSuffix || ""}`;
buckets[label]++;
continue;
}
for (let i = 0; i < boundaries.length - 1; i++) {
if (value >= boundaries[i] && value < boundaries[i + 1]) {
const label = formatLabel
? formatLabel(boundaries[i], boundaries[i + 1])
: `${formatNumber(boundaries[i])}-${formatNumber(boundaries[i + 1])}${unitSuffix || ""}`;
buckets[label]++;
placed = true;
break;
}
}
if (!placed) {
const label = formatLabel
? formatLabel(boundaries[boundaries.length - 1], null)
: `>${formatNumber(boundaries[boundaries.length - 1])}${unitSuffix || ""}`;
buckets[label]++;
}
}
return buckets;
}
function computeDistribution(values, config) {
if (values.length === 0) {
return {
mean: 0,
median: 0,
min: 0,
max: 0,
count: 0,
buckets: createEmptyBuckets(config),
};
}
const sorted = [...values].sort((a, b) => a - b);
const sum = sorted.reduce((acc, v) => acc + v, 0);
const medianIndex = Math.floor(sorted.length / 2);
const median = sorted.length % 2 === 0
? (sorted[medianIndex - 1] + sorted[medianIndex]) / 2
: sorted[medianIndex];
return {
mean: sum / sorted.length,
median,
min: sorted[0],
max: sorted[sorted.length - 1],
count: sorted.length,
buckets: computeBuckets(sorted, config),
};
}
function computeRetention(buttonPresses) {
const counts = { again: 0, hard: 0, good: 0, easy: 0 };
buttonPresses.forEach((button) => {
if (button === 1)
counts.again++;
else if (button === 2)
counts.hard++;
else if (button === 3)
counts.good++;
else if (button === 4)
counts.easy++;
});
const total = counts.again + counts.hard + counts.good + counts.easy;
const remembered = counts.hard + counts.good + counts.easy;
return {
overall: total > 0 ? remembered / total : 0,
by_rating: counts,
};
}
function calculateStreak(reviewsByDay, todayKey) {
const studiedDays = new Set(reviewsByDay.filter((day) => day.count > 0).map((day) => day.date));
let streak = 0;
let dayKey = todayKey;
while (studiedDays.has(dayKey)) {
streak++;
dayKey = (0, date_utils_1.addDaysToDayKey)(dayKey, -1);
}
return streak;
}
//# sourceMappingURL=stats.utils.js.map