Class: Edgar::EntityFacts

Inherits:
Object
  • Object
show all
Defined in:
lib/edgar/entity_facts.rb

Overview

XBRL financial facts (US-GAAP, IFRS, DEI) for a CIK entity.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cik:) ⇒ EntityFacts

Returns a new instance of EntityFacts.



8
9
10
11
# File 'lib/edgar/entity_facts.rb', line 8

def initialize(cik:)
  @cik = cik.to_i
  @raw = fetch_facts
end

Instance Attribute Details

#cikObject (readonly)

Returns the value of attribute cik.



6
7
8
# File 'lib/edgar/entity_facts.rb', line 6

def cik
  @cik
end

#rawObject (readonly)

Returns the value of attribute raw.



6
7
8
# File 'lib/edgar/entity_facts.rb', line 6

def raw
  @raw
end

Instance Method Details

#_select_value(concept_name, annual: true, period: nil) ⇒ Object

Select the best single value for a concept, matching Python edgartools' _get_standardized_concept_value semantics. When annual is true (default), prefers FY (fiscal-year) facts. When period is given (e.g. "2025-FY"), filters to that specific period. When multiple facts exist, selects by (filed desc, end desc) matching Python's max by (filing_date, period_end). Matches Python edgartools' fallback: if annual: true yields no results (e.g. balance sheet instant facts), falls back to the most recent fact.



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/edgar/entity_facts.rb', line 275

def _select_value(concept_name, annual: true, period: nil)
  c = concept(concept_name)
  return nil unless c

  values = c["units"]&.values&.flatten || []
  return nil if values.empty?

  if annual && period.nil?
    has_fp = values.first&.key?("fp")
    if has_fp
      fy_values = values.select { |v| v["fp"] == "FY" }
      values = fy_values unless fy_values.empty?
    end
  end

  if period
    fy_str, fp_str = period.split("-")
    values = values.select { |v| v["fy"].to_s == fy_str && v["fp"] == fp_str }
  end

  return nil if values.empty?

  # Select by (filed desc, end desc) matching Python's
  # max by (filing_date, period_end)
  best = values.max_by { |v| [v["filed"] || v["end"] || "", v["end"] || ""] }

  best["val"]
end

#available?Boolean

Returns:

  • (Boolean)


13
14
15
# File 'lib/edgar/entity_facts.rb', line 13

def available?
  @raw.key?("facts")
end

#balance_sheet(periods: 4) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/edgar/entity_facts.rb', line 72

def balance_sheet(periods: 4)
  concepts = %w[Assets CurrentAssets CashAndCashEquivalentsAtCarryingValue
                AccountsReceivableNetCurrent
                PropertyPlantAndEquipmentNet
                AssetsCurrent LiabilitiesCurrent
                LongTermDebtCurrent Liabilities
                StockholdersEquity]
  extract_statement(concepts, periods)
end

#cashflow_statement(periods: 4) ⇒ Object



82
83
84
85
86
87
88
# File 'lib/edgar/entity_facts.rb', line 82

def cashflow_statement(periods: 4)
  concepts = %w[NetCashProvidedByUsedInOperatingActivities
                NetCashProvidedByUsedInInvestingActivities
                NetCashProvidedByUsedInFinancingActivities
                CashAndCashEquivalentsPeriodIncreaseDecrease]
  extract_statement(concepts, periods)
end

#concept(name) ⇒ Object



33
34
35
# File 'lib/edgar/entity_facts.rb', line 33

def concept(name)
  us_gaap[name] || ifrs_full[name] || dei[name]
end

#concept_values(name, limit: nil, annual: nil) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/edgar/entity_facts.rb', line 37

def concept_values(name, limit: nil, annual: nil)
  c = concept(name)
  return [] unless c

  units = c["units"]
  return [] unless units

  values = units.values.flatten

  if annual
    has_fp = values.first&.key?("fp")
    if has_fp
      fy_values = values.select { |v| v["fp"] == "FY" }
      values = fy_values unless fy_values.empty?
    end
  end

  # Sort by filed date desc, then end date desc (matching Python edgartools'
  # max by (filing_date, period_end) semantics)
  values = values.sort_by { |v| [v["filed"] || v["end"] || "", v["end"] || ""] }.reverse

  values = values.first(limit) if limit
  values
end

#conceptsObject



29
30
31
# File 'lib/edgar/entity_facts.rb', line 29

def concepts
  us_gaap.keys + ifrs_full.keys
end

#deiObject



25
26
27
# File 'lib/edgar/entity_facts.rb', line 25

def dei
  @raw.dig("facts", "dei") || {}
end

#extract_statement(concept_names, periods) ⇒ Object



304
305
306
307
308
309
310
311
# File 'lib/edgar/entity_facts.rb', line 304

def extract_statement(concept_names, periods)
  result = {}
  concept_names.each do |name|
    values = concept_values(name, limit: periods)
    result[name] = values if values.any?
  end
  result
end

#get_gross_profit(limit: nil, annual: true, period: nil) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/edgar/entity_facts.rb', line 203

def get_gross_profit(limit: nil, annual: true, period: nil)
  # Matches Python edgartools EntityFacts concept variants
  gross_profit_concepts = %w[GrossProfit GrossMargin]
  if limit
    gross_profit_concepts.each do |name|
      v = concept_values(name, limit: limit, annual: annual)
      return v unless v.empty?
    end
  else
    gross_profit_concepts.each do |name|
      val = _select_value(name, annual: annual, period: period)
      return val if val
    end
  end
  nil
end

#get_net_income(limit: nil, annual: true, period: nil) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/edgar/entity_facts.rb', line 114

def get_net_income(limit: nil, annual: true, period: nil)
  # Matches Python edgartools EntityFacts concept variants
  net_income_concepts = %w[NetIncomeLoss ProfitLoss NetIncome
                           NetEarnings NetIncomeLossAttributableToParent]
  if limit
    net_income_concepts.each do |name|
      v = concept_values(name, limit: limit, annual: annual)
      return v unless v.empty?
    end
  else
    net_income_concepts.each do |name|
      val = _select_value(name, annual: annual, period: period)
      return val if val
    end
  end
  nil
end

#get_operating_income(limit: nil, annual: true, period: nil) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/edgar/entity_facts.rb', line 132

def get_operating_income(limit: nil, annual: true, period: nil)
  # Matches Python edgartools EntityFacts concept variants
  op_income_concepts = %w[OperatingIncomeLoss OperatingIncome
                          IncomeLossFromOperations OperatingProfit]
  if limit
    op_income_concepts.each do |name|
      v = concept_values(name, limit: limit, annual: annual)
      return v unless v.empty?
    end
  else
    op_income_concepts.each do |name|
      val = _select_value(name, annual: annual, period: period)
      return val if val
    end
  end
  nil
end

#get_revenue(limit: nil, annual: true, period: nil) ⇒ Object

Standardized financial accessors When limit: is given, returns an array of raw fact hashes (legacy). Otherwise returns a single numeric value (matching Python edgartools).



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/edgar/entity_facts.rb', line 94

def get_revenue(limit: nil, annual: true, period: nil)
  # Matches Python edgartools EntityFacts concept variants
  revenue_concepts = %w[RevenueFromContractWithCustomerExcludingAssessedTax
                        SalesRevenueNet
                        Revenues Revenue
                        TotalRevenues NetSales]
  if limit
    revenue_concepts.each do |name|
      vals = concept_values(name, limit: limit, annual: annual)
      return vals unless vals.empty?
    end
  else
    revenue_concepts.each do |name|
      val = _select_value(name, annual: annual, period: period)
      return val if val
    end
  end
  nil
end

#get_shareholders_equity(limit: nil, annual: true, period: nil) ⇒ Object Also known as: get_stockholders_equity



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/edgar/entity_facts.rb', line 184

def get_shareholders_equity(limit: nil, annual: true, period: nil)
  # Matches Python edgartools EntityFacts concept variants
  equity_concepts = %w[StockholdersEquity ShareholdersEquity
                       TotalEquity PartnersCapital MembersEquity]
  if limit
    equity_concepts.each do |name|
      v = concept_values(name, limit: limit, annual: annual)
      return v unless v.empty?
    end
  else
    equity_concepts.each do |name|
      val = _select_value(name, annual: annual, period: period)
      return val if val
    end
  end
  nil
end

#get_total_assets(limit: nil, annual: true, period: nil) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/edgar/entity_facts.rb', line 150

def get_total_assets(limit: nil, annual: true, period: nil)
  # Matches Python edgartools EntityFacts concept variants
  asset_concepts = %w[Assets TotalAssets AssetsCurrent]
  if limit
    asset_concepts.each do |name|
      v = concept_values(name, limit: limit, annual: annual)
      return v unless v.empty?
    end
  else
    asset_concepts.each do |name|
      val = _select_value(name, annual: annual, period: period)
      return val if val
    end
  end
  nil
end

#get_total_liabilities(limit: nil, annual: true, period: nil) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/edgar/entity_facts.rb', line 167

def get_total_liabilities(limit: nil, annual: true, period: nil)
  # Matches Python edgartools EntityFacts concept variants
  liability_concepts = %w[Liabilities TotalLiabilities LiabilitiesAndStockholdersEquity]
  if limit
    liability_concepts.each do |name|
      v = concept_values(name, limit: limit, annual: annual)
      return v unless v.empty?
    end
  else
    liability_concepts.each do |name|
      val = _select_value(name, annual: annual, period: period)
      return val if val
    end
  end
  nil
end

#ifrs_fullObject



21
22
23
# File 'lib/edgar/entity_facts.rb', line 21

def ifrs_full
  @raw.dig("facts", "ifrs-full") || {}
end

#income_statement(periods: 4) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/edgar/entity_facts.rb', line 62

def income_statement(periods: 4)
  concepts = %w[Revenues Revenue CostOfGoodsAndServicesSold
                GrossProfit OperatingExpenses
                OperatingIncomeLoss
                IncomeLossFromContinuingOperationsBeforeIncomeTaxExpenseBenefit
                IncomeTaxExpenseBenefit
                NetIncomeLoss]
  extract_statement(concepts, periods)
end

#shares_outstandingObject Also known as: get_shares_outstanding

Shares outstanding — tries DEI entity-level concept first, then falls back to US-GAAP balance sheet concept. Matches Python edgartools EntityFacts#shares_outstanding.



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/edgar/entity_facts.rb', line 223

def shares_outstanding
  # Try DEI entity-level concept first (most authoritative)
  val = _select_value("EntityCommonStockSharesOutstanding", annual: false)
  return val if val

  # Fallback to US-GAAP balance sheet concept (covers multi-class cos like GOOG)
  val = _select_value("CommonStockSharesOutstanding", annual: false)
  return val if val

  # Another fallback
  _select_value("WeightedAverageNumberOfSharesOutstandingBasic", annual: false)
end

#ttm(concept_name) ⇒ Object

Trailing Twelve Months (TTM) calculations. Delegates to TTMCalculator which matches Python edgartools' TTMCalculator:

1. Classify facts by period duration (quarter: 70-120d, YTD_6M: 140-229d,
 YTD_9M: 230-329d, annual: 330-420d)
2. Derive Q2=YTD_6M-Q1, Q3=YTD_9M-YTD_6M, Q4=FY-YTD_9M
3. Take 4 most recent quarters and sum them.


245
246
247
248
249
250
# File 'lib/edgar/entity_facts.rb', line 245

def ttm(concept_name)
  values = concept_values(concept_name, limit: nil, annual: false)
  return nil if values.empty?

  TTMCalculator.new(values).calculate
end

#ttm_net_incomeObject



259
260
261
# File 'lib/edgar/entity_facts.rb', line 259

def ttm_net_income
  ttm("NetIncomeLoss")
end

#ttm_operating_incomeObject



263
264
265
# File 'lib/edgar/entity_facts.rb', line 263

def ttm_operating_income
  ttm("OperatingIncomeLoss")
end

#ttm_revenueObject



252
253
254
255
256
257
# File 'lib/edgar/entity_facts.rb', line 252

def ttm_revenue
  result = ttm("RevenueFromContractWithCustomerExcludingAssessedTax")
  return result if result

  ttm("Revenues")
end

#us_gaapObject



17
18
19
# File 'lib/edgar/entity_facts.rb', line 17

def us_gaap
  @raw.dig("facts", "us-gaap") || {}
end