hyperjumptech / grule-rule-engine

Rule engine implementation in Golang
Other
2.2k stars 339 forks source link

FetchMatchingRules returns an empty array #346

Open goldytech opened 1 year ago

goldytech commented 1 year ago

Describe the bug I want to print the rulename which got successfully executed based on the given input Fact. I tried to use ruleEntries, err := engine.FetchMatchingRules(dataCtx, knowledgeBase) But ruleEntries returns an empty array

To Reproduce Steps to reproduce the behavior: This is my code for Evaulation

func Evaluate(stock *StockInfo) string {

    dataCtx := ast.NewDataContext()
    err := dataCtx.Add("stock", stock)
    if err != nil {
        panic(err)
    }
    knowledgeLibrary := ast.NewKnowledgeLibrary()
    ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary)
    fileRes := pkg.NewFileResource("StocksEvaluation/stock-rule.grl")
    err = ruleBuilder.BuildRuleFromResource("StockEvaluator", "0.0.1", fileRes)
    if err != nil {
        panic(err)
    }
    knowledgeBase := knowledgeLibrary.NewKnowledgeBaseInstance("StockEvaluator", "0.0.1")
    engine := engine.NewGruleEngine()

    err = engine.Execute(dataCtx, knowledgeBase)
    if err != nil {
        panic(err)
    }
    ruleEntries, err := engine.FetchMatchingRules(dataCtx, knowledgeBase)
    if err != nil {
        panic(err)
    }
    for _, ruleEntry := range ruleEntries {
        println(ruleEntry.RuleName)
    }
    return stock.Decision
}
  1. My Fact Object
    
    type StockInfo struct {
    StockName       string
    CashFlow        float64
    Profit          float64
    MarketCap       float64
    EnterpriseValue float64
    Dividend        float64
    PromoterHolding float64
    Decision        string
    }

func (stock *StockInfo) ShouldBuy(yesorno bool) string { //print(yesorno) if yesorno == true { return fmt.Sprintf("Buy %s", stock.StockName) } else { return fmt.Sprintf("Don't Buy %s", stock.StockName) }

}

My grl Rules

rule CashFlowVsNetProfit "Cash Flow is greater than Net Profit" salience 10 { when stock.CashFlow >= stock.Profit then Log("CashFlowVsNetProfit evaluated"); stock.Decision = stock.ShouldBuy(true); Retract("CashFlowVsNetProfit"); } rule MarketCapVsEnterpriseValue "If the enterprise value is way more than market cap, then it can be an alarming sign." salience 8 { when stock.EnterpriseValue > stock.MarketCap then Log("MarketCapVsEnterpriseValue evaluated"); stock.Decision = stock.ShouldBuy(false); Retract("MarketCapVsEnterpriseValue"); }

rule DividendYield "a company that gives a higher dividend might imply that it is not utilising its profits for growth." salience 7 { when stock.Dividend >= 3.0 then Log("Dividend Yield is evaluated"); stock.Decision = stock.ShouldBuy(false); Retract("DividendYield"); }


 I see that the CasFlowVsNetProfit gets successfully evaluated based on my fact input as the log message gets printed. But the array is still an empty object

**Expected behavior**
The array should be an empty object and it should print the rule evaluated.

**Additional context**
None
rajat002 commented 1 year ago

@goldytech The engine.Execute() marks rules as retracted in the knowledgebase once Retract() is called for a rule. You need to call knowledgeBase.Reset() just before the engine.FetchMatchingRules() to retrieve all the matching rules.

rifqifatih commented 1 year ago

@goldytech another solution is to implement your own ExecuteRuleEntry in GruleEngineListener so that you don't have to evaluate twice.

goldytech commented 1 year ago

Thanks, @rajat002 that worked for me. @rifqifatih can you please provide a code snippet for the implementation of your solution?

rifqifatih commented 1 year ago

@goldytech

type RuleListener struct {
    triggeredRuleName string
}

func (rl *RuleListener) ExecuteRuleEntry(cycle uint64, entry *ast.RuleEntry) {
    rl.triggeredRuleName = entry.RuleName
}

func (rl *RuleListener) EvaluateRuleEntry(cycle uint64, entry *ast.RuleEntry, candidate bool) {
    // No Op
}

func (rl *RuleListener) BeginCycle(cycle uint64) {
    // No Op
}

getting the triggered rule name:

listener := &RuleListener{}
gruleEngine := engine.NewGruleEngine()
gruleEngine.Listeners = []engine.GruleEngineListener{listener}

// execute ...

listener.triggeredRuleName