fin-infra 0.1.82__py3-none-any.whl → 0.4.0__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 (65) hide show
  1. fin_infra/analytics/__init__.py +2 -2
  2. fin_infra/analytics/add.py +3 -3
  3. fin_infra/analytics/cash_flow.py +3 -3
  4. fin_infra/analytics/models.py +5 -5
  5. fin_infra/analytics/portfolio.py +12 -12
  6. fin_infra/analytics/spending.py +4 -5
  7. fin_infra/banking/history.py +4 -4
  8. fin_infra/brokerage/__init__.py +7 -7
  9. fin_infra/budgets/alerts.py +2 -2
  10. fin_infra/budgets/ease.py +1 -2
  11. fin_infra/budgets/models.py +1 -2
  12. fin_infra/budgets/templates.py +4 -4
  13. fin_infra/budgets/tracker.py +3 -3
  14. fin_infra/categorization/__init__.py +1 -1
  15. fin_infra/categorization/ease.py +3 -3
  16. fin_infra/categorization/engine.py +1 -1
  17. fin_infra/categorization/llm_layer.py +6 -4
  18. fin_infra/categorization/models.py +3 -4
  19. fin_infra/chat/planning.py +1 -1
  20. fin_infra/cli/cmds/scaffold_cmds.py +6 -6
  21. fin_infra/credit/experian/parser.py +5 -5
  22. fin_infra/crypto/__init__.py +2 -2
  23. fin_infra/documents/models.py +2 -3
  24. fin_infra/goals/management.py +3 -3
  25. fin_infra/goals/milestones.py +6 -6
  26. fin_infra/goals/models.py +2 -2
  27. fin_infra/investments/__init__.py +2 -2
  28. fin_infra/investments/ease.py +2 -2
  29. fin_infra/investments/models.py +24 -26
  30. fin_infra/investments/providers/base.py +4 -4
  31. fin_infra/investments/scaffold_templates/README.md +17 -17
  32. fin_infra/markets/__init__.py +2 -2
  33. fin_infra/models/accounts.py +4 -5
  34. fin_infra/models/transactions.py +2 -2
  35. fin_infra/net_worth/__init__.py +6 -6
  36. fin_infra/net_worth/aggregator.py +1 -1
  37. fin_infra/net_worth/calculator.py +1 -1
  38. fin_infra/net_worth/insights.py +7 -7
  39. fin_infra/normalization/__init__.py +2 -2
  40. fin_infra/normalization/currency_converter.py +7 -8
  41. fin_infra/normalization/models.py +9 -10
  42. fin_infra/normalization/providers/static_mappings.py +1 -1
  43. fin_infra/normalization/symbol_resolver.py +3 -4
  44. fin_infra/obs/classifier.py +2 -2
  45. fin_infra/providers/brokerage/alpaca.py +1 -1
  46. fin_infra/recurring/add.py +1 -1
  47. fin_infra/recurring/detectors_llm.py +5 -5
  48. fin_infra/recurring/ease.py +4 -4
  49. fin_infra/recurring/insights.py +15 -14
  50. fin_infra/recurring/normalizer.py +6 -6
  51. fin_infra/recurring/normalizers.py +27 -26
  52. fin_infra/scaffold/goals.py +4 -4
  53. fin_infra/security/add.py +1 -2
  54. fin_infra/security/audit.py +6 -7
  55. fin_infra/security/pii_filter.py +10 -10
  56. fin_infra/security/token_store.py +2 -3
  57. fin_infra/tax/add.py +2 -2
  58. fin_infra/tax/tlh.py +5 -5
  59. fin_infra/utils/__init__.py +15 -1
  60. fin_infra/utils/deprecation.py +161 -0
  61. {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/METADATA +17 -9
  62. {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/RECORD +65 -64
  63. {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/LICENSE +0 -0
  64. {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/WHEEL +0 -0
  65. {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -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
@@ -1,6 +1,6 @@
1
1
  """Alpaca brokerage provider for paper and live trading.
2
2
 
3
- ⚠️ IMPORTANT: This module provides real trading capabilities. Always use paper trading
3
+ [!] IMPORTANT: This module provides real trading capabilities. Always use paper trading
4
4
  mode for development and testing. Live trading requires explicit opt-in.
5
5
  """
6
6
 
@@ -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
 
@@ -352,6 +352,7 @@ class SubscriptionInsightsGenerator:
352
352
  import json
353
353
 
354
354
  subscriptions_json = json.dumps(subscriptions, sort_keys=True)
355
+ # Security: B324 skip justified - MD5 used for cache key generation only.
355
356
  hash_hex = hashlib.md5(subscriptions_json.encode()).hexdigest()
356
357
  return f"insights:{hash_hex}"
357
358
 
@@ -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
@@ -330,6 +330,7 @@ class MerchantNormalizer:
330
330
  Format: merchant_norm:{md5_hex}
331
331
  """
332
332
  normalized = merchant_name.lower().strip()
333
+ # Security: B324 skip justified - MD5 used for cache key generation only.
333
334
  hash_hex = hashlib.md5(normalized.encode()).hexdigest()
334
335
  return f"merchant_norm:{hash_hex}"
335
336
 
@@ -386,7 +387,7 @@ class MerchantNormalizer:
386
387
  # Remove special characters
387
388
  normalized = normalized.replace("*", " ").replace("#", " ").replace(".", " ")
388
389
 
389
- # Remove store numbers (e.g., "starbucks 1234" "starbucks")
390
+ # Remove store numbers (e.g., "starbucks 1234" -> "starbucks")
390
391
  import re
391
392
 
392
393
  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)
fin_infra/security/add.py CHANGED
@@ -5,7 +5,6 @@ Easy integration of financial PII masking and token encryption.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Optional
9
8
 
10
9
  from fastapi import FastAPI
11
10
 
@@ -15,7 +14,7 @@ from .pii_filter import FinancialPIIFilter
15
14
 
16
15
  def add_financial_security(
17
16
  app: FastAPI,
18
- encryption_key: Optional[bytes] = None,
17
+ encryption_key: bytes | None = None,
19
18
  enable_pii_filter: bool = True,
20
19
  enable_audit_log: bool = True,
21
20
  mask_emails: bool = False,
@@ -6,7 +6,6 @@ Track access to sensitive financial data for compliance.
6
6
 
7
7
  import logging
8
8
  from datetime import datetime
9
- from typing import Optional
10
9
 
11
10
  from .models import PIIAccessLog
12
11
 
@@ -23,10 +22,10 @@ async def log_pii_access(
23
22
  pii_type: str,
24
23
  action: str,
25
24
  resource: str,
26
- ip_address: Optional[str] = None,
27
- user_agent: Optional[str] = None,
25
+ ip_address: str | None = None,
26
+ user_agent: str | None = None,
28
27
  success: bool = True,
29
- error_message: Optional[str] = None,
28
+ error_message: str | None = None,
30
29
  ) -> PIIAccessLog:
31
30
  """
32
31
  Log PII access for audit trail.
@@ -86,9 +85,9 @@ async def log_pii_access(
86
85
 
87
86
 
88
87
  def get_audit_logs(
89
- user_id: Optional[str] = None,
90
- pii_type: Optional[str] = None,
91
- action: Optional[str] = None,
88
+ user_id: str | None = None,
89
+ pii_type: str | None = None,
90
+ action: str | None = None,
92
91
  limit: int = 100,
93
92
  ) -> list[PIIAccessLog]:
94
93
  """
@@ -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):
@@ -5,7 +5,6 @@ Database operations for encrypted provider tokens.
5
5
  """
6
6
 
7
7
  from datetime import datetime
8
- from typing import Optional
9
8
 
10
9
  from sqlalchemy import Column, DateTime, String, Text, select, update
11
10
  from sqlalchemy.dialects.postgresql import UUID
@@ -48,8 +47,8 @@ async def store_provider_token(
48
47
  provider: str,
49
48
  token: str,
50
49
  encryption: ProviderTokenEncryption,
51
- expires_at: Optional[datetime] = None,
52
- key_id: Optional[str] = None,
50
+ expires_at: datetime | None = None,
51
+ key_id: str | None = None,
53
52
  ) -> ProviderTokenMetadata:
54
53
  """
55
54
  Store encrypted provider token in database.
fin_infra/tax/add.py CHANGED
@@ -279,7 +279,7 @@ def add_tax_data(
279
279
  that can be sold to offset capital gains. Suggests replacement securities
280
280
  to maintain market exposure without triggering wash sale rules.
281
281
 
282
- ⚠️ **DISCLAIMER**: Not a substitute for professional tax or financial advice.
282
+ [!] **DISCLAIMER**: Not a substitute for professional tax or financial advice.
283
283
  Consult a certified tax professional before executing TLH trades.
284
284
 
285
285
  Args:
@@ -344,7 +344,7 @@ def add_tax_data(
344
344
  Projects the outcome of executing provided TLH opportunities, including
345
345
  total tax savings, portfolio impact, and risk assessment.
346
346
 
347
- ⚠️ **DISCLAIMER**: Not a substitute for professional tax or financial advice.
347
+ [!] **DISCLAIMER**: Not a substitute for professional tax or financial advice.
348
348
  Consult a certified tax professional before executing TLH trades.
349
349
 
350
350
  Args:
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
  }
@@ -10,8 +10,22 @@ and should be imported from there:
10
10
 
11
11
  For async retry with exponential backoff:
12
12
  from fin_infra.utils.retry import retry_async, RetryError
13
+
14
+ For deprecation utilities:
15
+ from fin_infra.utils.deprecation import deprecated, deprecated_parameter
13
16
  """
14
17
 
18
+ from fin_infra.utils.deprecation import (
19
+ DeprecatedWarning,
20
+ deprecated,
21
+ deprecated_parameter,
22
+ )
15
23
  from fin_infra.utils.retry import RetryError, retry_async
16
24
 
17
- __all__ = ["RetryError", "retry_async"]
25
+ __all__ = [
26
+ "RetryError",
27
+ "retry_async",
28
+ "deprecated",
29
+ "deprecated_parameter",
30
+ "DeprecatedWarning",
31
+ ]