Files
miti99bot/tests/modules/semantle/format.test.js
T
tiennm99 fd5a1d2903 feat(semantle,doantu): calibrate cosine score via normalized sigmoid
BGE embeddings occupy a narrow cone in vector space, so raw cosine of
two unrelated words already sits at ~0.40-0.55. Displaying `raw * 100`
made every random guess read as 40-70% warm, which defeated the warmth
UX.

format.js now applies a normalized sigmoid (FLOOR 0.40, CENTER 0.60,
SCALE 8) to remap raw cosine → displayed 0-100. Unrelated pairs drop
to ≤30, loose relation lands around 40-55, clear synonyms hit 85+, and
exact match stays at 100. Emoji buckets were rebased onto the calibrated
score; formatWarmth lost its sign column (calibrated output is always
non-negative).

render.js rounds once and feeds the integer to both formatWarmth and
warmthEmoji so the display value and bucket stay in sync.

Constants are empirical — retune if swapping to a non-BGE model.
2026-04-23 00:33:54 +07:00

92 lines
2.8 KiB
JavaScript

import { describe, expect, it } from "vitest";
import { calibrate, formatWarmth, warmthEmoji } from "../../../src/modules/semantle/format.js";
describe("semantle/format", () => {
describe("calibrate", () => {
it("maps raw cosine <= floor to 0", () => {
expect(calibrate(0.4)).toBe(0);
expect(calibrate(0.2)).toBe(0);
expect(calibrate(-1)).toBe(0);
});
it("maps raw cosine = 1 to 100", () => {
expect(calibrate(1)).toBe(100);
});
it("is monotonically increasing between floor and 1", () => {
let prev = calibrate(0.4);
for (let r = 0.41; r <= 1.001; r += 0.02) {
const s = calibrate(r);
expect(s).toBeGreaterThanOrEqual(prev);
prev = s;
}
});
it("compresses mid-range cosines so unrelated-baseline reads low", () => {
// Unrelated BGE pairs cluster around 0.45-0.55 — should still look cold.
expect(calibrate(0.5)).toBeLessThan(25);
expect(calibrate(0.55)).toBeLessThan(35);
});
it("rewards clearly-related cosines with high scores", () => {
expect(calibrate(0.75)).toBeGreaterThan(70);
expect(calibrate(0.85)).toBeGreaterThan(85);
expect(calibrate(0.95)).toBeGreaterThan(95);
});
it("stays clamped to [0, 100]", () => {
expect(calibrate(2)).toBe(100);
expect(calibrate(-5)).toBe(0);
});
});
describe("formatWarmth", () => {
it("formats integer percent with zero-padding at width 2", () => {
expect(formatWarmth(0)).toBe("00");
expect(formatWarmth(7)).toBe("07");
expect(formatWarmth(73)).toBe("73");
});
it("returns '100' without padding at the max", () => {
expect(formatWarmth(100)).toBe("100");
});
it("rounds to nearest integer", () => {
expect(formatWarmth(50.4)).toBe("50");
expect(formatWarmth(50.5)).toBe("51");
expect(formatWarmth(99.5)).toBe("100");
});
});
describe("warmthEmoji", () => {
it("returns 🥶 for score < 15", () => {
expect(warmthEmoji(0)).toBe("🥶");
expect(warmthEmoji(14.9)).toBe("🥶");
});
it("returns 😐 for score in [15, 40)", () => {
expect(warmthEmoji(15)).toBe("😐");
expect(warmthEmoji(30)).toBe("😐");
expect(warmthEmoji(39.9)).toBe("😐");
});
it("returns 🌡️ for score in [40, 70)", () => {
expect(warmthEmoji(40)).toBe("🌡️");
expect(warmthEmoji(55)).toBe("🌡️");
expect(warmthEmoji(69.9)).toBe("🌡️");
});
it("returns 🔥 for score in [70, 90)", () => {
expect(warmthEmoji(70)).toBe("🔥");
expect(warmthEmoji(80)).toBe("🔥");
expect(warmthEmoji(89.9)).toBe("🔥");
});
it("returns 🎯 for score >= 90", () => {
expect(warmthEmoji(90)).toBe("🎯");
expect(warmthEmoji(99)).toBe("🎯");
expect(warmthEmoji(100)).toBe("🎯");
});
});
});