Class: Edgar::FilingFacts

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

Overview

Financial facts extracted from a filing's XBRL instance document, providing the same duck-typed interface as EntityFacts but scoped to the periods relevant to the filing (annual for 10-K, quarterly for 10-Q).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(xbrl, form_type: nil) ⇒ FilingFacts

Returns a new instance of FilingFacts.



10
11
12
13
14
15
# File 'lib/edgar/filing_facts.rb', line 10

def initialize(xbrl, form_type: nil)
  @xbrl = xbrl
  @form_type = form_type
  @contexts = build_context_index
  @facts_map = build_facts_map
end

Instance Attribute Details

#contextsObject (readonly)

Returns the value of attribute contexts.



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

def contexts
  @contexts
end

#facts_mapObject (readonly)

Returns the value of attribute facts_map.



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

def facts_map
  @facts_map
end

#form_typeObject (readonly)

Returns the value of attribute form_type.



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

def form_type
  @form_type
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 (end desc) matching Python's max by (period_end). FilingFacts doesn't have filed dates, so falls back to most recent end date.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/edgar/filing_facts.rb', line 206

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 end date desc (FilingFacts lacks filed dates, so use end date)
  best = values.max_by { |v| v["end"] || v["start"] || "" }

  best["val"]
end

#available?Boolean

Returns:

  • (Boolean)


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

def available?
  @facts_map.any?
end

#concept(name) ⇒ Object



21
22
23
24
25
26
# File 'lib/edgar/filing_facts.rb', line 21

def concept(name)
  values = @facts_map[name]
  return nil unless values&.any?

  { "label" => name, "units" => { "USD" => values } }
end

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



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/edgar/filing_facts.rb', line 28

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["end"] || "", v["end"] || ""] }.reverse

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

#conceptsObject



53
54
55
# File 'lib/edgar/filing_facts.rb', line 53

def concepts
  @facts_map.keys
end

#deiObject



65
66
67
# File 'lib/edgar/filing_facts.rb', line 65

def dei
  {}
end

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



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/edgar/filing_facts.rb', line 182

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



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

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



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/edgar/filing_facts.rb', line 111

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).



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/edgar/filing_facts.rb', line 73

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/edgar/filing_facts.rb', line 163

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



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/edgar/filing_facts.rb', line 129

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



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/edgar/filing_facts.rb', line 146

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



61
62
63
# File 'lib/edgar/filing_facts.rb', line 61

def ifrs_full
  {}
end

#us_gaapObject



57
58
59
# File 'lib/edgar/filing_facts.rb', line 57

def us_gaap
  @facts_map
end