packages feed

claims-x12-dsl-0.1.0.0: test/Spec.hs

{-# 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
  }