@vue-skuilder/platform-ui 0.2.1 → 0.2.2
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-DzAqw5dJ.js → About-CPH179ge.js} +2 -2
- package/dist/assets/{About-DzAqw5dJ.js.map → About-CPH179ge.js.map} +1 -1
- package/dist/assets/{AdminDashboard-CO0lYTsv.js → AdminDashboard-CM9f6x9-.js} +2 -2
- package/dist/assets/{AdminDashboard-CO0lYTsv.js.map → AdminDashboard-CM9f6x9-.js.map} +1 -1
- package/dist/assets/{ClassroomCtrlPanel-Dd75Twma.js → ClassroomCtrlPanel-5_2ZFb9g.js} +2 -2
- package/dist/assets/{ClassroomCtrlPanel-Dd75Twma.js.map → ClassroomCtrlPanel-5_2ZFb9g.js.map} +1 -1
- package/dist/assets/{Classrooms-DpCrSz8x.js → Classrooms-DS7Kw-Ds.js} +2 -2
- package/dist/assets/{Classrooms-DpCrSz8x.js.map → Classrooms-DS7Kw-Ds.js.map} +1 -1
- package/dist/assets/{CourseRouter-CDps4Tv0.js → CourseRouter-BZznEUML.js} +2 -2
- package/dist/assets/{CourseRouter-CDps4Tv0.js.map → CourseRouter-BZznEUML.js.map} +1 -1
- package/dist/assets/{CourseWare-BTFRjgBR-5SlCJurn.js → CourseWare-BTFRjgBR-CWxKpdWK.js} +3 -3
- package/dist/assets/{CourseWare-BTFRjgBR-5SlCJurn.js.map → CourseWare-BTFRjgBR-CWxKpdWK.js.map} +1 -1
- package/dist/assets/{Courses-CULyjBZw.js → Courses-Codenyvm.js} +2 -2
- package/dist/assets/{Courses-CULyjBZw.js.map → Courses-Codenyvm.js.map} +1 -1
- package/dist/assets/{EloModeration-Dt19J8zM.js → EloModeration-Norc4J12.js} +2 -2
- package/dist/assets/{EloModeration-Dt19J8zM.js.map → EloModeration-Norc4J12.js.map} +1 -1
- package/dist/assets/{JoinCode-BfKCVkCZ.js → JoinCode-DYDkO1h0.js} +2 -2
- package/dist/assets/{JoinCode-BfKCVkCZ.js.map → JoinCode-DYDkO1h0.js.map} +1 -1
- package/dist/assets/{NewCourseDialog-BiZeVKaf.js → NewCourseDialog-DwYz66VG.js} +2 -2
- package/dist/assets/{NewCourseDialog-BiZeVKaf.js.map → NewCourseDialog-DwYz66VG.js.map} +1 -1
- package/dist/assets/{ReleaseNotes-rpv55fv8.js → ReleaseNotes-CLsQtFIs.js} +2 -2
- package/dist/assets/{ReleaseNotes-rpv55fv8.js.map → ReleaseNotes-CLsQtFIs.js.map} +1 -1
- package/dist/assets/{RequestPasswordReset-DT5GqiRk.js → RequestPasswordReset-Cn-RmWf3.js} +2 -2
- package/dist/assets/{RequestPasswordReset-DT5GqiRk.js.map → RequestPasswordReset-Cn-RmWf3.js.map} +1 -1
- package/dist/assets/{ResetPassword-CmpWBFZz.js → ResetPassword-D7IDX2em.js} +2 -2
- package/dist/assets/{ResetPassword-CmpWBFZz.js.map → ResetPassword-D7IDX2em.js.map} +1 -1
- package/dist/assets/{Study-BeDOIC_S.js → Study-DBNOEhLk.js} +2 -2
- package/dist/assets/{Study-BeDOIC_S.js.map → Study-DBNOEhLk.js.map} +1 -1
- package/dist/assets/{TagInformation-SNKefKYT.js → TagInformation-uyEUthh5.js} +2 -2
- package/dist/assets/{TagInformation-SNKefKYT.js.map → TagInformation-uyEUthh5.js.map} +1 -1
- package/dist/assets/{User-Cjgu9LfH.js → User-BTB3YmVE.js} +2 -2
- package/dist/assets/{User-Cjgu9LfH.js.map → User-BTB3YmVE.js.map} +1 -1
- package/dist/assets/{UserStats-BF3jj2va.js → UserStats-DCekORDJ.js} +2 -2
- package/dist/assets/{UserStats-BF3jj2va.js.map → UserStats-DCekORDJ.js.map} +1 -1
- package/dist/assets/{VerifyEmail--vUp_NOq.js → VerifyEmail-BjY5roFL.js} +2 -2
- package/dist/assets/{VerifyEmail--vUp_NOq.js.map → VerifyEmail-BjY5roFL.js.map} +1 -1
- package/dist/assets/{chess-E2uOAyQS-DKOiY0iM.js → chess-E2uOAyQS-D6CGJtMJ.js} +2 -2
- package/dist/assets/{chess-E2uOAyQS-DKOiY0iM.js.map → chess-E2uOAyQS-D6CGJtMJ.js.map} +1 -1
- package/dist/assets/chess-Lfm7Fsvs-Boy3KK6p.js +1 -0
- package/dist/assets/{common-ui.es-BMHqJKBd.js → common-ui.es-DouiVilK.js} +4 -4
- package/dist/assets/{common-ui.es-BMHqJKBd.js.map → common-ui.es-DouiVilK.js.map} +1 -1
- package/dist/assets/common-ui.es-f4_2RuW2.js +1 -0
- package/dist/assets/dist-Bkb24N94.js +1 -0
- package/dist/assets/{dist-Jl-qWOhM.js → dist-D6VPcBkn.js} +5 -5
- package/dist/assets/dist-D6VPcBkn.js.map +1 -0
- package/dist/assets/{dist-DlNYJefd.js → dist-D9J5Gxrc.js} +3 -3
- package/dist/assets/{dist-DlNYJefd.js.map → dist-D9J5Gxrc.js.map} +1 -1
- package/dist/assets/dist-DTUOdu3A.js +1 -0
- package/dist/assets/{edit-ui.es-DW5ycedU.js → edit-ui.es-CKW9BqTp.js} +2 -2
- package/dist/assets/{edit-ui.es-DW5ycedU.js.map → edit-ui.es-CKW9BqTp.js.map} +1 -1
- package/dist/assets/edit-ui.es-DkHYRUUB.js +1 -0
- package/dist/assets/{french-Dk7YG8Td-CWEOqi3J.js → french-Dk7YG8Td-0vHDzcHK.js} +2 -2
- package/dist/assets/{french-Dk7YG8Td-CWEOqi3J.js.map → french-Dk7YG8Td-0vHDzcHK.js.map} +1 -1
- package/dist/assets/french-evUMlbbq-BMu87TgL.js +1 -0
- package/dist/assets/{index-Dbhh0GOx.js → index-DMVb_wJg.js} +4 -4
- package/dist/assets/{index-Dbhh0GOx.js.map → index-DMVb_wJg.js.map} +1 -1
- package/dist/assets/{math-B4HbgYf6-NoqPLSn0.js → math-B4HbgYf6-CwUf-vvm.js} +2 -2
- package/dist/assets/{math-B4HbgYf6-NoqPLSn0.js.map → math-B4HbgYf6-CwUf-vvm.js.map} +1 -1
- package/dist/assets/math-DYni7rRl-CMevcdvM.js +1 -0
- package/dist/assets/piano-BN5Btq91-CO-CaHNM.js +1 -0
- package/dist/assets/{piano-DF1g6yaX-NIs3PiJI.js → piano-DF1g6yaX-BASEa47l.js} +2 -2
- package/dist/assets/{piano-DF1g6yaX-NIs3PiJI.js.map → piano-DF1g6yaX-BASEa47l.js.map} +1 -1
- package/dist/assets/{pitch-CgGJFkZ1-ebKEVkAF.js → pitch-CgGJFkZ1-DozRwmni.js} +2 -2
- package/dist/assets/{pitch-CgGJFkZ1-ebKEVkAF.js.map → pitch-CgGJFkZ1-DozRwmni.js.map} +1 -1
- package/dist/assets/pitch-Dn0iNqiS-BQqjlMu-.js +1 -0
- package/dist/assets/{server-CnMJdYCB.js → server-BLOZGnZj.js} +2 -2
- package/dist/assets/{server-CnMJdYCB.js.map → server-BLOZGnZj.js.map} +1 -1
- package/dist/assets/{sightsing-BFQ7HRig-phTlC5pa.js → sightsing-BFQ7HRig-CF6Jn7XN.js} +2 -2
- package/dist/assets/{sightsing-BFQ7HRig-phTlC5pa.js.map → sightsing-BFQ7HRig-CF6Jn7XN.js.map} +1 -1
- package/dist/assets/sightsing-CJX3k0Fd-0Qyr03Sr.js +1 -0
- package/dist/assets/{typing-BevtfWlp-mup-WXem.js → typing-BevtfWlp-B3h84nOt.js} +2 -2
- package/dist/assets/{typing-BevtfWlp-mup-WXem.js.map → typing-BevtfWlp-B3h84nOt.js.map} +1 -1
- package/dist/assets/typing-k-ojjO7G-Dlq5J6Og.js +1 -0
- package/dist/assets/{word-work-BOnRlZgd-DCBkmXxW.js → word-work-BOnRlZgd-CnOPdDoj.js} +2 -2
- package/dist/assets/{word-work-BOnRlZgd-DCBkmXxW.js.map → word-work-BOnRlZgd-CnOPdDoj.js.map} +1 -1
- package/dist/assets/word-work-Dd6tIKrt-CkcooUc8.js +1 -0
- package/dist/index.html +3 -3
- package/dist/sw.js +1 -1
- package/dist/sw.js.map +1 -1
- package/package.json +7 -7
- package/dist/assets/chess-Lfm7Fsvs-DaE0DXZW.js +0 -1
- package/dist/assets/common-ui.es-0qiyH2Hn.js +0 -1
- package/dist/assets/dist-C2jJBpTM.js +0 -1
- package/dist/assets/dist-Jl-qWOhM.js.map +0 -1
- package/dist/assets/dist-LcOd5qZ-.js +0 -1
- package/dist/assets/edit-ui.es-BuN4pjlr.js +0 -1
- package/dist/assets/french-evUMlbbq-C9sx15IW.js +0 -1
- package/dist/assets/math-DYni7rRl-CxADtf-I.js +0 -1
- package/dist/assets/piano-BN5Btq91-BPIDDDSd.js +0 -1
- package/dist/assets/pitch-Dn0iNqiS-CjAjPYi5.js +0 -1
- package/dist/assets/sightsing-CJX3k0Fd-Irmbdu7-.js +0 -1
- package/dist/assets/typing-k-ojjO7G-Cf3pW46I.js +0 -1
- package/dist/assets/word-work-Dd6tIKrt-DIVGfIyb.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./vue.runtime.esm-bundler-C3q8JS9f.js";import"./common-ui.es-DouiVilK.js";import"./dist-leWgtXBf.js";import{T as e,m as t}from"./MarkdownRenderer-DoVbFpA6-CvaBjTS8.js";import"./dist-D6VPcBkn.js";export{e as MarkdownRenderer,t as piniaPlugin};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./dist-leWgtXBf.js";import{n as e}from"./dist-D6VPcBkn.js";export{e as CourseLookup};
|
|
@@ -246,7 +246,7 @@ 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 ELONavigator,init_elo=__esm({"src/core/navigators/generators/elo.ts"(){init_navigators(),init_logger(),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`})),o=a.map(e=>e.cardID),s=await this.course.getCardEloData(o),c=a.map((e,t)=>{let r=s[t]?.global?.score??1e3,a=Math.abs(r-n),o=Math.max(0,1-a/500),c=o>0?Math.random()**(1/o):0;return{cardId:e.cardID,courseId:e.courseID,score:c,provenance:[{strategy:`elo`,strategyName:this.strategyName||this.name,strategyId:this.strategyId||`NAVIGATION_STRATEGY-ELO-default`,action:`generated`,score:c,reason:`ELO distance ${Math.round(a)} (card: ${Math.round(r)}, user: ${Math.round(n)}), raw ${o.toFixed(3)}, key ${c.toFixed(3)}`}]}});c.sort((e,t)=>t.score-e.score);let l=c.slice(0,e);if(l.length>0){let e=l.slice(0,3).map(e=>e.score.toFixed(2)).join(`, `);logger.info(`[ELO] Course ${this.course.getCourseID()}: ${l.length} new cards (top scores: ${e})`)}else logger.info(`[ELO] Course ${this.course.getCourseID()}: No new cards available`);return{cards:l}}}}}),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,BASE_TARGET_SCORE,BASE_SUPPORT_SCORE,DISCOVERED_SUPPORT_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,BASE_TARGET_SCORE=1,BASE_SUPPORT_SCORE=.8,DISCOVERED_SUPPORT_SCORE=12,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);S.push(...r,...o,...u)}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],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})).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}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});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()}}),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 ELONavigator,init_elo=__esm({"src/core/navigators/generators/elo.ts"(){init_navigators(),init_logger(),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.max(0,1-r/500),o=a>0?Math.random()**(1/a):0;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)}), raw ${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,BASE_TARGET_SCORE,BASE_SUPPORT_SCORE,DISCOVERED_SUPPORT_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,BASE_TARGET_SCORE=1,BASE_SUPPORT_SCORE=.8,DISCOVERED_SUPPORT_SCORE=12,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);S.push(...r,...o,...u)}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],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})).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}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});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}
|
|
@@ -257,7 +257,7 @@ Example:
|
|
|
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+`
|
|
259
259
|
`)}`),this._courseIDs&&(e=e.filter(e=>this._courseIDs.includes(e._id))),logger.debug(`AllCourses.filtered: ${e.map(e=>e.name+`, `+e._id+`
|
|
260
|
-
`)}`),(await Promise.all(e.map(async e=>{try{let t=await getCredentialledCourseConfig(e._id);return logger.debug(`Found cfg: ${JSON.stringify(t)}`),t}catch(t){logger.warn(`Error fetching cfg for course ${e.name}, ${e._id}: ${t}`);return}}))).filter(e=>!!e)}async getCourseConfig(e){if(this._courseIDs&&this._courseIDs.length&&!this._courseIDs.includes(e))throw Error(`Course ${e} not in course list`);let t=await getCredentialledCourseConfig(e);if(t===void 0)throw Error(`Error fetching cfg for course ${e}`);return t}async disambiguateCourse(e,t){await CourseLookup.updateDisambiguator(e,t)}},CourseDB=class{constructor(e,t,n){_defineProperty(this,`db`,void 0),_defineProperty(this,`remoteDB`,void 0),_defineProperty(this,`id`,void 0),_defineProperty(this,`_getCurrentUser`,void 0),_defineProperty(this,`updateQueue`,void 0),_defineProperty(this,`_pendingHints`,null),this.id=e;let r=getCourseDB2(this.id);this.remoteDB=r,this.db=n??r,this._getCurrentUser=t,this.updateQueue=new UpdateQueue(this.remoteDB,this.remoteDB)}getCourseID(){return this.id}async getCourseInfo(){return{cardCount:(await this.db.find({selector:{docType:`CARD`},limit:1e3})).docs.length,registeredUsers:0}}async getInexperiencedCards(e=2){return(await this.db.query(`cardsByInexperience`,{limit:e})).rows.map(e=>({courseId:this.id,cardId:e.id,count:e.key,elo:e.value}))}async getCardsByEloLimits(e={low:0,high:-(2**53-1),limit:25,page:0}){return(await this.db.query(`elo`,{startkey:e.low,endkey:e.high,limit:e.limit,skip:e.limit*e.page})).rows.map(e=>`${this.id}-${e.id}-${e.key}`)}async getCardEloData(e){let t=await this.db.allDocs({keys:e,include_docs:!0}),n=[];return t.rows.forEach(e=>{isSuccessRow(e)?e.doc&&e.doc.elo?n.push(toCourseElo(e.doc.elo)):(logger.warn(`no elo data for card: `+e.id),n.push(blankCourseElo())):(logger.warn(`no elo data for card: `+JSON.stringify(e)),n.push(blankCourseElo()))}),n}async getELOBounds(){let[e,t]=await Promise.all([(await this.db.query(`elo`,{startkey:0,limit:1,include_docs:!1})).rows[0].key,(await this.db.query(`elo`,{limit:1,descending:!0,startkey:1e5})).rows[0].key]);return{low:e,high:t}}async removeCard(e){let t=await this.remoteDB.get(e);if(!t.docType||t.docType!==`CARD`)throw Error(`failed to remove ${e} from course ${this.id}. id does not point to a card`);try{let t=await this.getAppliedTags(e);(await Promise.allSettled(t.rows.map(async t=>{let n=t.id;await this.removeTagFromCard(e,n)}))).forEach((n,r)=>{if(n.status===`rejected`){let a=t.rows[r].id;logger.error(`Failed to remove card ${e} from tag ${a}: ${n.reason}`)}})}catch(t){logger.error(`Error removing card ${e} from tags: ${t}`)}return this.remoteDB.remove(t)}async getCardDisplayableDataIDs(e){logger.debug(e.join(`, `));let t=await this.db.allDocs({keys:e,include_docs:!0}),n={};return t.rows.forEach(e=>{isSuccessRow(e)&&(n[e.id]=e.doc.id_displayable_data)}),n}async getCardsByELO(e,t){e=parseInt(e);let n=t||25,r=await this.db.query(`elo`,{limit:Math.ceil(n/2),startkey:e,descending:!0}),a=n-r.rows.length,o=await this.db.query(`elo`,{limit:a,startkey:e+1}),s=r.rows;s=s.concat(o.rows);let c=s.sort((t,n)=>{let r=Math.abs(t.key-e)-Math.abs(n.key-e);return r===0?Math.random()-.5:r}).map(e=>({courseID:this.id,cardID:e.id,elo:e.key})),l=`below:
|
|
260
|
+
`)}`),(await Promise.all(e.map(async e=>{try{let t=await getCredentialledCourseConfig(e._id);return logger.debug(`Found cfg: ${JSON.stringify(t)}`),t}catch(t){logger.warn(`Error fetching cfg for course ${e.name}, ${e._id}: ${t}`);return}}))).filter(e=>!!e)}async getCourseConfig(e){if(this._courseIDs&&this._courseIDs.length&&!this._courseIDs.includes(e))throw Error(`Course ${e} not in course list`);let t=await getCredentialledCourseConfig(e);if(t===void 0)throw Error(`Error fetching cfg for course ${e}`);return t}async disambiguateCourse(e,t){await CourseLookup.updateDisambiguator(e,t)}},CourseDB=class{constructor(e,t,n){_defineProperty(this,`db`,void 0),_defineProperty(this,`remoteDB`,void 0),_defineProperty(this,`id`,void 0),_defineProperty(this,`_getCurrentUser`,void 0),_defineProperty(this,`updateQueue`,void 0),_defineProperty(this,`_pendingHints`,null),_defineProperty(this,`_eloPoolCache`,null),_defineProperty(this,`_eloPoolTtlMs`,300*1e3),_defineProperty(this,`_cachedNavigator`,null),_defineProperty(this,`_navigatorTtlMs`,300*1e3),this.id=e;let r=getCourseDB2(this.id);this.remoteDB=r,this.db=n??r,this._getCurrentUser=t,this.updateQueue=new UpdateQueue(this.remoteDB,this.remoteDB)}getCourseID(){return this.id}async getCourseInfo(){return{cardCount:(await this.db.find({selector:{docType:`CARD`},limit:1e3})).docs.length,registeredUsers:0}}async getInexperiencedCards(e=2){return(await this.db.query(`cardsByInexperience`,{limit:e})).rows.map(e=>({courseId:this.id,cardId:e.id,count:e.key,elo:e.value}))}async getCardsByEloLimits(e={low:0,high:-(2**53-1),limit:25,page:0}){return(await this.db.query(`elo`,{startkey:e.low,endkey:e.high,limit:e.limit,skip:e.limit*e.page})).rows.map(e=>`${this.id}-${e.id}-${e.key}`)}async getCardEloData(e){let t=await this.db.allDocs({keys:e,include_docs:!0}),n=[];return t.rows.forEach(e=>{isSuccessRow(e)?e.doc&&e.doc.elo?n.push(toCourseElo(e.doc.elo)):(logger.warn(`no elo data for card: `+e.id),n.push(blankCourseElo())):(logger.warn(`no elo data for card: `+JSON.stringify(e)),n.push(blankCourseElo()))}),n}async getELOBounds(){let[e,t]=await Promise.all([(await this.db.query(`elo`,{startkey:0,limit:1,include_docs:!1})).rows[0].key,(await this.db.query(`elo`,{limit:1,descending:!0,startkey:1e5})).rows[0].key]);return{low:e,high:t}}async removeCard(e){let t=await this.remoteDB.get(e);if(!t.docType||t.docType!==`CARD`)throw Error(`failed to remove ${e} from course ${this.id}. id does not point to a card`);try{let t=await this.getAppliedTags(e);(await Promise.allSettled(t.rows.map(async t=>{let n=t.id;await this.removeTagFromCard(e,n)}))).forEach((n,r)=>{if(n.status===`rejected`){let a=t.rows[r].id;logger.error(`Failed to remove card ${e} from tag ${a}: ${n.reason}`)}})}catch(t){logger.error(`Error removing card ${e} from tags: ${t}`)}return this.remoteDB.remove(t)}async getCardDisplayableDataIDs(e){logger.debug(e.join(`, `));let t=await this.db.allDocs({keys:e,include_docs:!0}),n={};return t.rows.forEach(e=>{isSuccessRow(e)&&(n[e.id]=e.doc.id_displayable_data)}),n}async getCardsByELO(e,t){e=parseInt(e);let n=t||25,r=await this.db.query(`elo`,{limit:Math.ceil(n/2),startkey:e,descending:!0}),a=n-r.rows.length,o=await this.db.query(`elo`,{limit:a,startkey:e+1}),s=r.rows;s=s.concat(o.rows);let c=s.sort((t,n)=>{let r=Math.abs(t.key-e)-Math.abs(n.key-e);return r===0?Math.random()-.5:r}).map(e=>({courseID:this.id,cardID:e.id,elo:e.key})),l=`below:
|
|
261
261
|
${r.rows.map(e=>` ${e.id}-${e.key}
|
|
262
262
|
`)}
|
|
263
263
|
|
|
@@ -267,8 +267,8 @@ ${o.rows.map(e=>` ${e.id}-${e.key}
|
|
|
267
267
|
|
|
268
268
|
`+l),c}async getCourseConfig(){let e=await getCredentialledCourseConfig(this.id);if(e)return e;throw Error(`Course config not found for course ID: ${this.id}`)}async updateCourseConfig(e){logger.debug(`Updating: ${JSON.stringify(e)}`);try{return await updateCredentialledCourseConfig(this.id,e)}catch(e){throw logger.error(`Error updating course config in course DB: ${e}`),e}}async updateCardElo(e,t){if(!t)throw Error(`Cannot update card elo with null or undefined value for card ID: ${e}`);try{return{ok:!0,id:e,rev:(await this.updateQueue.update(e,e=>(logger.debug(`Replacing ${JSON.stringify(e.elo)} with ${JSON.stringify(t)}`),e.elo=t,e)))._rev}}catch(t){throw logger.error(`Failed to update card elo for card ID: ${e}`,t),Error(`Failed to update card elo for card ID: ${e}`)}}async getAppliedTags(e){let t=await getAppliedTags(this.id,e);if(t)return t;throw Error(`Failed to find tags for card ${this.id}-${e}`)}async getAppliedTagsBatch(e){if(e.length===0)return new Map;let t=await this.db.query(`getTags`,{keys:e,include_docs:!1}),n=new Map;for(let t of e)n.set(t,[]);for(let e of t.rows){let t=e.key,r=e.value?.name;r&&n.has(t)&&n.get(t).push(r)}return n}async getAllCardIds(){return(await this.db.allDocs({startkey:`CARD-`,endkey:`CARD-`,include_docs:!1})).rows.map(e=>e.id)}async addTagToCard(e,t,n){return await addTagToCard(this.id,e,t,(await this._getCurrentUser()).getUsername(),n)}async removeTagFromCard(e,t){return await removeTagFromCard(this.id,e,t)}async createTag(e,t){return await createTag(this.id,e,t)}async getTag(e){return await getTag(this.id,e)}async updateTag(e){if(e.course!==this.id)throw Error(`Tag ${JSON.stringify(e)} does not belong to course ${this.id}`);return await updateTag(e)}async getCourseTagStubs(){return getCourseTagStubs(this.id)}async addNote(e,t,n,r,a,o,s=blankCourseElo()){try{let c=await addNote55(this.id,e,t,n,r,a,o,s);return c.ok?c.cardCreationFailed?(logger.warn(`[courseDB.addNote] Note added but card creation failed: ${c.cardCreationError}`),{status:Status.error,message:`Note was added but no cards were created: ${c.cardCreationError}`,id:c.id}):{status:Status.ok,message:``,id:c.id}:{status:Status.error,message:`Unexpected error adding note`}}catch(e){let t=e;return logger.error(`[addNote] error ${t.name}
|
|
269
269
|
reason: ${t.reason}
|
|
270
|
-
message: ${t.message}`),{status:Status.error,message:`Error adding note to course. ${e.reason||t.message}`}}}async getCourseDoc(e,t){return await this.db.get(e,t??{})}async getCourseDocs(e,t={}){return await this.db.allDocs({...t,keys:e})}getNavigationStrategy(e){if(logger.debug(`[courseDB] Getting navigation strategy: ${e}`),e==``){let e={_id:`NAVIGATION_STRATEGY-ELO`,docType:`NAVIGATION_STRATEGY`,name:`ELO`,description:`ELO-based navigation strategy for ordering content by difficulty`,implementingClass:`elo`,course:this.id,serializedData:``};return Promise.resolve(e)}else return this.db.get(e)}async getAllNavigationStrategies(){let e=DocTypePrefixes.NAVIGATION_STRATEGY;return(await this.db.allDocs({startkey:e,endkey:`${e}\uFFF0`,include_docs:!0})).rows.map(e=>e.doc)}async addNavigationStrategy(e){return logger.debug(`[courseDB] Adding navigation strategy: ${e._id}`),this.remoteDB.put(e).then(()=>{})}updateNavigationStrategy(e,t){return logger.debug(`[courseDB] Updating navigation strategy: ${e}`),logger.debug(JSON.stringify(t)),Promise.resolve()}async createNavigator(e){try{let t=await this.getAllNavigationStrategies();if(t.length===0)return logger.debug(`[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])`),createDefaultPipeline(e,this);let{pipeline:n,generatorStrategies:r,filterStrategies:a,warnings:o}=await new PipelineAssembler().assemble({strategies:t,user:e,course:this});for(let e of o)logger.warn(`[PipelineAssembler] ${e}`);return n?(logger.debug(`[courseDB] Using assembled pipeline with ${r.length} generator(s) and ${a.length} filter(s)`),n):(logger.debug(`[courseDB] Pipeline assembly failed, using default pipeline`),createDefaultPipeline(e,this))}catch(e){let t=e instanceof Error?`${e.message}
|
|
271
|
-
${e.stack}`:JSON.stringify(e);throw logger.error(`[courseDB] Error creating navigator: ${t}`),e}}setEphemeralHints(e){this._pendingHints=e}async getWeightedCards(e){let t=await this._getCurrentUser();try{let n=await this.createNavigator(t);return this._pendingHints&&(n.setEphemeralHints(this._pendingHints),this._pendingHints=null),n.getWeightedCards(e)}catch(e){throw logger.error(`[courseDB] Error getting weighted cards: ${e}`),e}}async getCardsCenteredAtELO(e={limit:99,elo:`user`},t){let n;if(e.elo===`user`){let e=await this._getCurrentUser();n=-1;try{n=EloToNumber((await e.getCourseRegistrationsDoc()).courses.find(e=>e.courseID===this.id).elo)}catch{n=1e3}}else if(e.elo===`random`){let e=await GET_CACHED(`elo-bounds-${this.id}`,()=>this.getELOBounds());n=Math.round(e.low+Math.random()*(e.high-e.low))}else n=e.elo;let r=[],a=4,o=-1,s=0;for(;r.length<e.limit&&s!==o;)r=await this.getCardsByELO(n,a*e.limit),o=s,s=r.length,logger.debug(`Found ${r.length} elo neighbor cards...`),t&&(r=r.filter(t),logger.debug(`Filtered to ${r.length} cards...`)),a*=2;let c=[];for(;c.length<e.limit&&r.length>0;){let e=randIntWeightedTowardZero(r.length),t=r.splice(e,1)[0];c.push(t)}return c.map(e=>({courseID:this.id,cardID:e.cardID,contentSourceType:`course`,contentSourceID:this.id,elo:e.elo,status:`new`}))}async searchCards(e){logger.log(`[CourseDB ${this.id}] Searching for: "${e}"`);let t;try{t=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`,"data.0.data":{$regex:`.*${e}.*`}}}),logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`)}catch(n){logger.log(`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,n);let r=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`}});logger.log(`[CourseDB ${this.id}] Retrieved ${r.docs.length} documents for manual filtering`),t={docs:r.docs.filter(t=>{let n=JSON.stringify(t).toLowerCase().includes(e.toLowerCase());return n&&logger.log(`[CourseDB ${this.id}] Manual match found in document: ${t._id}`),n})}}if(logger.log(`[CourseDB ${this.id}] Found ${t.docs.length} displayable data documents`),t.docs.length===0){let e=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`},limit:5});logger.log(`[CourseDB ${this.id}] Sample displayable data:`,e.docs.map(e=>({id:e._id,docType:e.docType,dataStructure:e.data?Object.keys(e.data):`no data field`,dataContent:e.data,fullDoc:e})))}let n=[];for(let e of t.docs){let t=await this.db.find({selector:{docType:`CARD`,id_displayable_data:{$in:[e._id]}}});logger.log(`[CourseDB ${this.id}] Displayable data ${e._id} linked to ${t.docs.length} cards`),n.push(...t.docs)}return logger.log(`[CourseDB ${this.id}] Total cards found: ${n.length}`),n}async find(e){return this.db.find(e)}}}}),classroomLookupDBTitle,CLASSROOM_CONFIG,ClassroomDBBase,StudentClassroomDB,TeacherClassroomDB,ClassroomLookupDB,init_classroomDB2=__esm({"src/impl/couch/classroomDB.ts"(){init_factory(),init_logger(),init_pouchdb_setup(),init_couch(),init_courseDB(),classroomLookupDBTitle=`classdb-lookup`,CLASSROOM_CONFIG=`ClassroomConfig`,ClassroomDBBase=class{constructor(){_defineProperty(this,`_id`,void 0),_defineProperty(this,`_db`,void 0),_defineProperty(this,`_cfg`,void 0),_defineProperty(this,`_initComplete`,!1),_defineProperty(this,`_content_prefix`,`content`)}get _content_searchkeys(){return getStartAndEndKeys2(this._content_prefix)}async getAssignedContent(){return logger.info(`Getting assigned content...`),(await this._db.allDocs({startkey:this._content_prefix,endkey:this._content_prefix+``,include_docs:!0})).rows.map(e=>e.doc)}getContentId(e){return e.type===`tag`?`${this._content_prefix}-${e.courseID}-${e.tagID}`:`${this._content_prefix}-${e.courseID}`}get ready(){return this._initComplete}getConfig(){return this._cfg}},StudentClassroomDB=class _StudentClassroomDB extends ClassroomDBBase{constructor(e,t){super(),_defineProperty(this,`userMessages`,void 0),_defineProperty(this,`_user`,void 0),this._id=e,this._user=t}async init(){let e=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+e,createPouchDBConfig());try{this._cfg=await this._db.get(CLASSROOM_CONFIG),this.userMessages=this._db.changes({since:`now`,live:!0,include_docs:!0}),this._initComplete=!0;return}catch(e){throw Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`)}}static async factory(e,t){let n=new _StudentClassroomDB(e,t);return await n.init(),n}setChangeFcn(e){this.userMessages.on(`change`,e)}async getWeightedCards(e){let t=[],n=(await this._user.getPendingReviews()).filter(e=>e.scheduledFor===`classroom`&&e.schedulingAgentId===this._id);for(let e of n)t.push({cardId:e.cardId,courseId:e.courseId,score:1,reviewID:e._id,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom scheduled review`}]});let r=await this._user.getActiveCards(),a=new Set(r.map(e=>e.cardID)),o=hooks.utc(),s=(await this.getAssignedContent()).filter(e=>o.isAfter(hooks.utc(e.activeOn,REVIEW_TIME_FORMAT2)));logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(s)}`);for(let n of s)if(n.type===`course`){let{cards:r}=await new CourseDB(n.courseID,async()=>this._user).getWeightedCards(e);for(let e of r)a.has(e.cardId)||t.push({...e,provenance:[...e.provenance,{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`passed`,score:e.score,reason:`Assigned via classroom from course ${n.courseID}`}]})}else if(n.type===`tag`){let e=await getTag(n.courseID,n.tagID);for(let r of e.taggedCards)a.has(r)||t.push({cardId:r,courseId:n.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned tag: ${n.tagID}, new card`}]})}else n.type===`card`&&(a.has(n.cardID)||t.push({cardId:n.cardID,courseId:n.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned card, new card`}]}));return logger.info(`[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ${t.length} total (reviews + new)`),{cards:t.sort((e,t)=>t.score-e.score).slice(0,e)}}},TeacherClassroomDB=class _TeacherClassroomDB extends ClassroomDBBase{constructor(e){super(),_defineProperty(this,`_stuDb`,void 0),this._id=e}async init(){let e=`classdb-teacher-${this._id}`,t=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+e,createPouchDBConfig()),this._stuDb=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+t,createPouchDBConfig());try{return this._db.get(CLASSROOM_CONFIG).then(e=>{this._cfg=e,this._initComplete=!0}).then(()=>{})}catch(e){throw Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`)}}static async factory(e){let t=new _TeacherClassroomDB(e);return await t.init(),t}async removeContent(e){let t=this.getContentId(e);try{let e=await this._db.get(t);await this._db.remove(e),this._db.replicate.to(this._stuDb,{doc_ids:[t]})}catch(e){logger.error(`Failed to remove content:`,t,e)}}async assignContent(e){let t,n=this.getContentId(e);return t=e.type===`tag`?await this._db.put({courseID:e.courseID,tagID:e.tagID,type:`tag`,_id:n,assignedBy:e.assignedBy,assignedOn:hooks.utc(),activeOn:e.activeOn||hooks.utc()}):await this._db.put({courseID:e.courseID,type:`course`,_id:n,assignedBy:e.assignedBy,assignedOn:hooks.utc(),activeOn:e.activeOn||hooks.utc()}),t.ok?(this._db.replicate.to(this._stuDb,{doc_ids:[n]}),!0):!1}},ClassroomLookupDB=()=>new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+classroomLookupDBTitle,{skip_setup:!0})}}),AdminDB,init_adminDB2=__esm({"src/impl/couch/adminDB.ts"(){init_pouchdb_setup(),init_factory(),init_couch(),init_classroomDB2(),init_courseLookupDB(),init_logger(),AdminDB=class{constructor(){_defineProperty(this,`usersDB`,void 0),this.usersDB=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`_users`,createPouchDBConfig())}async getUsers(){return(await this.usersDB.allDocs({include_docs:!0,...getStartAndEndKeys2(`org.couchdb.user:`)})).rows.map(e=>e.doc)}async getCourses(){let e=await CourseLookup.allCourseWare();return await Promise.all(e.map(e=>getCredentialledCourseConfig(e._id)))}async removeCourse(e){let t=await CourseLookup.delete(e),n=await getCredentialledCourseConfig(e);n.deleted=!0;let r=await updateCredentialledCourseConfig(e,n);return{ok:t.ok&&r.ok,id:t.id,rev:t.rev}}async getClassrooms(){let e=(await ClassroomLookupDB().allDocs({include_docs:!0})).rows.map(e=>e.doc.uuid);logger.debug(e.join(`, `));let t=[];for(let n=0;n<e.length;n++)try{let r=await TeacherClassroomDB.factory(e[n]);t.push(r)}catch(t){let r=t;r.error&&r.error===`not_found`&&logger.warn(`db ${e[n]} not found`)}return t.map(e=>({...e.getConfig(),_id:e._id}))}}}}),CourseSyncService_exports={};__export(CourseSyncService_exports,{CourseSyncService:()=>CourseSyncService});var DEFAULT_REPLICATION,CourseSyncService,init_CourseSyncService=__esm({"src/impl/couch/CourseSyncService.ts"(){var e;init_pouchdb_setup(),init_couch(),init_logger(),DEFAULT_REPLICATION={batchSize:1e3,batchesLimit:5},CourseSyncService=(e=class _CourseSyncService{constructor(){_defineProperty(this,`entries`,new Map),_defineProperty(this,`replicationOptions`,DEFAULT_REPLICATION)}static getInstance(){return _CourseSyncService.instance||(_CourseSyncService.instance=new _CourseSyncService),_CourseSyncService.instance}configure(e){e.replication&&(this.replicationOptions={...DEFAULT_REPLICATION,...e.replication},logger.info(`[CourseSyncService] Replication configured: batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit}`))}static resetInstance(){if(_CourseSyncService.instance){for(let[,e]of _CourseSyncService.instance.entries)e.localDB&&e.localDB.close().catch(()=>{});_CourseSyncService.instance.entries.clear()}_CourseSyncService.instance=null}async ensureSynced(e,t){let n=this.entries.get(e);if(n?.status.state===`ready`&&n.localDB){if(!await this.isLocalEpochStale(e,n.localDB))return;logger.info(`[CourseSyncService] Remote DB epoch changed for course ${e} \u2014 destroying stale local replica`);try{await n.localDB.destroy()}catch{}n.localDB=null,n.readyPromise=null}if(n?.status.state===`disabled`)return;if(n?.readyPromise)return n.readyPromise;let r={localDB:null,status:{state:`not-started`},readyPromise:null};return this.entries.set(e,r),r.readyPromise=this.performSync(e,r,t),r.readyPromise}getLocalDB(e){let t=this.entries.get(e);return t?.status.state===`ready`&&t.localDB?t.localDB:null}isReady(e){return this.entries.get(e)?.status.state===`ready`}getStatus(e){return this.entries.get(e)?.status??{state:`not-started`}}async performSync(e,t,n){try{if(!n&&(t.status={state:`checking-config`},!await this.checkLocalSyncEnabled(e))){t.status={state:`disabled`},t.readyPromise=null,logger.debug(`[CourseSyncService] Local sync disabled for course ${e}`);return}t.status={state:`syncing`};let r=this.localDBName(e),a=new pouchdb_setup_default(r);await this.isLocalEpochStale(e,a)&&(logger.info(`[CourseSyncService] Stale local DB detected for course ${e} \u2014 destroying before sync`),await a.destroy(),a=new pouchdb_setup_default(r)),t.localDB=a;let o=this.getRemoteDB(e),s=Date.now();logger.info(`[CourseSyncService] Starting one-shot replication for course ${e} (batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit})`);let c=await this.replicate(o,a),l=Date.now()-s;logger.info(`[CourseSyncService] Replication complete for course ${e}: ${c.docs_written} docs in ${l}ms`),t.status={state:`warming-views`};let u=Date.now();await this.warmViewIndices(a);let d=Date.now()-u;logger.info(`[CourseSyncService] View indices warmed for course ${e} in ${d}ms`),t.status={state:`ready`,docsReplicated:c.docs_written,syncTimeMs:l,viewWarmTimeMs:d}}catch(n){let r=n instanceof Error?n.message:String(n);if(logger.error(`[CourseSyncService] Sync failed for course ${e}: ${r}`),t.status={state:`error`,error:r},t.readyPromise=null,t.localDB){try{await t.localDB.destroy()}catch{}t.localDB=null}}}async checkLocalSyncEnabled(e){try{return(await this.getRemoteDB(e).get(`CourseConfig`)).localSync?.enabled===!0}catch(t){return logger.warn(`[CourseSyncService] Could not read CourseConfig for ${e}, assuming local sync disabled: ${t}`),!1}}replicate(e,t){return new Promise((n,r)=>{pouchdb_setup_default.replicate(e,t,{batch_size:this.replicationOptions.batchSize,batches_limit:this.replicationOptions.batchesLimit}).on(`complete`,e=>{n(e)}).on(`error`,e=>{r(e)})})}async warmViewIndices(e){for(let t of[`elo`,`getTags`])try{await e.query(t,{limit:1}),logger.debug(`[CourseSyncService] Warmed view index: ${t}`)}catch(e){logger.debug(`[CourseSyncService] Could not warm view ${t}: ${e}`)}}async isLocalEpochStale(e,t){try{let n=await this.getRemoteDB(e).get(`db-epoch`),r=null;try{r=await t.get(`db-epoch`)}catch{return!0}return n.epoch!==r.epoch}catch{return!1}}getRemoteDB(e){return getCourseDB2(e)}localDBName(e){return`coursedb-local-${e}`}},_defineProperty(e,`instance`,null),e)}});async function getCurrentSession(){try{if(ENV.COUCHDB_SERVER_URL===NOT_SET||ENV.COUCHDB_SERVER_PROTOCOL===NOT_SET)throw Error(`CouchDB server configuration not properly initialized. Protocol: "${ENV.COUCHDB_SERVER_PROTOCOL}", URL: "${ENV.COUCHDB_SERVER_URL}"`);let e=ENV.COUCHDB_SERVER_URL.endsWith(`/`)?ENV.COUCHDB_SERVER_URL.slice(0,-1):ENV.COUCHDB_SERVER_URL,t=`${ENV.COUCHDB_SERVER_PROTOCOL}://${e}/_session`;logger.debug(`Attempting session check at: ${t}`);let n=await(0,import_browser_ponyfill.default)(t,{method:`GET`,credentials:`include`});if(!n.ok)throw Error(`Session check failed: ${n.status}`);return await n.json()}catch(e){let t=ENV.COUCHDB_SERVER_URL.endsWith(`/`)?ENV.COUCHDB_SERVER_URL.slice(0,-1):ENV.COUCHDB_SERVER_URL,n=`${ENV.COUCHDB_SERVER_PROTOCOL}://${t}/_session`;throw logger.error(`Session check error attempting to connect to: ${n} - ${e}`),Error(`Session check failed connecting to ${n}: ${e}`)}}async function getLoggedInUsername(){let e=await getCurrentSession();if(e.userCtx.name&&e.userCtx.name!==``)return e.userCtx.name;throw Error(`No logged in user`)}var init_auth=__esm({"src/impl/couch/auth.ts"(){init_factory(),init_logger()}}),CouchDBSyncStrategy_exports={};__export(CouchDBSyncStrategy_exports,{CouchDBSyncStrategy:()=>CouchDBSyncStrategy});var log3,CouchDBSyncStrategy,init_CouchDBSyncStrategy=__esm({"src/impl/couch/CouchDBSyncStrategy.ts"(){init_factory(),init_types_legacy(),init_logger(),init_common(),init_pouchdb_setup(),init_couch(),init_auth(),log3=e=>{logger.info(e)},CouchDBSyncStrategy=class{constructor(){_defineProperty(this,`syncHandle`,void 0)}setupRemoteDB(e){return e===GuestUsername||e.startsWith(GuestUsername)?getLocalUserDB(e):this.getUserDB(e)}getWriteDB(e){return e===GuestUsername||e.startsWith(GuestUsername)?getLocalUserDB(e):this.getUserDB(e)}startSync(e,t){e!==t&&(this.syncHandle=pouchdb_setup_default.sync(e,t,{live:!0,retry:!0}))}stopSync(){this.syncHandle&&(this.syncHandle.cancel(),this.syncHandle=void 0)}canCreateAccount(){return!0}canAuthenticate(){return!0}async createAccount(e,t){let n=await this.getCurrentUsername(),r=n.startsWith(GuestUsername);r&&logger.info(`Creating account for funnel user ${n} -> ${e}`);try{let a=await this.getRemoteCouchRootDB().signUp(e,t);if(a.ok){log3(`CREATEACCOUNT: Successfully created account for ${e}`);try{let e=await this.getRemoteCouchRootDB().logOut();log3(`CREATEACCOUNT: logged out: ${e.ok}`)}catch{}let a=await this.getRemoteCouchRootDB().logIn(e,t);if(log3(`CREATEACCOUNT: logged in as new user: ${a.ok}`),a.ok){if(r){logger.info(`Migrating data from funnel account ${n} to ${e}`);let t=await this.migrateFunnelData(n,e);t.success||logger.warn(`Migration failed: ${t.error}`)}return{status:Status.ok,error:void 0}}else return{status:Status.error,error:`Failed to log in after account creation`}}else return logger.warn(`Signup not OK: ${JSON.stringify(a)}`),{status:Status.error,error:`Account creation failed`}}catch(e){return e.reason===`Document update conflict.`?{status:Status.error,error:`This username is taken!`}:(logger.error(`Error on signup: ${JSON.stringify(e)}`),{status:Status.error,error:e.message||`Unknown error during account creation`})}}async authenticate(e,t){try{return(await this.getRemoteCouchRootDB().logIn(e,t)).ok?(log3(`Successfully logged in as ${e}`),{ok:!0}):(log3(`Login failed for ${e}`),{ok:!1,error:`Invalid username or password`})}catch(t){return logger.error(`Authentication error for ${e}:`,t),{ok:!1,error:t.message||`Authentication failed`}}}async logout(){try{let e=await this.getRemoteCouchRootDB().logOut();return{ok:e.ok,error:e.ok?void 0:`Logout failed`}}catch(e){return logger.error(`Logout error:`,e),{ok:!1,error:e.message||`Logout failed`}}}async getCurrentUsername(){logger.log(`[funnel] CouchDBSyncStrategy.getCurrentUsername() called`);try{let e=await getLoggedInUsername();return logger.log(`[funnel] getLoggedInUsername() returned:`,e),e}catch(e){logger.log(`[funnel] getLoggedInUsername() failed, calling accomodateGuest()`),logger.log(`[funnel] Error was:`,e);let t=accomodateGuest();return logger.log(`[funnel] accomodateGuest() returned:`,t),t.username}}async migrateFunnelData(e,t){try{logger.info(`Starting data migration from ${e} to ${t}`);let n=getLocalUserDB(e),r=getLocalUserDB(t),a=await n.allDocs({include_docs:!0});logger.info(`Found ${a.rows.length} documents in funnel account`);let o=a.rows.filter(e=>!e.id.startsWith(`_design/`)).map(e=>({...e.doc,_rev:void 0}));return o.length>0?(await r.bulkDocs(o),logger.info(`Successfully migrated ${o.length} documents from ${e} to ${t}`)):logger.info(`No documents to migrate from funnel account`),{success:!0}}catch(e){return logger.error(`Migration failed:`,e),{success:!1,error:e instanceof Error?e.message:`Unknown error`}}}getRemoteCouchRootDB(){let e=ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`skuilder`;try{return new pouchdb_setup_default(e,{skip_setup:!0})}catch(e){throw logger.error(`Failed to initialize remote CouchDB connection:`,e),Error(`Failed to initialize CouchDB: ${JSON.stringify(e)}`)}}getUserDB(e){let t=!1,n=`userdb-${hexEncode(e)}`;return log3(`Fetching user database: ${n} (${e})`),new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+n,createPouchDBConfig())}}}});function createPouchDBConfig(){return ENV.COUCHDB_USERNAME&&ENV.COUCHDB_PASSWORD&&typeof window>`u`?{fetch(e,t={}){let n=btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`),r=new Headers(t.headers||{});r.set(`Authorization`,`Basic ${n}`);let a={...t,headers:r};return pouchdb_setup_default.fetch(e,a)}}:pouchDBincludeCredentialsConfig}function getCourseDB2(e){return new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`coursedb-`+e,createPouchDBConfig())}function filterAllDocsByPrefix2(e,t,n){let r={startkey:t,endkey:t+``,include_docs:!0};return n&&Object.assign(r,n),e.allDocs(r)}function getStartAndEndKeys2(e){return{startkey:e,endkey:e+``}}var isBrowser,GUEST_LOCAL_DB,localUserDB,pouchDBincludeCredentialsConfig,REVIEW_TIME_FORMAT2,init_couch=__esm({"src/impl/couch/index.ts"(){init_factory(),init_types_legacy(),init_logger(),init_pouchdb_setup(),init_contentSource(),init_adminDB2(),init_classroomDB2(),init_courseAPI(),init_courseDB(),init_CourseSyncService(),init_CouchDBSyncStrategy(),isBrowser=typeof window<`u`,isBrowser&&(window.process=import_browser.default),GUEST_LOCAL_DB=`userdb-${GuestUsername}`,new pouchdb_setup_default(GUEST_LOCAL_DB),pouchDBincludeCredentialsConfig={fetch(e,t){return t.credentials=`include`,pouchdb_setup_default.fetch(e,t)}},REVIEW_TIME_FORMAT2=`YYYY-MM-DD--kk:mm:ss-SSS`}});function accomodateGuest(){if(logger.log(`[funnel] accomodateGuest() called`),typeof localStorage>`u`)return logger.log(`[funnel] localStorage not available (Node.js environment), returning default guest`),{username:GuestUsername+`nodejs-test`,firstVisit:!0};let e=`sk-guest-uuid`,t,n=localStorage.getItem(e);if(logger.log(`[funnel] Checking localStorage for key:`,e),logger.log(`[funnel] Existing UUID value:`,n),logger.log(`[funnel] existingUUID !== null:`,n!==null),n!==null)t=!1,logger.log(`[funnel] Returning guest ${n} "logging in".`);else{t=!0,logger.log(`[funnel] No existing UUID, generating new one...`);let n=generateUUID();logger.log(`[funnel] Generated UUID:`,n),logger.log(`[funnel] UUID length:`,n.length);try{localStorage.setItem(e,n),logger.log(`[funnel] Successfully stored UUID in localStorage`);let t=localStorage.getItem(e);logger.log(`[funnel] Verification read from localStorage:`,t)}catch(e){logger.error(`[funnel] ERROR storing UUID:`,e)}logger.log(`[funnel] Accommodating a new guest with account: ${n}`)}let r=localStorage.getItem(e),a=GuestUsername+r;return logger.log(`[funnel] Final UUID from localStorage:`,r),logger.log(`[funnel] GuestUsername constant:`,GuestUsername),logger.log(`[funnel] Final username to return:`,a),{username:a,firstVisit:t};function generateUUID(){if(logger.log(`[funnel] Inside generateUUID()`),typeof crypto<`u`&&typeof crypto.randomUUID==`function`){let e=crypto.randomUUID();return logger.log(`[funnel] Generated UUID using crypto.randomUUID():`,e),e}if(typeof crypto<`u`&&typeof crypto.getRandomValues==`function`){let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=[Array.from(e.slice(0,4)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(4,6)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(6,8)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(8,10)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(10,16)).map(e=>e.toString(16).padStart(2,`0`)).join(``)].join(`-`);return logger.log(`[funnel] Generated UUID using crypto.getRandomValues():`,t),t}logger.warn(`[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)`);let e=new Date().getTime();typeof performance<`u`&&typeof performance.now==`function`&&(e+=performance.now());let t=`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,t=>{let n=(e+Math.random()*16)%16|0;return e=Math.floor(e/16),(t===`x`?n:n&3|8).toString(16)});return logger.log(`[funnel] Generated UUID (fallback):`,t),t}}async function getOrCreateClassroomRegistrationsDoc(e){let t;try{t=await getLocalUserDB(e).get(userClassroomsDoc)}catch(n){let r=n;if(r.status===404)await getLocalUserDB(e).put({_id:userClassroomsDoc,registrations:[]}),t=await getOrCreateClassroomRegistrationsDoc(e);else{let e={name:r.name,status:r.status,message:r.message,reason:r.reason,error:r.error};throw logger.error(`Database error in getOrCreateClassroomRegistrationsDoc (standalone function):`,e),Error(`Database error accessing classroom registrations: ${r.message||r.name||`Unknown error`} (status: ${r.status})`)}}return t}async function getOrCreateCourseRegistrationsDoc(e){let t;try{t=await getLocalUserDB(e).get(userCoursesDoc)}catch(n){if(n.status===404)await getLocalUserDB(e).put({_id:userCoursesDoc,courses:[],studyWeight:{}}),t=await getOrCreateCourseRegistrationsDoc(e);else throw Error(`Unexpected error ${JSON.stringify(n)} in getOrCreateCourseRegistrationDoc...`)}return t}async function updateUserElo(e,t,n){let r=await getOrCreateCourseRegistrationsDoc(e),a=r.courses.find(e=>e.courseID===t);return a.elo=n,getLocalUserDB(e).put(r)}async function registerUserForClassroom(e,t,n){return log4(`Registering user: ${e} in course: ${t}`),getOrCreateClassroomRegistrationsDoc(e).then(r=>{let a={classID:t,registeredAs:n};return r.registrations.filter(e=>e.classID===a.classID&&e.registeredAs===a.registeredAs).length===0?r.registrations.push(a):log4(`User ${e} is already registered for class ${t}`),getLocalUserDB(e).put(r)})}async function dropUserFromClassroom(e,t){return getOrCreateClassroomRegistrationsDoc(e).then(n=>{let r=-1;for(let e=0;e<n.registrations.length;e++)n.registrations[e].classID===t&&(r=e);return r!==-1&&n.registrations.splice(r,1),getLocalUserDB(e).put(n)})}async function getUserClassrooms(e){return getOrCreateClassroomRegistrationsDoc(e)}var log4,BaseUser,userCoursesDoc,userClassroomsDoc,init_BaseUserDB=__esm({"src/impl/common/BaseUserDB.ts"(){var e;init_core(),init_util(),init_types_legacy(),init_logger(),init_userDBHelpers(),init_updateQueue(),init_user_course_relDB(),init_couch(),log4=e=>{logger.info(e)},BaseUser=(e=class _BaseUser{static Dummy(e){return new _BaseUser(`Me`,e)}getUsername(){return this._username}isLoggedIn(){return!this._username.startsWith(GuestUsername)}remote(){return this.remoteDB}async createAccount(e,t){if(!this.syncStrategy.canCreateAccount())throw Error(`Account creation not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername))throw Error(`Cannot create a new account while logged in:
|
|
270
|
+
message: ${t.message}`),{status:Status.error,message:`Error adding note to course. ${e.reason||t.message}`}}}async getCourseDoc(e,t){return await this.db.get(e,t??{})}async getCourseDocs(e,t={}){return await this.db.allDocs({...t,keys:e})}getNavigationStrategy(e){if(logger.debug(`[courseDB] Getting navigation strategy: ${e}`),e==``){let e={_id:`NAVIGATION_STRATEGY-ELO`,docType:`NAVIGATION_STRATEGY`,name:`ELO`,description:`ELO-based navigation strategy for ordering content by difficulty`,implementingClass:`elo`,course:this.id,serializedData:``};return Promise.resolve(e)}else return this.db.get(e)}async getAllNavigationStrategies(){let e=DocTypePrefixes.NAVIGATION_STRATEGY;return(await this.db.allDocs({startkey:e,endkey:`${e}\uFFF0`,include_docs:!0})).rows.map(e=>e.doc)}async addNavigationStrategy(e){return logger.debug(`[courseDB] Adding navigation strategy: ${e._id}`),this.invalidateNavigatorCache(),this.remoteDB.put(e).then(()=>{})}updateNavigationStrategy(e,t){return logger.debug(`[courseDB] Updating navigation strategy: ${e}`),logger.debug(JSON.stringify(t)),Promise.resolve()}async createNavigator(e){try{let t=await this.getAllNavigationStrategies();if(t.length===0)return logger.debug(`[courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])`),createDefaultPipeline(e,this);let{pipeline:n,generatorStrategies:r,filterStrategies:a,warnings:o}=await new PipelineAssembler().assemble({strategies:t,user:e,course:this});for(let e of o)logger.warn(`[PipelineAssembler] ${e}`);return n?(logger.debug(`[courseDB] Using assembled pipeline with ${r.length} generator(s) and ${a.length} filter(s)`),n):(logger.debug(`[courseDB] Pipeline assembly failed, using default pipeline`),createDefaultPipeline(e,this))}catch(e){let t=e instanceof Error?`${e.message}
|
|
271
|
+
${e.stack}`:JSON.stringify(e);throw logger.error(`[courseDB] Error creating navigator: ${t}`),e}}setEphemeralHints(e){this._pendingHints=e}async getWeightedCards(e){let t=await this._getCurrentUser();try{let{navigator:n}=await this._getCachedNavigator(t);return this._pendingHints&&(n.setEphemeralHints(this._pendingHints),this._pendingHints=null),await n.getWeightedCards(e)}catch(e){throw logger.error(`[courseDB] Error getting weighted cards: ${e}`),e}}async _getCachedNavigator(e){let t=e.getUsername(),n=Date.now();if(this._cachedNavigator&&this._cachedNavigator.userId===t&&n-this._cachedNavigator.builtAt<this._navigatorTtlMs)return{navigator:this._cachedNavigator.navigator,cacheStatus:`hit`};let r=await this.createNavigator(e);return this._cachedNavigator={navigator:r,userId:t,builtAt:n},{navigator:r,cacheStatus:`miss`}}invalidateNavigatorCache(){this._cachedNavigator=null}async getCardsCenteredAtELO(e={limit:99,elo:`user`},t){let n;if(e.elo===`user`){let e=await this._getCurrentUser();n=-1;try{n=EloToNumber((await e.getCourseRegistrationsDoc()).courses.find(e=>e.courseID===this.id).elo)}catch{n=1e3}}else if(e.elo===`random`){let e=await GET_CACHED(`elo-bounds-${this.id}`,()=>this.getELOBounds());n=Math.round(e.low+Math.random()*(e.high-e.low))}else n=e.elo;let r=Math.max(2e3,e.limit*4),a=Date.now(),o=`hit`;if(!this._eloPoolCache||a-this._eloPoolCache.fetchedAt>this._eloPoolTtlMs){let e=await this.getCardsByELO(n,r);e.length>0&&(this._eloPoolCache={rows:e,fetchedAt:a}),o=`miss`}let rankAgainstCurrentElo=()=>{let e=this._eloPoolCache?.rows??[];return(t?e.filter(e=>t(e)):e).map(e=>({...e})).sort((e,t)=>Math.abs((e.elo??n)-n)-Math.abs((t.elo??n)-n))},s=rankAgainstCurrentElo();if(s.length<e.limit&&(o===`hit`||!this._eloPoolCache)){let e=await this.getCardsByELO(n,r);e.length>0&&(this._eloPoolCache={rows:e,fetchedAt:a}),s=rankAgainstCurrentElo(),o=`refresh`}let c=[];for(;c.length<e.limit&&s.length>0;){let e=randIntWeightedTowardZero(s.length),t=s.splice(e,1)[0];c.push(t)}return c.map(e=>({courseID:this.id,cardID:e.cardID,contentSourceType:`course`,contentSourceID:this.id,elo:e.elo,status:`new`}))}async searchCards(e){logger.log(`[CourseDB ${this.id}] Searching for: "${e}"`);let t;try{t=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`,"data.0.data":{$regex:`.*${e}.*`}}}),logger.log(`[CourseDB ${this.id}] Regex search on data[0].data successful`)}catch(n){logger.log(`[CourseDB ${this.id}] Regex search failed, falling back to manual search:`,n);let r=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`}});logger.log(`[CourseDB ${this.id}] Retrieved ${r.docs.length} documents for manual filtering`),t={docs:r.docs.filter(t=>{let n=JSON.stringify(t).toLowerCase().includes(e.toLowerCase());return n&&logger.log(`[CourseDB ${this.id}] Manual match found in document: ${t._id}`),n})}}if(logger.log(`[CourseDB ${this.id}] Found ${t.docs.length} displayable data documents`),t.docs.length===0){let e=await this.db.find({selector:{docType:`DISPLAYABLE_DATA`},limit:5});logger.log(`[CourseDB ${this.id}] Sample displayable data:`,e.docs.map(e=>({id:e._id,docType:e.docType,dataStructure:e.data?Object.keys(e.data):`no data field`,dataContent:e.data,fullDoc:e})))}let n=[];for(let e of t.docs){let t=await this.db.find({selector:{docType:`CARD`,id_displayable_data:{$in:[e._id]}}});logger.log(`[CourseDB ${this.id}] Displayable data ${e._id} linked to ${t.docs.length} cards`),n.push(...t.docs)}return logger.log(`[CourseDB ${this.id}] Total cards found: ${n.length}`),n}async find(e){return this.db.find(e)}}}}),classroomLookupDBTitle,CLASSROOM_CONFIG,ClassroomDBBase,StudentClassroomDB,TeacherClassroomDB,ClassroomLookupDB,init_classroomDB2=__esm({"src/impl/couch/classroomDB.ts"(){init_factory(),init_logger(),init_pouchdb_setup(),init_couch(),init_courseDB(),classroomLookupDBTitle=`classdb-lookup`,CLASSROOM_CONFIG=`ClassroomConfig`,ClassroomDBBase=class{constructor(){_defineProperty(this,`_id`,void 0),_defineProperty(this,`_db`,void 0),_defineProperty(this,`_cfg`,void 0),_defineProperty(this,`_initComplete`,!1),_defineProperty(this,`_content_prefix`,`content`)}get _content_searchkeys(){return getStartAndEndKeys2(this._content_prefix)}async getAssignedContent(){return logger.info(`Getting assigned content...`),(await this._db.allDocs({startkey:this._content_prefix,endkey:this._content_prefix+``,include_docs:!0})).rows.map(e=>e.doc)}getContentId(e){return e.type===`tag`?`${this._content_prefix}-${e.courseID}-${e.tagID}`:`${this._content_prefix}-${e.courseID}`}get ready(){return this._initComplete}getConfig(){return this._cfg}},StudentClassroomDB=class _StudentClassroomDB extends ClassroomDBBase{constructor(e,t){super(),_defineProperty(this,`userMessages`,void 0),_defineProperty(this,`_user`,void 0),this._id=e,this._user=t}async init(){let e=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+e,createPouchDBConfig());try{this._cfg=await this._db.get(CLASSROOM_CONFIG),this.userMessages=this._db.changes({since:`now`,live:!0,include_docs:!0}),this._initComplete=!0;return}catch(e){throw Error(`Error in StudentClassroomDB constructor: ${JSON.stringify(e)}`)}}static async factory(e,t){let n=new _StudentClassroomDB(e,t);return await n.init(),n}setChangeFcn(e){this.userMessages.on(`change`,e)}async getWeightedCards(e){let t=[],n=(await this._user.getPendingReviews()).filter(e=>e.scheduledFor===`classroom`&&e.schedulingAgentId===this._id);for(let e of n)t.push({cardId:e.cardId,courseId:e.courseId,score:1,reviewID:e._id,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom scheduled review`}]});let r=await this._user.getActiveCards(),a=new Set(r.map(e=>e.cardID)),o=hooks.utc(),s=(await this.getAssignedContent()).filter(e=>o.isAfter(hooks.utc(e.activeOn,REVIEW_TIME_FORMAT2)));logger.info(`[StudentClassroomDB] Due content: ${JSON.stringify(s)}`);for(let n of s)if(n.type===`course`){let{cards:r}=await new CourseDB(n.courseID,async()=>this._user).getWeightedCards(e);for(let e of r)a.has(e.cardId)||t.push({...e,provenance:[...e.provenance,{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`passed`,score:e.score,reason:`Assigned via classroom from course ${n.courseID}`}]})}else if(n.type===`tag`){let e=await getTag(n.courseID,n.tagID);for(let r of e.taggedCards)a.has(r)||t.push({cardId:r,courseId:n.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned tag: ${n.tagID}, new card`}]})}else n.type===`card`&&(a.has(n.cardID)||t.push({cardId:n.cardID,courseId:n.courseID,score:1,provenance:[{strategy:`classroom`,strategyName:`Classroom`,strategyId:`CLASSROOM`,action:`generated`,score:1,reason:`Classroom assigned card, new card`}]}));return logger.info(`[StudentClassroomDB] New cards from classroom ${this._cfg.name}: ${t.length} total (reviews + new)`),{cards:t.sort((e,t)=>t.score-e.score).slice(0,e)}}},TeacherClassroomDB=class _TeacherClassroomDB extends ClassroomDBBase{constructor(e){super(),_defineProperty(this,`_stuDb`,void 0),this._id=e}async init(){let e=`classdb-teacher-${this._id}`,t=`classdb-student-${this._id}`;this._db=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+e,createPouchDBConfig()),this._stuDb=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+t,createPouchDBConfig());try{return this._db.get(CLASSROOM_CONFIG).then(e=>{this._cfg=e,this._initComplete=!0}).then(()=>{})}catch(e){throw Error(`Error in TeacherClassroomDB constructor: ${JSON.stringify(e)}`)}}static async factory(e){let t=new _TeacherClassroomDB(e);return await t.init(),t}async removeContent(e){let t=this.getContentId(e);try{let e=await this._db.get(t);await this._db.remove(e),this._db.replicate.to(this._stuDb,{doc_ids:[t]})}catch(e){logger.error(`Failed to remove content:`,t,e)}}async assignContent(e){let t,n=this.getContentId(e);return t=e.type===`tag`?await this._db.put({courseID:e.courseID,tagID:e.tagID,type:`tag`,_id:n,assignedBy:e.assignedBy,assignedOn:hooks.utc(),activeOn:e.activeOn||hooks.utc()}):await this._db.put({courseID:e.courseID,type:`course`,_id:n,assignedBy:e.assignedBy,assignedOn:hooks.utc(),activeOn:e.activeOn||hooks.utc()}),t.ok?(this._db.replicate.to(this._stuDb,{doc_ids:[n]}),!0):!1}},ClassroomLookupDB=()=>new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+classroomLookupDBTitle,{skip_setup:!0})}}),AdminDB,init_adminDB2=__esm({"src/impl/couch/adminDB.ts"(){init_pouchdb_setup(),init_factory(),init_couch(),init_classroomDB2(),init_courseLookupDB(),init_logger(),AdminDB=class{constructor(){_defineProperty(this,`usersDB`,void 0),this.usersDB=new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`_users`,createPouchDBConfig())}async getUsers(){return(await this.usersDB.allDocs({include_docs:!0,...getStartAndEndKeys2(`org.couchdb.user:`)})).rows.map(e=>e.doc)}async getCourses(){let e=await CourseLookup.allCourseWare();return await Promise.all(e.map(e=>getCredentialledCourseConfig(e._id)))}async removeCourse(e){let t=await CourseLookup.delete(e),n=await getCredentialledCourseConfig(e);n.deleted=!0;let r=await updateCredentialledCourseConfig(e,n);return{ok:t.ok&&r.ok,id:t.id,rev:t.rev}}async getClassrooms(){let e=(await ClassroomLookupDB().allDocs({include_docs:!0})).rows.map(e=>e.doc.uuid);logger.debug(e.join(`, `));let t=[];for(let n=0;n<e.length;n++)try{let r=await TeacherClassroomDB.factory(e[n]);t.push(r)}catch(t){let r=t;r.error&&r.error===`not_found`&&logger.warn(`db ${e[n]} not found`)}return t.map(e=>({...e.getConfig(),_id:e._id}))}}}}),CourseSyncService_exports={};__export(CourseSyncService_exports,{CourseSyncService:()=>CourseSyncService});var DEFAULT_REPLICATION,CourseSyncService,init_CourseSyncService=__esm({"src/impl/couch/CourseSyncService.ts"(){var e;init_pouchdb_setup(),init_couch(),init_logger(),DEFAULT_REPLICATION={batchSize:1e3,batchesLimit:5},CourseSyncService=(e=class _CourseSyncService{constructor(){_defineProperty(this,`entries`,new Map),_defineProperty(this,`replicationOptions`,DEFAULT_REPLICATION)}static getInstance(){return _CourseSyncService.instance||(_CourseSyncService.instance=new _CourseSyncService),_CourseSyncService.instance}configure(e){e.replication&&(this.replicationOptions={...DEFAULT_REPLICATION,...e.replication},logger.info(`[CourseSyncService] Replication configured: batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit}`))}static resetInstance(){if(_CourseSyncService.instance){for(let[,e]of _CourseSyncService.instance.entries)e.localDB&&e.localDB.close().catch(()=>{});_CourseSyncService.instance.entries.clear()}_CourseSyncService.instance=null}async ensureSynced(e,t){let n=this.entries.get(e);if(n?.status.state===`ready`&&n.localDB){if(!await this.isLocalEpochStale(e,n.localDB))return;logger.info(`[CourseSyncService] Remote DB epoch changed for course ${e} \u2014 destroying stale local replica`);try{await n.localDB.destroy()}catch{}n.localDB=null,n.readyPromise=null}if(n?.status.state===`disabled`)return;if(n?.readyPromise)return n.readyPromise;let r={localDB:null,status:{state:`not-started`},readyPromise:null};return this.entries.set(e,r),r.readyPromise=this.performSync(e,r,t),r.readyPromise}getLocalDB(e){let t=this.entries.get(e);return t?.status.state===`ready`&&t.localDB?t.localDB:null}isReady(e){return this.entries.get(e)?.status.state===`ready`}getStatus(e){return this.entries.get(e)?.status??{state:`not-started`}}async performSync(e,t,n){try{if(!n&&(t.status={state:`checking-config`},!await this.checkLocalSyncEnabled(e))){t.status={state:`disabled`},t.readyPromise=null,logger.debug(`[CourseSyncService] Local sync disabled for course ${e}`);return}t.status={state:`syncing`};let r=this.localDBName(e),a=new pouchdb_setup_default(r);await this.isLocalEpochStale(e,a)&&(logger.info(`[CourseSyncService] Stale local DB detected for course ${e} \u2014 destroying before sync`),await a.destroy(),a=new pouchdb_setup_default(r)),t.localDB=a;let o=this.getRemoteDB(e),s=Date.now();logger.info(`[CourseSyncService] Starting one-shot replication for course ${e} (batch_size=${this.replicationOptions.batchSize}, batches_limit=${this.replicationOptions.batchesLimit})`);let c=await this.replicate(o,a),l=Date.now()-s;logger.info(`[CourseSyncService] Replication complete for course ${e}: ${c.docs_written} docs in ${l}ms`),t.status={state:`warming-views`};let u=Date.now();await this.warmViewIndices(a);let d=Date.now()-u;logger.info(`[CourseSyncService] View indices warmed for course ${e} in ${d}ms`),t.status={state:`ready`,docsReplicated:c.docs_written,syncTimeMs:l,viewWarmTimeMs:d}}catch(n){let r=n instanceof Error?n.message:String(n);if(logger.error(`[CourseSyncService] Sync failed for course ${e}: ${r}`),t.status={state:`error`,error:r},t.readyPromise=null,t.localDB){try{await t.localDB.destroy()}catch{}t.localDB=null}}}async checkLocalSyncEnabled(e){try{return(await this.getRemoteDB(e).get(`CourseConfig`)).localSync?.enabled===!0}catch(t){return logger.warn(`[CourseSyncService] Could not read CourseConfig for ${e}, assuming local sync disabled: ${t}`),!1}}replicate(e,t){return new Promise((n,r)=>{pouchdb_setup_default.replicate(e,t,{batch_size:this.replicationOptions.batchSize,batches_limit:this.replicationOptions.batchesLimit}).on(`complete`,e=>{n(e)}).on(`error`,e=>{r(e)})})}async warmViewIndices(e){for(let t of[`elo`,`getTags`])try{await e.query(t,{limit:1}),logger.debug(`[CourseSyncService] Warmed view index: ${t}`)}catch(e){logger.debug(`[CourseSyncService] Could not warm view ${t}: ${e}`)}}async isLocalEpochStale(e,t){try{let n=await this.getRemoteDB(e).get(`db-epoch`),r=null;try{r=await t.get(`db-epoch`)}catch{return!0}return n.epoch!==r.epoch}catch{return!1}}getRemoteDB(e){return getCourseDB2(e)}localDBName(e){return`coursedb-local-${e}`}},_defineProperty(e,`instance`,null),e)}});async function getCurrentSession(){try{if(ENV.COUCHDB_SERVER_URL===NOT_SET||ENV.COUCHDB_SERVER_PROTOCOL===NOT_SET)throw Error(`CouchDB server configuration not properly initialized. Protocol: "${ENV.COUCHDB_SERVER_PROTOCOL}", URL: "${ENV.COUCHDB_SERVER_URL}"`);let e=ENV.COUCHDB_SERVER_URL.endsWith(`/`)?ENV.COUCHDB_SERVER_URL.slice(0,-1):ENV.COUCHDB_SERVER_URL,t=`${ENV.COUCHDB_SERVER_PROTOCOL}://${e}/_session`;logger.debug(`Attempting session check at: ${t}`);let n=await(0,import_browser_ponyfill.default)(t,{method:`GET`,credentials:`include`});if(!n.ok)throw Error(`Session check failed: ${n.status}`);return await n.json()}catch(e){let t=ENV.COUCHDB_SERVER_URL.endsWith(`/`)?ENV.COUCHDB_SERVER_URL.slice(0,-1):ENV.COUCHDB_SERVER_URL,n=`${ENV.COUCHDB_SERVER_PROTOCOL}://${t}/_session`;throw logger.error(`Session check error attempting to connect to: ${n} - ${e}`),Error(`Session check failed connecting to ${n}: ${e}`)}}async function getLoggedInUsername(){let e=await getCurrentSession();if(e.userCtx.name&&e.userCtx.name!==``)return e.userCtx.name;throw Error(`No logged in user`)}var init_auth=__esm({"src/impl/couch/auth.ts"(){init_factory(),init_logger()}}),CouchDBSyncStrategy_exports={};__export(CouchDBSyncStrategy_exports,{CouchDBSyncStrategy:()=>CouchDBSyncStrategy});var log3,CouchDBSyncStrategy,init_CouchDBSyncStrategy=__esm({"src/impl/couch/CouchDBSyncStrategy.ts"(){init_factory(),init_types_legacy(),init_logger(),init_common(),init_pouchdb_setup(),init_couch(),init_auth(),log3=e=>{logger.info(e)},CouchDBSyncStrategy=class{constructor(){_defineProperty(this,`syncHandle`,void 0)}setupRemoteDB(e){return e===GuestUsername||e.startsWith(GuestUsername)?getLocalUserDB(e):this.getUserDB(e)}getWriteDB(e){return e===GuestUsername||e.startsWith(GuestUsername)?getLocalUserDB(e):this.getUserDB(e)}startSync(e,t){e!==t&&(this.syncHandle=pouchdb_setup_default.sync(e,t,{live:!0,retry:!0}))}stopSync(){this.syncHandle&&(this.syncHandle.cancel(),this.syncHandle=void 0)}canCreateAccount(){return!0}canAuthenticate(){return!0}async createAccount(e,t){let n=await this.getCurrentUsername(),r=n.startsWith(GuestUsername);r&&logger.info(`Creating account for funnel user ${n} -> ${e}`);try{let a=await this.getRemoteCouchRootDB().signUp(e,t);if(a.ok){log3(`CREATEACCOUNT: Successfully created account for ${e}`);try{let e=await this.getRemoteCouchRootDB().logOut();log3(`CREATEACCOUNT: logged out: ${e.ok}`)}catch{}let a=await this.getRemoteCouchRootDB().logIn(e,t);if(log3(`CREATEACCOUNT: logged in as new user: ${a.ok}`),a.ok){if(r){logger.info(`Migrating data from funnel account ${n} to ${e}`);let t=await this.migrateFunnelData(n,e);t.success||logger.warn(`Migration failed: ${t.error}`)}return{status:Status.ok,error:void 0}}else return{status:Status.error,error:`Failed to log in after account creation`}}else return logger.warn(`Signup not OK: ${JSON.stringify(a)}`),{status:Status.error,error:`Account creation failed`}}catch(e){return e.reason===`Document update conflict.`?{status:Status.error,error:`This username is taken!`}:(logger.error(`Error on signup: ${JSON.stringify(e)}`),{status:Status.error,error:e.message||`Unknown error during account creation`})}}async authenticate(e,t){try{return(await this.getRemoteCouchRootDB().logIn(e,t)).ok?(log3(`Successfully logged in as ${e}`),{ok:!0}):(log3(`Login failed for ${e}`),{ok:!1,error:`Invalid username or password`})}catch(t){return logger.error(`Authentication error for ${e}:`,t),{ok:!1,error:t.message||`Authentication failed`}}}async logout(){try{let e=await this.getRemoteCouchRootDB().logOut();return{ok:e.ok,error:e.ok?void 0:`Logout failed`}}catch(e){return logger.error(`Logout error:`,e),{ok:!1,error:e.message||`Logout failed`}}}async getCurrentUsername(){logger.log(`[funnel] CouchDBSyncStrategy.getCurrentUsername() called`);try{let e=await getLoggedInUsername();return logger.log(`[funnel] getLoggedInUsername() returned:`,e),e}catch(e){logger.log(`[funnel] getLoggedInUsername() failed, calling accomodateGuest()`),logger.log(`[funnel] Error was:`,e);let t=accomodateGuest();return logger.log(`[funnel] accomodateGuest() returned:`,t),t.username}}async migrateFunnelData(e,t){try{logger.info(`Starting data migration from ${e} to ${t}`);let n=getLocalUserDB(e),r=getLocalUserDB(t),a=await n.allDocs({include_docs:!0});logger.info(`Found ${a.rows.length} documents in funnel account`);let o=a.rows.filter(e=>!e.id.startsWith(`_design/`)).map(e=>({...e.doc,_rev:void 0}));return o.length>0?(await r.bulkDocs(o),logger.info(`Successfully migrated ${o.length} documents from ${e} to ${t}`)):logger.info(`No documents to migrate from funnel account`),{success:!0}}catch(e){return logger.error(`Migration failed:`,e),{success:!1,error:e instanceof Error?e.message:`Unknown error`}}}getRemoteCouchRootDB(){let e=ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`skuilder`;try{return new pouchdb_setup_default(e,{skip_setup:!0})}catch(e){throw logger.error(`Failed to initialize remote CouchDB connection:`,e),Error(`Failed to initialize CouchDB: ${JSON.stringify(e)}`)}}getUserDB(e){let t=!1,n=`userdb-${hexEncode(e)}`;return log3(`Fetching user database: ${n} (${e})`),new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+n,createPouchDBConfig())}}}});function createPouchDBConfig(){return ENV.COUCHDB_USERNAME&&ENV.COUCHDB_PASSWORD&&typeof window>`u`?{fetch(e,t={}){let n=btoa(`${ENV.COUCHDB_USERNAME}:${ENV.COUCHDB_PASSWORD}`),r=new Headers(t.headers||{});r.set(`Authorization`,`Basic ${n}`);let a={...t,headers:r};return pouchdb_setup_default.fetch(e,a)}}:pouchDBincludeCredentialsConfig}function getCourseDB2(e){return new pouchdb_setup_default(ENV.COUCHDB_SERVER_PROTOCOL+`://`+ENV.COUCHDB_SERVER_URL+`coursedb-`+e,createPouchDBConfig())}function filterAllDocsByPrefix2(e,t,n){let r={startkey:t,endkey:t+``,include_docs:!0};return n&&Object.assign(r,n),e.allDocs(r)}function getStartAndEndKeys2(e){return{startkey:e,endkey:e+``}}var isBrowser,GUEST_LOCAL_DB,localUserDB,pouchDBincludeCredentialsConfig,REVIEW_TIME_FORMAT2,init_couch=__esm({"src/impl/couch/index.ts"(){init_factory(),init_types_legacy(),init_logger(),init_pouchdb_setup(),init_contentSource(),init_adminDB2(),init_classroomDB2(),init_courseAPI(),init_courseDB(),init_CourseSyncService(),init_CouchDBSyncStrategy(),isBrowser=typeof window<`u`,isBrowser&&(window.process=import_browser.default),GUEST_LOCAL_DB=`userdb-${GuestUsername}`,new pouchdb_setup_default(GUEST_LOCAL_DB),pouchDBincludeCredentialsConfig={fetch(e,t){return t.credentials=`include`,pouchdb_setup_default.fetch(e,t)}},REVIEW_TIME_FORMAT2=`YYYY-MM-DD--kk:mm:ss-SSS`}});function accomodateGuest(){if(logger.log(`[funnel] accomodateGuest() called`),typeof localStorage>`u`)return logger.log(`[funnel] localStorage not available (Node.js environment), returning default guest`),{username:GuestUsername+`nodejs-test`,firstVisit:!0};let e=`sk-guest-uuid`,t,n=localStorage.getItem(e);if(logger.log(`[funnel] Checking localStorage for key:`,e),logger.log(`[funnel] Existing UUID value:`,n),logger.log(`[funnel] existingUUID !== null:`,n!==null),n!==null)t=!1,logger.log(`[funnel] Returning guest ${n} "logging in".`);else{t=!0,logger.log(`[funnel] No existing UUID, generating new one...`);let n=generateUUID();logger.log(`[funnel] Generated UUID:`,n),logger.log(`[funnel] UUID length:`,n.length);try{localStorage.setItem(e,n),logger.log(`[funnel] Successfully stored UUID in localStorage`);let t=localStorage.getItem(e);logger.log(`[funnel] Verification read from localStorage:`,t)}catch(e){logger.error(`[funnel] ERROR storing UUID:`,e)}logger.log(`[funnel] Accommodating a new guest with account: ${n}`)}let r=localStorage.getItem(e),a=GuestUsername+r;return logger.log(`[funnel] Final UUID from localStorage:`,r),logger.log(`[funnel] GuestUsername constant:`,GuestUsername),logger.log(`[funnel] Final username to return:`,a),{username:a,firstVisit:t};function generateUUID(){if(logger.log(`[funnel] Inside generateUUID()`),typeof crypto<`u`&&typeof crypto.randomUUID==`function`){let e=crypto.randomUUID();return logger.log(`[funnel] Generated UUID using crypto.randomUUID():`,e),e}if(typeof crypto<`u`&&typeof crypto.getRandomValues==`function`){let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=[Array.from(e.slice(0,4)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(4,6)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(6,8)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(8,10)).map(e=>e.toString(16).padStart(2,`0`)).join(``),Array.from(e.slice(10,16)).map(e=>e.toString(16).padStart(2,`0`)).join(``)].join(`-`);return logger.log(`[funnel] Generated UUID using crypto.getRandomValues():`,t),t}logger.warn(`[funnel] crypto API not available, using timestamp-based UUID (NOT SECURE)`);let e=new Date().getTime();typeof performance<`u`&&typeof performance.now==`function`&&(e+=performance.now());let t=`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,t=>{let n=(e+Math.random()*16)%16|0;return e=Math.floor(e/16),(t===`x`?n:n&3|8).toString(16)});return logger.log(`[funnel] Generated UUID (fallback):`,t),t}}async function getOrCreateClassroomRegistrationsDoc(e){let t;try{t=await getLocalUserDB(e).get(userClassroomsDoc)}catch(n){let r=n;if(r.status===404)await getLocalUserDB(e).put({_id:userClassroomsDoc,registrations:[]}),t=await getOrCreateClassroomRegistrationsDoc(e);else{let e={name:r.name,status:r.status,message:r.message,reason:r.reason,error:r.error};throw logger.error(`Database error in getOrCreateClassroomRegistrationsDoc (standalone function):`,e),Error(`Database error accessing classroom registrations: ${r.message||r.name||`Unknown error`} (status: ${r.status})`)}}return t}async function getOrCreateCourseRegistrationsDoc(e){let t;try{t=await getLocalUserDB(e).get(userCoursesDoc)}catch(n){if(n.status===404)await getLocalUserDB(e).put({_id:userCoursesDoc,courses:[],studyWeight:{}}),t=await getOrCreateCourseRegistrationsDoc(e);else throw Error(`Unexpected error ${JSON.stringify(n)} in getOrCreateCourseRegistrationDoc...`)}return t}async function updateUserElo(e,t,n){let r=await getOrCreateCourseRegistrationsDoc(e),a=r.courses.find(e=>e.courseID===t);return a.elo=n,getLocalUserDB(e).put(r)}async function registerUserForClassroom(e,t,n){return log4(`Registering user: ${e} in course: ${t}`),getOrCreateClassroomRegistrationsDoc(e).then(r=>{let a={classID:t,registeredAs:n};return r.registrations.filter(e=>e.classID===a.classID&&e.registeredAs===a.registeredAs).length===0?r.registrations.push(a):log4(`User ${e} is already registered for class ${t}`),getLocalUserDB(e).put(r)})}async function dropUserFromClassroom(e,t){return getOrCreateClassroomRegistrationsDoc(e).then(n=>{let r=-1;for(let e=0;e<n.registrations.length;e++)n.registrations[e].classID===t&&(r=e);return r!==-1&&n.registrations.splice(r,1),getLocalUserDB(e).put(n)})}async function getUserClassrooms(e){return getOrCreateClassroomRegistrationsDoc(e)}var log4,BaseUser,userCoursesDoc,userClassroomsDoc,init_BaseUserDB=__esm({"src/impl/common/BaseUserDB.ts"(){var e;init_core(),init_util(),init_types_legacy(),init_logger(),init_userDBHelpers(),init_updateQueue(),init_user_course_relDB(),init_couch(),log4=e=>{logger.info(e)},BaseUser=(e=class _BaseUser{static Dummy(e){return new _BaseUser(`Me`,e)}getUsername(){return this._username}isLoggedIn(){return!this._username.startsWith(GuestUsername)}remote(){return this.remoteDB}async createAccount(e,t){if(!this.syncStrategy.canCreateAccount())throw Error(`Account creation not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername))throw Error(`Cannot create a new account while logged in:
|
|
272
272
|
Currently logged-in as ${this._username}.`);let n=await this.syncStrategy.createAccount(e,t);if(n.status===Status.ok){log4(`Account created successfully, updating username to ${e}`),this._username=e;try{localStorage.removeItem(`sk-guest-uuid`)}catch(e){logger.warn(`localStorage not available (Node.js environment):`,e)}await this.init()}return{status:n.status,error:n.error||``}}async login(e,t){if(!this.syncStrategy.canAuthenticate())throw Error(`Authentication not supported by current sync strategy`);if(!this._username.startsWith(GuestUsername)&&this._username!=e){if(this._username!=e)throw Error(`Cannot change accounts while logged in.
|
|
273
273
|
Log out of account ${this.getUsername()} before logging in as ${e}.`);logger.warn(`User ${this._username} is already logged in, but executing login again.`)}let n=await this.syncStrategy.authenticate(e,t);if(n.ok){log4(`Logged in as ${e}`),this._username=e;try{localStorage.removeItem(`sk-guest-uuid`)}catch(e){logger.warn(`localStorage not available (Node.js environment):`,e)}await this.init()}return n}async resetUserData(){if(this.syncStrategy.canAuthenticate())return{status:Status.error,error:`Reset user data is only available for local-only mode. Use logout instead for remote sync.`};try{let e=getLocalUserDB(this._username),t=(await e.allDocs({include_docs:!1})).rows.filter(e=>{let t=e.id;return t.startsWith(DocTypePrefixes.CARDRECORD)||t.startsWith(DocTypePrefixes.SCHEDULED_CARD)||t.startsWith(DocTypePrefixes.STRATEGY_STATE)||t.startsWith(DocTypePrefixes.USER_OUTCOME)||t.startsWith(DocTypePrefixes.STRATEGY_LEARNING_STATE)||t===_BaseUser.DOC_IDS.COURSE_REGISTRATIONS||t===_BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS||t===_BaseUser.DOC_IDS.CONFIG}).map(e=>({_id:e.id,_rev:e.value.rev,_deleted:!0}));return t.length>0&&await e.bulkDocs(t),await this.init(),{status:Status.ok}}catch(e){return logger.error(`Failed to reset user data:`,e),{status:Status.error,error:e instanceof Error?e.message:`Unknown error during reset`}}}async logout(){if(!this.syncStrategy.canAuthenticate())return this._username=await this.syncStrategy.getCurrentUsername(),await this.init(),{ok:!0};let e=await this.syncStrategy.logout();return this._username=await this.syncStrategy.getCurrentUsername(),await this.init(),e}async get(e){return this.localDB.get(e)}update(e,t){return this.updateQueue.update(e,t)}async getCourseRegistrationsDoc(){logger.debug(`Fetching courseRegistrations for ${this.getUsername()}`);let e;try{return await this.localDB.get(_BaseUser.DOC_IDS.COURSE_REGISTRATIONS)}catch(t){if(t.status===404)await this.localDB.put({_id:_BaseUser.DOC_IDS.COURSE_REGISTRATIONS,courses:[],studyWeight:{}}),e=await this.getCourseRegistrationsDoc();else throw Error(`Unexpected error ${JSON.stringify(t)} in getOrCreateCourseRegistrationDoc...`)}return e}async getActiveCourses(){return(await this.getCourseRegistrationsDoc()).courses.filter(e=>e.status===void 0||e.status===`active`)}async getActiveCards(){let e=getStartAndEndKeys(DocTypePrefixes.SCHEDULED_CARD);return(await this.remoteDB.allDocs({startkey:e.startkey,endkey:e.endkey,include_docs:!0})).rows.map(e=>({courseID:e.doc.courseId,cardID:e.doc.cardId}))}async getActivityRecords(){try{let e=await this.getHistory(),t=[];if(!Array.isArray(e))return logger.error(`getHistory did not return an array:`,e),t;let n=0;for(let r=0;r<e.length;r++)try{e[r]&&Array.isArray(e[r].records)&&e[r].records.forEach(e=>{try{if(!e.timeStamp)return;let r;if(typeof e.timeStamp==`object`)if(typeof e.timeStamp.toDate==`function`)r=e.timeStamp.toISOString();else if(e.timeStamp instanceof Date)r=e.timeStamp.toISOString();else{n<3&&(logger.warn(`Unknown timestamp object type:`,e.timeStamp),n++);return}else if(typeof e.timeStamp==`string`){let t=new Date(e.timeStamp);if(isNaN(t.getTime()))return;r=e.timeStamp}else if(typeof e.timeStamp==`number`)r=new Date(e.timeStamp).toISOString();else return;t.push({timeStamp:r,courseID:e.courseID||`unknown`,cardID:e.cardID||`unknown`,timeSpent:e.timeSpent||0,type:`card_view`})}catch{}})}catch(e){logger.error(`Error processing history item:`,e)}return logger.debug(`Found ${t.length} activity records`),t}catch(e){return logger.error(`Error in getActivityRecords:`,e),[]}}async getReviewstoDate(e,t){let n=getStartAndEndKeys(DocTypePrefixes.SCHEDULED_CARD),r=await this.remoteDB.allDocs({startkey:n.startkey,endkey:n.endkey,include_docs:!0});return log4(`Fetching ${this._username}'s scheduled reviews${t?` for course ${t}`:``}.`),r.rows.filter(n=>{if(n.id.startsWith(DocTypePrefixes.SCHEDULED_CARD)){let r=hooks.utc(n.id.substr(DocTypePrefixes.SCHEDULED_CARD.length),REVIEW_TIME_FORMAT);if(e.isAfter(r)&&(t===void 0||n.doc.courseId===t))return!0}}).map(e=>e.doc)}async getReviewsForcast(e){let t=hooks.utc().add(e,`days`);return this.getReviewstoDate(t)}async getPendingReviews(e){let t=hooks.utc();return this.getReviewstoDate(t,e)}async getScheduledReviewCount(e){return(await this.getPendingReviews(e)).length}async getRegisteredCourses(){return(await this.getCourseRegistrationsDoc()).courses.filter(e=>!e.status||e.status===`active`||e.status===`maintenance-mode`)}async getCourseRegDoc(e){let t=(await this.getCourseRegistrationsDoc()).courses.find(t=>t.courseID===e);if(t)return t;throw Error(`Course registration not found for course ID: ${e}`)}async registerForCourse(e,t=!1){return this.getCourseRegistrationsDoc().then(n=>{let r=t?`preview`:`active`;logger.debug(`Registering for ${e} with status: ${r}`);let a={status:r,courseID:e,user:!0,admin:!1,moderator:!1,elo:{global:{score:1e3,count:0},tags:{},misc:{}}};return n.courses.filter(e=>e.courseID===a.courseID).length===0?(log4(`It's a new course registration!`),n.courses.push(a),n.studyWeight[e]=1):n.courses.forEach(t=>{log4(`Found the previously registered course!`),t.courseID===e&&(t.status=r)}),this.localDB.put(n)}).catch(e=>{throw log4(`Registration failed because of: ${JSON.stringify(e)}`),e})}async dropCourse(e,t=`dropped`){return this.getCourseRegistrationsDoc().then(n=>{let r=-1;for(let t=0;t<n.courses.length;t++)n.courses[t].courseID===e&&(r=t);if(r!==-1)delete n.studyWeight[e],n.courses[r].status=t;else throw Error(`User ${this.getUsername()} is not currently registered for course ${e}`);return this.localDB.put(n)})}async getCourseInterface(e){return new UsrCrsData(this,e)}async getUserEditableCourses(){let e=[],t=await this.getCourseRegistrationsDoc();return e=e.concat(t.courses.map(e=>e.courseID)),await Promise.all(e.map(async e=>await getCredentialledCourseConfig(e)))}async getConfig(){let e={_id:_BaseUser.DOC_IDS.CONFIG,darkMode:!1,likesConfetti:!1,sessionTimeLimit:5};try{let e=await this.localDB.get(_BaseUser.DOC_IDS.CONFIG);return logger.debug(`Raw config from DB:`,e),e}catch(t){let n=t;if(n.name&&n.name===`not_found`)return await this.localDB.put(e),this.getConfig();throw logger.error(`Error setting user default config:`,t),Error(`Error returning the user's configuration: ${JSON.stringify(t)}`)}}async setConfig(e){logger.debug(`Setting Config items ${JSON.stringify(e)}`);let t=await this.getConfig(),n=await this.localDB.put({...t,...e});n.ok?logger.debug(`Config items set: ${JSON.stringify(e)}`):logger.error(`Error setting config items: ${JSON.stringify(n)}`)}static async instance(e,t){return t?(_BaseUser._instance=new _BaseUser(t,e),await _BaseUser._instance.init(),_BaseUser._instance):_BaseUser._instance&&_BaseUser._initialized?_BaseUser._instance:_BaseUser._instance?new Promise(e=>{(function waitForUser(){if(_BaseUser._initialized)return e(_BaseUser._instance);setTimeout(waitForUser,50)})()}):(_BaseUser._instance=new _BaseUser(await e.getCurrentUsername(),e),await _BaseUser._instance.init(),_BaseUser._instance)}constructor(e,t){_defineProperty(this,`_username`,void 0),_defineProperty(this,`syncStrategy`,void 0),_defineProperty(this,`localDB`,void 0),_defineProperty(this,`remoteDB`,void 0),_defineProperty(this,`writeDB`,void 0),_defineProperty(this,`updateQueue`,void 0),_BaseUser._initialized=!1,this._username=e,this.syncStrategy=t,this.setDBandQ()}setDBandQ(){this.localDB=getLocalUserDB(this._username),this.remoteDB=this.syncStrategy.setupRemoteDB(this._username),this.writeDB=this.syncStrategy.getWriteDB?this.syncStrategy.getWriteDB(this._username):this.localDB,this.updateQueue=new UpdateQueue(this.localDB,this.writeDB)}async init(){if(_BaseUser._initialized=!1,this._username===`admin`){_BaseUser._initialized=!0;return}this.setDBandQ(),this.syncStrategy.startSync(this.localDB,this.remoteDB),this.applyDesignDocs().catch(e=>{log4(`Error in applyDesignDocs background task: ${e}`),e&&typeof e==`object`&&log4(`Full error details in applyDesignDocs: ${JSON.stringify(e)}`)}),this.deduplicateReviews().catch(e=>{log4(`Error in deduplicateReviews background task: ${e}`),e&&typeof e==`object`&&log4(`Full error details in background task: ${JSON.stringify(e)}`)}),_BaseUser._initialized=!0}async applyDesignDocs(){if(log4(`Starting applyDesignDocs for user: ${this._username}`),log4(`Remote DB name: ${this.remoteDB.name||`unknown`}`),this._username===`admin`){log4(`Skipping design docs for admin user`);return}log4(`Applying ${_BaseUser.designDocs.length} design docs`);for(let e of _BaseUser.designDocs){log4(`Applying design doc: ${e._id}`);try{try{let t=await this.remoteDB.get(e._id);await this.remoteDB.put({...e,_rev:t._rev})}catch(t){if(t?.name===`not_found`)await this.remoteDB.put(e);else throw t}}catch(t){if(t.name&&t.name===`conflict`)logger.warn(`Design doc ${e._id} update conflict - will retry`),await new Promise(e=>setTimeout(e,1e3)),await this.applyDesignDoc(e);else throw logger.error(`Failed to apply design doc ${e._id}:`,t),t}}}async applyDesignDoc(e,t=3){try{let t=await this.remoteDB.get(e._id);await this.remoteDB.put({...e,_rev:t._rev})}catch(n){if(n?.name===`conflict`&&t>0)return await new Promise(e=>setTimeout(e,1e3)),this.applyDesignDoc(e,t-1);throw n}}async putCardRecord(e){let t=getCardHistoryID(e.courseID,e.cardID);e.timeStamp=hooks.utc(e.timeStamp).toString();try{let n=await this.update(t,function(t){return t.records.push(e),t.bestInterval=t.bestInterval||0,t.lapses=t.lapses||0,t.streak=t.streak||0,t});return n.records=n.records.map(e=>{let t={...e};return t.timeStamp=hooks.utc(e.timeStamp),t}),n}catch(n){let r=n;if(r.status===404)try{let n={_id:t,cardID:e.cardID,courseID:e.courseID,records:[e],lapses:0,streak:0,bestInterval:0},r=await this.writeDB.put(n);return{...n,_rev:r.rev}}catch(e){throw Error(`Failed to create CardHistory for ${t}. Reason: ${e}`)}else throw Error(`putCardRecord failed because of:
|
|
274
274
|
name:${r.name}
|
|
@@ -373,4 +373,4 @@ Example:
|
|
|
373
373
|
`}let g=p.filter(e=>e.score>=_SessionController.WELL_INDICATED_SCORE).length,_=[];for(let e of p){let t={cardID:e.cardId,courseID:e.courseId,contentSourceType:`course`,contentSourceID:e.courseId,status:`new`};_.push(t),m+=`New: ${e.courseId}::${e.cardId} (score: ${e.score.toFixed(2)})
|
|
374
374
|
`}if(n){let e=this.newQ.mergeToFront(_,e=>e.cardID);m+=`Additive merge: ${e} new cards added to front of newQ
|
|
375
375
|
`}else if(t)this.newQ.replaceAll(_,e=>e.cardID);else for(let e of _)this.newQ.add(e,e.cardID);return this.log(m),g}_getItemsToHydrate(){let e=[],t=2;for(let t=0;t<Math.min(2,this.reviewQ.length);t++)e.push(this.reviewQ.peek(t));for(let t=0;t<Math.min(2,this.newQ.length);t++)e.push(this.newQ.peek(t));for(let t=0;t<Math.min(2,this.failedQ.length);t++)e.push(this.failedQ.peek(t));return e}_selectNextItemToHydrate(){let e=Math.random(),t=.1,n=.75;if(this.reviewQ.length===0&&this.failedQ.length===0&&this.newQ.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;if(this.newQ.dequeueCount<this.sources.length&&this.newQ.length)return this.newQ.peek(0);let r=this.estimateCleanupTime(),a=this.estimateReviewTime();return this._secondsRemaining-(r+a)>20?(t=.5,n=.9):this._secondsRemaining-r>20?(t=.05,n=.9):(t=.01,n=.1),this.failedQ.length===0&&(n=1),this.reviewQ.length===0&&(t=n),e<t&&this.newQ.length?this.newQ.peek(0):e<n&&this.reviewQ.length?this.reviewQ.peek(0):this.failedQ.length?this.failedQ.peek(0):(this.log(`No more cards available for the session!`),null)}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.newQ.length===0&&this.reviewQ.length===0&&this.failedQ.length===0&&(this.log(`nextCard: queues empty, awaiting in-flight replan before drawing`),await this._replanPromise),this.newQ.length<=_SessionController.DEPLETION_PREFETCH_THRESHOLD&&this._secondsRemaining>0&&!this._replanPromise){this._suppressQualityReplan=!1;let e=this.reviewQ.length+this.failedQ.length;this.log(`[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${e} in other queues) with ${this._secondsRemaining}s remaining. Triggering background replan.`),this.requestReplan()}if(!this._suppressQualityReplan&&this._wellIndicatedRemaining<=3&&this.newQ.length>0&&!this._replanPromise&&(this.log(`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (newQ: ${this.newQ.length}). Triggering background replan.`),this.requestReplan()),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.newQ.length===0&&this.reviewQ.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.newQ.length===0&&this.reviewQ.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`:e.status===`review`?`reviewQ`:`newQ`;return recordCardPresentation(e.cardID,e.courseID,this.courseNameCache.get(e.courseID),n,r),snapshotQueues(this.reviewQ.length,this.newQ.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};return await this.services.response.processResponse(e,t,u,n,r,a,o,s,c,l)}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)}else (e===`dismiss-error`||e===`dismiss-failed`)&&this.hydrationService.removeCard(this._currentCard.item.cardID)}removeItemFromQueue(e){this.reviewQ.peek(0)?.cardID===e.cardID?this.reviewQ.dequeue(e=>e.cardID):this.newQ.peek(0)?.cardID===e.cardID?(this.newQ.dequeue(e=>e.cardID),this._wellIndicatedRemaining>0&&this._wellIndicatedRemaining--):this.failedQ.peek(0)?.cardID===e.cardID&&this.failedQ.dequeue(e=>e.cardID)}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),_SessionController2);init_TagFilteredContentSource(),init_factory();export{processCustomQuestionsData as $,getDbPath as A,isFilter as B,createOrchestrationContext as C,getCardHistoryID as D,getAppDataDirectory as E,hasRegisteredNavigator as F,log as G,isQuestionRecord as H,importParsedCards as I,mountPipelineDebugger as J,mixerDebugAPI as K,initializeDataDirectory as L,getRegisteredNavigatorNames as M,getRegisteredNavigatorRole as N,getCardOrigin as O,getStudySource as P,pipelineDebugAPI as Q,initializeDataLayer as R,computeSpread as S,ensureAppDataDirectory as T,isQuestionTypeRegistered as U,isGenerator as V,isReview as W,mountUserDBDebugger as X,mountSessionDebugger as Y,newInterval as Z,buildStrategyStateId as _,validateStaticCourse as _t,ENV as a,registerSeedData as at,computeEffectiveWeight as b,Loggable as c,scoreAccuracyInZone as ct,NavigatorRoles as d,startSessionTracking as dt,recordCardPresentation as et,Navigators as f,updateLearningState as ft,areQuestionRecords as g,validateProcessorConfig as gt,TagFilteredContentSource as h,validateMigration as ht,DocTypePrefixes as i,registerQuestionType as it,getRegisteredNavigator as j,getDataLayer as k,NOT_SET as l,sessionDebugAPI as lt,SessionController as m,userDBDebugAPI as mt,CourseLookup as n,registerDataShape as nt,FileSystemError as o,removeDataShape as ot,QuotaRoundRobinMixer as p,updateStrategyWeight as pt,mountMixerDebugger as q,DocType as r,registerNavigator as rt,GuestUsername as s,removeQuestionType as st,ContentNavigator as t,recordUserOutcome as tt,NavigatorRole as u,snapshotQueues as ut,captureMixerRun as v,endSessionTracking as w,computeOutcomeSignal as x,computeDeviation as y,initializeNavigatorRegistry as z};
|
|
376
|
-
//# sourceMappingURL=dist-
|
|
376
|
+
//# sourceMappingURL=dist-D6VPcBkn.js.map
|