rti-ethics-engine 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,386 @@
1
+ """
2
+ RTI Ethics Engine - Results Module
3
+ EthicsResult dataclass with EXHAUSTIVE granular metric accessors and formatted output.
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from typing import Dict, Any, List, Optional, Tuple
8
+ from enum import Enum
9
+ import json
10
+
11
+
12
+ class RiskBand(Enum):
13
+ """Risk classification bands."""
14
+ LOW = "LOW"
15
+ MEDIUM = "MEDIUM"
16
+ HIGH = "HIGH"
17
+ CRITICAL = "CRITICAL"
18
+
19
+
20
+ class FinalLabel(Enum):
21
+ """Final ethics decision labels."""
22
+ ETHICAL = "ETHICAL"
23
+ CONDITIONALLY_ETHICAL = "CONDITIONALLY_ETHICAL"
24
+ UNETHICAL = "UNETHICAL"
25
+
26
+
27
+ # Dimension display names mapping
28
+ DIMENSION_NAMES = {
29
+ "legal": "Legal Compliance",
30
+ "fairness": "Bias & Fairness",
31
+ "explainability": "Explainability & Transparency",
32
+ "security": "Security & Robustness",
33
+ "monitoring": "Monitoring & Compliance",
34
+ "performance": "Model Performance",
35
+ "ux": "Human-AI UX",
36
+ "drift": "Data & Model Drift",
37
+ "environmental": "Environmental & Cost"
38
+ }
39
+
40
+
41
+ @dataclass
42
+ class DimensionResult:
43
+ """Result for a single ethical dimension with FULL metric access."""
44
+ key: str
45
+ name: str
46
+ l1_status: str
47
+ l1_reasons: List[str]
48
+ l2_risk: float
49
+ l2_indicators: List[str]
50
+ l3_evidence: Dict[str, Any]
51
+ raw_metrics: Dict[str, Any]
52
+
53
+ @property
54
+ def passed_l1(self) -> bool:
55
+ return self.l1_status == "PASS"
56
+
57
+ @property
58
+ def failed_l1(self) -> bool:
59
+ return self.l1_status == "FAIL"
60
+
61
+ def get_metric(self, metric_name: str, default: Any = None) -> Any:
62
+ """Get a specific raw metric value."""
63
+ return self.raw_metrics.get(metric_name, default)
64
+
65
+ def get_all_metrics(self) -> Dict[str, Any]:
66
+ """Get all raw metrics for this dimension."""
67
+ return self.raw_metrics.copy()
68
+
69
+ def format_l1_output(self) -> str:
70
+ """Format L1 gate result for display."""
71
+ icon = "โœ…" if self.passed_l1 else "โŒ"
72
+ status_text = f"{icon} L1 {self.l1_status}"
73
+ if self.l1_reasons:
74
+ reasons = "; ".join(self.l1_reasons)
75
+ return f"{status_text} | Violations: {reasons}"
76
+ return status_text
77
+
78
+ def format_l2_output(self) -> str:
79
+ """Format L2 risk score for display."""
80
+ risk_pct = self.l2_risk * 100
81
+ if self.l2_risk < 0.3:
82
+ icon = "๐ŸŸข"
83
+ elif self.l2_risk < 0.7:
84
+ icon = "๐ŸŸก"
85
+ else:
86
+ icon = "๐Ÿ”ด"
87
+ return f"{icon} L2 Risk: {risk_pct:.1f}%"
88
+
89
+ def format_l3_output(self) -> str:
90
+ """Format L3 evidence for display."""
91
+ explanation = self.l3_evidence.get("explanation", "No evidence.")
92
+ return f"๐Ÿ“‹ L3: {explanation}"
93
+
94
+ def format_metrics_table(self) -> str:
95
+ """Format all raw metrics as a table."""
96
+ lines = []
97
+ for k, v in self.raw_metrics.items():
98
+ if isinstance(v, float):
99
+ lines.append(f" {k}: {v:.4f}")
100
+ else:
101
+ lines.append(f" {k}: {v}")
102
+ return "\n".join(lines)
103
+
104
+ def to_dict(self) -> Dict[str, Any]:
105
+ return {
106
+ "key": self.key,
107
+ "name": self.name,
108
+ "l1_status": self.l1_status,
109
+ "l1_reasons": self.l1_reasons,
110
+ "l2_risk": self.l2_risk,
111
+ "l2_indicators": self.l2_indicators,
112
+ "l3_evidence": self.l3_evidence,
113
+ "raw_metrics": self.raw_metrics
114
+ }
115
+
116
+
117
+ @dataclass
118
+ class EthicsResult:
119
+ """
120
+ Complete ethics evaluation result with EXHAUSTIVE accessors.
121
+
122
+ Access patterns:
123
+ - result.final_label # "ETHICAL"
124
+ - result.s_llm # 0.92
125
+ - result.get_l1_metric("fairness", "spd") # 0.02
126
+ - result.get_l2_risk("legal") # 0.0
127
+ - result.get_l3_evidence("security") # {"explanation": "..."}
128
+ - result.print_full_audit() # Prints everything
129
+ """
130
+
131
+ final_label: str
132
+ layer1_status: str
133
+ layer2_risk_score: float
134
+ s_llm: float
135
+ risk_band: str
136
+ required_action: str
137
+ audit_id: str
138
+ risk_vector: Dict[str, float]
139
+ dimension_results: Dict[str, DimensionResult]
140
+ layer3_evidence: Dict[str, Any]
141
+
142
+ # ================== L1 METRIC ACCESS (GRANULAR) ==================
143
+
144
+ def get_l1_metric(self, dimension: str, metric: str, default: float = 0.0) -> float:
145
+ """Get raw L1 metric value. E.g. get_l1_metric('fairness', 'spd')"""
146
+ dim = self.dimension_results.get(dimension)
147
+ return dim.get_metric(metric, default) if dim else default
148
+
149
+ def get_l1_status(self, dimension: str) -> str:
150
+ """Get L1 status for dimension. Returns 'PASS' or 'FAIL'."""
151
+ dim = self.dimension_results.get(dimension)
152
+ return dim.l1_status if dim else "UNKNOWN"
153
+
154
+ def get_l1_reasons(self, dimension: str) -> List[str]:
155
+ """Get L1 failure reasons."""
156
+ dim = self.dimension_results.get(dimension)
157
+ return dim.l1_reasons if dim else []
158
+
159
+ def get_all_l1_metrics(self, dimension: str) -> Dict[str, Any]:
160
+ """Get ALL raw metrics for a dimension."""
161
+ dim = self.dimension_results.get(dimension)
162
+ return dim.get_all_metrics() if dim else {}
163
+
164
+ # Individual L1 metric shortcuts
165
+ @property
166
+ def spd(self) -> float:
167
+ """Statistical Parity Difference (Fairness L1)"""
168
+ return self.get_l1_metric("fairness", "spd")
169
+
170
+ @property
171
+ def di(self) -> float:
172
+ """Disparate Impact ratio (Fairness L1)"""
173
+ return self.get_l1_metric("fairness", "di", 1.0)
174
+
175
+ @property
176
+ def eop(self) -> float:
177
+ """Equal Opportunity Difference (Fairness L1)"""
178
+ return self.get_l1_metric("fairness", "eop")
179
+
180
+ @property
181
+ def aod(self) -> float:
182
+ """Average Odds Difference (Fairness L1)"""
183
+ return self.get_l1_metric("fairness", "aod")
184
+
185
+ @property
186
+ def psi(self) -> float:
187
+ """Population Stability Index (Drift L1)"""
188
+ return self.get_l1_metric("drift", "psi")
189
+
190
+ @property
191
+ def ks(self) -> float:
192
+ """Kolmogorov-Smirnov statistic (Drift L1)"""
193
+ return self.get_l1_metric("drift", "ks")
194
+
195
+ @property
196
+ def lawfulness(self) -> float:
197
+ """Lawfulness score (Legal L1)"""
198
+ return self.get_l1_metric("legal", "lawfulness", 1.0)
199
+
200
+ @property
201
+ def accuracy(self) -> float:
202
+ """Model accuracy (Performance L1)"""
203
+ return self.get_l1_metric("performance", "accuracy")
204
+
205
+ @property
206
+ def recall(self) -> float:
207
+ """Model recall (Performance L1)"""
208
+ return self.get_l1_metric("performance", "recall")
209
+
210
+ # ================== L2 RISK ACCESS ==================
211
+
212
+ def get_l2_risk(self, dimension: str, default: float = 0.0) -> float:
213
+ """Get L2 risk score for dimension [0, 1]."""
214
+ dim = self.dimension_results.get(dimension)
215
+ return dim.l2_risk if dim else default
216
+
217
+ def get_l2_indicators(self, dimension: str) -> List[str]:
218
+ """Get L2 risk indicators."""
219
+ dim = self.dimension_results.get(dimension)
220
+ return dim.l2_indicators if dim else []
221
+
222
+ # ================== L3 EVIDENCE ACCESS ==================
223
+
224
+ def get_l3_evidence(self, dimension: str) -> Dict[str, Any]:
225
+ """Get L3 forensic evidence for dimension."""
226
+ dim = self.dimension_results.get(dimension)
227
+ return dim.l3_evidence if dim else {"explanation": "Not found."}
228
+
229
+ def get_l3_explanation(self, dimension: str) -> str:
230
+ """Get L3 explanation text."""
231
+ return self.get_l3_evidence(dimension).get("explanation", "No explanation.")
232
+
233
+ # ================== AGGREGATION HELPERS ==================
234
+
235
+ def get_failed_dims(self) -> List[str]:
236
+ """Get human-readable names of failed dimensions."""
237
+ return [d.name for d in self.dimension_results.values() if d.failed_l1]
238
+
239
+ def get_failed_dim_keys(self) -> List[str]:
240
+ """Get keys of failed dimensions."""
241
+ return [k for k, d in self.dimension_results.items() if d.failed_l1]
242
+
243
+ def get_passed_dims(self) -> List[str]:
244
+ """Get human-readable names of passed dimensions."""
245
+ return [d.name for d in self.dimension_results.values() if d.passed_l1]
246
+
247
+ def l1_pass_rate(self) -> float:
248
+ """Fraction of L1 passes (e.g. 8/9 = 0.889)."""
249
+ if not self.dimension_results:
250
+ return 0.0
251
+ return sum(1 for d in self.dimension_results.values() if d.passed_l1) / len(self.dimension_results)
252
+
253
+ def l1_fail_count(self) -> int:
254
+ return sum(1 for d in self.dimension_results.values() if d.failed_l1)
255
+
256
+ def l1_pass_count(self) -> int:
257
+ return sum(1 for d in self.dimension_results.values() if d.passed_l1)
258
+
259
+ def get_highest_risk_dim(self) -> Tuple[str, float]:
260
+ """Get (dimension_key, risk_score) of highest risk dimension."""
261
+ if not self.risk_vector:
262
+ return ("", 0.0)
263
+ key = max(self.risk_vector, key=self.risk_vector.get)
264
+ return (key, self.risk_vector[key])
265
+
266
+ def get_dim_result(self, dimension: str) -> Optional[DimensionResult]:
267
+ """Get full DimensionResult object."""
268
+ return self.dimension_results.get(dimension)
269
+
270
+ # ================== FORMATTED OUTPUT ==================
271
+
272
+ def format_header(self) -> str:
273
+ """Format audit header."""
274
+ return f"{self.audit_id} | {self.final_label} | S_LLM={self.s_llm:.4f} | Risk={self.risk_band}"
275
+
276
+ def format_l1_summary(self) -> str:
277
+ """Format L1 summary table."""
278
+ lines = ["LAYER 1 - HARD GATES (Pass/Fail Thresholds):"]
279
+ for key, dim in self.dimension_results.items():
280
+ icon = "โœ…" if dim.passed_l1 else "โŒ"
281
+ reasons = f" [{'; '.join(dim.l1_reasons)}]" if dim.l1_reasons else ""
282
+ lines.append(f" {icon} {dim.name}: {dim.l1_status}{reasons}")
283
+ lines.append(f"\n L1 Pass Rate: {self.l1_pass_count()}/9 ({self.l1_pass_rate()*100:.1f}%)")
284
+ return "\n".join(lines)
285
+
286
+ def format_l2_summary(self) -> str:
287
+ """Format L2 risk summary."""
288
+ lines = ["LAYER 2 - WEIGHTED RISK SCORES:"]
289
+ for key, dim in self.dimension_results.items():
290
+ pct = dim.l2_risk * 100
291
+ bar = "โ–ˆ" * int(pct / 10) + "โ–‘" * (10 - int(pct / 10))
292
+ icon = "๐ŸŸข" if dim.l2_risk < 0.3 else "๐ŸŸก" if dim.l2_risk < 0.7 else "๐Ÿ”ด"
293
+ lines.append(f" {icon} {dim.name}: {pct:5.1f}% [{bar}]")
294
+ lines.append(f"\n Total L2 Risk: {self.layer2_risk_score:.4f}")
295
+ lines.append(f" S_LLM Score: {self.s_llm:.4f}")
296
+ return "\n".join(lines)
297
+
298
+ def format_l3_summary(self) -> str:
299
+ """Format L3 forensic evidence."""
300
+ lines = ["LAYER 3 - FORENSIC EVIDENCE BUNDLE:"]
301
+ for key, dim in self.dimension_results.items():
302
+ explanation = dim.l3_evidence.get("explanation", "Forensic data available.")
303
+ lines.append(f" ๐Ÿ“‹ {dim.name}:")
304
+ lines.append(f" โ””โ”€ {explanation}")
305
+ return "\n".join(lines)
306
+
307
+ def format_risk_vector(self) -> str:
308
+ """Format risk vector as JSON."""
309
+ return json.dumps(self.risk_vector, indent=2)
310
+
311
+ def format_metrics_details(self) -> str:
312
+ """Format ALL raw metrics for ALL dimensions."""
313
+ lines = ["RAW METRICS (All 36 Formulas):"]
314
+ for key, dim in self.dimension_results.items():
315
+ lines.append(f"\n [{dim.name.upper()}]")
316
+ for m, v in dim.raw_metrics.items():
317
+ if isinstance(v, float):
318
+ lines.append(f" โ€ข {m}: {v:.4f}")
319
+ elif v is not None:
320
+ lines.append(f" โ€ข {m}: {v}")
321
+ return "\n".join(lines)
322
+
323
+ def to_audit_table(self) -> str:
324
+ """Compact audit table."""
325
+ lines = [self.format_header()]
326
+ for dim in self.dimension_results.values():
327
+ icon = "โœ…" if dim.passed_l1 else "โŒ"
328
+ lines.append(f"{icon} {dim.name}: L2={dim.l2_risk:.2f}")
329
+ return "\n".join(lines)
330
+
331
+ def print_audit_table(self) -> None:
332
+ """Print compact audit table."""
333
+ print(self.to_audit_table())
334
+
335
+ def print_full_audit(self) -> None:
336
+ """Print EXHAUSTIVE audit output with L1, L2, L3 details."""
337
+ print("\n" + "=" * 80)
338
+ print("โš–๏ธ RTI ETHICS ENGINE - FULL AUDIT REPORT")
339
+ print("=" * 80)
340
+ print(f"\n๐Ÿ“Œ {self.format_header()}")
341
+ print(f"๐Ÿ“Œ Action Required: {self.required_action}")
342
+ print("\n" + "-" * 80)
343
+ print(self.format_l1_summary())
344
+ print("\n" + "-" * 80)
345
+ print(self.format_l2_summary())
346
+ print("\n" + "-" * 80)
347
+ print(self.format_l3_summary())
348
+ print("\n" + "-" * 80)
349
+ print("RISK VECTOR [R_1...R_9]:")
350
+ print(self.format_risk_vector())
351
+ print("\n" + "=" * 80)
352
+
353
+ # Final verdict
354
+ if self.final_label == "UNETHICAL":
355
+ print("๐Ÿšจ [CRITICAL] PROTOCOL BREACHED - OUTPUT BLOCKED ๐Ÿšจ")
356
+ elif self.final_label == "CONDITIONALLY_ETHICAL":
357
+ print("โš ๏ธ [WARNING] PARTIAL COMPLIANCE - HUMAN REVIEW REQUIRED โš ๏ธ")
358
+ else:
359
+ print("โœ… [PASS] ETHICAL COMPLIANCE VERIFIED")
360
+ print("=" * 80 + "\n")
361
+
362
+ def print_metrics_details(self) -> None:
363
+ """Print all 36 raw metric values."""
364
+ print(self.format_metrics_details())
365
+
366
+ # ================== SERIALIZATION ==================
367
+
368
+ def to_dict(self) -> Dict[str, Any]:
369
+ return {
370
+ "final_label": self.final_label,
371
+ "layer1_status": self.layer1_status,
372
+ "layer2_risk_score": self.layer2_risk_score,
373
+ "s_llm": self.s_llm,
374
+ "risk_band": self.risk_band,
375
+ "required_action": self.required_action,
376
+ "audit_id": self.audit_id,
377
+ "risk_vector": self.risk_vector,
378
+ "dimension_results": {k: v.to_dict() for k, v in self.dimension_results.items()},
379
+ "layer3_evidence": self.layer3_evidence
380
+ }
381
+
382
+ def to_json(self, indent: int = 2) -> str:
383
+ return json.dumps(self.to_dict(), indent=indent, default=str)
384
+
385
+ def __repr__(self) -> str:
386
+ return f"EthicsResult(label={self.final_label}, s_llm={self.s_llm:.3f}, band={self.risk_band})"
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: rti-ethics-engine
3
+ Version: 1.0.0
4
+ Summary: India's first granular AI ethics framework for RTI applications (36 metrics, 9 dimensions)
5
+ Project-URL: Homepage, https://github.com/rti-ethics/rti-ethics-engine
6
+ Project-URL: Documentation, https://rti-ethics-engine.readthedocs.io
7
+ Project-URL: Repository, https://github.com/rti-ethics/rti-ethics-engine
8
+ Project-URL: Issues, https://github.com/rti-ethics/rti-ethics-engine/issues
9
+ Author: RTI Ethics Engine Team
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 RTI Ethics Engine Team
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: ai-ethics,audit,compliance,fairness,government,india,llm,rti
33
+ Classifier: Development Status :: 5 - Production/Stable
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: Intended Audience :: Science/Research
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
44
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
45
+ Requires-Python: >=3.9
46
+ Requires-Dist: numpy>=1.24.0
47
+ Provides-Extra: dev
48
+ Requires-Dist: black>=23.0; extra == 'dev'
49
+ Requires-Dist: mypy>=1.0; extra == 'dev'
50
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
51
+ Requires-Dist: pytest>=7.0; extra == 'dev'
52
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
53
+ Provides-Extra: docs
54
+ Requires-Dist: sphinx-rtd-theme>=1.0; extra == 'docs'
55
+ Requires-Dist: sphinx>=5.0; extra == 'docs'
56
+ Description-Content-Type: text/markdown
57
+
58
+ # RTI Ethics Engine
59
+
60
+ **India's first granular AI ethics framework for RTI applications** - A production-ready Python package implementing 36 metrics across 9 ethical dimensions with granular formula access.
61
+
62
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
63
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
64
+
65
+ ## ๐Ÿš€ Installation
66
+
67
+ ```bash
68
+ pip install rti-ethics-engine
69
+ ```
70
+
71
+ Or install from source:
72
+
73
+ ```bash
74
+ git clone https://github.com/rti-ethics/rti-ethics-engine.git
75
+ cd rti-ethics-engine
76
+ pip install -e .
77
+ ```
78
+
79
+ ## โšก Quick Start
80
+
81
+ ```python
82
+ from rti_ethics_engine import evaluate
83
+
84
+ # Define your metrics
85
+ metrics = {
86
+ "fairness": {"spd": 0.02, "di": 1.0, "eop": 0.05, "aod": 0.03},
87
+ "drift": {"psi": 0.1, "ks": 0.02},
88
+ "legal": {"lawfulness": 1.0, "cvr": 0.98},
89
+ "performance": {"accuracy": 0.92, "recall": 0.88},
90
+ "explainability": {"confidence": 0.85, "fi": 0.3},
91
+ "security": {"adv_acc": 0.8, "pisr": 0.02},
92
+ "monitoring": {"alc": 0.98, "sla": 0.995},
93
+ "ux": {"tcr": 0.92, "ftr": 0.08},
94
+ "environmental": {"inf_cost": 0.02, "gpu_hours": 0.05}
95
+ }
96
+
97
+ # Evaluate
98
+ result = evaluate(metrics)
99
+
100
+ # Get the decision
101
+ print(result.final_label) # "ETHICAL"
102
+ print(result.s_llm) # 0.9234 (LLM Ethics Score)
103
+ print(result.risk_band) # "LOW"
104
+ ```
105
+
106
+ ## ๐ŸŽฏ Granular Metric Access
107
+
108
+ The killer feature - access ANY formula value at ANY layer:
109
+
110
+ ```python
111
+ # Access specific L1 formula values
112
+ spd = result.get_l1_metric("fairness", "spd") # 0.02
113
+ psi = result.get_l1_metric("drift", "psi") # 0.1
114
+ accuracy = result.get_l1_metric("performance", "accuracy") # 0.92
115
+
116
+ # Access L2 risk scores
117
+ fairness_risk = result.get_l2_risk("fairness") # 0.12
118
+ legal_risk = result.get_l2_risk("legal") # 0.00
119
+
120
+ # Access L3 forensic evidence
121
+ explanation = result.get_l3_evidence("legal")["explanation"]
122
+
123
+ # Aggregation helpers
124
+ failed_dims = result.get_failed_dims() # ["Legal Compliance"]
125
+ pass_rate = result.l1_pass_rate() # 0.889 (8/9 passed)
126
+
127
+ # Audit table for CIC compliance
128
+ result.print_audit_table()
129
+ # RTI-2026035174555 | ETHICAL | S_LLM=0.92
130
+ # โœ… Legal Compliance: L2=0.02
131
+ # โœ… Bias & Fairness: L2=0.05
132
+ # ...
133
+ ```
134
+
135
+ ## ๐Ÿ“Š 9 Ethical Dimensions
136
+
137
+ | Dimension | L1 Metrics | Description |
138
+ | -------------------- | ------------------- | ---------------------- |
139
+ | **Legal Compliance** | lawfulness, cvr | RTI Act 2005 alignment |
140
+ | **Bias & Fairness** | spd, di, eop, aod | Demographic equity |
141
+ | **Explainability** | confidence, fi | Model interpretability |
142
+ | **Security** | adv_acc, pisr | Adversarial robustness |
143
+ | **Monitoring** | alc, sla | Audit trail integrity |
144
+ | **Performance** | accuracy, recall | Statistical accuracy |
145
+ | **Human-AI UX** | tcr, ftr | User friction |
146
+ | **Drift** | psi, ks | Model stability |
147
+ | **Environmental** | inf_cost, gpu_hours | Carbon footprint |
148
+
149
+ ## ๐Ÿ—๏ธ Architecture
150
+
151
+ ```
152
+ rti_ethics_engine/
153
+ โ”œโ”€โ”€ __init__.py # Public API
154
+ โ”œโ”€โ”€ config.py # Thresholds & weights
155
+ โ”œโ”€โ”€ dimensions.py # 9 dimension evaluators
156
+ โ”œโ”€โ”€ core.py # EthicsEngine orchestrator
157
+ โ”œโ”€โ”€ results.py # EthicsResult with accessors
158
+ โ””โ”€โ”€ metrics.py # Computation helpers
159
+ ```
160
+
161
+ ## ๐Ÿ“– API Reference
162
+
163
+ ### `evaluate(metrics: dict) -> EthicsResult`
164
+
165
+ Quick evaluation entrypoint.
166
+
167
+ ### `EthicsEngine`
168
+
169
+ Full engine with `run_protocol()` method.
170
+
171
+ ### `EthicsResult`
172
+
173
+ Result object with:
174
+
175
+ - `final_label`: "ETHICAL" / "CONDITIONALLY_ETHICAL" / "UNETHICAL"
176
+ - `s_llm`: LLM Ethics Score [0, 1]
177
+ - `risk_band`: "LOW" / "MEDIUM" / "HIGH" / "CRITICAL"
178
+ - `get_l1_metric(dim, metric)`: Access any L1 formula value
179
+ - `get_l2_risk(dim)`: Get dimension risk score
180
+ - `get_l3_evidence(dim)`: Get forensic evidence
181
+ - `get_failed_dims()`: List failed dimensions
182
+ - `l1_pass_rate()`: Fraction of L1 passes
183
+ - `to_json()`: Export as JSON
184
+
185
+ ## ๐Ÿงช Testing
186
+
187
+ ```bash
188
+ pip install -e ".[dev]"
189
+ pytest tests/ -v
190
+ ```
191
+
192
+ ## ๐Ÿ“„ License
193
+
194
+ MIT License - See [LICENSE](LICENSE) for details.
195
+
196
+ ## ๐Ÿ‡ฎ๐Ÿ‡ณ Made for India
197
+
198
+ Designed specifically for RTI Act 2005 compliance and CIC audit requirements.
@@ -0,0 +1,10 @@
1
+ rti_ethics_engine/__init__.py,sha256=UDvG1BNdrjRptDzEX59WvFw-JfhTlrGkLL447cDsAc0,1890
2
+ rti_ethics_engine/config.py,sha256=PibTiAWlz_E7A39J-pyFQ8ix62y99GIhmn6kpOYlIRE,5485
3
+ rti_ethics_engine/core.py,sha256=6qRZhINYYy_P44ksOMp1DKmbKhVfEp2ukYBPgepKkz8,7130
4
+ rti_ethics_engine/dimensions.py,sha256=igJhGfOqC2aQ5yWpwv-z-UDTPNjRUXaMx4vL14-TeeI,14599
5
+ rti_ethics_engine/metrics.py,sha256=pOJsXBMWE2i_b_H5j2sfCRP5_qOCEuZCV5YcNLRsCcU,2112
6
+ rti_ethics_engine/results.py,sha256=CV_n-e5JgeP4Xn15DIsisnzLH74P82u85cw8uB1N5n8,14782
7
+ rti_ethics_engine-1.0.0.dist-info/METADATA,sha256=Dqzd_D0rpczcKDsl9i02oExXMQ1l71GOs6FMtS21jfk,7237
8
+ rti_ethics_engine-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
9
+ rti_ethics_engine-1.0.0.dist-info/licenses/LICENSE,sha256=l2g45qF6T13lWrSSf3XhkK1iRTGTwrzotiBalqL7sdM,1100
10
+ rti_ethics_engine-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RTI Ethics Engine Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.