Compare commits
4 Commits
156ca54906
...
main
Author | SHA1 | Date | |
---|---|---|---|
cc8f4f0b48 | |||
9d7f4f5932 | |||
4f05c1cbae | |||
5aadce63fd |
14
.github/workflows/workflow.yml
vendored
14
.github/workflows/workflow.yml
vendored
@ -1,14 +0,0 @@
|
|||||||
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
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.*env
|
.*env
|
||||||
*.csv
|
*.csv
|
||||||
.ruby-version
|
.ruby-version
|
||||||
|
.envrc
|
||||||
|
13
.rubocop.yml
13
.rubocop.yml
@ -1,13 +0,0 @@
|
|||||||
inherit_from: .rubocop_todo.yml
|
|
||||||
|
|
||||||
AllCops:
|
|
||||||
TargetRubyVersion: 2.6
|
|
||||||
|
|
||||||
Style/Documentation:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
Style/AccessModifierDeclarations:
|
|
||||||
EnforcedStyle: inline
|
|
||||||
|
|
||||||
Layout/MultilineMethodCallIndentation:
|
|
||||||
EnforcedStyle: indented
|
|
@ -1,7 +0,0 @@
|
|||||||
# 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.
|
|
42
Code.gs
Normal file
42
Code.gs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
11
Gemfile
11
Gemfile
@ -1,11 +0,0 @@
|
|||||||
# 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]
|
|
49
Gemfile.lock
49
Gemfile.lock
@ -1,49 +0,0 @@
|
|||||||
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
|
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright © 2020 Dan Buch
|
Copyright © 2023 Dan Buch
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
15
README.md
15
README.md
@ -1,21 +1,20 @@
|
|||||||
# cpi-feed
|
# cpi-feed
|
||||||
|
|
||||||
Transforms [this](https://api.bls.gov/publicAPI/v2/timeseries/data/CUUSA210SA0)
|
Transforms [this](https://api.bls.gov/publicAPI/v2/timeseries/data/CUUSA210SA0)
|
||||||
into [this](https://s3.amazonaws.com/meatballhat/cpi/current.csv) :tada:.
|
into CSV rows.
|
||||||
|
|
||||||
## usage
|
## usage
|
||||||
|
|
||||||
This is intended for use with Google Sheets via:
|
This is intended for use with Google Sheets via:
|
||||||
|
|
||||||
```
|
```
|
||||||
=IMPORTDATA("https://s3.amazonaws.com/meatballhat/cpi/current.csv")
|
=CURRENTCPI("{bls-token}")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
where `{bls-token}` is the value issued after registering with
|
||||||
|
[bls.gov](https://www.bls.gov/developers/home.htm).
|
||||||
|
|
||||||
## deployment
|
## deployment
|
||||||
|
|
||||||
A copy of this thing is deployed to Heroku with a Heroku Scheduler addon
|
Just like [this
|
||||||
configured to run the following once daily:
|
example](https://apipheny.io/import-json-google-sheets/).
|
||||||
|
|
||||||
``` bash
|
|
||||||
bundle exec ./sync
|
|
||||||
```
|
|
||||||
|
11
Rakefile
11
Rakefile
@ -1,11 +0,0 @@
|
|||||||
# 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]
|
|
@ -1,3 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
run ->(*) { [301, { 'Location' => ENV['CPI_FEED_URL'] }, []] }
|
|
@ -1,94 +0,0 @@
|
|||||||
# 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
|
|
@ -1,35 +0,0 @@
|
|||||||
# 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…
x
Reference in New Issue
Block a user