mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-22 11:51:01 +02:00
Initial open-source release
This commit is contained in:
commit
1a42152e6f
1199 changed files with 257054 additions and 0 deletions
108
packages/context/src/scan/relationship-scoring.test.ts
Normal file
108
packages/context/src/scan/relationship-scoring.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
calibrateWeightsFromSyntheticFixtures,
|
||||
defaultKloRelationshipScoreWeights,
|
||||
normalizeKloRelationshipScoreWeights,
|
||||
scoreKloRelationshipCandidate,
|
||||
type KloRelationshipSignalVector,
|
||||
} from './relationship-scoring.js';
|
||||
|
||||
function signals(overrides: Partial<KloRelationshipSignalVector> = {}): KloRelationshipSignalVector {
|
||||
return {
|
||||
nameSimilarity: 0.5,
|
||||
typeCompatibility: 1,
|
||||
valueOverlap: 0,
|
||||
embeddingSimilarity: 0,
|
||||
profileUniqueness: 0.5,
|
||||
profileNullRate: 0.5,
|
||||
structuralPrior: 0.5,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('relationship scoring', () => {
|
||||
it('scores stronger evidence higher without hard-gating on names', () => {
|
||||
const weakNameStrongProfile = scoreKloRelationshipCandidate(
|
||||
signals({
|
||||
nameSimilarity: 0.05,
|
||||
typeCompatibility: 1,
|
||||
valueOverlap: 0.7,
|
||||
profileUniqueness: 1,
|
||||
profileNullRate: 1,
|
||||
structuralPrior: 0.7,
|
||||
}),
|
||||
);
|
||||
const strongNameWeakProfile = scoreKloRelationshipCandidate(
|
||||
signals({
|
||||
nameSimilarity: 0.95,
|
||||
typeCompatibility: 1,
|
||||
valueOverlap: 0,
|
||||
profileUniqueness: 0.3,
|
||||
profileNullRate: 0.4,
|
||||
structuralPrior: 0.5,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(weakNameStrongProfile.score).toBeGreaterThan(strongNameWeakProfile.score);
|
||||
expect(weakNameStrongProfile.contributions.profileUniqueness).toBeGreaterThan(0);
|
||||
expect(weakNameStrongProfile.contributions.nameSimilarity).toBeLessThan(0.02);
|
||||
});
|
||||
|
||||
it('normalizes partial and invalid weights into a usable vector', () => {
|
||||
const weights = normalizeKloRelationshipScoreWeights({
|
||||
nameSimilarity: 3,
|
||||
typeCompatibility: -1,
|
||||
valueOverlap: Number.POSITIVE_INFINITY,
|
||||
profileUniqueness: 1,
|
||||
});
|
||||
|
||||
const total = Object.values(weights).reduce((sum, value) => sum + value, 0);
|
||||
expect(total).toBeCloseTo(1, 6);
|
||||
expect(weights.nameSimilarity).toBeGreaterThan(weights.profileUniqueness);
|
||||
expect(weights.typeCompatibility).toBe(0);
|
||||
expect(weights.valueOverlap).toBe(0);
|
||||
});
|
||||
|
||||
it('returns deterministic defaults as a defensive copy', () => {
|
||||
const first = defaultKloRelationshipScoreWeights();
|
||||
const second = defaultKloRelationshipScoreWeights();
|
||||
|
||||
expect(first).toEqual(second);
|
||||
expect(first).not.toBe(second);
|
||||
expect(Object.values(first).reduce((sum, value) => sum + value, 0)).toBeCloseTo(1, 6);
|
||||
});
|
||||
|
||||
it('calibrates only from synthetic observations', () => {
|
||||
expect(() =>
|
||||
calibrateWeightsFromSyntheticFixtures([
|
||||
{
|
||||
fixtureId: 'chinook_with_declared_metadata',
|
||||
origin: 'public',
|
||||
expectedRelationship: true,
|
||||
signals: signals({ nameSimilarity: 1 }),
|
||||
},
|
||||
]),
|
||||
).toThrow(/synthetic/i);
|
||||
});
|
||||
|
||||
it('calibrates deterministic weights from positive and negative synthetic observations', () => {
|
||||
const weights = calibrateWeightsFromSyntheticFixtures([
|
||||
{
|
||||
fixtureId: 'synthetic_positive',
|
||||
origin: 'synthetic',
|
||||
expectedRelationship: true,
|
||||
signals: signals({ nameSimilarity: 0.8, valueOverlap: 0.9, profileUniqueness: 1, profileNullRate: 1 }),
|
||||
},
|
||||
{
|
||||
fixtureId: 'synthetic_negative',
|
||||
origin: 'synthetic',
|
||||
expectedRelationship: false,
|
||||
signals: signals({ nameSimilarity: 0.2, valueOverlap: 0.1, profileUniqueness: 0.4, profileNullRate: 0.5 }),
|
||||
},
|
||||
]);
|
||||
|
||||
expect(Object.values(weights).reduce((sum, value) => sum + value, 0)).toBeCloseTo(1, 6);
|
||||
expect(weights.valueOverlap).toBeGreaterThan(weights.structuralPrior);
|
||||
expect(weights.profileUniqueness).toBeGreaterThan(weights.embeddingSimilarity);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue