{-# LANGUAGE OverloadedStrings #-}
import Test.Hspec
import Test.QuickCheck
import Claims.Types
import Claims.Interpreter
import Claims.Rules
import Claims.Parser
import Claims.Parser.AST
import Data.Time (fromGregorian)
import Data.Decimal (realFracToDecimal)
import Data.Text (Text)
main :: IO ()
main = hspec $ do
describe "Claims.Interpreter" $ do
it "evaluates amount greater than correctly" $ do
let claim = testClaim { totalAmount = realFracToDecimal 2 60000 }
eval claim (greaterThan 50000) `shouldBe` True
eval claim (greaterThan 70000) `shouldBe` False
it "evaluates amount less than correctly" $ do
let claim = testClaim { totalAmount = realFracToDecimal 2 40000 }
eval claim (lessThan 50000) `shouldBe` True
eval claim (lessThan 30000) `shouldBe` False
it "evaluates amount between correctly" $ do
let claim = testClaim { totalAmount = realFracToDecimal 2 50 }
eval claim (between 0 100) `shouldBe` True
eval claim (between 60 100) `shouldBe` False
it "evaluates diagnosis code check correctly" $ do
let claim = testClaim { diagnosisCodes = ["I21.0", "I25.10"] }
eval claim (hasDx "I21.0") `shouldBe` True
eval claim (hasDx "S06") `shouldBe` False
it "evaluates procedure code check correctly" $ do
let claim = testClaim { procedureCodes = ["99223", "93000"] }
eval claim (hasPx "99223") `shouldBe` True
eval claim (hasPx "99221") `shouldBe` False
it "evaluates claim type correctly" $ do
let claim = testClaim { claimType = Inpatient }
eval claim (isType Inpatient) `shouldBe` True
eval claim (isType Outpatient) `shouldBe` False
it "evaluates place of service correctly" $ do
let claim = testClaim { placeOfService = "21" }
eval claim (pos "21") `shouldBe` True
eval claim (pos "23") `shouldBe` False
it "evaluates AND correctly" $ do
let claim = testClaim { totalAmount = realFracToDecimal 2 60000, claimType = Inpatient }
eval claim (greaterThan 50000 `And` isType Inpatient) `shouldBe` True
eval claim (greaterThan 50000 `And` isType Outpatient) `shouldBe` False
it "evaluates OR correctly" $ do
let claim = testClaim { claimType = Inpatient }
eval claim (isType Inpatient `Or` isType Outpatient) `shouldBe` True
eval claim (isType Professional `Or` isType Outpatient) `shouldBe` False
it "evaluates NOT correctly" $ do
let claim = testClaim { claimType = Inpatient }
eval claim (Not (isType Outpatient)) `shouldBe` True
eval claim (Not (isType Inpatient)) `shouldBe` False
describe "Claims.Rules" $ do
it "highValueClaimRule triggers for claims over $50,000" $ do
let claim = testClaim { totalAmount = realFracToDecimal 2 75000 }
eval claim highValueClaimRule `shouldBe` Invalid "Review Required: High value claim exceeds $50,000"
it "highValueClaimRule approves claims under $50,000" $ do
let claim = testClaim { totalAmount = realFracToDecimal 2 40000 }
eval claim highValueClaimRule `shouldBe` Valid
it "erClaimRule rejects ER claims without emergency diagnosis" $ do
let claim = testClaim { placeOfService = "23", diagnosisCodes = ["Z00.00"] }
eval claim erClaimRule `shouldBe` Invalid "ER claim without emergency diagnosis code"
it "erClaimRule approves ER claims with emergency diagnosis" $ do
let claim = testClaim { placeOfService = "23", diagnosisCodes = ["I21"] }
eval claim erClaimRule `shouldBe` Valid
it "inpatientRule rejects inpatient claims without admission code" $ do
let claim = testClaim { claimType = Inpatient, procedureCodes = ["93000"] }
eval claim inpatientRule `shouldBe` Invalid "Inpatient claim missing admission procedure code"
it "inpatientRule approves inpatient claims with admission code" $ do
let claim = testClaim { claimType = Inpatient, procedureCodes = ["99223", "93000"] }
eval claim inpatientRule `shouldBe` Valid
it "outpatientSurgeryRule rejects low-value outpatient surgery claims" $ do
let claim = testClaim { claimType = Outpatient, placeOfService = "24", totalAmount = realFracToDecimal 2 50 }
eval claim outpatientSurgeryRule `shouldBe` Invalid "Outpatient surgical claim amount too low"
it "outpatientSurgeryRule approves normal outpatient surgery claims" $ do
let claim = testClaim { claimType = Outpatient, placeOfService = "24", totalAmount = realFracToDecimal 2 500 }
eval claim outpatientSurgeryRule `shouldBe` Valid
describe "Claims.Parser" $ do
it "parses a simple APPROVE rule" $ do
let input = unlines
[ "RULE SimpleApprove"
, "DESCRIPTION \"Always approve\""
, "WHEN"
, " claim.amount < 1000"
, "THEN"
, " APPROVE"
, "END"
]
case parseRules input of
Left err -> expectationFailure $ "Parse error: " ++ show err
Right [rule] -> do
Claims.Parser.AST.ruleName rule `shouldBe` "SimpleApprove"
ruleDescription rule `shouldBe` "Always approve"
Claims.Parser.AST.action rule `shouldBe` ApproveClaim
Right _ -> expectationFailure "Expected exactly one rule"
it "parses a REJECT rule with amount comparison" $ do
let input = unlines
[ "RULE HighValueReject"
, "DESCRIPTION \"Reject high value claims\""
, "WHEN"
, " claim.amount > 100000"
, "THEN"
, " REJECT \"Amount too high\""
, "END"
]
case parseRules input of
Left err -> expectationFailure $ "Parse error: " ++ show err
Right [rule] -> do
Claims.Parser.AST.action rule `shouldBe` RejectClaim "Amount too high"
Right _ -> expectationFailure "Expected exactly one rule"
it "parses a rule with AND condition" $ do
let input = unlines
[ "RULE ComplexRule"
, "DESCRIPTION \"Complex condition\""
, "WHEN"
, " claim.amount > 5000 AND claim.place_of_service = \"23\""
, "THEN"
, " REQUIRE_REVIEW \"Needs review\""
, "END"
]
case parseRules input of
Left err -> expectationFailure $ "Parse error: " ++ show err
Right [rule] -> do
case Claims.Parser.AST.conditions rule of
AndCond _ _ -> return ()
_ -> expectationFailure "Expected AND condition"
Right _ -> expectationFailure "Expected exactly one rule"
it "parses a rule with diagnosis code check" $ do
let input = unlines
[ "RULE DiagnosisCheck"
, "DESCRIPTION \"Check diagnosis\""
, "WHEN"
, " claim.has_diagnosis \"I21\""
, "THEN"
, " APPROVE"
, "END"
]
case parseRules input of
Left err -> expectationFailure $ "Parse error: " ++ show err
Right [rule] -> do
Claims.Parser.AST.conditions rule `shouldBe` CheckDiagnosis "I21"
Right _ -> expectationFailure "Expected exactly one rule"
-- | Test claim helper
testClaim :: Claim
testClaim = Claim
{ claimId = "TEST001"
, patientId = "PAT001"
, providerId = "PRV001"
, serviceDate = fromGregorian 2025 1 1
, totalAmount = realFracToDecimal 2 1000
, diagnosisCodes = []
, procedureCodes = []
, placeOfService = "11"
, claimType = Professional
}