fin-infra 0.1.65__tar.gz → 0.1.66__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.65 → fin_infra-0.1.66}/PKG-INFO +1 -2
  2. {fin_infra-0.1.65 → fin_infra-0.1.66}/pyproject.toml +1 -2
  3. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/cash_flow.py +6 -5
  4. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/portfolio.py +1 -2
  5. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/spending.py +6 -6
  6. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/banking/__init__.py +1 -1
  7. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/banking/utils.py +5 -6
  8. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/cashflows/__init__.py +6 -8
  9. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/categorization/engine.py +3 -3
  10. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/categorization/llm_layer.py +3 -4
  11. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/categorization/models.py +1 -1
  12. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/chat/__init__.py +6 -6
  13. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/chat/ease.py +1 -1
  14. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/compliance/__init__.py +0 -1
  15. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/crypto/insights.py +2 -4
  16. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/documents/add.py +1 -1
  17. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/insights/__init__.py +7 -7
  18. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/__init__.py +1 -1
  19. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/add.py +2 -3
  20. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/models.py +100 -46
  21. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/providers/plaid.py +2 -3
  22. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/providers/snaptrade.py +2 -2
  23. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/markets/__init__.py +0 -4
  24. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/transactions.py +1 -1
  25. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/add.py +8 -5
  26. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/aggregator.py +5 -4
  27. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/ease.py +1 -1
  28. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/models.py +237 -116
  29. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/normalization/__init__.py +6 -6
  30. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/obs/classifier.py +2 -2
  31. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/base.py +12 -17
  32. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/add.py +8 -8
  33. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/detectors_llm.py +9 -8
  34. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/ease.py +1 -1
  35. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/insights.py +8 -7
  36. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/models.py +3 -3
  37. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/normalizers.py +8 -7
  38. {fin_infra-0.1.65 → fin_infra-0.1.66}/LICENSE +0 -0
  39. {fin_infra-0.1.65 → fin_infra-0.1.66}/README.md +0 -0
  40. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/__init__.py +0 -0
  41. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/__main__.py +0 -0
  42. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/__init__.py +0 -0
  43. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/add.py +0 -0
  44. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/ease.py +0 -0
  45. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/models.py +0 -0
  46. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/projections.py +0 -0
  47. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/rebalancing.py +0 -0
  48. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/savings.py +0 -0
  49. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/analytics/scenarios.py +0 -0
  50. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/banking/history.py +0 -0
  51. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/brokerage/__init__.py +0 -0
  52. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/__init__.py +0 -0
  53. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/add.py +0 -0
  54. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/alerts.py +0 -0
  55. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/ease.py +0 -0
  56. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/models.py +0 -0
  57. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
  58. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
  59. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
  60. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
  61. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
  62. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/templates.py +0 -0
  63. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/budgets/tracker.py +0 -0
  64. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/cashflows/core.py +0 -0
  65. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/categorization/__init__.py +0 -0
  66. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/categorization/add.py +0 -0
  67. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/categorization/ease.py +0 -0
  68. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/categorization/rules.py +0 -0
  69. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/categorization/taxonomy.py +0 -0
  70. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/chat/planning.py +0 -0
  71. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/cli/__init__.py +0 -0
  72. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/cli/cmds/__init__.py +0 -0
  73. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/cli/cmds/scaffold_cmds.py +0 -0
  74. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/clients/__init__.py +0 -0
  75. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/clients/base.py +0 -0
  76. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/clients/plaid.py +0 -0
  77. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/credit/__init__.py +0 -0
  78. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/credit/add.py +0 -0
  79. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/credit/experian/__init__.py +0 -0
  80. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/credit/experian/auth.py +0 -0
  81. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/credit/experian/client.py +0 -0
  82. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/credit/experian/parser.py +0 -0
  83. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/credit/experian/provider.py +0 -0
  84. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/credit/mock.py +0 -0
  85. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/crypto/__init__.py +0 -0
  86. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/documents/__init__.py +0 -0
  87. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/documents/analysis.py +0 -0
  88. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/documents/ease.py +0 -0
  89. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/documents/models.py +0 -0
  90. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/documents/ocr.py +0 -0
  91. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/documents/storage.py +0 -0
  92. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/exceptions.py +0 -0
  93. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/__init__.py +0 -0
  94. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/add.py +0 -0
  95. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/funding.py +0 -0
  96. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/management.py +0 -0
  97. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/milestones.py +0 -0
  98. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/models.py +0 -0
  99. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
  100. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
  101. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
  102. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
  103. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
  104. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/insights/aggregator.py +0 -0
  105. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/insights/models.py +0 -0
  106. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/ease.py +0 -0
  107. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/providers/__init__.py +0 -0
  108. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/providers/base.py +0 -0
  109. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
  110. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
  111. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
  112. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
  113. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
  114. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/__init__.py +0 -0
  115. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/accounts.py +0 -0
  116. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/brokerage.py +0 -0
  117. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/candle.py +0 -0
  118. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/credit.py +0 -0
  119. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/money.py +0 -0
  120. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/quotes.py +0 -0
  121. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/models/tax.py +0 -0
  122. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/__init__.py +0 -0
  123. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/calculator.py +0 -0
  124. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/goals.py +0 -0
  125. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/insights.py +0 -0
  126. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
  127. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
  128. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
  129. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
  130. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
  131. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/normalization/currency_converter.py +0 -0
  132. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/normalization/models.py +0 -0
  133. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/normalization/providers/__init__.py +0 -0
  134. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/normalization/providers/exchangerate.py +0 -0
  135. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/normalization/providers/static_mappings.py +0 -0
  136. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/normalization/symbol_resolver.py +0 -0
  137. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/obs/__init__.py +0 -0
  138. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/__init__.py +0 -0
  139. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/banking/base.py +0 -0
  140. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/banking/plaid_client.py +0 -0
  141. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/banking/teller_client.py +0 -0
  142. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/brokerage/alpaca.py +0 -0
  143. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/brokerage/base.py +0 -0
  144. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/credit/experian.py +0 -0
  145. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
  146. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/market/alphavantage.py +0 -0
  147. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/market/base.py +0 -0
  148. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/market/ccxt_crypto.py +0 -0
  149. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/market/coingecko.py +0 -0
  150. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/market/yahoo.py +0 -0
  151. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/registry.py +0 -0
  152. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/tax/__init__.py +0 -0
  153. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/tax/irs.py +0 -0
  154. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/tax/mock.py +0 -0
  155. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/providers/tax/taxbit.py +0 -0
  156. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/py.typed +0 -0
  157. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/__init__.py +0 -0
  158. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/detector.py +0 -0
  159. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/normalizer.py +0 -0
  160. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/recurring/summary.py +0 -0
  161. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/scaffold/__init__.py +0 -0
  162. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/scaffold/budgets.py +0 -0
  163. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/scaffold/goals.py +0 -0
  164. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/security/__init__.py +0 -0
  165. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/security/add.py +0 -0
  166. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/security/audit.py +0 -0
  167. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/security/encryption.py +0 -0
  168. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/security/models.py +0 -0
  169. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/security/pii_filter.py +0 -0
  170. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/security/pii_patterns.py +0 -0
  171. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/security/token_store.py +0 -0
  172. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/settings.py +0 -0
  173. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/tax/__init__.py +0 -0
  174. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/tax/add.py +0 -0
  175. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/tax/tlh.py +0 -0
  176. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/utils/__init__.py +0 -0
  177. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/utils/http.py +0 -0
  178. {fin_infra-0.1.65 → fin_infra-0.1.66}/src/fin_infra/utils/retry.py +0 -0
  179. {fin_infra-0.1.65 → fin_infra-0.1.66}/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.65
3
+ Version: 0.1.66
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
@@ -30,7 +30,6 @@ Requires-Dist: plaid-python (>=25.0.0)
30
30
  Requires-Dist: pydantic (>=2.0)
31
31
  Requires-Dist: pydantic-settings (>=2.0)
32
32
  Requires-Dist: python-dotenv (>=1.0.0)
33
- Requires-Dist: svc-infra (>=0.1.0)
34
33
  Requires-Dist: tenacity (>=8.0.0)
35
34
  Requires-Dist: typing-extensions (>=4.0)
36
35
  Requires-Dist: yahooquery (>=2.3.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "fin-infra"
3
- version = "0.1.65"
3
+ version = "0.1.66"
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"
@@ -49,7 +49,6 @@ loguru = ">=0.7.0"
49
49
  numpy = ">=1.24.0"
50
50
  numpy-financial = ">=1.0.0"
51
51
  # svc-infra = { path = "../svc-infra", develop = true } # Local dev only - uncomment for local development
52
- svc-infra = ">=0.1.0" # Published version for CI/production
53
52
 
54
53
  # Provider dependencies (installed by default for easy_* functions)
55
54
  yahooquery = ">=2.3.0"
@@ -6,6 +6,7 @@ Provides income vs expense analysis, breakdowns by source/category, and forecast
6
6
  from __future__ import annotations
7
7
 
8
8
  from datetime import datetime, timedelta
9
+ from decimal import Decimal
9
10
  from typing import Any, Optional
10
11
 
11
12
  from ..models import Transaction
@@ -219,7 +220,7 @@ async def forecast_cash_flow(
219
220
  def _categorize_transactions(
220
221
  transactions: list[Transaction],
221
222
  categorization_provider=None,
222
- ) -> tuple[dict[str, float], dict[str, float]]:
223
+ ) -> tuple[dict[str, Decimal], dict[str, Decimal]]:
223
224
  """Helper to categorize transactions into income sources and expense categories.
224
225
 
225
226
  Args:
@@ -229,19 +230,19 @@ def _categorize_transactions(
229
230
  Returns:
230
231
  Tuple of (income_by_source, expenses_by_category) dicts
231
232
  """
232
- income_by_source: dict[str, float] = {}
233
- expenses_by_category: dict[str, float] = {}
233
+ income_by_source: dict[str, Decimal] = {}
234
+ expenses_by_category: dict[str, Decimal] = {}
234
235
 
235
236
  for txn in transactions:
236
237
  if txn.amount > 0:
237
238
  # Income transaction
238
239
  source = _determine_income_source(txn)
239
- income_by_source[source] = income_by_source.get(source, 0) + txn.amount
240
+ income_by_source[source] = income_by_source.get(source, Decimal(0)) + txn.amount
240
241
  else:
241
242
  # Expense transaction
242
243
  category = _get_expense_category(txn, categorization_provider)
243
244
  amount = abs(txn.amount)
244
- expenses_by_category[category] = expenses_by_category.get(category, 0) + amount
245
+ expenses_by_category[category] = expenses_by_category.get(category, Decimal(0)) + amount
245
246
 
246
247
  return income_by_source, expenses_by_category
247
248
 
@@ -565,7 +565,6 @@ def portfolio_metrics_with_holdings(holdings: list) -> PortfolioMetrics:
565
565
  >>> metrics = portfolio_metrics_with_holdings(holdings)
566
566
  """
567
567
  # Import here to avoid circular dependency
568
- from decimal import Decimal
569
568
 
570
569
  # Calculate total portfolio value and cost basis
571
570
  total_value = float(sum(
@@ -739,7 +738,7 @@ def _calculate_allocation_from_holdings(
739
738
  }
740
739
 
741
740
  # Sum values by asset class
742
- allocation_values = defaultdict(float)
741
+ allocation_values: dict[str, float] = defaultdict(float)
743
742
  for holding in holdings:
744
743
  security_type = holding.security.type.value if hasattr(holding.security.type, 'value') else holding.security.type
745
744
  asset_class = type_to_class.get(security_type, "Other")
@@ -292,10 +292,10 @@ async def _calculate_spending_trends(
292
292
  # In reality, would fetch historical data
293
293
  previous_amount = current_amount * Decimal("0.9")
294
294
 
295
- change_percent = (
296
- ((current_amount - previous_amount) / previous_amount) * 100
295
+ change_percent: float = (
296
+ float((current_amount - previous_amount) / previous_amount) * 100
297
297
  if previous_amount > 0
298
- else 0
298
+ else 0.0
299
299
  )
300
300
 
301
301
  # Threshold for "stable" is within 5%
@@ -343,8 +343,8 @@ async def _detect_spending_anomalies(
343
343
  # In reality, would calculate from historical data
344
344
  average_amount = current_amount * Decimal("0.8")
345
345
 
346
- deviation_percent = (
347
- ((current_amount - average_amount) / average_amount) * 100 if average_amount > 0 else 0
346
+ deviation_percent: float = (
347
+ float((current_amount - average_amount) / average_amount) * 100 if average_amount > 0 else 0.0
348
348
  )
349
349
 
350
350
  # Detect anomalies based on deviation
@@ -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 # type: ignore[attr-defined]
473
+ from ai_infra.llm import LLM
474
474
  except ImportError:
475
475
  # Graceful degradation: return rule-based insights
476
476
  return _generate_rule_based_insights(spending_insight, user_context)
@@ -592,7 +592,7 @@ def add_banking(
592
592
 
593
593
 
594
594
  # Import utilities at end to avoid circular imports
595
- from .utils import (
595
+ from .utils import ( # noqa: E402
596
596
  validate_plaid_token,
597
597
  validate_teller_token,
598
598
  validate_mx_token,
@@ -8,11 +8,9 @@ Apps still manage user-to-token mappings, but these utilities simplify common op
8
8
  from __future__ import annotations
9
9
 
10
10
  import re
11
- from datetime import datetime, timezone, timedelta
11
+ from datetime import datetime, timezone
12
12
  from typing import Any, Dict, Optional, Literal
13
13
  from pydantic import BaseModel, ConfigDict, Field
14
- from pydantic.json_schema import JsonSchemaValue
15
- from pydantic_core import core_schema
16
14
 
17
15
  from ..providers.base import BankingProvider
18
16
 
@@ -270,7 +268,7 @@ def sanitize_connection_status(status: BankingConnectionStatus) -> Dict[str, Any
270
268
  >>> safe_data = sanitize_connection_status(status)
271
269
  >>> return {"connections": safe_data} # Safe to return to client
272
270
  """
273
- result = {
271
+ result: dict[str, Any] = {
274
272
  "has_any_connection": status.has_any_connection,
275
273
  "connected_providers": status.connected_providers,
276
274
  "primary_provider": status.primary_provider,
@@ -280,7 +278,8 @@ def sanitize_connection_status(status: BankingConnectionStatus) -> Dict[str, Any
280
278
  for provider_name in ["plaid", "teller", "mx"]:
281
279
  info = getattr(status, provider_name)
282
280
  if info:
283
- result["providers"][provider_name] = {
281
+ providers_dict: dict[str, Any] = result["providers"]
282
+ providers_dict[provider_name] = {
284
283
  "connected": info.connected,
285
284
  "item_id": info.item_id,
286
285
  "enrollment_id": info.enrollment_id,
@@ -415,7 +414,7 @@ async def test_connection_health(
415
414
  """
416
415
  try:
417
416
  # Try to fetch accounts (lightweight call)
418
- accounts = provider.accounts(access_token)
417
+ provider.accounts(access_token)
419
418
 
420
419
  # If we got here, connection is healthy
421
420
  return True, None
@@ -22,10 +22,13 @@ Example usage:
22
22
  rate = irr(cashflows)
23
23
  """
24
24
 
25
- from typing import Iterable
25
+ from typing import TYPE_CHECKING
26
26
 
27
27
  import numpy_financial as npf
28
28
 
29
+ if TYPE_CHECKING:
30
+ from fastapi import FastAPI
31
+
29
32
  from .core import npv, irr
30
33
 
31
34
  __all__ = ["npv", "irr", "pmt", "fv", "pv", "add_cashflows"]
@@ -110,7 +113,7 @@ def pv(rate: float, nper: int, pmt: float, fv: float = 0, when: str = "end") ->
110
113
 
111
114
 
112
115
  def add_cashflows(
113
- app: "FastAPI", # type: ignore
116
+ app: "FastAPI",
114
117
  *,
115
118
  prefix: str = "/cashflows",
116
119
  ) -> None:
@@ -169,11 +172,6 @@ def add_cashflows(
169
172
  - Integrated with svc-infra observability
170
173
  - Scoped docs at {prefix}/docs
171
174
  """
172
- from typing import TYPE_CHECKING
173
-
174
- if TYPE_CHECKING:
175
- from fastapi import FastAPI
176
-
177
175
  from pydantic import BaseModel, Field
178
176
 
179
177
  # Import svc-infra public router (no auth - utility calculations)
@@ -254,4 +252,4 @@ def add_cashflows(
254
252
  # Mount router
255
253
  app.include_router(router, include_in_schema=True)
256
254
 
257
- print(f"✅ Cashflow calculations enabled (NPV, IRR, PMT, FV, PV)")
255
+ print("✅ Cashflow calculations enabled (NPV, IRR, PMT, FV, PV)")
@@ -95,7 +95,7 @@ class CategorizationEngine:
95
95
  Returns:
96
96
  CategoryPrediction with category, confidence, and method
97
97
  """
98
- start_time = time.perf_counter()
98
+ time.perf_counter()
99
99
 
100
100
  # Normalize merchant name
101
101
  normalized = self._normalize(merchant_name)
@@ -333,7 +333,7 @@ def get_engine() -> CategorizationEngine:
333
333
  return _default_engine
334
334
 
335
335
 
336
- def categorize(
336
+ async def categorize(
337
337
  merchant_name: str,
338
338
  user_id: Optional[str] = None,
339
339
  include_alternatives: bool = False,
@@ -350,4 +350,4 @@ def categorize(
350
350
  CategoryPrediction with category, confidence, and method
351
351
  """
352
352
  engine = get_engine()
353
- return engine.categorize(merchant_name, user_id, include_alternatives)
353
+ return await engine.categorize(merchant_name, user_id, include_alternatives)
@@ -15,13 +15,12 @@ Expected performance:
15
15
 
16
16
  import hashlib
17
17
  import logging
18
- from typing import Optional, List, Tuple, cast
18
+ from typing import Any, List, Optional, Tuple, cast
19
19
  from pydantic import BaseModel, Field
20
20
 
21
21
  # ai-infra imports
22
22
  try:
23
- from ai_infra.llm import LLM # type: ignore[attr-defined]
24
- from ai_infra.llm.providers import Providers # type: ignore[attr-defined]
23
+ from ai_infra.llm import LLM
25
24
  except ImportError:
26
25
  raise ImportError("ai-infra not installed. Install with: pip install ai-infra")
27
26
 
@@ -217,7 +216,7 @@ class LLMCategorizer:
217
216
  user_message = self._build_user_message(merchant_name, user_id)
218
217
 
219
218
  # Call LLM with retry logic
220
- extra = {
219
+ extra: dict[str, Any] = {
221
220
  "retry": {
222
221
  "max_tries": 3,
223
222
  "base": 0.5,
@@ -44,7 +44,7 @@ class CategoryPrediction(BaseModel):
44
44
  "category": "Coffee Shops",
45
45
  "confidence": 0.98,
46
46
  "method": "exact",
47
- "alternatives": [("Restaurants", 0.15), ("Fast Food", 0.10)],
47
+ "alternatives": [["Restaurants", 0.15], ["Fast Food", 0.10]],
48
48
  }
49
49
  }
50
50
  )
@@ -28,6 +28,11 @@ Example:
28
28
  )
29
29
  """
30
30
 
31
+ from typing import TYPE_CHECKING, Any
32
+
33
+ if TYPE_CHECKING:
34
+ from fastapi import FastAPI
35
+
31
36
  from fin_infra.chat.planning import (
32
37
  FinancialPlanningConversation,
33
38
  ConversationResponse,
@@ -51,7 +56,7 @@ __all__ = [
51
56
 
52
57
 
53
58
  def add_financial_conversation(
54
- app: "FastAPI", # type: ignore
59
+ app: "FastAPI",
55
60
  *,
56
61
  prefix: str = "/chat",
57
62
  conversation: FinancialPlanningConversation | None = None,
@@ -110,11 +115,6 @@ def add_financial_conversation(
110
115
  - Includes financial advice disclaimer in all responses
111
116
  - Logs all LLM calls for compliance (via svc-infra logging)
112
117
  """
113
- from typing import TYPE_CHECKING, Any
114
-
115
- if TYPE_CHECKING:
116
- from fastapi import FastAPI
117
-
118
118
  from pydantic import BaseModel, Field
119
119
 
120
120
  # Import svc-infra user router (requires auth)
@@ -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 # type: ignore[attr-defined]
72
+ from ai_infra.llm import LLM
73
73
 
74
74
  llm = LLM()
75
75
  except ImportError:
@@ -118,7 +118,6 @@ def add_compliance_tracking(
118
118
 
119
119
  # Track only GET requests (data access)
120
120
  if method != "GET":
121
- from starlette.responses import Response as StarletteResponse
122
121
  return cast("Response", await call_next(request))
123
122
 
124
123
  # Determine if path is a compliance-tracked endpoint
@@ -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 # type: ignore[attr-defined]
18
+ from ai_infra.llm import LLM
19
19
 
20
20
 
21
21
  class CryptoInsight(BaseModel):
@@ -258,10 +258,8 @@ Provide your insight:"""
258
258
 
259
259
  try:
260
260
  # Use natural language conversation (no output_schema)
261
- # Note: In tests, achat is mocked with messages= parameter
262
- # In production, this should use user_msg, provider, model_name parameters
263
261
  response = await llm.achat(
264
- messages=[{"role": "user", "content": prompt}],
262
+ user_msg=prompt,
265
263
  )
266
264
 
267
265
  # Parse response text
@@ -104,7 +104,7 @@ def add_documents(
104
104
 
105
105
  # Step 1: Mount base endpoints (upload, list, get, delete) via svc-infra
106
106
  # This returns the base DocumentManager, but we'll create our own FinancialDocumentManager
107
- if HAS_SVC_INFRA_DOCUMENTS and add_base_documents:
107
+ if HAS_SVC_INFRA_DOCUMENTS and add_base_documents is not None:
108
108
  add_base_documents(app, storage_backend=storage, prefix=prefix, tags=tags)
109
109
  else:
110
110
  # Legacy mode: mount basic endpoints inline (for svc-infra < 0.1.668)
@@ -10,6 +10,11 @@ Aggregates insights from multiple sources:
10
10
  - Cash flow projections
11
11
  """
12
12
 
13
+ from typing import TYPE_CHECKING
14
+
15
+ if TYPE_CHECKING:
16
+ from fastapi import FastAPI
17
+
13
18
  from .models import Insight, InsightFeed, InsightPriority, InsightCategory
14
19
  from .aggregator import aggregate_insights, get_user_insights
15
20
 
@@ -25,7 +30,7 @@ __all__ = [
25
30
 
26
31
 
27
32
  def add_insights(
28
- app: "FastAPI", # type: ignore
33
+ app: "FastAPI",
29
34
  *,
30
35
  prefix: str = "/insights",
31
36
  ) -> None:
@@ -71,11 +76,6 @@ def add_insights(
71
76
  - Real-time aggregation from net worth, budgets, goals, etc.
72
77
  - Notification system for critical insights
73
78
  """
74
- from typing import TYPE_CHECKING
75
-
76
- if TYPE_CHECKING:
77
- from fastapi import FastAPI
78
-
79
79
  from fastapi import Query
80
80
 
81
81
  # Import svc-infra user router (requires auth)
@@ -125,5 +125,5 @@ def add_insights(
125
125
  # Mount router
126
126
  app.include_router(router, include_in_schema=True)
127
127
 
128
- print(f"✅ Insights feed enabled (unified financial insights)")
128
+ print("✅ Insights feed enabled (unified financial insights)")
129
129
 
@@ -180,7 +180,7 @@ def add_investments(
180
180
  # Resolve provider from string Literal to actual InvestmentProvider instance
181
181
  resolved_provider: InvestmentProviderBase | None = None
182
182
  if provider is not None:
183
- resolved_provider = easy_investments(provider=provider, **provider_config) # type: ignore[assignment]
183
+ resolved_provider = easy_investments(provider=provider, **provider_config)
184
184
 
185
185
  return add_investments_impl(
186
186
  app,
@@ -7,14 +7,13 @@ transactions, accounts, allocation, and securities data.
7
7
  from __future__ import annotations
8
8
 
9
9
  from datetime import date
10
- from typing import TYPE_CHECKING, Optional, Literal, Annotated
10
+ from typing import TYPE_CHECKING, Optional
11
11
 
12
- from fastapi import HTTPException, Query, Depends
12
+ from fastapi import HTTPException
13
13
  from pydantic import BaseModel, Field
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from fastapi import FastAPI
17
- from svc_infra.api.fastapi.auth.security import Principal
18
17
 
19
18
  # Import Identity for dependency injection
20
19
  try:
@@ -17,10 +17,10 @@ Models are provider-agnostic and normalize data from Plaid, SnapTrade, etc.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- from datetime import date, datetime
20
+ from datetime import date
21
21
  from decimal import Decimal
22
22
  from enum import Enum
23
- from typing import Dict, List, Optional
23
+ from typing import TYPE_CHECKING, Dict, List, Optional
24
24
 
25
25
  from pydantic import BaseModel, ConfigDict, Field, computed_field
26
26
 
@@ -201,22 +201,41 @@ class Holding(BaseModel):
201
201
  unofficial_currency_code: Optional[str] = Field(None, description="For crypto/alt currencies")
202
202
  as_of_date: Optional[date] = Field(None, description="Date of pricing data")
203
203
 
204
- @computed_field
205
- @property
206
- def unrealized_gain_loss(self) -> Optional[Decimal]:
207
- """Calculate unrealized gain/loss (current value - cost basis)."""
208
- if self.cost_basis is None:
209
- return None
210
- return self.institution_value - self.cost_basis
211
-
212
- @computed_field
213
- @property
214
- def unrealized_gain_loss_percent(self) -> Optional[Decimal]:
215
- """Calculate unrealized gain/loss percentage."""
216
- if self.cost_basis is None or self.cost_basis == 0:
217
- return None
218
- gain_loss = self.institution_value - self.cost_basis
219
- return round((gain_loss / self.cost_basis) * 100, 2)
204
+ if TYPE_CHECKING:
205
+
206
+ @property
207
+ def unrealized_gain_loss(self) -> Optional[Decimal]:
208
+ """Calculate unrealized gain/loss (current value - cost basis)."""
209
+ if self.cost_basis is None:
210
+ return None
211
+ return self.institution_value - self.cost_basis
212
+
213
+ @property
214
+ def unrealized_gain_loss_percent(self) -> Optional[Decimal]:
215
+ """Calculate unrealized gain/loss percentage."""
216
+ if self.cost_basis is None or self.cost_basis == 0:
217
+ return None
218
+ gain_loss = self.institution_value - self.cost_basis
219
+ return round((gain_loss / self.cost_basis) * 100, 2)
220
+
221
+ else:
222
+
223
+ @computed_field
224
+ @property
225
+ def unrealized_gain_loss(self) -> Optional[Decimal]:
226
+ """Calculate unrealized gain/loss (current value - cost basis)."""
227
+ if self.cost_basis is None:
228
+ return None
229
+ return self.institution_value - self.cost_basis
230
+
231
+ @computed_field
232
+ @property
233
+ def unrealized_gain_loss_percent(self) -> Optional[Decimal]:
234
+ """Calculate unrealized gain/loss percentage."""
235
+ if self.cost_basis is None or self.cost_basis == 0:
236
+ return None
237
+ gain_loss = self.institution_value - self.cost_basis
238
+ return round((gain_loss / self.cost_basis) * 100, 2)
220
239
 
221
240
 
222
241
  class InvestmentTransaction(BaseModel):
@@ -350,34 +369,69 @@ class InvestmentAccount(BaseModel):
350
369
  # Holdings
351
370
  holdings: List[Holding] = Field(default_factory=list, description="List of holdings in account")
352
371
 
353
- @computed_field
354
- @property
355
- def total_value(self) -> Decimal:
356
- """Calculate total account value (sum of holdings + cash)."""
357
- holdings_value = sum(h.institution_value for h in self.holdings)
358
- cash_balance = self.balances.get("current") or Decimal(0)
359
- return holdings_value + cash_balance
360
-
361
- @computed_field
362
- @property
363
- def total_cost_basis(self) -> Decimal:
364
- """Calculate total cost basis (sum of cost_basis across holdings)."""
365
- return sum(h.cost_basis for h in self.holdings if h.cost_basis is not None)
366
-
367
- @computed_field
368
- @property
369
- def total_unrealized_gain_loss(self) -> Decimal:
370
- """Calculate total unrealized P&L (value - cost_basis)."""
371
- holdings_value = sum(h.institution_value for h in self.holdings)
372
- return holdings_value - self.total_cost_basis
373
-
374
- @computed_field
375
- @property
376
- def total_unrealized_gain_loss_percent(self) -> Optional[Decimal]:
377
- """Calculate total unrealized P&L percentage."""
378
- if self.total_cost_basis == 0:
379
- return None
380
- return round((self.total_unrealized_gain_loss / self.total_cost_basis) * 100, 2)
372
+ if TYPE_CHECKING:
373
+
374
+ @property
375
+ def total_value(self) -> Decimal:
376
+ """Calculate total account value (sum of holdings + cash)."""
377
+ holdings_value = sum((h.institution_value for h in self.holdings), start=Decimal(0))
378
+ cash_balance = self.balances.get("current") or Decimal(0)
379
+ return holdings_value + cash_balance
380
+
381
+ @property
382
+ def total_cost_basis(self) -> Decimal:
383
+ """Calculate total cost basis (sum of cost_basis across holdings)."""
384
+ return sum(
385
+ (h.cost_basis for h in self.holdings if h.cost_basis is not None),
386
+ start=Decimal(0),
387
+ )
388
+
389
+ @property
390
+ def total_unrealized_gain_loss(self) -> Decimal:
391
+ """Calculate total unrealized P&L (value - cost_basis)."""
392
+ holdings_value = sum((h.institution_value for h in self.holdings), start=Decimal(0))
393
+ return holdings_value - self.total_cost_basis
394
+
395
+ @property
396
+ def total_unrealized_gain_loss_percent(self) -> Optional[Decimal]:
397
+ """Calculate total unrealized P&L percentage."""
398
+ if self.total_cost_basis == 0:
399
+ return None
400
+ return round((self.total_unrealized_gain_loss / self.total_cost_basis) * 100, 2)
401
+
402
+ else:
403
+
404
+ @computed_field
405
+ @property
406
+ def total_value(self) -> Decimal:
407
+ """Calculate total account value (sum of holdings + cash)."""
408
+ holdings_value = sum((h.institution_value for h in self.holdings), start=Decimal(0))
409
+ cash_balance = self.balances.get("current") or Decimal(0)
410
+ return holdings_value + cash_balance
411
+
412
+ @computed_field
413
+ @property
414
+ def total_cost_basis(self) -> Decimal:
415
+ """Calculate total cost basis (sum of cost_basis across holdings)."""
416
+ return sum(
417
+ (h.cost_basis for h in self.holdings if h.cost_basis is not None),
418
+ start=Decimal(0),
419
+ )
420
+
421
+ @computed_field
422
+ @property
423
+ def total_unrealized_gain_loss(self) -> Decimal:
424
+ """Calculate total unrealized P&L (value - cost_basis)."""
425
+ holdings_value = sum((h.institution_value for h in self.holdings), start=Decimal(0))
426
+ return holdings_value - self.total_cost_basis
427
+
428
+ @computed_field
429
+ @property
430
+ def total_unrealized_gain_loss_percent(self) -> Optional[Decimal]:
431
+ """Calculate total unrealized P&L percentage."""
432
+ if self.total_cost_basis == 0:
433
+ return None
434
+ return round((self.total_unrealized_gain_loss / self.total_cost_basis) * 100, 2)
381
435
 
382
436
 
383
437
  class AssetAllocation(BaseModel):
@@ -31,7 +31,6 @@ from ..models import (
31
31
  InvestmentAccount,
32
32
  InvestmentTransaction,
33
33
  Security,
34
- SecurityType,
35
34
  TransactionType,
36
35
  )
37
36
  from .base import InvestmentProvider
@@ -344,8 +343,8 @@ class PlaidInvestmentProvider(InvestmentProvider):
344
343
  type=account_dict.get("type", "investment"),
345
344
  subtype=account_dict.get("subtype"),
346
345
  balances={
347
- "current": float(account_dict.get("balances", {}).get("current", 0)),
348
- "available": float(account_dict.get("balances", {}).get("available")),
346
+ "current": Decimal(str(account_dict.get("balances", {}).get("current", 0))),
347
+ "available": Decimal(str(account_dict.get("balances", {}).get("available") or 0)),
349
348
  },
350
349
  holdings=holdings,
351
350
  )
@@ -354,8 +354,8 @@ class SnapTradeInvestmentProvider(InvestmentProvider):
354
354
  type=account.get("type", "investment"),
355
355
  subtype=account.get("account_type"),
356
356
  balances={
357
- "current": float(balances.get("total", {}).get("amount", 0)),
358
- "available": float(balances.get("cash", {}).get("amount", 0)),
357
+ "current": Decimal(str(balances.get("total", {}).get("amount", 0))),
358
+ "available": Decimal(str(balances.get("cash", {}).get("amount", 0))),
359
359
  },
360
360
  holdings=holdings,
361
361
  )
@@ -181,14 +181,10 @@ def add_market_data(
181
181
  - docs/adr/0004-market-data-integration.md: Architecture decisions
182
182
  """
183
183
  from fastapi import HTTPException, Query
184
- from typing import TYPE_CHECKING, Optional
185
184
 
186
185
  # Import svc-infra public router (no auth required for market data)
187
186
  from svc_infra.api.fastapi.dual.public import public_router
188
187
 
189
- if TYPE_CHECKING:
190
- from fastapi import FastAPI
191
-
192
188
  # Create market provider instance (or use the provided one)
193
189
  if isinstance(provider, MarketDataProvider):
194
190
  market = provider
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from datetime import date
3
+ from datetime import date # noqa: F401 - used in type annotation
4
4
  from decimal import Decimal
5
5
  from typing import Optional
6
6