package com.sunlightlabs.congress.services;
import com.sunlightlabs.congress.models.Bill;
import com.sunlightlabs.congress.models.Bill.Action;
import com.sunlightlabs.congress.models.Bill.Vote;
import com.sunlightlabs.congress.models.CongressException;
import com.sunlightlabs.congress.models.Legislator;
import com.sunlightlabs.congress.models.UpcomingBill;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BillService {
public static String[] basicFields = {
"bill_id", "bill_type", "chamber", "number", "congress",
"introduced_on", "last_action_at", "last_vote_at",
"official_title", "short_title", "cosponsors_count",
"urls", "last_version.urls",
"history",
"sponsor",
"upcoming",
"last_action"
};
public static List<Bill> recentlyIntroduced(int page, int per_page) throws CongressException {
Map<String,String> params = new HashMap<String,String>();
params.put("order", "introduced_on,bill_type,number");
return billsFor(Congress.url("bills", basicFields, params, page, per_page));
}
public static List<Bill> recentlyActive(int page, int per_page) throws CongressException {
Map<String,String> params = new HashMap<String,String>();
params.put("order", "last_action_at");
params.put("history.active", "true");
return billsFor(Congress.url("bills", basicFields, params, page, per_page));
}
public static List<Bill> recentlyLaw(int page, int per_page) throws CongressException {
Map<String,String> params = new HashMap<String,String>();
params.put("order", "last_action_at");
params.put("history.enacted", "true");
return billsFor(Congress.url("bills", basicFields, params, page, per_page));
}
public static List<Bill> recentlySponsored(String sponsorId, int page, int per_page) throws CongressException {
Map<String,String> params = new HashMap<String,String>();
params.put("order", "introduced_on,bill_type,number");
params.put("sponsor_id", sponsorId);
return billsFor(Congress.url("bills", basicFields, params, page, per_page));
}
public static List<Bill> search(String query, Map<String,String> params, int page, int per_page) throws CongressException {
String quoted = "\"" + query+ "\"";
List<Bill> bills = billsFor(Congress.searchUrl("bills", quoted, true, basicFields, params, page, per_page));
// insert the search query on each bill
int length = bills.size();
for (int i=0; i<length; i++) {
Bill bill = bills.get(i);
bill.search.query = query;
}
return bills;
}
public static List<Bill> where(Map<String,String> params, int page, int per_page) throws CongressException {
if (!params.containsKey("order"))
params.put("order", "introduced_on,bill_type,number");
return billsFor(Congress.url("bills", basicFields, params, page, per_page));
}
public static Bill find(String id, String[] fields) throws CongressException {
Map<String,String> params = new HashMap<String,String>();
params.put("bill_id", id);
return billFor(Congress.url("bills", fields, params));
}
protected static Bill fromAPI(JSONObject json) throws JSONException, ParseException, CongressException {
Bill bill = new Bill();
if (!json.isNull("bill_id"))
bill.id = json.getString("bill_id");
if (!json.isNull("bill_type"))
bill.bill_type = json.getString("bill_type");
if (!json.isNull("chamber"))
bill.chamber = json.getString("chamber");
// todo: rename field
if (!json.isNull("congress"))
bill.congress = json.getInt("congress");
if (!json.isNull("number"))
bill.number = json.getInt("number");
if (!json.isNull("short_title"))
bill.short_title = json.getString("short_title");
if (!json.isNull("official_title"))
bill.official_title = json.getString("official_title");
if (!json.isNull("last_action_at"))
bill.last_action_at = Congress.parseDateEither(json.getString("last_action_at"));
if (!json.isNull("last_vote_at"))
bill.last_passage_vote_at = Congress.parseDateEither(json.getString("last_vote_at"));
if (!json.isNull("introduced_on"))
bill.introduced_on = Congress.parseDateOnly(json.getString("introduced_on"));
if (!json.isNull("cosponsors_count"))
bill.cosponsors_count = json.getInt("cosponsors_count");
// timeline dates
if (!json.isNull("history")) {
JSONObject history = json.getJSONObject("history");
if (!history.isNull("active_at"))
bill.active_at = Congress.parseDateEither(history.getString("active_at"));
if (!history.isNull("senate_cloture_result_at"))
bill.senate_cloture_result_at = Congress.parseDateEither(history.getString("senate_cloture_result_at"));
if (!history.isNull("house_passage_result_at"))
bill.house_passage_result_at = Congress.parseDateEither(history.getString("house_passage_result_at"));
if (!history.isNull("senate_passage_result_at"))
bill.senate_passage_result_at = Congress.parseDateEither(history.getString("senate_passage_result_at"));
if (!history.isNull("vetoed_at"))
bill.vetoed_at = Congress.parseDateEither(history.getString("vetoed_at"));
if (!history.isNull("house_override_result_at"))
bill.house_override_result_at = Congress.parseDateEither(history.getString("house_override_result_at"));
if (!history.isNull("senate_override_result_at"))
bill.senate_override_result_at = Congress.parseDateEither(history.getString("senate_override_result_at"));
if (!history.isNull("awaiting_signature_since"))
bill.awaiting_signature_since = Congress.parseDateEither(history.getString("awaiting_signature_since"));
if (!history.isNull("enacted_at"))
bill.enacted_at = Congress.parseDateEither(history.getString("enacted_at"));
// timeline flags and values
if (!history.isNull("active"))
bill.active = history.getBoolean("active");
if (!history.isNull("senate_cloture_result"))
bill.senate_cloture_result = history.getString("senate_cloture_result");
if (!history.isNull("house_passage_result"))
bill.house_passage_result = history.getString("house_passage_result");
if (!history.isNull("senate_passage_result"))
bill.senate_passage_result = history.getString("senate_passage_result");
if (!history.isNull("vetoed"))
bill.vetoed = history.getBoolean("vetoed");
if (!history.isNull("house_override_result"))
bill.house_override_result = history.getString("house_override_result");
if (!history.isNull("senate_override_result"))
bill.senate_override_result = history.getString("senate_override_result");
if (!history.isNull("awaiting_signature"))
bill.awaiting_signature = history.getBoolean("awaiting_signature");
if (!history.isNull("enacted"))
bill.enacted = history.getBoolean("enacted");
}
if (!json.isNull("sponsor"))
bill.sponsor = LegislatorService.fromAPI(json.getJSONObject("sponsor"));
if (!json.isNull("summary"))
bill.summary = json.getString("summary");
if (!json.isNull("cosponsors")) {
JSONArray cosponsorObjects = json.getJSONArray("cosponsors");
int length = cosponsorObjects.length();
bill.cosponsors = new ArrayList<Legislator>();
for (int i=0; i<length; i++)
bill.cosponsors.add(LegislatorService.fromAPI(cosponsorObjects.getJSONObject(i).getJSONObject("legislator")));
}
if (!json.isNull("votes")) {
JSONArray voteObjects = json.getJSONArray("votes");
int length = voteObjects.length();
bill.votes = new ArrayList<Bill.Vote>();
// load in descending order
for (int i = 0; i < length; i++)
bill.votes.add(0, voteFromAPI(voteObjects.getJSONObject(i)));
}
if (!json.isNull("actions")) {
JSONArray actionObjects = json.getJSONArray("actions");
int length = actionObjects.length();
bill.actions = new ArrayList<Bill.Action>();
// load in descending order
for (int i = 0; i < length; i++)
bill.actions.add(0, actionFromAPI(actionObjects.getJSONObject(i)));
}
if (!json.isNull("last_action"))
bill.lastAction = actionFromAPI(json.getJSONObject("last_action"));
if (!json.isNull("upcoming")) {
JSONArray upcomingObjects = json.getJSONArray("upcoming");
int length = upcomingObjects.length();
List<UpcomingBill> upcoming = new ArrayList<UpcomingBill>();
for (int i = 0; i < length; i++)
upcoming.add(UpcomingBillService.fromAPI(upcomingObjects.getJSONObject(i)));
// sort in order of legislative day
Collections.sort(upcoming, new Comparator<UpcomingBill>() {
@Override
public int compare(UpcomingBill a, UpcomingBill b) {
return a.legislativeDay.compareTo(b.legislativeDay);
}
});
bill.upcoming = upcoming;
}
if (!json.isNull("last_version")) {
JSONObject version = json.getJSONObject("last_version");
if (!version.isNull("urls")) {
bill.versionUrls = new HashMap<String,String>();
JSONObject urls = version.getJSONObject("urls");
if (!urls.isNull("html"))
bill.versionUrls.put("html", urls.getString("html"));
if (!urls.isNull("xml"))
bill.versionUrls.put("xml", urls.getString("xml"));
if (!urls.isNull("pdf"))
bill.versionUrls.put("pdf", urls.getString("pdf"));
}
}
if (!json.isNull("urls")) {
bill.urls = new HashMap<String,String>();
JSONObject urls = json.getJSONObject("urls");
if (!urls.isNull("congress"))
bill.urls.put("congress", urls.getString("congress"));
if (!urls.isNull("govtrack"))
bill.urls.put("govtrack", urls.getString("govtrack"));
if (!urls.isNull("opencongress"))
bill.urls.put("opencongress", urls.getString("opencongress"));
}
// coming from a search endpoint, generate a search object
if (!json.isNull("search"))
bill.search = Congress.SearchResult.from(json.getJSONObject("search"));
return bill;
}
protected static Vote voteFromAPI(JSONObject json) throws JSONException, ParseException, CongressException {
Vote vote = new Vote();
vote.result = json.getString("result");
vote.text = json.getString("text");
vote.how = json.getString("how");
vote.passage_type = json.getString("vote_type");
vote.chamber = json.getString("chamber");
vote.voted_at = Congress.parseDateEither(json.getString("acted_at"));
if (!json.isNull("roll_id"))
vote.roll_id = json.getString("roll_id");
return vote;
}
protected static Action actionFromAPI(JSONObject json) throws JSONException, ParseException, CongressException {
Action action = new Action();
action.text = json.getString("text");
action.type = json.getString("type");
action.acted_at = Congress.parseDateEither(json.getString("acted_at"));
if (!json.isNull("chamber"))
action.chamber = json.getString("chamber");
if (!json.isNull("result"))
action.result = json.getString("result");
return action;
}
/* Private helpers for loading single or plural bill objects */
private static Bill billFor(String url) throws CongressException {
try {
JSONObject json = Congress.firstResult(url);
if (json != null)
return fromAPI(json);
else
throw new BillNotFoundException();
} catch (JSONException e) {
throw new CongressException(e, "Problem parsing the JSON from " + url);
} catch (ParseException e) {
throw new CongressException(e, "Problem parsing a date in the JSON from " + url);
}
}
private static List<Bill> billsFor(String url) throws CongressException {
List<Bill> bills = new ArrayList<Bill>();
try {
JSONArray results = Congress.resultsFor(url);
int length = results.length();
for (int i = 0; i < length; i++)
bills.add(fromAPI(results.getJSONObject(i)));
} catch (JSONException e) {
throw new CongressException(e, "Problem parsing the JSON from " + url);
} catch (ParseException e) {
throw new CongressException(e, "Problem parsing a date in the JSON from " + url);
}
return bills;
}
public static class BillNotFoundException extends CongressException {
private static final long serialVersionUID = 1L;
public BillNotFoundException() {
super("No bill found by that ID.");
}
}
}