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.
- fin_infra/analytics/__init__.py +2 -2
- fin_infra/analytics/add.py +3 -3
- fin_infra/analytics/cash_flow.py +3 -3
- fin_infra/analytics/models.py +5 -5
- fin_infra/analytics/portfolio.py +12 -12
- fin_infra/analytics/spending.py +4 -5
- fin_infra/banking/history.py +4 -4
- fin_infra/brokerage/__init__.py +7 -7
- fin_infra/budgets/alerts.py +2 -2
- fin_infra/budgets/ease.py +1 -2
- fin_infra/budgets/models.py +1 -2
- fin_infra/budgets/templates.py +4 -4
- fin_infra/budgets/tracker.py +3 -3
- fin_infra/categorization/__init__.py +1 -1
- fin_infra/categorization/ease.py +3 -3
- fin_infra/categorization/engine.py +1 -1
- fin_infra/categorization/llm_layer.py +6 -4
- fin_infra/categorization/models.py +3 -4
- fin_infra/chat/planning.py +1 -1
- fin_infra/cli/cmds/scaffold_cmds.py +6 -6
- fin_infra/credit/experian/parser.py +5 -5
- fin_infra/crypto/__init__.py +2 -2
- fin_infra/documents/models.py +2 -3
- fin_infra/goals/management.py +3 -3
- fin_infra/goals/milestones.py +6 -6
- fin_infra/goals/models.py +2 -2
- fin_infra/investments/__init__.py +2 -2
- fin_infra/investments/ease.py +2 -2
- fin_infra/investments/models.py +24 -26
- fin_infra/investments/providers/base.py +4 -4
- fin_infra/investments/scaffold_templates/README.md +17 -17
- fin_infra/markets/__init__.py +2 -2
- fin_infra/models/accounts.py +4 -5
- fin_infra/models/transactions.py +2 -2
- fin_infra/net_worth/__init__.py +6 -6
- fin_infra/net_worth/aggregator.py +1 -1
- fin_infra/net_worth/calculator.py +1 -1
- fin_infra/net_worth/insights.py +7 -7
- fin_infra/normalization/__init__.py +2 -2
- fin_infra/normalization/currency_converter.py +7 -8
- fin_infra/normalization/models.py +9 -10
- fin_infra/normalization/providers/static_mappings.py +1 -1
- fin_infra/normalization/symbol_resolver.py +3 -4
- fin_infra/obs/classifier.py +2 -2
- fin_infra/providers/brokerage/alpaca.py +1 -1
- fin_infra/recurring/add.py +1 -1
- fin_infra/recurring/detectors_llm.py +5 -5
- fin_infra/recurring/ease.py +4 -4
- fin_infra/recurring/insights.py +15 -14
- fin_infra/recurring/normalizer.py +6 -6
- fin_infra/recurring/normalizers.py +27 -26
- fin_infra/scaffold/goals.py +4 -4
- fin_infra/security/add.py +1 -2
- fin_infra/security/audit.py +6 -7
- fin_infra/security/pii_filter.py +10 -10
- fin_infra/security/token_store.py +2 -3
- fin_infra/tax/add.py +2 -2
- fin_infra/tax/tlh.py +5 -5
- fin_infra/utils/__init__.py +15 -1
- fin_infra/utils/deprecation.py +161 -0
- {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/METADATA +17 -9
- {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/RECORD +65 -64
- {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/LICENSE +0 -0
- {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/WHEEL +0 -0
- {fin_infra-0.1.82.dist-info → fin_infra-0.4.0.dist-info}/entry_points.txt +0 -0
fin_infra/obs/classifier.py
CHANGED
|
@@ -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/*)
|
|
67
|
-
- All other routes
|
|
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
|
-
|
|
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
|
|
fin_infra/recurring/add.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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):
|
fin_infra/recurring/ease.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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"
|
|
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
|
|
fin_infra/recurring/insights.py
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
+
-> "You're paying for both Spotify and Apple Music ($20.98/month). Cancel one
|
|
116
116
|
to save $10.99/month."
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
-> total_monthly_cost: 20.98
|
|
118
|
+
-> potential_savings: 10.99
|
|
119
119
|
|
|
120
120
|
3. Subscriptions: LA Fitness $40, Planet Fitness $10
|
|
121
|
-
|
|
121
|
+
-> "You have 2 gym memberships totaling $50/month. Consider consolidating to
|
|
122
122
|
just Planet Fitness to save $40/month."
|
|
123
|
-
|
|
124
|
-
|
|
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)
|
|
156
|
-
2. Call LLM if cache miss
|
|
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})
|
|
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
|
|
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"
|
|
30
|
-
2. Remove domain suffixes: "netflix.com"
|
|
31
|
-
3. Remove special chars: "netflix*subscription"
|
|
32
|
-
4. Remove store/transaction numbers: "starbucks #12345"
|
|
33
|
-
5. Remove legal entities: "netflix inc"
|
|
34
|
-
6. Strip whitespace: " 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)
|
|
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"
|
|
97
|
-
2. "Netflix Inc"
|
|
98
|
-
3. "NETFLIX.COM"
|
|
99
|
-
4. "SQ *COZY CAFE"
|
|
100
|
-
5. "TST* STARBUCKS"
|
|
101
|
-
6. "AMZN MKTP US"
|
|
102
|
-
7. "SPFY*PREMIUM"
|
|
103
|
-
8. "UBER *TRIP 12345"
|
|
104
|
-
9. "LYFT *RIDE ABC"
|
|
105
|
-
10. "CLOVER* PIZZA PLACE"
|
|
106
|
-
11. "AAPL* ICLOUD STORAGE"
|
|
107
|
-
12. "MSFT*MICROSOFT 365"
|
|
108
|
-
13. "DISNEY PLUS #123"
|
|
109
|
-
14. "PRIME VIDEO"
|
|
110
|
-
15. "CITY ELECTRIC #456"
|
|
111
|
-
16. "T-MOBILE USA"
|
|
112
|
-
17. "VERIZON WIRELESS"
|
|
113
|
-
18. "WHOLE FOODS MKT #789"
|
|
114
|
-
19. "STARBUCKS #1234"
|
|
115
|
-
20. "LA FITNESS #567"
|
|
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)
|
|
135
|
-
2. Call LLM if cache miss
|
|
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)
|
|
224
|
+
1. Check cache (95% hit rate) -> <1ms
|
|
225
225
|
2. Check budget (daily/monthly caps)
|
|
226
|
-
3. Call LLM if cache miss
|
|
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"
|
|
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)
|
fin_infra/scaffold/goals.py
CHANGED
|
@@ -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
|
|
182
|
-
- schemas.py.tmpl
|
|
183
|
-
- repository.py.tmpl
|
|
184
|
-
- README.md
|
|
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:
|
|
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,
|
fin_infra/security/audit.py
CHANGED
|
@@ -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:
|
|
27
|
-
user_agent:
|
|
25
|
+
ip_address: str | None = None,
|
|
26
|
+
user_agent: str | None = None,
|
|
28
27
|
success: bool = True,
|
|
29
|
-
error_message:
|
|
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:
|
|
90
|
-
pii_type:
|
|
91
|
-
action:
|
|
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
|
"""
|
fin_infra/security/pii_filter.py
CHANGED
|
@@ -116,8 +116,8 @@ class FinancialPIIFilter(logging.Filter):
|
|
|
116
116
|
Mask Social Security Numbers.
|
|
117
117
|
|
|
118
118
|
Examples:
|
|
119
|
-
123-45-6789
|
|
120
|
-
123456789
|
|
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
|
|
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
|
|
158
|
-
4111111111111111
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
52
|
-
key_id:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
467
|
+
# Auto/EV -> sector alternatives
|
|
468
468
|
"TSLA": "ARKK", # Innovation ETF
|
|
469
469
|
"F": "XLI", # Industrials ETF
|
|
470
470
|
"GM": "XLI",
|
|
471
|
-
# Finance
|
|
471
|
+
# Finance -> sector ETF
|
|
472
472
|
"JPM": "XLF", # Financials ETF
|
|
473
473
|
"BAC": "XLF",
|
|
474
474
|
"GS": "XLF",
|
|
475
|
-
# Healthcare
|
|
475
|
+
# Healthcare -> sector ETF
|
|
476
476
|
"JNJ": "XLV", # Healthcare ETF
|
|
477
477
|
"PFE": "XLV",
|
|
478
478
|
"MRNA": "XBI", # Biotech ETF
|
|
479
|
-
# Crypto
|
|
479
|
+
# Crypto -> broad exposure
|
|
480
480
|
"BTC": "ETH", # Ethereum (different asset)
|
|
481
481
|
"ETH": "BTC", # Bitcoin (different asset)
|
|
482
482
|
}
|
fin_infra/utils/__init__.py
CHANGED
|
@@ -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__ = [
|
|
25
|
+
__all__ = [
|
|
26
|
+
"RetryError",
|
|
27
|
+
"retry_async",
|
|
28
|
+
"deprecated",
|
|
29
|
+
"deprecated_parameter",
|
|
30
|
+
"DeprecatedWarning",
|
|
31
|
+
]
|