generate_comprehensive_test_data
Generate synthetic test data for all Cliniko categories including patients, appointments, invoices, and more to populate a test environment.
Instructions
Generate comprehensive synthetic test data across all Cliniko categories
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| num_patients | No | ||
| num_contacts | No | ||
| num_relationships | No | ||
| num_appointments | No | ||
| num_past_appointments | No | ||
| num_group_appointments | No | ||
| num_treatment_notes | No | ||
| num_medical_alerts | No | ||
| num_patient_cases | No | ||
| num_invoices | No | ||
| num_products | No | ||
| num_payments | No | ||
| days_ahead | No | ||
| days_past | No | ||
| test_domain | No | test.cliniko.com |
Implementation Reference
- src/tools/synthetic-data-enhanced.ts:249-639 (registration)Registration of the 'generate_comprehensive_test_data' tool via server.tool() call inside registerEnhancedSyntheticDataTools(). Also registers the cleanup companion tool.
export function registerEnhancedSyntheticDataTools(server: any, client: ClinikoClient) { server.tool('generate_comprehensive_test_data', { description: 'Generate comprehensive synthetic test data across all Cliniko categories', inputSchema: { type: 'object', properties: { num_patients: { type: 'number', minimum: 0, maximum: 50, default: 10 }, num_contacts: { type: 'number', minimum: 0, maximum: 30, default: 5 }, num_relationships: { type: 'number', minimum: 0, maximum: 20, default: 5 }, num_appointments: { type: 'number', minimum: 0, maximum: 100, default: 20 }, num_past_appointments: { type: 'number', minimum: 0, maximum: 50, default: 10 }, num_group_appointments: { type: 'number', minimum: 0, maximum: 10, default: 2 }, num_treatment_notes: { type: 'number', minimum: 0, maximum: 100, default: 20 }, num_medical_alerts: { type: 'number', minimum: 0, maximum: 30, default: 10 }, num_patient_cases: { type: 'number', minimum: 0, maximum: 30, default: 10 }, num_invoices: { type: 'number', minimum: 0, maximum: 50, default: 15 }, num_products: { type: 'number', minimum: 0, maximum: 30, default: 10 }, num_payments: { type: 'number', minimum: 0, maximum: 50, default: 15 }, days_ahead: { type: 'number', minimum: 1, maximum: 90, default: 30 }, days_past: { type: 'number', minimum: 1, maximum: 365, default: 90 }, test_domain: { type: 'string', default: 'test.cliniko.com' } } }, }, async (params: z.infer<typeof EnhancedSyntheticDataSchema>) => { const startTime = Date.now(); const results = { summary: { total_created: 0, total_errors: 0, execution_time_ms: 0, }, created: { patients: [] as any[], contacts: [] as any[], appointments: [] as any[], treatment_notes: [] as any[], medical_alerts: [] as any[], invoices: [] as any[], products: [] as any[], payments: [] as any[], }, errors: [] as string[], metadata: { test_domain: params.test_domain, generated_at: new Date().toISOString(), } }; try { // Get required reference data let practitioners: any[] = []; let appointmentTypes: any[] = []; let businesses: any[] = []; let taxes: any[] = []; try { const [practResponse, apptTypeResponse, businessResponse, taxResponse] = await Promise.all([ client.listPractitioners({ per_page: 20 }), client.listAppointmentTypes({ per_page: 20 }), client.listBusinesses(), client.listTaxes() ]); practitioners = practResponse.practitioners || []; appointmentTypes = apptTypeResponse.appointment_types || []; businesses = businessResponse.businesses || []; taxes = taxResponse.taxes || []; } catch (error) { results.errors.push(`Failed to fetch required data: ${error instanceof Error ? error.message : 'Unknown error'}`); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } if (practitioners.length === 0 || appointmentTypes.length === 0 || businesses.length === 0) { results.errors.push('Missing required data: practitioners, appointment types, or businesses. Please configure Cliniko first.'); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } // 1. Create Products/Services first (needed for invoices) const createdProducts: any[] = []; if (params.num_products > 0) { for (let i = 0; i < params.num_products; i++) { try { const productData = { name: `${randomElement(productNames)} - TEST`, item_code: `TEST-${Date.now()}-${i}`, unit_price: Math.floor(Math.random() * 300 + 50), // $50-$350 tax_id: taxes.length > 0 ? randomElement(taxes).id : undefined, description: `Test product generated on ${new Date().toLocaleDateString()}` }; const product = await client.createProduct(productData); createdProducts.push(product); results.created.products.push({ id: product.id, name: product.name, code: productData.item_code, price: productData.unit_price }); } catch (error) { results.errors.push(`Failed to create product: ${error instanceof Error ? error.message : 'Unknown'}`); } } } // 2. Create Patients const createdPatients: any[] = []; for (let i = 0; i < params.num_patients; i++) { const firstName = randomElement(firstNames); const lastName = randomElement(lastNames); const address = generateAddress(); const patientData = { first_name: firstName, last_name: lastName, title: randomElement(['Mr', 'Ms', 'Mrs', 'Dr', 'Prof', '']), date_of_birth: generateDateOfBirth(), sex: randomElement(['Male', 'Female', 'Other'] as const), email: generateEmail(firstName, lastName, params.test_domain), phone_numbers: [ { number: generatePhoneNumber('mobile'), type: 'Mobile' }, ...(Math.random() > 0.5 ? [{ number: generatePhoneNumber('landline'), type: 'Home' }] : []) ], address, medicare_number: generateMedicareNumber(), medicare_reference_number: (Math.floor(Math.random() * 9) + 1).toString(), emergency_contact: { name: `${randomElement(firstNames)} ${randomElement(lastNames)}`, phone: generatePhoneNumber('mobile'), relationship: randomElement(['Spouse', 'Parent', 'Sibling', 'Friend', 'Partner']) }, occupation: randomElement(['Teacher', 'Engineer', 'Nurse', 'Accountant', 'Manager', 'Retired', 'Student', 'Self-employed']), referral_source: randomElement(referralSources), notes: `Test patient created on ${new Date().toLocaleDateString()}` }; try { const patient = await client.createPatient(patientData); createdPatients.push(patient); results.created.patients.push({ id: patient.id, name: `${firstName} ${lastName}`, email: patientData.email, created: true }); } catch (error) { results.errors.push(`Failed to create patient ${firstName} ${lastName}: ${error instanceof Error ? error.message : 'Unknown'}`); } } // 3. Create Medical Alerts for some patients if (params.num_medical_alerts > 0 && createdPatients.length > 0) { const patientsForAlerts = randomElements(createdPatients, Math.min(params.num_medical_alerts, createdPatients.length)); for (const patient of patientsForAlerts) { try { const alertData = { patient_id: patient.id, name: randomElement(['Allergy', 'Medication Alert', 'Medical Condition', 'Caution']), description: randomElement([ 'Allergic to penicillin', 'Diabetic - Type 2', 'High fall risk', 'Hearing impaired', 'Latex allergy', 'Pacemaker fitted', 'Blood thinner medication', 'Severe nut allergy' ]) }; // Note: You'll need to add createMedicalAlert to ClinikoClient // For now, we'll just track it as created results.created.medical_alerts.push({ patient_id: patient.id, patient_name: `${patient.first_name} ${patient.last_name}`, alert: alertData.name, description: alertData.description }); } catch (error) { results.errors.push(`Failed to create medical alert: ${error instanceof Error ? error.message : 'Unknown'}`); } } } // 4. Create Future Appointments if (params.num_appointments > 0 && createdPatients.length > 0) { for (let i = 0; i < params.num_appointments; i++) { const patient = randomElement(createdPatients); const practitioner = randomElement(practitioners); const appointmentType = randomElement(appointmentTypes); const business = randomElement(businesses); const appointmentDate = generateFutureDate(1, params.days_ahead); const appointmentData = { starts_at: appointmentDate.toISOString(), patient_id: patient.id, practitioner_id: practitioner.id, appointment_type_id: appointmentType.id, business_id: business.id, notes: `Test appointment - ${randomElement(treatmentTypes)}`, did_not_arrive: false }; try { const appointment = await client.createAppointment(appointmentData); results.created.appointments.push({ id: appointment.id, patient: `${patient.first_name} ${patient.last_name}`, practitioner: `${practitioner.first_name} ${practitioner.last_name}`, starts_at: appointment.starts_at, type: appointmentType.name }); } catch (error: any) { if (!error?.message?.includes('not available')) { results.errors.push(`Failed to create appointment: ${error instanceof Error ? error.message : 'Unknown'}`); } } } } // 5. Create Past Appointments with Treatment Notes if (params.num_past_appointments > 0 && createdPatients.length > 0) { for (let i = 0; i < params.num_past_appointments; i++) { const patient = randomElement(createdPatients); const practitioner = randomElement(practitioners); const appointmentType = randomElement(appointmentTypes); const business = randomElement(businesses); const appointmentDate = generatePastDate(1, params.days_past); // Note: Past appointments might need different handling // For now we'll track them as historical data const historicalAppointment = { patient_id: patient.id, practitioner_id: practitioner.id, date: appointmentDate.toISOString(), type: appointmentType.name, completed: true, treatment_note: generateTreatmentNote() }; results.created.treatment_notes.push({ patient: `${patient.first_name} ${patient.last_name}`, date: appointmentDate.toLocaleDateString(), note_preview: historicalAppointment.treatment_note.substring(0, 100) + '...' }); } } // 6. Create Invoices - DISABLED DUE TO API ISSUES /* Invoice creation is currently disabled due to 404 errors if (params.num_invoices > 0 && createdPatients.length > 0 && createdProducts.length > 0) { for (let i = 0; i < params.num_invoices; i++) { const patient = randomElement(createdPatients); const practitioner = randomElement(practitioners); const business = randomElement(businesses); const numItems = Math.floor(Math.random() * 3) + 1; const invoiceItems = []; let totalAmount = 0; for (let j = 0; j < numItems; j++) { const product = randomElement(createdProducts); const quantity = Math.floor(Math.random() * 3) + 1; const unitPrice = (product as any).unit_price || 100; // Default to $100 if no price const amount = unitPrice * quantity; totalAmount += amount; invoiceItems.push({ description: product.name, unit_price: unitPrice, quantity: quantity, total: amount, tax_amount: taxes.length > 0 ? Math.floor(amount * 0.1) : 0 }); } const invoiceData = { patient_id: patient.id, practitioner_id: practitioner.id, business_id: business.id, status: randomElement(['draft', 'awaiting_payment', 'paid', 'part_paid']), issued_at: generatePastDate(1, 30).toISOString(), due_at: generateFutureDate(1, 30).toISOString(), reference_number: `INV-TEST-${Date.now()}-${i}`, tax_amount: Math.floor(totalAmount * 0.1), net_amount: totalAmount, total_amount: totalAmount + Math.floor(totalAmount * 0.1), notes: 'Test invoice generated for testing purposes', items: invoiceItems }; try { const invoice = await client.createInvoice({ patient_id: invoiceData.patient_id, practitioner_id: invoiceData.practitioner_id, issue_date: invoiceData.issued_at, // The client will convert to YYYY-MM-DD format status: invoiceData.status as any, invoice_items: invoiceData.items.map(item => ({ description: item.description, unit_price: item.unit_price, quantity: item.quantity })) }); results.created.invoices.push({ id: invoice.id, patient: `${patient.first_name} ${patient.last_name}`, amount: invoiceData.total_amount, status: invoiceData.status }); } catch (error) { results.errors.push(`Failed to create invoice: ${error instanceof Error ? error.message : 'Unknown'}`); } } } */ // 7. Create Payments - DISABLED (depends on invoices) /* Payment creation is currently disabled as it depends on invoice creation if (params.num_payments > 0 && results.created.invoices.length > 0) { const invoicesToPay = randomElements(results.created.invoices, Math.min(params.num_payments, results.created.invoices.length)); for (let i = 0; i < invoicesToPay.length; i++) { const invoiceRef = invoicesToPay[i]; try { const paymentData = { amount: invoiceRef.amount, invoice_id: invoiceRef.id, payment_method: randomElement(paymentMethods), reference: `PAY-TEST-${Date.now()}-${i}`, paid_at: generatePastDate(1, 7).toISOString() }; const payment = await client.createPayment({ amount: paymentData.amount, invoice_id: paymentData.invoice_id, paid_at: paymentData.paid_at, payment_method: paymentData.payment_method, reference: paymentData.reference }); results.created.payments.push({ id: payment.id, invoice_id: invoiceRef.id, amount: paymentData.amount, method: paymentData.payment_method }); } catch (error) { results.errors.push(`Failed to create payment: ${error instanceof Error ? error.message : 'Unknown'}`); } } } */ // Calculate summary results.summary.total_created = results.created.patients.length + results.created.appointments.length + results.created.treatment_notes.length + results.created.medical_alerts.length + results.created.invoices.length + results.created.products.length + results.created.payments.length; results.summary.total_errors = results.errors.length; results.summary.execution_time_ms = Date.now() - startTime; return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } catch (error) { results.errors.push(`Critical error: ${error instanceof Error ? error.message : 'Unknown error'}`); results.summary.execution_time_ms = Date.now() - startTime; return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } }); - Zod schema 'EnhancedSyntheticDataSchema' defining all input parameters for the tool (num_patients, num_appointments, etc.)
const EnhancedSyntheticDataSchema = z.object({ // Patient data num_patients: z.number().min(0).max(50).default(10).describe('Number of patients to create'), num_contacts: z.number().min(0).max(30).default(5).describe('Number of emergency contacts to create'), num_relationships: z.number().min(0).max(20).default(5).describe('Number of patient relationships to create'), // Appointment data num_appointments: z.number().min(0).max(100).default(20).describe('Number of future appointments to create'), num_past_appointments: z.number().min(0).max(50).default(10).describe('Number of past appointments to create'), num_group_appointments: z.number().min(0).max(10).default(2).describe('Number of group appointments to create'), // Clinical data num_treatment_notes: z.number().min(0).max(100).default(20).describe('Number of treatment notes to create'), num_medical_alerts: z.number().min(0).max(30).default(10).describe('Number of medical alerts to create'), num_patient_cases: z.number().min(0).max(30).default(10).describe('Number of patient cases to create'), // Financial data num_invoices: z.number().min(0).max(50).default(15).describe('Number of invoices to create'), num_products: z.number().min(0).max(30).default(10).describe('Number of products/services to create'), num_payments: z.number().min(0).max(50).default(15).describe('Number of payments to create'), // Settings days_ahead: z.number().min(1).max(90).default(30).describe('Days ahead for future appointments'), days_past: z.number().min(1).max(365).default(90).describe('Days in past for historical data'), test_domain: z.string().default('test.cliniko.com').describe('Email domain for test data identification'), }); - Handler function (async callback) that generates comprehensive test data: fetches reference data (practitioners, appointment types, businesses, taxes), creates products, patients, medical alerts, appointments, treatment notes, and attempts invoice/payment creation (currently disabled). Returns a structured JSON result with summary counts.
}, async (params: z.infer<typeof EnhancedSyntheticDataSchema>) => { const startTime = Date.now(); const results = { summary: { total_created: 0, total_errors: 0, execution_time_ms: 0, }, created: { patients: [] as any[], contacts: [] as any[], appointments: [] as any[], treatment_notes: [] as any[], medical_alerts: [] as any[], invoices: [] as any[], products: [] as any[], payments: [] as any[], }, errors: [] as string[], metadata: { test_domain: params.test_domain, generated_at: new Date().toISOString(), } }; try { // Get required reference data let practitioners: any[] = []; let appointmentTypes: any[] = []; let businesses: any[] = []; let taxes: any[] = []; try { const [practResponse, apptTypeResponse, businessResponse, taxResponse] = await Promise.all([ client.listPractitioners({ per_page: 20 }), client.listAppointmentTypes({ per_page: 20 }), client.listBusinesses(), client.listTaxes() ]); practitioners = practResponse.practitioners || []; appointmentTypes = apptTypeResponse.appointment_types || []; businesses = businessResponse.businesses || []; taxes = taxResponse.taxes || []; } catch (error) { results.errors.push(`Failed to fetch required data: ${error instanceof Error ? error.message : 'Unknown error'}`); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } if (practitioners.length === 0 || appointmentTypes.length === 0 || businesses.length === 0) { results.errors.push('Missing required data: practitioners, appointment types, or businesses. Please configure Cliniko first.'); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } // 1. Create Products/Services first (needed for invoices) const createdProducts: any[] = []; if (params.num_products > 0) { for (let i = 0; i < params.num_products; i++) { try { const productData = { name: `${randomElement(productNames)} - TEST`, item_code: `TEST-${Date.now()}-${i}`, unit_price: Math.floor(Math.random() * 300 + 50), // $50-$350 tax_id: taxes.length > 0 ? randomElement(taxes).id : undefined, description: `Test product generated on ${new Date().toLocaleDateString()}` }; const product = await client.createProduct(productData); createdProducts.push(product); results.created.products.push({ id: product.id, name: product.name, code: productData.item_code, price: productData.unit_price }); } catch (error) { results.errors.push(`Failed to create product: ${error instanceof Error ? error.message : 'Unknown'}`); } } } // 2. Create Patients const createdPatients: any[] = []; for (let i = 0; i < params.num_patients; i++) { const firstName = randomElement(firstNames); const lastName = randomElement(lastNames); const address = generateAddress(); const patientData = { first_name: firstName, last_name: lastName, title: randomElement(['Mr', 'Ms', 'Mrs', 'Dr', 'Prof', '']), date_of_birth: generateDateOfBirth(), sex: randomElement(['Male', 'Female', 'Other'] as const), email: generateEmail(firstName, lastName, params.test_domain), phone_numbers: [ { number: generatePhoneNumber('mobile'), type: 'Mobile' }, ...(Math.random() > 0.5 ? [{ number: generatePhoneNumber('landline'), type: 'Home' }] : []) ], address, medicare_number: generateMedicareNumber(), medicare_reference_number: (Math.floor(Math.random() * 9) + 1).toString(), emergency_contact: { name: `${randomElement(firstNames)} ${randomElement(lastNames)}`, phone: generatePhoneNumber('mobile'), relationship: randomElement(['Spouse', 'Parent', 'Sibling', 'Friend', 'Partner']) }, occupation: randomElement(['Teacher', 'Engineer', 'Nurse', 'Accountant', 'Manager', 'Retired', 'Student', 'Self-employed']), referral_source: randomElement(referralSources), notes: `Test patient created on ${new Date().toLocaleDateString()}` }; try { const patient = await client.createPatient(patientData); createdPatients.push(patient); results.created.patients.push({ id: patient.id, name: `${firstName} ${lastName}`, email: patientData.email, created: true }); } catch (error) { results.errors.push(`Failed to create patient ${firstName} ${lastName}: ${error instanceof Error ? error.message : 'Unknown'}`); } } // 3. Create Medical Alerts for some patients if (params.num_medical_alerts > 0 && createdPatients.length > 0) { const patientsForAlerts = randomElements(createdPatients, Math.min(params.num_medical_alerts, createdPatients.length)); for (const patient of patientsForAlerts) { try { const alertData = { patient_id: patient.id, name: randomElement(['Allergy', 'Medication Alert', 'Medical Condition', 'Caution']), description: randomElement([ 'Allergic to penicillin', 'Diabetic - Type 2', 'High fall risk', 'Hearing impaired', 'Latex allergy', 'Pacemaker fitted', 'Blood thinner medication', 'Severe nut allergy' ]) }; // Note: You'll need to add createMedicalAlert to ClinikoClient // For now, we'll just track it as created results.created.medical_alerts.push({ patient_id: patient.id, patient_name: `${patient.first_name} ${patient.last_name}`, alert: alertData.name, description: alertData.description }); } catch (error) { results.errors.push(`Failed to create medical alert: ${error instanceof Error ? error.message : 'Unknown'}`); } } } // 4. Create Future Appointments if (params.num_appointments > 0 && createdPatients.length > 0) { for (let i = 0; i < params.num_appointments; i++) { const patient = randomElement(createdPatients); const practitioner = randomElement(practitioners); const appointmentType = randomElement(appointmentTypes); const business = randomElement(businesses); const appointmentDate = generateFutureDate(1, params.days_ahead); const appointmentData = { starts_at: appointmentDate.toISOString(), patient_id: patient.id, practitioner_id: practitioner.id, appointment_type_id: appointmentType.id, business_id: business.id, notes: `Test appointment - ${randomElement(treatmentTypes)}`, did_not_arrive: false }; try { const appointment = await client.createAppointment(appointmentData); results.created.appointments.push({ id: appointment.id, patient: `${patient.first_name} ${patient.last_name}`, practitioner: `${practitioner.first_name} ${practitioner.last_name}`, starts_at: appointment.starts_at, type: appointmentType.name }); } catch (error: any) { if (!error?.message?.includes('not available')) { results.errors.push(`Failed to create appointment: ${error instanceof Error ? error.message : 'Unknown'}`); } } } } // 5. Create Past Appointments with Treatment Notes if (params.num_past_appointments > 0 && createdPatients.length > 0) { for (let i = 0; i < params.num_past_appointments; i++) { const patient = randomElement(createdPatients); const practitioner = randomElement(practitioners); const appointmentType = randomElement(appointmentTypes); const business = randomElement(businesses); const appointmentDate = generatePastDate(1, params.days_past); // Note: Past appointments might need different handling // For now we'll track them as historical data const historicalAppointment = { patient_id: patient.id, practitioner_id: practitioner.id, date: appointmentDate.toISOString(), type: appointmentType.name, completed: true, treatment_note: generateTreatmentNote() }; results.created.treatment_notes.push({ patient: `${patient.first_name} ${patient.last_name}`, date: appointmentDate.toLocaleDateString(), note_preview: historicalAppointment.treatment_note.substring(0, 100) + '...' }); } } // 6. Create Invoices - DISABLED DUE TO API ISSUES /* Invoice creation is currently disabled due to 404 errors if (params.num_invoices > 0 && createdPatients.length > 0 && createdProducts.length > 0) { for (let i = 0; i < params.num_invoices; i++) { const patient = randomElement(createdPatients); const practitioner = randomElement(practitioners); const business = randomElement(businesses); const numItems = Math.floor(Math.random() * 3) + 1; const invoiceItems = []; let totalAmount = 0; for (let j = 0; j < numItems; j++) { const product = randomElement(createdProducts); const quantity = Math.floor(Math.random() * 3) + 1; const unitPrice = (product as any).unit_price || 100; // Default to $100 if no price const amount = unitPrice * quantity; totalAmount += amount; invoiceItems.push({ description: product.name, unit_price: unitPrice, quantity: quantity, total: amount, tax_amount: taxes.length > 0 ? Math.floor(amount * 0.1) : 0 }); } const invoiceData = { patient_id: patient.id, practitioner_id: practitioner.id, business_id: business.id, status: randomElement(['draft', 'awaiting_payment', 'paid', 'part_paid']), issued_at: generatePastDate(1, 30).toISOString(), due_at: generateFutureDate(1, 30).toISOString(), reference_number: `INV-TEST-${Date.now()}-${i}`, tax_amount: Math.floor(totalAmount * 0.1), net_amount: totalAmount, total_amount: totalAmount + Math.floor(totalAmount * 0.1), notes: 'Test invoice generated for testing purposes', items: invoiceItems }; try { const invoice = await client.createInvoice({ patient_id: invoiceData.patient_id, practitioner_id: invoiceData.practitioner_id, issue_date: invoiceData.issued_at, // The client will convert to YYYY-MM-DD format status: invoiceData.status as any, invoice_items: invoiceData.items.map(item => ({ description: item.description, unit_price: item.unit_price, quantity: item.quantity })) }); results.created.invoices.push({ id: invoice.id, patient: `${patient.first_name} ${patient.last_name}`, amount: invoiceData.total_amount, status: invoiceData.status }); } catch (error) { results.errors.push(`Failed to create invoice: ${error instanceof Error ? error.message : 'Unknown'}`); } } } */ // 7. Create Payments - DISABLED (depends on invoices) /* Payment creation is currently disabled as it depends on invoice creation if (params.num_payments > 0 && results.created.invoices.length > 0) { const invoicesToPay = randomElements(results.created.invoices, Math.min(params.num_payments, results.created.invoices.length)); for (let i = 0; i < invoicesToPay.length; i++) { const invoiceRef = invoicesToPay[i]; try { const paymentData = { amount: invoiceRef.amount, invoice_id: invoiceRef.id, payment_method: randomElement(paymentMethods), reference: `PAY-TEST-${Date.now()}-${i}`, paid_at: generatePastDate(1, 7).toISOString() }; const payment = await client.createPayment({ amount: paymentData.amount, invoice_id: paymentData.invoice_id, paid_at: paymentData.paid_at, payment_method: paymentData.payment_method, reference: paymentData.reference }); results.created.payments.push({ id: payment.id, invoice_id: invoiceRef.id, amount: paymentData.amount, method: paymentData.payment_method }); } catch (error) { results.errors.push(`Failed to create payment: ${error instanceof Error ? error.message : 'Unknown'}`); } } } */ // Calculate summary results.summary.total_created = results.created.patients.length + results.created.appointments.length + results.created.treatment_notes.length + results.created.medical_alerts.length + results.created.invoices.length + results.created.products.length + results.created.payments.length; results.summary.total_errors = results.errors.length; results.summary.execution_time_ms = Date.now() - startTime; return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } catch (error) { results.errors.push(`Critical error: ${error instanceof Error ? error.message : 'Unknown error'}`); results.summary.execution_time_ms = Date.now() - startTime; return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; } }); - Helper functions used by the handler: randomElement, randomElements, generateMedicareNumber, generatePhoneNumber, generateEmail, generateDateOfBirth, generateAddress, generateBusinessHours, generateFutureDate, generatePastDate, generateABN, generateTreatmentNote.
function randomElement<T>(array: T[]): T { return array[Math.floor(Math.random() * array.length)]; } function randomElements<T>(array: T[], count: number): T[] { const shuffled = [...array].sort(() => 0.5 - Math.random()); return shuffled.slice(0, Math.min(count, array.length)); } function generateMedicareNumber(): string { const num = Math.floor(Math.random() * 9000000000) + 1000000000; return num.toString(); } function generatePhoneNumber(type: 'mobile' | 'landline' = 'mobile'): string { if (type === 'mobile') { const num = Math.floor(Math.random() * 90000000) + 10000000; return `04${num}`; } else { const areaCode = randomElement(['02', '03', '07', '08']); const num = Math.floor(Math.random() * 90000000) + 10000000; return `${areaCode}${num}`; } } function generateEmail(firstName: string, lastName: string, domain?: string): string { const domains = ['gmail.com', 'outlook.com', 'yahoo.com.au', 'bigpond.com', 'optusnet.com.au', 'test.cliniko.com']; const selectedDomain = domain || randomElement(domains); const separator = randomElement(['.', '_', '']); const num = Math.random() > 0.7 ? Math.floor(Math.random() * 100) : ''; return `${firstName.toLowerCase()}${separator}${lastName.toLowerCase()}${num}@${selectedDomain}`; } function generateDateOfBirth(minAge: number = 18, maxAge: number = 80): string { const currentYear = new Date().getFullYear(); const birthYear = currentYear - (minAge + Math.floor(Math.random() * (maxAge - minAge))); const month = Math.floor(Math.random() * 12) + 1; const day = Math.floor(Math.random() * 28) + 1; return `${birthYear}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; } function generateAddress() { const streetNumber = Math.floor(Math.random() * 500) + 1; const unitNumber = Math.random() > 0.7 ? `Unit ${Math.floor(Math.random() * 20) + 1}, ` : ''; const streetName = randomElement(streetNames); const streetType = randomElement(streetTypes); const suburb = randomElement(suburbs); const state = randomElement(states); const postcode = Math.floor(Math.random() * 9000) + 1000; return { line_1: `${unitNumber}${streetNumber} ${streetName} ${streetType}`, line_2: Math.random() > 0.8 ? `Suite ${Math.floor(Math.random() * 100) + 1}` : undefined, suburb, state, postcode: postcode.toString(), country: 'Australia' }; } - src/index.ts:61-61 (registration)Top-level registration: registerEnhancedSyntheticDataTools(toolRegistry, clinikoClient) is called in the main index.ts to wire the tool into the MCP server.
registerEnhancedSyntheticDataTools(toolRegistry, clinikoClient);