Complete Diagnostic Workflow: From API Cases to Components Integration#

Overview#

This comprehensive tutorial demonstrates how to create diagnostic cases using the GekkoVet API’s cases endpoint, monitor their processing, and then seamlessly integrate them with the GekkoVet diagnostics component for interactive diagnosis workflows. This approach allows you to leverage the power of AI-driven case processing while providing users with an intuitive interface for continuing the diagnostic process.

Prerequisites#

  • Valid GekkoVet API credentials with appropriate scopes
  • Understanding of OAuth2 authentication
  • Basic knowledge of iframe integration and cross-frame communication

Required Scopes#

For the complete workflow, you’ll need the following OAuth2 scopes:

  • gekkovet/read - For reading case data and API resources
  • gekkovet/write and gekkovet/user.write - For creating and updating cases
  • gekkovet/user.read - For listing user’s cases
  • gekkovet/tools - For accessing components API

Part 1: Creating and Processing Diagnostic Cases#

Authentication#

All requests require OAuth2 authentication. Include the access token in the Authorization header:

Authorization: Bearer YOUR_ACCESS_TOKEN

Understanding Case States#

Cases progress through several states during AI processing:

  • PENDING - Case created and waiting for AI processing
  • PROCESSING - Case is being processed by AI services
  • INITIAL_AI_PROCESSING_DONE - Patient extracted and initial symptoms identified
  • SANITIZING - AI responses are being validated and refined
  • COMPLETED - Case processing completed successfully
  • ERROR - An error occurred during processing

Step 1: Create a Diagnostic Case#

Create a new case by sending a POST request to /v1/cases with veterinary case notes:

POST /v1/cases
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
  "notes": "A 5-year-old male Golden Retriever named Max presented with vomiting and diarrhea for the past 2 days. The owner reports lethargy and decreased appetite. No recent dietary changes. Physical examination reveals mild dehydration and abdominal discomfort. Weight: 28kg. Temperature: 39.2°C."
}

Response (201 Created):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "notes": "A 5-year-old male Golden Retriever named Max...",
  "status": "PENDING",
  "created_at": "2025-06-02T10:00:00Z",
  "updated_at": "2025-06-02T10:00:00Z",
  "selected_symptoms": [],
  "de_selected_symptoms": [],
  "attachment_ids": []
}

Step 2: Monitor Case Processing#

The AI processing typically takes 10-30 seconds. Poll the case status as it progresses through different states:

GET /v1/cases/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer YOUR_ACCESS_TOKEN

During INITIAL_AI_PROCESSING_DONE state:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "INITIAL_AI_PROCESSING_DONE",
  "notes": "A 5-year-old male Golden Retriever named Max...",
  "ai_extracted_patient": {
    "animal_type_id": "1",
    "breed": "Golden Retriever",
    "name": "Max",
    "gender": "male",
    "weight": "28",
    "neutered": false
  },
  "suggested_symptoms": [
    {
      "symptom": {
        "id": 42,
        "symptom_name": "Vomiting",
        "category_name": "Gastrointestinal",
        "_distance": 0.0
      },
      "key_phrases": ["vomiting"]
    },
    {
      "symptom": {
        "id": 45,
        "symptom_name": "Diarrhea",
        "category_name": "Gastrointestinal",
        "_distance": 0.0
      },
      "key_phrases": ["diarrhea"]
    }
  ],
  "raw_ai_response": {
    "animal": { },
    "symptoms": [ ]
  },
  "updated_at": "2025-06-02T10:01:30Z"
}

Final COMPLETED state:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "COMPLETED",
  "notes": "A 5-year-old male Golden Retriever named Max...",
  "patient": {
    "animal_type_id": 1,
    "breed_id": 15,
    "name": "Max",
    "gender_id": 1,
    "weight_kg": 28.0,
    "neutered": false
  },
  "ai_extracted_patient": { },
  "suggested_symptoms": [ ],
  "sanitized_symptoms": [ ],
  "selected_symptoms": [
    {
      "id": 42,
      "significant": false
    },
    {
      "id": 45,
      "significant": false
    }
  ],
  "de_selected_symptoms": [],
  "updated_at": "2025-06-02T10:02:00Z"
}

Step 3: Implementing Polling Logic#

Implement proper polling logic that handles all case states:

async function pollCaseStatus(caseId, maxAttempts = 20) {
  let attempt = 0;
  let delay = 1000; // Start with 1 second

  while (attempt < maxAttempts) {
    const response = await fetch(`/v1/cases/${caseId}`, {
      headers: {
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
      }
    });
    const caseData = await response.json();

    console.log(`Case status: ${caseData.status}`);

    // Handle different states
    switch(caseData.status) {
      case 'COMPLETED':
        console.log('Case processing complete!');
        return caseData;

      case 'ERROR':
        console.error('Case processing failed:', caseData.error);
        throw new Error(`Case processing failed: ${caseData.error}`);

      case 'PENDING':
        console.log('Waiting for AI processing to begin...');
        break;

      case 'PROCESSING':
        console.log('AI is analyzing case notes...');
        break;

      case 'INITIAL_AI_PROCESSING_DONE':
        console.log('Patient and symptoms extracted, continuing processing...');
        break;

      case 'SANITIZING':
        console.log('Validating and refining AI results...');
        break;
    }

    // Wait before next poll with exponential backoff
    await new Promise(resolve => setTimeout(resolve, delay));
    delay = Math.min(delay * 1.5, 30000); // Max 30 seconds
    attempt++;
  }

  throw new Error('Case processing timeout - please check case status manually');
}

Part 2: Creating Component Sessions with Case ID#

**Automatic Data Synchronization:** When a component is initialized with a `case_id`, it automatically syncs all data back to the backend case in real-time. You don't need to manually store data back to the case - the component handles this automatically while still firing callback events for real-time updates.

Once your case is processed, you can create a component session that references the case ID. This automatically loads all the processed information and sets up automatic data synchronization:

POST https://components.gekkovet.com/v1/sessions
Content-Type: application/json
Authorization: Bearer YOUR_JWT_TOKEN

{
  "component": "diagnostics",
  "case_id": "550e8400-e29b-41d4-a716-446655440000"
}

Response:

{
  "session_id": "sess_abc123def456",
  "url": "https://components.gekkovet.com/components/diagnostics/sess_abc123def456"
}

Part 3: Embedding and Cross-Frame Communication#

Embed the component in your application and set up event listening:

<iframe
  id="diagnostics-component"
  src="https://components.gekkovet.com/components/diagnostics/sess_abc123def456"
  width="100%"
  height="800px">
</iframe>

Event Handling#

The component will still fire real-time events for UI updates, but data persistence is handled automatically:

// Listen for events from the component
window.addEventListener('message', (event) => {
  // Security: Verify origin
  if (!event.origin.startsWith('https://components.gekkovet.com')) {
    return;
  }

  const data = JSON.parse(event.data);

  switch(data.type) {
    case 'init':
      console.log('Component initialized with case data');
      break;
    case 'symptomSelected':
      console.log('Symptom selected:', data.payload.name);
      // Data is automatically saved to the case - no manual sync needed
      break;
    case 'diagnoseSelected':
      console.log('Diagnosis selected:', data.payload.name);
      // Data is automatically saved to the case - no manual sync needed
      break;
    case 'save':
      console.log('User explicitly saved - case data updated automatically');
      // Optional: Show save confirmation to user
      showSaveConfirmation();
      break;
  }
});

Part 4: Complete Implementation Example#

Here’s a complete class that implements the entire workflow:

class DiagnosticWorkflow {
  constructor(apiToken, componentToken) {
    this.apiToken = apiToken;
    this.componentToken = componentToken;
    this.caseId = null;
    this.sessionId = null;
  }

  async createCase(clinicalNotes) {
    const response = await fetch('/v1/cases', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiToken}`
      },
      body: JSON.stringify({ notes: clinicalNotes })
    });

    const case_data = await response.json();
    this.caseId = case_data.id;

    // Wait for processing
    await this.waitForProcessing();
    return case_data;
  }

  async waitForProcessing() {
    const maxAttempts = 30;
    const delayMs = 2000;

    for (let i = 0; i < maxAttempts; i++) {
      const response = await fetch(`/v1/cases/${this.caseId}`, {
        headers: {
          'Authorization': `Bearer ${this.apiToken}`
        }
      });

      const case_data = await response.json();

      if (case_data.status === 'COMPLETED') {
        return case_data;
      }

      await new Promise(resolve => setTimeout(resolve, delayMs));
    }

    throw new Error('Case processing timeout');
  }

  async createComponentSession() {
    const response = await fetch('https://components.gekkovet.com/v1/sessions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.componentToken}`
      },
      body: JSON.stringify({
        component: 'diagnostics',
        case_id: this.caseId
      })
    });

    const session = await response.json();
    this.sessionId = session.session_id;
    return session;
  }

  embedComponent(containerId) {
    const iframe = document.createElement('iframe');
    iframe.src = `https://components.gekkovet.com/components/diagnostics/${this.sessionId}`;
    iframe.width = '100%';
    iframe.height = '800px';

    document.getElementById(containerId).appendChild(iframe);

    // Set up event listening
    window.addEventListener('message', this.handleComponentEvent.bind(this));
  }

  handleComponentEvent(event) {
    if (!event.origin.startsWith('https://components.gekkovet.com')) {
      return;
    }

    const data = JSON.parse(event.data);

    switch(data.type) {
      case 'save':
        // Data is automatically synced to the case - just show confirmation
        this.showSaveConfirmation();
        break;
      case 'soapNotesGenerated':
        this.handleSoapNotes(data.payload.notes);
        break;
      case 'symptomSelected':
        // Real-time updates for UI - data automatically saved
        this.updateUI('symptom', data.payload);
        break;
    }
  }

  showSaveConfirmation() {
    // Show user feedback that data was saved
    console.log('Case data automatically synchronized');
  }

  updateUI(type, payload) {
    // Update your UI in real-time based on component events
    // Data persistence is handled automatically by the component
  }
}

// Usage example
async function runDiagnosticWorkflow() {
  const workflow = new DiagnosticWorkflow(API_TOKEN, COMPONENT_TOKEN);

  // Create and process case
  const clinicalNotes = "5-year-old Golden Retriever with vomiting and diarrhea...";
  await workflow.createCase(clinicalNotes);

  // Create component session
  await workflow.createComponentSession();

  // Embed in page
  workflow.embedComponent('diagnostic-container');
}

Best Practices#

  • Error Handling: Always implement proper error handling for API calls and component events
  • Rate Limiting: Respect API rate limits when polling for case status
  • Security: Always validate message origins in cross-frame communication
  • User Experience: Provide loading indicators during case processing
  • Automatic Data Sync: When using case_id, take advantage of automatic data synchronization - use events for real-time UI updates while the component handles data persistence
  • Performance: Initialize components with case_id when possible to reduce manual data synchronization overhead

Troubleshooting#

Case Processing Issues#

  • Case stuck in PENDING: Verify case has substantial notes content
  • Missing patient information: Ensure notes contain clear animal details
  • Symptoms not extracted: Use recognizable veterinary terminology

Component Integration Issues#

  • Iframe not loading: Check session URL and authentication
  • Events not received: Verify message event listener and origin checking
  • Cross-origin errors: Ensure proper CORS configuration
**Next Steps:** With both API cases and components integrated, you can build comprehensive diagnostic workflows, create seamless user experiences, leverage AI-powered case processing, maintain data consistency across systems, and generate complete diagnostic documentation.