Files
Greenlens/server/lib/scanReview.js

60 lines
2.4 KiB
JavaScript

const { normalizeText } = require('./scanGrounding');
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
// Two DIFFERENT models independently naming the same species is genuine
// evidence beyond either model's self-reported confidence, so agreement
// earns a bonus on top of the better single estimate.
const REVIEW_AGREEMENT_BONUS = 0.2;
const REVIEW_AGREEMENT_CONFIDENCE_CAP = 0.97;
// Agreement must be judged on the RAW model answers, not the grounded ones:
// catalog grounding has a genus-level fallback that can collapse two different
// species onto the same catalog entry and fake an agreement.
const reviewAgreesWithPrimary = (rawPrimary, rawReview) => {
if (!rawPrimary || !rawReview) return false;
const primaryBotanical = normalizeText(rawPrimary.botanicalName);
const primaryName = normalizeText(rawPrimary.name);
const botanicalMatch = Boolean(primaryBotanical) && primaryBotanical === normalizeText(rawReview.botanicalName);
const nameMatch = Boolean(primaryName) && primaryName === normalizeText(rawReview.name);
return botanicalMatch || nameMatch;
};
// The review runs on a stronger model chain than the primary, and models are
// not calibrated against each other — so on disagreement the review wins even
// when it trails the primary by up to this margin.
const REVIEW_DISAGREEMENT_MARGIN = 0.05;
// A second low-confidence guess is not a verification: the review may only
// replace the primary when it agrees with it or is (near-)competitively
// confident. On agreement the higher-confidence variant wins (tie goes to
// the review, which runs on the stronger model chain).
const decideReviewOutcome = ({ primaryResult, reviewResult, agrees }) => {
const primaryConfidence = primaryResult?.confidence || 0;
const reviewConfidence = reviewResult?.confidence || 0;
if (agrees) {
return {
accept: true,
replace: reviewConfidence >= primaryConfidence,
reason: 'review-confirmed-primary',
confidence: clamp(
Math.max(primaryConfidence, reviewConfidence) + REVIEW_AGREEMENT_BONUS,
0.05,
REVIEW_AGREEMENT_CONFIDENCE_CAP,
),
};
}
if (reviewConfidence >= primaryConfidence - REVIEW_DISAGREEMENT_MARGIN) {
return { accept: true, replace: true, reason: 'review-overrode-primary' };
}
return { accept: false, replace: false, reason: 'review-rejected-low-confidence' };
};
module.exports = {
decideReviewOutcome,
reviewAgreesWithPrimary,
};