rapidsegment 1.2.3__tar.gz → 1.2.4__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 (52) hide show
  1. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/PKG-INFO +9 -3
  2. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/README.md +8 -2
  3. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/pyproject.toml +1 -1
  4. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/pyproject.toml.orig +1 -1
  5. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/_exit.py +122 -122
  6. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_120723_eaf4de/config.json +0 -32
  7. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_120723_eaf4de/logs.txt +0 -29
  8. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_120723_eaf4de/result.json +0 -511
  9. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_120723_eaf4de/scorecard.json +0 -34
  10. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_120723_eaf4de/sql.sql +0 -52
  11. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_120723_eaf4de/workbench.duckdb +0 -0
  12. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_121502_27ba31/config.json +0 -32
  13. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_121502_27ba31/logs.txt +0 -29
  14. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_121502_27ba31/result.json +0 -511
  15. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_121502_27ba31/scorecard.json +0 -34
  16. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_121502_27ba31/sql.sql +0 -52
  17. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_121502_27ba31/workbench.duckdb +0 -0
  18. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_122433_23c454/config.json +0 -32
  19. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_122433_23c454/logs.txt +0 -29
  20. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_122433_23c454/result.json +0 -511
  21. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_122433_23c454/scorecard.json +0 -34
  22. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_122433_23c454/sql.sql +0 -52
  23. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_122433_23c454/workbench.duckdb +0 -0
  24. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_123639_16e177/config.json +0 -32
  25. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_123639_16e177/logs.txt +0 -29
  26. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_123639_16e177/result.json +0 -511
  27. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_123639_16e177/scorecard.json +0 -34
  28. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_123639_16e177/sql.sql +0 -52
  29. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_123639_16e177/workbench.duckdb +0 -0
  30. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_134939_166a64/workbench.duckdb +0 -0
  31. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/artifacts/exp_20260822_134939_166a64/workbench.duckdb.wal +0 -0
  32. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/module1_data.duckdb +0 -0
  33. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/module1_data_modified.duckdb +0 -0
  34. rapidsegment-1.2.3/src/rapidsegment/ui/.rapidsegment_suite/suite_data.db +0 -0
  35. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/LICENSE +0 -0
  36. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/__init__.py +0 -0
  37. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/builder.py +0 -0
  38. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/py.typed +0 -0
  39. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/scorer.py +0 -0
  40. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/__init__.py +0 -0
  41. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/app.py +0 -0
  42. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/pages/1_Data_Loader.py +0 -0
  43. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/pages/2_Workbench.py +0 -0
  44. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/pages/3_Execution_Console.py +0 -0
  45. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/pages/4_Results_Dashboard.py +0 -0
  46. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/pages/5_Leaderboard.py +0 -0
  47. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/pages/6_Arena.py +0 -0
  48. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/ui/pages/__init__.py +0 -0
  49. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/utils/__init__.py +0 -0
  50. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/utils/data_loader.py +0 -0
  51. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/utils/on_gcp_feature_selection.py +0 -0
  52. {rapidsegment-1.2.3 → rapidsegment-1.2.4}/src/rapidsegment/utils/undersampler.sql +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rapidsegment
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: A fast, multi-segment population scoring and combinatorial heuristic rule extraction engine.
5
5
  Author: Bishwarup Biswas
6
6
  Author-email: Bishwarup Biswas <bishwarup1429@gmail.com>
@@ -38,10 +38,14 @@ Provides-Extra: prettytable
38
38
  Provides-Extra: ui
39
39
  Description-Content-Type: text/markdown
40
40
 
41
- > [!IMPORTANT]
41
+ > [!IMPORTANT]
42
42
  > **Legal Disclaimer**
43
43
  > This open‑source library (`RapidSegment`) is an independent, community‑driven predictive analytics framework. It is **completely unaffiliated** with any commercial products, SaaS platforms, or enterprise solutions of the same or similar name. Any overlap in nomenclature is purely coincidental.
44
44
 
45
+ <p align="center">
46
+ <img width="300" height="500" alt="adfd9cdf-251f-44e4-af79-20802d4a7a01" src="https://github.com/user-attachments/assets/803790d0-7ebe-4e79-a415-65f1daaea40b" />
47
+ </p>
48
+
45
49
  # 🚀 RapidSegment – Strategic Segmentation & Scorecard Engine
46
50
 
47
51
  [![PyPI version](https://img.shields.io/pypi/v/rapidsegment.svg)](https://pypi.org/project/rapidsegment/)
@@ -56,7 +60,7 @@ Description-Content-Type: text/markdown
56
60
  - [✨ Features](#-features)
57
61
  - [🌳 Decision Trees vs RapidSegment](#-decision-trees-vs-rapidsegment)
58
62
  - [⚡ Quick Start](#-quick-start)
59
- - [🖥️ Web UI](#-web-ui)
63
+ - [🖥️ Web UI](#️-web-ui)
60
64
  - [🧩 Components](#-components)
61
65
  - [🏗️ System Architecture](#️-system-architecture)
62
66
  - [⚙️ How It Works – Step by Step](#️-how-it-works--step-by-step)
@@ -104,6 +108,7 @@ In short, a decision tree is great when you want a predictive model structure; R
104
108
 
105
109
  ---
106
110
 
111
+
107
112
  ## ⚡ Quick Start
108
113
 
109
114
  ```python
@@ -555,3 +560,4 @@ Special Thanks to Mr. [Guillermo Navas Palencia](https://github.com/guillermo-na
555
560
 
556
561
  _Independent, open‑source, and ready for production._
557
562
  ```
563
+
@@ -1,7 +1,11 @@
1
- > [!IMPORTANT]
1
+ > [!IMPORTANT]
2
2
  > **Legal Disclaimer**
3
3
  > This open‑source library (`RapidSegment`) is an independent, community‑driven predictive analytics framework. It is **completely unaffiliated** with any commercial products, SaaS platforms, or enterprise solutions of the same or similar name. Any overlap in nomenclature is purely coincidental.
4
4
 
5
+ <p align="center">
6
+ <img width="300" height="500" alt="adfd9cdf-251f-44e4-af79-20802d4a7a01" src="https://github.com/user-attachments/assets/803790d0-7ebe-4e79-a415-65f1daaea40b" />
7
+ </p>
8
+
5
9
  # 🚀 RapidSegment – Strategic Segmentation & Scorecard Engine
6
10
 
7
11
  [![PyPI version](https://img.shields.io/pypi/v/rapidsegment.svg)](https://pypi.org/project/rapidsegment/)
@@ -16,7 +20,7 @@
16
20
  - [✨ Features](#-features)
17
21
  - [🌳 Decision Trees vs RapidSegment](#-decision-trees-vs-rapidsegment)
18
22
  - [⚡ Quick Start](#-quick-start)
19
- - [🖥️ Web UI](#-web-ui)
23
+ - [🖥️ Web UI](#️-web-ui)
20
24
  - [🧩 Components](#-components)
21
25
  - [🏗️ System Architecture](#️-system-architecture)
22
26
  - [⚙️ How It Works – Step by Step](#️-how-it-works--step-by-step)
@@ -64,6 +68,7 @@ In short, a decision tree is great when you want a predictive model structure; R
64
68
 
65
69
  ---
66
70
 
71
+
67
72
  ## ⚡ Quick Start
68
73
 
69
74
  ```python
@@ -515,3 +520,4 @@ Special Thanks to Mr. [Guillermo Navas Palencia](https://github.com/guillermo-na
515
520
 
516
521
  _Independent, open‑source, and ready for production._
517
522
  ```
523
+
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rapidsegment"
3
- version = "1.2.3"
3
+ version = "1.2.4"
4
4
  description = "A fast, multi-segment population scoring and combinatorial heuristic rule extraction engine."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rapidsegment"
3
- version = "1.2.3"
3
+ version = "1.2.4"
4
4
  description = "A fast, multi-segment population scoring and combinatorial heuristic rule extraction engine."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,124 +1,124 @@
1
- """Exit the RapidSegment UI and stop the Streamlit server.
2
-
3
- Streamlit has no built-in stop-server button, so when a user clicks Exit we
4
- terminate the Streamlit server process (and its children). run_ui() records the
5
- server PID when it launches the app; if the app was launched some other way
6
- (e.g. `streamlit run app.py` directly) we fall back to walking the process tree
7
- to find the Streamlit server. psutil is a hard dependency of the package, so the
8
- process-tree walk is always available.
9
- """
10
-
11
- import os
12
- import tempfile
13
-
14
- import streamlit as st
15
-
16
- # Where run_ui() records the Streamlit server PID (single-user local app).
17
- _PID_FILE = os.path.join(tempfile.gettempdir(), "rapidsegment_ui_server.pid")
18
-
19
-
20
- def write_server_pid(pid):
21
- try:
22
- with open(_PID_FILE, "w") as fh:
23
- fh.write(str(pid))
24
- except Exception:
25
- pass
26
-
27
-
28
- def _read_server_pid():
29
- try:
30
- if os.path.exists(_PID_FILE):
31
- with open(_PID_FILE) as fh:
32
- return int(fh.read().strip())
33
- except Exception:
34
- pass
35
- return None
36
-
37
-
38
- def _kill_tree(proc):
39
- """Kill a psutil process and all of its descendants."""
40
- try:
41
- for child in proc.children(recursive=True):
42
- try:
43
- child.kill()
44
- except Exception:
45
- pass
46
- proc.kill()
47
- return True
48
- except Exception:
49
- return False
50
-
51
-
52
- def exit_ui():
53
- """Terminate the Streamlit server (and this app run)."""
54
- targets = []
55
-
56
- try:
57
- import psutil
58
-
59
- pid = _read_server_pid()
60
- if pid is not None:
61
- try:
62
- targets.append(psutil.Process(pid))
63
- except Exception:
64
- pass
65
-
66
- # Fallback / supplement: walk up the process tree to find the
67
- # Streamlit server (its command line contains "streamlit").
68
- me = psutil.Process(os.getpid())
69
- anc = me
70
- for _ in range(8):
71
- anc = anc.parent()
72
- if anc is None:
73
- break
74
- cmd = " ".join(anc.cmdline()).lower()
75
- if "streamlit" in cmd:
76
- targets.append(anc)
77
- except Exception:
78
- pass
79
-
80
- seen = set()
81
- for t in targets:
82
- if t.pid in seen:
83
- continue
84
- seen.add(t.pid)
85
- _kill_tree(t)
86
-
87
- if not targets:
88
- # Last resort: kill our own process tree.
89
- try:
90
- import psutil
91
- me = psutil.Process(os.getpid())
92
- for child in me.children(recursive=True):
93
- try:
94
- child.kill()
95
- except Exception:
96
- pass
97
- me.kill()
98
- except Exception:
99
- try:
100
- os.kill(os.getpid(), 9)
101
- except Exception:
102
- pass
103
-
104
- # Always halt this script run.
105
- try:
106
- st.stop()
107
- except Exception:
108
- pass
109
- # If st.stop() did not end it (already torn down), force exit.
110
- try:
111
- os._exit(0)
112
- except Exception:
113
- pass
114
-
115
-
116
- def render_exit_button(label="Exit UI", key="exit_ui_global"):
117
- if st.sidebar.button(
118
- label,
119
- key=key,
120
- width="content",
121
- help="Stop the Streamlit server and close the app.",
122
- ):
1
+ """Exit the RapidSegment UI and stop the Streamlit server.
2
+
3
+ Streamlit has no built-in stop-server button, so when a user clicks Exit we
4
+ terminate the Streamlit server process (and its children). run_ui() records the
5
+ server PID when it launches the app; if the app was launched some other way
6
+ (e.g. `streamlit run app.py` directly) we fall back to walking the process tree
7
+ to find the Streamlit server. psutil is a hard dependency of the package, so the
8
+ process-tree walk is always available.
9
+ """
10
+
11
+ import os
12
+ import tempfile
13
+
14
+ import streamlit as st
15
+
16
+ # Where run_ui() records the Streamlit server PID (single-user local app).
17
+ _PID_FILE = os.path.join(tempfile.gettempdir(), "rapidsegment_ui_server.pid")
18
+
19
+
20
+ def write_server_pid(pid):
21
+ try:
22
+ with open(_PID_FILE, "w") as fh:
23
+ fh.write(str(pid))
24
+ except Exception:
25
+ pass
26
+
27
+
28
+ def _read_server_pid():
29
+ try:
30
+ if os.path.exists(_PID_FILE):
31
+ with open(_PID_FILE) as fh:
32
+ return int(fh.read().strip())
33
+ except Exception:
34
+ pass
35
+ return None
36
+
37
+
38
+ def _kill_tree(proc):
39
+ """Kill a psutil process and all of its descendants."""
40
+ try:
41
+ for child in proc.children(recursive=True):
42
+ try:
43
+ child.kill()
44
+ except Exception:
45
+ pass
46
+ proc.kill()
47
+ return True
48
+ except Exception:
49
+ return False
50
+
51
+
52
+ def exit_ui():
53
+ """Terminate the Streamlit server (and this app run)."""
54
+ targets = []
55
+
56
+ try:
57
+ import psutil
58
+
59
+ pid = _read_server_pid()
60
+ if pid is not None:
61
+ try:
62
+ targets.append(psutil.Process(pid))
63
+ except Exception:
64
+ pass
65
+
66
+ # Fallback / supplement: walk up the process tree to find the
67
+ # Streamlit server (its command line contains "streamlit").
68
+ me = psutil.Process(os.getpid())
69
+ anc = me
70
+ for _ in range(8):
71
+ anc = anc.parent()
72
+ if anc is None:
73
+ break
74
+ cmd = " ".join(anc.cmdline()).lower()
75
+ if "streamlit" in cmd:
76
+ targets.append(anc)
77
+ except Exception:
78
+ pass
79
+
80
+ seen = set()
81
+ for t in targets:
82
+ if t.pid in seen:
83
+ continue
84
+ seen.add(t.pid)
85
+ _kill_tree(t)
86
+
87
+ if not targets:
88
+ # Last resort: kill our own process tree.
89
+ try:
90
+ import psutil
91
+ me = psutil.Process(os.getpid())
92
+ for child in me.children(recursive=True):
93
+ try:
94
+ child.kill()
95
+ except Exception:
96
+ pass
97
+ me.kill()
98
+ except Exception:
99
+ try:
100
+ os.kill(os.getpid(), 9)
101
+ except Exception:
102
+ pass
103
+
104
+ # Always halt this script run.
105
+ try:
106
+ st.stop()
107
+ except Exception:
108
+ pass
109
+ # If st.stop() did not end it (already torn down), force exit.
110
+ try:
111
+ os._exit(0)
112
+ except Exception:
113
+ pass
114
+
115
+
116
+ def render_exit_button(label="Exit UI", key="exit_ui_global"):
117
+ if st.sidebar.button(
118
+ label,
119
+ key=key,
120
+ width="content",
121
+ help="Stop the Streamlit server and close the app.",
122
+ ):
123
123
  exit_ui()
124
124
 
@@ -1,32 +0,0 @@
1
- {
2
- "experiment_name": "EXP 1 Bank",
3
- "description": "",
4
- "data_table": "udl_data",
5
- "target_col": "Exited__binary",
6
- "primary_key": "",
7
- "top_n_vars": 15,
8
- "max_segments": 10,
9
- "max_feature_reuse": 1,
10
- "feature_groups": {},
11
- "enable_diversity": false,
12
- "ignore_features": [
13
- "id",
14
- "CustomerId",
15
- "Surname",
16
- "Exited"
17
- ],
18
- "binning_method": "optimal_cart",
19
- "naive_bins": 5,
20
- "max_expansion_hops": 0,
21
- "enable_1way": true,
22
- "enable_2way": true,
23
- "enable_3way": true,
24
- "selection_metric": "iv",
25
- "min_sample_size": 1000,
26
- "min_lift": 1.2,
27
- "min_events": 100,
28
- "param_grid": null,
29
- "sort_priority": "rate_lift_count",
30
- "n_jobs": -1,
31
- "expand_log_mode": "none"
32
- }
@@ -1,29 +0,0 @@
1
- 2026-08-22 12:07:23 | INFO | Experiment 'EXP 1 Bank' started — exp_id=exp_20260822_120723_eaf4de
2
- 2026-08-22 12:07:23 | INFO | Data: 165,034 rows × 15 cols · target=`Exited__binary`
3
- 2026-08-22 12:07:23 | INFO | 🚀 Starting hierarchical segment extraction...
4
- 2026-08-22 12:07:23 | INFO | ⚙️ DuckDB Configured for Disk Spilling: Threads=14/16, MemoryLimit=11GB, TempDir=C:\Users\Bishwarup\RapidSegment\rapidsegment\src\rapidsegment\ui\.rapidsegment_suite\artifacts\exp_20260822_120723_eaf4de\tmp
5
- 2026-08-22 12:07:23 | INFO | 📊 Sort priority: rate_lift_count
6
- 2026-08-22 12:07:23 | INFO | 📦 Binning method: optimal_cart
7
- 2026-08-22 12:07:24 | INFO | 🔒 Locking Original Base Rate: 21.16%
8
- 2026-08-22 12:07:24 | INFO | 🔄 Iteration 1 | Remaining Volume: 165,034 | Base Rate: 21.16%
9
- 2026-08-22 12:07:24 | INFO | 🔍 Computing IV and bins for 10 features...
10
- 2026-08-22 12:07:28 | INFO | 📌 Feature Usage Tracker Update -> 'Age' used count = 1
11
- 2026-08-22 12:07:28 | INFO | 📌 Feature Usage Tracker Update -> 'Geography' used count = 1
12
- 2026-08-22 12:07:28 | INFO | 📌 Feature Usage Tracker Update -> 'IsActiveMember' used count = 1
13
- 2026-08-22 12:07:28 | INFO | ✅ Segment 1 Captured (Size Floor: 1000 | Lift Floor: 1.2): rows=3164, events=2773.0, lift=4.14
14
- Rule: Age=[48.50, inf) & Geography=[Germany] & IsActiveMember=(-inf, 0.50)
15
- SQL: ("Age" >= 48.50) AND ("Geography" IN ('Germany')) AND ("IsActiveMember" < 0.50)
16
- 2026-08-22 12:07:28 | INFO | 🔄 Iteration 2 | Remaining Volume: 161,870 | Base Rate: 19.86%
17
- 2026-08-22 12:07:28 | INFO | 🔍 Computing IV and bins for 10 features...
18
- 2026-08-22 12:07:32 | INFO | 📌 Feature Usage Tracker Update -> 'Balance' used count = 1
19
- 2026-08-22 12:07:32 | INFO | 📌 Feature Usage Tracker Update -> 'Gender' used count = 1
20
- 2026-08-22 12:07:32 | INFO | 📌 Feature Usage Tracker Update -> 'NumOfProducts' used count = 1
21
- 2026-08-22 12:07:32 | INFO | ✅ Segment 2 Captured (Size Floor: 1000 | Lift Floor: 1.2): rows=11930, events=6431.0, lift=2.55
22
- Rule: Balance=(-inf, 1893.51) & Gender=[Female] & NumOfProducts=(-inf, 1.50)
23
- SQL: ("Balance" < 1893.51) AND ("Gender" IN ('Female')) AND ("NumOfProducts" < 1.50)
24
- 2026-08-22 12:07:32 | INFO | 🔄 Iteration 3 | Remaining Volume: 149,940 | Base Rate: 17.15%
25
- 2026-08-22 12:07:32 | INFO | 🔍 Computing IV and bins for 10 features...
26
- 2026-08-22 12:07:34 | INFO | ⏹️ No candidates cleared the grid. Stopping.
27
- 2026-08-22 12:07:34 | INFO | 🏁 Extraction complete.
28
- 2026-08-22 12:07:35 | INFO | Extraction done — computing final coverage (in-memory DuckDB)…
29
- 2026-08-22 12:07:37 | INFO | Extraction complete — 2 segment(s).