upgini 1.2.167a4220.dev8__tar.gz → 1.2.167a4220.dev9__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.
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/PKG-INFO +1 -1
- upgini-1.2.167a4220.dev9/src/upgini/__about__.py +1 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/features_enricher.py +55 -45
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/metadata.py +2 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/report/assemble.py +158 -36
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/report/data.py +6 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/report/html.py +19 -5
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/report/template.html +6 -3
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/search_task.py +25 -0
- upgini-1.2.167a4220.dev8/src/upgini/__about__.py +0 -1
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/.gitignore +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/LICENSE +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/README.md +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/pyproject.toml +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/ads.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/ads_management/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/ads_management/ads_manager.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/all_operators.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/binary.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/date.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/feature.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/groupby.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/operand.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/operator.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/base.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/cross.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/delta.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/lag.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/numpy_kernels.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/roll.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/trend.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/volatility.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/unary.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/vector.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/data_source/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/data_source/data_source_publisher.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/dataset.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/errors.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/http.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/mdc/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/mdc/context.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/metrics.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/normalizer/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/normalizer/normalize_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/report/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/report/stats.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/resource_bundle/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/resource_bundle/exceptions.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/resource_bundle/strings.properties +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/resource_bundle/strings_widget.properties +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/sampler/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/sampler/base.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/sampler/random_under_sampler.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/sampler/utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/spinner.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/Roboto-Regular.ttf +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/__init__.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/base_search_key_detector.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/blocked_time_series.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/config.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/country_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/cpu_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/custom_loss_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/cv_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/datetime_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/deduplicate_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/display_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/email_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/fallback_progress_bar.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/feature_info.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/features_validator.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/format.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/hash_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/ip_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/mstats.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/one_hot_encoder.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/phone_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/postal_code_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/progress_bar.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/psi.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/pyarrow_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/sample_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/search_key_derivations.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/sklearn_ext.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/sort.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/target_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/track_info.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/ts_utils.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/warning_counter.py +0 -0
- {upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/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.167a4220.
|
|
3
|
+
Version: 1.2.167a4220.dev9
|
|
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.167.a4220.dev9"
|
|
@@ -64,6 +64,7 @@ from upgini.metadata import (
|
|
|
64
64
|
FeaturesMetadataV2,
|
|
65
65
|
FileColumnMeaningType,
|
|
66
66
|
FileColumnMetadata,
|
|
67
|
+
GeneratedFeatureMetadata,
|
|
67
68
|
ModelTaskType,
|
|
68
69
|
RuntimeParameters,
|
|
69
70
|
SearchKey,
|
|
@@ -98,7 +99,13 @@ from upgini.utils.deduplicate_utils import (
|
|
|
98
99
|
clean_full_duplicates,
|
|
99
100
|
remove_fintech_duplicates,
|
|
100
101
|
)
|
|
101
|
-
from upgini.report.assemble import
|
|
102
|
+
from upgini.report.assemble import (
|
|
103
|
+
_generated_feature_names,
|
|
104
|
+
_parse_generated_feature,
|
|
105
|
+
assemble_report_data,
|
|
106
|
+
autofe_rows_from_description,
|
|
107
|
+
build_search_results,
|
|
108
|
+
)
|
|
102
109
|
from upgini.report.html import generate_html_report
|
|
103
110
|
from upgini.report.stats import make_report_frame
|
|
104
111
|
from upgini.utils.display_utils import (
|
|
@@ -2479,11 +2486,14 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2479
2486
|
generated_names=self._column_name_aliases(
|
|
2480
2487
|
self.fit_generated_features or [], self.fit_columns_renaming or {}
|
|
2481
2488
|
),
|
|
2489
|
+
generated_features=self._search_task.get_all_generated_features() if self._search_task else [],
|
|
2490
|
+
joined_ads_features_count=(
|
|
2491
|
+
self._search_task.get_joined_ads_features_count() if self._search_task else None
|
|
2492
|
+
),
|
|
2493
|
+
joined_ads_count=self._search_task.get_joined_ads_count() if self._search_task else None,
|
|
2482
2494
|
)
|
|
2483
|
-
autofe =
|
|
2495
|
+
autofe = autofe_rows_from_description(
|
|
2484
2496
|
self.get_autofe_features_description(features_meta=features_meta),
|
|
2485
|
-
features_meta,
|
|
2486
|
-
self._is_ensemble_feature,
|
|
2487
2497
|
self.bundle,
|
|
2488
2498
|
)
|
|
2489
2499
|
return features, sources, model_shap, summary, autofe
|
|
@@ -2503,15 +2513,29 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2503
2513
|
return False
|
|
2504
2514
|
return bool(aliases & self._column_name_aliases(list(ensemble_names), renaming))
|
|
2505
2515
|
|
|
2516
|
+
def _match_autofe_feature_meta(
|
|
2517
|
+
self,
|
|
2518
|
+
generated_meta: GeneratedFeatureMetadata,
|
|
2519
|
+
features_meta: list[FeaturesMetadataV2],
|
|
2520
|
+
used_names: set[str],
|
|
2521
|
+
) -> FeaturesMetadataV2 | None:
|
|
2522
|
+
available = [meta for meta in features_meta or [] if meta.name not in used_names]
|
|
2523
|
+
by_name = {meta.name: meta for meta in available}
|
|
2524
|
+
for name in _generated_feature_names(generated_meta):
|
|
2525
|
+
matched = by_name.get(name)
|
|
2526
|
+
if matched is not None:
|
|
2527
|
+
return matched
|
|
2528
|
+
return None
|
|
2529
|
+
|
|
2506
2530
|
def _ensemble_generated_metadata(self):
|
|
2507
2531
|
autofe_meta = self._search_task.get_autofe_metadata() if self._search_task else None
|
|
2508
2532
|
if not autofe_meta:
|
|
2509
2533
|
return []
|
|
2510
2534
|
matched = []
|
|
2511
2535
|
for meta in autofe_meta:
|
|
2512
|
-
|
|
2513
|
-
if
|
|
2514
|
-
matched.append((meta, op))
|
|
2536
|
+
feature = _parse_generated_feature(meta)
|
|
2537
|
+
if feature is not None and isinstance(feature.op, EnsembleModel):
|
|
2538
|
+
matched.append((meta, feature.op))
|
|
2515
2539
|
return matched
|
|
2516
2540
|
|
|
2517
2541
|
def _ensemble_feature_names_from_metadata(self) -> set[str]:
|
|
@@ -5864,6 +5888,7 @@ if response.status_code == 200:
|
|
|
5864
5888
|
|
|
5865
5889
|
selected_features_meta.sort(key=lambda m: (-m.shap_value, m.name))
|
|
5866
5890
|
psi_keepers = self._psi_stability_keepers() if self.psi_values is not None else set()
|
|
5891
|
+
single_ensemble = len(self._ensemble_generated_metadata()) == 1
|
|
5867
5892
|
|
|
5868
5893
|
for feature_meta in selected_features_meta:
|
|
5869
5894
|
original_name = original_names_dict.get(feature_meta.name, feature_meta.name)
|
|
@@ -5873,6 +5898,10 @@ if response.status_code == 200:
|
|
|
5873
5898
|
) or original_name in generated_aliases
|
|
5874
5899
|
is_client_feature = original_name in clients_features_df.columns and not is_generated_feature
|
|
5875
5900
|
|
|
5901
|
+
# Model-input ads/AutoFE rows are only for the HTML report; the selected output is the score.
|
|
5902
|
+
if single_ensemble and not is_client_feature and not self._is_ensemble_feature(feature_meta.name):
|
|
5903
|
+
continue
|
|
5904
|
+
|
|
5876
5905
|
if not is_client_feature and not is_generated_feature:
|
|
5877
5906
|
self.external_source_feature_names.append(original_name)
|
|
5878
5907
|
|
|
@@ -5887,8 +5916,6 @@ if response.status_code == 200:
|
|
|
5887
5916
|
if original_name in self.unstable_features or feature_meta.name in self.unstable_features:
|
|
5888
5917
|
continue
|
|
5889
5918
|
|
|
5890
|
-
# TODO make a decision about selected features based on special flag from mlb
|
|
5891
|
-
|
|
5892
5919
|
if original_shaps.get(feature_meta.name, 0.0) == 0.0 and not self._should_show_client_feature_in_report(
|
|
5893
5920
|
is_client_feature, feature_meta.name
|
|
5894
5921
|
):
|
|
@@ -5961,52 +5988,34 @@ if response.status_code == 200:
|
|
|
5961
5988
|
elif features_meta is None:
|
|
5962
5989
|
features_meta = self._search_task.get_all_features_metadata_v2()
|
|
5963
5990
|
|
|
5964
|
-
def get_feature_by_name(name: str):
|
|
5965
|
-
if not name or not features_meta:
|
|
5966
|
-
return None
|
|
5967
|
-
for candidate in features_meta:
|
|
5968
|
-
if candidate.name == name:
|
|
5969
|
-
return candidate
|
|
5970
|
-
return None
|
|
5971
|
-
|
|
5972
5991
|
descriptions = []
|
|
5992
|
+
used_names: set[str] = set()
|
|
5973
5993
|
for m in autofe_meta:
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5994
|
+
try:
|
|
5995
|
+
orig_to_hashed = {
|
|
5996
|
+
base_column.original_name: base_column.hashed_name for base_column in m.base_columns
|
|
5997
|
+
}
|
|
5998
|
+
autofe_feature = (
|
|
5999
|
+
Feature.from_formula(m.formula)
|
|
6000
|
+
.set_display_index(m.display_index or None)
|
|
6001
|
+
.set_alias(m.alias)
|
|
6002
|
+
.set_op_params(m.operator_params or {})
|
|
6003
|
+
.rename_columns(orig_to_hashed)
|
|
6004
|
+
)
|
|
6005
|
+
except Exception:
|
|
6006
|
+
self.logger.exception(f"Failed to parse AutoFE formula: {m.formula}")
|
|
6007
|
+
continue
|
|
5983
6008
|
|
|
5984
6009
|
is_ts = isinstance(autofe_feature.op, TimeSeriesBase)
|
|
5985
6010
|
|
|
5986
6011
|
if autofe_feature.op.is_vector and not is_ts:
|
|
5987
6012
|
continue
|
|
5988
6013
|
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
lookup_names = [autofe_feature.get_display_name(shorten=True, unhash=True)]
|
|
5992
|
-
if m.alias:
|
|
5993
|
-
lookup_names.extend([m.alias, f"f_autofe_{m.alias}"])
|
|
5994
|
-
feature_meta = None
|
|
5995
|
-
for lookup_name in lookup_names:
|
|
5996
|
-
feature_meta = get_feature_by_name(lookup_name)
|
|
5997
|
-
if feature_meta is not None:
|
|
5998
|
-
break
|
|
5999
|
-
if feature_meta is None and m.alias and features_meta:
|
|
6000
|
-
prefix = f"f_autofe_{m.alias}"
|
|
6001
|
-
matches = [
|
|
6002
|
-
candidate
|
|
6003
|
-
for candidate in features_meta
|
|
6004
|
-
if candidate.name == prefix or candidate.name.startswith(prefix + "_")
|
|
6005
|
-
]
|
|
6006
|
-
if len(matches) == 1:
|
|
6007
|
-
feature_meta = matches[0]
|
|
6014
|
+
feature_meta = self._match_autofe_feature_meta(m, features_meta, used_names)
|
|
6008
6015
|
if feature_meta is None or not feature_meta.shap_value:
|
|
6009
6016
|
continue
|
|
6017
|
+
|
|
6018
|
+
description = {}
|
|
6010
6019
|
description["shap"] = feature_meta.shap_value
|
|
6011
6020
|
source = feature_meta.data_source or ""
|
|
6012
6021
|
description[self.bundle.get("autofe_descriptions_sources")] = source.replace(
|
|
@@ -6028,6 +6037,7 @@ if response.status_code == 200:
|
|
|
6028
6037
|
sorted(autofe_feature.get_all_operand_names())
|
|
6029
6038
|
)
|
|
6030
6039
|
|
|
6040
|
+
used_names.add(feature_meta.name)
|
|
6031
6041
|
descriptions.append(description)
|
|
6032
6042
|
|
|
6033
6043
|
if len(descriptions) == 0:
|
|
@@ -346,6 +346,8 @@ class ProviderTaskMetadataV2(BaseModel):
|
|
|
346
346
|
zero_hit_rate_search_keys: Optional[List[str]] = None
|
|
347
347
|
features_used_for_embeddings: Optional[List[str]] = None
|
|
348
348
|
shuffle_kfold: Optional[bool] = None
|
|
349
|
+
joined_ads_features_count: Optional[int] = None
|
|
350
|
+
joined_ads_count: Optional[int] = None
|
|
349
351
|
generated_features: Optional[List[GeneratedFeatureMetadata]] = None
|
|
350
352
|
|
|
351
353
|
|
|
@@ -7,7 +7,9 @@ from typing import Callable, Optional
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
|
|
9
9
|
from upgini.__about__ import __version__
|
|
10
|
-
from upgini.
|
|
10
|
+
from upgini.autofe.feature import Feature
|
|
11
|
+
from upgini.autofe.vector import CatboostModel, EnsembleModel, OnnxModel
|
|
12
|
+
from upgini.metadata import BaseColumnMetadata, FeaturesMetadataV2, GeneratedFeatureMetadata
|
|
11
13
|
from upgini.report.data import (
|
|
12
14
|
FeatureRow,
|
|
13
15
|
ModelFeatureShap,
|
|
@@ -101,6 +103,9 @@ def build_search_results(
|
|
|
101
103
|
features_meta: list[FeaturesMetadataV2],
|
|
102
104
|
is_ensemble: Callable[[str], bool],
|
|
103
105
|
generated_names: Optional[set[str]] = None,
|
|
106
|
+
generated_features: Optional[list[GeneratedFeatureMetadata]] = None,
|
|
107
|
+
joined_ads_features_count: Optional[int] = None,
|
|
108
|
+
joined_ads_count: Optional[int] = None,
|
|
104
109
|
) -> tuple[list[FeatureRow], list[SourceRow], list[ModelFeatureShap], SearchResultsSummary]:
|
|
105
110
|
generated_names = generated_names or set()
|
|
106
111
|
model_rows: list[FeatureRow] = []
|
|
@@ -123,44 +128,19 @@ def build_search_results(
|
|
|
123
128
|
for index, row in enumerate(model_rows, start=1)
|
|
124
129
|
]
|
|
125
130
|
sources = _source_rows(found_rows)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
summary = _overview_summary(
|
|
132
|
+
features_meta=features_meta,
|
|
133
|
+
model_rows=model_rows,
|
|
134
|
+
found_rows=found_rows,
|
|
135
|
+
sources=sources,
|
|
136
|
+
is_ensemble=is_ensemble,
|
|
137
|
+
generated_features=generated_features or [],
|
|
138
|
+
joined_ads_features_count=joined_ads_features_count,
|
|
139
|
+
joined_ads_count=joined_ads_count,
|
|
135
140
|
)
|
|
136
141
|
return model_rows, sources, model_shap, summary
|
|
137
142
|
|
|
138
143
|
|
|
139
|
-
def autofe_rows_for_report(
|
|
140
|
-
df: Optional[pd.DataFrame],
|
|
141
|
-
features_meta: list[FeaturesMetadataV2],
|
|
142
|
-
is_ensemble: Callable[[str], bool],
|
|
143
|
-
bundle: ResourceBundle,
|
|
144
|
-
) -> list[dict[str, str]]:
|
|
145
|
-
rows = autofe_rows_from_description(df, bundle)
|
|
146
|
-
listed = {row["generatedFeature"] for row in rows if row.get("generatedFeature")}
|
|
147
|
-
for meta in features_meta:
|
|
148
|
-
if is_ensemble(meta.name) or meta.source != "generated":
|
|
149
|
-
continue
|
|
150
|
-
if not _has_nonzero_shap_value(meta.shap_value) or meta.name in listed:
|
|
151
|
-
continue
|
|
152
|
-
row = _feature_row(meta.name, meta)
|
|
153
|
-
rows.append(
|
|
154
|
-
{
|
|
155
|
-
"sources": row.source,
|
|
156
|
-
"generatedFeature": row.name,
|
|
157
|
-
"sourceFeatures": "",
|
|
158
|
-
"functions": "",
|
|
159
|
-
}
|
|
160
|
-
)
|
|
161
|
-
return rows
|
|
162
|
-
|
|
163
|
-
|
|
164
144
|
def autofe_rows_from_description(df: Optional[pd.DataFrame], bundle: ResourceBundle) -> list[dict[str, str]]:
|
|
165
145
|
if df is None or df.empty:
|
|
166
146
|
return []
|
|
@@ -275,11 +255,20 @@ def _feature_row(
|
|
|
275
255
|
drift=meta.drift_score,
|
|
276
256
|
coverage=meta.hit_rate,
|
|
277
257
|
provider=info.internal_provider,
|
|
278
|
-
source=info.internal_source,
|
|
258
|
+
source=_feature_source(meta, is_generated, info.internal_source),
|
|
279
259
|
stability_status=_psi_status(meta.psi_value),
|
|
280
260
|
)
|
|
281
261
|
|
|
282
262
|
|
|
263
|
+
def _feature_source(meta: FeaturesMetadataV2, is_generated: bool, fallback: str) -> str:
|
|
264
|
+
if is_generated:
|
|
265
|
+
if meta.data_source:
|
|
266
|
+
return meta.data_source
|
|
267
|
+
if meta.data_sources:
|
|
268
|
+
return ", ".join(meta.data_sources)
|
|
269
|
+
return fallback
|
|
270
|
+
|
|
271
|
+
|
|
283
272
|
def _source_rows(rows: list[FeatureRow]) -> list[SourceRow]:
|
|
284
273
|
grouped: dict[tuple[str, str], list[FeatureRow]] = {}
|
|
285
274
|
for row in rows:
|
|
@@ -311,3 +300,136 @@ def _psi_status(psi: Optional[float]) -> Optional[str]:
|
|
|
311
300
|
|
|
312
301
|
def _has_nonzero_shap_value(shap: Optional[float]) -> bool:
|
|
313
302
|
return shap is not None and not pd.isna(shap) and shap != 0
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _overview_summary(
|
|
306
|
+
*,
|
|
307
|
+
features_meta: list[FeaturesMetadataV2],
|
|
308
|
+
model_rows: list[FeatureRow],
|
|
309
|
+
found_rows: list[FeatureRow],
|
|
310
|
+
sources: list[SourceRow],
|
|
311
|
+
is_ensemble: Callable[[str], bool],
|
|
312
|
+
generated_features: list[GeneratedFeatureMetadata],
|
|
313
|
+
joined_ads_features_count: Optional[int],
|
|
314
|
+
joined_ads_count: Optional[int],
|
|
315
|
+
) -> SearchResultsSummary:
|
|
316
|
+
top_level = _unique_fold_model_columns(generated_features)
|
|
317
|
+
features_by_name = {meta.name: meta for meta in features_meta}
|
|
318
|
+
autofe_by_name = _autofe_generated_by_name(generated_features, is_ensemble)
|
|
319
|
+
if top_level:
|
|
320
|
+
counts = {"external": 0, "original": 0, "autofe": 0}
|
|
321
|
+
ads_ids: set[str] = set()
|
|
322
|
+
psis: list[Optional[float]] = []
|
|
323
|
+
for column in top_level:
|
|
324
|
+
kind = _top_level_kind(column, features_by_name, autofe_by_name, is_ensemble)
|
|
325
|
+
counts[kind] += 1
|
|
326
|
+
ads_ids.update(_leaf_ads_ids(column, autofe_by_name))
|
|
327
|
+
meta = features_by_name.get(column.hashed_name) or features_by_name.get(column.original_name)
|
|
328
|
+
psis.append(None if meta is None else meta.psi_value)
|
|
329
|
+
model_features = len(top_level)
|
|
330
|
+
external, original, autofe = counts["external"], counts["original"], counts["autofe"]
|
|
331
|
+
contributed = len(ads_ids)
|
|
332
|
+
else:
|
|
333
|
+
model_features = len(model_rows) or None
|
|
334
|
+
external = original = autofe = None
|
|
335
|
+
contributed = len({(row.provider, row.source) for row in found_rows if row.provider})
|
|
336
|
+
psis = [row.psi for row in model_rows]
|
|
337
|
+
share, stable, psi_count = _stability_from_psi(psis)
|
|
338
|
+
return SearchResultsSummary(
|
|
339
|
+
relevant_features=len(found_rows),
|
|
340
|
+
features_found=joined_ads_features_count if joined_ads_features_count is not None else len(found_rows),
|
|
341
|
+
model_features=model_features,
|
|
342
|
+
external_features=external,
|
|
343
|
+
original_features=original,
|
|
344
|
+
autofe_features=autofe,
|
|
345
|
+
data_sources=len(sources),
|
|
346
|
+
joined_sources=joined_ads_count if joined_ads_count is not None else len(sources),
|
|
347
|
+
stable_features_share=share,
|
|
348
|
+
contributed_sources=contributed,
|
|
349
|
+
stable_features=stable,
|
|
350
|
+
psi_features=psi_count,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _unique_fold_model_columns(generated_features: list[GeneratedFeatureMetadata]) -> list[BaseColumnMetadata]:
|
|
355
|
+
unique: dict[str, BaseColumnMetadata] = {}
|
|
356
|
+
for meta in generated_features:
|
|
357
|
+
feature = _parse_generated_feature(meta)
|
|
358
|
+
if feature is None or not isinstance(feature.op, (CatboostModel, OnnxModel)):
|
|
359
|
+
continue
|
|
360
|
+
for column in meta.base_columns:
|
|
361
|
+
unique.setdefault(column.hashed_name or column.original_name, column)
|
|
362
|
+
return list(unique.values())
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _autofe_generated_by_name(
|
|
366
|
+
generated_features: list[GeneratedFeatureMetadata], is_ensemble: Callable[[str], bool]
|
|
367
|
+
) -> dict[str, GeneratedFeatureMetadata]:
|
|
368
|
+
by_name: dict[str, GeneratedFeatureMetadata] = {}
|
|
369
|
+
for meta in generated_features:
|
|
370
|
+
feature = _parse_generated_feature(meta)
|
|
371
|
+
if feature is None or isinstance(feature.op, (CatboostModel, EnsembleModel, OnnxModel)):
|
|
372
|
+
continue
|
|
373
|
+
name = feature.get_display_name(shorten=True, unhash=True, cache=False)
|
|
374
|
+
if name and not is_ensemble(name):
|
|
375
|
+
by_name.setdefault(name, meta)
|
|
376
|
+
return by_name
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _parse_generated_feature(meta: GeneratedFeatureMetadata) -> Feature | None:
|
|
380
|
+
try:
|
|
381
|
+
parsed = Feature.from_formula(meta.formula)
|
|
382
|
+
if not isinstance(parsed, Feature):
|
|
383
|
+
return None
|
|
384
|
+
orig_to_hashed = {column.original_name: column.hashed_name for column in meta.base_columns}
|
|
385
|
+
return (
|
|
386
|
+
parsed.set_display_index(meta.display_index or None)
|
|
387
|
+
.set_alias(meta.alias)
|
|
388
|
+
.set_op_params(meta.operator_params or {})
|
|
389
|
+
.rename_columns(orig_to_hashed)
|
|
390
|
+
)
|
|
391
|
+
except Exception:
|
|
392
|
+
return None
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _generated_feature_names(meta: GeneratedFeatureMetadata) -> set[str]:
|
|
396
|
+
feature = _parse_generated_feature(meta)
|
|
397
|
+
if feature is None:
|
|
398
|
+
return set()
|
|
399
|
+
return {feature.get_display_name(shorten=True, unhash=True, cache=False)}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _top_level_kind(
|
|
403
|
+
column: BaseColumnMetadata,
|
|
404
|
+
features_by_name: dict[str, FeaturesMetadataV2],
|
|
405
|
+
autofe_by_name: dict[str, GeneratedFeatureMetadata],
|
|
406
|
+
is_ensemble: Callable[[str], bool],
|
|
407
|
+
) -> str:
|
|
408
|
+
names = [column.hashed_name, column.original_name]
|
|
409
|
+
if any(name in autofe_by_name for name in names if name):
|
|
410
|
+
return "autofe"
|
|
411
|
+
meta = next((features_by_name[name] for name in names if name in features_by_name), None)
|
|
412
|
+
if meta is not None and not is_ensemble(meta.name):
|
|
413
|
+
if meta.source == "generated":
|
|
414
|
+
return "autofe"
|
|
415
|
+
if meta.source == "ads":
|
|
416
|
+
return "external"
|
|
417
|
+
if meta.source == "etalon":
|
|
418
|
+
return "original"
|
|
419
|
+
if column.ads_definition_id:
|
|
420
|
+
return "external"
|
|
421
|
+
return "original"
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _leaf_ads_ids(column: BaseColumnMetadata, autofe_by_name: dict[str, GeneratedFeatureMetadata]) -> set[str]:
|
|
425
|
+
autofe = autofe_by_name.get(column.hashed_name) or autofe_by_name.get(column.original_name)
|
|
426
|
+
leaves = autofe.base_columns if autofe is not None else [column]
|
|
427
|
+
return {leaf.ads_definition_id for leaf in leaves if leaf.ads_definition_id}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _stability_from_psi(psis: list[Optional[float]]) -> tuple[Optional[float], Optional[int], Optional[int]]:
|
|
431
|
+
values = [psi for psi in psis if psi is not None and not pd.isna(psi)]
|
|
432
|
+
if not values:
|
|
433
|
+
return None, None, None
|
|
434
|
+
stable = sum(1 for psi in values if psi < _STABILITY_PSI)
|
|
435
|
+
return stable / len(values), stable, len(values)
|
|
@@ -66,11 +66,17 @@ class SourceRow:
|
|
|
66
66
|
@dataclass
|
|
67
67
|
class SearchResultsSummary:
|
|
68
68
|
relevant_features: Optional[int] = None
|
|
69
|
+
features_found: Optional[int] = None
|
|
69
70
|
model_features: Optional[int] = None
|
|
71
|
+
external_features: Optional[int] = None
|
|
72
|
+
original_features: Optional[int] = None
|
|
73
|
+
autofe_features: Optional[int] = None
|
|
70
74
|
data_sources: Optional[int] = None
|
|
75
|
+
joined_sources: Optional[int] = None
|
|
71
76
|
stable_features_share: Optional[float] = None
|
|
72
77
|
contributed_sources: Optional[int] = None
|
|
73
78
|
stable_features: Optional[int] = None
|
|
79
|
+
psi_features: Optional[int] = None
|
|
74
80
|
|
|
75
81
|
|
|
76
82
|
@dataclass
|
|
@@ -145,16 +145,30 @@ def _summary_cards(summary: SearchResultsSummary) -> list[dict]:
|
|
|
145
145
|
if summary.contributed_sources is not None:
|
|
146
146
|
sources_caption = f"{summary.contributed_sources} contributed"
|
|
147
147
|
stability_caption = ""
|
|
148
|
-
if summary.stable_features is not None and summary.
|
|
149
|
-
stability_caption = f"{summary.stable_features} of {summary.
|
|
148
|
+
if summary.stable_features is not None and summary.psi_features is not None:
|
|
149
|
+
stability_caption = f"{summary.stable_features} of {summary.psi_features} are stable"
|
|
150
|
+
features_found = summary.features_found if summary.features_found is not None else summary.relevant_features
|
|
151
|
+
data_sources = summary.joined_sources if summary.joined_sources is not None else summary.data_sources
|
|
150
152
|
return [
|
|
151
|
-
{"label": "Features found", "value": _dash(
|
|
152
|
-
{"label": "Used in model", "value": _dash(summary.model_features), "caption":
|
|
153
|
-
{"label": "Data sources", "value": _dash(
|
|
153
|
+
{"label": "Features found", "value": _dash(features_found), "caption": ""},
|
|
154
|
+
{"label": "Used in model", "value": _dash(summary.model_features), "caption": _used_in_model_caption(summary)},
|
|
155
|
+
{"label": "Data sources", "value": _dash(data_sources), "caption": sources_caption},
|
|
154
156
|
{"label": "Stability", "value": _dash(stability), "caption": stability_caption},
|
|
155
157
|
]
|
|
156
158
|
|
|
157
159
|
|
|
160
|
+
def _used_in_model_caption(summary: SearchResultsSummary) -> str:
|
|
161
|
+
if summary.external_features is None and summary.original_features is None and summary.autofe_features is None:
|
|
162
|
+
return ""
|
|
163
|
+
return " · ".join(
|
|
164
|
+
[
|
|
165
|
+
f"{summary.external_features or 0} external",
|
|
166
|
+
f"{summary.original_features or 0} original",
|
|
167
|
+
f"{summary.autofe_features or 0} AutoFE",
|
|
168
|
+
]
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
158
172
|
def _features_payload(features: list[FeatureRow]) -> list[dict]:
|
|
159
173
|
return [
|
|
160
174
|
{
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<style>
|
|
8
8
|
:root{--ink:#15211f;--muted:#6f7976;--line:#dce4e1;--paper:#fff;--bg:#f3f6f5;--blue:#1645ee;--green:#53cc61;--cyan:#20aab4;--warn:#e19132;--danger:#d45b5b;--shadow:0 18px 60px rgba(18,45,39,.08)}
|
|
9
9
|
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--bg);color:var(--ink);font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;line-height:1.45}.shell{max-width:1440px;margin:auto;min-height:100vh}.topbar{height:72px;padding:0 42px;display:flex;align-items:center;gap:25px;background:rgba(255,255,255,.92);backdrop-filter:blur(16px);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20}.brand{display:flex;align-items:center;gap:14px}.wordmark{font-size:25px;font-weight:800;letter-spacing:-.07em;line-height:1;background:linear-gradient(135deg,#0138f8,#5bd357);-webkit-background-clip:text;background-clip:text;color:transparent}.wordmark b{font-weight:850}.wordmark span{font-weight:650}.wordmark.small{font-size:20px;min-width:66px}.brand-divider{width:1px;height:25px;background:var(--line)}.brand-label{font-weight:700;color:#45514f}.top-meta{display:flex;gap:8px;margin-left:auto;align-items:center}.chip{background:#eef2f1;border:1px solid #e0e7e5;border-radius:999px;padding:7px 11px;color:#53605d;font-size:12px}.button{border:0;border-radius:10px;padding:10px 15px;font:inherit;font-weight:700;cursor:pointer;transition:.18s ease}.button:hover{transform:translateY(-1px)}.button.primary{background:var(--ink);color:white}.button.ghost{background:white;border:1px solid var(--line);color:var(--ink)}.layout{display:grid;grid-template-columns:210px minmax(0,1fr);gap:0}.sidenav{position:sticky;top:72px;height:calc(100vh - 72px);padding:30px 22px;border-right:1px solid var(--line);background:#f7f9f8}.nav-title{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:#98a29f;margin:0 10px 12px}.nav-link{display:flex;align-items:center;gap:10px;color:#66716f;text-decoration:none;padding:10px;border-radius:9px;margin:2px 0;font-weight:600}.nav-link:hover,.nav-link.active{background:white;color:var(--ink);box-shadow:0 4px 18px rgba(25,49,43,.06)}.nav-dot{width:7px;height:7px;border-radius:50%;background:#c4cecb}.nav-link.active .nav-dot{background:linear-gradient(135deg,var(--blue),var(--green))}.run-note{position:absolute;bottom:24px;left:22px;right:22px;padding:14px;background:white;border:1px solid var(--line);border-radius:12px;font-size:11px;color:var(--muted)}.run-note strong{display:block;color:var(--ink);font-size:12px;margin-bottom:4px}.main{min-width:0;padding:38px 44px 80px}.eyebrow{display:flex;align-items:center;gap:8px;text-transform:uppercase;letter-spacing:.12em;color:#74807d;font-size:11px;font-weight:800}.status-dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 0 4px #e7f8e9}.hero-title{display:flex;justify-content:space-between;align-items:flex-end;gap:30px;margin:12px 0 28px}.hero-title h1{font-size:36px;line-height:1.08;letter-spacing:-.04em;margin:0;max-width:720px}.hero-title p{color:var(--muted);margin:10px 0 0;max-width:690px}.run-stamp{text-align:right;min-width:190px;color:var(--muted);font-size:12px}.run-stamp strong{display:block;color:var(--ink);font-size:14px;margin-bottom:3px}.metric-hero{position:relative;overflow:hidden;background:#172421;color:white;border-radius:22px;padding:28px 30px;box-shadow:var(--shadow);margin-bottom:18px}.metric-hero:after{content:"";position:absolute;width:400px;height:400px;right:-150px;top:-250px;border-radius:50%;background:radial-gradient(circle,rgba(71,215,117,.24),transparent 68%)}.metric-head{position:relative;z-index:1;display:flex;justify-content:space-between;gap:20px}.metric-tabs{display:flex;gap:5px;background:rgba(255,255,255,.08);border-radius:11px;padding:4px}.metric-tab{border:0;background:transparent;color:#aebbb7;padding:8px 12px;border-radius:8px;font-weight:700;cursor:pointer}.metric-tab.active{background:white;color:#13201e}.scores{position:relative;z-index:1;display:grid;grid-template-columns:1fr 1fr 1.2fr;gap:25px;margin-top:32px;align-items:end}.score-label{text-transform:uppercase;letter-spacing:.11em;color:#8fa09b;font-size:10px;font-weight:800}.score-value{font-size:39px;line-height:1;margin-top:8px;font-variant-numeric:tabular-nums;letter-spacing:-.04em}.uplift .score-value{color:#73e57b}.uplift p{color:#aebcb8;margin:8px 0 0;font-size:12px}.summary-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:18px 0 34px}.summary{background:white;border:1px solid var(--line);border-radius:15px;padding:17px 18px}.summary .k{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.09em;font-weight:800}.summary .v{font-size:23px;font-weight:750;margin-top:5px;letter-spacing:-.02em}.summary .d{font-size:11px;color:#88928f;margin-top:4px}.section{scroll-margin-top:94px;margin-top:36px}.section-head{display:flex;align-items:end;justify-content:space-between;gap:20px;margin-bottom:15px}.section-head h2{font-size:22px;letter-spacing:-.025em;margin:0}.section-head p{color:var(--muted);margin:5px 0 0;font-size:12px}.panel{background:white;border:1px solid var(--line);border-radius:17px;box-shadow:0 4px 20px rgba(18,45,39,.035)}.chart-panel{padding:20px}.chart-head{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:10px}.legend{display:flex;gap:17px;flex-wrap:wrap;font-size:11px;color:var(--muted)}.legend i{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px}.control{border:1px solid var(--line);background:#fafcfb;color:var(--ink);border-radius:9px;padding:8px 10px;font:inherit;font-size:12px}.canvas-wrap{height:280px;position:relative}canvas{width:100%;height:100%}.callout{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;background:#fff8ed;border:1px solid #f1dcc0;border-radius:13px;color:#6b4b20}.callout strong{display:block;color:#4c361a;margin-bottom:2px}.callout-icon{width:27px;height:27px;display:grid;place-items:center;background:#f4c77f;border-radius:8px;font-weight:900;color:#65410c;flex:none}.stability-grid{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(260px,.5fr);gap:14px}.heatmap{overflow:auto;padding:16px}.heat-table{border-collapse:separate;border-spacing:5px;width:100%;font-size:11px}.heat-table th{color:var(--muted);font-weight:600;padding:2px 6px;white-space:nowrap}.heat-table th:first-child{text-align:left}.heat-table td:first-child{font-weight:700;padding-right:12px;white-space:nowrap}.heat{height:30px;min-width:47px;text-align:center;border-radius:6px;font-weight:750}.s1{background:#dbf4de;color:#276b2f}.s2{background:#e9f4cf;color:#526826}.s3{background:#fff0c7;color:#7f641b}.s4{background:#f8d5c8;color:#914634}.stability-side{padding:20px}.donut{width:140px;height:140px;border-radius:50%;margin:8px auto 17px;background:conic-gradient(var(--green) 0 72%,#e6b556 72% 91%,#df7474 91%);position:relative}.donut:after{content:"";position:absolute;inset:25px;background:white;border-radius:50%}.donut-label{position:absolute;inset:0;z-index:1;display:grid;place-items:center;text-align:center;font-weight:800;font-size:21px}.donut-label small{display:block;color:var(--muted);font-size:10px;font-weight:600}.stat-list{display:grid;gap:8px}.stat-row{display:flex;justify-content:space-between;padding-top:8px;border-top:1px solid #edf1ef;color:var(--muted);font-size:12px}.stat-row b{color:var(--ink)}.toolbar{display:flex;gap:9px;flex-wrap:wrap}.search{min-width:230px;border:1px solid var(--line);background:white;border-radius:9px;padding:9px 11px;font:inherit}.table-wrap{overflow:auto}.data-table{width:100%;border-collapse:collapse;min-width:860px}.data-table th{text-align:left;padding:12px 14px;background:#f8faf9;color:#77827f;text-transform:uppercase;letter-spacing:.07em;font-size:10px;border-bottom:1px solid var(--line)}.data-table td{padding:13px 14px;border-bottom:1px solid #edf1ef;font-size:12px}.data-table tr:last-child td{border-bottom:0}.data-table tbody tr:hover{background:#f8fbfa}.feature-name{font-weight:750;color:#25312f}.source-name{color:#74807d}.bar{height:6px;width:90px;background:#edf1ef;border-radius:99px;overflow:hidden}.bar i{height:100%;display:block;background:linear-gradient(90deg,var(--blue),#43c878);border-radius:99px}.tag{display:inline-flex;align-items:center;border-radius:999px;padding:4px 8px;font-size:10px;font-weight:750}.tag.good{background:#e8f7ea;color:#337139}.tag.watch{background:#fff1d9;color:#855d18}.tag.risk{background:#fde7e4;color:#984a40}.source-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.source-card{padding:19px}.source-top{display:flex;justify-content:space-between;gap:12px}.source-icon{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;background:linear-gradient(135deg,#e8eeff,#e8f9e8);font-weight:850;color:var(--blue)}.source-card h3{font-size:14px;margin:12px 0 4px}.source-card p{font-size:11px;color:var(--muted);margin:0}.source-metrics{display:flex;gap:18px;margin-top:17px;padding-top:13px;border-top:1px solid #edf1ef}.source-metrics b{display:block;font-size:16px}.source-metrics span{color:var(--muted);font-size:10px}.notebook{padding:0;overflow:hidden}.cell-head{background:#f8faf9;border-bottom:1px solid var(--line);padding:10px 14px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:#68736f;font-size:11px}.cell-output{padding:18px;display:flex;align-items:center;gap:25px;flex-wrap:wrap}.cell-quality{display:flex;gap:24px}.cell-quality span{font-size:11px;color:var(--muted)}.cell-quality b{font-size:17px;color:var(--ink);display:block}.footer{text-align:center;color:#8a9592;font-size:11px;padding:40px 0 10px}.empty{display:none;padding:24px;text-align:center;color:var(--muted)}
|
|
10
|
-
.sample-table{min-width:700px}.toggle-legend{margin-bottom:10px}.series-toggle{border:0;background:transparent;color:var(--muted);cursor:pointer;font:inherit;font-size:11px;padding:5px 7px;border-radius:6px}.series-toggle:not(.active){opacity:.35;text-decoration:line-through}.threshold-legend{display:flex;gap:8px;justify-content:flex-end;margin-bottom:10px}.shap-panel{padding:16px}.shap-row{display:grid;grid-template-columns:minmax(170px,1fr) 2fr 60px;gap:12px;align-items:center;padding:9px 4px;border-bottom:1px solid #edf1ef}.shap-row:last-child{border:0}.provider{font-size:9px;margin-left:6px;padding:3px
|
|
10
|
+
.sample-table{min-width:700px}.toggle-legend{margin-bottom:10px}.series-toggle{border:0;background:transparent;color:var(--muted);cursor:pointer;font:inherit;font-size:11px;padding:5px 7px;border-radius:6px}.series-toggle:not(.active){opacity:.35;text-decoration:line-through}.threshold-legend{display:flex;gap:8px;justify-content:flex-end;margin-bottom:10px}.shap-panel{padding:16px}.shap-row{display:grid;grid-template-columns:minmax(170px,1fr) 2fr 60px;gap:12px;align-items:center;padding:9px 4px;border-bottom:1px solid #edf1ef}.shap-row:last-child{border:0}.provider{display:inline-block;font-size:9px;margin-left:6px;padding:3px 7px;border-radius:8px;border:1px solid var(--line);color:var(--muted);line-height:1.3;text-align:center;vertical-align:middle;white-space:normal}.shap-track{height:10px;background:#edf1ef;border-radius:99px;overflow:hidden}.shap-fill{display:block;height:100%;border-radius:99px}.shap-fill.upgini{background:#1645ee}.shap-fill.user{background:#20aab4}.shap-fill.autofe{background:#53cc61}.shap-fill.external{background:#7b5cff}.result-tabs{display:flex;gap:4px;margin-bottom:12px}.result-tab{border:1px solid var(--line);background:#eef2f1;padding:9px 13px;border-radius:9px;font:inherit;cursor:pointer;color:var(--muted)}.result-tab.active{background:var(--ink);color:#fff}.result-tab b{font-size:10px;margin-left:5px}.result-pane{display:none}.result-pane.active{display:block}.table-actions{display:flex;gap:8px;justify-content:flex-end;flex-wrap:wrap;margin-bottom:10px}.chart-tooltip{position:absolute;display:none;pointer-events:none;background:#172421;color:white;padding:9px 11px;border-radius:8px;font-size:10px;box-shadow:var(--shadow);white-space:nowrap}.sidenav{overflow:auto}.run-note{position:static;margin-top:22px}.nav-link{font-size:12px;padding:8px 10px}
|
|
11
11
|
.metric-sample-table{position:relative;z-index:1;margin-top:26px}.metric-sample-head,.metric-sample-row{display:grid;grid-template-columns:minmax(140px,.9fr) 1fr 1fr .8fr .8fr;gap:18px;align-items:center}.metric-sample-ci{display:block;margin-top:6px;color:#8fa09b;font-size:11px;font-weight:600;font-variant-numeric:tabular-nums}.metric-note{color:#8fa09b;font-size:11px;margin-top:6px}.partner-logo{height:30px;width:auto;display:block}.metric-sample-head{padding:0 14px 8px;color:#8fa09b;text-transform:uppercase;letter-spacing:.11em;font-size:10px;font-weight:800}.metric-sample-row{padding:15px 14px;border-top:1px solid rgba(255,255,255,.12)}.metric-sample-name{display:flex;align-items:center;gap:10px;font-size:16px;font-weight:760}.metric-sample-name i{width:9px;height:9px;border-radius:50%;background:var(--blue);box-shadow:0 0 0 4px rgba(22,69,238,.18)}.metric-sample-row[data-sample="eval_1"] .metric-sample-name i{background:var(--cyan);box-shadow:0 0 0 4px rgba(32,170,180,.18)}.metric-sample-row[data-sample="eval_2"] .metric-sample-name i{background:var(--green);box-shadow:0 0 0 4px rgba(83,204,97,.18)}.metric-sample-name small{display:block;color:#8fa09b;font-size:10px;font-weight:650;margin-top:2px}.metric-sample-value{font-size:27px;line-height:1;font-variant-numeric:tabular-nums;letter-spacing:-.035em}.metric-sample-uplift .metric-sample-value{color:#73e57b}.metric-sample-uplift p{margin:5px 0 0;color:#aebcb8;font-size:11px}.tall-chart{height:340px}.chart-tooltip b{font-size:11px}.chart-tooltip .tip-label{color:#aebbb7}.canvas-wrap canvas{cursor:crosshair}.search-id-chip{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;max-width:360px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
12
12
|
@media(max-width:980px){.layout{grid-template-columns:1fr}.sidenav{display:none}.main{padding:28px 22px 70px}.topbar{padding:0 22px}.scores{grid-template-columns:1fr 1fr}.uplift{grid-column:1/-1}.summary-grid{grid-template-columns:1fr 1fr}.stability-grid,.sample-grid{grid-template-columns:1fr}.source-cards{grid-template-columns:1fr 1fr}}
|
|
13
13
|
@media(max-width:650px){.top-meta .chip,.brand-divider,.brand-label{display:none}.topbar{gap:10px}.button{padding:9px 10px}.hero-title{display:block}.run-stamp{text-align:left;margin-top:16px}.hero-title h1{font-size:30px}.metric-head{display:block}.metric-tabs{margin-top:15px;width:max-content}.summary-grid,.source-cards{grid-template-columns:1fr}.scores{gap:14px}.score-value{font-size:32px}.metric-sample-head{display:none}.metric-sample-row{grid-template-columns:1fr 1fr;gap:14px;padding:16px 2px}.metric-sample-name{grid-column:1/-1}.metric-sample-value{font-size:24px}.metric-sample-row>div:not(.metric-sample-name):before{display:block;color:#8fa09b;text-transform:uppercase;letter-spacing:.08em;font-size:8px;font-weight:800;margin-bottom:6px}.metric-sample-row>div:nth-child(2):before{content:"Baseline"}.metric-sample-row>div:nth-child(3):before{content:"Enriched"}.metric-sample-row>div:nth-child(4):before{content:"Uplift, abs"}.metric-sample-row>div:nth-child(5):before{content:"Uplift, %"}.main{padding-left:15px;padding-right:15px}.cell-output{gap:14px}}
|
|
@@ -127,6 +127,9 @@ const REPORT_DATA = __REPORT_DATA__;
|
|
|
127
127
|
const pct1 = v => (v == null ? '—' : Number(v).toFixed(1) + '%');
|
|
128
128
|
const signed3 = v => (v >= 0 ? '+' : '') + Number(v).toFixed(3);
|
|
129
129
|
const setText = (id, text) => { const el = document.getElementById(id); if (el) el.textContent = text; };
|
|
130
|
+
const providerBadge = provider => provider
|
|
131
|
+
? `<span class="provider">${String(provider).replace(/,\s*/g, '<br>')}</span>`
|
|
132
|
+
: '';
|
|
130
133
|
function rgba(hex, alpha) {
|
|
131
134
|
const h = hex.replace('#', '');
|
|
132
135
|
const n = parseInt(h.length === 3 ? h.split('').map(x => x + x).join('') : h, 16);
|
|
@@ -507,7 +510,7 @@ const REPORT_DATA = __REPORT_DATA__;
|
|
|
507
510
|
const maxImportance = Math.max(...features.map(f => f.importance)) || 1;
|
|
508
511
|
const list = shapAll ? features : features.slice(0, (D.shap && D.shap.topN) || features.length);
|
|
509
512
|
document.getElementById('shapList').innerHTML = list.map((f, i) =>
|
|
510
|
-
`<div class="shap-row"><div><b>#${i + 1} ${f.name}</b>${f.provider
|
|
513
|
+
`<div class="shap-row"><div><b>#${i + 1} ${f.name}</b>${providerBadge(f.provider)}</div>` +
|
|
511
514
|
`<div class="shap-track"><i class="shap-fill ${f.shapClass || 'upgini'}" style="width:${(f.importance / maxImportance * 100).toFixed(2)}%"></i></div>` +
|
|
512
515
|
`<b>${fmt3(Math.abs(f.shap))}</b></div>`).join('');
|
|
513
516
|
document.getElementById('showAllShap').textContent = shapAll ? `Show top ${(D.shap && D.shap.topN) || features.length}` : `Show all ${features.length}`;
|
|
@@ -534,7 +537,7 @@ const REPORT_DATA = __REPORT_DATA__;
|
|
|
534
537
|
const body = document.getElementById('featureBody');
|
|
535
538
|
body.innerHTML = rows.map(f =>
|
|
536
539
|
`<tr data-name="${f.name}"><td><span class="feature-name" title="${f.name}">${f.name}</span></td>` +
|
|
537
|
-
`<td>${f.provider
|
|
540
|
+
`<td>${providerBadge(f.provider)}</td><td class="source-name">${f.source}</td>` +
|
|
538
541
|
`<td>${shapLabel(f.shap)}</td><td><span class="tag ${f.status}">${fmt2(f.psi)}</span></td>` +
|
|
539
542
|
`<td>${f.drift == null ? '—' : fmt2(f.drift)}</td><td>${pct1(f.coverage)}</td></tr>`).join('');
|
|
540
543
|
document.getElementById('emptyState').style.display = rows.length ? 'none' : 'block';
|
|
@@ -210,6 +210,31 @@ class SearchTask:
|
|
|
210
210
|
if meta.generated_features is not None:
|
|
211
211
|
return meta.generated_features
|
|
212
212
|
|
|
213
|
+
def get_all_generated_features(self) -> List[GeneratedFeatureMetadata]:
|
|
214
|
+
if self.provider_metadata_v2 is None:
|
|
215
|
+
return []
|
|
216
|
+
generated: List[GeneratedFeatureMetadata] = []
|
|
217
|
+
for meta in self.provider_metadata_v2:
|
|
218
|
+
if meta.generated_features:
|
|
219
|
+
generated.extend(meta.generated_features)
|
|
220
|
+
return generated
|
|
221
|
+
|
|
222
|
+
def get_joined_ads_features_count(self) -> Optional[int]:
|
|
223
|
+
if self.provider_metadata_v2 is None:
|
|
224
|
+
return None
|
|
225
|
+
for meta in self.provider_metadata_v2:
|
|
226
|
+
if meta.joined_ads_features_count is not None:
|
|
227
|
+
return meta.joined_ads_features_count
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
def get_joined_ads_count(self) -> Optional[int]:
|
|
231
|
+
if self.provider_metadata_v2 is None:
|
|
232
|
+
return None
|
|
233
|
+
for meta in self.provider_metadata_v2:
|
|
234
|
+
if meta.joined_ads_count is not None:
|
|
235
|
+
return meta.joined_ads_count
|
|
236
|
+
return None
|
|
237
|
+
|
|
213
238
|
@staticmethod
|
|
214
239
|
def _get_provider_summaries(summary: SearchTaskSummary) -> List[ProviderTaskSummary]:
|
|
215
240
|
if summary.status in {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.167.a4220.dev8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/ads_management/ads_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/numpy_kernels.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/autofe/timeseries/volatility.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/normalizer/normalize_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/resource_bundle/__init__.py
RENAMED
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/resource_bundle/exceptions.py
RENAMED
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/resource_bundle/strings.properties
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/sampler/random_under_sampler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/base_search_key_detector.py
RENAMED
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/blocked_time_series.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/fallback_progress_bar.py
RENAMED
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/features_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.167a4220.dev8 → upgini-1.2.167a4220.dev9}/src/upgini/utils/search_key_derivations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|