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.
Which rule form to use#
| Form | Use it for | Where it runs | Status |
|---|---|---|---|
Patterns and checksums in a pack's rules and codons | Finding and guarding text: card numbers, identifiers, phrases. Fast and deterministic. | The embedded runtime | Available |
Conditions in a hosted playbook's hard_rules | One decision over a typed state object, with bands and a learning loop. See Close the learning loop. | The reasoning service | Preview |
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 type | Preview |
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
| Annotation | Meaning |
|---|---|
@domain | The business domain the rule belongs to. |
@ontology, @ontology_ver | The ontology, and its version, whose vocabulary the rule uses. |
@uses_classes, @uses_predicates | The classes and predicates of that ontology the rule reads. |
@authority | Where the rule's force comes from: statute, regulation, policy, procedure or guidance. The higher authority wins a conflict. |
@owner | Who answers for the rule. |
@severity | hard or soft. |
@category | A grouping for review and reporting. |
@priority | A number. Between conflicting recommendations of the same authority, the higher priority wins. |
@required_facts | Facts 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. |
@english | The rule as a sentence, for reviewers and for decision reports. |
@status, @effective_from, @effective_to, @jurisdiction, @exceptions | Whether 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 value | A named threshold the next rule's conditions use. |
Any other @name value | Kept with the rule as a custom annotation, for example @headroom_factor 1.0. It is recorded; a condition cannot use it. |
Conditions and conclusions:
| Form | Example |
|---|---|
| All of several conditions | IF A AND B AND C THEN D |
| Any of several alternatives | IF A OR B AND C THEN D (AND binds tighter) |
| Negation: not derived and not a true case fact | NOT HARD_STOP |
| A case fact has a value, whatever it is | KNOWN_SANCTIONS_HIT |
| Comparison with a number | HAS_CREDIT_SCORE >= 550, also ==, !=, >, <, <= |
| Comparison with a code | HAS_INDUSTRY_RISK == HIGH |
| Arithmetic in a comparison | HAS_MONTHLY_INCOME > HAS_MONTHLY_PAYMENT * 2 |
| Several conclusions | THEN RECOMMEND_HOLD AND REFER_RECOMMENDED |
| A decision | RECOMMEND_APPROVE, RECOMMEND_HOLD, RECOMMEND_DECLINE |
| Obligation, prohibition | THEN 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.
@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.| From | Relationship | To | Cardinality | Kind | Asserted or derived | Used by rules |
|---|---|---|---|---|---|---|
| Application | submittedBy | Borrower | * to 1 | depends-on | asserted | |
| Borrower | operatesIn | IndustrySegment | * to 1 | classified-as | asserted | yes |
| CreditFacility | partOfApplication | Application | * to 1 | part-of | asserted | |
| CreditFacility | securedBy | Collateral | * to 1..* | depends-on | asserted | yes |
| Borrower | ownsCollateral | Collateral | 1 to * | owns | asserted | |
| Collateral | locatedAt | Site | * to 1 | located-at | asserted | |
| Application | guaranteedBy | Guarantor | * to * | depends-on | asserted | yes |
| Application | assignedTo | CreditOfficer | * to 0..1 | assigned-to | asserted | yes |
| Collateral | collateralOwner | Borrower | * to 0..1 | owns | derived: inverse of ownsCollateral | |
| CreditFacility | facilityBorrower | Borrower | * to 1 | depends-on | derived: 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.
| Group | Rules | What they do |
|---|---|---|
| Required data | RDAT01 | Derives FILE_COMPLETE when the six core facts are known (KNOWN_), whatever their values. |
| Knockouts | RKO01–RKO08 | Sanctions, bankruptcy, a score below 550 or coverage below 1.0 each derive grounds for decline, and each ground derives HARD_STOP. |
| Risk signals | RRS01–RRS14 | Thin 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. |
| Recommendations | RDEC01–RDEC05 | Decline, 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. |
| Obligations | ROBL01–ROBL06 | A personal guarantee, audited statements, a credit officer, the credit committee, an adverse action notice, and a prohibition on disclosing a sanctions match. |
| Guidance | RGDN01 | A 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.
| Case | Outcome | Deciding rule |
|---|---|---|
| S1-clean-approve Established borrower with strong cash flow and modest leverage. | APPROVE | RDEC05: 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. | APPROVE | RDEC04: 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. | HOLD | RDEC02: Compound risk without a hard stop holds the application for a credit officer. |
| S4-refer-large-exposure A clean file asking for 400000. | HOLD | RDEC03: A large exposure without a hard stop holds the application for a credit officer. |
| S5-decline-sanctions Strong financials, but the sanctions screening matched. | DECLINE | RDEC01: Any hard stop declines the application. |
| S6-decline-cash-flow The business cannot cover the payment: debt service coverage 0.85. | DECLINE | RDEC01: Any hard stop declines the application. |
| S7-missing-data No credit score and no debt service coverage yet. | NEED_DATA | No 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. | DECLINE | RDEC01: 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.
ORsplits 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.