loan_approval.iml

IML example from the CodeLogician agent skill.

(*
   --------------------------------------------------------------------------
   title: Loan Approval System
   name: loan-approval
   description: Multiple conflicting loan approval criteria
   --------------------------------------------------------------------------
 
   Real-world TRICKY example: Loan Approval with Conflicting Criteria
 
   THE TRICKY PROBLEM:
   Loan approval involves multiple criteria that can conflict:
   1. Credit score: <600 reject, 600-700 manual review, >700 approve
   2. Debt-to-income ratio: >43% reject, 36-43% conditional, <36% approve
   3. Employment: <1 year unstable, 1-2 years questionable, >2 years stable
   4. Down payment: <10% risky, 10-20% acceptable, >20% excellent
   5. Special override: Existing customer with perfect payment history can override some rules
 
   TRICKY QUESTIONS:
   - What happens when credit score says "approve" but debt ratio says "reject"?
   - How many scenarios require manual review vs automatic decisions?
   - What are ALL the ways a loan can be automatically approved?
   - Are there contradictory rules that always reject?
 
   WITHOUT decomposition: Complex nested ifs, easy to miss cases
   WITH decomposition: See ALL decision paths, find contradictions
*)
 
type decision = Approved | Rejected | ManualReview
 
(* Calculate base credit tier *)
let credit_tier score =
  if score < 600 then "poor"
  else if score <= 700 then "fair"
  else "good"
 
(* Calculate employment stability score *)
let employment_score years =
  if years < 1 then 0
  else if years < 2 then 1
  else 2
 
(* THE TRICKY FUNCTION: Multiple interacting approval criteria *)
let evaluate_loan credit_score debt_ratio employment_years down_payment_pct is_existing_perfect_customer =
  (* Hard rejections that cannot be overridden *)
  if credit_score < 500 then
    Rejected
  else if debt_ratio > 50 then
    Rejected
  else if employment_years < 0 || down_payment_pct < 0 then
    (* Invalid input *)
    Rejected
  else
    (* Check if customer override applies *)
    if is_existing_perfect_customer && credit_score >= 650 then
      (* Existing perfect customers get approved with relaxed criteria *)
      Approved
    else if credit_score >= 720 && debt_ratio < 36 && employment_years >= 2 && down_payment_pct >= 10 then
      (* Ideal candidate: all green flags *)
      Approved
    else if credit_score < 600 then
      (* Low credit: reject unless perfect customer (handled above) *)
      Rejected
    else if debt_ratio > 43 then
      (* High debt ratio: automatic rejection *)
      Rejected
    else if credit_score >= 680 && debt_ratio <= 40 && employment_years >= 1 then
      (* Good enough for approval *)
      Approved
    else if credit_score >= 600 && employment_years < 1 then
      (* Insufficient employment history: manual review *)
      ManualReview
    else if down_payment_pct < 10 then
      (* Low down payment needs review *)
      ManualReview
    else
      (* All other cases: manual review *)
      ManualReview
[@@decomp top ~basis:[[%id credit_tier]; [%id employment_score]] ()]
 
(* VARIATION: With pruning to remove impossible combinations
   Question: Are there any branches that can never be reached?
*)
let evaluate_loan_pruned credit_score debt_ratio employment_years down_payment_pct is_existing_perfect_customer =
  if credit_score < 500 then
    Rejected
  else if debt_ratio > 50 then
    Rejected
  else if employment_years < 0 || down_payment_pct < 0 then
    Rejected
  else
    if is_existing_perfect_customer && credit_score >= 650 then
      Approved
    else if credit_score >= 720 && debt_ratio < 36 && employment_years >= 2 && down_payment_pct >= 10 then
      Approved
    else if credit_score < 600 then
      Rejected
    else if debt_ratio > 43 then
      Rejected
    else if credit_score >= 680 && debt_ratio <= 40 && employment_years >= 1 then
      Approved
    else if credit_score >= 600 && employment_years < 1 then
      ManualReview
    else if down_payment_pct < 10 then
      ManualReview
    else
      ManualReview
[@@decomp top ~prune:true ~basis:[[%id credit_tier]; [%id employment_score]] ()]
 
(* VARIATION: With pruning to remove impossible combinations *)
let evaluate_loan_valid credit_score debt_ratio employment_years down_payment_pct is_existing_perfect_customer =
  if credit_score < 500 then
    Rejected
  else if debt_ratio > 50 then
    Rejected
  else
    if is_existing_perfect_customer && credit_score >= 650 then
      Approved
    else if credit_score >= 720 && debt_ratio < 36 && employment_years >= 2 && down_payment_pct >= 10 then
      Approved
    else if credit_score < 600 then
      Rejected
    else if debt_ratio > 43 then
      Rejected
    else if credit_score >= 680 && debt_ratio <= 40 && employment_years >= 1 then
      Approved
    else if credit_score >= 600 && employment_years < 1 then
      ManualReview
    else if down_payment_pct < 10 then
      ManualReview
    else
      ManualReview
[@@decomp top ~prune:true ~ctx_simp:true ~basis:[[%id credit_tier]; [%id employment_score]] ()]