fin_it 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/ARCHITECTURE.md +24 -0
  3. data/CHANGELOG.md +9 -0
  4. data/CONTRIBUTING.md +20 -0
  5. data/LICENSE +21 -0
  6. data/QUICKSTART.md +56 -0
  7. data/README.md +74 -0
  8. data/Rakefile +23 -0
  9. data/SECURITY.md +14 -0
  10. data/assets/fin_it_logo.png +0 -0
  11. data/lib/fin_it/account.rb +120 -0
  12. data/lib/fin_it/calculator/currency_conversion.rb +27 -0
  13. data/lib/fin_it/calculator/date_helpers.rb +53 -0
  14. data/lib/fin_it/calculator/variable_hashing.rb +120 -0
  15. data/lib/fin_it/calculator.rb +480 -0
  16. data/lib/fin_it/categories/category.rb +137 -0
  17. data/lib/fin_it/complex_model.rb +169 -0
  18. data/lib/fin_it/dsl/account_builder.rb +35 -0
  19. data/lib/fin_it/dsl/calculated_builder.rb +87 -0
  20. data/lib/fin_it/dsl/config_builder.rb +58 -0
  21. data/lib/fin_it/dsl/model_builder.rb +938 -0
  22. data/lib/fin_it/dsl/model_template_builder.rb +29 -0
  23. data/lib/fin_it/dsl/plan_builder.rb +52 -0
  24. data/lib/fin_it/dsl/project_inheritance_resolver.rb +46 -0
  25. data/lib/fin_it/dsl/variable_builder.rb +41 -0
  26. data/lib/fin_it/dsl.rb +13 -0
  27. data/lib/fin_it/engine.rb +15 -0
  28. data/lib/fin_it/financial_model/account_balances.rb +99 -0
  29. data/lib/fin_it/financial_model/account_hierarchy.rb +158 -0
  30. data/lib/fin_it/financial_model/category_values.rb +179 -0
  31. data/lib/fin_it/financial_model/currency_helpers.rb +14 -0
  32. data/lib/fin_it/financial_model/date_helpers.rb +58 -0
  33. data/lib/fin_it/financial_model/debugging.rb +353 -0
  34. data/lib/fin_it/financial_model/period_flows.rb +121 -0
  35. data/lib/fin_it/financial_model/validation.rb +85 -0
  36. data/lib/fin_it/financial_model/variable_matching.rb +49 -0
  37. data/lib/fin_it/financial_model.rb +395 -0
  38. data/lib/fin_it/model_template.rb +121 -0
  39. data/lib/fin_it/outputs/base_output.rb +51 -0
  40. data/lib/fin_it/outputs/console_output.rb +1528 -0
  41. data/lib/fin_it/outputs/monthly_console_output.rb +145 -0
  42. data/lib/fin_it/outputs/zaxcel_output.rb +1264 -0
  43. data/lib/fin_it/payment_schedule.rb +112 -0
  44. data/lib/fin_it/plan.rb +159 -0
  45. data/lib/fin_it/reports/balance_sheet.rb +638 -0
  46. data/lib/fin_it/reports/base_report.rb +239 -0
  47. data/lib/fin_it/reports/cash_flow_statement.rb +480 -0
  48. data/lib/fin_it/reports/custom_sheet.rb +436 -0
  49. data/lib/fin_it/reports/income_statement.rb +793 -0
  50. data/lib/fin_it/reports/period_comparison.rb +309 -0
  51. data/lib/fin_it/reports/scenario_comparison.rb +296 -0
  52. data/lib/fin_it/temporal_value.rb +349 -0
  53. data/lib/fin_it/transaction_generator/account_resolver.rb +118 -0
  54. data/lib/fin_it/transaction_generator/cache_management.rb +39 -0
  55. data/lib/fin_it/transaction_generator/date_generation.rb +57 -0
  56. data/lib/fin_it/transaction_generator.rb +357 -0
  57. data/lib/fin_it/version.rb +6 -0
  58. data/lib/fin_it.rb +27 -0
  59. data/test/fin_it/calculator_test.rb +109 -0
  60. data/test/fin_it/complex_model_test.rb +198 -0
  61. data/test/fin_it/debugging_test.rb +112 -0
  62. data/test/fin_it/driver_variables_test.rb +109 -0
  63. data/test/fin_it/dsl_test.rb +581 -0
  64. data/test/fin_it/financial_model_test.rb +196 -0
  65. data/test/fin_it/frequency_test.rb +51 -0
  66. data/test/fin_it/outputs/console_output_test.rb +249 -0
  67. data/test/fin_it/plan_test.rb +281 -0
  68. data/test/fin_it/reports/account_balance_test.rb +232 -0
  69. data/test/fin_it/reports/balance_sheet_test.rb +355 -0
  70. data/test/fin_it/reports/cash_flow_statement_test.rb +234 -0
  71. data/test/fin_it/reports/custom_sheet_test.rb +246 -0
  72. data/test/fin_it/reports/income_statement_test.rb +431 -0
  73. data/test/fin_it/reports/period_comparison_test.rb +226 -0
  74. data/test/fin_it/reports/restaurant_model_test.rb +225 -0
  75. data/test/fin_it/reports/scenario_comparison_test.rb +414 -0
  76. data/test/scripts/generate_demo_reports.rb +47 -0
  77. data/test/scripts/startup_saas_demo.rb +62 -0
  78. data/test/test_helper.rb +25 -0
  79. data/test/verify_accounting_equation.rb +91 -0
  80. metadata +264 -0
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../test_helper"
4
+
5
+ class PlanTest < Minitest::Test
6
+ def setup
7
+ @model = FinIt.define(default_currency: 'USD') do
8
+ config do
9
+ start_date 2024
10
+ end
11
+
12
+ account :checking do
13
+ type :asset
14
+ currency 'USD'
15
+ opening_balance 10_000
16
+ end
17
+
18
+ # Driver variables
19
+ category :drivers, type: :driver do
20
+ variable :units_sold do
21
+ value 100
22
+ end
23
+
24
+ variable :price_per_unit do
25
+ value 50
26
+ end
27
+ end
28
+
29
+ # Income categories
30
+ category :income, type: :income,
31
+ default_account: :checking,
32
+ defaults: { frequency: :monthly, start_date: "2024-01-01", end_date: "2024-12-31" } do
33
+
34
+ calculated :product_sales,
35
+ formula: "units_sold * price_per_unit"
36
+
37
+ variable :service_revenue, currency: 'USD' do
38
+ value 2_000, start_date: "2024-01-01", end_date: "2024-12-31"
39
+ end
40
+ end
41
+
42
+ # Expense categories
43
+ category :expenses, type: :expense,
44
+ default_account: :checking,
45
+ defaults: { frequency: :monthly, start_date: "2024-01-01", end_date: "2024-12-31" } do
46
+
47
+ variable :operating_expenses, currency: 'USD' do
48
+ value 1_500, start_date: "2024-01-01", end_date: "2024-12-31"
49
+ end
50
+ end
51
+
52
+ # Define plans
53
+ plan :growth_scenario, description: "10% revenue growth" do
54
+ scale :service_revenue, 1.10
55
+ end
56
+
57
+ plan :cost_reduction, description: "Reduce costs by 20%" do
58
+ scale :operating_expenses, 0.80
59
+ end
60
+
61
+ plan :opening_balance_change do
62
+ set_opening_balance :checking, 50_000
63
+ end
64
+ end
65
+
66
+ @test_date = Date.new(2024, 6, 15)
67
+ end
68
+
69
+ def test_plan_class_creation
70
+ plan = FinIt::Plan.new(:test_plan, description: "Test")
71
+ assert_equal :test_plan, plan.name
72
+ assert_equal "Test", plan.description
73
+ assert_empty plan.overrides
74
+ end
75
+
76
+ def test_plan_set_override
77
+ plan = FinIt::Plan.new(:test)
78
+ plan.set(:revenue, 5000)
79
+
80
+ assert_equal 1, plan.overrides.length
81
+ assert_equal :set, plan.overrides.first[:type]
82
+ assert_equal :revenue, plan.overrides.first[:variable]
83
+ assert_equal 5000, plan.overrides.first[:value]
84
+ end
85
+
86
+ def test_plan_scale_override
87
+ plan = FinIt::Plan.new(:test)
88
+ plan.scale(:revenue, 1.15)
89
+
90
+ assert_equal 1, plan.overrides.length
91
+ assert_equal :scale, plan.overrides.first[:type]
92
+ assert_equal :revenue, plan.overrides.first[:variable]
93
+ assert_equal 1.15, plan.overrides.first[:factor]
94
+ end
95
+
96
+ def test_plan_adjust_override
97
+ plan = FinIt::Plan.new(:test)
98
+ plan.adjust(:revenue, 10000)
99
+
100
+ assert_equal 1, plan.overrides.length
101
+ assert_equal :adjust, plan.overrides.first[:type]
102
+ assert_equal :revenue, plan.overrides.first[:variable]
103
+ assert_equal 10000, plan.overrides.first[:amount]
104
+ end
105
+
106
+ def test_plan_formula_override
107
+ plan = FinIt::Plan.new(:test)
108
+ plan.formula(:calculated_var, "new_formula * 2")
109
+
110
+ assert_equal 1, plan.overrides.length
111
+ assert_equal :formula, plan.overrides.first[:type]
112
+ assert_equal :calculated_var, plan.overrides.first[:variable]
113
+ assert_equal "new_formula * 2", plan.overrides.first[:formula]
114
+ end
115
+
116
+ def test_plan_set_opening_balance
117
+ plan = FinIt::Plan.new(:test)
118
+ plan.set_opening_balance(:checking, 50_000)
119
+
120
+ assert_equal 1, plan.overrides.length
121
+ assert_equal :opening_balance, plan.overrides.first[:type]
122
+ assert_equal :checking, plan.overrides.first[:account]
123
+ assert_equal 50_000, plan.overrides.first[:amount]
124
+ end
125
+
126
+ def test_plan_chaining
127
+ plan = FinIt::Plan.new(:test)
128
+ result = plan
129
+ .scale(:revenue, 1.10)
130
+ .adjust(:expenses, -1000)
131
+ .set(:bonus, 5000)
132
+
133
+ assert_equal plan, result
134
+ assert_equal 3, plan.overrides.length
135
+ end
136
+
137
+ def test_model_has_plans
138
+ assert @model.plans.key?(:growth_scenario)
139
+ assert @model.plans.key?(:cost_reduction)
140
+ assert @model.plans.key?(:opening_balance_change)
141
+ end
142
+
143
+ def test_model_clone_creates_independent_copy
144
+ @model.generate_transactions(Date.new(2024, 12, 31))
145
+
146
+ cloned = @model.clone_model
147
+
148
+ # Cloned model should be a different object
149
+ refute_same @model, cloned
150
+ refute_same @model.calculator, cloned.calculator
151
+ refute_same @model.accounts, cloned.accounts
152
+ refute_same @model.categories, cloned.categories
153
+
154
+ # But values should be the same
155
+ assert_equal @model.accounts[:checking].opening_balance, cloned.accounts[:checking].opening_balance
156
+ end
157
+
158
+ def test_with_plan_returns_new_model
159
+ original_net_income = @model.period_net_income(
160
+ Date.new(2024, 1, 1), Date.new(2024, 12, 31)
161
+ )
162
+
163
+ growth_model = @model.with_plan(:growth_scenario)
164
+
165
+ # Original model should be unchanged
166
+ assert_equal original_net_income, @model.period_net_income(
167
+ Date.new(2024, 1, 1), Date.new(2024, 12, 31)
168
+ )
169
+
170
+ # Growth model should be different
171
+ refute_same @model, growth_model
172
+ end
173
+
174
+ def test_with_plan_applies_scale_override
175
+ @model.generate_transactions(Date.new(2024, 12, 31))
176
+
177
+ # Service revenue is 2000 monthly = 24000 annual
178
+ base_revenue = @model.calculator.calculate(
179
+ :service_revenue,
180
+ date: Date.new(2024, 6, 15),
181
+ output_currency: 'USD'
182
+ )
183
+
184
+ growth_model = @model.with_plan(:growth_scenario)
185
+ growth_model.generate_transactions(Date.new(2024, 12, 31))
186
+
187
+ scaled_revenue = growth_model.calculator.calculate(
188
+ :service_revenue,
189
+ date: Date.new(2024, 6, 15),
190
+ output_currency: 'USD'
191
+ )
192
+
193
+ # Should be 10% higher
194
+ expected = base_revenue.to_f * 1.10
195
+ assert_in_delta expected, scaled_revenue.to_f, 1.0
196
+ end
197
+
198
+ def test_with_plan_applies_opening_balance_override
199
+ assert_equal 10_000, @model.accounts[:checking].opening_balance
200
+
201
+ modified_model = @model.with_plan(:opening_balance_change)
202
+
203
+ # Original unchanged
204
+ assert_equal 10_000, @model.accounts[:checking].opening_balance
205
+
206
+ # Modified model has new opening balance
207
+ assert_equal 50_000, modified_model.accounts[:checking].opening_balance
208
+ end
209
+
210
+ def test_with_plans_applies_multiple
211
+ # Apply both growth and cost reduction
212
+ combined_model = @model.with_plans(:growth_scenario, :cost_reduction)
213
+
214
+ refute_same @model, combined_model
215
+
216
+ # Verify both plans were applied
217
+ combined_model.generate_transactions(Date.new(2024, 12, 31))
218
+
219
+ # Check that service_revenue was scaled up
220
+ scaled_revenue = combined_model.calculator.calculate(
221
+ :service_revenue,
222
+ date: Date.new(2024, 6, 15),
223
+ output_currency: 'USD'
224
+ )
225
+ base_revenue = @model.calculator.calculate(
226
+ :service_revenue,
227
+ date: Date.new(2024, 6, 15),
228
+ output_currency: 'USD'
229
+ )
230
+ expected_revenue = base_revenue.to_f * 1.10
231
+ assert_in_delta expected_revenue, scaled_revenue.to_f, 1.0
232
+ end
233
+
234
+ def test_with_external_plan
235
+ # Create plan outside of model definition
236
+ external_plan = FinIt::Plan.new(:external, description: "External plan")
237
+ external_plan.set_opening_balance(:checking, 100_000)
238
+
239
+ modified_model = @model.with_plan(external_plan)
240
+
241
+ assert_equal 10_000, @model.accounts[:checking].opening_balance
242
+ assert_equal 100_000, modified_model.accounts[:checking].opening_balance
243
+ end
244
+
245
+ def test_plan_with_temporal_scope
246
+ plan = FinIt::Plan.new(:q1_only, start_date: "2024-01-01", end_date: "2024-03-31")
247
+ plan.scale(:service_revenue, 2.0)
248
+
249
+ assert_equal Date.new(2024, 1, 1), plan.start_date
250
+ assert_equal Date.new(2024, 3, 31), plan.end_date
251
+ end
252
+
253
+ def test_model_isolation_after_multiple_operations
254
+ @model.generate_transactions(Date.new(2024, 12, 31))
255
+ base_income = @model.period_net_income(Date.new(2024, 1, 1), Date.new(2024, 12, 31))
256
+
257
+ # Create scenario A
258
+ scenario_a = @model.with_plan(:growth_scenario)
259
+ scenario_a.generate_transactions(Date.new(2024, 12, 31))
260
+
261
+ # Create scenario B
262
+ scenario_b = @model.with_plan(:cost_reduction)
263
+ scenario_b.generate_transactions(Date.new(2024, 12, 31))
264
+
265
+ # Create scenario C (both plans)
266
+ scenario_c = @model.with_plans(:growth_scenario, :cost_reduction)
267
+ scenario_c.generate_transactions(Date.new(2024, 12, 31))
268
+
269
+ # Base model should still have original value
270
+ @model.generate_transactions(Date.new(2024, 12, 31))
271
+ final_base_income = @model.period_net_income(Date.new(2024, 1, 1), Date.new(2024, 12, 31))
272
+
273
+ assert_equal base_income, final_base_income, "Base model should be unchanged after creating scenarios"
274
+ end
275
+
276
+ def test_plan_not_found_raises_error
277
+ assert_raises(ArgumentError) do
278
+ @model.with_plan(:nonexistent_plan)
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../test_helper"
4
+
5
+ class AccountBalanceTest < Minitest::Test
6
+ def setup
7
+ @model = FinIt.define(default_currency: 'USD') do
8
+ config do
9
+ start_date 2024
10
+ end
11
+
12
+ account :checking do
13
+ type :asset
14
+ currency 'USD'
15
+ opening_balance 10_000
16
+ end
17
+
18
+ account :credit_card do
19
+ type :liability
20
+ currency 'USD'
21
+ opening_balance 0
22
+ end
23
+
24
+ # Income that goes to checking account
25
+ category :income, type: :income do
26
+ variable :salary, currency: 'USD', frequency: :annual, account: :checking do
27
+ value 100_000, start_date: "2024-01-01", end_date: "2024-12-31"
28
+ description "Annual salary"
29
+ end
30
+
31
+ variable :bonus, currency: 'USD', frequency: :annual, account: :checking do
32
+ value 20_000, start_date: "2024-01-01", end_date: "2024-12-31"
33
+ description "Annual bonus"
34
+ end
35
+ end
36
+
37
+ # Expenses that come from checking or credit card
38
+ category :expenses, type: :expense do
39
+ variable :rent, currency: 'USD', frequency: :monthly, account: :checking do
40
+ value 2_000, start_date: "2024-01-01", end_date: "2024-12-31"
41
+ description "Monthly rent"
42
+ end
43
+
44
+ variable :utilities, currency: 'USD', frequency: :monthly, account: :checking do
45
+ value 300, start_date: "2024-01-01", end_date: "2024-12-31"
46
+ description "Monthly utilities"
47
+ end
48
+
49
+ variable :groceries, currency: 'USD', frequency: :monthly, account: :credit_card do
50
+ value 500, start_date: "2024-01-01", end_date: "2024-12-31"
51
+ description "Monthly groceries"
52
+ end
53
+ end
54
+
55
+ # Assets (checking account balance)
56
+ category :current_assets, type: :asset do
57
+ variable :checking_balance, currency: 'USD', frequency: :annual, account: :checking do
58
+ value 10_000, start_date: "2024-01-01", end_date: "2024-12-31"
59
+ description "Checking account balance"
60
+ end
61
+ end
62
+
63
+ # Liabilities (credit card balance)
64
+ category :current_liabilities, type: :liability do
65
+ variable :credit_card_balance, currency: 'USD', frequency: :annual, account: :credit_card do
66
+ value 0, start_date: "2024-01-01", end_date: "2024-12-31"
67
+ description "Credit card balance"
68
+ end
69
+ end
70
+
71
+ # Equity (to balance the sheet: Assets = Liabilities + Equity)
72
+ # Use calculated variable with account to get actual balance
73
+ category :equity, type: :equity do
74
+ calculated :owner_equity, formula: '0', account: :equity do
75
+ description "Owner's equity"
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def test_income_statement_shows_revenue
82
+ report = FinIt::Reports::IncomeStatement.new(
83
+ @model,
84
+ start_date: Date.new(2024, 1, 1),
85
+ end_date: Date.new(2024, 12, 31),
86
+ output_currency: 'USD'
87
+ )
88
+
89
+ report_data = report.generate
90
+ income_total = report_data[:totals][:income]
91
+
92
+ # Total income: 100,000 + 20,000 = 120,000
93
+ assert_in_delta 120_000, income_total, 0.01, "Total income should be $120,000"
94
+ end
95
+
96
+ def test_income_statement_shows_expenses
97
+ report = FinIt::Reports::IncomeStatement.new(
98
+ @model,
99
+ start_date: Date.new(2024, 1, 1),
100
+ end_date: Date.new(2024, 12, 31),
101
+ output_currency: 'USD'
102
+ )
103
+
104
+ report_data = report.generate
105
+ expenses_total = report_data[:totals][:expenses]
106
+
107
+ # Total expenses: (2,000 + 300) * 12 + (500 * 12) = 27,600 + 6,000 = 33,600
108
+ # Actually, these are monthly values that get annualized, so:
109
+ # rent: 2,000 * 12 = 24,000
110
+ # utilities: 300 * 12 = 3,600
111
+ # groceries: 500 * 12 = 6,000
112
+ # Total: 33,600
113
+ expected_expenses = (2_000 + 300 + 500) * 12
114
+ assert_in_delta expected_expenses, expenses_total, 1.0, "Total expenses should be correct"
115
+ end
116
+
117
+ def test_balance_sheet_shows_assets
118
+ # Generate transactions to get actual account balances
119
+ @model.generate_transactions(Date.new(2024, 12, 31))
120
+
121
+ report = FinIt::Reports::BalanceSheet.new(
122
+ @model,
123
+ start_date: Date.new(2024, 1, 1),
124
+ end_date: Date.new(2024, 12, 31),
125
+ output_currency: 'USD'
126
+ )
127
+
128
+ report_data = report.generate
129
+ assets_total = report_data[:totals][:assets]
130
+
131
+ # Assets: checking account balance at end of year
132
+ # Opening: 10,000 + Income: 120,000 - Expenses from checking: (2,000 + 300) * 12 = 27,600
133
+ # = 10,000 + 120,000 - 27,600 = 102,400
134
+ expected_assets = @model.account_balance(:checking, as_of_date: Date.new(2024, 12, 31))
135
+ assert_in_delta expected_assets, assets_total, 0.01, "Total assets should show checking balance"
136
+ end
137
+
138
+ def test_balance_sheet_shows_liabilities
139
+ # Generate transactions to get actual account balances
140
+ @model.generate_transactions(Date.new(2024, 12, 31))
141
+
142
+ report = FinIt::Reports::BalanceSheet.new(
143
+ @model,
144
+ start_date: Date.new(2024, 1, 1),
145
+ end_date: Date.new(2024, 12, 31),
146
+ output_currency: 'USD'
147
+ )
148
+
149
+ report_data = report.generate
150
+ liabilities_total = report_data[:totals][:liabilities]
151
+
152
+ # Liabilities: credit card balance at end of year
153
+ # Opening: 0 + Expenses on credit card: 500 * 12 = 6,000
154
+ expected_liabilities = @model.account_balance(:credit_card, as_of_date: Date.new(2024, 12, 31))
155
+ assert_in_delta expected_liabilities, liabilities_total, 0.01, "Total liabilities should show credit card balance"
156
+ end
157
+
158
+ def test_account_opening_balances
159
+ checking = @model.accounts[:checking]
160
+ credit_card = @model.accounts[:credit_card]
161
+
162
+ assert_equal 10_000, checking.opening_balance, "Checking should have $10,000 opening balance"
163
+ assert_equal 0, credit_card.opening_balance, "Credit card should have $0 opening balance"
164
+ end
165
+
166
+ def test_income_variables_have_accounts
167
+ income_category = @model.categories.find { |c| c.name == :income }
168
+ assert income_category, "Should have income category"
169
+
170
+ salary_var = income_category.variables.find { |v| v[:name] == :salary }
171
+ bonus_var = income_category.variables.find { |v| v[:name] == :bonus }
172
+
173
+ assert_equal :checking, salary_var[:account], "Salary should be associated with checking account"
174
+ assert_equal :checking, bonus_var[:account], "Bonus should be associated with checking account"
175
+ end
176
+
177
+ def test_expense_variables_have_accounts
178
+ expenses_category = @model.categories.find { |c| c.name == :expenses }
179
+ assert expenses_category, "Should have expenses category"
180
+
181
+ rent_var = expenses_category.variables.find { |v| v[:name] == :rent }
182
+ groceries_var = expenses_category.variables.find { |v| v[:name] == :groceries }
183
+
184
+ assert_equal :checking, rent_var[:account], "Rent should be associated with checking account"
185
+ assert_equal :credit_card, groceries_var[:account], "Groceries should be associated with credit card account"
186
+ end
187
+
188
+ def test_net_income_calculation
189
+ income_report = FinIt::Reports::IncomeStatement.new(
190
+ @model,
191
+ start_date: Date.new(2024, 1, 1),
192
+ end_date: Date.new(2024, 12, 31),
193
+ output_currency: 'USD'
194
+ )
195
+
196
+ income_data = income_report.generate
197
+ net_income = income_data[:totals][:net_income]
198
+
199
+ # Net income = income - expenses
200
+ # 120,000 - 33,600 = 86,400
201
+ assert net_income > 0, "Net income should be positive"
202
+ assert_in_delta 120_000 - 33_600, net_income, 1.0, "Net income should be income minus expenses"
203
+ end
204
+
205
+ def test_both_reports_use_same_model
206
+ # Verify both reports can be generated from the same model
207
+ income_report = FinIt::Reports::IncomeStatement.new(
208
+ @model,
209
+ start_date: Date.new(2024, 1, 1),
210
+ end_date: Date.new(2024, 12, 31),
211
+ output_currency: 'USD'
212
+ )
213
+
214
+ balance_sheet = FinIt::Reports::BalanceSheet.new(
215
+ @model,
216
+ start_date: Date.new(2024, 1, 1),
217
+ end_date: Date.new(2024, 12, 31),
218
+ output_currency: 'USD'
219
+ )
220
+
221
+ income_data = income_report.generate
222
+ balance_data = balance_sheet.generate
223
+
224
+ assert income_data, "Income statement should generate"
225
+ assert balance_data, "Balance sheet should generate"
226
+
227
+ # Both should have the same currency
228
+ assert_equal 'USD', income_data[:currency], "Income statement should be in USD"
229
+ assert_equal 'USD', balance_data[:currency], "Balance sheet should be in USD"
230
+ end
231
+ end
232
+