{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} module Prosper.Listing ( Listing(..) , Offer(..) , Credit(..) , Rating(..) , Category(..) , Status(..) ) where import Control.Applicative (pure, (<$>), (<*>)) import Control.Monad (mzero) import Data.Aeson import Data.Text.Read as R import Data.Typeable import GHC.Generics import Prosper.Money data Listing = Listing { listingId :: !Int , status :: !Status , rating :: !Rating -- ^ Letter score assigned by Prosper , score :: !(Maybe Int) -- ^ Prosper score , category :: !Category -- ^ The reason for the loan (e.g. Auto, Student, etc) -- Market data , amountRemaining :: !Money , offer :: !Offer -- ^ Contract data , credit :: !Credit -- ^ Credit data } deriving Show -- investmentType :: (Fractional | Whole) -- | Two 'Listing's are equivalent if their 'listingId's are equal instance Eq Listing where (Listing { listingId = id1 }) == (Listing { listingId = id2 }) = id1 == id2 -- | Data related to the listing's offer and contract terms data Offer = Offer { requestAmount :: !Money , rate :: !Double -- ^ Interest rate for the borrower , termInMonths :: !Int -- ^ Integer number of months , yield :: !Double , effectiveYield :: !Double , apr :: !Double -- ^ APR for the borrower } deriving Show -- estimatedLossRate :: Double -- Maybe Money? -- estimatedReturn :: Double -- Maybe Money? -- startDate :: Date -- Add dates later -- creationDate :: Date -- verificationStage :: Maybe Int -- Don't know what VerificationStage is... -- Add WholeLoanStartDate and WholeLoanEndDate -- | Data related to the credibility of the listing data Credit = Credit { fico :: !Int , bankcardUtilization :: !Double , isHomeowner :: !Bool , debtToIncome :: !Double , monthsEmployed :: !(Maybe Int) , currentDelinquencies :: !(Maybe Int) , amountDelinquent :: !(Maybe Money) , openCreditLines :: !(Maybe Int) , totOpenRevolvingAccts :: !(Maybe Int) , revolvingBalance :: !(Maybe Money) , revolvingAvailableCredit :: !(Maybe Int) -- ^ Percent , incomeRange :: !(Maybe (Money, Money)) -- ^ It is possible that they're unemployed, that's Nothing , statedMonthlyIncome :: !(Maybe Money) , currentCreditLines :: !(Maybe Int) , nowDelinquentDerog :: !(Maybe Int) , wasDelinquentDerog :: !(Maybe Int) } deriving Show -- firstCreditLine :: Date -- creditLinesLast7Years :: Int -- inquiriesLast6Months :: Int -- delinquenciesLast7Years :: Int -- These are maybe too specific to Prosper -- publicRecordsLast10Years :: Int -- oldestTradeOpenDate :: Date -- | Parse a 'Listing' from JSON instance FromJSON Listing where parseJSON (Object v) = Listing <$> v .: "ListingNumber" -- Is listingid? -- Prosper-specific data <*> v .: "ListingStatus" <*> v .: "ProsperRating" <*> v .:? "ProsperScore" <*> v .: "ListingCategory" <*> v .: "AmountRemaining" <*> (Offer <$> v .: "ListingRequestAmount" <*> v .: "BorrowerRate" <*> v .: "ListingTerm" <*> v .: "LenderYield" <*> v .: "EffectiveYield" <*> v .: "BorrowerAPR") <*> (Credit <$> (unFico <$> v .: "FICOScore") <*> v .: "BankcardUtilization" <*> v .: "IsHomeowner" <*> v .: "DTIwProsperLoan" <*> v .:? "MonthsEmployed" <*> v .:? "CurrentDelinquencies" <*> v .:? "AmountDelinquent" <*> v .:? "OpenCreditLines" <*> v .:? "TotalOpenRevolvingAccounts" <*> v .:? "RevolvingBalance" <*> v .:? "RevolvingAvailablePercent" -- Prosper credit data <*> (unIR <$> v .: "IncomeRange") <*> v .:? "StatedMonthlyIncome" <*> v .:? "CurrentCreditLines" <*> v .:? "NowDelinquentDerog" <*> v .:? "WasDelinquentDerog") parseJSON _ = mzero -- Prosper's API is really weird and has the FICO type as a String -- in JSON. newtype FICO = FICO { unFico :: Int } deriving Eq instance Show FICO where show (FICO x) = show x instance FromJSON FICO where parseJSON (String s) = readInt s where readInt x = case R.decimal x of Right (y,_) -> pure $ FICO y Left _ -> mzero parseJSON _ = mzero newtype IncomeRange = IR { unIR :: Maybe (Money, Money) } deriving Eq instance Show IncomeRange where show (IR (Just (l, h))) = "$" ++ show l ++ "-$" ++ show h show (IR Nothing) = "Not displayed or unemployed" instance FromJSON IncomeRange where parseJSON (Number 0) = pure $ IR Nothing -- Not displayed parseJSON (Number 1) = pure $ IR (Just (0, 0)) -- Make $0 parseJSON (Number 2) = pure $ IR (Just (1, 24999)) parseJSON (Number 3) = pure $ IR (Just (25000, 49999)) parseJSON (Number 4) = pure $ IR (Just (50000, 74999)) parseJSON (Number 5) = pure $ IR (Just (75000, 99999)) parseJSON (Number 6) = pure $ IR (Just (100000, 500000)) -- DOCTHIS 500,000 is just an arbitrary high value parseJSON (Number 7) = pure $ IR Nothing -- Unemployed parseJSON _ = pure $ IR Nothing -- | Prosper ratings, 'AA' is the most credible. 'HR' is the least credible. data Rating = HR | E | D | C | B | A | AA deriving (Show, Eq, Ord, Typeable, Generic, Read, Enum) instance FromJSON Rating where instance ToJSON Rating where -- | The 'Category' of a loan is the type of loan. -- This is basically copied verbatim from the Prosper API. data Category = NotAvailable | DebtConsolidation | HomeImprovement | Business | PersonalLoan | StudentUse | Auto | Other | BabyAdoptionLoans | Boat | CosmeticProcedures | EngagementRingFinancing | GreenLoans | HouseholdExpenses | LargePurchases | MedicalDental | Motorcycle | RV | Taxes | Vacation | WeddingLoans deriving (Show, Eq, Typeable, Generic, Read) instance FromJSON Category where parseJSON (Number 0) = pure NotAvailable parseJSON (Number 1) = pure DebtConsolidation parseJSON (Number 2) = pure HomeImprovement parseJSON (Number 3) = pure Business parseJSON (Number 4) = pure PersonalLoan parseJSON (Number 5) = pure StudentUse parseJSON (Number 6) = pure Auto parseJSON (Number 7) = pure Other parseJSON (Number 8) = pure BabyAdoptionLoans parseJSON (Number 9) = pure Boat parseJSON (Number 10) = pure CosmeticProcedures parseJSON (Number 11) = pure EngagementRingFinancing parseJSON (Number 12) = pure GreenLoans parseJSON (Number 13) = pure HouseholdExpenses parseJSON (Number 14) = pure LargePurchases parseJSON (Number 15) = pure MedicalDental parseJSON (Number 16) = pure Motorcycle parseJSON (Number 17) = pure RV parseJSON (Number 18) = pure Taxes parseJSON (Number 19) = pure Vacation parseJSON (Number 20) = pure WeddingLoans parseJSON _ = pure Other data Status = Active | Withdrawn | Expired | ListingCompleted | ListingCancelled | Pending deriving (Show, Eq, Typeable, Generic) instance FromJSON Status where parseJSON (Number 2) = pure Active parseJSON (Number 4) = pure Withdrawn parseJSON (Number 5) = pure Expired parseJSON (Number 6) = pure ListingCompleted parseJSON (Number 7) = pure ListingCancelled parseJSON (Number 8) = pure Pending parseJSON _ = mzero