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.
Files changed (42) hide show
  1. {priorityx-0.2.6 → priorityx-0.3.0}/PKG-INFO +5 -14
  2. {priorityx-0.2.6 → priorityx-0.3.0}/README.md +4 -13
  3. {priorityx-0.2.6 → priorityx-0.3.0}/docs/api-reference.md +14 -14
  4. {priorityx-0.2.6 → priorityx-0.3.0}/docs/quickstart.md +18 -14
  5. {priorityx-0.2.6 → priorityx-0.3.0}/examples/bugs/bugs_monitoring.py +3 -4
  6. {priorityx-0.2.6 → priorityx-0.3.0}/examples/incidents/incident_monitoring.py +9 -18
  7. {priorityx-0.2.6 → priorityx-0.3.0}/examples/violations/violations_monitoring.py +6 -10
  8. {priorityx-0.2.6 → priorityx-0.3.0}/pyproject.toml +1 -1
  9. priorityx-0.3.0/src/priorityx/__init__.py +43 -0
  10. priorityx-0.3.0/src/priorityx/api.py +41 -0
  11. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/core/glmm.py +3 -3
  12. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/tracking/drivers.py +8 -5
  13. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/tracking/transitions.py +12 -13
  14. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/viz/trajectory.py +8 -6
  15. {priorityx-0.2.6 → priorityx-0.3.0}/tests/test_glmm.py +50 -15
  16. priorityx-0.3.0/tests/test_movement_monthly.py +108 -0
  17. {priorityx-0.2.6 → priorityx-0.3.0}/tests/test_transitions.py +21 -0
  18. {priorityx-0.2.6 → priorityx-0.3.0}/uv.lock +82 -92
  19. priorityx-0.2.6/src/priorityx/__init__.py +0 -7
  20. {priorityx-0.2.6 → priorityx-0.3.0}/.github/workflows/release.yml +0 -0
  21. {priorityx-0.2.6 → priorityx-0.3.0}/.github/workflows/test.yml +0 -0
  22. {priorityx-0.2.6 → priorityx-0.3.0}/.gitignore +0 -0
  23. {priorityx-0.2.6 → priorityx-0.3.0}/LICENSE +0 -0
  24. {priorityx-0.2.6 → priorityx-0.3.0}/Makefile +0 -0
  25. {priorityx-0.2.6 → priorityx-0.3.0}/docs/README.md +0 -0
  26. {priorityx-0.2.6 → priorityx-0.3.0}/docs/methodology.md +0 -0
  27. {priorityx-0.2.6 → priorityx-0.3.0}/docs/output_naming.md +0 -0
  28. {priorityx-0.2.6 → priorityx-0.3.0}/docs/priority_classification.md +0 -0
  29. {priorityx-0.2.6 → priorityx-0.3.0}/examples/README.md +0 -0
  30. {priorityx-0.2.6 → priorityx-0.3.0}/scripts/version.py +0 -0
  31. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/core/__init__.py +0 -0
  32. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/core/filters.py +0 -0
  33. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/core/quadrants.py +0 -0
  34. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/tracking/__init__.py +0 -0
  35. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/tracking/movement.py +0 -0
  36. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/utils/__init__.py +0 -0
  37. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/utils/helpers.py +0 -0
  38. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/viz/__init__.py +0 -0
  39. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/viz/matrix.py +0 -0
  40. {priorityx-0.2.6 → priorityx-0.3.0}/src/priorityx/viz/timeline.py +0 -0
  41. {priorityx-0.2.6 → priorityx-0.3.0}/tests/test_drivers.py +0 -0
  42. {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.2.6
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
- from priorityx.core.glmm import fit_priority_matrix
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
- from priorityx.core.glmm import fit_priority_matrix
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
- from priorityx.core.glmm import fit_priority_matrix
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
- from priorityx.tracking.movement import track_cumulative_movement
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
- from priorityx.tracking.transitions import extract_transitions
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
- from priorityx.tracking.drivers import extract_transition_drivers
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
- from priorityx.viz.matrix import plot_priority_matrix
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
- from priorityx.viz.timeline import plot_transition_timeline
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
- from priorityx.viz.trajectory import plot_entity_trajectories
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
- from priorityx.core.glmm import fit_priority_matrix
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
- from priorityx.tracking.movement import track_cumulative_movement
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
- from priorityx.core.glmm import fit_priority_matrix
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
- from priorityx.core.glmm import fit_priority_matrix
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
- from priorityx.core.glmm import fit_priority_matrix
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,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "priorityx"
3
- version = "0.2.6"
3
+ version = "0.3.0"
4
4
  description = "Entity prioritization and escalation detection using GLMM statistical models"
5
5
  authors = [
6
6
  {name = "Okky Mabruri", email = "okkymbrur@gmail.com"}
@@ -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 priors (validated on regulatory data)
13
- DEFAULT_VCP_P = 3.5 # prior scale for random effects (higher = less shrinkage)
14
- DEFAULT_FE_P = 3.0 # prior scale for fixed effects
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 quarter (e.g., "2024-Q2")
281
- quarter_to: Ending quarter (e.g., "2024-Q3")
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["quarter"] == quarter_from]
320
- to_data = entity_data[entity_data["quarter"] == quarter_to]
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
- Required columns: entity, quarter, period_quadrant,
110
- global_quadrant, period_x, period_y, x_delta, y_delta
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
- # group by entity and sort by quarter
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("quarter")
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["quarter"],
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
- "entity": entity,
185
- "transition_quarter": next_row["quarter"],
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
- # trim trajectories after last quarter with new events
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")["quarter"].max()
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 quarter
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["quarter"] <= df_trim["last_active_quarter"])
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("quarter")
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["quarter"].values
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"]