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.
- fin_infra/__init__.py +53 -3
- fin_infra/analytics/__init__.py +13 -2
- fin_infra/analytics/add.py +24 -24
- fin_infra/analytics/cash_flow.py +3 -3
- fin_infra/analytics/ease.py +19 -20
- fin_infra/analytics/models.py +5 -5
- fin_infra/analytics/portfolio.py +18 -18
- fin_infra/analytics/projections.py +1 -3
- fin_infra/analytics/spending.py +4 -5
- fin_infra/banking/__init__.py +27 -28
- fin_infra/banking/history.py +12 -13
- fin_infra/banking/utils.py +27 -26
- fin_infra/brokerage/__init__.py +29 -31
- fin_infra/budgets/__init__.py +3 -3
- fin_infra/budgets/add.py +16 -17
- fin_infra/budgets/alerts.py +4 -4
- fin_infra/budgets/ease.py +1 -2
- fin_infra/budgets/models.py +1 -2
- fin_infra/budgets/templates.py +4 -4
- fin_infra/budgets/tracker.py +4 -4
- fin_infra/cashflows/__init__.py +3 -3
- fin_infra/cashflows/core.py +1 -1
- fin_infra/categorization/__init__.py +1 -1
- fin_infra/categorization/add.py +2 -3
- fin_infra/categorization/ease.py +3 -3
- fin_infra/categorization/engine.py +18 -15
- fin_infra/categorization/llm_layer.py +13 -10
- fin_infra/categorization/models.py +3 -4
- fin_infra/categorization/rules.py +2 -4
- fin_infra/categorization/taxonomy.py +2 -2
- fin_infra/chat/__init__.py +6 -6
- fin_infra/chat/planning.py +1 -2
- fin_infra/cli/cmds/scaffold_cmds.py +16 -17
- fin_infra/clients/__init__.py +23 -1
- fin_infra/clients/base.py +1 -1
- fin_infra/clients/plaid.py +2 -2
- fin_infra/compliance/__init__.py +5 -4
- fin_infra/credit/add.py +6 -7
- fin_infra/credit/experian/auth.py +2 -2
- fin_infra/credit/experian/client.py +1 -1
- fin_infra/credit/experian/parser.py +5 -5
- fin_infra/credit/experian/provider.py +4 -4
- fin_infra/crypto/__init__.py +9 -11
- fin_infra/crypto/insights.py +4 -3
- fin_infra/documents/add.py +6 -8
- fin_infra/documents/analysis.py +9 -9
- fin_infra/documents/ease.py +14 -14
- fin_infra/documents/models.py +5 -6
- fin_infra/documents/ocr.py +7 -7
- fin_infra/documents/storage.py +21 -13
- fin_infra/exceptions.py +0 -1
- fin_infra/goals/__init__.py +8 -8
- fin_infra/goals/add.py +36 -36
- fin_infra/goals/funding.py +4 -6
- fin_infra/goals/management.py +5 -6
- fin_infra/goals/milestones.py +7 -8
- fin_infra/goals/models.py +9 -13
- fin_infra/insights/__init__.py +6 -3
- fin_infra/insights/aggregator.py +1 -1
- fin_infra/investments/__init__.py +3 -3
- fin_infra/investments/add.py +23 -23
- fin_infra/investments/ease.py +2 -2
- fin_infra/investments/models.py +27 -29
- fin_infra/investments/providers/base.py +12 -13
- fin_infra/investments/providers/plaid.py +52 -26
- fin_infra/investments/providers/snaptrade.py +19 -19
- fin_infra/investments/scaffold_templates/README.md +17 -17
- fin_infra/markets/__init__.py +7 -5
- fin_infra/models/__init__.py +10 -10
- fin_infra/models/accounts.py +4 -5
- fin_infra/models/brokerage.py +2 -1
- fin_infra/models/candle.py +1 -0
- fin_infra/models/money.py +1 -0
- fin_infra/models/quotes.py +4 -3
- fin_infra/models/tax.py +2 -1
- fin_infra/models/transactions.py +4 -5
- fin_infra/net_worth/__init__.py +8 -1
- fin_infra/net_worth/aggregator.py +5 -3
- fin_infra/net_worth/calculator.py +1 -1
- fin_infra/net_worth/insights.py +7 -8
- fin_infra/normalization/__init__.py +4 -4
- fin_infra/normalization/currency_converter.py +7 -8
- fin_infra/normalization/models.py +9 -10
- fin_infra/normalization/providers/exchangerate.py +5 -5
- fin_infra/normalization/providers/static_mappings.py +1 -1
- fin_infra/normalization/symbol_resolver.py +3 -4
- fin_infra/obs/classifier.py +3 -3
- fin_infra/providers/banking/plaid_client.py +5 -5
- fin_infra/providers/banking/teller_client.py +7 -6
- fin_infra/providers/base.py +27 -2
- fin_infra/providers/brokerage/alpaca.py +4 -4
- fin_infra/providers/market/alphavantage.py +6 -11
- fin_infra/providers/market/ccxt_crypto.py +19 -3
- fin_infra/providers/market/coingecko.py +5 -6
- fin_infra/providers/market/yahoo.py +23 -8
- fin_infra/providers/tax/__init__.py +1 -1
- fin_infra/providers/tax/irs.py +1 -1
- fin_infra/providers/tax/mock.py +5 -5
- fin_infra/providers/tax/taxbit.py +1 -1
- fin_infra/recurring/__init__.py +6 -6
- fin_infra/recurring/add.py +6 -5
- fin_infra/recurring/detector.py +7 -7
- fin_infra/recurring/detectors_llm.py +10 -10
- fin_infra/recurring/ease.py +6 -8
- fin_infra/recurring/insights.py +25 -24
- fin_infra/recurring/normalizer.py +7 -7
- fin_infra/recurring/normalizers.py +31 -30
- fin_infra/recurring/summary.py +13 -15
- fin_infra/scaffold/budgets.py +9 -9
- fin_infra/scaffold/goals.py +9 -9
- fin_infra/security/__init__.py +8 -8
- fin_infra/security/add.py +1 -2
- fin_infra/security/audit.py +6 -7
- fin_infra/security/encryption.py +6 -6
- fin_infra/security/models.py +7 -7
- fin_infra/security/pii_filter.py +16 -16
- fin_infra/security/token_store.py +2 -3
- fin_infra/settings.py +2 -1
- fin_infra/tax/__init__.py +1 -1
- fin_infra/tax/add.py +5 -4
- fin_infra/tax/tlh.py +10 -10
- fin_infra/utils/__init__.py +15 -1
- fin_infra/utils/deprecation.py +161 -0
- fin_infra/utils/http.py +4 -3
- fin_infra/utils/retry.py +2 -1
- {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/METADATA +30 -16
- fin_infra-0.4.0.dist-info/RECORD +181 -0
- fin_infra-0.1.69.dist-info/RECORD +0 -180
- {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/LICENSE +0 -0
- {fin_infra-0.1.69.dist-info → fin_infra-0.4.0.dist-info}/WHEEL +0 -0
- {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
|
|
1
|
+
"""fin_infra: Financial Infrastructure Toolkit.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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",
|
fin_infra/analytics/__init__.py
CHANGED
|
@@ -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",
|
fin_infra/analytics/add.py
CHANGED
|
@@ -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
|
|
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
|
|
18
|
+
from .ease import AnalyticsEngine, easy_analytics
|
|
19
19
|
from .models import (
|
|
20
|
+
BenchmarkComparison,
|
|
20
21
|
CashFlowAnalysis,
|
|
21
|
-
|
|
22
|
-
SpendingInsight,
|
|
22
|
+
GrowthProjection,
|
|
23
23
|
PersonalizedSpendingAdvice,
|
|
24
24
|
PortfolioMetrics,
|
|
25
|
-
|
|
26
|
-
|
|
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:
|
|
37
|
-
annual_contribution:
|
|
38
|
-
conservative_return:
|
|
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:
|
|
41
|
+
moderate_return: float | None = Field(
|
|
42
42
|
None, description="Moderate return rate (e.g., 0.07 = 7%)"
|
|
43
43
|
)
|
|
44
|
-
aggressive_return:
|
|
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:
|
|
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:
|
|
128
|
-
end_date:
|
|
129
|
-
period_days:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
243
|
-
- Security types
|
|
244
|
-
- Current values
|
|
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:
|
|
289
|
+
benchmark: str | None = None,
|
|
290
290
|
period: str = "1y",
|
|
291
|
-
accounts:
|
|
291
|
+
accounts: list[str] | None = None,
|
|
292
292
|
) -> BenchmarkComparison:
|
|
293
293
|
"""
|
|
294
294
|
Compare portfolio to benchmark (e.g., SPY, VTI).
|
fin_infra/analytics/cash_flow.py
CHANGED
|
@@ -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
|
|
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:
|
|
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:
|
|
120
|
+
assumptions: dict[str, Any] | None = None,
|
|
121
121
|
*,
|
|
122
122
|
recurring_provider=None,
|
|
123
123
|
) -> list[CashFlowAnalysis]:
|
fin_infra/analytics/ease.py
CHANGED
|
@@ -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
|
-
|
|
30
|
-
SpendingInsight,
|
|
25
|
+
GrowthProjection,
|
|
31
26
|
PersonalizedSpendingAdvice,
|
|
32
27
|
PortfolioMetrics,
|
|
33
|
-
|
|
34
|
-
|
|
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:
|
|
97
|
-
end_date:
|
|
98
|
-
period_days:
|
|
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:
|
|
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:
|
|
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:
|
|
197
|
-
user_context:
|
|
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:
|
|
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:
|
|
252
|
+
benchmark: str | None = None,
|
|
254
253
|
period: str = "1y",
|
|
255
|
-
accounts:
|
|
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:
|
|
284
|
+
assumptions: dict | None = None,
|
|
286
285
|
) -> GrowthProjection:
|
|
287
286
|
"""Project net worth growth with scenarios.
|
|
288
287
|
|
fin_infra/analytics/models.py
CHANGED
|
@@ -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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
216
|
+
confidence_intervals: dict[str, tuple[float, float]] | None = Field(
|
|
217
217
|
None, description="95% confidence intervals by scenario"
|
|
218
218
|
)
|
fin_infra/analytics/portfolio.py
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
) ->
|
|
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
|
|
532
|
-
- Real security types
|
|
533
|
-
- Current market values
|
|
534
|
-
- No mock data
|
|
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)
|
|
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
|
|
708
|
-
- etf
|
|
709
|
-
- mutual_fund
|
|
710
|
-
- bond
|
|
711
|
-
- cash
|
|
712
|
-
- derivative
|
|
713
|
-
- 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:
|
|
35
|
+
assumptions: dict | None = None,
|
|
38
36
|
net_worth_provider=None,
|
|
39
37
|
cash_flow_provider=None,
|
|
40
38
|
) -> GrowthProjection:
|
fin_infra/analytics/spending.py
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
623
|
+
user_context: dict | None = None,
|
|
625
624
|
) -> "PersonalizedSpendingAdvice":
|
|
626
625
|
"""Generate rule-based insights when LLM is unavailable.
|
|
627
626
|
|