@vue-skuilder/platform-ui 0.2.35 → 0.2.36
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/assets/{About-BWM7KlDK.js → About-74htcNLT.js} +2 -2
- package/dist/assets/{About-BWM7KlDK.js.map → About-74htcNLT.js.map} +1 -1
- package/dist/assets/AdminDashboard-B00qno2M.js +2 -0
- package/dist/assets/{AdminDashboard-CO2rxkF5.js.map → AdminDashboard-B00qno2M.js.map} +1 -1
- package/dist/assets/AdminLearner-SJNtSyJ_.js +2 -0
- package/dist/assets/{AdminLearner-CZO2Hlk1.js.map → AdminLearner-SJNtSyJ_.js.map} +1 -1
- package/dist/assets/BaseUserDB-CV2ZPZOR.js +1 -0
- package/dist/assets/ClassroomCtrlPanel-BSAG3h8B.js +2 -0
- package/dist/assets/{ClassroomCtrlPanel-cj6bokz5.js.map → ClassroomCtrlPanel-BSAG3h8B.js.map} +1 -1
- package/dist/assets/{Classrooms-BIC1PGz0.js → Classrooms-DXNEbtsM.js} +3 -3
- package/dist/assets/{Classrooms-BIC1PGz0.js.map → Classrooms-DXNEbtsM.js.map} +1 -1
- package/dist/assets/CouchDBSyncStrategy-BjM7ROXC.js +1 -0
- package/dist/assets/CourseRouter-CsVa_92r.js +7 -0
- package/dist/assets/{CourseRouter-CMMxD9us.js.map → CourseRouter-CsVa_92r.js.map} +1 -1
- package/dist/assets/CourseSyncService-CPU_Ll1h.js +1 -0
- package/dist/assets/CourseWare-BTFRjgBR-DBp_fIwH.js +3 -0
- package/dist/assets/{CourseWare-BTFRjgBR-C3wi5YHa.js.map → CourseWare-BTFRjgBR-DBp_fIwH.js.map} +1 -1
- package/dist/assets/{Courses-Dw7mgMnI.js → Courses-CB6GGtdm.js} +3 -3
- package/dist/assets/{Courses-Dw7mgMnI.js.map → Courses-CB6GGtdm.js.map} +1 -1
- package/dist/assets/EloModeration-DUBgs-Vh.js +2 -0
- package/dist/assets/{EloModeration-COgCrvb8.js.map → EloModeration-DUBgs-Vh.js.map} +1 -1
- package/dist/assets/{JoinCode-Fu9Tks7r.js → JoinCode-BBvYapCA.js} +2 -2
- package/dist/assets/{JoinCode-Fu9Tks7r.js.map → JoinCode-BBvYapCA.js.map} +1 -1
- package/dist/assets/MarkdownRenderer-WG9YrHpe.js +1 -0
- package/dist/assets/MarkdownRenderer-borh39Lq.js +53 -0
- package/dist/assets/MarkdownRenderer-borh39Lq.js.map +1 -0
- package/dist/assets/NewCourseDialog-DBgFVnt3.js +2 -0
- package/dist/assets/{NewCourseDialog-C11dSwWM.js.map → NewCourseDialog-DBgFVnt3.js.map} +1 -1
- package/dist/assets/PipelineDebugger-BHaqzqZw.js +177 -0
- package/dist/assets/PipelineDebugger-BHaqzqZw.js.map +1 -0
- package/dist/assets/PouchDataLayerProvider-DxsnSIKd.js +2 -0
- package/dist/assets/PouchDataLayerProvider-DxsnSIKd.js.map +1 -0
- package/dist/assets/{ReleaseNotes-CeTkuF5d.js → ReleaseNotes-Dg6mLL1C.js} +2 -2
- package/dist/assets/{ReleaseNotes-CeTkuF5d.js.map → ReleaseNotes-Dg6mLL1C.js.map} +1 -1
- package/dist/assets/RequestPasswordReset-CcFMTnTc.js +2 -0
- package/dist/assets/{RequestPasswordReset-vTzfGItp.js.map → RequestPasswordReset-CcFMTnTc.js.map} +1 -1
- package/dist/assets/ResetPassword-Dw_cBJU2.js +2 -0
- package/dist/assets/{ResetPassword-CmGaVYsI.js.map → ResetPassword-Dw_cBJU2.js.map} +1 -1
- package/dist/assets/StaticDataLayerProvider-CtgU3VBh.js +2 -0
- package/dist/assets/StaticDataLayerProvider-CtgU3VBh.js.map +1 -0
- package/dist/assets/Study-DDsGQCTJ.js +2 -0
- package/dist/assets/{Study-CkJvraIv.js.map → Study-DDsGQCTJ.js.map} +1 -1
- package/dist/assets/TagInformation-DBLoXA2a.js +2 -0
- package/dist/assets/{TagInformation-xZcQ1C1t.js.map → TagInformation-DBLoXA2a.js.map} +1 -1
- package/dist/assets/User-DfXUXjFb.js +2 -0
- package/dist/assets/User-DfXUXjFb.js.map +1 -0
- package/dist/assets/UserDBDebugger-DPqlc4O_.js +23 -0
- package/dist/assets/UserDBDebugger-DPqlc4O_.js.map +1 -0
- package/dist/assets/UserDBDebugger-xmycWjck.js +1 -0
- package/dist/assets/UserStats-C04YVYv7.js +2 -0
- package/dist/assets/{UserStats-CioNBbmL.js.map → UserStats-C04YVYv7.js.map} +1 -1
- package/dist/assets/VerifyEmail-Y05BSR57.js +2 -0
- package/dist/assets/{VerifyEmail-Bh-T9191.js.map → VerifyEmail-Y05BSR57.js.map} +1 -1
- package/dist/assets/admin-DcFjvYJw.js +21 -0
- package/dist/assets/admin-DcFjvYJw.js.map +1 -0
- package/dist/assets/chess-E2uOAyQS-BZi0vihz.js +7 -0
- package/dist/assets/{chess-E2uOAyQS-C8213VIQ.js.map → chess-E2uOAyQS-BZi0vihz.js.map} +1 -1
- package/dist/assets/chess-Lfm7Fsvs-DgN08Qx9.js +1 -0
- package/dist/assets/contentSource-DwznMSMs.js +34 -0
- package/dist/assets/contentSource-DwznMSMs.js.map +1 -0
- package/dist/assets/defineProperty-y9rbOPYz.js +1 -0
- package/dist/assets/dist-BcwaOf54.js +1 -0
- package/dist/assets/dist-BsRvt0gH.js +38 -0
- package/dist/assets/dist-BsRvt0gH.js.map +1 -0
- package/dist/assets/dist-CAHkbl3M.js +18 -0
- package/dist/assets/dist-CAHkbl3M.js.map +1 -0
- package/dist/assets/dist-CgW7D_Gh.js +1 -0
- package/dist/assets/dist-Dnehy0sn.js +1 -0
- package/dist/assets/dist-Dp6eBE2S.js +64 -0
- package/dist/assets/dist-Dp6eBE2S.js.map +1 -0
- package/dist/assets/edit-ui.es-BWqvElsJ.js +1 -0
- package/dist/assets/edit-ui.es-UaQFL_My.js +41 -0
- package/dist/assets/edit-ui.es-UaQFL_My.js.map +1 -0
- package/dist/assets/elo-B5qCwvSC.js +2 -0
- package/dist/assets/elo-B5qCwvSC.js.map +1 -0
- package/dist/assets/elo-COgu6Hu5.js +2 -0
- package/dist/assets/elo-COgu6Hu5.js.map +1 -0
- package/dist/assets/elo-Cwa8a3Yo.js +1 -0
- package/dist/assets/eloDistance-BV7vfiK5.js +2 -0
- package/dist/assets/eloDistance-BV7vfiK5.js.map +1 -0
- package/dist/assets/eloDistance-MReQeAuj.js +1 -0
- package/dist/assets/french-Dk7YG8Td-CD7Hms2j.js +2 -0
- package/dist/assets/{french-Dk7YG8Td-D9eV2DXL.js.map → french-Dk7YG8Td-CD7Hms2j.js.map} +1 -1
- package/dist/assets/french-evUMlbbq-nGCjilG6.js +1 -0
- package/dist/assets/hierarchyDefinition-DLC5DjaA.js +2 -0
- package/dist/assets/hierarchyDefinition-DLC5DjaA.js.map +1 -0
- package/dist/assets/{index-NFrxkZjT.css → index-B7mmpKZd.css} +1 -1
- package/dist/assets/index-CK-rfSIx.js +4 -0
- package/dist/assets/{index-CDxzcoT1.js.map → index-CK-rfSIx.js.map} +1 -1
- package/dist/assets/interferenceMitigator-D78KgOxz.js +2 -0
- package/dist/assets/interferenceMitigator-D78KgOxz.js.map +1 -0
- package/dist/assets/logshim-YQI0WloD.js +2 -0
- package/dist/assets/logshim-YQI0WloD.js.map +1 -0
- package/dist/assets/math-DR3fXDcX-C9KfHLHP.js +1 -0
- package/dist/assets/math-D_8HiEQR-E4QDLrWp.js +34 -0
- package/dist/assets/{math-D_8HiEQR-DdtcYQLB.js.map → math-D_8HiEQR-E4QDLrWp.js.map} +1 -1
- package/dist/assets/moment-DbT30ZpG.js +5 -0
- package/dist/assets/moment-DbT30ZpG.js.map +1 -0
- package/dist/assets/piano-BN5Btq91-CnKhyLGE.js +1 -0
- package/dist/assets/piano-DF1g6yaX-Ck3BH5EK.js +2 -0
- package/dist/assets/{piano-DF1g6yaX-B7f_kt1X.js.map → piano-DF1g6yaX-Ck3BH5EK.js.map} +1 -1
- package/dist/assets/pitch-CgGJFkZ1-Dd0LzmEe.js +2 -0
- package/dist/assets/{pitch-CgGJFkZ1-CuezrVTg.js.map → pitch-CgGJFkZ1-Dd0LzmEe.js.map} +1 -1
- package/dist/assets/pitch-Dn0iNqiS-BxAdpDDb.js +1 -0
- package/dist/assets/prescribed-BWO17Gan.js +2 -0
- package/dist/assets/prescribed-BWO17Gan.js.map +1 -0
- package/dist/assets/relativePriority-DwPMECAp.js +2 -0
- package/dist/assets/relativePriority-DwPMECAp.js.map +1 -0
- package/dist/assets/server-CqR7xtQy.js +2 -0
- package/dist/assets/server-CqR7xtQy.js.map +1 -0
- package/dist/assets/{sightsing-BFQ7HRig-CLh3Xcy7.js → sightsing-BFQ7HRig-DV69ELZf.js} +23 -23
- package/dist/assets/{sightsing-BFQ7HRig-CLh3Xcy7.js.map → sightsing-BFQ7HRig-DV69ELZf.js.map} +1 -1
- package/dist/assets/sightsing-CJX3k0Fd-CG94OiDr.js +1 -0
- package/dist/assets/srs-DGscV6W4.js +1 -0
- package/dist/assets/srs-DOsG9Y-i.js +2 -0
- package/dist/assets/srs-DOsG9Y-i.js.map +1 -0
- package/dist/assets/tagRoles-jR-PJAgj.js +2 -0
- package/dist/assets/tagRoles-jR-PJAgj.js.map +1 -0
- package/dist/assets/typing-BevtfWlp-CvStu5BY.js +2 -0
- package/dist/assets/{typing-BevtfWlp-B__x_Ia1.js.map → typing-BevtfWlp-CvStu5BY.js.map} +1 -1
- package/dist/assets/typing-k-ojjO7G-AF8vU_q7.js +1 -0
- package/dist/assets/userDBKeys-DYLGKnVC.js +4 -0
- package/dist/assets/userDBKeys-DYLGKnVC.js.map +1 -0
- package/dist/assets/userTagPreference-cZCPqadC.js +2 -0
- package/dist/assets/userTagPreference-cZCPqadC.js.map +1 -0
- package/dist/assets/validators-lpYAHmRR.js +2 -0
- package/dist/assets/validators-lpYAHmRR.js.map +1 -0
- package/dist/assets/word-work-BOnRlZgd-CCGZdwyf.js +2 -0
- package/dist/assets/{word-work-BOnRlZgd-CxcTzMPz.js.map → word-work-BOnRlZgd-CCGZdwyf.js.map} +1 -1
- package/dist/assets/word-work-Dd6tIKrt-BNTSCq5F.js +1 -0
- package/dist/assets/{workbox-window.prod.es5-r2bRTooK.js → workbox-window.prod.es5-YsLcd14J.js} +1 -1
- package/dist/assets/{workbox-window.prod.es5-r2bRTooK.js.map → workbox-window.prod.es5-YsLcd14J.js.map} +1 -1
- package/dist/index.html +17 -7
- package/dist/sw.js +1 -1
- package/dist/sw.js.map +1 -1
- package/package.json +7 -7
- package/dist/assets/AdminDashboard-CO2rxkF5.js +0 -2
- package/dist/assets/AdminLearner-CZO2Hlk1.js +0 -2
- package/dist/assets/ClassroomCtrlPanel-cj6bokz5.js +0 -2
- package/dist/assets/CourseRouter-CMMxD9us.js +0 -7
- package/dist/assets/CourseWare-BTFRjgBR-C3wi5YHa.js +0 -3
- package/dist/assets/EloModeration-COgCrvb8.js +0 -2
- package/dist/assets/MarkdownRenderer-U_iPiA2x-DDruDu7E.js +0 -53
- package/dist/assets/MarkdownRenderer-U_iPiA2x-DDruDu7E.js.map +0 -1
- package/dist/assets/MarkdownRenderer-U_iPiA2x-VCa5mXh1.js +0 -1
- package/dist/assets/NewCourseDialog-C11dSwWM.js +0 -2
- package/dist/assets/RequestPasswordReset-vTzfGItp.js +0 -2
- package/dist/assets/ResetPassword-CmGaVYsI.js +0 -2
- package/dist/assets/Study-CkJvraIv.js +0 -2
- package/dist/assets/TagInformation-xZcQ1C1t.js +0 -2
- package/dist/assets/User-MzKFSneO.js +0 -3
- package/dist/assets/User-MzKFSneO.js.map +0 -1
- package/dist/assets/UserStats-CioNBbmL.js +0 -2
- package/dist/assets/VerifyEmail-Bh-T9191.js +0 -2
- package/dist/assets/admin-Dt07rW2C.js +0 -21
- package/dist/assets/admin-Dt07rW2C.js.map +0 -1
- package/dist/assets/chess-E2uOAyQS-C8213VIQ.js +0 -7
- package/dist/assets/chess-Lfm7Fsvs-6ElY3d5h.js +0 -1
- package/dist/assets/common-ui.es-B9r6QiWN.js +0 -38
- package/dist/assets/common-ui.es-B9r6QiWN.js.map +0 -1
- package/dist/assets/common-ui.es-j6sezLNk.js +0 -1
- package/dist/assets/dist-BeppzTWu.js +0 -380
- package/dist/assets/dist-BeppzTWu.js.map +0 -1
- package/dist/assets/dist-C1hpwy00.js +0 -1
- package/dist/assets/dist-CKO_fXB-.js +0 -18
- package/dist/assets/dist-CKO_fXB-.js.map +0 -1
- package/dist/assets/dist-CkRURFlm.js +0 -1
- package/dist/assets/dist-V9Xbab2f.js +0 -12
- package/dist/assets/dist-V9Xbab2f.js.map +0 -1
- package/dist/assets/edit-ui.es-B00e6OcT.js +0 -36
- package/dist/assets/edit-ui.es-B00e6OcT.js.map +0 -1
- package/dist/assets/edit-ui.es-BGTYLTZO.js +0 -1
- package/dist/assets/french-Dk7YG8Td-D9eV2DXL.js +0 -2
- package/dist/assets/french-evUMlbbq-DOmp0PEx.js +0 -1
- package/dist/assets/index-CDxzcoT1.js +0 -4
- package/dist/assets/math-DR3fXDcX-Clr1rrj8.js +0 -1
- package/dist/assets/math-D_8HiEQR-DdtcYQLB.js +0 -34
- package/dist/assets/piano-BN5Btq91-BO9b38qV.js +0 -1
- package/dist/assets/piano-DF1g6yaX-B7f_kt1X.js +0 -2
- package/dist/assets/pitch-CgGJFkZ1-CuezrVTg.js +0 -2
- package/dist/assets/pitch-Dn0iNqiS-L0LRhcjZ.js +0 -1
- package/dist/assets/server-BP-NvFg7.js +0 -2
- package/dist/assets/server-BP-NvFg7.js.map +0 -1
- package/dist/assets/sightsing-CJX3k0Fd-DiVzeNly.js +0 -1
- package/dist/assets/typing-BevtfWlp-B__x_Ia1.js +0 -2
- package/dist/assets/typing-k-ojjO7G-Dn4uH4pS.js +0 -1
- package/dist/assets/word-work-BOnRlZgd-CxcTzMPz.js +0 -2
- package/dist/assets/word-work-Dd6tIKrt-BOmhWlr6.js +0 -1
- /package/dist/assets/{common-ui-BY2Ut5jf.css → dist-BY2Ut5jf.css} +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{t as e}from"./tagRoles-jR-PJAgj.js";import{t}from"./defineProperty-y9rbOPYz.js";var EloRanker=class{constructor(e=32){t(this,`k`,void 0),this.k=e}setKFactor(e){this.k=e}getKFactor(){return this.k}getExpected(e,t){return 1/(1+10**((t-e)/400))}updateRating(e,t,n){return Math.round(n+this.k*(t-e))}};function pushTagPresentation(e,t,n=12){let r=[...e.recent??[],t],i=r.length>n?r.slice(r.length-n):r;return{...e,recent:i}}function countOnlyTouch(t,n){if(!n)return{count:1,score:-1};let r=e(t)?-1:n.score;return{...n,count:n.count+1,score:r}}function blankCourseElo(){return{global:{score:990+Math.round(Math.random()*20),count:0},tags:{},misc:{}}}function EloToNumber(e){return typeof e==`number`?e:isCourseElo(e)?e.global.score:e.score}function toCourseElo(e){if(typeof e==`string`)throw Error(`unsuitiably typed input to toCourseElo`);return typeof e==`number`?{global:{score:e,count:0},misc:{},tags:{}}:isCourseElo(e)?e:e===void 0?{global:{score:995+Math.random()*10,count:0},tags:{},misc:{}}:{global:e,tags:{},misc:{}}}function isCourseElo(e){return!e||typeof e!=`object`?!1:`global`in e&&`tags`in e}function adjustCourseScores(t,n,r,i){if(r<0||r>1)throw Error(`ELO performance rating must be between 0 and 1 - received ${r}`);let a=toCourseElo(t),o=toCourseElo(n);(i==null||!i.globalOnly)&&Object.keys(o.tags).forEach(t=>{if(e(t)){a.tags[t]=countOnlyTouch(t,a.tags[t]);return}let n=adjustScores(a.tags[t]?a.tags[t]:{count:0,score:a.global.score},o.tags[t],r);a.tags[t]=n.userElo,o.tags[t]=n.cardElo});let s=adjustScores(a.global,o.global,r);return a.global=s.userElo,o.global=s.cardElo,{userElo:a,cardElo:o}}function kForCount(e){return 16+48/(1+e/10)}function adjustScores(e,t,n){if(n<0||n>1)throw Error(`ELO performance rating must be between 0 and 1 - received ${n}`);let r=new EloRanker(kForCount(e.count)),i=new EloRanker(kForCount(t.count)),a=r.getExpected(e.score,t.score),o=r.updateRating(a,n,e.score),s=i.updateRating(1-a,1-n,t.score);return{userElo:{...e,score:o,count:e.count+1},cardElo:{...t,score:s,count:t.count+1}}}function adjustCourseScoresPerTag(t,n,r){let i=r._global;if(i<0||i>1)throw Error(`ELO _global score must be between 0 and 1 - received ${i}`);let a=toCourseElo(t),o=toCourseElo(n);for(let[t,n]of Object.entries(r)){if(t===`_global`)continue;if(n===null||e(t)){a.tags[t]=countOnlyTouch(t,a.tags[t]);continue}if(typeof n!=`number`||n<0||n>1)throw Error(`ELO tag score for '${t}' must be between 0 and 1 - received ${n}`);let r=a.tags[t],i=adjustScores(r&&r.score!==-1?r:{...r??{},count:r?.count??0,score:a.global.score},o.tags[t]??{count:0,score:o.global.score},n);a.tags[t]=i.userElo,o.tags[t]=i.cardElo}let s=adjustScores(a.global,o.global,i);return a.global=s.userElo,o.global=s.cardElo,{userElo:a,cardElo:o}}export{isCourseElo as a,blankCourseElo as i,adjustCourseScores as n,pushTagPresentation as o,adjustCourseScoresPerTag as r,toCourseElo as s,EloToNumber as t};
|
|
2
|
+
//# sourceMappingURL=elo-B5qCwvSC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elo-B5qCwvSC.js","names":[],"sources":["../../../common/dist/elo.mjs"],"sourcesContent":["import { isCountOnlyTag } from './tagRoles.mjs';\nexport class EloRanker {\n k;\n constructor(k = 32) {\n this.k = k;\n }\n setKFactor(k) {\n this.k = k;\n }\n getKFactor() {\n return this.k;\n }\n getExpected(a, b) {\n return 1 / (1 + Math.pow(10, (b - a) / 400));\n }\n updateRating(expected, actual, current) {\n return Math.round(current + this.k * (actual - expected));\n }\n}\n/** Ring-buffer capacity for `EloRank.recent`. Oldest entry is dropped on push. */\nexport const TAG_RECENT_MAX = 12;\n/**\n * Append a presentation to a rank's ring buffer, returning a new rank.\n * Pure: neither `rank` nor its `recent` array is mutated.\n */\nexport function pushTagPresentation(rank, entry, max = TAG_RECENT_MAX) {\n const prior = rank.recent ?? [];\n const next = [...prior, entry];\n const trimmed = next.length > max ? next.slice(next.length - max) : next;\n return { ...rank, recent: trimmed };\n}\n/** Score held by a count-only tag rating: clearly not a real ELO. */\nexport const COUNT_ONLY_SCORE = -1;\n/**\n * One count-only touch on a learner's tag rating: the count goes up, no ELO\n * moves. A bookkeeping-role tag (see tagRoles.ts) holds the sentinel, having\n * no rating by definition. Any other tag keeps a real score it already has: a\n * course may send null on a graded tag, and that must not erase its rating.\n */\nfunction countOnlyTouch(tag, rank) {\n if (!rank)\n return { count: 1, score: COUNT_ONLY_SCORE };\n const score = isCountOnlyTag(tag) ? COUNT_ONLY_SCORE : rank.score;\n return { ...rank, count: rank.count + 1, score };\n}\nexport function blankCourseElo() {\n return {\n global: {\n score: 990 + Math.round(Math.random() * 20),\n count: 0,\n },\n tags: {},\n misc: {},\n };\n}\nexport function EloToNumber(elo) {\n if (typeof elo === 'number') {\n return elo;\n }\n else if (isCourseElo(elo)) {\n return elo.global.score;\n }\n {\n return elo.score;\n }\n}\nexport function toElo(elo) {\n if (typeof elo === 'number') {\n return {\n score: elo,\n count: 0,\n };\n }\n else {\n return elo;\n }\n}\nexport function toCourseElo(elo) {\n if (typeof elo === 'string') {\n throw new Error('unsuitiably typed input to toCourseElo');\n }\n if (typeof elo === 'number') {\n return {\n global: {\n score: elo,\n count: 0,\n },\n misc: {},\n tags: {},\n };\n }\n else if (isCourseElo(elo)) {\n return elo;\n }\n else if (elo === undefined) {\n return {\n global: {\n score: 995 + Math.random() * 10,\n count: 0,\n },\n tags: {},\n misc: {},\n };\n }\n else {\n return {\n global: elo,\n tags: {},\n misc: {},\n };\n }\n}\nexport function isCourseElo(x) {\n if (!x || typeof x !== 'object') {\n return false;\n }\n return 'global' in x && 'tags' in x;\n}\n/**\n * Calculates updated ELO scores for users and content after they interact\n *\n * @param userElo current ELO score of the user\n * @param cardElo current ELO score of the card\n * @param userScore user performance against the card in range [0,1]\n * @param k optional scaling factor. Higher values -> larger score adjustments. Default 32.\n * @returns\n */\nexport function adjustCourseScores(aElo, bElo, userScore, options) {\n if (userScore < 0 || userScore > 1) {\n throw new Error(`ELO performance rating must be between 0 and 1 - received ${userScore}`);\n }\n const userElo = toCourseElo(aElo);\n const cardElo = toCourseElo(bElo);\n if (options == undefined || !options.globalOnly) {\n // grade on each tag present for the card; bookkeeping roles are only counted\n Object.keys(cardElo.tags).forEach((k) => {\n if (isCountOnlyTag(k)) {\n userElo.tags[k] = countOnlyTouch(k, userElo.tags[k]);\n return;\n }\n const userTagElo = userElo.tags[k]\n ? userElo.tags[k]\n : {\n count: 0,\n score: userElo.global.score, // todo: 1000?\n };\n const adjusted = adjustScores(userTagElo, cardElo.tags[k], userScore);\n userElo.tags[k] = adjusted.userElo;\n cardElo.tags[k] = adjusted.cardElo;\n });\n }\n const adjusted = adjustScores(userElo.global, cardElo.global, userScore);\n userElo.global = adjusted.userElo;\n cardElo.global = adjusted.cardElo;\n return {\n userElo,\n cardElo,\n };\n}\n/**\n * Hyperbolic K-factor: high sensitivity at low count (uncertain rating),\n * converging to a stable floor as evidence accumulates.\n *\n * K(0) = 64 — first encounter, large update\n * K(10) = 40 — midpoint at halflife\n * K(∞) → 16 — well-established rating\n */\nfunction kForCount(count) {\n const K_MIN = 16;\n const K_MAX = 64;\n const HALFLIFE = 10;\n return K_MIN + (K_MAX - K_MIN) / (1 + count / HALFLIFE);\n}\nfunction adjustScores(userElo, cardElo, userScore) {\n if (userScore < 0 || userScore > 1) {\n throw new Error(`ELO performance rating must be between 0 and 1 - received ${userScore}`);\n }\n const userRanker = new EloRanker(kForCount(userElo.count));\n const cardRanker = new EloRanker(kForCount(cardElo.count));\n const exp = userRanker.getExpected(userElo.score, cardElo.score);\n const updatedUserElo = userRanker.updateRating(exp, userScore, userElo.score);\n const updatedCardElo = cardRanker.updateRating(1 - exp, 1 - userScore, cardElo.score);\n // Spread first so sibling fields (e.g. `recent`) survive the adjustment.\n return {\n userElo: {\n ...userElo,\n score: updatedUserElo,\n count: userElo.count + 1,\n },\n cardElo: {\n ...cardElo,\n score: updatedCardElo,\n count: cardElo.count + 1,\n },\n };\n}\n/**\n * Adjusts ELO scores with per-tag granularity.\n *\n * Unlike adjustCourseScores which applies the same score to all tags,\n * this function allows different scores per tag for granular skill tracking.\n *\n * Tags can be scored (number 0-1) or count-only (null). Count-only tags are\n * useful for exposure tracking (e.g., gpc:expose:*) where we only care about\n * \"how many times has the user seen this?\" without measuring performance.\n * Bookkeeping roles (`expose`, `intro`; see tagRoles.ts) are count-only\n * whatever score is sent for them.\n *\n * @param aElo - User's current ELO (will be converted to CourseElo)\n * @param bElo - Card's current ELO (will be converted to CourseElo)\n * @param taggedPerformance - Object with _global score and per-tag scores/null\n * @returns Updated user and card ELOs\n *\n * @example\n * // Spelling \"cat\" as \"kat\" - got 'a' and 't' right, but 'c' wrong\n * adjustCourseScoresPerTag(userElo, cardElo, {\n * _global: 0.67,\n * 'gpc:exercise:c-K': 0,\n * 'gpc:exercise:a-AE': 1,\n * 'gpc:exercise:t-T': 1,\n * });\n *\n * @example\n * // WhoSaidThat - exercise target, expose distractors (count-only)\n * adjustCourseScoresPerTag(userElo, cardElo, {\n * _global: 1.0,\n * 'gpc:exercise:sh-SH': 1.0,\n * 'gpc:expose:s-S': null, // count-only\n * 'gpc:expose:ch-CH': null, // count-only\n * });\n */\nexport function adjustCourseScoresPerTag(aElo, bElo, taggedPerformance) {\n const globalScore = taggedPerformance._global;\n if (globalScore < 0 || globalScore > 1) {\n throw new Error(`ELO _global score must be between 0 and 1 - received ${globalScore}`);\n }\n const userElo = toCourseElo(aElo);\n const cardElo = toCourseElo(bElo);\n // Process each tag in the performance object\n for (const [key, tagScore] of Object.entries(taggedPerformance)) {\n if (key === '_global')\n continue;\n // Count-only: sent as null, or a bookkeeping role (whatever was sent).\n // The count goes up; no ELO moves, the card's included.\n if (tagScore === null || isCountOnlyTag(key)) {\n userElo.tags[key] = countOnlyTouch(key, userElo.tags[key]);\n continue;\n }\n if (typeof tagScore !== 'number' || tagScore < 0 || tagScore > 1) {\n throw new Error(`ELO tag score for '${key}' must be between 0 and 1 - received ${tagScore}`);\n }\n // Initialize tag ELO on user if missing or if the existing entry is the\n // count-only sentinel (-1). A sentinel entry means the tag was only seen\n // via null/exposure interactions; treat the first real-scored interaction\n // as a clean start from global ELO so it isn't penalized against a\n // fictitiously low prior. Preserve accumulated count so exposure-based\n // count-threshold progress is not discarded.\n const existingUserTagElo = userElo.tags[key];\n const userTagElo = existingUserTagElo && existingUserTagElo.score !== COUNT_ONLY_SCORE\n ? existingUserTagElo\n : {\n ...(existingUserTagElo ?? {}),\n count: existingUserTagElo?.count ?? 0,\n score: userElo.global.score,\n };\n // Initialize tag ELO on card if missing (use global score as baseline)\n const cardTagElo = cardElo.tags[key] ?? {\n count: 0,\n score: cardElo.global.score,\n };\n // Apply per-tag score\n const adjusted = adjustScores(userTagElo, cardTagElo, tagScore);\n userElo.tags[key] = adjusted.userElo;\n cardElo.tags[key] = adjusted.cardElo;\n }\n // Apply global score to global ELO\n const adjustedGlobal = adjustScores(userElo.global, cardElo.global, globalScore);\n userElo.global = adjustedGlobal.userElo;\n cardElo.global = adjustedGlobal.cardElo;\n return {\n userElo,\n cardElo,\n };\n}\n//# sourceMappingURL=elo.mjs.map\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{t as e}from"./defineProperty-y9rbOPYz.js";import{s as t}from"./elo-B5qCwvSC.js";import{k as n,l as r}from"./PipelineDebugger-BHaqzqZw.js";var ELONavigator=class extends r{constructor(t,n,r){super(t,n,r),e(this,`name`,void 0),this.name=r?.name||`ELO`}async getWeightedCards(e,r){let i;i=r?.userElo===void 0?t((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score:r.userElo;let[a,o]=await Promise.all([this.user.getActiveCards(),this.user.getSeenCards(this.course.getCourseID()).catch(()=>[])]),s=new Set([...a.map(e=>e.cardID),...o]),c=(await this.course.getCardsCenteredAtELO({limit:e,elo:i},e=>!s.has(e.cardID))).map(e=>({...e,status:`new`})).map(e=>{let t=e.elo??1e3,n=Math.abs(t-i),r=.5+.5*Math.random();return{cardId:e.cardID,courseId:e.courseID,score:r,cardElo:t,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:r,reason:`new card near learner ELO: distance ${Math.round(n)} (card: ${Math.round(t)}, user: ${Math.round(i)}), jitter ${r.toFixed(3)}`}]}});c.sort((e,t)=>t.score-e.score);let l=c.slice(0,e);if(l.length>0){let e=l.slice(0,3).map(e=>e.score.toFixed(2)).join(`, `);n.info(`[ELO] Course ${this.course.getCourseID()}: ${l.length} new cards (top scores: ${e})`)}else n.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:l}}};export{ELONavigator as t};
|
|
2
|
+
//# sourceMappingURL=elo-COgu6Hu5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elo-COgu6Hu5.js","names":[],"sources":["../../../db/dist/core/navigators/generators/elo.mjs"],"sourcesContent":["import \"../../../_virtual/_rolldown/runtime.mjs\";\nimport { logger } from \"../../../util/logger.mjs\";\nimport { ContentNavigator } from \"../index.mjs\";\nimport { toCourseElo } from \"@vue-skuilder/common\";\n//#region src/core/navigators/generators/elo.ts\n/**\n* A navigation strategy that retrieves new cards near the learner's ELO.\n*\n* Implements CardGenerator for use in Pipeline architecture.\n* Also extends ContentNavigator for backward compatibility with legacy code.\n*\n* Only returns new cards - use SRSNavigator for reviews.\n*/\nvar ELONavigator = class extends ContentNavigator {\n\t/** Human-readable name for CardGenerator interface */\n\tname;\n\tconstructor(user, course, strategyData) {\n\t\tsuper(user, course, strategyData);\n\t\tthis.name = strategyData?.name || \"ELO\";\n\t}\n\t/**\n\t* Get the new cards nearest the learner's ELO, each scored with a flat\n\t* jitter in [0.5, 1]. Carries each card's ELO for the ELO distance filter.\n\t*\n\t* NOTE: This generator only handles NEW cards. Reviews are handled by\n\t* SRSNavigator. Use CompositeGenerator to combine both.\n\t*\n\t* This method supports both the legacy signature (limit only) and the\n\t* CardGenerator interface signature (limit, context).\n\t*\n\t* @param limit - Maximum number of cards to return\n\t* @param context - Optional GeneratorContext (used when called via Pipeline)\n\t*/\n\tasync getWeightedCards(limit, context) {\n\t\tlet userGlobalElo;\n\t\tif (context?.userElo !== void 0) userGlobalElo = context.userElo;\n\t\telse userGlobalElo = toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score;\n\t\tconst [activeCards, seenCards] = await Promise.all([this.user.getActiveCards(), this.user.getSeenCards(this.course.getCourseID()).catch(() => [])]);\n\t\tconst notNew = new Set([...activeCards.map((ac) => ac.cardID), ...seenCards]);\n\t\tconst scored = (await this.course.getCardsCenteredAtELO({\n\t\t\tlimit,\n\t\t\telo: userGlobalElo\n\t\t}, (c) => !notNew.has(c.cardID))).map((c) => ({\n\t\t\t...c,\n\t\t\tstatus: \"new\"\n\t\t})).map((c) => {\n\t\t\tconst cardElo = c.elo ?? 1e3;\n\t\t\tconst distance = Math.abs(cardElo - userGlobalElo);\n\t\t\tconst jitter = .5 + .5 * Math.random();\n\t\t\treturn {\n\t\t\t\tcardId: c.cardID,\n\t\t\t\tcourseId: c.courseID,\n\t\t\t\tscore: jitter,\n\t\t\t\tcardElo,\n\t\t\t\tprovenance: [{\n\t\t\t\t\tstrategy: \"elo\",\n\t\t\t\t\tstrategyName: this.strategyName || this.name,\n\t\t\t\t\tstrategyId: this.strategyId || \"NAVIGATION_STRATEGY-ELO-default\",\n\t\t\t\t\taction: \"generated\",\n\t\t\t\t\tscore: jitter,\n\t\t\t\t\treason: `new card near learner ELO: distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userGlobalElo)}), jitter ${jitter.toFixed(3)}`\n\t\t\t\t}]\n\t\t\t};\n\t\t});\n\t\tscored.sort((a, b) => b.score - a.score);\n\t\tconst cards = scored.slice(0, limit);\n\t\tif (cards.length > 0) {\n\t\t\tconst topScores = cards.slice(0, 3).map((c) => c.score.toFixed(2)).join(\", \");\n\t\t\tlogger.info(`[ELO] Course ${this.course.getCourseID()}: ${cards.length} new cards (top scores: ${topScores})`);\n\t\t} else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);\n\t\treturn { cards };\n\t}\n};\n//#endregion\nexport { ELONavigator as default };\n\n//# sourceMappingURL=elo.mjs.map"],"mappings":"6KAaA,IAAI,aAAe,cAAc,CAAiB,CAGjD,YAAY,EAAM,EAAQ,EAAc,CACvC,MAAM,EAAM,EAAQ,EAAa,QAFlC,OAAA,IAAA,GAAK,CAGJ,KAAK,KAAO,GAAc,MAAQ,MAenC,MAAM,iBAAiB,EAAO,EAAS,CACtC,IAAI,EACJ,AAAiC,EAA7B,GAAS,UAAY,IAAK,GACT,GAAa,MAAM,KAAK,KAAK,gBAAgB,KAAK,OAAO,aAAa,CAAC,EAAE,IAAI,CAAC,OAAO,MADzD,EAAQ,QAEzD,GAAM,CAAC,EAAa,GAAa,MAAM,QAAQ,IAAI,CAAC,KAAK,KAAK,gBAAgB,CAAE,KAAK,KAAK,aAAa,KAAK,OAAO,aAAa,CAAC,CAAC,UAAY,EAAE,CAAC,CAAC,CAAC,CAC7I,EAAS,IAAI,IAAI,CAAC,GAAG,EAAY,IAAK,GAAO,EAAG,OAAO,CAAE,GAAG,EAAU,CAAC,CACvE,GAAU,MAAM,KAAK,OAAO,sBAAsB,CACvD,QACA,IAAK,EACL,CAAG,GAAM,CAAC,EAAO,IAAI,EAAE,OAAO,CAAC,EAAE,IAAK,IAAO,CAC7C,GAAG,EACH,OAAQ,MACR,EAAE,CAAC,IAAK,GAAM,CACd,IAAM,EAAU,EAAE,KAAO,IACnB,EAAW,KAAK,IAAI,EAAU,EAAc,CAC5C,EAAS,GAAK,GAAK,KAAK,QAAQ,CACtC,MAAO,CACN,OAAQ,EAAE,OACV,SAAU,EAAE,SACZ,MAAO,EACP,UACA,WAAY,CAAC,CACZ,SAAU,MACV,aAAc,KAAK,cAAgB,KAAK,KACxC,WAAY,KAAK,YAAc,kCAC/B,OAAQ,YACR,MAAO,EACP,OAAQ,uCAAuC,KAAK,MAAM,EAAS,CAAC,UAAU,KAAK,MAAM,EAAQ,CAAC,UAAU,KAAK,MAAM,EAAc,CAAC,YAAY,EAAO,QAAQ,EAAE,GACnK,CAAC,CACF,EACA,CACF,EAAO,MAAM,EAAG,IAAM,EAAE,MAAQ,EAAE,MAAM,CACxC,IAAM,EAAQ,EAAO,MAAM,EAAG,EAAM,CACpC,GAAI,EAAM,OAAS,EAAG,CACrB,IAAM,EAAY,EAAM,MAAM,EAAG,EAAE,CAAC,IAAK,GAAM,EAAE,MAAM,QAAQ,EAAE,CAAC,CAAC,KAAK,KAAK,CAC7E,EAAO,KAAK,gBAAgB,KAAK,OAAO,aAAa,CAAC,IAAI,EAAM,OAAO,0BAA0B,EAAU,GAAG,MACxG,EAAO,KAAK,gBAAgB,KAAK,OAAO,aAAa,CAAC,0BAA0B,CACvF,MAAO,CAAE,QAAO"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./PipelineDebugger-BHaqzqZw.js";import{t as e}from"./elo-COgu6Hu5.js";export{e as default};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{t as e}from"./tagRoles-jR-PJAgj.js";import{t}from"./defineProperty-y9rbOPYz.js";import{k as n,l as r}from"./PipelineDebugger-BHaqzqZw.js";var i,a={halfLife:300,minMultiplier:.05,maxMultiplier:1,tagAware:!1,minTagCount:3},o=10,s=1e3;function computeMultiplier(e,t,n,r){let i=e/t,a=Math.exp(-(i*i));return n+(r-n)*a}function resolveConfig(e){return{...a,...e}}var c=`skuilder:multiDimElo`,l=readMultiDimElo();function readMultiDimElo(){try{return typeof localStorage<`u`&&localStorage.getItem(c)===`true`}catch{return!1}}function mountMultiDimEloToggle(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},Object.defineProperty(e.skuilder,`multiDimElo`,{configurable:!0,enumerable:!0,get:()=>l,set:e=>{l=e===!0;try{l?localStorage.setItem(c,`true`):localStorage.removeItem(c)}catch(e){n.warn(`[EloDistance] multiDimElo not persisted (localStorage unavailable): ${e}`)}n.print(`[EloDistance] multiDimElo ${l?`on: tag-aware`:`off: per strategy config`} ELO distance from the next replan`)}})}mountMultiDimEloToggle();function tagAwareGap(t,n,r,i){let a=n.global.score-r.global.score,s=[];for(let a of t){let t=r.tags[a];if(e(a)||!t||t.score===-1||t.count<i)continue;let c=n.tags[a]?.score??n.global.score;s.push({tag:a,gap:c-t.score,weight:t.count/(t.count+o)})}if(s.length===0)return{gap:a,globalGap:a,tags:s};let c=s.reduce((e,t)=>e+t.weight,0),l=s.reduce((e,t)=>e+t.weight*t.gap,0)/c,u=c/(c+1);return{gap:u*l+(1-u)*a,globalGap:a,tags:s}}function tagReason(e,t){let n=[...e].sort((e,t)=>Math.abs(t.gap)*t.weight-Math.abs(e.gap)*e.weight).slice(0,3).map(e=>`${e.tag} ${e.gap>=0?`+`:``}${Math.round(e.gap)}`).join(`, `),r=e.length>3?`, +${e.length-3} more`:``;return` on ${e.length} shared tag(s) [${n}${r}] (global ${t>=0?`+`:``}${Math.round(t)})`}async function applyEloDistance(e,t,n,r){let{course:i,userElo:a,userCourseElo:o}=t,{halfLife:c,minMultiplier:u,maxMultiplier:d}=n,f=(n.tagAware||l)&&o!==void 0,p=e.filter(e=>f||e.cardElo===void 0).map(e=>e.cardId),m=new Map;if(p.length>0){let e=await i.getCardEloData(p);p.forEach((t,n)=>{e[n]&&m.set(t,e[n])})}return e.map(e=>{let t=m.get(e.cardId),i=t?.global?.score??e.cardElo??s,l=Math.abs(i-a),p=``;if(f){let r=t?tagAwareGap(e.tags??[],t,o,n.minTagCount):void 0;r&&(l=Math.abs(r.gap)),p=r?.tags.length?`, tag-aware${tagReason(r.tags,r.globalGap)}`:`, tag-aware: global (no shared tags)`}let h=computeMultiplier(l,c,u,d),g=e.score*h,_=h<d-.01?`penalized`:`passed`;return{...e,cardElo:i,score:g,provenance:[...e.provenance,{strategy:`eloDistance`,strategyName:r.name,strategyId:r.strategyId,action:_,score:g,reason:`ELO distance ${Math.round(l)} (card: ${Math.round(i)}, user: ${Math.round(a)})${p} → ${h.toFixed(2)}x`}]}})}function createEloDistanceFilter(e){let t=resolveConfig(e),n={name:`ELO Distance Filter`,strategyId:`ELO_DISTANCE_FILTER`};return{name:n.name,kind:`signal`,liveOnly:!0,transform:(e,r)=>applyEloDistance(e,r,t,n)}}var u=(i=class EloDistanceFilter extends r{constructor(e,n,r){super(e,n,r),t(this,`name`,void 0),t(this,`kind`,EloDistanceFilter.kind),t(this,`liveOnly`,!0),t(this,`config`,void 0),this.name=r.name||`ELO Distance`;let i;try{i=r.serializedData?JSON.parse(r.serializedData):void 0}catch{i=void 0}this.config=resolveConfig(i)}transform(e,t){return applyEloDistance(e,t,this.config,{name:this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-eloDistance-default`})}async getWeightedCards(e){throw Error(`EloDistanceFilter is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}},t(i,`kind`,`signal`),i);export{tagAwareGap as a,mountMultiDimEloToggle as i,u as n,createEloDistanceFilter as r,a as t};
|
|
2
|
+
//# sourceMappingURL=eloDistance-BV7vfiK5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eloDistance-BV7vfiK5.js","names":[],"sources":["../../../db/dist/core/navigators/filters/eloDistance.mjs"],"sourcesContent":["import \"../../../_virtual/_rolldown/runtime.mjs\";\nimport { logger } from \"../../../util/logger.mjs\";\nimport { ContentNavigator } from \"../index.mjs\";\nimport { COUNT_ONLY_SCORE, isCountOnlyTag } from \"@vue-skuilder/common\";\n//#region src/core/navigators/filters/eloDistance.ts\n/**\n* The default curve. The default strategy doc carries it as its config, so\n* the admin pipeline view shows what runs.\n*/\nconst DEFAULT_ELO_DISTANCE_CONFIG = {\n\thalfLife: 300,\n\tminMultiplier: .05,\n\tmaxMultiplier: 1,\n\ttagAware: false,\n\tminTagCount: 3\n};\n/**\n* Graded responses at which a tag's gap carries half its full weight. Thin\n* tag evidence leans on global ELO; well-practised tags dominate.\n*/\nconst TAG_CONFIDENCE_HALF = 10;\n/** Fallback card ELO when a card has none recorded. */\nconst DEFAULT_CARD_ELO = 1e3;\nfunction computeMultiplier(distance, halfLife, minMultiplier, maxMultiplier) {\n\tconst normalizedDistance = distance / halfLife;\n\tconst decay = Math.exp(-(normalizedDistance * normalizedDistance));\n\treturn minMultiplier + (maxMultiplier - minMultiplier) * decay;\n}\nfunction resolveConfig(config) {\n\treturn {\n\t\t...DEFAULT_ELO_DISTANCE_CONFIG,\n\t\t...config\n\t};\n}\nconst MULTI_DIM_ELO_KEY = \"skuilder:multiDimElo\";\nlet multiDimElo = readMultiDimElo();\nfunction readMultiDimElo() {\n\ttry {\n\t\treturn typeof localStorage !== \"undefined\" && localStorage.getItem(MULTI_DIM_ELO_KEY) === \"true\";\n\t} catch {\n\t\treturn false;\n\t}\n}\n/**\n* Mount the toggle on window.skuilder.multiDimElo.\n*/\nfunction mountMultiDimEloToggle() {\n\tif (typeof window === \"undefined\") return;\n\tconst win = window;\n\twin.skuilder = win.skuilder || {};\n\tObject.defineProperty(win.skuilder, \"multiDimElo\", {\n\t\tconfigurable: true,\n\t\tenumerable: true,\n\t\tget: () => multiDimElo,\n\t\tset: (on) => {\n\t\t\tmultiDimElo = on === true;\n\t\t\ttry {\n\t\t\t\tif (multiDimElo) localStorage.setItem(MULTI_DIM_ELO_KEY, \"true\");\n\t\t\t\telse localStorage.removeItem(MULTI_DIM_ELO_KEY);\n\t\t\t} catch (e) {\n\t\t\t\tlogger.warn(`[EloDistance] multiDimElo not persisted (localStorage unavailable): ${e}`);\n\t\t\t}\n\t\t\tlogger.print(`[EloDistance] multiDimElo ${multiDimElo ? \"on: tag-aware\" : \"off: per strategy config\"} ELO distance from the next replan`);\n\t\t}\n\t});\n}\nmountMultiDimEloToggle();\n/**\n* The card's difficulty for this learner, in ELO points (card − learner),\n* read on the tags they share.\n*\n* - Shared tags: the card's tags on which the learner has a real rating\n* (not a bookkeeping role, nor the count-only sentinel) resting on at least\n* `minTagCount` graded responses. A card with no rating of its own on a tag\n* uses its global, as the ELO update does when it first grades that tag.\n* - Each tag's gap is weighted by the learner's evidence on it,\n* n / (n + TAG_CONFIDENCE_HALF).\n* - The weighted mean of the tag gaps is blended with the global gap by the\n* total evidence W: λ = W / (W + 1). One thin tag barely moves it; several\n* practised tags dominate it.\n*\n* OPEN (draft, 2026-10-01). Shipped on by default in 0.2.29 by accident (it\n* rode along in the version-bump commit); off by default since, and live only\n* behind the toggle. On one LP learner's 09-30 dump the effect was modest: every touched card shared a rated tag\n* (median 4); mean multiplier ×0.70 → ×0.75 over 352 touched cards, ×0.84 →\n* ×0.88 over 42 due reviews. The biggest movers were old who-said-that cards:\n* ~400 below the learner globally, ~280 below on their own skills.\n*\n* 1. Compensatory or conjunctive. This averages: a weak skill is offset by\n* strong ones. For spelling a word, which needs every grapheme, the hardest\n* shared skill arguably sets the difficulty: take the largest gap instead.\n* The main design question.\n* 2. Which tags count. Broad tags (`concept:match:simple`, `ui:*`) sit on most\n* cards and act like a second global rating. The framework may want the\n* course to say which tags are skills (cf. LP's diagnostics interpreter\n* `isSkillTag`), e.g. as tag patterns in the strategy config.\n* The admin ELO viewer (in progress, 2026-10) answers the same question for\n* display with its own tag batches (role × namespace). Before tag-aware goes\n* on by default, make that one predicate:\n* - a serializable selector (roles, prefix patterns), beside `tagRoles.ts`\n* in common, so a strategy doc can carry it;\n* - an `EloDistanceConfig` field for it, read by `tagAwareGap`;\n* - `tagAwareGap` moved to a pure module (this file imports `core`, which\n* the diagnostics entry can't), so the viewer can show what the filter\n* would compute;\n* - the viewer's default batch read from the course's eloDistance config.\n* 3. New cards matter most, and that dump couldn't show them. An unseen\n* card's tag ratings fall back to its global, so for new cards this reads\n* \"card global vs the learner's rating on that skill\". Measuring it needs a\n* `--course all` dump.\n* 4. Cost. Tag-aware fetches full ratings for every candidate (~500 card docs\n* per run); global-only fetches only reviews'.\n* 5. Retrieval stays global. The ELO generator's window is the cards nearest\n* the learner's global ELO, so a card that's well matched on its skills but\n* far off globally may never be retrieved.\n* 6. Target. Distance 0 means ~50% expected success. Early learners may do\n* better aiming higher: a shift of the curve's centre, independent of this.\n*/\nfunction tagAwareGap(cardTags, cardElo, userElo, minTagCount) {\n\tconst globalGap = cardElo.global.score - userElo.global.score;\n\tconst tags = [];\n\tfor (const tag of cardTags) {\n\t\tconst user = userElo.tags[tag];\n\t\tif (isCountOnlyTag(tag) || !user || user.score === COUNT_ONLY_SCORE || user.count < minTagCount) continue;\n\t\tconst card = cardElo.tags[tag]?.score ?? cardElo.global.score;\n\t\ttags.push({\n\t\t\ttag,\n\t\t\tgap: card - user.score,\n\t\t\tweight: user.count / (user.count + TAG_CONFIDENCE_HALF)\n\t\t});\n\t}\n\tif (tags.length === 0) return {\n\t\tgap: globalGap,\n\t\tglobalGap,\n\t\ttags\n\t};\n\tconst total = tags.reduce((w, t) => w + t.weight, 0);\n\tconst tagGap = tags.reduce((g, t) => g + t.weight * t.gap, 0) / total;\n\tconst lambda = total / (total + 1);\n\treturn {\n\t\tgap: lambda * tagGap + (1 - lambda) * globalGap,\n\t\tglobalGap,\n\t\ttags\n\t};\n}\nfunction tagReason(tags, globalGap) {\n\tconst shown = [...tags].sort((a, b) => Math.abs(b.gap) * b.weight - Math.abs(a.gap) * a.weight).slice(0, 3).map((t) => `${t.tag} ${t.gap >= 0 ? \"+\" : \"\"}${Math.round(t.gap)}`).join(\", \");\n\tconst more = tags.length > 3 ? `, +${tags.length - 3} more` : \"\";\n\treturn ` on ${tags.length} shared tag(s) [${shown}${more}] (global ${globalGap >= 0 ? \"+\" : \"\"}${Math.round(globalGap)})`;\n}\nasync function applyEloDistance(cards, context, config, source) {\n\tconst { course, userElo, userCourseElo } = context;\n\tconst { halfLife, minMultiplier, maxMultiplier } = config;\n\tconst tagAware = (config.tagAware || multiDimElo) && userCourseElo !== void 0;\n\tconst missing = cards.filter((c) => tagAware || c.cardElo === void 0).map((c) => c.cardId);\n\tconst fetched = /* @__PURE__ */ new Map();\n\tif (missing.length > 0) {\n\t\tconst elos = await course.getCardEloData(missing);\n\t\tmissing.forEach((id, i) => {\n\t\t\tif (elos[i]) fetched.set(id, elos[i]);\n\t\t});\n\t}\n\treturn cards.map((card) => {\n\t\tconst full = fetched.get(card.cardId);\n\t\tconst cardElo = full?.global?.score ?? card.cardElo ?? DEFAULT_CARD_ELO;\n\t\tlet distance = Math.abs(cardElo - userElo);\n\t\tlet how = \"\";\n\t\tif (tagAware) {\n\t\t\tconst t = full ? tagAwareGap(card.tags ?? [], full, userCourseElo, config.minTagCount) : void 0;\n\t\t\tif (t) distance = Math.abs(t.gap);\n\t\t\thow = t?.tags.length ? `, tag-aware${tagReason(t.tags, t.globalGap)}` : \", tag-aware: global (no shared tags)\";\n\t\t}\n\t\tconst multiplier = computeMultiplier(distance, halfLife, minMultiplier, maxMultiplier);\n\t\tconst newScore = card.score * multiplier;\n\t\tconst action = multiplier < maxMultiplier - .01 ? \"penalized\" : \"passed\";\n\t\treturn {\n\t\t\t...card,\n\t\t\tcardElo,\n\t\t\tscore: newScore,\n\t\t\tprovenance: [...card.provenance, {\n\t\t\t\tstrategy: \"eloDistance\",\n\t\t\t\tstrategyName: source.name,\n\t\t\t\tstrategyId: source.strategyId,\n\t\t\t\taction,\n\t\t\t\tscore: newScore,\n\t\t\t\treason: `ELO distance ${Math.round(distance)} (card: ${Math.round(cardElo)}, user: ${Math.round(userElo)})${how} → ${multiplier.toFixed(2)}x`\n\t\t\t}]\n\t\t};\n\t});\n}\n/**\n* Create an ELO distance filter outside the strategy-doc path.\n*\n* @param config - Optional configuration for the curve\n*/\nfunction createEloDistanceFilter(config) {\n\tconst resolved = resolveConfig(config);\n\tconst source = {\n\t\tname: \"ELO Distance Filter\",\n\t\tstrategyId: \"ELO_DISTANCE_FILTER\"\n\t};\n\treturn {\n\t\tname: source.name,\n\t\tkind: \"signal\",\n\t\tliveOnly: true,\n\t\ttransform: (cards, context) => applyEloDistance(cards, context, resolved, source)\n\t};\n}\n/**\n* The ELO distance filter as a strategy-doc navigator (`implementingClass:\n* 'eloDistance'`, serializedData: an EloDistanceConfig as JSON, or empty).\n*/\nvar EloDistanceFilter = class EloDistanceFilter extends ContentNavigator {\n\tname;\n\t/** See CardFilter.kind. Static, so the pipeline plan can read it without an instance. */\n\tstatic kind = \"signal\";\n\tkind = EloDistanceFilter.kind;\n\tliveOnly = true;\n\tconfig;\n\tconstructor(user, course, strategyData) {\n\t\tsuper(user, course, strategyData);\n\t\tthis.name = strategyData.name || \"ELO Distance\";\n\t\tlet parsed;\n\t\ttry {\n\t\t\tparsed = strategyData.serializedData ? JSON.parse(strategyData.serializedData) : void 0;\n\t\t} catch {\n\t\t\tparsed = void 0;\n\t\t}\n\t\tthis.config = resolveConfig(parsed);\n\t}\n\ttransform(cards, context) {\n\t\treturn applyEloDistance(cards, context, this.config, {\n\t\t\tname: this.name,\n\t\t\tstrategyId: this.strategyId || \"NAVIGATION_STRATEGY-eloDistance-default\"\n\t\t});\n\t}\n\tasync getWeightedCards(_limit) {\n\t\tthrow new Error(\"EloDistanceFilter is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().\");\n\t}\n};\n//#endregion\nexport { DEFAULT_ELO_DISTANCE_CONFIG, createEloDistanceFilter, EloDistanceFilter as default, mountMultiDimEloToggle, tagAwareGap };\n\n//# sourceMappingURL=eloDistance.mjs.map"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./PipelineDebugger-BHaqzqZw.js";import{a as e,i as t,n,r,t as i}from"./eloDistance-BV7vfiK5.js";export{i as DEFAULT_ELO_DISTANCE_CONFIG,r as createEloDistanceFilter,n as default,t as mountMultiDimEloToggle,e as tagAwareGap};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{C as e,H as t,Ht as n,K as r,S as i,T as a,_ as o,b as s,f as c,g as l,ut as u,y as d}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{C as f,S as p,i as m}from"./MarkdownRenderer-borh39Lq.js";import{P as h}from"./dist-BsRvt0gH.js";import{K as g,U as _}from"./contentSource-DwznMSMs.js";import{t as v}from"./defineProperty-y9rbOPYz.js";import{t as y}from"./validators-lpYAHmRR.js";import{m as b,n as x,t as S,u as C}from"./CourseWare-BTFRjgBR-DBp_fIwH.js";var w,T,E=a({name:`AudioParseView`,components:{AudioAutoPlayer:m},props:{data:{type:Array,required:!0},modifyDifficulty:{type:Number,required:!1,default:0}},setup(e,{emit:t}){let n=f(e,t,`AudioParseView`),r=p(n),i=u(``);r.question.value=new k(e.data);let a=l(()=>r.question.value),submit=()=>{a.value};return{...n,...r,answer:i,question:a,submit}}}),D={"data-viewable":`AudioParseView`};function _sfc_render$1(a,l,u,f,p,m){let h=r(`AudioAutoPlayer`);return t(),s(`div`,D,[a.question?(t(),s(c,{key:0},[e(h,{src:a.getURL(`audio`)},null,8,[`src`]),l[0]||(l[0]=o(`br`,null,null,-1)),l[1]||(l[1]=o(`br`,null,null,-1)),i(` `+n(a.question.text),1)],64)):d(``,!0)])}C(_sfc_render$1,`_sfc_render`);var O=S(E,[[`render`,_sfc_render$1]]),k=(w=class AudioParsingQuestion extends h{constructor(e){var _super=(...e)=>(super(...e),v(this,`audio`,void 0),v(this,`text`,void 0),this);_super(e),this.audio=e[0].audio,this.text=e[0].text}isCorrect(e){throw Error(`isCorrect() not implemented - cannor parse ${e}`)}dataShapes(){return AudioParsingQuestion.dataShapes}views(){return AudioParsingQuestion.views}},v(w,`dataShapes`,[{name:b.FRENCH_AudioParse,fields:[{name:`audio`,type:_.IMAGE},{name:`text`,type:_.STRING,validator:{test:e=>{let t=e.length<45;return{msg:t?``:`It's too long!`,status:t?g.ok:g.error}}}}]}]),v(w,`views`,[O]),w),A=a({name:`IdentifyVocab`,components:{},props:{data:{type:Array,required:!0},modifyDifficulty:{type:Number,required:!1,default:0}},setup(e,{emit:t}){let n=f(e,t,`IdentifyVocab`),r=p(n);r.question.value=new P(e.data);let i=l(()=>r.question.value);return{...n,...r,question:i}}}),j={"data-viewable":`IdentifyVocab`};function _sfc_render(e,n,r,a,o,l){return t(),s(`div`,j,[e.question?(t(),s(c,{key:0},[i(` This is a vocabulary question. `)],64)):d(``,!0)])}var M=S(A,[[`render`,_sfc_render]]),N=[{name:`word`,type:_.STRING,validator:y.NonEmptyString},{name:`image`,type:_.IMAGE},{name:`audio`,type:_.AUDIO}],P=(T=class VocabQuestion extends h{isCorrect(e){throw Error(`isCorrect() not implemented. Cannot parse ${e}`)}dataShapes(){return VocabQuestion.dataShapes}views(){return VocabQuestion.views}},v(T,`dataShapes`,[{fields:N,name:b.FRENCH_Vocab}]),v(T,`views`,[M]),T),F=new x(`french`,[k,P]);export{F as t};
|
|
2
|
+
//# sourceMappingURL=french-Dk7YG8Td-CD7Hms2j.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"french-Dk7YG8Td-
|
|
1
|
+
{"version":3,"file":"french-Dk7YG8Td-CD7Hms2j.js","names":["u","h","v","_","f","i","p","d","o","r","l","s","c","m","a","e","t","g","y","b","x","S","n"],"sources":["../../../courseware/dist/french-Dk7YG8Td.js"],"sourcesContent":["import { r as e } from \"./shapes-BQUVJdp5.js\";\nimport { r as t, t as n } from \"./CourseWare-BTFRjgBR.js\";\nimport { Fragment as r, computed as i, createCommentVNode as a, createElementBlock as o, createElementVNode as s, createTextVNode as c, createVNode as l, defineComponent as u, openBlock as d, ref as f, resolveComponent as p, toDisplayString as m } from \"vue\";\nimport { AudioAutoPlayer as h, Question as g, useQuestionView as _, useViewable as v } from \"@vue-skuilder/common-ui\";\nimport { DataShapeName as y, FieldType as b, Status as x, Validators as S } from \"@vue-skuilder/common\";\n//#region src/french/questions/audioparse/view.vue?vue&type=script&lang.ts\nvar C = u({\n\tname: \"AudioParseView\",\n\tcomponents: { AudioAutoPlayer: h },\n\tprops: {\n\t\tdata: {\n\t\t\ttype: Array,\n\t\t\trequired: !0\n\t\t},\n\t\tmodifyDifficulty: {\n\t\t\ttype: Number,\n\t\t\trequired: !1,\n\t\t\tdefault: 0\n\t\t}\n\t},\n\tsetup(e, { emit: t }) {\n\t\tlet n = v(e, t, \"AudioParseView\"), r = _(n), a = f(\"\");\n\t\tr.question.value = new E(e.data);\n\t\tlet o = i(() => r.question.value), submit = () => {\n\t\t\to.value;\n\t\t};\n\t\treturn {\n\t\t\t...n,\n\t\t\t...r,\n\t\t\tanswer: a,\n\t\t\tquestion: o,\n\t\t\tsubmit\n\t\t};\n\t}\n}), w = { \"data-viewable\": \"AudioParseView\" };\nfunction _sfc_render$1(e, t, n, i, u, f) {\n\tlet h = p(\"AudioAutoPlayer\");\n\treturn d(), o(\"div\", w, [e.question ? (d(), o(r, { key: 0 }, [\n\t\tl(h, { src: e.getURL(\"audio\") }, null, 8, [\"src\"]),\n\t\tt[0] ||= s(\"br\", null, null, -1),\n\t\tt[1] ||= s(\"br\", null, null, -1),\n\t\tc(\" \" + m(e.question.text), 1)\n\t], 64)) : a(\"\", !0)]);\n}\ne(_sfc_render$1, \"_sfc_render\");\nvar T = /* @__PURE__ */ t(C, [[\"render\", _sfc_render$1]]), E = class AudioParsingQuestion extends g {\n\tstatic dataShapes = [{\n\t\tname: y.FRENCH_AudioParse,\n\t\tfields: [{\n\t\t\tname: \"audio\",\n\t\t\ttype: b.IMAGE\n\t\t}, {\n\t\t\tname: \"text\",\n\t\t\ttype: b.STRING,\n\t\t\tvalidator: { test: (e) => {\n\t\t\t\tlet t = e.length < 45;\n\t\t\t\treturn {\n\t\t\t\t\tmsg: t ? \"\" : \"It's too long!\",\n\t\t\t\t\tstatus: t ? x.ok : x.error\n\t\t\t\t};\n\t\t\t} }\n\t\t}]\n\t}];\n\tstatic views = [T];\n\taudio;\n\ttext;\n\tconstructor(e) {\n\t\tsuper(e), this.audio = e[0].audio, this.text = e[0].text;\n\t}\n\tisCorrect(e) {\n\t\tthrow Error(`isCorrect() not implemented - cannor parse ${e}`);\n\t}\n\tdataShapes() {\n\t\treturn AudioParsingQuestion.dataShapes;\n\t}\n\tviews() {\n\t\treturn AudioParsingQuestion.views;\n\t}\n}, D = u({\n\tname: \"IdentifyVocab\",\n\tcomponents: {},\n\tprops: {\n\t\tdata: {\n\t\t\ttype: Array,\n\t\t\trequired: !0\n\t\t},\n\t\tmodifyDifficulty: {\n\t\t\ttype: Number,\n\t\t\trequired: !1,\n\t\t\tdefault: 0\n\t\t}\n\t},\n\tsetup(e, { emit: t }) {\n\t\tlet n = v(e, t, \"IdentifyVocab\"), r = _(n);\n\t\tr.question.value = new j(e.data);\n\t\tlet a = i(() => r.question.value);\n\t\treturn {\n\t\t\t...n,\n\t\t\t...r,\n\t\t\tquestion: a\n\t\t};\n\t}\n}), O = { \"data-viewable\": \"IdentifyVocab\" };\nfunction _sfc_render(e, t, n, i, s, l) {\n\treturn d(), o(\"div\", O, [e.question ? (d(), o(r, { key: 0 }, [c(\" This is a vocabulary question. \")], 64)) : a(\"\", !0)]);\n}\nvar k = /* @__PURE__ */ t(D, [[\"render\", _sfc_render]]), A = [\n\t{\n\t\tname: \"word\",\n\t\ttype: b.STRING,\n\t\tvalidator: S.NonEmptyString\n\t},\n\t{\n\t\tname: \"image\",\n\t\ttype: b.IMAGE\n\t},\n\t{\n\t\tname: \"audio\",\n\t\ttype: b.AUDIO\n\t}\n], j = class VocabQuestion extends g {\n\tstatic dataShapes = [{\n\t\tfields: A,\n\t\tname: y.FRENCH_Vocab\n\t}];\n\tstatic views = [k];\n\tisCorrect(e) {\n\t\tthrow Error(`isCorrect() not implemented. Cannot parse ${e}`);\n\t}\n\tdataShapes() {\n\t\treturn VocabQuestion.dataShapes;\n\t}\n\tviews() {\n\t\treturn VocabQuestion.views;\n\t}\n}, M = new n(\"french\", [E, j]);\n//#endregion\nexport { M as t };\n\n//# sourceMappingURL=french-Dk7YG8Td.js.map"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./vue.runtime.esm-bundler--Fxmd6l9.js";import"./MarkdownRenderer-borh39Lq.js";import"./moment-DbT30ZpG.js";import"./dist-BsRvt0gH.js";import"./contentSource-DwznMSMs.js";import"./dist-Dp6eBE2S.js";import"./CourseWare-BTFRjgBR-DBp_fIwH.js";import"./PipelineDebugger-BHaqzqZw.js";import"./eloDistance-BV7vfiK5.js";import{t as e}from"./french-Dk7YG8Td-CD7Hms2j.js";export{e as default};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{t as e}from"./defineProperty-y9rbOPYz.js";import{s as t}from"./elo-B5qCwvSC.js";import{k as n,l as r}from"./PipelineDebugger-BHaqzqZw.js";var i,a=3,o=(i=class HierarchyDefinitionNavigator extends r{constructor(t,n,r){super(t,n,r),e(this,`config`,void 0),e(this,`name`,void 0),e(this,`kind`,HierarchyDefinitionNavigator.kind),this.config=this.parseConfig(r.serializedData),this.name=r.name||`Hierarchy Definition`}parseConfig(e){try{return{prerequisites:JSON.parse(e).prerequisites||{}}}catch{return{prerequisites:{}}}}isPrerequisiteMet(e,t,n){if(!t)return!1;let r=e.masteryThreshold?.minCount??a;return t.count<r?!1:e.masteryThreshold?.minElo===void 0?e.masteryThreshold?.minCount===void 0?t.score>=n:!0:t.score>=e.masteryThreshold.minElo}async getMasteredTags(e){let n=new Set;try{let r=t((await e.user.getCourseRegDoc(e.course.getCourseID())).elo);for(let e of Object.values(this.config.prerequisites))for(let t of e){let e=r.tags[t.tag];this.isPrerequisiteMet(t,e,r.global.score)&&n.add(t.tag)}}catch{}return n}getUnlockedTags(e){let t=new Set;for(let[n,r]of Object.entries(this.config.prerequisites))r.every(t=>e.has(t.tag))&&t.add(n);return t}hasPrerequisites(e){return e in this.config.prerequisites}async checkCardUnlock(e,t,n,r){try{let t=e.tags??[],i=t.filter(e=>this.hasPrerequisites(e)&&!n.has(e));return i.length===0?{isUnlocked:!0,reason:`Prerequisites met, tags: ${t.length>0?t.join(`, `):`none`}`}:{isUnlocked:!1,reason:`Blocked: missing prerequisites ${i.flatMap(e=>(this.config.prerequisites[e]||[]).filter(e=>!r.has(e.tag)).map(e=>e.tag)).join(`, `)} for tags ${i.join(`, `)}`}}catch{return{isUnlocked:!0,reason:`Prerequisites check skipped (tag lookup failed)`}}}getPreReqBoosts(e,t){let n=new Map;for(let[r,i]of Object.entries(this.config.prerequisites))if(!e.has(r))for(let e of i){if(!e.preReqBoost||e.preReqBoost<=1||t.has(e.tag))continue;let r=n.get(e.tag)??1;n.set(e.tag,Math.max(r,e.preReqBoost))}return n}getTargetBoosts(e){let t=new Map,r=Object.keys(this.config.prerequisites),i=[...e];n.info(`[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${r.length}, unlocked=${i.length} (${i.slice(0,5).join(`, `)}${i.length>5?`...`:``})`);for(let[r,i]of Object.entries(this.config.prerequisites))if(e.has(r)){n.info(`[HierarchyDefinition:targetBoost:trace] UNLOCKED ${r}: ${i.length} prereqs, raw=${JSON.stringify(i.map(e=>({tag:e.tag,tb:e.targetBoost})))}`);for(let e of i){if(!e.targetBoost||e.targetBoost<=1)continue;let n=t.get(r)??1;t.set(r,Math.max(n,e.targetBoost))}}return t.size>0?n.info(`[HierarchyDefinition] targetBoosts active: ${[...t.entries()].map(([e,t])=>`${e}=×${t}`).join(`, `)}`):n.info(`[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${i.length} unlocked tags`),t}async transform(e,t){let r=await this.getMasteredTags(t),i=this.getUnlockedTags(r),a=this.getPreReqBoosts(i,r),o=this.getTargetBoosts(i),s=[];for(let c of e){let{isUnlocked:e,reason:l}=await this.checkCardUnlock(c,t.course,i,r),u=e?c.score:c.score*.02,d=e?`passed`:`penalized`,f=l;if(e&&a.size>0){let e=c.tags??[],t=1,r=[];for(let n of e){let e=a.get(n);e&&e>t&&(t=e,r.push(n))}t>1&&(u*=t,d=`boosted`,f=`${l} | preReqBoost ×${t.toFixed(2)} for ${r.join(`, `)}`,n.info(`[HierarchyDefinition] preReqBoost ×${t.toFixed(2)} applied to card ${c.cardId} via tags [${r.join(`, `)}] (score: ${c.score.toFixed(3)} → ${u.toFixed(3)})`))}if(e&&o.size>0){let e=c.tags??[],t=1,r=[];for(let n of e){let e=o.get(n);e&&e>t&&(t=e,r.push(n))}t>1&&(u*=t,d=`boosted`,f=`${f} | targetBoost ×${t.toFixed(2)} for ${r.join(`, `)}`,n.info(`[HierarchyDefinition] targetBoost ×${t.toFixed(2)} applied to card ${c.cardId} via tags [${r.join(`, `)}] (score: ${c.score.toFixed(3)} → ${u.toFixed(3)})`))}s.push({...c,score:u,provenance:[...c.provenance,{strategy:`hierarchyDefinition`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-hierarchy`,action:d,score:u,reason:f}]})}return s}async getWeightedCards(e){throw Error(`HierarchyDefinitionNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}},e(i,`kind`,`gate`),i);export{o as default};
|
|
2
|
+
//# sourceMappingURL=hierarchyDefinition-DLC5DjaA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchyDefinition-DLC5DjaA.js","names":[],"sources":["../../../db/dist/core/navigators/filters/hierarchyDefinition.mjs"],"sourcesContent":["import \"../../../_virtual/_rolldown/runtime.mjs\";\nimport { logger } from \"../../../util/logger.mjs\";\nimport { ContentNavigator } from \"../index.mjs\";\nimport { toCourseElo } from \"@vue-skuilder/common\";\n//#region src/core/navigators/filters/hierarchyDefinition.ts\nconst DEFAULT_MIN_COUNT = 3;\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*/\nvar HierarchyDefinitionNavigator = class HierarchyDefinitionNavigator extends ContentNavigator {\n\tconfig;\n\t/** Human-readable name for CardFilter interface */\n\tname;\n\t/** See CardFilter.kind. Static, so the pipeline plan can read it without an instance. */\n\tstatic kind = \"gate\";\n\tkind = HierarchyDefinitionNavigator.kind;\n\tconstructor(user, course, strategyData) {\n\t\tsuper(user, course, strategyData);\n\t\tthis.config = this.parseConfig(strategyData.serializedData);\n\t\tthis.name = strategyData.name || \"Hierarchy Definition\";\n\t}\n\tparseConfig(serializedData) {\n\t\ttry {\n\t\t\treturn { prerequisites: JSON.parse(serializedData).prerequisites || {} };\n\t\t} catch {\n\t\t\treturn { prerequisites: {} };\n\t\t}\n\t}\n\t/**\n\t* Check if a specific prerequisite is satisfied\n\t*/\n\tisPrerequisiteMet(prereq, userTagElo, userGlobalElo) {\n\t\tif (!userTagElo) return false;\n\t\tconst minCount = prereq.masteryThreshold?.minCount ?? DEFAULT_MIN_COUNT;\n\t\tif (userTagElo.count < minCount) return false;\n\t\tif (prereq.masteryThreshold?.minElo !== void 0) return userTagElo.score >= prereq.masteryThreshold.minElo;\n\t\telse if (prereq.masteryThreshold?.minCount !== void 0) return true;\n\t\telse return userTagElo.score >= userGlobalElo;\n\t}\n\t/**\n\t* Get the set of tags the user has mastered.\n\t* A tag is \"mastered\" if it appears as a prerequisite somewhere and meets its threshold.\n\t*/\n\tasync getMasteredTags(context) {\n\t\tconst mastered = /* @__PURE__ */ new Set();\n\t\ttry {\n\t\t\tconst userElo = toCourseElo((await context.user.getCourseRegDoc(context.course.getCourseID())).elo);\n\t\t\tfor (const prereqs of Object.values(this.config.prerequisites)) for (const prereq of prereqs) {\n\t\t\t\tconst tagElo = userElo.tags[prereq.tag];\n\t\t\t\tif (this.isPrerequisiteMet(prereq, tagElo, userElo.global.score)) mastered.add(prereq.tag);\n\t\t\t}\n\t\t} catch {}\n\t\treturn mastered;\n\t}\n\t/**\n\t* Get the set of tags that are unlocked (prerequisites met)\n\t*/\n\tgetUnlockedTags(masteredTags) {\n\t\tconst unlocked = /* @__PURE__ */ new Set();\n\t\tfor (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) if (prereqs.every((prereq) => masteredTags.has(prereq.tag))) unlocked.add(tagId);\n\t\treturn unlocked;\n\t}\n\t/**\n\t* Check if a tag has prerequisites defined in config\n\t*/\n\thasPrerequisites(tagId) {\n\t\treturn tagId in this.config.prerequisites;\n\t}\n\t/**\n\t* Check if a card is unlocked and generate reason.\n\t*/\n\tasync checkCardUnlock(card, _course, unlockedTags, masteredTags) {\n\t\ttry {\n\t\t\tconst cardTags = card.tags ?? [];\n\t\t\tconst lockedTags = cardTags.filter((tag) => this.hasPrerequisites(tag) && !unlockedTags.has(tag));\n\t\t\tif (lockedTags.length === 0) return {\n\t\t\t\tisUnlocked: true,\n\t\t\t\treason: `Prerequisites met, tags: ${cardTags.length > 0 ? cardTags.join(\", \") : \"none\"}`\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tisUnlocked: false,\n\t\t\t\treason: `Blocked: missing prerequisites ${lockedTags.flatMap((tag) => {\n\t\t\t\t\treturn (this.config.prerequisites[tag] || []).filter((p) => !masteredTags.has(p.tag)).map((p) => p.tag);\n\t\t\t\t}).join(\", \")} for tags ${lockedTags.join(\", \")}`\n\t\t\t};\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tisUnlocked: true,\n\t\t\t\treason: \"Prerequisites check skipped (tag lookup failed)\"\n\t\t\t};\n\t\t}\n\t}\n\t/**\n\t* Build a map of prereq tag → max configured boost for all *closed* gates.\n\t*\n\t* When a gate is closed (prereqs unmet), cards carrying that gate's prereq\n\t* tags get boosted — steering the pipeline toward content that helps unlock\n\t* the gated material. Once the gate opens, the boost disappears.\n\t*/\n\tgetPreReqBoosts(unlockedTags, masteredTags) {\n\t\tconst boosts = /* @__PURE__ */ new Map();\n\t\tfor (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {\n\t\t\tif (unlockedTags.has(tagId)) continue;\n\t\t\tfor (const prereq of prereqs) {\n\t\t\t\tif (!prereq.preReqBoost || prereq.preReqBoost <= 1) continue;\n\t\t\t\tif (masteredTags.has(prereq.tag)) continue;\n\t\t\t\tconst existing = boosts.get(prereq.tag) ?? 1;\n\t\t\t\tboosts.set(prereq.tag, Math.max(existing, prereq.preReqBoost));\n\t\t\t}\n\t\t}\n\t\treturn boosts;\n\t}\n\t/**\n\t* Build a map of gated tag → max configured targetBoost for all *open* gates.\n\t*\n\t* When a gate opens (prereqs met), cards carrying the gated tag get boosted —\n\t* ensuring newly-unlocked content surfaces promptly. The boost is a static\n\t* multiplier; natural ELO/SRS deprioritization after interaction handles decay.\n\t*/\n\tgetTargetBoosts(unlockedTags) {\n\t\tconst boosts = /* @__PURE__ */ new Map();\n\t\tconst configKeys = Object.keys(this.config.prerequisites);\n\t\tconst unlockedArr = [...unlockedTags];\n\t\tlogger.info(`[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${configKeys.length}, unlocked=${unlockedArr.length} (${unlockedArr.slice(0, 5).join(\", \")}${unlockedArr.length > 5 ? \"...\" : \"\"})`);\n\t\tfor (const [tagId, prereqs] of Object.entries(this.config.prerequisites)) {\n\t\t\tif (!unlockedTags.has(tagId)) continue;\n\t\t\tlogger.info(`[HierarchyDefinition:targetBoost:trace] UNLOCKED ${tagId}: ${prereqs.length} prereqs, raw=${JSON.stringify(prereqs.map((p) => ({\n\t\t\t\ttag: p.tag,\n\t\t\t\ttb: p.targetBoost\n\t\t\t})))}`);\n\t\t\tfor (const prereq of prereqs) {\n\t\t\t\tif (!prereq.targetBoost || prereq.targetBoost <= 1) continue;\n\t\t\t\tconst existing = boosts.get(tagId) ?? 1;\n\t\t\t\tboosts.set(tagId, Math.max(existing, prereq.targetBoost));\n\t\t\t}\n\t\t}\n\t\tif (boosts.size > 0) logger.info(`[HierarchyDefinition] targetBoosts active: ${[...boosts.entries()].map(([t, b]) => `${t}=×${b}`).join(\", \")}`);\n\t\telse logger.info(`[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${unlockedArr.length} unlocked tags`);\n\t\treturn boosts;\n\t}\n\t/**\n\t* CardFilter.transform implementation.\n\t*\n\t* Three effects:\n\t* 1. Cards with locked tags receive score * 0.02 (gating penalty)\n\t* 2. Cards carrying prereq tags of closed gates receive preReqBoost\n\t* 3. Cards carrying gated tags of open gates receive targetBoost\n\t*/\n\tasync transform(cards, context) {\n\t\tconst masteredTags = await this.getMasteredTags(context);\n\t\tconst unlockedTags = this.getUnlockedTags(masteredTags);\n\t\tconst preReqBoosts = this.getPreReqBoosts(unlockedTags, masteredTags);\n\t\tconst targetBoosts = this.getTargetBoosts(unlockedTags);\n\t\tconst gated = [];\n\t\tfor (const card of cards) {\n\t\t\tconst { isUnlocked, reason } = await this.checkCardUnlock(card, context.course, unlockedTags, masteredTags);\n\t\t\tlet finalScore = isUnlocked ? card.score : card.score * .02;\n\t\t\tlet action = isUnlocked ? \"passed\" : \"penalized\";\n\t\t\tlet finalReason = reason;\n\t\t\tif (isUnlocked && preReqBoosts.size > 0) {\n\t\t\t\tconst cardTags = card.tags ?? [];\n\t\t\t\tlet maxBoost = 1;\n\t\t\t\tconst boostedPrereqs = [];\n\t\t\t\tfor (const tag of cardTags) {\n\t\t\t\t\tconst boost = preReqBoosts.get(tag);\n\t\t\t\t\tif (boost && boost > maxBoost) {\n\t\t\t\t\t\tmaxBoost = boost;\n\t\t\t\t\t\tboostedPrereqs.push(tag);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (maxBoost > 1) {\n\t\t\t\t\tfinalScore *= maxBoost;\n\t\t\t\t\taction = \"boosted\";\n\t\t\t\t\tfinalReason = `${reason} | preReqBoost ×${maxBoost.toFixed(2)} for ${boostedPrereqs.join(\", \")}`;\n\t\t\t\t\tlogger.info(`[HierarchyDefinition] preReqBoost ×${maxBoost.toFixed(2)} applied to card ${card.cardId} via tags [${boostedPrereqs.join(\", \")}] (score: ${card.score.toFixed(3)} → ${finalScore.toFixed(3)})`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isUnlocked && targetBoosts.size > 0) {\n\t\t\t\tconst cardTags = card.tags ?? [];\n\t\t\t\tlet maxTargetBoost = 1;\n\t\t\t\tconst boostedTargets = [];\n\t\t\t\tfor (const tag of cardTags) {\n\t\t\t\t\tconst boost = targetBoosts.get(tag);\n\t\t\t\t\tif (boost && boost > maxTargetBoost) {\n\t\t\t\t\t\tmaxTargetBoost = boost;\n\t\t\t\t\t\tboostedTargets.push(tag);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (maxTargetBoost > 1) {\n\t\t\t\t\tfinalScore *= maxTargetBoost;\n\t\t\t\t\taction = \"boosted\";\n\t\t\t\t\tfinalReason = `${finalReason} | targetBoost ×${maxTargetBoost.toFixed(2)} for ${boostedTargets.join(\", \")}`;\n\t\t\t\t\tlogger.info(`[HierarchyDefinition] targetBoost ×${maxTargetBoost.toFixed(2)} applied to card ${card.cardId} via tags [${boostedTargets.join(\", \")}] (score: ${card.score.toFixed(3)} → ${finalScore.toFixed(3)})`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tgated.push({\n\t\t\t\t...card,\n\t\t\t\tscore: finalScore,\n\t\t\t\tprovenance: [...card.provenance, {\n\t\t\t\t\tstrategy: \"hierarchyDefinition\",\n\t\t\t\t\tstrategyName: this.strategyName || this.name,\n\t\t\t\t\tstrategyId: this.strategyId || \"NAVIGATION_STRATEGY-hierarchy\",\n\t\t\t\t\taction,\n\t\t\t\t\tscore: finalScore,\n\t\t\t\t\treason: finalReason\n\t\t\t\t}]\n\t\t\t});\n\t\t}\n\t\treturn gated;\n\t}\n\t/**\n\t* Legacy getWeightedCards - now throws as filters should not be used as generators.\n\t*\n\t* Use transform() via Pipeline instead.\n\t*/\n\tasync getWeightedCards(_limit) {\n\t\tthrow new Error(\"HierarchyDefinitionNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().\");\n\t}\n};\n//#endregion\nexport { HierarchyDefinitionNavigator as default };\n\n//# sourceMappingURL=hierarchyDefinition.mjs.map"],"mappings":"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"}
|