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.
- fin_infra/analytics/add.py +3 -3
- fin_infra/analytics/portfolio.py +12 -12
- fin_infra/brokerage/__init__.py +2 -2
- fin_infra/categorization/__init__.py +1 -1
- fin_infra/categorization/engine.py +1 -1
- fin_infra/categorization/llm_layer.py +4 -4
- fin_infra/credit/experian/parser.py +5 -5
- fin_infra/crypto/__init__.py +2 -2
- fin_infra/goals/models.py +2 -2
- fin_infra/investments/__init__.py +2 -2
- fin_infra/investments/providers/base.py +4 -4
- fin_infra/markets/__init__.py +2 -2
- fin_infra/net_worth/__init__.py +1 -1
- fin_infra/net_worth/aggregator.py +1 -1
- fin_infra/net_worth/calculator.py +1 -1
- fin_infra/net_worth/insights.py +3 -3
- fin_infra/normalization/__init__.py +2 -2
- fin_infra/normalization/currency_converter.py +3 -3
- fin_infra/normalization/providers/static_mappings.py +1 -1
- fin_infra/obs/classifier.py +2 -2
- 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 +14 -14
- fin_infra/recurring/normalizer.py +6 -6
- fin_infra/recurring/normalizers.py +26 -26
- fin_infra/scaffold/goals.py +4 -4
- fin_infra/security/pii_filter.py +10 -10
- fin_infra/tax/tlh.py +5 -5
- {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/METADATA +13 -12
- {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/RECORD +34 -34
- {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/LICENSE +0 -0
- {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/WHEEL +0 -0
- {fin_infra-0.2.1.dist-info → fin_infra-0.2.3.dist-info}/entry_points.txt +0 -0
fin_infra/analytics/add.py
CHANGED
|
@@ -239,9 +239,9 @@ def add_analytics(
|
|
|
239
239
|
|
|
240
240
|
Note:
|
|
241
241
|
Real holdings provide:
|
|
242
|
-
- Accurate cost basis
|
|
243
|
-
- Security types
|
|
244
|
-
- Current values
|
|
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:
|
fin_infra/analytics/portfolio.py
CHANGED
|
@@ -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
|
|
531
|
-
- Real security types
|
|
532
|
-
- Current market values
|
|
533
|
-
- No mock data
|
|
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)
|
|
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
|
|
707
|
-
- etf
|
|
708
|
-
- mutual_fund
|
|
709
|
-
- bond
|
|
710
|
-
- cash
|
|
711
|
-
- derivative
|
|
712
|
-
- 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
|
|
fin_infra/brokerage/__init__.py
CHANGED
|
@@ -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
|
|
58
|
-
2. Otherwise
|
|
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
|
|
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
|
|
@@ -197,7 +197,7 @@ class LLMCategorizer:
|
|
|
197
197
|
self._track_cost()
|
|
198
198
|
|
|
199
199
|
logger.info(
|
|
200
|
-
f"LLM categorized '{merchant_name}'
|
|
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
|
|
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}
|
|
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}
|
|
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
|
|
5
|
-
- parse_credit_report(): dict
|
|
6
|
-
- parse_account(): dict
|
|
7
|
-
- parse_inquiry(): dict
|
|
8
|
-
- parse_public_record(): dict
|
|
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")
|
fin_infra/crypto/__init__.py
CHANGED
|
@@ -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
|
|
33
|
-
2. Otherwise
|
|
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
|
|
97
|
-
- Allocation percentages must sum to
|
|
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
|
|
78
|
-
2. If SNAPTRADE_CLIENT_ID set
|
|
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"
|
|
240
|
-
Plaid: "mutual fund"
|
|
241
|
-
SnapTrade: "cs"
|
|
242
|
-
SnapTrade: "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.
|
fin_infra/markets/__init__.py
CHANGED
|
@@ -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
|
|
37
|
-
2. Otherwise
|
|
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").
|
fin_infra/net_worth/__init__.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
6
|
+
- Currency normalization (all currencies -> base currency)
|
|
7
7
|
- Asset allocation breakdown
|
|
8
8
|
- Change detection (amount + percentage)
|
|
9
9
|
|
fin_infra/net_worth/insights.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
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}
|
|
111
|
+
logger.error(f"Failed to get rate {from_currency} -> {to_currency}: {e}")
|
|
112
112
|
raise CurrencyNotSupportedError(
|
|
113
|
-
f"Rate not available: {from_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
|
|
112
|
+
# Maps provider-specific format -> standard ticker
|
|
113
113
|
PROVIDER_SYMBOL_MAP = {
|
|
114
114
|
"yahoo": {
|
|
115
115
|
# Yahoo Finance uses dashes for crypto
|
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
|
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
|
|
|
@@ -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
|
|
@@ -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"
|
|
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)
|
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/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):
|
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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: fin-infra
|
|
3
|
-
Version: 0.2.
|
|
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
|
-
|
|
52
|
+
**Financial data infrastructure for fintech apps.**
|
|
53
|
+
|
|
55
54
|
[](https://pypi.org/project/fin-infra/)
|
|
55
|
+
[](https://github.com/nfraxlab/fin-infra/actions/workflows/ci.yml)
|
|
56
56
|
[](https://pypi.org/project/fin-infra/)
|
|
57
57
|
[](LICENSE)
|
|
58
|
-
[](https://pypi.org/project/fin-infra/)
|
|
59
|
-
[](https://codecov.io/gh/nfraxlab/fin-infra)
|
|
60
|
-
|
|
61
|
-
### Financial data infrastructure for fintech apps
|
|
62
58
|
|
|
63
|
-
|
|
59
|
+
## Overview
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
Banking, investments, market data, credit scores, and financial calculations in one toolkit.
|
|
66
62
|
|
|
67
|
-
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
36
|
-
fin_infra/categorization/llm_layer.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
108
|
-
fin_infra/net_worth/calculator.py,sha256=
|
|
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=
|
|
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=
|
|
119
|
-
fin_infra/normalization/currency_converter.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
151
|
-
fin_infra/recurring/ease.py,sha256=
|
|
152
|
-
fin_infra/recurring/insights.py,sha256
|
|
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=
|
|
155
|
-
fin_infra/recurring/normalizers.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
178
|
-
fin_infra-0.2.
|
|
179
|
-
fin_infra-0.2.
|
|
180
|
-
fin_infra-0.2.
|
|
181
|
-
fin_infra-0.2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|