@gera-services/mcp-gera-clinic 0.1.1 → 1.0.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 CHANGED
@@ -1,80 +1,113 @@
1
- # @gera-services/mcp-gera-clinic
1
+ # GeraClinic MCP Server
2
2
 
3
- MCP (Model Context Protocol) server for [GeraClinic](https://geraclinic.com) -- the AI-powered telemedicine platform by [Gera Services](https://gera.services).
3
+ [![A Gera Systems product](https://img.shields.io/badge/Gera-Systems-0a7?style=flat)](https://geraclinic.com)
4
4
 
5
- Enables AI assistants (Claude, ChatGPT, etc.) to search doctors, check availability, and book medical appointments across 50+ countries.
5
+ An [MCP](https://modelcontextprotocol.io) server that lets AI agents Claude
6
+ Desktop, ChatGPT with tools, Cursor, Windsurf, or any MCP client — **find
7
+ CQC-registered UK care and health providers**, read **area care statistics**,
8
+ and run **non-diagnostic health calculators**, fully offline.
9
+
10
+ It bundles a snapshot of GeraClinic's real **Care Quality Commission (CQC)**
11
+ provider directory — GP surgeries, dentists, hospitals, clinics, care/nursing
12
+ homes, hospices and more — plus GeraClinic's published health-calculator math.
13
+ **No backend, no network, no auth** — everything is computed locally, so it is
14
+ safe to run anywhere and gives the same answers the
15
+ [GeraClinic](https://geraclinic.com) product gives.
16
+
17
+ > Source: Care Quality Commission `www.cqc.org.uk`, licensed under the
18
+ > [Open Government Licence v3.0](https://www.cqc.org.uk/about-us/transparency/using-cqc-data).
19
+ > CQC ratings are categorical (Outstanding / Good / Requires improvement /
20
+ > Inadequate), never numeric. Health calculators are educational reference math,
21
+ > not medical advice.
6
22
 
7
23
  ## Tools
8
24
 
9
- | Tool | Description | Auth Required |
10
- |------|-------------|:---:|
11
- | `search_doctors` | Search doctors by specialty, location, language, appointment type, and rating | No |
12
- | `get_doctor_profile` | Get detailed doctor profile (bio, qualifications, fees, languages) | No |
13
- | `get_available_slots` | Get open appointment time slots for a doctor on a given date | No |
14
- | `list_specialties` | List all medical specialties available in a country | No |
15
- | `get_featured_doctors` | Get top-rated, verified doctors | No |
16
- | `get_health_services` | Get all health service types available in a country (telemedicine, pharmacy, labs, home nursing) | No |
17
- | `book_appointment` | Book a video, in-person, or chat consultation | Yes |
25
+ | Tool | What it does |
26
+ |------|--------------|
27
+ | `find_care_provider` | Search real CQC-registered providers by name, postcode (full or outward), service type, and/or local authority. Returns address, phone, website, service types, last-inspected date, and the CQC profile URL. |
28
+ | `get_cqc_area_stats` | Aggregated CQC statistics for a UK authority or locality: total providers, phone/website coverage, service-type breakdown (GPs, dentists, care homes, hospitals…), top service type, latest inspection date. |
29
+ | `list_care_authorities` | List the UK areas the directory covers (optionally filtered by region) with provider counts and the available service types — use to discover valid `area` / `authority` values. |
30
+ | `list_health_calculators` | List the available health calculators with their inputs and the public reference standard each uses. |
31
+ | `run_health_calculator` | Run a calculator: BMI, BMR/TDEE calories, ideal weight, blood-pressure category, heart-rate zones, A1C↔glucose, water intake, or waist-to-height ratio. |
32
+
33
+ A typical agent flow: `list_care_authorities` `find_care_provider`
34
+ (by area + service type) or `get_cqc_area_stats`; and separately
35
+ `list_health_calculators` → `run_health_calculator`.
18
36
 
19
- ## Installation
37
+ ## Install & run
20
38
 
21
39
  ```bash
22
- npm install @gera-services/mcp-gera-clinic
23
- ```
40
+ # Run directly (no global install) once published to npm:
41
+ npx -y @gera-services/mcp-gera-clinic
24
42
 
25
- ## Usage
43
+ # Or from this repo:
44
+ cd packages/mcp-gera-clinic
45
+ npm run build # tsc --noCheck -> dist/ (+ copies the CQC data file)
46
+ node bin/cli.js # starts on stdio
47
+ ```
26
48
 
27
- ### With Claude Desktop
49
+ ## Client configuration
28
50
 
29
- Add to your `claude_desktop_config.json`:
51
+ ### Claude Desktop / Claude Code (`claude_desktop_config.json`)
30
52
 
31
53
  ```json
32
54
  {
33
55
  "mcpServers": {
34
56
  "gera-clinic": {
35
57
  "command": "npx",
36
- "args": ["@gera-services/mcp-gera-clinic"]
58
+ "args": ["-y", "@gera-services/mcp-gera-clinic"]
37
59
  }
38
60
  }
39
61
  }
40
62
  ```
41
63
 
42
- ### With a custom API endpoint
64
+ Local (unpublished) variant point at the built CLI:
43
65
 
44
66
  ```json
45
67
  {
46
68
  "mcpServers": {
47
69
  "gera-clinic": {
48
- "command": "npx",
49
- "args": ["@gera-services/mcp-gera-clinic"],
50
- "env": {
51
- "GERACLINIC_API_URL": "https://your-api-url.example.com"
52
- }
70
+ "command": "node",
71
+ "args": ["/Users/armen/Gera/packages/mcp-gera-clinic/bin/cli.js"]
53
72
  }
54
73
  }
55
74
  }
56
75
  ```
57
76
 
58
- ### Standalone
77
+ ### Cursor / Windsurf (`.cursor/mcp.json` etc.)
59
78
 
60
- ```bash
61
- npx @gera-services/mcp-gera-clinic
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "gera-clinic": {
83
+ "command": "npx",
84
+ "args": ["-y", "@gera-services/mcp-gera-clinic"]
85
+ }
86
+ }
87
+ }
62
88
  ```
63
89
 
64
- ## Configuration
90
+ ## Verify it works
65
91
 
66
- | Environment Variable | Description | Default |
67
- |---------------------|-------------|---------|
68
- | `GERACLINIC_API_URL` | Base URL for the GeraClinic API | `https://api.geraclinic.com` |
92
+ ```bash
93
+ npm run build
94
+ node scripts/smoke.mjs
95
+ ```
69
96
 
70
- ## Multi-Country Support
97
+ The smoke test speaks raw MCP JSON-RPC over stdio (`initialize` → `tools/list`
98
+ → several `tools/call`) and asserts the results. Expected output ends with
99
+ `ALL SMOKE CHECKS PASSED`.
71
100
 
72
- GeraClinic operates across 50+ countries. Pass a country code (ISO 3166-1 alpha-2) to any tool to scope results to that country. Supported countries include Georgia (GE), Uganda (UG), Ghana (GH), Kenya (KE), Nigeria (NG), Pakistan (PK), Bangladesh (BD), Colombia (CO), Peru (PE), Ecuador (EC), and many more.
101
+ ## Example
73
102
 
74
- ## Authentication
103
+ Ask your agent: *"Find me a dentist in Kent, and what's the BMI for someone
104
+ 82 kg and 178 cm?"*
75
105
 
76
- Read-only tools (search, availability, specialties) work without authentication. Booking requires a user bearer token passed via the `auth_token` parameter.
106
+ The agent calls `find_care_provider` with
107
+ `{ authority: "kent", serviceType: "Dentist" }` and `run_health_calculator`
108
+ with `{ calculator: "bmi", weightKg: 82, heightCm: 178 }`, returning real CQC
109
+ provider records and a BMI of `25.9` (`Overweight`, WHO category).
77
110
 
78
111
  ## License
79
112
 
80
- MIT
113
+ MIT © Gera Systems Ltd
package/bin/cli.js CHANGED
@@ -1,2 +1,12 @@
1
1
  #!/usr/bin/env node
2
- require('../dist/server.js');
2
+ /**
3
+ * CLI entry point for the GeraClinic MCP server.
4
+ * Starts the server on stdio. Intended to be launched by an MCP client
5
+ * (Claude Desktop, ChatGPT-with-tools, Cursor, etc.) — not run interactively.
6
+ */
7
+ import { main } from '../dist/server.js';
8
+
9
+ main().catch((err) => {
10
+ console.error('Fatal:', err);
11
+ process.exit(1);
12
+ });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * GeraClinic health calculators — pure, non-diagnostic reference math.
3
+ *
4
+ * These mirror GeraClinic's published health-tools library
5
+ * (apps/gera-clinic-web/src/lib/health-tools/calc.ts) exactly: same formulas,
6
+ * same WHO/NIH/ACC-AHA/ADAG reference ranges. Every function returns an
7
+ * OBJECTIVE number plus, where applicable, a public reference CATEGORY label.
8
+ * Nothing here diagnoses a condition or prescribes a dose — orientation only.
9
+ */
10
+ export declare const LB_PER_KG = 2.2046226218;
11
+ export declare const CM_PER_INCH = 2.54;
12
+ export declare function cmToIn(cm: number): number;
13
+ export type BmiCategory = 'Underweight' | 'Healthy weight' | 'Overweight' | 'Obesity';
14
+ export declare function bmiCategory(value: number): BmiCategory;
15
+ export declare function bmi(weightKg: number, heightCm: number): {
16
+ bmi: number;
17
+ category: BmiCategory;
18
+ healthyWeightRangeKg: {
19
+ minKg: number;
20
+ maxKg: number;
21
+ };
22
+ reference: string;
23
+ };
24
+ export declare function healthyWeightRangeKg(heightCm: number): {
25
+ minKg: number;
26
+ maxKg: number;
27
+ };
28
+ export type Sex = 'male' | 'female';
29
+ export type ActivityLevel = 'sedentary' | 'light' | 'moderate' | 'active' | 'very_active';
30
+ export declare const ACTIVITY_FACTORS: Record<ActivityLevel, number>;
31
+ export declare function bmrValue(weightKg: number, heightCm: number, ageYears: number, sex: Sex): number;
32
+ export declare function bmrTdee(weightKg: number, heightCm: number, ageYears: number, sex: Sex, activity: ActivityLevel): {
33
+ bmrKcalPerDay: number;
34
+ tdeeKcalPerDay: number;
35
+ activityFactor: number;
36
+ reference: string;
37
+ };
38
+ export declare function idealWeightKg(heightCm: number, sex: Sex): {
39
+ reference: string;
40
+ devineKg: number;
41
+ robinsonKg: number;
42
+ millerKg: number;
43
+ hamwiKg: number;
44
+ };
45
+ export type BpCategory = 'Normal' | 'Elevated' | 'Hypertension stage 1' | 'Hypertension stage 2' | 'Hypertensive crisis';
46
+ export declare function bloodPressureCategory(systolic: number, diastolic: number): {
47
+ systolic: number;
48
+ diastolic: number;
49
+ category: BpCategory;
50
+ reference: string;
51
+ };
52
+ export declare function heartRateZones(ageYears: number): {
53
+ estimatedMaxHr: number;
54
+ zones: {
55
+ description: string;
56
+ lowBpm: number;
57
+ highBpm: number;
58
+ name: string;
59
+ }[];
60
+ reference: string;
61
+ };
62
+ export declare const MGDL_PER_MMOL = 18.0182;
63
+ export declare function a1c(a1cPercent?: number, avgGlucoseMgDl?: number): {
64
+ a1cPercent: number;
65
+ estAvgGlucoseMgDl: number;
66
+ estAvgGlucoseMmolL: number;
67
+ reference: string;
68
+ avgGlucoseMgDl?: undefined;
69
+ estA1cPercent?: undefined;
70
+ avgGlucoseMmolL?: undefined;
71
+ error?: undefined;
72
+ } | {
73
+ avgGlucoseMgDl: number;
74
+ estA1cPercent: number;
75
+ avgGlucoseMmolL: number;
76
+ reference: string;
77
+ a1cPercent?: undefined;
78
+ estAvgGlucoseMgDl?: undefined;
79
+ estAvgGlucoseMmolL?: undefined;
80
+ error?: undefined;
81
+ } | {
82
+ error: string;
83
+ a1cPercent?: undefined;
84
+ estAvgGlucoseMgDl?: undefined;
85
+ estAvgGlucoseMmolL?: undefined;
86
+ reference?: undefined;
87
+ avgGlucoseMgDl?: undefined;
88
+ estA1cPercent?: undefined;
89
+ avgGlucoseMmolL?: undefined;
90
+ };
91
+ export declare function waterIntakeLitres(weightKg: number, activityMinutes?: number): {
92
+ litresPerDay: number;
93
+ reference: string;
94
+ };
95
+ export type WaistHeightCategory = 'Low' | 'Healthy' | 'Increased' | 'High';
96
+ export declare function waistToHeight(waistCm: number, heightCm: number): {
97
+ ratio: number;
98
+ category: WaistHeightCategory;
99
+ reference: string;
100
+ };
101
+ export interface CalculatorInfo {
102
+ id: string;
103
+ title: string;
104
+ description: string;
105
+ inputs: string[];
106
+ reference: string;
107
+ }
108
+ export declare const CALCULATORS: CalculatorInfo[];
@@ -0,0 +1,227 @@
1
+ /**
2
+ * GeraClinic health calculators — pure, non-diagnostic reference math.
3
+ *
4
+ * These mirror GeraClinic's published health-tools library
5
+ * (apps/gera-clinic-web/src/lib/health-tools/calc.ts) exactly: same formulas,
6
+ * same WHO/NIH/ACC-AHA/ADAG reference ranges. Every function returns an
7
+ * OBJECTIVE number plus, where applicable, a public reference CATEGORY label.
8
+ * Nothing here diagnoses a condition or prescribes a dose — orientation only.
9
+ */
10
+ export const LB_PER_KG = 2.2046226218;
11
+ export const CM_PER_INCH = 2.54;
12
+ export function cmToIn(cm) {
13
+ return cm / CM_PER_INCH;
14
+ }
15
+ export function bmiCategory(value) {
16
+ if (value < 18.5)
17
+ return 'Underweight';
18
+ if (value < 25)
19
+ return 'Healthy weight';
20
+ if (value < 30)
21
+ return 'Overweight';
22
+ return 'Obesity';
23
+ }
24
+ export function bmi(weightKg, heightCm) {
25
+ const m = heightCm / 100;
26
+ const value = Math.round((weightKg / (m * m)) * 10) / 10;
27
+ const range = healthyWeightRangeKg(heightCm);
28
+ return {
29
+ bmi: value,
30
+ category: bmiCategory(value),
31
+ healthyWeightRangeKg: range,
32
+ reference: 'WHO adult BMI categories (educational).',
33
+ };
34
+ }
35
+ export function healthyWeightRangeKg(heightCm) {
36
+ const m = heightCm / 100;
37
+ return {
38
+ minKg: Math.round(18.5 * m * m * 10) / 10,
39
+ maxKg: Math.round(24.9 * m * m * 10) / 10,
40
+ };
41
+ }
42
+ export const ACTIVITY_FACTORS = {
43
+ sedentary: 1.2,
44
+ light: 1.375,
45
+ moderate: 1.55,
46
+ active: 1.725,
47
+ very_active: 1.9,
48
+ };
49
+ export function bmrValue(weightKg, heightCm, ageYears, sex) {
50
+ const base = 10 * weightKg + 6.25 * heightCm - 5 * ageYears;
51
+ return Math.round(sex === 'male' ? base + 5 : base - 161);
52
+ }
53
+ export function bmrTdee(weightKg, heightCm, ageYears, sex, activity) {
54
+ const basal = bmrValue(weightKg, heightCm, ageYears, sex);
55
+ return {
56
+ bmrKcalPerDay: basal,
57
+ tdeeKcalPerDay: Math.round(basal * ACTIVITY_FACTORS[activity]),
58
+ activityFactor: ACTIVITY_FACTORS[activity],
59
+ reference: 'Mifflin-St Jeor equation (educational estimate).',
60
+ };
61
+ }
62
+ // ── Ideal weight (Devine / Robinson / Miller / Hamwi) ────────────────────────
63
+ export function idealWeightKg(heightCm, sex) {
64
+ const inchesOver5ft = Math.max(0, cmToIn(heightCm) - 60);
65
+ const r = (n) => Math.round(n * 10) / 10;
66
+ const result = sex === 'male'
67
+ ? {
68
+ devineKg: r(50 + 2.3 * inchesOver5ft),
69
+ robinsonKg: r(52 + 1.9 * inchesOver5ft),
70
+ millerKg: r(56.2 + 1.41 * inchesOver5ft),
71
+ hamwiKg: r(48 + 2.7 * inchesOver5ft),
72
+ }
73
+ : {
74
+ devineKg: r(45.5 + 2.3 * inchesOver5ft),
75
+ robinsonKg: r(49 + 1.7 * inchesOver5ft),
76
+ millerKg: r(53.1 + 1.36 * inchesOver5ft),
77
+ hamwiKg: r(45.5 + 2.2 * inchesOver5ft),
78
+ };
79
+ return {
80
+ ...result,
81
+ reference: 'Classic clinical reference formulas (Devine, Robinson, Miller, Hamwi). Population estimate, not a personal target.',
82
+ };
83
+ }
84
+ export function bloodPressureCategory(systolic, diastolic) {
85
+ let category;
86
+ if (systolic > 180 || diastolic > 120)
87
+ category = 'Hypertensive crisis';
88
+ else if (systolic >= 140 || diastolic >= 90)
89
+ category = 'Hypertension stage 2';
90
+ else if (systolic >= 130 || diastolic >= 80)
91
+ category = 'Hypertension stage 1';
92
+ else if (systolic >= 120 && diastolic < 80)
93
+ category = 'Elevated';
94
+ else
95
+ category = 'Normal';
96
+ return {
97
+ systolic,
98
+ diastolic,
99
+ category,
100
+ reference: 'ACC/AHA 2017 reference categories (educational). Does not diagnose hypertension — that needs repeated clinical measurement.',
101
+ };
102
+ }
103
+ // ── Heart-rate training zones (220 - age) ────────────────────────────────────
104
+ export function heartRateZones(ageYears) {
105
+ const maxHr = 220 - ageYears;
106
+ const z = (lo, hi) => ({
107
+ lowBpm: Math.round(maxHr * lo),
108
+ highBpm: Math.round(maxHr * hi),
109
+ });
110
+ return {
111
+ estimatedMaxHr: maxHr,
112
+ zones: [
113
+ { name: 'Zone 1 — Very light', ...z(0.5, 0.6), description: 'Warm-up and recovery' },
114
+ { name: 'Zone 2 — Light', ...z(0.6, 0.7), description: 'Fat burning, base endurance' },
115
+ { name: 'Zone 3 — Moderate', ...z(0.7, 0.8), description: 'Aerobic fitness' },
116
+ { name: 'Zone 4 — Hard', ...z(0.8, 0.9), description: 'Increased performance' },
117
+ { name: 'Zone 5 — Maximum', ...z(0.9, 1.0), description: 'Maximal effort, short bursts' },
118
+ ],
119
+ reference: 'Estimated max HR = 220 − age, with the 5 standard training zones (educational).',
120
+ };
121
+ }
122
+ // ── A1C <-> average glucose (ADAG) ───────────────────────────────────────────
123
+ export const MGDL_PER_MMOL = 18.0182;
124
+ export function a1c(a1cPercent, avgGlucoseMgDl) {
125
+ if (a1cPercent != null) {
126
+ const mgDl = Math.round(28.7 * a1cPercent - 46.7);
127
+ return {
128
+ a1cPercent,
129
+ estAvgGlucoseMgDl: mgDl,
130
+ estAvgGlucoseMmolL: Math.round((mgDl / MGDL_PER_MMOL) * 10) / 10,
131
+ reference: 'ADAG formula: eAG(mg/dL) = 28.7 × A1C − 46.7 (educational).',
132
+ };
133
+ }
134
+ if (avgGlucoseMgDl != null) {
135
+ return {
136
+ avgGlucoseMgDl,
137
+ estA1cPercent: Math.round(((avgGlucoseMgDl + 46.7) / 28.7) * 10) / 10,
138
+ avgGlucoseMmolL: Math.round((avgGlucoseMgDl / MGDL_PER_MMOL) * 10) / 10,
139
+ reference: 'ADAG formula inverted (educational).',
140
+ };
141
+ }
142
+ return { error: 'Provide either a1cPercent or avgGlucoseMgDl.' };
143
+ }
144
+ // ── Water intake ─────────────────────────────────────────────────────────────
145
+ export function waterIntakeLitres(weightKg, activityMinutes = 0) {
146
+ const base = weightKg * 0.035;
147
+ const activityBump = (activityMinutes / 30) * 0.35;
148
+ return {
149
+ litresPerDay: Math.round((base + activityBump) * 100) / 100,
150
+ reference: '~35 ml per kg body weight plus a small activity bump (educational rule of thumb).',
151
+ };
152
+ }
153
+ export function waistToHeight(waistCm, heightCm) {
154
+ const ratio = Math.round((waistCm / heightCm) * 100) / 100;
155
+ let category;
156
+ if (ratio < 0.4)
157
+ category = 'Low';
158
+ else if (ratio < 0.5)
159
+ category = 'Healthy';
160
+ else if (ratio < 0.6)
161
+ category = 'Increased';
162
+ else
163
+ category = 'High';
164
+ return {
165
+ ratio,
166
+ category,
167
+ reference: 'Waist-to-height ratio reference bands (educational).',
168
+ };
169
+ }
170
+ export const CALCULATORS = [
171
+ {
172
+ id: 'bmi',
173
+ title: 'Body Mass Index',
174
+ description: 'BMI value, WHO category, and the healthy-weight range for the height.',
175
+ inputs: ['weightKg', 'heightCm'],
176
+ reference: 'WHO adult BMI ranges',
177
+ },
178
+ {
179
+ id: 'bmr_tdee',
180
+ title: 'BMR & TDEE (calories)',
181
+ description: 'Basal metabolic rate and total daily energy expenditure.',
182
+ inputs: ['weightKg', 'heightCm', 'ageYears', 'sex', 'activity'],
183
+ reference: 'Mifflin-St Jeor',
184
+ },
185
+ {
186
+ id: 'ideal_weight',
187
+ title: 'Ideal body weight',
188
+ description: 'Devine / Robinson / Miller / Hamwi reference estimates (kg).',
189
+ inputs: ['heightCm', 'sex'],
190
+ reference: 'Devine, Robinson, Miller, Hamwi formulas',
191
+ },
192
+ {
193
+ id: 'blood_pressure',
194
+ title: 'Blood-pressure category',
195
+ description: 'Maps a systolic/diastolic reading to its ACC/AHA reference category.',
196
+ inputs: ['systolic', 'diastolic'],
197
+ reference: 'ACC/AHA 2017 categories',
198
+ },
199
+ {
200
+ id: 'heart_rate_zones',
201
+ title: 'Heart-rate training zones',
202
+ description: 'Estimated max HR (220 − age) and the 5 standard training zones.',
203
+ inputs: ['ageYears'],
204
+ reference: '220 − age',
205
+ },
206
+ {
207
+ id: 'a1c',
208
+ title: 'A1C ↔ average glucose',
209
+ description: 'Convert A1C% to estimated average glucose, or the reverse.',
210
+ inputs: ['a1cPercent OR avgGlucoseMgDl'],
211
+ reference: 'ADAG study formula',
212
+ },
213
+ {
214
+ id: 'water_intake',
215
+ title: 'Daily water intake',
216
+ description: 'General daily water guidance from body weight and activity minutes.',
217
+ inputs: ['weightKg', 'activityMinutes?'],
218
+ reference: '~35 ml/kg rule of thumb',
219
+ },
220
+ {
221
+ id: 'waist_to_height',
222
+ title: 'Waist-to-height ratio',
223
+ description: 'Ratio plus its reference band.',
224
+ inputs: ['waistCm', 'heightCm'],
225
+ reference: 'Waist-to-height ratio bands',
226
+ },
227
+ ];
package/dist/cqc.d.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Real Care Quality Commission (CQC) provider dataset, bundled as JSON.
3
+ *
4
+ * Source: Care Quality Commission www.cqc.org.uk, crawled into GeraClinic's
5
+ * `crawled_listings` (vertical='geraclinic', country_code='GB') and snapshotted
6
+ * from apps/gera-clinic-web/src/data/cqc-cluster.generated.ts. Every record
7
+ * traces to a real registered provider. Public sector information licensed
8
+ * under the Open Government Licence v3.0.
9
+ *
10
+ * CQC publishes a CATEGORICAL rating scheme
11
+ * (Outstanding / Good / Requires improvement / Inadequate), never a 0–5 numeric
12
+ * scale, so this dataset carries NO numeric ratings. The `lastInspected` date
13
+ * is the real last-checked date for the provider record.
14
+ */
15
+ export interface CqcServiceTypeBucket {
16
+ type: string;
17
+ count: number;
18
+ pct: number;
19
+ }
20
+ export interface CqcStats {
21
+ total: number;
22
+ withPhone: number;
23
+ withPhonePct: number;
24
+ withWebsite: number;
25
+ serviceTypes: CqcServiceTypeBucket[];
26
+ topServiceType: string | null;
27
+ latestCheck: string | null;
28
+ }
29
+ export interface CqcProvider {
30
+ cqcLocationId: string;
31
+ name: string;
32
+ providerName: string | null;
33
+ address: string | null;
34
+ postcode: string | null;
35
+ phone: string | null;
36
+ website: string | null;
37
+ serviceTypes: string[];
38
+ lastInspected: string | null;
39
+ }
40
+ export interface CqcLocality {
41
+ slug: string;
42
+ outwardCode: string;
43
+ stats: CqcStats;
44
+ providers: CqcProvider[];
45
+ }
46
+ export interface CqcAuthority {
47
+ slug: string;
48
+ name: string;
49
+ region: string | null;
50
+ stats: CqcStats;
51
+ providers: CqcProvider[];
52
+ localities: CqcLocality[];
53
+ }
54
+ export interface CqcDataset {
55
+ meta: {
56
+ generatedAt: string;
57
+ totalProviders: number;
58
+ totalLocalities: number;
59
+ attribution: string;
60
+ url: string;
61
+ attributionUrl: string;
62
+ };
63
+ authorities: CqcAuthority[];
64
+ }
65
+ export declare const CQC_META: {
66
+ generatedAt: string;
67
+ totalProviders: number;
68
+ totalLocalities: number;
69
+ attribution: string;
70
+ url: string;
71
+ attributionUrl: string;
72
+ };
73
+ export declare const CQC_AUTHORITIES: CqcAuthority[];
74
+ export declare const CQC_ATTRIBUTION: string;
75
+ export declare const ALL_PROVIDERS: CqcProvider[];
76
+ /** All distinct service-type labels present in the dataset, sorted. */
77
+ export declare const SERVICE_TYPES: string[];
78
+ export interface ProviderSearchParams {
79
+ query?: string;
80
+ postcode?: string;
81
+ serviceType?: string;
82
+ authority?: string;
83
+ limit: number;
84
+ }
85
+ export interface ProviderSearchResult {
86
+ provider: CqcProvider;
87
+ authoritySlug: string;
88
+ authorityName: string;
89
+ region: string | null;
90
+ }
91
+ /**
92
+ * Search the real CQC dataset. All filters are AND-combined; any omitted.
93
+ * Matching is case-insensitive substring for name/serviceType, and outward- or
94
+ * full-postcode prefix for postcode.
95
+ */
96
+ export declare function searchProviders(params: ProviderSearchParams): ProviderSearchResult[];
97
+ /** Look up one authority or locality area by slug or name. */
98
+ export declare function findArea(slugOrName: string): {
99
+ kind: 'authority' | 'locality';
100
+ area: CqcAuthority | CqcLocality;
101
+ authorityName: string;
102
+ } | null;