fin-infra 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl

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 (34) hide show
  1. fin_infra/analytics/add.py +3 -3
  2. fin_infra/analytics/portfolio.py +12 -12
  3. fin_infra/brokerage/__init__.py +2 -2
  4. fin_infra/categorization/__init__.py +1 -1
  5. fin_infra/categorization/engine.py +1 -1
  6. fin_infra/categorization/llm_layer.py +4 -4
  7. fin_infra/credit/experian/parser.py +5 -5
  8. fin_infra/crypto/__init__.py +2 -2
  9. fin_infra/goals/models.py +2 -2
  10. fin_infra/investments/__init__.py +2 -2
  11. fin_infra/investments/providers/base.py +4 -4
  12. fin_infra/markets/__init__.py +2 -2
  13. fin_infra/net_worth/__init__.py +1 -1
  14. fin_infra/net_worth/aggregator.py +1 -1
  15. fin_infra/net_worth/calculator.py +1 -1
  16. fin_infra/net_worth/insights.py +3 -3
  17. fin_infra/normalization/__init__.py +2 -2
  18. fin_infra/normalization/currency_converter.py +3 -3
  19. fin_infra/normalization/providers/static_mappings.py +1 -1
  20. fin_infra/obs/classifier.py +2 -2
  21. fin_infra/recurring/add.py +1 -1
  22. fin_infra/recurring/detectors_llm.py +5 -5
  23. fin_infra/recurring/ease.py +4 -4
  24. fin_infra/recurring/insights.py +14 -14
  25. fin_infra/recurring/normalizer.py +6 -6
  26. fin_infra/recurring/normalizers.py +26 -26
  27. fin_infra/scaffold/goals.py +4 -4
  28. fin_infra/security/pii_filter.py +10 -10
  29. fin_infra/tax/tlh.py +5 -5
  30. {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/METADATA +13 -12
  31. {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/RECORD +34 -34
  32. {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/LICENSE +0 -0
  33. {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/WHEEL +0 -0
  34. {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/entry_points.txt +0 -0
@@ -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.
fin_infra/goals/models.py CHANGED
@@ -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
@@ -8,7 +8,7 @@ Handles cryptic merchant names that fail pattern-based normalization:
8
8
  - Legal entities: Inc, LLC, Corp, Ltd
9
9
 
10
10
  Uses ai-infra LLM with few-shot prompting for 90-95% accuracy.
11
- Caches results for 7 days (95% hit rate expected) <1ms latency.
11
+ Caches results for 7 days (95% hit rate expected) -> <1ms latency.
12
12
  Falls back to RapidFuzz if LLM fails or disabled.
13
13
  """
14
14
 
@@ -93,26 +93,26 @@ Common patterns:
93
93
  - POS systems: TST* (Toast), CLOVER*, SQUARE*
94
94
 
95
95
  Examples:
96
- 1. "NFLX*SUB #12345" Netflix (streaming)
97
- 2. "Netflix Inc" Netflix (streaming)
98
- 3. "NETFLIX.COM" Netflix (streaming)
99
- 4. "SQ *COZY CAFE" Cozy Cafe (coffee_shop, Square processor)
100
- 5. "TST* STARBUCKS" Starbucks (coffee_shop, Toast POS)
101
- 6. "AMZN MKTP US" Amazon (online_shopping)
102
- 7. "SPFY*PREMIUM" Spotify (streaming)
103
- 8. "UBER *TRIP 12345" Uber (rideshare)
104
- 9. "LYFT *RIDE ABC" Lyft (rideshare)
105
- 10. "CLOVER* PIZZA PLACE" Pizza Place (restaurant, Clover POS)
106
- 11. "AAPL* ICLOUD STORAGE" Apple iCloud (cloud_storage)
107
- 12. "MSFT*MICROSOFT 365" Microsoft 365 (software_subscription)
108
- 13. "DISNEY PLUS #123" Disney Plus (streaming)
109
- 14. "PRIME VIDEO" Amazon Prime Video (streaming)
110
- 15. "CITY ELECTRIC #456" City Electric (utility_electric)
111
- 16. "T-MOBILE USA" T-Mobile (phone_service)
112
- 17. "VERIZON WIRELESS" Verizon (phone_service)
113
- 18. "WHOLE FOODS MKT #789" Whole Foods (grocery)
114
- 19. "STARBUCKS #1234" Starbucks (coffee_shop)
115
- 20. "LA FITNESS #567" LA Fitness (gym)
96
+ 1. "NFLX*SUB #12345" -> Netflix (streaming)
97
+ 2. "Netflix Inc" -> Netflix (streaming)
98
+ 3. "NETFLIX.COM" -> Netflix (streaming)
99
+ 4. "SQ *COZY CAFE" -> Cozy Cafe (coffee_shop, Square processor)
100
+ 5. "TST* STARBUCKS" -> Starbucks (coffee_shop, Toast POS)
101
+ 6. "AMZN MKTP US" -> Amazon (online_shopping)
102
+ 7. "SPFY*PREMIUM" -> Spotify (streaming)
103
+ 8. "UBER *TRIP 12345" -> Uber (rideshare)
104
+ 9. "LYFT *RIDE ABC" -> Lyft (rideshare)
105
+ 10. "CLOVER* PIZZA PLACE" -> Pizza Place (restaurant, Clover POS)
106
+ 11. "AAPL* ICLOUD STORAGE" -> Apple iCloud (cloud_storage)
107
+ 12. "MSFT*MICROSOFT 365" -> Microsoft 365 (software_subscription)
108
+ 13. "DISNEY PLUS #123" -> Disney Plus (streaming)
109
+ 14. "PRIME VIDEO" -> Amazon Prime Video (streaming)
110
+ 15. "CITY ELECTRIC #456" -> City Electric (utility_electric)
111
+ 16. "T-MOBILE USA" -> T-Mobile (phone_service)
112
+ 17. "VERIZON WIRELESS" -> Verizon (phone_service)
113
+ 18. "WHOLE FOODS MKT #789" -> Whole Foods (grocery)
114
+ 19. "STARBUCKS #1234" -> Starbucks (coffee_shop)
115
+ 20. "LA FITNESS #567" -> LA Fitness (gym)
116
116
 
117
117
  Output format (JSON):
118
118
  {
@@ -131,8 +131,8 @@ class MerchantNormalizer:
131
131
  LLM-based merchant name normalizer with caching and fallback.
132
132
 
133
133
  Layer 2 of 4-layer hybrid architecture:
134
- 1. Check cache first (95% hit rate, 7-day TTL) <1ms
135
- 2. Call LLM if cache miss 200-400ms
134
+ 1. Check cache first (95% hit rate, 7-day TTL) -> <1ms
135
+ 2. Call LLM if cache miss -> 200-400ms
136
136
  3. Cache result for 7 days
137
137
  4. Return MerchantNormalized
138
138
 
@@ -221,9 +221,9 @@ class MerchantNormalizer:
221
221
  Normalize merchant name using LLM with caching.
222
222
 
223
223
  Flow:
224
- 1. Check cache (95% hit rate) <1ms
224
+ 1. Check cache (95% hit rate) -> <1ms
225
225
  2. Check budget (daily/monthly caps)
226
- 3. Call LLM if cache miss 200-400ms
226
+ 3. Call LLM if cache miss -> 200-400ms
227
227
  4. Validate confidence threshold
228
228
  5. Cache result (7-day TTL)
229
229
  6. Return MerchantNormalized
@@ -386,7 +386,7 @@ class MerchantNormalizer:
386
386
  # Remove special characters
387
387
  normalized = normalized.replace("*", " ").replace("#", " ").replace(".", " ")
388
388
 
389
- # Remove store numbers (e.g., "starbucks 1234" "starbucks")
389
+ # Remove store numbers (e.g., "starbucks 1234" -> "starbucks")
390
390
  import re
391
391
 
392
392
  normalized = re.sub(r"\b\d{3,}\b", "", normalized)
@@ -178,10 +178,10 @@ def scaffold_goals_core(
178
178
  Scaffold goals domain files: models, schemas, repository (optional), and __init__.py.
179
179
 
180
180
  Generates production-ready code from templates in fin_infra.goals.scaffold_templates:
181
- - models.py.tmpl Goal model with progress tracking, status, priority, milestones
182
- - schemas.py.tmpl GoalBase, GoalCreate, GoalUpdate, GoalRead with status validation
183
- - repository.py.tmpl GoalRepository with CRUD + domain methods (get_active, update_progress)
184
- - README.md Complete usage guide with examples
181
+ - models.py.tmpl -> Goal model with progress tracking, status, priority, milestones
182
+ - schemas.py.tmpl -> GoalBase, GoalCreate, GoalUpdate, GoalRead with status validation
183
+ - repository.py.tmpl -> GoalRepository with CRUD + domain methods (get_active, update_progress)
184
+ - README.md -> Complete usage guide with examples
185
185
 
186
186
  Args:
187
187
  dest_dir: Destination directory (will be created if missing)
@@ -116,8 +116,8 @@ class FinancialPIIFilter(logging.Filter):
116
116
  Mask Social Security Numbers.
117
117
 
118
118
  Examples:
119
- 123-45-6789 ***-**-6789
120
- 123456789 *****6789 (with context)
119
+ 123-45-6789 -> ***-**-6789
120
+ 123456789 -> *****6789 (with context)
121
121
  """
122
122
  # With dashes (high confidence)
123
123
  text = SSN_PATTERN.sub(lambda m: f"***-**-{m.group()[-4:]}", text)
@@ -145,7 +145,7 @@ class FinancialPIIFilter(logging.Filter):
145
145
  Mask Employer Identification Numbers.
146
146
 
147
147
  Example:
148
- 12-3456789 **-****789
148
+ 12-3456789 -> **-****789
149
149
  """
150
150
  return EIN_PATTERN.sub(lambda m: f"**-****{m.group()[-3:]}", text)
151
151
 
@@ -154,8 +154,8 @@ class FinancialPIIFilter(logging.Filter):
154
154
  Mask credit card numbers using Luhn validation.
155
155
 
156
156
  Examples:
157
- 4111 1111 1111 1111 **** **** **** 1111
158
- 4111111111111111 ************1111
157
+ 4111 1111 1111 1111 -> **** **** **** 1111
158
+ 4111111111111111 -> ************1111
159
159
  """
160
160
 
161
161
  def mask_card_match(match):
@@ -182,7 +182,7 @@ class FinancialPIIFilter(logging.Filter):
182
182
  Mask ABA routing numbers with checksum validation.
183
183
 
184
184
  Example:
185
- 021000021 ******021
185
+ 021000021 -> ******021
186
186
  """
187
187
 
188
188
  def mask_routing_match(match):
@@ -207,7 +207,7 @@ class FinancialPIIFilter(logging.Filter):
207
207
  Mask bank account numbers.
208
208
 
209
209
  Example:
210
- 1234567890 ******7890
210
+ 1234567890 -> ******7890
211
211
  """
212
212
 
213
213
  def mask_account_match(match):
@@ -231,7 +231,7 @@ class FinancialPIIFilter(logging.Filter):
231
231
  Mask CVV codes (context-dependent).
232
232
 
233
233
  Example:
234
- CVV: 123 CVV: ***
234
+ CVV: 123 -> CVV: ***
235
235
  """
236
236
 
237
237
  def mask_cvv_match(match):
@@ -255,7 +255,7 @@ class FinancialPIIFilter(logging.Filter):
255
255
  Mask email addresses.
256
256
 
257
257
  Example:
258
- user@example.com u***@example.com
258
+ user@example.com -> u***@example.com
259
259
  """
260
260
 
261
261
  def mask_email_match(match):
@@ -272,7 +272,7 @@ class FinancialPIIFilter(logging.Filter):
272
272
  Mask phone numbers.
273
273
 
274
274
  Example:
275
- (555) 123-4567 (***) ***-4567
275
+ (555) 123-4567 -> (***) ***-4567
276
276
  """
277
277
 
278
278
  def mask_phone_match(match):
fin_infra/tax/tlh.py CHANGED
@@ -456,7 +456,7 @@ def _suggest_replacement(symbol: str, asset_class: str) -> str:
456
456
  # Simple rule-based suggestions (v1)
457
457
  # TODO: Replace with ai-infra LLM for intelligent suggestions
458
458
  replacements = {
459
- # Tech stocks sector ETFs
459
+ # Tech stocks -> sector ETFs
460
460
  "AAPL": "VGT", # Tech ETF
461
461
  "MSFT": "VGT",
462
462
  "GOOGL": "VGT",
@@ -464,19 +464,19 @@ def _suggest_replacement(symbol: str, asset_class: str) -> str:
464
464
  "META": "VGT",
465
465
  "NVDA": "SOXX", # Semiconductor ETF
466
466
  "AMD": "SOXX",
467
- # Auto/EV sector alternatives
467
+ # Auto/EV -> sector alternatives
468
468
  "TSLA": "ARKK", # Innovation ETF
469
469
  "F": "XLI", # Industrials ETF
470
470
  "GM": "XLI",
471
- # Finance sector ETF
471
+ # Finance -> sector ETF
472
472
  "JPM": "XLF", # Financials ETF
473
473
  "BAC": "XLF",
474
474
  "GS": "XLF",
475
- # Healthcare sector ETF
475
+ # Healthcare -> sector ETF
476
476
  "JNJ": "XLV", # Healthcare ETF
477
477
  "PFE": "XLV",
478
478
  "MRNA": "XBI", # Biotech ETF
479
- # Crypto broad exposure
479
+ # Crypto -> broad exposure
480
480
  "BTC": "ETH", # Ethereum (different asset)
481
481
  "ETH": "BTC", # Bitcoin (different asset)
482
482
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fin-infra
3
- Version: 0.2.1
3
+ Version: 0.2.3
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,11 +1,11 @@
1
1
  fin_infra/__init__.py,sha256=7oL-CCsALNifBODAn9LriicaIrzgJkmVPvE-9duP0mw,1574
2
2
  fin_infra/__main__.py,sha256=1qNP7j0ffw0wFs1dBwDcJ9TNXlC6FcYuulzoV87pMi8,262
3
3
  fin_infra/analytics/__init__.py,sha256=-OsQUqLgOdafSNzbcIHy3G37nX2OwD0OzGj8C2Aiufs,2325
4
- fin_infra/analytics/add.py,sha256=-iAqKdvf9t0gQjjo3uOO56qA_ei4omKYDOynxb4SZPI,12654
4
+ fin_infra/analytics/add.py,sha256=OdITXA5CjijhbBzokV3xlTcJCTiMLBDuUJ8OH9Pwkhw,12651
5
5
  fin_infra/analytics/cash_flow.py,sha256=VSsQHwTi6r6VFoScH9nu9Fj8XHC4O4B9TkAI8Alobm4,10284
6
6
  fin_infra/analytics/ease.py,sha256=aTAPc-Lmh6XP7REPVlAom38MtKCqeS-auNW-rpXRZJs,14282
7
7
  fin_infra/analytics/models.py,sha256=XFHeVZoPE0PcSFj59dNsdxISFVwdrTLU4sDCtExumrg,8193
8
- fin_infra/analytics/portfolio.py,sha256=d39sZGa3TQVX1xN2nplPHuxhsE-rahuiyzd612DeZ_Y,26417
8
+ fin_infra/analytics/portfolio.py,sha256=3xP34nQXvLdj_6Bpz5sufRoND5hpGDnmespWAixgh80,26405
9
9
  fin_infra/analytics/projections.py,sha256=T7LLG0Pe5f-WwgfDITdTMk-l8cCLZTM9cEC_Vxf6mkc,9154
10
10
  fin_infra/analytics/rebalancing.py,sha256=VM8MgoJofmrCXPK1rbmVqWGB4FauNmHCL5HOEbrZR2g,14610
11
11
  fin_infra/analytics/savings.py,sha256=n3rGNFP8TU5mW-uz9kOuqX_mDiVnDyAeDN06Q7Abotw,7570
@@ -14,7 +14,7 @@ fin_infra/analytics/spending.py,sha256=ogLcfF5ZOLMkBIj02RISnA3hiY_PsLWZ2_AAzA7Fe
14
14
  fin_infra/banking/__init__.py,sha256=6wwGNITzyehC9MBQc5jy0ewSTuNetyQu2AdND51O55w,22450
15
15
  fin_infra/banking/history.py,sha256=YB-v9A03IZ_qki6A6mA-RO5Y4imlqk1CyP0W482ufdQ,10563
16
16
  fin_infra/banking/utils.py,sha256=HhxZbeaA8zqVttgMiJGnShTo_r_0DaD7T3IMq8n8340,15252
17
- fin_infra/brokerage/__init__.py,sha256=Z5w1vWqETaajWNFPJ3DbHscVpqjlkKtDAJ6O8JgS7Tg,17139
17
+ fin_infra/brokerage/__init__.py,sha256=IXm5ko5T607LodexYSbKNZc6I_2CQGaOwQUlb-w9-ks,17137
18
18
  fin_infra/budgets/__init__.py,sha256=GgfujfgdVld6_yUf8bhkW-bsIXBLb4THlJMocVpA4mM,3986
19
19
  fin_infra/budgets/add.py,sha256=i7Av--Z14sYQDJHETiF9VL71ouWBZcs3H3VZ62gqFEo,13508
20
20
  fin_infra/budgets/alerts.py,sha256=XDSUPpRR1LNgSQU0Szlht0bLWI7RRBh1ElbcwGexbPs,9701
@@ -29,11 +29,11 @@ fin_infra/budgets/templates.py,sha256=V1Hfh0P_6iMdaSLtnFm9iEvXDUyFb7yqBObat98hDj
29
29
  fin_infra/budgets/tracker.py,sha256=RDPfYhOLSAp-RFlTWFTpYaB5mysu3QfQhhcqJzmVWUU,16444
30
30
  fin_infra/cashflows/__init__.py,sha256=N9ZGKwIrQs-eiZZxqCow2aLBhW1MKcJoasQjGtyV6l4,8517
31
31
  fin_infra/cashflows/core.py,sha256=YWvF0DVOfBkvO_MuDODjE-V_g52H2ixFRH_TjMXExDE,541
32
- fin_infra/categorization/__init__.py,sha256=efLje12AW-ec9Vs5ynb41r4XCIWx5a-Z9WoGb3kQdIE,2030
32
+ fin_infra/categorization/__init__.py,sha256=p5uEFX0piPI5QmYtQ4lTrr0Cph5r01XpHDOi4RwwVv4,2027
33
33
  fin_infra/categorization/add.py,sha256=-I-V7R8vWj2-B8yjtFn_pNryQV5uascFR18a1Ltcqm0,6247
34
34
  fin_infra/categorization/ease.py,sha256=gvlVb_TL7kwm5oM_gWdrhZt4cJOSvYEgGipNJKK8CpM,5820
35
- fin_infra/categorization/engine.py,sha256=u1ZYDmuB0gZeJHn-CKvvwd7Z1HFiB-S91YuV_ZrK_Rc,12117
36
- fin_infra/categorization/llm_layer.py,sha256=CSe6q-cKQgnKUKSItdDQwzMd8K1dsp-Mwua7SWZ9Ux0,12670
35
+ fin_infra/categorization/engine.py,sha256=tZ-WTkPUUcKe6rDFoCSu5BF_gezcjJfx8Yn9eUX4lDQ,12114
36
+ fin_infra/categorization/llm_layer.py,sha256=hCqTfF3LPy7xs_7wJgBbwG4hYkoY2TtRSs_XWyihHuI,12665
37
37
  fin_infra/categorization/models.py,sha256=A8m3hAIbsUUV3eNjXpTiPWD1sYGjSdXEByTS3ZZASEA,5894
38
38
  fin_infra/categorization/rules.py,sha256=IpDnHBeuykRdu5vs3Lph4Y9-3RseIjjleQ5hZphQvNk,12849
39
39
  fin_infra/categorization/taxonomy.py,sha256=p-tSOwJ0O-rFZ1LIlHSYdaYdSc65084j0fdMI_6LW84,13251
@@ -52,10 +52,10 @@ fin_infra/credit/add.py,sha256=8mYshzS8VGlnI_2b5WxXU5QFBrI-iEPfN1RDNz0apcA,8556
52
52
  fin_infra/credit/experian/__init__.py,sha256=g3IJGvDOMsnB0er0Uwdvl6hGKKTOazqJxSDnB2oIBm0,761
53
53
  fin_infra/credit/experian/auth.py,sha256=1TfT0wnqjAs-RCzDZKF_NH8LjTPDi8uMq7uyNPLJmS4,5612
54
54
  fin_infra/credit/experian/client.py,sha256=jNvfiBM7Qn9w8J_ZqQReDa4ACymyoFVgopQpdznTI-o,8645
55
- fin_infra/credit/experian/parser.py,sha256=7ptdLyTWWqHWqCo1CXn6L7XaIn9ZRRuOaATbFmMZZ64,7489
55
+ fin_infra/credit/experian/parser.py,sha256=KFLQd4_R_tXhQkJPJrDbdD1-GlGdJy6cAToVxKoZHcY,7484
56
56
  fin_infra/credit/experian/provider.py,sha256=a_BLyfc4wLUUqt8rkcbpHJuDB_DjFhS2kTZ-loVLdhM,13680
57
57
  fin_infra/credit/mock.py,sha256=xKWZk3fhuIYRfiZkNc9fbHUNViNKjmOLSj0MTI1f4ik,5356
58
- fin_infra/crypto/__init__.py,sha256=VTi2vjiDrlg0lU9H9tM3X-scRN-Z7QJ9UpdYIOeQcaw,8262
58
+ fin_infra/crypto/__init__.py,sha256=Qv-Herm67r2dFJz2GA7ljemT_1shv2ZeWM3w6osY5HI,8260
59
59
  fin_infra/crypto/insights.py,sha256=cgMsjdrR7vOTKvEDbFCXpS7O7I8dMs1oz6cIbtrJWZI,11393
60
60
  fin_infra/documents/__init__.py,sha256=Ub1hbX3PTrBSsBdcbL8PFf6oq8jSH4pYxW45-qOYPqs,1909
61
61
  fin_infra/documents/add.py,sha256=2RSf_i39-JUT6c_jVsEQwW3FfQSlH4H9Z0t_H0vz86U,8091
@@ -70,7 +70,7 @@ fin_infra/goals/add.py,sha256=zobcqEsEzsmd9itSPyPmzpkzFQYZlETo9d17bU9s3mc,20534
70
70
  fin_infra/goals/funding.py,sha256=Fw6hUvb8yNWineEbRXFYgw4_ZRnGrBSChBjkb7Qozrs,9303
71
71
  fin_infra/goals/management.py,sha256=1Ur76J6-EoxIbLZVnyfw64F4wwTq8Sp5e-YWDk5qVP4,33859
72
72
  fin_infra/goals/milestones.py,sha256=1Sq-ZbW_H9Makw-kz4ekuw5kFGeDBNgnyTy-1Lr9TQo,9972
73
- fin_infra/goals/models.py,sha256=5X0BddvK9FBsXMTXBQn8YFBNLtIbXue9a4VdLH3rWR8,10173
73
+ fin_infra/goals/models.py,sha256=SUBaQ9PaKsvWVmyT4qWdFxHsoJxevleTg9O6Qho03Zk,10171
74
74
  fin_infra/goals/scaffold_templates/README.md,sha256=CoE_3I2K32orOFH6CvfVBaJBTGDYIESd5-48V7vU1FI,9974
75
75
  fin_infra/goals/scaffold_templates/__init__.py,sha256=rLFam-mRsj8LvJu5kRBEIJtw9rFUof7KApgD2IRE56c,107
76
76
  fin_infra/goals/scaffold_templates/models.py.tmpl,sha256=b23Nlwm05MFMQE4qkrylTPXqulsN6cuFzNev2liY7DI,5714
@@ -79,12 +79,12 @@ fin_infra/goals/scaffold_templates/schemas.py.tmpl,sha256=M1hS1pK9UDXcNqPW-NGu98
79
79
  fin_infra/insights/__init__.py,sha256=fFYymoAY2zd7eooE-RqyFifXB_J-vVkHjyMak7o4wnQ,3984
80
80
  fin_infra/insights/aggregator.py,sha256=HtaJipSA-O_HVComBcyQdkGs6guoW81sYwRYHXGdBJI,10251
81
81
  fin_infra/insights/models.py,sha256=xov_YV8oBLJt3YdyVjbryRfcXqmGeGiPvZsZHSbvtl8,3202
82
- fin_infra/investments/__init__.py,sha256=DQJa32LdXspbtxydric-AfT3o5iDkqIByAsSriSfGak,6729
82
+ fin_infra/investments/__init__.py,sha256=hJDHlKNXG0Hr3zsJeXdTEyvxL9PkVR8NkS2ULKnELWY,6727
83
83
  fin_infra/investments/add.py,sha256=UV2_99z1p8cUiifVFJXOF0lGd0ucMgZ5s9N7IFyE_NY,17193
84
84
  fin_infra/investments/ease.py,sha256=s716nYv5kzkJ-vGbVeIumqAN0-TEl1ai-If84uP9h68,9474
85
85
  fin_infra/investments/models.py,sha256=Es1GdrB640k50gtdzTtfh_66nxvYxORq0xbtXryoRGo,18319
86
86
  fin_infra/investments/providers/__init__.py,sha256=V1eIzz6EnGJ-pq-9L3S2-evmcExF-YdZfd5P6JMyDtc,383
87
- fin_infra/investments/providers/base.py,sha256=1plM-o_fV6jQyyPnpZT5q8ir96oyxvh0zwA3qpiVIFY,9810
87
+ fin_infra/investments/providers/base.py,sha256=T9XC47hQ55rL0RG9r00PZsKS68wlXJacXudwPVf19Qs,9806
88
88
  fin_infra/investments/providers/plaid.py,sha256=X42cMpsHNcOPcYvl73jrP8x7nQ399n2_it3Ko9UIkI0,18751
89
89
  fin_infra/investments/providers/snaptrade.py,sha256=Hs5uM3F31mGzSONgVXLlL2BR86mjOWs8OhcpLiUeaAo,23251
90
90
  fin_infra/investments/scaffold_templates/README.md,sha256=q9xB__7xqSK5JTvzsDDHMsFirwWV-7TNn45R0k9F2aE,12081
@@ -92,7 +92,7 @@ fin_infra/investments/scaffold_templates/__init__.py,sha256=iR0oiAzXFYXHBnVJjaEn
92
92
  fin_infra/investments/scaffold_templates/models.py.tmpl,sha256=5inP5-jw-qEfPYxSN71tn4AojZ9PesOIeuHTw181N-c,5849
93
93
  fin_infra/investments/scaffold_templates/repository.py.tmpl,sha256=XwOEpQZfuXut1WLiq-GSSvv0oX0iYCW54eJNL0Cav94,14656
94
94
  fin_infra/investments/scaffold_templates/schemas.py.tmpl,sha256=knWmn-Kyr7AdgPD4ZPMb6T49ZuPXeuOMqmjYNyA0CA0,5451
95
- fin_infra/markets/__init__.py,sha256=Ba9iUTfDkkla8GwPo3gULTJYUDQh5gSgGN15BxHINpo,9865
95
+ fin_infra/markets/__init__.py,sha256=pVPtfOZxIeHsPuCDOCydmJjdsOc44R9B2d5EbCZNhRU,9863
96
96
  fin_infra/models/__init__.py,sha256=y94RJ_1-bzgNUCxqE76X56WIOk3-El_Jueqy7uB0rb8,860
97
97
  fin_infra/models/accounts.py,sha256=m_HdYHOe_m0GLnc_f5njo9n-zscWu-C0rJB6SAd5-aY,1098
98
98
  fin_infra/models/brokerage.py,sha256=TV5KMe78e-ttjcUbZIfdGo3x0NisAQ3puwv_ehtgSHc,8312
@@ -102,28 +102,28 @@ fin_infra/models/money.py,sha256=63pdGD1WBMHicJ1w7pbU1g5fqt4gIzPuqQQ2-NSlBuc,401
102
102
  fin_infra/models/quotes.py,sha256=-YBzgnjjCihRAprUdaPRtfwKrgliDIGgqAnaM69VbDU,521
103
103
  fin_infra/models/tax.py,sha256=as40J9h24BB7LmeaIfay509UaYEctmk5CPW9CfcjWZc,15657
104
104
  fin_infra/models/transactions.py,sha256=gzc6hLKKU6VUWGshqw2WChU_x0qyXhdvOGwmj9H_H3I,790
105
- fin_infra/net_worth/__init__.py,sha256=mTus1Y_JCwWuWMstbkQVlxIJqZa2SWRn3ERUTFVH9xg,3445
105
+ fin_infra/net_worth/__init__.py,sha256=hAxX5nM9jCf-rA0WF42Fkt6_dZLjcAZZH5KBkMhr9Ak,3444
106
106
  fin_infra/net_worth/add.py,sha256=QWfHIHJs2CV99WRBqjQ2OteiOrn5cR9nurmxTF9v5rg,23191
107
- fin_infra/net_worth/aggregator.py,sha256=9Kx2vUR71QwqYZdGaCfmYrJ1hNxzd1EEuAdWJoNjqTI,12780
108
- fin_infra/net_worth/calculator.py,sha256=SQJGJDok5HgvoAhKBxeeqt8vhGMchABU3zPmNRpqNy4,13139
107
+ fin_infra/net_worth/aggregator.py,sha256=Zlb0-Z7xdHMzjvmxc90KeYwwr8zNkybX2ra9oAFi6UM,12779
108
+ fin_infra/net_worth/calculator.py,sha256=l13BWEtJNCWau0M7J97ek8WVQN3_9NVXnJkmit0CcLU,13138
109
109
  fin_infra/net_worth/ease.py,sha256=ERdFrUjjb5l5BRp_c2tEfE1obTpRc_-FA9LnV7BTiEw,15883
110
110
  fin_infra/net_worth/goals.py,sha256=BJGxdsMjvgQDELFEJo-ai3DvsAzUNXvzMXkwovHr8yQ,1238
111
- fin_infra/net_worth/insights.py,sha256=MdZHrwk4T0WNNIo97DroNelQBDoBr9z70VS05yBXqb8,25209
111
+ fin_infra/net_worth/insights.py,sha256=RlCp247kFZ73xBlU-1LYTXs0R6sg4FvL0SidLwFz-6M,25206
112
112
  fin_infra/net_worth/models.py,sha256=sZv3dGw5zwckE2XQ7n5ehK7sv4jdGCo9c2g4ZtZHwCI,26880
113
113
  fin_infra/net_worth/scaffold_templates/README.md,sha256=Wqd6ksqFjmtNdDFOWVV_duuAcePWwiu3_YgkVM9N_WY,14363
114
114
  fin_infra/net_worth/scaffold_templates/__init__.py,sha256=OKeMCC_JNw6m8rBWr_wesOIJ1OR9LCBeIkXKahbCGC4,132
115
115
  fin_infra/net_worth/scaffold_templates/models.py.tmpl,sha256=9BKsoD08RZbSdOm0wFTbx5OzKfAEtuA1NcWyS1Aywx4,5934
116
116
  fin_infra/net_worth/scaffold_templates/repository.py.tmpl,sha256=DSErnNxeAe4pWeefARRK3bU0hHltqdIFffENfVwdd7c,12798
117
117
  fin_infra/net_worth/scaffold_templates/schemas.py.tmpl,sha256=VkFsxyZx4DFDhXDhn-7KT0IgrXCvgaS5ZdWbjyezWj0,4709
118
- fin_infra/normalization/__init__.py,sha256=O-8YvPbKWgfdC6E2qzXuCFLJ6cM-CS8GmBO8TDcJ9X0,6246
119
- fin_infra/normalization/currency_converter.py,sha256=dhiSAYD7onQgCiT17TtGzL4czeX_qJ9_yW2zsvvBErg,7018
118
+ fin_infra/normalization/__init__.py,sha256=7PWJn_T-8O464cvM4BeA5krOS2YaJXhCmEV9ZtQqbB0,6244
119
+ fin_infra/normalization/currency_converter.py,sha256=YIi7EgeE_Oe2qiXaCLnmgj9AKHvKBO0eLm_7bkHMFJM,7015
120
120
  fin_infra/normalization/models.py,sha256=h8zC2n642WwVWC1yvx_VCILDOgUwPDrZwjb0QIbA8cE,1873
121
121
  fin_infra/normalization/providers/__init__.py,sha256=LFU1tB2hVO42Yrkw-IDpPexD4mIlxob9lRrJEeGYqpE,559
122
122
  fin_infra/normalization/providers/exchangerate.py,sha256=I_2TK_LLOcYCy3HcN9Nut3UwqTWEOX26GktmX3cczvY,6340
123
- fin_infra/normalization/providers/static_mappings.py,sha256=m14VHmTZipbqrgyE0ABToabVx-pDcyB577LNWrACEUM,6809
123
+ fin_infra/normalization/providers/static_mappings.py,sha256=X8eSXLU_9qwXLcSL84nNR81c_H_95-saJhwnp0h_sgE,6808
124
124
  fin_infra/normalization/symbol_resolver.py,sha256=UAfhxmiINpQhE8tPLh-GMCfDZGmK5wf2DwtYZP3sSUo,8093
125
125
  fin_infra/obs/__init__.py,sha256=kMMVl0fdwtJtZeKiusTuw0iO61Jo9-HNXsLmn3ffLRE,631
126
- fin_infra/obs/classifier.py,sha256=JasWCqSkYjllJNZ5Gwbrd53ZhLwhYNZ0i2nbTcklEog,5155
126
+ fin_infra/obs/classifier.py,sha256=S7kSphgHN1O4GiMUdr3IjuXpoXU0XgGq132_U-njXX4,5153
127
127
  fin_infra/providers/__init__.py,sha256=jxhQm79T6DVXf7Wpy7luL-p50cE_IMUbjt4o3apzJQU,768
128
128
  fin_infra/providers/banking/base.py,sha256=KeNU4ur3zLKHVsBF1LQifcs2AKX06IEE-Rx_SetFeAs,102
129
129
  fin_infra/providers/banking/plaid_client.py,sha256=8Nvd9Ow_v6Scnw79R86uSvRcBRHPgc3ytsQze50E7aM,6524
@@ -145,37 +145,37 @@ fin_infra/providers/tax/mock.py,sha256=-6VD9F3usfoxU-KqAj8jC3Q1xsnJ4o5ZT1W5Ze7X6
145
145
  fin_infra/providers/tax/taxbit.py,sha256=Lp3eH64oUrqwVT5P8oPxa01LFAmhhlfbquDFiiiDcz8,4271
146
146
  fin_infra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
147
147
  fin_infra/recurring/__init__.py,sha256=lYeMdpgxq52x7siFp62_xVqzUx84vMNKrTuiaT2JTi4,2308
148
- fin_infra/recurring/add.py,sha256=Kc5Wv-4XshS8ET1wuydDJfRz6fQAkbZ2uQ1qFAJ0X5w,19055
148
+ fin_infra/recurring/add.py,sha256=l_VsqJNAs-s5VZ7WFA3wfXJtjib2eqBhMJofTYgDR2w,19053
149
149
  fin_infra/recurring/detector.py,sha256=MCq_48V6o9Ig9lTTHM054KNh2I_udEjWBmV_iF361ac,20131
150
- fin_infra/recurring/detectors_llm.py,sha256=MB9hGUTo8WEa-lpevOkrp0OQOXnatskzW02l_EfwTPo,11486
151
- fin_infra/recurring/ease.py,sha256=8t0lXMqNxHZkO0fw29sEn_lQjitDq0UxebtqA74tag0,11157
152
- fin_infra/recurring/insights.py,sha256=-SgskqHqOlkDwCfj4NsPMwlq1bHY2J5gM-1c_t0bSv0,15853
150
+ fin_infra/recurring/detectors_llm.py,sha256=2EMBQTIHvmxJR6gqvhlg6VNj2-eDTJiJULMxTCZMnJw,11481
151
+ fin_infra/recurring/ease.py,sha256=jaBFMul1LxjY2TAkSsmyaK6Kkflj8EfxwWPbyRGA8mw,11151
152
+ fin_infra/recurring/insights.py,sha256=zHHcD7jEYJF39TI9-5LSgjh-zHhXjxuKfLyKI6SIA8Y,15839
153
153
  fin_infra/recurring/models.py,sha256=o0N8G-QhVb4zILEyry6M1VZ7liFJIOHwlejvn6p4K8M,8894
154
- fin_infra/recurring/normalizer.py,sha256=mihqV84Dd5AJ9JYU6KW3W8nH3TCU8FiKpPicipuClPM,9760
155
- fin_infra/recurring/normalizers.py,sha256=4DGy0RGHz5NameRpZC-QYbS2PkZc9UqjtjTuVQ_OJHA,15914
154
+ fin_infra/recurring/normalizer.py,sha256=sU4qCJ7_wVDugY0R0Td63Ol8asJNpMOHAPX92J9XJhY,9754
155
+ fin_infra/recurring/normalizers.py,sha256=oGxCTDGUixORwXiC424wN5QYxTPdJu2rSrSG84QkVlA,15888
156
156
  fin_infra/recurring/summary.py,sha256=wQshznaswIbGUPIMyayubRcRfUvVNBtyzmYx50-nqs0,14621
157
157
  fin_infra/scaffold/__init__.py,sha256=IfL_CHHMpQB1efqY37BlIu07356tLaeVI2Mv3C0qYDs,827
158
158
  fin_infra/scaffold/budgets.py,sha256=qLnyPh-a-ZrslstaxRtR__Bpwlz0EncSdVnCM7fDBI4,9481
159
- fin_infra/scaffold/goals.py,sha256=C1mexWRbWV3aRdmiT65LpKMMWzhTWTX_upeCcsjTcWw,9684
159
+ fin_infra/scaffold/goals.py,sha256=0iCUJI242sExlVttd1xb2HZCuAgzhKI_URrYnSfxrRg,9680
160
160
  fin_infra/security/__init__.py,sha256=wM341EZioKtfTawpFCciaFh0FlpHHkoEoviW9pGYqpI,1363
161
161
  fin_infra/security/add.py,sha256=SIMBlSQ4FWCNiBfzgbzeVpRoo2JS3dqW-51uvq3hQU0,2715
162
162
  fin_infra/security/audit.py,sha256=nb4H2Jq7XwalHLOx6LqleUtSXkBRz6u65LJ7lyuQNDo,3276
163
163
  fin_infra/security/encryption.py,sha256=Cr_9xhBiUvYx1tDjCiLTzyY9NRTlqljWGgswqXT499Y,6139
164
164
  fin_infra/security/models.py,sha256=s8dsvpxP-7DThUZl3Fvr3S6KVAfL5r3TKcd4uj_hCTE,1689
165
- fin_infra/security/pii_filter.py,sha256=nVqzbG_-Z6m8fzzS1KBy9yFwaZ8eRrU00Q72fSkVyU8,8307
165
+ fin_infra/security/pii_filter.py,sha256=DF95mSMkk8CTGAEYtEnR-CCoOIUwE7D-9voQ-Koa3j4,8297
166
166
  fin_infra/security/pii_patterns.py,sha256=SM-o7cL6NdgkOmtBedsN2nJZ5QPbeYehZdYmAujk8Y8,3070
167
167
  fin_infra/security/token_store.py,sha256=FsfoAkIMQ2NRfkBuyG1eH30nnfO3w_V5tDPAZwUj9Os,6041
168
168
  fin_infra/settings.py,sha256=11JgIhjGwWnwixV-hveEWpoWd_JC0ixLnOQoLWCiwNo,1387
169
169
  fin_infra/tax/__init__.py,sha256=U0EUKQwbDqnAYwU8WRx6AD07TaB-ELdKGISOQ-904lw,6103
170
170
  fin_infra/tax/add.py,sha256=d17Zuoi-xMjuJNykDHzQXnAUVzd_41BUMHQdqm23jJ8,14547
171
- fin_infra/tax/tlh.py,sha256=3khtiEkGKIIHi2mbMM0ss-GKQ8vM3FS6hly8R00qseY,21482
171
+ fin_infra/tax/tlh.py,sha256=rUglLeq09XxEbC2W7aJ9G0E8kj9g5QdG4ymSL4z8RK0,21477
172
172
  fin_infra/utils/__init__.py,sha256=x_FUlv7FONdTPHwXTbPWY-EEOBwWnm-y8TPJ1W1PrXM,944
173
173
  fin_infra/utils/deprecation.py,sha256=DTcqv7ECnrWOOwoA07JOnRci4Hqqo9YtKSSmoS-DVPY,5187
174
174
  fin_infra/utils/http.py,sha256=rDEgYsEBrEe75ml5RA-iSs3xeU5W-3j-czJlT7WbrM4,632
175
175
  fin_infra/utils/retry.py,sha256=YiyTgy26eJ1ah7fE2_-ZPa4hv4bIT4OzjYolkNWb5j0,1057
176
176
  fin_infra/version.py,sha256=4t_crzhrLum--oyowUMxtjBTzUtWp7oRTF22ewEvJG4,49
177
- fin_infra-0.2.1.dist-info/LICENSE,sha256=wK-Ya7Ylxa38dSIZRhvNj1ZVLIrHC-BAI8v38PNADiA,1061
178
- fin_infra-0.2.1.dist-info/METADATA,sha256=NdGFOF-FPeuEB_BxH8xAhdKjvjdYycn6akhJ9COg0wo,10806
179
- fin_infra-0.2.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
180
- fin_infra-0.2.1.dist-info/entry_points.txt,sha256=Sr1uikvALZMeKm-DIkeKG4L9c4SNqysXGO_IRF8_9eU,53
181
- fin_infra-0.2.1.dist-info/RECORD,,
177
+ fin_infra-0.2.3.dist-info/LICENSE,sha256=wK-Ya7Ylxa38dSIZRhvNj1ZVLIrHC-BAI8v38PNADiA,1061
178
+ fin_infra-0.2.3.dist-info/METADATA,sha256=w5qNMO31igjhpOU9FHYisQ_gSdjk2W4dZS1UAhUj9Gk,10842
179
+ fin_infra-0.2.3.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
180
+ fin_infra-0.2.3.dist-info/entry_points.txt,sha256=Sr1uikvALZMeKm-DIkeKG4L9c4SNqysXGO_IRF8_9eU,53
181
+ fin_infra-0.2.3.dist-info/RECORD,,