Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Attribute Invalidation (CO-1762)
Benn Oshrin committed Jun 18, 2023
1 parent ae1ef15 commit 4d93dcc
Showing 7 changed files with 107 additions and 24 deletions.
1 change: 0 additions & 1 deletion app/config/schema/schema.json
@@ -162,7 +162,6 @@
"index_display": { "type": "boolean" },
"alphanumeric": { "type": "boolean" },
"case_sensitive": { "type": "boolean" },
"invalidates": { "type": "boolean" },
"null_equivalents": { "type": "boolean" },
"search_distance": { "type": "integer" },
"search_exact": { "type": "boolean" },
6 changes: 3 additions & 3 deletions app/resources/locales/en_US/default.po
@@ -260,6 +260,9 @@ msgstr "Exact"
msgid "match.en.SearchTypeEnum.M"
msgstr "Mapping"

msgid "match.en.SearchTypeEnum.IN"
msgstr "Invalidate"

msgid "match.en.SearchTypeEnum.X"
msgstr "Skip"

@@ -492,9 +495,6 @@ msgstr "ID"
msgid "match.fd.index_display"
msgstr "Display Field in Matchgrid Index"

msgid "match.fd.invalidates"
msgstr "Invalidates"

msgid "match.fd.label"
msgstr "Label"

11 changes: 6 additions & 5 deletions app/src/Lib/Enum/SearchTypeEnum.php
@@ -30,9 +30,10 @@
namespace App\Lib\Enum;

class SearchTypeEnum extends StandardEnum {
const Distance = 'D';
const Exact = 'E';
const Mapping = 'M';
const Skip = 'X';
const Substring = 'S';
const Distance = 'D';
const Exact = 'E';
const Invalidate = "IN";
const Mapping = 'M';
const Skip = 'X';
const Substring = 'S';
}
90 changes: 78 additions & 12 deletions app/src/Lib/Match/MatchService.php
@@ -625,7 +625,9 @@ protected function search(string $mode,
];

foreach($rule->rule_attributes as $ruleattr) {
if($ruleattr->search_type == SearchTypeEnum::Skip) {
if($ruleattr->search_type == SearchTypeEnum::Skip
// We skip invalidate rules as well, these are handled in searchReferenceId()
|| $ruleattr->search_type == SearchTypeEnum::Invalidate) {
continue;
}

@@ -849,20 +851,80 @@ public function searchReferenceId(string $sor, string $sorid, AttributeManager $
skipSor: $trustMode == TrustModeEnum::Trust
);

// Was a canonical match downgraded?
$downgraded = false;

switch($canonicalMatches->count()) {
case 1:
// Before returning this canonical match, we need to perform some checks that
// might demote the result to potential. Note that $results may have more than
// one row even for a canonical match, since there may be multiple matchgrid
// entries that point to the same Reference ID.

$results = $canonicalMatches->getRawResults();

// First, check if any Attribute Rules in "Invalidate" mode will demote this result.

foreach($results as $rowId => $attrs) {
// Find the rule that generated this match.

$rulename = $canonicalMatches->getRuleForResult($rowId);

foreach($this->mgConfig->canonical_rules as $rule) {
if($rule->name == $rulename) {
// This is the correct rule, check the Rule Attributes. Note there could
// be more than one Rule Attribute with Search Type "Invalidate".

foreach($rule->rule_attributes as $ruleAttribute) {
if($ruleAttribute->search_type == SearchTypeEnum::Invalidate) {
// The name of the attribute for this Rule Attribute, eg "dob".
// This is the column name, not the API name.
$ruleAttributeName = $ruleAttribute->attribute->name;

// The value in the search request
$searchValue = $attributes->getValueByAttribute($ruleAttribute->attribute);
// The value in the matched row
$rowValue = $results[$rowId][$ruleAttributeName];

// We only perform the validation check if both values are not empty.
if(!empty($searchValue)
&& !empty($rowValue)
&& ($searchValue !== $rowValue)) {
// The returned value does not match, invalidate the request and drop
// the result to potential

Log::write('debug', $sor . "/" . $sorid . " Invalidate mode for $ruleAttributeName ($rulename) downgrading result to Potential");

$downgraded = [
'rule' => "_invalidate",
'attrs' => $attrs
];

// No need to continue with any loop
break 3;
}
}
}

// We found the rule we're looking for, no need to continue with the loop
break;
}
}

// If we have multiple rows, we should have the same rule for each row,
// so we can stop after the first.
break;
}

if(!$downgraded) {
// Next we need to handle Potential Trust Mode, check to see if any result row
// matched the SOR.

if($trustMode != TrustModeEnum::Potential) {
// Exact match, return
return $canonicalMatches;
}

// Handling Potential mode is a bit more complicated. First, get the
// raw results. Even though count() was 1, $results may have more than
// one entry since we're getting the per-row matches. Check to see if
// _any_ row matched the SOR.

$results = $canonicalMatches->getRawResults();

foreach($results as $rowId => $attrs) {
if($attrs['sor'] == $sor) {
$sorMatch = $attrs;
@@ -876,8 +938,12 @@ public function searchReferenceId(string $sor, string $sorid, AttributeManager $
return $canonicalMatches;
}

$downgraded = [
'rule' => "_trustmode",
'attrs' => $sorMatch
];
Log::write('debug', $sor . "/" . $sorid . " Trust Mode downgrading result to Potential");

}
break;
case 0:
// Fall through and try potential matches
@@ -900,11 +966,11 @@ public function searchReferenceId(string $sor, string $sorid, AttributeManager $
skipSor: $trustMode == TrustModeEnum::Trust
);

// Add in the potential match from above, if configured
// Add in the downgraded potential match from above, if configured

if($sorMatch) {
if($downgraded !== false) {
// We use a psuedo-rule name
$potentialMatches->add($sorMatch, "_trustmode");
$potentialMatches->add($downgraded['attrs'], $downgraded['rule']);
}

// The calling code generally checks to see if any rules successfully ran,
20 changes: 19 additions & 1 deletion app/src/Lib/Match/ResultManager.php
@@ -39,12 +39,16 @@ class ResultManager {
// for the same referenceId). Attributes are stored in the same format as AttributeManager
// to faciliate wire representation.
protected $results = [];
// Track which rule created which results. This will only be populated for rules
// that generated results.
protected $resultRules = [];
// Keep the raw results as well, to facilitate UI representation.
protected $rawResults = [];
// "canonical" or "potential"
protected $confidenceMode = null;
protected $attrconfig = [];
// We track which rules ran successfully as a way of validating the inbound request
// We track which rules ran successfully as a way of validating the inbound request.
// This includes rules that did not generate any results.
protected $successfulRules = [];

/**
@@ -97,6 +101,8 @@ public function add(array $attributes, string $rule=null) {
}

$this->results[$referenceId][$rowId] = $parsed;

$this->resultRules[$rowId] = $rule;

$this->rawResults[$rowId] = $attributes;
}
@@ -274,6 +280,18 @@ public function getResultsForJson($mode="search") {
return $ret;
}

/**
* Get the name of the rule that generated the result for the specified row ID.
*
* @since COmanage Match v1.2.0
* @param int $rowId Row to return rule for
* @return string Rule name (if found) or false
*/

public function getRuleForResult(int $rowId) {
return $this->resultRules[$rowId] ?? false;
}

/**
* Obtain an array of successfully executed rules as reported to the ResultManager.
* Note that successful rules may have generated no matches.
1 change: 1 addition & 0 deletions app/src/Model/Table/RuleAttributesTable.php
@@ -122,6 +122,7 @@ public function validationDefault(Validator $validator): Validator {
[ 'rule' => [ 'inList', [
SearchTypeEnum::Distance,
SearchTypeEnum::Exact,
SearchTypeEnum::Invalidate,
SearchTypeEnum::Mapping,
SearchTypeEnum::Skip,
SearchTypeEnum::Substring
2 changes: 0 additions & 2 deletions app/templates/Attributes/fields.inc
@@ -37,8 +37,6 @@ if($action == 'add' || $action == 'edit') {

print $this->Field->control('alphanumeric', [], false);
print $this->Field->control('case_sensitive', [], false);
// CO-1762
// print $this->Field->control('invalidates', [], false);
print $this->Field->control('null_equivalents', [], false);

print $this->Field->control('search_distance', [], false);

0 comments on commit 4d93dcc

Please sign in to comment.