Files
Greenlens/__tests__/server/scanReview.test.js

134 lines
5.2 KiB
JavaScript

const { decideReviewOutcome, reviewAgreesWithPrimary } = require('../../server/lib/scanReview');
const ai = (name, botanicalName, confidence) => ({ name, botanicalName, confidence });
describe('reviewAgreesWithPrimary', () => {
it('agrees on matching botanical names regardless of casing/accents', () => {
expect(reviewAgreesWithPrimary(
ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.7),
ai('Fensterblatt', 'MONSTERA DELICIOSA', 0.6),
)).toBe(true);
});
it('agrees on matching common names when botanicals differ', () => {
expect(reviewAgreesWithPrimary(
ai('Rose', 'Rosa chinensis', 0.5),
ai('Rose', 'Rosa hybrida', 0.5),
)).toBe(true);
});
it('does not agree for different species in the same genus', () => {
// Regression: post-grounding comparison used to collapse these onto the
// same catalog entry and fake an agreement.
expect(reviewAgreesWithPrimary(
ai('Fiddle Leaf Fig', 'Ficus lyrata', 0.7),
ai('Rubber Plant', 'Ficus elastica', 0.7),
)).toBe(false);
});
it('never agrees when either side is missing', () => {
expect(reviewAgreesWithPrimary(null, ai('Rose', 'Rosa chinensis', 0.5))).toBe(false);
expect(reviewAgreesWithPrimary(ai('Rose', 'Rosa chinensis', 0.5), null)).toBe(false);
});
});
describe('decideReviewOutcome', () => {
it('rejects a disagreeing review at lower confidence', () => {
const decision = decideReviewOutcome({
primaryResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.85),
reviewResult: ai('Rose', 'Rosa chinensis', 0.55),
agrees: false,
});
expect(decision).toEqual({ accept: false, replace: false, reason: 'review-rejected-low-confidence' });
});
it('accepts a disagreeing review at higher confidence', () => {
const decision = decideReviewOutcome({
primaryResult: ai('Rose', 'Rosa chinensis', 0.55),
reviewResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.8),
agrees: false,
});
expect(decision).toEqual({ accept: true, replace: true, reason: 'review-overrode-primary' });
});
it('accepts a disagreeing stronger-model review within the 0.05 calibration margin', () => {
// Models are not calibrated against each other: an honest gpt-5 answer at
// 0.75 must not lose to an overconfident gpt-5-mini answer at 0.79.
const decision = decideReviewOutcome({
primaryResult: ai('Rose', 'Rosa chinensis', 0.79),
reviewResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.75),
agrees: false,
});
expect(decision).toEqual({ accept: true, replace: true, reason: 'review-overrode-primary' });
});
it('still rejects a disagreeing review clearly below the margin', () => {
const decision = decideReviewOutcome({
primaryResult: ai('Rose', 'Rosa chinensis', 0.79),
reviewResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.73),
agrees: false,
});
expect(decision.accept).toBe(false);
});
it('keeps the higher-confidence primary when the review agrees at lower confidence', () => {
const decision = decideReviewOutcome({
primaryResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.85),
reviewResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.6),
agrees: true,
});
expect(decision.accept).toBe(true);
expect(decision.replace).toBe(false);
expect(decision.reason).toBe('review-confirmed-primary');
});
it('boosts confidence when two different models agree on the species', () => {
// The 55%-forever bug: primary 0.55 + agreeing review 0.54 must NOT stay
// at 0.55 — independent cross-model agreement is real evidence.
const decision = decideReviewOutcome({
primaryResult: ai('Sacred lotus', 'Nelumbo nucifera', 0.55),
reviewResult: ai('Sacred lotus', 'Nelumbo nucifera', 0.54),
agrees: true,
});
expect(decision.confidence).toBeCloseTo(0.75, 5);
});
it('caps the agreement-boosted confidence at 0.97', () => {
const decision = decideReviewOutcome({
primaryResult: ai('Sacred lotus', 'Nelumbo nucifera', 0.95),
reviewResult: ai('Sacred lotus', 'Nelumbo nucifera', 0.9),
agrees: true,
});
expect(decision.confidence).toBe(0.97);
});
it('gives no confidence bonus on disagreement', () => {
const decision = decideReviewOutcome({
primaryResult: ai('Rose', 'Rosa chinensis', 0.5),
reviewResult: ai('Sacred lotus', 'Nelumbo nucifera', 0.6),
agrees: false,
});
expect(decision.confidence).toBeUndefined();
});
it('replaces with the agreeing review on a confidence tie (stronger model wins ties)', () => {
const decision = decideReviewOutcome({
primaryResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.7),
reviewResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.7),
agrees: true,
});
expect(decision.accept).toBe(true);
expect(decision.replace).toBe(true);
});
it('treats missing confidences as 0 without crashing', () => {
const decision = decideReviewOutcome({
primaryResult: ai('Rose', 'Rosa chinensis', undefined),
reviewResult: ai('Swiss Cheese Plant', 'Monstera deliciosa', 0.5),
agrees: false,
});
expect(decision.accept).toBe(true);
expect(decision.replace).toBe(true);
});
});