fairmind 0.3.1a0__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.
- fairmind/__init__.py +31 -0
- fairmind/client.py +247 -0
- fairmind/types.py +102 -0
- fairmind-0.3.1a0.dist-info/METADATA +63 -0
- fairmind-0.3.1a0.dist-info/RECORD +6 -0
- fairmind-0.3.1a0.dist-info/WHEEL +4 -0
fairmind/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Typed Python client for FairMind."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.3.1a0"
|
|
4
|
+
|
|
5
|
+
from .client import FairMind, FairMindError # noqa: E402
|
|
6
|
+
from .types import ( # noqa: E402
|
|
7
|
+
Attestation,
|
|
8
|
+
Clause,
|
|
9
|
+
ClauseMap,
|
|
10
|
+
FairnessRun,
|
|
11
|
+
Framework,
|
|
12
|
+
Gate,
|
|
13
|
+
GateCheck,
|
|
14
|
+
MonitoringRun,
|
|
15
|
+
System,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"FairMind",
|
|
20
|
+
"FairMindError",
|
|
21
|
+
"Attestation",
|
|
22
|
+
"Clause",
|
|
23
|
+
"ClauseMap",
|
|
24
|
+
"FairnessRun",
|
|
25
|
+
"Framework",
|
|
26
|
+
"Gate",
|
|
27
|
+
"GateCheck",
|
|
28
|
+
"MonitoringRun",
|
|
29
|
+
"System",
|
|
30
|
+
"__version__",
|
|
31
|
+
]
|
fairmind/client.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""HTTP client for the FairMind API. Mirrors the conventions of the fairmind CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
from contextlib import ExitStack
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from . import __version__
|
|
15
|
+
from .types import Attestation, AttestableCheck, ClauseMap, FairnessRun, Gate, MonitoringRun, System
|
|
16
|
+
|
|
17
|
+
GOV = "/api/v1/ai-governance"
|
|
18
|
+
PACKS = "/api/v1/packs"
|
|
19
|
+
DEFAULT_API_URL = "http://localhost:8010"
|
|
20
|
+
|
|
21
|
+
# legacy framework ids -> catalogue keys, as accepted by the report request
|
|
22
|
+
_FRAMEWORK_ALIASES = {"eu_ai_act": "EUAIA", "iso_42001": "ISO42001", "nist_ai_rmf": "NISTRMF", "india_ai": "INDAI"}
|
|
23
|
+
|
|
24
|
+
PathLike = Union[str, "os.PathLike[str]"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FairMindError(Exception):
|
|
28
|
+
"""A failed call. `status_code` is None when the server could not be reached."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, message: str, status_code: Optional[int] = None) -> None:
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.message = message
|
|
33
|
+
self.status_code = status_code
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return f"{self.status_code}: {self.message}" if self.status_code else self.message
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _split(value: Union[str, Sequence[str]]) -> str:
|
|
40
|
+
return value if isinstance(value, str) else ",".join(value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FairMind:
|
|
44
|
+
"""`FairMind(api_url=None, api_key=None)`; FAIRMIND_API_URL and FAIRMIND_API_KEY are read when omitted."""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
api_url: Optional[str] = None,
|
|
49
|
+
api_key: Optional[str] = None,
|
|
50
|
+
*,
|
|
51
|
+
timeout: Union[float, httpx.Timeout] = httpx.Timeout(180.0, connect=10.0),
|
|
52
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
self.api_url = (api_url or os.environ.get("FAIRMIND_API_URL") or DEFAULT_API_URL).rstrip("/")
|
|
55
|
+
key = api_key or os.environ.get("FAIRMIND_API_KEY") or ""
|
|
56
|
+
headers = {"Accept": "application/json", "User-Agent": f"fairmind-sdk/{__version__}"}
|
|
57
|
+
if key:
|
|
58
|
+
headers["Authorization"] = f"Bearer {key}"
|
|
59
|
+
self._http = httpx.Client(base_url=self.api_url, headers=headers, timeout=timeout, transport=transport)
|
|
60
|
+
|
|
61
|
+
def close(self) -> None:
|
|
62
|
+
self._http.close()
|
|
63
|
+
|
|
64
|
+
def __enter__(self) -> "FairMind":
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def __exit__(self, *exc: Any) -> None:
|
|
68
|
+
self.close()
|
|
69
|
+
|
|
70
|
+
# -- transport -----------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def _call(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
73
|
+
try:
|
|
74
|
+
response = self._http.request(method, path, **kwargs)
|
|
75
|
+
except httpx.HTTPError as exc:
|
|
76
|
+
raise FairMindError(f"Could not reach FairMind at {self.api_url}: {exc}") from exc
|
|
77
|
+
code = response.status_code
|
|
78
|
+
if code == 401:
|
|
79
|
+
raise FairMindError("Not signed in. Set FAIRMIND_API_KEY to a key from POST /api/v1/auth/api-keys.", code)
|
|
80
|
+
if code >= 400:
|
|
81
|
+
try:
|
|
82
|
+
body = response.json()
|
|
83
|
+
detail = body.get("detail") or body.get("message") or response.text
|
|
84
|
+
except (ValueError, AttributeError):
|
|
85
|
+
detail = response.text
|
|
86
|
+
if code == 404:
|
|
87
|
+
detail = f"Not found: {path}. Check the system id and that your key belongs to its organisation. {detail}"
|
|
88
|
+
raise FairMindError(f"{method} {path} failed: {detail}", code)
|
|
89
|
+
try:
|
|
90
|
+
return response.json()
|
|
91
|
+
except ValueError as exc:
|
|
92
|
+
raise FairMindError(f"{method} {path} returned something that is not JSON", code) from exc
|
|
93
|
+
|
|
94
|
+
def _upload(self, path: str, csv_path: PathLike, data: Dict[str, str]) -> Any:
|
|
95
|
+
p = Path(csv_path)
|
|
96
|
+
if not p.is_file():
|
|
97
|
+
raise FairMindError(f"No such file: {p}")
|
|
98
|
+
with p.open("rb") as handle:
|
|
99
|
+
return self._call("POST", path, data=data, files={"file": (p.name, handle, "text/csv")})
|
|
100
|
+
|
|
101
|
+
# -- reads ---------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
def systems(self) -> List[System]:
|
|
104
|
+
return self._call("GET", f"{GOV}/systems")
|
|
105
|
+
|
|
106
|
+
def gate(self, system_id: str) -> Gate:
|
|
107
|
+
return self._call("GET", f"{GOV}/systems/{system_id}/gate")
|
|
108
|
+
|
|
109
|
+
def clause_map(self, system_id: str, frameworks: Optional[Sequence[str]] = None) -> ClauseMap:
|
|
110
|
+
"""The gate read per framework clause. `frameworks` filters client-side by catalogue key (EUAIA, ISO42001, ...)."""
|
|
111
|
+
result = self._call("GET", f"{GOV}/systems/{system_id}/clause-map")
|
|
112
|
+
if frameworks:
|
|
113
|
+
wanted = {_FRAMEWORK_ALIASES.get(f.lower(), f).upper() for f in frameworks}
|
|
114
|
+
result["frameworks"] = [f for f in result["frameworks"] if f["key"].upper() in wanted]
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
def seals(self, system_id: str) -> List[Dict[str, Any]]:
|
|
118
|
+
return self._call("GET", f"{GOV}/systems/{system_id}/seals")
|
|
119
|
+
|
|
120
|
+
def fairness_runs(self, system_id: str) -> List[Dict[str, Any]]:
|
|
121
|
+
return self._call("GET", f"{GOV}/systems/{system_id}/fairness-runs")
|
|
122
|
+
|
|
123
|
+
def monitoring_runs(self, system_id: str) -> List[Dict[str, Any]]:
|
|
124
|
+
return self._call("GET", f"{GOV}/systems/{system_id}/monitoring-runs")
|
|
125
|
+
|
|
126
|
+
# -- writes --------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
def inspect_csv(self, system_id: str, path: PathLike) -> Dict[str, Any]:
|
|
129
|
+
"""Columns of a CSV with sample values and a suggested outcome/prediction/score/attributes mapping."""
|
|
130
|
+
return self._upload(f"{GOV}/systems/{system_id}/fairness-runs/inspect", path, {})
|
|
131
|
+
|
|
132
|
+
def run_fairness(
|
|
133
|
+
self,
|
|
134
|
+
system_id: str,
|
|
135
|
+
path: PathLike,
|
|
136
|
+
*,
|
|
137
|
+
outcome: str,
|
|
138
|
+
attributes: Union[str, Sequence[str]],
|
|
139
|
+
prediction: Optional[str] = None,
|
|
140
|
+
score: Optional[str] = None,
|
|
141
|
+
favorable_label: Optional[str] = None,
|
|
142
|
+
) -> FairnessRun:
|
|
143
|
+
if not prediction and not score:
|
|
144
|
+
raise FairMindError("Give prediction (a 0/1 column) or score (a 0 to 1 column)")
|
|
145
|
+
data = {"outcome": outcome, "attributes": _split(attributes)}
|
|
146
|
+
for key, value in (("prediction", prediction), ("score", score), ("favorable_label", favorable_label)):
|
|
147
|
+
if value:
|
|
148
|
+
data[key] = value
|
|
149
|
+
return self._upload(f"{GOV}/systems/{system_id}/fairness-runs", path, data)
|
|
150
|
+
|
|
151
|
+
def run_monitoring(
|
|
152
|
+
self,
|
|
153
|
+
system_id: str,
|
|
154
|
+
path: PathLike,
|
|
155
|
+
*,
|
|
156
|
+
attributes: Union[str, Sequence[str]],
|
|
157
|
+
prediction: Optional[str] = None,
|
|
158
|
+
score: Optional[str] = None,
|
|
159
|
+
favorable_label: Optional[str] = None,
|
|
160
|
+
) -> MonitoringRun:
|
|
161
|
+
if not prediction and not score:
|
|
162
|
+
raise FairMindError("Give prediction (a 0/1 column) or score (a 0 to 1 column)")
|
|
163
|
+
data = {"attributes": _split(attributes)}
|
|
164
|
+
for key, value in (("prediction", prediction), ("score", score), ("favorable_label", favorable_label)):
|
|
165
|
+
if value:
|
|
166
|
+
data[key] = value
|
|
167
|
+
return self._upload(f"{GOV}/systems/{system_id}/monitoring-runs", path, data)
|
|
168
|
+
|
|
169
|
+
def attest(self, system_id: str, check: AttestableCheck, statement: str, reference: Optional[str] = None) -> Attestation:
|
|
170
|
+
"""Record that a person states a gate check is satisfied. Manual provenance: it attests, it never proves."""
|
|
171
|
+
body: Dict[str, Any] = {"check": check, "statement": statement}
|
|
172
|
+
if reference:
|
|
173
|
+
body["reference"] = reference
|
|
174
|
+
return self._call("POST", f"{GOV}/systems/{system_id}/attestations", json=body)
|
|
175
|
+
|
|
176
|
+
# -- catalogue evaluations ---------------------------------------------
|
|
177
|
+
|
|
178
|
+
def organization_id(self) -> str:
|
|
179
|
+
"""The organisation the evaluation endpoints act in: FAIRMIND_ORG_ID, else the key's first organisation."""
|
|
180
|
+
if not getattr(self, "_org_id", None):
|
|
181
|
+
org = os.environ.get("FAIRMIND_ORG_ID")
|
|
182
|
+
if not org:
|
|
183
|
+
orgs = self._call("GET", "/api/v1/organizations").get("organizations") or []
|
|
184
|
+
if not orgs:
|
|
185
|
+
raise FairMindError("This key belongs to no organisation")
|
|
186
|
+
org = orgs[0]["id"]
|
|
187
|
+
self._org_id = org
|
|
188
|
+
return self._org_id
|
|
189
|
+
|
|
190
|
+
def _packs(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
191
|
+
params = {**kwargs.pop("params", {}), "org_id": self.organization_id()}
|
|
192
|
+
return self._call(method, f"{PACKS}{path}", params=params, **kwargs)
|
|
193
|
+
|
|
194
|
+
def evaluations(self, query: Optional[str] = None, pillar: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
195
|
+
"""Catalogue evaluations; `runnable` is true for those FairMind computes itself."""
|
|
196
|
+
params = {k: v for k, v in (("q", query), ("pillar", pillar)) if v}
|
|
197
|
+
return self._packs("GET", "/evaluations", params=params)
|
|
198
|
+
|
|
199
|
+
def evaluation_engine(self, evaluation_id: str) -> Dict[str, Any]:
|
|
200
|
+
"""The files (slots) and params an evaluation takes, and its live alternate if it has one."""
|
|
201
|
+
return self._packs("GET", f"/evaluations/{evaluation_id}/engine")
|
|
202
|
+
|
|
203
|
+
def evaluation_runs(self, evaluation_id: str, system_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
204
|
+
params = {"system_id": system_id} if system_id else {}
|
|
205
|
+
return self._packs("GET", f"/evaluations/{evaluation_id}/runs", params=params)
|
|
206
|
+
|
|
207
|
+
def run_evaluation(
|
|
208
|
+
self,
|
|
209
|
+
system_id: str,
|
|
210
|
+
evaluation_id: str,
|
|
211
|
+
files: Optional[Dict[str, PathLike]] = None,
|
|
212
|
+
params: Optional[Dict[str, Any]] = None,
|
|
213
|
+
*,
|
|
214
|
+
live: bool = False,
|
|
215
|
+
source: str = "upload",
|
|
216
|
+
timeout: float = 900.0,
|
|
217
|
+
poll_interval: float = 3.0,
|
|
218
|
+
) -> Dict[str, Any]:
|
|
219
|
+
"""Run a catalogue evaluation and return the stored run (status passed, failed or insufficient, metrics, detail).
|
|
220
|
+
|
|
221
|
+
`files` maps the engine's slot names to local paths. `live=True` runs the evaluation's behaviour test
|
|
222
|
+
against the system's connected endpoint; `source="connection"` reads the organisation's agent platform.
|
|
223
|
+
Behaviour tests run as background jobs on the server; this waits for them up to `timeout` seconds.
|
|
224
|
+
"""
|
|
225
|
+
data = {"system_id": system_id, "params": json.dumps(params or {}), "source": source}
|
|
226
|
+
if live:
|
|
227
|
+
data["mode"] = "live"
|
|
228
|
+
with ExitStack() as stack:
|
|
229
|
+
uploads = {}
|
|
230
|
+
for slot, path in (files or {}).items():
|
|
231
|
+
p = Path(path)
|
|
232
|
+
if not p.is_file():
|
|
233
|
+
raise FairMindError(f"No such file: {p}")
|
|
234
|
+
uploads[slot] = (p.name, stack.enter_context(p.open("rb")))
|
|
235
|
+
result = self._packs("POST", f"/evaluations/{evaluation_id}/runs", data=data, files=uploads or None)
|
|
236
|
+
job = result.get("job") if isinstance(result, dict) else None
|
|
237
|
+
if not job:
|
|
238
|
+
return result
|
|
239
|
+
deadline = time.monotonic() + timeout
|
|
240
|
+
while job["status"] not in ("done", "failed"):
|
|
241
|
+
if time.monotonic() > deadline:
|
|
242
|
+
raise FairMindError(f"Evaluation job {job['id']} is still running after {timeout:.0f}s; check it later")
|
|
243
|
+
time.sleep(poll_interval)
|
|
244
|
+
job = self._packs("GET", f"/evaluation-jobs/{job['id']}")
|
|
245
|
+
if job["status"] == "failed":
|
|
246
|
+
raise FairMindError(f"Evaluation {evaluation_id} failed: {job.get('error') or 'no reason given'}")
|
|
247
|
+
return self._packs("GET", f"/evaluation-runs/{job['evidenceId']}")
|
fairmind/types.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Response shapes, as the API returns them (camelCase keys where the API uses them)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Literal, Optional, TypedDict
|
|
6
|
+
|
|
7
|
+
Verdict = Literal["go", "conditional_go", "no_go"]
|
|
8
|
+
ApprovalStatus = Literal["not_requested", "pending", "approved", "rejected"]
|
|
9
|
+
CheckState = Literal["proven", "attested", "open"]
|
|
10
|
+
AttestableCheck = Literal["data_governance", "human_oversight", "monitoring", "environmental"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class System(TypedDict, total=False):
|
|
14
|
+
id: str
|
|
15
|
+
name: str
|
|
16
|
+
owner: str
|
|
17
|
+
riskTier: str
|
|
18
|
+
lifecycleStage: str
|
|
19
|
+
workspaceId: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GateCheck(TypedDict):
|
|
23
|
+
key: str
|
|
24
|
+
label: str
|
|
25
|
+
state: CheckState
|
|
26
|
+
confidence: float
|
|
27
|
+
evidenceId: Optional[str]
|
|
28
|
+
detail: Optional[Dict[str, Any]]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Gate(TypedDict, total=False):
|
|
32
|
+
verdict: Verdict
|
|
33
|
+
confidence: float
|
|
34
|
+
band: str
|
|
35
|
+
checks: List[GateCheck]
|
|
36
|
+
reasons: List[str]
|
|
37
|
+
proven: int
|
|
38
|
+
attested: int
|
|
39
|
+
open: int
|
|
40
|
+
systemId: str
|
|
41
|
+
riskTier: str
|
|
42
|
+
approvalStatus: ApprovalStatus # latest release decision; "rejected" forces verdict no_go
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ClauseCheck(TypedDict):
|
|
46
|
+
check: str
|
|
47
|
+
label: str
|
|
48
|
+
state: CheckState
|
|
49
|
+
confidence: float
|
|
50
|
+
evidenceId: Optional[str]
|
|
51
|
+
relation: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Clause(TypedDict):
|
|
55
|
+
key: str
|
|
56
|
+
control: str
|
|
57
|
+
title: str
|
|
58
|
+
group: Optional[str]
|
|
59
|
+
checks: List[ClauseCheck]
|
|
60
|
+
state: CheckState
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Framework(TypedDict):
|
|
64
|
+
key: str
|
|
65
|
+
name: str
|
|
66
|
+
clauses: List[Clause]
|
|
67
|
+
counts: Dict[str, int]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ClauseMap(TypedDict):
|
|
71
|
+
verdict: Verdict
|
|
72
|
+
confidence: float
|
|
73
|
+
frameworks: List[Framework]
|
|
74
|
+
systemId: str
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class FairnessRun(TypedDict):
|
|
78
|
+
"""Result of POST /fairness-runs. `result` holds per-attribute metrics and `all_checks_pass`."""
|
|
79
|
+
|
|
80
|
+
runId: str
|
|
81
|
+
evidenceId: str
|
|
82
|
+
systemId: str
|
|
83
|
+
title: str
|
|
84
|
+
result: Dict[str, Any]
|
|
85
|
+
gate: Gate
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MonitoringRun(TypedDict):
|
|
89
|
+
"""Result of POST /monitoring-runs. `result` holds drift, impact ratio and score PSI per attribute."""
|
|
90
|
+
|
|
91
|
+
evidenceId: str
|
|
92
|
+
systemId: str
|
|
93
|
+
title: str
|
|
94
|
+
result: Dict[str, Any]
|
|
95
|
+
gate: Gate
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Attestation(TypedDict):
|
|
99
|
+
evidenceId: str
|
|
100
|
+
systemId: str
|
|
101
|
+
check: AttestableCheck
|
|
102
|
+
gate: Gate
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fairmind
|
|
3
|
+
Version: 0.3.1a0
|
|
4
|
+
Summary: Typed Python client for FairMind: release gate, fairness tests, monitoring and attestations.
|
|
5
|
+
Author-email: Adhithya Rajasekaran <adhithya@axonome.xyz>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# fairmind (Python SDK)
|
|
12
|
+
|
|
13
|
+
A small typed client for the FairMind API: read the release gate, run fairness tests, record monitoring snapshots and attestations. One dependency, `httpx`.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install ./apps/sdk # from a checkout; or: uv add ./apps/sdk
|
|
17
|
+
export FAIRMIND_API_URL=https://fairmind.example.com
|
|
18
|
+
export FAIRMIND_API_KEY=... # minted at POST /api/v1/auth/api-keys
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from fairmind import FairMind, FairMindError
|
|
23
|
+
|
|
24
|
+
fm = FairMind() # reads FAIRMIND_API_URL and FAIRMIND_API_KEY
|
|
25
|
+
|
|
26
|
+
system = fm.systems()[0]["id"]
|
|
27
|
+
run = fm.run_fairness(system, "predictions.csv", outcome="repaid", prediction="approved", attributes=["gender", "region"])
|
|
28
|
+
gate = run["gate"]
|
|
29
|
+
print(gate["verdict"], gate["confidence"])
|
|
30
|
+
for check in gate["checks"]:
|
|
31
|
+
print(check["label"], check["state"])
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Methods
|
|
35
|
+
|
|
36
|
+
| Method | Endpoint |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `systems()` | `GET /systems` |
|
|
39
|
+
| `gate(system_id)` | `GET /systems/{id}/gate` |
|
|
40
|
+
| `clause_map(system_id, frameworks=None)` | `GET /systems/{id}/clause-map`; `frameworks` (for example `["EUAIA", "ISO42001"]`) filters the result |
|
|
41
|
+
| `seals(system_id)` | `GET /systems/{id}/seals` |
|
|
42
|
+
| `inspect_csv(system_id, path)` | `POST /systems/{id}/fairness-runs/inspect` |
|
|
43
|
+
| `run_fairness(system_id, path, *, outcome, attributes, prediction=None, score=None, favorable_label=None)` | `POST /systems/{id}/fairness-runs` |
|
|
44
|
+
| `fairness_runs(system_id)` | `GET /systems/{id}/fairness-runs` |
|
|
45
|
+
| `run_monitoring(system_id, path, *, attributes, prediction=None, score=None, favorable_label=None)` | `POST /systems/{id}/monitoring-runs` |
|
|
46
|
+
| `monitoring_runs(system_id)` | `GET /systems/{id}/monitoring-runs` |
|
|
47
|
+
| `attest(system_id, check, statement, reference=None)` | `POST /systems/{id}/attestations` |
|
|
48
|
+
| `evaluations(query=None, pillar=None)` | `GET /api/v1/packs/evaluations` |
|
|
49
|
+
| `evaluation_engine(evaluation_id)` | `GET /api/v1/packs/evaluations/{id}/engine`: the files and params it takes |
|
|
50
|
+
| `run_evaluation(system_id, evaluation_id, files=None, params=None, live=False, source="upload")` | `POST /api/v1/packs/evaluations/{id}/runs`; waits for behaviour-test jobs and returns the stored run |
|
|
51
|
+
| `evaluation_runs(evaluation_id, system_id=None)` | `GET /api/v1/packs/evaluations/{id}/runs` |
|
|
52
|
+
|
|
53
|
+
The evaluation methods act in the organisation from `FAIRMIND_ORG_ID`, or the key's first organisation when it is unset.
|
|
54
|
+
|
|
55
|
+
`attributes` is a list or a comma-separated string. `run_fairness` and `run_monitoring` need `prediction` (a 0/1 column) or `score` (a 0 to 1 column). Responses are plain dicts typed with `TypedDict`s (`Gate`, `GateCheck`, `ClauseMap`, `FairnessRun`, `MonitoringRun`, ...).
|
|
56
|
+
|
|
57
|
+
Every failure raises `FairMindError` with `status_code` (`None` when the server could not be reached) and `message` (the server's `detail` when it sent one).
|
|
58
|
+
|
|
59
|
+
## Tests
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd apps/sdk && uv run --with pytest pytest -q -p no:cacheprovider tests
|
|
63
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
fairmind/__init__.py,sha256=TTE4AQr_EJvAmSO9773u9idfSg5-wP8MfHuPhWEekxc,514
|
|
2
|
+
fairmind/client.py,sha256=io29ARGfzcy4OLUHp7x-BcLOz6Z4NwkNf9CMs-J0wcA,11278
|
|
3
|
+
fairmind/types.py,sha256=w5DVPEDO379fdeIEfXm0eXRL2ru4n67DkhV1Y_NXfZg,2233
|
|
4
|
+
fairmind-0.3.1a0.dist-info/METADATA,sha256=1DTgv0GJvRFfbbReXNY6Y4g-brsLLXwGRe87CzKlrOs,3205
|
|
5
|
+
fairmind-0.3.1a0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
6
|
+
fairmind-0.3.1a0.dist-info/RECORD,,
|