fin-infra 0.1.64__py3-none-any.whl → 0.1.65__py3-none-any.whl

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.
@@ -470,7 +470,7 @@ async def generate_spending_insights(
470
470
 
471
471
  # Try to import ai-infra LLM (optional dependency)
472
472
  try:
473
- from ai_infra.llm import LLM
473
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
474
474
  except ImportError:
475
475
  # Graceful degradation: return rule-based insights
476
476
  return _generate_rule_based_insights(spending_insight, user_context)
@@ -136,21 +136,19 @@ def add_categorization(
136
136
  categories = get_all_categories()
137
137
 
138
138
  # Return category metadata
139
- return [
140
- {
141
- "name": cat.value,
142
- "group": get_category_metadata(cat).group.value
143
- if get_category_metadata(cat)
144
- else None,
145
- "display_name": get_category_metadata(cat).display_name
146
- if get_category_metadata(cat)
147
- else cat.value,
148
- "description": get_category_metadata(cat).description
149
- if get_category_metadata(cat)
150
- else None,
151
- }
152
- for cat in categories
153
- ]
139
+ result = []
140
+ for cat in categories:
141
+ meta = get_category_metadata(cat)
142
+ result.append(
143
+ {
144
+ "name": cat.value,
145
+ "group": meta.group.value if meta else None,
146
+ "display_name": meta.display_name if meta else cat.value,
147
+ "description": meta.description if meta else None,
148
+ }
149
+ )
150
+
151
+ return result
154
152
 
155
153
  @router.get("/stats", response_model=CategoryStats)
156
154
  async def get_stats():
@@ -20,8 +20,8 @@ from pydantic import BaseModel, Field
20
20
 
21
21
  # ai-infra imports
22
22
  try:
23
- from ai_infra.llm import LLM
24
- from ai_infra.llm.providers import Providers
23
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
24
+ from ai_infra.llm.providers import Providers # type: ignore[attr-defined]
25
25
  except ImportError:
26
26
  raise ImportError("ai-infra not installed. Install with: pip install ai-infra")
27
27
 
@@ -149,15 +149,6 @@ def add_financial_conversation(
149
149
  # TODO: Get user_id from svc-infra auth context
150
150
  user_id = "demo_user" # Placeholder
151
151
 
152
- # Check for sensitive content
153
- if is_sensitive_question(request.question):
154
- return ConversationResponse(
155
- answer="I cannot process requests containing sensitive information like SSNs, passwords, or account numbers. Please rephrase your question without this information.",
156
- follow_up_questions=[],
157
- conversation_id=f"{user_id}_denied",
158
- disclaimer="This is an automated safety response.",
159
- )
160
-
161
152
  # Ask conversation
162
153
  response = await conversation.ask(
163
154
  user_id=user_id,
@@ -173,7 +164,7 @@ def add_financial_conversation(
173
164
  # TODO: Get user_id from svc-infra auth context
174
165
  user_id = "demo_user"
175
166
  context = await conversation._get_context(user_id)
176
- return context.exchanges if context else []
167
+ return context.previous_exchanges if context else []
177
168
 
178
169
  @router.delete("/history")
179
170
  async def clear_history():
fin_infra/chat/ease.py CHANGED
@@ -69,7 +69,7 @@ def easy_financial_conversation(
69
69
  # Auto-create LLM if not provided
70
70
  if llm is None:
71
71
  try:
72
- from ai_infra.llm import LLM
72
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
73
73
 
74
74
  llm = LLM()
75
75
  except ImportError:
@@ -338,8 +338,65 @@ class FinancialPlanningConversation:
338
338
  # Save updated context (24h TTL)
339
339
  await self._save_context(context)
340
340
 
341
+ # Track latest session id for convenience endpoints (history/clear).
342
+ # Best-effort: failures here must not break the chat response.
343
+ try:
344
+ await self.cache.set(
345
+ self._latest_session_key(user_id),
346
+ context.session_id,
347
+ ttl=86400,
348
+ )
349
+ except Exception:
350
+ pass
351
+
341
352
  return response
342
353
 
354
+ # ---------------------------------------------------------------------
355
+ # Backward-compatible context helpers
356
+ # ---------------------------------------------------------------------
357
+
358
+ def _latest_session_key(self, user_id: str) -> str:
359
+ return f"fin_infra:conversation_latest_session:{user_id}"
360
+
361
+ async def _get_latest_session_id(self, user_id: str) -> str | None:
362
+ try:
363
+ value = await self.cache.get(self._latest_session_key(user_id))
364
+ except Exception:
365
+ return None
366
+
367
+ if value is None:
368
+ return None
369
+ if isinstance(value, bytes):
370
+ try:
371
+ return value.decode("utf-8")
372
+ except Exception:
373
+ return None
374
+ if isinstance(value, str):
375
+ return value
376
+ return str(value)
377
+
378
+ async def _get_context(
379
+ self, user_id: str, session_id: str | None = None
380
+ ) -> ConversationContext | None:
381
+ if session_id is None:
382
+ session_id = await self._get_latest_session_id(user_id)
383
+ if session_id is None:
384
+ return None
385
+
386
+ return await self._load_context(user_id=user_id, session_id=session_id)
387
+
388
+ async def _clear_context(self, user_id: str, session_id: str | None = None) -> None:
389
+ if session_id is None:
390
+ session_id = await self._get_latest_session_id(user_id)
391
+
392
+ if session_id is not None:
393
+ await self.clear_session(user_id=user_id, session_id=session_id)
394
+
395
+ try:
396
+ await self.cache.delete(self._latest_session_key(user_id))
397
+ except Exception:
398
+ pass
399
+
343
400
  async def _load_context(
344
401
  self,
345
402
  user_id: str,
@@ -15,7 +15,7 @@ from typing import TYPE_CHECKING
15
15
  from pydantic import BaseModel, Field
16
16
 
17
17
  if TYPE_CHECKING:
18
- from ai_infra.llm import LLM
18
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
19
19
 
20
20
 
21
21
  class CryptoInsight(BaseModel):
@@ -121,6 +121,15 @@ class NetWorthTracker:
121
121
  self.goal_tracker = goal_tracker
122
122
  self.conversation = conversation
123
123
 
124
+ # Configuration set by easy_net_worth(); declared here for type checkers.
125
+ self.snapshot_schedule: str = "daily"
126
+ self.change_threshold_percent: float = 5.0
127
+ self.change_threshold_amount: float = 10000.0
128
+ self.enable_llm: bool = False
129
+ self.llm_provider: str | None = None
130
+ self.llm_model: str | None = None
131
+ self.config: dict[str, Any] = {}
132
+
124
133
  async def calculate_net_worth(
125
134
  self,
126
135
  user_id: str,
@@ -368,12 +377,20 @@ def easy_net_worth(
368
377
 
369
378
  if enable_llm:
370
379
  try:
371
- from ai_infra.llm import LLM
380
+ from ai_infra.llm.llm import LLM # type: ignore[attr-defined]
372
381
  except ImportError:
373
382
  raise ImportError(
374
383
  "LLM features require ai-infra package. " "Install with: pip install ai-infra"
375
384
  )
376
385
 
386
+ cache = None
387
+ try:
388
+ from svc_infra.cache import get_cache
389
+
390
+ cache = get_cache()
391
+ except Exception:
392
+ cache = None
393
+
377
394
  # Determine default model
378
395
  default_models = {
379
396
  "google": "gemini-2.0-flash-exp",
@@ -416,18 +433,22 @@ def easy_net_worth(
416
433
  # goals.management not yet implemented, skip
417
434
  pass
418
435
 
419
- try:
420
- from fin_infra.conversation import FinancialPlanningConversation
421
-
422
- conversation = FinancialPlanningConversation(
423
- llm=llm,
424
- cache=cache, # Required for context storage
425
- provider=llm_provider,
426
- model_name=model_name,
427
- )
428
- except ImportError:
429
- # conversation module not yet implemented, skip
430
- pass
436
+ if cache is not None:
437
+ try:
438
+ from fin_infra.conversation import FinancialPlanningConversation
439
+
440
+ conversation = FinancialPlanningConversation(
441
+ llm=llm,
442
+ cache=cache, # Required for context storage
443
+ provider=llm_provider,
444
+ model_name=model_name,
445
+ )
446
+ except ImportError:
447
+ # conversation module not yet implemented, skip
448
+ pass
449
+ except Exception:
450
+ # Cache not configured or other runtime issue; skip optional conversation wiring.
451
+ pass
431
452
 
432
453
  # Create tracker
433
454
  tracker = NetWorthTracker(
@@ -150,14 +150,14 @@ def add_normalization(
150
150
  ):
151
151
  """Convert amount between currencies."""
152
152
  try:
153
- result = await converter.convert(amount, from_currency, to_currency)
153
+ result = await converter.convert_with_details(amount, from_currency, to_currency)
154
154
  return {
155
- "amount": amount,
156
- "from_currency": from_currency,
157
- "to_currency": to_currency,
158
- "result": result.amount,
155
+ "amount": result.amount,
156
+ "from_currency": result.from_currency,
157
+ "to_currency": result.to_currency,
158
+ "result": result.converted,
159
159
  "rate": result.rate,
160
- "timestamp": result.timestamp.isoformat(),
160
+ "timestamp": result.date.isoformat() if result.date else None,
161
161
  }
162
162
  except CurrencyNotSupportedError as e:
163
163
  raise HTTPException(status_code=400, detail=str(e))
@@ -67,7 +67,15 @@ class BankingProvider(ABC):
67
67
  class BrokerageProvider(ABC):
68
68
  @abstractmethod
69
69
  def submit_order(
70
- self, symbol: str, qty: float, side: str, type_: str, time_in_force: str
70
+ self,
71
+ symbol: str,
72
+ qty: float,
73
+ side: str,
74
+ type_: str,
75
+ time_in_force: str,
76
+ limit_price: float | None = None,
77
+ stop_price: float | None = None,
78
+ client_order_id: str | None = None,
71
79
  ) -> dict:
72
80
  pass
73
81
 
@@ -75,6 +83,71 @@ class BrokerageProvider(ABC):
75
83
  def positions(self) -> Iterable[dict]:
76
84
  pass
77
85
 
86
+ @abstractmethod
87
+ def get_account(self) -> dict:
88
+ """Get trading account information."""
89
+ pass
90
+
91
+ @abstractmethod
92
+ def get_position(self, symbol: str) -> dict:
93
+ """Get position for a specific symbol."""
94
+ pass
95
+
96
+ @abstractmethod
97
+ def close_position(self, symbol: str) -> dict:
98
+ """Close a position (market sell/cover)."""
99
+ pass
100
+
101
+ @abstractmethod
102
+ def list_orders(self, status: str = "open", limit: int = 50) -> list[dict]:
103
+ """List orders."""
104
+ pass
105
+
106
+ @abstractmethod
107
+ def get_order(self, order_id: str) -> dict:
108
+ """Get order by ID."""
109
+ pass
110
+
111
+ @abstractmethod
112
+ def cancel_order(self, order_id: str) -> None:
113
+ """Cancel an order."""
114
+ pass
115
+
116
+ @abstractmethod
117
+ def get_portfolio_history(self, period: str = "1M", timeframe: str = "1D") -> dict:
118
+ """Get portfolio value history."""
119
+ pass
120
+
121
+ @abstractmethod
122
+ def create_watchlist(self, name: str, symbols: list[str] | None = None) -> dict:
123
+ """Create a new watchlist."""
124
+ pass
125
+
126
+ @abstractmethod
127
+ def list_watchlists(self) -> list[dict]:
128
+ """List all watchlists."""
129
+ pass
130
+
131
+ @abstractmethod
132
+ def get_watchlist(self, watchlist_id: str) -> dict:
133
+ """Get a watchlist by ID."""
134
+ pass
135
+
136
+ @abstractmethod
137
+ def delete_watchlist(self, watchlist_id: str) -> None:
138
+ """Delete a watchlist."""
139
+ pass
140
+
141
+ @abstractmethod
142
+ def add_to_watchlist(self, watchlist_id: str, symbol: str) -> dict:
143
+ """Add a symbol to a watchlist."""
144
+ pass
145
+
146
+ @abstractmethod
147
+ def remove_from_watchlist(self, watchlist_id: str, symbol: str) -> dict:
148
+ """Remove a symbol from a watchlist."""
149
+ pass
150
+
78
151
 
79
152
  class IdentityProvider(ABC):
80
153
  @abstractmethod
@@ -91,6 +164,11 @@ class CreditProvider(ABC):
91
164
  def get_credit_score(self, user_id: str, **kwargs) -> dict | None:
92
165
  pass
93
166
 
167
+ @abstractmethod
168
+ def get_credit_report(self, user_id: str, **kwargs) -> dict | None:
169
+ """Retrieve full credit report for a user."""
170
+ pass
171
+
94
172
 
95
173
  class TaxProvider(ABC):
96
174
  """Provider for tax data and document retrieval."""
@@ -100,6 +178,11 @@ class TaxProvider(ABC):
100
178
  """Retrieve tax forms for a user and tax year."""
101
179
  pass
102
180
 
181
+ @abstractmethod
182
+ def get_tax_documents(self, user_id: str, tax_year: int, **kwargs) -> list[dict]:
183
+ """Retrieve tax documents for a user and tax year."""
184
+ pass
185
+
103
186
  @abstractmethod
104
187
  def get_tax_document(self, document_id: str, **kwargs) -> dict:
105
188
  """Retrieve a specific tax document by ID."""
@@ -110,6 +193,20 @@ class TaxProvider(ABC):
110
193
  """Calculate capital gains from crypto transactions."""
111
194
  pass
112
195
 
196
+ @abstractmethod
197
+ def calculate_tax_liability(
198
+ self,
199
+ user_id: str,
200
+ income: float,
201
+ deductions: float,
202
+ filing_status: str,
203
+ tax_year: int,
204
+ state: str | None = None,
205
+ **kwargs,
206
+ ) -> dict:
207
+ """Calculate estimated tax liability."""
208
+ pass
209
+
113
210
 
114
211
  class InvestmentProvider(ABC):
115
212
  """Provider for investment holdings and portfolio data (Plaid, SnapTrade).
@@ -11,3 +11,8 @@ class ExperianCredit(CreditProvider):
11
11
  self, user_id: str, **kwargs
12
12
  ) -> dict | None: # pragma: no cover - placeholder
13
13
  return None
14
+
15
+ def get_credit_report(
16
+ self, user_id: str, **kwargs
17
+ ) -> dict | None: # pragma: no cover - placeholder
18
+ return None
@@ -403,7 +403,16 @@ def add_recurring_detection(
403
403
 
404
404
  # Generate insights with LLM
405
405
  # TODO: Pass user_id for better caching (currently uses subscriptions hash)
406
- insights = await detector.insights_generator.generate(subscriptions)
406
+ insights_generator = detector.insights_generator
407
+ if insights_generator is None:
408
+ from fastapi import HTTPException
409
+
410
+ raise HTTPException(
411
+ status_code=500,
412
+ detail="Subscription insights generator not configured (enable_llm=True required).",
413
+ )
414
+
415
+ insights = await insights_generator.generate(subscriptions)
407
416
 
408
417
  return insights
409
418
  else:
@@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict, Field
20
20
 
21
21
  # Lazy import for optional dependency (ai-infra)
22
22
  try:
23
- from ai_infra.llm import LLM
23
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
24
24
  except ImportError:
25
25
  LLM = None
26
26
 
@@ -21,7 +21,7 @@ from pydantic import BaseModel, ConfigDict, Field
21
21
 
22
22
  # Lazy import for optional dependency (ai-infra)
23
23
  try:
24
- from ai_infra.llm import LLM
24
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
25
25
  except ImportError:
26
26
  LLM = None
27
27
 
@@ -22,7 +22,7 @@ from pydantic import BaseModel, ConfigDict, Field
22
22
 
23
23
  # Lazy import for optional dependency (ai-infra)
24
24
  try:
25
- from ai_infra.llm import LLM
25
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
26
26
  except ImportError:
27
27
  LLM = None
28
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fin-infra
3
- Version: 0.1.64
3
+ Version: 0.1.65
4
4
  Summary: Financial infrastructure toolkit: banking connections, market data, credit, cashflows, and brokerage integrations
5
5
  License: MIT
6
6
  Keywords: finance,banking,plaid,brokerage,markets,credit,tax,cashflow,fintech,infra
@@ -10,7 +10,7 @@ fin_infra/analytics/projections.py,sha256=7cuG6w1KXq8sd3UNufu5aOcxG5n-foswrHqrgW
10
10
  fin_infra/analytics/rebalancing.py,sha256=K3S7KQiIU2LwyAwWN9VrSly4AOl24vN9tz_JX7I9FJ8,14642
11
11
  fin_infra/analytics/savings.py,sha256=tavIRZtu9FjCm-DeWg5f060GcsdgD-cl-vgKOnieOUw,7574
12
12
  fin_infra/analytics/scenarios.py,sha256=LE_dZVkbxxAx5sxitGhiOhZfWTlYtVbIvS9pEXkijLc,12246
13
- fin_infra/analytics/spending.py,sha256=SxfsBPdLoHrFKcx56Da63j0YEV_zxYxaV2lEIQIaMQk,26197
13
+ fin_infra/analytics/spending.py,sha256=-11Pm2E-UjuLfYWLD8wQFf5Xyk_0es1xmiedTsBj-Bc,26227
14
14
  fin_infra/banking/__init__.py,sha256=-Pn8YxpE_aOefFGL4bBmOogywNWShEV271CKxlrvTIM,22556
15
15
  fin_infra/banking/history.py,sha256=1ufAwkTnXr-QJetFzJl4xA2e3dqd1-TkT8pf46MNfho,10630
16
16
  fin_infra/banking/utils.py,sha256=B2ebnTeUz-56l8XMBWnf2txFOr0bXIo3cKPio7_bhc4,15711
@@ -30,16 +30,16 @@ fin_infra/budgets/tracker.py,sha256=U8C7k2VV8bOjtjPIWR8qXktsBNSyBAUnE9o2mjEs1MU,
30
30
  fin_infra/cashflows/__init__.py,sha256=OEleZSwEHffxTvz0J52qqlkBwnu4BHbQUW0vKwzsWAs,8579
31
31
  fin_infra/cashflows/core.py,sha256=Or0hPqCvY_ypV0YiMXh-mle6xWK0tE8WuPPAqHGUp8E,532
32
32
  fin_infra/categorization/__init__.py,sha256=efLje12AW-ec9Vs5ynb41r4XCIWx5a-Z9WoGb3kQdIE,2030
33
- fin_infra/categorization/add.py,sha256=rsFPNWx-c8bXHiizA_MHMjsn6YiCPUBVU2kwA_8yoeM,6382
33
+ fin_infra/categorization/add.py,sha256=JDOvxngh-7oWHTddOyP4GAse9vLuxSTfoIhrDKUHOKg,6278
34
34
  fin_infra/categorization/ease.py,sha256=oIcPVuxXOPaAhSe_OcfO4eumCg9WpfIZUdg-k-Xx800,5859
35
35
  fin_infra/categorization/engine.py,sha256=ZJm-V6I1okDSFQA34GFZCOTsLCztNtmHlbm2-r51mwQ,12108
36
- fin_infra/categorization/llm_layer.py,sha256=0Y71o7jzE_Xs2wl_x7COM37PeP8NuTiaKiXzNCVm2sE,12727
36
+ fin_infra/categorization/llm_layer.py,sha256=sOR3gtyPR4y58-5a_XW7U4GMwPsvBwaL_mtmaExC_zk,12787
37
37
  fin_infra/categorization/models.py,sha256=O8ceQOM0ljRh0jkmnjV7CK5Jyq1DI3lG07UTeeMheNg,5931
38
38
  fin_infra/categorization/rules.py,sha256=m3OogJY0hJe5BrmZqOvOKS2-HRdW4Y5jvvtlPDn9Pn8,12884
39
39
  fin_infra/categorization/taxonomy.py,sha256=qsgo7VJkM6GFBBOaTRHWP82vl5SinRKnMsj4ICarEyQ,13281
40
- fin_infra/chat/__init__.py,sha256=_4yQ7jRgrOgQAeiplrOnPJnTk9Ojc4Mdxg9thHhI_MQ,6837
41
- fin_infra/chat/ease.py,sha256=8T0BQUkWQVpaTooD5-ZtinackkciqGargXnzWzayj3M,3113
42
- fin_infra/chat/planning.py,sha256=gmVo7t-KLohEaI3r8rJ_-6EFtMnpEmlfl8JhZLAHx94,17936
40
+ fin_infra/chat/__init__.py,sha256=NsYyRxZGwUFvYEmoLfuTaAfBWn34KOKqeRi6_hSVgGE,6356
41
+ fin_infra/chat/ease.py,sha256=b99CtKSe9UJqXPLqlqoUI0mI6bmxHVlbpdFq4K1oCGg,3143
42
+ fin_infra/chat/planning.py,sha256=eKUW6VDHJS-xQTks7bgjNQaO32Fr5gA_oP5NLt2y5Zs,19916
43
43
  fin_infra/cli/__init__.py,sha256=7M8gKULnui4__9kXRKRHgETuFwZlacK9xrq5rSZ31CM,376
44
44
  fin_infra/cli/cmds/__init__.py,sha256=BvL3wRoUl3cO5wesv1Cqoatup7VeYMhq82tS19iNZHE,136
45
45
  fin_infra/cli/cmds/scaffold_cmds.py,sha256=HZrnJ6NgTBYbt6LuJeoi7JKJgWE_umX9v7zjtwYfP-g,7659
@@ -56,7 +56,7 @@ fin_infra/credit/experian/parser.py,sha256=7ptdLyTWWqHWqCo1CXn6L7XaIn9ZRRuOaATbF
56
56
  fin_infra/credit/experian/provider.py,sha256=l3NW6dppgxeUkrThftH-IB43bwuZGhNcW0jVBGF8XGY,13783
57
57
  fin_infra/credit/mock.py,sha256=xKWZk3fhuIYRfiZkNc9fbHUNViNKjmOLSj0MTI1f4ik,5356
58
58
  fin_infra/crypto/__init__.py,sha256=HpplYEY8GiBz55ehYRDQxs8SWJIW1smBs9eFOKt_nzI,8318
59
- fin_infra/crypto/insights.py,sha256=u5gzoLtVPuWbQcFYX-TW-bJtILqB_AvCIxXZ9hd8oQg,11625
59
+ fin_infra/crypto/insights.py,sha256=2Q0QnpOC-nXJa8pYZmjNre6csz2peGbb5chN5-cipWI,11655
60
60
  fin_infra/documents/__init__.py,sha256=Ub1hbX3PTrBSsBdcbL8PFf6oq8jSH4pYxW45-qOYPqs,1909
61
61
  fin_infra/documents/add.py,sha256=ztSFCY42hfLLgUbXefxvf_AAbzaxJ6xpEZJpcHE8g7c,8133
62
62
  fin_infra/documents/analysis.py,sha256=zY5OQEIlq3JLNND_cg2KheFdryUmIecPOR2lR6oKhPw,13992
@@ -106,7 +106,7 @@ fin_infra/net_worth/__init__.py,sha256=EjEuHNg8gEfFwbfko1-o5j-gSUZ2FcO9h7l05C-zA
106
106
  fin_infra/net_worth/add.py,sha256=5xYy2L5hEEPiQNF79i-ArWVztLXk2XM97DoZYNWGAz8,23100
107
107
  fin_infra/net_worth/aggregator.py,sha256=grif-N8qk77L_JQ4IlcOJaKKP1qpxel0lIV_ll3HgjI,12646
108
108
  fin_infra/net_worth/calculator.py,sha256=JERDtZyFurw5x2NYqfHvJzv6qigamI3AFfR-wesTj_E,13133
109
- fin_infra/net_worth/ease.py,sha256=XvOaowgj64JLKOtEq-B8MIc3fgrysL4vG8m1e5j-ukY,15094
109
+ fin_infra/net_worth/ease.py,sha256=n1YI5rEmh48homMJvJMETCbcnICVE5myOnd1OZDJgiY,15920
110
110
  fin_infra/net_worth/goals.py,sha256=BJGxdsMjvgQDELFEJo-ai3DvsAzUNXvzMXkwovHr8yQ,1238
111
111
  fin_infra/net_worth/insights.py,sha256=vVK4BtfHNJGb1wyk9XD0fLpoadATTdorF8OxHOgD9b0,25222
112
112
  fin_infra/net_worth/models.py,sha256=A5idGtMEQy1J4jaLMq9ZZslmvOhxfkW7cjLKW23AMQo,22403
@@ -115,7 +115,7 @@ fin_infra/net_worth/scaffold_templates/__init__.py,sha256=OKeMCC_JNw6m8rBWr_wesO
115
115
  fin_infra/net_worth/scaffold_templates/models.py.tmpl,sha256=9BKsoD08RZbSdOm0wFTbx5OzKfAEtuA1NcWyS1Aywx4,5934
116
116
  fin_infra/net_worth/scaffold_templates/repository.py.tmpl,sha256=DSErnNxeAe4pWeefARRK3bU0hHltqdIFffENfVwdd7c,12798
117
117
  fin_infra/net_worth/scaffold_templates/schemas.py.tmpl,sha256=VkFsxyZx4DFDhXDhn-7KT0IgrXCvgaS5ZdWbjyezWj0,4709
118
- fin_infra/normalization/__init__.py,sha256=-7EP_lTExQpoCtgsx1wD3j8aMH9y3SlFgHke3mWCQI8,6195
118
+ fin_infra/normalization/__init__.py,sha256=foCru-Nf9M1zP1jdrT0oNazzmp6AWiaDbUDoiyJefvA,6252
119
119
  fin_infra/normalization/currency_converter.py,sha256=uuu8ASa5ppEniWLEVEpiDxXjZzln9nopWrhrATcD6Z4,7058
120
120
  fin_infra/normalization/models.py,sha256=gNC9chpbQPRN58V2j__VEPVNReO1N8jH_AHObwGPWu0,1928
121
121
  fin_infra/normalization/providers/__init__.py,sha256=LFU1tB2hVO42Yrkw-IDpPexD4mIlxob9lRrJEeGYqpE,559
@@ -128,10 +128,10 @@ fin_infra/providers/__init__.py,sha256=jxhQm79T6DVXf7Wpy7luL-p50cE_IMUbjt4o3apzJ
128
128
  fin_infra/providers/banking/base.py,sha256=KeNU4ur3zLKHVsBF1LQifcs2AKX06IEE-Rx_SetFeAs,102
129
129
  fin_infra/providers/banking/plaid_client.py,sha256=21m6ZkovwXuUuj0-dgQVDLxSfxZVjhuXj8di_-q3jGc,6617
130
130
  fin_infra/providers/banking/teller_client.py,sha256=QmrsBlk3_rHT-pTQPrIAA74kjIjcgdi-gOb8NA3oBO8,10268
131
- fin_infra/providers/base.py,sha256=oLzdExPGE7yg-URtin3vGTQ8hEzG7UnTmDGDWJB5oL0,4273
131
+ fin_infra/providers/base.py,sha256=zQBiIPYrWELl65bvekAsl7WnuUYHAkM96cYmKQYTb3U,6857
132
132
  fin_infra/providers/brokerage/alpaca.py,sha256=wRVfVmExiYXCk1pLRmHSrfo91714JIm3rrD0djrNfT8,9938
133
133
  fin_infra/providers/brokerage/base.py,sha256=JJFH0Cqca4Rg4rmxfiwcQt-peRoBf4JpG3g6jx8DVks,106
134
- fin_infra/providers/credit/experian.py,sha256=hNEVqmCaPT72NHV3Nw3sKOYPX0kIsl819ucqUc-7z2k,341
134
+ fin_infra/providers/credit/experian.py,sha256=r7lpFecgOdNEhb_Lxz2Z-BG8R3p2n0XlqDKL7y8NZ-0,482
135
135
  fin_infra/providers/identity/stripe_identity.py,sha256=JQGJRuQdWP5dWDcROgtz1RrmpkytRv95H6Fn-x1kifU,501
136
136
  fin_infra/providers/market/alphavantage.py,sha256=srZdkf-frBuKyPTdWasMmVrpnh76BEBDXa-nsYtLzNc,8963
137
137
  fin_infra/providers/market/base.py,sha256=ljBzZTfjYQS9tXahmxFic7JQSZeyoiDMUZ1NY0R7yto,108
@@ -145,14 +145,14 @@ fin_infra/providers/tax/mock.py,sha256=AxI3RmEn3exdQeeUNkQYqZ-war5PS--WnLGXfRRee
145
145
  fin_infra/providers/tax/taxbit.py,sha256=DEA7vgQPYMjz4ZdC0DpY7112FLZJ2kvwgAbDZnpHFy0,4271
146
146
  fin_infra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
147
147
  fin_infra/recurring/__init__.py,sha256=ihMPywft8pGqzMu6EXxbQCU7ByoMl_dvad00gWV1mnk,2308
148
- fin_infra/recurring/add.py,sha256=bOvpRbtMjWYqNpq8dTR6aCNsR0iTRMyXWGZyMWQZk8A,18573
148
+ fin_infra/recurring/add.py,sha256=bPAVHknGoTLqk3T4vGZw1ROiFGHvASSakafGGmMlL9k,18917
149
149
  fin_infra/recurring/detector.py,sha256=1e6PRoBAT2NxoGAgcVHAWwpPtznkJMaYSrJtvSq0YqM,20154
150
- fin_infra/recurring/detectors_llm.py,sha256=LGNj_uMK8bQGtKnmDsjWDr11WJM331lvfEZ4ZdBD67c,11504
150
+ fin_infra/recurring/detectors_llm.py,sha256=jCW7xXaFRISnsCYDgvyiovXThNceVK_ypWYEawUfE78,11534
151
151
  fin_infra/recurring/ease.py,sha256=OrpxGHi8kt6LkMmww5l0Xy2pU-5hP_dR4IgdOiaIRaU,11179
152
- fin_infra/recurring/insights.py,sha256=J_Gvbv9-pkb0IcjNJYSitejNVQPnKJ1N5L1hTkzbnGA,15886
152
+ fin_infra/recurring/insights.py,sha256=bpZGPZiCgrPK0Nr24QS3jc1F9sCldm6Jd5t-i6wbu7M,15916
153
153
  fin_infra/recurring/models.py,sha256=N4G_LM0xZr3ptHtlqOmcsw3AL2v9g7IX92SmBljkNek,8894
154
154
  fin_infra/recurring/normalizer.py,sha256=Rc1ntIDGir6X-I5lgv49kdLry_zHGJ8cys_Jf3F6Lhk,9761
155
- fin_infra/recurring/normalizers.py,sha256=9kGsbNuxGb4xYUwMqjJZ84m924jT6jWZPQUSXGkqkNU,15928
155
+ fin_infra/recurring/normalizers.py,sha256=9by3lF7EVkCWy_-rLiEKpgw-qJWzEiDOKUMscFx8hmI,15958
156
156
  fin_infra/recurring/summary.py,sha256=1Wte58ZZkEFulkb-nnpwfC5h7C_JrqByy47itdVdWwc,14665
157
157
  fin_infra/scaffold/__init__.py,sha256=OyD8ZtIC4eNTHqD16rbpT8KU0TpZUI6VV4xne4vpaHg,831
158
158
  fin_infra/scaffold/budgets.py,sha256=XXOLlEcyBXVwdbJB__qObRXJ0oe1okwDT_-5tG8c9Yk,9515
@@ -173,8 +173,8 @@ fin_infra/utils/__init__.py,sha256=gKacLSWMAis--pasd8AuVN7ap0e9Z1TjRGur0J23EDo,6
173
173
  fin_infra/utils/http.py,sha256=pvcxbNQ9oisoGPkNe3xX9aAgWzEN6mmdtr1w-L02Xj8,629
174
174
  fin_infra/utils/retry.py,sha256=ISBrup5XCuXqHZh9kjTGvGQYcuyYyqZE4u26wW7r3CM,1030
175
175
  fin_infra/version.py,sha256=4t_crzhrLum--oyowUMxtjBTzUtWp7oRTF22ewEvJG4,49
176
- fin_infra-0.1.64.dist-info/LICENSE,sha256=wK-Ya7Ylxa38dSIZRhvNj1ZVLIrHC-BAI8v38PNADiA,1061
177
- fin_infra-0.1.64.dist-info/METADATA,sha256=e3BndWoe-yekpgp-__h85zwt-_f5PMLj3inQ4Y-oS_8,10218
178
- fin_infra-0.1.64.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
179
- fin_infra-0.1.64.dist-info/entry_points.txt,sha256=Sr1uikvALZMeKm-DIkeKG4L9c4SNqysXGO_IRF8_9eU,53
180
- fin_infra-0.1.64.dist-info/RECORD,,
176
+ fin_infra-0.1.65.dist-info/LICENSE,sha256=wK-Ya7Ylxa38dSIZRhvNj1ZVLIrHC-BAI8v38PNADiA,1061
177
+ fin_infra-0.1.65.dist-info/METADATA,sha256=qOIl3r1vojckM7SamBAKGx-kTPqhqfp5Pji27lxINsk,10218
178
+ fin_infra-0.1.65.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
179
+ fin_infra-0.1.65.dist-info/entry_points.txt,sha256=Sr1uikvALZMeKm-DIkeKG4L9c4SNqysXGO_IRF8_9eU,53
180
+ fin_infra-0.1.65.dist-info/RECORD,,