inquirex-tty 0.4.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.
- checksums.yaml +7 -0
- data/.envrc +3 -0
- data/.relaxed_rubocop.yml +153 -0
- data/.secrets.baseline +127 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +530 -0
- data/Rakefile +36 -0
- data/docs/badges/coverage_badge.svg +21 -0
- data/examples/01_hello_world.rb +32 -0
- data/examples/02_yes_or_no.rb +33 -0
- data/examples/03_food_preferences.png +0 -0
- data/examples/03_food_preferences.rb +65 -0
- data/examples/04_event_registration.rb +86 -0
- data/examples/05_job_application.rb +122 -0
- data/examples/06_health_assessment.rb +173 -0
- data/examples/07_loan_application.rb +217 -0
- data/examples/08_tax_preparer.rb +195 -0
- data/examples/09_tax_preparer_llm.rb +151 -0
- data/examples/10_answers.json +53 -0
- data/examples/10_real_tax_preparer.rb +604 -0
- data/examples/README.md +50 -0
- data/exe/inquirex +24 -0
- data/justfile +56 -0
- data/lefthook.yml +36 -0
- data/lib/inquirex/tty/commands/export.rb +81 -0
- data/lib/inquirex/tty/commands/graph.rb +116 -0
- data/lib/inquirex/tty/commands/run.rb +193 -0
- data/lib/inquirex/tty/commands/validate.rb +108 -0
- data/lib/inquirex/tty/commands/version.rb +19 -0
- data/lib/inquirex/tty/commands.rb +19 -0
- data/lib/inquirex/tty/flow_loader.rb +41 -0
- data/lib/inquirex/tty/output_path.rb +66 -0
- data/lib/inquirex/tty/renderer.rb +245 -0
- data/lib/inquirex/tty/ui_helper.rb +58 -0
- data/lib/inquirex/tty/version.rb +7 -0
- data/lib/inquirex/tty.rb +41 -0
- data/sig/inquirex/tty.rbs +6 -0
- metadata +197 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Example 8: Tax Preparer Intake (Realistic, Privacy-Safe)
|
|
4
|
+
#
|
|
5
|
+
# Real-world intake wizard used by tax preparers to estimate how long a
|
|
6
|
+
# return will take and how much phone time the client will consume. Every
|
|
7
|
+
# question is scoped so the answer changes the fee quote.
|
|
8
|
+
#
|
|
9
|
+
# Privacy rule: this form lives on an unsecured embeddable widget, so we
|
|
10
|
+
# collect NO PII — no names, no SSN / ITIN, no date of birth, no addresses,
|
|
11
|
+
# no account numbers. The only contact field is an email so we can send
|
|
12
|
+
# the quote back. Everything else is bucketed multiple choice.
|
|
13
|
+
#
|
|
14
|
+
# The flow branches on:
|
|
15
|
+
#
|
|
16
|
+
# * residency status (non-resident / dual-status multiplies the fee),
|
|
17
|
+
# * business ownership (entity mix, employee / contractor bands, and
|
|
18
|
+
# which books — P&L, Balance Sheet, GL, Trial Balance — are ready),
|
|
19
|
+
# * investment, crypto, rental, and foreign-earned income blocks,
|
|
20
|
+
# * the "surprise pile" (IRS letters, state notices) that blows up
|
|
21
|
+
# timelines if not flagged early.
|
|
22
|
+
#
|
|
23
|
+
# Bucketed `enum` and `multi_enum` options are used everywhere so a
|
|
24
|
+
# downstream pricing pass can `accumulate` fees per selected option.
|
|
25
|
+
#
|
|
26
|
+
# Run: bundle exec exe/inquirex run examples/08_tax_preparer.rb
|
|
27
|
+
|
|
28
|
+
US_STATES = %w[
|
|
29
|
+
AL AK AZ AR CA CO CT DE FL GA
|
|
30
|
+
HI ID IL IN IA KS KY LA ME MD
|
|
31
|
+
MA MI MN MS MO MT NE NV NH NJ
|
|
32
|
+
NM NY NC ND OH OK OR PA RI SC
|
|
33
|
+
SD TN TX UT VT VA WA WV WI WY
|
|
34
|
+
DC
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
Inquirex.define id: "tax-preparer-2025", version: "2.0.0" do
|
|
38
|
+
meta title: "Tax Preparation Intake",
|
|
39
|
+
subtitle: "Help us scope your return and quote you a fair fee"
|
|
40
|
+
|
|
41
|
+
start :intro
|
|
42
|
+
|
|
43
|
+
# -----------------------------------------------------------------------
|
|
44
|
+
# Opening
|
|
45
|
+
# -----------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
say :intro do
|
|
48
|
+
text "Short multiple-choice scoping questions — 2 to 3 minutes.\n" \
|
|
49
|
+
"Please do NOT enter any personal information: no names,\n" \
|
|
50
|
+
"SSN, ITIN, addresses, or account numbers. We only need\n" \
|
|
51
|
+
"enough to quote a fee."
|
|
52
|
+
transition to: :residency_status
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# -----------------------------------------------------------------------
|
|
56
|
+
# Scope-driving baseline (residency + prior return)
|
|
57
|
+
# -----------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
ask :residency_status do
|
|
60
|
+
type :enum
|
|
61
|
+
question "Which best describes your US tax residency for 2025?"
|
|
62
|
+
options({
|
|
63
|
+
"us_person" => "US citizen or permanent resident",
|
|
64
|
+
"resident" => "Resident alien (substantial presence)",
|
|
65
|
+
"non_resident" => "Non-resident alien (1040-NR)",
|
|
66
|
+
"dual_status" => "Dual-status (arrived or departed this year)"
|
|
67
|
+
})
|
|
68
|
+
widget target: :tty, type: :select
|
|
69
|
+
widget target: :desktop, type: :radio_group, columns: 2
|
|
70
|
+
widget target: :mobile, type: :dropdown
|
|
71
|
+
transition to: :prior_return_available
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
ask :prior_return_available do
|
|
75
|
+
type :enum
|
|
76
|
+
question "Do you have a copy of your most recent tax return?"
|
|
77
|
+
options({
|
|
78
|
+
"yes_last_year" => "Yes, last year's return",
|
|
79
|
+
"yes_older" => "Yes, but older than last year",
|
|
80
|
+
"no" => "No",
|
|
81
|
+
"first_time" => "First return — never filed before"
|
|
82
|
+
})
|
|
83
|
+
widget target: :tty, type: :select
|
|
84
|
+
widget target: :desktop, type: :radio_group, columns: 2
|
|
85
|
+
transition to: :filing_status
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
ask :filing_status do
|
|
89
|
+
type :enum
|
|
90
|
+
question "What is your filing status for 2025?"
|
|
91
|
+
options({
|
|
92
|
+
"single" => "Single",
|
|
93
|
+
"married_filing_jointly" => "Married Filing Jointly",
|
|
94
|
+
"married_filing_separately" => "Married Filing Separately",
|
|
95
|
+
"head_of_household" => "Head of Household",
|
|
96
|
+
"widowed" => "Qualifying Widow(er)"
|
|
97
|
+
})
|
|
98
|
+
widget target: :tty, type: :select
|
|
99
|
+
widget target: :desktop, type: :radio_group, columns: 1
|
|
100
|
+
widget target: :mobile, type: :dropdown
|
|
101
|
+
transition to: :dependents_band
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
ask :dependents_band do
|
|
105
|
+
type :enum
|
|
106
|
+
question "How many dependents will you claim?"
|
|
107
|
+
options %w[0 1 2 3 4+]
|
|
108
|
+
widget target: :tty, type: :select
|
|
109
|
+
widget target: :desktop, type: :radio_group, columns: 5
|
|
110
|
+
widget target: :mobile, type: :dropdown
|
|
111
|
+
transition to: :income_types
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# -----------------------------------------------------------------------
|
|
115
|
+
# Income sources
|
|
116
|
+
# -----------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
ask :income_types do
|
|
119
|
+
type :multi_enum
|
|
120
|
+
question "Select every type of income you had in 2025."
|
|
121
|
+
options({
|
|
122
|
+
"W2" => "W-2 wages",
|
|
123
|
+
"1099_nec" => "1099-NEC (contractor)",
|
|
124
|
+
"1099_k" => "1099-K (payment apps, marketplaces)",
|
|
125
|
+
"business" => "Business income (self-employed or entity)",
|
|
126
|
+
"investment" => "Investment income (brokerage, dividends)",
|
|
127
|
+
"crypto" => "Cryptocurrency",
|
|
128
|
+
"rental" => "Rental property",
|
|
129
|
+
"retirement" => "Retirement distributions (1099-R)",
|
|
130
|
+
"social_sec" => "Social Security",
|
|
131
|
+
"gambling" => "Gambling / lottery winnings",
|
|
132
|
+
"foreign" => "Foreign earned income",
|
|
133
|
+
"home_sale" => "Sold a home or primary residence",
|
|
134
|
+
"inheritance" => "Inheritance or large gift received",
|
|
135
|
+
"none" => "None of the above"
|
|
136
|
+
})
|
|
137
|
+
widget target: :tty, type: :multi_select
|
|
138
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
139
|
+
transition to: :w2_count, if_rule: contains(:income_types, "W2")
|
|
140
|
+
transition to: :business_entities, if_rule: contains(:income_types, "business")
|
|
141
|
+
transition to: :investment_details, if_rule: contains(:income_types, "investment")
|
|
142
|
+
transition to: :crypto_volume, if_rule: contains(:income_types, "crypto")
|
|
143
|
+
transition to: :rental_count, if_rule: contains(:income_types, "rental")
|
|
144
|
+
transition to: :foreign_earned, if_rule: contains(:income_types, "foreign")
|
|
145
|
+
transition to: :state_filing
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
ask :w2_count do
|
|
149
|
+
type :enum
|
|
150
|
+
question "How many W-2s will you report?"
|
|
151
|
+
options %w[1 2 3 4 5+]
|
|
152
|
+
widget target: :tty, type: :select
|
|
153
|
+
widget target: :desktop, type: :radio_group, columns: 5
|
|
154
|
+
transition to: :business_entities, if_rule: contains(:income_types, "business")
|
|
155
|
+
transition to: :investment_details, if_rule: contains(:income_types, "investment")
|
|
156
|
+
transition to: :crypto_volume, if_rule: contains(:income_types, "crypto")
|
|
157
|
+
transition to: :rental_count, if_rule: contains(:income_types, "rental")
|
|
158
|
+
transition to: :foreign_earned, if_rule: contains(:income_types, "foreign")
|
|
159
|
+
transition to: :state_filing
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# -----------------------------------------------------------------------
|
|
163
|
+
# Business block
|
|
164
|
+
# -----------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
ask :business_entities do
|
|
167
|
+
type :multi_enum
|
|
168
|
+
question "Which business entity types do you own or partner in?"
|
|
169
|
+
options({
|
|
170
|
+
"sole_prop" => "Sole Proprietor / Schedule C",
|
|
171
|
+
"single_llc" => "Single-member LLC",
|
|
172
|
+
"multi_llc" => "Multi-member LLC / Partnership",
|
|
173
|
+
"s_corp" => "S-Corporation",
|
|
174
|
+
"c_corp" => "C-Corporation",
|
|
175
|
+
"partnership" => "Partnership (non-LLC)",
|
|
176
|
+
"trust" => "Trust / Estate",
|
|
177
|
+
"nonprofit" => "Nonprofit / 501(c)"
|
|
178
|
+
})
|
|
179
|
+
widget target: :tty, type: :multi_select
|
|
180
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
181
|
+
transition to: :business_count_band
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
ask :business_count_band do
|
|
185
|
+
type :enum
|
|
186
|
+
question "Across all entities, how many separate business returns?"
|
|
187
|
+
options %w[1 2-3 4-5 6+]
|
|
188
|
+
widget target: :tty, type: :select
|
|
189
|
+
widget target: :desktop, type: :radio_group, columns: 4
|
|
190
|
+
transition to: :books_ready
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
ask :books_ready do
|
|
194
|
+
type :multi_enum
|
|
195
|
+
question "Which of these are already prepared for the full year?"
|
|
196
|
+
options({
|
|
197
|
+
"pnl" => "Profit & Loss (Income Statement)",
|
|
198
|
+
"balance" => "Balance Sheet (beginning and end of year)",
|
|
199
|
+
"gl" => "General Ledger",
|
|
200
|
+
"trial" => "Trial Balance",
|
|
201
|
+
"bank_recs" => "Bank reconciliations",
|
|
202
|
+
"none" => "None / unsure"
|
|
203
|
+
})
|
|
204
|
+
widget target: :tty, type: :multi_select
|
|
205
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
206
|
+
transition to: :employee_band
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
ask :employee_band do
|
|
210
|
+
type :enum
|
|
211
|
+
question "How many W-2 employees across all businesses?"
|
|
212
|
+
options({
|
|
213
|
+
"0" => "None",
|
|
214
|
+
"1-10" => "1-10",
|
|
215
|
+
"10-50" => "10-50",
|
|
216
|
+
"50-500" => "50-500",
|
|
217
|
+
"500-1000" => "500-1,000",
|
|
218
|
+
"1000+" => "1,000 or more"
|
|
219
|
+
})
|
|
220
|
+
widget target: :tty, type: :select
|
|
221
|
+
widget target: :desktop, type: :radio_group, columns: 3
|
|
222
|
+
widget target: :mobile, type: :dropdown
|
|
223
|
+
transition to: :contractor_band
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
ask :contractor_band do
|
|
227
|
+
type :enum
|
|
228
|
+
question "How many 1099 contractors paid over $600 in 2025?"
|
|
229
|
+
options %w[0 1-5 6-20 21-50 50+]
|
|
230
|
+
widget target: :tty, type: :select
|
|
231
|
+
widget target: :desktop, type: :radio_group, columns: 5
|
|
232
|
+
transition to: :ppp_eidl
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
ask :ppp_eidl do
|
|
236
|
+
type :multi_enum
|
|
237
|
+
question "Any pandemic-era loans or credits still open?"
|
|
238
|
+
options({
|
|
239
|
+
"ppp_forgiven" => "PPP (forgiven)",
|
|
240
|
+
"ppp_pending" => "PPP (forgiveness pending)",
|
|
241
|
+
"eidl" => "EIDL loan still outstanding",
|
|
242
|
+
"erc" => "Employee Retention Credit claimed",
|
|
243
|
+
"none" => "None"
|
|
244
|
+
})
|
|
245
|
+
widget target: :tty, type: :multi_select
|
|
246
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
247
|
+
transition to: :fringe_benefits
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
ask :fringe_benefits do
|
|
251
|
+
type :multi_enum
|
|
252
|
+
question "Fringe benefits or business deductions to account for?"
|
|
253
|
+
options({
|
|
254
|
+
"company_car" => "Company vehicle / personal use",
|
|
255
|
+
"reimburse" => "Accountable-plan reimbursements",
|
|
256
|
+
"home_office" => "Home office",
|
|
257
|
+
"meals" => "Business meals / travel",
|
|
258
|
+
"education" => "Education / tuition assistance",
|
|
259
|
+
"none" => "None"
|
|
260
|
+
})
|
|
261
|
+
widget target: :tty, type: :multi_select
|
|
262
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
263
|
+
transition to: :business_retirement
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
ask :business_retirement do
|
|
267
|
+
type :multi_enum
|
|
268
|
+
question "Business-side retirement plans in 2025?"
|
|
269
|
+
options({
|
|
270
|
+
"solo_401k" => "Solo 401(k)",
|
|
271
|
+
"sep_ira" => "SEP-IRA",
|
|
272
|
+
"simple_ira" => "SIMPLE IRA",
|
|
273
|
+
"401k" => "Traditional 401(k) plan",
|
|
274
|
+
"defined" => "Defined benefit / cash balance plan",
|
|
275
|
+
"none" => "None"
|
|
276
|
+
})
|
|
277
|
+
widget target: :tty, type: :multi_select
|
|
278
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
279
|
+
transition to: :investment_details, if_rule: contains(:income_types, "investment")
|
|
280
|
+
transition to: :crypto_volume, if_rule: contains(:income_types, "crypto")
|
|
281
|
+
transition to: :rental_count, if_rule: contains(:income_types, "rental")
|
|
282
|
+
transition to: :foreign_earned, if_rule: contains(:income_types, "foreign")
|
|
283
|
+
transition to: :state_filing
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# -----------------------------------------------------------------------
|
|
287
|
+
# Investment block
|
|
288
|
+
# -----------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
ask :investment_details do
|
|
291
|
+
type :multi_enum
|
|
292
|
+
question "What types of investments did you hold?"
|
|
293
|
+
options({
|
|
294
|
+
"brokerage" => "Taxable brokerage (stocks, ETFs)",
|
|
295
|
+
"options" => "Options / derivatives",
|
|
296
|
+
"private" => "Private equity / angel / K-1s",
|
|
297
|
+
"real_estate" => "REITs",
|
|
298
|
+
"hsa" => "HSA",
|
|
299
|
+
"529" => "529 plan"
|
|
300
|
+
})
|
|
301
|
+
widget target: :tty, type: :multi_select
|
|
302
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
303
|
+
transition to: :brokerage_count, if_rule: contains(:investment_details, "brokerage")
|
|
304
|
+
transition to: :k1_count, if_rule: contains(:investment_details, "private")
|
|
305
|
+
transition to: :crypto_volume, if_rule: contains(:income_types, "crypto")
|
|
306
|
+
transition to: :rental_count, if_rule: contains(:income_types, "rental")
|
|
307
|
+
transition to: :foreign_earned, if_rule: contains(:income_types, "foreign")
|
|
308
|
+
transition to: :state_filing
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
ask :brokerage_count do
|
|
312
|
+
type :enum
|
|
313
|
+
question "How many brokerage 1099-B statements will you receive?"
|
|
314
|
+
options %w[1 2 3 4-5 6+]
|
|
315
|
+
widget target: :tty, type: :select
|
|
316
|
+
widget target: :desktop, type: :radio_group, columns: 5
|
|
317
|
+
transition to: :k1_count, if_rule: contains(:investment_details, "private")
|
|
318
|
+
transition to: :crypto_volume, if_rule: contains(:income_types, "crypto")
|
|
319
|
+
transition to: :rental_count, if_rule: contains(:income_types, "rental")
|
|
320
|
+
transition to: :foreign_earned, if_rule: contains(:income_types, "foreign")
|
|
321
|
+
transition to: :state_filing
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
ask :k1_count do
|
|
325
|
+
type :enum
|
|
326
|
+
question "How many K-1s will you receive?"
|
|
327
|
+
options %w[1 2-3 4-6 7-10 10+]
|
|
328
|
+
widget target: :tty, type: :select
|
|
329
|
+
widget target: :desktop, type: :radio_group, columns: 5
|
|
330
|
+
transition to: :crypto_volume, if_rule: contains(:income_types, "crypto")
|
|
331
|
+
transition to: :rental_count, if_rule: contains(:income_types, "rental")
|
|
332
|
+
transition to: :foreign_earned, if_rule: contains(:income_types, "foreign")
|
|
333
|
+
transition to: :state_filing
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# -----------------------------------------------------------------------
|
|
337
|
+
# Crypto block
|
|
338
|
+
# -----------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
ask :crypto_volume do
|
|
341
|
+
type :enum
|
|
342
|
+
question "Roughly how many crypto transactions in 2025?"
|
|
343
|
+
options({
|
|
344
|
+
"0-10" => "Fewer than 10",
|
|
345
|
+
"10-100" => "10-100",
|
|
346
|
+
"100-1000" => "100-1,000",
|
|
347
|
+
"1000-10000" => "1,000-10,000",
|
|
348
|
+
"10000+" => "More than 10,000"
|
|
349
|
+
})
|
|
350
|
+
widget target: :tty, type: :select
|
|
351
|
+
widget target: :desktop, type: :radio_group, columns: 5
|
|
352
|
+
transition to: :crypto_activity
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
ask :crypto_activity do
|
|
356
|
+
type :multi_enum
|
|
357
|
+
question "Which crypto activities apply?"
|
|
358
|
+
options({
|
|
359
|
+
"trading" => "Buying / selling",
|
|
360
|
+
"staking" => "Staking rewards",
|
|
361
|
+
"mining" => "Mining",
|
|
362
|
+
"nfts" => "NFTs",
|
|
363
|
+
"defi" => "DeFi (lending, LP, yield)",
|
|
364
|
+
"airdrops" => "Airdrops / forks",
|
|
365
|
+
"payments" => "Received crypto as payment"
|
|
366
|
+
})
|
|
367
|
+
widget target: :tty, type: :multi_select
|
|
368
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
369
|
+
transition to: :crypto_report_ready
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
ask :crypto_report_ready do
|
|
373
|
+
type :boolean
|
|
374
|
+
question "Do you already have a CoinTracker / Koinly / TokenTax " \
|
|
375
|
+
"tax report covering 2025?"
|
|
376
|
+
widget target: :tty, type: :yes_no
|
|
377
|
+
widget target: :desktop, type: :yes_no_buttons
|
|
378
|
+
transition to: :rental_count, if_rule: contains(:income_types, "rental")
|
|
379
|
+
transition to: :foreign_earned, if_rule: contains(:income_types, "foreign")
|
|
380
|
+
transition to: :state_filing
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# -----------------------------------------------------------------------
|
|
384
|
+
# Rental block
|
|
385
|
+
# -----------------------------------------------------------------------
|
|
386
|
+
|
|
387
|
+
ask :rental_count do
|
|
388
|
+
type :enum
|
|
389
|
+
question "How many rental properties do you own?"
|
|
390
|
+
options %w[1 2-3 4-6 7-10 10+]
|
|
391
|
+
widget target: :tty, type: :select
|
|
392
|
+
widget target: :desktop, type: :radio_group, columns: 5
|
|
393
|
+
transition to: :rental_types
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
ask :rental_types do
|
|
397
|
+
type :multi_enum
|
|
398
|
+
question "What types of rental properties?"
|
|
399
|
+
options({
|
|
400
|
+
"residential_long" => "Residential long-term",
|
|
401
|
+
"short_term" => "Short-term (Airbnb / VRBO)",
|
|
402
|
+
"commercial" => "Commercial",
|
|
403
|
+
"vacation" => "Vacation / mixed personal use",
|
|
404
|
+
"foreign_rental" => "Foreign rental"
|
|
405
|
+
})
|
|
406
|
+
widget target: :tty, type: :multi_select
|
|
407
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
408
|
+
transition to: :foreign_earned, if_rule: contains(:income_types, "foreign")
|
|
409
|
+
transition to: :state_filing
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# -----------------------------------------------------------------------
|
|
413
|
+
# Foreign earned income block
|
|
414
|
+
# -----------------------------------------------------------------------
|
|
415
|
+
|
|
416
|
+
ask :foreign_earned do
|
|
417
|
+
type :multi_enum
|
|
418
|
+
question "What foreign income situation applies?"
|
|
419
|
+
options({
|
|
420
|
+
"worked_abroad" => "Worked abroad during 2025",
|
|
421
|
+
"foreign_employer" => "Paid by a foreign employer",
|
|
422
|
+
"foreign_self" => "Self-employed abroad",
|
|
423
|
+
"us_expat" => "US citizen living abroad",
|
|
424
|
+
"treaty" => "Tax treaty benefits expected"
|
|
425
|
+
})
|
|
426
|
+
widget target: :tty, type: :multi_select
|
|
427
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
428
|
+
transition to: :state_filing
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# -----------------------------------------------------------------------
|
|
432
|
+
# State filing (all 50 + DC, rendered as a grid)
|
|
433
|
+
# -----------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
ask :state_filing do
|
|
436
|
+
type :multi_enum
|
|
437
|
+
question "Select every state (and DC) you need to file a return in."
|
|
438
|
+
options US_STATES
|
|
439
|
+
widget target: :tty, type: :multi_select
|
|
440
|
+
widget target: :desktop, type: :checkbox_group, columns: 10, layout: :grid
|
|
441
|
+
widget target: :mobile, type: :multi_select_dropdown
|
|
442
|
+
transition to: :state_residency
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
ask :state_residency do
|
|
446
|
+
type :enum
|
|
447
|
+
question "Were you a resident of more than one state during 2025?"
|
|
448
|
+
options({
|
|
449
|
+
"no" => "No, one state all year",
|
|
450
|
+
"part_year" => "Yes, part-year resident",
|
|
451
|
+
"multi" => "Yes, multiple states",
|
|
452
|
+
"non_resident" => "No, but earned income in another state"
|
|
453
|
+
})
|
|
454
|
+
widget target: :tty, type: :select
|
|
455
|
+
widget target: :desktop, type: :radio_group, columns: 2
|
|
456
|
+
transition to: :foreign_accounts
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# -----------------------------------------------------------------------
|
|
460
|
+
# Foreign accounts (FBAR / Form 8938 scope trigger)
|
|
461
|
+
# -----------------------------------------------------------------------
|
|
462
|
+
|
|
463
|
+
ask :foreign_accounts do
|
|
464
|
+
type :enum
|
|
465
|
+
question "Did you hold foreign financial accounts in 2025?"
|
|
466
|
+
options({
|
|
467
|
+
"no" => "No",
|
|
468
|
+
"under10k" => "Yes, aggregate always under $10,000",
|
|
469
|
+
"over10k" => "Yes, aggregate exceeded $10,000",
|
|
470
|
+
"unsure" => "Not sure"
|
|
471
|
+
})
|
|
472
|
+
widget target: :tty, type: :select
|
|
473
|
+
widget target: :desktop, type: :radio_group, columns: 2
|
|
474
|
+
transition to: :foreign_account_count, if_rule: equals(:foreign_accounts, "over10k")
|
|
475
|
+
transition to: :foreign_account_count, if_rule: equals(:foreign_accounts, "unsure")
|
|
476
|
+
transition to: :surprise_heads_up
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
ask :foreign_account_count do
|
|
480
|
+
type :enum
|
|
481
|
+
question "How many foreign accounts?"
|
|
482
|
+
options %w[1 2-3 4-10 10+]
|
|
483
|
+
widget target: :tty, type: :select
|
|
484
|
+
widget target: :desktop, type: :radio_group, columns: 4
|
|
485
|
+
transition to: :surprise_heads_up
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# -----------------------------------------------------------------------
|
|
489
|
+
# "Surprise pile" — late-arriving items that blow up timelines
|
|
490
|
+
# -----------------------------------------------------------------------
|
|
491
|
+
|
|
492
|
+
warning :surprise_heads_up do
|
|
493
|
+
text "Last stretch: items that commonly arrive late and add billable\n" \
|
|
494
|
+
"hours. Flagging them now keeps our quote accurate."
|
|
495
|
+
transition to: :tax_notices
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
ask :tax_notices do
|
|
499
|
+
type :multi_enum
|
|
500
|
+
question "Any outstanding tax notices we should know about?"
|
|
501
|
+
options({
|
|
502
|
+
"irs_cp2000" => "IRS CP2000 (underreported income)",
|
|
503
|
+
"irs_balance" => "IRS balance-due notice",
|
|
504
|
+
"irs_audit" => "IRS audit or examination",
|
|
505
|
+
"irs_identity" => "IRS identity verification",
|
|
506
|
+
"irs_lien_levy" => "IRS lien or levy",
|
|
507
|
+
"state_notice" => "State or local tax notice",
|
|
508
|
+
"none" => "None"
|
|
509
|
+
})
|
|
510
|
+
widget target: :tty, type: :multi_select
|
|
511
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
512
|
+
transition to: :estimated_payments
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
ask :estimated_payments do
|
|
516
|
+
type :enum
|
|
517
|
+
question "Did you make 2025 federal estimated tax payments?"
|
|
518
|
+
options({
|
|
519
|
+
"all_four" => "Yes, all four quarters",
|
|
520
|
+
"some" => "Yes, some quarters",
|
|
521
|
+
"none" => "No",
|
|
522
|
+
"refund" => "No — applied prior-year refund instead",
|
|
523
|
+
"unsure" => "Not sure"
|
|
524
|
+
})
|
|
525
|
+
widget target: :tty, type: :select
|
|
526
|
+
widget target: :desktop, type: :radio_group, columns: 2
|
|
527
|
+
transition to: :deduction_types
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# -----------------------------------------------------------------------
|
|
531
|
+
# Deductions & credits
|
|
532
|
+
# -----------------------------------------------------------------------
|
|
533
|
+
|
|
534
|
+
ask :deduction_types do
|
|
535
|
+
type :multi_enum
|
|
536
|
+
question "Which deductions or credits apply?"
|
|
537
|
+
options({
|
|
538
|
+
"mortgage" => "Mortgage interest",
|
|
539
|
+
"salt" => "State & local taxes (SALT)",
|
|
540
|
+
"medical" => "Major medical expenses",
|
|
541
|
+
"charitable" => "Charitable contributions",
|
|
542
|
+
"education" => "Education credits / student loan interest",
|
|
543
|
+
"energy" => "Energy / EV credits",
|
|
544
|
+
"childcare" => "Childcare expenses",
|
|
545
|
+
"hsa_fsa" => "HSA / FSA contributions",
|
|
546
|
+
"none" => "None / standard deduction"
|
|
547
|
+
})
|
|
548
|
+
widget target: :tty, type: :multi_select
|
|
549
|
+
widget target: :desktop, type: :checkbox_group, columns: 2
|
|
550
|
+
transition to: :charitable_band, if_rule: contains(:deduction_types, "charitable")
|
|
551
|
+
transition to: :timeline
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
ask :charitable_band do
|
|
555
|
+
type :enum
|
|
556
|
+
question "Roughly how much in charitable contributions?"
|
|
557
|
+
options({
|
|
558
|
+
"under1k" => "Under $1,000",
|
|
559
|
+
"1k-5k" => "$1,000 - $5,000",
|
|
560
|
+
"5k-25k" => "$5,000 - $25,000",
|
|
561
|
+
"25k-100k" => "$25,000 - $100,000",
|
|
562
|
+
"over100k" => "Over $100,000",
|
|
563
|
+
"non_cash" => "Includes non-cash donations"
|
|
564
|
+
})
|
|
565
|
+
widget target: :tty, type: :select
|
|
566
|
+
widget target: :desktop, type: :radio_group, columns: 3
|
|
567
|
+
transition to: :timeline
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# -----------------------------------------------------------------------
|
|
571
|
+
# Timing (affects rush pricing)
|
|
572
|
+
# -----------------------------------------------------------------------
|
|
573
|
+
|
|
574
|
+
ask :timeline do
|
|
575
|
+
type :enum
|
|
576
|
+
question "How soon do you need this return filed?"
|
|
577
|
+
options({
|
|
578
|
+
"asap" => "As soon as possible",
|
|
579
|
+
"by_april" => "By the April deadline",
|
|
580
|
+
"extension" => "Happy to file an extension",
|
|
581
|
+
"already_ext" => "Already on extension"
|
|
582
|
+
})
|
|
583
|
+
widget target: :tty, type: :select
|
|
584
|
+
widget target: :desktop, type: :radio_group, columns: 2
|
|
585
|
+
transition to: :client_email
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# -----------------------------------------------------------------------
|
|
589
|
+
# Single contact field — email only, so we can send the quote back.
|
|
590
|
+
# No names, no phone numbers, no addresses.
|
|
591
|
+
# -----------------------------------------------------------------------
|
|
592
|
+
|
|
593
|
+
ask :client_email do
|
|
594
|
+
type :email
|
|
595
|
+
question "Email address to send your fee quote to:"
|
|
596
|
+
widget target: :tty, type: :text_input
|
|
597
|
+
transition to: :thanks
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
say :thanks do
|
|
601
|
+
text "Thanks! We'll email your fee quote and a document checklist\n" \
|
|
602
|
+
"within one business day. No personal information was stored."
|
|
603
|
+
end
|
|
604
|
+
end
|
data/examples/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
This folder contains examples of the Ruby DSL that is converted in the question/answer
|
|
4
|
+
dialog with the end-user and results in the single JSON file containing all the answers.
|
|
5
|
+
|
|
6
|
+
The binary that comes with this gem supports multiple sub-commands:
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
❯ inquirex
|
|
10
|
+
Commands:
|
|
11
|
+
inquirex export FLOW_FILE # Export a flow definition as JSON or YAML .
|
|
12
|
+
inquirex graph FLOW_FILE # Export a flow definition as a Mermaid diagram source, an image, or both.
|
|
13
|
+
inquirex run FLOW_FILE # Run a flow definition interactively
|
|
14
|
+
inquirex validate FLOW_FILE # Validate a flow definition file
|
|
15
|
+
inquirex version # Print version information
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Each command supports `--help` of its own.
|
|
19
|
+
|
|
20
|
+
## Flow File
|
|
21
|
+
|
|
22
|
+
The argument `FLOW_FILE` in the above help definition is a ruby file, just like the ones
|
|
23
|
+
contained in this folder. If you modify it, or write your own, the first you should likely take
|
|
24
|
+
is to run `validate` subcommand on this file.
|
|
25
|
+
|
|
26
|
+
For instance, let's validate the simple questionare about your food preferences:
|
|
27
|
+
|
|
28
|
+
### Validation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
❯ inquirex validate 03_food_preferences.rb
|
|
32
|
+
Flow definition is valid!
|
|
33
|
+
ID: food-preferences
|
|
34
|
+
Version: 1.0.0
|
|
35
|
+
Start step: diet
|
|
36
|
+
Total steps: 5
|
|
37
|
+
Steps: diet, meat_preference, seafood_preference, dessert_preference, summary
|
|
38
|
+
Title: Food Preferences
|
|
39
|
+
Subtitle: Tell us what you enjoy
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If the file is valid, most likely all the other commands will work as well.
|
|
43
|
+
|
|
44
|
+
You may want to start with `graph` to visualize the flow of the questions on the aptly named "flow chart":
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
We deliberately used a very simple DSL file, because for a more complex (and likely more realistic) scenario
|
|
49
|
+
the flow chart could be enormous and difficult to follow. Sometimes it's easier to follow the DSL than it is
|
|
50
|
+
to understand the graph.
|
data/exe/inquirex
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# When this file lives inside a checkout (inquirex-tty/exe/inquirex) we want to
|
|
5
|
+
# use the gems pinned in the repo's Gemfile — NOT whatever rubygems has
|
|
6
|
+
# installed globally. Bundler's `with_unbundled_env`-style bootstrapping:
|
|
7
|
+
# point BUNDLE_GEMFILE at our Gemfile (if present), then require bundler/setup.
|
|
8
|
+
gemfile = File.expand_path("../Gemfile", __dir__)
|
|
9
|
+
if File.file?(gemfile)
|
|
10
|
+
ENV["BUNDLE_GEMFILE"] = gemfile
|
|
11
|
+
begin
|
|
12
|
+
require "bundler/setup"
|
|
13
|
+
rescue LoadError
|
|
14
|
+
# Bundler isn't available — fall through to plain $LOAD_PATH priming below.
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if File.directory?("#{__dir__}/../lib")
|
|
19
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
require "inquirex/tty"
|
|
23
|
+
|
|
24
|
+
Dry::CLI.new(Inquirex::TTY::Commands).call
|