fin-infra 0.1.64__tar.gz → 0.1.65__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.64 → fin_infra-0.1.65}/PKG-INFO +1 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/pyproject.toml +1 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/spending.py +1 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/add.py +13 -15
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/llm_layer.py +2 -2
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/chat/__init__.py +1 -10
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/chat/ease.py +1 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/chat/planning.py +57 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/crypto/insights.py +1 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/ease.py +34 -13
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/__init__.py +6 -6
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/base.py +98 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/credit/experian.py +5 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/add.py +10 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/detectors_llm.py +1 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/insights.py +1 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/normalizers.py +1 -1
- {fin_infra-0.1.64 → fin_infra-0.1.65}/LICENSE +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/README.md +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/__main__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/cash_flow.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/ease.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/portfolio.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/projections.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/rebalancing.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/savings.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/scenarios.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/banking/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/banking/history.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/banking/utils.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/brokerage/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/alerts.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/ease.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/templates.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/tracker.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cashflows/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cashflows/core.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/ease.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/engine.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/rules.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/taxonomy.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cli/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cli/cmds/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cli/cmds/scaffold_cmds.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/clients/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/clients/base.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/clients/plaid.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/compliance/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/auth.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/client.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/parser.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/provider.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/mock.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/crypto/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/analysis.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/ease.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/ocr.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/storage.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/exceptions.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/funding.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/management.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/milestones.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/insights/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/insights/aggregator.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/insights/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/ease.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/providers/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/providers/base.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/providers/plaid.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/providers/snaptrade.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/markets/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/accounts.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/brokerage.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/candle.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/credit.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/money.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/quotes.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/tax.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/transactions.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/aggregator.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/calculator.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/goals.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/insights.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/currency_converter.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/providers/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/providers/exchangerate.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/providers/static_mappings.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/symbol_resolver.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/obs/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/obs/classifier.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/banking/base.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/banking/plaid_client.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/banking/teller_client.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/brokerage/alpaca.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/brokerage/base.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/alphavantage.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/base.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/ccxt_crypto.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/coingecko.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/yahoo.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/registry.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/tax/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/tax/irs.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/tax/mock.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/tax/taxbit.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/py.typed +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/detector.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/ease.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/normalizer.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/summary.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/scaffold/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/scaffold/budgets.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/scaffold/goals.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/audit.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/encryption.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/models.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/pii_filter.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/pii_patterns.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/token_store.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/settings.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/tax/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/tax/add.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/tax/tlh.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/utils/__init__.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/utils/http.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/utils/retry.py +0 -0
- {fin_infra-0.1.64 → fin_infra-0.1.65}/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.65
|
|
4
4
|
Summary: Financial infrastructure toolkit: banking connections, market data, credit, cashflows, and brokerage integrations
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: finance,banking,plaid,brokerage,markets,credit,tax,cashflow,fintech,infra
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "fin-infra"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.65"
|
|
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"
|
|
@@ -470,7 +470,7 @@ async def generate_spending_insights(
|
|
|
470
470
|
|
|
471
471
|
# Try to import ai-infra LLM (optional dependency)
|
|
472
472
|
try:
|
|
473
|
-
from ai_infra.llm import LLM
|
|
473
|
+
from ai_infra.llm import LLM # type: ignore[attr-defined]
|
|
474
474
|
except ImportError:
|
|
475
475
|
# Graceful degradation: return rule-based insights
|
|
476
476
|
return _generate_rule_based_insights(spending_insight, user_context)
|
|
@@ -136,21 +136,19 @@ def add_categorization(
|
|
|
136
136
|
categories = get_all_categories()
|
|
137
137
|
|
|
138
138
|
# Return category metadata
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
for cat in categories
|
|
153
|
-
]
|
|
139
|
+
result = []
|
|
140
|
+
for cat in categories:
|
|
141
|
+
meta = get_category_metadata(cat)
|
|
142
|
+
result.append(
|
|
143
|
+
{
|
|
144
|
+
"name": cat.value,
|
|
145
|
+
"group": meta.group.value if meta else None,
|
|
146
|
+
"display_name": meta.display_name if meta else cat.value,
|
|
147
|
+
"description": meta.description if meta else None,
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return result
|
|
154
152
|
|
|
155
153
|
@router.get("/stats", response_model=CategoryStats)
|
|
156
154
|
async def get_stats():
|
|
@@ -20,8 +20,8 @@ from pydantic import BaseModel, Field
|
|
|
20
20
|
|
|
21
21
|
# ai-infra imports
|
|
22
22
|
try:
|
|
23
|
-
from ai_infra.llm import LLM
|
|
24
|
-
from ai_infra.llm.providers import Providers
|
|
23
|
+
from ai_infra.llm import LLM # type: ignore[attr-defined]
|
|
24
|
+
from ai_infra.llm.providers import Providers # type: ignore[attr-defined]
|
|
25
25
|
except ImportError:
|
|
26
26
|
raise ImportError("ai-infra not installed. Install with: pip install ai-infra")
|
|
27
27
|
|
|
@@ -149,15 +149,6 @@ def add_financial_conversation(
|
|
|
149
149
|
# TODO: Get user_id from svc-infra auth context
|
|
150
150
|
user_id = "demo_user" # Placeholder
|
|
151
151
|
|
|
152
|
-
# Check for sensitive content
|
|
153
|
-
if is_sensitive_question(request.question):
|
|
154
|
-
return ConversationResponse(
|
|
155
|
-
answer="I cannot process requests containing sensitive information like SSNs, passwords, or account numbers. Please rephrase your question without this information.",
|
|
156
|
-
follow_up_questions=[],
|
|
157
|
-
conversation_id=f"{user_id}_denied",
|
|
158
|
-
disclaimer="This is an automated safety response.",
|
|
159
|
-
)
|
|
160
|
-
|
|
161
152
|
# Ask conversation
|
|
162
153
|
response = await conversation.ask(
|
|
163
154
|
user_id=user_id,
|
|
@@ -173,7 +164,7 @@ def add_financial_conversation(
|
|
|
173
164
|
# TODO: Get user_id from svc-infra auth context
|
|
174
165
|
user_id = "demo_user"
|
|
175
166
|
context = await conversation._get_context(user_id)
|
|
176
|
-
return context.
|
|
167
|
+
return context.previous_exchanges if context else []
|
|
177
168
|
|
|
178
169
|
@router.delete("/history")
|
|
179
170
|
async def clear_history():
|
|
@@ -338,8 +338,65 @@ class FinancialPlanningConversation:
|
|
|
338
338
|
# Save updated context (24h TTL)
|
|
339
339
|
await self._save_context(context)
|
|
340
340
|
|
|
341
|
+
# Track latest session id for convenience endpoints (history/clear).
|
|
342
|
+
# Best-effort: failures here must not break the chat response.
|
|
343
|
+
try:
|
|
344
|
+
await self.cache.set(
|
|
345
|
+
self._latest_session_key(user_id),
|
|
346
|
+
context.session_id,
|
|
347
|
+
ttl=86400,
|
|
348
|
+
)
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
341
352
|
return response
|
|
342
353
|
|
|
354
|
+
# ---------------------------------------------------------------------
|
|
355
|
+
# Backward-compatible context helpers
|
|
356
|
+
# ---------------------------------------------------------------------
|
|
357
|
+
|
|
358
|
+
def _latest_session_key(self, user_id: str) -> str:
|
|
359
|
+
return f"fin_infra:conversation_latest_session:{user_id}"
|
|
360
|
+
|
|
361
|
+
async def _get_latest_session_id(self, user_id: str) -> str | None:
|
|
362
|
+
try:
|
|
363
|
+
value = await self.cache.get(self._latest_session_key(user_id))
|
|
364
|
+
except Exception:
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
if value is None:
|
|
368
|
+
return None
|
|
369
|
+
if isinstance(value, bytes):
|
|
370
|
+
try:
|
|
371
|
+
return value.decode("utf-8")
|
|
372
|
+
except Exception:
|
|
373
|
+
return None
|
|
374
|
+
if isinstance(value, str):
|
|
375
|
+
return value
|
|
376
|
+
return str(value)
|
|
377
|
+
|
|
378
|
+
async def _get_context(
|
|
379
|
+
self, user_id: str, session_id: str | None = None
|
|
380
|
+
) -> ConversationContext | None:
|
|
381
|
+
if session_id is None:
|
|
382
|
+
session_id = await self._get_latest_session_id(user_id)
|
|
383
|
+
if session_id is None:
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
return await self._load_context(user_id=user_id, session_id=session_id)
|
|
387
|
+
|
|
388
|
+
async def _clear_context(self, user_id: str, session_id: str | None = None) -> None:
|
|
389
|
+
if session_id is None:
|
|
390
|
+
session_id = await self._get_latest_session_id(user_id)
|
|
391
|
+
|
|
392
|
+
if session_id is not None:
|
|
393
|
+
await self.clear_session(user_id=user_id, session_id=session_id)
|
|
394
|
+
|
|
395
|
+
try:
|
|
396
|
+
await self.cache.delete(self._latest_session_key(user_id))
|
|
397
|
+
except Exception:
|
|
398
|
+
pass
|
|
399
|
+
|
|
343
400
|
async def _load_context(
|
|
344
401
|
self,
|
|
345
402
|
user_id: str,
|
|
@@ -121,6 +121,15 @@ class NetWorthTracker:
|
|
|
121
121
|
self.goal_tracker = goal_tracker
|
|
122
122
|
self.conversation = conversation
|
|
123
123
|
|
|
124
|
+
# Configuration set by easy_net_worth(); declared here for type checkers.
|
|
125
|
+
self.snapshot_schedule: str = "daily"
|
|
126
|
+
self.change_threshold_percent: float = 5.0
|
|
127
|
+
self.change_threshold_amount: float = 10000.0
|
|
128
|
+
self.enable_llm: bool = False
|
|
129
|
+
self.llm_provider: str | None = None
|
|
130
|
+
self.llm_model: str | None = None
|
|
131
|
+
self.config: dict[str, Any] = {}
|
|
132
|
+
|
|
124
133
|
async def calculate_net_worth(
|
|
125
134
|
self,
|
|
126
135
|
user_id: str,
|
|
@@ -368,12 +377,20 @@ def easy_net_worth(
|
|
|
368
377
|
|
|
369
378
|
if enable_llm:
|
|
370
379
|
try:
|
|
371
|
-
from ai_infra.llm import LLM
|
|
380
|
+
from ai_infra.llm.llm import LLM # type: ignore[attr-defined]
|
|
372
381
|
except ImportError:
|
|
373
382
|
raise ImportError(
|
|
374
383
|
"LLM features require ai-infra package. " "Install with: pip install ai-infra"
|
|
375
384
|
)
|
|
376
385
|
|
|
386
|
+
cache = None
|
|
387
|
+
try:
|
|
388
|
+
from svc_infra.cache import get_cache
|
|
389
|
+
|
|
390
|
+
cache = get_cache()
|
|
391
|
+
except Exception:
|
|
392
|
+
cache = None
|
|
393
|
+
|
|
377
394
|
# Determine default model
|
|
378
395
|
default_models = {
|
|
379
396
|
"google": "gemini-2.0-flash-exp",
|
|
@@ -416,18 +433,22 @@ def easy_net_worth(
|
|
|
416
433
|
# goals.management not yet implemented, skip
|
|
417
434
|
pass
|
|
418
435
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
436
|
+
if cache is not None:
|
|
437
|
+
try:
|
|
438
|
+
from fin_infra.conversation import FinancialPlanningConversation
|
|
439
|
+
|
|
440
|
+
conversation = FinancialPlanningConversation(
|
|
441
|
+
llm=llm,
|
|
442
|
+
cache=cache, # Required for context storage
|
|
443
|
+
provider=llm_provider,
|
|
444
|
+
model_name=model_name,
|
|
445
|
+
)
|
|
446
|
+
except ImportError:
|
|
447
|
+
# conversation module not yet implemented, skip
|
|
448
|
+
pass
|
|
449
|
+
except Exception:
|
|
450
|
+
# Cache not configured or other runtime issue; skip optional conversation wiring.
|
|
451
|
+
pass
|
|
431
452
|
|
|
432
453
|
# Create tracker
|
|
433
454
|
tracker = NetWorthTracker(
|
|
@@ -150,14 +150,14 @@ def add_normalization(
|
|
|
150
150
|
):
|
|
151
151
|
"""Convert amount between currencies."""
|
|
152
152
|
try:
|
|
153
|
-
result = await converter.
|
|
153
|
+
result = await converter.convert_with_details(amount, from_currency, to_currency)
|
|
154
154
|
return {
|
|
155
|
-
"amount": amount,
|
|
156
|
-
"from_currency": from_currency,
|
|
157
|
-
"to_currency": to_currency,
|
|
158
|
-
"result": result.
|
|
155
|
+
"amount": result.amount,
|
|
156
|
+
"from_currency": result.from_currency,
|
|
157
|
+
"to_currency": result.to_currency,
|
|
158
|
+
"result": result.converted,
|
|
159
159
|
"rate": result.rate,
|
|
160
|
-
"timestamp": result.
|
|
160
|
+
"timestamp": result.date.isoformat() if result.date else None,
|
|
161
161
|
}
|
|
162
162
|
except CurrencyNotSupportedError as e:
|
|
163
163
|
raise HTTPException(status_code=400, detail=str(e))
|
|
@@ -67,7 +67,15 @@ class BankingProvider(ABC):
|
|
|
67
67
|
class BrokerageProvider(ABC):
|
|
68
68
|
@abstractmethod
|
|
69
69
|
def submit_order(
|
|
70
|
-
self,
|
|
70
|
+
self,
|
|
71
|
+
symbol: str,
|
|
72
|
+
qty: float,
|
|
73
|
+
side: str,
|
|
74
|
+
type_: str,
|
|
75
|
+
time_in_force: str,
|
|
76
|
+
limit_price: float | None = None,
|
|
77
|
+
stop_price: float | None = None,
|
|
78
|
+
client_order_id: str | None = None,
|
|
71
79
|
) -> dict:
|
|
72
80
|
pass
|
|
73
81
|
|
|
@@ -75,6 +83,71 @@ class BrokerageProvider(ABC):
|
|
|
75
83
|
def positions(self) -> Iterable[dict]:
|
|
76
84
|
pass
|
|
77
85
|
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def get_account(self) -> dict:
|
|
88
|
+
"""Get trading account information."""
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def get_position(self, symbol: str) -> dict:
|
|
93
|
+
"""Get position for a specific symbol."""
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def close_position(self, symbol: str) -> dict:
|
|
98
|
+
"""Close a position (market sell/cover)."""
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def list_orders(self, status: str = "open", limit: int = 50) -> list[dict]:
|
|
103
|
+
"""List orders."""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def get_order(self, order_id: str) -> dict:
|
|
108
|
+
"""Get order by ID."""
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
@abstractmethod
|
|
112
|
+
def cancel_order(self, order_id: str) -> None:
|
|
113
|
+
"""Cancel an order."""
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def get_portfolio_history(self, period: str = "1M", timeframe: str = "1D") -> dict:
|
|
118
|
+
"""Get portfolio value history."""
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def create_watchlist(self, name: str, symbols: list[str] | None = None) -> dict:
|
|
123
|
+
"""Create a new watchlist."""
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
@abstractmethod
|
|
127
|
+
def list_watchlists(self) -> list[dict]:
|
|
128
|
+
"""List all watchlists."""
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
@abstractmethod
|
|
132
|
+
def get_watchlist(self, watchlist_id: str) -> dict:
|
|
133
|
+
"""Get a watchlist by ID."""
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def delete_watchlist(self, watchlist_id: str) -> None:
|
|
138
|
+
"""Delete a watchlist."""
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
@abstractmethod
|
|
142
|
+
def add_to_watchlist(self, watchlist_id: str, symbol: str) -> dict:
|
|
143
|
+
"""Add a symbol to a watchlist."""
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
@abstractmethod
|
|
147
|
+
def remove_from_watchlist(self, watchlist_id: str, symbol: str) -> dict:
|
|
148
|
+
"""Remove a symbol from a watchlist."""
|
|
149
|
+
pass
|
|
150
|
+
|
|
78
151
|
|
|
79
152
|
class IdentityProvider(ABC):
|
|
80
153
|
@abstractmethod
|
|
@@ -91,6 +164,11 @@ class CreditProvider(ABC):
|
|
|
91
164
|
def get_credit_score(self, user_id: str, **kwargs) -> dict | None:
|
|
92
165
|
pass
|
|
93
166
|
|
|
167
|
+
@abstractmethod
|
|
168
|
+
def get_credit_report(self, user_id: str, **kwargs) -> dict | None:
|
|
169
|
+
"""Retrieve full credit report for a user."""
|
|
170
|
+
pass
|
|
171
|
+
|
|
94
172
|
|
|
95
173
|
class TaxProvider(ABC):
|
|
96
174
|
"""Provider for tax data and document retrieval."""
|
|
@@ -100,6 +178,11 @@ class TaxProvider(ABC):
|
|
|
100
178
|
"""Retrieve tax forms for a user and tax year."""
|
|
101
179
|
pass
|
|
102
180
|
|
|
181
|
+
@abstractmethod
|
|
182
|
+
def get_tax_documents(self, user_id: str, tax_year: int, **kwargs) -> list[dict]:
|
|
183
|
+
"""Retrieve tax documents for a user and tax year."""
|
|
184
|
+
pass
|
|
185
|
+
|
|
103
186
|
@abstractmethod
|
|
104
187
|
def get_tax_document(self, document_id: str, **kwargs) -> dict:
|
|
105
188
|
"""Retrieve a specific tax document by ID."""
|
|
@@ -110,6 +193,20 @@ class TaxProvider(ABC):
|
|
|
110
193
|
"""Calculate capital gains from crypto transactions."""
|
|
111
194
|
pass
|
|
112
195
|
|
|
196
|
+
@abstractmethod
|
|
197
|
+
def calculate_tax_liability(
|
|
198
|
+
self,
|
|
199
|
+
user_id: str,
|
|
200
|
+
income: float,
|
|
201
|
+
deductions: float,
|
|
202
|
+
filing_status: str,
|
|
203
|
+
tax_year: int,
|
|
204
|
+
state: str | None = None,
|
|
205
|
+
**kwargs,
|
|
206
|
+
) -> dict:
|
|
207
|
+
"""Calculate estimated tax liability."""
|
|
208
|
+
pass
|
|
209
|
+
|
|
113
210
|
|
|
114
211
|
class InvestmentProvider(ABC):
|
|
115
212
|
"""Provider for investment holdings and portfolio data (Plaid, SnapTrade).
|
|
@@ -11,3 +11,8 @@ class ExperianCredit(CreditProvider):
|
|
|
11
11
|
self, user_id: str, **kwargs
|
|
12
12
|
) -> dict | None: # pragma: no cover - placeholder
|
|
13
13
|
return None
|
|
14
|
+
|
|
15
|
+
def get_credit_report(
|
|
16
|
+
self, user_id: str, **kwargs
|
|
17
|
+
) -> dict | None: # pragma: no cover - placeholder
|
|
18
|
+
return None
|
|
@@ -403,7 +403,16 @@ def add_recurring_detection(
|
|
|
403
403
|
|
|
404
404
|
# Generate insights with LLM
|
|
405
405
|
# TODO: Pass user_id for better caching (currently uses subscriptions hash)
|
|
406
|
-
|
|
406
|
+
insights_generator = detector.insights_generator
|
|
407
|
+
if insights_generator is None:
|
|
408
|
+
from fastapi import HTTPException
|
|
409
|
+
|
|
410
|
+
raise HTTPException(
|
|
411
|
+
status_code=500,
|
|
412
|
+
detail="Subscription insights generator not configured (enable_llm=True required).",
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
insights = await insights_generator.generate(subscriptions)
|
|
407
416
|
|
|
408
417
|
return insights
|
|
409
418
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl
RENAMED
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl
RENAMED
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl
RENAMED
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/README.md
RENAMED
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/__init__.py
RENAMED
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/models.py.tmpl
RENAMED
|
File without changes
|
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/__init__.py
RENAMED
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl
RENAMED
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl
RENAMED
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/providers/static_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|