mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 10:23:27 +00:00
feat: wire frontend to platform data, migrate to Jotai + Vercel AI SDK
- Replace mock DataProvider with real GraphQL queries through sidecar - Add queries.ts and transforms.ts for platform field name mapping - Migrate SIP state from React Context to Jotai atoms (React 19 compat) - Add singleton SIP manager to survive StrictMode remounts - Remove hardcoded Olivia/Sienna accounts from nav menu - Add password eye toggle, remember me checkbox, forgot password link - Fix worklist hook to transform platform field names - Add seed scripts for clinics, health packages, lab tests - Update test harness for new doctor→clinic relation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
250
scripts/seed-lab-tests.ts
Normal file
250
scripts/seed-lab-tests.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* Seed lab tests and link them to health packages via packageTest junction.
|
||||
* Run: cd helix-engage && npx tsx scripts/seed-lab-tests.ts
|
||||
*
|
||||
* Prerequisites: health packages already seeded via seed-new-entities.ts
|
||||
*
|
||||
* Platform field mapping:
|
||||
* LabTest: testCategory→category, testDepartment→department,
|
||||
* durationMinutes→durationMin, isActive→active, preparation→preparationInstructions
|
||||
* PackageTest: position→order, isMandatory→mandatory
|
||||
*/
|
||||
|
||||
const GQL = 'http://localhost:4000/graphql';
|
||||
const SUB = 'fortytwo-dev';
|
||||
const ORIGIN = 'http://fortytwo-dev.localhost:4010';
|
||||
|
||||
let token = '';
|
||||
|
||||
async function gql(query: string, variables?: any) {
|
||||
const h: Record<string, string> = { 'Content-Type': 'application/json', 'X-Workspace-Subdomain': SUB };
|
||||
if (token) h['Authorization'] = `Bearer ${token}`;
|
||||
const r = await fetch(GQL, { method: 'POST', headers: h, body: JSON.stringify({ query, variables }) });
|
||||
const d: any = await r.json();
|
||||
if (d.errors) { console.error('❌', d.errors[0].message); throw new Error(d.errors[0].message); }
|
||||
return d.data;
|
||||
}
|
||||
|
||||
async function auth() {
|
||||
const d1 = await gql(`mutation { getLoginTokenFromCredentials(email: "dev@fortytwo.dev", password: "tim@apple.dev", origin: "${ORIGIN}") { loginToken { token } } }`);
|
||||
const lt = d1.getLoginTokenFromCredentials.loginToken.token;
|
||||
const d2 = await gql(`mutation { getAuthTokensFromLoginToken(loginToken: "${lt}", origin: "${ORIGIN}") { tokens { accessOrWorkspaceAgnosticToken { token } } } }`);
|
||||
token = d2.getAuthTokensFromLoginToken.tokens.accessOrWorkspaceAgnosticToken.token;
|
||||
}
|
||||
|
||||
async function mk(entity: string, data: any): Promise<string> {
|
||||
const cap = entity[0].toUpperCase() + entity.slice(1);
|
||||
const d = await gql(`mutation($data: ${cap}CreateInput!) { create${cap}(data: $data) { id } }`, { data });
|
||||
return d[`create${cap}`].id;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🧪 Seeding lab tests and package linkages...\n');
|
||||
await auth();
|
||||
console.log('✅ Auth OK\n');
|
||||
|
||||
// Fetch existing health package IDs
|
||||
const pkgData = await gql(`{ healthPackages(first: 10) { edges { node { id packageName } } } }`);
|
||||
const pkgs: Record<string, string> = {};
|
||||
for (const e of pkgData.healthPackages.edges) {
|
||||
pkgs[e.node.packageName] = e.node.id;
|
||||
}
|
||||
console.log(`📦 Found ${Object.keys(pkgs).length} packages: ${Object.keys(pkgs).join(', ')}\n`);
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// LAB TESTS
|
||||
// ═══════════════════════════════════════════
|
||||
console.log('🧪 Lab Tests');
|
||||
|
||||
// Blood tests
|
||||
const cbc = await mk('labTest', { name: 'CBC', testName: 'Complete Blood Count', testCode: 'CBC-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 350_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (EDTA tube)', preparationInstructions: 'No fasting required', active: true });
|
||||
console.log(' CBC');
|
||||
|
||||
const lipid = await mk('labTest', { name: 'Lipid Profile', testName: 'Lipid Profile', testCode: 'LIP-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 500_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: '12-hour fasting required', active: true });
|
||||
console.log(' Lipid Profile');
|
||||
|
||||
const lft = await mk('labTest', { name: 'Liver Function Test', testName: 'Liver Function Test', testCode: 'LFT-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 450_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: '12-hour fasting recommended', active: true });
|
||||
console.log(' LFT');
|
||||
|
||||
const kft = await mk('labTest', { name: 'Kidney Function Test', testName: 'Kidney Function Test', testCode: 'KFT-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 450_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'No fasting required', active: true });
|
||||
console.log(' KFT');
|
||||
|
||||
const thyroid = await mk('labTest', { name: 'Thyroid Panel', testName: 'Thyroid Profile (T3, T4, TSH)', testCode: 'THY-001', category: 'HORMONE', department: 'PATHOLOGY', price: { amountMicros: 600_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'No fasting required. Collect sample before thyroid medication.', active: true });
|
||||
console.log(' Thyroid Panel');
|
||||
|
||||
const bloodSugar = await mk('labTest', { name: 'Blood Sugar (Fasting + PP)', testName: 'Blood Sugar Fasting & Post-Prandial', testCode: 'GLU-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 200_000_000, currencyCode: 'INR' }, durationMin: 10, sampleType: 'Blood (fluoride tube)', preparationInstructions: '10-12 hour fasting for FBS; 2h after meal for PP', active: true });
|
||||
console.log(' Blood Sugar');
|
||||
|
||||
const hba1c = await mk('labTest', { name: 'HbA1c', testName: 'Glycated Hemoglobin', testCode: 'HBA-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 400_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (EDTA tube)', preparationInstructions: 'No fasting required', active: true });
|
||||
console.log(' HbA1c');
|
||||
|
||||
const vitD = await mk('labTest', { name: 'Vitamin D', testName: 'Vitamin D (25-Hydroxy)', testCode: 'VIT-D01', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 800_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'No fasting required', active: true });
|
||||
console.log(' Vitamin D');
|
||||
|
||||
const calcium = await mk('labTest', { name: 'Calcium', testName: 'Serum Calcium', testCode: 'CAL-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 200_000_000, currencyCode: 'INR' }, durationMin: 10, sampleType: 'Blood (serum)', preparationInstructions: 'No fasting required', active: true });
|
||||
console.log(' Calcium');
|
||||
|
||||
const uricAcid = await mk('labTest', { name: 'Uric Acid', testName: 'Serum Uric Acid', testCode: 'URA-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 200_000_000, currencyCode: 'INR' }, durationMin: 10, sampleType: 'Blood (serum)', preparationInstructions: 'No fasting required', active: true });
|
||||
console.log(' Uric Acid');
|
||||
|
||||
const raFactor = await mk('labTest', { name: 'RA Factor', testName: 'Rheumatoid Factor', testCode: 'RAF-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 500_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'No fasting required', active: true });
|
||||
console.log(' RA Factor');
|
||||
|
||||
const hsCrp = await mk('labTest', { name: 'hs-CRP', testName: 'High-Sensitivity C-Reactive Protein', testCode: 'CRP-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 600_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'No fasting required', active: true });
|
||||
console.log(' hs-CRP');
|
||||
|
||||
const homocysteine = await mk('labTest', { name: 'Homocysteine', testName: 'Serum Homocysteine', testCode: 'HCY-001', category: 'BLOOD_TEST', department: 'PATHOLOGY', price: { amountMicros: 700_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: '12-hour fasting recommended', active: true });
|
||||
console.log(' Homocysteine');
|
||||
|
||||
await auth();
|
||||
|
||||
// Hormone panel
|
||||
const amh = await mk('labTest', { name: 'AMH', testName: 'Anti-Müllerian Hormone', testCode: 'AMH-001', category: 'HORMONE', department: 'GYNECOLOGY', price: { amountMicros: 1_500_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'No fasting. Can be done on any day of cycle.', active: true });
|
||||
console.log(' AMH');
|
||||
|
||||
const fsh = await mk('labTest', { name: 'FSH', testName: 'Follicle-Stimulating Hormone', testCode: 'FSH-001', category: 'HORMONE', department: 'GYNECOLOGY', price: { amountMicros: 500_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'Day 2-3 of menstrual cycle preferred', active: true });
|
||||
console.log(' FSH');
|
||||
|
||||
const lh = await mk('labTest', { name: 'LH', testName: 'Luteinizing Hormone', testCode: 'LH-001', category: 'HORMONE', department: 'GYNECOLOGY', price: { amountMicros: 500_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'Day 2-3 of menstrual cycle preferred', active: true });
|
||||
console.log(' LH');
|
||||
|
||||
const estradiol = await mk('labTest', { name: 'Estradiol', testName: 'Estradiol (E2)', testCode: 'EST-001', category: 'HORMONE', department: 'GYNECOLOGY', price: { amountMicros: 600_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'Day 2-3 of menstrual cycle preferred', active: true });
|
||||
console.log(' Estradiol');
|
||||
|
||||
const prolactin = await mk('labTest', { name: 'Prolactin', testName: 'Serum Prolactin', testCode: 'PRL-001', category: 'HORMONE', department: 'GYNECOLOGY', price: { amountMicros: 500_000_000, currencyCode: 'INR' }, durationMin: 15, sampleType: 'Blood (serum)', preparationInstructions: 'Fasting. Morning sample preferred.', active: true });
|
||||
console.log(' Prolactin');
|
||||
|
||||
// Imaging
|
||||
const ecg = await mk('labTest', { name: 'ECG', testName: 'Electrocardiogram (12-lead)', testCode: 'ECG-001', category: 'CARDIAC', department: 'CARDIOLOGY', price: { amountMicros: 300_000_000, currencyCode: 'INR' }, durationMin: 15, preparationInstructions: 'No preparation needed', active: true });
|
||||
console.log(' ECG');
|
||||
|
||||
const echo = await mk('labTest', { name: '2D Echocardiogram', testName: '2D Echocardiogram with Color Doppler', testCode: 'ECHO-001', category: 'CARDIAC', department: 'CARDIOLOGY', price: { amountMicros: 2_000_000_000, currencyCode: 'INR' }, durationMin: 30, preparationInstructions: 'No preparation needed', active: true });
|
||||
console.log(' 2D Echo');
|
||||
|
||||
const tmt = await mk('labTest', { name: 'TMT', testName: 'Treadmill Test (Stress Test)', testCode: 'TMT-001', category: 'CARDIAC', department: 'CARDIOLOGY', price: { amountMicros: 1_500_000_000, currencyCode: 'INR' }, durationMin: 45, preparationInstructions: 'Wear comfortable shoes. Light meal 2h before. Avoid caffeine.', active: true });
|
||||
console.log(' TMT');
|
||||
|
||||
const chestXray = await mk('labTest', { name: 'Chest X-ray', testName: 'Chest X-ray PA View', testCode: 'XR-001', category: 'IMAGING', department: 'RADIOLOGY', price: { amountMicros: 400_000_000, currencyCode: 'INR' }, durationMin: 10, preparationInstructions: 'Remove metal jewellery', active: true });
|
||||
console.log(' Chest X-ray');
|
||||
|
||||
const tvUltrasound = await mk('labTest', { name: 'Transvaginal Ultrasound', testName: 'Transvaginal Ultrasound', testCode: 'TVS-001', category: 'IMAGING', department: 'GYNECOLOGY', price: { amountMicros: 1_200_000_000, currencyCode: 'INR' }, durationMin: 20, preparationInstructions: 'Empty bladder preferred', active: true });
|
||||
console.log(' Transvaginal Ultrasound');
|
||||
|
||||
const pelvicUS = await mk('labTest', { name: 'Pelvic Ultrasound', testName: 'Pelvic Ultrasound', testCode: 'PUS-001', category: 'IMAGING', department: 'GYNECOLOGY', price: { amountMicros: 1_000_000_000, currencyCode: 'INR' }, durationMin: 20, preparationInstructions: 'Full bladder required — drink 4-5 glasses of water 1h before', active: true });
|
||||
console.log(' Pelvic Ultrasound');
|
||||
|
||||
const mammogram = await mk('labTest', { name: 'Mammogram', testName: 'Digital Mammogram (Bilateral)', testCode: 'MAM-001', category: 'SCREENING', department: 'RADIOLOGY', price: { amountMicros: 1_500_000_000, currencyCode: 'INR' }, durationMin: 20, preparationInstructions: 'Schedule 1 week after period. No deodorant/powder on exam day.', active: true });
|
||||
console.log(' Mammogram');
|
||||
|
||||
const boneDensity = await mk('labTest', { name: 'Bone Density Scan', testName: 'DEXA Scan (Bone Mineral Density)', testCode: 'DEXA-001', category: 'BONE_DENSITY', department: 'RADIOLOGY', price: { amountMicros: 1_800_000_000, currencyCode: 'INR' }, durationMin: 15, preparationInstructions: 'No calcium supplements 24h before', active: true });
|
||||
console.log(' Bone Density (DEXA)');
|
||||
|
||||
const jointXray = await mk('labTest', { name: 'Joint X-rays', testName: 'Joint X-rays (Knee/Hip/Spine)', testCode: 'XR-JNT01', category: 'IMAGING', department: 'ORTHOPEDICS', price: { amountMicros: 600_000_000, currencyCode: 'INR' }, durationMin: 15, preparationInstructions: 'Remove metal jewellery', active: true });
|
||||
console.log(' Joint X-rays');
|
||||
|
||||
await auth();
|
||||
|
||||
// Screening
|
||||
const papSmear = await mk('labTest', { name: 'PAP Smear', testName: 'PAP Smear (Cervical Cytology)', testCode: 'PAP-001', category: 'SCREENING', department: 'GYNECOLOGY', price: { amountMicros: 800_000_000, currencyCode: 'INR' }, durationMin: 10, preparationInstructions: 'Avoid intercourse 48h before. Not during menstruation.', active: true });
|
||||
console.log(' PAP Smear');
|
||||
|
||||
const bmi = await mk('labTest', { name: 'BMI Assessment', testName: 'BMI & Body Composition', testCode: 'BMI-001', category: 'OTHER', department: 'GENERAL_MEDICINE', price: { amountMicros: 100_000_000, currencyCode: 'INR' }, durationMin: 10, preparationInstructions: 'No preparation needed', active: true });
|
||||
console.log(' BMI');
|
||||
|
||||
const vision = await mk('labTest', { name: 'Vision Test', testName: 'Visual Acuity & Eye Screening', testCode: 'VIS-001', category: 'SCREENING', department: 'OPHTHALMOLOGY', price: { amountMicros: 300_000_000, currencyCode: 'INR' }, durationMin: 15, preparationInstructions: 'Bring current glasses if any', active: true });
|
||||
console.log(' Vision Test');
|
||||
|
||||
const dental = await mk('labTest', { name: 'Dental Check', testName: 'Dental Screening & Oral Health', testCode: 'DEN-001', category: 'SCREENING', department: 'DENTAL', price: { amountMicros: 300_000_000, currencyCode: 'INR' }, durationMin: 15, preparationInstructions: 'No preparation needed', active: true });
|
||||
console.log(' Dental Check');
|
||||
|
||||
const semenAnalysis = await mk('labTest', { name: 'Semen Analysis', testName: 'Semen Analysis (Seminogram)', testCode: 'SEM-001', category: 'OTHER', department: 'PATHOLOGY', price: { amountMicros: 800_000_000, currencyCode: 'INR' }, durationMin: 30, sampleType: 'Semen', preparationInstructions: '3-5 days abstinence required. Collect at lab.', active: true });
|
||||
console.log(' Semen Analysis');
|
||||
|
||||
// Consultations
|
||||
const cardioConsult = await mk('labTest', { name: 'Cardiologist Consultation', testName: 'Cardiologist Consultation', testCode: 'CON-CAR01', category: 'CONSULTATION', department: 'CARDIOLOGY', price: { amountMicros: 800_000_000, currencyCode: 'INR' }, durationMin: 30, preparationInstructions: 'Bring previous reports and medication list', active: true });
|
||||
console.log(' Cardiologist Consultation');
|
||||
|
||||
const gyneConsult = await mk('labTest', { name: 'Gynecologist Consultation', testName: 'Gynecologist Consultation', testCode: 'CON-GYN01', category: 'CONSULTATION', department: 'GYNECOLOGY', price: { amountMicros: 700_000_000, currencyCode: 'INR' }, durationMin: 30, preparationInstructions: 'Bring previous reports and menstrual history', active: true });
|
||||
console.log(' Gynecologist Consultation');
|
||||
|
||||
const orthoConsult = await mk('labTest', { name: 'Orthopedic Consultation', testName: 'Orthopedic Consultation', testCode: 'CON-ORT01', category: 'CONSULTATION', department: 'ORTHOPEDICS', price: { amountMicros: 600_000_000, currencyCode: 'INR' }, durationMin: 30, preparationInstructions: 'Bring previous X-rays and reports', active: true });
|
||||
console.log(' Orthopedic Consultation');
|
||||
|
||||
const fertilityConsult = await mk('labTest', { name: 'Fertility Specialist Consultation', testName: 'Fertility Specialist Consultation', testCode: 'CON-FER01', category: 'CONSULTATION', department: 'GYNECOLOGY', price: { amountMicros: 1_000_000_000, currencyCode: 'INR' }, durationMin: 45, preparationInstructions: 'Bring previous reports, cycle history, and partner details', active: true });
|
||||
console.log(' Fertility Specialist Consultation');
|
||||
|
||||
console.log(`\n Total: 34 lab tests created\n`);
|
||||
|
||||
await auth();
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// LINK TESTS TO PACKAGES (packageTest junction)
|
||||
// ═══════════════════════════════════════════
|
||||
console.log('🔗 Linking tests to packages\n');
|
||||
|
||||
async function link(pkgName: string, tests: { id: string; pos: number; mandatory?: boolean }[]) {
|
||||
const pkgId = pkgs[pkgName];
|
||||
if (!pkgId) { console.log(` ⚠️ Package "${pkgName}" not found — skipping`); return; }
|
||||
for (const t of tests) {
|
||||
await mk('packageTest', {
|
||||
name: `${pkgName} — Test #${t.pos}`,
|
||||
healthPackageId: pkgId,
|
||||
labTestId: t.id,
|
||||
order: t.pos,
|
||||
mandatory: t.mandatory ?? true,
|
||||
});
|
||||
}
|
||||
console.log(` ${pkgName}: ${tests.length} tests linked`);
|
||||
}
|
||||
|
||||
// Master Health Checkup (11 tests)
|
||||
await link('Master Health Checkup', [
|
||||
{ id: cbc, pos: 1 }, { id: lipid, pos: 2 }, { id: lft, pos: 3 },
|
||||
{ id: kft, pos: 4 }, { id: thyroid, pos: 5 }, { id: bloodSugar, pos: 6 },
|
||||
{ id: ecg, pos: 7 }, { id: chestXray, pos: 8 }, { id: bmi, pos: 9 },
|
||||
{ id: vision, pos: 10 }, { id: dental, pos: 11 },
|
||||
]);
|
||||
|
||||
await auth();
|
||||
|
||||
// Cardiac Screening (8 tests)
|
||||
await link('Cardiac Screening', [
|
||||
{ id: ecg, pos: 1 }, { id: echo, pos: 2 }, { id: tmt, pos: 3 },
|
||||
{ id: lipid, pos: 4 }, { id: hsCrp, pos: 5 }, { id: homocysteine, pos: 6 },
|
||||
{ id: hba1c, pos: 7 }, { id: cardioConsult, pos: 8 },
|
||||
]);
|
||||
|
||||
await auth();
|
||||
|
||||
// Women's Wellness (10 tests)
|
||||
await link("Women's Wellness Package", [
|
||||
{ id: papSmear, pos: 1 }, { id: mammogram, pos: 2 }, { id: pelvicUS, pos: 3 },
|
||||
{ id: thyroid, pos: 4 }, { id: vitD, pos: 5 }, { id: calcium, pos: 6 },
|
||||
{ id: cbc, pos: 7 }, { id: fsh, pos: 8 }, { id: boneDensity, pos: 9 },
|
||||
{ id: gyneConsult, pos: 10 },
|
||||
]);
|
||||
|
||||
await auth();
|
||||
|
||||
// Orthopedic Assessment (7 tests)
|
||||
await link('Orthopedic Assessment', [
|
||||
{ id: jointXray, pos: 1 }, { id: boneDensity, pos: 2 }, { id: vitD, pos: 3 },
|
||||
{ id: calcium, pos: 4 }, { id: uricAcid, pos: 5 }, { id: raFactor, pos: 6 },
|
||||
{ id: orthoConsult, pos: 7 },
|
||||
]);
|
||||
|
||||
await auth();
|
||||
|
||||
// IVF Consultation (9 tests)
|
||||
await link('IVF Consultation Package', [
|
||||
{ id: amh, pos: 1 }, { id: fsh, pos: 2 }, { id: lh, pos: 3 },
|
||||
{ id: estradiol, pos: 4 }, { id: prolactin, pos: 5 }, { id: thyroid, pos: 6 },
|
||||
{ id: tvUltrasound, pos: 7 }, { id: semenAnalysis, pos: 8 },
|
||||
{ id: fertilityConsult, pos: 9 },
|
||||
]);
|
||||
|
||||
console.log('\n🎉 Done!');
|
||||
console.log(' 34 lab tests · 45 package-test linkages across 5 packages');
|
||||
}
|
||||
|
||||
main().catch(e => { console.error('💥', e.message); process.exit(1); });
|
||||
348
scripts/seed-new-entities.ts
Normal file
348
scripts/seed-new-entities.ts
Normal file
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* Seed clinics, health packages, insurance partners, and link doctors to clinics.
|
||||
* Run: cd helix-engage && npx tsx scripts/seed-new-entities.ts
|
||||
*
|
||||
* Prerequisites: doctors already seeded via seed-data.ts
|
||||
*
|
||||
* Platform field mapping (SDK name → platform name):
|
||||
* Clinic: address→addressCustom, operatingHoursWeekday→weekdayHours,
|
||||
* operatingHoursSaturday→saturdayHours, operatingHoursSunday→sundayHours,
|
||||
* clinicStatus→status, onlineBookingEnabled→onlineBooking,
|
||||
* arriveEarlyMinutes→arriveEarlyMin, paymentCash→acceptsCash,
|
||||
* paymentCard→acceptsCard, paymentUpi→acceptsUpi
|
||||
* HealthPackage: packageDepartment→department, durationMinutes→durationMin, isActive→active
|
||||
* InsurancePartner: planTypes→planTypesAccepted
|
||||
*/
|
||||
|
||||
const GQL = 'http://localhost:4000/graphql';
|
||||
const SUB = 'fortytwo-dev';
|
||||
const ORIGIN = 'http://fortytwo-dev.localhost:4010';
|
||||
|
||||
let token = '';
|
||||
|
||||
async function gql(query: string, variables?: any) {
|
||||
const h: Record<string, string> = { 'Content-Type': 'application/json', 'X-Workspace-Subdomain': SUB };
|
||||
if (token) h['Authorization'] = `Bearer ${token}`;
|
||||
const r = await fetch(GQL, { method: 'POST', headers: h, body: JSON.stringify({ query, variables }) });
|
||||
const d: any = await r.json();
|
||||
if (d.errors) { console.error('❌', d.errors[0].message); throw new Error(d.errors[0].message); }
|
||||
return d.data;
|
||||
}
|
||||
|
||||
async function auth() {
|
||||
const d1 = await gql(`mutation { getLoginTokenFromCredentials(email: "dev@fortytwo.dev", password: "tim@apple.dev", origin: "${ORIGIN}") { loginToken { token } } }`);
|
||||
const lt = d1.getLoginTokenFromCredentials.loginToken.token;
|
||||
const d2 = await gql(`mutation { getAuthTokensFromLoginToken(loginToken: "${lt}", origin: "${ORIGIN}") { tokens { accessOrWorkspaceAgnosticToken { token } } } }`);
|
||||
token = d2.getAuthTokensFromLoginToken.tokens.accessOrWorkspaceAgnosticToken.token;
|
||||
}
|
||||
|
||||
async function mk(entity: string, data: any): Promise<string> {
|
||||
const cap = entity[0].toUpperCase() + entity.slice(1);
|
||||
const d = await gql(`mutation($data: ${cap}CreateInput!) { create${cap}(data: $data) { id } }`, { data });
|
||||
return d[`create${cap}`].id;
|
||||
}
|
||||
|
||||
async function update(entity: string, id: string, data: any): Promise<void> {
|
||||
const cap = entity[0].toUpperCase() + entity.slice(1);
|
||||
await gql(`mutation($id: UUID!, $data: ${cap}UpdateInput!) { update${cap}(id: $id, data: $data) { id } }`, { id, data });
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Seeding clinics, health packages, insurance partners...\n');
|
||||
await auth();
|
||||
console.log('✅ Auth OK\n');
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// CLINICS
|
||||
// ═══════════════════════════════════════════
|
||||
console.log('🏥 Clinics');
|
||||
const koramangala = await mk('clinic', {
|
||||
name: 'Global Hospital — Koramangala',
|
||||
clinicName: 'Koramangala',
|
||||
addressCustom: {
|
||||
addressStreet1: '#45, 80 Feet Road',
|
||||
addressCity: 'Bengaluru',
|
||||
addressState: 'Karnataka',
|
||||
addressPostcode: '560034',
|
||||
addressCountry: 'India',
|
||||
},
|
||||
phone: { primaryPhoneNumber: '08041234567', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
email: { primaryEmail: 'koramangala@globalhospital.com' },
|
||||
weekdayHours: '8:00 AM – 8:00 PM',
|
||||
saturdayHours: '8:00 AM – 8:00 PM',
|
||||
sundayHours: '9:00 AM – 2:00 PM',
|
||||
status: 'ACTIVE',
|
||||
walkInAllowed: true,
|
||||
onlineBooking: true,
|
||||
cancellationWindowHours: 4,
|
||||
arriveEarlyMin: 15,
|
||||
requiredDocuments: 'ID proof + medical records',
|
||||
acceptsCash: 'YES',
|
||||
acceptsCard: 'YES',
|
||||
acceptsUpi: 'YES',
|
||||
});
|
||||
console.log(` Koramangala: ${koramangala}`);
|
||||
|
||||
const whitefield = await mk('clinic', {
|
||||
name: 'Global Hospital — Whitefield',
|
||||
clinicName: 'Whitefield',
|
||||
addressCustom: {
|
||||
addressStreet1: 'Prestige Shantiniketan',
|
||||
addressCity: 'Bengaluru',
|
||||
addressState: 'Karnataka',
|
||||
addressPostcode: '560048',
|
||||
addressCountry: 'India',
|
||||
},
|
||||
phone: { primaryPhoneNumber: '08041234568', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
email: { primaryEmail: 'whitefield@globalhospital.com' },
|
||||
weekdayHours: '8:00 AM – 8:00 PM',
|
||||
saturdayHours: '8:00 AM – 8:00 PM',
|
||||
sundayHours: 'Closed',
|
||||
status: 'ACTIVE',
|
||||
walkInAllowed: true,
|
||||
onlineBooking: true,
|
||||
cancellationWindowHours: 4,
|
||||
arriveEarlyMin: 15,
|
||||
requiredDocuments: 'ID proof + medical records',
|
||||
acceptsCash: 'YES',
|
||||
acceptsCard: 'YES',
|
||||
acceptsUpi: 'YES',
|
||||
});
|
||||
console.log(` Whitefield: ${whitefield}`);
|
||||
|
||||
const indiranagar = await mk('clinic', {
|
||||
name: 'Global Hospital — Indiranagar',
|
||||
clinicName: 'Indiranagar',
|
||||
addressCustom: {
|
||||
addressStreet1: '#12, 100 Feet Road',
|
||||
addressCity: 'Bengaluru',
|
||||
addressState: 'Karnataka',
|
||||
addressPostcode: '560038',
|
||||
addressCountry: 'India',
|
||||
},
|
||||
phone: { primaryPhoneNumber: '08041234569', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
email: { primaryEmail: 'indiranagar@globalhospital.com' },
|
||||
weekdayHours: '9:00 AM – 7:00 PM',
|
||||
saturdayHours: '9:00 AM – 7:00 PM',
|
||||
sundayHours: '10:00 AM – 1:00 PM',
|
||||
status: 'ACTIVE',
|
||||
walkInAllowed: true,
|
||||
onlineBooking: true,
|
||||
cancellationWindowHours: 4,
|
||||
arriveEarlyMin: 15,
|
||||
requiredDocuments: 'ID proof + medical records',
|
||||
acceptsCash: 'YES',
|
||||
acceptsCard: 'YES',
|
||||
acceptsUpi: 'YES',
|
||||
});
|
||||
console.log(` Indiranagar: ${indiranagar}\n`);
|
||||
|
||||
await auth();
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// LINK DOCTORS TO CLINICS
|
||||
// ═══════════════════════════════════════════
|
||||
console.log('🔗 Linking doctors to clinics');
|
||||
const doctors: Record<string, string> = {
|
||||
'da5678f3-6b52-492e-87d3-c4707d105938': 'Dr. Sharma', // Koramangala
|
||||
'b080cdf0-4527-46c7-b723-47f2eee623e4': 'Dr. Patel', // Indiranagar
|
||||
'd780976a-7ddb-4a00-9a56-e7e3a77fa416': 'Dr. Kumar', // Whitefield
|
||||
'bf77c148-438f-4b6f-9e5d-b1c1ff2e10f8': 'Dr. Reddy', // Koramangala
|
||||
'e71c2c59-574f-4e81-b8cd-2d7b4b5da8e5': 'Dr. Singh', // Indiranagar
|
||||
};
|
||||
const doctorClinicMap: Record<string, string> = {
|
||||
'da5678f3-6b52-492e-87d3-c4707d105938': koramangala,
|
||||
'b080cdf0-4527-46c7-b723-47f2eee623e4': indiranagar,
|
||||
'd780976a-7ddb-4a00-9a56-e7e3a77fa416': whitefield,
|
||||
'bf77c148-438f-4b6f-9e5d-b1c1ff2e10f8': koramangala,
|
||||
'e71c2c59-574f-4e81-b8cd-2d7b4b5da8e5': indiranagar,
|
||||
};
|
||||
for (const [docId, clinicId] of Object.entries(doctorClinicMap)) {
|
||||
await update('doctor', docId, { clinicId });
|
||||
console.log(` ${doctors[docId]} → ${clinicId === koramangala ? 'Koramangala' : clinicId === whitefield ? 'Whitefield' : 'Indiranagar'}`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
await auth();
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// HEALTH PACKAGES
|
||||
// ═══════════════════════════════════════════
|
||||
console.log('📦 Health Packages');
|
||||
await mk('healthPackage', {
|
||||
name: 'Master Health Checkup',
|
||||
packageName: 'Master Health Checkup',
|
||||
description: 'Comprehensive annual health screening — blood work, ECG, chest X-ray, BMI, vision, dental',
|
||||
price: { amountMicros: 2_999_000_000, currencyCode: 'INR' },
|
||||
department: 'PREVENTIVE',
|
||||
inclusions: 'CBC, Lipid Profile, Liver Function, Kidney Function, Thyroid, Blood Sugar, ECG, Chest X-ray, BMI, Vision Test, Dental Check',
|
||||
durationMin: 180,
|
||||
eligibility: 'All adults above 18 years',
|
||||
active: true,
|
||||
clinicId: koramangala,
|
||||
});
|
||||
console.log(' Master Health Checkup — ₹2,999');
|
||||
|
||||
await mk('healthPackage', {
|
||||
name: 'Cardiac Screening',
|
||||
packageName: 'Cardiac Screening',
|
||||
description: 'Heart health assessment — ECG, Echo, TMT, lipid panel, cardiac risk markers',
|
||||
price: { amountMicros: 4_999_000_000, currencyCode: 'INR' },
|
||||
department: 'CARDIOLOGY',
|
||||
inclusions: 'ECG, 2D Echocardiogram, TMT (Treadmill Test), Lipid Profile, hs-CRP, Homocysteine, HbA1c, Cardiologist Consultation',
|
||||
durationMin: 240,
|
||||
eligibility: 'Adults above 30 or family history of heart disease',
|
||||
active: true,
|
||||
clinicId: koramangala,
|
||||
});
|
||||
console.log(' Cardiac Screening — ₹4,999');
|
||||
|
||||
await mk('healthPackage', {
|
||||
name: "Women's Wellness Package",
|
||||
packageName: "Women's Wellness Package",
|
||||
description: 'Complete women\'s health screening — PAP smear, mammogram, hormone panel, bone density',
|
||||
price: { amountMicros: 3_499_000_000, currencyCode: 'INR' },
|
||||
department: 'GYNECOLOGY',
|
||||
inclusions: 'PAP Smear, Mammogram, Pelvic Ultrasound, Thyroid Panel, Vitamin D, Calcium, CBC, Hormone Panel (FSH/LH/Estradiol), Bone Density Scan, Gynecologist Consultation',
|
||||
durationMin: 210,
|
||||
eligibility: 'Women above 25 years',
|
||||
active: true,
|
||||
clinicId: indiranagar,
|
||||
});
|
||||
console.log(" Women's Wellness — ₹3,499");
|
||||
|
||||
await mk('healthPackage', {
|
||||
name: 'Orthopedic Assessment',
|
||||
packageName: 'Orthopedic Assessment',
|
||||
description: 'Joint and bone health evaluation — X-rays, bone density, vitamin levels, specialist consult',
|
||||
price: { amountMicros: 1_999_000_000, currencyCode: 'INR' },
|
||||
department: 'ORTHOPEDICS',
|
||||
inclusions: 'Joint X-rays (knee/hip/spine), Bone Density Scan, Vitamin D, Calcium, Uric Acid, RA Factor, Orthopedic Consultation',
|
||||
durationMin: 120,
|
||||
eligibility: 'Adults with joint pain or age above 40',
|
||||
active: true,
|
||||
clinicId: whitefield,
|
||||
});
|
||||
console.log(' Orthopedic Assessment — ₹1,999');
|
||||
|
||||
await mk('healthPackage', {
|
||||
name: 'IVF Consultation Package',
|
||||
packageName: 'IVF Consultation Package',
|
||||
description: 'Initial fertility assessment — hormone panel, ultrasound, semen analysis, specialist consult',
|
||||
price: { amountMicros: 5_999_000_000, currencyCode: 'INR' },
|
||||
discountedPrice: { amountMicros: 0, currencyCode: 'INR' },
|
||||
department: 'GYNECOLOGY',
|
||||
inclusions: 'AMH, FSH, LH, Estradiol, Prolactin, Thyroid Panel, Transvaginal Ultrasound, Semen Analysis, Fertility Specialist Consultation',
|
||||
durationMin: 150,
|
||||
eligibility: 'Couples planning IVF or facing infertility (1+ year)',
|
||||
active: true,
|
||||
clinicId: indiranagar,
|
||||
});
|
||||
console.log(' IVF Consultation — ₹5,999 (free first visit via campaign)\n');
|
||||
|
||||
await auth();
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// INSURANCE PARTNERS
|
||||
// ═══════════════════════════════════════════
|
||||
console.log('🛡️ Insurance Partners');
|
||||
await mk('insurancePartner', {
|
||||
name: 'Star Health Insurance',
|
||||
insurerName: 'Star Health',
|
||||
tpaName: 'Star Health & Allied Insurance',
|
||||
planTypesAccepted: 'Family Health Optima, Comprehensive, Senior Citizen Red Carpet',
|
||||
settlementType: 'BOTH',
|
||||
empanelmentStatus: 'ACTIVE',
|
||||
validUntil: '2027-03-31',
|
||||
contactPerson: 'Rajesh M',
|
||||
contactPhone: { primaryPhoneNumber: '9900200001', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
clinicId: koramangala,
|
||||
});
|
||||
console.log(' Star Health — cashless + reimbursement');
|
||||
|
||||
await mk('insurancePartner', {
|
||||
name: 'ICICI Lombard',
|
||||
insurerName: 'ICICI Lombard',
|
||||
tpaName: 'Medi Assist',
|
||||
planTypesAccepted: 'iHealth, Complete Health Insurance, Group Mediclaim',
|
||||
settlementType: 'CASHLESS',
|
||||
empanelmentStatus: 'ACTIVE',
|
||||
validUntil: '2027-06-30',
|
||||
contactPerson: 'Priya K',
|
||||
contactPhone: { primaryPhoneNumber: '9900200002', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
clinicId: koramangala,
|
||||
});
|
||||
console.log(' ICICI Lombard — cashless');
|
||||
|
||||
await mk('insurancePartner', {
|
||||
name: 'Bajaj Allianz',
|
||||
insurerName: 'Bajaj Allianz',
|
||||
tpaName: 'Health India TPA',
|
||||
planTypesAccepted: 'Health Guard, Silver Health, Group Health',
|
||||
settlementType: 'BOTH',
|
||||
empanelmentStatus: 'ACTIVE',
|
||||
validUntil: '2027-01-31',
|
||||
contactPerson: 'Suresh V',
|
||||
contactPhone: { primaryPhoneNumber: '9900200003', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
clinicId: whitefield,
|
||||
});
|
||||
console.log(' Bajaj Allianz — cashless + reimbursement');
|
||||
|
||||
await mk('insurancePartner', {
|
||||
name: 'HDFC Ergo',
|
||||
insurerName: 'HDFC Ergo',
|
||||
tpaName: 'Paramount Health Services',
|
||||
planTypesAccepted: 'Optima Secure, Optima Restore, My Health Suraksha',
|
||||
settlementType: 'CASHLESS',
|
||||
empanelmentStatus: 'ACTIVE',
|
||||
validUntil: '2027-09-30',
|
||||
contactPerson: 'Anita S',
|
||||
contactPhone: { primaryPhoneNumber: '9900200004', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
clinicId: indiranagar,
|
||||
});
|
||||
console.log(' HDFC Ergo — cashless');
|
||||
|
||||
await mk('insurancePartner', {
|
||||
name: 'Niva Bupa (Max Bupa)',
|
||||
insurerName: 'Niva Bupa',
|
||||
tpaName: 'Niva Bupa Health Insurance',
|
||||
planTypesAccepted: 'Health Recharge, Health Premia, ReAssure 2.0',
|
||||
settlementType: 'BOTH',
|
||||
empanelmentStatus: 'ACTIVE',
|
||||
validUntil: '2027-04-30',
|
||||
contactPerson: 'Deepak R',
|
||||
contactPhone: { primaryPhoneNumber: '9900200005', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
clinicId: koramangala,
|
||||
});
|
||||
console.log(' Niva Bupa — cashless + reimbursement');
|
||||
|
||||
await mk('insurancePartner', {
|
||||
name: 'New India Assurance',
|
||||
insurerName: 'New India Assurance',
|
||||
tpaName: 'Raksha TPA',
|
||||
planTypesAccepted: 'Mediclaim Policy, Jan Arogya Bima, Group Mediclaim',
|
||||
settlementType: 'REIMBURSEMENT',
|
||||
empanelmentStatus: 'ACTIVE',
|
||||
validUntil: '2027-12-31',
|
||||
contactPerson: 'Mohan T',
|
||||
contactPhone: { primaryPhoneNumber: '9900200006', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
|
||||
clinicId: whitefield,
|
||||
});
|
||||
console.log(' New India Assurance — reimbursement\n');
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
// CLEANUP: Delete the empty doctor record
|
||||
// ═══════════════════════════════════════════
|
||||
try {
|
||||
await gql(`mutation { deleteDoctor(id: "967e1959-9b85-497e-9f8e-cad65120c5de") { id } }`);
|
||||
console.log('🧹 Cleaned up empty doctor record\n');
|
||||
} catch {
|
||||
// Ignore if already deleted
|
||||
}
|
||||
|
||||
console.log('🎉 Done!');
|
||||
console.log(' 3 clinics · 5 health packages · 6 insurance partners');
|
||||
console.log(' 5 doctors linked to clinics');
|
||||
}
|
||||
|
||||
main().catch(e => { console.error('💥', e.message); process.exit(1); });
|
||||
@@ -144,11 +144,12 @@ async function main() {
|
||||
const doctorsData = await gql(`{ doctors(first: 10) { edges { node {
|
||||
id name fullName { firstName lastName }
|
||||
department specialty qualifications yearsOfExperience
|
||||
branchClinic visitingHours
|
||||
visitingHours
|
||||
consultationFeeNew { amountMicros currencyCode }
|
||||
consultationFeeFollowUp { amountMicros currencyCode }
|
||||
active registrationNumber
|
||||
portalUserId
|
||||
clinic { id clinicName }
|
||||
} } } }`);
|
||||
const doctors = doctorsData.doctors.edges.map((e: any) => e.node);
|
||||
assert(doctors.length === 5, `Found ${doctors.length} doctors`);
|
||||
@@ -208,7 +209,7 @@ async function main() {
|
||||
console.log(` AI Summary: ${matchedLead?.aiSummary}`);
|
||||
console.log(` Suggested Action: ${matchedLead?.aiSuggestedAction}`);
|
||||
console.log(` Doctor: Dr. Patel — ${drPatel?.specialty}`);
|
||||
console.log(` Next Appointment: ${drPatel?.visitingHours} at ${drPatel?.branchClinic}`);
|
||||
console.log(` Next Appointment: ${drPatel?.visitingHours} at ${drPatel?.clinic?.clinicName ?? 'N/A'}`);
|
||||
console.log(` Fee: ₹${(drPatel?.consultationFeeNew?.amountMicros ?? 0) / 1_000_000} (new) / ₹${(drPatel?.consultationFeeFollowUp?.amountMicros ?? 0) / 1_000_000} (follow-up)`);
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user