This commit is contained in:
136
packages/hive-mind-core/tests/mind/scoring.test.ts
Normal file
136
packages/hive-mind-core/tests/mind/scoring.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Scoring substrate tests — ported from hive-mind/packages/core/src/mind/scoring.test.ts.
|
||||
*
|
||||
* Memory Sync Repair Step 2. Source: hive-mind file at HEAD c363257.
|
||||
*
|
||||
* Verbatim port — only the import path is adjusted from `./scoring.js`
|
||||
* to `../../src/mind/scoring.js` to match waggle-os's `tests/mind/`
|
||||
* placement convention. SCORING_PROFILES + 4 compute helpers + the
|
||||
* computeRelevance combinator are byte-identical between the two
|
||||
* repos at this HEAD pair, so the suite exercises the same algebra
|
||||
* either way.
|
||||
*/
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
SCORING_PROFILES,
|
||||
computeTemporalScore,
|
||||
computePopularityScore,
|
||||
computeContextualScore,
|
||||
computeImportanceScore,
|
||||
computeRelevance,
|
||||
} from '../../src/mind/scoring.js';
|
||||
|
||||
describe('scoring (hive-mind port)', () => {
|
||||
describe('computeTemporalScore', () => {
|
||||
it('returns 1.0 for timestamps within the 7-day recency window', () => {
|
||||
const now = new Date();
|
||||
expect(computeTemporalScore(now.toISOString())).toBe(1.0);
|
||||
|
||||
const fiveDaysAgo = new Date(now.getTime() - 5 * 86400_000);
|
||||
expect(computeTemporalScore(fiveDaysAgo.toISOString())).toBe(1.0);
|
||||
});
|
||||
|
||||
it('decays exponentially past the recency window (half-life = 30 days)', () => {
|
||||
const now = Date.now();
|
||||
const thirtyDaysAgo = new Date(now - 30 * 86400_000);
|
||||
const score = computeTemporalScore(thirtyDaysAgo.toISOString());
|
||||
expect(score).toBeGreaterThan(0.48);
|
||||
expect(score).toBeLessThan(0.52);
|
||||
});
|
||||
|
||||
it('approaches zero for very old timestamps', () => {
|
||||
const twoYearsAgo = new Date(Date.now() - 2 * 365 * 86400_000);
|
||||
expect(computeTemporalScore(twoYearsAgo.toISOString())).toBeLessThan(0.01);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computePopularityScore', () => {
|
||||
it('returns 1.0 for zero accesses (log10(1) = 0)', () => {
|
||||
expect(computePopularityScore(0)).toBe(1.0);
|
||||
});
|
||||
|
||||
it('grows sub-linearly with access count', () => {
|
||||
const nine = computePopularityScore(9);
|
||||
const ninetyNine = computePopularityScore(99);
|
||||
expect(nine).toBeCloseTo(1.1, 5);
|
||||
expect(ninetyNine).toBeCloseTo(1.2, 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeContextualScore', () => {
|
||||
it('returns 0 when no graph context is provided', () => {
|
||||
expect(computeContextualScore(42, undefined)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 for frames missing from the distance map', () => {
|
||||
const distances = new Map<number, number>([[1, 0]]);
|
||||
expect(computeContextualScore(42, distances)).toBe(0);
|
||||
});
|
||||
|
||||
it('decreases with graph distance in the documented steps', () => {
|
||||
const distances = new Map<number, number>([
|
||||
[1, 0],
|
||||
[2, 1],
|
||||
[3, 2],
|
||||
[4, 3],
|
||||
[5, 4],
|
||||
]);
|
||||
expect(computeContextualScore(1, distances)).toBe(1.0);
|
||||
expect(computeContextualScore(2, distances)).toBe(0.7);
|
||||
expect(computeContextualScore(3, distances)).toBe(0.4);
|
||||
expect(computeContextualScore(4, distances)).toBe(0.2);
|
||||
expect(computeContextualScore(5, distances)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeImportanceScore', () => {
|
||||
it('maps each importance tier to the documented multiplier', () => {
|
||||
expect(computeImportanceScore('critical')).toBe(2.0);
|
||||
expect(computeImportanceScore('important')).toBe(1.5);
|
||||
expect(computeImportanceScore('normal')).toBe(1.0);
|
||||
expect(computeImportanceScore('temporary')).toBe(0.7);
|
||||
expect(computeImportanceScore('deprecated')).toBe(0.3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeRelevance', () => {
|
||||
it('combines the four feature scores by their weights', () => {
|
||||
const now = new Date().toISOString();
|
||||
const weights = SCORING_PROFILES.balanced;
|
||||
const score = computeRelevance(
|
||||
{ id: 1, last_accessed: now, access_count: 0, importance: 'normal' },
|
||||
weights,
|
||||
);
|
||||
expect(score).toBeCloseTo(0.8, 5);
|
||||
});
|
||||
|
||||
it('rewards higher importance under the `important` profile', () => {
|
||||
const now = new Date().toISOString();
|
||||
const balanced = computeRelevance(
|
||||
{ id: 1, last_accessed: now, access_count: 0, importance: 'critical' },
|
||||
SCORING_PROFILES.balanced,
|
||||
);
|
||||
const important = computeRelevance(
|
||||
{ id: 1, last_accessed: now, access_count: 0, importance: 'critical' },
|
||||
SCORING_PROFILES.important,
|
||||
);
|
||||
expect(important).toBeGreaterThan(balanced);
|
||||
});
|
||||
|
||||
it('boosts graph-adjacent frames under the `connected` profile', () => {
|
||||
const oneYearAgo = new Date(Date.now() - 365 * 86400_000).toISOString();
|
||||
const distances = new Map<number, number>([[1, 0]]);
|
||||
const balanced = computeRelevance(
|
||||
{ id: 1, last_accessed: oneYearAgo, access_count: 0, importance: 'normal' },
|
||||
SCORING_PROFILES.balanced,
|
||||
{ graphDistances: distances },
|
||||
);
|
||||
const connected = computeRelevance(
|
||||
{ id: 1, last_accessed: oneYearAgo, access_count: 0, importance: 'normal' },
|
||||
SCORING_PROFILES.connected,
|
||||
{ graphDistances: distances },
|
||||
);
|
||||
expect(connected).toBeGreaterThan(balanced);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user