prosper-0.1.0.0: src/Prosper/Listing.hs
{-# 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