medintell 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.
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .env
7
+ .venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MedIntell
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.
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: medintell
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the MedIntell REST API.
5
+ Project-URL: Documentation, https://docs.medintell.co/api/sdks
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: api,fhir,healthcare,his,medintell,sdk
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+
12
+ # medintell
13
+
14
+ Official Python SDK for the [MedIntell REST API](https://docs.medintell.co/api/overview).
15
+ Zero third-party dependencies (standard-library only).
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install medintell
21
+ ```
22
+
23
+ ## Quickstart
24
+
25
+ ```python
26
+ import os
27
+ from medintell import MedIntell
28
+
29
+ mi = MedIntell(api_key=os.environ["MI_KEY"])
30
+
31
+ # Verify the connection
32
+ health = mi.health()
33
+ # {"status": "ok", "org_id": "org_3aK9Lm", "role": "manager", "facility_id": "fac_1B7dQ2"}
34
+
35
+ # Seed reference data in dependency order, reusing the returned ids
36
+ dept = mi.departments.create(name="Cardiology", facility_id="fac_1B7dQ2")
37
+ doc = mi.doctors.create(name="Dr. Shakshouka Hummusi", department_id=dept["id"], facility_id="fac_1B7dQ2")
38
+ payer = mi.payers.create(name="Falafel Assurance Co.")
39
+
40
+ patient = mi.patients.create(
41
+ mrn="MRN-2026-00001",
42
+ facility_id="fac_1B7dQ2",
43
+ first_name="Kabsa",
44
+ last_name="Al-Majboos",
45
+ dob="1985-03-15",
46
+ gender="M",
47
+ )
48
+
49
+ visit = mi.visits.create(
50
+ facility_id="fac_1B7dQ2",
51
+ patient_mrn="MRN-2026-00001",
52
+ source_visit_id="V-2026-12345",
53
+ visit_date="2026-06-15T10:30:00",
54
+ type_of_visit="Consultation",
55
+ department_id=dept["id"],
56
+ doctor_id=doc["id"],
57
+ payer_id=payer["payer_id"],
58
+ total_cost=1500.0,
59
+ )
60
+ ```
61
+
62
+ ## Features
63
+
64
+ - **Resource model** — `mi.<resource>.<action>()` mirrors the REST API.
65
+ - **Auth** — the API key is attached to every request.
66
+ - **Idempotency** — every create sends an `Idempotency-Key` automatically; pass
67
+ `idempotency_key=...` to make a specific retry idempotent.
68
+ - **Retries** — `429` / `5xx` / network errors retry with backoff (honours `Retry-After`).
69
+ - **Cursor pagination** — `for patient in mi.patients.iterate(): ...`.
70
+ - **Typed errors** — failures raise `MedIntellError` with `.status`, `.code`, `.request_id`.
71
+
72
+ ## Pagination
73
+
74
+ ```python
75
+ for patient in mi.patients.iterate(search="MRN-2026"):
76
+ print(patient["mrno"])
77
+ ```
78
+
79
+ ## Errors
80
+
81
+ ```python
82
+ from medintell import MedIntell, MedIntellError
83
+
84
+ try:
85
+ mi.patients.retrieve("pat_does_not_exist")
86
+ except MedIntellError as err:
87
+ print(err.status, err.code, err.request_id)
88
+ ```
89
+
90
+ ## License
91
+
92
+ MIT
@@ -0,0 +1,81 @@
1
+ # medintell
2
+
3
+ Official Python SDK for the [MedIntell REST API](https://docs.medintell.co/api/overview).
4
+ Zero third-party dependencies (standard-library only).
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pip install medintell
10
+ ```
11
+
12
+ ## Quickstart
13
+
14
+ ```python
15
+ import os
16
+ from medintell import MedIntell
17
+
18
+ mi = MedIntell(api_key=os.environ["MI_KEY"])
19
+
20
+ # Verify the connection
21
+ health = mi.health()
22
+ # {"status": "ok", "org_id": "org_3aK9Lm", "role": "manager", "facility_id": "fac_1B7dQ2"}
23
+
24
+ # Seed reference data in dependency order, reusing the returned ids
25
+ dept = mi.departments.create(name="Cardiology", facility_id="fac_1B7dQ2")
26
+ doc = mi.doctors.create(name="Dr. Shakshouka Hummusi", department_id=dept["id"], facility_id="fac_1B7dQ2")
27
+ payer = mi.payers.create(name="Falafel Assurance Co.")
28
+
29
+ patient = mi.patients.create(
30
+ mrn="MRN-2026-00001",
31
+ facility_id="fac_1B7dQ2",
32
+ first_name="Kabsa",
33
+ last_name="Al-Majboos",
34
+ dob="1985-03-15",
35
+ gender="M",
36
+ )
37
+
38
+ visit = mi.visits.create(
39
+ facility_id="fac_1B7dQ2",
40
+ patient_mrn="MRN-2026-00001",
41
+ source_visit_id="V-2026-12345",
42
+ visit_date="2026-06-15T10:30:00",
43
+ type_of_visit="Consultation",
44
+ department_id=dept["id"],
45
+ doctor_id=doc["id"],
46
+ payer_id=payer["payer_id"],
47
+ total_cost=1500.0,
48
+ )
49
+ ```
50
+
51
+ ## Features
52
+
53
+ - **Resource model** — `mi.<resource>.<action>()` mirrors the REST API.
54
+ - **Auth** — the API key is attached to every request.
55
+ - **Idempotency** — every create sends an `Idempotency-Key` automatically; pass
56
+ `idempotency_key=...` to make a specific retry idempotent.
57
+ - **Retries** — `429` / `5xx` / network errors retry with backoff (honours `Retry-After`).
58
+ - **Cursor pagination** — `for patient in mi.patients.iterate(): ...`.
59
+ - **Typed errors** — failures raise `MedIntellError` with `.status`, `.code`, `.request_id`.
60
+
61
+ ## Pagination
62
+
63
+ ```python
64
+ for patient in mi.patients.iterate(search="MRN-2026"):
65
+ print(patient["mrno"])
66
+ ```
67
+
68
+ ## Errors
69
+
70
+ ```python
71
+ from medintell import MedIntell, MedIntellError
72
+
73
+ try:
74
+ mi.patients.retrieve("pat_does_not_exist")
75
+ except MedIntellError as err:
76
+ print(err.status, err.code, err.request_id)
77
+ ```
78
+
79
+ ## License
80
+
81
+ MIT
@@ -0,0 +1,6 @@
1
+ """Official Python SDK for the MedIntell REST API."""
2
+
3
+ from .client import MedIntell, MedIntellError
4
+
5
+ __all__ = ["MedIntell", "MedIntellError"]
6
+ __version__ = "0.1.0"
@@ -0,0 +1,235 @@
1
+ """MedIntell — official Python client for the MedIntell REST API.
2
+
3
+ Zero third-party dependencies (standard-library ``urllib``).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import random
10
+ import time
11
+ import urllib.error
12
+ import urllib.parse
13
+ import urllib.request
14
+ import uuid
15
+ from typing import Any, Iterator
16
+
17
+ DEFAULT_BASE_URL = "https://api.medintell.co"
18
+
19
+
20
+ class MedIntellError(Exception):
21
+ """Raised on any non-2xx API response or transport failure."""
22
+
23
+ def __init__(self, message: str, *, status: int | None = None, code: str | None = None,
24
+ type: str | None = None, request_id: str | None = None):
25
+ super().__init__(message)
26
+ self.status = status
27
+ self.code = code
28
+ self.type = type
29
+ self.request_id = request_id
30
+
31
+
32
+ class _Http:
33
+ def __init__(self, api_key: str, base_url: str, timeout: float, max_retries: int):
34
+ if not api_key:
35
+ raise MedIntellError("api_key is required")
36
+ self._api_key = api_key
37
+ self._base_url = base_url.rstrip("/")
38
+ self._timeout = timeout
39
+ self._max_retries = max_retries
40
+
41
+ def request(self, method: str, path: str, *, query: dict | None = None,
42
+ body: Any | None = None, idempotency_key: str | None = None) -> Any:
43
+ url = self._base_url + path
44
+ if query:
45
+ clean = {k: v for k, v in query.items() if v is not None}
46
+ if clean:
47
+ url += "?" + urllib.parse.urlencode(clean)
48
+
49
+ data = json.dumps(body).encode() if body is not None else None
50
+ headers = {"Authorization": f"Bearer {self._api_key}", "Accept": "application/json"}
51
+ if data is not None:
52
+ headers["Content-Type"] = "application/json"
53
+ if method == "POST":
54
+ headers["Idempotency-Key"] = idempotency_key or str(uuid.uuid4())
55
+
56
+ attempt = 0
57
+ while True:
58
+ req = urllib.request.Request(url, data=data, headers=headers, method=method)
59
+ try:
60
+ with urllib.request.urlopen(req, timeout=self._timeout) as resp:
61
+ raw = resp.read().decode()
62
+ return json.loads(raw) if raw else {}
63
+ except urllib.error.HTTPError as e:
64
+ if e.code == 429 or e.code >= 500:
65
+ if attempt < self._max_retries:
66
+ retry_after = e.headers.get("Retry-After")
67
+ time.sleep(float(retry_after) if retry_after else 2 ** attempt * 0.2 + random.random() * 0.1)
68
+ attempt += 1
69
+ continue
70
+ raw = e.read().decode() if e.fp else ""
71
+ payload = json.loads(raw) if raw else {}
72
+ err = payload.get("error", {}) if isinstance(payload, dict) else {}
73
+ raise MedIntellError(
74
+ err.get("message") or f"HTTP {e.code}",
75
+ status=e.code,
76
+ code=err.get("code"),
77
+ type=err.get("type"),
78
+ request_id=err.get("request_id") or e.headers.get("X-Request-ID"),
79
+ ) from None
80
+ except (urllib.error.URLError, TimeoutError) as e:
81
+ if attempt < self._max_retries:
82
+ time.sleep(2 ** attempt * 0.2 + random.random() * 0.1)
83
+ attempt += 1
84
+ continue
85
+ raise MedIntellError(f"network error: {e}") from None
86
+
87
+ def get(self, path, query=None):
88
+ return self.request("GET", path, query=query)
89
+
90
+ def post(self, path, body=None, idempotency_key=None):
91
+ return self.request("POST", path, body=body, idempotency_key=idempotency_key)
92
+
93
+ def put(self, path, body=None):
94
+ return self.request("PUT", path, body=body)
95
+
96
+
97
+ class _Resource:
98
+ def __init__(self, http: _Http, base: str):
99
+ self._http = http
100
+ self._base = base
101
+
102
+ def iterate(self, **query) -> Iterator[dict]:
103
+ """Yield every row, walking pages via the cursor."""
104
+ cursor = "" # empty cursor = keyset mode from page one
105
+ while True:
106
+ page = self._http.get(self._base, {"limit": 200, "cursor": cursor, **query})
107
+ for row in page.get("data", []):
108
+ yield row
109
+ if not page.get("has_more") or not page.get("next_cursor"):
110
+ return
111
+ cursor = page["next_cursor"]
112
+
113
+
114
+ class _Organizations(_Resource):
115
+ def list(self):
116
+ return self._http.get(self._base)
117
+
118
+ def retrieve(self, org_id):
119
+ return self._http.get(f"{self._base}/{org_id}")
120
+
121
+
122
+ class _Facilities(_Resource):
123
+ def list(self):
124
+ return self._http.get(self._base)
125
+
126
+ def create(self, *, org_id, **body):
127
+ return self._http.post(f"/api/v1/organizations/{org_id}/branches", body)
128
+
129
+
130
+ class _Departments(_Resource):
131
+ def list(self, **query):
132
+ return self._http.get(self._base, query)
133
+
134
+ def retrieve(self, department_id):
135
+ return self._http.get(f"{self._base}/{department_id}")
136
+
137
+ def create(self, *, idempotency_key=None, **body):
138
+ return self._http.post(self._base, body, idempotency_key)
139
+
140
+ def update(self, department_id, **body):
141
+ return self._http.put(f"{self._base}/{department_id}", body)
142
+
143
+ def kpis(self, **query):
144
+ return self._http.get(f"{self._base}/kpis", query)
145
+
146
+
147
+ class _Doctors(_Resource):
148
+ def list(self, **query):
149
+ return self._http.get(self._base, query)
150
+
151
+ def retrieve(self, doctor_id):
152
+ return self._http.get(f"{self._base}/{doctor_id}")
153
+
154
+ def create(self, *, idempotency_key=None, **body):
155
+ return self._http.post(self._base, body, idempotency_key)
156
+
157
+ def update(self, doctor_id, **body):
158
+ return self._http.put(f"{self._base}/{doctor_id}", body)
159
+
160
+ def kpis(self, **query):
161
+ return self._http.get(f"{self._base}/kpis", query)
162
+
163
+
164
+ class _Payers(_Resource):
165
+ def list(self, **query):
166
+ return self._http.get(self._base, query)
167
+
168
+ def create(self, *, idempotency_key=None, **body):
169
+ return self._http.post(self._base, body, idempotency_key)
170
+
171
+ def update(self, payer_id, **body):
172
+ return self._http.put(f"{self._base}/{payer_id}", body)
173
+
174
+
175
+ class _Patients(_Resource):
176
+ def list(self, **query):
177
+ return self._http.get(self._base, query)
178
+
179
+ def retrieve(self, patient_id):
180
+ return self._http.get(f"{self._base}/{patient_id}")
181
+
182
+ def create(self, *, idempotency_key=None, **body):
183
+ return self._http.post(self._base, body, idempotency_key)
184
+
185
+ def update(self, patient_id, **body):
186
+ return self._http.put(f"{self._base}/{patient_id}", body)
187
+
188
+
189
+ class _Visits(_Resource):
190
+ def list(self, **query):
191
+ return self._http.get(self._base, query)
192
+
193
+ def stats(self):
194
+ return self._http.get(f"{self._base}/stats")
195
+
196
+ def create(self, *, idempotency_key=None, **body):
197
+ return self._http.post(self._base, body, idempotency_key)
198
+
199
+ def update(self, visit_id, **body):
200
+ return self._http.put(f"{self._base}/{visit_id}", body)
201
+
202
+ def correct_diagnosis(self, visit_id, **body):
203
+ return self._http.put(f"{self._base}/{visit_id}/diagnoses", body)
204
+
205
+
206
+ class _Ingest:
207
+ def __init__(self, http: _Http):
208
+ self._http = http
209
+
210
+ def patients(self, *, idempotency_key=None, **body):
211
+ return self._http.post("/api/v1/ingest/patients", body, idempotency_key)
212
+
213
+ def schema(self):
214
+ return self._http.get("/api/v1/ingest/schema")
215
+
216
+
217
+ class MedIntell:
218
+ """Entry point. ``MedIntell(api_key=...)`` then ``client.<resource>.<action>()``."""
219
+
220
+ def __init__(self, api_key: str, *, base_url: str = DEFAULT_BASE_URL,
221
+ timeout: float = 30.0, max_retries: int = 2):
222
+ http = _Http(api_key, base_url, timeout, max_retries)
223
+ self._http = http
224
+ self.organizations = _Organizations(http, "/api/v1/organizations")
225
+ self.facilities = _Facilities(http, "/api/v1/branches")
226
+ self.departments = _Departments(http, "/api/v1/departments")
227
+ self.doctors = _Doctors(http, "/api/v1/doctors")
228
+ self.payers = _Payers(http, "/api/v1/payers")
229
+ self.patients = _Patients(http, "/api/v1/patients")
230
+ self.visits = _Visits(http, "/api/v1/visits")
231
+ self.ingest = _Ingest(http)
232
+
233
+ def health(self) -> dict:
234
+ """Authenticated connectivity check."""
235
+ return self._http.get("/api/v1/health")
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "medintell"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for the MedIntell REST API."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ keywords = ["medintell", "healthcare", "api", "sdk", "fhir", "his"]
13
+ dependencies = []
14
+
15
+ [project.urls]
16
+ Documentation = "https://docs.medintell.co/api/sdks"
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["medintell"]
@@ -0,0 +1,15 @@
1
+ import pytest
2
+ from medintell import MedIntell, MedIntellError
3
+
4
+
5
+ def test_constructs_resources():
6
+ mi = MedIntell(api_key="mi_live_test")
7
+ for r in ["organizations", "facilities", "departments", "doctors", "payers", "patients", "visits", "ingest"]:
8
+ assert hasattr(mi, r)
9
+ assert callable(mi.health)
10
+ assert callable(mi.patients.iterate)
11
+
12
+
13
+ def test_requires_api_key():
14
+ with pytest.raises(MedIntellError):
15
+ MedIntell(api_key="")