taxforge 0.9.16__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taxforge
3
- Version: 0.9.16
3
+ Version: 0.9.17
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.17"
8
8
  description = "AI-powered tax preparation assistant"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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.17
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
File without changes