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.
Files changed (179) hide show
  1. {fin_infra-0.1.64 → fin_infra-0.1.65}/PKG-INFO +1 -1
  2. {fin_infra-0.1.64 → fin_infra-0.1.65}/pyproject.toml +1 -1
  3. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/spending.py +1 -1
  4. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/add.py +13 -15
  5. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/llm_layer.py +2 -2
  6. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/chat/__init__.py +1 -10
  7. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/chat/ease.py +1 -1
  8. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/chat/planning.py +57 -0
  9. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/crypto/insights.py +1 -1
  10. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/ease.py +34 -13
  11. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/__init__.py +6 -6
  12. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/base.py +98 -1
  13. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/credit/experian.py +5 -0
  14. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/add.py +10 -1
  15. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/detectors_llm.py +1 -1
  16. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/insights.py +1 -1
  17. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/normalizers.py +1 -1
  18. {fin_infra-0.1.64 → fin_infra-0.1.65}/LICENSE +0 -0
  19. {fin_infra-0.1.64 → fin_infra-0.1.65}/README.md +0 -0
  20. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/__init__.py +0 -0
  21. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/__main__.py +0 -0
  22. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/__init__.py +0 -0
  23. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/add.py +0 -0
  24. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/cash_flow.py +0 -0
  25. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/ease.py +0 -0
  26. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/models.py +0 -0
  27. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/portfolio.py +0 -0
  28. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/projections.py +0 -0
  29. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/rebalancing.py +0 -0
  30. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/savings.py +0 -0
  31. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/analytics/scenarios.py +0 -0
  32. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/banking/__init__.py +0 -0
  33. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/banking/history.py +0 -0
  34. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/banking/utils.py +0 -0
  35. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/brokerage/__init__.py +0 -0
  36. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/__init__.py +0 -0
  37. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/add.py +0 -0
  38. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/alerts.py +0 -0
  39. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/ease.py +0 -0
  40. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/models.py +0 -0
  41. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
  42. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
  43. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
  44. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
  45. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
  46. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/templates.py +0 -0
  47. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/budgets/tracker.py +0 -0
  48. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cashflows/__init__.py +0 -0
  49. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cashflows/core.py +0 -0
  50. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/__init__.py +0 -0
  51. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/ease.py +0 -0
  52. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/engine.py +0 -0
  53. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/models.py +0 -0
  54. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/rules.py +0 -0
  55. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/categorization/taxonomy.py +0 -0
  56. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cli/__init__.py +0 -0
  57. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cli/cmds/__init__.py +0 -0
  58. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/cli/cmds/scaffold_cmds.py +0 -0
  59. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/clients/__init__.py +0 -0
  60. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/clients/base.py +0 -0
  61. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/clients/plaid.py +0 -0
  62. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/compliance/__init__.py +0 -0
  63. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/__init__.py +0 -0
  64. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/add.py +0 -0
  65. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/__init__.py +0 -0
  66. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/auth.py +0 -0
  67. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/client.py +0 -0
  68. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/parser.py +0 -0
  69. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/experian/provider.py +0 -0
  70. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/credit/mock.py +0 -0
  71. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/crypto/__init__.py +0 -0
  72. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/__init__.py +0 -0
  73. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/add.py +0 -0
  74. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/analysis.py +0 -0
  75. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/ease.py +0 -0
  76. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/models.py +0 -0
  77. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/ocr.py +0 -0
  78. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/documents/storage.py +0 -0
  79. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/exceptions.py +0 -0
  80. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/__init__.py +0 -0
  81. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/add.py +0 -0
  82. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/funding.py +0 -0
  83. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/management.py +0 -0
  84. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/milestones.py +0 -0
  85. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/models.py +0 -0
  86. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
  87. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
  88. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
  89. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
  90. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
  91. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/insights/__init__.py +0 -0
  92. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/insights/aggregator.py +0 -0
  93. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/insights/models.py +0 -0
  94. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/__init__.py +0 -0
  95. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/add.py +0 -0
  96. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/ease.py +0 -0
  97. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/models.py +0 -0
  98. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/providers/__init__.py +0 -0
  99. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/providers/base.py +0 -0
  100. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/providers/plaid.py +0 -0
  101. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/providers/snaptrade.py +0 -0
  102. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
  103. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
  104. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
  105. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
  106. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
  107. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/markets/__init__.py +0 -0
  108. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/__init__.py +0 -0
  109. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/accounts.py +0 -0
  110. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/brokerage.py +0 -0
  111. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/candle.py +0 -0
  112. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/credit.py +0 -0
  113. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/money.py +0 -0
  114. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/quotes.py +0 -0
  115. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/tax.py +0 -0
  116. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/models/transactions.py +0 -0
  117. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/__init__.py +0 -0
  118. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/add.py +0 -0
  119. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/aggregator.py +0 -0
  120. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/calculator.py +0 -0
  121. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/goals.py +0 -0
  122. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/insights.py +0 -0
  123. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/models.py +0 -0
  124. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
  125. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
  126. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
  127. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
  128. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
  129. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/currency_converter.py +0 -0
  130. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/models.py +0 -0
  131. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/providers/__init__.py +0 -0
  132. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/providers/exchangerate.py +0 -0
  133. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/providers/static_mappings.py +0 -0
  134. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/normalization/symbol_resolver.py +0 -0
  135. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/obs/__init__.py +0 -0
  136. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/obs/classifier.py +0 -0
  137. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/__init__.py +0 -0
  138. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/banking/base.py +0 -0
  139. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/banking/plaid_client.py +0 -0
  140. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/banking/teller_client.py +0 -0
  141. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/brokerage/alpaca.py +0 -0
  142. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/brokerage/base.py +0 -0
  143. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
  144. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/alphavantage.py +0 -0
  145. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/base.py +0 -0
  146. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/ccxt_crypto.py +0 -0
  147. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/coingecko.py +0 -0
  148. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/market/yahoo.py +0 -0
  149. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/registry.py +0 -0
  150. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/tax/__init__.py +0 -0
  151. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/tax/irs.py +0 -0
  152. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/tax/mock.py +0 -0
  153. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/providers/tax/taxbit.py +0 -0
  154. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/py.typed +0 -0
  155. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/__init__.py +0 -0
  156. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/detector.py +0 -0
  157. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/ease.py +0 -0
  158. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/models.py +0 -0
  159. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/normalizer.py +0 -0
  160. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/recurring/summary.py +0 -0
  161. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/scaffold/__init__.py +0 -0
  162. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/scaffold/budgets.py +0 -0
  163. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/scaffold/goals.py +0 -0
  164. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/__init__.py +0 -0
  165. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/add.py +0 -0
  166. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/audit.py +0 -0
  167. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/encryption.py +0 -0
  168. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/models.py +0 -0
  169. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/pii_filter.py +0 -0
  170. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/pii_patterns.py +0 -0
  171. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/security/token_store.py +0 -0
  172. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/settings.py +0 -0
  173. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/tax/__init__.py +0 -0
  174. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/tax/add.py +0 -0
  175. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/tax/tlh.py +0 -0
  176. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/utils/__init__.py +0 -0
  177. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/utils/http.py +0 -0
  178. {fin_infra-0.1.64 → fin_infra-0.1.65}/src/fin_infra/utils/retry.py +0 -0
  179. {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.64
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.64"
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
- return [
140
- {
141
- "name": cat.value,
142
- "group": get_category_metadata(cat).group.value
143
- if get_category_metadata(cat)
144
- else None,
145
- "display_name": get_category_metadata(cat).display_name
146
- if get_category_metadata(cat)
147
- else cat.value,
148
- "description": get_category_metadata(cat).description
149
- if get_category_metadata(cat)
150
- else None,
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.exchanges if context else []
167
+ return context.previous_exchanges if context else []
177
168
 
178
169
  @router.delete("/history")
179
170
  async def clear_history():
@@ -69,7 +69,7 @@ def easy_financial_conversation(
69
69
  # Auto-create LLM if not provided
70
70
  if llm is None:
71
71
  try:
72
- from ai_infra.llm import LLM
72
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
73
73
 
74
74
  llm = LLM()
75
75
  except ImportError:
@@ -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,
@@ -15,7 +15,7 @@ from typing import TYPE_CHECKING
15
15
  from pydantic import BaseModel, Field
16
16
 
17
17
  if TYPE_CHECKING:
18
- from ai_infra.llm import LLM
18
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
19
19
 
20
20
 
21
21
  class CryptoInsight(BaseModel):
@@ -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
- try:
420
- from fin_infra.conversation import FinancialPlanningConversation
421
-
422
- conversation = FinancialPlanningConversation(
423
- llm=llm,
424
- cache=cache, # Required for context storage
425
- provider=llm_provider,
426
- model_name=model_name,
427
- )
428
- except ImportError:
429
- # conversation module not yet implemented, skip
430
- pass
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.convert(amount, from_currency, to_currency)
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.amount,
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.timestamp.isoformat(),
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, symbol: str, qty: float, side: str, type_: str, time_in_force: str
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
- insights = await detector.insights_generator.generate(subscriptions)
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:
@@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict, Field
20
20
 
21
21
  # Lazy import for optional dependency (ai-infra)
22
22
  try:
23
- from ai_infra.llm import LLM
23
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
24
24
  except ImportError:
25
25
  LLM = None
26
26
 
@@ -21,7 +21,7 @@ from pydantic import BaseModel, ConfigDict, Field
21
21
 
22
22
  # Lazy import for optional dependency (ai-infra)
23
23
  try:
24
- from ai_infra.llm import LLM
24
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
25
25
  except ImportError:
26
26
  LLM = None
27
27
 
@@ -22,7 +22,7 @@ from pydantic import BaseModel, ConfigDict, Field
22
22
 
23
23
  # Lazy import for optional dependency (ai-infra)
24
24
  try:
25
- from ai_infra.llm import LLM
25
+ from ai_infra.llm import LLM # type: ignore[attr-defined]
26
26
  except ImportError:
27
27
  LLM = None
28
28
 
File without changes
File without changes