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 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
+ });
@@ -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 };
@@ -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
+ }