Compare commits
No commits in common. 'main' and '156ca54906a7bd71b48339e2f4895127f9a4541b' have entirely different histories.
main
...
156ca54906
@ -0,0 +1,14 @@
|
||||
name: Do Things
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '2.6.x'
|
||||
- run: |
|
||||
gem install bundler
|
||||
bundle install --jobs 4 --retry 3
|
||||
bundle exec rake
|
@ -0,0 +1,13 @@
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.6
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/AccessModifierDeclarations:
|
||||
EnforcedStyle: inline
|
||||
|
||||
Layout/MultilineMethodCallIndentation:
|
||||
EnforcedStyle: indented
|
@ -0,0 +1,7 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2020-01-11 15:25:31 -0500 using RuboCop version 0.78.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
@ -1,42 +0,0 @@
|
||||
// vim:filetype=javascript
|
||||
const defaultSeriesURL = "https://api.bls.gov/publicAPI/v2/timeseries/data/";
|
||||
const defaultSeriesID = "CUUR0200SA0";
|
||||
const defaultStartYear = "2014";
|
||||
|
||||
function CURRENTCPI(blsToken, options) {
|
||||
var seriesURL = options?.seriesURL || defaultSeriesURL;
|
||||
var seriesID = options?.seriesID || defaultSeriesID;
|
||||
var startYear = options?.startYear || defaultStartYear;
|
||||
var endYear = options?.endYear || new Date().getFullYear().toString();
|
||||
|
||||
var reqOptions = {
|
||||
"method": "post",
|
||||
"payload": JSON.stringify({
|
||||
"seriesid": [seriesID],
|
||||
"startyear": startYear,
|
||||
"endyear": endYear
|
||||
}),
|
||||
"contentType": "application/json",
|
||||
"headers": {
|
||||
"Authorization": "token "+blsToken,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
var resp = UrlFetchApp.fetch(seriesURL, reqOptions);
|
||||
var rawData = resp.getContentText().toString().trim();
|
||||
var parsedData = JSON.parse(rawData);
|
||||
var respData = parsedData.Results.series[0].data;
|
||||
var rows = [["year", "period", "period_name", "value"]];
|
||||
|
||||
for (var i = 0; i < respData.length; i++) {
|
||||
rows.push([
|
||||
respData[i].year,
|
||||
respData[i].period,
|
||||
respData[i].periodName,
|
||||
respData[i].value
|
||||
]);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '2.7.2' if ENV.key?('DYNO')
|
||||
|
||||
gem 'aws-sdk', '~> 2'
|
||||
gem 'pry', group: %i[development test]
|
||||
gem 'rack'
|
||||
gem 'rake'
|
||||
gem 'rubocop', group: %i[development test]
|
@ -0,0 +1,49 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
ast (2.4.0)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-sdk (2.11.421)
|
||||
aws-sdk-resources (= 2.11.421)
|
||||
aws-sdk-core (2.11.421)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.11.421)
|
||||
aws-sdk-core (= 2.11.421)
|
||||
aws-sigv4 (1.1.0)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
coderay (1.1.2)
|
||||
jaro_winkler (1.5.4)
|
||||
jmespath (1.4.0)
|
||||
method_source (0.9.2)
|
||||
parallel (1.19.1)
|
||||
parser (2.7.0.1)
|
||||
ast (~> 2.4.0)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
rack (2.0.8)
|
||||
rainbow (3.0.0)
|
||||
rake (13.0.1)
|
||||
rubocop (0.78.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.6)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
ruby-progressbar (1.10.1)
|
||||
unicode-display_width (1.6.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
aws-sdk (~> 2)
|
||||
pry
|
||||
rack
|
||||
rake
|
||||
rubocop
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.2
|
@ -1,20 +1,21 @@
|
||||
# cpi-feed
|
||||
|
||||
Transforms [this](https://api.bls.gov/publicAPI/v2/timeseries/data/CUUSA210SA0)
|
||||
into CSV rows.
|
||||
into [this](https://s3.amazonaws.com/meatballhat/cpi/current.csv) :tada:.
|
||||
|
||||
## usage
|
||||
|
||||
This is intended for use with Google Sheets via:
|
||||
|
||||
```
|
||||
=CURRENTCPI("{bls-token}")
|
||||
=IMPORTDATA("https://s3.amazonaws.com/meatballhat/cpi/current.csv")
|
||||
```
|
||||
|
||||
where `{bls-token}` is the value issued after registering with
|
||||
[bls.gov](https://www.bls.gov/developers/home.htm).
|
||||
|
||||
## deployment
|
||||
|
||||
Just like [this
|
||||
example](https://apipheny.io/import-json-google-sheets/).
|
||||
A copy of this thing is deployed to Heroku with a Heroku Scheduler addon
|
||||
configured to run the following once daily:
|
||||
|
||||
``` bash
|
||||
bundle exec ./sync
|
||||
```
|
||||
|
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
begin
|
||||
require 'rubocop/rake_task'
|
||||
rescue LoadError => e
|
||||
warn e
|
||||
end
|
||||
|
||||
RuboCop::RakeTask.new if defined?(RuboCop)
|
||||
|
||||
task default: %i[rubocop]
|
@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
run ->(*) { [301, { 'Location' => ENV['CPI_FEED_URL'] }, []] }
|
@ -0,0 +1,94 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
require 'json'
|
||||
require 'net/http'
|
||||
require 'net/https'
|
||||
require 'uri'
|
||||
|
||||
class CPIFetcher
|
||||
def cpi
|
||||
resp = fetch_raw_response
|
||||
return nil unless resp['Results']['series']
|
||||
|
||||
resp['Results']['series'].first['data']
|
||||
end
|
||||
|
||||
def cpi_csv
|
||||
CSV.generate do |csv|
|
||||
csv << %w[year period period_name value]
|
||||
cpi.each do |rec|
|
||||
csv << %w[
|
||||
year period periodName value
|
||||
].map { |k| rec.fetch(k) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private def url
|
||||
@url ||= URI(ENV['CPI_SERIES_URL'] || File.join(
|
||||
'https://api.bls.gov',
|
||||
'publicAPI/v2/timeseries/data/'
|
||||
))
|
||||
end
|
||||
|
||||
private def series_id
|
||||
@series_id ||= ENV.fetch(
|
||||
'CPI_SERIES_ID', 'CUUR0200SA0'
|
||||
).split(/[, ]/).map(&:strip).reject(&:empty?)
|
||||
end
|
||||
|
||||
private def start_year
|
||||
@start_year ||= ENV.fetch('CPI_SERIES_START_YEAR', '2014')
|
||||
end
|
||||
|
||||
private def end_year
|
||||
@end_year ||= ENV.fetch('CPI_SERIES_END_YEAR', Time.now.year.to_s)
|
||||
end
|
||||
|
||||
private def bls_token
|
||||
@bls_token ||= ENV.fetch('BLS_TOKEN')
|
||||
end
|
||||
|
||||
private def start_cpi_value(data)
|
||||
data = data.sort do |a, b|
|
||||
record_key(a) <=> record_key(b)
|
||||
end
|
||||
data.first.fetch('value')
|
||||
end
|
||||
|
||||
private def record_key(record)
|
||||
"#{record['year']}.#{record['period']}"
|
||||
end
|
||||
|
||||
private def latest_cpi_value(data)
|
||||
data.find { |d| d['latest'] == 'true' }.fetch('value')
|
||||
end
|
||||
|
||||
private def fetch_raw_response
|
||||
Net::HTTP.new(url.hostname, url.port)
|
||||
.tap { |h| h.use_ssl = true }
|
||||
.tap { |h| h.verify_mode = OpenSSL::SSL::VERIFY_PEER }
|
||||
.then { |h| JSON.parse(h.request(build_request).body) }
|
||||
end
|
||||
|
||||
private def build_request
|
||||
Net::HTTP::Post.new(url)
|
||||
.tap { |r| r['Authorization'] = "token #{bls_token}" }
|
||||
.tap { |r| r['Content-Type'] = 'application/json' }
|
||||
.tap { |r| r.body = JSON.generate(build_request_body) }
|
||||
end
|
||||
|
||||
private def build_request_body
|
||||
{
|
||||
'seriesid' => series_id,
|
||||
'startyear' => start_year,
|
||||
'endyear' => end_year
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if $PROGRAM_NAME == __FILE__
|
||||
puts CPIFetcher.new.cpi_csv
|
||||
exit 0
|
||||
end
|
@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'aws-sdk'
|
||||
|
||||
class MiniS3put
|
||||
def initialize(key: nil, instream: $stdin)
|
||||
@bucket = ENV.fetch('CPI_FEED_AWS_BUCKET')
|
||||
@key = key || ENV.fetch('CPI_FEED_AWS_KEY')
|
||||
@instream = instream
|
||||
end
|
||||
|
||||
attr_reader :bucket, :key, :instream
|
||||
private :bucket, :key, :instream
|
||||
|
||||
def put
|
||||
Aws::S3::Resource.new.bucket(bucket).object(key).put(
|
||||
body: instream.read
|
||||
).on_success(&method(:on_put_success))
|
||||
end
|
||||
|
||||
private def on_put_success(response)
|
||||
puts response.data
|
||||
|
||||
Aws::S3::Client.new.put_object_acl(
|
||||
bucket: bucket, key: key, acl: 'public-read'
|
||||
).on_success do |acl_response|
|
||||
puts acl_response.data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if $PROGRAM_NAME == __FILE__
|
||||
MiniS3put.new(key: ARGV.first).put
|
||||
exit 0
|
||||
end
|
Loading…
Reference in new issue