@vue-skuilder/db 0.2.5 → 0.2.7

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.
@@ -304,6 +304,42 @@ interface CardFilter {
304
304
  */
305
305
  type CardFilterFactory<TConfig = unknown> = (config: TConfig) => CardFilter;
306
306
 
307
+ interface DiversityRerankOptions {
308
+ /**
309
+ * How hard repetition is penalised. Larger → steeper demotion of repeated
310
+ * distinctive tags. Penalty = 1 / (1 + strength·load).
311
+ */
312
+ strength?: number;
313
+ /**
314
+ * Minimum penalty multiplier. A card is never demoted below `floor × score`,
315
+ * however much it repeats. Keeps a strong-but-repeated card from being driven
316
+ * under downstream "well-indicated" thresholds (which would mislabel it as
317
+ * filler and could trigger spurious quality-replans). Tunes "perturb ordering"
318
+ * vs "annihilate candidates."
319
+ */
320
+ floor?: number;
321
+ }
322
+ /** Default repetition strength. See DiversityRerankOptions.strength. */
323
+ declare const DIVERSITY_STRENGTH = 0.6;
324
+ /** Default penalty floor. See DiversityRerankOptions.floor. */
325
+ declare const DIVERSITY_FLOOR = 0.3;
326
+ /**
327
+ * Re-rank a scored candidate pool for answer/concept variety.
328
+ *
329
+ * Pure: returns a new array (diversified order, adjusted scores, appended
330
+ * provenance) and does not mutate the input cards. Cards entering are assumed
331
+ * to have score > 0 (the Pipeline strips zero-score cards before this stage).
332
+ * Non-finite scores (mandatory `requireCards`, score = +Infinity) are emitted
333
+ * untouched and still count toward repetition for later cards.
334
+ *
335
+ * @param cards - Post-filter, post-hint candidates.
336
+ * @param opts - Optional strength/floor overrides (defaults are sane and
337
+ * course-general; promote to strategy config if you ever want
338
+ * this learnable under the orchestration layer).
339
+ * @returns Cards in diversified order with penalised scores.
340
+ */
341
+ declare function diversityRerank(cards: WeightedCard[], opts?: DiversityRerankOptions): WeightedCard[];
342
+
307
343
  /**
308
344
  * Diagnosis of the full card space for the current user.
309
345
  */
@@ -706,4 +742,4 @@ declare const userDBDebugAPI: {
706
742
  */
707
743
  declare function mountUserDBDebugger(): void;
708
744
 
709
- export { type BulkCardProcessorConfig, type CardFilter, type CardFilterFactory, CardHistory, CardRecord, CourseDBInterface, DocType, DocTypePrefixes, type FilterContext, type FilterImpact, type GeneratorSummary, type GradientObservation, type GradientResult, type ImportResult, LearnableWeight, Loggable, OrchestrationContext, type PeriodUpdateInput, type PeriodUpdateResult, type PipelineRunReport, QuestionRecord, ReplanHints, type SignalConfig, StrategyContribution, type StrategyLearningState, type StrategyStateDoc, type StrategyStateId, UserDBInterface, UserOutcomeRecord, WeightedCard, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, computeOutcomeSignal, computeStrategyGradient, docIsDeleted, getCardHistoryID, getDefaultLearnableWeight, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig };
745
+ export { type BulkCardProcessorConfig, type CardFilter, type CardFilterFactory, CardHistory, CardRecord, CourseDBInterface, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, type DiversityRerankOptions, DocType, DocTypePrefixes, type FilterContext, type FilterImpact, type GeneratorSummary, type GradientObservation, type GradientResult, type ImportResult, LearnableWeight, Loggable, OrchestrationContext, type PeriodUpdateInput, type PeriodUpdateResult, type PipelineRunReport, QuestionRecord, ReplanHints, type SignalConfig, StrategyContribution, type StrategyLearningState, type StrategyStateDoc, type StrategyStateId, UserDBInterface, UserOutcomeRecord, WeightedCard, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getCardHistoryID, getDefaultLearnableWeight, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig };
@@ -304,6 +304,42 @@ interface CardFilter {
304
304
  */
305
305
  type CardFilterFactory<TConfig = unknown> = (config: TConfig) => CardFilter;
306
306
 
307
+ interface DiversityRerankOptions {
308
+ /**
309
+ * How hard repetition is penalised. Larger → steeper demotion of repeated
310
+ * distinctive tags. Penalty = 1 / (1 + strength·load).
311
+ */
312
+ strength?: number;
313
+ /**
314
+ * Minimum penalty multiplier. A card is never demoted below `floor × score`,
315
+ * however much it repeats. Keeps a strong-but-repeated card from being driven
316
+ * under downstream "well-indicated" thresholds (which would mislabel it as
317
+ * filler and could trigger spurious quality-replans). Tunes "perturb ordering"
318
+ * vs "annihilate candidates."
319
+ */
320
+ floor?: number;
321
+ }
322
+ /** Default repetition strength. See DiversityRerankOptions.strength. */
323
+ declare const DIVERSITY_STRENGTH = 0.6;
324
+ /** Default penalty floor. See DiversityRerankOptions.floor. */
325
+ declare const DIVERSITY_FLOOR = 0.3;
326
+ /**
327
+ * Re-rank a scored candidate pool for answer/concept variety.
328
+ *
329
+ * Pure: returns a new array (diversified order, adjusted scores, appended
330
+ * provenance) and does not mutate the input cards. Cards entering are assumed
331
+ * to have score > 0 (the Pipeline strips zero-score cards before this stage).
332
+ * Non-finite scores (mandatory `requireCards`, score = +Infinity) are emitted
333
+ * untouched and still count toward repetition for later cards.
334
+ *
335
+ * @param cards - Post-filter, post-hint candidates.
336
+ * @param opts - Optional strength/floor overrides (defaults are sane and
337
+ * course-general; promote to strategy config if you ever want
338
+ * this learnable under the orchestration layer).
339
+ * @returns Cards in diversified order with penalised scores.
340
+ */
341
+ declare function diversityRerank(cards: WeightedCard[], opts?: DiversityRerankOptions): WeightedCard[];
342
+
307
343
  /**
308
344
  * Diagnosis of the full card space for the current user.
309
345
  */
@@ -706,4 +742,4 @@ declare const userDBDebugAPI: {
706
742
  */
707
743
  declare function mountUserDBDebugger(): void;
708
744
 
709
- export { type BulkCardProcessorConfig, type CardFilter, type CardFilterFactory, CardHistory, CardRecord, CourseDBInterface, DocType, DocTypePrefixes, type FilterContext, type FilterImpact, type GeneratorSummary, type GradientObservation, type GradientResult, type ImportResult, LearnableWeight, Loggable, OrchestrationContext, type PeriodUpdateInput, type PeriodUpdateResult, type PipelineRunReport, QuestionRecord, ReplanHints, type SignalConfig, StrategyContribution, type StrategyLearningState, type StrategyStateDoc, type StrategyStateId, UserDBInterface, UserOutcomeRecord, WeightedCard, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, computeOutcomeSignal, computeStrategyGradient, docIsDeleted, getCardHistoryID, getDefaultLearnableWeight, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig };
745
+ export { type BulkCardProcessorConfig, type CardFilter, type CardFilterFactory, CardHistory, CardRecord, CourseDBInterface, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, type DiversityRerankOptions, DocType, DocTypePrefixes, type FilterContext, type FilterImpact, type GeneratorSummary, type GradientObservation, type GradientResult, type ImportResult, LearnableWeight, Loggable, OrchestrationContext, type PeriodUpdateInput, type PeriodUpdateResult, type PipelineRunReport, QuestionRecord, ReplanHints, type SignalConfig, StrategyContribution, type StrategyLearningState, type StrategyStateDoc, type StrategyStateId, UserDBInterface, UserOutcomeRecord, WeightedCard, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getCardHistoryID, getDefaultLearnableWeight, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig };
@@ -719,6 +719,95 @@ var init_courseLookupDB = __esm({
719
719
  }
720
720
  });
721
721
 
722
+ // src/core/navigators/diversityRerank.ts
723
+ var diversityRerank_exports = {};
724
+ __export(diversityRerank_exports, {
725
+ DIVERSITY_FLOOR: () => DIVERSITY_FLOOR,
726
+ DIVERSITY_STRENGTH: () => DIVERSITY_STRENGTH,
727
+ diversityRerank: () => diversityRerank
728
+ });
729
+ function diversityRerank(cards, opts = {}) {
730
+ const strength = opts.strength ?? DIVERSITY_STRENGTH;
731
+ const floor = opts.floor ?? DIVERSITY_FLOOR;
732
+ const n = cards.length;
733
+ if (n <= 1) return cards;
734
+ const df = /* @__PURE__ */ new Map();
735
+ for (const card of cards) {
736
+ for (const tag of card.tags ?? []) {
737
+ df.set(tag, (df.get(tag) ?? 0) + 1);
738
+ }
739
+ }
740
+ const idf = /* @__PURE__ */ new Map();
741
+ for (const [tag, freq] of df) {
742
+ idf.set(tag, Math.log(n / freq));
743
+ }
744
+ const remaining = [...cards];
745
+ const emittedCount = /* @__PURE__ */ new Map();
746
+ const out = [];
747
+ const repetitionLoad = (card) => {
748
+ let load = 0;
749
+ for (const tag of card.tags ?? []) {
750
+ const seen = emittedCount.get(tag);
751
+ if (seen) load += (idf.get(tag) ?? 0) * seen;
752
+ }
753
+ return load;
754
+ };
755
+ while (remaining.length > 0) {
756
+ let bestIdx = 0;
757
+ let bestValue = -Infinity;
758
+ let bestPenalty = 1;
759
+ let bestLoad = 0;
760
+ for (let i = 0; i < remaining.length; i++) {
761
+ const card = remaining[i];
762
+ const load = repetitionLoad(card);
763
+ const penalty = load > 0 ? Math.max(floor, 1 / (1 + strength * load)) : 1;
764
+ const value = card.score * penalty;
765
+ if (value > bestValue) {
766
+ bestValue = value;
767
+ bestIdx = i;
768
+ bestPenalty = penalty;
769
+ bestLoad = load;
770
+ }
771
+ }
772
+ const [picked] = remaining.splice(bestIdx, 1);
773
+ if (Number.isFinite(picked.score) && bestPenalty < 1) {
774
+ const newScore = picked.score * bestPenalty;
775
+ out.push({
776
+ ...picked,
777
+ score: newScore,
778
+ provenance: [
779
+ ...picked.provenance,
780
+ {
781
+ strategy: STRATEGY,
782
+ strategyId: STRATEGY_ID,
783
+ strategyName: STRATEGY_NAME,
784
+ action: "penalized",
785
+ score: newScore,
786
+ reason: `repeated tags (load ${bestLoad.toFixed(2)}) \u2192 \xD7${bestPenalty.toFixed(2)}`
787
+ }
788
+ ]
789
+ });
790
+ } else {
791
+ out.push(picked);
792
+ }
793
+ for (const tag of picked.tags ?? []) {
794
+ emittedCount.set(tag, (emittedCount.get(tag) ?? 0) + 1);
795
+ }
796
+ }
797
+ return out;
798
+ }
799
+ var DIVERSITY_STRENGTH, DIVERSITY_FLOOR, STRATEGY, STRATEGY_ID, STRATEGY_NAME;
800
+ var init_diversityRerank = __esm({
801
+ "src/core/navigators/diversityRerank.ts"() {
802
+ "use strict";
803
+ DIVERSITY_STRENGTH = 0.6;
804
+ DIVERSITY_FLOOR = 0.3;
805
+ STRATEGY = "diversityRerank";
806
+ STRATEGY_ID = "DIVERSITY_RERANK";
807
+ STRATEGY_NAME = "Diversity Re-rank";
808
+ }
809
+ });
810
+
722
811
  // src/core/navigators/PipelineDebugger.ts
723
812
  var PipelineDebugger_exports = {};
724
813
  __export(PipelineDebugger_exports, {
@@ -1914,7 +2003,7 @@ function shuffleInPlace(arr) {
1914
2003
  function pickTopByScore(cards, limit) {
1915
2004
  return [...cards].sort((a, b) => b.score - a.score || a.cardId.localeCompare(b.cardId)).slice(0, limit);
1916
2005
  }
1917
- var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, DISCOVERED_SUPPORT_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, PRESCRIBED_DEBUG_VERSION, PrescribedCardsGenerator;
2006
+ var DEFAULT_FRESHNESS_WINDOW, DEFAULT_MAX_DIRECT_PER_RUN, DEFAULT_MAX_SUPPORT_PER_RUN, DEFAULT_HIERARCHY_DEPTH, DEFAULT_MIN_COUNT, DEFAULT_PRACTICE_MIN_COUNT, DEFAULT_MAX_PRACTICE_PER_RUN, BASE_TARGET_SCORE, BASE_SUPPORT_SCORE, DISCOVERED_SUPPORT_SCORE, BASE_PRACTICE_SCORE, MAX_TARGET_MULTIPLIER, MAX_SUPPORT_MULTIPLIER, PRESCRIBED_DEBUG_VERSION, PrescribedCardsGenerator;
1918
2007
  var init_prescribed = __esm({
1919
2008
  "src/core/navigators/generators/prescribed.ts"() {
1920
2009
  "use strict";
@@ -1925,9 +2014,12 @@ var init_prescribed = __esm({
1925
2014
  DEFAULT_MAX_SUPPORT_PER_RUN = 3;
1926
2015
  DEFAULT_HIERARCHY_DEPTH = 2;
1927
2016
  DEFAULT_MIN_COUNT = 3;
2017
+ DEFAULT_PRACTICE_MIN_COUNT = 3;
2018
+ DEFAULT_MAX_PRACTICE_PER_RUN = 4;
1928
2019
  BASE_TARGET_SCORE = 1;
1929
2020
  BASE_SUPPORT_SCORE = 0.8;
1930
2021
  DISCOVERED_SUPPORT_SCORE = 12;
2022
+ BASE_PRACTICE_SCORE = 1;
1931
2023
  MAX_TARGET_MULTIPLIER = 8;
1932
2024
  MAX_SUPPORT_MULTIPLIER = 4;
1933
2025
  PRESCRIBED_DEBUG_VERSION = "testversion-prescribed-v3";
@@ -2035,7 +2127,18 @@ var init_prescribed = __esm({
2035
2127
  courseId,
2036
2128
  emittedIds
2037
2129
  );
2038
- emitted.push(...directCards, ...supportCards, ...discoveredSupportCards);
2130
+ const practiceCards = this.buildPracticeCards({
2131
+ group,
2132
+ courseId,
2133
+ emittedIds,
2134
+ cardsByTag,
2135
+ hierarchyConfigs,
2136
+ userTagElo,
2137
+ userGlobalElo,
2138
+ activeIds,
2139
+ seenIds
2140
+ });
2141
+ emitted.push(...directCards, ...supportCards, ...discoveredSupportCards, ...practiceCards);
2039
2142
  }
2040
2143
  const hintSummary = this.buildSupportHintSummary(groupRuntimes);
2041
2144
  const hints = Object.keys(hintSummary.boostTags).length > 0 ? {
@@ -2063,6 +2166,7 @@ var init_prescribed = __esm({
2063
2166
  const surfacedByGroup = /* @__PURE__ */ new Map();
2064
2167
  for (const card of finalCards) {
2065
2168
  const prov = card.provenance[0];
2169
+ if (prov?.reason.includes("mode=practice")) continue;
2066
2170
  const groupId = prov?.reason.match(/group=([^;]+)/)?.[1];
2067
2171
  const mode = prov?.reason.includes("mode=support") ? "supportIds" : "targetIds";
2068
2172
  if (!groupId) continue;
@@ -2132,7 +2236,12 @@ var init_prescribed = __esm({
2132
2236
  enabled: raw.hierarchyWalk?.enabled !== false,
2133
2237
  maxDepth: typeof raw.hierarchyWalk?.maxDepth === "number" ? raw.hierarchyWalk.maxDepth : DEFAULT_HIERARCHY_DEPTH
2134
2238
  },
2135
- retireOnEncounter: raw.retireOnEncounter !== false
2239
+ retireOnEncounter: raw.retireOnEncounter !== false,
2240
+ practiceTagPatterns: dedupe(
2241
+ Array.isArray(raw.practiceTagPatterns) ? raw.practiceTagPatterns.filter((v) => typeof v === "string") : []
2242
+ ),
2243
+ practiceMinCount: typeof raw.practiceMinCount === "number" ? raw.practiceMinCount : DEFAULT_PRACTICE_MIN_COUNT,
2244
+ maxPracticeCardsPerRun: typeof raw.maxPracticeCardsPerRun === "number" ? raw.maxPracticeCardsPerRun : DEFAULT_MAX_PRACTICE_PER_RUN
2136
2245
  })).filter((g) => g.targetCardIds.length > 0);
2137
2246
  return { groups };
2138
2247
  } catch {
@@ -2355,6 +2464,92 @@ var init_prescribed = __esm({
2355
2464
  }
2356
2465
  return cards;
2357
2466
  }
2467
+ /**
2468
+ * Emit drill cards for *unlocked-but-under-practiced* skills.
2469
+ *
2470
+ * For each course tag matching the group's `practiceTagPatterns` that is both
2471
+ * unlocked (all hierarchy prerequisites met — i.e. the learner has been
2472
+ * introduced to it) and under-practiced (per-tag attempt count below
2473
+ * `practiceMinCount`), this resolves cards carrying that tag and emits them
2474
+ * into the candidate pool. It exists because global-ELO retrieval
2475
+ * systematically fails to fetch the (low-ELO) drill cards for a
2476
+ * freshly-introduced skill — putting them in the pool here lets the pipeline's
2477
+ * scoring + the durable per-skill boost order them. Ordering/emphasis is NOT
2478
+ * this method's job; it only guarantees presence.
2479
+ *
2480
+ * Fully data-driven: the unlock relation comes from the hierarchy config and
2481
+ * practice-status from per-tag ELO. No card-id or tag-namespace hard-coding.
2482
+ */
2483
+ buildPracticeCards(args) {
2484
+ const {
2485
+ group,
2486
+ courseId,
2487
+ emittedIds,
2488
+ cardsByTag,
2489
+ hierarchyConfigs,
2490
+ userTagElo,
2491
+ userGlobalElo,
2492
+ activeIds,
2493
+ seenIds
2494
+ } = args;
2495
+ const patterns = group.practiceTagPatterns ?? [];
2496
+ if (patterns.length === 0) return [];
2497
+ const practiceMinCount = group.practiceMinCount ?? DEFAULT_PRACTICE_MIN_COUNT;
2498
+ const maxPractice = group.maxPracticeCardsPerRun ?? DEFAULT_MAX_PRACTICE_PER_RUN;
2499
+ const practiceTags = [...cardsByTag.keys()].filter(
2500
+ (tag) => patterns.some((p) => matchesTagPattern(tag, p)) && this.isUnlockedGatedSkill(tag, hierarchyConfigs, userTagElo, userGlobalElo) && (userTagElo[tag]?.count ?? 0) < practiceMinCount
2501
+ );
2502
+ if (practiceTags.length === 0) return [];
2503
+ const practiceCardIds = this.findDiscoveredSupportCards({
2504
+ supportTags: practiceTags,
2505
+ cardsByTag,
2506
+ activeIds,
2507
+ seenIds,
2508
+ excludedIds: emittedIds,
2509
+ limit: maxPractice
2510
+ });
2511
+ if (practiceCardIds.length === 0) return [];
2512
+ logger.info(
2513
+ `[Prescribed] Group '${group.id}' practice: ${practiceTags.length} unlocked under-practiced skill(s), emitting ${practiceCardIds.length} drill card(s)`
2514
+ );
2515
+ const cards = [];
2516
+ for (const cardId of practiceCardIds) {
2517
+ emittedIds.add(cardId);
2518
+ cards.push({
2519
+ cardId,
2520
+ courseId,
2521
+ score: BASE_PRACTICE_SCORE,
2522
+ provenance: [
2523
+ {
2524
+ strategy: "prescribed",
2525
+ strategyName: this.strategyName || this.name,
2526
+ strategyId: this.strategyId || "NAVIGATION_STRATEGY-prescribed",
2527
+ action: "generated",
2528
+ score: BASE_PRACTICE_SCORE,
2529
+ reason: `mode=practice;group=${group.id};underPracticedSkills=${practiceTags.length};practiceTags=${practiceTags.slice(0, 8).join("|")}${practiceTags.length > 8 ? "|\u2026" : ""};testversion=${PRESCRIBED_DEBUG_VERSION}`
2530
+ }
2531
+ ]
2532
+ });
2533
+ }
2534
+ return cards;
2535
+ }
2536
+ /**
2537
+ * True for a skill that was *gated and is now reached*: it has at least one
2538
+ * declared hierarchy prerequisite set, and every set is fully satisfied by the
2539
+ * learner's per-tag ELO. This deliberately EXCLUDES tags with no prerequisites
2540
+ * — an ungated tag was never "introduced" in the curricular sense, so it isn't
2541
+ * a post-intro drill target (e.g. whole-word spelling tags that share the
2542
+ * `gpc:exercise:*` prefix but have no intro gate). Those are left to normal
2543
+ * ELO retrieval. This is the precise population the retrieval gap strands:
2544
+ * just-unlocked, low-ELO skills.
2545
+ */
2546
+ isUnlockedGatedSkill(tag, hierarchyConfigs, userTagElo, userGlobalElo) {
2547
+ const prereqSets = hierarchyConfigs.map((hierarchy) => hierarchy.prerequisites[tag]).filter((prereqs) => Array.isArray(prereqs) && prereqs.length > 0);
2548
+ if (prereqSets.length === 0) return false;
2549
+ return prereqSets.every(
2550
+ (prereqs) => prereqs.every((pr) => this.isPrerequisiteMet(pr, userTagElo[pr.tag], userGlobalElo))
2551
+ );
2552
+ }
2358
2553
  findSupportCardsByTags(group, tagsByCard, supportTags) {
2359
2554
  if (supportTags.length === 0) {
2360
2555
  return [];
@@ -4148,7 +4343,7 @@ function logResultCards(cards) {
4148
4343
  for (let i = 0; i < cards.length; i++) {
4149
4344
  const c = cards[i];
4150
4345
  const tags = c.tags?.slice(0, 3).join(", ") || "";
4151
- const filters = c.provenance.filter((p) => p.strategy === "hierarchyDefinition" || p.strategy === "priorityDefinition" || p.strategy === "interferenceFilter" || p.strategy === "letterGating" || p.strategy === "ephemeralHint").map((p) => {
4346
+ const filters = c.provenance.filter((p) => p.strategy === "hierarchyDefinition" || p.strategy === "priorityDefinition" || p.strategy === "interferenceFilter" || p.strategy === "letterGating" || p.strategy === "ephemeralHint" || p.strategy === "diversityRerank").map((p) => {
4152
4347
  const arrow = p.action === "boosted" ? "\u2191" : p.action === "penalized" ? "\u2193" : "=";
4153
4348
  return `${p.strategyName}${arrow}${p.score.toFixed(2)}`;
4154
4349
  }).join(" | ");
@@ -4180,6 +4375,7 @@ var init_Pipeline = __esm({
4180
4375
  init_logger();
4181
4376
  init_orchestration();
4182
4377
  init_PipelineDebugger();
4378
+ init_diversityRerank();
4183
4379
  VERBOSE_RESULTS = true;
4184
4380
  Pipeline = class extends ContentNavigator {
4185
4381
  generator;
@@ -4353,6 +4549,7 @@ var init_Pipeline = __esm({
4353
4549
  this._ephemeralHints = null;
4354
4550
  cards = this.applyHints(cards, hints, allCardsBeforeFiltering);
4355
4551
  }
4552
+ cards = diversityRerank(cards);
4356
4553
  cards.sort((a, b) => b.score - a.score);
4357
4554
  const tFilter = performance.now();
4358
4555
  const result = cards.slice(0, limit);
@@ -4921,6 +5118,7 @@ var init_3 = __esm({
4921
5118
  "./PipelineAssembler.ts": () => Promise.resolve().then(() => (init_PipelineAssembler(), PipelineAssembler_exports)),
4922
5119
  "./PipelineDebugger.ts": () => Promise.resolve().then(() => (init_PipelineDebugger(), PipelineDebugger_exports)),
4923
5120
  "./defaults.ts": () => Promise.resolve().then(() => (init_defaults(), defaults_exports)),
5121
+ "./diversityRerank.ts": () => Promise.resolve().then(() => (init_diversityRerank(), diversityRerank_exports)),
4924
5122
  "./filters/WeightedFilter.ts": () => Promise.resolve().then(() => (init_WeightedFilter(), WeightedFilter_exports)),
4925
5123
  "./filters/eloDistance.ts": () => Promise.resolve().then(() => (init_eloDistance(), eloDistance_exports)),
4926
5124
  "./filters/hierarchyDefinition.ts": () => Promise.resolve().then(() => (init_hierarchyDefinition(), hierarchyDefinition_exports)),
@@ -4946,9 +5144,12 @@ var init_3 = __esm({
4946
5144
  var navigators_exports = {};
4947
5145
  __export(navigators_exports, {
4948
5146
  ContentNavigator: () => ContentNavigator,
5147
+ DIVERSITY_FLOOR: () => DIVERSITY_FLOOR,
5148
+ DIVERSITY_STRENGTH: () => DIVERSITY_STRENGTH,
4949
5149
  NavigatorRole: () => NavigatorRole,
4950
5150
  NavigatorRoles: () => NavigatorRoles,
4951
5151
  Navigators: () => Navigators,
5152
+ diversityRerank: () => diversityRerank,
4952
5153
  getCardOrigin: () => getCardOrigin,
4953
5154
  getRegisteredNavigator: () => getRegisteredNavigator,
4954
5155
  getRegisteredNavigatorNames: () => getRegisteredNavigatorNames,
@@ -5032,6 +5233,7 @@ var navigatorRegistry, Navigators, NavigatorRole, NavigatorRoles, ContentNavigat
5032
5233
  var init_navigators = __esm({
5033
5234
  "src/core/navigators/index.ts"() {
5034
5235
  "use strict";
5236
+ init_diversityRerank();
5035
5237
  init_PipelineDebugger();
5036
5238
  init_logger();
5037
5239
  init_();
@@ -8039,6 +8241,8 @@ Examples:
8039
8241
  var core_exports = {};
8040
8242
  __export(core_exports, {
8041
8243
  ContentNavigator: () => ContentNavigator,
8244
+ DIVERSITY_FLOOR: () => DIVERSITY_FLOOR,
8245
+ DIVERSITY_STRENGTH: () => DIVERSITY_STRENGTH,
8042
8246
  DocType: () => DocType,
8043
8247
  DocTypePrefixes: () => DocTypePrefixes,
8044
8248
  GuestUsername: () => GuestUsername,
@@ -8055,6 +8259,7 @@ __export(core_exports, {
8055
8259
  computeSpread: () => computeSpread,
8056
8260
  computeStrategyGradient: () => computeStrategyGradient,
8057
8261
  createOrchestrationContext: () => createOrchestrationContext,
8262
+ diversityRerank: () => diversityRerank,
8058
8263
  docIsDeleted: () => docIsDeleted,
8059
8264
  getCardHistoryID: () => getCardHistoryID,
8060
8265
  getCardOrigin: () => getCardOrigin,
@@ -8104,6 +8309,8 @@ init_core();
8104
8309
  // Annotate the CommonJS export names for ESM import in node:
8105
8310
  0 && (module.exports = {
8106
8311
  ContentNavigator,
8312
+ DIVERSITY_FLOOR,
8313
+ DIVERSITY_STRENGTH,
8107
8314
  DocType,
8108
8315
  DocTypePrefixes,
8109
8316
  GuestUsername,
@@ -8120,6 +8327,7 @@ init_core();
8120
8327
  computeSpread,
8121
8328
  computeStrategyGradient,
8122
8329
  createOrchestrationContext,
8330
+ diversityRerank,
8123
8331
  docIsDeleted,
8124
8332
  getCardHistoryID,
8125
8333
  getCardOrigin,