Skip to main content

Architecture Overview

Justina is built on a Clean/Hexagonal Architecture pattern, promoting separation of concerns, testability, and maintainability. The system consists of three major components communicating via REST APIs and WebSocket connections.

Clean Architecture Principles

The backend follows Robert C. Martin’s Clean Architecture with four distinct layers:

Layer Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     PRESENTATION LAYER                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ REST Controllersβ”‚  β”‚ WebSocket       β”‚  β”‚ Security            β”‚ β”‚
β”‚  β”‚                 β”‚  β”‚ Handlers        β”‚  β”‚ Filters             β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚                     β”‚                     β”‚
            β–Ό                     β–Ό                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      APPLICATION LAYER                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                    Application Services                       β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚  β”‚
β”‚  β”‚  β”‚ AuthService β”‚  β”‚SurgeryServiceβ”‚ β”‚ TelemetryService   β”‚   β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                           PORTS (Interfaces)                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        DOMAIN LAYER                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Core Models   β”‚  β”‚      DTOs      β”‚  β”‚   Domain Logic       β”‚  β”‚
β”‚  β”‚  - User        β”‚  β”‚- AuthResponse  β”‚  β”‚  - Business Rules    β”‚  β”‚
β”‚  β”‚  - Surgery     β”‚  β”‚- TelemetryDTO  β”‚  β”‚  - Validations       β”‚  β”‚
β”‚  β”‚  - Movement    β”‚  β”‚- TrajectoryDTO β”‚  β”‚  - Calculations      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚               Repository Interfaces (Ports)                   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    INFRASTRUCTURE LAYER                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚                        ADAPTERS                               β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚
β”‚  β”‚  β”‚ JPA/Hibernate   β”‚  β”‚ JWT Security    β”‚  β”‚ WebSocket   β”‚  β”‚  β”‚
β”‚  β”‚  β”‚ PostgreSQL/H2   β”‚  β”‚ BCrypt Encoder  β”‚  β”‚ STOMP       β”‚  β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Benefits of This Architecture

Independence

Business logic is independent of frameworks, UI, database, and external agencies

Testability

Business rules can be tested without UI, database, web server, or external elements

UI Independence

UI can change without changing the rest of the system (e.g., swap React for Angular)

Database Independence

Can swap H2 for PostgreSQL without touching business logic

Backend Architecture

Technology Stack

ComponentTechnologyVersionPurpose
FrameworkSpring Boot4.0.2Application framework
LanguageJava21Primary language
Build ToolMaven3.9+Dependency management
ORMHibernate (JPA)-Database abstraction
SecuritySpring Security + JWT-Authentication & authorization
WebSocketSpring WebSocket-Real-time communication
API DocsSpringDoc OpenAPI2.8.6API documentation
Database (Dev)H2-In-memory database
Database (Prod)PostgreSQL15+Production database

Project Structure

src/main/java/project/Justina/
β”œβ”€β”€ JustinaApplication.java              // Application entry point
β”‚
β”œβ”€β”€ application/                          // APPLICATION LAYER
β”‚   β”œβ”€β”€ ports/                           // Port interfaces
β”‚   └── service/                         // Business logic services
β”‚       β”œβ”€β”€ AuthService.java            // Authentication service
β”‚       └── SurgeryService.java         // Surgery management
β”‚
β”œβ”€β”€ domain/                              // DOMAIN LAYER
β”‚   β”œβ”€β”€ model/                          // Core business entities
β”‚   β”‚   β”œβ”€β”€ User.java                   // User entity
β”‚   β”‚   β”œβ”€β”€ SurgerySession.java         // Surgery session
β”‚   β”‚   β”œβ”€β”€ Movement.java               // Movement point
β”‚   β”‚   └── SurgeryEvent.java          // Critical events
β”‚   β”œβ”€β”€ dto/                            // Data transfer objects
β”‚   β”‚   β”œβ”€β”€ LoginRequestDTO.java
β”‚   β”‚   β”œβ”€β”€ AuthResponseDTO.java
β”‚   β”‚   β”œβ”€β”€ TelemetryDTO.java
β”‚   β”‚   └── TrajectoryDTO.java
β”‚   β”œβ”€β”€ repository/                     // Repository interfaces
β”‚   β”‚   β”œβ”€β”€ UserRepository.java
β”‚   β”‚   └── SurgeryRepository.java
β”‚   └── exception/                      // Domain exceptions
β”‚       β”œβ”€β”€ AuthException.java
β”‚       └── SurgeryNotFoundException.java
β”‚
└── infrastructure/                      // INFRASTRUCTURE LAYER
    β”œβ”€β”€ adapter/                        // Implementation adapters
    β”‚   β”œβ”€β”€ entity/                     // JPA entities
    β”‚   β”œβ”€β”€ mapper/                     // Entity-DTO mappers
    β”‚   └── repository/                 // JPA repository implementations
    β”œβ”€β”€ config/                         // Spring configuration
    β”‚   β”œβ”€β”€ DataInitializer.java       // Initialize default users
    β”‚   └── OpenApiConfig.java         // Swagger configuration
    β”œβ”€β”€ controller/                     // REST endpoints
    β”‚   β”œβ”€β”€ AuthController.java        // Authentication endpoints
    β”‚   β”œβ”€β”€ SurgeryController.java     // Surgery endpoints
    β”‚   └── GlobalExceptionHandler.java // Error handling
    β”œβ”€β”€ security/                       // Security infrastructure
    β”‚   β”œβ”€β”€ SecurityConfig.java        // Security configuration
    β”‚   β”œβ”€β”€ JwtService.java            // JWT token management
    β”‚   β”œβ”€β”€ JwtAuthenticationFilter.java // JWT validation filter
    β”‚   └── ApplicationConfig.java     // Bean configurations
    └── websocket/                      // WebSocket infrastructure
        β”œβ”€β”€ WebSocketConfig.java       // WebSocket configuration
        β”œβ”€β”€ SimulationWebSocketHandler.java // Surgeon connection
        β”œβ”€β”€ AIWebSocketHandler.java    // AI service connection
        └── HandshakeInterceptorImpl.java // WebSocket security

Key Design Patterns

1. Dependency Injection

@Service
public class AuthService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtService jwtService;
    
    // Constructor injection (Spring auto-wires dependencies)
    @Autowired
    public AuthService(
        UserRepository userRepository,
        PasswordEncoder passwordEncoder,
        JwtService jwtService
    ) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.jwtService = jwtService;
    }
    
    public AuthResponseDTO login(LoginRequestDTO request) {
        // Business logic...
    }
}

2. Repository Pattern

// Domain layer - Interface (Port)
public interface UserRepository {
    Optional<User> findByUsername(String username);
    User save(User user);
}

// Infrastructure layer - Implementation (Adapter)
@Repository
public interface UserJpaRepository 
    extends JpaRepository<UserEntity, Long>, UserRepository {
    // JPA provides implementation automatically
}

3. DTO Pattern

Separate internal domain models from external API contracts:
// Domain model (internal)
@Entity
public class User {
    private Long id;
    private String username;
    private String passwordHash;
    private String role;
    // Internal implementation details
}

// DTO (external API)
public record AuthResponseDTO(
    String token,
    String username,
    String role
) {
    // Only expose what clients need
}

Frontend Architecture

Technology Stack

ComponentTechnologyVersionPurpose
FrameworkNext.js16.1.6React framework
LanguageTypeScript5.xType-safe JavaScript
UI LibraryReact19.2.3Component library
3D EngineBabylon.js8.51.2WebGL 3D rendering
State ManagementZustand5.0.11Global state
WebSocketSTOMP.js7.3.0WebSocket protocol
UI ComponentsRadix UI-Accessible components
StylingTailwind CSS4.1.18Utility-first CSS

Application Structure

frontend/
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ components/                      // React components
β”‚   β”‚   β”œβ”€β”€ Scene.tsx                   // Babylon.js 3D scene
β”‚   β”‚   β”œβ”€β”€ UserDropdown.tsx            // User menu
β”‚   β”‚   β”œβ”€β”€ nav.tsx                     // Navigation bar
β”‚   β”‚   β”œβ”€β”€ dashboard/
β”‚   β”‚   β”‚   └── ModuleCard.tsx         // Dashboard cards
β”‚   β”‚   └── ui/                         // Reusable UI components
β”‚   β”‚       β”œβ”€β”€ button.tsx
β”‚   β”‚       β”œβ”€β”€ card.tsx
β”‚   β”‚       └── input.tsx
β”‚   β”‚
β”‚   β”œβ”€β”€ lib/                            // Utility libraries
β”‚   β”‚   β”œβ”€β”€ api.ts                     // REST API client
β”‚   β”‚   β”œβ”€β”€ websocketConfig.ts         // WebSocket setup
β”‚   β”‚   β”œβ”€β”€ enviarTelemetria.ts        // Telemetry sender
β”‚   β”‚   └── config.ts                  // Environment config
β”‚   β”‚
β”‚   β”œβ”€β”€ store/                          // State management
β”‚   β”‚   └── surgeryStore.ts            // Surgery state (Zustand)
β”‚   β”‚
β”‚   β”œβ”€β”€ login/
β”‚   β”‚   β”œβ”€β”€ page.tsx                   // Login page
β”‚   β”‚   └── login.actions.ts           // Login logic
β”‚   β”‚
β”‚   β”œβ”€β”€ dashboard/
β”‚   β”‚   β”œβ”€β”€ page.tsx                   // Dashboard page
β”‚   β”‚   └── module.data.ts             // Module definitions
β”‚   β”‚
β”‚   β”œβ”€β”€ layout.tsx                      // Root layout
β”‚   └── page.tsx                        // Home page
β”‚
β”œβ”€β”€ public/                              // Static assets
β”œβ”€β”€ package.json                         // Dependencies
└── tsconfig.json                        // TypeScript config

3D Simulation with Babylon.js

The surgical simulation is powered by Babylon.js, a powerful WebGL 3D engine.
import * as BABYLON from "@babylonjs/core";
import { useEffect, useRef } from "react";

export default function BabylonScene() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const websocketRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    if (!canvasRef.current) return;

    // Create Babylon.js engine
    const engine = new BABYLON.Engine(canvasRef.current, true);
    const scene = new BABYLON.Scene(engine);

    // Setup camera, lights, and 3D objects
    const camera = new BABYLON.ArcRotateCamera(
      "camera",
      Math.PI / 2,
      Math.PI / 2,
      10,
      BABYLON.Vector3.Zero(),
      scene
    );
    camera.attachControl(canvasRef.current, true);

    // Load surgical models, instruments, etc.
    // ...

    // Render loop
    engine.runRenderLoop(() => {
      scene.render();
    });

    // Connect WebSocket for telemetry
    conectarWebSocket();

    return () => {
      engine.dispose();
      websocketRef.current?.close();
    };
  }, []);

  return <canvas ref={canvasRef} className="w-full h-full" />;
}

WebSocket Integration

Real-time telemetry streaming from the 3D simulation:
let websocket: WebSocket | null = null;

export function conectarWebSocket(token: string) {
  const WS_URL = process.env.NEXT_PUBLIC_WS_URL;
  websocket = new WebSocket(`${WS_URL}/ws/simulation?token=${token}`);

  websocket.onopen = () => {
    console.log('πŸ”Œ WebSocket connected - Simulation started');
    // Send START event
    enviarTelemetria(0, 0, 0, 'START');
  };

  websocket.onmessage = (event) => {
    const message = JSON.parse(event.data);
    if (message.status === 'SAVED') {
      console.log('πŸ’Ύ Surgery saved:', message.surgeryId);
      localStorage.setItem('lastSurgeryId', message.surgeryId);
    }
  };

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

  websocket.onclose = () => {
    console.log('πŸ”Œ WebSocket disconnected');
  };
}

export function enviarTelemetria(
  x: number,
  y: number,
  z: number,
  event: string
) {
  if (websocket?.readyState === WebSocket.OPEN) {
    const telemetria = {
      coordinates: { x, y, z },
      event: event, // START, MOVE, FINISH
      timestamp: new Date().toISOString()
    };
    websocket.send(JSON.stringify(telemetria));
  }
}

State Management

Using Zustand for lightweight, performant state management:
import { create } from 'zustand';

interface SurgeryStore {
  currentEvent: string;
  surgeryId: string | null;
  setEvent: (event: string) => void;
  setSurgeryId: (id: string) => void;
}

export const useSurgeryStore = create<SurgeryStore>((set) => ({
  currentEvent: 'IDLE',
  surgeryId: null,
  setEvent: (event) => set({ currentEvent: event }),
  setSurgeryId: (id) => set({ surgeryId: id })
}));

AI Service Architecture

Technology Stack

ComponentTechnologyPurpose
LanguagePython 3.xCore language
HTTP ClientrequestsREST API calls
Data Processingpandas, numpyTrajectory analysis
WebSocketwebsocket-clientReal-time notifications
Web ServerFlaskHealth check endpoint
Configpython-dotenvEnvironment variables

Pipeline Architecture

The AI service implements a 5-step analysis pipeline for surgical evaluation:
def run_pipeline(trajectory_data: Dict) -> Tuple[float, str]:
    """
    COMPLETE 5-STEP ANALYSIS PIPELINE
    """
    # STEP 1: Data Ingestion & Cleaning
    df = _paso1_ingestar_y_limpiar(trajectory_data)
    
    # STEP 2: Dexterity Metrics (Physics-based)
    metricas = _paso2_calcular_destreza(df)
    
    # STEP 3: Benchmarking Against Gold Standard
    benchmarking = _paso3_benchmarking(df)
    
    # STEP 4: Risk Analysis
    riesgo = _paso4_analizar_riesgo(df)
    
    # STEP 5: Intelligent Feedback Generation
    score, feedback = _paso5_generar_feedback(metricas, benchmarking, riesgo)
    
    return score, feedback

Step 1: Data Ingestion & Cleaning

def _paso1_ingestar_y_limpiar(trajectory_data: Dict) -> pd.DataFrame:
    movements = trajectory_data["movements"]
    df = pd.DataFrame([{
        "x": m["coordinates"][0],
        "y": m["coordinates"][1],
        "z": m["coordinates"][2] if len(m["coordinates"]) > 2 else 0,
        "event": m["event"],
        "timestamp": m["timestamp"]
    } for m in movements])
    
    # Sort by timestamp and calculate relative time
    df = df.sort_values("timestamp").reset_index(drop=True)
    df["t"] = (df["timestamp"] - df["timestamp"].iloc[0]) / 1000.0  # seconds
    df["dt"] = df["t"].diff().fillna(0)
    
    return df

Step 2: Dexterity Metrics

Calculates physics-based surgical performance metrics:
def _paso2_calcular_destreza(df: pd.DataFrame) -> Dict:
    # Calculate position differences
    dx = df["x"].diff().fillna(0)
    dy = df["y"].diff().fillna(0)
    dz = df["z"].diff().fillna(0)
    dist = np.sqrt(dx**2 + dy**2 + dz**2)
    
    # Velocity: v = distance / time
    v = dist / df["dt"].replace(0, np.inf)
    v = v.replace(np.inf, 0)
    
    # Acceleration: a = dv / dt
    a = v.diff() / df["dt"].replace(0, np.inf)
    a = a.replace(np.inf, 0)
    
    # Jerk: j = da / dt (measures smoothness)
    j = a.diff() / df["dt"].replace(0, np.inf)
    j = j.replace(np.inf, 0)
    
    # Movement economy: total distance / direct distance
    total_dist = dist.sum()
    p1 = np.array([df["x"].iloc[0], df["y"].iloc[0], df["z"].iloc[0]])
    p2 = np.array([df["x"].iloc[-1], df["y"].iloc[-1], df["z"].iloc[-1]])
    direct_dist = np.linalg.norm(p2 - p1)
    economia = total_dist / direct_dist if direct_dist > 0 else 1.0
    
    return {
        "economia": economia,        # Lower is better (ideal < 1.2)
        "v_avg": v.mean(),          # Average velocity
        "a_max": a.abs().max(),     # Maximum acceleration
        "j_avg": j.abs().mean(),    # Average jerk (smoothness)
        "total_dist": total_dist,
        "duration": df["t"].iloc[-1]
    }

Step 3: Benchmarking

Compares trajectory against ideal straight-line path:
def _paso3_benchmarking(df: pd.DataFrame) -> Dict:
    p_start = np.array([df["x"].iloc[0], df["y"].iloc[0], df["z"].iloc[0]])
    p_end = np.array([df["x"].iloc[-1], df["y"].iloc[-1], df["z"].iloc[-1]])
    
    def dist_to_line(p, a, b):
        """Calculate perpendicular distance from point to line"""
        if np.all(a == b): return np.linalg.norm(p - a)
        return np.linalg.norm(np.cross(b - a, a - p)) / np.linalg.norm(b - a)
    
    # Calculate deviation for each point
    desviaciones = [
        dist_to_line(np.array([r.x, r.y, r.z]), p_start, p_end)
        for r in df.itertuples()
    ]
    
    return {
        "desviacion_avg": np.mean(desviaciones),
        "precision": max(0, 100 - np.mean(desviaciones) * 10)  # 0-100 scale
    }

Step 4: Risk Analysis

def _paso4_analizar_riesgo(df: pd.DataFrame) -> Dict:
    # Count critical events
    tumor_touches = (df["event"] == "TUMOR_TOUCH").sum()
    hemorrhages = (df["event"] == "HEMORRHAGE").sum()
    
    # Identify problem quadrants
    mid_x = (df["x"].max() + df["x"].min()) / 2
    mid_y = (df["y"].max() + df["y"].min()) / 2
    
    problemas = df[df["event"].isin(["TUMOR_TOUCH", "HEMORRHAGE"])]
    cuadrantes_criticos = []
    if not problemas.empty:
        for p in problemas.itertuples():
            pos = ("Sup" if p.y > mid_y else "Inf") + \
                  ("-Der" if p.x > mid_x else "-Izq")
            if pos not in cuadrantes_criticos:
                cuadrantes_criticos.append(pos)
    
    return {
        "touches": int(tumor_touches),
        "hemorrhages": int(hemorrhages),
        "cuadrantes": cuadrantes_criticos
    }

Step 5: Feedback Generation

def _paso5_generar_feedback(m: Dict, b: Dict, r: Dict) -> Tuple[float, str]:
    # Calculate final score
    score = 100.0
    score -= r["touches"] * 8           # -8 per tumor contact
    score -= r["hemorrhages"] * 15      # -15 per hemorrhage
    if m["economia"] > 1.5: score -= 10  # -10 for inefficient movement
    score = max(0, min(100, score))
    
    # Generate feedback markdown
    status = (
        "🌟 EXCELENTE" if score >= 90 else
        "βœ… BUENO" if score >= 75 else
        "⚠️ MEJORABLE" if score >= 60 else
        "❌ DEFICIENTE"
    )
    
    feedback = f"""
### {status} - Score: {score:.1f}/100

#### 🚨 CRITICAL ALERTS
- Hemorrhages: {r["hemorrhages"]} {"(REVIEW TECHNIQUE)" if r["hemorrhages"] > 0 else "(None)"}
- Tumor Contacts: {r["touches"]}
- Risk Quadrants: {", ".join(r["cuadrantes"]) if r["cuadrantes"] else "None"}

#### πŸ“Š DEXTERITY METRICS
- **Movement Economy:** {m["economia"]:.2f}x (Ideal < 1.2x)
- **Smoothness (Avg Jerk):** {m["j_avg"]:.2f}
- **Precision vs Gold Standard:** {b["precision"]:.1f}%

#### πŸ“ˆ STATISTICS
- **Total Duration:** {m["duration"]:.1f}s
- **Distance Traveled:** {m["total_dist"]:.2f} units
- **Average Velocity:** {m["v_avg"]:.2f} u/s

#### πŸ’‘ RECOMMENDATIONS
"""
    
    # Add specific recommendations
    if r["hemorrhages"] > 0:
        feedback += "- Prioritize vascular control in critical quadrants.\n"
    if m["economia"] > 1.8:
        feedback += "- Plan more direct trajectories to reduce fatigue.\n"
    if b["precision"] < 70:
        feedback += "- Maintain better stability in executing ideal path.\n"
    if score < 80:
        feedback += "- Increase simulator practice to improve motor coordination.\n"
    
    return score, feedback.strip()

WebSocket Client

The AI service listens for new surgery notifications:
class AIWebSocketClient:
    def on_message(self, ws, message):
        data = json.loads(message)
        
        # Check if it's a new surgery notification
        if data.get("event") == "NEW_SURGERY":
            surgery_id = data.get("surgeryId")
            print(f"πŸ”” New surgery detected: {surgery_id}")
            
            # Process in separate thread
            thread = threading.Thread(
                target=self.procesar_cirugia_async,
                args=(surgery_id,)
            )
            thread.daemon = True
            thread.start()
    
    def procesar_cirugia_async(self, surgery_id):
        # 1. Fetch trajectory data
        trajectory_data = self.client.get_trajectory(surgery_id)
        
        # 2. Analyze with pipeline
        score, feedback = run_pipeline(trajectory_data)
        
        # 3. Send results back
        self.client.send_analysis(surgery_id, score, feedback)

Communication Protocols

REST API Endpoints

Authentication

POST /api/v1/auth/login
POST /api/v1/auth/register
GET  /api/v1/auth/me

Surgery Management

GET  /api/v1/surgeries/{id}/trajectory    # Get movement data
POST /api/v1/surgeries/{id}/analysis      # Submit AI analysis

WebSocket Endpoints

Simulation Stream (Surgeons)

ws://backend:8080/ws/simulation?token=<JWT>
Client β†’ Server (Telemetry):
{
  "coordinates": {"x": 10.5, "y": 20.3, "z": 15.7},
  "event": "MOVE",
  "timestamp": "2024-01-15T10:30:00Z"
}
Server β†’ Client (Confirmation):
{
  "status": "SAVED",
  "surgeryId": "550e8400-e29b-41d4-a716-446655440000"
}

AI Notification Channel

ws://backend:8080/ws/ai?token=<JWT>
Server β†’ AI (New Surgery):
{
  "event": "NEW_SURGERY",
  "surgeryId": "550e8400-e29b-41d4-a716-446655440000"
}

Security Architecture

JWT Authentication Flow

Role-Based Access Control

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .authorizeHttpRequests(auth -> auth
                // Public endpoints
                .requestMatchers("/api/v1/auth/**").permitAll()
                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                
                // Surgeon-only endpoints
                .requestMatchers("/api/v1/surgeries/*/trajectory")
                    .hasAnyRole("SURGEON", "IA")
                
                // AI-only endpoints
                .requestMatchers("/api/v1/surgeries/*/analysis")
                    .hasRole("IA")
                
                // All other endpoints require authentication
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
}

Database Schema

Entity Relationship Diagram

Example Queries

// Fetch surgery with all movements
SurgerySession surgery = surgeryRepository.findById(surgeryId)
    .orElseThrow(() -> new SurgeryNotFoundException(surgeryId));

List<Movement> movements = surgery.getMovements(); // Lazy loaded

// Get user's surgeries
List<SurgerySession> surgeries = surgeryRepository
    .findByUserId(userId);

// Get surgeries by status
List<SurgerySession> pending = surgeryRepository
    .findByStatus(SurgeryStatus.PENDING_ANALYSIS);

Performance Considerations

Backend Optimizations

  • Connection Pooling: HikariCP for efficient database connections
  • Lazy Loading: JPA relationships loaded on-demand
  • Caching: Spring Cache for frequently accessed data
  • Async Processing: AI analysis runs in separate threads

Frontend Optimizations

  • Code Splitting: Next.js automatic route-based splitting
  • Tree Shaking: Unused code eliminated in production builds
  • WebGL Optimization: Babylon.js scene optimization
  • State Minimization: Zustand for lightweight state management

WebSocket Optimizations

  • Binary Frames: Efficient data transfer format
  • Compression: WebSocket compression extension
  • Heartbeat: Keep-alive pings to maintain connection
  • Reconnection: Automatic reconnection on disconnect

Deployment Architecture

Development

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Frontend   │────▢│   Backend   │────▢│  H2 Memory  β”‚
β”‚  :3000      β”‚     β”‚   :8080     β”‚     β”‚  Database   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ AI Service  β”‚
                    β”‚  Python     β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Production (Docker)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Docker Compose                      β”‚
β”‚                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Frontend  │───▢│  Backend  │───▢│  PostgreSQL   β”‚  β”‚
β”‚  β”‚  Next.js  β”‚    β”‚Spring Bootβ”‚    β”‚   Database    β”‚  β”‚
β”‚  β”‚  :3000    β”‚    β”‚   :8080   β”‚    β”‚    :5432      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                          β”‚                              β”‚
β”‚                          β–Ό                              β”‚
β”‚                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                         β”‚
β”‚                   β”‚AI Service β”‚                         β”‚
β”‚                   β”‚  Python   β”‚                         β”‚
β”‚                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Next Steps

API Reference

Complete endpoint documentation with examples

Deployment Guide

Deploy Justina to production environments

Features

Explore platform capabilities

User Guide

Learn how to use the platform

Build docs developers (and LLMs) love