-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
220 lines (186 loc) · 4.91 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package main
import (
"encoding/csv"
"fmt"
"io"
"log"
"os"
"sort"
"strconv"
)
// File names
const SlcspFileName string = "slcsp.csv"
const ZipsFileName string = "zips.csv"
const PlansFileName string = "plans.csv"
// RateData holds the rating information for a zip code
// RateArea is a string where `state` and `rate_area` are concatenated from ZipsFileName/PlansFileName
// Rates is a slice of applicable rates found for the RateArea from PlansFileName
// Ambiguous marks whether a zip has multiple RateArea
type RateData struct {
RateArea string
Rates []float64
Ambiguous bool
}
// concatRateArea creates the RateArea string for use in RateData
// It expects the `state` and the `rate_area` from ZipsFileName/PlansFileName
func concatRateArea(state string, code string) string {
rateArea := fmt.Sprintf("%s%s", state, code)
return rateArea
}
// parseSlcsp reads the data in SlcspFileName and returns all of the zip codes from it
func parseSlcsp() ([]string, error) {
zips := make([]string, 0)
slcspFile, err := os.Open(SlcspFileName)
if err != nil {
return zips, err
}
defer slcspFile.Close()
slcspReader := csv.NewReader(slcspFile)
slcspReader.FieldsPerRecord = 2
// Skip first line (header)
_, err = slcspReader.Read()
if err != nil {
return zips, err
}
// Read file data
for {
record, err := slcspReader.Read()
// Stop at end of file
if err == io.EOF {
break
}
if err != nil {
return zips, err
}
// Record fields:
// 0 - zipcode
// 1 - rate
// Only store the zipcode field since rate will be empty here
zips = append(zips, record[0])
}
return zips, err
}
// parseZips reads the data from ZipsFileName and adds RateArea info to the zip
func parseZips(zips map[string]*RateData) (map[string]*RateData, error) {
zipsFile, err := os.Open(ZipsFileName)
if err != nil {
return zips, err
}
defer zipsFile.Close()
zipsReader := csv.NewReader(zipsFile)
zipsReader.FieldsPerRecord = 5
// Skip first line (header)
_, err = zipsReader.Read()
if err != nil {
return zips, err
}
// Read file data
for {
record, err := zipsReader.Read()
// Stop at end of file
if err == io.EOF {
break
}
if err != nil {
return zips, err
}
// Record fields:
// 0 - zipcode
// 1 - state
// 2 - county_code
// 3 - name
// 4 - rate_area
zip := record[0]
// Store the rate area if the record's zipcode matches one in zips
// If the rate area is already set and differs from the current record's, mark the data as ambiguous
if _, exists := zips[zip]; exists {
rateArea := concatRateArea(record[1], record[4])
if zips[zip].RateArea == "" {
zips[zip].RateArea = rateArea
} else if zips[zip].RateArea != rateArea {
zips[zip].Ambiguous = true
}
}
}
return zips, err
}
// parsePlans reads the data from PlansFileName and adds Rates to the zip/RateArea struct
func parsePlans(zips map[string]*RateData) (map[string]*RateData, error) {
plansFile, err := os.Open(PlansFileName)
if err != nil {
return zips, err
}
defer plansFile.Close()
plansReader := csv.NewReader(plansFile)
plansReader.FieldsPerRecord = 5
// Skip first line (header)
_, err = plansReader.Read()
if err != nil {
return zips, err
}
// Read file data
for {
record, err := plansReader.Read()
// Stop at end of file
if err == io.EOF {
break
}
if err != nil {
return zips, err
}
// Record fields:
// 0 - plan_id
// 1 - state
// 2 - metal_level
// 3 - rate
// 4 - rate_area
rateArea := concatRateArea(record[1], record[4])
rate, err := strconv.ParseFloat(record[3], 64)
if err != nil {
return zips, err
}
// Loop through each stored rate area
// Store the rate if the record's rate area matches and it's a Silver plan
// Skip the zip's rate area if it's been marked as ambiguous
for _, rateData := range zips {
if rateArea == rateData.RateArea && !rateData.Ambiguous && record[2] == "Silver" {
rateData.Rates = append(rateData.Rates, rate)
}
}
}
return zips, err
}
func main() {
// Read SlcspFileName to get zip codes to be checked
zips, err := parseSlcsp()
if err != nil {
log.Fatal("Error parsing data from "+SlcspFileName, err)
}
// Create map from slice returned by parseSlcsp
zipData := make(map[string]*RateData)
for _, zip := range zips {
zipData[zip] = &RateData{}
}
// Read ZipsFileName to get zip to rate area mappings
zipData, err = parseZips(zipData)
if err != nil {
log.Fatal("Error parsing data from "+ZipsFileName, err)
}
// Read PlansFileName to get rates for each rate area
zipData, err = parsePlans(zipData)
if err != nil {
log.Fatal("Error parsing data from "+PlansFileName, err)
}
// Output
fmt.Println("zipcode,rate")
for _, zip := range zips {
rateData := zipData[zip]
// If no second lowest rate, just output zip
if len(rateData.Rates) < 2 {
fmt.Println(zip + ",")
} else {
sort.Float64s(rateData.Rates) // sort least to greatest
fmt.Printf("%s,%.2f\n", zip, rateData.Rates[1])
}
}
}