fbo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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