procurelytics-ai 0.1.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.
- procurelytics_ai-0.1.0/.gitignore +12 -0
- procurelytics_ai-0.1.0/LICENSE +0 -0
- procurelytics_ai-0.1.0/PKG-INFO +217 -0
- procurelytics_ai-0.1.0/README.md +188 -0
- procurelytics_ai-0.1.0/pyproject.toml +48 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/__init__.py +3 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/cli.py +61 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/config.py +7 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/datasets/__init__.py +0 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/datasets/loaders.py +13 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/datasets/schema.py +13 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/esg/__init__.py +0 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/esg/scoring.py +39 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/exceptions.py +11 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/governance/__init__.py +0 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/governance/responsible_ai.py +47 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/insights/__init__.py +0 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/insights/cards.py +18 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/insights/prompts.py +16 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/ml/__init__.py +0 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/ml/features.py +35 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/ml/models.py +24 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/ml/predict.py +32 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/process_mining/__init__.py +0 -0
- procurelytics_ai-0.1.0/src/procurelytics_ai/process_mining/mining.py +52 -0
- procurelytics_ai-0.1.0/tests/test_process_mining.py +0 -0
- procurelytics_ai-0.1.0/tests/test_responsible_ai.py +14 -0
|
File without changes
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: procurelytics-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-powered procurement analytics: predictive sourcing, spend optimization, process mining, ESG scoring, and responsible AI governance.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jvasanthada/procurelytics-ai
|
|
6
|
+
Project-URL: Issues, https://github.com/jvasanthada/procurelytics-ai/issues
|
|
7
|
+
Author: Jagadeesh Vasanthada
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai,esg,governance,ml,process-mining,procurement,sourcing,spend-analytics
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Office/Business
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: numpy>=1.26.0
|
|
23
|
+
Requires-Dist: pandas>=2.2.0
|
|
24
|
+
Requires-Dist: pydantic>=2.7.0
|
|
25
|
+
Requires-Dist: rich>=13.7.1
|
|
26
|
+
Requires-Dist: scikit-learn>=1.4.0
|
|
27
|
+
Requires-Dist: typer>=0.12.3
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# procurelytics-ai
|
|
31
|
+
|
|
32
|
+
**procurelytics-ai** is an open-source Python library that delivers **AI-powered procurement analytics** for modern procurement and ERP environments.
|
|
33
|
+
|
|
34
|
+
It is designed to help teams transform raw procurement data into:
|
|
35
|
+
- **Predictive sourcing signals** (ML-based supplier health / risk scoring)
|
|
36
|
+
- **Spend optimization intelligence** (category/supplier consolidation hooks)
|
|
37
|
+
- **Process mining foundations** (variants, lead-time patterns; extensible)
|
|
38
|
+
- **ESG & sustainability scoring** (supplier responsibility insights)
|
|
39
|
+
- **Responsible AI governance checks** (data quality and representation proxy checks)
|
|
40
|
+
- **GenAI-ready prompts** (provider-agnostic prompts for copilots and LLMs)
|
|
41
|
+
|
|
42
|
+
The library is vendor-neutral and works with exports from **SAP/ERP systems**, procurement platforms, and analytics warehouses.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Why procurelytics-ai?
|
|
47
|
+
|
|
48
|
+
Procurement teams often face these challenges:
|
|
49
|
+
- Lack of visibility into supplier performance risks
|
|
50
|
+
- Difficulty detecting where procurement cycles slow down
|
|
51
|
+
- ESG and compliance pressures with inconsistent data
|
|
52
|
+
- A need for executive-friendly insights, not just raw reports
|
|
53
|
+
|
|
54
|
+
**procurelytics-ai** provides explainable analytics primitives plus AI-ready outputs so you can:
|
|
55
|
+
- prioritize supplier strategies,
|
|
56
|
+
- reduce risk,
|
|
57
|
+
- improve resilience, and
|
|
58
|
+
- accelerate decision-making.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
Install from PyPI:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install procurelytics-ai
|
|
68
|
+
```
|
|
69
|
+
## Requirements
|
|
70
|
+
- Python 3.9+
|
|
71
|
+
|
|
72
|
+
## Quick Start (CLI)
|
|
73
|
+
### 1) Prepare a supplier dataset
|
|
74
|
+
|
|
75
|
+
#### Supported formats:
|
|
76
|
+
|
|
77
|
+
- .csv
|
|
78
|
+
|
|
79
|
+
- .json
|
|
80
|
+
|
|
81
|
+
- .jsonl
|
|
82
|
+
|
|
83
|
+
#### Recommended columns:
|
|
84
|
+
|
|
85
|
+
- supplier_id (required)
|
|
86
|
+
|
|
87
|
+
- esg_score (0..100)
|
|
88
|
+
|
|
89
|
+
- on_time_rate (0..1)
|
|
90
|
+
|
|
91
|
+
- defect_rate (0..1)
|
|
92
|
+
|
|
93
|
+
- country (optional)
|
|
94
|
+
|
|
95
|
+
- category (optional)
|
|
96
|
+
|
|
97
|
+
## Example CSV:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
supplier_id,country,category,esg_score,on_time_rate,defect_rate
|
|
101
|
+
SUP-1,US,IT,88,0.93,0.01
|
|
102
|
+
SUP-2,IN,Hardware,72,0.84,0.03
|
|
103
|
+
SUP-3,US,IT,55,0.78,0.05
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 2) Run supplier scoring
|
|
107
|
+
```
|
|
108
|
+
procurelytics score-suppliers suppliers.csv
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### You will get:
|
|
112
|
+
|
|
113
|
+
- top supplier ranking (health score)
|
|
114
|
+
|
|
115
|
+
- governance findings (data quality + representation proxy checks)
|
|
116
|
+
|
|
117
|
+
- a provider-agnostic GenAI prompt you can paste into any LLM/copilot
|
|
118
|
+
|
|
119
|
+
## Programmatic Usage
|
|
120
|
+
### Supplier feature engineering + ML scoring
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
import pandas as pd
|
|
124
|
+
from procurelytics_ai.ml.features import build_supplier_features
|
|
125
|
+
from procurelytics_ai.ml.predict import train_and_score_suppliers
|
|
126
|
+
|
|
127
|
+
df = pd.read_csv("suppliers.csv")
|
|
128
|
+
X = build_supplier_features(df)
|
|
129
|
+
ranked = train_and_score_suppliers(X)
|
|
130
|
+
|
|
131
|
+
print(ranked.head(10))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### ESG scoring (optional)
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
from procurelytics_ai.esg.scoring import weighted_esg_score
|
|
138
|
+
|
|
139
|
+
score = weighted_esg_score(
|
|
140
|
+
supplier_id="SUP-1",
|
|
141
|
+
factors={"co2_intensity": 0.7, "compliance": 0.9, "labor_risk": 0.8}
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
print(score.score, score.rationale)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Responsible AI / Governance
|
|
148
|
+
|
|
149
|
+
- procurelytics-ai includes lightweight governance checks to support safer analytics:
|
|
150
|
+
|
|
151
|
+
#### Data quality report
|
|
152
|
+
|
|
153
|
+
- warns on high missingness
|
|
154
|
+
|
|
155
|
+
- flags invalid ESG score ranges
|
|
156
|
+
|
|
157
|
+
#### Representation proxy check
|
|
158
|
+
|
|
159
|
+
- warns when one group dominates the dataset (e.g., one country is 80%+)
|
|
160
|
+
|
|
161
|
+
- These checks are intentionally lightweight and transparent—useful for production workflows without heavy dependencies.
|
|
162
|
+
|
|
163
|
+
### GenAI Support
|
|
164
|
+
|
|
165
|
+
- procurelytics-ai generates a provider-agnostic GenAI prompt, so you can feed results into:
|
|
166
|
+
|
|
167
|
+
- internal copilots
|
|
168
|
+
|
|
169
|
+
- chatbots
|
|
170
|
+
|
|
171
|
+
- LLM tools (OpenAI / Vertex / Azure / local models)
|
|
172
|
+
|
|
173
|
+
- This package does not force any vendor lock-in.
|
|
174
|
+
|
|
175
|
+
### Testing
|
|
176
|
+
```
|
|
177
|
+
pytest
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Roadmap
|
|
181
|
+
|
|
182
|
+
#### Planned enhancements:
|
|
183
|
+
|
|
184
|
+
- CSV/BigQuery ingestion utilities
|
|
185
|
+
|
|
186
|
+
- Spend optimization modules (leakage detection, consolidation suggestions)
|
|
187
|
+
|
|
188
|
+
- Process mining expansion (rework loops, conformance scoring)
|
|
189
|
+
|
|
190
|
+
- Optional LLM adapters (kept modular)
|
|
191
|
+
|
|
192
|
+
#### Contributing
|
|
193
|
+
|
|
194
|
+
##### Contributions are welcome:
|
|
195
|
+
|
|
196
|
+
- new analytics modules
|
|
197
|
+
|
|
198
|
+
- data adapters for ERP platforms
|
|
199
|
+
|
|
200
|
+
- examples and notebooks
|
|
201
|
+
|
|
202
|
+
- documentation improvements
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
## MIT License
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
MIT License
|
|
209
|
+
|
|
210
|
+
Copyright (c) 2026 Jagadeesh Vasanthada
|
|
211
|
+
|
|
212
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
213
|
+
|
|
214
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
215
|
+
|
|
216
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
217
|
+
```
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# procurelytics-ai
|
|
2
|
+
|
|
3
|
+
**procurelytics-ai** is an open-source Python library that delivers **AI-powered procurement analytics** for modern procurement and ERP environments.
|
|
4
|
+
|
|
5
|
+
It is designed to help teams transform raw procurement data into:
|
|
6
|
+
- **Predictive sourcing signals** (ML-based supplier health / risk scoring)
|
|
7
|
+
- **Spend optimization intelligence** (category/supplier consolidation hooks)
|
|
8
|
+
- **Process mining foundations** (variants, lead-time patterns; extensible)
|
|
9
|
+
- **ESG & sustainability scoring** (supplier responsibility insights)
|
|
10
|
+
- **Responsible AI governance checks** (data quality and representation proxy checks)
|
|
11
|
+
- **GenAI-ready prompts** (provider-agnostic prompts for copilots and LLMs)
|
|
12
|
+
|
|
13
|
+
The library is vendor-neutral and works with exports from **SAP/ERP systems**, procurement platforms, and analytics warehouses.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Why procurelytics-ai?
|
|
18
|
+
|
|
19
|
+
Procurement teams often face these challenges:
|
|
20
|
+
- Lack of visibility into supplier performance risks
|
|
21
|
+
- Difficulty detecting where procurement cycles slow down
|
|
22
|
+
- ESG and compliance pressures with inconsistent data
|
|
23
|
+
- A need for executive-friendly insights, not just raw reports
|
|
24
|
+
|
|
25
|
+
**procurelytics-ai** provides explainable analytics primitives plus AI-ready outputs so you can:
|
|
26
|
+
- prioritize supplier strategies,
|
|
27
|
+
- reduce risk,
|
|
28
|
+
- improve resilience, and
|
|
29
|
+
- accelerate decision-making.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Install from PyPI:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install procurelytics-ai
|
|
39
|
+
```
|
|
40
|
+
## Requirements
|
|
41
|
+
- Python 3.9+
|
|
42
|
+
|
|
43
|
+
## Quick Start (CLI)
|
|
44
|
+
### 1) Prepare a supplier dataset
|
|
45
|
+
|
|
46
|
+
#### Supported formats:
|
|
47
|
+
|
|
48
|
+
- .csv
|
|
49
|
+
|
|
50
|
+
- .json
|
|
51
|
+
|
|
52
|
+
- .jsonl
|
|
53
|
+
|
|
54
|
+
#### Recommended columns:
|
|
55
|
+
|
|
56
|
+
- supplier_id (required)
|
|
57
|
+
|
|
58
|
+
- esg_score (0..100)
|
|
59
|
+
|
|
60
|
+
- on_time_rate (0..1)
|
|
61
|
+
|
|
62
|
+
- defect_rate (0..1)
|
|
63
|
+
|
|
64
|
+
- country (optional)
|
|
65
|
+
|
|
66
|
+
- category (optional)
|
|
67
|
+
|
|
68
|
+
## Example CSV:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
supplier_id,country,category,esg_score,on_time_rate,defect_rate
|
|
72
|
+
SUP-1,US,IT,88,0.93,0.01
|
|
73
|
+
SUP-2,IN,Hardware,72,0.84,0.03
|
|
74
|
+
SUP-3,US,IT,55,0.78,0.05
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 2) Run supplier scoring
|
|
78
|
+
```
|
|
79
|
+
procurelytics score-suppliers suppliers.csv
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### You will get:
|
|
83
|
+
|
|
84
|
+
- top supplier ranking (health score)
|
|
85
|
+
|
|
86
|
+
- governance findings (data quality + representation proxy checks)
|
|
87
|
+
|
|
88
|
+
- a provider-agnostic GenAI prompt you can paste into any LLM/copilot
|
|
89
|
+
|
|
90
|
+
## Programmatic Usage
|
|
91
|
+
### Supplier feature engineering + ML scoring
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
import pandas as pd
|
|
95
|
+
from procurelytics_ai.ml.features import build_supplier_features
|
|
96
|
+
from procurelytics_ai.ml.predict import train_and_score_suppliers
|
|
97
|
+
|
|
98
|
+
df = pd.read_csv("suppliers.csv")
|
|
99
|
+
X = build_supplier_features(df)
|
|
100
|
+
ranked = train_and_score_suppliers(X)
|
|
101
|
+
|
|
102
|
+
print(ranked.head(10))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### ESG scoring (optional)
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
from procurelytics_ai.esg.scoring import weighted_esg_score
|
|
109
|
+
|
|
110
|
+
score = weighted_esg_score(
|
|
111
|
+
supplier_id="SUP-1",
|
|
112
|
+
factors={"co2_intensity": 0.7, "compliance": 0.9, "labor_risk": 0.8}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
print(score.score, score.rationale)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Responsible AI / Governance
|
|
119
|
+
|
|
120
|
+
- procurelytics-ai includes lightweight governance checks to support safer analytics:
|
|
121
|
+
|
|
122
|
+
#### Data quality report
|
|
123
|
+
|
|
124
|
+
- warns on high missingness
|
|
125
|
+
|
|
126
|
+
- flags invalid ESG score ranges
|
|
127
|
+
|
|
128
|
+
#### Representation proxy check
|
|
129
|
+
|
|
130
|
+
- warns when one group dominates the dataset (e.g., one country is 80%+)
|
|
131
|
+
|
|
132
|
+
- These checks are intentionally lightweight and transparent—useful for production workflows without heavy dependencies.
|
|
133
|
+
|
|
134
|
+
### GenAI Support
|
|
135
|
+
|
|
136
|
+
- procurelytics-ai generates a provider-agnostic GenAI prompt, so you can feed results into:
|
|
137
|
+
|
|
138
|
+
- internal copilots
|
|
139
|
+
|
|
140
|
+
- chatbots
|
|
141
|
+
|
|
142
|
+
- LLM tools (OpenAI / Vertex / Azure / local models)
|
|
143
|
+
|
|
144
|
+
- This package does not force any vendor lock-in.
|
|
145
|
+
|
|
146
|
+
### Testing
|
|
147
|
+
```
|
|
148
|
+
pytest
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Roadmap
|
|
152
|
+
|
|
153
|
+
#### Planned enhancements:
|
|
154
|
+
|
|
155
|
+
- CSV/BigQuery ingestion utilities
|
|
156
|
+
|
|
157
|
+
- Spend optimization modules (leakage detection, consolidation suggestions)
|
|
158
|
+
|
|
159
|
+
- Process mining expansion (rework loops, conformance scoring)
|
|
160
|
+
|
|
161
|
+
- Optional LLM adapters (kept modular)
|
|
162
|
+
|
|
163
|
+
#### Contributing
|
|
164
|
+
|
|
165
|
+
##### Contributions are welcome:
|
|
166
|
+
|
|
167
|
+
- new analytics modules
|
|
168
|
+
|
|
169
|
+
- data adapters for ERP platforms
|
|
170
|
+
|
|
171
|
+
- examples and notebooks
|
|
172
|
+
|
|
173
|
+
- documentation improvements
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
## MIT License
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
MIT License
|
|
180
|
+
|
|
181
|
+
Copyright (c) 2026 Jagadeesh Vasanthada
|
|
182
|
+
|
|
183
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
184
|
+
|
|
185
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
186
|
+
|
|
187
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
188
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "procurelytics-ai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AI-powered procurement analytics: predictive sourcing, spend optimization, process mining, ESG scoring, and responsible AI governance."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Jagadeesh Vasanthada"}]
|
|
13
|
+
keywords = ["procurement", "ai", "ml", "process-mining", "esg", "sourcing", "spend-analytics", "governance"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
18
|
+
"Intended Audience :: Information Technology",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Topic :: Office/Business",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
dependencies = [
|
|
28
|
+
"pydantic>=2.7.0",
|
|
29
|
+
"typer>=0.12.3",
|
|
30
|
+
"rich>=13.7.1",
|
|
31
|
+
"pandas>=2.2.0",
|
|
32
|
+
"numpy>=1.26.0",
|
|
33
|
+
"scikit-learn>=1.4.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/jvasanthada/procurelytics-ai"
|
|
38
|
+
Issues = "https://github.com/jvasanthada/procurelytics-ai/issues"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
procurelytics = "procurelytics_ai.cli:app"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/procurelytics_ai"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import typer
|
|
4
|
+
from rich import print
|
|
5
|
+
|
|
6
|
+
from .datasets.loaders import load_table
|
|
7
|
+
from .ml.features import build_supplier_features
|
|
8
|
+
from .ml.predict import train_and_score_suppliers
|
|
9
|
+
from .governance.responsible_ai import data_quality_report, representation_proxy_check
|
|
10
|
+
from .insights.cards import InsightCard, render_cards
|
|
11
|
+
from .insights.prompts import genai_prompt
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="procurelytics-ai: AI-powered procurement analytics (predictive sourcing + governance + ESG).")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command()
|
|
17
|
+
def score_suppliers(input_file: Path):
|
|
18
|
+
"""
|
|
19
|
+
Score suppliers using a baseline ML model + Responsible AI checks.
|
|
20
|
+
Input file: CSV/JSON/JSONL with supplier attributes.
|
|
21
|
+
"""
|
|
22
|
+
df = load_table(input_file)
|
|
23
|
+
X = build_supplier_features(df)
|
|
24
|
+
ranked = train_and_score_suppliers(X)
|
|
25
|
+
|
|
26
|
+
dq = data_quality_report(df)
|
|
27
|
+
rep = representation_proxy_check(df)
|
|
28
|
+
|
|
29
|
+
cards = [
|
|
30
|
+
InsightCard(
|
|
31
|
+
title="Supplier health ranking (ML baseline)",
|
|
32
|
+
summary="Suppliers ranked using risk signals: late/quality/ESG risk (explainable baseline model).",
|
|
33
|
+
data={"top_suppliers": ranked.head(10).to_dict(orient="records")},
|
|
34
|
+
)
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
findings = [f.__dict__ for f in (dq + rep)]
|
|
38
|
+
if findings:
|
|
39
|
+
cards.append(
|
|
40
|
+
InsightCard(
|
|
41
|
+
title="Responsible AI / governance findings",
|
|
42
|
+
summary="Data quality and representation proxy checks for safer decision-making.",
|
|
43
|
+
data={"findings": findings},
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
results = {
|
|
48
|
+
"top_suppliers": ranked.head(10).to_dict(orient="records"),
|
|
49
|
+
"governance_findings": findings,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
print("[bold]Insights[/bold]")
|
|
53
|
+
print(render_cards(cards))
|
|
54
|
+
|
|
55
|
+
print("\n[bold]GenAI prompt (provider-agnostic)[/bold]")
|
|
56
|
+
print(genai_prompt(results))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
app()
|
|
61
|
+
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_table(path: str | Path) -> pd.DataFrame:
|
|
7
|
+
path = Path(path)
|
|
8
|
+
if path.suffix.lower() == ".csv":
|
|
9
|
+
return pd.read_csv(path)
|
|
10
|
+
if path.suffix.lower() in [".json", ".jsonl"]:
|
|
11
|
+
return pd.read_json(path, lines=path.suffix.lower() == ".jsonl")
|
|
12
|
+
raise ValueError("Unsupported file type. Use .csv, .json, or .jsonl")
|
|
13
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SupplierRecord(BaseModel):
|
|
7
|
+
supplier_id: str
|
|
8
|
+
country: Optional[str] = None
|
|
9
|
+
category: Optional[str] = None
|
|
10
|
+
esg_score: Optional[float] = None # 0..100
|
|
11
|
+
on_time_rate: Optional[float] = None # 0..1
|
|
12
|
+
defect_rate: Optional[float] = None # 0..1
|
|
13
|
+
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ESGScore:
|
|
8
|
+
supplier_id: str
|
|
9
|
+
score: float # 0..100
|
|
10
|
+
rationale: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def weighted_esg_score(
|
|
14
|
+
supplier_id: str,
|
|
15
|
+
factors: Dict[str, float],
|
|
16
|
+
weights: Optional[Dict[str, float]] = None,
|
|
17
|
+
) -> ESGScore:
|
|
18
|
+
"""
|
|
19
|
+
factors should be normalized 0..1 where higher is better
|
|
20
|
+
e.g. {"co2_intensity": 0.7, "compliance": 0.9}
|
|
21
|
+
"""
|
|
22
|
+
if not factors:
|
|
23
|
+
return ESGScore(supplier_id, 0.0, "No ESG factors provided.")
|
|
24
|
+
|
|
25
|
+
if weights is None:
|
|
26
|
+
weights = {k: 1.0 for k in factors.keys()}
|
|
27
|
+
|
|
28
|
+
total_w = 0.0
|
|
29
|
+
total = 0.0
|
|
30
|
+
for k, v in factors.items():
|
|
31
|
+
w = float(weights.get(k, 1.0))
|
|
32
|
+
total_w += w
|
|
33
|
+
total += w * float(v)
|
|
34
|
+
|
|
35
|
+
normalized = (total / total_w) if total_w > 0 else 0.0
|
|
36
|
+
score = max(0.0, min(100.0, normalized * 100.0))
|
|
37
|
+
rationale = "Weighted ESG score from normalized factors: " + ", ".join(f"{k}={v:.2f}" for k, v in factors.items())
|
|
38
|
+
return ESGScore(supplier_id, score, rationale)
|
|
39
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class ProcurelyticsError(Exception):
|
|
2
|
+
"""Base exception for procurelytics-ai."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DataError(ProcurelyticsError):
|
|
6
|
+
"""Raised when input data is missing or invalid."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ModelError(ProcurelyticsError):
|
|
10
|
+
"""Raised when model training or prediction fails."""
|
|
11
|
+
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import List
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class GovernanceFinding:
|
|
9
|
+
severity: str # LOW, MEDIUM, HIGH
|
|
10
|
+
message: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def data_quality_report(df: pd.DataFrame) -> List[GovernanceFinding]:
|
|
14
|
+
findings: List[GovernanceFinding] = []
|
|
15
|
+
|
|
16
|
+
if df.empty:
|
|
17
|
+
return [GovernanceFinding("HIGH", "Dataset is empty. Cannot compute analytics.")]
|
|
18
|
+
|
|
19
|
+
missing = df.isna().mean().to_dict()
|
|
20
|
+
high_missing = [k for k, v in missing.items() if v >= 0.30]
|
|
21
|
+
if high_missing:
|
|
22
|
+
findings.append(GovernanceFinding(
|
|
23
|
+
"MEDIUM",
|
|
24
|
+
f"High missingness (>=30%) detected in: {', '.join(high_missing)}. Consider improving source completeness."
|
|
25
|
+
))
|
|
26
|
+
|
|
27
|
+
if "esg_score" in df.columns:
|
|
28
|
+
s = pd.to_numeric(df["esg_score"], errors="coerce").dropna()
|
|
29
|
+
if not s.empty and ((s < 0).any() or (s > 100).any()):
|
|
30
|
+
findings.append(GovernanceFinding("HIGH", "esg_score contains values outside 0..100."))
|
|
31
|
+
|
|
32
|
+
return findings
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def representation_proxy_check(df: pd.DataFrame, group_col: str = "country") -> List[GovernanceFinding]:
|
|
36
|
+
findings: List[GovernanceFinding] = []
|
|
37
|
+
if group_col not in df.columns:
|
|
38
|
+
return findings
|
|
39
|
+
|
|
40
|
+
counts = df[group_col].fillna("UNKNOWN").value_counts(normalize=True)
|
|
41
|
+
if not counts.empty and counts.iloc[0] > 0.75:
|
|
42
|
+
findings.append(GovernanceFinding(
|
|
43
|
+
"MEDIUM",
|
|
44
|
+
f"Representation imbalance: '{counts.index[0]}' is {counts.iloc[0]*100:.1f}% of records."
|
|
45
|
+
))
|
|
46
|
+
return findings
|
|
47
|
+
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, Any, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class InsightCard:
|
|
8
|
+
title: str
|
|
9
|
+
summary: str
|
|
10
|
+
data: Dict[str, Any]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def render_cards(cards: List[InsightCard]) -> str:
|
|
14
|
+
lines = []
|
|
15
|
+
for i, c in enumerate(cards, 1):
|
|
16
|
+
lines.append(f"{i}. {c.title}\n {c.summary}")
|
|
17
|
+
return "\n".join(lines)
|
|
18
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def genai_prompt(results: Dict[str, Any]) -> str:
|
|
6
|
+
return (
|
|
7
|
+
"You are an AI procurement copilot. Summarize analytics into actionable recommendations.\n\n"
|
|
8
|
+
f"RESULTS_JSON:\n{results}\n\n"
|
|
9
|
+
"Return:\n"
|
|
10
|
+
"1) Top 3 supplier risks\n"
|
|
11
|
+
"2) Top 3 savings opportunities\n"
|
|
12
|
+
"3) Resilience improvements\n"
|
|
13
|
+
"4) ESG/compliance notes\n"
|
|
14
|
+
"Use simple executive language."
|
|
15
|
+
)
|
|
16
|
+
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def build_supplier_features(df: pd.DataFrame) -> pd.DataFrame:
|
|
6
|
+
out = df.copy()
|
|
7
|
+
|
|
8
|
+
if "supplier_id" not in out.columns:
|
|
9
|
+
raise ValueError("Missing required column: supplier_id")
|
|
10
|
+
|
|
11
|
+
# Defaults
|
|
12
|
+
defaults = {
|
|
13
|
+
"esg_score": 50.0,
|
|
14
|
+
"on_time_rate": 0.85,
|
|
15
|
+
"defect_rate": 0.02,
|
|
16
|
+
}
|
|
17
|
+
for col, default in defaults.items():
|
|
18
|
+
if col not in out.columns:
|
|
19
|
+
out[col] = default
|
|
20
|
+
out[col] = pd.to_numeric(out[col], errors="coerce").fillna(default)
|
|
21
|
+
|
|
22
|
+
out["late_risk"] = 1.0 - out["on_time_rate"].clip(0, 1)
|
|
23
|
+
out["quality_risk"] = out["defect_rate"].clip(0, 1)
|
|
24
|
+
out["esg_risk"] = 1.0 - (out["esg_score"].clip(0, 100) / 100.0)
|
|
25
|
+
|
|
26
|
+
# category diversity can help approximate resilience
|
|
27
|
+
if "category" in out.columns:
|
|
28
|
+
cat_div = out.groupby("supplier_id")["category"].nunique().rename("category_diversity")
|
|
29
|
+
out = out.merge(cat_div, on="supplier_id", how="left")
|
|
30
|
+
else:
|
|
31
|
+
out["category_diversity"] = 1
|
|
32
|
+
|
|
33
|
+
features = out[["supplier_id", "late_risk", "quality_risk", "esg_risk", "category_diversity"]]
|
|
34
|
+
return features.drop_duplicates("supplier_id").set_index("supplier_id")
|
|
35
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import numpy as np
|
|
4
|
+
from sklearn.ensemble import RandomForestRegressor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class SupplierRiskModel:
|
|
9
|
+
model: RandomForestRegressor
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def train_baseline(cls, X, y) -> "SupplierRiskModel":
|
|
13
|
+
m = RandomForestRegressor(
|
|
14
|
+
n_estimators=250,
|
|
15
|
+
random_state=42,
|
|
16
|
+
max_depth=7,
|
|
17
|
+
n_jobs=-1,
|
|
18
|
+
)
|
|
19
|
+
m.fit(X, y)
|
|
20
|
+
return cls(model=m)
|
|
21
|
+
|
|
22
|
+
def predict(self, X) -> np.ndarray:
|
|
23
|
+
return self.model.predict(X)
|
|
24
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from .models import SupplierRiskModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def baseline_labels_from_features(X: pd.DataFrame) -> np.ndarray:
|
|
8
|
+
# Lower is better (risk score)
|
|
9
|
+
return (
|
|
10
|
+
0.45 * X["late_risk"].to_numpy()
|
|
11
|
+
+ 0.35 * X["quality_risk"].to_numpy()
|
|
12
|
+
+ 0.20 * X["esg_risk"].to_numpy()
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def train_and_score_suppliers(X: pd.DataFrame) -> pd.DataFrame:
|
|
17
|
+
y = baseline_labels_from_features(X)
|
|
18
|
+
model = SupplierRiskModel.train_baseline(X, y)
|
|
19
|
+
pred = model.predict(X)
|
|
20
|
+
|
|
21
|
+
# Convert risk to 0..100 health
|
|
22
|
+
health = (1.0 - np.clip(pred, 0, 1)) * 100.0
|
|
23
|
+
|
|
24
|
+
out = pd.DataFrame(
|
|
25
|
+
{
|
|
26
|
+
"supplier_id": X.index.astype(str),
|
|
27
|
+
"risk_score": pred,
|
|
28
|
+
"supplier_health": health,
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
return out.sort_values("supplier_health", ascending=False).reset_index(drop=True)
|
|
32
|
+
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from collections import defaultdict, Counter
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Iterable, Dict, List, Tuple
|
|
5
|
+
from datetime import timedelta, datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Event:
|
|
10
|
+
case_id: str
|
|
11
|
+
event_type: str
|
|
12
|
+
ts: datetime
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class VariantResult:
|
|
17
|
+
variant: Tuple[str, ...]
|
|
18
|
+
count: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def variants(events: Iterable[Event], max_variants: int = 10) -> List[VariantResult]:
|
|
22
|
+
per_case: Dict[str, List[Event]] = defaultdict(list)
|
|
23
|
+
for e in events:
|
|
24
|
+
per_case[e.case_id].append(e)
|
|
25
|
+
|
|
26
|
+
seqs = []
|
|
27
|
+
for _, evs in per_case.items():
|
|
28
|
+
evs_sorted = sorted(evs, key=lambda x: x.ts)
|
|
29
|
+
seqs.append(tuple(e.event_type for e in evs_sorted))
|
|
30
|
+
|
|
31
|
+
c = Counter(seqs)
|
|
32
|
+
return [VariantResult(k, v) for k, v in c.most_common(max_variants)]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def lead_time(events: Iterable[Event], start: str, end: str) -> Dict[str, timedelta]:
|
|
36
|
+
per_case: Dict[str, List[Event]] = defaultdict(list)
|
|
37
|
+
for e in events:
|
|
38
|
+
per_case[e.case_id].append(e)
|
|
39
|
+
|
|
40
|
+
out: Dict[str, timedelta] = {}
|
|
41
|
+
for case_id, evs in per_case.items():
|
|
42
|
+
evs_sorted = sorted(evs, key=lambda x: x.ts)
|
|
43
|
+
s = next((e.ts for e in evs_sorted if e.event_type == start), None)
|
|
44
|
+
t = next((e.ts for e in evs_sorted if e.event_type == end), None)
|
|
45
|
+
if s and t and t >= s:
|
|
46
|
+
out[case_id] = t - s
|
|
47
|
+
return out
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def slowest_cases(lead_times: Dict[str, timedelta], top_n: int = 10):
|
|
51
|
+
return sorted(lead_times.items(), key=lambda x: x[1], reverse=True)[:top_n]
|
|
52
|
+
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from procurelytics_ai.governance.responsible_ai import data_quality_report, representation_proxy_check
|
|
3
|
+
|
|
4
|
+
def test_governance():
|
|
5
|
+
df = pd.DataFrame({
|
|
6
|
+
"supplier_id": ["A", "B", "C", "D"],
|
|
7
|
+
"country": ["US", "US", "US", "US"],
|
|
8
|
+
"esg_score": [90, 80, None, 110],
|
|
9
|
+
})
|
|
10
|
+
dq = data_quality_report(df)
|
|
11
|
+
assert len(dq) >= 1
|
|
12
|
+
rep = representation_proxy_check(df, "country")
|
|
13
|
+
assert len(rep) >= 1
|
|
14
|
+
|