taxforge 0.9.16__tar.gz → 0.9.18__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taxforge
3
- Version: 0.9.16
3
+ Version: 0.9.18
4
4
  Summary: AI-powered tax preparation assistant
5
5
  Author: TaxForge Team
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "taxforge"
7
- version = "0.9.16"
7
+ version = "0.9.18"
8
8
  description = "AI-powered tax preparation assistant"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -488,13 +488,13 @@ def review_page() -> rx.Component:
488
488
  rx.hstack(
489
489
  rx.button(
490
490
  "Select All Pending",
491
- size="sm",
491
+ size="1",
492
492
  variant="outline",
493
493
  on_click=TaxAppState.select_all_pending,
494
494
  ),
495
495
  rx.button(
496
496
  "Clear",
497
- size="sm",
497
+ size="1",
498
498
  variant="ghost",
499
499
  on_click=TaxAppState.clear_selection,
500
500
  ),
@@ -673,7 +673,7 @@ def review_page() -> rx.Component:
673
673
  rx.text("🏠 Rental Properties (Schedule E)",
674
674
  color=COLORS["text_primary"], font_weight="600"),
675
675
  rx.spacer(),
676
- rx.button("+ Add", size="sm", on_click=TaxAppState.toggle_rental_form),
676
+ rx.button("+ Add", size="1", on_click=TaxAppState.toggle_rental_form),
677
677
  ),
678
678
  rx.cond(
679
679
  TaxAppState.rental_properties.length() > 0,
@@ -732,7 +732,7 @@ def review_page() -> rx.Component:
732
732
  rx.text("💼 Business Income (Schedule C)",
733
733
  color=COLORS["text_primary"], font_weight="600"),
734
734
  rx.spacer(),
735
- rx.button("+ Add", size="sm", on_click=TaxAppState.toggle_business_form),
735
+ rx.button("+ Add", size="1", on_click=TaxAppState.toggle_business_form),
736
736
  ),
737
737
  rx.cond(
738
738
  TaxAppState.business_income.length() > 0,
@@ -784,7 +784,7 @@ def review_page() -> rx.Component:
784
784
  rx.hstack(
785
785
  rx.text("📋 Other Income", color=COLORS["text_primary"], font_weight="600"),
786
786
  rx.spacer(),
787
- rx.button("+ Add", size="sm", on_click=TaxAppState.toggle_other_income_form),
787
+ rx.button("+ Add", size="1", on_click=TaxAppState.toggle_other_income_form),
788
788
  ),
789
789
  rx.cond(
790
790
  TaxAppState.other_income.length() > 0,
@@ -818,7 +818,7 @@ def review_page() -> rx.Component:
818
818
  rx.hstack(
819
819
  rx.text("📉 Other Deductions", color=COLORS["text_primary"], font_weight="600"),
820
820
  rx.spacer(),
821
- rx.button("+ Add", size="sm", on_click=TaxAppState.toggle_other_deduction_form),
821
+ rx.button("+ Add", size="1", on_click=TaxAppState.toggle_other_deduction_form),
822
822
  ),
823
823
  rx.cond(
824
824
  TaxAppState.other_deductions.length() > 0,
@@ -852,7 +852,7 @@ def review_page() -> rx.Component:
852
852
  rx.hstack(
853
853
  rx.text("👨‍👩‍👧‍👦 Dependents", color=COLORS["text_primary"], font_weight="600"),
854
854
  rx.spacer(),
855
- rx.button("+ Add", size="sm", on_click=TaxAppState.toggle_dependent_form),
855
+ rx.button("+ Add", size="1", on_click=TaxAppState.toggle_dependent_form),
856
856
  ),
857
857
  rx.cond(
858
858
  TaxAppState.dependents.length() > 0,
@@ -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 extract_with_retry, convert_to_w2, convert_to_1099, REQUEST_DELAY_SECONDS
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
 
@@ -208,6 +215,7 @@ class TaxAppState(rx.State):
208
215
  taxable_income: float = 0.0
209
216
  total_tax: float = 0.0
210
217
  total_withholding: float = 0.0
218
+ other_withholding: float = 0.0 # From 1099-NEC/MISC Box 4 (goes to Line 25c)
211
219
  refund_or_owed: float = 0.0
212
220
  is_refund: bool = True
213
221
 
@@ -435,6 +443,62 @@ class TaxAppState(rx.State):
435
443
  "source_file": filename,
436
444
  })
437
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
438
502
 
439
503
  # Recalculate totals
440
504
  self._recalculate()
@@ -456,14 +520,22 @@ class TaxAppState(rx.State):
456
520
  lower = filename.lower()
457
521
  if "w2" in lower or "w-2" in lower:
458
522
  return "W-2"
459
- elif "1099-int" in lower:
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:
460
528
  return "1099-INT"
461
- elif "1099-div" in lower:
529
+ elif "1099-div" in lower or "1099div" in lower:
462
530
  return "1099-DIV"
531
+ elif "1099-b" in lower or "1099b" in lower:
532
+ return "1099-B"
463
533
  elif "1099" in lower:
464
534
  return "1099"
465
535
  elif "1098" in lower:
466
536
  return "1098"
537
+ elif "5498" in lower:
538
+ return "5498-SA"
467
539
  return "Unknown"
468
540
 
469
541
  def add_w2(self, employer: str, wages: float, withheld: float):
@@ -783,6 +855,7 @@ class TaxAppState(rx.State):
783
855
  # Withholding from all sources
784
856
  self.total_withholding = sum(w.get("federal_withheld", 0) for w in self.w2_list)
785
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
786
859
 
787
860
  # Line 2b: Taxable interest (1099-INT)
788
861
  self.total_interest = sum(
@@ -1060,6 +1133,7 @@ class TaxAppState(rx.State):
1060
1133
  self.niit = 0.0
1061
1134
  self.total_tax = 0.0
1062
1135
  self.total_withholding = 0.0
1136
+ self.other_withholding = 0.0
1063
1137
  self.refund_or_owed = 0.0
1064
1138
  self.is_refund = True
1065
1139
  self.return_status = "not_started"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taxforge
3
- Version: 0.9.16
3
+ Version: 0.9.18
4
4
  Summary: AI-powered tax preparation assistant
5
5
  Author: TaxForge Team
6
6
  License: MIT
File without changes
File without changes
File without changes