Class: BankOfThailand::Response

Inherits:
Object
  • Object
show all
Defined in:
lib/bank_of_thailand/response.rb

Overview

Wrapper for API responses with convenience methods

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(raw_response) ⇒ Response

Initialize a new Response

Parameters:

  • raw_response (Hash)

    the raw API response



17
18
19
20
# File 'lib/bank_of_thailand/response.rb', line 17

def initialize(raw_response)
  @raw = raw_response
  @data = extract_data(raw_response)
end

Instance Attribute Details

#dataArray<Hash> (readonly)

Returns the extracted data array.

Returns:

  • (Array<Hash>)

    the extracted data array



13
14
15
# File 'lib/bank_of_thailand/response.rb', line 13

def data
  @data
end

#rawHash (readonly)

Returns the raw API response.

Returns:

  • (Hash)

    the raw API response



10
11
12
# File 'lib/bank_of_thailand/response.rb', line 10

def raw
  @raw
end

Instance Method Details

#[](key) ⇒ Object

Allow hash-like access for backward compatibility

Parameters:

  • key (String, Symbol)

    the key to access

Returns:

  • (Object)

    the value at the key



25
26
27
28
29
# File 'lib/bank_of_thailand/response.rb', line 25

def [](key)
  return nil unless raw.is_a?(Hash)

  raw[key.to_s]
end

#average(column) ⇒ Float Also known as: mean

Average value for a column

Parameters:

  • column (String)

    the column name

Returns:

  • (Float)

    average value or 0 if no data



89
90
91
92
93
94
# File 'lib/bank_of_thailand/response.rb', line 89

def average(column)
  vals = values_for(column)
  return 0.0 if vals.empty?

  vals.sum / vals.size.to_f
end

#change(column = "value") ⇒ Hash?

Calculate change metrics for a column

Parameters:

  • column (String) (defaults to: "value")

    the column name (default: "value")

Returns:

  • (Hash, nil)

    hash with :absolute, :percentage, :first_value, :last_value



144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/bank_of_thailand/response.rb', line 144

def change(column = "value")
  vals = values_for(column)
  return nil if vals.size < 2

  first_val = vals.first
  last_val = vals.last

  {
    absolute: last_val - first_val,
    percentage: first_val.zero? ? 0.0 : ((last_val - first_val) / first_val * 100).round(4),
    first_value: first_val,
    last_value: last_val
  }
end

#complete?Boolean

Check if data is complete for the date range

Returns:

  • (Boolean)

    true if all expected days are present



120
121
122
123
124
125
# File 'lib/bank_of_thailand/response.rb', line 120

def complete?
  expected_days = period_days
  return true if expected_days.zero?

  count >= expected_days
end

#countInteger

Count of data points

Returns:

  • (Integer)

    number of data points



42
43
44
# File 'lib/bank_of_thailand/response.rb', line 42

def count
  data.size
end

#daily_changes(column = "value") ⇒ Array<Hash>

Calculate daily changes for a column

Parameters:

  • column (String) (defaults to: "value")

    the column name (default: "value")

Returns:

  • (Array<Hash>)

    array of change hashes with :absolute and :percentage



162
163
164
165
166
167
168
169
170
171
172
# File 'lib/bank_of_thailand/response.rb', line 162

def daily_changes(column = "value")
  vals = values_for(column)
  return [] if vals.size < 2

  vals.each_cons(2).map do |prev, curr|
    {
      absolute: curr - prev,
      percentage: prev.zero? ? 0.0 : ((curr - prev) / prev * 100).round(4)
    }
  end
end

#date_rangeArray<String>?

Date range covered by the data

Returns:

  • (Array<String>, nil)

    [start_date, end_date] or nil if no dates



100
101
102
103
104
105
# File 'lib/bank_of_thailand/response.rb', line 100

def date_range
  dates = data.select { |row| row.is_a?(Hash) }.map { |row| row["period"] || row["date"] }.compact
  return nil if dates.empty?

  dates.minmax
end

#dig(*keys) ⇒ Object

Allow hash-like dig for backward compatibility

Parameters:

  • keys (Array)

    the keys to dig through

Returns:

  • (Object)

    the value at the nested keys



34
35
36
37
38
# File 'lib/bank_of_thailand/response.rb', line 34

def dig(*keys)
  return nil unless raw.is_a?(Hash)

  raw.dig(*keys.map(&:to_s))
end

#firstHash?

First data point

Returns:

  • (Hash, nil)

    first data point or nil if empty



48
49
50
# File 'lib/bank_of_thailand/response.rb', line 48

def first
  data.first
end

#lastHash?

Last data point

Returns:

  • (Hash, nil)

    last data point or nil if empty



54
55
56
# File 'lib/bank_of_thailand/response.rb', line 54

def last
  data.last
end

#max(column) ⇒ Float?

Maximum value for a column

Parameters:

  • column (String)

    the column name

Returns:

  • (Float, nil)

    maximum value or nil if no data



75
76
77
# File 'lib/bank_of_thailand/response.rb', line 75

def max(column)
  values_for(column).max
end

#min(column) ⇒ Float?

Minimum value for a column

Parameters:

  • column (String)

    the column name

Returns:

  • (Float, nil)

    minimum value or nil if no data



68
69
70
# File 'lib/bank_of_thailand/response.rb', line 68

def min(column)
  values_for(column).min
end

#missing_datesArray<Date>

Find missing dates in the range

Returns:

  • (Array<Date>)

    array of missing dates



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/bank_of_thailand/response.rb', line 129

def missing_dates
  return [] unless date_range

  start_date = Date.parse(date_range[0])
  end_date = Date.parse(date_range[1])
  actual_dates = data.select { |row| row.is_a?(Hash) }.map { |row| Date.parse(row["period"] || row["date"]) }

  (start_date..end_date).reject { |date| actual_dates.include?(date) }
rescue Date::Error
  []
end

#period_daysInteger

Number of days in the period

Returns:

  • (Integer)

    number of days



109
110
111
112
113
114
115
116
# File 'lib/bank_of_thailand/response.rb', line 109

def period_days
  range = date_range
  return 0 unless range

  (Date.parse(range[1]) - Date.parse(range[0])).to_i + 1
rescue Date::Error
  0
end

#sum(column) ⇒ Float

Sum of values for a column

Parameters:

  • column (String)

    the column name

Returns:

  • (Float)

    sum of values



82
83
84
# File 'lib/bank_of_thailand/response.rb', line 82

def sum(column)
  values_for(column).sum
end

#to_csv(filename = nil) ⇒ String

Export data to CSV

Parameters:

  • filename (String, nil) (defaults to: nil)

    optional filename to save CSV

Returns:

  • (String)

    CSV string or filename if saved



207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/bank_of_thailand/response.rb', line 207

def to_csv(filename = nil)
  csv_data = CSV.generate do |csv|
    csv << extract_headers
    extract_rows.each { |row| csv << row }
  end

  if filename
    File.write(filename, csv_data)
    filename
  else
    csv_data
  end
end

#trend(column = "value") ⇒ Symbol

Determine trend direction

Parameters:

  • column (String) (defaults to: "value")

    the column name (default: "value")

Returns:

  • (Symbol)

    :up, :down, or :flat



189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/bank_of_thailand/response.rb', line 189

def trend(column = "value")
  change_data = change(column)
  return :flat unless change_data

  pct = change_data[:percentage]

  if pct > 1
    :up
  elsif pct < -1
    :down
  else
    :flat
  end
end

#values_for(column) ⇒ Array<Float>

Extract numeric values for a column

Parameters:

  • column (String)

    the column name

Returns:

  • (Array<Float>)

    array of numeric values



61
62
63
# File 'lib/bank_of_thailand/response.rb', line 61

def values_for(column)
  data.select { |row| row.is_a?(Hash) }.map { |row| row[column]&.to_f }.compact
end

#volatility(column = "value") ⇒ Float

Calculate volatility (standard deviation of daily percentage changes)

Parameters:

  • column (String) (defaults to: "value")

    the column name (default: "value")

Returns:

  • (Float)

    volatility as standard deviation



177
178
179
180
181
182
183
184
# File 'lib/bank_of_thailand/response.rb', line 177

def volatility(column = "value")
  changes = daily_changes(column).map { |c| c[:percentage] }
  return 0.0 if changes.empty?

  mean = changes.sum / changes.size.to_f
  variance = changes.sum { |x| (x - mean)**2 } / changes.size.to_f
  Math.sqrt(variance).round(4)
end