@vue-skuilder/db 0.2.5 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.d.cts +75 -1
- package/dist/core/index.d.ts +75 -1
- package/dist/core/index.js +281 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +277 -4
- package/dist/core/index.mjs.map +1 -1
- package/dist/impl/couch/index.js +273 -4
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +273 -4
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.js +273 -4
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +273 -4
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +307 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +303 -7
- package/dist/index.mjs.map +1 -1
- package/docs/navigators-architecture.md +42 -2
- package/package.json +3 -3
- package/src/core/navigators/Pipeline.ts +103 -2
- package/src/core/navigators/PipelineDebugger.ts +11 -1
- package/src/core/navigators/diversityRerank.ts +185 -0
- package/src/core/navigators/generators/prescribed.ts +173 -1
- package/src/core/navigators/index.ts +12 -0
- package/src/study/ItemQueue.test.ts +71 -0
- package/src/study/ItemQueue.ts +19 -1
- package/src/study/SessionController.ts +20 -5
|
@@ -5,17 +5,29 @@ export type { CardFilter, FilterContext, CardFilterFactory } from './filters/typ
|
|
|
5
5
|
|
|
6
6
|
// Re-export generator types
|
|
7
7
|
export type { CardGenerator, GeneratorContext, CardGeneratorFactory, GeneratorResult, ReplanHints } from './generators/types';
|
|
8
|
+
|
|
9
|
+
// Re-export the diversity re-rank stage (pipeline stage 3)
|
|
10
|
+
export {
|
|
11
|
+
diversityRerank,
|
|
12
|
+
DIVERSITY_STRENGTH,
|
|
13
|
+
DIVERSITY_FLOOR,
|
|
14
|
+
type DiversityRerankOptions,
|
|
15
|
+
} from './diversityRerank';
|
|
8
16
|
import type { GeneratorResult, ReplanHints } from './generators/types';
|
|
9
17
|
|
|
10
18
|
// Re-export pipeline debugger API
|
|
11
19
|
export {
|
|
12
20
|
pipelineDebugAPI,
|
|
13
21
|
mountPipelineDebugger,
|
|
22
|
+
getActivePipeline,
|
|
14
23
|
type PipelineRunReport,
|
|
15
24
|
type GeneratorSummary,
|
|
16
25
|
type FilterImpact,
|
|
17
26
|
} from './PipelineDebugger';
|
|
18
27
|
|
|
28
|
+
// Re-export the commit-free forecast capability surface.
|
|
29
|
+
export type { PipelineForecaster } from './Pipeline';
|
|
30
|
+
|
|
19
31
|
import { LearnableWeight } from '../types/contentNavigationStrategy';
|
|
20
32
|
export type { ContentNavigationStrategyData, LearnableWeight } from '../types/contentNavigationStrategy';
|
|
21
33
|
import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ItemQueue } from './ItemQueue';
|
|
3
|
+
|
|
4
|
+
type Item = { cardID: string };
|
|
5
|
+
const id = (i: Item) => i.cardID;
|
|
6
|
+
const item = (cardID: string): Item => ({ cardID });
|
|
7
|
+
const ids = (q: ItemQueue<Item>): string[] =>
|
|
8
|
+
Array.from({ length: q.length }, (_, i) => q.peek(i).cardID);
|
|
9
|
+
|
|
10
|
+
describe('ItemQueue.mergeToFront', () => {
|
|
11
|
+
it('adds new items to the front, preserving batch order', () => {
|
|
12
|
+
const q = new ItemQueue<Item>();
|
|
13
|
+
q.addAll([item('a'), item('b')], id);
|
|
14
|
+
|
|
15
|
+
const added = q.mergeToFront([item('x'), item('y')], id);
|
|
16
|
+
|
|
17
|
+
expect(added).toBe(2);
|
|
18
|
+
expect(ids(q)).toEqual(['x', 'y', 'a', 'b']);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('skips an ordinary duplicate, leaving it in place', () => {
|
|
22
|
+
const q = new ItemQueue<Item>();
|
|
23
|
+
q.addAll([item('a'), item('b'), item('c')], id);
|
|
24
|
+
|
|
25
|
+
// 'b' already queued and not mandatory → left where it is; 'x' fronted.
|
|
26
|
+
const added = q.mergeToFront([item('x'), item('b')], id);
|
|
27
|
+
|
|
28
|
+
expect(added).toBe(1);
|
|
29
|
+
expect(ids(q)).toEqual(['x', 'a', 'b', 'c']);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('re-fronts an already-queued mandatory card instead of burying it', () => {
|
|
33
|
+
// Repro of the require-card burial: 'req' was fronted by a prior burst
|
|
34
|
+
// replan, then an additive merge brings fresh non-required cards. Without
|
|
35
|
+
// the mandatory re-front, 'x'/'y' would leapfrog 'req' and sink it.
|
|
36
|
+
const q = new ItemQueue<Item>();
|
|
37
|
+
q.addAll([item('req'), item('a'), item('b')], id);
|
|
38
|
+
|
|
39
|
+
const added = q.mergeToFront(
|
|
40
|
+
[item('req'), item('x'), item('y')],
|
|
41
|
+
id,
|
|
42
|
+
new Set(['req'])
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// 'req' is not a *new* add, so it isn't counted...
|
|
46
|
+
expect(added).toBe(2);
|
|
47
|
+
// ...but it leads the queue (ahead of the freshly merged 'x'/'y').
|
|
48
|
+
expect(ids(q)).toEqual(['req', 'x', 'y', 'a', 'b']);
|
|
49
|
+
// and isn't duplicated.
|
|
50
|
+
expect(ids(q).filter((c) => c === 'req')).toHaveLength(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('keeps a mandatory card already at the front at the front', () => {
|
|
54
|
+
const q = new ItemQueue<Item>();
|
|
55
|
+
q.addAll([item('req'), item('a')], id);
|
|
56
|
+
|
|
57
|
+
q.mergeToFront([item('req'), item('x')], id, new Set(['req']));
|
|
58
|
+
|
|
59
|
+
expect(ids(q)).toEqual(['req', 'x', 'a']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('without forceFrontIds, preserves the legacy skip-duplicate behavior', () => {
|
|
63
|
+
const q = new ItemQueue<Item>();
|
|
64
|
+
q.addAll([item('req'), item('a')], id);
|
|
65
|
+
|
|
66
|
+
// No mandatory set → 'req' stays put and is buried behind the merged 'x'.
|
|
67
|
+
q.mergeToFront([item('req'), item('x')], id);
|
|
68
|
+
|
|
69
|
+
expect(ids(q)).toEqual(['x', 'req', 'a']);
|
|
70
|
+
});
|
|
71
|
+
});
|
package/src/study/ItemQueue.ts
CHANGED
|
@@ -73,8 +73,21 @@ export class ItemQueue<T> {
|
|
|
73
73
|
* Merge new items into the front of the queue, skipping duplicates.
|
|
74
74
|
* Used by additive replans to inject high-quality candidates without
|
|
75
75
|
* discarding the existing queue contents.
|
|
76
|
+
*
|
|
77
|
+
* `forceFrontIds` carries the mandatory (`+INF`) cards in this batch — a
|
|
78
|
+
* durable `requireCard`/`requireTag` re-asserted by every replan. An ordinary
|
|
79
|
+
* duplicate is left in place (skip), but a mandatory one that's *already*
|
|
80
|
+
* queued is pulled out of its current slot so it rejoins at the front in batch
|
|
81
|
+
* order. Without this, an additive merge unshifts fresh non-required cards
|
|
82
|
+
* ahead of an already-present required card, steadily burying it until it never
|
|
83
|
+
* gets drawn — defeating the "must appear" guarantee. Returns the count of
|
|
84
|
+
* genuinely new cards added (re-fronted duplicates are not counted).
|
|
76
85
|
*/
|
|
77
|
-
public mergeToFront(
|
|
86
|
+
public mergeToFront(
|
|
87
|
+
items: T[],
|
|
88
|
+
cardIdExtractor: (item: T) => string,
|
|
89
|
+
forceFrontIds?: ReadonlySet<string>
|
|
90
|
+
): number {
|
|
78
91
|
let added = 0;
|
|
79
92
|
const toInsert: T[] = [];
|
|
80
93
|
for (const item of items) {
|
|
@@ -83,6 +96,11 @@ export class ItemQueue<T> {
|
|
|
83
96
|
this.seenCardIds.push(cardId);
|
|
84
97
|
toInsert.push(item);
|
|
85
98
|
added++;
|
|
99
|
+
} else if (forceFrontIds?.has(cardId)) {
|
|
100
|
+
const idx = this.q.findIndex((qi) => cardIdExtractor(qi) === cardId);
|
|
101
|
+
if (idx >= 0) {
|
|
102
|
+
toInsert.push(...this.q.splice(idx, 1));
|
|
103
|
+
}
|
|
86
104
|
}
|
|
87
105
|
}
|
|
88
106
|
this.q.unshift(...toInsert);
|
|
@@ -1161,9 +1161,22 @@ export class SessionController<TView = unknown> extends Loggable {
|
|
|
1161
1161
|
// additive merge, or a stale generator candidate. This is the general guard
|
|
1162
1162
|
// (see _servedCardIds); it makes re-presentation structurally impossible
|
|
1163
1163
|
// rather than relying on each upstream path to exclude correctly.
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1164
|
+
const newCandidates = mixedWeighted.filter(
|
|
1165
|
+
(w) => getCardOrigin(w) === 'new' && !this._servedCardIds.has(w.cardId)
|
|
1166
|
+
);
|
|
1167
|
+
// `+INF` is the hard "include at all costs" sentinel applied by require*
|
|
1168
|
+
// injection (see Pipeline.applyRequirement). Partition these mandatory cards
|
|
1169
|
+
// to the front and exempt them from the newLimit slice, so neither the
|
|
1170
|
+
// mixer's source-shuffle/round-robin nor the cap can bury or drop a required
|
|
1171
|
+
// card before it reaches newQ. The set is also handed to mergeToFront so an
|
|
1172
|
+
// already-queued required card gets re-fronted rather than leapfrogged.
|
|
1173
|
+
const mandatoryWeighted = newCandidates.filter((w) => w.score === Number.POSITIVE_INFINITY);
|
|
1174
|
+
const optionalWeighted = newCandidates.filter((w) => w.score !== Number.POSITIVE_INFINITY);
|
|
1175
|
+
const newWeighted = [
|
|
1176
|
+
...mandatoryWeighted,
|
|
1177
|
+
...optionalWeighted.slice(0, Math.max(0, newLimit - mandatoryWeighted.length)),
|
|
1178
|
+
];
|
|
1179
|
+
const mandatoryIds = new Set(mandatoryWeighted.map((w) => w.cardId));
|
|
1167
1180
|
|
|
1168
1181
|
logger.debug(`[reviews] got ${reviewWeighted.length} reviews from mixer`);
|
|
1169
1182
|
|
|
@@ -1206,8 +1219,10 @@ export class SessionController<TView = unknown> extends Loggable {
|
|
|
1206
1219
|
}
|
|
1207
1220
|
|
|
1208
1221
|
if (additive) {
|
|
1209
|
-
// Additive replan: merge new candidates into front of existing queue
|
|
1210
|
-
|
|
1222
|
+
// Additive replan: merge new candidates into front of existing queue.
|
|
1223
|
+
// Pass mandatory (+INF) ids so an already-queued required card is pulled
|
|
1224
|
+
// back to the front instead of being buried by fresh non-required cards.
|
|
1225
|
+
const added = this.newQ.mergeToFront(newItems, (item) => item.cardID, mandatoryIds);
|
|
1211
1226
|
report += `Additive merge: ${added} new cards added to front of newQ\n`;
|
|
1212
1227
|
} else if (replan) {
|
|
1213
1228
|
// Atomic swap: replace entire newQ contents at once (no empty-queue window)
|