@vue-skuilder/platform-ui 0.2.8 → 0.2.9
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-zjTECQNT.js → About-CeoCtzXk.js} +2 -2
- package/dist/assets/{About-zjTECQNT.js.map → About-CeoCtzXk.js.map} +1 -1
- package/dist/assets/{AdminDashboard-_zq4nONY.js → AdminDashboard-j4cKNpdn.js} +2 -2
- package/dist/assets/{AdminDashboard-_zq4nONY.js.map → AdminDashboard-j4cKNpdn.js.map} +1 -1
- package/dist/assets/{ClassroomCtrlPanel-DCbpbIEB.js → ClassroomCtrlPanel-DzUzmKEd.js} +2 -2
- package/dist/assets/{ClassroomCtrlPanel-DCbpbIEB.js.map → ClassroomCtrlPanel-DzUzmKEd.js.map} +1 -1
- package/dist/assets/{Classrooms-BUI7d6V7.js → Classrooms-CYyQEJGM.js} +2 -2
- package/dist/assets/{Classrooms-BUI7d6V7.js.map → Classrooms-CYyQEJGM.js.map} +1 -1
- package/dist/assets/{CourseRouter-D3ww8C0T.js → CourseRouter-YJKWWArA.js} +3 -3
- package/dist/assets/{CourseRouter-D3ww8C0T.js.map → CourseRouter-YJKWWArA.js.map} +1 -1
- package/dist/assets/{CourseWare-BTFRjgBR-BKjh4u_F.js → CourseWare-BTFRjgBR-B3XXRdBw.js} +3 -3
- package/dist/assets/{CourseWare-BTFRjgBR-BKjh4u_F.js.map → CourseWare-BTFRjgBR-B3XXRdBw.js.map} +1 -1
- package/dist/assets/{Courses-BUtV1A6B.js → Courses-CPYbYJrk.js} +2 -2
- package/dist/assets/{Courses-BUtV1A6B.js.map → Courses-CPYbYJrk.js.map} +1 -1
- package/dist/assets/{EloModeration-6WAH9JnJ.js → EloModeration-CsEovP87.js} +2 -2
- package/dist/assets/{EloModeration-6WAH9JnJ.js.map → EloModeration-CsEovP87.js.map} +1 -1
- package/dist/assets/{JoinCode-jR454PDt.js → JoinCode-Cz4jmDr8.js} +2 -2
- package/dist/assets/{JoinCode-jR454PDt.js.map → JoinCode-Cz4jmDr8.js.map} +1 -1
- package/dist/assets/{NewCourseDialog-CItEIHPl.js → NewCourseDialog-yOrEOAPR.js} +2 -2
- package/dist/assets/{NewCourseDialog-CItEIHPl.js.map → NewCourseDialog-yOrEOAPR.js.map} +1 -1
- package/dist/assets/{ReleaseNotes-P12l56VV.js → ReleaseNotes-CAYTtxbB.js} +2 -2
- package/dist/assets/{ReleaseNotes-P12l56VV.js.map → ReleaseNotes-CAYTtxbB.js.map} +1 -1
- package/dist/assets/{RequestPasswordReset-DIIQQm5q.js → RequestPasswordReset-BmvA5Eh7.js} +2 -2
- package/dist/assets/{RequestPasswordReset-DIIQQm5q.js.map → RequestPasswordReset-BmvA5Eh7.js.map} +1 -1
- package/dist/assets/ResetPassword-DYfgD1oj.js +2 -0
- package/dist/assets/{ResetPassword-q5BbSDfD.js.map → ResetPassword-DYfgD1oj.js.map} +1 -1
- package/dist/assets/{Study-B1IGZg7q.js → Study-D1qZJTcD.js} +2 -2
- package/dist/assets/{Study-B1IGZg7q.js.map → Study-D1qZJTcD.js.map} +1 -1
- package/dist/assets/TagInformation-ByWw-H0T.js +2 -0
- package/dist/assets/{TagInformation-Brkhk5Uc.js.map → TagInformation-ByWw-H0T.js.map} +1 -1
- package/dist/assets/{User-xO6M7dY_.js → User-CwvzLT5o.js} +3 -3
- package/dist/assets/{User-xO6M7dY_.js.map → User-CwvzLT5o.js.map} +1 -1
- package/dist/assets/{UserStats-B1LVmIde.js → UserStats-Cw1LI01p.js} +2 -2
- package/dist/assets/{UserStats-B1LVmIde.js.map → UserStats-Cw1LI01p.js.map} +1 -1
- package/dist/assets/{VerifyEmail-AhuAGEz9.js → VerifyEmail-D0zGflMG.js} +2 -2
- package/dist/assets/{VerifyEmail-AhuAGEz9.js.map → VerifyEmail-D0zGflMG.js.map} +1 -1
- package/dist/assets/{chess-E2uOAyQS-BOg8AjuS.js → chess-E2uOAyQS-Ph5ebfF8.js} +2 -2
- package/dist/assets/{chess-E2uOAyQS-BOg8AjuS.js.map → chess-E2uOAyQS-Ph5ebfF8.js.map} +1 -1
- package/dist/assets/chess-Lfm7Fsvs-C83h2_lg.js +1 -0
- package/dist/assets/{common-ui.es-BcsR0aYn.js → common-ui.es-CBhYagmE.js} +10 -10
- package/dist/assets/common-ui.es-CBhYagmE.js.map +1 -0
- package/dist/assets/common-ui.es-CI4N3XlC.js +1 -0
- package/dist/assets/dist-B3lXq4kN.js +18 -0
- package/dist/assets/{dist-DlWUPaK5.js.map → dist-B3lXq4kN.js.map} +1 -1
- package/dist/assets/dist-BWDugYtL.js +1 -0
- package/dist/assets/dist-B_IxjB5y.js +1 -0
- package/dist/assets/{dist-DHNWv7su.js → dist-C6pY-D6H.js} +13 -16
- package/dist/assets/dist-C6pY-D6H.js.map +1 -0
- package/dist/assets/edit-ui.es-CycNbyRr.js +36 -0
- package/dist/assets/{edit-ui.es-Ddfco4Pk.js.map → edit-ui.es-CycNbyRr.js.map} +1 -1
- package/dist/assets/edit-ui.es-DV8ZYWU5.js +1 -0
- package/dist/assets/{french-Dk7YG8Td-CYgzVCIJ.js → french-Dk7YG8Td-BY6XQG1-.js} +2 -2
- package/dist/assets/{french-Dk7YG8Td-CYgzVCIJ.js.map → french-Dk7YG8Td-BY6XQG1-.js.map} +1 -1
- package/dist/assets/french-evUMlbbq-Wy2-CZYM.js +1 -0
- package/dist/assets/{index-CACQ1HiQ.css → index-Cz6xT9Hi.css} +1 -1
- package/dist/assets/{index-DqcURlBg.js → index-DyRkL_mn.js} +4 -4
- package/dist/assets/{index-DqcURlBg.js.map → index-DyRkL_mn.js.map} +1 -1
- package/dist/assets/{math-B4HbgYf6-vUjsDv91.js → math-B4HbgYf6-DM25Q288.js} +6 -6
- package/dist/assets/{math-B4HbgYf6-vUjsDv91.js.map → math-B4HbgYf6-DM25Q288.js.map} +1 -1
- package/dist/assets/math-DYni7rRl-DLwlThCT.js +1 -0
- package/dist/assets/piano-BN5Btq91-pa4xWzbM.js +1 -0
- package/dist/assets/piano-DF1g6yaX-CMH9wv3m.js +2 -0
- package/dist/assets/{piano-DF1g6yaX-vK-4k-Zr.js.map → piano-DF1g6yaX-CMH9wv3m.js.map} +1 -1
- package/dist/assets/{pitch-CgGJFkZ1-Cjyh79lG.js → pitch-CgGJFkZ1-3DyqAAGX.js} +2 -2
- package/dist/assets/{pitch-CgGJFkZ1-Cjyh79lG.js.map → pitch-CgGJFkZ1-3DyqAAGX.js.map} +1 -1
- package/dist/assets/pitch-Dn0iNqiS-DBpVWj0e.js +1 -0
- package/dist/assets/{server-DSSG5Zqs.js → server-B0yHgONt.js} +2 -2
- package/dist/assets/{server-DSSG5Zqs.js.map → server-B0yHgONt.js.map} +1 -1
- package/dist/assets/{sightsing-BFQ7HRig-DSg6WCvP.js → sightsing-BFQ7HRig-fw0KP1_O.js} +3 -3
- package/dist/assets/{sightsing-BFQ7HRig-DSg6WCvP.js.map → sightsing-BFQ7HRig-fw0KP1_O.js.map} +1 -1
- package/dist/assets/sightsing-CJX3k0Fd-BGvMhVmJ.js +1 -0
- package/dist/assets/{typing-BevtfWlp-BZPlnLfL.js → typing-BevtfWlp-BecoTp3F.js} +2 -2
- package/dist/assets/{typing-BevtfWlp-BZPlnLfL.js.map → typing-BevtfWlp-BecoTp3F.js.map} +1 -1
- package/dist/assets/typing-k-ojjO7G-BCNay9zo.js +1 -0
- package/dist/assets/{word-work-BOnRlZgd-0cck95wD.js → word-work-BOnRlZgd-BImj7zR2.js} +2 -2
- package/dist/assets/{word-work-BOnRlZgd-0cck95wD.js.map → word-work-BOnRlZgd-BImj7zR2.js.map} +1 -1
- package/dist/assets/word-work-Dd6tIKrt-BR9BApEq.js +1 -0
- package/dist/index.html +4 -4
- package/dist/sw.js +1 -1
- package/dist/sw.js.map +1 -1
- package/package.json +7 -7
- package/dist/assets/ResetPassword-q5BbSDfD.js +0 -2
- package/dist/assets/TagInformation-Brkhk5Uc.js +0 -2
- package/dist/assets/chess-Lfm7Fsvs-gHEeTsK2.js +0 -1
- package/dist/assets/common-ui.es-BcsR0aYn.js.map +0 -1
- package/dist/assets/common-ui.es-DeiLeCAz.js +0 -1
- package/dist/assets/dist-DHNWv7su.js.map +0 -1
- package/dist/assets/dist-DTgyj0s-.js +0 -1
- package/dist/assets/dist-DlWUPaK5.js +0 -18
- package/dist/assets/dist-aM_puP0T.js +0 -1
- package/dist/assets/edit-ui.es-B6B148Iu.js +0 -1
- package/dist/assets/edit-ui.es-Ddfco4Pk.js +0 -36
- package/dist/assets/french-evUMlbbq-Ck2dkMEb.js +0 -1
- package/dist/assets/math-DYni7rRl-ClYSEmZE.js +0 -1
- package/dist/assets/piano-BN5Btq91-B--Hu9NY.js +0 -1
- package/dist/assets/piano-DF1g6yaX-vK-4k-Zr.js +0 -2
- package/dist/assets/pitch-Dn0iNqiS-CyQaBUyI.js +0 -1
- package/dist/assets/sightsing-CJX3k0Fd-CzhjJG5U.js +0 -1
- package/dist/assets/typing-k-ojjO7G-CrlVRJ6T.js +0 -1
- package/dist/assets/word-work-Dd6tIKrt-JBcDjC3D.js +0 -1
|
@@ -246,13 +246,13 @@ Example:
|
|
|
246
246
|
window.skuilder.pipeline.showLastRun()
|
|
247
247
|
window.skuilder.pipeline.showRun(1)
|
|
248
248
|
await window.skuilder.pipeline.diagnoseCardSpace()
|
|
249
|
-
`)}},mountPipelineDebugger()}}),CompositeGenerator_exports={};__export(CompositeGenerator_exports,{AggregationMode:()=>AggregationMode,default:()=>CompositeGenerator});function mergeHints(e){let t=e.filter(e=>e!==void 0);if(t.length===0)return;let n={},r={};for(let e of t)for(let[t,n]of Object.entries(e.boostTags??{}))r[t]=(r[t]??1)*n;Object.keys(r).length>0&&(n.boostTags=r);let a={};for(let e of t)for(let[t,n]of Object.entries(e.boostCards??{}))a[t]=(a[t]??1)*n;Object.keys(a).length>0&&(n.boostCards=a);let concatUnique=e=>{let r=t.flatMap(t=>t[e]??[]);r.length>0&&(n[e]=[...new Set(r)])};concatUnique(`requireTags`),concatUnique(`requireCards`),concatUnique(`excludeTags`),concatUnique(`excludeCards`);let o=t.map(e=>e._label).filter(Boolean);return o.length>0&&(n._label=o.join(`; `)),Object.keys(n).length>0?n:void 0}var AggregationMode,DEFAULT_AGGREGATION_MODE,FREQUENCY_BOOST_FACTOR,CompositeGenerator,init_CompositeGenerator=__esm({"src/core/navigators/generators/CompositeGenerator.ts"(){init_navigators(),init_logger(),AggregationMode=(e=>(e.MAX=`max`,e.AVERAGE=`average`,e.FREQUENCY_BOOST=`frequencyBoost`,e))(AggregationMode||{}),DEFAULT_AGGREGATION_MODE=`frequencyBoost`,FREQUENCY_BOOST_FACTOR=.1,CompositeGenerator=class _CompositeGenerator extends ContentNavigator{constructor(e,t=DEFAULT_AGGREGATION_MODE){if(super(),_defineProperty(this,`name`,`Composite Generator`),_defineProperty(this,`generators`,void 0),_defineProperty(this,`aggregationMode`,void 0),this.generators=e,this.aggregationMode=t,e.length===0)throw Error(`CompositeGenerator requires at least one generator`);logger.debug(`[CompositeGenerator] Created with ${e.length} generators, mode: ${t}`)}static async fromStrategies(e,t,n,r=DEFAULT_AGGREGATION_MODE){return new _CompositeGenerator(await Promise.all(n.map(n=>ContentNavigator.create(e,t,n))),r)}async getWeightedCards(e,t){if(!t)throw Error(`CompositeGenerator.getWeightedCards requires a GeneratorContext. It should be called via Pipeline, not directly.`);let n=await Promise.all(this.generators.map(n=>n.getWeightedCards(e,t))),r=[];n.forEach((e,t)=>{let n=e.cards,a=this.generators[t].name||`Generator ${t}`,o=n.filter(e=>e.provenance[0]?.reason?.includes(`new card`)),s=n.filter(e=>e.provenance[0]?.reason?.includes(`review`));if(n.length>0){let e=Math.max(...n.map(e=>e.score)).toFixed(2),t=[];o.length>0&&t.push(`${o.length} new`),s.length>0&&t.push(`${s.length} reviews`);let c=t.length>0?t.join(`, `):`${n.length} cards`;r.push(`${a}: ${c} (top: ${e})`)}else r.push(`${a}: 0 cards`)}),logger.info(`[Composite] Generator breakdown: ${r.join(` | `)}`);let a=new Map;n.forEach((e,n)=>{let r=e.cards,o=this.generators[n],s=o.learnable?.weight??1,c;if(o.learnable&&!o.staticWeight&&t.orchestration){let e=o.strategyId;e&&(s=t.orchestration.getEffectiveWeight(e,o.learnable),c=t.orchestration.getDeviation(e))}for(let e of r){e.provenance.length>0&&(e.provenance[0].effectiveWeight=s,e.provenance[0].deviation=c);let t=a.get(e.cardId)||[];t.push({card:e,weight:s}),a.set(e.cardId,t)}});let o=[];for(let[,e]of a){let t=e.map(e=>e.card),n=this.aggregateScores(e),r=Math.max(0,n),a=t.flatMap(e=>e.provenance),s=t[0].score,c=r>s?`boosted`:r<s?`penalized`:`passed`,l=this.buildAggregationReason(e,r);o.push({...t[0],score:r,provenance:[...a,{strategy:`composite`,strategyName:`Composite Generator`,strategyId:`COMPOSITE_GENERATOR`,action:c,score:r,reason:l}]})}return{cards:o.sort((e,t)=>t.score-e.score).slice(0,e),hints:mergeHints(n.map(e=>e.hints))}}buildAggregationReason(e,t){let n=e.map(e=>e.card),r=n.length,a=n.map(e=>e.score.toFixed(2)).join(`, `);if(r===1){let n=Math.abs(e[0].weight-1)>.001?` (w=${e[0].weight.toFixed(2)})`:``;return`Single generator, score ${t.toFixed(2)}${n}`}let o=n.map(e=>e.provenance[0]?.strategy||`unknown`).join(`, `);switch(this.aggregationMode){case`max`:return`Max of ${r} generators (${o}): scores [${a}] \u2192 ${t.toFixed(2)}`;case`average`:return`Weighted Avg of ${r} generators (${o}): scores [${a}] \u2192 ${t.toFixed(2)}`;case`frequencyBoost`:{let n=e.reduce((e,t)=>e+t.weight,0),a=e.reduce((e,t)=>e+t.card.score*t.weight,0),s=n>0?a/n:0,c=1+FREQUENCY_BOOST_FACTOR*(r-1);return`Frequency boost from ${r} generators (${o}): w-avg ${s.toFixed(2)} \xD7 ${c.toFixed(2)} \u2192 ${t.toFixed(2)}`}default:return`Aggregated from ${r} generators: ${t.toFixed(2)}`}}aggregateScores(e){let t=e.map(e=>e.card.score);switch(this.aggregationMode){case`max`:return Math.max(...t);case`average`:{let t=e.reduce((e,t)=>e+t.weight,0);return t===0?0:e.reduce((e,t)=>e+t.card.score*t.weight,0)/t}case`frequencyBoost`:{let t=e.reduce((e,t)=>e+t.weight,0),n=e.reduce((e,t)=>e+t.card.score*t.weight,0);return(t>0?n/t:0)*(1+FREQUENCY_BOOST_FACTOR*(e.length-1))}default:return t[0]}}}}}),elo_exports={};__export(elo_exports,{default:()=>ELONavigator});var ELO_RELEVANCE_SIGMA,ELONavigator,init_elo=__esm({"src/core/navigators/generators/elo.ts"(){init_navigators(),init_logger(),ELO_RELEVANCE_SIGMA=300,ELONavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),this.name=n?.name||`ELO`}async getWeightedCards(e,t){let n;n=t?.userElo===void 0?toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score:t.userElo;let r=await this.user.getActiveCards(),a=(await this.course.getCardsCenteredAtELO({limit:e,elo:`user`},e=>!r.some(t=>e.cardID===t.cardID))).map(e=>({...e,status:`new`})).map(e=>{let t=e.elo??1e3,r=Math.abs(t-n),a=Math.exp(-((r/ELO_RELEVANCE_SIGMA)**2)),o=a*(.5+.5*Math.random());return{cardId:e.cardID,courseId:e.courseID,score:o,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:o,reason:`ELO distance ${Math.round(r)} (card: ${Math.round(t)}, user: ${Math.round(n)}), relevance ${a.toFixed(3)}, key ${o.toFixed(3)}`}]}});a.sort((e,t)=>t.score-e.score);let o=a.slice(0,e);if(o.length>0){let e=o.slice(0,3).map(e=>e.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${o.length} new cards (top scores: ${e})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:o}}}}}),generators_exports={},init_generators=__esm({"src/core/navigators/generators/index.ts"(){}}),prescribed_exports={};__export(prescribed_exports,{default:()=>PrescribedCardsGenerator});function dedupe(e){return[...new Set(e)]}function isoNow(){return new Date().toISOString()}function clamp(e,t,n){return Math.max(t,Math.min(n,e))}function matchesTagPattern(e,t){if(t===`*`)return!0;let n=t.replace(/[.+^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`);return RegExp(`^${n}$`).test(e)}function extractWordStem(e){for(let t of[`c-ml-`,`c-ws-`,`c-spelling-`])if(e.startsWith(t)){let n=e.slice(t.length),r=n.lastIndexOf(`-`);return r>0?n.slice(0,r):n}return e}function shuffleInPlace(e){for(let t=e.length-1;t>0;t--){let n=Math.floor(Math.random()*(t+1));[e[t],e[n]]=[e[n],e[t]]}}function pickTopByScore(e,t){return[...e].sort((e,t)=>t.score-e.score||e.cardId.localeCompare(t.cardId)).slice(0,t)}var DEFAULT_FRESHNESS_WINDOW,DEFAULT_MAX_DIRECT_PER_RUN,DEFAULT_MAX_SUPPORT_PER_RUN,DEFAULT_HIERARCHY_DEPTH,DEFAULT_MIN_COUNT,DEFAULT_PRACTICE_MIN_COUNT,DEFAULT_MAX_PRACTICE_PER_RUN,BASE_TARGET_SCORE,BASE_SUPPORT_SCORE,DISCOVERED_SUPPORT_SCORE,BASE_PRACTICE_SCORE,MAX_TARGET_MULTIPLIER,MAX_SUPPORT_MULTIPLIER,PRESCRIBED_DEBUG_VERSION,PrescribedCardsGenerator,init_prescribed=__esm({"src/core/navigators/generators/prescribed.ts"(){init_navigators(),init_logger(),DEFAULT_FRESHNESS_WINDOW=3,DEFAULT_MAX_DIRECT_PER_RUN=3,DEFAULT_MAX_SUPPORT_PER_RUN=3,DEFAULT_HIERARCHY_DEPTH=2,DEFAULT_MIN_COUNT=3,DEFAULT_PRACTICE_MIN_COUNT=3,DEFAULT_MAX_PRACTICE_PER_RUN=4,BASE_TARGET_SCORE=1,BASE_SUPPORT_SCORE=.8,DISCOVERED_SUPPORT_SCORE=12,BASE_PRACTICE_SCORE=1,MAX_TARGET_MULTIPLIER=8,MAX_SUPPORT_MULTIPLIER=4,PRESCRIBED_DEBUG_VERSION=`testversion-prescribed-v3`,PrescribedCardsGenerator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),_defineProperty(this,`config`,void 0),this.name=n.name||`Prescribed Cards`,this.config=this.parseConfig(n.serializedData),logger.debug(`[Prescribed] Initialized with ${this.config.groups.length} groups and ${this.config.groups.reduce((e,t)=>e+t.targetCardIds.length,0)} targets`)}get strategyKey(){return`PrescribedProgress`}async getWeightedCards(e,t){if(this.config.groups.length===0||e<=0)return{cards:[]};let n=this.course.getCourseID(),r=await this.user.getActiveCards(),a=new Set(r.map(e=>e.cardID)),o=await this.user.getSeenCards(n).catch(()=>[]),s=new Set(o),c=await this.getStrategyState()??{updatedAt:isoNow(),groups:{}},l=await this.loadHierarchyConfigs(),u=await this.user.getCourseRegDoc(n).catch(()=>null),d=typeof u?.elo==`number`?u.elo:u?.elo?.global?.score??t?.userElo??1e3,p=typeof u?.elo==`number`?{}:u?.elo?.tags??{},m=dedupe(this.config.groups.flatMap(e=>e.targetCardIds)),g=dedupe(this.config.groups.flatMap(e=>e.supportCardIds??[])),_=dedupe([...m,...g]),v=_.length>0?await this.course.getAppliedTagsBatch(_):new Map,y=await this.course.getCourseTagStubs().catch(()=>({rows:[],offset:0,total_rows:0})),b=new Map;for(let e of y.rows??[]){let t=e.doc;t?.name&&Array.isArray(t.taggedCards)&&b.set(t.name,[...t.taggedCards])}let x={updatedAt:isoNow(),groups:{}},S=[],C=new Set,w=[];for(let e of this.config.groups){let t=this.buildGroupRuntimeState({group:e,priorState:c.groups[e.id],activeIds:a,seenIds:s,tagsByCard:v,cardsByTag:b,hierarchyConfigs:l,userTagElo:p,userGlobalElo:d});w.push(t),logger.info(`[Prescribed] Group '${e.id}': ${e.targetCardIds.length} targets total, ${t.encounteredTargets.size} encountered, ${t.pendingTargets.length} pending (${t.surfaceableTargets.length} surfaceable, ${t.blockedTargets.length} blocked), ${t.supportCandidates.length} authored support candidates, ${t.discoveredSupportCandidates.length} discovered support candidates, pressure=${t.pressureMultiplier.toFixed(2)}`),t.blockedTargets.length>0&&(logger.info(`[Prescribed] Group '${e.id}' blocked targets: ${t.blockedTargets.join(`, `)}`),logger.info(`[Prescribed] Group '${e.id}' support tags needed: ${t.supportTags.join(`, `)||`(none)`}`),logger.info(`[Prescribed] Group '${e.id}' escalation mode: `+(t.supportCandidates.length>0?`direct-support`:t.discoveredSupportCandidates.length>0?`inserted-support-candidates`:`boost-only`)),t.discoveredSupportCandidates.length>0&&logger.info(`[Prescribed] Group '${e.id}' discovered support candidates: ${t.discoveredSupportCandidates.join(`, `)}`)),x.groups[e.id]=this.buildNextGroupState(t,c.groups[e.id]);let r=this.buildDirectTargetCards(t,n,C),o=this.buildSupportCards(t,n,C),u=this.buildDiscoveredSupportCards(t,n,C),m=this.buildPracticeCards({group:e,courseId:n,emittedIds:C,cardsByTag:b,hierarchyConfigs:l,userTagElo:p,userGlobalElo:d,activeIds:a,seenIds:s});S.push(...r,...o,...u,...m)}let T=this.buildSupportHintSummary(w),E=Object.keys(T.boostTags).length>0?{boostTags:T.boostTags,_label:`prescribed-support (${T.supportTags.length} tags; blocked=${T.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`}:void 0;if(E){let e=Object.entries(E.boostTags??{});logger.info(`[Prescribed] Emitting ${e.length} boost hint(s): `+e.map(([e,t])=>`${e}\xD7${t.toFixed(1)}`).join(`, `))}else logger.info(`[Prescribed] No hints to emit (no blocked targets or no support tags)`);if(S.length===0)return logger.info(`[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)`+(E?` — boost hints emitted but may not survive filters`:``)),await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`)}),E?{cards:[],hints:E}:{cards:[]};let D=pickTopByScore(S,e),O=new Map;for(let e of D){let t=e.provenance[0];if(t?.reason.includes(`mode=practice`))continue;let n=t?.reason.match(/group=([^;]+)/)?.[1],r=t?.reason.includes(`mode=support`)?`supportIds`:`targetIds`;n&&(O.has(n)||O.set(n,{targetIds:[],supportIds:[]}),O.get(n)[r].push(e.cardId))}for(let e of this.config.groups){let t=x.groups[e.id],n=O.get(e.id);n&&(n.targetIds.length>0||n.supportIds.length>0)&&(t.lastSurfacedAt=isoNow(),t.sessionsSinceSurfaced=0,n.supportIds.length>0&&(t.lastSupportAt=isoNow()))}return await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`)}),logger.info(`[Prescribed] Emitting ${D.length} cards (${D.filter(e=>e.provenance[0]?.reason.includes(`mode=target`)).length} target, ${D.filter(e=>e.provenance[0]?.reason.includes(`mode=support`)).length} support, ${D.filter(e=>e.provenance[0]?.reason.includes(`mode=discovered-support`)).length} discovered support)`),E?{cards:D,hints:E}:{cards:D}}buildSupportHintSummary(e){let t={},n=new Set,r=new Set;for(let a of e)if(!(a.blockedTargets.length===0||a.supportTags.length===0)){a.blockedTargets.forEach(e=>n.add(e));for(let e of a.supportTags)r.add(e),t[e]=(t[e]??1)*a.supportMultiplier}return{boostTags:t,blockedTargetIds:[...n].sort(),supportTags:[...r].sort()}}parseConfig(e){try{let t=JSON.parse(e);return{groups:(Array.isArray(t.groups)?t.groups:[]).map((e,t)=>({id:typeof e.id==`string`&&e.id.trim().length>0?e.id:`group-${t+1}`,targetCardIds:dedupe(Array.isArray(e.targetCardIds)?e.targetCardIds.filter(e=>typeof e==`string`):[]),supportCardIds:dedupe(Array.isArray(e.supportCardIds)?e.supportCardIds.filter(e=>typeof e==`string`):[]),supportTagPatterns:dedupe(Array.isArray(e.supportTagPatterns)?e.supportTagPatterns.filter(e=>typeof e==`string`):[]),freshnessWindowSessions:typeof e.freshnessWindowSessions==`number`?e.freshnessWindowSessions:DEFAULT_FRESHNESS_WINDOW,maxDirectTargetsPerRun:typeof e.maxDirectTargetsPerRun==`number`?e.maxDirectTargetsPerRun:DEFAULT_MAX_DIRECT_PER_RUN,maxSupportCardsPerRun:typeof e.maxSupportCardsPerRun==`number`?e.maxSupportCardsPerRun:DEFAULT_MAX_SUPPORT_PER_RUN,hierarchyWalk:{enabled:e.hierarchyWalk?.enabled!==!1,maxDepth:typeof e.hierarchyWalk?.maxDepth==`number`?e.hierarchyWalk.maxDepth:DEFAULT_HIERARCHY_DEPTH},retireOnEncounter:e.retireOnEncounter!==!1,practiceTagPatterns:dedupe(Array.isArray(e.practiceTagPatterns)?e.practiceTagPatterns.filter(e=>typeof e==`string`):[]),practiceMinCount:typeof e.practiceMinCount==`number`?e.practiceMinCount:DEFAULT_PRACTICE_MIN_COUNT,maxPracticeCardsPerRun:typeof e.maxPracticeCardsPerRun==`number`?e.maxPracticeCardsPerRun:DEFAULT_MAX_PRACTICE_PER_RUN})).filter(e=>e.targetCardIds.length>0)}}catch{return{groups:[]}}}async loadHierarchyConfigs(){try{return(await this.course.getAllNavigationStrategies()).filter(e=>e.implementingClass===`hierarchyDefinition`).map(e=>{try{return{prerequisites:JSON.parse(e.serializedData).prerequisites||{}}}catch{return{prerequisites:{}}}})}catch(e){return logger.debug(`[Prescribed] Failed to load hierarchy configs: ${e}`),[]}}buildGroupRuntimeState(e){let{group:t,priorState:n,activeIds:r,seenIds:a,tagsByCard:o,cardsByTag:s,hierarchyConfigs:c,userTagElo:l,userGlobalElo:u}=e,d=new Set;for(let e of t.targetCardIds)(r.has(e)||a.has(e))&&d.add(e);if(n?.encounteredCardIds?.length)for(let e of n.encounteredCardIds)d.add(e);let p=t.targetCardIds.filter(e=>!d.has(e)),m=new Map;for(let e of p)m.set(e,o.get(e)??[]);let g=[],_=[],v=new Set;for(let e of p){let n=m.get(e)??[],r=this.resolveBlockedSupportTags(n,c,l,u,t.hierarchyWalk?.enabled!==!1,t.hierarchyWalk?.maxDepth??DEFAULT_HIERARCHY_DEPTH),a=n.filter(e=>e.startsWith(`gpc:intro:`)),o=new Set(n.filter(e=>e.startsWith(`gpc:expose:`)));for(let e of a){let t=e.slice(10);t&&o.add(`gpc:expose:${t}`)}let s=[...o].filter(e=>{let t=l[e];return!t||t.count<DEFAULT_MIN_COUNT});s.length>0&&s.forEach(e=>v.add(e)),r.blocked||s.length>0?(g.push(e),r.supportTags.forEach(e=>v.add(e))):_.push(e)}let y=dedupe([...t.supportCardIds??[],...this.findSupportCardsByTags(t,o,[...v])]).filter(e=>!r.has(e)&&!a.has(e)),b=g.length>0&&v.size>0&&y.length===0?this.findDiscoveredSupportCards({supportTags:[...v],cardsByTag:s,activeIds:r,seenIds:a,excludedIds:new Set([...t.targetCardIds,...t.supportCardIds??[]]),limit:t.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN}):[];g.length>0&&v.size>0&&b.length===0&&logger.info(`[Prescribed] Group '${t.id}' discovered 0 broader support candidates (blocked=${g.length}; authoredSupport=${y.length})`);let x=n?.sessionsSinceSurfaced??0,S=t.freshnessWindowSessions??DEFAULT_FRESHNESS_WINDOW,C=Math.max(0,x-S),w=p.length===0?1:clamp(1+C*.75+Math.min(2,p.length*.1),1,MAX_TARGET_MULTIPLIER),T=g.length===0?1:clamp(1+C*.5+Math.min(1.5,g.length*.15),1,MAX_SUPPORT_MULTIPLIER);return{group:t,encounteredTargets:d,pendingTargets:p,blockedTargets:g,surfaceableTargets:_,targetTags:m,supportCandidates:y,discoveredSupportCandidates:b,supportTags:[...v],pressureMultiplier:w,supportMultiplier:T,debugVersion:PRESCRIBED_DEBUG_VERSION}}buildNextGroupState(e,t){let n=t?.sessionsSinceSurfaced??0,r=!1;return{encounteredCardIds:[...e.encounteredTargets].sort(),pendingTargetIds:[...e.pendingTargets].sort(),lastSurfacedAt:t?.lastSurfacedAt??null,sessionsSinceSurfaced:n+1,lastSupportAt:t?.lastSupportAt??null,blockedTargetIds:[...e.blockedTargets].sort(),lastResolvedSupportTags:[...e.supportTags].sort()}}buildDirectTargetCards(e,t,n){let r=e.group.maxDirectTargetsPerRun??DEFAULT_MAX_DIRECT_PER_RUN,a=e.surfaceableTargets.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:BASE_TARGET_SCORE*e.pressureMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_TARGET_SCORE*e.pressureMultiplier,reason:`mode=target;group=${e.group.id};pending=${e.pendingTargets.length};surfaceable=${e.surfaceableTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.pressureMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildSupportCards(e,t,n){if(e.blockedTargets.length===0||e.supportCandidates.length===0)return[];let r=e.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,a=e.supportCandidates.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:BASE_SUPPORT_SCORE*e.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_SUPPORT_SCORE*e.supportMultiplier,reason:`mode=support;group=${e.group.id};pending=${e.pendingTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportCard=${r};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.supportMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildDiscoveredSupportCards(e,t,n){if(e.blockedTargets.length===0||e.discoveredSupportCandidates.length===0)return[];let r=e.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,a=e.discoveredSupportCandidates.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:DISCOVERED_SUPPORT_SCORE*e.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:DISCOVERED_SUPPORT_SCORE*e.supportMultiplier,reason:`mode=discovered-support;group=${e.group.id};pending=${e.pendingTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportCard=${r};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.supportMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildPracticeCards(e){let{group:t,courseId:n,emittedIds:r,cardsByTag:a,hierarchyConfigs:o,userTagElo:s,userGlobalElo:c,activeIds:l,seenIds:u}=e,d=t.practiceTagPatterns??[];if(d.length===0)return[];let p=t.practiceMinCount??DEFAULT_PRACTICE_MIN_COUNT,m=t.maxPracticeCardsPerRun??DEFAULT_MAX_PRACTICE_PER_RUN,g=[...a.keys()].filter(e=>d.some(t=>matchesTagPattern(e,t))&&this.isUnlockedGatedSkill(e,o,s,c)&&(s[e]?.count??0)<p);if(g.length===0)return[];let _=this.findDiscoveredSupportCards({supportTags:g,cardsByTag:a,activeIds:l,seenIds:u,excludedIds:r,limit:m});if(_.length===0)return[];logger.info(`[Prescribed] Group '${t.id}' practice: ${g.length} unlocked under-practiced skill(s), emitting ${_.length} drill card(s)`);let v=[];for(let e of _)r.add(e),v.push({cardId:e,courseId:n,score:BASE_PRACTICE_SCORE,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_PRACTICE_SCORE,reason:`mode=practice;group=${t.id};underPracticedSkills=${g.length};practiceTags=${g.slice(0,8).join(`|`)}${g.length>8?`|…`:``};testversion=${PRESCRIBED_DEBUG_VERSION}`}]});return v}isUnlockedGatedSkill(e,t,n,r){let a=t.map(t=>t.prerequisites[e]).filter(e=>Array.isArray(e)&&e.length>0);return a.length===0?!1:a.every(e=>e.every(e=>this.isPrerequisiteMet(e,n[e.tag],r)))}findSupportCardsByTags(e,t,n){if(n.length===0)return[];let r=e.supportCardIds??[],a=e.supportTagPatterns??[];if(r.length===0&&a.length===0)return[];let o=new Set;for(let e of r){let r=t.get(e)??[],s=n.some(e=>r.includes(e)),c=a.some(e=>r.some(t=>matchesTagPattern(t,e)));(s||c)&&o.add(e)}return[...o]}findDiscoveredSupportCards(e){let{supportTags:t,cardsByTag:n,activeIds:r,seenIds:a,excludedIds:o,limit:s}=e,c=new Map;for(let e of t){let t=n.get(e)??[];for(let e of t){if(r.has(e)||a.has(e)||o.has(e))continue;let t=c.get(e);t?t.matches+=1:c.set(e,{cardId:e,matches:1})}}let l=[...c.values()].sort((e,t)=>t.matches-e.matches||e.cardId.localeCompare(t.cardId)),u=new Set,d=[],p=[];for(let e of l){let t=extractWordStem(e.cardId);u.has(t)?p.push(e):(u.add(t),d.push(e))}return shuffleInPlace(d),shuffleInPlace(p),[...d,...p].slice(0,s).map(e=>e.cardId)}resolveBlockedSupportTags(e,t,n,r,a,o){let s=new Set,c=!1;for(let l of e){let e=t.map(e=>e.prerequisites[l]).filter(e=>Array.isArray(e)&&e.length>0);if(e.length!==0&&e.some(e=>e.some(e=>!this.isPrerequisiteMet(e,n[e.tag],r)))){if(c=!0,!a){for(let t of e)for(let e of t)this.isPrerequisiteMet(e,n[e.tag],r)||s.add(e.tag);continue}for(let a of e)for(let e of a)this.isPrerequisiteMet(e,n[e.tag],r)||this.collectSupportTagsRecursive(e.tag,t,n,r,o,new Set,s)}}return{blocked:c,supportTags:[...s]}}collectSupportTagsRecursive(e,t,n,r,a,o,s){if(a<0||o.has(e))return;o.add(e);let c=!1;for(let l of t){let u=l.prerequisites[e];if(!u||u.length===0)continue;let d=u.filter(e=>!this.isPrerequisiteMet(e,n[e.tag],r));if(d.length>0&&a>0){c=!0;for(let e of d)this.collectSupportTagsRecursive(e.tag,t,n,r,a-1,o,s)}}c||s.add(e)}isPrerequisiteMet(e,t,n){if(!t)return!1;let r=e.masteryThreshold?.minCount??DEFAULT_MIN_COUNT;return t.count<r?!1:e.masteryThreshold?.minElo===void 0?e.masteryThreshold?.minCount===void 0?t.score>=n:!0:t.score>=e.masteryThreshold.minElo}}}}),srs_exports={};__export(srs_exports,{default:()=>SRSNavigator});var DEFAULT_HEALTHY_BACKLOG,MAX_BACKLOG_PRESSURE,SRSNavigator,init_srs=__esm({"src/core/navigators/generators/srs.ts"(){init_navigators(),init_logger(),DEFAULT_HEALTHY_BACKLOG=20,MAX_BACKLOG_PRESSURE=.5,SRSNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),_defineProperty(this,`healthyBacklog`,void 0),this.name=n?.name||`SRS`,this.healthyBacklog=this.parseConfig(n?.serializedData).healthyBacklog??DEFAULT_HEALTHY_BACKLOG}parseConfig(e){if(!e)return{};try{return JSON.parse(e)}catch{return logger.warn(`[SRS] Failed to parse strategy config, using defaults`),{}}}async getWeightedCards(e,t){if(!this.user||!this.course)throw Error(`SRSNavigator requires user and course to be set`);let n=this.course.getCourseID(),r=await this.user.getPendingReviews(n),a=hooks.utc(),o=r.filter(e=>a.isAfter(hooks.utc(e.reviewTime)));if(o.length>0){let e=[...new Set(o.map(e=>e.cardId))],t=await this.course.getAppliedTagsBatch(e),n=[];if(o=o.filter(e=>(t.get(e.cardId)??[]).includes(`srs:skip`)?(n.push(e._id),!1):!0),n.length>0){logger.info(`[SRS] Removing ${n.length} scheduled reviews for srs:skip cards`);for(let e of n)this.user.removeScheduledCardReview(e)}}let s=this.computeBacklogPressure(o.length);if(o.length>0){let e=s>0?` [backlog pressure: +${s.toFixed(2)}]`:` [healthy backlog]`;logger.info(`[SRS] Course ${n}: ${o.length} reviews due now (of ${r.length} scheduled)${e}`)}else if(r.length>0){let e=[...r].sort((e,t)=>hooks.utc(e.reviewTime).diff(hooks.utc(t.reviewTime)))[0],t=hooks.utc(e.reviewTime),o=hooks.duration(t.diff(a)),s=o.asHours()<1?`${Math.round(o.asMinutes())}m`:o.asHours()<24?`${Math.round(o.asHours())}h`:`${Math.round(o.asDays())}d`;logger.info(`[SRS] Course ${n}: 0 reviews due now (${r.length} scheduled, next in ${s})`)}else logger.info(`[SRS] Course ${n}: No reviews scheduled`);return{cards:o.map(e=>{let{score:t,reason:n}=this.computeUrgencyScore(e,a,s);return{cardId:e.cardId,courseId:e.courseId,score:t,reviewID:e._id,provenance:[{strategy:`srs`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-SRS-default`,action:`generated`,score:t,reason:n}]}}).sort((e,t)=>t.score-e.score).slice(0,e)}}computeBacklogPressure(e){if(e<=this.healthyBacklog)return 0;let t=(e-this.healthyBacklog)/this.healthyBacklog*(MAX_BACKLOG_PRESSURE/2);return Math.min(MAX_BACKLOG_PRESSURE,t)}computeUrgencyScore(e,t,n){let r=hooks.utc(e.scheduledAt),a=hooks.utc(e.reviewTime),o=Math.max(1,a.diff(r,`hours`)),s=t.diff(a,`hours`),c=s/o,l=.3+.7*Math.exp(-o/720),u=.5+(Math.min(1,Math.max(0,c))*.5+l*.5)*.45,d=Math.min(1,u+n),p=[`${Math.round(s)}h overdue`,`interval: ${Math.round(o)}h`,`relative: ${c.toFixed(2)}`,`recency: ${l.toFixed(2)}`];return n>0&&p.push(`backlog: +${n.toFixed(2)}`),p.push(`review`),{score:d,reason:p.join(`, `)}}}}}),types_exports={},init_types=__esm({"src/core/navigators/generators/types.ts"(){}}),globImport_generators,init_=__esm({'import("./generators/**/*") in src/core/navigators/index.ts'(){globImport_generators=__glob({"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports))})}}),DEFAULT_LEARNABLE_WEIGHT,init_contentNavigationStrategy=__esm({"src/core/types/contentNavigationStrategy.ts"(){DEFAULT_LEARNABLE_WEIGHT={weight:1,confidence:.1,sampleSize:0}}}),WeightedFilter_exports={};__export(WeightedFilter_exports,{WeightedFilter:()=>WeightedFilter});var WeightedFilter,init_WeightedFilter=__esm({"src/core/navigators/filters/WeightedFilter.ts"(){init_contentNavigationStrategy(),WeightedFilter=class{constructor(e,t=DEFAULT_LEARNABLE_WEIGHT,n=!1,r){_defineProperty(this,`name`,void 0),_defineProperty(this,`inner`,void 0),_defineProperty(this,`learnable`,void 0),_defineProperty(this,`staticWeight`,void 0),_defineProperty(this,`strategyId`,void 0),this.inner=e,this.name=e.name,this.learnable=t,this.staticWeight=n,this.strategyId=r}async transform(e,t){let n=this.learnable.weight,r;if(!this.staticWeight&&t.orchestration){let e=this.strategyId||this.inner.strategyId||this.name;n=t.orchestration.getEffectiveWeight(e,this.learnable),r=t.orchestration.getDeviation(e)}if(Math.abs(n-1)<.001)return this.inner.transform(e,t);let a=new Map;for(let t of e)a.set(t.cardId,t.score);return(await this.inner.transform(e,t)).map(e=>{let t=a.get(e.cardId);if(t===void 0||t===0||e.score===0)return e;let o=e.score/t;if(Math.abs(o-1)<1e-4)return e;let s=t*o**+n,c=e.provenance.length-1,l=e.provenance[c];if(l){let t=[...e.provenance];return t[c]={...l,score:s,effectiveWeight:n,deviation:r},{...e,score:s,provenance:t}}return{...e,score:s}})}}}}),eloDistance_exports={};__export(eloDistance_exports,{DEFAULT_HALF_LIFE:()=>DEFAULT_HALF_LIFE,DEFAULT_MAX_MULTIPLIER:()=>DEFAULT_MAX_MULTIPLIER,DEFAULT_MIN_MULTIPLIER:()=>DEFAULT_MIN_MULTIPLIER,createEloDistanceFilter:()=>createEloDistanceFilter});function computeMultiplier(e,t,n,r){let a=e/t,o=Math.exp(-(a*a));return n+(r-n)*o}function createEloDistanceFilter(e){let t=e?.halfLife??DEFAULT_HALF_LIFE,n=e?.minMultiplier??DEFAULT_MIN_MULTIPLIER,r=e?.maxMultiplier??DEFAULT_MAX_MULTIPLIER;return{name:`ELO Distance Filter`,async transform(e,a){let{course:o,userElo:s}=a,c=e.map(e=>e.cardId),l=await o.getCardEloData(c);return e.map((e,a)=>{let o=l[a]?.global?.score??1e3,c=Math.abs(o-s),u=computeMultiplier(c,t,n,r),d=e.score*u,p=u<r-.01?`penalized`:`passed`;return{...e,score:d,provenance:[...e.provenance,{strategy:`eloDistance`,strategyName:`ELO Distance Filter`,strategyId:`ELO_DISTANCE_FILTER`,action:p,score:d,reason:`ELO distance ${Math.round(c)} (card: ${Math.round(o)}, user: ${Math.round(s)}) \u2192 ${u.toFixed(2)}x`}]}})}}}var DEFAULT_HALF_LIFE,DEFAULT_MIN_MULTIPLIER,DEFAULT_MAX_MULTIPLIER,init_eloDistance=__esm({"src/core/navigators/filters/eloDistance.ts"(){DEFAULT_HALF_LIFE=200,DEFAULT_MIN_MULTIPLIER=.3,DEFAULT_MAX_MULTIPLIER=1}}),hierarchyDefinition_exports={};__export(hierarchyDefinition_exports,{default:()=>HierarchyDefinitionNavigator});var DEFAULT_MIN_COUNT2,HierarchyDefinitionNavigator,init_hierarchyDefinition=__esm({"src/core/navigators/filters/hierarchyDefinition.ts"(){init_navigators(),init_logger(),DEFAULT_MIN_COUNT2=3,HierarchyDefinitionNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),this.config=this.parseConfig(n.serializedData),this.name=n.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??DEFAULT_MIN_COUNT2;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 t=new Set;try{let n=toCourseElo((await e.user.getCourseRegDoc(e.course.getCourseID())).elo);for(let e of Object.values(this.config.prerequisites))for(let r of e){let e=n.tags[r.tag];this.isPrerequisiteMet(r,e,n.global.score)&&t.add(r.tag)}}catch{}return t}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??[],a=t.filter(e=>this.hasPrerequisites(e)&&!n.has(e));return a.length===0?{isUnlocked:!0,reason:`Prerequisites met, tags: ${t.length>0?t.join(`, `):`none`}`}:{isUnlocked:!1,reason:`Blocked: missing prerequisites ${a.flatMap(e=>(this.config.prerequisites[e]||[]).filter(e=>!r.has(e.tag)).map(e=>e.tag)).join(`, `)} for tags ${a.join(`, `)}`}}catch{return{isUnlocked:!0,reason:`Prerequisites check skipped (tag lookup failed)`}}}getPreReqBoosts(e,t){let n=new Map;for(let[r,a]of Object.entries(this.config.prerequisites))if(!e.has(r))for(let e of a){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,n=Object.keys(this.config.prerequisites),r=[...e];logger.info(`[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${n.length}, unlocked=${r.length} (${r.slice(0,5).join(`, `)}${r.length>5?`...`:``})`);for(let[n,r]of Object.entries(this.config.prerequisites))if(e.has(n)){logger.info(`[HierarchyDefinition:targetBoost:trace] UNLOCKED ${n}: ${r.length} prereqs, raw=${JSON.stringify(r.map(e=>({tag:e.tag,tb:e.targetBoost})))}`);for(let e of r){if(!e.targetBoost||e.targetBoost<=1)continue;let r=t.get(n)??1;t.set(n,Math.max(r,e.targetBoost))}}return t.size>0?logger.info(`[HierarchyDefinition] targetBoosts active: ${[...t.entries()].map(([e,t])=>`${e}=\xD7${t}`).join(`, `)}`):logger.info(`[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${r.length} unlocked tags`),t}async transform(e,t){let n=await this.getMasteredTags(t),r=this.getUnlockedTags(n),a=this.getPreReqBoosts(r,n),o=this.getTargetBoosts(r),s=[];for(let c of e){let{isUnlocked:e,reason:l}=await this.checkCardUnlock(c,t.course,r,n),u=e?c.score:c.score*.02,d=e?`passed`:`penalized`,p=l;if(e&&a.size>0){let e=c.tags??[],t=1,n=[];for(let r of e){let e=a.get(r);e&&e>t&&(t=e,n.push(r))}t>1&&(u*=t,d=`boosted`,p=`${l} | preReqBoost \xD7${t.toFixed(2)} for ${n.join(`, `)}`,logger.info(`[HierarchyDefinition] preReqBoost \xD7${t.toFixed(2)} applied to card ${c.cardId} via tags [${n.join(`, `)}] (score: ${c.score.toFixed(3)} \u2192 ${u.toFixed(3)})`))}if(e&&o.size>0){let e=c.tags??[],t=1,n=[];for(let r of e){let e=o.get(r);e&&e>t&&(t=e,n.push(r))}t>1&&(u*=t,d=`boosted`,p=`${p} | targetBoost \xD7${t.toFixed(2)} for ${n.join(`, `)}`,logger.info(`[HierarchyDefinition] targetBoost \xD7${t.toFixed(2)} applied to card ${c.cardId} via tags [${n.join(`, `)}] (score: ${c.score.toFixed(3)} \u2192 ${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:p}]})}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().`)}}}}),userTagPreference_exports={};__export(userTagPreference_exports,{default:()=>UserTagPreferenceFilter});var UserTagPreferenceFilter,init_userTagPreference=__esm({"src/core/navigators/filters/userTagPreference.ts"(){init_navigators(),UserTagPreferenceFilter=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`_strategyData`,void 0),_defineProperty(this,`name`,void 0),this._strategyData=n,this.name=n.name||`User Tag Preferences`}computeMultiplier(e,t){let n=e.map(e=>t[e]).filter(e=>e!==void 0);return n.length===0?1:Math.max(...n)}buildReason(e,t,n){let r=e.filter(e=>t[e]===n);return n===0?`Excluded by user preference: ${r.join(`, `)} (${n}x)`:n<1?`Penalized by user preference: ${r.join(`, `)} (${n.toFixed(2)}x)`:n>1?`Boosted by user preference: ${r.join(`, `)} (${n.toFixed(2)}x)`:`No matching user preferences`}async transform(e,t){let n=await this.getStrategyState();return!n||Object.keys(n.boost).length===0?e.map(e=>({...e,provenance:[...e.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:`passed`,score:e.score,reason:`No user tag preferences configured`}]})):await Promise.all(e.map(async e=>{let t=e.tags??[],r=this.computeMultiplier(t,n.boost),a=Math.min(1,e.score*r),o;return o=r===0||r<1?`penalized`:r>1?`boosted`:`passed`,{...e,score:a,provenance:[...e.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:o,score:a,reason:this.buildReason(t,n.boost,r)}]}}))}async getWeightedCards(e){throw Error(`UserTagPreferenceFilter is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),filters_exports={};__export(filters_exports,{UserTagPreferenceFilter:()=>UserTagPreferenceFilter,createEloDistanceFilter:()=>createEloDistanceFilter});var init_filters=__esm({"src/core/navigators/filters/index.ts"(){init_eloDistance(),init_userTagPreference()}}),inferredPreferenceStub_exports={};__export(inferredPreferenceStub_exports,{INFERRED_PREFERENCE_NAVIGATOR_STUB:()=>INFERRED_PREFERENCE_NAVIGATOR_STUB});var INFERRED_PREFERENCE_NAVIGATOR_STUB,init_inferredPreferenceStub=__esm({"src/core/navigators/filters/inferredPreferenceStub.ts"(){INFERRED_PREFERENCE_NAVIGATOR_STUB=!0}}),interferenceMitigator_exports={};__export(interferenceMitigator_exports,{default:()=>InterferenceMitigatorNavigator});var DEFAULT_MIN_COUNT3,DEFAULT_MIN_ELAPSED_DAYS,DEFAULT_INTERFERENCE_DECAY,InterferenceMitigatorNavigator,init_interferenceMitigator=__esm({"src/core/navigators/filters/interferenceMitigator.ts"(){init_navigators(),DEFAULT_MIN_COUNT3=10,DEFAULT_MIN_ELAPSED_DAYS=3,DEFAULT_INTERFERENCE_DECAY=.8,InterferenceMitigatorNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),_defineProperty(this,`interferenceMap`,void 0),this.config=this.parseConfig(n.serializedData),this.interferenceMap=this.buildInterferenceMap(),this.name=n.name||`Interference Mitigator`}parseConfig(e){try{let t=JSON.parse(e),n=t.interferenceSets||[];return n.length>0&&Array.isArray(n[0])&&(n=n.map(e=>({tags:e}))),{interferenceSets:n,maturityThreshold:{minCount:t.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,minElo:t.maturityThreshold?.minElo,minElapsedDays:t.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:t.defaultDecay??DEFAULT_INTERFERENCE_DECAY}}catch{return{interferenceSets:[],maturityThreshold:{minCount:DEFAULT_MIN_COUNT3,minElapsedDays:DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:DEFAULT_INTERFERENCE_DECAY}}}buildInterferenceMap(){let e=new Map;for(let t of this.config.interferenceSets){let n=t.decay??this.config.defaultDecay??DEFAULT_INTERFERENCE_DECAY;for(let r of t.tags){e.has(r)||e.set(r,[]);let a=e.get(r);for(let e of t.tags)if(e!==r){let t=a.find(t=>t.partner===e);t?t.decay=Math.max(t.decay,n):a.push({partner:e,decay:n})}}}return e}async getImmatureTags(e){let t=new Set;try{let n=toCourseElo((await e.user.getCourseRegDoc(e.course.getCourseID())).elo),r=this.config.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,a=this.config.maturityThreshold?.minElo,o=(this.config.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS)*2;for(let[e,s]of Object.entries(n.tags)){if(s.count===0)continue;let n=s.count<r,c=a!==void 0&&s.score<a,l=s.count<o;(n||c||l)&&t.add(e)}}catch{}return t}getTagsToAvoid(e){let t=new Map;for(let n of e){let r=this.interferenceMap.get(n);if(r){for(let{partner:n,decay:a}of r)if(!e.has(n)){let e=t.get(n)??0;t.set(n,Math.max(e,a))}}}return t}computeInterferenceEffect(e,t,n){if(t.size===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let r=1,a=[];for(let n of e){let e=t.get(n);e!==void 0&&(a.push(n),r*=1-e)}if(a.length===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let o=new Set;for(let e of a)for(let t of n)this.interferenceMap.get(t)?.some(t=>t.partner===e)&&o.add(t);let s=`Interferes with immature tags ${Array.from(o).join(`, `)} (tags: ${a.join(`, `)}, multiplier: ${r.toFixed(2)})`;return{multiplier:r,interferingTags:a,reason:s}}async transform(e,t){let n=await this.getImmatureTags(t),r=this.getTagsToAvoid(n),a=[];for(let t of e){let e=t.tags??[],{multiplier:o,reason:s}=this.computeInterferenceEffect(e,r,n),c=t.score*o,l=o<1?`penalized`:o>1?`boosted`:`passed`;a.push({...t,score:c,provenance:[...t.provenance,{strategy:`interferenceMitigator`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-interference`,action:l,score:c,reason:s}]})}return a}async getWeightedCards(e){throw Error(`InterferenceMitigatorNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),relativePriority_exports={};__export(relativePriority_exports,{default:()=>RelativePriorityNavigator});var DEFAULT_PRIORITY,DEFAULT_PRIORITY_INFLUENCE,DEFAULT_COMBINE_MODE,RelativePriorityNavigator,init_relativePriority=__esm({"src/core/navigators/filters/relativePriority.ts"(){init_navigators(),DEFAULT_PRIORITY=.5,DEFAULT_PRIORITY_INFLUENCE=.5,DEFAULT_COMBINE_MODE=`max`,RelativePriorityNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),this.config=this.parseConfig(n.serializedData),this.name=n.name||`Relative Priority`}parseConfig(e){try{let t=JSON.parse(e);return{tagPriorities:t.tagPriorities||{},defaultPriority:t.defaultPriority??DEFAULT_PRIORITY,combineMode:t.combineMode??DEFAULT_COMBINE_MODE,priorityInfluence:t.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE}}catch{return{tagPriorities:{},defaultPriority:DEFAULT_PRIORITY,combineMode:DEFAULT_COMBINE_MODE,priorityInfluence:DEFAULT_PRIORITY_INFLUENCE}}}getTagPriority(e){return this.config.tagPriorities[e]??this.config.defaultPriority??DEFAULT_PRIORITY}computeCardPriority(e){if(e.length===0)return this.config.defaultPriority??DEFAULT_PRIORITY;let t=e.map(e=>this.getTagPriority(e));switch(this.config.combineMode){case`max`:return Math.max(...t);case`min`:return Math.min(...t);case`average`:return t.reduce((e,t)=>e+t,0)/t.length;default:return Math.max(...t)}}computeBoostFactor(e){let t=this.config.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE;return 1+(e-.5)*t}buildPriorityReason(e,t,n,r){if(e.length===0)return`No tags, neutral priority (${t.toFixed(2)})`;let a=e.slice(0,3).join(`, `),o=e.length>3?` (+${e.length-3} more)`:``;return n===1?`Neutral priority (${t.toFixed(2)}) for tags: ${a}${o}`:n>1?`High-priority tags: ${a}${o} (priority ${t.toFixed(2)} \u2192 boost ${n.toFixed(2)}x \u2192 ${r.toFixed(2)})`:`Low-priority tags: ${a}${o} (priority ${t.toFixed(2)} \u2192 reduce ${n.toFixed(2)}x \u2192 ${r.toFixed(2)})`}async transform(e,t){return await Promise.all(e.map(async e=>{let t=e.tags??[],n=this.computeCardPriority(t),r=this.computeBoostFactor(n),a=Math.max(0,e.score*r),o=r>1?`boosted`:r<1?`penalized`:`passed`,s=this.buildPriorityReason(t,n,r,a);return{...e,score:a,provenance:[...e.provenance,{strategy:`relativePriority`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-priority`,action:o,score:a,reason:s}]}}))}async getWeightedCards(e){throw Error(`RelativePriorityNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),types_exports2={},init_types2=__esm({"src/core/navigators/filters/types.ts"(){}}),userGoalStub_exports={};__export(userGoalStub_exports,{USER_GOAL_NAVIGATOR_STUB:()=>USER_GOAL_NAVIGATOR_STUB});var USER_GOAL_NAVIGATOR_STUB,init_userGoalStub=__esm({"src/core/navigators/filters/userGoalStub.ts"(){USER_GOAL_NAVIGATOR_STUB=!0}}),globImport_filters,init_2=__esm({'import("./filters/**/*") in src/core/navigators/index.ts'(){globImport_filters=__glob({"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports))})}});function aggregateOutcomesForGradient(e,t){let n=[];for(let r of e){let e=r.deviations[t];e!==void 0&&n.push({deviation:e,outcomeValue:r.outcomeValue,weight:1})}return logger.debug(`[Orchestration] Aggregated ${n.length} observations for strategy ${t}`),n}function computeStrategyGradient(e){let t=e.length;if(t<3)return logger.debug(`[Orchestration] Insufficient observations for gradient (${t} < 3)`),null;let n=0,r=0,a=0;for(let t of e){let e=t.weight??1;n+=t.deviation*e,r+=t.outcomeValue*e,a+=e}let o=n/a,s=r/a,c=0,l=0,u=0;for(let t of e){let e=t.weight??1,n=t.deviation-o,r=t.outcomeValue-s;c+=e*n*r,l+=e*n*n,u+=e*r*r}if(l<1e-10)return logger.debug(`[Orchestration] No variance in deviations, cannot compute gradient`),{gradient:0,intercept:s,rSquared:0,sampleSize:t};let d=c/l,p=s-d*o,m=0;for(let t of e){let e=t.weight??1,n=d*t.deviation+p,r=t.outcomeValue-n;m+=e*r*r}let g=u>1e-10?1-m/u:0;return logger.debug(`[Orchestration] Computed gradient: ${d.toFixed(4)}, intercept: ${p.toFixed(4)}, R\xB2: ${g.toFixed(4)}, n=${t}`),{gradient:d,intercept:p,rSquared:Math.max(0,Math.min(1,g)),sampleSize:t}}var init_gradient=__esm({"src/core/orchestration/gradient.ts"(){init_logger()}});function updateStrategyWeight(e,t){if(t.sampleSize<MIN_OBSERVATIONS_FOR_UPDATE)return logger.debug(`[Orchestration] Insufficient samples (${t.sampleSize} < ${MIN_OBSERVATIONS_FOR_UPDATE}), keeping current weight`),{...e,sampleSize:e.sampleSize+t.sampleSize};let n=t.rSquared>=MIN_R_SQUARED_FOR_GRADIENT,r=Math.abs(t.gradient)<FLAT_GRADIENT_THRESHOLD,a=e.weight,o=e.confidence;if(!n||r){let n=.05*(1-e.confidence);o=Math.min(1,e.confidence+n),logger.debug(`[Orchestration] Flat/unreliable gradient (|g|=${Math.abs(t.gradient).toFixed(4)}, R\xB2=${t.rSquared.toFixed(4)}). Increasing confidence: ${e.confidence.toFixed(3)} \u2192 ${o.toFixed(3)}`)}else{let n=t.gradient*LEARNING_RATE;n=Math.max(-MAX_WEIGHT_DELTA,Math.min(MAX_WEIGHT_DELTA,n)),a=e.weight+n,a=Math.max(.1,Math.min(3,a));let r=.02*(1-e.confidence);o=Math.min(1,e.confidence+r),logger.debug(`[Orchestration] Adjusting weight: ${e.weight.toFixed(3)} \u2192 ${a.toFixed(3)} (gradient=${t.gradient.toFixed(4)}, delta=${n.toFixed(4)})`)}return{weight:a,confidence:o,sampleSize:e.sampleSize+t.sampleSize}}function updateLearningState(e,t,n,r,a){let o=new Date().toISOString(),s=`STRATEGY_LEARNING_STATE::${e}::${t}`,c={timestamp:o,weight:n.weight,confidence:n.confidence,gradient:r.gradient},l=a?.history??[];return l=[...l,c],l.length>MAX_HISTORY_LENGTH&&(l=l.slice(l.length-MAX_HISTORY_LENGTH)),{_id:s,_rev:a?._rev,docType:`STRATEGY_LEARNING_STATE`,courseId:e,strategyId:t,currentWeight:n,regression:{gradient:r.gradient,intercept:r.intercept,rSquared:r.rSquared,sampleSize:r.sampleSize,computedAt:o},history:l,updatedAt:o}}function runPeriodUpdate(e){let{courseId:t,strategyId:n,currentWeight:r,gradient:a,existingState:o}=e;logger.info(`[Orchestration] Running period update for strategy ${n} (${a.sampleSize} observations)`);let s=updateStrategyWeight(r,a),c=s.weight!==r.weight,l=updateLearningState(t,n,s,a,o);return logger.info(`[Orchestration] Period update complete for ${n}: weight ${r.weight.toFixed(3)} \u2192 ${s.weight.toFixed(3)}, confidence ${r.confidence.toFixed(3)} \u2192 ${s.confidence.toFixed(3)}`),{strategyId:n,previousWeight:r,newWeight:s,gradient:a,learningState:l,updated:c}}function getDefaultLearnableWeight(){return{...DEFAULT_LEARNABLE_WEIGHT}}var MIN_OBSERVATIONS_FOR_UPDATE,LEARNING_RATE,MAX_WEIGHT_DELTA,MIN_R_SQUARED_FOR_GRADIENT,FLAT_GRADIENT_THRESHOLD,MAX_HISTORY_LENGTH,init_learning=__esm({"src/core/orchestration/learning.ts"(){init_contentNavigationStrategy(),init_types_legacy(),init_logger(),MIN_OBSERVATIONS_FOR_UPDATE=10,LEARNING_RATE=.1,MAX_WEIGHT_DELTA=.3,MIN_R_SQUARED_FOR_GRADIENT=.05,FLAT_GRADIENT_THRESHOLD=.02,MAX_HISTORY_LENGTH=100}});function computeOutcomeSignal(e,t={}){if(!e||e.length===0)return null;let n=t.targetAccuracy??.85,r=t.tolerance??.05,a=0;for(let t of e)t.isCorrect&&a++;return scoreAccuracyInZone(a/e.length,n,r)}function scoreAccuracyInZone(e,t,n){let r=Math.abs(e-t);if(r<=n)return 1;let a=r-n;return Math.max(0,1-a*2.5)}var init_signal=__esm({"src/core/orchestration/signal.ts"(){}});async function recordUserOutcome(e,t,n,r,a,o=0,s=0,c){let{user:l,course:u,userId:d}=e,p=u.getCourseID(),m=computeOutcomeSignal(r,c);if(m===null){logger.debug(`[Orchestration] No outcome signal computed for ${d} (insufficient data). Skipping record.`);return}let g={};for(let t of a)g[t]=e.getDeviation(t);let _=`USER_OUTCOME::${p}::${d}::${n}`,v={_id:_,docType:`USER_OUTCOME`,courseId:p,userId:d,periodStart:t,periodEnd:n,outcomeValue:m,deviations:g,metadata:{sessionsCount:1,cardsSeen:r.length,eloStart:o,eloEnd:s,signalType:`accuracy_in_zone`}};try{await l.putUserOutcome(v),logger.debug(`[Orchestration] Recorded outcome ${m.toFixed(3)} for ${d} (doc: ${_})`)}catch(e){logger.error(`[Orchestration] Failed to record outcome: ${e}`)}}var init_recording=__esm({"src/core/orchestration/recording.ts"(){init_signal(),init_types_legacy(),init_logger()}});function fnv1a(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return t>>>0}function computeDeviation(e,t,n){return fnv1a(`${e}:${t}:${n}`)/4294967296*2-1}function computeSpread(e){return MAX_SPREAD-Math.max(0,Math.min(1,e))*(MAX_SPREAD-MIN_SPREAD)}function computeEffectiveWeight(e,t,n,r){let a=computeDeviation(t,n,r)*computeSpread(e.confidence)*e.weight,o=e.weight+a;return Math.max(MIN_WEIGHT,Math.min(MAX_WEIGHT,o))}async function createOrchestrationContext(e,t){let n;try{n=await t.getCourseConfig()}catch(e){logger.error(`[Orchestration] Failed to load course config: ${e}`),n={name:`Unknown`,description:``,public:!1,deleted:!1,creator:``,admins:[],moderators:[],dataShapes:[],questionTypes:[],orchestration:{salt:`default`}}}let r=e.getUsername(),a=n.orchestration?.salt||`default_salt`;return{user:e,course:t,userId:r,courseConfig:n,getEffectiveWeight(e,t){return computeEffectiveWeight(t,r,e,a)},getDeviation(e){return computeDeviation(r,e,a)}}}var MIN_SPREAD,MAX_SPREAD,MIN_WEIGHT,MAX_WEIGHT,init_orchestration=__esm({"src/core/orchestration/index.ts"(){init_logger(),init_gradient(),init_learning(),init_signal(),init_recording(),MIN_SPREAD=.1,MAX_SPREAD=.5,MIN_WEIGHT=.1,MAX_WEIGHT=3}}),Pipeline_exports={};__export(Pipeline_exports,{Pipeline:()=>Pipeline,mergeHints:()=>mergeHints2});function globToRegex(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`);return RegExp(`^${t}$`)}function globMatch(e,t){return t.includes(`*`)?globToRegex(t).test(e):e===t}function cardMatchesTagPattern(e,t){return(e.tags??[]).some(e=>globMatch(e,t))}function mergeHints2(e){let t=e.filter(e=>e!=null);if(t.length===0)return;let n={},r={};for(let e of t)for(let[t,n]of Object.entries(e.boostTags??{}))r[t]=(r[t]??1)*n;Object.keys(r).length>0&&(n.boostTags=r);let a={};for(let e of t)for(let[t,n]of Object.entries(e.boostCards??{}))a[t]=(a[t]??1)*n;Object.keys(a).length>0&&(n.boostCards=a);let concatUnique=e=>{let r=t.flatMap(t=>t[e]??[]);r.length>0&&(n[e]=[...new Set(r)])};concatUnique(`requireTags`),concatUnique(`requireCards`),concatUnique(`excludeTags`),concatUnique(`excludeCards`);let o=t.map(e=>e._label).filter(Boolean);return o.length>0&&(n._label=o.join(`; `)),Object.keys(n).length>0?n:void 0}function logPipelineConfig(e,t){let n=t.length>0?`
|
|
249
|
+
`)}},mountPipelineDebugger()}}),SrsDebugger_exports={};__export(SrsDebugger_exports,{captureSrsBacklog:()=>captureSrsBacklog,clearSrsBacklogDebug:()=>clearSrsBacklogDebug,getSrsBacklogDebug:()=>getSrsBacklogDebug});function captureSrsBacklog(e){snapshots.set(e.courseId,e)}function getSrsBacklogDebug(){return[...snapshots.values()].sort((e,t)=>t.timestamp-e.timestamp)}function clearSrsBacklogDebug(){snapshots.clear()}var snapshots,init_SrsDebugger=__esm({"src/core/navigators/SrsDebugger.ts"(){snapshots=new Map}}),CompositeGenerator_exports={};__export(CompositeGenerator_exports,{AggregationMode:()=>AggregationMode,default:()=>CompositeGenerator});function mergeHints(e){let t=e.filter(e=>e!==void 0);if(t.length===0)return;let n={},r={};for(let e of t)for(let[t,n]of Object.entries(e.boostTags??{}))r[t]=(r[t]??1)*n;Object.keys(r).length>0&&(n.boostTags=r);let a={};for(let e of t)for(let[t,n]of Object.entries(e.boostCards??{}))a[t]=(a[t]??1)*n;Object.keys(a).length>0&&(n.boostCards=a);let concatUnique=e=>{let r=t.flatMap(t=>t[e]??[]);r.length>0&&(n[e]=[...new Set(r)])};concatUnique(`requireTags`),concatUnique(`requireCards`),concatUnique(`excludeTags`),concatUnique(`excludeCards`);let o=t.map(e=>e._label).filter(Boolean);return o.length>0&&(n._label=o.join(`; `)),Object.keys(n).length>0?n:void 0}var AggregationMode,DEFAULT_AGGREGATION_MODE,FREQUENCY_BOOST_FACTOR,CompositeGenerator,init_CompositeGenerator=__esm({"src/core/navigators/generators/CompositeGenerator.ts"(){init_navigators(),init_logger(),AggregationMode=(e=>(e.MAX=`max`,e.AVERAGE=`average`,e.FREQUENCY_BOOST=`frequencyBoost`,e))(AggregationMode||{}),DEFAULT_AGGREGATION_MODE=`frequencyBoost`,FREQUENCY_BOOST_FACTOR=.1,CompositeGenerator=class _CompositeGenerator extends ContentNavigator{constructor(e,t=DEFAULT_AGGREGATION_MODE){if(super(),_defineProperty(this,`name`,`Composite Generator`),_defineProperty(this,`generators`,void 0),_defineProperty(this,`aggregationMode`,void 0),this.generators=e,this.aggregationMode=t,e.length===0)throw Error(`CompositeGenerator requires at least one generator`);logger.debug(`[CompositeGenerator] Created with ${e.length} generators, mode: ${t}`)}static async fromStrategies(e,t,n,r=DEFAULT_AGGREGATION_MODE){return new _CompositeGenerator(await Promise.all(n.map(n=>ContentNavigator.create(e,t,n))),r)}async getWeightedCards(e,t){if(!t)throw Error(`CompositeGenerator.getWeightedCards requires a GeneratorContext. It should be called via Pipeline, not directly.`);let n=await Promise.all(this.generators.map(n=>n.getWeightedCards(e,t))),r=[];n.forEach((e,t)=>{let n=e.cards,a=this.generators[t].name||`Generator ${t}`,o=n.filter(e=>e.provenance[0]?.reason?.includes(`new card`)),s=n.filter(e=>e.provenance[0]?.reason?.includes(`review`));if(n.length>0){let e=Math.max(...n.map(e=>e.score)).toFixed(2),t=[];o.length>0&&t.push(`${o.length} new`),s.length>0&&t.push(`${s.length} reviews`);let c=t.length>0?t.join(`, `):`${n.length} cards`;r.push(`${a}: ${c} (top: ${e})`)}else r.push(`${a}: 0 cards`)}),logger.info(`[Composite] Generator breakdown: ${r.join(` | `)}`);let a=new Map;n.forEach((e,n)=>{let r=e.cards,o=this.generators[n],s=o.learnable?.weight??1,c;if(o.learnable&&!o.staticWeight&&t.orchestration){let e=o.strategyId;e&&(s=t.orchestration.getEffectiveWeight(e,o.learnable),c=t.orchestration.getDeviation(e))}for(let e of r){e.provenance.length>0&&(e.provenance[0].effectiveWeight=s,e.provenance[0].deviation=c);let t=a.get(e.cardId)||[];t.push({card:e,weight:s}),a.set(e.cardId,t)}});let o=[];for(let[,e]of a){let t=e.map(e=>e.card),n=this.aggregateScores(e),r=Math.max(0,n),a=t.flatMap(e=>e.provenance),s=t[0].score,c=r>s?`boosted`:r<s?`penalized`:`passed`,l=this.buildAggregationReason(e,r);o.push({...t[0],score:r,provenance:[...a,{strategy:`composite`,strategyName:`Composite Generator`,strategyId:`COMPOSITE_GENERATOR`,action:c,score:r,reason:l}]})}return{cards:o.sort((e,t)=>t.score-e.score).slice(0,e),hints:mergeHints(n.map(e=>e.hints))}}buildAggregationReason(e,t){let n=e.map(e=>e.card),r=n.length,a=n.map(e=>e.score.toFixed(2)).join(`, `);if(r===1){let n=Math.abs(e[0].weight-1)>.001?` (w=${e[0].weight.toFixed(2)})`:``;return`Single generator, score ${t.toFixed(2)}${n}`}let o=n.map(e=>e.provenance[0]?.strategy||`unknown`).join(`, `);switch(this.aggregationMode){case`max`:return`Max of ${r} generators (${o}): scores [${a}] \u2192 ${t.toFixed(2)}`;case`average`:return`Weighted Avg of ${r} generators (${o}): scores [${a}] \u2192 ${t.toFixed(2)}`;case`frequencyBoost`:{let n=e.reduce((e,t)=>e+t.weight,0),a=e.reduce((e,t)=>e+t.card.score*t.weight,0),s=n>0?a/n:0,c=1+FREQUENCY_BOOST_FACTOR*(r-1);return`Frequency boost from ${r} generators (${o}): w-avg ${s.toFixed(2)} \xD7 ${c.toFixed(2)} \u2192 ${t.toFixed(2)}`}default:return`Aggregated from ${r} generators: ${t.toFixed(2)}`}}aggregateScores(e){let t=e.map(e=>e.card.score);switch(this.aggregationMode){case`max`:return Math.max(...t);case`average`:{let t=e.reduce((e,t)=>e+t.weight,0);return t===0?0:e.reduce((e,t)=>e+t.card.score*t.weight,0)/t}case`frequencyBoost`:{let t=e.reduce((e,t)=>e+t.weight,0),n=e.reduce((e,t)=>e+t.card.score*t.weight,0);return(t>0?n/t:0)*(1+FREQUENCY_BOOST_FACTOR*(e.length-1))}default:return t[0]}}}}}),elo_exports={};__export(elo_exports,{default:()=>ELONavigator});var ELO_RELEVANCE_SIGMA,ELONavigator,init_elo=__esm({"src/core/navigators/generators/elo.ts"(){init_navigators(),init_logger(),ELO_RELEVANCE_SIGMA=300,ELONavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),this.name=n?.name||`ELO`}async getWeightedCards(e,t){let n;n=t?.userElo===void 0?toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score:t.userElo;let r=await this.user.getActiveCards(),a=(await this.course.getCardsCenteredAtELO({limit:e,elo:`user`},e=>!r.some(t=>e.cardID===t.cardID))).map(e=>({...e,status:`new`})).map(e=>{let t=e.elo??1e3,r=Math.abs(t-n),a=Math.exp(-((r/ELO_RELEVANCE_SIGMA)**2)),o=a*(.5+.5*Math.random());return{cardId:e.cardID,courseId:e.courseID,score:o,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:o,reason:`ELO distance ${Math.round(r)} (card: ${Math.round(t)}, user: ${Math.round(n)}), relevance ${a.toFixed(3)}, key ${o.toFixed(3)}`}]}});a.sort((e,t)=>t.score-e.score);let o=a.slice(0,e);if(o.length>0){let e=o.slice(0,3).map(e=>e.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${o.length} new cards (top scores: ${e})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:o}}}}}),generators_exports={},init_generators=__esm({"src/core/navigators/generators/index.ts"(){}}),prescribed_exports={};__export(prescribed_exports,{default:()=>PrescribedCardsGenerator});function dedupe(e){return[...new Set(e)]}function isoNow(){return new Date().toISOString()}function clamp(e,t,n){return Math.max(t,Math.min(n,e))}function matchesTagPattern(e,t){if(t===`*`)return!0;let n=t.replace(/[.+^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`);return RegExp(`^${n}$`).test(e)}function extractWordStem(e){for(let t of[`c-ml-`,`c-ws-`,`c-spelling-`])if(e.startsWith(t)){let n=e.slice(t.length),r=n.lastIndexOf(`-`);return r>0?n.slice(0,r):n}return e}function shuffleInPlace(e){for(let t=e.length-1;t>0;t--){let n=Math.floor(Math.random()*(t+1));[e[t],e[n]]=[e[n],e[t]]}}function pickTopByScore(e,t){return[...e].sort((e,t)=>t.score-e.score||e.cardId.localeCompare(t.cardId)).slice(0,t)}var DEFAULT_FRESHNESS_WINDOW,DEFAULT_MAX_DIRECT_PER_RUN,DEFAULT_MAX_SUPPORT_PER_RUN,DEFAULT_HIERARCHY_DEPTH,DEFAULT_MIN_COUNT,DEFAULT_PRACTICE_MIN_COUNT,DEFAULT_MAX_PRACTICE_PER_RUN,BASE_TARGET_SCORE,BASE_SUPPORT_SCORE,DISCOVERED_SUPPORT_SCORE,BASE_PRACTICE_SCORE,PRACTICE_BASE_MULT,MAX_PRACTICE_MULTIPLIER,PRACTICE_STALENESS_BUMP_PER_DAY,MAX_TARGET_MULTIPLIER,MAX_SUPPORT_MULTIPLIER,PRESCRIBED_DEBUG_VERSION,PrescribedCardsGenerator,init_prescribed=__esm({"src/core/navigators/generators/prescribed.ts"(){init_navigators(),init_logger(),DEFAULT_FRESHNESS_WINDOW=3,DEFAULT_MAX_DIRECT_PER_RUN=3,DEFAULT_MAX_SUPPORT_PER_RUN=3,DEFAULT_HIERARCHY_DEPTH=2,DEFAULT_MIN_COUNT=3,DEFAULT_PRACTICE_MIN_COUNT=3,DEFAULT_MAX_PRACTICE_PER_RUN=4,BASE_TARGET_SCORE=1,BASE_SUPPORT_SCORE=.8,DISCOVERED_SUPPORT_SCORE=12,BASE_PRACTICE_SCORE=1,PRACTICE_BASE_MULT=2,MAX_PRACTICE_MULTIPLIER=4,PRACTICE_STALENESS_BUMP_PER_DAY=.5,MAX_TARGET_MULTIPLIER=8,MAX_SUPPORT_MULTIPLIER=4,PRESCRIBED_DEBUG_VERSION=`testversion-prescribed-v3`,PrescribedCardsGenerator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),_defineProperty(this,`config`,void 0),this.name=n.name||`Prescribed Cards`,this.config=this.parseConfig(n.serializedData),logger.debug(`[Prescribed] Initialized with ${this.config.groups.length} groups and ${this.config.groups.reduce((e,t)=>e+t.targetCardIds.length,0)} targets`)}get strategyKey(){return`PrescribedProgress`}async getWeightedCards(e,t){if(this.config.groups.length===0||e<=0)return{cards:[]};let n=this.course.getCourseID(),r=await this.user.getActiveCards(),a=new Set(r.map(e=>e.cardID)),o=await this.user.getSeenCards(n).catch(()=>[]),s=new Set(o),c=await this.getStrategyState()??{updatedAt:isoNow(),groups:{}},l=await this.loadHierarchyConfigs(),u=await this.user.getCourseRegDoc(n).catch(()=>null),d=typeof u?.elo==`number`?u.elo:u?.elo?.global?.score??t?.userElo??1e3,p=typeof u?.elo==`number`?{}:u?.elo?.tags??{},m=dedupe(this.config.groups.flatMap(e=>e.targetCardIds)),g=dedupe(this.config.groups.flatMap(e=>e.supportCardIds??[])),_=dedupe([...m,...g]),v=_.length>0?await this.course.getAppliedTagsBatch(_):new Map,y=await this.course.getCourseTagStubs().catch(()=>({rows:[],offset:0,total_rows:0})),b=new Map;for(let e of y.rows??[]){let t=e.doc;t?.name&&Array.isArray(t.taggedCards)&&b.set(t.name,[...t.taggedCards])}let x={updatedAt:isoNow(),groups:{}},S=[],C=new Set,w=[],T=c.practiceDebt??{},E={};for(let e of this.config.groups){let t=this.buildGroupRuntimeState({group:e,priorState:c.groups[e.id],activeIds:a,seenIds:s,tagsByCard:v,cardsByTag:b,hierarchyConfigs:l,userTagElo:p,userGlobalElo:d});w.push(t),logger.info(`[Prescribed] Group '${e.id}': ${e.targetCardIds.length} targets total, ${t.encounteredTargets.size} encountered, ${t.pendingTargets.length} pending (${t.surfaceableTargets.length} surfaceable, ${t.blockedTargets.length} blocked), ${t.supportCandidates.length} authored support candidates, ${t.discoveredSupportCandidates.length} discovered support candidates, pressure=${t.pressureMultiplier.toFixed(2)}`),t.blockedTargets.length>0&&(logger.info(`[Prescribed] Group '${e.id}' blocked targets: ${t.blockedTargets.join(`, `)}`),logger.info(`[Prescribed] Group '${e.id}' support tags needed: ${t.supportTags.join(`, `)||`(none)`}`),logger.info(`[Prescribed] Group '${e.id}' escalation mode: `+(t.supportCandidates.length>0?`direct-support`:t.discoveredSupportCandidates.length>0?`inserted-support-candidates`:`boost-only`)),t.discoveredSupportCandidates.length>0&&logger.info(`[Prescribed] Group '${e.id}' discovered support candidates: ${t.discoveredSupportCandidates.join(`, `)}`)),x.groups[e.id]=this.buildNextGroupState(t,c.groups[e.id]);let r=this.buildDirectTargetCards(t,n,C),o=this.buildSupportCards(t,n,C),u=this.buildDiscoveredSupportCards(t,n,C),m=this.buildPracticeCards({group:e,courseId:n,emittedIds:C,cardsByTag:b,hierarchyConfigs:l,userTagElo:p,userGlobalElo:d,activeIds:a,seenIds:s,priorPracticeDebt:T,nextPracticeDebt:E});S.push(...r,...o,...u,...m)}x.practiceDebt=E;let D=this.buildSupportHintSummary(w),O=Object.keys(D.boostTags).length>0?{boostTags:D.boostTags,_label:`prescribed-support (${D.supportTags.length} tags; blocked=${D.blockedTargetIds.length}; testversion=${PRESCRIBED_DEBUG_VERSION})`}:void 0;if(O){let e=Object.entries(O.boostTags??{});logger.info(`[Prescribed] Emitting ${e.length} boost hint(s): `+e.map(([e,t])=>`${e}\xD7${t.toFixed(1)}`).join(`, `))}else logger.info(`[Prescribed] No hints to emit (no blocked targets or no support tags)`);if(S.length===0)return logger.info(`[Prescribed] 0 cards emitted (all targets blocked, authored/discovered support candidates exhausted)`+(O?` — boost hints emitted but may not survive filters`:``)),await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist empty-state update: ${e}`)}),O?{cards:[],hints:O}:{cards:[]};let k=pickTopByScore(S,e),A=new Map;for(let e of k){let t=e.provenance[0];if(t?.reason.includes(`mode=practice`))continue;let n=t?.reason.match(/group=([^;]+)/)?.[1],r=t?.reason.includes(`mode=support`)?`supportIds`:`targetIds`;n&&(A.has(n)||A.set(n,{targetIds:[],supportIds:[]}),A.get(n)[r].push(e.cardId))}for(let e of this.config.groups){let t=x.groups[e.id],n=A.get(e.id);n&&(n.targetIds.length>0||n.supportIds.length>0)&&(t.lastSurfacedAt=isoNow(),t.sessionsSinceSurfaced=0,n.supportIds.length>0&&(t.lastSupportAt=isoNow()))}return await this.putStrategyState(x).catch(e=>{logger.debug(`[Prescribed] Failed to persist prescribed progress: ${e}`)}),logger.info(`[Prescribed] Emitting ${k.length} cards (${k.filter(e=>e.provenance[0]?.reason.includes(`mode=target`)).length} target, ${k.filter(e=>e.provenance[0]?.reason.includes(`mode=support`)).length} support, ${k.filter(e=>e.provenance[0]?.reason.includes(`mode=discovered-support`)).length} discovered support)`),O?{cards:k,hints:O}:{cards:k}}buildSupportHintSummary(e){let t={},n=new Set,r=new Set;for(let a of e)if(!(a.blockedTargets.length===0||a.supportTags.length===0)){a.blockedTargets.forEach(e=>n.add(e));for(let e of a.supportTags)r.add(e),t[e]=(t[e]??1)*a.supportMultiplier}return{boostTags:t,blockedTargetIds:[...n].sort(),supportTags:[...r].sort()}}parseConfig(e){try{let t=JSON.parse(e);return{groups:(Array.isArray(t.groups)?t.groups:[]).map((e,t)=>({id:typeof e.id==`string`&&e.id.trim().length>0?e.id:`group-${t+1}`,targetCardIds:dedupe(Array.isArray(e.targetCardIds)?e.targetCardIds.filter(e=>typeof e==`string`):[]),supportCardIds:dedupe(Array.isArray(e.supportCardIds)?e.supportCardIds.filter(e=>typeof e==`string`):[]),supportTagPatterns:dedupe(Array.isArray(e.supportTagPatterns)?e.supportTagPatterns.filter(e=>typeof e==`string`):[]),freshnessWindowSessions:typeof e.freshnessWindowSessions==`number`?e.freshnessWindowSessions:DEFAULT_FRESHNESS_WINDOW,maxDirectTargetsPerRun:typeof e.maxDirectTargetsPerRun==`number`?e.maxDirectTargetsPerRun:DEFAULT_MAX_DIRECT_PER_RUN,maxSupportCardsPerRun:typeof e.maxSupportCardsPerRun==`number`?e.maxSupportCardsPerRun:DEFAULT_MAX_SUPPORT_PER_RUN,hierarchyWalk:{enabled:e.hierarchyWalk?.enabled!==!1,maxDepth:typeof e.hierarchyWalk?.maxDepth==`number`?e.hierarchyWalk.maxDepth:DEFAULT_HIERARCHY_DEPTH},retireOnEncounter:e.retireOnEncounter!==!1,practiceTagPatterns:dedupe(Array.isArray(e.practiceTagPatterns)?e.practiceTagPatterns.filter(e=>typeof e==`string`):[]),practiceMinCount:typeof e.practiceMinCount==`number`?e.practiceMinCount:DEFAULT_PRACTICE_MIN_COUNT,maxPracticeCardsPerRun:typeof e.maxPracticeCardsPerRun==`number`?e.maxPracticeCardsPerRun:DEFAULT_MAX_PRACTICE_PER_RUN})).filter(e=>e.targetCardIds.length>0)}}catch{return{groups:[]}}}async loadHierarchyConfigs(){try{return(await this.course.getAllNavigationStrategies()).filter(e=>e.implementingClass===`hierarchyDefinition`).map(e=>{try{return{prerequisites:JSON.parse(e.serializedData).prerequisites||{}}}catch{return{prerequisites:{}}}})}catch(e){return logger.debug(`[Prescribed] Failed to load hierarchy configs: ${e}`),[]}}buildGroupRuntimeState(e){let{group:t,priorState:n,activeIds:r,seenIds:a,tagsByCard:o,cardsByTag:s,hierarchyConfigs:c,userTagElo:l,userGlobalElo:u}=e,d=new Set;for(let e of t.targetCardIds)(r.has(e)||a.has(e))&&d.add(e);if(n?.encounteredCardIds?.length)for(let e of n.encounteredCardIds)d.add(e);let p=t.targetCardIds.filter(e=>!d.has(e)),m=new Map;for(let e of p)m.set(e,o.get(e)??[]);let g=[],_=[],v=new Set;for(let e of p){let n=m.get(e)??[],r=this.resolveBlockedSupportTags(n,c,l,u,t.hierarchyWalk?.enabled!==!1,t.hierarchyWalk?.maxDepth??DEFAULT_HIERARCHY_DEPTH),a=n.filter(e=>e.startsWith(`gpc:intro:`)),o=new Set(n.filter(e=>e.startsWith(`gpc:expose:`)));for(let e of a){let t=e.slice(10);t&&o.add(`gpc:expose:${t}`)}let s=[...o].filter(e=>{let t=l[e];return!t||t.count<DEFAULT_MIN_COUNT});s.length>0&&s.forEach(e=>v.add(e)),r.blocked||s.length>0?(g.push(e),r.supportTags.forEach(e=>v.add(e))):_.push(e)}let y=dedupe([...t.supportCardIds??[],...this.findSupportCardsByTags(t,o,[...v])]).filter(e=>!r.has(e)&&!a.has(e)),b=g.length>0&&v.size>0&&y.length===0?this.findDiscoveredSupportCards({supportTags:[...v],cardsByTag:s,activeIds:r,seenIds:a,excludedIds:new Set([...t.targetCardIds,...t.supportCardIds??[]]),limit:t.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN}):[];g.length>0&&v.size>0&&b.length===0&&logger.info(`[Prescribed] Group '${t.id}' discovered 0 broader support candidates (blocked=${g.length}; authoredSupport=${y.length})`);let x=n?.sessionsSinceSurfaced??0,S=t.freshnessWindowSessions??DEFAULT_FRESHNESS_WINDOW,C=Math.max(0,x-S),w=p.length===0?1:clamp(1+C*.75+Math.min(2,p.length*.1),1,MAX_TARGET_MULTIPLIER),T=g.length===0?1:clamp(1+C*.5+Math.min(1.5,g.length*.15),1,MAX_SUPPORT_MULTIPLIER);return{group:t,encounteredTargets:d,pendingTargets:p,blockedTargets:g,surfaceableTargets:_,targetTags:m,supportCandidates:y,discoveredSupportCandidates:b,supportTags:[...v],pressureMultiplier:w,supportMultiplier:T,debugVersion:PRESCRIBED_DEBUG_VERSION}}buildNextGroupState(e,t){let n=t?.sessionsSinceSurfaced??0,r=!1;return{encounteredCardIds:[...e.encounteredTargets].sort(),pendingTargetIds:[...e.pendingTargets].sort(),lastSurfacedAt:t?.lastSurfacedAt??null,sessionsSinceSurfaced:n+1,lastSupportAt:t?.lastSupportAt??null,blockedTargetIds:[...e.blockedTargets].sort(),lastResolvedSupportTags:[...e.supportTags].sort()}}buildDirectTargetCards(e,t,n){let r=e.group.maxDirectTargetsPerRun??DEFAULT_MAX_DIRECT_PER_RUN,a=e.surfaceableTargets.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:BASE_TARGET_SCORE*e.pressureMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_TARGET_SCORE*e.pressureMultiplier,reason:`mode=target;group=${e.group.id};pending=${e.pendingTargets.length};surfaceable=${e.surfaceableTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.pressureMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildSupportCards(e,t,n){if(e.blockedTargets.length===0||e.supportCandidates.length===0)return[];let r=e.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,a=e.supportCandidates.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:BASE_SUPPORT_SCORE*e.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:BASE_SUPPORT_SCORE*e.supportMultiplier,reason:`mode=support;group=${e.group.id};pending=${e.pendingTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportCard=${r};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.supportMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildDiscoveredSupportCards(e,t,n){if(e.blockedTargets.length===0||e.discoveredSupportCandidates.length===0)return[];let r=e.group.maxSupportCardsPerRun??DEFAULT_MAX_SUPPORT_PER_RUN,a=e.discoveredSupportCandidates.filter(e=>!n.has(e)).slice(0,r),o=[];for(let r of a)n.add(r),o.push({cardId:r,courseId:t,score:DISCOVERED_SUPPORT_SCORE*e.supportMultiplier,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:DISCOVERED_SUPPORT_SCORE*e.supportMultiplier,reason:`mode=discovered-support;group=${e.group.id};pending=${e.pendingTargets.length};blocked=${e.blockedTargets.length};blockedTargets=${e.blockedTargets.join(`|`)||`none`};supportCard=${r};supportTags=${e.supportTags.join(`|`)||`none`};multiplier=${e.supportMultiplier.toFixed(2)};testversion=${e.debugVersion}`}]});return o}buildPracticeCards(e){let{group:t,courseId:n,emittedIds:r,cardsByTag:a,hierarchyConfigs:o,userTagElo:s,userGlobalElo:c,activeIds:l,seenIds:u,priorPracticeDebt:d,nextPracticeDebt:p}=e,m=t.practiceTagPatterns??[];if(m.length===0)return[];let g=t.practiceMinCount??DEFAULT_PRACTICE_MIN_COUNT,_=t.maxPracticeCardsPerRun??DEFAULT_MAX_PRACTICE_PER_RUN,v=[...a.keys()].filter(e=>m.some(t=>matchesTagPattern(e,t))&&this.isUnlockedGatedSkill(e,o,s,c)&&(s[e]?.count??0)<g);if(v.length===0)return[];let y=Date.now(),b=1440*60*1e3,x=new Map;for(let e of v){let t=d[e]??isoNow();p[e]=t;let n=Math.max(0,(y-new Date(t).getTime())/864e5),r=clamp(PRACTICE_BASE_MULT+n*PRACTICE_STALENESS_BUMP_PER_DAY,PRACTICE_BASE_MULT,MAX_PRACTICE_MULTIPLIER);x.set(e,r)}let S=this.findDiscoveredSupportCards({supportTags:v,cardsByTag:a,activeIds:l,seenIds:u,excludedIds:r,limit:_});if(S.length===0)return[];logger.info(`[Prescribed] Group '${t.id}' practice: ${v.length} unlocked under-practiced skill(s), emitting ${S.length} drill card(s)`);let C=[];for(let e of S){r.add(e);let o=PRACTICE_BASE_MULT;for(let t of v)(a.get(t)?.includes(e)??!1)&&(o=Math.max(o,x.get(t)??PRACTICE_BASE_MULT));let s=BASE_PRACTICE_SCORE*o;C.push({cardId:e,courseId:n,score:s,provenance:[{strategy:`prescribed`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-prescribed`,action:`generated`,score:s,reason:`mode=practice;group=${t.id};debtMult=\xD7${o.toFixed(2)};underPracticedSkills=${v.length};practiceTags=${v.slice(0,8).join(`|`)}${v.length>8?`|…`:``};testversion=${PRESCRIBED_DEBUG_VERSION}`}]})}return C}isUnlockedGatedSkill(e,t,n,r){let a=t.map(t=>t.prerequisites[e]).filter(e=>Array.isArray(e)&&e.length>0);return a.length===0?!1:a.every(e=>e.every(e=>this.isPrerequisiteMet(e,n[e.tag],r)))}findSupportCardsByTags(e,t,n){if(n.length===0)return[];let r=e.supportCardIds??[],a=e.supportTagPatterns??[];if(r.length===0&&a.length===0)return[];let o=new Set;for(let e of r){let r=t.get(e)??[],s=n.some(e=>r.includes(e)),c=a.some(e=>r.some(t=>matchesTagPattern(t,e)));(s||c)&&o.add(e)}return[...o]}findDiscoveredSupportCards(e){let{supportTags:t,cardsByTag:n,activeIds:r,seenIds:a,excludedIds:o,limit:s}=e,c=new Map;for(let e of t){let t=n.get(e)??[];for(let e of t){if(r.has(e)||a.has(e)||o.has(e))continue;let t=c.get(e);t?t.matches+=1:c.set(e,{cardId:e,matches:1})}}let l=[...c.values()].sort((e,t)=>t.matches-e.matches||e.cardId.localeCompare(t.cardId)),u=new Set,d=[],p=[];for(let e of l){let t=extractWordStem(e.cardId);u.has(t)?p.push(e):(u.add(t),d.push(e))}return shuffleInPlace(d),shuffleInPlace(p),[...d,...p].slice(0,s).map(e=>e.cardId)}resolveBlockedSupportTags(e,t,n,r,a,o){let s=new Set,c=!1;for(let l of e){let e=t.map(e=>e.prerequisites[l]).filter(e=>Array.isArray(e)&&e.length>0);if(e.length!==0&&e.some(e=>e.some(e=>!this.isPrerequisiteMet(e,n[e.tag],r)))){if(c=!0,!a){for(let t of e)for(let e of t)this.isPrerequisiteMet(e,n[e.tag],r)||s.add(e.tag);continue}for(let a of e)for(let e of a)this.isPrerequisiteMet(e,n[e.tag],r)||this.collectSupportTagsRecursive(e.tag,t,n,r,o,new Set,s)}}return{blocked:c,supportTags:[...s]}}collectSupportTagsRecursive(e,t,n,r,a,o,s){if(a<0||o.has(e))return;o.add(e);let c=!1;for(let l of t){let u=l.prerequisites[e];if(!u||u.length===0)continue;let d=u.filter(e=>!this.isPrerequisiteMet(e,n[e.tag],r));if(d.length>0&&a>0){c=!0;for(let e of d)this.collectSupportTagsRecursive(e.tag,t,n,r,a-1,o,s)}}c||s.add(e)}isPrerequisiteMet(e,t,n){if(!t)return!1;let r=e.masteryThreshold?.minCount??DEFAULT_MIN_COUNT;return t.count<r?!1:e.masteryThreshold?.minElo===void 0?e.masteryThreshold?.minCount===void 0?t.score>=n:!0:t.score>=e.masteryThreshold.minElo}}}}),srs_exports={};__export(srs_exports,{default:()=>SRSNavigator});var DEFAULT_HEALTHY_BACKLOG,MAX_BACKLOG_MULTIPLIER,SRSNavigator,init_srs=__esm({"src/core/navigators/generators/srs.ts"(){init_navigators(),init_SrsDebugger(),init_logger(),DEFAULT_HEALTHY_BACKLOG=20,MAX_BACKLOG_MULTIPLIER=2,SRSNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`name`,void 0),_defineProperty(this,`healthyBacklog`,void 0),this.name=n?.name||`SRS`,this.healthyBacklog=this.parseConfig(n?.serializedData).healthyBacklog??DEFAULT_HEALTHY_BACKLOG}parseConfig(e){if(!e)return{};try{return JSON.parse(e)}catch{return logger.warn(`[SRS] Failed to parse strategy config, using defaults`),{}}}async getWeightedCards(e,t){if(!this.user||!this.course)throw Error(`SRSNavigator requires user and course to be set`);let n=this.course.getCourseID(),r=await this.user.getPendingReviews(n),a=hooks.utc(),o=r.filter(e=>a.isAfter(hooks.utc(e.reviewTime)));if(o.length>0){let e=[...new Set(o.map(e=>e.cardId))],t=await this.course.getAppliedTagsBatch(e),n=[];if(o=o.filter(e=>(t.get(e.cardId)??[]).includes(`srs:skip`)?(n.push(e._id),!1):!0),n.length>0){logger.info(`[SRS] Removing ${n.length} scheduled reviews for srs:skip cards`);for(let e of n)this.user.removeScheduledCardReview(e)}}let s=this.computeBacklogMultiplier(o.length),c=r.filter(e=>!a.isAfter(hooks.utc(e.reviewTime))),l=null;if(c.length>0){let e=c.reduce((e,t)=>hooks.utc(e.reviewTime).isBefore(hooks.utc(t.reviewTime))?e:t),t=hooks.duration(hooks.utc(e.reviewTime).diff(a));l=t.asHours()<1?`${Math.round(t.asMinutes())}m`:t.asHours()<24?`${Math.round(t.asHours())}h`:`${Math.round(t.asDays())}d`}if(o.length>0){let e=s>1?` [backlog pressure: \xD7${s.toFixed(2)}]`:` [healthy backlog]`;logger.info(`[SRS] Course ${n}: ${o.length} reviews due now (of ${r.length} scheduled)${e}`)}else if(r.length>0){let e=[...r].sort((e,t)=>hooks.utc(e.reviewTime).diff(hooks.utc(t.reviewTime)))[0],t=hooks.utc(e.reviewTime),o=hooks.duration(t.diff(a)),s=o.asHours()<1?`${Math.round(o.asMinutes())}m`:o.asHours()<24?`${Math.round(o.asHours())}h`:`${Math.round(o.asDays())}d`;logger.info(`[SRS] Course ${n}: 0 reviews due now (${r.length} scheduled, next in ${s})`)}else logger.info(`[SRS] Course ${n}: No reviews scheduled`);let u=o.map(e=>{let{score:t,reason:n}=this.computeUrgencyScore(e,a,s);return{cardId:e.cardId,courseId:e.courseId,score:t,reviewID:e._id,provenance:[{strategy:`srs`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-SRS-default`,action:`generated`,score:t,reason:n}]}}).sort((e,t)=>t.score-e.score);return captureSrsBacklog({courseId:n,scheduledTotal:r.length,dueNow:o.length,healthyBacklog:this.healthyBacklog,backlogMultiplier:s,maxBacklogMultiplier:MAX_BACKLOG_MULTIPLIER,topReviewScore:u.length>0?u[0].score:null,nextDueIn:l,timestamp:Date.now()}),{cards:u.slice(0,e)}}computeBacklogMultiplier(e){if(e<=this.healthyBacklog)return 1;let t=1+(e-this.healthyBacklog)/this.healthyBacklog*((MAX_BACKLOG_MULTIPLIER-1)/2);return Math.min(MAX_BACKLOG_MULTIPLIER,t)}computeUrgencyScore(e,t,n){let r=hooks.utc(e.scheduledAt),a=hooks.utc(e.reviewTime),o=Math.max(1,a.diff(r,`hours`)),s=t.diff(a,`hours`),c=s/o,l=.3+.7*Math.exp(-o/720),u=(.5+(Math.min(1,Math.max(0,c))*.5+l*.5)*.45)*n,d=[`${Math.round(s)}h overdue`,`interval: ${Math.round(o)}h`,`relative: ${c.toFixed(2)}`,`recency: ${l.toFixed(2)}`];return n>1&&d.push(`backlog: \xD7${n.toFixed(2)}`),d.push(`review`),{score:u,reason:d.join(`, `)}}}}}),types_exports={},init_types=__esm({"src/core/navigators/generators/types.ts"(){}}),globImport_generators,init_=__esm({'import("./generators/**/*") in src/core/navigators/index.ts'(){globImport_generators=__glob({"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports))})}}),DEFAULT_LEARNABLE_WEIGHT,init_contentNavigationStrategy=__esm({"src/core/types/contentNavigationStrategy.ts"(){DEFAULT_LEARNABLE_WEIGHT={weight:1,confidence:.1,sampleSize:0}}}),WeightedFilter_exports={};__export(WeightedFilter_exports,{WeightedFilter:()=>WeightedFilter});var WeightedFilter,init_WeightedFilter=__esm({"src/core/navigators/filters/WeightedFilter.ts"(){init_contentNavigationStrategy(),WeightedFilter=class{constructor(e,t=DEFAULT_LEARNABLE_WEIGHT,n=!1,r){_defineProperty(this,`name`,void 0),_defineProperty(this,`inner`,void 0),_defineProperty(this,`learnable`,void 0),_defineProperty(this,`staticWeight`,void 0),_defineProperty(this,`strategyId`,void 0),this.inner=e,this.name=e.name,this.learnable=t,this.staticWeight=n,this.strategyId=r}async transform(e,t){let n=this.learnable.weight,r;if(!this.staticWeight&&t.orchestration){let e=this.strategyId||this.inner.strategyId||this.name;n=t.orchestration.getEffectiveWeight(e,this.learnable),r=t.orchestration.getDeviation(e)}if(Math.abs(n-1)<.001)return this.inner.transform(e,t);let a=new Map;for(let t of e)a.set(t.cardId,t.score);return(await this.inner.transform(e,t)).map(e=>{let t=a.get(e.cardId);if(t===void 0||t===0||e.score===0)return e;let o=e.score/t;if(Math.abs(o-1)<1e-4)return e;let s=t*o**+n,c=e.provenance.length-1,l=e.provenance[c];if(l){let t=[...e.provenance];return t[c]={...l,score:s,effectiveWeight:n,deviation:r},{...e,score:s,provenance:t}}return{...e,score:s}})}}}}),eloDistance_exports={};__export(eloDistance_exports,{DEFAULT_HALF_LIFE:()=>DEFAULT_HALF_LIFE,DEFAULT_MAX_MULTIPLIER:()=>DEFAULT_MAX_MULTIPLIER,DEFAULT_MIN_MULTIPLIER:()=>DEFAULT_MIN_MULTIPLIER,createEloDistanceFilter:()=>createEloDistanceFilter});function computeMultiplier(e,t,n,r){let a=e/t,o=Math.exp(-(a*a));return n+(r-n)*o}function createEloDistanceFilter(e){let t=e?.halfLife??DEFAULT_HALF_LIFE,n=e?.minMultiplier??DEFAULT_MIN_MULTIPLIER,r=e?.maxMultiplier??DEFAULT_MAX_MULTIPLIER;return{name:`ELO Distance Filter`,async transform(e,a){let{course:o,userElo:s}=a,c=e.map(e=>e.cardId),l=await o.getCardEloData(c);return e.map((e,a)=>{let o=l[a]?.global?.score??1e3,c=Math.abs(o-s),u=computeMultiplier(c,t,n,r),d=e.score*u,p=u<r-.01?`penalized`:`passed`;return{...e,score:d,provenance:[...e.provenance,{strategy:`eloDistance`,strategyName:`ELO Distance Filter`,strategyId:`ELO_DISTANCE_FILTER`,action:p,score:d,reason:`ELO distance ${Math.round(c)} (card: ${Math.round(o)}, user: ${Math.round(s)}) \u2192 ${u.toFixed(2)}x`}]}})}}}var DEFAULT_HALF_LIFE,DEFAULT_MIN_MULTIPLIER,DEFAULT_MAX_MULTIPLIER,init_eloDistance=__esm({"src/core/navigators/filters/eloDistance.ts"(){DEFAULT_HALF_LIFE=200,DEFAULT_MIN_MULTIPLIER=.3,DEFAULT_MAX_MULTIPLIER=1}}),hierarchyDefinition_exports={};__export(hierarchyDefinition_exports,{default:()=>HierarchyDefinitionNavigator});var DEFAULT_MIN_COUNT2,HierarchyDefinitionNavigator,init_hierarchyDefinition=__esm({"src/core/navigators/filters/hierarchyDefinition.ts"(){init_navigators(),init_logger(),DEFAULT_MIN_COUNT2=3,HierarchyDefinitionNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),this.config=this.parseConfig(n.serializedData),this.name=n.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??DEFAULT_MIN_COUNT2;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 t=new Set;try{let n=toCourseElo((await e.user.getCourseRegDoc(e.course.getCourseID())).elo);for(let e of Object.values(this.config.prerequisites))for(let r of e){let e=n.tags[r.tag];this.isPrerequisiteMet(r,e,n.global.score)&&t.add(r.tag)}}catch{}return t}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??[],a=t.filter(e=>this.hasPrerequisites(e)&&!n.has(e));return a.length===0?{isUnlocked:!0,reason:`Prerequisites met, tags: ${t.length>0?t.join(`, `):`none`}`}:{isUnlocked:!1,reason:`Blocked: missing prerequisites ${a.flatMap(e=>(this.config.prerequisites[e]||[]).filter(e=>!r.has(e.tag)).map(e=>e.tag)).join(`, `)} for tags ${a.join(`, `)}`}}catch{return{isUnlocked:!0,reason:`Prerequisites check skipped (tag lookup failed)`}}}getPreReqBoosts(e,t){let n=new Map;for(let[r,a]of Object.entries(this.config.prerequisites))if(!e.has(r))for(let e of a){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,n=Object.keys(this.config.prerequisites),r=[...e];logger.info(`[HierarchyDefinition:targetBoost:trace] ${this.name} | configKeys=${n.length}, unlocked=${r.length} (${r.slice(0,5).join(`, `)}${r.length>5?`...`:``})`);for(let[n,r]of Object.entries(this.config.prerequisites))if(e.has(n)){logger.info(`[HierarchyDefinition:targetBoost:trace] UNLOCKED ${n}: ${r.length} prereqs, raw=${JSON.stringify(r.map(e=>({tag:e.tag,tb:e.targetBoost})))}`);for(let e of r){if(!e.targetBoost||e.targetBoost<=1)continue;let r=t.get(n)??1;t.set(n,Math.max(r,e.targetBoost))}}return t.size>0?logger.info(`[HierarchyDefinition] targetBoosts active: ${[...t.entries()].map(([e,t])=>`${e}=\xD7${t}`).join(`, `)}`):logger.info(`[HierarchyDefinition:targetBoost:trace] no targetBoosts found despite ${r.length} unlocked tags`),t}async transform(e,t){let n=await this.getMasteredTags(t),r=this.getUnlockedTags(n),a=this.getPreReqBoosts(r,n),o=this.getTargetBoosts(r),s=[];for(let c of e){let{isUnlocked:e,reason:l}=await this.checkCardUnlock(c,t.course,r,n),u=e?c.score:c.score*.02,d=e?`passed`:`penalized`,p=l;if(e&&a.size>0){let e=c.tags??[],t=1,n=[];for(let r of e){let e=a.get(r);e&&e>t&&(t=e,n.push(r))}t>1&&(u*=t,d=`boosted`,p=`${l} | preReqBoost \xD7${t.toFixed(2)} for ${n.join(`, `)}`,logger.info(`[HierarchyDefinition] preReqBoost \xD7${t.toFixed(2)} applied to card ${c.cardId} via tags [${n.join(`, `)}] (score: ${c.score.toFixed(3)} \u2192 ${u.toFixed(3)})`))}if(e&&o.size>0){let e=c.tags??[],t=1,n=[];for(let r of e){let e=o.get(r);e&&e>t&&(t=e,n.push(r))}t>1&&(u*=t,d=`boosted`,p=`${p} | targetBoost \xD7${t.toFixed(2)} for ${n.join(`, `)}`,logger.info(`[HierarchyDefinition] targetBoost \xD7${t.toFixed(2)} applied to card ${c.cardId} via tags [${n.join(`, `)}] (score: ${c.score.toFixed(3)} \u2192 ${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:p}]})}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().`)}}}}),userTagPreference_exports={};__export(userTagPreference_exports,{default:()=>UserTagPreferenceFilter});var UserTagPreferenceFilter,init_userTagPreference=__esm({"src/core/navigators/filters/userTagPreference.ts"(){init_navigators(),UserTagPreferenceFilter=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`_strategyData`,void 0),_defineProperty(this,`name`,void 0),this._strategyData=n,this.name=n.name||`User Tag Preferences`}computeMultiplier(e,t){let n=e.map(e=>t[e]).filter(e=>e!==void 0);return n.length===0?1:Math.max(...n)}buildReason(e,t,n){let r=e.filter(e=>t[e]===n);return n===0?`Excluded by user preference: ${r.join(`, `)} (${n}x)`:n<1?`Penalized by user preference: ${r.join(`, `)} (${n.toFixed(2)}x)`:n>1?`Boosted by user preference: ${r.join(`, `)} (${n.toFixed(2)}x)`:`No matching user preferences`}async transform(e,t){let n=await this.getStrategyState();return!n||Object.keys(n.boost).length===0?e.map(e=>({...e,provenance:[...e.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:`passed`,score:e.score,reason:`No user tag preferences configured`}]})):await Promise.all(e.map(async e=>{let t=e.tags??[],r=this.computeMultiplier(t,n.boost),a=Math.min(1,e.score*r),o;return o=r===0||r<1?`penalized`:r>1?`boosted`:`passed`,{...e,score:a,provenance:[...e.provenance,{strategy:`userTagPreference`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||this._strategyData._id,action:o,score:a,reason:this.buildReason(t,n.boost,r)}]}}))}async getWeightedCards(e){throw Error(`UserTagPreferenceFilter is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),filters_exports={};__export(filters_exports,{UserTagPreferenceFilter:()=>UserTagPreferenceFilter,createEloDistanceFilter:()=>createEloDistanceFilter});var init_filters=__esm({"src/core/navigators/filters/index.ts"(){init_eloDistance(),init_userTagPreference()}}),inferredPreferenceStub_exports={};__export(inferredPreferenceStub_exports,{INFERRED_PREFERENCE_NAVIGATOR_STUB:()=>INFERRED_PREFERENCE_NAVIGATOR_STUB});var INFERRED_PREFERENCE_NAVIGATOR_STUB,init_inferredPreferenceStub=__esm({"src/core/navigators/filters/inferredPreferenceStub.ts"(){INFERRED_PREFERENCE_NAVIGATOR_STUB=!0}}),interferenceMitigator_exports={};__export(interferenceMitigator_exports,{default:()=>InterferenceMitigatorNavigator});var DEFAULT_MIN_COUNT3,DEFAULT_MIN_ELAPSED_DAYS,DEFAULT_INTERFERENCE_DECAY,InterferenceMitigatorNavigator,init_interferenceMitigator=__esm({"src/core/navigators/filters/interferenceMitigator.ts"(){init_navigators(),DEFAULT_MIN_COUNT3=10,DEFAULT_MIN_ELAPSED_DAYS=3,DEFAULT_INTERFERENCE_DECAY=.8,InterferenceMitigatorNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),_defineProperty(this,`interferenceMap`,void 0),this.config=this.parseConfig(n.serializedData),this.interferenceMap=this.buildInterferenceMap(),this.name=n.name||`Interference Mitigator`}parseConfig(e){try{let t=JSON.parse(e),n=t.interferenceSets||[];return n.length>0&&Array.isArray(n[0])&&(n=n.map(e=>({tags:e}))),{interferenceSets:n,maturityThreshold:{minCount:t.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,minElo:t.maturityThreshold?.minElo,minElapsedDays:t.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:t.defaultDecay??DEFAULT_INTERFERENCE_DECAY}}catch{return{interferenceSets:[],maturityThreshold:{minCount:DEFAULT_MIN_COUNT3,minElapsedDays:DEFAULT_MIN_ELAPSED_DAYS},defaultDecay:DEFAULT_INTERFERENCE_DECAY}}}buildInterferenceMap(){let e=new Map;for(let t of this.config.interferenceSets){let n=t.decay??this.config.defaultDecay??DEFAULT_INTERFERENCE_DECAY;for(let r of t.tags){e.has(r)||e.set(r,[]);let a=e.get(r);for(let e of t.tags)if(e!==r){let t=a.find(t=>t.partner===e);t?t.decay=Math.max(t.decay,n):a.push({partner:e,decay:n})}}}return e}async getImmatureTags(e){let t=new Set;try{let n=toCourseElo((await e.user.getCourseRegDoc(e.course.getCourseID())).elo),r=this.config.maturityThreshold?.minCount??DEFAULT_MIN_COUNT3,a=this.config.maturityThreshold?.minElo,o=(this.config.maturityThreshold?.minElapsedDays??DEFAULT_MIN_ELAPSED_DAYS)*2;for(let[e,s]of Object.entries(n.tags)){if(s.count===0)continue;let n=s.count<r,c=a!==void 0&&s.score<a,l=s.count<o;(n||c||l)&&t.add(e)}}catch{}return t}getTagsToAvoid(e){let t=new Map;for(let n of e){let r=this.interferenceMap.get(n);if(r){for(let{partner:n,decay:a}of r)if(!e.has(n)){let e=t.get(n)??0;t.set(n,Math.max(e,a))}}}return t}computeInterferenceEffect(e,t,n){if(t.size===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let r=1,a=[];for(let n of e){let e=t.get(n);e!==void 0&&(a.push(n),r*=1-e)}if(a.length===0)return{multiplier:1,interferingTags:[],reason:`No interference detected`};let o=new Set;for(let e of a)for(let t of n)this.interferenceMap.get(t)?.some(t=>t.partner===e)&&o.add(t);let s=`Interferes with immature tags ${Array.from(o).join(`, `)} (tags: ${a.join(`, `)}, multiplier: ${r.toFixed(2)})`;return{multiplier:r,interferingTags:a,reason:s}}async transform(e,t){let n=await this.getImmatureTags(t),r=this.getTagsToAvoid(n),a=[];for(let t of e){let e=t.tags??[],{multiplier:o,reason:s}=this.computeInterferenceEffect(e,r,n),c=t.score*o,l=o<1?`penalized`:o>1?`boosted`:`passed`;a.push({...t,score:c,provenance:[...t.provenance,{strategy:`interferenceMitigator`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-interference`,action:l,score:c,reason:s}]})}return a}async getWeightedCards(e){throw Error(`InterferenceMitigatorNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),relativePriority_exports={};__export(relativePriority_exports,{default:()=>RelativePriorityNavigator});var DEFAULT_PRIORITY,DEFAULT_PRIORITY_INFLUENCE,DEFAULT_COMBINE_MODE,RelativePriorityNavigator,init_relativePriority=__esm({"src/core/navigators/filters/relativePriority.ts"(){init_navigators(),DEFAULT_PRIORITY=.5,DEFAULT_PRIORITY_INFLUENCE=.5,DEFAULT_COMBINE_MODE=`max`,RelativePriorityNavigator=class extends ContentNavigator{constructor(e,t,n){super(e,t,n),_defineProperty(this,`config`,void 0),_defineProperty(this,`name`,void 0),this.config=this.parseConfig(n.serializedData),this.name=n.name||`Relative Priority`}parseConfig(e){try{let t=JSON.parse(e);return{tagPriorities:t.tagPriorities||{},defaultPriority:t.defaultPriority??DEFAULT_PRIORITY,combineMode:t.combineMode??DEFAULT_COMBINE_MODE,priorityInfluence:t.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE}}catch{return{tagPriorities:{},defaultPriority:DEFAULT_PRIORITY,combineMode:DEFAULT_COMBINE_MODE,priorityInfluence:DEFAULT_PRIORITY_INFLUENCE}}}getTagPriority(e){return this.config.tagPriorities[e]??this.config.defaultPriority??DEFAULT_PRIORITY}computeCardPriority(e){if(e.length===0)return this.config.defaultPriority??DEFAULT_PRIORITY;let t=e.map(e=>this.getTagPriority(e));switch(this.config.combineMode){case`max`:return Math.max(...t);case`min`:return Math.min(...t);case`average`:return t.reduce((e,t)=>e+t,0)/t.length;default:return Math.max(...t)}}computeBoostFactor(e){let t=this.config.priorityInfluence??DEFAULT_PRIORITY_INFLUENCE;return 1+(e-.5)*t}buildPriorityReason(e,t,n,r){if(e.length===0)return`No tags, neutral priority (${t.toFixed(2)})`;let a=e.slice(0,3).join(`, `),o=e.length>3?` (+${e.length-3} more)`:``;return n===1?`Neutral priority (${t.toFixed(2)}) for tags: ${a}${o}`:n>1?`High-priority tags: ${a}${o} (priority ${t.toFixed(2)} \u2192 boost ${n.toFixed(2)}x \u2192 ${r.toFixed(2)})`:`Low-priority tags: ${a}${o} (priority ${t.toFixed(2)} \u2192 reduce ${n.toFixed(2)}x \u2192 ${r.toFixed(2)})`}async transform(e,t){return await Promise.all(e.map(async e=>{let t=e.tags??[],n=this.computeCardPriority(t),r=this.computeBoostFactor(n),a=Math.max(0,e.score*r),o=r>1?`boosted`:r<1?`penalized`:`passed`,s=this.buildPriorityReason(t,n,r,a);return{...e,score:a,provenance:[...e.provenance,{strategy:`relativePriority`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-priority`,action:o,score:a,reason:s}]}}))}async getWeightedCards(e){throw Error(`RelativePriorityNavigator is a filter and should not be used as a generator. Use Pipeline with a generator and this filter via transform().`)}}}}),types_exports2={},init_types2=__esm({"src/core/navigators/filters/types.ts"(){}}),userGoalStub_exports={};__export(userGoalStub_exports,{USER_GOAL_NAVIGATOR_STUB:()=>USER_GOAL_NAVIGATOR_STUB});var USER_GOAL_NAVIGATOR_STUB,init_userGoalStub=__esm({"src/core/navigators/filters/userGoalStub.ts"(){USER_GOAL_NAVIGATOR_STUB=!0}}),globImport_filters,init_2=__esm({'import("./filters/**/*") in src/core/navigators/index.ts'(){globImport_filters=__glob({"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports))})}});function aggregateOutcomesForGradient(e,t){let n=[];for(let r of e){let e=r.deviations[t];e!==void 0&&n.push({deviation:e,outcomeValue:r.outcomeValue,weight:1})}return logger.debug(`[Orchestration] Aggregated ${n.length} observations for strategy ${t}`),n}function computeStrategyGradient(e){let t=e.length;if(t<3)return logger.debug(`[Orchestration] Insufficient observations for gradient (${t} < 3)`),null;let n=0,r=0,a=0;for(let t of e){let e=t.weight??1;n+=t.deviation*e,r+=t.outcomeValue*e,a+=e}let o=n/a,s=r/a,c=0,l=0,u=0;for(let t of e){let e=t.weight??1,n=t.deviation-o,r=t.outcomeValue-s;c+=e*n*r,l+=e*n*n,u+=e*r*r}if(l<1e-10)return logger.debug(`[Orchestration] No variance in deviations, cannot compute gradient`),{gradient:0,intercept:s,rSquared:0,sampleSize:t};let d=c/l,p=s-d*o,m=0;for(let t of e){let e=t.weight??1,n=d*t.deviation+p,r=t.outcomeValue-n;m+=e*r*r}let g=u>1e-10?1-m/u:0;return logger.debug(`[Orchestration] Computed gradient: ${d.toFixed(4)}, intercept: ${p.toFixed(4)}, R\xB2: ${g.toFixed(4)}, n=${t}`),{gradient:d,intercept:p,rSquared:Math.max(0,Math.min(1,g)),sampleSize:t}}var init_gradient=__esm({"src/core/orchestration/gradient.ts"(){init_logger()}});function updateStrategyWeight(e,t){if(t.sampleSize<MIN_OBSERVATIONS_FOR_UPDATE)return logger.debug(`[Orchestration] Insufficient samples (${t.sampleSize} < ${MIN_OBSERVATIONS_FOR_UPDATE}), keeping current weight`),{...e,sampleSize:e.sampleSize+t.sampleSize};let n=t.rSquared>=MIN_R_SQUARED_FOR_GRADIENT,r=Math.abs(t.gradient)<FLAT_GRADIENT_THRESHOLD,a=e.weight,o=e.confidence;if(!n||r){let n=.05*(1-e.confidence);o=Math.min(1,e.confidence+n),logger.debug(`[Orchestration] Flat/unreliable gradient (|g|=${Math.abs(t.gradient).toFixed(4)}, R\xB2=${t.rSquared.toFixed(4)}). Increasing confidence: ${e.confidence.toFixed(3)} \u2192 ${o.toFixed(3)}`)}else{let n=t.gradient*LEARNING_RATE;n=Math.max(-MAX_WEIGHT_DELTA,Math.min(MAX_WEIGHT_DELTA,n)),a=e.weight+n,a=Math.max(.1,Math.min(3,a));let r=.02*(1-e.confidence);o=Math.min(1,e.confidence+r),logger.debug(`[Orchestration] Adjusting weight: ${e.weight.toFixed(3)} \u2192 ${a.toFixed(3)} (gradient=${t.gradient.toFixed(4)}, delta=${n.toFixed(4)})`)}return{weight:a,confidence:o,sampleSize:e.sampleSize+t.sampleSize}}function updateLearningState(e,t,n,r,a){let o=new Date().toISOString(),s=`STRATEGY_LEARNING_STATE::${e}::${t}`,c={timestamp:o,weight:n.weight,confidence:n.confidence,gradient:r.gradient},l=a?.history??[];return l=[...l,c],l.length>MAX_HISTORY_LENGTH&&(l=l.slice(l.length-MAX_HISTORY_LENGTH)),{_id:s,_rev:a?._rev,docType:`STRATEGY_LEARNING_STATE`,courseId:e,strategyId:t,currentWeight:n,regression:{gradient:r.gradient,intercept:r.intercept,rSquared:r.rSquared,sampleSize:r.sampleSize,computedAt:o},history:l,updatedAt:o}}function runPeriodUpdate(e){let{courseId:t,strategyId:n,currentWeight:r,gradient:a,existingState:o}=e;logger.info(`[Orchestration] Running period update for strategy ${n} (${a.sampleSize} observations)`);let s=updateStrategyWeight(r,a),c=s.weight!==r.weight,l=updateLearningState(t,n,s,a,o);return logger.info(`[Orchestration] Period update complete for ${n}: weight ${r.weight.toFixed(3)} \u2192 ${s.weight.toFixed(3)}, confidence ${r.confidence.toFixed(3)} \u2192 ${s.confidence.toFixed(3)}`),{strategyId:n,previousWeight:r,newWeight:s,gradient:a,learningState:l,updated:c}}function getDefaultLearnableWeight(){return{...DEFAULT_LEARNABLE_WEIGHT}}var MIN_OBSERVATIONS_FOR_UPDATE,LEARNING_RATE,MAX_WEIGHT_DELTA,MIN_R_SQUARED_FOR_GRADIENT,FLAT_GRADIENT_THRESHOLD,MAX_HISTORY_LENGTH,init_learning=__esm({"src/core/orchestration/learning.ts"(){init_contentNavigationStrategy(),init_types_legacy(),init_logger(),MIN_OBSERVATIONS_FOR_UPDATE=10,LEARNING_RATE=.1,MAX_WEIGHT_DELTA=.3,MIN_R_SQUARED_FOR_GRADIENT=.05,FLAT_GRADIENT_THRESHOLD=.02,MAX_HISTORY_LENGTH=100}});function computeOutcomeSignal(e,t={}){if(!e||e.length===0)return null;let n=t.targetAccuracy??.85,r=t.tolerance??.05,a=0;for(let t of e)t.isCorrect&&a++;return scoreAccuracyInZone(a/e.length,n,r)}function scoreAccuracyInZone(e,t,n){let r=Math.abs(e-t);if(r<=n)return 1;let a=r-n;return Math.max(0,1-a*2.5)}var init_signal=__esm({"src/core/orchestration/signal.ts"(){}});async function recordUserOutcome(e,t,n,r,a,o=0,s=0,c){let{user:l,course:u,userId:d}=e,p=u.getCourseID(),m=computeOutcomeSignal(r,c);if(m===null){logger.debug(`[Orchestration] No outcome signal computed for ${d} (insufficient data). Skipping record.`);return}let g={};for(let t of a)g[t]=e.getDeviation(t);let _=`USER_OUTCOME::${p}::${d}::${n}`,v={_id:_,docType:`USER_OUTCOME`,courseId:p,userId:d,periodStart:t,periodEnd:n,outcomeValue:m,deviations:g,metadata:{sessionsCount:1,cardsSeen:r.length,eloStart:o,eloEnd:s,signalType:`accuracy_in_zone`}};try{await l.putUserOutcome(v),logger.debug(`[Orchestration] Recorded outcome ${m.toFixed(3)} for ${d} (doc: ${_})`)}catch(e){logger.error(`[Orchestration] Failed to record outcome: ${e}`)}}var init_recording=__esm({"src/core/orchestration/recording.ts"(){init_signal(),init_types_legacy(),init_logger()}});function fnv1a(e){let t=2166136261;for(let n=0;n<e.length;n++)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return t>>>0}function computeDeviation(e,t,n){return fnv1a(`${e}:${t}:${n}`)/4294967296*2-1}function computeSpread(e){return MAX_SPREAD-Math.max(0,Math.min(1,e))*(MAX_SPREAD-MIN_SPREAD)}function computeEffectiveWeight(e,t,n,r){let a=computeDeviation(t,n,r)*computeSpread(e.confidence)*e.weight,o=e.weight+a;return Math.max(MIN_WEIGHT,Math.min(MAX_WEIGHT,o))}async function createOrchestrationContext(e,t){let n;try{n=await t.getCourseConfig()}catch(e){logger.error(`[Orchestration] Failed to load course config: ${e}`),n={name:`Unknown`,description:``,public:!1,deleted:!1,creator:``,admins:[],moderators:[],dataShapes:[],questionTypes:[],orchestration:{salt:`default`}}}let r=e.getUsername(),a=n.orchestration?.salt||`default_salt`;return{user:e,course:t,userId:r,courseConfig:n,getEffectiveWeight(e,t){return computeEffectiveWeight(t,r,e,a)},getDeviation(e){return computeDeviation(r,e,a)}}}var MIN_SPREAD,MAX_SPREAD,MIN_WEIGHT,MAX_WEIGHT,init_orchestration=__esm({"src/core/orchestration/index.ts"(){init_logger(),init_gradient(),init_learning(),init_signal(),init_recording(),MIN_SPREAD=.1,MAX_SPREAD=.5,MIN_WEIGHT=.1,MAX_WEIGHT=3}}),Pipeline_exports={};__export(Pipeline_exports,{Pipeline:()=>Pipeline,mergeHints:()=>mergeHints2});function globToRegex(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\*/g,`.*`);return RegExp(`^${t}$`)}function globMatch(e,t){return t.includes(`*`)?globToRegex(t).test(e):e===t}function cardMatchesTagPattern(e,t){return(e.tags??[]).some(e=>globMatch(e,t))}function mergeHints2(e){let t=e.filter(e=>e!=null);if(t.length===0)return;let n={},r={};for(let e of t)for(let[t,n]of Object.entries(e.boostTags??{}))r[t]=(r[t]??1)*n;Object.keys(r).length>0&&(n.boostTags=r);let a={};for(let e of t)for(let[t,n]of Object.entries(e.boostCards??{}))a[t]=(a[t]??1)*n;Object.keys(a).length>0&&(n.boostCards=a);let concatUnique=e=>{let r=t.flatMap(t=>t[e]??[]);r.length>0&&(n[e]=[...new Set(r)])};concatUnique(`requireTags`),concatUnique(`requireCards`),concatUnique(`excludeTags`),concatUnique(`excludeCards`);let o=t.map(e=>e._label).filter(Boolean);return o.length>0&&(n._label=o.join(`; `)),Object.keys(n).length>0?n:void 0}function logPipelineConfig(e,t){let n=t.length>0?`
|
|
250
250
|
- `+t.map(e=>e.name).join(`
|
|
251
251
|
- `):` none`;logger.info(`[Pipeline] Configuration:
|
|
252
252
|
Generator: ${e.name}
|
|
253
253
|
Filters:${n}`)}function logTagHydration(e,t){let n=Array.from(t.values()).reduce((e,t)=>e+t.length,0),r=Array.from(t.values()).filter(e=>e.length>0).length;logger.debug(`[Pipeline] Tag hydration: ${e.length} cards, ${r} have tags (${n} total tags) - single batch query`)}function logExecutionSummary(e,t,n,r,a,o){let s=a.length>0?a.map(e=>e.toFixed(2)).join(`, `):`none`,c=``;o.length>0&&(c=`
|
|
254
254
|
Filter impact: ${o.map(e=>{let t=[];return e.boosted>0&&t.push(`+${e.boosted}`),e.penalized>0&&t.push(`-${e.penalized}`),e.passed>0&&t.push(`=${e.passed}`),`${e.name}: ${t.join(`/`)}`}).join(`, `)}`),logger.info(`[Pipeline] Execution: ${e} produced ${t} \u2192 ${n} filters \u2192 ${r} results (top scores: ${s})`+c+`
|
|
255
|
-
💡 Inspect: window.skuilder.pipeline`)}function logResultCards(e){if(!(!VERBOSE_RESULTS||e.length===0)){logger.info(`[Pipeline] Results (${e.length} cards):`);for(let t=0;t<e.length;t++){let n=e[t],r=n.tags?.slice(0,3).join(`, `)||``,a=n.provenance.filter(e=>e.strategy===`hierarchyDefinition`||e.strategy===`priorityDefinition`||e.strategy===`interferenceFilter`||e.strategy===`letterGating`||e.strategy===`ephemeralHint`||e.strategy===`diversityRerank`).map(e=>{let t=e.action===`boosted`?`↑`:e.action===`penalized`?`↓`:`=`;return`${e.strategyName}${t}${e.score.toFixed(2)}`}).join(` | `);logger.info(`[Pipeline] ${String(t+1).padStart(2)}. ${n.score.toFixed(4)} ${n.cardId} [${r}]${a?` {${a}}`:``}`)}}}function logCardProvenance(e,t=3){let n=e.slice(0,t);logger.debug(`[Pipeline] Provenance for top ${n.length} cards:`);for(let e of n){logger.debug(`[Pipeline] ${e.cardId} (final score: ${e.score.toFixed(3)}):`);for(let t of e.provenance){let e=t.score.toFixed(3),n=t.action.padEnd(9);logger.debug(`[Pipeline] ${n} ${e} - ${t.strategyName}: ${t.reason}`)}}}var VERBOSE_RESULTS,Pipeline,init_Pipeline=__esm({"src/core/navigators/Pipeline.ts"(){init_navigators(),init_logger(),init_orchestration(),init_PipelineDebugger(),init_diversityRerank(),VERBOSE_RESULTS=!0,Pipeline=class extends ContentNavigator{constructor(e,t,n,r){super(),_defineProperty(this,`generator`,void 0),_defineProperty(this,`filters`,void 0),_defineProperty(this,`_cachedOrchestration`,null),_defineProperty(this,`_tagCache`,new Map),_defineProperty(this,`_ephemeralHints`,null),this.generator=e,this.filters=t,this.user=n,this.course=r,r.getCourseConfig().then(e=>{logger.debug(`[pipeline] Crated pipeline for ${e.name}`)}).catch(e=>{logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`)}),logPipelineConfig(e,t),registerPipelineForDebug(this)}setEphemeralHints(e){this._ephemeralHints=e,logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(e)}`)}async getWeightedCards(e){let t=performance.now(),n=await this.buildContext(),r=performance.now(),a=500;logger.debug(`[Pipeline] Fetching 500 candidates from generator '${this.generator.name}'`);let o=await this.generator.getWeightedCards(500,n),s=o.cards,c=performance.now(),l=s.length;this._ephemeralHints=mergeHints2([this._ephemeralHints,o.hints])??null;let u;if(this.generator.generators){let e=new Map;for(let t of s){let n=t.provenance[0];if(n){let r=n.strategyName;e.has(r)||e.set(r,{cards:[]}),e.get(r).cards.push(t)}}u=Array.from(e.entries()).map(([e,t])=>{let n=t.cards.filter(e=>e.provenance[0]?.reason?.includes(`new card`)),r=t.cards.filter(e=>e.provenance[0]?.reason?.includes(`review`));return{name:e,cardCount:t.cards.length,newCount:n.length,reviewCount:r.length,topScore:Math.max(...t.cards.map(e=>e.score),0)}})}logger.debug(`[Pipeline] Generator returned ${l} candidates`),s=await this.hydrateTags(s);let d=performance.now(),p=[...s],m=this._ephemeralHints;if(m?.requireCards?.length){let e=new Set(p.map(e=>e.cardId)),t=m.requireCards.filter(t=>!t.includes(`*`)&&!e.has(t));if(t.length>0){let e=await this.course.getAppliedTagsBatch(t),n=this.course.getCourseID();for(let r of t)p.push({cardId:r,courseId:n,score:1,tags:e.get(r)??[],provenance:[]});logger.info(`[Pipeline] Pre-fetched ${t.length} required card(s) into pool: ${t.join(`, `)}`)}}let g=new Set(s.filter(e=>e.provenance.some(e=>e.strategy===`prescribed`)).map(e=>e.cardId)),_=[];for(let e of this.filters){let t=s.length,r=new Map(s.map(e=>[e.cardId,e.score]));s=await e.transform(s,n);let a=0,o=0,c=0,l=t-s.length;for(let e of s){let t=r.get(e.cardId)??0;e.score>t?a++:e.score<t?o++:c++}if(_.push({name:e.name,boosted:a,penalized:o,passed:c,removed:l}),g.size>0){let t=new Set(s.map(e=>e.cardId)),n=[...g].filter(e=>!t.has(e)),r=s.filter(e=>g.has(e.cardId)&&e.score===0).map(e=>e.cardId);(n.length>0||r.length>0)&&(logger.info(`[Pipeline] Filter '${e.name}' impact on prescribed cards: `+(n.length>0?`removed=[${n.join(`, `)}] `:``)+(r.length>0?`zeroed=[${r.join(`, `)}]`:``)),n.forEach(e=>g.delete(e)))}logger.debug(`[Pipeline] Filter '${e.name}': ${r.size} \u2192 ${s.length} cards (\u2191${a} \u2193${o} =${c})`)}s=s.filter(e=>e.score>0);let v=this._ephemeralHints;v&&(this._ephemeralHints=null,s=this.applyHints(s,v,p)),s=diversityRerank(s),s.sort((e,t)=>t.score-e.score);let y=performance.now(),b=s.slice(0,e);logger.info(`[Pipeline:timing] total=${(y-t).toFixed(0)}ms (context=${(r-t).toFixed(0)} generate=${(c-r).toFixed(0)} hydrate=${(d-c).toFixed(0)} filter=${(y-d).toFixed(0)})`);let x=b.slice(0,3).map(e=>e.score);logExecutionSummary(this.generator.name,l,this.filters.length,b.length,x,_),logResultCards(b),logCardProvenance(b,3);try{let e=await this.course?.getCourseConfig().then(e=>e.name).catch(()=>void 0);captureRun(buildRunReport(this.course?.getCourseID()||`unknown`,e,this.generator.name,u,l,_,s,b,n.userElo,v??void 0))}catch(e){logger.debug(`[Pipeline] Failed to capture debug run: ${e}`)}return{cards:b}}async hydrateTags(e){if(e.length===0)return e;let t=[];for(let n of e)this._tagCache.has(n.cardId)||t.push(n.cardId);if(t.length>0){let e=await this.course.getAppliedTagsBatch(t);for(let[t,n]of e)this._tagCache.set(t,n)}let n=new Map;for(let t of e)n.set(t.cardId,this._tagCache.get(t.cardId)??[]);return logTagHydration(e,n),e.map(e=>({...e,tags:this._tagCache.get(e.cardId)??[]}))}applyHints(e,t,n){let r=e.length;if(t.excludeCards?.length&&(e=e.filter(e=>!t.excludeCards.some(t=>globMatch(e.cardId,t)))),t.excludeTags?.length&&(e=e.filter(e=>!t.excludeTags.some(t=>cardMatchesTagPattern(e,t)))),t.boostTags)for(let[n,r]of Object.entries(t.boostTags))for(let a of e)cardMatchesTagPattern(a,n)&&(a.score*=r,a.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:t._label?`Replan Hint (${t._label})`:`Replan Hint`,action:`boosted`,score:a.score,reason:`boostTag ${n} \xD7${r}`}));if(t.boostCards)for(let[n,r]of Object.entries(t.boostCards))for(let a of e)globMatch(a.cardId,n)&&(a.score*=r,a.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:t._label?`Replan Hint (${t._label})`:`Replan Hint`,action:`boosted`,score:a.score,reason:`boostCard ${n} \xD7${r}`}));let a=new Set(e.map(e=>e.cardId)),o=new Map(e.map(e=>[e.cardId,e])),s=t._label?`Replan Hint (${t._label})`:`Replan Hint`,applyRequirement=(t,n)=>{let r=1/0,c=o.get(t.cardId);c?c.score<r&&(c.score=r,c.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:s,action:`boosted`,score:r,reason:`${n} (upgrade to mandatory score)`})):(e.push({...t,score:r,provenance:[...t.provenance,{strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:s,action:`boosted`,score:r,reason:n}]}),a.add(t.cardId),o.set(t.cardId,e[e.length-1]))};if(t.requireCards?.length)for(let e of t.requireCards){for(let t of a)globMatch(t,e)&&applyRequirement(o.get(t),`requireCard ${e}`);for(let t of n)globMatch(t.cardId,e)&&applyRequirement(t,`requireCard ${e}`)}if(t.requireTags?.length)for(let e of t.requireTags){for(let t of a){let n=o.get(t);cardMatchesTagPattern(n,e)&&applyRequirement(n,`requireTag ${e}`)}for(let t of n)cardMatchesTagPattern(t,e)&&applyRequirement(t,`requireTag ${e}`)}return logger.info(`[Pipeline] Hints applied: ${r} \u2192 ${e.length} cards`),e}async buildContext(){let e=1e3;try{e=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score}catch(e){logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`)}this._cachedOrchestration||(this._cachedOrchestration=await createOrchestrationContext(this.user,this.course));let t=this._cachedOrchestration;return{user:this.user,course:this.course,userElo:e,orchestration:t}}getCourseID(){return this.course.getCourseID()}async getOrchestrationContext(){return createOrchestrationContext(this.user,this.course)}getStrategyIds(){let e=[],extractId=e=>e.strategyId?e.strategyId:null,t=extractId(this.generator);t&&e.push(t),this.generator.generators&&Array.isArray(this.generator.generators)&&this.generator.generators.forEach(t=>{let n=extractId(t);n&&e.push(n)});for(let t of this.filters){let n=extractId(t);n&&e.push(n)}return[...new Set(e)]}async getTagEloStatus(e){let t=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo),n={};if(e){let r=Array.isArray(e)?e:[e];for(let e of r){let r=globToRegex(e);for(let[e,a]of Object.entries(t.tags))r.test(e)&&(n[e]={score:a.score,count:a.count})}}else for(let[e,r]of Object.entries(t.tags))n[e]={score:r.score,count:r.count};return n}async forecast(e){let t=e?.threshold??.1,n=e?.unseenOnly??!0,r=this.course.getCourseID(),a=(await this.course.getAllCardIds()).map(e=>({cardId:e,courseId:r,score:1,provenance:[]}));a=await this.hydrateTags(a);let o=a.slice(),s=await this.buildContext();for(let e of this.filters)a=await e.transform(a,s);if(e?.hints&&(a=this.applyHints(a,e.hints,o)),a=a.filter(e=>e.score>=t),n){let e;try{e=new Set(await this.user.getSeenCards(r))}catch{e=new Set}a=a.filter(t=>!e.has(t.cardId))}return a.sort((e,t)=>t.score-e.score),e?.limit?a.slice(0,e.limit):a}async diagnoseCardSpace(e){let t=e?.threshold??.1,n=performance.now(),r=await this.course.getAllCardIds(),a=r.map(e=>({cardId:e,courseId:this.course.getCourseID(),score:1,provenance:[]}));a=await this.hydrateTags(a);let o=await this.buildContext(),s=[];for(let e of this.filters){a=await e.transform(a,o);let n=a.filter(e=>e.score>=t).length;s.push({name:e.name,wellIndicated:n})}let c=a.filter(e=>e.score>=t),l=new Set(c.map(e=>e.cardId)),u;try{let e=this.course.getCourseID(),t=await this.user.getSeenCards(e);u=new Set(t)}catch{u=new Set}let d=c.filter(e=>!u.has(e.cardId)),p=new Map;for(let e of a){let n=e.cardId.split(`-`)[1]||`unknown`;p.has(n)||p.set(n,{total:0,wellIndicated:0,new:0});let r=p.get(n);r.total++,e.score>=t&&(r.wellIndicated++,u.has(e.cardId)||r.new++)}let m=performance.now()-n,g={totalCards:r.length,threshold:t,wellIndicated:l.size,encountered:u.size,wellIndicatedNew:d.length,byType:Object.fromEntries(p),filterBreakdown:s,elapsedMs:Math.round(m)};logger.info(`[Pipeline:diagnose] Card space scan (${g.elapsedMs}ms):`),logger.info(`[Pipeline:diagnose] Total cards: ${g.totalCards}`),logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${t}): ${g.wellIndicated}`),logger.info(`[Pipeline:diagnose] Encountered: ${g.encountered}`),logger.info(`[Pipeline:diagnose] Well-indicated & new: ${g.wellIndicatedNew}`),logger.info(`[Pipeline:diagnose] By type:`);for(let[e,t]of p)logger.info(`[Pipeline:diagnose] ${e}: ${t.wellIndicated}/${t.total} well-indicated, ${t.new} new`);logger.info(`[Pipeline:diagnose] After each filter:`);for(let e of s)logger.info(`[Pipeline:diagnose] ${e.name}: ${e.wellIndicated} well-indicated`);return g}}}}),defaults_exports={};__export(defaults_exports,{createDefaultEloStrategy:()=>createDefaultEloStrategy,createDefaultPipeline:()=>createDefaultPipeline,createDefaultSrsStrategy:()=>createDefaultSrsStrategy});function createDefaultEloStrategy(e){return{_id:`NAVIGATION_STRATEGY-ELO-default`,docType:`NAVIGATION_STRATEGY`,name:`ELO (default)`,description:`Default ELO-based navigation strategy for new cards`,implementingClass:`elo`,course:e,serializedData:``}}function createDefaultSrsStrategy(e){return{_id:`NAVIGATION_STRATEGY-SRS-default`,docType:`NAVIGATION_STRATEGY`,name:`SRS (default)`,description:`Default SRS-based navigation strategy for reviews`,implementingClass:`srs`,course:e,serializedData:``}}function createDefaultPipeline(e,t){let n=t.getCourseID(),r=new ELONavigator(e,t,createDefaultEloStrategy(n)),a=new SRSNavigator(e,t,createDefaultSrsStrategy(n)),o=new CompositeGenerator([r,a]),s=createEloDistanceFilter();return new Pipeline(o,[s],e,t)}var init_defaults=__esm({"src/core/navigators/defaults.ts"(){init_navigators(),init_Pipeline(),init_CompositeGenerator(),init_elo(),init_srs(),init_eloDistance(),init_types_legacy()}}),PipelineAssembler_exports={};__export(PipelineAssembler_exports,{PipelineAssembler:()=>PipelineAssembler});var PipelineAssembler,init_PipelineAssembler=__esm({"src/core/navigators/PipelineAssembler.ts"(){init_navigators(),init_WeightedFilter(),init_Pipeline(),init_logger(),init_CompositeGenerator(),init_defaults(),PipelineAssembler=class{async assemble(e){let{strategies:t,user:n,course:r}=e,a=[];if(t.length===0)return{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:a};let o=[],s=[];for(let e of t)isGenerator(e.implementingClass)?o.push(e):isFilter(e.implementingClass)?s.push(e):a.push(`Unknown strategy type '${e.implementingClass}', skipping: ${e.name}`);let c=r.getCourseID(),l=o.some(e=>e.implementingClass===`elo`),u=o.some(e=>e.implementingClass===`srs`);if(l||(logger.debug(`[PipelineAssembler] No ELO generator configured, adding default`),o.push(createDefaultEloStrategy(c))),u||(logger.debug(`[PipelineAssembler] No SRS generator configured, adding default`),o.push(createDefaultSrsStrategy(c))),o.length===0)return a.push(`No generator strategy found`),{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:a};let d;o.length===1?(d=await ContentNavigator.create(n,r,o[0]),logger.debug(`[PipelineAssembler] Using single generator: ${o[0].name}`)):(logger.debug(`[PipelineAssembler] Using CompositeGenerator for ${o.length} generators: ${o.map(e=>e.name).join(`, `)}`),d=await CompositeGenerator.fromStrategies(n,r,o));let p=[],m=[...s].sort((e,t)=>e.name.localeCompare(t.name));for(let e of m)try{let t=await ContentNavigator.create(n,r,e);if(`transform`in t&&typeof t.transform==`function`){let n=t;e.learnable&&(n=new WeightedFilter(n,e.learnable,e.staticWeight,e._id)),p.push(n),logger.debug(`[PipelineAssembler] Added filter: ${e.name}`)}else a.push(`Filter '${e.name}' does not implement CardFilter.transform(), skipping`)}catch(t){a.push(`Failed to instantiate filter '${e.name}': ${t}`)}let g=new Pipeline(d,p,n,r);return logger.debug(`[PipelineAssembler] Assembled pipeline with ${o.length} generator(s) and ${p.length} filter(s)`),{pipeline:g,generatorStrategies:o,filterStrategies:m,warnings:a}}}}}),globImport,init_3=__esm({'import("./**/*") in src/core/navigators/index.ts'(){globImport=__glob({"./Pipeline.ts":()=>Promise.resolve().then(()=>(init_Pipeline(),Pipeline_exports)),"./PipelineAssembler.ts":()=>Promise.resolve().then(()=>(init_PipelineAssembler(),PipelineAssembler_exports)),"./PipelineDebugger.ts":()=>Promise.resolve().then(()=>(init_PipelineDebugger(),PipelineDebugger_exports)),"./defaults.ts":()=>Promise.resolve().then(()=>(init_defaults(),defaults_exports)),"./diversityRerank.ts":()=>Promise.resolve().then(()=>(init_diversityRerank(),diversityRerank_exports)),"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports)),"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports)),"./index.ts":()=>Promise.resolve().then(()=>(init_navigators(),navigators_exports))})}}),navigators_exports={};__export(navigators_exports,{ContentNavigator:()=>ContentNavigator,DIVERSITY_FLOOR:()=>DIVERSITY_FLOOR,DIVERSITY_STRENGTH:()=>DIVERSITY_STRENGTH,NavigatorRole:()=>NavigatorRole,NavigatorRoles:()=>NavigatorRoles,Navigators:()=>Navigators,diversityRerank:()=>diversityRerank,getActivePipeline:()=>getActivePipeline,getCardOrigin:()=>getCardOrigin,getRegisteredNavigator:()=>getRegisteredNavigator,getRegisteredNavigatorNames:()=>getRegisteredNavigatorNames,getRegisteredNavigatorRole:()=>getRegisteredNavigatorRole,hasRegisteredNavigator:()=>hasRegisteredNavigator,initializeNavigatorRegistry:()=>initializeNavigatorRegistry,isFilter:()=>isFilter,isGenerator:()=>isGenerator,mountPipelineDebugger:()=>mountPipelineDebugger,pipelineDebugAPI:()=>pipelineDebugAPI,registerNavigator:()=>registerNavigator});function registerNavigator(e,t,n){navigatorRegistry.set(e,{constructor:t,role:n}),logger.debug(`[NavigatorRegistry] Registered: ${e}${n?` (${n})`:``}`)}function getRegisteredNavigator(e){return navigatorRegistry.get(e)?.constructor}function hasRegisteredNavigator(e){return navigatorRegistry.has(e)}function getRegisteredNavigatorRole(e){return navigatorRegistry.get(e)?.role}function getRegisteredNavigatorNames(){return Array.from(navigatorRegistry.keys())}async function initializeNavigatorRegistry(){logger.debug(`[NavigatorRegistry] Initializing built-in navigators...`);let[e,t]=await Promise.all([Promise.resolve().then(()=>(init_elo(),elo_exports)),Promise.resolve().then(()=>(init_srs(),srs_exports))]),n=await Promise.resolve().then(()=>(init_prescribed(),prescribed_exports));registerNavigator(`elo`,e.default),registerNavigator(`srs`,t.default),registerNavigator(`prescribed`,n.default);let[r,a,o,s]=await Promise.all([Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports))]);registerNavigator(`hierarchyDefinition`,r.default),registerNavigator(`interferenceMitigator`,a.default),registerNavigator(`relativePriority`,o.default),registerNavigator(`userTagPreference`,s.default),logger.debug(`[NavigatorRegistry] Initialized ${navigatorRegistry.size} navigators: ${getRegisteredNavigatorNames().join(`, `)}`)}function getCardOrigin(e){if(e.provenance.length===0)throw Error(`Card has no provenance - cannot determine origin`);let t=e.provenance[0].reason.toLowerCase();return t.includes(`failed`)?`failed`:t.includes(`review`)?`review`:`new`}function isGenerator(e){return NavigatorRoles[e]===`generator`?!0:getRegisteredNavigatorRole(e)===`generator`}function isFilter(e){return NavigatorRoles[e]===`filter`?!0:getRegisteredNavigatorRole(e)===`filter`}var navigatorRegistry,Navigators,NavigatorRole,NavigatorRoles,ContentNavigator,init_navigators=__esm({"src/core/navigators/index.ts"(){init_diversityRerank(),init_PipelineDebugger(),init_logger(),init_(),init_2(),init_3(),navigatorRegistry=new Map,Navigators=(e=>(e.ELO=`elo`,e.SRS=`srs`,e.PRESCRIBED=`prescribed`,e.HIERARCHY=`hierarchyDefinition`,e.INTERFERENCE=`interferenceMitigator`,e.RELATIVE_PRIORITY=`relativePriority`,e.USER_TAG_PREFERENCE=`userTagPreference`,e))(Navigators||{}),NavigatorRole=(e=>(e.GENERATOR=`generator`,e.FILTER=`filter`,e))(NavigatorRole||{}),NavigatorRoles={elo:`generator`,srs:`generator`,prescribed:`generator`,hierarchyDefinition:`filter`,interferenceMitigator:`filter`,relativePriority:`filter`,userTagPreference:`filter`},ContentNavigator=class{constructor(e,t,n){_defineProperty(this,`user`,void 0),_defineProperty(this,`course`,void 0),_defineProperty(this,`strategyName`,void 0),_defineProperty(this,`strategyId`,void 0),_defineProperty(this,`learnable`,void 0),_defineProperty(this,`staticWeight`,void 0),this.user=e,this.course=t,n&&(this.strategyName=n.name,this.strategyId=n._id,this.learnable=n.learnable,this.staticWeight=n.staticWeight)}get strategyKey(){return this.constructor.name}async getStrategyState(){if(!this.user||!this.course)throw Error(`Cannot get strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.getStrategyState(this.course.getCourseID(),this.strategyKey)}async putStrategyState(e){if(!this.user||!this.course)throw Error(`Cannot put strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.putStrategyState(this.course.getCourseID(),this.strategyKey,e)}static async create(e,t,n){let r=n.implementingClass,a=getRegisteredNavigator(r);if(a)return logger.debug(`[ContentNavigator.create] Using registered navigator: ${r}`),new a(e,t,n);logger.debug(`[ContentNavigator.create] Navigator not in registry, attempting dynamic import: ${r}`);let o;for(let e of[`.ts`,`.js`,``]){try{if(o=(await globImport_generators(`./generators/${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load generator ${r}${e}:`,t)}try{if(o=(await globImport_filters(`./filters/${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load filter ${r}${e}:`,t)}try{if(o=(await globImport(`./${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load legacy ${r}${e}:`,t)}if(o)break}if(!o)throw Error(`Could not load navigator implementation for: ${r}`);return new o(e,t,n)}async getWeightedCards(e){throw Error(`${this.constructor.name} must implement getWeightedCards(). `)}setEphemeralHints(e){}}}});function randIntWeightedTowardZero(e){return Math.floor(Math.random()*Math.random()*Math.random()*e)}async function getCourseTagStubs(e){logger.debug(`Getting tag stubs for course: ${e}`);let t=await filterAllDocsByPrefix2(getCourseDB2(e),`TAG`.valueOf()+`-`);return t.rows.forEach(e=>{logger.debug(` Tag stub for doc: ${e.id}`)}),t}async function createTag(e,t,n){logger.debug(`Creating tag: ${t}...`);let r=getTagID(t);return await getCourseDB2(e).put({course:e,docType:`TAG`,name:t,snippet:``,taggedCards:[],wiki:``,author:n,_id:r})}async function updateTag(e){let t=await getTag(e.course,e.name);return await getCourseDB2(e.course).put({...e,_rev:t._rev})}async function getTag(e,t){let n=getTagID(t);return getCourseDB2(e).get(n)}async function removeTagFromCard(e,t,n){n=getTagID(n);let r=getCourseDB2(e),a=await r.get(n);return a.taggedCards=a.taggedCards.filter(e=>t!==e),r.put(a)}async function getAppliedTags(e,t){return await getCourseDB2(e).query(`getTags`,{startkey:t,endkey:t})}async function updateCredentialledCourseConfig(e,t){logger.debug(`Updating course config:
|
|
255
|
+
💡 Inspect: window.skuilder.pipeline`)}function logResultCards(e){if(!(!VERBOSE_RESULTS||e.length===0)){logger.info(`[Pipeline] Results (${e.length} cards):`);for(let t=0;t<e.length;t++){let n=e[t],r=n.tags?.slice(0,3).join(`, `)||``,a=n.provenance.filter(e=>e.strategy===`hierarchyDefinition`||e.strategy===`priorityDefinition`||e.strategy===`interferenceFilter`||e.strategy===`letterGating`||e.strategy===`ephemeralHint`||e.strategy===`diversityRerank`).map(e=>{let t=e.action===`boosted`?`↑`:e.action===`penalized`?`↓`:`=`;return`${e.strategyName}${t}${e.score.toFixed(2)}`}).join(` | `);logger.info(`[Pipeline] ${String(t+1).padStart(2)}. ${n.score.toFixed(4)} ${n.cardId} [${r}]${a?` {${a}}`:``}`)}}}function logCardProvenance(e,t=3){let n=e.slice(0,t);logger.debug(`[Pipeline] Provenance for top ${n.length} cards:`);for(let e of n){logger.debug(`[Pipeline] ${e.cardId} (final score: ${e.score.toFixed(3)}):`);for(let t of e.provenance){let e=t.score.toFixed(3),n=t.action.padEnd(9);logger.debug(`[Pipeline] ${n} ${e} - ${t.strategyName}: ${t.reason}`)}}}var VERBOSE_RESULTS,Pipeline,init_Pipeline=__esm({"src/core/navigators/Pipeline.ts"(){init_navigators(),init_logger(),init_orchestration(),init_PipelineDebugger(),init_diversityRerank(),VERBOSE_RESULTS=!0,Pipeline=class extends ContentNavigator{constructor(e,t,n,r){super(),_defineProperty(this,`generator`,void 0),_defineProperty(this,`filters`,void 0),_defineProperty(this,`_cachedOrchestration`,null),_defineProperty(this,`_tagCache`,new Map),_defineProperty(this,`_ephemeralHints`,null),this.generator=e,this.filters=t,this.user=n,this.course=r,r.getCourseConfig().then(e=>{logger.debug(`[pipeline] Crated pipeline for ${e.name}`)}).catch(e=>{logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`)}),logPipelineConfig(e,t),registerPipelineForDebug(this)}setEphemeralHints(e){this._ephemeralHints=e,logger.info(`[Pipeline] Ephemeral hints set: ${JSON.stringify(e)}`)}async getWeightedCards(e){let t=performance.now(),n=await this.buildContext(),r=performance.now(),a=500;logger.debug(`[Pipeline] Fetching 500 candidates from generator '${this.generator.name}'`);let o=await this.generator.getWeightedCards(500,n),s=o.cards,c=performance.now(),l=s.length;this._ephemeralHints=mergeHints2([this._ephemeralHints,o.hints])??null;let u;if(this.generator.generators){let e=new Map;for(let t of s){let n=t.provenance[0];if(n){let r=n.strategyName;e.has(r)||e.set(r,{cards:[]}),e.get(r).cards.push(t)}}u=Array.from(e.entries()).map(([e,t])=>{let n=t.cards.filter(e=>e.provenance[0]?.reason?.includes(`new card`)),r=t.cards.filter(e=>e.provenance[0]?.reason?.includes(`review`));return{name:e,cardCount:t.cards.length,newCount:n.length,reviewCount:r.length,topScore:Math.max(...t.cards.map(e=>e.score),0)}})}logger.debug(`[Pipeline] Generator returned ${l} candidates`),s=await this.hydrateTags(s);let d=performance.now(),p=[...s],m=this._ephemeralHints;if(m?.requireCards?.length){let e=new Set(p.map(e=>e.cardId)),t=m.requireCards.filter(t=>!t.includes(`*`)&&!e.has(t));if(t.length>0){let e=await this.course.getAppliedTagsBatch(t),n=this.course.getCourseID();for(let r of t)p.push({cardId:r,courseId:n,score:1,tags:e.get(r)??[],provenance:[]});logger.info(`[Pipeline] Pre-fetched ${t.length} required card(s) into pool: ${t.join(`, `)}`)}}let g=new Set(s.filter(e=>e.provenance.some(e=>e.strategy===`prescribed`)).map(e=>e.cardId)),_=[];for(let e of this.filters){let t=s.length,r=new Map(s.map(e=>[e.cardId,e.score]));s=await e.transform(s,n);let a=0,o=0,c=0,l=t-s.length;for(let e of s){let t=r.get(e.cardId)??0;e.score>t?a++:e.score<t?o++:c++}if(_.push({name:e.name,boosted:a,penalized:o,passed:c,removed:l}),g.size>0){let t=new Set(s.map(e=>e.cardId)),n=[...g].filter(e=>!t.has(e)),r=s.filter(e=>g.has(e.cardId)&&e.score===0).map(e=>e.cardId);(n.length>0||r.length>0)&&(logger.info(`[Pipeline] Filter '${e.name}' impact on prescribed cards: `+(n.length>0?`removed=[${n.join(`, `)}] `:``)+(r.length>0?`zeroed=[${r.join(`, `)}]`:``)),n.forEach(e=>g.delete(e)))}logger.debug(`[Pipeline] Filter '${e.name}': ${r.size} \u2192 ${s.length} cards (\u2191${a} \u2193${o} =${c})`)}s=s.filter(e=>e.score>0);let v=this._ephemeralHints;v&&(this._ephemeralHints=null,s=this.applyHints(s,v,p)),s=diversityRerank(s),s.sort((e,t)=>t.score-e.score);let y=performance.now(),b=s.slice(0,e);logger.info(`[Pipeline:timing] total=${(y-t).toFixed(0)}ms (context=${(r-t).toFixed(0)} generate=${(c-r).toFixed(0)} hydrate=${(d-c).toFixed(0)} filter=${(y-d).toFixed(0)})`);let x=b.slice(0,3).map(e=>e.score);logExecutionSummary(this.generator.name,l,this.filters.length,b.length,x,_),logResultCards(b),logCardProvenance(b,3);try{let e=await this.course?.getCourseConfig().then(e=>e.name).catch(()=>void 0);captureRun(buildRunReport(this.course?.getCourseID()||`unknown`,e,this.generator.name,u,l,_,s,b,n.userElo,v??void 0))}catch(e){logger.debug(`[Pipeline] Failed to capture debug run: ${e}`)}return{cards:b}}async hydrateTags(e){if(e.length===0)return e;let t=[];for(let n of e)this._tagCache.has(n.cardId)||t.push(n.cardId);if(t.length>0){let e=await this.course.getAppliedTagsBatch(t);for(let[t,n]of e)this._tagCache.set(t,n)}let n=new Map;for(let t of e)n.set(t.cardId,this._tagCache.get(t.cardId)??[]);return logTagHydration(e,n),e.map(e=>({...e,tags:this._tagCache.get(e.cardId)??[]}))}applyHints(e,t,n){let r=e.length;if(t.excludeCards?.length&&(e=e.filter(e=>!t.excludeCards.some(t=>globMatch(e.cardId,t)))),t.excludeTags?.length&&(e=e.filter(e=>!t.excludeTags.some(t=>cardMatchesTagPattern(e,t)))),t.boostTags)for(let[n,r]of Object.entries(t.boostTags))for(let a of e)cardMatchesTagPattern(a,n)&&(a.score*=r,a.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:t._label?`Replan Hint (${t._label})`:`Replan Hint`,action:`boosted`,score:a.score,reason:`boostTag ${n} \xD7${r}`}));if(t.boostCards)for(let[n,r]of Object.entries(t.boostCards))for(let a of e)globMatch(a.cardId,n)&&(a.score*=r,a.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:t._label?`Replan Hint (${t._label})`:`Replan Hint`,action:`boosted`,score:a.score,reason:`boostCard ${n} \xD7${r}`}));let a=new Set(e.map(e=>e.cardId)),o=new Map(e.map(e=>[e.cardId,e])),s=t._label?`Replan Hint (${t._label})`:`Replan Hint`,applyRequirement=(t,n)=>{let r=1/0,c=o.get(t.cardId);c?c.score<r&&(c.score=r,c.provenance.push({strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:s,action:`boosted`,score:r,reason:`${n} (upgrade to mandatory score)`})):(e.push({...t,score:r,provenance:[...t.provenance,{strategy:`ephemeralHint`,strategyId:`ephemeral-hint`,strategyName:s,action:`boosted`,score:r,reason:n}]}),a.add(t.cardId),o.set(t.cardId,e[e.length-1]))};if(t.requireCards?.length)for(let e of t.requireCards){for(let t of a)globMatch(t,e)&&applyRequirement(o.get(t),`requireCard ${e}`);for(let t of n)globMatch(t.cardId,e)&&applyRequirement(t,`requireCard ${e}`)}if(t.requireTags?.length)for(let e of t.requireTags){for(let t of a){let n=o.get(t);cardMatchesTagPattern(n,e)&&applyRequirement(n,`requireTag ${e}`)}for(let t of n)cardMatchesTagPattern(t,e)&&applyRequirement(t,`requireTag ${e}`)}return logger.info(`[Pipeline] Hints applied: ${r} \u2192 ${e.length} cards`),e}async buildContext(){let e=1e3;try{e=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo).global.score}catch(e){logger.debug(`[Pipeline] Could not get user ELO, using default: ${e}`)}this._cachedOrchestration||(this._cachedOrchestration=await createOrchestrationContext(this.user,this.course));let t=this._cachedOrchestration;return{user:this.user,course:this.course,userElo:e,orchestration:t}}getCourseID(){return this.course.getCourseID()}async getOrchestrationContext(){return createOrchestrationContext(this.user,this.course)}getStrategyIds(){let e=[],extractId=e=>e.strategyId?e.strategyId:null,t=extractId(this.generator);t&&e.push(t),this.generator.generators&&Array.isArray(this.generator.generators)&&this.generator.generators.forEach(t=>{let n=extractId(t);n&&e.push(n)});for(let t of this.filters){let n=extractId(t);n&&e.push(n)}return[...new Set(e)]}async getTagEloStatus(e){let t=toCourseElo((await this.user.getCourseRegDoc(this.course.getCourseID())).elo),n={};if(e){let r=Array.isArray(e)?e:[e];for(let e of r){let r=globToRegex(e);for(let[e,a]of Object.entries(t.tags))r.test(e)&&(n[e]={score:a.score,count:a.count})}}else for(let[e,r]of Object.entries(t.tags))n[e]={score:r.score,count:r.count};return n}async forecast(e){let t=e?.threshold??.1,n=e?.unseenOnly??!0,r=this.course.getCourseID(),a=(await this.course.getAllCardIds()).map(e=>({cardId:e,courseId:r,score:1,provenance:[]}));a=await this.hydrateTags(a);let o=a.slice(),s=await this.buildContext();for(let e of this.filters)a=await e.transform(a,s);if(e?.hints&&(a=this.applyHints(a,e.hints,o)),a=a.filter(e=>e.score>=t),n){let e;try{e=new Set(await this.user.getSeenCards(r))}catch{e=new Set}a=a.filter(t=>!e.has(t.cardId))}return a.sort((e,t)=>t.score-e.score),e?.limit?a.slice(0,e.limit):a}async diagnoseCardSpace(e){let t=e?.threshold??.1,n=performance.now(),r=await this.course.getAllCardIds(),a=r.map(e=>({cardId:e,courseId:this.course.getCourseID(),score:1,provenance:[]}));a=await this.hydrateTags(a);let o=await this.buildContext(),s=[];for(let e of this.filters){a=await e.transform(a,o);let n=a.filter(e=>e.score>=t).length;s.push({name:e.name,wellIndicated:n})}let c=a.filter(e=>e.score>=t),l=new Set(c.map(e=>e.cardId)),u;try{let e=this.course.getCourseID(),t=await this.user.getSeenCards(e);u=new Set(t)}catch{u=new Set}let d=c.filter(e=>!u.has(e.cardId)),p=new Map;for(let e of a){let n=e.cardId.split(`-`)[1]||`unknown`;p.has(n)||p.set(n,{total:0,wellIndicated:0,new:0});let r=p.get(n);r.total++,e.score>=t&&(r.wellIndicated++,u.has(e.cardId)||r.new++)}let m=performance.now()-n,g={totalCards:r.length,threshold:t,wellIndicated:l.size,encountered:u.size,wellIndicatedNew:d.length,byType:Object.fromEntries(p),filterBreakdown:s,elapsedMs:Math.round(m)};logger.info(`[Pipeline:diagnose] Card space scan (${g.elapsedMs}ms):`),logger.info(`[Pipeline:diagnose] Total cards: ${g.totalCards}`),logger.info(`[Pipeline:diagnose] Well-indicated (score >= ${t}): ${g.wellIndicated}`),logger.info(`[Pipeline:diagnose] Encountered: ${g.encountered}`),logger.info(`[Pipeline:diagnose] Well-indicated & new: ${g.wellIndicatedNew}`),logger.info(`[Pipeline:diagnose] By type:`);for(let[e,t]of p)logger.info(`[Pipeline:diagnose] ${e}: ${t.wellIndicated}/${t.total} well-indicated, ${t.new} new`);logger.info(`[Pipeline:diagnose] After each filter:`);for(let e of s)logger.info(`[Pipeline:diagnose] ${e.name}: ${e.wellIndicated} well-indicated`);return g}}}}),defaults_exports={};__export(defaults_exports,{createDefaultEloStrategy:()=>createDefaultEloStrategy,createDefaultPipeline:()=>createDefaultPipeline,createDefaultSrsStrategy:()=>createDefaultSrsStrategy});function createDefaultEloStrategy(e){return{_id:`NAVIGATION_STRATEGY-ELO-default`,docType:`NAVIGATION_STRATEGY`,name:`ELO (default)`,description:`Default ELO-based navigation strategy for new cards`,implementingClass:`elo`,course:e,serializedData:``}}function createDefaultSrsStrategy(e){return{_id:`NAVIGATION_STRATEGY-SRS-default`,docType:`NAVIGATION_STRATEGY`,name:`SRS (default)`,description:`Default SRS-based navigation strategy for reviews`,implementingClass:`srs`,course:e,serializedData:``}}function createDefaultPipeline(e,t){let n=t.getCourseID(),r=new ELONavigator(e,t,createDefaultEloStrategy(n)),a=new SRSNavigator(e,t,createDefaultSrsStrategy(n)),o=new CompositeGenerator([r,a]),s=createEloDistanceFilter();return new Pipeline(o,[s],e,t)}var init_defaults=__esm({"src/core/navigators/defaults.ts"(){init_navigators(),init_Pipeline(),init_CompositeGenerator(),init_elo(),init_srs(),init_eloDistance(),init_types_legacy()}}),PipelineAssembler_exports={};__export(PipelineAssembler_exports,{PipelineAssembler:()=>PipelineAssembler});var PipelineAssembler,init_PipelineAssembler=__esm({"src/core/navigators/PipelineAssembler.ts"(){init_navigators(),init_WeightedFilter(),init_Pipeline(),init_logger(),init_CompositeGenerator(),init_defaults(),PipelineAssembler=class{async assemble(e){let{strategies:t,user:n,course:r}=e,a=[];if(t.length===0)return{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:a};let o=[],s=[];for(let e of t)isGenerator(e.implementingClass)?o.push(e):isFilter(e.implementingClass)?s.push(e):a.push(`Unknown strategy type '${e.implementingClass}', skipping: ${e.name}`);let c=r.getCourseID(),l=o.some(e=>e.implementingClass===`elo`),u=o.some(e=>e.implementingClass===`srs`);if(l||(logger.debug(`[PipelineAssembler] No ELO generator configured, adding default`),o.push(createDefaultEloStrategy(c))),u||(logger.debug(`[PipelineAssembler] No SRS generator configured, adding default`),o.push(createDefaultSrsStrategy(c))),o.length===0)return a.push(`No generator strategy found`),{pipeline:null,generatorStrategies:[],filterStrategies:[],warnings:a};let d;o.length===1?(d=await ContentNavigator.create(n,r,o[0]),logger.debug(`[PipelineAssembler] Using single generator: ${o[0].name}`)):(logger.debug(`[PipelineAssembler] Using CompositeGenerator for ${o.length} generators: ${o.map(e=>e.name).join(`, `)}`),d=await CompositeGenerator.fromStrategies(n,r,o));let p=[],m=[...s].sort((e,t)=>e.name.localeCompare(t.name));for(let e of m)try{let t=await ContentNavigator.create(n,r,e);if(`transform`in t&&typeof t.transform==`function`){let n=t;e.learnable&&(n=new WeightedFilter(n,e.learnable,e.staticWeight,e._id)),p.push(n),logger.debug(`[PipelineAssembler] Added filter: ${e.name}`)}else a.push(`Filter '${e.name}' does not implement CardFilter.transform(), skipping`)}catch(t){a.push(`Failed to instantiate filter '${e.name}': ${t}`)}let g=new Pipeline(d,p,n,r);return logger.debug(`[PipelineAssembler] Assembled pipeline with ${o.length} generator(s) and ${p.length} filter(s)`),{pipeline:g,generatorStrategies:o,filterStrategies:m,warnings:a}}}}}),globImport,init_3=__esm({'import("./**/*") in src/core/navigators/index.ts'(){globImport=__glob({"./Pipeline.ts":()=>Promise.resolve().then(()=>(init_Pipeline(),Pipeline_exports)),"./PipelineAssembler.ts":()=>Promise.resolve().then(()=>(init_PipelineAssembler(),PipelineAssembler_exports)),"./PipelineDebugger.ts":()=>Promise.resolve().then(()=>(init_PipelineDebugger(),PipelineDebugger_exports)),"./SrsDebugger.ts":()=>Promise.resolve().then(()=>(init_SrsDebugger(),SrsDebugger_exports)),"./defaults.ts":()=>Promise.resolve().then(()=>(init_defaults(),defaults_exports)),"./diversityRerank.ts":()=>Promise.resolve().then(()=>(init_diversityRerank(),diversityRerank_exports)),"./filters/WeightedFilter.ts":()=>Promise.resolve().then(()=>(init_WeightedFilter(),WeightedFilter_exports)),"./filters/eloDistance.ts":()=>Promise.resolve().then(()=>(init_eloDistance(),eloDistance_exports)),"./filters/hierarchyDefinition.ts":()=>Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),"./filters/index.ts":()=>Promise.resolve().then(()=>(init_filters(),filters_exports)),"./filters/inferredPreferenceStub.ts":()=>Promise.resolve().then(()=>(init_inferredPreferenceStub(),inferredPreferenceStub_exports)),"./filters/interferenceMitigator.ts":()=>Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),"./filters/relativePriority.ts":()=>Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),"./filters/types.ts":()=>Promise.resolve().then(()=>(init_types2(),types_exports2)),"./filters/userGoalStub.ts":()=>Promise.resolve().then(()=>(init_userGoalStub(),userGoalStub_exports)),"./filters/userTagPreference.ts":()=>Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports)),"./generators/CompositeGenerator.ts":()=>Promise.resolve().then(()=>(init_CompositeGenerator(),CompositeGenerator_exports)),"./generators/elo.ts":()=>Promise.resolve().then(()=>(init_elo(),elo_exports)),"./generators/index.ts":()=>Promise.resolve().then(()=>(init_generators(),generators_exports)),"./generators/prescribed.ts":()=>Promise.resolve().then(()=>(init_prescribed(),prescribed_exports)),"./generators/srs.ts":()=>Promise.resolve().then(()=>(init_srs(),srs_exports)),"./generators/types.ts":()=>Promise.resolve().then(()=>(init_types(),types_exports)),"./index.ts":()=>Promise.resolve().then(()=>(init_navigators(),navigators_exports))})}}),navigators_exports={};__export(navigators_exports,{ContentNavigator:()=>ContentNavigator,DIVERSITY_FLOOR:()=>DIVERSITY_FLOOR,DIVERSITY_STRENGTH:()=>DIVERSITY_STRENGTH,NavigatorRole:()=>NavigatorRole,NavigatorRoles:()=>NavigatorRoles,Navigators:()=>Navigators,clearSrsBacklogDebug:()=>clearSrsBacklogDebug,diversityRerank:()=>diversityRerank,getActivePipeline:()=>getActivePipeline,getCardOrigin:()=>getCardOrigin,getRegisteredNavigator:()=>getRegisteredNavigator,getRegisteredNavigatorNames:()=>getRegisteredNavigatorNames,getRegisteredNavigatorRole:()=>getRegisteredNavigatorRole,getSrsBacklogDebug:()=>getSrsBacklogDebug,hasRegisteredNavigator:()=>hasRegisteredNavigator,initializeNavigatorRegistry:()=>initializeNavigatorRegistry,isFilter:()=>isFilter,isGenerator:()=>isGenerator,mountPipelineDebugger:()=>mountPipelineDebugger,pipelineDebugAPI:()=>pipelineDebugAPI,registerNavigator:()=>registerNavigator});function registerNavigator(e,t,n){navigatorRegistry.set(e,{constructor:t,role:n}),logger.debug(`[NavigatorRegistry] Registered: ${e}${n?` (${n})`:``}`)}function getRegisteredNavigator(e){return navigatorRegistry.get(e)?.constructor}function hasRegisteredNavigator(e){return navigatorRegistry.has(e)}function getRegisteredNavigatorRole(e){return navigatorRegistry.get(e)?.role}function getRegisteredNavigatorNames(){return Array.from(navigatorRegistry.keys())}async function initializeNavigatorRegistry(){logger.debug(`[NavigatorRegistry] Initializing built-in navigators...`);let[e,t]=await Promise.all([Promise.resolve().then(()=>(init_elo(),elo_exports)),Promise.resolve().then(()=>(init_srs(),srs_exports))]),n=await Promise.resolve().then(()=>(init_prescribed(),prescribed_exports));registerNavigator(`elo`,e.default),registerNavigator(`srs`,t.default),registerNavigator(`prescribed`,n.default);let[r,a,o,s]=await Promise.all([Promise.resolve().then(()=>(init_hierarchyDefinition(),hierarchyDefinition_exports)),Promise.resolve().then(()=>(init_interferenceMitigator(),interferenceMitigator_exports)),Promise.resolve().then(()=>(init_relativePriority(),relativePriority_exports)),Promise.resolve().then(()=>(init_userTagPreference(),userTagPreference_exports))]);registerNavigator(`hierarchyDefinition`,r.default),registerNavigator(`interferenceMitigator`,a.default),registerNavigator(`relativePriority`,o.default),registerNavigator(`userTagPreference`,s.default),logger.debug(`[NavigatorRegistry] Initialized ${navigatorRegistry.size} navigators: ${getRegisteredNavigatorNames().join(`, `)}`)}function getCardOrigin(e){if(e.provenance.length===0)throw Error(`Card has no provenance - cannot determine origin`);let t=e.provenance[0].reason.toLowerCase();return t.includes(`failed`)?`failed`:t.includes(`review`)?`review`:`new`}function isGenerator(e){return NavigatorRoles[e]===`generator`?!0:getRegisteredNavigatorRole(e)===`generator`}function isFilter(e){return NavigatorRoles[e]===`filter`?!0:getRegisteredNavigatorRole(e)===`filter`}var navigatorRegistry,Navigators,NavigatorRole,NavigatorRoles,ContentNavigator,init_navigators=__esm({"src/core/navigators/index.ts"(){init_diversityRerank(),init_PipelineDebugger(),init_SrsDebugger(),init_logger(),init_(),init_2(),init_3(),navigatorRegistry=new Map,Navigators=(e=>(e.ELO=`elo`,e.SRS=`srs`,e.PRESCRIBED=`prescribed`,e.HIERARCHY=`hierarchyDefinition`,e.INTERFERENCE=`interferenceMitigator`,e.RELATIVE_PRIORITY=`relativePriority`,e.USER_TAG_PREFERENCE=`userTagPreference`,e))(Navigators||{}),NavigatorRole=(e=>(e.GENERATOR=`generator`,e.FILTER=`filter`,e))(NavigatorRole||{}),NavigatorRoles={elo:`generator`,srs:`generator`,prescribed:`generator`,hierarchyDefinition:`filter`,interferenceMitigator:`filter`,relativePriority:`filter`,userTagPreference:`filter`},ContentNavigator=class{constructor(e,t,n){_defineProperty(this,`user`,void 0),_defineProperty(this,`course`,void 0),_defineProperty(this,`strategyName`,void 0),_defineProperty(this,`strategyId`,void 0),_defineProperty(this,`learnable`,void 0),_defineProperty(this,`staticWeight`,void 0),this.user=e,this.course=t,n&&(this.strategyName=n.name,this.strategyId=n._id,this.learnable=n.learnable,this.staticWeight=n.staticWeight)}get strategyKey(){return this.constructor.name}async getStrategyState(){if(!this.user||!this.course)throw Error(`Cannot get strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.getStrategyState(this.course.getCourseID(),this.strategyKey)}async putStrategyState(e){if(!this.user||!this.course)throw Error(`Cannot put strategy state: navigator not properly initialized. Ensure user and course are provided to constructor.`);return this.user.putStrategyState(this.course.getCourseID(),this.strategyKey,e)}static async create(e,t,n){let r=n.implementingClass,a=getRegisteredNavigator(r);if(a)return logger.debug(`[ContentNavigator.create] Using registered navigator: ${r}`),new a(e,t,n);logger.debug(`[ContentNavigator.create] Navigator not in registry, attempting dynamic import: ${r}`);let o;for(let e of[`.ts`,`.js`,``]){try{if(o=(await globImport_generators(`./generators/${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load generator ${r}${e}:`,t)}try{if(o=(await globImport_filters(`./filters/${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load filter ${r}${e}:`,t)}try{if(o=(await globImport(`./${r}${e}`)).default,o)break}catch(t){logger.debug(`Failed to load legacy ${r}${e}:`,t)}if(o)break}if(!o)throw Error(`Could not load navigator implementation for: ${r}`);return new o(e,t,n)}async getWeightedCards(e){throw Error(`${this.constructor.name} must implement getWeightedCards(). `)}setEphemeralHints(e){}}}});function randIntWeightedTowardZero(e){return Math.floor(Math.random()*Math.random()*Math.random()*e)}async function getCourseTagStubs(e){logger.debug(`Getting tag stubs for course: ${e}`);let t=await filterAllDocsByPrefix2(getCourseDB2(e),`TAG`.valueOf()+`-`);return t.rows.forEach(e=>{logger.debug(` Tag stub for doc: ${e.id}`)}),t}async function createTag(e,t,n){logger.debug(`Creating tag: ${t}...`);let r=getTagID(t);return await getCourseDB2(e).put({course:e,docType:`TAG`,name:t,snippet:``,taggedCards:[],wiki:``,author:n,_id:r})}async function updateTag(e){let t=await getTag(e.course,e.name);return await getCourseDB2(e.course).put({...e,_rev:t._rev})}async function getTag(e,t){let n=getTagID(t);return getCourseDB2(e).get(n)}async function removeTagFromCard(e,t,n){n=getTagID(n);let r=getCourseDB2(e),a=await r.get(n);return a.taggedCards=a.taggedCards.filter(e=>t!==e),r.put(a)}async function getAppliedTags(e,t){return await getCourseDB2(e).query(`getTags`,{startkey:t,endkey:t})}async function updateCredentialledCourseConfig(e,t){logger.debug(`Updating course config:
|
|
256
256
|
|
|
257
257
|
${JSON.stringify(t)}
|
|
258
258
|
`);let n=getCourseDB2(e),r=await getCredentialledCourseConfig(e);return await n.put({...t,_rev:r._rev})}function isSuccessRow(e){return`doc`in e&&e.doc!==null&&e.doc!==void 0}var CoursesDB,CourseDB,init_courseDB=__esm({"src/impl/couch/courseDB.ts"(){init_couch(),init_updateQueue(),init_types_legacy(),init_logger(),init_clientCache(),init_courseAPI(),init_courseLookupDB(),init_navigators(),init_PipelineAssembler(),init_defaults(),CoursesDB=class{constructor(e){_defineProperty(this,`_courseIDs`,void 0),e&&e.length>0?this._courseIDs=e:this._courseIDs=void 0}async getCourseList(){let e=await CourseLookup.allCourseWare();return logger.debug(`AllCourses: ${e.map(e=>e.name+`, `+e._id+`
|
|
@@ -340,10 +340,10 @@ Example:
|
|
|
340
340
|
window.skuilder.mixer.showLastMix()
|
|
341
341
|
window.skuilder.mixer.explainSourceBalance()
|
|
342
342
|
window.skuilder.mixer.compareScores()
|
|
343
|
-
`)}};function mountMixerDebugger(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},e.skuilder.mixer=mixerDebugAPI}mountMixerDebugger(),init_logger(),init_PipelineDebugger(),init_logger();var activeController=null;function registerActiveController(e){activeController=e}function getActiveController(){return activeController}var OVERLAY_ID=`skuilder-session-overlay`,POLL_MS=300,INLINE_THRESHOLD=5,SPINNER_FRAMES=[`⠋`,`⠙`,`⠹`,`⠸`,`⠼`,`⠴`,`⠦`,`⠧`,`⠇`,`⠏`],spinnerFrame=0,overlayEl=null,pollHandle=null,lastSnapshot=null,copyFlashUntil=0,minified=!1,expanded={
|
|
344
|
-
${e}`)}function headerHtml(){let e=Date.now()<copyFlashUntil;return`<div style="font-weight:600;color:#93c5fd;margin-bottom:${minified?0:4}px">${`<span data-copy style="cursor:pointer;float:right;font-weight:400;color:${e?`#86efac`:`#93c5fd`};border:1px solid currentColor;border-radius:4px;padding:0 4px;line-height:1.3">${e?`✓ copied`:`⎘ copy`}</span>`}${`<span data-min title="${minified?`Restore`:`Minify`}" style="cursor:pointer;float:right;font-weight:400;color:#93c5fd;border:1px solid currentColor;border-radius:4px;padding:0 5px;margin-right:4px;line-height:1.3">${minified?`▢`:`—`}</span>`}\u2699 SessionController</div>`}function replanHtml(e){return e.replanActive?`<div style="margin-bottom:6px;color:#fde047">${SPINNER_FRAMES[spinnerFrame%SPINNER_FRAMES.length]} replanning <span style="opacity:.85">[${esc(e.replanLabel??`(auto)`)}]</span></div>`:`<div style="margin-bottom:6px;opacity:.45">○ idle</div>`}function metaHtml(e){return`<div style="margin-bottom:6px">${[`time ${formatTime(e.secondsRemaining)}${e.hasCardGuarantee?` \xB7 <span style="color:#fbbf24">guarantee ${e.minCardsGuarantee}</span>`:``}`,`well-indicated left: ${e.wellIndicatedRemaining}`,`current: ${e.currentCard?esc(e.currentCard):`<span style="opacity:.6">—</span>`}`].map(e=>`<div>${e}</div>`).join(``)}</div>`}function hintsHtml(e){let t=[];return e&&(e.boostTags&&Object.keys(e.boostTags).length&&t.push(`boost: `+Object.entries(e.boostTags).map(([e,t])=>`${esc(e)}<span style="opacity:.6">\xD7${t}</span>`).join(`, `)),e.boostCards&&Object.keys(e.boostCards).length&&t.push(`boostCards: `+Object.entries(e.boostCards).map(([e,t])=>`${esc(e)}<span style="opacity:.6">\xD7${t}</span>`).join(`, `)),e.requireCards?.length&&t.push(`require: ${e.requireCards.map(esc).join(`, `)}`),e.requireTags?.length&&t.push(`requireTags: ${e.requireTags.map(esc).join(`, `)}`),e.excludeTags?.length&&t.push(`exclude: ${e.excludeTags.map(esc).join(`, `)}`),e.excludeCards?.length&&t.push(`excludeCards: ${e.excludeCards.map(esc).join(`, `)}`)),`<div style="margin-bottom:6px"><div style="color:#86efac">sessionHints</div>${t.length?t.map(e=>`<div style="margin-left:6px">${e}</div>`).join(``):`<div style="margin-left:6px;opacity:.6">none</div>`}</div>`}function queueHtml(e,t,n){let r=n.length>INLINE_THRESHOLD,a=r&&expanded[e],o=r?expanded[e]?`▾ `:`▸ `:``,s=n.dequeueCount?` <span style="opacity:.5">drawn ${n.dequeueCount}</span>`:``,c=r?`cursor:pointer;color:#f9a8d4`:`color:#f9a8d4`,l=`<div${r?` data-q="${e}"`:``} style="${c}">${o}${t}: ${n.length}${s}</div>`;if(!n.cards.length)return l+`<div style="margin:1px 0 6px 6px;opacity:.5">empty</div>`;let u=a?n.cards:n.cards.slice(0,INLINE_THRESHOLD),d=n.length-u.length,p=`<ol style="margin:2px 0 ${r?2:6}px 0;padding-left:20px">`+u.map(e=>`<li style="white-space:nowrap">${
|
|
345
|
-
`):` none`);let queueText=(e,n)=>{t.push(``),t.push(`${e}: ${n.length} (drawn ${n.dequeueCount})`),n.cards.forEach((e,n)=>t.push(` ${n+1}. ${e}`))};return queueText(`
|
|
346
|
-
`)}function formatTime(e){let t=Math.max(0,Math.round(e));return`${Math.floor(t/60)}:${(t%60).toString().padStart(2,`0`)}`}function esc(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`)}var activeSession=null,sessionHistory=[],MAX_HISTORY=5;function startSessionTracking(e,t
|
|
343
|
+
`)}};function mountMixerDebugger(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},e.skuilder.mixer=mixerDebugAPI}mountMixerDebugger(),init_logger(),init_PipelineDebugger(),init_SrsDebugger(),init_logger();var activeController=null;function registerActiveController(e){activeController=e}function getActiveController(){return activeController}var OVERLAY_ID=`skuilder-session-overlay`,POLL_MS=300,INLINE_THRESHOLD=5,SPINNER_FRAMES=[`⠋`,`⠙`,`⠹`,`⠸`,`⠼`,`⠴`,`⠦`,`⠧`,`⠇`,`⠏`],spinnerFrame=0,overlayEl=null,pollHandle=null,lastSnapshot=null,copyFlashUntil=0,minified=!1,expanded={supplyQ:!1,failedQ:!1,drawn:!1};function toggleSessionOverlay(){if(typeof document>`u`){logger.info(`[Session Overlay] No DOM available (non-browser host); overlay unavailable.`);return}overlayEl?(teardown(),logger.info(`[Session Overlay] Hidden.`)):(mount(),logger.info(`[Session Overlay] Shown. Toggle off with window.skuilder.session.dbgOverlay().`))}function mount(){minified=!1,overlayEl=document.createElement(`div`),overlayEl.id=OVERLAY_ID,Object.assign(overlayEl.style,{position:`fixed`,top:`8px`,left:`8px`,zIndex:`2147483647`,maxWidth:`320px`,maxHeight:`90vh`,overflowY:`auto`,padding:`8px 10px`,background:`rgba(17, 24, 39, 0.92)`,color:`#e5e7eb`,font:`11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace`,borderRadius:`6px`,boxShadow:`0 4px 16px rgba(0,0,0,0.4)`,pointerEvents:`auto`,userSelect:`none`}),document.body.appendChild(overlayEl),render(),pollHandle=setInterval(render,POLL_MS)}function teardown(){pollHandle!==null&&(clearInterval(pollHandle),pollHandle=null),overlayEl?.parentNode&&overlayEl.parentNode.removeChild(overlayEl),overlayEl=null}function render(){if(!overlayEl)return;spinnerFrame++;let e=getActiveController();if(!e){lastSnapshot=null,overlayEl.innerHTML=headerHtml()+(minified?``:`<div style="opacity:.65">No active session.</div>`),attachHandlers();return}let t=e.getDebugSnapshot();if(lastSnapshot=t,minified){overlayEl.innerHTML=headerHtml(),attachHandlers();return}overlayEl.innerHTML=headerHtml()+replanHtml(t)+metaHtml(t)+hintsHtml(t.sessionHints)+backlogHtml(t.reviewBacklog)+queueHtml(`supplyQ`,`supplyQ`,t.supplyQ)+queueHtml(`failedQ`,`failedQ`,t.failedQ)+drawnHtml(`drawn`,t.drawnCards),attachHandlers()}function attachHandlers(){if(!overlayEl)return;overlayEl.querySelectorAll(`[data-q]`).forEach(e=>{e.onclick=()=>{let t=e.dataset.q;t&&(expanded[t]=!expanded[t],render())}});let e=overlayEl.querySelector(`[data-copy]`);e&&(e.onclick=e=>{e.stopPropagation(),copySnapshot()});let t=overlayEl.querySelector(`[data-min]`);t&&(t.onclick=e=>{e.stopPropagation(),minified=!minified,render()})}function copySnapshot(){let e=snapshotToText(lastSnapshot),flash=()=>{copyFlashUntil=Date.now()+1200,render()},t=typeof navigator<`u`?navigator.clipboard:void 0;t?.writeText?t.writeText(e).then(flash,e=>{logger.warn(`[Session Overlay] Clipboard write failed: ${String(e)}`)}):logger.info(`[Session Overlay] Clipboard unavailable; snapshot follows:
|
|
344
|
+
${e}`)}function headerHtml(){let e=Date.now()<copyFlashUntil;return`<div style="font-weight:600;color:#93c5fd;margin-bottom:${minified?0:4}px">${`<span data-copy style="cursor:pointer;float:right;font-weight:400;color:${e?`#86efac`:`#93c5fd`};border:1px solid currentColor;border-radius:4px;padding:0 4px;line-height:1.3">${e?`✓ copied`:`⎘ copy`}</span>`}${`<span data-min title="${minified?`Restore`:`Minify`}" style="cursor:pointer;float:right;font-weight:400;color:#93c5fd;border:1px solid currentColor;border-radius:4px;padding:0 5px;margin-right:4px;line-height:1.3">${minified?`▢`:`—`}</span>`}\u2699 SessionController</div>`}function replanHtml(e){return e.replanActive?`<div style="margin-bottom:6px;color:#fde047">${SPINNER_FRAMES[spinnerFrame%SPINNER_FRAMES.length]} replanning <span style="opacity:.85">[${esc(e.replanLabel??`(auto)`)}]</span></div>`:`<div style="margin-bottom:6px;opacity:.45">○ idle</div>`}function metaHtml(e){return`<div style="margin-bottom:6px">${[`time ${formatTime(e.secondsRemaining)}${e.hasCardGuarantee?` \xB7 <span style="color:#fbbf24">guarantee ${e.minCardsGuarantee}</span>`:``}`,`well-indicated left: ${e.wellIndicatedRemaining}`,`current: ${e.currentCard?esc(e.currentCard):`<span style="opacity:.6">—</span>`}`].map(e=>`<div>${e}</div>`).join(``)}</div>`}function hintsHtml(e){let t=[];return e&&(e.boostTags&&Object.keys(e.boostTags).length&&t.push(`boost: `+Object.entries(e.boostTags).map(([e,t])=>`${esc(e)}<span style="opacity:.6">\xD7${t}</span>`).join(`, `)),e.boostCards&&Object.keys(e.boostCards).length&&t.push(`boostCards: `+Object.entries(e.boostCards).map(([e,t])=>`${esc(e)}<span style="opacity:.6">\xD7${t}</span>`).join(`, `)),e.requireCards?.length&&t.push(`require: ${e.requireCards.map(esc).join(`, `)}`),e.requireTags?.length&&t.push(`requireTags: ${e.requireTags.map(esc).join(`, `)}`),e.excludeTags?.length&&t.push(`exclude: ${e.excludeTags.map(esc).join(`, `)}`),e.excludeCards?.length&&t.push(`excludeCards: ${e.excludeCards.map(esc).join(`, `)}`)),`<div style="margin-bottom:6px"><div style="color:#86efac">sessionHints</div>${t.length?t.map(e=>`<div style="margin-left:6px">${e}</div>`).join(``):`<div style="margin-left:6px;opacity:.6">none</div>`}</div>`}function backlogHtml(e){return e.length?`<div style="margin-bottom:6px"><div style="color:#93c5fd">review backpressure</div>${e.map(e=>{let t=e.backlogMultiplier>=e.maxBacklogMultiplier-1e-9,n=e.backlogMultiplier<=1?`#86efac`:t?`#fca5a5`:`#fcd34d`,r=t?`maxed — boosts decide order until they relax`:e.backlogMultiplier>1?`climbing as backlog grows`:`healthy — no pressure`,a=e.topReviewScore===null?`—`:e.topReviewScore.toFixed(2),o=e.nextDueIn?` <span style="opacity:.6">\xB7 next due ${esc(e.nextDueIn)}</span>`:``;return`<div style="margin-left:6px"><span style="opacity:.7">${esc(e.courseId.slice(0,8))}</span> due ${e.dueNow}/${e.scheduledTotal} <span style="opacity:.6">(healthy ${e.healthyBacklog})</span>${o}<div style="margin-left:6px">pressure <span style="color:${n}">\xD7${e.backlogMultiplier.toFixed(2)}/${e.maxBacklogMultiplier.toFixed(2)}</span> <span style="opacity:.6">${r} \xB7 top review ${a}</span></div></div>`}).join(``)}</div>`:``}function fmtScore(e){return e===void 0?``:Number.isFinite(e)?e.toFixed(2):`REQ`}function queueItemHtml(e){let t=e.origin===`review`?`#93c5fd`:`#fcd34d`,n=fmtScore(e.score),r=`${e.origin===`review`?`r`:`n`}${n?` `+n:``}`;return`${esc(e.cardID)}<span style="color:${t};opacity:.85"> [${r}]</span>`}function queueHtml(e,t,n){let r=n.length>INLINE_THRESHOLD,a=r&&expanded[e],o=r?expanded[e]?`▾ `:`▸ `:``,s=n.dequeueCount?` <span style="opacity:.5">drawn ${n.dequeueCount}</span>`:``,c=r?`cursor:pointer;color:#f9a8d4`:`color:#f9a8d4`,l=`<div${r?` data-q="${e}"`:``} style="${c}">${o}${t}: ${n.length}${s}</div>`;if(!n.cards.length)return l+`<div style="margin:1px 0 6px 6px;opacity:.5">empty</div>`;let u=a?n.cards:n.cards.slice(0,INLINE_THRESHOLD),d=n.length-u.length,p=`<ol style="margin:2px 0 ${r?2:6}px 0;padding-left:20px">`+u.map(e=>`<li style="white-space:nowrap">${queueItemHtml(e)}</li>`).join(``)+`</ol>`;if(r){let t=a?`▾ show less`:`\u2026 +${d} more`;p+=`<div data-q="${e}" style="cursor:pointer;margin:0 0 6px 20px;opacity:.6">${t}</div>`}return l+p}function outcomeGlyph(e){return e===!0?`<span style="color:#86efac">✓</span>`:e===!1?`<span style="color:#fca5a5">✗</span>`:`<span style="opacity:.5">·</span>`}function drawnHtml(e,t){let n=t.length>INLINE_THRESHOLD,r=n&&expanded[e],a=n?expanded[e]?`▾ `:`▸ `:``,o=n?`cursor:pointer;color:#c4b5fd`:`color:#c4b5fd`,s=`<div${n?` data-q="${e}"`:``} style="${o}">${a}drawn: ${t.length}</div>`;if(!t.length)return s+`<div style="margin:1px 0 6px 6px;opacity:.5">none yet</div>`;let c=r?t:t.slice(0,INLINE_THRESHOLD),l=t.length-c.length,u=`<ol style="margin:2px 0 ${n?2:6}px 0;padding-left:20px">${c.map(e=>{let t=e.attempts>1?`<span style="opacity:.5"> \xD7${e.attempts}</span>`:``,n=`<span style="opacity:.45"> ${Math.round(e.timeSpentMs/100)/10}s</span>`;return`<li style="white-space:nowrap">${outcomeGlyph(e.correct)} ${esc(e.cardID)}<span style="opacity:.5"> [${esc(e.status)}]</span>${t}${n}</li>`}).join(``)}</ol>`;if(n){let t=r?`▾ show less`:`\u2026 +${l} more`;u+=`<div data-q="${e}" style="cursor:pointer;margin:0 0 6px 20px;opacity:.6">${t}</div>`}return s+u}function snapshotToText(e){if(!e)return`SessionController — no active session.`;let t=[];t.push(`=== SessionController ===`),t.push(`time ${formatTime(e.secondsRemaining)}`),e.hasCardGuarantee&&t.push(`guarantee: ${e.minCardsGuarantee}`),t.push(`well-indicated left: ${e.wellIndicatedRemaining}`),t.push(`current: ${e.currentCard??`—`}`),t.push(e.replanActive?`replan: ACTIVE [${e.replanLabel??`(auto)`}]`:`replan: idle`),t.push(``),t.push(`sessionHints:`);let n=e.sessionHints,r=[];if(n&&(n.boostTags&&Object.keys(n.boostTags).length&&r.push(` boost: ${Object.entries(n.boostTags).map(([e,t])=>`${e}\xD7${t}`).join(`, `)}`),n.boostCards&&Object.keys(n.boostCards).length&&r.push(` boostCards: ${Object.entries(n.boostCards).map(([e,t])=>`${e}\xD7${t}`).join(`, `)}`),n.requireCards?.length&&r.push(` require: ${n.requireCards.join(`, `)}`),n.requireTags?.length&&r.push(` requireTags: ${n.requireTags.join(`, `)}`),n.excludeTags?.length&&r.push(` exclude: ${n.excludeTags.join(`, `)}`),n.excludeCards?.length&&r.push(` excludeCards: ${n.excludeCards.join(`, `)}`)),t.push(r.length?r.join(`
|
|
345
|
+
`):` none`),e.reviewBacklog.length){t.push(``),t.push(`review backpressure:`);for(let n of e.reviewBacklog){let e=n.backlogMultiplier>=n.maxBacklogMultiplier-1e-9?`maxed (boosts decide order)`:n.backlogMultiplier>1?`climbing`:`healthy`,r=n.topReviewScore===null?`—`:n.topReviewScore.toFixed(2),a=n.nextDueIn?`, next due ${n.nextDueIn}`:``;t.push(` ${n.courseId.slice(0,8)}: due ${n.dueNow}/${n.scheduledTotal} (healthy ${n.healthyBacklog})${a}; pressure \xD7${n.backlogMultiplier.toFixed(2)}/${n.maxBacklogMultiplier.toFixed(2)} ${e}; top review ${r}`)}}let queueText=(e,n)=>{t.push(``),t.push(`${e}: ${n.length} (drawn ${n.dequeueCount})`),n.cards.forEach((e,n)=>{let r=fmtScore(e.score),a=`${e.origin===`review`?`r`:`n`}${r?` `+r:``}`;t.push(` ${n+1}. ${e.cardID} [${a}]`)})};return queueText(`supplyQ`,e.supplyQ),queueText(`failedQ`,e.failedQ),t.push(``),t.push(`drawn: ${e.drawnCards.length}`),e.drawnCards.forEach((e,n)=>{let r=e.correct===!0?`✓`:e.correct===!1?`✗`:`·`,a=`${Math.round(e.timeSpentMs/100)/10}s`;t.push(` ${n+1}. ${r} ${e.cardID} [${e.status}] \xD7${e.attempts} ${a}`)}),t.join(`
|
|
346
|
+
`)}function formatTime(e){let t=Math.max(0,Math.round(e));return`${Math.floor(t/60)}:${(t%60).toString().padStart(2,`0`)}`}function esc(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`)}var activeSession=null,sessionHistory=[],MAX_HISTORY=5;function startSessionTracking(e,t){clearRunHistory(),clearSrsBacklogDebug();let n=`session-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;activeSession={sessionId:n,startTime:new Date,initialQueues:{timestamp:new Date,supplyQLength:e,failedQLength:t},presentations:[],queueSnapshots:[]},logger.debug(`[SessionDebugger] Started tracking session: ${n}`)}function recordCardPresentation(e,t,n,r,a,o){if(!activeSession){logger.warn(`[SessionDebugger] No active session to record presentation`);return}activeSession.presentations.push({timestamp:new Date,sequenceNumber:activeSession.presentations.length+1,cardId:e,courseId:t,courseName:n,origin:r,queueSource:a,score:o})}function snapshotQueues(e,t,n){activeSession&&activeSession.queueSnapshots.push({timestamp:new Date,supplyQLength:e,failedQLength:t,supplyQNext3:n})}function endSessionTracking(){activeSession&&(activeSession.endTime=new Date,sessionHistory.unshift(activeSession),sessionHistory.length>MAX_HISTORY&&sessionHistory.pop(),logger.debug(`[SessionDebugger] Ended session: ${activeSession.sessionId}`),activeSession=null)}function showCurrentQueue(){if(!activeSession){logger.info(`[Session Debug] No active session.`);return}let e=activeSession.queueSnapshots[activeSession.queueSnapshots.length-1]||activeSession.initialQueues;console.group(`📊 Current Queue State`),logger.info(`Supply Queue: ${e.supplyQLength} cards`),e.supplyQNext3&&e.supplyQNext3.length>0&&logger.info(` Next: ${e.supplyQNext3.join(`, `)}`),logger.info(`Failed Queue: ${e.failedQLength} cards`),console.groupEnd()}function showPresentationHistory(e=0){let t=e===0&&activeSession?activeSession:sessionHistory[e];if(!t){logger.info(`[Session Debug] No session found at index ${e}`);return}console.group(`\u{1F4DC} Session History: ${t.sessionId}`),logger.info(`Started: ${t.startTime.toLocaleTimeString()}`),t.endTime&&logger.info(`Ended: ${t.endTime.toLocaleTimeString()}`),logger.info(`Cards presented: ${t.presentations.length}`),t.presentations.length>0&&console.table(t.presentations.map(e=>({"#":e.sequenceNumber,course:e.courseName||e.courseId.slice(0,8),origin:e.origin,queue:e.queueSource,score:e.score?.toFixed(3)||`-`,time:e.timestamp.toLocaleTimeString()}))),console.groupEnd()}function showInterleaving(e=0){let t=e===0&&activeSession?activeSession:sessionHistory[e];if(!t){logger.info(`[Session Debug] No session found at index ${e}`);return}console.group(`🔀 Interleaving Analysis`);let n=new Map,r=new Map;if(t.presentations.forEach(e=>{let t=e.courseName||e.courseId;n.set(t,(n.get(t)||0)+1),r.has(t)||r.set(t,{review:0,new:0,failed:0});let a=r.get(t);a[e.origin]++}),logger.info(`Course distribution:`),console.table(Array.from(n.entries()).map(([e,n])=>{let a=r.get(e);return{course:e,total:n,reviews:a.review,new:a.new,failed:a.failed,percentage:(n/t.presentations.length*100).toFixed(1)+`%`}})),t.presentations.length>0){logger.info(`
|
|
347
347
|
Presentation sequence (first 20):`);let e=t.presentations.slice(0,20).map((e,t)=>`${t+1}. ${e.courseName||e.courseId.slice(0,8)} (${e.origin})`).join(`
|
|
348
348
|
`);logger.info(e)}let a=0,o=1,s=t.presentations[0]?.courseId;for(let e=1;e<t.presentations.length;e++)t.presentations[e].courseId===s?(o++,a=Math.max(a,o)):(s=t.presentations[e].courseId,o=1);a>3&&(logger.info(`
|
|
349
349
|
\u26A0\uFE0F Detected clustering: max ${a} cards from same course in a row`),logger.info(`This suggests cards are sorted by score rather than round-robin by course.`)),console.groupEnd()}var sessionDebugAPI={get sessions(){return[...sessionHistory]},get active(){return activeSession},showQueue(){showCurrentQueue()},dbgOverlay(){toggleSessionOverlay()},showHistory(e=0){showPresentationHistory(e)},showInterleaving(e=0){showInterleaving(e)},listSessions(){if(activeSession&&logger.info(`Active session: ${activeSession.sessionId} (${activeSession.presentations.length} cards presented)`),sessionHistory.length===0){logger.info(`[Session Debug] No completed sessions in history.`);return}console.table(sessionHistory.map((e,t)=>({index:t,id:e.sessionId.slice(-8),started:e.startTime.toLocaleTimeString(),ended:e.endTime?.toLocaleTimeString()||`incomplete`,cards:e.presentations.length})))},export(){let e={active:activeSession,history:sessionHistory},t=JSON.stringify(e,null,2);return logger.info(`[Session Debug] Session data exported. Copy the returned string or use:`),logger.info(` copy(window.skuilder.session.export())`),t},clear(){sessionHistory.length=0,logger.info(`[Session Debug] Session history cleared.`)},help(){logger.info(`
|
|
@@ -365,16 +365,13 @@ Example:
|
|
|
365
365
|
window.skuilder.session.showHistory()
|
|
366
366
|
window.skuilder.session.showInterleaving()
|
|
367
367
|
window.skuilder.session.showQueue()
|
|
368
|
-
`)}};function mountSessionDebugger(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},e.skuilder.session=sessionDebugAPI}mountSessionDebugger(),init_logger();var SessionController=(_SessionController2=class _SessionController extends Loggable{set sessionRecord(e){this._sessionRecord=e}get secondsRemaining(){return this._secondsRemaining}get hasCardGuarantee(){return this._minCardsGuarantee>0}get report(){let e=this.
|
|
369
|
-
`+this.
|
|
370
|
-
`+this.failedQ.toString}constructor(e,t,n,r,a,o){super(),_defineProperty(this,`_className`,`SessionController`),_defineProperty(this,`services`,void 0),_defineProperty(this,`srsService`,void 0),_defineProperty(this,`eloService`,void 0),_defineProperty(this,`hydrationService`,void 0),_defineProperty(this,`mixer`,void 0),_defineProperty(this,`dataLayer`,void 0),_defineProperty(this,`courseNameCache`,new Map),_defineProperty(this,`_defaultBatchLimit`,20),_defineProperty(this,`_initialReviewCap`,200),_defineProperty(this,`sources`,void 0),_defineProperty(this,`_sessionRecord`,[]),_defineProperty(this,`_currentCard`,null),_defineProperty(this,`reviewQ`,new ItemQueue),_defineProperty(this,`newQ`,new ItemQueue),_defineProperty(this,`failedQ`,new ItemQueue),_defineProperty(this,`_replanPromise`,null),_defineProperty(this,`_activeReplanLabel`,null),_defineProperty(this,`_wellIndicatedRemaining`,0),_defineProperty(this,`_suppressQualityReplan`,!1),_defineProperty(this,`_minCardsGuarantee`,0),_defineProperty(this,`_sessionHints`,null),_defineProperty(this,`_servedCardIds`,new Set),_defineProperty(this,`_outcomeObservers`,[]),_defineProperty(this,`_sessionControls`,null),_defineProperty(this,`startTime`,void 0),_defineProperty(this,`endTime`,void 0),_defineProperty(this,`_secondsRemaining`,void 0),_defineProperty(this,`_intervalHandle`,void 0),this.dataLayer=n,this.mixer=a||new QuotaRoundRobinMixer,this.srsService=new SrsService(n.getUserDB()),this.eloService=new EloService(n,n.getUserDB()),this.hydrationService=new CardHydrationService(r,e=>n.getCourseDB(e),()=>this._getItemsToHydrate()),this.services={response:new ResponseProcessor(this.srsService,this.eloService)},this.sources=e,this.startTime=new Date,this._secondsRemaining=t,this.endTime=new Date(this.startTime.valueOf()+1e3*this._secondsRemaining),o?.defaultBatchLimit!==void 0&&(this._defaultBatchLimit=o.defaultBatchLimit),o?.initialReviewCap!==void 0&&(this._initialReviewCap=o.initialReviewCap),o?.outcomeObservers?.length&&(this._outcomeObservers=[...o.outcomeObservers]),this.log(`Session constructed:
|
|
368
|
+
`)}};function mountSessionDebugger(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},e.skuilder.session=sessionDebugAPI}mountSessionDebugger(),init_logger();var SessionController=(_SessionController2=class _SessionController extends Loggable{set sessionRecord(e){this._sessionRecord=e}get secondsRemaining(){return this._secondsRemaining}get hasCardGuarantee(){return this._minCardsGuarantee>0}get report(){let e=this.supplyQ.dequeueCount;return`${e} supply ${e===1?`card`:`cards`} drawn`}get detailedReport(){return this.supplyQ.toString+`
|
|
369
|
+
`+this.failedQ.toString}constructor(e,t,n,r,a,o){super(),_defineProperty(this,`_className`,`SessionController`),_defineProperty(this,`services`,void 0),_defineProperty(this,`srsService`,void 0),_defineProperty(this,`eloService`,void 0),_defineProperty(this,`hydrationService`,void 0),_defineProperty(this,`mixer`,void 0),_defineProperty(this,`dataLayer`,void 0),_defineProperty(this,`courseNameCache`,new Map),_defineProperty(this,`_defaultBatchLimit`,20),_defineProperty(this,`sources`,void 0),_defineProperty(this,`_sessionRecord`,[]),_defineProperty(this,`_currentCard`,null),_defineProperty(this,`supplyQ`,new ItemQueue),_defineProperty(this,`failedQ`,new ItemQueue),_defineProperty(this,`_supplyDrawsSinceFailed`,0),_defineProperty(this,`_replanPromise`,null),_defineProperty(this,`_activeReplanLabel`,null),_defineProperty(this,`_wellIndicatedRemaining`,0),_defineProperty(this,`_suppressQualityReplan`,!1),_defineProperty(this,`_minCardsGuarantee`,0),_defineProperty(this,`_sessionHints`,null),_defineProperty(this,`_servedCardIds`,new Set),_defineProperty(this,`_outcomeObservers`,[]),_defineProperty(this,`_sessionControls`,null),_defineProperty(this,`startTime`,void 0),_defineProperty(this,`endTime`,void 0),_defineProperty(this,`_secondsRemaining`,void 0),_defineProperty(this,`_intervalHandle`,void 0),this.dataLayer=n,this.mixer=a||new QuotaRoundRobinMixer,this.srsService=new SrsService(n.getUserDB()),this.eloService=new EloService(n,n.getUserDB()),this.hydrationService=new CardHydrationService(r,e=>n.getCourseDB(e),()=>this._getItemsToHydrate()),this.services={response:new ResponseProcessor(this.srsService,this.eloService)},this.sources=e,this.startTime=new Date,this._secondsRemaining=t,this.endTime=new Date(this.startTime.valueOf()+1e3*this._secondsRemaining),o?.defaultBatchLimit!==void 0&&(this._defaultBatchLimit=o.defaultBatchLimit),o?.outcomeObservers?.length&&(this._outcomeObservers=[...o.outcomeObservers]),this.log(`Session constructed:
|
|
371
370
|
startTime: ${this.startTime}
|
|
372
371
|
endTime: ${this.endTime}
|
|
373
|
-
defaultBatchLimit: ${this._defaultBatchLimit}
|
|
374
|
-
initialReviewCap: ${this._initialReviewCap}`),registerActiveController(this)}tick(){this._secondsRemaining=Math.floor((this.endTime.valueOf()-Date.now())/1e3),this._secondsRemaining<=0&&clearInterval(this._intervalHandle)}estimateCleanupTime(){let e=0;for(let t=0;t<this.failedQ.length;t++){let n=this.failedQ.peek(t),r=this._sessionRecord.find(e=>e.item.cardID===n.cardID),a=0;if(r){for(let e=0;e<r.records.length;e++)a+=r.records[e].timeSpent;a/=r.records.length,e+=a}}let t=e/1e3;return this.log(`Failed card cleanup estimate: ${Math.round(t)}`),t}estimateReviewTime(){let e=5*this.reviewQ.length;return this.log(`Review card time estimate: ${e}`),e}async prepareSession(){if(this.sources.some(e=>typeof e.getWeightedCards!=`function`))throw Error(`[SessionController] All content sources must implement getWeightedCards().`);let e=await this.getWeightedContent();this._wellIndicatedRemaining=e,e>=0&&e<_SessionController.MIN_WELL_INDICATED&&this.log(`[Init] Only ${e}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards in initial load`),await this.hydrationService.ensureHydratedCards(),startSessionTracking(this.reviewQ.length,this.newQ.length,this.failedQ.length),this._intervalHandle=setInterval(()=>{this.tick()},1e3)}async requestReplan(e){let t=this.normalizeReplanOptions(e),n=this._replanHasIntent(t);if(this._replanPromise){if(!n)return this.log(`Replan already in progress, coalescing unhinted auto-replan`),this._replanPromise;let e=t.label?` [${t.label}]`:``;this.log(`Replan in progress; queueing hint-bearing replan${e} behind in-flight run`);let r=this._replanPromise.catch(()=>void 0).then(()=>this._runReplan(t)),a=r.finally(()=>{this._replanPromise===a&&(this._replanPromise=null,this._activeReplanLabel=null)});return this._replanPromise=a,r}let r=this._runReplan(t),a=r.finally(()=>{this._replanPromise===a&&(this._replanPromise=null,this._activeReplanLabel=null)});this._replanPromise=a,await r}_replanHasIntent(e){return!!(e.limit!==void 0||e.minFollowUpCards!==void 0||e.mode&&e.mode!==`replace`||e.hints&&Object.keys(e.hints).length>0||e.sessionHints!==void 0||e.mergeSessionHints!==void 0)}async _runReplan(e){this._activeReplanLabel=e.label??`(auto)`,e.hints||(e.hints={});let t=e.hints,n=new Set(t.excludeCards??[]);this._currentCard?.item.cardID&&n.add(this._currentCard.item.cardID);for(let e of this._sessionRecord)n.add(e.card.card_id);this.newQ.length>0&&n.add(this.newQ.peek(0).cardID),t.excludeCards=[...n],e.sessionHints!==void 0&&(this._sessionHints=e.sessionHints,this.log(`[Replan] Session hints ${e.sessionHints?`set`:`cleared`}: ${JSON.stringify(e.sessionHints)}`)),e.mergeSessionHints!==void 0&&(this._sessionHints=mergeHints2([this._sessionHints,e.mergeSessionHints])??null,this.log(`[Replan] Session hints merged: ${JSON.stringify(this._sessionHints)}`)),this._applyHintsToSources(e.hints,e.label);let r=e.label?` [${e.label}]`:``;this.log(`Mid-session replan requested${r} (limit: ${e.limit??`default`}, mode: ${e.mode??`replace`}${e.hints?`, with hints`:``})`),e.minFollowUpCards!==void 0&&e.minFollowUpCards>0&&(this._minCardsGuarantee=Math.max(this._minCardsGuarantee,e.minFollowUpCards),this.log(`[Replan] Card guarantee set to ${this._minCardsGuarantee}`)),await this._executeReplan(e)}setSessionHints(e){this._sessionHints=e,this.log(`Session hints ${e?`set`:`cleared`}: ${JSON.stringify(e)}`)}getSessionHints(){return this._sessionHints}getDebugSnapshot(){let describe=e=>{let t=[];for(let n=0;n<e.length;n++)t.push(e.peek(n).cardID);return{length:e.length,dequeueCount:e.dequeueCount,cards:t}},e=this._sessionRecord.map(e=>{let t=e.records[e.records.length-1];return{cardID:e.item.cardID,status:e.item.status,attempts:e.records.length,correct:t&&isQuestionRecord(t)?t.isCorrect:null,timeSpentMs:e.records.reduce((e,t)=>e+t.timeSpent,0)}});return{secondsRemaining:this.secondsRemaining,hasCardGuarantee:this.hasCardGuarantee,minCardsGuarantee:this._minCardsGuarantee,wellIndicatedRemaining:this._wellIndicatedRemaining,currentCard:this._currentCard?.item.cardID??null,sessionHints:this._sessionHints,replanActive:this._replanPromise!==null,replanLabel:this._activeReplanLabel,reviewQ:describe(this.reviewQ),newQ:describe(this.newQ),failedQ:describe(this.failedQ),drawnCards:e}}mergeSessionHints(e){this._sessionHints=mergeHints2([this._sessionHints,e])??null,this.log(`Session hints merged: ${JSON.stringify(this._sessionHints)}`)}_applyHintsToSources(e,t){let n=e&&t?{...e,_label:t}:e,r=mergeHints2([this._sessionHints,n]);if(r)for(let e of this.sources)e.setEphemeralHints?.(r)}_getSessionControls(){return this._sessionControls||(this._sessionControls={getSessionHints:()=>this.getSessionHints(),setSessionHints:e=>this.setSessionHints(e),mergeSessionHints:e=>this.mergeSessionHints(e),requestReplan:e=>this.requestReplan(e)}),this._sessionControls}async _notifyOutcomeObservers(e,t,n){if(this._outcomeObservers.length===0||!isQuestionRecord(e))return;let r={record:e,card:t.card,result:n},a=this._getSessionControls();for(let e of this._outcomeObservers)try{await e(r,a)}catch(e){this.error(`[OutcomeObserver] observer threw; ignoring`,e)}}async _replanUncoalesced(e){let t=this._runReplan(e),n=t.finally(()=>{this._replanPromise===n&&(this._replanPromise=null,this._activeReplanLabel=null)});this._replanPromise=n,await t}normalizeReplanOptions(e){if(!e)return{};let t=[`hints`,`sessionHints`,`limit`,`mode`,`label`,`minFollowUpCards`];return Object.keys(e).some(e=>t.includes(e))?e:{hints:e}}async _executeReplan(e={}){let t=e.limit,n=e.mode??`replace`,r=await this.getWeightedContent({replan:!0,additive:n===`merge`,limit:t});this._wellIndicatedRemaining=r,t!==void 0&&t<this._defaultBatchLimit?(this._suppressQualityReplan=!0,this.log(`[Replan] Burst mode (limit=${t}): suppressing quality-based auto-replan`)):this._suppressQualityReplan=!1,r>=0&&r<_SessionController.MIN_WELL_INDICATED&&this.log(`[Replan] Only ${r}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`),await this.hydrationService.ensureHydratedCards();let a=e.label?` [${e.label}]`:``;this.log(`Replan complete${a}: newQ now has ${this.newQ.length} cards (mode=${n})`),snapshotQueues(this.reviewQ.length,this.newQ.length,this.failedQ.length)}addTime(e){this.endTime=new Date(this.endTime.valueOf()+1e3*e)}get failedCount(){return this.failedQ.length}toString(){return`Session: ${this.reviewQ.length} Reviews, ${this.newQ.length} New, ${this.failedQ.length} failed`}reportString(){return`${this.reviewQ.dequeueCount} Reviews, ${this.newQ.dequeueCount} New, ${this.failedQ.dequeueCount} failed`}getDebugInfo(){let e=this.sources.some(e=>typeof e.getWeightedCards==`function`),extractQueueItems=(e,t=10)=>{let n=[];for(let r=0;r<Math.min(e.length,t);r++){let t=e.peek(r);n.push({courseID:t.courseID||`unknown`,cardID:t.cardID||`unknown`,status:t.status||`unknown`})}return n};return{api:{mode:e?`weighted`:`legacy`,description:e?`Using getWeightedCards() API with scored candidates`:`ERROR: getWeightedCards() not a function.`},reviewQueue:{length:this.reviewQ.length,dequeueCount:this.reviewQ.dequeueCount,items:extractQueueItems(this.reviewQ)},newQueue:{length:this.newQ.length,dequeueCount:this.newQ.dequeueCount,items:extractQueueItems(this.newQ)},failedQueue:{length:this.failedQ.length,dequeueCount:this.failedQ.dequeueCount,items:extractQueueItems(this.failedQ)},hydratedCache:{count:this.hydrationService.hydratedCount,cardIds:this.hydrationService.getHydratedCardIds()},replan:{inProgress:this._replanPromise!==null,suppressQualityReplan:this._suppressQualityReplan,defaultBatchLimit:this._defaultBatchLimit,minCardsGuarantee:this._minCardsGuarantee}}}async getWeightedContent(e){let t=e?.replan??!1,n=e?.additive??!1,r=e?.limit??this._defaultBatchLimit,a=t?r:r+this._initialReviewCap;t||this._applyHintsToSources();let o=[];for(let e=0;e<this.sources.length;e++){let t=this.sources[e];try{let n=(await t.getWeightedCards(a)).cards;o.push({sourceIndex:e,weighted:n})}catch(t){if(this.error(`Failed to get content from source ${e}:`,t),this.sources.length===1)throw Error(`Cannot start session: failed to load content from source ${e}`)}}if(o.length===0){if(t)return this.log(`Replan: no content from any source, keeping existing newQ`),-1;throw Error(`Cannot start session: failed to load content from all ${this.sources.length} source(s). Check logs for details.`)}let s=this.mixer.mix(o,a*this.sources.length),c=o.map(e=>e.weighted[0]?.courseId||`source-${e.sourceIndex}`);await Promise.all(c.map(async e=>{if(!this.courseNameCache.has(e))try{let t=await this.dataLayer.getCoursesDB().getCourseConfig(e);this.courseNameCache.set(e,t.name)}catch{}}));let l=c.map(e=>this.courseNameCache.get(e)),u=this.mixer instanceof QuotaRoundRobinMixer?Math.ceil(a*this.sources.length/o.length):void 0;captureMixerRun(this.mixer.constructor.name,o,c,l,a*this.sources.length,u,s);let d=s.filter(e=>getCardOrigin(e)===`review`).slice(0,this._initialReviewCap),p=s.filter(e=>getCardOrigin(e)===`new`&&!this._servedCardIds.has(e.cardId)),m=p.filter(e=>e.score===1/0),g=p.filter(e=>e.score!==1/0),_=[...m,...g.slice(0,Math.max(0,r-m.length))],v=new Set(m.map(e=>e.cardId));logger.debug(`[reviews] got ${d.length} reviews from mixer`);let y=t?`Replan content:
|
|
372
|
+
defaultBatchLimit: ${this._defaultBatchLimit}`),registerActiveController(this)}tick(){this._secondsRemaining=Math.floor((this.endTime.valueOf()-Date.now())/1e3),this._secondsRemaining<=0&&clearInterval(this._intervalHandle)}estimateCleanupTime(){let e=0;for(let t=0;t<this.failedQ.length;t++){let n=this.failedQ.peek(t),r=this._sessionRecord.find(e=>e.item.cardID===n.cardID),a=0;if(r){for(let e=0;e<r.records.length;e++)a+=r.records[e].timeSpent;a/=r.records.length,e+=a}}let t=e/1e3;return this.log(`Failed card cleanup estimate: ${Math.round(t)}`),t}async prepareSession(){if(this.sources.some(e=>typeof e.getWeightedCards!=`function`))throw Error(`[SessionController] All content sources must implement getWeightedCards().`);let e=await this.getWeightedContent();this._wellIndicatedRemaining=e,e>=0&&e<_SessionController.MIN_WELL_INDICATED&&this.log(`[Init] Only ${e}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards in initial load`),await this.hydrationService.ensureHydratedCards(),startSessionTracking(this.supplyQ.length,this.failedQ.length),this._intervalHandle=setInterval(()=>{this.tick()},1e3)}async requestReplan(e){let t=this.normalizeReplanOptions(e),n=this._replanHasIntent(t);if(this._replanPromise){if(!n)return this.log(`Replan already in progress, coalescing unhinted auto-replan`),this._replanPromise;let e=t.label?` [${t.label}]`:``;this.log(`Replan in progress; queueing hint-bearing replan${e} behind in-flight run`);let r=this._replanPromise.catch(()=>void 0).then(()=>this._runReplan(t)),a=r.finally(()=>{this._replanPromise===a&&(this._replanPromise=null,this._activeReplanLabel=null)});return this._replanPromise=a,r}let r=this._runReplan(t),a=r.finally(()=>{this._replanPromise===a&&(this._replanPromise=null,this._activeReplanLabel=null)});this._replanPromise=a,await r}_replanHasIntent(e){return!!(e.limit!==void 0||e.minFollowUpCards!==void 0||e.mode&&e.mode!==`replace`||e.hints&&Object.keys(e.hints).length>0||e.sessionHints!==void 0||e.mergeSessionHints!==void 0)}async _runReplan(e){this._activeReplanLabel=e.label??`(auto)`,e.hints||(e.hints={});let t=e.hints,n=new Set(t.excludeCards??[]);this._currentCard?.item.cardID&&n.add(this._currentCard.item.cardID);for(let e of this._sessionRecord)n.add(e.card.card_id);this.supplyQ.length>0&&n.add(this.supplyQ.peek(0).cardID),t.excludeCards=[...n],e.sessionHints!==void 0&&(this._sessionHints=e.sessionHints,this.log(`[Replan] Session hints ${e.sessionHints?`set`:`cleared`}: ${JSON.stringify(e.sessionHints)}`)),e.mergeSessionHints!==void 0&&(this._sessionHints=mergeHints2([this._sessionHints,e.mergeSessionHints])??null,this.log(`[Replan] Session hints merged: ${JSON.stringify(this._sessionHints)}`)),this._applyHintsToSources(e.hints,e.label);let r=e.label?` [${e.label}]`:``;this.log(`Mid-session replan requested${r} (limit: ${e.limit??`default`}, mode: ${e.mode??`replace`}${e.hints?`, with hints`:``})`),e.minFollowUpCards!==void 0&&e.minFollowUpCards>0&&(this._minCardsGuarantee=Math.max(this._minCardsGuarantee,e.minFollowUpCards),this.log(`[Replan] Card guarantee set to ${this._minCardsGuarantee}`)),await this._executeReplan(e)}setSessionHints(e){this._sessionHints=e,this.log(`Session hints ${e?`set`:`cleared`}: ${JSON.stringify(e)}`)}getSessionHints(){return this._sessionHints}getDebugSnapshot(){let describe=e=>{let t=[];for(let n=0;n<e.length;n++){let r=e.peek(n);t.push({cardID:r.cardID,status:r.status,origin:isReview(r)?`review`:`new`,score:r.score})}return{length:e.length,dequeueCount:e.dequeueCount,cards:t}},e=this._sessionRecord.map(e=>{let t=e.records[e.records.length-1];return{cardID:e.item.cardID,status:e.item.status,attempts:e.records.length,correct:t&&isQuestionRecord(t)?t.isCorrect:null,timeSpentMs:e.records.reduce((e,t)=>e+t.timeSpent,0)}});return{secondsRemaining:this.secondsRemaining,hasCardGuarantee:this.hasCardGuarantee,minCardsGuarantee:this._minCardsGuarantee,wellIndicatedRemaining:this._wellIndicatedRemaining,currentCard:this._currentCard?.item.cardID??null,sessionHints:this._sessionHints,replanActive:this._replanPromise!==null,replanLabel:this._activeReplanLabel,supplyQ:describe(this.supplyQ),failedQ:describe(this.failedQ),reviewBacklog:getSrsBacklogDebug(),drawnCards:e}}mergeSessionHints(e){this._sessionHints=mergeHints2([this._sessionHints,e])??null,this.log(`Session hints merged: ${JSON.stringify(this._sessionHints)}`)}_applyHintsToSources(e,t){let n=e&&t?{...e,_label:t}:e,r=mergeHints2([this._sessionHints,n]);if(r)for(let e of this.sources)e.setEphemeralHints?.(r)}_getSessionControls(){return this._sessionControls||(this._sessionControls={getSessionHints:()=>this.getSessionHints(),setSessionHints:e=>this.setSessionHints(e),mergeSessionHints:e=>this.mergeSessionHints(e),requestReplan:e=>this.requestReplan(e)}),this._sessionControls}async _notifyOutcomeObservers(e,t,n){if(this._outcomeObservers.length===0||!isQuestionRecord(e))return;let r={record:e,card:t.card,result:n},a=this._getSessionControls();for(let e of this._outcomeObservers)try{await e(r,a)}catch(e){this.error(`[OutcomeObserver] observer threw; ignoring`,e)}}async _replanUncoalesced(e){let t=this._runReplan(e),n=t.finally(()=>{this._replanPromise===n&&(this._replanPromise=null,this._activeReplanLabel=null)});this._replanPromise=n,await t}normalizeReplanOptions(e){if(!e)return{};let t=[`hints`,`sessionHints`,`limit`,`mode`,`label`,`minFollowUpCards`];return Object.keys(e).some(e=>t.includes(e))?e:{hints:e}}async _executeReplan(e={}){let t=e.limit,n=e.mode??`replace`,r=await this.getWeightedContent({replan:!0,additive:n===`merge`,limit:t});this._wellIndicatedRemaining=r,t!==void 0&&t<this._defaultBatchLimit?(this._suppressQualityReplan=!0,this.log(`[Replan] Burst mode (limit=${t}): suppressing quality-based auto-replan`)):this._suppressQualityReplan=!1,r>=0&&r<_SessionController.MIN_WELL_INDICATED&&this.log(`[Replan] Only ${r}/${_SessionController.MIN_WELL_INDICATED} well-indicated cards after replan`),await this.hydrationService.ensureHydratedCards();let a=e.label?` [${e.label}]`:``;this.log(`Replan complete${a}: supplyQ now has ${this.supplyQ.length} cards (mode=${n})`),snapshotQueues(this.supplyQ.length,this.failedQ.length)}addTime(e){this.endTime=new Date(this.endTime.valueOf()+1e3*e)}get failedCount(){return this.failedQ.length}toString(){return`Session: ${this.supplyQ.length} supply, ${this.failedQ.length} failed`}reportString(){return`${this.supplyQ.dequeueCount} supply, ${this.failedQ.dequeueCount} failed`}getDebugInfo(){let e=this.sources.some(e=>typeof e.getWeightedCards==`function`),extractQueueItems=(e,t=10)=>{let n=[];for(let r=0;r<Math.min(e.length,t);r++){let t=e.peek(r);n.push({courseID:t.courseID||`unknown`,cardID:t.cardID||`unknown`,status:t.status||`unknown`,score:t.score})}return n};return{api:{mode:e?`weighted`:`legacy`,description:e?`Using getWeightedCards() API with scored candidates`:`ERROR: getWeightedCards() not a function.`},supplyQueue:{length:this.supplyQ.length,dequeueCount:this.supplyQ.dequeueCount,items:extractQueueItems(this.supplyQ)},failedQueue:{length:this.failedQ.length,dequeueCount:this.failedQ.dequeueCount,items:extractQueueItems(this.failedQ)},hydratedCache:{count:this.hydrationService.hydratedCount,cardIds:this.hydrationService.getHydratedCardIds()},replan:{inProgress:this._replanPromise!==null,suppressQualityReplan:this._suppressQualityReplan,defaultBatchLimit:this._defaultBatchLimit,minCardsGuarantee:this._minCardsGuarantee}}}async getWeightedContent(e){let t=e?.replan??!1,n=e?.additive??!1,r=e?.limit??this._defaultBatchLimit,a=r;t||this._applyHintsToSources();let o=[];for(let e=0;e<this.sources.length;e++){let t=this.sources[e];try{let n=(await t.getWeightedCards(a)).cards;o.push({sourceIndex:e,weighted:n})}catch(t){if(this.error(`Failed to get content from source ${e}:`,t),this.sources.length===1)throw Error(`Cannot start session: failed to load content from source ${e}`)}}if(o.length===0){if(t)return this.log(`Replan: no content from any source, keeping existing supplyQ`),-1;throw Error(`Cannot start session: failed to load content from all ${this.sources.length} source(s). Check logs for details.`)}let s=this.mixer.mix(o,a*this.sources.length),c=o.map(e=>e.weighted[0]?.courseId||`source-${e.sourceIndex}`);await Promise.all(c.map(async e=>{if(!this.courseNameCache.has(e))try{let t=await this.dataLayer.getCoursesDB().getCourseConfig(e);this.courseNameCache.set(e,t.name)}catch{}}));let l=c.map(e=>this.courseNameCache.get(e)),u=this.mixer instanceof QuotaRoundRobinMixer?Math.ceil(a*this.sources.length/o.length):void 0;captureMixerRun(this.mixer.constructor.name,o,c,l,a*this.sources.length,u,s);let d=s.filter(e=>!this._servedCardIds.has(e.cardId)),p=d.filter(e=>e.score===1/0),m=d.filter(e=>e.score!==1/0),g=[...p,...m.slice(0,Math.max(0,r-p.length))],_=new Set(p.map(e=>e.cardId)),v=g.filter(e=>e.score>=_SessionController.WELL_INDICATED_SCORE).length,y=t?`Replan content:
|
|
375
373
|
`:`Mixed content session created with:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
`}if(n){let e=this.
|
|
379
|
-
|
|
380
|
-
//# sourceMappingURL=dist-DHNWv7su.js.map
|
|
374
|
+
`,b=g.map(e=>{let t=getCardOrigin(e),n=Number.isFinite(e.score)?e.score.toFixed(2):`+INF`;return y+=`${t===`review`?`Review`:`New`}: ${e.courseId}::${e.cardId} (score: ${n})
|
|
375
|
+
`,this._buildSupplyItem(e,t)});if(n){let e=this.supplyQ.mergeToFront(b,e=>e.cardID,_);y+=`Additive merge: ${e} cards added to front of supplyQ
|
|
376
|
+
`}else if(t)this.supplyQ.replaceAll(b,e=>e.cardID);else for(let e of b)this.supplyQ.add(e,e.cardID);return this.log(y),v}_buildSupplyItem(e,t=getCardOrigin(e)){let n={cardID:e.cardId,courseID:e.courseId,contentSourceType:`course`,contentSourceID:e.courseId,score:e.score};return t===`review`?{...n,status:`review`,reviewID:e.reviewID}:{...n,status:`new`}}_getItemsToHydrate(){let e=[],t=3,n=2;for(let t=0;t<Math.min(3,this.supplyQ.length);t++)e.push(this.supplyQ.peek(t));for(let t=0;t<Math.min(2,this.failedQ.length);t++)e.push(this.failedQ.peek(t));return e}_selectNextItemToHydrate(){if(this.supplyQ.length===0&&this.failedQ.length===0||this._secondsRemaining<2&&this.failedQ.length===0&&this._minCardsGuarantee<=0)return null;if(this._secondsRemaining<=0&&this._minCardsGuarantee<=0)return this.failedQ.length>0?this.failedQ.peek(0):null;let e=this.supplyQ.length>0?this.supplyQ.peek(0):null;return this._minCardsGuarantee>0&&e?e:this.failedQ.length>0&&this._shouldInterleaveFailed(e!==null)?this.failedQ.peek(0):e||(this.failedQ.length>0?this.failedQ.peek(0):(this.log(`No more cards available for the session!`),null))}_shouldInterleaveFailed(e){return this.failedQ.length===0?!1:!e||this._secondsRemaining-this.estimateCleanupTime()<=_SessionController.FAILED_ENDGAME_SLACK_SECONDS?!0:this._supplyDrawsSinceFailed>=_SessionController.FAILED_INTERLEAVE_EVERY}async nextCard(e=`dismiss-success`){if(this.dismissCurrentCard(e),this._minCardsGuarantee>0&&(this._minCardsGuarantee--,this.log(`[CardGuarantee] ${this._minCardsGuarantee} guaranteed cards remaining`)),this._replanPromise&&this.supplyQ.length===0&&this.failedQ.length===0&&(this.log(`nextCard: queues empty, awaiting in-flight replan before drawing`),await this._replanPromise),this.supplyQ.length<=_SessionController.DEPLETION_PREFETCH_THRESHOLD&&this._secondsRemaining>0&&!this._replanPromise&&(this._suppressQualityReplan=!1,this.log(`[AutoReplan:depletion] supplyQ has ${this.supplyQ.length} card(s) (${this.failedQ.length} failed pending) with ${this._secondsRemaining}s remaining. Triggering background replan.`),this.requestReplan({label:`auto:depletion`,mode:`merge`})),!this._suppressQualityReplan&&this._wellIndicatedRemaining<=3&&this.supplyQ.length>0&&!this._replanPromise&&(this.log(`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (supplyQ: ${this.supplyQ.length}). Triggering background replan.`),this.requestReplan({label:`auto:quality`})),this._secondsRemaining<=0&&this.failedQ.length===0&&this._minCardsGuarantee<=0)return this._currentCard=null,endSessionTracking(),null;let t=3,n=250,r=0;for(;this._secondsRemaining>0&&this.supplyQ.length===0&&this.failedQ.length===0;)if(this.log(`[WedgeBreaker] All queues empty with ${this._secondsRemaining}s remaining. Running pipeline (attempt ${r+1}/3).`),await this._replanUncoalesced({label:`wedge-breaker`}),this.supplyQ.length===0&&this.failedQ.length===0){if(r++,r>=3){this.log(`[WedgeBreaker] Pipeline returned no content 3 consecutive times. Giving up; session will end.`);break}await new Promise(e=>setTimeout(e,250))}else r=0;let a=20;for(let e=0;e<20;e++){let e=this._selectNextItemToHydrate();if(!e)return this._currentCard=null,endSessionTracking(),null;let t=this.hydrationService.getHydratedCard(e.cardID);if(t||(t=await this.hydrationService.waitForCard(e.cardID)),this.removeItemFromQueue(e),t){await this.hydrationService.ensureHydratedCards(),this._currentCard=t;let n=e.status===`review`||e.status===`failed-review`?`review`:e.status===`new`||e.status===`failed-new`?`new`:`failed`,r=e.status.startsWith(`failed`)?`failedQ`:`supplyQ`;return recordCardPresentation(e.cardID,e.courseID,this.courseNameCache.get(e.courseID),n,r,e.score),snapshotQueues(this.supplyQ.length,this.failedQ.length),t}this.log(`Skipping card ${e.cardID}: hydration failed, trying next`),isReview(e)&&this.srsService.removeReview(e.reviewID)}return this.log(`Exhausted 20 skip attempts finding a hydratable card`),this._currentCard=null,endSessionTracking(),null}async submitResponse(e,t,n,r,a,o,s,c,l){let u={...r.item},d=await this.services.response.processResponse(e,t,u,n,r,a,o,s,c,l);return await this._notifyOutcomeObservers(e,r,d),d}dismissCurrentCard(e=`dismiss-success`){if(this._currentCard)if(e===`dismiss-success`)this.hydrationService.removeCard(this._currentCard.item.cardID);else if(e===`marked-failed`){let e;e=isReview(this._currentCard.item)?{cardID:this._currentCard.item.cardID,courseID:this._currentCard.item.courseID,contentSourceID:this._currentCard.item.contentSourceID,contentSourceType:this._currentCard.item.contentSourceType,status:`failed-review`,reviewID:this._currentCard.item.reviewID}:{cardID:this._currentCard.item.cardID,courseID:this._currentCard.item.courseID,contentSourceID:this._currentCard.item.contentSourceID,contentSourceType:this._currentCard.item.contentSourceType,status:`failed-new`},this.failedQ.add(e,e.cardID),this._supplyDrawsSinceFailed=0}else (e===`dismiss-error`||e===`dismiss-failed`)&&this.hydrationService.removeCard(this._currentCard.item.cardID)}removeItemFromQueue(e){this._clearDurableRequirement(e.cardID),this._servedCardIds.add(e.cardID),this.failedQ.peek(0)?.cardID===e.cardID?(this.failedQ.dequeue(e=>e.cardID),this._supplyDrawsSinceFailed=0):this.supplyQ.peek(0)?.cardID===e.cardID&&(this.supplyQ.dequeue(e=>e.cardID),this._supplyDrawsSinceFailed++,this._wellIndicatedRemaining>0&&this._wellIndicatedRemaining--)}_clearDurableRequirement(e){let t=this._sessionHints?.requireCards;if(!t||t.length===0)return;let n=t.filter(t=>t!==e);n.length!==t.length&&(this._sessionHints={...this._sessionHints,requireCards:n.length>0?n:void 0},this.log(`[Replan] Durable requirement satisfied & cleared on draw: ${e}`))}async endSession(){if(!this._sessionRecord||this._sessionRecord.length===0)return;let e=this._sessionRecord.flatMap(e=>e.records).filter(e=>e.userAnswer!==void 0);if(e.length===0)return;let t=null,n=[];for(let e of this.sources)if(e.getOrchestrationContext){try{t=await e.getOrchestrationContext(),e.getStrategyIds&&n.push(...e.getStrategyIds())}catch(e){logger.warn(`[SessionController] Failed to get orchestration context: ${e}`)}if(t)break}if(!t){logger.debug(`[SessionController] No orchestration context available, skipping outcome recording`);return}let r=new Date().toISOString(),a=new Date(this.startTime).toISOString();await recordUserOutcome(t,a,r,e,n)}},_defineProperty(_SessionController2,`MIN_WELL_INDICATED`,5),_defineProperty(_SessionController2,`WELL_INDICATED_SCORE`,.1),_defineProperty(_SessionController2,`DEPLETION_PREFETCH_THRESHOLD`,3),_defineProperty(_SessionController2,`FAILED_INTERLEAVE_EVERY`,4),_defineProperty(_SessionController2,`FAILED_ENDGAME_SLACK_SECONDS`,20),_SessionController2);init_TagFilteredContentSource(),init_factory();export{mountMixerDebugger as $,getActivePipeline as A,getStudySource as B,computeEffectiveWeight as C,validateStaticCourse as Ct,diversityRerank as D,createOrchestrationContext as E,getDbPath as F,initializeNavigatorRegistry as G,importParsedCards as H,getRegisteredNavigator as I,isQuestionRecord as J,isFilter as K,getRegisteredNavigatorNames as L,getCardHistoryID as M,getCardOrigin as N,endSessionTracking as O,getDataLayer as P,mixerDebugAPI as Q,getRegisteredNavigatorRole as R,computeDeviation as S,validateProcessorConfig as St,computeSpread as T,initializeDataDirectory as U,hasRegisteredNavigator as V,initializeDataLayer as W,isReview as X,isQuestionTypeRegistered as Y,log as Z,TagFilteredContentSource as _,startSessionTracking as _t,DocType as a,processCustomQuestionsData as at,captureMixerRun as b,userDBDebugAPI as bt,FileSystemError as c,registerDataShape as ct,NOT_SET as d,registerSeedData as dt,mountPipelineDebugger as et,NavigatorRole as f,removeDataShape as ft,SessionController as g,snapshotQueues as gt,QuotaRoundRobinMixer as h,sessionDebugAPI as ht,DIVERSITY_STRENGTH as i,pipelineDebugAPI as it,getAppDataDirectory as j,ensureAppDataDirectory as k,GuestUsername as l,registerNavigator as lt,Navigators as m,scoreAccuracyInZone as mt,CourseLookup as n,mountUserDBDebugger as nt,DocTypePrefixes as o,recordCardPresentation as ot,NavigatorRoles as p,removeQuestionType as pt,isGenerator as q,DIVERSITY_FLOOR as r,newInterval as rt,ENV as s,recordUserOutcome as st,ContentNavigator as t,mountSessionDebugger as tt,Loggable as u,registerQuestionType as ut,areQuestionRecords as v,updateLearningState as vt,computeOutcomeSignal as w,clearSrsBacklogDebug as x,validateMigration as xt,buildStrategyStateId as y,updateStrategyWeight as yt,getSrsBacklogDebug as z};
|
|
377
|
+
//# sourceMappingURL=dist-C6pY-D6H.js.map
|