priorityx 0.2.2__tar.gz → 0.2.3__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 (37) hide show
  1. {priorityx-0.2.2 → priorityx-0.2.3}/PKG-INFO +1 -1
  2. {priorityx-0.2.2 → priorityx-0.2.3}/examples/README.md +3 -4
  3. {priorityx-0.2.2 → priorityx-0.2.3}/examples/bugs/bugs_monitoring.py +8 -8
  4. {priorityx-0.2.2 → priorityx-0.2.3}/examples/incidents/incident_monitoring.py +6 -24
  5. {priorityx-0.2.2 → priorityx-0.2.3}/examples/violations/violations_monitoring.py +10 -19
  6. {priorityx-0.2.2 → priorityx-0.2.3}/pyproject.toml +3 -1
  7. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/__init__.py +1 -1
  8. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/core/glmm.py +17 -17
  9. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/tracking/transitions.py +28 -1
  10. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/utils/helpers.py +3 -3
  11. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/viz/matrix.py +22 -7
  12. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/viz/timeline.py +21 -6
  13. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/viz/trajectory.py +27 -12
  14. {priorityx-0.2.2 → priorityx-0.2.3}/uv.lock +233 -1
  15. {priorityx-0.2.2 → priorityx-0.2.3}/.github/workflows/release.yml +0 -0
  16. {priorityx-0.2.2 → priorityx-0.2.3}/.github/workflows/test.yml +0 -0
  17. {priorityx-0.2.2 → priorityx-0.2.3}/.gitignore +0 -0
  18. {priorityx-0.2.2 → priorityx-0.2.3}/LICENSE +0 -0
  19. {priorityx-0.2.2 → priorityx-0.2.3}/Makefile +0 -0
  20. {priorityx-0.2.2 → priorityx-0.2.3}/README.md +0 -0
  21. {priorityx-0.2.2 → priorityx-0.2.3}/docs/api-reference.md +0 -0
  22. {priorityx-0.2.2 → priorityx-0.2.3}/docs/methodology.md +0 -0
  23. {priorityx-0.2.2 → priorityx-0.2.3}/docs/priority_classification.md +0 -0
  24. {priorityx-0.2.2 → priorityx-0.2.3}/docs/quickstart.md +0 -0
  25. {priorityx-0.2.2 → priorityx-0.2.3}/scripts/version.py +0 -0
  26. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/core/__init__.py +0 -0
  27. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/core/filters.py +0 -0
  28. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/core/quadrants.py +0 -0
  29. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/tracking/__init__.py +0 -0
  30. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/tracking/drivers.py +0 -0
  31. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/tracking/movement.py +0 -0
  32. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/utils/__init__.py +0 -0
  33. {priorityx-0.2.2 → priorityx-0.2.3}/src/priorityx/viz/__init__.py +0 -0
  34. {priorityx-0.2.2 → priorityx-0.2.3}/tests/test_drivers.py +0 -0
  35. {priorityx-0.2.2 → priorityx-0.2.3}/tests/test_glmm.py +0 -0
  36. {priorityx-0.2.2 → priorityx-0.2.3}/tests/test_quadrants.py +0 -0
  37. {priorityx-0.2.2 → priorityx-0.2.3}/tests/test_transitions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: priorityx
3
- Version: 0.2.2
3
+ Version: 0.2.3
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
@@ -16,18 +16,17 @@ pip install git+https://github.com/okkymabruri/priorityx.git
16
16
 
17
17
  ## Running Examples
18
18
 
19
- **IT Incidents** (1,621 incidents, 10 services)
19
+ **IT Incidents** (1,621 incidents, 10 services) — pulls CSV from GitHub raw
20
20
  ```bash
21
21
  python examples/incidents/incident_monitoring.py
22
22
  ```
23
23
 
24
- **Compliance Violations** (495 violations, 10 departments)
24
+ **Compliance Violations** (495 violations, 10 departments) — synthetic dataset
25
25
  ```bash
26
26
  python examples/violations/violations_monitoring.py
27
27
  ```
28
28
 
29
- **Software Bugs** (576 bugs, 10 components)
30
- Pre-generated CSV only. Use as input for your own analysis.
29
+ **Software Bugs** (576 bugs, 10 components) — pulls CSV from GitHub raw
31
30
 
32
31
  ## Output
33
32
 
@@ -1,19 +1,20 @@
1
- # software bug tracking example
2
-
1
+ # %%
2
+ # software bug tracking example (data pulled from GitHub raw)
3
3
  import pandas as pd
4
4
 
5
5
  from priorityx.core.glmm import fit_priority_matrix
6
6
  from priorityx.viz.matrix import plot_priority_matrix
7
7
 
8
- # load bug reports
9
- df = pd.read_csv("examples/bugs/bugs.csv")
8
+ RAW_DATA_URL = "https://raw.githubusercontent.com/okkymabruri/priorityx/main/examples/bugs/bugs.csv"
10
9
 
11
- # parse date column
12
- df["date"] = pd.to_datetime(df["reported_date"])
10
+ # load bug reports from GitHub raw
11
+ df = pd.read_csv(RAW_DATA_URL, parse_dates=["reported_date"])
12
+ df["date"] = df["reported_date"]
13
13
 
14
14
  # fit priority matrix
15
15
  temporal_granularity = "quarterly"
16
16
  entity_name = "Component"
17
+
17
18
  results, stats = fit_priority_matrix(
18
19
  df,
19
20
  entity_col="component",
@@ -33,9 +34,8 @@ plot_priority_matrix(
33
34
  show_quadrant_labels=True,
34
35
  save_plot=True,
35
36
  save_csv=True,
36
- output_dir="examples/bugs/plot",
37
37
  )
38
38
  print()
39
- print("Outputs saved to examples/bugs/plot/ and examples/bugs/results/")
39
+ print("Outputs saved to plot/ and results/ (under the current working directory)")
40
40
 
41
41
  # %%
@@ -1,13 +1,9 @@
1
- # it incident monitoring example
2
-
3
- from pathlib import Path
4
- from io import StringIO
1
+ # %%
2
+ # it incident monitoring example (data pulled from GitHub raw)
5
3
 
6
4
  import pandas as pd
7
- import requests
8
5
 
9
6
  RAW_DATA_URL = "https://raw.githubusercontent.com/okkymabruri/priorityx/main/examples/incidents/incidents.csv"
10
- LOCAL_DATA_PATH = Path(__file__).parent / "incidents.csv"
11
7
 
12
8
  from priorityx.core.glmm import fit_priority_matrix
13
9
  from priorityx.tracking.movement import track_cumulative_movement
@@ -25,16 +21,9 @@ from priorityx.utils.helpers import (
25
21
  save_dataframe_to_csv,
26
22
  )
27
23
 
28
- # load data (prefer hosted CSV so the example works out-of-the-box)
29
- try:
30
- response = requests.get(RAW_DATA_URL, timeout=10)
31
- response.raise_for_status()
32
- df = pd.read_csv(StringIO(response.text))
33
- print("Loaded incidents from GitHub raw URL")
34
- except Exception:
35
- print("Falling back to local incidents.csv")
36
- df = pd.read_csv(LOCAL_DATA_PATH)
37
- df["date"] = pd.to_datetime(df["date"])
24
+ # load data from GitHub raw
25
+ df = pd.read_csv(RAW_DATA_URL, parse_dates=["date"])
26
+ print("Loaded incidents from GitHub raw URL")
38
27
 
39
28
  print(f"Loaded {len(df)} incidents for {df['service'].nunique()} services")
40
29
  print(f"Date range: {df['date'].min()} to {df['date'].max()}")
@@ -66,7 +55,6 @@ plot_priority_matrix(
66
55
  show_quadrant_labels=True,
67
56
  save_plot=True,
68
57
  save_csv=True,
69
- output_dir="examples/incidents/plot",
70
58
  )
71
59
 
72
60
  # track movement
@@ -92,7 +80,6 @@ movement_path = save_dataframe_to_csv(
92
80
  artifact="trajectories",
93
81
  entity_name=entity_name,
94
82
  temporal_granularity=temporal_granularity,
95
- output_dir="examples/incidents/results",
96
83
  )
97
84
  print(f"Movement CSV saved: {movement_path}")
98
85
 
@@ -103,8 +90,6 @@ plot_entity_trajectories(
103
90
  max_entities=5,
104
91
  save_plot=True,
105
92
  save_csv=True,
106
- output_dir="examples/incidents/plot",
107
- results_dir="examples/incidents/results",
108
93
  )
109
94
 
110
95
  # detect transitions
@@ -118,7 +103,6 @@ plot_transition_timeline(
118
103
  entity_name=entity_name,
119
104
  save_plot=True,
120
105
  save_csv=True,
121
- output_dir="examples/incidents/plot",
122
106
  movement_df=movement,
123
107
  )
124
108
 
@@ -160,8 +144,6 @@ if len(critical_transitions) > 0:
160
144
  display_transition_drivers(driver_analysis)
161
145
 
162
146
  print()
163
- print(
164
- "Analysis complete. Check examples/incidents/plot/ and examples/incidents/results/"
165
- )
147
+ print("Analysis complete. Check plot/ and results/ in the current working directory.")
166
148
 
167
149
  # %%
@@ -1,4 +1,4 @@
1
- import os
1
+ # %%
2
2
  import random
3
3
  from datetime import datetime, timedelta
4
4
 
@@ -12,6 +12,7 @@ from priorityx.viz.timeline import plot_transition_timeline
12
12
  from priorityx.utils.helpers import (
13
13
  display_quadrant_summary,
14
14
  display_transition_summary,
15
+ save_dataframe_to_csv,
15
16
  )
16
17
 
17
18
  random.seed(42)
@@ -63,11 +64,6 @@ df = pd.DataFrame(data)
63
64
  temporal_granularity = "quarterly"
64
65
  entity_name = "Department"
65
66
 
66
- PLOT_DIR = "examples/violations/plot"
67
- RESULTS_DIR = "examples/violations/results"
68
- os.makedirs(PLOT_DIR, exist_ok=True)
69
- os.makedirs(RESULTS_DIR, exist_ok=True)
70
-
71
67
  results, stats = fit_priority_matrix(
72
68
  df,
73
69
  entity_col="department",
@@ -86,7 +82,6 @@ plot_priority_matrix(
86
82
  show_quadrant_labels=True,
87
83
  save_plot=True,
88
84
  save_csv=True,
89
- output_dir=PLOT_DIR,
90
85
  )
91
86
 
92
87
  movement, meta = track_cumulative_movement(
@@ -98,17 +93,12 @@ movement, meta = track_cumulative_movement(
98
93
  temporal_granularity=temporal_granularity,
99
94
  )
100
95
 
101
- timestamp = datetime.now().strftime("%Y%m%d")
102
- granularity_suffix = {
103
- "quarterly": "Q",
104
- "yearly": "Y",
105
- "semiannual": "S",
106
- }.get(temporal_granularity, "Q")
107
- movement_path = (
108
- f"{RESULTS_DIR}/trajectories-{entity_name.lower()}-"
109
- f"{granularity_suffix}-{timestamp}.csv"
96
+ movement_path = save_dataframe_to_csv(
97
+ movement,
98
+ artifact="trajectories",
99
+ entity_name=entity_name,
100
+ temporal_granularity=temporal_granularity,
110
101
  )
111
- movement.to_csv(movement_path, index=False)
112
102
  print(f"Movement CSV saved: {movement_path}")
113
103
  transitions = extract_transitions(movement, focus_risk_increasing=True)
114
104
 
@@ -120,9 +110,10 @@ plot_transition_timeline(
120
110
  entity_name=entity_name,
121
111
  save_plot=True,
122
112
  save_csv=True,
123
- output_dir=PLOT_DIR,
124
113
  movement_df=movement,
125
114
  )
126
115
 
127
116
  print()
128
- print("Outputs saved to examples/violations/plot/ and examples/violations/results/")
117
+ print("Outputs saved to plot/ and results/ in the current working directory.")
118
+
119
+ # %%
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "priorityx"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Entity prioritization and escalation detection using GLMM statistical models"
5
5
  authors = [
6
6
  {name = "Okky Mabruri", email = "okkymbrur@gmail.com"}
@@ -39,6 +39,8 @@ build-backend = "hatchling.build"
39
39
  [dependency-groups]
40
40
  dev = [
41
41
  "jupyter>=1.1.1",
42
+ "jupytext>=1.18.1",
43
+ "mssql-python>=1.0.0",
42
44
  "polars>=1.35.1",
43
45
  "priorityx",
44
46
  "pyarrow>=22.0.0",
@@ -1,6 +1,6 @@
1
1
  """priorityx: Entity prioritization and escalation detection using GLMM statistical models."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.2.3"
4
4
 
5
5
  # API will be imported
6
6
 
@@ -3,6 +3,7 @@
3
3
  from typing import Dict, Literal, Optional, Tuple
4
4
  from datetime import timedelta
5
5
  import os
6
+ import warnings
6
7
  import numpy as np
7
8
 
8
9
  import pandas as pd
@@ -71,6 +72,7 @@ def fit_priority_matrix(
71
72
  temporal_granularity: Literal["yearly", "quarterly", "semiannual"] = "yearly",
72
73
  vcp_p: float = DEFAULT_VCP_P,
73
74
  fe_p: float = DEFAULT_FE_P,
75
+ verbose: bool = False,
74
76
  ) -> Tuple[pd.DataFrame, Dict]:
75
77
  """
76
78
  Fit Poisson GLMM to classify entities into priority quadrants.
@@ -87,6 +89,7 @@ def fit_priority_matrix(
87
89
  temporal_granularity: Time aggregation level
88
90
  vcp_p: Random effects prior scale
89
91
  fe_p: Fixed effects prior scale
92
+ verbose: When True, print diagnostic logs; otherwise stay quiet
90
93
 
91
94
  Returns:
92
95
  Tuple of (results DataFrame, statistics dict)
@@ -94,6 +97,11 @@ def fit_priority_matrix(
94
97
  # create copy to avoid modifying original
95
98
  df = df.copy()
96
99
 
100
+ if not verbose:
101
+ warnings.filterwarnings(
102
+ "ignore", message="VB fitting did not converge", category=UserWarning
103
+ )
104
+
97
105
  # ensure timestamp column is datetime
98
106
  df[timestamp_col] = pd.to_datetime(df[timestamp_col])
99
107
 
@@ -129,7 +137,7 @@ def fit_priority_matrix(
129
137
  df = df[df[entity_col].isin(valid_entities)]
130
138
  n_after_volume_filter = df[entity_col].nunique()
131
139
  n_filtered_volume = n_before_volume_filter - n_after_volume_filter
132
- if n_filtered_volume > 0:
140
+ if verbose and n_filtered_volume > 0:
133
141
  print(
134
142
  f" Filtered {n_filtered_volume} entities (<{min_total_count} total count)"
135
143
  )
@@ -308,22 +316,14 @@ def fit_priority_matrix(
308
316
  fe_p=fe_p,
309
317
  )
310
318
 
311
- # DIAGNOSTIC LOGGING
312
- print(f"\n[GLMM DEBUG] Formula: {formula}")
313
- print(f"[GLMM DEBUG] N observations: {len(df_prepared)}")
314
- print(f"[GLMM DEBUG] N entities: {df_prepared[entity_col].nunique()}")
315
- print(f"[GLMM DEBUG] Year range: {df_prepared['year'].min()}-{df_prepared['year'].max()}")
316
- print(f"[GLMM DEBUG] Time variable: mean={df_prepared['time'].mean():.6f}, std={df_prepared['time'].std():.6f}")
317
- print(f"[GLMM DEBUG] Entity sample: {df_prepared[entity_col].unique()[:5].tolist()}")
318
- print(f"[GLMM DEBUG] vcp_p={vcp_p}, fe_p={fe_p}")
319
-
320
- # Print first entity's data
321
- first_entity = df_prepared[entity_col].iloc[0]
322
- entity_data = df_prepared[df_prepared[entity_col] == first_entity]
323
- print(f"\n[GLMM DEBUG] First entity '{first_entity}' data:")
324
- debug_cols = [c for c in ["year", "quarter", "semester", entity_col, "count", "time"] if c in entity_data.columns]
325
- print(entity_data[debug_cols].head(10).to_string())
326
-
319
+ if verbose:
320
+ print(f"\n[GLMM DEBUG] Formula: {formula}")
321
+ print(f"[GLMM DEBUG] N observations: {len(df_prepared)}")
322
+ print(f"[GLMM DEBUG] N entities: {df_prepared[entity_col].nunique()}")
323
+ print(f"[GLMM DEBUG] Year range: {df_prepared['year'].min()}-{df_prepared['year'].max()}")
324
+ print(f"[GLMM DEBUG] Time variable: mean={df_prepared['time'].mean():.6f}, std={df_prepared['time'].std():.6f}")
325
+ print(f"[GLMM DEBUG] Entity sample: {df_prepared[entity_col].unique()[:5].tolist()}")
326
+ print(f"[GLMM DEBUG] vcp_p={vcp_p}, fe_p={fe_p}")
327
327
  # use variational bayes (returns posterior mean)
328
328
  # avoids boundary convergence issues vs map
329
329
  _apply_random_seed()
@@ -1,6 +1,6 @@
1
1
  """Transition detection from movement tracking data."""
2
2
 
3
- from typing import Tuple
3
+ from typing import Iterable, Tuple
4
4
 
5
5
  import pandas as pd
6
6
 
@@ -212,6 +212,33 @@ def extract_transitions(
212
212
  return pd.DataFrame(transitions)
213
213
 
214
214
 
215
+ def filter_transitions(
216
+ df: pd.DataFrame,
217
+ *,
218
+ window: Iterable[str],
219
+ exclude_quadrant_prefixes: Iterable[str] = ("Q4",),
220
+ ) -> pd.DataFrame:
221
+ """Filter transitions to a time window and drop moves involving excluded quadrants."""
222
+ if df.empty:
223
+ return df
224
+
225
+ df = df.copy()
226
+ df["transition_quarter"] = df["transition_quarter"].astype(str)
227
+ df["from_quadrant"] = df["from_quadrant"].astype(str)
228
+ df["to_quadrant"] = df["to_quadrant"].astype(str)
229
+
230
+ filtered = df[df["transition_quarter"].isin(window)].copy()
231
+
232
+ if exclude_quadrant_prefixes:
233
+ mask = False
234
+ for prefix in exclude_quadrant_prefixes:
235
+ mask |= filtered["from_quadrant"].str.startswith(prefix)
236
+ mask |= filtered["to_quadrant"].str.startswith(prefix)
237
+ filtered = filtered[~mask]
238
+
239
+ return filtered.reset_index(drop=True)
240
+
241
+
215
242
  def classify_priority(
216
243
  from_quadrant: str,
217
244
  to_quadrant: str,
@@ -239,7 +239,7 @@ def generate_output_path(
239
239
  artifact: str,
240
240
  entity_name: str,
241
241
  temporal_granularity: str = "quarterly",
242
- output_dir: str = "results",
242
+ output_dir: str = "results/csv",
243
243
  extension: str = "csv",
244
244
  ) -> str:
245
245
  """Generate a standardized output path for saved artifacts.
@@ -248,7 +248,7 @@ def generate_output_path(
248
248
  artifact: Artifact name prefix (e.g., "priority_matrix", "movement")
249
249
  entity_name: Friendly entity label (e.g., "Service")
250
250
  temporal_granularity: Granularity key for suffix (default: "quarterly")
251
- output_dir: Directory where file should be saved
251
+ output_dir: Directory where file should be saved (default: "results/csv")
252
252
  extension: File extension (default: "csv")
253
253
 
254
254
  Returns:
@@ -274,7 +274,7 @@ def save_dataframe_to_csv(
274
274
  artifact: str,
275
275
  entity_name: str,
276
276
  temporal_granularity: str = "quarterly",
277
- output_dir: str = "results",
277
+ output_dir: str = "results/csv",
278
278
  ) -> str:
279
279
  """Save a DataFrame using the standardized naming convention.
280
280
 
@@ -1,6 +1,7 @@
1
1
  """Priority matrix scatter plot visualization."""
2
2
 
3
3
  from typing import List, Optional, Tuple
4
+ import warnings
4
5
 
5
6
  import matplotlib.pyplot as plt
6
7
  import matplotlib.ticker as ticker
@@ -16,6 +17,13 @@ except ImportError:
16
17
  ADJUSTTEXT_AVAILABLE = False
17
18
  print("Warning: adjustText not available. Labels may overlap.")
18
19
 
20
+ # suppress noisy FancyArrowPatch fallback warnings from adjustText/annotate
21
+ warnings.filterwarnings(
22
+ "ignore",
23
+ message=".*FancyArrowPatch.*",
24
+ category=UserWarning,
25
+ )
26
+
19
27
 
20
28
  def plot_priority_matrix(
21
29
  results_df: pd.DataFrame,
@@ -30,8 +38,10 @@ def plot_priority_matrix(
30
38
  skip_label_min_count: int = 0,
31
39
  save_plot: bool = False,
32
40
  save_csv: bool = False,
33
- output_dir: str = "plot",
41
+ plot_dir: str = "results/plot",
42
+ csv_dir: str = "results/csv",
34
43
  temporal_granularity: str = "quarterly",
44
+ close_fig: bool = False,
35
45
  ) -> plt.Figure:
36
46
  """
37
47
  Visualize priority matrix as scatter plot.
@@ -53,8 +63,10 @@ def plot_priority_matrix(
53
63
  skip_label_min_count: Skip labeling entities with count < threshold
54
64
  save_plot: Save plot to file
55
65
  save_csv: Save data to CSV
56
- output_dir: Output directory for saved files
66
+ plot_dir: Output directory for plot files
67
+ csv_dir: Output directory for CSV files
57
68
  temporal_granularity: Time granularity ('quarterly', 'yearly', 'semiannual')
69
+ close_fig: Close the figure before returning (set True if you see duplicate inline renders)
58
70
 
59
71
  Returns:
60
72
  Matplotlib figure
@@ -298,14 +310,14 @@ def plot_priority_matrix(
298
310
  import os
299
311
  from datetime import datetime
300
312
 
301
- os.makedirs(output_dir, exist_ok=True)
313
+ os.makedirs(plot_dir, exist_ok=True)
302
314
  timestamp = datetime.now().strftime("%Y%m%d")
303
315
  granularity_suffix = {
304
316
  "quarterly": "Q",
305
317
  "yearly": "Y",
306
318
  "semiannual": "S",
307
319
  }.get(temporal_granularity, "Q")
308
- save_path = f"{output_dir}/priority_matrix-{entity_name.lower()}-{granularity_suffix}-{timestamp}.png"
320
+ save_path = f"{plot_dir}/priority_matrix-{entity_name.lower()}-{granularity_suffix}-{timestamp}.png"
309
321
 
310
322
  plt.savefig(save_path, dpi=300, bbox_inches="tight", format="png")
311
323
  print(f"Plot saved: {save_path}")
@@ -315,14 +327,14 @@ def plot_priority_matrix(
315
327
  import os
316
328
  from datetime import datetime
317
329
 
318
- os.makedirs(f"{output_dir}/../results", exist_ok=True)
330
+ os.makedirs(csv_dir, exist_ok=True)
319
331
  timestamp = datetime.now().strftime("%Y%m%d")
320
332
  granularity_suffix = {
321
333
  "quarterly": "Q",
322
334
  "yearly": "Y",
323
335
  "semiannual": "S",
324
336
  }.get(temporal_granularity, "Q")
325
- csv_path = f"{output_dir}/../results/priority_matrix-{entity_name.lower()}-{granularity_suffix}-{timestamp}.csv"
337
+ csv_path = f"{csv_dir}/priority_matrix-{entity_name.lower()}-{granularity_suffix}-{timestamp}.csv"
326
338
 
327
339
  # save key columns
328
340
  cols_to_save = [
@@ -335,4 +347,7 @@ def plot_priority_matrix(
335
347
  df[[c for c in cols_to_save if c in df.columns]].to_csv(csv_path, index=False)
336
348
  print(f"CSV saved: {csv_path}")
337
349
 
338
- return plt.gcf()
350
+ fig = plt.gcf()
351
+ if close_fig:
352
+ plt.close(fig)
353
+ return fig
@@ -1,12 +1,20 @@
1
1
  """Transition timeline heatmap visualization."""
2
2
 
3
3
  from typing import List, Literal, Optional, Tuple
4
+ import warnings
4
5
 
5
6
  import matplotlib.pyplot as plt
6
7
  import pandas as pd
7
8
 
8
9
  from priorityx.tracking.transitions import classify_priority
9
10
 
11
+ # suppress noisy FancyArrowPatch fallback warnings from annotate/adjustments
12
+ warnings.filterwarnings(
13
+ "ignore",
14
+ message=".*FancyArrowPatch.*",
15
+ category=UserWarning,
16
+ )
17
+
10
18
 
11
19
  def plot_transition_timeline(
12
20
  transitions_df: pd.DataFrame,
@@ -22,9 +30,11 @@ def plot_transition_timeline(
22
30
  show_all_periods: bool = False,
23
31
  save_plot: bool = False,
24
32
  save_csv: bool = False,
25
- output_dir: str = "plot",
33
+ plot_dir: str = "results/plot",
34
+ csv_dir: str = "results/csv",
26
35
  temporal_granularity: str = "quarterly",
27
36
  movement_df: Optional[pd.DataFrame] = None,
37
+ close_fig: bool = False,
28
38
  ) -> plt.Figure:
29
39
  """
30
40
  Visualize transition timeline as heatmap.
@@ -48,9 +58,11 @@ def plot_transition_timeline(
48
58
  show_all_periods: Include starting period even if no transitions
49
59
  save_plot: Save plot to file
50
60
  save_csv: Save data to CSV
51
- output_dir: Output directory for saved files
61
+ plot_dir: Output directory for plot files
62
+ csv_dir: Output directory for CSV files
52
63
  temporal_granularity: Time granularity for file naming
53
64
  movement_df: Optional movement DataFrame for priority calculation
65
+ close_fig: Close the figure before returning (set True if you see duplicate inline renders)
54
66
 
55
67
  Returns:
56
68
  Matplotlib figure
@@ -368,14 +380,14 @@ def plot_transition_timeline(
368
380
  import os
369
381
  from datetime import datetime
370
382
 
371
- os.makedirs(output_dir, exist_ok=True)
383
+ os.makedirs(plot_dir, exist_ok=True)
372
384
  timestamp = datetime.now().strftime("%Y%m%d")
373
385
  granularity_suffix = {
374
386
  "quarterly": "Q",
375
387
  "yearly": "Y",
376
388
  "semiannual": "S",
377
389
  }.get(temporal_granularity, "Q")
378
- plot_path = f"{output_dir}/transition_timeline-{entity_name.lower()}-{granularity_suffix}-{timestamp}.png"
390
+ plot_path = f"{plot_dir}/transition_timeline-{entity_name.lower()}-{granularity_suffix}-{timestamp}.png"
379
391
  plt.savefig(plot_path, dpi=300, bbox_inches="tight", format="png")
380
392
  print(f"Transition plot saved: {plot_path}")
381
393
 
@@ -384,15 +396,18 @@ def plot_transition_timeline(
384
396
  import os
385
397
  from datetime import datetime
386
398
 
387
- os.makedirs(f"{output_dir}/../results", exist_ok=True)
399
+ os.makedirs(csv_dir, exist_ok=True)
388
400
  timestamp = datetime.now().strftime("%Y%m%d")
389
401
  granularity_suffix = {
390
402
  "quarterly": "Q",
391
403
  "yearly": "Y",
392
404
  "semiannual": "S",
393
405
  }.get(temporal_granularity, "Q")
394
- csv_path = f"{output_dir}/../results/transitions-{entity_name.lower()}-{granularity_suffix}-{timestamp}.csv"
406
+ csv_path = f"{csv_dir}/transitions-{entity_name.lower()}-{granularity_suffix}-{timestamp}.csv"
395
407
  df.to_csv(csv_path, index=False)
396
408
  print(f"Transitions CSV saved: {csv_path}")
397
409
 
410
+ if close_fig:
411
+ plt.close(fig)
412
+
398
413
  return fig
@@ -1,12 +1,20 @@
1
1
  """Entity movement trajectory plots."""
2
2
 
3
3
  from typing import List, Optional, Tuple
4
+ import warnings
4
5
 
5
6
  import matplotlib.pyplot as plt
6
7
  import pandas as pd
7
8
 
8
9
  from priorityx.utils.helpers import save_dataframe_to_csv
9
10
 
11
+ # suppress noisy FancyArrowPatch fallback warnings from annotate/adjustments
12
+ warnings.filterwarnings(
13
+ "ignore",
14
+ message=".*FancyArrowPatch.*",
15
+ category=UserWarning,
16
+ )
17
+
10
18
  def plot_entity_trajectories(
11
19
  movement_df: pd.DataFrame,
12
20
  entity_name: str = "Entity",
@@ -14,11 +22,12 @@ def plot_entity_trajectories(
14
22
  max_entities: int = 10,
15
23
  figsize: Tuple[int, int] = (16, 12),
16
24
  title: Optional[str] = None,
17
- save_plot: bool = True,
25
+ save_plot: bool = False,
18
26
  save_csv: bool = False,
19
- output_dir: str = "plot",
20
- results_dir: Optional[str] = None,
27
+ plot_dir: str = "results/plot",
28
+ csv_dir: Optional[str] = "results/csv",
21
29
  temporal_granularity: str = "quarterly",
30
+ close_fig: bool = False,
22
31
  ) -> plt.Figure:
23
32
  """
24
33
  Visualize entity trajectories through priority space.
@@ -35,9 +44,12 @@ def plot_entity_trajectories(
35
44
  max_entities: Maximum entities to show (default: 10)
36
45
  figsize: Figure size (width, height)
37
46
  title: Optional custom title
38
- save_plot: Save plot to file
39
- output_dir: Output directory for saved files
47
+ save_plot: Save plot to file (default: False)
48
+ save_csv: Save trajectories to CSV (default: False)
49
+ plot_dir: Output directory for plot files
50
+ csv_dir: Output directory for CSV files
40
51
  temporal_granularity: Time granularity for file naming
52
+ close_fig: Close the figure before returning (set True if you see duplicate inline renders)
41
53
 
42
54
  Returns:
43
55
  Matplotlib figure
@@ -214,8 +226,8 @@ def plot_entity_trajectories(
214
226
 
215
227
  # set labels
216
228
  axis_fontsize = 15
217
- ax.set_xlabel("X-axis: Volume (Random Intercept)", fontsize=axis_fontsize)
218
- ax.set_ylabel("Y-axis: Growth Rate (Random Slope)", fontsize=axis_fontsize)
229
+ ax.set_xlabel(f"{entity_name} Volume (Relative)", fontsize=axis_fontsize)
230
+ ax.set_ylabel(f"{entity_name} Growth Rate (Relative)", fontsize=axis_fontsize)
219
231
 
220
232
  if title:
221
233
  ax.set_title(title, fontsize=17, fontweight="bold", pad=20)
@@ -251,8 +263,8 @@ def plot_entity_trajectories(
251
263
  ax.spines['left'].set_visible(False)
252
264
 
253
265
  # keep quadrant dividers but make them more subtle
254
- ax.axhline(0, color="lightgray", linestyle="-", alpha=0.4, linewidth=0.8, zorder=0)
255
- ax.axvline(0, color="lightgray", linestyle="-", alpha=0.4, linewidth=0.8, zorder=0)
266
+ ax.axhline(0, color="grey", linestyle="--", alpha=0.7, linewidth=1, zorder=0)
267
+ ax.axvline(0, color="grey", linestyle="--", alpha=0.7, linewidth=1, zorder=0)
256
268
 
257
269
  tick_fontsize = 15
258
270
  ax.tick_params(axis="both", which="major", labelsize=tick_fontsize)
@@ -264,19 +276,19 @@ def plot_entity_trajectories(
264
276
  import os
265
277
  from datetime import datetime
266
278
 
267
- os.makedirs(output_dir, exist_ok=True)
279
+ os.makedirs(plot_dir, exist_ok=True)
268
280
  timestamp = datetime.now().strftime("%Y%m%d")
269
281
  granularity_suffix = {
270
282
  "quarterly": "Q",
271
283
  "yearly": "Y",
272
284
  "semiannual": "S",
273
285
  }.get(temporal_granularity, "Q")
274
- plot_path = f"{output_dir}/trajectories-{entity_name.lower()}-{granularity_suffix}-{timestamp}.png"
286
+ plot_path = f"{plot_dir}/trajectories-{entity_name.lower()}-{granularity_suffix}-{timestamp}.png"
275
287
  plt.savefig(plot_path, dpi=300, bbox_inches="tight", format="png")
276
288
  print(f"Entity trajectory plot saved: {plot_path}")
277
289
 
278
290
  if save_csv:
279
- target_dir = results_dir if results_dir else f"{output_dir}/../results"
291
+ target_dir = csv_dir if csv_dir else f"{plot_dir}/../results"
280
292
  csv_path = save_dataframe_to_csv(
281
293
  movement_df,
282
294
  artifact="trajectories",
@@ -286,4 +298,7 @@ def plot_entity_trajectories(
286
298
  )
287
299
  print(f"Trajectories CSV saved: {csv_path}")
288
300
 
301
+ if close_fig:
302
+ plt.close(fig)
303
+
289
304
  return fig
@@ -139,6 +139,35 @@ wheels = [
139
139
  { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
140
140
  ]
141
141
 
142
+ [[package]]
143
+ name = "azure-core"
144
+ version = "1.36.0"
145
+ source = { registry = "https://pypi.org/simple" }
146
+ dependencies = [
147
+ { name = "requests" },
148
+ { name = "typing-extensions" },
149
+ ]
150
+ sdist = { url = "https://files.pythonhosted.org/packages/0a/c4/d4ff3bc3ddf155156460bff340bbe9533f99fac54ddea165f35a8619f162/azure_core-1.36.0.tar.gz", hash = "sha256:22e5605e6d0bf1d229726af56d9e92bc37b6e726b141a18be0b4d424131741b7", size = 351139, upload-time = "2025-10-15T00:33:49.083Z" }
151
+ wheels = [
152
+ { url = "https://files.pythonhosted.org/packages/b1/3c/b90d5afc2e47c4a45f4bba00f9c3193b0417fad5ad3bb07869f9d12832aa/azure_core-1.36.0-py3-none-any.whl", hash = "sha256:fee9923a3a753e94a259563429f3644aaf05c486d45b1215d098115102d91d3b", size = 213302, upload-time = "2025-10-15T00:33:51.058Z" },
153
+ ]
154
+
155
+ [[package]]
156
+ name = "azure-identity"
157
+ version = "1.25.1"
158
+ source = { registry = "https://pypi.org/simple" }
159
+ dependencies = [
160
+ { name = "azure-core" },
161
+ { name = "cryptography" },
162
+ { name = "msal" },
163
+ { name = "msal-extensions" },
164
+ { name = "typing-extensions" },
165
+ ]
166
+ sdist = { url = "https://files.pythonhosted.org/packages/06/8d/1a6c41c28a37eab26dc85ab6c86992c700cd3f4a597d9ed174b0e9c69489/azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456", size = 279826, upload-time = "2025-10-06T20:30:02.194Z" }
167
+ wheels = [
168
+ { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" },
169
+ ]
170
+
142
171
  [[package]]
143
172
  name = "babel"
144
173
  version = "2.17.0"
@@ -637,6 +666,71 @@ toml = [
637
666
  { name = "tomli", marker = "python_full_version <= '3.11'" },
638
667
  ]
639
668
 
669
+ [[package]]
670
+ name = "cryptography"
671
+ version = "46.0.3"
672
+ source = { registry = "https://pypi.org/simple" }
673
+ dependencies = [
674
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
675
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
676
+ ]
677
+ sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
678
+ wheels = [
679
+ { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
680
+ { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
681
+ { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
682
+ { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
683
+ { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
684
+ { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
685
+ { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
686
+ { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
687
+ { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
688
+ { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
689
+ { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
690
+ { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
691
+ { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
692
+ { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
693
+ { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
694
+ { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
695
+ { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
696
+ { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
697
+ { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
698
+ { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" },
699
+ { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" },
700
+ { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" },
701
+ { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" },
702
+ { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" },
703
+ { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
704
+ { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
705
+ { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
706
+ { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
707
+ { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
708
+ { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
709
+ { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
710
+ { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
711
+ { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
712
+ { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
713
+ { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
714
+ { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
715
+ { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
716
+ { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
717
+ { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
718
+ { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
719
+ { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
720
+ { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
721
+ { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
722
+ { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
723
+ { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
724
+ { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" },
725
+ { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" },
726
+ { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" },
727
+ { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" },
728
+ { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" },
729
+ { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" },
730
+ { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" },
731
+ { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
732
+ ]
733
+
640
734
  [[package]]
641
735
  name = "cycler"
642
736
  version = "0.12.1"
@@ -1245,6 +1339,23 @@ wheels = [
1245
1339
  { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" },
1246
1340
  ]
1247
1341
 
1342
+ [[package]]
1343
+ name = "jupytext"
1344
+ version = "1.18.1"
1345
+ source = { registry = "https://pypi.org/simple" }
1346
+ dependencies = [
1347
+ { name = "markdown-it-py" },
1348
+ { name = "mdit-py-plugins" },
1349
+ { name = "nbformat" },
1350
+ { name = "packaging" },
1351
+ { name = "pyyaml" },
1352
+ { name = "tomli", marker = "python_full_version < '3.11'" },
1353
+ ]
1354
+ sdist = { url = "https://files.pythonhosted.org/packages/9b/5d/82a614a49493fa84b2019a3e03020a8b9927208ae177b81f7e0b30330c82/jupytext-1.18.1.tar.gz", hash = "sha256:5c0962ca8d222db45cbe1848b4805dbbe3ddb957603fc96651b6cd7fd403fafb", size = 4270997, upload-time = "2025-10-19T15:06:30.992Z" }
1355
+ wheels = [
1356
+ { url = "https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-py3-none-any.whl", hash = "sha256:24f999400726a1c658beae55e15fdd2a6255ab1a418697864cd779874e6011ab", size = 167143, upload-time = "2025-10-19T15:06:28.975Z" },
1357
+ ]
1358
+
1248
1359
  [[package]]
1249
1360
  name = "kiwisolver"
1250
1361
  version = "1.4.9"
@@ -1362,6 +1473,18 @@ wheels = [
1362
1473
  { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" },
1363
1474
  ]
1364
1475
 
1476
+ [[package]]
1477
+ name = "markdown-it-py"
1478
+ version = "4.0.0"
1479
+ source = { registry = "https://pypi.org/simple" }
1480
+ dependencies = [
1481
+ { name = "mdurl" },
1482
+ ]
1483
+ sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
1484
+ wheels = [
1485
+ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
1486
+ ]
1487
+
1365
1488
  [[package]]
1366
1489
  name = "markupsafe"
1367
1490
  version = "3.0.3"
@@ -1534,6 +1657,27 @@ wheels = [
1534
1657
  { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" },
1535
1658
  ]
1536
1659
 
1660
+ [[package]]
1661
+ name = "mdit-py-plugins"
1662
+ version = "0.5.0"
1663
+ source = { registry = "https://pypi.org/simple" }
1664
+ dependencies = [
1665
+ { name = "markdown-it-py" },
1666
+ ]
1667
+ sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" }
1668
+ wheels = [
1669
+ { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" },
1670
+ ]
1671
+
1672
+ [[package]]
1673
+ name = "mdurl"
1674
+ version = "0.1.2"
1675
+ source = { registry = "https://pypi.org/simple" }
1676
+ sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
1677
+ wheels = [
1678
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
1679
+ ]
1680
+
1537
1681
  [[package]]
1538
1682
  name = "mistune"
1539
1683
  version = "3.1.4"
@@ -1546,6 +1690,76 @@ wheels = [
1546
1690
  { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" },
1547
1691
  ]
1548
1692
 
1693
+ [[package]]
1694
+ name = "msal"
1695
+ version = "1.34.0"
1696
+ source = { registry = "https://pypi.org/simple" }
1697
+ dependencies = [
1698
+ { name = "cryptography" },
1699
+ { name = "pyjwt", extra = ["crypto"] },
1700
+ { name = "requests" },
1701
+ ]
1702
+ sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" }
1703
+ wheels = [
1704
+ { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" },
1705
+ ]
1706
+
1707
+ [[package]]
1708
+ name = "msal-extensions"
1709
+ version = "1.3.1"
1710
+ source = { registry = "https://pypi.org/simple" }
1711
+ dependencies = [
1712
+ { name = "msal" },
1713
+ ]
1714
+ sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" }
1715
+ wheels = [
1716
+ { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" },
1717
+ ]
1718
+
1719
+ [[package]]
1720
+ name = "mssql-python"
1721
+ version = "1.0.0"
1722
+ source = { registry = "https://pypi.org/simple" }
1723
+ dependencies = [
1724
+ { name = "azure-identity" },
1725
+ ]
1726
+ wheels = [
1727
+ { url = "https://files.pythonhosted.org/packages/5d/b9/4b2882a7b295b5392b854e670f48a409739adb215d169dad76602b7c991a/mssql_python-1.0.0-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:b5be5727da0961eb11e14140e17aa7e96f490820fe512e3c1d320c8597f3c64c", size = 22658812, upload-time = "2025-11-18T18:14:13.588Z" },
1728
+ { url = "https://files.pythonhosted.org/packages/31/84/5ea86fe34dee5ee28171c005df0703ccc19e386c26b954bdd05568d2aef1/mssql_python-1.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:e29ae83d7426684137cc72e2788b0bd8a96675dde886e26c5ec30cb387a7e192", size = 22308637, upload-time = "2025-11-18T18:14:16.423Z" },
1729
+ { url = "https://files.pythonhosted.org/packages/43/05/62b4d7779f0153036dfdfc746919f51e25e2e7321ff75821dc157cd7d9fc/mssql_python-1.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:bfae644108f48cc2afc996e9cf908ae7c6f26a2856348caf1b60a27120527314", size = 22329143, upload-time = "2025-11-18T18:14:19.192Z" },
1730
+ { url = "https://files.pythonhosted.org/packages/8e/55/7cb38fe0a49573df108c2598b42b4c6278feb708679592509f511dfc81cf/mssql_python-1.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:35982ad272550625c0f312d67be431862130d47da59bbaa97c457a2f0e11626a", size = 22248391, upload-time = "2025-11-18T18:14:21.628Z" },
1731
+ { url = "https://files.pythonhosted.org/packages/fa/9a/f4921de1ecf0df8544c8b43ae4631b9337e0cbf436b2afca2b5ff4f1b49d/mssql_python-1.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0a7c2e6b7e1fcfad91153e369864eab536a797ded8f57b2f478f25291d4e12c3", size = 22251931, upload-time = "2025-11-18T18:14:24.982Z" },
1732
+ { url = "https://files.pythonhosted.org/packages/d7/14/460dc6f9a7e5a8dc869290c4bcd2f07f3e21a1790206b8968ef8bf71aa7e/mssql_python-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:78a53248cdeac72a0b426c2d80d978e91ab95f678428a2cdda3187bd810c78b0", size = 12488974, upload-time = "2025-11-18T18:14:27.323Z" },
1733
+ { url = "https://files.pythonhosted.org/packages/2f/0b/841a472488e4c5b5cb2dce632ac909ca5c3b53848528b1d5dcf01c436a1f/mssql_python-1.0.0-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:8405a53f67d39ace5a27e9741bfd34a13016fbfdddf8011daddfbc563bea0a88", size = 22659254, upload-time = "2025-11-18T18:14:29.673Z" },
1734
+ { url = "https://files.pythonhosted.org/packages/fc/a2/df5f503280cd031ee14bcb7692dd996b797e75dddcbaf42ff00d2386a69a/mssql_python-1.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1c202a19b848dd4e1a850dfd23014a48fb4b4f6226921f2e6f270c142f6a37a0", size = 23165735, upload-time = "2025-11-18T18:14:32.178Z" },
1735
+ { url = "https://files.pythonhosted.org/packages/01/3e/6678707c7f431f134ac1f5eecaad580418b3b6b5d7b5a6f8153cd4f226a4/mssql_python-1.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a10114713b3e0defda4ac5bb96c4040cff3fd9f08eb5a79ce401c774db6f6f8e", size = 23206243, upload-time = "2025-11-18T18:14:34.633Z" },
1736
+ { url = "https://files.pythonhosted.org/packages/13/eb/620f4ea3a2ca873651cd9841ebe90780c0244e360078eebcbecb16510d2b/mssql_python-1.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3614b77e030c655b1453f15c243c72a0b794bdea3c0816a0efe3cad5438b946a", size = 23045592, upload-time = "2025-11-18T18:14:37.078Z" },
1737
+ { url = "https://files.pythonhosted.org/packages/4d/b6/22a60e5ebf4ddf5cc55a6e29938cc075f735eed0f1ade3e382daf5f400b0/mssql_python-1.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab189aa1657e8a169730cf9c16684e74f5ef11a58b4e1213a7a6fbc06d4a943e", size = 23052390, upload-time = "2025-11-18T18:14:39.625Z" },
1738
+ { url = "https://files.pythonhosted.org/packages/47/f3/73d17fd1f33213324fe53d2470f39562d017865d91988dc8836a19ec572c/mssql_python-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb7ea5d4c1fb17ac28e11a2c398879c4a325d4885a5fbea031fe4ac88e0b5b22", size = 12489425, upload-time = "2025-11-18T18:14:42.894Z" },
1739
+ { url = "https://files.pythonhosted.org/packages/17/b1/a27790a12d42546f28075eb13a07ac0eff4cb5e86f2d86876b536d3837df/mssql_python-1.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:a5e00c4e3281ba8fef25b313edb8d1ea01730f83e1f7e813eae4d02b7b9e1d43", size = 15639253, upload-time = "2025-11-18T18:14:45.043Z" },
1740
+ { url = "https://files.pythonhosted.org/packages/79/35/1fc07bf9070835809d3ab0954fdda7aab4155e13b0abb10d578f6cdaf55b/mssql_python-1.0.0-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4644dad6abc52867c2838de1968cc7bf745fc32e190d0233f1c623725280c297", size = 22663922, upload-time = "2025-11-18T18:14:47.497Z" },
1741
+ { url = "https://files.pythonhosted.org/packages/6f/15/636d35e9d061f592ac685a97bdb2cbbc34ab62a08a1bff46b96df8c52123/mssql_python-1.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:35ee9cdc43c367bcbe8d8f926e278badab3954b238073879c9df8d18930d505e", size = 24024548, upload-time = "2025-11-18T18:14:49.959Z" },
1742
+ { url = "https://files.pythonhosted.org/packages/93/88/ab8db8d80d9cfc4c2cdd69c840cace61f8e6eac9bd668a26d4c65e92756c/mssql_python-1.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5ca5040981889ae70a315a99b8c8f766e46ff727e57b1ab73b0396e1c4cb9345", size = 24085942, upload-time = "2025-11-18T18:14:52.501Z" },
1743
+ { url = "https://files.pythonhosted.org/packages/cf/72/516918898f6df3cd94adf625ee96011352eb4a94fbe247aa79893237d14f/mssql_python-1.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bb80e9f97206ca1dd901c2cf26fc924514fc752b0cc0e3a7ad12122d00942a7", size = 23844526, upload-time = "2025-11-18T18:14:55.143Z" },
1744
+ { url = "https://files.pythonhosted.org/packages/47/f7/456bdcc13792654b4ffb6dbb3a6ffbcdd70571a8c6879d64522ef8400088/mssql_python-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b3037c259c875c69c7b2cb839118ca4a4f40ce2121128037d2cab09a6c2100b7", size = 23855903, upload-time = "2025-11-18T18:14:57.869Z" },
1745
+ { url = "https://files.pythonhosted.org/packages/d3/d9/67b82e0f7f3db4cf23bdd9d686ba575a5a3a4b8fc48df1106ec784aed1e4/mssql_python-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:818dbbecef2ac472ad1e4cef8895fd41bbde35cf6195c5239ac8fa11e4ccd426", size = 12490794, upload-time = "2025-11-18T18:15:00.31Z" },
1746
+ { url = "https://files.pythonhosted.org/packages/94/52/501138846d2f18b61c243ca2afb110f29ad1010ee19f141b709511e3c0dd/mssql_python-1.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:f2961d44cdd2814cf178186d5dfe578d8826b8a9e23f5e2da25dda6b5cd494fe", size = 15639660, upload-time = "2025-11-18T18:15:03.058Z" },
1747
+ { url = "https://files.pythonhosted.org/packages/d4/b7/67dd4658fca022f7db8c09e8fa559a5e892599569609689d028467557489/mssql_python-1.0.0-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:194d99954fb7c903e933fea8e4b4f1f31a11fda9fad1e0069e65c29e5f200de8", size = 22664273, upload-time = "2025-11-18T18:15:05.369Z" },
1748
+ { url = "https://files.pythonhosted.org/packages/c2/49/4c12db12ed04640cc0f4878cb8e94990dccb2277d2499b30bac810d99f4c/mssql_python-1.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:6873ae4accc14bc46cdf467bad7b96324d0281de99a3d375b119b58d41d5566a", size = 24883416, upload-time = "2025-11-18T18:15:08.053Z" },
1749
+ { url = "https://files.pythonhosted.org/packages/e1/d5/89d12d606058c03e9ef0a05ef28d39bb899dbb5406dbccca0d9326f6ae03/mssql_python-1.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:28a45c87df1d40cfb867cfe8ed9623a9566fa91702e8aacdab8afda717f1ca2e", size = 24966127, upload-time = "2025-11-18T18:15:10.62Z" },
1750
+ { url = "https://files.pythonhosted.org/packages/aa/05/d24fd7a03702ee2989fcfa38415ad1f0e7540877272c092138d6d8c8d839/mssql_python-1.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f53c6d93b2b06919db62db0dfaba2c7ff5e0876c5ab236362273647cd42fdb60", size = 24643713, upload-time = "2025-11-18T18:15:13.245Z" },
1751
+ { url = "https://files.pythonhosted.org/packages/ed/47/dee18fa0103440524dcdf66a029df7ff04f9ff2aa8105863cfbefd9ca4e4/mssql_python-1.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36b619743b004b2a9cfa291465705677d3ae0ee6ebee552d6114a208f215045e", size = 24659902, upload-time = "2025-11-18T18:15:16.012Z" },
1752
+ { url = "https://files.pythonhosted.org/packages/90/33/124a3c614d0a170b4939437be73f923f24db3ef65408f035f1849d4d83b3/mssql_python-1.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:dbde23191f67c4ad667476b4f2ede9894676c87aa20e075ec7d479fea8fef650", size = 12490846, upload-time = "2025-11-18T18:15:18.698Z" },
1753
+ { url = "https://files.pythonhosted.org/packages/ab/5c/aba670223a8307e0e53f5e9df79d8abbbf9a24de109a432db004b1faabaf/mssql_python-1.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:26e4c63eb924fe8ec4f9d8a720bd85d9af97df4f026b08c22a0beb55e8896d2d", size = 15639617, upload-time = "2025-11-18T18:15:20.901Z" },
1754
+ { url = "https://files.pythonhosted.org/packages/6d/ab/6151f8bdc08e6cb17566dc63973e1d24534d43c317f921123a5b0e7004d9/mssql_python-1.0.0-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:8cd7c6aaf771de0758a5f06fc8e72994bc526f958b4e8684c435297b7c8e3ef3", size = 22664258, upload-time = "2025-11-18T18:15:24.157Z" },
1755
+ { url = "https://files.pythonhosted.org/packages/cf/0f/cc79b8879f95f79e53010e960c8650a9dcf51539071949b58d29f8cd1144/mssql_python-1.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:b544948ea470e04a000893888d75fd4ae6c410c7434512e55442324686433455", size = 25742397, upload-time = "2025-11-18T18:15:27.052Z" },
1756
+ { url = "https://files.pythonhosted.org/packages/48/4f/29f29d386f19a935cc6d250f1f7c297723762f53a9a0180a03ebbcc28b56/mssql_python-1.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:a85ce62e6cf30e5918fff54cf21ce386bed5f2fbcb3e7c433697c5196213008a", size = 25846056, upload-time = "2025-11-18T18:15:30.041Z" },
1757
+ { url = "https://files.pythonhosted.org/packages/f5/06/594251d0fb35782851413a27d0924e11fdb90068fcda2483efabefc52fce/mssql_python-1.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2d81f5ca4252e879dc8bb25eecf7cb37dc4c3ddd64d0aa4a1767fb0a79825fba", size = 25442913, upload-time = "2025-11-18T18:15:33.008Z" },
1758
+ { url = "https://files.pythonhosted.org/packages/2c/32/5c73d63d63572ba6840698436dd2b7b986b8294e8ac86cba762856292c60/mssql_python-1.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:291ee3bc6dd5c62672ec882701efe0792d48c78573e191605e557728a54c4a62", size = 25463798, upload-time = "2025-11-18T18:15:35.818Z" },
1759
+ { url = "https://files.pythonhosted.org/packages/4e/7d/eb7151156653db5081f19cd25f44cf55691b9cb49d0329396f480e107587/mssql_python-1.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:daa2f2c72c85505c797cf2acbfcbac33b0600e1d1beb98d0343f7a91b40ec76a", size = 12897695, upload-time = "2025-11-18T18:15:38.191Z" },
1760
+ { url = "https://files.pythonhosted.org/packages/86/bc/5b20c353f67eb28523685110fe85a401b5e32a49d5bd260b7e1acebaac59/mssql_python-1.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:3c615e0d7093c13b23dda0725845093f9507fba4dd2c0e77d8b40afa9eb1cb86", size = 16144926, upload-time = "2025-11-18T18:15:41.489Z" },
1761
+ ]
1762
+
1549
1763
  [[package]]
1550
1764
  name = "nbclient"
1551
1765
  version = "0.10.2"
@@ -2056,7 +2270,7 @@ wheels = [
2056
2270
 
2057
2271
  [[package]]
2058
2272
  name = "priorityx"
2059
- version = "0.2.1"
2273
+ version = "0.2.2"
2060
2274
  source = { editable = "." }
2061
2275
  dependencies = [
2062
2276
  { name = "adjusttext" },
@@ -2068,6 +2282,8 @@ dependencies = [
2068
2282
  [package.dev-dependencies]
2069
2283
  dev = [
2070
2284
  { name = "jupyter" },
2285
+ { name = "jupytext" },
2286
+ { name = "mssql-python" },
2071
2287
  { name = "polars" },
2072
2288
  { name = "priorityx" },
2073
2289
  { name = "pyarrow" },
@@ -2087,6 +2303,8 @@ requires-dist = [
2087
2303
  [package.metadata.requires-dev]
2088
2304
  dev = [
2089
2305
  { name = "jupyter", specifier = ">=1.1.1" },
2306
+ { name = "jupytext", specifier = ">=1.18.1" },
2307
+ { name = "mssql-python", specifier = ">=1.0.0" },
2090
2308
  { name = "polars", specifier = ">=1.35.1" },
2091
2309
  { name = "priorityx", editable = "." },
2092
2310
  { name = "pyarrow", specifier = ">=22.0.0" },
@@ -2235,6 +2453,20 @@ wheels = [
2235
2453
  { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
2236
2454
  ]
2237
2455
 
2456
+ [[package]]
2457
+ name = "pyjwt"
2458
+ version = "2.10.1"
2459
+ source = { registry = "https://pypi.org/simple" }
2460
+ sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
2461
+ wheels = [
2462
+ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
2463
+ ]
2464
+
2465
+ [package.optional-dependencies]
2466
+ crypto = [
2467
+ { name = "cryptography" },
2468
+ ]
2469
+
2238
2470
  [[package]]
2239
2471
  name = "pyparsing"
2240
2472
  version = "3.2.5"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes