packages feed

prosper (empty) → 0.1.0.0

raw patch · 14 files changed

+866/−0 lines, 14 filesdep +HsOpenSSLdep +aesondep +basesetup-changed

Dependencies added: HsOpenSSL, aeson, base, bytestring, cereal, containers, http-streams, io-streams, mtl, text, transformers, vector

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Matthew Wraith++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Matthew Wraith nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ prosper.cabal view
@@ -0,0 +1,48 @@+-- Initial prosper.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                prosper+version:             0.1.0.0+synopsis:            Bindings to the Prosper marketplace API+-- description:         +homepage:            https://api.prosper.com+license:             BSD3+license-file:        LICENSE+author:              Matthew Wraith+maintainer:          wraithm@gmail.com+copyright:           (c) 2015 Matthew Wraith+category:            Finance+build-type:          Simple+cabal-version:       >=1.10++library+  exposed-modules:     +    Prosper, +    Prosper.Account, +    Prosper.Listing, +    Prosper.User, +    Prosper.Note, +    Prosper.Money, +    Prosper.Commands, +    Prosper.Invest,+    Prosper.Internal.Request, +    Prosper.Internal.JSON, +    Prosper.Internal.CSV++  build-depends:       +    base >=4.8 && <4.9, +    transformers >=0.4 && <0.5, +    text >=1.2 && <1.3, +    bytestring >=0.10 && <0.11, +    mtl >=2.2 && <2.3, +    containers >=0.5 && <0.6,+    vector,+    aeson,+    cereal,+    io-streams,+    HsOpenSSL,+    http-streams+++  hs-source-dirs:      src+  default-language:    Haskell2010
+ src/Prosper.hs view
@@ -0,0 +1,50 @@+-- | These are bindings to the Prosper marketplace API.+-- The documentation for the API can be seen at+-- https://api.prosper.com/.+module Prosper+    (+    -- * Commands+      invest+    , account+    , allListings+    , notes+    , listingFromNote++    , AccountException (..)++    -- * User+    , User (..)++    -- * Account+    , Account (..)+    , emptyAccount++    -- * Listing+    , Listing (..)+    , Offer (..)+    , Credit (..)+    , Rating (..)+    , Category (..)+    , Status (..)++    -- * Note+    , NoteStatus (..)+    , NoteDefaultReason (..)+    , Note (..)++    -- * Invest+    , InvestStatus (..)+    , InvestMessage (..)+    , InvestResponse (..)++    -- * Types+    , Money+    ) where++import           Prosper.Account+import           Prosper.Commands+import           Prosper.Invest+import           Prosper.Listing+import           Prosper.Money+import           Prosper.Note+import           Prosper.User
+ src/Prosper/Account.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE OverloadedStrings #-}++module Prosper.Account+    ( Account (..)+    , emptyAccount+    ) where++import           Control.Applicative ((<$>), (<*>))+import           Control.Monad       (mzero)++import           Data.Aeson++import           Prosper.Money++-- | A data structure for holding account information for Prosper+data Account = Account+    { availableCash                   :: !Money+      -- ^ Available cash for investing+    , pendingInvestments              :: !Money+      -- ^ Amount of money invested that is pending origination+    , totPrincipalRecvdOnActiveNotes  :: !Money+      -- ^ Total principal payments received on active notes+    , totalInvestedOnActiveNotes      :: !Money+      -- ^ Total amount invested+    , outstandingPrincipalActiveNotes :: !Money+      -- ^ Total principal outstanding on active notes+    , -- | Total dollars held in investible cash,+      -- pending orders in the market, outstanding principal+      totalAccountValue               :: !Money+    , pendingInvestmentsSecondaryMkt  :: !Money+      -- ^ Investments on Folio+    , pendingQuickInvestOrders        :: !Money+      -- ^ Prosper Quick Invests+    } deriving (Show, Eq)++-- | An empty account. All values are zero.+emptyAccount :: Account+emptyAccount = Account 0 0 0 0 0 0 0 0++-- | Parser for account info+--+-- Located at api/Account+instance FromJSON Account where+    parseJSON (Object v) = Account+        <$> v .: "AvailableCashBalance"+        <*> v .: "PendingInvestmentsPrimaryMkt"+        <*> v .: "TotalPrincipalReceivedOnActiveNotes"+        <*> v .: "TotalAmountInvestedOnActiveNotes"+        <*> v .: "OutstandingPrincipalOnActiveNotes"+        <*> v .: "TotalAccountValue"+        <*> v .: "PendingInvestmentsSecondaryMkt"+        <*> v .: "PendingQuickInvestOrders"+    parseJSON _ = mzero
+ src/Prosper/Commands.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings  #-}++module Prosper.Commands+    ( invest+    , account+    , allListings+    , notes+    , listingFromNote++    , listingCSV++    , AccountException (..)+    ) where++import           Control.Exception     (Exception, throwIO)+import           Control.Monad         (when)++import           Data.ByteString.Char8 as C+import           Data.Maybe            (listToMaybe)+import           Data.Monoid           ((<>))+import           Data.Typeable         (Typeable)+import           Data.Vector           (Vector)++import           Network.Http.Client+import           System.IO.Streams     (InputStream)++import           Prosper.Account+import           Prosper.Internal.JSON+import           Prosper.Internal.CSV+import           Prosper.Invest+import           Prosper.Listing+import           Prosper.Money+import           Prosper.Note+import           Prosper.User++-- | An investment request. This requires a 'User', an amount of 'Money', and a 'Listing'.+invest :: User -> Money -> Listing -> IO InvestResponse+invest user amt l = investRequest user (listingId l) amt++-- | Request 'Account' information from Prosper+account :: User -> IO Account+account user = jsonGetHandler user "Account" accountHandler+  where+    accountHandler resp is =+        if getStatusCode resp == 500+            then throwIO (AccountException (getStatusMessage resp))+            else jsonHandler resp is++-- | Used as a hack around the 500 Critical Exception error+data AccountException = AccountException ByteString+    deriving (Typeable, Show)++instance Exception AccountException++-- | Request notes for a Prosper user+notes :: User -> IO (Vector Note)+notes user = jsonGet user "Notes"++-- | Given a 'Note', look up the associated 'Listing' by the ListingId+listingFromNote :: User -> Note -> IO (Maybe Listing)+listingFromNote user (Note { listingNumber = lid }) = do+    ls <- jsonGet user command+    return $ listToMaybe ls+  where+    command = "ListingsHistorical?$filter=ListingNumber eq " <> C.pack (show lid)++-- | Send a request to the Listings end-point at Prosper+allListings :: User -> IO (Vector Listing)+allListings user = jsonGet user "Listings"++-- | Request a particular 'Listing' via the CSV endpoint+listingCSV :: User -> Listing -> (InputStream ByteString -> IO a) -> IO a+listingCSV user l = csvGetStream user url+  where+    lid = listingId l+    url = "Listings?$filter=ListingNumber%20eq%20" <> C.pack (show lid)
+ src/Prosper/Internal/CSV.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}++module Prosper.Internal.CSV+    ( csvGet+    , csvGetStream+    ) where++import           Network.Http.Client+import           System.IO.Streams     (InputStream)++import           Data.ByteString       (ByteString)++import           Prosper.Money+import           Prosper.User+import           Prosper.Internal.Request++-- | Make a GET request to Prosper's CSV api, return the raw CSV data+csvGet+    :: User -- ^ The user name and password for the prosper user+    -> ByteString -- ^ The name of the API service, relative to the API url+    -> IO ByteString -- ^ Raw CSV response+csvGet userInfo url = issueRequest userInfo url GET "text/csv" concatHandler []++-- | Make a GET request to Prosper's CSV api, return the raw CSV data+csvGetStream+    :: User -- ^ The user name and password for the prosper user+    -> ByteString -- ^ The name of the API service, relative to the API url+    -> (InputStream ByteString -> IO a)+    -> IO a -- ^ Raw CSV response+csvGetStream userInfo url handler =+    issueRequest userInfo url GET "text/csv" (\_ is -> handler is) []
+ src/Prosper/Internal/JSON.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE OverloadedStrings #-}++module Prosper.Internal.JSON+    ( jsonGet+    , jsonGetHandler+    , investRequest+    ) where++import           Network.Http.Client+import           System.IO.Streams        (InputStream)++import           Data.Aeson               (FromJSON)+import           Data.ByteString          (ByteString)+import qualified Data.ByteString.Char8    as C+import           Data.Monoid              ((<>))++import           Prosper.Internal.Request+import           Prosper.Invest+import           Prosper.Money+import           Prosper.User++-- | Make a GET request to Prosper's JSON api, return the parsed JSON data+jsonGet+    :: FromJSON a+    => User -- ^ The user name and password for the prosper user+    -> ByteString -- ^ The name of the API service, relative to the API url+    -> IO a -- ^ JSON response+jsonGet userInfo url =+    issueRequest userInfo url GET "application/json" jsonHandler []++jsonGetHandler+    :: FromJSON a+    => User -- ^ The user name and password for the prosper user+    -> ByteString -- ^ The name of the API service, relative to the API url+    -> (Response -> InputStream ByteString -> IO a)+    -> IO a -- ^ JSON response+jsonGetHandler userInfo url handler =+    issueRequest userInfo url GET "application/json" handler []++investRequest+    :: User+    -> Int -- ^ The listing id+    -> Money -- ^ Amount+    -> IO InvestResponse -- ^ JSON response from Prosper+investRequest userInfo listingId amount =+    issueRequest userInfo "Invest" POST "application/x-www-form-urlencoded" jsonHandler+        [ ("listingId", C.pack $ show listingId)+        , ("amount", C.pack $ show amount)+        ]
+ src/Prosper/Internal/Request.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE OverloadedStrings #-}++module Prosper.Internal.Request where++import           Network.Http.Client+import           OpenSSL+import           System.IO.Streams     (InputStream)++import           Data.ByteString       (ByteString)+import qualified Data.ByteString.Char8 as C+import           Data.Monoid           ((<>))++import           Prosper.User++apiUrl :: ByteString+apiUrl = "api.prosper.com"++issueRequest+    :: User+    -> ByteString+    -> Method+    -> ContentType+    -> (Response -> InputStream ByteString -> IO a)+    -> [(ByteString, ByteString)]+    -> IO a+issueRequest (User user pass) url method ct handler params = withOpenSSL $ do+    ctx <- baselineContextSSL+    con <- openConnectionSSL ctx apiUrl 443+    req <- buildRequest $ do+        http method $ "/api/" <> url+        setAuthorizationBasic user pass+        setContentType ct+    sendRequest con req (setBody params)+    resp <- receiveResponse con handler+    closeConnection con+    return resp+  where+    setBody [] = emptyBody+    setBody xs = encodedFormBody xs
+ src/Prosper/Invest.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE OverloadedStrings #-}++module Prosper.Invest+    ( InvestStatus (..)+    , InvestMessage (..)+    , InvestResponse (..)+    ) where++import           Control.Applicative (pure, (<$>), (<*>))+import           Control.Monad       (mzero)++import           Data.Aeson          hiding (Error, Success)+import           Data.Serialize+import           Data.Text.Read      as R++import           GHC.Generics++import           Prosper.Money++-- | Status of an invest request+data InvestStatus+    = Success+    | Failed+    | Error+    | PartialSuccess+    deriving (Show, Eq, Read, Generic)++-- | Status of Invest request response JSON parser+instance FromJSON InvestStatus where+    parseJSON (String "SUCCESS") = pure Success+    parseJSON (String "FAILED") = pure Failed+    parseJSON (String "ERROR") = pure Error+    parseJSON (String "PARTIAL_SUCCESS") = pure PartialSuccess+    parseJSON _ = mzero++instance Serialize InvestStatus where++-- | Message associated with an error+data InvestMessage+    = NoError+    | InternalError+    | InvestedAmountLessThanRequested+    | ListingNotAvailable+    | ListingNotFound+    | InsufficientFunds+    | ListingClosedBeforeBidPlaced+    | ServerBusy+    | SuitabilityRequirementsNotMet+    | InvestedAmountLessThanMinimumRequired+    | OtherError -- ^ Not in the prosper docs, this is a catch all+    deriving (Show, Eq, Read, Generic)++-- | Parser for message in an error+instance FromJSON InvestMessage where+    parseJSON (String "NO_ERROR") = pure NoError+    parseJSON (String "INTERNAL_ERROR") = pure InternalError+    parseJSON (String "INVESTED_AMOUNT_LESS_THAN_REQUESTED") =+        pure InvestedAmountLessThanRequested+    parseJSON (String "LISTING_NOT_AVAILABLE") = pure ListingNotAvailable+    parseJSON (String "LISTING_NOT_FOUND") = pure ListingNotFound+    parseJSON (String "INSUFFICIENT_FUNDS") = pure InsufficientFunds+    parseJSON (String "NOT_ENOUGH_BALANCE_AVAILABLE") = pure InsufficientFunds -- Isn't in the Prosper Docs+    parseJSON (String "LISTING_CLOSED_BEFORE_BID_PLACED") =+        pure ListingClosedBeforeBidPlaced+    parseJSON (String "SERVER_BUSY") = pure ServerBusy+    parseJSON (String "SUITABILITY_REQUIREMENTS_NOT_MET") =+        pure SuitabilityRequirementsNotMet+    parseJSON (String "INVESTED_AMOUNT_LESS_THAN_MINIMUM_REQUIRED") =+        pure InvestedAmountLessThanMinimumRequired+    parseJSON _ = pure OtherError++instance Serialize InvestMessage where++-- | JSON response to an invest request+data InvestResponse = InvestResponse+    { investStatus    :: InvestStatus+    , requestedAmount :: Money+    , investListingId :: Int+    , investMessage   :: InvestMessage+    , amountInvested  :: Money+    } deriving (Show, Generic)++newtype Money' = MkMoney { unMoney :: Money } deriving (Eq, Generic)++instance Show Money' where+    show (MkMoney x) = show x++instance FromJSON Money' where+    parseJSON (String x) = readDouble x+      where+        readDouble y = case R.double y of+            Right (z,_) -> pure $ MkMoney z+            Left _ -> mzero+    parseJSON _ = mzero++-- | Parser for invest request response on api/Invest+instance FromJSON InvestResponse where+    parseJSON (Object v) = InvestResponse+        <$> v .: "Status"+        <*> (unMoney <$> v .: "RequestedAmount")+        <*> v .: "ListingId"+        <*> v .: "Message"+        <*> (unMoney <$> v .: "AmountInvested")+    parseJSON _ = mzero++instance Serialize InvestResponse where
+ src/Prosper/Listing.hs view
@@ -0,0 +1,241 @@+{-# 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
+ src/Prosper/Money.hs view
@@ -0,0 +1,3 @@+module Prosper.Money where++type Money = Double
+ src/Prosper/Note.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE OverloadedStrings #-}++module Prosper.Note +    ( NoteStatus (..)+    , NoteDefaultReason (..)+    , Note (..)+    ) where++import           Control.Applicative (pure, (<$>), (<*>))++import           Data.Aeson+import           Data.Text++import           GHC.Generics++import           Prosper.Listing     (Rating (..))+import           Prosper.Money++data NoteStatus+    = OriginationDelayed+    | Current+    | ChargeOff+    | Defaulted+    | NoteCompleted+    | FinalPaymentInProgress+    | NoteCancelled+    deriving (Show, Eq)++instance ToJSON NoteStatus where+    toJSON OriginationDelayed = Number 0+    toJSON Current = Number 1+    toJSON ChargeOff = Number 2+    toJSON Defaulted = Number 3+    toJSON NoteCompleted = Number 4+    toJSON FinalPaymentInProgress = Number 5+    toJSON NoteCancelled = Number 6++instance FromJSON NoteStatus where+    parseJSON (Number 0) = pure OriginationDelayed+    parseJSON (Number 1) = pure Current+    parseJSON (Number 2) = pure ChargeOff+    parseJSON (Number 3) = pure Defaulted+    parseJSON (Number 4) = pure NoteCompleted+    parseJSON (Number 5) = pure FinalPaymentInProgress+    parseJSON (Number 6) = pure NoteCancelled+    parseJSON _ = fail "Could not parse NoteStatus"++data NoteDefaultReason+    = Delinquency+    | Bankruptcy+    | Deceased+    | Repurchased+    | PaidInFull+    | SettledInFull+    deriving (Show, Eq)++instance ToJSON NoteDefaultReason where+    toJSON Delinquency = Number 1+    toJSON Bankruptcy = Number 2+    toJSON Deceased = Number 3+    toJSON Repurchased = Number 4+    toJSON PaidInFull = Number 5+    toJSON SettledInFull = Number 6++instance FromJSON NoteDefaultReason where+    parseJSON (Number 1) = pure Delinquency+    parseJSON (Number 2) = pure Bankruptcy+    parseJSON (Number 3) = pure Deceased+    parseJSON (Number 4) = pure Repurchased+    parseJSON (Number 5) = pure PaidInFull+    parseJSON (Number 6) = pure SettledInFull+    parseJSON _ = fail "Could not parse NoteDefaultReason"++data Note = Note+    { loanNoteId          :: Text+    , listingNumber       :: Int+    , amountParticipation :: Money+    , totalAmountBorrowed :: Money+    , borrowerRate        :: Double+    , noteRating          :: Rating+    , term                :: Int+    , ageInMonths         :: Int+--     , originationDate :: Date+    , daysPastDue         :: Int+    , principalBalance    :: Money+    , principalRepaid     :: Money+    , interestPaid        :: Money+    , serviceFees         :: Money+    , prosperFees         :: Money+    , lateFees            :: Money+    -- groupLeaderReward+    , noteStatus          :: NoteStatus+    , noteDefaultReason   :: Maybe NoteDefaultReason+    , isSold              :: Bool+    } deriving (Show, Eq, Generic)++instance FromJSON Note where+    parseJSON (Object v) = Note+        <$> v .: "LoanNoteID"+        <*> v .: "ListingNumber"+        <*> v .: "AmountParticipation"+        <*> v .: "TotalAmountBorrowed"+        <*> v .: "BorrowerRate"+        <*> v .: "ProsperRating"+        <*> v .: "Term"+        <*> v .: "AgeInMonths"+        <*> v .: "DaysPastDue"+        <*> v .: "PrincipalBalance"+        <*> v .: "PrincipalRepaid"+        <*> v .: "InterestPaid"+        <*> v .: "ServiceFees"+        <*> v .: "ProsperFees"+        <*> v .: "LateFees"+        <*> v .: "NoteStatus"+        <*> v .:? "NoteDefaultReason"+        <*> v .: "IsSold"+    parseJSON _ = fail "Could not parse Note"++instance ToJSON Note where
+ src/Prosper/User.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}++module Prosper.User+    ( User(..)+    ) where++import           Control.Monad             (when)++import           Data.ByteString           (ByteString)++-- | User info for Prosper data+data User = User+    { username :: !ByteString -- ^ Username for Prosper+    , password :: !ByteString -- ^ Password for Prosper+    } deriving (Show, Eq)