All OpenStates Methods Implemented

This commit is contained in:
Brian Buller 2016-06-17 13:37:49 -05:00
parent 6fdf497703
commit f77c2932ec
12 changed files with 437 additions and 57 deletions

View File

@ -21,7 +21,13 @@ func main() {
//d, err := o.GetBillDetail(states.Kansas, "2013-2014", "HR 6020")
//d, err := o.GetLegislatorsForState(states.Kansas)
//d, err := o.GetLegislatorDetail("KSL000018")
d, err := o.GetLegislatorsForGeo(35.79, -78.78)
//d, err := o.GetLegislatorsForGeo(35.79, -78.78)
//v := url.Values{}
//d, err := o.SearchEvents(v)
//d, err := o.GetEventsForState(states.Texas)
//d, err := o.GetEventDetail("TXE00028743")
//d, err := o.GetDistrictsForState(states.Kansas)
d, err := o.GetDistrictBoundary("ocd-division/country:us/state:ks/sldu:33")
if err != nil {
fmt.Println(err.Error())
return

View File

@ -52,11 +52,6 @@ type Action struct {
Actor string `json:"actor"`
}
// Source is a source in a bill
type Source struct {
URL string `json:"url"`
}
// Sponsor is a sponsor on a bill
type Sponsor struct {
LegID string `json:"leg_id"`

View File

@ -2,16 +2,49 @@ package openstates
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"time"
"github.com/br0xen/sunlight-api/openstates/states"
)
// GetBillsWithParams returns all bills with parameters 'v'
func (o *OpenStates) GetBillsWithParams(v url.Values) ([]Bill, error) {
// SearchBills returns all bills with parameters 'v'
// Valid Parameters:
// * state - Filter by state
// * chamber - Filter by chamber
// * bill_id - Only bills with id bill_id
// * q - Bills matching provided full text query
// * search_window - By default all bills are searched, but you
// can limit the search window with these:
// * all - Default, include all sessions
// * term - Only bills from the current term
// * session - Only bills from the current session
// * session:2009 - Only bills from the session named 2009
// * term:2009-2011 - Only bills from the term named 2009-2011
// * updated_since - Only bills updated since the provided date
// * sponsor_id - Bills sponsored by a given legislator id
// * subject - Only bills categorized by OpenStates as
// belonging to this subject
// * type - Only bills of a given type
// ('bill', 'resolution', etc.)
// Additionally, 'sort' is a valid parameter, defaults to 'last'
// Other sort options:
// * first - Oldest first
// * last - Newest first (default)
// * signed - Signed first
// * passed_lower - Passed Lower first
// * passed_upper - Passed Upper first
// * updated_at - Sort by updated_at time
// * created_at - Sort by created_at time
//
// The API will not return exceedingly large responses, so it may
// be necessary to use 'page' and 'per_page' to control the # of
// of results returned:
// page - Page of results, each of size per_page (defaults to 1)
// per_page - Number of results per page, is unlimited unless page is
// set, in which case it defaults to 50.
func (o *OpenStates) SearchBills(v url.Values) ([]Bill, error) {
var ret []Bill
var err error
var getVal []byte
@ -21,14 +54,7 @@ func (o *OpenStates) GetBillsWithParams(v url.Values) ([]Bill, error) {
err = json.Unmarshal(getVal, &ret)
if err == nil {
for i := range ret {
ret[i].CreatedAt, err = time.Parse("2006-01-02 15:04:05", ret[i].CreatedAtStr)
if err != nil {
fmt.Println("Error on idx: " + strconv.Itoa(i))
return ret, err
}
ret[i].UpdatedAt, err = time.Parse("2006-01-02 15:04:05", ret[i].UpdatedAtStr)
if err != nil {
fmt.Println("Error on idx: " + strconv.Itoa(i))
if err = o.fixBillTimes(&ret[i]); err != nil {
return ret, err
}
}
@ -44,7 +70,7 @@ func (o *OpenStates) GetBillsForState(st string) ([]Bill, error) {
}
vals := url.Values{}
vals.Set("state", st)
return o.GetBillsWithParams(vals)
return o.SearchBills(vals)
}
// QueryBillsForState Does a search for bills with text 'q' in state 'st
@ -56,7 +82,7 @@ func (o *OpenStates) QueryBillsForState(st string, q string) ([]Bill, error) {
vals := url.Values{}
vals.Set("state", st)
vals.Set("q", q)
return o.GetBillsWithParams(vals)
return o.SearchBills(vals)
}
func (o *OpenStates) getBillDetailForEndpoint(ep string) (*Bill, error) {
@ -71,23 +97,7 @@ func (o *OpenStates) getBillDetailForEndpoint(ep string) (*Bill, error) {
err = json.Unmarshal(getVal, &ret)
ret.HasDetail = true
if err == nil {
if err = UnmarshalTimeString(ret.CreatedAtStr, &ret.CreatedAt); err != nil {
return ret, err
}
if err = UnmarshalTimeString(ret.UpdatedAtStr, &ret.UpdatedAt); err != nil {
return ret, err
}
UnmarshalTimeString(ret.ActionDates.PassedUpperStr, &ret.ActionDates.PassedUpper)
UnmarshalTimeString(ret.ActionDates.PassedLowerStr, &ret.ActionDates.PassedLower)
UnmarshalTimeString(ret.ActionDates.LastStr, &ret.ActionDates.Last)
UnmarshalTimeString(ret.ActionDates.SignedStr, &ret.ActionDates.Signed)
UnmarshalTimeString(ret.ActionDates.FirstStr, &ret.ActionDates.First)
for i := range ret.Actions {
UnmarshalTimeString(ret.Actions[i].DateStr, &ret.Actions[i].Date)
}
for i := range ret.Votes {
UnmarshalTimeString(ret.Votes[i].DateStr, &ret.Votes[i].Date)
}
err = o.fixBillTimes(ret)
}
return ret, err
}
@ -105,3 +115,25 @@ func (o *OpenStates) GetBillDetail(st, sess, name string) (*Bill, error) {
func (o *OpenStates) GetBillDetailFromID(id string) (*Bill, error) {
return o.getBillDetailForEndpoint("bills/" + id)
}
func (o *OpenStates) fixBillTimes(b *Bill) error {
var err error
if err = UnmarshalTimeString(b.CreatedAtStr, &b.CreatedAt); err != nil {
return errors.New("No Created At Time")
}
if err = UnmarshalTimeString(b.UpdatedAtStr, &b.UpdatedAt); err != nil {
return errors.New("No Updated At Time")
}
UnmarshalTimeString(b.ActionDates.PassedUpperStr, &b.ActionDates.PassedUpper)
UnmarshalTimeString(b.ActionDates.PassedLowerStr, &b.ActionDates.PassedLower)
UnmarshalTimeString(b.ActionDates.LastStr, &b.ActionDates.Last)
UnmarshalTimeString(b.ActionDates.SignedStr, &b.ActionDates.Signed)
UnmarshalTimeString(b.ActionDates.FirstStr, &b.ActionDates.First)
for i := range b.Actions {
UnmarshalTimeString(b.Actions[i].DateStr, &b.Actions[i].Date)
}
for i := range b.Votes {
UnmarshalTimeString(b.Votes[i].DateStr, &b.Votes[i].Date)
}
return nil
}

View File

@ -0,0 +1,28 @@
package openstates
import "time"
// Committee is a committee
type Committee struct {
Level string `json:"level"`
CreatedAtStr string `json:"created_at"`
CreatedAt time.Time `json:"-"`
UpdatedAtStr string `json:"updated_at"`
UpdatedAt time.Time `json:"-"`
ParentID string `json:"parent_id"`
State string `json:"state"`
Subcommittee string `json:"subcommittee"`
Name string `json:"committee"`
Chamber string `json:"chamber"`
ID string `json:"id"`
HasDetail bool `json:"-"`
Members []CommitteeMember `json:"members"`
Sources []Source `json:"sources"`
}
// CommitteeMember is a member of a committee
type CommitteeMember struct {
LegID string `json:"leg_id"`
Role string `json:"role"`
Name string `json:"name"`
}

73
openstates/committees.go Normal file
View File

@ -0,0 +1,73 @@
package openstates
import (
"encoding/json"
"errors"
"net/url"
"github.com/br0xen/sunlight-api/openstates/states"
)
// SearchCommittees retrieves a list of committees based on a number of
// parameters.
// Valid Parameters:
// * committee
// * subcommittee
// * chamber
// * state
func (o *OpenStates) SearchCommittees(v url.Values) ([]Committee, error) {
var ret []Committee
var err error
var getVal []byte
if getVal, err = o.call("committees/", v); err != nil {
return ret, err
}
err = json.Unmarshal(getVal, &ret)
if err == nil {
for i := range ret {
if err = o.fixCommitteeTimes(&ret[i]); err != nil {
return ret, err
}
}
}
return ret, err
}
// GetCommitteesForState gets all committees for a state
func (o *OpenStates) GetCommitteesForState(st string) ([]Committee, error) {
st, err := states.ScrubToAbbr(st)
if err != nil {
return []Committee{}, err
}
v := url.Values{}
v.Set("state", st)
return o.SearchCommittees(v)
}
// GetCommitteeDetail gets the details for a committee
func (o *OpenStates) GetCommitteeDetail(id string) (*Committee, error) {
var ret *Committee
var err error
var getVal []byte
v := url.Values{}
if getVal, err = o.call("committees/"+id+"/", v); err != nil {
return ret, err
}
err = json.Unmarshal(getVal, &ret)
if err == nil {
if err = o.fixCommitteeTimes(ret); err != nil {
return ret, err
}
}
return ret, err
}
func (o *OpenStates) fixCommitteeTimes(c *Committee) error {
if err := UnmarshalTimeString(c.CreatedAtStr, &c.CreatedAt); err != nil {
return errors.New("No Created At Time")
}
if err := UnmarshalTimeString(c.UpdatedAtStr, &c.UpdatedAt); err != nil {
return errors.New("No Updated At Time")
}
return nil
}

View File

@ -0,0 +1,46 @@
package openstates
// District is a district
type District struct {
Abbr string `json:"abbr"`
BoundaryID string `json:"boundary_id"`
Chamber string `json:"chamber"`
ID string `json:"id"`
Legislators []DistrictLegislator `json:"legislators"`
Name string `json:"name"`
NumSeats int `json:"num_seats"`
}
// DistrictLegislator is a legislator entry in a district
type DistrictLegislator struct {
FullName string `json:"full_name"`
LegID string `json:"leg_id"`
}
// DistrictBoundary describes the boundary of a district
type DistrictBoundary struct {
DivisionID string `json:"division_id"`
Name string `json:"name"`
Region *DistrictRegion `json:"region"`
Chamber string `json:"chamber"`
Shape *DistrictShape `json:"shape"`
Abbr string `json:"abbr"`
BoundaryID string `json:"boundary_id"`
NumSeats int `json:"num_seats"`
ID string `json:"id"`
BBox [2]DistrictBoundaryXY `json:"bbox"`
}
// DistrictBoundaryXY is an x/y
type DistrictBoundaryXY [2]float64
// DistrictRegion is a bounding region
type DistrictRegion struct {
CenterLat float64 `json:"center_lat"`
CenterLon float64 `json:"center_lon"`
LatDelta float64 `json:"lat_delta"`
LonDelta float64 `json:"lon_delta"`
}
// DistrictShape is a bounding shape
type DistrictShape [][][]DistrictBoundaryXY

56
openstates/districts.go Normal file
View File

@ -0,0 +1,56 @@
package openstates
import (
"encoding/json"
"errors"
"net/url"
"github.com/br0xen/sunlight-api/openstates/states"
)
// SearchDistricts retrieves a list of districts based on a number of
// parameters
// Valid
func (o *OpenStates) SearchDistricts(v url.Values) ([]District, error) {
var ret []District
var err error
if v.Get("state") == "" {
return ret, errors.New("SearchDistricts: State is required")
}
var getVal []byte
ep := "districts/" + v.Get("state") + "/"
if v.Get("chamber") != "" {
ep += v.Get("chamber") + "/"
v.Del("chamber")
}
v.Del("state")
if getVal, err = o.call(ep, v); err != nil {
return ret, err
}
err = json.Unmarshal(getVal, &ret)
return ret, err
}
// GetDistrictsForState returns all districts for state st
func (o *OpenStates) GetDistrictsForState(st string) ([]District, error) {
st, err := states.ScrubToAbbr(st)
if err != nil {
return []District{}, err
}
vals := url.Values{}
vals.Set("state", st)
return o.SearchDistricts(vals)
}
// GetDistrictBoundary returns the boundary object for a district
func (o *OpenStates) GetDistrictBoundary(bid string) (*DistrictBoundary, error) {
var ret *DistrictBoundary
var err error
var getVal []byte
v := url.Values{}
if getVal, err = o.call("/districts/boundary/"+bid, v); err != nil {
return ret, err
}
err = json.Unmarshal(getVal, &ret)
return ret, err
}

View File

@ -0,0 +1,54 @@
package openstates
import "time"
// Event is an event
type Event struct {
Documents []Document `json:"documents"`
End time.Time `json:"-"`
EndStr string `json:"end"`
Description string `json:"description"`
State string `json:"state"`
Agenda string `json:"+agenda"`
CreatedAtStr string `json:"created_at"`
CreatedAt time.Time `json:"-"`
When time.Time `json:"-"`
WhenStr string `json:"when"`
UpdatedAtStr string `json:"updated_at"`
UpdatedAt time.Time `json:"-"`
Sources []Source `json:"sources"`
Participants []Participant `json:"participants"`
Session string `json:"session"`
Location string `json:"location"`
RelatedBills []EventBill `json:"related_bills"`
Timezone string `json:"timezone"`
Type string `json:"type"`
ID string `json:"id"`
Chamber string `json:"+chamber"`
}
// EventBill is a bill associated with an event
type EventBill struct {
Description string `json:"description"`
Type string `json:"type"`
ID string `json:"id"`
BillID string `json:"bill_id"`
Chamber string `json:"+chamber"`
}
// Document is a document for an event (agenda, etc)
type Document struct {
URL string `json:"url"`
MimeType string `json:"+mimetype"`
Name string `json:"name"`
Type string `json:"+type"`
}
// Participant is a participant in an event
type Participant struct {
Chamber string `json:"chamber"`
ParticipantType string `json:"participant_type"`
Name string `json:"participant"`
ID string `json:"id"`
Type string `json:"type"`
}

70
openstates/events.go Normal file
View File

@ -0,0 +1,70 @@
package openstates
import (
"encoding/json"
"errors"
"net/url"
"github.com/br0xen/sunlight-api/openstates/states"
)
// SearchEvents retrieves a list of events based on some parameters
// Valid Parameters
// * state - Filter by state
// * type - Filter by type
func (o *OpenStates) SearchEvents(v url.Values) ([]Event, error) {
var ret []Event
var err error
var getVal []byte
if getVal, err = o.call("events", v); err != nil {
return ret, err
}
err = json.Unmarshal(getVal, &ret)
if err == nil {
for i := range ret {
if err = o.fixEventTimes(&ret[i]); err != nil {
return ret, err
}
}
}
return ret, err
}
// GetEventsForState returns all events for a state
func (o *OpenStates) GetEventsForState(st string) ([]Event, error) {
st, err := states.ScrubToAbbr(st)
if err != nil {
return []Event{}, err
}
v := url.Values{}
v.Set("state", st)
return o.SearchEvents(v)
}
// GetEventDetail returns event detail for an event id
func (o *OpenStates) GetEventDetail(id string) (*Event, error) {
var ret *Event
var err error
var getVal []byte
v := url.Values{}
if getVal, err = o.call("events/"+id+"/", v); err != nil {
return ret, err
}
err = json.Unmarshal(getVal, &ret)
if err == nil {
err = o.fixEventTimes(ret)
}
return ret, err
}
func (o *OpenStates) fixEventTimes(e *Event) error {
if err := UnmarshalTimeString(e.CreatedAtStr, &e.CreatedAt); err != nil {
return errors.New("No Created At Time")
}
if err := UnmarshalTimeString(e.UpdatedAtStr, &e.UpdatedAt); err != nil {
return errors.New("No Updated At Time")
}
UnmarshalTimeString(e.EndStr, &e.End)
UnmarshalTimeString(e.WhenStr, &e.When)
return nil
}

View File

@ -32,7 +32,9 @@ func (o *OpenStates) SearchLegislators(v url.Values) ([]Legislator, error) {
err = json.Unmarshal(getVal, &ret)
if err == nil {
for i := range ret {
o.fixLegislatorTimes(&ret[i])
if err = o.fixLegislatorTimes(&ret[i]); err != nil {
return ret, err
}
}
}
return ret, err

View File

@ -70,6 +70,20 @@ func (o *OpenStates) StateMetadata(st string) (*StateMeta, error) {
return ret, err
}
func (o *OpenStates) fixStateMetaTimes(m *StateMeta) error {
var err error
if err = UnmarshalTimeString(m.LatestCSVDateStr, &m.LatestCSVDate); err != nil {
return errors.New("No CSV Date")
}
if err = UnmarshalTimeString(m.LatestJSONDateStr, &m.LatestJSONDate); err != nil {
return errors.New("No JSON Date")
}
if err = UnmarshalTimeString(m.LatestUpdateStr, &m.LatestUpdate); err != nil {
return errors.New("No Latest Update Time")
}
return err
}
// UnmarshalTimeString Takes a time string and a pointer to a time object
// and populates the time object with the value from the string
func UnmarshalTimeString(s string, t *time.Time) error {

View File

@ -10,25 +10,24 @@ type OpenStates struct {
// StateMeta is all of the metadata for a state
type StateMeta struct {
Name string `json:"name"`
Abbr string `json:"abbreviation"`
Chambers map[string]StateChamber `json:"chambers"`
FeatureFlags []string `json:"feature_flags"`
CapitolTimezone time.Location `json:"-"`
CapitolTimezoneStr string `json:"capitol_timezone"`
ID string `json:"id"`
LatestCSVDate time.Time `json:"-"`
LatestCSVDateStr string `json:"latest_csv_date"`
LatestCSVURL string `json:"latest_csv_url"`
LatestJSONDate time.Time `json:"-"`
LatestJSONDateStr string `json:"latest_json_date"`
LatestJSONURL string `json:"latest_json_url"`
LatestUpdate time.Time `json:"-"`
LatestUpdateStr string `json:"latest_update"`
LegislatureName string `json:"legislature_name"`
LegislatureURL string `json:"legislature_url"`
SessionDetails map[string]SessionDetail `json:"session_details"`
Terms []Term `json:"terms"`
Name string `json:"name"`
Abbr string `json:"abbreviation"`
Chambers map[string]StateChamber `json:"chambers"`
FeatureFlags []string `json:"feature_flags"`
CapitolTimezone string `json:"capitol_timezone"`
ID string `json:"id"`
LatestCSVDate time.Time `json:"-"`
LatestCSVDateStr string `json:"latest_csv_date"`
LatestCSVURL string `json:"latest_csv_url"`
LatestJSONDate time.Time `json:"-"`
LatestJSONDateStr string `json:"latest_json_date"`
LatestJSONURL string `json:"latest_json_url"`
LatestUpdate time.Time `json:"-"`
LatestUpdateStr string `json:"latest_update"`
LegislatureName string `json:"legislature_name"`
LegislatureURL string `json:"legislature_url"`
SessionDetails map[string]SessionDetail `json:"session_details"`
Terms []Term `json:"terms"`
}
// StateChamber is a Chamber in the state's government
@ -52,3 +51,8 @@ type Term struct {
Name string `json:"name"`
Sessions []string `json:"sessions"`
}
// Source is a source in a bill/committee, etc.
type Source struct {
URL string `json:"url"`
}