@vue-skuilder/db 0.1.17 → 0.1.18

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 (80) hide show
  1. package/dist/{userDB-DNa0XPtn.d.ts → classroomDB-BgfrVb8d.d.ts} +357 -103
  2. package/dist/{userDB-BqwxtJ_7.d.mts → classroomDB-CTOenngH.d.cts} +358 -104
  3. package/dist/core/index.d.cts +230 -0
  4. package/dist/core/index.d.ts +161 -23
  5. package/dist/core/index.js +1964 -154
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +1925 -121
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-BV5iZqt_.d.ts → dataLayerProvider-CZxC9GtB.d.ts} +1 -1
  10. package/dist/{dataLayerProvider-VlngD19_.d.mts → dataLayerProvider-D6PoCwS6.d.cts} +1 -1
  11. package/dist/impl/couch/{index.d.mts → index.d.cts} +46 -5
  12. package/dist/impl/couch/index.d.ts +44 -3
  13. package/dist/impl/couch/index.js +1971 -171
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +1933 -134
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/{index.d.mts → index.d.cts} +5 -6
  18. package/dist/impl/static/index.d.ts +2 -3
  19. package/dist/impl/static/index.js +1614 -119
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +1585 -92
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/{index-Bmll7Xse.d.mts → index-D-Fa4Smt.d.cts} +1 -1
  24. package/dist/{index.d.mts → index.d.cts} +97 -13
  25. package/dist/index.d.ts +90 -6
  26. package/dist/index.js +2085 -153
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +2031 -106
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/pouch/index.js +3 -3
  31. package/dist/{types-Dbp5DaRR.d.mts → types-CzPDLAK6.d.cts} +1 -1
  32. package/dist/util/packer/{index.d.mts → index.d.cts} +3 -3
  33. package/dist/util/packer/index.js.map +1 -1
  34. package/dist/util/packer/index.mjs.map +1 -1
  35. package/docs/brainstorm-navigation-paradigm.md +369 -0
  36. package/docs/navigators-architecture.md +265 -0
  37. package/docs/todo-evolutionary-orchestration.md +310 -0
  38. package/docs/todo-nominal-tag-types.md +121 -0
  39. package/docs/todo-pipeline-optimization.md +117 -0
  40. package/docs/todo-strategy-authoring.md +401 -0
  41. package/docs/todo-strategy-state-storage.md +278 -0
  42. package/eslint.config.mjs +1 -1
  43. package/package.json +9 -4
  44. package/src/core/interfaces/contentSource.ts +88 -4
  45. package/src/core/interfaces/navigationStrategyManager.ts +0 -5
  46. package/src/core/navigators/CompositeGenerator.ts +268 -0
  47. package/src/core/navigators/Pipeline.ts +205 -0
  48. package/src/core/navigators/PipelineAssembler.ts +194 -0
  49. package/src/core/navigators/elo.ts +104 -15
  50. package/src/core/navigators/filters/eloDistance.ts +132 -0
  51. package/src/core/navigators/filters/index.ts +6 -0
  52. package/src/core/navigators/filters/types.ts +115 -0
  53. package/src/core/navigators/generators/index.ts +2 -0
  54. package/src/core/navigators/generators/types.ts +107 -0
  55. package/src/core/navigators/hardcodedOrder.ts +111 -12
  56. package/src/core/navigators/hierarchyDefinition.ts +266 -0
  57. package/src/core/navigators/index.ts +345 -3
  58. package/src/core/navigators/interferenceMitigator.ts +367 -0
  59. package/src/core/navigators/relativePriority.ts +267 -0
  60. package/src/core/navigators/srs.ts +195 -0
  61. package/src/impl/couch/classroomDB.ts +51 -0
  62. package/src/impl/couch/courseDB.ts +117 -39
  63. package/src/impl/static/courseDB.ts +0 -4
  64. package/src/study/SessionController.ts +149 -1
  65. package/src/study/TagFilteredContentSource.ts +255 -0
  66. package/src/study/index.ts +1 -0
  67. package/src/util/dataDirectory.test.ts +51 -22
  68. package/src/util/logger.ts +0 -1
  69. package/tests/core/navigators/CompositeGenerator.test.ts +455 -0
  70. package/tests/core/navigators/Pipeline.test.ts +405 -0
  71. package/tests/core/navigators/PipelineAssembler.test.ts +351 -0
  72. package/tests/core/navigators/SRSNavigator.test.ts +344 -0
  73. package/tests/core/navigators/eloDistanceFilter.test.ts +192 -0
  74. package/tests/core/navigators/navigators.test.ts +710 -0
  75. package/tsconfig.json +1 -1
  76. package/vitest.config.ts +29 -0
  77. package/dist/core/index.d.mts +0 -92
  78. /package/dist/{SyncStrategy-CyATpyLQ.d.mts → SyncStrategy-CyATpyLQ.d.cts} +0 -0
  79. /package/dist/pouch/{index.d.mts → index.d.cts} +0 -0
  80. /package/dist/{types-legacy-6ettoclI.d.mts → types-legacy-6ettoclI.d.cts} +0 -0
@@ -0,0 +1,192 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { createEloDistanceFilter } from '../../../src/core/navigators/filters/eloDistance';
3
+ import { WeightedCard } from '../../../src/core/navigators/index';
4
+ import { FilterContext } from '../../../src/core/navigators/filters/types';
5
+ import { CourseDBInterface } from '../../../src/core/interfaces/courseDB';
6
+ import { UserDBInterface } from '../../../src/core/interfaces/userDB';
7
+
8
+ /**
9
+ * Helper to create a weighted card for testing
10
+ */
11
+ function createCard(id: string, score: number): WeightedCard {
12
+ return {
13
+ cardId: id,
14
+ courseId: 'test-course',
15
+ score,
16
+ provenance: [
17
+ {
18
+ strategy: 'test',
19
+ strategyName: 'Test',
20
+ strategyId: 'TEST',
21
+ action: 'generated',
22
+ score,
23
+ reason: 'Test card',
24
+ },
25
+ ],
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Create mock context with specified user ELO and card ELO responses
31
+ */
32
+ function createMockContext(userElo: number, cardEloMap: Record<string, number>): FilterContext {
33
+ const mockCourse = {
34
+ getCourseID: vi.fn().mockReturnValue('test-course'),
35
+ getCardEloData: vi.fn().mockImplementation((cardIds: string[]) => {
36
+ return Promise.resolve(
37
+ cardIds.map((id) => ({
38
+ global: { score: cardEloMap[id] ?? 1000, count: 10 },
39
+ tags: {},
40
+ }))
41
+ );
42
+ }),
43
+ } as unknown as CourseDBInterface;
44
+
45
+ const mockUser = {} as unknown as UserDBInterface;
46
+
47
+ return {
48
+ user: mockUser,
49
+ course: mockCourse,
50
+ userElo,
51
+ };
52
+ }
53
+
54
+ describe('ELO Distance Filter', () => {
55
+ describe('smooth curve behavior', () => {
56
+ it('should return ~1.0 multiplier for exact ELO match', async () => {
57
+ const filter = createEloDistanceFilter();
58
+ const cards = [createCard('card-1', 1.0)];
59
+ const context = createMockContext(1000, { 'card-1': 1000 });
60
+
61
+ const result = await filter.transform(cards, context);
62
+
63
+ expect(result[0].score).toBeCloseTo(1.0, 2);
64
+ });
65
+
66
+ it('should return ~0.6 multiplier at halfLife distance (200 ELO)', async () => {
67
+ const filter = createEloDistanceFilter();
68
+ const cards = [createCard('card-1', 1.0)];
69
+ const context = createMockContext(1000, { 'card-1': 1200 }); // 200 distance
70
+
71
+ const result = await filter.transform(cards, context);
72
+
73
+ // At halfLife: minMultiplier + (maxMultiplier - minMultiplier) * exp(-1)
74
+ // = 0.3 + 0.7 * 0.368 ≈ 0.56
75
+ expect(result[0].score).toBeCloseTo(0.56, 1);
76
+ });
77
+
78
+ it('should approach minMultiplier for very large distances', async () => {
79
+ const filter = createEloDistanceFilter();
80
+ const cards = [createCard('card-1', 1.0)];
81
+ const context = createMockContext(1000, { 'card-1': 2000 }); // 1000 distance (5x halfLife)
82
+
83
+ const result = await filter.transform(cards, context);
84
+
85
+ // Should be very close to minMultiplier (0.3)
86
+ expect(result[0].score).toBeCloseTo(0.3, 1);
87
+ });
88
+
89
+ it('should be symmetric - same penalty above and below user ELO', async () => {
90
+ const filter = createEloDistanceFilter();
91
+ const cards = [createCard('above', 1.0), createCard('below', 1.0)];
92
+ const context = createMockContext(1000, {
93
+ above: 1150, // 150 above
94
+ below: 850, // 150 below
95
+ });
96
+
97
+ const result = await filter.transform(cards, context);
98
+
99
+ expect(result[0].score).toBeCloseTo(result[1].score, 4);
100
+ });
101
+
102
+ it('should produce monotonically decreasing scores with increasing distance', async () => {
103
+ const filter = createEloDistanceFilter();
104
+ const cards = [
105
+ createCard('d0', 1.0),
106
+ createCard('d100', 1.0),
107
+ createCard('d200', 1.0),
108
+ createCard('d300', 1.0),
109
+ createCard('d400', 1.0),
110
+ ];
111
+ const context = createMockContext(1000, {
112
+ d0: 1000,
113
+ d100: 1100,
114
+ d200: 1200,
115
+ d300: 1300,
116
+ d400: 1400,
117
+ });
118
+
119
+ const result = await filter.transform(cards, context);
120
+
121
+ // Each should be less than the previous (smooth decay)
122
+ for (let i = 1; i < result.length; i++) {
123
+ expect(result[i].score).toBeLessThan(result[i - 1].score);
124
+ }
125
+ });
126
+
127
+ it('should have no discontinuities - adjacent distances should have similar scores', async () => {
128
+ const filter = createEloDistanceFilter();
129
+ const cards = [createCard('d99', 1.0), createCard('d100', 1.0), createCard('d101', 1.0)];
130
+ const context = createMockContext(1000, {
131
+ d99: 1099,
132
+ d100: 1100,
133
+ d101: 1101,
134
+ });
135
+
136
+ const result = await filter.transform(cards, context);
137
+
138
+ // Adjacent scores should differ by less than 1%
139
+ const diff1 = Math.abs(result[0].score - result[1].score);
140
+ const diff2 = Math.abs(result[1].score - result[2].score);
141
+
142
+ expect(diff1).toBeLessThan(0.01);
143
+ expect(diff2).toBeLessThan(0.01);
144
+ });
145
+ });
146
+
147
+ describe('provenance tracking', () => {
148
+ it('should append provenance entry with ELO details', async () => {
149
+ const filter = createEloDistanceFilter();
150
+ const cards = [createCard('card-1', 0.8)];
151
+ const context = createMockContext(1000, { 'card-1': 1200 });
152
+
153
+ const result = await filter.transform(cards, context);
154
+
155
+ expect(result[0].provenance).toHaveLength(2);
156
+ const entry = result[0].provenance[1];
157
+ expect(entry.strategy).toBe('eloDistance');
158
+ expect(entry.action).toBe('penalized');
159
+ expect(entry.reason).toContain('200');
160
+ });
161
+ });
162
+
163
+ describe('custom configuration', () => {
164
+ it('should use custom halfLife when provided', async () => {
165
+ // With halfLife=100, distance 100 should give ~0.56 multiplier
166
+ const filter = createEloDistanceFilter({ halfLife: 100 });
167
+ const cards = [createCard('card-1', 1.0)];
168
+ const context = createMockContext(1000, { 'card-1': 1100 }); // 100 distance
169
+
170
+ const result = await filter.transform(cards, context);
171
+
172
+ expect(result[0].score).toBeCloseTo(0.56, 1);
173
+ });
174
+
175
+ it('should use custom min/max multipliers when provided', async () => {
176
+ const filter = createEloDistanceFilter({
177
+ minMultiplier: 0.5,
178
+ maxMultiplier: 0.9,
179
+ });
180
+ const cards = [createCard('exact', 1.0), createCard('far', 1.0)];
181
+ const context = createMockContext(1000, {
182
+ exact: 1000, // 0 distance
183
+ far: 2000, // very far
184
+ });
185
+
186
+ const result = await filter.transform(cards, context);
187
+
188
+ expect(result[0].score).toBeCloseTo(0.9, 2); // maxMultiplier
189
+ expect(result[1].score).toBeCloseTo(0.5, 1); // approaches minMultiplier
190
+ });
191
+ });
192
+ });