Skip to main content

Overview

The /ws/ai WebSocket channel provides real-time notifications to AI analysis systems when surgeons complete surgical simulations. This enables immediate AI processing and feedback generation without polling the REST API.

Connection

Endpoint

ws://localhost:8080/ws/ai?token=<jwt-token>

Authentication

Requires a valid JWT token with ROLE_AI (or ROLE_IA). Surgeon accounts cannot connect to this channel.

Connection Example

const aiToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const ws = new WebSocket(`ws://localhost:8080/ws/ai?token=${aiToken}`);

ws.onopen = () => {
  console.log('🤖 AI system connected');
};

ws.onmessage = (event) => {
  const notification = JSON.parse(event.data);
  console.log('🔔 Received notification:', notification);
  
  if (notification.event === 'NEW_SURGERY') {
    handleNewSurgery(notification.surgeryId);
  }
};

ws.onerror = (error) => {
  console.error('❌ WebSocket error:', error);
};

ws.onclose = (event) => {
  console.log(`🔌 Disconnected: ${event.code}`);
};

Message Protocol

Server → Client: Connection Confirmation

Immediately after successful connection, the server sends a confirmation message:
status
string
Connection status, always "connected"
message
string
Confirmation message
Connection Confirmation
{
  "status": "connected",
  "message": "IA conectada exitosamente"
}

Server → Client: Surgery Notification

When a surgeon completes a simulation (sends FINISH event), the AI channel receives:
event
string
Event type, always "NEW_SURGERY"
surgeryId
UUID
Unique identifier of the completed surgery
New Surgery Notification
{
  "event": "NEW_SURGERY",
  "surgeryId": "550e8400-e29b-41d4-a716-446655440000"
}

Client → Server: Optional Messages

AI systems can send messages to the server (currently for logging/debugging):
// Send acknowledgment (optional)
ws.send(JSON.stringify({
  "status": "PROCESSING",
  "surgeryId": "550e8400-e29b-41d4-a716-446655440000"
}));
The backend logs client messages but does not process them. This feature is reserved for future bidirectional communication.

Complete AI Workflow

1

AI System Connects

Establish persistent WebSocket connection with ROLE_AI token
2

Receive Confirmation

Server confirms connection with connected status message
3

Wait for Notifications

Listen for NEW_SURGERY events (blocking/event-driven)
4

Fetch Trajectory

On notification, call REST API: GET /api/v1/surgeries/{id}/trajectory
5

Analyze Surgery

Process trajectory data:
  • Calculate performance metrics
  • Detect errors (tumor touches, hemorrhages)
  • Evaluate movement smoothness
  • Generate textual feedback
6

Submit Analysis

Call REST API: POST /api/v1/surgeries/{id}/analysis
7

Continue Listening

Return to waiting for next NEW_SURGERY notification

Full Implementation Example

import axios from 'axios';

class AIAnalysisSystem {
  constructor(aiToken, apiBaseUrl) {
    this.token = aiToken;
    this.apiBaseUrl = apiBaseUrl;
    this.ws = null;
  }

  connect() {
    this.ws = new WebSocket(`ws://localhost:8080/ws/ai?token=${this.token}`);

    this.ws.onopen = () => {
      console.log('🤖 AI system connected and ready');
    };

    this.ws.onmessage = async (event) => {
      const notification = JSON.parse(event.data);

      // Handle connection confirmation
      if (notification.status === 'connected') {
        console.log('✅', notification.message);
        return;
      }

      // Handle new surgery notification
      if (notification.event === 'NEW_SURGERY') {
        console.log(`🔔 New surgery: ${notification.surgeryId}`);
        await this.processSurgery(notification.surgeryId);
      }
    };

    this.ws.onerror = (error) => {
      console.error('❌ WebSocket error:', error);
    };

    this.ws.onclose = (event) => {
      console.log('🔌 Disconnected, reconnecting in 5s...');
      setTimeout(() => this.connect(), 5000);
    };
  }

  async processSurgery(surgeryId) {
    try {
      // 1. Fetch trajectory data
      console.log(`📥 Fetching trajectory for ${surgeryId}`);
      const response = await axios.get(
        `${this.apiBaseUrl}/surgeries/${surgeryId}/trajectory`,
        {
          headers: { Authorization: `Bearer ${this.token}` }
        }
      );

      const trajectory = response.data;

      // 2. Analyze the surgery
      console.log(`🧠 Analyzing ${trajectory.movements.length} movements...`);
      const analysis = this.analyzeSurgery(trajectory);

      // 3. Submit analysis
      console.log(`📤 Submitting analysis (score: ${analysis.score})`);
      await axios.post(
        `${this.apiBaseUrl}/surgeries/${surgeryId}/analysis`,
        analysis,
        {
          headers: { Authorization: `Bearer ${this.token}` }
        }
      );

      console.log(`✅ Analysis complete for ${surgeryId}`);
    } catch (error) {
      console.error(`❌ Error processing surgery ${surgeryId}:`, error.message);
    }
  }

  analyzeSurgery(trajectory) {
    // Simple scoring algorithm (replace with actual AI model)
    const movements = trajectory.movements;
    const duration = new Date(trajectory.endTime) - new Date(trajectory.startTime);
    const durationMinutes = duration / 60000;

    // Count events
    const tumorTouches = movements.filter(m => m.event === 'TUMOR_TOUCH').length;
    const hemorrhages = movements.filter(m => m.event === 'HEMORRHAGE').length;

    // Calculate score (0-100)
    let score = 100;
    score -= tumorTouches * 10; // -10 per tumor touch
    score -= hemorrhages * 20;   // -20 per hemorrhage
    score -= Math.max(0, durationMinutes - 15) * 2; // -2 per minute over 15
    score = Math.max(0, Math.min(100, score));

    // Generate feedback
    const feedback = `
      Surgery completed in ${durationMinutes.toFixed(1)} minutes.
      Tumor contacts: ${tumorTouches}
      Hemorrhage events: ${hemorrhages}
      
      ${score >= 90 ? 'Excellent performance!' : ''}
      ${score >= 70 && score < 90 ? 'Good performance with room for improvement.' : ''}
      ${score < 70 ? 'Practice recommended to improve precision and speed.' : ''}
      
      ${tumorTouches > 0 ? 'Minimize tumor contact to prevent spread.' : ''}
      ${hemorrhages > 0 ? 'Focus on vascular control techniques.' : ''}
    `.trim();

    return { score, feedback };
  }
}

// Usage
const aiSystem = new AIAnalysisSystem(
  'your-ai-jwt-token',
  'http://localhost:8080/api/v1'
);
aiSystem.connect();

Error Handling

Authentication Errors

Invalid token or wrong role results in immediate disconnection:
ws.onclose = (event) => {
  if (event.code === 1008) {
    console.error('❌ Authentication failed: ROLE_AI token required');
    // AI token may have expired, refresh and reconnect
  }
};

Reconnection Strategy

Implement automatic reconnection with exponential backoff:
class AIWebSocketClient {
  constructor(token) {
    this.token = token;
    this.reconnectDelay = 1000;
    this.maxReconnectDelay = 30000;
  }

  connect() {
    this.ws = new WebSocket(`ws://localhost:8080/ws/ai?token=${this.token}`);

    this.ws.onopen = () => {
      console.log('✅ Connected');
      this.reconnectDelay = 1000; // Reset delay on success
    };

    this.ws.onclose = () => {
      console.log(`🔄 Reconnecting in ${this.reconnectDelay / 1000}s...`);
      setTimeout(() => this.connect(), this.reconnectDelay);
      
      // Exponential backoff
      this.reconnectDelay = Math.min(
        this.reconnectDelay * 2,
        this.maxReconnectDelay
      );
    };
  }
}

Backend Implementation

The AI channel handler (AIWebSocketHandler.java) performs:
1

Validate Role

Check that connecting user has ROLE_AI
2

Register Session

Add WebSocket session to active AI sessions map
3

Send Confirmation

Send connection confirmation message
4

Listen for Notifications

Static method notificarNuevaCirugia() broadcasts to all AI sessions
5

Handle Disconnection

Remove session from map on close

Notification Trigger

Notifications are sent from SimulationWebSocketHandler.java when a surgery finishes:
// When FINISH event is received:
if (movement.event() == SurgeryEvent.FINISH) {
    surgery.endSurgery();
    surgeryRepository.save(surgery);
    activeSessions.remove(session.getId());

    // Send confirmation to surgeon
    String response = String.format(
        "{\"status\":\"SAVED\", \"surgeryId\":\"%s\"}",
        surgery.getId()
    );
    session.sendMessage(new TextMessage(response));

    // Notify all connected AI systems
    AIWebSocketHandler.notificarNuevaCirugia(surgery.getId());
}

Multi-AI Support

The channel supports multiple concurrent AI connections:
  • All connected AI systems receive notifications
  • Each AI can process surgeries independently
  • Useful for:
    • Load balancing across AI instances
    • Running multiple AI models simultaneously
    • Development/staging environments
If multiple AI systems submit analysis for the same surgery, the last submission overwrites previous results.

Performance Considerations

Notification Latency

Notifications are sent immediately after surgery persistence:
  • Typical latency: < 100ms
  • No polling delay
  • Synchronous broadcast to all AI sessions

Connection Stability

Recommendations for production:
  • Implement heartbeat/ping mechanism
  • Monitor connection health
  • Log all notifications for audit trail
  • Handle temporary network interruptions gracefully

Next Steps

Surgery Endpoints

Fetch trajectory data and submit AI analysis

Simulation Channel

Learn how surgeons stream telemetry data

Build docs developers (and LLMs) love