upgini 1.2.166a4253.dev1__tar.gz → 1.2.166a4253.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/PKG-INFO +1 -1
  2. upgini-1.2.166a4253.dev2/src/upgini/__about__.py +1 -0
  3. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/features_enricher.py +116 -23
  4. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/metadata.py +4 -0
  5. upgini-1.2.166a4253.dev1/src/upgini/__about__.py +0 -1
  6. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/.gitignore +0 -0
  7. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/LICENSE +0 -0
  8. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/README.md +0 -0
  9. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/pyproject.toml +0 -0
  10. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/__init__.py +0 -0
  11. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/ads.py +0 -0
  12. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/ads_management/__init__.py +0 -0
  13. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/ads_management/ads_manager.py +0 -0
  14. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/__init__.py +0 -0
  15. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/all_operators.py +0 -0
  16. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/binary.py +0 -0
  17. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/date.py +0 -0
  18. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/feature.py +0 -0
  19. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/groupby.py +0 -0
  20. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/operand.py +0 -0
  21. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/operator.py +0 -0
  22. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/__init__.py +0 -0
  23. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/base.py +0 -0
  24. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/cross.py +0 -0
  25. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/delta.py +0 -0
  26. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/lag.py +0 -0
  27. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/numpy_kernels.py +0 -0
  28. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/roll.py +0 -0
  29. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/trend.py +0 -0
  30. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/timeseries/volatility.py +0 -0
  31. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/unary.py +0 -0
  32. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/utils.py +0 -0
  33. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/autofe/vector.py +0 -0
  34. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/data_source/__init__.py +0 -0
  35. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/data_source/data_source_publisher.py +0 -0
  36. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/dataset.py +0 -0
  37. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/errors.py +0 -0
  38. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/http.py +0 -0
  39. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/mdc/__init__.py +0 -0
  40. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/mdc/context.py +0 -0
  41. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/metrics.py +0 -0
  42. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/normalizer/__init__.py +0 -0
  43. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/normalizer/normalize_utils.py +0 -0
  44. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/resource_bundle/__init__.py +0 -0
  45. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/resource_bundle/exceptions.py +0 -0
  46. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/resource_bundle/strings.properties +0 -0
  47. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/resource_bundle/strings_widget.properties +0 -0
  48. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/sampler/__init__.py +0 -0
  49. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/sampler/base.py +0 -0
  50. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/sampler/random_under_sampler.py +0 -0
  51. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/sampler/utils.py +0 -0
  52. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/search_task.py +0 -0
  53. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/spinner.py +0 -0
  54. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/Roboto-Regular.ttf +0 -0
  55. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/__init__.py +0 -0
  56. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/base_search_key_detector.py +0 -0
  57. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/blocked_time_series.py +0 -0
  58. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/config.py +0 -0
  59. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/country_utils.py +0 -0
  60. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/cpu_utils.py +0 -0
  61. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/custom_loss_utils.py +0 -0
  62. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/cv_utils.py +0 -0
  63. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/datetime_utils.py +0 -0
  64. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/deduplicate_utils.py +0 -0
  65. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/display_utils.py +0 -0
  66. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/email_utils.py +0 -0
  67. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/fallback_progress_bar.py +0 -0
  68. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/feature_info.py +0 -0
  69. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/features_validator.py +0 -0
  70. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/format.py +0 -0
  71. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/hash_utils.py +0 -0
  72. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/ip_utils.py +0 -0
  73. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/mstats.py +0 -0
  74. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/one_hot_encoder.py +0 -0
  75. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/phone_utils.py +0 -0
  76. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/postal_code_utils.py +0 -0
  77. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/progress_bar.py +0 -0
  78. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/psi.py +0 -0
  79. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/pyarrow_utils.py +0 -0
  80. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/sample_utils.py +0 -0
  81. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/search_key_derivations.py +0 -0
  82. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/sklearn_ext.py +0 -0
  83. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/sort.py +0 -0
  84. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/target_utils.py +0 -0
  85. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/track_info.py +0 -0
  86. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/ts_utils.py +0 -0
  87. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/utils/warning_counter.py +0 -0
  88. {upgini-1.2.166a4253.dev1 → upgini-1.2.166a4253.dev2}/src/upgini/version_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: upgini
3
- Version: 1.2.166a4253.dev1
3
+ Version: 1.2.166a4253.dev2
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/
@@ -0,0 +1 @@
1
+ __version__ = "1.2.166.a4253.dev2"
@@ -32,6 +32,7 @@ from sklearn.preprocessing import OrdinalEncoder
32
32
 
33
33
  from upgini.autofe.feature import Feature
34
34
  from upgini.autofe.timeseries import TimeSeriesBase
35
+ from upgini.autofe.utils import pydantic_dump_method
35
36
  from upgini.autofe.vector import EnsembleModel
36
37
  from upgini.data_source.data_source_publisher import CommercialSchema
37
38
  from upgini.dataset import Dataset
@@ -275,6 +276,7 @@ class FeaturesEnricher(TransformerMixin):
275
276
 
276
277
  self.passed_features: list[str] = []
277
278
  self.df_with_original_index: pd.DataFrame | None = None
279
+ self._fit_match_search_keys: dict[str, SearchKey] | None = None
278
280
  self.fit_columns_renaming: dict[str, str] | None = None
279
281
  self.country_added = False
280
282
  self.fit_generated_features: list[str] = []
@@ -1759,10 +1761,12 @@ class FeaturesEnricher(TransformerMixin):
1759
1761
  rows_to_drop: pd.DataFrame | None = None,
1760
1762
  left_columns: list[str] | None = None,
1761
1763
  ) -> tuple[pd.DataFrame, list[str], dict[str, SearchKey]] | None:
1762
- if self.df_with_original_index is None or self._search_task is None:
1764
+ if self._search_task is None:
1763
1765
  return None
1764
1766
  if self.fit_columns_renaming is None or self.fit_search_keys is None:
1765
1767
  return None
1768
+ if self.df_with_original_index is None and not self.__has_persisted_match_hashes():
1769
+ return None
1766
1770
 
1767
1771
  fit_features = self._search_task.get_all_initial_raw_features(self._get_trace_id(), metrics_calculation=True)
1768
1772
  if fit_features is None or len(fit_features) == 0:
@@ -1775,6 +1779,12 @@ class FeaturesEnricher(TransformerMixin):
1775
1779
  ]
1776
1780
  self.logger.info(f"After dropping target outliers size: {len(fit_features)}")
1777
1781
 
1782
+ generated_features = [self.fit_columns_renaming.get(c, c) for c in (self.fit_generated_features or [])]
1783
+ search_keys = {self.fit_columns_renaming.get(k, k): v for k, v in self.fit_search_keys.items()}
1784
+
1785
+ if self.df_with_original_index is None:
1786
+ return fit_features.rename(columns=self.fit_columns_renaming), generated_features, search_keys
1787
+
1778
1788
  left = self.df_with_original_index
1779
1789
  if left_columns is not None:
1780
1790
  left = left.loc[:, left_columns]
@@ -1791,10 +1801,80 @@ class FeaturesEnricher(TransformerMixin):
1791
1801
  drop_system_record_id=False,
1792
1802
  )
1793
1803
  enriched_Xy = enriched_Xy.rename(columns=self.fit_columns_renaming)
1794
- generated_features = [self.fit_columns_renaming.get(c, c) for c in (self.fit_generated_features or [])]
1795
- search_keys = {self.fit_columns_renaming.get(k, k): v for k, v in self.fit_search_keys.items()}
1796
1804
  return enriched_Xy, generated_features, search_keys
1797
1805
 
1806
+ def __has_persisted_match_hashes(self) -> bool:
1807
+ if self.add_info is None:
1808
+ return False
1809
+ entity_ids = self.add_info.transform_match_entity_ids
1810
+ hashes = self.add_info.transform_match_hashes
1811
+ return bool(entity_ids) and bool(hashes) and len(entity_ids) == len(hashes)
1812
+
1813
+ def __reuse_match_columns(self, df: pd.DataFrame, search_keys: dict[str, SearchKey]) -> list[str] | None:
1814
+ if self._search_task is None:
1815
+ return None
1816
+ features_for_transform = [
1817
+ f
1818
+ for f in self._search_task.get_features_for_transform()
1819
+ if f not in get_derived_search_key_columns(search_keys)
1820
+ ]
1821
+ renaming = self.fit_columns_renaming or {}
1822
+ reverse = {v: k for k, v in renaming.items()}
1823
+ resolved: list[str] = []
1824
+ for feature in features_for_transform:
1825
+ col = feature if feature in df.columns else reverse.get(feature)
1826
+ if col is None or col not in df.columns:
1827
+ self.logger.info(f"Skip persisting transform match hashes: missing column {feature}")
1828
+ return None
1829
+ if col not in search_keys:
1830
+ resolved.append(col)
1831
+ match_cols = self.__transform_match_columns(search_keys, resolved)
1832
+ missing = [c for c in match_cols if c not in df.columns]
1833
+ if missing:
1834
+ self.logger.info(f"Skip persisting transform match hashes: missing columns {missing}")
1835
+ return None
1836
+ return match_cols
1837
+
1838
+ def __persist_transform_match_hashes(self) -> None:
1839
+ if self.add_info is None or self.df_with_original_index is None or self._search_task is None:
1840
+ return
1841
+ if ENTITY_SYSTEM_RECORD_ID not in self.df_with_original_index.columns:
1842
+ return
1843
+ match_cols = self.__reuse_match_columns(
1844
+ self.df_with_original_index,
1845
+ self._fit_match_search_keys or self.fit_search_keys or {},
1846
+ )
1847
+ if not match_cols:
1848
+ return
1849
+ match_hash = pd.util.hash_pandas_object(self.df_with_original_index[match_cols], index=False).astype("float64")
1850
+ pairs = pd.DataFrame(
1851
+ {
1852
+ ENTITY_SYSTEM_RECORD_ID: self.df_with_original_index[ENTITY_SYSTEM_RECORD_ID].to_numpy(),
1853
+ MATCH_HASH: match_hash.to_numpy(),
1854
+ }
1855
+ )
1856
+ pairs = self.__dedupe_on(pairs, [ENTITY_SYSTEM_RECORD_ID])
1857
+ pairs = self.__dedupe_on(pairs, [MATCH_HASH])
1858
+ self.add_info.transform_match_entity_ids = [float(v) for v in pairs[ENTITY_SYSTEM_RECORD_ID].to_numpy()]
1859
+ self.add_info.transform_match_hashes = [float(v) for v in pairs[MATCH_HASH].to_numpy()]
1860
+ try:
1861
+ payload = pydantic_dump_method(self.add_info)(exclude_none=True)
1862
+ payload.pop("autodetected_search_keys", None)
1863
+ self._search_task.update_add_info(self._get_trace_id(), payload)
1864
+ except Exception:
1865
+ self.logger.exception("Failed to persist transform match hashes in add_info")
1866
+
1867
+ def __persisted_hash_pairs(self) -> pd.DataFrame | None:
1868
+ if not self.__has_persisted_match_hashes():
1869
+ return None
1870
+ pairs = pd.DataFrame(
1871
+ {
1872
+ ENTITY_SYSTEM_RECORD_ID: self.add_info.transform_match_entity_ids,
1873
+ MATCH_HASH: self.add_info.transform_match_hashes,
1874
+ }
1875
+ )
1876
+ return self.__dedupe_on(pairs, [MATCH_HASH])
1877
+
1798
1878
  @staticmethod
1799
1879
  def __transform_match_columns(search_keys: dict[str, SearchKey], features_for_transform: list[str]) -> list[str]:
1800
1880
  return sorted(dict.fromkeys(list(search_keys.keys()) + list(features_for_transform)))
@@ -1831,41 +1911,50 @@ class FeaturesEnricher(TransformerMixin):
1831
1911
  feature_cols: list[str],
1832
1912
  enriched_Xy: pd.DataFrame,
1833
1913
  ) -> pd.DataFrame | None:
1834
- if self.df_with_original_index is None or enriched_Xy is None or len(enriched_Xy) == 0:
1914
+ if enriched_Xy is None or len(enriched_Xy) == 0:
1835
1915
  return None
1836
1916
  if not match_columns_hashed or not feature_cols:
1837
1917
  return None
1838
1918
  if ENTITY_SYSTEM_RECORD_ID not in enriched_Xy.columns:
1839
1919
  return None
1840
- if ENTITY_SYSTEM_RECORD_ID not in self.df_with_original_index.columns:
1841
- return None
1842
1920
 
1843
1921
  ads_cols = [c for c in feature_cols if c in enriched_Xy.columns]
1844
1922
  if not ads_cols:
1845
1923
  return None
1846
1924
 
1847
- key_cols = [c for c in match_columns_hashed if c in self.df_with_original_index.columns]
1848
- if len(key_cols) != len(match_columns_hashed):
1849
- return None
1850
-
1851
1925
  ads = self.__dedupe_on(enriched_Xy.loc[:, [ENTITY_SYSTEM_RECORD_ID] + ads_cols], [ENTITY_SYSTEM_RECORD_ID])
1852
1926
  covered_ids = ads[ENTITY_SYSTEM_RECORD_ID].to_numpy()
1853
1927
 
1854
- fit_keys = self.df_with_original_index.loc[:, key_cols + [ENTITY_SYSTEM_RECORD_ID]]
1855
- covered_mask = fit_keys[ENTITY_SYSTEM_RECORD_ID].isin(covered_ids)
1856
- if not covered_mask.all():
1857
- fit_keys = fit_keys.loc[covered_mask]
1858
- fit_keys = self.__dedupe_on(fit_keys, [ENTITY_SYSTEM_RECORD_ID])
1859
- if len(fit_keys) == 0:
1928
+ if self.df_with_original_index is not None and ENTITY_SYSTEM_RECORD_ID in self.df_with_original_index.columns:
1929
+ key_cols = [c for c in match_columns_hashed if c in self.df_with_original_index.columns]
1930
+ if len(key_cols) != len(match_columns_hashed):
1931
+ return None
1932
+ fit_keys = self.df_with_original_index.loc[:, key_cols + [ENTITY_SYSTEM_RECORD_ID]]
1933
+ covered_mask = fit_keys[ENTITY_SYSTEM_RECORD_ID].isin(covered_ids)
1934
+ if not covered_mask.all():
1935
+ fit_keys = fit_keys.loc[covered_mask]
1936
+ fit_keys = self.__dedupe_on(fit_keys, [ENTITY_SYSTEM_RECORD_ID])
1937
+ if len(fit_keys) == 0:
1938
+ return None
1939
+ aligned = self.__align_match_frame(fit_keys, df_transform, match_columns_hashed)
1940
+ if aligned is None:
1941
+ return None
1942
+ match_hash = pd.util.hash_pandas_object(aligned, index=False).astype("float64")
1943
+ lookup = ads.set_index(ENTITY_SYSTEM_RECORD_ID).reindex(fit_keys[ENTITY_SYSTEM_RECORD_ID].to_numpy())
1944
+ lookup[MATCH_HASH] = match_hash.to_numpy()
1945
+ lookup.reset_index(drop=True, inplace=True)
1946
+ return self.__dedupe_on(lookup, [MATCH_HASH])
1947
+
1948
+ pairs = self.__persisted_hash_pairs()
1949
+ if pairs is None:
1860
1950
  return None
1861
-
1862
- aligned = self.__align_match_frame(fit_keys, df_transform, match_columns_hashed)
1863
- if aligned is None:
1951
+ covered_mask = pairs[ENTITY_SYSTEM_RECORD_ID].isin(covered_ids)
1952
+ if not covered_mask.all():
1953
+ pairs = pairs.loc[covered_mask]
1954
+ if len(pairs) == 0:
1864
1955
  return None
1865
-
1866
- match_hash = pd.util.hash_pandas_object(aligned, index=False).astype("float64")
1867
- lookup = ads.set_index(ENTITY_SYSTEM_RECORD_ID).reindex(fit_keys[ENTITY_SYSTEM_RECORD_ID].to_numpy())
1868
- lookup[MATCH_HASH] = match_hash.to_numpy()
1956
+ lookup = ads.set_index(ENTITY_SYSTEM_RECORD_ID).reindex(pairs[ENTITY_SYSTEM_RECORD_ID].to_numpy())
1957
+ lookup[MATCH_HASH] = pairs[MATCH_HASH].to_numpy()
1869
1958
  lookup.reset_index(drop=True, inplace=True)
1870
1959
  return self.__dedupe_on(lookup, [MATCH_HASH])
1871
1960
 
@@ -4034,6 +4123,7 @@ if response.status_code == 200:
4034
4123
  self._search_task = None
4035
4124
  self.warning_counter.reset()
4036
4125
  self.df_with_original_index = None
4126
+ self._fit_match_search_keys = None
4037
4127
  self.__cached_sampled_datasets = dict()
4038
4128
  self.metrics = None
4039
4129
  self.fit_columns_renaming = None
@@ -4280,6 +4370,8 @@ if response.status_code == 200:
4280
4370
 
4281
4371
  # TODO check that this is correct for enrichment
4282
4372
  self.df_with_original_index = df.copy()
4373
+ # Search keys at this point are pre-explode; transform hashes the same grain.
4374
+ self._fit_match_search_keys = dict(self.fit_search_keys)
4283
4375
  # TODO check maybe need to drop _time column from df_with_original_index
4284
4376
 
4285
4377
  df, unnest_search_keys = self._explode_multiple_search_keys(df, self.fit_search_keys, self.fit_columns_renaming)
@@ -4443,6 +4535,7 @@ if response.status_code == 200:
4443
4535
  raise e
4444
4536
 
4445
4537
  self._search_task.poll_result(self._get_trace_id(), quiet=True)
4538
+ self.__persist_transform_match_hashes()
4446
4539
 
4447
4540
  seconds_left = time.time() - start_time
4448
4541
  progress = SearchProgress(97.0, ProgressStage.GENERATING_REPORT, seconds_left)
@@ -395,6 +395,10 @@ class AddInfo(BaseModel):
395
395
  true_one_hot_groups: Optional[Dict[str, List[str]]] = None
396
396
  pseudo_one_hot_groups: Optional[Dict[str, List[str]]] = None
397
397
  autodetected_search_keys: Optional[Dict[str, SearchKey]] = None
398
+ # Unique pre-explode match hashes for transform reuse after restore (search_id).
399
+ # Parallel lists: entity_system_record_id[i] <-> hash[i].
400
+ transform_match_entity_ids: Optional[List[float]] = None
401
+ transform_match_hashes: Optional[List[float]] = None
398
402
 
399
403
 
400
404
  class AdsHintType(str, Enum):
@@ -1 +0,0 @@
1
- __version__ = "1.2.166.a4253.dev1"