fin-infra 0.2.1__tar.gz → 0.2.2__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.2.1 → fin_infra-0.2.2}/PKG-INFO +13 -12
- {fin_infra-0.2.1 → fin_infra-0.2.2}/README.md +12 -11
- {fin_infra-0.2.1 → fin_infra-0.2.2}/pyproject.toml +1 -1
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/add.py +3 -3
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/portfolio.py +12 -12
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/brokerage/__init__.py +2 -2
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/categorization/__init__.py +1 -1
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/categorization/engine.py +1 -1
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/categorization/llm_layer.py +4 -4
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/credit/experian/parser.py +5 -5
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/crypto/__init__.py +2 -2
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/models.py +2 -2
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/__init__.py +2 -2
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/providers/base.py +4 -4
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/markets/__init__.py +2 -2
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/__init__.py +1 -1
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/aggregator.py +1 -1
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/calculator.py +1 -1
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/insights.py +3 -3
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/normalization/__init__.py +2 -2
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/normalization/currency_converter.py +3 -3
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/normalization/providers/static_mappings.py +1 -1
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/obs/classifier.py +2 -2
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/add.py +1 -1
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/detectors_llm.py +5 -5
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/ease.py +4 -4
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/insights.py +14 -14
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/normalizer.py +6 -6
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/normalizers.py +26 -26
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/scaffold/goals.py +4 -4
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/security/pii_filter.py +10 -10
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/tax/tlh.py +5 -5
- {fin_infra-0.2.1 → fin_infra-0.2.2}/LICENSE +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/__main__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/cash_flow.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/ease.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/projections.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/rebalancing.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/savings.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/scenarios.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/analytics/spending.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/banking/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/banking/history.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/banking/utils.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/alerts.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/ease.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/templates.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/budgets/tracker.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/cashflows/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/cashflows/core.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/categorization/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/categorization/ease.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/categorization/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/categorization/rules.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/categorization/taxonomy.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/chat/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/chat/ease.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/chat/planning.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/cli/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/cli/cmds/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/cli/cmds/scaffold_cmds.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/clients/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/clients/base.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/clients/plaid.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/compliance/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/credit/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/credit/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/credit/experian/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/credit/experian/auth.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/credit/experian/client.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/credit/experian/provider.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/credit/mock.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/crypto/insights.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/documents/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/documents/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/documents/analysis.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/documents/ease.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/documents/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/documents/ocr.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/documents/storage.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/exceptions.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/funding.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/management.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/milestones.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/insights/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/insights/aggregator.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/insights/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/ease.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/providers/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/providers/plaid.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/providers/snaptrade.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/accounts.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/brokerage.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/candle.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/credit.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/money.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/quotes.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/tax.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/models/transactions.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/ease.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/goals.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/normalization/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/normalization/providers/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/normalization/providers/exchangerate.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/normalization/symbol_resolver.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/obs/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/banking/base.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/banking/plaid_client.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/banking/teller_client.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/base.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/brokerage/alpaca.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/brokerage/base.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/credit/experian.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/market/alphavantage.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/market/base.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/market/ccxt_crypto.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/market/coingecko.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/market/yahoo.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/registry.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/tax/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/tax/irs.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/tax/mock.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/providers/tax/taxbit.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/py.typed +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/detector.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/recurring/summary.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/scaffold/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/scaffold/budgets.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/security/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/security/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/security/audit.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/security/encryption.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/security/models.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/security/pii_patterns.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/security/token_store.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/settings.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/tax/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/tax/add.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/utils/__init__.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/utils/deprecation.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/utils/http.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/utils/retry.py +0 -0
- {fin_infra-0.2.1 → fin_infra-0.2.2}/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.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
@@ -47,26 +47,27 @@ Project-URL: Issues, https://github.com/nfraxlab/fin-infra/issues
|
|
|
47
47
|
Project-URL: Repository, https://github.com/nfraxlab/fin-infra
|
|
48
48
|
Description-Content-Type: text/markdown
|
|
49
49
|
|
|
50
|
-
<div align="center">
|
|
51
|
-
|
|
52
50
|
# fin-infra
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
**Financial data infrastructure for fintech apps.**
|
|
53
|
+
|
|
55
54
|
[](https://pypi.org/project/fin-infra/)
|
|
55
|
+
[](https://github.com/nfraxlab/fin-infra/actions/workflows/ci.yml)
|
|
56
56
|
[](https://pypi.org/project/fin-infra/)
|
|
57
57
|
[](LICENSE)
|
|
58
|
-
[](https://pypi.org/project/fin-infra/)
|
|
59
|
-
[](https://codecov.io/gh/nfraxlab/fin-infra)
|
|
60
|
-
|
|
61
|
-
### Financial data infrastructure for fintech apps
|
|
62
58
|
|
|
63
|
-
|
|
59
|
+
## Overview
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
Banking, investments, market data, credit scores, and financial calculations in one toolkit.
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
### Key Features
|
|
68
64
|
|
|
69
|
-
|
|
65
|
+
- **Banking** - Plaid/Teller integration, accounts, transactions
|
|
66
|
+
- **Investments** - Holdings, portfolio data, real P/L with cost basis
|
|
67
|
+
- **Market Data** - Stocks, crypto, forex quotes and history
|
|
68
|
+
- **Credit** - Credit scores and monitoring
|
|
69
|
+
- **Analytics** - Cash flow, savings rate, spending insights
|
|
70
|
+
- **Cashflows** - NPV, IRR, loan amortization calculations
|
|
70
71
|
|
|
71
72
|
## Why fin-infra?
|
|
72
73
|
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
1
|
# fin-infra
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
**Financial data infrastructure for fintech apps.**
|
|
4
|
+
|
|
6
5
|
[](https://pypi.org/project/fin-infra/)
|
|
6
|
+
[](https://github.com/nfraxlab/fin-infra/actions/workflows/ci.yml)
|
|
7
7
|
[](https://pypi.org/project/fin-infra/)
|
|
8
8
|
[](LICENSE)
|
|
9
|
-
[](https://pypi.org/project/fin-infra/)
|
|
10
|
-
[](https://codecov.io/gh/nfraxlab/fin-infra)
|
|
11
|
-
|
|
12
|
-
### Financial data infrastructure for fintech apps
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
## Overview
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
Banking, investments, market data, credit scores, and financial calculations in one toolkit.
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
### Key Features
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
- **Banking** - Plaid/Teller integration, accounts, transactions
|
|
17
|
+
- **Investments** - Holdings, portfolio data, real P/L with cost basis
|
|
18
|
+
- **Market Data** - Stocks, crypto, forex quotes and history
|
|
19
|
+
- **Credit** - Credit scores and monitoring
|
|
20
|
+
- **Analytics** - Cash flow, savings rate, spending insights
|
|
21
|
+
- **Cashflows** - NPV, IRR, loan amortization calculations
|
|
21
22
|
|
|
22
23
|
## Why fin-infra?
|
|
23
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "fin-infra"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
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"
|
|
@@ -239,9 +239,9 @@ def add_analytics(
|
|
|
239
239
|
|
|
240
240
|
Note:
|
|
241
241
|
Real holdings provide:
|
|
242
|
-
- Accurate cost basis
|
|
243
|
-
- Security types
|
|
244
|
-
- Current values
|
|
242
|
+
- Accurate cost basis -> real profit/loss
|
|
243
|
+
- Security types -> precise asset allocation
|
|
244
|
+
- Current values -> live portfolio tracking
|
|
245
245
|
"""
|
|
246
246
|
# If with_holdings requested and investment provider available
|
|
247
247
|
if with_holdings:
|
|
@@ -527,10 +527,10 @@ def portfolio_metrics_with_holdings(holdings: list) -> PortfolioMetrics:
|
|
|
527
527
|
PortfolioMetrics with real portfolio analysis
|
|
528
528
|
|
|
529
529
|
Real Data Advantages:
|
|
530
|
-
- Actual cost basis
|
|
531
|
-
- Real security types
|
|
532
|
-
- Current market values
|
|
533
|
-
- No mock data
|
|
530
|
+
- Actual cost basis -> accurate P/L calculations
|
|
531
|
+
- Real security types -> precise asset allocation
|
|
532
|
+
- Current market values -> live portfolio value
|
|
533
|
+
- No mock data -> production-ready analytics
|
|
534
534
|
|
|
535
535
|
Limitations:
|
|
536
536
|
- Day/YTD/MTD returns require historical snapshots (not in holdings)
|
|
@@ -652,7 +652,7 @@ def calculate_day_change_with_snapshot(
|
|
|
652
652
|
Function matches holdings by account_id + security_id for accurate tracking
|
|
653
653
|
of individual position changes (accounts for buys/sells, not just price moves).
|
|
654
654
|
"""
|
|
655
|
-
# Build lookup map for previous snapshot: (account_id, security_id)
|
|
655
|
+
# Build lookup map for previous snapshot: (account_id, security_id) -> value
|
|
656
656
|
previous_map = {}
|
|
657
657
|
for holding in previous_snapshot:
|
|
658
658
|
key = (holding.account_id, holding.security.security_id)
|
|
@@ -703,13 +703,13 @@ def _calculate_allocation_from_holdings(
|
|
|
703
703
|
list[AssetAllocation] with asset_class, value, and percentage
|
|
704
704
|
|
|
705
705
|
Asset Class Mapping:
|
|
706
|
-
- equity
|
|
707
|
-
- etf
|
|
708
|
-
- mutual_fund
|
|
709
|
-
- bond
|
|
710
|
-
- cash
|
|
711
|
-
- derivative
|
|
712
|
-
- other
|
|
706
|
+
- equity -> Stocks
|
|
707
|
+
- etf -> Stocks (equity ETFs grouped with stocks)
|
|
708
|
+
- mutual_fund -> Bonds (conservative assumption)
|
|
709
|
+
- bond -> Bonds
|
|
710
|
+
- cash -> Cash
|
|
711
|
+
- derivative -> Other
|
|
712
|
+
- other -> Other
|
|
713
713
|
"""
|
|
714
714
|
from collections import defaultdict
|
|
715
715
|
|
|
@@ -54,8 +54,8 @@ def easy_brokerage(
|
|
|
54
54
|
[!] **SAFETY**: Defaults to paper trading mode. Live trading requires explicit mode="live".
|
|
55
55
|
|
|
56
56
|
Auto-detects provider based on environment variables:
|
|
57
|
-
1. If ALPACA_API_KEY and ALPACA_API_SECRET are set
|
|
58
|
-
2. Otherwise
|
|
57
|
+
1. If ALPACA_API_KEY and ALPACA_API_SECRET are set -> Alpaca
|
|
58
|
+
2. Otherwise -> Raises error (credentials required)
|
|
59
59
|
|
|
60
60
|
Args:
|
|
61
61
|
provider: Provider name ("alpaca"). If None, defaults to alpaca.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Transaction categorization module.
|
|
3
3
|
|
|
4
4
|
Provides ML-based categorization of merchant transactions into 56 categories
|
|
5
|
-
using a hybrid approach (exact match
|
|
5
|
+
using a hybrid approach (exact match -> regex -> sklearn Naive Bayes -> LLM).
|
|
6
6
|
|
|
7
7
|
Basic usage:
|
|
8
8
|
from fin_infra.categorization import categorize
|
|
@@ -197,7 +197,7 @@ class LLMCategorizer:
|
|
|
197
197
|
self._track_cost()
|
|
198
198
|
|
|
199
199
|
logger.info(
|
|
200
|
-
f"LLM categorized '{merchant_name}'
|
|
200
|
+
f"LLM categorized '{merchant_name}' -> {prediction.category} "
|
|
201
201
|
f"(confidence={prediction.confidence:.2f})"
|
|
202
202
|
)
|
|
203
203
|
|
|
@@ -252,7 +252,7 @@ class LLMCategorizer:
|
|
|
252
252
|
# Format few-shot examples
|
|
253
253
|
examples_text = "\n\n".join(
|
|
254
254
|
[
|
|
255
|
-
f'Merchant: "{merchant}"\n
|
|
255
|
+
f'Merchant: "{merchant}"\n-> Category: "{category}"\n-> Reasoning: "{reasoning}"'
|
|
256
256
|
for merchant, category, reasoning in FEW_SHOT_EXAMPLES
|
|
257
257
|
]
|
|
258
258
|
)
|
|
@@ -338,10 +338,10 @@ Return JSON with category, confidence, and reasoning."""
|
|
|
338
338
|
|
|
339
339
|
def reset_daily_cost(self):
|
|
340
340
|
"""Reset daily cost counter (called at midnight UTC)."""
|
|
341
|
-
logger.info(f"Resetting daily cost: ${self.daily_cost:.5f}
|
|
341
|
+
logger.info(f"Resetting daily cost: ${self.daily_cost:.5f} -> $0.00")
|
|
342
342
|
self.daily_cost = 0.0
|
|
343
343
|
|
|
344
344
|
def reset_monthly_cost(self):
|
|
345
345
|
"""Reset monthly cost counter (called on 1st of month)."""
|
|
346
|
-
logger.info(f"Resetting monthly cost: ${self.monthly_cost:.5f}
|
|
346
|
+
logger.info(f"Resetting monthly cost: ${self.monthly_cost:.5f} -> $0.00")
|
|
347
347
|
self.monthly_cost = 0.0
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Response parsers for Experian API data to fin_infra models.
|
|
2
2
|
|
|
3
3
|
Converts Experian API JSON responses to typed Pydantic models:
|
|
4
|
-
- parse_credit_score(): dict
|
|
5
|
-
- parse_credit_report(): dict
|
|
6
|
-
- parse_account(): dict
|
|
7
|
-
- parse_inquiry(): dict
|
|
8
|
-
- parse_public_record(): dict
|
|
4
|
+
- parse_credit_score(): dict -> CreditScore
|
|
5
|
+
- parse_credit_report(): dict -> CreditReport
|
|
6
|
+
- parse_account(): dict -> CreditAccount
|
|
7
|
+
- parse_inquiry(): dict -> CreditInquiry
|
|
8
|
+
- parse_public_record(): dict -> PublicRecord
|
|
9
9
|
|
|
10
10
|
Example:
|
|
11
11
|
>>> data = await client.get_credit_score("user123")
|
|
@@ -29,8 +29,8 @@ def easy_crypto(
|
|
|
29
29
|
"""Create a crypto data provider with zero or minimal configuration.
|
|
30
30
|
|
|
31
31
|
Auto-detects provider based on environment variables:
|
|
32
|
-
1. If COINGECKO_API_KEY is set
|
|
33
|
-
2. Otherwise
|
|
32
|
+
1. If COINGECKO_API_KEY is set -> CoinGecko Pro
|
|
33
|
+
2. Otherwise -> CoinGecko Free (no key needed)
|
|
34
34
|
|
|
35
35
|
Args:
|
|
36
36
|
provider: Provider name ("coingecko"). If None, defaults to coingecko.
|
|
@@ -93,8 +93,8 @@ class FundingSource(BaseModel):
|
|
|
93
93
|
|
|
94
94
|
Supports split allocation:
|
|
95
95
|
- Multiple accounts can fund one goal (e.g., savings + checking)
|
|
96
|
-
- One account can fund multiple goals (e.g., savings
|
|
97
|
-
- Allocation percentages must sum to
|
|
96
|
+
- One account can fund multiple goals (e.g., savings -> emergency + vacation)
|
|
97
|
+
- Allocation percentages must sum to <=100% per account
|
|
98
98
|
"""
|
|
99
99
|
|
|
100
100
|
goal_id: str = Field(..., description="Goal identifier")
|
|
@@ -74,8 +74,8 @@ def easy_investments(
|
|
|
74
74
|
InvestmentProvider instance for fetching holdings, transactions, securities.
|
|
75
75
|
|
|
76
76
|
Environment detection order:
|
|
77
|
-
1. If PLAID_CLIENT_ID set
|
|
78
|
-
2. If SNAPTRADE_CLIENT_ID set
|
|
77
|
+
1. If PLAID_CLIENT_ID set -> Plaid
|
|
78
|
+
2. If SNAPTRADE_CLIENT_ID set -> SnapTrade
|
|
79
79
|
3. Default: Plaid (most common)
|
|
80
80
|
|
|
81
81
|
Examples:
|
|
@@ -236,10 +236,10 @@ class InvestmentProvider(ABC):
|
|
|
236
236
|
Standardized SecurityType enum value
|
|
237
237
|
|
|
238
238
|
Example mappings:
|
|
239
|
-
Plaid: "equity"
|
|
240
|
-
Plaid: "mutual fund"
|
|
241
|
-
SnapTrade: "cs"
|
|
242
|
-
SnapTrade: "etf"
|
|
239
|
+
Plaid: "equity" -> SecurityType.equity
|
|
240
|
+
Plaid: "mutual fund" -> SecurityType.mutual_fund
|
|
241
|
+
SnapTrade: "cs" -> SecurityType.equity (common stock)
|
|
242
|
+
SnapTrade: "etf" -> SecurityType.etf
|
|
243
243
|
|
|
244
244
|
Note:
|
|
245
245
|
Override in provider-specific implementations for custom mappings.
|
|
@@ -33,8 +33,8 @@ def easy_market(
|
|
|
33
33
|
"""Create a market data provider with zero or minimal configuration.
|
|
34
34
|
|
|
35
35
|
Auto-detects provider based on environment variables:
|
|
36
|
-
1. If ALPHA_VANTAGE_API_KEY or ALPHAVANTAGE_API_KEY is set
|
|
37
|
-
2. Otherwise
|
|
36
|
+
1. If ALPHA_VANTAGE_API_KEY or ALPHAVANTAGE_API_KEY is set -> Alpha Vantage
|
|
37
|
+
2. Otherwise -> Yahoo Finance (no key needed)
|
|
38
38
|
|
|
39
39
|
Args:
|
|
40
40
|
provider: Provider name ("alphavantage" or "yahoo").
|
|
@@ -13,7 +13,7 @@ Calculates net worth by aggregating balances from multiple financial providers
|
|
|
13
13
|
|
|
14
14
|
**Key Features**:
|
|
15
15
|
- Multi-provider aggregation (banking + brokerage + crypto)
|
|
16
|
-
- Currency normalization (all currencies
|
|
16
|
+
- Currency normalization (all currencies -> USD)
|
|
17
17
|
- Historical snapshots (daily at midnight UTC)
|
|
18
18
|
- Change detection (>5% or >$10k triggers webhook)
|
|
19
19
|
- Asset allocation breakdown (pie charts)
|
|
@@ -59,7 +59,7 @@ class NetWorthAggregator:
|
|
|
59
59
|
- Multi-provider support (banking, brokerage, crypto)
|
|
60
60
|
- Parallel account fetching (faster performance)
|
|
61
61
|
- Graceful error handling (continue if one provider fails)
|
|
62
|
-
- Currency normalization (all
|
|
62
|
+
- Currency normalization (all -> base currency)
|
|
63
63
|
- Market value calculation (stocks/crypto)
|
|
64
64
|
|
|
65
65
|
**Example**:
|
|
@@ -3,7 +3,7 @@ Net Worth Calculator Module
|
|
|
3
3
|
|
|
4
4
|
Provides core calculation functions for net worth tracking:
|
|
5
5
|
- Net worth calculation (assets - liabilities)
|
|
6
|
-
- Currency normalization (all currencies
|
|
6
|
+
- Currency normalization (all currencies -> base currency)
|
|
7
7
|
- Asset allocation breakdown
|
|
8
8
|
- Change detection (amount + percentage)
|
|
9
9
|
|
|
@@ -167,7 +167,7 @@ Be specific with numbers. Cite percentage changes and dollar amounts.
|
|
|
167
167
|
Focus on actionable insights, not generic advice.
|
|
168
168
|
|
|
169
169
|
Example 1:
|
|
170
|
-
User: Net worth: $500k
|
|
170
|
+
User: Net worth: $500k -> $575k over 6 months. Assets: +$65k (investments +$60k, savings +$5k). Liabilities: -$10k (new mortgage).
|
|
171
171
|
Response: {
|
|
172
172
|
"summary": "Net worth increased 15% ($75k) over 6 months, driven primarily by strong investment performance.",
|
|
173
173
|
"period": "6 months",
|
|
@@ -191,7 +191,7 @@ Response: {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
Example 2:
|
|
194
|
-
User: Net worth: $100k
|
|
194
|
+
User: Net worth: $100k -> $95k over 3 months. Assets: -$2k (market down). Liabilities: +$3k (credit card debt).
|
|
195
195
|
Response: {
|
|
196
196
|
"summary": "Net worth decreased 5% ($5k) over 3 months due to market decline and rising credit card debt.",
|
|
197
197
|
"period": "3 months",
|
|
@@ -333,7 +333,7 @@ Given current allocation, age, and risk tolerance:
|
|
|
333
333
|
3. Provide specific rebalancing steps
|
|
334
334
|
|
|
335
335
|
Rule of thumb:
|
|
336
|
-
- Stock allocation = 100 - age (e.g., age 35
|
|
336
|
+
- Stock allocation = 100 - age (e.g., age 35 -> 65% stocks)
|
|
337
337
|
- Bonds for stability (increases with age)
|
|
338
338
|
- Cash for emergency fund (3-6 months expenses)
|
|
339
339
|
|
|
@@ -54,7 +54,7 @@ def easy_normalization(
|
|
|
54
54
|
Example:
|
|
55
55
|
>>> from fin_infra.normalization import easy_normalization
|
|
56
56
|
>>> resolver, converter = easy_normalization()
|
|
57
|
-
>>> ticker = await resolver.to_ticker("037833100") # CUSIP
|
|
57
|
+
>>> ticker = await resolver.to_ticker("037833100") # CUSIP -> AAPL
|
|
58
58
|
>>> eur = await converter.convert(100, "USD", "EUR") # 92.0
|
|
59
59
|
"""
|
|
60
60
|
global _resolver_instance, _converter_instance
|
|
@@ -107,7 +107,7 @@ def add_normalization(
|
|
|
107
107
|
>>> resolver, converter = add_normalization(app)
|
|
108
108
|
>>>
|
|
109
109
|
>>> # Routes available:
|
|
110
|
-
>>> # GET /normalize/symbol/037833100
|
|
110
|
+
>>> # GET /normalize/symbol/037833100 -> {"ticker": "AAPL", ...}
|
|
111
111
|
>>> # GET /normalize/convert?amount=100&from_currency=USD&to_currency=EUR
|
|
112
112
|
|
|
113
113
|
Integration with svc-infra:
|
|
@@ -71,7 +71,7 @@ class CurrencyConverter:
|
|
|
71
71
|
except ExchangeRateAPIError as e:
|
|
72
72
|
logger.error(f"Failed to convert {from_currency} to {to_currency}: {e}")
|
|
73
73
|
raise CurrencyNotSupportedError(
|
|
74
|
-
f"Conversion failed: {from_currency}
|
|
74
|
+
f"Conversion failed: {from_currency} -> {to_currency}"
|
|
75
75
|
) from e
|
|
76
76
|
|
|
77
77
|
async def get_rate(
|
|
@@ -108,9 +108,9 @@ class CurrencyConverter:
|
|
|
108
108
|
return rate_data.rate
|
|
109
109
|
|
|
110
110
|
except ExchangeRateAPIError as e:
|
|
111
|
-
logger.error(f"Failed to get rate {from_currency}
|
|
111
|
+
logger.error(f"Failed to get rate {from_currency} -> {to_currency}: {e}")
|
|
112
112
|
raise CurrencyNotSupportedError(
|
|
113
|
-
f"Rate not available: {from_currency}
|
|
113
|
+
f"Rate not available: {from_currency} -> {to_currency}"
|
|
114
114
|
) from e
|
|
115
115
|
|
|
116
116
|
async def get_rates(self, base_currency: str = "USD") -> dict[str, float]:
|
{fin_infra-0.2.1 → fin_infra-0.2.2}/src/fin_infra/normalization/providers/static_mappings.py
RENAMED
|
@@ -109,7 +109,7 @@ TICKER_TO_ISIN = {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
# Provider-specific symbol normalization
|
|
112
|
-
# Maps provider-specific format
|
|
112
|
+
# Maps provider-specific format -> standard ticker
|
|
113
113
|
PROVIDER_SYMBOL_MAP = {
|
|
114
114
|
"yahoo": {
|
|
115
115
|
# Yahoo Finance uses dashes for crypto
|
|
@@ -63,8 +63,8 @@ def financial_route_classifier(route_path: str, method: str) -> str:
|
|
|
63
63
|
svc-infra's add_observability route_classifier parameter.
|
|
64
64
|
|
|
65
65
|
Classification Logic:
|
|
66
|
-
- Financial routes (e.g., /banking/*, /market/*)
|
|
67
|
-
- All other routes
|
|
66
|
+
- Financial routes (e.g., /banking/*, /market/*) -> "financial"
|
|
67
|
+
- All other routes -> "public"
|
|
68
68
|
|
|
69
69
|
This allows Grafana dashboards to split metrics by route class:
|
|
70
70
|
- Filter by route_class="financial" for financial provider SLOs
|
|
@@ -109,7 +109,7 @@ def add_recurring_detection(
|
|
|
109
109
|
Detect recurring patterns in transaction history.
|
|
110
110
|
|
|
111
111
|
Analyzes transaction history for recurring subscriptions and bills using
|
|
112
|
-
3-layer hybrid detection (fixed
|
|
112
|
+
3-layer hybrid detection (fixed -> variable -> irregular).
|
|
113
113
|
|
|
114
114
|
**Example Request:**
|
|
115
115
|
```json
|
|
@@ -100,30 +100,30 @@ Examples:
|
|
|
100
100
|
1. Merchant: "City Electric"
|
|
101
101
|
Amounts: [$45, $52, $48, $55, $50, $49]
|
|
102
102
|
Dates: Monthly (15th ±7 days)
|
|
103
|
-
|
|
103
|
+
-> is_recurring: true, cadence: "monthly", range: (40, 60),
|
|
104
104
|
reasoning: "Seasonal winter heating variation", confidence: 0.85
|
|
105
105
|
|
|
106
106
|
2. Merchant: "T-Mobile"
|
|
107
107
|
Amounts: [$50, $50, $50, $78, $50, $50]
|
|
108
108
|
Dates: Monthly (20th ±3 days)
|
|
109
|
-
|
|
109
|
+
-> is_recurring: true, cadence: "monthly", range: (50, 80),
|
|
110
110
|
reasoning: "Occasional overage charge spike", confidence: 0.80
|
|
111
111
|
|
|
112
112
|
3. Merchant: "Random Store"
|
|
113
113
|
Amounts: [$10, $45, $23, $67, $12]
|
|
114
114
|
Dates: Irregular
|
|
115
|
-
|
|
115
|
+
-> is_recurring: false, reasoning: "Too much variance, no pattern", confidence: 0.95
|
|
116
116
|
|
|
117
117
|
4. Merchant: "Gas Company"
|
|
118
118
|
Amounts: [$45, $48, $50, $52, $120, $115]
|
|
119
119
|
Dates: Monthly
|
|
120
|
-
|
|
120
|
+
-> is_recurring: true, cadence: "monthly", range: (40, 120),
|
|
121
121
|
reasoning: "Winter heating season doubles bill", confidence: 0.80
|
|
122
122
|
|
|
123
123
|
5. Merchant: "Gym Membership"
|
|
124
124
|
Amounts: [$40, $40, $0, $40, $40]
|
|
125
125
|
Dates: Monthly
|
|
126
|
-
|
|
126
|
+
-> is_recurring: true, cadence: "monthly", range: (0, 40),
|
|
127
127
|
reasoning: "Annual fee waived one month", confidence: 0.75
|
|
128
128
|
|
|
129
129
|
Output format (JSON):
|
|
@@ -43,7 +43,7 @@ def easy_recurring_detection(
|
|
|
43
43
|
V2 Parameters (LLM Enhancement):
|
|
44
44
|
enable_llm: Enable LLM for merchant normalization and variable detection (default: False)
|
|
45
45
|
When False, uses V1 pattern-based only (fast, $0 cost)
|
|
46
|
-
When True, uses 4-layer hybrid (RapidFuzz
|
|
46
|
+
When True, uses 4-layer hybrid (RapidFuzz -> LLM normalization -> Statistical -> LLM variable detection)
|
|
47
47
|
llm_provider: LLM provider to use (default: "google")
|
|
48
48
|
Options: "google" (Gemini 2.0 Flash, cheapest), "openai" (GPT-4o-mini), "anthropic" (Claude 3.5 Haiku)
|
|
49
49
|
llm_model: Override default model for provider (default: None)
|
|
@@ -54,9 +54,9 @@ def easy_recurring_detection(
|
|
|
54
54
|
Higher values (0.9) call LLM more often (more accurate, higher cost)
|
|
55
55
|
Lower values (0.7) call LLM less often (less accurate, lower cost)
|
|
56
56
|
llm_cache_merchant_ttl: Merchant normalization cache TTL in seconds (default: 604800 = 7 days)
|
|
57
|
-
95% cache hit rate expected
|
|
57
|
+
95% cache hit rate expected -> most requests <1ms
|
|
58
58
|
llm_cache_insights_ttl: Insights generation cache TTL in seconds (default: 86400 = 24 hours)
|
|
59
|
-
80% cache hit rate expected
|
|
59
|
+
80% cache hit rate expected -> most requests <1ms
|
|
60
60
|
llm_max_cost_per_day: Daily budget cap in USD (default: $0.10)
|
|
61
61
|
Supports ~33k normalizations or ~1k variable detections per day
|
|
62
62
|
Sufficient for 100k+ users
|
|
@@ -80,7 +80,7 @@ def easy_recurring_detection(
|
|
|
80
80
|
>>> # V2: LLM-enhanced detection (better accuracy, minimal cost)
|
|
81
81
|
>>> detector = easy_recurring_detection(enable_llm=True)
|
|
82
82
|
>>> patterns = detector.detect_patterns(transactions)
|
|
83
|
-
>>> # Merchant normalization: "NFLX*SUB"
|
|
83
|
+
>>> # Merchant normalization: "NFLX*SUB" -> "Netflix" (90-95% accuracy)
|
|
84
84
|
>>> # Variable detection: Utility bills with seasonal variance (85-88% accuracy)
|
|
85
85
|
>>> # Cost: ~$0.003/user/year with caching
|
|
86
86
|
|
|
@@ -7,7 +7,7 @@ Provides on-demand insights for users:
|
|
|
7
7
|
- Cost-saving recommendations (bundle deals, unused subscriptions)
|
|
8
8
|
|
|
9
9
|
Uses ai-infra LLM with few-shot prompting.
|
|
10
|
-
Caches results for 24 hours (80% hit rate expected)
|
|
10
|
+
Caches results for 24 hours (80% hit rate expected) -> <1ms latency.
|
|
11
11
|
Triggered via GET /recurring/insights API endpoint (not automatic).
|
|
12
12
|
"""
|
|
13
13
|
|
|
@@ -105,23 +105,23 @@ Guidelines:
|
|
|
105
105
|
|
|
106
106
|
Examples:
|
|
107
107
|
1. Subscriptions: Netflix $15.99, Hulu $12.99, Disney+ $10.99, Spotify $9.99, Amazon Prime $14.99
|
|
108
|
-
|
|
108
|
+
-> "You have 5 subscriptions totaling $64.95/month. Consider the Disney+ bundle
|
|
109
109
|
(Disney+, Hulu, ESPN+ for $19.99) to save $29.98/month. Also, Amazon Prime
|
|
110
110
|
includes Prime Video - you may be able to cancel Netflix or Hulu."
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
-> total_monthly_cost: 64.95
|
|
112
|
+
-> potential_savings: 30.00
|
|
113
113
|
|
|
114
114
|
2. Subscriptions: Spotify $9.99, Apple Music $10.99
|
|
115
|
-
|
|
115
|
+
-> "You're paying for both Spotify and Apple Music ($20.98/month). Cancel one
|
|
116
116
|
to save $10.99/month."
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
-> total_monthly_cost: 20.98
|
|
118
|
+
-> potential_savings: 10.99
|
|
119
119
|
|
|
120
120
|
3. Subscriptions: LA Fitness $40, Planet Fitness $10
|
|
121
|
-
|
|
121
|
+
-> "You have 2 gym memberships totaling $50/month. Consider consolidating to
|
|
122
122
|
just Planet Fitness to save $40/month."
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
-> total_monthly_cost: 50.00
|
|
124
|
+
-> potential_savings: 40.00
|
|
125
125
|
|
|
126
126
|
Output format (JSON):
|
|
127
127
|
{
|
|
@@ -152,8 +152,8 @@ class SubscriptionInsightsGenerator:
|
|
|
152
152
|
LLM-based subscription insights generator with caching.
|
|
153
153
|
|
|
154
154
|
Layer 5 of 4-layer hybrid architecture (on-demand, optional):
|
|
155
|
-
1. Check cache first (80% hit rate, 24h TTL)
|
|
156
|
-
2. Call LLM if cache miss
|
|
155
|
+
1. Check cache first (80% hit rate, 24h TTL) -> <1ms
|
|
156
|
+
2. Call LLM if cache miss -> 300-500ms
|
|
157
157
|
3. Cache result for 24 hours
|
|
158
158
|
4. Return SubscriptionInsights
|
|
159
159
|
|
|
@@ -236,9 +236,9 @@ class SubscriptionInsightsGenerator:
|
|
|
236
236
|
Generate subscription insights with natural language recommendations.
|
|
237
237
|
|
|
238
238
|
Flow:
|
|
239
|
-
1. Check cache (80% hit rate, key: insights:{user_id})
|
|
239
|
+
1. Check cache (80% hit rate, key: insights:{user_id}) -> <1ms
|
|
240
240
|
2. Check budget (daily/monthly caps)
|
|
241
|
-
3. Call LLM if cache miss
|
|
241
|
+
3. Call LLM if cache miss -> 300-500ms
|
|
242
242
|
4. Cache result (24h TTL)
|
|
243
243
|
5. Return SubscriptionInsights
|
|
244
244
|
|
|
@@ -26,12 +26,12 @@ def normalize_merchant(raw_name: str) -> str:
|
|
|
26
26
|
Normalize merchant name for grouping.
|
|
27
27
|
|
|
28
28
|
Pipeline:
|
|
29
|
-
1. Lowercase: "NETFLIX.COM"
|
|
30
|
-
2. Remove domain suffixes: "netflix.com"
|
|
31
|
-
3. Remove special chars: "netflix*subscription"
|
|
32
|
-
4. Remove store/transaction numbers: "starbucks #12345"
|
|
33
|
-
5. Remove legal entities: "netflix inc"
|
|
34
|
-
6. Strip whitespace: " netflix "
|
|
29
|
+
1. Lowercase: "NETFLIX.COM" -> "netflix.com"
|
|
30
|
+
2. Remove domain suffixes: "netflix.com" -> "netflix"
|
|
31
|
+
3. Remove special chars: "netflix*subscription" -> "netflix subscription"
|
|
32
|
+
4. Remove store/transaction numbers: "starbucks #12345" -> "starbucks"
|
|
33
|
+
5. Remove legal entities: "netflix inc" -> "netflix"
|
|
34
|
+
6. Strip whitespace: " netflix " -> "netflix"
|
|
35
35
|
|
|
36
36
|
Args:
|
|
37
37
|
raw_name: Original merchant name
|