Decision Engine
Sign Up Now →Sign Up Now

Rule language

Patterns find things in text. Business policy needs more: conditions over typed facts, rules that build on each other, an owner and an authority for every rule, the facts a rule cannot run without, and a sentence a reviewer can read. Helixor's rule language carries all of that in one file, and every rule is bound to the ontology that defines its vocabulary.

PreviewDigital workersConcept + runnable example

Which rule form to use#

FormUse it forWhere it runsStatus
Patterns and checksums in a pack's rules and codonsFinding and guarding text: card numbers, identifiers, phrases. Fast and deterministic.The embedded runtimeAvailable
Conditions in a hosted playbook's hard_rulesOne decision over a typed state object, with bands and a learning loop. See Close the learning loop.The reasoning servicePreview
The rule language (rules.dsl)Business logic over typed facts: thresholds, chained conclusions, knockouts, obligations and prohibitions, with ownership, authority and required facts on every rule.Digital workers, which mount a rulebook on a decision typePreview

The embedded runtime does not run the rule language. Use patterns there for text extraction and guards, and send business decisions over typed facts to a digital worker.

Every form and annotation, with what a digital worker does with each one today, is in the Rule language reference.

Anatomy of a rule#

A rule is one line, ID: IF conditions THEN conclusions, preceded by annotations that apply to it:

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@required_fact_mode explicit
@required_facts [HAS_DEBT_SERVICE_COVERAGE]
@english Debt service coverage below 1.0 means the business cannot carry the payment: grounds for decline.
@uses_predicates [hasDebtServiceCoverage]
@uses_classes [Application]
RKO04: IF FILE_COMPLETE AND HAS_DEBT_SERVICE_COVERAGE < 1.0 THEN DECLINE_GROUNDS_CASHFLOW
AnnotationMeaning
@domainThe business domain the rule belongs to.
@ontology, @ontology_verThe ontology, and its version, whose vocabulary the rule uses.
@uses_classes, @uses_predicatesThe classes and predicates of that ontology the rule reads.
@authorityWhere the rule's force comes from: statute, regulation, policy, procedure or guidance. The higher authority wins a conflict.
@ownerWho answers for the rule.
@severityhard or soft.
@categoryA grouping for review and reporting.
@priorityA number. Between conflicting recommendations of the same authority, the higher priority wins.
@required_factsFacts the rule cannot run without; a rule whose required fact is missing is blocked. With @required_fact_mode explicit only these count; otherwise the case facts the rule's conditions read count too.
@englishThe rule as a sentence, for reviewers and for decision reports.
@status, @effective_from, @effective_to, @jurisdiction, @exceptionsWhether the rule can fire for a case: its life-cycle status, the dates it is in effect, where it applies, and the rules that are exceptions to it.
@param name valueA named threshold the next rule's conditions use.
Any other @name valueKept with the rule as a custom annotation, for example @headroom_factor 1.0. It is recorded; a condition cannot use it.

Conditions and conclusions:

FormExample
All of several conditionsIF A AND B AND C THEN D
Any of several alternativesIF A OR B AND C THEN D (AND binds tighter)
Negation: not derived and not a true case factNOT HARD_STOP
A case fact has a value, whatever it isKNOWN_SANCTIONS_HIT
Comparison with a numberHAS_CREDIT_SCORE >= 550, also ==, !=, >, <, <=
Comparison with a codeHAS_INDUSTRY_RISK == HIGH
Arithmetic in a comparisonHAS_MONTHLY_INCOME > HAS_MONTHLY_PAYMENT * 2
Several conclusionsTHEN RECOMMEND_HOLD AND REFER_RECOMMENDED
A decisionRECOMMEND_APPROVE, RECOMMEND_HOLD, RECOMMEND_DECLINE
Obligation, prohibitionTHEN OBLIGATION(assign_credit_officer), THEN PROHIBITION(disclose_screening_result)
Comment# anything to the end of the line, at the start of a line or after a space

The file is parsed, normalized, lowered into a typed intermediate form and validated. Loading fails closed: a form the engine cannot evaluate, an unknown status or authority, a duplicate id, a cycle through NOT, or vocabulary the rulebook's ontology does not declare is rejected with a diagnostic naming the rule, and the rulebook does not load. File order does not matter: the engine orders rules by what they negate and repeats them until nothing new is derived.

The ontology is the vocabulary#

An ontology declares the classes a decision is about (Borrower, Application, CreditFacility and so on), the attributes of each with their types, and the relationships between them. That declared vocabulary is what rules are written against. Each rule names the parts it reads in @uses_classes and @uses_predicates, and @ontology with @ontology_ver pins the version. The diagram is generated from the example's ontology file; highlighted parts are the ones the rulebook declares it uses.

submittedBy depends-on * 1 operatesIn classified-as * 1 partOfApplication part-of * 1 securedBy depends-on * 1..* ownsCollateral owns 1 * locatedAt located-at * 1 guaranteedBy depends-on * * assignedTo assigned-to * 0..1 collateralOwner (derived) owns * 0..1 facilityBorrower (derived) depends-on * 1 Borrower hasYearsInBusiness number hasCreditScore number hasSanctionsHit number hasRecentBankruptcy number hasCustomerYears number hasPaymentDefaults number IndustrySegment hasRiskCode string Application hasRequestedAmount number hasDebtServiceCoverage number hasAuditedFinancials number CreditFacility hasLoanToValue number Collateral hasAppraisedValue number Site hasRegion string Guarantor (no attributes) CreditOfficer (no attributes)
Generated from the ontology file of the rule example. Blue boxes, attributes and lines are the classes and predicates the rules declare in @uses_classes and @uses_predicates. Dashed lines are derived relationships. Labels at each end of a line are cardinalities. Declared in the ontology but used by no rule: classes Site; predicates hasAppraisedValue, hasRegion, submittedBy, partOfApplication, ownsCollateral, locatedAt, collateralOwner, facilityBorrower.
FromRelationshipToCardinalityKindAsserted or derivedUsed by rules
ApplicationsubmittedByBorrower* to 1depends-onasserted
BorroweroperatesInIndustrySegment* to 1classified-asassertedyes
CreditFacilitypartOfApplicationApplication* to 1part-ofasserted
CreditFacilitysecuredByCollateral* to 1..*depends-onassertedyes
BorrowerownsCollateralCollateral1 to *ownsasserted
CollaterallocatedAtSite* to 1located-atasserted
ApplicationguaranteedByGuarantor* to *depends-onassertedyes
ApplicationassignedToCreditOfficer* to 0..1assigned-toassertedyes
CollateralcollateralOwnerBorrower* to 0..1ownsderived: inverse of ownsCollateral
CreditFacilityfacilityBorrowerBorrower* to 1depends-onderived: chain partOfApplication then submittedBy

How facts enter#

Case data is typed against the rulebook's ontology: each value becomes a statement about an instance of a class. Rules then match facts in working memory, where each case value name appears as HAS_NAME with its value and as KNOWN_NAME, true whenever it has one. A plain HAS_NAME condition is false for 0 and false. The ontology connects the two: the predicate hasCreditScore is read from the fact credit_score, which rules see as HAS_CREDIT_SCORE.

This run follows one case, the compound-risk scenario below, through those steps:

1. S3-refer-compound-risk typed against example_credit_ops v1
   Borrower:B-1003 hasYearsInBusiness = 4   (fact years_in_business)
   Borrower:B-1003 hasCreditScore = 612   (fact credit_score)
   Borrower:B-1003 hasSanctionsHit = 0   (fact sanctions_hit)
   Borrower:B-1003 hasRecentBankruptcy = 0   (fact recent_bankruptcy)
   Borrower:B-1003 hasCustomerYears = 0   (fact customer_years)
   Borrower:B-1003 hasPaymentDefaults = 0   (fact payment_defaults)
   IndustrySegment:IND-ST hasRiskCode = STANDARD   (fact industry_risk)
   Application:A-2203 hasRequestedAmount = 90000   (fact requested_amount)
   Application:A-2203 hasDebtServiceCoverage = 1.12   (fact debt_service_coverage)
   Application:A-2203 hasAuditedFinancials = 1   (fact audited_financials)
   CreditFacility:F-3303 hasLoanToValue = 0.6   (fact loan_to_value)

2. working memory: HAS_CREDIT_SCORE = 612
   RDAT01 reads the credit score -> FILE_COMPLETE: fired
   RKO03 reads the credit score -> DECLINE_GROUNDS_CREDIT: did not fire
   RRS02 reads the credit score -> WEAK_CREDIT: fired
   derived during the run (not in the ontology): FILE_COMPLETE THIN_CASHFLOW WEAK_CREDIT COMPOUND_RISK ELEVATED_RISK RECOMMEND_HOLD REFER_RECOMMENDED OBLIGATION_ASSIGN_CREDIT_OFFICER

The credit score of 612 is typed as hasCreditScore on the borrower, enters working memory as HAS_CREDIT_SCORE = 612, completes the file, and fires RRS02 (weak credit) but not the 550 knockout. The facts derived during the run, such as FILE_COMPLETE and ELEVATED_RISK, extend the vocabulary for that run only; they are not added to the ontology.

What is checked#

Here a rule that reads hasCreditRating, which the ontology does not declare, is added to the rulebook:

3. rulebook plus RBAD01 (uses hasCreditRating), loaded bound to the ontology
   engine: rejected, 2 problems
     RBAD01: ontology_predicate_unknown: @uses_predicates hasCreditRating is not declared by ontology example_credit_ops
     RBAD01: ontology_fact_unknown: HAS_CREDIT_RATING reads case fact credit_rating, which no predicate of ontology example_credit_ops carries

The engine rejects it when it loads the rulebook bound to the ontology: a predicate or class the ontology does not declare, or a fact no predicate carries, stops the load with a diagnostic per problem. A rulebook that names an ontology the digital worker does not have does not load either; the example's rulebook ships its ontology file with it.

Changing the vocabulary#

Adding a term, such as hasDebtServiceCoverage, is a change to the ontology, released as a new version. Rules that use the term cite that version in @ontology_ver, and the rulebook is released as a new version too. The running version never changes in place. Codons, symbols and a vocabulary that changes walks through it.

A complete rulebook#

The example is a fictional equipment credit line: 35 rules in six groups.

GroupRulesWhat they do
Required dataRDAT01Derives FILE_COMPLETE when the six core facts are known (KNOWN_), whatever their values.
KnockoutsRKO01–RKO08Sanctions, bankruptcy, a score below 550 or coverage below 1.0 each derive grounds for decline, and each ground derives HARD_STOP.
Risk signalsRRS01–RRS14Thin cash flow, weak credit, a young business, high leverage, a high-risk industry and a large exposure; pairs of signals derive COMPOUND_RISK, any single one ELEVATED_RISK.
RecommendationsRDEC01–RDEC05Decline, hold for a credit officer, approve with conditions, or approve. Each recommendation is guarded with NOT on the stronger ones, so they cannot both fire.
ObligationsROBL01–ROBL06A personal guarantee, audited statements, a credit officer, the credit committee, an adverse action notice, and a prohibition on disclosing a sanctions match.
GuidanceRGDN01A relationship rule with guidance authority that suggests approval for long-standing customers.
# Example equipment credit line rulebook in the Helixor rule language.
# Fictional domain for helixor.dev. Classes and predicates: credit_ops.ontology.json.
# Each case fact <name> reaches the rules as HAS_<NAME>, carrying its value, and KNOWN_<NAME>, true when it has one.
# File order does not matter: the engine orders rules by what they negate and repeats them until nothing new is derived.
# Recommendations are resolved by @authority, then @priority, then decline > hold > approve.

# ---------------------------------------------------------------- required data

@domain commercial_credit
@ontology example_credit_ops
@ontology_ver 1
@authority policy
@owner credit-policy
@severity hard
@category data
@priority 100
@required_fact_mode explicit
@required_facts [HAS_CREDIT_SCORE, HAS_DEBT_SERVICE_COVERAGE, HAS_YEARS_IN_BUSINESS, HAS_REQUESTED_AMOUNT, HAS_SANCTIONS_HIT, HAS_RECENT_BANKRUPTCY]
@english The file is complete when the credit score, debt service coverage, years in business, requested amount and both screening results are all known.
@uses_predicates [hasCreditScore, hasDebtServiceCoverage, hasYearsInBusiness, hasRequestedAmount, hasSanctionsHit, hasRecentBankruptcy]
@uses_classes [Borrower, Application]
RDAT01: IF KNOWN_CREDIT_SCORE AND KNOWN_DEBT_SERVICE_COVERAGE AND KNOWN_YEARS_IN_BUSINESS AND KNOWN_REQUESTED_AMOUNT AND KNOWN_SANCTIONS_HIT AND KNOWN_RECENT_BANKRUPTCY THEN FILE_COMPLETE

# ---------------------------------------------------------------- knockouts

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@required_fact_mode explicit
@required_facts [HAS_SANCTIONS_HIT]
@english A borrower with a sanctions screening hit has grounds for decline.
@uses_predicates [hasSanctionsHit]
@uses_classes [Borrower]
RKO01: IF FILE_COMPLETE AND HAS_SANCTIONS_HIT == 1 THEN DECLINE_GROUNDS_SANCTIONS

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@required_fact_mode explicit
@required_facts [HAS_RECENT_BANKRUPTCY]
@english A borrower with a bankruptcy in the last seven years has grounds for decline.
@uses_predicates [hasRecentBankruptcy]
@uses_classes [Borrower]
RKO02: IF FILE_COMPLETE AND HAS_RECENT_BANKRUPTCY == 1 THEN DECLINE_GROUNDS_BANKRUPTCY

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@required_fact_mode explicit
@required_facts [HAS_CREDIT_SCORE]
@english A credit score below 550 is grounds for decline.
@uses_predicates [hasCreditScore]
@uses_classes [Borrower]
RKO03: IF FILE_COMPLETE AND HAS_CREDIT_SCORE < 550 THEN DECLINE_GROUNDS_CREDIT

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@required_fact_mode explicit
@required_facts [HAS_DEBT_SERVICE_COVERAGE]
@english Debt service coverage below 1.0 means the business cannot carry the payment: grounds for decline.
@uses_predicates [hasDebtServiceCoverage]
@uses_classes [Application]
RKO04: IF FILE_COMPLETE AND HAS_DEBT_SERVICE_COVERAGE < 1.0 THEN DECLINE_GROUNDS_CASHFLOW

@domain commercial_credit
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@english Sanctions grounds stop the application.
RKO05: IF DECLINE_GROUNDS_SANCTIONS THEN HARD_STOP

@domain commercial_credit
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@english Bankruptcy grounds stop the application.
RKO06: IF DECLINE_GROUNDS_BANKRUPTCY THEN HARD_STOP

@domain commercial_credit
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@english Credit score grounds stop the application.
RKO07: IF DECLINE_GROUNDS_CREDIT THEN HARD_STOP

@domain commercial_credit
@authority policy
@owner credit-policy
@severity hard
@category knockout
@priority 100
@english Cash flow grounds stop the application.
RKO08: IF DECLINE_GROUNDS_CASHFLOW THEN HARD_STOP

# ---------------------------------------------------------------- risk signals

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@required_fact_mode explicit
@required_facts [HAS_DEBT_SERVICE_COVERAGE]
@english Debt service coverage from 1.0 up to 1.25 is thin cash flow.
@uses_predicates [hasDebtServiceCoverage]
@uses_classes [Application]
RRS01: IF FILE_COMPLETE AND HAS_DEBT_SERVICE_COVERAGE >= 1.0 AND HAS_DEBT_SERVICE_COVERAGE < 1.25 THEN THIN_CASHFLOW

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@required_fact_mode explicit
@required_facts [HAS_CREDIT_SCORE]
@english A credit score from 550 up to 650 is weak credit.
@uses_predicates [hasCreditScore]
@uses_classes [Borrower]
RRS02: IF FILE_COMPLETE AND HAS_CREDIT_SCORE >= 550 AND HAS_CREDIT_SCORE < 650 THEN WEAK_CREDIT

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@required_fact_mode explicit
@required_facts [HAS_YEARS_IN_BUSINESS]
@english A business trading for less than two years is a young business.
@uses_predicates [hasYearsInBusiness]
@uses_classes [Borrower]
RRS03: IF FILE_COMPLETE AND HAS_YEARS_IN_BUSINESS < 2 THEN YOUNG_BUSINESS

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@required_fact_mode explicit
@required_facts [HAS_LOAN_TO_VALUE]
@english A facility above 80 percent of the collateral value is highly leveraged.
@uses_predicates [hasLoanToValue, securedBy]
@uses_classes [CreditFacility, Collateral]
RRS04: IF HAS_LOAN_TO_VALUE > 0.8 THEN HIGH_LEVERAGE

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@required_fact_mode explicit
@required_facts [HAS_INDUSTRY_RISK]
@english A borrower in an industry segment coded HIGH is in a high-risk industry.
@uses_predicates [hasRiskCode, operatesIn]
@uses_classes [Borrower, IndustrySegment]
RRS05: IF HAS_INDUSTRY_RISK == HIGH THEN HIGH_RISK_INDUSTRY

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-policy
@severity hard
@category exposure
@priority 80
@required_fact_mode explicit
@required_facts [HAS_REQUESTED_AMOUNT]
@english A request above 250000 is a large exposure.
@uses_predicates [hasRequestedAmount]
@uses_classes [Application]
RRS06: IF FILE_COMPLETE AND HAS_REQUESTED_AMOUNT > 250000 THEN LARGE_EXPOSURE

@domain commercial_credit
@authority policy
@owner credit-risk
@severity hard
@category risk-signal
@priority 70
@english Thin cash flow together with weak credit is compound risk.
RRS07: IF THIN_CASHFLOW AND WEAK_CREDIT THEN COMPOUND_RISK

@domain commercial_credit
@authority policy
@owner credit-risk
@severity hard
@category risk-signal
@priority 70
@english A young business in a high-risk industry is compound risk.
RRS08: IF YOUNG_BUSINESS AND HIGH_RISK_INDUSTRY THEN COMPOUND_RISK

@domain commercial_credit
@authority policy
@owner credit-risk
@severity hard
@category risk-signal
@priority 70
@english High leverage together with thin cash flow is compound risk.
RRS09: IF HIGH_LEVERAGE AND THIN_CASHFLOW THEN COMPOUND_RISK

@domain commercial_credit
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@english Thin cash flow on its own is elevated risk.
RRS10: IF THIN_CASHFLOW THEN ELEVATED_RISK

@domain commercial_credit
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@english Weak credit on its own is elevated risk.
RRS11: IF WEAK_CREDIT THEN ELEVATED_RISK

@domain commercial_credit
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@english A young business on its own is elevated risk.
RRS12: IF YOUNG_BUSINESS THEN ELEVATED_RISK

@domain commercial_credit
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@english High leverage on its own is elevated risk.
RRS13: IF HIGH_LEVERAGE THEN ELEVATED_RISK

@domain commercial_credit
@authority policy
@owner credit-risk
@severity soft
@category risk-signal
@priority 50
@english A high-risk industry on its own is elevated risk.
RRS14: IF HIGH_RISK_INDUSTRY THEN ELEVATED_RISK

# ---------------------------------------------------------------- recommendations

@domain commercial_credit
@authority policy
@owner credit-policy
@severity hard
@category decision
@priority 100
@english Any hard stop declines the application.
RDEC01: IF HARD_STOP THEN RECOMMEND_DECLINE

@domain commercial_credit
@authority policy
@owner credit-policy
@severity hard
@category decision
@priority 80
@english Compound risk without a hard stop holds the application for a credit officer.
RDEC02: IF COMPOUND_RISK AND NOT HARD_STOP THEN RECOMMEND_HOLD AND REFER_RECOMMENDED

@domain commercial_credit
@authority policy
@owner credit-policy
@severity hard
@category decision
@priority 80
@english A large exposure without a hard stop holds the application for a credit officer.
RDEC03: IF LARGE_EXPOSURE AND NOT HARD_STOP THEN RECOMMEND_HOLD AND REFER_RECOMMENDED

@domain commercial_credit
@authority policy
@owner credit-policy
@severity soft
@category decision
@priority 60
@english A complete file with a single elevated risk and no hold grounds is approved with conditions.
RDEC04: IF FILE_COMPLETE AND ELEVATED_RISK AND NOT COMPOUND_RISK AND NOT LARGE_EXPOSURE AND NOT HARD_STOP THEN RECOMMEND_APPROVE AND CONDITIONS_REQUIRED

@domain commercial_credit
@authority policy
@owner credit-policy
@severity soft
@category decision
@priority 40
@english A complete file with no risk signal and no hold grounds is approved.
RDEC05: IF FILE_COMPLETE AND NOT ELEVATED_RISK AND NOT LARGE_EXPOSURE AND NOT HARD_STOP THEN RECOMMEND_APPROVE

# ---------------------------------------------------------------- obligations

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-operations
@severity hard
@category obligation
@priority 60
@english An approval with conditions on a highly leveraged facility needs a personal guarantee.
@uses_predicates [guaranteedBy]
@uses_classes [Application, Guarantor]
ROBL01: IF CONDITIONS_REQUIRED AND HIGH_LEVERAGE THEN OBLIGATION(request_personal_guarantee)

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-operations
@severity hard
@category obligation
@priority 60
@required_fact_mode explicit
@required_facts [HAS_AUDITED_FINANCIALS]
@english An approval with conditions without audited financial statements must collect them before funding.
@uses_predicates [hasAuditedFinancials]
@uses_classes [Application]
ROBL02: IF CONDITIONS_REQUIRED AND HAS_AUDITED_FINANCIALS == 0 THEN OBLIGATION(collect_audited_financials)

@domain commercial_credit
@ontology example_credit_ops
@authority policy
@owner credit-operations
@severity hard
@category obligation
@priority 80
@english A referred application is assigned to a credit officer.
@uses_predicates [assignedTo]
@uses_classes [Application, CreditOfficer]
ROBL03: IF REFER_RECOMMENDED THEN OBLIGATION(assign_credit_officer)

@domain commercial_credit
@authority policy
@owner credit-operations
@severity hard
@category obligation
@priority 80
@english A large exposure goes to the credit committee.
ROBL04: IF LARGE_EXPOSURE AND NOT HARD_STOP THEN OBLIGATION(credit_committee_review)

@domain commercial_credit
@authority policy
@owner credit-operations
@severity hard
@category obligation
@priority 100
@english A decline requires an adverse action notice to the applicant.
ROBL05: IF RECOMMEND_DECLINE THEN OBLIGATION(send_adverse_action_notice)

@domain commercial_credit
@authority policy
@owner compliance
@severity hard
@category obligation
@priority 100
@english The applicant is never told that a sanctions screening matched.
ROBL06: IF DECLINE_GROUNDS_SANCTIONS THEN PROHIBITION(disclose_screening_result)

# ---------------------------------------------------------------- relationship guidance

@domain commercial_credit
@ontology example_credit_ops
@authority guidance
@owner relationship-banking
@severity soft
@category guidance
@priority 10
@required_fact_mode explicit
@required_facts [HAS_CUSTOMER_YEARS, HAS_PAYMENT_DEFAULTS]
@english A customer of five years or more with no payment defaults is suggested for approval.
@uses_predicates [hasCustomerYears, hasPaymentDefaults]
@uses_classes [Borrower]
RGDN01: IF HAS_CUSTOMER_YEARS >= 5 AND HAS_PAYMENT_DEFAULTS == 0 THEN RECOMMEND_APPROVE AND RELATIONSHIP_APPROVAL_SUGGESTED

Eight cases, run#

Each case went through the full path: the rulebook was parsed and lowered, the case facts became working memory, the digital worker fired the mounted rulebook (credit-line@v1) and turned the engine's resolved decision into its own: APPROVE, HOLD, DECLINE or NEED_DATA.

CaseOutcomeDeciding rule
S1-clean-approve
Established borrower with strong cash flow and modest leverage.
APPROVERDEC05: A complete file with no risk signal and no hold grounds is approved.
S2-approve-with-conditions
Sound borrower, but the facility is 86 percent of the equipment value and the statements are unaudited.
APPROVERDEC04: A complete file with a single elevated risk and no hold grounds is approved with conditions.
S3-refer-compound-risk
Thin cash flow and a weak credit score together.
HOLDRDEC02: Compound risk without a hard stop holds the application for a credit officer.
S4-refer-large-exposure
A clean file asking for 400000.
HOLDRDEC03: A large exposure without a hard stop holds the application for a credit officer.
S5-decline-sanctions
Strong financials, but the sanctions screening matched.
DECLINERDEC01: Any hard stop declines the application.
S6-decline-cash-flow
The business cannot cover the payment: debt service coverage 0.85.
DECLINERDEC01: Any hard stop declines the application.
S7-missing-data
No credit score and no debt service coverage yet.
NEED_DATANo rule ran: missing credit_score, debt_service_coverage
S8-conflict-policy-vs-guidance
A long-standing customer with no defaults, but debt service coverage of 0.92.
DECLINERDEC01: Any hard stop declines the application.

The full report for every case:

ontology example_credit_ops v1: 8 classes, 23 properties
rulebook credit-line@v1: 35 rules, bound to ontology example_credit_ops: yes

== S1-clean-approve: Established borrower with strong cash flow and modest leverage.
facts: credit_score=742, debt_service_coverage=1.82, years_in_business=9, requested_amount=120000, sanctions_hit=0, recent_bankruptcy=0, loan_to_value=0.55, industry_risk=STANDARD, audited_financials=1, customer_years=2, payment_defaults=0
outcome: APPROVE
report: fired; fired 2 of 35 rules
fired: RDAT01 RDEC05
derived facts: FILE_COMPLETE
recommendations: RECOMMEND_APPROVE
obligations: none
deciding rule RDEC05 (policy, soft, priority 40): A complete file with no risk signal and no hold grounds is approved.
resolution: only_candidate: RDEC05 recommends APPROVE

== S2-approve-with-conditions: Sound borrower, but the facility is 86 percent of the equipment value and the statements are unaudited.
facts: credit_score=705, debt_service_coverage=1.6, years_in_business=6, requested_amount=180000, sanctions_hit=0, recent_bankruptcy=0, loan_to_value=0.86, industry_risk=STANDARD, audited_financials=0, customer_years=1, payment_defaults=0
outcome: APPROVE
report: fired; fired 6 of 35 rules
fired: RDAT01 RRS04 RRS13 RDEC04 ROBL01 ROBL02
derived facts: FILE_COMPLETE HIGH_LEVERAGE ELEVATED_RISK CONDITIONS_REQUIRED
recommendations: RECOMMEND_APPROVE
obligations: OBLIGATION_REQUEST_PERSONAL_GUARANTEE OBLIGATION_COLLECT_AUDITED_FINANCIALS
deciding rule RDEC04 (policy, soft, priority 60): A complete file with a single elevated risk and no hold grounds is approved with conditions.
resolution: only_candidate: RDEC04 recommends APPROVE

== S3-refer-compound-risk: Thin cash flow and a weak credit score together.
facts: credit_score=612, debt_service_coverage=1.12, years_in_business=4, requested_amount=90000, sanctions_hit=0, recent_bankruptcy=0, loan_to_value=0.6, industry_risk=STANDARD, audited_financials=1, customer_years=0, payment_defaults=0
outcome: HOLD
report: fired; fired 8 of 35 rules
fired: RDAT01 RRS01 RRS02 RRS07 RRS10 RRS11 RDEC02 ROBL03
derived facts: FILE_COMPLETE THIN_CASHFLOW WEAK_CREDIT COMPOUND_RISK ELEVATED_RISK REFER_RECOMMENDED
recommendations: RECOMMEND_HOLD
obligations: OBLIGATION_ASSIGN_CREDIT_OFFICER
deciding rule RDEC02 (policy, hard, priority 80): Compound risk without a hard stop holds the application for a credit officer.
resolution: only_candidate: RDEC02 recommends HOLD

== S4-refer-large-exposure: A clean file asking for 400000.
facts: credit_score=760, debt_service_coverage=2.1, years_in_business=15, requested_amount=400000, sanctions_hit=0, recent_bankruptcy=0, loan_to_value=0.5, industry_risk=STANDARD, audited_financials=1, customer_years=3, payment_defaults=0
outcome: HOLD
report: fired; fired 5 of 35 rules
fired: RDAT01 RRS06 RDEC03 ROBL03 ROBL04
derived facts: FILE_COMPLETE LARGE_EXPOSURE REFER_RECOMMENDED
recommendations: RECOMMEND_HOLD
obligations: OBLIGATION_ASSIGN_CREDIT_OFFICER OBLIGATION_CREDIT_COMMITTEE_REVIEW
deciding rule RDEC03 (policy, hard, priority 80): A large exposure without a hard stop holds the application for a credit officer.
resolution: only_candidate: RDEC03 recommends HOLD

== S5-decline-sanctions: Strong financials, but the sanctions screening matched.
facts: credit_score=731, debt_service_coverage=1.9, years_in_business=7, requested_amount=150000, sanctions_hit=1, recent_bankruptcy=0, loan_to_value=0.5, industry_risk=STANDARD, audited_financials=1, customer_years=0, payment_defaults=0
outcome: DECLINE
report: fired; fired 6 of 35 rules
fired: RDAT01 RKO01 RKO05 RDEC01 ROBL05 ROBL06
derived facts: FILE_COMPLETE DECLINE_GROUNDS_SANCTIONS HARD_STOP
recommendations: RECOMMEND_DECLINE
obligations: OBLIGATION_SEND_ADVERSE_ACTION_NOTICE PROHIBITION_DISCLOSE_SCREENING_RESULT
deciding rule RDEC01 (policy, hard, priority 100): Any hard stop declines the application.
resolution: only_candidate: RDEC01 recommends DECLINE

== S6-decline-cash-flow: The business cannot cover the payment: debt service coverage 0.85.
facts: credit_score=688, debt_service_coverage=0.85, years_in_business=5, requested_amount=75000, sanctions_hit=0, recent_bankruptcy=0, loan_to_value=0.7, industry_risk=HIGH, audited_financials=1, customer_years=0, payment_defaults=0
outcome: DECLINE
report: fired; fired 7 of 35 rules
fired: RDAT01 RKO04 RKO08 RRS05 RRS14 RDEC01 ROBL05
derived facts: FILE_COMPLETE DECLINE_GROUNDS_CASHFLOW HARD_STOP HIGH_RISK_INDUSTRY ELEVATED_RISK
recommendations: RECOMMEND_DECLINE
obligations: OBLIGATION_SEND_ADVERSE_ACTION_NOTICE
deciding rule RDEC01 (policy, hard, priority 100): Any hard stop declines the application.
resolution: only_candidate: RDEC01 recommends DECLINE

== S7-missing-data: No credit score and no debt service coverage yet.
facts: years_in_business=3, requested_amount=60000, sanctions_hit=0, recent_bankruptcy=0, loan_to_value=0.65, industry_risk=STANDARD
outcome: NEED_DATA
missing facts: credit_score, debt_service_coverage
pack not fired: the decision type's required facts are missing
readiness: missing_facts; case facts missing: HAS_AUDITED_FINANCIALS, HAS_CREDIT_SCORE, HAS_CUSTOMER_YEARS, HAS_DEBT_SERVICE_COVERAGE, HAS_PAYMENT_DEFAULTS
  RDAT01 cannot run without HAS_CREDIT_SCORE, HAS_DEBT_SERVICE_COVERAGE
  RGDN01 cannot run without HAS_CUSTOMER_YEARS, HAS_PAYMENT_DEFAULTS
  RKO03 cannot run without HAS_CREDIT_SCORE
  RKO04 cannot run without HAS_DEBT_SERVICE_COVERAGE
  ROBL02 cannot run without HAS_AUDITED_FINANCIALS
  RRS01 cannot run without HAS_DEBT_SERVICE_COVERAGE
  RRS02 cannot run without HAS_CREDIT_SCORE

== S8-conflict-policy-vs-guidance: A long-standing customer with no defaults, but debt service coverage of 0.92.
facts: credit_score=701, debt_service_coverage=0.92, years_in_business=12, requested_amount=110000, sanctions_hit=0, recent_bankruptcy=0, loan_to_value=0.6, industry_risk=STANDARD, audited_financials=1, customer_years=8, payment_defaults=0
outcome: DECLINE
report: fired; fired 6 of 35 rules
fired: RDAT01 RKO04 RKO08 RDEC01 ROBL05 RGDN01
derived facts: FILE_COMPLETE DECLINE_GROUNDS_CASHFLOW HARD_STOP RELATIONSHIP_APPROVAL_SUGGESTED
recommendations: RECOMMEND_DECLINE RECOMMEND_APPROVE
obligations: OBLIGATION_SEND_ADVERSE_ACTION_NOTICE
deciding rule RDEC01 (policy, hard, priority 100): Any hard stop declines the application.
overridden RGDN01 (guidance, priority 10): A customer of five years or more with no payment defaults is suggested for approval.
resolution: authority: RDEC01 (policy) outranks RGDN01 (guidance): DECLINE over APPROVE

Conflicts#

Case S8 shows two recommendations firing: policy rule RDEC01 declines the application, and guidance rule RGDN01 suggests approval for a long-standing customer. The engine resolves conflicts by @authority first, then @priority, then the more conservative outcome (decline beats hold beats approve), and records which rules competed, which won and why: here policy outranks guidance. Rules RDEC02–RDEC05 also write precedence into their conditions with NOT HARD_STOP, so they never compete.

Missing facts#

Case S7 has no credit score and no debt service coverage. The decision type lists the facts it needs, so the worker returns NEED_DATA with the missing names before any rule runs. Inside a run, a rule whose required facts are missing is blocked, and when that could change the decision the outcome is NEED_DATA too. The readiness check reports, per rule, which declared facts are missing.

Limits#

  • Derived facts are flags. A rule cannot set a value; compute values before the case reaches the rules, as the example does with debt service coverage and loan to value, or compute in a comparison.
  • No grouping. OR splits a condition into alternatives; there are no parentheses around conditions.
  • Exact jurisdictions. A rule for one jurisdiction code does not apply to a sub-jurisdiction.
  • More. The forms the engine rejects and every deliberate limit are in the reference: Rejected forms and Deliberate limits.

Next steps#