fbo 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +27 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +39 -0
  8. data/Rakefile +1 -0
  9. data/fbo.gemspec +30 -0
  10. data/lib/fbo.rb +25 -0
  11. data/lib/fbo/file.rb +14 -0
  12. data/lib/fbo/notice.rb +9 -0
  13. data/lib/fbo/notices.rb +6 -0
  14. data/lib/fbo/notices/amendment.rb +13 -0
  15. data/lib/fbo/notices/award.rb +14 -0
  16. data/lib/fbo/notices/combined_solicitation.rb +14 -0
  17. data/lib/fbo/notices/modification.rb +14 -0
  18. data/lib/fbo/notices/presolicitation.rb +14 -0
  19. data/lib/fbo/notices/sources_sought.rb +13 -0
  20. data/lib/fbo/notices/unknown.rb +7 -0
  21. data/lib/fbo/parser.rb +79 -0
  22. data/lib/fbo/parser/amendment_handler.rb +58 -0
  23. data/lib/fbo/parser/award_handler.rb +62 -0
  24. data/lib/fbo/parser/combined_solicitation_handler.rb +57 -0
  25. data/lib/fbo/parser/modification_handler.rb +66 -0
  26. data/lib/fbo/parser/notice_handler.rb +27 -0
  27. data/lib/fbo/parser/parser_helper.rb +275 -0
  28. data/lib/fbo/parser/presolicitation_handler.rb +57 -0
  29. data/lib/fbo/parser/sources_sought_handler.rb +57 -0
  30. data/lib/fbo/parser/unknown_handler.rb +20 -0
  31. data/lib/fbo/remote_file.rb +39 -0
  32. data/lib/fbo/version.rb +3 -0
  33. data/spec/fbo/file_spec.rb +20 -0
  34. data/spec/fbo/parser/amendment_handler_spec.rb +46 -0
  35. data/spec/fbo/parser/award_handler_spec.rb +51 -0
  36. data/spec/fbo/parser/combined_solicitation_handler_spec.rb +47 -0
  37. data/spec/fbo/parser/modification_handler_spec.rb +47 -0
  38. data/spec/fbo/parser/presolicitation_handler_spec.rb +46 -0
  39. data/spec/fbo/parser/sources_sought_handler_spec.rb +46 -0
  40. data/spec/fbo/parser/unknown_handler_spec.rb +19 -0
  41. data/spec/fbo/parser_spec.rb +85 -0
  42. data/spec/fbo/remote_file_spec.rb +55 -0
  43. data/spec/fixtures/FBOFeed20130331 +5668 -0
  44. data/spec/fixtures/FBOFeed20130404 +45653 -0
  45. data/spec/fixtures/FBOFeed20130406 +10152 -0
  46. data/spec/fixtures/FBOFeed20130407 +6610 -0
  47. data/spec/fixtures/notices/amdcss +26 -0
  48. data/spec/fixtures/notices/award +31 -0
  49. data/spec/fixtures/notices/combine +29 -0
  50. data/spec/fixtures/notices/mod +28 -0
  51. data/spec/fixtures/notices/presol +25 -0
  52. data/spec/fixtures/notices/snote +26 -0
  53. data/spec/fixtures/notices/srcsgt +27 -0
  54. data/spec/spec_helper.rb +23 -0
  55. metadata +154 -0
@@ -0,0 +1,62 @@
1
+ module FBO::Parser::AwardHandler
2
+ TAGS = %w( AWARD DATE YEAR CBAC PASSWORD ZIP CLASSCOD NAICS OFFADD ) +
3
+ %w( AGENCY OFFICE LOCATION SUBJECT SOLNBR NTYPE DESC CONTACT AWDNBR AWDAMT ) +
4
+ %w( LINENBR AWDDATE ARCHDATE AWARDEE AWARDEEDUNS LINK URL EMAIL ADDRESS ) +
5
+ %w( SETASIDE CORRECTION /AWARD )
6
+
7
+ ANY_AWARD_TAG = TAGS.join("|")
8
+ AWARD_PATTERN = /^<AWARD>/
9
+
10
+
11
+ def is_award?(text)
12
+ text =~ AWARD_PATTERN
13
+ end
14
+
15
+ def parse(text)
16
+ params = {
17
+ date: date(text),
18
+ year: year(text),
19
+ zip: zip(text),
20
+ class_code: classification_code(text),
21
+ naics_code: naics_code(text),
22
+ agency: agency(text),
23
+ office: office(text),
24
+ location: location(text),
25
+ office_address: office_address(text),
26
+ subject: subject(text),
27
+ solicitation_number: solicitation_number(text),
28
+ notice_type: notice_type(text),
29
+ description: description(text),
30
+ contact_info: contact(text),
31
+ award_number: award_number(text),
32
+ award_amount: award_amount(text),
33
+ line_number: line_number(text),
34
+ award_date: award_date(text),
35
+ archive_date: archive_date(text),
36
+ awardee: awardee(text),
37
+ awardee_duns: awardee_duns(text),
38
+ link_url: link_url(text),
39
+ link_description: link_description(text),
40
+ email_address: email_address(text),
41
+ email_description: email_description(text),
42
+ setaside: set_aside(text),
43
+ correction: correction?(text)
44
+ }
45
+ FBO::Notices::Award.new(params)
46
+ end
47
+
48
+
49
+ protected
50
+
51
+ # Returns a concatenated list of all tags for the notice
52
+ #
53
+ # @return [String]
54
+ def any_notice_tag
55
+ ANY_AWARD_TAG
56
+ end
57
+
58
+
59
+ extend FBO::Parser::NoticeHandler
60
+ extend FBO::Parser::ParserHelper
61
+ extend self
62
+ end
@@ -0,0 +1,57 @@
1
+ module FBO::Parser::CombinedSolicitationHandler
2
+ TAGS = %w( COMBINE DATE YEAR CBAC PASSWORD ZIP CLASSCOD NAICS OFFADD ) +
3
+ %w( AGENCY OFFICE LOCATION SUBJECT SOLNBR RESPDATE ARCHDATE CONTACT DESC ) +
4
+ %w( LINK URL EMAIL ADDRESS SETASIDE POPADDRESS POPZIP POPCOUNTRY \/COMBINE )
5
+
6
+ ANY_COMBINE_TAG = TAGS.join("|")
7
+ COMBINED_PATTERN = /^<COMBINE>/
8
+
9
+
10
+ def is_combined_solicitation?(text)
11
+ text =~ COMBINED_PATTERN
12
+ end
13
+
14
+ def parse(text)
15
+ params = {
16
+ date: date(text),
17
+ year: year(text),
18
+ zip: zip(text),
19
+ class_code: classification_code(text),
20
+ naics_code: naics_code(text),
21
+ agency: agency(text),
22
+ office: office(text),
23
+ location: location(text),
24
+ office_address: office_address(text),
25
+ subject: subject(text),
26
+ solicitation_number: solicitation_number(text),
27
+ response_date: response_date(text),
28
+ archive_date: archive_date(text),
29
+ contact_info: contact(text),
30
+ description: description(text),
31
+ link_url: link_url(text),
32
+ link_description: link_description(text),
33
+ email_address: email_address(text),
34
+ email_description: email_description(text),
35
+ setaside: set_aside(text),
36
+ pop_address: pop_address(text),
37
+ pop_zip: pop_zip_code(text),
38
+ pop_country: pop_country(text)
39
+ }
40
+ FBO::Notices::CombinedSolicitation.new(params)
41
+ end
42
+
43
+
44
+ protected
45
+
46
+ # Returns a concatenated list of all tags for the notice
47
+ #
48
+ # @return [String]
49
+ def any_notice_tag
50
+ ANY_COMBINE_TAG
51
+ end
52
+
53
+
54
+ extend FBO::Parser::NoticeHandler
55
+ extend FBO::Parser::ParserHelper
56
+ extend self
57
+ end
@@ -0,0 +1,66 @@
1
+ module FBO::Parser::ModificationHandler
2
+ TAGS = %w( MOD DATE YEAR CBAC PASSWORD ZIP CLASSCOD NAICS OFFADD AGENCY) +
3
+ %w( OFFICE LOCATION SUBJECT SOLNBR NTYPE RESPDATE ARCHDATE CONTACT DESC ) +
4
+ %w( LINK URL EMAIL ADDRESS SETASIDE POPADDRESS POPZIP POPCOUNTRY \/MOD )
5
+
6
+ ANY_MOD_TAG = TAGS.join("|")
7
+ MODIFICATION_PATTERN = /^<MOD>/
8
+
9
+
10
+ def is_modification?(text)
11
+ text =~ MODIFICATION_PATTERN
12
+ end
13
+
14
+ def parse(text)
15
+ params = {
16
+ date: date(text),
17
+ year: year(text),
18
+ zip: zip(text),
19
+ class_code: classification_code(text),
20
+ naics_code: naics_code(text),
21
+ agency: agency(text),
22
+ office: office(text),
23
+ location: location(text),
24
+ office_address: office_address(text),
25
+ subject: subject(text),
26
+ solicitation_number: solicitation_number(text),
27
+ notice_type: notice_type(text),
28
+ response_date: response_date(text),
29
+ archive_date: archive_date(text),
30
+ contact_info: contact(text),
31
+ description: description(text),
32
+ link_url: link_url(text),
33
+ link_description: link_description(text),
34
+ email_address: email_address(text),
35
+ email_description: email_description(text),
36
+ setaside: set_aside(text),
37
+ pop_address: pop_address(text),
38
+ pop_zip: pop_zip_code(text),
39
+ pop_country: pop_country(text)
40
+ }
41
+ FBO::Notices::Modification.new(params)
42
+ end
43
+
44
+
45
+ protected
46
+
47
+ # Returns a concatenated list of all tags for the notice
48
+ #
49
+ # @return [String]
50
+ def any_notice_tag
51
+ ANY_MOD_TAG
52
+ end
53
+
54
+ # Get the notice type that this modification is affecting.
55
+ #
56
+ # @param text [String] the full text of the notice
57
+ # @return [Date] the notice type
58
+ def notice_type(text)
59
+ simple_tag_contents(text, "NTYPE")
60
+ end
61
+
62
+
63
+ extend FBO::Parser::NoticeHandler
64
+ extend FBO::Parser::ParserHelper
65
+ extend self
66
+ end
@@ -0,0 +1,27 @@
1
+ module FBO::Parser::NoticeHandler
2
+
3
+ protected
4
+
5
+ # Returns a concatenated list of all tags for the notice
6
+ #
7
+ # @return [String]
8
+ def any_notice_tag
9
+ ""
10
+ end
11
+
12
+ # Given a tag string, return the data associated with it as a String object
13
+ # or nil if not present.
14
+ #
15
+ # @param text [String] the full text of the notice
16
+ # @param tag [String] the tag that should be searched for
17
+ # @return [String] contents or nil if not present
18
+ def simple_tag_contents(text, tag)
19
+ regexp = (tag.is_a?(Regexp) ? tag : /<#{ tag }>/)
20
+ match = text.match(regexp)
21
+ return nil unless match
22
+ match2 = match.post_match.match(/<(?:#{ any_notice_tag })>/)
23
+ prematch = match2.pre_match if match2
24
+ prematch ? prematch.strip : nil
25
+ end
26
+
27
+ end
@@ -0,0 +1,275 @@
1
+ require "date"
2
+
3
+ module FBO::Parser::ParserHelper
4
+ extend FBO::Parser::NoticeHandler
5
+
6
+ protected
7
+
8
+ # Get the date of the notice.
9
+ #
10
+ # @param text [String] the full text of the notice
11
+ # @return [Date] the date that the notice was posted
12
+ def date(text)
13
+ date_string = simple_tag_contents(text, "DATE")
14
+ Date.parse("#{ year(text) }#{ date_string }")
15
+ end
16
+
17
+ # Get the year from the notice or assume the current year.
18
+ #
19
+ # @param [text] the whole notice text
20
+ # @return [Fixnum] the year of the notice
21
+ def year(text)
22
+ year_string = simple_tag_contents(text, "YEAR")
23
+ "20#{ year_string }".to_i
24
+ end
25
+
26
+ # Get the zip code of the contracting office.
27
+ #
28
+ # @param text [String] the full text of the notice
29
+ # @return [String] the zip code of the contracting office
30
+ def zip(text)
31
+ simple_tag_contents(text, "ZIP")
32
+ end
33
+
34
+ # Get the two-digit or single alphabetic code for classifying the notice.
35
+ #
36
+ # @param text [String] the full text of the notice
37
+ # @return [String] the classification code as a string
38
+ def classification_code(text)
39
+ simple_tag_contents(text, "CLASSCOD")
40
+ end
41
+
42
+ # Get the NAICS code that should be referenced with this notice.
43
+ #
44
+ # @param text [String] the full text of the notice
45
+ # @return [String] the relevant NAICS code
46
+ def naics_code(text)
47
+ simple_tag_contents(text, "NAICS")
48
+ end
49
+
50
+ # Get the agency name for this notice.
51
+ #
52
+ # @param text [String] the full text of the notice
53
+ # @return [String] the name of the agency
54
+ def agency(text)
55
+ simple_tag_contents(text, "AGENCY")
56
+ end
57
+
58
+ # Get the agency office name for this notice.
59
+ #
60
+ # @param text [String] the full text of the notice
61
+ # @return [String] the name of the agency office
62
+ def office(text)
63
+ simple_tag_contents(text, "OFFICE")
64
+ end
65
+
66
+ # Get the location of the office issuing the notice (often used for department).
67
+ #
68
+ # @param text [String] the full text of the notice
69
+ # @return [String] the location text
70
+ def location(text)
71
+ simple_tag_contents(text, "LOCATION")
72
+ end
73
+
74
+ # Get the office address from which the notice is issued.
75
+ #
76
+ # @param text [String] the full text of the notice
77
+ # @return [String] the office address
78
+ def office_address(text)
79
+ simple_tag_contents(text, "OFFADD")
80
+ end
81
+
82
+ # Get the subject of the notice.
83
+ #
84
+ # @param text [String] the full text of the notice
85
+ # @return [String] the subject address
86
+ def subject(text)
87
+ simple_tag_contents(text, "SUBJECT")
88
+ end
89
+
90
+ # Get the solicitation number for the notice.
91
+ #
92
+ # @param text [String] the full text of the notice
93
+ # @return [String] the solicitation number
94
+ def solicitation_number(text)
95
+ simple_tag_contents(text, "SOLNBR")
96
+ end
97
+
98
+ # Get the type of the related notice in the case of dependent notices.
99
+ #
100
+ # @param text [String] the full text of the notice
101
+ # @return [String] the notice type
102
+ def notice_type(text)
103
+ simple_tag_contents(text, "NTYPE")
104
+ end
105
+
106
+ # Get the deadline for responding to the notice.
107
+ #
108
+ # @param text [String] the full text of the notice
109
+ # @return [Date] the response date
110
+ def response_date(text)
111
+ date_string = simple_tag_contents(text, "RESPDATE")
112
+ date_string ? Date.strptime(date_string, "%m%d%y") : nil
113
+ end
114
+
115
+ # Get the date when the notice will be archived.
116
+ #
117
+ # @param text [String] the full text of the notice
118
+ # @return [Date] the archive date
119
+ def archive_date(text)
120
+ date_string = simple_tag_contents(text, "ARCHDATE")
121
+ date_string ? Date.strptime(date_string, "%m%d%Y") : nil
122
+ end
123
+
124
+ # Get the contact information for responding to the notice.
125
+ #
126
+ # @param text [String] the full text of the notice
127
+ # @return [String] the contact info
128
+ def contact(text)
129
+ simple_tag_contents(text, "CONTACT")
130
+ end
131
+
132
+ # Get the full description of the notice.
133
+ # This implementation accounts for the fact that there may be more than one
134
+ # DESC tag in the notice and that the one we want will be the first one.
135
+ #
136
+ # @param text [String] the full text of the notice
137
+ # @return [String] the description
138
+ def description(text)
139
+ match = text.match(/<DESC>/)
140
+ match2 = match.post_match.match(/<(?:#{ any_notice_tag })>/)
141
+ prematch = match2.pre_match
142
+ prematch ? prematch.strip : nil
143
+ end
144
+
145
+ # Get the link URL.
146
+ #
147
+ # @param text [String] the full text of the notice
148
+ # @return [String] the URL
149
+ def link_url(text)
150
+ match = text.match(/<LINK>/)
151
+ simple_tag_contents(match.post_match, "URL") if match
152
+ end
153
+
154
+ # Get the link description / text.
155
+ #
156
+ # @param text [String] the full text of the notice
157
+ # @return [String] the link description
158
+ def link_description(text)
159
+ match = text.match(/<LINK>/)
160
+ description(match.post_match) if match
161
+ end
162
+
163
+ # Get the contact email address.
164
+ #
165
+ # @param text [String] the full text of the notice
166
+ # @return [String] the email address
167
+ def email_address(text)
168
+ match = text.match(/<EMAIL>/)
169
+ simple_tag_contents(match.post_match, "(EMAIL|ADDRESS)") if match
170
+ end
171
+
172
+ # Get the contact email description (link text).
173
+ #
174
+ # @param text [String] the full text of the notice
175
+ # @return [String] the email description
176
+ def email_description(text)
177
+ match = text.match(/<EMAIL>/)
178
+ description(match.post_match) if match
179
+ end
180
+
181
+ # Get the set-aside identifier for the notice.
182
+ #
183
+ # @param text [String] the full text of the notice
184
+ # @return [String] the set-aside
185
+ def set_aside(text)
186
+ simple_tag_contents(text, "SETASIDE")
187
+ end
188
+
189
+ # Get the address of the "place of performance".
190
+ #
191
+ # @param text [String] the full text of the notice
192
+ # @return [String] the address
193
+ def pop_address(text)
194
+ simple_tag_contents(text, "POPADDRESS")
195
+ end
196
+
197
+ # Get the zip code of the "place of performance".
198
+ #
199
+ # @param text [String] the full text of the notice
200
+ # @return [String] the zip code
201
+ def pop_zip_code(text)
202
+ simple_tag_contents(text, "POPZIP")
203
+ end
204
+
205
+ # Get the country of the "place of performance".
206
+ #
207
+ # @param text [String] the full text of the notice
208
+ # @return [String] the country
209
+ def pop_country(text)
210
+ simple_tag_contents(text, "POPCOUNTRY")
211
+ end
212
+
213
+ # Get the award number with which the notice is associated.
214
+ #
215
+ # @param text [String] the full text of the notice
216
+ # @return [String] the award number
217
+ def award_number(text)
218
+ simple_tag_contents(text, "AWDNBR")
219
+ end
220
+
221
+ # Get the amount of the award with which the notice is associated.
222
+ #
223
+ # @param text [String] the full text of the notice
224
+ # @return [Float] the award amount (in dollars)
225
+ def award_amount(text)
226
+ amount_string = simple_tag_contents(text, "AWDAMT")
227
+ amount_string.gsub(/[^\d\.]/, "").to_f
228
+ end
229
+
230
+ # Get the line number of the contract with which the award is associated.
231
+ #
232
+ # @param text [String] the full text of the notice
233
+ # @return [String] the line number (which might not be a Fixnum)
234
+ def line_number(text)
235
+ simple_tag_contents(text, "LINENBR")
236
+ end
237
+
238
+ # Get the date of the award.
239
+ #
240
+ # @param text [String] the full text of the notice
241
+ # @return [Date] the date that the award was given
242
+ def award_date(text)
243
+ date_string = simple_tag_contents(text, "AWDDATE")
244
+ Date.strptime(date_string, "%m%d%y")
245
+ end
246
+
247
+ # Get the name of the awardee.
248
+ #
249
+ # @param text [String] the full text of the notice
250
+ # @return [String] the awardee's name
251
+ def awardee(text)
252
+ simple_tag_contents(text, "AWARDEE")
253
+ end
254
+
255
+ # Get the DUNS number for the awardee.
256
+ #
257
+ # @param text [String] the full text of the notice
258
+ # @return [String] the awardee's DUNS number
259
+ def awardee_duns(text)
260
+ simple_tag_contents(text, "AWARDEEDUNS")
261
+ end
262
+
263
+ # If the notice is a correction of a previous notice, return true; otherwise, false.
264
+ #
265
+ # @param text [String] the full text of the notice
266
+ # @return [boolean]
267
+ def correction?(text)
268
+ correction_string = simple_tag_contents(text, "CORRECTION")
269
+ if correction_string && correction_string =~ /^y/i
270
+ return true
271
+ end
272
+ false
273
+ end
274
+
275
+ end