fin-infra 0.1.80__tar.gz → 0.1.82__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.80 → fin_infra-0.1.82}/PKG-INFO +1 -1
  2. {fin_infra-0.1.80 → fin_infra-0.1.82}/pyproject.toml +56 -1
  3. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/__init__.py +3 -2
  4. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/add.py +21 -21
  5. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/ease.py +19 -20
  6. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/portfolio.py +6 -6
  7. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/projections.py +1 -3
  8. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/banking/__init__.py +27 -27
  9. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/banking/history.py +8 -9
  10. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/banking/utils.py +27 -26
  11. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/brokerage/__init__.py +22 -24
  12. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/add.py +16 -17
  13. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/alerts.py +3 -3
  14. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/tracker.py +2 -2
  15. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/cashflows/__init__.py +2 -2
  16. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/cashflows/core.py +1 -1
  17. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/categorization/add.py +2 -3
  18. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/categorization/engine.py +6 -6
  19. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/categorization/llm_layer.py +7 -6
  20. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/categorization/rules.py +2 -4
  21. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/categorization/taxonomy.py +2 -2
  22. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/chat/planning.py +0 -1
  23. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/cli/cmds/scaffold_cmds.py +10 -11
  24. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/clients/base.py +1 -1
  25. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/clients/plaid.py +2 -2
  26. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/compliance/__init__.py +5 -4
  27. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/credit/add.py +6 -7
  28. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/credit/experian/auth.py +2 -2
  29. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/credit/experian/client.py +1 -1
  30. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/credit/experian/provider.py +4 -4
  31. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/crypto/__init__.py +7 -9
  32. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/documents/add.py +6 -8
  33. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/documents/analysis.py +9 -9
  34. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/documents/ease.py +14 -14
  35. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/documents/models.py +4 -4
  36. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/documents/ocr.py +7 -7
  37. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/documents/storage.py +21 -13
  38. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/exceptions.py +0 -1
  39. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/add.py +36 -36
  40. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/funding.py +4 -6
  41. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/management.py +2 -3
  42. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/milestones.py +1 -2
  43. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/models.py +7 -11
  44. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/insights/__init__.py +2 -2
  45. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/insights/aggregator.py +1 -1
  46. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/__init__.py +1 -1
  47. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/add.py +23 -23
  48. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/models.py +5 -5
  49. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/providers/base.py +8 -9
  50. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/providers/plaid.py +17 -17
  51. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/providers/snaptrade.py +19 -19
  52. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/markets/__init__.py +1 -1
  53. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/__init__.py +10 -10
  54. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/brokerage.py +2 -1
  55. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/candle.py +1 -0
  56. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/money.py +1 -0
  57. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/quotes.py +4 -3
  58. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/tax.py +2 -1
  59. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/transactions.py +3 -4
  60. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/insights.py +0 -1
  61. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/normalization/__init__.py +2 -2
  62. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/normalization/providers/exchangerate.py +5 -5
  63. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/obs/classifier.py +1 -1
  64. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/banking/plaid_client.py +5 -5
  65. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/banking/teller_client.py +7 -6
  66. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/base.py +2 -1
  67. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/brokerage/alpaca.py +3 -3
  68. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/market/alphavantage.py +6 -11
  69. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/market/ccxt_crypto.py +2 -2
  70. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/market/coingecko.py +5 -6
  71. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/market/yahoo.py +6 -6
  72. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/add.py +5 -4
  73. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/detector.py +7 -7
  74. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/detectors_llm.py +6 -6
  75. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/ease.py +2 -4
  76. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/insights.py +13 -13
  77. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/normalizer.py +1 -1
  78. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/normalizers.py +4 -4
  79. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/summary.py +13 -15
  80. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/scaffold/budgets.py +9 -9
  81. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/scaffold/goals.py +5 -5
  82. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/security/__init__.py +8 -8
  83. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/security/encryption.py +6 -6
  84. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/security/models.py +7 -7
  85. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/settings.py +2 -1
  86. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/tax/__init__.py +1 -1
  87. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/tax/add.py +3 -2
  88. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/tax/tlh.py +5 -5
  89. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/utils/http.py +4 -3
  90. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/utils/retry.py +2 -1
  91. {fin_infra-0.1.80 → fin_infra-0.1.82}/LICENSE +0 -0
  92. {fin_infra-0.1.80 → fin_infra-0.1.82}/README.md +0 -0
  93. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/__init__.py +0 -0
  94. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/__main__.py +0 -0
  95. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/cash_flow.py +0 -0
  96. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/models.py +0 -0
  97. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/rebalancing.py +0 -0
  98. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/savings.py +0 -0
  99. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/scenarios.py +0 -0
  100. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/analytics/spending.py +0 -0
  101. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/__init__.py +3 -3
  102. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/ease.py +0 -0
  103. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/models.py +0 -0
  104. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/scaffold_templates/README.md +0 -0
  105. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/scaffold_templates/__init__.py +0 -0
  106. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/scaffold_templates/models.py.tmpl +0 -0
  107. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/scaffold_templates/repository.py.tmpl +0 -0
  108. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/scaffold_templates/schemas.py.tmpl +0 -0
  109. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/budgets/templates.py +0 -0
  110. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/categorization/__init__.py +0 -0
  111. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/categorization/ease.py +0 -0
  112. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/categorization/models.py +0 -0
  113. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/chat/__init__.py +5 -5
  114. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/chat/ease.py +0 -0
  115. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/cli/__init__.py +0 -0
  116. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/cli/cmds/__init__.py +0 -0
  117. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/clients/__init__.py +0 -0
  118. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/credit/__init__.py +0 -0
  119. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/credit/experian/__init__.py +0 -0
  120. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/credit/experian/parser.py +0 -0
  121. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/credit/mock.py +0 -0
  122. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/crypto/insights.py +0 -0
  123. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/documents/__init__.py +0 -0
  124. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/__init__.py +8 -8
  125. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/scaffold_templates/README.md +0 -0
  126. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/scaffold_templates/__init__.py +0 -0
  127. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/scaffold_templates/models.py.tmpl +0 -0
  128. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/scaffold_templates/repository.py.tmpl +0 -0
  129. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/goals/scaffold_templates/schemas.py.tmpl +0 -0
  130. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/insights/models.py +0 -0
  131. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/ease.py +0 -0
  132. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/providers/__init__.py +0 -0
  133. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/scaffold_templates/README.md +0 -0
  134. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/scaffold_templates/__init__.py +0 -0
  135. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/scaffold_templates/models.py.tmpl +0 -0
  136. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/scaffold_templates/repository.py.tmpl +0 -0
  137. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/investments/scaffold_templates/schemas.py.tmpl +0 -0
  138. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/accounts.py +0 -0
  139. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/models/credit.py +0 -0
  140. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/__init__.py +0 -0
  141. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/add.py +0 -0
  142. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/aggregator.py +0 -0
  143. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/calculator.py +0 -0
  144. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/ease.py +0 -0
  145. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/goals.py +0 -0
  146. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/models.py +0 -0
  147. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/scaffold_templates/README.md +0 -0
  148. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/scaffold_templates/__init__.py +0 -0
  149. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/scaffold_templates/models.py.tmpl +0 -0
  150. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/scaffold_templates/repository.py.tmpl +0 -0
  151. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/net_worth/scaffold_templates/schemas.py.tmpl +0 -0
  152. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/normalization/currency_converter.py +0 -0
  153. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/normalization/models.py +0 -0
  154. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/normalization/providers/__init__.py +0 -0
  155. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/normalization/providers/static_mappings.py +0 -0
  156. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/normalization/symbol_resolver.py +0 -0
  157. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/obs/__init__.py +0 -0
  158. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/__init__.py +0 -0
  159. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/banking/base.py +0 -0
  160. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/brokerage/base.py +0 -0
  161. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/credit/experian.py +0 -0
  162. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/identity/stripe_identity.py +0 -0
  163. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/market/base.py +0 -0
  164. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/registry.py +0 -0
  165. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/tax/__init__.py +1 -1
  166. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/tax/irs.py +1 -1
  167. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/tax/mock.py +5 -5
  168. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/providers/tax/taxbit.py +1 -1
  169. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/py.typed +0 -0
  170. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/__init__.py +6 -6
  171. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/recurring/models.py +0 -0
  172. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/scaffold/__init__.py +0 -0
  173. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/security/add.py +0 -0
  174. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/security/audit.py +0 -0
  175. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/security/pii_filter.py +6 -6
  176. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/security/pii_patterns.py +0 -0
  177. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/security/token_store.py +0 -0
  178. {fin_infra-0.1.80 → fin_infra-0.1.82}/src/fin_infra/utils/__init__.py +0 -0
  179. {fin_infra-0.1.80 → fin_infra-0.1.82}/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.80
3
+ Version: 0.1.82
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.80"
3
+ version = "0.1.82"
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"
@@ -101,6 +101,61 @@ markers = [
101
101
  line-length = 100
102
102
  target-version = "py311"
103
103
 
104
+ [tool.ruff.lint]
105
+ select = [
106
+ "E", # pycodestyle errors
107
+ "W", # pycodestyle warnings
108
+ "F", # Pyflakes
109
+ "I", # isort
110
+ "B", # flake8-bugbear
111
+ "C4", # flake8-comprehensions
112
+ "UP", # pyupgrade
113
+ "SIM", # flake8-simplify
114
+ "TC", # flake8-type-checking
115
+ "RUF", # Ruff-specific rules
116
+ ]
117
+
118
+ ignore = [
119
+ "E501", # line too long (handled by formatter)
120
+ "RUF022", # __all__ not sorted (we use logical grouping)
121
+ "RUF012", # mutable class attrs (handled by dataclass/pydantic)
122
+ "B007", # unused loop variable (often intentional)
123
+ "B008", # function call in default arg (typer.Option pattern)
124
+ "B027", # empty method in ABC (intentional for optional callbacks)
125
+ "B904", # raise from err (not always clearer)
126
+ "B905", # zip strict (Python 3.10+ only, adds verbosity)
127
+ "SIM102", # nested if statements (sometimes clearer)
128
+ "SIM103", # return condition directly (sometimes clearer with if)
129
+ "SIM105", # contextlib.suppress (explicit try/except often clearer)
130
+ "SIM108", # ternary operator (readability preference)
131
+ "SIM110", # use all() (sometimes clearer with explicit loop)
132
+ "SIM113", # use enumerate (sometimes clearer without)
133
+ "SIM115", # context manager for files (not always applicable)
134
+ "SIM117", # single with statement (sometimes clearer separate)
135
+ "SIM118", # key in dict vs dict.keys() (minor)
136
+ "TC001", # move application import to type-checking (breaks runtime)
137
+ "TC002", # move third-party import to type-checking (breaks runtime)
138
+ "TC003", # move stdlib import to type-checking (breaks runtime)
139
+ "UP028", # yield from (sometimes clearer with explicit yield)
140
+ "UP038", # isinstance(x, (A, B)) is valid Python and often clearer
141
+ "UP045", # X | None (runtime Optional needed for Pydantic create_model)
142
+ "UP007", # Union[X, Y] (runtime Union needed for Pydantic create_model)
143
+ "RUF001", # ambiguous unicode char in string (intentional in docs)
144
+ "RUF002", # ambiguous unicode char in docstring (intentional curly quotes)
145
+ "RUF003", # ambiguous unicode char in comment (intentional curly quotes)
146
+ ]
147
+
148
+ [tool.ruff.lint.per-file-ignores]
149
+ "tests/**/*.py" = [
150
+ "B018", # useless expression in tests is fine
151
+ ]
152
+
153
+ [tool.ruff.lint.isort]
154
+ known-first-party = ["fin_infra"]
155
+
156
+ [tool.ruff.lint.pydocstyle]
157
+ convention = "google"
158
+
104
159
  [tool.mypy]
105
160
  python_version = "3.13"
106
161
  ignore_missing_imports = true
@@ -55,10 +55,11 @@ Dependencies:
55
55
 
56
56
  from __future__ import annotations
57
57
 
58
- # Import actual implementations
59
- from .ease import easy_analytics, AnalyticsEngine
60
58
  from .add import add_analytics
61
59
 
60
+ # Import actual implementations
61
+ from .ease import AnalyticsEngine, easy_analytics
62
+
62
63
  __all__ = [
63
64
  "easy_analytics",
64
65
  "add_analytics",
@@ -7,7 +7,7 @@ MUST use svc-infra dual routers (user_router) - NEVER generic APIRouter.
7
7
  from __future__ import annotations
8
8
 
9
9
  from datetime import datetime
10
- from typing import TYPE_CHECKING, Optional
10
+ from typing import TYPE_CHECKING
11
11
 
12
12
  from fastapi import HTTPException, Query
13
13
  from pydantic import BaseModel, Field
@@ -15,15 +15,15 @@ from pydantic import BaseModel, Field
15
15
  if TYPE_CHECKING:
16
16
  from fastapi import FastAPI
17
17
 
18
- from .ease import easy_analytics, AnalyticsEngine
18
+ from .ease import AnalyticsEngine, easy_analytics
19
19
  from .models import (
20
+ BenchmarkComparison,
20
21
  CashFlowAnalysis,
21
- SavingsRateData,
22
- SpendingInsight,
22
+ GrowthProjection,
23
23
  PersonalizedSpendingAdvice,
24
24
  PortfolioMetrics,
25
- BenchmarkComparison,
26
- GrowthProjection,
25
+ SavingsRateData,
26
+ SpendingInsight,
27
27
  )
28
28
 
29
29
 
@@ -33,15 +33,15 @@ class NetWorthForecastRequest(BaseModel):
33
33
 
34
34
  user_id: str = Field(..., description="User identifier")
35
35
  years: int = Field(default=30, ge=1, le=50, description="Projection years (1-50)")
36
- initial_net_worth: Optional[float] = Field(None, description="Override initial net worth")
37
- annual_contribution: Optional[float] = Field(None, description="Annual savings contribution")
38
- conservative_return: Optional[float] = Field(
36
+ initial_net_worth: float | None = Field(None, description="Override initial net worth")
37
+ annual_contribution: float | None = Field(None, description="Annual savings contribution")
38
+ conservative_return: float | None = Field(
39
39
  None, description="Conservative return rate (e.g., 0.05 = 5%)"
40
40
  )
41
- moderate_return: Optional[float] = Field(
41
+ moderate_return: float | None = Field(
42
42
  None, description="Moderate return rate (e.g., 0.07 = 7%)"
43
43
  )
44
- aggressive_return: Optional[float] = Field(
44
+ aggressive_return: float | None = Field(
45
45
  None, description="Aggressive return rate (e.g., 0.10 = 10%)"
46
46
  )
47
47
 
@@ -49,7 +49,7 @@ class NetWorthForecastRequest(BaseModel):
49
49
  def add_analytics(
50
50
  app: FastAPI,
51
51
  prefix: str = "/analytics",
52
- provider: Optional[AnalyticsEngine] = None,
52
+ provider: AnalyticsEngine | None = None,
53
53
  include_in_schema: bool = True,
54
54
  ) -> AnalyticsEngine:
55
55
  """Add analytics endpoints to FastAPI application.
@@ -124,9 +124,9 @@ def add_analytics(
124
124
  )
125
125
  async def get_cash_flow(
126
126
  user_id: str,
127
- start_date: Optional[datetime] = None,
128
- end_date: Optional[datetime] = None,
129
- period_days: Optional[int] = None,
127
+ start_date: datetime | None = None,
128
+ end_date: datetime | None = None,
129
+ period_days: int | None = None,
130
130
  ) -> CashFlowAnalysis:
131
131
  """
132
132
  Calculate cash flow analysis for a user.
@@ -164,7 +164,7 @@ def add_analytics(
164
164
  )
165
165
  async def get_spending_insights(
166
166
  user_id: str,
167
- period_days: Optional[int] = None,
167
+ period_days: int | None = None,
168
168
  include_trends: bool = True,
169
169
  ) -> SpendingInsight:
170
170
  """
@@ -186,7 +186,7 @@ def add_analytics(
186
186
  )
187
187
  async def get_spending_advice(
188
188
  user_id: str,
189
- period_days: Optional[int] = None,
189
+ period_days: int | None = None,
190
190
  ) -> PersonalizedSpendingAdvice:
191
191
  """
192
192
  Generate personalized spending advice using AI.
@@ -206,11 +206,11 @@ def add_analytics(
206
206
  )
207
207
  async def get_portfolio_metrics(
208
208
  user_id: str,
209
- accounts: Optional[list[str]] = None,
209
+ accounts: list[str] | None = None,
210
210
  with_holdings: bool = Query(
211
211
  False, description="Use real holdings data from investment provider for accurate P/L"
212
212
  ),
213
- access_token: Optional[str] = Query(
213
+ access_token: str | None = Query(
214
214
  None, description="Investment provider access token (required if with_holdings=true)"
215
215
  ),
216
216
  ) -> PortfolioMetrics:
@@ -286,9 +286,9 @@ def add_analytics(
286
286
  )
287
287
  async def get_benchmark_comparison(
288
288
  user_id: str,
289
- benchmark: Optional[str] = None,
289
+ benchmark: str | None = None,
290
290
  period: str = "1y",
291
- accounts: Optional[list[str]] = None,
291
+ accounts: list[str] | None = None,
292
292
  ) -> BenchmarkComparison:
293
293
  """
294
294
  Compare portfolio to benchmark (e.g., SPY, VTI).
@@ -16,23 +16,22 @@ Typical usage:
16
16
  portfolio = await analytics.portfolio_metrics(user_id="user123")
17
17
  """
18
18
 
19
- from typing import Optional
20
19
  from datetime import datetime, timedelta
21
20
 
22
21
  from .cash_flow import calculate_cash_flow
23
- from .savings import calculate_savings_rate, SavingsDefinition
24
- from .spending import analyze_spending, generate_spending_insights
25
- from .portfolio import calculate_portfolio_metrics, compare_to_benchmark
26
- from .projections import project_net_worth, calculate_compound_interest
27
22
  from .models import (
23
+ BenchmarkComparison,
28
24
  CashFlowAnalysis,
29
- SavingsRateData,
30
- SpendingInsight,
25
+ GrowthProjection,
31
26
  PersonalizedSpendingAdvice,
32
27
  PortfolioMetrics,
33
- BenchmarkComparison,
34
- GrowthProjection,
28
+ SavingsRateData,
29
+ SpendingInsight,
35
30
  )
31
+ from .portfolio import calculate_portfolio_metrics, compare_to_benchmark
32
+ from .projections import calculate_compound_interest, project_net_worth
33
+ from .savings import SavingsDefinition, calculate_savings_rate
34
+ from .spending import analyze_spending, generate_spending_insights
36
35
 
37
36
 
38
37
  class AnalyticsEngine:
@@ -93,9 +92,9 @@ class AnalyticsEngine:
93
92
  self,
94
93
  user_id: str,
95
94
  *,
96
- start_date: Optional[datetime] = None,
97
- end_date: Optional[datetime] = None,
98
- period_days: Optional[int] = None,
95
+ start_date: datetime | None = None,
96
+ end_date: datetime | None = None,
97
+ period_days: int | None = None,
99
98
  ) -> CashFlowAnalysis:
100
99
  """Analyze cash flow (income vs expenses).
101
100
 
@@ -129,7 +128,7 @@ class AnalyticsEngine:
129
128
  self,
130
129
  user_id: str,
131
130
  *,
132
- definition: Optional[str | SavingsDefinition] = None,
131
+ definition: str | SavingsDefinition | None = None,
133
132
  period: str = "monthly",
134
133
  ) -> SavingsRateData:
135
134
  """Calculate savings rate.
@@ -163,7 +162,7 @@ class AnalyticsEngine:
163
162
  self,
164
163
  user_id: str,
165
164
  *,
166
- period_days: Optional[int] = None,
165
+ period_days: int | None = None,
167
166
  include_trends: bool = True,
168
167
  ) -> SpendingInsight:
169
168
  """Analyze spending patterns and generate insights.
@@ -193,8 +192,8 @@ class AnalyticsEngine:
193
192
  self,
194
193
  user_id: str,
195
194
  *,
196
- period_days: Optional[int] = None,
197
- user_context: Optional[dict] = None,
195
+ period_days: int | None = None,
196
+ user_context: dict | None = None,
198
197
  ) -> PersonalizedSpendingAdvice:
199
198
  """Generate AI-powered personalized spending advice.
200
199
 
@@ -228,7 +227,7 @@ class AnalyticsEngine:
228
227
  self,
229
228
  user_id: str,
230
229
  *,
231
- accounts: Optional[list[str]] = None,
230
+ accounts: list[str] | None = None,
232
231
  ) -> PortfolioMetrics:
233
232
  """Calculate portfolio performance metrics.
234
233
 
@@ -250,9 +249,9 @@ class AnalyticsEngine:
250
249
  self,
251
250
  user_id: str,
252
251
  *,
253
- benchmark: Optional[str] = None,
252
+ benchmark: str | None = None,
254
253
  period: str = "1y",
255
- accounts: Optional[list[str]] = None,
254
+ accounts: list[str] | None = None,
256
255
  ) -> BenchmarkComparison:
257
256
  """Compare portfolio to benchmark index.
258
257
 
@@ -282,7 +281,7 @@ class AnalyticsEngine:
282
281
  user_id: str,
283
282
  *,
284
283
  years: int = 30,
285
- assumptions: Optional[dict] = None,
284
+ assumptions: dict | None = None,
286
285
  ) -> GrowthProjection:
287
286
  """Project net worth growth with scenarios.
288
287
 
@@ -35,7 +35,6 @@ Examples:
35
35
  """
36
36
 
37
37
  from datetime import datetime
38
- from typing import Optional
39
38
 
40
39
  from fin_infra.analytics.models import (
41
40
  AssetAllocation,
@@ -47,7 +46,7 @@ from fin_infra.analytics.models import (
47
46
  async def calculate_portfolio_metrics(
48
47
  user_id: str,
49
48
  *,
50
- accounts: Optional[list[str]] = None,
49
+ accounts: list[str] | None = None,
51
50
  brokerage_provider=None,
52
51
  market_provider=None,
53
52
  ) -> PortfolioMetrics:
@@ -131,7 +130,7 @@ async def compare_to_benchmark(
131
130
  *,
132
131
  benchmark: str = "SPY",
133
132
  period: str = "1y",
134
- accounts: Optional[list[str]] = None,
133
+ accounts: list[str] | None = None,
135
134
  brokerage_provider=None,
136
135
  market_provider=None,
137
136
  ) -> BenchmarkComparison:
@@ -221,7 +220,7 @@ async def compare_to_benchmark(
221
220
 
222
221
  def _generate_mock_holdings(
223
222
  user_id: str,
224
- accounts: Optional[list[str]] = None,
223
+ accounts: list[str] | None = None,
225
224
  ) -> list[dict]:
226
225
  """Generate mock portfolio holdings for testing.
227
226
 
@@ -415,7 +414,7 @@ def _parse_benchmark_period(period: str) -> int:
415
414
  def _calculate_portfolio_return(
416
415
  user_id: str,
417
416
  period_days: int,
418
- accounts: Optional[list[str]] = None,
417
+ accounts: list[str] | None = None,
419
418
  ) -> tuple[float, float]:
420
419
  """Calculate portfolio return for specified period.
421
420
 
@@ -483,7 +482,7 @@ def _calculate_beta(
483
482
  user_id: str,
484
483
  benchmark: str,
485
484
  period_days: int,
486
- ) -> Optional[float]:
485
+ ) -> float | None:
487
486
  """Calculate portfolio beta (volatility relative to benchmark).
488
487
 
489
488
  Beta = Covariance(portfolio_returns, benchmark_returns) / Variance(benchmark_returns)
@@ -713,6 +712,7 @@ def _calculate_allocation_from_holdings(
713
712
  - other → Other
714
713
  """
715
714
  from collections import defaultdict
715
+
716
716
  from .models import AssetAllocation
717
717
 
718
718
  if total_value == 0:
@@ -19,12 +19,10 @@ Typical usage:
19
19
  )
20
20
  """
21
21
 
22
- from typing import Optional
23
22
  import math
24
23
 
25
24
  from fin_infra.analytics.models import GrowthProjection, Scenario
26
25
 
27
-
28
26
  # ============================================================================
29
27
  # Public API
30
28
  # ============================================================================
@@ -34,7 +32,7 @@ async def project_net_worth(
34
32
  user_id: str,
35
33
  *,
36
34
  years: int = 30,
37
- assumptions: Optional[dict] = None,
35
+ assumptions: dict | None = None,
38
36
  net_worth_provider=None,
39
37
  cash_flow_provider=None,
40
38
  ) -> GrowthProjection:
@@ -45,12 +45,12 @@ from __future__ import annotations
45
45
 
46
46
  import os
47
47
  from datetime import date
48
- from typing import TYPE_CHECKING, Optional, cast
48
+ from typing import TYPE_CHECKING, cast
49
49
 
50
50
  from pydantic import BaseModel, Field
51
51
 
52
- from ..providers.registry import resolve
53
52
  from ..providers.base import BankingProvider
53
+ from ..providers.registry import resolve
54
54
 
55
55
  if TYPE_CHECKING:
56
56
  from fastapi import FastAPI
@@ -99,7 +99,7 @@ class ExchangeTokenResponse(BaseModel):
99
99
  """Response model for token exchange."""
100
100
 
101
101
  access_token: str
102
- item_id: Optional[str] = None
102
+ item_id: str | None = None
103
103
 
104
104
 
105
105
  class BalanceHistoryStats(BaseModel):
@@ -198,11 +198,11 @@ def easy_banking(provider: str = "teller", **config) -> BankingProvider:
198
198
  }
199
199
 
200
200
  # Use provider registry to dynamically load and configure provider
201
- return cast(BankingProvider, resolve("banking", provider, **config))
201
+ return cast("BankingProvider", resolve("banking", provider, **config))
202
202
 
203
203
 
204
204
  def add_banking(
205
- app: "FastAPI",
205
+ app: FastAPI,
206
206
  *,
207
207
  provider: str | BankingProvider | None = None,
208
208
  prefix: str = "/banking",
@@ -349,25 +349,25 @@ def add_banking(
349
349
  @router.get("/transactions")
350
350
  async def get_transactions(
351
351
  access_token: str = Depends(get_access_token),
352
- start_date: Optional[date] = Query(None, description="Filter by start date (ISO format)"),
353
- end_date: Optional[date] = Query(None, description="Filter by end date (ISO format)"),
354
- merchant: Optional[str] = Query(
352
+ start_date: date | None = Query(None, description="Filter by start date (ISO format)"),
353
+ end_date: date | None = Query(None, description="Filter by end date (ISO format)"),
354
+ merchant: str | None = Query(
355
355
  None, description="Filter by merchant name (partial match, case-insensitive)"
356
356
  ),
357
- category: Optional[str] = Query(
357
+ category: str | None = Query(
358
358
  None, description="Filter by category (comma-separated list for multiple)"
359
359
  ),
360
- min_amount: Optional[float] = Query(
360
+ min_amount: float | None = Query(
361
361
  None, description="Minimum transaction amount (inclusive)"
362
362
  ),
363
- max_amount: Optional[float] = Query(
363
+ max_amount: float | None = Query(
364
364
  None, description="Maximum transaction amount (inclusive)"
365
365
  ),
366
- tags: Optional[str] = Query(None, description="Filter by tags (comma-separated list)"),
367
- account_id: Optional[str] = Query(None, description="Filter by specific account ID"),
368
- is_recurring: Optional[bool] = Query(None, description="Filter by recurring status"),
369
- sort_by: Optional[str] = Query("date", description="Sort field: date, amount, or merchant"),
370
- order: Optional[str] = Query("desc", description="Sort order: asc or desc"),
366
+ tags: str | None = Query(None, description="Filter by tags (comma-separated list)"),
367
+ account_id: str | None = Query(None, description="Filter by specific account ID"),
368
+ is_recurring: bool | None = Query(None, description="Filter by recurring status"),
369
+ sort_by: str | None = Query("date", description="Sort field: date, amount, or merchant"),
370
+ order: str | None = Query("desc", description="Sort order: asc or desc"),
371
371
  page: int = Query(1, ge=1, description="Page number (starts at 1)"),
372
372
  per_page: int = Query(50, ge=1, le=200, description="Items per page (max 200)"),
373
373
  ):
@@ -475,7 +475,7 @@ def add_banking(
475
475
  @router.get("/balances")
476
476
  async def get_balances(
477
477
  access_token: str = Depends(get_access_token),
478
- account_id: Optional[str] = Query(None),
478
+ account_id: str | None = Query(None),
479
479
  ):
480
480
  """Get current balances."""
481
481
  balances = banking.balances(
@@ -592,17 +592,17 @@ def add_banking(
592
592
 
593
593
  # Import utilities at end to avoid circular imports
594
594
  from .utils import ( # noqa: E402
595
- validate_plaid_token,
596
- validate_teller_token,
597
- validate_mx_token,
598
- validate_provider_token,
595
+ BankingConnectionInfo,
596
+ BankingConnectionStatus,
597
+ get_primary_access_token,
598
+ mark_connection_healthy,
599
+ mark_connection_unhealthy,
599
600
  parse_banking_providers,
600
601
  sanitize_connection_status,
601
- mark_connection_unhealthy,
602
- mark_connection_healthy,
603
- get_primary_access_token,
604
- test_connection_health,
605
602
  should_refresh_token,
606
- BankingConnectionInfo,
607
- BankingConnectionStatus,
603
+ test_connection_health,
604
+ validate_mx_token,
605
+ validate_plaid_token,
606
+ validate_provider_token,
607
+ validate_teller_token,
608
608
  )
@@ -42,9 +42,8 @@ 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
46
- from pydantic import BaseModel, Field, ConfigDict
47
45
 
46
+ from pydantic import BaseModel, ConfigDict, Field
48
47
 
49
48
  __all__ = [
50
49
  "BalanceSnapshot",
@@ -59,7 +58,7 @@ _logger = logging.getLogger(__name__)
59
58
 
60
59
  # In-memory storage for testing (will be replaced with SQL database in production)
61
60
  # ⚠️ WARNING: All data is LOST on restart when using in-memory storage!
62
- _balance_snapshots: List[BalanceSnapshot] = []
61
+ _balance_snapshots: list[BalanceSnapshot] = []
63
62
  _production_warning_logged = False
64
63
 
65
64
 
@@ -155,9 +154,9 @@ def record_balance_snapshot(
155
154
  def get_balance_history(
156
155
  account_id: str,
157
156
  days: int = 90,
158
- start_date: Optional[date] = None,
159
- end_date: Optional[date] = None,
160
- ) -> List[BalanceSnapshot]:
157
+ start_date: date | None = None,
158
+ end_date: date | None = None,
159
+ ) -> list[BalanceSnapshot]:
161
160
  """Get balance history for an account.
162
161
 
163
162
  Retrieves balance snapshots for the specified account within a date range.
@@ -216,8 +215,8 @@ def get_balance_history(
216
215
 
217
216
  def get_balance_snapshots(
218
217
  account_id: str,
219
- dates: List[date],
220
- ) -> List[BalanceSnapshot]:
218
+ dates: list[date],
219
+ ) -> list[BalanceSnapshot]:
221
220
  """Get balance snapshots for specific dates.
222
221
 
223
222
  Args:
@@ -248,7 +247,7 @@ def get_balance_snapshots(
248
247
 
249
248
  def delete_balance_history(
250
249
  account_id: str,
251
- before_date: Optional[date] = None,
250
+ before_date: date | None = None,
252
251
  ) -> int:
253
252
  """Delete balance history for an account.
254
253