taxforge 0.9.15__tar.gz → 0.9.17__tar.gz
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.
- {taxforge-0.9.15 → taxforge-0.9.17}/PKG-INFO +1 -1
- {taxforge-0.9.15 → taxforge-0.9.17}/pyproject.toml +1 -1
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/aitax/__init__.py +1 -1
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/aitax/aitax.py +63 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/aitax/cli.py +1 -1
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/aitax/document_extractor.py +96 -1
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/aitax/state.py +201 -7
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/taxforge.egg-info/PKG-INFO +1 -1
- {taxforge-0.9.15 → taxforge-0.9.17}/README.md +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/setup.cfg +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/tests/test_fact_graph.py +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/tests/test_investments.py +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/tests/test_tax_engine.py +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/aitax/components.py +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/taxforge.egg-info/SOURCES.txt +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/taxforge.egg-info/dependency_links.txt +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/taxforge.egg-info/entry_points.txt +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/taxforge.egg-info/requires.txt +0 -0
- {taxforge-0.9.15 → taxforge-0.9.17}/ui/taxforge.egg-info/top_level.txt +0 -0
|
@@ -847,6 +847,69 @@ def review_page() -> rx.Component:
|
|
|
847
847
|
background=COLORS["bg_hover"], border_radius="8px",
|
|
848
848
|
),
|
|
849
849
|
|
|
850
|
+
# Dependents & Child Tax Credit
|
|
851
|
+
rx.vstack(
|
|
852
|
+
rx.hstack(
|
|
853
|
+
rx.text("👨👩👧👦 Dependents", color=COLORS["text_primary"], font_weight="600"),
|
|
854
|
+
rx.spacer(),
|
|
855
|
+
rx.button("+ Add", size="sm", on_click=TaxAppState.toggle_dependent_form),
|
|
856
|
+
),
|
|
857
|
+
rx.cond(
|
|
858
|
+
TaxAppState.dependents.length() > 0,
|
|
859
|
+
rx.foreach(
|
|
860
|
+
TaxAppState.dependents,
|
|
861
|
+
lambda d, idx: rx.hstack(
|
|
862
|
+
rx.text(d["name"], flex="1"),
|
|
863
|
+
rx.text(f"{d['relationship']}, Age {d['age']}"),
|
|
864
|
+
rx.badge(
|
|
865
|
+
rx.cond(d["is_child"], "CTC $2,000", "Credit $500"),
|
|
866
|
+
color_scheme=rx.cond(d["is_child"], "green", "blue"),
|
|
867
|
+
),
|
|
868
|
+
rx.icon("x", size=14, cursor="pointer",
|
|
869
|
+
on_click=lambda: TaxAppState.remove_dependent(idx)),
|
|
870
|
+
width="100%", padding="8px",
|
|
871
|
+
),
|
|
872
|
+
),
|
|
873
|
+
rx.text("No dependents added", color=COLORS["text_muted"], font_style="italic"),
|
|
874
|
+
),
|
|
875
|
+
# Dependent Form
|
|
876
|
+
rx.cond(
|
|
877
|
+
TaxAppState.show_dependent_form,
|
|
878
|
+
rx.vstack(
|
|
879
|
+
rx.hstack(
|
|
880
|
+
rx.input(placeholder="Name", value=TaxAppState.dependent_form_name, on_change=TaxAppState.set_dependent_name, flex="1"),
|
|
881
|
+
rx.input(placeholder="Relationship", value=TaxAppState.dependent_form_relationship, on_change=TaxAppState.set_dependent_relationship, width="120px"),
|
|
882
|
+
rx.input(placeholder="Age", value=TaxAppState.dependent_form_age, on_change=TaxAppState.set_dependent_age, type="number", width="80px"),
|
|
883
|
+
width="100%",
|
|
884
|
+
),
|
|
885
|
+
rx.text("Under 17 = $2,000 Child Tax Credit | 17+ = $500 Other Dependent Credit",
|
|
886
|
+
color=COLORS["text_muted"], font_size="12px"),
|
|
887
|
+
rx.hstack(
|
|
888
|
+
rx.button("Cancel", variant="outline", on_click=TaxAppState.toggle_dependent_form),
|
|
889
|
+
rx.button("Add Dependent", on_click=TaxAppState.submit_dependent_form),
|
|
890
|
+
width="100%", justify="end",
|
|
891
|
+
),
|
|
892
|
+
width="100%", spacing="2", padding="12px",
|
|
893
|
+
background="white", border_radius="8px", border=f"1px solid {COLORS['border']}",
|
|
894
|
+
),
|
|
895
|
+
),
|
|
896
|
+
# Credits Summary
|
|
897
|
+
rx.cond(
|
|
898
|
+
TaxAppState.child_tax_credit > 0,
|
|
899
|
+
rx.text(f"Child Tax Credit: ${TaxAppState.child_tax_credit:,.2f}",
|
|
900
|
+
color=COLORS["success"], font_size="13px"),
|
|
901
|
+
),
|
|
902
|
+
rx.cond(
|
|
903
|
+
TaxAppState.other_dependent_credit > 0,
|
|
904
|
+
rx.text(f"Other Dependent Credit: ${TaxAppState.other_dependent_credit:,.2f}",
|
|
905
|
+
color=COLORS["success"], font_size="13px"),
|
|
906
|
+
),
|
|
907
|
+
rx.text(f"Total Tax Credits: ${TaxAppState.total_credits:,.2f}",
|
|
908
|
+
color=COLORS["success"], font_size="13px", font_weight="600"),
|
|
909
|
+
width="100%", spacing="2", padding="12px",
|
|
910
|
+
background=COLORS["bg_hover"], border_radius="8px",
|
|
911
|
+
),
|
|
912
|
+
|
|
850
913
|
spacing="4",
|
|
851
914
|
width="100%",
|
|
852
915
|
),
|
|
@@ -65,9 +65,29 @@ For 1099-SA (HSA distributions), extract:
|
|
|
65
65
|
- distribution: Box 1 - Gross distribution
|
|
66
66
|
- earnings: Box 2 - Earnings on excess contributions
|
|
67
67
|
|
|
68
|
+
For 1099-NEC (Nonemployee Compensation), extract:
|
|
69
|
+
- payer_name: Company/person who paid you
|
|
70
|
+
- payer_ein: Payer's TIN (if shown)
|
|
71
|
+
- nonemployee_compensation: Box 1 - Nonemployee compensation (this is self-employment income)
|
|
72
|
+
- federal_withheld: Box 4 - Federal income tax withheld (if any)
|
|
73
|
+
- state_tax_withheld: Box 5 - State tax withheld (if any)
|
|
74
|
+
|
|
75
|
+
For 1099-MISC (Miscellaneous Income), extract:
|
|
76
|
+
- payer_name: Company/person who paid you
|
|
77
|
+
- rents: Box 1 - Rents received
|
|
78
|
+
- royalties: Box 2 - Royalties
|
|
79
|
+
- other_income: Box 3 - Other income
|
|
80
|
+
- fishing_boat_proceeds: Box 5 - Fishing boat proceeds
|
|
81
|
+
- medical_payments: Box 6 - Medical and health care payments
|
|
82
|
+
- nonemployee_compensation: Box 7 - Nonemployee compensation (pre-2020 forms, now on 1099-NEC)
|
|
83
|
+
- substitute_payments: Box 8 - Substitute payments in lieu of dividends
|
|
84
|
+
- crop_insurance: Box 9 - Crop insurance proceeds
|
|
85
|
+
- attorney_fees: Box 10 - Gross proceeds paid to an attorney
|
|
86
|
+
- federal_withheld: Box 4 - Federal income tax withheld
|
|
87
|
+
|
|
68
88
|
Respond ONLY with a valid JSON object containing:
|
|
69
89
|
{
|
|
70
|
-
"document_type": "W-2" | "1099-INT" | "1099-DIV" | "1099-B" | "1098" | "5498-SA" | "1099-SA" | "OTHER",
|
|
90
|
+
"document_type": "W-2" | "1099-INT" | "1099-DIV" | "1099-B" | "1098" | "5498-SA" | "1099-SA" | "1099-NEC" | "1099-MISC" | "OTHER",
|
|
71
91
|
"confidence": 0.0-1.0,
|
|
72
92
|
"extracted_data": {
|
|
73
93
|
// relevant fields based on document type
|
|
@@ -296,3 +316,78 @@ def convert_to_1099(extracted_data: dict, form_type: str) -> dict:
|
|
|
296
316
|
"amount": amount,
|
|
297
317
|
"form_type": form_type,
|
|
298
318
|
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def convert_to_business_income(extracted_data: dict, source_type: str) -> dict:
|
|
322
|
+
"""
|
|
323
|
+
Convert 1099-NEC or 1099-MISC to Schedule C business income.
|
|
324
|
+
|
|
325
|
+
1099-NEC Box 1 = Self-employment income (goes to Schedule C)
|
|
326
|
+
1099-MISC Box 7 (pre-2020) = Same as 1099-NEC
|
|
327
|
+
"""
|
|
328
|
+
gross_income = 0.0
|
|
329
|
+
payer_name = extracted_data.get("payer_name", "Unknown Client")
|
|
330
|
+
|
|
331
|
+
if source_type == "1099-NEC":
|
|
332
|
+
gross_income = float(extracted_data.get("nonemployee_compensation", 0))
|
|
333
|
+
elif source_type == "1099-MISC":
|
|
334
|
+
# Box 7 was nonemployee comp before 2020, now rarely used
|
|
335
|
+
gross_income = float(extracted_data.get("nonemployee_compensation", 0))
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
"name": f"Self-Employment ({payer_name})",
|
|
339
|
+
"gross_income": gross_income,
|
|
340
|
+
"expenses": 0.0, # User can edit to add expenses
|
|
341
|
+
"net_profit": gross_income,
|
|
342
|
+
"source_form": source_type,
|
|
343
|
+
"payer_name": payer_name,
|
|
344
|
+
"federal_withheld": float(extracted_data.get("federal_withheld", 0)),
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def convert_1099_misc_to_other_income(extracted_data: dict) -> list:
|
|
349
|
+
"""
|
|
350
|
+
Convert 1099-MISC non-self-employment boxes to other income entries.
|
|
351
|
+
|
|
352
|
+
Returns a list of income entries for: rents, royalties, other income, etc.
|
|
353
|
+
"""
|
|
354
|
+
entries = []
|
|
355
|
+
payer = extracted_data.get("payer_name", "Unknown Payer")
|
|
356
|
+
|
|
357
|
+
# Box 1 - Rents (goes to Schedule E if rental property, otherwise other income)
|
|
358
|
+
rents = float(extracted_data.get("rents", 0))
|
|
359
|
+
if rents > 0:
|
|
360
|
+
entries.append({
|
|
361
|
+
"description": f"Rental Income - {payer} (1099-MISC Box 1)",
|
|
362
|
+
"amount": rents,
|
|
363
|
+
"category": "rental",
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
# Box 2 - Royalties (goes to Schedule E)
|
|
367
|
+
royalties = float(extracted_data.get("royalties", 0))
|
|
368
|
+
if royalties > 0:
|
|
369
|
+
entries.append({
|
|
370
|
+
"description": f"Royalties - {payer} (1099-MISC Box 2)",
|
|
371
|
+
"amount": royalties,
|
|
372
|
+
"category": "royalty",
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
# Box 3 - Other income (taxable, goes to Schedule 1 Line 8)
|
|
376
|
+
other = float(extracted_data.get("other_income", 0))
|
|
377
|
+
if other > 0:
|
|
378
|
+
entries.append({
|
|
379
|
+
"description": f"Other Income - {payer} (1099-MISC Box 3)",
|
|
380
|
+
"amount": other,
|
|
381
|
+
"category": "other",
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
# Box 10 - Attorney fees (gross proceeds)
|
|
385
|
+
attorney = float(extracted_data.get("attorney_fees", 0))
|
|
386
|
+
if attorney > 0:
|
|
387
|
+
entries.append({
|
|
388
|
+
"description": f"Attorney Fees - {payer} (1099-MISC Box 10)",
|
|
389
|
+
"amount": attorney,
|
|
390
|
+
"category": "other",
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
return entries
|
|
@@ -7,7 +7,14 @@ from pathlib import Path
|
|
|
7
7
|
from typing import List, Dict
|
|
8
8
|
from enum import Enum
|
|
9
9
|
|
|
10
|
-
from .document_extractor import
|
|
10
|
+
from .document_extractor import (
|
|
11
|
+
extract_with_retry,
|
|
12
|
+
convert_to_w2,
|
|
13
|
+
convert_to_1099,
|
|
14
|
+
convert_to_business_income,
|
|
15
|
+
convert_1099_misc_to_other_income,
|
|
16
|
+
REQUEST_DELAY_SECONDS
|
|
17
|
+
)
|
|
11
18
|
import asyncio
|
|
12
19
|
|
|
13
20
|
|
|
@@ -136,11 +143,18 @@ class TaxAppState(rx.State):
|
|
|
136
143
|
other_deduction_form_desc: str = ""
|
|
137
144
|
other_deduction_form_amount: str = ""
|
|
138
145
|
|
|
146
|
+
# Dependent form
|
|
147
|
+
dependent_form_name: str = ""
|
|
148
|
+
dependent_form_relationship: str = ""
|
|
149
|
+
dependent_form_age: str = ""
|
|
150
|
+
child_care_form_amount: str = ""
|
|
151
|
+
|
|
139
152
|
# Form visibility
|
|
140
153
|
show_rental_form: bool = False
|
|
141
154
|
show_business_form: bool = False
|
|
142
155
|
show_other_income_form: bool = False
|
|
143
156
|
show_other_deduction_form: bool = False
|
|
157
|
+
show_dependent_form: bool = False
|
|
144
158
|
|
|
145
159
|
# ===== Document State =====
|
|
146
160
|
uploaded_files: List[str] = []
|
|
@@ -168,6 +182,12 @@ class TaxAppState(rx.State):
|
|
|
168
182
|
other_income: List[Dict] = [] # {description, amount, tax_type}
|
|
169
183
|
other_deductions: List[Dict] = [] # {description, amount, deduction_type}
|
|
170
184
|
|
|
185
|
+
# Dependents
|
|
186
|
+
dependents: List[Dict] = [] # {name, relationship, age, ssn_last4, is_child}
|
|
187
|
+
|
|
188
|
+
# Child and Dependent Care
|
|
189
|
+
child_care_expenses: float = 0.0 # Expenses for dependent care while working
|
|
190
|
+
|
|
171
191
|
# ===== Calculated Values =====
|
|
172
192
|
total_wages: float = 0.0
|
|
173
193
|
total_interest: float = 0.0
|
|
@@ -186,9 +206,16 @@ class TaxAppState(rx.State):
|
|
|
186
206
|
capital_gains_tax: float = 0.0 # Separate tax on capital gains
|
|
187
207
|
additional_medicare_tax: float = 0.0 # 0.9% on wages > $250k (MFJ)
|
|
188
208
|
niit: float = 0.0 # Net Investment Income Tax 3.8%
|
|
209
|
+
|
|
210
|
+
# Tax Credits
|
|
211
|
+
child_tax_credit: float = 0.0 # $2,000 per child under 17
|
|
212
|
+
other_dependent_credit: float = 0.0 # $500 per other dependent
|
|
213
|
+
child_care_credit: float = 0.0 # Credit for child/dependent care
|
|
214
|
+
total_credits: float = 0.0
|
|
189
215
|
taxable_income: float = 0.0
|
|
190
216
|
total_tax: float = 0.0
|
|
191
217
|
total_withholding: float = 0.0
|
|
218
|
+
other_withholding: float = 0.0 # From 1099-NEC/MISC Box 4 (goes to Line 25c)
|
|
192
219
|
refund_or_owed: float = 0.0
|
|
193
220
|
is_refund: bool = True
|
|
194
221
|
|
|
@@ -416,6 +443,62 @@ class TaxAppState(rx.State):
|
|
|
416
443
|
"source_file": filename,
|
|
417
444
|
})
|
|
418
445
|
parsed_count += 1
|
|
446
|
+
|
|
447
|
+
elif ai_doc_type == "1099-NEC":
|
|
448
|
+
# Nonemployee compensation → Schedule C (self-employment)
|
|
449
|
+
business_data = convert_to_business_income(extracted, "1099-NEC")
|
|
450
|
+
business_data["source_file"] = filename
|
|
451
|
+
self.business_income.append(business_data)
|
|
452
|
+
|
|
453
|
+
# Track any withholding
|
|
454
|
+
withheld = float(extracted.get("federal_withheld", 0))
|
|
455
|
+
if withheld > 0:
|
|
456
|
+
# Add to a special 1099 withholding tracker (goes to Line 25c)
|
|
457
|
+
if not hasattr(self, 'other_withholding'):
|
|
458
|
+
self.other_withholding = 0.0
|
|
459
|
+
self.other_withholding += withheld
|
|
460
|
+
parsed_count += 1
|
|
461
|
+
|
|
462
|
+
elif ai_doc_type == "1099-MISC":
|
|
463
|
+
# Check if it has nonemployee comp (Box 7, pre-2020)
|
|
464
|
+
nec_amount = float(extracted.get("nonemployee_compensation", 0))
|
|
465
|
+
if nec_amount > 0:
|
|
466
|
+
# Treat like 1099-NEC
|
|
467
|
+
business_data = convert_to_business_income(extracted, "1099-MISC")
|
|
468
|
+
business_data["source_file"] = filename
|
|
469
|
+
self.business_income.append(business_data)
|
|
470
|
+
|
|
471
|
+
# Handle other 1099-MISC income (rents, royalties, other)
|
|
472
|
+
other_entries = convert_1099_misc_to_other_income(extracted)
|
|
473
|
+
for entry in other_entries:
|
|
474
|
+
entry["source_file"] = filename
|
|
475
|
+
if entry.get("category") == "rental":
|
|
476
|
+
# Add as simple rental (user can expand)
|
|
477
|
+
self.rental_properties.append({
|
|
478
|
+
"address": entry["description"],
|
|
479
|
+
"rent_income": entry["amount"],
|
|
480
|
+
"mortgage_interest": 0,
|
|
481
|
+
"property_tax": 0,
|
|
482
|
+
"insurance": 0,
|
|
483
|
+
"repairs": 0,
|
|
484
|
+
"management": 0,
|
|
485
|
+
"utilities": 0,
|
|
486
|
+
"depreciation": 0,
|
|
487
|
+
"other_expenses": 0,
|
|
488
|
+
"total_expenses": 0,
|
|
489
|
+
"net_income": entry["amount"],
|
|
490
|
+
"source_file": filename,
|
|
491
|
+
})
|
|
492
|
+
else:
|
|
493
|
+
# Other income
|
|
494
|
+
self.other_income.append({
|
|
495
|
+
"description": entry["description"],
|
|
496
|
+
"amount": entry["amount"],
|
|
497
|
+
"source_file": filename,
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
if nec_amount > 0 or other_entries:
|
|
501
|
+
parsed_count += 1
|
|
419
502
|
|
|
420
503
|
# Recalculate totals
|
|
421
504
|
self._recalculate()
|
|
@@ -437,14 +520,22 @@ class TaxAppState(rx.State):
|
|
|
437
520
|
lower = filename.lower()
|
|
438
521
|
if "w2" in lower or "w-2" in lower:
|
|
439
522
|
return "W-2"
|
|
440
|
-
elif "1099-
|
|
523
|
+
elif "1099-nec" in lower or "1099nec" in lower:
|
|
524
|
+
return "1099-NEC"
|
|
525
|
+
elif "1099-misc" in lower or "1099misc" in lower:
|
|
526
|
+
return "1099-MISC"
|
|
527
|
+
elif "1099-int" in lower or "1099int" in lower:
|
|
441
528
|
return "1099-INT"
|
|
442
|
-
elif "1099-div" in lower:
|
|
529
|
+
elif "1099-div" in lower or "1099div" in lower:
|
|
443
530
|
return "1099-DIV"
|
|
531
|
+
elif "1099-b" in lower or "1099b" in lower:
|
|
532
|
+
return "1099-B"
|
|
444
533
|
elif "1099" in lower:
|
|
445
534
|
return "1099"
|
|
446
535
|
elif "1098" in lower:
|
|
447
536
|
return "1098"
|
|
537
|
+
elif "5498" in lower:
|
|
538
|
+
return "5498-SA"
|
|
448
539
|
return "Unknown"
|
|
449
540
|
|
|
450
541
|
def add_w2(self, employer: str, wages: float, withheld: float):
|
|
@@ -555,6 +646,28 @@ class TaxAppState(rx.State):
|
|
|
555
646
|
self.other_deductions.pop(index)
|
|
556
647
|
self._recalculate()
|
|
557
648
|
|
|
649
|
+
# ===== Dependent Methods =====
|
|
650
|
+
def add_dependent(self, name: str, relationship: str, age: int, is_child: bool = True):
|
|
651
|
+
"""Add a dependent (child or other)."""
|
|
652
|
+
self.dependents.append({
|
|
653
|
+
"name": name,
|
|
654
|
+
"relationship": relationship,
|
|
655
|
+
"age": age,
|
|
656
|
+
"is_child": is_child and age < 17, # Child Tax Credit requires under 17
|
|
657
|
+
})
|
|
658
|
+
self._recalculate()
|
|
659
|
+
|
|
660
|
+
def remove_dependent(self, index: int):
|
|
661
|
+
"""Remove a dependent."""
|
|
662
|
+
if 0 <= index < len(self.dependents):
|
|
663
|
+
self.dependents.pop(index)
|
|
664
|
+
self._recalculate()
|
|
665
|
+
|
|
666
|
+
def set_child_care_expenses(self, amount: float):
|
|
667
|
+
"""Set child/dependent care expenses."""
|
|
668
|
+
self.child_care_expenses = amount
|
|
669
|
+
self._recalculate()
|
|
670
|
+
|
|
558
671
|
# ===== Form Toggle Methods =====
|
|
559
672
|
def toggle_rental_form(self):
|
|
560
673
|
self.show_rental_form = not self.show_rental_form
|
|
@@ -568,6 +681,9 @@ class TaxAppState(rx.State):
|
|
|
568
681
|
def toggle_other_deduction_form(self):
|
|
569
682
|
self.show_other_deduction_form = not self.show_other_deduction_form
|
|
570
683
|
|
|
684
|
+
def toggle_dependent_form(self):
|
|
685
|
+
self.show_dependent_form = not self.show_dependent_form
|
|
686
|
+
|
|
571
687
|
# ===== Form Input Setters =====
|
|
572
688
|
def set_rental_address(self, val): self.rental_form_address = val
|
|
573
689
|
def set_rental_income(self, val): self.rental_form_income = val
|
|
@@ -587,6 +703,11 @@ class TaxAppState(rx.State):
|
|
|
587
703
|
def set_other_deduction_desc(self, val): self.other_deduction_form_desc = val
|
|
588
704
|
def set_other_deduction_amount(self, val): self.other_deduction_form_amount = val
|
|
589
705
|
|
|
706
|
+
def set_dependent_name(self, val): self.dependent_form_name = val
|
|
707
|
+
def set_dependent_relationship(self, val): self.dependent_form_relationship = val
|
|
708
|
+
def set_dependent_age(self, val): self.dependent_form_age = val
|
|
709
|
+
def set_child_care_amount(self, val): self.child_care_form_amount = val
|
|
710
|
+
|
|
590
711
|
# ===== Form Submit Methods =====
|
|
591
712
|
def submit_rental_form(self):
|
|
592
713
|
"""Submit rental property form."""
|
|
@@ -659,6 +780,34 @@ class TaxAppState(rx.State):
|
|
|
659
780
|
except ValueError:
|
|
660
781
|
self.error_message = "Invalid amount"
|
|
661
782
|
|
|
783
|
+
def submit_dependent_form(self):
|
|
784
|
+
"""Submit dependent form."""
|
|
785
|
+
try:
|
|
786
|
+
age = int(self.dependent_form_age or 0)
|
|
787
|
+
self.add_dependent(
|
|
788
|
+
name=self.dependent_form_name or "Dependent",
|
|
789
|
+
relationship=self.dependent_form_relationship or "Child",
|
|
790
|
+
age=age,
|
|
791
|
+
is_child=(age < 17),
|
|
792
|
+
)
|
|
793
|
+
self.dependent_form_name = ""
|
|
794
|
+
self.dependent_form_relationship = ""
|
|
795
|
+
self.dependent_form_age = ""
|
|
796
|
+
self.show_dependent_form = False
|
|
797
|
+
self.success_message = "Dependent added!"
|
|
798
|
+
except ValueError:
|
|
799
|
+
self.error_message = "Invalid age"
|
|
800
|
+
|
|
801
|
+
def submit_child_care_expenses(self):
|
|
802
|
+
"""Submit child care expenses."""
|
|
803
|
+
try:
|
|
804
|
+
self.child_care_expenses = float(self.child_care_form_amount or 0)
|
|
805
|
+
self.child_care_form_amount = ""
|
|
806
|
+
self._recalculate()
|
|
807
|
+
self.success_message = "Child care expenses updated!"
|
|
808
|
+
except ValueError:
|
|
809
|
+
self.error_message = "Invalid amount"
|
|
810
|
+
|
|
662
811
|
def remove_file(self, filename: str):
|
|
663
812
|
"""Remove an uploaded file and its extracted data."""
|
|
664
813
|
if filename in self.uploaded_files:
|
|
@@ -706,6 +855,7 @@ class TaxAppState(rx.State):
|
|
|
706
855
|
# Withholding from all sources
|
|
707
856
|
self.total_withholding = sum(w.get("federal_withheld", 0) for w in self.w2_list)
|
|
708
857
|
self.total_withholding += sum(f.get("federal_withheld", 0) for f in self.form_1099_list)
|
|
858
|
+
self.total_withholding += self.other_withholding # From 1099-NEC/MISC
|
|
709
859
|
|
|
710
860
|
# Line 2b: Taxable interest (1099-INT)
|
|
711
861
|
self.total_interest = sum(
|
|
@@ -871,10 +1021,51 @@ class TaxAppState(rx.State):
|
|
|
871
1021
|
niit_base = min(investment_income, max(0, self.adjusted_gross_income - niit_threshold))
|
|
872
1022
|
self.niit = niit_base * 0.038
|
|
873
1023
|
|
|
874
|
-
#
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1024
|
+
# Gross tax before credits
|
|
1025
|
+
gross_tax = (ordinary_tax + self.capital_gains_tax +
|
|
1026
|
+
self.additional_medicare_tax + self.niit +
|
|
1027
|
+
self.self_employment_tax)
|
|
1028
|
+
|
|
1029
|
+
# ===== TAX CREDITS =====
|
|
1030
|
+
|
|
1031
|
+
# Child Tax Credit ($2,000 per qualifying child under 17)
|
|
1032
|
+
# Phase out: MFJ $400k, others $200k
|
|
1033
|
+
ctc_phaseout = 400000 if self.filing_status == "married_filing_jointly" else 200000
|
|
1034
|
+
qualifying_children = sum(1 for d in self.dependents if d.get("is_child", False))
|
|
1035
|
+
base_ctc = qualifying_children * 2000
|
|
1036
|
+
|
|
1037
|
+
# Phase out: reduce by $50 for each $1,000 over threshold
|
|
1038
|
+
if self.adjusted_gross_income > ctc_phaseout:
|
|
1039
|
+
reduction = ((self.adjusted_gross_income - ctc_phaseout) // 1000) * 50
|
|
1040
|
+
self.child_tax_credit = max(0, base_ctc - reduction)
|
|
1041
|
+
else:
|
|
1042
|
+
self.child_tax_credit = base_ctc
|
|
1043
|
+
|
|
1044
|
+
# Credit for Other Dependents ($500 per dependent not qualifying for CTC)
|
|
1045
|
+
other_dependents = sum(1 for d in self.dependents if not d.get("is_child", False))
|
|
1046
|
+
self.other_dependent_credit = other_dependents * 500
|
|
1047
|
+
|
|
1048
|
+
# Child and Dependent Care Credit (20-35% of expenses, max $3,000/1 or $6,000/2+)
|
|
1049
|
+
if self.child_care_expenses > 0 and qualifying_children > 0:
|
|
1050
|
+
max_expenses = 3000 if qualifying_children == 1 else 6000
|
|
1051
|
+
eligible_expenses = min(self.child_care_expenses, max_expenses)
|
|
1052
|
+
# Credit rate: 35% if AGI <= $15,000, phases down to 20% at $43,000+
|
|
1053
|
+
if self.adjusted_gross_income <= 15000:
|
|
1054
|
+
rate = 0.35
|
|
1055
|
+
elif self.adjusted_gross_income >= 43000:
|
|
1056
|
+
rate = 0.20
|
|
1057
|
+
else:
|
|
1058
|
+
rate = 0.35 - ((self.adjusted_gross_income - 15000) // 2000) * 0.01
|
|
1059
|
+
rate = max(0.20, rate)
|
|
1060
|
+
self.child_care_credit = eligible_expenses * rate
|
|
1061
|
+
else:
|
|
1062
|
+
self.child_care_credit = 0.0
|
|
1063
|
+
|
|
1064
|
+
# Total credits (nonrefundable - can't exceed tax)
|
|
1065
|
+
self.total_credits = self.child_tax_credit + self.other_dependent_credit + self.child_care_credit
|
|
1066
|
+
|
|
1067
|
+
# Total tax after credits
|
|
1068
|
+
self.total_tax = max(0, gross_tax - self.total_credits)
|
|
878
1069
|
|
|
879
1070
|
# ===== REFUND OR AMOUNT OWED =====
|
|
880
1071
|
self.refund_or_owed = self.total_withholding - self.total_tax
|
|
@@ -919,6 +1110,8 @@ class TaxAppState(rx.State):
|
|
|
919
1110
|
self.business_income = []
|
|
920
1111
|
self.other_income = []
|
|
921
1112
|
self.other_deductions = []
|
|
1113
|
+
self.dependents = []
|
|
1114
|
+
self.child_care_expenses = 0.0
|
|
922
1115
|
self.form_1099_list = []
|
|
923
1116
|
self.total_wages = 0.0
|
|
924
1117
|
self.total_interest = 0.0
|
|
@@ -940,6 +1133,7 @@ class TaxAppState(rx.State):
|
|
|
940
1133
|
self.niit = 0.0
|
|
941
1134
|
self.total_tax = 0.0
|
|
942
1135
|
self.total_withholding = 0.0
|
|
1136
|
+
self.other_withholding = 0.0
|
|
943
1137
|
self.refund_or_owed = 0.0
|
|
944
1138
|
self.is_refund = True
|
|
945
1139
|
self.return_status = "not_started"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|