fin-infra 0.1.79__tar.gz → 0.1.81__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 (180) hide show
  1. {fin_infra-0.1.79 → fin_infra-0.1.81}/PKG-INFO +1 -1
  2. {fin_infra-0.1.79 → fin_infra-0.1.81}/pyproject.toml +5 -1
  3. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/__init__.py +10 -0
  4. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/banking/__init__.py +0 -1
  5. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/banking/history.py +5 -5
  6. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/banking/utils.py +9 -9
  7. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/alerts.py +3 -3
  8. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/tracker.py +2 -2
  9. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/cashflows/core.py +1 -1
  10. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/categorization/llm_layer.py +2 -2
  11. fin_infra-0.1.81/src/fin_infra/clients/__init__.py +25 -0
  12. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/clients/base.py +1 -1
  13. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/clients/plaid.py +1 -1
  14. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/compliance/__init__.py +2 -1
  15. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/documents/analysis.py +2 -2
  16. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/documents/models.py +4 -4
  17. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/documents/ocr.py +2 -2
  18. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/documents/storage.py +2 -2
  19. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/add.py +9 -9
  20. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/funding.py +3 -5
  21. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/models.py +5 -5
  22. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/providers/base.py +9 -9
  23. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/providers/plaid.py +11 -11
  24. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/providers/snaptrade.py +12 -12
  25. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/markets/__init__.py +4 -2
  26. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/__init__.py +7 -0
  27. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/obs/classifier.py +1 -1
  28. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/base.py +27 -2
  29. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/market/alphavantage.py +1 -1
  30. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/market/yahoo.py +1 -1
  31. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/summary.py +11 -11
  32. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/scaffold/budgets.py +4 -4
  33. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/scaffold/goals.py +4 -4
  34. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/security/encryption.py +4 -4
  35. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/utils/retry.py +2 -1
  36. fin_infra-0.1.79/src/fin_infra/clients/__init__.py +0 -3
  37. {fin_infra-0.1.79 → fin_infra-0.1.81}/LICENSE +0 -0
  38. {fin_infra-0.1.79 → fin_infra-0.1.81}/README.md +0 -0
  39. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/__init__.py +0 -0
  40. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/__main__.py +0 -0
  41. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/add.py +0 -0
  42. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/cash_flow.py +0 -0
  43. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/ease.py +0 -0
  44. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/models.py +0 -0
  45. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/portfolio.py +0 -0
  46. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/projections.py +0 -0
  47. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/rebalancing.py +0 -0
  48. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/savings.py +0 -0
  49. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/scenarios.py +0 -0
  50. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/analytics/spending.py +0 -0
  51. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/brokerage/__init__.py +0 -0
  52. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/__init__.py +0 -0
  53. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/add.py +0 -0
  54. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/ease.py +0 -0
  55. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/models.py +0 -0
  56. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
  57. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
  58. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
  59. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
  60. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
  61. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/budgets/templates.py +0 -0
  62. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/cashflows/__init__.py +0 -0
  63. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/categorization/__init__.py +0 -0
  64. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/categorization/add.py +0 -0
  65. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/categorization/ease.py +0 -0
  66. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/categorization/engine.py +0 -0
  67. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/categorization/models.py +0 -0
  68. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/categorization/rules.py +0 -0
  69. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/categorization/taxonomy.py +0 -0
  70. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/chat/__init__.py +0 -0
  71. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/chat/ease.py +0 -0
  72. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/chat/planning.py +0 -0
  73. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/cli/__init__.py +0 -0
  74. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/cli/cmds/__init__.py +0 -0
  75. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/cli/cmds/scaffold_cmds.py +0 -0
  76. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/credit/__init__.py +0 -0
  77. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/credit/add.py +0 -0
  78. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/credit/experian/__init__.py +0 -0
  79. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/credit/experian/auth.py +0 -0
  80. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/credit/experian/client.py +0 -0
  81. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/credit/experian/parser.py +0 -0
  82. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/credit/experian/provider.py +0 -0
  83. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/credit/mock.py +0 -0
  84. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/crypto/__init__.py +0 -0
  85. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/crypto/insights.py +0 -0
  86. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/documents/__init__.py +0 -0
  87. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/documents/add.py +0 -0
  88. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/documents/ease.py +0 -0
  89. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/exceptions.py +0 -0
  90. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/__init__.py +0 -0
  91. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/management.py +0 -0
  92. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/milestones.py +0 -0
  93. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/models.py +0 -0
  94. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
  95. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
  96. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
  97. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
  98. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
  99. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/insights/__init__.py +0 -0
  100. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/insights/aggregator.py +0 -0
  101. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/insights/models.py +0 -0
  102. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/__init__.py +0 -0
  103. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/add.py +0 -0
  104. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/ease.py +0 -0
  105. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/providers/__init__.py +0 -0
  106. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
  107. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
  108. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
  109. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
  110. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
  111. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/__init__.py +0 -0
  112. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/accounts.py +0 -0
  113. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/brokerage.py +0 -0
  114. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/candle.py +0 -0
  115. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/credit.py +0 -0
  116. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/money.py +0 -0
  117. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/quotes.py +0 -0
  118. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/tax.py +0 -0
  119. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/models/transactions.py +0 -0
  120. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/add.py +0 -0
  121. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/aggregator.py +0 -0
  122. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/calculator.py +0 -0
  123. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/ease.py +0 -0
  124. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/goals.py +0 -0
  125. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/insights.py +0 -0
  126. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/models.py +0 -0
  127. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
  128. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
  129. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
  130. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
  131. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
  132. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/normalization/__init__.py +0 -0
  133. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/normalization/currency_converter.py +0 -0
  134. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/normalization/models.py +0 -0
  135. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/normalization/providers/__init__.py +0 -0
  136. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/normalization/providers/exchangerate.py +0 -0
  137. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/normalization/providers/static_mappings.py +0 -0
  138. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/normalization/symbol_resolver.py +0 -0
  139. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/obs/__init__.py +0 -0
  140. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/__init__.py +0 -0
  141. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/banking/base.py +0 -0
  142. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/banking/plaid_client.py +0 -0
  143. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/banking/teller_client.py +0 -0
  144. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/brokerage/alpaca.py +0 -0
  145. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/brokerage/base.py +0 -0
  146. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/credit/experian.py +0 -0
  147. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
  148. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/market/base.py +0 -0
  149. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/market/ccxt_crypto.py +0 -0
  150. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/market/coingecko.py +0 -0
  151. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/registry.py +0 -0
  152. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/tax/__init__.py +0 -0
  153. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/tax/irs.py +0 -0
  154. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/tax/mock.py +0 -0
  155. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/providers/tax/taxbit.py +0 -0
  156. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/py.typed +0 -0
  157. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/__init__.py +0 -0
  158. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/add.py +0 -0
  159. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/detector.py +0 -0
  160. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/detectors_llm.py +0 -0
  161. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/ease.py +0 -0
  162. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/insights.py +0 -0
  163. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/models.py +0 -0
  164. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/normalizer.py +0 -0
  165. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/recurring/normalizers.py +0 -0
  166. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/scaffold/__init__.py +0 -0
  167. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/security/__init__.py +0 -0
  168. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/security/add.py +0 -0
  169. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/security/audit.py +0 -0
  170. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/security/models.py +0 -0
  171. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/security/pii_filter.py +0 -0
  172. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/security/pii_patterns.py +0 -0
  173. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/security/token_store.py +0 -0
  174. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/settings.py +0 -0
  175. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/tax/__init__.py +0 -0
  176. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/tax/add.py +0 -0
  177. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/tax/tlh.py +0 -0
  178. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/utils/__init__.py +0 -0
  179. {fin_infra-0.1.79 → fin_infra-0.1.81}/src/fin_infra/utils/http.py +0 -0
  180. {fin_infra-0.1.79 → fin_infra-0.1.81}/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.79
3
+ Version: 0.1.81
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.79"
3
+ version = "0.1.81"
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"
@@ -81,6 +81,10 @@ pre-commit = ">=3.0.0"
81
81
  types-requests = ">=2.31.0"
82
82
  pytest-cov = ">=4.0.0"
83
83
  aiosqlite = ">=0.20.0"
84
+ # Documentation
85
+ mkdocs = ">=1.6.0"
86
+ mkdocs-material = ">=9.5.0"
87
+ mkdocstrings = {extras = ["python"], version = ">=0.25.0"}
84
88
 
85
89
  [tool.pytest.ini_options]
86
90
  testpaths = ["tests"]
@@ -7,6 +7,16 @@ This module provides comprehensive financial analytics capabilities including:
7
7
  - Portfolio analytics (returns, allocation, benchmarking)
8
8
  - Growth projections (net worth forecasting with scenarios)
9
9
 
10
+ Feature Status:
11
+ ✅ STABLE: Core calculation functions (all analytics work with provided data)
12
+ ⚠️ INTEGRATION: Auto-fetching from providers requires setup:
13
+ - Banking provider for transaction data
14
+ - Brokerage provider for investment data
15
+ - Categorization for expense categorization
16
+
17
+ When providers aren't configured, functions accept data directly or return
18
+ sensible placeholder values for testing/development.
19
+
10
20
  Serves multiple use cases:
11
21
  - Personal finance apps (cash flow, savings tracking)
12
22
  - Wealth management platforms (portfolio analytics, projections)
@@ -174,7 +174,6 @@ def easy_banking(provider: str = "teller", **config) -> BankingProvider:
174
174
  See Also:
175
175
  - add_banking(): For FastAPI integration with routes
176
176
  - docs/banking.md: Comprehensive banking integration guide
177
- - docs/adr/0003-banking-integration.md: Architecture decisions
178
177
  """
179
178
  # Auto-detect provider config from environment if not explicitly provided
180
179
  # Only auto-detect if no config params were passed
@@ -42,7 +42,7 @@ from __future__ import annotations
42
42
  import logging
43
43
  import os
44
44
  from datetime import date, datetime, timedelta
45
- from typing import List, Optional
45
+ from typing import Optional
46
46
  from pydantic import BaseModel, Field, ConfigDict
47
47
 
48
48
 
@@ -59,7 +59,7 @@ _logger = logging.getLogger(__name__)
59
59
 
60
60
  # In-memory storage for testing (will be replaced with SQL database in production)
61
61
  # ⚠️ WARNING: All data is LOST on restart when using in-memory storage!
62
- _balance_snapshots: List[BalanceSnapshot] = []
62
+ _balance_snapshots: list[BalanceSnapshot] = []
63
63
  _production_warning_logged = False
64
64
 
65
65
 
@@ -157,7 +157,7 @@ def get_balance_history(
157
157
  days: int = 90,
158
158
  start_date: Optional[date] = None,
159
159
  end_date: Optional[date] = None,
160
- ) -> List[BalanceSnapshot]:
160
+ ) -> list[BalanceSnapshot]:
161
161
  """Get balance history for an account.
162
162
 
163
163
  Retrieves balance snapshots for the specified account within a date range.
@@ -216,8 +216,8 @@ def get_balance_history(
216
216
 
217
217
  def get_balance_snapshots(
218
218
  account_id: str,
219
- dates: List[date],
220
- ) -> List[BalanceSnapshot]:
219
+ dates: list[date],
220
+ ) -> list[BalanceSnapshot]:
221
221
  """Get balance snapshots for specific dates.
222
222
 
223
223
  Args:
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import re
11
11
  from datetime import datetime, timezone
12
- from typing import Any, Dict, Optional, Literal
12
+ from typing import Any, Optional, Literal
13
13
  from pydantic import BaseModel, ConfigDict, Field
14
14
 
15
15
  from ..providers.base import BankingProvider
@@ -179,7 +179,7 @@ def validate_provider_token(provider: str, access_token: str) -> bool:
179
179
  return validator(access_token)
180
180
 
181
181
 
182
- def parse_banking_providers(banking_providers: Dict[str, Any]) -> BankingConnectionStatus:
182
+ def parse_banking_providers(banking_providers: dict[str, Any]) -> BankingConnectionStatus:
183
183
  """
184
184
  Parse banking_providers JSON field into structured status.
185
185
 
@@ -257,7 +257,7 @@ def parse_banking_providers(banking_providers: Dict[str, Any]) -> BankingConnect
257
257
  return status
258
258
 
259
259
 
260
- def sanitize_connection_status(status: BankingConnectionStatus) -> Dict[str, Any]:
260
+ def sanitize_connection_status(status: BankingConnectionStatus) -> dict[str, Any]:
261
261
  """
262
262
  Sanitize connection status for API responses (removes access tokens).
263
263
 
@@ -298,10 +298,10 @@ def sanitize_connection_status(status: BankingConnectionStatus) -> Dict[str, Any
298
298
 
299
299
 
300
300
  def mark_connection_unhealthy(
301
- banking_providers: Dict[str, Any],
301
+ banking_providers: dict[str, Any],
302
302
  provider: str,
303
303
  error_message: str,
304
- ) -> Dict[str, Any]:
304
+ ) -> dict[str, Any]:
305
305
  """
306
306
  Mark a provider connection as unhealthy (for error handling).
307
307
 
@@ -335,9 +335,9 @@ def mark_connection_unhealthy(
335
335
 
336
336
 
337
337
  def mark_connection_healthy(
338
- banking_providers: Dict[str, Any],
338
+ banking_providers: dict[str, Any],
339
339
  provider: str,
340
- ) -> Dict[str, Any]:
340
+ ) -> dict[str, Any]:
341
341
  """
342
342
  Mark a provider connection as healthy (after successful sync).
343
343
 
@@ -368,7 +368,7 @@ def mark_connection_healthy(
368
368
 
369
369
 
370
370
  def get_primary_access_token(
371
- banking_providers: Dict[str, Any],
371
+ banking_providers: dict[str, Any],
372
372
  ) -> tuple[Optional[str], Optional[str]]:
373
373
  """
374
374
  Get the primary access token and provider name.
@@ -437,7 +437,7 @@ async def test_connection_health(
437
437
  return False, error_msg
438
438
 
439
439
 
440
- def should_refresh_token(banking_providers: Dict[str, Any], provider: str) -> bool:
440
+ def should_refresh_token(banking_providers: dict[str, Any], provider: str) -> bool:
441
441
  """
442
442
  Check if a provider token should be refreshed.
443
443
 
@@ -35,7 +35,7 @@ Example:
35
35
  from __future__ import annotations
36
36
 
37
37
  from datetime import datetime
38
- from typing import TYPE_CHECKING, List, Optional
38
+ from typing import TYPE_CHECKING, Optional
39
39
 
40
40
  from fin_infra.budgets.models import (
41
41
  AlertSeverity,
@@ -52,7 +52,7 @@ async def check_budget_alerts(
52
52
  budget_id: str,
53
53
  tracker: BudgetTracker,
54
54
  thresholds: Optional[dict[str, float]] = None,
55
- ) -> List[BudgetAlert]:
55
+ ) -> list[BudgetAlert]:
56
56
  """
57
57
  Check budget for alerts (overspending, approaching limits, unusual patterns).
58
58
 
@@ -111,7 +111,7 @@ async def check_budget_alerts(
111
111
  # Get budget progress
112
112
  progress = await tracker.get_budget_progress(budget_id)
113
113
 
114
- alerts: List[BudgetAlert] = []
114
+ alerts: list[BudgetAlert] = []
115
115
 
116
116
  # Check each category for alerts
117
117
  for category in progress.categories:
@@ -36,7 +36,7 @@ from __future__ import annotations
36
36
 
37
37
  import uuid
38
38
  from datetime import datetime, timedelta
39
- from typing import TYPE_CHECKING, List, Optional
39
+ from typing import TYPE_CHECKING, Optional
40
40
 
41
41
  from sqlalchemy.ext.asyncio import async_sessionmaker
42
42
 
@@ -206,7 +206,7 @@ class BudgetTracker:
206
206
  self,
207
207
  user_id: str,
208
208
  type: Optional[str] = None,
209
- ) -> List[Budget]:
209
+ ) -> list[Budget]:
210
210
  """
211
211
  Get all budgets for a user.
212
212
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Iterable
3
+ from collections.abc import Iterable
4
4
 
5
5
  import numpy as np
6
6
  import numpy_financial as npf
@@ -15,7 +15,7 @@ Expected performance:
15
15
 
16
16
  import hashlib
17
17
  import logging
18
- from typing import Any, List, Optional, Tuple, cast
18
+ from typing import Any, Optional, cast
19
19
  from pydantic import BaseModel, Field
20
20
 
21
21
  # ai-infra imports
@@ -40,7 +40,7 @@ class CategoryPrediction(BaseModel):
40
40
 
41
41
 
42
42
  # Few-shot examples (20 diverse merchants covering all major categories)
43
- FEW_SHOT_EXAMPLES: List[Tuple[str, str, str]] = [
43
+ FEW_SHOT_EXAMPLES: list[tuple[str, str, str]] = [
44
44
  # Food & Dining (5 examples)
45
45
  ("STARBUCKS #1234", "Coffee Shops", "Popular coffee shop chain"),
46
46
  ("MCDONALD'S", "Fast Food", "Fast food restaurant"),
@@ -0,0 +1,25 @@
1
+ """DEPRECATED: Use fin_infra.providers instead.
2
+
3
+ This module is deprecated and will be removed in a future version.
4
+ All ABCs have been consolidated into fin_infra.providers.base.
5
+
6
+ Migration:
7
+ # Old (deprecated)
8
+ from fin_infra.clients import BankingClient, MarketDataClient
9
+
10
+ # New
11
+ from fin_infra.providers.base import BankingProvider, MarketDataProvider
12
+ """
13
+
14
+ import warnings
15
+
16
+ from .base import BankingClient, CreditClient, MarketDataClient
17
+
18
+ warnings.warn(
19
+ "fin_infra.clients is deprecated. Use fin_infra.providers instead. "
20
+ "This module will be removed in a future version.",
21
+ DeprecationWarning,
22
+ stacklevel=2,
23
+ )
24
+
25
+ __all__ = ["BankingClient", "MarketDataClient", "CreditClient"]
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import abc
4
- from typing import Iterable, Sequence
4
+ from collections.abc import Iterable, Sequence
5
5
 
6
6
  from ..models import Account, Quote, Transaction
7
7
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Sequence
3
+ from collections.abc import Sequence
4
4
 
5
5
  from .base import BankingClient
6
6
  from ..models import Account
@@ -21,7 +21,8 @@ from __future__ import annotations
21
21
 
22
22
  import logging
23
23
  from datetime import datetime
24
- from typing import Any, Callable, TYPE_CHECKING, cast
24
+ from typing import Any, TYPE_CHECKING, cast
25
+ from collections.abc import Callable
25
26
 
26
27
  if TYPE_CHECKING:
27
28
  from fastapi import FastAPI, Request, Response
@@ -24,7 +24,7 @@ from __future__ import annotations
24
24
 
25
25
  import re
26
26
  from datetime import datetime
27
- from typing import TYPE_CHECKING, Dict
27
+ from typing import TYPE_CHECKING
28
28
 
29
29
  if TYPE_CHECKING:
30
30
  from svc_infra.storage.base import StorageBackend
@@ -32,7 +32,7 @@ if TYPE_CHECKING:
32
32
  from .models import DocumentAnalysis
33
33
 
34
34
  # In-memory analysis cache (production: use svc-infra cache)
35
- _analysis_cache: Dict[str, "DocumentAnalysis"] = {}
35
+ _analysis_cache: dict[str, "DocumentAnalysis"] = {}
36
36
 
37
37
 
38
38
  async def analyze_document(
@@ -31,7 +31,7 @@ from __future__ import annotations
31
31
 
32
32
  from datetime import datetime
33
33
  from enum import Enum
34
- from typing import Dict, List, Optional
34
+ from typing import Optional
35
35
 
36
36
  from pydantic import BaseModel, ConfigDict, Field
37
37
  from svc_infra.documents import Document as BaseDocument
@@ -145,7 +145,7 @@ class OCRResult(BaseModel):
145
145
  confidence: float = Field(
146
146
  ..., description="Overall OCR confidence score (0.0-1.0)", ge=0.0, le=1.0
147
147
  )
148
- fields_extracted: Dict[str, str] = Field(
148
+ fields_extracted: dict[str, str] = Field(
149
149
  default_factory=dict,
150
150
  description="Structured fields extracted from document (names, amounts, dates)",
151
151
  )
@@ -181,10 +181,10 @@ class DocumentAnalysis(BaseModel):
181
181
 
182
182
  document_id: str = Field(..., description="Document that was analyzed")
183
183
  summary: str = Field(..., description="High-level document summary")
184
- key_findings: List[str] = Field(
184
+ key_findings: list[str] = Field(
185
185
  default_factory=list, description="Important facts extracted from document"
186
186
  )
187
- recommendations: List[str] = Field(
187
+ recommendations: list[str] = Field(
188
188
  default_factory=list, description="Action items or suggestions based on document content"
189
189
  )
190
190
  analysis_date: datetime = Field(
@@ -25,7 +25,7 @@ from __future__ import annotations
25
25
 
26
26
  import re
27
27
  from datetime import datetime
28
- from typing import TYPE_CHECKING, Dict, Optional
28
+ from typing import TYPE_CHECKING, Optional
29
29
 
30
30
  if TYPE_CHECKING:
31
31
  from svc_infra.storage.base import StorageBackend
@@ -33,7 +33,7 @@ if TYPE_CHECKING:
33
33
  from .models import OCRResult
34
34
 
35
35
  # In-memory OCR cache (production: use svc-infra cache)
36
- _ocr_cache: Dict[str, "OCRResult"] = {}
36
+ _ocr_cache: dict[str, "OCRResult"] = {}
37
37
 
38
38
 
39
39
  async def extract_text(
@@ -36,7 +36,7 @@ Quick Start:
36
36
 
37
37
  from __future__ import annotations
38
38
 
39
- from typing import TYPE_CHECKING, List, Optional
39
+ from typing import TYPE_CHECKING, Optional
40
40
 
41
41
  try:
42
42
  from svc_infra.documents import (
@@ -242,7 +242,7 @@ def list_documents(
242
242
  tax_year: Optional[int] = None,
243
243
  limit: int = 100,
244
244
  offset: int = 0,
245
- ) -> List["FinancialDocument"]:
245
+ ) -> list["FinancialDocument"]:
246
246
  """
247
247
  List user's financial documents with optional filters (delegates to svc-infra).
248
248
 
@@ -29,7 +29,7 @@ add_goals(app)
29
29
 
30
30
  import logging
31
31
  from datetime import datetime
32
- from typing import Any, List, Optional, cast
32
+ from typing import Any, Optional, cast
33
33
 
34
34
  from fastapi import FastAPI, HTTPException, status, Query, Body
35
35
  from pydantic import BaseModel, Field
@@ -89,7 +89,7 @@ class CreateGoalRequest(BaseModel):
89
89
  description: Optional[str] = Field(None, description="Goal description")
90
90
  current_amount: Optional[float] = Field(0.0, ge=0, description="Current amount")
91
91
  auto_contribute: Optional[bool] = Field(False, description="Auto-contribute enabled")
92
- tags: Optional[List[str]] = Field(None, description="Goal tags")
92
+ tags: Optional[list[str]] = Field(None, description="Goal tags")
93
93
 
94
94
 
95
95
  class UpdateGoalRequest(BaseModel):
@@ -102,7 +102,7 @@ class UpdateGoalRequest(BaseModel):
102
102
  current_amount: Optional[float] = Field(None, ge=0)
103
103
  status: Optional[GoalStatus] = None
104
104
  auto_contribute: Optional[bool] = None
105
- tags: Optional[List[str]] = None
105
+ tags: Optional[list[str]] = None
106
106
 
107
107
 
108
108
  class AddMilestoneRequest(BaseModel):
@@ -238,14 +238,14 @@ def add_goals(
238
238
  except ValueError as e:
239
239
  raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
240
240
 
241
- @router.get("", response_model=List[dict])
241
+ @router.get("", response_model=list[dict])
242
242
  async def list_goals_endpoint(
243
243
  user_id: Optional[str] = Query(
244
244
  None, description="User identifier (optional, returns all if not provided)"
245
245
  ),
246
246
  goal_type: Optional[str] = Query(None, description="Filter by goal type"),
247
247
  status_filter: Optional[str] = Query(None, alias="status", description="Filter by status"),
248
- ) -> List[dict]:
248
+ ) -> list[dict]:
249
249
  """
250
250
  List all goals for a user with optional filters.
251
251
 
@@ -442,8 +442,8 @@ def add_goals(
442
442
  except ValueError as e:
443
443
  raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
444
444
 
445
- @router.get("/{goal_id}/milestones", response_model=List[dict])
446
- async def list_milestones_endpoint(goal_id: str) -> List[dict]:
445
+ @router.get("/{goal_id}/milestones", response_model=list[dict])
446
+ async def list_milestones_endpoint(goal_id: str) -> list[dict]:
447
447
  """
448
448
  List all milestones for a goal.
449
449
 
@@ -540,8 +540,8 @@ def add_goals(
540
540
  except ValueError as e:
541
541
  raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
542
542
 
543
- @router.get("/{goal_id}/funding", response_model=List[dict])
544
- async def list_funding_sources_endpoint(goal_id: str) -> List[dict]:
543
+ @router.get("/{goal_id}/funding", response_model=list[dict])
544
+ async def list_funding_sources_endpoint(goal_id: str) -> list[dict]:
545
545
  """
546
546
  List all funding sources for a goal.
547
547
 
@@ -22,14 +22,12 @@ Example:
22
22
  >>> # Raises ValueError if total allocation > 100%
23
23
  """
24
24
 
25
- from typing import Dict, List
26
-
27
25
  from fin_infra.goals.models import FundingSource
28
26
  from fin_infra.goals.management import get_goal
29
27
 
30
28
  # In-memory storage for funding allocations
31
29
  # Structure: {account_id: {goal_id: allocation_percent}}
32
- _FUNDING_STORE: Dict[str, Dict[str, float]] = {}
30
+ _FUNDING_STORE: dict[str, dict[str, float]] = {}
33
31
 
34
32
 
35
33
  def link_account_to_goal(
@@ -108,7 +106,7 @@ def link_account_to_goal(
108
106
  )
109
107
 
110
108
 
111
- def get_goal_funding_sources(goal_id: str) -> List[FundingSource]:
109
+ def get_goal_funding_sources(goal_id: str) -> list[FundingSource]:
112
110
  """
113
111
  Get all accounts funding a specific goal.
114
112
 
@@ -154,7 +152,7 @@ def get_goal_funding_sources(goal_id: str) -> List[FundingSource]:
154
152
  return funding_sources
155
153
 
156
154
 
157
- def get_account_allocations(account_id: str) -> Dict[str, float]:
155
+ def get_account_allocations(account_id: str) -> dict[str, float]:
158
156
  """
159
157
  Get all goal allocations for a specific account.
160
158
 
@@ -20,7 +20,7 @@ from __future__ import annotations
20
20
  from datetime import date
21
21
  from decimal import Decimal
22
22
  from enum import Enum
23
- from typing import TYPE_CHECKING, Dict, List, Optional
23
+ from typing import TYPE_CHECKING, Optional
24
24
 
25
25
  from pydantic import BaseModel, ConfigDict, Field, computed_field
26
26
 
@@ -374,12 +374,12 @@ class InvestmentAccount(BaseModel):
374
374
  subtype: Optional[str] = Field(None, description="Account subtype (401k, ira, brokerage)")
375
375
 
376
376
  # Balances
377
- balances: Dict[str, Optional[Decimal]] = Field(
377
+ balances: dict[str, Optional[Decimal]] = Field(
378
378
  ..., description="Current, available, and limit balances"
379
379
  )
380
380
 
381
381
  # Holdings
382
- holdings: List[Holding] = Field(default_factory=list, description="List of holdings in account")
382
+ holdings: list[Holding] = Field(default_factory=list, description="List of holdings in account")
383
383
 
384
384
  if TYPE_CHECKING:
385
385
 
@@ -487,11 +487,11 @@ class AssetAllocation(BaseModel):
487
487
  },
488
488
  )
489
489
 
490
- by_security_type: Dict[SecurityType, float] = Field(
490
+ by_security_type: dict[SecurityType, float] = Field(
491
491
  default_factory=dict,
492
492
  description="Percentage breakdown by security type (equity, bond, etc.)",
493
493
  )
494
- by_sector: Dict[str, float] = Field(
494
+ by_sector: dict[str, float] = Field(
495
495
  default_factory=dict,
496
496
  description="Percentage breakdown by sector (Technology, Healthcare, etc.)",
497
497
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from abc import ABC, abstractmethod
6
6
  from datetime import date
7
- from typing import List, Optional
7
+ from typing import Optional
8
8
 
9
9
  # Import will work once models.py is fully implemented in Task 3
10
10
  # For now, using TYPE_CHECKING to avoid circular imports
@@ -30,8 +30,8 @@ class InvestmentProvider(ABC):
30
30
 
31
31
  @abstractmethod
32
32
  async def get_holdings(
33
- self, access_token: str, account_ids: Optional[List[str]] = None
34
- ) -> List[Holding]:
33
+ self, access_token: str, account_ids: Optional[list[str]] = None
34
+ ) -> list[Holding]:
35
35
  """Fetch holdings for investment accounts.
36
36
 
37
37
  Args:
@@ -54,8 +54,8 @@ class InvestmentProvider(ABC):
54
54
  access_token: str,
55
55
  start_date: date,
56
56
  end_date: date,
57
- account_ids: Optional[List[str]] = None,
58
- ) -> List[InvestmentTransaction]:
57
+ account_ids: Optional[list[str]] = None,
58
+ ) -> list[InvestmentTransaction]:
59
59
  """Fetch investment transactions within date range.
60
60
 
61
61
  Args:
@@ -77,7 +77,7 @@ class InvestmentProvider(ABC):
77
77
  pass
78
78
 
79
79
  @abstractmethod
80
- async def get_securities(self, access_token: str, security_ids: List[str]) -> List[Security]:
80
+ async def get_securities(self, access_token: str, security_ids: list[str]) -> list[Security]:
81
81
  """Fetch security details (ticker, name, type, current price).
82
82
 
83
83
  Args:
@@ -95,7 +95,7 @@ class InvestmentProvider(ABC):
95
95
  pass
96
96
 
97
97
  @abstractmethod
98
- async def get_investment_accounts(self, access_token: str) -> List[InvestmentAccount]:
98
+ async def get_investment_accounts(self, access_token: str) -> list[InvestmentAccount]:
99
99
  """Fetch investment accounts with aggregated holdings.
100
100
 
101
101
  Args:
@@ -113,7 +113,7 @@ class InvestmentProvider(ABC):
113
113
 
114
114
  # Helper methods (concrete - shared across all providers)
115
115
 
116
- def calculate_allocation(self, holdings: List[Holding]) -> AssetAllocation:
116
+ def calculate_allocation(self, holdings: list[Holding]) -> AssetAllocation:
117
117
  """Calculate asset allocation by security type and sector.
118
118
 
119
119
  Groups holdings by security type (equity, bond, ETF, etc.) and calculates
@@ -183,7 +183,7 @@ class InvestmentProvider(ABC):
183
183
  cash_percent=cash_percent,
184
184
  )
185
185
 
186
- def calculate_portfolio_metrics(self, holdings: List[Holding]) -> dict:
186
+ def calculate_portfolio_metrics(self, holdings: list[Holding]) -> dict:
187
187
  """Calculate total value, cost basis, unrealized gain/loss.
188
188
 
189
189
  Aggregates holdings to calculate portfolio-level metrics.
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from datetime import date
12
12
  from decimal import Decimal
13
- from typing import Any, Dict, List, Optional, cast
13
+ from typing import Any, Optional, cast
14
14
 
15
15
  try:
16
16
  from plaid.api import plaid_api
@@ -131,8 +131,8 @@ class PlaidInvestmentProvider(InvestmentProvider):
131
131
  return cast(str, hosts.get(environment.lower(), plaid.Environment.Sandbox))
132
132
 
133
133
  async def get_holdings(
134
- self, access_token: str, account_ids: Optional[List[str]] = None
135
- ) -> List[Holding]:
134
+ self, access_token: str, account_ids: Optional[list[str]] = None
135
+ ) -> list[Holding]:
136
136
  """Fetch investment holdings from Plaid.
137
137
 
138
138
  Retrieves holdings with security details, quantity, cost basis, and current value.
@@ -189,8 +189,8 @@ class PlaidInvestmentProvider(InvestmentProvider):
189
189
  access_token: str,
190
190
  start_date: date,
191
191
  end_date: date,
192
- account_ids: Optional[List[str]] = None,
193
- ) -> List[InvestmentTransaction]:
192
+ account_ids: Optional[list[str]] = None,
193
+ ) -> list[InvestmentTransaction]:
194
194
  """Fetch investment transactions from Plaid.
195
195
 
196
196
  Retrieves buy/sell/dividend transactions within the specified date range.
@@ -252,7 +252,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
252
252
  except ApiException as e:
253
253
  raise self._transform_error(e)
254
254
 
255
- async def get_securities(self, access_token: str, security_ids: List[str]) -> List[Security]:
255
+ async def get_securities(self, access_token: str, security_ids: list[str]) -> list[Security]:
256
256
  """Fetch security details from Plaid holdings.
257
257
 
258
258
  Note: Plaid doesn't have a dedicated securities endpoint.
@@ -290,7 +290,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
290
290
  except ApiException as e:
291
291
  raise self._transform_error(e)
292
292
 
293
- async def get_investment_accounts(self, access_token: str) -> List[InvestmentAccount]:
293
+ async def get_investment_accounts(self, access_token: str) -> list[InvestmentAccount]:
294
294
  """Fetch investment accounts with aggregated holdings.
295
295
 
296
296
  Returns accounts with total value, cost basis, and unrealized P&L.
@@ -321,7 +321,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
321
321
  }
322
322
 
323
323
  # Group holdings by account
324
- accounts_map: Dict[str, Dict[str, Any]] = {}
324
+ accounts_map: dict[str, dict[str, Any]] = {}
325
325
  for plaid_holding in response.holdings:
326
326
  holding_dict = plaid_holding.to_dict()
327
327
  account_id = holding_dict["account_id"]
@@ -373,7 +373,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
373
373
 
374
374
  # Helper methods for data transformation
375
375
 
376
- def _transform_security(self, plaid_security: Dict[str, Any]) -> Security:
376
+ def _transform_security(self, plaid_security: dict[str, Any]) -> Security:
377
377
  """Transform Plaid security data to Security model."""
378
378
  # Handle close_price - Plaid may return None for securities without recent pricing
379
379
  close_price_raw = plaid_security.get("close_price")
@@ -394,7 +394,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
394
394
  currency=plaid_security.get("iso_currency_code", "USD"),
395
395
  )
396
396
 
397
- def _transform_holding(self, plaid_holding: Dict[str, Any], security: Security) -> Holding:
397
+ def _transform_holding(self, plaid_holding: dict[str, Any], security: Security) -> Holding:
398
398
  """Transform Plaid holding data to Holding model."""
399
399
  return Holding(
400
400
  account_id=plaid_holding["account_id"],
@@ -410,7 +410,7 @@ class PlaidInvestmentProvider(InvestmentProvider):
410
410
  )
411
411
 
412
412
  def _transform_transaction(
413
- self, plaid_transaction: Dict[str, Any], security: Security
413
+ self, plaid_transaction: dict[str, Any], security: Security
414
414
  ) -> InvestmentTransaction:
415
415
  """Transform Plaid investment transaction to InvestmentTransaction model."""
416
416
  # Map Plaid transaction type to our enum