fin-infra 0.1.69__py3-none-any.whl → 0.4.0__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.
Files changed (131) hide show
  1. fin_infra/__init__.py +53 -3
  2. fin_infra/analytics/__init__.py +13 -2
  3. fin_infra/analytics/add.py +24 -24
  4. fin_infra/analytics/cash_flow.py +3 -3
  5. fin_infra/analytics/ease.py +19 -20
  6. fin_infra/analytics/models.py +5 -5
  7. fin_infra/analytics/portfolio.py +18 -18
  8. fin_infra/analytics/projections.py +1 -3
  9. fin_infra/analytics/spending.py +4 -5
  10. fin_infra/banking/__init__.py +27 -28
  11. fin_infra/banking/history.py +12 -13
  12. fin_infra/banking/utils.py +27 -26
  13. fin_infra/brokerage/__init__.py +29 -31
  14. fin_infra/budgets/__init__.py +3 -3
  15. fin_infra/budgets/add.py +16 -17
  16. fin_infra/budgets/alerts.py +4 -4
  17. fin_infra/budgets/ease.py +1 -2
  18. fin_infra/budgets/models.py +1 -2
  19. fin_infra/budgets/templates.py +4 -4
  20. fin_infra/budgets/tracker.py +4 -4
  21. fin_infra/cashflows/__init__.py +3 -3
  22. fin_infra/cashflows/core.py +1 -1
  23. fin_infra/categorization/__init__.py +1 -1
  24. fin_infra/categorization/add.py +2 -3
  25. fin_infra/categorization/ease.py +3 -3
  26. fin_infra/categorization/engine.py +18 -15
  27. fin_infra/categorization/llm_layer.py +13 -10
  28. fin_infra/categorization/models.py +3 -4
  29. fin_infra/categorization/rules.py +2 -4
  30. fin_infra/categorization/taxonomy.py +2 -2
  31. fin_infra/chat/__init__.py +6 -6
  32. fin_infra/chat/planning.py +1 -2
  33. fin_infra/cli/cmds/scaffold_cmds.py +16 -17
  34. fin_infra/clients/__init__.py +23 -1
  35. fin_infra/clients/base.py +1 -1
  36. fin_infra/clients/plaid.py +2 -2
  37. fin_infra/compliance/__init__.py +5 -4
  38. fin_infra/credit/add.py +6 -7
  39. fin_infra/credit/experian/auth.py +2 -2
  40. fin_infra/credit/experian/client.py +1 -1
  41. fin_infra/credit/experian/parser.py +5 -5
  42. fin_infra/credit/experian/provider.py +4 -4
  43. fin_infra/crypto/__init__.py +9 -11
  44. fin_infra/crypto/insights.py +4 -3
  45. fin_infra/documents/add.py +6 -8
  46. fin_infra/documents/analysis.py +9 -9
  47. fin_infra/documents/ease.py +14 -14
  48. fin_infra/documents/models.py +5 -6
  49. fin_infra/documents/ocr.py +7 -7
  50. fin_infra/documents/storage.py +21 -13
  51. fin_infra/exceptions.py +0 -1
  52. fin_infra/goals/__init__.py +8 -8
  53. fin_infra/goals/add.py +36 -36
  54. fin_infra/goals/funding.py +4 -6
  55. fin_infra/goals/management.py +5 -6
  56. fin_infra/goals/milestones.py +7 -8
  57. fin_infra/goals/models.py +9 -13
  58. fin_infra/insights/__init__.py +6 -3
  59. fin_infra/insights/aggregator.py +1 -1
  60. fin_infra/investments/__init__.py +3 -3
  61. fin_infra/investments/add.py +23 -23
  62. fin_infra/investments/ease.py +2 -2
  63. fin_infra/investments/models.py +27 -29
  64. fin_infra/investments/providers/base.py +12 -13
  65. fin_infra/investments/providers/plaid.py +52 -26
  66. fin_infra/investments/providers/snaptrade.py +19 -19
  67. fin_infra/investments/scaffold_templates/README.md +17 -17
  68. fin_infra/markets/__init__.py +7 -5
  69. fin_infra/models/__init__.py +10 -10
  70. fin_infra/models/accounts.py +4 -5
  71. fin_infra/models/brokerage.py +2 -1
  72. fin_infra/models/candle.py +1 -0
  73. fin_infra/models/money.py +1 -0
  74. fin_infra/models/quotes.py +4 -3
  75. fin_infra/models/tax.py +2 -1
  76. fin_infra/models/transactions.py +4 -5
  77. fin_infra/net_worth/__init__.py +8 -1
  78. fin_infra/net_worth/aggregator.py +5 -3
  79. fin_infra/net_worth/calculator.py +1 -1
  80. fin_infra/net_worth/insights.py +7 -8
  81. fin_infra/normalization/__init__.py +4 -4
  82. fin_infra/normalization/currency_converter.py +7 -8
  83. fin_infra/normalization/models.py +9 -10
  84. fin_infra/normalization/providers/exchangerate.py +5 -5
  85. fin_infra/normalization/providers/static_mappings.py +1 -1
  86. fin_infra/normalization/symbol_resolver.py +3 -4
  87. fin_infra/obs/classifier.py +3 -3
  88. fin_infra/providers/banking/plaid_client.py +5 -5
  89. fin_infra/providers/banking/teller_client.py +7 -6
  90. fin_infra/providers/base.py +27 -2
  91. fin_infra/providers/brokerage/alpaca.py +4 -4
  92. fin_infra/providers/market/alphavantage.py +6 -11
  93. fin_infra/providers/market/ccxt_crypto.py +19 -3
  94. fin_infra/providers/market/coingecko.py +5 -6
  95. fin_infra/providers/market/yahoo.py +23 -8
  96. fin_infra/providers/tax/__init__.py +1 -1
  97. fin_infra/providers/tax/irs.py +1 -1
  98. fin_infra/providers/tax/mock.py +5 -5
  99. fin_infra/providers/tax/taxbit.py +1 -1
  100. fin_infra/recurring/__init__.py +6 -6
  101. fin_infra/recurring/add.py +6 -5
  102. fin_infra/recurring/detector.py +7 -7
  103. fin_infra/recurring/detectors_llm.py +10 -10
  104. fin_infra/recurring/ease.py +6 -8
  105. fin_infra/recurring/insights.py +25 -24
  106. fin_infra/recurring/normalizer.py +7 -7
  107. fin_infra/recurring/normalizers.py +31 -30
  108. fin_infra/recurring/summary.py +13 -15
  109. fin_infra/scaffold/budgets.py +9 -9
  110. fin_infra/scaffold/goals.py +9 -9
  111. fin_infra/security/__init__.py +8 -8
  112. fin_infra/security/add.py +1 -2
  113. fin_infra/security/audit.py +6 -7
  114. fin_infra/security/encryption.py +6 -6
  115. fin_infra/security/models.py +7 -7
  116. fin_infra/security/pii_filter.py +16 -16
  117. fin_infra/security/token_store.py +2 -3
  118. fin_infra/settings.py +2 -1
  119. fin_infra/tax/__init__.py +1 -1
  120. fin_infra/tax/add.py +5 -4
  121. fin_infra/tax/tlh.py +10 -10
  122. fin_infra/utils/__init__.py +15 -1
  123. fin_infra/utils/deprecation.py +161 -0
  124. fin_infra/utils/http.py +4 -3
  125. fin_infra/utils/retry.py +2 -1
  126. {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/METADATA +30 -16
  127. fin_infra-0.4.0.dist-info/RECORD +181 -0
  128. fin_infra-0.1.69.dist-info/RECORD +0 -180
  129. {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/LICENSE +0 -0
  130. {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/WHEEL +0 -0
  131. {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/entry_points.txt +0 -0
fin_infra/__init__.py CHANGED
@@ -1,9 +1,45 @@
1
- """fin_infra: Financial infrastructure toolkit.
1
+ """fin_infra: Financial Infrastructure Toolkit.
2
2
 
3
- Public surface is intentionally small at this stage. Import from submodules for
4
- specific domains (clients, models, markets, credit).
3
+ A comprehensive financial infrastructure library providing:
4
+ - Banking integration (Plaid, Teller, MX)
5
+ - Brokerage integration (Alpaca, Interactive Brokers)
6
+ - Market data (stocks, crypto, forex)
7
+ - Credit scores (Experian, Equifax, TransUnion)
8
+ - Financial calculations (NPV, IRR, PMT, FV, PV)
9
+ - Portfolio analytics (returns, allocation, benchmarking)
10
+ - Transaction categorization (rule-based + ML)
11
+ - Budget management and cash flow analysis
12
+ - Net worth tracking and goal management
13
+
14
+ Example:
15
+ from fin_infra.banking import easy_banking
16
+ from fin_infra.markets import easy_market
17
+
18
+ banking = easy_banking()
19
+ market = easy_market()
20
+ quote = market.quote("AAPL")
5
21
  """
6
22
 
23
+ from __future__ import annotations
24
+
25
+ # Core modules - can be imported as `from fin_infra import banking`
26
+ from . import (
27
+ analytics,
28
+ banking,
29
+ brokerage,
30
+ budgets,
31
+ cashflows,
32
+ categorization,
33
+ credit,
34
+ crypto,
35
+ investments,
36
+ markets,
37
+ net_worth,
38
+ recurring,
39
+ tax,
40
+ )
41
+
42
+ # Base exceptions
7
43
  from .exceptions import (
8
44
  FinInfraError,
9
45
  ProviderError,
@@ -14,6 +50,20 @@ from .version import __version__
14
50
 
15
51
  __all__ = [
16
52
  "__version__",
53
+ # Core modules
54
+ "analytics",
55
+ "banking",
56
+ "brokerage",
57
+ "budgets",
58
+ "cashflows",
59
+ "categorization",
60
+ "credit",
61
+ "crypto",
62
+ "investments",
63
+ "markets",
64
+ "net_worth",
65
+ "recurring",
66
+ "tax",
17
67
  # Base errors
18
68
  "FinInfraError",
19
69
  "ProviderError",
@@ -7,6 +7,16 @@ This module provides comprehensive financial analytics capabilities including:
7
7
  - Portfolio analytics (returns, allocation, benchmarking)
8
8
  - Growth projections (net worth forecasting with scenarios)
9
9
 
10
+ Feature Status:
11
+ [OK] STABLE: Core calculation functions (all analytics work with provided data)
12
+ [!] INTEGRATION: Auto-fetching from providers requires setup:
13
+ - Banking provider for transaction data
14
+ - Brokerage provider for investment data
15
+ - Categorization for expense categorization
16
+
17
+ When providers aren't configured, functions accept data directly or return
18
+ sensible placeholder values for testing/development.
19
+
10
20
  Serves multiple use cases:
11
21
  - Personal finance apps (cash flow, savings tracking)
12
22
  - Wealth management platforms (portfolio analytics, projections)
@@ -45,10 +55,11 @@ Dependencies:
45
55
 
46
56
  from __future__ import annotations
47
57
 
48
- # Import actual implementations
49
- from .ease import easy_analytics, AnalyticsEngine
50
58
  from .add import add_analytics
51
59
 
60
+ # Import actual implementations
61
+ from .ease import AnalyticsEngine, easy_analytics
62
+
52
63
  __all__ = [
53
64
  "easy_analytics",
54
65
  "add_analytics",
@@ -7,7 +7,7 @@ MUST use svc-infra dual routers (user_router) - NEVER generic APIRouter.
7
7
  from __future__ import annotations
8
8
 
9
9
  from datetime import datetime
10
- from typing import TYPE_CHECKING, Optional
10
+ from typing import TYPE_CHECKING
11
11
 
12
12
  from fastapi import HTTPException, Query
13
13
  from pydantic import BaseModel, Field
@@ -15,15 +15,15 @@ from pydantic import BaseModel, Field
15
15
  if TYPE_CHECKING:
16
16
  from fastapi import FastAPI
17
17
 
18
- from .ease import easy_analytics, AnalyticsEngine
18
+ from .ease import AnalyticsEngine, easy_analytics
19
19
  from .models import (
20
+ BenchmarkComparison,
20
21
  CashFlowAnalysis,
21
- SavingsRateData,
22
- SpendingInsight,
22
+ GrowthProjection,
23
23
  PersonalizedSpendingAdvice,
24
24
  PortfolioMetrics,
25
- BenchmarkComparison,
26
- GrowthProjection,
25
+ SavingsRateData,
26
+ SpendingInsight,
27
27
  )
28
28
 
29
29
 
@@ -33,15 +33,15 @@ class NetWorthForecastRequest(BaseModel):
33
33
 
34
34
  user_id: str = Field(..., description="User identifier")
35
35
  years: int = Field(default=30, ge=1, le=50, description="Projection years (1-50)")
36
- initial_net_worth: Optional[float] = Field(None, description="Override initial net worth")
37
- annual_contribution: Optional[float] = Field(None, description="Annual savings contribution")
38
- conservative_return: Optional[float] = Field(
36
+ initial_net_worth: float | None = Field(None, description="Override initial net worth")
37
+ annual_contribution: float | None = Field(None, description="Annual savings contribution")
38
+ conservative_return: float | None = Field(
39
39
  None, description="Conservative return rate (e.g., 0.05 = 5%)"
40
40
  )
41
- moderate_return: Optional[float] = Field(
41
+ moderate_return: float | None = Field(
42
42
  None, description="Moderate return rate (e.g., 0.07 = 7%)"
43
43
  )
44
- aggressive_return: Optional[float] = Field(
44
+ aggressive_return: float | None = Field(
45
45
  None, description="Aggressive return rate (e.g., 0.10 = 10%)"
46
46
  )
47
47
 
@@ -49,7 +49,7 @@ class NetWorthForecastRequest(BaseModel):
49
49
  def add_analytics(
50
50
  app: FastAPI,
51
51
  prefix: str = "/analytics",
52
- provider: Optional[AnalyticsEngine] = None,
52
+ provider: AnalyticsEngine | None = None,
53
53
  include_in_schema: bool = True,
54
54
  ) -> AnalyticsEngine:
55
55
  """Add analytics endpoints to FastAPI application.
@@ -124,9 +124,9 @@ def add_analytics(
124
124
  )
125
125
  async def get_cash_flow(
126
126
  user_id: str,
127
- start_date: Optional[datetime] = None,
128
- end_date: Optional[datetime] = None,
129
- period_days: Optional[int] = None,
127
+ start_date: datetime | None = None,
128
+ end_date: datetime | None = None,
129
+ period_days: int | None = None,
130
130
  ) -> CashFlowAnalysis:
131
131
  """
132
132
  Calculate cash flow analysis for a user.
@@ -164,7 +164,7 @@ def add_analytics(
164
164
  )
165
165
  async def get_spending_insights(
166
166
  user_id: str,
167
- period_days: Optional[int] = None,
167
+ period_days: int | None = None,
168
168
  include_trends: bool = True,
169
169
  ) -> SpendingInsight:
170
170
  """
@@ -186,7 +186,7 @@ def add_analytics(
186
186
  )
187
187
  async def get_spending_advice(
188
188
  user_id: str,
189
- period_days: Optional[int] = None,
189
+ period_days: int | None = None,
190
190
  ) -> PersonalizedSpendingAdvice:
191
191
  """
192
192
  Generate personalized spending advice using AI.
@@ -206,11 +206,11 @@ def add_analytics(
206
206
  )
207
207
  async def get_portfolio_metrics(
208
208
  user_id: str,
209
- accounts: Optional[list[str]] = None,
209
+ accounts: list[str] | None = None,
210
210
  with_holdings: bool = Query(
211
211
  False, description="Use real holdings data from investment provider for accurate P/L"
212
212
  ),
213
- access_token: Optional[str] = Query(
213
+ access_token: str | None = Query(
214
214
  None, description="Investment provider access token (required if with_holdings=true)"
215
215
  ),
216
216
  ) -> PortfolioMetrics:
@@ -239,9 +239,9 @@ def add_analytics(
239
239
 
240
240
  Note:
241
241
  Real holdings provide:
242
- - Accurate cost basis real profit/loss
243
- - Security types precise asset allocation
244
- - Current values live portfolio tracking
242
+ - Accurate cost basis -> real profit/loss
243
+ - Security types -> precise asset allocation
244
+ - Current values -> live portfolio tracking
245
245
  """
246
246
  # If with_holdings requested and investment provider available
247
247
  if with_holdings:
@@ -286,9 +286,9 @@ def add_analytics(
286
286
  )
287
287
  async def get_benchmark_comparison(
288
288
  user_id: str,
289
- benchmark: Optional[str] = None,
289
+ benchmark: str | None = None,
290
290
  period: str = "1y",
291
- accounts: Optional[list[str]] = None,
291
+ accounts: list[str] | None = None,
292
292
  ) -> BenchmarkComparison:
293
293
  """
294
294
  Compare portfolio to benchmark (e.g., SPY, VTI).
@@ -7,7 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  from datetime import datetime, timedelta
9
9
  from decimal import Decimal
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  from ..models import Transaction
13
13
  from .models import CashFlowAnalysis
@@ -17,7 +17,7 @@ async def calculate_cash_flow(
17
17
  user_id: str,
18
18
  start_date: str | datetime,
19
19
  end_date: str | datetime,
20
- accounts: Optional[list[str]] = None,
20
+ accounts: list[str] | None = None,
21
21
  *,
22
22
  banking_provider=None,
23
23
  categorization_provider=None,
@@ -117,7 +117,7 @@ async def calculate_cash_flow(
117
117
  async def forecast_cash_flow(
118
118
  user_id: str,
119
119
  months: int = 6,
120
- assumptions: Optional[dict[str, Any]] = None,
120
+ assumptions: dict[str, Any] | None = None,
121
121
  *,
122
122
  recurring_provider=None,
123
123
  ) -> list[CashFlowAnalysis]:
@@ -16,23 +16,22 @@ Typical usage:
16
16
  portfolio = await analytics.portfolio_metrics(user_id="user123")
17
17
  """
18
18
 
19
- from typing import Optional
20
19
  from datetime import datetime, timedelta
21
20
 
22
21
  from .cash_flow import calculate_cash_flow
23
- from .savings import calculate_savings_rate, SavingsDefinition
24
- from .spending import analyze_spending, generate_spending_insights
25
- from .portfolio import calculate_portfolio_metrics, compare_to_benchmark
26
- from .projections import project_net_worth, calculate_compound_interest
27
22
  from .models import (
23
+ BenchmarkComparison,
28
24
  CashFlowAnalysis,
29
- SavingsRateData,
30
- SpendingInsight,
25
+ GrowthProjection,
31
26
  PersonalizedSpendingAdvice,
32
27
  PortfolioMetrics,
33
- BenchmarkComparison,
34
- GrowthProjection,
28
+ SavingsRateData,
29
+ SpendingInsight,
35
30
  )
31
+ from .portfolio import calculate_portfolio_metrics, compare_to_benchmark
32
+ from .projections import calculate_compound_interest, project_net_worth
33
+ from .savings import SavingsDefinition, calculate_savings_rate
34
+ from .spending import analyze_spending, generate_spending_insights
36
35
 
37
36
 
38
37
  class AnalyticsEngine:
@@ -93,9 +92,9 @@ class AnalyticsEngine:
93
92
  self,
94
93
  user_id: str,
95
94
  *,
96
- start_date: Optional[datetime] = None,
97
- end_date: Optional[datetime] = None,
98
- period_days: Optional[int] = None,
95
+ start_date: datetime | None = None,
96
+ end_date: datetime | None = None,
97
+ period_days: int | None = None,
99
98
  ) -> CashFlowAnalysis:
100
99
  """Analyze cash flow (income vs expenses).
101
100
 
@@ -129,7 +128,7 @@ class AnalyticsEngine:
129
128
  self,
130
129
  user_id: str,
131
130
  *,
132
- definition: Optional[str | SavingsDefinition] = None,
131
+ definition: str | SavingsDefinition | None = None,
133
132
  period: str = "monthly",
134
133
  ) -> SavingsRateData:
135
134
  """Calculate savings rate.
@@ -163,7 +162,7 @@ class AnalyticsEngine:
163
162
  self,
164
163
  user_id: str,
165
164
  *,
166
- period_days: Optional[int] = None,
165
+ period_days: int | None = None,
167
166
  include_trends: bool = True,
168
167
  ) -> SpendingInsight:
169
168
  """Analyze spending patterns and generate insights.
@@ -193,8 +192,8 @@ class AnalyticsEngine:
193
192
  self,
194
193
  user_id: str,
195
194
  *,
196
- period_days: Optional[int] = None,
197
- user_context: Optional[dict] = None,
195
+ period_days: int | None = None,
196
+ user_context: dict | None = None,
198
197
  ) -> PersonalizedSpendingAdvice:
199
198
  """Generate AI-powered personalized spending advice.
200
199
 
@@ -228,7 +227,7 @@ class AnalyticsEngine:
228
227
  self,
229
228
  user_id: str,
230
229
  *,
231
- accounts: Optional[list[str]] = None,
230
+ accounts: list[str] | None = None,
232
231
  ) -> PortfolioMetrics:
233
232
  """Calculate portfolio performance metrics.
234
233
 
@@ -250,9 +249,9 @@ class AnalyticsEngine:
250
249
  self,
251
250
  user_id: str,
252
251
  *,
253
- benchmark: Optional[str] = None,
252
+ benchmark: str | None = None,
254
253
  period: str = "1y",
255
- accounts: Optional[list[str]] = None,
254
+ accounts: list[str] | None = None,
256
255
  ) -> BenchmarkComparison:
257
256
  """Compare portfolio to benchmark index.
258
257
 
@@ -282,7 +281,7 @@ class AnalyticsEngine:
282
281
  user_id: str,
283
282
  *,
284
283
  years: int = 30,
285
- assumptions: Optional[dict] = None,
284
+ assumptions: dict | None = None,
286
285
  ) -> GrowthProjection:
287
286
  """Project net worth growth with scenarios.
288
287
 
@@ -7,7 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  from datetime import datetime
9
9
  from enum import Enum
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  from pydantic import BaseModel, ConfigDict, Field
13
13
 
@@ -72,7 +72,7 @@ class SavingsRateData(BaseModel):
72
72
  expenses: float = Field(..., description="Total expenses for period")
73
73
  period: Period = Field(..., description="Period type")
74
74
  definition: SavingsDefinition = Field(..., description="Calculation method used")
75
- trend: Optional[TrendDirection] = Field(None, description="Trend over time")
75
+ trend: TrendDirection | None = Field(None, description="Trend over time")
76
76
 
77
77
 
78
78
  class SpendingAnomaly(BaseModel):
@@ -132,7 +132,7 @@ class PersonalizedSpendingAdvice(BaseModel):
132
132
  alerts: list[str] = Field(
133
133
  default_factory=list, description="Urgent spending issues requiring attention"
134
134
  )
135
- estimated_monthly_savings: Optional[float] = Field(
135
+ estimated_monthly_savings: float | None = Field(
136
136
  None, description="Potential monthly savings if recommendations followed"
137
137
  )
138
138
 
@@ -183,7 +183,7 @@ class BenchmarkComparison(BaseModel):
183
183
  benchmark_return_percent: float = Field(..., description="Benchmark return percentage")
184
184
  benchmark_symbol: str = Field(..., description="Benchmark ticker (e.g., SPY)")
185
185
  alpha: float = Field(..., description="Portfolio alpha (excess return)")
186
- beta: Optional[float] = Field(None, description="Portfolio beta (volatility vs benchmark)")
186
+ beta: float | None = Field(None, description="Portfolio beta (volatility vs benchmark)")
187
187
  period: str = Field(..., description="Comparison period (1y, 3y, 5y, etc.)")
188
188
 
189
189
 
@@ -213,6 +213,6 @@ class GrowthProjection(BaseModel):
213
213
  assumptions: dict[str, Any] = Field(
214
214
  default_factory=dict, description="Assumptions used (inflation, returns, etc.)"
215
215
  )
216
- confidence_intervals: Optional[dict[str, tuple[float, float]]] = Field(
216
+ confidence_intervals: dict[str, tuple[float, float]] | None = Field(
217
217
  None, description="95% confidence intervals by scenario"
218
218
  )
@@ -35,7 +35,6 @@ Examples:
35
35
  """
36
36
 
37
37
  from datetime import datetime
38
- from typing import Optional
39
38
 
40
39
  from fin_infra.analytics.models import (
41
40
  AssetAllocation,
@@ -47,7 +46,7 @@ from fin_infra.analytics.models import (
47
46
  async def calculate_portfolio_metrics(
48
47
  user_id: str,
49
48
  *,
50
- accounts: Optional[list[str]] = None,
49
+ accounts: list[str] | None = None,
51
50
  brokerage_provider=None,
52
51
  market_provider=None,
53
52
  ) -> PortfolioMetrics:
@@ -131,7 +130,7 @@ async def compare_to_benchmark(
131
130
  *,
132
131
  benchmark: str = "SPY",
133
132
  period: str = "1y",
134
- accounts: Optional[list[str]] = None,
133
+ accounts: list[str] | None = None,
135
134
  brokerage_provider=None,
136
135
  market_provider=None,
137
136
  ) -> BenchmarkComparison:
@@ -221,7 +220,7 @@ async def compare_to_benchmark(
221
220
 
222
221
  def _generate_mock_holdings(
223
222
  user_id: str,
224
- accounts: Optional[list[str]] = None,
223
+ accounts: list[str] | None = None,
225
224
  ) -> list[dict]:
226
225
  """Generate mock portfolio holdings for testing.
227
226
 
@@ -415,7 +414,7 @@ def _parse_benchmark_period(period: str) -> int:
415
414
  def _calculate_portfolio_return(
416
415
  user_id: str,
417
416
  period_days: int,
418
- accounts: Optional[list[str]] = None,
417
+ accounts: list[str] | None = None,
419
418
  ) -> tuple[float, float]:
420
419
  """Calculate portfolio return for specified period.
421
420
 
@@ -483,7 +482,7 @@ def _calculate_beta(
483
482
  user_id: str,
484
483
  benchmark: str,
485
484
  period_days: int,
486
- ) -> Optional[float]:
485
+ ) -> float | None:
487
486
  """Calculate portfolio beta (volatility relative to benchmark).
488
487
 
489
488
  Beta = Covariance(portfolio_returns, benchmark_returns) / Variance(benchmark_returns)
@@ -528,10 +527,10 @@ def portfolio_metrics_with_holdings(holdings: list) -> PortfolioMetrics:
528
527
  PortfolioMetrics with real portfolio analysis
529
528
 
530
529
  Real Data Advantages:
531
- - Actual cost basis accurate P/L calculations
532
- - Real security types precise asset allocation
533
- - Current market values live portfolio value
534
- - No mock data production-ready analytics
530
+ - Actual cost basis -> accurate P/L calculations
531
+ - Real security types -> precise asset allocation
532
+ - Current market values -> live portfolio value
533
+ - No mock data -> production-ready analytics
535
534
 
536
535
  Limitations:
537
536
  - Day/YTD/MTD returns require historical snapshots (not in holdings)
@@ -653,7 +652,7 @@ def calculate_day_change_with_snapshot(
653
652
  Function matches holdings by account_id + security_id for accurate tracking
654
653
  of individual position changes (accounts for buys/sells, not just price moves).
655
654
  """
656
- # Build lookup map for previous snapshot: (account_id, security_id) value
655
+ # Build lookup map for previous snapshot: (account_id, security_id) -> value
657
656
  previous_map = {}
658
657
  for holding in previous_snapshot:
659
658
  key = (holding.account_id, holding.security.security_id)
@@ -704,15 +703,16 @@ def _calculate_allocation_from_holdings(
704
703
  list[AssetAllocation] with asset_class, value, and percentage
705
704
 
706
705
  Asset Class Mapping:
707
- - equity Stocks
708
- - etf Stocks (equity ETFs grouped with stocks)
709
- - mutual_fund Bonds (conservative assumption)
710
- - bond Bonds
711
- - cash Cash
712
- - derivative Other
713
- - other Other
706
+ - equity -> Stocks
707
+ - etf -> Stocks (equity ETFs grouped with stocks)
708
+ - mutual_fund -> Bonds (conservative assumption)
709
+ - bond -> Bonds
710
+ - cash -> Cash
711
+ - derivative -> Other
712
+ - other -> Other
714
713
  """
715
714
  from collections import defaultdict
715
+
716
716
  from .models import AssetAllocation
717
717
 
718
718
  if total_value == 0:
@@ -19,12 +19,10 @@ Typical usage:
19
19
  )
20
20
  """
21
21
 
22
- from typing import Optional
23
22
  import math
24
23
 
25
24
  from fin_infra.analytics.models import GrowthProjection, Scenario
26
25
 
27
-
28
26
  # ============================================================================
29
27
  # Public API
30
28
  # ============================================================================
@@ -34,7 +32,7 @@ async def project_net_worth(
34
32
  user_id: str,
35
33
  *,
36
34
  years: int = 30,
37
- assumptions: Optional[dict] = None,
35
+ assumptions: dict | None = None,
38
36
  net_worth_provider=None,
39
37
  cash_flow_provider=None,
40
38
  ) -> GrowthProjection:
@@ -45,7 +45,6 @@ Examples:
45
45
  from collections import defaultdict
46
46
  from datetime import timedelta
47
47
  from decimal import Decimal
48
- from typing import Optional
49
48
 
50
49
  from fin_infra.analytics.models import (
51
50
  PersonalizedSpendingAdvice,
@@ -60,7 +59,7 @@ async def analyze_spending(
60
59
  user_id: str,
61
60
  *,
62
61
  period: str = "30d",
63
- categories: Optional[list[str]] = None,
62
+ categories: list[str] | None = None,
64
63
  banking_provider=None,
65
64
  categorization_provider=None,
66
65
  ) -> SpendingInsight:
@@ -426,7 +425,7 @@ def _generate_mock_transactions(days: int) -> list[Transaction]:
426
425
  async def generate_spending_insights(
427
426
  spending_insight: SpendingInsight,
428
427
  *,
429
- user_context: Optional[dict] = None,
428
+ user_context: dict | None = None,
430
429
  llm_provider=None,
431
430
  ) -> "PersonalizedSpendingAdvice":
432
431
  """Generate personalized spending insights using LLM.
@@ -521,7 +520,7 @@ async def generate_spending_insights(
521
520
 
522
521
  def _build_spending_insights_prompt(
523
522
  spending_insight: SpendingInsight,
524
- user_context: Optional[dict] = None,
523
+ user_context: dict | None = None,
525
524
  ) -> str:
526
525
  """Build LLM prompt with financial context.
527
526
 
@@ -621,7 +620,7 @@ Be specific, encouraging, and actionable. Focus on realistic savings, not extrem
621
620
 
622
621
  def _generate_rule_based_insights(
623
622
  spending_insight: SpendingInsight,
624
- user_context: Optional[dict] = None,
623
+ user_context: dict | None = None,
625
624
  ) -> "PersonalizedSpendingAdvice":
626
625
  """Generate rule-based insights when LLM is unavailable.
627
626