mongodb-rag-core
Version:
Common elements used by MongoDB Chatbot Framework components.
203 lines (202 loc) • 8.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.fuzzyMatchArrays = exports.fuzzyMatchAggregation = exports.fuzzyMatchExecutionResults = exports.fuzzyMatchSyntheticKey = exports.ERRORS = void 0;
const bson_1 = require("bson");
const assert_1 = require("assert");
exports.ERRORS = {
EXPECTED_ARRAY: "Expected value must be an array",
SINGLE_ITEM_ARRAY: "Expected value and output value must both be a single document arrays",
};
exports.fuzzyMatchSyntheticKey = "__output__";
function fuzzyMatchExecutionResults({ mongoDbOutput, expected, orderMatters = false, isAggregation = false, allowedNumberDifference = 0.01, }) {
const outputEjson = bson_1.EJSON.serialize(mongoDbOutput, { relaxed: true });
const expectedEjson = bson_1.EJSON.serialize(expected, { relaxed: true });
const outputIsArray = Array.isArray(outputEjson);
const expectedIsArray = Array.isArray(expectedEjson);
(0, assert_1.strict)(expectedIsArray, exports.ERRORS.EXPECTED_ARRAY);
if (isAggregation) {
return fuzzyMatchAggregation(expectedEjson, outputEjson, orderMatters, allowedNumberDifference);
}
if (outputIsArray && expectedIsArray) {
return fuzzyMatchArrays(expectedEjson, outputEjson, orderMatters, allowedNumberDifference);
}
return null;
}
exports.fuzzyMatchExecutionResults = fuzzyMatchExecutionResults;
function fuzzyMatchAggregation(truthArray, testInput, nestedArrayOrderMatters, allowedNumberDifference = 0.01) {
const testIsArray = Array.isArray(testInput);
let testArray;
if (testIsArray) {
// If testInput is an array of arrays, keep it as is to avoid flattening
testArray = testInput;
}
else if (typeof testInput === "object") {
testArray = [testInput];
}
else {
testArray = [
{
[exports.fuzzyMatchSyntheticKey]: testInput,
},
];
}
// Since order doesn't matter, we need to find a matching object for each object in truthArray
// Create a copy of testArray to keep track of unmatched objects
const testArrayCopy = [...testArray];
for (const truthObj of truthArray) {
// Try to find a matching object in testArrayCopy
const matchIndex = testArrayCopy.findIndex((testObj) => fuzzyMatchObjectsIgnoreKeys(truthObj, testObj, nestedArrayOrderMatters, allowedNumberDifference));
if (matchIndex === -1) {
// No matching object found
return false;
}
else {
// Remove the matched object to prevent duplicate matching
testArrayCopy.splice(matchIndex, 1);
}
}
// All objects matched
return true;
}
exports.fuzzyMatchAggregation = fuzzyMatchAggregation;
/**
Performs a fuzzy match between two arrays of objects.
@param truthArray - The array of objects to match against
@param testArray - The array of objects to test
*/
function fuzzyMatchArrays(truthArray, testArray, orderMatters, allowedNumberDifference) {
// If array lengths are different, they can't match
if (truthArray.length !== testArray.length)
return false;
// If order matters, compare each object in order
if (orderMatters) {
for (let i = 0; i < truthArray.length; i++) {
if (!fuzzyMatchObjects(truthArray[i], testArray[i], orderMatters, allowedNumberDifference)) {
return false;
}
}
return true;
}
// Create a copy of array2 to keep track of matched objects
const testArrayCopy = [...testArray];
// Iterate through each object in array1
for (const truthObj of truthArray) {
// Find the index of a matching object in testArrayCopy
const matchIndex = testArrayCopy.findIndex((testObj) => fuzzyMatchObjects(truthObj, testObj, orderMatters, allowedNumberDifference));
if (matchIndex === -1) {
// No matching object found
return false;
}
else {
// Remove the matched object to prevent duplicate matching
testArrayCopy.splice(matchIndex, 1);
}
}
// All objects matched
return true;
}
exports.fuzzyMatchArrays = fuzzyMatchArrays;
/**
Fuzzy comparison function for two objects.
Allow small differences in numeric values.
Only validates keys present in the truth object.
*/
function fuzzyMatchObjects(truthObject, testObject, nestedArrayOrderMatters, allowedNumberDifference = 0.01) {
const truthKeys = Object.keys(truthObject);
for (const key of truthKeys) {
if (!Object.prototype.hasOwnProperty.call(testObject, key)) {
return false;
}
const truthVal = truthObject[key];
const testVal = testObject[key];
// Fuzzy conditions:
// - If values are numbers, allow a small difference
if (typeof truthVal === "number" && typeof testVal === "number") {
const difference = Math.abs(truthVal - testVal);
const allowedDifference = allowedNumberDifference;
if (difference > allowedDifference)
return false;
}
// If values are arrays, recursively call fuzzyMatchArrays
else if (Array.isArray(truthVal) && Array.isArray(testVal)) {
if (!fuzzyMatchArrays(truthVal, testVal, nestedArrayOrderMatters))
return false;
}
// Handle null values explicitly to allow for
// typeof object test below, b/c null is typeof object in JS :(
else if (truthVal === null) {
if (testVal !== null)
return false;
}
// Nested objects
else if (typeof truthVal === "object" && typeof testVal === "object") {
if (!fuzzyMatchObjects(truthVal, testVal, nestedArrayOrderMatters))
return false;
}
else {
// For other types, use strict equality
if (truthVal !== testVal)
return false;
}
}
return true;
}
/**
Compares two objects by their values, ignoring keys.
*/
function fuzzyMatchObjectsIgnoreKeys(truthObject, testObject, nestedArrayOrderMatters, allowedNumberDifference = 0.01) {
const truthValues = Object.values(truthObject);
const testValues = Object.values(testObject);
// If more truth values than test values, the objects can't match
if (truthValues.length > testValues.length)
return false;
// Compare values regardless of order
const testValuesCopy = [...testValues];
for (const truthValue of truthValues) {
const matchIndex = testValuesCopy.findIndex((testValue) => fuzzyMatchValues(truthValue, testValue, nestedArrayOrderMatters, allowedNumberDifference));
if (matchIndex === -1) {
// No matching value found
return false;
}
else {
// Remove the matched value to prevent duplicate matching
testValuesCopy.splice(matchIndex, 1);
}
}
// All values matched
return true;
}
/**
Fuzzy comparison function for values.
*/
function fuzzyMatchValues(truthVal, testVal, nestedArrayOrderMatters, allowedNumberDifference = 0.01) {
// Fuzzy conditions:
// - If values are numbers, allow a small difference
if (typeof truthVal === "number" && typeof testVal === "number") {
const difference = Math.abs(truthVal - testVal);
if (difference > allowedNumberDifference)
return false;
}
// If values are arrays, recursively call fuzzyMatchArrays
else if (Array.isArray(truthVal) && Array.isArray(testVal)) {
if (fuzzyMatchArrays(truthVal, testVal, nestedArrayOrderMatters, allowedNumberDifference) === false)
return false;
}
// Handle null values explicitly
else if (truthVal === null) {
if (testVal !== null)
return false;
}
// Nested objects
else if (typeof truthVal === "object" && typeof testVal === "object") {
if (fuzzyMatchObjects(truthVal, testVal, nestedArrayOrderMatters, allowedNumberDifference) === false)
return false;
}
else {
// For other types, use strict equality
if (truthVal !== testVal)
return false;
}
return true;
}
//# sourceMappingURL=fuzzyMatchExecutionResults.js.map