Tuesday, August 4, 2009

LINQ to REST Webservice

Kiva is a microlending site that allows you to loan as little as $25 (along with others to total $1500 or whatever) to entrepreneurs in developing nations. Some loans contain more risks than others and it's a good idea to look over your options to prevent loss if that's a concern of yours. Kiva has RESTful webservices that can spit out either XML or JSON. Below I have code that I run in LINQPad to select the best of all available loans, then it orders the results by when it will be repaid in full so that as a lender, you can re-lend that money as quickly as possible to help someone else.

It first goes through and selects all the highest rated field partners (established, 5 star rating, no delinquency, no defaults). Then it pulls the number of pages of data that are available for loans and then pulls page by page of loan information to get a collection of all new loans and filters out any that do not still have the "fundraising" status and selects only the group loans and joins these loans with the filtered partner listing. In the final block, it goes out and looks up each loan individually to get the repayment terms (the final payment) and filters out any loans that are not protected from loss due to exchange rates or nonpayment and orders by the final payment date.

//PARTNERS: fetch all partners, then remove any that have non-0 delinquency or default rates, are non-5 star rating, or are not "active" status (removes: pilot,paused,closed)
var partners = from partner in XDocument.Load(@"http://api.kivaws.org/v1/partners.xml").Descendants("partners").Descendants("partner")
               let status = (string)partner.Element("status")
               let rating = (string)partner.Element("rating") == "" ? 0.00 : (float)partner.Element("rating")
               let delinquency_rate = (string)partner.Element("delinquency_rate") == "" ? 0.00 : (float)partner.Element("delinquency_rate")
               let default_rate = (string)partner.Element("default_rate") == "" ? 0.00 : (float)partner.Element("default_rate")
               where status == "active" && default_rate == 0.00 && delinquency_rate == 0.00 && rating == 5
               select new { id = (int)partner.Element("id"), rating, name = (string)partner.Element("name") };
 
//LOANS: cycle over all the pages of the newest loans, keeping only ones that are still unfunded group
int pagecount = (int)XDocument.Load(@"http://api.kivaws.org/v1/loans/newest.xml").Descendants("paging").First().Element("pages");
var tot = from page in Enumerable.Range(1, pagecount)
          from loan in XDocument.Load(string.Format(@"http://api.kivaws.org/v1/loans/newest.xml?page={0}", page)).Descendants("loan")
          where (string)loan.Element("status") == "fundraising"
          let loan_amount = (int)loan.Element("loan_amount")
          let funded_amount = (string)loan.Element("funded_amount") == "" ? 0 : (int)loan.Element("funded_amount")
          let basket_amount = (string)loan.Element("basket_amount") == "" ? 0 : (int)loan.Element("basket_amount")
          let borrower_count = (int)loan.Element("borrower_count")
          let partner_id = (int)loan.Element("partner_id")
          let lid = (string)loan.Element("id")
          let remaining = loan_amount - funded_amount                      
          where borrower_count > 1
          join p in partners on partner_id equals p.id
          select new
          {
              lid,
              name = (string)loan.Element("name"),
              loan_amount,
              funded_amount,
              remaining,
              basket_amount,
              borrower_count,
              activity = (string)loan.Element("activity"),
              sector = (string)loan.Element("sector"),
              use = (string)loan.Element("use"),
              pid = p.id,
              partner_name = p.name,
              partner_rating = p.rating,
              url = string.Format(@"http://www.kiva.org/app.php?page=businesses&action=about&id={0}", lid) 
          };
 
//FINAL WEEDING PROCESS, REMOVE ALL RISK: removes any loans that can have currency loss or risk of nonpayment, order by the due date
var finalists = from l in tot
                let ldet = XDocument.Load(string.Format(@"http://api.kivaws.org/v1/loans/{0}.xml", l.lid))
                let finalpayment = DateTime.Parse((string)ldet.Descendants("scheduled_payments").Descendants("scheduled_payment").Last().Element("due_date"))
                let nonpayment = (string)ldet.Descendants("loss_liability").First().Element("nonpayment")
                let currency_exchange = (string)ldet.Descendants("loss_liability").First().Element("currency_exchange")
                where nonpayment == "partner" && currency_exchange == "partner"
                orderby finalpayment ascending
                select new { loan = l, finalpayment, nonpayment, currency_exchange };

To see these queries in action in a Silverlight app, visit my site.