fin-infra 0.1.61__tar.gz → 0.1.63__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.61 → fin_infra-0.1.63}/PKG-INFO +1 -1
- {fin_infra-0.1.61 → fin_infra-0.1.63}/pyproject.toml +1 -1
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/banking/__init__.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/__init__.py +4 -4
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/categorization/llm_layer.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/compliance/__init__.py +4 -3
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/credit/add.py +3 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/credit/experian/auth.py +3 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/credit/experian/client.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/credit/experian/provider.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/crypto/insights.py +1 -1
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/add.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/management.py +3 -3
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/milestones.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/models.py +6 -6
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/providers/plaid.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/providers/snaptrade.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/models.py +14 -14
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/normalization/providers/exchangerate.py +3 -3
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/obs/classifier.py +4 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/banking/plaid_client.py +4 -3
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/banking/teller_client.py +6 -6
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/brokerage/alpaca.py +6 -6
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/market/ccxt_crypto.py +5 -3
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/detectors_llm.py +3 -3
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/insights.py +3 -3
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/normalizer.py +2 -1
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/normalizers.py +3 -3
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/security/encryption.py +2 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/security/pii_patterns.py +1 -1
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/security/token_store.py +6 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/tax/add.py +1 -1
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/utils/http.py +3 -2
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/utils/retry.py +1 -1
- {fin_infra-0.1.61 → fin_infra-0.1.63}/LICENSE +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/README.md +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/__main__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/add.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/cash_flow.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/ease.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/portfolio.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/projections.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/rebalancing.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/savings.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/scenarios.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/analytics/spending.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/banking/history.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/banking/utils.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/brokerage/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/add.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/alerts.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/ease.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/templates.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/budgets/tracker.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/cashflows/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/cashflows/core.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/categorization/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/categorization/add.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/categorization/ease.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/categorization/engine.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/categorization/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/categorization/rules.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/categorization/taxonomy.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/chat/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/chat/ease.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/chat/planning.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/cli/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/cli/cmds/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/cli/cmds/scaffold_cmds.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/clients/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/clients/base.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/clients/plaid.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/credit/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/credit/experian/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/credit/experian/parser.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/credit/mock.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/crypto/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/documents/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/documents/add.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/documents/analysis.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/documents/ease.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/documents/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/documents/ocr.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/documents/storage.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/exceptions.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/funding.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/insights/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/insights/aggregator.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/insights/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/add.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/ease.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/providers/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/providers/base.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/markets/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/accounts.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/brokerage.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/candle.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/credit.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/money.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/quotes.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/tax.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/models/transactions.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/add.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/aggregator.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/calculator.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/ease.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/goals.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/insights.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/normalization/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/normalization/currency_converter.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/normalization/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/normalization/providers/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/normalization/providers/static_mappings.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/normalization/symbol_resolver.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/obs/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/banking/base.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/base.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/brokerage/base.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/credit/experian.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/market/alphavantage.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/market/base.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/market/coingecko.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/market/yahoo.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/registry.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/tax/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/tax/irs.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/tax/mock.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/providers/tax/taxbit.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/py.typed +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/add.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/detector.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/ease.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/recurring/summary.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/scaffold/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/scaffold/budgets.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/scaffold/goals.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/security/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/security/add.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/security/audit.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/security/models.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/security/pii_filter.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/settings.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/tax/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/tax/tlh.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/src/fin_infra/utils/__init__.py +0 -0
- {fin_infra-0.1.61 → fin_infra-0.1.63}/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.63
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "fin-infra"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.63"
|
|
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"
|
|
@@ -45,7 +45,7 @@ from __future__ import annotations
|
|
|
45
45
|
|
|
46
46
|
import os
|
|
47
47
|
from datetime import date
|
|
48
|
-
from typing import TYPE_CHECKING, Optional
|
|
48
|
+
from typing import TYPE_CHECKING, Optional, cast
|
|
49
49
|
|
|
50
50
|
from pydantic import BaseModel, Field
|
|
51
51
|
|
|
@@ -199,7 +199,7 @@ def easy_banking(provider: str = "teller", **config) -> BankingProvider:
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
# Use provider registry to dynamically load and configure provider
|
|
202
|
-
return resolve("banking", provider, **config)
|
|
202
|
+
return cast(BankingProvider, resolve("banking", provider, **config))
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
def add_banking(
|
|
@@ -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
|
|
|
@@ -15,7 +15,7 @@ Expected performance:
|
|
|
15
15
|
|
|
16
16
|
import hashlib
|
|
17
17
|
import logging
|
|
18
|
-
from typing import Optional, List, Tuple
|
|
18
|
+
from typing import Optional, List, Tuple, cast
|
|
19
19
|
from pydantic import BaseModel, Field
|
|
20
20
|
|
|
21
21
|
# ai-infra imports
|
|
@@ -245,7 +245,7 @@ class LLMCategorizer:
|
|
|
245
245
|
f"Must be one of {len(valid_categories)} valid categories."
|
|
246
246
|
)
|
|
247
247
|
|
|
248
|
-
return response
|
|
248
|
+
return cast(CategoryPrediction, response)
|
|
249
249
|
|
|
250
250
|
def _build_system_prompt(self) -> str:
|
|
251
251
|
"""Build system prompt with few-shot examples (reused across all requests)."""
|
|
@@ -21,7 +21,7 @@ from __future__ import annotations
|
|
|
21
21
|
|
|
22
22
|
import logging
|
|
23
23
|
from datetime import datetime
|
|
24
|
-
from typing import Any, Callable, TYPE_CHECKING
|
|
24
|
+
from typing import Any, Callable, TYPE_CHECKING, cast
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from fastapi import FastAPI, Request, Response
|
|
@@ -118,7 +118,8 @@ def add_compliance_tracking(
|
|
|
118
118
|
|
|
119
119
|
# Track only GET requests (data access)
|
|
120
120
|
if method != "GET":
|
|
121
|
-
|
|
121
|
+
from starlette.responses import Response as StarletteResponse
|
|
122
|
+
return cast("Response", await call_next(request))
|
|
122
123
|
|
|
123
124
|
# Determine if path is a compliance-tracked endpoint
|
|
124
125
|
event = None
|
|
@@ -148,7 +149,7 @@ def add_compliance_tracking(
|
|
|
148
149
|
if on_event:
|
|
149
150
|
on_event(event, context)
|
|
150
151
|
|
|
151
|
-
return response
|
|
152
|
+
return cast("Response", response)
|
|
152
153
|
|
|
153
154
|
logger.info(
|
|
154
155
|
"Compliance tracking enabled",
|
|
@@ -23,6 +23,7 @@ Example:
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import logging
|
|
26
|
+
from typing import cast
|
|
26
27
|
|
|
27
28
|
from fastapi import FastAPI, Depends, HTTPException, status
|
|
28
29
|
|
|
@@ -175,7 +176,7 @@ def add_credit(
|
|
|
175
176
|
# Don't fail request if webhook publishing fails
|
|
176
177
|
logger.warning(f"Failed to publish credit.score_changed webhook: {e}")
|
|
177
178
|
|
|
178
|
-
return score
|
|
179
|
+
return cast(CreditScore, score)
|
|
179
180
|
|
|
180
181
|
@router.post("/report", response_model=CreditReport)
|
|
181
182
|
@credit_resource.cache_read(ttl=cache_ttl, suffix="report")
|
|
@@ -219,7 +220,7 @@ def add_credit(
|
|
|
219
220
|
detail="Credit bureau service unavailable",
|
|
220
221
|
)
|
|
221
222
|
|
|
222
|
-
return report
|
|
223
|
+
return cast(CreditReport, report)
|
|
223
224
|
|
|
224
225
|
# Mount router with dual routes (with/without trailing slash)
|
|
225
226
|
app.include_router(router, include_in_schema=True)
|
|
@@ -24,6 +24,7 @@ Example:
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
import base64
|
|
27
|
+
from typing import cast
|
|
27
28
|
|
|
28
29
|
import httpx
|
|
29
30
|
from svc_infra.cache import cache_read
|
|
@@ -85,7 +86,7 @@ class ExperianAuthManager:
|
|
|
85
86
|
>>> headers = {"Authorization": f"Bearer {token}"}
|
|
86
87
|
"""
|
|
87
88
|
# Call the cached implementation with client_id for cache key
|
|
88
|
-
return await self._get_token_cached(client_id=self.client_id)
|
|
89
|
+
return cast(str, await self._get_token_cached(client_id=self.client_id))
|
|
89
90
|
|
|
90
91
|
@cache_read(
|
|
91
92
|
key="oauth_token:experian:{client_id}", # Use client_id for uniqueness
|
|
@@ -140,7 +141,7 @@ class ExperianAuthManager:
|
|
|
140
141
|
|
|
141
142
|
# Parse and return token
|
|
142
143
|
data = response.json()
|
|
143
|
-
return data["access_token"]
|
|
144
|
+
return cast(str, data["access_token"])
|
|
144
145
|
|
|
145
146
|
async def invalidate(self) -> None:
|
|
146
147
|
"""Invalidate cached token for THIS client (force refresh on next get_token call).
|
|
@@ -14,7 +14,7 @@ Example:
|
|
|
14
14
|
>>> data = await client.get_credit_score("user123")
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
from typing import Any
|
|
17
|
+
from typing import Any, cast
|
|
18
18
|
|
|
19
19
|
import httpx
|
|
20
20
|
from tenacity import (
|
|
@@ -155,7 +155,7 @@ class ExperianClient:
|
|
|
155
155
|
**kwargs,
|
|
156
156
|
)
|
|
157
157
|
response.raise_for_status()
|
|
158
|
-
return response.json()
|
|
158
|
+
return cast(dict[str, Any], response.json())
|
|
159
159
|
|
|
160
160
|
except httpx.HTTPStatusError as e:
|
|
161
161
|
# Parse error response
|
|
@@ -31,7 +31,7 @@ Example:
|
|
|
31
31
|
|
|
32
32
|
import logging
|
|
33
33
|
from datetime import datetime, timezone
|
|
34
|
-
from typing import Literal
|
|
34
|
+
from typing import Literal, cast
|
|
35
35
|
|
|
36
36
|
from fin_infra.credit.experian.auth import ExperianAuthManager
|
|
37
37
|
from fin_infra.credit.experian.client import ExperianClient
|
|
@@ -360,4 +360,4 @@ class ExperianProvider(CreditProvider):
|
|
|
360
360
|
signature_key=signature_key,
|
|
361
361
|
)
|
|
362
362
|
|
|
363
|
-
return data.get("subscriptionId", "unknown")
|
|
363
|
+
return cast(str, data.get("subscriptionId", "unknown"))
|
|
@@ -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
|
|
|
@@ -29,7 +29,7 @@ add_goals(app)
|
|
|
29
29
|
|
|
30
30
|
import logging
|
|
31
31
|
from datetime import datetime
|
|
32
|
-
from typing import List, Optional
|
|
32
|
+
from typing import Any, List, Optional, cast
|
|
33
33
|
|
|
34
34
|
from fastapi import FastAPI, HTTPException, status, Query, Body
|
|
35
35
|
from pydantic import BaseModel, Field
|
|
@@ -469,7 +469,7 @@ def add_goals(
|
|
|
469
469
|
# Get all milestones from the goal (check_milestones only returns newly reached ones)
|
|
470
470
|
goal = get_goal(goal_id)
|
|
471
471
|
milestones = goal.get("milestones", [])
|
|
472
|
-
return milestones
|
|
472
|
+
return cast(list[dict[Any, Any]], milestones)
|
|
473
473
|
except KeyError:
|
|
474
474
|
raise HTTPException(
|
|
475
475
|
status_code=status.HTTP_404_NOT_FOUND, detail=f"Goal {goal_id} not found"
|
|
@@ -41,7 +41,7 @@ Example:
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
from datetime import datetime
|
|
44
|
-
from typing import Any
|
|
44
|
+
from typing import Any, cast
|
|
45
45
|
|
|
46
46
|
from pydantic import BaseModel, Field
|
|
47
47
|
|
|
@@ -839,7 +839,7 @@ def get_goal(goal_id: str) -> dict[str, Any]:
|
|
|
839
839
|
if goal_id not in _GOALS_STORE:
|
|
840
840
|
raise KeyError(f"Goal not found: {goal_id}")
|
|
841
841
|
|
|
842
|
-
return _GOALS_STORE[goal_id]
|
|
842
|
+
return cast(dict[str, Any], _GOALS_STORE[goal_id])
|
|
843
843
|
|
|
844
844
|
|
|
845
845
|
def update_goal(
|
|
@@ -885,7 +885,7 @@ def update_goal(
|
|
|
885
885
|
|
|
886
886
|
Goal(**goal) # Will raise ValidationError if invalid
|
|
887
887
|
|
|
888
|
-
return goal
|
|
888
|
+
return cast(dict[str, Any], goal)
|
|
889
889
|
|
|
890
890
|
|
|
891
891
|
def delete_goal(goal_id: str) -> None:
|
|
@@ -26,7 +26,7 @@ Example:
|
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
from datetime import datetime
|
|
29
|
-
from typing import Any
|
|
29
|
+
from typing import Any, cast
|
|
30
30
|
|
|
31
31
|
from fin_infra.goals.management import get_goal, update_goal
|
|
32
32
|
from fin_infra.goals.models import Milestone
|
|
@@ -229,7 +229,7 @@ def get_next_milestone(goal_id: str) -> dict[str, Any] | None:
|
|
|
229
229
|
# Find first unreached milestone (sorted by amount)
|
|
230
230
|
for milestone in milestones:
|
|
231
231
|
if not milestone.get("reached", False):
|
|
232
|
-
return milestone
|
|
232
|
+
return cast(dict[str, Any], milestone)
|
|
233
233
|
|
|
234
234
|
return None
|
|
235
235
|
|
|
@@ -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."""
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
from datetime import date
|
|
12
12
|
from decimal import Decimal
|
|
13
|
-
from typing import Any, Dict, List, Optional
|
|
13
|
+
from typing import Any, Dict, List, Optional, cast
|
|
14
14
|
|
|
15
15
|
from plaid.api import plaid_api
|
|
16
16
|
from plaid.model.investments_holdings_get_request import InvestmentsHoldingsGetRequest
|
|
@@ -103,7 +103,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
|
|
|
103
103
|
"development": plaid.Environment.Sandbox, # Map development to sandbox
|
|
104
104
|
"production": plaid.Environment.Production,
|
|
105
105
|
}
|
|
106
|
-
return hosts.get(environment.lower(), plaid.Environment.Sandbox)
|
|
106
|
+
return cast(str, hosts.get(environment.lower(), plaid.Environment.Sandbox))
|
|
107
107
|
|
|
108
108
|
async def get_holdings(
|
|
109
109
|
self, access_token: str, account_ids: Optional[List[str]] = None
|
|
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
from datetime import date
|
|
13
13
|
from decimal import Decimal
|
|
14
|
-
from typing import Any, Dict, List, Optional
|
|
14
|
+
from typing import Any, Dict, List, Optional, cast
|
|
15
15
|
|
|
16
16
|
import httpx
|
|
17
17
|
|
|
@@ -393,7 +393,7 @@ class SnapTradeInvestmentProvider(InvestmentProvider):
|
|
|
393
393
|
url = f"{self.base_url}/connections"
|
|
394
394
|
response = await self.client.get(url, headers=auth_headers)
|
|
395
395
|
response.raise_for_status()
|
|
396
|
-
return await response.json()
|
|
396
|
+
return cast(list[dict[str, Any]], await response.json())
|
|
397
397
|
|
|
398
398
|
except httpx.HTTPStatusError as e:
|
|
399
399
|
raise self._transform_error(e)
|
|
@@ -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."""
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from datetime import date as DateType
|
|
5
|
-
from typing import Optional
|
|
5
|
+
from typing import Optional, cast
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
|
|
@@ -66,10 +66,10 @@ class ExchangeRateClient:
|
|
|
66
66
|
raise ExchangeRateAPIError(
|
|
67
67
|
f"API returned error: {data.get('error-type', 'unknown')}"
|
|
68
68
|
)
|
|
69
|
-
return data["conversion_rates"]
|
|
69
|
+
return cast(dict[str, float], data["conversion_rates"])
|
|
70
70
|
else:
|
|
71
71
|
# Free tier response format
|
|
72
|
-
return data["rates"]
|
|
72
|
+
return cast(dict[str, float], data["rates"])
|
|
73
73
|
|
|
74
74
|
except httpx.HTTPError as e:
|
|
75
75
|
raise ExchangeRateAPIError(f"HTTP error fetching rates: {e}")
|
|
@@ -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
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import date, datetime, timedelta
|
|
4
|
+
from typing import Any, cast
|
|
4
5
|
|
|
5
6
|
# Plaid SDK v25+ uses new API structure
|
|
6
7
|
try:
|
|
@@ -96,7 +97,7 @@ class PlaidClient(BankingProvider):
|
|
|
96
97
|
language="en",
|
|
97
98
|
)
|
|
98
99
|
response = self.client.link_token_create(request)
|
|
99
|
-
return response["link_token"]
|
|
100
|
+
return cast(str, response["link_token"])
|
|
100
101
|
|
|
101
102
|
def exchange_public_token(self, public_token: str) -> dict:
|
|
102
103
|
request = ItemPublicTokenExchangeRequest(public_token=public_token)
|
|
@@ -146,8 +147,8 @@ class PlaidClient(BankingProvider):
|
|
|
146
147
|
# Return all balances
|
|
147
148
|
return {"balances": [acc.get("balances", {}) for acc in accounts]}
|
|
148
149
|
|
|
149
|
-
def identity(self, access_token: str) -> dict:
|
|
150
|
+
def identity(self, access_token: str) -> dict[Any, Any]:
|
|
150
151
|
"""Fetch identity/account holder information."""
|
|
151
152
|
request = IdentityGetRequest(access_token=access_token)
|
|
152
153
|
response = self.client.identity_get(request)
|
|
153
|
-
return response.to_dict()
|
|
154
|
+
return cast(dict[Any, Any], response.to_dict())
|
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
|
|
25
25
|
import ssl
|
|
26
26
|
import httpx
|
|
27
|
-
from typing import Any
|
|
27
|
+
from typing import Any, cast
|
|
28
28
|
|
|
29
29
|
from ..base import BankingProvider
|
|
30
30
|
|
|
@@ -139,7 +139,7 @@ class TellerClient(BankingProvider):
|
|
|
139
139
|
"products": ["accounts", "transactions", "balances", "identity"],
|
|
140
140
|
},
|
|
141
141
|
)
|
|
142
|
-
return response.get("enrollment_id", "")
|
|
142
|
+
return cast(str, response.get("enrollment_id", ""))
|
|
143
143
|
|
|
144
144
|
def exchange_public_token(self, public_token: str) -> dict:
|
|
145
145
|
"""Exchange public token for access token.
|
|
@@ -186,7 +186,7 @@ class TellerClient(BankingProvider):
|
|
|
186
186
|
auth=(access_token, ""),
|
|
187
187
|
)
|
|
188
188
|
response.raise_for_status()
|
|
189
|
-
return response.json()
|
|
189
|
+
return cast(list[dict[Any, Any]], response.json())
|
|
190
190
|
|
|
191
191
|
def transactions(
|
|
192
192
|
self,
|
|
@@ -229,7 +229,7 @@ class TellerClient(BankingProvider):
|
|
|
229
229
|
params=params,
|
|
230
230
|
)
|
|
231
231
|
response.raise_for_status()
|
|
232
|
-
return response.json()
|
|
232
|
+
return cast(list[dict[Any, Any]], response.json())
|
|
233
233
|
|
|
234
234
|
def balances(self, access_token: str, account_id: str | None = None) -> dict:
|
|
235
235
|
"""Fetch current balances.
|
|
@@ -261,7 +261,7 @@ class TellerClient(BankingProvider):
|
|
|
261
261
|
)
|
|
262
262
|
|
|
263
263
|
response.raise_for_status()
|
|
264
|
-
return response.json()
|
|
264
|
+
return cast(dict[Any, Any], response.json())
|
|
265
265
|
|
|
266
266
|
def identity(self, access_token: str) -> dict:
|
|
267
267
|
"""Fetch identity/account holder information.
|
|
@@ -285,7 +285,7 @@ class TellerClient(BankingProvider):
|
|
|
285
285
|
auth=(access_token, ""),
|
|
286
286
|
)
|
|
287
287
|
response.raise_for_status()
|
|
288
|
-
return response.json()
|
|
288
|
+
return cast(dict[Any, Any], response.json())
|
|
289
289
|
|
|
290
290
|
def __del__(self) -> None:
|
|
291
291
|
"""Close HTTP client on cleanup."""
|
|
@@ -7,7 +7,7 @@ mode for development and testing. Live trading requires explicit opt-in.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
|
-
from typing import Literal
|
|
10
|
+
from typing import Any, Literal, cast
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
13
|
from alpaca_trade_api import REST
|
|
@@ -15,7 +15,7 @@ try:
|
|
|
15
15
|
ALPACA_AVAILABLE = True
|
|
16
16
|
except ImportError:
|
|
17
17
|
ALPACA_AVAILABLE = False
|
|
18
|
-
REST = None
|
|
18
|
+
REST = None
|
|
19
19
|
|
|
20
20
|
from ..base import BrokerageProvider
|
|
21
21
|
|
|
@@ -308,14 +308,14 @@ class AlpacaBrokerage(BrokerageProvider):
|
|
|
308
308
|
return self._extract_raw(watchlist)
|
|
309
309
|
|
|
310
310
|
@staticmethod
|
|
311
|
-
def _extract_raw(obj) -> dict:
|
|
311
|
+
def _extract_raw(obj: Any) -> dict[Any, Any]:
|
|
312
312
|
"""Extract raw dict from Alpaca entity object.
|
|
313
313
|
|
|
314
314
|
Alpaca entities have a _raw attribute with the API response data.
|
|
315
315
|
"""
|
|
316
316
|
if hasattr(obj, "_raw"):
|
|
317
|
-
return obj._raw
|
|
317
|
+
return cast(dict[Any, Any], obj._raw)
|
|
318
318
|
elif hasattr(obj, "__dict__"):
|
|
319
|
-
return obj.__dict__
|
|
319
|
+
return cast(dict[Any, Any], obj.__dict__)
|
|
320
320
|
else:
|
|
321
|
-
return obj
|
|
321
|
+
return cast(dict[Any, Any], obj)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
|
|
3
5
|
import ccxt
|
|
4
6
|
|
|
5
7
|
from ..base import CryptoDataProvider
|
|
@@ -15,14 +17,14 @@ class CCXTCryptoData(CryptoDataProvider):
|
|
|
15
17
|
# Defer load_markets to first call to avoid network on construction
|
|
16
18
|
self._markets_loaded = False
|
|
17
19
|
|
|
18
|
-
def ticker(self, symbol_pair: str) -> dict:
|
|
20
|
+
def ticker(self, symbol_pair: str) -> dict[Any, Any]:
|
|
19
21
|
if not self._markets_loaded:
|
|
20
22
|
self.exchange.load_markets()
|
|
21
23
|
self._markets_loaded = True
|
|
22
|
-
return self.exchange.fetch_ticker(symbol_pair)
|
|
24
|
+
return cast(dict[Any, Any], self.exchange.fetch_ticker(symbol_pair))
|
|
23
25
|
|
|
24
26
|
def ohlcv(self, symbol_pair: str, timeframe: str = "1d", limit: int = 100) -> list[list[float]]:
|
|
25
27
|
if not self._markets_loaded:
|
|
26
28
|
self.exchange.load_markets()
|
|
27
29
|
self._markets_loaded = True
|
|
28
|
-
return self.exchange.fetch_ohlcv(symbol_pair, timeframe=timeframe, limit=limit)
|
|
30
|
+
return cast(list[list[float]], self.exchange.fetch_ohlcv(symbol_pair, timeframe=timeframe, limit=limit))
|