fin-infra 0.2.1__tar.gz → 0.2.2__tar.gz

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