upgini 1.2.113a3__py3-none-any.whl → 1.2.113a5__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.
upgini/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.2.113a3"
1
+ __version__ = "1.2.113a5"
@@ -112,7 +112,7 @@ except Exception:
112
112
  CustomFallbackProgressBar as ProgressBar,
113
113
  )
114
114
 
115
- from upgini.utils.psi import calculate_features_psi
115
+ from upgini.utils.psi import calculate_features_psi, calculate_sparsity_psi
116
116
  from upgini.utils.sample_utils import SampleColumns, SampleConfig, _num_samples, sample
117
117
  from upgini.utils.sort import sort_columns
118
118
  from upgini.utils.target_utils import calculate_psi, define_task
@@ -1059,24 +1059,9 @@ class FeaturesEnricher(TransformerMixin):
1059
1059
  groups,
1060
1060
  _cv,
1061
1061
  columns_renaming,
1062
- eval_set_dates,
1062
+ _,
1063
1063
  ) = prepared_data
1064
1064
 
1065
- # rename cat_features
1066
- if client_cat_features:
1067
- for new_c, old_c in columns_renaming.items():
1068
- if old_c in client_cat_features:
1069
- client_cat_features.remove(old_c)
1070
- client_cat_features.append(new_c)
1071
- for cat_feature in client_cat_features:
1072
- if cat_feature not in fitting_X.columns:
1073
- self.logger.error(
1074
- f"Client cat_feature `{cat_feature}` not found in"
1075
- f" x columns: {fitting_X.columns.to_list()}"
1076
- )
1077
- else:
1078
- client_cat_features = []
1079
-
1080
1065
  # rename baseline_score_column
1081
1066
  reversed_renaming = {v: k for k, v in columns_renaming.items()}
1082
1067
  baseline_score_column = self.baseline_score_column
@@ -1305,7 +1290,7 @@ class FeaturesEnricher(TransformerMixin):
1305
1290
  metrics.append(eval_metrics)
1306
1291
 
1307
1292
  if updating_shaps is not None:
1308
- decoded_X = self._decode_id_columns(fitting_X, columns_renaming)
1293
+ decoded_X = self._decode_id_columns(fitting_X)
1309
1294
  self._update_shap_values(trace_id, decoded_X, updating_shaps, silent=not internal_call)
1310
1295
 
1311
1296
  metrics_df = pd.DataFrame(metrics)
@@ -1376,12 +1361,23 @@ class FeaturesEnricher(TransformerMixin):
1376
1361
  if isinstance(X, np.ndarray):
1377
1362
  search_keys = {str(k): v for k, v in search_keys.items()}
1378
1363
 
1379
- has_date = self._get_date_column(search_keys) is not None
1380
- if not has_date or not validated_eval_set:
1381
- self.logger.info("No date column or eval set for OOT psi calculation")
1364
+ date_column = self._get_date_column(search_keys)
1365
+ has_date = date_column is not None
1366
+ if not has_date:
1367
+ self.logger.info("No date column for OOT PSI calculation")
1368
+ return
1369
+ if not validated_eval_set:
1370
+ self.logger.info("No eval set for OOT PSI calculation")
1371
+ return
1372
+ if validated_X[date_column].nunique() <= 1:
1373
+ self.logger.warning("Constant date for OOT PSI calculation")
1374
+ return
1375
+ if self.cv is not None and self.cv.is_time_series():
1376
+ self.logger.warning("Time series CV is not supported for OOT PSI calculation")
1382
1377
  return
1383
1378
 
1384
1379
  cat_features_from_backend = self.__get_categorical_features()
1380
+ cat_features_from_backend = [self.fit_columns_renaming.get(c, c) for c in cat_features_from_backend]
1385
1381
  client_cat_features, search_keys_for_metrics = self._get_and_validate_client_cat_features(
1386
1382
  estimator, validated_X, search_keys
1387
1383
  )
@@ -1390,13 +1386,13 @@ class FeaturesEnricher(TransformerMixin):
1390
1386
  cat_features_from_backend = [
1391
1387
  c
1392
1388
  for c in cat_features_from_backend
1393
- if self.fit_columns_renaming.get(c, c) not in self.id_columns_encoder.feature_names_in_
1389
+ if c not in self.id_columns_encoder.feature_names_in_
1394
1390
  ]
1395
1391
  if client_cat_features:
1396
1392
  client_cat_features = [
1397
1393
  c
1398
1394
  for c in client_cat_features
1399
- if self.fit_columns_renaming.get(c, c) not in self.id_columns_encoder.feature_names_in_
1395
+ if c not in self.id_columns_encoder.feature_names_in_
1400
1396
  ]
1401
1397
 
1402
1398
  prepared_data = self._prepare_data_for_metrics(
@@ -1431,20 +1427,6 @@ class FeaturesEnricher(TransformerMixin):
1431
1427
  eval_set_dates,
1432
1428
  ) = prepared_data
1433
1429
 
1434
- # rename cat_features
1435
- if client_cat_features:
1436
- for new_c, old_c in columns_renaming.items():
1437
- if old_c in client_cat_features:
1438
- client_cat_features.remove(old_c)
1439
- client_cat_features.append(new_c)
1440
- for cat_feature in client_cat_features:
1441
- if cat_feature not in fitting_X.columns:
1442
- self.logger.error(
1443
- f"Client cat_feature `{cat_feature}` not found in" f" x columns: {fitting_X.columns.to_list()}"
1444
- )
1445
- else:
1446
- client_cat_features = []
1447
-
1448
1430
  model_task_type = self.model_task_type or define_task(y_sorted, has_date, self.logger, silent=True)
1449
1431
  cat_features = list(set(client_cat_features + cat_features_from_backend))
1450
1432
 
@@ -1496,14 +1478,6 @@ class FeaturesEnricher(TransformerMixin):
1496
1478
  # Find latest eval set or earliest if all eval sets are before train set
1497
1479
  date_column = self._get_date_column(search_keys)
1498
1480
 
1499
- if (
1500
- date_column is None
1501
- or not eval_set
1502
- or not eval_set_dates
1503
- or (self.cv is not None and self.cv.is_time_series())
1504
- ):
1505
- return []
1506
-
1507
1481
  # Get minimum date from main dataset X
1508
1482
  main_min_date = X[date_column].min()
1509
1483
 
@@ -1539,15 +1513,29 @@ class FeaturesEnricher(TransformerMixin):
1539
1513
 
1540
1514
  checking_eval_set_df[date_column] = eval_set_dates[selected_eval_set_idx]
1541
1515
 
1516
+ psi_values_sparse = calculate_sparsity_psi(
1517
+ checking_eval_set_df, cat_features, date_column, self.logger, model_task_type
1518
+ )
1519
+
1520
+ unstable_by_sparsity = [feature for feature, psi in psi_values_sparse.items() if psi > stability_threshold]
1521
+ if unstable_by_sparsity:
1522
+ self.logger.info(f"Unstable by sparsity features: {sorted(unstable_by_sparsity)}")
1523
+
1542
1524
  psi_values = calculate_features_psi(
1543
1525
  checking_eval_set_df, cat_features, date_column, self.logger, model_task_type
1544
1526
  )
1545
1527
 
1528
+ unstable_by_value = [feature for feature, psi in psi_values.items() if psi > stability_threshold]
1529
+ if unstable_by_value:
1530
+ self.logger.info(f"Unstable by value features: {sorted(unstable_by_value)}")
1531
+
1546
1532
  self.psi_values = {
1547
1533
  feature: psi_value for feature, psi_value in psi_values.items() if psi_value <= stability_threshold
1548
1534
  }
1549
1535
 
1550
- return [feature for feature, psi in psi_values.items() if psi > stability_threshold]
1536
+ total_unstable_features = sorted(set(unstable_by_sparsity + unstable_by_value))
1537
+
1538
+ return total_unstable_features
1551
1539
 
1552
1540
  def _update_shap_values(self, trace_id: str, df: pd.DataFrame, new_shaps: Dict[str, float], silent: bool = False):
1553
1541
  renaming = self.fit_columns_renaming or {}
@@ -1757,7 +1745,7 @@ class FeaturesEnricher(TransformerMixin):
1757
1745
  def _get_and_validate_client_cat_features(
1758
1746
  self, estimator: Optional[Any], X: pd.DataFrame, search_keys: Dict[str, SearchKey]
1759
1747
  ) -> Tuple[Optional[List[str]], List[str]]:
1760
- cat_features = None
1748
+ cat_features = []
1761
1749
  search_keys_for_metrics = []
1762
1750
  if (
1763
1751
  estimator is not None
@@ -1926,7 +1914,7 @@ class FeaturesEnricher(TransformerMixin):
1926
1914
  fitting_X, y_sorted, search_keys, self.model_task_type, sort_all_columns=True, logger=self.logger
1927
1915
  )
1928
1916
  fitting_X = fitting_X[fitting_x_columns]
1929
- fitting_X, _ = self._encode_id_columns(fitting_X, self.fit_columns_renaming)
1917
+ fitting_X, _ = self._encode_id_columns(fitting_X)
1930
1918
  self.logger.info(f"Final sorted list of fitting X columns: {fitting_x_columns}")
1931
1919
  fitting_enriched_x_columns = fitting_enriched_X.columns.to_list()
1932
1920
  fitting_enriched_x_columns = sort_columns(
@@ -1938,7 +1926,7 @@ class FeaturesEnricher(TransformerMixin):
1938
1926
  logger=self.logger,
1939
1927
  )
1940
1928
  fitting_enriched_X = fitting_enriched_X[fitting_enriched_x_columns]
1941
- fitting_enriched_X, _ = self._encode_id_columns(fitting_enriched_X, self.fit_columns_renaming)
1929
+ fitting_enriched_X, _ = self._encode_id_columns(fitting_enriched_X)
1942
1930
  self.logger.info(f"Final sorted list of fitting enriched X columns: {fitting_enriched_x_columns}")
1943
1931
  date_column = self._get_date_column(search_keys)
1944
1932
  eval_set_dates = {}
@@ -1970,8 +1958,8 @@ class FeaturesEnricher(TransformerMixin):
1970
1958
  .astype(np.float64)
1971
1959
  )
1972
1960
 
1973
- fitting_eval_X, unknown_dict = self._encode_id_columns(fitting_eval_X, self.fit_columns_renaming)
1974
- fitting_enriched_eval_X, _ = self._encode_id_columns(fitting_enriched_eval_X, self.fit_columns_renaming)
1961
+ fitting_eval_X, unknown_dict = self._encode_id_columns(fitting_eval_X)
1962
+ fitting_enriched_eval_X, _ = self._encode_id_columns(fitting_enriched_eval_X)
1975
1963
 
1976
1964
  if len(unknown_dict) > 0:
1977
1965
  print(self.bundle.get("unknown_id_column_value_in_eval_set").format(unknown_dict))
@@ -2299,13 +2287,7 @@ class FeaturesEnricher(TransformerMixin):
2299
2287
  enriched_df, x_columns, enriched_X.columns.tolist(), len(eval_set) if has_eval_set else 0
2300
2288
  )
2301
2289
 
2302
- # Add hash-suffixes because output of transform has original names
2303
- reversed_renaming = {v: k for k, v in columns_renaming.items()}
2304
- X_sampled.rename(columns=reversed_renaming, inplace=True)
2305
- enriched_X.rename(columns=reversed_renaming, inplace=True)
2306
- for _, (eval_X_sampled, enriched_eval_X, _) in eval_set_sampled_dict.items():
2307
- eval_X_sampled.rename(columns=reversed_renaming, inplace=True)
2308
- enriched_eval_X.rename(columns=reversed_renaming, inplace=True)
2290
+ search_keys = {columns_renaming.get(k, k): v for k, v in search_keys.items()}
2309
2291
 
2310
2292
  # Cache and return results
2311
2293
  datasets_hash = hash_input(validated_X, validated_y, eval_set)
@@ -3205,7 +3187,7 @@ if response.status_code == 200:
3205
3187
  is_numeric_dtype(df[self.TARGET_NAME])
3206
3188
  and self.model_task_type in [ModelTaskType.BINARY, ModelTaskType.MULTICLASS]
3207
3189
  and has_date
3208
- and not self.cv.is_time_series()
3190
+ and (self.cv is None or not self.cv.is_time_series())
3209
3191
  ):
3210
3192
  self._validate_PSI(df.sort_values(by=maybe_date_column))
3211
3193
 
@@ -3238,8 +3220,7 @@ if response.status_code == 200:
3238
3220
  self.fit_generated_features = [f for f in self.fit_generated_features if f not in self.fit_dropped_features]
3239
3221
 
3240
3222
  # Group columns should have normalized names
3241
- self.cv = None
3242
- self.__adjust_cv(df)
3223
+ self.__adjust_cv(df, force=True)
3243
3224
  if self.id_columns is not None and self.cv is not None and self.cv.is_time_series():
3244
3225
  id_columns = self.__get_renamed_id_columns()
3245
3226
  if id_columns:
@@ -3544,19 +3525,21 @@ if response.status_code == 200:
3544
3525
  reverse_renaming = {v: k for k, v in renaming.items()}
3545
3526
  return None if self.id_columns is None else [reverse_renaming.get(c) or c for c in self.id_columns]
3546
3527
 
3547
- def __adjust_cv(self, df: pd.DataFrame):
3528
+ def __adjust_cv(self, df: pd.DataFrame, force: bool = False):
3529
+ if self.cv is not None and not force:
3530
+ return
3531
+
3548
3532
  date_column = SearchKey.find_key(self.fit_search_keys, [SearchKey.DATE, SearchKey.DATETIME])
3549
3533
  # Check Multivariate time series
3550
3534
  if (
3551
- self.cv is None
3552
- and date_column
3535
+ date_column
3553
3536
  and self.model_task_type == ModelTaskType.REGRESSION
3554
3537
  and len({SearchKey.PHONE, SearchKey.EMAIL, SearchKey.HEM}.intersection(self.fit_search_keys.keys())) == 0
3555
3538
  and is_blocked_time_series(df, date_column, list(self.fit_search_keys.keys()) + [TARGET])
3556
3539
  ):
3557
3540
  msg = self.bundle.get("multivariate_timeseries_detected")
3558
3541
  self.__override_cv(CVType.blocked_time_series, msg, print_warning=False)
3559
- elif self.cv is None and self.model_task_type != ModelTaskType.REGRESSION:
3542
+ elif self.model_task_type != ModelTaskType.REGRESSION:
3560
3543
  msg = self.bundle.get("group_k_fold_in_classification")
3561
3544
  self.__override_cv(CVType.group_k_fold, msg, print_warning=self.cv is not None)
3562
3545
  group_columns = self._get_group_columns(df, self.fit_search_keys)
@@ -3594,39 +3577,32 @@ if response.status_code == 200:
3594
3577
  def _encode_id_columns(
3595
3578
  self,
3596
3579
  X: pd.DataFrame,
3597
- columns_renaming: Optional[Dict[str, str]] = None,
3598
3580
  ) -> Tuple[pd.DataFrame, Dict[str, List[Any]]]:
3599
- columns_renaming = columns_renaming or {}
3600
3581
  unknown_dict = {}
3601
3582
 
3602
3583
  if self.id_columns and self.id_columns_encoder is not None:
3603
- inverse_columns_renaming = {v: k for k, v in columns_renaming.items()}
3604
- renamed_id_columns = [
3605
- inverse_columns_renaming.get(col, col) for col in self.id_columns_encoder.feature_names_in_
3606
- ]
3607
- self.logger.info(f"Convert id columns to int: {renamed_id_columns}")
3608
- encoded = self.id_columns_encoder.transform(X[renamed_id_columns].rename(columns=columns_renaming))
3609
- for i, c in enumerate(renamed_id_columns):
3610
- unknown_values = X[encoded[:, i] == -1][c].unique().tolist()
3611
- if len(unknown_values) > 0:
3612
- unknown_dict[c] = unknown_values
3613
- X[renamed_id_columns] = encoded
3614
- X = X.loc[(X[renamed_id_columns] != -1).all(axis=1)]
3615
-
3616
- if len(unknown_dict) > 0:
3617
- self.logger.warning(f"Unknown values in id columns: {unknown_dict}")
3584
+ encoding_id_columns = [c for c in self.id_columns if c in X.columns]
3585
+ if len(encoding_id_columns) > 0:
3586
+ self.logger.info(f"Convert id columns to int: {encoding_id_columns}")
3587
+ encoded = self.id_columns_encoder.transform(X[encoding_id_columns])
3588
+ for i, c in enumerate(encoding_id_columns):
3589
+ unknown_values = X[encoded[:, i] == -1][c].unique().tolist()
3590
+ if len(unknown_values) > 0:
3591
+ unknown_dict[c] = unknown_values
3592
+ X[encoding_id_columns] = encoded
3593
+ X = X.loc[(X[encoding_id_columns] != -1).all(axis=1)]
3594
+
3595
+ if len(unknown_dict) > 0:
3596
+ self.logger.warning(f"Unknown values in id columns: {unknown_dict}")
3618
3597
 
3619
3598
  return X, unknown_dict
3620
3599
 
3621
- def _decode_id_columns(self, X: pd.DataFrame, columns_renaming: Dict[str, str]):
3622
- columns_renaming = columns_renaming or {}
3600
+ def _decode_id_columns(self, X: pd.DataFrame):
3623
3601
  if self.id_columns and self.id_columns_encoder is not None:
3624
- inverse_columns_renaming = {v: k for k, v in columns_renaming.items()}
3625
- renamed_id_columns = [
3626
- inverse_columns_renaming.get(col, col) for col in self.id_columns_encoder.feature_names_in_
3627
- ]
3628
- decoded = self.id_columns_encoder.inverse_transform(X[renamed_id_columns].rename(columns=columns_renaming))
3629
- X[renamed_id_columns] = decoded
3602
+ decoding_id_columns = [c for c in self.id_columns if c in X.columns]
3603
+ if len(decoding_id_columns) > 0:
3604
+ decoded = self.id_columns_encoder.inverse_transform(X[self.id_columns])
3605
+ X[self.id_columns] = decoded
3630
3606
 
3631
3607
  return X
3632
3608
 
@@ -4172,7 +4148,7 @@ if response.status_code == 200:
4172
4148
  columns_to_sort = [date_column] if date_column is not None else []
4173
4149
 
4174
4150
  do_sorting = True
4175
- if self.id_columns and self.cv.is_time_series():
4151
+ if self.id_columns and self.cv is not None and self.cv.is_time_series():
4176
4152
  # Check duplicates by date and id_columns
4177
4153
  reversed_columns_renaming = {v: k for k, v in columns_renaming.items()}
4178
4154
  renamed_id_columns = [reversed_columns_renaming.get(c, c) for c in self.id_columns]
upgini/utils/psi.py CHANGED
@@ -42,6 +42,32 @@ DEFAULT_FEATURES_PARAMS = StabilityParams(
42
42
  )
43
43
 
44
44
 
45
+ def calculate_sparsity_psi(
46
+ df: pd.DataFrame,
47
+ cat_features: list[str],
48
+ date_column: str,
49
+ logger: logging.Logger,
50
+ model_task_type: ModelTaskType,
51
+ psi_features_params: StabilityParams = DEFAULT_FEATURES_PARAMS,
52
+ psi_target_params: StabilityParams = DEFAULT_TARGET_PARAMS,
53
+ ) -> Dict[str, float]:
54
+ sparse_features = df.columns[df.isna().sum() > 0].to_list()
55
+ if len(sparse_features) > 0:
56
+ logger.info(f"Calculating sparsity stability for {len(sparse_features)} sparse features")
57
+ sparse_df = df[sparse_features].notna()
58
+ sparse_df[date_column] = df[date_column]
59
+ return calculate_features_psi(
60
+ sparse_df,
61
+ cat_features,
62
+ date_column,
63
+ logger,
64
+ model_task_type,
65
+ psi_target_params,
66
+ psi_features_params,
67
+ )
68
+ return {}
69
+
70
+
45
71
  def calculate_features_psi(
46
72
  df: pd.DataFrame,
47
73
  cat_features: list[str],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: upgini
3
- Version: 1.2.113a3
3
+ Version: 1.2.113a5
4
4
  Summary: Intelligent data search & enrichment for Machine Learning
5
5
  Project-URL: Bug Reports, https://github.com/upgini/upgini/issues
6
6
  Project-URL: Homepage, https://upgini.com/
@@ -1,9 +1,9 @@
1
- upgini/__about__.py,sha256=8dzTgZycBqXQF8H6x1n5BSvRmuPEQB4RCActXRN2QB8,26
1
+ upgini/__about__.py,sha256=QdA0r4M8wEBY37BMjK9uA_83s1sWkyXy2XJhfn7vl3A,26
2
2
  upgini/__init__.py,sha256=LXSfTNU0HnlOkE69VCxkgIKDhWP-JFo_eBQ71OxTr5Y,261
3
3
  upgini/ads.py,sha256=nvuRxRx5MHDMgPr9SiU-fsqRdFaBv8p4_v1oqiysKpc,2714
4
4
  upgini/dataset.py,sha256=xFi0a-A3uvtxVwFM6JOyitkEPd1I2slIBj5SWfys3hQ,32724
5
5
  upgini/errors.py,sha256=2b_Wbo0OYhLUbrZqdLIx5jBnAsiD1Mcenh-VjR4HCTw,950
6
- upgini/features_enricher.py,sha256=oEVZVbN6qkeeTQkQfBCwnWw7hBEW9ZO2I9_r_m3bFUw,235890
6
+ upgini/features_enricher.py,sha256=wifdmDP-3e3y51KYhCHPYuN6vU8mj2m3SYo-kMWcNz0,234523
7
7
  upgini/http.py,sha256=zeAZvT6IAzOs9jQ3WG8mJBANLajgvv2LZePFzKz004w,45482
8
8
  upgini/metadata.py,sha256=sx4X9fPkyCgXB6FPk9Rq_S1Kx8ibkbaWA-qNDVCuSmg,12811
9
9
  upgini/metrics.py,sha256=O19UqmgZ6SA136eCYV5lVU3J26ecgZlGXnxGblMvZJc,45869
@@ -64,7 +64,7 @@ upgini/utils/mstats.py,sha256=u3gQVUtDRbyrOQK6V1UJ2Rx1QbkSNYGjXa6m3Z_dPVs,6286
64
64
  upgini/utils/phone_utils.py,sha256=IrbztLuOJBiePqqxllfABWfYlfAjYevPhXKipl95wUI,10432
65
65
  upgini/utils/postal_code_utils.py,sha256=5M0sUqH2DAr33kARWCTXR-ACyzWbjDq_-0mmEml6ZcU,1716
66
66
  upgini/utils/progress_bar.py,sha256=N-Sfdah2Hg8lXP_fV9EfUTXz_PyRt4lo9fAHoUDOoLc,1550
67
- upgini/utils/psi.py,sha256=gYeZ3FOwGriVUnuO3BbdicSgXGQqPU14f7yleyO55f0,10108
67
+ upgini/utils/psi.py,sha256=pLtECcCeco_WRqMjFnQvhUB4vHArjHtD5HzJFP9ICMc,10972
68
68
  upgini/utils/sample_utils.py,sha256=lZJ4yf9Jiq9Em2Ny9m3RIiF7WSxBPrc4E3xxn_8sQk8,15417
69
69
  upgini/utils/sklearn_ext.py,sha256=jLJWAKkqQinV15Z4y1ZnsN3c-fKFwXTsprs00COnyVU,49315
70
70
  upgini/utils/sort.py,sha256=8uuHs2nfSMVnz8GgvbOmgMB1PgEIZP1uhmeRFxcwnYw,7039
@@ -72,7 +72,7 @@ upgini/utils/target_utils.py,sha256=i3Xt5l9ybB2_nF_ma5cfPuL3OeFTs2dY2xDI0p4Azpg,
72
72
  upgini/utils/track_info.py,sha256=G5Lu1xxakg2_TQjKZk4b5SvrHsATTXNVV3NbvWtT8k8,5663
73
73
  upgini/utils/ts_utils.py,sha256=26vhC0pN7vLXK6R09EEkMK3Lwb9IVPH7LRdqFIQ3kPs,1383
74
74
  upgini/utils/warning_counter.py,sha256=-GRY8EUggEBKODPSuXAkHn9KnEQwAORC0mmz_tim-PM,254
75
- upgini-1.2.113a3.dist-info/METADATA,sha256=cyLDSMCN6fY7o3yXNpqMgPYG4TgY9CQNWA27O-ACBF8,49531
76
- upgini-1.2.113a3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
77
- upgini-1.2.113a3.dist-info/licenses/LICENSE,sha256=5RRzgvdJUu3BUDfv4bzVU6FqKgwHlIay63pPCSmSgzw,1514
78
- upgini-1.2.113a3.dist-info/RECORD,,
75
+ upgini-1.2.113a5.dist-info/METADATA,sha256=VOeoK4hhJyhb0OJWG2cgsN-hES6xe3QIRyZMovxP8ek,49531
76
+ upgini-1.2.113a5.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
77
+ upgini-1.2.113a5.dist-info/licenses/LICENSE,sha256=5RRzgvdJUu3BUDfv4bzVU6FqKgwHlIay63pPCSmSgzw,1514
78
+ upgini-1.2.113a5.dist-info/RECORD,,