fcc 1.1 → 1.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1a2e65473c73d939b88beed853dfe8cb2ab4bb67684c522996a4b1eb0f8d7f8
4
- data.tar.gz: d86f808b6da433d743ac6e2546307cf4bdfeb42ec521796cb78a0ecfffb7da63
3
+ metadata.gz: d0c284a2224092f6ee580603ca213e6754277f0c205296a606dfa3b1c6d33990
4
+ data.tar.gz: da7535bfbc0a8f367f24d67f080a939fccced3a545c9f92d749aabf322ce4043
5
5
  SHA512:
6
- metadata.gz: e6a3bea8e717984ddd25fed17731e3ba06709c70dcb49340a274d148fcdd281e1463baf6483879d9a2427961589f2e0d7af908d8ce4ab6508776c8ba1d80c5e3
7
- data.tar.gz: 6da0ba2408b94f8488c45900938d3950e580a5cb71493b942a78696b462f0bc4dee61ea26b5bf4cbd1c54a155ad0b0c5670cde37e90c99ede9581b196cd75586
6
+ metadata.gz: ef717fe2529d3773b8c68208b6fd3a1a86c6aa270873a9c4d4bf9a9a70caad6808f2e46a137dc1243f47129b15105954817d4217c0504bf3bcfd19d2b7224e09
7
+ data.tar.gz: 30a58df413f3e68a5a858b2fd24d147c40b3c694f2a12ce0c2275be48a19db0ad12c985f4d669a5a555573cfa6af76ee1186e63373d7607c38af287ad8b637df
data/Gemfile.lock CHANGED
@@ -1,14 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fcc (1.1)
4
+ fcc (1.2.0)
5
5
  activesupport (>= 6.1)
6
6
  httparty (~> 0.18)
7
+ lightly (~> 0.3.3)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
- activesupport (6.1.0)
12
+ activesupport (6.1.4)
12
13
  concurrent-ruby (~> 1.0, >= 1.0.2)
13
14
  i18n (>= 1.6, < 2)
14
15
  minitest (>= 5.1)
@@ -16,17 +17,18 @@ GEM
16
17
  zeitwerk (~> 2.3)
17
18
  awesome_print (1.8.0)
18
19
  byebug (11.1.3)
19
- concurrent-ruby (1.1.7)
20
+ concurrent-ruby (1.1.9)
20
21
  diff-lcs (1.4.4)
21
22
  httparty (0.18.1)
22
23
  mime-types (~> 3.0)
23
24
  multi_xml (>= 0.5.2)
24
- i18n (1.8.6)
25
+ i18n (1.8.10)
25
26
  concurrent-ruby (~> 1.0)
27
+ lightly (0.3.3)
26
28
  mime-types (3.3.1)
27
29
  mime-types-data (~> 3.2015)
28
- mime-types-data (3.2020.1104)
29
- minitest (5.14.2)
30
+ mime-types-data (3.2021.0704)
31
+ minitest (5.14.4)
30
32
  multi_xml (0.6.0)
31
33
  rake (12.3.3)
32
34
  rspec (3.9.0)
data/README.md CHANGED
@@ -19,28 +19,33 @@ gem install fcc
19
19
  ```ruby
20
20
  station = FCC::Station.find(:fm, "KOOP")
21
21
 
22
- #Basic attributes, available quickly because the FCC actually caches these in a CDN:
23
- station.id #=> 65320
24
- station.status #=> LICENSED
25
- station.rf_channel #=> 219
26
- station.license_expiration_date #=> "08/01/2021"
27
- station.facility_type #=> ED
28
- station.frequency #=> 91.7
29
- station.contact #=> <struct FCC::Station::Contact>
30
- station.owner #=> <struct FCC::Station::Contact>
31
- station.community #=> <struct FCC::Station::Community city="HORNSBY", state="TX">
32
-
33
- #Extended attributes, takes several seconds to load because the FCC is running this endpoint on a 1960s era mainframe operated by trained hamsters
34
- station.station_class #=> A
35
- station.signal_strength #=> 3.0 kW
36
- station.antenna_type #=> ND
37
- station.effective_radiated_power #=> 3.0 kW
38
- station.haat_horizontal #=> 26.0
39
- station.haat_vertical #=> 26.0
40
- station.latitude #=> "30.266861111111112"
41
- station.longitude #=> "-97.67444444444445"
42
- station.file_number #=> BLED-19950103KA
43
- ```
22
+ if station.exists? && station.licensed?
23
+ #Basic attributes, available quickly because the FCC actually caches these in a CDN:
24
+ station.id #=> 65320
25
+ station.status #=> LICENSED
26
+ station.rf_channel #=> 219
27
+ station.license_expiration_date #=> "08/01/2021"
28
+ station.facility_type #=> ED
29
+ station.frequency #=> 91.7
30
+ station.contact #=> <struct FCC::Station::Contact>
31
+ station.owner #=> <struct FCC::Station::Contact>
32
+ station.community #=> <struct FCC::Station::Community city="HORNSBY", state="TX">
33
+
34
+ # Extended attributes, takes several seconds to load initially because the FCC is running this endpoint on a 1960s era mainframe operated by trained hamsters.
35
+ station.station_class #=> A
36
+ station.signal_strength #=> 3.0 kW
37
+ station.antenna_type #=> ND
38
+ station.effective_radiated_power #=> 3.0 kW
39
+ station.haat_horizontal #=> 26.0
40
+ station.haat_vertical #=> 26.0
41
+ station.latitude #=> "30.266861111111112"
42
+ station.longitude #=> "-97.67444444444445"
43
+ station.file_number #=> BLED-19950103KA
44
+ ```
45
+ end
46
+
47
+ ### Caching
48
+ Extended attributes take several seconds to load from transition.fcc.gov. In order to work around this, we query the entire dataset and then cache the result locally for 3 days (using the lightly gem). To use your own cache, set `FCC.cache=` to your cache class (Rails.cache, maybe?) which should have a #fetch method that should take a key and a block like `cache.fetch(key) { // yield for expensive fetch }}`.
44
49
 
45
50
  ### Get all station call signs on a particular service (:fm, :am, :tv)
46
51
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 1.2.0
data/fcc.gemspec CHANGED
@@ -6,7 +6,6 @@ Gem::Specification.new do |spec|
6
6
  spec.name = %q{fcc}
7
7
  spec.version = FCC::VERSION
8
8
  spec.authors = ["Jeff Keen"]
9
- spec.date = %q{2011-01-30}
10
9
  spec.description = %q{}
11
10
  spec.email = %q{jeff@keen.me}
12
11
  spec.homepage = "http://github.com/jkeen/fcc"
@@ -20,6 +19,7 @@ Gem::Specification.new do |spec|
20
19
  spec.summary = %q{Searches the FCC's FM, AM, and TV databases}
21
20
  spec.add_dependency "activesupport", ">= 6.1"
22
21
  spec.add_dependency "httparty", "~> 0.18"
22
+ spec.add_dependency "lightly", "~> 0.3.3"
23
23
  spec.add_development_dependency "bundler", "~> 2.1"
24
24
  spec.add_development_dependency "rake", "~> 12.3.3"
25
25
  spec.add_development_dependency 'rspec', '~> 3.9.0'
data/lib/fcc.rb CHANGED
@@ -6,5 +6,11 @@ require_relative './fcc/station/extended_info'
6
6
  require_relative './fcc/station/result_delegate'
7
7
 
8
8
  module FCC
9
+ def self.cache
10
+ @cache ||= Station::Cache.new
11
+ end
9
12
 
13
+ def self.cache=(cache_service)
14
+ @cache = cache_service
15
+ end
10
16
  end
data/lib/fcc/station.rb CHANGED
@@ -18,7 +18,9 @@ module FCC
18
18
  end
19
19
 
20
20
  def self.find(service, call_sign, options = {})
21
- Result.new(service, call_sign, options)
21
+ result = Result.new(service, call_sign, options)
22
+
23
+ result if result&.exists?
22
24
  end
23
25
 
24
26
  def self.index(service)
@@ -34,14 +36,12 @@ module FCC
34
36
  end
35
37
  end
36
38
 
37
- def self.extended_info_cache(service)
38
- @cache ||= {}
39
- @cache[service] ||= Station::Cache.new(service)
40
- @cache[service]
39
+ def self.extended_info_cache
40
+ @cache ||= Station::Cache.new
41
41
  end
42
42
 
43
43
  class Result
44
- EXTENDED_ATTRIBUTES = %i[signal_strength latitude longitude coordinates station_class file_number effective_radiated_power haat_horizontal haat_vertical] # these take a long time to query
44
+ EXTENDED_ATTRIBUTES = %i[signal_strength latitude longitude station_class file_number effective_radiated_power haat_horizontal haat_vertical antenna_type] # these take a long time to query
45
45
  BASIC_ATTRIBUTES = %i[id call_sign status rf_channel license_expiration_date facility_type frequency]
46
46
 
47
47
  delegate *EXTENDED_ATTRIBUTES, to: :extended_data
@@ -57,15 +57,31 @@ module FCC
57
57
  data
58
58
  end
59
59
 
60
+ def details_available?
61
+ exists? && data.latitude.present?
62
+ end
63
+
64
+ def licensed?
65
+ exists? && data.status == 'LICENSED' && data.license_expiration_date && Time.parse(data.license_expiration_date) > Time.now
66
+ end
67
+
68
+ def exists?
69
+ data.instance_variable_get('@result')
70
+ end
71
+
60
72
  def to_json
61
73
  {}.tap do |hash|
62
74
  [EXTENDED_ATTRIBUTES | BASIC_ATTRIBUTES | %i[contact owner community]].flatten.each do |attr|
63
75
  result = send(attr.to_sym)
64
- if result.respond_to?(:to_h)
65
- hash[attr] = result.to_h
66
- else
67
- hash[attr] = result.to_s
68
- end
76
+ next unless result
77
+
78
+ hash[attr] = if result.is_a?(Struct)
79
+ result.to_h
80
+ elsif result.is_a?(Array) && result.compact.size > 0
81
+ result
82
+ elsif result.present?
83
+ result.to_s
84
+ end
69
85
  end
70
86
  end
71
87
  end
@@ -78,23 +94,27 @@ module FCC
78
94
  @community ||= Community.new(city: data.communityCity, state: data.communityState)
79
95
  end
80
96
 
97
+ def operating_hours
98
+ extended_data.am_operating_time if @service == :am
99
+ end
100
+
81
101
  def contact
82
102
  contact = data.mainStudioContact
83
103
  @contact ||= Contact.new(name: contact['contactName'], title: contact['contactTitle'], address: contact['contactAddress1'], address2: contact['contactAddress2'], city: contact['contactCity'], state: contact['contactState'], zip_code: contact['contactZip'], phone: contact['contactPhone'], fax: contact['contactFax'], email: contact['contactEmail'], website: contact['contactWebsite'])
84
104
  end
85
105
 
86
- def coordinates
87
- [latitude.to_f, longitude.to_f].to_json
88
- end
89
-
90
106
  def coordinates_url
91
- "https://www.google.com/maps/search/#{coordinates[0]},#{coordinates[1]}"
107
+ "https://www.google.com/maps/search/#{latitude},#{longitude}" if latitude.present? && longitude.present?
92
108
  end
93
109
 
94
110
  def extended_data_url
95
111
  "https://transition.fcc.gov/fcc-bin/#{@service.to_s.downcase}q?list=4&facid=#{id}"
96
112
  end
97
113
 
114
+ def enterprise_data_url
115
+ "https://enterpriseefiling.fcc.gov/dataentry/public/tv/publicFacilityDetails.html?facilityId=#{id}"
116
+ end
117
+
98
118
  def extended_data
99
119
  @extended_data ||= ResultDelegate.new(ExtendedInfo.new(@service).find(@call_sign))
100
120
  end
@@ -1,28 +1,22 @@
1
1
  require 'httparty'
2
- require 'byebug'
3
2
  require_relative './extended_info/parser'
3
+ require 'byebug'
4
+ require 'lightly'
4
5
 
5
6
  module FCC
6
7
  module Station
7
8
  class Cache
8
9
  attr_reader :store
9
10
 
10
- def initialize(service)
11
- @service = service
12
- @store = Station::ExtendedInfo.new(@service)
13
- end
14
-
15
- def find(fcc_id)
16
- results.detect { |r| r[:fcc_id].to_s == fcc_id.to_s }
17
- end
18
-
19
- def results
20
- #TODO: add redis caching tie-in because this query is molasses
21
- @store.all_results.parsed_response
11
+ def initialize
12
+ @lightly = Lightly.new dir: "tmp/fcc_#{@service}_data", life: '3d', hash: true
22
13
  end
23
14
 
24
- def inspect
25
- "<Cache @service=#{service}>"
15
+ def fetch key
16
+ @lightly.get key.to_s do
17
+ puts "loading up cache with all results"
18
+ yield
19
+ end
26
20
  end
27
21
  end
28
22
  end
@@ -7,7 +7,8 @@ module FCC
7
7
  class ExtendedInfo
8
8
  include HTTParty
9
9
  attr_accessor :results, :service
10
- base_uri 'http://transition.fcc.gov/fcc-bin/'
10
+
11
+ base_uri 'https://transition.fcc.gov/fcc-bin/'
11
12
 
12
13
  def initialize(service)
13
14
  @service = service
@@ -19,8 +20,8 @@ module FCC
19
20
  # call: nil,
20
21
  # city: nil,
21
22
  # arn: nil,
22
- serv: service.to_s.downcase,
23
- vac: 3, # licensed records only
23
+ serv: service.to_s.downcase, # Only return primary main records, no backup transmitters, etc… for now
24
+ status: 3, # licensed records only
24
25
  # freq: @service.to_sym == :fm ? '87.1' : '530',
25
26
  # fre2: @service.to_sym == :fm ? '107.9' : '1700',
26
27
  # facid: nil,
@@ -42,11 +43,16 @@ module FCC
42
43
  end
43
44
 
44
45
  def all_results
45
- @all_results ||= begin
46
- puts "charging cache for #{service} extended info"
47
- response = self.class.get("/#{service.to_s.downcase}q", @options.merge(query: @query))
48
- puts response.request.uri.to_s.gsub('&list=4', '&list=0')
49
- response
46
+ begin
47
+ cache_key = "#{self.class.instance_variable_get('@default_options')[:base_uri]}/#{@service.to_s.downcase}q"
48
+ FCC.cache.fetch cache_key do
49
+ response = self.class.get("/#{service.to_s.downcase}q", @options.merge(query: @query))
50
+ puts response.request.uri.to_s.gsub('&list=4', '&list=0')
51
+ response.parsed_response
52
+ end
53
+ rescue StandardError => e
54
+ puts e.message
55
+ puts e.backtrace
50
56
  end
51
57
  end
52
58
 
@@ -58,13 +64,11 @@ module FCC
58
64
  end
59
65
 
60
66
  begin
61
- FCC::Station.extended_info_cache(@service).find(id)
62
- rescue Exception => e
67
+ all_results.filter { |r| r[:fcc_id].to_s == id.to_s }
68
+ rescue StandardError => e
63
69
  response = self.class.get("/#{service.to_s.downcase}q", @options.merge(query: @query.merge(facid: id)))
64
70
  puts response.request.uri.to_s.gsub('&list=4', '&list=0')
65
- result = response.first
66
- result['source_url'] = response.request.uri.to_s.gsub('&list=4', '&list=0')
67
- result
71
+ response&.parsed_response
68
72
  end
69
73
  end
70
74
 
@@ -1,5 +1,4 @@
1
1
  require 'httparty'
2
- require 'byebug'
3
2
 
4
3
  module FCC
5
4
  module Station
@@ -16,9 +15,9 @@ module FCC
16
15
  attrs[:band] = fields[2]
17
16
  attrs[:channel] = fields[3]
18
17
  attrs[:antenna_type] = fields[4] # Directional Antenna (DA) or NonDirectional (ND)
19
- attrs[:am_operating_time] = fields[5] if fields[5] && attrs[:band] == "AM" # (Only used for AM)
18
+ attrs[:am_operating_time] = fields[5] if fields[5] && attrs[:band]&.upcase == "AM" # (Only used for AM)
20
19
  attrs[:station_class] = fields[6]
21
- attrs[:region_2_station_class] = fields[7] if fields[7] && attrs[:band] == "AM" # (only used for AM)
20
+ attrs[:region_2_station_class] = fields[7] if fields[7] && attrs[:band]&.upcase == "AM" # (only used for AM)
22
21
  attrs[:status] = fields[8]
23
22
  attrs[:city] = fields[9]
24
23
  attrs[:state] = fields[10]
@@ -1,5 +1,5 @@
1
1
  require 'httparty'
2
- require 'byebug'
2
+
3
3
  module FCC
4
4
  module Station
5
5
  class Index
@@ -1,5 +1,4 @@
1
1
  require 'httparty'
2
- require 'byebug'
3
2
 
4
3
  module FCC
5
4
  module Station
@@ -21,7 +20,12 @@ module FCC
21
20
  end
22
21
 
23
22
  response = self.class.get("/api/service/#{service.to_s.downcase}/facility/id/#{id}.json")
24
- response['results']['facility']
23
+
24
+ begin
25
+ response['results']['facility']
26
+ rescue StandardError => e
27
+ return nil
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -1,5 +1,4 @@
1
1
  require 'active_support/inflector'
2
- require 'byebug'
3
2
 
4
3
  module FCC
5
4
  module Station
@@ -9,14 +8,33 @@ module FCC
9
8
  end
10
9
 
11
10
  def method_missing(m, *args, &block)
12
- if @result
13
- matched_key = @result.keys.detect { |d| m.to_s == d.to_s } || @result.keys.detect { |d| m.to_s == d.underscore.to_s }
14
-
15
- if matched_key
16
- @result[matched_key]
17
- else
18
- super
19
- end
11
+ return find_result(@result, m) unless @result.is_a?(Array)
12
+ return find_result(@result.first, m) if @result.size == 1
13
+
14
+ filtered_results = @result.filter { |result|
15
+ result[:status] == 'LIC' # Licensed only, no construction permits
16
+ }
17
+
18
+ filtered_results = filtered_results.collect { |res|
19
+ find_result(res, m)
20
+ }.uniq
21
+
22
+ filtered_results.size == 1 ? filtered_results.first : filtered_results
23
+ end
24
+
25
+ private
26
+
27
+ def find_key(result, name)
28
+ result.keys.detect { |d| name.to_s == d.to_s } || result.keys.detect { |d| name.to_s == d.to_s.underscore }
29
+ end
30
+
31
+ def find_result(result, name)
32
+ matched_key = find_key(result, name)
33
+
34
+ if matched_key
35
+ result[matched_key]
36
+ else
37
+ nil
20
38
  end
21
39
  end
22
40
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FCC
4
- VERSION = '1.1'
4
+ VERSION = '1.2.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fcc
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Keen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2011-01-30 00:00:00.000000000 Z
11
+ date: 2021-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.18'
41
+ - !ruby/object:Gem::Dependency
42
+ name: lightly
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.3
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.3
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -157,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
171
  - !ruby/object:Gem::Version
158
172
  version: '0'
159
173
  requirements: []
160
- rubygems_version: 3.1.2
174
+ rubygems_version: 3.1.4
161
175
  signing_key:
162
176
  specification_version: 4
163
177
  summary: Searches the FCC's FM, AM, and TV databases