glucoguard 0.1.0
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.
- package/README.md +178 -0
- package/dist/index.cjs +228 -0
- package/dist/index.d.cts +216 -0
- package/dist/index.d.ts +216 -0
- package/dist/index.js +197 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# glucoguard — TypeScript/JavaScript SDK
|
|
2
|
+
|
|
3
|
+
Typed client for the GlucoGuard REST API. Works in Node 18+ and modern
|
|
4
|
+
browsers. Zero runtime dependencies.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install glucoguard
|
|
10
|
+
# or
|
|
11
|
+
pnpm add glucoguard
|
|
12
|
+
# or
|
|
13
|
+
yarn add glucoguard
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { GlucoGuard } from 'glucoguard';
|
|
20
|
+
|
|
21
|
+
const gg = new GlucoGuard({ apiKey: 'sk_live_...' });
|
|
22
|
+
|
|
23
|
+
const result = await gg.predictDiabetes({
|
|
24
|
+
age: 55,
|
|
25
|
+
gender: 0,
|
|
26
|
+
bmi: 30,
|
|
27
|
+
systolic_bp: 140,
|
|
28
|
+
diastolic_bp: 88,
|
|
29
|
+
hba1c: 6.5,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`Risk: ${result.risk_score}% (${result.risk_category})`);
|
|
33
|
+
for (const factor of result.top_factors.slice(0, 3)) {
|
|
34
|
+
console.log(` ${factor.feature}: ${factor.direction} (${factor.shap_value.toFixed(3)})`);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Batch predictions
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const patients = [
|
|
42
|
+
{ age: 55, gender: 0, bmi: 30, systolic_bp: 140, diastolic_bp: 88 },
|
|
43
|
+
{ age: 35, gender: 1, bmi: 22, systolic_bp: 118, diastolic_bp: 72 },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const result = await gg.predictBatch(patients);
|
|
47
|
+
|
|
48
|
+
if (GlucoGuard.isInlineResult(result)) {
|
|
49
|
+
console.log(`Processed ${result.total} patients in ${result.processing_time_ms}ms`);
|
|
50
|
+
for (const item of result.results) {
|
|
51
|
+
console.log(`Patient ${item.index}: diabetes ${item.diabetes.risk_score}%`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Webhook batch jobs
|
|
57
|
+
|
|
58
|
+
For long-running batches, pass a `webhookUrl` and GlucoGuard will POST the
|
|
59
|
+
result to your endpoint when complete. The response includes the signed
|
|
60
|
+
HMAC-SHA256 signature in `X-GlucoGuard-Signature`.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const ack = await gg.predictBatch(patients, {
|
|
64
|
+
webhookUrl: 'https://your-server.com/glucoguard-hook',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (GlucoGuard.isWebhookResult(ack)) {
|
|
68
|
+
console.log(`Job queued: ${ack.job_id}`);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Error handling
|
|
73
|
+
|
|
74
|
+
Every failure is a typed error. Check with `instanceof`:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import {
|
|
78
|
+
GlucoGuard,
|
|
79
|
+
AuthenticationError,
|
|
80
|
+
RateLimitError,
|
|
81
|
+
ValidationError,
|
|
82
|
+
GlucoGuardError,
|
|
83
|
+
} from 'glucoguard';
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await gg.predictDiabetes(patient);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err instanceof AuthenticationError) {
|
|
89
|
+
console.error('Bad API key');
|
|
90
|
+
} else if (err instanceof RateLimitError) {
|
|
91
|
+
console.error('Slow down and retry');
|
|
92
|
+
} else if (err instanceof ValidationError) {
|
|
93
|
+
console.error('Bad request:', err.message);
|
|
94
|
+
} else if (err instanceof GlucoGuardError) {
|
|
95
|
+
console.error(`API error ${err.statusCode}: ${err.message}`);
|
|
96
|
+
console.error(`request_id: ${err.requestId}`); // useful for support
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Ops endpoints
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
await gg.health(); // public liveness probe
|
|
105
|
+
await gg.status(); // detailed status + model versions
|
|
106
|
+
await gg.usage(7); // your key's usage over 7 days
|
|
107
|
+
await gg.modelInfo('diabetes'); // model metrics + feature importance
|
|
108
|
+
await gg.samplePatients(); // pre-configured demo patients
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## PDF reports
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { writeFile } from 'node:fs/promises';
|
|
115
|
+
|
|
116
|
+
const bytes = await gg.generatePDFReport({
|
|
117
|
+
patient,
|
|
118
|
+
diabetes_result: diabetesResult,
|
|
119
|
+
cvd_result: cvdResult,
|
|
120
|
+
patient_name: 'Jane Smith',
|
|
121
|
+
});
|
|
122
|
+
await writeFile('report.pdf', Buffer.from(bytes));
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Browser usage
|
|
126
|
+
|
|
127
|
+
The client uses the built-in `fetch` API, so it works in modern browsers
|
|
128
|
+
with zero config. **Do not expose your API key in client-side code** —
|
|
129
|
+
always call the API from a trusted backend and proxy requests.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// browser-only — proxy through your own backend
|
|
133
|
+
const gg = new GlucoGuard({
|
|
134
|
+
apiKey: 'sk_live_server_proxy_token',
|
|
135
|
+
baseUrl: '/proxy/glucoguard', // your backend forwards to the real API
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Node 16 or older
|
|
140
|
+
|
|
141
|
+
The SDK requires a `fetch` implementation. Node 18+ has it built in.
|
|
142
|
+
For older versions:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm install undici
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { fetch } from 'undici';
|
|
150
|
+
import { GlucoGuard } from 'glucoguard';
|
|
151
|
+
|
|
152
|
+
const gg = new GlucoGuard({
|
|
153
|
+
apiKey: 'sk_live_...',
|
|
154
|
+
fetch: fetch as unknown as typeof globalThis.fetch,
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## TypeScript
|
|
159
|
+
|
|
160
|
+
Every method is fully typed. The package ships with `.d.ts` files, so
|
|
161
|
+
IDE autocompletion works out of the box.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import type { PatientInput, PredictionResult } from 'glucoguard';
|
|
165
|
+
|
|
166
|
+
function makePatient(): PatientInput {
|
|
167
|
+
return { age: 55, gender: 0, bmi: 30, systolic_bp: 140, diastolic_bp: 88 };
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Links
|
|
172
|
+
|
|
173
|
+
- API docs: https://glucoguard.analyticadss.com/docs
|
|
174
|
+
- Web app: https://glucoguard.analyticadss.com
|
|
175
|
+
- Status: https://glucoguard.analyticadss.com/status.html
|
|
176
|
+
- Issues: mailto:api@analyticadss.com
|
|
177
|
+
|
|
178
|
+
Built by [Analytica Data Science Solutions](https://analyticadss.com).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
GlucoGuard: () => GlucoGuard,
|
|
25
|
+
GlucoGuardError: () => GlucoGuardError,
|
|
26
|
+
RateLimitError: () => RateLimitError,
|
|
27
|
+
ValidationError: () => ValidationError,
|
|
28
|
+
default: () => GlucoGuard
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/errors.ts
|
|
33
|
+
var GlucoGuardError = class extends Error {
|
|
34
|
+
statusCode;
|
|
35
|
+
body;
|
|
36
|
+
requestId;
|
|
37
|
+
constructor(message, opts = {}) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "GlucoGuardError";
|
|
40
|
+
this.statusCode = opts.statusCode;
|
|
41
|
+
this.body = opts.body;
|
|
42
|
+
this.requestId = opts.requestId;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var AuthenticationError = class extends GlucoGuardError {
|
|
46
|
+
constructor(message, opts = {}) {
|
|
47
|
+
super(message, opts);
|
|
48
|
+
this.name = "AuthenticationError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var RateLimitError = class extends GlucoGuardError {
|
|
52
|
+
constructor(message, opts = {}) {
|
|
53
|
+
super(message, opts);
|
|
54
|
+
this.name = "RateLimitError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var ValidationError = class extends GlucoGuardError {
|
|
58
|
+
constructor(message, opts = {}) {
|
|
59
|
+
super(message, opts);
|
|
60
|
+
this.name = "ValidationError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/client.ts
|
|
65
|
+
var DEFAULT_BASE_URL = "https://glucoguard.analyticadss.com/api";
|
|
66
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
67
|
+
var DEFAULT_USER_AGENT = "glucoguard-js/0.1.0";
|
|
68
|
+
var GlucoGuard = class {
|
|
69
|
+
apiKey;
|
|
70
|
+
baseUrl;
|
|
71
|
+
timeout;
|
|
72
|
+
fetchImpl;
|
|
73
|
+
userAgent;
|
|
74
|
+
constructor(options) {
|
|
75
|
+
if (!options.apiKey || !options.apiKey.startsWith("sk_")) {
|
|
76
|
+
throw new Error('apiKey must start with "sk_" (e.g. sk_live_...)');
|
|
77
|
+
}
|
|
78
|
+
this.apiKey = options.apiKey;
|
|
79
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
80
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
81
|
+
this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
|
|
82
|
+
const resolvedFetch = options.fetch ?? globalThis.fetch;
|
|
83
|
+
if (!resolvedFetch) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"No fetch implementation available. Upgrade to Node 18+ or pass a fetch option."
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
this.fetchImpl = resolvedFetch;
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------------
|
|
91
|
+
// Core request helper
|
|
92
|
+
// ---------------------------------------------------------------------
|
|
93
|
+
async request(method, path, body, returnBinary = false) {
|
|
94
|
+
const url = `${this.baseUrl}${path}`;
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
97
|
+
let response;
|
|
98
|
+
try {
|
|
99
|
+
response = await this.fetchImpl(url, {
|
|
100
|
+
method,
|
|
101
|
+
headers: {
|
|
102
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
"User-Agent": this.userAgent
|
|
105
|
+
},
|
|
106
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
107
|
+
signal: controller.signal
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
if (err.name === "AbortError") {
|
|
112
|
+
throw new GlucoGuardError(`Request timed out after ${this.timeout}ms`);
|
|
113
|
+
}
|
|
114
|
+
throw new GlucoGuardError(`Network error: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
let errorBody = null;
|
|
120
|
+
let detail = `HTTP ${response.status}`;
|
|
121
|
+
try {
|
|
122
|
+
errorBody = await response.json();
|
|
123
|
+
const body2 = errorBody;
|
|
124
|
+
if (typeof body2.detail === "string") {
|
|
125
|
+
detail = body2.detail;
|
|
126
|
+
} else if (Array.isArray(body2.detail)) {
|
|
127
|
+
detail = body2.detail.map((d) => JSON.stringify(d)).join("; ");
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
try {
|
|
131
|
+
detail = await response.text();
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const opts = { statusCode: response.status, body: errorBody, requestId };
|
|
136
|
+
if (response.status === 401 || response.status === 403) {
|
|
137
|
+
throw new AuthenticationError(detail, opts);
|
|
138
|
+
}
|
|
139
|
+
if (response.status === 429) {
|
|
140
|
+
throw new RateLimitError(detail, opts);
|
|
141
|
+
}
|
|
142
|
+
if (response.status === 422 || response.status === 400) {
|
|
143
|
+
throw new ValidationError(detail, opts);
|
|
144
|
+
}
|
|
145
|
+
throw new GlucoGuardError(`${method} ${path} failed: ${detail}`, opts);
|
|
146
|
+
}
|
|
147
|
+
if (returnBinary) {
|
|
148
|
+
return await response.arrayBuffer();
|
|
149
|
+
}
|
|
150
|
+
return await response.json();
|
|
151
|
+
}
|
|
152
|
+
// ---------------------------------------------------------------------
|
|
153
|
+
// Predictions
|
|
154
|
+
// ---------------------------------------------------------------------
|
|
155
|
+
/** Predict diabetes risk for a single patient. */
|
|
156
|
+
async predictDiabetes(patient) {
|
|
157
|
+
return this.request("POST", "/predict/diabetes", patient);
|
|
158
|
+
}
|
|
159
|
+
/** Predict cardiovascular disease risk for a single patient. */
|
|
160
|
+
async predictCVD(patient) {
|
|
161
|
+
return this.request("POST", "/predict/cvd", patient);
|
|
162
|
+
}
|
|
163
|
+
/** Generic disease predictor — use the specific methods above for stronger typing. */
|
|
164
|
+
async predict(disease, patient) {
|
|
165
|
+
return this.request("POST", `/predict/${disease}`, patient);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Run batch predictions.
|
|
169
|
+
*
|
|
170
|
+
* - Without `webhookUrl`: runs synchronously and returns inline results.
|
|
171
|
+
* - With `webhookUrl`: returns `{ job_id, status: 'queued', ... }` immediately
|
|
172
|
+
* and POSTs the final result to your URL when complete.
|
|
173
|
+
*/
|
|
174
|
+
async predictBatch(patients, options = {}) {
|
|
175
|
+
const body = { patients };
|
|
176
|
+
if (options.webhookUrl) {
|
|
177
|
+
body.webhook_url = options.webhookUrl;
|
|
178
|
+
}
|
|
179
|
+
return this.request("POST", "/predict/batch", body);
|
|
180
|
+
}
|
|
181
|
+
/** Type guard to narrow a BatchResult to the webhook-mode shape. */
|
|
182
|
+
static isWebhookResult(result) {
|
|
183
|
+
return result.job_id !== void 0;
|
|
184
|
+
}
|
|
185
|
+
/** Type guard to narrow a BatchResult to the inline-mode shape. */
|
|
186
|
+
static isInlineResult(result) {
|
|
187
|
+
return result.results !== void 0;
|
|
188
|
+
}
|
|
189
|
+
// ---------------------------------------------------------------------
|
|
190
|
+
// Ops endpoints
|
|
191
|
+
// ---------------------------------------------------------------------
|
|
192
|
+
/** Simple liveness probe — public endpoint, works without a key. */
|
|
193
|
+
async health() {
|
|
194
|
+
return this.request("GET", "/health");
|
|
195
|
+
}
|
|
196
|
+
/** Detailed service status — uptime, model versions, AUC scores. Public. */
|
|
197
|
+
async status() {
|
|
198
|
+
return this.request("GET", "/status");
|
|
199
|
+
}
|
|
200
|
+
/** Usage statistics for the authenticated key. Accepts an optional time window in days (1-90). */
|
|
201
|
+
async usage(days = 7) {
|
|
202
|
+
return this.request("GET", `/usage?days=${days}`);
|
|
203
|
+
}
|
|
204
|
+
// ---------------------------------------------------------------------
|
|
205
|
+
// Metadata
|
|
206
|
+
// ---------------------------------------------------------------------
|
|
207
|
+
async modelInfo(disease) {
|
|
208
|
+
return this.request("GET", `/model/info/${disease}`);
|
|
209
|
+
}
|
|
210
|
+
async samplePatients() {
|
|
211
|
+
return this.request("GET", "/patients/samples");
|
|
212
|
+
}
|
|
213
|
+
// ---------------------------------------------------------------------
|
|
214
|
+
// PDF report
|
|
215
|
+
// ---------------------------------------------------------------------
|
|
216
|
+
/** Generate a PDF report. Returns the raw bytes as an ArrayBuffer. */
|
|
217
|
+
async generatePDFReport(request) {
|
|
218
|
+
return this.request("POST", "/report/pdf", request, true);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
222
|
+
0 && (module.exports = {
|
|
223
|
+
AuthenticationError,
|
|
224
|
+
GlucoGuard,
|
|
225
|
+
GlucoGuardError,
|
|
226
|
+
RateLimitError,
|
|
227
|
+
ValidationError
|
|
228
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the GlucoGuard API.
|
|
3
|
+
*/
|
|
4
|
+
type Disease = 'diabetes' | 'cvd';
|
|
5
|
+
type RiskCategory = 'low' | 'moderate' | 'high';
|
|
6
|
+
type Tier = 'free' | 'pro' | 'enterprise';
|
|
7
|
+
interface PatientInput {
|
|
8
|
+
/** Age in years, 20-120 */
|
|
9
|
+
age: number;
|
|
10
|
+
/** 0 = Female, 1 = Male */
|
|
11
|
+
gender: 0 | 1;
|
|
12
|
+
/** BMI in kg/m² */
|
|
13
|
+
bmi: number;
|
|
14
|
+
/** Systolic blood pressure in mmHg */
|
|
15
|
+
systolic_bp: number;
|
|
16
|
+
/** Diastolic blood pressure in mmHg */
|
|
17
|
+
diastolic_bp: number;
|
|
18
|
+
/** HbA1c / Glycohemoglobin percentage */
|
|
19
|
+
hba1c?: number | null;
|
|
20
|
+
/** HDL cholesterol in mg/dL */
|
|
21
|
+
hdl_cholesterol?: number | null;
|
|
22
|
+
/** Total cholesterol in mg/dL */
|
|
23
|
+
total_cholesterol?: number | null;
|
|
24
|
+
/** Waist circumference in cm */
|
|
25
|
+
waist?: number | null;
|
|
26
|
+
/** Weight in kg */
|
|
27
|
+
weight?: number | null;
|
|
28
|
+
/** Height in cm */
|
|
29
|
+
height?: number | null;
|
|
30
|
+
}
|
|
31
|
+
interface TopFactor {
|
|
32
|
+
feature: string;
|
|
33
|
+
shap_value: number;
|
|
34
|
+
direction: 'increases' | 'decreases';
|
|
35
|
+
input_value: number;
|
|
36
|
+
}
|
|
37
|
+
interface PredictionResult {
|
|
38
|
+
/** Risk score 0-100 */
|
|
39
|
+
risk_score: number;
|
|
40
|
+
risk_category: RiskCategory;
|
|
41
|
+
recommendation: string;
|
|
42
|
+
shap_values: Record<string, number>;
|
|
43
|
+
top_factors: TopFactor[];
|
|
44
|
+
}
|
|
45
|
+
interface BatchResultItem {
|
|
46
|
+
index: number;
|
|
47
|
+
patient: PatientInput;
|
|
48
|
+
diabetes: PredictionResult;
|
|
49
|
+
cvd: PredictionResult;
|
|
50
|
+
}
|
|
51
|
+
interface BatchInlineResult {
|
|
52
|
+
results: BatchResultItem[];
|
|
53
|
+
total: number;
|
|
54
|
+
processing_time_ms: number;
|
|
55
|
+
}
|
|
56
|
+
interface BatchWebhookResult {
|
|
57
|
+
job_id: string;
|
|
58
|
+
status: 'queued';
|
|
59
|
+
patient_count: number;
|
|
60
|
+
webhook_url: string;
|
|
61
|
+
message: string;
|
|
62
|
+
}
|
|
63
|
+
type BatchResult = BatchInlineResult | BatchWebhookResult;
|
|
64
|
+
interface HealthResponse {
|
|
65
|
+
status: 'ok' | 'degraded';
|
|
66
|
+
service: string;
|
|
67
|
+
version: string;
|
|
68
|
+
}
|
|
69
|
+
interface StatusResponse {
|
|
70
|
+
status: 'ok' | 'degraded';
|
|
71
|
+
service: string;
|
|
72
|
+
version: string;
|
|
73
|
+
uptime_seconds: number;
|
|
74
|
+
uptime_human: string;
|
|
75
|
+
started_at: string;
|
|
76
|
+
current_time: string;
|
|
77
|
+
models: {
|
|
78
|
+
diabetes?: ModelSummary;
|
|
79
|
+
cvd?: ModelSummary;
|
|
80
|
+
error?: string;
|
|
81
|
+
};
|
|
82
|
+
built_by: string;
|
|
83
|
+
docs: {
|
|
84
|
+
swagger: string;
|
|
85
|
+
redoc: string;
|
|
86
|
+
openapi: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
interface ModelSummary {
|
|
90
|
+
type: string;
|
|
91
|
+
auc_roc: number;
|
|
92
|
+
n_samples: number;
|
|
93
|
+
n_features: number;
|
|
94
|
+
}
|
|
95
|
+
interface ModelInfoResponse {
|
|
96
|
+
metrics: Record<string, number>;
|
|
97
|
+
feature_importance: Record<string, number>;
|
|
98
|
+
n_samples: number;
|
|
99
|
+
n_features: number;
|
|
100
|
+
feature_names: string[];
|
|
101
|
+
model_type?: string;
|
|
102
|
+
all_results?: Array<Record<string, unknown>>;
|
|
103
|
+
}
|
|
104
|
+
interface UsageResponse {
|
|
105
|
+
key: {
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
tier: Tier;
|
|
109
|
+
prefix: string;
|
|
110
|
+
};
|
|
111
|
+
usage: {
|
|
112
|
+
days: number;
|
|
113
|
+
total_requests: number;
|
|
114
|
+
avg_duration_ms: number;
|
|
115
|
+
errors_4xx: number;
|
|
116
|
+
errors_5xx: number;
|
|
117
|
+
by_endpoint: Record<string, number>;
|
|
118
|
+
error?: string;
|
|
119
|
+
};
|
|
120
|
+
global?: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
interface SamplePatient {
|
|
123
|
+
name: string;
|
|
124
|
+
description: string;
|
|
125
|
+
data: PatientInput;
|
|
126
|
+
}
|
|
127
|
+
interface ClientOptions {
|
|
128
|
+
/** API key, must start with "sk_live_..." */
|
|
129
|
+
apiKey: string;
|
|
130
|
+
/** Base URL — defaults to production */
|
|
131
|
+
baseUrl?: string;
|
|
132
|
+
/** Request timeout in milliseconds — default 30000 */
|
|
133
|
+
timeout?: number;
|
|
134
|
+
/** Custom fetch implementation (for Node 16, tests, etc.) */
|
|
135
|
+
fetch?: typeof fetch;
|
|
136
|
+
/** Custom user agent */
|
|
137
|
+
userAgent?: string;
|
|
138
|
+
}
|
|
139
|
+
interface PDFReportRequest {
|
|
140
|
+
patient: PatientInput;
|
|
141
|
+
diabetes_result: Partial<PredictionResult>;
|
|
142
|
+
cvd_result: Partial<PredictionResult>;
|
|
143
|
+
patient_name?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* GlucoGuard API client.
|
|
148
|
+
*
|
|
149
|
+
* A thin, typed wrapper around the REST API. Works in Node 18+ and any
|
|
150
|
+
* modern browser thanks to the built-in fetch API. Zero runtime dependencies.
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
declare class GlucoGuard {
|
|
154
|
+
private readonly apiKey;
|
|
155
|
+
private readonly baseUrl;
|
|
156
|
+
private readonly timeout;
|
|
157
|
+
private readonly fetchImpl;
|
|
158
|
+
private readonly userAgent;
|
|
159
|
+
constructor(options: ClientOptions);
|
|
160
|
+
private request;
|
|
161
|
+
/** Predict diabetes risk for a single patient. */
|
|
162
|
+
predictDiabetes(patient: PatientInput): Promise<PredictionResult>;
|
|
163
|
+
/** Predict cardiovascular disease risk for a single patient. */
|
|
164
|
+
predictCVD(patient: PatientInput): Promise<PredictionResult>;
|
|
165
|
+
/** Generic disease predictor — use the specific methods above for stronger typing. */
|
|
166
|
+
predict(disease: Disease, patient: PatientInput): Promise<PredictionResult>;
|
|
167
|
+
/**
|
|
168
|
+
* Run batch predictions.
|
|
169
|
+
*
|
|
170
|
+
* - Without `webhookUrl`: runs synchronously and returns inline results.
|
|
171
|
+
* - With `webhookUrl`: returns `{ job_id, status: 'queued', ... }` immediately
|
|
172
|
+
* and POSTs the final result to your URL when complete.
|
|
173
|
+
*/
|
|
174
|
+
predictBatch(patients: PatientInput[], options?: {
|
|
175
|
+
webhookUrl?: string;
|
|
176
|
+
}): Promise<BatchResult>;
|
|
177
|
+
/** Type guard to narrow a BatchResult to the webhook-mode shape. */
|
|
178
|
+
static isWebhookResult(result: BatchResult): result is BatchWebhookResult;
|
|
179
|
+
/** Type guard to narrow a BatchResult to the inline-mode shape. */
|
|
180
|
+
static isInlineResult(result: BatchResult): result is BatchInlineResult;
|
|
181
|
+
/** Simple liveness probe — public endpoint, works without a key. */
|
|
182
|
+
health(): Promise<HealthResponse>;
|
|
183
|
+
/** Detailed service status — uptime, model versions, AUC scores. Public. */
|
|
184
|
+
status(): Promise<StatusResponse>;
|
|
185
|
+
/** Usage statistics for the authenticated key. Accepts an optional time window in days (1-90). */
|
|
186
|
+
usage(days?: number): Promise<UsageResponse>;
|
|
187
|
+
modelInfo(disease: Disease): Promise<ModelInfoResponse>;
|
|
188
|
+
samplePatients(): Promise<SamplePatient[]>;
|
|
189
|
+
/** Generate a PDF report. Returns the raw bytes as an ArrayBuffer. */
|
|
190
|
+
generatePDFReport(request: PDFReportRequest): Promise<ArrayBuffer>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Typed errors surfaced by the GlucoGuard client.
|
|
195
|
+
*/
|
|
196
|
+
declare class GlucoGuardError extends Error {
|
|
197
|
+
readonly statusCode?: number;
|
|
198
|
+
readonly body?: unknown;
|
|
199
|
+
readonly requestId?: string;
|
|
200
|
+
constructor(message: string, opts?: {
|
|
201
|
+
statusCode?: number;
|
|
202
|
+
body?: unknown;
|
|
203
|
+
requestId?: string;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
declare class AuthenticationError extends GlucoGuardError {
|
|
207
|
+
constructor(message: string, opts?: ConstructorParameters<typeof GlucoGuardError>[1]);
|
|
208
|
+
}
|
|
209
|
+
declare class RateLimitError extends GlucoGuardError {
|
|
210
|
+
constructor(message: string, opts?: ConstructorParameters<typeof GlucoGuardError>[1]);
|
|
211
|
+
}
|
|
212
|
+
declare class ValidationError extends GlucoGuardError {
|
|
213
|
+
constructor(message: string, opts?: ConstructorParameters<typeof GlucoGuardError>[1]);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { AuthenticationError, type BatchInlineResult, type BatchResult, type BatchResultItem, type BatchWebhookResult, type ClientOptions, type Disease, GlucoGuard, GlucoGuardError, type HealthResponse, type ModelInfoResponse, type ModelSummary, type PDFReportRequest, type PatientInput, type PredictionResult, RateLimitError, type RiskCategory, type SamplePatient, type StatusResponse, type Tier, type TopFactor, type UsageResponse, ValidationError, GlucoGuard as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the GlucoGuard API.
|
|
3
|
+
*/
|
|
4
|
+
type Disease = 'diabetes' | 'cvd';
|
|
5
|
+
type RiskCategory = 'low' | 'moderate' | 'high';
|
|
6
|
+
type Tier = 'free' | 'pro' | 'enterprise';
|
|
7
|
+
interface PatientInput {
|
|
8
|
+
/** Age in years, 20-120 */
|
|
9
|
+
age: number;
|
|
10
|
+
/** 0 = Female, 1 = Male */
|
|
11
|
+
gender: 0 | 1;
|
|
12
|
+
/** BMI in kg/m² */
|
|
13
|
+
bmi: number;
|
|
14
|
+
/** Systolic blood pressure in mmHg */
|
|
15
|
+
systolic_bp: number;
|
|
16
|
+
/** Diastolic blood pressure in mmHg */
|
|
17
|
+
diastolic_bp: number;
|
|
18
|
+
/** HbA1c / Glycohemoglobin percentage */
|
|
19
|
+
hba1c?: number | null;
|
|
20
|
+
/** HDL cholesterol in mg/dL */
|
|
21
|
+
hdl_cholesterol?: number | null;
|
|
22
|
+
/** Total cholesterol in mg/dL */
|
|
23
|
+
total_cholesterol?: number | null;
|
|
24
|
+
/** Waist circumference in cm */
|
|
25
|
+
waist?: number | null;
|
|
26
|
+
/** Weight in kg */
|
|
27
|
+
weight?: number | null;
|
|
28
|
+
/** Height in cm */
|
|
29
|
+
height?: number | null;
|
|
30
|
+
}
|
|
31
|
+
interface TopFactor {
|
|
32
|
+
feature: string;
|
|
33
|
+
shap_value: number;
|
|
34
|
+
direction: 'increases' | 'decreases';
|
|
35
|
+
input_value: number;
|
|
36
|
+
}
|
|
37
|
+
interface PredictionResult {
|
|
38
|
+
/** Risk score 0-100 */
|
|
39
|
+
risk_score: number;
|
|
40
|
+
risk_category: RiskCategory;
|
|
41
|
+
recommendation: string;
|
|
42
|
+
shap_values: Record<string, number>;
|
|
43
|
+
top_factors: TopFactor[];
|
|
44
|
+
}
|
|
45
|
+
interface BatchResultItem {
|
|
46
|
+
index: number;
|
|
47
|
+
patient: PatientInput;
|
|
48
|
+
diabetes: PredictionResult;
|
|
49
|
+
cvd: PredictionResult;
|
|
50
|
+
}
|
|
51
|
+
interface BatchInlineResult {
|
|
52
|
+
results: BatchResultItem[];
|
|
53
|
+
total: number;
|
|
54
|
+
processing_time_ms: number;
|
|
55
|
+
}
|
|
56
|
+
interface BatchWebhookResult {
|
|
57
|
+
job_id: string;
|
|
58
|
+
status: 'queued';
|
|
59
|
+
patient_count: number;
|
|
60
|
+
webhook_url: string;
|
|
61
|
+
message: string;
|
|
62
|
+
}
|
|
63
|
+
type BatchResult = BatchInlineResult | BatchWebhookResult;
|
|
64
|
+
interface HealthResponse {
|
|
65
|
+
status: 'ok' | 'degraded';
|
|
66
|
+
service: string;
|
|
67
|
+
version: string;
|
|
68
|
+
}
|
|
69
|
+
interface StatusResponse {
|
|
70
|
+
status: 'ok' | 'degraded';
|
|
71
|
+
service: string;
|
|
72
|
+
version: string;
|
|
73
|
+
uptime_seconds: number;
|
|
74
|
+
uptime_human: string;
|
|
75
|
+
started_at: string;
|
|
76
|
+
current_time: string;
|
|
77
|
+
models: {
|
|
78
|
+
diabetes?: ModelSummary;
|
|
79
|
+
cvd?: ModelSummary;
|
|
80
|
+
error?: string;
|
|
81
|
+
};
|
|
82
|
+
built_by: string;
|
|
83
|
+
docs: {
|
|
84
|
+
swagger: string;
|
|
85
|
+
redoc: string;
|
|
86
|
+
openapi: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
interface ModelSummary {
|
|
90
|
+
type: string;
|
|
91
|
+
auc_roc: number;
|
|
92
|
+
n_samples: number;
|
|
93
|
+
n_features: number;
|
|
94
|
+
}
|
|
95
|
+
interface ModelInfoResponse {
|
|
96
|
+
metrics: Record<string, number>;
|
|
97
|
+
feature_importance: Record<string, number>;
|
|
98
|
+
n_samples: number;
|
|
99
|
+
n_features: number;
|
|
100
|
+
feature_names: string[];
|
|
101
|
+
model_type?: string;
|
|
102
|
+
all_results?: Array<Record<string, unknown>>;
|
|
103
|
+
}
|
|
104
|
+
interface UsageResponse {
|
|
105
|
+
key: {
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
tier: Tier;
|
|
109
|
+
prefix: string;
|
|
110
|
+
};
|
|
111
|
+
usage: {
|
|
112
|
+
days: number;
|
|
113
|
+
total_requests: number;
|
|
114
|
+
avg_duration_ms: number;
|
|
115
|
+
errors_4xx: number;
|
|
116
|
+
errors_5xx: number;
|
|
117
|
+
by_endpoint: Record<string, number>;
|
|
118
|
+
error?: string;
|
|
119
|
+
};
|
|
120
|
+
global?: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
interface SamplePatient {
|
|
123
|
+
name: string;
|
|
124
|
+
description: string;
|
|
125
|
+
data: PatientInput;
|
|
126
|
+
}
|
|
127
|
+
interface ClientOptions {
|
|
128
|
+
/** API key, must start with "sk_live_..." */
|
|
129
|
+
apiKey: string;
|
|
130
|
+
/** Base URL — defaults to production */
|
|
131
|
+
baseUrl?: string;
|
|
132
|
+
/** Request timeout in milliseconds — default 30000 */
|
|
133
|
+
timeout?: number;
|
|
134
|
+
/** Custom fetch implementation (for Node 16, tests, etc.) */
|
|
135
|
+
fetch?: typeof fetch;
|
|
136
|
+
/** Custom user agent */
|
|
137
|
+
userAgent?: string;
|
|
138
|
+
}
|
|
139
|
+
interface PDFReportRequest {
|
|
140
|
+
patient: PatientInput;
|
|
141
|
+
diabetes_result: Partial<PredictionResult>;
|
|
142
|
+
cvd_result: Partial<PredictionResult>;
|
|
143
|
+
patient_name?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* GlucoGuard API client.
|
|
148
|
+
*
|
|
149
|
+
* A thin, typed wrapper around the REST API. Works in Node 18+ and any
|
|
150
|
+
* modern browser thanks to the built-in fetch API. Zero runtime dependencies.
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
declare class GlucoGuard {
|
|
154
|
+
private readonly apiKey;
|
|
155
|
+
private readonly baseUrl;
|
|
156
|
+
private readonly timeout;
|
|
157
|
+
private readonly fetchImpl;
|
|
158
|
+
private readonly userAgent;
|
|
159
|
+
constructor(options: ClientOptions);
|
|
160
|
+
private request;
|
|
161
|
+
/** Predict diabetes risk for a single patient. */
|
|
162
|
+
predictDiabetes(patient: PatientInput): Promise<PredictionResult>;
|
|
163
|
+
/** Predict cardiovascular disease risk for a single patient. */
|
|
164
|
+
predictCVD(patient: PatientInput): Promise<PredictionResult>;
|
|
165
|
+
/** Generic disease predictor — use the specific methods above for stronger typing. */
|
|
166
|
+
predict(disease: Disease, patient: PatientInput): Promise<PredictionResult>;
|
|
167
|
+
/**
|
|
168
|
+
* Run batch predictions.
|
|
169
|
+
*
|
|
170
|
+
* - Without `webhookUrl`: runs synchronously and returns inline results.
|
|
171
|
+
* - With `webhookUrl`: returns `{ job_id, status: 'queued', ... }` immediately
|
|
172
|
+
* and POSTs the final result to your URL when complete.
|
|
173
|
+
*/
|
|
174
|
+
predictBatch(patients: PatientInput[], options?: {
|
|
175
|
+
webhookUrl?: string;
|
|
176
|
+
}): Promise<BatchResult>;
|
|
177
|
+
/** Type guard to narrow a BatchResult to the webhook-mode shape. */
|
|
178
|
+
static isWebhookResult(result: BatchResult): result is BatchWebhookResult;
|
|
179
|
+
/** Type guard to narrow a BatchResult to the inline-mode shape. */
|
|
180
|
+
static isInlineResult(result: BatchResult): result is BatchInlineResult;
|
|
181
|
+
/** Simple liveness probe — public endpoint, works without a key. */
|
|
182
|
+
health(): Promise<HealthResponse>;
|
|
183
|
+
/** Detailed service status — uptime, model versions, AUC scores. Public. */
|
|
184
|
+
status(): Promise<StatusResponse>;
|
|
185
|
+
/** Usage statistics for the authenticated key. Accepts an optional time window in days (1-90). */
|
|
186
|
+
usage(days?: number): Promise<UsageResponse>;
|
|
187
|
+
modelInfo(disease: Disease): Promise<ModelInfoResponse>;
|
|
188
|
+
samplePatients(): Promise<SamplePatient[]>;
|
|
189
|
+
/** Generate a PDF report. Returns the raw bytes as an ArrayBuffer. */
|
|
190
|
+
generatePDFReport(request: PDFReportRequest): Promise<ArrayBuffer>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Typed errors surfaced by the GlucoGuard client.
|
|
195
|
+
*/
|
|
196
|
+
declare class GlucoGuardError extends Error {
|
|
197
|
+
readonly statusCode?: number;
|
|
198
|
+
readonly body?: unknown;
|
|
199
|
+
readonly requestId?: string;
|
|
200
|
+
constructor(message: string, opts?: {
|
|
201
|
+
statusCode?: number;
|
|
202
|
+
body?: unknown;
|
|
203
|
+
requestId?: string;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
declare class AuthenticationError extends GlucoGuardError {
|
|
207
|
+
constructor(message: string, opts?: ConstructorParameters<typeof GlucoGuardError>[1]);
|
|
208
|
+
}
|
|
209
|
+
declare class RateLimitError extends GlucoGuardError {
|
|
210
|
+
constructor(message: string, opts?: ConstructorParameters<typeof GlucoGuardError>[1]);
|
|
211
|
+
}
|
|
212
|
+
declare class ValidationError extends GlucoGuardError {
|
|
213
|
+
constructor(message: string, opts?: ConstructorParameters<typeof GlucoGuardError>[1]);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { AuthenticationError, type BatchInlineResult, type BatchResult, type BatchResultItem, type BatchWebhookResult, type ClientOptions, type Disease, GlucoGuard, GlucoGuardError, type HealthResponse, type ModelInfoResponse, type ModelSummary, type PDFReportRequest, type PatientInput, type PredictionResult, RateLimitError, type RiskCategory, type SamplePatient, type StatusResponse, type Tier, type TopFactor, type UsageResponse, ValidationError, GlucoGuard as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var GlucoGuardError = class extends Error {
|
|
3
|
+
statusCode;
|
|
4
|
+
body;
|
|
5
|
+
requestId;
|
|
6
|
+
constructor(message, opts = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "GlucoGuardError";
|
|
9
|
+
this.statusCode = opts.statusCode;
|
|
10
|
+
this.body = opts.body;
|
|
11
|
+
this.requestId = opts.requestId;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var AuthenticationError = class extends GlucoGuardError {
|
|
15
|
+
constructor(message, opts = {}) {
|
|
16
|
+
super(message, opts);
|
|
17
|
+
this.name = "AuthenticationError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var RateLimitError = class extends GlucoGuardError {
|
|
21
|
+
constructor(message, opts = {}) {
|
|
22
|
+
super(message, opts);
|
|
23
|
+
this.name = "RateLimitError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var ValidationError = class extends GlucoGuardError {
|
|
27
|
+
constructor(message, opts = {}) {
|
|
28
|
+
super(message, opts);
|
|
29
|
+
this.name = "ValidationError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/client.ts
|
|
34
|
+
var DEFAULT_BASE_URL = "https://glucoguard.analyticadss.com/api";
|
|
35
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
36
|
+
var DEFAULT_USER_AGENT = "glucoguard-js/0.1.0";
|
|
37
|
+
var GlucoGuard = class {
|
|
38
|
+
apiKey;
|
|
39
|
+
baseUrl;
|
|
40
|
+
timeout;
|
|
41
|
+
fetchImpl;
|
|
42
|
+
userAgent;
|
|
43
|
+
constructor(options) {
|
|
44
|
+
if (!options.apiKey || !options.apiKey.startsWith("sk_")) {
|
|
45
|
+
throw new Error('apiKey must start with "sk_" (e.g. sk_live_...)');
|
|
46
|
+
}
|
|
47
|
+
this.apiKey = options.apiKey;
|
|
48
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
49
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
50
|
+
this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
|
|
51
|
+
const resolvedFetch = options.fetch ?? globalThis.fetch;
|
|
52
|
+
if (!resolvedFetch) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"No fetch implementation available. Upgrade to Node 18+ or pass a fetch option."
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
this.fetchImpl = resolvedFetch;
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------
|
|
60
|
+
// Core request helper
|
|
61
|
+
// ---------------------------------------------------------------------
|
|
62
|
+
async request(method, path, body, returnBinary = false) {
|
|
63
|
+
const url = `${this.baseUrl}${path}`;
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
66
|
+
let response;
|
|
67
|
+
try {
|
|
68
|
+
response = await this.fetchImpl(url, {
|
|
69
|
+
method,
|
|
70
|
+
headers: {
|
|
71
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"User-Agent": this.userAgent
|
|
74
|
+
},
|
|
75
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
76
|
+
signal: controller.signal
|
|
77
|
+
});
|
|
78
|
+
} catch (err) {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
if (err.name === "AbortError") {
|
|
81
|
+
throw new GlucoGuardError(`Request timed out after ${this.timeout}ms`);
|
|
82
|
+
}
|
|
83
|
+
throw new GlucoGuardError(`Network error: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
let errorBody = null;
|
|
89
|
+
let detail = `HTTP ${response.status}`;
|
|
90
|
+
try {
|
|
91
|
+
errorBody = await response.json();
|
|
92
|
+
const body2 = errorBody;
|
|
93
|
+
if (typeof body2.detail === "string") {
|
|
94
|
+
detail = body2.detail;
|
|
95
|
+
} else if (Array.isArray(body2.detail)) {
|
|
96
|
+
detail = body2.detail.map((d) => JSON.stringify(d)).join("; ");
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
try {
|
|
100
|
+
detail = await response.text();
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const opts = { statusCode: response.status, body: errorBody, requestId };
|
|
105
|
+
if (response.status === 401 || response.status === 403) {
|
|
106
|
+
throw new AuthenticationError(detail, opts);
|
|
107
|
+
}
|
|
108
|
+
if (response.status === 429) {
|
|
109
|
+
throw new RateLimitError(detail, opts);
|
|
110
|
+
}
|
|
111
|
+
if (response.status === 422 || response.status === 400) {
|
|
112
|
+
throw new ValidationError(detail, opts);
|
|
113
|
+
}
|
|
114
|
+
throw new GlucoGuardError(`${method} ${path} failed: ${detail}`, opts);
|
|
115
|
+
}
|
|
116
|
+
if (returnBinary) {
|
|
117
|
+
return await response.arrayBuffer();
|
|
118
|
+
}
|
|
119
|
+
return await response.json();
|
|
120
|
+
}
|
|
121
|
+
// ---------------------------------------------------------------------
|
|
122
|
+
// Predictions
|
|
123
|
+
// ---------------------------------------------------------------------
|
|
124
|
+
/** Predict diabetes risk for a single patient. */
|
|
125
|
+
async predictDiabetes(patient) {
|
|
126
|
+
return this.request("POST", "/predict/diabetes", patient);
|
|
127
|
+
}
|
|
128
|
+
/** Predict cardiovascular disease risk for a single patient. */
|
|
129
|
+
async predictCVD(patient) {
|
|
130
|
+
return this.request("POST", "/predict/cvd", patient);
|
|
131
|
+
}
|
|
132
|
+
/** Generic disease predictor — use the specific methods above for stronger typing. */
|
|
133
|
+
async predict(disease, patient) {
|
|
134
|
+
return this.request("POST", `/predict/${disease}`, patient);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Run batch predictions.
|
|
138
|
+
*
|
|
139
|
+
* - Without `webhookUrl`: runs synchronously and returns inline results.
|
|
140
|
+
* - With `webhookUrl`: returns `{ job_id, status: 'queued', ... }` immediately
|
|
141
|
+
* and POSTs the final result to your URL when complete.
|
|
142
|
+
*/
|
|
143
|
+
async predictBatch(patients, options = {}) {
|
|
144
|
+
const body = { patients };
|
|
145
|
+
if (options.webhookUrl) {
|
|
146
|
+
body.webhook_url = options.webhookUrl;
|
|
147
|
+
}
|
|
148
|
+
return this.request("POST", "/predict/batch", body);
|
|
149
|
+
}
|
|
150
|
+
/** Type guard to narrow a BatchResult to the webhook-mode shape. */
|
|
151
|
+
static isWebhookResult(result) {
|
|
152
|
+
return result.job_id !== void 0;
|
|
153
|
+
}
|
|
154
|
+
/** Type guard to narrow a BatchResult to the inline-mode shape. */
|
|
155
|
+
static isInlineResult(result) {
|
|
156
|
+
return result.results !== void 0;
|
|
157
|
+
}
|
|
158
|
+
// ---------------------------------------------------------------------
|
|
159
|
+
// Ops endpoints
|
|
160
|
+
// ---------------------------------------------------------------------
|
|
161
|
+
/** Simple liveness probe — public endpoint, works without a key. */
|
|
162
|
+
async health() {
|
|
163
|
+
return this.request("GET", "/health");
|
|
164
|
+
}
|
|
165
|
+
/** Detailed service status — uptime, model versions, AUC scores. Public. */
|
|
166
|
+
async status() {
|
|
167
|
+
return this.request("GET", "/status");
|
|
168
|
+
}
|
|
169
|
+
/** Usage statistics for the authenticated key. Accepts an optional time window in days (1-90). */
|
|
170
|
+
async usage(days = 7) {
|
|
171
|
+
return this.request("GET", `/usage?days=${days}`);
|
|
172
|
+
}
|
|
173
|
+
// ---------------------------------------------------------------------
|
|
174
|
+
// Metadata
|
|
175
|
+
// ---------------------------------------------------------------------
|
|
176
|
+
async modelInfo(disease) {
|
|
177
|
+
return this.request("GET", `/model/info/${disease}`);
|
|
178
|
+
}
|
|
179
|
+
async samplePatients() {
|
|
180
|
+
return this.request("GET", "/patients/samples");
|
|
181
|
+
}
|
|
182
|
+
// ---------------------------------------------------------------------
|
|
183
|
+
// PDF report
|
|
184
|
+
// ---------------------------------------------------------------------
|
|
185
|
+
/** Generate a PDF report. Returns the raw bytes as an ArrayBuffer. */
|
|
186
|
+
async generatePDFReport(request) {
|
|
187
|
+
return this.request("POST", "/report/pdf", request, true);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
export {
|
|
191
|
+
AuthenticationError,
|
|
192
|
+
GlucoGuard,
|
|
193
|
+
GlucoGuardError,
|
|
194
|
+
RateLimitError,
|
|
195
|
+
ValidationError,
|
|
196
|
+
GlucoGuard as default
|
|
197
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "glucoguard",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript client for the GlucoGuard diabetes and cardiovascular risk prediction API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"glucoguard",
|
|
29
|
+
"health",
|
|
30
|
+
"diabetes",
|
|
31
|
+
"cvd",
|
|
32
|
+
"cardiovascular",
|
|
33
|
+
"machine-learning",
|
|
34
|
+
"ml",
|
|
35
|
+
"risk-prediction",
|
|
36
|
+
"healthcare",
|
|
37
|
+
"api-client",
|
|
38
|
+
"sdk"
|
|
39
|
+
],
|
|
40
|
+
"author": "Analytica Data Science Solutions <api@analyticadss.com>",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"homepage": "https://glucoguard.analyticadss.com",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/aousabdo/glucoguard.git",
|
|
46
|
+
"directory": "sdk/typescript"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"email": "api@analyticadss.com"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.0.0",
|
|
56
|
+
"tsup": "^8.3.0",
|
|
57
|
+
"typescript": "^5.6.0",
|
|
58
|
+
"vitest": "^4.1.4"
|
|
59
|
+
}
|
|
60
|
+
}
|