fin-infra 0.1.60__tar.gz → 0.1.62__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.
- {fin_infra-0.1.60 → fin_infra-0.1.62}/PKG-INFO +2 -1
- {fin_infra-0.1.60 → fin_infra-0.1.62}/pyproject.toml +2 -1
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/models.py +1 -1
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/spending.py +5 -5
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/__init__.py +4 -4
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/categorization/llm_layer.py +1 -1
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/chat/__init__.py +3 -3
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/chat/ease.py +8 -8
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/chat/planning.py +7 -7
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/crypto/insights.py +12 -12
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/documents/analysis.py +5 -5
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/management.py +6 -6
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/models.py +6 -6
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/ease.py +3 -3
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/insights.py +7 -7
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/models.py +14 -14
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/obs/classifier.py +4 -2
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/brokerage/alpaca.py +1 -1
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/detectors_llm.py +10 -10
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/insights.py +10 -10
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/normalizers.py +10 -10
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/security/token_store.py +6 -2
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/tax/add.py +1 -1
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/tax/tlh.py +5 -5
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/utils/retry.py +1 -1
- {fin_infra-0.1.60 → fin_infra-0.1.62}/LICENSE +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/README.md +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/__main__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/cash_flow.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/ease.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/portfolio.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/projections.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/rebalancing.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/savings.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/analytics/scenarios.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/banking/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/banking/history.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/banking/utils.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/brokerage/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/alerts.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/ease.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/models.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/templates.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/budgets/tracker.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/cashflows/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/cashflows/core.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/categorization/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/categorization/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/categorization/ease.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/categorization/engine.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/categorization/models.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/categorization/rules.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/categorization/taxonomy.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/cli/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/cli/cmds/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/cli/cmds/scaffold_cmds.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/clients/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/clients/base.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/clients/plaid.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/compliance/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/credit/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/credit/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/credit/experian/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/credit/experian/auth.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/credit/experian/client.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/credit/experian/parser.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/credit/experian/provider.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/credit/mock.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/crypto/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/documents/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/documents/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/documents/ease.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/documents/models.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/documents/ocr.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/documents/storage.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/exceptions.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/funding.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/milestones.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/models.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/insights/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/insights/aggregator.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/insights/models.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/ease.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/providers/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/providers/base.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/providers/plaid.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/providers/snaptrade.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/markets/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/accounts.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/brokerage.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/candle.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/credit.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/money.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/quotes.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/tax.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/models/transactions.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/aggregator.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/calculator.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/goals.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/normalization/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/normalization/currency_converter.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/normalization/models.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/normalization/providers/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/normalization/providers/exchangerate.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/normalization/providers/static_mappings.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/normalization/symbol_resolver.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/obs/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/banking/base.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/banking/plaid_client.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/banking/teller_client.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/base.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/brokerage/base.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/credit/experian.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/market/alphavantage.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/market/base.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/market/ccxt_crypto.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/market/coingecko.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/market/yahoo.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/registry.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/tax/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/tax/irs.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/tax/mock.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/providers/tax/taxbit.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/py.typed +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/detector.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/ease.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/models.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/normalizer.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/recurring/summary.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/scaffold/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/scaffold/budgets.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/scaffold/goals.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/security/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/security/add.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/security/audit.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/security/encryption.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/security/models.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/security/pii_filter.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/security/pii_patterns.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/settings.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/tax/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/utils/__init__.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/utils/http.py +0 -0
- {fin_infra-0.1.60 → fin_infra-0.1.62}/src/fin_infra/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: fin-infra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.62
|
|
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
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
19
19
|
Classifier: Topic :: Office/Business :: Financial
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
21
|
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Dist: ai-infra (>=0.1.142)
|
|
22
23
|
Requires-Dist: cashews[redis] (>=7.0)
|
|
23
24
|
Requires-Dist: ccxt (>=4.0.0)
|
|
24
25
|
Requires-Dist: httpx (>=0.25.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "fin-infra"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.62"
|
|
4
4
|
description = "Financial infrastructure toolkit: banking connections, market data, credit, cashflows, and brokerage integrations"
|
|
5
5
|
authors = ["Ali Khatami <aliikhatami94@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -55,6 +55,7 @@ svc-infra = ">=0.1.0" # Published version for CI/production
|
|
|
55
55
|
yahooquery = ">=2.3.0"
|
|
56
56
|
ccxt = ">=4.0.0"
|
|
57
57
|
plaid-python = ">=25.0.0"
|
|
58
|
+
ai-infra = ">=0.1.142"
|
|
58
59
|
|
|
59
60
|
[tool.poetry.group.dev.dependencies]
|
|
60
61
|
pytest = ">=8.0.0"
|
|
@@ -114,7 +114,7 @@ class SpendingInsight(BaseModel):
|
|
|
114
114
|
class PersonalizedSpendingAdvice(BaseModel):
|
|
115
115
|
"""LLM-generated personalized spending advice.
|
|
116
116
|
|
|
117
|
-
Uses ai-infra
|
|
117
|
+
Uses ai-infra LLM for structured output generation.
|
|
118
118
|
"""
|
|
119
119
|
|
|
120
120
|
model_config = ConfigDict(extra="forbid")
|
|
@@ -12,7 +12,7 @@ Generic Applicability:
|
|
|
12
12
|
|
|
13
13
|
Features:
|
|
14
14
|
- Statistical analysis: Top merchants, category breakdowns, trends, anomalies
|
|
15
|
-
- LLM-powered insights: Personalized recommendations using ai-infra
|
|
15
|
+
- LLM-powered insights: Personalized recommendations using ai-infra LLM
|
|
16
16
|
- Graceful degradation: Falls back to rule-based insights if LLM unavailable
|
|
17
17
|
- Cost-effective: Structured output for predictable token usage (<$0.01/insight)
|
|
18
18
|
|
|
@@ -427,12 +427,12 @@ async def generate_spending_insights(
|
|
|
427
427
|
) -> "PersonalizedSpendingAdvice":
|
|
428
428
|
"""Generate personalized spending insights using LLM.
|
|
429
429
|
|
|
430
|
-
Uses ai-infra
|
|
430
|
+
Uses ai-infra LLM for structured output generation with financial context.
|
|
431
431
|
|
|
432
432
|
Args:
|
|
433
433
|
spending_insight: Analyzed spending data from analyze_spending()
|
|
434
434
|
user_context: Optional context (income, goals, budget, preferences)
|
|
435
|
-
llm_provider: Optional
|
|
435
|
+
llm_provider: Optional LLM instance (defaults to Google Gemini)
|
|
436
436
|
|
|
437
437
|
Returns:
|
|
438
438
|
PersonalizedSpendingAdvice with LLM-generated recommendations
|
|
@@ -468,14 +468,14 @@ async def generate_spending_insights(
|
|
|
468
468
|
|
|
469
469
|
# Try to import ai-infra LLM (optional dependency)
|
|
470
470
|
try:
|
|
471
|
-
from ai_infra.llm import
|
|
471
|
+
from ai_infra.llm import LLM
|
|
472
472
|
except ImportError:
|
|
473
473
|
# Graceful degradation: return rule-based insights
|
|
474
474
|
return _generate_rule_based_insights(spending_insight, user_context)
|
|
475
475
|
|
|
476
476
|
# Initialize LLM if not provided
|
|
477
477
|
if llm_provider is None:
|
|
478
|
-
llm_provider =
|
|
478
|
+
llm_provider = LLM()
|
|
479
479
|
|
|
480
480
|
# Build financial context prompt
|
|
481
481
|
prompt = _build_spending_insights_prompt(spending_insight, user_context)
|
|
@@ -87,11 +87,11 @@ __all__ = [
|
|
|
87
87
|
def __getattr__(name: str):
|
|
88
88
|
"""Lazy import for budgets module components."""
|
|
89
89
|
if name == "easy_budgets":
|
|
90
|
-
from fin_infra.budgets.ease import easy_budgets
|
|
90
|
+
from fin_infra.budgets.ease import easy_budgets
|
|
91
91
|
|
|
92
92
|
return easy_budgets
|
|
93
93
|
elif name == "add_budgets":
|
|
94
|
-
from fin_infra.budgets.add import add_budgets
|
|
94
|
+
from fin_infra.budgets.add import add_budgets
|
|
95
95
|
|
|
96
96
|
return add_budgets
|
|
97
97
|
elif name in (
|
|
@@ -119,11 +119,11 @@ def __getattr__(name: str):
|
|
|
119
119
|
|
|
120
120
|
return BudgetTracker
|
|
121
121
|
elif name == "check_budget_alerts":
|
|
122
|
-
from fin_infra.budgets.alerts import check_budget_alerts
|
|
122
|
+
from fin_infra.budgets.alerts import check_budget_alerts
|
|
123
123
|
|
|
124
124
|
return check_budget_alerts
|
|
125
125
|
elif name == "apply_template":
|
|
126
|
-
from fin_infra.budgets.templates import apply_template
|
|
126
|
+
from fin_infra.budgets.templates import apply_template
|
|
127
127
|
|
|
128
128
|
return apply_template
|
|
129
129
|
|
|
@@ -20,7 +20,7 @@ from pydantic import BaseModel, Field
|
|
|
20
20
|
|
|
21
21
|
# ai-infra imports
|
|
22
22
|
try:
|
|
23
|
-
from ai_infra.llm import
|
|
23
|
+
from ai_infra.llm import LLM
|
|
24
24
|
from ai_infra.llm.providers import Providers
|
|
25
25
|
except ImportError:
|
|
26
26
|
raise ImportError("ai-infra not installed. Install with: pip install ai-infra")
|
|
@@ -10,10 +10,10 @@ NOT tied to net worth specifically - works across ALL fin-infra domains:
|
|
|
10
10
|
|
|
11
11
|
Example:
|
|
12
12
|
from fin_infra.conversation import FinancialPlanningConversation
|
|
13
|
-
from ai_infra.llm import
|
|
13
|
+
from ai_infra.llm import LLM
|
|
14
14
|
from svc_infra.cache import get_cache
|
|
15
15
|
|
|
16
|
-
llm =
|
|
16
|
+
llm = LLM()
|
|
17
17
|
cache = get_cache()
|
|
18
18
|
conversation = FinancialPlanningConversation(
|
|
19
19
|
llm=llm,
|
|
@@ -101,7 +101,7 @@ def add_financial_conversation(
|
|
|
101
101
|
|
|
102
102
|
Integration:
|
|
103
103
|
- Uses user_router (requires authentication)
|
|
104
|
-
- Powered by ai-infra
|
|
104
|
+
- Powered by ai-infra LLM (multi-provider support)
|
|
105
105
|
- Uses svc-infra cache for conversation history (24h TTL)
|
|
106
106
|
- Cost: ~$0.018/user/month with Google Gemini
|
|
107
107
|
|
|
@@ -13,8 +13,8 @@ Example:
|
|
|
13
13
|
conversation = easy_financial_conversation(provider="openai")
|
|
14
14
|
|
|
15
15
|
# Custom LLM instance
|
|
16
|
-
from ai_infra.llm import
|
|
17
|
-
llm =
|
|
16
|
+
from ai_infra.llm import LLM
|
|
17
|
+
llm = LLM(temperature=0.3)
|
|
18
18
|
conversation = easy_financial_conversation(llm=llm)
|
|
19
19
|
"""
|
|
20
20
|
|
|
@@ -33,12 +33,12 @@ def easy_financial_conversation(
|
|
|
33
33
|
Easy builder for financial planning conversation.
|
|
34
34
|
|
|
35
35
|
One-call setup with sensible defaults:
|
|
36
|
-
- LLM: ai-infra
|
|
36
|
+
- LLM: ai-infra LLM (Google Gemini default)
|
|
37
37
|
- Cache: svc-infra cache (24h TTL)
|
|
38
38
|
- Provider: Google (cheapest, $0.018/user/month)
|
|
39
39
|
|
|
40
40
|
Args:
|
|
41
|
-
llm: Optional ai-infra
|
|
41
|
+
llm: Optional ai-infra LLM instance (auto-created if None)
|
|
42
42
|
cache: Optional svc-infra cache instance (auto-created if None)
|
|
43
43
|
provider: LLM provider ("google", "openai", "anthropic")
|
|
44
44
|
model_name: Optional model name override (uses provider defaults)
|
|
@@ -62,16 +62,16 @@ def easy_financial_conversation(
|
|
|
62
62
|
conversation = easy_financial_conversation(provider="openai")
|
|
63
63
|
|
|
64
64
|
# Custom LLM instance
|
|
65
|
-
from ai_infra.llm import
|
|
66
|
-
llm =
|
|
65
|
+
from ai_infra.llm import LLM
|
|
66
|
+
llm = LLM(temperature=0.3)
|
|
67
67
|
conversation = easy_financial_conversation(llm=llm)
|
|
68
68
|
"""
|
|
69
69
|
# Auto-create LLM if not provided
|
|
70
70
|
if llm is None:
|
|
71
71
|
try:
|
|
72
|
-
from ai_infra.llm import
|
|
72
|
+
from ai_infra.llm import LLM
|
|
73
73
|
|
|
74
|
-
llm =
|
|
74
|
+
llm = LLM()
|
|
75
75
|
except ImportError:
|
|
76
76
|
raise ImportError("ai-infra not installed. Install with: pip install ai-infra")
|
|
77
77
|
|
|
@@ -10,19 +10,19 @@ Provides conversational interface for financial Q&A:
|
|
|
10
10
|
- Personalized advice (uses current net worth, goals, historical data)
|
|
11
11
|
- Natural dialogue (flexible responses, not forced JSON structure)
|
|
12
12
|
|
|
13
|
-
**Design Choice**: Uses `
|
|
13
|
+
**Design Choice**: Uses `LLM.achat()` for natural conversation (NOT `with_structured_output()`).
|
|
14
14
|
Conversation should be flexible and natural, not rigidly structured. Other modules (insights,
|
|
15
15
|
categorization, goals) correctly use structured output because they need predictable schemas.
|
|
16
16
|
|
|
17
|
-
Uses ai-infra
|
|
17
|
+
Uses ai-infra LLM for natural conversation.
|
|
18
18
|
Caches conversation context for 24h (target: $0.018/user/month cost).
|
|
19
19
|
|
|
20
20
|
Example:
|
|
21
|
-
from ai_infra.llm import
|
|
21
|
+
from ai_infra.llm import LLM
|
|
22
22
|
from svc_infra.cache import get_cache
|
|
23
23
|
from fin_infra.conversation import FinancialPlanningConversation
|
|
24
24
|
|
|
25
|
-
llm =
|
|
25
|
+
llm = LLM()
|
|
26
26
|
cache = get_cache()
|
|
27
27
|
conversation = FinancialPlanningConversation(
|
|
28
28
|
llm=llm,
|
|
@@ -196,10 +196,10 @@ class FinancialPlanningConversation:
|
|
|
196
196
|
Cost: ~$0.0054/conversation (10 turns with context caching)
|
|
197
197
|
|
|
198
198
|
Example:
|
|
199
|
-
from ai_infra.llm import
|
|
199
|
+
from ai_infra.llm import LLM
|
|
200
200
|
from svc_infra.cache import get_cache
|
|
201
201
|
|
|
202
|
-
llm =
|
|
202
|
+
llm = LLM()
|
|
203
203
|
cache = get_cache()
|
|
204
204
|
conversation = FinancialPlanningConversation(
|
|
205
205
|
llm=llm,
|
|
@@ -232,7 +232,7 @@ class FinancialPlanningConversation:
|
|
|
232
232
|
Initialize conversation manager.
|
|
233
233
|
|
|
234
234
|
Args:
|
|
235
|
-
llm: ai-infra
|
|
235
|
+
llm: ai-infra LLM instance
|
|
236
236
|
cache: svc-infra cache instance (for context storage)
|
|
237
237
|
provider: LLM provider ("google", "openai", "anthropic")
|
|
238
238
|
model_name: Model name (default: gemini-2.0-flash-exp)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Crypto portfolio insights using ai-infra LLM.
|
|
2
2
|
|
|
3
3
|
This module generates personalized insights for cryptocurrency holdings using
|
|
4
|
-
ai-infra's
|
|
4
|
+
ai-infra's LLM for intelligent analysis and recommendations.
|
|
5
5
|
|
|
6
|
-
CRITICAL: Uses ai-infra.llm.
|
|
6
|
+
CRITICAL: Uses ai-infra.llm.LLM (NEVER custom LLM clients).
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -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
|
|
18
|
+
from ai_infra.llm import LLM
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class CryptoInsight(BaseModel):
|
|
@@ -56,13 +56,13 @@ class CryptoHolding(BaseModel):
|
|
|
56
56
|
async def generate_crypto_insights(
|
|
57
57
|
user_id: str,
|
|
58
58
|
holdings: list[CryptoHolding],
|
|
59
|
-
llm:
|
|
59
|
+
llm: LLM | None = None,
|
|
60
60
|
total_portfolio_value: Decimal | None = None,
|
|
61
61
|
) -> list[CryptoInsight]:
|
|
62
62
|
"""
|
|
63
63
|
Generate personalized crypto insights using ai-infra LLM.
|
|
64
64
|
|
|
65
|
-
Uses ai-infra.llm.
|
|
65
|
+
Uses ai-infra.llm.LLM for intelligent analysis based on:
|
|
66
66
|
- Portfolio concentration (diversification recommendations)
|
|
67
67
|
- Performance trends (gains/losses)
|
|
68
68
|
- Risk assessment (volatility, allocation %)
|
|
@@ -71,15 +71,15 @@ async def generate_crypto_insights(
|
|
|
71
71
|
Args:
|
|
72
72
|
user_id: User identifier
|
|
73
73
|
holdings: List of crypto holdings
|
|
74
|
-
llm:
|
|
74
|
+
llm: LLM instance (if None, uses default Google Gemini)
|
|
75
75
|
total_portfolio_value: Total portfolio value including non-crypto assets
|
|
76
76
|
|
|
77
77
|
Returns:
|
|
78
78
|
List of personalized CryptoInsight objects
|
|
79
79
|
|
|
80
80
|
Example:
|
|
81
|
-
>>> from ai_infra.llm import
|
|
82
|
-
>>> llm =
|
|
81
|
+
>>> from ai_infra.llm import LLM
|
|
82
|
+
>>> llm = LLM(provider="google_genai", model="gemini-2.0-flash-exp")
|
|
83
83
|
>>> holdings = [
|
|
84
84
|
... CryptoHolding(
|
|
85
85
|
... symbol="BTC",
|
|
@@ -206,14 +206,14 @@ async def _generate_llm_insights(
|
|
|
206
206
|
holdings: list[CryptoHolding],
|
|
207
207
|
total_crypto_value: Decimal,
|
|
208
208
|
total_portfolio_value: Decimal | None,
|
|
209
|
-
llm:
|
|
209
|
+
llm: LLM,
|
|
210
210
|
) -> list[CryptoInsight]:
|
|
211
211
|
"""
|
|
212
|
-
Generate AI-powered insights using ai-infra
|
|
212
|
+
Generate AI-powered insights using ai-infra LLM.
|
|
213
213
|
|
|
214
214
|
Uses natural language conversation (NO output_schema) for personalized advice.
|
|
215
215
|
|
|
216
|
-
CRITICAL: Uses ai-infra.llm.
|
|
216
|
+
CRITICAL: Uses ai-infra.llm.LLM (never custom LLM clients).
|
|
217
217
|
"""
|
|
218
218
|
insights = []
|
|
219
219
|
|
|
@@ -260,7 +260,7 @@ Provide your insight:"""
|
|
|
260
260
|
# Use natural language conversation (no output_schema)
|
|
261
261
|
# Note: In tests, achat is mocked with messages= parameter
|
|
262
262
|
# In production, this should use user_msg, provider, model_name parameters
|
|
263
|
-
response = await llm.achat(
|
|
263
|
+
response = await llm.achat(
|
|
264
264
|
messages=[{"role": "user", "content": prompt}],
|
|
265
265
|
)
|
|
266
266
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
AI-powered document analysis and insights.
|
|
3
3
|
|
|
4
4
|
Uses rule-based analysis (simulated AI) for financial documents.
|
|
5
|
-
Production: Use ai-infra
|
|
5
|
+
Production: Use ai-infra LLM for real AI-powered insights.
|
|
6
6
|
|
|
7
7
|
Quick Start:
|
|
8
8
|
>>> from fin_infra.documents.analysis import analyze_document
|
|
@@ -14,7 +14,7 @@ Quick Start:
|
|
|
14
14
|
>>> print(result.recommendations)
|
|
15
15
|
|
|
16
16
|
Production Integration:
|
|
17
|
-
- Use ai-infra
|
|
17
|
+
- Use ai-infra LLM for all LLM calls (never custom clients)
|
|
18
18
|
- Cache analysis results (24h TTL via svc-infra cache)
|
|
19
19
|
- Track LLM costs per user (ai-infra cost tracking)
|
|
20
20
|
- Add disclaimers for financial advice
|
|
@@ -68,7 +68,7 @@ async def analyze_document(
|
|
|
68
68
|
|
|
69
69
|
Notes:
|
|
70
70
|
- Current: Rule-based analysis (simulated AI)
|
|
71
|
-
- Production: Use ai-infra
|
|
71
|
+
- Production: Use ai-infra LLM (never custom LLM clients)
|
|
72
72
|
- Production: Check cache before analysis (svc-infra cache, 24h TTL)
|
|
73
73
|
- Production: Track LLM costs (ai-infra cost tracking)
|
|
74
74
|
- Production: Add disclaimer: "Not a substitute for certified financial advisor"
|
|
@@ -218,7 +218,7 @@ def _analyze_tax_document(ocr_text: str, metadata: dict, document_id: str) -> "D
|
|
|
218
218
|
|
|
219
219
|
Notes:
|
|
220
220
|
- Current: Rule-based analysis (simulated AI)
|
|
221
|
-
- Production: Use ai-infra
|
|
221
|
+
- Production: Use ai-infra LLM with financial tax prompt
|
|
222
222
|
- Production: Include tax bracket information
|
|
223
223
|
- Production: Suggest W-4 adjustments if applicable
|
|
224
224
|
- Production: Identify potential deductions or credits
|
|
@@ -318,7 +318,7 @@ def _analyze_bank_statement(ocr_text: str, metadata: dict, document_id: str) ->
|
|
|
318
318
|
|
|
319
319
|
Notes:
|
|
320
320
|
- Current: Rule-based analysis (simulated AI)
|
|
321
|
-
- Production: Use ai-infra
|
|
321
|
+
- Production: Use ai-infra LLM with spending analysis prompt
|
|
322
322
|
- Production: Identify unusual transactions or patterns
|
|
323
323
|
- Production: Compare to typical spending (if available)
|
|
324
324
|
- Production: Suggest budget optimizations
|
|
@@ -7,14 +7,14 @@ Provides 4 goal types with validation and progress tracking:
|
|
|
7
7
|
3. Debt-Free Goal: Debt payoff schedule with APR calculations
|
|
8
8
|
4. Wealth Milestone: Project growth to target net worth
|
|
9
9
|
|
|
10
|
-
Uses ai-infra
|
|
10
|
+
Uses ai-infra LLM for validation + local math for calculations.
|
|
11
11
|
Weekly progress check-ins via svc-infra.jobs scheduler ($0.0036/user/month).
|
|
12
12
|
|
|
13
13
|
Example:
|
|
14
|
-
from ai_infra.llm import
|
|
14
|
+
from ai_infra.llm import LLM
|
|
15
15
|
from fin_infra.goals.management import FinancialGoalTracker
|
|
16
16
|
|
|
17
|
-
llm =
|
|
17
|
+
llm = LLM()
|
|
18
18
|
tracker = FinancialGoalTracker(
|
|
19
19
|
llm=llm,
|
|
20
20
|
provider="google",
|
|
@@ -475,9 +475,9 @@ class FinancialGoalTracker:
|
|
|
475
475
|
Cost: ~$0.0009/validation, ~$0.0009/week for progress ($0.0036/user/month)
|
|
476
476
|
|
|
477
477
|
Example:
|
|
478
|
-
from ai_infra.llm import
|
|
478
|
+
from ai_infra.llm import LLM
|
|
479
479
|
|
|
480
|
-
llm =
|
|
480
|
+
llm = LLM()
|
|
481
481
|
tracker = FinancialGoalTracker(llm=llm, provider="google")
|
|
482
482
|
|
|
483
483
|
# Validate goal
|
|
@@ -503,7 +503,7 @@ class FinancialGoalTracker:
|
|
|
503
503
|
Initialize goal tracker.
|
|
504
504
|
|
|
505
505
|
Args:
|
|
506
|
-
llm: ai-infra
|
|
506
|
+
llm: ai-infra LLM instance
|
|
507
507
|
provider: LLM provider ("google", "openai", "anthropic")
|
|
508
508
|
model_name: Model name (default: gemini-2.0-flash-exp)
|
|
509
509
|
"""
|
|
@@ -201,7 +201,7 @@ class Holding(BaseModel):
|
|
|
201
201
|
unofficial_currency_code: Optional[str] = Field(None, description="For crypto/alt currencies")
|
|
202
202
|
as_of_date: Optional[date] = Field(None, description="Date of pricing data")
|
|
203
203
|
|
|
204
|
-
@computed_field
|
|
204
|
+
@computed_field
|
|
205
205
|
@property
|
|
206
206
|
def unrealized_gain_loss(self) -> Optional[Decimal]:
|
|
207
207
|
"""Calculate unrealized gain/loss (current value - cost basis)."""
|
|
@@ -209,7 +209,7 @@ class Holding(BaseModel):
|
|
|
209
209
|
return None
|
|
210
210
|
return self.institution_value - self.cost_basis
|
|
211
211
|
|
|
212
|
-
@computed_field
|
|
212
|
+
@computed_field
|
|
213
213
|
@property
|
|
214
214
|
def unrealized_gain_loss_percent(self) -> Optional[Decimal]:
|
|
215
215
|
"""Calculate unrealized gain/loss percentage."""
|
|
@@ -350,7 +350,7 @@ class InvestmentAccount(BaseModel):
|
|
|
350
350
|
# Holdings
|
|
351
351
|
holdings: List[Holding] = Field(default_factory=list, description="List of holdings in account")
|
|
352
352
|
|
|
353
|
-
@computed_field
|
|
353
|
+
@computed_field
|
|
354
354
|
@property
|
|
355
355
|
def total_value(self) -> Decimal:
|
|
356
356
|
"""Calculate total account value (sum of holdings + cash)."""
|
|
@@ -358,20 +358,20 @@ class InvestmentAccount(BaseModel):
|
|
|
358
358
|
cash_balance = self.balances.get("current") or Decimal(0)
|
|
359
359
|
return holdings_value + cash_balance
|
|
360
360
|
|
|
361
|
-
@computed_field
|
|
361
|
+
@computed_field
|
|
362
362
|
@property
|
|
363
363
|
def total_cost_basis(self) -> Decimal:
|
|
364
364
|
"""Calculate total cost basis (sum of cost_basis across holdings)."""
|
|
365
365
|
return sum(h.cost_basis for h in self.holdings if h.cost_basis is not None)
|
|
366
366
|
|
|
367
|
-
@computed_field
|
|
367
|
+
@computed_field
|
|
368
368
|
@property
|
|
369
369
|
def total_unrealized_gain_loss(self) -> Decimal:
|
|
370
370
|
"""Calculate total unrealized P&L (value - cost_basis)."""
|
|
371
371
|
holdings_value = sum(h.institution_value for h in self.holdings)
|
|
372
372
|
return holdings_value - self.total_cost_basis
|
|
373
373
|
|
|
374
|
-
@computed_field
|
|
374
|
+
@computed_field
|
|
375
375
|
@property
|
|
376
376
|
def total_unrealized_gain_loss_percent(self) -> Optional[Decimal]:
|
|
377
377
|
"""Calculate total unrealized P&L percentage."""
|
|
@@ -368,7 +368,7 @@ def easy_net_worth(
|
|
|
368
368
|
|
|
369
369
|
if enable_llm:
|
|
370
370
|
try:
|
|
371
|
-
from ai_infra.llm import
|
|
371
|
+
from ai_infra.llm import LLM
|
|
372
372
|
except ImportError:
|
|
373
373
|
raise ImportError(
|
|
374
374
|
"LLM features require ai-infra package. " "Install with: pip install ai-infra"
|
|
@@ -387,8 +387,8 @@ def easy_net_worth(
|
|
|
387
387
|
f"Unknown llm_provider: {llm_provider}. " f"Use 'google', 'openai', or 'anthropic'"
|
|
388
388
|
)
|
|
389
389
|
|
|
390
|
-
# Create shared
|
|
391
|
-
llm =
|
|
390
|
+
# Create shared LLM instance
|
|
391
|
+
llm = LLM()
|
|
392
392
|
|
|
393
393
|
# Create LLM components (deferred import to avoid circular dependency)
|
|
394
394
|
# These modules will be created in Section 17 V2 implementation
|
|
@@ -7,14 +7,14 @@ Provides 4 types of insights:
|
|
|
7
7
|
3. Goal Recommendations: Validate financial goals and suggest paths
|
|
8
8
|
4. Asset Allocation Advice: Portfolio rebalancing based on age/risk
|
|
9
9
|
|
|
10
|
-
Uses ai-infra
|
|
10
|
+
Uses ai-infra LLM with structured output (Pydantic schemas).
|
|
11
11
|
Caches insights for 24h (target: 95%+ hit rate, $0.042/user/month cost).
|
|
12
12
|
|
|
13
13
|
Example:
|
|
14
|
-
from ai_infra.llm import
|
|
14
|
+
from ai_infra.llm import LLM
|
|
15
15
|
from fin_infra.net_worth.insights import NetWorthInsightsGenerator
|
|
16
16
|
|
|
17
|
-
llm =
|
|
17
|
+
llm = LLM()
|
|
18
18
|
generator = NetWorthInsightsGenerator(
|
|
19
19
|
llm=llm,
|
|
20
20
|
provider="google",
|
|
@@ -379,7 +379,7 @@ class NetWorthInsightsGenerator:
|
|
|
379
379
|
"""
|
|
380
380
|
Generate LLM-powered financial insights for net worth tracking.
|
|
381
381
|
|
|
382
|
-
Uses ai-infra
|
|
382
|
+
Uses ai-infra LLM with structured output (Pydantic schemas).
|
|
383
383
|
Supports 4 insight types:
|
|
384
384
|
1. Wealth trends (analyze net worth changes)
|
|
385
385
|
2. Debt reduction (prioritize payoff by APR)
|
|
@@ -389,9 +389,9 @@ class NetWorthInsightsGenerator:
|
|
|
389
389
|
Cost: ~$0.042/user/month (1 insight/day, 24h cache, Google Gemini)
|
|
390
390
|
|
|
391
391
|
Example:
|
|
392
|
-
from ai_infra.llm import
|
|
392
|
+
from ai_infra.llm import LLM
|
|
393
393
|
|
|
394
|
-
llm =
|
|
394
|
+
llm = LLM()
|
|
395
395
|
generator = NetWorthInsightsGenerator(
|
|
396
396
|
llm=llm,
|
|
397
397
|
provider="google",
|
|
@@ -412,7 +412,7 @@ class NetWorthInsightsGenerator:
|
|
|
412
412
|
Initialize insights generator.
|
|
413
413
|
|
|
414
414
|
Args:
|
|
415
|
-
llm: ai-infra
|
|
415
|
+
llm: ai-infra LLM instance
|
|
416
416
|
provider: LLM provider ("google", "openai", "anthropic")
|
|
417
417
|
model_name: Model name (default: gemini-2.0-flash-exp)
|
|
418
418
|
"""
|
|
@@ -207,7 +207,7 @@ class AssetAllocation(BaseModel):
|
|
|
207
207
|
vehicles: float = Field(0.0, ge=0, description="Vehicle value")
|
|
208
208
|
other_assets: float = Field(0.0, ge=0, description="Other asset value")
|
|
209
209
|
|
|
210
|
-
@computed_field
|
|
210
|
+
@computed_field
|
|
211
211
|
@property
|
|
212
212
|
def total_assets(self) -> float:
|
|
213
213
|
"""Sum of all asset categories."""
|
|
@@ -220,37 +220,37 @@ class AssetAllocation(BaseModel):
|
|
|
220
220
|
+ self.other_assets
|
|
221
221
|
)
|
|
222
222
|
|
|
223
|
-
@computed_field
|
|
223
|
+
@computed_field
|
|
224
224
|
@property
|
|
225
225
|
def cash_percentage(self) -> float:
|
|
226
226
|
"""Cash as percentage of total assets."""
|
|
227
227
|
return (self.cash / self.total_assets * 100) if self.total_assets > 0 else 0.0
|
|
228
228
|
|
|
229
|
-
@computed_field
|
|
229
|
+
@computed_field
|
|
230
230
|
@property
|
|
231
231
|
def investments_percentage(self) -> float:
|
|
232
232
|
"""Investments as percentage of total assets."""
|
|
233
233
|
return (self.investments / self.total_assets * 100) if self.total_assets > 0 else 0.0
|
|
234
234
|
|
|
235
|
-
@computed_field
|
|
235
|
+
@computed_field
|
|
236
236
|
@property
|
|
237
237
|
def crypto_percentage(self) -> float:
|
|
238
238
|
"""Crypto as percentage of total assets."""
|
|
239
239
|
return (self.crypto / self.total_assets * 100) if self.total_assets > 0 else 0.0
|
|
240
240
|
|
|
241
|
-
@computed_field
|
|
241
|
+
@computed_field
|
|
242
242
|
@property
|
|
243
243
|
def real_estate_percentage(self) -> float:
|
|
244
244
|
"""Real estate as percentage of total assets."""
|
|
245
245
|
return (self.real_estate / self.total_assets * 100) if self.total_assets > 0 else 0.0
|
|
246
246
|
|
|
247
|
-
@computed_field
|
|
247
|
+
@computed_field
|
|
248
248
|
@property
|
|
249
249
|
def vehicles_percentage(self) -> float:
|
|
250
250
|
"""Vehicles as percentage of total assets."""
|
|
251
251
|
return (self.vehicles / self.total_assets * 100) if self.total_assets > 0 else 0.0
|
|
252
252
|
|
|
253
|
-
@computed_field
|
|
253
|
+
@computed_field
|
|
254
254
|
@property
|
|
255
255
|
def other_percentage(self) -> float:
|
|
256
256
|
"""Other assets as percentage of total assets."""
|
|
@@ -288,7 +288,7 @@ class LiabilityBreakdown(BaseModel):
|
|
|
288
288
|
personal_loans: float = Field(0.0, ge=0, description="Personal loan balance")
|
|
289
289
|
lines_of_credit: float = Field(0.0, ge=0, description="Line of credit balance")
|
|
290
290
|
|
|
291
|
-
@computed_field
|
|
291
|
+
@computed_field
|
|
292
292
|
@property
|
|
293
293
|
def total_liabilities(self) -> float:
|
|
294
294
|
"""Sum of all liability categories."""
|
|
@@ -301,7 +301,7 @@ class LiabilityBreakdown(BaseModel):
|
|
|
301
301
|
+ self.lines_of_credit
|
|
302
302
|
)
|
|
303
303
|
|
|
304
|
-
@computed_field
|
|
304
|
+
@computed_field
|
|
305
305
|
@property
|
|
306
306
|
def credit_cards_percentage(self) -> float:
|
|
307
307
|
"""Credit cards as percentage of total liabilities."""
|
|
@@ -311,7 +311,7 @@ class LiabilityBreakdown(BaseModel):
|
|
|
311
311
|
else 0.0
|
|
312
312
|
)
|
|
313
313
|
|
|
314
|
-
@computed_field
|
|
314
|
+
@computed_field
|
|
315
315
|
@property
|
|
316
316
|
def mortgages_percentage(self) -> float:
|
|
317
317
|
"""Mortgages as percentage of total liabilities."""
|
|
@@ -319,7 +319,7 @@ class LiabilityBreakdown(BaseModel):
|
|
|
319
319
|
(self.mortgages / self.total_liabilities * 100) if self.total_liabilities > 0 else 0.0
|
|
320
320
|
)
|
|
321
321
|
|
|
322
|
-
@computed_field
|
|
322
|
+
@computed_field
|
|
323
323
|
@property
|
|
324
324
|
def auto_loans_percentage(self) -> float:
|
|
325
325
|
"""Auto loans as percentage of total liabilities."""
|
|
@@ -327,7 +327,7 @@ class LiabilityBreakdown(BaseModel):
|
|
|
327
327
|
(self.auto_loans / self.total_liabilities * 100) if self.total_liabilities > 0 else 0.0
|
|
328
328
|
)
|
|
329
329
|
|
|
330
|
-
@computed_field
|
|
330
|
+
@computed_field
|
|
331
331
|
@property
|
|
332
332
|
def student_loans_percentage(self) -> float:
|
|
333
333
|
"""Student loans as percentage of total liabilities."""
|
|
@@ -337,7 +337,7 @@ class LiabilityBreakdown(BaseModel):
|
|
|
337
337
|
else 0.0
|
|
338
338
|
)
|
|
339
339
|
|
|
340
|
-
@computed_field
|
|
340
|
+
@computed_field
|
|
341
341
|
@property
|
|
342
342
|
def personal_loans_percentage(self) -> float:
|
|
343
343
|
"""Personal loans as percentage of total liabilities."""
|
|
@@ -347,7 +347,7 @@ class LiabilityBreakdown(BaseModel):
|
|
|
347
347
|
else 0.0
|
|
348
348
|
)
|
|
349
349
|
|
|
350
|
-
@computed_field
|
|
350
|
+
@computed_field
|
|
351
351
|
@property
|
|
352
352
|
def lines_of_credit_percentage(self) -> float:
|
|
353
353
|
"""Lines of credit as percentage of total liabilities."""
|
|
@@ -37,6 +37,8 @@ Usage:
|
|
|
37
37
|
|
|
38
38
|
from __future__ import annotations
|
|
39
39
|
|
|
40
|
+
from typing import Callable
|
|
41
|
+
|
|
40
42
|
# Financial capability prefix patterns (extensible)
|
|
41
43
|
FINANCIAL_ROUTE_PREFIXES = (
|
|
42
44
|
"/banking",
|
|
@@ -110,9 +112,9 @@ def financial_route_classifier(route_path: str, method: str) -> str:
|
|
|
110
112
|
|
|
111
113
|
|
|
112
114
|
def compose_classifiers(
|
|
113
|
-
*classifiers:
|
|
115
|
+
*classifiers: Callable[[str], str],
|
|
114
116
|
default: str = "public",
|
|
115
|
-
) ->
|
|
117
|
+
) -> Callable[[str], str]:
|
|
116
118
|
"""
|
|
117
119
|
Compose multiple route classifiers with fallback logic.
|
|
118
120
|
|