upgini 1.2.167a4220.dev2__tar.gz → 1.2.167a4220.dev3__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.dev2 → upgini-1.2.167a4220.dev3}/PKG-INFO +1 -1
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/pyproject.toml +3 -0
- upgini-1.2.167a4220.dev3/src/upgini/__about__.py +1 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/features_enricher.py +12 -23
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/report/assemble.py +2 -11
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/report/data.py +6 -1
- upgini-1.2.167a4220.dev3/src/upgini/report/html.py +167 -0
- upgini-1.2.167a4220.dev3/src/upgini/report/template.html +732 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/display_utils.py +31 -20
- upgini-1.2.167a4220.dev2/src/upgini/__about__.py +0 -1
- upgini-1.2.167a4220.dev2/src/upgini/report/html.py +0 -385
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/.gitignore +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/LICENSE +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/README.md +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/ads.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/ads_management/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/ads_management/ads_manager.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/all_operators.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/binary.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/date.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/feature.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/groupby.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/operand.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/operator.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/base.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/cross.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/delta.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/lag.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/numpy_kernels.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/roll.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/trend.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/timeseries/volatility.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/unary.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/autofe/vector.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/data_source/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/data_source/data_source_publisher.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/dataset.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/errors.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/http.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/mdc/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/mdc/context.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/metadata.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/metrics.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/normalizer/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/normalizer/normalize_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/report/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/resource_bundle/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/resource_bundle/exceptions.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/resource_bundle/strings.properties +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/resource_bundle/strings_widget.properties +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/sampler/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/sampler/base.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/sampler/random_under_sampler.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/sampler/utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/search_task.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/spinner.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/Roboto-Regular.ttf +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/__init__.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/base_search_key_detector.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/blocked_time_series.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/config.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/country_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/cpu_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/custom_loss_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/cv_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/datetime_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/deduplicate_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/email_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/fallback_progress_bar.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/feature_info.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/features_validator.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/format.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/hash_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/ip_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/mstats.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/one_hot_encoder.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/phone_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/postal_code_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/progress_bar.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/psi.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/pyarrow_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/sample_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/search_key_derivations.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/sklearn_ext.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/sort.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/target_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/track_info.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/ts_utils.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/src/upgini/utils/warning_counter.py +0 -0
- {upgini-1.2.167a4220.dev2 → upgini-1.2.167a4220.dev3}/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.dev3
|
|
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.dev3"
|
|
@@ -185,9 +185,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
185
185
|
|
|
186
186
|
shared_datasets: list of str, optional (default=None)
|
|
187
187
|
list of private shared dataset ids for custom search
|
|
188
|
-
|
|
189
|
-
reports_path: str, optional (default=None)
|
|
190
|
-
Directory for the HTML search report. Defaults to a `reports` folder in the current working directory.
|
|
191
188
|
"""
|
|
192
189
|
|
|
193
190
|
TARGET_NAME = "target"
|
|
@@ -265,7 +262,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
265
262
|
sample_config: SampleConfig | None = None,
|
|
266
263
|
print_trace_id: bool = False,
|
|
267
264
|
print_loaded_report: bool = True,
|
|
268
|
-
reports_path: str | None = None,
|
|
269
265
|
**kwargs,
|
|
270
266
|
):
|
|
271
267
|
self.bundle = get_custom_bundle(custom_bundle_config)
|
|
@@ -335,9 +331,8 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
335
331
|
self.disable_force_downsampling = disable_force_downsampling
|
|
336
332
|
self.print_trace_id = print_trace_id
|
|
337
333
|
self.add_info: AddInfo | None = None
|
|
338
|
-
self.reports_path = reports_path
|
|
339
334
|
self.search_duration_seconds: float | None = None
|
|
340
|
-
self.
|
|
335
|
+
self.report_html: str | None = None
|
|
341
336
|
|
|
342
337
|
if search_id:
|
|
343
338
|
search_task = SearchTask(search_id, rest_client=self.rest_client, logger=self.logger)
|
|
@@ -2305,9 +2300,6 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2305
2300
|
return None
|
|
2306
2301
|
return ensemble[0]
|
|
2307
2302
|
|
|
2308
|
-
def _reports_dir(self) -> Path:
|
|
2309
|
-
return Path(self.reports_path) if self.reports_path else Path.cwd() / "reports"
|
|
2310
|
-
|
|
2311
2303
|
def _reference_rows(self) -> int | None:
|
|
2312
2304
|
if self.X is None:
|
|
2313
2305
|
return None
|
|
@@ -2337,18 +2329,12 @@ class FeaturesEnricher(TransformerMixin):
|
|
|
2337
2329
|
bundle=self.bundle,
|
|
2338
2330
|
)
|
|
2339
2331
|
|
|
2340
|
-
def
|
|
2332
|
+
def _score_report_html(self) -> str | None:
|
|
2341
2333
|
if self._search_task is None or not self._has_single_ensemble_score():
|
|
2342
2334
|
return None
|
|
2343
2335
|
report_data = self._assemble_report_data()
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
reports_dir.mkdir(parents=True, exist_ok=True)
|
|
2347
|
-
path = reports_dir / f"upgini-report-{report_data.metadata.search_id}.html"
|
|
2348
|
-
path.write_text(html, encoding="utf-8")
|
|
2349
|
-
self.report_file_path = str(path)
|
|
2350
|
-
self.logger.info(f"Score report saved to {path}")
|
|
2351
|
-
return self.report_file_path
|
|
2336
|
+
self.report_html = generate_html_report(report_data)
|
|
2337
|
+
return self.report_html
|
|
2352
2338
|
|
|
2353
2339
|
def _is_ensemble_feature(self, column_name: str) -> bool:
|
|
2354
2340
|
renaming = self.fit_columns_renaming or {}
|
|
@@ -4267,7 +4253,7 @@ if response.status_code == 200:
|
|
|
4267
4253
|
self.metrics = None
|
|
4268
4254
|
self.metrics_raw = None
|
|
4269
4255
|
self.metrics_metric_name = None
|
|
4270
|
-
self.
|
|
4256
|
+
self.report_html = None
|
|
4271
4257
|
self.fit_columns_renaming = None
|
|
4272
4258
|
self.fit_dropped_features = set()
|
|
4273
4259
|
self.fit_generated_features = []
|
|
@@ -6085,13 +6071,16 @@ if response.status_code == 200:
|
|
|
6085
6071
|
|
|
6086
6072
|
def __show_report_button(self, display_id: str | None = None, display_handle=None):
|
|
6087
6073
|
try:
|
|
6088
|
-
|
|
6089
|
-
if
|
|
6074
|
+
report_html = self._score_report_html() or self.report_html
|
|
6075
|
+
if report_html:
|
|
6090
6076
|
if not ipython_available():
|
|
6091
|
-
print(f"Full report saved to {report_path}")
|
|
6092
6077
|
return
|
|
6078
|
+
search_id = self._search_task.search_task_id if self._search_task is not None else (self.search_id or "")
|
|
6093
6079
|
return show_button_open_report(
|
|
6094
|
-
|
|
6080
|
+
report_html,
|
|
6081
|
+
download_name=f"upgini-report-{search_id}.html",
|
|
6082
|
+
display_id=display_id,
|
|
6083
|
+
display_handle=display_handle,
|
|
6095
6084
|
)
|
|
6096
6085
|
|
|
6097
6086
|
eval_sets_drift_df = self._get_eval_sets_drift_summary()
|
|
@@ -79,8 +79,9 @@ def _quality_samples(
|
|
|
79
79
|
evaluation_scope=(_as_str(row[segment_col]) or "") if segment_col in metrics_df.columns else "",
|
|
80
80
|
metric=metric_name or "",
|
|
81
81
|
baseline=_as_float(row[baseline_col]) if baseline_col in metrics_df.columns else None,
|
|
82
|
+
baseline_std=_as_float(row[baseline_std_col]) if baseline_std_col in metrics_df.columns else None,
|
|
82
83
|
enriched=_as_float(row[enriched_col]) if enriched_col in metrics_df.columns else None,
|
|
83
|
-
|
|
84
|
+
enriched_std=_as_float(row[enriched_std_col]) if enriched_std_col in metrics_df.columns else None,
|
|
84
85
|
uplift=_as_float(row[uplift_col]) if uplift_col in metrics_df.columns else None,
|
|
85
86
|
relative_uplift=_as_str(row[uplift_pct_col]) if uplift_pct_col in metrics_df.columns else None,
|
|
86
87
|
)
|
|
@@ -112,16 +113,6 @@ def _sample_stats(
|
|
|
112
113
|
return stats
|
|
113
114
|
|
|
114
115
|
|
|
115
|
-
def _metric_std(row, columns, enriched_std_col: Optional[str], baseline_std_col: Optional[str]) -> Optional[float]:
|
|
116
|
-
if enriched_std_col in columns:
|
|
117
|
-
std = _as_float(row[enriched_std_col])
|
|
118
|
-
if std is not None:
|
|
119
|
-
return std
|
|
120
|
-
if baseline_std_col in columns:
|
|
121
|
-
return _as_float(row[baseline_std_col])
|
|
122
|
-
return None
|
|
123
|
-
|
|
124
|
-
|
|
125
116
|
def _as_str(value) -> Optional[str]:
|
|
126
117
|
return None if pd.isna(value) else str(value)
|
|
127
118
|
|
|
@@ -21,11 +21,16 @@ class QualitySample:
|
|
|
21
21
|
evaluation_scope: str
|
|
22
22
|
metric: str
|
|
23
23
|
baseline: Optional[float] = None
|
|
24
|
+
baseline_std: Optional[float] = None
|
|
24
25
|
enriched: Optional[float] = None
|
|
25
|
-
|
|
26
|
+
enriched_std: Optional[float] = None
|
|
26
27
|
uplift: Optional[float] = None
|
|
27
28
|
relative_uplift: Optional[str] = None
|
|
28
29
|
|
|
30
|
+
@property
|
|
31
|
+
def std(self) -> Optional[float]:
|
|
32
|
+
return self.enriched_std if self.enriched_std is not None else self.baseline_std
|
|
33
|
+
|
|
29
34
|
|
|
30
35
|
@dataclass
|
|
31
36
|
class SampleStats:
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from upgini.report.data import ReportData, SampleStats, SearchResultsSummary
|
|
8
|
+
|
|
9
|
+
_TEMPLATE_PATH = Path(__file__).with_name("template.html")
|
|
10
|
+
_SAMPLE_COLORS = ("#1645ee", "#20aab4", "#53cc61", "#e19132", "#d45b5b")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_html_report(data: ReportData) -> str:
|
|
14
|
+
template = _TEMPLATE_PATH.read_text(encoding="utf-8")
|
|
15
|
+
if "__REPORT_DATA__" not in template:
|
|
16
|
+
raise RuntimeError("Report template is missing the REPORT_DATA placeholder")
|
|
17
|
+
payload = json.dumps(_report_payload(data), ensure_ascii=False, indent=2)
|
|
18
|
+
payload = payload.replace("<", "\\u003c").replace(">", "\\u003e")
|
|
19
|
+
return template.replace("__REPORT_DATA__", payload, 1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _report_payload(data: ReportData) -> dict:
|
|
23
|
+
samples = _samples(data)
|
|
24
|
+
return {
|
|
25
|
+
"meta": _meta(data, samples),
|
|
26
|
+
"timeline": {"months": [], "periodOptions": [], "defaultPeriod": 0},
|
|
27
|
+
"samples": samples,
|
|
28
|
+
"keyResult": _key_result(data),
|
|
29
|
+
"summaryCards": _summary_cards(data.summary),
|
|
30
|
+
"sampleStats": _sample_stats_payload(data, samples),
|
|
31
|
+
"scoreAnalysis": {"bySample": {}},
|
|
32
|
+
"scoreDistribution": {"bySample": {}},
|
|
33
|
+
"scoreStability": {"bySample": {}},
|
|
34
|
+
"features": [],
|
|
35
|
+
"featureStability": {"bySample": {}},
|
|
36
|
+
"shap": {"topN": 5},
|
|
37
|
+
"searchResults": {
|
|
38
|
+
"relevantFeaturesCount": _dash(data.summary.relevant_features),
|
|
39
|
+
"dataSourcesCount": _dash(data.summary.data_sources),
|
|
40
|
+
"autofeCount": _dash(len(data.autofe) if data.autofe else None),
|
|
41
|
+
"sources": [],
|
|
42
|
+
"autofe": [],
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _meta(data: ReportData, samples: list[dict]) -> dict:
|
|
48
|
+
meta = data.metadata
|
|
49
|
+
payload = {
|
|
50
|
+
"reportTitle": "Feature enrichment report",
|
|
51
|
+
"reportSubtitle": (
|
|
52
|
+
"Impact of external data on model quality, score dynamics, and the stability of discovered features."
|
|
53
|
+
),
|
|
54
|
+
"searchId": meta.search_id,
|
|
55
|
+
"generatedAt": meta.generated_at,
|
|
56
|
+
"pylibVersion": meta.pylib_version,
|
|
57
|
+
"searchKeys": list(meta.search_keys),
|
|
58
|
+
"sampleNames": [sample["id"] for sample in samples],
|
|
59
|
+
"searchDuration": meta.search_duration,
|
|
60
|
+
"sourcesCount": data.summary.data_sources,
|
|
61
|
+
"totalRows": meta.reference_rows,
|
|
62
|
+
"statusLine": "FIT completed successfully",
|
|
63
|
+
"downloadFileName": f"upgini-report-{meta.search_id}.html",
|
|
64
|
+
}
|
|
65
|
+
if meta.logo_url:
|
|
66
|
+
payload["partnerLogo"] = {"src": meta.logo_url, "alt": "", "height": 30}
|
|
67
|
+
return payload
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _samples(data: ReportData) -> list[dict]:
|
|
71
|
+
names = [stats.sample for stats in data.sample_stats] or list(data.metadata.samples)
|
|
72
|
+
return [
|
|
73
|
+
{
|
|
74
|
+
"id": _slug(name),
|
|
75
|
+
"label": name,
|
|
76
|
+
"caption": "",
|
|
77
|
+
"color": _SAMPLE_COLORS[index % len(_SAMPLE_COLORS)],
|
|
78
|
+
}
|
|
79
|
+
for index, name in enumerate(names)
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _key_result(data: ReportData) -> dict:
|
|
84
|
+
metrics: dict[str, dict] = {}
|
|
85
|
+
for sample in data.quality_by_sample:
|
|
86
|
+
key = _slug(sample.metric or "score")
|
|
87
|
+
bucket = metrics.setdefault(key, {"name": sample.metric or "score", "bySample": {}})
|
|
88
|
+
bucket["bySample"][_slug(sample.evaluation_scope)] = {
|
|
89
|
+
"baseline": sample.baseline,
|
|
90
|
+
"baselineCi": sample.baseline_std,
|
|
91
|
+
"enriched": sample.enriched,
|
|
92
|
+
"enrichedCi": sample.enriched_std,
|
|
93
|
+
}
|
|
94
|
+
default_metric = next(iter(metrics), "")
|
|
95
|
+
has_std = any(
|
|
96
|
+
sample.baseline_std is not None or sample.enriched_std is not None for sample in data.quality_by_sample
|
|
97
|
+
)
|
|
98
|
+
return {
|
|
99
|
+
"title": "Key result",
|
|
100
|
+
"headline": "",
|
|
101
|
+
"defaultMetric": default_metric,
|
|
102
|
+
"confidenceNote": "± values are cross-validation standard deviation" if has_std else "",
|
|
103
|
+
"columns": {
|
|
104
|
+
"sample": "Sample",
|
|
105
|
+
"baseline": "Baseline",
|
|
106
|
+
"enriched": "Enriched",
|
|
107
|
+
"upliftAbs": "Uplift, abs",
|
|
108
|
+
"upliftPct": "Uplift, %",
|
|
109
|
+
},
|
|
110
|
+
"metrics": metrics,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _summary_cards(summary: SearchResultsSummary) -> list[dict]:
|
|
115
|
+
stability = None
|
|
116
|
+
if summary.stable_features_share is not None:
|
|
117
|
+
stability = f"{summary.stable_features_share * 100:.0f}%"
|
|
118
|
+
return [
|
|
119
|
+
{"label": "Features found", "value": _dash(summary.relevant_features), "caption": ""},
|
|
120
|
+
{"label": "Used in model", "value": _dash(summary.model_features), "caption": ""},
|
|
121
|
+
{"label": "Data sources", "value": _dash(summary.data_sources), "caption": ""},
|
|
122
|
+
{"label": "Stability", "value": _dash(stability), "caption": ""},
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _sample_stats_payload(data: ReportData, samples: list[dict]) -> dict:
|
|
127
|
+
by_id = {_slug(stats.sample): stats for stats in data.sample_stats}
|
|
128
|
+
rows = [
|
|
129
|
+
_stat_row("Date range", samples, by_id, lambda stats: stats.date_range),
|
|
130
|
+
_stat_row("Count", samples, by_id, lambda stats: _format_int(stats.rows)),
|
|
131
|
+
_stat_row("Count (target)", samples, by_id, lambda stats: _format_int(stats.labeled)),
|
|
132
|
+
]
|
|
133
|
+
if data.is_binary:
|
|
134
|
+
rows.append(_stat_row("Count 1's (target)", samples, by_id, lambda stats: _format_int(stats.positive)))
|
|
135
|
+
rows.extend(
|
|
136
|
+
[
|
|
137
|
+
_stat_row("Mean (target)", samples, by_id, lambda stats: _format_mean(stats.mean_target)),
|
|
138
|
+
_stat_row("Count (unlabeled)", samples, by_id, lambda stats: _format_int(stats.unlabeled)),
|
|
139
|
+
]
|
|
140
|
+
)
|
|
141
|
+
return {"rows": rows, "monthlyAxis": [], "monthlyPoints": []}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _stat_row(label: str, samples: list[dict], by_id: dict[str, SampleStats], value) -> dict:
|
|
145
|
+
values = {}
|
|
146
|
+
for sample in samples:
|
|
147
|
+
stats = by_id.get(sample["id"]) or SampleStats(sample=sample["label"])
|
|
148
|
+
values[sample["id"]] = _dash(value(stats))
|
|
149
|
+
return {"label": label, "values": values}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _slug(name: str) -> str:
|
|
153
|
+
return name.strip().lower().replace(" ", "_")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _dash(value: Optional[object]) -> str:
|
|
157
|
+
if value is None or value == "":
|
|
158
|
+
return "—"
|
|
159
|
+
return str(value)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _format_int(value: Optional[int]) -> Optional[str]:
|
|
163
|
+
return f"{value:,}" if value is not None else None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _format_mean(value: Optional[float]) -> Optional[str]:
|
|
167
|
+
return f"{value:.3f}" if value is not None else None
|