sfpm-js
Version:
A lightweight, dependency-free, forward-chaining inference engine for managing complex state and logic in a declarative way.
76 lines (64 loc) • 2.05 kB
JavaScript
// src/RuleMatcher.js
/**
* @param {import('./Rule').Rule[]} rules
* @returns {import('./Rule').Rule[]}
*/
export function orderBySpecificity(rules) {
return [...rules].sort((a, b) => b.criteriaCount - a.criteriaCount);
}
/**
* @param {import('./Rule').Rule[]} rules
* @returns {import('./Rule').Rule}
*/
export function mostSpecificRule(rules) {
if (rules.length === 0) {
throw new Error("Rules collection cannot be empty.");
}
return rules.reduce((prev, current) =>
prev.criteriaCount > current.criteriaCount ? prev : current
);
}
/**
* @param {import('./Rule').Rule[]} rules
* @returns {import('./Rule').Rule}
*/
export function leastSpecificRule(rules) {
if (rules.length === 0) {
throw new Error("Rules collection cannot be empty.");
}
return rules.reduce((prev, current) =>
prev.criteriaCount < current.criteriaCount ? prev : current
);
}
/**
* @param {import('./Rule').Rule[]} rules
* @param {import('./FactSource').FactSource} facts
*/
export function match(rules, facts, ...data) {
const matchedRules = rules.filter((rule) => rule.evaluate(facts).isTrue);
if (matchedRules.length === 0) {
return;
}
if (matchedRules.length === 1) {
matchedRules[0].executePayload(...data);
return;
}
const highestScore = Math.max(...matchedRules.map((r) => r.criteriaCount));
const bestRules = matchedRules.filter(
(r) => r.criteriaCount === highestScore
);
if (bestRules.length === 1) {
bestRules[0].executePayload(...data);
return;
}
const highestPriority = Math.max(...bestRules.map((r) => r.priority));
const priorityCandidates = bestRules.filter(
(r) => r.priority === highestPriority
);
if (priorityCandidates.length === 1) {
priorityCandidates[0].executePayload(...data);
return;
}
const randomIndex = Math.floor(Math.random() * priorityCandidates.length);
priorityCandidates[randomIndex].executePayload(...data);
}