@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.
Files changed (65) hide show
  1. package/README.md +3 -2
  2. package/dist/browser/contracts/index.js +1 -1
  3. package/dist/browser/contracts/journey.js +1 -0
  4. package/dist/browser/contracts/operations.js +1 -1
  5. package/dist/browser/docs/index.js +13 -12
  6. package/dist/browser/docs/learning-journey.docblock.js +13 -12
  7. package/dist/browser/engines/index.js +1 -1
  8. package/dist/browser/engines/xp.js +1 -1
  9. package/dist/browser/entities/index.js +1 -1
  10. package/dist/browser/entities/journey.js +1 -0
  11. package/dist/browser/index.js +14 -13
  12. package/dist/browser/learning-journey.feature.js +1 -1
  13. package/dist/browser/runtime/index.js +1 -0
  14. package/dist/browser/runtime/matchers.js +1 -0
  15. package/dist/browser/runtime/progress-state.js +1 -0
  16. package/dist/browser/runtime/snapshot.js +1 -0
  17. package/dist/contracts/index.d.ts +1 -1
  18. package/dist/contracts/index.js +1 -1
  19. package/dist/contracts/{onboarding.d.ts → journey.d.ts} +477 -180
  20. package/dist/contracts/journey.js +2 -0
  21. package/dist/contracts/operations.js +1 -1
  22. package/dist/docs/index.js +13 -12
  23. package/dist/docs/learning-journey.docblock.js +13 -12
  24. package/dist/engines/index.js +1 -1
  25. package/dist/engines/xp.d.ts +1 -1
  26. package/dist/engines/xp.js +1 -1
  27. package/dist/entities/index.d.ts +28 -27
  28. package/dist/entities/index.js +1 -1
  29. package/dist/entities/{onboarding.d.ts → journey.d.ts} +61 -74
  30. package/dist/entities/journey.js +2 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +14 -13
  33. package/dist/learning-journey.feature.js +1 -1
  34. package/dist/node/contracts/index.js +1 -1
  35. package/dist/node/contracts/journey.js +1 -0
  36. package/dist/node/contracts/operations.js +1 -1
  37. package/dist/node/docs/index.js +13 -12
  38. package/dist/node/docs/learning-journey.docblock.js +13 -12
  39. package/dist/node/engines/index.js +1 -1
  40. package/dist/node/engines/xp.js +1 -1
  41. package/dist/node/entities/index.js +1 -1
  42. package/dist/node/entities/journey.js +1 -0
  43. package/dist/node/index.js +14 -13
  44. package/dist/node/learning-journey.feature.js +1 -1
  45. package/dist/node/runtime/index.js +1 -0
  46. package/dist/node/runtime/matchers.js +1 -0
  47. package/dist/node/runtime/progress-state.js +1 -0
  48. package/dist/node/runtime/snapshot.js +1 -0
  49. package/dist/runtime/index.d.ts +3 -0
  50. package/dist/runtime/index.js +2 -0
  51. package/dist/runtime/journey-runtime.test.d.ts +1 -0
  52. package/dist/runtime/matchers.d.ts +20 -0
  53. package/dist/runtime/matchers.js +2 -0
  54. package/dist/runtime/progress-state.d.ts +19 -0
  55. package/dist/runtime/progress-state.js +2 -0
  56. package/dist/runtime/snapshot.d.ts +8 -0
  57. package/dist/runtime/snapshot.js +2 -0
  58. package/dist/track-spec.d.ts +118 -87
  59. package/package.json +86 -30
  60. package/dist/browser/contracts/onboarding.js +0 -1
  61. package/dist/browser/entities/onboarding.js +0 -1
  62. package/dist/contracts/onboarding.js +0 -2
  63. package/dist/entities/onboarding.js +0 -2
  64. package/dist/node/contracts/onboarding.js +0 -1
  65. package/dist/node/entities/onboarding.js +0 -1
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ var q=["modules.learning-journey"];import{defineCommand as F,defineQuery as B}from"@contractspec/lib.contracts-spec";import{defineSchemaModel as x,ScalarTypeEnum as j}from"@contractspec/lib.schema";var H=x({name:"JourneyCondition",description:"Adaptive completion condition for a journey step.",fields:{kind:{type:j.String_unsecure(),isOptional:!0},eventName:{type:j.String_unsecure(),isOptional:!1},eventVersion:{type:j.Int_unsecure(),isOptional:!0},sourceModule:{type:j.String_unsecure(),isOptional:!0},payloadFilter:{type:j.JSON(),isOptional:!0},atLeast:{type:j.Int_unsecure(),isOptional:!0},withinHours:{type:j.Int_unsecure(),isOptional:!0},withinHoursOfStart:{type:j.Int_unsecure(),isOptional:!0},availableAfterHours:{type:j.Int_unsecure(),isOptional:!0},skillIdField:{type:j.String_unsecure(),isOptional:!0},masteryField:{type:j.String_unsecure(),isOptional:!0},minimumMastery:{type:j.Float_unsecure(),isOptional:!0},requiredCount:{type:j.Int_unsecure(),isOptional:!0}}}),D=x({name:"JourneyReward",description:"XP and badge rewards for journey outcomes.",fields:{badgeKey:{type:j.String_unsecure(),isOptional:!0},xp:{type:j.Int_unsecure(),isOptional:!0}}}),K=x({name:"JourneyStep",description:"Adaptive learning journey step definition.",fields:{id:{type:j.String_unsecure(),isOptional:!1},title:{type:j.String_unsecure(),isOptional:!1},description:{type:j.String_unsecure(),isOptional:!0},instructions:{type:j.String_unsecure(),isOptional:!0},helpUrl:{type:j.String_unsecure(),isOptional:!0},order:{type:j.Int_unsecure(),isOptional:!0},completion:{type:H,isOptional:!1},availability:{type:j.JSON(),isOptional:!0},prerequisites:{type:j.JSON(),isOptional:!0},prerequisiteMode:{type:j.String_unsecure(),isOptional:!0},branches:{type:j.JSON(),isOptional:!0},reward:{type:D,isOptional:!0},xpReward:{type:j.Int_unsecure(),isOptional:!0},isRequired:{type:j.Boolean(),isOptional:!0},canSkip:{type:j.Boolean(),isOptional:!0},actionUrl:{type:j.String_unsecure(),isOptional:!0},actionLabel:{type:j.String_unsecure(),isOptional:!0},metadata:{type:j.JSON(),isOptional:!0}}}),V=x({name:"JourneyTrack",description:"Headless adaptive journey track definition.",fields:{id:{type:j.String_unsecure(),isOptional:!1},productId:{type:j.String_unsecure(),isOptional:!0},name:{type:j.String_unsecure(),isOptional:!1},description:{type:j.String_unsecure(),isOptional:!0},targetUserSegment:{type:j.String_unsecure(),isOptional:!0},targetRole:{type:j.String_unsecure(),isOptional:!0},totalXp:{type:j.Int_unsecure(),isOptional:!0},isActive:{type:j.Boolean(),isOptional:!0},isRequired:{type:j.Boolean(),isOptional:!0},canSkip:{type:j.Boolean(),isOptional:!0},streakRule:{type:j.JSON(),isOptional:!0},completionRewards:{type:D,isOptional:!0},metadata:{type:j.JSON(),isOptional:!0},steps:{type:K,isArray:!0,isOptional:!1}}}),X=x({name:"JourneyStepProgress",description:"Persisted and projected state for a journey step.",fields:{stepId:{type:j.String_unsecure(),isOptional:!1},status:{type:j.String_unsecure(),isOptional:!1},xpEarned:{type:j.Int_unsecure(),isOptional:!1},selectedBranchKey:{type:j.String_unsecure(),isOptional:!0},availableAt:{type:j.DateTime(),isOptional:!0},dueAt:{type:j.DateTime(),isOptional:!0},completedAt:{type:j.DateTime(),isOptional:!0},skippedAt:{type:j.DateTime(),isOptional:!0},blockedAt:{type:j.DateTime(),isOptional:!0},missedAt:{type:j.DateTime(),isOptional:!0},triggeringEvent:{type:j.String_unsecure(),isOptional:!0},eventPayload:{type:j.JSON(),isOptional:!0},occurrences:{type:j.Int_unsecure(),isOptional:!0},masteryCount:{type:j.Int_unsecure(),isOptional:!0}}}),z=x({name:"JourneyProgress",description:"Projected adaptive journey progress snapshot.",fields:{learnerId:{type:j.String_unsecure(),isOptional:!0},trackId:{type:j.String_unsecure(),isOptional:!1},progressPercent:{type:j.Int_unsecure(),isOptional:!1},isCompleted:{type:j.Boolean(),isOptional:!1},xpEarned:{type:j.Int_unsecure(),isOptional:!1},streakDays:{type:j.Int_unsecure(),isOptional:!1},badges:{type:j.String_unsecure(),isArray:!0,isOptional:!1},activeStepCount:{type:j.Int_unsecure(),isOptional:!1},completedStepCount:{type:j.Int_unsecure(),isOptional:!1},nextStepId:{type:j.String_unsecure(),isOptional:!0},currentStepId:{type:j.String_unsecure(),isOptional:!0},startedAt:{type:j.DateTime(),isOptional:!0},completedAt:{type:j.DateTime(),isOptional:!0},lastActivityAt:{type:j.DateTime(),isOptional:!0},steps:{type:X,isArray:!0,isOptional:!1}}}),Z=x({name:"JourneyTrackListInput",description:"Filters for listing adaptive journey tracks.",fields:{learnerId:{type:j.String_unsecure(),isOptional:!0},productId:{type:j.String_unsecure(),isOptional:!0},trackIds:{type:j.String_unsecure(),isArray:!0,isOptional:!0},includeProgress:{type:j.Boolean(),isOptional:!0}}}),$=x({name:"JourneyTrackListOutput",description:"Adaptive journey catalog with optional progress snapshots.",fields:{tracks:{type:V,isArray:!0,isOptional:!1},progress:{type:z,isArray:!0,isOptional:!0}}}),v=x({name:"JourneyProgressInput",description:"Input for fetching projected progress for one track.",fields:{learnerId:{type:j.String_unsecure(),isOptional:!0},trackId:{type:j.String_unsecure(),isOptional:!1}}}),b=x({name:"JourneyRecordEventInput",description:"Domain event used to advance an adaptive journey.",fields:{learnerId:{type:j.String_unsecure(),isOptional:!1},trackId:{type:j.String_unsecure(),isOptional:!0},eventName:{type:j.String_unsecure(),isOptional:!1},eventVersion:{type:j.Int_unsecure(),isOptional:!0},sourceModule:{type:j.String_unsecure(),isOptional:!0},eventPayload:{type:j.JSON(),isOptional:!0},occurredAt:{type:j.DateTime(),isOptional:!0}}}),Q=B({meta:{key:"learning.journey.listTracks",version:"1.0.0",stability:"stable",owners:[...q],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:Z,output:$},policy:{auth:"user"}}),U=B({meta:{key:"learning.journey.getProgress",version:"1.0.0",stability:"stable",owners:[...q],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:v,output:z},policy:{auth:"user"}}),W=F({meta:{key:"learning.journey.recordEvent",version:"1.0.0",stability:"stable",owners:[...q],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:b,output:z},policy:{auth:"user"}});export{W as RecordJourneyEventContract,Q as ListJourneyTracksContract,V as JourneyTrackModel,z as JourneyProgressModel,U as GetJourneyProgressContract};
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{defineSchemaModel as t,ScalarTypeEnum as e}from"@contractspec/lib.schema";var m=t({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}}}),f=t({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}}}),n=t({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}}}),S=t({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}}}),I=t({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}}}),O=t({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}}}),g=t({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}}}),a=t({name:"EnrollInCourseInput",description:"Input for enrolling in a course",fields:{courseId:{type:e.String_unsecure(),isOptional:!1}}}),p=t({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}}}),l=t({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}}}),o=t({name:"GetDueCardsInput",description:"Input for getting due cards",fields:{deckId:{type:e.String_unsecure(),isOptional:!0},limit:{type:e.Int_unsecure(),isOptional:!0}}}),u=t({name:"GetDueCardsOutput",description:"Output for getting due cards",fields:{cards:{type:O,isArray:!0,isOptional:!1},total:{type:e.Int_unsecure(),isOptional:!1}}}),c=t({name:"GetLearnerDashboardInput",description:"Input for getting learner dashboard",fields:{learnerId:{type:e.String_unsecure(),isOptional:!0}}}),d=t({name:"LearnerDashboard",description:"Learner dashboard data",fields:{learner:{type:f,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:n,isArray:!0,isOptional:!1},recentAchievements:{type:g,isArray:!0,isOptional:!1},dueCardCount:{type:e.Int_unsecure(),isOptional:!1}}}),i=t({name:"SuccessOutput",description:"Generic success output",fields:{success:{type:e.Boolean(),isOptional:!1},xpEarned:{type:e.Int_unsecure(),isOptional:!0}}});var s=["modules.learning-journey"];import{defineCommand as r,defineQuery as y}from"@contractspec/lib.contracts-spec";var T=r({meta:{key:"learning.enroll",version:"1.0.0",stability:"stable",owners:[...s],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:a,output:n,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"}}),v=r({meta:{key:"learning.completeLesson",version:"1.0.0",stability:"stable",owners:[...s],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:p,output:i,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"}}),C=r({meta:{key:"learning.submitCardReview",version:"1.0.0",stability:"stable",owners:[...s],tags:["learning","flashcards"],description:"Submit a flashcard review.",goal:"Record review and update SRS schedule.",context:"Called when reviewing flashcards."},io:{input:l,output:i,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"}}),b=y({meta:{key:"learning.getDueCards",version:"1.0.0",stability:"stable",owners:[...s],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:o,output:u},policy:{auth:"user"}}),M=y({meta:{key:"learning.getDashboard",version:"1.0.0",stability:"stable",owners:[...s],tags:["learning","dashboard"],description:"Get learner dashboard data.",goal:"Display learner progress and stats.",context:"Called when viewing the learning dashboard."},io:{input:c,output:d},policy:{auth:"user"}});export{C as SubmitCardReviewContract,M as GetLearnerDashboardContract,b as GetDueCardsContract,T as EnrollInCourseContract,v as CompleteLessonContract};
2
+ var s=["modules.learning-journey"];import{defineSchemaModel as t,ScalarTypeEnum as e}from"@contractspec/lib.schema";var S=t({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}}}),f=t({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}}}),n=t({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}}}),I=t({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}}}),x=t({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}}}),O=t({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}}}),g=t({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}}}),a=t({name:"EnrollInCourseInput",description:"Input for enrolling in a course",fields:{courseId:{type:e.String_unsecure(),isOptional:!1}}}),p=t({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}}}),l=t({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}}}),o=t({name:"GetDueCardsInput",description:"Input for getting due cards",fields:{deckId:{type:e.String_unsecure(),isOptional:!0},limit:{type:e.Int_unsecure(),isOptional:!0}}}),u=t({name:"GetDueCardsOutput",description:"Output for getting due cards",fields:{cards:{type:O,isArray:!0,isOptional:!1},total:{type:e.Int_unsecure(),isOptional:!1}}}),c=t({name:"GetLearnerDashboardInput",description:"Input for getting learner dashboard",fields:{learnerId:{type:e.String_unsecure(),isOptional:!0}}}),d=t({name:"LearnerDashboard",description:"Learner dashboard data",fields:{learner:{type:f,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:n,isArray:!0,isOptional:!1},recentAchievements:{type:g,isArray:!0,isOptional:!1},dueCardCount:{type:e.Int_unsecure(),isOptional:!1}}}),i=t({name:"SuccessOutput",description:"Generic success output",fields:{success:{type:e.Boolean(),isOptional:!1},xpEarned:{type:e.Int_unsecure(),isOptional:!0}}});import{defineCommand as r,defineQuery as y}from"@contractspec/lib.contracts-spec";var T=r({meta:{key:"learning.enroll",version:"1.0.0",stability:"stable",owners:[...s],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:a,output:n,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"}}),v=r({meta:{key:"learning.completeLesson",version:"1.0.0",stability:"stable",owners:[...s],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:p,output:i,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"}}),C=r({meta:{key:"learning.submitCardReview",version:"1.0.0",stability:"stable",owners:[...s],tags:["learning","flashcards"],description:"Submit a flashcard review.",goal:"Record review and update SRS schedule.",context:"Called when reviewing flashcards."},io:{input:l,output:i,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"}}),b=y({meta:{key:"learning.getDueCards",version:"1.0.0",stability:"stable",owners:[...s],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:o,output:u},policy:{auth:"user"}}),M=y({meta:{key:"learning.getDashboard",version:"1.0.0",stability:"stable",owners:[...s],tags:["learning","dashboard"],description:"Get learner dashboard data.",goal:"Display learner progress and stats.",context:"Called when viewing the learning dashboard."},io:{input:c,output:d},policy:{auth:"user"}});export{C as SubmitCardReviewContract,M as GetLearnerDashboardContract,b as GetDueCardsContract,T as EnrollInCourseContract,v as CompleteLessonContract};
@@ -1,16 +1,17 @@
1
1
  // @bun
2
- import{registerDocBlocks as m}from"@contractspec/lib.contracts-spec/docs";var f=[{id:"docs.learning-journey.engine",title:"Learning Journey Engine",summary:"Tracks learners, tracks/modules/steps, progress, quizzes, streaks, XP, and AI coaching hooks for product-integrated onboarding.",kind:"reference",visibility:"public",route:"/docs/learning-journey/engine",tags:["learning","onboarding","journey","education"],body:`## Capabilities
2
+ import{registerDocBlocks as m}from"@contractspec/lib.contracts-spec/docs";var f=[{id:"docs.learning-journey.engine",title:"Learning Journey Engine",summary:"Headless adaptive journey runtime for tracks, branching steps, progress, quizzes, streaks, XP, and AI coaching hooks.",kind:"reference",visibility:"public",route:"/docs/learning-journey/engine",tags:["adaptive-learning","journey","education"],body:`## Capabilities
3
3
 
4
4
  - **Entities**: Learner, Track, Module, Step, Progress, Quiz, Flashcard, AI Coach, Gamification (XP, streaks, badges).
5
- - **Contracts**: enroll/resume/advance steps, complete quizzes, record streaks, assign XP, fetch progress dashboards, onboarding list/progress/recordEvent.
5
+ - **Contracts**: enroll/resume/advance steps, complete quizzes, record streaks, assign XP, fetch progress dashboards, journey list/progress/recordEvent.
6
+ - **Runtime**: canonical branch/prerequisite evaluator + projected next-step snapshots.
6
7
  - **Engines**: spaced-repetition (SRS), streak calculator, XP progression.
7
- - **Events**: learner.enrolled, step.completed, quiz.scored, streak.reset, xp.awarded, onboarding.started/completed/step_completed.
8
+ - **Events**: learner.enrolled, step.completed, quiz.scored, streak.reset, xp.awarded, journey.started/completed/step_completed.
8
9
 
9
10
  ## Completion conditions
10
11
  - Event-based: name/version/source + payload filter
11
12
  - Count-based: require N events (optional time window)
12
13
  - Time-bounded: must complete within a window; steps can unlock by day/hour
13
- - SRS mastery: complete when cards/skills hit mastery thresholds (with required counts)
14
+ - Mastery: complete when cards/skills hit mastery thresholds (with required counts)
14
15
 
15
16
  ## Usage
16
17
 
@@ -21,7 +22,7 @@ import{registerDocBlocks as m}from"@contractspec/lib.contracts-spec/docs";var f=
21
22
  - Import from \`@contractspec/module.learning-journey\` into your spec registry.
22
23
 
23
24
  3) Bind to product actions
24
- - Tie \`Step\` completion conditions to domain events (e.g., deal.created, agent.run.completed, drill.session.completed).
25
+ - Tie \`JourneyStep\` completion conditions and branch rules to domain events (e.g., deal.created, agent.run.completed, drill.session.completed).
25
26
  - Trigger notifications via Notification Center and audits on completion.
26
27
 
27
28
  4) Gamification
@@ -42,10 +43,10 @@ const updated = streak.compute({ lastActiveAt: new Date(), today: new Date() });
42
43
  - Keep steps bound to real product events, not just button clicks.
43
44
  - Avoid storing PII in content; keep org/user scoping for multi-tenant isolation.
44
45
  - Emit analytics and audit logs for completions; respect \`prefers-reduced-motion\` in UIs consuming these specs.
45
- - Track completion bonuses: \`completionXpBonus\`, \`completionBadgeKey\`, optional \`streakHoursWindow\` + \`streakBonusXp\`.
46
+ - Track completion bonuses: \`completionRewards\`, optional \`streakRule\`, and branch-level rewards.
46
47
  `},{id:"docs.learning-journey.goal",title:"Learning Journey \u2014 Goal",summary:"Why the learning journey engine exists and the outcomes it targets.",kind:"goal",visibility:"public",route:"/docs/learning-journey/goal",tags:["learning","goal"],body:`## Why it matters
47
- - Provides a regenerable onboarding/education engine tied to product signals.
48
- - Keeps tracks, steps, quizzes, and gamification consistent across surfaces.
48
+ - Provides a regenerable adaptive-journey engine tied to product signals.
49
+ - Keeps tracks, steps, branches, quizzes, and gamification consistent across surfaces.
49
50
 
50
51
  ## Business/Product goal
51
52
  - Drive activation and retention with measurable progress, SRS, and streaks.
@@ -56,18 +57,18 @@ const updated = streak.compute({ lastActiveAt: new Date(), today: new Date() });
56
57
  - Analytics/audit hooks exist for completions and streaks.`},{id:"docs.learning-journey.usage",title:"Learning Journey \u2014 Usage",summary:"How to compose, bind, and regenerate journeys safely.",kind:"usage",visibility:"public",route:"/docs/learning-journey/usage",tags:["learning","usage"],body:`## Setup
57
58
  1) Include \`learningJourneyEntities\` in schema composition.
58
59
  2) Register contracts/events from \`@contractspec/module.learning-journey\`.
59
- 3) Bind steps to real product events (e.g., deal.created, run.completed).
60
+ 3) Bind steps and branch outcomes to real product events (e.g., deal.created, run.completed).
60
61
 
61
62
  ## Extend & regenerate
62
- 1) Update track/module/step definitions or quiz schemas in spec.
63
+ 1) Update track/module/step definitions, branch rules, or quiz schemas in spec.
63
64
  2) Regenerate to sync UI/API/events; mark PII paths where needed.
64
65
  3) Use Feature Flags to trial new tracks or streak rules.
65
66
 
66
67
  ## Guardrails
67
- - Avoid hardcoded progression; keep engines declarative.
68
+ - Avoid hardcoded progression; keep runtime rules declarative.
68
69
  - Emit analytics/audit for completions; respect user locale/accessibility in presentations.
69
70
  - Keep content free of PII; scope learners by org/tenant.`},{id:"docs.learning-journey.constraints",title:"Learning Journey \u2014 Constraints & Safety",summary:"Internal guardrails for progression, telemetry, and regeneration semantics.",kind:"reference",visibility:"internal",route:"/docs/learning-journey/constraints",tags:["learning","constraints","internal"],body:`## Constraints
70
- - Progression (tracks/modules/steps) and engines (SRS, streaks, XP) must stay declarative in spec.
71
+ - Progression (tracks/modules/steps/branches) and engines (SRS, streaks, XP) must stay declarative in spec.
71
72
  - Events to emit: learner.enrolled, step.completed, quiz.scored, streak.reset, xp.awarded.
72
73
  - Regeneration should not change scoring/streak rules without explicit spec change.
73
74
 
@@ -1,16 +1,17 @@
1
1
  // @bun
2
- import{registerDocBlocks as f}from"@contractspec/lib.contracts-spec/docs";var h=[{id:"docs.learning-journey.engine",title:"Learning Journey Engine",summary:"Tracks learners, tracks/modules/steps, progress, quizzes, streaks, XP, and AI coaching hooks for product-integrated onboarding.",kind:"reference",visibility:"public",route:"/docs/learning-journey/engine",tags:["learning","onboarding","journey","education"],body:`## Capabilities
2
+ import{registerDocBlocks as f}from"@contractspec/lib.contracts-spec/docs";var h=[{id:"docs.learning-journey.engine",title:"Learning Journey Engine",summary:"Headless adaptive journey runtime for tracks, branching steps, progress, quizzes, streaks, XP, and AI coaching hooks.",kind:"reference",visibility:"public",route:"/docs/learning-journey/engine",tags:["adaptive-learning","journey","education"],body:`## Capabilities
3
3
 
4
4
  - **Entities**: Learner, Track, Module, Step, Progress, Quiz, Flashcard, AI Coach, Gamification (XP, streaks, badges).
5
- - **Contracts**: enroll/resume/advance steps, complete quizzes, record streaks, assign XP, fetch progress dashboards, onboarding list/progress/recordEvent.
5
+ - **Contracts**: enroll/resume/advance steps, complete quizzes, record streaks, assign XP, fetch progress dashboards, journey list/progress/recordEvent.
6
+ - **Runtime**: canonical branch/prerequisite evaluator + projected next-step snapshots.
6
7
  - **Engines**: spaced-repetition (SRS), streak calculator, XP progression.
7
- - **Events**: learner.enrolled, step.completed, quiz.scored, streak.reset, xp.awarded, onboarding.started/completed/step_completed.
8
+ - **Events**: learner.enrolled, step.completed, quiz.scored, streak.reset, xp.awarded, journey.started/completed/step_completed.
8
9
 
9
10
  ## Completion conditions
10
11
  - Event-based: name/version/source + payload filter
11
12
  - Count-based: require N events (optional time window)
12
13
  - Time-bounded: must complete within a window; steps can unlock by day/hour
13
- - SRS mastery: complete when cards/skills hit mastery thresholds (with required counts)
14
+ - Mastery: complete when cards/skills hit mastery thresholds (with required counts)
14
15
 
15
16
  ## Usage
16
17
 
@@ -21,7 +22,7 @@ import{registerDocBlocks as f}from"@contractspec/lib.contracts-spec/docs";var h=
21
22
  - Import from \`@contractspec/module.learning-journey\` into your spec registry.
22
23
 
23
24
  3) Bind to product actions
24
- - Tie \`Step\` completion conditions to domain events (e.g., deal.created, agent.run.completed, drill.session.completed).
25
+ - Tie \`JourneyStep\` completion conditions and branch rules to domain events (e.g., deal.created, agent.run.completed, drill.session.completed).
25
26
  - Trigger notifications via Notification Center and audits on completion.
26
27
 
27
28
  4) Gamification
@@ -42,10 +43,10 @@ const updated = streak.compute({ lastActiveAt: new Date(), today: new Date() });
42
43
  - Keep steps bound to real product events, not just button clicks.
43
44
  - Avoid storing PII in content; keep org/user scoping for multi-tenant isolation.
44
45
  - Emit analytics and audit logs for completions; respect \`prefers-reduced-motion\` in UIs consuming these specs.
45
- - Track completion bonuses: \`completionXpBonus\`, \`completionBadgeKey\`, optional \`streakHoursWindow\` + \`streakBonusXp\`.
46
+ - Track completion bonuses: \`completionRewards\`, optional \`streakRule\`, and branch-level rewards.
46
47
  `},{id:"docs.learning-journey.goal",title:"Learning Journey \u2014 Goal",summary:"Why the learning journey engine exists and the outcomes it targets.",kind:"goal",visibility:"public",route:"/docs/learning-journey/goal",tags:["learning","goal"],body:`## Why it matters
47
- - Provides a regenerable onboarding/education engine tied to product signals.
48
- - Keeps tracks, steps, quizzes, and gamification consistent across surfaces.
48
+ - Provides a regenerable adaptive-journey engine tied to product signals.
49
+ - Keeps tracks, steps, branches, quizzes, and gamification consistent across surfaces.
49
50
 
50
51
  ## Business/Product goal
51
52
  - Drive activation and retention with measurable progress, SRS, and streaks.
@@ -56,18 +57,18 @@ const updated = streak.compute({ lastActiveAt: new Date(), today: new Date() });
56
57
  - Analytics/audit hooks exist for completions and streaks.`},{id:"docs.learning-journey.usage",title:"Learning Journey \u2014 Usage",summary:"How to compose, bind, and regenerate journeys safely.",kind:"usage",visibility:"public",route:"/docs/learning-journey/usage",tags:["learning","usage"],body:`## Setup
57
58
  1) Include \`learningJourneyEntities\` in schema composition.
58
59
  2) Register contracts/events from \`@contractspec/module.learning-journey\`.
59
- 3) Bind steps to real product events (e.g., deal.created, run.completed).
60
+ 3) Bind steps and branch outcomes to real product events (e.g., deal.created, run.completed).
60
61
 
61
62
  ## Extend & regenerate
62
- 1) Update track/module/step definitions or quiz schemas in spec.
63
+ 1) Update track/module/step definitions, branch rules, or quiz schemas in spec.
63
64
  2) Regenerate to sync UI/API/events; mark PII paths where needed.
64
65
  3) Use Feature Flags to trial new tracks or streak rules.
65
66
 
66
67
  ## Guardrails
67
- - Avoid hardcoded progression; keep engines declarative.
68
+ - Avoid hardcoded progression; keep runtime rules declarative.
68
69
  - Emit analytics/audit for completions; respect user locale/accessibility in presentations.
69
70
  - Keep content free of PII; scope learners by org/tenant.`},{id:"docs.learning-journey.constraints",title:"Learning Journey \u2014 Constraints & Safety",summary:"Internal guardrails for progression, telemetry, and regeneration semantics.",kind:"reference",visibility:"internal",route:"/docs/learning-journey/constraints",tags:["learning","constraints","internal"],body:`## Constraints
70
- - Progression (tracks/modules/steps) and engines (SRS, streaks, XP) must stay declarative in spec.
71
+ - Progression (tracks/modules/steps/branches) and engines (SRS, streaks, XP) must stay declarative in spec.
71
72
  - Events to emit: learner.enrolled, step.completed, quiz.scored, streak.reset, xp.awarded.
72
73
  - Regeneration should not change scoring/streak rules without explicit spec change.
73
74
 
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var N={learningSteps:[1,10],graduatingInterval:1,easyInterval:4,relearningSteps:[10],minEaseFactor:1.3,maxInterval:365,intervalModifier:1,newIntervalModifier:0.5,hardIntervalModifier:1.2,easyBonus:1.3};class K{config;constructor(j={}){this.config={...N,...j}}calculateNextReview(j,Q,W=new Date){if(!j.isGraduated&&!j.isRelearning)return this.handleLearningCard(j,Q,W);if(j.isRelearning)return this.handleRelearningCard(j,Q,W);return this.handleReviewCard(j,Q,W)}getInitialState(){return{interval:0,easeFactor:2.5,repetitions:0,learningStep:0,isGraduated:!1,isRelearning:!1,lapses:0}}isDue(j,Q=new Date){return j<=Q}getOverdueDays(j,Q=new Date){let W=Q.getTime()-j.getTime();return Math.floor(W/86400000)}handleLearningCard(j,Q,W){let H=this.config.learningSteps,V=j.learningStep,Z=!1,$=0,J;switch(Q){case"AGAIN":V=0,$=H[0]??1,J=this.addMinutes(W,$);break;case"HARD":$=H[V]??H[0]??1,J=this.addMinutes(W,$);break;case"GOOD":if(V++,V>=H.length)Z=!0,$=this.config.graduatingInterval,J=this.addDays(W,$);else $=H[V]??10,J=this.addMinutes(W,$);break;case"EASY":Z=!0,$=this.config.easyInterval,J=this.addDays(W,$);break}return{interval:Z?$:0,easeFactor:j.easeFactor,repetitions:Z?1:0,nextReviewAt:J,learningStep:V,isGraduated:Z,isRelearning:!1,lapses:j.lapses}}handleRelearningCard(j,Q,W){let H=this.config.relearningSteps,V=j.learningStep,Z=!0,$=0,J;switch(Q){case"AGAIN":V=0,$=H[0]??10,J=this.addMinutes(W,$);break;case"HARD":$=H[V]??H[0]??10,J=this.addMinutes(W,$);break;case"GOOD":if(V++,V>=H.length)Z=!1,$=Math.max(1,Math.floor(j.interval*this.config.newIntervalModifier)),J=this.addDays(W,$);else $=H[V]??10,J=this.addMinutes(W,$);break;case"EASY":Z=!1,$=Math.max(1,Math.floor(j.interval*this.config.newIntervalModifier*1.5)),J=this.addDays(W,$);break}return{interval:Z?j.interval:$,easeFactor:j.easeFactor,repetitions:Z?j.repetitions:j.repetitions+1,nextReviewAt:J,learningStep:V,isGraduated:!0,isRelearning:Z,lapses:j.lapses}}handleReviewCard(j,Q,W){let H,V=j.easeFactor,Z=j.repetitions,$=!1,J=0,q=j.lapses;switch(Q){case"AGAIN":return q++,$=!0,J=0,V=Math.max(this.config.minEaseFactor,V-0.2),H=j.interval,{interval:H,easeFactor:V,repetitions:Z,nextReviewAt:this.addMinutes(W,this.config.relearningSteps[0]??10),learningStep:J,isGraduated:!0,isRelearning:!0,lapses:q};case"HARD":V=Math.max(this.config.minEaseFactor,V-0.15),H=Math.max(j.interval+1,j.interval*this.config.hardIntervalModifier);break;case"GOOD":H=j.interval*V*this.config.intervalModifier,Z++;break;case"EASY":V=V+0.15,H=j.interval*V*this.config.easyBonus*this.config.intervalModifier,Z++;break}return H=Math.min(Math.round(H),this.config.maxInterval),H=Math.max(1,H),{interval:H,easeFactor:V,repetitions:Z,nextReviewAt:this.addDays(W,H),learningStep:J,isGraduated:!0,isRelearning:$,lapses:q}}addMinutes(j,Q){return new Date(j.getTime()+Q*60*1000)}addDays(j,Q){return new Date(j.getTime()+Q*24*60*60*1000)}}var I=new K;var O={timezone:"UTC",freezesPerMonth:2,maxFreezes:5,gracePeriodHours:4};class h{config;constructor(j={}){this.config={...O,...j}}update(j,Q=new Date){let W=this.getDateString(Q),H={state:{...j},streakMaintained:!1,streakLost:!1,freezeUsed:!1,newStreak:!1,daysMissed:0};if(!j.lastActivityDate)return H.state.currentStreak=1,H.state.longestStreak=Math.max(1,j.longestStreak),H.state.lastActivityAt=Q,H.state.lastActivityDate=W,H.newStreak=!0,H.streakMaintained=!0,H;if(j.lastActivityDate===W)return H.state.lastActivityAt=Q,H.streakMaintained=!0,H;let V=this.getDaysBetween(j.lastActivityDate,W);if(V===1)return H.state.currentStreak=j.currentStreak+1,H.state.longestStreak=Math.max(H.state.currentStreak,j.longestStreak),H.state.lastActivityAt=Q,H.state.lastActivityDate=W,H.streakMaintained=!0,H;H.daysMissed=V-1;let Z=H.daysMissed;if(Z<=j.freezesRemaining)return H.state.freezesRemaining=j.freezesRemaining-Z,H.state.freezeUsedAt=Q,H.state.currentStreak=j.currentStreak+1,H.state.longestStreak=Math.max(H.state.currentStreak,j.longestStreak),H.state.lastActivityAt=Q,H.state.lastActivityDate=W,H.freezeUsed=!0,H.streakMaintained=!0,H;return H.streakLost=!0,H.state.currentStreak=1,H.state.lastActivityAt=Q,H.state.lastActivityDate=W,H.newStreak=!0,H}checkStatus(j,Q=new Date){if(!j.lastActivityDate)return{isActive:!1,willExpireAt:null,canUseFreeze:!1,daysUntilExpiry:0};let W=this.getDateString(Q),H=this.getDaysBetween(j.lastActivityDate,W);if(H===0){let Z=this.addDays(Q,1);return Z.setHours(23,59,59,999),{isActive:!0,willExpireAt:Z,canUseFreeze:j.freezesRemaining>0,daysUntilExpiry:1}}if(H===1){let Z=new Date(Q);return Z.setHours(23+this.config.gracePeriodHours,59,59,999),{isActive:!0,willExpireAt:Z,canUseFreeze:j.freezesRemaining>0,daysUntilExpiry:0}}let V=H-1;return{isActive:V<=j.freezesRemaining,willExpireAt:null,canUseFreeze:V<=j.freezesRemaining,daysUntilExpiry:-V}}useFreeze(j,Q=new Date){if(j.freezesRemaining<=0)return null;return{...j,freezesRemaining:j.freezesRemaining-1,freezeUsedAt:Q}}awardMonthlyFreezes(j){return{...j,freezesRemaining:Math.min(j.freezesRemaining+this.config.freezesPerMonth,this.config.maxFreezes)}}getInitialState(){return{currentStreak:0,longestStreak:0,lastActivityAt:null,lastActivityDate:null,freezesRemaining:this.config.freezesPerMonth,freezeUsedAt:null}}getMilestones(j){let Q=[3,7,14,30,60,90,180,365,500,1000],W=Q.filter((V)=>j>=V),H=Q.find((V)=>j<V)??null;return{achieved:W,next:H}}getDateString(j){let Q=j.getFullYear(),W=String(j.getMonth()+1).padStart(2,"0"),H=String(j.getDate()).padStart(2,"0");return`${Q}-${W}-${H}`}getDaysBetween(j,Q){let W=new Date(j),V=new Date(Q).getTime()-W.getTime();return Math.floor(V/86400000)}addDays(j,Q){return new Date(j.getTime()+Q*24*60*60*1000)}}var x=new h;import{defineTranslation as L}from"@contractspec/lib.contracts-spec/translations";var B=L({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 G}from"@contractspec/lib.contracts-spec/translations";var M=G({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\xF3n por puntuaci\xF3n",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Puntuaci\xF3n 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\xF3n por reintento",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonificaci\xF3n por racha",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as T}from"@contractspec/lib.contracts-spec/translations";var U=T({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\xE9nalit\xE9 de r\xE9essai",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonus de s\xE9rie",description:"XP breakdown label for streak bonus"}}});import{createI18nFactory as m}from"@contractspec/lib.contracts-spec/translations";var z=m({specKey:"learning-journey.messages",catalogs:[B,U,M]}),P=z.create,v=z.getDefault,d=z.resetRegistry;var Y={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,onboarding_step:5,onboarding_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 C{config;constructor(j={}){this.config={...Y,...j,baseValues:{...Y.baseValues,...j.baseValues},scoreThresholds:j.scoreThresholds||Y.scoreThresholds,streakTiers:j.streakTiers||Y.streakTiers}}calculate(j){let Q=[],W=j.baseXp??this.config.baseValues[j.activity],H=W;if(Q.push({source:"base",amount:W}),j.score!==void 0){let V=this.getScoreMultiplier(j.score);if(V!==1){let Z=Math.round(W*(V-1));H+=Z,Q.push({source:"score_bonus",amount:Z,multiplier:V})}if(j.score===100){let Z=Math.round(W*(this.config.perfectScoreMultiplier-1));H+=Z,Q.push({source:"perfect_score",amount:Z,multiplier:this.config.perfectScoreMultiplier})}}if(j.attemptNumber===1&&!j.isRetry)H+=this.config.firstAttemptBonus,Q.push({source:"first_attempt",amount:this.config.firstAttemptBonus});if(j.isRetry){let V=Math.round(H*(1-this.config.retryPenalty));H-=V,Q.push({source:"retry_penalty",amount:-V,multiplier:this.config.retryPenalty})}if(j.currentStreak&&j.currentStreak>0){let V=this.getStreakBonus(j.currentStreak);if(V>0)H+=V,Q.push({source:"streak_bonus",amount:V})}if(W>0)H=Math.max(1,H);return{totalXp:Math.round(H),baseXp:W,breakdown:Q}}calculateStreakBonus(j){let Q=this.getStreakBonus(j);return{totalXp:Q,baseXp:Q,breakdown:[{source:"streak_bonus",amount:Q}]}}getXpForLevel(j){if(j<=1)return 0;return Math.round(100*Math.pow(j-1,1.5))}getLevelFromXp(j){let Q=1,W=this.getXpForLevel(Q+1);while(j>=W&&Q<1000)Q++,W=this.getXpForLevel(Q+1);let H=this.getXpForLevel(Q),V=this.getXpForLevel(Q+1);return{level:Q,xpInLevel:j-H,xpForNextLevel:V-H}}getScoreMultiplier(j){for(let Q of this.config.scoreThresholds)if(j>=Q.min)return Q.multiplier;return 1}getStreakBonus(j){for(let Q of this.config.streakTiers)if(j>=Q.days)return Q.bonus;return 0}}var _={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 u(j,Q){let W=P(Q),H=_[j];return H?W.t(H):j}var w=new C;export{w as xpEngine,x as streakEngine,I as srsEngine,u as getXpSourceLabel,C as XPEngine,h as StreakEngine,K as SRSEngine,Y as DEFAULT_XP_CONFIG,O as DEFAULT_STREAK_CONFIG,N as DEFAULT_SRS_CONFIG};
2
+ var N={learningSteps:[1,10],graduatingInterval:1,easyInterval:4,relearningSteps:[10],minEaseFactor:1.3,maxInterval:365,intervalModifier:1,newIntervalModifier:0.5,hardIntervalModifier:1.2,easyBonus:1.3};class K{config;constructor(j={}){this.config={...N,...j}}calculateNextReview(j,Q,W=new Date){if(!j.isGraduated&&!j.isRelearning)return this.handleLearningCard(j,Q,W);if(j.isRelearning)return this.handleRelearningCard(j,Q,W);return this.handleReviewCard(j,Q,W)}getInitialState(){return{interval:0,easeFactor:2.5,repetitions:0,learningStep:0,isGraduated:!1,isRelearning:!1,lapses:0}}isDue(j,Q=new Date){return j<=Q}getOverdueDays(j,Q=new Date){let W=Q.getTime()-j.getTime();return Math.floor(W/86400000)}handleLearningCard(j,Q,W){let H=this.config.learningSteps,V=j.learningStep,Z=!1,$=0,J;switch(Q){case"AGAIN":V=0,$=H[0]??1,J=this.addMinutes(W,$);break;case"HARD":$=H[V]??H[0]??1,J=this.addMinutes(W,$);break;case"GOOD":if(V++,V>=H.length)Z=!0,$=this.config.graduatingInterval,J=this.addDays(W,$);else $=H[V]??10,J=this.addMinutes(W,$);break;case"EASY":Z=!0,$=this.config.easyInterval,J=this.addDays(W,$);break}return{interval:Z?$:0,easeFactor:j.easeFactor,repetitions:Z?1:0,nextReviewAt:J,learningStep:V,isGraduated:Z,isRelearning:!1,lapses:j.lapses}}handleRelearningCard(j,Q,W){let H=this.config.relearningSteps,V=j.learningStep,Z=!0,$=0,J;switch(Q){case"AGAIN":V=0,$=H[0]??10,J=this.addMinutes(W,$);break;case"HARD":$=H[V]??H[0]??10,J=this.addMinutes(W,$);break;case"GOOD":if(V++,V>=H.length)Z=!1,$=Math.max(1,Math.floor(j.interval*this.config.newIntervalModifier)),J=this.addDays(W,$);else $=H[V]??10,J=this.addMinutes(W,$);break;case"EASY":Z=!1,$=Math.max(1,Math.floor(j.interval*this.config.newIntervalModifier*1.5)),J=this.addDays(W,$);break}return{interval:Z?j.interval:$,easeFactor:j.easeFactor,repetitions:Z?j.repetitions:j.repetitions+1,nextReviewAt:J,learningStep:V,isGraduated:!0,isRelearning:Z,lapses:j.lapses}}handleReviewCard(j,Q,W){let H,V=j.easeFactor,Z=j.repetitions,$=!1,J=0,q=j.lapses;switch(Q){case"AGAIN":return q++,$=!0,J=0,V=Math.max(this.config.minEaseFactor,V-0.2),H=j.interval,{interval:H,easeFactor:V,repetitions:Z,nextReviewAt:this.addMinutes(W,this.config.relearningSteps[0]??10),learningStep:J,isGraduated:!0,isRelearning:!0,lapses:q};case"HARD":V=Math.max(this.config.minEaseFactor,V-0.15),H=Math.max(j.interval+1,j.interval*this.config.hardIntervalModifier);break;case"GOOD":H=j.interval*V*this.config.intervalModifier,Z++;break;case"EASY":V=V+0.15,H=j.interval*V*this.config.easyBonus*this.config.intervalModifier,Z++;break}return H=Math.min(Math.round(H),this.config.maxInterval),H=Math.max(1,H),{interval:H,easeFactor:V,repetitions:Z,nextReviewAt:this.addDays(W,H),learningStep:J,isGraduated:!0,isRelearning:$,lapses:q}}addMinutes(j,Q){return new Date(j.getTime()+Q*60*1000)}addDays(j,Q){return new Date(j.getTime()+Q*24*60*60*1000)}}var I=new K;var O={timezone:"UTC",freezesPerMonth:2,maxFreezes:5,gracePeriodHours:4};class h{config;constructor(j={}){this.config={...O,...j}}update(j,Q=new Date){let W=this.getDateString(Q),H={state:{...j},streakMaintained:!1,streakLost:!1,freezeUsed:!1,newStreak:!1,daysMissed:0};if(!j.lastActivityDate)return H.state.currentStreak=1,H.state.longestStreak=Math.max(1,j.longestStreak),H.state.lastActivityAt=Q,H.state.lastActivityDate=W,H.newStreak=!0,H.streakMaintained=!0,H;if(j.lastActivityDate===W)return H.state.lastActivityAt=Q,H.streakMaintained=!0,H;let V=this.getDaysBetween(j.lastActivityDate,W);if(V===1)return H.state.currentStreak=j.currentStreak+1,H.state.longestStreak=Math.max(H.state.currentStreak,j.longestStreak),H.state.lastActivityAt=Q,H.state.lastActivityDate=W,H.streakMaintained=!0,H;H.daysMissed=V-1;let Z=H.daysMissed;if(Z<=j.freezesRemaining)return H.state.freezesRemaining=j.freezesRemaining-Z,H.state.freezeUsedAt=Q,H.state.currentStreak=j.currentStreak+1,H.state.longestStreak=Math.max(H.state.currentStreak,j.longestStreak),H.state.lastActivityAt=Q,H.state.lastActivityDate=W,H.freezeUsed=!0,H.streakMaintained=!0,H;return H.streakLost=!0,H.state.currentStreak=1,H.state.lastActivityAt=Q,H.state.lastActivityDate=W,H.newStreak=!0,H}checkStatus(j,Q=new Date){if(!j.lastActivityDate)return{isActive:!1,willExpireAt:null,canUseFreeze:!1,daysUntilExpiry:0};let W=this.getDateString(Q),H=this.getDaysBetween(j.lastActivityDate,W);if(H===0){let Z=this.addDays(Q,1);return Z.setHours(23,59,59,999),{isActive:!0,willExpireAt:Z,canUseFreeze:j.freezesRemaining>0,daysUntilExpiry:1}}if(H===1){let Z=new Date(Q);return Z.setHours(23+this.config.gracePeriodHours,59,59,999),{isActive:!0,willExpireAt:Z,canUseFreeze:j.freezesRemaining>0,daysUntilExpiry:0}}let V=H-1;return{isActive:V<=j.freezesRemaining,willExpireAt:null,canUseFreeze:V<=j.freezesRemaining,daysUntilExpiry:-V}}useFreeze(j,Q=new Date){if(j.freezesRemaining<=0)return null;return{...j,freezesRemaining:j.freezesRemaining-1,freezeUsedAt:Q}}awardMonthlyFreezes(j){return{...j,freezesRemaining:Math.min(j.freezesRemaining+this.config.freezesPerMonth,this.config.maxFreezes)}}getInitialState(){return{currentStreak:0,longestStreak:0,lastActivityAt:null,lastActivityDate:null,freezesRemaining:this.config.freezesPerMonth,freezeUsedAt:null}}getMilestones(j){let Q=[3,7,14,30,60,90,180,365,500,1000],W=Q.filter((V)=>j>=V),H=Q.find((V)=>j<V)??null;return{achieved:W,next:H}}getDateString(j){let Q=j.getFullYear(),W=String(j.getMonth()+1).padStart(2,"0"),H=String(j.getDate()).padStart(2,"0");return`${Q}-${W}-${H}`}getDaysBetween(j,Q){let W=new Date(j),V=new Date(Q).getTime()-W.getTime();return Math.floor(V/86400000)}addDays(j,Q){return new Date(j.getTime()+Q*24*60*60*1000)}}var x=new h;import{defineTranslation as L}from"@contractspec/lib.contracts-spec/translations";var B=L({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 G}from"@contractspec/lib.contracts-spec/translations";var M=G({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\xF3n por puntuaci\xF3n",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Puntuaci\xF3n 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\xF3n por reintento",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonificaci\xF3n por racha",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as T}from"@contractspec/lib.contracts-spec/translations";var U=T({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\xE9nalit\xE9 de r\xE9essai",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonus de s\xE9rie",description:"XP breakdown label for streak bonus"}}});import{createI18nFactory as m}from"@contractspec/lib.contracts-spec/translations";var z=m({specKey:"learning-journey.messages",catalogs:[B,U,M]}),P=z.create,v=z.getDefault,d=z.resetRegistry;var Y={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 C{config;constructor(j={}){this.config={...Y,...j,baseValues:{...Y.baseValues,...j.baseValues},scoreThresholds:j.scoreThresholds||Y.scoreThresholds,streakTiers:j.streakTiers||Y.streakTiers}}calculate(j){let Q=[],W=j.baseXp??this.config.baseValues[j.activity],H=W;if(Q.push({source:"base",amount:W}),j.score!==void 0){let V=this.getScoreMultiplier(j.score);if(V!==1){let Z=Math.round(W*(V-1));H+=Z,Q.push({source:"score_bonus",amount:Z,multiplier:V})}if(j.score===100){let Z=Math.round(W*(this.config.perfectScoreMultiplier-1));H+=Z,Q.push({source:"perfect_score",amount:Z,multiplier:this.config.perfectScoreMultiplier})}}if(j.attemptNumber===1&&!j.isRetry)H+=this.config.firstAttemptBonus,Q.push({source:"first_attempt",amount:this.config.firstAttemptBonus});if(j.isRetry){let V=Math.round(H*(1-this.config.retryPenalty));H-=V,Q.push({source:"retry_penalty",amount:-V,multiplier:this.config.retryPenalty})}if(j.currentStreak&&j.currentStreak>0){let V=this.getStreakBonus(j.currentStreak);if(V>0)H+=V,Q.push({source:"streak_bonus",amount:V})}if(W>0)H=Math.max(1,H);return{totalXp:Math.round(H),baseXp:W,breakdown:Q}}calculateStreakBonus(j){let Q=this.getStreakBonus(j);return{totalXp:Q,baseXp:Q,breakdown:[{source:"streak_bonus",amount:Q}]}}getXpForLevel(j){if(j<=1)return 0;return Math.round(100*Math.pow(j-1,1.5))}getLevelFromXp(j){let Q=1,W=this.getXpForLevel(Q+1);while(j>=W&&Q<1000)Q++,W=this.getXpForLevel(Q+1);let H=this.getXpForLevel(Q),V=this.getXpForLevel(Q+1);return{level:Q,xpInLevel:j-H,xpForNextLevel:V-H}}getScoreMultiplier(j){for(let Q of this.config.scoreThresholds)if(j>=Q.min)return Q.multiplier;return 1}getStreakBonus(j){for(let Q of this.config.streakTiers)if(j>=Q.days)return Q.bonus;return 0}}var _={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 u(j,Q){let W=P(Q),H=_[j];return H?W.t(H):j}var w=new C;export{w as xpEngine,x as streakEngine,I as srsEngine,u as getXpSourceLabel,C as XPEngine,h as StreakEngine,K as SRSEngine,Y as DEFAULT_XP_CONFIG,O as DEFAULT_STREAK_CONFIG,N as DEFAULT_SRS_CONFIG};
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Calculates XP rewards for various learning activities.
5
5
  */
6
- export type XPActivityType = 'lesson_complete' | 'quiz_pass' | 'quiz_perfect' | 'flashcard_review' | 'course_complete' | 'module_complete' | 'streak_bonus' | 'achievement_unlock' | 'daily_goal_complete' | 'first_lesson' | 'onboarding_step' | 'onboarding_complete';
6
+ export type XPActivityType = 'lesson_complete' | 'quiz_pass' | 'quiz_perfect' | 'flashcard_review' | 'course_complete' | 'module_complete' | 'streak_bonus' | 'achievement_unlock' | 'daily_goal_complete' | 'first_lesson' | 'journey_step' | 'journey_complete';
7
7
  export interface XPCalculationInput {
8
8
  /** Type of activity */
9
9
  activity: XPActivityType;
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{defineTranslation as R}from"@contractspec/lib.contracts-spec/translations";var m=R({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 S}from"@contractspec/lib.contracts-spec/translations";var J=S({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\xF3n por puntuaci\xF3n",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Puntuaci\xF3n 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\xF3n por reintento",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonificaci\xF3n por racha",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as h}from"@contractspec/lib.contracts-spec/translations";var Y=h({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\xE9nalit\xE9 de r\xE9essai",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonus de s\xE9rie",description:"XP breakdown label for streak bonus"}}});import{createI18nFactory as G}from"@contractspec/lib.contracts-spec/translations";var $=G({specKey:"learning-journey.messages",catalogs:[m,Y,J]}),q=$.create,T=$.getDefault,E=$.resetRegistry;var Z={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,onboarding_step:5,onboarding_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 K{config;constructor(j={}){this.config={...Z,...j,baseValues:{...Z.baseValues,...j.baseValues},scoreThresholds:j.scoreThresholds||Z.scoreThresholds,streakTiers:j.streakTiers||Z.streakTiers}}calculate(j){let z=[],V=j.baseXp??this.config.baseValues[j.activity],H=V;if(z.push({source:"base",amount:V}),j.score!==void 0){let Q=this.getScoreMultiplier(j.score);if(Q!==1){let W=Math.round(V*(Q-1));H+=W,z.push({source:"score_bonus",amount:W,multiplier:Q})}if(j.score===100){let W=Math.round(V*(this.config.perfectScoreMultiplier-1));H+=W,z.push({source:"perfect_score",amount:W,multiplier:this.config.perfectScoreMultiplier})}}if(j.attemptNumber===1&&!j.isRetry)H+=this.config.firstAttemptBonus,z.push({source:"first_attempt",amount:this.config.firstAttemptBonus});if(j.isRetry){let Q=Math.round(H*(1-this.config.retryPenalty));H-=Q,z.push({source:"retry_penalty",amount:-Q,multiplier:this.config.retryPenalty})}if(j.currentStreak&&j.currentStreak>0){let Q=this.getStreakBonus(j.currentStreak);if(Q>0)H+=Q,z.push({source:"streak_bonus",amount:Q})}if(V>0)H=Math.max(1,H);return{totalXp:Math.round(H),baseXp:V,breakdown:z}}calculateStreakBonus(j){let z=this.getStreakBonus(j);return{totalXp:z,baseXp:z,breakdown:[{source:"streak_bonus",amount:z}]}}getXpForLevel(j){if(j<=1)return 0;return Math.round(100*Math.pow(j-1,1.5))}getLevelFromXp(j){let z=1,V=this.getXpForLevel(z+1);while(j>=V&&z<1000)z++,V=this.getXpForLevel(z+1);let H=this.getXpForLevel(z),Q=this.getXpForLevel(z+1);return{level:z,xpInLevel:j-H,xpForNextLevel:Q-H}}getScoreMultiplier(j){for(let z of this.config.scoreThresholds)if(j>=z.min)return z.multiplier;return 1}getStreakBonus(j){for(let z of this.config.streakTiers)if(j>=z.days)return z.bonus;return 0}}var A={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 y(j,z){let V=q(z),H=A[j];return H?V.t(H):j}var _=new K;export{_ as xpEngine,y as getXpSourceLabel,K as XPEngine,Z as DEFAULT_XP_CONFIG};
2
+ import{defineTranslation as R}from"@contractspec/lib.contracts-spec/translations";var m=R({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 S}from"@contractspec/lib.contracts-spec/translations";var J=S({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\xF3n por puntuaci\xF3n",description:"XP breakdown label for score-based bonus"},"xp.source.perfectScore":{value:"Puntuaci\xF3n 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\xF3n por reintento",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonificaci\xF3n por racha",description:"XP breakdown label for streak bonus"}}});import{defineTranslation as h}from"@contractspec/lib.contracts-spec/translations";var Y=h({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\xE9nalit\xE9 de r\xE9essai",description:"XP breakdown label for retry penalty"},"xp.source.streakBonus":{value:"Bonus de s\xE9rie",description:"XP breakdown label for streak bonus"}}});import{createI18nFactory as G}from"@contractspec/lib.contracts-spec/translations";var $=G({specKey:"learning-journey.messages",catalogs:[m,Y,J]}),q=$.create,T=$.getDefault,E=$.resetRegistry;var Z={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 K{config;constructor(j={}){this.config={...Z,...j,baseValues:{...Z.baseValues,...j.baseValues},scoreThresholds:j.scoreThresholds||Z.scoreThresholds,streakTiers:j.streakTiers||Z.streakTiers}}calculate(j){let z=[],V=j.baseXp??this.config.baseValues[j.activity],H=V;if(z.push({source:"base",amount:V}),j.score!==void 0){let Q=this.getScoreMultiplier(j.score);if(Q!==1){let W=Math.round(V*(Q-1));H+=W,z.push({source:"score_bonus",amount:W,multiplier:Q})}if(j.score===100){let W=Math.round(V*(this.config.perfectScoreMultiplier-1));H+=W,z.push({source:"perfect_score",amount:W,multiplier:this.config.perfectScoreMultiplier})}}if(j.attemptNumber===1&&!j.isRetry)H+=this.config.firstAttemptBonus,z.push({source:"first_attempt",amount:this.config.firstAttemptBonus});if(j.isRetry){let Q=Math.round(H*(1-this.config.retryPenalty));H-=Q,z.push({source:"retry_penalty",amount:-Q,multiplier:this.config.retryPenalty})}if(j.currentStreak&&j.currentStreak>0){let Q=this.getStreakBonus(j.currentStreak);if(Q>0)H+=Q,z.push({source:"streak_bonus",amount:Q})}if(V>0)H=Math.max(1,H);return{totalXp:Math.round(H),baseXp:V,breakdown:z}}calculateStreakBonus(j){let z=this.getStreakBonus(j);return{totalXp:z,baseXp:z,breakdown:[{source:"streak_bonus",amount:z}]}}getXpForLevel(j){if(j<=1)return 0;return Math.round(100*Math.pow(j-1,1.5))}getLevelFromXp(j){let z=1,V=this.getXpForLevel(z+1);while(j>=V&&z<1000)z++,V=this.getXpForLevel(z+1);let H=this.getXpForLevel(z),Q=this.getXpForLevel(z+1);return{level:z,xpInLevel:j-H,xpForNextLevel:Q-H}}getScoreMultiplier(j){for(let z of this.config.scoreThresholds)if(j>=z.min)return z.multiplier;return 1}getStreakBonus(j){for(let z of this.config.streakTiers)if(j>=z.days)return z.bonus;return 0}}var A={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 y(j,z){let V=q(z),H=A[j];return H?V.t(H):j}var _=new K;export{_ as xpEngine,y as getXpSourceLabel,K as XPEngine,Z as DEFAULT_XP_CONFIG};
@@ -1,7 +1,7 @@
1
1
  import type { ModuleSchemaContribution } from '@contractspec/lib.schema';
2
2
  export * from './course';
3
3
  export * from './learner';
4
- export * from './onboarding';
4
+ export * from './journey';
5
5
  export * from './flashcard';
6
6
  export * from './quiz';
7
7
  export * from './gamification';
@@ -183,19 +183,12 @@ export declare const learningJourneyEntities: (import("@contractspec/lib.schema"
183
183
  description: import("@contractspec/lib.schema").EntityScalarField;
184
184
  targetUserSegment: import("@contractspec/lib.schema").EntityScalarField;
185
185
  targetRole: import("@contractspec/lib.schema").EntityScalarField;
186
- welcomeTitle: import("@contractspec/lib.schema").EntityScalarField;
187
- welcomeMessage: import("@contractspec/lib.schema").EntityScalarField;
188
- completionTitle: import("@contractspec/lib.schema").EntityScalarField;
189
- completionMessage: import("@contractspec/lib.schema").EntityScalarField;
190
186
  isActive: import("@contractspec/lib.schema").EntityScalarField;
191
187
  isRequired: import("@contractspec/lib.schema").EntityScalarField;
192
188
  canSkip: import("@contractspec/lib.schema").EntityScalarField;
193
189
  totalXp: import("@contractspec/lib.schema").EntityScalarField;
194
- completionXpBonus: import("@contractspec/lib.schema").EntityScalarField;
195
- completionBadgeKey: import("@contractspec/lib.schema").EntityScalarField;
196
- streakHoursWindow: import("@contractspec/lib.schema").EntityScalarField;
197
- streakBonusXp: import("@contractspec/lib.schema").EntityScalarField;
198
- orgId: import("@contractspec/lib.schema").EntityScalarField;
190
+ streakRule: import("@contractspec/lib.schema").EntityScalarField;
191
+ completionRewards: import("@contractspec/lib.schema").EntityScalarField;
199
192
  metadata: import("@contractspec/lib.schema").EntityScalarField;
200
193
  createdAt: import("@contractspec/lib.schema").EntityScalarField;
201
194
  updatedAt: import("@contractspec/lib.schema").EntityScalarField;
@@ -209,18 +202,17 @@ export declare const learningJourneyEntities: (import("@contractspec/lib.schema"
209
202
  instructions: import("@contractspec/lib.schema").EntityScalarField;
210
203
  helpUrl: import("@contractspec/lib.schema").EntityScalarField;
211
204
  order: import("@contractspec/lib.schema").EntityScalarField;
212
- triggerEvent: import("@contractspec/lib.schema").EntityScalarField;
213
- completionEvent: import("@contractspec/lib.schema").EntityScalarField;
214
- completionEventVersion: import("@contractspec/lib.schema").EntityScalarField;
215
- completionSourceModule: import("@contractspec/lib.schema").EntityScalarField;
216
- completionEventFilter: import("@contractspec/lib.schema").EntityScalarField;
217
- actionUrl: import("@contractspec/lib.schema").EntityScalarField;
218
- actionLabel: import("@contractspec/lib.schema").EntityScalarField;
219
- highlightSelector: import("@contractspec/lib.schema").EntityScalarField;
220
- tooltipPosition: import("@contractspec/lib.schema").EntityScalarField;
205
+ completion: import("@contractspec/lib.schema").EntityScalarField;
206
+ availability: import("@contractspec/lib.schema").EntityScalarField;
207
+ prerequisites: import("@contractspec/lib.schema").EntityScalarField;
208
+ prerequisiteMode: import("@contractspec/lib.schema").EntityScalarField;
209
+ branches: import("@contractspec/lib.schema").EntityScalarField;
210
+ reward: import("@contractspec/lib.schema").EntityScalarField;
221
211
  xpReward: import("@contractspec/lib.schema").EntityScalarField;
222
212
  isRequired: import("@contractspec/lib.schema").EntityScalarField;
223
213
  canSkip: import("@contractspec/lib.schema").EntityScalarField;
214
+ actionUrl: import("@contractspec/lib.schema").EntityScalarField;
215
+ actionLabel: import("@contractspec/lib.schema").EntityScalarField;
224
216
  metadata: import("@contractspec/lib.schema").EntityScalarField;
225
217
  createdAt: import("@contractspec/lib.schema").EntityScalarField;
226
218
  updatedAt: import("@contractspec/lib.schema").EntityScalarField;
@@ -229,32 +221,41 @@ export declare const learningJourneyEntities: (import("@contractspec/lib.schema"
229
221
  id: import("@contractspec/lib.schema").EntityScalarField;
230
222
  learnerId: import("@contractspec/lib.schema").EntityScalarField;
231
223
  trackId: import("@contractspec/lib.schema").EntityScalarField;
232
- currentStepId: import("@contractspec/lib.schema").EntityScalarField;
233
- completedSteps: import("@contractspec/lib.schema").EntityScalarField;
234
- skippedSteps: import("@contractspec/lib.schema").EntityScalarField;
235
- progress: import("@contractspec/lib.schema").EntityScalarField;
224
+ progressPercent: import("@contractspec/lib.schema").EntityScalarField;
236
225
  isCompleted: import("@contractspec/lib.schema").EntityScalarField;
226
+ nextStepId: import("@contractspec/lib.schema").EntityScalarField;
237
227
  xpEarned: import("@contractspec/lib.schema").EntityScalarField;
228
+ badges: import("@contractspec/lib.schema").EntityScalarField;
229
+ streakState: import("@contractspec/lib.schema").EntityScalarField;
230
+ eventLog: import("@contractspec/lib.schema").EntityScalarField;
231
+ completionRewardApplied: import("@contractspec/lib.schema").EntityScalarField;
238
232
  startedAt: import("@contractspec/lib.schema").EntityScalarField;
239
233
  completedAt: import("@contractspec/lib.schema").EntityScalarField;
240
234
  lastActivityAt: import("@contractspec/lib.schema").EntityScalarField;
241
- isDismissed: import("@contractspec/lib.schema").EntityScalarField;
242
- dismissedAt: import("@contractspec/lib.schema").EntityScalarField;
243
- metadata: import("@contractspec/lib.schema").EntityScalarField;
244
235
  createdAt: import("@contractspec/lib.schema").EntityScalarField;
245
236
  updatedAt: import("@contractspec/lib.schema").EntityScalarField;
246
237
  learner: import("@contractspec/lib.schema").EntityRelationField;
247
238
  track: import("@contractspec/lib.schema").EntityRelationField;
239
+ stepProgress: import("@contractspec/lib.schema").EntityRelationField;
248
240
  }> | import("@contractspec/lib.schema").EntitySpec<{
249
241
  id: import("@contractspec/lib.schema").EntityScalarField;
250
242
  progressId: import("@contractspec/lib.schema").EntityScalarField;
251
243
  stepId: import("@contractspec/lib.schema").EntityScalarField;
252
244
  status: import("@contractspec/lib.schema").EntityEnumField;
245
+ selectedBranchKey: import("@contractspec/lib.schema").EntityScalarField;
253
246
  xpEarned: import("@contractspec/lib.schema").EntityScalarField;
247
+ occurrences: import("@contractspec/lib.schema").EntityScalarField;
248
+ masteryCount: import("@contractspec/lib.schema").EntityScalarField;
249
+ availableAt: import("@contractspec/lib.schema").EntityScalarField;
250
+ dueAt: import("@contractspec/lib.schema").EntityScalarField;
251
+ completedAt: import("@contractspec/lib.schema").EntityScalarField;
252
+ skippedAt: import("@contractspec/lib.schema").EntityScalarField;
253
+ blockedAt: import("@contractspec/lib.schema").EntityScalarField;
254
+ missedAt: import("@contractspec/lib.schema").EntityScalarField;
254
255
  triggeringEvent: import("@contractspec/lib.schema").EntityScalarField;
255
256
  eventPayload: import("@contractspec/lib.schema").EntityScalarField;
256
- completedAt: import("@contractspec/lib.schema").EntityScalarField;
257
257
  createdAt: import("@contractspec/lib.schema").EntityScalarField;
258
+ updatedAt: import("@contractspec/lib.schema").EntityScalarField;
258
259
  progress: import("@contractspec/lib.schema").EntityRelationField;
259
260
  step: import("@contractspec/lib.schema").EntityRelationField;
260
261
  }> | import("@contractspec/lib.schema").EntitySpec<{