@contractspec/module.learning-journey 3.7.18 → 4.0.0
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/README.md +3 -2
- package/dist/browser/contracts/index.js +1 -1
- package/dist/browser/contracts/journey.js +1 -0
- package/dist/browser/contracts/operations.js +1 -1
- package/dist/browser/docs/index.js +13 -12
- package/dist/browser/docs/learning-journey.docblock.js +13 -12
- package/dist/browser/engines/index.js +1 -1
- package/dist/browser/engines/xp.js +1 -1
- package/dist/browser/entities/index.js +1 -1
- package/dist/browser/entities/journey.js +1 -0
- package/dist/browser/index.js +14 -13
- package/dist/browser/learning-journey.feature.js +1 -1
- package/dist/browser/runtime/index.js +1 -0
- package/dist/browser/runtime/matchers.js +1 -0
- package/dist/browser/runtime/progress-state.js +1 -0
- package/dist/browser/runtime/snapshot.js +1 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/{onboarding.d.ts → journey.d.ts} +477 -180
- package/dist/contracts/journey.js +2 -0
- package/dist/contracts/operations.js +1 -1
- package/dist/docs/index.js +13 -12
- package/dist/docs/learning-journey.docblock.js +13 -12
- package/dist/engines/index.js +1 -1
- package/dist/engines/xp.d.ts +1 -1
- package/dist/engines/xp.js +1 -1
- package/dist/entities/index.d.ts +28 -27
- package/dist/entities/index.js +1 -1
- package/dist/entities/{onboarding.d.ts → journey.d.ts} +61 -74
- package/dist/entities/journey.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +14 -13
- package/dist/learning-journey.feature.js +1 -1
- package/dist/node/contracts/index.js +1 -1
- package/dist/node/contracts/journey.js +1 -0
- package/dist/node/contracts/operations.js +1 -1
- package/dist/node/docs/index.js +13 -12
- package/dist/node/docs/learning-journey.docblock.js +13 -12
- package/dist/node/engines/index.js +1 -1
- package/dist/node/engines/xp.js +1 -1
- package/dist/node/entities/index.js +1 -1
- package/dist/node/entities/journey.js +1 -0
- package/dist/node/index.js +14 -13
- package/dist/node/learning-journey.feature.js +1 -1
- package/dist/node/runtime/index.js +1 -0
- package/dist/node/runtime/matchers.js +1 -0
- package/dist/node/runtime/progress-state.js +1 -0
- package/dist/node/runtime/snapshot.js +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/journey-runtime.test.d.ts +1 -0
- package/dist/runtime/matchers.d.ts +20 -0
- package/dist/runtime/matchers.js +2 -0
- package/dist/runtime/progress-state.d.ts +19 -0
- package/dist/runtime/progress-state.js +2 -0
- package/dist/runtime/snapshot.d.ts +8 -0
- package/dist/runtime/snapshot.js +2 -0
- package/dist/track-spec.d.ts +118 -87
- package/package.json +86 -30
- package/dist/browser/contracts/onboarding.js +0 -1
- package/dist/browser/entities/onboarding.js +0 -1
- package/dist/contracts/onboarding.js +0 -2
- package/dist/entities/onboarding.js +0 -2
- package/dist/node/contracts/onboarding.js +0 -1
- package/dist/node/entities/onboarding.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var R={timezone:"UTC",freezesPerMonth:2,maxFreezes:5,gracePeriodHours:4};class f{config;constructor(e={}){this.config={...R,...e}}update(e,r=new Date){let o=this.getDateString(r),n={state:{...e},streakMaintained:!1,streakLost:!1,freezeUsed:!1,newStreak:!1,daysMissed:0};if(!e.lastActivityDate)return n.state.currentStreak=1,n.state.longestStreak=Math.max(1,e.longestStreak),n.state.lastActivityAt=r,n.state.lastActivityDate=o,n.newStreak=!0,n.streakMaintained=!0,n;if(e.lastActivityDate===o)return n.state.lastActivityAt=r,n.streakMaintained=!0,n;let t=this.getDaysBetween(e.lastActivityDate,o);if(t===1)return n.state.currentStreak=e.currentStreak+1,n.state.longestStreak=Math.max(n.state.currentStreak,e.longestStreak),n.state.lastActivityAt=r,n.state.lastActivityDate=o,n.streakMaintained=!0,n;n.daysMissed=t-1;let s=n.daysMissed;if(s<=e.freezesRemaining)return n.state.freezesRemaining=e.freezesRemaining-s,n.state.freezeUsedAt=r,n.state.currentStreak=e.currentStreak+1,n.state.longestStreak=Math.max(n.state.currentStreak,e.longestStreak),n.state.lastActivityAt=r,n.state.lastActivityDate=o,n.freezeUsed=!0,n.streakMaintained=!0,n;return n.streakLost=!0,n.state.currentStreak=1,n.state.lastActivityAt=r,n.state.lastActivityDate=o,n.newStreak=!0,n}checkStatus(e,r=new Date){if(!e.lastActivityDate)return{isActive:!1,willExpireAt:null,canUseFreeze:!1,daysUntilExpiry:0};let o=this.getDateString(r),n=this.getDaysBetween(e.lastActivityDate,o);if(n===0){let s=this.addDays(r,1);return s.setHours(23,59,59,999),{isActive:!0,willExpireAt:s,canUseFreeze:e.freezesRemaining>0,daysUntilExpiry:1}}if(n===1){let s=new Date(r);return s.setHours(23+this.config.gracePeriodHours,59,59,999),{isActive:!0,willExpireAt:s,canUseFreeze:e.freezesRemaining>0,daysUntilExpiry:0}}let t=n-1;return{isActive:t<=e.freezesRemaining,willExpireAt:null,canUseFreeze:t<=e.freezesRemaining,daysUntilExpiry:-t}}useFreeze(e,r=new Date){if(e.freezesRemaining<=0)return null;return{...e,freezesRemaining:e.freezesRemaining-1,freezeUsedAt:r}}awardMonthlyFreezes(e){return{...e,freezesRemaining:Math.min(e.freezesRemaining+this.config.freezesPerMonth,this.config.maxFreezes)}}getInitialState(){return{currentStreak:0,longestStreak:0,lastActivityAt:null,lastActivityDate:null,freezesRemaining:this.config.freezesPerMonth,freezeUsedAt:null}}getMilestones(e){let r=[3,7,14,30,60,90,180,365,500,1000],o=r.filter((t)=>e>=t),n=r.find((t)=>e<t)??null;return{achieved:o,next:n}}getDateString(e){let r=e.getFullYear(),o=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return`${r}-${o}-${n}`}getDaysBetween(e,r){let o=new Date(e),t=new Date(r).getTime()-o.getTime();return Math.floor(t/86400000)}addDays(e,r){return new Date(e.getTime()+r*24*60*60*1000)}}var X=new f;import{defineTranslation as V}from"@contractspec/lib.contracts-spec/translations";var B=V({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels for the learning-journey module",owners:["platform"],stability:"experimental"},locale:"en",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Score Bonus",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Perfect Score",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"First Attempt",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Retry Penalty",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Streak Bonus",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as q}from"@contractspec/lib.contracts-spec/translations";var T=q({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels (Spanish)",owners:["platform"],stability:"experimental"},locale:"es",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Bonificación por puntuación",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Puntuación perfecta",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"Primer intento",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Penalización por reintento",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonificación por racha",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as W}from"@contractspec/lib.contracts-spec/translations";var w=W({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels (French)",owners:["platform"],stability:"experimental"},locale:"fr",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Bonus de score",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Score parfait",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"Premier essai",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Pénalité de réessai",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonus de série",description:"XP breakdown label for streak bonus"}}});import{createI18nFactory as z}from"@contractspec/lib.contracts-spec/translations";var l=z({specKey:"learning-journey.messages",catalogs:[B,w,T]}),D=l.create,ae=l.getDefault,ye=l.resetRegistry;var g={baseValues:{lesson_complete:10,quiz_pass:20,quiz_perfect:50,flashcard_review:1,course_complete:200,module_complete:50,streak_bonus:5,achievement_unlock:0,daily_goal_complete:15,first_lesson:25,journey_step:5,journey_complete:50},scoreThresholds:[{min:90,multiplier:1.5},{min:80,multiplier:1.25},{min:70,multiplier:1},{min:60,multiplier:0.75},{min:0,multiplier:0.5}],streakTiers:[{days:365,bonus:50},{days:180,bonus:30},{days:90,bonus:20},{days:30,bonus:15},{days:14,bonus:10},{days:7,bonus:5},{days:3,bonus:2},{days:1,bonus:0}],perfectScoreMultiplier:1.5,firstAttemptBonus:10,retryPenalty:0.5,speedBonusMultiplier:1.2,speedBonusThreshold:0.8};class J{config;constructor(e={}){this.config={...g,...e,baseValues:{...g.baseValues,...e.baseValues},scoreThresholds:e.scoreThresholds||g.scoreThresholds,streakTiers:e.streakTiers||g.streakTiers}}calculate(e){let r=[],o=e.baseXp??this.config.baseValues[e.activity],n=o;if(r.push({source:"base",amount:o}),e.score!==void 0){let t=this.getScoreMultiplier(e.score);if(t!==1){let s=Math.round(o*(t-1));n+=s,r.push({source:"score_bonus",amount:s,multiplier:t})}if(e.score===100){let s=Math.round(o*(this.config.perfectScoreMultiplier-1));n+=s,r.push({source:"perfect_score",amount:s,multiplier:this.config.perfectScoreMultiplier})}}if(e.attemptNumber===1&&!e.isRetry)n+=this.config.firstAttemptBonus,r.push({source:"first_attempt",amount:this.config.firstAttemptBonus});if(e.isRetry){let t=Math.round(n*(1-this.config.retryPenalty));n-=t,r.push({source:"retry_penalty",amount:-t,multiplier:this.config.retryPenalty})}if(e.currentStreak&&e.currentStreak>0){let t=this.getStreakBonus(e.currentStreak);if(t>0)n+=t,r.push({source:"streak_bonus",amount:t})}if(o>0)n=Math.max(1,n);return{totalXp:Math.round(n),baseXp:o,breakdown:r}}calculateStreakBonus(e){let r=this.getStreakBonus(e);return{totalXp:r,baseXp:r,breakdown:[{source:"streak_bonus",amount:r}]}}getXpForLevel(e){if(e<=1)return 0;return Math.round(100*Math.pow(e-1,1.5))}getLevelFromXp(e){let r=1,o=this.getXpForLevel(r+1);while(e>=o&&r<1000)r++,o=this.getXpForLevel(r+1);let n=this.getXpForLevel(r),t=this.getXpForLevel(r+1);return{level:r,xpInLevel:e-n,xpForNextLevel:t-n}}getScoreMultiplier(e){for(let r of this.config.scoreThresholds)if(e>=r.min)return r.multiplier;return 1}getStreakBonus(e){for(let r of this.config.streakTiers)if(e>=r.days)return r.bonus;return 0}}var Q={base:"xp.source.base",score_bonus:"xp.source.scoreBonus",perfect_score:"xp.source.perfectScore",first_attempt:"xp.source.firstAttempt",retry_penalty:"xp.source.retryPenalty",streak_bonus:"xp.source.streakBonus"};function fe(e,r){let o=D(r),n=Q[e];return n?o.t(n):e}var ge=new J;var A=(e,r)=>{if(!e)return!0;if(!r)return!1;return Object.entries(e).every(([o,n])=>r[o]===n)},h=(e,r)=>{if(e.eventName!==r.name)return!1;if(e.eventVersion!==void 0&&r.version!==void 0&&e.eventVersion!==r.version)return!1;if(e.sourceModule&&r.sourceModule&&e.sourceModule!==r.sourceModule)return!1;return A(e.payloadFilter,r.payload)},E=(e,r,o,n)=>{if(e.kind==="count"){if(!h(e,r))return{matched:!1};let t=o.occurrences+1;return{matched:(e.withinHours===void 0||n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000<=e.withinHours)&&t>=e.atLeast,occurrences:t}}if(e.kind==="time_window"){if(!h(e,r))return{matched:!1};if(e.availableAfterHours!==void 0&&n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000<e.availableAfterHours)return{matched:!1};if(e.withinHoursOfStart!==void 0&&n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000>e.withinHoursOfStart)return{matched:!1};return{matched:!0}}if(e.kind==="mastery"){if(r.name!==e.eventName)return{matched:!1};if(!A(e.payloadFilter,r.payload))return{matched:!1};let t=e.masteryField??"mastery",s=r.payload?.[t];if(typeof s!=="number")return{matched:!1};if(s<e.minimumMastery)return{matched:!1};let u=o.masteryCount+1;return{masteryCount:u,matched:u>=(e.requiredCount??1)}}return{matched:h(e,r)}},k=(e,r)=>{if(!e||!r)return{};let o=r.getTime(),n=o;if(e.unlockOnDay!==void 0)n=o+(e.unlockOnDay-1)*24*60*60*1000;if(e.unlockAfterHours!==void 0)n=o+e.unlockAfterHours*60*60*1000;let t=new Date(n),s=e.dueWithinHours!==void 0?new Date(t.getTime()+e.dueWithinHours*60*60*1000):void 0;return{availableAt:t,dueAt:s}},I=(e,r)=>{let o=r.steps.find((n)=>n.stepId===e.stepId);if(!o)return!1;if(e.kind==="step_completed")return o.status==="COMPLETED"||o.status==="SKIPPED";return o.selectedBranchKey===e.branchKey},b=(e,r,o,n)=>{if(!e.branches?.length)return;if(!r)return e.branches.find((t)=>t.when===void 0);for(let t of e.branches){if(!t.when)continue;if(E(t.when,r,o,n).matched)return t}return e.branches.find((t)=>t.when===void 0)};var Z=(e)=>e.map((r)=>({...r})),$=(e,r)=>{let o=new Map;for(let n of e.steps){let t=r.steps.find((u)=>u.stepId===n.id),s=n.branches?.find((u)=>u.key===t?.selectedBranchKey);if(!s?.blockStepIds?.length)continue;for(let u of s.blockStepIds)o.set(u,{blockedByBranchKey:s.key,blockedByStepId:n.id})}return o},y=(e,r,o={})=>{let n=o.now??new Date,t={...r,badges:[...r.badges],eventLog:[...r.eventLog],steps:Z(r.steps),streak:{...r.streak}},s=$(e,t);for(let u of e.steps){let c=t.steps.find((S)=>S.stepId===u.id);if(!c)continue;let{availableAt:i,dueAt:d}=k(u.availability,t.startedAt);if(c.availableAt=i,c.dueAt=d,c.status==="COMPLETED"||c.status==="SKIPPED")continue;let a=s.get(u.id);if(a){c.blockedAt??=n,c.blockedByBranchKey=a.blockedByBranchKey,c.blockedByStepId=a.blockedByStepId,c.status="BLOCKED";continue}let m=u.prerequisites??[],p=u.prerequisiteMode==="any"?m.some((S)=>I(S,t)):m.every((S)=>I(S,t)),j=(m.length===0?!0:p)&&(!i||n.getTime()>=i.getTime());if(d&&n.getTime()>d.getTime()){c.missedAt??=n,c.status="MISSED";continue}c.status=j?"AVAILABLE":"LOCKED"}return t},P=(e)=>{let r=e.steps.filter((o)=>o.status!=="BLOCKED");return r.length>0&&r.every((o)=>o.status==="COMPLETED"||o.status==="SKIPPED")},x=(e,r,o={})=>{let n=y(e,r,o),t=n.steps.filter((p)=>p.status!=="BLOCKED"),s=n.steps.filter((p)=>p.status==="COMPLETED"),u=n.steps.filter((p)=>p.status==="AVAILABLE").map((p)=>p.stepId),c=n.steps.filter((p)=>p.status==="BLOCKED").map((p)=>p.stepId),i=n.steps.filter((p)=>p.status==="MISSED").map((p)=>p.stepId),d=t.filter((p)=>p.status==="COMPLETED"||p.status==="SKIPPED").length,a=t.length>0?Math.round(d/t.length*100):0,m=u[0]??null;return{activeStepCount:t.length,availableStepIds:u,badges:[...n.badges],blockedStepIds:c,completedAt:n.completedAt,completedStepCount:d,completedStepIds:s.map((p)=>p.stepId),currentStepId:m,isCompleted:P(n),lastActivityAt:n.lastActivityAt,learnerId:n.learnerId,missedStepIds:i,nextStepId:m,progressPercent:a,startedAt:n.startedAt,steps:n.steps,streakDays:n.streak.currentStreak,totalSteps:e.steps.length,trackId:n.trackId,xpEarned:n.xpEarned}};var C=new f,L=new J,K=(e)=>({...e,badges:[...e.badges],eventLog:[...e.eventLog],steps:e.steps.map((r)=>({...r})),streak:{...e.streak}}),N=(e)=>e.reward?.xp??e.xpReward??0,M=(e,r)=>e.steps.find((o)=>o.stepId===r),v=(e,r)=>{if(r&&!e.badges.includes(r))e.badges.push(r)},O=(e,r,o,n)=>{if(!P(r)||r.completionRewardApplied)return r;let t=e.completionRewards?.xp??0;if(t>0)r.xpEarned+=n.calculate({activity:"journey_complete",baseXp:t,currentStreak:r.streak.currentStreak}).totalXp;return v(r,e.completionRewards?.badgeKey),r.completedAt=o,r.completionRewardApplied=!0,r},H=(e,r,o,n,t,s,u,c)=>{if(n.status==="COMPLETED")return;let i=N(o);if(i>0)r.xpEarned+=c.calculate({activity:"journey_step",baseXp:i,currentStreak:r.streak.currentStreak}).totalXp;n.completedAt=t,n.eventPayload=s?.payload,n.manual=u,n.status="COMPLETED",n.triggeringEvent=s?.name??"journey.step.manual",n.xpEarned=i;let d=b(o,s,n,r.startedAt);if(!d)return;if(n.selectedBranchKey=d.key,d.reward?.xp)r.xpEarned+=d.reward.xp;v(r,d.reward?.badgeKey)},Y=(e,r={})=>y(e,{badges:[],completionRewardApplied:!1,eventLog:[],learnerId:r.learnerId,startedAt:r.now,steps:e.steps.map((o)=>({masteryCount:0,occurrences:0,status:"LOCKED",stepId:o.id,xpEarned:0})),streak:C.getInitialState(),trackId:e.id,xpEarned:0},{now:r.now}),G=(e,r,o,n={})=>{let t=n.streakEngine??C,s=n.xpEngine??L,u=o.occurredAt??new Date,c=y(e,K(r),{now:u});c.eventLog.push({...o,occurredAt:u,trackId:e.id}),c.lastActivityAt=u,c.startedAt??=u,c.streak=t.update(c.streak,u).state;for(let i of e.steps){let d=M(c,i.id);if(!d||d.status!=="AVAILABLE")continue;let a=E(i.completion,o,d,c.startedAt);if(a.occurrences!==void 0)d.occurrences=a.occurrences;if(a.masteryCount!==void 0)d.masteryCount=a.masteryCount;if(!a.matched)continue;H(e,c,i,d,u,o,!1,s)}return O(e,y(e,c,{now:u}),u,s)},U=(e,r,o,n={})=>{let t=n.streakEngine??C,s=n.xpEngine??L,u=n.now??new Date,c=y(e,K(r),{now:u}),i=e.steps.find((a)=>a.id===o),d=M(c,o);if(!i||!d||d.status!=="AVAILABLE")return c;return c.eventLog.push({name:"journey.step.manual",occurredAt:u,trackId:e.id}),c.lastActivityAt=u,c.startedAt??=u,c.streak=t.update(c.streak,u).state,H(e,c,i,d,u,{name:"journey.step.manual",occurredAt:u},!0,s),O(e,y(e,c,{now:u}),u,s)},_=(e,r,o={})=>x(e,r,o);export{y as synchronizeJourneyProgressState,b as resolveJourneyBranch,k as resolveJourneyAvailability,G as recordJourneyEvent,_ as projectJourneyProgress,A as matchesPayloadFilter,E as matchesJourneyCondition,h as matchesBaseJourneyEvent,P as isJourneyComplete,Y as createJourneyProgressState,U as completeJourneyStep,x as buildJourneyProgressSnapshot};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var d=(e,r)=>{if(!e)return!0;if(!r)return!1;return Object.entries(e).every(([u,n])=>r[u]===n)},o=(e,r)=>{if(e.eventName!==r.name)return!1;if(e.eventVersion!==void 0&&r.version!==void 0&&e.eventVersion!==r.version)return!1;if(e.sourceModule&&r.sourceModule&&e.sourceModule!==r.sourceModule)return!1;return d(e.payloadFilter,r.payload)},f=(e,r,u,n)=>{if(e.kind==="count"){if(!o(e,r))return{matched:!1};let t=u.occurrences+1;return{matched:(e.withinHours===void 0||n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000<=e.withinHours)&&t>=e.atLeast,occurrences:t}}if(e.kind==="time_window"){if(!o(e,r))return{matched:!1};if(e.availableAfterHours!==void 0&&n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000<e.availableAfterHours)return{matched:!1};if(e.withinHoursOfStart!==void 0&&n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000>e.withinHoursOfStart)return{matched:!1};return{matched:!0}}if(e.kind==="mastery"){if(r.name!==e.eventName)return{matched:!1};if(!d(e.payloadFilter,r.payload))return{matched:!1};let t=e.masteryField??"mastery",s=r.payload?.[t];if(typeof s!=="number")return{matched:!1};if(s<e.minimumMastery)return{matched:!1};let a=u.masteryCount+1;return{masteryCount:a,matched:a>=(e.requiredCount??1)}}return{matched:o(e,r)}},i=(e,r)=>{if(!e||!r)return{};let u=r.getTime(),n=u;if(e.unlockOnDay!==void 0)n=u+(e.unlockOnDay-1)*24*60*60*1000;if(e.unlockAfterHours!==void 0)n=u+e.unlockAfterHours*60*60*1000;let t=new Date(n),s=e.dueWithinHours!==void 0?new Date(t.getTime()+e.dueWithinHours*60*60*1000):void 0;return{availableAt:t,dueAt:s}},c=(e,r)=>{let u=r.steps.find((n)=>n.stepId===e.stepId);if(!u)return!1;if(e.kind==="step_completed")return u.status==="COMPLETED"||u.status==="SKIPPED";return u.selectedBranchKey===e.branchKey},m=(e,r,u,n)=>{if(!e.branches?.length)return;if(!r)return e.branches.find((t)=>t.when===void 0);for(let t of e.branches){if(!t.when)continue;if(f(t.when,r,u,n).matched)return t}return e.branches.find((t)=>t.when===void 0)};export{m as resolveJourneyBranch,i as resolveJourneyAvailability,d as matchesPayloadFilter,f as matchesJourneyCondition,o as matchesBaseJourneyEvent,c as isJourneyPrerequisiteSatisfied};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var V={timezone:"UTC",freezesPerMonth:2,maxFreezes:5,gracePeriodHours:4};class m{config;constructor(e={}){this.config={...V,...e}}update(e,r=new Date){let o=this.getDateString(r),n={state:{...e},streakMaintained:!1,streakLost:!1,freezeUsed:!1,newStreak:!1,daysMissed:0};if(!e.lastActivityDate)return n.state.currentStreak=1,n.state.longestStreak=Math.max(1,e.longestStreak),n.state.lastActivityAt=r,n.state.lastActivityDate=o,n.newStreak=!0,n.streakMaintained=!0,n;if(e.lastActivityDate===o)return n.state.lastActivityAt=r,n.streakMaintained=!0,n;let t=this.getDaysBetween(e.lastActivityDate,o);if(t===1)return n.state.currentStreak=e.currentStreak+1,n.state.longestStreak=Math.max(n.state.currentStreak,e.longestStreak),n.state.lastActivityAt=r,n.state.lastActivityDate=o,n.streakMaintained=!0,n;n.daysMissed=t-1;let s=n.daysMissed;if(s<=e.freezesRemaining)return n.state.freezesRemaining=e.freezesRemaining-s,n.state.freezeUsedAt=r,n.state.currentStreak=e.currentStreak+1,n.state.longestStreak=Math.max(n.state.currentStreak,e.longestStreak),n.state.lastActivityAt=r,n.state.lastActivityDate=o,n.freezeUsed=!0,n.streakMaintained=!0,n;return n.streakLost=!0,n.state.currentStreak=1,n.state.lastActivityAt=r,n.state.lastActivityDate=o,n.newStreak=!0,n}checkStatus(e,r=new Date){if(!e.lastActivityDate)return{isActive:!1,willExpireAt:null,canUseFreeze:!1,daysUntilExpiry:0};let o=this.getDateString(r),n=this.getDaysBetween(e.lastActivityDate,o);if(n===0){let s=this.addDays(r,1);return s.setHours(23,59,59,999),{isActive:!0,willExpireAt:s,canUseFreeze:e.freezesRemaining>0,daysUntilExpiry:1}}if(n===1){let s=new Date(r);return s.setHours(23+this.config.gracePeriodHours,59,59,999),{isActive:!0,willExpireAt:s,canUseFreeze:e.freezesRemaining>0,daysUntilExpiry:0}}let t=n-1;return{isActive:t<=e.freezesRemaining,willExpireAt:null,canUseFreeze:t<=e.freezesRemaining,daysUntilExpiry:-t}}useFreeze(e,r=new Date){if(e.freezesRemaining<=0)return null;return{...e,freezesRemaining:e.freezesRemaining-1,freezeUsedAt:r}}awardMonthlyFreezes(e){return{...e,freezesRemaining:Math.min(e.freezesRemaining+this.config.freezesPerMonth,this.config.maxFreezes)}}getInitialState(){return{currentStreak:0,longestStreak:0,lastActivityAt:null,lastActivityDate:null,freezesRemaining:this.config.freezesPerMonth,freezeUsedAt:null}}getMilestones(e){let r=[3,7,14,30,60,90,180,365,500,1000],o=r.filter((t)=>e>=t),n=r.find((t)=>e<t)??null;return{achieved:o,next:n}}getDateString(e){let r=e.getFullYear(),o=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return`${r}-${o}-${n}`}getDaysBetween(e,r){let o=new Date(e),t=new Date(r).getTime()-o.getTime();return Math.floor(t/86400000)}addDays(e,r){return new Date(e.getTime()+r*24*60*60*1000)}}var G=new m;import{defineTranslation as j}from"@contractspec/lib.contracts-spec/translations";var I=j({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels for the learning-journey module",owners:["platform"],stability:"experimental"},locale:"en",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Score Bonus",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Perfect Score",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"First Attempt",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Retry Penalty",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Streak Bonus",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as q}from"@contractspec/lib.contracts-spec/translations";var b=q({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels (Spanish)",owners:["platform"],stability:"experimental"},locale:"es",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Bonificación por puntuación",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Puntuación perfecta",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"Primer intento",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Penalización por reintento",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonificación por racha",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as W}from"@contractspec/lib.contracts-spec/translations";var x=W({meta:{key:"learning-journey.messages",version:"1.0.0",domain:"learning-journey",description:"XP source labels (French)",owners:["platform"],stability:"experimental"},locale:"fr",fallback:"en",messages:{"xp.source.base":{value:"Base",description:"XP breakdown label for base XP"},"xp.source.scoreBonus":{value:"Bonus de score",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Score parfait",description:"XP breakdown label for perfect score bonus"},"xp.source.firstAttempt":{value:"Premier essai",description:"XP breakdown label for first attempt bonus"},"xp.source.retryPenalty":{value:"Pénalité de réessai",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonus de série",description:"XP breakdown label for streak bonus"}}});import{createI18nFactory as z}from"@contractspec/lib.contracts-spec/translations";var h=z({specKey:"learning-journey.messages",catalogs:[I,x,b]}),C=h.create,ce=h.getDefault,de=h.resetRegistry;var g={baseValues:{lesson_complete:10,quiz_pass:20,quiz_perfect:50,flashcard_review:1,course_complete:200,module_complete:50,streak_bonus:5,achievement_unlock:0,daily_goal_complete:15,first_lesson:25,journey_step:5,journey_complete:50},scoreThresholds:[{min:90,multiplier:1.5},{min:80,multiplier:1.25},{min:70,multiplier:1},{min:60,multiplier:0.75},{min:0,multiplier:0.5}],streakTiers:[{days:365,bonus:50},{days:180,bonus:30},{days:90,bonus:20},{days:30,bonus:15},{days:14,bonus:10},{days:7,bonus:5},{days:3,bonus:2},{days:1,bonus:0}],perfectScoreMultiplier:1.5,firstAttemptBonus:10,retryPenalty:0.5,speedBonusMultiplier:1.2,speedBonusThreshold:0.8};class J{config;constructor(e={}){this.config={...g,...e,baseValues:{...g.baseValues,...e.baseValues},scoreThresholds:e.scoreThresholds||g.scoreThresholds,streakTiers:e.streakTiers||g.streakTiers}}calculate(e){let r=[],o=e.baseXp??this.config.baseValues[e.activity],n=o;if(r.push({source:"base",amount:o}),e.score!==void 0){let t=this.getScoreMultiplier(e.score);if(t!==1){let s=Math.round(o*(t-1));n+=s,r.push({source:"score_bonus",amount:s,multiplier:t})}if(e.score===100){let s=Math.round(o*(this.config.perfectScoreMultiplier-1));n+=s,r.push({source:"perfect_score",amount:s,multiplier:this.config.perfectScoreMultiplier})}}if(e.attemptNumber===1&&!e.isRetry)n+=this.config.firstAttemptBonus,r.push({source:"first_attempt",amount:this.config.firstAttemptBonus});if(e.isRetry){let t=Math.round(n*(1-this.config.retryPenalty));n-=t,r.push({source:"retry_penalty",amount:-t,multiplier:this.config.retryPenalty})}if(e.currentStreak&&e.currentStreak>0){let t=this.getStreakBonus(e.currentStreak);if(t>0)n+=t,r.push({source:"streak_bonus",amount:t})}if(o>0)n=Math.max(1,n);return{totalXp:Math.round(n),baseXp:o,breakdown:r}}calculateStreakBonus(e){let r=this.getStreakBonus(e);return{totalXp:r,baseXp:r,breakdown:[{source:"streak_bonus",amount:r}]}}getXpForLevel(e){if(e<=1)return 0;return Math.round(100*Math.pow(e-1,1.5))}getLevelFromXp(e){let r=1,o=this.getXpForLevel(r+1);while(e>=o&&r<1000)r++,o=this.getXpForLevel(r+1);let n=this.getXpForLevel(r),t=this.getXpForLevel(r+1);return{level:r,xpInLevel:e-n,xpForNextLevel:t-n}}getScoreMultiplier(e){for(let r of this.config.scoreThresholds)if(e>=r.min)return r.multiplier;return 1}getStreakBonus(e){for(let r of this.config.streakTiers)if(e>=r.days)return r.bonus;return 0}}var Q={base:"xp.source.base",score_bonus:"xp.source.scoreBonus",perfect_score:"xp.source.perfectScore",first_attempt:"xp.source.firstAttempt",retry_penalty:"xp.source.retryPenalty",streak_bonus:"xp.source.streakBonus"};function ae(e,r){let o=C(r),n=Q[e];return n?o.t(n):e}var ye=new J;var T=(e,r)=>{if(!e)return!0;if(!r)return!1;return Object.entries(e).every(([o,n])=>r[o]===n)},E=(e,r)=>{if(e.eventName!==r.name)return!1;if(e.eventVersion!==void 0&&r.version!==void 0&&e.eventVersion!==r.version)return!1;if(e.sourceModule&&r.sourceModule&&e.sourceModule!==r.sourceModule)return!1;return T(e.payloadFilter,r.payload)},P=(e,r,o,n)=>{if(e.kind==="count"){if(!E(e,r))return{matched:!1};let t=o.occurrences+1;return{matched:(e.withinHours===void 0||n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000<=e.withinHours)&&t>=e.atLeast,occurrences:t}}if(e.kind==="time_window"){if(!E(e,r))return{matched:!1};if(e.availableAfterHours!==void 0&&n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000<e.availableAfterHours)return{matched:!1};if(e.withinHoursOfStart!==void 0&&n!==void 0&&r.occurredAt!==void 0&&(r.occurredAt.getTime()-n.getTime())/3600000>e.withinHoursOfStart)return{matched:!1};return{matched:!0}}if(e.kind==="mastery"){if(r.name!==e.eventName)return{matched:!1};if(!T(e.payloadFilter,r.payload))return{matched:!1};let t=e.masteryField??"mastery",s=r.payload?.[t];if(typeof s!=="number")return{matched:!1};if(s<e.minimumMastery)return{matched:!1};let u=o.masteryCount+1;return{masteryCount:u,matched:u>=(e.requiredCount??1)}}return{matched:E(e,r)}},B=(e,r)=>{if(!e||!r)return{};let o=r.getTime(),n=o;if(e.unlockOnDay!==void 0)n=o+(e.unlockOnDay-1)*24*60*60*1000;if(e.unlockAfterHours!==void 0)n=o+e.unlockAfterHours*60*60*1000;let t=new Date(n),s=e.dueWithinHours!==void 0?new Date(t.getTime()+e.dueWithinHours*60*60*1000):void 0;return{availableAt:t,dueAt:s}},l=(e,r)=>{let o=r.steps.find((n)=>n.stepId===e.stepId);if(!o)return!1;if(e.kind==="step_completed")return o.status==="COMPLETED"||o.status==="SKIPPED";return o.selectedBranchKey===e.branchKey},w=(e,r,o,n)=>{if(!e.branches?.length)return;if(!r)return e.branches.find((t)=>t.when===void 0);for(let t of e.branches){if(!t.when)continue;if(P(t.when,r,o,n).matched)return t}return e.branches.find((t)=>t.when===void 0)};var Z=(e)=>e.map((r)=>({...r})),$=(e,r)=>{let o=new Map;for(let n of e.steps){let t=r.steps.find((u)=>u.stepId===n.id),s=n.branches?.find((u)=>u.key===t?.selectedBranchKey);if(!s?.blockStepIds?.length)continue;for(let u of s.blockStepIds)o.set(u,{blockedByBranchKey:s.key,blockedByStepId:n.id})}return o},y=(e,r,o={})=>{let n=o.now??new Date,t={...r,badges:[...r.badges],eventLog:[...r.eventLog],steps:Z(r.steps),streak:{...r.streak}},s=$(e,t);for(let u of e.steps){let c=t.steps.find((S)=>S.stepId===u.id);if(!c)continue;let{availableAt:i,dueAt:d}=B(u.availability,t.startedAt);if(c.availableAt=i,c.dueAt=d,c.status==="COMPLETED"||c.status==="SKIPPED")continue;let a=s.get(u.id);if(a){c.blockedAt??=n,c.blockedByBranchKey=a.blockedByBranchKey,c.blockedByStepId=a.blockedByStepId,c.status="BLOCKED";continue}let f=u.prerequisites??[],p=u.prerequisiteMode==="any"?f.some((S)=>l(S,t)):f.every((S)=>l(S,t)),R=(f.length===0?!0:p)&&(!i||n.getTime()>=i.getTime());if(d&&n.getTime()>d.getTime()){c.missedAt??=n,c.status="MISSED";continue}c.status=R?"AVAILABLE":"LOCKED"}return t},A=(e)=>{let r=e.steps.filter((o)=>o.status!=="BLOCKED");return r.length>0&&r.every((o)=>o.status==="COMPLETED"||o.status==="SKIPPED")},D=(e,r,o={})=>{let n=y(e,r,o),t=n.steps.filter((p)=>p.status!=="BLOCKED"),s=n.steps.filter((p)=>p.status==="COMPLETED"),u=n.steps.filter((p)=>p.status==="AVAILABLE").map((p)=>p.stepId),c=n.steps.filter((p)=>p.status==="BLOCKED").map((p)=>p.stepId),i=n.steps.filter((p)=>p.status==="MISSED").map((p)=>p.stepId),d=t.filter((p)=>p.status==="COMPLETED"||p.status==="SKIPPED").length,a=t.length>0?Math.round(d/t.length*100):0,f=u[0]??null;return{activeStepCount:t.length,availableStepIds:u,badges:[...n.badges],blockedStepIds:c,completedAt:n.completedAt,completedStepCount:d,completedStepIds:s.map((p)=>p.stepId),currentStepId:f,isCompleted:A(n),lastActivityAt:n.lastActivityAt,learnerId:n.learnerId,missedStepIds:i,nextStepId:f,progressPercent:a,startedAt:n.startedAt,steps:n.steps,streakDays:n.streak.currentStreak,totalSteps:e.steps.length,trackId:n.trackId,xpEarned:n.xpEarned}};var k=new m,L=new J,K=(e)=>({...e,badges:[...e.badges],eventLog:[...e.eventLog],steps:e.steps.map((r)=>({...r})),streak:{...e.streak}}),N=(e)=>e.reward?.xp??e.xpReward??0,M=(e,r)=>e.steps.find((o)=>o.stepId===r),O=(e,r)=>{if(r&&!e.badges.includes(r))e.badges.push(r)},H=(e,r,o,n)=>{if(!A(r)||r.completionRewardApplied)return r;let t=e.completionRewards?.xp??0;if(t>0)r.xpEarned+=n.calculate({activity:"journey_complete",baseXp:t,currentStreak:r.streak.currentStreak}).totalXp;return O(r,e.completionRewards?.badgeKey),r.completedAt=o,r.completionRewardApplied=!0,r},v=(e,r,o,n,t,s,u,c)=>{if(n.status==="COMPLETED")return;let i=N(o);if(i>0)r.xpEarned+=c.calculate({activity:"journey_step",baseXp:i,currentStreak:r.streak.currentStreak}).totalXp;n.completedAt=t,n.eventPayload=s?.payload,n.manual=u,n.status="COMPLETED",n.triggeringEvent=s?.name??"journey.step.manual",n.xpEarned=i;let d=w(o,s,n,r.startedAt);if(!d)return;if(n.selectedBranchKey=d.key,d.reward?.xp)r.xpEarned+=d.reward.xp;O(r,d.reward?.badgeKey)},le=(e,r={})=>y(e,{badges:[],completionRewardApplied:!1,eventLog:[],learnerId:r.learnerId,startedAt:r.now,steps:e.steps.map((o)=>({masteryCount:0,occurrences:0,status:"LOCKED",stepId:o.id,xpEarned:0})),streak:k.getInitialState(),trackId:e.id,xpEarned:0},{now:r.now}),Ae=(e,r,o,n={})=>{let t=n.streakEngine??k,s=n.xpEngine??L,u=o.occurredAt??new Date,c=y(e,K(r),{now:u});c.eventLog.push({...o,occurredAt:u,trackId:e.id}),c.lastActivityAt=u,c.startedAt??=u,c.streak=t.update(c.streak,u).state;for(let i of e.steps){let d=M(c,i.id);if(!d||d.status!=="AVAILABLE")continue;let a=P(i.completion,o,d,c.startedAt);if(a.occurrences!==void 0)d.occurrences=a.occurrences;if(a.masteryCount!==void 0)d.masteryCount=a.masteryCount;if(!a.matched)continue;v(e,c,i,d,u,o,!1,s)}return H(e,y(e,c,{now:u}),u,s)},ke=(e,r,o,n={})=>{let t=n.streakEngine??k,s=n.xpEngine??L,u=n.now??new Date,c=y(e,K(r),{now:u}),i=e.steps.find((a)=>a.id===o),d=M(c,o);if(!i||!d||d.status!=="AVAILABLE")return c;return c.eventLog.push({name:"journey.step.manual",occurredAt:u,trackId:e.id}),c.lastActivityAt=u,c.startedAt??=u,c.streak=t.update(c.streak,u).state,v(e,c,i,d,u,{name:"journey.step.manual",occurredAt:u},!0,s),H(e,y(e,c,{now:u}),u,s)},Ie=(e,r,o={})=>D(e,r,o);export{Ae as recordJourneyEvent,Ie as projectJourneyProgress,le as createJourneyProgressState,ke as completeJourneyStep};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var S=(e,t)=>{if(!e)return!0;if(!t)return!1;return Object.entries(e).every(([n,r])=>t[n]===r)},y=(e,t)=>{if(e.eventName!==t.name)return!1;if(e.eventVersion!==void 0&&t.version!==void 0&&e.eventVersion!==t.version)return!1;if(e.sourceModule&&t.sourceModule&&e.sourceModule!==t.sourceModule)return!1;return S(e.payloadFilter,t.payload)},J=(e,t,n,r)=>{if(e.kind==="count"){if(!y(e,t))return{matched:!1};let s=n.occurrences+1;return{matched:(e.withinHours===void 0||r!==void 0&&t.occurredAt!==void 0&&(t.occurredAt.getTime()-r.getTime())/3600000<=e.withinHours)&&s>=e.atLeast,occurrences:s}}if(e.kind==="time_window"){if(!y(e,t))return{matched:!1};if(e.availableAfterHours!==void 0&&r!==void 0&&t.occurredAt!==void 0&&(t.occurredAt.getTime()-r.getTime())/3600000<e.availableAfterHours)return{matched:!1};if(e.withinHoursOfStart!==void 0&&r!==void 0&&t.occurredAt!==void 0&&(t.occurredAt.getTime()-r.getTime())/3600000>e.withinHoursOfStart)return{matched:!1};return{matched:!0}}if(e.kind==="mastery"){if(t.name!==e.eventName)return{matched:!1};if(!S(e.payloadFilter,t.payload))return{matched:!1};let s=e.masteryField??"mastery",a=t.payload?.[s];if(typeof a!=="number")return{matched:!1};if(a<e.minimumMastery)return{matched:!1};let u=n.masteryCount+1;return{masteryCount:u,matched:u>=(e.requiredCount??1)}}return{matched:y(e,t)}},h=(e,t)=>{if(!e||!t)return{};let n=t.getTime(),r=n;if(e.unlockOnDay!==void 0)r=n+(e.unlockOnDay-1)*24*60*60*1000;if(e.unlockAfterHours!==void 0)r=n+e.unlockAfterHours*60*60*1000;let s=new Date(r),a=e.dueWithinHours!==void 0?new Date(s.getTime()+e.dueWithinHours*60*60*1000):void 0;return{availableAt:s,dueAt:a}},m=(e,t)=>{let n=t.steps.find((r)=>r.stepId===e.stepId);if(!n)return!1;if(e.kind==="step_completed")return n.status==="COMPLETED"||n.status==="SKIPPED";return n.selectedBranchKey===e.branchKey},E=(e,t,n,r)=>{if(!e.branches?.length)return;if(!t)return e.branches.find((s)=>s.when===void 0);for(let s of e.branches){if(!s.when)continue;if(J(s.when,t,n,r).matched)return s}return e.branches.find((s)=>s.when===void 0)};var b=(e)=>e.map((t)=>({...t})),k=(e,t)=>{let n=new Map;for(let r of e.steps){let s=t.steps.find((u)=>u.stepId===r.id),a=r.branches?.find((u)=>u.key===s?.selectedBranchKey);if(!a?.blockStepIds?.length)continue;for(let u of a.blockStepIds)n.set(u,{blockedByBranchKey:a.key,blockedByStepId:r.id})}return n},P=(e,t,n={})=>{let r=n.now??new Date,s={...t,badges:[...t.badges],eventLog:[...t.eventLog],steps:b(t.steps),streak:{...t.streak}},a=k(e,s);for(let u of e.steps){let c=s.steps.find((f)=>f.stepId===u.id);if(!c)continue;let{availableAt:p,dueAt:d}=h(u.availability,s.startedAt);if(c.availableAt=p,c.dueAt=d,c.status==="COMPLETED"||c.status==="SKIPPED")continue;let l=a.get(u.id);if(l){c.blockedAt??=r,c.blockedByBranchKey=l.blockedByBranchKey,c.blockedByStepId=l.blockedByStepId,c.status="BLOCKED";continue}let i=u.prerequisites??[],o=u.prerequisiteMode==="any"?i.some((f)=>m(f,s)):i.every((f)=>m(f,s)),g=(i.length===0?!0:o)&&(!p||r.getTime()>=p.getTime());if(d&&r.getTime()>d.getTime()){c.missedAt??=r,c.status="MISSED";continue}c.status=g?"AVAILABLE":"LOCKED"}return s},A=(e)=>{let t=e.steps.filter((n)=>n.status!=="BLOCKED");return t.length>0&&t.every((n)=>n.status==="COMPLETED"||n.status==="SKIPPED")},w=(e,t,n={})=>{let r=P(e,t,n),s=r.steps.filter((o)=>o.status!=="BLOCKED"),a=r.steps.filter((o)=>o.status==="COMPLETED"),u=r.steps.filter((o)=>o.status==="AVAILABLE").map((o)=>o.stepId),c=r.steps.filter((o)=>o.status==="BLOCKED").map((o)=>o.stepId),p=r.steps.filter((o)=>o.status==="MISSED").map((o)=>o.stepId),d=s.filter((o)=>o.status==="COMPLETED"||o.status==="SKIPPED").length,l=s.length>0?Math.round(d/s.length*100):0,i=u[0]??null;return{activeStepCount:s.length,availableStepIds:u,badges:[...r.badges],blockedStepIds:c,completedAt:r.completedAt,completedStepCount:d,completedStepIds:a.map((o)=>o.stepId),currentStepId:i,isCompleted:A(r),lastActivityAt:r.lastActivityAt,learnerId:r.learnerId,missedStepIds:p,nextStepId:i,progressPercent:l,startedAt:r.startedAt,steps:r.steps,streakDays:r.streak.currentStreak,totalSteps:e.steps.length,trackId:r.trackId,xpEarned:r.xpEarned}};export{P as synchronizeJourneyProgressState,A as isJourneyComplete,w as buildJourneyProgressSnapshot};
|
package/dist/contracts/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{defineSchemaModel as s,ScalarTypeEnum as e}from"@contractspec/lib.schema";var z=s({name:"Course",description:"A learning course",fields:{id:{type:e.String_unsecure(),isOptional:!1},title:{type:e.String_unsecure(),isOptional:!1},slug:{type:e.String_unsecure(),isOptional:!1},description:{type:e.String_unsecure(),isOptional:!0},difficulty:{type:e.String_unsecure(),isOptional:!1},status:{type:e.String_unsecure(),isOptional:!1},estimatedDuration:{type:e.Int_unsecure(),isOptional:!0},thumbnailUrl:{type:e.String_unsecure(),isOptional:!0},createdAt:{type:e.DateTime(),isOptional:!1}}}),x=s({name:"Learner",description:"A learner profile",fields:{id:{type:e.String_unsecure(),isOptional:!1},userId:{type:e.String_unsecure(),isOptional:!1},displayName:{type:e.String_unsecure(),isOptional:!0},level:{type:e.Int_unsecure(),isOptional:!1},totalXp:{type:e.Int_unsecure(),isOptional:!1},currentStreak:{type:e.Int_unsecure(),isOptional:!1},longestStreak:{type:e.Int_unsecure(),isOptional:!1},createdAt:{type:e.DateTime(),isOptional:!1}}}),o=s({name:"Enrollment",description:"A course enrollment",fields:{id:{type:e.String_unsecure(),isOptional:!1},learnerId:{type:e.String_unsecure(),isOptional:!1},courseId:{type:e.String_unsecure(),isOptional:!1},status:{type:e.String_unsecure(),isOptional:!1},progress:{type:e.Int_unsecure(),isOptional:!1},startedAt:{type:e.DateTime(),isOptional:!0},completedAt:{type:e.DateTime(),isOptional:!0}}}),F=s({name:"LessonProgress",description:"Lesson progress",fields:{id:{type:e.String_unsecure(),isOptional:!1},learnerId:{type:e.String_unsecure(),isOptional:!1},lessonId:{type:e.String_unsecure(),isOptional:!1},status:{type:e.String_unsecure(),isOptional:!1},progress:{type:e.Int_unsecure(),isOptional:!1},score:{type:e.Int_unsecure(),isOptional:!0},timeSpent:{type:e.Int_unsecure(),isOptional:!1},completedAt:{type:e.DateTime(),isOptional:!0}}}),H=s({name:"Deck",description:"A flashcard deck",fields:{id:{type:e.String_unsecure(),isOptional:!1},title:{type:e.String_unsecure(),isOptional:!1},description:{type:e.String_unsecure(),isOptional:!0},cardCount:{type:e.Int_unsecure(),isOptional:!1},isPublic:{type:e.Boolean(),isOptional:!1},createdAt:{type:e.DateTime(),isOptional:!1}}}),y=s({name:"Card",description:"A flashcard",fields:{id:{type:e.String_unsecure(),isOptional:!1},deckId:{type:e.String_unsecure(),isOptional:!1},front:{type:e.String_unsecure(),isOptional:!1},back:{type:e.String_unsecure(),isOptional:!1},hints:{type:e.JSON(),isOptional:!0},isDue:{type:e.Boolean(),isOptional:!1},nextReviewAt:{type:e.DateTime(),isOptional:!0}}}),D=s({name:"Achievement",description:"An achievement",fields:{id:{type:e.String_unsecure(),isOptional:!1},key:{type:e.String_unsecure(),isOptional:!1},name:{type:e.String_unsecure(),isOptional:!1},description:{type:e.String_unsecure(),isOptional:!1},icon:{type:e.String_unsecure(),isOptional:!0},xpReward:{type:e.Int_unsecure(),isOptional:!1},unlockedAt:{type:e.DateTime(),isOptional:!0}}}),l=s({name:"EnrollInCourseInput",description:"Input for enrolling in a course",fields:{courseId:{type:e.String_unsecure(),isOptional:!1}}}),u=s({name:"CompleteLessonInput",description:"Input for completing a lesson",fields:{lessonId:{type:e.String_unsecure(),isOptional:!1},score:{type:e.Int_unsecure(),isOptional:!0},timeSpent:{type:e.Int_unsecure(),isOptional:!1}}}),a=s({name:"SubmitCardReviewInput",description:"Input for submitting a card review",fields:{cardId:{type:e.String_unsecure(),isOptional:!1},rating:{type:e.String_unsecure(),isOptional:!1},responseTimeMs:{type:e.Int_unsecure(),isOptional:!0}}}),f=s({name:"GetDueCardsInput",description:"Input for getting due cards",fields:{deckId:{type:e.String_unsecure(),isOptional:!0},limit:{type:e.Int_unsecure(),isOptional:!0}}}),O=s({name:"GetDueCardsOutput",description:"Output for getting due cards",fields:{cards:{type:y,isArray:!0,isOptional:!1},total:{type:e.Int_unsecure(),isOptional:!1}}}),d=s({name:"GetLearnerDashboardInput",description:"Input for getting learner dashboard",fields:{learnerId:{type:e.String_unsecure(),isOptional:!0}}}),c=s({name:"LearnerDashboard",description:"Learner dashboard data",fields:{learner:{type:x,isOptional:!1},currentStreak:{type:e.Int_unsecure(),isOptional:!1},dailyXpGoal:{type:e.Int_unsecure(),isOptional:!1},dailyXpProgress:{type:e.Int_unsecure(),isOptional:!1},activeEnrollments:{type:o,isArray:!0,isOptional:!1},recentAchievements:{type:D,isArray:!0,isOptional:!1},dueCardCount:{type:e.Int_unsecure(),isOptional:!1}}}),r=s({name:"SuccessOutput",description:"Generic success output",fields:{success:{type:e.Boolean(),isOptional:!1},xpEarned:{type:e.Int_unsecure(),isOptional:!0}}});var i=["modules.learning-journey"];import{defineCommand as A,defineQuery as _}from"@contractspec/lib.contracts-spec";import{defineSchemaModel as n,ScalarTypeEnum as t}from"@contractspec/lib.schema";var k=n({name:"OnboardingStepCondition",description:"Structured completion condition for onboarding steps.",fields:{eventName:{type:t.String_unsecure(),isOptional:!1},eventVersion:{type:t.String_unsecure(),isOptional:!0},sourceModule:{type:t.String_unsecure(),isOptional:!0},payloadFilter:{type:t.JSON(),isOptional:!0}}}),v=n({name:"OnboardingStep",description:"Declarative onboarding step definition.",fields:{id:{type:t.String_unsecure(),isOptional:!1},trackId:{type:t.String_unsecure(),isOptional:!1},title:{type:t.String_unsecure(),isOptional:!1},description:{type:t.String_unsecure(),isOptional:!0},instructions:{type:t.String_unsecure(),isOptional:!0},helpUrl:{type:t.String_unsecure(),isOptional:!0},order:{type:t.Int_unsecure(),isOptional:!1},completionEvent:{type:t.String_unsecure(),isOptional:!1},completionCondition:{type:k,isOptional:!0},xpReward:{type:t.Int_unsecure(),isOptional:!0},isRequired:{type:t.Boolean(),isOptional:!0},canSkip:{type:t.Boolean(),isOptional:!0},actionUrl:{type:t.String_unsecure(),isOptional:!0},actionLabel:{type:t.String_unsecure(),isOptional:!0},metadata:{type:t.JSON(),isOptional:!0}}}),C=n({name:"OnboardingTrack",description:"Onboarding track metadata and steps.",fields:{id:{type:t.String_unsecure(),isOptional:!1},productId:{type:t.String_unsecure(),isOptional:!0},name:{type:t.String_unsecure(),isOptional:!1},description:{type:t.String_unsecure(),isOptional:!0},targetUserSegment:{type:t.String_unsecure(),isOptional:!0},targetRole:{type:t.String_unsecure(),isOptional:!0},isActive:{type:t.Boolean(),isOptional:!0},isRequired:{type:t.Boolean(),isOptional:!0},canSkip:{type:t.Boolean(),isOptional:!0},totalXp:{type:t.Int_unsecure(),isOptional:!0},completionXpBonus:{type:t.Int_unsecure(),isOptional:!0},completionBadgeKey:{type:t.String_unsecure(),isOptional:!0},streakHoursWindow:{type:t.Int_unsecure(),isOptional:!0},streakBonusXp:{type:t.Int_unsecure(),isOptional:!0},metadata:{type:t.JSON(),isOptional:!0},steps:{type:v,isArray:!0,isOptional:!1}}}),w=n({name:"OnboardingStepProgress",description:"Progress for a specific onboarding step.",fields:{stepId:{type:t.String_unsecure(),isOptional:!1},status:{type:t.String_unsecure(),isOptional:!1},xpEarned:{type:t.Int_unsecure(),isOptional:!0},triggeringEvent:{type:t.String_unsecure(),isOptional:!0},eventPayload:{type:t.JSON(),isOptional:!0},completedAt:{type:t.DateTime(),isOptional:!0}}}),g=n({name:"OnboardingProgress",description:"Aggregated progress for an onboarding track.",fields:{learnerId:{type:t.String_unsecure(),isOptional:!0},trackId:{type:t.String_unsecure(),isOptional:!1},progress:{type:t.Int_unsecure(),isOptional:!1},isCompleted:{type:t.Boolean(),isOptional:!1},xpEarned:{type:t.Int_unsecure(),isOptional:!0},startedAt:{type:t.DateTime(),isOptional:!0},completedAt:{type:t.DateTime(),isOptional:!0},lastActivityAt:{type:t.DateTime(),isOptional:!0},steps:{type:w,isArray:!0,isOptional:!0}}}),B=n({name:"ListOnboardingTracksInput",description:"Filters for listing onboarding tracks.",fields:{learnerId:{type:t.String_unsecure(),isOptional:!0},productId:{type:t.String_unsecure(),isOptional:!0},trackIds:{type:t.String_unsecure(),isArray:!0,isOptional:!0},includeProgress:{type:t.Boolean(),isOptional:!0}}}),X=n({name:"ListOnboardingTracksOutput",description:"Available onboarding tracks with optional progress.",fields:{tracks:{type:C,isArray:!0,isOptional:!1},progress:{type:g,isArray:!0,isOptional:!0}}}),h=n({name:"GetOnboardingProgressInput",description:"Input for fetching onboarding progress for a track.",fields:{trackId:{type:t.String_unsecure(),isOptional:!1},learnerId:{type:t.String_unsecure(),isOptional:!0}}}),j=n({name:"RecordOnboardingEventInput",description:"Record a domain event to advance onboarding progress via completion conditions.",fields:{learnerId:{type:t.String_unsecure(),isOptional:!1},trackId:{type:t.String_unsecure(),isOptional:!0},eventName:{type:t.String_unsecure(),isOptional:!1},eventVersion:{type:t.String_unsecure(),isOptional:!0},eventPayload:{type:t.JSON(),isOptional:!0},occurredAt:{type:t.DateTime(),isOptional:!0}}}),G=_({meta:{key:"learning.onboarding.listTracks",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","onboarding","journey"],description:"List onboarding tracks available to a learner or product.",goal:"Expose track catalog for UI/API surfaces.",context:"Called when showing onboarding/learning journey catalog."},io:{input:B,output:X},policy:{auth:"user"}}),b=_({meta:{key:"learning.onboarding.getProgress",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","onboarding","journey"],description:"Fetch onboarding progress for a specific track.",goal:"Display learner progress and remaining steps.",context:"Called when rendering a track detail or widget."},io:{input:h,output:g},policy:{auth:"user"}}),Q=A({meta:{key:"learning.onboarding.recordEvent",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","onboarding","events"],description:"Record a domain event to evaluate onboarding step completion conditions.",goal:"Advance onboarding automatically from product events.",context:"Called by event bus handlers when relevant product events fire (e.g., deal.created)."},io:{input:j,output:r,errors:{TRACK_NOT_FOUND:{description:"Track not found for event routing",http:404,gqlCode:"TRACK_NOT_FOUND",when:"Track ID or routing context is invalid"},STEP_NOT_FOUND:{description:"Step not found for completion condition",http:404,gqlCode:"STEP_NOT_FOUND",when:"No step matches the incoming event"}}},policy:{auth:"user"}});import{defineCommand as p,defineQuery as I}from"@contractspec/lib.contracts-spec";var Y=p({meta:{key:"learning.enroll",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","enrollment"],description:"Enroll in a course.",goal:"Start learning a new course.",context:"Called when a learner wants to start a course."},io:{input:l,output:o,errors:{COURSE_NOT_FOUND:{description:"Course does not exist",http:404,gqlCode:"COURSE_NOT_FOUND",when:"Course ID is invalid"},ALREADY_ENROLLED:{description:"Already enrolled in course",http:409,gqlCode:"ALREADY_ENROLLED",when:"Learner is already enrolled"}}},policy:{auth:"user"}}),R=p({meta:{key:"learning.completeLesson",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","progress"],description:"Mark a lesson as completed.",goal:"Record lesson completion and earn XP.",context:"Called when a learner finishes a lesson."},io:{input:u,output:r,errors:{LESSON_NOT_FOUND:{description:"Lesson does not exist",http:404,gqlCode:"LESSON_NOT_FOUND",when:"Lesson ID is invalid"},NOT_ENROLLED:{description:"Not enrolled in course",http:403,gqlCode:"NOT_ENROLLED",when:"Learner is not enrolled in the course"}}},policy:{auth:"user"}}),m=p({meta:{key:"learning.submitCardReview",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","flashcards"],description:"Submit a flashcard review.",goal:"Record review and update SRS schedule.",context:"Called when reviewing flashcards."},io:{input:a,output:r,errors:{CARD_NOT_FOUND:{description:"Card does not exist",http:404,gqlCode:"CARD_NOT_FOUND",when:"Card ID is invalid"},INVALID_RATING:{description:"Invalid rating",http:400,gqlCode:"INVALID_RATING",when:"Rating must be AGAIN, HARD, GOOD, or EASY"}}},policy:{auth:"user"}}),N=I({meta:{key:"learning.getDueCards",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","flashcards"],description:"Get flashcards due for review.",goal:"Get the next batch of cards to review.",context:"Called when starting a review session."},io:{input:f,output:O},policy:{auth:"user"}}),S=I({meta:{key:"learning.getDashboard",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","dashboard"],description:"Get learner dashboard data.",goal:"Display learner progress and stats.",context:"Called when viewing the learning dashboard."},io:{input:d,output:c},policy:{auth:"user"}});export{r as SuccessOutput,a as SubmitCardReviewInput,m as SubmitCardReviewContract,Q as RecordOnboardingEventContract,F as ProgressModel,C as OnboardingTrackModel,w as OnboardingStepProgressModel,v as OnboardingStepModel,g as OnboardingProgressModel,G as ListOnboardingTracksContract,x as LearnerModel,c as LearnerDashboardModel,i as LEARNING_JOURNEY_OWNERS,b as GetOnboardingProgressContract,d as GetLearnerDashboardInput,S as GetLearnerDashboardContract,O as GetDueCardsOutput,f as GetDueCardsInput,N as GetDueCardsContract,o as EnrollmentModel,l as EnrollInCourseInput,Y as EnrollInCourseContract,H as DeckModel,z as CourseModel,u as CompleteLessonInput,R as CompleteLessonContract,y as CardModel,D as AchievementModel};
|
|
2
|
+
var i=["modules.learning-journey"];import{defineCommand as D,defineQuery as u}from"@contractspec/lib.contracts-spec";import{defineSchemaModel as n,ScalarTypeEnum as t}from"@contractspec/lib.schema";var A=n({name:"JourneyCondition",description:"Adaptive completion condition for a journey step.",fields:{kind:{type:t.String_unsecure(),isOptional:!0},eventName:{type:t.String_unsecure(),isOptional:!1},eventVersion:{type:t.Int_unsecure(),isOptional:!0},sourceModule:{type:t.String_unsecure(),isOptional:!0},payloadFilter:{type:t.JSON(),isOptional:!0},atLeast:{type:t.Int_unsecure(),isOptional:!0},withinHours:{type:t.Int_unsecure(),isOptional:!0},withinHoursOfStart:{type:t.Int_unsecure(),isOptional:!0},availableAfterHours:{type:t.Int_unsecure(),isOptional:!0},skillIdField:{type:t.String_unsecure(),isOptional:!0},masteryField:{type:t.String_unsecure(),isOptional:!0},minimumMastery:{type:t.Float_unsecure(),isOptional:!0},requiredCount:{type:t.Int_unsecure(),isOptional:!0}}}),f=n({name:"JourneyReward",description:"XP and badge rewards for journey outcomes.",fields:{badgeKey:{type:t.String_unsecure(),isOptional:!0},xp:{type:t.Int_unsecure(),isOptional:!0}}}),k=n({name:"JourneyStep",description:"Adaptive learning journey step definition.",fields:{id:{type:t.String_unsecure(),isOptional:!1},title:{type:t.String_unsecure(),isOptional:!1},description:{type:t.String_unsecure(),isOptional:!0},instructions:{type:t.String_unsecure(),isOptional:!0},helpUrl:{type:t.String_unsecure(),isOptional:!0},order:{type:t.Int_unsecure(),isOptional:!0},completion:{type:A,isOptional:!1},availability:{type:t.JSON(),isOptional:!0},prerequisites:{type:t.JSON(),isOptional:!0},prerequisiteMode:{type:t.String_unsecure(),isOptional:!0},branches:{type:t.JSON(),isOptional:!0},reward:{type:f,isOptional:!0},xpReward:{type:t.Int_unsecure(),isOptional:!0},isRequired:{type:t.Boolean(),isOptional:!0},canSkip:{type:t.Boolean(),isOptional:!0},actionUrl:{type:t.String_unsecure(),isOptional:!0},actionLabel:{type:t.String_unsecure(),isOptional:!0},metadata:{type:t.JSON(),isOptional:!0}}}),v=n({name:"JourneyTrack",description:"Headless adaptive journey track definition.",fields:{id:{type:t.String_unsecure(),isOptional:!1},productId:{type:t.String_unsecure(),isOptional:!0},name:{type:t.String_unsecure(),isOptional:!1},description:{type:t.String_unsecure(),isOptional:!0},targetUserSegment:{type:t.String_unsecure(),isOptional:!0},targetRole:{type:t.String_unsecure(),isOptional:!0},totalXp:{type:t.Int_unsecure(),isOptional:!0},isActive:{type:t.Boolean(),isOptional:!0},isRequired:{type:t.Boolean(),isOptional:!0},canSkip:{type:t.Boolean(),isOptional:!0},streakRule:{type:t.JSON(),isOptional:!0},completionRewards:{type:f,isOptional:!0},metadata:{type:t.JSON(),isOptional:!0},steps:{type:k,isArray:!0,isOptional:!1}}}),C=n({name:"JourneyStepProgress",description:"Persisted and projected state for a journey step.",fields:{stepId:{type:t.String_unsecure(),isOptional:!1},status:{type:t.String_unsecure(),isOptional:!1},xpEarned:{type:t.Int_unsecure(),isOptional:!1},selectedBranchKey:{type:t.String_unsecure(),isOptional:!0},availableAt:{type:t.DateTime(),isOptional:!0},dueAt:{type:t.DateTime(),isOptional:!0},completedAt:{type:t.DateTime(),isOptional:!0},skippedAt:{type:t.DateTime(),isOptional:!0},blockedAt:{type:t.DateTime(),isOptional:!0},missedAt:{type:t.DateTime(),isOptional:!0},triggeringEvent:{type:t.String_unsecure(),isOptional:!0},eventPayload:{type:t.JSON(),isOptional:!0},occurrences:{type:t.Int_unsecure(),isOptional:!0},masteryCount:{type:t.Int_unsecure(),isOptional:!0}}}),o=n({name:"JourneyProgress",description:"Projected adaptive journey progress snapshot.",fields:{learnerId:{type:t.String_unsecure(),isOptional:!0},trackId:{type:t.String_unsecure(),isOptional:!1},progressPercent:{type:t.Int_unsecure(),isOptional:!1},isCompleted:{type:t.Boolean(),isOptional:!1},xpEarned:{type:t.Int_unsecure(),isOptional:!1},streakDays:{type:t.Int_unsecure(),isOptional:!1},badges:{type:t.String_unsecure(),isArray:!0,isOptional:!1},activeStepCount:{type:t.Int_unsecure(),isOptional:!1},completedStepCount:{type:t.Int_unsecure(),isOptional:!1},nextStepId:{type:t.String_unsecure(),isOptional:!0},currentStepId:{type:t.String_unsecure(),isOptional:!0},startedAt:{type:t.DateTime(),isOptional:!0},completedAt:{type:t.DateTime(),isOptional:!0},lastActivityAt:{type:t.DateTime(),isOptional:!0},steps:{type:C,isArray:!0,isOptional:!1}}}),b=n({name:"JourneyTrackListInput",description:"Filters for listing adaptive journey tracks.",fields:{learnerId:{type:t.String_unsecure(),isOptional:!0},productId:{type:t.String_unsecure(),isOptional:!0},trackIds:{type:t.String_unsecure(),isArray:!0,isOptional:!0},includeProgress:{type:t.Boolean(),isOptional:!0}}}),B=n({name:"JourneyTrackListOutput",description:"Adaptive journey catalog with optional progress snapshots.",fields:{tracks:{type:v,isArray:!0,isOptional:!1},progress:{type:o,isArray:!0,isOptional:!0}}}),X=n({name:"JourneyProgressInput",description:"Input for fetching projected progress for one track.",fields:{learnerId:{type:t.String_unsecure(),isOptional:!0},trackId:{type:t.String_unsecure(),isOptional:!1}}}),w=n({name:"JourneyRecordEventInput",description:"Domain event used to advance an adaptive journey.",fields:{learnerId:{type:t.String_unsecure(),isOptional:!1},trackId:{type:t.String_unsecure(),isOptional:!0},eventName:{type:t.String_unsecure(),isOptional:!1},eventVersion:{type:t.Int_unsecure(),isOptional:!0},sourceModule:{type:t.String_unsecure(),isOptional:!0},eventPayload:{type:t.JSON(),isOptional:!0},occurredAt:{type:t.DateTime(),isOptional:!0}}}),V=u({meta:{key:"learning.journey.listTracks",version:"1.0.0",stability:"stable",owners:[...i],tags:["adaptive-learning","journey","learning"],description:"List adaptive learning journeys for a learner or product.",goal:"Expose the adaptive journey catalog to UI and API surfaces.",context:"Called when rendering journey lists, launchers, or registries."},io:{input:b,output:B},policy:{auth:"user"}}),Z=u({meta:{key:"learning.journey.getProgress",version:"1.0.0",stability:"stable",owners:[...i],tags:["adaptive-learning","journey","progress"],description:"Project the latest adaptive journey progress for one track.",goal:"Render next-step, branch, and reward state consistently.",context:"Called by widgets, journey detail pages, and adaptive shells."},io:{input:X,output:o},policy:{auth:"user"}}),$=D({meta:{key:"learning.journey.recordEvent",version:"1.0.0",stability:"stable",owners:[...i],tags:["adaptive-learning","journey","events"],description:"Record a domain event and re-evaluate an adaptive journey.",goal:"Advance branch-aware journey progress from product activity.",context:"Called by event handlers when product activity may unlock or complete steps."},io:{input:w,output:o},policy:{auth:"user"}});import{defineSchemaModel as s,ScalarTypeEnum as e}from"@contractspec/lib.schema";var G=s({name:"Course",description:"A learning course",fields:{id:{type:e.String_unsecure(),isOptional:!1},title:{type:e.String_unsecure(),isOptional:!1},slug:{type:e.String_unsecure(),isOptional:!1},description:{type:e.String_unsecure(),isOptional:!0},difficulty:{type:e.String_unsecure(),isOptional:!1},status:{type:e.String_unsecure(),isOptional:!1},estimatedDuration:{type:e.Int_unsecure(),isOptional:!0},thumbnailUrl:{type:e.String_unsecure(),isOptional:!0},createdAt:{type:e.DateTime(),isOptional:!1}}}),j=s({name:"Learner",description:"A learner profile",fields:{id:{type:e.String_unsecure(),isOptional:!1},userId:{type:e.String_unsecure(),isOptional:!1},displayName:{type:e.String_unsecure(),isOptional:!0},level:{type:e.Int_unsecure(),isOptional:!1},totalXp:{type:e.Int_unsecure(),isOptional:!1},currentStreak:{type:e.Int_unsecure(),isOptional:!1},longestStreak:{type:e.Int_unsecure(),isOptional:!1},createdAt:{type:e.DateTime(),isOptional:!1}}}),r=s({name:"Enrollment",description:"A course enrollment",fields:{id:{type:e.String_unsecure(),isOptional:!1},learnerId:{type:e.String_unsecure(),isOptional:!1},courseId:{type:e.String_unsecure(),isOptional:!1},status:{type:e.String_unsecure(),isOptional:!1},progress:{type:e.Int_unsecure(),isOptional:!1},startedAt:{type:e.DateTime(),isOptional:!0},completedAt:{type:e.DateTime(),isOptional:!0}}}),Q=s({name:"LessonProgress",description:"Lesson progress",fields:{id:{type:e.String_unsecure(),isOptional:!1},learnerId:{type:e.String_unsecure(),isOptional:!1},lessonId:{type:e.String_unsecure(),isOptional:!1},status:{type:e.String_unsecure(),isOptional:!1},progress:{type:e.Int_unsecure(),isOptional:!1},score:{type:e.Int_unsecure(),isOptional:!0},timeSpent:{type:e.Int_unsecure(),isOptional:!1},completedAt:{type:e.DateTime(),isOptional:!0}}}),U=s({name:"Deck",description:"A flashcard deck",fields:{id:{type:e.String_unsecure(),isOptional:!1},title:{type:e.String_unsecure(),isOptional:!1},description:{type:e.String_unsecure(),isOptional:!0},cardCount:{type:e.Int_unsecure(),isOptional:!1},isPublic:{type:e.Boolean(),isOptional:!1},createdAt:{type:e.DateTime(),isOptional:!1}}}),h=s({name:"Card",description:"A flashcard",fields:{id:{type:e.String_unsecure(),isOptional:!1},deckId:{type:e.String_unsecure(),isOptional:!1},front:{type:e.String_unsecure(),isOptional:!1},back:{type:e.String_unsecure(),isOptional:!1},hints:{type:e.JSON(),isOptional:!0},isDue:{type:e.Boolean(),isOptional:!1},nextReviewAt:{type:e.DateTime(),isOptional:!0}}}),q=s({name:"Achievement",description:"An achievement",fields:{id:{type:e.String_unsecure(),isOptional:!1},key:{type:e.String_unsecure(),isOptional:!1},name:{type:e.String_unsecure(),isOptional:!1},description:{type:e.String_unsecure(),isOptional:!1},icon:{type:e.String_unsecure(),isOptional:!0},xpReward:{type:e.Int_unsecure(),isOptional:!1},unlockedAt:{type:e.DateTime(),isOptional:!0}}}),O=s({name:"EnrollInCourseInput",description:"Input for enrolling in a course",fields:{courseId:{type:e.String_unsecure(),isOptional:!1}}}),d=s({name:"CompleteLessonInput",description:"Input for completing a lesson",fields:{lessonId:{type:e.String_unsecure(),isOptional:!1},score:{type:e.Int_unsecure(),isOptional:!0},timeSpent:{type:e.Int_unsecure(),isOptional:!1}}}),g=s({name:"SubmitCardReviewInput",description:"Input for submitting a card review",fields:{cardId:{type:e.String_unsecure(),isOptional:!1},rating:{type:e.String_unsecure(),isOptional:!1},responseTimeMs:{type:e.Int_unsecure(),isOptional:!0}}}),_=s({name:"GetDueCardsInput",description:"Input for getting due cards",fields:{deckId:{type:e.String_unsecure(),isOptional:!0},limit:{type:e.Int_unsecure(),isOptional:!0}}}),a=s({name:"GetDueCardsOutput",description:"Output for getting due cards",fields:{cards:{type:h,isArray:!0,isOptional:!1},total:{type:e.Int_unsecure(),isOptional:!1}}}),I=s({name:"GetLearnerDashboardInput",description:"Input for getting learner dashboard",fields:{learnerId:{type:e.String_unsecure(),isOptional:!0}}}),c=s({name:"LearnerDashboard",description:"Learner dashboard data",fields:{learner:{type:j,isOptional:!1},currentStreak:{type:e.Int_unsecure(),isOptional:!1},dailyXpGoal:{type:e.Int_unsecure(),isOptional:!1},dailyXpProgress:{type:e.Int_unsecure(),isOptional:!1},activeEnrollments:{type:r,isArray:!0,isOptional:!1},recentAchievements:{type:q,isArray:!0,isOptional:!1},dueCardCount:{type:e.Int_unsecure(),isOptional:!1}}}),p=s({name:"SuccessOutput",description:"Generic success output",fields:{success:{type:e.Boolean(),isOptional:!1},xpEarned:{type:e.Int_unsecure(),isOptional:!0}}});import{defineCommand as l,defineQuery as x}from"@contractspec/lib.contracts-spec";var J=l({meta:{key:"learning.enroll",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","enrollment"],description:"Enroll in a course.",goal:"Start learning a new course.",context:"Called when a learner wants to start a course."},io:{input:O,output:r,errors:{COURSE_NOT_FOUND:{description:"Course does not exist",http:404,gqlCode:"COURSE_NOT_FOUND",when:"Course ID is invalid"},ALREADY_ENROLLED:{description:"Already enrolled in course",http:409,gqlCode:"ALREADY_ENROLLED",when:"Learner is already enrolled"}}},policy:{auth:"user"}}),R=l({meta:{key:"learning.completeLesson",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","progress"],description:"Mark a lesson as completed.",goal:"Record lesson completion and earn XP.",context:"Called when a learner finishes a lesson."},io:{input:d,output:p,errors:{LESSON_NOT_FOUND:{description:"Lesson does not exist",http:404,gqlCode:"LESSON_NOT_FOUND",when:"Lesson ID is invalid"},NOT_ENROLLED:{description:"Not enrolled in course",http:403,gqlCode:"NOT_ENROLLED",when:"Learner is not enrolled in the course"}}},policy:{auth:"user"}}),N=l({meta:{key:"learning.submitCardReview",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","flashcards"],description:"Submit a flashcard review.",goal:"Record review and update SRS schedule.",context:"Called when reviewing flashcards."},io:{input:g,output:p,errors:{CARD_NOT_FOUND:{description:"Card does not exist",http:404,gqlCode:"CARD_NOT_FOUND",when:"Card ID is invalid"},INVALID_RATING:{description:"Invalid rating",http:400,gqlCode:"INVALID_RATING",when:"Rating must be AGAIN, HARD, GOOD, or EASY"}}},policy:{auth:"user"}}),m=x({meta:{key:"learning.getDueCards",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","flashcards"],description:"Get flashcards due for review.",goal:"Get the next batch of cards to review.",context:"Called when starting a review session."},io:{input:_,output:a},policy:{auth:"user"}}),S=x({meta:{key:"learning.getDashboard",version:"1.0.0",stability:"stable",owners:[...i],tags:["learning","dashboard"],description:"Get learner dashboard data.",goal:"Display learner progress and stats.",context:"Called when viewing the learning dashboard."},io:{input:I,output:c},policy:{auth:"user"}});export{p as SuccessOutput,g as SubmitCardReviewInput,N as SubmitCardReviewContract,$ as RecordJourneyEventContract,Q as ProgressModel,V as ListJourneyTracksContract,j as LearnerModel,c as LearnerDashboardModel,i as LEARNING_JOURNEY_OWNERS,v as JourneyTrackModel,o as JourneyProgressModel,I as GetLearnerDashboardInput,S as GetLearnerDashboardContract,Z as GetJourneyProgressContract,a as GetDueCardsOutput,_ as GetDueCardsInput,m as GetDueCardsContract,r as EnrollmentModel,O as EnrollInCourseInput,J as EnrollInCourseContract,U as DeckModel,G as CourseModel,d as CompleteLessonInput,R as CompleteLessonContract,h as CardModel,q as AchievementModel};
|