49 lines
2.1 KiB
JavaScript
49 lines
2.1 KiB
JavaScript
export function getRecord(state, conceptId, dimension = "mastery") {
|
|
return state.records.find((r) => r.concept_id === conceptId && r.dimension === dimension) || null;
|
|
}
|
|
|
|
export function conceptStatus(state, concept, minScore = 0.65, minConfidence = 0.45) {
|
|
const rec = getRecord(state, concept.id, concept.masteryDimension || "mastery");
|
|
if (rec && rec.score >= minScore && rec.confidence >= minConfidence) return "mastered";
|
|
const prereqsOk = (concept.prerequisites || []).every((pid) => {
|
|
const p = getRecord(state, pid, concept.masteryDimension || "mastery");
|
|
return p && p.score >= minScore && p.confidence >= minConfidence;
|
|
});
|
|
if (prereqsOk) return rec ? "active" : "available";
|
|
return "locked";
|
|
}
|
|
|
|
export function buildMasteryMap(state, domain) {
|
|
return domain.concepts.map((c) => ({ id: c.id, label: c.title, status: conceptStatus(state, c) }));
|
|
}
|
|
|
|
export function progressPercent(state, domain) {
|
|
const total = Math.max(1, domain.concepts.length);
|
|
const mastered = domain.concepts.filter((c) => conceptStatus(state, c) === "mastered").length;
|
|
return Math.round((mastered / total) * 100);
|
|
}
|
|
|
|
export function milestoneMessages(state, domain) {
|
|
const msgs = [];
|
|
for (const concept of domain.concepts) {
|
|
if (conceptStatus(state, concept) === "mastered") msgs.push(`${concept.title} mastered`);
|
|
}
|
|
if (msgs.length === 0) msgs.push("Complete your first guided exercise to earn a visible mastery marker");
|
|
return msgs;
|
|
}
|
|
|
|
export function claimReadiness(state, domain, minScore = 0.75, minConfidence = 0.60) {
|
|
const records = domain.concepts
|
|
.map((c) => getRecord(state, c.id, c.masteryDimension || "mastery"))
|
|
.filter(Boolean);
|
|
const mastered = records.filter((r) => r.score >= minScore && r.confidence >= minConfidence).length;
|
|
const avgScore = records.length ? records.reduce((a, r) => a + r.score, 0) / records.length : 0;
|
|
const avgConfidence = records.length ? records.reduce((a, r) => a + r.confidence, 0) / records.length : 0;
|
|
return {
|
|
ready: mastered >= Math.max(1, domain.concepts.length - 1) && avgScore >= minScore && avgConfidence >= minConfidence,
|
|
mastered,
|
|
avgScore,
|
|
avgConfidence
|
|
};
|
|
}
|