priorityx 0.2.6__tar.gz → 0.3.0__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.
- {priorityx-0.2.6 → priorityx-0.3.0}/PKG-INFO +5 -14
- {priorityx-0.2.6 → priorityx-0.3.0}/README.md +4 -13
- {priorityx-0.2.6 → priorityx-0.3.0}/docs/api-reference.md +14 -14
- {priorityx-0.2.6 → priorityx-0.3.0}/docs/quickstart.md +18 -14
- {priorityx-0.2.6 → priorityx-0.3.0}/examples/bugs/bugs_monitoring.py +3 -4
- {priorityx-0.2.6 → priorityx-0.3.0}/examples/incidents/incident_monitoring.py +9 -18
- {priorityx-0.2.6 → priorityx-0.3.0}/examples/violations/violations_monitoring.py +6 -10
- {priorityx-0.2.6 → priorityx-0.3.0}/pyproject.toml +1 -1
- priorityx-0.3.0/src/priorityx/__init__.py +43 -0
- priorityx-0.3.0/src/priorityx/api.py +41 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/core/glmm.py +3 -3
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/tracking/drivers.py +8 -5
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/tracking/transitions.py +12 -13
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/viz/trajectory.py +8 -6
- {priorityx-0.2.6 → priorityx-0.3.0}/tests/test_glmm.py +50 -15
- priorityx-0.3.0/tests/test_movement_monthly.py +108 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/tests/test_transitions.py +21 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/uv.lock +82 -92
- priorityx-0.2.6/src/priorityx/__init__.py +0 -7
- {priorityx-0.2.6 → priorityx-0.3.0}/.github/workflows/release.yml +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/.github/workflows/test.yml +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/.gitignore +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/LICENSE +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/Makefile +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/docs/README.md +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/docs/methodology.md +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/docs/output_naming.md +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/docs/priority_classification.md +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/examples/README.md +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/scripts/version.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/core/__init__.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/core/filters.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/core/quadrants.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/tracking/__init__.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/tracking/movement.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/utils/__init__.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/utils/helpers.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/viz/__init__.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/viz/matrix.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/viz/timeline.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/tests/test_drivers.py +0 -0
- {priorityx-0.2.6 → priorityx-0.3.0}/tests/test_quadrants.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: priorityx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Entity prioritization and escalation detection using GLMM statistical models
|
|
5
5
|
Project-URL: Homepage, https://github.com/okkymabruri/priorityx
|
|
6
6
|
Project-URL: Repository, https://github.com/okkymabruri/priorityx
|
|
@@ -44,29 +44,20 @@ pip install priorityx
|
|
|
44
44
|
|
|
45
45
|
## Quick Start
|
|
46
46
|
|
|
47
|
-
### Deterministic runs
|
|
48
|
-
|
|
49
|
-
Set `PRIORITYX_GLMM_SEED` (or call `set_glmm_random_seed()`) before running analyses to obtain repeatable GLMM estimates during debugging or CI checks:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
PRIORITYX_GLMM_SEED=1234 python your_script.py
|
|
53
|
-
```
|
|
54
|
-
|
|
55
47
|
```python
|
|
56
48
|
import pandas as pd
|
|
57
|
-
|
|
58
|
-
from priorityx.viz.matrix import plot_priority_matrix
|
|
49
|
+
import priorityx as px
|
|
59
50
|
|
|
60
51
|
df = pd.read_csv("data.csv")
|
|
61
52
|
|
|
62
|
-
results, stats = fit_priority_matrix(
|
|
53
|
+
results, stats = px.fit_priority_matrix(
|
|
63
54
|
df,
|
|
64
55
|
entity_col="service",
|
|
65
56
|
timestamp_col="date",
|
|
66
|
-
temporal_granularity="quarterly"
|
|
57
|
+
temporal_granularity="quarterly",
|
|
67
58
|
)
|
|
68
59
|
|
|
69
|
-
plot_priority_matrix(results, entity_name="Service", save_plot=True)
|
|
60
|
+
px.plot_priority_matrix(results, entity_name="Service", save_plot=True)
|
|
70
61
|
```
|
|
71
62
|
|
|
72
63
|
## Features
|
|
@@ -16,29 +16,20 @@ pip install priorityx
|
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
|
-
### Deterministic runs
|
|
20
|
-
|
|
21
|
-
Set `PRIORITYX_GLMM_SEED` (or call `set_glmm_random_seed()`) before running analyses to obtain repeatable GLMM estimates during debugging or CI checks:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
PRIORITYX_GLMM_SEED=1234 python your_script.py
|
|
25
|
-
```
|
|
26
|
-
|
|
27
19
|
```python
|
|
28
20
|
import pandas as pd
|
|
29
|
-
|
|
30
|
-
from priorityx.viz.matrix import plot_priority_matrix
|
|
21
|
+
import priorityx as px
|
|
31
22
|
|
|
32
23
|
df = pd.read_csv("data.csv")
|
|
33
24
|
|
|
34
|
-
results, stats = fit_priority_matrix(
|
|
25
|
+
results, stats = px.fit_priority_matrix(
|
|
35
26
|
df,
|
|
36
27
|
entity_col="service",
|
|
37
28
|
timestamp_col="date",
|
|
38
|
-
temporal_granularity="quarterly"
|
|
29
|
+
temporal_granularity="quarterly",
|
|
39
30
|
)
|
|
40
31
|
|
|
41
|
-
plot_priority_matrix(results, entity_name="Service", save_plot=True)
|
|
32
|
+
px.plot_priority_matrix(results, entity_name="Service", save_plot=True)
|
|
42
33
|
```
|
|
43
34
|
|
|
44
35
|
## Features
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
### fit_priority_matrix
|
|
6
6
|
|
|
7
7
|
```python
|
|
8
|
-
|
|
8
|
+
import priorityx as px
|
|
9
9
|
|
|
10
|
-
results, stats = fit_priority_matrix(
|
|
10
|
+
results, stats = px.fit_priority_matrix(
|
|
11
11
|
df,
|
|
12
12
|
entity_col,
|
|
13
13
|
timestamp_col,
|
|
@@ -50,9 +50,9 @@ Fits Poisson GLMM to classify entities into priority quadrants.
|
|
|
50
50
|
### track_cumulative_movement
|
|
51
51
|
|
|
52
52
|
```python
|
|
53
|
-
|
|
53
|
+
import priorityx as px
|
|
54
54
|
|
|
55
|
-
movement, meta = track_cumulative_movement(
|
|
55
|
+
movement, meta = px.track_cumulative_movement(
|
|
56
56
|
df,
|
|
57
57
|
entity_col,
|
|
58
58
|
timestamp_col,
|
|
@@ -74,9 +74,9 @@ Tracks entity movement through priority quadrants over time.
|
|
|
74
74
|
### extract_transitions
|
|
75
75
|
|
|
76
76
|
```python
|
|
77
|
-
|
|
77
|
+
import priorityx as px
|
|
78
78
|
|
|
79
|
-
transitions = extract_transitions(
|
|
79
|
+
transitions = px.extract_transitions(
|
|
80
80
|
movement_df,
|
|
81
81
|
focus_risk_increasing=True
|
|
82
82
|
)
|
|
@@ -90,9 +90,9 @@ Extracts quadrant transitions from movement data.
|
|
|
90
90
|
### extract_transition_drivers
|
|
91
91
|
|
|
92
92
|
```python
|
|
93
|
-
|
|
93
|
+
import priorityx as px
|
|
94
94
|
|
|
95
|
-
analysis = extract_transition_drivers(
|
|
95
|
+
analysis = px.extract_transition_drivers(
|
|
96
96
|
movement_df,
|
|
97
97
|
df_raw,
|
|
98
98
|
entity_name="Service A",
|
|
@@ -156,9 +156,9 @@ Classifies supervisory priority (1=Critical, 2=Investigate, 3=Monitor, 4=Low).
|
|
|
156
156
|
### plot_priority_matrix
|
|
157
157
|
|
|
158
158
|
```python
|
|
159
|
-
|
|
159
|
+
import priorityx as px
|
|
160
160
|
|
|
161
|
-
fig = plot_priority_matrix(
|
|
161
|
+
fig = px.plot_priority_matrix(
|
|
162
162
|
results_df,
|
|
163
163
|
entity_name="Entity",
|
|
164
164
|
figsize=(16, 12),
|
|
@@ -174,9 +174,9 @@ Creates scatter plot of priority matrix.
|
|
|
174
174
|
### plot_transition_timeline
|
|
175
175
|
|
|
176
176
|
```python
|
|
177
|
-
|
|
177
|
+
import priorityx as px
|
|
178
178
|
|
|
179
|
-
fig = plot_transition_timeline(
|
|
179
|
+
fig = px.plot_transition_timeline(
|
|
180
180
|
transitions_df,
|
|
181
181
|
entity_name="Entity",
|
|
182
182
|
filter_risk_levels=["critical", "high"],
|
|
@@ -192,9 +192,9 @@ Creates timeline heatmap of transitions. Passing `movement_df` is required to co
|
|
|
192
192
|
### plot_entity_trajectories
|
|
193
193
|
|
|
194
194
|
```python
|
|
195
|
-
|
|
195
|
+
import priorityx as px
|
|
196
196
|
|
|
197
|
-
fig = plot_entity_trajectories(
|
|
197
|
+
fig = px.plot_entity_trajectories(
|
|
198
198
|
movement_df,
|
|
199
199
|
entity_name="Entity",
|
|
200
200
|
max_entities=10,
|
|
@@ -10,36 +10,32 @@ pip install priorityx
|
|
|
10
10
|
|
|
11
11
|
```python
|
|
12
12
|
import pandas as pd
|
|
13
|
-
|
|
14
|
-
from priorityx.viz.matrix import plot_priority_matrix
|
|
13
|
+
import priorityx as px
|
|
15
14
|
|
|
16
15
|
# load your data
|
|
17
16
|
df = pd.read_csv("your_data.csv")
|
|
18
17
|
df["date"] = pd.to_datetime(df["date"])
|
|
19
18
|
|
|
20
19
|
# fit priority matrix
|
|
21
|
-
results, stats = fit_priority_matrix(
|
|
20
|
+
results, stats = px.fit_priority_matrix(
|
|
22
21
|
df,
|
|
23
22
|
entity_col="service", # your entity column
|
|
24
23
|
timestamp_col="date", # your date column
|
|
25
24
|
temporal_granularity="quarterly",
|
|
26
|
-
min_observations=8
|
|
25
|
+
min_observations=8,
|
|
27
26
|
)
|
|
28
27
|
|
|
29
28
|
# visualize
|
|
30
|
-
plot_priority_matrix(results, entity_name="Service", save_plot=True)
|
|
29
|
+
px.plot_priority_matrix(results, entity_name="Service", save_plot=True)
|
|
31
30
|
```
|
|
32
31
|
|
|
33
32
|
## Full Workflow
|
|
34
33
|
|
|
35
34
|
```python
|
|
36
|
-
|
|
37
|
-
from priorityx.tracking.transitions import extract_transitions
|
|
38
|
-
from priorityx.tracking.drivers import extract_transition_drivers, display_transition_drivers
|
|
39
|
-
from priorityx.viz.timeline import plot_transition_timeline
|
|
35
|
+
import priorityx as px
|
|
40
36
|
|
|
41
37
|
# track movement over time
|
|
42
|
-
movement, meta = track_cumulative_movement(
|
|
38
|
+
movement, meta = px.track_cumulative_movement(
|
|
43
39
|
df,
|
|
44
40
|
entity_col="service",
|
|
45
41
|
timestamp_col="date",
|
|
@@ -47,15 +43,15 @@ movement, meta = track_cumulative_movement(
|
|
|
47
43
|
)
|
|
48
44
|
|
|
49
45
|
# detect transitions
|
|
50
|
-
transitions = extract_transitions(movement)
|
|
46
|
+
transitions = px.extract_transitions(movement)
|
|
51
47
|
|
|
52
48
|
# visualize transitions
|
|
53
|
-
plot_transition_timeline(transitions, entity_name="Service", save_plot=True)
|
|
49
|
+
px.plot_transition_timeline(transitions, entity_name="Service", save_plot=True)
|
|
54
50
|
|
|
55
51
|
# analyze drivers for specific transition
|
|
56
52
|
if not transitions.empty:
|
|
57
53
|
first_transition = transitions.iloc[0]
|
|
58
|
-
analysis = extract_transition_drivers(
|
|
54
|
+
analysis = px.extract_transition_drivers(
|
|
59
55
|
movement_df=movement,
|
|
60
56
|
df_raw=df,
|
|
61
57
|
entity_name=first_transition["entity"],
|
|
@@ -66,7 +62,7 @@ if not transitions.empty:
|
|
|
66
62
|
top_n_subcategories=5,
|
|
67
63
|
min_subcategory_delta=2
|
|
68
64
|
)
|
|
69
|
-
display_transition_drivers(analysis)
|
|
65
|
+
px.display_transition_drivers(analysis)
|
|
70
66
|
|
|
71
67
|
# subcategory columns (e.g., severity/product/topic) are auto-detected
|
|
72
68
|
# when not provided; pass subcategory_cols=[...] to override.
|
|
@@ -86,3 +82,11 @@ By default, outputs saved to:
|
|
|
86
82
|
- **`results/`** - All CSV data files (entity scores, transitions, movement tracking)
|
|
87
83
|
|
|
88
84
|
You can customize output directories using the `output_dir` parameter in visualization functions.
|
|
85
|
+
|
|
86
|
+
## Deterministic runs
|
|
87
|
+
|
|
88
|
+
Set `PRIORITYX_GLMM_SEED` (or call `set_glmm_random_seed()`) before running analyses to obtain repeatable GLMM estimates during debugging or CI checks:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
PRIORITYX_GLMM_SEED=1234 python your_script.py
|
|
92
|
+
```
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
# software bug tracking example (data pulled from GitHub raw)
|
|
3
3
|
import pandas as pd
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
from priorityx.viz.matrix import plot_priority_matrix
|
|
5
|
+
import priorityx as px
|
|
7
6
|
|
|
8
7
|
RAW_DATA_URL = "https://raw.githubusercontent.com/okkymabruri/priorityx/main/examples/bugs/bugs.csv"
|
|
9
8
|
|
|
@@ -15,7 +14,7 @@ df["date"] = df["reported_date"]
|
|
|
15
14
|
temporal_granularity = "quarterly"
|
|
16
15
|
entity_name = "Component"
|
|
17
16
|
|
|
18
|
-
results, stats = fit_priority_matrix(
|
|
17
|
+
results, stats = px.fit_priority_matrix(
|
|
19
18
|
df,
|
|
20
19
|
entity_col="component",
|
|
21
20
|
timestamp_col="date",
|
|
@@ -28,7 +27,7 @@ print("Software Bug Priority Matrix:")
|
|
|
28
27
|
print(results[["entity", "Random_Intercept", "Random_Slope", "count", "quadrant"]])
|
|
29
28
|
|
|
30
29
|
# visualize
|
|
31
|
-
plot_priority_matrix(
|
|
30
|
+
px.plot_priority_matrix(
|
|
32
31
|
results,
|
|
33
32
|
entity_name=entity_name,
|
|
34
33
|
show_quadrant_labels=True,
|
|
@@ -3,16 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from priorityx.tracking.movement import track_cumulative_movement
|
|
8
|
-
from priorityx.tracking.transitions import extract_transitions
|
|
9
|
-
from priorityx.tracking.drivers import (
|
|
10
|
-
extract_transition_drivers,
|
|
11
|
-
display_transition_drivers,
|
|
12
|
-
)
|
|
13
|
-
from priorityx.viz.matrix import plot_priority_matrix
|
|
14
|
-
from priorityx.viz.timeline import plot_transition_timeline
|
|
15
|
-
from priorityx.viz.trajectory import plot_entity_trajectories
|
|
6
|
+
import priorityx as px
|
|
16
7
|
from priorityx.utils.helpers import (
|
|
17
8
|
display_quadrant_summary,
|
|
18
9
|
display_transition_summary,
|
|
@@ -34,7 +25,7 @@ print("PRIORITY MATRIX ANALYSIS")
|
|
|
34
25
|
|
|
35
26
|
temporal_granularity = "quarterly"
|
|
36
27
|
entity_name = "Service"
|
|
37
|
-
results, stats = fit_priority_matrix(
|
|
28
|
+
results, stats = px.fit_priority_matrix(
|
|
38
29
|
df,
|
|
39
30
|
entity_col="service",
|
|
40
31
|
timestamp_col="date",
|
|
@@ -49,7 +40,7 @@ print(results[["entity", "Random_Intercept", "Random_Slope", "count", "quadrant"
|
|
|
49
40
|
display_quadrant_summary(results, entity_name=entity_name, min_count=0)
|
|
50
41
|
|
|
51
42
|
# visualize
|
|
52
|
-
plot_priority_matrix(
|
|
43
|
+
px.plot_priority_matrix(
|
|
53
44
|
results,
|
|
54
45
|
entity_name=entity_name,
|
|
55
46
|
show_quadrant_labels=True,
|
|
@@ -61,7 +52,7 @@ plot_priority_matrix(
|
|
|
61
52
|
print()
|
|
62
53
|
print("CUMULATIVE MOVEMENT TRACKING")
|
|
63
54
|
|
|
64
|
-
movement, meta = track_cumulative_movement(
|
|
55
|
+
movement, meta = px.track_cumulative_movement(
|
|
65
56
|
df,
|
|
66
57
|
entity_col="service",
|
|
67
58
|
timestamp_col="date",
|
|
@@ -84,7 +75,7 @@ movement_path = save_dataframe_to_csv(
|
|
|
84
75
|
print(f"Movement CSV saved: {movement_path}")
|
|
85
76
|
|
|
86
77
|
# visualize entity trajectories
|
|
87
|
-
plot_entity_trajectories(
|
|
78
|
+
px.plot_entity_trajectories(
|
|
88
79
|
movement,
|
|
89
80
|
entity_name=entity_name,
|
|
90
81
|
max_entities=5,
|
|
@@ -93,12 +84,12 @@ plot_entity_trajectories(
|
|
|
93
84
|
)
|
|
94
85
|
|
|
95
86
|
# detect transitions
|
|
96
|
-
transitions = extract_transitions(movement, focus_risk_increasing=True)
|
|
87
|
+
transitions = px.extract_transitions(movement, focus_risk_increasing=True)
|
|
97
88
|
|
|
98
89
|
print(f"\nDetected {len(transitions)} risk-increasing transitions")
|
|
99
90
|
display_transition_summary(transitions, entity_name=entity_name)
|
|
100
91
|
|
|
101
|
-
plot_transition_timeline(
|
|
92
|
+
px.plot_transition_timeline(
|
|
102
93
|
transitions,
|
|
103
94
|
entity_name=entity_name,
|
|
104
95
|
save_plot=True,
|
|
@@ -129,7 +120,7 @@ if len(critical_transitions) > 0:
|
|
|
129
120
|
)
|
|
130
121
|
|
|
131
122
|
if prev_quarter:
|
|
132
|
-
driver_analysis = extract_transition_drivers(
|
|
123
|
+
driver_analysis = px.extract_transition_drivers(
|
|
133
124
|
movement_df=movement,
|
|
134
125
|
df_raw=df,
|
|
135
126
|
entity_name=trans["entity"],
|
|
@@ -141,7 +132,7 @@ if len(critical_transitions) > 0:
|
|
|
141
132
|
min_subcategory_delta=2,
|
|
142
133
|
)
|
|
143
134
|
|
|
144
|
-
display_transition_drivers(driver_analysis)
|
|
135
|
+
px.display_transition_drivers(driver_analysis)
|
|
145
136
|
|
|
146
137
|
print()
|
|
147
138
|
print("Analysis complete. Check plot/ and results/ in the current working directory.")
|
|
@@ -4,11 +4,7 @@ from datetime import datetime, timedelta
|
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
from priorityx.tracking.movement import track_cumulative_movement
|
|
9
|
-
from priorityx.tracking.transitions import extract_transitions
|
|
10
|
-
from priorityx.viz.matrix import plot_priority_matrix
|
|
11
|
-
from priorityx.viz.timeline import plot_transition_timeline
|
|
7
|
+
import priorityx as px
|
|
12
8
|
from priorityx.utils.helpers import (
|
|
13
9
|
display_quadrant_summary,
|
|
14
10
|
display_transition_summary,
|
|
@@ -64,7 +60,7 @@ df = pd.DataFrame(data)
|
|
|
64
60
|
temporal_granularity = "quarterly"
|
|
65
61
|
entity_name = "Department"
|
|
66
62
|
|
|
67
|
-
results, stats = fit_priority_matrix(
|
|
63
|
+
results, stats = px.fit_priority_matrix(
|
|
68
64
|
df,
|
|
69
65
|
entity_col="department",
|
|
70
66
|
timestamp_col="date",
|
|
@@ -76,7 +72,7 @@ print(results[["entity", "Random_Intercept", "Random_Slope", "count", "quadrant"
|
|
|
76
72
|
|
|
77
73
|
display_quadrant_summary(results, entity_name=entity_name, min_count=0)
|
|
78
74
|
|
|
79
|
-
plot_priority_matrix(
|
|
75
|
+
px.plot_priority_matrix(
|
|
80
76
|
results,
|
|
81
77
|
entity_name=entity_name,
|
|
82
78
|
show_quadrant_labels=True,
|
|
@@ -84,7 +80,7 @@ plot_priority_matrix(
|
|
|
84
80
|
save_csv=True,
|
|
85
81
|
)
|
|
86
82
|
|
|
87
|
-
movement, meta = track_cumulative_movement(
|
|
83
|
+
movement, meta = px.track_cumulative_movement(
|
|
88
84
|
df,
|
|
89
85
|
entity_col="department",
|
|
90
86
|
timestamp_col="date",
|
|
@@ -100,12 +96,12 @@ movement_path = save_dataframe_to_csv(
|
|
|
100
96
|
temporal_granularity=temporal_granularity,
|
|
101
97
|
)
|
|
102
98
|
print(f"Movement CSV saved: {movement_path}")
|
|
103
|
-
transitions = extract_transitions(movement, focus_risk_increasing=True)
|
|
99
|
+
transitions = px.extract_transitions(movement, focus_risk_increasing=True)
|
|
104
100
|
|
|
105
101
|
print(f"\nDetected {len(transitions)} risk-increasing transitions")
|
|
106
102
|
display_transition_summary(transitions, entity_name=entity_name)
|
|
107
103
|
|
|
108
|
-
plot_transition_timeline(
|
|
104
|
+
px.plot_transition_timeline(
|
|
109
105
|
transitions,
|
|
110
106
|
entity_name=entity_name,
|
|
111
107
|
save_plot=True,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""priorityx: Entity prioritization and escalation detection using GLMM statistical models.
|
|
2
|
+
|
|
3
|
+
The :mod:`priorityx.api` module exposes a small convenience facade so
|
|
4
|
+
users can write::
|
|
5
|
+
|
|
6
|
+
import priorityx as px
|
|
7
|
+
results, stats = px.fit_priority_matrix(...)
|
|
8
|
+
|
|
9
|
+
while more advanced workflows can continue to import from the
|
|
10
|
+
``core``, ``tracking`` and ``viz`` subpackages directly.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .api import ( # noqa: F401
|
|
14
|
+
display_transition_drivers,
|
|
15
|
+
extract_transition_drivers,
|
|
16
|
+
extract_transitions,
|
|
17
|
+
fit,
|
|
18
|
+
fit_priority_matrix,
|
|
19
|
+
load_or_track_movement,
|
|
20
|
+
plot_entity_trajectories,
|
|
21
|
+
plot_priority_matrix,
|
|
22
|
+
plot_transition_timeline,
|
|
23
|
+
set_glmm_random_seed,
|
|
24
|
+
track_cumulative_movement,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__version__ = "0.3.0"
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"__version__",
|
|
31
|
+
"fit_priority_matrix",
|
|
32
|
+
"fit",
|
|
33
|
+
"set_glmm_random_seed",
|
|
34
|
+
"track_cumulative_movement",
|
|
35
|
+
"load_or_track_movement",
|
|
36
|
+
"extract_transitions",
|
|
37
|
+
"extract_transition_drivers",
|
|
38
|
+
"display_transition_drivers",
|
|
39
|
+
"plot_priority_matrix",
|
|
40
|
+
"plot_transition_timeline",
|
|
41
|
+
"plot_entity_trajectories",
|
|
42
|
+
]
|
|
43
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Public convenience API for PriorityX.
|
|
2
|
+
|
|
3
|
+
This module exposes the most commonly used functions via a compact
|
|
4
|
+
namespace so users can write:
|
|
5
|
+
|
|
6
|
+
import priorityx as px
|
|
7
|
+
results, stats = px.fit_priority_matrix(...)
|
|
8
|
+
|
|
9
|
+
without losing access to the more detailed submodules under
|
|
10
|
+
``priorityx.core``, ``priorityx.tracking``, or ``priorityx.viz``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .core.glmm import fit_priority_matrix, set_glmm_random_seed
|
|
14
|
+
from .tracking.movement import track_cumulative_movement, load_or_track_movement
|
|
15
|
+
from .tracking.transitions import extract_transitions
|
|
16
|
+
from .tracking.drivers import (
|
|
17
|
+
extract_transition_drivers,
|
|
18
|
+
display_transition_drivers,
|
|
19
|
+
)
|
|
20
|
+
from .viz.matrix import plot_priority_matrix
|
|
21
|
+
from .viz.timeline import plot_transition_timeline
|
|
22
|
+
from .viz.trajectory import plot_entity_trajectories
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Thin alias for the common GLMM fit path.
|
|
26
|
+
fit = fit_priority_matrix
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"fit_priority_matrix",
|
|
31
|
+
"fit",
|
|
32
|
+
"set_glmm_random_seed",
|
|
33
|
+
"track_cumulative_movement",
|
|
34
|
+
"load_or_track_movement",
|
|
35
|
+
"extract_transitions",
|
|
36
|
+
"extract_transition_drivers",
|
|
37
|
+
"display_transition_drivers",
|
|
38
|
+
"plot_priority_matrix",
|
|
39
|
+
"plot_transition_timeline",
|
|
40
|
+
"plot_entity_trajectories",
|
|
41
|
+
]
|
|
@@ -9,9 +9,9 @@ import numpy as np
|
|
|
9
9
|
import pandas as pd
|
|
10
10
|
from statsmodels.genmod.bayes_mixed_glm import PoissonBayesMixedGLM
|
|
11
11
|
|
|
12
|
-
# default
|
|
13
|
-
DEFAULT_VCP_P = 3.5 #
|
|
14
|
-
DEFAULT_FE_P = 3.0 # prior scale
|
|
12
|
+
# default prior scales for random and fixed effects
|
|
13
|
+
DEFAULT_VCP_P = 3.5 # random effects prior scale
|
|
14
|
+
DEFAULT_FE_P = 3.0 # fixed effects prior scale
|
|
15
15
|
|
|
16
16
|
_ENV_VAR_NAME = "PRIORITYX_GLMM_SEED"
|
|
17
17
|
_GLMM_RANDOM_SEED: Optional[int] = None
|
|
@@ -274,11 +274,13 @@ def extract_transition_drivers(
|
|
|
274
274
|
including volume changes, growth changes, and contributing sub-categories.
|
|
275
275
|
|
|
276
276
|
Args:
|
|
277
|
-
movement_df: Output from track_cumulative_movement()
|
|
277
|
+
movement_df: Output from track_cumulative_movement(). Expected to
|
|
278
|
+
contain the canonical period column ("quarter") whose labels
|
|
279
|
+
may be quarterly ("YYYY-QN") or monthly ("YYYY-MM").
|
|
278
280
|
df_raw: Raw event data (pandas DataFrame)
|
|
279
281
|
entity_name: Entity to analyze
|
|
280
|
-
quarter_from: Starting
|
|
281
|
-
quarter_to: Ending
|
|
282
|
+
quarter_from: Starting period label (e.g., "2024-Q2" or "2024-03")
|
|
283
|
+
quarter_to: Ending period label (e.g., "2024-Q3" or "2024-04")
|
|
282
284
|
entity_col: Entity column name in df_raw
|
|
283
285
|
timestamp_col: Timestamp column name in df_raw
|
|
284
286
|
subcategory_cols: Optional list of subcategory columns to analyze
|
|
@@ -311,13 +313,14 @@ def extract_transition_drivers(
|
|
|
311
313
|
df_raw[timestamp_col] = pd.to_datetime(df_raw[timestamp_col])
|
|
312
314
|
|
|
313
315
|
# get transition data from movement_df
|
|
316
|
+
period_col = "quarter"
|
|
314
317
|
entity_data = movement_df[movement_df["entity"] == entity_name]
|
|
315
318
|
|
|
316
319
|
if len(entity_data) == 0:
|
|
317
320
|
raise ValueError(f"Entity '{entity_name}' not found in movement data")
|
|
318
321
|
|
|
319
|
-
from_data = entity_data[entity_data[
|
|
320
|
-
to_data = entity_data[entity_data[
|
|
322
|
+
from_data = entity_data[entity_data[period_col] == quarter_from]
|
|
323
|
+
to_data = entity_data[entity_data[period_col] == quarter_to]
|
|
321
324
|
|
|
322
325
|
if len(from_data) == 0:
|
|
323
326
|
raise ValueError(f"Quarter '{quarter_from}' not found for '{entity_name}'")
|
|
@@ -99,15 +99,12 @@ def extract_transitions(
|
|
|
99
99
|
1. Quadrant-to-quadrant transitions (Q3→Q2, Q2→Q1, etc.)
|
|
100
100
|
2. Within-quadrant dramatic changes (velocity thresholds)
|
|
101
101
|
|
|
102
|
-
Key Innovation:
|
|
103
|
-
- Detects transitions from period_quadrant changes (actual movement)
|
|
104
|
-
- Classifies risk using global_quadrant (stable baseline)
|
|
105
|
-
- No oscillations in risk classification
|
|
106
|
-
|
|
107
102
|
Args:
|
|
108
|
-
movement_df: DataFrame from track_cumulative_movement()
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
movement_df: DataFrame from track_cumulative_movement(). Expected
|
|
104
|
+
to contain the canonical period column (``quarter``)
|
|
105
|
+
whose labels may be quarterly ("YYYY-QN") or monthly
|
|
106
|
+
("YYYY-MM"), plus: entity, period_quadrant,
|
|
107
|
+
global_quadrant, period_x, period_y, x_delta, y_delta.
|
|
111
108
|
focus_risk_increasing: Only return risk-increasing transitions (default: True)
|
|
112
109
|
If False, includes all transitions including improvements
|
|
113
110
|
|
|
@@ -132,9 +129,11 @@ def extract_transitions(
|
|
|
132
129
|
"""
|
|
133
130
|
transitions = []
|
|
134
131
|
|
|
135
|
-
|
|
132
|
+
period_col = "quarter"
|
|
133
|
+
|
|
134
|
+
# group by entity and sort by period
|
|
136
135
|
for entity, entity_data in movement_df.groupby("entity"):
|
|
137
|
-
entity_data = entity_data.sort_values(
|
|
136
|
+
entity_data = entity_data.sort_values(period_col)
|
|
138
137
|
|
|
139
138
|
# get global quadrant (stable across all periods)
|
|
140
139
|
global_quad = entity_data.iloc[0]["global_quadrant"]
|
|
@@ -161,7 +160,7 @@ def extract_transitions(
|
|
|
161
160
|
transitions.append(
|
|
162
161
|
{
|
|
163
162
|
"entity": entity,
|
|
164
|
-
"transition_quarter": next_row[
|
|
163
|
+
"transition_quarter": next_row[period_col],
|
|
165
164
|
"from_quadrant": from_quad,
|
|
166
165
|
"to_quadrant": to_quad,
|
|
167
166
|
"risk_level": risk_level,
|
|
@@ -181,8 +180,8 @@ def extract_transitions(
|
|
|
181
180
|
if growth_change > 1.0:
|
|
182
181
|
transitions.append(
|
|
183
182
|
{
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
"entity": entity,
|
|
184
|
+
"transition_quarter": next_row[period_col],
|
|
186
185
|
"from_quadrant": curr_row["period_quadrant"],
|
|
187
186
|
"to_quadrant": f"{next_row['period_quadrant']}*Y", # *Y = growth spike
|
|
188
187
|
"risk_level": "critical",
|
|
@@ -71,22 +71,24 @@ def plot_entity_trajectories(
|
|
|
71
71
|
print("No movement data to visualize")
|
|
72
72
|
return None
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
period_col = "quarter"
|
|
75
|
+
|
|
76
|
+
# trim trajectories after last period with new events
|
|
75
77
|
if "period_count" in movement_df.columns:
|
|
76
78
|
df_trim = movement_df.copy()
|
|
77
79
|
active = df_trim[df_trim["period_count"].fillna(0) > 0]
|
|
78
80
|
if not active.empty:
|
|
79
|
-
last_active = active.groupby("entity")[
|
|
81
|
+
last_active = active.groupby("entity")[period_col].max()
|
|
80
82
|
df_trim = df_trim.merge(
|
|
81
83
|
last_active.rename("last_active_quarter"),
|
|
82
84
|
on="entity",
|
|
83
85
|
how="left",
|
|
84
86
|
)
|
|
85
87
|
# keep only entities that ever had positive period_count
|
|
86
|
-
# and cut them at their last active
|
|
88
|
+
# and cut them at their last active period
|
|
87
89
|
df_trim = df_trim[
|
|
88
90
|
df_trim["last_active_quarter"].notna()
|
|
89
|
-
& (df_trim[
|
|
91
|
+
& (df_trim[period_col] <= df_trim["last_active_quarter"])
|
|
90
92
|
]
|
|
91
93
|
movement_df = df_trim
|
|
92
94
|
|
|
@@ -136,7 +138,7 @@ def plot_entity_trajectories(
|
|
|
136
138
|
|
|
137
139
|
# plot trajectories for each entity
|
|
138
140
|
for entity in entities_to_plot:
|
|
139
|
-
entity_data = df_plot[df_plot["entity"] == entity].sort_values(
|
|
141
|
+
entity_data = df_plot[df_plot["entity"] == entity].sort_values(period_col)
|
|
140
142
|
|
|
141
143
|
if len(entity_data) < 2:
|
|
142
144
|
continue
|
|
@@ -144,7 +146,7 @@ def plot_entity_trajectories(
|
|
|
144
146
|
# use period coordinates for cumulative trajectory
|
|
145
147
|
x = entity_data["period_x"].values
|
|
146
148
|
y = entity_data["period_y"].values
|
|
147
|
-
quarters = entity_data[
|
|
149
|
+
quarters = entity_data[period_col].values
|
|
148
150
|
|
|
149
151
|
# get color from global quadrant
|
|
150
152
|
global_quad = entity_data.iloc[0]["global_quadrant"]
|