taxforge 0.9.21__tar.gz → 0.9.23__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.21
3
+ Version: 0.9.23
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.21"
7
+ version = "0.9.23"
8
8
  description = "AI-powered tax preparation assistant"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,3 +1,3 @@
1
1
  """TaxForge - AI-powered tax preparation"""
2
2
 
3
- __version__ = "0.9.21"
3
+ __version__ = "0.9.23"
@@ -617,11 +617,7 @@ def review_page() -> rx.Component:
617
617
  {"background": "#E8EDFF"},
618
618
  {},
619
619
  ),
620
- on_click=rx.cond(
621
- doc["status"] != "parsed",
622
- lambda: TaxAppState.toggle_file_selection(doc["name"]),
623
- lambda: None,
624
- ),
620
+ on_click=lambda: TaxAppState.toggle_file_selection(doc["name"]),
625
621
  transition="all 0.15s ease",
626
622
  ),
627
623
  ),
@@ -1512,7 +1508,7 @@ app = rx.App(
1512
1508
  )
1513
1509
 
1514
1510
  # Register pages
1515
- app.add_page(dashboard_page, route="/", title="TaxForge - Dashboard")
1511
+ app.add_page(dashboard_page, route="/", title="TaxForge - Dashboard", on_load=TaxAppState.load_saved_data)
1516
1512
  app.add_page(upload_page, route="/upload", title="TaxForge - Upload")
1517
1513
  app.add_page(review_page, route="/review", title="TaxForge - Review")
1518
1514
  app.add_page(settings_page, route="/settings", title="TaxForge - Settings")
@@ -8,7 +8,7 @@ import shutil
8
8
  from pathlib import Path
9
9
 
10
10
 
11
- __version__ = "0.9.21"
11
+ __version__ = "0.9.23"
12
12
 
13
13
  RXCONFIG_CONTENT = '''"""Reflex config for TaxForge."""
14
14
  import reflex as rx
@@ -0,0 +1,197 @@
1
+ """
2
+ TaxForge - Data Persistence
3
+ Save and load tax data to/from local JSON file.
4
+ """
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Dict, Any, Optional
9
+ from datetime import datetime
10
+ import base64
11
+ import hashlib
12
+
13
+ # Data directory
14
+ DATA_DIR = Path.home() / ".taxforge"
15
+ DATA_FILE = DATA_DIR / "tax_data.json"
16
+ BACKUP_DIR = DATA_DIR / "backups"
17
+
18
+ # Fields to persist (from TaxAppState)
19
+ PERSIST_FIELDS = [
20
+ # Personal info
21
+ "first_name", "last_name", "filing_status",
22
+ # API key (will be obfuscated)
23
+ "openai_api_key",
24
+ # Documents
25
+ "uploaded_files", "parsed_documents",
26
+ # Tax data
27
+ "w2_list", "form_1099_list", "form_1098_list", "form_5498_list",
28
+ # Manual entries
29
+ "rental_properties", "business_income", "other_income", "other_deductions",
30
+ "dependents", "child_care_expenses",
31
+ # Calculated values we want to persist
32
+ "total_wages", "total_interest", "total_dividends", "total_capital_gains",
33
+ "total_rental_income", "total_business_income", "total_other_income",
34
+ "hsa_deduction", "self_employment_tax", "mortgage_interest_deduction",
35
+ "adjusted_gross_income", "itemized_deductions", "standard_deduction",
36
+ "total_deductions", "taxable_income", "total_tax", "total_withholding",
37
+ "other_withholding", "refund_or_owed", "is_refund",
38
+ "child_tax_credit", "other_dependent_credit", "child_care_credit", "total_credits",
39
+ # Status
40
+ "return_status", "return_generated",
41
+ ]
42
+
43
+ # Fields that should be obfuscated (not encrypted, just not plain text)
44
+ SENSITIVE_FIELDS = ["openai_api_key"]
45
+
46
+
47
+ def _obfuscate(value: str) -> str:
48
+ """Simple obfuscation for sensitive values (not real encryption)."""
49
+ if not value:
50
+ return ""
51
+ return base64.b64encode(value.encode()).decode()
52
+
53
+
54
+ def _deobfuscate(value: str) -> str:
55
+ """Reverse obfuscation."""
56
+ if not value:
57
+ return ""
58
+ try:
59
+ return base64.b64decode(value.encode()).decode()
60
+ except:
61
+ return value # Return as-is if can't decode
62
+
63
+
64
+ def ensure_data_dir():
65
+ """Ensure data directory exists."""
66
+ DATA_DIR.mkdir(parents=True, exist_ok=True)
67
+ BACKUP_DIR.mkdir(parents=True, exist_ok=True)
68
+
69
+
70
+ def save_state(state) -> bool:
71
+ """
72
+ Save state to JSON file.
73
+
74
+ Args:
75
+ state: TaxAppState instance
76
+
77
+ Returns:
78
+ True if successful, False otherwise
79
+ """
80
+ try:
81
+ ensure_data_dir()
82
+
83
+ # Extract fields to save
84
+ data = {
85
+ "_saved_at": datetime.now().isoformat(),
86
+ "_version": "0.9.21",
87
+ }
88
+
89
+ for field in PERSIST_FIELDS:
90
+ if hasattr(state, field):
91
+ value = getattr(state, field)
92
+ # Handle special types
93
+ if isinstance(value, (list, dict)):
94
+ data[field] = value
95
+ else:
96
+ data[field] = value
97
+
98
+ # Obfuscate sensitive fields
99
+ if field in SENSITIVE_FIELDS and value:
100
+ data[field] = _obfuscate(str(value))
101
+
102
+ # Create backup of existing file
103
+ if DATA_FILE.exists():
104
+ backup_name = f"tax_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
105
+ backup_path = BACKUP_DIR / backup_name
106
+ # Keep only last 5 backups
107
+ existing_backups = sorted(BACKUP_DIR.glob("tax_data_*.json"))
108
+ if len(existing_backups) >= 5:
109
+ for old_backup in existing_backups[:-4]:
110
+ old_backup.unlink()
111
+
112
+ # Write to file
113
+ with open(DATA_FILE, 'w') as f:
114
+ json.dump(data, f, indent=2, default=str)
115
+
116
+ return True
117
+
118
+ except Exception as e:
119
+ print(f"[TaxForge] Error saving state: {e}")
120
+ return False
121
+
122
+
123
+ def load_state(state) -> bool:
124
+ """
125
+ Load state from JSON file.
126
+
127
+ Args:
128
+ state: TaxAppState instance to populate
129
+
130
+ Returns:
131
+ True if data was loaded, False if no data or error
132
+ """
133
+ try:
134
+ if not DATA_FILE.exists():
135
+ print("[TaxForge] No saved data found, starting fresh")
136
+ return False
137
+
138
+ with open(DATA_FILE, 'r') as f:
139
+ data = json.load(f)
140
+
141
+ print(f"[TaxForge] Loading saved data from {data.get('_saved_at', 'unknown')}")
142
+
143
+ # Restore fields
144
+ for field in PERSIST_FIELDS:
145
+ if field in data:
146
+ value = data[field]
147
+
148
+ # Deobfuscate sensitive fields
149
+ if field in SENSITIVE_FIELDS and value:
150
+ value = _deobfuscate(value)
151
+
152
+ # Set the attribute
153
+ if hasattr(state, field):
154
+ setattr(state, field, value)
155
+
156
+ print(f"[TaxForge] Loaded: {len(data.get('w2_list', []))} W-2s, "
157
+ f"{len(data.get('form_1099_list', []))} 1099s, "
158
+ f"{len(data.get('rental_properties', []))} rentals")
159
+
160
+ return True
161
+
162
+ except Exception as e:
163
+ print(f"[TaxForge] Error loading state: {e}")
164
+ return False
165
+
166
+
167
+ def clear_saved_data() -> bool:
168
+ """Clear all saved data."""
169
+ try:
170
+ if DATA_FILE.exists():
171
+ DATA_FILE.unlink()
172
+ return True
173
+ except Exception as e:
174
+ print(f"[TaxForge] Error clearing data: {e}")
175
+ return False
176
+
177
+
178
+ def get_data_info() -> Dict[str, Any]:
179
+ """Get info about saved data."""
180
+ info = {
181
+ "exists": DATA_FILE.exists(),
182
+ "path": str(DATA_FILE),
183
+ "size": 0,
184
+ "saved_at": None,
185
+ }
186
+
187
+ if DATA_FILE.exists():
188
+ info["size"] = DATA_FILE.stat().st_size
189
+ try:
190
+ with open(DATA_FILE, 'r') as f:
191
+ data = json.load(f)
192
+ info["saved_at"] = data.get("_saved_at")
193
+ info["version"] = data.get("_version")
194
+ except:
195
+ pass
196
+
197
+ return info
@@ -15,6 +15,7 @@ from .document_extractor import (
15
15
  convert_1099_misc_to_other_income,
16
16
  REQUEST_DELAY_SECONDS
17
17
  )
18
+ from .persistence import save_state, load_state, clear_saved_data
18
19
  import asyncio
19
20
 
20
21
 
@@ -269,6 +270,7 @@ class TaxAppState(rx.State):
269
270
  self.openai_api_key = self.temp_api_key
270
271
  self.show_api_modal = False
271
272
  self.success_message = "API key saved!"
273
+ self._auto_save() # Persist API key
272
274
 
273
275
  def check_api_key_for_upload(self):
274
276
  """Check if API key is set, open modal if not."""
@@ -328,6 +330,7 @@ class TaxAppState(rx.State):
328
330
  if new_files > 0:
329
331
  self.success_message = f"Uploaded {new_files} file(s). Go to Review to process with AI."
330
332
  self.return_status = "in_progress"
333
+ self._auto_save() # Persist uploaded files list
331
334
  except Exception as e:
332
335
  self.error_message = f"Upload failed: {str(e)}"
333
336
 
@@ -1086,6 +1089,16 @@ class TaxAppState(rx.State):
1086
1089
 
1087
1090
  if self.total_wages > 0 or total_income > 0:
1088
1091
  self.return_status = "in_progress"
1092
+
1093
+ # Auto-save after recalculation
1094
+ self._auto_save()
1095
+
1096
+ def _auto_save(self):
1097
+ """Auto-save state to disk."""
1098
+ try:
1099
+ save_state(self)
1100
+ except Exception as e:
1101
+ print(f"[TaxForge] Auto-save failed: {e}")
1089
1102
 
1090
1103
  def _calculate_tax(self, income: float, brackets: list) -> float:
1091
1104
  """Calculate tax from brackets."""
@@ -1153,6 +1166,15 @@ class TaxAppState(rx.State):
1153
1166
  self.return_generated = False
1154
1167
  self.error_message = ""
1155
1168
  self.success_message = ""
1169
+ # Also clear persisted data
1170
+ clear_saved_data()
1171
+
1172
+ def load_saved_data(self):
1173
+ """Load previously saved data from disk."""
1174
+ if load_state(self):
1175
+ self.success_message = "Previous session restored!"
1176
+ else:
1177
+ self.success_message = ""
1156
1178
 
1157
1179
  # ===== Computed Properties =====
1158
1180
  @rx.var
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taxforge
3
- Version: 0.9.21
3
+ Version: 0.9.23
4
4
  Summary: AI-powered tax preparation assistant
5
5
  Author: TaxForge Team
6
6
  License: MIT
@@ -8,6 +8,7 @@ ui/aitax/aitax.py
8
8
  ui/aitax/cli.py
9
9
  ui/aitax/components.py
10
10
  ui/aitax/document_extractor.py
11
+ ui/aitax/persistence.py
11
12
  ui/aitax/state.py
12
13
  ui/taxforge.egg-info/PKG-INFO
13
14
  ui/taxforge.egg-info/SOURCES.txt
File without changes
File without changes