diff --git a/app/EasyMinerModule/presenters/RuleSetsPresenter.php b/app/EasyMinerModule/presenters/RuleSetsPresenter.php index d390ccd3..24e0005d 100644 --- a/app/EasyMinerModule/presenters/RuleSetsPresenter.php +++ b/app/EasyMinerModule/presenters/RuleSetsPresenter.php @@ -2,11 +2,15 @@ namespace EasyMinerCenter\EasyMinerModule\Presenters; +use EasyMinerCenter\Model\EasyMiner\Entities\Cedent; +use EasyMinerCenter\Model\EasyMiner\Entities\KnowledgeBaseRuleRelation; use EasyMinerCenter\Model\EasyMiner\Entities\RuleSet; use EasyMinerCenter\Model\EasyMiner\Entities\RuleSetRuleRelation; +use EasyMinerCenter\Model\EasyMiner\Facades\KnowledgeBaseFacade; use EasyMinerCenter\Model\EasyMiner\Facades\RulesFacade; use EasyMinerCenter\Model\EasyMiner\Facades\RuleSetsFacade; use EasyMinerCenter\Model\EasyMiner\Facades\UsersFacade; +use EasyMinerCenter\Model\EasyMiner\Repositories\RuleRuleRelationsRepository; use EasyMinerCenter\Model\EasyMiner\Serializers\AssociationRulesXmlSerializer; use EasyMinerCenter\Model\EasyMiner\Transformators\XmlTransformator; use Nette\InvalidArgumentException; @@ -24,6 +28,8 @@ class RuleSetsPresenter extends BasePresenter{ private $rulesFacade; /** @var RuleSetsFacade $ruleSetsFacade */ private $ruleSetsFacade; + /** @var KnowledgeBaseFacade $knowledgeBaseFacade */ + private $knowledgeBaseFacade; /** @var UsersFacade $usersFacade */ private $usersFacade; /** @var XmlTransformator $xmlTransformator */ @@ -265,6 +271,198 @@ private function prepareRulesResult($ruleIdsArr, $ruleSet=null){ #endregion actions for manipulation of relations between Rules and RuleSets + #region actions for rule comparing + + /** + * Action returning Rule set with rule names and relations from Datasource of Miner + * @param Int $id RuleSet id + * @param Int $miner Miner id + */ + public function actionGetRulesNames($id, $miner){ + //najití RuleSetu a kontroly + $ruleSet=$this->ruleSetsFacade->findRuleSet($id); + $this->ruleSetsFacade->checkRuleSetAccess($ruleSet,$this->user->id); + //připravení výstupu + $result=[ + 'ruleset'=>$ruleSet->getDataArr(), + 'rules'=>[] + ]; + if ($ruleSet->rulesCount>0 || true){ + $rules=$this->knowledgeBaseFacade->findRulesByDatasource($ruleSet,$miner); + if (!empty($rules)){ + //$result['rules'] = $rules; + foreach($rules as $rule){ + $result['rules'][$rule->ruleId]=[ + 'name' => $rule->text, + 'relation' => $rule->relation + ]; + } + } + } + $this->sendJsonResponse($result); + } + + /** + * Method for finding Rule from Rule set with the best similarity to compared Rule + * @param Int $id RuleSet id + * @param Int $rule Rule id + */ + public function actionCompareRuleWithRuleset($id, $rule){ + //finding Rule set and checking + $ruleSet=$this->ruleSetsFacade->findRuleSet($id); + $this->ruleSetsFacade->checkRuleSetAccess($ruleSet,$this->user->id); + //prepearing output + $result=[]; + $compareResults=[]; + try{ + $ruleSimilarity = $this->knowledgeBaseFacade->findRuleSimilarity($id, $rule); + }catch (\Exception $e){ + $ruleSimilarity = null; + } + if($ruleSimilarity && ($ruleSimilarity->resultDate->getTimestamp() >= $ruleSet->lastModified->getTimestamp())){ // result from past, which is newer than Rule set last modification + $result['max'] = $ruleSimilarity->rate; + $result['rule'] = [ + RuleRuleRelationsRepository::COLUMN_RULESET_RULE => $ruleSimilarity->knowledgeBaseRuleId, + RuleRuleRelationsRepository::COLUMN_RELATION => $ruleSimilarity->relation, + RuleRuleRelationsRepository::COLUMN_RATE=> $ruleSimilarity->rate + ]; + } elseif ($ruleSet->rulesCount>0 && $rule){ + if(!$ruleSimilarity){ + $ruleSimilarity = $rule; + } + $ruleObj = $this->rulesFacade->findRule($rule); + $ruleParts = [ + "ant" => $this->decomposeCedent($ruleObj->antecedent), + "con" => $this->decomposeCedent($ruleObj->consequent) + ]; + $result['max'] = 0; + $result['rule'] = [ + RuleRuleRelationsRepository::COLUMN_RELATION => '' + ]; + + $ruleCompareResults = $this->knowledgeBaseFacade->getRulesComparingResults($rule, $id); + if(count($ruleCompareResults) > 0){ + $bestResult = array_slice($ruleCompareResults,0,1)[0]; + if($bestResult[RuleRuleRelationsRepository::COLUMN_RATE] > 0){ + $result['rule'] = $bestResult; + $result['max'] = $bestResult[RuleRuleRelationsRepository::COLUMN_RATE]; + } + } + + foreach($this->ruleSetsFacade->findRulesByRuleSet($ruleSet, null) as $ruleSetRule){ + if(isset($ruleCompareResults[$ruleSetRule->ruleId])){ // has been already compared and is impossible to have higher rate due to DB ordering + continue; + } + $compareResult = $this->compareRules($ruleParts, $ruleSetRule); + if($compareResult[RuleRuleRelationsRepository::COLUMN_RATE] > $result['max']){ + $result['max'] = $compareResult[RuleRuleRelationsRepository::COLUMN_RATE]; + $result['rule'] = $compareResult; + } + $compareResult[RuleRuleRelationsRepository::COLUMN_RULE] = $rule; + $compareResult[RuleRuleRelationsRepository::COLUMN_RULE_SET] = $id; + $compareResults[] = $compareResult; + } + + if($result['max'] > 0){ + try{ + $this->knowledgeBaseFacade->addRuleToKBRuleRelation( + $id, $ruleSimilarity, + $result['rule'][RuleRuleRelationsRepository::COLUMN_RULESET_RULE], + $result['rule'][RuleRuleRelationsRepository::COLUMN_RELATION], + $result['max'] + ); + }catch (\Exception $e){} + } + + $this->knowledgeBaseFacade->saveComparingResults($compareResults); + } + $this->sendJsonResponse($result); + } + + /** + * Method for comparing two rules + * @param Array $ruleParts parts of Rule for comparing + * @param Array $ruleSetRule Rule from Rule set + * @return Array [ID, relation, comparing rate] + */ + private function compareRules($ruleParts, $ruleSetRule){ + $antecedentAttributesCount = $consequentAttributesCount = 0; + $antecedentAttributesSame = $consequentAttributesSame = 0; + $crossAttributesConflict = false; + $ruleSetRuleDecomposed = json_decode($ruleSetRule->decomposed, true); + if(!$ruleSetRuleDecomposed){ // if hasn't rule decomposed yet + $ruleSetRuleDecomposed = [ + "ant" => $this->decomposeCedent($ruleSetRule->antecedent), + "con" => $this->decomposeCedent($ruleSetRule->consequent) + ]; + try{ + $this->knowledgeBaseFacade->setDecomposedRuleSetRule($ruleSetRule->ruleId, json_encode($ruleSetRuleDecomposed)); + }catch (\Exception $e){} + } + foreach($ruleSetRuleDecomposed['ant'] as $attribute => $value){ + if(isset($ruleParts['con'][$attribute]) || array_key_exists($attribute, $ruleParts['con'])){ + $crossAttributesConflict = true; // attribute on opposite site of rule + break; + } + if(isset($ruleParts['ant'][$attribute]) || array_key_exists($attribute, $ruleParts['ant'])){ + $antecedentAttributesSame++; // same attribute + if($ruleParts['ant'][$attribute] == $value){ + $antecedentAttributesSame++; // same value or values bin + } + } + $antecedentAttributesCount++; + } + foreach($ruleSetRuleDecomposed['con'] as $attribute => $value){ + if(isset($ruleParts['ant'][$attribute]) || array_key_exists($attribute, $ruleParts['ant'])){ + $crossAttributesConflict = true; // attribute on opposite site of rule + break; + } + if(isset($ruleParts['con'][$attribute]) || array_key_exists($attribute, $ruleParts['con'])){ + $consequentAttributesSame++; // same attribute + if($ruleParts['con'][$attribute] == $value){ + $consequentAttributesSame++; // same value or values bin + } + } + $consequentAttributesCount++; + } + $sameRateAntecedent = $antecedentAttributesSame/($antecedentAttributesCount+count($ruleParts['ant'])); + $sameRateConsequent = $consequentAttributesSame/($consequentAttributesCount+count($ruleParts['con'])); + $sameRateFinal = $crossAttributesConflict ? 0 : ($sameRateAntecedent+$sameRateConsequent)/2; + + return [ + RuleRuleRelationsRepository::COLUMN_RULESET_RULE => $ruleSetRule->ruleId, + RuleRuleRelationsRepository::COLUMN_RELATION => $ruleSetRule->relation, + RuleRuleRelationsRepository::COLUMN_RATE => $sameRateFinal + ]; + } + + /** + * Method for decomposing Cedent (mostly used recursively!) + * @param Cedent $cedent for decomposing + * @return Array Attribute => Value + */ + private function decomposeCedent($cedent){ + $ruleAttributes = []; + if(!($cedent instanceof Cedent)){ + return $ruleAttributes; + } + foreach($cedent->cedents as $childCedent){ + $ruleAttributes = $ruleAttributes + $this->decomposeCedent($childCedent); + } + foreach($cedent->ruleAttributes as $ruleAttribute){ + if($ruleAttribute->value){ + $ruleAttributes[$ruleAttribute->attribute->preprocessing->preprocessingId] = $ruleAttribute->value->valueId; + } elseif($ruleAttribute->valuesBin){ + $ruleAttributes[$ruleAttribute->attribute->preprocessing->preprocessingId] = $ruleAttribute->valuesBin->valuesBinId; + } else{ + $ruleAttributes[$ruleAttribute->attribute->preprocessing->preprocessingId] = ""; + } + } + return $ruleAttributes; + } + + #endregion actions for rule comparing + #region injections /** * @param RulesFacade $rulesFacade @@ -278,6 +476,12 @@ public function injectRulesFacade(RulesFacade $rulesFacade){ public function injectRuleSetsFacade(RuleSetsFacade $ruleSetsFacade){ $this->ruleSetsFacade=$ruleSetsFacade; } + /** + * @param KnowledgeBaseFacade $knowledgeBaseFacade + */ + public function injectKnowledgeBaseFacade(KnowledgeBaseFacade $knowledgeBaseFacade){ + $this->knowledgeBaseFacade=$knowledgeBaseFacade; + } /** * @param UsersFacade $usersFacade */ diff --git a/app/EasyMinerModule/templates/MiningUi/config.latte b/app/EasyMinerModule/templates/MiningUi/config.latte index 95aab43f..d06b441d 100644 --- a/app/EasyMinerModule/templates/MiningUi/config.latte +++ b/app/EasyMinerModule/templates/MiningUi/config.latte @@ -65,6 +65,8 @@ var Config = new Class({ knowledgeBaseGetRulesUrl: {plink RuleSets:getRules id=>'__RULESETID__',offset=>'-999991999',limit=>'-999992999',order=>'__ORDER__'}.replace('-999991999','__OFFSET__').replace('-999992999','__LIMIT__'), knowledgeBaseGetRuleSetsUrl: {plink RuleSets:list}, + knowledgeBaseGetRulesNamesUrl: {plink RuleSets:getRulesNames id=>'__RULESETID__',miner=>'__MINERID__'}, + knowledgeBaseCompareRuleUrl: {plink RuleSets:compareRuleWithRuleset id=>'__RULESETID__',rule=>'__RULE__'}, knowledgeBaseAddRuleSetUrl: {plink RuleSets:new name=>'__NAME__',description=>'__DESCRIPTION__'}, knowledgeBaseRenameRuleSetUrl: {plink RuleSets:rename id=>'__RULESETID__',name=>'__NAME__',description=>'__DESCRIPTION__'}, @@ -188,6 +190,14 @@ var Config = new Class({ return this.knowledgeBaseGetRulesUrl.replace('__RULESETID__',rulesetId).replace('__OFFSET__',offset).replace('__LIMIT__',limit).replace('__ORDER__',order); }, + getKnowledgeBaseGetRulesNamesUrl: function (rulesetId) { + return this.knowledgeBaseGetRulesNamesUrl.replace('__RULESETID__',rulesetId).replace('__MINERID__',this.getMinerId()); + }, + + getKnowledgeBaseCompareRuleUrl: function (ruleId,rulesetId) { + return this.knowledgeBaseCompareRuleUrl.replace('__RULE__',ruleId).replace('__RULESETID__',rulesetId); + }, + getKnowledgeBaseGetRuleSetsUrl: function () { return this.knowledgeBaseGetRuleSetsUrl; }, diff --git a/app/config/config.neon b/app/config/config.neon index 0dd63ace..f653da64 100644 --- a/app/config/config.neon +++ b/app/config/config.neon @@ -168,6 +168,10 @@ services: - EasyMinerCenter\Model\EasyMiner\Facades\PreprocessingsFacade - EasyMinerCenter\Model\EasyMiner\Facades\RuleSetsFacade + - EasyMinerCenter\Model\EasyMiner\Facades\KnowledgeBaseFacade + - EasyMinerCenter\Model\EasyMiner\Repositories\KnowledgeBaseRuleRelationsRepository + - EasyMinerCenter\Model\EasyMiner\Repositories\RuleRuleRelationsRepository + - EasyMinerCenter\Model\EasyMiner\Repositories\DatasourcesRepository - EasyMinerCenter\Model\EasyMiner\Repositories\DatasourceColumnsRepository - EasyMinerCenter\Model\EasyMiner\Repositories\MetasourcesRepository diff --git a/app/model/easyminer/entities/KnowledgeBaseRuleRelation.php b/app/model/easyminer/entities/KnowledgeBaseRuleRelation.php new file mode 100644 index 00000000..fbc5a311 --- /dev/null +++ b/app/model/easyminer/entities/KnowledgeBaseRuleRelation.php @@ -0,0 +1,25 @@ +$this->name, 'description'=>(!empty($this->description)?$this->description:""), 'rulesCount'=>$this->rulesCount, - 'lastModified'=>(!empty($this->lastModified))?$this->lastModified->format('c'):null + 'lastModified'=>(!empty($this->lastModified))?$this->lastModified->getTimestamp():null ]; } diff --git a/app/model/easyminer/entities/RuleSetRuleRelation.php b/app/model/easyminer/entities/RuleSetRuleRelation.php index fda1e062..cfa580b9 100644 --- a/app/model/easyminer/entities/RuleSetRuleRelation.php +++ b/app/model/easyminer/entities/RuleSetRuleRelation.php @@ -13,6 +13,7 @@ * @property Rule $rule m:hasOne * @property RuleSet $ruleSet m:hasOne * @property string $relation m:Enum(self::RELATION_*) + * @property string $decomposed */ class RuleSetRuleRelation extends Entity{ const RELATION_POSITIVE='positive'; diff --git a/app/model/easyminer/facades/KnowledgeBaseFacade.php b/app/model/easyminer/facades/KnowledgeBaseFacade.php new file mode 100644 index 00000000..1c67f510 --- /dev/null +++ b/app/model/easyminer/facades/KnowledgeBaseFacade.php @@ -0,0 +1,140 @@ +ruleSetsRepository=$ruleSetsRepository; + $this->ruleSetRuleRelationsRepository=$ruleSetRuleRelationsRepository; + $this->knowledgeBaseRuleRelationsRepository=$knowledgeBaseRuleRelationsRepository; + $this->minersFacade=$minersFacade; + $this->rulesFacade=$rulesFacade; + $this->ruleComparingCache=$ruleComparingCache; + } + + /** + * Method for finding rules in Knowledge base from datasource + * @param RuleSet|int $ruleSet + * @param int $minerId + * @return Rule[] + */ + public function findRulesByDatasource($ruleSet,$miner){ + $currentDatasource = $this->minersFacade->findMiner($miner)->getDataArr()['datasourceId']; + $rulesInRuleset = $this->ruleSetRuleRelationsRepository->findAllRulesByRuleSet($ruleSet); + $toReturn = []; + foreach($rulesInRuleset as $rule){ + if($rule->task->miner->datasource->datasourceId === $currentDatasource){ + $toReturn[] = $rule; + } + } + return $toReturn; + } + + /** + * Method for adding/updating relation between Rule and Rule from Knowledge base + * @param KnowledgeBaseRuleRelation|int $rule + * @param int $ruleSetId + * @param int $KBrule + * @param string $relation + * @param int $rate + * @return bool + * @throws \Exception + */ + public function addRuleToKBRuleRelation($ruleSetId, $rule, $KBrule, $relation, $rate){ + if($rule instanceof KnowledgeBaseRuleRelation){ + $ruleToKBRuleRelation = $rule; + } else{ + $ruleToKBRuleRelation=new KnowledgeBaseRuleRelation(); + $ruleToKBRuleRelation->ruleSetId=$ruleSetId; + $ruleToKBRuleRelation->ruleId=$rule; + } + $ruleToKBRuleRelation->knowledgeBaseRuleId=$KBrule; + $ruleToKBRuleRelation->relation=$relation; + $ruleToKBRuleRelation->rate=$rate; + $ruleToKBRuleRelation->resultDate=new \DateTime(); + $result=$this->knowledgeBaseRuleRelationsRepository->persist($ruleToKBRuleRelation); + return $result; + } + + /** + * Method for finding the best saved similarity + * @param int $ruleId + * @return KnowledgeBaseRuleRelation + * @throws \Exception + */ + public function findRuleSimilarity($ruleSetId, $ruleId){ + return $this->knowledgeBaseRuleRelationsRepository->findBy([ + 'rule_set_id'=>$ruleSetId, + 'rule_id'=>$ruleId + ]); + } + + /** + * Method for saving all new comparations + * @param Array $results as INSERT array + */ + public function saveComparingResults($results){ + if(!empty($results)){ + $this->ruleComparingCache->saveComparing($results); + } + } + + /** + * Method for finding all previous comparations with rules from Rule set + * @param $ruleId + * @param $ruleSetId + * @return array + */ + public function getRulesComparingResults($ruleId, $ruleSetId){ + return $this->ruleComparingCache->getComparingHistory($ruleId, $ruleSetId); + } + + /** + * Method for saving decomposed name of rule in Rule set + * @param Int $ruleId + * @param String $decomposed + */ + public function setDecomposedRuleSetRule($ruleId, $decomposed){ + $this->ruleSetRuleRelationsRepository->setDecomposed($ruleId, $decomposed); + } + +} \ No newline at end of file diff --git a/app/model/easyminer/repositories/KnowledgeBaseRuleRelationsRepository.php b/app/model/easyminer/repositories/KnowledgeBaseRuleRelationsRepository.php new file mode 100644 index 00000000..c37b924e --- /dev/null +++ b/app/model/easyminer/repositories/KnowledgeBaseRuleRelationsRepository.php @@ -0,0 +1,19 @@ +connection=$connection; + } + + /** + * Get all history records of comparing of rule with ruleset as associate array with rule_set_rule_id key + * @param $ruleId + * @param $ruleSetId + * @return array + */ + public function getComparingHistory($ruleId, $ruleSetId){ + return $this->connection->select(self::COLUMN_RULESET_RULE.','.self::COLUMN_RELATION.','.self::COLUMN_RATE) + ->from(self::TABLE_NAME)->where(self::COLUMN_RULE_SET.' = ?',$ruleSetId) + ->where(self::COLUMN_RULE.' = ?',$ruleId) + ->orderBy(self::COLUMN_RATE.' DESC')->fetchAssoc(self::COLUMN_RULESET_RULE); + } + + /** + * Multiinsert of comparing results + * @param $data values to be inserted + */ + public function saveComparing($data){ + array_unshift($data, "INSERT INTO " . self::TABLE_NAME); + $this->connection->query($data); + } + +} \ No newline at end of file diff --git a/app/model/easyminer/repositories/RuleSetRuleRelationsRepository.php b/app/model/easyminer/repositories/RuleSetRuleRelationsRepository.php index 3366aec7..68873004 100644 --- a/app/model/easyminer/repositories/RuleSetRuleRelationsRepository.php +++ b/app/model/easyminer/repositories/RuleSetRuleRelationsRepository.php @@ -38,7 +38,7 @@ public function findAllRulesByRuleSet(RuleSet $ruleSet,$order=null,$offset=null, $relevantTable='rules'; /** @var Fluent $query */ - $query=$this->connection->select($relevantTable.'.*') + $query=$this->connection->select($relevantTable.'.*,'.$this->getTable().'.relation,'.$this->getTable().'.decomposed') ->from($relevantTable) ->leftJoin('rule_set_rule_relations')->on($relevantTable.'.rule_id=%n.rule_id',$this->getTable()) ->where('rule_set_id = ?',$ruleSet->ruleSetId); @@ -47,7 +47,6 @@ public function findAllRulesByRuleSet(RuleSet $ruleSet,$order=null,$offset=null, } $entityClass=$this->mapper->getEntityClass($relevantTable); - $ruleRows=$query->fetchAll($offset, $limit); $result=[]; foreach ($ruleRows as $ruleRow){ @@ -72,5 +71,14 @@ public function findCountRulesByRuleSet(RuleSet $ruleSet){ return $result; } + /** + * Nastavení dekomponovaného tvaru pravidla pro všechny jeho výskyty + * @param Int $ruleId + * @param String $decomposed + */ + public function setDecomposed($ruleId, $decomposed){ + return $this->connection->update($this->getTable(), ['decomposed' => $decomposed]) + ->where('rule_id = ?',$ruleId)->execute(); + } } \ No newline at end of file